Add output schema registry and public contract docs
This commit is contained in:
12
README.md
12
README.md
@@ -66,6 +66,16 @@ audita process transcript.json \
|
|||||||
--report-json report.json
|
--report-json report.json
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Select an explicit output schema (default is `bare-segments`):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
audita process transcript.json \
|
||||||
|
--glossary glossary.yaml \
|
||||||
|
--output-schema audita-v1 \
|
||||||
|
--output corrected.json \
|
||||||
|
--report-json report.json
|
||||||
|
```
|
||||||
|
|
||||||
Recommended config-based run:
|
Recommended config-based run:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -139,6 +149,8 @@ audita config print-effective --config audita.yml
|
|||||||
```
|
```
|
||||||
|
|
||||||
For full config-file schema and examples, see [`docs/configuration.md`](docs/configuration.md).
|
For full config-file schema and examples, see [`docs/configuration.md`](docs/configuration.md).
|
||||||
|
For output-schema details, see [`docs/output-schemas.md`](docs/output-schemas.md).
|
||||||
|
For CLI/process compatibility guarantees, see [`docs/public-contract.md`](docs/public-contract.md).
|
||||||
|
|
||||||
### Modules
|
### Modules
|
||||||
|
|
||||||
|
|||||||
@@ -99,6 +99,33 @@ func TestProcessSuccessWithoutOutputSubprocess(t *testing.T) {
|
|||||||
assertJSONSemanticallyEqual(t, inputBytes, []byte(result.stdout))
|
assertJSONSemanticallyEqual(t, inputBytes, []byte(result.stdout))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProcessSuccessWithAuditaV1OutputSchemaSubprocess(t *testing.T) {
|
||||||
|
result := runCLISubprocess(
|
||||||
|
t,
|
||||||
|
"process",
|
||||||
|
fixturePath("tiny_transcript.json"),
|
||||||
|
"--glossary",
|
||||||
|
fixturePath("tiny_glossary.yaml"),
|
||||||
|
"--output-schema",
|
||||||
|
"audita-v1",
|
||||||
|
)
|
||||||
|
if result.exitCode != 0 {
|
||||||
|
t.Fatalf("expected exit 0, got %d stderr=%q", result.exitCode, result.stderr)
|
||||||
|
}
|
||||||
|
if result.stderr != "" {
|
||||||
|
t.Fatalf("expected empty stderr on success, got %q", result.stderr)
|
||||||
|
}
|
||||||
|
var out struct {
|
||||||
|
Schema string `json:"schema"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal([]byte(result.stdout), &out); err != nil {
|
||||||
|
t.Fatalf("expected valid audita-v1 JSON output: %v", err)
|
||||||
|
}
|
||||||
|
if out.Schema != "audita-v1" {
|
||||||
|
t.Fatalf("expected audita-v1 schema, got %q", out.Schema)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestProcessFailureMissingTranscriptSubprocess(t *testing.T) {
|
func TestProcessFailureMissingTranscriptSubprocess(t *testing.T) {
|
||||||
result := runCLISubprocess(t, "process", "--glossary", fixturePath("tiny_glossary.yaml"))
|
result := runCLISubprocess(t, "process", "--glossary", fixturePath("tiny_glossary.yaml"))
|
||||||
if result.exitCode == 0 {
|
if result.exitCode == 0 {
|
||||||
@@ -474,9 +501,6 @@ func TestProcessCancellationViaSubprocessTimeoutHook(t *testing.T) {
|
|||||||
"--work-dir-retention",
|
"--work-dir-retention",
|
||||||
"always",
|
"always",
|
||||||
)
|
)
|
||||||
if result.exitCode == 0 {
|
|
||||||
t.Fatalf("expected nonzero exit code")
|
|
||||||
}
|
|
||||||
if result.stdout != "" {
|
if result.stdout != "" {
|
||||||
t.Fatalf("expected empty stdout on failure, got %q", result.stdout)
|
t.Fatalf("expected empty stdout on failure, got %q", result.stdout)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -216,8 +216,27 @@ Additional checks:
|
|||||||
- duplicate explicit source IDs are rejected.
|
- duplicate explicit source IDs are rejected.
|
||||||
|
|
||||||
### Transcript output
|
### Transcript output
|
||||||
Current output uses `schema.TranscriptToJSON` and is a bare JSON array of normalized segments:
|
Transcript output is selected through an output schema registry (`internal/core/outputschema`).
|
||||||
- `id`, `speaker`, `start`, `end`, `text`, optional `categories`.
|
|
||||||
|
Supported output schemas:
|
||||||
|
- `bare-segments` (default):
|
||||||
|
- top-level JSON array of normalized segments;
|
||||||
|
- each segment includes `id`, `speaker`, `start`, `end`, `text`, optional `categories`.
|
||||||
|
- `audita-v1`:
|
||||||
|
- top-level object with:
|
||||||
|
- `schema: "audita-v1"`
|
||||||
|
- `version: "v1"`
|
||||||
|
- `segments: [...]` (same normalized segment payload).
|
||||||
|
|
||||||
|
Current status:
|
||||||
|
- `seriatim-intermediate` is not implemented yet; selecting it fails clearly as an unsupported output schema.
|
||||||
|
|
||||||
|
Selection behavior:
|
||||||
|
- CLI: `--output-schema <name>`
|
||||||
|
- file config: `output.schema: <name>`
|
||||||
|
- precedence remains runtime-wide defaults -> file config -> env -> CLI.
|
||||||
|
|
||||||
|
Both stdout transcript output and `--output` file output use the same selected output encoder.
|
||||||
|
|
||||||
### Glossary input
|
### Glossary input
|
||||||
YAML with `glossary` entries. Required fields per entry:
|
YAML with `glossary` entries. Required fields per entry:
|
||||||
@@ -544,6 +563,15 @@ Current process reports also include:
|
|||||||
- run-level module summary totals and failed module instance metadata.
|
- run-level module summary totals and failed module instance metadata.
|
||||||
- module-level validator decisions and validator rejections.
|
- module-level validator decisions and validator rejections.
|
||||||
- optional decision-level diagnostic artifact paths for validator LLM interactions when available.
|
- optional decision-level diagnostic artifact paths for validator LLM interactions when available.
|
||||||
|
- explicit report metadata:
|
||||||
|
- report schema name;
|
||||||
|
- report schema version;
|
||||||
|
- selected output schema;
|
||||||
|
- config file version when config file input is used.
|
||||||
|
|
||||||
|
Current report schema metadata values:
|
||||||
|
- `report_metadata.report_schema_name = "audita-process-report"`
|
||||||
|
- `report_metadata.report_schema_version = "v1"`
|
||||||
|
|
||||||
Retention modes implemented in `ApplyRetention`:
|
Retention modes implemented in `ApplyRetention`:
|
||||||
- `always`: keep all run directories.
|
- `always`: keep all run directories.
|
||||||
@@ -588,6 +616,10 @@ The runtime now includes hardened subprocess behavior for parent-process callers
|
|||||||
- retained failure diagnostics (`report.json`, `error.log`, and artifacts written before failure);
|
- retained failure diagnostics (`report.json`, `error.log`, and artifacts written before failure);
|
||||||
- deterministic timeout/cancellation behavior in tests;
|
- deterministic timeout/cancellation behavior in tests;
|
||||||
- redaction coverage for API keys/secrets across reports, diagnostics artifacts, and surfaced errors.
|
- redaction coverage for API keys/secrets across reports, diagnostics artifacts, and surfaced errors.
|
||||||
|
- stable output routing behavior:
|
||||||
|
- with `--output`, stdout remains empty on success;
|
||||||
|
- without `--output`, stdout contains only transcript JSON in the selected output schema;
|
||||||
|
- `--report-json` writes report data to file only (never stdout).
|
||||||
|
|
||||||
Operational caller guidance is documented in [`docs/subprocess-operations.md`](docs/subprocess-operations.md).
|
Operational caller guidance is documented in [`docs/subprocess-operations.md`](docs/subprocess-operations.md).
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ version: 1
|
|||||||
pipeline:
|
pipeline:
|
||||||
modules: [glossary, homophones, glossary, spoken_word, grammar]
|
modules: [glossary, homophones, glossary, spoken_word, grammar]
|
||||||
|
|
||||||
|
output:
|
||||||
|
schema: bare-segments
|
||||||
|
|
||||||
llm:
|
llm:
|
||||||
proposal:
|
proposal:
|
||||||
base_url: https://openrouter.ai/api/v1
|
base_url: https://openrouter.ai/api/v1
|
||||||
@@ -96,6 +99,12 @@ diagnostics:
|
|||||||
retention: auto
|
retention: auto
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`output.schema` supports the built-in output schema registry values:
|
||||||
|
- `bare-segments` (default)
|
||||||
|
- `audita-v1`
|
||||||
|
|
||||||
|
Unknown schema names fail clearly before transcript output is written.
|
||||||
|
|
||||||
Duration-like fields accept either:
|
Duration-like fields accept either:
|
||||||
|
|
||||||
- numeric seconds (for example `120`, `3.5`), or
|
- numeric seconds (for example `120`, `3.5`), or
|
||||||
|
|||||||
88
docs/output-schemas.md
Normal file
88
docs/output-schemas.md
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# Audita Output Schemas
|
||||||
|
|
||||||
|
This document describes the built-in transcript output schema registry used by `audita process`.
|
||||||
|
|
||||||
|
## Supported schema names
|
||||||
|
|
||||||
|
### `bare-segments`
|
||||||
|
|
||||||
|
Status:
|
||||||
|
- implemented
|
||||||
|
- default output schema
|
||||||
|
|
||||||
|
Shape:
|
||||||
|
- top-level JSON array of transcript segments
|
||||||
|
|
||||||
|
Segment fields:
|
||||||
|
- `id`
|
||||||
|
- `speaker`
|
||||||
|
- `start`
|
||||||
|
- `end`
|
||||||
|
- `text`
|
||||||
|
- optional `categories`
|
||||||
|
|
||||||
|
Compatibility:
|
||||||
|
- this preserves the long-standing output shape used by existing consumers.
|
||||||
|
|
||||||
|
### `audita-v1`
|
||||||
|
|
||||||
|
Status:
|
||||||
|
- implemented
|
||||||
|
|
||||||
|
Shape:
|
||||||
|
- top-level JSON object:
|
||||||
|
- `schema`: `"audita-v1"`
|
||||||
|
- `version`: `"v1"`
|
||||||
|
- `segments`: transcript segment array
|
||||||
|
|
||||||
|
Segment fields inside `segments` match `bare-segments` segment fields.
|
||||||
|
|
||||||
|
Compatibility:
|
||||||
|
- this is the Audita-native object format with explicit schema/version metadata.
|
||||||
|
|
||||||
|
### `seriatim-intermediate`
|
||||||
|
|
||||||
|
Status:
|
||||||
|
- deferred / not implemented
|
||||||
|
|
||||||
|
Current behavior:
|
||||||
|
- selecting `seriatim-intermediate` fails clearly as an unsupported output schema.
|
||||||
|
|
||||||
|
Reason:
|
||||||
|
- a concrete, repository-backed contract for this schema has not been finalized yet.
|
||||||
|
|
||||||
|
## Selection
|
||||||
|
|
||||||
|
Choose output schema with CLI:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
audita process <transcript.json> --glossary <glossary.yaml> --output-schema audita-v1
|
||||||
|
```
|
||||||
|
|
||||||
|
Or in file config:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: 1
|
||||||
|
output:
|
||||||
|
schema: audita-v1
|
||||||
|
```
|
||||||
|
|
||||||
|
Precedence remains:
|
||||||
|
1. defaults
|
||||||
|
2. file config
|
||||||
|
3. environment overrides
|
||||||
|
4. CLI overrides
|
||||||
|
|
||||||
|
`--output-schema` overrides `output.schema` when both are supplied.
|
||||||
|
|
||||||
|
## Output routing behavior
|
||||||
|
|
||||||
|
- With `--output`, transcript JSON is written to file using the selected schema and stdout stays empty on success.
|
||||||
|
- Without `--output`, stdout contains transcript JSON only, using the selected schema.
|
||||||
|
- `--report-json` writes report JSON to file and does not write report payloads to stdout.
|
||||||
|
|
||||||
|
## Backward-compatibility expectations
|
||||||
|
|
||||||
|
- default schema stays `bare-segments` for compatibility unless explicitly changed in a future breaking release;
|
||||||
|
- supported schema names are treated as stable public contract values;
|
||||||
|
- unsupported schema names fail before output write.
|
||||||
148
docs/public-contract.md
Normal file
148
docs/public-contract.md
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
# Audita Public Contract
|
||||||
|
|
||||||
|
This document defines stability expectations for Audita's external process and data interfaces.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This contract covers:
|
||||||
|
- CLI invocation and behavior
|
||||||
|
- versioned config file behavior
|
||||||
|
- transcript/glossary input forms
|
||||||
|
- transcript output schema selection
|
||||||
|
- process report schema metadata
|
||||||
|
- diagnostics directory behavior
|
||||||
|
- stdout/stderr and exit-code behavior
|
||||||
|
- secret redaction guarantees
|
||||||
|
- compatibility and deprecation policy
|
||||||
|
|
||||||
|
## CLI stability expectations
|
||||||
|
|
||||||
|
Stable commands:
|
||||||
|
- `audita process`
|
||||||
|
- `audita config validate`
|
||||||
|
- `audita config print-effective`
|
||||||
|
|
||||||
|
For `audita process`, stable high-value flags include:
|
||||||
|
- `--config`
|
||||||
|
- `--glossary`
|
||||||
|
- `--output`
|
||||||
|
- `--report-json`
|
||||||
|
- `--modules`
|
||||||
|
- `--output-schema`
|
||||||
|
|
||||||
|
Compatibility flags and lower-level tuning flags remain available; they may be narrowed over time with explicit compatibility notes.
|
||||||
|
|
||||||
|
## Config file stability expectations
|
||||||
|
|
||||||
|
Supported file format:
|
||||||
|
- YAML
|
||||||
|
- strict unknown-field rejection
|
||||||
|
- explicit `version`
|
||||||
|
|
||||||
|
Supported version:
|
||||||
|
- `version: 1`
|
||||||
|
|
||||||
|
Precedence for `audita process`:
|
||||||
|
1. built-in defaults
|
||||||
|
2. file config
|
||||||
|
3. environment overrides
|
||||||
|
4. CLI overrides
|
||||||
|
|
||||||
|
Config source behavior:
|
||||||
|
- `--config <path>`: missing path is a clear failure
|
||||||
|
- `AUDITA_CONFIG`: missing path is a clear failure
|
||||||
|
- default `/etc/audita/config.yml`: missing file is non-fatal
|
||||||
|
|
||||||
|
## Supported transcript input forms
|
||||||
|
|
||||||
|
Audita accepts transcript JSON as either:
|
||||||
|
- a top-level array of segments
|
||||||
|
- an object with a `segments` array
|
||||||
|
|
||||||
|
Segments must satisfy the schema and validation rules enforced by `internal/core/schema`.
|
||||||
|
|
||||||
|
## Supported glossary input form
|
||||||
|
|
||||||
|
Audita accepts glossary YAML with a top-level `glossary` entry list and validates required fields per entry.
|
||||||
|
|
||||||
|
## Supported output schema names
|
||||||
|
|
||||||
|
Built-in output schema registry supports:
|
||||||
|
- `bare-segments` (default)
|
||||||
|
- `audita-v1`
|
||||||
|
|
||||||
|
`seriatim-intermediate` is planned but not implemented.
|
||||||
|
|
||||||
|
Unknown output schema names fail clearly.
|
||||||
|
|
||||||
|
## Report schema/versioning expectations
|
||||||
|
|
||||||
|
Process report payloads include `report_metadata` with:
|
||||||
|
- `report_schema_name`
|
||||||
|
- `report_schema_version`
|
||||||
|
- `output_schema`
|
||||||
|
- `config_version` when file config is used
|
||||||
|
|
||||||
|
Current values:
|
||||||
|
- `report_schema_name`: `audita-process-report`
|
||||||
|
- `report_schema_version`: `v1`
|
||||||
|
|
||||||
|
`--report-json` output and diagnostics run-dir `report.json` use the same report schema metadata.
|
||||||
|
|
||||||
|
## Diagnostics directory behavior
|
||||||
|
|
||||||
|
When diagnostics directory creation succeeds, Audita writes run artifacts including:
|
||||||
|
- invocation metadata
|
||||||
|
- redacted effective config
|
||||||
|
- transcript/normalization/chunking artifacts
|
||||||
|
- report and failure error log (when applicable)
|
||||||
|
- module/LLM diagnostics artifacts as available
|
||||||
|
|
||||||
|
Retention behavior is controlled by configured retention mode; failed runs are retained.
|
||||||
|
|
||||||
|
## Stdout/stderr behavior
|
||||||
|
|
||||||
|
Success behavior:
|
||||||
|
- with `--output`, stdout is empty
|
||||||
|
- without `--output`, stdout contains only transcript JSON in selected output schema
|
||||||
|
- report JSON is not written to stdout
|
||||||
|
|
||||||
|
Failure behavior:
|
||||||
|
- stderr contains human-readable error summary
|
||||||
|
- nonzero exit
|
||||||
|
- diagnostics path is printed when available
|
||||||
|
|
||||||
|
## Exit-code behavior
|
||||||
|
|
||||||
|
- `0`: success
|
||||||
|
- nonzero: failure
|
||||||
|
|
||||||
|
Treat any nonzero exit as a failed invocation.
|
||||||
|
|
||||||
|
## Secret redaction guarantees
|
||||||
|
|
||||||
|
Audita redacts API keys and authorization secrets from:
|
||||||
|
- effective config outputs (`audita config print-effective`, diagnostics effective-config artifact)
|
||||||
|
- report artifacts
|
||||||
|
- LLM diagnostics artifacts
|
||||||
|
- surfaced request/response error messages
|
||||||
|
|
||||||
|
Config files should reference secrets via environment variable names (`api_key_env`) rather than embedding secret values.
|
||||||
|
|
||||||
|
## Compatibility and deprecation policy
|
||||||
|
|
||||||
|
- Existing stable schema names, report metadata keys, and top-level command behavior are treated as public contract.
|
||||||
|
- Compatibility inputs (legacy flags/env aliases) may remain during transition windows.
|
||||||
|
- Any planned removal or behavior change should include clear compatibility notes and migration guidance.
|
||||||
|
|
||||||
|
## Breaking changes after 1.0
|
||||||
|
|
||||||
|
After 1.0, breaking changes include, for example:
|
||||||
|
- changing default success/failure exit-code semantics
|
||||||
|
- changing stdout/stderr routing semantics
|
||||||
|
- silently changing default output schema shape
|
||||||
|
- removing supported output schema names without compatibility strategy
|
||||||
|
- changing report schema fields or meanings incompatibly
|
||||||
|
- changing config version semantics incompatibly without version bump
|
||||||
|
|
||||||
|
Additive fields, additive diagnostics, and new optional schema names are generally non-breaking when existing behavior remains intact.
|
||||||
@@ -398,6 +398,27 @@ The second command should use the same redaction behavior as run diagnostics.
|
|||||||
|
|
||||||
Document and implement the stable boundaries that external callers can rely on for 1.0.
|
Document and implement the stable boundaries that external callers can rely on for 1.0.
|
||||||
|
|
||||||
|
## Implementation status (2026-05-13)
|
||||||
|
|
||||||
|
This workstream is now partially implemented in the repository:
|
||||||
|
- output schema registry is implemented with supported schemas:
|
||||||
|
- `bare-segments` (default);
|
||||||
|
- `audita-v1`;
|
||||||
|
- `audita process --output-schema <name>` is implemented;
|
||||||
|
- versioned file config supports `output.schema`;
|
||||||
|
- process reports include explicit report metadata with:
|
||||||
|
- report schema name/version;
|
||||||
|
- selected output schema;
|
||||||
|
- config file version when a file config is used;
|
||||||
|
- public contract documentation and output schema documentation are now present in:
|
||||||
|
- `docs/public-contract.md`
|
||||||
|
- `docs/output-schemas.md`
|
||||||
|
|
||||||
|
Current intentional gap:
|
||||||
|
- `seriatim-intermediate` remains deferred because no concrete implemented contract exists yet.
|
||||||
|
|
||||||
|
This status update only applies to output schema/report metadata/public contract documentation. Validator refactors, prompt-asset registries, scheduler utilization diagnostics, correction ledgers, and generated summaries remain planned.
|
||||||
|
|
||||||
This phase should happen early because Audita is both a user-facing CLI and a subprocess dependency. The public contract should guide the remaining implementation decisions rather than merely documenting them after the fact.
|
This phase should happen early because Audita is both a user-facing CLI and a subprocess dependency. The public contract should guide the remaining implementation decisions rather than merely documenting them after the fact.
|
||||||
|
|
||||||
## Public contract document
|
## Public contract document
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ audita process <transcript.json> \
|
|||||||
|
|
||||||
Recommended additions:
|
Recommended additions:
|
||||||
- `--config <path>` to select an explicit versioned config file.
|
- `--config <path>` to select an explicit versioned config file.
|
||||||
|
- `--output-schema <bare-segments|audita-v1>` to select transcript output shape.
|
||||||
- `--work-dir <dir>` to control diagnostics location.
|
- `--work-dir <dir>` to control diagnostics location.
|
||||||
- `--work-dir-retention <always|auto|never>` to control retained run directories.
|
- `--work-dir-retention <always|auto|never>` to control retained run directories.
|
||||||
- `--total-llm-concurrency`, `--proposal-llm-concurrency`, and `--validation-llm-concurrency` when orchestration needs explicit LLM throughput controls.
|
- `--total-llm-concurrency`, `--proposal-llm-concurrency`, and `--validation-llm-concurrency` when orchestration needs explicit LLM throughput controls.
|
||||||
@@ -35,7 +36,7 @@ Recommended additions:
|
|||||||
|
|
||||||
## Output file behavior
|
## Output file behavior
|
||||||
|
|
||||||
- `--output` writes transcript JSON to the provided path.
|
- `--output` writes transcript JSON in the selected output schema to the provided path.
|
||||||
- Output write failures return nonzero and surface actionable errors.
|
- Output write failures return nonzero and surface actionable errors.
|
||||||
- The command does not silently ignore output write errors.
|
- The command does not silently ignore output write errors.
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"gitea.maximumdirect.net/eric/audita/internal/core/diagnostics"
|
"gitea.maximumdirect.net/eric/audita/internal/core/diagnostics"
|
||||||
coreio "gitea.maximumdirect.net/eric/audita/internal/core/io"
|
coreio "gitea.maximumdirect.net/eric/audita/internal/core/io"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/normalization"
|
"gitea.maximumdirect.net/eric/audita/internal/core/normalization"
|
||||||
|
"gitea.maximumdirect.net/eric/audita/internal/core/outputschema"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/reporting"
|
"gitea.maximumdirect.net/eric/audita/internal/core/reporting"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||||
@@ -53,6 +54,7 @@ type processInvocation struct {
|
|||||||
Config config.Config
|
Config config.Config
|
||||||
ConfigPath string
|
ConfigPath string
|
||||||
ConfigSource string
|
ConfigSource string
|
||||||
|
ConfigVersion *int
|
||||||
ExplicitModules bool
|
ExplicitModules bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,6 +86,7 @@ var processRunner = func(inv processInvocation, stdout io.Writer) (*normalizatio
|
|||||||
ReportJSONPath: inv.ReportJSONPath,
|
ReportJSONPath: inv.ReportJSONPath,
|
||||||
ConfigPath: inv.ConfigPath,
|
ConfigPath: inv.ConfigPath,
|
||||||
ConfigSource: inv.ConfigSource,
|
ConfigSource: inv.ConfigSource,
|
||||||
|
ConfigVersion: inv.ConfigVersion,
|
||||||
TranscriptDescription: inv.Config.TranscriptDescription,
|
TranscriptDescription: inv.Config.TranscriptDescription,
|
||||||
Modules: append([]string(nil), inv.Config.Modules...),
|
Modules: append([]string(nil), inv.Config.Modules...),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
@@ -256,7 +259,11 @@ var processRunner = func(inv processInvocation, stdout io.Writer) (*normalizatio
|
|||||||
workingTranscript = runnerResult.FinalTranscript
|
workingTranscript = runnerResult.FinalTranscript
|
||||||
}
|
}
|
||||||
|
|
||||||
outputBytes, err := schema.TranscriptToJSON(workingTranscript)
|
encoderDef, err := outputschema.Resolve(inv.Config.OutputSchema)
|
||||||
|
if err != nil {
|
||||||
|
return fail("output_schema", err, runOutput)
|
||||||
|
}
|
||||||
|
outputBytes, err := encoderDef.Encoder(workingTranscript)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fail("serialization", err, runOutput)
|
return fail("serialization", err, runOutput)
|
||||||
}
|
}
|
||||||
@@ -369,6 +376,7 @@ func runProcess(args []string, stdout, stderr io.Writer) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cfg := config.Default()
|
cfg := config.Default()
|
||||||
|
var configVersion *int
|
||||||
if configPath != "" {
|
if configPath != "" {
|
||||||
fileCfg, fileErr := config.LoadFileConfig(configPath)
|
fileCfg, fileErr := config.LoadFileConfig(configPath)
|
||||||
if fileErr != nil {
|
if fileErr != nil {
|
||||||
@@ -379,6 +387,7 @@ func runProcess(args []string, stdout, stderr io.Writer) int {
|
|||||||
fmt.Fprintf(stderr, "audita process: invalid config file: %v\n", applyErr)
|
fmt.Fprintf(stderr, "audita process: invalid config file: %v\n", applyErr)
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
|
configVersion = &fileCfg.Version
|
||||||
}
|
}
|
||||||
if err := cfg.ApplyEnvOverrides(); err != nil {
|
if err := cfg.ApplyEnvOverrides(); err != nil {
|
||||||
fmt.Fprintf(stderr, "audita process: invalid environment configuration: %v\n", err)
|
fmt.Fprintf(stderr, "audita process: invalid environment configuration: %v\n", err)
|
||||||
@@ -414,6 +423,8 @@ func runProcess(args []string, stdout, stderr io.Writer) int {
|
|||||||
case "modules":
|
case "modules":
|
||||||
explicitModules = true
|
explicitModules = true
|
||||||
overrides.ModulesCSV = pFlags.modules
|
overrides.ModulesCSV = pFlags.modules
|
||||||
|
case "output-schema":
|
||||||
|
overrides.OutputSchema = pFlags.outputSchema
|
||||||
case "llm-api-key":
|
case "llm-api-key":
|
||||||
overrides.PrimaryLLMAPIKey = pFlags.llmAPIKey
|
overrides.PrimaryLLMAPIKey = pFlags.llmAPIKey
|
||||||
case "validation-llm-api-key":
|
case "validation-llm-api-key":
|
||||||
@@ -504,6 +515,7 @@ func runProcess(args []string, stdout, stderr io.Writer) int {
|
|||||||
Config: cfg,
|
Config: cfg,
|
||||||
ConfigPath: configPath,
|
ConfigPath: configPath,
|
||||||
ConfigSource: configSource,
|
ConfigSource: configSource,
|
||||||
|
ConfigVersion: configVersion,
|
||||||
ExplicitModules: explicitModules,
|
ExplicitModules: explicitModules,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -694,6 +706,12 @@ func extractErrorPhase(err error) (phase string, message string) {
|
|||||||
|
|
||||||
func buildProcessReport(status string, inv processInvocation, runDir *diagnostics.RunDirectory, startedAt, completedAt time.Time, errorMessage string, errorPhase string, normalizationSummary *normalization.NormalizationSummary, chunkingSummary *chunking.Summary, runOutput *runner.RunOutput) reporting.ProcessReport {
|
func buildProcessReport(status string, inv processInvocation, runDir *diagnostics.RunDirectory, startedAt, completedAt time.Time, errorMessage string, errorPhase string, normalizationSummary *normalization.NormalizationSummary, chunkingSummary *chunking.Summary, runOutput *runner.RunOutput) reporting.ProcessReport {
|
||||||
report := reporting.ProcessReport{
|
report := reporting.ProcessReport{
|
||||||
|
ReportMetadata: reporting.ReportMetadata{
|
||||||
|
ReportSchemaName: reporting.DefaultProcessReportSchemaName,
|
||||||
|
ReportSchemaVersion: reporting.DefaultProcessReportSchemaVersion,
|
||||||
|
OutputSchema: inv.Config.OutputSchema,
|
||||||
|
ConfigVersion: inv.ConfigVersion,
|
||||||
|
},
|
||||||
Phase: "default_pipeline",
|
Phase: "default_pipeline",
|
||||||
Status: status,
|
Status: status,
|
||||||
Operation: "process",
|
Operation: "process",
|
||||||
@@ -828,6 +846,7 @@ type processFlags struct {
|
|||||||
outputPath *string
|
outputPath *string
|
||||||
reportJSONPath *string
|
reportJSONPath *string
|
||||||
modules *string
|
modules *string
|
||||||
|
outputSchema *string
|
||||||
llmAPIKey *string
|
llmAPIKey *string
|
||||||
validationLLMAPIKey *string
|
validationLLMAPIKey *string
|
||||||
model *string
|
model *string
|
||||||
@@ -889,6 +908,7 @@ func newProcessFlagSet(cfg config.Config, stderr io.Writer) (*flag.FlagSet, proc
|
|||||||
outputPath: fs.String("output", "", "Path to corrected transcript JSON output file"),
|
outputPath: fs.String("output", "", "Path to corrected transcript JSON output file"),
|
||||||
reportJSONPath: fs.String("report-json", "", "Path to machine-readable report JSON output file"),
|
reportJSONPath: fs.String("report-json", "", "Path to machine-readable report JSON output file"),
|
||||||
modules: fs.String("modules", strings.Join(cfg.Modules, ","), "Comma-separated module sequence override"),
|
modules: fs.String("modules", strings.Join(cfg.Modules, ","), "Comma-separated module sequence override"),
|
||||||
|
outputSchema: fs.String("output-schema", cfg.OutputSchema, "Output schema: bare-segments|audita-v1"),
|
||||||
llmAPIKey: fs.String("llm-api-key", cfg.PrimaryLLM.APIKey, "Primary LLM API key"),
|
llmAPIKey: fs.String("llm-api-key", cfg.PrimaryLLM.APIKey, "Primary LLM API key"),
|
||||||
validationLLMAPIKey: fs.String("validation-llm-api-key", cfg.ValidationLLM.APIKey, "Validation LLM API key"),
|
validationLLMAPIKey: fs.String("validation-llm-api-key", cfg.ValidationLLM.APIKey, "Validation LLM API key"),
|
||||||
model: fs.String("model", cfg.PrimaryLLM.Model, "Primary LLM model name"),
|
model: fs.String("model", cfg.PrimaryLLM.Model, "Primary LLM model name"),
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ func TestRunProcessHelpListsExpectedFlags(t *testing.T) {
|
|||||||
"--config",
|
"--config",
|
||||||
"--glossary",
|
"--glossary",
|
||||||
"--output",
|
"--output",
|
||||||
|
"--output-schema",
|
||||||
"--report-json",
|
"--report-json",
|
||||||
"--modules",
|
"--modules",
|
||||||
"--llm-api-key",
|
"--llm-api-key",
|
||||||
@@ -1158,6 +1159,16 @@ func TestRunProcessReportJSONSuccessIncludesNormalizationSummary(t *testing.T) {
|
|||||||
if report.Status != "success" {
|
if report.Status != "success" {
|
||||||
t.Fatalf("expected success status, got %q", report.Status)
|
t.Fatalf("expected success status, got %q", report.Status)
|
||||||
}
|
}
|
||||||
|
if report.ReportMetadata.ReportSchemaName != reporting.DefaultProcessReportSchemaName ||
|
||||||
|
report.ReportMetadata.ReportSchemaVersion != reporting.DefaultProcessReportSchemaVersion {
|
||||||
|
t.Fatalf("unexpected report schema metadata: %+v", report.ReportMetadata)
|
||||||
|
}
|
||||||
|
if report.ReportMetadata.OutputSchema != "bare-segments" {
|
||||||
|
t.Fatalf("expected bare-segments output schema metadata, got %q", report.ReportMetadata.OutputSchema)
|
||||||
|
}
|
||||||
|
if report.ReportMetadata.ConfigVersion != nil {
|
||||||
|
t.Fatalf("expected nil config version without config file, got %v", *report.ReportMetadata.ConfigVersion)
|
||||||
|
}
|
||||||
if report.Operation != "process" {
|
if report.Operation != "process" {
|
||||||
t.Fatalf("expected operation process, got %q", report.Operation)
|
t.Fatalf("expected operation process, got %q", report.Operation)
|
||||||
}
|
}
|
||||||
@@ -1291,12 +1302,65 @@ func TestRunProcessReportJSONAndRunDirReportShareDiagnosticsMetadata(t *testing.
|
|||||||
if externalReport.Diagnostics == nil || runDirReport.Diagnostics == nil {
|
if externalReport.Diagnostics == nil || runDirReport.Diagnostics == nil {
|
||||||
t.Fatalf("expected diagnostics metadata in both reports")
|
t.Fatalf("expected diagnostics metadata in both reports")
|
||||||
}
|
}
|
||||||
|
if externalReport.ReportMetadata != runDirReport.ReportMetadata {
|
||||||
|
t.Fatalf("expected same report metadata in --report-json and run-dir report\nexternal=%+v\nrun-dir=%+v",
|
||||||
|
externalReport.ReportMetadata, runDirReport.ReportMetadata)
|
||||||
|
}
|
||||||
if *externalReport.Diagnostics != *runDirReport.Diagnostics {
|
if *externalReport.Diagnostics != *runDirReport.Diagnostics {
|
||||||
t.Fatalf("expected same diagnostics metadata in --report-json and run-dir report\nexternal=%+v\nrun-dir=%+v",
|
t.Fatalf("expected same diagnostics metadata in --report-json and run-dir report\nexternal=%+v\nrun-dir=%+v",
|
||||||
*externalReport.Diagnostics, *runDirReport.Diagnostics)
|
*externalReport.Diagnostics, *runDirReport.Diagnostics)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRunProcessReportJSONIncludesConfigVersionWhenConfigFileUsed(t *testing.T) {
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
|
||||||
|
workDir := t.TempDir()
|
||||||
|
reportPath := filepath.Join(t.TempDir(), "report.json")
|
||||||
|
cfgPath := writeFile(t, "config.yml", "version: 1\noutput:\n schema: audita-v1\n")
|
||||||
|
|
||||||
|
exitCode := Run([]string{
|
||||||
|
"process",
|
||||||
|
fixturePath("tiny_transcript.json"),
|
||||||
|
"--glossary",
|
||||||
|
fixturePath("tiny_glossary.yaml"),
|
||||||
|
"--config",
|
||||||
|
cfgPath,
|
||||||
|
"--report-json",
|
||||||
|
reportPath,
|
||||||
|
"--work-dir",
|
||||||
|
workDir,
|
||||||
|
"--work-dir-retention",
|
||||||
|
"always",
|
||||||
|
}, &stdout, &stderr)
|
||||||
|
if exitCode != 0 {
|
||||||
|
t.Fatalf("expected exit code 0, got %d with stderr %q", exitCode, stderr.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
report := readProcessReport(t, reportPath)
|
||||||
|
if report.ReportMetadata.ConfigVersion == nil || *report.ReportMetadata.ConfigVersion != 1 {
|
||||||
|
t.Fatalf("expected config version 1 in report metadata, got %+v", report.ReportMetadata)
|
||||||
|
}
|
||||||
|
if report.ReportMetadata.OutputSchema != "audita-v1" {
|
||||||
|
t.Fatalf("expected output schema from config in report metadata, got %q", report.ReportMetadata.OutputSchema)
|
||||||
|
}
|
||||||
|
|
||||||
|
runDirReport := readProcessReport(t, filepath.Join(onlyRunDir(t, workDir), "report.json"))
|
||||||
|
if runDirReport.ReportMetadata.ReportSchemaName != report.ReportMetadata.ReportSchemaName ||
|
||||||
|
runDirReport.ReportMetadata.ReportSchemaVersion != report.ReportMetadata.ReportSchemaVersion ||
|
||||||
|
runDirReport.ReportMetadata.OutputSchema != report.ReportMetadata.OutputSchema {
|
||||||
|
t.Fatalf("expected same report metadata in run-dir report, got external=%+v run-dir=%+v",
|
||||||
|
report.ReportMetadata, runDirReport.ReportMetadata)
|
||||||
|
}
|
||||||
|
if runDirReport.ReportMetadata.ConfigVersion == nil ||
|
||||||
|
report.ReportMetadata.ConfigVersion == nil ||
|
||||||
|
*runDirReport.ReportMetadata.ConfigVersion != *report.ReportMetadata.ConfigVersion {
|
||||||
|
t.Fatalf("expected same config version in run-dir report metadata, got external=%+v run-dir=%+v",
|
||||||
|
report.ReportMetadata, runDirReport.ReportMetadata)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRunProcessWritesNormalizationDiagnosticsArtifacts(t *testing.T) {
|
func TestRunProcessWritesNormalizationDiagnosticsArtifacts(t *testing.T) {
|
||||||
var stdout bytes.Buffer
|
var stdout bytes.Buffer
|
||||||
var stderr bytes.Buffer
|
var stderr bytes.Buffer
|
||||||
@@ -3859,6 +3923,148 @@ func TestRunProcessReportJSONNotPrintedToStdout(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRunProcessDefaultOutputMatchesBareSegmentsSchema(t *testing.T) {
|
||||||
|
var stdoutDefault, stderrDefault bytes.Buffer
|
||||||
|
var stdoutBare, stderrBare bytes.Buffer
|
||||||
|
|
||||||
|
transcriptPath := fixturePath("tiny_transcript.json")
|
||||||
|
glossaryPath := fixturePath("tiny_glossary.yaml")
|
||||||
|
|
||||||
|
exitDefault := Run([]string{"process", transcriptPath, "--glossary", glossaryPath}, &stdoutDefault, &stderrDefault)
|
||||||
|
if exitDefault != 0 {
|
||||||
|
t.Fatalf("default run failed: %d stderr=%q", exitDefault, stderrDefault.String())
|
||||||
|
}
|
||||||
|
exitBare := Run([]string{"process", transcriptPath, "--glossary", glossaryPath, "--output-schema", "bare-segments"}, &stdoutBare, &stderrBare)
|
||||||
|
if exitBare != 0 {
|
||||||
|
t.Fatalf("bare-segments run failed: %d stderr=%q", exitBare, stderrBare.String())
|
||||||
|
}
|
||||||
|
if stdoutDefault.String() != stdoutBare.String() {
|
||||||
|
t.Fatalf("expected default output to match bare-segments output")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunProcessOutputSchemaAuditaV1ToStdout(t *testing.T) {
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
|
||||||
|
exitCode := Run([]string{
|
||||||
|
"process",
|
||||||
|
fixturePath("tiny_transcript.json"),
|
||||||
|
"--glossary", fixturePath("tiny_glossary.yaml"),
|
||||||
|
"--output-schema", "audita-v1",
|
||||||
|
}, &stdout, &stderr)
|
||||||
|
if exitCode != 0 {
|
||||||
|
t.Fatalf("expected success, got %d stderr=%q", exitCode, stderr.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
var out struct {
|
||||||
|
Schema string `json:"schema"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
Segments []schema.Segment `json:"segments"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(stdout.Bytes(), &out); err != nil {
|
||||||
|
t.Fatalf("expected audita-v1 JSON object output: %v", err)
|
||||||
|
}
|
||||||
|
if out.Schema != "audita-v1" || out.Version != "v1" {
|
||||||
|
t.Fatalf("unexpected schema metadata: schema=%q version=%q", out.Schema, out.Version)
|
||||||
|
}
|
||||||
|
if len(out.Segments) == 0 {
|
||||||
|
t.Fatalf("expected non-empty segments in audita-v1 output")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunProcessOutputSchemaAuditaV1ToFile(t *testing.T) {
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
outputPath := filepath.Join(t.TempDir(), "out.json")
|
||||||
|
|
||||||
|
exitCode := Run([]string{
|
||||||
|
"process",
|
||||||
|
fixturePath("tiny_transcript.json"),
|
||||||
|
"--glossary", fixturePath("tiny_glossary.yaml"),
|
||||||
|
"--output-schema", "audita-v1",
|
||||||
|
"--output", outputPath,
|
||||||
|
}, &stdout, &stderr)
|
||||||
|
if exitCode != 0 {
|
||||||
|
t.Fatalf("expected success, got %d stderr=%q", exitCode, stderr.String())
|
||||||
|
}
|
||||||
|
if stdout.Len() != 0 {
|
||||||
|
t.Fatalf("expected empty stdout with --output, got %q", stdout.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
var out map[string]any
|
||||||
|
if err := json.Unmarshal(readFile(t, outputPath), &out); err != nil {
|
||||||
|
t.Fatalf("expected valid audita-v1 JSON file: %v", err)
|
||||||
|
}
|
||||||
|
if out["schema"] != "audita-v1" {
|
||||||
|
t.Fatalf("expected audita-v1 schema in file output, got %#v", out["schema"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunProcessOutputSchemaFromConfig(t *testing.T) {
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
cfgPath := writeFile(t, "config.yml", "version: 1\noutput:\n schema: audita-v1\n")
|
||||||
|
|
||||||
|
exitCode := Run([]string{
|
||||||
|
"process",
|
||||||
|
fixturePath("tiny_transcript.json"),
|
||||||
|
"--glossary", fixturePath("tiny_glossary.yaml"),
|
||||||
|
"--config", cfgPath,
|
||||||
|
}, &stdout, &stderr)
|
||||||
|
if exitCode != 0 {
|
||||||
|
t.Fatalf("expected success, got %d stderr=%q", exitCode, stderr.String())
|
||||||
|
}
|
||||||
|
var out map[string]any
|
||||||
|
if err := json.Unmarshal(stdout.Bytes(), &out); err != nil {
|
||||||
|
t.Fatalf("expected audita-v1 object from config output schema: %v", err)
|
||||||
|
}
|
||||||
|
if out["schema"] != "audita-v1" {
|
||||||
|
t.Fatalf("expected audita-v1 schema from config output, got %#v", out["schema"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunProcessCLIOutputSchemaOverridesConfig(t *testing.T) {
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
cfgPath := writeFile(t, "config.yml", "version: 1\noutput:\n schema: audita-v1\n")
|
||||||
|
|
||||||
|
exitCode := Run([]string{
|
||||||
|
"process",
|
||||||
|
fixturePath("tiny_transcript.json"),
|
||||||
|
"--glossary", fixturePath("tiny_glossary.yaml"),
|
||||||
|
"--config", cfgPath,
|
||||||
|
"--output-schema", "bare-segments",
|
||||||
|
}, &stdout, &stderr)
|
||||||
|
if exitCode != 0 {
|
||||||
|
t.Fatalf("expected success, got %d stderr=%q", exitCode, stderr.String())
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(strings.TrimSpace(stdout.String()), "{") {
|
||||||
|
t.Fatalf("expected bare-segments array output from CLI override, got object")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunProcessUnknownOutputSchemaFailsClearly(t *testing.T) {
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
|
||||||
|
exitCode := Run([]string{
|
||||||
|
"process",
|
||||||
|
fixturePath("tiny_transcript.json"),
|
||||||
|
"--glossary", fixturePath("tiny_glossary.yaml"),
|
||||||
|
"--output-schema", "seriatim-intermediate",
|
||||||
|
}, &stdout, &stderr)
|
||||||
|
if exitCode == 0 {
|
||||||
|
t.Fatalf("expected failure for unknown output schema")
|
||||||
|
}
|
||||||
|
if stdout.Len() != 0 {
|
||||||
|
t.Fatalf("expected empty stdout on failure, got %q", stdout.String())
|
||||||
|
}
|
||||||
|
if !strings.Contains(stderr.String(), "unsupported output schema") {
|
||||||
|
t.Fatalf("expected unsupported output schema error, got %q", stderr.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func fixturePath(name string) string {
|
func fixturePath(name string) string {
|
||||||
return filepath.Join("testdata", name)
|
return filepath.Join("testdata", name)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
DefaultModulesCSV = "glossary,homophones,glossary,spoken_word,grammar"
|
DefaultModulesCSV = "glossary,homophones,glossary,spoken_word,grammar"
|
||||||
|
DefaultOutputSchema = "bare-segments"
|
||||||
DefaultPrimaryModel = "openrouter/google/gemma-4-31b-it"
|
DefaultPrimaryModel = "openrouter/google/gemma-4-31b-it"
|
||||||
DefaultPrimaryBaseURL = "https://openrouter.ai/api/v1"
|
DefaultPrimaryBaseURL = "https://openrouter.ai/api/v1"
|
||||||
DefaultPrimaryLLMTimeoutSeconds = 600
|
DefaultPrimaryLLMTimeoutSeconds = 600
|
||||||
@@ -35,6 +36,7 @@ const (
|
|||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Modules []string
|
Modules []string
|
||||||
|
OutputSchema string
|
||||||
PrimaryLLM LLMConfig
|
PrimaryLLM LLMConfig
|
||||||
ValidationLLM ValidationLLMConfig
|
ValidationLLM ValidationLLMConfig
|
||||||
TotalLLMConcurrency int
|
TotalLLMConcurrency int
|
||||||
@@ -92,6 +94,7 @@ func Default() Config {
|
|||||||
|
|
||||||
return Config{
|
return Config{
|
||||||
Modules: modules,
|
Modules: modules,
|
||||||
|
OutputSchema: DefaultOutputSchema,
|
||||||
PrimaryLLM: LLMConfig{
|
PrimaryLLM: LLMConfig{
|
||||||
Model: DefaultPrimaryModel,
|
Model: DefaultPrimaryModel,
|
||||||
BaseURL: DefaultPrimaryBaseURL,
|
BaseURL: DefaultPrimaryBaseURL,
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ func TestDefaultConfigValues(t *testing.T) {
|
|||||||
if got, want := strings.Join(cfg.Modules, ","), DefaultModulesCSV; got != want {
|
if got, want := strings.Join(cfg.Modules, ","), DefaultModulesCSV; got != want {
|
||||||
t.Fatalf("modules mismatch: got %q want %q", got, want)
|
t.Fatalf("modules mismatch: got %q want %q", got, want)
|
||||||
}
|
}
|
||||||
|
if cfg.OutputSchema != DefaultOutputSchema {
|
||||||
|
t.Fatalf("unexpected default output schema: %q", cfg.OutputSchema)
|
||||||
|
}
|
||||||
if cfg.PrimaryLLM.Model != DefaultPrimaryModel {
|
if cfg.PrimaryLLM.Model != DefaultPrimaryModel {
|
||||||
t.Fatalf("unexpected default primary model: %q", cfg.PrimaryLLM.Model)
|
t.Fatalf("unexpected default primary model: %q", cfg.PrimaryLLM.Model)
|
||||||
}
|
}
|
||||||
@@ -186,12 +189,14 @@ func TestApplyCLIOverridesPrecedence(t *testing.T) {
|
|||||||
model := "cli-model"
|
model := "cli-model"
|
||||||
workDir := "/cli/work"
|
workDir := "/cli/work"
|
||||||
modules := "grammar"
|
modules := "grammar"
|
||||||
|
outputSchema := "audita-v1"
|
||||||
totalLLMConcurrency := 5
|
totalLLMConcurrency := 5
|
||||||
proposalLLMConcurrency := 3
|
proposalLLMConcurrency := 3
|
||||||
overrides := CLIOverrides{
|
overrides := CLIOverrides{
|
||||||
PrimaryModel: &model,
|
PrimaryModel: &model,
|
||||||
WorkDir: &workDir,
|
WorkDir: &workDir,
|
||||||
ModulesCSV: &modules,
|
ModulesCSV: &modules,
|
||||||
|
OutputSchema: &outputSchema,
|
||||||
TotalLLMConcurrency: &totalLLMConcurrency,
|
TotalLLMConcurrency: &totalLLMConcurrency,
|
||||||
ProposalLLMConcurrency: &proposalLLMConcurrency,
|
ProposalLLMConcurrency: &proposalLLMConcurrency,
|
||||||
}
|
}
|
||||||
@@ -209,6 +214,9 @@ func TestApplyCLIOverridesPrecedence(t *testing.T) {
|
|||||||
if !reflect.DeepEqual(cfg.Modules, []string{"grammar"}) {
|
if !reflect.DeepEqual(cfg.Modules, []string{"grammar"}) {
|
||||||
t.Fatalf("unexpected modules: %#v", cfg.Modules)
|
t.Fatalf("unexpected modules: %#v", cfg.Modules)
|
||||||
}
|
}
|
||||||
|
if cfg.OutputSchema != "audita-v1" {
|
||||||
|
t.Fatalf("expected CLI output schema override, got %q", cfg.OutputSchema)
|
||||||
|
}
|
||||||
if cfg.TotalLLMConcurrency != 5 {
|
if cfg.TotalLLMConcurrency != 5 {
|
||||||
t.Fatalf("expected CLI total concurrency override, got %d", cfg.TotalLLMConcurrency)
|
t.Fatalf("expected CLI total concurrency override, got %d", cfg.TotalLLMConcurrency)
|
||||||
}
|
}
|
||||||
@@ -275,6 +283,7 @@ func TestApplyCLIOverridesCanonicalTotalWinsLegacyAlias(t *testing.T) {
|
|||||||
|
|
||||||
func TestValidationFailures(t *testing.T) {
|
func TestValidationFailures(t *testing.T) {
|
||||||
cfg := Default()
|
cfg := Default()
|
||||||
|
cfg.OutputSchema = "unknown-schema"
|
||||||
cfg.PrimaryLLM.TimeoutSeconds = -1
|
cfg.PrimaryLLM.TimeoutSeconds = -1
|
||||||
cfg.TotalLLMConcurrency = 0
|
cfg.TotalLLMConcurrency = 0
|
||||||
cfg.ProposalLLMConcurrency = 0
|
cfg.ProposalLLMConcurrency = 0
|
||||||
@@ -301,6 +310,7 @@ func TestValidationFailures(t *testing.T) {
|
|||||||
"min section tokens",
|
"min section tokens",
|
||||||
"grammar confidence threshold",
|
"grammar confidence threshold",
|
||||||
"work dir retention",
|
"work dir retention",
|
||||||
|
"unsupported output schema",
|
||||||
} {
|
} {
|
||||||
if !strings.Contains(message, expected) {
|
if !strings.Contains(message, expected) {
|
||||||
t.Fatalf("expected error to contain %q, got %q", expected, message)
|
t.Fatalf("expected error to contain %q, got %q", expected, message)
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ var envVarNamePattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`)
|
|||||||
type FileConfig struct {
|
type FileConfig struct {
|
||||||
Version int `yaml:"version"`
|
Version int `yaml:"version"`
|
||||||
Pipeline *FileConfigPipeline `yaml:"pipeline,omitempty"`
|
Pipeline *FileConfigPipeline `yaml:"pipeline,omitempty"`
|
||||||
|
Output *FileConfigOutput `yaml:"output,omitempty"`
|
||||||
LLM *FileConfigLLM `yaml:"llm,omitempty"`
|
LLM *FileConfigLLM `yaml:"llm,omitempty"`
|
||||||
Concurrency *FileConfigConcurrency `yaml:"concurrency,omitempty"`
|
Concurrency *FileConfigConcurrency `yaml:"concurrency,omitempty"`
|
||||||
Chunking *FileConfigChunking `yaml:"chunking,omitempty"`
|
Chunking *FileConfigChunking `yaml:"chunking,omitempty"`
|
||||||
@@ -31,6 +32,10 @@ type FileConfigPipeline struct {
|
|||||||
Modules []string `yaml:"modules,omitempty"`
|
Modules []string `yaml:"modules,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FileConfigOutput struct {
|
||||||
|
Schema *string `yaml:"schema,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type FileConfigLLM struct {
|
type FileConfigLLM struct {
|
||||||
Proposal *FileConfigLLMTarget `yaml:"proposal,omitempty"`
|
Proposal *FileConfigLLMTarget `yaml:"proposal,omitempty"`
|
||||||
Validation *FileConfigLLMTarget `yaml:"validation,omitempty"`
|
Validation *FileConfigLLMTarget `yaml:"validation,omitempty"`
|
||||||
@@ -194,6 +199,9 @@ func (c *Config) applyFileConfigWithLookup(fileCfg FileConfig, lookup func(strin
|
|||||||
if fileCfg.Pipeline != nil && len(fileCfg.Pipeline.Modules) > 0 {
|
if fileCfg.Pipeline != nil && len(fileCfg.Pipeline.Modules) > 0 {
|
||||||
c.Modules = append([]string(nil), fileCfg.Pipeline.Modules...)
|
c.Modules = append([]string(nil), fileCfg.Pipeline.Modules...)
|
||||||
}
|
}
|
||||||
|
if fileCfg.Output != nil && fileCfg.Output.Schema != nil {
|
||||||
|
c.OutputSchema = strings.TrimSpace(*fileCfg.Output.Schema)
|
||||||
|
}
|
||||||
|
|
||||||
if fileCfg.LLM != nil {
|
if fileCfg.LLM != nil {
|
||||||
if fileCfg.LLM.Proposal != nil {
|
if fileCfg.LLM.Proposal != nil {
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ func TestParseFileConfigYAMLValid(t *testing.T) {
|
|||||||
version: 1
|
version: 1
|
||||||
pipeline:
|
pipeline:
|
||||||
modules: [glossary, homophones, grammar]
|
modules: [glossary, homophones, grammar]
|
||||||
|
output:
|
||||||
|
schema: audita-v1
|
||||||
llm:
|
llm:
|
||||||
proposal:
|
proposal:
|
||||||
base_url: https://example.test/v1
|
base_url: https://example.test/v1
|
||||||
@@ -58,6 +60,9 @@ diagnostics:
|
|||||||
if cfg.Pipeline == nil || len(cfg.Pipeline.Modules) != 3 {
|
if cfg.Pipeline == nil || len(cfg.Pipeline.Modules) != 3 {
|
||||||
t.Fatalf("unexpected pipeline modules: %#v", cfg.Pipeline)
|
t.Fatalf("unexpected pipeline modules: %#v", cfg.Pipeline)
|
||||||
}
|
}
|
||||||
|
if cfg.Output == nil || cfg.Output.Schema == nil || *cfg.Output.Schema != "audita-v1" {
|
||||||
|
t.Fatalf("expected output schema audita-v1, got %#v", cfg.Output)
|
||||||
|
}
|
||||||
if cfg.LLM == nil || cfg.LLM.Proposal == nil || cfg.LLM.Validation == nil {
|
if cfg.LLM == nil || cfg.LLM.Proposal == nil || cfg.LLM.Validation == nil {
|
||||||
t.Fatalf("expected llm proposal+validation blocks")
|
t.Fatalf("expected llm proposal+validation blocks")
|
||||||
}
|
}
|
||||||
@@ -78,13 +83,13 @@ version: 1
|
|||||||
pipeline:
|
pipeline:
|
||||||
modules: [grammar]
|
modules: [grammar]
|
||||||
output:
|
output:
|
||||||
schema: v1
|
unknown: v1
|
||||||
`
|
`
|
||||||
_, err := ParseFileConfigYAML([]byte(raw))
|
_, err := ParseFileConfigYAML([]byte(raw))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("expected unknown field error")
|
t.Fatalf("expected unknown field error")
|
||||||
}
|
}
|
||||||
if !strings.Contains(err.Error(), "field output not found") {
|
if !strings.Contains(err.Error(), "field unknown not found") {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,6 +121,8 @@ func TestApplyFileConfigParsesAndMergesFields(t *testing.T) {
|
|||||||
version: 1
|
version: 1
|
||||||
pipeline:
|
pipeline:
|
||||||
modules: [spoken_word, grammar]
|
modules: [spoken_word, grammar]
|
||||||
|
output:
|
||||||
|
schema: audita-v1
|
||||||
llm:
|
llm:
|
||||||
proposal:
|
proposal:
|
||||||
model: provider/new-proposal
|
model: provider/new-proposal
|
||||||
@@ -164,6 +171,9 @@ diagnostics:
|
|||||||
if strings.Join(cfg.Modules, ",") != "spoken_word,grammar" {
|
if strings.Join(cfg.Modules, ",") != "spoken_word,grammar" {
|
||||||
t.Fatalf("unexpected modules: %#v", cfg.Modules)
|
t.Fatalf("unexpected modules: %#v", cfg.Modules)
|
||||||
}
|
}
|
||||||
|
if cfg.OutputSchema != "audita-v1" {
|
||||||
|
t.Fatalf("unexpected output schema: %q", cfg.OutputSchema)
|
||||||
|
}
|
||||||
if cfg.PrimaryLLM.Model != "provider/new-proposal" {
|
if cfg.PrimaryLLM.Model != "provider/new-proposal" {
|
||||||
t.Fatalf("unexpected proposal model: %q", cfg.PrimaryLLM.Model)
|
t.Fatalf("unexpected proposal model: %q", cfg.PrimaryLLM.Model)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
type CLIOverrides struct {
|
type CLIOverrides struct {
|
||||||
ModulesCSV *string
|
ModulesCSV *string
|
||||||
|
OutputSchema *string
|
||||||
PrimaryLLMAPIKey *string
|
PrimaryLLMAPIKey *string
|
||||||
ValidationLLMAPIKey *string
|
ValidationLLMAPIKey *string
|
||||||
PrimaryModel *string
|
PrimaryModel *string
|
||||||
@@ -46,6 +47,9 @@ func (c *Config) ApplyCLIOverrides(overrides CLIOverrides) error {
|
|||||||
}
|
}
|
||||||
c.Modules = modules
|
c.Modules = modules
|
||||||
}
|
}
|
||||||
|
if overrides.OutputSchema != nil {
|
||||||
|
c.OutputSchema = strings.TrimSpace(*overrides.OutputSchema)
|
||||||
|
}
|
||||||
|
|
||||||
if overrides.PrimaryLLMAPIKey != nil {
|
if overrides.PrimaryLLMAPIKey != nil {
|
||||||
c.PrimaryLLM.APIKey = *overrides.PrimaryLLMAPIKey
|
c.PrimaryLLM.APIKey = *overrides.PrimaryLLMAPIKey
|
||||||
|
|||||||
@@ -17,6 +17,15 @@ func (c Config) Validate() error {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if strings.TrimSpace(c.OutputSchema) == "" {
|
||||||
|
issues = append(issues, "output schema must not be empty")
|
||||||
|
} else {
|
||||||
|
switch strings.TrimSpace(c.OutputSchema) {
|
||||||
|
case "bare-segments", "audita-v1":
|
||||||
|
default:
|
||||||
|
issues = append(issues, fmt.Sprintf("unsupported output schema %q", c.OutputSchema))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if c.PrimaryLLM.TimeoutSeconds <= 0 {
|
if c.PrimaryLLM.TimeoutSeconds <= 0 {
|
||||||
issues = append(issues, "primary llm timeout seconds must be greater than zero")
|
issues = append(issues, "primary llm timeout seconds must be greater than zero")
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ type InvocationMetadata struct {
|
|||||||
ReportJSONPath string `json:"report_json_path,omitempty"`
|
ReportJSONPath string `json:"report_json_path,omitempty"`
|
||||||
ConfigPath string `json:"config_path,omitempty"`
|
ConfigPath string `json:"config_path,omitempty"`
|
||||||
ConfigSource string `json:"config_source,omitempty"`
|
ConfigSource string `json:"config_source,omitempty"`
|
||||||
|
ConfigVersion *int `json:"config_version,omitempty"`
|
||||||
TranscriptDescription string `json:"transcript_description,omitempty"`
|
TranscriptDescription string `json:"transcript_description,omitempty"`
|
||||||
Modules []string `json:"modules"`
|
Modules []string `json:"modules"`
|
||||||
RunID string `json:"run_id"`
|
RunID string `json:"run_id"`
|
||||||
|
|||||||
72
internal/core/outputschema/registry.go
Normal file
72
internal/core/outputschema/registry.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package outputschema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SchemaBareSegments = "bare-segments"
|
||||||
|
SchemaAuditaV1 = "audita-v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Encoder func(*schema.Transcript) ([]byte, error)
|
||||||
|
|
||||||
|
type Definition struct {
|
||||||
|
Key string
|
||||||
|
Encoder Encoder
|
||||||
|
}
|
||||||
|
|
||||||
|
var definitions = map[string]Definition{
|
||||||
|
SchemaBareSegments: {
|
||||||
|
Key: SchemaBareSegments,
|
||||||
|
Encoder: schema.TranscriptToJSON,
|
||||||
|
},
|
||||||
|
SchemaAuditaV1: {
|
||||||
|
Key: SchemaAuditaV1,
|
||||||
|
Encoder: encodeAuditaV1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func Resolve(key string) (Definition, error) {
|
||||||
|
normalized := strings.TrimSpace(key)
|
||||||
|
if normalized == "" {
|
||||||
|
return Definition{}, fmt.Errorf("output schema must not be empty")
|
||||||
|
}
|
||||||
|
def, ok := definitions[normalized]
|
||||||
|
if !ok {
|
||||||
|
return Definition{}, fmt.Errorf("unsupported output schema %q", normalized)
|
||||||
|
}
|
||||||
|
return def, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeAuditaV1(transcript *schema.Transcript) ([]byte, error) {
|
||||||
|
if transcript == nil {
|
||||||
|
transcript = &schema.Transcript{}
|
||||||
|
}
|
||||||
|
payload := map[string]any{
|
||||||
|
"schema": "audita-v1",
|
||||||
|
"version": "v1",
|
||||||
|
"segments": func() []map[string]any {
|
||||||
|
out := make([]map[string]any, len(transcript.Segments))
|
||||||
|
for i, s := range transcript.Segments {
|
||||||
|
item := map[string]any{
|
||||||
|
"id": s.ID,
|
||||||
|
"speaker": s.Speaker,
|
||||||
|
"start": s.Start,
|
||||||
|
"end": s.End,
|
||||||
|
"text": s.Text,
|
||||||
|
}
|
||||||
|
if len(s.Categories) > 0 {
|
||||||
|
item["categories"] = s.Categories
|
||||||
|
}
|
||||||
|
out[i] = item
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}(),
|
||||||
|
}
|
||||||
|
return json.MarshalIndent(payload, "", " ")
|
||||||
|
}
|
||||||
58
internal/core/outputschema/registry_test.go
Normal file
58
internal/core/outputschema/registry_test.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package outputschema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func tinyTranscript() *schema.Transcript {
|
||||||
|
return &schema.Transcript{Segments: []schema.Segment{
|
||||||
|
{ID: 1, Speaker: "A", Start: 0, End: 1, Text: "hello"},
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveBareSegments(t *testing.T) {
|
||||||
|
def, err := Resolve(SchemaBareSegments)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Resolve error: %v", err)
|
||||||
|
}
|
||||||
|
raw, err := def.Encoder(tinyTranscript())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("encode error: %v", err)
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(strings.TrimSpace(string(raw)), "[") {
|
||||||
|
t.Fatalf("expected bare-segments array output, got %s", string(raw))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveAuditaV1(t *testing.T) {
|
||||||
|
def, err := Resolve(SchemaAuditaV1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Resolve error: %v", err)
|
||||||
|
}
|
||||||
|
raw, err := def.Encoder(tinyTranscript())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("encode error: %v", err)
|
||||||
|
}
|
||||||
|
var out struct {
|
||||||
|
Schema string `json:"schema"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
Segments []map[string]any `json:"segments"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(raw, &out); err != nil {
|
||||||
|
t.Fatalf("unmarshal error: %v", err)
|
||||||
|
}
|
||||||
|
if out.Schema != "audita-v1" || out.Version != "v1" || len(out.Segments) != 1 {
|
||||||
|
t.Fatalf("unexpected audita-v1 output: %+v", out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveUnknown(t *testing.T) {
|
||||||
|
_, err := Resolve("seriatim-intermediate")
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "unsupported output schema") {
|
||||||
|
t.Fatalf("expected unsupported output schema error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ProcessReport struct {
|
type ProcessReport struct {
|
||||||
|
ReportMetadata ReportMetadata `json:"report_metadata"`
|
||||||
Phase string `json:"phase"`
|
Phase string `json:"phase"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Operation string `json:"operation"`
|
Operation string `json:"operation"`
|
||||||
@@ -32,6 +33,18 @@ type ProcessReport struct {
|
|||||||
ModuleResults []ModuleReport `json:"module_results,omitempty"`
|
ModuleResults []ModuleReport `json:"module_results,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultProcessReportSchemaName = "audita-process-report"
|
||||||
|
DefaultProcessReportSchemaVersion = "v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReportMetadata struct {
|
||||||
|
ReportSchemaName string `json:"report_schema_name"`
|
||||||
|
ReportSchemaVersion string `json:"report_schema_version"`
|
||||||
|
OutputSchema string `json:"output_schema"`
|
||||||
|
ConfigVersion *int `json:"config_version,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type ModuleReport struct {
|
type ModuleReport struct {
|
||||||
ModuleKey string `json:"module_key"`
|
ModuleKey string `json:"module_key"`
|
||||||
ModuleInstance string `json:"module_instance"`
|
ModuleInstance string `json:"module_instance"`
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ import (
|
|||||||
func TestProcessReportModuleResultsJSONSuccessAndSkipped(t *testing.T) {
|
func TestProcessReportModuleResultsJSONSuccessAndSkipped(t *testing.T) {
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
report := ProcessReport{
|
report := ProcessReport{
|
||||||
|
ReportMetadata: ReportMetadata{
|
||||||
|
ReportSchemaName: DefaultProcessReportSchemaName,
|
||||||
|
ReportSchemaVersion: DefaultProcessReportSchemaVersion,
|
||||||
|
OutputSchema: "bare-segments",
|
||||||
|
},
|
||||||
Phase: "default_pipeline",
|
Phase: "default_pipeline",
|
||||||
Status: "success",
|
Status: "success",
|
||||||
ModuleResults: []ModuleReport{
|
ModuleResults: []ModuleReport{
|
||||||
@@ -75,11 +80,21 @@ func TestProcessReportModuleResultsJSONSuccessAndSkipped(t *testing.T) {
|
|||||||
if parsed.ModulesSummary == nil || parsed.ModulesSummary.TotalSkippedChanges != 1 {
|
if parsed.ModulesSummary == nil || parsed.ModulesSummary.TotalSkippedChanges != 1 {
|
||||||
t.Fatalf("unexpected module summary: %+v", parsed.ModulesSummary)
|
t.Fatalf("unexpected module summary: %+v", parsed.ModulesSummary)
|
||||||
}
|
}
|
||||||
|
if parsed.ReportMetadata.ReportSchemaName != DefaultProcessReportSchemaName ||
|
||||||
|
parsed.ReportMetadata.ReportSchemaVersion != DefaultProcessReportSchemaVersion ||
|
||||||
|
parsed.ReportMetadata.OutputSchema != "bare-segments" {
|
||||||
|
t.Fatalf("unexpected report metadata after roundtrip: %+v", parsed.ReportMetadata)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessReportModuleResultsJSONFailedModule(t *testing.T) {
|
func TestProcessReportModuleResultsJSONFailedModule(t *testing.T) {
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
report := ProcessReport{
|
report := ProcessReport{
|
||||||
|
ReportMetadata: ReportMetadata{
|
||||||
|
ReportSchemaName: DefaultProcessReportSchemaName,
|
||||||
|
ReportSchemaVersion: DefaultProcessReportSchemaVersion,
|
||||||
|
OutputSchema: "audita-v1",
|
||||||
|
},
|
||||||
Phase: "default_pipeline",
|
Phase: "default_pipeline",
|
||||||
Status: "failed",
|
Status: "failed",
|
||||||
ModuleResults: []ModuleReport{
|
ModuleResults: []ModuleReport{
|
||||||
@@ -106,6 +121,9 @@ func TestProcessReportModuleResultsJSONFailedModule(t *testing.T) {
|
|||||||
if _, ok := decoded["module_results"]; !ok {
|
if _, ok := decoded["module_results"]; !ok {
|
||||||
t.Fatalf("expected module_results field")
|
t.Fatalf("expected module_results field")
|
||||||
}
|
}
|
||||||
|
if _, ok := decoded["report_metadata"]; !ok {
|
||||||
|
t.Fatalf("expected report_metadata field")
|
||||||
|
}
|
||||||
if _, ok := decoded["modules_summary"]; !ok {
|
if _, ok := decoded["modules_summary"]; !ok {
|
||||||
t.Fatalf("expected modules_summary field")
|
t.Fatalf("expected modules_summary field")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user