Plan analyze artifact checkpointing

This commit is contained in:
2026-09-04 13:21:27 +00:00
parent 1e222a4db3
commit 6d18a3126e

View File

@@ -1,483 +1,462 @@
# Runner Lifecycle Refactoring Implementation Plan # Workstream 6 Implementation Plan — Per-Artifact Analyze Checkpointing
## Status And Purpose ## Status
**Status: Completed** Planning document for Workstream 6, **Per-artifact analyze checkpointing and
recovery**, in [`refactoring.md`](refactoring.md). Workstreams 1 through 5 are
complete and are prerequisites for this plan. All stages below are pending and
must be implemented in numerical order.
This is the decision-complete implementation plan for Workstream 5, **Runner The purpose of this workstream is to make analyze a deterministic resumable
lifecycle and pre-execution decisions**, in artifact queue backed only by the session manifest. If artifacts A and B
[`refactoring.md`](refactoring.md). Workstreams 1 through 4 are complete. succeed, C fails, and D is not attempted, A and B must remain reusable, C must
Workstream 5 is complete; Workstream 6 is the next pending workstream. This be retried, and D must follow after C succeeds. The implementation must remove
plan's implementation scope remained limited to Workstream 5. the current end-of-stage analyze projection and rollback machinery rather than
building checkpointing alongside it as a second durable model.
The implementation must reduce the change blast and cognitive load of ## Authoritative Scope And Decisions
`internal/app.executePlan` without changing the pipeline's observable behavior.
It must leave one explicit lifecycle owner, one session lock, one durable
session-manifest authority, a narrow shared planning/execution decision model,
and invocation-scoped test seams. It must not create a workflow engine, a
general dependency-injection framework, or new public extension points.
The authoritative scope and target state are Workstream 5, the dependency The authoritative product and architecture scope is Workstream 6, the
relationships, and the consolidated target state in dependency relationships, compatibility intent, and consolidated target state
[`refactoring.md`](refactoring.md). The supporting audit decisions are H04, in [`refactoring.md`](refactoring.md). Supporting decisions are H04, H14, H15,
H07, H14, H15, H17, H34, and H35 in H36, H37, C02, and C03 in [`audit-decisions.md`](audit-decisions.md), with the
[`audit-decisions.md`](audit-decisions.md), with detailed analysis in detailed rationale in H36 and H37 of [`audit.md`](audit.md) and Finding 3 of
[`audit.md`](audit.md). [`contract-audit.md`](contract-audit.md).
## Scope And Fixed Decisions The following decisions are fixed for this implementation:
- Keep the existing `internal/app` package boundary. Use private types, - Keep analyze as one fixed pipeline stage in `internal/stage`; per-artifact
functions, and focused files; do not create a runner package or expose work does not become dynamic pipeline stages or a generic workflow engine.
lifecycle internals. - Keep the session manifest's `analyze_artifacts` collection as the only durable
- Keep the session manifest as the sole durable local progress ledger. Run IDs, per-artifact authority. Do not introduce an invocation collection, journal,
run directories, diagnostics, and `RunSummary` remain, but no second durable sidecar, database, or replacement run manifest.
invocation record may be introduced. - Use the existing `stage.Env.ManifestStore` and the canonical session-manifest
- Preserve the canonical flat stage order, bounded contiguous execution, path for checkpoints. Production execution already supplies that store and
dependency-aware invalidation outside the selected range, stage-owned resume holds the session lock for the entire `Stage.Run` call. Do not add a callback
validation, self-skip semantics, force semantics, and current analyze framework or expose application lifecycle types to `internal/stage`.
projection behavior. - Checkpoint a copy-on-write candidate manifest. Validate and save the candidate
- Preserve the two prerequisite checks: a read-only check before persistent before installing its analyze collection into the live in-memory manifest.
mutation and a second check against the manifest while holding the session A failed current-record checkpoint must never make newly materialized bytes
lock. authoritative through a later terminal save.
- Keep lock acquisition and the deferred release visibly paired in the - Persist the reconciled starting collection before the first adapter call when
top-level lifecycle function. A release failure remains the final invocation it changes durable authority. This removes obsolete records and stales
error when no earlier error exists and is joined with an earlier error scheduled or invalid current records before replacement work begins.
otherwise. - For every successful artifact: validate and materialize its canonical output,
- Preserve manifest ordering: establish identity before adapter construction; construct and validate its current record, update the runtime catalog, stale
persist `running` before invoking a stage; persist terminal stage state before transitive dependents when output identity changed, checkpoint the complete
moving to the next stage; and persist terminal invocation failures where the session collection, and only then attempt the next artifact.
current runner does so. Do not make diagnostics a persistence authority. - For an artifact failure: retain earlier successful checkpoints, replace the
- Keep actual `Stage.Run` results, result validation, persistence, logging, and active artifact with a bounded failed record, stale its current transitive
failure handling execution-only. Planning may model a successful or dependents, and expose that collection on the live manifest. The application
configured self-skipped outcome on a cloned manifest, but must never pretend runner remains responsible for marking aggregate analyze failed, invalidating
that it observed a runtime result. publish/notify, and atomically persisting the terminal manifest before the
- The shared pure decision value must describe only whether to run or skip and command returns the error.
the pre-execution stale/invalidation work implied by recorded status, force, - If persistence of a successful-artifact checkpoint fails, stop immediately.
and normalized resume validation. Small transition helpers may be shared Keep the last durable collection, mark the active artifact failed in the live
only where planning and execution perform the same manifest mutation. collection without promoting its incidental output, and let the runner's
- Preserve analyze's current single end-of-stage projection/save boundary and terminal failure save make that retry state durable when storage permits.
rollback on save failure. Per-artifact checkpointing belongs to Workstream 6 - Preserve the existing deterministic work planner, reconciliation,
and is out of scope. fingerprints, evidence validation, dependency order, and current-output
- Preserve post-publish cleanup and its durable obligation behavior until identity comparison. File presence alone never creates authority.
Workstream 9. This work may move the call into finalization and make its - Preserve artifact selection and force behavior. With no `--artifacts`, the
destructive function invocation-scoped, but must not turn cleanup into a effective configured set is targeted. With `--artifacts`, only the named
stage or change eligibility/retry semantics. targets and their required dependency closure participate. Force reruns
- Continue using `stage.Env` for injected stage collaborators. Add only small explicit targets; a valid current prerequisite is reused unless it is itself
private dependency values for nondeterministic or external construction explicit or becomes non-current through dependency work. Unrelated valid
boundaries that cannot be represented by `stage.Env`, and pass those values records are retained.
explicitly per command or invocation. - On aggregate success, return ordinary `StageResult.Outputs` rebuilt in
- Remove `executeStagesFn` and `runCommandFn`. Replace their tests with private deterministic normalized artifact-key order from all current session analyze
command helpers that accept a pipeline executor value for that call. Do not records. Per-artifact records retain their own logs, generated configuration
migrate unrelated restore/config-loader globals in this workstream; their references, bounded Scriptorium provenance, and evidence; aggregate logs and
owners should adopt the same narrow pattern when later work touches them. metadata continue to describe the current invocation.
- Replace the runner's mutable clock use with an invocation-scoped clock. A - On aggregate failure, aggregate outputs remain empty. Earlier current
normal immutable `nowUTC()` function may remain for unrelated command owners. per-artifact records remain available to a later analyze run, but publish and
Route post-publish deletion through the invocation dependency value and notify are invalidated by the existing application-stage dependency relation.
remove `removeRunScopedDirFn`. - A crash or cancellation after a successful checkpoint may leave aggregate
- Preserve current CLI syntax, output, errors at their stable semantic level, analyze `running` or later normalized to `interrupted`; the next ordinary run
configuration behavior, workspace modes, secret loading/redaction, remote must reconcile the durable collection, reuse valid completed artifacts, and
lock revalidation, Linux/macOS/Windows support, and offline operation. continue with the first failed, stale, or missing artifact.
- Do not optimize for a particular private file layout or exact helper count. - Keep the manifest schema and analyze-state contract version unchanged. This
The finished top-level runner must nonetheless read as a short, explicit work changes persistence timing and removes an internal projection transport;
sequence of preparation, lock ownership, locked initialization, composition, it does not introduce a new serialized representation.
stage execution, and finalization rather than forwarding all work to another - Do not enter Workstream 7 configuration decomposition, Workstream 8 publish
monolith. locks/decomposition, or Workstream 9 cleanup-stage work.
- Mark only Workstream 5 `Complete`, and only after all stages and full
validation succeed. Workstream 6 remains the next pending workstream.
## Instructions For Every Stage ## Instructions For Every Stage
Before each stage: Before each stage:
1. Read `docs/development.md`, every document under `docs/policy/`, this plan, 1. Read `docs/development.md`, every document under `docs/policy/`, this plan,
Workstream 5 plus the dependency and consolidated-target sections of and Workstream 6 in [`refactoring.md`](refactoring.md). Read the decision
[`refactoring.md`](refactoring.md), and audit rows H04, H07, H14, H15, H17, entries and audit sections named above; do not re-audit unrelated findings.
H34, and H35 in [`audit-decisions.md`](audit-decisions.md). Consult the 2. Read [`../internal/stage-analyze.md`](../internal/stage-analyze.md) and
corresponding detailed findings in [`audit.md`](audit.md) when a boundary is [`../internal/manifest.md`](../internal/manifest.md), then inspect the
unclear. production files and focused tests named by the stage.
2. Inspect the named production code and focused tests. Prefer the codebase 3. Prefer the codebase knowledge graph for Go symbol discovery and call tracing.
knowledge graph for Go symbol discovery and call tracing; use text search for Use text search for serialized fields, documentation, fixtures, and removal
documentation, fixtures, serialized fields, and global-variable assignments. of projection terminology.
3. Confirm the worktree state and preserve unrelated changes. Do not enter 4. Confirm the worktree state and preserve unrelated changes. Do not begin a
Workstream 6 or Workstream 9 merely because analyze or cleanup code is later workstream merely because adjacent code is visible.
adjacent to runner lifecycle code.
During every stage: During every stage:
- Keep the repository compiling and focused tests passing. Do not leave both an - Keep the repository compiling and focused tests passing. A temporary private
old and new runner path active except for a temporary private delegation that delegation or projection bridge is permitted only where the next numbered
the next numbered stage explicitly removes. stage explicitly removes it; do not leave two durable authorities active.
- Preserve package ownership and acyclic dependency direction. Private state - Preserve package ownership and acyclic dependency direction. Keep new types
should aggregate already-related values, not become a service locator. private and cohesive; do not add public extension points.
- Preserve error wrapping and joining where errors represent distinct failures. - Keep all tests deterministic, offline, and independent of real Scriptorium or
Do not freeze incidental complete error strings in new tests. paid APIs. Prefer the existing fake runner, real temporary filesystem, and a
- Keep tests deterministic, offline, behavior-focused, and lean under small recording/failing manifest store only where checkpoint interaction is
`docs/policy/testing.md`. Prefer existing real temporary filesystems and itself the contract.
current adapter/storage fakes. Do not add mock choreography for private - Assert persisted manifest state, adapter calls, dependency order, and retry
lifecycle helpers. behavior. Do not pin private helper choreography, complete error wording, or
- Update the canonical current-behavior document owned by a changed behavior in incidental timestamps.
the same stage. Pure file movement or private extraction requires no user - Preserve bounded, non-secret errors and diagnostics. Never place prompt or
documentation change. transcript content, variables containing secrets, or raw credentials in new
- Run `gofmt` on changed Go files, focused tests for the changed boundary, and manifest fields or error assertions.
`git diff --check`. Mark a stage `Completed` only after its exit criteria pass. - Use `gofmt` on changed Go files, run the focused tests listed for the stage,
and run `git diff --check`. Mark a stage `Completed` only after its exit
criteria pass.
## Stage 1 — Unify Pre-Execution Decisions ## Stage 1 — Decompose The Single-Artifact Transaction
**Status: Completed** **Status:** Pending
### Goal ### Goal
Give planning and execution one pure representation of the decision made before Make one artifact execution understandable and independently reviewable before
a stage attempt, while keeping planning predictions distinct from observed changing its persistence timing. This stage is behavior-preserving.
runtime outcomes.
### Required Work ### Required Work
1. In `internal/app/run_control.go` or a focused private file, replace the 1. Refactor `executeAnalyzeArtifact` in `internal/stage/analyze.go` into one
current action-only decision with an unexported value that records: short transaction owner plus private phases with explicit intermediate
`run` versus `skip`; whether the selected successful record must first be values. Keep the existing `analyzeExecutionContext` and artifact plan, or
marked stale; the stage-owned reason for that stale transition; and the replace them with equally narrow private values; do not restore the former
application-owned invalidation reason, if any. Its constructor must be pure: broad parameter list.
it receives the prior manifest status, `force`, and an already normalized 2. Separate these concerns without changing their order or contracts:
resume-validation result and does not mutate state, persist, log, read files, - input identity resolution, omitted optional inputs, reused-current input
invoke a stage, or obtain the current time. reporting, Scriptorium variables, timeout, canonical/run-local paths, and
2. Encode these cases explicitly: request/diagnostic path preparation;
- a non-forced succeeded record with no validator result or a resumable - optional render-debug invocation and validation;
result skips; - Scriptorium artifact invocation and adapter failure adaptation;
- a non-forced succeeded record with a non-resumable result runs after the - authoritative output-path selection, bounded non-empty regular-file
selected record is staled with the validator's reason and succeeded validation, output-contract construction, canonical materialization, and
dependents are invalidated with `staleReasonNotResumable`; installed-checksum verification; and
- a forced stage runs and invalidates succeeded dependents with - result/evidence construction, including provenance, logs, generated
`staleReasonForcedReplacement` when the attempt begins; and configuration paths, metadata, output size, and reused artifacts.
- absent, failed, stale, or skipped aggregate records run without inventing a 3. Place the extracted code in focused analyze-owned files when that improves
resume-stale transition. navigation. Suggested responsibilities are preparation, adapter invocation,
3. Add a small helper that applies only the identical pre-execution manifest and validated materialization/evidence; exact private filenames are not a
changes described by the decision at a caller-supplied timestamp. Retain the contract.
existing fixed invalidation relation and stable application-owned reasons. 4. Keep Scriptorium request and response types inside the stage/adapter boundary.
The caller remains responsible for persistence and error context. Do not move Narratio artifact, manifest, path, or metadata policy into the
4. Use the decision constructor and application helper from both `Plan` and Scriptorium adapter.
`executePlan`. Each caller may perform stage-owned resume validation before 5. Preserve the current run-local-before-canonical materialization boundary,
constructing the decision. Preserve analyze planning's read-only artifact render-debug behavior, useful error context, workspace modes, centralized
details when analyze is already a run candidate; those details are preview size limit, and checksum comparison. Do not weaken validation or add a new
information, not an execution outcome. write path while moving code.
5. Extract only the exact shared attempt/result mutations already identical in 6. Retain existing behavior tests at their stable boundaries. Consolidate only
both paths: beginning an attempt (`running` plus forced invalidation), tests that become purely coupled to moved private helpers; do not add one
applying a configured/runtime self-skip and its conditional invalidation, test per extracted function.
and applying a predicted/actual success and its conditional invalidation.
Require callers to supply the actual or modeled outcome, outputs, semantic
fingerprint, prior outcome, and timestamp. Do not put `Stage.Run`, result
validation, logging, or manifest saves in these helpers.
6. Rewrite `modelPlannedStageRun` around these values or delete it if the
resulting focused helpers make it redundant. Keep all planning mutation on
the cloned manifest.
7. Add table-driven decision tests for every status/force/resume combination
and focused parity tests proving that plan and execution produce the same
run/skip choice and dependent-stale effects for ordinary skip, non-resumable
success, forced replacement, unchanged self-skip, changed self-skip, and a
successful result replacing a non-success record. Assert durable state and
decision semantics, not private helper call order or exact timestamps.
### Exit Criteria ### Exit Criteria
- `go test ./internal/app` succeeds. - `executeAnalyzeArtifact` reads as a short ordered transaction over explicit
- `Plan` and `executePlan` no longer independently encode the same phases and no longer mixes all preparation, invocation, materialization, and
pre-execution stale/force decision sequence. evidence construction in one large function.
- The decision constructor is pure, and actual stage outcomes and persistence - Existing analyze request, render, validation, materialization, provenance,
remain outside it. and failure behavior is unchanged.
- Planning remains read-only and execution behavior is unchanged. - `go test ./internal/stage ./internal/adapters/scriptorium` succeeds.
- `git diff --check` succeeds. - `git diff --check` succeeds.
## Stage 2 — Establish The Invocation Runtime And Lock Lifecycle ## Stage 2 — Establish Session-Only Analyze Execution State
**Status: Completed** **Status:** Pending
### Goal ### Goal
Make invocation state cohesive and make the top-level transaction order obvious Replace `analyzeStage.Run`'s parallel local collections with one private
without hiding session-lock ownership. session-oriented execution state that is ready to checkpoint, while retaining
the existing end-of-stage persistence boundary temporarily.
### Required Work ### Required Work
1. Introduce an unexported `invocationRuntime` (or equivalently narrow private 1. Introduce a private analyze execution state/accumulator that owns:
type) under `internal/app`. It should carry only state already shared across - the shared execution context and deterministic work plan;
lifecycle phases: context, resolved configuration, bounded plan/stages, - the complete reconciled session artifact collection;
normalized options, effective artifacts, run identity and canonical paths, - prior current records needed for output-identity comparison;
`stage.Env`, manifest store, artifact store, loaded session manifest, and - the evaluated/scheduled key set used for dependent invalidation;
summary accumulators. Do not expose it or let stages depend on it. - the mutable runtime artifact catalog; and
2. Extract unlocked preparation that performs the current read-only bounded - invocation-only aggregate logs, generated configurations, generated
prerequisite inspection, resolves effective artifacts, generates the run metadata, reused-artifact metadata, and its deduplication set.
ID, resolves invocation identity, initializes/clones `stage.Env` fields, and 2. Keep setup, reconciliation, work planning, adapter-availability validation,
supplies default manifest/artifact stores and logger. Preserve the rule that deterministic iteration, and final `StageResult` assembly visible in
injected `Env` values provide collaborators but never an alternate config, `analyzeStage.Run`. Move per-item state mutation and diagnostic collection
selected-artifact set, or effective artifact set. behind narrow methods on the private state; do not turn the accumulator into
3. Extract layout preparation separately. Keep `EnsureLayoutFor` before lock a service locator.
acquisition as today, verify its manifest path against the canonical 3. Rename `analyzeWorkPlan.ProjectedRecords`, `projectedAnalyzeRecords`, and
resolved path, and return enough state for the locked lifecycle. related private terminology to describe the reconciled starting session
4. In the short top-level executor, acquire the session lock after unlocked collection. It is the candidate durable state, not an invocation projection.
preparation and visibly install one deferred release beside that acquisition. Preserve the planner's read-only behavior and existing classification.
Preserve exact release-error joining behavior. Do not put lock ownership in 4. Make the accumulator install its complete collection into the passed session
`invocationRuntime`, a constructor, or a helper whose lifetime is ambiguous. manifest on success and its failed/staled collection on error. During this
5. Extract locked initialization that checks the incomplete-restore marker, stage, continue returning the existing `AnalyzeStateProjection` as a
loads and validates or creates the session manifest, rechecks bounded temporary compatibility bridge for the application runner; both views must
prerequisites, applies identity and effective-configuration provenance, be derived from the same collection, and no additional save is introduced.
saves the identity transition, ensures the canonical run directory, and 5. Add optional `ProducerRunID` provenance to `artifacts.Ref`. Update the
loads configured secrets. All steps after manifest availability that generic application result mapper to use that value when it is non-empty and
currently record a terminal failure must continue to do so through the otherwise retain the current behavior of assigning the active run ID. This
session-only failure helper. is data needed by ordinary stage-result transport, not an analyze-specific
6. Keep pre-lock failures free of persistent mutation and keep all later branch; cover both explicit and fallback behavior in the mapper tests.
manifest transitions protected by the same held lock. Do not add rollback, 6. Add a private helper that deterministically converts all current analyze
a second lock, or a second manifest authority. records into ordinary `artifacts.Ref` outputs. Preserve source ID, relative
7. Adapt existing bounded-run, restore-marker, identity, run-directory, path, contract, checksum, and producer run ID. Include valid current records
terminal-failure, and lock-release tests to exercise the assembled lifecycle. retained outside a partial selection; do not include stale, missing, or
Add coverage only if the two-check prerequisite ordering or lock-release failed records. Continue using the projection path for aggregate outputs
error behavior is not already credibly protected. until Stage 3 switches ownership.
7. Rewrite stage tests to inspect the manifest collection mutated by the real
analyze stage rather than manually installing returned projections. Retain a
minimal assertion that the temporary projection matches that collection so
the transition cannot silently diverge.
### Exit Criteria ### Exit Criteria
- `go test ./internal/app ./internal/artifacts ./internal/manifest` succeeds. - `analyzeStage.Run` coordinates one private session collection rather than
- The top-level executor visibly performs unlocked preparation, acquires and parallel record and diagnostic variables.
defers release of exactly one session lock, initializes locked state, and - Planning/reconciliation remains read-only, execution order and force/selection
proceeds to execution. behavior are unchanged, and stage tests primarily assert manifest state.
- Invalid preflight requests create no persistent invocation state; the locked - Ordinary `StageResult.Outputs` can preserve a reused artifact's original
prerequisite recheck still prevents races with a prior invocation. producer run identity without an analyze-specific application path.
- The private runtime has cohesive fields and no public or cross-package API. - The existing application projection path still functions and no checkpoint
timing has changed yet.
- `go test ./internal/stage ./internal/app` succeeds.
- `git diff --check` succeeds. - `git diff --check` succeeds.
## Stage 3 — Isolate Collaborator Composition ## Stage 3 — Cut Analyze Over To Durable Per-Artifact Checkpoints
**Status: Completed** **Status:** Pending
### Goal ### Goal
Make production collaborator construction one bounded lifecycle phase that is Make each validated artifact completion durable before later artifact work and
selective, testable per invocation, and independent of stage transitions. stop using the end-of-stage projection transport for real analyze execution.
### Required Work ### Required Work
1. Define a small unexported invocation dependency value with explicit defaults 1. Add a private analyze checkpoint operation in `internal/stage`. It must:
for the nondeterministic and external construction boundaries used by this - require a non-nil session manifest, analyze stage record, and
runner: UTC time, run-ID generation, default adapter/object-store creation, `Env.ManifestStore` when analyze has state to persist;
effective remote-lock loading/revalidation support, and post-publish scoped - derive the canonical manifest path through `artifacts` helpers from the
removal. Include a field only where tests need control over a real external, resolved workspace, campaign, and session identity;
destructive, clock, or randomness boundary; continue injecting completed - validate the full candidate collection with
stage collaborators through `stage.Env`. `manifest.ValidateAnalyzeArtifactCollection`;
2. Add a private executor variant that accepts that dependency value and have - shallow-copy the manifest, clone the top-level stage map, copy the analyze
the ordinary executor supply production defaults. Normalize missing fields stage record, and deep-clone the candidate analyze collection so the save
once at the entry boundary so lifecycle methods never fall back to mutable cannot mutate live authority before success;
package state. Do not export the dependency type or store it globally. - preserve the aggregate analyze status (normally `running`) and every
3. Route run-ID creation and every runner timestamp through the invocation unrelated manifest field;
dependencies. Convert `nowUTC` to an ordinary function for unrelated - save the candidate through `Env.ManifestStore`; and
command owners if it no longer needs to be mutable; do not broaden this stage - only after success, install the candidate analyze version/collection and
into a restore lifecycle refactor. the store-updated manifest timestamp into the live manifest.
4. Extract collaborator composition from the runner into one operation over the Do not add a general manifest transaction framework or JSON round-trip clone.
invocation runtime. Preserve current selection gates exactly: WhisperX only 2. Before invoking the first scheduled artifact, checkpoint the reconciled
for `transcribe`; Seriatim only for `merge`, `normalize`, `trim`, or `render`; starting collection when it differs from durable state. This must remove
Audita only for `polish`; Notarius only when selected extraction requires it; records for removed configuration and stale any selected/forced/non-current
Scriptorium only for `trim` or `analyze`; object storage only when selected authority before replacement work begins. If this save fails, invoke no
stages/effective artifacts require it; remote lock loading only when needed; adapter.
and the no-op notifier only for `notify`. 3. For each scheduled artifact, preserve this order:
5. Preserve injected collaborators, secret-loaded environment behavior, - compute its execution fingerprint;
effective lock merging, and the publish-time revalidation closure. A - execute the decomposed artifact transaction;
composition failure after identity initialization remains a persisted - construct and validate its current manifest record;
terminal session failure with its existing useful context. - prove the generated record can be added to the runtime catalog for use by
6. Route post-publish cleanup's scoped deletion through the invocation later dependents;
dependency value and delete `removeRunScopedDirFn`. Preserve cleanup - stale unscheduled transitive dependents only when prior and replacement
obligation creation, per-target persistence, retry, and failure behavior. output identities differ;
7. Consolidate selective-composition tests into table-driven or focused - checkpoint the complete updated session collection; and
behavioral cases. Test that an unselected external boundary is not - only then expose the result to a later artifact.
constructed only where avoiding that construction is an operational 4. If a successful artifact's checkpoint fails, stop immediately and do not
requirement; do not assert private helper ordering. call the next artifact. Build a failed record for the active artifact from
the last committed collection, with no output authority; stale its current
transitive dependents; and install that failure candidate in memory so the
runner's terminal save can persist retry state. Return the checkpoint
failure with artifact identity and manifest context.
5. Have real analyze execution return ordinary `StageResult.Outputs` built from
every current session record plus its existing aggregate diagnostics and
metadata. Stop returning `AnalyzeStateProjection` from `analyzeStage.Run`.
The legacy projection type and application handlers may remain only as dead
transitional code until Stage 4; add no new uses.
6. In `executeSelectedStage`, make a terminal save failure after any stage's
apparent success mark that stage failed and invalidate succeeded transitive
dependents before terminal persistence. This behavior currently occurs only
on the analyze-projection branch; it must remain correct when real analyze no
longer returns a projection and should be correct generically.
7. Update direct analyze fixtures so they supply a real temporary
`manifest.LocalStore`, complete campaign/run identity, an aggregate analyze
record in the state appropriate to the test, and an initially persisted
manifest. Keep unit-only planner/reconciliation tests free of persistence.
8. Add focused checkpoint tests at the stage/application boundary:
- the manifest store observes A current before B is invoked and A+B current
before C is invoked;
- a failed B checkpoint prevents C from being invoked;
- the failed B checkpoint never persists B as current merely because its
canonical file exists; and
- final aggregate success exposes outputs for all and only current records
in deterministic normalized artifact-key order, preserving each record's
producer run identity.
### Exit Criteria ### Exit Criteria
- `go test ./internal/app` succeeds, including composition, remote-lock, - Every successful scheduled artifact is durable before the next adapter call.
cleanup, and bounded-stage tests. - A checkpoint failure stops later work and cannot promote incidental output.
- Collaborator construction is absent from the top-level runner and stage loop, - Real analyze execution no longer relies on an end-of-stage state projection.
but remains an application-owned production-composition concern. - Aggregate success/failure persistence and dependent invalidation remain
- Runner time, randomness, external construction, and destructive cleanup are application-owned and correct.
controllable per invocation without mutable runner globals. - `go test ./internal/stage ./internal/app ./internal/manifest` succeeds.
- Selected-stage composition and terminal-failure behavior remain unchanged.
- `git diff --check` succeeds. - `git diff --check` succeeds.
## Stage 4 — Extract One-Stage Execution ## Stage 4 — Complete Failure Recovery And Remove Projection Machinery
**Status: Completed** **Status:** Pending
### Goal ### Goal
Give one private operation complete ownership of a single stage attempt while Complete the A/B/C/D recovery contract and delete the superseded projection,
preserving every durable transition and failure boundary. validation, application, and rollback paths.
### Required Work ### Required Work
1. Extract an operation on the invocation runtime that executes exactly one 1. On any artifact-specific failure—including fingerprinting, preparation,
selected stage and returns a small execution-owned outcome sufficient to render, adapter execution, result validation, materialization, evidence
update the in-memory summary or stop the invocation. It may coordinate the construction, catalog admission, or checkpoint persistence—construct one
shared decision/transition helpers but must not select the next stage or bounded failed record for the active key, clear its output authority, and
release the session lock. stale current transitive dependents. Preserve unrelated current records and
2. Within that operation, preserve this order: all earlier successfully checkpointed records.
- compute the stage semantic fingerprint; 2. Install the failed/staled collection on the live manifest before returning
- run stage-owned resume validation when the recorded success is a skip the artifact error. The runner must then use its ordinary stage-error path to
candidate; mark aggregate analyze failed, clear aggregate outputs, invalidate publish
- build and apply the pure pre-execution decision and persist any and notify, and persist the terminal session manifest. Join a terminal save
non-resumable stale/invalidation transition; failure with the originating artifact/checkpoint error; never replace the
- return a skip outcome without invoking the stage when still resumable; useful cause.
- capture prior aggregate/analyze state, begin the attempt, persist 3. Ensure unattempted artifacts behave according to existing authority:
`running`, and then invoke `Stage.Run`; previously absent records remain absent/missing on reconciliation; unrelated
- validate the stage result and any analyze projection; valid current records remain current; and a prior current record that
- apply failure, self-skip, or success transitions and required dependent depends directly or transitively on the failed artifact becomes stale with
invalidation; and no output authority.
- persist the terminal state before returning. 4. Remove `AnalyzeStateProjection` from `stage.StageResult`, delete
3. Preserve failure semantics for semantic fingerprinting, resume validation, `internal/app/analyze_projection.go`, and remove all validation, cloning,
invalidation, the pre-run save, stage execution, result validation, application, capture, rollback, and special output reconstruction that exists
self-skip/success saves, and terminal failure persistence. A failed stage or solely for that projection. Remove fake projection stages and projection-only
persistence operation stops later stages. Distinct invalidation/save errors tests; replace their meaningful risks with real analyze checkpoint/recovery
remain joined with the originating failure.
4. Preserve current analyze behavior exactly: a valid partial failure
projection may be applied before recording failure; a successful projection
supplies aggregate outputs; and failure to save a successful projection
restores prior analyze authority before recording aggregate failure.
Introduce no callback/store dependency into `internal/stage`.
5. Keep manifest save calls explicit in the one-stage owner. Do not hide them in
generic state-machine hooks or teach `manifest.Store` orchestration policy.
6. Reduce the outer stage loop to call the one-stage operation, append its
executed/skipped classification, and stop on error. Preserve deterministic
`StageNames`, `Executed`, and `Skipped` order and current logging semantics.
7. Reconcile runner tests around observable manifest state, invocation summary,
stage calls, and errors. Preserve coverage for ordinary skip, force,
non-resumable state, self-skip, result validation, cancellation, downstream
invalidation outside a bounded range, save failures, analyze rollback, and
stopping after failure. Remove redundant tests that exist only to pin the old
monolithic control flow.
### Exit Criteria
- `go test ./internal/app ./internal/stage ./internal/manifest` succeeds.
- The stage loop contains no duplicated transition policy and one-stage
execution has one clear persistence owner.
- A later stage is never attempted after a stage or required manifest save
fails, and no failed/interrupted attempt is presented as successful.
- Analyze still uses its pre-Workstream-6 aggregate persistence boundary.
- `git diff --check` succeeds.
## Stage 5 — Isolate Finalization And Simplify The Lifecycle Owner
**Status: Completed**
### Goal
Complete the runner decomposition so the lifecycle and transaction boundaries
are easier to read than the former monolith.
### Required Work
1. Extract finalization that performs the current post-publish cleanup only
when publish actually executed, using the runtime's session manifest,
manifest path, environment, executed-stage list, and invocation-scoped
deletion dependency. Preserve the special cleanup-failure persistence and
the fact that a committed remote publish remains authoritative.
2. Build `RunSummary` from the runtime's resolved identity and ordered
accumulators only after all selected stages and required cleanup complete.
Continue returning no success summary on invocation failure.
3. Make the top-level executor an explicit sequence: unlocked preparation;
layout preparation; visible lock acquisition/deferred release; locked state
initialization; collaborator composition; ordered one-stage execution; and
finalization/summary. It must retain meaningful error context at each phase
and must not delegate that entire sequence to a new monolithic helper.
4. Place private lifecycle concerns in focused files according to ownership—for
example runtime/preparation, composition, stage execution, and finalization—
while keeping exact shared transition logic in `run_control.go`. Remove dead
helpers, stale comments, duplicate state, and imports left by extraction.
5. Review the resulting runtime for service-locator growth. Remove fields used
by only one local operation unless retaining them materially clarifies the
lifecycle. Keep collaborators in `stage.Env` and persistence/path policy in
their existing owners.
6. Run focused assembled-workflow, cleanup, bounded-run, resume, manifest-save,
and release-error tests. Add no tests for private file placement, helper
count, or line count.
### Exit Criteria
- `go test ./internal/app ./internal/stage ./internal/manifest ./internal/artifacts`
succeeds.
- `executePlan` is a short, readable lifecycle owner with one visibly scoped
lock and no nested stage state machine or collaborator-construction block.
- Cleanup remains a post-publish obligation with unchanged behavior, awaiting
Workstream 9.
- The refactor has removed obsolete code rather than retaining parallel old and
new lifecycle paths.
- `git diff --check` succeeds.
## Stage 6 — Remove Global Runner Seams And Close Workstream 5
**Status: Completed**
### Goal
Prove the target architecture through invocation-scoped command tests, accurate
current documentation, and complete repository validation.
### Required Work
1. Introduce the smallest private command-level helper needed to pass a
pipeline-executor function for one call through `Execute`, `Run`,
`RegenerateArtifacts`, and the single-stage command path. Public command
functions continue to use the production executor. The alias must still be
exactly equivalent to `run --force --from extract --through analyze` with
caller options forwarded under the existing rules.
2. Delete `executeStagesFn` and `runCommandFn`. Rewrite affected CLI, profile,
remote-session, restore-workflow, assembled-workflow, bounded-run, alias, and
artifact-selection tests to call the private command helper with an explicit
executor or to exercise the real assembled runner. Do not serialize tests
around package-global save/replace/restore choreography.
3. Confirm that `removeRunScopedDirFn` is gone and that runner tests do not
replace `nowUTC`. Inventory remaining mutable function variables in
`internal/app`; leave unrelated configuration-loading and restore seams for
their owning future work, but ensure no runner, run-command, alias, or
post-publish-cleanup test mutates process-global behavior.
4. Review test value under `docs/policy/testing.md`. Consolidate duplicate
command-wiring and lifecycle cases exposed by the new narrow seams while
retaining consequential lock, persistence, invalidation, recovery,
cancellation, external-boundary, and representative assembled-workflow
coverage. coverage.
5. Update current internal documentation where ownership requires it: 5. Simplify `executeSelectedStage` to its generic result/error handling while
`docs/internal/overview.md` should summarize the explicit runner lifecycle; preserving analyze's live manifest collection across aggregate lifecycle
`docs/internal/manifest.md` should describe the shared pre-execution decision clearing. Do not add a type assertion, stage-name branch, callback, or new
and single persistence owner without documenting private helper names; and result union to replace the projection special case.
`docs/internal/adapters.md` should describe selective application-owned 6. Add a durable assembled-run regression for the approved scenario:
composition. Update architecture policy only if implementation reveals a - configure deterministic A, B, C, and D work in dependency order;
normative boundary not already stated. Do not duplicate CLI/configuration - make A and B succeed, C fail, and verify D is not attempted;
contracts or edit historical release documents and completed audit findings. - reload the manifest and verify A/B current, C failed, aggregate analyze
6. Search current code and documentation for stale claims about the monolithic failed, and the correct missing/stale state for D;
runner, independent plan/run decisions, or mutable runner seams. Remove only - run again without force, verify only C and then D execute, and verify the
inaccurate current-behavior text; roadmap and audit history remain valid. final aggregate and all selected records are current; and
7. Update Workstream 5 in `refactoring.md` from `Pending` to `Complete`, and - verify valid unrelated records and upstream transcript stages were never
change all six stage statuses plus this plan's status to completed only after invalidated or rerun.
the target state and all checks are proven. Leave Workstream 6 `Pending`. 7. Add focused variants for failure of the first and last artifact, cancellation
8. Run the complete repository validation from `docs/development.md`: after at least one checkpoint, changed-output invalidation of an unselected
`go test ./...`, `go test -race ./...`, `go vet ./...`, `go build ./...`, transitive dependent, and byte/contract-identical replacement preserving an
`go test ./internal/doccheck`, and unselected dependent. Consolidate with existing incremental/failure tests so
`go test ./internal/config -run '^TestExamplesLoadAndValidate$'`. Also run each durable risk has one clear owner.
`git diff --check` and confirm the implementation commit leaves no unintended
worktree changes.
### Exit Criteria ### Exit Criteria
- All repository-wide validation succeeds on Go 1.26. - A/B remain durable after C fails; an ordinary later run retries C and then D
- Planning and execution share one pure pre-execution decision model while without invoking A/B.
actual results and persistence remain execution-owned. - Artifact failure and checkpoint failure leave no false current authority, and
- The runner reads as an explicit lifecycle with one session lock and one publish/notify cannot remain successfully current after aggregate failure.
session-manifest authority. - No analyze projection type, field, helper, application branch, rollback path,
- Runner, run-command, alias, and cleanup tests use invocation-scoped test fixture, or documentation claim remains.
dependencies rather than mutable package-global function substitution. - `go test ./internal/stage ./internal/app ./internal/manifest ./internal/artifacts`
- Current internal documentation accurately describes the implemented design succeeds.
at the appropriate level. - `git diff --check` succeeds.
- Workstream 5 and all six stages are marked `Complete`; Workstream 6 is the
next pending workstream. ## Stage 5 — Verify Force Semantics, Align Documentation, And Close Workstream 6
- `git status --short` is empty after the implementation commit.
**Status:** Pending
### Goal
Verify the complete operational contract, remove superseded test/documentation
language, and leave Workstream 6 closed with repository-wide evidence.
### Required Work
1. Review existing force/selection/reconciliation tests and retain a lean set
that proves:
- an ordinary run reuses every valid current selected artifact;
- unqualified force reruns the full effective configured target set;
- selected force reruns the explicit targets, includes their dependency
closure, reuses valid current prerequisites, and leaves unrelated valid
artifacts untouched;
- stale, failed, missing, or dependency-invalid prerequisites execute in
deterministic topological order before their dependents; and
- an incidental canonical file without current manifest evidence is never
reused.
2. Replace projection-oriented test names, helpers, and assertions with
checkpoint/recovery language. Remove redundant tests whose only purpose was
to validate the retired transport or rollback choreography. Do not reduce
coverage of output validation, dependency invalidation, cancellation,
persistence failure, or assembled resume behavior.
3. Update the canonical current-behavior owners:
- `docs/internal/stage-analyze.md` for checkpoint order, resume behavior,
failure semantics, and the absence of projections;
- `docs/internal/manifest.md` for per-artifact checkpoint authority,
aggregate status, interruption recovery, and persistence failure;
- `docs/policy/architecture.md` only where its stage/persistence description
currently implies a single end-of-stage analyze transaction; and
- `docs/operations.md` and `docs/troubleshooting.md` only where operators
need to understand reuse after partial analyze failure or recover from a
manifest checkpoint error.
Do not duplicate CLI or configuration contracts, since syntax and fields do
not change.
4. Search production, tests, and current-behavior documentation for
`AnalyzeStateProjection`, `AnalyzeState`, `analyze projection`,
`projected records`, and `end-of-stage` language. Remove or revise every
reference tied to the retired mechanism; preserve unrelated planning uses of
the ordinary English word “projection” only when accurate.
5. Run the full repository validation from `docs/development.md`:
```sh
go test ./...
go test -race ./...
go vet ./...
go build ./...
go test ./internal/doccheck
go test ./internal/config -run '^TestExamplesLoadAndValidate$'
```
6. Run `go test -race -shuffle=on -count=3 ./internal/stage ./internal/app` to
stress the changed recovery and persistence boundaries. Run the supported
release cross-build checks if an implementation change affects build-tagged
code; otherwise the repository build is sufficient for this workstream.
7. After all checks pass, mark Stages 1–5 in this document `Completed` and mark
only Workstream 6 in [`refactoring.md`](refactoring.md) `Complete`.
Workstream 7 must remain the next pending workstream.
### Exit Criteria
- Current documentation describes analyze as a session-manifest-backed,
per-artifact checkpointed queue and contains no retired projection behavior.
- The force, partial-selection, dependency, interruption, and persistence-error
cases are protected through stable behavioral boundaries.
- All repository validation and focused shuffled race checks pass.
- Workstream 6 is marked complete, Workstream 7 remains pending, and the
worktree contains no accidental generated or temporary files.
- `git diff --check` succeeds.
## Open Questions ## Open Questions
None. The approved audits, current policies, and Workstream 5 decisions resolve None. The audits, approved Workstream 6 scope, existing selection contract, and
the behavior, architecture, testability, and sequencing choices required for the fixed decisions above resolve the product and implementation choices needed
implementation. for the coding agent to execute this plan.