Files
notarius/docs/roadmap/implementation.md

20 KiB

Implementation Plan: Ordered Pipeline Follow-Up

Status

Completed on 2026-07-22. This document retains the implementation sequence for historical context; current contracts are maintained in the canonical CLI, configuration, operations, integration, and internal documentation linked from the development guide.

Purpose

Record the work that closed the correctness, observability, test, and maintainability gaps in the implemented Ordered Pipeline Steps feature. That feature roadmap remains the authority for product intent, policy choices, acceptance criteria, and exclusions. This document preserves the ordered, decision-complete implementation sequence for the follow-up work.

Background

The original implementation is complete in broad architecture. Pipelines now resolve and prepare one canonical ordered-step model, execute hard barriers between steps, hand accepted normalized artifacts to later operations as canonical generated references, include generated identity in checkpoint and output provenance, support --recompute-step, and use generated NPC output at operation time in the D&D spell and combat consumers. Current documentation and maintained examples describe that model.

The completed follow-up addressed these narrower gaps:

  • selective recomputation now hydrates an unselected predecessor from its accepted normalized artifact without requiring its extract and merge state;
  • the failed checkpoint decision is recorded before a required-predecessor error returns;
  • checkpoint reason codes are assigned explicitly rather than inferred from human-readable prose;
  • runner lane and checkpoint orchestration has explicit responsibility seams; and
  • CLI and resumed-producer acceptance coverage exercises the complete recovery contract.

No new product feature is introduced by this plan. Preserve configuration file version 3 and checkpoint workspace schema notarius.workspace.v3; the fixes do not require a new persistent format.

Instructions For Every Stage

Before changing code, read docs/development.md, all documents under docs/policy/, and the task-specific internal documents named there. Use the repository's code knowledge graph for code discovery before falling back to text search.

Implement the stages in order. Each stage must leave the repository formatted, building, and passing its focused tests. Preserve these invariants throughout:

  • The pipeline retains one input, chunk plan, output encoder, run identity, checkpoint identity, failure boundary, worker budget, and provider scheduler.
  • Every selected module and validator is constructed before source parsing.
  • Steps schedule fixed extract, merge, and normalize lanes; this work must not introduce module-to-module calls or a general DAG scheduler.
  • Generated artifacts remain cloned operation-time context, never source evidence. Never emit their content, source material, credentials, or local paths in decisions, manifests, logs, or summaries.
  • Public ordering remains step order, lane ID, and source/chunk order as applicable. Refactoring must not expose completion order.
  • Tests must follow docs/policy/testing.md: protect observable recovery, orchestration, and CLI contracts without asserting private helper calls, exact prose, goroutine choreography, or full-document snapshots.
  • Update current-behavior documentation in the same stage that changes the corresponding behavior. Keep detailed implementation sequencing only here.

Target Checkpoint Semantics

Use these definitions consistently in all stages:

  • A stage checkpoint is internal resumable state for extract, merge, or normalize. Ordinary resume may continue to reuse this state progressively.
  • An accepted lane artifact is the one successful normalized artifact for a (step ID, lane ID, normalizer module) under the current checkpoint identity. It is the dependency exposed to a later step.
  • An unselected required predecessor satisfies selective recomputation when its accepted lane artifact can be validated and hydrated. Its extract and merge stage checkpoints are not prerequisites for that handoff.
  • A selected lane and its transitive dependents execute. They must never use accepted-artifact hydration to bypass forced execution.
  • If a required predecessor's accepted lane artifact is missing, rejected, corrupt, non-canonical, or incompatible, fail the run before any dependent lane starts. Do not implicitly rerun that predecessor.
  • Ordinary resume without --recompute-step keeps its existing progressive stage-reuse and cold-miss behavior.

Stage 1: Decompose Runner And Checkpoint Orchestration

Objective

Create explicit, testable ownership seams for ordered-step coordination, per-lane execution, and per-stage checkpoint handling without changing observable behavior.

