Files
narratio/docs/roadmap/audit-findings.md

146 KiB

Codebase Audit Findings

Status: in progress

This document is the working ledger and final report for the audit defined by the audit plan and audit sequence. The audit is investigative: candidate findings below are not remediation changes.

Audit Identity And Baseline

Field Value
Audited revision 74e2d21de5fb2ada0be5ef3fe9333e0d48ac7fb3 (Close the completed roadmap documents)
Branch main, attached worktree
Initial worktree state Untracked docs/roadmap/audit-plan.md and docs/roadmap/audit-sequence.md; no production or test changes
Audit date 2026-08-10 UTC
Toolchain go version go1.26.5 linux/amd64
Platform GOOS=linux, GOARCH=amd64
Repository root /home/eric/Workspace/narratio

The two initial untracked files are the audit specification supplied for this run. Adding this ledger and tracking those documents changes documentation only; all implementation and test evidence remains pinned to the revision above. If implementation or tests change, affected audit stages must be rerun and this section must record the new revision.

Baseline Commands

Command Result Wall time Evidence or limitation
go test -count=1 ./... pass 3.34 s All 23 packages passed; cmd/narratio has no test files.
go test -race -count=1 ./... fail 55.65 s Race in internal/adapters/whisperx.(*FakeClient).Transcribe at fake.go:45, reached concurrently by TestTranscribeStageTranscribesPreparedAudio; candidate TST-001. All packages reported before internal/stage passed.
go vet ./... pass 0.47 s No diagnostics.
go build -o "$audit_build_dir/narratio" ./cmd/narratio pass 1.03 s Built outside the repository in /tmp/tmp.x11pJL7014.
go test -coverprofile="$audit_build_dir/coverage.out" ./... pass 11.05 s Diagnostic coverage only; no percentage is treated as a gate.

Coverage ranged from 69.8% (internal/manifest) to 100% (internal/logging) among tested packages. cmd/narratio reported 0% because it has no tests. The remaining package results ranged from 70.1% to 88.1%. Stage 12 owns the risk-based interpretation; these numbers are inventory signals only.

Code Graph Freshness And Structural Inventory

The narratio graph was rebuilt in moderate mode after the revision was pinned. Its branch record reports the exact audited HEAD, main, and the repository root above. The index contains 2,407 nodes and 13,181 edges across 224 modeled files: 1,494 functions, 136 methods, 226 structs, 12 interfaces, and 20 modeled package nodes. The moderate filter excluded documentation, examples, .git, .codex, and cmd/narratio; the executable entry point was therefore verified through go list and direct inspection instead of graph evidence. Internal production code is represented at the pinned revision.

Repository inventory at that revision:

  • 23 Go packages, including cmd/narratio;
  • 221 tracked Go files and 95 tracked _test.go files;
  • 278 tracked files total;
  • one process entry point, cmd/narratio/main.go, delegating to internal/app.Execute;
  • 11 canonical stages returned by internal/stage.All; and
  • 12 modeled interfaces, of which 11 are Narratio boundaries and one is the private AWS S3 client seam.

Graph call tracing from internal/app.Execute confirms command dispatch into run, single-stage, clean, and session-helper paths, followed by configuration, artifact/path, manifest, stage, storage, restore, and cleanup owners. The production import inventory shows no lower-level package importing internal/app; apparent graph rollups such as stage -> app, adapters -> app, and config -> app came from test relationships or graph classification and are rejected as production dependency reversals at this mapping stage.

Metric Signals For Later Review

These are prioritization signals, not findings:

Signal Evidence Assigned review
High fan-in app.Error (207), stage Run symbols (151), app.Execute (108), stage.sessionPathsForEnv (105), manifest.New (72), manifest.MarkStageSucceeded (56), app.executeStages (48), artifacts.S3SessionPrefix (41), and artifacts.SessionWorkDirForCampaign (37) Owning behavior stages, then Stage 11
High complexity app.executeStages cyclomatic 54/cognitive 96; previouscache.BuildPlan 22/38; analyzeStage.Run 20/27; audita.NewSubprocessRunner 17/25; app.SessionInit 20/21 Stages 2, 5, 7, 10, then 11
Exact similarity app.Analyze/app.Publish, manifest.Load/LoadRun, manifest.Create/CreateRun, adapter constructors, and Seriatim fake methods Owning behavior stages, then Stage 11
Test-heavy hotspot noise Several test functions and fakes rank highly in transitive-depth and fan-in results Stage 12; do not infer production risk from the metric

Automation And Fixture Inventory

  • .woodpecker/release.yml is tag-only release automation. It cross-builds Linux, macOS, and Windows binaries with Go 1.25, then publishes release assets. It does not run tests, race tests, vet, or example validation.
  • examples/ contains 19 maintained files: pipeline, campaign, session, template, stable-input, and placeholder-audio fixtures. Configuration tests are documented as their validation owner.
  • No fuzz tests, golden files, golden-update switches, opt-in/live test tags, or go:generate test mechanisms were found.
  • Platform build constraints exist for the native no-replace directory tests and unsupported-platform fallback in internal/fileops.

Execution Coverage Ledger

Stage Status Evidence and result
0: baseline complete Revision/environment pinned; graph refreshed; inventories and every prescribed baseline command recorded. TST-001 owns the non-blocking race limitation.
1: contract and boundary map complete Canonical contracts and focused internal docs read; ownership, stage-contract, lifecycle, scenario, area, and preliminary risk-to-test matrices recorded below.
2: runner and manifest complete Full/single-stage entry paths, every lifecycle outcome, both manifest models/transitions, save disagreement states, canonical invalidation boundaries, and runner lock lifetime reviewed. Focused app/manifest test and race commands passed. Confirmed COR-001 and RSK-001; assigned DUP-001, SIM-001, COM-001, TST-002, and lock-release details to later stages.
3: paths and filesystem complete Canonical local/remote paths, every artifact source family, filesystem mutations, directory promotion, cleanup confinement, permissions, and lock mechanics reviewed. Focused normal and race commands passed. Confirmed COR-002, COR-003, RSK-002, RSK-003, and RSK-004; added DUP-002 and TST-003, and refined DUP-001.
4: publish and cleanup complete Publish prerequisites/source families, deterministic upload order, every partial remote outcome, retry semantics, effective locks, status/restore interpretation, and automatic/manual cleanup gates reviewed. Focused stage/app/artifacts/storage tests passed. Confirmed COR-004 through COR-007 and RSK-005; added ARC-003, COM-002, and TST-004.
5: restore and previous state complete Restore discovery/planning/execution/reporting, remote-current identity and scope, every local failure boundary, audio cache/spool identity, previous-cache planning/consumption, and status/validate policy reviewed. Focused app/previouscache/audio/artifacts/storage tests passed. Confirmed COR-008 through COR-011, RSK-006 through RSK-008, and EFF-001; added DUP-003 and TST-005.
6: configuration and composition not_started Assigned configuration, CLI composition, and process-boundary areas below.
7: adapters and shared support not_started Assigned external-boundary and cancellation areas below.
8: ordinary stages not_started Assigned prepare/transcript behavior and disabled-outcome questions below.
9: extraction not_started Assigned extraction promotion, provenance, and resume scenario below.
10: analyze and dependencies not_started Assigned artifact dependency/source and selection scenario below.
11: maintainability not_started Seeded by graph complexity, similarity, and fan-in signals only.
12: test policy not_started Seeded by intended owners and baseline execution observations.
13: synthesis not_started No final ranking or accepted-risk decisions yet.

Area Coverage And Ownership

Every area in the audit plan has a primary execution owner. assigned means it has been mapped but not behaviorally audited.

Inspection area Canonical implementation owner Primary audit stage Status
Process and application boundary cmd/narratio, internal/app 6 (runner lifecycle portions in 2; publish/restore portions in 4-5) assigned
Stage registry and runner internal/stage, internal/app 2 reviewed
Configuration internal/config 6 assigned
Prepare and audio internal/stage, internal/audio, internal/previouscache 8 assigned
Transcript stages internal/stage plus tool adapters 8 assigned
Extraction internal/stage, Notarius adapter, internal/fileops 9 assigned
Analyze and artifact dependencies internal/stage, internal/artifacts, internal/artifactpolicy 10 assigned
Publish and cleanup internal/stage, internal/app 4 reviewed
Manifest state internal/manifest, transition policy in internal/app 2 reviewed
Artifacts, paths, and policy internal/artifacts, internal/artifactpolicy, internal/pathsafe 3 (resolution consumption revisited in 10) reviewed
Restore internal/app, internal/artifacts, internal/previouscache, internal/audio 5 reviewed
File operations internal/fileops, internal/pathsafe, local artifact store 3 (promotion vertical slice in 9) reviewed
External adapters and storage internal/adapters, internal/audio 7 assigned
Shared models and diagnostics internal/artifactmodel, internal/contracts, internal/logging 7 (maintainability revisited in 11) assigned
Tests, examples, and automation package test owners, examples/, .woodpecker/ 12 assigned

Package And Interface Ownership Map

Package Owned contract or policy Important boundaries Audit owner
cmd/narratio Process entry and exit; CLI delegates behavior to app main -> app.Execute 6
internal/app Command dispatch, composition, locking, planning, lifecycle, restore, cleanup, reporting Execute, executeStages; consumes stage/artifact/manifest/adapter contracts 2, 4-6
internal/config Strict discovery, defaults, resolve, template, and validation rules Config models and load/resolve/validate functions 6
internal/stage Canonical order and stage behavior Stage, ResumeValidator, Env; adapter interfaces are injected 2, 4, 8-10
internal/manifest Session/run models, transitions, validation, atomic persistence Store; transition methods record but do not choose policy 2
internal/artifacts Artifact identity/resolution, paths/keys, local store, remote current-state mechanics Store; consumes explicit storage keys 3, 5, 10
internal/artifactpolicy Configured source/destination identity and safety policy Narrow validators used by config, artifacts, app, and stages 3, 10
internal/artifactmodel Shared serialized artifact, contract, and provenance models Data contract only 3, 7
internal/pathsafe Confined relative path and destination mechanics Narrow validation helpers; no stage policy 3
internal/fileops Atomic files, copies, hashing, no-replace directory promotion Filesystem mechanics receive explicit paths 3, 9
internal/previouscache Deterministic previous-session requirement planning/materialization Uses explicit object-store and artifact contracts 5, 8, 10
internal/audio S3 audio spool/cache materialization Uses storage.ObjectStore; no stage ordering 5, 8
internal/contracts Bounds and shared JSON validation models Data contract only 7, 8
internal/logging Shared logger construction slog composition 7, 11
internal/adapters/whisperx WhisperX HTTP protocol Client 7
internal/adapters/seriatim Merge/normalize/trim/render subprocess protocol Runner 7
internal/adapters/audita Audita subprocess protocol Runner 7
internal/adapters/scriptorium Scriptorium run/render subprocess protocol Runner 7
internal/adapters/notarius Notarius invocation and receipt boundary Runner 7 (vertical behavior in 9)
internal/adapters/notify Notification transport Sender 7
internal/adapters/storage Explicit bucket-relative object-store operations and S3 mechanics ObjectStore; private s3API test seam 7
internal/adapters/subprocess Shared bounded subprocess/config/log mechanics Concrete helper package, not stage policy 7

The graph reported no inbound production callers of Stage.Declares; text search found definitions and test stubs but no production invocation. This reduces the current impact of ARC-001 but makes the interface's intended owner and future use an explicit question rather than resolving the mismatch.

Stage Contract Matrix

The table separates declared/static contracts from dynamic behavior. All executed stages use the runner's session/run transitions. Unless noted, a successful result records returned outputs, diagnostics, generated configuration, and metadata; a different effective executed outcome can stale succeeded downstream work, while force pre-stales succeeded downstream work.

