diff --git a/docs/policy/architecture.md b/docs/policy/architecture.md index 10018f9..b8df33f 100644 --- a/docs/policy/architecture.md +++ b/docs/policy/architecture.md @@ -29,7 +29,7 @@ Source-format details belong in input modules. Transcript-specific concepts such as segments, speakers, timestamps, and transcript schemas must not spread into runner, extractor, or validator framework code. -Extraction-domain details belong in process modules. D&D-specific concepts +Extraction-domain details belong in extract modules. D&D-specific concepts such as spells, NPCs, items, combat turns, and encounters must not spread into core source, runner, or LLM framework packages. @@ -39,11 +39,11 @@ should point to generic source units, not to transcript-only structures. The application workflow is: ```text -input -> chunk -> process -> merge -> normalize -> output +input -> chunk -> extract -> merge -> normalize -> output ``` These stages should remain explicit in the architecture. Chunking, merging, and -normalization must not be hidden inside domain process modules when they represent +normalization must not be hidden inside domain extract modules when they represent general pipeline behavior. ## Dependency Policy @@ -99,7 +99,7 @@ Domain implementations: - `internal/modules/input/`: input-stage modules that parse external input into core source documents. - `internal/modules/chunk/`: chunk-stage modules. -- `internal/modules/process//`: process-stage extractor modules. +- `internal/modules/extract//`: extract-stage extractor modules. - `internal/modules/merge/` or `internal/modules/merge//`: merge-stage modules. - `internal/modules/normalize/` or `internal/modules/normalize//`: normalize-stage modules. - `internal/modules/output/`: output-stage modules. @@ -120,7 +120,7 @@ workflow visible in the filesystem: ```text internal/modules/input/... internal/modules/chunk/... -internal/modules/process/... +internal/modules/extract/... internal/modules/merge/... internal/modules/normalize/... internal/modules/output/... @@ -141,7 +141,7 @@ decisions. Other packages should interact with source input through adapter contracts and core source types. Input module implementation details and external dependency -types must not leak into framework or process module packages. +types must not leak into framework or extract module packages. Input module metadata may preserve source-specific facts such as transcript speaker, timestamps, Markdown heading path, page number, or block ID. Framework code may @@ -152,7 +152,7 @@ shape. Extractors are independent modules that process source chunks or whole source documents and produce one kind of structured artifact candidate. -Each process module owns: +Each extract module owns: - its artifact semantics; - its prompt usage; @@ -160,7 +160,7 @@ Each process module owns: - its validator chain; - any domain-specific mapping or interpretation. -Process modules should depend on framework contracts and core source/artifact +Extract modules should depend on framework contracts and core source/artifact types. They should not depend on concrete input module packages. Extractors should not be the only place where chunking, merging, or @@ -181,7 +181,7 @@ The pipeline has six conceptual stages: 1. input: external source material becomes a `SourceDocument`; 2. chunk: a `SourceDocument` becomes ordered source chunks; -3. process: extractors produce artifact candidates from chunks or whole documents; +3. extract: extractors produce artifact candidates from chunks or whole documents; 4. merge: per-chunk candidates become a merged candidate collection; 5. normalize: merged candidates are reconciled for duplicates, aliases, consistency, or cross-chunk issues; 6. output: final artifacts are serialized. @@ -290,11 +290,11 @@ be opt-in. ## Testing Core logic should be testable without real external services. Use fakes, -fixtures, or local test doubles for adapters, extractors, validators, and LLM -clients where practical. +fixtures, or local test doubles for input modules, extract modules, validators, +and LLM clients where practical. Contract-first work should include fake implementations that prove interfaces -compose before real adapters or extractors depend on them. +compose before real input modules or extract modules depend on them. Config examples should be load-tested once config files exist. Important CLI workflows should have parser or command tests. Adapter, extractor, and validator @@ -312,7 +312,7 @@ unit, source reference, input adapter, extractor, chunker, merger, normalizer, artifact, validator, and run manifest. Source-format details belong in input module or integration docs. -Domain-specific extraction details belong in process module or artifact docs. +Domain-specific extraction details belong in extract module or artifact docs. When changing architecture, config, CLI behavior, stage modules, extractor contracts, validator contracts, LLM runtime behavior, or artifact schemas, update diff --git a/docs/roadmap/1-core-contracts-and-skeleton.md b/docs/roadmap/1-core-contracts-and-skeleton.md index cab47f2..4b4ff45 100644 --- a/docs/roadmap/1-core-contracts-and-skeleton.md +++ b/docs/roadmap/1-core-contracts-and-skeleton.md @@ -26,7 +26,7 @@ In scope: Out of scope: - real input modules; -- real process modules; +- real extract modules; - LLM provider calls; - prompt or response-schema assets; - diagnostics run directory; @@ -47,7 +47,7 @@ contract packages: structured LLM interfaces used by later checkpoints. The contracts should be proven with fake implementations in tests. Those tests -should demonstrate composition without real input modules, real process modules, +should demonstrate composition without real input modules, real extract modules, LLM provider calls, prompt assets, or diagnostics infrastructure. Implementation staging belongs in diff --git a/docs/roadmap/2-framework-composition.md b/docs/roadmap/2-framework-composition.md index 370f3d8..fd16fd1 100644 --- a/docs/roadmap/2-framework-composition.md +++ b/docs/roadmap/2-framework-composition.md @@ -27,7 +27,7 @@ Out of scope: - real input parsing; - real input modules; -- real process modules; +- real extract modules; - real LLM calls; - prompt assets; - response schema assets; @@ -60,7 +60,7 @@ Implementation staging belongs in - `go test ./...` passes. - Fake adapter/extractor/validator registrations work in tests. - The runner operates on `SourceDocument`, not transcript-specific structures. -- The runner does not import concrete D&D process module packages. +- The runner does not import concrete D&D extract module packages. ## Review Questions diff --git a/docs/roadmap/3-pipeline-stages-chunking-merge-normalize.md b/docs/roadmap/3-pipeline-stages-chunking-merge-normalize.md index c6a443e..d56315c 100644 --- a/docs/roadmap/3-pipeline-stages-chunking-merge-normalize.md +++ b/docs/roadmap/3-pipeline-stages-chunking-merge-normalize.md @@ -7,17 +7,17 @@ This document describes planned work, not implemented behavior. ## Goal Make Notarius's application workflow first-class before adding real input -modules or process modules. +modules or extract modules. The workflow should be: ```text -input -> chunk -> process -> merge -> normalize -> output +input -> chunk -> extract -> merge -> normalize -> output ``` Checkpoint 3 should define the contracts and minimal fake-tested framework behavior for chunking, per-chunk processing, merging, and normalization. It -should not add real input modules, real domain process modules, LLM provider code, +should not add real input modules, real domain extract modules, LLM provider code, or output encoders. ## Scope @@ -26,7 +26,7 @@ In scope: - source chunk model; - chunker contract; -- process-stage contract for extractors operating on chunks; +- extract-stage contract for extractors operating on chunks; - merge-stage contract; - normalize-stage contract; - output-stage contract shape, if useful for pipeline completeness; @@ -50,13 +50,13 @@ The repository should contain explicit pipeline-stage contracts: - `InputAdapter`: external source input to `SourceDocument`. - `Chunker`: `SourceDocument` to ordered `SourceChunk` values. -- `Extractor` or processor: `SourceChunk` to artifact candidates. +- `Extractor`: `SourceChunk` to artifact candidates. - `Merger`: per-chunk candidates to merged candidates. - `Normalizer`: merged candidates to normalized candidates. - `OutputEncoder`: final artifact bundle to bytes, if introduced in this checkpoint. -The runner should orchestrate fake implementations through chunk, process, +The runner should orchestrate fake implementations through chunk, extract, merge, normalize, and approval/validation behavior in tests. ## Design Intent @@ -113,17 +113,17 @@ Domain-specific normalizers may later: - `go test ./...` passes. - Pipeline-stage contracts are explicit and source/domain agnostic. -- Fake tests prove input source documents can be chunked, processed, merged, and +- Fake tests prove input source documents can be chunked, extracted, merged, and normalized. - Merge and normalize are distinct concepts in code and tests. - The runner no longer implies whole-document-only extraction as the core application model. -- No concrete input module, process module, LLM provider, prompt, response schema, +- No concrete input module, extract module, LLM provider, prompt, response schema, diagnostics, config, or D&D artifact code is added. ## Review Questions -- Is the workflow clearly represented as input, chunk, process, merge, +- Is the workflow clearly represented as input, chunk, extract, merge, normalize, and output? - Are merge and normalize cleanly separated? - Can a generic merger handle simple chronological artifact streams? diff --git a/docs/roadmap/6-dnd-spells-extractor.md b/docs/roadmap/6-dnd-spells-extractor.md index c0c15a3..038c94a 100644 --- a/docs/roadmap/6-dnd-spells-extractor.md +++ b/docs/roadmap/6-dnd-spells-extractor.md @@ -6,7 +6,7 @@ This document describes planned work, not implemented behavior. ## Goal -Implement the first useful process-stage module: D&D spell casts from a +Implement the first useful extract-stage module: D&D spell casts from a Seriatim transcript source document. This checkpoint should produce the first meaningful vertical slice from real @@ -19,7 +19,7 @@ In scope: - D&D spell artifact schema and Go structs; - structured response schema asset; - prompt assets; -- `internal/modules/process/dnd/spells`; +- `internal/modules/extract/dnd/spells`; - source-reference and schema validators in the extractor chain; - fake LLM tests; - CLI-level integration test if the CLI path is ready. @@ -50,7 +50,7 @@ type SpellCast struct { } ``` -Keep this schema inside the D&D spells process module or a D&D artifact package, +Keep this schema inside the D&D spells extract module or a D&D artifact package, not inside core framework packages. ### Stage 2: Structured Response Schema @@ -76,7 +76,7 @@ Prompts should: ### Stage 4: Process Module Implementation -Implement `internal/modules/process/dnd/spells`. +Implement `internal/modules/extract/dnd/spells`. The extractor should: @@ -117,13 +117,13 @@ The test should use fake LLM wiring and fixture input. - `go test ./...` passes. - Seriatim input can flow through the runner into the D&D spells extractor. - Spell artifacts include valid source references. -- D&D concepts are contained in process module/artifact packages and docs. +- D&D concepts are contained in extract module/artifact packages and docs. - The first meaningful vertical slice is available through tests, and through CLI if the CLI path is ready. ## Review Questions -- Is the spell process module domain-specific without making the framework +- Is the spell extract module domain-specific without making the framework D&D-specific? - Are source references valid and useful for downstream validation? - Is prompt/schema ownership clear? diff --git a/docs/roadmap/documentation.md b/docs/roadmap/documentation.md index 916cc91..cc4cff8 100644 --- a/docs/roadmap/documentation.md +++ b/docs/roadmap/documentation.md @@ -11,7 +11,7 @@ not describe implemented behavior. Notarius documentation should make three boundaries obvious: - source-format support belongs to input-stage modules; -- extraction-domain behavior belongs to process-stage modules; +- extraction-domain behavior belongs to extract-stage modules; - core framework behavior is source-agnostic and domain-agnostic. Documentation should avoid making the MVP look more transcript-specific or @@ -47,7 +47,7 @@ Core docs should avoid transcript-specific terms such as segment, speaker, timestamp, and transcript range unless discussing an input adapter or an example. Core docs should avoid D&D-specific terms such as spell, NPC, item, combat, and -encounter unless discussing process modules, artifact docs, or examples. +encounter unless discussing extract modules, artifact docs, or examples. ### Input Module Docs Own Source Formats @@ -95,12 +95,12 @@ Stage module docs should cover: - prompt and response-schema ownership; - examples. -D&D concepts should be documented in D&D process-module or artifact docs, not in +D&D concepts should be documented in D&D extract-module or artifact docs, not in generic runner or framework docs. ### CLI Docs Should Reflect Extensibility -The CLI reference should present input modules, chunk modules, process modules, +The CLI reference should present input modules, chunk modules, extract modules, merge modules, normalize modules, and output modules as selectable or configurable components as they become user-facing. @@ -114,7 +114,7 @@ Once implemented, `docs/cli.md` should document: - positional source input path; - input module selection; -- process module selection; +- extract module selection; - chunk/merge/normalize/output selection when configurable; - config path behavior; - output path behavior; @@ -127,7 +127,7 @@ Once implemented, `docs/cli.md` should document: - input module selection and module-specific options; - chunk module selection and module-specific options; -- process module selection and module-specific options; +- extract module selection and module-specific options; - merge module selection and module-specific options; - normalize module selection and module-specific options; - output module selection and module-specific options; @@ -176,7 +176,7 @@ Before merging docs, check: - Does the document describe implemented behavior outside `docs/roadmap/`? - Are source-format details isolated to input module or integration docs? -- Are D&D details isolated to process module or artifact docs? +- Are D&D details isolated to extract module or artifact docs? - Is there one canonical home for the topic? - Do command examples match implemented CLI syntax? - Are examples valid, maintained, and free of secrets? diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 694ad19..f812b1c 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -7,7 +7,7 @@ This is a staged implementation plan for an LLM coding agent to follow stage by stage. This plan implements only checkpoint 2. Do not implement real input modules, -real process modules, source chunking, LLM provider clients, prompt assets, +real extract modules, source chunking, LLM provider clients, prompt assets, response schema registries, diagnostics run directories, production config loading, or D&D artifact schemas in this pass. @@ -22,7 +22,7 @@ Required boundaries: - framework packages must stay source-agnostic and domain-agnostic; - input adapter registry code must not import concrete input module packages; -- extractor registry and runner code must not import concrete process module +- extractor registry and runner code must not import concrete extract module packages; - runner code should operate on `SourceDocument`, not transcript-specific structures; @@ -196,7 +196,7 @@ Cover the same cases as the input adapter registry: - sorted `RegisteredKeys`; - nil registry behavior. -Use fake extractors only. Do not add concrete process module packages. +Use fake extractors only. Do not add concrete extract module packages. ### Validation @@ -236,7 +236,7 @@ Define helpers: ```go func Approved(candidateIndex int) contracts.ValidationDecision func Rejected(candidateIndex int, reasonCode string, message string) contracts.ValidationDecision -func EnforceDecisionCardinality(candidates []artifacts.Candidate, decisions []contracts.ValidationDecision) error +func EnforceDecisionCardinality(candidates []artifacts.ArtifactCandidate, decisions []contracts.ValidationDecision) error ``` ### Required Behavior @@ -264,8 +264,8 @@ func EnforceDecisionCardinality(candidates []artifacts.Candidate, decisions []co - work with candidate indices, not slice positions; - allow an empty candidate slice only when decisions are also empty. -Candidate indices are expected to be unique by the time validators run. If the -candidate slice itself contains duplicate indices, return an error. +Artifact candidate indices are expected to be unique by the time validators run. +If the candidate slice itself contains duplicate indices, return an error. ### Required Tests @@ -396,7 +396,7 @@ func (r *Runner) Run(ctx context.Context, input RunInput) (RunOutput, error) 1. reject nil runner or nil extractor factory; 2. validate `input.Source` with `source.ValidateDocument`; 3. reject an empty `ExtractorKeys` list; -4. process extractor keys in input order; +4. iterate extractor keys in input order; 5. build each extractor through the factory; 6. call `Extractor.Extract` with the source, LLM client, and metadata; 7. append extractor warnings to `RunOutput.Warnings`; @@ -406,9 +406,9 @@ func (r *Runner) Run(ctx context.Context, input RunInput) (RunOutput, error) 11. reject candidates denied by validators; 12. return approved/rejected artifacts in deterministic order. -Candidate normalization must: +Artifact candidate normalization must: -- assign `Candidate.Index` using a global monotonically increasing counter; +- assign `ArtifactCandidate.Index` using a global monotonically increasing counter; - fill empty `ExtractorKey`, `ArtifactType`, and `SchemaVersion` from the owning extractor; - return an error if a candidate's non-empty `ExtractorKey` differs from @@ -502,7 +502,7 @@ Assertions: - extractor execution follows configured key order; - approved artifacts are in deterministic order; - rejected artifacts are in deterministic order; -- no concrete input module or process module packages are imported. +- no concrete input module or extract module packages are imported. ### Validation @@ -528,7 +528,7 @@ Check: - no package names mention transcripts, Seriatim, D&D, spells, NPCs, items, or combat; -- no concrete input module or process module package exists; +- no concrete input module or extract module package exists; - no LLM provider code exists; - no prompt, response schema, diagnostics, config, or source chunking package was added; diff --git a/docs/roadmap/initial-architecture.md b/docs/roadmap/initial-architecture.md index 26e0cdd..dae4191 100644 --- a/docs/roadmap/initial-architecture.md +++ b/docs/roadmap/initial-architecture.md @@ -14,10 +14,10 @@ The first MVP should target audio transcripts generated by Seriatim. That choice should be implemented as an input-stage module, not as a transcript-specific assumption in the application core. Later input sources, such as unstructured Markdown notes or Obsidian documents, should be addable -through new input and process modules without reshaping the framework. +through new input and extract modules without reshaping the framework. The first extraction domain should be D&D session analysis, starting with spell -casts. That domain should live in process-stage modules and related schemas, not +casts. That domain should live in extract-stage modules and related schemas, not in core framework packages. The application should follow the same broad architecture as Audita: @@ -25,7 +25,7 @@ The application should follow the same broad architecture as Audita: - deterministic core packages for config, source documents, artifacts, diagnostics, and reporting; - input-stage modules that translate external source formats into a small internal source model; - reusable framework packages for contracts, orchestration, LLM runtime, structured output, and validation; -- independent process-stage modules that own domain-specific behavior; +- independent extract-stage modules that own domain-specific behavior; - independent validator packages; - embedded prompt and JSON schema assets; - CLI orchestration that wires the pieces together without owning domain logic. @@ -37,7 +37,7 @@ artifacts rather than proposing and applying transcript corrections. - Keep the core input model generic: ordered text units plus metadata. - Keep source-format details in hexagonal input modules. -- Keep extraction-domain details in process modules. +- Keep extraction-domain details in extract modules. - Treat evidence as source references, not transcript references. - Prefer narrow, useful abstractions over a universal document model. - Preserve enough provenance for validation, replay, and downstream inspection. @@ -77,10 +77,10 @@ internal/modules/input/markdown internal/modules/chunk/generic internal/modules/chunk/dndtranscript -internal/modules/process/dnd/spells -internal/modules/process/dnd/items -internal/modules/process/dnd/npcs -internal/modules/process/dnd/combat +internal/modules/extract/dnd/spells +internal/modules/extract/dnd/items +internal/modules/extract/dnd/npcs +internal/modules/extract/dnd/combat internal/modules/merge/appendorder internal/modules/merge/dnd/spells @@ -193,7 +193,7 @@ type Extractor interface { ArtifactType() string SchemaVersion() string Validators() []Validator - Process(ctx context.Context, req ProcessRequest) (ProcessResult, error) + Extract(ctx context.Context, req ExtractionRequest) (ExtractionResult, error) } ``` @@ -201,9 +201,9 @@ An extractor should receive either a whole source document or a source chunk, depending on processing mode. It should return typed artifact candidates plus warnings. It should not mutate the source document. -Process modules own domain concepts. For example, D&D spell extraction should -live under `internal/modules/process/dnd/spells`; a future to-do extractor for -notes should live under a different process-module path and use the same +Extract modules own domain concepts. For example, D&D spell extraction should +live under `internal/modules/extract/dnd/spells`; a future to-do extractor for +notes should live under a different extract-module path and use the same framework contract. ### Chunker @@ -262,9 +262,13 @@ Per-run provenance record. ```go type RunManifest struct { - InputAdapter string `json:"input_adapter"` + InputModule string `json:"input_module"` + Chunker string `json:"chunker"` SourceDigests []string `json:"source_digests"` Extractors []string `json:"extractors"` + Merger string `json:"merger"` + Normalizer string `json:"normalizer"` + OutputEncoder string `json:"output_encoder"` SchemaVersion string `json:"schema_version"` ValidationStatus string `json:"validation_status"` } @@ -345,7 +349,7 @@ The architecture should support extractors outside the D&D domain. Examples: - decisions and action items from meeting transcripts; - named people, places, and dates from research notes. -These should be addable as process modules without changing runner, +These should be addable as extract modules without changing runner, validator, source-reference, or LLM framework contracts. ## Proposed Pipeline Flow @@ -353,7 +357,7 @@ validator, source-reference, or LLM framework contracts. The application workflow should be first-class: ```text -input -> chunk -> process -> merge -> normalize -> output +input -> chunk -> extract -> merge -> normalize -> output ``` Proposed runner flow: diff --git a/internal/core/artifacts/artifacts.go b/internal/core/artifacts/artifacts.go index 02e115a..e68230f 100644 --- a/internal/core/artifacts/artifacts.go +++ b/internal/core/artifacts/artifacts.go @@ -7,7 +7,7 @@ import ( "gitea.maximumdirect.net/eric/notarius/internal/core/source" ) -type Candidate struct { +type ArtifactCandidate struct { Index int `json:"index"` ExtractorKey string `json:"extractor_key"` ArtifactType string `json:"artifact_type"` @@ -27,24 +27,28 @@ type Artifact struct { } type RejectedArtifact struct { - Candidate Candidate `json:"candidate"` - ValidatorName string `json:"validator_name"` - ReasonCode string `json:"reason_code"` - Message string `json:"message"` + Candidate ArtifactCandidate `json:"candidate"` + ValidatorName string `json:"validator_name"` + ReasonCode string `json:"reason_code"` + Message string `json:"message"` } type RunManifest struct { RunID string `json:"run_id,omitempty"` - InputAdapter string `json:"input_adapter,omitempty"` + InputModule string `json:"input_module,omitempty"` + Chunker string `json:"chunker,omitempty"` SourceDigests []string `json:"source_digests,omitempty"` Extractors []string `json:"extractors,omitempty"` + Merger string `json:"merger,omitempty"` + Normalizer string `json:"normalizer,omitempty"` + OutputEncoder string `json:"output_encoder,omitempty"` SchemaVersion string `json:"schema_version,omitempty"` ValidationStatus string `json:"validation_status,omitempty"` StartedAt *time.Time `json:"started_at,omitempty"` CompletedAt *time.Time `json:"completed_at,omitempty"` } -func ArtifactFromCandidate(candidate Candidate) Artifact { +func ArtifactFromCandidate(candidate ArtifactCandidate) Artifact { return Artifact{ ExtractorKey: candidate.ExtractorKey, ArtifactType: candidate.ArtifactType, diff --git a/internal/core/artifacts/artifacts_test.go b/internal/core/artifacts/artifacts_test.go index e3361c4..ff1425a 100644 --- a/internal/core/artifacts/artifacts_test.go +++ b/internal/core/artifacts/artifacts_test.go @@ -9,7 +9,7 @@ import ( ) func TestArtifactFromCandidatePreservesCandidateFields(t *testing.T) { - candidate := Candidate{ + candidate := ArtifactCandidate{ Index: 7, ExtractorKey: "generic-extractor", ArtifactType: "generic-artifact", @@ -60,7 +60,7 @@ func TestArtifactFromCandidatePreservesCandidateFields(t *testing.T) { } func TestJSONMarshalUsesExpectedFieldNames(t *testing.T) { - candidate := Candidate{ + candidate := ArtifactCandidate{ Index: 1, ExtractorKey: "generic-extractor", ArtifactType: "generic-artifact", diff --git a/internal/framework/contracts/composition_test.go b/internal/framework/contracts/composition_test.go index 49842c3..19349a6 100644 --- a/internal/framework/contracts/composition_test.go +++ b/internal/framework/contracts/composition_test.go @@ -107,7 +107,7 @@ func (extractor compositionExtractor) Extract(ctx context.Context, req contracts } return contracts.ExtractionResult{ - Candidates: []artifacts.Candidate{ + Candidates: []artifacts.ArtifactCandidate{ { Index: 0, ExtractorKey: extractor.Key(), diff --git a/internal/framework/contracts/contracts.go b/internal/framework/contracts/contracts.go index 53a4eee..e19dbee 100644 --- a/internal/framework/contracts/contracts.go +++ b/internal/framework/contracts/contracts.go @@ -53,8 +53,8 @@ type ExtractionRequest struct { } type ExtractionResult struct { - Candidates []artifacts.Candidate `json:"candidates,omitempty"` - Warnings []Warning `json:"warnings,omitempty"` + Candidates []artifacts.ArtifactCandidate `json:"candidates,omitempty"` + Warnings []Warning `json:"warnings,omitempty"` } type Extractor interface { @@ -66,9 +66,9 @@ type Extractor interface { } type ValidationRequest struct { - Source *source.SourceDocument `json:"-"` - Candidates []artifacts.Candidate `json:"candidates"` - Metadata map[string]any `json:"metadata,omitempty"` + Source *source.SourceDocument `json:"-"` + Candidates []artifacts.ArtifactCandidate `json:"candidates"` + Metadata map[string]any `json:"metadata,omitempty"` } type ValidationDecision struct { diff --git a/internal/framework/contracts/contracts_test.go b/internal/framework/contracts/contracts_test.go index 642dc80..e091686 100644 --- a/internal/framework/contracts/contracts_test.go +++ b/internal/framework/contracts/contracts_test.go @@ -58,19 +58,19 @@ func TestFakeExtractorReturnsCandidateAndValidator(t *testing.T) { candidate := result.Candidates[0] if candidate.Index != 0 { - t.Fatalf("Candidate.Index = %d, want 0", candidate.Index) + t.Fatalf("ArtifactCandidate.Index = %d, want 0", candidate.Index) } if candidate.ExtractorKey != extractor.Key() { - t.Fatalf("Candidate.ExtractorKey = %q, want %q", candidate.ExtractorKey, extractor.Key()) + t.Fatalf("ArtifactCandidate.ExtractorKey = %q, want %q", candidate.ExtractorKey, extractor.Key()) } if candidate.ArtifactType != extractor.ArtifactType() { - t.Fatalf("Candidate.ArtifactType = %q, want %q", candidate.ArtifactType, extractor.ArtifactType()) + t.Fatalf("ArtifactCandidate.ArtifactType = %q, want %q", candidate.ArtifactType, extractor.ArtifactType()) } if candidate.SchemaVersion != extractor.SchemaVersion() { - t.Fatalf("Candidate.SchemaVersion = %q, want %q", candidate.SchemaVersion, extractor.SchemaVersion()) + t.Fatalf("ArtifactCandidate.SchemaVersion = %q, want %q", candidate.SchemaVersion, extractor.SchemaVersion()) } if string(candidate.Payload) != `{"value":"example"}` { - t.Fatalf("Candidate.Payload = %s, want example payload", candidate.Payload) + t.Fatalf("ArtifactCandidate.Payload = %s, want example payload", candidate.Payload) } } @@ -112,7 +112,7 @@ func (extractor fakeExtractor) Validators() []Validator { func (extractor fakeExtractor) Extract(ctx context.Context, req ExtractionRequest) (ExtractionResult, error) { return ExtractionResult{ - Candidates: []artifacts.Candidate{ + Candidates: []artifacts.ArtifactCandidate{ { Index: 0, ExtractorKey: extractor.key, diff --git a/internal/framework/runner/registry_integration_test.go b/internal/framework/runner/registry_integration_test.go index a17db12..4772003 100644 --- a/internal/framework/runner/registry_integration_test.go +++ b/internal/framework/runner/registry_integration_test.go @@ -82,7 +82,7 @@ func (extractor integrationExtractor) Validators() []contracts.Validator { func (extractor integrationExtractor) Extract(ctx context.Context, req contracts.ExtractionRequest) (contracts.ExtractionResult, error) { *extractor.executedKeys = append(*extractor.executedKeys, extractor.key) return contracts.ExtractionResult{ - Candidates: []artifacts.Candidate{ + Candidates: []artifacts.ArtifactCandidate{ {Payload: []byte(`{"value":true}`)}, }, }, nil diff --git a/internal/framework/runner/runner.go b/internal/framework/runner/runner.go index 1555d7a..8f6714c 100644 --- a/internal/framework/runner/runner.go +++ b/internal/framework/runner/runner.go @@ -90,8 +90,8 @@ func (r *Runner) Run(ctx context.Context, input RunInput) (RunOutput, error) { return output, nil } -func normalizeCandidates(extractor contracts.Extractor, candidates []artifacts.Candidate, nextIndex *int) ([]artifacts.Candidate, error) { - normalized := make([]artifacts.Candidate, 0, len(candidates)) +func normalizeCandidates(extractor contracts.Extractor, candidates []artifacts.ArtifactCandidate, nextIndex *int) ([]artifacts.ArtifactCandidate, error) { + normalized := make([]artifacts.ArtifactCandidate, 0, len(candidates)) for _, candidate := range candidates { candidate.Index = *nextIndex *nextIndex = *nextIndex + 1 @@ -119,7 +119,7 @@ func normalizeCandidates(extractor contracts.Extractor, candidates []artifacts.C return normalized, nil } -func runValidators(ctx context.Context, extractor contracts.Extractor, doc *source.SourceDocument, candidates []artifacts.Candidate, metadata map[string]any) ([]artifacts.Candidate, []artifacts.RejectedArtifact, []contracts.Warning, error) { +func runValidators(ctx context.Context, extractor contracts.Extractor, doc *source.SourceDocument, candidates []artifacts.ArtifactCandidate, metadata map[string]any) ([]artifacts.ArtifactCandidate, []artifacts.RejectedArtifact, []contracts.Warning, error) { eligible := candidates var rejected []artifacts.RejectedArtifact var warnings []contracts.Warning @@ -149,7 +149,7 @@ func runValidators(ctx context.Context, extractor contracts.Extractor, doc *sour decisions[decision.CandidateIndex] = decision } - nextEligible := make([]artifacts.Candidate, 0, len(eligible)) + nextEligible := make([]artifacts.ArtifactCandidate, 0, len(eligible)) for _, candidate := range eligible { decision := decisions[candidate.Index] if decision.Approved { diff --git a/internal/framework/runner/runner_test.go b/internal/framework/runner/runner_test.go index 4c734fa..41dac2a 100644 --- a/internal/framework/runner/runner_test.go +++ b/internal/framework/runner/runner_test.go @@ -99,7 +99,7 @@ func TestRunRejectsNilExtractorFromFactory(t *testing.T) { func TestRunUsesConfiguredExtractorOrderAndAssignsGlobalIndices(t *testing.T) { var order []string var seenIndices []int - recordIndices := func(candidates []artifacts.Candidate) []contracts.ValidationDecision { + recordIndices := func(candidates []artifacts.ArtifactCandidate) []contracts.ValidationDecision { decisions := make([]contracts.ValidationDecision, 0, len(candidates)) for _, candidate := range candidates { seenIndices = append(seenIndices, candidate.Index) @@ -133,7 +133,7 @@ func TestRunUsesConfiguredExtractorOrderAndAssignsGlobalIndices(t *testing.T) { func TestRunFillsEmptyCandidateExtractorMetadata(t *testing.T) { factory := fakeFactory{extractors: map[string]contracts.Extractor{ - "generic-extractor": fakeExtractor{key: "generic-extractor", artifactType: "generic-artifact", schemaVersion: "v1", candidates: []artifacts.Candidate{{Payload: []byte(`{"value":true}`)}}}, + "generic-extractor": fakeExtractor{key: "generic-extractor", artifactType: "generic-artifact", schemaVersion: "v1", candidates: []artifacts.ArtifactCandidate{{Payload: []byte(`{"value":true}`)}}}, }} output, err := New(factory).Run(context.Background(), RunInput{Source: validSourceDocument(), ExtractorKeys: []string{"generic-extractor"}}) @@ -150,18 +150,18 @@ func TestRunFillsEmptyCandidateExtractorMetadata(t *testing.T) { func TestRunRejectsCandidateMetadataMismatches(t *testing.T) { tests := []struct { name string - candidate artifacts.Candidate + candidate artifacts.ArtifactCandidate error string }{ - {name: "extractor key", candidate: artifacts.Candidate{ExtractorKey: "other"}, error: "extractor_key"}, - {name: "artifact type", candidate: artifacts.Candidate{ArtifactType: "other"}, error: "artifact_type"}, - {name: "schema version", candidate: artifacts.Candidate{SchemaVersion: "other"}, error: "schema_version"}, + {name: "extractor key", candidate: artifacts.ArtifactCandidate{ExtractorKey: "other"}, error: "extractor_key"}, + {name: "artifact type", candidate: artifacts.ArtifactCandidate{ArtifactType: "other"}, error: "artifact_type"}, + {name: "schema version", candidate: artifacts.ArtifactCandidate{SchemaVersion: "other"}, error: "schema_version"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { factory := fakeFactory{extractors: map[string]contracts.Extractor{ - "generic-extractor": fakeExtractor{key: "generic-extractor", artifactType: "generic-artifact", schemaVersion: "v1", candidates: []artifacts.Candidate{tt.candidate}}, + "generic-extractor": fakeExtractor{key: "generic-extractor", artifactType: "generic-artifact", schemaVersion: "v1", candidates: []artifacts.ArtifactCandidate{tt.candidate}}, }} _, err := New(factory).Run(context.Background(), RunInput{Source: validSourceDocument(), ExtractorKeys: []string{"generic-extractor"}}) @@ -194,7 +194,7 @@ func TestRunApprovesCandidatesWithoutValidators(t *testing.T) { } func TestRunValidatorApprovalProducesApprovedArtifacts(t *testing.T) { - validator := fakeValidator{name: "generic-validator", decisions: func(candidates []artifacts.Candidate) []contracts.ValidationDecision { + validator := fakeValidator{name: "generic-validator", decisions: func(candidates []artifacts.ArtifactCandidate) []contracts.ValidationDecision { return []contracts.ValidationDecision{validationhelpers.Approved(candidates[0].Index)} }} factory := fakeFactory{extractors: map[string]contracts.Extractor{ @@ -212,13 +212,13 @@ func TestRunValidatorApprovalProducesApprovedArtifacts(t *testing.T) { func TestRunValidatorRejectionRemovesCandidateFromLaterValidators(t *testing.T) { var laterSeen int - rejectFirst := fakeValidator{name: "reject-first", decisions: func(candidates []artifacts.Candidate) []contracts.ValidationDecision { + rejectFirst := fakeValidator{name: "reject-first", decisions: func(candidates []artifacts.ArtifactCandidate) []contracts.ValidationDecision { return []contracts.ValidationDecision{ validationhelpers.Rejected(candidates[0].Index, "invalid", "not accepted"), validationhelpers.Approved(candidates[1].Index), } }} - approveRemaining := fakeValidator{name: "approve-remaining", decisions: func(candidates []artifacts.Candidate) []contracts.ValidationDecision { + approveRemaining := fakeValidator{name: "approve-remaining", decisions: func(candidates []artifacts.ArtifactCandidate) []contracts.ValidationDecision { laterSeen = len(candidates) return []contracts.ValidationDecision{validationhelpers.Approved(candidates[0].Index)} }} @@ -254,7 +254,7 @@ func TestRunSurfacesValidatorNameMismatch(t *testing.T) { } func TestRunSurfacesValidatorCardinalityError(t *testing.T) { - validator := fakeValidator{name: "generic-validator", decisions: func(candidates []artifacts.Candidate) []contracts.ValidationDecision { + validator := fakeValidator{name: "generic-validator", decisions: func(candidates []artifacts.ArtifactCandidate) []contracts.ValidationDecision { return nil }} factory := factoryWithValidator(validator) @@ -359,7 +359,7 @@ type fakeExtractor struct { artifactType string schemaVersion string candidateCount int - candidates []artifacts.Candidate + candidates []artifacts.ArtifactCandidate validators []contracts.Validator warnings []contracts.Warning err error @@ -386,9 +386,9 @@ func (extractor fakeExtractor) Extract(ctx context.Context, req contracts.Extrac if extractor.order != nil { *extractor.order = append(*extractor.order, extractor.key) } - candidates := append([]artifacts.Candidate(nil), extractor.candidates...) + candidates := append([]artifacts.ArtifactCandidate(nil), extractor.candidates...) for len(candidates) < extractor.candidateCount { - candidates = append(candidates, artifacts.Candidate{Payload: []byte(`{"value":true}`)}) + candidates = append(candidates, artifacts.ArtifactCandidate{Payload: []byte(`{"value":true}`)}) } return contracts.ExtractionResult{ Candidates: candidates, @@ -399,7 +399,7 @@ func (extractor fakeExtractor) Extract(ctx context.Context, req contracts.Extrac type fakeValidator struct { name string resultName string - decisions func([]artifacts.Candidate) []contracts.ValidationDecision + decisions func([]artifacts.ArtifactCandidate) []contracts.ValidationDecision warnings []contracts.Warning err error } @@ -436,7 +436,7 @@ func factoryWithValidator(validator contracts.Validator) fakeFactory { }} } -func approveAll(candidates []artifacts.Candidate) []contracts.ValidationDecision { +func approveAll(candidates []artifacts.ArtifactCandidate) []contracts.ValidationDecision { decisions := make([]contracts.ValidationDecision, 0, len(candidates)) for _, candidate := range candidates { decisions = append(decisions, validationhelpers.Approved(candidate.Index)) diff --git a/internal/framework/validators/validators.go b/internal/framework/validators/validators.go index 5129129..b4442bc 100644 --- a/internal/framework/validators/validators.go +++ b/internal/framework/validators/validators.go @@ -30,7 +30,7 @@ func Rejected(candidateIndex int, reasonCode string, message string) contracts.V } } -func EnforceDecisionCardinality(candidates []artifacts.Candidate, decisions []contracts.ValidationDecision) error { +func EnforceDecisionCardinality(candidates []artifacts.ArtifactCandidate, decisions []contracts.ValidationDecision) error { if len(candidates) != len(decisions) { return fmt.Errorf("validator returned %d decisions for %d candidates", len(decisions), len(candidates)) } diff --git a/internal/framework/validators/validators_test.go b/internal/framework/validators/validators_test.go index 5cab651..53f97e9 100644 --- a/internal/framework/validators/validators_test.go +++ b/internal/framework/validators/validators_test.go @@ -43,7 +43,7 @@ func TestRejectedTrimsReasonAndMessage(t *testing.T) { } func TestEnforceDecisionCardinalityAllowsNonZeroCandidateIndices(t *testing.T) { - candidates := []artifacts.Candidate{{Index: 4}, {Index: 8}} + candidates := []artifacts.ArtifactCandidate{{Index: 4}, {Index: 8}} decisions := []contracts.ValidationDecision{Approved(8), Approved(4)} if err := EnforceDecisionCardinality(candidates, decisions); err != nil { @@ -59,7 +59,7 @@ func TestEnforceDecisionCardinalityAllowsEmptyInputs(t *testing.T) { func TestEnforceDecisionCardinalityRejectsUnknownDecisionIndex(t *testing.T) { err := EnforceDecisionCardinality( - []artifacts.Candidate{{Index: 1}}, + []artifacts.ArtifactCandidate{{Index: 1}}, []contracts.ValidationDecision{Approved(2)}, ) @@ -68,7 +68,7 @@ func TestEnforceDecisionCardinalityRejectsUnknownDecisionIndex(t *testing.T) { func TestEnforceDecisionCardinalityRejectsDuplicateDecisionIndex(t *testing.T) { err := EnforceDecisionCardinality( - []artifacts.Candidate{{Index: 1}, {Index: 2}}, + []artifacts.ArtifactCandidate{{Index: 1}, {Index: 2}}, []contracts.ValidationDecision{Approved(1), Approved(1)}, ) @@ -77,7 +77,7 @@ func TestEnforceDecisionCardinalityRejectsDuplicateDecisionIndex(t *testing.T) { func TestEnforceDecisionCardinalityRejectsMissingDecisionIndex(t *testing.T) { err := EnforceDecisionCardinality( - []artifacts.Candidate{{Index: 1}, {Index: 2}}, + []artifacts.ArtifactCandidate{{Index: 1}, {Index: 2}}, []contracts.ValidationDecision{Approved(1)}, ) @@ -86,7 +86,7 @@ func TestEnforceDecisionCardinalityRejectsMissingDecisionIndex(t *testing.T) { func TestEnforceDecisionCardinalityRejectsDuplicateCandidateIndex(t *testing.T) { err := EnforceDecisionCardinality( - []artifacts.Candidate{{Index: 1}, {Index: 1}}, + []artifacts.ArtifactCandidate{{Index: 1}, {Index: 1}}, []contracts.ValidationDecision{Approved(1), Approved(1)}, )