837 lines
36 KiB
Markdown
837 lines
36 KiB
Markdown
# Domain-Typed Pipeline Implementation Plan
|
|
|
|
## Purpose
|
|
|
|
This document is the executable implementation plan for the target state in the
|
|
[domain-typed pipeline feature roadmap](domain.md). It assumes the decisions in
|
|
[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).
|
|
|
|
The intended operator is an LLM coding agent working through one stage per
|
|
implementation prompt. Complete the stages in order. Each stage must leave the
|
|
repository buildable and tested; do not defer a broken intermediate state to a
|
|
later stage.
|
|
|
|
## Implementation Rules
|
|
|
|
For every stage:
|
|
|
|
1. Read `docs/development.md` and follow its task-specific reading guide. Read
|
|
the current implementation and focused tests for every touched subsystem.
|
|
2. Treat the feature roadmap as the canonical owner of desired behavior and
|
|
this document as the canonical owner of task sequencing. Do not restate
|
|
future behavior in current-behavior documentation before it exists.
|
|
3. Preserve unrelated user changes. Use mechanical moves where possible so file
|
|
history and test intent remain legible.
|
|
4. Add focused tests with the change. Run those tests while iterating, then run
|
|
`go test ./...`, `go vet ./...`, and `go build ./cmd/notarius` before ending
|
|
the stage.
|
|
5. Run `go test -race ./...` in stages that introduce or change concurrency and
|
|
in the final stage.
|
|
6. Update the canonical current-behavior documents in the same stage in which
|
|
behavior changes. At minimum, reconsider `docs/policy/architecture.md`,
|
|
`docs/internal/overview.md`, `docs/internal/pipeline.md`,
|
|
`docs/internal/modules.md`, `docs/internal/llm.md`, `docs/config.md`,
|
|
`docs/operations.md`, and `docs/integrations/` according to the documentation
|
|
policy; edit only the documents whose owned facts changed.
|
|
7. Do not change user-visible module keys, validator keys, output paths, durable
|
|
D&D JSON, prompt/schema identities, default chains, or rejection semantics
|
|
unless this plan explicitly requires it.
|
|
8. Stop after a stage if an exit criterion cannot be met. Record the concrete
|
|
blocker rather than implementing a second architecture alongside this one.
|
|
|
|
## Fixed Technical Decisions
|
|
|
|
The following choices are inputs to implementation, not questions to reopen in
|
|
individual stages.
|
|
|
|
### Source types
|
|
|
|
Keep the existing names `source.SourceDocument`, `source.SourceUnit`, and
|
|
`source.SourceRef`. Add `Ref source.SourceRef` to `SourceUnit`. Move
|
|
`contracts.SourceChunk` to `internal/core/source` as `source.Chunk`, with this
|
|
logical shape:
|
|
|
|
```go
|
|
type Chunk struct {
|
|
ID string
|
|
SourceID string
|
|
Index int
|
|
Ref SourceRef
|
|
Content []byte
|
|
MediaType string
|
|
Units []SourceUnit
|
|
Metadata map[string]any
|
|
}
|
|
```
|
|
|
|
Remove `StartUnitID` and `EndUnitID`; `Ref` is the only chunk-boundary
|
|
representation. A Seriatim unit's self-reference is
|
|
`{SourceID: document ID, StartUnitID: unit ID, EndUnitID: unit ID}`. A chunk
|
|
reference spans its first and last included units. Advance persisted workspace
|
|
state from `notarius.workspace.v1` to `notarius.workspace.v2`; v1 state is
|
|
incompatible and must be recomputed, but never deleted automatically.
|
|
|
|
### Artifact contracts
|
|
|
|
Place engine-owned artifact primitives with the other universal contracts under
|
|
`internal/framework/contracts`:
|
|
|
|
```go
|
|
type ArtifactKind string
|
|
|
|
type ArtifactSchema struct {
|
|
ID string
|
|
Name string
|
|
Version string
|
|
JSONSchema []byte
|
|
}
|
|
|
|
type SerializedArtifact struct {
|
|
Kind ArtifactKind
|
|
Schema ArtifactSchema
|
|
MediaType string
|
|
Content []byte
|
|
Metadata map[string]any
|
|
}
|
|
|
|
type ArtifactCodec[T any] interface {
|
|
Kind() ArtifactKind
|
|
Schema() ArtifactSchema
|
|
MediaType() string
|
|
Encode(T) ([]byte, error)
|
|
Decode([]byte) (T, error)
|
|
}
|
|
```
|
|
|
|
Use strict JSON decoding for JSON codecs: reject unknown fields and trailing
|
|
tokens. Encoding must be deterministic for equal canonical values. Clone byte
|
|
slices and maps at framework ownership boundaries. Validate non-empty artifact
|
|
kind, schema ID, schema name, schema version, media type, and JSON Schema during
|
|
registration. Compute and retain a SHA-256 digest of the JSON Schema bytes.
|
|
|
|
Use generic `Extractor[T]`, `Merger[T]`, `Normalizer[T]`, and
|
|
`TypedValidator[T]` contracts. The exact request/result structs may retain
|
|
existing names where that reduces churn, but they must satisfy these rules:
|
|
|
|
- the extractor returns `T`, warnings, and framework-owned chunk provenance;
|
|
- merge receives accepted per-chunk typed values carrying lane ID, source ID,
|
|
chunk ID, chunk index, and chunk reference, already sorted by chunk index;
|
|
- normalize receives and returns `T`;
|
|
- typed validators receive `T` plus the relevant universal source, chunk,
|
|
reference, lane, stage, profile, and metadata context;
|
|
- the LLM client and decoded module options are held by constructed
|
|
implementations, not passed in operation requests; and
|
|
- no module-facing Zone-B request or result contains `RawPayload`, `any`, or
|
|
serialized JSON as its artifact value.
|
|
|
|
Retain separate framework wrappers around `T` for extract, merge, and normalize
|
|
provenance. Do not put lane IDs, module keys, or framework warnings into the D&D
|
|
domain value itself.
|
|
|
|
Support a second `SerializedValidator` contract for representation-level
|
|
generic validators. Its request contains immutable bytes, media type, and
|
|
optional schema metadata. For a Zone-B value, the framework produces that
|
|
request with the lane codec. Keep a separate non-generic `ChunkValidator`
|
|
contract for semantic validation of immutable `[]source.Chunk`; when a
|
|
representation validator is selected at chunk, the framework instead supplies
|
|
its canonical JSON chunk encoding. `valid_json` and `valid_json_schema` use the
|
|
serialized path, domain validators use `TypedValidator[T]`, and generic
|
|
approve/reject validators register explicit chunk and typed-artifact variants.
|
|
|
|
### Typed registry model
|
|
|
|
Because Go methods cannot introduce type parameters, expose free generic
|
|
registration functions in `internal/framework/pipeline`, backed by private
|
|
non-generic registry entries. Use `reflect.TypeFor[T]()` only inside registration
|
|
and framework assembly to prove exact type equality.
|
|
|
|
- Codec registry key: artifact kind. Exactly one codec may be registered per
|
|
kind.
|
|
- Extractor registry key: existing module key. Each entry declares one artifact
|
|
kind and exact Go type.
|
|
- Merger and normalizer registry key: `(existing module key, artifact kind)`.
|
|
- Typed validator registry key: `(existing validator key, artifact kind)`.
|
|
- Chunk-validator registry key: existing validator key in the distinct chunk
|
|
target namespace.
|
|
- Serialized validators retain their existing validator key and declare whether
|
|
they support chunk values, artifact values, or both.
|
|
- Duplicate keys/variants, missing codecs, Go-type mismatches, and incompatible
|
|
selected variants are errors.
|
|
|
|
The extractor selected for a lane establishes the lane artifact kind. During
|
|
resolution, look up merger, normalizer, and validators against that kind.
|
|
Record artifact kind, schema ID, schema version, and schema digest on the
|
|
resolved lane and in the resolved-pipeline digest. A resolved pipeline with an
|
|
incompatible lane must fail before preparation or source execution.
|
|
|
|
The private erased lane entry owns closures for construction and execution of
|
|
one concrete `T`. It may store a value as `any` internally, but it must verify
|
|
the exact registered `reflect.Type` at every erased boundary and return a
|
|
descriptive framework error rather than panic.
|
|
|
|
### Construction and preparation
|
|
|
|
Use one uniform construction context:
|
|
|
|
```go
|
|
type ModuleDependencies struct {
|
|
LLM contracts.StructuredLLMClient
|
|
}
|
|
|
|
type BuildRequest struct {
|
|
Dependencies ModuleDependencies
|
|
Options map[string]any
|
|
}
|
|
```
|
|
|
|
Each registry entry stores both an option-validation closure and a constructor.
|
|
Implementations own concrete option structs and one decoder used by both
|
|
closures. Resolution/configuration validation calls the decoder and discards
|
|
the value; preparation calls it once and supplies the decoded value to the
|
|
constructor. Reject unknown option fields. Empty options produce the
|
|
implementation's explicit defaults.
|
|
|
|
Add:
|
|
|
|
```go
|
|
func Prepare(
|
|
resolved ResolvedPipeline,
|
|
registries Registries,
|
|
deps ModuleDependencies,
|
|
) (*PreparedPipeline, error)
|
|
```
|
|
|
|
`PreparedPipeline` retains explicit resolved input, chunk, artifact-lane, and
|
|
output fields and all constructed validators. Construct in stable pipeline
|
|
order: input; chunk and its validators; each resolved lane in order with extract,
|
|
merge, normalize, and their validator chains in stage order; then output. On the
|
|
first failure, return an error identifying stage, lane if any, module or
|
|
validator key, and cause. No operation method may have run.
|
|
|
|
Create the one scheduled production LLM client first, inject it into
|
|
preparation, and then run only the prepared pipeline. Test-only deterministic
|
|
modules may accept a nil LLM dependency; any implementation that declares or
|
|
uses LLM-backed execution must reject a nil client at preparation. Remove LLM
|
|
clients and raw option maps from operation requests after every production
|
|
implementation has migrated.
|
|
|
|
Constructed modules and validators are reused for a run. Anything callable from
|
|
parallel extract workers must be concurrency-safe; production implementations
|
|
should be immutable after construction.
|
|
|
|
### Package registration
|
|
|
|
Each package-family registrar exposes:
|
|
|
|
```go
|
|
func Register(registries pipeline.Registries, assets *llm.AssetRegistry) error
|
|
```
|
|
|
|
The generic and Seriatim registrars ignore the asset argument until they need
|
|
it. Registrars validate the registry pointers they use and return contextual
|
|
errors. The CLI creates one complete registry set and one asset registry, then
|
|
calls registrars in this order: generic, Seriatim, D&D. The D&D registrar owns
|
|
D&D codecs, implementations, typed generic specializations, prompt/schema
|
|
assets, and default validator chains.
|
|
|
|
`internal/modules/dnd` owns D&D shared types. Its `register` sibling may import
|
|
children and generic strategies; the root package must not import its children.
|
|
`internal/modules/generic` never imports D&D. `internal/modules/seriatim` does not
|
|
import D&D. Move domain-neutral embedded prompt-filesystem helpers to
|
|
`internal/framework/promptfs`.
|
|
|
|
### D&D typed model
|
|
|
|
Define `dnd.SpellList`, `dnd.SpellCast`, and evidence/source-reference fields at
|
|
the D&D package root. Use `source.SourceRef`; do not create another D&D unit-ref
|
|
type for artifact provenance. The stable artifact kind is
|
|
`dnd/spell-list`.
|
|
|
|
The spell extractor owns a private LLM DTO and the existing
|
|
`dnd_spells_llm.v1.json` response schema. It canonicalizes and maps that DTO to
|
|
`dnd.SpellList`. The codec package owns `dnd_spells.v1.json` and the durable
|
|
encoding. Keep those schemas separate. Remove the validator-only
|
|
`spellpayload` model after all three D&D validators consume `dnd.SpellList`.
|
|
|
|
Make `appendorder` a generic strategy that accepts a typed combine function at
|
|
registration/construction. The D&D registrar supplies a function that appends
|
|
spell casts in already-sorted source-chunk order. Make `noop` a generic typed
|
|
strategy. Neither generic package imports D&D.
|
|
|
|
### Serialized boundaries
|
|
|
|
After normalize, encode `T` once to `SerializedArtifact` for final output.
|
|
Output remains domain-neutral and receives serialized artifacts. Preserve the
|
|
existing output bundle and index contract.
|
|
|
|
Extract, merge, and normalize checkpoints encode and decode through the same
|
|
lane codec. Checkpoint identity includes artifact kind, schema ID, schema
|
|
version, and schema digest. A missing codec or any mismatch invalidates reuse
|
|
and recomputes the stage; it is not a fatal run error by itself. Codec decode
|
|
failure also invalidates that checkpoint and records the reason. Never pass
|
|
serialized checkpoint content directly to the next typed stage.
|
|
|
|
Debug recording uses the codec for typed artifact values and preserves existing
|
|
opt-in/sensitive-content rules. Artifact/checkpoint digests use the stable codec
|
|
bytes. Aggregate manifests, checkpoint indexes, warning slices, and rejection
|
|
slices have one coordinator writer.
|
|
|
|
### Concurrency
|
|
|
|
Version-2 configuration gains:
|
|
|
|
```yaml
|
|
concurrency:
|
|
total_llm: 4
|
|
stage_workers:
|
|
extract: 4
|
|
```
|
|
|
|
Represent effective stage worker limits as `map[string]int`. Initially accept
|
|
only `extract`; reject unknown keys. Missing extract defaults to `total_llm` and
|
|
its valid range is `1..total_llm`. Add the environment override
|
|
`NOTARIUS_STAGE_WORKERS_EXTRACT`. Preserve precedence rules and keep the file
|
|
configuration version at 2.
|
|
|
|
Use one run-wide fixed extract worker pool. Dispatch jobs in round-robin order
|
|
with source chunk as the outer loop and resolved lane as the inner loop. Use one
|
|
job channel whose capacity equals the effective extract worker count, so the
|
|
dispatcher applies bounded backpressure. Do not create one goroutine per job. A
|
|
job includes extract retries and extract-stage validators. Store results by
|
|
lane index and chunk index.
|
|
|
|
When all extract jobs for a lane are terminal, run that lane's merge and then
|
|
normalize serially. Lane continuations may overlap. All LLM calls at all stages,
|
|
including retries and validators, use the single injected scheduled client, so
|
|
`total_llm` remains the authoritative process-wide provider-call ceiling.
|
|
|
|
Rejections are terminal results and do not cancel other work. A framework error
|
|
cancels a derived run context, stops dispatching jobs not yet started, and waits
|
|
for started tasks to finish or observe cancellation. Choose the returned error
|
|
as follows:
|
|
|
|
1. if the parent context is canceled, return its error;
|
|
2. otherwise discard internal `context.Canceled`/`DeadlineExceeded` errors when
|
|
at least one non-context framework error exists; and
|
|
3. choose the earliest remaining error by stage order (`extract`, `merge`,
|
|
`normalize`), resolved lane index, chunk index for chunk-scoped work, and
|
|
configured validator/operation index.
|
|
|
|
Use a sentinel chunk index after all real chunks for lane-scoped merge and
|
|
normalize errors. Retain other started-task errors only in opt-in diagnostics.
|
|
Sort accepted artifacts, warnings, and rejections by resolved lane index, source
|
|
chunk index where applicable, stage order, validator order, and original
|
|
within-result order. Completion timing must not affect public output. Run output
|
|
only if every lane has a successful or rejection-only terminal outcome and no
|
|
framework error occurred.
|
|
|
|
## Staged Implementation
|
|
|
|
### Stage 1: Compatibility Baselines and ADR Acceptance
|
|
|
|
Goal: lock down behavior that subsequent internal migrations must preserve and
|
|
record the architectural decisions as accepted.
|
|
|
|
Tasks:
|
|
|
|
- Add semantic or golden compatibility tests for the maintained Seriatim-to-D&D
|
|
path: durable output files and JSON, output index, manifest provenance,
|
|
warnings, rejections, and stable lane/chunk ordering.
|
|
- Snapshot production module keys, validator keys, default validator chains,
|
|
prompt/schema identities, and maintained example/profile resolution in tests.
|
|
- Strengthen runner tests for fixed topology, validator rejection as a nonfatal
|
|
outcome, framework-error abort, retries, parent cancellation, checkpoint reuse
|
|
and invalidation, diagnostics, and opt-in debug behavior.
|
|
- Add an instrumented scheduled-client test showing that all existing production
|
|
LLM callers share `concurrency.total_llm`. It need not demonstrate parallel
|
|
lanes yet.
|
|
- Review the three ADRs against this decision-complete plan, set their status to
|
|
`Accepted`, and update their dates only if ADR policy requires an acceptance
|
|
date. Do not rewrite accepted decision text after this stage.
|
|
|
|
Exit criteria:
|
|
|
|
- compatibility tests fail on an unintended durable-output, key, chain,
|
|
provenance, or outcome-semantics change; and
|
|
- ADR-0002, ADR-0003, and ADR-0004 are accepted.
|
|
|
|
### Stage 2: Registrar Composition Without Package Moves
|
|
|
|
Goal: replace CLI leaf-by-leaf registration with package-family composition
|
|
before changing imports.
|
|
|
|
Tasks:
|
|
|
|
- Add `internal/modules/generic/register`, `internal/modules/seriatim/register`,
|
|
and `internal/modules/dnd/register` using the fixed registrar signature.
|
|
- Initially let those registrars import the existing stage-oriented packages.
|
|
Move ownership of production validators, default chains, and prompt assets out
|
|
of `internal/cli/catalog.go` and into the appropriate registrar.
|
|
- Have the CLI allocate complete registries and the asset registry once, invoke
|
|
generic, Seriatim, then D&D registration, and retain its existing test
|
|
injection paths.
|
|
- Test nil registry handling, duplicate registration errors, stable registered
|
|
keys, default chains, and asset identities.
|
|
|
|
Exit criteria:
|
|
|
|
- the CLI composition root names only the three registrar packages, framework
|
|
registry types, and asset registry; and
|
|
- no production key, chain, prompt, schema, or runtime behavior changes.
|
|
|
|
### Stage 3: Mechanical Generic and Seriatim Package Moves
|
|
|
|
Goal: establish the domain-first generic and source-format trees without
|
|
changing contracts.
|
|
|
|
Tasks:
|
|
|
|
- Move the Seriatim adapter and tests to
|
|
`internal/modules/seriatim/input/transcript`.
|
|
- Move the generic chunker to `internal/modules/generic/chunk/units`, retaining
|
|
the configured key `generic`.
|
|
- Move append-order merge, no-op normalize, JSON output, and all generic
|
|
validators to their target paths under `internal/modules/generic`.
|
|
- Update only registrar imports and affected black-box tests. Preserve package
|
|
behavior and all public registry keys.
|
|
- Remove the emptied old directories.
|
|
|
|
Exit criteria:
|
|
|
|
- generic and Seriatim production implementations exist only under their target
|
|
trees; and
|
|
- compatibility baselines remain green.
|
|
|
|
### Stage 4: Mechanical D&D Package Move and Import Guard
|
|
|
|
Goal: establish the D&D tree and enforce ADR-0004 dependency direction while
|
|
legacy contracts are still intact.
|
|
|
|
Tasks:
|
|
|
|
- Move the D&D scenes chunker, spell extractor, validators, schemas, prompt
|
|
assets, and D&D shared helpers into the target D&D tree. Do not create the
|
|
typed root model or codec yet.
|
|
- Move domain-neutral prompt filesystem helpers from
|
|
`internal/modules/sharedassets` to `internal/framework/promptfs`.
|
|
- Move tests with their owning implementation. Relocate tests that intentionally
|
|
compose domains to a black-box integration-test package rather than creating
|
|
peer-domain production imports.
|
|
- Add a Go-parser-based import-boundary test. It must reject concrete
|
|
D&D-to-Seriatim and Seriatim-to-D&D imports, all generic-to-D&D imports, and
|
|
root-domain imports of child implementations. Allow domain registrars, the CLI
|
|
composition root, and designated external integration tests to compose
|
|
packages.
|
|
- Remove old empty stage-oriented and validator directories.
|
|
|
|
Exit criteria:
|
|
|
|
- all production extensions use the target domain-first package layout except
|
|
the not-yet-created typed codec/model pieces;
|
|
- the import guard detects a deliberate fixture violation; and
|
|
- behavior and keys remain unchanged.
|
|
|
|
### Stage 5: Source-Unit Provenance
|
|
|
|
Goal: add canonical provenance to engine-owned source units without yet changing
|
|
the chunk type.
|
|
|
|
Tasks:
|
|
|
|
- Add `Ref source.SourceRef` to `source.SourceUnit`, including clone and debug
|
|
representations.
|
|
- Make the Seriatim adapter assign the fixed self-reference for every unit.
|
|
- Extend `source.ValidateDocument` to require the unit reference's source ID to
|
|
match the document, require start and end IDs to equal the unit ID, reject
|
|
missing/invalid/reversed references, and preserve the existing unit-order and
|
|
uniqueness checks.
|
|
- Make source digests and source checkpoint serialization include the new
|
|
reference deterministically.
|
|
- Add focused tests for valid refs and missing, foreign, non-self, and reversed
|
|
refs, plus source checkpoint/debug round trips.
|
|
|
|
Exit criteria:
|
|
|
|
- every produced source unit has a validated self-reference; and
|
|
- source state preserves it through clone, debug, digest, and checkpoint paths.
|
|
|
|
### Stage 6: Engine-Owned Chunks and Workspace v2
|
|
|
|
Goal: finish the Zone-A source model and make its persisted compatibility break
|
|
explicit.
|
|
|
|
Tasks:
|
|
|
|
- Add `source.Chunk` with the fixed shape and update universal chunk contracts,
|
|
modules, validators, runner code, checkpoints, debug envelopes, and tests to
|
|
use it.
|
|
- Derive `Chunk.Ref` from the first and last included unit references. Validate
|
|
source identity, non-empty ordered units, contiguous boundary agreement, and
|
|
exact correspondence between the chunk ref and first/last unit refs.
|
|
- Remove `contracts.SourceChunk`, `StartUnitID`, and `EndUnitID` after all
|
|
consumers migrate. Do not keep aliases.
|
|
- Advance `workspace.WorkspaceSchemaVersion` to `notarius.workspace.v2`. Make
|
|
loader behavior explicitly classify v1 as incompatible and recompute while
|
|
leaving files untouched.
|
|
- Update checkpoint identity/digest tests and operations documentation for the
|
|
one-time v1 resume miss.
|
|
|
|
Exit criteria:
|
|
|
|
- no production code imports a framework-owned chunk type;
|
|
- chunk provenance round-trips exactly; and
|
|
- v1 workspaces are safely ignored while v2 workspaces reuse successfully.
|
|
|
|
### Stage 7: Artifact and Codec Foundation
|
|
|
|
Goal: add the typed primitives and prove strict serialization independently of
|
|
production lanes.
|
|
|
|
Tasks:
|
|
|
|
- Add the fixed artifact types, codec interface, schema digest helper, and clone
|
|
helpers under `internal/framework/contracts`.
|
|
- Add `ArtifactCodecRegistry` to `pipeline.Registries` and `ModuleCatalog`.
|
|
- Implement generic codec registration and private erasure/type tracking.
|
|
- Validate registration metadata and duplicates. Ensure erased encode/decode
|
|
returns typed errors, never reflection panics.
|
|
- Use two small test artifact types to cover registration, exact type identity,
|
|
deterministic encoding, strict decoding, cloning, duplicate kind rejection,
|
|
and schema metadata/digest behavior.
|
|
- Wire the new empty registry through CLI/test registry constructors without
|
|
changing production lane execution.
|
|
|
|
Exit criteria:
|
|
|
|
- codecs for heterogeneous test types can coexist and safely round-trip through
|
|
erased framework storage; and
|
|
- current production behavior remains on the legacy raw path and unchanged.
|
|
|
|
### Stage 8: Typed Contracts, Variants, and Resolution
|
|
|
|
Goal: resolve a complete type-compatible lane before executing it.
|
|
|
|
Tasks:
|
|
|
|
- Add the typed stage, typed validator, chunk validator, serialized validator,
|
|
and provenance wrapper contracts from the fixed decisions.
|
|
- Extend extractor specs with artifact kind/type. Convert merger, normalizer,
|
|
and typed validator registries to artifact-kind variants while retaining
|
|
serialized-validator registration by key.
|
|
- Implement free generic registration helpers and private erased entries.
|
|
- Extend lane resolution to derive kind from extractor, require its codec,
|
|
select exact merger/normalizer/validator variants, and include artifact/schema
|
|
identity in resolved lanes and pipeline digest.
|
|
- Keep legacy registration helpers only as explicitly named transitional APIs;
|
|
do not let a raw registration satisfy a typed lane.
|
|
- Add composition tests with two artifact types and heterogeneous lanes. Cover
|
|
missing codec, missing variant, Go-type mismatch, duplicate variant, wrong
|
|
validator kind, stable resolution order, and digest changes on schema identity
|
|
or schema digest changes.
|
|
|
|
Exit criteria:
|
|
|
|
- heterogeneous typed test lanes resolve without module-facing erasure;
|
|
- every incompatible selection fails before execution; and
|
|
- existing raw production lanes continue to resolve only through their visible
|
|
transitional path.
|
|
|
|
### Stage 9: Preparation and Construction Foundation
|
|
|
|
Goal: construct and validate an entire resolved pipeline before source work.
|
|
|
|
Tasks:
|
|
|
|
- Add `ModuleDependencies`, `BuildRequest`, `PreparedPipeline`, and `Prepare` as
|
|
specified.
|
|
- Extend registry entries/specs with option validation and construction
|
|
functions. Supply adapters for legacy zero-argument constructors during the
|
|
migration.
|
|
- Call option validation for every selected module and validator during
|
|
resolution/config validation. Reject unknown fields and contextualize errors.
|
|
- Have preparation construct all selected components in fixed order and retain
|
|
immutable prepared lane executors.
|
|
- Update the runner API so `Run` receives a prepared pipeline. At the CLI, create
|
|
the shared scheduled LLM client, prepare, and only then invoke the runner.
|
|
- Prove with fakes that malformed options, missing required LLM dependencies,
|
|
and late-component construction failures occur before the input adapter's
|
|
`Parse` method.
|
|
|
|
Exit criteria:
|
|
|
|
- all components are constructed before source work;
|
|
- preparation errors identify exact scope and perform no operations; and
|
|
- legacy production modules still run through temporary construction adapters.
|
|
|
|
### Stage 10: Migrate Universal Modules to Construction
|
|
|
|
Goal: remove legacy option/dependency handling from input, chunk, and output.
|
|
|
|
Tasks:
|
|
|
|
- Give Seriatim input, generic units chunking, D&D scenes chunking, generic JSON
|
|
output, and all applicable chunk validators implementation-owned option
|
|
structs and strict decoders.
|
|
- Build those implementations with decoded options and injected dependencies.
|
|
Require the LLM client for D&D scenes; keep deterministic implementations
|
|
independent of it.
|
|
- Remove `Options` and `LLMClient` from the corresponding operation requests.
|
|
Retain per-run source, reference, profile, session, and metadata fields.
|
|
- Update registrars and focused tests. Verify options are decoded once during
|
|
preparation and operation methods do not inspect raw maps.
|
|
|
|
Exit criteria:
|
|
|
|
- no universal production module parses raw options during execution; and
|
|
- every universal LLM call uses the injected shared client.
|
|
|
|
### Stage 11: Canonical D&D Model, Codec, and Typed Extractor
|
|
|
|
Goal: establish the first production `T` and its extraction boundary.
|
|
|
|
Tasks:
|
|
|
|
- Add canonical spell types at the D&D root using engine-owned source refs.
|
|
- Add `internal/modules/dnd/codec/spells`, register kind `dnd/spell-list`, and
|
|
make it own the existing durable `dnd_spells.v1.json` schema and strict stable
|
|
encoding.
|
|
- Keep the private spell-extraction LLM DTO and
|
|
`dnd_spells_llm.v1.json` in the extractor package. Map canonicalized DTO values
|
|
to `dnd.SpellList` and do not expose the DTO to validators or the codec.
|
|
- Convert the extractor to `Extractor[dnd.SpellList]`, construction-time options
|
|
and dependency injection. Preserve prompt assets, retries, warnings, evidence,
|
|
and LLM response validation.
|
|
- Register the codec and typed extractor from the D&D registrar.
|
|
- Add codec compatibility tests against existing durable fixtures and tests
|
|
proving LLM schema ownership is separate from durable schema ownership.
|
|
|
|
Exit criteria:
|
|
|
|
- the typed extractor returns the canonical domain model;
|
|
- codec output is semantically identical to the maintained durable spell JSON;
|
|
and
|
|
- no downstream production consumer is switched until the next stages.
|
|
|
|
### Stage 12: Typed Validators and Generic Typed Strategies
|
|
|
|
Goal: complete all typed components required by the D&D lane.
|
|
|
|
Tasks:
|
|
|
|
- Convert D&D spell shape, source-reference, and source-relatedness validators
|
|
to `TypedValidator[dnd.SpellList]` with construction-time options/dependencies.
|
|
- Remove JSON reparsing from those validators. Remove the duplicate
|
|
`spellpayload` package after its final consumer migrates.
|
|
- Convert `valid_json` and `valid_json_schema` to serialized validators. Register
|
|
them so the framework uses the D&D codec when they occur in the spell chain.
|
|
- Implement generic typed append-order merge and no-op normalize. In the D&D
|
|
registrar, register D&D variants using a spell-list append function and
|
|
`noop[dnd.SpellList]`.
|
|
- Convert always-accept/reject into explicit chunk and typed variants and
|
|
register the D&D variants without changing their keys. Verify serialized
|
|
validators operate on the framework encoding at chunk and the codec encoding
|
|
at artifact stages.
|
|
- Test typed validator requests, source refs, relatedness LLM injection, generic
|
|
strategy reuse with a second test type, default chain order, and rejection
|
|
behavior.
|
|
|
|
Exit criteria:
|
|
|
|
- every selected spell-lane component has a compatible D&D typed variant;
|
|
- generic packages import no D&D code; and
|
|
- the duplicate validator payload model and inter-validator JSON parsing are
|
|
gone.
|
|
|
|
### Stage 13: Typed D&D Runner Vertical Slice
|
|
|
|
Goal: execute one complete production lane as `dnd.SpellList` while retaining
|
|
temporary raw output/checkpoint adapters.
|
|
|
|
Tasks:
|
|
|
|
- Implement the erased typed lane executor and runner path for extract, stage
|
|
retries, typed/serialized validation, merge, and normalize.
|
|
- Keep accepted extract values indexed by chunk and pass them to merge in source
|
|
order. Preserve warnings and rejections in stable scope order.
|
|
- Add narrow transitional adapters from typed stage outputs to the existing raw
|
|
checkpoint/debug/output envelopes. These adapters must use the registered
|
|
codec and be named/commented as migration-only.
|
|
- Route the production D&D lane through the typed path; leave no production raw
|
|
D&D stage module registered in parallel.
|
|
- Add end-to-end and checkpoint-disabled tests proving the maintained D&D output
|
|
and outcome semantics are unchanged. Add incompatible-lane tests proving
|
|
failure occurs before input.
|
|
|
|
Exit criteria:
|
|
|
|
- D&D values remain typed from extractor through normalize and typed validation;
|
|
- the runner's only D&D erasure is its private lane adapter and explicit codec
|
|
boundary; and
|
|
- durable output remains unchanged through the transitional adapter.
|
|
|
|
### Stage 14: Typed Checkpoints, Debugging, and Output
|
|
|
|
Goal: move every serialization side effect and the final Zone-C boundary to the
|
|
codec model.
|
|
|
|
Tasks:
|
|
|
|
- Change runner/output contracts so final normalized results are
|
|
`SerializedArtifact` values. Update generic JSON output without importing D&D.
|
|
- Preserve logical file names, index fields, media types, manifest contents, and
|
|
durable spell JSON.
|
|
- Change extract, merge, and normalize checkpoints to store codec bytes plus
|
|
artifact kind, schema ID, version, and schema digest. Decode reused values
|
|
back to `T` before the next stage.
|
|
- Implement safe invalidation for missing/mismatched codecs and decode failure,
|
|
with explicit checkpoint-event reasons.
|
|
- Change typed debug envelopes to serialize through the codec, preserving opt-in
|
|
and redaction behavior. Use stable codec bytes for artifact digests.
|
|
- Remove the transitional raw output/checkpoint/debug adapters introduced in
|
|
Stage 13 after all tests use the typed boundaries.
|
|
|
|
Exit criteria:
|
|
|
|
- typed values round-trip at every stage checkpoint;
|
|
- incompatible checkpoint artifacts recompute safely;
|
|
- output and debug code are domain-neutral; and
|
|
- no D&D typed lane depends on a raw-boundary adapter.
|
|
|
|
### Stage 15: Finish Construction Migration and Remove Raw Contracts
|
|
|
|
Goal: leave one production extension system rather than parallel raw and typed
|
|
models.
|
|
|
|
Tasks:
|
|
|
|
- Migrate any remaining merge, normalize, extract, and validator implementations
|
|
to construction-time option decoding and dependency injection.
|
|
- Remove LLM clients and raw option maps from all remaining operation requests.
|
|
- Remove legacy raw extractor/merger/normalizer/validator contracts,
|
|
constructors, registry entries, `RawPayload`, `ResponseSchema` if superseded,
|
|
raw clone helpers, raw checkpoint envelopes, and migration-only adapters.
|
|
- Remove dead duplicate models and compatibility helpers. Search for production
|
|
references to old stage-oriented paths and raw Zone-B types.
|
|
- Keep serialized artifacts only at codec, checkpoint/debug, and output
|
|
boundaries.
|
|
|
|
Exit criteria:
|
|
|
|
- no production Zone-B handoff uses JSON bytes, `RawPayload`, or `any`;
|
|
- all production configuration options are validated before execution;
|
|
- all production LLM users receive the one injected client; and
|
|
- there is no legacy production registration path.
|
|
|
|
### Stage 16: Stage-Worker Configuration
|
|
|
|
Goal: add the decided scheduling control without changing runner execution yet.
|
|
|
|
Tasks:
|
|
|
|
- Add `StageWorkers map[string]int` to effective concurrency configuration and
|
|
`stage_workers` to the version-2 YAML shape. Deep-clone the map.
|
|
- Accept only `extract`, reject unknown or empty keys, default missing extract to
|
|
effective `total_llm`, and validate the inclusive range
|
|
`1..total_llm` after file/environment precedence resolves.
|
|
- Add `NOTARIUS_STAGE_WORKERS_EXTRACT` with the existing environment precedence
|
|
and integer error style.
|
|
- Preserve redaction/effective-config diagnostics and version 2.
|
|
- Update `docs/config.md` and maintained examples that intentionally demonstrate
|
|
concurrency. Do not add the field to every example when the default conveys
|
|
the intended behavior.
|
|
- Add file, default, merge/precedence, environment, unknown-key, boundary, and
|
|
redacted-effective-config tests.
|
|
|
|
Exit criteria:
|
|
|
|
- every run has a validated effective extract worker count;
|
|
- omitted configuration preserves current effective behavior at the default
|
|
`total_llm: 1`; and
|
|
- current configuration documentation owns the implemented contract.
|
|
|
|
### Stage 17: Concurrent Lane and Extract Scheduling
|
|
|
|
Goal: implement bounded concurrent lanes while preserving deterministic public
|
|
behavior and the global LLM invariant.
|
|
|
|
Tasks:
|
|
|
|
- Add the fixed-size run-wide worker pool and central round-robin dispatcher.
|
|
Bound queued work so dispatch applies backpressure; do not enqueue the whole
|
|
run into unbounded memory.
|
|
- Treat extract, retries, and extract validators as one job. Publish immutable
|
|
results to a coordinator indexed by lane and chunk.
|
|
- Start each lane's serial merge/normalize continuation only after all its
|
|
extract jobs are terminal. Permit different lane continuations to overlap.
|
|
- Make the coordinator the sole writer of aggregate output, manifest,
|
|
checkpoint-event collection, warnings, and rejections. Use attempt-specific
|
|
debug paths and synchronize any recorder state that remains shared.
|
|
- Implement rejection, cancellation, stable sorting, deterministic primary
|
|
error selection, and output gating exactly as specified under Concurrency.
|
|
- Audit every concurrently reused extractor, validator, codec, LLM/debug wrapper,
|
|
checkpoint loader/recorder, and manifest metadata provider. Make production
|
|
implementations immutable or add narrowly scoped synchronization.
|
|
- Add deterministic barrier-controlled tests that force reverse completion
|
|
order, simultaneous failures, parent cancellation, rejections mixed with
|
|
successes, lane continuation overlap, and undispatched-job cancellation.
|
|
- Add instrumented integration tests issuing calls from multiple lanes, retries,
|
|
and LLM-backed validators. Assert provider calls never exceed
|
|
`total_llm`, extract jobs never exceed the effective extract worker count, and
|
|
both limits are exercised independently.
|
|
- Run `go test -race ./...` and eliminate races rather than weakening tests.
|
|
|
|
Exit criteria:
|
|
|
|
- lanes and extract jobs actually overlap when configured above one;
|
|
- job and provider-call limits are independently enforced;
|
|
- public results and primary errors are identical across forced completion
|
|
orders; and
|
|
- full race testing passes.
|
|
|
|
### Stage 18: Documentation, Cleanup, and Final Verification
|
|
|
|
Goal: make the implemented repository and its canonical documentation agree,
|
|
then close the focused roadmap.
|
|
|
|
Tasks:
|
|
|
|
- Review every current-behavior document routed by `docs/development.md` and
|
|
update only its owned facts: architecture and dependency direction, package
|
|
inventory, pipeline resolution/preparation/execution, typed module contracts,
|
|
LLM scheduling, configuration, checkpoint compatibility, diagnostics,
|
|
operations, and durable integration contracts.
|
|
- Verify maintained examples and copyable files against the implementation.
|
|
- Remove stale old package paths, raw-contract terminology, and superseded
|
|
future-work entries. Validate all changed documentation links.
|
|
- Update ADR consequences only in ways permitted for accepted ADR metadata; do
|
|
not edit accepted decision text. Record a new superseding ADR if final code
|
|
required an architectural change.
|
|
- Mark the feature roadmap implemented and reduce this implementation plan to a
|
|
concise completion record, or move it to the repository's established
|
|
completed-roadmap location if one exists. Do not let this file become a second
|
|
current-behavior reference.
|
|
- Run final checks:
|
|
|
|
```sh
|
|
go test ./...
|
|
go test -race ./...
|
|
go vet ./...
|
|
go build ./cmd/notarius
|
|
```
|
|
|
|
Exit criteria:
|
|
|
|
- all feature-roadmap completion criteria are met;
|
|
- no stale production paths or legacy typed/raw bridge remain;
|
|
- current documentation and examples describe only implemented behavior; and
|
|
- all final validation commands pass.
|
|
|
|
## Open Questions
|
|
|
|
None. The feature-policy decisions and the implementation choices required to
|
|
begin each stage are resolved above. If implementation evidence contradicts one
|
|
of the accepted architectural decisions, stop and handle that as an ADR change
|
|
or supersession rather than treating it as an implicit implementation choice.
|