13 KiB
Extraction References Implementation Plan
Purpose
Implement the extraction-reference feature described in references.md. This plan is decision-complete for an LLM coding agent: implement each stage in order, keep the repository compiling after each stage, and do not move planned behavior into non-roadmap docs until the relevant behavior exists.
Core decisions to preserve:
- references are opaque framework inputs and domain semantics stay in extract modules;
- references are not evidence and must not be addressable through
SourceRef; - reference binding is lane-scoped;
- extractors expose
ReferenceSlots()directly on the first-class extractor contract; - token budgeting is deferred; enforce only UTF-8 text handling, empty-file
warnings, and declared
MaxBytes; - run manifests record references in a dedicated section, separate from
source_digests; - CLI unbinding uses
--without-reference.
Stage 1: Contracts and Mechanical Adoption
Add the framework contracts needed to describe references without changing runtime behavior. Slot declarations must be available without constructing extractor modules, because pipeline/config validation should use registry metadata rather than runtime module instances.
Implementation steps:
- In
internal/framework/contracts, add reference model types:ReferenceSlot,ReferenceOrigin,ReferenceItem,ResolvedReferenceSlot,ReferenceSet, and a binding-source enum or string constants forconfigandcli. - Include
Name,Description,Required,AcceptedMediaTypes,Multiple, andMaxBytesonReferenceSlot. - Include slot name, media type, content bytes, digest, origin, size bytes, and
binding source on
ReferenceItem. - Add
References ReferenceSettocontracts.ExtractionRequest. - Add
ReferenceSlots() []ReferenceSlottocontracts.Extractor. - Extend extractor registration metadata so reference slots are also declared
through the extractor's registry spec. Prefer the smallest idiomatic change to
the existing registry model, such as adding
ReferenceSlotstoModuleSpecwith validation that non-extractor modules leave it empty, unless the codebase shape clearly supports a narrower extractor-specific spec. - Update every concrete extractor and all extractor fakes/test doubles to
implement
ReferenceSlots(). Existing extractors without references should returnnil. - Add tests that compare a production extractor's runtime
ReferenceSlots()with its registered spec slots so the two declarations cannot drift. - Add contract tests for empty reference sets, slot copying expectations if helpers are introduced, and compile-time coverage through existing fakes.
Verification:
go test ./internal/framework/contractsgo test ./internal/framework/pipelinego test ./...
Stage 2: Config Shape and Pipeline-Level Resolution
Add unresolved reference bindings to config and resolved lane bindings to the pipeline model. Do not read reference files in this stage.
Implementation steps:
- Add
referencesmaps to file config parsing at both pipeline and artifact lane level. - Add corresponding fields to
pipeline.PipelineProfileandpipeline.ArtifactLaneProfile. - Preserve deterministic map handling and duplicate-after-trim validation.
- Extend config cloning, effective config, redaction, validation, and tests for the new fields.
- Add resolved reference binding structures to
internal/framework/pipeline. They should represent lane ID, slot name, source URI/path, and binding source, but not file bytes. - During
pipeline.ResolvePipeline, collect selected lanes, read each lane extractor's declared slots from registry metadata, and validate without building extractor instances:- every bound slot is declared by the lane extractor;
- required slots are bound after applying pipeline-level and lane-level config;
- required slots remain bound after any CLI unbinds supplied to resolution;
- selected lanes under
--onlyare the only lanes considered.
- Apply pipeline-level bindings as defaults only to lanes whose extractor declares the matching slot.
- Apply lane-level bindings as overrides or additions for that lane.
- Keep reference bindings out of source digests and artifact source references.
- Add tests proving reference-slot validation works through registry specs even when extractor constructors would fail if called.
Verification:
go test ./internal/core/configgo test ./internal/framework/pipelinego test ./...
Stage 3: CLI Reference Overrides and Unbinds
Add run-time CLI syntax for reference binding overrides and optional unbinding.
Implementation steps:
- Add repeatable
--referenceflags tonotarius run. Accepted forms:slot=pathfor unambiguous slot names across selected lanes;lane.slot=pathfor explicit lane-scoped binding.
- Add repeatable
--without-referenceflags tonotarius run. Accepted forms:slot;lane.slot.
- Reject empty paths for
--reference; use--without-referencefor unbinding. - Reject malformed values with concise CLI errors before expensive work.
- Pass parsed override/unbind requests into config/pipeline resolution.
- Resolve flat CLI names only when exactly one selected lane declares the slot.
If multiple selected lanes declare the same slot, fail and instruct the user
to use
lane.slot. - Let CLI bindings override config bindings for the same lane and slot.
- Let CLI unbinds remove config-bound optional slots for the same lane and slot.
- Fail if unbinding leaves a required slot unbound.
- Add CLI tests for flat binding, lane-qualified binding, ambiguous flat binding, malformed syntax, optional unbind, and required-slot unbind failure.
Verification:
go test ./internal/cligo test ./internal/core/configgo test ./internal/framework/pipelinego test ./...
Stage 4: Run Preparation and Reference Materialization
Read, validate, digest, and materialize resolved file references before any LLM call.
Implementation steps:
- Add a reference resolver/materializer near pipeline run preparation. Keep file I/O out of pure config parsing.
- Ensure run preparation receives the loaded config path or config directory so config-relative reference paths can be resolved after pure config parsing.
- Resolve config-relative paths relative to the config file path and CLI-relative paths relative to the current working directory.
- For MVP, accept only UTF-8 text files. Reject non-UTF-8 content with an error naming pipeline, lane, slot, and path.
- Compute
sha256:content digests over the raw reference bytes. - Populate
ReferenceItemvalues with content bytes, media type, digest, origin typefile, normalized origin URI/path, size bytes, and binding source. - Enforce declared
MaxByteswhen greater than zero. The error should name the pipeline, lane, slot, actual size, limit, and path. - Emit a warning for empty bound files, but do not fail.
- Add
ReferenceSetvalues to the runner input or resolved pipeline path in a way that keeps lane-scoped references available when calling each extractor. - Pass the correct lane-specific
ReferenceSetintocontracts.ExtractionRequest. - Ensure no reference content is written to ordinary diagnostics, logs, errors, or manifests.
Verification:
- Focused resolver/materializer tests for path resolution, digest stability,
UTF-8 rejection, empty-file warning,
MaxBytes, and binding source. go test ./internal/cligo test ./internal/framework/pipelinego test ./...
Stage 5: Prompt Template Reference Functions
Make references available to module-owned prompt templates.
Implementation steps:
- Extend
internal/framework/promptso prompt bundles can be compiled with declared reference slots. - Add
referenceandhasreferencetemplate functions. - Validate at bundle build time, or the earliest feasible equivalent, that templates reference only declared slots.
- Render a declared but unbound optional slot as an empty string.
- Ensure
hasreferencereturns true only when the slot has at least one bound item with content. - Render multiple items deterministically if future
Multiplesupport is enabled; for MVP, reject multiple bindings unless the slot declaresMultiple. - Keep prompt metadata hashes based on template source. Do not include rendered reference content in prompt identity.
- Add deterministic rendering tests proving byte-identical output across runs with the same reference bytes and config.
Verification:
go test ./internal/framework/promptgo test ./internal/modules/extract/dnd/spellsgo test ./...
Stage 6: Manifest and Diagnostics Provenance
Record reference provenance separately from source provenance.
Implementation steps:
- Add a dedicated references section to
artifacts.RunManifest. The shape should be lane-scoped and include lane ID, slot name, origin type, origin URI/path, digest, media type, size bytes, and binding source. - Do not add reference digests to
source_digests. - Include reference digests in any cache/idempotency key if such a key exists. If no cache/idempotency key exists, add a test or comment documenting that no additional key needs updating yet.
- Write a diagnostics artifact for resolved references that contains provenance only, not full content, consistent with redacted effective config behavior.
- Ensure durable JSON output manifests include the new manifest section.
- Add manifest round-trip tests and a CLI/run test where two runs that differ only in reference bytes produce distinguishable manifests.
Verification:
go test ./internal/core/artifactsgo test ./internal/core/diagnosticsgo test ./internal/modules/output/jsongo test ./internal/cligo test ./...
Stage 7: D&D Spells Consumer
Use the new reference feature in the first production extractor.
Implementation steps:
- Declare optional
rosterandglossaryslots ondnd/spells. - Set accepted media type to text/UTF-8. Add conservative
MaxByteslimits only if a clear module-owned limit is chosen; otherwise leaveMaxBytesunset. - Update the D&D spells prompt bundle to include conditional reference sections
using
hasreferenceandreference. - Frame references as supporting material only. The prompt must instruct the model to extract only spell-cast events present in the source transcript and use references only for disambiguation.
- Update prompt metadata tests as needed while preserving template-hash semantics.
- Add fixture coverage with no references, with roster/glossary references, and with a roster that mentions a spell never cast in the transcript. The last case must assert no spell-cast artifact is produced for the uncast spell.
- If existing deterministic source-reference validation can be extended cleanly, add warning-level relatedness checks for spell names or close variants near cited source text. If this becomes large, defer that validator enhancement to a separate roadmap item and keep the prompt/regression fixture guard in this stage.
Verification:
go test ./internal/modules/extract/dnd/spellsgo test ./internal/framework/pipelinego test ./internal/cligo test ./...
Stage 8: Canonical Documentation and Examples
Move implemented behavior out of roadmap-only status once code exists.
Implementation steps:
- Update
docs/cli.mdwith--referenceand--without-referencesyntax, precedence, ambiguity behavior, and examples. - Update
docs/config.mdwith pipeline-level and lane-levelreferencesblocks. - Update
docs/internal/modules.mdor the most appropriate internal docs with module-author guidance forReferenceSlots(), reference request delivery, prompt functions, evidence exclusion, and provenance. - Update
docs/internal/pipeline.mdwith reference resolution lifecycle and lane-scoped delivery. - Update
docs/integrations/json-output.mdwith the manifest reference provenance shape. - Update
docs/operations.mdordocs/troubleshooting.mdfor common reference errors such as unknown slot, ambiguous flat override, missing required slot, unreadable file, non-UTF-8 content, andMaxBytesfailures. - Add maintained example reference files and update
examples/dnd-spells.config.ymlonly after the CLI/config behavior is implemented and covered by tests. - Keep future-only material in
docs/roadmap/references.md; do not duplicate canonical current behavior there after implementation.
Verification:
rg -n "references:|--reference|--without-reference|ReferenceSlots|reference \"|hasreference" docs examplesgo test ./...go vet ./...go build ./cmd/notarius
Final Acceptance Criteria
The feature is complete when:
- extractor modules can declare reference slots through the first-class extractor contract;
- config and CLI can bind and unbind lane-scoped file references;
- selected-pipeline validation catches unknown, ambiguous, or missing required references before any LLM call;
- run preparation materializes UTF-8 text references with digests, size checks, and empty-file warnings;
- extractors receive lane-scoped resolved references;
- prompt templates can render
referenceandhasreferencedeterministically; - D&D spell extraction uses optional roster and glossary references;
- run manifests and diagnostics record reference provenance without recording full content or treating references as source evidence;
- canonical docs and maintained examples describe only implemented behavior;
go test ./...,go vet ./..., andgo build ./cmd/notariuspass.