454 lines
19 KiB
Markdown
454 lines
19 KiB
Markdown
# Domain-Typed Pipeline Implementation Roadmap
|
|
|
|
## Status
|
|
|
|
The domain-typed pipeline was implemented on 2026-07-17. A subsequent review
|
|
identified five follow-up issues. The stages below are the decision-complete
|
|
implementation plan for resolving them.
|
|
|
|
Implement the stages in order. Keep each stage independently reviewable and
|
|
leave the repository passing its full validation suite before beginning the
|
|
next stage. Unless a stage explicitly says otherwise, preserve public CLI,
|
|
configuration, module-key, validator-key, durable output, and checkpoint file
|
|
contracts.
|
|
|
|
Current architectural policy is defined by
|
|
[Architecture](../policy/architecture.md). The design context remains in the
|
|
[feature roadmap](domain.md) and
|
|
[ADR-0002](../adr/0002-linear-pipes-and-filters-pipeline.md),
|
|
[ADR-0003](../adr/0003-typed-interfaces-with-two-zone-data-model.md), and
|
|
[ADR-0004](../adr/0004-package-modules-by-domain.md).
|
|
|
|
## Delivered Baseline
|
|
|
|
The existing implementation already provides:
|
|
|
|
- domain-first production extensions and package-family registrars;
|
|
- engine-owned source provenance using `source.SourceRef` and workspace schema
|
|
v2;
|
|
- one domain-owned Go type through extraction, merge, normalization, and typed
|
|
validation;
|
|
- schema-aware artifact codecs at checkpoint, debug, and output boundaries;
|
|
- pre-execution typed resolution, option validation, and preparation;
|
|
- one shared scheduled LLM client; and
|
|
- bounded chunk-first, lane-second extraction with deterministic public
|
|
outcomes.
|
|
|
|
The following work corrects checkpoint identity, completes retry debugging,
|
|
removes kind-ambiguous registry lookup, strengthens architectural enforcement,
|
|
and removes the obsolete sequential extraction path.
|
|
|
|
## Implementation Rules
|
|
|
|
Apply these rules to every stage:
|
|
|
|
1. Read the task-specific documents listed in
|
|
[Development](../development.md) before changing the affected subsystem.
|
|
2. Add focused regression tests that fail against the pre-stage code and pass
|
|
after the change.
|
|
3. Preserve deterministic behavior. Do not base digests, public ordering,
|
|
reference resolution, or reported errors on Go map iteration or goroutine
|
|
completion order.
|
|
4. Keep framework-owned type erasure private. Do not reintroduce module-facing
|
|
`any`, raw JSON handoffs, or a generic processing interface.
|
|
5. Update current-behavior documentation in the same stage when externally
|
|
observable or documented internal behavior changes. Follow
|
|
[Documentation Policy](../policy/documentation.md); do not describe a later
|
|
stage as already implemented.
|
|
6. Run focused tests while iterating. At the end of every stage, run:
|
|
|
|
```sh
|
|
go test ./...
|
|
go test -race ./...
|
|
go vet ./...
|
|
go build ./cmd/notarius
|
|
git diff --check
|
|
```
|
|
|
|
## Stage 1: Include Resolved Validator Policy In Pipeline Identity
|
|
|
|
### Goal
|
|
|
|
Ensure that every effective validator-chain change alters the resolved
|
|
pipeline digest and therefore prevents reuse of checkpoints created under a
|
|
different validation policy.
|
|
|
|
### Required Changes
|
|
|
|
1. Extend `resolvedPipelineDigest` in
|
|
`internal/framework/pipeline/profile.go` to include the complete resolved
|
|
validator-chain collection. Include, for every chain:
|
|
|
|
- stage, lane ID, and owning module key;
|
|
- validator order;
|
|
- each complete resolved binding, including module key, LLM profile,
|
|
retries, options, and references;
|
|
- validator execution class;
|
|
- validator target; and
|
|
- artifact kind.
|
|
|
|
2. Hash the already canonical `ResolvedPipeline.ValidatorChains` order produced
|
|
by resolution. Do not independently sort validators or otherwise weaken
|
|
configured order. Continue excluding only the digest field itself.
|
|
3. Use the same deterministic JSON-and-SHA-256 mechanism as the existing
|
|
pipeline digest. Go map keys encoded inside bindings must retain the
|
|
deterministic ordering supplied by `encoding/json`.
|
|
4. Do not add validator fingerprints separately to individual stage
|
|
dependencies. Pipeline/workspace identity is the authoritative invalidation
|
|
boundary for validation-policy changes.
|
|
5. Do not advance the workspace schema version. The corrected digest naturally
|
|
selects a different checkpoint directory; existing checkpoints remain
|
|
intact and become reuse misses for the changed pipeline identity.
|
|
|
|
### Tests
|
|
|
|
Add focused tests under `internal/framework/pipeline` and, where useful,
|
|
`internal/core/workspace` proving that:
|
|
|
|
- registering a different default chain changes the resolved pipeline digest;
|
|
- adding, removing, or reordering a default validator changes the digest;
|
|
- changing a resolved validator binding option or LLM profile changes the
|
|
digest;
|
|
- changing execution class, target, or artifact kind changes the digest;
|
|
- resolving the same pipeline and catalog repeatedly produces the same digest;
|
|
- an explicit empty override produces an empty resolved chain and differs from
|
|
a non-empty inherited default; and
|
|
- a changed validator-chain digest produces a different checkpoint identity,
|
|
while an unchanged chain preserves it.
|
|
|
|
Retain the existing tests proving that the digest field itself is excluded and
|
|
that artifact schema identity participates in the digest.
|
|
|
|
### Documentation
|
|
|
|
Update the checkpoint invalidation description in `docs/operations.md` and the
|
|
identity description in `docs/internal/pipeline.md` to state concisely that the
|
|
effective resolved validator policy participates in pipeline identity.
|
|
|
|
### Completion Gate
|
|
|
|
A checkpoint accepted under one resolved default or explicit validator chain
|
|
must not be reusable after that chain changes.
|
|
|
|
## Stage 2: Complete Attempt-Scoped Debugging For Merge And Normalize
|
|
|
|
### Goal
|
|
|
|
Give merge and normalize retries the same attempt-level observability and
|
|
nested LLM-call association already provided for chunk and extract attempts.
|
|
|
|
### Required Changes
|
|
|
|
1. In `internal/framework/pipeline/runner_typed.go`, create an attempt-specific
|
|
debug context before invoking each merge or normalize module. Pass that
|
|
context to both the module operation and its validation chain.
|
|
2. Use these stable paths:
|
|
|
|
```text
|
|
merge/<lane-id>/attempt-<NN>.json
|
|
merge/<lane-id>/attempt-<NN>/prompt-<NNNN>.json
|
|
merge/<lane-id>/attempt-<NN>/response-<NNNN>.json
|
|
merge/<lane-id>/attempt-<NN>/response-content-<NNNN>.<ext>
|
|
|
|
normalize/<lane-id>/attempt-<NN>.json
|
|
normalize/<lane-id>/attempt-<NN>/prompt-<NNNN>.json
|
|
normalize/<lane-id>/attempt-<NN>/response-<NNNN>.json
|
|
normalize/<lane-id>/attempt-<NN>/response-content-<NNNN>.<ext>
|
|
```
|
|
|
|
Continue using two-digit retry attempt numbers and the existing debug path
|
|
sanitization and LLM call numbering behavior.
|
|
3. Write one module-attempt envelope for every attempted merge and normalize
|
|
operation:
|
|
|
|
- on success, record the codec-backed candidate artifact and warnings that
|
|
will be promoted if the attempt is accepted;
|
|
- on validator rejection, record the rejection without promoting discarded
|
|
warnings;
|
|
- on module, validation, serialization, or debug failure, record the error;
|
|
and
|
|
- in all cases, attach the LLM calls recorded in the module attempt scope.
|
|
|
|
4. Keep validator-specific debug scopes under the existing `validate/...`
|
|
hierarchy. A validator's LLM calls remain linked to its validator attempt;
|
|
the merge or normalize module envelope links only calls made by that module
|
|
attempt.
|
|
5. Preserve the existing stage-level `input.json` and `output.json` artifacts.
|
|
Checkpoint reuse should continue to emit stage-level artifacts but should
|
|
not synthesize retry attempts that did not execute.
|
|
6. Factor common attempt-envelope behavior into a small private helper where it
|
|
prevents chunk, extract, merge, and normalize instrumentation from drifting.
|
|
Do not introduce a new public runner abstraction solely for debugging.
|
|
7. Treat any debug write failure as a framework error, consistent with current
|
|
debug policy.
|
|
|
|
### Tests
|
|
|
|
Add tests with instrumented LLM-backed fake mergers and normalizers proving
|
|
that:
|
|
|
|
- first-attempt success writes the expected attempt and nested LLM artifacts;
|
|
- a failed first attempt followed by success writes two distinct attempt
|
|
envelopes and associates each call with the correct attempt;
|
|
- module errors, validator errors, and final rejection are represented in the
|
|
corresponding attempt envelope;
|
|
- validator LLM calls remain under validator paths rather than being attributed
|
|
to the module scope;
|
|
- discarded-attempt warnings are not promoted;
|
|
- checkpoint reuse produces no module-attempt files; and
|
|
- no merge or normalize call falls back to an unscoped stage-name debug path.
|
|
|
|
Extend the CLI debug integration test only as needed to verify the public debug
|
|
directory layout. Keep most behavioral coverage in the pipeline package.
|
|
|
|
### Documentation
|
|
|
|
Update `docs/operations.md` if necessary to show the stable merge and normalize
|
|
attempt paths. Update `docs/internal/pipeline.md` only where its implementation
|
|
description needs clarification; its existing attempt-scoping guarantee should
|
|
become fully true rather than be weakened.
|
|
|
|
### Completion Gate
|
|
|
|
Every executed merge and normalize retry must have a distinct debug envelope,
|
|
and every LLM call made by that module attempt must be nested under and linked
|
|
from that attempt.
|
|
|
|
## Stage 3: Make Typed Variant Spec Lookup Kind-Specific
|
|
|
|
### Goal
|
|
|
|
Ensure that CLI reference-target discovery and other behavior-sensitive lookup
|
|
select the merger or normalizer specification for the lane's actual artifact
|
|
kind, never an arbitrary map entry.
|
|
|
|
### Required Changes
|
|
|
|
1. Add explicit kind-specific lookup methods to `MergerRegistry` and
|
|
`NormalizerRegistry`, named:
|
|
|
|
```go
|
|
SpecForArtifact(key string, kind contracts.ArtifactKind) (ModuleSpec, bool)
|
|
```
|
|
|
|
Normalize the key and artifact kind in the same way as typed registration
|
|
and return a cloned spec.
|
|
2. Change CLI reference-target discovery in `internal/cli/run.go` to retain the
|
|
selected extractor's declared artifact kind and use it for merger and
|
|
normalizer spec lookup in that lane.
|
|
3. A missing typed variant must produce a deterministic error naming the
|
|
pipeline, lane, stage, module key, requested artifact kind, and sorted
|
|
registered kinds, matching the quality of full pipeline resolution errors.
|
|
4. Keep `Spec(key)` for kind-neutral catalog inspection and compatibility with
|
|
existing callers, but remove its map-order dependence. Select the first
|
|
registered artifact kind in sorted order before cloning its spec. Add a
|
|
comment making clear that behavior-sensitive code must use
|
|
`SpecForArtifact`.
|
|
5. Do not require typed variants under one reusable key to expose identical
|
|
reference slots or capabilities. Their kind-specific specs are allowed to
|
|
differ, and resolution must consistently choose the matching variant.
|
|
6. Audit all merger and normalizer `Spec` callers. Convert any caller making a
|
|
lane-specific decision to `SpecForArtifact`; leave only catalog or display
|
|
callers on the kind-neutral method.
|
|
|
|
### Tests
|
|
|
|
Register at least two artifact-kind variants under the same merger key and the
|
|
same normalizer key with intentionally different reference slots. Prove that:
|
|
|
|
- `SpecForArtifact` returns the correct cloned variant;
|
|
- lookup is stable regardless of registration order;
|
|
- CLI qualified and unqualified reference discovery uses the selected lane's
|
|
variant;
|
|
- a reference accepted by one variant is not incorrectly accepted for another;
|
|
- a missing variant reports sorted available kinds; and
|
|
- repeated `Spec(key)` calls return the same deterministic catalog result.
|
|
|
|
Retain coverage for the existing single-variant D&D catalog behavior.
|
|
|
|
### Documentation
|
|
|
|
Update `docs/internal/pipeline.md` or `docs/internal/modules.md` only if either
|
|
currently describes registry lookup mechanics. No CLI or configuration syntax
|
|
change is intended.
|
|
|
|
### Completion Gate
|
|
|
|
No behavior-sensitive merger or normalizer lookup may depend on map iteration,
|
|
and CLI reference resolution must use the artifact kind selected by the lane's
|
|
extractor.
|
|
|
|
## Stage 4: Enforce Domain Import Boundaries Generically
|
|
|
|
### Goal
|
|
|
|
Make the repository guard enforce ADR-0004 for present and future module
|
|
families without a hardcoded list of domain names or domain pairs.
|
|
|
|
### Required Changes
|
|
|
|
1. Refactor `internal/modules/import_boundaries_test.go` so that a module family
|
|
is derived from the first path segment under `internal/modules/`, rather than
|
|
recognized by a fixed `isDomain` list.
|
|
2. Treat `generic` as the domain-neutral reusable extension family. Treat every
|
|
other production family, including `dnd`, `seriatim`, and future families,
|
|
as concrete for import-boundary purposes.
|
|
3. Enforce these rules:
|
|
|
|
- a family root package must not import its own child implementation or
|
|
registrar packages;
|
|
- child packages within the same concrete family may import the family root,
|
|
shared helpers, or sibling implementations when needed;
|
|
- a concrete family must not import another concrete family;
|
|
- generic packages must not import concrete families;
|
|
- concrete implementation packages must not import generic implementation
|
|
packages directly;
|
|
- `internal/modules/<family>/register` may import its own family packages and
|
|
generic packages to compose typed strategies;
|
|
- the generic registrar may import generic child packages; and
|
|
- the application composition root and designated black-box integration
|
|
tests may compose multiple families.
|
|
|
|
4. Exclude `internal/modules/integration` from production-family discovery.
|
|
Preserve its exemption only for `_test.go` black-box composition files; do
|
|
not create a blanket exemption for production Go files.
|
|
5. Apply production import rules to white-box tests located in concrete module
|
|
packages. If an existing test composes concrete and generic implementations,
|
|
move that cross-family coverage to `internal/modules/integration` or replace
|
|
the foreign implementation with a package-local test double. Do not exempt
|
|
arbitrary `_test.go` files merely because they are tests.
|
|
6. Keep the test based on parsed Go imports. Do not add a new build tool or
|
|
external dependency for this guard.
|
|
|
|
### Tests
|
|
|
|
Expand the table-driven import tests to cover:
|
|
|
|
- a hypothetical future concrete family, demonstrating that no code change is
|
|
needed to enforce its boundaries;
|
|
- generic-to-concrete rejection for both current and hypothetical families;
|
|
- concrete-to-peer-concrete rejection;
|
|
- concrete implementation-to-generic rejection;
|
|
- concrete registrar-to-generic acceptance;
|
|
- family-root-to-child rejection;
|
|
- child-to-family-root and same-family sibling acceptance;
|
|
- application composition-root acceptance; and
|
|
- black-box integration-test acceptance without exempting non-test files.
|
|
|
|
Run the guard against the complete current repository and confirm that no
|
|
production package must be moved to satisfy it.
|
|
|
|
### Documentation
|
|
|
|
No ADR change is required. Update `docs/internal/modules.md` only if its package
|
|
boundary description needs to name the registrar-only generic composition
|
|
rule more clearly.
|
|
|
|
### Completion Gate
|
|
|
|
Adding a new directory under `internal/modules/<new-family>` must automatically
|
|
receive the same cross-family and registrar enforcement as current production
|
|
families.
|
|
|
|
## Stage 5: Remove The Obsolete Sequential Extraction Path
|
|
|
|
### Goal
|
|
|
|
Make the runner's structure match the implemented concurrent architecture:
|
|
the coordinator owns extraction, and a lane continuation begins from finalized
|
|
extract results and performs only merge and normalize work.
|
|
|
|
### Required Changes
|
|
|
|
1. Replace `runTypedLane` with a continuation-oriented private function whose
|
|
input explicitly contains the finalized extraction state needed by merge and
|
|
normalize:
|
|
|
|
- accepted typed extract artifacts;
|
|
- serialized checkpoint artifacts;
|
|
- accepted warnings;
|
|
- rejected outputs; and
|
|
- the original extract checkpoint decision.
|
|
|
|
Use a private struct if it keeps the call boundary clear and avoids a long
|
|
positional parameter list.
|
|
2. Change `continueLane` in `runner_concurrent.go` to pass the finalized state
|
|
directly. Do not make completed extraction look like checkpoint reuse.
|
|
3. Remove:
|
|
|
|
- `completedExtractLoader`;
|
|
- the `RunInput.extractDecision` private override;
|
|
- the sequential extraction branch formerly contained in `runTypedLane`;
|
|
and
|
|
- duplicate extraction retry, validation, checkpoint, and debug logic made
|
|
unreachable by concurrent coordination.
|
|
|
|
4. Keep extraction checkpoint loading and validation in `prepareLaneExtract`,
|
|
extract execution in `runExtractJob`, and deterministic final aggregation in
|
|
`finalizeLaneExtract`.
|
|
5. Preserve existing behavior exactly:
|
|
|
|
- extract checkpoint events report the real loader decision;
|
|
- reused and freshly computed extract results enter continuation through the
|
|
same typed state;
|
|
- accepted artifacts reach merge in chunk-index order;
|
|
- warnings and rejections retain stable ordering and promotion semantics;
|
|
- a lane with no accepted extracts follows the current merge behavior;
|
|
- stage-level extract input/output debug artifacts remain unchanged; and
|
|
- failure classification and cancellation retain their deterministic scope.
|
|
|
|
6. Keep merge and normalize serial within a lane and keep cross-lane
|
|
continuations bounded by the existing worker policy. Do not introduce a
|
|
goroutine per lane or chunk.
|
|
|
|
### Tests
|
|
|
|
Add or adjust focused runner tests proving parity for:
|
|
|
|
- fresh extraction, fully reused extraction, and a mixture of accepted and
|
|
rejected chunk results;
|
|
- deterministic extract ordering under reverse completion;
|
|
- warning promotion across retries;
|
|
- real checkpoint decision reporting for reused and recomputed extracts;
|
|
- extract input/output and attempt debug paths;
|
|
- framework cancellation and deterministic error selection; and
|
|
- unchanged bounded worker and global LLM concurrency behavior.
|
|
|
|
Use source search or a package-local compile-time assertion where practical to
|
|
confirm there is only one extraction execution path and no remaining
|
|
`completedExtractLoader` or `extractDecision` compatibility shim.
|
|
|
|
### Documentation
|
|
|
|
Update `docs/internal/pipeline.md` if its execution-flow description names the
|
|
old continuation mechanism. This stage is an internal refactor and must not
|
|
change operator or integration contracts.
|
|
|
|
### Completion Gate
|
|
|
|
The concurrent coordinator must be the only code path that executes extraction,
|
|
and lane continuation must consume finalized extraction state without routing
|
|
it through a synthetic checkpoint loader.
|
|
|
|
## Final Verification And Closeout
|
|
|
|
After all five stages:
|
|
|
|
1. Run the full validation commands from this document on a clean worktree.
|
|
2. Exercise the maintained D&D production example with both a fresh workspace
|
|
and checkpoint resume.
|
|
3. Verify that changing the registered default spell validator chain selects a
|
|
different checkpoint identity.
|
|
4. Verify merge and normalize attempt debug behavior with instrumented
|
|
LLM-backed test modules.
|
|
5. Confirm current production imports satisfy the generic boundary guard.
|
|
6. Re-read current-behavior documentation for statements made true or obsolete
|
|
by these stages.
|
|
7. Replace this roadmap's status with a concise completion record only after all
|
|
stages and documentation updates have landed.
|
|
|
|
## Open Questions
|
|
|
|
None. The implementation choices required for these five remediation stages
|
|
are specified above.
|