Add implementation roadmap to clean up D&D extract module validation issues

This commit is contained in:
2026-07-22 09:19:13 -05:00
parent 906d97b391
commit 23c55f8925
2 changed files with 129 additions and 693 deletions

View File

@@ -1,399 +1,174 @@
# Implementation Plan: Ordered Pipeline Follow-Up
# D&D Validation-Boundary Alignment Implementation Plan
## Status
Status: Proposed.
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](../development.md).
Implement this plan in order. It repairs the immediate combat extraction
failure, makes D&D validation diagnostics consistently domain-owned, and then
aligns the spell and NPC private response boundaries with the same policy.
## Purpose
Do not change durable artifact schemas, artifact kinds, public Go types,
framework retry behavior, checkpoint formats, or the scene chunker. Existing
v1 private-schema identities may be corrected in place because Notarius has not
been run in production. Changed prompt and schema content will invalidate
development checkpoints through existing fingerprints.
Record the work that closed the correctness, observability, test, and
maintainability gaps in the implemented
[Ordered Pipeline Steps](ordered-pipeline-steps.md) 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.
## Cross-Stage Decisions
## Background
Private LLM schemas own the transport envelope: required fields, JSON types,
nullability, array/object shape, and unknown-field rejection. Deterministic
domain validators own semantic rules: supported enum values, nonblank values,
required non-empty collections, positive and resolvable source units,
catalog/identity policy, and normalized invariants.
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.
For typed D&D artifacts, `generic/valid_json` remains first as a representation
sanity check. All deterministic domain validators that can reject a candidate
run next. `generic/valid_json_schema` runs after them as a durable-schema
backstop, followed by warning-only relatedness validators. This order gives
expected candidate failures bounded domain reason codes while retaining a final
check that typed encoding conforms to the durable contract.
The completed follow-up addressed these narrower gaps:
Do not expose raw JSON Schema errors or candidate values through the generic
validator. Do not add tests that require particular words or phrases to remain
in prompt prose.
- 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.
## Stage 1: Repair Combat Extraction
### 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.
- Update the combat extraction instructions to enumerate the complete allowed
values:
- `turn_kind`: `turn`, `reaction`, `legendary_action`, `lair_action`,
`other`;
- action `category`: `attack`, `spell`, `movement`, `item`,
`ability_check`, `saving_throw`, `condition`, `other`.
- Keep the private combat schema structurally permissive and the durable schema
strict. Do not restore enum, minimum, `minLength`, or `minItems` constraints
to the private schema.
- Reorder the combat extraction default chain to:
`generic/valid_json`, combat shape, combat source references,
`generic/valid_json_schema`, combat source relatedness.
- Reorder the combat normalization default chain to:
`generic/valid_json`, combat shape, normalized invariants, combat source
references, `generic/valid_json_schema`, combat source relatedness.
- Preserve configured validator overrides as authoritative; only production
default composition changes.
### 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.
- Add an assembled combat pipeline case whose raw LLM response contains an
unsupported turn kind or action category. Exhausted retries must produce a
non-fatal `invalid_combat_turn_shape` rejection owned by the combat shape
validator, not `json_schema_invalid` or a framework error.
- Retain coverage that a later valid retry succeeds and discarded-attempt
warnings/rejections do not become durable.
- Update registrar contract tests to assert the new extraction and
normalization order.
- Rely on behavioral enum/schema tests and prompt fingerprint coverage; do not
add prompt-word change-detector tests.
### Completion Gate
### Completion Check
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.
Run `go test ./internal/modules/dnd/extract/combatturns
./internal/modules/dnd/validate/combatturns/... ./internal/modules/dnd/register
./internal/modules/integration` and `git diff --check`.
## 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.
## Stage 2: Make D&D Validator Ordering Consistent
### 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.
- Move `generic/valid_json_schema` behind all rejecting domain validators in
every spell and NPC extraction and normalization default chain:
- spell extraction/normalization: shape, catalog, source references, schema,
then source relatedness;
- NPC extraction: shape, source references, schema, then source relatedness;
- NPC normalization: shape, identity, source references, schema, then source
relatedness.
- Keep `generic/valid_json` first and warning-only source relatedness last.
- Do not change validator implementations, reason codes, warning promotion,
retry counts, or user-provided chain order.
- Document the default-chain policy in the pipeline/module internals: domain
validators diagnose expected semantic failures and the generic schema
validator is the final rejecting representation backstop.
### 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.
- Update production registrar tests for every affected chain.
- Add one representative spell and NPC assembled rejection proving that a
domain-invalid but encodable candidate is attributed to the owning domain
validator rather than the generic schema validator. Do not duplicate each
validator package's existing case matrix at integration level.
- Confirm explicitly configured validator chains retain their exact configured
order.
### Completion Gate
### Completion Check
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.
Run `go test ./internal/modules/dnd/register ./internal/modules/integration
./internal/framework/pipeline` and `git diff --check`.
## 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.
## Stage 3: Align Spell and NPC Private Response Boundaries
### 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.
- Revise the existing v1 spell and NPC private schemas in place:
- retain required fields, JSON types, array/object structure,
`additionalProperties: false`, and omission of framework-assigned fields;
- remove `minLength`, `minItems`, and positive-number `minimum` constraints;
- leave the durable spell and NPC schemas unchanged.
- Replace `shared.UnitRef` in the private spell and NPC response DTOs with
integer candidates so zero and negative unit IDs survive decoding and mapping
into `source.SourceRef` for deterministic source validation.
- Update canonicalization and ordering helpers to operate on candidate integers
without repairing invalid values. Valid positive IDs retain current output,
ordering, and exact-deduplication behavior; invalid ranges remain available
to validators.
- Keep malformed JSON, missing/unknown fields, wrong JSON types, and
non-integer source IDs as LLM-boundary errors.
- Update LLM/module internals and the spell/NPC integration contracts to state
the structural-private/semantic-validator ownership boundary.
### 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.
- For each private schema, prove structurally valid candidates with blank
strings, empty required collections, and nonpositive unit IDs pass the
private schema, while missing fields, unknown fields, and wrong JSON types do
not.
- Through raw-JSON LLM fakes, prove semantic values survive decoding and mapping
without repair.
- Add representative assembled cases showing:
- blank or empty spell/NPC fields are rejected by the appropriate shape
validator;
- nonpositive or nonexistent unit IDs are rejected by the appropriate source
validator; and
- exhausted validation retries remain non-fatal rejected outputs.
- Preserve existing valid mapping, source-position ordering, deduplication,
catalog, identity, checkpoint-fingerprint, and durable codec tests.
### Completion Gate
### Completion Check
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.
Run `go test ./internal/modules/dnd/extract/spells
./internal/modules/dnd/extract/npcs ./internal/modules/dnd/validate/spells/...
./internal/modules/dnd/validate/npcs/... ./internal/modules/integration` and
`git diff --check`.
## Stage 4: Complete Recompute CLI And Recovery Acceptance Coverage
## Final Verification
### Objective
Run:
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`:
```sh
```text
git diff --check
go test ./...
go vet ./...
go build ./cmd/notarius
go test -race ./internal/modules/dnd/... ./internal/framework/pipeline ./internal/cli ./internal/modules/integration
```
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.
Review current-behavior documentation for stale statements that private spell,
NPC, or combat schemas own semantic validation. Confirm the scene schema and
prompt remain unchanged: their enumerations are explicitly communicated and
scene-plan construction has a distinct structural mapping boundary.
## 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.
None. The stages above define the validation ownership, default ordering,
compatibility policy, diagnostic behavior, and test boundaries required for
implementation.

