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

4817 lines
365 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Codebase Audit Findings
Status: complete
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: findings below are not remediation changes.
The classifications and backlog describe proposed future work; no production
change is included in this report.
## Executive Assessment
Narratio has a clear stage-oriented architecture, deterministic normal
execution, strong package ownership for most parsing and protocol behavior, and
substantial focused coverage of ordinary lifecycle, adapter, artifact, and
workflow paths. The session manifest is consistently treated as progress
authority, the canonical stage order is explicit, run-local outputs are usually
validated before canonical materialization, publish writes its pointer last,
restore installs the manifest last, and the default test suite is fast,
offline, and credential-free. The audit found no production dependency
reversal, generic workflow-engine drift, unsupported live-service dependency in
tests, or broad need for new abstraction.
Those strengths do not close several consequential boundary defects. The most
urgent findings are symlink-following mutation/deletion and publish reads
(`COR-003`, `COR-005`), restore accepting state not bound to the selected
remote commit (`COR-008`), unsafe identity components crossing local and remote
namespaces (`COR-002`), and secret/private-data exposure through permissive
modes, filesystem-secret reads, and subprocess diagnostics (`RSK-004`,
`RSK-010`, `RSK-012`). Remote replacement publish can also invalidate the
previous readable commit before its final pointer write (`COR-004`). These are
high-consequence integrity, confidentiality, destructive-operation, and
recovery boundaries even though several require an uncommon failure, hostile
filesystem state, or multi-user deployment.
The broader result is not a recommendation for wholesale redesign. The
preferred remediation is a dependency-ordered set of narrow owners: establish
confined filesystem and durable-write capabilities; make manifest identity and
terminal persistence singular; repair publish/restore commit authority; then
correct configuration, adapter, stage, and analyze contracts. Structural,
test, efficiency, and comment work should follow or accompany those roots only
where it makes the corrected invariant easier to preserve. The ordered backlog
below consolidates 77 confirmed IDs into coherent workstreams so remediation
does not repeat discovery or fix the same mechanism in several callers.
## 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%. The Stage 12 risk-based
interpretation is recorded below; these numbers are diagnostic 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) | Reviewed through Stage 11; stable ownership seams, interface dispatch, and graph name ambiguity explain the broad signal. `SIM-001` owns the only justified runner extraction. |
| High complexity | `app.executeStages` cyclomatic 54/cognitive 96; `previouscache.BuildPlan` 22/38; `analyzeStage.Run` 20/27; `audita.NewSubprocessRunner` 17/25; `app.SessionInit` 20/21 | Reviewed through Stage 11. `SIM-001`, `SIM-003`, and `ARC-005` own narrow reductions; the remaining branching preserves distinct policy and validation. |
| Exact similarity | `app.Analyze`/`app.Publish`, `manifest.Load`/`LoadRun`, `manifest.Create`/`CreateRun`, adapter constructors, and Seriatim fake methods | Reviewed through Stage 11. Thin command, typed-model, protocol-constructor, and fake similarities are coincidental or deliberately explicit; atomic file writing remains the shared mechanism in `DUP-001`/`DUP-005`. |
| Test-heavy hotspot noise | Several test functions and fakes rank highly in transitive-depth and fan-in results | Stage 12; do not infer production risk from the metric |
### Automation And Fixture Inventory
- `.woodpecker/release.yml` is tag-only release automation. It cross-builds
Linux, macOS, and Windows binaries with Go 1.25, then publishes release
assets. It does not run tests, race tests, vet, or example validation.
- `examples/` contains 19 maintained files: pipeline, campaign, session,
template, stable-input, and placeholder-audio fixtures. Configuration tests
are documented as their validation owner.
- No fuzz tests, golden files, golden-update switches, opt-in/live test tags, or
`go:generate` test mechanisms were found.
- Platform build constraints exist for the native no-replace directory tests
and unsupported-platform fallback in `internal/fileops`.
## Execution Coverage Ledger
| Stage | Status | Evidence and result |
| --- | --- | --- |
| 0: baseline | complete | Revision/environment pinned; graph refreshed; inventories and every prescribed baseline command recorded. `TST-001` owns the non-blocking race limitation. |
| 1: contract and boundary map | complete | Canonical contracts and focused internal docs read; ownership, stage-contract, lifecycle, scenario, area, and preliminary risk-to-test matrices recorded below. |
| 2: runner and manifest | complete | Full/single-stage entry paths, every lifecycle outcome, both manifest models/transitions, save disagreement states, canonical invalidation boundaries, and runner lock lifetime reviewed. Focused app/manifest test and race commands passed. Confirmed `COR-001` and `RSK-001`; assigned `DUP-001`, `SIM-001`, `COM-001`, `TST-002`, and lock-release details to later stages. |
| 3: paths and filesystem | complete | Canonical local/remote paths, every artifact source family, filesystem mutations, directory promotion, cleanup confinement, permissions, and lock mechanics reviewed. Focused normal and race commands passed. Confirmed `COR-002`, `COR-003`, `RSK-002`, `RSK-003`, and `RSK-004`; added `DUP-002` and `TST-003`, and refined `DUP-001`. |
| 4: publish and cleanup | complete | Publish prerequisites/source families, deterministic upload order, every partial remote outcome, retry semantics, effective locks, status/restore interpretation, and automatic/manual cleanup gates reviewed. Focused stage/app/artifacts/storage tests passed. Confirmed `COR-004` through `COR-007` and `RSK-005`; added `ARC-003`, `COM-002`, and `TST-004`. |
| 5: restore and previous state | complete | Restore discovery/planning/execution/reporting, remote-current identity and scope, every local failure boundary, audio cache/spool identity, previous-cache planning/consumption, and status/validate policy reviewed. Focused app/previouscache/audio/artifacts/storage tests passed. Confirmed `COR-008` through `COR-011`, `RSK-006` through `RSK-008`, and `EFF-001`; added `DUP-003` and `TST-005`. |
| 6: configuration and composition | complete | Discovery/precedence, strict loading, defaults/normalization, templates, cross-field validation, CLI selection, filesystem secrets, conditional adapter composition, and maintained examples reviewed. Exact focused tests and repository vet passed. Confirmed `COR-012` through `COR-015`, `RSK-009`, and `RSK-010`; added `ARC-004`, `DUP-004`, and `TST-006`. |
| 7: adapters and shared support | complete | All HTTP, subprocess, notification, storage, audio, shared-model, and diagnostic boundaries were compared with their contracts and production callers. Focused normal and race commands passed. Confirmed `COR-016`, `RSK-011` through `RSK-014`, `EFF-002`, and `ARC-004`; added `ARC-005`, `DUP-005`, `COM-003`, and `TST-007`, and refined `TST-001`. |
| 8: ordinary stages | complete | Prepare, transcribe, merge, polish, normalize, trim, and render were traced from resolved inputs through adapters, validation, run-local/canonical outputs, diagnostics, and manifest recording. The focused normal command passed; the required race command reproduced only `TST-001`. Confirmed `COR-017` through `COR-019`; added `ARC-006`, `DUP-006`, `COM-004`, and `TST-008`, and refined `ARC-001`, `ARC-002`, `ARC-005`, `RSK-013`, and scenario 8. |
| 9: extraction | complete | Configuration, transcript resolution, fingerprinting, Notarius execution, receipt/index/lane validation, immutable promotion, manifest advertisement, catalog hydration, resume, and explicit analyze/publish consumption were traced as one slice. The exact focused command passed. Confirmed `COR-020`; added `DUP-007`, `SIM-002`, and `TST-009`, and refined `ARC-001`, `RSK-013`, and scenario 3. |
| 10: analyze and dependencies | complete | All five source-policy families, seven built-in catalog entries, configured-artifact execution/reuse, dependency validation/order, previous-cache locality, lifecycle, and publish selection were traced as one slice. The exact focused command passed. Confirmed `COR-021` through `COR-024` and `RSK-015`; added `ARC-007`, `DUP-008`, `SIM-003`, `COM-005`, and `TST-010`; and refined `ARC-001`, `ARC-002`, `RSK-013`, and scenario 9. |
| 11: maintainability | complete | Production graph metrics, change coupling, dead-code/static patterns, all structural candidates, efficiency workloads, comments, dependencies, and platform assumptions reviewed. Confirmed the narrow `ARC`, `DUP`, and `SIM` corrections recorded below, merged lifecycle wording and extraction proof candidates, rejected generic abstractions and micro-optimizations, added `SIM-004` and `COM-006`, and passed the full normal test suite and vet. |
| 12: test policy | complete | All 749 tests were inventoried by behavior owner and consequential risk; every prior `TST` candidate was classified, five suite-wide candidates were added, and coverage, doubles, helpers, determinism, offline behavior, runtime, fuzzing, and automation were assessed. The prescribed shuffled run exposed `TST-011`; the prescribed race run reproduced only `TST-001`. |
| 13: synthesis | complete | Revalidated the pinned implementation boundary, reconciled all registers and matrices, retained 77 confirmed IDs with four merged/rejected dispositions, recorded positive conclusions, dimensions and accepted risks, and produced an 11-workstream dependency-ordered remediation backlog. Report links and document integrity were validated; no production change occurred. |
## 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) | reviewed |
| Stage registry and runner | `internal/stage`, `internal/app` | 2 | reviewed |
| Configuration | `internal/config` | 6 | reviewed |
| Prepare and audio | `internal/stage`, `internal/audio`, `internal/previouscache` | 8 | reviewed |
| Transcript stages | `internal/stage` plus tool adapters | 8 | reviewed |
| Extraction | `internal/stage`, Notarius adapter, `internal/fileops` | 9 | reviewed |
| Analyze and artifact dependencies | `internal/stage`, `internal/artifacts`, `internal/artifactpolicy` | 10 | reviewed |
| Publish and cleanup | `internal/stage`, `internal/app` | 4 | reviewed |
| Manifest state | `internal/manifest`, transition policy in `internal/app` | 2 | reviewed |
| Artifacts, paths, and policy | `internal/artifacts`, `internal/artifactpolicy`, `internal/pathsafe` | 3 (resolution consumption revisited in 10) | reviewed |
| Restore | `internal/app`, `internal/artifacts`, `internal/previouscache`, `internal/audio` | 5 | reviewed |
| File operations | `internal/fileops`, `internal/pathsafe`, local artifact store | 3 (promotion vertical slice in 9) | reviewed |
| External adapters and storage | `internal/adapters`, `internal/audio` | 7 | reviewed |
| Shared models and diagnostics | `internal/artifactmodel`, `internal/contracts`, `internal/logging` | 7 (maintainability revisited in 11) | reviewed |
| Tests, examples, and automation | package test owners, `examples/`, `.woodpecker/` | 12 | reviewed |
## 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 sorted input records; unlike processing stages, `Declares` labels produced canonical files as inputs. Repeated explicit audio paths create duplicate records (`COR-019`), and zero previous requirements leave stale managed state (`COR-017`). |
| 2 `transcribe` | Prepared FLAC files -> one raw JSON per unique filename-derived speaker | WhisperX language/retry/timeout/concurrency; `whisperx.Client` | Ordinary succeeded-record skip; no validator/self-skip | Bounded concurrent run-local writes, exact adapter path check, JSON validation, sorted results, then canonical materialization. Cancellation can nevertheless return a successful incomplete set (`COR-018`). |
| 3 `merge` | Manifest raw transcripts or directory fallback, speakers, autocorrect -> base transcript, optional report | Seriatim merge fields; `seriatim.Runner` | Ordinary succeeded-record skip | Deterministically normalized scratch inputs and run-local transcript/report validate before canonical materialization; logs/config are diagnostics. |
| 4 `polish` | Manifest base transcript or canonical fallback, glossary -> polished transcript, optional report | Audita fields/credential reference; `audita.Runner` | Ordinary succeeded-record skip | Run-local transcript/report validate before canonical materialization; logs/config are diagnostics. Static request/constructor ownership remains `ARC-005`. |
| 5 `normalize` | Manifest polished transcript or canonical fallback -> final transcript, optional report | Normalize plus Seriatim fields; `seriatim.Runner` | Ordinary succeeded-record skip | Run-local schema/report validation then configured canonical materialization; logs/config are diagnostics. |
| 6 `trim` | Manifest final transcript or configured canonical fallback -> final-trimmed transcript and, when enabled, bounds | Trim, bounds, Scriptorium, and Seriatim fields; both runners when enabled | Disabled trim copies and validates the normalized transcript, then succeeds; no explicit self-skip or resume validator | Enabled bounds/trim results validate before canonical materialization; render-debug and subprocess logs/config are diagnostics, not outputs. |
| 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. The validator does not bind reuse to the current trimmed-transcript bytes (`COR-020`). | Validates the run-local receipt/index/configured lanes, promotes the complete regular-file bundle to a unique no-replace destination, rechecks promoted checksums, and records exact checksums/contracts/provenance. Only configured lanes are selectable; index and unconfigured bundle members remain audit state. |
| 8 `render` | Manifest final/final-trimmed JSON or canonical fallback -> two Markdown transcripts | Render and Seriatim fields; `seriatim.Runner` | Disabled returns a zero-disposition no-output result, therefore durable success rather than explicit self-skip; later enablement needs force; no validator | Enabled run-local text validates non-empty before either canonical result is recorded; logs/config are diagnostics. Focused wording is imprecise under `COM-004`. |
| 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 ordinary success with skip metadata, not self-skip; later configuration enablement needs force; no validator | Selected artifacts run in stable topological order; non-executable configured outputs may be reused; each generated run-local output is validated, materialized canonically, and exposed to later dependents. Static `Declares` omits dynamic outputs and several input families. Optional built-ins, explicit selection/prerequisite planning, guidance, and inert input fields have confirmed defects below. |
| 10 `publish` | Session/run state, selected output rules, locks, previous cache -> remote run/output/current objects | Publish/storage/selection fields; `storage.ObjectStore` | Disabled publish or run upload returns ordinary success with skip metadata, not a self-skip; force cannot bypass locks; no validator | Deterministic uploads; `current/manifest.json` before `current/run_id.txt`; post-commit local metadata gates cleanup. Static prerequisites omit extract because disabled extraction is valid and lane resolution enforces required extraction state when selected. |
| 11 `notify` | No implemented persisted pipeline input/output | Optional `notify.Sender`; default no-op | Ordinary succeeded-record skip; no explicit self-skip or validator | Placeholder metadata and optional notification call; no returned output. `Declares` nevertheless advertises placeholder input/output paths. |
Configuration, adapters, skip policy, and dynamic outputs are not represented
by `IODecl`; their current canonical owners are the focused stage,
configuration, and integration contracts. Whether `IODecl` should remain a
partial display type or become an enforceable declaration is deferred as
`ARC-001`.
## Lifecycle Matrix
This began as the intended contract map and is now source-backed for both
durable ledgers by the Stage 2 review.
| Outcome | Session manifest intent | Invocation manifest intent | Downstream and next-invocation intent |
| --- | --- | --- | --- |
| First run | Pending/non-succeeded stage becomes running, then succeeded/failed/skipped; executing clears older result payload first | New run record; action `run`; terminal status records this invocation | Success enables later stages; failure stops current execution and an effective outcome change may stale succeeded downstream records. |
| Already-succeeded skip | Existing succeeded session record and payload remain unchanged, subject to resume validation | Action/status record a skip and stable reason for this invocation | Reusable result remains authoritative; pipeline continues. |
| Explicit self-skip | Session stage becomes skipped, clears older result payload, and may record bounded current skip details | Action was `run`, outcome is skipped with reason | Reconsidered later; a changed effective upstream outcome stales succeeded downstream work; identical extraction disabled skip is stable. |
| Failure | Current stage becomes failed with error; current output/log/config/metadata payload is cleared | Action `run`, failed outcome and overall failed run | Current execution stops; affected succeeded downstream work is intended to stale; later invocation reruns non-succeeded stages. |
| Interruption | Model admits `interrupted`, but production never writes it; process death leaves the last durable status `running` and the running transition has already cleared the target's prior result details | The run remains non-terminal at its last durable per-stage state; no load or startup reconciliation changes it | Non-succeeded session stages execute on the next included plan, so continuation is conservative; the old invocation record remains inaccurate under confirmed `RSK-001`. |
| Forced replacement | Target execution starts fresh; succeeded downstream records are pre-marked stale; current target payload clears on running | Force flag and `run` action recorded | Replacement result determines later execution; locks and safety policy remain authoritative. |
| Non-resumable success | Prior success becomes stale while retaining details long enough for diagnosis/validation, then running clears them | Current invocation records execution after validation rejects skip | Obsolete result reruns; unsafe inability to decide stops without silently replacing current success. |
| Successful rerun | Target becomes succeeded with only new outputs/diagnostics/config/metadata | Current invocation records its own new success; earlier run manifests remain unchanged | A rerun after a non-succeeded state stales succeeded downstream work; force already stales it before execution. The runner does not compare output contents; identical repeated self-skip is the narrow no-invalidation case. |
Stage 2 verified the matrix. The runner treats the session manifest as the only
cross-invocation decision source and each run manifest as a record of one
invocation. The resulting field behavior is:
- entering `running` clears the session stage's former outputs, logs, generated
configuration, metadata, completion, and error; success installs only the
current result and clears the stage error, while failure and self-skip clear
result data before bounded current skip diagnostics are reapplied;
- staling deliberately retains prior result data and timestamps for diagnosis,
changes status/error/updated time, and prevents the result from being reused;
- an already-succeeded skip does not mutate the session record; the run record
separately stores action `skip`, status `skipped`, and reason
`already_succeeded` without copying the reusable outputs;
- an executed self-skip stores action `run` and status `skipped` in the run
record, so it remains distinguishable from an idempotent skip;
- a stage failure marks the session stage and run stage failed, records the
error in both ledgers, makes the run overall failed, and stops execution;
- force is stored at run level and pre-stales all succeeded canonical
downstream stages; non-resumable validation first stales and saves the target
and succeeded downstream stages, then executes; and a successful execution
following any non-succeeded prior state stales remaining succeeded downstream
work; and
- the session-level `last_error` is retained as historical information after a
later success. No production reader treats it as current status; stage and
run status are the operative fields.
The invalidation helper derives position from the complete canonical registry,
not the selected plan. Consequently a single-stage replacement has the same
downstream effect as that stage in a full run. `prepare` can invalidate every
later succeeded stage and `notify` has no downstream target. Only succeeded
records need explicit staling: failed, skipped, stale, pending, running, and
interrupted records already execute on the next included plan.
### Runner Entry, Lock, And Persistence Conclusions
`Run` validates the assembled configuration and selection, builds the full
canonical plan, and delegates to `executeStages`. `RunStage`, `Analyze`, and
`Publish` select one canonical stage; the latter two force it. Single-stage
execution still uses the same lifecycle, session lock, invalidation, dual
manifests, cleanup check, and final run transition as a full run.
The session lock is acquired after layout creation and before the session
manifest is loaded, then held through stage execution, all manifest saves,
post-publish cleanup, and the final run save. A competing same-session runner
therefore cannot enter manifest decision-making while the first holds the lock.
The deferred release error is discarded. Whether close/unlink failures can
leave a blocking or misleading lock requires the filesystem implementation
review assigned to Stage 3; the assembled runner suite has no concurrent-runner
or release-failure case.
Both manifest stores use temp-file write, file sync, close, and same-directory
rename, so an error before rename leaves the prior individual file in place.
There is no atomic transaction or reconciliation protocol across the two
manifest files. The runner saves the run record first when
announcing execution, then saves the session record; for terminal outcomes it
saves the session authority first, then the run audit. The possible durable
states and their later interpretation are:
| Failure boundary | Durable state | Later invocation behavior |
| --- | --- | --- |
| New session or identity save fails before initial run save | No run record; session is absent or remains at its prior contents. An identity-save attempt mutates the in-memory identity and `updated_at`, but none of it becomes durable. | The command stops before a stage. A later invocation loads/creates from the last durable session state. |
| Initial run save fails after session identity save | Session points at the new run ID, but that run's audit file may not exist. | Session stage states still govern reuse; a later invocation creates a different run ID. |
| Resume validation reports unsafe/indeterminate | Session success is preserved, but the already-created run remains overall `running`. | The prior success remains authoritative and validation is attempted again; the abandoned run is never reconciled. |
| Saving non-resumable staleness fails | Run remains initially `running`; session remains at the prior success. No stage executes. | Validation is attempted again without silently replacing the prior success. |
| Saving an ordinary skip to the run file fails | Session remains succeeded; the run file remains at its preceding state. | The stage is safely reconsidered as another skip. |
| Run `running` save fails | Session is unchanged and the stage does not execute. | Session authority makes the next decision conservatively. |
| Session `running` save fails after the run save | Run stage is `running`; session remains at its prior state and the stage does not execute. Forced downstream staleness is not durable. | A non-succeeded target retries; a prior success skips unless force/resume validation again requires replacement. |
| Terminal session save fails after stage work | Both durable records remain `running`, although the stage may already have external or canonical effects. | The session stage reruns because `running` is not reusable. Stage-owned idempotency remains essential. |
| Terminal run save fails after terminal session save | Session has the authoritative success, skip, failure, and downstream state; run stays `running`. | Execution resumes safely from the session, but the historical run remains inaccurate. |
| Post-publish cleanup fails | Session publish remains succeeded; a successful run save marks the invocation failed, while a failed run save leaves its prior overall `running` state. | A normal later invocation skips publish, so the execution-based cleanup gate does not retry. A forced publish or explicit manual clean is required; confirmed `COR-006`. |
| Final overall run save fails | Session and per-stage run records are terminal, but overall run status remains `running`. | A later invocation skips or reruns from session state and does not repair the old run. |
Save errors are returned with both the stage error and persistence error when
both exist. The session store is injectable, but run persistence is a concrete
`LocalStore` outside the `manifest.Store` interface. This leaves the run-side
failure rows above unexercised by focused runner tests and makes centralized
terminalization/reconciliation harder to test.
### Lifecycle Scenario Conclusions
- Scenario 1 is functionally safe for reuse: a non-resumable success and its
succeeded downstream records are persisted stale before execution; a failed
rerun leaves the target failed and downstream stale; an ordinary retry runs
both. An inability to validate preserves the prior success rather than
replacing it. `RSK-001` records the inaccurate invocation audit left by that
controlled error.
- Scenario 2 is conservative and source-backed: force pre-stales succeeded
downstream work; failure and a changed effective outcome stale it; a changed
self-skip stales it; and an identical repeated self-skip does not. Here
identical means prior status skipped, zero outputs, and the same reason;
diagnostic/metadata differences are not compared. Disabled stages represented
as success remain reusable success, while an explicitly skipped downstream
stage is naturally reconsidered because only success is ever skipped. The
runner-level distinction is coherent; `ARC-002` remains assigned to Stages 4
and 8 for the stage-specific contract and wording.
- The lock portion of Scenario 10 is resolved at the application boundary:
acquisition occurs before manifest access and the lock spans the entire
mutation lifetime. Stage 3 must decide the ignored-release and underlying
lock-file questions.
## Cross-Boundary Scenario Assignments
| Scenario | Primary audit stage | Final conclusion | Principal findings or accepted boundary |
| --- | --- | --- | --- |
| 1. Success becomes non-resumable, rerun fails, later reuse decision | 2 | Session reuse is conservative and correct; handled validation errors leave inaccurate invocation audit state. | `RSK-001`, `TST-002`; abrupt-death residue is accepted below after handled errors are repaired. |
| 2. Forced/changed upstream outcome with succeeded, self-skipped, disabled downstream | 2 | Canonical invalidation is sound; durable success, self-skip, and idempotent skip are distinct, but documentation blurs them. | `COM-002`; no production lifecycle defect. |
| 3. Extraction bundle followed by configuration/transitive-input change | 9 | Configured values are fingerprinted and external same-path changes are operator-forced; the direct trimmed input is incorrectly omitted. | `COR-020`; accepted external-force boundary below. |
| 4. Published/restored/prepared previous state consumed locally by analyze | 5 | Analyze is correctly local-only, but commit binding, readiness, source mapping, and effective selection are incomplete. | `COR-008`, `COR-010`, `COR-011`, `COR-022`. |
| 5. Publish failure at every upload boundary, then status/restore/retry | 4 | Pointer-last order alone is insufficient because the fixed manifest replaces the prior pair and readers do not share one strict authority rule. | `COR-004`, `COR-008`, `TST-004`. |
| 6. Restore identical/conflict/unsafe/cache/pre-manifest-install cases | 5 | Ordinary deterministic planning is strong; committed scope, directory force, coherent transition, audio identity, and foreign paths remain unsafe. | `COR-008`, `COR-009`, `RSK-006` through `RSK-008`, `TST-005`. |
| 7. Cleanup after skipped/failed/locked/partial/committed publish | 4 | Gates correctly require an explicit commit, but automatic cleanup is not durable/retryable and path mutation is not symlink-confined. | `COR-003`, `COR-006`, `COR-007`. |
| 8. Cancellation through workers, HTTP, subprocess, storage, manifests | 7 | Direct operations generally receive cancellation and release resources; transcribe can report partial success and subprocess descendants can survive. | `COR-018`, `RSK-011`, `RSK-014`, `EFF-002`. |
| 9. Disabled/unselected/reused/generated/extraction/previous source then publish filtering | 10 | Source families and publish filtering are deterministic, but effective selection does not consistently drive validation/prerequisites and some optional/passthrough contracts fail. | `COR-021` through `COR-024`, `RSK-015`, `ARC-007`. |
| 10. Concurrent same-session invocation and lock cleanup failures | 3 | Live local exclusion spans the full mutation lifetime; stale/release failures and remote lock snapshots remain operational risks. | `RSK-003`, `RSK-005`, `TST-003`. |
Scenario 3 is resolved. The fingerprint deterministically observes the resolved
executable and top-level config *paths*, pipeline ID, normalized timeout,
working directory, and sorted configured lane contracts. Changes to those
values make the result non-resumable. It intentionally cannot observe the
contents of the executable, Notarius configuration, profiles, prompts,
modules, references, environment, or other external/transitive inputs;
operations and extract documentation require `--force` after those changes.
That is an explicit operator-owned limitation, although a same-path executable
replacement deserves the same guidance as the documented configuration cases.
The current fingerprint also omits the resolved final-trimmed transcript and
its bytes. That is a direct Narratio-owned input rather than an unknowable
external dependency and is confirmed as `COR-020`. Force always pre-stales
succeeded downstream stages; automatically detected invocation-contract or
bundle-evidence changes rerun extract and invalidate succeeded downstream work
through the ordinary changed-outcome path.
Scenario 8 is resolved through the ordinary stages. Parent cancellation reaches
HTTP attempts, retry waits, every AWS call, audio downloads, and each direct
child process. HTTP/S3/local-file resources are released on error, and a
started direct child is waited. Cancellation is not complete for multipart
body construction (`EFF-002`) or subprocess descendants (`RSK-011`), and a
malformed repeated S3 continuation token needs cancellation to escape its
otherwise non-progressing loop (`RSK-014`). Merge, polish, normalize, trim, and
render invoke subprocess adapters synchronously and propagate their errors;
they do not introduce another worker lifetime. Transcribe alone aggregates
workers, and `COR-018` confirms that parent cancellation before an adapter
records an error can be mistaken for successful zero or partial output. Runner
reporting remains the Stage 2 dual-ledger behavior.
Scenario 9 is resolved. Without an explicit filter, configured artifacts are
executable exactly when enabled. With a non-empty filter, catalog registration
makes exactly the named configured keys executable even when their `enabled`
field is false; every other configured artifact becomes non-executable. A
non-executable artifact is available for dependency/input reuse only when its
configured canonical output is an existing non-empty file. That includes both
truly disabled artifacts and enabled-but-unselected artifacts, although both
receive provenance named `filesystem.disabled_artifact_output`. A selected
artifact may depend on an unselected artifact only through that reusable local
file. Selected dependencies are ordered before dependents; independent ready
nodes and final results are lexically stable. Cycles in the enabled graph are
rejected by configuration and cycles in an explicitly selected graph are
rejected again at runtime. The preflight's first unavailable-dependency error
is not stable when several selected nodes fail, which is `RSK-015`.
Built-in transcript/bounds entries resolve manifest-first then canonical,
prepared stable inputs resolve fixed `inputs/*.yml` paths, extraction entries
hydrate only from a complete compatible current extract record, configured
entries resolve only through catalog availability, and previous-session entries
resolve only from manifest-backed or filesystem `previous/` cache state. No
analyze resolver calls the object store. Required/optional behavior is coherent
for prepared, extraction, configured, previous, polished-transcript, and bounds
sources, but normalized/trimmed/Markdown built-ins bypass the optional policy
(`COR-021`). Previous-input preparation scans enabled artifacts only while
selection can execute disabled artifacts (`COR-022`), and its missing-input
guidance is not an executable CLI command (`COR-023`). Input-level `artifact`
and `path` fields are accepted and documented but never reach resolution or the
adapter (`COR-024`).
Each successful Scriptorium invocation validates a run-local non-empty output,
materializes it to the configured canonical path, and marks it generated for
later selected dependents. Generated/reused metadata and logs/configuration are
sorted or deduplicated deterministically. Publish builds an independent
availability catalog and filters only publish rules sourced from
`narratio.artifact.<key>`: an unselected configured rule is skipped even when
required, while built-in and explicit extraction rules are unaffected. A
selected configured rule must be locally available; publication never causes
analyze execution. Missing/no-executable analyze returns ordinary successful
no-output metadata so the pipeline can continue to publish; it is durably
reused until forced, closing analyze's behavior portion of `ARC-002` and leaving
the wording gap in `COM-005`.
## Intended Risk-To-Test Ownership Matrix
This Stage 1 matrix identifies intended owners. The final Stage 12 sufficiency
assessment follows the accumulated behavior-pass observations below.
| 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. |
Stage 7 test observations for this matrix:
| Risk | Existing focused protection | Gap or disposition |
| --- | --- | --- |
| HTTP retry, cancellation, and response installation | WhisperX tests cover success, retryable/non-retryable statuses, attempt timeout, parent cancellation, malformed JSON, and absence of failed output. | No supported-scheme table, streaming/body-production cancellation, response close observation, or oversized response case; `COR-016`, `EFF-002`, and `TST-007`. |
| Process launch, wait, and diagnostics | Shared tests cover successful separated/shared logs, start/exit context, a direct-child timeout, environment inheritance/override, bounded tail use, and override-secret error redaction. Each protocol adapter checks exact invocations and normal failures. | No process-descendant, explicit cancellation, inherited-secret, raw-log redaction, symlink, non-regular, or oversized-output case; `RSK-011` through `RSK-013` and `TST-007`. |
| S3 resources and pagination | Focused tests cover one-page normalization, streamed download/upload, not-found adaptation, and credential option construction. Temporary-download tests cover failure cleanup and wrapped context causes. | No valid multi-page, repeated/empty-token, later-page failure, cancellation, or body-close probe; `RSK-014` and `TST-007`. |
| Adapter fakes under production concurrency | No-op/fake tests cover cancellation/error and deterministic placeholder materialization. The focused adapter race command passes. | The WhisperX fake's unsynchronized request slice fails the full race suite when the transcribe stage uses it concurrently; `TST-001`. Other fakes currently have sequential production callers, so no blanket race finding. |
| Shared models and logging | Artifact-model JSON/conversion tests and bounds success/error tables protect current serialized shapes; logger tests protect output/nil-writer construction. | Output acquisition bounds/type remain `RSK-013`; suite-wide fake/model/logging value and redundancy remain Stage 12 work. |
Stage 8 test observations for this matrix:
| Risk | Existing focused protection | Gap or disposition |
| --- | --- | --- |
| Stable prepare inputs and source modes | Prepare tests cover explicit files, directory enumeration, S3 sorting/cache behavior, provenance, local/S3 conflict, idempotence, required/optional previous hydration, and replacement while requirements remain. | The no-requirement test asserts stale previous state survives, and no repeated-explicit-audio case crosses into transcribe; `COR-017`, `COR-019`, and `TST-008`. |
| Bounded deterministic transcription | Tests cover concurrency bounds, filename-derived identity, adapter error, invalid JSON, exact run-local output use, canonical materialization, and sorted result assertions. | No pre-canceled or mid-dispatch context case proves all jobs complete before success; `COR-018` and `TST-008`. The required race command also reproduces `TST-001` in the concurrent fake. |
| Manifest-first transformation and schema/report validation | Merge, polish, normalize, trim, and render suites cover manifest-first and fallback sources, missing/invalid inputs, adapter failures, configured reports/schemas, disabled paths, diagnostics, run-local paths, and canonical materialization. | Alternate valid adapter-returned paths, link/non-regular/oversized results, and multi-output materialization failure boundaries are not coherently tested; `ARC-006`, `RSK-013`, and `TST-008`. |
| Disabled ordinary-stage lifecycle | Trim tests prove disabled execution copies a valid canonical output; render tests prove disabled execution returns no outputs. Runner tests separately define durable success versus self-skip. | Render's focused document says only “skips,” without the durable-success and later-force consequence; `ARC-002` is resolved for these stages and `COM-004` owns the wording. Analyze remains Stage 10. |
Stage 9 test observations for this matrix:
| Risk | Existing focused protection | Gap or disposition |
| --- | --- | --- |
| Receipt, index, and configured-lane acceptance | Notarius adapter tables cover process errors, bounded receipt/index/summary parsing, exact management paths, bundle confinement, symlinks, required receipt fields, descriptor uniqueness, and optional descriptor contracts. Extract tests cover rejection, missing/duplicate/incompatible lanes, invalid/empty JSON, provenance construction, and deterministic output ordering. | Configured lane bodies have no acquisition bound, and extraction/catalog reread them without one; this extends `RSK-013`. Protocol cases otherwise have clear adapter or stage owners. |
| Promotion and immutable identity | `fileops` tests cover regular nested trees, permissions, source symlink/non-regular/root and entry replacement, destination no-replace races, destination-inside-source, platform support, and cleanup. Stage tests prove promotion errors advertise no outputs. | The assembled extraction slice does not inject a failure after successful install or exercise an ancestor/root replacement during resume; coordinate `TST-009` with the lower-level coverage and `COR-003` rather than duplicating every filesystem case. |
| Reuse and downstream invalidation | Stage and runner tests cover immediate and cross-invocation reuse, disabled-to-enabled reconsideration, configuration-value changes, missing/tampered payloads, source/contract/provenance mismatches, forced replacement, failed retry, unsafe resume errors, and changed-outcome invalidation. | No test changes the direct trimmed transcript beneath an otherwise succeeded record, so `COR-020` remains green. Same-path external dependency changes are documented force cases; one lifecycle contract test is sufficient if an explicit external revision mechanism is added. |
| Catalog and explicit consumption | Catalog tables require one complete current bundle and reject unsafe, incomplete, mismatched, incidental, or tampered state. Analyze and publish tests prove only explicitly configured lanes are passed/uploaded and invalid required lanes fail before execution/upload. | Resume and catalog independently encode much of the same bundle proof (`DUP-007`); future shared evidence tests should preserve resume's obsolete-versus-unsafe result and catalog's fail-closed all-or-none behavior. |
Stage 10 test observations for this matrix:
| Risk | Existing focused protection | Gap or disposition |
| --- | --- | --- |
| Source-family resolution | Analyze tests cover successful built-in transcript variants, prepared stable inputs, configured generated/reused outputs, extraction lanes, previous-cache manifest/fallback paths, required failures, and optional absence for prepared/configured/extraction/previous sources. Artifact-policy/config tables protect accepted identities. | No missing optional normalized, trimmed, or Markdown built-in case exists, so `COR-021` remains green. The focused analyze document also omits extraction and successful no-output lifecycle consequences (`COM-005`). |
| Selection, reuse, and dependency order | Catalog tests explicitly require selection to override `enabled`; analyze tests cover one selection filter, generated/reused metadata, independent lexical order, selected dependencies, cycles, and unavailable reused dependencies. Publish tests cover selected/unselected configured rules and prove built-in/extraction rules are unaffected. | No assembled test selects a disabled artifact, crosses that choice into previous-requirement preparation, or creates several simultaneously unavailable dependencies. `COR-022`, `RSK-015`, `ARC-007`, and `TST-010` own those seams. |
| Previous-session locality and guidance | Requirement collection covers enabled/disabled, deduplication, required-wins, and stable ordering. Prepare/restore/app tests cover planned requirements; analyze proves manifest/fallback local resolution and explicitly asserts no object-store call. | Selection is absent from the collector API (`COR-022`), while the guidance test asserts only a fragment of the malformed command and therefore preserves `COR-023`. |
| Scriptorium request and output materialization | Stage tests inspect exact named input paths, vars, generated/reused metadata, render-debug, logs/configuration, and canonical materialization. Adapter tests assert deterministic flags and generated invocation configuration. | Input `artifact`/`path` fields have no consumer or request representation (`COR-024`); no stage-side link/non-regular/oversized result case covers analyze, extending `RSK-013`; and the broad execution/resolution shape remains `SIM-003`. |
## Test-Suite Policy Conclusions
The graph inventory contains 904 functions in 95 test files, including 749
`Test` functions and no fuzz tests or benchmarks. Review grouped those tests by
the policy owner they protect rather than by filename. Parsing and validation
are concentrated in `config`, `artifactpolicy`, manifest decoding, and adapter
protocol tests; domain and durable state in `manifest`, `artifacts`, and app
lifecycle tests; filesystem safety in `pathsafe`, `fileops`, and the local
store; adapter contracts in their owning packages; orchestration and CLI in
`app`; and representative assembled behavior in app/stage workflow tests.
### Final risk-to-test sufficiency matrix
| Consequential invariant or risk | Current protection | Proper owner and realistic protected defect | Missing modes or cross-layer overlap | Sufficiency conclusion |
| --- | --- | --- | --- | --- |
| Canonical order, planning, and lifecycle transitions | Registry/planner tables, manifest transition tests, and app first-run/skip/force/failure/rerun cases | App lifecycle tests protect durable state-machine outcomes; manifest tests own field mutation | Run-store failures and resume-validation terminalization cannot be injected; several assembled tests repeat per-stage metadata | Strong ordinary protection; add the narrow persistence seam in `TST-002` and consolidate overlap under `TST-015` |
| Session/run identity and immutable invocation audit | Per-invocation runner tests and typed manifest load/save tests | App owns identity synchronization; manifest owns serialization | Derived identity conflicts and disagreement states remain visible only through `COR-001`/`TST-002` | Insufficient at the cross-manifest boundary; `TST-002` is the intended addition |
| Strict configuration, defaults, identity, and examples | Extensive strict-load/default/validation tables plus maintained-example loading | Config tests protect rejected language and normalized values; one example test protects assembly | A 949-line loader/validator table repeats complete YAML and broad default assertions already owned elsewhere | Behavior breadth is strong; restructure for clearer ownership under `TST-014` without reducing contract cases |
| Confined identities, paths, locks, and atomic persistence | `pathsafe`, `fileops`, artifact, cleanup, and local-store tests | Low-level owners protect no-escape/no-follow/atomicity; one app or stage case protects composition | Unsafe identity, destination-ancestor links, directory sync, stale/release lock faults, and concurrent runner cases are absent; duplicating each at every caller would be wasteful | Insufficient for confirmed safety roots; add one owner-level case per root plus representative composition under `TST-003` |
| Remote publish commit and cleanup recovery | Publish source/order/failure tests and cleanup path/effect tests | Publish owns pointer-last snapshot visibility; cleanup owns durable retry evidence | Current doubles record calls but do not model prior readable versions, accepted-with-error, barriers, or cleanup reload/retry | Insufficient for recovery/idempotency/concurrency; stateful fake and focused cases in `TST-004` |
| Restore committed-state authority and partial replacement | Restore plan/execution/workflow tables, conflict/force, malformed manifest, cache tests | Restore package owns snapshot selection, conflict policy, manifest-last commit, and resumable failure | No generation change, committed-vs-stray scope, partial forced overwrite, plan/lock race, same-size replacement, or foreign-path case | Insufficient at durable transition boundaries; package-level stateful cases in `TST-005` |
| Composition has one resolved configuration authority | Production assembly is exercised; injected environments generally leave `Env.Config` nil | App composition tests protect production-shaped dependency injection | No test intentionally diverges the two configs, so the impossible split fixture remains allowed | Insufficient seam fidelity; one authority test in `TST-006` |
| Adapter protocol, cancellation, resource, output, and secret boundaries | Strong argument/schema, retry/status, direct-child timeout, ordinary output, S3 one-page, and override-secret tests | Each adapter owns protocol behavior; shared subprocess/file acquisition owns mechanical limits and redaction | Descendant kill, inherited/raw secret leakage, scheme restriction, streaming cancellation, close observation, pagination progress, and non-regular/oversized outputs are absent | Insufficient at adversarial boundaries; targeted additions in `TST-007`, not duplicated protocol matrices |
| Bounded transcription concurrency and cancellation | Concurrency bound/order and ordinary adapter-error tests | Transcribe owns dispatch/cancel completion; WhisperX fake must honor concurrent interface use | The fake races and no barrier-controlled mid-dispatch cancellation exists | Insufficient; `TST-001` restores race signal and `TST-008` protects cancellation |
| Ordinary-stage input transitions and run-local output authority | Broad prepare and transcript-stage behavior suites | Prepare owns source replacement/deduplication; stage/shared acquisition owns requested path and multi-output commit behavior | Stale previous state, duplicate audio, alternate adapter path, and partial multi-output materialization are absent; schema tables already overlap heavily | Add only the transition/authority cases in `TST-008`; current schema and protocol coverage is otherwise sufficient |
| Extraction identity, immutable promotion, and consumer evidence | Strong adapter validation, lifecycle/reuse, catalog hydration, explicit-consumer, and low-level promotion tests | Extract owns direct-input fingerprint and lifecycle; artifact/file owners protect evidence and promotion | Direct input mutation and one assembled unsafe-root case are absent. Post-install orphan residue is not advertised authority and would require private choreography | Add the two marginal cases in `TST-009`; reject a dedicated orphan-residue test and reuse `TST-002`/`TST-003` owners |
| Analyze effective selection, optional inputs, dependencies, and passthrough contract | Strong source-family, catalog, order/cycle, reuse, local-previous, and publish-filter tests | Analyze/app composition owns effective selection; artifact owners retain source parsing | Optional built-ins, selected-disabled prerequisites, simultaneous errors, actionable guidance, and accepted passthrough fields are not protected | Insufficient at cross-owner seams; focused additions in `TST-010`, without repeating all source spellings |
| Artifact/source identifiers and remote/local mapping reject hostile structured input | Deterministic tables cover representative traversal, source families, and mappings | `pathsafe` and `artifactpolicy` are pure security-sensitive owners; restore mapping is a secondary seed source | No fuzz target probes arbitrary separators, normalization idempotence, round-trip mapping, or no-escape properties | Add focused seeded property fuzzing under `TST-013`; generic YAML/JSON parser fuzzing has lower marginal value |
| CLI parsing and operator rendering remain compatible and actionable | Command tables cover valid/invalid flags, dispatch, status/validate rendering, and semantic error fragments | App command tests own supported invocation language and actionable output | A few broad workflow cases repeat downstream policy, but no consequential CLI gap was established beyond `COR-023` guidance | Sufficient once `TST-010` asserts repaired guidance; retain semantic fragments rather than exact full prose |
| Representative assembled workflows prove boundary composition | App/stage tests run real filesystem/config/manifest collaborators with adapter fakes | App owns a small number of end-to-end success/failure cases; focused packages own details | Stage metadata checklists and a six-adapter generic failure matrix repeat focused suites and generic runner behavior | Overprotected and costly in places; consolidate under `TST-015`, retaining one assembled success and one generic failure |
| Default suite is deterministic, isolated, offline, and credential-free | Normal full suite passes in about 3.5 seconds using temp dirs, loopback HTTP, and test-binary subprocesses | Each test owns cleanup of process-global state; repository execution owns the aggregate signal | A config-secret test leaves environment values behind and fails under repeated execution; no tests use `t.Parallel` | Offline/credential isolation is sufficient; determinism is not until `TST-011` is fixed |
| Race diagnostics remain trustworthy | Baseline and shuffled race runs exercise all packages | Concurrent consumers and their doubles jointly own race-safe fixtures | WhisperX fake request capture races, causing the only observed race failure | Insufficient until `TST-001`; no second production race was observed |
| Repository automation enforces supported validation | Tag release automation cross-builds Linux, macOS, and Windows binaries | Normal change automation should protect test/vet/build compatibility before release | No PR/push test, vet, or build job exists; tag publishing is not tied to a validated revision | Insufficient; record the proportional enforcement decision in `TST-012` |
### Doubles, helpers, redundancy, and brittleness
Most tests follow the preferred collaborator order. Pure validation uses real
values; filesystem and workflow tests use real temporary directories and local
manifests; HTTP tests use loopback servers; and protocol subprocess tests run
the current test binary. Those subprocess argument assertions are interaction
contracts, not mock choreography, and should remain. Storage and publish are
the important exception: call-recording stubs cannot express version
visibility, accepted-with-error outcomes, pagination progress, or concurrent
commit barriers. `TST-004` and `TST-005` therefore call for one shared stateful
object-store fake. The WhisperX request-capture fake is the only double proven
unsafe for its actual concurrent consumer (`TST-001`); sequential fakes do not
need blanket synchronization.
The largest test, `config.TestLoadAndValidate`, spans roughly 949 lines and
combines strict YAML loading, normalization/default checks, and validation in
one repeated full-document table. It matches semantic error fragments rather
than exact full messages, but its fixture duplication obscures which layer
failed and raises change cost; `TST-014` owns a split into strict-load cases,
constructed validator tables, and a small assembly sample. By contrast, the
large artifact requirement table and protocol helper-process tests express
meaningful contract cases and should remain table-driven. Existing helpers are
mostly domain-specific setup; no generic fixture framework is justified.
The broad stage metadata checklist and six-adapter assembled failure matrix
repeat focused per-stage protocol/behavior suites and generic runner result
mapping. `TST-015` names the stronger protection that must remain before those
rows are deleted: focused owners plus one representative assembled success and
one representative terminal failure. No oversized snapshots or golden files
were found. Exact full `err.Error()` equality is rare; semantic fragments,
`errors.Is`, typed errors, and externally visible protocol arguments dominate,
so no repository-wide error-assertion rewrite is warranted.
### Determinism, coverage, fuzzing, and automation
`go test -count=1 -cover ./...` passed all packages in 3.49 seconds. Package
coverage ranged from 69.8% in `internal/manifest` to 100% in
`internal/logging`, with `cmd/narratio` at 0% because it has no test files.
These percentages are not a quality score: manifest's lowest result aligns
with the uninjectable save-failure branch in `TST-002`, while logging's 100%
does not justify more tests and the placeholder notification path remains an
architectural decision in `ARC-004`, not a coverage target.
The default suite uses no live services, paid APIs, ambient credentials, or
fixed external ports. It uses temporary paths, loopback servers, and helper
subprocesses, and no test calls `t.Parallel`; normal runtime does not justify
parallelizing process-global fixtures. A few bounded sleeps exercise actual
time/process behavior, but the observed order defect is instead deterministic:
`go test -shuffle=on -count=3 ./...` failed in 4.71 seconds with seed
`1786373771816345415` because
`TestLoadSecretsFromConfigLoadsValidFiles` leaves two secret environment
variables set. The isolated command
`go test -shuffle=1786373771816345415 -count=3 -run '^TestLoadSecretsFromConfigLoadsValidFiles$' ./internal/app`
reproduced failures on repetitions two and three. `TST-011` owns restoration of
the prior environment state.
`go test -race -shuffle=on -count=1 ./...` failed in 54.21 seconds with seed
`1786373816980315094`; the only reported race was the known WhisperX fake in
`TST-001`, now reached by `TestTranscribeStageConcurrencyBounded`. Every other
package passed. Barrier-controlled cancellation/concurrency tests should
replace additional timing dependence where `TST-008` touches that path, but no
general flakiness conclusion follows from the bounded evidence.
There are no fuzz tests. The highest-value additions are seeded property tests
for confined path normalization/join and artifact source identifiers, with
properties such as no panic, no root escape, stable normalization, and valid
remote/local round trips (`TST-013`). Existing YAML/JSON tables already protect
Narratio's own strict schemas; indiscriminate fuzzing of standard-library
decoders or every manifest/config wrapper would add less marginal value.
`.woodpecker/release.yml` runs only for tags and cross-builds release binaries
with Go 1.25 before publishing them. No automation runs the repository's local
test, vet, or build requirements on ordinary changes. `TST-012` recommends a
normal validation workflow and requires a release to consume or repeat the
validated revision. The 54-second race suite belongs in automation after
`TST-001` is fixed, at a frequency chosen against its cost; repeated shuffled
runs are suitable for scheduled/audit diagnostics rather than necessarily
every change.
## Path, Artifact, Filesystem, And Lock Conclusions
### Canonical ownership and normalization
`internal/artifacts/paths.go` owns the campaign/session layout, run-local
layout, previous cache, Notarius bundle, spool, and audio-cache constructors.
`internal/artifacts/s3_keys.go` owns session/run/current and published-output
keys. `internal/pathsafe` is the shared lexical boundary for slash-normalized
relative destinations: it rejects empty, absolute, drive-qualified, traversal,
and leading-backslash forms, normalizes mixed separators, and verifies a
joined destination remains lexically under its root. `internal/artifactpolicy`
adds configured source/destination policy without performing filesystem I/O.
Most production callers consume those owners directly. Previous-cache planning
normalizes configured and manifest-derived relative paths before calling the
path constructor, and restore derives a normalized relative path before
installing it. One ad hoc reconstruction remains in publish:
`resolvePublishRunManifestSource` joins the literal `manifest.json` to an
already-derived run root instead of using the canonical run-manifest helper;
`DUP-002` assigns that maintainability decision to Stage 11.
The constructors themselves do not enforce that campaign, session, run, or
artifact-relative components are safe opaque segments. Configuration currently
checks session identifiers and campaign identity only for presence. As a
result, traversal-bearing operator identity reaches both local `filepath.Join`
and remote `path.Join`; `COR-002` records the concrete namespace escape. The
exported previous-artifact helper also promises a path under
`previous/artifacts` while accepting a traversal value, although its current
production callers validate first. That helper-level contract should be fixed
with the same boundary rather than treated as a separate defect.
### Artifact resolution
The complete resolution chain is deterministic and matches the documented
source-family distinctions:
- built-ins normalize against a fixed registry, prefer matching producer
outputs from the manifest, validate content shape, then use a canonical
session fallback; missing state returns `SessionArtifactNotFoundError`;
- configured sources resolve only through explicit runtime-catalog
availability, preserve generated-versus-disabled-file provenance, validate
non-empty text, and return the same typed missing condition when planned but
unavailable;
- extraction hydration requires a successful current extract record, exact
configured definitions, producer/receipt identity, compatible contract and
external provenance, confined regular files, a complete index/lane set, and
matching checksums before making any source available;
- previous-session planning sorts requirements and records, validates relative
cache destinations, loads current remote state with expected campaign,
session, and run identity, and hydrates only explicit remote objects into the
local cache. Analyze-time resolution is then local-only, preferring a
manifest input path before the documented filesystem fallback and returning
a typed missing error; and
- current-state loading has distinct typed missing-pointer and missing-manifest
errors, rejects empty or malformed state, and validates expected campaign,
session, and run identity or pointer/manifest run consistency.
Only extraction claims checksum/contract/external-provenance enforcement at
resolution time, and its implementation enforces all three. Built-in,
configured, and previous-cache resolvers enforce their documented content and
availability rules but do not re-hash ordinary manifest records. That is not a
Stage 3 contract mismatch; Stages 5 and 10 remain responsible for deciding
whether restore or analyze threat models require stronger trust than the
documented previous-cache fallback and manifest-aware built-in behavior.
### Filesystem mutation mechanics
The mutation inventory separates lexical policy from explicit-path mechanics:
| Mechanism | Normal-process atomicity and cleanup | Confinement and durability conclusion |
| --- | --- | --- |
| `WriteFileAtomic` | Same-directory temporary file; write, file sync, close, chmod, rename; removes a failed temporary file; replaces an existing destination where the platform rename permits it. | Accepts an explicit destination as intended, but follows symlinked destination ancestors and does not sync the parent after rename. |
| `CopyFileAtomicWithChecksum` / `CopyFileAtomic` | Opens the explicit source, copies and hashes through a same-directory temporary file, syncs/closes/chmods, then renames; prior destination survives failures before rename. | Does not require a regular source, follows source and destination symlinks, and does not sync the destination parent. Current canonical materialization callers validate content but inherit destination confinement and crash-durability findings. |
| `InstallDownloadedTempFile` | Chmods and renames a caller-supplied sibling temporary file; same-filesystem placement is established by current audio, previous-cache, and restore callers. | Does not sync the downloaded file or destination directory. The object-store downloads close their path-based files but expose no completed-data sync guarantee. |
| `PromoteDirectory` | Copies to a temporary sibling, permits only regular files/directories, uses confined source handles and identity checks, syncs files/directories, atomically installs without replacement, syncs the destination parent, cleans failures, and reports unsupported platforms before creating the promotion tree. | Source replacement, source symlinks/non-regular entries, existing or concurrently-created destinations, same-filesystem placement, and platform behavior are strongly handled. Destination ancestors remain path-based and can be symlinked or replaced. |
| Cleanup removal | Refuses empty roots/targets, lexical root deletion and traversal, wrong final-node type, and a final target that is a symlink. Missing targets are idempotent. | It does not inspect root or ancestor components and performs `RemoveAll` after a path-based check, so a symlinked ancestor can redirect deletion outside the root. |
Low-level file and storage helpers correctly accept explicit destinations and
do not infer stage, workspace, or cleanup policy. Policy belongs in their
callers. The current caller-side checks are lexical and final-node checks,
however, so they cannot prevent destination-ancestor symlink traversal or
replacement. `COR-003` consolidates the write, promotion-destination, and
destructive-cleanup variants under that single root cause; later stage and
adapter audits should reference it rather than duplicate it.
Single-file writers preserve the prior visible file on failures before rename,
but unlike directory promotion they do not sync the containing directory after
rename. Download installs additionally do not establish a file-sync boundary.
`RSK-002` records the resulting crash/power-loss durability gap for canonical
files and manifests. Stage 3 also confirmed that `DUP-001` duplicates this
same incomplete persistence sequence across both manifest save paths.
Runtime layout and copied/generated files use fixed requested modes `0755` and
`0644` subject to process umask, including transcripts, artifacts, logs,
manifests, and the lock. No operating contract requires a restrictive umask or
documents an ownership/mode boundary. `RSK-004` records the private-data
exposure risk rather than assuming a deployment-specific parent directory is
always restrictive.
### Lock scenario and focused tests
The local lock is an atomically created `O_CREATE|O_EXCL` sentinel. This is
sufficient to serialize two live contenders before either reads a manifest,
and the existing focused conflict test exercises that mechanism. It is not an
OS-owned advisory lock: any existing sentinel conflicts without interpreting
the recorded PID/time, process death cannot remove it, and recovery is manual.
Release closes then unlinks, but the runner discards its error. An unlink
failure can therefore let a command report success while every later invocation
is blocked. `RSK-003` resolves Scenario 10 and the Stage 2 release question.
Existing focused tests strongly cover lexical mixed-slash/traversal handling,
artifact-source ordering and typed missing states, extraction trust checks,
current-state identity, ordinary atomic replacement/temp cleanup, source-side
promotion races and special files, no-replace installation, permissions, and
basic lock acquire/conflict/release. They do not cover unsafe identity segments,
destination-ancestor symlinks or replacement, cleanup through a symlinked
ancestor, directory-sync/file-sync guarantees, stale-lock ownership/recovery,
release failure, or assembled concurrent runners. `TST-003` assigns a narrow
cross-package regression set to Stage 12; each confirmed finding below also
identifies its smallest behavior-level test.
## Publish, Remote Commit, Lock, And Cleanup Conclusions
### Prerequisites, sources, and deterministic order
Enabled publish requires successful `prepare`, `transcribe`, `merge`, `polish`,
`normalize`, `trim`, `render`, and `analyze` session records before any remote
write. `extract` is deliberately absent: disabled extraction is valid, while an
explicit required extraction output still fails artifact resolution unless the
current extraction state is complete and trusted. Publish disabled or
`upload_run` disabled returns ordinary stage success with `skipped` metadata;
it is not the self-skip claimed by the focused stage document. `COM-002` records
that contract mismatch and resolves the publish portion of `ARC-002`.
Publish builds all plans before uploading. Configured-artifact selection applies
only to `narratio.artifact.*` rules; built-in and extraction rules are unaffected.
Static and remote locks are merged before execution, with static rules winning.
A matching lock intentionally skips an output even when required and preserves
an existing published destination. An unselected configured output is recorded
as unselected before lock handling. Missing unlocked optional outputs skip;
missing unlocked required outputs fail. `--force` changes runner reuse and
invalidation only: it never enters output resolution and cannot bypass either
kind of lock.
The upload sequence is deterministic:
1. run files sorted by slash-normalized relative path;
2. resolved published outputs in validated configuration order;
3. previous-cache files sorted by relative path;
4. the fixed `current/manifest.json`; and
5. `current/run_id.txt`, always the final object-store call.
The run archive includes `manifest.json`, receipt/stderr diagnostics, and every
other non-directory entry except exact `audio/**` and
`extract/notarius-output/**` paths. Similarly named paths remain included.
Audio is not uploaded. Previous-cache inclusion recursively includes every
non-directory entry under the session's durable `previous` directory. Neither
walk rejects symlink entries before storage opens the local path, which is the
confidentiality defect in `COR-005`.
### Remote-current authority and partial outcomes
The storage boundary accepts explicit local paths and keys and implements upload
as unconditional object replacement. It has no transaction, conditional write,
generation check, rollback, or indeterminate-outcome query. Those are publish
protocol responsibilities rather than storage policy. Every successful upload
before the pointer is durable partial state; retry uploads the complete plan
again and overwrites the same destinations. Partial run/session objects are not
removed. `COR-001` separately means a later invocation can combine a new run ID
with a stale remote run prefix.
`LoadCurrentState` reads the pointer first, then the fixed manifest, and can
reject pointer/manifest run disagreement when its caller requests run
validation. Previous-cache planning does request it; Stage 5 found that the
shared restore/status discovery path does not. Thus the pointer is the intended
commit marker, but `COR-008` confirms that two primary readers can accept a
manifest from a different run. Independently, overwriting the fixed manifest
before the pointer does not preserve the prior coherent pair; `COR-004` owns
that publish-side root cause.
| Publish boundary or durable remote state | Remote-current interpretation | Retry and cleanup consequence |
| --- | --- | --- |
| Planning/prerequisite/source resolution fails | No upload occurs; any prior pointer/manifest pair remains current. | Non-succeeded local publish reruns; cleanup is ineligible. |
| A run, published-output, or previous-cache upload fails | Earlier objects remain or overwrite existing keys; the prior current pair is still intact because the fixed manifest was not reached. With no prior pair, no current state exists. | Retry unconditionally reuploads the whole plan. Cleanup is ineligible. |
| Current-manifest construction fails | All planned data objects may exist, but the prior current pair remains intact. | Same retry behavior; cleanup is ineligible. |
| Current-manifest upload definitely fails before remote acceptance | The prior pair remains intact; no pointer attempt occurs. | Same retry behavior; cleanup is ineligible. |
| New current manifest is accepted while the old pointer remains | First publish has a manifest but no pointer and restore fails on the missing pointer. A replacement publish has old pointer/new manifest disagreement: previous-cache loading rejects it, but restore/status discovery accepts it under `COR-008`. | A successful retry repairs the pair; cleanup is ineligible until then. This includes the ordinary window between the final two uploads. |
| Pointer upload returns an error | If not accepted, the disagreement above remains. If the service accepted the write but the response was lost, remote current is committed although local publish is marked failed. The interface cannot distinguish these outcomes. | Retry is conservative and overwrites again; automatic cleanup does not run for the failed local outcome. |
| Pointer upload succeeds | Pointer and manifest identities agree and the new run is current. | The stage returns `uploaded=true` and `current_pointer_written=true`; automatic cleanup may become eligible. |
| Remote commit succeeds, then terminal session/run persistence fails | Remote current remains committed. A session-save failure leaves local publish running and causes republish; a run-save failure leaves session publish succeeded and ordinary retry skips it. | The latter boundary can permanently miss automatic cleanup under `COR-006`; remote state itself remains authoritative. |
The current manifest is generated before commit with
`current_pointer_written=false`, while the local post-upload result records the
same field as true. Current-state readers correctly derive authority from the
actual pointer and ignore that remote metadata, and cleanup correctly uses the
post-commit local record. `ARC-003` records the future-consumer ambiguity rather
than treating the precommit snapshot as remote authority.
### Lock authority and cleanup truth tables
Loaded static and remote locks are honored even for forced publish, but the
remote lock store is an unsynchronized snapshot. Publish loads it before the
local session lock, and lock add/remove performs an unconditional read-modify-
write without a remote generation check or the runner's session lock. Concurrent
operator mutations can lose one another, and a lock added after publish's load
does not protect that in-flight upload. `RSK-005` records this limit; sequential
lock behavior and static precedence are otherwise correct.
Automatic cleanup uses the following gate. “Confined” here means the existing
lexical/final-target validation; symlinked ancestors remain the shared
`COR-003` defect, and stale run-derived targets remain a `COR-001` consequence.
| Policy and local publish record | Automatic action |
| --- | --- |
| Both cleanup policies false | No cleanup lookup, deletion, or metadata mutation. |
| Either policy true, but `publish` absent from this invocation's executed list | No cleanup, even if the session publish record is succeeded and records a commit. This one-shot behavior is `COR-006`. |
| Publish executed but its session status is not succeeded | No cleanup. |
| Publish succeeded, but publish/upload is disabled, `skipped=true`, `uploaded` is not true, `current_pointer_written` is not true, or pointer key is empty | No deletion; cleanup-skip reason is persisted. |
| Publish succeeded with enabled upload, `uploaded=true`, pointer true/key present, and spool policy true | Validate the run-scoped spool target beneath the configured spool root, then delete it. Spool-only completion metadata is persisted. |
| The same commit gate with workspace policy true | Validate the run-scoped work target beneath the workspace root, then delete it. Completion metadata is only mutated in memory and is lost under `COR-007`. |
| Either requested deletion fails | Record best-effort failure metadata and fail the invocation. The succeeded publish remains reusable, so ordinary retry does not retry cleanup (`COR-006`). |
Manual `clean` is a separate explicit operator boundary and correctly does not
require a prior publish or commit metadata. Session cleanup requires an explicit
campaign/session resolution and deletes the confined session work and spool
trees. `clean --all` rejects session/campaign selectors, deletes the confined
workspace `work` tree and non-symlink spool-root children, and optionally clears
the configured audio-cache namespace. Cache deletion requires `--clear-cache`;
`--dry-run` performs validation and reports without removing. There is no clean
force flag. Publish `--force` therefore cannot reach or weaken manual or
automatic target validation.
Focused tests cover successful ordering and exact exclusions, source-family
selection, required/optional/locked rules, force with a preloaded remote lock,
pointer absence on selected failures, ordinary cleanup eligibility, unsafe
final targets, and manual dry-run/scope behavior. They do not seed a prior
current pair across a manifest/pointer failure, model ambiguous upload success,
exercise retry after partial commit, reject symlink upload sources, assert
workspace-cleanup metadata durability, retry failed cleanup, or model concurrent
remote lock writers. `TST-004` assigns the smallest stateful protocol cases to
Stage 12.
## Restore, Audio, And Previous-State Conclusions
### Discovery authority and caller policy
Restore resolves configuration and storage, then delegates pointer/manifest
loading to `artifacts.LoadCurrentState`. Campaign and session expectations are
always checked. The helper's run check is optional, however: previous-cache
planning and previous-readiness inspection set `ValidateRunID=true`, while
restore/status discovery does not. Missing pointer or manifest is fatal to
restore, displayed non-fatally by status, and is skipped only when every
previous-artifact requirement is optional. Malformed or inconsistent previous
state remains an error even for optional requirements. Those distinct missing-
state policies are appropriate; omitted restore/status run validation is part
of the broader snapshot defect in `COR-008`.
Restore then lists the entire session prefix instead of deriving its file set
from the discovered manifest. It maps the fixed current manifest to local
`manifest.json`, includes `transcripts/**` and `artifacts/**`, optionally
includes `audio/**`, and excludes current/run archives, logs, reports, generated
configuration, inputs, and the current session's archived `previous/**`.
Required previous-session cache objects are planned separately from that prior
session's current manifest. Lexical path normalization and root-relative joins
reject traversal and produce deterministic local paths; `COR-002` still owns
unsafe identity components and `COR-003` owns filesystem-link confinement.
Prefix-wide listing is not a committed snapshot. Failed publish objects, stale
destinations left by older runs, and locked/unselected leftovers can all enter a
restore even when absent from the accepted manifest. Execution later downloads
the same mutable keys again, and downloaded current/previous manifests are not
revalidated against the discovered run ID. `COR-008` consolidates these
scope/version/run-binding failures and corrects the Stage 4 reader truth table.
### Planning, force, dry-run, and local serialization
Actions sort by local relative path and then remote key. Classification is:
| Local target and remote metadata | Without force | With force |
| --- | --- | --- |
| Missing | `download` | `download` |
| Directory where a file is expected | `conflict` | Still `conflict`; command-level handling is defective under `COR-009`. |
| Audio with positive remote size and equal local size | `skip_same` without content comparison | Same `skip_same`; force does not refresh it. |
| Audio size mismatch or unavailable remote size | `conflict` | `download` |
| Non-audio positive-size mismatch | `conflict` | `download` |
| Non-audio equal/unknown size and equal downloaded checksum | `skip_same` | `skip_same` |
| Non-audio equal/unknown size and different downloaded checksum | `conflict` | `download` |
Normal differing files therefore require explicit force and identical files
remain untouched. Audio's size-only shortcut and cache validation are the
integrity risk in `RSK-007`. Final symlinks are followed by `os.Stat` and can be
classified as same; ancestor/final-link trust should be repaired with the shared
filesystem capability required by `COR-003` rather than with restore-only
lexical checks.
Dry-run returns after discovery, planning, and summary rendering. It does not
create the session layout, acquire a lock, write a report, populate audio cache
or spool, install a file, or mutate remote state. It does download pointer,
manifest, and same-size non-audio bodies to system temporary files, which are
removed. Thus it is durable-session pure, not literally free of local temporary
writes. The same checksum classification can download a differing object once
during planning and again during forced execution; current and previous
manifests are also downloaded at discovery/planning and again for installation.
`EFF-001` records this avoidable I/O and the documentation precision issue.
Executable restore acquires the local session lock only after its plan is
complete. It does not reclassify `skip_same`, `conflict`, or download decisions
under the lock. Together with incremental installation and intentional lack of
rollback, this creates the coherent-local-transition risk in `RSK-006`.
`--force` cannot bypass path joining or the local session lock, but the command
only blocks conflicts when force is false. A directory conflict therefore
survives a forced plan, is ignored by execution, and can coexist with a
successful report and newly installed manifest (`COR-009`).
### Execution, manifest-last behavior, and retry
Execution filters to `download` actions, preserves their sorted order, moves
the single current-session manifest action to the end, and rejects multiple
manifest downloads. Ordinary files download to sibling temporary files and
install by rename. The manifest temp is decoded and checked for requested and
discovered campaign/session before rename, but not for the discovered run ID.
Audio delegates to the shared spool/cache materializer. A successful execution
then writes `reports/restore-latest.json`; the report is diagnostic and is the
only intended write after manifest installation.
| Failure boundary | Durable local result and retry behavior |
| --- | --- |
| Discovery or planning | No session layout/report/restored file is written; system temporary reads are cleaned. Fix remote/config state and retry. |
| Dry-run | No durable session mutation; reported actions are recomputed on apply. |
| Layout or lock acquisition | Layout creation can precede a lock failure, but no planned file or report is installed. Retry after lock recovery. |
| Unforced conflicts | No planned file is installed; a failed conflict report is persisted under the lock. Resolve or retry with force. |
| Non-manifest download/install | Earlier installs remain; the old local manifest remains; the failing temp is removed and a failure report is attempted. Retry reclassifies completed files as same. |
| Audio download/validation/materialization | Download failure removes its temp and preserves any prior spool destination; a post-download validation failure can leave the newly installed invalid spool file. A destination copied before cache-population failure also remains. Retry overwrites/reuses those states deterministically. |
| Manifest download/decode/identity/install | All earlier files remain; failures before rename preserve the prior local manifest. Retry must complete the remaining plan; forced partial replacement can make the old manifest describe changed files (`RSK-006`). |
| Manifest installed, report write fails | Restored durable state is installed and authoritative despite command failure; report may be absent/stale. Retry normally classifies files as same and can recreate the report. |
| Report succeeds, summary write fails | Restore state and success report remain complete; only command output failed. |
There is no rollback, transaction marker, or runner check for an incomplete
restore. Manifest-last prevents a new remote manifest from being installed
before its files, but it cannot keep the old local manifest coherent after a
forced partial overwrite. Individual rename visibility is good in an ordinary
process; `RSK-002` remains the shared crash-durability gap for downloaded-file,
manifest, and report installation.
### Audio and previous-cache identity
`audio.MaterializeS3Audio` is correctly shared by prepare and restore. Its cache
namespace includes bucket and full object key. A cache miss downloads through a
sibling spool temp, validates non-empty/expected size, atomically copies to the
destination with a computed checksum, then optionally populates cache. A cache
hit avoids storage and copies directly. The stored ETag and computed checksum
are not bound to cache validity: any nonempty cache file of expected size is
accepted, and existing restore audio of equal size is skipped without reading
either body. `RSK-007` records stale/corrupt same-size reuse. Failed downloads
clean temporary files, while post-install validation/cache failures leave the
explicit partial states in the table above.
Previous requirements come only from enabled configured artifacts, deduplicate
by artifact name with required winning, and sort deterministically. With no
previous session ID, required requirements fail and optional ones skip. With an
ID, planning strictly validates campaign/session/pointer-run identity, always
maps the previous current manifest into `previous/manifest.json`, and maps
available artifact objects beneath the current session's `previous/` cache.
Missing pointer/manifest or artifact objects fail required requirements and
skip optional ones. Prepare consumes the same plan but intentionally
overwrites its managed cache and records checksums/inputs; restore applies local
conflict policy. Analyze later resolves these files locally without storage,
preferring a matching main-manifest input and otherwise using the deterministic
cache path.
The remote manifest does not retain a source-to-destination map for ordinary
published outputs. Previous planning tries the producer's local relative path,
then any `published_paths` entry with the same basename, then the current
artifact output path. A custom publish destination with a different basename is
unresolvable; duplicate basenames are ambiguous. `COR-011` owns that identity
loss. Separately, status/validate only prove that the prior current pair exists:
they neither apply `BuildPlan`'s artifact-object checks nor its optional missing-
ID policy, so they can report missing optional state as an error or missing
required objects as ready (`COR-010`).
Restored manifests preserve remote host-local absolute path fields. Top-level
run/work/spool fields trigger the already confirmed `COR-001` on the next
invocation. Output/input records are also trusted preferentially when their old
absolute path happens to exist, allowing later consumers to read outside the
new workspace instead of the restored canonical copy; `RSK-008` records this
distinct restored-reference risk.
Focused tests strongly cover typed current-state failures when run validation
is enabled, restore campaign/session mismatch, default/include-audio mapping,
traversal rejection, normal same/conflict/force actions, deterministic previous
requirements, required/optional missing remote state, cache miss/hit/refresh,
download cleanup, manifest-last validation, conflicts, lock failure, reports,
and restore-to-run/analyze workflows. They omit the exact committed-snapshot,
forced-directory, plan-under-lock race, partial forced rollback, same-size audio
mutation, custom publish destination, optional operator-readiness, and foreign
absolute-path cases above. `TST-005` assigns one stable behavior test per root
invariant to Stage 12.
## Configuration And Composition Conclusions
### Discovery, precedence, defaults, and validation order
The process entry point delegates directly to `app.Execute`, which owns command
parsing, exit classification, output streams, and dispatch. Pipeline selection
uses an explicit path first and otherwise the first existing system default.
Campaign selection rejects simultaneous ID and file selectors, loads an
explicit file or registry/default ID, and checks that the selected ID agrees
with the loaded campaign. Session selection uses an explicit path, then the
first local default, then the configured campaign/session remote key when a
session ID is available. Session stable inputs override campaign values; their
resolved value retains the owning config path and source kind.
Pipeline, campaign, rendered-template, and session inputs all use the same
known-field YAML decoder. Defaults are applied before resolution and validation.
Pointer booleans and integers distinguish omission from explicit false/zero;
an explicitly empty module list remains empty. Ordinary empty scalar and
publish-output values receive documented defaults, while `normalize.output_path`
tracks YAML presence so an explicit empty value is rejected. Enabled Notarius
paths are resolved relative to the pipeline file. A concrete session template
is rendered from a narrow variable set, rejected for missing/unknown/unused
values, decoded through the ordinary session loader, resolved, and fully
validated before replacement.
The final order is pipeline load/defaults, campaign load, session
load/selection, stable-input resolution, then pipeline, campaign, session, and
cross-config validation. The remote-session fallback necessarily builds enough
pipeline/campaign/storage state to fetch the session before final validation;
secrets are loaded before constructing that object store. This ordering is
otherwise coherent, but the shared decoder's trailing-document check is
incorrect under `COR-012`.
### Operator field-to-consumer trace
| Operator field family | Default/normalization and validation | Runtime consumer and conclusion |
| --- | --- | --- |
| Stage enablement/order, concurrency, locks, workspace/cache/campaign/spool roots | Canonical stage order is fixed; enabled stages, positive global concurrency, lock settings, and non-empty principal roots are validated. Relative configured roots remain supported. | Planning selects enabled/requested stages; the runner derives layout, local locks, artifact/manifest stores, and worker bounds from the resolved config. No unconsumed execution selector was found. |
| Campaign/session IDs, stable inputs, audio source, and previous session | Resolution enforces campaign consistency and stable-input precedence; validation requires one audio mode and required campaign/session values, with S3 bucket cross-checks. | Selection, layout, audio materialization, publish keys, and previous-cache planning consume these values. Unsafe ID segment syntax remains `COR-002`; the previous-session CLI expectation is incomplete under `COR-015`. |
| WhisperX, Seriatim, Audita, and Scriptorium command/protocol settings | Defaults fill endpoints, commands, retry/concurrency/tuning, timeouts, and artifact contracts; enums, safe paths, environment-variable names, dependencies, and cycles are checked. | Adapter constructors and transcript/analyze/trim/render stages consume the settings. Parseable non-positive durations can pass configuration but fail at composition or stage execution under `COR-013`. |
| Notarius extraction and lane settings | Disabled configurations stay lightweight; enabled configurations require command, config/work paths, declared inputs/outputs, unique lanes, timeout, and positive concurrency. Relative paths are anchored to the pipeline file. | The subprocess runner is constructed only when extraction is selected and Notarius is enabled. Stage 9 confirmed that resolved settings reach the invocation/fingerprint/output-contract boundary; no unconditional external work was found. |
| Analyze artifact source/destination, previous requirements, trim/render bounds | Source/destination identities, normalized relative paths, uniqueness, dependency existence, cycles, and bounds syntax are validated. | Artifact resolution and Scriptorium/Seriatim stages consume the values. Deeper dependency semantics remain assigned to Stage 10 rather than being inferred from configuration shape. |
| Publish outputs/backend, S3 storage, cleanup, and remote locks | Publish sources/destinations and S3/env/path prerequisites are cross-checked; cleanup and lock defaults are explicit. | Publish, storage construction, remote locking, restore, and cleanup consume these fields. The storage backend selector itself is not validated and is not authoritative under `COR-014`. |
| Filesystem secrets directory and credential variable names | The directory is optional; relative values intentionally use process working directory. Entry names and configured env-var names use the environment-name grammar. Existing process values win. | Secrets are installed into the process environment before adapter/store construction; values are not copied into config, metadata, reports, or logs. Entry-type trust is unsafe under `RSK-010`. |
| Notification backend, recipient, and timeout | Fields are accepted and the timeout is syntax-checked. | Production composition always injects the no-op sender, so these operator-facing settings currently have no behavioral consumer. `ARC-004` assigns the boundary decision to Stage 7. |
Validation is strong for enumerations, artifact paths, cross-stage dependencies,
unique destinations, session/campaign consistency, environment names, and
positive concurrency/tuning. The deliberate empty-value/default behavior in
the maintained examples agrees with `docs/config.md`. Four maintained pipeline
examples are loaded with representative local or S3 sessions by the config
suite, and extraction contract tests preserve their published Notarius shapes.
Examples contain only fictional endpoints and credential variable names, not
secret values. The material documentation drift is the notification block:
the public annotated example presents settings as optional configuration while
the internal overview alone explains that notification is a placeholder/no-op.
### Secrets, conditional composition, and lifecycle
The filesystem secret loader reads directory entries in deterministic name
order, skips directories and invalid environment names, trims only trailing
line endings, preserves existing environment values, and reports only the
directory and counts. Error and success messages name fields/files but never
include secret content. S3 and Audita resolve values only at their adapter
boundaries. No config serialization, manifest, report, stage metadata, or log
path retaining a raw secret value was found.
The runner supplies lightweight WhisperX, Seriatim, Audita, and Scriptorium
wrappers when callers do not inject them. Those constructors do not connect to
external services or start subprocesses. Notarius construction is conditional
on selected extraction, and object-store/remote-lock construction is
conditional on selected behavior that needs remote state. These collaborators
own no closeable process-level resource: HTTP clients and AWS clients are
reused value wrappers and subprocesses are owned per invocation. Thus there is
no adapter shutdown leak at the composition boundary. Loading secrets again in
the object-store helper is redundant but bounded and preserves ordering; it is
not a standalone efficiency finding.
Successful remote session discovery is the exception to otherwise explicit
temporary-file ownership. It downloads `session.yml` to a system temporary
file and retains that path in resolved provenance without any success cleanup.
Full runs later copy the file into canonical inputs but still leave the private
temporary copy; read-only and single-stage commands leak it directly. This is
recorded as `RSK-009`.
Tests can inject an `Env` and production supplies defaults only for nil
collaborators. This is an effective no-live-credential seam, and focused tests
cover enabled/disabled composition without network access. The seam can,
however, retain an injected `Env.Config` different from the separately supplied
resolved config, splitting layout/manifest identity from secrets, adapters,
locks, and stage behavior. Production never exposes that combination, so
`TST-006` assigns a guard/fixture decision to Stage 12 rather than treating it
as a production defect. Mutable package-level constructor seams and small
single-stage command wrappers are likewise test/dispatch mechanics, not new
architectural findings.
## External Adapter And Shared-Support Conclusions
### Boundary and resource matrix
Stages depend only on Narratio request/result interfaces. HTTP, multipart, AWS
SDK, Smithy, `exec.Cmd`, and process-state types remain private to their adapter
packages; no transport type or retry policy leaks into stage code. Arguments,
working directories, environment changes, generated invocation configuration,
stream routing, exit adaptation, and first-pass output validation are likewise
adapter-owned. Production callers choose run-local paths and materialize
validated results, which is the intended division of policy.
| Boundary | Acquisition | Cancellation and waiting | Release and conclusion |
| --- | --- | --- | --- |
| WhisperX HTTP | Opens the audio file for each attempt, constructs multipart content, and obtains one response. | Each attempt has its own timeout; parent cancellation suppresses retries and interrupts retry timers and HTTP I/O. Building the multipart body itself is not cancellation-aware. | Audio files are closed and response bodies are closed on every response path. Responses are bounded to 10 MiB and installed only after successful JSON validation. Request prebuffering remains `EFF-002`; retry/status behavior otherwise matches the integration contract. |
| Shared subprocess | Opens zero, one shared, or two separate log files, then starts one direct child and waits synchronously. All subprocess adapters use this owner. | Parent cancellation and optional timeout reach `exec.CommandContext`; `Wait` always runs for a successfully started direct child. Only that process is killed, not its descendants (`RSK-011`). | Open-failure cleanup is correct and log descriptors are closed after `Wait`, although close errors are intentionally discarded. Raw stream and diagnostic redaction is incomplete under `RSK-012`. |
| S3 object storage | AWS client construction is lazy with respect to network I/O. Each download obtains a response body and destination file; upload opens and streams a source file. | Every SDK call receives the caller context. List pagination has no deadline or progress guard (`RSK-014`). | Response bodies and local files are closed; uploads stream rather than prebuffer. Missing-object errors are adapted to `(false, nil)`. Successful system-temp ownership remains the caller issue in `RSK-009`, not an adapter leak. |
| Audio materialization | Creates a sibling temporary file and delegates one download on cache miss. | Context is checked before work and passed to storage. | Failure removes the temporary file and success renames it into place. Cache identity remains `RSK-007`; repeated install mechanics remain `DUP-003`. |
| Notification | The no-op/fake sender acquires no external resource. | Both honor a canceled context before returning. | There is no production delivery resource to release because no production transport exists; the accepted operator configuration is therefore confirmed as `ARC-004`. |
No adapter creates an internal goroutine or channel. WhisperX concurrency is
owned by the transcribe stage, while every subprocess call is synchronous. The
focused adapter race command consequently passes; the full baseline race still
fails only when the unsynchronized WhisperX fake is exercised concurrently by
the stage. `TST-001` therefore represents a fake/consumer contract defect, not
an HTTP-client race. Stage 8 confirmed the worker's bounded concurrent contract;
Stage 12 owns the test-double repair.
### HTTP, storage, retry, and malformed-response behavior
WhisperX uses stable multipart field names, per-attempt timeouts, a bounded
response reader, context-aware retry delay, and the documented retry classes:
429, 5xx, attempt timeout, and network errors retry; other 4xx, malformed
successful JSON, and explicit cancellation do not. Failed attempts never
install the output. The constructor does, however, accept any absolute URL with
a host, including `ftp://`, although the concrete HTTP transport cannot execute
that request; this is `COR-016`. The full audio file is copied into a
`bytes.Buffer` before the HTTP request begins on every attempt. This both scales
memory with concurrent input size and delays cancellation until after local
copying, as recorded in `EFF-002`.
S3 operations normalize bucket-relative keys and leave ordering policy to
callers. The callers that need deterministic order sort their resulting object
sets. Downloads and uploads stream, response bodies are closed, provider
not-found shapes are adapted, and system-temporary download failure removes the
partial file. Pagination continues only while the response is truncated and a
next token exists, but a repeated non-empty token is accepted forever. A faulty
or S3-compatible provider can therefore make one list call spin and append
duplicate pages until cancellation or exhaustion (`RSK-014`). No focused test
models multiple pages, token progress, or a malformed pagination response.
### Subprocess protocol and output validation
Audita, Seriatim, Scriptorium, and Notarius all delegate executable launch,
timeout, environment merging, stream capture, wait, exit metadata, and bounded
diagnostic-tail mechanics to `internal/adapters/subprocess`. Their flag order is
deterministic and their generated configuration stores credential environment
names/presence rather than values. Audita deliberately maps its configured
credential into `AUDITA_LLM_API_KEY`; Notarius and Scriptorium use the inherited
environment documented by their protocols. No current adapter places a raw
credential in arguments or generated configuration.
Notarius has the strongest external-output boundary: stdout is not parsed after
a process failure; receipt, index, warnings, and rejection payloads are size
bounded; inputs and bundle/lane outputs must be regular, non-symlinked files;
bundle roots cannot escape; and declared lane media/schema/module contracts are
matched before results are exposed. The other subprocess adapters use
unbounded `os.ReadFile` for known JSON/text results or `os.Stat` for presence
and non-empty checks. Those calls follow symlinks and do not establish a
regular-file handle before parsing. Shared bounds parsing has the same
unbounded, link-following shape. `RSK-013` records this one external-output
trust-boundary cause rather than separate findings for every adapter.
The shared launcher correctly reports executable, argument, working-directory,
timeout, exit, and log-path context, and limits the returned stderr tail to 2
KiB. It only redacts sensitive values supplied in `EnvOverrides`, while the
actual stdout/stderr files are always raw and inherited sensitive environment
values are unknown to the redactor. A downstream tool echoing either kind of
credential can therefore place it in persisted logs, and an inherited value can
also enter the returned error and both manifests. This contradicts the
repository's explicit no-secrets-in-logs/manifests invariant (`RSK-012`).
Request and result contracts otherwise match their external documents.
Scriptorium's validation-failure exit is adapted distinctly; Audita and
Seriatim validate their documented JSON shapes; Notarius preserves structured,
bounded diagnostics; and failure results retain non-secret paths and process
metadata. Audita's request object redundantly carries most static constructor
settings even though the production runner reads only `Modules` from the
request. Because production supplies equal values, this is not a current
correctness defect; `ARC-005` assigns the contract/fake-fidelity decision to the
later maintainability and test passes.
### Shared models, diagnostics, and repeated mechanics
`internal/artifactmodel` and `internal/contracts` contain stable JSON-tagged
Narratio models rather than provider objects. Artifact conversion clones slice
state at the boundary, and no lossy transport conversion was found. Bounds
parsing accepts compatible unknown fields and normalizes integer-like IDs, then
validates ordering and membership against the transcript. Its unbounded
external reads are included in `RSK-013`; no separate serialization defect was
established.
`internal/logging` is a deliberately small `slog` text-handler constructor and
owns no resource. It does not offer redaction, so callers must not submit secret
values; the concrete violation comes from subprocess error/log construction in
`RSK-012`, not from logger construction itself.
The two adapter-local atomic byte writers and the shared `fileops` writer repeat
the same same-directory temp/write/sync/chmod/rename mechanism. This is a real
mechanical duplication (`DUP-005`), and all variants inherit `RSK-002`'s missing
parent-directory sync. Protocol-specific argument builders, output schemas, and
error adaptations are meaningfully different and should remain local. Repeated
fake placeholder materialization is test support whose suite-wide value and
fidelity remain assigned to Stage 12; it is not a production abstraction
candidate from this pass.
## Prepare And Transcript-Processing Conclusions
### Prepare inputs, previous state, and deterministic recording
Prepare rechecks the resolved configuration and runtime collaborators, enforces
local-versus-S3 audio exclusivity, resolves stable inputs according to campaign/
session precedence, and copies the selected campaign, session, resolved
pipeline, stable-input, and audio bytes into canonical session paths. S3 object
sets and local directory entries are sorted; colliding basenames from different
sources are rejected; checksums and remote/session provenance are recorded; and
the final manifest input slice is sorted by kind and path. Previous-cache
planning and hydration use the Stage 5 owner, validate required artifacts, and
add deterministic manifest/artifact input records. No secret value enters the
resolved pipeline copy.
Two configuration transitions violate that otherwise deterministic handoff.
First, managed `previous/**` state is cleared only when the newly resolved
configuration has at least one previous requirement. Removing the last
requirement leaves old bytes outside `manifest.inputs`; publish later walks and
uploads the directory independently of those records. This is `COR-017`, and
the focused test currently encodes the stale-state behavior. Second, explicit
`audio_files` entries are sorted but not deduplicated. Repeating the same source
therefore lets prepare succeed with duplicate manifest records, while
transcribe's manifest-first validator rejects the duplicate path. Configuration
validation does not reject it; `COR-019` owns the inconsistent boundary.
### Transcription concurrency, cancellation, and result identity
Transcribe derives one speaker identity from each prepared FLAC basename,
rejects duplicate speakers/paths, clamps positive configured concurrency to the
job count, requests one distinct run-local JSON output per speaker, and requires
the adapter-returned path to equal that request. Successful files are validated
before any canonical copy; metadata and outputs are ordered by speaker, and any
recorded adapter/validation error cancels peers and prevents canonical
materialization. This satisfies unique identity, output-path authority,
bounded concurrency, and ordinary partial-error ordering.
Cancellation itself is not included in the completion decision. A worker that
observes the derived context before its adapter call exits silently, dispatch
stops silently on that same context, and the coordinator checks only the first
recorded adapter/validation error. A pre-canceled context can therefore return
success with no outputs, and cancellation after some completions can return and
materialize a successful subset. `COR-018` records the correctness defect.
The required race test also confirms that the WhisperX fake is invoked under a
legitimate concurrent interface contract and races while appending requests;
this closes Stage 8's behavioral check for `TST-001` without suggesting a
production HTTP-client race.
### Transformation, rendering, and output classification
Merge, polish, normalize, trim, and render prefer recorded producer outputs and
use documented canonical fallbacks. They pass run-local destinations to their
synchronous adapters, validate JSON/transcript/report/bounds or non-empty text
as appropriate, and materialize only validated results into canonical session
paths. Merge sorts raw inputs and normalizes each into run scratch before its
merge; trim validates bounds ordering, membership, and selector behavior; and
render waits for both requested render calls before canonical materialization.
Subprocess logs, generated invocation configuration, and trim render-debug are
diagnostics, while requested transcript, report, bounds, and Markdown results
are stage outputs. Their ordinary output acquisition still inherits
`RSK-013`'s unbounded, link-following validators.
Current production adapters return the requested destination, but authority is
inconsistent at the stage seam: transcribe rejects any alternate returned path,
while several transformation stages validate and materialize a result path
returned by the adapter and others treat the request path as authoritative.
No current production adapter intentionally redirects output, so this is the
contract candidate `ARC-006`, not a confirmed data defect. Polish likewise has
no need for per-invocation overrides of the static Audita values duplicated in
its request; `ARC-005` should make constructor state authoritative and retain
only truly invocation-specific request fields unless a later product contract
introduces overrides.
Disabled trim is real processing: it validates and copies normalized JSON to
the configured trimmed output and is correctly durable success. Disabled
render deliberately returns successful no-output metadata so the pipeline can
continue without Markdown; like any previously succeeded stage, later
enablement requires force. Those ordinary-stage outcomes are coherent and
resolve their part of `ARC-002`; the focused render document's generic “skips”
wording is retained as `COM-004`. Analyze's no-op outcome was deferred to Stage
10, which confirmed the same durable-success behavior under `COM-005`.
### Similarity classification
- `resolveRunStageLayout`, `runLocalPathForCanonical`, and
`materializeRunLocalOutput` already form the narrow shared owner for run-local
isolation and canonical copying; keeping adapter calls explicit is
intentional.
- Raw-transcript discovery is meaningfully plural and directory-aware. The
three singleton manifest-first transcript resolvers, however, repeat nearly
identical candidate, local-path, existence, and fallback mechanics even
though the artifact registry already owns the same policy shape; `DUP-006`
assigns a narrow resolver decision to Stage 11.
- JSON, transcript, report, bounds, and text semantic checks should remain
contract-specific. Safe bounded regular-file acquisition is the shared
mechanism already required by `RSK-013`, not another generic validator.
- Stage metadata and adapter requests expose protocol-specific facts. A generic
stage/template framework would hide important ordering and failure
differences and is rejected from this audit pass.
## Extraction Vertical-Slice Conclusions
### Configuration, execution, validation, and promotion
Enabled Notarius configuration is validated before composition and its relative
paths are anchored to the pipeline document. Application composition constructs
the runner only when extraction is selected and enabled. Extract resolves the
final-trimmed artifact through the shared registry, normalizes every invocation
path, creates run-local receipt/log/output locations, and passes one explicit
request to the adapter. The adapter separates stdout receipt from stderr,
requires successful process completion before decoding, bounds the receipt,
index, rejection, and warning documents, and rejects a non-canonical or
symlinked bundle tree. The stage then selects exactly one descriptor for every
sorted configured output, rejects matching rejections or contract differences,
and verifies regular non-empty JSON lane payloads before promotion.
Promotion copies the complete regular-file source tree through verified source
handles, syncs files and directories, and installs one unique Narratio-run-ID
destination through the platform no-replace primitive. It never replaces an
existing bundle. The stage derives promoted paths from previously confined
relative names and rechecks index and lane checksums before returning one
non-selectable index plus the exact configured lane set. Each lane carries its
checksum, configured contract, producing Narratio run ID, and Notarius system,
run, pipeline, and lane provenance. Unconfigured bundle members remain in the
immutable audit bundle but never become manifest outputs.
### Promotion, advertisement, catalog, and resume authority
The four relevant authorities are intentionally distinct:
- `fileops.PromoteDirectory` establishes an all-regular, immutable,
no-replacement durable directory; its existence alone is not success.
- The runner's succeeded session-stage record advertises the current result.
A failed replacement clears the old current payload, and an orphan promoted
directory is not rediscovered by scanning.
- Extraction resume accepts a succeeded record only after the current
invocation fingerprint, canonical producer/bundle/receipt identity, exact
source set, contracts/provenance, confinement, regular type, and checksums
agree. Missing or obsolete evidence causes a rerun; unsafe paths, symlinks,
and inspection failures stop execution without replacing the prior success.
- Catalog hydration independently fails closed and marks no extraction lane
available unless the whole configured record, index, bundle, provenance,
checksums, and JSON payload set validate. Analyze and publish consume only
those catalog entries named explicitly by artifact inputs or publish rules.
This prevents a durable bundle, diagnostic file, unconfigured lane, or stale
invocation manifest from becoming implicitly selectable. It also exposes the
maintenance tension in `DUP-007`: resume and catalog repeat one evidence policy
while deliberately mapping failures differently.
### Failure and residue classification
Configuration, input, directory, and adapter-start failures can leave only
run-local directories or diagnostics and return no result. A subprocess
failure can leave its receipt/stderr and staging tree for inspection, but none
is advertised or reusable. Receipt, index, rejection, descriptor, payload, or
pre-install promotion failures likewise retain diagnostic/staging evidence
without a current selectable result. If no-replace installation succeeds and
a later parent-directory sync, promoted-path resolution, or checksum check
fails, the complete uniquely named durable bundle can remain as orphan audit
residue; it still has no manifest advertisement and catalog hydration never
scans for it. A later successful invocation uses a new run identity.
After a successful stage result, runner manifest persistence is the
advertisement boundary. Its session/run disagreement risks remain the shared
`RSK-001`, `RSK-002`, and `TST-002` findings rather than extraction-specific
duplicates. On replacement, the running transition clears the earlier current
payload; failure retains the previous immutable bundle and prior invocation
record only for inspection. Downstream succeeded state is invalidated by force
or a changed executed outcome, while identical repeated disabled self-skip is
stable.
### Validation shape and bounded acquisition
`ValidateResume` has cyclomatic complexity 40 and cognitive complexity 53
because it combines fingerprint comparison, canonical bundle identity,
receipt/source-set proof, contract/provenance checks, and filesystem evidence.
These checks are consequential and their order is largely justified. `SIM-002`
therefore proposes only named evidence sub-decisions: cheap record and
fingerprint checks first, confinement before filesystem access, exact set and
identity proof before payload acceptance, and an explicit final all-or-none
decision. It must preserve every obsolete-versus-unsafe classification.
Notarius's bounded management-document reads do not bound configured lane
payloads. Extract's `checksumRegularFile` reads each lane fully to validate JSON
and hash it, and catalog hydration streams a checksum and then reads the same
file fully again for JSON validation. This broadens the already confirmed
`RSK-013`; safe regular-file shape is stronger here than in ordinary adapters,
but an external multi-gigabyte JSON lane can still exhaust memory.
## Analyze And Artifact-Dependency Conclusions
### Catalog state, selection, and reuse
The runtime catalog keeps registration, executability, availability, and
provenance separate. Analyze registers every built-in, configured artifact,
and configured extraction source. Extraction availability is manifest-backed
and all-or-none. Configured availability is different: entries executable in
the current invocation are initially unavailable, while every non-executable
entry with a valid non-empty canonical output is marked reusable from disk.
After one selected artifact succeeds, its canonical materialized output is
marked generated so later selected dependents can consume it in the same run.
Selection is authoritative over the enabled flag inside catalog registration,
and a catalog unit test explicitly preserves that behavior. With no selection,
enabled controls execution. With a selection, exact membership controls
execution and may activate a disabled artifact or suppress an enabled one.
The CLI validates only that selected names exist. Configuration, previous-
requirement planning, and focused documentation do not consistently share this
rule: disabled artifacts need not have an executable prompt/output contract,
previous requirements scan enabled artifacts only, and the artifact document
defines executable as both selected and enabled. `ARC-007` owns the unresolved
authority choice, while `COR-022` records the already observable prerequisite
failure.
Reuse deliberately does not require an old analyze success record: a configured
non-executable output is accepted by canonical path and non-empty-file shape.
This supports operator-prepared or preserved disabled dependencies, but it is
not freshness validation. Enabled-but-unselected files receive provenance named
for disabled outputs, so metadata cannot distinguish those two causes. This is
an observability consequence of `ARC-007`, not a separate correctness defect.
Publish registers the same configured identities but independently marks any
existing canonical configured output available, then applies selection only to
configured-source publish rules. It neither executes artifacts nor treats
selection as a filter for built-in/extraction sources.
### Source resolution and required policy
Artifact policy classifies built-in, prepared-stable, extraction, configured,
and previous-artifact families before analyze resolves them. The built-in
catalog contains base, polished, final, final-trimmed, both rendered Markdown
variants, and bounds. Built-ins prefer manifest output records and then their
canonical path; the resolver applies transcript JSON, non-empty Markdown, or
bounds JSON validation. Stable inputs map to fixed prepared files. Extraction
and configured sources require catalog availability. Previous sources use a
separate resolver that prefers matching manifest input records and falls back
to local `previous/` paths; neither path reaches object storage.
Optional absence is consistently omitted for prepared stable, extraction,
configured, previous, polished-transcript, and bounds sources. Final,
final-trimmed, and both Markdown cases instead return guidance errors inside
the source-specific branch before the caller can apply `required=false`; this
is `COR-021`. Required prepared and extraction errors identify the producing
stage/configuration. Configured absence is clear but has no repair command.
Required previous absence emits a syntactically invalid and sessionless command
(`COR-023`). The accepted `artifact` and `path` input fields are not consulted
by policy, resolution, execution, either adapter request, generated invocation
configuration, or previous planning; `COR-024` owns that silent contract.
### Dependency order, execution, and lifecycle
Configuration validates dependency identities, self-reference, configured-
source/`depends_on` agreement, referenced output paths, and cycles among enabled
artifacts. Analyze validates the executable subgraph again. A dependency
outside that subgraph must already be catalog-available; a dependency inside it
becomes a directed edge. Edges and ready nodes are sorted, so successful order
is deterministic and dependencies precede dependents. The initial unavailable-
dependency pass ranges over a map, however, so several invalid selected nodes
can produce different first errors (`RSK-015`). Re-sorting the ready slice on
every insertion is more work than necessary, but configured artifact counts are
small and no material efficiency defect was established.
For each plan, input names are sorted, optional omissions and reused sources are
recorded, variables are normalized with Narratio's sticky session identifier,
and optional render-debug completes before the run request. The result must be
non-empty, then run-local bytes are materialized to the canonical artifact path.
Logs/generated configs are deduplicated and sorted, execution metadata follows
plan order, and reused metadata is stable. Output checks still inherit the
link-following, non-regular, and unbounded acquisition risk in `RSK-013`.
Failure after an earlier artifact materializes can leave that canonical output,
but no stage result is advertised; an ordinary retry executes the same selected
set again rather than implicitly resuming mid-graph.
Missing Scriptorium configuration, an empty artifact map, or no executable
entries returns zero-disposition metadata and is recorded as success by the
runner. That permits full-pipeline continuation and satisfies publish's analyze
prerequisite, but the success is reused after later configuration changes until
forced. The dedicated `analyze` command force-runs, mitigating the explicit
operator workflow. This settles analyze's state behavior for `ARC-002`; the
focused document's generic “skips” wording and omitted extraction family remain
`COM-005`.
### Representation and ownership
`analyzeStage.Run` coordinates catalog, planning, transcript-reference
diagnostics, execution, catalog mutation, and aggregation. Its artifact helper
is 239 lines with ten parameters; the source resolver is 84 lines with six
parameters and two unused contextual parameters; the dependency orderer is 86
lines with ten loops. These metrics support only the narrow `SIM-003`: a typed
execution context/plan and source-resolution result could make state ownership
visible without introducing a generic stage framework or collapsing distinct
source guidance. `resolveInputPathForRead` has no production or test caller and
belongs in that review.
Analyze, publish, and app operator helpers also repeat built-in/configured/
extraction catalog registration and extraction hydration. Their final
availability policies differ intentionally, but the shared registration
mechanics and source-definition construction are one policy repeated in three
places; `DUP-008` assigns a narrow common bootstrap decision to Stage 11.
## Maintainability And Structural Conclusions
Stage 11 re-ran production-only similarity, complexity, fan, loop-depth, and
change-coupling queries, then traced the resulting owners and callers. The
graph reported no production function with a direct scan-in-loop or
allocation-in-loop flag. High transitive loop depths came from composition and
tests rather than a new credible hot path. Change coupling was dominated by an
implementation and its focused tests, with expected cohesive changes among the
runner, stage registry, configuration, and command wrappers.
The resulting design rule is to share mechanics and typed evidence, not whole
workflows:
- `fileops` should own atomic replacement and durable installation mechanics;
manifest serialization, remote acquisition, validation, conflicts, and
reporting remain with their current policy owners;
- `artifacts` should own canonical artifact resolution, catalog bootstrap, and
extraction-bundle evidence; stages retain required/optional, lifecycle, and
publication decisions;
- the runner should expose one narrow terminal-failure transition while keeping
running-versus-terminal ledger order visible; and
- analyze should use a typed execution context and indexed effective plan, but
its five source-policy branches should remain explicit.
No generic stage, workflow, validator, resolver, or graph framework is
justified. `previouscache.BuildPlan` is long because it visibly separates
required and optional absence, remote-current validation, candidate selection,
and deterministic ordering; extracting those branches without a new owner
would only move complexity. The repeated command wrappers, typed manifest
load/create methods, adapter constructors, and protocol fakes likewise share
shape but not policy.
The dependency inventory has six direct module dependencies. AWS configuration,
credentials, S3, and Smithy are used by the S3 adapter; YAML is used by strict
configuration and generated adapter files; and `x/sys` supplies native
no-replace directory installation. No dependency can be replaced by the
standard library without losing a current protocol or platform guarantee.
Linux, macOS, and Windows have explicit file-operation implementations;
unsupported systems fail the no-replace capability clearly. The remaining
portability risks are already owned by `RSK-002`, `RSK-003`, and `RSK-011`, not
new dependency findings.
No benchmarks exist in the repository. That is not a general test defect: the
two confirmed efficiency findings below now state representative workloads and
specific byte-count, latency, allocation, and peak-memory measurements. No
other sorting, copying, map/slice allocation, serialization, adapter
construction, remote-call, filesystem-pass, or goroutine/channel pattern had a
credible workload large enough to justify a performance finding.
## Audit Completion And Final Disposition
The implementation, tests, and canonical current-behavior documentation remain
identical to the pinned audited revision. `git diff` from
`74e2d21de5fb2ada0be5ef3fe9333e0d48ac7fb3` through the Stage 12 entry revision
contains only this ledger and its two audit specifications; there is no Go,
module, example, policy, CLI, configuration, operations, integration, or
internal-document change. Stage 13 re-ran graph ownership searches across the
runner, canonical paths, file operations, publish/current state, restore,
configuration, adapters, ordinary stages, extraction, analyze, and their
focused tests. Every detailed finding below still resolves to its recorded
implementation owner and consumer boundary. No item rests on a metric alone,
and no confirmed item was downgraded or rejected during revalidation.
### Completion criteria
| Criterion | Final evidence and conclusion |
| --- | --- |
| Every inspection area reviewed | All 15 area-ledger rows are `reviewed`; Stages 2-12 record contracts, production owners, focused tests, commands, findings, and explicit no-finding conclusions. |
| Lifecycle and scenarios concluded | Every lifecycle outcome is source-backed for both manifests. The ten-scenario matrix above now records a final conclusion and root finding or accepted boundary for each row. |
| Duplication classified | `DUP-001` through `DUP-003` and `DUP-005` through `DUP-008` are narrow shared mechanisms/policies; `DUP-004` is rejected because its accepted language differs. Broad stage, workflow, resolver, validator, adapter, fake, and manifest abstractions are explicitly rejected. |
| Simplification and efficiency bounded | `SIM-001`, `SIM-003`, and `SIM-004` name smaller owners without hiding policy; `SIM-002` merges into `DUP-007`. `EFF-001` and `EFF-002` name representative byte/latency/memory measurements; no other performance claim survived workload review. |
| Comments preserve rationale | `COM-001` owns ledger ordering rationale; `COM-002` owns durable no-output terminology; `COM-003`, `COM-005`, and `COM-006` repair verified stale/incomplete claims. `COM-004` merges into `COM-002`. |
| Test suite assessed by risk | The final matrix covers integrity, destructive actions, compatibility, security, concurrency, idempotency, recovery, cancellation, partial success, redundancy, doubles, helpers, coverage, fuzzing, determinism, offline behavior, runtime, and automation. |
| Findings deduplicated and ranked | 77 IDs remain confirmed. Four stable IDs are non-independent dispositions: `ARC-002` and `COM-004` merge into `COM-002`, `SIM-002` merges into `DUP-007`, and `DUP-004` is rejected. The backlog below is authoritative for dependency/risk order. |
| Questions, accepted risks, and limitations explicit | The final decision table after the registers assigns an owner and safe interim boundary to every unresolved contract family, followed by intentionally accepted risks and evidence limitations. |
### Positive conclusions
- The canonical stage registry and planner are deterministic, and force,
changed outcomes, self-skip, ordinary success, failure, and retry invalidate
or preserve downstream state conservatively.
- Session state is consistently the cross-invocation authority; individual
atomic saves preserve the previous file on ordinary pre-rename failure, and
terminal session-first ordering preserves safe reuse even when the audit
ledger becomes inaccurate.
- Directory promotion strongly validates and identity-checks its source,
rejects replacement of an existing immutable destination, syncs its durable
tree, and fails explicitly on unsupported platforms.
- Publish uses deterministic upload ordering and writes the current pointer
last. Manual cleanup is explicit, scoped, dry-runnable, cache-preserving by
default, and correctly independent of automatic postcommit policy.
- Restore planning is deterministic, confines lexical remote mappings, keeps
dry-run free of durable local writes, validates downloads before install, and
installs the session manifest last. Analyze consumes previous-session state
locally and never performs an implicit remote read.
- Configuration rejects unknown fields, centralizes defaults, validates
maintained examples, keeps ordinary secrets indirect, and conditionally
composes external adapters rather than contacting live services at startup.
- Adapter protocol suites protect command arguments, schemas, retries,
cancellation at direct boundaries, error adaptation, and deterministic
outputs. Stages generally validate run-local results before canonical
materialization and record artifacts separately from diagnostics.
- Production dependencies are active and confined to their boundary. The audit
found no generic workflow-engine need, broad dependency-direction inversion,
additional material hot path, or reason to chase uniform coverage.
- The normal suite passes in about 3.5 seconds without live services, paid APIs,
ambient credentials, or fixed external ports. Most tests use real temporary
files, loopback HTTP, or the current test binary and assert observable state
rather than private choreography.
### Final prioritization dimensions
The 42 detailed `COR`, `RSK`, `EFF`, and confirmed `ARC-004` entries already
record impact, likelihood, confidence, scope, owner, tests, and dependencies
separately; Stage 13 revalidation did not change those ratings. The following
compact register supplies the same dimensions for the confirmed structural,
test, and clarity entries. “Inherited” impact means the item is required to
make its named correctness/security root durable rather than representing a
second production defect.
| IDs | Impact | Likelihood | Confidence | Scope |
| --- | --- | --- | --- | --- |
| `ARC-001` | Low current; medium future contract misuse | Low until a consumer appears | High | Small removal |
| `ARC-003` | Medium remote/local state ambiguity | Low-to-moderate as consumers grow | High | Small-to-medium model/protocol clarification |
| `ARC-005` | Medium adapter/test contract drift | Moderate during adapter change | High | Small request/constructor change |
| `ARC-006` | High run-local output-authority risk | Low currently; moderate with a divergent adapter | High | Medium shared contract and caller repair |
| `ARC-007` | High effective-selection correctness | Moderate for explicit selection | High | Medium app/config/artifact planning change |
| `DUP-001`, `DUP-003`, `DUP-005` | High durability/security drift inherited from `RSK-002`/`COR-003` | Moderate during shared repair | High | Medium shared fileops capability and caller migration |
| `DUP-002` | Medium run-identity drift | Low-to-moderate | High | Tiny path-owner correction |
| `DUP-006` | Medium transcript-source drift | Moderate during source changes | High | Small-to-medium artifact resolver migration |
| `DUP-007` | High extraction evidence drift | Moderate during compatibility/security changes | High | Medium typed evidence owner |
| `DUP-008` | High analyze/publish/operator catalog drift | Moderate as source families change | High | Medium shared bootstrap with explicit caller policy |
| `SIM-001` | Medium audit/persistence correctness support | Moderate on new error paths | High | Small-to-medium runner extraction |
| `SIM-003` | Medium maintainability and ordering support | Moderate during analyze changes | High | Medium typed plan/context refactor |
| `SIM-004` | Low dead-code cost | Certain but harmless | High | Tiny deletion |
| `COM-001` | Medium risk of weakening ledger order | Moderate during runner repair | High | Tiny comment after `SIM-001` |
| `COM-002`, `COM-005` | Medium operator/developer lifecycle misunderstanding | Present in current docs | High | Small documentation correction |
| `COM-003`, `COM-006` | Low stale-contract friction | Present in current comments | High | Tiny comment deletion/update |
| `TST-001`, `TST-011` | Medium loss of diagnostic determinism | Certain under race/repetition runs | High, reproduced | Small fake/environment cleanup |
| `TST-002` through `TST-010`, `TST-013` | Inherited critical/high confidence for named integrity, recovery, security, and concurrency roots | Same plausible scenarios as linked findings | High after cross-layer inventory | Medium distributed regression additions, each at its named owner |
| `TST-012` | High release-validation exposure | Ongoing on every unvalidated change/tag | High | Small-to-medium automation addition |
| `TST-014`, `TST-015` | Low-to-medium recurring maintenance friction | High during legitimate config/stage changes | High | Medium test consolidation |
### Dependency-ordered remediation backlog
This order is authoritative over the category-number order used by the detailed
registers. Regression tests named by a workstream should land with that repair;
standalone test-suite and documentation cleanup follows the owning behavior.
1. Establish safe identity, filesystem, secret, and durable-write foundations:
`COR-002`, `COR-003`, `COR-005`, `RSK-002`, `RSK-004`, `RSK-010`,
`RSK-012`, and `RSK-013`, together with `DUP-001`, `DUP-003`, `DUP-005`,
`TST-003`, the relevant `TST-007` cases, and `TST-013`. This capability must
exist before caller-specific restore, cleanup, adapter, or promotion fixes.
2. Make manifest identity and terminal persistence singular: `COR-001`,
`RSK-001`, `SIM-001`, `COM-001`, `TST-002`, and `TST-006`. Preserve session
authority and visible running-versus-terminal save order; do not introduce a
generic lifecycle framework or promise cross-file atomicity.
3. Repair remote publication and postcommit cleanup: `COR-004`, `COR-006`,
`COR-007`, `RSK-005`, `ARC-003`, `DUP-002`, and `TST-004`. Use one immutable
commit model and stateful store tests; retain pointer-last semantics and make
cleanup evidence durable/retryable.
4. Bind restore and previous state to that commit model: `COR-008` through
`COR-011`, `RSK-006` through `RSK-009`, `TST-005`, and then `EFF-001`.
Reuse the safe filesystem capability and current-state truth table rather
than creating restore-only variants.
5. Make configuration and composition truthful before execution: `COR-012`
through `COR-016`, `COR-024`, and `ARC-004`, with `TST-011` and `TST-014`.
Resolve the storage, notification, previous-session, and Scriptorium contract
decisions explicitly; reject unsupported values rather than guessing.
6. Complete transport liveness and bounded resource behavior: `RSK-011`,
`RSK-014`, `EFF-002`, `TST-001`, and the remaining `TST-007` cases. Reuse
the step-1 output/redaction owner and keep platform process mechanics in the
shared launcher.
7. Correct ordinary-stage and extraction transitions: `COR-017` through
`COR-020`, `ARC-006`, `DUP-006`, `DUP-007`, `TST-008`, and `TST-009`.
Preserve explicit stage policy while sharing only artifact resolution and
typed extraction evidence.
8. Unify effective analyze selection and dependency behavior: `COR-021`
through `COR-023`, `RSK-015`, `ARC-007`, `DUP-008`, `SIM-003`, and
`TST-010`. Coordinate `COR-024` from step 5 rather than inventing wire
semantics in this refactor.
9. Perform remaining architectural and structural cleanup: `ARC-001`,
`ARC-005`, `SIM-004`, and any now-obsolete wrapper code. These are
independently small but should not distract from data-safety work.
10. Enforce and streamline the suite: `TST-012`, then `TST-014` and `TST-015`.
Keep focused owners and representative assembled workflows; add the race
job only after `TST-001` makes its signal trustworthy.
11. Apply documentation/comment repairs after their contracts settle:
`COM-002`, `COM-003`, `COM-005`, and `COM-006`. Update canonical current-
behavior documents in the same changes that implement the decisions.
`EFF-001` and `EFF-002` deliberately follow their correctness owners and require
the measurements stated in their detailed entries. No other performance work
should be added to this backlog without a representative workload.
## 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; Stage 6 found only safe segment-style IDs in
maintained examples and documentation, but deployed configuration is unknown.
- 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 confirmed that no configuration validator closes this
boundary. `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.
### `COR-004`: precommit current-manifest replacement invalidates the prior readable commit
- Category: confirmed correctness/recovery defect.
- Locations/invariant: `internal/stage/publish.go` upload order and
`internal/artifacts/current_state.go` in `LoadCurrentState`. The pointer must
be the sole commit point, and work before it must not destroy the previously
committed state.
- Evidence: publish unconditionally replaces the fixed
`current/manifest.json`, then uploads `current/run_id.txt`. A replacement
publish that uploads the new manifest but has not yet written, or fails to
write, the pointer therefore leaves old pointer/new manifest disagreement.
The shared loader can reject that pair, but only when the caller enables run
validation; restore/status do not (`COR-008`). Existing publish tests prove no
pointer call follows selected failures but do not seed and preserve a prior
current pair.
- Realistic scenario: run A is current. Publishing run B reaches the current
manifest, then the pointer upload fails or a reader runs during the gap. Run
A's coherent pair is gone and run B is not committed. Strict previous-cache
readers report unavailable state, while restore/status can incorrectly accept
run B's manifest under run A's pointer. An upload error after server
acceptance also makes local outcome ambiguous.
- Impact/likelihood/confidence: high recovery availability and integrity impact;
upload failure likelihood is low per call but the disagreement window occurs
on every replacement publish; high confidence from fixed keys, unconditional
upload, and identity validation.
- Estimated remediation scope and owner: medium publish/artifacts protocol
change. Publish an immutable run-specific manifest first and make the final
commit object select that immutable state, or use a versioned/conditional
current representation that preserves the previous pair. Do not move commit
policy into the generic storage adapter.
- Test changes: use a stateful store seeded with run A, fail or pause every run
B boundary, and assert run A remains readable until a successful final commit;
add the first-publish and indeterminate-pointer-response cases. Existing
pointer-last tests remain useful but are insufficient alone.
- Dependencies: `COR-001` can misplace the immutable run prefix and must be
fixed consistently. Stage 5 should consume the established current-state
truth table rather than duplicate this publish root cause.
### `COR-005`: publish follows symlinked archive entries and can upload files outside its roots
- Category: confirmed correctness/security defect.
- Locations/invariant: `internal/stage/publish.go` in
`collectPublishRunFiles` and `collectPublishPreviousFiles`, plus
`internal/adapters/storage` upload implementations. Run and previous archives
must be confined to the enumerated local trees and contain eligible regular
files only.
- Evidence: both `WalkDir` collectors append every non-directory entry without
rejecting symlinks or other special files. Storage then opens the recorded
path (`os.Open` in the S3 backend and file reads in the fake), which follows a
symlink final component. The run-manifest check also uses `os.Stat` and accepts
any non-directory. Existing exclusion tests cover path names, not entry type.
- Realistic scenario: an adapter, restored tree, local user, or compromised
subprocess leaves `runs/<id>/logs/debug.log` as a symlink to a credential or
unrelated private file. Publish uploads its contents under the apparently
harmless run key. A symlink in durable previous cache has the same effect.
- Impact/likelihood/confidence: critical confidentiality impact; low-to-moderate
likelihood depending on workspace ownership and subprocess trust; high
confidence from standard open semantics and the missing type checks.
- Estimated remediation scope and owner: small-to-medium publish/file-boundary
change. Enumerate and open regular files without following links, keep the
opened object tied to the verified entry where platform support permits, and
reject unsafe roots/ancestors consistently. Storage should continue accepting
explicit paths rather than infer archive policy.
- Test changes: create run, previous, and manifest symlinks to an outside
sentinel and prove publish fails before uploading sentinel contents; include
an entry-replacement race case if the implementation adopts path-based
`Lstat` only. Existing exact exclusion/order tests should remain.
- Dependencies: `COR-003` owns symlinked ancestors for mutation and cleanup;
this finding is distinct because it is a publish read/exfiltration boundary.
`RSK-004` affects who can create the malicious entry but is not required for
exploitation by a trusted subprocess gone wrong.
### `COR-006`: automatic cleanup is one-shot and is not retried after postcommit failure
- Category: confirmed correctness/operational defect.
- Locations/invariant: `internal/app/runner.go` around terminal publish saves
and `runPostPublishCleanup`, and `post_publish_cleanup.go` in
`publishStageRecordForCleanup`. An enabled cleanup policy should remain
recoverable after a committed publish until its requested cleanup succeeds.
- Evidence: cleanup requires `publish` to appear in the current invocation's
`executed` list. Once the terminal session publish save succeeds, later
ordinary invocations treat publish as already succeeded and omit it from that
list. A terminal run-manifest save failure before cleanup, a cleanup deletion
failure, or cleanup metadata-save failure can therefore leave requested data
present while every normal retry silently bypasses cleanup. A forced publish
or manual `clean` is the only retry path.
- Realistic scenario: remote commit succeeds and the session publish record is
saved, but saving the run record fails, so cleanup is never entered. The
operator fixes the filesystem and reruns normally; publish skips and the
sensitive spool remains despite `delete_audio_after_publish=true`. The same
occurs when the first removal attempt itself fails.
- Impact/likelihood/confidence: medium confidentiality/storage and operator-
expectation impact; low-to-moderate lifetime likelihood around filesystem
failures; high confidence from the execution-list gate and session reuse
policy.
- Estimated remediation scope and owner: medium app/manifest change. Persist a
cleanup obligation/state independently of “publish executed this invocation”
and retry it idempotently whenever the committed local record proves
eligibility. Keep explicit policy and confinement checks on every attempt.
- Test changes: inject a failure immediately after publish session success,
fail each requested deletion/save once, then run normally and assert cleanup
retries without republishing or deleting an uncommitted target. Existing
commit-gate tests remain useful.
- Dependencies: `TST-002` notes that run-save boundaries are not injectable;
`COR-001` and `COR-003` still govern target identity/confinement. `COR-007`
separately owns missing success evidence after work deletion.
### `COR-007`: successful workspace cleanup metadata is never persisted
- Category: confirmed correctness/diagnosability defect.
- Locations/invariant: `internal/app/post_publish_cleanup.go` in the
`workRequested` success path. Cleanup outcomes promised as manifest metadata
must survive the invocation that performed the destructive action.
- Evidence: spool-only cleanup sets completion metadata and saves the session
manifest. When workspace cleanup is requested, the function removes the work
directory, mutates `workdir_cleanup_deleted`, `cleanup_completed`, and
`cleanup_skipped` only in memory, then returns without saving the session.
The subsequent final run save copies identity fields, not the mutated session
stage record. If both policies are enabled, the spool deletion metadata is
lost with the same unsaved map. Existing cleanup tests assert paths only.
- Realistic scenario: automatic cleanup successfully removes both run work and
spool audio. The durable session and run records retain the pre-cleanup
publish metadata, so status, incident review, or a future cleanup retry cannot
distinguish completed cleanup from a path that was never considered.
- Impact/likelihood/confidence: medium audit/recovery impact; occurs on every
successful workspace cleanup; high confidence from the missing save and run
record copy behavior.
- Estimated remediation scope and owner: small app/manifest change. Persist one
authoritative cleanup transition after requested deletions, with ordering
that remains meaningful if saving fails after deletion. Coordinate that
state with the retry obligation in `COR-006`.
- Test changes: after workspace-only and combined cleanup, reload both durable
ledgers and assert the chosen authoritative completion fields and deleted
paths. Add a post-delete save-failure case to define retry/reporting behavior;
no existing path-preservation test should be removed.
- Dependencies: fixing `COR-006` and this finding together avoids inventing two
competing cleanup state machines. `RSK-002` remains the crash-durability
guarantee of the eventual manifest save.
### `COR-008`: restore is not bound to the pointer-selected committed remote run
- Category: confirmed correctness/recovery defect.
- Locations/invariant: `internal/app/restore_discovery.go`,
`restore_plan.go`, and `restore_execute.go`; shared validation in
`internal/artifacts/current_state.go`; previous object selection in
`internal/previouscache`. Restore and status must interpret only the run
committed by the current pointer, and one restore must install one coherent
remote snapshot.
- Evidence: restore/status discovery passes campaign/session expectations but
omits `ValidateRunID`, so old-pointer/new-manifest disagreement is accepted.
The planner then lists every `transcripts/**` and `artifacts/**` object under
the mutable session prefix instead of limiting actions to manifest-declared
published/locked state. Execution downloads those keys and the fixed manifest
again; manifest validation checks campaign/session but not the discovered run
ID or bytes. Previous planning enables pointer/run validation but selects and
later downloads mutable published keys without generation binding. The unit
mismatch test exercises the helper only with `ValidateRunID=true`.
- Realistic scenario: run A is current. A failed run B publish replaces the
fixed manifest and uploads one artifact but never advances the pointer.
Restore reports run A, accepts manifest B, includes B's partial artifact plus
any stale prefix objects, and installs them locally. A concurrent successful
publish can similarly change fixed objects between discovery, planning, and
execution, producing a mixed A/B restore.
- Impact/likelihood/confidence: critical recovery integrity impact; the
disagreement window occurs on every replacement publish and partial objects
are retained by design, while concurrent/failed publish likelihood is
low-to-moderate; high confidence from validation flags, list scope, and
repeated unconditional downloads.
- Estimated remediation scope and owner: medium-to-large publish/restore/
artifacts protocol change. Pair `COR-004`'s immutable run-specific commit
representation with restore planning from explicit manifest source-to-object
records, bind object versions/digests through execution, validate run identity
everywhere, and preserve an explicit compatibility policy for old manifests.
Storage should expose only the narrow conditional/version metadata the
protocol requires, not infer current-state policy.
- Test changes: seed pointer A with manifest/object state A plus uncommitted and
stale B keys; assert restore/status reject mismatch and restore only declared
A objects. Use barriers/versioned fake objects to change pointer, manifest,
ordinary output, and previous output at every discovery/execute boundary and
prove the result is all one run or fails before manifest installation.
- Dependencies: `COR-004` is the publish-side fixed-pair root; both should share
one protocol repair. `COR-011` separately owns the missing source/destination
identity needed to construct an explicit restore set. Stage 7 owns transport-
level version/checksum feasibility.
### `COR-009`: forced restore ignores unresolved directory conflicts and can report success
- Category: confirmed correctness defect.
- Locations/invariant: `internal/app/restore_plan.go` in
`classifyRestoreAction`, `restore.go` conflict gating, and
`restore_execute.go` download filtering. Force may authorize file replacement,
but it must not turn an unexecutable conflict into silent success.
- Evidence: a local directory where a remote file is expected is always
classified `conflict`, even with force. The command blocks conflicts only
when `!force`; execution processes only `download` actions and silently
ignores the remaining conflict. It can then install the current manifest,
mark the report succeeded, and leave the directory in place. Existing force
tests cover differing regular files only.
- Realistic scenario: `artifacts/session_recap.md` is accidentally a directory.
The operator reviews the conflict and reruns with `--force`. Restore exits
successfully, its report still contains a conflict action, and the new
manifest is installed although the required artifact was never restored.
- Impact/likelihood/confidence: high local integrity/operator-trust impact;
low-to-moderate likelihood from damaged or manually edited workspaces; high
confidence from the action and command branches.
- Estimated remediation scope and owner: small application-policy change.
Require zero conflicts before execution regardless of force, or explicitly
define and safely implement directory replacement as a separate destructive
action. Successful reports must be impossible while any conflict remains.
- Test changes: cover a directory at ordinary, previous-cache, and manifest
targets with force; assert failure, old manifest preservation, and a failed
report. One table-driven application test can own all target categories.
- Dependencies: target removal would require `COR-003`'s confined deletion
capability. Do not implement ad hoc `RemoveAll` in restore.
### `COR-010`: status and validation do not verify previous-artifact readiness
- Category: confirmed correctness/operator defect.
- Locations/invariant: `internal/app/operator_inspection.go` in
`inspectPreviousArtifactReadiness`, `operator_status.go`, and
`operator_session_validate.go`; canonical planning in
`internal/previouscache.BuildPlan`. Operator readiness must match the
required/optional and object-resolution policy that prepare/restore will use.
- Evidence: inspection checks only that `previous_session_id` is nonempty and
its remote current pair validates. It reports every requirement ready without
resolving candidate keys or calling `Exists`. It also marks a missing ID as
unavailable/error whenever any previous requirement exists, although
`BuildPlan` correctly skips that state when all are optional. Focused operator
tests cover a missing pointer for a required fixture, not missing artifact
objects or optional requirements.
- Realistic scenario: a previous session has a valid committed manifest but the
required published recap was never uploaded or was removed. `session status`
says ready and `session validate` succeeds; restore/prepare then fails. In the
opposite case, an optional previous recap with no previous ID makes validation
fail even though pipeline execution would intentionally omit it.
- Impact/likelihood/confidence: medium operator and automation correctness
impact; moderate likelihood as optional/missing published outputs are normal
modeled states; high confidence from the inspection shortcut and canonical
plan branches.
- Estimated remediation scope and owner: small-to-medium application/
previouscache change. Share one read-only requirement-resolution result from
`BuildPlan` (or a narrower readiness API), then let status remain non-fatal and
validation choose finding severity without duplicating required/optional
semantics.
- Test changes: add required and optional matrices for missing ID, missing
pointer/manifest, missing candidate object, custom destination, and ready
state. Own resolution cases in `previouscache`; sample only status rendering
and validation exit classification in app tests.
- Dependencies: `COR-011` must be fixed for readiness to recognize every valid
custom destination. `COR-008` owns remote version binding, not caller severity.
### `COR-011`: previous-cache planning loses source identity for custom publish destinations
- Category: confirmed correctness defect.
- Locations/invariant: `internal/previouscache/previouscache.go` in
`artifactRelativePathCandidates`, `manifestPublishedPaths`, and candidate
selection; publish metadata in `internal/stage/publish.go`. A configured
previous-artifact source must resolve to the exact remote object that publish
committed for that source.
- Evidence: the remote manifest records only an ordered `published_paths` list,
not ordinary source-to-destination pairs. Planning starts with the analyze
output's local relative path, adds published destinations only when their
basename matches, and finally tries the current artifact output path. A custom
destination with a different basename is therefore invisible. Multiple
destinations with the same basename are candidates for the wrong source, and
the first existing key wins. Tests use identical output/published paths.
- Realistic scenario: `session_recap` is produced at
`artifacts/session_recap.md` and intentionally published as
`history/recap-v2.txt`. A later required previous-session recap reports
unavailable although the committed object exists. With two artifacts both
published as different directories' `summary.md`, one requirement can hydrate
the other's content under the expected local cache path.
- Impact/likelihood/confidence: high cross-session artifact integrity impact;
moderate likelihood because custom publish destinations are a supported
configuration feature; high confidence from candidate construction.
- Estimated remediation scope and owner: medium publish manifest/previouscache
contract change. Persist a deterministic source/destination mapping for
uploaded and intentionally locked preserved outputs, consume it by exact
source ID, and define backward-compatible fallback behavior without basename
guessing when identity is ambiguous.
- Test changes: cover changed basename, duplicate basename, locked preserved
output, absent mapping in an old manifest, and exact source mapping. Existing
default-path/fallback tests remain as compatibility cases.
- Dependencies: this mapping is also required for the committed restore scope
in `COR-008`. Stage 6 confirmed that custom destinations are supported and
validated, so remediation must preserve them; Stage 10 later confirmed that
artifact-consumer semantics do not repair remote identity selection.
### `COR-012`: strict YAML loading silently ignores a valid trailing document
- Category: confirmed correctness defect.
- Locations/invariant: `internal/config/load.go` in
`decodeStrictYAMLFromReader`, shared by pipeline, campaign, and session
loaders. A strict configuration file must contain exactly one known-field
document; content after it must not be silently discarded.
- Evidence: after decoding the requested value, the loader decodes once into
`extra` and returns an error only when that second decode returns a non-EOF
error. A valid second document returns nil, so it is accepted and ignored.
Existing strict-decode tests cover unknown fields but no multi-document input.
- Realistic scenario: an operator or generated deployment file appends an
override document with a different storage bucket, timeout, or stage setting.
Validation succeeds against only the first document and the command runs with
behavior different from the complete visible file.
- Impact/likelihood/confidence: high operator-integrity impact; low-to-moderate
likelihood because multi-document YAML is common in generated configuration;
high confidence from the decoder control flow.
- Estimated remediation scope and owner: small config-loader correction. Require
the second decode to return EOF and reject any decoded value, including null,
with a contextual trailing-document error.
- Test changes: table-test pipeline, campaign, byte-session, and rendered
template paths with a valid second mapping, null/empty separators, malformed
trailing YAML, unknown fields, and ordinary single-document EOF.
- Dependencies: none. Apply before reasoning about future configuration
migration formats so compatibility is explicit rather than accidental.
### `COR-013`: configuration accepts non-positive durations that runtime consumers reject
- Category: confirmed correctness defect.
- Locations/invariant: `internal/config/validate.go` in `validateDuration` and
its WhisperX, Seriatim, Audita, Scriptorium, trim/render-bounds, and related
callers; corresponding adapter and stage duration parsing. A configuration
accepted by validation should satisfy constructor/stage preconditions.
- Evidence: the shared validator trims and parses duration syntax but imposes no
sign constraint. Zero or negative request timeouts therefore pass config
validation, while WhisperX, Seriatim, Audita, and Scriptorium execution reject
non-positive timeouts. A negative WhisperX retry delay similarly passes config
validation and is rejected by the HTTP client constructor. Notarius uses a
separate positive check and does not have this defect.
- Realistic scenario: `narratio session validate` or plan reports a deployment
valid with `whisperx.timeout: 0s`; the subsequent run fails before useful work
when composition rejects the same value. Scriptorium artifact timeouts can
fail later only when their stage is selected.
- Impact/likelihood/confidence: medium correctness/operability impact; moderate
typo or generated-config likelihood; high confidence from paired validation
and consumer checks.
- Estimated remediation scope and owner: small config-policy correction. Give
timeout and delay fields explicit sign rules, share helpers only where their
contracts truly match, and keep empty artifact timeout as documented fallback.
- Test changes: table-test zero and negative values for every consumed duration,
positive/subsecond values, empty optional artifact timeout, and a focused
assertion that validated config reaches constructors without sign errors.
- Dependencies: Stage 7 may refine adapter error contracts, but config should
reject values that no current consumer accepts independently of that work.
### `COR-014`: the storage backend selector is neither validated nor authoritative
- Category: confirmed correctness defect.
- Locations/invariant: `internal/config/validate.go` in `validateStorage` and
cross-config S3 checks; `internal/adapters/storage/factory.go` in
`NewObjectStoreFromConfig`. An operator-selected backend must be recognized
and must determine the adapter that is constructed.
- Evidence: storage validation checks S3 path, endpoint, and credential-name
fields but never checks `storage.backend`. The factory selects S3 for backend
`s3`, but also selects it for every other spelling whenever the defaulted S3
block has a bucket. An unknown backend with a bucket silently becomes S3; an
unknown backend without one can pass config validation and fail later only
when a command needs remote storage.
- Realistic scenario: `backend: s33` with a production bucket validates and
uploads to S3 despite the operator's invalid selector. The same typo in a
locally exercised config may stay latent until remote session discovery,
publish, restore, or status constructs the store.
- Impact/likelihood/confidence: high boundary-selection impact; moderate typo or
migration likelihood; high confidence from validator and factory branches.
- Estimated remediation scope and owner: small config/factory correction. Define
the supported backend enum and disabled/local meaning, validate it before
cross-field checks, and make construction switch only on that normalized
value. Do not infer backend from populated provider fields.
- Test changes: cover exact/case policy for supported values, unknown and empty
selectors with and without bucket, commands that need/do not need storage,
and factory non-invocation after invalid config.
- Dependencies: Stage 7 owns provider adapter behavior, not the operator-facing
selection policy established here.
### `COR-015`: the previous-session expectation flag is ignored when the session omits the field
- Category: confirmed correctness defect.
- Locations/invariant: command flag construction and `internal/app/config_loader.go`
session options; `internal/config/load.go` in `LoadSessionBytesWithOptions`.
An option described and typed as an expected previous-session identifier must
either establish that identity or reject a session that does not contain it.
- Evidence: the loader rejects a mismatch only when both the expected option and
decoded `previous_session_id` are non-empty. If the session omits the optional
field, a supplied `--previous-session-id` neither fills it nor fails loading,
so planning and execution behave exactly as if the flag were absent. Tests
cover a non-empty mismatch but not omission.
- Realistic scenario: an operator validates or runs session B while pinning
previous session A at the command line. A session file that accidentally
drops `previous_session_id` passes the expectation and runs without the
intended previous-artifact context.
- Impact/likelihood/confidence: medium cross-session behavior impact; moderate
likelihood during generated/session-template edits; high confidence from the
option contract and conditional comparison.
- Estimated remediation scope and owner: small CLI/config-selection decision.
Treat the flag as a strict expectation and reject omission, or explicitly
define it as an override and populate before resolution; keep the chosen
semantics consistent across local and remote session paths and documentation.
- Test changes: cover absent/equal/different file values for local, remote, and
rendered sessions, plus no-flag optional behavior.
- Dependencies: `COR-011` owns remote artifact source mapping after an identity
is selected; this finding owns selection of that identity.
### `COR-016`: the WhisperX HTTP client accepts schemes its transport cannot execute
- Category: confirmed correctness defect.
- Locations/invariant: `internal/adapters/whisperx/http.go` in `NewHTTPClient`
and the WhisperX integration contract. A successfully constructed HTTP
adapter should accept only endpoint schemes its transport supports.
- Evidence: construction requires only a parsed URL with a non-empty scheme and
host. Values such as `ftp://example.com/transcribe` pass, while the standard
HTTP client later rejects the request as an unsupported protocol scheme.
Constructor tests cover missing and syntactically malformed values but no
non-HTTP absolute scheme.
- Realistic scenario: a copied or templated endpoint uses `ftp` or another
absolute scheme. Configuration and adapter construction succeed, but every
speaker transcription fails only after pipeline work reaches the first
request.
- Impact/likelihood/confidence: medium operability impact; low-to-moderate
configuration-error likelihood; high confidence from the constructor and
concrete transport contract.
- Estimated remediation scope and owner: small WhisperX adapter validation
change. Admit only `http` and `https` after normalization and retain the
existing absolute-host requirement.
- Test changes: table-test supported HTTP/HTTPS endpoints and reject FTP,
scheme-relative, hostless, and malformed values at construction. Add a
config-to-constructor case only if config validation also chooses to own the
scheme rule.
- Dependencies: `COR-013` separately owns duration validation disagreement;
neither finding requires changing retry behavior.
### `COR-017`: removing the final previous-artifact requirement leaves stale publishable state
- Category: confirmed correctness and data-integrity defect.
- Locations/invariant: `internal/stage/prepare.go` in `prepareStage.Run` and
`clearManagedPreviousState`, plus `internal/stage/publish.go` previous-file
collection. Prepare must make managed `previous/**` state match the current
requirement set, and publish must not expose bytes excluded from that set.
- Evidence: prepare calls `clearManagedPreviousState` only inside
`len(previousRequirements) > 0`. With zero current requirements it replaces
`manifest.inputs` without previous records but leaves the directory intact.
`TestPrepareStageWithoutPreviousRequirementsDoesNotTouchPreviousState`
explicitly requires a stale file to survive. Publish independently walks all
files below the previous directory and uploads them under the current session
prefix; it does not filter that walk through current input records.
- Realistic scenario: one pipeline revision consumes a previous recap and
prepare hydrates it. The operator removes the final previous input and forces
prepare for the same session. Later publish uploads the old recap again even
though current configuration and manifest inputs no longer declare it.
- Impact/likelihood/confidence: high stale-data and possible confidentiality
impact; moderate configuration-evolution likelihood; high confidence from
the focused test and publish caller.
- Estimated remediation scope and owner: small-to-medium prepare/publish
invariant correction. Clear the managed previous tree on every prepare before
optionally hydrating the current requirement set, or make publication consume
one explicit current-set record. Preserve confined deletion requirements.
- Test changes: replace the stale-survival assertion with a transition case that
hydrates a requirement, removes the final requirement, reruns prepare, and
proves both the local tree and publish upload set omit it. Retain the optional
missing requirement clear case.
- Dependencies: `COR-011` owns source identity for requirements that remain;
`COR-003` governs safe recursive clearing. Stage 4's deterministic publish
behavior is not reopened.
### `COR-018`: transcribe can report successful partial work after cancellation
- Category: confirmed correctness and lifecycle defect.
- Locations/invariant: `internal/stage/transcribe.go` worker, dispatcher, and
completion decision; `internal/app/runner.go` success recording. A successful
transcription result must represent every discovered audio job, while parent
cancellation must produce an error rather than a reusable success.
- Evidence: a worker returns without recording an error when `stageCtx.Err()`
is already non-nil, and dispatch breaks silently on `stageCtx.Done()`. After
waiting, the coordinator checks only `firstErr`; it never checks the parent
context or verifies completed results equal planned jobs. A pre-canceled
context therefore produces a successful result with zero outputs. If some
workers complete before cancellation stops dispatch, their subset is sorted,
materialized, and returned as success. The runner has no independent context
check and records that result as succeeded.
- Realistic scenario: an operator cancels a multi-speaker session while one
fast request has completed but other work is queued. The command can report
success and make the partial raw transcript set reusable by merge on the next
invocation.
- Impact/likelihood/confidence: high transcript completeness/integrity impact;
moderate cancellation likelihood; high confidence from the explicit channel,
context, and result-count control flow.
- Estimated remediation scope and owner: small transcribe coordinator change.
Track planned/dispatched/completed jobs and return the parent cancellation
cause whenever the complete set was not produced, while retaining the first
concrete adapter/validation error when it caused cancellation. Materialize
canonical outputs only after complete success.
- Test changes: add pre-canceled and barrier-controlled mid-dispatch cases that
assert an error, no canonical partial materialization, and stable adapter-error
precedence. Keep the bounded concurrency and deterministic ordering cases.
- Dependencies: `EFF-002` and `RSK-011` govern transport/process cancellation
latency; this finding owns worker aggregation after cancellation. `TST-001`
separately owns the racing fake used under valid concurrency.
### `COR-019`: repeated explicit audio input is accepted by prepare and rejected by transcribe
- Category: confirmed correctness and operability defect.
- Locations/invariant: `internal/config.validateSession`,
`internal/stage.resolveLocalAudioFiles`, `materializeLocalAudioInputs`, and
`discoverPreparedAudio`. One accepted audio selection must have a consistent
identity from configuration through prepared manifest consumption.
- Evidence: configuration validation accepts any non-empty `audio_files` slice
and does not check duplicates. Local resolution sorts but does not deduplicate
it. Materialization rejects only when the same destination basename maps to a
*different* source, so the same path repeated is copied and registered twice;
prepare succeeds with duplicate audio input records. Transcribe then prefers
those manifest records and `validateAudioFiles` rejects the duplicate clean
path before any adapter call.
- Realistic scenario: a generated or hand-merged session file repeats one FLAC
entry. Validation and prepare both succeed, but the deterministic next stage
fails with `duplicate audio file path`, requiring a configuration correction
and rerun.
- Impact/likelihood/confidence: medium delayed-configuration-failure impact;
low-to-moderate authoring/tooling likelihood; high confidence from the
consecutive producer/consumer checks.
- Estimated remediation scope and owner: small config/prepare decision. Prefer
rejecting duplicate cleaned sources during configuration/resolution with
field context; alternatively deduplicate deterministically before copying and
recording, but do not allow duplicate manifest identities.
- Test changes: cover exact and clean-path-equivalent duplicates, distinct
sources with the same basename, and a valid multi-file set at the earliest
chosen owner; retain transcribe's defensive duplicate-manifest rejection.
- Dependencies: `COR-002` owns unsafe identity segments and `RSK-008` foreign
restored paths; neither changes duplicate semantics.
### `COR-020`: extraction reuse is not bound to the current trimmed transcript
- Category: confirmed correctness and data-integrity defect.
- Locations/invariant: `internal/stage/extract.go` in `extractionFingerprint`
and result metadata construction, and
`internal/stage/extract_resume.go` in `ValidateResume`. A reusable extraction
must represent the current direct transcript input as well as the current
invocation contract and durable output bytes.
- Evidence: execution resolves `narratio.transcript.final_trimmed` and passes
its absolute path to Notarius, but the fingerprint contains only executable
and config paths, pipeline ID, timeout, working directory, and sorted output
contracts. Neither the input path, producer identity, nor an input checksum
is recorded. Resume never resolves or hashes the transcript; after comparing
the configuration fingerprint it validates only the old promoted bundle and
output record. Existing output checksums can therefore all agree while the
direct input bytes no longer do. The focused extract documentation lists the
fingerprint fields and external transitive limitations but does not assign
direct transcript identity to the operator.
- Realistic scenario: the canonical final-trimmed file is restored, repaired,
or modified out of band while the trim and extract stage records remain
succeeded. An ordinary run skips extract and continues with lanes derived
from the previous transcript. This is especially difficult to observe
because the immutable bundle and its checksums are internally valid.
- Impact/likelihood/confidence: high artifact-integrity impact; low-to-moderate
manual restore/repair or state-disagreement likelihood; high confidence from
the complete fingerprint document and resume control flow.
- Estimated remediation scope and owner: small-to-medium extraction identity
change. Hash the resolved trimmed transcript before invocation, store its
digest plus stable source/producer identity in extraction metadata, and
recompute the same evidence during resume before accepting the bundle. Keep
external/transitive Notarius dependencies under the documented force rule or
add an explicit operator-controlled dependency revision; do not pretend an
incomplete recursive file scan can prove them.
- Test changes: add one lifecycle test that succeeds extraction, changes the
resolved transcript bytes without changing the extraction record, and proves
the next invocation reruns and invalidates succeeded downstream work. Keep
configuration-change and output-tamper tables separate because they protect
different evidence.
- Dependencies: `RSK-008` concerns foreign restored absolute paths generally;
this finding owns direct extraction-input identity even when the path is
canonical. `DUP-007`/`SIM-002` must carry the new evidence into any shared
validation structure. Same-path Notarius files remain the documented
scenario-3 force limitation.
### `COR-021`: optional normalized, trimmed, and Markdown analyze inputs fail when absent
- Category: confirmed correctness defect.
- Locations/invariant: `internal/stage/analyze.go` in
`resolveScriptoriumInput`. Every Scriptorium input with `required: false`
must be omitted when its valid source is unavailable, independent of source
family; `required: true` owns source-specific failure and repair guidance.
- Evidence: after the catalog resolver returns `ErrSessionArtifactNotFound`,
extraction and configured branches consult `inputCfg.Required`, while
polished and default built-ins return unresolved for the caller to apply the
flag. The cases for `narratio.transcript.final`, `final_trimmed`,
`final_markdown`, and `final_trimmed_markdown` instead return errors
unconditionally. The caller therefore never reaches its optional-omission
branch. Focused tests cover successful values and required missing values for
these sources, but no optional missing case.
- Realistic scenario: one artifact can use an optional rendered transcript to
improve a prompt while remaining valid from a prepared or extraction input.
On a session without render output, analyze fails instead of invoking
Scriptorium without that optional input.
- Impact/likelihood/confidence: medium workflow-availability impact; moderate
likelihood for optional prompt enrichment; high confidence from the direct
branch ordering and the repository-wide required/optional contract.
- Estimated remediation scope and owner: small analyze source-resolution
change. Return source-specific errors only when required, otherwise return an
unresolved optional result. Preserve the distinct normalize/trim/render
guidance for required inputs.
- Test changes: table all built-in transcript/bounds identities as missing with
both required values, asserting omission for optional and the correct producer
guidance for required. Avoid duplicating successful resolver tests.
- Dependencies: use `SIM-003` only to make the decision shape clearer; do not
merge the source-specific guidance policies.
### `COR-022`: explicit artifact selection bypasses previous-input prerequisite planning
- Category: confirmed cross-boundary correctness defect.
- Locations/invariant: `internal/artifacts/catalog.go` in
`RegisterConfiguredArtifacts`, `internal/artifacts/previous_requirements.go`
in `CollectPreviousArtifactRequirements`, and its app/prepare/restore/status/
validation callers. Every artifact that analyze can execute must contribute
its required previous-session inputs to prerequisite planning.
- Evidence: a non-empty selected set makes membership authoritative over
`Enabled`; a focused catalog test explicitly selects and makes a disabled
artifact executable. CLI validation accepts any configured key regardless of
enabled state. Requirement collection has no selection input and skips every
disabled artifact. Consequently prepare, restore planning, object-store
composition, artifact listing, status, and validation all omit dependencies
that analyze will require from an explicitly selected disabled artifact.
- Realistic scenario: an operator keeps an occasional artifact disabled but
selects it for one run. It requires a previous-session recap. The run does
not compose/fetch/prepare that requirement, and analyze fails. Following its
prepare guidance still cannot populate the file while the artifact remains
disabled.
- Impact/likelihood/confidence: medium-to-high workflow correctness impact;
moderate likelihood because explicit selection is the natural one-off path;
high confidence from the tested selection override and collector signature.
- Estimated remediation scope and owner: medium app/artifact-planning change
after `ARC-007` chooses authority. If selection can activate disabled entries,
derive one effective artifact set and pass it to every prerequisite consumer.
If selection must intersect enabled entries, reject disabled selections
before planning. Do not let analyze and prepare recompute different sets.
- Test changes: an assembled run/prepare/analyze test must select a disabled
artifact with a required previous source and prove composition, planning,
materialization, and execution agree. Also cover optional previous input and
no-selection enabled behavior.
- Dependencies: `COR-010` concerns readiness checks for requirements that were
collected; this finding owns requirements omitted by selection. `ARC-007`
owns the product choice, and `TST-010` owns regression placement.
### `COR-023`: required previous-input failure recommends an invalid command
- Category: confirmed correctness/operability defect.
- Locations/invariant: `internal/stage/analyze.go` in
`resolveScriptoriumInput`, with syntax owned by `docs/cli.md` and
`internal/app` command parsing. Actionable failure guidance must name an
executable command for the current session.
- Evidence: the error says `run narratio run-stage --force prepare`. The actual
grammar is `narratio run-stage prepare <session_id> --force`; the emitted
form places a flag where the required stage argument belongs, reverses the
stage/flag order, adds an extraneous leading `run`, and omits the session ID.
The existing test checks only the fragment `run-stage --force prepare`, so it
codifies rather than catches the bad syntax. Prepared-stable and Markdown
guidance already include the session ID in the correct order.
- Realistic scenario: analyze fails on an absent required previous artifact and
the operator copies the suggested recovery command. Parsing fails before
prepare runs, extending an already blocked recovery path.
- Impact/likelihood/confidence: low data impact but direct recovery/operability
impact; high likelihood whenever this failure occurs; high confidence from
exact CLI grammar and emitted text.
- Estimated remediation scope and owner: tiny analyze message change. Emit
`narratio run-stage prepare <session_id> --force`, or route producer guidance
through a small command formatter if `SIM-003` establishes one. Include the
session ID already available in `paths`.
- Test changes: assert the complete command string, not an invalid fragment.
A parser round-trip is optional if a shared formatter is introduced.
- Dependencies: `COR-022` can make prepare itself omit the dependency; repair
both before claiming selected-disabled recovery is actionable.
### `COR-024`: documented Scriptorium input passthrough fields are silently discarded
- Category: confirmed configuration-contract defect.
- Locations/invariant: `internal/config/config.go` in
`ScriptoriumInputConfig`, `docs/config.md` in Scriptorium artifact entries,
`internal/stage/analyze.go`, and `internal/adapters/scriptorium`. Accepted and
documented operator fields must affect behavior or be rejected/reserved
explicitly.
- Evidence: the strict schema accepts `artifact` and `path`, and configuration
documents both as optional passthrough adapter fields. Graph-augmented exact
searches find no read of `inputCfg.Artifact` or `inputCfg.Path`. Resolution
consumes only `Source` and `Required`; execution sends only a map from input
name to resolved filesystem path. Neither Scriptorium request type nor its
CLI/generated invocation representation has fields for the accepted values.
- Realistic scenario: an operator sets either field based on the maintained
configuration reference to select an upstream adapter artifact/path. Config
loading and validation succeed, but the invocation is identical to one where
the fields were absent, with no warning that intent was lost.
- Impact/likelihood/confidence: medium configuration-trust impact; low-to-
moderate use likelihood because no maintained example uses the fields; high
confidence that current values are inert, with intended upstream semantics
intentionally left unresolved.
- Estimated remediation scope and owner: product/config-and-adapter decision.
Define and implement exact Scriptorium wire semantics if the feature is
supported; otherwise remove the fields from schema/documentation or reject
non-empty values with a migration message. Do not guess how `artifact` and
`path` combine with canonical `source` resolution.
- Test changes: once authority is chosen, assert end-to-end adapter invocation
semantics or strict rejection. A decode-only test is insufficient.
- Dependencies: `COM-005` owns other analyze documentation gaps. `ARC-007`
concerns selection, not these inert per-input fields.
### `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. The final accepted-risk section
permits genuinely abrupt interruption residue only after handled errors are
terminalized and session progress remains authoritative.
### `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 confirmed there is no mode/config override or documented
deployment privacy guarantee. Stage 7 owns adapter diagnostics; the final
backlog places secure defaults in the first safety workstream while retaining
deployment ACL/umask details as an explicit evidence limitation.
### `RSK-005`: remote publish locks are race-prone snapshots
- Category: confirmed correctness/concurrency risk.
- Locations/invariant: `internal/app/runner.go` remote-lock load order,
`remote_locks.go`, `operator_locks.go`, and unconditional
`storage.ObjectStore.Upload`. An operator lock intended to protect a published
destination should not be silently lost or bypassed by concurrent control-
plane activity.
- Evidence: a publish-capable run loads and merges the remote lock document
before acquiring its local session lock, then uses that in-memory slice for
the entire invocation. Lock add/remove separately loads the full document,
mutates it, and unconditionally uploads a replacement without a generation
check or the runner lock. Two mutations can lose an update, and a lock added
after publish's load cannot affect that in-flight upload. Existing tests are
sequential and prove only static precedence, mutation validation, and loaded
lock enforcement.
- Realistic scenario: two operators lock different outputs at the same time;
the last full-document upload drops the other lock. Or an operator locks a
destination while a long run is approaching publish, but that run already
loaded the old document and overwrites the destination despite the command
reporting that it was locked.
- Impact/likelihood/confidence: high protected-output integrity impact;
low-to-moderate likelihood in multi-operator or multi-host use; high confidence
in the lost-update/stale-read mechanics, moderate confidence that deployments
rely on concurrent lock mutation because no concurrency contract is stated.
- Estimated remediation scope and owner: medium app/storage-capability change.
Define lock activation semantics and use object generation/ETag conditional
replacement with retry, or a remote coordination primitive. Re-read or bind
the effective generation at the publish commit boundary. Static locks remain
immutable configuration and need no remote mutation protocol.
- Test changes: add a version-aware stateful store and deterministic barriers
for two add/remove writers plus add-during-publish; prove no lock is lost and
define whether the in-flight publish aborts or observes a committed snapshot.
Retain sequential force/static-lock tests.
- Dependencies: `RSK-003` concerns only the local single-writer sentinel and
cannot serialize other hosts or lock commands. `COR-004` may influence the
appropriate remote compare-and-swap capability.
### `RSK-006`: restore does not protect one coherent local transition from plan through manifest install
- Category: confirmed correctness/recovery risk.
- Locations/invariant: `internal/app/restore.go`, `restore_plan.go`, and
`restore_execute.go`; runner reuse of the session manifest. Local action
decisions and the manifest-last transition must remain coherent despite
competing local work and mid-restore failure.
- Evidence: restore discovers and classifies local paths before acquiring the
session lock, then never revalidates `skip_same` or conflicts after lock
acquisition. Another completed runner/restore or local edit in that window can
make the plan stale. During execution, files install incrementally with no
rollback or incomplete marker. A forced failure before manifest installation
leaves the old successful manifest in place even though some files it governs
were overwritten with remote content. Later runners do not inspect the failed
restore report.
- Realistic scenario: planning marks a transcript `skip_same`; another runner
acquires/releases the lock and changes it; restore then acquires the lock,
skips the stale decision, and installs the remote manifest. Or forced restore
overwrites that transcript, fails on a later artifact, and releases the lock
with the old manifest still claiming success over changed content. A normal
run can then reuse that manifest instead of completing restore.
- Impact/likelihood/confidence: high pipeline integrity impact; low likelihood
per restore but realistic under operator concurrency or recovery from damaged
storage; high confidence in ordering/no-revalidation, with exact external edit
likelihood environment-dependent.
- Estimated remediation scope and owner: medium-to-large application/filesystem
change. Acquire the session lock before executable classification (dry-run can
remain unlocked/read-only), revalidate under lock, and make incomplete forced
restore observable to the runner. Consider staging a complete tree or a
durable restore transaction marker rather than attempting broad rollback.
- Test changes: deterministic barriers around plan/lock plus a failure after one
forced install; assert stale skip decisions cannot commit and ordinary runner
reuse is blocked until retry completes. Preserve the current manifest-last and
retry-idempotency tests.
- Dependencies: `RSK-003` governs stale local lock recovery; `COR-003` and
`RSK-002` govern confined and crash-durable installation. `COR-008` is the
analogous remote snapshot problem.
### `RSK-007`: audio restore and cache hits use size as content identity
- Category: confirmed correctness/data-integrity risk.
- Locations/invariant: `internal/app/restore_plan.go` audio classification and
`internal/audio/s3_audio.go` cache validation. Reused audio must correspond to
the intended remote object generation, not merely have a plausible length.
- Evidence: existing restore audio with the same positive size is
`skip_same` without a body/checksum/ETag comparison, even under force. Cache
validity accepts any nonempty non-directory path and, when available, equal
size. Although `ObjectInfo.ETag` is carried into the materializer and newly
copied files compute a checksum, neither participates in later cache identity.
With unknown remote size, any nonempty cache entry is accepted. A focused test
deliberately proves same-sized different strings skip without download.
- Realistic scenario: an S3 audio key is replaced with corrected audio of the
same byte length, or a cache file is corrupted without changing length.
Restore/prepare silently reuse the old bytes; transcription proceeds from
audio that no longer matches remote operator intent.
- Impact/likelihood/confidence: high downstream content-integrity impact;
low-to-moderate likelihood for same-size replacement/corruption; high
confidence in the comparison rules. The shortcut is an evident performance
tradeoff, so classification as risk rather than certain wrong output for every
cache hit is appropriate.
- Estimated remediation scope and owner: medium audio/storage metadata change.
Bind cache entries to a stable object generation or trustworthy checksum via
sidecar metadata, validate regular-file/no-follow status, and define multipart
ETag limitations. Force should refresh or verify when explicitly requested.
- Test changes: same-size remote replacement, corrupt same-size cache, unknown-
size cache, ETag/generation change, and force semantics. Keep the current hit/
miss/invalid-size cases as lower-cost mechanism coverage.
- Dependencies: Stage 7 owns which S3 metadata is trustworthy and portable.
`COR-003` owns symlink-based cache/path escape; do not solve identity with
path checks alone.
### `RSK-008`: restored manifests retain foreign absolute paths that later consumers prefer
- Category: confirmed correctness/security risk.
- Locations/invariant: manifest installation in
`internal/app/restore_execute.go`, path preservation in `internal/manifest`,
and manifest-first resolution in `internal/artifacts/artifact_resolver.go`.
Restored state should resolve to the selected local workspace unless a trusted
external path contract explicitly authorizes otherwise.
- Evidence: restore installs current manifest bytes unchanged. Published
manifests normally contain absolute producer-local output/input paths plus
top-level work/spool/run paths. Artifact resolution returns an absolute
manifest path unchanged and prefers it over the canonical restored fallback
whenever it exists and validates. The extraction round-trip test deliberately
preserves `/prior/workspace/...` metadata but does not run a consumer against
an existing foreign path. Top-level fields already trigger `COR-001` on the
next invocation.
- Realistic scenario: restore moves a session to a host or workspace where the
old absolute path exists with stale or attacker-controlled transcript content.
Analyze/publish reads that file instead of the restored canonical copy. Even
when it does not exist, session identity retains wrong run/work/spool metadata
and can direct later work through `COR-001`.
- Impact/likelihood/confidence: high integrity/confidentiality impact; low-to-
moderate likelihood because path existence depends on host/layout reuse; high
confidence in preservation and resolver precedence.
- Estimated remediation scope and owner: medium restore/artifact/manifest
change. Separate portable logical references from host-local diagnostics,
rebase or reject restored absolute paths outside the selected session root,
and preserve original values only as bounded provenance if useful.
- Test changes: restore a real manifest with built-in/configured input/output
records from a different root, create a conflicting outside sentinel, and
assert downstream resolution uses the restored canonical path. Extend
`COR-001` tests for top-level restored identity.
- Dependencies: `COR-001` owns stale top-level run identity; `COR-003` owns
filesystem symlink confinement. Stage 10 should reuse this result when
reviewing manifest-first artifact trust.
### `RSK-009`: remotely discovered session configuration is left in system temporary storage
- Category: confirmed confidentiality/lifecycle risk.
- Locations/invariant: `internal/app/config_loader.go` in `loadCommandConfig`
and `internal/adapters/storage/temp_download.go` in
`DownloadObjectToTemp`. Every successful temporary download must have an
explicit owner and cleanup point, especially when it contains private session
configuration.
- Evidence: failed downloads remove their temporary file, but successful remote
session fallback returns the path into `Config.SessionPath` and
`SessionSource`. No caller removes it. Full execution later copies it to
canonical `inputs/session.yml` yet leaves the original; plan, status,
validate, and single-stage commands can leak one on every invocation. The
ephemeral path can also be retained as source/spool provenance in a manifest.
- Realistic scenario: a long-running worker repeatedly uses remote-only session
configs. System temporary storage accumulates readable copies containing S3
audio references and private campaign/session settings after commands finish,
until an unrelated host cleanup policy happens to remove them.
- Impact/likelihood/confidence: medium confidentiality and disk-lifecycle impact;
deterministic for remote fallback; high confidence from all successful caller
paths.
- Estimated remediation scope and owner: medium app loader/command-lifetime
change. Return an owned cleanup handle or bytes, defer cleanup at the command
boundary after all consumers finish, and record canonical logical provenance
rather than the ephemeral host path.
- Test changes: assert removal after successful and failed full, read-only, and
single-stage commands; preserve the existing failure cleanup assertion; verify
manifests and reports do not retain a nonexistent system-temp source path.
- Dependencies: do not remove the file before prepare or other consumers finish.
`RSK-004` remains the broader runtime-mode disclosure boundary.
### `RSK-010`: filesystem secret loading follows links and reads non-regular entries
- Category: confirmed confidentiality/availability risk.
- Locations/invariant: `internal/app/secrets_env.go` in
`loadSecretsFromConfig`. A configured secrets directory should define a
bounded set of regular secret files, not grant an ambient read capability
through entry replacement or special file types.
- Evidence: the loader skips only directory entries and invalid names, then
calls `os.ReadFile` on the joined path. It follows a validly named symlink and
accepts any other non-directory entry that `ReadFile` can open, without a
regular-file/type/size check. The unreadable-entry test uses a broken symlink
only to assert an error; it does not reject a working link before reading it.
- Realistic scenario: a writable or incorrectly provisioned secrets directory
contains `AUDITA_TOKEN` linked to an unrelated readable file, or a large/special
entry. A more privileged invocation reads the target into process environment
inherited by subprocess adapters, or incurs unbounded I/O/memory and blocks
composition.
- Impact/likelihood/confidence: high confidentiality/availability impact when
directory ownership is weak; deployment-dependent likelihood; high confidence
from the entry-type and read flow.
- Estimated remediation scope and owner: small-to-medium app/filesystem boundary
correction. Inspect without following links, admit regular files only, enforce
a documented size bound, and use a handle-relative/no-follow read where the
supported platforms allow entry replacement races to be closed.
- Test changes: cover working and broken symlinks, FIFO/special entries where
portable, oversized files, replacement races at the chosen primitive, valid
newline trimming, and existing-environment precedence.
- Dependencies: Stage 7 should verify downstream environment propagation but
must not duplicate filesystem policy. `COR-003` owns mutation confinement,
whereas this finding owns reads from the secret directory.
### `RSK-011`: subprocess cancellation terminates only the direct child
- Category: confirmed correctness/operational risk.
- Locations/invariant: `internal/adapters/subprocess.Run`, used by every Audita,
Seriatim, Scriptorium, and Notarius invocation; integration contracts state
that parent cancellation and timeouts bound invocations. Cancellation must
terminate all work started for one tool invocation, not merely return from
waiting on its first process.
- Evidence: the launcher uses `exec.CommandContext` without changing process
attributes or `Cmd.Cancel`. In the audited Go toolchain that cancel function
calls `Kill` on `cmd.Process`; no process group/job ownership or descendant
cleanup exists. The timeout test starts a sleeping direct helper only.
- Realistic scenario: an external CLI starts a worker subprocess and then the
stage times out. Narratio kills and waits for the CLI, returns a timeout, and
closes its descriptors, while the worker continues consuming CPU, writing
run-local files, or making paid API requests after the manifest records
failure.
- Impact/likelihood/confidence: high resource/integrity impact; moderate
likelihood for Python/worker-based external tools; high confidence in the
launcher semantics, with exact descendant behavior dependent on each tool.
- Estimated remediation scope and owner: medium, platform-aware subprocess
mechanism. Establish an invocation-owned process group on Unix and equivalent
job/process-tree behavior on supported Windows, terminate the group on
cancellation, wait/reap deterministically, and document any unavoidable
platform limit.
- Test changes: add a helper that spawns a descendant, records its identity,
times out, and proves the descendant cannot write a delayed sentinel. Keep
the direct-child timeout test and add explicit parent cancellation.
- Dependencies: keep process mechanics in the shared launcher; adapter packages
should not each implement their own kill policy.
### `RSK-012`: subprocess diagnostics can persist raw credentials
- Category: confirmed confidentiality risk.
- Locations/invariant: `internal/adapters/subprocess.Run`, `openLogWriters`,
`readRedactedTail`, and inherited-environment use by Notarius and Scriptorium.
Architecture policy forbids raw secrets in logs and manifests.
- Evidence: stdout and stderr are written directly to persisted log files with
no filtering. The 2 KiB error tail replaces only values whose keys look
sensitive in `RunRequest.EnvOverrides`; values inherited through
`os.Environ()` are not considered. Audita's mapped key is protected only in
the returned tail, not in its raw log. Scriptorium and Notarius intentionally
inherit their environment, so a child that echoes an inherited credential
can also place it in the wrapped stage error persisted to both manifests.
- Realistic scenario: a downstream tool includes its API key in a debug/error
dump. Narratio retains the value in a run log, and for an inherited key also
embeds it in the durable failure text. Run archives or troubleshooting access
then disclose the credential beyond its intended environment boundary.
- Impact/likelihood/confidence: high confidentiality impact; low-to-moderate
faulty/debug-tool likelihood; high confidence in the diagnostic flow.
- Estimated remediation scope and owner: medium shared subprocess/adapter
correction. Define the sensitive environment names supplied to each child,
redact their values in streaming log writers and returned tails, minimize the
inherited environment where protocol-compatible, and preserve useful bounded
diagnostics without copying raw secret material into manifests.
- Test changes: cover override and inherited sensitive values in stdout,
stderr, on-disk logs, and returned errors; assert non-sensitive diagnostics
remain readable. Avoid real credentials in fixtures.
- Dependencies: `RSK-010` owns safe secret-file acquisition; this finding owns
propagation after values enter the process. `RSK-004` owns broad file modes,
not the prohibited content itself.
### `RSK-013`: ordinary subprocess output validation is unbounded and follows links
- Category: confirmed correctness/availability risk.
- Locations/invariant: Audita `validateProcessedOutput`/`validateJSONFile`,
Seriatim `validateJSONFile`/`validateTranscriptFile`/render validation,
Scriptorium output checks, and `internal/contracts` bounds/transcript reads.
External output must be a bounded regular result at the requested path before
a stage trusts or materializes it.
- Evidence: these validators use unbounded `os.ReadFile` or link-following
`os.Stat`. They do not reject a symlink before parsing, and most do not first
establish a regular-file handle. A malformed or oversized result can allocate
until memory exhaustion; a symlink to an existing valid JSON/text file can
satisfy validation and be copied as the stage output. Notarius already uses
`Lstat`, same-file checks, regular-file enforcement, and explicit size limits,
while WhisperX bounds responses to 10 MiB, demonstrating a compatible local
boundary pattern. Stage 8 confirmed the same acquisition path is used again
by `validateTranscriptJSONFile`, `validateProcessedTranscriptOutput`,
`copyTranscript`, `requireNonEmptyFile`, and run-local materialization before
merge, polish, normalize, trim, and render record canonical outputs. The risk
therefore crosses the adapter/stage boundary rather than ending at adapter
first-pass validation. Stage 9 found the bounded Notarius management-file
boundary stops short of configured lane bodies: extract's
`checksumRegularFile` performs one unbounded `os.ReadFile` per lane to parse
and hash it, while catalog hydration first streams the checksum and then
performs another unbounded `os.ReadFile` for JSON validity. These paths reject
links and non-regular files but still admit memory-exhausting external JSON.
- Realistic scenario: a faulty external CLI writes a multi-gigabyte JSON result
or leaves the requested output as a symlink to stale data. It exits zero;
Narratio then exhausts memory or records unrelated bytes as a successful
canonical transcript/artifact.
- Impact/likelihood/confidence: high availability/integrity impact; low-to-
moderate faulty-tool or filesystem-reuse likelihood; high confidence in the
validators, with appropriate limits requiring contract decisions.
- Estimated remediation scope and owner: medium shared read-mechanism plus
adapter-specific schema policy. Open without following links where supported,
require a regular file, enforce documented per-contract limits, parse from
the established handle, and leave semantic schema checks in each adapter.
- Test changes: add one shared table for symlink/non-regular/oversized reads and
focused adapter cases proving semantic errors retain their context. Include
one configured Notarius lane/catalog case at the owner of the chosen bound;
avoid duplicating the same large fixture for every JSON validator.
- Dependencies: coordinate no-follow mechanics with `COR-003` and size policy
with Stage 8/10 consumers. Do not weaken Notarius's stricter bundle boundary.
### `RSK-014`: S3 listing has no continuation-token progress guard
- Category: confirmed availability/resource risk.
- Locations/invariant: `internal/adapters/storage.S3Backend.List`. Pagination
must either make observable progress, finish, fail, or honor cancellation
without unbounded duplicate accumulation.
- Evidence: the loop repeats while `IsTruncated` is true and a next token is
non-nil, assigning that token without comparing it with the prior token. A
provider that repeats one token returns the same page indefinitely. The
production callers generally have no operation-specific deadline, and the
storage fake/test returns only one page and ignores continuation behavior.
- Realistic scenario: an S3-compatible endpoint emits a malformed truncated
response with a repeated token. Session discovery, prepare, restore, status,
or cleanup loops at full request rate and appends duplicate objects until an
operator cancels or the process exhausts memory.
- Impact/likelihood/confidence: high availability and possible request-cost
impact; low provider-fault likelihood; high confidence in loop behavior.
- Estimated remediation scope and owner: small storage-adapter correction.
Reject an empty or repeated next token on a truncated response with contextual
provider/protocol error; retain caller-context cancellation and caller-owned
ordering.
- Test changes: use a stateful S3 fake for two valid pages, repeated/empty token,
later-page error, cancellation, and deterministic normalized aggregation.
- Dependencies: snapshot/generation authority remains `COR-008`; this finding
concerns completion of one list operation only.
### `RSK-015`: analyze dependency preflight chooses errors nondeterministically
- Category: confirmed determinism/operability risk.
- Locations/invariant: `internal/stage/analyze.go` in
`orderSelectedScriptoriumArtifacts`. Given one configuration, selection, and
filesystem state, dependency validation should return one stable diagnostic
before any adapter or output mutation.
- Evidence: selected names are copied into a map, then the initial unknown/
unavailable dependency preflight ranges directly over that map and returns
the first failure. Go map order is unspecified. When two selected artifacts
each have an unavailable unselected dependency, either artifact can own the
returned error across executions. Later graph construction also ranges over
maps, but it sorts edges and ready nodes before observable successful order;
the nondeterminism is confined to preflight diagnostics.
- Realistic scenario: a configuration deploy omits several reused dependency
files. Repeated CI or operator invocations report different first blockers,
making logs, snapshots, and one-at-a-time recovery unstable even though no
external state changed.
- Impact/likelihood/confidence: low execution-integrity impact but moderate
diagnostic/reproducibility impact; moderate multi-error likelihood; high
confidence from explicit map iteration before first return.
- Estimated remediation scope and owner: tiny analyze ordering change. Iterate
the already sorted selected slice or sort map keys before preflight. Preserve
lexical successful topological ordering; do not introduce a generic graph
package solely for this repair.
- Test changes: construct at least two independently unavailable dependencies
from intentionally shuffled insertion order and assert one stable exact
error across repetitions.
- Dependencies: `SIM-003` may provide an indexed plan, but this correction does
not depend on structural refactoring.
### `EFF-001`: restore repeatedly downloads the same objects during planning and execution
- Category: confirmed efficiency and clarity issue.
- Locations/invariant: `internal/artifacts.LoadCurrentState`,
`internal/app/restore_plan.go` checksum classification,
`internal/previouscache.BuildPlan`, and `restore_execute.go`. Recovery should
avoid redundant remote transfer while preserving conflict and snapshot
correctness.
- Evidence: discovery downloads the current manifest, planning can download it
and every same-size non-audio object again for checksum comparison, and
execution re-downloads every forced differing object plus current/previous
manifests for installation. `RestorePlanOptions.DryRun` is otherwise unused,
so dry-run performs the same temporary body downloads for classification.
Temporary files are cleaned and no durable session write occurs, but the
focused internal document's “performs no local writes” wording obscures these
system-temp writes.
- Realistic scenario: a forced restore of several large same-sized artifacts
downloads each body to decide it differs, discards it, then downloads it again
to install. High-latency or metered storage doubles transfer and lengthens the
interval exposed to `COR-008` remote changes.
- Impact/likelihood/confidence: low-to-medium cost/latency impact that scales
with artifact size and remote pricing; occurs deterministically for same-size
differing forced files and repeated manifests; high confidence.
- Estimated remediation scope and owner: medium restore/storage-contract change.
Couple snapshot/version repair with a verified downloaded candidate that can
be retained for execution, or expose trustworthy digest/version metadata.
Document dry-run as having no durable/session mutation unless truly streaming
comparison eliminates all temporary writes.
- Scale/current-versus-proposed cost: for `n` same-size non-audio objects with
aggregate body size `B`, planning performs `n` full downloads and reads `B`
remote plus `B` local bytes for checksums. A forced apply of differing bodies
then downloads up to another `B`; current-state discovery also fetches the
pointer and manifest before the listed manifest can be classified again.
Retaining generation-bound verified candidates would keep apply to one body
transfer per changed object rather than two, while dry-run necessarily keeps
its one classification transfer unless trustworthy remote digests are
available. Metadata/list and local checksum costs remain linear.
- Measurement: use a counting/versioned object-store fake with representative
100 MiB and 1 GiB aggregate bodies to record calls, bytes, and elapsed time for
missing, equal, size-different, and same-size-different objects in dry-run and
apply modes. The repair should demonstrate the lower byte bound without
weakening generation revalidation; no CPU-only microbenchmark is useful.
- Test changes: count bytes/downloads for missing, equal, size-different, and
same-size-different objects in dry-run and apply modes; assert relational upper
bounds rather than exact private call choreography after snapshot design is
chosen.
- Dependencies: solve with `COR-008` so caching a plan download cannot install a
stale generation. Stage 7 owns adapter metadata/cost tradeoffs.
### `EFF-002`: WhisperX buffers each complete multipart upload in memory
- Category: confirmed efficiency/resource-use issue.
- Locations/invariant: `internal/adapters/whisperx.doTranscribeAttempt` and the
transcribe stage's configured concurrent use of `Client.Transcribe`. Upload
memory should remain bounded independently of aggregate audio size.
- Evidence: every attempt copies the complete audio file into a `bytes.Buffer`,
adds the remaining multipart fields, and only then constructs and sends the
request. The copy does not observe context cancellation. Concurrent speakers
therefore retain roughly one full audio file each in memory, and every retry
repeats the allocation/copy before network I/O. Response memory is separately
and correctly capped at 10 MiB.
- Realistic scenario: several long speaker tracks are transcribed with the
configured worker concurrency. Narratio allocates their aggregate size at
once and can be killed for memory pressure before the HTTP server receives a
byte; canceling during a large local copy does not stop that work promptly.
- Impact/likelihood/confidence: medium-to-high memory/operability impact that
scales with ordinary media size and concurrency; high likelihood on long
sessions; high confidence from the request construction path.
- Estimated remediation scope and owner: medium WhisperX adapter change. Stream
multipart content from a per-attempt reopenable audio source, propagate copy
errors and cancellation through the request body, and preserve replay across
retries without sharing a consumed reader.
- Scale/current-versus-proposed cost: with worker concurrency `c` and average
audio size `s`, request construction retains approximately `O(c*s)` bytes
before network progress, plus multipart overhead, and repeats that allocation
on each retry. A pipe-backed multipart producer with bounded copy buffers
keeps application buffering at `O(c)` while total network I/O remains
necessarily `O(c*s)` per attempt. This matters for ordinary multi-hour audio,
where each speaker track can be hundreds of MiB; it is not a small-allocation
optimization.
- Measurement: add a benchmark or controlled transport test using several
100 MiB sparse/generated inputs at configured concurrency, reporting
`-benchmem`, peak heap/RSS, time until the transport receives its first byte,
cancellation latency, and retry reopen behavior. The expected win is bounded
peak memory and earlier upload progress, not fewer transmitted bytes.
- Test changes: use a blocking/counting reader or transport to prove the request
begins before the complete source is buffered, cancellation interrupts body
production, retries reopen cleanly, and response/output bounds remain intact.
- Dependencies: Stage 8 confirmed the worker concurrency limit and stable
successful result ordering; streaming should make each worker bounded rather
than changing that stage policy. `COR-018` separately owns cancellation being
mistaken for complete success.
### `ARC-004`: notification configuration has no production transport consumer
- Category: confirmed architectural/operator-boundary defect.
- Locations/invariant: `internal/config.NotificationConfig`, pipeline examples
and configuration reference, `internal/app/runner.go` notifier composition,
and `internal/adapters/notify`. Accepted operator settings must either select
implemented behavior or be rejected/described as reserved.
- Evidence: backend, recipient, and timeout are accepted and the timeout is
parsed, but composition always installs `notify.NoopSender` when no test
collaborator is injected. The adapter package contains only no-op and fake
senders and has no mapping for those three fields. The notify stage can thus
succeed with placeholder metadata regardless of configured recipient. Only
the internal overview calls it a placeholder; the public config table and
annotated example present ordinary optional settings.
- Realistic scenario: an operator configures a backend and recipient, observes
a succeeded final stage, and assumes a completion or failure notice was
delivered when no external call occurred.
- Impact/likelihood/confidence: high operator-expectation impact; moderate
likelihood because the fields are publicly surfaced; high confidence from
complete composition and adapter inventory.
- Estimated remediation scope and owner: small config/documentation change if
delivery remains deferred, or medium integration/composition work if delivery
is required. The smallest safe correction is to reject non-empty
backend/recipient values and label/remove reserved settings until a canonical
notification integration contract exists; do not silently map them in the
stage.
- Test changes: assert non-placeholder settings cannot validate while no
transport exists, or, after a transport is specified, add adapter contract
tests for timeout/cancellation/error adaptation and an assembled composition
test proving selection. Preserve the no-op path only when explicitly chosen.
- Dependencies: the ordinary-success lifecycle vocabulary must be settled with
the later `ARC-002`/maintainability synthesis; this finding owns transport
selection and operator truthfulness, not stage-state mechanics.
## Classified Structural, Test, And Clarity Register
The entries retain their original locations for traceability, but their Stage
11 or Stage 12 classifications are final: confirmed recommendations are ordered
in the remediation backlog above, merged entries defer to the named stronger
root, and rejected entries require new evidence before reopening.
### `ARC-001`: `IODecl` is not a complete or consistently classified stage contract
- Category: architectural boundary/ownership candidate.
- Stage 11 classification: confirmed. Remove `Declares` and `IODecl` from the
runtime `Stage` interface and implementations rather than expanding an unused
partial model. Focused stage documentation already owns the complete dynamic
contract. If a future planner needs machine-readable contracts, introduce a
purpose-built model for that consumer rather than treating todays static
path hints as authoritative.
- 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. Stage 8 also
found unconditional optional report/bounds declarations, configured output
paths that can differ from the static declaration, and render outputs declared
even when disabled render succeeds with none. Stage 9 found extract's row is
directionally accurate but necessarily uses wildcard/run placeholders, cannot
declare the configured lane source IDs or contracts, and says nothing about
the nonselectable index, diagnostics, adapter, fingerprint, or self-skip
lifecycle. 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.
- Review history: Stages 8-10 completed ordinary, extraction, and analyze rows;
the final removal decision follows from the absent production consumer and
the models inability to describe those established contracts.
### `ARC-002`: disabled-stage “skip” terminology spans two different durable outcomes
- Category: architectural/lifecycle ownership candidate.
- Stage 11 classification: merged into the documentation root `COM-002`.
Production behavior is coherent: `StageDispositionSkipped` means an executed
self-skip, while zero-disposition no-output results are ordinary success.
A new lifecycle abstraction or state change would obscure rather than repair
that distinction; the remaining defect is inconsistent language.
- 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.
- Review history: Stage 2 confirmed the runner truth table. Stage 4
confirmed publish's ordinary-success behavior is used deliberately by the
cleanup gate, while `COM-002` owns its incorrect “self-skip” documentation.
Stage 8 confirmed disabled trim is real successful copy processing and
disabled render is deliberately successful with no output; `COM-004` owns
render wording. Stage 10 confirmed absent/no-executable analyze is likewise
successful with no output so the pipeline can continue and publish's
prerequisite can be satisfied; broadened `COM-002` now owns the shared
lifecycle vocabulary without changing those behaviors.
### `ARC-003`: committed and local manifests give `current_pointer_written` different meanings
- Category: architectural boundary/ownership candidate.
- Stage 11 classification: confirmed. The remote immutable snapshot should not
serialize a postcommit assertion before commit. Prefer deriving commitment
from the loaded pointer, or represent precommit publish metadata separately
from local postcommit execution metadata. Never repair this with a
post-pointer overwrite, which would break pointer-last atomicity.
- Evidence: publish must serialize `current/manifest.json` before the commit
marker, so `publishMetadataPreview` records
`current_pointer_written=false`. After pointer success, the local session and
invocation results record the same field as true. Current-state readers use
the actual pointer and ignore the remote field; automatic cleanup uses the
local true value. Existing behavior is therefore safe for current consumers.
- Realistic risk: a future status, restore, reconciliation, or cleanup feature
may treat the committed remote manifest's field literally and report a valid
commit as incomplete, while another consumer interprets the local copy as an
assertion about remote state. Updating the fixed manifest after pointer would
instead violate pointer-last ordering.
- Remediation boundary: use distinct precommit/local representation, omit the
field remotely, or derive it from loaded pointer identity. Any implementation
must preserve `COR-004`s pointer-last protocol; do not add a post-pointer
upload.
### `ARC-005`: Audita request and constructor both advertise ownership of static settings
- Category: architectural boundary/ownership candidate.
- Stage 11 classification: confirmed. Constructor state should own base URL,
model, transcript description, config/schema paths, retention, and
concurrency. `PolishRequest` should retain invocation paths and the genuinely
per-run module override only. This matches the production runner and removes
fake-only apparent overrides.
- Evidence: `PolishRequest` and its integration document carry base URL, model,
transcript description, config path, output schema, work-dir retention, and
concurrency values. `SubprocessRunner.Run` ignores those request fields and
builds arguments, generated config, and metadata from constructor state;
only request `Modules` can override the configured list. The polish stage
currently copies the same config values into both places, so production
behavior agrees by convention. The fake captures the request and does not
reveal that the real runner ignores most of it.
- Realistic risk: a caller or focused stage test supplies a per-request setting
and observes it in the fake, while the production runner silently uses its
older constructor value. Future changes may update one representation only
and make provenance disagree with the apparent request contract.
- Review history: Stage 8 found no polish use case for per-invocation overrides
and recommended constructor authority for static settings. Stage 12 should
align the fake with the chosen contract.
Do not merge genuinely request-specific paths/modules into static runner
construction.
### `ARC-006`: stage authority for adapter-returned output paths is inconsistent
- Category: architectural boundary/ownership candidate.
- Stage 11 classification: confirmed. The stage-requested run-local destination
is authoritative. Adapters should either return no path or return exactly the
requested path, and stages should validate that identity before consuming the
file. A redirect contract is not justified by any current adapter and would
expand filesystem authority across the isolation boundary.
- Evidence: transcribe requires the adapter result path to equal its requested
run-local destination and then validates the requested path. Transformation
stages all request run-local paths, but merge, polish, normalize, render, and
Scriptorium bounds/render-debug prefer non-empty returned paths, while the
Seriatim trim branch validates the requested destination. Current Audita,
Seriatim, and Scriptorium production adapters return the requested path, so
production agrees by convention rather than an explicit shared rule.
- Contract tension: stages own run-local isolation and canonical materialization,
while adapters own protocol execution. Allowing an adapter to redirect output
gives it filesystem-placement authority that the request appears to reserve
to the stage; ignoring a returned path makes that result field misleading.
- Realistic risk: a future adapter version or fake returns a valid stale,
canonical, or outside-work path. Depending on the stage, Narratio may consume
it, ignore it, or reject it, making tests and isolation guarantees disagree.
- Test boundary: Stage 12 should add one requested-path contract test across
affected fakes/adapters rather than duplicating every stage case.
### `ARC-007`: enabled and selected artifact authority is split across boundaries
- Category: architectural boundary/ownership candidate.
- Stage 11 classification: confirmed. Preserve the CLI and catalogs tested
rule that an explicit selection is a one-invocation execution override; when
absent, `enabled` defines the default set. Compute a typed effective artifact
set before validation, prerequisite collection, catalog composition, and
planning, and validate selected disabled definitions as fully executable.
Publish keeps its distinct role as a filter over configured output rules.
- Evidence: catalog registration makes `enabled` authoritative only when no
explicit selection exists; a non-empty selection replaces it, and a unit test
requires a selected disabled artifact to become executable. CLI validation
accepts that choice. Configuration requires prompt/output fields and checks
cycles only for enabled artifacts, previous-requirement collection scans only
enabled artifacts, while `docs/internal/artifacts.md` defines executable as
both selected and enabled. Publish uses selection as a filter over configured
rules but availability ignores enabled state.
- Contract tension: `enabled` can mean default execution, complete executable
configuration, prerequisite participation, or publication availability,
depending on the boundary. `selected` can mean an override or a filter. No
single effective-artifact-set owner states which interpretation is canonical.
- Realistic risk: `COR-022` is the concrete prior-input failure. A selected
disabled artifact can also reach runtime with fields that configuration did
not require, while an enabled-but-unselected artifact is reported with
disabled-output provenance. Future validation/publish changes can widen the
disagreement.
- Remediation boundary: compute the one-off overrides typed effective set
before configuration-dependent composition/planning and validate selected
entries as executable. Preserve publish's documented rule that built-in and
extraction sources are unaffected, and do not overload availability with
executability.
### `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.
- Stage 7 refinement: the production HTTP client is stateless during requests
and passes its focused race suite. The fake appends to `Requests` without
synchronization, while the transcribe stage is contractually allowed to call
the client concurrently. The defect is therefore in fake fidelity at the
adapter/consumer seam, not evidence of a production HTTP-client race.
- Stage 8 resolution: transcribe intentionally invokes the interface
concurrently within a configured bound, and the required focused race command
reproduces the fake's request-slice race. Worker cancellation has a separate
correctness defect in `COR-018`; it does not make concurrent fake mutation
valid.
- Confirmation owner: Stage 12 should classify suite impact and the smallest
durable fake fix. Do not change the fake during this investigative stage.
- Stage 12 classification: confirmed. Protect request capture with the fake's
own synchronization and expose a safe snapshot accessor. This restores the
full race suite without changing legitimate production concurrency.
### `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.
- Stage 12 classification: confirmed. Inject the run-store/terminal-operation
boundary and retain one session-first terminal save failure plus one
resume-validation terminalization case. Repeating every `SaveRun` call order
would test private choreography rather than a distinct defect.
### `DUP-001`: session save duplicates the shared atomic JSON writer
- Category: duplicated mechanism candidate.
- Stage 11 classification: confirmed shared mechanism. Both manifest models
should serialize and normalize in their typed owner, then delegate atomic
replacement to the same `fileops` capability while preserving the session
saves context checkpoint and caller-specific error prefix. Coordinate this
with `DUP-005` and the directory-durability correction in `RSK-002`; do not
create a manifest-generic persistence interface.
- 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.
- Review history: Stage 3 established the shared filesystem guarantees; Stage
11 confirmed that caller error context can remain in wrappers over one
mechanism.
### `DUP-002`: publish reconstructs the canonical run-manifest path
- Category: duplicated path-ownership candidate.
- Stage 11 classification: confirmed duplicated ownership. Publish should use
the canonical artifacts path helper or receive the already-derived path.
Keep the remote `manifest.json` spelling explicit as a publication protocol
constant; only local canonical path construction moves to its existing owner.
- 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.
- Remediation boundary: publish may receive the canonical path or call the
artifacts helper; preserve the explicit remote relative name separately as a
protocol constant.
### `DUP-003`: sibling-temp download and install mechanics are repeated across restore and prepare
- Category: duplicated mechanism candidate.
- Stage 11 classification: confirmed shared mechanism. Add one narrow
destination-confined sibling-temp acquisition/install capability, or compose
a shared sibling-temp creator with the durable install primitive. Object
selection, download validation, force/conflict decisions, cache records,
manifest-last ordering, and reports remain in restore, audio, and prepare.
- Evidence: restore's `downloadObjectToSiblingTemp` plus
`InstallDownloadedTempFile`, audio's `downloadObjectAtomic`, and prepare's
previous-cache loop each create a sibling directory/temp file, close it,
download through `ObjectStore`, clean failure, and rename-install. The callers
legitimately differ in conflict, cache, content-validation, input-record, and
report policy. `storage.DownloadObjectToTemp` is a separate system-temp
inspection primitive. All install variants inherit `RSK-002`'s durability
requirements.
- Realistic risk: a future sync, permission, cancellation-cleanup, no-follow, or
download-size fix reaches restore but not prepare/audio, creating different
guarantees for the same remote-to-canonical transition. Conversely, sharing
the entire workflows would incorrectly merge caller policy.
- Remediation boundary: leave validation, conflicts, cache, manifest-last, and
reporting in their current owners. Coordinate with `DUP-001`/`RSK-002` rather
than adding another incomplete atomic writer.
### `DUP-004`: configuration repeats lexical relative-path policy
- Category: duplication/ownership candidate.
- Stage 11 classification: rejected as a shared-mechanism extraction. Exact
comparison shows config deliberately rejects every `..` segment, including
`a/../b`, while `pathsafe.NormalizeRelativeDestination` accepts and cleans a
non-escaping occurrence. Config also preserves field-specific errors. A
parameterized shared validator would hide the stricter accepted language for
only three small callers; keep this policy explicit and test its language.
- Evidence: `internal/config.validateRelativeSafePath` independently checks
absolute paths, cleaned dot/traversal forms, and separators already represented
by `internal/pathsafe` primitives, while adding config-specific field/error
context. This is not currently a behavior defect and identity/root validation
has deliberately different rules.
- Realistic risk: new artifact fields can be accepted by one validator and
rejected by another, multiplying the path-policy drift behind `COR-002` and
future artifact configuration work.
- Rejection boundary: do not collapse identifier, filesystem-root, artifact
relationship, or strict configuration path language into one generic
validator.
### `DUP-005`: adapter packages repeat atomic byte-write mechanics
- Category: duplicated mechanism candidate.
- Stage 11 classification: confirmed shared mechanism and the preferred owner
for the `DUP-001` family. Consolidate byte replacement in `fileops`, including
permission, cleanup, destination safety, and directory-sync guarantees;
callers wrap errors with protocol context. YAML serialization, context/state
policy, and output validation remain outside the low-level primitive.
- Evidence: `internal/adapters/subprocess.WriteFileAtomic` and WhisperX's
unexported `writeFileAtomic` independently implement same-directory temp-file
creation, write, file sync, close, chmod, rename, and failure cleanup. Fakes
and generated-YAML writers use the subprocess copy. `internal/fileops` owns a
third equivalent mechanism for application files. All three omit the
directory sync identified by `RSK-002`.
- Realistic risk: the durability or symlink-safe destination repair reaches the
canonical fileops path but leaves adapter outputs/configuration with weaker
guarantees, or adapter copies drift in permissions and cleanup behavior.
- Remediation boundary: preserve caller context/error wrapping and keep YAML
serialization and protocol output validation local. Coordinate with
`DUP-001`, `DUP-003`, and `RSK-002` rather than creating another utilities
package.
### `DUP-006`: singleton transcript stages repeat manifest-first resolution policy
- Category: duplication/ownership candidate.
- Stage 11 classification: confirmed duplicated policy. Route the three
singleton source wrappers through typed `artifacts.ResolveSessionArtifact`
identities, retaining only stage-specific provenance/guidance adapters.
Preserve the existing content validation and keep plural raw-transcript
discovery separate. This also removes current drift over whether a missing
manifest candidate is returned or treated as unavailable.
- Evidence: `discoverMergedTranscript`, `discoverProcessedTranscript`, and
`discoverNormalizedTranscript` each scan one producer's manifest outputs,
trim/resolve candidate local paths, deduplicate/sort, stat candidates, select
one, and fall back to a canonical transcript path. Their output kinds,
producer records, fallbacks, and ambiguity messages differ, but the policy
skeleton is nearly identical. The artifact registry/resolver already owns a
manifest-first/canonical-fallback abstraction used by render and later stages.
- Realistic risk: restored-path handling, content validation, ambiguity, or
provenance changes are applied to the registry resolver and one discovery
helper but not the others, producing stage-specific source selection drift.
- Remediation boundary: retain explicit stage wrappers only for provenance and
guidance over `artifacts.ResolveSessionArtifact`. Keep plural raw-directory
discovery separate and do not create a generic stage framework.
### `DUP-007`: extraction resume and catalog hydration duplicate bundle-evidence policy
- Category: duplication/ownership candidate.
- Stage 11 classification: confirmed duplicated policy. `artifacts` should own
one typed extraction-bundle evidence proof whose result distinguishes absent,
obsolete, unsafe, and valid evidence. Resume maps that proof to rerun/error
lifecycle decisions; catalog hydration keeps its all-or-none fail-closed
mapping. Confinement-before-read and exact identity/checksum ordering remain
visible named proof steps, not a generic validation framework.
- Evidence: `extractStage.ValidateResume` and
`ArtifactCatalog.HydrateExtractionArtifacts` independently reconstruct the
producer bundle, receipt identity, exact configured source/index set,
contracts, Notarius provenance, confinement and symlink rules, regular-file
shape, and checksums. They have separate segment, metadata, receipt,
contract/provenance, component, and payload helpers. Some difference is
intentional: resume distinguishes obsolete evidence from unsafe errors and
can rely on a prior JSON-valid checksum, while catalog treats the record as
untrusted and fails closed without returning errors.
- Realistic risk: a new identity field such as the direct input digest required
by `COR-020`, a contract compatibility change, or a path-safety repair is
enforced by resume but not catalog (or vice versa). Extraction can then be
skipped as reusable while its consumers refuse it, or a downstream catalog
can expose evidence resume would reject.
- Remediation boundary: the `internal/artifacts` evidence validator must accept
explicit current definitions and return typed evidence
reasons; resume should map missing/obsolete versus unsafe reasons, while the
catalog retains all-or-none fail-closed hydration. Preserve canonical bundle
identity, root confinement before reads, exact index/source count,
contract/provenance checks, no-follow regular files, checksums, and catalog's
JSON validity. Do not move lifecycle decisions into `fileops`.
### `DUP-008`: runtime catalog bootstrap policy is repeated across three consumers
- Category: duplication/ownership candidate.
- Stage 11 classification: confirmed duplicated bootstrap policy. Extract one
deterministic definition-registration function for built-ins, configured
definitions, and extraction definitions/evidence. Analyze, publish, and
operator helpers then apply their own explicit executable selection, disk
availability, publication filtering, and rendering policies.
- Evidence: `buildAnalyzeRuntimeArtifactCatalog`,
`buildPublishRuntimeArtifactCatalog`, and `buildHelperArtifactCatalog` each
create a catalog, register built-ins, translate Scriptorium configuration to
configured definitions, register extraction definitions, and conditionally
hydrate extraction evidence. Analyze and publish additionally repeat local
configured-output path resolution/availability loops. Their final policies
intentionally differ: analyze applies executable selection and reuses only
non-executable outputs; publish needs every existing configured source before
applying output selection; operator helpers primarily render identities.
- Realistic risk: a new built-in/source family, extraction evidence rule, or
configured-definition field is registered in one consumer and omitted from
another. Analyze can then accept a source that publish/status cannot render,
or publication can expose availability analyze classifies differently.
- Remediation boundary: extract only common deterministic
registration/definition bootstrap, returning a catalog that callers enrich
with explicit availability/executability policy. Coordinate extraction proof
mechanics with `DUP-007` but do not collapse resume, analyze reuse, publish
selection, and operator rendering into one mode-heavy builder.
### `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.
- Stage 12 classification: confirmed and narrowed. Put unsafe identity and
symlink-ancestor cases at their low-level policy owners, retain one
destructive and one writer composition case, add a durability interaction
case at the injectable filesystem capability, and add stale/release failure
plus one concurrent assembled runner case. Do not repeat the same ancestry
table at every caller.
### `TST-004`: publish protocol tests do not preserve prior current state or exercise recovery
- Category: test-suite sufficiency candidate.
- Evidence: focused publish tests strongly cover successful upload contents,
pointer-last order, and absence of a pointer call after output/current-
manifest failures. Cleanup tests cover ordinary commit metadata and path
effects. They do not seed a prior pointer/manifest pair, inspect readability
between the last two calls, retry a partial publish, model an upload accepted
with an error response, reload workspace-cleanup metadata, retry cleanup, use
symlink archive entries, or coordinate concurrent lock mutations.
- Realistic risk: tests can continue proving “pointer was not advanced” while a
failed attempt has already made the prior commit unreadable (`COR-004`), or
can prove directories disappeared without detecting lost cleanup evidence
and retry obligations (`COR-006`/`COR-007`). The same suite would not prevent
partial fixes to `COR-005` or `RSK-005`.
- Confirmation owner: Stage 12 should add the smallest stateful publish fake
with barriers/version semantics and one behavior-level case per distinct
invariant. Prefer extending current order/cleanup fixtures over duplicating
all source-family tables, which already have good focused coverage.
- Stage 12 classification: confirmed. One versioned object-store fake should
prove prior-pair readability across the manifest-before-pointer window and
failure, accepted-with-error ambiguity, retry, cleanup evidence durability,
and concurrent mutation. Existing source-family tables remain the stronger
protection for selection and payload composition.
### `TST-005`: restore tests omit committed-snapshot and partial-transition invariants
- Category: test-suite sufficiency candidate.
- Evidence: the restore suite has strong happy-path, lexical traversal,
ordinary conflict/force, typed missing state, cache, previous-cache, report,
and workflow coverage. Run mismatch is tested only on the shared helper with
validation enabled. No assembled case covers restore/status mismatch,
uncommitted/stale prefix objects, remote generation changes, forced directory
conflict, plan-before-lock changes, partial forced overwrite followed by a
runner, same-size audio replacement, foreign absolute output paths, custom
publish destinations, or optional readiness. Failure coverage centers on an
invalid second manifest and lock conflict rather than every distinct durable
boundary.
- Realistic risk: `COR-008` through `COR-011` and `RSK-006` through `RSK-008`
can remain or be partially repaired while broad restore tests stay green;
several current tests explicitly encode size-only audio and prefix-wide scope
without tying those choices to the stronger authority invariants.
- Confirmation owner: Stage 12 should add one stateful package-level behavior
case per root risk, reuse the publish fake/version barriers from `TST-004`, and
consolidate caller rendering matrices. Avoid a mock for every mechanically
identical download boundary; retain the existing manifest-invalid case as the
representative pre-rename validation failure.
- Stage 12 classification: confirmed. Reuse the stateful store and barriers
from `TST-004` for one package-level case per committed-scope, generation,
conflict/partial-transition, identity, and recovery root. Keep the existing
manifest-invalid case; do not mock every download.
### `TST-006`: injected runner environments can split configuration authority
- Category: test-seam fidelity candidate.
- Evidence: `executeStages` receives both a resolved `cfg` argument and an
optional injected `stage.Env`. It assigns `env.Config = cfg` only when the
injected field is nil. With different non-nil values, layout, selected
artifacts, and default stores derive from `cfg`, while secrets, adapter
defaults, locks, and stage execution consume `env.Config`. Production passes
no injected environment, and current tests generally use the same pointer by
convention rather than an enforced invariant.
- Realistic risk: a unit test can pass while exercising different stage enablement,
credentials, paths, or timeouts from the manifest/layout under test, obscuring
a production composition regression or creating an impossible fixture.
- Confirmation owner: Stage 12 should inventory intentional divergent fixtures
and either reject differing configs, always rebind to the resolved config, or
replace the dual input with a constructor that makes ownership singular.
- Stage 12 classification: confirmed. No intentional divergent fixture was
found. One app composition test should prove that injected collaborators
cannot introduce a second configuration authority; production should always
bind the environment to the resolved configuration.
### `TST-007`: adapter tests omit adversarial liveness and output-trust boundaries
- Category: test-suite sufficiency candidate.
- Evidence: focused adapter tests cover successful invocations, direct-child
timeout, override-value tail redaction, HTTP retry/status/cancellation and
malformed JSON, ordinary S3 not-found, and normal/invalid subprocess outputs.
They do not cover descendant termination, inherited-secret or on-disk log
redaction, non-HTTP URL schemes, streaming/cancelable request production,
symlink/non-regular/oversized outputs, response-body close observation, or
valid and non-progressing multi-page S3 responses. Fakes generally append
requests without synchronization and materialize valid placeholder outputs;
only the WhisperX fake is currently called concurrently in production-shaped
tests.
- Realistic risk: fixes for `COR-016`, `RSK-011` through `RSK-014`, and
`EFF-002` can be incomplete while broad adapter coverage remains green, and a
fake can make a stage appear to validate output that the real adapter would
reject or race under the real concurrency contract.
- Confirmation owner: Stage 12 should add the smallest boundary test per root
risk, share process/file/HTTP fixtures where the behavior is mechanical, and
retain protocol-specific argument/schema assertions. It should classify
automatic fake materialization by the stage risks it protects rather than
mechanically rewriting every fake.
- Stage 12 classification: confirmed. Add one focused case per process-tree,
inherited/raw secret, URL scheme, streaming cancellation, bounded regular
output, response closure, and pagination-progress root. Retain exact protocol
argument/schema assertions; they are observable contracts, not brittle mock
choreography.
### `TST-008`: ordinary-stage tests omit transition, cancellation, and output-authority boundaries
- Category: test-suite sufficiency candidate.
- Evidence: focused prepare and transcript-stage tests strongly cover ordinary
sources, manifest-first fallback, deterministic concurrency/order, adapter
errors, invalid schemas/reports/bounds, disabled behavior, diagnostics,
run-local paths, and canonical materialization. They do not cover removing
the final previous requirement, repeated explicit audio, pre-canceled or
mid-dispatch transcription, a valid alternate adapter-returned output path,
or failure while materializing the second of multiple validated outputs. One
prepare test actively requires stale previous state to survive.
- Realistic risk: `COR-017` through `COR-019`, `ARC-006`, and the stage-side
reach of `RSK-013` can remain or receive partial fixes while broad normal
stage tests pass. The existing race command is also unusable until `TST-001`
is repaired.
- Confirmation owner: Stage 12 should add behavior tests at the narrowest owner:
one prepare transition, duplicate-input validation, barrier-controlled
transcribe cancellation, and one cross-adapter output-authority contract.
Reuse shared safe-output acquisition tests from `TST-007`; do not duplicate
every schema error or stage fixture already covered.
- Stage 12 classification: confirmed. Add exactly those transition,
duplicate-input, barrier-cancellation, requested-path, and representative
multi-output partial-materialization cases. Safe output shape/size stays at
the shared owner from `TST-007`; existing schema tables remain sufficient.
### `TST-009`: extraction tests omit direct-input identity and assembled late-failure boundaries
- Category: test-suite sufficiency candidate.
- Evidence: focused extraction coverage is strong for process/receipt/index
validation, required lanes, immediate and cross-invocation reuse,
configuration-value change, missing/tampered outputs, source/contract/
provenance mismatches, disabled/forced/failed lifecycle outcomes, explicit
consumers, and low-level promotion source replacement/no-replace behavior.
No case mutates the resolved trimmed transcript beneath a succeeded extract
record, so `COR-020` is invisible. Stage tests stop at an injected promotion
error and do not exercise a failure after the durable directory has installed;
resume has one outside-root error but no assembled ancestor/root replacement
case. Configured-lane size is also untested at the acquisition boundary.
- Realistic risk: the direct-input repair, orphan-bundle policy, unsafe-versus-
obsolete mapping, or `RSK-013` lane bound can be incomplete while all current
extraction/lifecycle tests pass. Conversely, copying every adversarial
`fileops` case upward would add redundant suite friction.
- Confirmation owner: Stage 12 should add the direct-input lifecycle regression,
one deterministic post-install failure/residue assertion if a seam can expose
it without private choreography, and one representative assembled unsafe-root
case. Put the size case at the shared bounded-reader owner. Reuse the existing
downstream invalidation and catalog fixtures rather than creating a broad new
end-to-end framework; coordinate manifest-persistence faults with `TST-002`
and destination ancestry with `TST-003`/`COR-003`.
- Stage 12 classification: confirmed with one rejection. Add the direct-input
lifecycle regression and one assembled unsafe-root case; keep lane size at
the shared bounded reader. Reject a dedicated post-install orphan-residue
assertion because an unadvertised orphan is not authority and exposing the
point would test private choreography. Persistence and ancestry remain owned
by `TST-002` and `TST-003`.
### `TST-010`: analyze tests omit effective-selection and optional built-in boundaries
- Category: test-suite sufficiency candidate.
- Evidence: analyze, catalog, artifact-policy, configuration, previous-
requirement, publish, and command tests strongly cover ordinary source
success, required/optional absence for four source families, generated/reused
dependencies, successful lexical order, cycles, unavailable dependencies,
selection propagation, local-only previous resolution, and publish filtering.
They do not cover missing optional normalized/trimmed/Markdown built-ins,
execution of a selected disabled artifact, prerequisite planning for that
effective selection, multiple simultaneous dependency errors, or any
behavioral use/rejection of accepted input `artifact`/`path` fields. The
previous-guidance test asserts only the malformed command fragment.
- Realistic risk: fixes for `COR-021` through `COR-024`, `RSK-015`, and
`ARC-007` can be partial while each narrow package suite stays green. A broad
end-to-end matrix would duplicate strong catalog/source tests and obscure
which boundary owns a failure.
- Confirmation owner: Stage 12 should add a built-in required/optional table at
analyze, one assembled selected-disabled previous-requirement workflow at app
or prepare/analyze composition, one deterministic multi-error dependency
case, exact actionable guidance, and either adapter propagation or strict
rejection for the passthrough fields after their contract is chosen. Reuse
existing fixtures for publish/source success and do not retest every
artifact-policy spelling.
- Stage 12 classification: confirmed. Add the optional-built-in table, one
selected-disabled assembled workflow, deterministic simultaneous-error
ordering, semantic actionable guidance, and the chosen passthrough behavior.
Existing source-family and publish tables remain the stronger protection and
should not be repeated end to end.
### `TST-011`: filesystem-secret test leaks process environment across repetitions
- Category: test-suite determinism defect.
- Stage 12 classification: confirmed by isolated reproduction.
- Evidence: `TestLoadSecretsFromConfigLoadsValidFiles` invokes the production
loader, which sets `NARRATIO_TEST_SECRET_A` and
`NARRATIO_TEST_SECRET_B`, but the test does not restore their prior state.
`go test -shuffle=on -count=3 ./...` failed with seed
`1786373771816345415`; an isolated same-seed, three-count invocation failed
on its second and third repetitions because the variables were already
present and correctly reported as preserved rather than loaded.
- Realistic defect and marginal value: leaked secrets make the package depend
on repetition/order and can conceal whether later cases loaded or preserved
values. Restore the exact prior environment state in cleanup, including
unset-versus-empty semantics. One cleanup repair protects the behavior; no
production change or broad environment fixture is warranted.
### `TST-012`: automation does not enforce repository validation before release
- Category: test-suite execution/automation risk.
- Stage 12 classification: confirmed.
- Evidence: `.woodpecker/release.yml` is tag-only and cross-builds/publishes
binaries. No repository automation runs `go test ./...`, `go vet ./...`, or a
normal build for pushes or proposed changes, and release publication is not
tied to a revision that passed those checks.
- Realistic defect and marginal value: a change or tagged revision can ship
despite breaking tests, static analysis, or a supported build. Add ordinary
test/vet/build validation and make release consume or repeat the validated
revision. Add race execution after `TST-001` is repaired at a frequency
justified by its roughly 54-second cost; reserve repeated shuffle runs for a
scheduled/audit diagnostic unless CI capacity supports them.
### `TST-013`: security-sensitive path and source parsers have no property fuzzing
- Category: test-suite sufficiency opportunity.
- Stage 12 classification: confirmed narrow addition.
- Evidence: no fuzz target exists. Deterministic tables cover known separators,
traversal spellings, source families, and remote/local mappings, but the
`pathsafe` and `artifactpolicy` boundaries accept attacker- or
configuration-controlled strings whose combinations are larger than those
tables.
- Realistic defect and marginal value: an unanticipated separator,
normalization sequence, or source token can escape a root, classify
inconsistently, or break a round trip. Seed the existing tables and assert no
panic, no root escape, normalization stability, and valid mapping round
trips. Do not blanket-fuzz standard YAML/JSON decoders or every typed manifest
wrapper; their Narratio-specific schema cases already have stronger tables.
### `TST-014`: configuration tests conflate loader, defaults, and validator ownership
- Category: test-suite redundancy and maintainability issue.
- Stage 12 classification: confirmed consolidation.
- Evidence: `config.TestLoadAndValidate` is roughly 949 lines with a large table
of repeated complete YAML documents followed by broad normalized/default
assertions and validator checks. It already uses semantic error substrings,
so exact prose is not the defect; fixture breadth and mixed ownership are.
- Realistic defect and marginal value: changing an unrelated required/default
field forces many validation fixtures to change and a failure does not
identify whether strict decoding, defaulting, or domain validation owns the
contract. Retain small strict-load cases, construct typed configurations for
validator tables, and keep one representative load/default/validate assembly
case. Those focused owners are the stronger remaining protection; do not
introduce a generic fixture framework.
### `TST-015`: assembled stage matrices duplicate focused behavior owners
- Category: test-suite redundancy and brittleness issue.
- Stage 12 classification: confirmed consolidation.
- Evidence: `TestStagesReturnExpectedMetadata` is a broad per-stage metadata
checklist that overlaps focused stage suites and incidental metadata, while
`TestAdapterBackedStageFailureMarksManifestFailed` repeats six adapters to
prove the runner's generic terminal-failure mapping. Similar broad success
assertions exist in `TestExecuteStagesPlaceholderSuccessUpdatesManifest`.
- Realistic defect and marginal value: a harmless metadata/default change
causes several layers to fail while real stage defects remain better
diagnosed by focused owners. Retain focused stage/adapter protocol and
behavior tests, one representative assembled success proving composition and
durable manifests, and one representative adapter failure proving generic
runner terminalization. Delete the per-stage checklist and repeated generic
failure rows only after those stronger protections are explicit.
### `SIM-001`: runner terminalization and persistence ordering lack a narrow owner
- Category: simplification candidate.
- Stage 11 classification: confirmed narrow simplification. Introduce a typed
terminal-failure operation that updates session authority first, then the run
audit ledger, and compounds persistence errors without hiding the stage
failure. Keep the running transition separate and visibly run-first, and
route resume-validation failure through terminalization. Do not extract a
generic lifecycle state machine.
- 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.
- Remediation boundary: use only the typed terminal failure operation described
above; Stages 3-10 established that broader transition extraction would hide
required ordering.
### `SIM-002`: extraction resume validation conflates distinct evidence decisions
- Category: simplification candidate.
- Stage 11 classification: merged into `DUP-007`. The shared typed evidence
proof is the smaller control flow: named ordered proof steps reduce resume
complexity while leaving lifecycle mapping in `ValidateResume`. A second
abstraction would duplicate that owner.
- Evidence: `extractStage.ValidateResume` spans 154 lines with cyclomatic
complexity 40 and cognitive complexity 53. It validates environment and
record state, producer identity, parsed/resolved configuration and
fingerprint, session layout, canonical bundle and symlink safety, receipt,
exact output set, contracts/provenance, and payload evidence in one function.
The branches are individually necessary, but the proof phases and the reason
each branch is obsolete versus unsafe are difficult to review as a whole.
- Realistic risk: adding `COR-020`'s input evidence or sharing catalog mechanics
can place an I/O check before confinement, accidentally turn an unsafe state
into an automatic replacement, or omit one exact-set/identity check.
- Merge boundary: `DUP-007` should extract only named evidence decisions or a
small typed proof result. Preserve visible
ordering: cheap manifest/producer and fingerprint rejection; canonical root
derivation and component confinement before filesystem access; receipt and
exact index/source identity; contract/provenance; then regular-file/checksum
evidence and one final completeness decision. Preserve every existing
non-resumable outcome and every unsafe error, and keep `ValidateResume` as the
lifecycle mapper rather than introducing a generic validation framework.
### `SIM-003`: analyze execution passes a wide context through repeated resolution branches
- Category: simplification candidate.
- Stage 11 classification: confirmed narrow simplification. Build a typed
analyze execution context containing immutable session/run paths, diagnostics,
manifest, catalog, and an indexed effective plan. Have each explicit source
branch return a typed resolution result containing path, provenance, optional
absence, or unavailability. Remove unused parameters and the uncalled helper;
retain distinct policy/guidance and visible dependency/output commit order.
- Evidence: `executeAnalyzeArtifact` spans 239 lines and accepts ten parameters
for environment, manifest, session paths/identity, run layout, plan,
transcript diagnostics, and mutable catalog state. `resolveScriptoriumInput`
adds six parameters and 84 lines/cognitive complexity 71; `inputName` and
`sessionDir` are unused there. It interleaves family dispatch, availability,
required/optional decisions, previous-cache provenance, and operator guidance.
`orderSelectedScriptoriumArtifacts` separately rebuilds selected sets and
graph indexes. `resolveInputPathForRead` has no caller.
- Realistic risk: another source family or guidance rule can bypass optional
semantics as in `COR-021`, while new per-run context widens signatures and
makes test fixtures construct states that production never uses. A generic
resolver abstraction would create the opposite risk by erasing intentionally
different source policies.
- Remediation boundary: use a small typed analyze execution context, an indexed
effective plan, and a resolution result carrying path,
optional absence, provenance, and typed unavailability. Keep policy dispatch
explicit and source-specific guidance close to its producer. Remove the dead
helper and unused parameters if confirmed; do not create a generic stage or
graph framework, and coordinate set authority with `ARC-007`.
### `SIM-004`: three private path-resolution helpers are dead
- Category: confirmed dead-code simplification.
- Evidence: production-only zero-inbound graph search followed by exact text
search found `stage.resolveInputPathForRead`,
`stage.resolvePublishSessionRoot`, and `app.resolveSessionConfigPath` only at
their definitions. Their active replacements are source-family resolution,
the publish session-path model, and `resolveSessionConfigPathWithCandidates`.
No interface, reflection, registration, test, or command path refers to them.
- Stage 11 classification: confirmed. Delete the three helpers when structural
remediation begins. Do not preserve wrappers for hypothetical callers inside
`internal` packages; their distinct fallback behavior otherwise suggests
authority that production does not use.
- Realistic risk and scope: low direct risk and tiny deletion scope, but the
unused fallbacks distract reviews of already-sensitive path authority and
made the analyze execution surface appear broader than it is. Existing
focused stage/app tests are sufficient after deletion.
### `COM-001`: dual-ledger save order and partial-failure policy lack rationale
- Category: comment/clarity candidate.
- Stage 11 classification: confirmed. After `SIM-001`, preserve one concise
why-comment at the transition owner: session state is resume authority, the
run manifest is the invocation audit ledger, running must be durable before
execution, and terminal disagreement must remain visible. A comment that
merely restates save order is insufficient.
- 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.
- Remediation boundary: prefer a named operation that makes the invariant
obvious; add a concise rationale only where code structure cannot.
### `COM-002`: successful no-output stages are documented as skips
- Category: comment/clarity candidate.
- Stage 11 classification: confirmed and broadened to absorb `ARC-002` and
`COM-004`. Publish, disabled render, and absent/no-executable analyze complete
successfully with no outputs; they are not durable self-skips and are reused
on ordinary reruns until forced. Documentation should reserve “self-skip” for
explicit `StageDispositionSkipped`, explain reconsideration, and describe
each no-output success directly.
- Evidence: `docs/internal/stage-publish.md` says disabled publish or run upload
can “self-skip.” The implementation returns zero disposition with skip
metadata, so both manifests record succeeded and later normal runs reuse that
success. True self-skip is a durable skipped outcome and is reconsidered by
the runner. Focused tests check returned metadata but do not make the durable
distinction obvious at the stage-document boundary.
- Realistic risk: an operator or maintainer can expect enabling publish and
rerunning normally to reconsider a skipped stage, or can change it to explicit
self-skip and unintentionally alter downstream/cleanup behavior. The `publish`
command currently forces execution, which mitigates the common explicit
operator workflow but not the terminology.
- Remediation boundary: state the durable outcome and reconsideration behavior
precisely; no production lifecycle change is indicated.
### `COM-003`: Audita's adapter contract still says its real adapter is unimplemented
- Category: stale comment candidate.
- Stage 11 classification: confirmed stale comment. Remove the TODO. If
`ARC-005` leaves a non-obvious constructor/request boundary, replace it with a
rationale comment stating that constructor configuration is static and the
request carries invocation data; do not restate argument construction.
- Evidence: `internal/adapters/audita/runner.go` carries a TODO to implement a
real subprocess/service adapter, while `SubprocessRunner` is production
composed, documented, and covered by extensive invocation tests.
- Realistic risk: a maintainer treats the adapter as placeholder-only, bypasses
the existing implementation, or leaves genuinely missing notification work
confused with already completed Audita work.
- Remediation boundary: remove the stale TODO and, only if useful, replace it
with rationale about static constructor settings versus invocation request
data. No production behavior change is needed.
### `COM-004`: render documentation calls a durable success a skip
- Category: comment/clarity candidate.
- Stage 11 classification: merged into broadened `COM-002`; it is the same
lifecycle-language defect and needs no separate implementation or comment.
- Evidence: `docs/internal/stage-render.md` says disabled render “skips with
stage metadata.” The implementation returns a zero-disposition result, so the
runner records succeeded with no outputs and an ordinary later invocation
reuses that success. Enabling render after that outcome requires force under
the documented global lifecycle. This is intentionally different from
extraction's explicit self-skip and from trim's successful copy output.
- Realistic risk: an operator expects enablement to be automatically
reconsidered, or a maintainer changes render to `StageDispositionSkipped` and
unintentionally alters downstream lifecycle behavior.
- Merge boundary: broadened `COM-002` describes successful no-output execution
and the force consequence directly; no production behavior change is
indicated by Stage 8.
### `COM-005`: analyze documentation omits one source family and durable no-op consequences
- Category: documentation/clarity candidate.
- Stage 11 classification: confirmed in part and merged in part. Add extraction
to the source/catalog descriptions and align executable-set wording with
`ARC-007`. The successful no-output/force consequence is owned by broadened
`COM-002`. Do not document the inert `artifact`/`path` fields as working
passthrough until `COR-024` is repaired.
- Evidence: `docs/internal/stage-analyze.md` lists built-in, prepared,
configured, and previous-session sources but omits supported
`narratio.extraction.<name>` inputs. It says missing configuration/no
executable artifacts “skips with metadata,” while the result has no skipped
disposition: both manifests record succeeded, publish's prerequisite is
satisfied, and ordinary later runs reuse that success until force. The same
document says the runtime catalog contains only built-ins and configured
artifacts despite extraction registration/hydration. Separately,
`docs/internal/artifacts.md` says executable means selected and enabled even
though current selection overrides enabled; that authority question remains
`ARC-007` rather than a wording-only repair.
- Realistic risk: operators omit usable extraction context or expect newly
configured artifacts to run without force; maintainers can change no-op
analyze into an explicit self-skip and break pipeline/publish behavior. The
dedicated `analyze` command force-runs, which mitigates but does not correct
the contract.
- Remediation boundary: add extraction to catalog/source lists and use
`ARC-007`s effective-selection language. Broadened `COM-002` owns successful
no-output persistence and force wording. Do not describe the inert
`artifact`/`path` fields until `COR-024` chooses implement versus reject.
### `COM-006`: source-classification comment omits extraction sources
- Category: confirmed stale comment.
- Evidence: `artifactpolicy.ClassifySource` says it classifies built-in,
configured, or previous-session configured IDs, but its production branch
also recognizes `narratio.extraction.<name>` and returns
`SourceKindExtraction`.
- Stage 11 classification: confirmed. Update the doc comment to name extraction
sources, because the list documents a supported compatibility vocabulary.
Keep the why-comments in `run_control` and the notification placeholder: the
former accurately records the explicit force/freshness limitation, and the
latter accurately identifies the missing transport already owned by
`ARC-004`.
- Realistic risk and scope: low implementation risk and one-line scope, but the
stale exported comment can cause new consumers to duplicate classification or
reject a supported source family.
## 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 | rejected as broad abstractions at Stage 11 | Thin command wrappers, typed manifest models, distinct protocol constructors, and deliberately simple fakes share syntax rather than policy. Atomic byte writing is the narrow shared mechanism retained in `DUP-001`/`DUP-005`. |
| Coverage percentages | rejected as standalone findings | Stage 12 used them only to revisit consequential branches and high-coverage duplication; the final test assessment sets no numeric target. |
| Production fan-in/fan-out leaders | rejected as standalone Stage 11 findings | Stable owners such as configuration load/validate, canonical path helpers, manifest transitions, subprocess launch, and command dispatch should have many callers. Ambiguous method names and interface dispatch inflate graph rollups; caller tracing found no new ownership inversion. |
| `previouscache.BuildPlan` complexity 22/38 | rejected as a simplification finding at Stage 11 | Its visible branches preserve required/optional absence, remote-current validation, candidate existence, and deterministic ordering. A helper would have no narrower policy owner; remote transfer cost is instead measured under `EFF-001`. |
| Direct scan/allocation-in-loop graph signals | rejected as a production efficiency source at Stage 11 | Production filtering found no direct flagged occurrence. High transitive depth was composition/test propagation, and Stage 10 already rejected tiny artifact-set sorting as immaterial. |
| Three private zero-inbound path helpers | confirmed as `SIM-004` | Exact text search found only their definitions and no registration/reflection seam; current production paths use newer typed/configurable owners. |
| Relative-path validators have similar lexical checks | rejected as `DUP-004` | Config rejects any `..` segment while the shared normalizer accepts non-escaping cleanup. Preserving that stricter language explicitly is clearer than a mode-heavy helper. |
| Source classifier comment lists every family | confirmed as `COM-006` | The exported comment omits the implemented extraction branch and compatibility spelling. |
| Direct module dependencies can be replaced by the standard library | rejected at Stage 11 | Each direct dependency owns an active S3, YAML, or native no-replace platform contract; `go mod why -m` resolved all six. |
| 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” | architecture candidate merged into `COM-002` at Stage 11 | Run action/status already distinguishes idempotent skip, executed self-skip, and ordinary successful no-output execution. The behavior is coherent; publish/render/analyze documentation must use those exact terms. |
| 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; confirmed as `ARC-003` at Stage 11 | The snapshot is necessarily precommit and current-state loaders use actual pointer identity, but the shared remote/local field remains ambiguous. Derive commitment from the pointer or split precommit/local metadata without a post-pointer upload. |
| 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. |
| Known-field YAML decoding makes configuration strictly single-document | confirmed as `COR-012` | Known fields are enforced in the first document, but the second decode treats a successfully decoded trailing document as acceptable instead of requiring EOF. |
| Any parseable duration is executable | confirmed as `COR-013` | Several adapters and stage parsers require positive timeouts, and the WhisperX constructor rejects a negative retry delay, while shared config validation checks syntax only. |
| Populated S3 fields are a compatible implicit backend selector | confirmed as `COR-014` | The documented backend field is operator authority. Silently selecting S3 after an unknown spelling hides invalid configuration and makes validation disagree with construction. |
| `--previous-session-id` is only a conditional consistency hint | confirmed as `COR-015` | The option is described and modeled as an expected identifier. Ignoring it when the session omits the field defeats the only CLI-provided expectation. |
| Successful temporary remote-session download has caller-owned cleanup | confirmed as `RSK-009` | The helper transfers ownership on success, but no command caller removes the file after config consumers finish and ephemeral provenance can be persisted. |
| A trusted secrets directory makes link/type checks unnecessary | confirmed as `RSK-010` | Deployment ownership is not validated by the process and mistakes are realistic. The loader follows working links and admits non-regular entries without a bound. |
| All adapter constructors are unconditional expensive/external work | rejected at Stage 6 | Default HTTP/subprocess wrappers do no connection or process work at construction. Notarius, object storage, and remote locks are conditional on selected behavior that needs them. |
| Repeated adapter constructors and single-stage commands require immediate consolidation | rejected as standalone Stage 6 findings | Constructors translate distinct protocols, and single-stage dispatch already funnels through `runSingleStageCommand`. Similar shape does not establish duplicated policy. |
| Repeated filesystem secret scans are an efficiency defect | rejected as material at Stage 6 | Object-store construction can repeat a deterministic bounded directory scan, but the scan is small, preserves secret-before-adapter ordering, and no material latency or external cost was established. |
| Notification settings are consumed because notify succeeds | confirmed as `ARC-004` at Stage 7 | Production success is supplied by a no-op sender regardless of backend/recipient; no transport contract or composition path consumes the accepted public fields. Non-placeholder values must be rejected/reserved until a real integration exists, or a transport must be specified and composed. |
| Injectable runner `Env` always represents the resolved config | retained as `TST-006` | Production composition does, but a non-nil injected `Env.Config` is retained and can differ from the explicit config used by other runner setup. |
| Configuration relative-path validation is distinct from shared lexical safety | consolidation rejected as `DUP-004` at Stage 11 | Config rejects every `..` segment while `pathsafe` accepts non-escaping cleanup, and it owns field-specific diagnostics. A mode-heavy shared validator would obscure that deliberate policy. |
| `exec.CommandContext` bounds a whole external-tool process tree | confirmed as `RSK-011` | The audited toolchain kills only `cmd.Process`; Narratio establishes no process group/job or descendant cleanup. |
| Sensitive override-tail redaction makes subprocess diagnostics secret-safe | confirmed as `RSK-012` | Raw logs are unfiltered and inherited sensitive values are absent from the tail redaction set, so the architecture invariant does not hold. |
| Successful exit plus JSON/non-empty validation establishes a safe subprocess result | confirmed as `RSK-013` | Ordinary adapters use unbounded, link-following reads/stats; Notarius's bounded regular-file checks show the stronger boundary is both necessary and locally expressible. |
| A non-nil S3 continuation token guarantees pagination progress | confirmed as `RSK-014` | The adapter never compares tokens, so a malformed repeated token produces unbounded requests and duplicate accumulation. |
| Similar subprocess adapter argument builders should be consolidated | rejected at Stage 7 | The shared launcher already owns common resource/process mechanics; protocol flags, schemas, exit mapping, and validation differ materially and remain clearer in their adapters. Only atomic byte writing is retained as `DUP-005`. |
| Audita request fields are authoritative per invocation | rejected; constructor authority confirmed as `ARC-005` at Stage 11 | Polish has no per-invocation override behavior and the real runner already uses constructor state. Remove redundant static request fields; keep invocation paths and modules in the request. |
| Adapter fake request slices all require synchronization now | rejected as a blanket Stage 7 finding; WhisperX confirmed under `TST-001` | Transcribe legitimately calls WhisperX concurrently and the required Stage 8 race command reproduces its fake's slice race. Other fakes still have sequential production callers; Stage 12 should assess future fidelity from actual consumers. |
| Zero previous requirements means no previous state is managed | confirmed as `COR-017` | Prepare's current test treats the directory as untouched, but publish later uploads it independently of current manifest inputs. Removing the final requirement must remove or exclude stale managed bytes. |
| Repeating an explicit audio path is harmless deterministic input | confirmed as `COR-019` | Prepare sorts and records the duplicate twice, while the manifest-first transcribe boundary rejects it. Configuration, producer, and consumer must share one duplicate policy. |
| Context cancellation necessarily becomes a transcribe error | confirmed as `COR-018` | Workers and dispatch silently stop on the derived context, while completion checks only a recorded adapter/validation error. Zero or partial results can therefore be returned as success. |
| Adapter result paths have one run-local authority rule | confirmed as `ARC-006` at Stage 11 | Stages own run-local destinations; adapters must return no path or the exact requested path. Current redirects have no production use case and would broaden filesystem authority. |
| Transcript discovery helpers are intentionally all distinct | plural raw discovery rejected; singleton policy confirmed as `DUP-006` | Raw inputs need directory enumeration and plural ordering. Merged/processed/normalized singletons should use typed manifest-first/canonical resolution from the artifact owner. |
| Similar ordinary-stage `Run` methods need a common framework | rejected at Stage 8 | Adapter sequencing, schemas, optional reports, disabled behavior, diagnostics, and multi-output failure order differ materially. Existing run-local helpers are the correct narrow shared mechanism. |
| A valid immutable extraction bundle proves it represents the current transcript | confirmed as `COR-020` | Bundle checksums prove only promoted output integrity. The fingerprint and resume validator never resolve or hash the current direct transcript, so an internally valid old bundle can be reused after those input bytes change. |
| Same-path external Notarius changes are automatically observable | documented force limitation, not a separate Stage 9 defect | Fingerprinting paths cannot prove executable, config, profile, prompt, module, reference, environment, provider, or runtime contents. Configuration/transitive changes are explicitly assigned to `--force`; operations should extend that wording to same-path executable replacement. Direct Narratio transcript identity is separately confirmed in `COR-020`. |
| Every durable promoted bundle is advertised or reusable | rejected at Stage 9 | Promotion establishes immutable bytes, not success. A later sync/checksum/result-persistence failure may leave a uniquely named orphan bundle, but session-manifest success is advertisement authority and neither resume nor catalog scans incidental directories. |
| Resume and catalog bundle checks are merely coincidental similarity | rejected; duplicated evidence policy confirmed as `DUP-007` at Stage 11 | They repeat one exact-set, identity, contract/provenance, path, type, and checksum proof. A typed proof can be shared while lifecycle and fail-closed mappings remain separate. |
| Extraction resume complexity justifies a generic validation framework | rejected; `SIM-002` merged into `DUP-007` at Stage 11 | The shared evidence proof supplies named ordered decisions without hiding security ordering or moving lifecycle policy into a framework. |
| Optional analyze inputs behave uniformly across source families | confirmed as `COR-021` | Final, final-trimmed, and both Markdown built-ins return hard producer-guidance errors before the caller can honor `required: false`; other missing optional families are omitted. |
| Explicit selection is only a filter over enabled artifacts | rejected; explicit override confirmed as `ARC-007` at Stage 11 | CLI help and catalog tests establish selection as a one-invocation override. A typed effective set must make validation and prerequisite planning follow that authority; publish retains separate filter semantics. |
| Successful topological order implies deterministic dependency validation | confirmed as `RSK-015` | Edges and ready nodes are sorted for success, but the first unavailable-dependency preflight returns from unsorted selected-set map iteration. |
| Existing configured output needs a prior analyze success to be reusable | rejected as the current contract | Non-executable configured artifacts intentionally use canonical non-empty files without manifest provenance so disabled/operator-prepared dependencies can be reused. Freshness is operator-owned; misleading enabled-but-unselected provenance stays under `ARC-007`. |
| Previous-session resolution can fetch remotely during analyze | rejected at Stage 10 | Analyze uses only manifest-backed and filesystem `previous/` paths. Remote discovery/download belongs to restore/prepare composition, and a boundary test proves the object store is not called. Selection can omit that earlier planning under `COR-022`. |
| Input `artifact` and `path` are adapter passthrough fields | confirmed as `COR-024` | Strict config accepts and documents them, but no production read, request field, CLI argument, or generated invocation field exists; values are silently discarded. |
| Repeated analyze resolution branches justify a generic resolver framework | rejected; narrow `SIM-003` confirmed at Stage 11 | Source families deliberately differ in authority, provenance, optional absence, and repair guidance. A typed context/result and indexed effective plan reduce width without erasing those policies. |
## Open Decisions, Accepted Risks, And Limitations
The audit establishes the unsafe current behavior and smallest safe interim
boundary; it does not make product or platform choices that require deployment
knowledge. These decisions are inputs to the remediation sequence, not reasons
to leave the current defects implicit.
| Decision family | Owner | Required safe interim boundary | Related findings |
| --- | --- | --- | --- |
| Run-scoped manifest identity and abandoned invocation presentation | App/manifest maintainers | Recompute or validate one identity unit; terminalize every handled error. Status may label genuinely abandoned records without changing session progress authority. | `COR-001`, `RSK-001`, `SIM-001` |
| Cross-platform confined mutation, durable replacement, and local locking | Fileops/artifacts owners with supported-platform maintainers | No path-based destructive operation may follow an untrusted ancestor; unsupported safe primitives must fail closed. Lock release failure must be observable. | `COR-003`, `RSK-002`, `RSK-003` |
| Identifier compatibility | Config/artifacts owners with operators | Inventory deployed spellings before enforcing one opaque-segment grammar; meanwhile no constructor may return an escaped namespace. | `COR-002` |
| Runtime permissions and filesystem-secret directory guarantees | Packaging/operations owner with app/fileops | Use private defaults and no-follow, bounded regular-file acquisition unless a documented service ownership/ACL contract proves a stronger equivalent. | `RSK-004`, `RSK-010` |
| Scriptorium `artifact`/`path` semantics | Product owner and Scriptorium integration owner | Reject non-empty unsupported fields or remove them from the public contract until exact wire semantics are specified. | `COR-024` |
| Remote commit representation, lock activation, and committed restore scope | Publish/restore/artifacts owners with storage capability owner | Preserve the old readable commit until one final atomic selection; restore and status must validate the same selected identity. Do not emulate conditional writes with an unsafe load/replace race. | `COR-004`, `COR-008`, `RSK-005`, `ARC-003` |
| Restore local commit/recovery, content identity, canonical paths, and previous-source mapping | Restore/artifacts/audio/previouscache owners | Keep the manifest last, revalidate under the lock, never report unresolved conflicts as success, and prefer workspace-relative typed identity over foreign absolute paths or size-only reuse. | `COR-009` through `COR-011`, `RSK-006` through `RSK-008` |
| Previous-session expectation and storage backend language | CLI/config/product owner | Preserve the documented flag as a strict expectation unless a compatibility review deliberately changes it; accept only explicitly implemented backend values. | `COR-014`, `COR-015` |
| Remote session configuration lifetime | App configuration owner | Return an owned temporary handle/lifetime whose cleanup occurs after all command consumers finish; never persist ephemeral provenance as a durable path. | `RSK-009` |
| Notification product requirement | Product and integration owner | Reject or clearly reserve non-placeholder settings until a provider contract and production sender exist. A no-op must be an explicit operator choice. | `ARC-004` |
| Process trees, diagnostic redaction, and output size limits | Shared subprocess/adapter owners with platform and operations input | Kill and wait the owned tree, never persist known secret values, and use bounded no-follow regular-file acquisition. Choose limits per external contract rather than one global constant. | `RSK-011` through `RSK-013` |
| External Notarius dependency identity | Notarius integration and operations owners | Continue to require/document `--force` for same-path external/transitive changes unless a version/digest contract is introduced; direct Narratio input bytes must still join the fingerprint. | `COR-020` |
| Validation automation and race cadence | Repository maintainers | Every change and release revision must receive normal test/vet/build validation. Add race automation after `TST-001`; choose per-change versus scheduled cadence from measured capacity, not by omitting the check entirely. | `TST-001`, `TST-012` |
### Intentionally accepted risks
- The two manifest files do not form one atomic transaction. This is accepted
because the session manifest is the sole progress authority and a run
manifest is an invocation audit record. Remediation must test every durable
disagreement boundary and terminalize handled errors; it need not introduce
a distributed transaction.
- An uncatchable process or host death may leave an old invocation record
`running`. After handled errors are terminalized, that residual historical
inaccuracy is accepted provided startup/status can distinguish or document it
and the session manifest continues to drive conservative retry. It is not
acceptable for controlled errors to leave the same ambiguity.
- Restore remains an incremental operation without rollback. The cost and
portability of a full workspace transaction are not justified by current
evidence. This acceptance does not waive `RSK-006`: planning must be
revalidated under the lock, partial state must be diagnosable/retryable, and
the old manifest must not silently authorize replaced bytes.
- Same-path changes to external Notarius executables, profiles, prompts,
modules, references, environment, and provider behavior remain an
operator-forced invalidation boundary. Narratio cannot infer all transitive
external state without a new integration digest contract. Its direct trimmed
transcript is not part of this acceptance and remains `COR-020`.
- A configured artifact that is deliberately non-executable may be reused from
its canonical non-empty file without prior analyze-manifest provenance.
Freshness is operator-owned for that workflow. Explicit selection and
prerequisite planning must still use one effective-set authority under
`ARC-007`/`COR-022`.
- The normal test suite remains serial where process-global fixtures exist; no
blanket `t.Parallel` conversion is justified by its roughly 3.5-second
runtime. Repeated shuffled execution may be scheduled rather than run on
every change, provided `TST-011` is fixed and ordinary CI remains mandatory.
### Evidence limitations
- Dynamic validation ran on Linux/amd64 with Go 1.26.5. macOS and Windows were
cross-build/reasoning targets only; platform-specific no-follow, directory
sync, process-tree, and lock choices require focused native validation.
- No live S3 service, paid API, production subprocess, credentialed operation,
deliberate power loss, or destructive external workflow was exercised. The
audit used source reasoning, existing deterministic fakes, temporary
filesystems, loopback HTTP, and helper subprocesses as required by policy.
- Crash durability, upload accepted-with-error behavior, remote conditional
writes, and deployment ACL/umask protection remain environment-dependent.
Their absence was confirmed in the current contracts, but exact provider and
filesystem failure rates were not estimated.
- The moderate code graph excludes documentation, examples, and the command
entry point. Those artifacts were reviewed directly with repository tools;
graph metrics were never used alone to confirm a finding.
- This report is pinned to the implementation revision in the audit identity.
Later commits through Stage 13 alter audit documentation only. Any production,
test, canonical-contract, example, or dependency change invalidates the
affected evidence and must trigger focused re-audit before remediation uses
the conclusions.
## Completed-Stage Evidence
### Stage 0
- Contracts and records: development guide, audit plan and sequence, all policy
documents, repository/branch/toolchain state.
- Graph evidence: refreshed moderate index at exact HEAD; architecture,
interface, complexity, similarity, fan-in, and `Execute` call trace queries.
- Commands: every baseline command listed above; Go/package/file/test and
automation inventories.
- Candidates: `TST-001`; metric signals assigned to later owners.
- Explicit no-finding conclusion: no production dependency reversal into
`internal/app` was found in the package import inventory.
- Limitation disposition: the graph excludes the executable entry point, which
was verified directly; the race failure is owned by Stages 8 and 12 and does
not prevent read-only audit work.
### Stage 1
- Contracts reviewed: architecture, testing and documentation policy; internal
overview and every focused internal document; CLI, configuration,
operations, and every integration contract.
- Code/evidence reviewed: canonical registry and stage declarations; all
modeled interfaces; production import graph; application dispatch trace;
explicit self-skip usages; interrupted-state usages; focused test ownership
references.
- Outputs: package/interface ownership, area coverage, stage contract,
lifecycle, cross-boundary scenario, and preliminary risk-to-test matrices.
- Candidates: `ARC-001`, `ARC-002`, `RSK-001`; no candidate was confirmed from
mapping evidence alone.
- Explicit no-finding conclusion: the canonical stage order agrees across the
registry, internal overview, CLI, and operations contract.
- Follow-up: all unresolved behavior has a named owner in Stages 2-12; every
area and invariant has an implementation owner and intended test owner.
### Stage 2
- Contracts and code reviewed: planner and full/single-stage entry points;
`executeStages`, run-control and identity helpers; session/run manifest
models, creation, loading, validation, normalization, atomic persistence, and
every transition method; runner lock lifetime; focused internal manifest
documentation and Stage 1 matrices.
- Graph/source evidence: call traces into full and selected execution; all
identity-field consumers; manifest transition/save callers; status and
`last_error` usages; runner complexity and atomic-save similarity; complete
focused test-function inventory.
- Validation: `go test -count=1 ./internal/app ./internal/manifest` passed
(`internal/app` 0.708 s, `internal/manifest` 0.010 s; 1.60 s command wall
time). `go test -race -count=1 ./internal/app ./internal/manifest` passed
(`internal/app` 45.842 s, `internal/manifest` 1.026 s).
- Conclusions: every lifecycle cell and dual-save boundary is recorded above;
scenarios 1 and 2 are resolved at runner level; lock acquisition/lifetime is
resolved and release mechanics assigned to Stage 3. Confirmed `COR-001` and
`RSK-001`; added `DUP-001`, `SIM-001`, `COM-001`, and `TST-002` for named
later owners.
- Explicit no-finding conclusions: canonical invalidation works at both first
and last stage and is independent of selected-plan width; stale transitions
intentionally retain diagnosis data while running/failure/skip clear it;
session authority makes all enumerated disagreement states retry or reuse
conservatively; the runner's two skip forms are durably distinguishable.
### Stage 3
- Contracts and code reviewed: architecture/path/security policy; internal
artifacts, workspace, manifest, operations, and troubleshooting contracts;
all canonical local/S3/cache constructors; pathsafe and artifactpolicy;
built-in, configured, extraction, previous, and current-state resolution;
local-store layout/copy/lock code; atomic write/copy/download installation;
directory promotion and platform-specific no-replace/directory-sync support;
restore/audio/previous download callers; manual and post-publish cleanup.
- Graph/source evidence: canonical-helper and direct-mutation inventories;
callers of path/key, artifact-resolution, fileops, current-state, previous-
cache, and lock helpers; focused test-function inventories; fallback text
search for non-code policy and direct OS mutation sites where graph results
were insufficient.
- Validation: `go test -count=1 ./internal/artifacts
./internal/artifactpolicy ./internal/pathsafe ./internal/fileops` passed (0.98
s wall time). `go test -race -count=1 ./internal/artifacts
./internal/fileops` passed (2.25 s wall time).
- Conclusions: canonical owners and artifact resolution order are recorded
above; lexical normalization handles mixed separators, traversal, absolute,
and drive forms when callers invoke it; extraction source trust and source-
side promotion are strong; low-level helpers correctly consume explicit
destinations. Scenario 10 is resolved: live contenders are excluded, while
stale sentinel/release behavior is unsafe operationally.
- Findings: confirmed `COR-002`, `COR-003`, `RSK-002`, `RSK-003`, and
`RSK-004`; added `DUP-002` and `TST-003`; refined `DUP-001` with the shared
durability gap.
- Explicit no-finding conclusions: current-state helpers have typed missing
cases and support strict identity checks when callers request them; artifact
resolution is deterministic and matches its documented source-specific
validation; promotion preserves an
existing/concurrent destination and rejects unsafe source trees; temporary
files/trees are cleaned on ordinary failures; unsupported promotion
platforms fail before creating a durable bundle; fileops does not infer
higher-level policy.
- Follow-up: Stages 4-10 should cite the shared confinement/durability roots for
concrete callers. Stages 5 and 10 must decide whether ordinary manifest and
previous-cache checksum trust is sufficient. Stages 6, 11, and 12 own
compatibility, simplification, and durable regression coverage respectively.
### Stage 4
- Contracts and code reviewed: architecture publish/cleanup/force invariants;
focused publish, storage, workspace, manifest, operations, CLI, and
troubleshooting contracts; publish stage prerequisites, artifact catalog and
output resolution, selection, locks, run/previous collection, every upload,
current snapshot/pointer generation, storage upload semantics, effective
remote-lock loading/mutation, current-state discovery/identity validation,
status/restore entry interpretation, runner terminal ordering, automatic
cleanup, cleanup target validation, and manual session/global/cache cleanup.
- Graph/source evidence: call traces from publish and lock commands into
storage; exact source for current-state readers, cleanup gates, lock mutation,
and runner persistence; complete focused publish/cleanup/lock/current-state
test inventory; fallback source/text inspection for the generic stage method,
S3 `os.Open`, and non-code contracts where graph modeling was insufficient.
- Validation: `go test -count=1 ./internal/stage ./internal/app
./internal/artifacts ./internal/adapters/storage` passed (`internal/stage`
0.440 s, `internal/app` 0.749 s, `internal/artifacts` 0.028 s,
`internal/adapters/storage` 0.019 s; 1.86 s command wall time).
- Conclusions: publish plans fully before writing and uploads sorted run files,
configuration-ordered outputs, sorted previous files, current manifest, then
the pointer last. Output family, required/optional, selection, lock, exclusion,
retry, existing-object, and force behavior is recorded above. Remote-current
and automatic-cleanup truth tables resolve scenarios 5 and 7 at every
boundary; manual cleanup is correctly a separate explicit authorization.
- Findings: confirmed `COR-004`, `COR-005`, `COR-006`, `COR-007`, and
`RSK-005`; added `ARC-003`, `COM-002`, and `TST-004`; resolved publish's
portion of `ARC-002` and corrected Stage 2's cleanup-retry conclusion.
- Explicit no-finding conclusions: pointer is unequivocally the final upload
and intended current marker (Stage 5 later confirmed incomplete reader
enforcement as `COR-008`); failure before current-manifest publication
preserves any prior current pair; successful retry is idempotent by
unconditional replacement; static and loaded remote locks, including required
outputs, survive force; disabled extraction is safe because only explicit
extraction rules resolve it; manual clean does not need publish metadata;
storage correctly remains policy-neutral and consumes explicit paths/keys.
- Follow-up: Stage 5 reused and corrected the remote-current reader truth table.
Stage 11 owns metadata vocabulary/duplication decisions;
Stage 12 owns the smallest stateful commit, cleanup-retry, symlink-read, and
remote-lock concurrency tests.
### Stage 5
- Contracts and code reviewed: architecture and testing policy; restore,
workspace, storage, artifact, manifest, operations, troubleshooting, and CLI
contracts; remote-current discovery, restore planning/classification,
execution/reporting, ordinary and audio download installation, cache/spool
materialization, previous-artifact requirement collection/planning, prepare
consumption, and status/validate readiness reporting.
- Graph/source evidence: callers and exact options for current-state loading;
restore plan/action and manifest-last traces; storage/list/download and
temporary-install paths; audio cache-key and validation paths; previous-cache
candidate resolution and consumer traces; focused test-function inventories.
Direct source and text inspection covered non-code contracts and implementation
details the graph could not distinguish.
- Validation: `go test -count=1 ./internal/app ./internal/previouscache
./internal/audio ./internal/artifacts ./internal/adapters/storage` passed
(`internal/app` 0.759 s, `internal/previouscache` 0.009 s, `internal/audio`
0.012 s, `internal/artifacts` 0.025 s, `internal/adapters/storage` 0.008 s;
1.87 s command wall time).
- Conclusions: restore authority, complete remote-to-local mapping, deterministic
ordering, action/force/dry-run behavior, manifest-last execution, report and
every failure boundary are recorded above. Audio cache/spool identity and
previous-session required/optional, candidate, and fallback policies are
explicit. Shared mechanics are separated from restore, prepare, status, and
validate caller policy.
- Findings: confirmed `COR-008`, `COR-009`, `COR-010`, `COR-011`, `RSK-006`,
`RSK-007`, `RSK-008`, and `EFF-001`; added `DUP-003` and `TST-005` with named
later owners.
- Explicit no-finding conclusions: relative target construction and ordering are
deterministic and lexically confined; force does not bypass identity,
traversal, or lock validation; dry-run creates no durable restore state;
ordinary download failures remove the active temporary file; the storage
adapter remains policy-neutral; prepare intentionally overwrites its private
previous-cache destination while restore classifies existing destinations.
- Scenario disposition: scenario 4 confirms the pointer is not sufficient
authority because restore/status omit run validation and restore reads mutable
prefix objects; scenario 6 confirms partial incremental replacement, old-
manifest authority before the final install, and restored state despite a
later report failure. Rollback and automatic retry are intentionally absent.
### Stage 6
- Contracts and code reviewed: development guide, all repository policy,
configuration/CLI/example and internal adapter contracts; process entry and
dispatch; pipeline/campaign/session discovery; strict YAML loading, defaults,
resolution, templates, and every validation family; secrets loading; runner,
adapter, object-store, lock, and remote-session composition.
- Graph/source evidence: exact snippets and caller/data-flow traces for loaders,
defaults, `Resolve`, `Validate`, duration and storage checks, command selection,
remote fallback, secret loading, `executeStages`, conditional constructors,
temporary downloads, and injected environments. Direct source/text inspection
covered YAML tags, help text, documentation, maintained examples, and lifecycle
details the graph could not distinguish.
- Validation: `go test -count=1 ./internal/config ./internal/app ./cmd/narratio`
passed (`internal/config` 0.099 s, `internal/app` 0.818 s, CLI has no test
files; 2.11 s command wall time). `go vet ./...` passed (1.88 s command wall
time).
- Conclusions: explicit/default selection precedence, empty-value semantics,
relative-path anchoring, resolution and validation order, every operator field
family and runtime consumer, secret propagation, enabled/disabled adapter
composition, resource ownership, and maintained example validity are recorded
above. No live credentials or external services were required.
- Findings: confirmed `COR-012`, `COR-013`, `COR-014`, `COR-015`, `RSK-009`,
and `RSK-010`; added `ARC-004`, `DUP-004`, and `TST-006` with named later
owners.
- Explicit no-finding conclusions: pointer-valued defaults preserve explicit
false/zero; explicit empty modules remain distinct from omission; Notarius
paths are anchored to the pipeline; external work is conditionally composed;
default client/runner construction opens no closeable resource; secret values
are not directly persisted or logged by config/composition code; Stage 7
later found that child-produced logs and inherited-secret error tails violate
the broader invariant under `RSK-012`. Maintained examples use non-secret
placeholders and are executable under representative sessions. Repeated
bounded secret scans and protocol-specific constructors do not establish
material efficiency or duplication defects.
- Documentation/example disposition: config defaults and maintained example
structure match implementation. Notification backend/recipient settings are
the material drift because production always uses a no-op sender; `ARC-004`
assigns the transport/documentation decision to Stage 7.
### Stage 7
- Entry revision: `0920062` (`Document configuration and composition audit
findings`). Commits since the pinned audit revision modify audit
documentation only, so implementation/test evidence remains pinned to the
identity recorded above.
- Contracts and code reviewed: development guide, audit sequence, all repository
policy, adapter/internal/storage/audio documentation, and every production
file under `internal/adapters`, `internal/audio`, `internal/logging`,
`internal/contracts`, and `internal/artifactmodel`; production stage/app
callers were traced for each boundary.
- Graph/source evidence: scoped architectures, symbol inventory, caller/callee
and code searches for every adapter interface and external operation, then
exact source inspection of HTTP request/retry handling, shared subprocess
launch and diagnostics, all subprocess argument/config/result adapters, S3
pagination/body/file handling, audio temporary installation, notification
composition, fakes, shared models, and focused tests. Local Go 1.26.5
`CommandContext` documentation/source confirmed direct-process kill behavior.
- Validation: `go test -count=1 ./internal/adapters/... ./internal/audio
./internal/logging ./internal/contracts ./internal/artifactmodel` passed (all
13 package results passed; 1.62 s command wall time).
`go test -race -count=1 ./internal/adapters/... ./internal/audio` passed (all
10 package results passed; 21.61 s command wall time). No live service,
credential, destructive, or paid operation was exercised.
- Resource conclusions: every HTTP response, S3 body, opened local file, retry
timer, direct subprocess, log descriptor, and temporary download has an
explicit normal/error release path. Context reaches HTTP/S3 operations and
direct child processes; gaps are multipart pre-copy cancellation
(`EFF-002`), descendant termination (`RSK-011`), and pagination progress
(`RSK-014`). No adapter-owned goroutine/channel or process-level shutdown
resource exists.
- Findings: confirmed `COR-016`, `RSK-011`, `RSK-012`, `RSK-013`, `RSK-014`,
`EFF-002`, and prior candidate `ARC-004`; added `ARC-005`, `DUP-005`,
`COM-003`, and `TST-007`, and refined `TST-001` as a concurrent-fake defect
rather than a production HTTP-client race.
- Explicit no-finding conclusions: transport/SDK/process types and protocol
retry policy do not leak into stages; ordinary response bodies/files/timers
are closed; retry status classes and output install ordering match WhisperX;
Notarius performs bounded regular-file/root/lane validation; arguments and
generated configs are deterministic and contain credential names/presence,
not values; S3 callers own sorting/policy and not-found adaptation is correct;
shared artifact models have stable tags and non-lossy slice conversion; the
logger constructor owns no resource. Protocol-specific adapter builders are
justified rather than a consolidation target.
- Later-stage assignments: Stage 8 owns transcribe worker/fake behavior,
ordinary stage output consumption, notify lifecycle, and Audita override
intent. Stage 11 owns `ARC-005`, `DUP-005`, and `COM-003`; Stage 12 owns the
risk-based additions/consolidation in `TST-001` and `TST-007`.
### Stage 8
- Entry revision: `0a772e0` (`Document external adapter audit findings`).
Commits since the pinned audit revision modify audit documentation only, so
implementation/test evidence remains pinned to the identity recorded above.
- Contracts and code reviewed: development guide, Stage 8 sequence, all
repository policy, overview, focused prepare/transcribe/merge/polish/
normalize/trim/render documents, WhisperX/Seriatim/Audita/Scriptorium
integration contracts, and the complete production vertical slices in
`internal/stage`, with supporting config, audio, previous-cache, publish, and
runner callers where they establish the scoped input/lifecycle boundary.
- Graph/source evidence: scoped stage architecture and symbol inventory,
manifest/config/caller traces, and exact source inspection of audio selection
and materialization, previous hydration/clearing, every stage `Run`, run-local
helpers, transcript discovery/validation, result path use, declarations,
disabled branches, adapter fakes, and all focused test names/cases. Similarity
was evaluated only after stage-specific differences were recorded.
- Validation: the exact focused normal command passed all seven packages (1.34
s command wall time). `go test -race -count=1 ./internal/stage
./internal/audio` failed in `internal/stage` at the already registered
WhisperX fake request-slice race from
`TestTranscribeStageTranscribesPreparedAudio`; `internal/audio` passed (1.85
s command wall time). This is the expected `TST-001` limitation and no new
race signature appeared. No live adapter, credential, destructive, or paid
operation was exercised.
- Contract conclusions: prepare's ordinary source selection, stable copying,
provenance, sorting, S3/local exclusivity, and non-empty previous hydration
are coherent. Transcribe has unique filename-derived speakers, bounded
concurrency, exact returned-path identity, deterministic successful ordering,
and all-or-nothing handling of recorded adapter failures. Transformations are
manifest-first, run-local, schema/report aware, and preserve diagnostic/output
classification. Disabled trim is successful copy processing; disabled render
is successful no-output execution whose later enablement requires force.
- Findings: confirmed `COR-017`, `COR-018`, and `COR-019`; added `ARC-006`,
`DUP-006`, `COM-004`, and `TST-008`; refined `ARC-001`, `ARC-002`, `ARC-005`,
`RSK-013`, and `TST-001`. Scenario 8 is resolved through worker aggregation
and subprocess-backed ordinary stages.
- Explicit no-finding conclusions: sorted current inputs and transformation
results are deterministic; basename collisions from different sources fail;
transcribe rejects redirected results; recorded adapter/schema failure does
not canonically materialize a successful subset; subprocess-backed stages are
synchronous and propagate adapter errors; trim debug render, logs, and
generated configs remain diagnostics; a generic stage framework is not
justified. Current production transformation adapters return their requested
output paths, so inconsistent result-path authority remains architectural,
not a present production data defect.
- Later-stage assignments: Stage 10 later resolved analyze's `ARC-002` outcome.
Stage 11 owns `ARC-001`, `ARC-005`, `ARC-006`, `DUP-006`, `COM-004`, and the
narrow shared-owner decisions. Stage 12 owns the risk-based additions in
`TST-001`, `TST-007`, and `TST-008`.
### Stage 9
- Entry revision: `57cac5d` (`Document ordinary stage audit findings`). Commits
since the pinned audit revision modify audit documentation only, so
implementation/test evidence remains pinned to the identity recorded above.
- Contracts and code reviewed: development guide, exact Stage 9 sequence, all
repository policy, overview, extract/artifacts/manifest/analyze/publish
internal documents, Notarius integration, configuration, operations,
troubleshooting, CLI run behavior, and the complete extraction production
slice through configuration, composition, stage execution/resume, adapter,
promotion, manifests, catalog, analyze, and publish.
- Graph/source evidence: scoped stage architecture, extraction cohesion and
hotspot inventory, caller/callee and symbol searches for configuration,
fingerprint, execution, promotion, resume, and consumers, followed by exact
source inspection of every production branch and focused test case.
`ValidateResume` measured cyclomatic complexity 40/cognitive complexity 53;
that metric was used only after its proof decisions were manually traced.
- Validation: the exact required command, `go test -count=1 ./internal/stage
./internal/artifacts ./internal/fileops ./internal/adapters/notarius
./internal/app`, passed all five packages (1.94 s command wall time). No live
Notarius process, credential, destructive, remote, or paid operation was
exercised.
- Contract conclusions: run-local receipt/log/staging state is distinct from
the complete no-replace durable bundle; promotion is distinct from
session-manifest advertisement; resume is distinct from fail-closed catalog
hydration. Configured lanes require exact contracts and provenance and are
the only selectable outputs. Missing/obsolete evidence reruns extraction,
unsafe filesystem evidence stops it, and unadvertised bundles are never
discovered incidentally. Scenario 3 is resolved with explicit external/
transitive force limits and a missing direct-input identity defect.
- Findings: confirmed `COR-020`; added `DUP-007`, `SIM-002`, and `TST-009`; and
refined `ARC-001` and `RSK-013`. The same-path Notarius dependency limitation
is documented and classified rather than duplicated as another defect.
- Explicit no-finding conclusions: output-map iteration is sorted; required
lane descriptors are exact and rejection-aware; index and lane paths are
confined; configured lanes are regular, non-empty JSON; checksums are taken
before and after promotion; destination installation is atomic no-replace on
supported platforms; contract/provenance survives explicit publication;
unconfigured lanes and the index are neither selectable nor implicitly
uploaded; disabled extraction self-skips coherently; process/receipt/
prepromotion failures advertise no result; a generic validation or stage
framework is not justified.
- Later-stage assignments: Stage 10 later completed the analyze source-family
and publish-selection matrix. Stage 11 owns `ARC-001`, `DUP-007`, and `SIM-002`.
Stage 12 owns the risk-based additions/consolidation in `TST-009` and the
extraction reach of `RSK-013` alongside `TST-002`, `TST-003`, and `TST-007`.
### Stage 10
- Entry revision: `083decc` (`Document extraction audit findings`). Commits
since the pinned audit revision modify audit documentation only, so
implementation/test evidence remains pinned to the identity recorded above.
- Contracts and code reviewed: development guide, exact Stage 10 sequence, all
repository policy, overview, analyze/artifacts/publish/manifest internal
documents, Scriptorium integration, configuration, CLI, operations, and
troubleshooting; complete production flow through config validation,
selection propagation, previous-requirement consumers, artifact policy and
catalog/resolvers, analyze planning/execution/materialization, Scriptorium
adapter requests, lifecycle persistence, and publish source filtering.
- Graph/source evidence: scoped stage architecture and hotspot inventory;
searches, snippets, and call/data-flow traces for source classification,
catalog registration/availability, configured selection, previous
requirements, dependency ordering, execution, resolution, and publish/helper
catalog consumers; followed by exact production/test inspection. The main
execution helper measured 239 lines/ten parameters, source resolution 84
lines/cognitive complexity 71, and dependency ordering 86 lines/ten loops;
these were treated as review signals only after behavior was traced.
- Validation: the exact required command, `go test -count=1 ./internal/stage
./internal/artifacts ./internal/artifactpolicy ./internal/config
./internal/adapters/scriptorium ./internal/app`, passed all six packages.
The command completed in 2.01 s wall time.
No live Scriptorium process, credential, remote, destructive, or paid
operation was exercised.
- Contract conclusions: registered, executable, available, generated, and
reused states are distinct. Selection currently overrides enabled state;
non-executable configured files can be reused; successful order is lexical
and dependency-correct; generated outputs become immediately available to
later plans. All source-policy families were traced, and previous-session
consumption is local-only. Publish selection filters configured-source rules
only and does not trigger analyze or suppress built-in/extraction rules.
Missing/no-executable analyze is durable successful no-output execution.
- Findings: confirmed `COR-021`, `COR-022`, `COR-023`, `COR-024`, and
`RSK-015`; added `ARC-007`, `DUP-008`, `SIM-003`, `COM-005`, and `TST-010`;
refined `ARC-001`, `ARC-002`, and `RSK-013`; and resolved scenario 9.
- Explicit no-finding conclusions: unknown source/dependency identities and
enabled cycles are configuration errors; explicit selected cycles are caught
at runtime; successful plan/output/metadata order is deterministic; a missing
unselected dependency cannot be silently executed; configured disk reuse is
an intentional non-manifest freshness contract; extraction hydration remains
fail-closed; previous resolution performs no remote call; publication cannot
execute artifacts; partial failed analyze output is not advertised as stage
success; no material dependency-order efficiency defect or generic resolver/
graph framework is justified.
- Later-stage assignments: Stage 11 owns `ARC-001`, `ARC-002`, `ARC-007`,
`DUP-008`, `SIM-003`, and `COM-005`, coordinated with earlier candidates.
Stage 12 owns the risk-based additions/consolidation in `TST-010` and the
analyze reach of `RSK-013` alongside existing safe-output test candidates.
### Stage 11
- Entry revision: `9cb9008` (`Document analyze dependency audit findings`).
Commits since the pinned audit revision modify audit documentation only, so
implementation/test evidence remains pinned to the identity recorded above.
- Contracts and code reviewed: development guide, exact Stage 11 sequence, all
repository policy, every accumulated `ARC`, `DUP`, `SIM`, `EFF`, and `COM`
entry, production callers for each candidate, direct dependencies, platform
implementations, TODO/FIXME/build-tag patterns, and existing benchmark
inventory.
- Graph/source evidence: production similarity, complexity, fan-in/fan-out,
loop-depth, direct scan/allocation-in-loop, change-coupling, zero-inbound, and
call-path queries followed by exact snippets and text search. Notable signals
were runner complexity 54/96, `previouscache.BuildPlan` 22/38,
`HydrateExtractionArtifacts` 17/25, analyze executions wide context,
identical 41-43-line atomic writers, three 30-34-line singleton transcript
resolvers, and three private zero-inbound helpers. Metrics were classified
only after their complete policy/caller paths were compared.
- Validation: `go test -count=1 ./...` passed all 22 packages in 3.22 s wall
time, and `go vet ./...` passed in 0.46 s. No benchmarks exist, so no invented
performance result is reported; `EFF-001` and `EFF-002` specify representative
byte/latency and memory/allocation measurements. No live adapter, credential,
remote, destructive, or paid operation was exercised.
- Structural conclusions: confirmed removal of the unused partial `IODecl`
interface, explicit-selection override authority, requested adapter output
path authority, constructor-owned Audita static settings, and a correction to
ambiguous remote/local commit metadata. Confirmed narrow shared owners for atomic
replacement, sibling-temp installation, canonical paths/singleton artifacts,
extraction evidence, catalog bootstrap, runner terminal failure, and analyze
execution context. Rejected shared config path validation and broad workflow,
resolver, graph, stage, adapter, or fake abstractions.
- Candidate disposition: `ARC-001`, `ARC-003`, and `ARC-005` through `ARC-007`;
`DUP-001` through `DUP-003` and `DUP-005` through `DUP-008`; `SIM-001` and
`SIM-003`; and `COM-001` through `COM-003`, the source/catalog portion of
`COM-005`, and new `SIM-004`/`COM-006` are confirmed. `ARC-002`, `COM-004`,
and the no-output portion of `COM-005` merge into broadened `COM-002`;
`SIM-002` merges into `DUP-007`; `DUP-004` is rejected after exact semantic
comparison. `EFF-001`, `EFF-002`, and `ARC-004` remain confirmed with sharper
remediation/measurement boundaries.
- Explicit no-finding conclusions: no direct production scan/allocation-in-loop
signal, material small-set sorting/copying issue, excessive adapter
initialization, removable direct dependency, unsupported-platform silent
fallback, generic framework opportunity, or additional concurrency/channel
defect was established. The accurate stale-detection and notification TODOs
remain rationale/future-contract markers; only the obsolete Audita TODO and
incomplete exported source-family comment are clarity findings.
- Later-stage assignments: Stage 12 owns only the risk-based `TST` inventory and
test-policy audit. Stage 13 owns prioritization, dependency ordering, and
accepted-risk decisions; it should treat the merged/rejected structural
dispositions above as resolved rather than reopening them from metric shape.
### Stage 12
- Entry revision: `f387222` (`Document maintainability audit conclusions`).
Commits since the pinned audit revision modify audit documentation only, so
implementation/test evidence remains pinned to the identity recorded above.
- Policy and scope reviewed: development guide, exact Stage 12 sequence, every
repository policy document, the complete risk-to-test ledger and prior
behavior-pass observations, all ten accumulated `TST` candidates, and the
repository's test/example/automation inventory.
- Graph/source evidence: 904 test-file functions, 749 `Test` functions, 95 test
files, no fuzz tests, no benchmarks, no `t.Parallel` use, test-size and
complexity hotspots, process-global environment/directory mutation,
time-dependent cases, test doubles, helper subprocesses, loopback HTTP, and
assembled workflow overlap. Exact source inspection followed the graph for
large/mixed-owner tests, secret loading, fakes, and release automation.
- Coverage diagnostic: `go test -count=1 -cover ./...` passed all packages in
3.49 s. Tested-package coverage ranged from 69.8% to 100%; `cmd/narratio`
reported 0% because it has no tests. Percentages were used only to revisit
weak consequential branches and high-coverage duplication. Manifest's low
end aligns with `TST-002`; logging's 100% and the notification placeholder do
not justify percentage-driven additions.
- Determinism validation: `go test -shuffle=on -count=3 ./...` failed in 4.71 s
with seed `1786373771816345415` because
`TestLoadSecretsFromConfigLoadsValidFiles` leaked two environment values. The
isolated same-seed, same-test, three-count command reproduced failure on
repetitions two and three, confirming `TST-011` rather than random flakiness.
- Race validation: `go test -race -shuffle=on -count=1 ./...` failed in 54.21 s
with seed `1786373816980315094`. The only race was the already recorded
WhisperX fake request-slice mutation in `TST-001`, reached by the bounded
concurrent transcribe test; every other package passed and no second
production race was observed.
- Final documentation-only verification: `go test -count=1 ./...` passed all
packages in 3.50 s and `go vet ./...` passed in 0.78 s. `git diff --check`
reported no whitespace errors.
- Suite conclusions: normal execution is fast, offline, credential-free, and
based on temp files, loopback services, or the current test binary. Current
focused tests strongly protect ordinary parsing, protocol, lifecycle, and
source-family behavior. Confirmed additions target durable disagreement,
destructive recovery, adversarial output/resource boundaries, cancellation,
effective selection, and direct input identity. Stateful object-store
behavior is preferable to additional call-recording mocks; protocol argument
assertions remain contractual.
- Candidate disposition: confirmed `TST-001` through `TST-008` and `TST-010`;
confirmed `TST-009` after rejecting its low-value orphan-residue subcase; and
added confirmed `TST-011` through `TST-015` for environment isolation,
automation, focused fuzzing, configuration-test ownership, and assembled-test
consolidation. Every proposed addition names its realistic defect and owner;
every consolidation names the focused/representative protection that remains.
- Explicit no-finding conclusions: no live network, paid-service, ambient
credential, fixed-port, oversized snapshot, golden-file, pervasive exact-
error, generic fixture-framework, blanket fake-synchronization, or general
parallel-test opportunity was established. Existing YAML/JSON tables do not
justify indiscriminate parser fuzzing, and bounded evidence did not establish
flakiness beyond the reproducible environment leak and known fake race.
- Later-stage assignment: Stage 13 owns only deduplication, ranking,
remediation ordering, accepted-risk decisions, and final audit closeout. It
should not reopen the risk-based test ownership and marginal-value decisions
recorded here without new evidence.
### Stage 13
- Entry revision: `14ef59a` (`Document test suite policy audit conclusions`),
with a clean worktree. The implementation, tests, examples, dependencies, and
canonical current-behavior documents are byte-identical to the pinned audit
revision; commits since it add only the audit plan, sequence, and ledger.
- Scope and policy reviewed: development guide, all architecture,
documentation, and testing policy, exact Stage 13 sequence, audit completion
criteria, every lifecycle/scenario/risk matrix, all 81 stable finding IDs and
their detailed evidence, the classification log, unresolved questions, and
completed-stage records.
- Revalidation evidence: current graph architecture and targeted ownership
searches covered the runner/manifest, path/fileops, publish/current-state,
restore/previous/audio, configuration/composition, adapters, ordinary stages,
extraction, analyze/artifact, and focused-test boundaries. A repository diff
from the pinned revision confirmed no affected source, test, contract,
example, or module change. The prior exact snippets, callers, tests, and
canonical contracts therefore remain current; no metric-only finding was
admitted and no confirmed root required downgrade.
- Reconciliation: 77 IDs remain confirmed. `ARC-002` and `COM-004` merge into
`COM-002`; `SIM-002` merges into `DUP-007`; `DUP-004` remains rejected;
`COM-005` remains independently confirmed only for its source/catalog
omission while its no-output wording is owned by `COM-002`. Rejected signal
themes remain in the classification log to prevent rediscovery.
- Final report additions: executive assessment; completed scenario and audit-
criteria matrices; positive conclusions; separate impact, likelihood,
confidence, and scope ratings for structural/test/clarity items; an
11-workstream dependency/risk-ordered remediation backlog; owned open
decisions; intentionally accepted residual risks; and environmental/evidence
limitations.
- Validation: all relative links in `audit-findings.md`, `audit-plan.md`, and
`audit-sequence.md` resolve; `git diff --check` passes. The full Stage 0
implementation baseline was not rerun because Stage 13 proved that no
implementation, test, dependency, example, or canonical contract changed;
Stage 12's final normal suite and vet results remain the latest validation.
- Exit conclusion: every audit-plan completion criterion and Stage 13
deliverable is satisfied. No production, test, example, dependency, or
current-behavior documentation change is included. The report is sufficient
to prepare remediation work without repeating discovery.