Document and verify independent output destinations

This commit is contained in:
2026-10-03 13:53:05 +00:00
parent ddfc3dd4c7
commit 6ae1d7cd5f
8 changed files with 145 additions and 38 deletions

View File

@@ -799,6 +799,41 @@ func TestSubprocessStdoutAndSelectedReportDirectory(t *testing.T) {
}
}
func TestSubprocessUsesIndependentTranscriptReportAndDebugDestinations(t *testing.T) {
f := newProcessFixture(t, "normal", "[grammar]")
transcriptDir := filepath.Join(f.dir, "transcripts")
reportDir := filepath.Join(f.dir, "reports")
debugDir := filepath.Join(f.dir, "debug")
for _, dir := range []string{transcriptDir, reportDir} {
if err := os.Mkdir(dir, 0700); err != nil {
t.Fatal(err)
}
}
f.output = filepath.Join(transcriptDir, "corrected.json")
code, stdout, stderr := f.run(t, true, "--report-dir", reportDir, "--debug", "--work-dir", debugDir)
if code != 0 || stdout != "" || !strings.HasPrefix(stderr, "Completed:") {
t.Fatalf("code=%d stdout=%q stderr=%q", code, stdout, stderr)
}
if output, err := os.ReadFile(f.output); err != nil || !bytes.Contains(output, []byte("Hello world")) {
t.Fatalf("transcript: %s %v", output, err)
}
for _, name := range []string{"errors.yml", "recoveries.yml", "report.md"} {
if _, err := os.Stat(filepath.Join(reportDir, name)); err != nil {
t.Fatalf("report %s: %v", name, err)
}
if _, err := os.Stat(filepath.Join(transcriptDir, name)); !os.IsNotExist(err) {
t.Fatalf("report %s appeared beside transcript: %v", name, err)
}
}
dirs, err := filepath.Glob(filepath.Join(debugDir, "run-*"))
if err != nil || len(dirs) != 1 {
t.Fatalf("debug runs: %v %v", dirs, err)
}
if _, err := os.Stat(filepath.Join(dirs[0], "correction-ledger.json")); err != nil {
t.Fatalf("debug capture: %v", err)
}
}
func TestSubprocessReportingAndDebugSelections(t *testing.T) {
var expectedOutput, expectedSummary string
for _, selection := range []struct{ reporting, debug bool }{
@@ -1016,6 +1051,44 @@ func TestSubprocessInvalidInputLeavesFailedReports(t *testing.T) {
}
}
func TestSubprocessInvalidInputWritesFailedReportsToSelectedDirectory(t *testing.T) {
f := newProcessFixture(t, "normal", "[grammar]")
if err := os.WriteFile(f.transcript, []byte("not JSON"), 0600); err != nil {
t.Fatal(err)
}
reportDir := filepath.Join(f.dir, "failed-reports")
if err := os.Mkdir(reportDir, 0700); err != nil {
t.Fatal(err)
}
code, _, _ := f.run(t, true, "--report-dir", reportDir)
if code != 1 || f.provider.callCount() != 0 {
t.Fatalf("invalid input dispatched: code=%d calls=%d", code, f.provider.callCount())
}
raw, err := os.ReadFile(filepath.Join(reportDir, "errors.yml"))
if err != nil {
t.Fatal(err)
}
var document reporting.ErrorsDocument
if err := yaml.Unmarshal(raw, &document); err != nil {
t.Fatal(err)
}
if document.Status != reporting.StatusFailed || len(document.Errors) != 1 || document.Errors[0].Scope != "run" {
t.Fatalf("failed report: %+v", document)
}
if _, err := os.Stat(filepath.Join(reportDir, "recoveries.yml")); err != nil {
t.Fatal(err)
}
if raw, err := os.ReadFile(filepath.Join(reportDir, "report.md")); err != nil || !bytes.Contains(raw, []byte("Status: failed")) {
t.Fatalf("Markdown status: %s %v", raw, err)
}
if _, err := os.Stat(filepath.Join(f.dir, "errors.yml")); !os.IsNotExist(err) {
t.Fatalf("failed report appeared in default directory: %v", err)
}
if _, err := os.Stat(f.output); !os.IsNotExist(err) {
t.Fatalf("failed transcript published: %v", err)
}
}
func TestSubprocessOutputFailureBeforeModelDispatch(t *testing.T) {
f := newProcessFixture(t, "normal", "[grammar]")
f.output = filepath.Join(f.dir, "missing", "output.json")

View File

@@ -48,12 +48,17 @@ validation. The shared setting flags are:
An omitted or empty output value selects stdout. Nonempty output filenames are
used literally, including whitespace.
Reporting defaults to enabled. `--reporting=false` disables all three ordinary
reports; an explicit false overrides a true file or overlay setting. With file
output, reports go beside the transcript and `--report-dir` has no effect. With
stdout output, `--report-dir` selects the report directory, defaulting to the
invocation working directory. `--debug` separately enables retained detailed
artifacts under `--work-dir`. Configuration inspection does not create or probe
these output directories.
reports; an explicit false overrides a true file or overlay setting. For stdout
output, reports use the effective `reporting.stdout_dir`, which defaults to the
invocation working directory. For file output, they go beside the transcript
unless `--report-dir` is explicitly supplied. The flag selects the report
directory for either transcript destination; a config-file or overlay value for
`reporting.stdout_dir` alone affects stdout output only. A selected report
directory must be nonblank when reporting is enabled. With reporting disabled,
the report destination is inactive and is not probed. `--debug` separately
enables retained detailed artifacts under `--work-dir`; selecting `--work-dir`
alone does not enable capture. Configuration inspection does not create or
probe these output directories.
Per-role model, base URL, key, timeout, retry, and concurrency flags have been
removed. Models and generation controls belong to PromptKit profiles, backend
credentials and capacity to configuration, and retry/deadline settings to the

View File

@@ -62,17 +62,20 @@ field provenance separately from ConfigKit's redacted settings YAML.
| `thresholds.glossary`, `.homophones`, `.spoken_word`, `.grammar` | `0.8` each | Confidence thresholds in `[0, 1]`. |
| `context.description` | Empty | Trimmed prompt context, at most 500 bytes. |
| `reporting.enabled` | `true` | Write the ordinary three-file report bundle when processing. |
| `reporting.stdout_dir` | `.` | Report directory for stdout transcript output; ignored for file output or disabled reporting. |
| `reporting.stdout_dir` | `.` | Report directory for stdout transcript output; a `--report-dir` flag can select reports for either transcript destination. |
| `diagnostics.debug` | `false` | Persist detailed debug artifacts when processing. |
| `diagnostics.work_dir` | `/tmp/audita` | Parent of the retained debug run directory; required only when debug is enabled. |
Module keys select correction modules; validator chains remain built into them.
The loader preserves module order and repetitions. Normalization time settings
accept numeric seconds only. The operation timeout uses a duration string.
`reporting.stdout_dir` must be nonblank only for reporting-enabled stdout runs;
`diagnostics.work_dir` must be nonblank only with debug enabled. These path
requirements are checked when choosing process destinations, not during
configuration inspection.
`reporting.stdout_dir` must be nonblank when it supplies the active report
destination: for stdout output, or for file output when `--report-dir` is
explicitly supplied. File output without that flag uses the transcript's parent,
so the configured value is inactive. Reporting-disabled runs do not select a
report directory. `diagnostics.work_dir` must be nonblank only with debug
enabled. These path requirements are checked when choosing process destinations,
not during configuration inspection.
## Credentials

View File

@@ -9,11 +9,14 @@ The [CLI reference](../cli.md) owns command syntax, streams, and exit semantics;
Use an argument array with the transcript path and required glossary argument,
following the [process examples](../cli.md#commands-and-help). For machine workflows,
select an explicit transcript file destination and a configuration path. Reports
then go beside the transcript. For stdout output, select `--report-dir` or use
the invocation working directory. Debug is independent and off by default.
Supplying a configuration path also
makes file selection predictable; only explicitly referenced credential variables
are read from the environment.
go beside the transcript unless an explicit `--report-dir` selects another
directory. For stdout output, reports use the effective `reporting.stdout_dir`
(defaulting to the invocation working directory), including a `--report-dir`
override. A config-file or overlay value for `reporting.stdout_dir` alone does
not redirect file-output reports. `--work-dir` selects a debug root, while
configuration or `--debug` enables capture; the path flag alone does not.
Supplying a configuration path also makes file selection predictable; only
explicitly referenced credential variables are read from the environment.
Resolve the executable deliberately and set the child's working directory so
relative input/output paths and reported artifact paths have a known base.
@@ -28,7 +31,8 @@ Consume transcript/report data through the
[output-file contract](output-files.md), and treat stderr as human-readable detail.
The three reports may be absent after early configuration or destination
failures. After initialization, failed reports can describe invalid input or
partial processing. Exit `0` can include recoverable model errors and applied
partial processing and use the same selected report directory as successful
runs. Exit `0` can include recoverable model errors and applied
corrections; inspect `errors.yml` and `recoveries.yml` for those outcomes. A
transcript may have been written before a later required report failure, so
the file alone does not prove completion.

View File

@@ -17,10 +17,14 @@ settings, and the [output contract](integrations/output-files.md) owns schemas.
## Required Output Files
Reporting is enabled by default. For file transcript output, `errors.yml`,
`recoveries.yml`, and `report.md` go in the transcript output's parent directory.
For stdout transcript output, they go in `reporting.stdout_dir` (default `.`),
overridden by `--report-dir`. `--reporting=false` suppresses all three reports
and removes their path requirements. It does not suppress the stderr summary or
`recoveries.yml`, and `report.md` go in the transcript output's parent directory
unless `--report-dir` is explicitly supplied. For stdout transcript output, they
go in the effective `reporting.stdout_dir` (default `.`), including a
`--report-dir` override. A config-file or overlay value for
`reporting.stdout_dir` alone does not redirect file-output reports. An active
selected report directory must be nonblank. `--reporting=false` suppresses all
three reports and leaves their destination inactive, so it is not probed merely
because `--report-dir` was supplied. It does not suppress the stderr summary or
fatal errors. Stdout contains only transcript JSON; file output leaves it empty.
Existing report directories and parents for regular transcript files must be
@@ -32,8 +36,9 @@ Preparation never truncates an existing transcript. Device and pipe destinations
(such as `/dev/null` and named pipes) receive ordinary writes at publication time;
they are not opened during preflight or replaced with regular files. A pipe write
can wait for its reader, and stream write errors are fatal. With reporting enabled,
reports still use the output path's parent; disable reporting when that placement
is unsuitable for a stream destination.
reports still use the output path's parent unless an explicit `--report-dir`
selects another directory; disable reporting when that placement is unsuitable
for a stream destination.
With reporting enabled, all three reports are initialized as `in_progress` before
input processing. Output is encoded fully before publication. Each regular output
@@ -48,10 +53,12 @@ finalized where possible. An uncatchable termination can leave `in_progress`.
## Diagnostics Directory
`--debug` enables detailed evidence independently of reporting. One directory
named `run-<id>` is created under `diagnostics.work_dir` (default `/tmp/audita`)
and retained on success or failure. Without debug, no directory or payload
files are created. The parent is created when needed only for debug. Failure to
`--debug` or `diagnostics.debug` enables detailed evidence independently of
reporting. One directory named `run-<id>` is created under
`diagnostics.work_dir` (default `/tmp/audita`), which `--work-dir` can override,
and retained on success or failure. Selecting `--work-dir` alone does not enable
capture. Without debug, no directory or payload files are created. The parent
is created when needed only for debug. Failure to
write optional debug evidence prints a redacted stderr diagnostic but does not
change correction decisions or the exit status.

View File

@@ -1,7 +1,7 @@
# Implementation Plan: Independent Output Destinations
**Last updated:** 2026-10-03
**Status:** Stage 1 complete; Stage 2 pending.
**Status:** Complete; Stages 1–2 implemented and verified.
**Audience:** gpt-5.6-terra coding agent.
## Scope and Execution
@@ -94,6 +94,9 @@ destination constraints, then commit the coherent code/test unit.
**Files:** `cmd/audita/main_integration_test.go`, `docs/cli.md`,
`docs/config.md`, `docs/operations.md`,
`docs/integrations/subprocess.md`, and `docs/roadmap/narratio-feature.md`.
**Completion:** Complete — the full Go test suite, maintained configuration
examples, affected Markdown links, and whitespace checks pass.
Touch another document only if its current statement actually conflicts with
the new behavior.

View File

@@ -2,10 +2,10 @@
**Last updated:** 2026-10-03
**Status:** Proposed; not implemented. This roadmap records the intended external
behavior and the reasons for it. The [implementation plan](implementation.md)
owns the ordered work; the [Narratio request](feature-request-narratio.md)
provides the consumer context.
**Status:** Implemented and verified. This roadmap records the external behavior
and the reasons for it. The [implementation plan](implementation.md) records the
completed work; the [Narratio request](feature-request-narratio.md) provides the
consumer context.
## Purpose
@@ -21,8 +21,9 @@ working-state lifecycle; Narratio owns its destination paths and operator views.
- `--output FILE` writes the transcript to `FILE`, or stdout when omitted.
- Ordinary reporting is enabled by default. With file output, `errors.yml`,
`recoveries.yml`, and `report.md` go beside the transcript. With stdout output,
`--report-dir DIR` or `reporting.stdout_dir` selects their directory. The
current selection logic ignores `--report-dir` when `--output` is supplied.
`--report-dir DIR` or `reporting.stdout_dir` selects their directory. An
explicit `--report-dir` also selects reports for file output; without it, the
configured `reporting.stdout_dir` value remains stdout-only.
- Reports are initialized before input processing, then updated on completion or
failure where possible. Early configuration or destination failures can occur
before report initialization and therefore leave no report files.
@@ -30,10 +31,10 @@ working-state lifecycle; Narratio owns its destination paths and operator views.
enable debug capture. Capture is enabled by `diagnostics.debug` or an explicit
`--debug`; when enabled, Audita retains a `run-<id>` directory beneath the
selected root. Debug remains optional, including after unsuccessful runs.
- The output session already receives distinct transcript and report paths and
checks active destinations for conflicts with each other and protected inputs.
The limitation is primarily in CLI report-directory selection, not in report
encoding or the output session's basic shape.
- The output session receives distinct transcript and report paths and checks
active destinations for conflicts with each other and protected inputs. The
CLI selects the report destination while the output session retains its
existing collision checks, preflight, initialization, and finalization.
The current contracts are in [CLI](../cli.md), [configuration](../config.md),
[operations](../operations.md), [subprocess guidance](../integrations/subprocess.md),

View File

@@ -215,6 +215,17 @@ func TestReportingAndDebugOverridesRemainIndependent(t *testing.T) {
t.Fatalf("%s did not honor explicit false: %+v", command, selected.Result.Config)
}
}
processInput, _, err := parseConfigInput("process", []string{"input.json", "--glossary", "glossary.yml", "--config", main, "--overlay", "testing"}, io.Discard)
if err != nil {
t.Fatal(err)
}
processConfig, err := loadConfigInput(processInput, "process")
if err != nil {
t.Fatal(err)
}
if got, err := selectedReportDir(processConfig.Result.Config, filepath.Join("out", "corrected.json"), processInput.ReportDirSet); err != nil || got != "out" {
t.Fatalf("overlay stdout directory redirected file output: %q %v", got, err)
}
}
func TestReportDirectorySelectionOnlyUsesActiveDestination(t *testing.T) {