I read this as a read-only V3 failure-semantics audit - I’ll trace the controller, tests, V2 receipt, TransactionGuard/durable/installer implementations, and live-target contracts, then return evidence-backed blockers and a minimum binary-observable fault matrix without modifying state.


The relevant chain is identified: the active work is the v1.5 live transaction rehearsal, with its V2 blocker receipt, and the predecessor safety model is the sealed v1.4 V7 package. I’m now narrowing the audit to those exact trees plus the current profile/runtime contracts.


The V3 test file already describes two intended repairs, but the implementation does not yet contain them: successor permission normalization and cleanup-failure recording/primary-error preservation. I’m validating the current RED boundary and then checking the lower-level durability and service-state gaps that those tests still do not cover.


I read this as resuming the interrupted read-only V3 audit - I’ll continue from the established evidence, re-read only the concurrently changed controller files, and finish with the blockers and minimum fault matrix.


I read this as completing the read-only V3 rollback audit - I traced the repaired source, V2 blocker, V7 precedent, durability layer, and live contracts, and derived the minimum pre-live fault matrix.

# Verdict: RED / NO LIVE EXECUTION

The immediate V2 permission failure has a source-level repair and its focused suite passes, but V3 is not sealed and still has critical recovery, durability, service-state, and migration correctness gaps.

## Evidence baseline

- V2 blocker receipt: `live-transaction-rehearsal-v2/blocker-receipt.json`
  - `PermissionError` under the copied read-only venv.
  - Rollback left the successor present and clone service stopped.
  - Explicitly records `secondary_unreached_config_mismatch: true`.
- V2 is formally rejected by `live-transaction-preseal-v3/rejected-v2.json`.
- No V3 package exists beyond that rejection receipt.
- Current source is not bound to V2:
  - Current `nutricoach_v150_concrete_host.py`: `7ab670fc...`
  - V2 manifest expected: `0eea5fd2...`
  - Current controller: `50f10291...`
  - V2 expected: `af5d59dc...`
  - Current test: `791e63f7...`
  - V2 expected: `ea4519de...`
- Current focused suite passed once read-only against disposable state: **26 passed**.

# Critical blockers

## P0-1: The live migration does not enable the actual weekly-operations contract

The exact live config contains zero occurrences of:

- `weekly_pilot`
- `weekly_operations`
- `channel_inbox`

Yet `ConcreteLiveHost.migration_apply()` only performs:

```python
replace(b"weekly_pilot: false", b"weekly_pilot: true")
```

at `nutricoach_v150_concrete_host.py:256-258`. This is a silent no-op on the current live config.

The runtime contract instead expects nested `nutrition_coaching.weekly_operations`; absent configuration remains inert in `nutrition_weekly_operations_config.py:138-147`. `configured_weekly_authority()` likewise returns `None` when that nested section is absent in `telegram_weekly_reminder.py:143-145`.

`post_fence()` checks only capacity and the unit runtime path, not:

- weekly configuration enabled;
- parseable runtime authority;
- authority/config digest agreement;
- loaded weekly schedule policy.

Therefore the controller can return success while weekly operations remain OFF. This corroborates the V2 receipt’s `secondary_unreached_config_mismatch`.

**Required RED test:** run against a byte clone of the exact live config. Success must be impossible unless `configured_weekly_authority()` returns the expected enabled config and authority. Binary observable: current code returns success while the parser returns `None`.

## P0-2: No real installed/loaded-runtime fence

The V7 predecessor:

- runs installed successor probes in `production.py:65-99`;
- runs OFF and ON probes;
- validates staged wheel identity;
- checks real service state in `production.py:223-237`;
- rewrites candidate-bound runtime credentials.

V3 instead:

- uses a synthetic `installed-wheels.json` in disposable tests;
- makes `off_smoke()` only parse config;
- checks only that the successor path appears in the unit;
- leaves the existing v1.3.2 `LoadCredential` files unchanged;
- never proves the running process loaded candidate `066a...`, either wheel, or the expected authority.

The current live drop-in still points to `dualcoach-v1.3.2-daily-checkin-rebound-first-customer-credentials`.

