package pipeline import ( "context" "encoding/json" "errors" "reflect" "strings" "testing" "time" "gitea.maximumdirect.net/eric/notarius/internal/core/source" "gitea.maximumdirect.net/eric/notarius/internal/framework/chunkmap" "gitea.maximumdirect.net/eric/notarius/internal/framework/contracts" ) type recordingChunkPlanStore struct { record ChunkPlanRecord decision ChunkPlanDecision loadErr error saveErr error loads int saves int saved ChunkPlanRecord } func (s *recordingChunkPlanStore) Load(string) (ChunkPlanRecord, ChunkPlanDecision, error) { s.loads++ return s.record, s.decision, s.loadErr } func (s *recordingChunkPlanStore) Save(record ChunkPlanRecord) error { s.saves++ s.saved = record return s.saveErr } type countingChunkValidator struct { calls int result contracts.ValidationResult err error } type capturingChunkMapOutput struct { requests []contracts.OutputRequest } func (*capturingChunkMapOutput) Key() string { return "capture/chunk-map" } func (output *capturingChunkMapOutput) Encode(_ context.Context, request contracts.OutputRequest) (contracts.OutputResult, error) { output.requests = append(output.requests, request) return contracts.OutputResult{}, nil } func (*countingChunkValidator) Name() string { return "test/counting-chunks" } func (*countingChunkValidator) ExecutionClass() contracts.ExecutionClass { return contracts.ExecutionClassDeterministic } func (v *countingChunkValidator) Validate(context.Context, contracts.ChunkValidationRequest) (contracts.ValidationResult, error) { v.calls++ return v.result, v.err } type manifestChunker struct { terminalChunker metadata map[string]any } func (c manifestChunker) ManifestMetadata() map[string]any { return c.metadata } func (manifestChunker) ExecutionClass() contracts.ExecutionClass { return contracts.ExecutionClassLLMBacked } type llmCountingChunker struct { terminalChunker llmCalls *int } func (llmCountingChunker) ExecutionClass() contracts.ExecutionClass { return contracts.ExecutionClassLLMBacked } func (c llmCountingChunker) Plan(ctx context.Context, request contracts.ChunkRequest) (contracts.ChunkPlanResult, error) { (*c.llmCalls)++ return c.terminalChunker.Plan(ctx, request) } type deterministicChunker struct{ terminalChunker } func (deterministicChunker) ExecutionClass() contracts.ExecutionClass { return contracts.ExecutionClassDeterministic } type retryingChunker struct { key string plan source.ChunkPlan calls int } func (c *retryingChunker) Key() string { return c.key } func (*retryingChunker) ReferenceSlots() []contracts.ReferenceSlot { return nil } func (c *retryingChunker) Plan(context.Context, contracts.ChunkRequest) (contracts.ChunkPlanResult, error) { c.calls++ if c.calls == 1 { return contracts.ChunkPlanResult{Warnings: []contracts.Warning{{Scope: "discarded", ReasonCode: "retry", Message: "discarded warning"}}}, errors.New("retry generation") } return contracts.ChunkPlanResult{Plan: source.CloneChunkPlan(c.plan), Warnings: []contracts.Warning{{Scope: "accepted", ReasonCode: "observed", Message: "accepted warning"}}}, nil } type dependencyLoader struct { CheckpointLoader extractDependencies []CheckpointFingerprint } func (l *dependencyLoader) Extract(_ string, _ string, dependencies []CheckpointFingerprint) (ExtractCheckpoint, CheckpointDecision) { l.extractDependencies = append([]CheckpointFingerprint(nil), dependencies...) return ExtractCheckpoint{}, CheckpointDecision{Reason: "not found"} } func TestRunnerChunkPlanModeMatrix(t *testing.T) { tests := []struct { name string mode ChunkCacheMode decision ChunkPlanDecision wantLoads int wantSaves int wantCalls int wantAction string }{ {name: "auto hit", mode: ChunkCacheAuto, decision: ChunkPlanDecision{Status: ChunkPlanHit}, wantLoads: 1, wantAction: "reused"}, {name: "auto missing", mode: ChunkCacheAuto, decision: ChunkPlanDecision{Status: ChunkPlanMissing}, wantLoads: 1, wantSaves: 1, wantCalls: 1, wantAction: "generated"}, {name: "auto invalid", mode: ChunkCacheAuto, decision: ChunkPlanDecision{Status: ChunkPlanInvalid}, wantLoads: 1, wantSaves: 1, wantCalls: 1, wantAction: "generated"}, {name: "bypass", mode: ChunkCacheBypass, wantCalls: 1, wantAction: "bypassed"}, {name: "empty bypass", wantCalls: 1, wantAction: "bypassed"}, {name: "refresh", mode: ChunkCacheRefresh, wantSaves: 1, wantCalls: 1, wantAction: "refreshed"}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { prepared, plan := preparedTerminalDebugPipeline(t) calls := 0 llmCalls := 0 prepared.chunker = llmCountingChunker{terminalChunker: terminalChunker{key: prepared.resolved.Chunk.Module, plan: plan, calls: &calls}, llmCalls: &llmCalls} store := &recordingChunkPlanStore{record: chunkPlanRecord(t, prepared, plan), decision: tc.decision} output, err := New().Run(context.Background(), RunInput{Prepared: prepared, RawInput: []byte("input"), ChunkCacheMode: tc.mode, ChunkPlans: store}) if err != nil { t.Fatalf("Run() error = %v", err) } if store.loads != tc.wantLoads || store.saves != tc.wantSaves || calls != tc.wantCalls { t.Fatalf("calls = load %d save %d module %d, want %d %d %d", store.loads, store.saves, calls, tc.wantLoads, tc.wantSaves, tc.wantCalls) } if output.Manifest.ChunkPlan == nil || output.Manifest.ChunkPlan.Action != tc.wantAction || output.ChunkPlan == nil || output.ChunkPlan.Action != tc.wantAction { t.Fatalf("chunk plan manifest = %#v summary = %#v, want action %q", output.Manifest.ChunkPlan, output.ChunkPlan, tc.wantAction) } }) } } func TestRunnerUsesStatusDerivedChunkPlanSummaryReasons(t *testing.T) { for _, tc := range []struct { name string status ChunkPlanStatus wantStatus string wantReason string }{ {name: "hit", status: ChunkPlanHit, wantStatus: "hit", wantReason: "stored chunk plan is valid"}, {name: "missing", status: ChunkPlanMissing, wantStatus: "missing", wantReason: "chunk plan not found"}, {name: "invalid", status: ChunkPlanInvalid, wantStatus: "invalid", wantReason: "stored chunk plan is invalid"}, } { t.Run(tc.name, func(t *testing.T) { prepared, plan := preparedTerminalDebugPipeline(t) store := &recordingChunkPlanStore{ record: chunkPlanRecord(t, prepared, plan), decision: ChunkPlanDecision{Status: tc.status, Reason: "SENTINEL_STORE_REASON"}, } output, err := New().Run(context.Background(), RunInput{Prepared: prepared, RawInput: []byte("input"), ChunkCacheMode: ChunkCacheAuto, ChunkPlans: store}) if err != nil { t.Fatal(err) } if output.ChunkPlan.LookupStatus != tc.wantStatus || output.ChunkPlan.LookupReason != tc.wantReason || strings.Contains(output.ChunkPlan.LookupReason, "SENTINEL_STORE_REASON") { t.Fatalf("summary = %#v", output.ChunkPlan) } }) } prepared, _ := preparedTerminalDebugPipeline(t) output, err := New().Run(context.Background(), RunInput{ Prepared: prepared, RawInput: []byte("input"), ChunkCacheMode: ChunkCacheAuto, ChunkPlans: &recordingChunkPlanStore{loadErr: errors.New("read failed")}, }) if err == nil || output.ChunkPlan == nil || output.ChunkPlan.LookupStatus != "skipped" || output.ChunkPlan.LookupReason != "chunk plan lookup skipped" { t.Fatalf("operational lookup output=%#v error=%v", output.ChunkPlan, err) } } func TestRunnerChunkPlanHitUsesStoredProducerProvenance(t *testing.T) { prepared, plan := preparedTerminalDebugPipeline(t) prepared.resolved.Chunk.Module = "chunk/requested" prepared.resolved.Chunk.LLMProfile = "current-profile" record := chunkPlanRecord(t, prepared, plan) record.Producer.InputModule = "input/original" record.Producer.ChunkModule = "chunk/original" record.Producer.LLMProfile = "original-profile" record.Producer.Metadata = map[string]any{"prompt": map[string]any{"id": "original"}} store := &recordingChunkPlanStore{record: record, decision: ChunkPlanDecision{Status: ChunkPlanHit}} output, err := New().Run(context.Background(), RunInput{Prepared: prepared, RawInput: []byte("input"), ChunkCacheMode: ChunkCacheAuto, ChunkPlans: store}) if err != nil { t.Fatal(err) } manifest := output.Manifest.ChunkPlan if manifest.RequestedModule != "chunk/requested" || manifest.ProducerModule != "chunk/original" || manifest.ProducerLLMProfile != "original-profile" { t.Fatalf("chunk plan manifest = %#v", manifest) } storedNested := record.Producer.Metadata["prompt"].(map[string]any) storedNested["id"] = "mutated" if got := manifest.ProducerMetadata["prompt"].(map[string]any)["id"]; got != "original" { t.Fatalf("producer metadata changed through stored alias: %v", got) } } func TestRunnerProvidesAcceptedChunkMapToOutput(t *testing.T) { for _, test := range []struct { name string configure func(*PreparedPipeline, source.ChunkPlan) (RunInput, chunkmap.Producer) }{ { name: "bypassed plan", configure: func(prepared *PreparedPipeline, _ source.ChunkPlan) (RunInput, chunkmap.Producer) { return RunInput{Prepared: prepared, RawInput: []byte("input"), ChunkCacheMode: ChunkCacheBypass}, chunkmap.Producer{ InputModule: prepared.input.Key(), ChunkModule: prepared.chunker.Key(), } }, }, { name: "cached plan", configure: func(prepared *PreparedPipeline, plan source.ChunkPlan) (RunInput, chunkmap.Producer) { prepared.resolved.Chunk.Module = "chunk/requested" record := chunkPlanRecord(t, prepared, plan) record.Producer.InputModule = "input/original" record.Producer.ChunkModule = "chunk/original" record.Producer.LLMProfile = "original-profile" return RunInput{ Prepared: prepared, RawInput: []byte("input"), ChunkCacheMode: ChunkCacheAuto, ChunkPlans: &recordingChunkPlanStore{record: record, decision: ChunkPlanDecision{Status: ChunkPlanHit}}, }, chunkmap.Producer{InputModule: "input/original", ChunkModule: "chunk/original", LLMProfile: "original-profile"} }, }, } { t.Run(test.name, func(t *testing.T) { prepared, plan := preparedTerminalDebugPipeline(t) encoder := &capturingChunkMapOutput{} prepared.output = encoder input, wantProducer := test.configure(prepared, plan) if _, err := New().Run(context.Background(), input); err != nil { t.Fatalf("Run() error = %v", err) } if len(encoder.requests) != 1 || encoder.requests[0].ChunkMap == nil { t.Fatalf("output requests = %#v, want one accepted chunk map", encoder.requests) } artifact := encoder.requests[0].ChunkMap if artifact.Kind != chunkmap.ArtifactKind || artifact.Schema.ID != chunkmap.SchemaID || artifact.MediaType != chunkmap.MediaType { t.Fatalf("chunk map artifact = %#v, want fixed serialized identity", artifact) } value, err := chunkmap.New().Decode(artifact.Content) if err != nil { t.Fatalf("decode output chunk map: %v", err) } if value.RequestedChunker != prepared.resolved.Chunk.Module || value.Producer != wantProducer { t.Fatalf("chunk map = %#v, want current request and producer %#v", value, wantProducer) } }) } } func TestRunnerOmitsChunkMapForRejectedPlan(t *testing.T) { prepared, _ := preparedTerminalDebugPipeline(t) encoder := &capturingChunkMapOutput{} prepared.output = encoder prepared.chunkValidators.validators = []preparedValidator{{ resolved: ResolvedValidator{Binding: Binding("reject/chunk"), Target: ValidatorTargetChunk}, chunk: &countingChunkValidator{result: contracts.ValidationResult{Approved: false, ReasonCode: "rejected", Message: "not accepted"}}, }} output, err := New().Run(context.Background(), RunInput{Prepared: prepared, RawInput: []byte("input")}) if err != nil { t.Fatalf("Run() error = %v", err) } if len(output.Rejected) != 1 || len(encoder.requests) != 1 || encoder.requests[0].ChunkMap != nil { t.Fatalf("output = %#v requests = %#v, want rejected plan without chunk map", output.Rejected, encoder.requests) } } func TestRunnerRetainsChunkMapAfterLaneRejection(t *testing.T) { prepared, _ := preparedTerminalDebugPipeline(t) encoder := &capturingChunkMapOutput{} prepared.output = encoder prepared.Steps[0].lanes[0].mergeValidators.validators = []preparedValidator{{ resolved: ResolvedValidator{Binding: Binding("reject/lane"), Target: ValidatorTargetTyped, ArtifactKind: "test/notes"}, typedValidate: func(context.Context, any, typedValidationTarget) (contracts.ValidationResult, error) { return contracts.ValidationResult{Approved: false, ReasonCode: "rejected", Message: "not accepted"}, nil }, }} output, err := New().Run(context.Background(), RunInput{Prepared: prepared, RawInput: []byte("input")}) if err != nil { t.Fatalf("Run() error = %v", err) } if len(output.Rejected) != 1 || len(encoder.requests) != 1 || encoder.requests[0].ChunkMap == nil { t.Fatalf("output = %#v requests = %#v, want lane rejection with accepted chunk map", output.Rejected, encoder.requests) } } func TestRunnerChunkMapRequestDoesNotAliasStoredPlan(t *testing.T) { prepared, plan := preparedTerminalDebugPipeline(t) encoder := &capturingChunkMapOutput{} prepared.output = encoder record := chunkPlanRecord(t, prepared, plan) record.Plan.Annotations = source.ChunkAnnotations{"dnd/scenes": json.RawMessage(`{"title":"Opening"}`)} store := &recordingChunkPlanStore{record: record, decision: ChunkPlanDecision{Status: ChunkPlanHit}} if _, err := New().Run(context.Background(), RunInput{Prepared: prepared, RawInput: []byte("input"), ChunkCacheMode: ChunkCacheAuto, ChunkPlans: store}); err != nil { t.Fatalf("Run() error = %v", err) } store.record.Plan.Annotations["dnd/scenes"][0] = '[' value, err := chunkmap.New().Decode(encoder.requests[0].ChunkMap.Content) if err != nil { t.Fatalf("decode captured chunk map after store mutation: %v", err) } if string(value.PlanAnnotations["dnd/scenes"]) != `{"title":"Opening"}` { t.Fatalf("captured chunk map aliases stored plan: %s", value.PlanAnnotations["dnd/scenes"]) } } func TestRunnerChunkPlanManifestRetainsCandidateOnRejection(t *testing.T) { prepared, _ := preparedTerminalDebugPipeline(t) validator := &countingChunkValidator{result: contracts.ValidationResult{Approved: false, ReasonCode: "policy", Message: "no"}} prepared.chunkValidators.validators = []preparedValidator{{resolved: ResolvedValidator{Binding: Binding(validator.Name()), Target: ValidatorTargetChunk}, chunk: validator}} output, err := New().Run(context.Background(), RunInput{Prepared: prepared, RawInput: []byte("input"), ChunkCacheMode: ChunkCacheRefresh, ChunkPlans: &recordingChunkPlanStore{}}) if err != nil { t.Fatal(err) } if output.Manifest.ChunkPlan.Action != "refreshed" || output.Manifest.ChunkPlan.PlanDigest == "" || output.ChunkPlan.ValidationStatus != "rejected" || output.ChunkPlan.PublicationStatus != "not_published" { t.Fatalf("manifest = %#v summary = %#v", output.Manifest.ChunkPlan, output.ChunkPlan) } } func TestRunnerValidatesChunkPlanPolicyInputs(t *testing.T) { for _, tc := range []struct { name string mode ChunkCacheMode want string }{ {name: "auto requires store", mode: ChunkCacheAuto, want: "store is required"}, {name: "refresh requires store", mode: ChunkCacheRefresh, want: "store is required"}, {name: "invalid mode", mode: "sometimes", want: "not supported"}, } { t.Run(tc.name, func(t *testing.T) { prepared, _ := preparedTerminalDebugPipeline(t) _, err := New().Run(context.Background(), RunInput{Prepared: prepared, ChunkCacheMode: tc.mode}) if err == nil || !strings.Contains(err.Error(), tc.want) { t.Fatalf("Run() error = %v, want %q", err, tc.want) } }) } } func TestRunnerChunkPlanStoreErrorsAreFrameworkErrors(t *testing.T) { for _, tc := range []struct { name string mode ChunkCacheMode decision ChunkPlanDecision loadErr error saveErr error wantError string wantCalls int }{ {name: "load", mode: ChunkCacheAuto, loadErr: errors.New("read failed"), wantError: "load chunk plan", wantCalls: 0}, {name: "save", mode: ChunkCacheRefresh, saveErr: errors.New("write failed"), wantError: "save chunk plan", wantCalls: 1}, } { t.Run(tc.name, func(t *testing.T) { prepared, plan := preparedTerminalDebugPipeline(t) calls := 0 llmCalls := 0 prepared.chunker = llmCountingChunker{terminalChunker: terminalChunker{key: prepared.resolved.Chunk.Module, plan: plan, calls: &calls}, llmCalls: &llmCalls} store := &recordingChunkPlanStore{decision: tc.decision, loadErr: tc.loadErr, saveErr: tc.saveErr} _, err := New().Run(context.Background(), RunInput{Prepared: prepared, RawInput: []byte("input"), ChunkCacheMode: tc.mode, ChunkPlans: store}) if err == nil || !strings.Contains(err.Error(), tc.wantError) || calls != tc.wantCalls { t.Fatalf("Run() error = %v, module calls = %d, want %q and %d", err, calls, tc.wantError, tc.wantCalls) } }) } } func TestRunnerRegeneratesStructurallyInvalidHit(t *testing.T) { prepared, plan := preparedTerminalDebugPipeline(t) calls := 0 prepared.chunker = terminalChunker{key: prepared.resolved.Chunk.Module, plan: plan, calls: &calls} invalid := chunkPlanRecord(t, prepared, plan) invalid.Plan.Ranges[0].StartUnitID = 999 store := &recordingChunkPlanStore{record: invalid, decision: ChunkPlanDecision{Status: ChunkPlanHit}} if _, err := New().Run(context.Background(), RunInput{Prepared: prepared, RawInput: []byte("input"), ChunkCacheMode: ChunkCacheAuto, ChunkPlans: store}); err != nil { t.Fatal(err) } if calls != 1 || store.loads != 1 || store.saves != 1 { t.Fatalf("calls = module %d load %d save %d, want 1 1 1", calls, store.loads, store.saves) } } func TestRunnerReusesAnnotationsFromDifferentChunkerAsOptionalData(t *testing.T) { prepared, plan := preparedTerminalDebugPipeline(t) plan.Annotations = source.ChunkAnnotations{"test/chunker": json.RawMessage(`{"label":"fixture"}`)} plan.Ranges[0].Annotations = source.ChunkAnnotations{"test/chunker": json.RawMessage(`{"category":"sample"}`)} record := chunkPlanRecord(t, prepared, plan) record.Producer.ChunkModule = "chunk/producer" store := &recordingChunkPlanStore{record: record, decision: ChunkPlanDecision{Status: ChunkPlanHit}} output, err := New().Run(context.Background(), RunInput{Prepared: prepared, RawInput: []byte("input"), ChunkCacheMode: ChunkCacheAuto, ChunkPlans: store}) if err != nil { t.Fatal(err) } if output.Manifest.ChunkPlan.ProducerModule != "chunk/producer" || output.Manifest.ChunkPlan.Action != "reused" { t.Fatalf("different-chunker annotation plan was not reused: %#v", output.Manifest.ChunkPlan) } } func TestRunnerRetriesBeforePublishingAcceptedPlan(t *testing.T) { prepared, plan := preparedTerminalDebugPipeline(t) prepared.resolved.Chunk.Retries = 1 chunker := &retryingChunker{key: prepared.resolved.Chunk.Module, plan: plan} prepared.chunker = chunker store := &recordingChunkPlanStore{decision: ChunkPlanDecision{Status: ChunkPlanMissing}} output, err := New().Run(context.Background(), RunInput{Prepared: prepared, RawInput: []byte("input"), ChunkCacheMode: ChunkCacheAuto, ChunkPlans: store}) if err != nil { t.Fatal(err) } if chunker.calls != 2 || store.saves != 1 { t.Fatalf("module calls = %d saves = %d, want 2 and 1", chunker.calls, store.saves) } if len(output.Warnings) != 1 || output.Warnings[0].Scope != "accepted" || len(store.saved.Warnings) != 1 || store.saved.Warnings[0].Scope != "accepted" { t.Fatalf("output warnings = %#v stored warnings = %#v", output.Warnings, store.saved.Warnings) } } func TestRunnerAutoHitValidatesOnceWithoutRegenerationOrMutation(t *testing.T) { tests := []struct { name string result contracts.ValidationResult validatorErr error wantError string wantReject bool }{ {name: "warning", result: contracts.ValidationResult{Approved: true, Warnings: []contracts.Warning{{Scope: "current", ReasonCode: "observed", Message: "current warning"}}}}, {name: "rejection", result: contracts.ValidationResult{Approved: false, ReasonCode: "rejected", Message: "rejected hit"}, wantReject: true}, {name: "error", validatorErr: errors.New("validator failed"), wantError: "validator failed"}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { prepared, plan := preparedTerminalDebugPipeline(t) calls := 0 llmCalls := 0 prepared.chunker = llmCountingChunker{terminalChunker: terminalChunker{key: prepared.resolved.Chunk.Module, plan: plan, calls: &calls}, llmCalls: &llmCalls} validator := &countingChunkValidator{result: tc.result, err: tc.validatorErr} prepared.chunkValidators.validators = []preparedValidator{{resolved: ResolvedValidator{Binding: Binding(validator.Name()), Target: ValidatorTargetChunk}, chunk: validator}} record := chunkPlanRecord(t, prepared, plan) record.Warnings = []contracts.Warning{{Scope: "stored", ReasonCode: "observed", Message: "stored warning"}} store := &recordingChunkPlanStore{record: record, decision: ChunkPlanDecision{Status: ChunkPlanHit}} debug := newCapturedDebugRecorder() output, err := New().Run(context.Background(), RunInput{Prepared: prepared, RawInput: []byte("input"), ChunkCacheMode: ChunkCacheAuto, ChunkPlans: store, Debug: debug}) if tc.wantError != "" { if err == nil || !strings.Contains(err.Error(), tc.wantError) { t.Fatalf("Run() error = %v, want %q", err, tc.wantError) } } else if err != nil { t.Fatalf("Run() error = %v", err) } if validator.calls != 1 || calls != 0 || llmCalls != 0 || store.loads != 1 || store.saves != 0 { t.Fatalf("calls = validator %d module %d llm %d load %d save %d", validator.calls, calls, llmCalls, store.loads, store.saves) } assertAttemptEnvelopeSequence(t, debug, "chunk") if tc.wantError == "" { encoded := string(debug.json["chunk/output.json"]) if !strings.Contains(encoded, `"status":"hit"`) || !strings.Contains(encoded, `"plan":`) || !strings.Contains(encoded, `"materialized_chunks":`) { t.Fatalf("chunk hit debug = %s", encoded) } } if tc.wantReject && (len(output.Rejected) != 1 || output.Rejected[0].ReasonCode != "rejected") { t.Fatalf("rejected = %#v", output.Rejected) } if tc.wantError == "" && len(output.Warnings) != 1+len(tc.result.Warnings) { t.Fatalf("warnings = %#v, want stored warning once plus current warnings", output.Warnings) } }) } } func TestRunnerPublishesOnlyAcceptedGeneratedPlans(t *testing.T) { for _, tc := range []struct { name string moduleErr error validator contracts.ValidationResult cancel bool wantCalls int wantReject bool }{ {name: "module error", moduleErr: errors.New("generation failed"), wantCalls: 2}, {name: "validator rejection", validator: contracts.ValidationResult{Approved: false, ReasonCode: "rejected", Message: "rejected generated plan"}, wantCalls: 2, wantReject: true}, {name: "cancellation", cancel: true}, } { t.Run(tc.name, func(t *testing.T) { prepared, plan := preparedTerminalDebugPipeline(t) prepared.resolved.Chunk.Retries = 1 calls := 0 prepared.chunker = terminalChunker{key: prepared.resolved.Chunk.Module, plan: plan, calls: &calls, err: tc.moduleErr} if tc.wantReject { validator := &countingChunkValidator{result: tc.validator} prepared.chunkValidators.validators = []preparedValidator{{resolved: ResolvedValidator{Binding: Binding(validator.Name()), Target: ValidatorTargetChunk}, chunk: validator}} } ctx := context.Background() if tc.cancel { cancelled, cancel := context.WithCancel(ctx) cancel() ctx = cancelled } store := &recordingChunkPlanStore{decision: ChunkPlanDecision{Status: ChunkPlanInvalid}} output, err := New().Run(ctx, RunInput{Prepared: prepared, RawInput: []byte("input"), ChunkCacheMode: ChunkCacheAuto, ChunkPlans: store}) if tc.wantReject { if err != nil || len(output.Rejected) != 1 { t.Fatalf("Run() error = %v rejected = %#v", err, output.Rejected) } } else if err == nil { t.Fatal("Run() error = nil") } if calls != tc.wantCalls || store.saves != 0 { t.Fatalf("module calls = %d, saves = %d; want %d, 0", calls, store.saves, tc.wantCalls) } }) } } func TestRunnerStoresProducerProvenanceAndProducerWarnings(t *testing.T) { prepared, plan := preparedTerminalDebugPipeline(t) prepared.resolved.Chunk.LLMProfile = "chunk-profile" prepared.resolved.ChunkReferences = ResolvedReferenceTarget{ Stage: StageChunk, ReferenceSet: contracts.ReferenceSet{Slots: map[string]contracts.ResolvedReferenceSlot{ "guide": {Items: []contracts.ReferenceItem{{SlotName: "guide", Digest: "sha256:guide", Origin: contracts.ReferenceOrigin{Type: "file", URI: "file:///guide.txt"}, Content: []byte("sensitive")}}}, }}, } prepared.chunker = manifestChunker{terminalChunker: terminalChunker{key: prepared.resolved.Chunk.Module, plan: plan, warnings: []contracts.Warning{{Scope: "producer", ReasonCode: "observed", Message: "producer warning"}}}, metadata: map[string]any{"prompt_id": "chunk/prompt"}} validator := &countingChunkValidator{result: contracts.ValidationResult{Approved: true, Warnings: []contracts.Warning{{Scope: "validator", ReasonCode: "observed", Message: "validator warning"}}}} prepared.chunkValidators.validators = []preparedValidator{{resolved: ResolvedValidator{Binding: Binding(validator.Name()), Target: ValidatorTargetChunk}, chunk: validator}} store := &recordingChunkPlanStore{} if _, err := New().Run(context.Background(), RunInput{Prepared: prepared, RawInput: []byte("input"), ChunkCacheMode: ChunkCacheRefresh, ChunkPlans: store}); err != nil { t.Fatal(err) } producer := store.saved.Producer if producer.InputModule != prepared.input.Key() || producer.ChunkModule != prepared.chunker.Key() || producer.LLMProfile != "chunk-profile" || producer.Metadata["prompt_id"] != "chunk/prompt" { t.Fatalf("producer = %#v", producer) } if len(producer.References) != 1 || producer.References[0].Digest != "sha256:guide" { t.Fatalf("producer references = %#v", producer.References) } if len(store.saved.Warnings) != 1 || store.saved.Warnings[0].Scope != "producer" { t.Fatalf("stored warnings = %#v, want only producer warning", store.saved.Warnings) } } func TestRunnerRejectsUncloneableModuleManifestMetadata(t *testing.T) { prepared, plan := preparedTerminalDebugPipeline(t) cyclic := make(map[string]any) cyclic["self"] = cyclic prepared.chunker = manifestChunker{ terminalChunker: terminalChunker{key: prepared.resolved.Chunk.Module, plan: plan}, metadata: map[string]any{"cyclic": cyclic}, } _, err := New().Run(context.Background(), RunInput{Prepared: prepared, RawInput: []byte("input")}) if err == nil || !strings.Contains(err.Error(), `clone manifest metadata for module "chunker": metadata.cyclic.self contains a cycle`) { t.Fatalf("Run() error = %v, want contextual module metadata clone failure", err) } } func TestRunnerOmitsProducerProfileForDeterministicChunker(t *testing.T) { prepared, plan := preparedTerminalDebugPipeline(t) prepared.resolved.Chunk.LLMProfile = "configured-but-unused" prepared.chunker = deterministicChunker{terminalChunker{key: prepared.resolved.Chunk.Module, plan: plan}} store := &recordingChunkPlanStore{} output, err := New().Run(context.Background(), RunInput{Prepared: prepared, RawInput: []byte("input"), ChunkCacheMode: ChunkCacheRefresh, ChunkPlans: store}) if err != nil { t.Fatal(err) } if store.saved.Producer.LLMProfile != "" || output.Manifest.ChunkPlan.ProducerLLMProfile != "" { t.Fatalf("deterministic producer profile = stored %q manifest %q", store.saved.Producer.LLMProfile, output.Manifest.ChunkPlan.ProducerLLMProfile) } } func TestRunnerRefreshChangesDownstreamChunkFingerprint(t *testing.T) { doc := typedTestDocumentWithUnits(2) prepared := preparedConcurrentPipeline(t, 1) prepared.Steps[0].lanes = prepared.Steps[0].lanes[:1] prepared.resolved.Steps[0].ArtifactLanes = prepared.resolved.Steps[0].ArtifactLanes[:1] prepared.Steps[0].ArtifactLanes = prepared.Steps[0].ArtifactLanes[:1] prepared.input = &typedTestInput{key: prepared.resolved.Input.Module, doc: doc} run := func(plan source.ChunkPlan) []CheckpointFingerprint { prepared.chunker = terminalChunker{key: prepared.resolved.Chunk.Module, plan: plan} loader := &dependencyLoader{CheckpointLoader: NoopCheckpointLoader()} if _, err := New().Run(context.Background(), RunInput{Prepared: prepared, ChunkCacheMode: ChunkCacheRefresh, ChunkPlans: &recordingChunkPlanStore{}, Checkpoint: loader}); err != nil { t.Fatal(err) } return loader.extractDependencies } separate := typedTestPlan(doc) combined := source.ChunkPlan{SourceDigest: doc.Digest, Ranges: []source.ChunkRange{{StartUnitID: 1, EndUnitID: 2}}} if first, second := run(separate), run(combined); reflect.DeepEqual(first, second) { t.Fatalf("downstream fingerprints did not change: %#v", first) } } func chunkPlanRecord(t *testing.T, prepared *PreparedPipeline, plan source.ChunkPlan) ChunkPlanRecord { t.Helper() digest, err := source.DigestChunkPlan(plan) if err != nil { t.Fatal(err) } return ChunkPlanRecord{ SchemaVersion: ChunkPlanSchemaVersion, SourceDigest: plan.SourceDigest, PlanDigest: digest, Plan: source.CloneChunkPlan(plan), Producer: ChunkPlanProducer{InputModule: prepared.input.Key(), ChunkModule: prepared.chunker.Key()}, CreatedAt: time.Now().UTC(), } }