From 64d461fc189d352dfc23d2519604dae05483c090 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Mon, 27 Jul 2026 12:09:14 -0500 Subject: [PATCH] Add a feature roadmap and implementation plan for a context evidence artifact --- docs/roadmap/evidence.md | 226 +++++++++++ docs/roadmap/implementation.md | 686 +++++++++++++++++++++------------ 2 files changed, 670 insertions(+), 242 deletions(-) create mode 100644 docs/roadmap/evidence.md diff --git a/docs/roadmap/evidence.md b/docs/roadmap/evidence.md new file mode 100644 index 0000000..cd86f02 --- /dev/null +++ b/docs/roadmap/evidence.md @@ -0,0 +1,226 @@ +# Published Evidence Context + +## Status + +Accepted for implementation. + +## Purpose + +Let downstream consumers build narrative reports from normalized artifacts +without separately parsing the original transcript or resolving source-unit +references themselves. + +The production JSON output optionally publishes one deterministic, deduplicated +evidence-context artifact containing the transcript units relevant to explicitly +selected normalized lanes. Existing lane payloads remain the canonical semantic +results and retain their precise source references. + +## Desired End State + +When evidence-context publication is enabled, a consumer can: + +1. discover one versioned evidence-context document through `index.json`; +2. obtain the union of source units needed to understand evidence cited by the + selected normalized lanes; +3. distinguish each artifact's direct evidence references from surrounding + units included only for narrative context; +4. retain speaker, timestamp, and other accepted source-unit metadata needed to + interpret the transcript; and +5. produce a narrative report without receiving duplicated transcript text in + every lane payload. + +This is deterministic output projection. It does not invoke an LLM, change +normalization, or make surrounding context part of an artifact's evidence. + +## Configuration Policy + +Evidence publication is configured on the production JSON output module. The +intended configuration shape is: + +```yaml +output: + module: json + options: + evidence_context: + enabled: true + window_units: 3 + lanes: + - combat-turns + - item-events + - npc-interactions + - npcs + - spells +``` + +- Omitting `evidence_context` disables publication. When the object is present, + `enabled` is required. +- `enabled: false` accepts no `lanes` or `window_units` fields, preventing + silently ignored configuration. +- `lanes` is a required, non-empty allowlist of configured final lane IDs when + evidence publication is enabled. Values are trimmed, unique, and normalized + to lexical order. +- `window_units` is a non-negative integer and defaults to `3`. Zero publishes + only directly referenced units. +- Unknown lanes, duplicate lane IDs, and selected lanes whose artifact kind + cannot expose source evidence fail configuration resolution or pipeline + preparation. +- A selected lane that completes without a normalized output contributes no + evidence and does not make an otherwise successful run fail. +- Invocation-level lane filtering does not invalidate the configured allowlist. + Allowlisted lanes excluded from the effective run contribute nothing, while + the evidence document still records the configured allowlist. + +The allowlist is intentional safety and stability policy. Scene descriptions +and other broad-range lanes are excluded unless named expressly. Adding a new +pipeline lane never silently increases output size or publishes more transcript +content. + +## Evidence Collection Boundary + +Evidence collection applies to accepted final normalized artifacts from the +selected lanes. It must not inspect arbitrary serialized JSON for fields named +`source_ref` or `source_refs`, and the generic JSON output module must not +depend on D&D artifact types. + +Artifact-kind registrations expose their source references through an explicit +typed projection contract. The framework uses that contract to assemble a +domain-neutral evidence request containing: + +- the accepted generic source document; +- the selected lane and artifact identities; and +- defensive copies of their direct source references. + +The output stage owns publication of the resulting logical artifact. Generic +framework code owns range validation, position-based expansion, and union +logic. Domain-specific adapters own only the extraction of evidence references +from their typed artifacts. + +Both plural-reference artifacts and singular-reference artifacts, such as +scene descriptions, can participate through the same projection contract. +They do so only when their configured lane is allowlisted. + +## Range Expansion And Deduplication + +For every valid direct source reference: + +1. resolve its endpoints through source-document positions, not numeric + unit-ID arithmetic; +2. expand the range by `window_units` positions on each side; +3. clip the expanded range at document boundaries; and +4. union overlapping or contiguous expanded ranges. + +Published contexts and units remain in source-document order. Each source unit +appears at most once in a merged context. Original direct references remain +unchanged and are associated with their contributing lane IDs so consumers can +tell why a context was included. + +The projector must not silently omit or repair an invalid reference that +reaches this boundary. Such a value violates the accepted normalized-artifact +contract and causes output projection to fail with a content-safe error. + +No implicit coverage limit truncates selected evidence. If the allowlisted +lanes collectively cite most or all of a transcript, the evidence document may +contain most or all of it. The explicit lane allowlist is the control that +prevents a broad lane such as scene descriptions from doing so accidentally. + +## Durable Evidence Artifact + +The JSON bundle gains one optional, non-lane artifact with these durable +identities: + +| Property | Value | +| --- | --- | +| Logical file | `evidence-context.json` | +| Index descriptor | `evidence_context` | +| Artifact kind | `source/evidence-context` | +| Media type | `application/json` | +| Schema ID | `notarius.source.evidence_context` | +| Schema name | `notarius_source_evidence_context_v1` | +| Schema version | `v1` | + +The descriptor in `index.json` carries the artifact and schema identities, +analogous to the existing chunk-map descriptor. The artifact is present +whenever evidence publication is enabled, including when its context collection +is empty. + +The document contains: + +- the source document ID and semantic digest; +- the effective window size; +- the sorted configured lane allowlist; +- an ordered context collection; +- each context's expanded start and end unit IDs; +- the original direct references and contributing lane IDs covered by that + context; and +- the ordered accepted source units, including unit ID, kind, text, + self-reference, and metadata. + +Expanded context bounds are navigation aids, not citations. The original +references embedded in each context remain the authoritative direct evidence. +The evidence artifact is discovered separately from lane payloads and does not +increase the normalized-lane count reported by the runner or subprocess +receipt. + +## Failure And Publication Semantics + +- Evidence projection occurs only after selected normalized outputs are known + and before the output encoder returns its logical files. +- Projection or encoding failure is an output-stage framework error; the CLI + does not publish a partially assembled output bundle. +- Rejected or absent lane outputs contribute nothing. Their attempted values + and source references must not be published through this artifact. +- Context generation is deterministic for the same source document, selected + normalized outputs, lane allowlist, and window size. +- Existing output, checkpoint, warning, rejection, debug, and subprocess + success semantics remain unchanged. + +## Sensitivity And Size + +Unlike the current chunk map, the evidence artifact contains transcript text +and source-unit metadata. Enabling it therefore creates additional durable +sensitive data and may materially increase bundle size. + +The implemented configuration, operations, integration, and consumer documents +state that: + +- evidence publication is opt-in; +- output permissions and retention must be appropriate for source content; +- selecting broad or numerous lanes can publish most of the transcript; and +- the artifact must not contain raw input bytes, LLM prompts or responses, + auxiliary reference content, credentials, debug-only data, or filesystem + paths. + +## Acceptance Criteria + +- Evidence publication is disabled by default and leaves existing bundles + unchanged. +- Enabling it requires an explicit non-empty lane allowlist. +- References from all selected successful lanes contribute to one deduplicated + document. +- Non-monotonic unit IDs are expanded and ordered correctly by document + position. +- Overlapping windows share one ordered copy of each included source unit. +- Direct references remain distinguishable from added context. +- Scene descriptions cannot contribute unless their lane is explicitly + allowlisted. +- Invalid selected lanes and unsupported artifact kinds fail before execution; + invalid accepted references fail output projection rather than being ignored. +- Empty selected-lane results produce a valid empty evidence artifact. +- Existing D&D lane schemas, normalized-output counts, and source-reference + semantics do not change. +- Generic framework and output packages do not depend on D&D types or parse + artifact JSON heuristically. +- The published contract and operational documentation clearly describe source + sensitivity, discovery, compatibility, and retention. + +## Out Of Scope + +- Embedding transcript units directly into each D&D record or lane payload. +- Replacing precise source references with expanded context ranges. +- Automatically including every configured lane. +- An explicit full-transcript publication mode. +- LLM summarization, retrieval, ranking, or narrative generation. +- Per-record window sizes or lane-specific window sizes. +- CLI overrides for evidence configuration. +- Reading rejected attempts, debug artifacts, auxiliary references, or prior + output bundles as evidence sources. diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 8b11e07..05f35d9 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -1,284 +1,486 @@ -# Subprocess Integration Implementation Plan +# Published Evidence Context Implementation Plan ## Status -Completed. +Ready for implementation. ## Objective -Implement the accepted [Subprocess Integration Contract](subprocess.md) as a -small, generic extension of the existing CLI boundary. The completed work must -let a subprocess caller discover a successful Notarius output bundle through a -versioned JSON receipt without parsing human prose, while preserving current -pipeline, output, rejection, warning, configuration, and interactive CLI -behavior. +Implement the accepted [Published Evidence Context](evidence.md) roadmap as an +optional, deterministic extension of the production JSON output. The completed +work must publish one deduplicated source-context artifact for an explicit set +of successful normalized lanes while leaving existing lane payloads, +normalization, checkpointing, subprocess results, and disabled output bundles +unchanged. -Complete the stages below in order. Each stage must leave its affected package -passing before the next begins. Do not add Narratio-specific production code, -a public Go client, a new output encoder, or any feature listed as out of scope -in the feature roadmap. +Complete the stages below in order. Each stage must leave its affected packages +passing before the next begins. Do not implement per-record hydration, implicit +all-lane collection, a full-transcript mode, LLM processing, or any other +roadmap item marked out of scope. ## Decisions And Invariants -- `--json` is a boolean flag on `notarius run`; it does not affect pipeline - resolution, execution, checkpoint identity, output encoding, or publication. -- The machine result is a private CLI Go type implementing the public - `notarius.run-result.v1` wire contract. Do not expose framework or CLI Go - packages for external import. -- The finalized run manifest is authoritative for `run_id`, `pipeline_id`, and - `validation_status`. Verify that its pipeline ID matches the effective - resolved pipeline rather than emitting conflicting provenance. -- Human-oriented stdout remains byte-for-byte governed by its current path when - `--json` is absent. Machine mode emits no human status or debug-path line on - stdout. -- Warnings and errors remain on stderr. Exit statuses remain 0 for success, 1 - for runtime failure, and 2 for syntax failure. -- Validation rejection remains a successful pipeline outcome. The receipt - reports counts but does not decide which lanes an orchestrator requires. -- The receipt points to the existing bundle; it does not duplicate output - descriptors, lane payloads, warnings, rejections, or manifest content. -- Reported filesystem paths are lexical absolute paths produced with - `filepath.Abs`. Do not resolve symlinks or change the physical directories - used for publication. -- For the production `json` output module, `index_file` is exactly - `index.json` and is included only after confirming that the runner returned - exactly one logical file with that clean name. For any injected or future - non-`json` output module, omit `index_file`; the machine receipt remains - generic. -- Machine-result construction and JSON encoding occur before debug success - terminalization, but stdout writing occurs only after output publication and - successful requested debug terminalization. -- Standard output is not transactional. A final writer failure returns exit 1 - and may leave partial bytes. Consumers must ignore stdout unless the process - exits 0. Because debug terminalization must precede receipt emission, a - result-delivery failure does not rewrite an already persisted successful run - report; it is additionally reported on stderr and the process exits 1. -- Do not add a stable JSON error envelope or promote diagnostic wording into a - compatibility contract. +- Evidence publication is output policy. Normalizers continue to return + semantic artifacts with precise source references and do not receive + hydration responsibilities. +- The framework operates on the accepted generic `source.SourceDocument` and + typed artifact projections. It must not inspect serialized JSON for + `source_ref` or `source_refs`, and generic packages must not depend on D&D + types. +- The selected lane allowlist is explicit, non-empty, and globally addressed by + resolved lane ID. Pipeline resolution already guarantees lane IDs are unique + across ordered steps. +- The framework decodes accepted serialized normalize outputs through their + registered artifact codecs before invoking typed evidence projectors. This + supports both fresh and checkpoint-reused normalize outputs without retaining + a second typed result channel. +- Expansion uses source-document positions. Numeric unit IDs are identities, + not sequence numbers. +- Direct source references are never widened or rewritten. Expanded ranges are + context bounds only. +- Rejected, failed, and absent normalized lane outputs contribute no evidence. +- Evidence output is sensitive durable source content, not cache or debug + state. It contains accepted source units only and never raw input bytes, + prompts, model responses, auxiliary references, paths, or credentials. +- The optional `evidence_context` index field is an additive v1 JSON-bundle + change. Existing D&D artifact schemas and the subprocess receipt do not + change. -## Stage 1: Define The Run-Result Wire Model +## Stage 1: Add Typed Evidence Capability And Resolve Output Policy -Add a focused private implementation under `internal/cli`, preferably -`run_result.go`, with: +Add a dedicated artifact-evidence registry under the pipeline framework: -- a constant for `notarius.run-result.v1`; -- a private result struct whose JSON fields and requiredness match the feature - roadmap; -- a constructor that accepts the resolved pipeline, completed runner output, - run output directory, and optional debug directory; and -- serialization into an owned byte slice containing exactly one compact JSON - object followed by one newline; and -- a complete-write helper for delivering those prepared bytes. +- `pipeline.ArtifactEvidenceRegistry` stores one typed projector per artifact + kind. +- `pipeline.ArtifactEvidenceProjector[T]` is + `func(T) []source.SourceRef`. +- `pipeline.RegisterArtifactEvidence[T](registry, kind, projector)` accepts a + non-empty kind and non-nil projector, records the exact Go type for `T`, and + rejects duplicate kinds. +- The erased projection boundary checks the exact registered type, invokes the + projector, and returns a defensive copy of its references. +- The registry exposes only the discovery and projection operations required by + resolution, preparation, and execution; do not expose its mutable entries. -The result fields are: +Add the registry to `pipeline.Registries` and `pipeline.ModuleCatalog`, including +CLI catalog conversion, production construction, empty-set detection, and +test registry helpers. A nil evidence registry remains valid when evidence +publication is disabled. Production construction and the D&D registrar require +and populate it. -- required `schema_version`, `run_id`, `pipeline_id`, `output_directory`, - `normalized_output_count`, `rejected_output_count`, and `warning_count`; -- required non-empty `validation_status`, copied without reinterpretation from - the final run manifest; -- optional `index_file`, set to `index.json` for the resolved production JSON - output module; and -- optional `debug_directory`, present only when debug capture was requested and - its path is non-empty. +Define these framework-level output-policy contracts: -The constructor must: +```go +type EvidenceContextPolicy struct { + Enabled bool + WindowUnits int + LaneIDs []string +} -1. validate the required run ID, pipeline ID, final validation status, and - non-empty run output directory rather than emit a malformed receipt; -2. verify that the final manifest pipeline ID matches the effective resolved - pipeline ID; -3. convert the output bundle and optional debug bundle paths to lexical absolute - paths; -4. derive all counts from the completed `pipeline.RunOutput`; -5. identify the output module through the resolved module key, not concrete - encoder types or D&D knowledge; and -6. for module key `pipeline.DefaultOutputModule`, verify exactly one returned - logical output file is named `index.json` before setting `index_file`. +type EvidenceContextPolicyProvider interface { + EvidenceContextPolicy() EvidenceContextPolicy +} +``` -Treat a missing or duplicate `index.json` from the production JSON output as a -runtime contract error. Do not inspect or decode the index contents here; the -output encoder and its existing tests own that format. +Provider and prepared-pipeline boundaries defensively copy `LaneIDs`. -Implement the writer with `encoding/json` and complete-write handling. It must -surface encoding errors, zero-progress writes, short writes, and underlying -writer errors to the command boundary. The command must map a delivery failure -to a fixed, bounded result-write error without wrapping caller-supplied writer -text into user or debug diagnostics. Do not add an injected serializer or a -generic serialization framework merely to manufacture unreachable error cases -for tests. +Add an optional output-profile option-validation callback to +`OutputEncoderRegistry`: -Add lean focused tests for the wire-model boundary: +```go +type OutputProfileOptionContext struct { + LaneIDs []string +} -- required fields, counts, schema identity, and compact newline-terminated JSON; -- blank required manifest identities, blank validation status, and a mismatch - between final-manifest and effective pipeline IDs; -- lexical absolute conversion for relative output and debug paths; -- production JSON entry-point discovery; -- omission of `index_file` for a non-JSON output module; -- missing and duplicate production entry points; and -- a representative failing or zero-progress writer. +type OutputProfileOptionValidator func( + OutputProfileOptionContext, + map[string]any, +) error +``` -Decode structured output in tests rather than snapshotting an entire JSON -string. Assert literal field names and the schema identity because they are the -public compatibility contract. +Add `RegisterBuilderWithProfileValidation(spec, validateOptions, +validateProfile, builder)` and make existing output registration methods +delegate to it with no profile callback. The registry passes defensive copies +to validation. The callback receives the complete configured lane-ID set before +invocation-level `--only` filtering. The production JSON output uses it only to +prove that every configured evidence lane exists. Keep the extension generic: +the pipeline supplies lane identities, while the output module interprets its +own options. Build that lane set from the normalized legacy-or-steps profile +before selection and reject duplicate configured lane IDs through the existing +pipeline identity rules. + +Extend the production JSON output options with the nested +`evidence_context` object: + +- omission disables the feature; +- `enabled` is required when the object is present; +- `enabled: false` permits no `lanes` or `window_units` fields; +- `enabled: true` requires a non-empty `lanes` array; +- lane values are strings, trimmed, non-empty, unique after trimming, and + normalized to lexical order; +- `window_units` is an optional non-negative integer with default `3`; and +- outer and nested unknown fields and incompatible YAML value types remain + strict configuration errors. + +The JSON encoder implements the policy provider from its decoded immutable +options. Pipeline resolution invokes its profile validator against all +configured steps, so an unknown evidence lane fails even when another lane is +selected with `--only`. + +During `pipeline.Prepare`, after constructing the output encoder: + +1. obtain and defensively normalize an enabled policy; +2. intersect its configured IDs with the effective prepared lanes, treating + allowlisted lanes removed by invocation-level filtering as inactive; +3. require an artifact-evidence registration for each active lane kind; +4. prove that its projector Go type exactly matches the active lane's registered + artifact codec type; and +5. retain an immutable private evidence plan on `PreparedPipeline`. + +Duplicate or empty provider values, a missing evidence registry for an active +lane, unsupported active artifact kinds, and type mismatches fail preparation +with pipeline/output/lane context. A disabled or non-participating output +encoder creates no evidence plan and preserves existing preparation behavior. +The private plan retains both the full configured allowlist for publication and +the active lane/projector intersection for execution. + +Register D&D evidence projectors for all six current artifact kinds. Each +projector returns copies of the artifact's direct references in record order: +spells, NPCs, combat turns, item events, NPC interactions, and the singular +reference from each scene description. Scene descriptions gain capability but +remain excluded unless their configured lane ID is allowlisted. + +Stage tests: + +- Registry tests cover nil, blank, duplicate, exact-type, defensive-copy, and + deterministic discovery behavior. +- JSON option tests cover disabled, enabled/default-window, explicit zero + window, normalization, duplicates, unknown fields, and invalid types. +- Preparation tests cover selected lanes across steps, unknown lanes, + unsupported kinds, projector/codec type mismatch, disabled behavior, and + defensive policy ownership. +- Resolution/preparation tests prove a valid allowlist survives `--only`, an + excluded lane contributes no active projector, and a genuinely unknown + configured lane still fails profile resolution. +- D&D registration tests prove every production D&D artifact kind has the + expected evidence capability without testing individual field loops + redundantly. +- One table-driven D&D projector test supplies representative values for all + six artifact kinds and proves plural and singular references are copied + without aliasing or semantic rewriting. Stage completion: -- `go test ./internal/cli` - -## Stage 2: Integrate Machine Mode With Run Finalization - -Update the run command in `internal/cli/run.go`: - -- register `--json` with the existing `flag.FlagSet`; -- include it in root usage without adding it to `runFlagTakesValue`; -- leave all existing flag combinations valid; and -- do not add configuration or environment equivalents. - -After the runner succeeds and requested debug summaries have been written, -apply this exact finalization order: - -1. when `--json` is selected, construct and encode the complete receipt into - owned memory; fail before physical output publication if its required fields - or production entry point are invalid; -2. publish the runner's logical output files through the existing confined - output writer; -3. terminalize requested debug reporting as successful and stop without - writing stdout if terminalization fails; -4. in machine mode, write the prepared receipt to stdout and return a runtime - failure if that write does not complete; -5. otherwise, use the unchanged human-oriented success reporting path; and -6. report successful-run warning counts to stderr as today. - -The receipt's `output_directory` is the absolute path to the run-specific -bundle, not the configured output root. `debug_directory` is the absolute path -to the allocated run-specific debug bundle. - -On a machine-result stdout failure after terminalization: - -- report a fixed, bounded, code-owned result-write error on stderr through the - existing CLI failure reporting surface without including the underlying - writer error; -- include the existing debug-path discovery line when applicable; -- return exit 1; -- do not attempt terminal reporting a second time; and -- leave the successfully published output and debug bundle intact. - -Do not change `pipelineCommandState`, the debug run-report wire shape, the -output encoder contract, or `writeOutputFiles` solely to support this feature. -The machine receipt belongs to CLI reporting after the runner and output module -have completed their existing responsibilities. - -Stage completion: - -- `go test ./internal/cli` - -## Stage 3: Protect The Public CLI Contract - -Add or extend behavior-level CLI tests at the narrowest stable boundaries. -Reuse existing production components, fakes, state harnesses, and maintained -examples rather than creating a second subprocess fixture framework. - -Cover: - -- a representative maintained production invocation with `--json`, decoding - exactly one stdout document and verifying the schema identity, run and - pipeline IDs, absolute output directory, `index_file`, counts, validation - status, and the existence of the referenced bundle entry point; -- a warning-bearing debug run, proving a correct warning count, warning - reporting on stderr, an absolute optional debug path, and no human prose in - stdout; -- a rejection-bearing successful run using an explicit deterministic rejecting - validator, proving zero or partial normalized outputs and the correct - rejection count without changing exit status; -- one representative syntax failure and one representative runtime failure - with `--json`, proving the established exit class, stderr ownership, and no - completed success document; -- an injected stdout writer failure after successful publication, proving exit - 1, retained output files, fixed diagnostic reporting, and omission of a - recognizable writer-error sentinel from stderr and debug diagnostics; and -- the existing no-`--json` tests continuing to protect interactive output. - -For the writer-failure case, use a writer that fails before accepting bytes when -asserting empty stdout. The public contract nevertheless remains that arbitrary -writers may leave partial bytes and consumers must ignore stdout on nonzero -exit. - -Do not: - -- duplicate the JSON output encoder's lane, manifest, warning, or rejection - serialization matrix; -- assert complete human error strings; -- add a golden file for the small receipt; -- test private helper call order; or -- add a prompt-language or unrelated end-to-end test. - -Stage completion: - -- `go test ./internal/cli` +- `go test ./internal/framework/pipeline` - `go test ./internal/modules/generic/output/json` -- `go test ./internal/modules/integration` +- `go test ./internal/modules/dnd/register` +- `go test ./internal/cli` -## Stage 4: Publish The Consumer-Facing Documentation +## Stage 2: Define And Build The Evidence-Context Artifact -After the code and behavior tests pass, update the canonical current-behavior -documentation: +Add a domain-neutral `internal/framework/evidencecontext` package that owns the +durable model, JSON Schema, strict codec, projection algorithm, and these exact +identities: -- `docs/cli.md` - - add `--json` to run syntax and its flag table; - - define stdout/stderr behavior and the requirement to parse machine stdout - only after exit 0; - - preserve the existing exit-status table and link to the run-result - contract. -- `docs/integrations/run-result.md` - - own the complete `notarius.run-result.v1` field table, requiredness, - example, path semantics, production `index_file` rule, partial-write rule, - and additive compatibility policy; - - link to the published JSON bundle contract rather than repeating its - descriptors or payload schemas. -- `docs/integrations/json-output.md` - - add only a narrow cross-link explaining that a subprocess caller obtains - the physical bundle root from the run-result receipt before using - `index.json` for logical discovery; - - do not duplicate run-result fields or stream semantics. -- `docs/consumers/subprocess.md` - - provide a concise task workflow for preflight, invocation, separate stream - capture, exit checking, receipt decoding, confined index resolution, lane - lookup by ID, media-type and schema-identity checks, required-versus-optional - consumer policy, provenance retention, and sensitive-data handling; - - use a generic orchestrator example with placeholders, not private Narratio - paths, credentials, or a duplicate complete configuration. -- `docs/internal/cli.md` - - document receipt construction before publication, emission after - publication and debug terminalization, and the stdout failure behavior; - - preserve the CLI composition-root boundary. -- `README.md` and `docs/development.md` - - add only the smallest links needed to make subprocess consumer guidance and - its implementation owner discoverable. +- artifact kind `source/evidence-context`; +- media type `application/json`; +- schema ID `notarius.source.evidence_context`; +- schema name `notarius_source_evidence_context_v1`; and +- schema version `v1`. -Do not otherwise change `docs/config.md`, `docs/operations.md`, or the JSON -output bundle contract unless implementation reveals an actual change to -behavior they own. The machine receipt adds no configuration field, filesystem -surface, retention policy, or lane-output shape. +Use these package-level model and build contracts: -After all code, tests, and current-behavior documentation are complete: +```go +type Document struct { + SourceID string + SourceDigest string + WindowUnits int + SelectedLanes []string + Contexts []Context +} -- change [the feature roadmap](subprocess.md) status to `Implemented`; -- change this plan's status to `Completed`; and -- ensure neither roadmap is used as the canonical description of current - behavior. +type Context struct { + ContextRef source.SourceRef + EvidenceRefs []EvidenceRef + Units []source.SourceUnit +} + +type EvidenceRef struct { + LaneID string + SourceRef source.SourceRef +} + +type LaneEvidence struct { + LaneID string + SourceRefs []source.SourceRef +} + +type BuildRequest struct { + Source *source.SourceDocument + WindowUnits int + SelectedLanes []string + LaneEvidence []LaneEvidence +} +``` + +Apply the JSON field names shown below. Provide `Build(BuildRequest)`, +`Serialize(BuildRequest)`, and a `Codec` with the same identity/encode/decode +responsibilities as the chunk-map codec. + +The v1 payload has this exact shape: + +```json +{ + "source_id": "session-alpha", + "source_digest": "sha256:0000000000000000000000000000000000000000000000000000000000000000", + "window_units": 0, + "selected_lanes": ["npcs"], + "contexts": [ + { + "context_ref": { + "source_id": "session-alpha", + "start_unit_id": 13, + "end_unit_id": 13 + }, + "evidence_refs": [ + { + "lane_id": "npcs", + "source_ref": { + "source_id": "session-alpha", + "start_unit_id": 13, + "end_unit_id": 13 + } + } + ], + "units": [ + { + "id": 13, + "kind": "transcript_segment", + "text": "The party meets Rowan.", + "ref": { + "source_id": "session-alpha", + "start_unit_id": 13, + "end_unit_id": 13 + } + } + ] + } + ] +} +``` + +All displayed fields are required. `selected_lanes`, `contexts`, +`evidence_refs`, and `units` encode as arrays rather than `null`; `contexts` +may be empty. Each unit uses the existing `source.SourceUnit` JSON shape with +required `id`, `kind`, `text`, and `ref`, plus optional JSON-shaped `metadata`. +Fixed objects reject unknown fields; metadata remains an arbitrary JSON object. + +The builder accepts the validated source document, selected lane IDs, effective +window, and lane-attributed direct references, then: + +1. requires a non-negative window and a non-empty, trimmed, unique selected + lane set, then stores that set in lexical order; +2. requires every `LaneEvidence.LaneID` to belong to the selected set; +3. validates the source document, recomputes its semantic digest, and requires + it to equal `SourceDocument.Digest`; +4. validates every direct reference against one `source.DocumentIndex`; +5. deduplicates exact `(lane_id, source_ref)` contributions; +6. resolves endpoints to document positions; +7. expands each side without integer overflow and clips at document bounds; +8. sorts by expanded document position with deterministic lane/reference + tie-breakers; +9. merges overlapping or position-contiguous expanded intervals; +10. unions and deterministically sorts each merged context's direct + contributions; and +11. deep-clones the corresponding source units and JSON-shaped metadata. + +Contexts are disjoint and ordered by document position, so a source unit occurs +at most once in the document. `context_ref` identifies the first and last +included units; `evidence_refs` retains only original citations. An empty +reference collection produces the same source identity, window, sorted +allowlist, and an explicit empty contexts array. + +Projection failures identify only structural scope such as lane and reference +position. They must not include source text, metadata values, raw serialized +artifacts, or unrelated paths. + +The codec must validate its model before encoding, produce deterministic JSON, +strictly decode the checked-in schema, and return independently owned values. +The JSON output encoder remains responsible for pretty-printing the logical +file with its standard trailing newline. Follow the existing chunk-map +package's separation between model, builder, codec, schema asset, and contract +tests where useful, without coupling the two artifact formats. + +Stage tests: + +- A table-driven builder suite covers zero and nonzero windows, boundary + clipping, non-monotonic unit IDs, separate gaps, overlapping and contiguous + windows, duplicate contributions, multiple lanes, stable ordering, empty + contexts, invalid selected/contributing lanes, source-digest mismatch, and + invalid references. +- Ownership tests prove output mutation cannot affect the source document or + projector inputs, including nested metadata. +- Codec tests cover round trip, required arrays, schema identity, malformed and + trailing JSON, unknown fixed fields, invalid ordering/ranges, mismatched + source identities, and independently owned decoded metadata. +- Use structured assertions and a compact valid fixture; do not add a large + transcript golden file. Stage completion: -- validate documentation links and command examples against the implementation; +- `go test ./internal/framework/evidencecontext` + +## Stage 3: Integrate Projection With Runner Output + +Extend `contracts.OutputRequest` with an optional +`EvidenceContext *SerializedArtifact` field and clone it at every ownership +handoff, following the existing chunk-map pointer pattern. + +After lane execution and final manifest population, but before invoking the +output encoder, the runner must: + +1. skip all work when the prepared evidence plan is absent; +2. index accepted `NormalizeOutputs` by their globally unique lane IDs and fail + on an internal duplicate rather than silently overwrite it; +3. for each selected lane with an output, verify its source and artifact kind, + decode it through the prepared artifact codec registry, and invoke the + prepared typed projector; +4. build and serialize the evidence document through the evidence-context + package; and +5. pass a defensive serialized-artifact copy to the output encoder. + +Selected lanes without normalized output contribute nothing. Normalize +rejections remain successful pipeline outcomes; evidence projection does not +inspect rejected candidates. An invalid accepted reference, incompatible +serialized artifact, projection type failure, or evidence serialization failure +is an output-stage framework error before logical files are returned or +physically published. + +At this external-content consumption boundary, do not propagate artifact-codec +or metadata-cloning errors with `%w` when their text could contain artifact +fields or source metadata. Return fixed, actionable categories scoped by lane +and operation; detailed codec errors remain available to direct trusted +callers and their focused tests. + +Add an allowlisted debug summary containing only evidence artifact identity, +selected lanes, window, context count, unit count, and source digest. Do not +duplicate transcript text or source-unit metadata into a new evidence-specific +debug envelope. Existing normalized-output debug behavior remains unchanged. + +The output artifact is not a normalized lane, generated reference, checkpoint, +or manifest normalized-output entry. It does not alter normalized-output, +rejection, or warning counts. Resume continues to reuse normalized checkpoints; +evidence is deterministically rebuilt during the always-executed output stage. + +Stage tests: + +- Runner tests use a real codec, evidence projector, and small capturing output + encoder to prove selected-lane union, absent/rejected lane omission, invalid + accepted-reference failure, output-request defensive ownership, and no work + when disabled. +- Include one checkpoint-reused normalized-output case to prove evidence is + reconstructed identically without retaining typed normalize values. +- Confirm projection failures prevent output encoding and return a failed + manifest without changing rejection semantics. + +Stage completion: + +- `go test ./internal/framework/pipeline` + +## Stage 4: Publish Through The JSON Bundle + +Teach the production JSON encoder to recognize the optional evidence-context +artifact, verify its exact kind, media type, schema identity, schema digest, and +payload validity through the evidence-context codec, and emit: + +- logical file `evidence-context.json`; and +- optional `index.json` descriptor field `evidence_context`. + +The descriptor uses the same six fields as `chunk_map`: +`artifact_kind`, `file`, `media_type`, `schema_id`, `schema_name`, and +`schema_version`. Refactor the encoder's private descriptor representation only +as needed to share that shape; do not change the existing `chunk_map` wire +contract. Evidence output is ordered with the encoder's other fixed logical +files, remains a non-lane artifact, and is present with an empty contexts array +when enabled but no selected lane produces references. + +The JSON encoder's validation boundary returns a fixed content-safe evidence +artifact error rather than propagating decoder or schema diagnostics that could +echo transcript text or metadata. Direct evidence-context codec tests retain +detailed structural errors. + +Update the maintained complete D&D configuration to enable evidence context +with window `3` for `item-events`, `npcs`, `spells`, `combat-turns`, and +`npc-interactions`. Deliberately omit `scene-descriptions`. Keep the minimal +configuration disabled by omission. + +Stage tests: + +- JSON encoder tests own descriptor shape, exact logical filename, identity + checking, empty evidence publication, and disabled bundle stability. +- One assembled production D&D test uses multiple selected lanes with + overlapping references and non-monotonic unit IDs, decodes the published + artifact through its production codec, and proves union/deduplication and + scene-description exclusion. +- A second narrow case explicitly allowlists a scene-description lane to prove + capability is opt-in rather than hard-coded exclusion. +- Existing index, chunk-map, lane, manifest, warning, and rejection tests remain + the owners of their current formats; do not repeat their full matrices. + +Stage completion: + +- `go test ./internal/modules/generic/output/json` +- `go test ./internal/modules/dnd/...` +- `go test ./internal/modules/integration` +- `go test ./internal/cli` + +## Stage 5: Publish Current-Behavior Documentation + +After implementation and behavioral tests pass, update canonical documentation: + +- `docs/config.md` owns the nested JSON output options, defaults, strict + validation, required lane allowlist, and a small configuration snippet. +- A new `docs/integrations/evidence-context.md` owns the complete v1 payload, + identities, direct-evidence versus context semantics, ordering, + compatibility, and a compact valid example. +- `docs/integrations/json-output.md` owns the optional logical file and + `index.json` descriptor; link to the evidence contract rather than repeating + its payload. +- `docs/operations.md` owns durable source-content sensitivity, permissions, + retention, and the possibility that selected lanes cover most of a + transcript. +- `docs/consumers/subprocess.md` explains discovery through the optional index + descriptor and requires consumers to treat `evidence_refs`, not expanded + context bounds, as citations. +- `docs/policy/architecture.md` records the generic typed evidence-projection + boundary and output ownership without adding D&D or wire-format detail. +- `docs/internal/pipeline.md` and `docs/internal/modules.md` describe the typed + evidence registry, preparation checks, reconstruction from serialized + normalize outputs, and output-stage ownership without restating public wire + fields. + +Update only the smallest orientation links needed for discoverability. Do not +add a CLI flag, configuration environment override, or duplicate the complete +configuration outside `examples/`. + +After all current-behavior documentation is accurate: + +- set [the feature roadmap](evidence.md) status to `Implemented`; +- set this plan's status to `Completed`; and +- leave the integration and configuration documents, not either roadmap, as + the canonical implemented contract. + +Final verification: + - `git diff --check` - `go test ./...` - `go vet ./...` - `go build ./cmd/notarius` -- `go test -race ./internal/cli` +- `go test -race ./internal/framework/evidencecontext ./internal/framework/pipeline ./internal/modules/generic/output/json ./internal/modules/dnd/... ./internal/modules/integration ./internal/cli` ## Open Questions -None. The decisions above resolve stdout partial-write behavior, output-module -generality, path normalization, partial pipeline success, terminal reporting, -documentation ownership, and test boundaries. +None. The plan fixes the configuration shape and defaults, typed projection +boundary, preparation timing, durable schema and identities, range-union +algorithm, failure semantics, JSON discovery, D&D coverage, documentation +ownership, and test boundaries.