**Required RED test:** after start, expose a process/runtime probe reporting executable, imported module origins, wheel/RECORD identities, candidate digest, authority credentials, and weekly parsed state. Any predecessor identity must fail and roll back.

## P0-3: Service recovery trusts an in-memory boolean, not systemd

`SystemdService.running` starts as `True` and is changed only after successful subprocess returns (`nutricoach_v150_sealed_target.py:67-91`). There is no `systemctl show` state query.

Failure windows include:

- systemd completes stop, then the Python process receives `KeyboardInterrupt`/SIGTERM before `running=False`;
- start command returns but the unit immediately fails;
- systemctl reports success while `ActiveState` is not active/running;
- rollback start fails twice.

`RollbackGuard` then reasons from stale `running`, and its final postcondition is also only that boolean. Unlike V7 `SystemdService.active()`, no real service-state check exists.

**Required RED tests:** inject interruption after actual stop and immediate unit failure after start. Binary observable must be `ActiveState=active`, `SubState=running`, nonzero `MainPID`, expected `ExecStart`, and a fresh start timestamp. A boolean is not acceptable.

## P0-4: Crash/SIGKILL recovery is absent

Context managers consume `KeyboardInterrupt`, `SystemExit`, and ordinary exceptions, but cannot recover from SIGKILL, power loss, interpreter abort, or host restart.

The ledger records only reservation/consumption. There is no durable stage journal or recovery entrypoint capable of determining whether to:

- restart the predecessor;
- restore snapshots;
- remove a partial successor;
- complete reservation consumption.

A crash after stop can therefore leave the service stopped, the approval permanently reserved, and the next invocation refused.

**Required RED matrix:** kill a subprocess at every boundary from post-reservation through post-fence, then invoke a dedicated recovery path. Binary observables: exact original bytes/modes, no created paths, service active on predecessor, approval permanently consumed as failed, and no second mutation attempt.

## P0-5: Ledger durability can mask the primary error

`GlobalLedger.Attempt.__exit__()` directly calls `consume()` for every `BaseException` at `nutricoach_v150_sealed_authority.py:177`. If consumption fails, that exception replaces the operation/rollback primary error.

Additional durability gaps:

- reservation writes once and ignores the returned byte count (`:132`);
- reservation fsyncs the file but not its parent directory;
- consumption unlinks the reservation (`:153`) without a subsequent parent fsync;
- `atomic_write()` uses one unchecked buffered write (`:33`);
- cleanup `temporary.unlink()` can mask an earlier write/fsync/rename failure.

The V7 `DurableWriter` explicitly loops short writes, retries EINTR, fsyncs temporary/published/parent boundaries, and verifies readback (`upgrade_v7/durable.py`).

**Required behavior:** preserve the primary exception identity while attaching ledger/rollback failures structurally; leave a fail-closed one-use artifact whenever publication may have occurred.

## P0-6: Partial restore can restart a mixed contract

`restore()` restores entries sequentially. A failure on entry N stops restoration; `RollbackGuard` records one `snapshot_restore` failure, continues cleanup/reload, and starts the service.

This can restart with a mixture of predecessor and successor config, registry, unit, and drop-in.

**Required RED test:** fail restore publication/fsync independently for each of the four snapshot entries. Binary observable: service must not start until all four bytes and modes match the snapshot. If complete restoration is impossible, recovery must end loudly with the service stopped, never running a mixed contract.

## P0-7: Created-path ownership is not durably established

`remove_created()` always targets:

- successor runtime root;
- `data/weekly-operations-authority`.

There is no post-stop durable proof that both were absent before this attempt. A path created in the inspect-to-stop race, or a pre-existing path missed by contract classification, can be chmodded and recursively deleted.

There is also no no-follow ownership inventory for descendants; cleanup chmod traverses paths before deletion.

**Required behavior:** post-stop `lstat` absence fence plus a durable created-path ownership manifest before creation. Cleanup may remove only inode/path identities recorded as owned by this attempt.

## P1-1: Existing tests do not prove real fresh successor installation

The permission repair ORs owner-write bits onto the copied successor and leaves the predecessor unchanged. That closes the exact read-only clone mechanism in source.

However, disposable tests do not run `uv`; they write `installed-wheels.json`. They therefore do not prove:

