Archive Audita reports as polish diagnostics
This commit is contained in:
@@ -12,21 +12,26 @@ Run Audita polishing on base transcript and produce polished transcript.
|
|||||||
## Outputs
|
## Outputs
|
||||||
|
|
||||||
- `transcripts/polished.json`
|
- `transcripts/polished.json`
|
||||||
- optional `artifacts/audita.report.json`
|
|
||||||
|
The transcript is the only canonical output. Available `errors.yml`,
|
||||||
|
`recoveries.yml`, and `report.md` remain beside the run-local transcript and
|
||||||
|
are recorded as diagnostics, not artifact outputs.
|
||||||
|
|
||||||
## Key Behavior
|
## Key Behavior
|
||||||
|
|
||||||
- resolves base transcript only from verified merge manifest output evidence.
|
- resolves base transcript only from verified merge manifest output evidence.
|
||||||
- invokes an Audita runner configured with static model/runtime options; the
|
- invokes an Audita v2 runner with external configuration selectors and
|
||||||
invocation supplies paths and modules.
|
explicitly forwarded provider credentials; the invocation supplies paths.
|
||||||
- validates processed transcript structure (`segments` array required).
|
- validates processed transcript structure (`segments` array required).
|
||||||
- validates optional report JSON.
|
- validates reported diagnostic paths against the three fixed adjacent names.
|
||||||
- materializes canonical outputs; records logs/generated config and adapter metadata.
|
- materializes only the canonical transcript; records available reports with
|
||||||
|
stdout/stderr logs, generated invocation, and safe adapter metadata.
|
||||||
|
|
||||||
## Invariants
|
## Invariants
|
||||||
|
|
||||||
- polished transcript schema validation is mandatory.
|
- polished transcript schema validation is mandatory.
|
||||||
- report output is config-gated.
|
- reports are optional diagnostics; Audita's external configuration may disable
|
||||||
|
them. A failed invocation never promotes an incidental transcript.
|
||||||
|
|
||||||
## Resume Semantics
|
## Resume Semantics
|
||||||
|
|
||||||
|
|||||||
@@ -426,6 +426,9 @@ tree and reports which stream exceeded the limit.
|
|||||||
|
|
||||||
Run-local diagnostics are:
|
Run-local diagnostics are:
|
||||||
|
|
||||||
|
- `runs/{run_id}/polish/outputs/transcripts/errors.yml`,
|
||||||
|
`recoveries.yml`, and `report.md` when Audita writes them beside its
|
||||||
|
run-local polished transcript
|
||||||
- `runs/{run_id}/extract/notarius.receipt.json`
|
- `runs/{run_id}/extract/notarius.receipt.json`
|
||||||
- `runs/{run_id}/extract/notarius.stderr.log`
|
- `runs/{run_id}/extract/notarius.stderr.log`
|
||||||
- `runs/{run_id}/extract/notarius-output/` before durable promotion
|
- `runs/{run_id}/extract/notarius-output/` before durable promotion
|
||||||
@@ -439,6 +442,10 @@ Unlisted files and unsafe entries are never uploaded; a run with no declared
|
|||||||
current-run diagnostics uploads no run-local files. No distinguished diagnostic
|
current-run diagnostics uploads no run-local files. No distinguished diagnostic
|
||||||
file is uploaded. The durable bundle is never scanned for implicit publication;
|
file is uploaded. The durable bundle is never scanned for implicit publication;
|
||||||
only lanes named by explicit `pipeline.publish.outputs` rules are uploaded.
|
only lanes named by explicit `pipeline.publish.outputs` rules are uploaded.
|
||||||
|
To inspect a published polish report remotely, read its declared object under
|
||||||
|
the run archive's `polish/outputs/transcripts/` prefix. Session restore does
|
||||||
|
not restore run-archive diagnostics as canonical session files; it restores only
|
||||||
|
the committed manifest and declared transcript/artifact objects.
|
||||||
|
|
||||||
To intentionally replace the current extraction result, run:
|
To intentionally replace the current extraction result, run:
|
||||||
|
|
||||||
|
|||||||
@@ -316,7 +316,10 @@ semantic integration; all stages are required before release.
|
|||||||
|
|
||||||
## Stage 5: Integrate polish reports with diagnostic lifecycle
|
## Stage 5: Integrate polish reports with diagnostic lifecycle
|
||||||
|
|
||||||
Status: Not started.
|
Status: Complete (2026-09-25).
|
||||||
|
|
||||||
|
Verification: `go test ./internal/stage ./internal/app ./internal/manifest ./internal/artifacts`,
|
||||||
|
`go test -race ./internal/stage ./internal/app`, and `git diff --check` passed.
|
||||||
|
|
||||||
Preparation: `internal/stage/polish.go`, `polish_test.go`, `run_local.go`,
|
Preparation: `internal/stage/polish.go`, `polish_test.go`, `run_local.go`,
|
||||||
`stage.go`, `publish_sources.go`; result application in `internal/app/runner.go`;
|
`stage.go`, `publish_sources.go`; result application in `internal/app/runner.go`;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ func (n *NoopRunner) Run(ctx context.Context, req PolishRequest) (PolishResult,
|
|||||||
if err := ctx.Err(); err != nil {
|
if err := ctx.Err(); err != nil {
|
||||||
return PolishResult{}, err
|
return PolishResult{}, err
|
||||||
}
|
}
|
||||||
if err := materializePlaceholders(req); err != nil {
|
if err := materializePlaceholders(req, false); err != nil {
|
||||||
return PolishResult{}, err
|
return PolishResult{}, err
|
||||||
}
|
}
|
||||||
return PolishResult{
|
return PolishResult{
|
||||||
@@ -45,13 +45,21 @@ func (f *FakeRunner) Run(ctx context.Context, req PolishRequest) (PolishResult,
|
|||||||
if f.Err != nil {
|
if f.Err != nil {
|
||||||
return PolishResult{}, f.Err
|
return PolishResult{}, f.Err
|
||||||
}
|
}
|
||||||
if err := materializePlaceholders(req); err != nil {
|
if err := materializePlaceholders(req, true); err != nil {
|
||||||
return PolishResult{}, err
|
return PolishResult{}, err
|
||||||
}
|
}
|
||||||
res := f.Result
|
res := f.Result
|
||||||
if res.ProcessedTranscriptPath == "" {
|
if res.ProcessedTranscriptPath == "" {
|
||||||
res.ProcessedTranscriptPath = req.OutputProcessedPath
|
res.ProcessedTranscriptPath = req.OutputProcessedPath
|
||||||
}
|
}
|
||||||
|
if res.Reports == (ReportBundle{}) {
|
||||||
|
dir := filepath.Dir(req.OutputProcessedPath)
|
||||||
|
res.Reports = ReportBundle{
|
||||||
|
ErrorsPath: filepath.Join(dir, "errors.yml"),
|
||||||
|
RecoveriesPath: filepath.Join(dir, "recoveries.yml"),
|
||||||
|
OverviewPath: filepath.Join(dir, "report.md"),
|
||||||
|
}
|
||||||
|
}
|
||||||
if res.StdoutLogPath == "" {
|
if res.StdoutLogPath == "" {
|
||||||
res.StdoutLogPath = req.StdoutLogPath
|
res.StdoutLogPath = req.StdoutLogPath
|
||||||
}
|
}
|
||||||
@@ -67,7 +75,7 @@ func (f *FakeRunner) Run(ctx context.Context, req PolishRequest) (PolishResult,
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func materializePlaceholders(req PolishRequest) error {
|
func materializePlaceholders(req PolishRequest, reports bool) error {
|
||||||
if req.GeneratedConfigPath != "" {
|
if req.GeneratedConfigPath != "" {
|
||||||
payload := map[string]any{
|
payload := map[string]any{
|
||||||
"schema": "audita.generated.v2",
|
"schema": "audita.generated.v2",
|
||||||
@@ -95,6 +103,19 @@ func materializePlaceholders(req PolishRequest) error {
|
|||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if !reports {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, report := range []struct{ name, contents string }{
|
||||||
|
{"errors.yml", "schema: audita-errors\nversion: 1\nstatus: completed\nerrors: []\n"},
|
||||||
|
{"recoveries.yml", "schema: audita-recoveries\nversion: 1\nstatus: completed\nrecoveries: []\n"},
|
||||||
|
{"report.md", "# Audita report\n"},
|
||||||
|
} {
|
||||||
|
path := filepath.Join(filepath.Dir(req.OutputProcessedPath), report.name)
|
||||||
|
if err := fileops.WriteFileAtomic(path, []byte(report.contents), fileops.WorkspaceFileMode); err != nil {
|
||||||
|
return fmt.Errorf("write report %q: %w", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -274,6 +274,28 @@ inputs:
|
|||||||
if !strings.Contains(stdout.String(), "stage=polish executed=1 skipped=0") {
|
if !strings.Contains(stdout.String(), "stage=polish executed=1 skipped=0") {
|
||||||
t.Fatalf("stdout = %q, want polish execution", stdout.String())
|
t.Fatalf("stdout = %q, want polish execution", stdout.String())
|
||||||
}
|
}
|
||||||
|
recorded, err := (&manifest.LocalStore{}).Load(context.Background(), filepath.Join(workRoot, "manifest.json"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("load polished manifest: %v", err)
|
||||||
|
}
|
||||||
|
polish := recorded.Stages["polish"]
|
||||||
|
if polish == nil || len(polish.Outputs) != 1 || polish.Outputs[0].Kind != "transcript_polished" {
|
||||||
|
t.Fatalf("polish outputs = %#v, want only canonical transcript", polish)
|
||||||
|
}
|
||||||
|
if len(polish.Logs) != 5 || len(polish.GeneratedConfigs) != 1 {
|
||||||
|
t.Fatalf("polish diagnostics = %#v, want logs, reports, and invocation", polish)
|
||||||
|
}
|
||||||
|
for _, name := range []string{"errors.yml", "recoveries.yml", "report.md"} {
|
||||||
|
found := false
|
||||||
|
for _, path := range polish.Logs {
|
||||||
|
if filepath.Base(path) == name {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Fatalf("report %q absent from manifest diagnostics: %#v", name, polish.Logs)
|
||||||
|
}
|
||||||
|
}
|
||||||
foundReference := false
|
foundReference := false
|
||||||
if err := filepath.WalkDir(workRoot, func(path string, entry os.DirEntry, walkErr error) error {
|
if err := filepath.WalkDir(workRoot, func(path string, entry os.DirEntry, walkErr error) error {
|
||||||
if walkErr != nil || entry.IsDir() {
|
if walkErr != nil || entry.IsDir() {
|
||||||
|
|||||||
@@ -44,6 +44,28 @@ func TestCommittedRestorePlanUsesOnlyDeclaredObjects(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCommittedRestoreDoesNotRequirePolishRunDiagnostics(t *testing.T) {
|
||||||
|
cfg := restorePlanConfig(t)
|
||||||
|
fake := &storage.FakeBackend{}
|
||||||
|
current := seedCommittedRestoreSnapshot(t, cfg, fake, "20260519T010203Z-a1b2c3d4", map[string][]byte{
|
||||||
|
"transcripts/full.json": []byte(`{"segments":[]}`),
|
||||||
|
})
|
||||||
|
diagnostic := filepath.Join(cfg.Pipeline.Workspace.Root, "runs", current.RunID, "polish", "outputs", "transcripts", "errors.yml")
|
||||||
|
current.Manifest.Stages["polish"] = &manifest.StageRecord{
|
||||||
|
Name: "polish", Status: manifest.StatusSucceeded,
|
||||||
|
Logs: []string{diagnostic},
|
||||||
|
}
|
||||||
|
plan, err := buildRestorePlan(context.Background(), cfg, current, fake, RestorePlanOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("buildRestorePlan() error = %v", err)
|
||||||
|
}
|
||||||
|
for _, action := range plan.Actions {
|
||||||
|
if strings.Contains(action.LocalRelativePath, "errors.yml") {
|
||||||
|
t.Fatalf("diagnostic became a required restore target: %#v", action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCommittedRestoreDownloadsCarriedOutputFromDeclaredRun(t *testing.T) {
|
func TestCommittedRestoreDownloadsCarriedOutputFromDeclaredRun(t *testing.T) {
|
||||||
cfg := restorePlanConfig(t)
|
cfg := restorePlanConfig(t)
|
||||||
fake := &storage.FakeBackend{}
|
fake := &storage.FakeBackend{}
|
||||||
|
|||||||
@@ -87,7 +87,8 @@ func (polishStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*St
|
|||||||
|
|
||||||
res, err := env.Audita.Run(ctx, req)
|
res, err := env.Audita.Run(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("polish: audita polish failed: %w", err)
|
return nil, fmt.Errorf("polish: audita polish failed (output_dir=%q, stdout_log=%q, stderr_log=%q): %w",
|
||||||
|
filepath.Dir(processedPath), stdoutPath, stderrPath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
finalProcessedPath, err := authoritativeOutputPath(processedPath, res.ProcessedTranscriptPath)
|
finalProcessedPath, err := authoritativeOutputPath(processedPath, res.ProcessedTranscriptPath)
|
||||||
@@ -97,6 +98,27 @@ func (polishStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*St
|
|||||||
if err := validateProcessedTranscriptOutput(finalProcessedPath); err != nil {
|
if err := validateProcessedTranscriptOutput(finalProcessedPath); err != nil {
|
||||||
return nil, fmt.Errorf("polish: processed transcript %q invalid: %w", finalProcessedPath, err)
|
return nil, fmt.Errorf("polish: processed transcript %q invalid: %w", finalProcessedPath, err)
|
||||||
}
|
}
|
||||||
|
reportPaths := make([]string, 0, 3)
|
||||||
|
reportMetadata := map[string]string{}
|
||||||
|
for _, report := range []struct{ name, returned string }{
|
||||||
|
{"errors.yml", res.Reports.ErrorsPath},
|
||||||
|
{"recoveries.yml", res.Reports.RecoveriesPath},
|
||||||
|
{"report.md", res.Reports.OverviewPath},
|
||||||
|
} {
|
||||||
|
if report.returned == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
expected := filepath.Join(filepath.Dir(processedPath), report.name)
|
||||||
|
path, err := authoritativeOutputPath(expected, report.returned)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("polish: report %s: %w", report.name, err)
|
||||||
|
}
|
||||||
|
if err := requireRegularReadableFile(path, "audita report"); err != nil {
|
||||||
|
return nil, fmt.Errorf("polish: %w", err)
|
||||||
|
}
|
||||||
|
reportPaths = append(reportPaths, path)
|
||||||
|
reportMetadata[report.name] = path
|
||||||
|
}
|
||||||
|
|
||||||
materializedProcessed, err := materializeRunLocalOutput(env.ArtifactStore, finalProcessedPath, canonicalProcessedPath, artifacts.Ref{
|
materializedProcessed, err := materializeRunLocalOutput(env.ArtifactStore, finalProcessedPath, canonicalProcessedPath, artifacts.Ref{
|
||||||
Kind: "transcript_polished",
|
Kind: "transcript_polished",
|
||||||
@@ -133,6 +155,7 @@ func (polishStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*St
|
|||||||
"adapter_generated_config_path": res.GeneratedConfigPath,
|
"adapter_generated_config_path": res.GeneratedConfigPath,
|
||||||
"adapter_stdout_log_path": res.StdoutLogPath,
|
"adapter_stdout_log_path": res.StdoutLogPath,
|
||||||
"adapter_stderr_log_path": res.StderrLogPath,
|
"adapter_stderr_log_path": res.StderrLogPath,
|
||||||
|
"report_paths": reportMetadata,
|
||||||
}
|
}
|
||||||
if res.Metadata != nil {
|
if res.Metadata != nil {
|
||||||
meta["adapter_metadata"] = res.Metadata
|
meta["adapter_metadata"] = res.Metadata
|
||||||
@@ -140,7 +163,7 @@ func (polishStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*St
|
|||||||
|
|
||||||
return &StageResult{
|
return &StageResult{
|
||||||
Outputs: outputs,
|
Outputs: outputs,
|
||||||
Logs: []string{stdoutPath, stderrPath},
|
Logs: append([]string{stdoutPath, stderrPath}, reportPaths...),
|
||||||
GeneratedConfigs: []string{generatedConfigPath},
|
GeneratedConfigs: []string{generatedConfigPath},
|
||||||
Metadata: meta,
|
Metadata: meta,
|
||||||
}, nil
|
}, nil
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ package stage
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -15,6 +17,12 @@ import (
|
|||||||
"gitea.maximumdirect.net/eric/narratio/internal/manifest"
|
"gitea.maximumdirect.net/eric/narratio/internal/manifest"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type polishRunnerFunc func(context.Context, audita.PolishRequest) (audita.PolishResult, error)
|
||||||
|
|
||||||
|
func (run polishRunnerFunc) Run(ctx context.Context, req audita.PolishRequest) (audita.PolishResult, error) {
|
||||||
|
return run(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
func TestPolishStagePolishesMergedTranscriptAndRecordsMetadata(t *testing.T) {
|
func TestPolishStagePolishesMergedTranscriptAndRecordsMetadata(t *testing.T) {
|
||||||
env, m := setupPolishEnv(t)
|
env, m := setupPolishEnv(t)
|
||||||
paths := sessionPathsForEnv(env, m.SessionID)
|
paths := sessionPathsForEnv(env, m.SessionID)
|
||||||
@@ -50,8 +58,14 @@ func TestPolishStagePolishesMergedTranscriptAndRecordsMetadata(t *testing.T) {
|
|||||||
if result.Outputs[0].Kind != "transcript_polished" {
|
if result.Outputs[0].Kind != "transcript_polished" {
|
||||||
t.Fatalf("output[0] kind = %q, want transcript_polished", result.Outputs[0].Kind)
|
t.Fatalf("output[0] kind = %q, want transcript_polished", result.Outputs[0].Kind)
|
||||||
}
|
}
|
||||||
if len(result.Logs) != 2 {
|
if len(result.Logs) != 5 {
|
||||||
t.Fatalf("logs = %#v, want 2", result.Logs)
|
t.Fatalf("logs = %#v, want stdout, stderr, and three reports", result.Logs)
|
||||||
|
}
|
||||||
|
for _, name := range []string{"errors.yml", "recoveries.yml", "report.md"} {
|
||||||
|
path := filepath.Join(filepath.Dir(req.OutputProcessedPath), name)
|
||||||
|
if !slices.Contains(result.Logs, path) {
|
||||||
|
t.Fatalf("report %q missing from diagnostics: %#v", path, result.Logs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if len(result.GeneratedConfigs) != 1 {
|
if len(result.GeneratedConfigs) != 1 {
|
||||||
t.Fatalf("generated configs = %#v, want 1", result.GeneratedConfigs)
|
t.Fatalf("generated configs = %#v, want 1", result.GeneratedConfigs)
|
||||||
@@ -63,6 +77,9 @@ func TestPolishStagePolishesMergedTranscriptAndRecordsMetadata(t *testing.T) {
|
|||||||
if result.Metadata["output_schema"] != "audita-v1" {
|
if result.Metadata["output_schema"] != "audita-v1" {
|
||||||
t.Fatalf("metadata output_schema = %#v, want audita-v1", result.Metadata["output_schema"])
|
t.Fatalf("metadata output_schema = %#v, want audita-v1", result.Metadata["output_schema"])
|
||||||
}
|
}
|
||||||
|
if _, err := os.Stat(filepath.Join(paths.ArtifactsDir, "audita.report.json")); !errors.Is(err, os.ErrNotExist) {
|
||||||
|
t.Fatalf("legacy report destination exists: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPolishStageRejectsIncidentalMergedTranscriptPath(t *testing.T) {
|
func TestPolishStageRejectsIncidentalMergedTranscriptPath(t *testing.T) {
|
||||||
@@ -149,6 +166,58 @@ func TestPolishStageFailsWhenAdapterFails(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPolishStageDoesNotPromoteTranscriptAfterAdapterFailure(t *testing.T) {
|
||||||
|
env, m := setupPolishEnv(t)
|
||||||
|
env.Config.Session.Campaign = "sample-campaign"
|
||||||
|
m.Campaign = "sample-campaign"
|
||||||
|
m.RunID = "20260518T010203Z-abcdef12"
|
||||||
|
paths := sessionPathsForEnv(env, m.SessionID)
|
||||||
|
mergedPath := filepath.Join(paths.TranscriptsDir, "base.json")
|
||||||
|
writeFile(t, mergedPath, `{"segments":[]}`)
|
||||||
|
setCurrentBuiltInEvidence(t, m, "merge", artifacts.TranscriptOutputKindBase, artifacts.ArtifactTranscriptBase, mergedPath)
|
||||||
|
writeFile(t, filepath.Join(paths.InputsDir, "glossary.yml"), "terms: []\n")
|
||||||
|
failure := errors.New("audita exited after output")
|
||||||
|
var runOutput string
|
||||||
|
env.Audita = polishRunnerFunc(func(_ context.Context, req audita.PolishRequest) (audita.PolishResult, error) {
|
||||||
|
runOutput = req.OutputProcessedPath
|
||||||
|
writeFile(t, runOutput, `{"segments":[]}`)
|
||||||
|
writeFile(t, filepath.Join(filepath.Dir(runOutput), "errors.yml"), "status: failed\n")
|
||||||
|
return audita.PolishResult{ProcessedTranscriptPath: runOutput}, failure
|
||||||
|
})
|
||||||
|
_, err := (polishStage{}).Run(context.Background(), env, m)
|
||||||
|
if !errors.Is(err, failure) || !strings.Contains(err.Error(), filepath.Dir(runOutput)) {
|
||||||
|
t.Fatalf("error = %v, want original failure with output directory", err)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(filepath.Join(paths.TranscriptsDir, "polished.json")); !errors.Is(err, os.ErrNotExist) {
|
||||||
|
t.Fatalf("failed output was promoted: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(filepath.Join(filepath.Dir(runOutput), "errors.yml")); err != nil {
|
||||||
|
t.Fatalf("run-local diagnostic missing: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPolishStageRejectsRedirectedReportPath(t *testing.T) {
|
||||||
|
env, m := setupPolishEnv(t)
|
||||||
|
env.Config.Session.Campaign = "sample-campaign"
|
||||||
|
m.Campaign = "sample-campaign"
|
||||||
|
m.RunID = "20260518T010203Z-abcdef12"
|
||||||
|
paths := sessionPathsForEnv(env, m.SessionID)
|
||||||
|
mergedPath := filepath.Join(paths.TranscriptsDir, "base.json")
|
||||||
|
writeFile(t, mergedPath, `{"segments":[]}`)
|
||||||
|
setCurrentBuiltInEvidence(t, m, "merge", artifacts.TranscriptOutputKindBase, artifacts.ArtifactTranscriptBase, mergedPath)
|
||||||
|
writeFile(t, filepath.Join(paths.InputsDir, "glossary.yml"), "terms: []\n")
|
||||||
|
redirected := filepath.Join(t.TempDir(), "errors.yml")
|
||||||
|
writeFile(t, redirected, "status: completed\n")
|
||||||
|
env.Audita = &audita.FakeRunner{Result: audita.PolishResult{Reports: audita.ReportBundle{ErrorsPath: redirected}}}
|
||||||
|
_, err := (polishStage{}).Run(context.Background(), env, m)
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "did not match requested path") {
|
||||||
|
t.Fatalf("error = %v, want authoritative report path rejection", err)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(filepath.Join(paths.TranscriptsDir, "polished.json")); !errors.Is(err, os.ErrNotExist) {
|
||||||
|
t.Fatalf("report-path failure promoted output: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPolishStageFailsWhenProcessedOutputInvalid(t *testing.T) {
|
func TestPolishStageFailsWhenProcessedOutputInvalid(t *testing.T) {
|
||||||
env, m := setupPolishEnv(t)
|
env, m := setupPolishEnv(t)
|
||||||
paths := sessionPathsForEnv(env, m.SessionID)
|
paths := sessionPathsForEnv(env, m.SessionID)
|
||||||
|
|||||||
@@ -1131,6 +1131,32 @@ func TestPublishFailsWithoutObjectStore(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPublishArchivesCurrentPolishReportsOnlyAsDiagnostics(t *testing.T) {
|
||||||
|
env, m, runRoot := publishFixture(t)
|
||||||
|
polish := m.Stages["polish"]
|
||||||
|
polish.Outputs = nil
|
||||||
|
for _, name := range []string{"errors.yml", "recoveries.yml", "report.md"} {
|
||||||
|
path := filepath.Join(runRoot, "polish", "outputs", "transcripts", name)
|
||||||
|
writeStageTestFile(t, path, "diagnostic\n")
|
||||||
|
polish.Logs = append(polish.Logs, path)
|
||||||
|
}
|
||||||
|
if _, err := (publishStage{}).Run(context.Background(), env, m); err != nil {
|
||||||
|
t.Fatalf("publish reports: %v", err)
|
||||||
|
}
|
||||||
|
fake := env.ObjectStore.(*storage.FakeBackend)
|
||||||
|
for _, name := range []string{"errors.yml", "recoveries.yml", "report.md"} {
|
||||||
|
relative := "polish/outputs/transcripts/" + name
|
||||||
|
if _, ok := fake.Objects[m.S3RunPrefix+relative]; !ok {
|
||||||
|
t.Fatalf("missing run-archive diagnostic %q", relative)
|
||||||
|
}
|
||||||
|
for key := range fake.Objects {
|
||||||
|
if strings.Contains(key, "published/"+name) {
|
||||||
|
t.Fatalf("report was published as canonical output: %q", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func publishFixture(t *testing.T) (*Env, *manifest.Manifest, string) {
|
func publishFixture(t *testing.T) (*Env, *manifest.Manifest, string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user