Changes

  1. Keep Runner.Run as the pipeline-wide coordinator. Extract a small step-coordination helper responsible only for iterating prepared steps, building generated reference sets at each barrier, invoking the lane engine, and merging deterministic outcomes.
  2. Split runLanes in internal/framework/pipeline/runner_concurrent.go into helpers with these responsibilities:
    • initialize lane state and resolve extract checkpoint state in lane order;
    • run the bounded extract worker/continuation engine;
    • collect terminal lane results and choose failures deterministically; and
    • merge lane-local output into step output. Preserve the existing bounded channels, cancellation, drain behavior, chunk-first dispatch, lane ordering, and step barrier.
  3. Split continueTypedLane in internal/framework/pipeline/runner_typed.go into stage-specific merge and normalize helpers. Each helper should own dependency construction, checkpoint loading and canonical validation, execution/retry/validation when needed, checkpoint recording, debug envelopes, and its typed result. Use small result structs rather than long parallel return lists.
  4. Centralize repeated checkpoint-decision flow in one pipeline helper that can apply forced-execution policy, validate canonical stored artifacts, record the final observable decision, and return a contextual error. Do not yet change categories, reason codes, or required-predecessor semantics; Stages 2 and 3 will change those deliberately.
  5. Keep stage-specific code where the data shapes genuinely differ. Do not introduce reflection, a generic stage state machine, or callbacks that hide the fixed extract/merge/normalize lifecycle.

Tests

  • Existing runner, checkpoint, barrier, ordering, cancellation, retry, debug, and D&D integration tests must pass without expectation changes except moves required by renamed private test fixtures.
  • Add no tests for helper boundaries or collaborator call counts. Add a narrow regression assertion only if the refactor exposes an observable behavior not already protected.

Completion Gate

Run go test ./internal/framework/pipeline ./internal/framework/checkpoint ./internal/modules/integration and the pipeline race tests. Review the diff to confirm this stage changes structure only: serialized output, checkpoint state, decision values, failure selection, and module invocation behavior must remain unchanged.

Stage 2: Make Checkpoint Decisions Typed And Observable

Objective

Assign stable decision categories and reason codes explicitly, and retain the decision that causes a required-dependency failure.

Changes

  1. In internal/framework/pipeline/checkpoint.go, introduce string-backed internal types and constants for checkpoint decision categories and reason codes. Keep the current JSON strings and public artifact fields compatible. Categories remain exactly executed, reused, forced_recompute, and dependency_invalidated.
  2. Replace prose inspection in internal/framework/checkpoint/loader.go with an explicit decision constructor accepting category, reason code, and optional detail. Remove every strings.Contains classification branch. Assign a code at the validation site using this bounded vocabulary:
    • loading_disabled, checkpoint_missing, checkpoint_path_invalid, checkpoint_read_failed, and checkpoint_decode_failed;
    • workspace_schema_incompatible and identity_mismatch;
    • stage_mismatch, step_mismatch, lane_mismatch, module_mismatch, and status_not_reusable;
    • dependency_mismatch for the dependency_invalidated category;
    • artifact_payload_invalid, artifact_digest_mismatch, artifact_codec_incompatible, and artifact_not_canonical; and
    • checkpoint_reused and accepted_artifact_reused for successful reuse, and recompute_step for forced execution. Retain an existing code not listed here only when a current external or documented contract already depends on it.
  3. Keep detail human-readable, UTF-8, bounded, and sanitized through one helper. Detail may identify stage, step, lane, module, expected status, or schema version, but must not include artifact/reference content, source content, credentials, environment values, or local paths. Tests must assert codes and safety properties, not exact detail prose.
  4. Change the centralized runner decision flow from Stage 1 so the final loader or canonical-validation decision is recorded before returning a required-predecessor error. The contextual error must identify the step and lane and include the stable reason code; it must not interpolate unsafe loader detail.
  5. Propagate typed values without lossy conversion through checkpoint events, run manifests, debug summaries, and CLI diagnostics. Convert to strings only at existing serialized boundaries unless changing an internal field type is simpler and wire-compatible.
  6. Update the canonical current-behavior owners in the same change: docs/internal/state.md owns the internal decision flow, while docs/operations.md owns operator diagnosis and any operator-visible reason code table. Link rather than duplicate the table elsewhere.

