I read this as a read-only lifecycle audit of V14 r5 - I’ll trace the source, preseal, candidate, and rehearsal artifacts end to end, then return an evidence-backed PASS/FAIL without modifying or invoking live systems.


I read this as establishing the authoritative lifecycle contract before judging artifacts - I’ll now compare the V14 transaction plan, sealed target, registry shape, candidate qualification, and rehearsal claims against the controller implementation and safety tests.


I read this as checking the implementation rather than trusting PASS labels - I’ll trace the controller’s mutation/rollback order, host path ownership, service observations, ledger semantics, and how the rehearsal constructs its two-row clone.


I read this as validating rollback completeness at the filesystem boundary - I’ll inspect canonical weekly-file creation, snapshot coverage, postimage publication, and the exact preseal path inventory, including whether parent directories or credentials escape cleanup.


I read this as stress-checking crash recovery, not just in-process exceptions - I’ll compare reconstructed-host recovery with in-memory rollback state and verify whether startup smoke is genuinely offline and non-mutating.


I read this as closing the audit with independently checkable references - I’ll gather final line/JSON-pointer evidence for the active registry, immutable package state, and the specific untested crash windows before issuing the verdict.


I read this as a possible concurrent source drift during the audit - I’ll re-hash and diff the current source against the frozen preseal/candidate before relying on any line references.


I read this as freezing the final evidence snapshot - I’ll take one last read-only hash/state check so the verdict reflects the audit-close state rather than an earlier concurrent worktree revision.


I read this as a read-only lifecycle audit of V14 r5 - I traced the frozen controller, candidate, preseal, rehearsal, and live registry evidence end to end.

# Verdict: FAIL

Do not authorize V14 r5. The frozen controller does not safely complete abrupt-process recovery.

## Gate results

| Requirement | Result | Evidence |
|---|---|---|
| All rollback-created paths | **FAIL** | The five paths are sealed at `sealed-target.json#/weekly_authority/authority_created_paths_rollback`. The two canonical files are tracked only in volatile `ConcreteLiveHost._created_files`: frozen `controller-source/scripts/nutricoach_v150_concrete_host.py:106-108,294-312,355-360`. A reconstructed recovery host starts with an empty tuple, so crash rollback can leave `events.jsonl` and `.events.lock`. |
| `STOPPING` crash recovery | **FAIL** | A fresh host has `_preflight = None`, while rollback calls `verify_preflight()`: frozen `nutricoach_v150_concrete_host.py:106-108,190-202`; `nutricoach_v150_sealed_controller.py:121-129`. `execute_authorized()` calls `recover_pending()` before `_execute()` captures preflight. The result is `RECOVERY_REQUIRED`, not terminal `ROLLED_BACK`. |
| `COMMITTING` crash recovery | **FAIL** | `nutricoach_v150_sealed_controller.py:212-214` advances directly to `COMMITTED` when the ledger says `SUCCEEDED`; it performs no service observation, restart, successor identity check, or postimage check. |
| Service restart confirmation | **FAIL** | Ordinary rollback checks `service.running` at `nutricoach_v150_sealed_controller.py:112-120`, and normal success checks active/running/PID/ExecStart at `nutricoach_v150_host_operations.py:139-163`. The `COMMITTING` recovery branch bypasses both. |
| One-use handling | **FAIL** for lifecycle completeness | Replay prevention itself is sound: exclusive reservation and durable consumption are at `nutricoach_v150_sealed_authority.py:190-246`; rehearsal retries are denied at `rehearse_nutricoach_v150_v14.py:219-225`. However, reservation is durable before `RESERVED` phase publication (`sealed_controller.py:172-176`). A crash in that window leaves no phase; `PhaseJournal.recovery_required()` treats absent phase as terminal (`phase_journal.py:34-40`), while the reservation blocks every retry. |
| Startup smoke | **PASS** | Exact installed interpreter and scrubbed environment: `nutricoach_v150_runtime_ops.py:125-161`. It constructs registered capabilities and ticks every enabled owner: `nutricoach_v150_weekly_startup_smoke.py:48-87`. The success report includes `weekly_startup_smoke`. |
| Actual two-row shape | **PASS** | Live registry SHA-256 is `bd61ed95...660b29`, exactly matching `registry-shape.json#/registry_sha256`. Actual rows are disabled `task26_claim_20260818145508_1b96b23d` at registry lines 12-14 and enabled `pilot_20260820_01` at lines 236-238. Rehearsal copies the live registry at `rehearse_nutricoach_v150_v14.py:82-88` and asserts both exact projections at lines 137-159. |

## Why the recorded rehearsal PASS is insufficient

`task-v14r5-rehearsal/report.json` reports 13 PASS cases, but none is a crash/reconstructed-process case. The generator injects exceptions and rolls back using the same in-memory host at `rehearse_nutricoach_v150_v14.py:200-226`. It therefore preserves both `_preflight` and `_created_files`, masking the actual recovery defects.

The persisted rehearsal directory contains only `report.json`; no raw case roots remain to inspect canonical-file cleanup independently.

The two nominal crash tests have the same weakness:

- `tests/test_nutricoach_v150_v14_safety.py:227-242` captures preflight and recovers on the same host.
- Lines 245-260 use an already-running default service for `COMMITTING`, so the test would still pass if recovery never checked service state—which it does not.

## Binding and artifact state

- Frozen preseal closure: **318/318 hashes matched**.
- Frozen package inventory: **326/326 hashes matched**, with no writable or symlink entries.
- Candidate manifest SHA-256: `d5d508cc...51c330`, matching `sealed-target.json#/candidate_manifest_sha256`.
- Both wheel builds are byte-identical; sealed wheel hashes match.
- Ledger, execution root, and successor root were absent at audit close.
- Current worktree has diverged from frozen r5 in two closure files:
  - `scripts/nutricoach_v150_concrete_host.py`: current `2ec1b677...39ee6`, frozen `5a90b1a6...07cf11`
  - `tests/test_nutricoach_v150_v14_safety.py`: current `262ac99a...6529`, frozen `a6f58589...f2a3`

The detached execution remains bound to the frozen, defective closure—not the newer worktree content.

## Architecture options

1. **Recommended: one durable recovery manifest.** Before `STOPPING`, atomically persist preflight hashes, originally absent created paths, candidate identity, and expected service/postimage identity. Recovery reconstructs exclusively from this manifest. Low migration cost and directly testable with real subprocess termination.
2. **Per-mutation write-ahead journal.** Record every mutation and compensation durably. More extensible, but substantially greater coupling, ordering complexity, and failure surface than these fixed five paths justify.

The first design is the simplest architecture that closes the observed gaps. It must be resealed as a new candidate/preseal revision and tested with fresh-process `STOPPING`, post-migration, and `COMMITTING` recovery.

Audit was read-only: no files changed and no live execution, service command, network, Telegram, provider, or authorization was invoked.