Order and stage Inputs and outputs Configuration and adapters Skip/resume behavior Materialization and manifest effects
1 prepare Config, stable inputs, one audio mode, optional previous requirements -> canonical inputs/**, audio/**, optional previous/**, manifest.inputs All resolved config; storage for S3/current previous state; audio/artifact/previous-cache services No stage-specific resume validator or explicit self-skip Writes canonical session inputs and deterministic input records; unlike processing stages, Declares labels produced canonical files as inputs.
2 transcribe Prepared FLAC files -> raw per-speaker JSON WhisperX URL/language/retry/timeout/concurrency; whisperx.Client Ordinary succeeded-record skip; no validator/self-skip Bounded concurrent run-local writes, validation, then canonical transcript materialization.
3 merge Raw transcripts, speakers, autocorrect -> base transcript, optional report Seriatim merge fields; seriatim.Runner Ordinary succeeded-record skip Normalized scratch inputs and run-local results validate before canonical transcript/report materialization.
4 polish Base transcript, glossary -> polished transcript, optional report Audita fields/credential reference; audita.Runner Ordinary succeeded-record skip Run-local output, report, logs, and generated config; validates before canonical materialization.
5 normalize Polished transcript -> final transcript, optional report Normalize plus Seriatim fields; seriatim.Runner Ordinary succeeded-record skip Manifest-first input; run-local validation then configurable canonical output/report.
6 trim Final transcript -> final-trimmed transcript and, when enabled, bounds Trim, bounds, Scriptorium, and Seriatim fields; both runners when enabled Disabled trim copies input and still succeeds; no explicit self-skip or resume validator Run-local bounds/trim result validates then materializes; debug render is diagnostic, not output.
7 extract Final-trimmed source -> immutable index and configured lane outputs Notarius executable/config/pipeline/timeout/output contracts; notarius.Runner Disabled is explicit notarius_disabled self-skip; only current ResumeValidator; obsolete reruns, unsafe validation errors Validates run-local receipt/bundle completely, promotes to unique immutable bundle, records checksums/contracts/provenance; identical repeated self-skip is stable.
8 render Final and final-trimmed JSON -> two Markdown transcripts Render and Seriatim fields; seriatim.Runner Disabled returns a zero-disposition result with skip metadata, therefore runner-level success rather than explicit self-skip; no validator Run-local text validates non-empty before canonical materialization when enabled.
9 analyze Dynamic built-in, prepared, extraction, configured, and previous sources -> selected configured artifact outputs Scriptorium artifact graph/selection; scriptorium.Runner Missing config or no executable artifacts returns success with skip metadata; no validator Topological run-local generation/reuse, validation, canonical outputs, deterministic metadata; static Declares omits dynamic outputs and several input families.
10 publish Session/run state, selected output rules, locks, previous cache -> remote run/output/current objects Publish/storage/selection fields; storage.ObjectStore Disabled publish or run upload returns ordinary success with skip metadata, not a self-skip; force cannot bypass locks; no validator Deterministic uploads; current/manifest.json before current/run_id.txt; post-commit local metadata gates cleanup. Static prerequisites omit extract because disabled extraction is valid and lane resolution enforces required extraction state when selected.
11 notify No implemented persisted pipeline input/output Optional notify.Sender; default no-op Ordinary succeeded-record skip; no explicit self-skip or validator Placeholder metadata and optional notification call; no returned output. Declares nevertheless advertises placeholder input/output paths.

Configuration, adapters, skip policy, and dynamic outputs are not represented by IODecl; their current canonical owners are the focused stage, configuration, and integration contracts. Whether IODecl should remain a partial display type or become an enforceable declaration is deferred as ARC-001.

Lifecycle Matrix

This began as the intended contract map and is now source-backed for both durable ledgers by the Stage 2 review.

Outcome Session manifest intent Invocation manifest intent Downstream and next-invocation intent
First run Pending/non-succeeded stage becomes running, then succeeded/failed/skipped; executing clears older result payload first New run record; action run; terminal status records this invocation Success enables later stages; failure stops current execution and an effective outcome change may stale succeeded downstream records.
Already-succeeded skip Existing succeeded session record and payload remain unchanged, subject to resume validation Action/status record a skip and stable reason for this invocation Reusable result remains authoritative; pipeline continues.
Explicit self-skip Session stage becomes skipped, clears older result payload, and may record bounded current skip details Action was run, outcome is skipped with reason Reconsidered later; a changed effective upstream outcome stales succeeded downstream work; identical extraction disabled skip is stable.
Failure Current stage becomes failed with error; current output/log/config/metadata payload is cleared Action run, failed outcome and overall failed run Current execution stops; affected succeeded downstream work is intended to stale; later invocation reruns non-succeeded stages.
Interruption Model admits interrupted, but production never writes it; process death leaves the last durable status running and the running transition has already cleared the target's prior result details The run remains non-terminal at its last durable per-stage state; no load or startup reconciliation changes it Non-succeeded session stages execute on the next included plan, so continuation is conservative; the old invocation record remains inaccurate under confirmed RSK-001.
Forced replacement Target execution starts fresh; succeeded downstream records are pre-marked stale; current target payload clears on running Force flag and run action recorded Replacement result determines later execution; locks and safety policy remain authoritative.
Non-resumable success Prior success becomes stale while retaining details long enough for diagnosis/validation, then running clears them Current invocation records execution after validation rejects skip Obsolete result reruns; unsafe inability to decide stops without silently replacing current success.
Successful rerun Target becomes succeeded with only new outputs/diagnostics/config/metadata Current invocation records its own new success; earlier run manifests remain unchanged A rerun after a non-succeeded state stales succeeded downstream work; force already stales it before execution. The runner does not compare output contents; identical repeated self-skip is the narrow no-invalidation case.

Stage 2 verified the matrix. The runner treats the session manifest as the only cross-invocation decision source and each run manifest as a record of one invocation. The resulting field behavior is:

  • entering running clears the session stage's former outputs, logs, generated configuration, metadata, completion, and error; success installs only the current result and clears the stage error, while failure and self-skip clear result data before bounded current skip diagnostics are reapplied;
  • staling deliberately retains prior result data and timestamps for diagnosis, changes status/error/updated time, and prevents the result from being reused;
  • an already-succeeded skip does not mutate the session record; the run record separately stores action skip, status skipped, and reason already_succeeded without copying the reusable outputs;
  • an executed self-skip stores action run and status skipped in the run record, so it remains distinguishable from an idempotent skip;
  • a stage failure marks the session stage and run stage failed, records the error in both ledgers, makes the run overall failed, and stops execution;
  • force is stored at run level and pre-stales all succeeded canonical downstream stages; non-resumable validation first stales and saves the target and succeeded downstream stages, then executes; and a successful execution following any non-succeeded prior state stales remaining succeeded downstream work; and
  • the session-level last_error is retained as historical information after a later success. No production reader treats it as current status; stage and run status are the operative fields.

The invalidation helper derives position from the complete canonical registry, not the selected plan. Consequently a single-stage replacement has the same downstream effect as that stage in a full run. prepare can invalidate every later succeeded stage and notify has no downstream target. Only succeeded records need explicit staling: failed, skipped, stale, pending, running, and interrupted records already execute on the next included plan.

Runner Entry, Lock, And Persistence Conclusions

Run validates the assembled configuration and selection, builds the full canonical plan, and delegates to executeStages. RunStage, Analyze, and Publish select one canonical stage; the latter two force it. Single-stage execution still uses the same lifecycle, session lock, invalidation, dual manifests, cleanup check, and final run transition as a full run.

The session lock is acquired after layout creation and before the session manifest is loaded, then held through stage execution, all manifest saves, post-publish cleanup, and the final run save. A competing same-session runner therefore cannot enter manifest decision-making while the first holds the lock. The deferred release error is discarded. Whether close/unlink failures can leave a blocking or misleading lock requires the filesystem implementation review assigned to Stage 3; the assembled runner suite has no concurrent-runner or release-failure case.

Both manifest stores use temp-file write, file sync, close, and same-directory rename, so an error before rename leaves the prior individual file in place. There is no atomic transaction or reconciliation protocol across the two manifest files. The runner saves the run record first when announcing execution, then saves the session record; for terminal outcomes it saves the session authority first, then the run audit. The possible durable states and their later interpretation are:

Failure boundary Durable state Later invocation behavior
New session or identity save fails before initial run save No run record; session is absent or remains at its prior contents. An identity-save attempt mutates the in-memory identity and updated_at, but none of it becomes durable. The command stops before a stage. A later invocation loads/creates from the last durable session state.
Initial run save fails after session identity save Session points at the new run ID, but that run's audit file may not exist. Session stage states still govern reuse; a later invocation creates a different run ID.
Resume validation reports unsafe/indeterminate Session success is preserved, but the already-created run remains overall running. The prior success remains authoritative and validation is attempted again; the abandoned run is never reconciled.
Saving non-resumable staleness fails Run remains initially running; session remains at the prior success. No stage executes. Validation is attempted again without silently replacing the prior success.
Saving an ordinary skip to the run file fails Session remains succeeded; the run file remains at its preceding state. The stage is safely reconsidered as another skip.
Run running save fails Session is unchanged and the stage does not execute. Session authority makes the next decision conservatively.
Session running save fails after the run save Run stage is running; session remains at its prior state and the stage does not execute. Forced downstream staleness is not durable. A non-succeeded target retries; a prior success skips unless force/resume validation again requires replacement.
Terminal session save fails after stage work Both durable records remain running, although the stage may already have external or canonical effects. The session stage reruns because running is not reusable. Stage-owned idempotency remains essential.
Terminal run save fails after terminal session save Session has the authoritative success, skip, failure, and downstream state; run stays running. Execution resumes safely from the session, but the historical run remains inaccurate.
Post-publish cleanup fails Session publish remains succeeded; a successful run save marks the invocation failed, while a failed run save leaves its prior overall running state. A normal later invocation skips publish, so the execution-based cleanup gate does not retry. A forced publish or explicit manual clean is required; confirmed COR-006.
Final overall run save fails Session and per-stage run records are terminal, but overall run status remains running. A later invocation skips or reruns from session state and does not repair the old run.

Save errors are returned with both the stage error and persistence error when both exist. The session store is injectable, but run persistence is a concrete LocalStore outside the manifest.Store interface. This leaves the run-side failure rows above unexercised by focused runner tests and makes centralized terminalization/reconciliation harder to test.

Lifecycle Scenario Conclusions

  • Scenario 1 is functionally safe for reuse: a non-resumable success and its succeeded downstream records are persisted stale before execution; a failed rerun leaves the target failed and downstream stale; an ordinary retry runs both. An inability to validate preserves the prior success rather than replacing it. RSK-001 records the inaccurate invocation audit left by that controlled error.
  • Scenario 2 is conservative and source-backed: force pre-stales succeeded downstream work; failure and a changed effective outcome stale it; a changed self-skip stales it; and an identical repeated self-skip does not. Here identical means prior status skipped, zero outputs, and the same reason; diagnostic/metadata differences are not compared. Disabled stages represented as success remain reusable success, while an explicitly skipped downstream stage is naturally reconsidered because only success is ever skipped. The runner-level distinction is coherent; ARC-002 remains assigned to Stages 4 and 8 for the stage-specific contract and wording.
  • The lock portion of Scenario 10 is resolved at the application boundary: acquisition occurs before manifest access and the lock spans the entire mutation lifetime. Stage 3 must decide the ignored-release and underlying lock-file questions.

Cross-Boundary Scenario Assignments

Scenario Primary audit stage Supporting packages and focused tests
1. Success becomes non-resumable, rerun fails, later reuse decision 2 internal/app, internal/manifest, stage.ResumeValidator; runner_test.go, extract_lifecycle_test.go, manifest transition tests
2. Forced/changed upstream outcome with succeeded, self-skipped, disabled downstream 2 internal/app, internal/stage, internal/manifest; run-control, runner, extraction-lifecycle tests; disabled-stage detail revisited in 8
3. Extraction bundle followed by configuration/transitive-input change 9 Extract/resume, Notarius adapter, artifacts/fileops tests; downstream resolution revisited in 10
4. Published/restored/prepared previous state consumed locally by analyze 5 internal/app, internal/previouscache, internal/audio, internal/artifacts; restore/prepare tests; analyze consumption revisited in 10
5. Publish failure at every upload boundary, then status/restore/retry 4 Publish stage, storage fake/adapter, app status/restore; publish and operator-helper tests; restore interpretation revisited in 5
6. Restore identical/conflict/unsafe/cache/pre-manifest-install cases 5 Restore discovery/plan/execute/report, artifacts, previouscache, audio; restore test suite
7. Cleanup after skipped/failed/locked/partial/committed publish 4 Publish metadata, post-publish cleanup, cleanup targets, pathsafe; publish/cleanup tests
8. Cancellation through workers, HTTP, subprocess, storage, manifests 7 Adapter and subprocess tests; transcribe/stage tests in 8; runner reporting in 2
9. Disabled/unselected/reused/generated/extraction/previous source then publish filtering 10 Analyze, artifact catalog/resolver/policy, publish tests; config ownership in 6 and publish result in 4
10. Concurrent same-session invocation and lock cleanup failures 3 Runner lock lifetime in 2; local artifact store, path/file cleanup and lock tests in 3

Preliminary Risk-To-Test Matrix

This matrix identifies intended owners only. It makes no sufficiency judgment.

Architectural invariant or risk Implementation owner Intended test owner
One deterministic canonical stage order internal/stage, planner in internal/app internal/app/planner_test.go, narrow registry tests
Session manifest is cross-invocation authority; run manifest is immutable invocation audit internal/app, internal/manifest Manifest transition tests plus assembled runner/run-stage tests
First run, skip, self-skip, failure, force, invalidation, and rerun transitions internal/app, internal/manifest App lifecycle tests as primary; manifest helpers own field mutation
Obsolete versus unsafe resume validation Stage-specific ResumeValidator, runner Extract resume tests plus runner integration tests
Run-local validation before canonical materialization Individual stages and run_local.go Focused stage package behavioral tests; fileops owns atomic mechanism
Strict config, defaults, identity, and cross-field validation internal/config Config package tests; example load/validation test samples assembly
Canonical path/key ownership and traversal confinement Artifacts, artifactpolicy, pathsafe Owning package tests; app/stage tests only for assembled policy
Immutable extraction promotion and provenance/checksum validation Extract, fileops, artifacts, Notarius adapter Fileops mechanism, extract behavior, artifact hydration, adapter contract tests
Deterministic artifact dependency and source resolution Artifacts, artifactpolicy, analyze Artifact/package tests and analyze package behavior tests
Previous-session consumption remains local in analyze Previouscache/prepare/artifacts/analyze Previouscache and prepare tests; one analyze boundary test for no remote call
Remote current pointer is publish's final commit point Publish stage Publish tests with stateful object-store fake; storage tests own transport only
Restore is confined, deterministic, conflict-safe, and installs manifest last Restore app modules, artifacts/previouscache/audio Restore plan/execution/workflow tests plus low-level path/file tests
Cleanup requires explicit scope and committed publish metadata App cleanup modules, pathsafe Cleanup-target and post-publish integration tests
Session single-writer lock and safe release Local artifact store, app lifetime Artifact local-store tests plus assembled concurrent runner tests
Adapter cancellation, error adaptation, and resource closure Each adapter and shared subprocess package Focused adapter boundary tests; stage tests sample propagation
Bounded deterministic transcription concurrency Transcribe stage and WhisperX client Stage concurrency/result-order tests; HTTP adapter retry/cancel tests
Secrets never persist or appear in diagnostics Config/app composition and each adapter/logging boundary Owning config/adapter tests plus selected assembled redaction checks
Default suite remains deterministic, offline, and credential-free Every package; automation Stage 12 repository-wide execution and test-policy audit

Stage 2 test observations for this matrix:

Risk Existing focused protection Gap or disposition
Normal lifecycle and payload clearing Manifest helper tests plus runner/run-stage/extraction-lifecycle tests cover first success, existing-success skip, force, failure, self-skip, repeated self-skip, unsafe and obsolete resume validation, retry, and canonical downstream invalidation. Strong behavior coverage for successful persistence; no finding.
Session/run invocation identity Per-invocation runner test asserts distinct run IDs, manifest paths, and the latest session run_id. It does not assert refreshed local/spool/remote derived fields or reject loaded identity conflicts; required by COR-001.
Partial persistence and handled pre-stage errors Session manifest is injectable and the unsafe-resume test proves old success is preserved. Run persistence is concrete; no disagreement-boundary tests and no terminal run assertion on resume error; candidate TST-002.
Interruption and restart Non-succeeded action logic and retry tests indirectly prove running is rerunnable. No kill/reload normalization, reconciliation, or abandoned-run status test; confirmed RSK-001.
Same-session concurrency Artifact store has focused lock tests. No assembled concurrent runner or release-failure test; Stage 3 owns the mechanism and sufficiency decision.

Path, Artifact, Filesystem, And Lock Conclusions

Canonical ownership and normalization

internal/artifacts/paths.go owns the campaign/session layout, run-local layout, previous cache, Notarius bundle, spool, and audio-cache constructors. internal/artifacts/s3_keys.go owns session/run/current and published-output keys. internal/pathsafe is the shared lexical boundary for slash-normalized relative destinations: it rejects empty, absolute, drive-qualified, traversal, and leading-backslash forms, normalizes mixed separators, and verifies a joined destination remains lexically under its root. internal/artifactpolicy adds configured source/destination policy without performing filesystem I/O.

Most production callers consume those owners directly. Previous-cache planning normalizes configured and manifest-derived relative paths before calling the path constructor, and restore derives a normalized relative path before installing it. One ad hoc reconstruction remains in publish: resolvePublishRunManifestSource joins the literal manifest.json to an already-derived run root instead of using the canonical run-manifest helper; DUP-002 assigns that maintainability decision to Stage 11.

The constructors themselves do not enforce that campaign, session, run, or artifact-relative components are safe opaque segments. Configuration currently checks session identifiers and campaign identity only for presence. As a result, traversal-bearing operator identity reaches both local filepath.Join and remote path.Join; COR-002 records the concrete namespace escape. The exported previous-artifact helper also promises a path under previous/artifacts while accepting a traversal value, although its current production callers validate first. That helper-level contract should be fixed with the same boundary rather than treated as a separate defect.

Artifact resolution

The complete resolution chain is deterministic and matches the documented source-family distinctions:

  • built-ins normalize against a fixed registry, prefer matching producer outputs from the manifest, validate content shape, then use a canonical session fallback; missing state returns SessionArtifactNotFoundError;
  • configured sources resolve only through explicit runtime-catalog availability, preserve generated-versus-disabled-file provenance, validate non-empty text, and return the same typed missing condition when planned but unavailable;
  • extraction hydration requires a successful current extract record, exact configured definitions, producer/receipt identity, compatible contract and external provenance, confined regular files, a complete index/lane set, and matching checksums before making any source available;
  • previous-session planning sorts requirements and records, validates relative cache destinations, loads current remote state with expected campaign, session, and run identity, and hydrates only explicit remote objects into the local cache. Analyze-time resolution is then local-only, preferring a manifest input path before the documented filesystem fallback and returning a typed missing error; and
  • current-state loading has distinct typed missing-pointer and missing-manifest errors, rejects empty or malformed state, and validates expected campaign, session, and run identity or pointer/manifest run consistency.

Only extraction claims checksum/contract/external-provenance enforcement at resolution time, and its implementation enforces all three. Built-in, configured, and previous-cache resolvers enforce their documented content and availability rules but do not re-hash ordinary manifest records. That is not a Stage 3 contract mismatch; Stages 5 and 10 remain responsible for deciding whether restore or analyze threat models require stronger trust than the documented previous-cache fallback and manifest-aware built-in behavior.

Filesystem mutation mechanics

The mutation inventory separates lexical policy from explicit-path mechanics:

Mechanism Normal-process atomicity and cleanup Confinement and durability conclusion
WriteFileAtomic Same-directory temporary file; write, file sync, close, chmod, rename; removes a failed temporary file; replaces an existing destination where the platform rename permits it. Accepts an explicit destination as intended, but follows symlinked destination ancestors and does not sync the parent after rename.
CopyFileAtomicWithChecksum / CopyFileAtomic Opens the explicit source, copies and hashes through a same-directory temporary file, syncs/closes/chmods, then renames; prior destination survives failures before rename. Does not require a regular source, follows source and destination symlinks, and does not sync the destination parent. Current canonical materialization callers validate content but inherit destination confinement and crash-durability findings.
InstallDownloadedTempFile Chmods and renames a caller-supplied sibling temporary file; same-filesystem placement is established by current audio, previous-cache, and restore callers. Does not sync the downloaded file or destination directory. The object-store downloads close their path-based files but expose no completed-data sync guarantee.
PromoteDirectory Copies to a temporary sibling, permits only regular files/directories, uses confined source handles and identity checks, syncs files/directories, atomically installs without replacement, syncs the destination parent, cleans failures, and reports unsupported platforms before creating the promotion tree. Source replacement, source symlinks/non-regular entries, existing or concurrently-created destinations, same-filesystem placement, and platform behavior are strongly handled. Destination ancestors remain path-based and can be symlinked or replaced.
Cleanup removal Refuses empty roots/targets, lexical root deletion and traversal, wrong final-node type, and a final target that is a symlink. Missing targets are idempotent. It does not inspect root or ancestor components and performs RemoveAll after a path-based check, so a symlinked ancestor can redirect deletion outside the root.

Low-level file and storage helpers correctly accept explicit destinations and do not infer stage, workspace, or cleanup policy. Policy belongs in their callers. The current caller-side checks are lexical and final-node checks, however, so they cannot prevent destination-ancestor symlink traversal or replacement. COR-003 consolidates the write, promotion-destination, and destructive-cleanup variants under that single root cause; later stage and adapter audits should reference it rather than duplicate it.

Single-file writers preserve the prior visible file on failures before rename, but unlike directory promotion they do not sync the containing directory after rename. Download installs additionally do not establish a file-sync boundary. RSK-002 records the resulting crash/power-loss durability gap for canonical files and manifests. Stage 3 also confirmed that DUP-001 duplicates this same incomplete persistence sequence across both manifest save paths.

Runtime layout and copied/generated files use fixed requested modes 0755 and 0644 subject to process umask, including transcripts, artifacts, logs, manifests, and the lock. No operating contract requires a restrictive umask or documents an ownership/mode boundary. RSK-004 records the private-data exposure risk rather than assuming a deployment-specific parent directory is always restrictive.

Lock scenario and focused tests

The local lock is an atomically created O_CREATE|O_EXCL sentinel. This is sufficient to serialize two live contenders before either reads a manifest, and the existing focused conflict test exercises that mechanism. It is not an OS-owned advisory lock: any existing sentinel conflicts without interpreting the recorded PID/time, process death cannot remove it, and recovery is manual. Release closes then unlinks, but the runner discards its error. An unlink failure can therefore let a command report success while every later invocation is blocked. RSK-003 resolves Scenario 10 and the Stage 2 release question.

Existing focused tests strongly cover lexical mixed-slash/traversal handling, artifact-source ordering and typed missing states, extraction trust checks, current-state identity, ordinary atomic replacement/temp cleanup, source-side promotion races and special files, no-replace installation, permissions, and basic lock acquire/conflict/release. They do not cover unsafe identity segments, destination-ancestor symlinks or replacement, cleanup through a symlinked ancestor, directory-sync/file-sync guarantees, stale-lock ownership/recovery, release failure, or assembled concurrent runners. TST-003 assigns a narrow cross-package regression set to Stage 12; each confirmed finding below also identifies its smallest behavior-level test.

Publish, Remote Commit, Lock, And Cleanup Conclusions

Prerequisites, sources, and deterministic order

Enabled publish requires successful prepare, transcribe, merge, polish, normalize, trim, render, and analyze session records before any remote write. extract is deliberately absent: disabled extraction is valid, while an explicit required extraction output still fails artifact resolution unless the current extraction state is complete and trusted. Publish disabled or upload_run disabled returns ordinary stage success with skipped metadata; it is not the self-skip claimed by the focused stage document. COM-002 records that contract mismatch and resolves the publish portion of ARC-002.

Publish builds all plans before uploading. Configured-artifact selection applies only to narratio.artifact.* rules; built-in and extraction rules are unaffected. Static and remote locks are merged before execution, with static rules winning. A matching lock intentionally skips an output even when required and preserves an existing published destination. An unselected configured output is recorded as unselected before lock handling. Missing unlocked optional outputs skip; missing unlocked required outputs fail. --force changes runner reuse and invalidation only: it never enters output resolution and cannot bypass either kind of lock.

The upload sequence is deterministic:

  1. run files sorted by slash-normalized relative path;
  2. resolved published outputs in validated configuration order;
  3. previous-cache files sorted by relative path;
  4. the fixed current/manifest.json; and
  5. current/run_id.txt, always the final object-store call.

The run archive includes manifest.json, receipt/stderr diagnostics, and every other non-directory entry except exact audio/** and extract/notarius-output/** paths. Similarly named paths remain included. Audio is not uploaded. Previous-cache inclusion recursively includes every non-directory entry under the session's durable previous directory. Neither walk rejects symlink entries before storage opens the local path, which is the confidentiality defect in COR-005.

Remote-current authority and partial outcomes

The storage boundary accepts explicit local paths and keys and implements upload as unconditional object replacement. It has no transaction, conditional write, generation check, rollback, or indeterminate-outcome query. Those are publish protocol responsibilities rather than storage policy. Every successful upload before the pointer is durable partial state; retry uploads the complete plan again and overwrites the same destinations. Partial run/session objects are not removed. COR-001 separately means a later invocation can combine a new run ID with a stale remote run prefix.

LoadCurrentState reads the pointer first, then the fixed manifest, and can reject pointer/manifest run disagreement when its caller requests run validation. Previous-cache planning does request it; Stage 5 found that the shared restore/status discovery path does not. Thus the pointer is the intended commit marker, but COR-008 confirms that two primary readers can accept a manifest from a different run. Independently, overwriting the fixed manifest before the pointer does not preserve the prior coherent pair; COR-004 owns that publish-side root cause.

Publish boundary or durable remote state Remote-current interpretation Retry and cleanup consequence
Planning/prerequisite/source resolution fails No upload occurs; any prior pointer/manifest pair remains current. Non-succeeded local publish reruns; cleanup is ineligible.
A run, published-output, or previous-cache upload fails Earlier objects remain or overwrite existing keys; the prior current pair is still intact because the fixed manifest was not reached. With no prior pair, no current state exists. Retry unconditionally reuploads the whole plan. Cleanup is ineligible.
Current-manifest construction fails All planned data objects may exist, but the prior current pair remains intact. Same retry behavior; cleanup is ineligible.
Current-manifest upload definitely fails before remote acceptance The prior pair remains intact; no pointer attempt occurs. Same retry behavior; cleanup is ineligible.
New current manifest is accepted while the old pointer remains First publish has a manifest but no pointer and restore fails on the missing pointer. A replacement publish has old pointer/new manifest disagreement: previous-cache loading rejects it, but restore/status discovery accepts it under COR-008. A successful retry repairs the pair; cleanup is ineligible until then. This includes the ordinary window between the final two uploads.
Pointer upload returns an error If not accepted, the disagreement above remains. If the service accepted the write but the response was lost, remote current is committed although local publish is marked failed. The interface cannot distinguish these outcomes. Retry is conservative and overwrites again; automatic cleanup does not run for the failed local outcome.
Pointer upload succeeds Pointer and manifest identities agree and the new run is current. The stage returns uploaded=true and current_pointer_written=true; automatic cleanup may become eligible.
Remote commit succeeds, then terminal session/run persistence fails Remote current remains committed. A session-save failure leaves local publish running and causes republish; a run-save failure leaves session publish succeeded and ordinary retry skips it. The latter boundary can permanently miss automatic cleanup under COR-006; remote state itself remains authoritative.

The current manifest is generated before commit with current_pointer_written=false, while the local post-upload result records the same field as true. Current-state readers correctly derive authority from the actual pointer and ignore that remote metadata, and cleanup correctly uses the post-commit local record. ARC-003 records the future-consumer ambiguity rather than treating the precommit snapshot as remote authority.

Lock authority and cleanup truth tables

Loaded static and remote locks are honored even for forced publish, but the remote lock store is an unsynchronized snapshot. Publish loads it before the local session lock, and lock add/remove performs an unconditional read-modify- write without a remote generation check or the runner's session lock. Concurrent operator mutations can lose one another, and a lock added after publish's load does not protect that in-flight upload. RSK-005 records this limit; sequential lock behavior and static precedence are otherwise correct.

Automatic cleanup uses the following gate. “Confined” here means the existing lexical/final-target validation; symlinked ancestors remain the shared COR-003 defect, and stale run-derived targets remain a COR-001 consequence.

Policy and local publish record Automatic action
Both cleanup policies false No cleanup lookup, deletion, or metadata mutation.
Either policy true, but publish absent from this invocation's executed list No cleanup, even if the session publish record is succeeded and records a commit. This one-shot behavior is COR-006.
Publish executed but its session status is not succeeded No cleanup.
Publish succeeded, but publish/upload is disabled, skipped=true, uploaded is not true, current_pointer_written is not true, or pointer key is empty No deletion; cleanup-skip reason is persisted.
Publish succeeded with enabled upload, uploaded=true, pointer true/key present, and spool policy true Validate the run-scoped spool target beneath the configured spool root, then delete it. Spool-only completion metadata is persisted.
The same commit gate with workspace policy true Validate the run-scoped work target beneath the workspace root, then delete it. Completion metadata is only mutated in memory and is lost under COR-007.
Either requested deletion fails Record best-effort failure metadata and fail the invocation. The succeeded publish remains reusable, so ordinary retry does not retry cleanup (COR-006).

Manual clean is a separate explicit operator boundary and correctly does not require a prior publish or commit metadata. Session cleanup requires an explicit campaign/session resolution and deletes the confined session work and spool trees. clean --all rejects session/campaign selectors, deletes the confined workspace work tree and non-symlink spool-root children, and optionally clears the configured audio-cache namespace. Cache deletion requires --clear-cache; --dry-run performs validation and reports without removing. There is no clean force flag. Publish --force therefore cannot reach or weaken manual or automatic target validation.

Focused tests cover successful ordering and exact exclusions, source-family selection, required/optional/locked rules, force with a preloaded remote lock, pointer absence on selected failures, ordinary cleanup eligibility, unsafe final targets, and manual dry-run/scope behavior. They do not seed a prior current pair across a manifest/pointer failure, model ambiguous upload success, exercise retry after partial commit, reject symlink upload sources, assert workspace-cleanup metadata durability, retry failed cleanup, or model concurrent remote lock writers. TST-004 assigns the smallest stateful protocol cases to Stage 12.

Restore, Audio, And Previous-State Conclusions

Discovery authority and caller policy

Restore resolves configuration and storage, then delegates pointer/manifest loading to artifacts.LoadCurrentState. Campaign and session expectations are always checked. The helper's run check is optional, however: previous-cache planning and previous-readiness inspection set ValidateRunID=true, while restore/status discovery does not. Missing pointer or manifest is fatal to restore, displayed non-fatally by status, and is skipped only when every previous-artifact requirement is optional. Malformed or inconsistent previous state remains an error even for optional requirements. Those distinct missing- state policies are appropriate; omitted restore/status run validation is part of the broader snapshot defect in COR-008.

Restore then lists the entire session prefix instead of deriving its file set from the discovered manifest. It maps the fixed current manifest to local manifest.json, includes transcripts/** and artifacts/**, optionally includes audio/**, and excludes current/run archives, logs, reports, generated configuration, inputs, and the current session's archived previous/**. Required previous-session cache objects are planned separately from that prior session's current manifest. Lexical path normalization and root-relative joins reject traversal and produce deterministic local paths; COR-002 still owns unsafe identity components and COR-003 owns filesystem-link confinement.

Prefix-wide listing is not a committed snapshot. Failed publish objects, stale destinations left by older runs, and locked/unselected leftovers can all enter a restore even when absent from the accepted manifest. Execution later downloads the same mutable keys again, and downloaded current/previous manifests are not revalidated against the discovered run ID. COR-008 consolidates these scope/version/run-binding failures and corrects the Stage 4 reader truth table.

Planning, force, dry-run, and local serialization

Actions sort by local relative path and then remote key. Classification is:

Local target and remote metadata Without force With force
Missing download download
Directory where a file is expected conflict Still conflict; command-level handling is defective under COR-009.
Audio with positive remote size and equal local size skip_same without content comparison Same skip_same; force does not refresh it.
Audio size mismatch or unavailable remote size conflict download
Non-audio positive-size mismatch conflict download
Non-audio equal/unknown size and equal downloaded checksum skip_same skip_same
Non-audio equal/unknown size and different downloaded checksum conflict download

Normal differing files therefore require explicit force and identical files remain untouched. Audio's size-only shortcut and cache validation are the integrity risk in RSK-007. Final symlinks are followed by os.Stat and can be classified as same; ancestor/final-link trust should be repaired with the shared filesystem capability required by COR-003 rather than with restore-only lexical checks.

Dry-run returns after discovery, planning, and summary rendering. It does not create the session layout, acquire a lock, write a report, populate audio cache or spool, install a file, or mutate remote state. It does download pointer, manifest, and same-size non-audio bodies to system temporary files, which are removed. Thus it is durable-session pure, not literally free of local temporary writes. The same checksum classification can download a differing object once during planning and again during forced execution; current and previous manifests are also downloaded at discovery/planning and again for installation. EFF-001 records this avoidable I/O and the documentation precision issue.

Executable restore acquires the local session lock only after its plan is complete. It does not reclassify skip_same, conflict, or download decisions under the lock. Together with incremental installation and intentional lack of rollback, this creates the coherent-local-transition risk in RSK-006. --force cannot bypass path joining or the local session lock, but the command only blocks conflicts when force is false. A directory conflict therefore survives a forced plan, is ignored by execution, and can coexist with a successful report and newly installed manifest (COR-009).

Execution, manifest-last behavior, and retry

Execution filters to download actions, preserves their sorted order, moves the single current-session manifest action to the end, and rejects multiple manifest downloads. Ordinary files download to sibling temporary files and install by rename. The manifest temp is decoded and checked for requested and discovered campaign/session before rename, but not for the discovered run ID. Audio delegates to the shared spool/cache materializer. A successful execution then writes reports/restore-latest.json; the report is diagnostic and is the only intended write after manifest installation.

Failure boundary Durable local result and retry behavior
Discovery or planning No session layout/report/restored file is written; system temporary reads are cleaned. Fix remote/config state and retry.
Dry-run No durable session mutation; reported actions are recomputed on apply.
Layout or lock acquisition Layout creation can precede a lock failure, but no planned file or report is installed. Retry after lock recovery.
Unforced conflicts No planned file is installed; a failed conflict report is persisted under the lock. Resolve or retry with force.
Non-manifest download/install Earlier installs remain; the old local manifest remains; the failing temp is removed and a failure report is attempted. Retry reclassifies completed files as same.
Audio download/validation/materialization Download failure removes its temp and preserves any prior spool destination; a post-download validation failure can leave the newly installed invalid spool file. A destination copied before cache-population failure also remains. Retry overwrites/reuses those states deterministically.
Manifest download/decode/identity/install All earlier files remain; failures before rename preserve the prior local manifest. Retry must complete the remaining plan; forced partial replacement can make the old manifest describe changed files (RSK-006).
Manifest installed, report write fails Restored durable state is installed and authoritative despite command failure; report may be absent/stale. Retry normally classifies files as same and can recreate the report.
Report succeeds, summary write fails Restore state and success report remain complete; only command output failed.

There is no rollback, transaction marker, or runner check for an incomplete restore. Manifest-last prevents a new remote manifest from being installed before its files, but it cannot keep the old local manifest coherent after a forced partial overwrite. Individual rename visibility is good in an ordinary process; RSK-002 remains the shared crash-durability gap for downloaded-file, manifest, and report installation.

Audio and previous-cache identity

audio.MaterializeS3Audio is correctly shared by prepare and restore. Its cache namespace includes bucket and full object key. A cache miss downloads through a sibling spool temp, validates non-empty/expected size, atomically copies to the destination with a computed checksum, then optionally populates cache. A cache hit avoids storage and copies directly. The stored ETag and computed checksum are not bound to cache validity: any nonempty cache file of expected size is accepted, and existing restore audio of equal size is skipped without reading either body. RSK-007 records stale/corrupt same-size reuse. Failed downloads clean temporary files, while post-install validation/cache failures leave the explicit partial states in the table above.

Previous requirements come only from enabled configured artifacts, deduplicate by artifact name with required winning, and sort deterministically. With no previous session ID, required requirements fail and optional ones skip. With an ID, planning strictly validates campaign/session/pointer-run identity, always maps the previous current manifest into previous/manifest.json, and maps available artifact objects beneath the current session's previous/ cache. Missing pointer/manifest or artifact objects fail required requirements and skip optional ones. Prepare consumes the same plan but intentionally overwrites its managed cache and records checksums/inputs; restore applies local conflict policy. Analyze later resolves these files locally without storage, preferring a matching main-manifest input and otherwise using the deterministic cache path.

The remote manifest does not retain a source-to-destination map for ordinary published outputs. Previous planning tries the producer's local relative path, then any published_paths entry with the same basename, then the current artifact output path. A custom publish destination with a different basename is unresolvable; duplicate basenames are ambiguous. COR-011 owns that identity loss. Separately, status/validate only prove that the prior current pair exists: they neither apply BuildPlan's artifact-object checks nor its optional missing- ID policy, so they can report missing optional state as an error or missing required objects as ready (COR-010).

Restored manifests preserve remote host-local absolute path fields. Top-level run/work/spool fields trigger the already confirmed COR-001 on the next invocation. Output/input records are also trusted preferentially when their old absolute path happens to exist, allowing later consumers to read outside the new workspace instead of the restored canonical copy; RSK-008 records this distinct restored-reference risk.

Focused tests strongly cover typed current-state failures when run validation is enabled, restore campaign/session mismatch, default/include-audio mapping, traversal rejection, normal same/conflict/force actions, deterministic previous requirements, required/optional missing remote state, cache miss/hit/refresh, download cleanup, manifest-last validation, conflicts, lock failure, reports, and restore-to-run/analyze workflows. They omit the exact committed-snapshot, forced-directory, plan-under-lock race, partial forced rollback, same-size audio mutation, custom publish destination, optional operator-readiness, and foreign absolute-path cases above. TST-005 assigns one stable behavior test per root invariant to Stage 12.

Confirmed Findings

COR-001: session identity initialization preserves stale invocation paths and accepts conflicting identity

  • Category: confirmed correctness defect.
  • Locations/invariant: internal/app/runner.go in ensureManifestIdentity and syncRunManifestIdentityFromSession; consumers include prepare's work/spool resolution, publish's run prefix, and post-publish cleanup. The session and invocation manifests must describe one internally consistent campaign, session, and run.
  • Evidence: every invocation replaces Manifest.RunID, but LocalWorkDir, LocalSpoolDir, and S3RunPrefix are computed only when empty. A second invocation therefore records run B while retaining paths and the remote prefix derived from run A, and copies those stale values into run B's manifest. The same helper fills an empty campaign but neither rejects nor reconciles a loaded campaign/session that conflicts with the configured manifest path. TestExecuteStagesCreatesRunManifestPerInvocation proves IDs and run-manifest paths differ but does not assert the dependent identities.
  • Realistic scenario: a forced second prepare writes through run A's work/spool identity; a forced second publish can target run A's remote prefix while its manifests claim run B. A misplaced or incorrectly restored manifest can also make the runner hold session A's lock while stages derive paths from the manifest's session B identity.
  • Impact/likelihood/confidence: high integrity impact; stale derived identity occurs on every ordinary second invocation after the fields are initialized, while a conflicting loaded identity is less common; high confidence from the assignment guards and direct consumers.
  • Estimated remediation scope and owner: small-to-medium application/manifest change. Define whether run-scoped locations belong in the session manifest, recompute them as one identity unit whenever the run changes, and reject configured/persisted campaign or session conflicts before stage execution.
  • Test changes: extend the existing per-invocation test to load both manifests and assert every run-derived field against run B; add a loaded-identity mismatch test that proves no stage or cross-session path is touched. Stages 3 and 4 should add the path and remote-prefix boundary assertions after their focused review.
  • Dependencies: Stage 3 owns exact path confinement consequences, Stage 4 owns publish/cleanup impact, Stage 5 owns restored-manifest provenance, and Stage 6 owns configuration identity validation. They should reference this root finding rather than create duplicates.

COR-002: unsafe identity components escape canonical local and remote namespaces

  • Category: confirmed correctness/security defect.
  • Locations/invariant: internal/artifacts/paths.go canonical constructors, internal/artifacts/s3_keys.go in S3SessionPrefix, and internal/config/validate.go in validateCampaign, validateSession, and validateSessionIdentifier. Canonical identities must be opaque safe segments, and traversal must not broaden local or remote operation scope.
  • Evidence: campaign, session, and previous-session identifiers are checked only for non-emptiness. Path/key constructors clean and join the raw values without rejecting ./.., separators, drive forms, or traversal. A value such as ../../outside therefore changes the cleaned local workspace/spool destination and the S3 namespace. EnsureLayoutFor also validates only presence before creating the derived directories. The exported SessionPreviousArtifactPath* helpers similarly claim an under-root result but accept traversal; current production callers happen to validate their relative artifact values first.
  • Realistic scenario: an operator typo, generated session config, or untrusted restored identity containing traversal makes prepare create/copy files outside the intended workspace or spool root. Publish can read or write a different campaign/session prefix, potentially colliding with unrelated remote state.
  • Impact/likelihood/confidence: high local and remote integrity/confidentiality impact; low-to-moderate likelihood because ordinary date-like IDs are safe but configuration is operator-controlled; high confidence from direct data flow into canonical joins.
  • Estimated remediation scope and owner: small-to-medium shared config/artifacts change. Define one strict opaque-segment contract, validate identities before layout/key construction, and make canonical helpers fail closed rather than return an escaped path. Decide compatibility for existing non-segment IDs explicitly in Stage 6.
  • Test changes: table-test separator, traversal, absolute/drive, dot, and mixed- slash identities at the configuration boundary; add artifacts tests proving no local directory or S3 key can escape its expected namespace. Add a direct unsafe-relative test for the exported previous-artifact constructor if it remains public.
  • Dependencies: Stage 6 owns configuration compatibility and validation. COR-001 separately owns conflicts among otherwise valid identities; COR-003 owns symlink-based escape after lexical identities are safe.

COR-003: filesystem mutation confinement follows symlinked destination ancestors

  • Category: confirmed correctness/security defect.
  • Locations/invariant: lexical joins in internal/pathsafe, path-based writers in internal/fileops/fileops.go, destination setup in internal/fileops/directory.go, local layout/copy operations in internal/artifacts/local.go, and cleanup validation/removal in internal/app/cleanup_targets.go, clean.go, and post_publish_cleanup.go. Writes, replacements, promotions, and deletions must remain beneath an explicit root despite symlinks or replacement races.
  • Evidence: lexical Rel checks cannot observe filesystem links. File writers call MkdirAll/CreateTemp/Rename through destination paths. Promotion strongly confines and identity-checks its source, but only path-checks the destination parent before creating/installing the sibling tree. Cleanup Lstats only the final target and then calls RemoveAll; it neither rejects a symlinked root/ancestor nor holds a confined directory handle. A target such as root/campaign/session, where campaign is a symlink to an outside tree, passes the lexical and final-node checks and deletes the outside session directory. The analogous ancestor redirects writes and promotion.
  • Realistic scenario: a stale, user-created, restored, or concurrently replaced workspace component redirects prepare/materialization into another tree; a later clean session or post-publish cleanup recursively removes data there. The same gap permits a time-of-check/time-of-use replacement of a destination ancestor.
  • Impact/likelihood/confidence: critical destructive and confidentiality impact; low-to-moderate likelihood depending on workspace ownership and multi-user exposure; high confidence from the path-based checks and standard symlink resolution semantics.
  • Estimated remediation scope and owner: medium shared filesystem change. Use root-relative directory handles/no-follow component traversal (with explicit platform behavior) for mutations and deletion, and carry validated handles through install/remove where feasible. Keep fileops policy-neutral by passing an explicit root/destination capability rather than inferring stage policy.
  • Test changes: real-filesystem tests for a symlinked root, intermediate component, destination parent replacement, and cleanup ancestor; assert an outside sentinel survives and no outside temporary/output is created. Retain the existing promotion source-race suite as the model for narrow hooks.
  • Dependencies: Stages 4, 5, 7-9 must reference this finding for their concrete cleanup, restore/download, adapter, stage-materialization, and extraction consequences. COR-002 owns lexical identity traversal separately.

COR-004: precommit current-manifest replacement invalidates the prior readable commit

  • Category: confirmed correctness/recovery defect.
  • Locations/invariant: internal/stage/publish.go upload order and internal/artifacts/current_state.go in LoadCurrentState. The pointer must be the sole commit point, and work before it must not destroy the previously committed state.
  • Evidence: publish unconditionally replaces the fixed current/manifest.json, then uploads current/run_id.txt. A replacement publish that uploads the new manifest but has not yet written, or fails to write, the pointer therefore leaves old pointer/new manifest disagreement. The shared loader can reject that pair, but only when the caller enables run validation; restore/status do not (COR-008). Existing publish tests prove no pointer call follows selected failures but do not seed and preserve a prior current pair.
  • Realistic scenario: run A is current. Publishing run B reaches the current manifest, then the pointer upload fails or a reader runs during the gap. Run A's coherent pair is gone and run B is not committed. Strict previous-cache readers report unavailable state, while restore/status can incorrectly accept run B's manifest under run A's pointer. An upload error after server acceptance also makes local outcome ambiguous.
  • Impact/likelihood/confidence: high recovery availability and integrity impact; upload failure likelihood is low per call but the disagreement window occurs on every replacement publish; high confidence from fixed keys, unconditional upload, and identity validation.
  • Estimated remediation scope and owner: medium publish/artifacts protocol change. Publish an immutable run-specific manifest first and make the final commit object select that immutable state, or use a versioned/conditional current representation that preserves the previous pair. Do not move commit policy into the generic storage adapter.
  • Test changes: use a stateful store seeded with run A, fail or pause every run B boundary, and assert run A remains readable until a successful final commit; add the first-publish and indeterminate-pointer-response cases. Existing pointer-last tests remain useful but are insufficient alone.
  • Dependencies: COR-001 can misplace the immutable run prefix and must be fixed consistently. Stage 5 should consume the established current-state truth table rather than duplicate this publish root cause.

COR-005: publish follows symlinked archive entries and can upload files outside its roots

  • Category: confirmed correctness/security defect.
  • Locations/invariant: internal/stage/publish.go in collectPublishRunFiles and collectPublishPreviousFiles, plus internal/adapters/storage upload implementations. Run and previous archives must be confined to the enumerated local trees and contain eligible regular files only.
  • Evidence: both WalkDir collectors append every non-directory entry without rejecting symlinks or other special files. Storage then opens the recorded path (os.Open in the S3 backend and file reads in the fake), which follows a symlink final component. The run-manifest check also uses os.Stat and accepts any non-directory. Existing exclusion tests cover path names, not entry type.
  • Realistic scenario: an adapter, restored tree, local user, or compromised subprocess leaves runs/<id>/logs/debug.log as a symlink to a credential or unrelated private file. Publish uploads its contents under the apparently harmless run key. A symlink in durable previous cache has the same effect.
  • Impact/likelihood/confidence: critical confidentiality impact; low-to-moderate likelihood depending on workspace ownership and subprocess trust; high confidence from standard open semantics and the missing type checks.
  • Estimated remediation scope and owner: small-to-medium publish/file-boundary change. Enumerate and open regular files without following links, keep the opened object tied to the verified entry where platform support permits, and reject unsafe roots/ancestors consistently. Storage should continue accepting explicit paths rather than infer archive policy.
  • Test changes: create run, previous, and manifest symlinks to an outside sentinel and prove publish fails before uploading sentinel contents; include an entry-replacement race case if the implementation adopts path-based Lstat only. Existing exact exclusion/order tests should remain.
  • Dependencies: COR-003 owns symlinked ancestors for mutation and cleanup; this finding is distinct because it is a publish read/exfiltration boundary. RSK-004 affects who can create the malicious entry but is not required for exploitation by a trusted subprocess gone wrong.

COR-006: automatic cleanup is one-shot and is not retried after postcommit failure

  • Category: confirmed correctness/operational defect.
  • Locations/invariant: internal/app/runner.go around terminal publish saves and runPostPublishCleanup, and post_publish_cleanup.go in publishStageRecordForCleanup. An enabled cleanup policy should remain recoverable after a committed publish until its requested cleanup succeeds.
  • Evidence: cleanup requires publish to appear in the current invocation's executed list. Once the terminal session publish save succeeds, later ordinary invocations treat publish as already succeeded and omit it from that list. A terminal run-manifest save failure before cleanup, a cleanup deletion failure, or cleanup metadata-save failure can therefore leave requested data present while every normal retry silently bypasses cleanup. A forced publish or manual clean is the only retry path.
  • Realistic scenario: remote commit succeeds and the session publish record is saved, but saving the run record fails, so cleanup is never entered. The operator fixes the filesystem and reruns normally; publish skips and the sensitive spool remains despite delete_audio_after_publish=true. The same occurs when the first removal attempt itself fails.
  • Impact/likelihood/confidence: medium confidentiality/storage and operator- expectation impact; low-to-moderate lifetime likelihood around filesystem failures; high confidence from the execution-list gate and session reuse policy.
  • Estimated remediation scope and owner: medium app/manifest change. Persist a cleanup obligation/state independently of “publish executed this invocation” and retry it idempotently whenever the committed local record proves eligibility. Keep explicit policy and confinement checks on every attempt.
  • Test changes: inject a failure immediately after publish session success, fail each requested deletion/save once, then run normally and assert cleanup retries without republishing or deleting an uncommitted target. Existing commit-gate tests remain useful.
  • Dependencies: TST-002 notes that run-save boundaries are not injectable; COR-001 and COR-003 still govern target identity/confinement. COR-007 separately owns missing success evidence after work deletion.

COR-007: successful workspace cleanup metadata is never persisted

  • Category: confirmed correctness/diagnosability defect.
  • Locations/invariant: internal/app/post_publish_cleanup.go in the workRequested success path. Cleanup outcomes promised as manifest metadata must survive the invocation that performed the destructive action.
  • Evidence: spool-only cleanup sets completion metadata and saves the session manifest. When workspace cleanup is requested, the function removes the work directory, mutates workdir_cleanup_deleted, cleanup_completed, and cleanup_skipped only in memory, then returns without saving the session. The subsequent final run save copies identity fields, not the mutated session stage record. If both policies are enabled, the spool deletion metadata is lost with the same unsaved map. Existing cleanup tests assert paths only.
  • Realistic scenario: automatic cleanup successfully removes both run work and spool audio. The durable session and run records retain the pre-cleanup publish metadata, so status, incident review, or a future cleanup retry cannot distinguish completed cleanup from a path that was never considered.
  • Impact/likelihood/confidence: medium audit/recovery impact; occurs on every successful workspace cleanup; high confidence from the missing save and run record copy behavior.
  • Estimated remediation scope and owner: small app/manifest change. Persist one authoritative cleanup transition after requested deletions, with ordering that remains meaningful if saving fails after deletion. Coordinate that state with the retry obligation in COR-006.
  • Test changes: after workspace-only and combined cleanup, reload both durable ledgers and assert the chosen authoritative completion fields and deleted paths. Add a post-delete save-failure case to define retry/reporting behavior; no existing path-preservation test should be removed.
  • Dependencies: fixing COR-006 and this finding together avoids inventing two competing cleanup state machines. RSK-002 remains the crash-durability guarantee of the eventual manifest save.

COR-008: restore is not bound to the pointer-selected committed remote run

  • Category: confirmed correctness/recovery defect.
  • Locations/invariant: internal/app/restore_discovery.go, restore_plan.go, and restore_execute.go; shared validation in internal/artifacts/current_state.go; previous object selection in internal/previouscache. Restore and status must interpret only the run committed by the current pointer, and one restore must install one coherent remote snapshot.
  • Evidence: restore/status discovery passes campaign/session expectations but omits ValidateRunID, so old-pointer/new-manifest disagreement is accepted. The planner then lists every transcripts/** and artifacts/** object under the mutable session prefix instead of limiting actions to manifest-declared published/locked state. Execution downloads those keys and the fixed manifest again; manifest validation checks campaign/session but not the discovered run ID or bytes. Previous planning enables pointer/run validation but selects and later downloads mutable published keys without generation binding. The unit mismatch test exercises the helper only with ValidateRunID=true.
  • Realistic scenario: run A is current. A failed run B publish replaces the fixed manifest and uploads one artifact but never advances the pointer. Restore reports run A, accepts manifest B, includes B's partial artifact plus any stale prefix objects, and installs them locally. A concurrent successful publish can similarly change fixed objects between discovery, planning, and execution, producing a mixed A/B restore.
  • Impact/likelihood/confidence: critical recovery integrity impact; the disagreement window occurs on every replacement publish and partial objects are retained by design, while concurrent/failed publish likelihood is low-to-moderate; high confidence from validation flags, list scope, and repeated unconditional downloads.
  • Estimated remediation scope and owner: medium-to-large publish/restore/ artifacts protocol change. Pair COR-004's immutable run-specific commit representation with restore planning from explicit manifest source-to-object records, bind object versions/digests through execution, validate run identity everywhere, and preserve an explicit compatibility policy for old manifests. Storage should expose only the narrow conditional/version metadata the protocol requires, not infer current-state policy.
  • Test changes: seed pointer A with manifest/object state A plus uncommitted and stale B keys; assert restore/status reject mismatch and restore only declared A objects. Use barriers/versioned fake objects to change pointer, manifest, ordinary output, and previous output at every discovery/execute boundary and prove the result is all one run or fails before manifest installation.
  • Dependencies: COR-004 is the publish-side fixed-pair root; both should share one protocol repair. COR-011 separately owns the missing source/destination identity needed to construct an explicit restore set. Stage 7 owns transport- level version/checksum feasibility.

COR-009: forced restore ignores unresolved directory conflicts and can report success

  • Category: confirmed correctness defect.
  • Locations/invariant: internal/app/restore_plan.go in classifyRestoreAction, restore.go conflict gating, and restore_execute.go download filtering. Force may authorize file replacement, but it must not turn an unexecutable conflict into silent success.
  • Evidence: a local directory where a remote file is expected is always classified conflict, even with force. The command blocks conflicts only when !force; execution processes only download actions and silently ignores the remaining conflict. It can then install the current manifest, mark the report succeeded, and leave the directory in place. Existing force tests cover differing regular files only.
  • Realistic scenario: artifacts/session_recap.md is accidentally a directory. The operator reviews the conflict and reruns with --force. Restore exits successfully, its report still contains a conflict action, and the new manifest is installed although the required artifact was never restored.
  • Impact/likelihood/confidence: high local integrity/operator-trust impact; low-to-moderate likelihood from damaged or manually edited workspaces; high confidence from the action and command branches.
  • Estimated remediation scope and owner: small application-policy change. Require zero conflicts before execution regardless of force, or explicitly define and safely implement directory replacement as a separate destructive action. Successful reports must be impossible while any conflict remains.
  • Test changes: cover a directory at ordinary, previous-cache, and manifest targets with force; assert failure, old manifest preservation, and a failed report. One table-driven application test can own all target categories.
  • Dependencies: target removal would require COR-003's confined deletion capability. Do not implement ad hoc RemoveAll in restore.

COR-010: status and validation do not verify previous-artifact readiness

  • Category: confirmed correctness/operator defect.
  • Locations/invariant: internal/app/operator_inspection.go in inspectPreviousArtifactReadiness, operator_status.go, and operator_session_validate.go; canonical planning in internal/previouscache.BuildPlan. Operator readiness must match the required/optional and object-resolution policy that prepare/restore will use.
  • Evidence: inspection checks only that previous_session_id is nonempty and its remote current pair validates. It reports every requirement ready without resolving candidate keys or calling Exists. It also marks a missing ID as unavailable/error whenever any previous requirement exists, although BuildPlan correctly skips that state when all are optional. Focused operator tests cover a missing pointer for a required fixture, not missing artifact objects or optional requirements.
  • Realistic scenario: a previous session has a valid committed manifest but the required published recap was never uploaded or was removed. session status says ready and session validate succeeds; restore/prepare then fails. In the opposite case, an optional previous recap with no previous ID makes validation fail even though pipeline execution would intentionally omit it.
  • Impact/likelihood/confidence: medium operator and automation correctness impact; moderate likelihood as optional/missing published outputs are normal modeled states; high confidence from the inspection shortcut and canonical plan branches.
  • Estimated remediation scope and owner: small-to-medium application/ previouscache change. Share one read-only requirement-resolution result from BuildPlan (or a narrower readiness API), then let status remain non-fatal and validation choose finding severity without duplicating required/optional semantics.
  • Test changes: add required and optional matrices for missing ID, missing pointer/manifest, missing candidate object, custom destination, and ready state. Own resolution cases in previouscache; sample only status rendering and validation exit classification in app tests.
  • Dependencies: COR-011 must be fixed for readiness to recognize every valid custom destination. COR-008 owns remote version binding, not caller severity.

COR-011: previous-cache planning loses source identity for custom publish destinations

  • Category: confirmed correctness defect.
  • Locations/invariant: internal/previouscache/previouscache.go in artifactRelativePathCandidates, manifestPublishedPaths, and candidate selection; publish metadata in internal/stage/publish.go. A configured previous-artifact source must resolve to the exact remote object that publish committed for that source.
  • Evidence: the remote manifest records only an ordered published_paths list, not ordinary source-to-destination pairs. Planning starts with the analyze output's local relative path, adds published destinations only when their basename matches, and finally tries the current artifact output path. A custom destination with a different basename is therefore invisible. Multiple destinations with the same basename are candidates for the wrong source, and the first existing key wins. Tests use identical output/published paths.
  • Realistic scenario: session_recap is produced at artifacts/session_recap.md and intentionally published as history/recap-v2.txt. A later required previous-session recap reports unavailable although the committed object exists. With two artifacts both published as different directories' summary.md, one requirement can hydrate the other's content under the expected local cache path.
  • Impact/likelihood/confidence: high cross-session artifact integrity impact; moderate likelihood because custom publish destinations are a supported configuration feature; high confidence from candidate construction.
  • Estimated remediation scope and owner: medium publish manifest/previouscache contract change. Persist a deterministic source/destination mapping for uploaded and intentionally locked preserved outputs, consume it by exact source ID, and define backward-compatible fallback behavior without basename guessing when identity is ambiguous.
  • Test changes: cover changed basename, duplicate basename, locked preserved output, absent mapping in an old manifest, and exact source mapping. Existing default-path/fallback tests remain as compatibility cases.
  • Dependencies: this mapping is also required for the committed restore scope in COR-008. Stage 6 owns configuration compatibility; Stage 10 owns later artifact-consumer semantics, not remote identity selection.

RSK-001: invocation audit records can remain indefinitely running

  • Category: confirmed correctness/operational risk.
  • Locations/invariant: internal/app/runner.go in executeStages, internal/manifest/store.go normalization, and the unused production StatusInterrupted model value. Every completed or handled invocation should have an intelligible terminal audit outcome, while process interruption must remain safely resumable.
  • Evidence: the initial run manifest is saved with overall status running. A resume-validator error returns directly without marking it failed; terminal session-save and run-save failures leave the last run state running; and a process death after either running save has the same effect. Neither load normalizer converts running records to interrupted, and later invocations consult only the session manifest and never reconcile older run manifests. StatusInterrupted has no production writer. The existing resume-validation error test checks preservation of the session success but not the run record.
  • Realistic scenario: extraction resume validation encounters an unsafe or unreadable receipt. The command returns a controlled error, the reusable session result is correctly preserved, and the run audit file remains running forever. A kill or persistence failure can leave analogous dual- ledger disagreement.
  • Impact/likelihood/confidence: medium operator/audit impact and low risk of unsafe reuse because non-succeeded session stages rerun; moderate likelihood over the life of a long-running pipeline; high confidence.
  • Estimated remediation scope and owner: medium application/manifest change. Terminalize handled post-creation errors when persistence is available and define an explicit startup/status reconciliation policy for abandoned running records. Preserve the current conservative session-authority rule.
  • Test changes: extend the resume-validation error integration test to assert a terminal failed run; add interruption/restart and injectable session/run-save boundary cases. Filesystem crash durability itself remains a Stage 3 concern.
  • Dependencies: SIM-001 may provide one failure-finalization path and TST-002 records the missing persistence seam. Stage 5 should check how status/restore presents abandoned runs; Stage 13 should decide whether true process interruption is accepted risk after handled errors are fixed.

RSK-002: single-file atomic replacement is not crash-durable

  • Category: confirmed data-durability risk.
  • Locations/invariant: internal/fileops.WriteFileAtomic, CopyFileAtomicWithChecksum, InstallDownloadedTempFile, and both atomic manifest-save sequences in internal/manifest/store.go. Successful canonical files and durable ledgers must survive a crash/power-loss boundary consistent with reported success.
  • Evidence: writers that create their own temporary file sync its data before rename, but none syncs the containing directory after rename. Download callers close an initially empty sibling temp before object-store download, and neither the download interface nor InstallDownloadedTempFile syncs the completed file before rename. Directory promotion already demonstrates the stronger sequence by syncing copied files, temporary directories, and the destination parent after no-replace rename.
  • Realistic scenario: a command reports a saved session/run manifest or materialized canonical output, then the host loses power. The directory entry rename is not durable and can disappear or expose filesystem-dependent state; a downloaded restore/previous/audio file has an additional unsynced-data window.
  • Impact/likelihood/confidence: high integrity/recovery impact; low likelihood per invocation but cumulative operational exposure; high confidence that the sync calls are absent, with exact failure manifestation filesystem-dependent.
  • Estimated remediation scope and owner: small-to-medium shared fileops and manifest change. Centralize the durable temp-file install sequence, sync completed downloads before install, then sync the parent directory with the same explicit platform policy used by directory promotion.
  • Test changes: introduce a narrow injectable sync/rename seam or ordered filesystem-operation fake to assert file-sync-before-rename and directory- sync-after-rename for write, copy, download install, and both manifest types; retain real-filesystem overwrite/temp-cleanup tests for visible atomicity.
  • Dependencies: DUP-001 is the maintainability multiplier. Stages 5 and 7 should reference this root for restore/audio/storage download behavior rather than create new durability findings.

RSK-003: stale sentinel locks can block a session indefinitely and release failures are hidden

  • Category: confirmed availability/operational risk.
  • Locations/invariant: internal/artifacts/local.go lock acquisition/release, the ignored deferred release in internal/app/runner.go, and the manual stale lock procedure in docs/troubleshooting.md. A live same-session invocation must exclude competitors, while completed or dead ownership must have a safe, observable recovery path.
  • Evidence: O_CREATE|O_EXCL correctly serializes live contenders, but any existing .lock conflicts without checking whether its recorded PID/time is live. Process death leaves the file forever. ReleaseSessionLock can report close or unlink failure, but the runner defers it as _ = ...; an unlink failure can therefore be reported as command success while the next run is blocked. Recovery requires the operator to inspect process state and manually delete the file.
  • Realistic scenario: the process is killed or the filesystem rejects unlink after a successful long run. Every later invocation for the session fails at acquisition until an operator notices and safely removes the sentinel.
  • Impact/likelihood/confidence: medium-to-high availability impact; moderate lifetime likelihood for interruption and low likelihood for unlink failure; high confidence. Mutual exclusion itself is sound in the ordinary live- process case.
  • Estimated remediation scope and owner: medium artifact-store/application change. Prefer an OS-released lock while retaining useful metadata, or define a conservative ownership/lease protocol; surface release failures without obscuring an earlier command error and document automated versus manual recovery.
  • Test changes: add process/concurrency coverage for live exclusion and death recovery, an injectable close/unlink failure proving the command cannot silently succeed, and a subsequent-acquisition assertion. Do not encode unsafe PID reuse assumptions in a unit test.
  • Dependencies: RSK-001 covers abandoned invocation audit state, not exclusion. Stage 5 should check operator status presentation; Stage 12 owns the smallest durable assembled-runner case.

RSK-004: default runtime modes can expose private campaign material to other local users

  • Category: confirmed security/operational risk.
  • Locations/invariant: layout and mutation modes across internal/artifacts/local.go, internal/fileops, stage/download writers, and manifest persistence; security contract in docs/policy/architecture.md. Transcripts, prompts, artifacts, reports, logs, and manifests are private campaign material.
  • Evidence: runtime directories request 0755 and files request 0644, subject only to ambient process umask. The default workspace is /var/lib/narratio; Narratio can create its layout with those modes, and no operations contract requires a restrictive umask, private parent, service-user ownership, or configurable mode policy. Fixed-mode promotion also normalizes bundle files to 0644 and directories to 0755.
  • Realistic scenario: a service or operator runs with the common 0022 umask on a multi-user host. Other local accounts can traverse the workspace and read transcripts, prompts, generated artifacts, diagnostics, and manifests.
  • Impact/likelihood/confidence: high confidentiality impact; environment- dependent but realistic likelihood; high confidence in requested modes and documentation absence, moderate confidence in exposure on any particular deployment because parent ACLs can mitigate it.
  • Estimated remediation scope and owner: medium operations/configuration and shared-writer change. Establish secure directory/file defaults, preserve deliberate executability where needed, define ownership/ACL/umask expectations, and provide an explicit compatibility/migration story.
  • Test changes: assert privacy-oriented effective modes under a controlled permissive umask for representative layout, manifest, artifact, log, and promoted-bundle paths; document platform/ACL limitations instead of assuming POSIX bits are universal.
  • Dependencies: Stage 6 owns configuration/default compatibility, Stage 7 owns adapter diagnostics, and Stage 13 should rank the risk against deployment assumptions.

RSK-005: remote publish locks are race-prone snapshots

  • Category: confirmed correctness/concurrency risk.
  • Locations/invariant: internal/app/runner.go remote-lock load order, remote_locks.go, operator_locks.go, and unconditional storage.ObjectStore.Upload. An operator lock intended to protect a published destination should not be silently lost or bypassed by concurrent control- plane activity.
  • Evidence: a publish-capable run loads and merges the remote lock document before acquiring its local session lock, then uses that in-memory slice for the entire invocation. Lock add/remove separately loads the full document, mutates it, and unconditionally uploads a replacement without a generation check or the runner lock. Two mutations can lose an update, and a lock added after publish's load cannot affect that in-flight upload. Existing tests are sequential and prove only static precedence, mutation validation, and loaded lock enforcement.
  • Realistic scenario: two operators lock different outputs at the same time; the last full-document upload drops the other lock. Or an operator locks a destination while a long run is approaching publish, but that run already loaded the old document and overwrites the destination despite the command reporting that it was locked.
  • Impact/likelihood/confidence: high protected-output integrity impact; low-to-moderate likelihood in multi-operator or multi-host use; high confidence in the lost-update/stale-read mechanics, moderate confidence that deployments rely on concurrent lock mutation because no concurrency contract is stated.
  • Estimated remediation scope and owner: medium app/storage-capability change. Define lock activation semantics and use object generation/ETag conditional replacement with retry, or a remote coordination primitive. Re-read or bind the effective generation at the publish commit boundary. Static locks remain immutable configuration and need no remote mutation protocol.
  • Test changes: add a version-aware stateful store and deterministic barriers for two add/remove writers plus add-during-publish; prove no lock is lost and define whether the in-flight publish aborts or observes a committed snapshot. Retain sequential force/static-lock tests.
  • Dependencies: RSK-003 concerns only the local single-writer sentinel and cannot serialize other hosts or lock commands. COR-004 may influence the appropriate remote compare-and-swap capability.

RSK-006: restore does not protect one coherent local transition from plan through manifest install

  • Category: confirmed correctness/recovery risk.
  • Locations/invariant: internal/app/restore.go, restore_plan.go, and restore_execute.go; runner reuse of the session manifest. Local action decisions and the manifest-last transition must remain coherent despite competing local work and mid-restore failure.
  • Evidence: restore discovers and classifies local paths before acquiring the session lock, then never revalidates skip_same or conflicts after lock acquisition. Another completed runner/restore or local edit in that window can make the plan stale. During execution, files install incrementally with no rollback or incomplete marker. A forced failure before manifest installation leaves the old successful manifest in place even though some files it governs were overwritten with remote content. Later runners do not inspect the failed restore report.
  • Realistic scenario: planning marks a transcript skip_same; another runner acquires/releases the lock and changes it; restore then acquires the lock, skips the stale decision, and installs the remote manifest. Or forced restore overwrites that transcript, fails on a later artifact, and releases the lock with the old manifest still claiming success over changed content. A normal run can then reuse that manifest instead of completing restore.
  • Impact/likelihood/confidence: high pipeline integrity impact; low likelihood per restore but realistic under operator concurrency or recovery from damaged storage; high confidence in ordering/no-revalidation, with exact external edit likelihood environment-dependent.
  • Estimated remediation scope and owner: medium-to-large application/filesystem change. Acquire the session lock before executable classification (dry-run can remain unlocked/read-only), revalidate under lock, and make incomplete forced restore observable to the runner. Consider staging a complete tree or a durable restore transaction marker rather than attempting broad rollback.
  • Test changes: deterministic barriers around plan/lock plus a failure after one forced install; assert stale skip decisions cannot commit and ordinary runner reuse is blocked until retry completes. Preserve the current manifest-last and retry-idempotency tests.
  • Dependencies: RSK-003 governs stale local lock recovery; COR-003 and RSK-002 govern confined and crash-durable installation. COR-008 is the analogous remote snapshot problem.

RSK-007: audio restore and cache hits use size as content identity

  • Category: confirmed correctness/data-integrity risk.
  • Locations/invariant: internal/app/restore_plan.go audio classification and internal/audio/s3_audio.go cache validation. Reused audio must correspond to the intended remote object generation, not merely have a plausible length.
  • Evidence: existing restore audio with the same positive size is skip_same without a body/checksum/ETag comparison, even under force. Cache validity accepts any nonempty non-directory path and, when available, equal size. Although ObjectInfo.ETag is carried into the materializer and newly copied files compute a checksum, neither participates in later cache identity. With unknown remote size, any nonempty cache entry is accepted. A focused test deliberately proves same-sized different strings skip without download.
  • Realistic scenario: an S3 audio key is replaced with corrected audio of the same byte length, or a cache file is corrupted without changing length. Restore/prepare silently reuse the old bytes; transcription proceeds from audio that no longer matches remote operator intent.
  • Impact/likelihood/confidence: high downstream content-integrity impact; low-to-moderate likelihood for same-size replacement/corruption; high confidence in the comparison rules. The shortcut is an evident performance tradeoff, so classification as risk rather than certain wrong output for every cache hit is appropriate.
  • Estimated remediation scope and owner: medium audio/storage metadata change. Bind cache entries to a stable object generation or trustworthy checksum via sidecar metadata, validate regular-file/no-follow status, and define multipart ETag limitations. Force should refresh or verify when explicitly requested.
  • Test changes: same-size remote replacement, corrupt same-size cache, unknown- size cache, ETag/generation change, and force semantics. Keep the current hit/ miss/invalid-size cases as lower-cost mechanism coverage.
  • Dependencies: Stage 7 owns which S3 metadata is trustworthy and portable. COR-003 owns symlink-based cache/path escape; do not solve identity with path checks alone.

RSK-008: restored manifests retain foreign absolute paths that later consumers prefer

  • Category: confirmed correctness/security risk.
  • Locations/invariant: manifest installation in internal/app/restore_execute.go, path preservation in internal/manifest, and manifest-first resolution in internal/artifacts/artifact_resolver.go. Restored state should resolve to the selected local workspace unless a trusted external path contract explicitly authorizes otherwise.
  • Evidence: restore installs current manifest bytes unchanged. Published manifests normally contain absolute producer-local output/input paths plus top-level work/spool/run paths. Artifact resolution returns an absolute manifest path unchanged and prefers it over the canonical restored fallback whenever it exists and validates. The extraction round-trip test deliberately preserves /prior/workspace/... metadata but does not run a consumer against an existing foreign path. Top-level fields already trigger COR-001 on the next invocation.
  • Realistic scenario: restore moves a session to a host or workspace where the old absolute path exists with stale or attacker-controlled transcript content. Analyze/publish reads that file instead of the restored canonical copy. Even when it does not exist, session identity retains wrong run/work/spool metadata and can direct later work through COR-001.
  • Impact/likelihood/confidence: high integrity/confidentiality impact; low-to- moderate likelihood because path existence depends on host/layout reuse; high confidence in preservation and resolver precedence.
  • Estimated remediation scope and owner: medium restore/artifact/manifest change. Separate portable logical references from host-local diagnostics, rebase or reject restored absolute paths outside the selected session root, and preserve original values only as bounded provenance if useful.
  • Test changes: restore a real manifest with built-in/configured input/output records from a different root, create a conflicting outside sentinel, and assert downstream resolution uses the restored canonical path. Extend COR-001 tests for top-level restored identity.
  • Dependencies: COR-001 owns stale top-level run identity; COR-003 owns filesystem symlink confinement. Stage 10 should reuse this result when reviewing manifest-first artifact trust.

EFF-001: restore repeatedly downloads the same objects during planning and execution

  • Category: confirmed efficiency and clarity issue.
  • Locations/invariant: internal/artifacts.LoadCurrentState, internal/app/restore_plan.go checksum classification, internal/previouscache.BuildPlan, and restore_execute.go. Recovery should avoid redundant remote transfer while preserving conflict and snapshot correctness.
  • Evidence: discovery downloads the current manifest, planning can download it and every same-size non-audio object again for checksum comparison, and execution re-downloads every forced differing object plus current/previous manifests for installation. RestorePlanOptions.DryRun is otherwise unused, so dry-run performs the same temporary body downloads for classification. Temporary files are cleaned and no durable session write occurs, but the focused internal document's “performs no local writes” wording obscures these system-temp writes.
  • Realistic scenario: a forced restore of several large same-sized artifacts downloads each body to decide it differs, discards it, then downloads it again to install. High-latency or metered storage doubles transfer and lengthens the interval exposed to COR-008 remote changes.
  • Impact/likelihood/confidence: low-to-medium cost/latency impact that scales with artifact size and remote pricing; occurs deterministically for same-size differing forced files and repeated manifests; high confidence.
  • Estimated remediation scope and owner: medium restore/storage-contract change. Couple snapshot/version repair with a verified downloaded candidate that can be retained for execution, or expose trustworthy digest/version metadata. Document dry-run as having no durable/session mutation unless truly streaming comparison eliminates all temporary writes.
  • Test changes: count bytes/downloads for missing, equal, size-different, and same-size-different objects in dry-run and apply modes; assert relational upper bounds rather than exact private call choreography after snapshot design is chosen.
  • Dependencies: solve with COR-008 so caching a plan download cannot install a stale generation. Stage 7 owns adapter metadata/cost tradeoffs.

Candidate Register

The remaining candidates require inspection by their named owners. Stages 2 and 3 have promoted their confirmed defects and risks into the register above.

ARC-001: IODecl is not a complete or consistently classified stage contract

  • Category: architectural boundary/ownership candidate.
  • Evidence: prepare.Declares lists files it produces under Inputs; analyze.Declares omits dynamic input families and has no outputs; publish.Declares exposes only the manifest; and notify.Declares advertises placeholder paths although its result has no persisted output. No production caller of Declares was found.
  • Contract tension: architecture says every stage declares required inputs, produced output state, configuration, adapters, lifecycle, and failure behavior; the Go interface declares only partial static artifacts.
  • Realistic risk: a future planner, validator, or operator view could treat the interface as authoritative and make incorrect dependency or readiness decisions. Current likelihood appears low because the method has no production caller.
  • Confirmation owners: Stages 8-10 for dynamic contracts, then Stage 11 for interface purpose/simplification. Smallest plausible outcome may be clearer naming/documentation, a complete contract, or removal; do not choose yet.

ARC-002: disabled-stage “skip” terminology spans two different durable outcomes

  • Category: architectural/lifecycle ownership candidate.
  • Evidence: production use of StageDispositionSkipped was found only in extraction. Disabled render, absent/no-op analyze, and disabled publish return zero-disposition results with skip metadata, which the runner treats as success. Focused and operator docs use “skip” for several of these cases, while manifest docs reserve self-skip for a durable skipped state.
  • Realistic risk: maintainers or operator features may assume all disabled outcomes clear state, are reconsidered, and invalidate downstream work in the same way. Conversely, changing them to explicit self-skip could break valid pipeline continuation or cleanup semantics.
  • Confirmation owners: Stage 2 confirmed the runner truth table. Stage 4 confirmed publish's ordinary-success behavior is used deliberately by the cleanup gate, while COM-002 owns its incorrect “self-skip” documentation. Stage 8 must decide the remaining ordinary disabled-stage behavior before this architectural candidate can close.

ARC-003: committed and local manifests give current_pointer_written different meanings

  • Category: architectural boundary/ownership candidate.
  • Evidence: publish must serialize current/manifest.json before the commit marker, so publishMetadataPreview records current_pointer_written=false. After pointer success, the local session and invocation results record the same field as true. Current-state readers use the actual pointer and ignore the remote field; automatic cleanup uses the local true value. Existing behavior is therefore safe for current consumers.
  • Realistic risk: a future status, restore, reconciliation, or cleanup feature may treat the committed remote manifest's field literally and report a valid commit as incomplete, while another consumer interprets the local copy as an assertion about remote state. Updating the fixed manifest after pointer would instead violate pointer-last ordering.
  • Confirmation owner: Stage 11 should decide whether this is best represented by distinct precommit/local metadata types, omission from the remote snapshot, or derivation from loaded pointer identity. Any change must preserve the protocol correction selected for COR-004; do not add a post-pointer upload.

TST-001: full race baseline fails in the concurrent transcribe test

  • Category: test-suite execution candidate.
  • Evidence: the race detector reported concurrent slice access in internal/adapters/whisperx/fake.go:45 from transcribe workers in TestTranscribeStageTranscribesPreparedAudio.
  • Observed impact: the canonical full race command exits nonzero, weakening its signal for other packages. The report currently points to a test fake, not a production data race.
  • Confirmation owners: Stage 8 should inspect the worker/fake contract; Stage 12 should classify suite impact and the smallest durable fix. Do not change the fake during this investigative stage.

TST-002: runner tests cannot exercise invocation-manifest save failures

  • Category: test-suite sufficiency candidate.
  • Evidence: Env.ManifestStore injects only session Create, Load, and Save; executeStages constructs a concrete manifest.LocalStore for run creation and every SaveRun. Focused tests cover normal and stage-failure transitions but no save disagreement row. The existing resume-validation error test also omits the surviving run status.
  • Realistic risk: future ordering or error-path changes can advertise a handled invocation as running, lose the audit half of a terminal transition, or weaken conservative retry behavior without an assembled test failing.
  • Confirmation owner: Stage 12 should decide the smallest persistence seam and representative boundary cases; avoid exhaustive choreography tests for every mechanically identical save call.

DUP-001: session save duplicates the shared atomic JSON writer

  • Category: duplicated mechanism candidate.
  • Evidence: LocalStore.Save contains its own temp/create/write/sync/close/ context-check/rename sequence, while SaveRun delegates the same mechanism to writeJSONAtomically. Error prefixes differ, but the durability mechanism is otherwise repeated. Stage 3 confirmed that both copies omit the same parent-directory sync required by RSK-002.
  • Realistic risk: a future durability, cleanup, permission, or platform fix may reach only one manifest type, creating different guarantees for the two ledgers.
  • Confirmation owners: Stage 3 should compare filesystem guarantees and Stage 11 should decide whether sharing the helper preserves useful error context. The filesystem comparison is complete; the simplification decision remains assigned to Stage 11.

DUP-002: publish reconstructs the canonical run-manifest path

  • Category: duplicated path-ownership candidate.
  • Evidence: internal/stage/publish.go in resolvePublishRunManifestSource joins the literal manifest.json to an already-derived run root, while internal/artifacts owns SessionRunManifestPath* and the run-manifest path model. The same literal is repeated when constructing the upload-relative record.
  • Realistic risk: a layout/name change can update canonical path construction without updating publish discovery, causing a completed run to fail publish or upload the wrong record. Current values agree, so this is not a correctness defect at the audited revision.
  • Confirmation owner: Stage 11 should decide whether publish should receive the canonical run-manifest path from its caller/model or use an artifacts helper; preserve the explicit remote relative name separately if it is a protocol constant.

DUP-003: sibling-temp download and install mechanics are repeated across restore and prepare

  • Category: duplicated mechanism candidate.
  • Evidence: restore's downloadObjectToSiblingTemp plus InstallDownloadedTempFile, audio's downloadObjectAtomic, and prepare's previous-cache loop each create a sibling directory/temp file, close it, download through ObjectStore, clean failure, and rename-install. The callers legitimately differ in conflict, cache, content-validation, input-record, and report policy. storage.DownloadObjectToTemp is a separate system-temp inspection primitive. All install variants inherit RSK-002's durability requirements.
  • Realistic risk: a future sync, permission, cancellation-cleanup, no-follow, or download-size fix reaches restore but not prepare/audio, creating different guarantees for the same remote-to-canonical transition. Conversely, sharing the entire workflows would incorrectly merge caller policy.
  • Confirmation owner: Stage 11 should consider one narrow sibling-temp download and durable-install helper/capability while leaving validation, conflict, cache, manifest-last, and reporting in their current owners. Coordinate with DUP-001/RSK-002 rather than adding another incomplete atomic writer.

TST-003: filesystem safety tests omit destination and lock-recovery boundaries

  • Category: test-suite sufficiency candidate.
  • Evidence: focused path/file tests cover lexical traversal, mixed slashes, ordinary atomic overwrite/cleanup, promotion source symlinks and replacement, no-replace installation, and basic lock conflict/release. No test covers an unsafe identity component, symlinked destination ancestor, destination-parent replacement, cleanup through an ancestor symlink, file/directory sync order, stale lock recovery, lock release failure, or concurrent assembled runners.
  • Realistic risk: the confirmed COR-002, COR-003, RSK-002, and RSK-003 mechanisms can regress or be only partially repaired while a broad focused suite remains green.
  • Confirmation owner: Stage 12 should select one narrow behavior-level case per distinct invariant and reuse shared low-level tests across callers. Avoid duplicating every path spelling or persistence call sequence.

TST-004: publish protocol tests do not preserve prior current state or exercise recovery

  • Category: test-suite sufficiency candidate.
  • Evidence: focused publish tests strongly cover successful upload contents, pointer-last order, and absence of a pointer call after output/current- manifest failures. Cleanup tests cover ordinary commit metadata and path effects. They do not seed a prior pointer/manifest pair, inspect readability between the last two calls, retry a partial publish, model an upload accepted with an error response, reload workspace-cleanup metadata, retry cleanup, use symlink archive entries, or coordinate concurrent lock mutations.
  • Realistic risk: tests can continue proving “pointer was not advanced” while a failed attempt has already made the prior commit unreadable (COR-004), or can prove directories disappeared without detecting lost cleanup evidence and retry obligations (COR-006/COR-007). The same suite would not prevent partial fixes to COR-005 or RSK-005.
  • Confirmation owner: Stage 12 should add the smallest stateful publish fake with barriers/version semantics and one behavior-level case per distinct invariant. Prefer extending current order/cleanup fixtures over duplicating all source-family tables, which already have good focused coverage.

TST-005: restore tests omit committed-snapshot and partial-transition invariants

  • Category: test-suite sufficiency candidate.
  • Evidence: the restore suite has strong happy-path, lexical traversal, ordinary conflict/force, typed missing state, cache, previous-cache, report, and workflow coverage. Run mismatch is tested only on the shared helper with validation enabled. No assembled case covers restore/status mismatch, uncommitted/stale prefix objects, remote generation changes, forced directory conflict, plan-before-lock changes, partial forced overwrite followed by a runner, same-size audio replacement, foreign absolute output paths, custom publish destinations, or optional readiness. Failure coverage centers on an invalid second manifest and lock conflict rather than every distinct durable boundary.
  • Realistic risk: COR-008 through COR-011 and RSK-006 through RSK-008 can remain or be partially repaired while broad restore tests stay green; several current tests explicitly encode size-only audio and prefix-wide scope without tying those choices to the stronger authority invariants.
  • Confirmation owner: Stage 12 should add one stateful package-level behavior case per root risk, reuse the publish fake/version barriers from TST-004, and consolidate caller rendering matrices. Avoid a mock for every mechanically identical download boundary; retain the existing manifest-invalid case as the representative pre-rename validation failure.

SIM-001: runner terminalization and persistence ordering lack a narrow owner

  • Category: simplification candidate.
  • Evidence: executeStages is 274 lines with cyclomatic complexity 54 and cognitive complexity 96. Much of the length is justified visible state- machine ordering, but session-first terminal save, run-first running save, result mapping, identity sync, and compound error handling are repeated inline. The resume-validation return bypasses run terminalization.
  • Realistic risk: adding another pre-stage or terminal error path can repeat the abandoned-run behavior in RSK-001; extracting too broadly could instead hide the critical order.
  • Confirmation owner: Stage 11 should consider a small terminal failure helper or typed transition operation only after Stages 3-10 settle ordering needs.

COM-001: dual-ledger save order and partial-failure policy lack rationale

  • Category: comment/clarity candidate.
  • Evidence: the runner visibly saves run-before-session for running and session-before-run for terminal outcomes, but no local comment explains which file is authoritative, why the order differs, or how partial state is meant to be interpreted.
  • Realistic risk: a maintainer may make the calls symmetrical or reorder them, inadvertently allowing a stage to execute without a durable running session transition or preferring an audit record over resume authority.
  • Confirmation owner: Stage 11, after any SIM-001 restructuring. Prefer a named operation that makes the invariant obvious; add a concise rationale only where code structure cannot.

COM-002: publish documentation calls ordinary success a self-skip

  • Category: comment/clarity candidate.
  • Evidence: docs/internal/stage-publish.md says disabled publish or run upload can “self-skip.” The implementation returns zero disposition with skip metadata, so both manifests record succeeded and later normal runs reuse that success. True self-skip is a durable skipped outcome and is reconsidered by the runner. Focused tests check returned metadata but do not make the durable distinction obvious at the stage-document boundary.
  • Realistic risk: an operator or maintainer can expect enabling publish and rerunning normally to reconsider a skipped stage, or can change it to explicit self-skip and unintentionally alter downstream/cleanup behavior. The publish command currently forces execution, which mitigates the common explicit operator workflow but not the terminology.
  • Confirmation owner: documentation owner with Stage 8 coordination for the broader ARC-002 vocabulary. State the durable outcome and reconsideration behavior precisely; change code only if a later lifecycle decision establishes different intended semantics.

Candidate Classification Log

Candidate signal Classification Reason
Graph rollups stage -> app, adapters -> app, config -> app rejected as a production reversal at Stage 1 go list production imports contain no lower-level import of internal/app; graph connections include tests and ambiguous package grouping. Reopen only with a concrete production edge.
Similar wrapper/manifest/adapter functions deferred metric signals, not findings Similarity alone does not establish duplicated policy; owning behavior stages must first establish contracts.
Coverage percentages deferred diagnostic signals, not findings Stage 12 must reason from risk and test ownership, not a numeric target.
Session last_error survives a later stage success rejected as a current-state defect at Stage 2 No production reader was found; current status and per-stage error are authoritative, so the field can serve as historical context. Reopen only if an operator surface treats it as the active error.
Minimal loaded-manifest status/timestamp validation rejected as a standalone Stage 2 finding Unknown/non-succeeded statuses fail conservatively into execution, nil maps/records are normalized, and no realistic unsafe caller was established. Configured-versus-persisted identity conflict is separately confirmed in COR-001.
Ignored runner lock-release error confirmed as RSK-003 at Stage 3 The lock is an exclusive-create sentinel, not an OS-released lock. An unlink failure leaves the conflicting file while the runner suppresses the error; process death does the same without a release attempt.
Two durable meanings of “skip” runner and publish portions confirmed; ordinary stages deferred Run action/status distinguishes idempotent skip from executed self-skip. Publish deliberately returns success metadata consumed by cleanup, but its focused document incorrectly calls this self-skip (COM-002). Stage 8 owns remaining disabled stages.
Exported previous-artifact helper accepts traversal consolidated into COR-002 Current production callers normalize first, but the helper's under-root contract is false in isolation. The identity/relative-segment boundary should be repaired once rather than as separate caller bugs.
Built-in and previous resolvers do not re-hash manifest records documented trust distinction; deferred to Stages 5 and 10 Extraction explicitly requires checksum/contract/provenance validation and enforces it. Other source families explicitly use content validation and a previous-cache filesystem fallback; consumer/restore threat models must establish a stronger requirement before this becomes a finding.
Promotion destination is path-based while source is handle-confined consolidated into COR-003 Source hardening is strong, but destination ancestors share the same symlink/replacement root cause as ordinary writers and cleanup.
Pointer is written last, so failed publish cannot advance current rejected as sufficient atomic-commit proof at Stage 4; reader enforcement corrected at Stage 5 The fixed current manifest is overwritten first (COR-004). Strict callers reject old-pointer/new-manifest disagreement, but restore/status omit run validation and accept it (COR-008).
Remote current manifest records current_pointer_written=false safe for current readers; retained as ARC-003 The snapshot is necessarily precommit, current-state loaders use actual pointer identity, and cleanup uses postcommit local metadata. The shared field remains ambiguous for future consumers.
Post-publish cleanup is revisited by later invocations corrected and confirmed as COR-006 The runner invokes the helper, but its gate requires publish in the current executed list. Once session publish is succeeded, ordinary retry skips publish and therefore skips cleanup.
Manual clean should require publish commit metadata rejected as a policy requirement at Stage 4 Manual clean is explicit operator authorization with session/global scope, dry-run, cache opt-in, and confined targets. Publish execution/upload/pointer gates correctly apply only to automatic cleanup.
Restore dry-run performs no local writes rejected as a literal implementation guarantee; retained as documentation precision under EFF-001 Dry-run avoids durable workspace, spool, cache, report, layout, and lock writes, but equal-size/unknown-size classification downloads remote bodies to system temporary files for checksumming and removes them afterward.
Force means every conflicting restore target is replaced confirmed as COR-009 File conflicts become downloads, but a directory at a planned file path remains a conflict action. The force gate permits execution, which ignores that action and can still install the manifest and report success.
Size equality is sufficient audio identity confirmed as RSK-007 Restore skips existing audio with the same positive remote size, and the shared cache accepts same-size content without ETag or checksum validation. Focused tests lock in the same-size restore shortcut.
Previous-artifact readiness is equivalent to loading the prior current pair confirmed as COR-010 Status/validate stop after pointer/manifest validation and do not resolve or check required artifact objects; their missing-previous-session policy also disagrees with optional planning behavior.
Restore should roll back files written before a later failure rejected as the current contract; retained as RSK-006 The documented operation is incremental and explicitly has no transaction or rollback. The risk is that the old manifest remains authoritative over partially replaced files and planning is not revalidated under the local lock.
Ordinary manifest and previous-cache reads must always re-hash bytes rejected as a universal rule at Stage 5 Restore verifies remote/local equality when needed during classification, and prepare validates required previous artifacts before analysis. The confirmed defects concern generation binding, incomplete readiness checks, and lost source identity rather than a blanket checksum requirement.

Unresolved Questions And Follow-Up

  • Should startup/status reconcile abandoned running invocation records, mark them interrupted, or retain them as an explicitly accepted audit limitation?
  • Which manifest model should retain run-scoped identity after COR-001 is repaired, given that publish archives and automatic cleanup both consume it?
  • Which cross-platform root-relative filesystem primitive should own the symlink-safe mutation capability required by COR-003 without moving stage policy into low-level fileops?
  • Should sentinel locking be replaced by an OS-released lock, or can a conservative ownership/recovery protocol satisfy RSK-003 across supported platforms?
  • Which existing identifier spellings constrain the strict segment validation required by COR-002?
  • Do deployment packaging and service definitions already enforce an umask, ownership, or ACL that reduces RSK-004, and where should that contract be documented and tested?
  • Are disabled render/analyze outcomes intentionally successful so pipeline continuation works, and do all operator views describe that distinction accurately? Publish is now resolved as successful with a documentation gap.
  • Is IODecl intended only for display/tests, or should it own enforceable dependency declarations?
  • Should remote lock activation be snapshot-based or immediate, and what conditional-write capability can enforce the chosen rule across hosts?
  • Should committed remote manifests omit precommit-only metadata, or should local/remote publish metadata have distinct types after COR-004 is fixed?
  • Should restore consume only objects declared by the pointer-selected manifest, or should the committed model carry an immutable generation map that can bind every downloaded artifact, audio object, and previous-cache source?
  • Should restore's local commit point use a staging tree/snapshot swap, or can a journal plus locked revalidation make incremental replacement and recovery sufficiently explicit under RSK-006?
  • What durable identity should govern audio cache and existing-file reuse: storage version, ETag with provider-specific rules, or a published checksum?
  • Should restored manifests be rewritten to canonical local paths, or should all consumers ignore persisted absolute paths in favor of workspace-relative identities?
  • How should publish metadata retain a stable source-to-destination mapping for previous artifacts without reintroducing mutable configuration authority?
  • Which native CI runner limitations explain the absence of validation jobs in the tag-only release workflow? Stage 12 owns the automation conclusion.

No accepted risks or final audit conclusions are recorded yet.

Completed-Stage Evidence

Stage 0

  • Contracts and records: development guide, audit plan and sequence, all policy documents, repository/branch/toolchain state.
  • Graph evidence: refreshed moderate index at exact HEAD; architecture, interface, complexity, similarity, fan-in, and Execute call trace queries.
  • Commands: every baseline command listed above; Go/package/file/test and automation inventories.
  • Candidates: TST-001; metric signals assigned to later owners.
  • Explicit no-finding conclusion: no production dependency reversal into internal/app was found in the package import inventory.
  • Limitation disposition: the graph excludes the executable entry point, which was verified directly; the race failure is owned by Stages 8 and 12 and does not prevent read-only audit work.

Stage 1

  • Contracts reviewed: architecture, testing and documentation policy; internal overview and every focused internal document; CLI, configuration, operations, and every integration contract.
  • Code/evidence reviewed: canonical registry and stage declarations; all modeled interfaces; production import graph; application dispatch trace; explicit self-skip usages; interrupted-state usages; focused test ownership references.
  • Outputs: package/interface ownership, area coverage, stage contract, lifecycle, cross-boundary scenario, and preliminary risk-to-test matrices.
  • Candidates: ARC-001, ARC-002, RSK-001; no candidate was confirmed from mapping evidence alone.
  • Explicit no-finding conclusion: the canonical stage order agrees across the registry, internal overview, CLI, and operations contract.
  • Follow-up: all unresolved behavior has a named owner in Stages 2-12; every area and invariant has an implementation owner and intended test owner.

Stage 2

  • Contracts and code reviewed: planner and full/single-stage entry points; executeStages, run-control and identity helpers; session/run manifest models, creation, loading, validation, normalization, atomic persistence, and every transition method; runner lock lifetime; focused internal manifest documentation and Stage 1 matrices.
  • Graph/source evidence: call traces into full and selected execution; all identity-field consumers; manifest transition/save callers; status and last_error usages; runner complexity and atomic-save similarity; complete focused test-function inventory.
  • Validation: go test -count=1 ./internal/app ./internal/manifest passed (internal/app 0.708 s, internal/manifest 0.010 s; 1.60 s command wall time). go test -race -count=1 ./internal/app ./internal/manifest passed (internal/app 45.842 s, internal/manifest 1.026 s).
  • Conclusions: every lifecycle cell and dual-save boundary is recorded above; scenarios 1 and 2 are resolved at runner level; lock acquisition/lifetime is resolved and release mechanics assigned to Stage 3. Confirmed COR-001 and RSK-001; added DUP-001, SIM-001, COM-001, and TST-002 for named later owners.
  • Explicit no-finding conclusions: canonical invalidation works at both first and last stage and is independent of selected-plan width; stale transitions intentionally retain diagnosis data while running/failure/skip clear it; session authority makes all enumerated disagreement states retry or reuse conservatively; the runner's two skip forms are durably distinguishable.

Stage 3

  • Contracts and code reviewed: architecture/path/security policy; internal artifacts, workspace, manifest, operations, and troubleshooting contracts; all canonical local/S3/cache constructors; pathsafe and artifactpolicy; built-in, configured, extraction, previous, and current-state resolution; local-store layout/copy/lock code; atomic write/copy/download installation; directory promotion and platform-specific no-replace/directory-sync support; restore/audio/previous download callers; manual and post-publish cleanup.
  • Graph/source evidence: canonical-helper and direct-mutation inventories; callers of path/key, artifact-resolution, fileops, current-state, previous- cache, and lock helpers; focused test-function inventories; fallback text search for non-code policy and direct OS mutation sites where graph results were insufficient.
  • Validation: go test -count=1 ./internal/artifacts ./internal/artifactpolicy ./internal/pathsafe ./internal/fileops passed (0.98 s wall time). go test -race -count=1 ./internal/artifacts ./internal/fileops passed (2.25 s wall time).
  • Conclusions: canonical owners and artifact resolution order are recorded above; lexical normalization handles mixed separators, traversal, absolute, and drive forms when callers invoke it; extraction source trust and source- side promotion are strong; low-level helpers correctly consume explicit destinations. Scenario 10 is resolved: live contenders are excluded, while stale sentinel/release behavior is unsafe operationally.
  • Findings: confirmed COR-002, COR-003, RSK-002, RSK-003, and RSK-004; added DUP-002 and TST-003; refined DUP-001 with the shared durability gap.
  • Explicit no-finding conclusions: current-state helpers have typed missing cases and support strict identity checks when callers request them; artifact resolution is deterministic and matches its documented source-specific validation; promotion preserves an existing/concurrent destination and rejects unsafe source trees; temporary files/trees are cleaned on ordinary failures; unsupported promotion platforms fail before creating a durable bundle; fileops does not infer higher-level policy.
  • Follow-up: Stages 4-10 should cite the shared confinement/durability roots for concrete callers. Stages 5 and 10 must decide whether ordinary manifest and previous-cache checksum trust is sufficient. Stages 6, 11, and 12 own compatibility, simplification, and durable regression coverage respectively.

Stage 4

  • Contracts and code reviewed: architecture publish/cleanup/force invariants; focused publish, storage, workspace, manifest, operations, CLI, and troubleshooting contracts; publish stage prerequisites, artifact catalog and output resolution, selection, locks, run/previous collection, every upload, current snapshot/pointer generation, storage upload semantics, effective remote-lock loading/mutation, current-state discovery/identity validation, status/restore entry interpretation, runner terminal ordering, automatic cleanup, cleanup target validation, and manual session/global/cache cleanup.
  • Graph/source evidence: call traces from publish and lock commands into storage; exact source for current-state readers, cleanup gates, lock mutation, and runner persistence; complete focused publish/cleanup/lock/current-state test inventory; fallback source/text inspection for the generic stage method, S3 os.Open, and non-code contracts where graph modeling was insufficient.
  • Validation: go test -count=1 ./internal/stage ./internal/app ./internal/artifacts ./internal/adapters/storage passed (internal/stage 0.440 s, internal/app 0.749 s, internal/artifacts 0.028 s, internal/adapters/storage 0.019 s; 1.86 s command wall time).
  • Conclusions: publish plans fully before writing and uploads sorted run files, configuration-ordered outputs, sorted previous files, current manifest, then the pointer last. Output family, required/optional, selection, lock, exclusion, retry, existing-object, and force behavior is recorded above. Remote-current and automatic-cleanup truth tables resolve scenarios 5 and 7 at every boundary; manual cleanup is correctly a separate explicit authorization.
  • Findings: confirmed COR-004, COR-005, COR-006, COR-007, and RSK-005; added ARC-003, COM-002, and TST-004; resolved publish's portion of ARC-002 and corrected Stage 2's cleanup-retry conclusion.
  • Explicit no-finding conclusions: pointer is unequivocally the final upload and intended current marker (Stage 5 later confirmed incomplete reader enforcement as COR-008); failure before current-manifest publication preserves any prior current pair; successful retry is idempotent by unconditional replacement; static and loaded remote locks, including required outputs, survive force; disabled extraction is safe because only explicit extraction rules resolve it; manual clean does not need publish metadata; storage correctly remains policy-neutral and consumes explicit paths/keys.
  • Follow-up: Stage 5 reused and corrected the remote-current reader truth table. Stage 11 owns metadata vocabulary/duplication decisions; Stage 12 owns the smallest stateful commit, cleanup-retry, symlink-read, and remote-lock concurrency tests.

Stage 5

  • Contracts and code reviewed: architecture and testing policy; restore, workspace, storage, artifact, manifest, operations, troubleshooting, and CLI contracts; remote-current discovery, restore planning/classification, execution/reporting, ordinary and audio download installation, cache/spool materialization, previous-artifact requirement collection/planning, prepare consumption, and status/validate readiness reporting.
  • Graph/source evidence: callers and exact options for current-state loading; restore plan/action and manifest-last traces; storage/list/download and temporary-install paths; audio cache-key and validation paths; previous-cache candidate resolution and consumer traces; focused test-function inventories. Direct source and text inspection covered non-code contracts and implementation details the graph could not distinguish.
  • Validation: go test -count=1 ./internal/app ./internal/previouscache ./internal/audio ./internal/artifacts ./internal/adapters/storage passed (internal/app 0.759 s, internal/previouscache 0.009 s, internal/audio 0.012 s, internal/artifacts 0.025 s, internal/adapters/storage 0.008 s; 1.87 s command wall time).
  • Conclusions: restore authority, complete remote-to-local mapping, deterministic ordering, action/force/dry-run behavior, manifest-last execution, report and every failure boundary are recorded above. Audio cache/spool identity and previous-session required/optional, candidate, and fallback policies are explicit. Shared mechanics are separated from restore, prepare, status, and validate caller policy.
  • Findings: confirmed COR-008, COR-009, COR-010, COR-011, RSK-006, RSK-007, RSK-008, and EFF-001; added DUP-003 and TST-005 with named later owners.
  • Explicit no-finding conclusions: relative target construction and ordering are deterministic and lexically confined; force does not bypass identity, traversal, or lock validation; dry-run creates no durable restore state; ordinary download failures remove the active temporary file; the storage adapter remains policy-neutral; prepare intentionally overwrites its private previous-cache destination while restore classifies existing destinations.
  • Scenario disposition: scenario 4 confirms the pointer is not sufficient authority because restore/status omit run validation and restore reads mutable prefix objects; scenario 6 confirms partial incremental replacement, old- manifest authority before the final install, and restored state despite a later report failure. Rollback and automatic retry are intentionally absent.