Tests

  • Add a table in internal/framework/checkpoint that exercises one representative input per reason-code family and asserts category, code, bounded valid UTF-8 detail, and absence of supplied secret/path sentinels.
  • Add pipeline behavior tests proving a required-predecessor failure records the underlying missing, corrupt, incompatible, or dependency-invalidated decision before returning.
  • Retain focused manifest/debug serialization assertions for category and code; do not add exact-detail or full-manifest snapshots.

Completion Gate

Run go test ./internal/framework/checkpoint ./internal/framework/pipeline ./internal/core/artifacts ./internal/core/debugbundle ./internal/cli. Search the production checkpoint package to confirm no decision category or reason code is derived from diagnostic prose.

Stage 3: Hydrate Required Predecessors From Accepted Normalized Artifacts

Objective

Make selective recomputation enforce the lane-level accepted-artifact contract: a valid normalized producer artifact is sufficient even when its extract or merge stage cache is unavailable.

Changes

  1. Extend the checkpoint loader contract with a dedicated accepted-normalized- artifact lookup. Implement it in the filesystem loader and every no-op or test implementation. The lookup receives step ID, lane ID, and normalizer module key and returns the normalized artifact, its warnings, and an explicit checkpoint decision. It must not require caller-supplied extract or merge dependency fingerprints.
  2. The filesystem lookup may reuse the existing normalize manifest and payload; do not add a second persistent copy. It is reusable only when all of the following hold:
    • workspace schema is v3 and the non-empty stored checkpoint identity matches the current invocation identity;
    • stage, step, lane, normalizer module, and successful status match;
    • the manifest output digest matches the payload; and
    • the payload can subsequently be validated through the registered artifact codec. Skipping caller-supplied merge dependencies is safe only because the matched non-empty checkpoint identity already binds the current input, resolved topology and configuration, external references, runtime overrides, LLM profiles, and component semantic fingerprints. Do not weaken or omit that identity check. A loader lacking a verifiable current identity must return an unavailable decision rather than perform accepted-artifact reuse.
  3. Add a pipeline hydration helper that decodes the returned serialized artifact through the producer's registered codec, re-encodes it canonically, and requires exact artifact kind, schema ID/name/version/digest, media type, content bytes, and content digest. Return a runner-owned clone with producer step, lane, module, and source identity. Any mismatch is an explicit bounded decision and the bytes never reach a consumer.
  4. Before normal execution of a lane marked in RequireReusableLanes, use the accepted-artifact lookup:
    • on success, mark the lane terminal without invoking extract, merge, normalize, or their validators;
    • append the accepted normalized output in the normal deterministic location and restore only the warnings stored with that normalized checkpoint;
    • record one reused normalize decision with a stable accepted-output reuse reason code; do not synthesize extract/merge decisions, warnings, or rejections that were not loaded; and
    • allow the ordinary step barrier and generated handoff code to consume that output exactly as it consumes a freshly executed output.
  5. On missing, rejected, corrupt, non-canonical, or incompatible accepted state, record the decision and fail the run before the dependent step begins. Do not fall back to stage execution. Forced lanes must bypass this hydration path and execute normally.
  6. Leave ordinary resume unchanged when no lane is marked RequireReusableLanes: it may reuse or recompute extract, merge, and normalize progressively under the existing cold-miss rules.
  7. Keep generated-reference fingerprints and provenance unchanged. Hydrating a byte-identical producer must yield the same canonical handoff digest and downstream dependency fingerprint as fresh execution.
  8. Update docs/internal/pipeline.md, docs/internal/state.md, and docs/operations.md in this stage to distinguish progressive stage reuse from accepted normalized-artifact hydration and to document the fail-rather- than-rerun rule for invalid required predecessors.

Tests

  • At the pipeline/checkpoint boundary, create a valid producer normalize checkpoint while omitting or corrupting its extract and merge checkpoints. Select a later step for recomputation and prove the producer hydrates, no producer operation or validator runs, and the dependent receives the exact canonical artifact.
  • Cover missing, rejected-status, corrupt, non-canonical, wrong-codec-identity, and wrong-content-digest normalize state. Assert failure and the recorded stable decision before any consumer invocation.
  • Prove forced producers execute instead of hydrating, while a reusable predecessor and an unrelated lane remain reusable.
  • Prove fresh and hydrated producer outputs create identical generated provenance and downstream checkpoint fingerprints without exposing content or paths.

