Add a feature roadmap and implementation plan for a context evidence artifact

This commit is contained in:
2026-07-27 12:09:14 -05:00
parent fb1134e591
commit 64d461fc18
2 changed files with 670 additions and 242 deletions

226
docs/roadmap/evidence.md Normal file
View File

@@ -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.

View File

@@ -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.