package checkpoint import ( "bytes" "encoding/json" "os" "path/filepath" "runtime" "strings" "testing" "unicode/utf8" "gitea.maximumdirect.net/eric/notarius/internal/core/source" "gitea.maximumdirect.net/eric/notarius/internal/framework/contracts" "gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline" ) type filesystemCheckpointFixture struct { root string identity Identity loader pipeline.CheckpointLoader doc source.SourceDocument extract pipeline.CheckpointArtifact merge pipeline.CheckpointArtifact normalize pipeline.CheckpointArtifact dependencies []pipeline.CheckpointFingerprint warnings []contracts.Warning rejected []contracts.RejectedOutput } func TestFilesystemCheckpointRoundTripsAllStages(t *testing.T) { fixture := seedFilesystemCheckpoints(t) // Recording owns its inputs. These mutations must not change the durable values. fixture.doc.Units[0].Text = "caller mutation" fixture.doc.Metadata["owner"] = "caller mutation" fixture.extract.Artifact.Content[0] = 'x' fixture.extract.Artifact.Metadata["content"] = "caller mutation" fixture.merge.Artifact.Content[0] = 'x' fixture.normalize.Artifact.Content[0] = 'x' fixture.warnings[0].Message = "caller mutation" fixture.rejected[0].Message = "caller mutation" t.Run("source", func(t *testing.T) { got, decision := fixture.loader.Source("source-module") if !decision.Reused { t.Fatalf("source decision = %#v", decision) } if got.Document == nil || got.Document.ID != "document-1" || got.Document.Units[0].Text != "original source" || got.Document.Metadata["owner"] != "fixture" { t.Fatalf("source was not restored: %#v", got) } got.Document.Units[0].Text = "loaded mutation" got.Document.Metadata["owner"] = "loaded mutation" reloaded, decision := fixture.loader.Source("source-module") if !decision.Reused || reloaded.Document.Units[0].Text != "original source" || reloaded.Document.Metadata["owner"] != "fixture" { t.Fatalf("source reload changed after loaded mutation: %#v decision=%#v", reloaded, decision) } }) t.Run("extract", func(t *testing.T) { got, decision := fixture.loader.Extract("lane-a", "extract-module", fixture.dependencies) if !decision.Reused || len(got.Outputs) != 1 || len(got.Rejected) != 1 || len(got.Warnings) != 1 { t.Fatalf("extract result=%#v decision=%#v", got, decision) } output := got.Outputs[0] if !bytes.Equal(output.Artifact.Content, []byte(`{"spell":"fire"}`)) || output.Artifact.Kind != "spell" || output.Artifact.Schema.ID != "spell-schema" || output.Artifact.Schema.Version != "1" || output.Artifact.MediaType != "application/json" || output.Artifact.Metadata["chunk"] != "chunk-a" || output.ChunkRef.StartUnitID != 1 || got.Warnings[0].ReasonCode != "partial" || got.Rejected[0].ReasonCode != "invalid_source" { t.Fatalf("extract values were not restored: %#v", got) } manifest := readManifest[ExtractLaneManifest](t, filepath.Join(fixture.root, mustRelativePath(t, fixture.identity), "extract", "lane-a", "manifest.json")) if manifest.Status != StatusSucceededWithRejections { t.Fatalf("extract status = %q, want succeeded with rejections", manifest.Status) } got.Outputs[0].Artifact.Content[0] = 'y' got.Warnings[0].Message = "loaded mutation" reloaded, decision := fixture.loader.Extract("lane-a", "extract-module", fixture.dependencies) if !decision.Reused || !bytes.Equal(reloaded.Outputs[0].Artifact.Content, []byte(`{"spell":"fire"}`)) || reloaded.Warnings[0].Message != "partial output" { t.Fatalf("extract reload changed after loaded mutation: %#v decision=%#v", reloaded, decision) } }) for _, tt := range []struct { name string load func() (pipeline.CheckpointArtifact, []contracts.Warning, pipeline.CheckpointDecision) want []byte }{ {name: "merge", load: func() (pipeline.CheckpointArtifact, []contracts.Warning, pipeline.CheckpointDecision) { got, decision := fixture.loader.Merge("lane-a", "merge-module", fixture.dependencies) return got.Output, got.Warnings, decision }, want: []byte(`{"spells":["fire"]}`)}, {name: "normalize", load: func() (pipeline.CheckpointArtifact, []contracts.Warning, pipeline.CheckpointDecision) { got, decision := fixture.loader.Normalize("lane-a", "normalize-module", fixture.dependencies) return got.Output, got.Warnings, decision }, want: []byte(`{"spells":["fire"],"normalized":true}`)}, } { t.Run(tt.name, func(t *testing.T) { got, warnings, decision := tt.load() if !decision.Reused || !bytes.Equal(got.Artifact.Content, tt.want) || got.Artifact.Kind != "spell" || got.Artifact.Schema.ID != "spell-schema" || got.Artifact.Schema.Version != "1" || got.Artifact.Metadata["lane"] != "lane-a" || len(warnings) != 1 || warnings[0].ReasonCode != "review" { t.Fatalf("%s result=%#v warnings=%#v decision=%#v", tt.name, got, warnings, decision) } got.Artifact.Content[0] = 'z' reloaded, warnings, decision := tt.load() if !decision.Reused || !bytes.Equal(reloaded.Artifact.Content, tt.want) || warnings[0].Message != "review manually" { t.Fatalf("%s reload changed after loaded mutation: %#v warnings=%#v decision=%#v", tt.name, reloaded, warnings, decision) } }) } if runtime.GOOS != "windows" { t.Run("restrictive permissions", func(t *testing.T) { root := filepath.Join(fixture.root, mustRelativePath(t, fixture.identity)) err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if err != nil { return err } want := os.FileMode(0o600) if info.IsDir() { want = 0o700 } if got := info.Mode().Perm(); got != want { t.Errorf("%s permissions = %o, want %o", path, got, want) } return nil }) if err != nil { t.Fatal(err) } }) } } func TestFilesystemCheckpointRejectsMissingAndCorruptState(t *testing.T) { for _, stage := range checkpointStages() { t.Run(stage.name+" missing manifest", func(t *testing.T) { fixture := seedFilesystemCheckpoints(t) if err := os.Remove(stage.manifest(fixture)); err != nil { t.Fatal(err) } assertStageNotReused(t, stage, fixture, pipeline.CheckpointReasonMissing) }) t.Run(stage.name+" malformed manifest", func(t *testing.T) { fixture := seedFilesystemCheckpoints(t) if err := os.WriteFile(stage.manifest(fixture), []byte("{"), 0o600); err != nil { t.Fatal(err) } assertStageNotReused(t, stage, fixture, pipeline.CheckpointReasonDecodeFailed) }) } } func TestFilesystemCheckpointRejectsIncompatibleManifests(t *testing.T) { for _, tt := range []struct { name string edit func(map[string]any) want pipeline.CheckpointReasonCode }{ {"v1 schema", func(m map[string]any) { m["workspace_schema_version"] = WorkspaceSchemaVersionV1 }, pipeline.CheckpointReasonWorkspaceSchemaIncompatible}, {"v2 schema", func(m map[string]any) { m["workspace_schema_version"] = WorkspaceSchemaVersionV2 }, pipeline.CheckpointReasonWorkspaceSchemaIncompatible}, {"unknown schema", func(m map[string]any) { m["workspace_schema_version"] = "notarius.workspace.future" }, pipeline.CheckpointReasonWorkspaceSchemaIncompatible}, {"identity", func(m map[string]any) { m["metadata"].(map[string]any)["checkpoint_identity_digest"] = "sha256:other" }, pipeline.CheckpointReasonIdentityMismatch}, {"stage", func(m map[string]any) { m["stage"] = string(StageMerge) }, pipeline.CheckpointReasonStageMismatch}, {"lane", func(m map[string]any) { m["lane_id"] = "lane-other" }, pipeline.CheckpointReasonLaneMismatch}, {"module", func(m map[string]any) { m["module_key"] = "module-other" }, pipeline.CheckpointReasonModuleMismatch}, {"dependency", func(m map[string]any) { m["dependency_fingerprints"] = []map[string]string{{"name": "input", "value": "other"}} }, pipeline.CheckpointReasonDependencyMismatch}, } { t.Run(tt.name, func(t *testing.T) { fixture := seedFilesystemCheckpoints(t) editManifest(t, checkpointStages()[1].manifest(fixture), tt.edit) assertStageNotReused(t, checkpointStages()[1], fixture, tt.want) }) } } func TestFilesystemCheckpointRejectsNonTerminalStatuses(t *testing.T) { for _, status := range []StageStatus{StatusRunning, StatusFailed, StatusPending, StatusInvalidated} { t.Run(string(status), func(t *testing.T) { fixture := seedFilesystemCheckpoints(t) editManifest(t, checkpointStages()[1].manifest(fixture), func(m map[string]any) { m["status"] = string(status) }) assertStageNotReused(t, checkpointStages()[1], fixture, pipeline.CheckpointReasonStatusNotReusable) }) } } func TestFilesystemCheckpointRejectsIncompleteArtifactsAndContent(t *testing.T) { for _, tt := range []struct { name string edit func(map[string]any) want pipeline.CheckpointReasonCode }{ {"artifact kind", func(m map[string]any) { m["outputs"].([]any)[0].(map[string]any)["artifact_kind"] = "" }, pipeline.CheckpointReasonArtifactCodecIncompatible}, {"schema id", func(m map[string]any) { m["outputs"].([]any)[0].(map[string]any)["schema"].(map[string]any)["id"] = "" }, pipeline.CheckpointReasonArtifactCodecIncompatible}, {"schema version", func(m map[string]any) { m["outputs"].([]any)[0].(map[string]any)["schema"].(map[string]any)["version"] = "" }, pipeline.CheckpointReasonArtifactCodecIncompatible}, {"schema digest", func(m map[string]any) { m["outputs"].([]any)[0].(map[string]any)["schema_digest"] = "" }, pipeline.CheckpointReasonArtifactCodecIncompatible}, {"base64", func(m map[string]any) { m["outputs"].([]any)[0].(map[string]any)["content"].(map[string]any)["content_base64"] = "%" }, pipeline.CheckpointReasonArtifactPayloadInvalid}, {"content digest", func(m map[string]any) { m["outputs"].([]any)[0].(map[string]any)["content"].(map[string]any)["content_digest"] = "sha256:other" }, pipeline.CheckpointReasonArtifactDigestMismatch}, } { t.Run(tt.name, func(t *testing.T) { fixture := seedFilesystemCheckpoints(t) editJSON(t, filepath.Join(fixture.root, mustRelativePath(t, fixture.identity), "extract", "lane-a", "outputs.json"), tt.edit) assertStageNotReused(t, checkpointStages()[1], fixture, tt.want) }) } } func TestFilesystemCheckpointRejectsSourceAndOutputDigestMismatches(t *testing.T) { t.Run("invalid source document", func(t *testing.T) { fixture := seedFilesystemCheckpoints(t) editJSON(t, filepath.Join(fixture.root, mustRelativePath(t, fixture.identity), "source", "source-document.json"), func(m map[string]any) { m["document"].(map[string]any)["units"].([]any)[0].(map[string]any)["text"] = "" }) assertStageNotReused(t, checkpointStages()[0], fixture, pipeline.CheckpointReasonArtifactPayloadInvalid) }) t.Run("source output digest", func(t *testing.T) { fixture := seedFilesystemCheckpoints(t) editJSON(t, filepath.Join(fixture.root, mustRelativePath(t, fixture.identity), "source", "source-document.json"), func(m map[string]any) { m["document"].(map[string]any)["digest"] = "sha256:other" }) assertStageNotReused(t, checkpointStages()[0], fixture, pipeline.CheckpointReasonArtifactDigestMismatch) }) for _, stage := range checkpointStages()[1:] { t.Run(stage.name+" output digest", func(t *testing.T) { fixture := seedFilesystemCheckpoints(t) editManifest(t, stage.manifest(fixture), func(m map[string]any) { m["output_digests"].([]any)[0].(map[string]any)["value"] = "sha256:other" }) assertStageNotReused(t, stage, fixture, pipeline.CheckpointReasonArtifactDigestMismatch) }) } } func TestFilesystemCheckpointReusesExtractWithRejections(t *testing.T) { fixture := seedFilesystemCheckpoints(t) result, decision := fixture.loader.Extract("lane-a", "extract-module", fixture.dependencies) if !decision.Reused || len(result.Rejected) != 1 || result.Rejected[0].Message != "source reference is invalid" { t.Fatalf("result=%#v decision=%#v", result, decision) } } func TestFilesystemCheckpointDependencyDecisionIsBoundedAndCategorized(t *testing.T) { fixture := seedFilesystemCheckpoints(t) _, decision := fixture.loader.Extract("lane-a", "extract-module", []pipeline.CheckpointFingerprint{{Name: "source", Value: "sha256:changed"}}) if decision.Reused || decision.Category != "dependency_invalidated" || decision.ReasonCode != "dependency_mismatch" { t.Fatalf("dependency decision = %#v", decision) } if strings.Contains(decision.Reason, fixture.root) || strings.Contains(decision.Detail, fixture.root) { t.Fatalf("dependency decision leaked checkpoint path: %#v", decision) } } func TestFilesystemCheckpointDecisionFamiliesAreStableAndSafe(t *testing.T) { const secretSentinel = "do-not-expose-checkpoint-secret" tests := []struct { name string prepare func(*testing.T, filesystemCheckpointFixture) pipeline.CheckpointDecision category pipeline.CheckpointDecisionCategory code pipeline.CheckpointReasonCode }{ {"loading disabled", func(t *testing.T, _ filesystemCheckpointFixture) pipeline.CheckpointDecision { _, decision := pipeline.NoopCheckpointLoader().Source("source") return decision }, pipeline.CheckpointDecisionExecuted, pipeline.CheckpointReasonLoadingDisabled}, {"checkpoint unavailable", func(t *testing.T, fixture filesystemCheckpointFixture) pipeline.CheckpointDecision { if err := os.Remove(checkpointStages()[1].manifest(fixture)); err != nil { t.Fatal(err) } return checkpointStages()[1].load(fixture) }, pipeline.CheckpointDecisionExecuted, pipeline.CheckpointReasonMissing}, {"workspace identity", func(t *testing.T, fixture filesystemCheckpointFixture) pipeline.CheckpointDecision { editManifest(t, checkpointStages()[1].manifest(fixture), func(m map[string]any) { m["workspace_schema_version"] = secretSentinel }) return checkpointStages()[1].load(fixture) }, pipeline.CheckpointDecisionExecuted, pipeline.CheckpointReasonWorkspaceSchemaIncompatible}, {"manifest scope", func(t *testing.T, fixture filesystemCheckpointFixture) pipeline.CheckpointDecision { editManifest(t, checkpointStages()[1].manifest(fixture), func(m map[string]any) { m["module_key"] = secretSentinel }) return checkpointStages()[1].load(fixture) }, pipeline.CheckpointDecisionExecuted, pipeline.CheckpointReasonModuleMismatch}, {"dependency invalidated", func(t *testing.T, fixture filesystemCheckpointFixture) pipeline.CheckpointDecision { _, decision := fixture.loader.Extract("lane-a", "extract-module", []pipeline.CheckpointFingerprint{{Name: "input", Value: secretSentinel}}) return decision }, pipeline.CheckpointDecisionDependencyInvalidated, pipeline.CheckpointReasonDependencyMismatch}, {"artifact payload", func(t *testing.T, fixture filesystemCheckpointFixture) pipeline.CheckpointDecision { editJSON(t, filepath.Join(fixture.root, mustRelativePath(t, fixture.identity), "extract", "lane-a", "outputs.json"), func(m map[string]any) { m["outputs"].([]any)[0].(map[string]any)["artifact_kind"] = "" m["outputs"].([]any)[0].(map[string]any)["source_id"] = secretSentinel }) return checkpointStages()[1].load(fixture) }, pipeline.CheckpointDecisionExecuted, pipeline.CheckpointReasonArtifactCodecIncompatible}, {"checkpoint reused", func(t *testing.T, fixture filesystemCheckpointFixture) pipeline.CheckpointDecision { return checkpointStages()[1].load(fixture) }, pipeline.CheckpointDecisionReused, pipeline.CheckpointReasonReused}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { fixture := seedFilesystemCheckpoints(t) decision := test.prepare(t, fixture) if decision.Category != test.category || decision.ReasonCode != test.code { t.Fatalf("decision = %#v, want category %q and code %q", decision, test.category, test.code) } if len([]byte(decision.Detail)) > 512 || !utf8.ValidString(decision.Detail) { t.Fatalf("decision detail is not bounded valid UTF-8: %#v", decision) } for _, forbidden := range []string{secretSentinel, fixture.root} { if strings.Contains(decision.Detail, forbidden) || strings.Contains(decision.Reason, forbidden) { t.Fatalf("decision leaked %q: %#v", forbidden, decision) } } }) } } func TestFilesystemLoaderReadsAcceptedNormalizeWithoutStageDependencies(t *testing.T) { fixture := seedAcceptedNormalizeCheckpoint(t) identityRoot := filepath.Join(fixture.root, mustRelativePath(t, fixture.identity)) for _, stage := range []string{"extract", "merge"} { if _, err := os.Stat(filepath.Join(identityRoot, laneManifestPath(stage, "step-1", "lane-a"))); !os.IsNotExist(err) { t.Fatalf("%s checkpoint stat error = %v, want absent prerequisite", stage, err) } } checkpoint, decision := fixture.loader.AcceptedNormalize("step-1", "lane-a", "normalize-module") if !decision.Reused || decision.Category != pipeline.CheckpointDecisionReused || decision.ReasonCode != pipeline.CheckpointReasonAcceptedArtifactReused { t.Fatalf("accepted normalize decision = %#v", decision) } if checkpoint.Output.Artifact.Content == nil || string(checkpoint.Output.Artifact.Content) != string(fixture.normalize.Artifact.Content) { t.Fatalf("accepted normalize output = %#v, want recorded artifact", checkpoint.Output) } if len(checkpoint.Warnings) != 1 || checkpoint.Warnings[0].ReasonCode != "normalized" { t.Fatalf("accepted normalize warnings = %#v", checkpoint.Warnings) } } func TestFilesystemLoaderRejectsInvalidAcceptedNormalize(t *testing.T) { tests := []struct { name string mutate func(*testing.T, *filesystemCheckpointFixture) code pipeline.CheckpointReasonCode }{ {"missing", func(t *testing.T, fixture *filesystemCheckpointFixture) { if err := os.Remove(acceptedNormalizeManifest(t, *fixture)); err != nil { t.Fatal(err) } }, pipeline.CheckpointReasonMissing}, {"rejected status", func(t *testing.T, fixture *filesystemCheckpointFixture) { editManifest(t, acceptedNormalizeManifest(t, *fixture), func(m map[string]any) { m["status"] = string(StatusSucceededWithRejections) }) }, pipeline.CheckpointReasonStatusNotReusable}, {"corrupt payload", func(t *testing.T, fixture *filesystemCheckpointFixture) { if err := os.WriteFile(acceptedNormalizePayload(t, *fixture), []byte("{"), 0o600); err != nil { t.Fatal(err) } }, pipeline.CheckpointReasonDecodeFailed}, {"wrong codec identity", func(t *testing.T, fixture *filesystemCheckpointFixture) { editJSON(t, acceptedNormalizePayload(t, *fixture), func(m map[string]any) { m["output"].(map[string]any)["artifact_kind"] = "" }) }, pipeline.CheckpointReasonArtifactCodecIncompatible}, {"wrong content digest", func(t *testing.T, fixture *filesystemCheckpointFixture) { editJSON(t, acceptedNormalizePayload(t, *fixture), func(m map[string]any) { m["output"].(map[string]any)["content"].(map[string]any)["content_digest"] = "sha256:wrong" }) }, pipeline.CheckpointReasonArtifactDigestMismatch}, {"unverifiable identity", func(t *testing.T, fixture *filesystemCheckpointFixture) { loader := fixture.loader.(*FilesystemLoader) fixture.loader = &FilesystemLoader{root: loader.root} }, pipeline.CheckpointReasonIdentityMismatch}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { fixture := seedAcceptedNormalizeCheckpoint(t) test.mutate(t, &fixture) _, decision := fixture.loader.AcceptedNormalize("step-1", "lane-a", "normalize-module") if decision.Reused || decision.ReasonCode != test.code { t.Fatalf("accepted normalize decision = %#v, want %q", decision, test.code) } if strings.Contains(decision.Detail, fixture.root) || strings.Contains(decision.Detail, string(fixture.normalize.Artifact.Content)) { t.Fatalf("accepted normalize decision leaked path or content: %#v", decision) } if _, err := os.Stat(acceptedNormalizePayload(t, fixture)); err != nil { t.Fatalf("accepted normalize payload was removed after rejection: %v", err) } }) } } func seedAcceptedNormalizeCheckpoint(t *testing.T) filesystemCheckpointFixture { t.Helper() root := t.TempDir() identity := testIdentity(t) artifact := checkpointArtifact("normalize-module", `{"accepted":true}`) recorder, err := NewFilesystemRecorder(root, identity) if err != nil { t.Fatal(err) } stepRecorder := recorder.(pipeline.StepCheckpointRecorder) warnings := []contracts.Warning{{Scope: "normalize", ReasonCode: "normalized", Message: "normalized warning"}} if err := stepRecorder.NormalizeSucceededForStep("step-1", "lane-a", "normalize-module", []pipeline.CheckpointFingerprint{{Name: "merge", Value: "sha256:unavailable"}}, artifact, warnings); err != nil { t.Fatal(err) } loader, err := NewFilesystemLoader(root, identity) if err != nil { t.Fatal(err) } return filesystemCheckpointFixture{root: root, identity: identity, loader: loader, normalize: artifact} } func acceptedNormalizeManifest(t *testing.T, fixture filesystemCheckpointFixture) string { t.Helper() return filepath.Join(fixture.root, mustRelativePath(t, fixture.identity), laneManifestPath("normalize", "step-1", "lane-a")) } func acceptedNormalizePayload(t *testing.T, fixture filesystemCheckpointFixture) string { t.Helper() return filepath.Join(fixture.root, mustRelativePath(t, fixture.identity), lanePayloadPath("normalize", "step-1", "lane-a", "output.json")) } type checkpointStage struct { name string manifest func(filesystemCheckpointFixture) string load func(filesystemCheckpointFixture) pipeline.CheckpointDecision } func checkpointStages() []checkpointStage { return []checkpointStage{ { name: "source", manifest: func(f filesystemCheckpointFixture) string { return filepath.Join(f.root, mustRelativePathForTest(f.identity), "source", "manifest.json") }, load: func(f filesystemCheckpointFixture) pipeline.CheckpointDecision { _, d := f.loader.Source("source-module") return d }, }, { name: "extract", manifest: func(f filesystemCheckpointFixture) string { return filepath.Join(f.root, mustRelativePathForTest(f.identity), "extract", "lane-a", "manifest.json") }, load: func(f filesystemCheckpointFixture) pipeline.CheckpointDecision { _, d := f.loader.Extract("lane-a", "extract-module", f.dependencies) return d }, }, { name: "merge", manifest: func(f filesystemCheckpointFixture) string { return filepath.Join(f.root, mustRelativePathForTest(f.identity), "merge", "lane-a", "manifest.json") }, load: func(f filesystemCheckpointFixture) pipeline.CheckpointDecision { _, d := f.loader.Merge("lane-a", "merge-module", f.dependencies) return d }, }, { name: "normalize", manifest: func(f filesystemCheckpointFixture) string { return filepath.Join(f.root, mustRelativePathForTest(f.identity), "normalize", "lane-a", "manifest.json") }, load: func(f filesystemCheckpointFixture) pipeline.CheckpointDecision { _, d := f.loader.Normalize("lane-a", "normalize-module", f.dependencies) return d }, }, } } func seedFilesystemCheckpoints(t *testing.T) filesystemCheckpointFixture { t.Helper() root := t.TempDir() identity, err := NewIdentity(representativeIdentityInput()) if err != nil { t.Fatal(err) } recorder, err := NewFilesystemRecorder(root, identity) if err != nil { t.Fatal(err) } fixture := filesystemCheckpointFixture{ root: root, identity: identity, doc: checkpointDocument(), extract: checkpointArtifact("extract", `{"spell":"fire"}`), merge: checkpointArtifact("merge", `{"spells":["fire"]}`), normalize: checkpointArtifact("normalize", `{"spells":["fire"],"normalized":true}`), dependencies: []pipeline.CheckpointFingerprint{{Name: "source", Value: "sha256:source"}, {Name: "chunk-plan", Value: "sha256:plan"}}, warnings: []contracts.Warning{{Scope: "extract", ReasonCode: "partial", Message: "partial output"}}, rejected: []contracts.RejectedOutput{{Stage: "extract", LaneID: "lane-a", ModuleKey: "extract-module", ChunkID: "chunk-a", ValidatorName: "source_refs", ReasonCode: "invalid_source", Message: "source reference is invalid", AttemptCount: 1}}, } if err := recorder.SourceSucceeded("source-module", &fixture.doc); err != nil { t.Fatal(err) } if err := recorder.ExtractSucceeded("lane-a", "extract-module", fixture.dependencies, []pipeline.CheckpointArtifact{fixture.extract}, fixture.rejected, fixture.warnings); err != nil { t.Fatal(err) } mergeWarnings := []contracts.Warning{{Scope: "merge", ReasonCode: "review", Message: "review manually"}} if err := recorder.MergeSucceeded("lane-a", "merge-module", fixture.dependencies, fixture.merge, mergeWarnings); err != nil { t.Fatal(err) } if err := recorder.NormalizeSucceeded("lane-a", "normalize-module", fixture.dependencies, fixture.normalize, mergeWarnings); err != nil { t.Fatal(err) } fixture.loader, err = NewFilesystemLoader(root, identity) if err != nil { t.Fatal(err) } return fixture } func checkpointDocument() source.SourceDocument { return source.SourceDocument{ ID: "document-1", Kind: "transcript", Format: "text", Digest: "sha256:document", Metadata: map[string]any{"owner": "fixture", "number": float64(1)}, Units: []source.SourceUnit{{ID: 1, Kind: "line", Text: "original source", Ref: source.SourceRef{SourceID: "document-1", StartUnitID: 1, EndUnitID: 1}, Metadata: map[string]any{"speaker": "narrator"}}}, } } func checkpointArtifact(module, content string) pipeline.CheckpointArtifact { return pipeline.CheckpointArtifact{ LaneID: "lane-a", ModuleKey: module, SourceID: "document-1", ChunkID: "chunk-a", ChunkIndex: 0, ChunkRef: source.SourceRef{SourceID: "document-1", StartUnitID: 1, EndUnitID: 1}, SchemaDigest: "sha256:schema", Artifact: contracts.SerializedArtifact{Kind: "spell", Schema: contracts.ArtifactSchema{ID: "spell-schema", Name: "Spell", Version: "1", JSONSchema: []byte(`{"type":"object"}`)}, MediaType: "application/json", Content: []byte(content), Metadata: map[string]any{"chunk": "chunk-a", "lane": "lane-a"}}, } } func assertStageNotReused(t *testing.T, stage checkpointStage, fixture filesystemCheckpointFixture, want pipeline.CheckpointReasonCode) { t.Helper() decision := stage.load(fixture) if decision.Reused || decision.ReasonCode != want { t.Fatalf("%s decision=%#v, want non-reused reason code %q", stage.name, decision, want) } } func editManifest(t *testing.T, path string, edit func(map[string]any)) { t.Helper() editJSON(t, path, edit) } func editJSON(t *testing.T, path string, edit func(map[string]any)) { t.Helper() data, err := os.ReadFile(path) if err != nil { t.Fatal(err) } var value map[string]any if err := json.Unmarshal(data, &value); err != nil { t.Fatal(err) } edit(value) data, err = json.Marshal(value) if err != nil { t.Fatal(err) } if err := os.WriteFile(path, data, 0o600); err != nil { t.Fatal(err) } } func readManifest[T any](t *testing.T, path string) T { t.Helper() data, err := os.ReadFile(path) if err != nil { t.Fatal(err) } var value T if err := json.Unmarshal(data, &value); err != nil { t.Fatal(err) } return value } func mustRelativePath(t *testing.T, identity Identity) string { t.Helper() return mustRelativePathForTest(identity) } func mustRelativePathForTest(identity Identity) string { path, err := identity.RelativePath() if err != nil { panic(err) } return path }