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
| 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.
| 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. |
| 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 `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. |
| 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
| 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 |
| 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 |
| 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 |
| 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. |
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.
| 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
| 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
| 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. |
| 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. |