21 KiB
Workstream 3 Implementation Plan
Purpose And Status
This is the executable plan for refactoring.md, Workstream
3: Canonical contracts and compatibility purge. Finding 1 of
contract-audit.md and rows C01/H43 of
audit-decisions.md own the approved decisions.
All seven stages are pending and must be implemented in order. Each is bounded
for one gpt-5.6-terra prompt. Workstreams 1 and 2 are complete. Workstreams
4 and later are out of scope: do not remove the run manifest, redesign runner
lifecycle, change analyze checkpoint timing, replace locks, decompose publish,
or add cleanup while following this plan.
Settled Implementation Decisions
- Delete deprecated representations rather than renaming, wrapping, or preserving them behind an internal compatibility facade. Historical releases and completed audits remain unchanged.
- Give both currently persisted local JSON manifest types an explicit required
schema_version:narratio.session-manifest.v1forManifestandnarratio.run-manifest.v1forRunManifest, with discoverable constants in packagemanifest. Constructors set it; save and load validate it; neither path fills in a missing or unsupported value. Workstream 4 may later delete the versioned run-manifest model. - Loaded current state may not acquire required schema, stage identity, stage records, or analyze authority through normalization. Representation- preserving normalization may remain.
- Every newly created
analyzestage record in either manifest usesAnalyzeStateContractVersion. An empty collection is valid current state. Version zero, an unknown version, or aggregate-only authority is rejected. - The only party input is a strict
narratio.party.v1document selected by required, campaign-ownedcampaign.inputs.party_file. Remove unversioned parties,campaign.inputs.players_file,session.inputs.players_file, andsession.inputs.party_file; strict YAML decoding rejects those keys. - Players remain the deterministic
narratio.players.v1projection generated from the party. Keep that prepared input, semantic identity, Notarius reference role, and operator visibility; it is never separately configured. - Simplify party runtime data to a canonical party and source provenance.
Remove
PartyMode, both mode constants, the compatibility-onlyPartyDocumentshell, andparty_legacy.go.ParsePartyreturns the canonical representation directly. - The only remote current selector is versioned
current/commit.jsonand its verified immutable commit. Pointer absence returns a typed current-commit-pointer-missing error with publish/republish guidance. Do not inspectcurrent/run_id.txtorcurrent/manifest.json. - Successful current-state loads always contain a validated non-nil pointer,
commit, and committed session manifest. Keep
CurrentPointerKeyand the immutable manifest key; remove legacy key names and duplicated fields. - Restore uses only objects declared by the selected commit and never lists a session prefix to reconstruct a snapshot. Preserve committed verification, manifest-last installation, conflicts, and root-scoped safety.
- Previous-session hydration resolves each requested source exactly once from
the commit's unique
published_outputdeclaration. Derive its relative destination from that declaration's immutable destination key. Do not consult current config or previous-manifest outputs for alternate paths and do not issue candidateExistscalls. - A built-in artifact is authoritative only when its succeeded producer stage has exactly one output matching registered source and kind, with safe path, producer run ID, and checksum. Verify checksum and content. Remove canonical- path and filename fallback; disk bytes alone never establish availability.
- Analyze authority comes only from the current versioned
AnalyzeArtifactscollection. Preserve fingerprints, dependencies, selection, force behavior, and existing session/run projections until their later workstreams; remove only aggregate-only and incidental-file compatibility here. - Extraction availability continues to require current input and receipt identity, canonical immutable paths, a complete configured source set, checksums, contracts, and Notarius provenance. The manifest schema boundary rejects older records; add no extraction migration reader.
- Unsupported state errors identify the missing/received and supported version and provide concise migration guidance. Tests assert error categories and small stable fragments, not full prose.
- Current docs must direct operators to replace legacy party config, move aside and regenerate unsupported local state, restore a current committed snapshot when authoritative, or republish healthy local state. Do not rewrite historical documents.
- Mark only Workstream 3
Complete, and only after all stages and final validation succeed.
Instructions For Every Stage
Before each stage:
- Read
docs/development.md, every file underdocs/policy/, this plan, Workstream 3 plus dependency/target sections ofrefactoring.md, Finding 1 ofcontract-audit.md, and rows C01/H43 ofaudit-decisions.md. - Inspect named implementation and focused tests. Prefer the codebase graph for Go discovery and text search for fields, fixtures, examples, and docs.
- Confirm worktree state and preserve unrelated changes. Do not enter a later workstream because adjacent code would also benefit.
During every stage:
- Keep the repository building and focused tests passing. Temporary current-only migration structure is allowed only when a later stage explicitly removes it; add no compatibility shim or automatic migration reader.
- Preserve package ownership, strict deterministic decoding, group workspace behavior, bounded I/O, secret redaction, remote verification, restore safety, and Linux/macOS/Windows support.
- Keep tests offline and behavior-focused. Delete tests only with their retired contract. Update canonical current-behavior docs with the code change.
- Run
gofmton changed Go, focused tests, andgit diff --check. Mark a stageCompletedonly after all exit criteria pass.
Stage 1 — Establish Explicit Current Manifest Schemas
Status: Completed
Goal
Create an unambiguous local-state boundary before removing downstream legacy branches.
Required Work
- Add the settled schema constants and required
schema_versionfields ininternal/manifest/manifest.goandrun_manifest.go; set them inNewandNewRun. - Require exact versions in
LocalStore.Save,LoadReader,SaveRun, andLoadRunReader. Missing/unknown versions return a clear migration error. Saving a hand-built zero-value manifest fails rather than backfilling. - Require a non-nil stage map; reject nil stage entries and embedded stage names that are empty or differ from their map keys. Remove load normalization that synthesizes these required markers, retaining only harmless ordering or cloning.
- Make session and run
ensureStageinitialize the analyze contract version and an empty collection foranalyze. MakevalidateAnalyzeStageStaterequire that version for every present analyze stage regardless of lifecycle status. Delete version-zero acceptance. - Update current test construction to use constructors or explicit markers. Cover missing/supported/unknown versions, nil/mismatched stages, newly created analyze state, empty current collections, and aggregate-only rejection.
- Update
docs/internal/manifest.md,docs/internal/artifacts.md,docs/operations.md, anddocs/troubleshooting.mdwith schema and local recovery guidance. Leave party/remote removal for their stages.
Exit Criteria
go test ./internal/manifest ./internal/artifacts ./internal/stage ./internal/appsucceeds.- Both manifest kinds round-trip only with exact supported versions.
- A present analyze stage cannot round-trip without current per-artifact versioning; an empty current collection can.
- Load does not synthesize required stage identity or authority.
git diff --checksucceeds.
Stage 2 — Reject Unversioned Party Documents
Status: Completed
Goal
End interpretation of unversioned party files before removing the configuration fields and mode shell that supported them.
Required Work
- Make
ParsePartyrequire exactlyPartySchemaVersionin the first and only YAML document. Missing/unknown versions identify the supported schema; retain strict malformed, duplicate, ambiguity, and trailing-document checks. - Delete
classifyLegacyPartyDocument,resolveLegacyParty, andparty_legacy.go. For this stage only, the resolved mode shell may remain so all packages compile, but production cannot construct legacy mode. - Simplify
resolvePartyInputto return parsed canonical data and source directly, with no classification branch. - Replace legacy parser/resolution tests with required-v1, missing/unknown version, multi-document, strict-field, alias, identity/order, and deterministic players-projection tests.
- Update
docs/integrations/party.md, the party section ofdocs/config.md,docs/internal/configuration.md, and troubleshooting. Do not yet claim removed config keys are unknown; Stage 3 changes structs.
Exit Criteria
go test ./internal/config ./internal/stage ./internal/appsucceeds.- No production code classifies or resolves an unversioned party, and
party_legacy.gois absent. - Unsupported party schema fails before family expansion or preparation.
- Canonical parsing and derived players output remain deterministic.
git diff --checksucceeds.
Stage 3 — Remove Legacy Party And Players Configuration
Status: Completed
Goal
Make one campaign party the only configured roster authority and players an explicitly derived value.
Required Work
- Remove
PlayersFilefromCampaignInputsConfigand bothPlayersFileandPartyFilefromSessionInputsConfig. Strict decode must reject all three retired YAML paths as unknown unsupported fields. - Keep campaign
PartyFilein resolved stable inputs. Represent generated players as derived data, not a selectableResolvedInputFile; introduce a clearly named derived value if prepared-input consumers need one. - Remove
PartyMode, both constants,PartyDocument,IsCanonical, and all mode branches. MakeResolvedPartyhold non-nilCanonicalPartyplus source provenance, and makeParsePartyreturn*CanonicalParty. - Simplify
mergeCampaignSession, campaign-party loaders, resolved validation, family expansion, effective digest/source reporting, and config commands. Party selection always comes from campaign config. - Delete
internal/stage/prepare_legacy.go. Simplify prepare/resume, extraction references, and semantic contracts to copy canonical party and render players. Preserve prepared paths, checksums, input kinds, Notarius selectors, and meaningfulderived_from_partyprovenance. - Make operator/source rendering show campaign party and derived players, with
no
legacy_playerrole or separate player path. - Rewrite affected config, stage, app, template, profile, and command tests. Add strict rejection for all retired keys and an integrated config/prepare test proving one party produces both prepared documents.
- Remove legacy roster text/fixtures from
docs/config.md,docs/cli.md,docs/internal/configuration.md,docs/internal/stage-prepare.md,docs/internal/stage-extract.md, andexamples/.
Exit Criteria
go test ./internal/config ./internal/stage ./internal/appsucceeds.- No production YAML tag accepts
players_fileor sessionparty_file. - No production
PartyMode,PartyDocument, or legacy prepare symbol remains. - One campaign v1 party supplies both prepared party and derived players.
- Current docs/examples contain no legacy roster workflow.
git diff --checksucceeds.
Stage 4 — Make Remote Current State Commit-Only
Status: Completed
Goal
Make the versioned commit pointer the sole remote authority for status, restore, and reuse.
Required Work
- Replace legacy missing errors with typed
CurrentCommitPointerMissingErrorkeyed toS3CurrentCommitPointerKey.LoadCurrentStatereturns it with publish/republish guidance when absent and otherwise loads only committed state. - Delete
current_state_legacy.go, legacy limits,ResolveCurrentStateKeys, and retired key helpers once callers are gone. Retain current pointer/commit/session-manifest bounds. - Require non-nil pointer, commit, and manifest in
CurrentState; keepCurrentPointerKeyand committed manifest key; removeCurrentRunIDKeyand legacy-oriented duplication. Update identity errors. - Mirror this in
app.RemoteCurrentState, discovery, status, lock inspection, restore, and helpers. Status may report missing pointer as not published; malformed or unsupported current data remains an error. - Delete
buildLegacyRestoreActions, legacy key mapping, prefix listing, and checksum inference. Require committed restore planning while preserving audio scope, verification, conflicts, reports, manifest-last order, and installation safety. - Replace legacy fixtures with complete pointer/commit/manifest fixtures. Cover missing pointer, bad versions/identity, undeclared/missing objects, checksum/size/generation mismatch, and proof that restore neither lists nor reads retired keys.
- Remove legacy remote language from
docs/operations.md,docs/troubleshooting.md,docs/internal/manifest.md,docs/internal/artifacts.md,docs/internal/command-restore.md, anddocs/internal/stage-publish.md; document republishing.
Exit Criteria
go test ./internal/artifacts ./internal/app ./internal/stagesucceeds.- Production never reads
current/manifest.jsonorcurrent/run_id.txt. - Every successful current load has a verified pointer, commit, and manifest.
- Restore derives all remote scope from the commit without prefix listing.
git diff --checksucceeds.
Stage 5 — Resolve Previous Artifacts From Commit Declarations
Status: Pending
Goal
Use one deterministic source-to-object lookup per previous requirement without path candidates or remote probing.
Required Work
- Rewrite
previouscache.Resolvearound guaranteed committed state. Require the unique session-manifest declaration and always copy its checksum, size, generation, and verified content into the plan. - For each sorted requirement, derive its canonical source ID and select its
unique
published_output. Validate and strip the selected run's immutable published-output prefix fromDestinationKeyto derive the local relative path; never use current config or previous-manifest outputs for it. - Preserve required/optional behavior, deterministic ordering, safe local paths, and checksum/size/generation metadata.
- Delete legacy candidate/basename matching, manifest/config fallbacks,
candidate
Existscalls, probing ambiguity errors, and unused derivation helpers. Delete the test-onlyBuildPlanalias; callResolve. - Test exact declarations, no candidate
Existsor list calls, required/optional outcomes, unsafe destinations, duplicate-source rejection, and verified metadata through prepare/restore integration. - Update
docs/internal/stage-prepare.md,docs/internal/command-restore.md,docs/internal/artifacts.md,docs/operations.md, and troubleshooting.
Exit Criteria
go test ./internal/previouscache ./internal/stage ./internal/appsucceeds.- Resolution contains no candidate, basename/config fallback, ambiguity probe, or compatibility alias.
- Every hydration traces to one verified commit declaration.
- Optional absence skips and required absence fails without probing.
git diff --checksucceeds.
Stage 6 — Make Local Artifact Evidence Manifest-Only
Status: Pending
Goal
Remove aggregate and incidental paths so transcript, extraction, and analyze consumers require current manifest evidence.
Required Work
- Rewrite
ResolveSessionArtifactand its private candidate logic to require a succeeded registered producer and exactly one output matching bothKindandSourceID. Require safe path, producer run ID, and valid checksum; verify checksum and registered content before returning. Reject ambiguous/malformed evidence. - Delete
fallback.canonical_path, canonical-path probing, filename fallback, and old-candidate deduplication. Missing evidence remains typed unavailable even when expected bytes exist. - Update render, extract input identity, transcript resolution, and analyze built-in resolution to use this path while retaining stage context, content checks, and semantic fingerprints.
- Remove
analyzeReconciliationLegacyand the version-zero reconciliation branch. Unsupported in-memory state is non-resumable/corrupt, not legacy state eligible for recovery. - Audit catalog hydration, analyze inputs, publish discovery, operator
inspection, and projections: configured analyze output authority comes only
from a current
AnalyzeArtifactsrecord and its output evidence, never aggregate outputs or configured paths. Do not implement Workstream 6 checkpointing. - Retain only strict extraction proof in
InspectExtractionEvidence: succeeded stage, matching input, canonical bundle, receipt, exact lane set plus index, verified JSON/checksums, contracts, and Notarius provenance. Delete any acceptance of missing contract/provenance; do not redesign the adapter. - Test that incidental files, aggregate-only analyze outputs, missing/failed/stale/unselected records, mismatched checksums, and incomplete extraction evidence remain unavailable. Retain positive coverage for all built-in transcript sources, configured extraction lanes, and analyze outputs.
- Update
docs/internal/artifacts.md,docs/internal/stage-analyze.md,docs/internal/stage-extract.md,docs/internal/stage-publish.md,docs/internal/manifest.md,docs/config.md, operations, and troubleshooting without legacy/fallback language.
Exit Criteria
go test ./internal/artifacts ./internal/manifest ./internal/stage ./internal/appsucceeds.- Disk files cannot establish transcript or analyze availability without current evidence; aggregate analyze outputs cannot substitute.
- Extraction lacking receipt, contract, provenance, complete sources, or verified bytes cannot hydrate the catalog.
- Positive current transcript/extraction/analyze/publish/operator flows work.
git diff --checksucceeds.
Stage 7 — Complete The Purge And Validate The Repository
Status: Pending
Goal
Remove residual compatibility material, validate the current-only architecture, and close Workstream 3 without entering later work.
Required Work
- Search active code, tests, fixtures, examples, and current docs for retired
remote keys/readers, previous candidates, unversioned party modes,
players_file, session party overrides, aggregate analyze authority, incidental artifact fallback, missing manifest schemas, and extraction evidence without contract/provenance. - Delete unused constants, fields, types, errors, helpers, aliases, files, fixtures, and imports belonging to those paths. Do not remove unrelated adapter defaults, optional-input behavior, or later-workstream seams merely because they use “fallback.”
- Review canonical docs under the documentation policy for consistent current
behavior and actionable migration. Leave
docs/releases/, audits, and decision history unchanged. - Run
go test ./...,go test -race ./...,go vet ./..., documented docs/example validation, and supported Linux/macOS/Windows release builds. Cross-builds prove compilation only. - Review tests against testing policy: retain rejection and positive tests at every authority boundary without recreating deleted algorithms in tests.
- Mark only Workstream 3
Completeinrefactoring.mdand all stages hereCompleted. Accurately record unavailable native-platform evidence.
Exit Criteria
- Full tests, race tests, vet, docs/examples, and release cross-builds succeed.
- No runtime branch accepts or infers a retired Workstream 3 representation.
- Each current manifest, party, remote snapshot, previous input, transcript, extraction artifact, and analyze artifact has one documented authority.
- Only Workstream 3 is newly complete and
git diff --checksucceeds.
Open Questions
None. The approved audits, roadmap, policies, and current implementation make this plan decision-complete.