Completion Gate

Run go test ./internal/framework/checkpoint ./internal/framework/pipeline ./internal/modules/integration and the corresponding race tests. Manually inspect one failed test fixture to confirm the accepted artifact remains on disk but its bytes do not appear in the manifest, decision detail, debug summary, or error.

Stage 4: Complete Recompute CLI And Recovery Acceptance Coverage

Objective

Exercise the operator-visible recomputation contract through stable boundaries and close the acceptance-test omissions from the original plan.

Changes

  1. Add one table-driven CLI contract test for --recompute-step covering:
    • a valid explicit step and the implicit default step;
    • repeated flags and an empty or unknown step ID;
    • use without --resume;
    • use when checkpoint recording is disabled; and
    • combination with --only. Assert exit classification and stable identifying fragments or reason codes, not complete prose.
  2. Add one filesystem-backed CLI execution test using deterministic fake modules and a three-step generated dependency chain plus one unrelated lane:
    • perform a fresh checkpointed run;
    • remove or corrupt only the required producer's extract and merge state inside t.TempDir(), leaving its normalize artifact valid;
    • resume with the middle step selected;
    • assert the selected lane and transitive dependents execute, the predecessor hydrates without module calls, the unrelated lane reuses, and output and decision ordering are deterministic; and
    • invalidate the producer normalize state in a subcase and assert the command fails before dependent execution with the bounded decision preserved.
  3. Add or extend one generic two-step pipeline test proving handoff succeeds both from fresh producer execution and from accepted normalized-artifact hydration. Keep this at the pipeline boundary if the CLI execution test already proves flag wiring; do not duplicate every CLI case end to end.
  4. Review existing recomputation policy tests. Retain the pure closure test because it protects transitive selection, but remove or consolidate any new test that merely repeats the CLI or pipeline behavior above.
  5. Make only production changes revealed as necessary by these contract tests; do not add new flag semantics or broaden the feature roadmap.

Completion Gate

Run go test ./internal/cli ./internal/framework/pipeline ./internal/framework/checkpoint, then run the CLI, pipeline, and checkpoint packages under the race detector. Confirm no test asserts internal loader call counts, exact decision detail, filesystem layout outside a temp workspace, or the exact length of any prompt or prefix.

Stage 5: Current Documentation And Release Verification

Objective

Reconcile all canonical documentation after the staged changes and verify the complete feature against policy and roadmap.

Changes

  1. Re-read docs/internal/pipeline.md, docs/internal/state.md, docs/operations.md, and docs/cli.md against the final code. Correct any stale statements left by Stages 1 through 4 without duplicating their canonical contracts.
  2. Ensure internal documentation describes the coordinator, lane engine, stage checkpoint flow, and accepted-artifact validation at the responsibility level without listing volatile private helper names.
  3. Confirm the operator-visible reason-code table has one canonical owner and other documents link to it rather than maintaining parallel copies.
  4. Remove stale implementation claims exposed by this work and ensure the feature roadmap continues to describe target policy rather than task sequencing.

Verification

Run the repository-prescribed commands from docs/development.md:

go test ./...
go vet ./...
go build ./cmd/notarius

Also run race tests for the pipeline, checkpoint, CLI state, D&D NPC registry, and D&D integration packages. Validate maintained examples/configurations through the production catalog. Review one fresh run, ordinary resumed run, forced-recompute run, hydrated-predecessor run, and invalid-predecessor failure for deterministic ordering, correct decisions, bounded provenance, and absence of generated content, secrets, or local paths in manifests and diagnostics.

Completion Gate

The follow-up is complete only when every finding summarized above is protected by a stable behavioral test, the current documentation matches the corrected implementation, the full verification suite passes, and the worktree contains no temporary adapters or TODOs introduced by these stages.

Open Questions

None. The feature roadmap and this plan fix the required product and architecture choices. If implementation reveals that accepted normalized artifacts cannot be validated safely without a persistent format change, stop and amend this plan rather than weakening identity, codec, or content validation.