# ADR-0005 Implementation Record This document records the implementation of the target state in [ADR-0005 Feature Roadmap](adr0005.md), governed by [ADR-0005](../adr/0005-cache-canonical-chunk-plans-by-source.md). All stages are complete. This document remains a concise historical implementation record. Current behavior is documented in the canonical references linked from [Development](../development.md). ## Execution Rules Before every stage: 1. read [Development](../development.md), both documents under `docs/policy/`, ADR-0005, this plan, and every stage-specific document named below; 2. inspect `git status` and preserve user changes; 3. inspect the focused package contracts and tests before editing; and 4. confirm that all earlier stages are complete. During every stage: - implement only that stage and prerequisites discovered to be inseparable; - use existing package boundaries and helpers before introducing abstractions; - keep framework and core packages independent of concrete D&D modules; - preserve cancellation, deterministic ordering, redaction, path confinement, and atomic-write invariants; - add or update focused tests with every behavior change; - do not update current-behavior documentation before the final documentation stage, except for ADR acceptance in Stage 1; and - do not begin the next stage after meeting the current exit criteria. At the end of every stage: 1. run the focused tests listed for that stage; 2. run `go test ./...`; 3. run `go vet ./...`; 4. run `go build ./cmd/notarius`; 5. run `git diff --check`; and 6. mark the stage complete in this document only after all checks pass. If repository reality conflicts with this plan, stop and update the plan in the same change before implementing a materially different design. Do not silently invent a new contract. ## Fixed Decisions These choices are settled for this implementation: - `source.SourceDocument.Digest` is the cache lookup identity. - The chunk-plan root is independent of `workspace.directory` and every other state root. - Its default joins the directory returned by `os.UserCacheDir` with `notarius/chunk-plans`. - `workspace.chunk_cache.directory` overrides that default and names the chunk-plan root itself. The recommended value for a system-wide Linux service is `/var/cache/notarius/chunk-plans`. - The plan file is `/<64-character-lowercase-source-sha256-hex>/plan.json`. The validated `sha256:` prefix remains part of the logical digest but is removed from the path segment. - The plan envelope schema is `notarius.chunk-plan.v1`. - There is one mutable plan file per source, atomically replaced. There are no immutable plan objects, pointers, history, locks, or rollback. - Concurrent writers use last-successful-atomic-write semantics; they must never expose a partial file. - Chunk modules retain the `contracts.Chunker` name but replace `Chunk` with a `Plan` operation. No legacy interface remains after Stage 2. - Plans and ranges use `map[string]json.RawMessage` annotations. Annotation values are canonical valid JSON and namespaces are non-empty trimmed strings. - Materialized chunks expose range annotations through `source.Chunk.Annotations` and plan annotations through `source.Chunk.PlanAnnotations`, never through `Metadata`. - Framework materialization owns chunk IDs, indexes, references, content, media type, units, and generic metadata. - Only the generic `chunks` capability is hard; `chunks.scenes` is removed. - Cached-plan hits still construct the configured chunker during preparation but never call its operation or the chunk-stage LLM. - The current pipeline's configured chunk validators run against materialized chunks on every path. - Canonical plan storage is the only chunk-reuse mechanism; chunk checkpoint APIs and payloads are removed. - The mode values are `auto`, `bypass`, and `refresh`. They are valid in config, environment, and CLI, including persistent `refresh` configuration. - The YAML field is `workspace.chunk_cache.mode`. - The YAML directory field is `workspace.chunk_cache.directory`. - The environment variable is `NOTARIUS_WORKSPACE_CHUNK_CACHE_MODE`. - The root environment variable is `NOTARIUS_WORKSPACE_CHUNK_CACHE_DIR`. - The CLI flag is `--chunk_cache `. - Precedence is CLI, environment, file, then default `auto`. - Chunk-plan-root precedence is environment, file, then the per-user default; there is no CLI root override. - Every supplied file, environment, and CLI value is parsed strictly even when a higher-precedence source would override it; precedence selects among valid values and does not mask malformed configuration. - Existing chunk checkpoint files are ignored and never migrated. - A structurally invalid stored plan is an `auto` miss. A configured-validator rejection of a structurally valid hit does not trigger regeneration. ## Planned Contracts Use these names unless an existing collision requires the smallest obvious adjustment. ### Source-zone types Add to `internal/core/source`: ```go type ChunkAnnotations map[string]json.RawMessage type ChunkPlan struct { SourceDigest string `json:"source_digest"` Ranges []ChunkRange `json:"ranges"` Annotations ChunkAnnotations `json:"annotations,omitempty"` } type ChunkRange struct { StartUnitID int `json:"start_unit_id"` EndUnitID int `json:"end_unit_id"` Annotations ChunkAnnotations `json:"annotations,omitempty"` } ``` Add `Annotations ChunkAnnotations` and `PlanAnnotations ChunkAnnotations` to `source.Chunk`, with JSON names `annotations` and `plan_annotations` and `omitempty` on both. The plan digest covers `SourceDigest`, ordered ranges, and canonical annotation bytes. It excludes producer provenance, warnings, timestamps, and storage schema. Materialization produces: - IDs `chunk-%06d`, numbered from one; - zero-based indexes in plan order; - references spanning the first and last units in each range; - cloned contiguous source units; - media type `application/json`; - canonical JSON content shaped as `{"units":[...]}`; - generic metadata containing `start_unit_id`, `end_unit_id`, and `unit_count`; - cloned range annotations in `Chunk.Annotations`; and - cloned plan annotations in `Chunk.PlanAnnotations`. The plan and range scopes remain distinct even when both use the same namespace. Copying plan annotations into each runtime chunk is intentional: it makes them available to downstream chunk-scoped operations without duplicating source content in durable storage. Framework validation permits gaps and overlap between ranges, but requires strictly increasing start positions and rejects duplicate or backward ranges. Producing modules may impose stricter policy before returning a plan. ### Module contract Replace the current chunk result and operation with: ```go type ChunkPlanResult struct { Plan source.ChunkPlan Warnings []Warning } type Chunker interface { Key() string ReferenceSlots() []ReferenceSlot Plan(context.Context, ChunkRequest) (ChunkPlanResult, error) } ``` Keep `ChunkRequest`, chunk module keys, stage name `chunk`, configuration bindings, registries, and preparation ownership unchanged. ### Durable plan record Define framework-facing record and store contracts beside other pipeline collaborator contracts: ```go type ChunkPlanProducer struct { InputModule string ChunkModule string LLMProfile string References []artifacts.ReferenceProvenance Metadata map[string]any } type ChunkPlanRecord struct { SchemaVersion string SourceDigest string PlanDigest string Plan source.ChunkPlan Producer ChunkPlanProducer Warnings []contracts.Warning CreatedAt time.Time } type ChunkPlanStore interface { Load(sourceDigest string) (ChunkPlanRecord, ChunkPlanDecision, error) Save(ChunkPlanRecord) error } type ChunkPlanStoreFactory func(root string) (ChunkPlanStore, error) type ChunkPlanDecision struct { Status ChunkPlanStatus Reason string } type ChunkPlanStatus string const ( ChunkPlanHit ChunkPlanStatus = "hit" ChunkPlanMissing ChunkPlanStatus = "missing" ChunkPlanInvalid ChunkPlanStatus = "invalid" ) ``` `ChunkPlanDecision` reports `hit`, `missing`, or `invalid` plus a redacted reason. A miss or invalid result returns a zero record and nil error. Missing and structurally invalid records are recoverable in `auto`. Filesystem access failures other than absence return an error and are framework failures. The concrete constructor is: ```go // Package chunkplan func NewFilesystemStore(root string) (pipeline.ChunkPlanStore, error) ``` It treats `root` as the exact chunk-plan root. CLI composition injects this constructor through `cli.Options.ChunkPlanStoreFactory`; tests may inject a recording factory. `cli.Options.UserCacheDir` has signature `func() (string, error)` and defaults to `os.UserCacheDir`. The JSON envelope uses snake-case equivalents of the record fields. It stores references as provenance only, never reference content. Before returning a hit, the loader validates schema version, source digest, plan digest, plan structure, annotation JSON, and materializability against the current source in the runner. ### Cache policy and provenance Add a `ChunkCacheMode` enum with `auto`, `bypass`, and `refresh`, plus strict parsing and validation. Add to `RunInput`: ```go ChunkCacheMode ChunkCacheMode ChunkPlans ChunkPlanStore ``` An empty mode used by direct framework callers means `bypass`; the production CLI always supplies the effective non-empty mode. Add a run-manifest summary: ```go type ChunkPlanManifest struct { Mode string `json:"mode"` Action string `json:"action,omitempty"` SourceDigest string `json:"source_digest,omitempty"` PlanDigest string `json:"plan_digest,omitempty"` PlanSchemaVersion string `json:"plan_schema_version,omitempty"` RequestedModule string `json:"requested_module"` ProducerInputModule string `json:"producer_input_module,omitempty"` ProducerModule string `json:"producer_module,omitempty"` ProducerLLMProfile string `json:"producer_llm_profile,omitempty"` ProducerReferences []ReferenceProvenance `json:"producer_references,omitempty"` ProducerMetadata map[string]any `json:"producer_metadata,omitempty"` CreatedAt *time.Time `json:"created_at,omitempty"` } ``` `Action` is one of `reused`, `generated`, `refreshed`, or `bypassed`. Retain the existing top-level `input_module` and `chunker` fields as the requested pipeline modules. Add this field to `artifacts.RunManifest`: ```go ChunkPlan *ChunkPlanManifest `json:"chunk_plan,omitempty"` ``` The runner initializes it with the effective mode and requested chunk module, then fills candidate and producer fields when a plan reaches materialization. ## Completed Stage Summaries ### Stage 1: Finalize ADR and add the source-zone plan model **Status:** Complete Accepted ADR-0005 and added the domain-neutral chunk-plan, range, annotation, validation, digest, cloning, and deterministic materialization primitives in `internal/core/source`. ### Stage 2: Replace the chunk operation with plan generation **Status:** Complete Replaced chunk-returning module operations with `Chunker.Plan`, converted the generic and D&D scene chunkers to produce ranges and optional annotations, and made the runner materialize and validate generated plans. ### Stage 3: Add dedicated cache configuration and the plan store **Status:** Complete Added strict cache modes, independent per-user or explicitly configured chunk-plan roots, the source-addressed filesystem store, restrictive permissions, envelope validation, and atomic replacement. ### Stage 4: Integrate cache policy and remove chunk checkpoints **Status:** Complete Implemented runner semantics for `auto`, `bypass`, and `refresh`; retained current chunk-validator behavior; made accepted canonical plans the sole chunk reuse mechanism; and removed chunk checkpoint loading and recording. ### Stage 5: Wire persistent policy into the CLI **Status:** Complete Added CLI, environment, and file configuration precedence; per-user root resolution; persistent store construction; bypass isolation; and cross-run reuse through normal CLI execution. ### Stage 6: Expose producer provenance and safe diagnostics **Status:** Complete Added requested-versus-producer chunk-plan provenance to manifests and a payload-free chunk-plan decision summary to normal diagnostics, with complete plan material confined to opt-in debug artifacts. ### Stage 7: Add cross-run, corruption, and concurrency hardening **Status:** Complete Added integration, corruption, interruption, concurrency, compatibility, cross-domain annotation, import-boundary, and race coverage for the implemented cache design. ### Stage 8: Publish current-behavior documentation **Status:** Complete Updated architecture, pipeline, module, configuration, CLI, operations, diagnostics, integration, and overview documentation for the implemented feature and its per-user and system-wide deployment guidance. ## Stage 9: Redact invalid cache-record diagnostics **Status:** Complete ### Objective Ensure malformed or incompatible cache contents cannot enter normal errors, warnings, manifests, or diagnostics while retaining useful closed lookup decisions. ### Read first - [Architecture Policy](../policy/architecture.md), especially secret handling - [Diagnostics Internals](../internal/diagnostics.md) - `internal/framework/chunkplan/store.go` and its focused tests - `internal/framework/pipeline/runner_chunk_plan.go` and its focused tests - `internal/core/artifacts/artifacts.go` - the CLI path that writes `chunk-plan.json` ### Implement 1. Stop interpolating decoder errors, unknown field names, schema values, annotation namespaces, timestamps, paths, or any other stored content into recoverable `ChunkPlanDecision.Reason` values. 2. At the normal-diagnostic boundary, ignore the reason supplied by a `ChunkPlanStore` and derive `ChunkPlanSummary.LookupReason` solely from status using exactly: - `hit`: `stored chunk plan is valid`; - `missing`: `chunk plan not found`; - `invalid`: `stored chunk plan is invalid`; and - `skipped`: `chunk plan lookup skipped`. 3. Keep operational read failures as framework errors rather than recoverable invalid-record decisions. Their normal error text must identify the failed operation without including cache-file content. 4. Do not add raw invalid-record details to another default surface. Direct operator inspection or a future explicitly sensitive debug facility may expose them, but this stage does not need to add such a facility. ### Tests Add focused tests proving that sentinel secrets placed in unknown field names, schema values, annotation namespaces, timestamps, and malformed trailing JSON appear in neither the store decision nor `chunk-plan.json`. Add a runner test with a custom store whose reason contains a sentinel and prove that the summary uses the fixed status-derived reason. Preserve useful missing, hit, invalid, and operational-error classification. Run focused tests for `internal/framework/chunkplan`, `internal/framework/pipeline`, and `internal/cli`, then perform the repository-wide validation in the execution rules. ### Exit criteria Every normal lookup reason belongs to the fixed safe vocabulary, hostile invalid-file content cannot reach default diagnostics, operational failures remain correctly classified, and all focused and repository-wide checks pass. ## Stage 10: Enforce rooted chunk-plan filesystem access **Status:** Complete ### Objective Make the cache's path-confinement guarantee resistant to symlinked cache entries while preserving atomicity, permissions, and concurrent access semantics. ### Read first - [Architecture Policy](../policy/architecture.md), especially state and path invariants - [Operations](../operations.md) - `internal/framework/chunkplan/store.go` and all store tests - Go's `os.Root` API for the repository's declared Go version ### Implement 1. Treat the configured cache root as the filesystem trust boundary. A symlink in the operator-supplied root path itself may be followed when the root is opened, but every store-owned operation after that point must remain beneath the opened root. 2. Perform digest-directory, plan-file, temporary-file, and rename operations through `os.Root` or an equivalently race-resistant directory-relative mechanism. Do not rely on `filepath.Join` plus a preflight `Lstat` as the confinement boundary. 3. Reject symlinks and unexpected file types at the store-owned digest-directory and `plan.json` positions. A missing cache root on `Load` remains a normal miss and is not created; `Save` creates the root and digest directory as needed. 4. Preserve the exact `//plan.json` layout, `0700` directories, `0600` plan files, strict digest validation, and no implicit path suffix. 5. Publish using an exclusively created random temporary file inside the digest directory, sync and close it, then atomically rename it through the same rooted handle. Remove temporary files on every pre-rename failure. Preserve last-successful-write and complete-reader semantics. 6. Keep store access serial at the runner boundary and safe for concurrent independent store callers. Do not introduce a lock, history, or rollback protocol. ### Tests Add load and save cases for digest-directory and plan-file symlinks that resolve outside the configured root. Prove that they are rejected and that no outside file is read, created, chmodded, or replaced. Include unexpected file types and failed-publication preservation. Retain and run tests for absent-root behavior, exact paths, permissions, round trip, malformed digests, atomic replacement, interrupted writes, temporary-file cleanup, concurrent readers, and concurrent writers. Run the race detector for `internal/framework/chunkplan`, then perform the repository-wide validation in the execution rules. ### Exit criteria No store-owned operation can escape the opened root through a cache entry, normal layout and permissions remain unchanged, publication remains atomic, concurrency tests and the race detector pass, and the repository is green. ## Stage 11: Deep-clone source metadata during materialization **Status:** Complete ### Objective Ensure source documents, materialized chunks, and separate materializations never share mutable metadata storage, regardless of the concrete JSON-shaped map, slice, array, interface, raw-message, or byte-slice types used by an input adapter. ### Read first - `internal/core/source/source.go` - `internal/core/source/chunk_plan.go` and its focused tests - `internal/core/source/digest.go` and validation behavior - every metadata clone helper and ownership boundary found with `rg` - focused runner tests that hand chunks to validators and extractors ### Implement 1. Replace the concrete-type allowlist used by plan materialization with one reusable source-owned deep-clone implementation for supported JSON-serializable metadata. 2. Recursively clone string-keyed maps, slices, arrays, interfaces, `json.RawMessage`, and byte slices without sharing mutable backing storage. Preserve scalar values and the logical JSON representation. Typed composite containers must not fall through as shared values. 3. Detect cyclic or unsupported values and return a contextual error from materialization rather than retaining an alias, panicking, or recursing indefinitely. 4. Use the source-owned helper at pipeline ownership boundaries where doing so removes a duplicate incomplete implementation. Do not introduce a dependency from `internal/core/source` to a framework package and do not expand this stage into unrelated metadata-schema redesign. 5. Preserve deterministic materialized content, chunk digests, annotations, IDs, references, and metadata fields for all currently supported production inputs. ### Tests Cover typed nested maps, slices of typed maps, arrays, interfaces, `json.RawMessage`, and byte slices. Mutate each materialized value and prove that neither the source document nor a separately materialized chunk changes. Add cyclic and unsupported-value cases that fail deterministically with contextual errors. Retain the existing exact-materialization and digest tests. Run focused tests for `internal/core/source`, `internal/framework/pipeline`, and affected module packages, then perform the repository-wide validation in the execution rules. ### Exit criteria Every supported mutable metadata value has independent ownership across source, chunk, validation, and extraction boundaries; invalid metadata fails safely; current materialized bytes remain stable; and all checks pass. ## Stage 12: Finalize remediation documentation and roadmap status **Status:** Complete ### Objective Make the roadmap and canonical documentation accurately describe the completed feature after Stages 9 through 11 land. ### Read first - [Documentation Policy](../policy/documentation.md) - [ADR-0005 Feature Roadmap](adr0005.md) - [Architecture Policy](../policy/architecture.md) - [Diagnostics Internals](../internal/diagnostics.md) - [Operations](../operations.md) - the final code and tests from Stages 9 through 11 ### Implement 1. Remove the feature roadmap's statement that ADR-0005 is unimplemented and describe it as the implemented target-state record. Mark its completion outcomes as achieved without adding staged implementation detail. 2. Recheck architecture, diagnostics, operations, pipeline, and module documentation against the remediated behavior. Update a canonical document only where Stages 9 through 11 changed a current operator or developer contract; do not duplicate implementation detail across documents. 3. Mark Stages 9 through 12 complete only after their respective code, documentation, focused tests, and repository-wide validation have passed. Update this document's introduction to state that all stages are complete and that it remains as a concise historical implementation record. 4. Validate every changed link, heading, symbol, diagnostic field, permission, and filesystem claim against the implementation. ### Tests and validation Run: ```sh go test ./... go vet ./... go build ./cmd/notarius git diff --check ``` Run any repository documentation or link checker if present, and manually inspect the reading map in [Development](../development.md) and component links in [Internal Overview](../internal/overview.md). ### Exit criteria The feature roadmap and canonical references describe implemented behavior, this plan accurately records completion, documentation ownership remains clear, all links and names are current, and the repository-wide validation is green.