- offline `uv pip install` succeeds against the read-only live-runtime clone;
- both exact wheels replace inherited products;
- console scripts and RECORD files are correct;
- imports originate exclusively from the successor;
- partial installer failure is cleanly removable.

The only real concrete rehearsal remains the V2 failure.

## P1-2: Exact mode restoration is not asserted across fault stages

Snapshots retain modes, and restore passes them to `atomic_write`, but stage-fault tests compare bytes only.

On the success path, controller writes use the default `0600`, changing current `0664` config/unit/drop-in modes. Whether intentional or not, it is not sealed as a target contract.

Every rollback test must compare `(bytes, stat.S_IMODE, type, nlink)` for all four paths.

## P1-3: Alternate-root one-use semantics are untested

The test called “consumes globally” retries only the same disposable root. Each disposable root gets its own `global-authorization`, so the same approval succeeds from root A and root B.

The live public entrypoint fixes its ledger root, which is good, but the proof must show all executable routes converge on that sole namespace and no copied controller/alternate working directory can substitute another root.

# Minimum passing fault matrix

Each row must assert:

1. primary exception identity/type is preserved;
2. all snapshot paths match exact bytes, mode, regular-file type, and link count;
3. owned created paths are absent;
4. real service state matches the initial active/running predecessor contract;
5. approval is durably consumed exactly once as `FAILED`;
6. no network, Telegram, or provider event occurred.

| Lane | Fault injection |
|---|---|
| Authorization | wrong, old, malformed approval; candidate/wheel/package drift |
| Global one-use | concurrent reserve; repeated same root; copied controller from alternate CWD/root |
| Reservation durability | short/zero/EINTR write; file fsync; parent fsync; crash after create before fsync |
| Stop | command failure before stop; interruption after actual stop; stopped-state postcheck failure |
| Snapshot | mkdir/open/write/short-write/temp-fsync/rename/published-fsync/parent-fsync/readback failure for each entry and manifest |
| Snapshot safety | symlink, hardlink, FIFO, missing path, mode drift, path replacement during capture |
| Fresh install | exact read-only live clone; copy failure at each depth; chmod failure; uv partial failure; RECORD/script/import mismatch |
| Migration dry run | malformed registry; existing policy; exact live config with absent weekly section |
| Migration apply | registry/config/authority write failures at write/fsync/rename/readback boundaries |
| Created ownership | pre-existing successor; pre-existing weekly authority; race-created path; read-only nested cleanup; symlink substitution |
| Systemd switch | unit first succeeds/drop-in fails and vice versa; mode restoration for both |
| Reload | daemon-reload failure before and after accepting changed unit |
| Start | command failure; interruption after actual start; immediate unit death; wrong ExecStart |
| Post-fence | wrong loaded module, wheel, RECORD, candidate, credential, weekly config, authority, or capacity |
| Rollback stop | failure while stopping a partially started successor |
| Restore | independent failure for each of four entries; retry/recovery must prevent mixed-service restart |
| Cleanup | chmod, traversal, unlink, and rmtree failures independently for both owned roots |
| Rollback reload | daemon-reload failure after exact restoration |
| Service restore | first start fails; second fails; starts wrong runtime; active then immediately dead |
| Consumption | short/zero/EINTR write; every fsync/rename/unlink boundary; consumption failure concurrent with primary failure |
| BaseException | `RuntimeError`, `ValueError`, `AssertionError`, `KeyboardInterrupt`, `SystemExit`, `GeneratorExit`, cancellation, SIGTERM |
| Crash recovery | SIGKILL after every durable boundary from reservation through commit |
| Success | real offline wheel install, parsed weekly authority enabled, capacity five, exact loaded identities, expected final modes |
| Repeatability | run full disposable success and every rollback lane twice from two unrelated absolute roots |

## Release threshold

Live execution remains blocked until:

- the exact live-config migration is corrected;
- real loaded-runtime/service fences exist;
- durable ledger and restore fault injection pass;
- SIGKILL recovery is defined and tested;
- a real offline read-only-runtime installation rehearsal passes;
- a new immutable V3 package binds the repaired source/test hashes and supersedes V2.

No live profile, service, authorization ledger, or execution root was modified during this audit.