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

1036 lines
79 KiB
Markdown

# Codebase Audit Findings
Status: in progress
This document is the working ledger and final report for the audit defined by
the [audit plan](audit-plan.md) and [audit sequence](audit-sequence.md). 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 | not_started | Assigned remote commit and cleanup scenarios below. |
| 5: restore and previous state | not_started | Assigned restore and previous-cache scenarios below. |
| 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 | assigned |
| 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 | assigned |
| 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 success with skip metadata; force cannot bypass locks; no validator | Deterministic uploads; `current/manifest.json` before `current/run_id.txt`; commit 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 stage outcomes remain terminal; a successful run save marks the invocation failed, while a failed run save leaves its prior overall `running` state. | A later invocation decides stages from the session and re-enters the cleanup gate; Stage 4 owns whether that retry is safe for every cleanup state. |
| 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.
## 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
`Lstat`s 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.
### `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.
## 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 for runner truth table, Stage 4 for publish and
cleanup, Stage 8 for ordinary disabled stages. Treat wording and behavior as
unresolved until those flows are traced. Stage 2 confirmed that the two
outcomes are deliberate and internally distinguishable at runner level.
### `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.
### `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.
### `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.
## 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 portion confirmed, stage-contract conclusion deferred | Run action/status distinguishes idempotent skip from executed self-skip. Stages 4 and 8 must determine whether successful disabled outcomes and their wording are intentional. |
| 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. |
## 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?
- Do Stage 3 path mechanics and Stage 4 publish/cleanup behavior confirm every
concrete consequence of the stale run-derived fields in `COR-001`?
- 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/publish outcomes intentionally successful so
pipeline continuation and optional outputs work, and do all operator views
describe that distinction accurately?
- Is `IODecl` intended only for display/tests, or should it own enforceable
dependency declarations?
- Does publish's omission of extract from its static prerequisite list combine
safely with every configured extraction output rule and disabled extraction?
- 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 strict identity checks; 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.