View File

@@ -1,339 +0,0 @@
# Scope: Ordered Pipeline Steps
## Status
Implemented. This document preserves the bounded feature policy, architecture
choices, acceptance criteria, and exclusions. Current behavior belongs in the
canonical [CLI](../cli.md), [Configuration](../config.md),
[Operations](../operations.md), and [internal pipeline](../internal/pipeline.md)
documentation rather than in this roadmap.
## Policy Recommendation
Treat ordered pipeline steps, generated artifact references, and
dependency-aware checkpoint reuse as one coherent platform capability. The D&D
proving workflow produces accepted normalized NPC output first and then supplies
it to spell extraction, combat-turn extraction, and combat-turn normalization.
## Intended Outcome
A configured pipeline may contain multiple ordered steps while retaining one
pipeline-wide input, chunk plan, output, worker budget, LLM scheduler, run
manifest, and failure boundary. Every artifact lane still follows the fixed
extract, validate, merge, validate, normalize, and validate lifecycle. Steps
add explicit barriers between groups of lanes; they do not create arbitrary
stage graphs.
An accepted normalized artifact from an earlier step may be bound explicitly
to declared reference slots in a later step. The framework remains
domain-neutral, and generated references remain contextual material rather than
source evidence.
## Fixed Product And Architecture Decisions
### Pipeline shape
- Input parsing and chunk planning remain pipeline-wide and execute once.
- A step contains one or more artifact lanes. Step order is configuration order.
- Lanes within a step remain independent and may use the existing bounded
concurrency model.
- A later step cannot begin until every lane in the current step is terminal
and every generated artifact it requires is accepted and available.
- Public artifact and failure ordering is step order followed by deterministic
lane and source-chunk order, never completion order.
- Output encoding occurs once, after every step succeeds.
- This is not an arbitrary DAG, a general workflow language, concurrent
cross-lane reconciliation, or permission for modules to invoke other modules.
### Configuration model
Existing single-step pipelines remain valid. A top-level `artifacts` map is
treated as an implicit step with stable ID `default`. A pipeline may configure
either `artifacts` or `steps`, but not both. Explicit steps must be non-empty
and have unique, trimmed, non-empty IDs. Artifact lane IDs must remain unique
across the entire pipeline so output paths, selectors, manifests, errors, and
checkpoint scopes remain unambiguous.
The target configuration shape is:
```yaml
pipelines:
dnd-session:
input: seriatim
chunk: generic
steps:
- id: identify-npcs
artifacts:
npcs:
extract: dnd/npcs
normalize: dnd/npcs
- id: grounded-events
references:
npcs:
artifact:
step: identify-npcs
lane: npcs
artifacts:
spells:
extract: dnd/spells
normalize: dnd/spells
combat:
extract: dnd/combat-turns
normalize: dnd/combat-turns
output: json
```
Existing scalar reference values continue to represent external file paths.
The structured `artifact` form identifies accepted normalized output from one
earlier step and lane. Generated artifact bindings are allowed at step scope or
at an individual module target; they are not inferred from module keys, lane
names, slot names, or domain knowledge.
A step-scoped reference applies automatically to every selected target in that
step that declares the slot. In the example, one `npcs` binding reaches the
spell extractor plus the combat extractor and normalizer. A target-local
binding is used when only one module should consume the artifact.
Pipeline-level external references remain defaults. Step-local external
references override pipeline-level external defaults, and target-local
external references retain their existing precedence. A generated reference
and an external reference may not resolve to the same effective target slot;
configuration or a runtime override that creates that conflict is invalid.
Likewise, a step-scoped and target-local generated binding cannot both target
the same effective slot.
Each effective target slot accepts at most one producer. One producer may fan
out to multiple compatible slots in a later step. Aggregating several producer
artifacts into one slot is outside this scope.
Reference-slot specs gain optional generated-artifact compatibility metadata.
A generated binding is allowed only when the consumer slot declares the
producer's artifact kind; the producer's registered codec supplies the exact
schema identity and media type used for the handoff. The D&D `npcs` consumer
slots declare the normalized NPC-list artifact kind. Existing external-file
slots and bindings retain their current behavior and do not acquire an artifact
kind merely because their bytes happen to decode as one.
### Resolution and preparation
Resolution validates the complete ordered structure before source processing.
It must reject duplicate identities, missing producers, same-step or forward
references, undeclared slots, reference conflicts, and incompatible artifact
kind, schema, media type, or cardinality constraints that are statically
discoverable. Size is checked when canonical producer bytes exist at handoff.
Ordered steps make cycles structurally impossible; resolution must not
introduce a general graph scheduler to rediscover their order.
The resolved pipeline and its digest include step order, step IDs, lane
membership, generated-reference topology, producer identity, consumer targets,
and existing module and validator policy. Cloning, redaction, canonical JSON,
debug summaries, and manifests preserve the same structure without reference
content or secrets.
All modules and validators are still selected, option-validated, and
constructed before source parsing. Generated content cannot be supplied during
construction because it does not exist yet. The framework therefore augments
the existing operation-request `References` at the step boundary. Consumers
that currently assume an NPC registry is construction-only must accept the
generated registry from their operation request without deferring general
module construction until after upstream work.
Only validation that inherently depends on generated bytes may occur at the
handoff. A handoff validation failure is a contextual framework error and fails
the run before any consumer in that step begins.
### Generated artifact handoff
Only accepted normalized output may cross a step boundary. Raw extraction
responses, rejected artifacts, merge intermediates, and validator diagnostics
cannot be bound as references.
The framework serializes the producer through its registered canonical artifact
codec and constructs one immutable reference item containing:
- the declared target slot;
- canonical artifact bytes and media type;
- artifact kind and schema ID, name, version, and schema digest;
- canonical content digest and size; and
- producer pipeline, step, lane, and module provenance.
A generated binding requires exactly one accepted normalized artifact from its
producer lane. No artifact is a missing dependency, while more than one is a
cardinality error; a typed collection such as an NPC list remains one artifact.
Combining several normalized outputs into one reference is aggregation and is
outside this scope.
The existing slot contract remains authoritative for accepted media types,
maximum size, and cardinality. Generated content is cloned at ownership
boundaries and never exposed through a filesystem path. Manifests and debug
summaries record identities and bounded provenance, not artifact content.
Configuring a generated binding makes that dependency required even when the
consumer module declares the underlying slot optional. An accepted artifact
whose domain collection is empty is still a valid artifact and may be handed
off. If the producer has no accepted normalized artifact, the entire run fails
with a deterministic dependency error and no later step begins.
### Checkpoint reuse and selective recomputation
Generated references participate in downstream checkpoint dependencies by
artifact kind, complete schema identity, media type, and canonical content
digest. The pipeline digest protects topology; stage dependency fingerprints
protect the exact upstream artifact consumed. The runner must never combine a
new or changed producer with stale dependent output.
Ordinary resume may progressively decode compatible producer and consumer stage
checkpoints through the registered codec. Selective recomputation may hydrate a
required unselected producer directly from its accepted normalized artifact;
its extract and merge state are not prerequisites. Missing, rejected, corrupt,
incompatible, or changed accepted state stops the run before dependent
execution rather than implicitly rerunning the producer. Independent work
remains reusable.
The operator control `--recompute-step <step-id>` has these semantics:
- it requires checkpoint recording and `--resume`;
- the selected step and all transitive dependents execute rather than reuse
their checkpoints;
- valid required predecessors and unrelated work remain reusable;
- the recompute selection affects loader decisions, not the persistent
checkpoint identity of otherwise identical work; and
- the command fails before dependent execution if a required predecessor has
no reusable accepted artifact.
Existing `--only` behavior remains unchanged for implicit single-step
pipelines. Combining `--only` with explicit multi-step pipelines is outside
this scope and should be rejected with actionable guidance rather than given
implicit dependency-expansion semantics.
Checkpoint events, manifests, and diagnostics distinguish executed, reused,
forced-recomputed, and dependency-invalidated work. Invalidation reasons are
bounded, deterministic, and free of reference content, local paths, or secrets.
Old checkpoint state need not be migrated; it must produce a safe, explicit
cold miss rather than an error or unsafe reuse.
### Failure, cancellation, and concurrency
The existing run-wide worker and provider-call limits apply across every step.
Workers may be reused between steps, but concurrency cannot cross a step
barrier. A framework error cancels started work using the existing bounded
drain behavior and prevents later steps and output encoding. Rejections remain
recorded outcomes, but failure to produce a normalized artifact required by a
generated binding escalates to the run-level dependency error described above.
The failed manifest retains completed upstream outcomes, step and lane
provenance, rejections, checkpoint events, and the dependency failure without
embedding generated artifact content.
## D&D Proving Workflow
The production acceptance workflow has two explicit steps:
1. `identify-npcs` runs the NPC lane through normalization and its complete
validator policy.
2. `grounded-events` receives the canonical NPC artifact in its step-scoped
`npcs` reference and runs spell and combat-turn lanes. The binding reaches
spell extraction, combat-turn extraction, and combat-turn normalization.
Spell and combat-turn lanes may execute concurrently after the handoff. NPC
content may ground names and identities but cannot establish a spell cast or
combat event; source units remain the only event evidence.
The maintained workflow uses one ordered-pipeline example instead of a manual
two-run NPC-to-spell or NPC-to-combat handoff. Existing module keys, artifact
contracts, reference slot names, prompt IDs, and D&D evidence policy remain
unchanged.
## Included Work
- Configuration parsing, validation, cloning, defaults, redaction, and
documentation for explicit steps and structured generated references.
- Domain-neutral resolved step, dependency, producer, and consumer identities.
- Generated-artifact compatibility metadata on reference-slot contracts,
including D&D NPC-list declarations for every `npcs` consumer.
- Step-aware preparation metadata and runner orchestration.
- Canonical codec handoff into existing reference request contracts.
- Required-dependency failure and bounded provenance behavior.
- Dependency-aware checkpoint reuse, invalidation, events, and selective step
recomputation.
- D&D NPC-first production composition for spell and combat-turn consumers.
- Refactoring the affected D&D consumers so generated NPC references are
available at operation time while retaining early static construction.
- Maintained examples and current architecture, configuration, CLI, operations,
internal, integration, and testing documentation.
- An ADR recording the bounded ordered-step extension to the fixed pipeline
architecture and its explicit rejection of a general DAG.
## Explicitly Out Of Scope
- D&D item extraction or any other new artifact lane.
- Cross-artifact NPC ID fields or artifact-schema migration machinery.
- Arbitrary DAGs, conditional branches, loops, joins, dynamic step creation, or
module-controlled scheduling.
- Multiple source inputs, per-step input adapters, per-step chunk plans, or
per-step output encoders.
- Aggregating multiple generated artifacts into one reference slot.
- Optional or best-effort generated dependencies; a configured dependency is
required in this scope.
- Prior-run or cross-pipeline generated references.
- `--only` dependency closure for explicit multi-step pipelines.
- Cross-lane reconciliation or domain concepts in the generic framework.
- Live-provider tests or model-quality changes to D&D prompts.
## Acceptance Criteria
The scope is complete when:
- all existing single-step configurations retain their current behavior;
- explicit step order and dependency topology resolve deterministically and
affect pipeline identity;
- invalid producer, consumer, conflict, ordering, type, schema, media, and
cardinality configurations fail before source processing when statically
discoverable, while content-size violations fail at handoff;
- no consumer step begins before all required generated artifacts are accepted,
canonicalized, and validated for its target slots;
- one producer artifact fans out safely to every compatible target selected by
a step-scoped binding;
- missing required producer output fails the complete run before dependent work;
- changing NPC output invalidates spell and combat-turn checkpoints while
leaving compatible independent work reusable;
- selective step recomputation executes exactly the selected dependency closure
and reports why work was executed, reused, or invalidated;
- the D&D ordered workflow supplies NPC content to spell extraction, combat-turn
extraction, and combat-turn normalization without treating it as evidence;
- completion timing cannot change public ordering, failure selection, or
dependency behavior;
- output, manifests, checkpoints, and debug artifacts contain the required
identities and provenance without leaking generated reference content; and
- repository-wide tests, vet, build, maintained-example checks, and
documentation validation pass.
## Testing Strategy
Tests should protect behavior and invariants rather than the implementation's
internal scheduler shape.
- Configuration contract tests own legacy shorthand, explicit step parsing,
source-form discrimination, conflicts, and redaction.
- Resolution tests own ordering, global lane uniqueness, dependency validation,
slot compatibility, fan-out, cloning, canonical JSON, and digest changes.
- Runner tests own step barriers, within-step bounded concurrency, stable
ordering, cancellation, required-producer failure, and immutable handoff.
- Checkpoint tests own producer decoding, exact dependency matching, transitive
invalidation, forced recomputation, cold misses, and bounded decisions.
- One CLI contract test should cover the recompute control and its invalid
combinations.
- One D&D integration test with offline fake LLM responses should prove the
complete NPC-to-spell-and-combat handoff, including combat normalization.
- Maintained configuration examples should be parsed and resolved through the
production catalog.
Do not add scheduler choreography tests, exact goroutine-count assertions,
complete manifest snapshots, exact diagnostic strings, or duplicated tests for
every invalid configuration at every layer. No test may require credentials or
a live model provider.
## Open Questions
None required to define this scope. Exact internal type names and implementation
decomposition are intentionally not feature-policy decisions.