I read this as a read-only adversarial architecture audit - I’ll trace the V3 scripts, preseal artifacts, V2 blocker receipt, and proven v1.4 v7 transaction end to end, then return ranked blockers, two minimal viable designs, and mandatory invariants with exact evidence.


I read this as resuming the interrupted read-only audit - I’ll continue from the missing v7 installer/systemd evidence, then synthesize the ranked blockers and mandatory V3 invariants without repeating prior exploration.


I read this as completing the resumed advisory audit now - I’ll check only whether the in-progress V3 files moved during the restart, then issue the final ranked blockers, viable designs, recommendation, and invariants.


I read this as a completed read-only adversarial audit - the V2 authority must remain blocked, and V3 needs a newly sealed, externally bound transaction architecture before authorization.

## Verdict

**BLOCK. Do not authorize digest `869c1e8...`.**

The current source fixes the immediate read-only-runtime cleanup failure, but it is not safely authorizable:

- V2 is explicitly marked `authorizable: false` and `SUPERSEDED_NON_REUSABLE` (`/home/cube/.hermes/migrations/nutricoach-v1.5.0-combined/live-transaction-preseal-v3/rejected-v2.json:2-9`).
- The executable still accepts that same `869c1e8...` approval and executes the live target (`.../scripts/nutricoach_v150_sealed_controller.py:23-31,150-157`).
- Current controller hashes no longer match the V2 source manifest:
  - `nutricoach_v150_concrete_host.py`: manifest `0eea5f...`, current `7ab670...`
  - `nutricoach_v150_sealed_controller.py`: manifest `af5d59...`, current `50f102...`
  - Expected hashes: `.../live-transaction-preseal-v2/controller-source-manifest.json:6-9`.

This is a concrete demonstration that approved package digest, executed controller, and mutation plan are not one authority.

## Ranked design blockers

### 1. P0 — Superseded V2 authority is still executable

The administrative rejection is not enforced by the live controller. `execute_authorized()` recognizes only the older `a362d9...` phrase as superseded; `869c1e8...` remains the accepted phrase (`nutricoach_v150_sealed_controller.py:23-31,150-157`).

The rehearsal consumed only its clone approval, while the receipt records no live global ledger (`blocker-receipt.json#/clone/approval`, `#/live_before/global_ledger_exists`). Therefore an operator holding the rejected V2 phrase could still enter the current, modified controller.

**Distinguishing failure:** rejected V2 authorization launches unsealed V3-era code instead of failing before ledger creation.

**Mandatory correction:** V3 must use a new seal ID and approval phrase; `869c1e8...` must be rejected in executable code.

---

### 2. P0 — Approval binds the read-only preflight, not the controller or complete mutation plan

`inspect_package()` derives the approval from only the preflight payload (`execute_nutricoach_v150_live_upgrade.py:295-320`). That payload binds existing target roots and snapshots, but not the concrete successor path, wheel bytes, expected post-switch bytes, rollback namespace, or controller digest (`preflight-v2/package.json:1`, especially `payload.target` and `payload` keys).

Those details exist separately in `sealed-target.json:5-52` and the controller manifest, but the preseal content digest is `163539...` while the approval phrase remains based on unrelated `869c1e8...` (`live-transaction-preseal-v2/package-manifest.json:3-14`). The runtime never verifies that preseal manifest.

The proven v1.4 seal instead places `controller_digest`, `plan_digest`, and `seal_id` in the same permission object and approval (`.../upgrade-preflight-v7-20260826T173500KST/permission-seal.json:1`).

**Distinguishing failure:** change controller behavior, successor path, or wheel files while leaving preflight-v2 unchanged; the approval still validates.

---

### 3. P0 — No installed or loaded successor identity proof

The live install invokes `uv` using wheel paths but never hashes the wheel files or validates installed RECORD contents (`nutricoach_v150_concrete_host.py:162-197`). The actual sealed controller does not invoke the hash checks in the unused generic transaction.

The “off smoke” only parses configuration; it does not execute successor code (`nutricoach_v150_concrete_host.py:199-203`). The post-fence checks capacity and the presence of a successor string in the unit file, but not installed bytes, loaded module origins, PID, or runtime (`:280-290`).

The proven v7 host validates raw inputs, installed identity, and loaded origins before and after switching (`upgrade_v7/production.py:65-100,223-237`).

**Distinguishing failures:**

- A modified wheel at the sealed path is installed and accepted.
- The unit contains the successor string while Python imports profile code from the predecessor.
- A different executable starts successfully and still passes `post_fence()`.

---

### 4. P0 — Systemd switching is textual, incomplete, and observed through a synthetic boolean

The switch blindly replaces the current runtime substring in the unit and one drop-in; it does not require an exact occurrence count or compare generated bytes with a sealed postimage (`nutricoach_v150_concrete_host.py:262-270`).

The proven target contract also requires successor credentials and `DUALCOACH_PROFILE_PACKAGE` rebinding. V7 explicitly creates successor authority credentials and rewrites named `LoadCredential` and profile-package entries (`upgrade_v7/production.py:165-213`). V1.5 has no equivalent credential derivation.

Real service state is represented by `SystemdService.running`, initialized to `True` and toggled after commands, rather than queried from systemd (`nutricoach_v150_sealed_target.py:63-91`). Rollback and commit decisions consume that synthetic value (`nutricoach_v150_sealed_controller.py:80-99`).

**Distinguishing failures:**

- Drop-in credentials remain bound to the predecessor candidate.
- `systemctl start` returns, the process immediately exits, and `running=True` still permits commit.
- A comment contains the successor path while the effective `ExecStart` remains wrong.

Also, Bubblewrap isolates the controller (`execute_nutricoach_v150_sealed_live.py:23-41`), not a service launched by the external user-systemd manager. A post-start network/privacy fence cannot rely on the controller’s empty event arrays.

---

### 5. P0 — Rollback authority is incomplete and not crash-recoverable

Only config, registry, unit, and one drop-in are snapshotted (`nutricoach_v150_sealed_target.py:108-110`; controller use at `nutricoach_v150_sealed_controller.py:131-143`). The successor is started before the final fence, so it can modify other profile data before a rollback.

The proven v7 transaction snapshots explicit mutable, append-only, volatile, and rooted service state, then verifies protected bytes after readiness (`upgrade_v7/operation.py:46-63,145-168`).

Current rollback continuation is an improvement, but:

- `restore()` aborts remaining entries on the first restore error (`nutricoach_v150_sealed_authority.py:91-97`).
- Secondary failures are only attached as in-memory exception notes (`nutricoach_v150_sealed_controller.py:80-99`).
- Service restoration is judged by the synthetic boolean.
- Durable snapshots cannot be reconstructed by a recovery-only invocation after SIGKILL, process death, or power loss.

The V2 receipt demonstrates the consequence class: exact mutable bytes restored, successor left behind, and service stopped (`blocker-receipt.json#/blocker/consequence`, `#/clone/service_running_after_failed_rollback`, `#/clone/successor_remained_after_failed_rollback`).

**Distinguishing failure:** successor writes an operational ledger, post-fence fails, four files are restored, but the ledger mutation remains.

---

### 6. P0 — Clean-boundary validity has a pre-stop race

`inspect_package()` verifies clean boundary while the Gateway is still running (`execute_nutricoach_v150_live_upgrade.py:321-333`). Afterward, `_execute()` captures only four preflight hashes, stops the service, and checks only those same four (`nutricoach_v150_sealed_controller.py:122-135`; `nutricoach_v150_concrete_host.py:135-160`).

**Distinguishing failure:** a pending send, onboarding transition, or provider outcome appears between package inspection and stop; none of the four hashes changes, so the transaction proceeds across a non-clean boundary.

V3 must repeat semantic clean-boundary validation after confirmed stop.

---

### 7. P0 gate — Writable-successor fix is directionally correct but not sealed or realistically rehearsed

The V2 receipt proves the original failure occurred after stop/snapshot/probe with `PermissionError` on the copied successor and left rollback incomplete (`blocker-receipt.json#/blocker/exact_error`, `#/blocker/reached_stages`, `#/blocker/consequence`).

Current source now:

- Requires the successor to be absent.
- Copies and adds owner write permissions (`nutricoach_v150_concrete_host.py:162-168`).
- Force-normalizes permissions before removal (`:292-301`).
- Continues recovery after cleanup failures (`nutricoach_v150_sealed_controller.py:80-99`).

That closes the obvious design error, but these bytes are outside the V2 seal and there is no successful V3 live-equivalent installer/rollback receipt. The disposable tests do not invoke real `uv`; they write an `installed-wheels.json` receipt instead (`nutricoach_v150_concrete_host.py:169-196`; test assertions at `tests/test_nutricoach_v150_sealed_controller.py:196-234`).

**Distinguishing failure:** copied permissions pass the synthetic test but real offline `uv`, generated scripts, or partial-copy cleanup still fails after service stop.

---

### 8. P1 — Two incompatible controller architectures remain

The real executable imports `nutricoach_v150_sealed_controller` (`execute_nutricoach_v150_sealed_live.py:11-14,43`). The parallel `live_transaction` route still binds rejected package `a362d9...` (`nutricoach_v150_live_models.py:15-18`), omits `successor_runtime` from `_verify_binding()` (`nutricoach_v150_live_transaction.py:45-59`), and expects a `LiveHost` API that the concrete host does not implement (`nutricoach_v150_live_models.py:74-123` versus concrete methods).

Those generic files are also absent from the V2 controller-source manifest (`controller-source-manifest.json:3-12`).

**Distinguishing failure:** tests or review validate one transaction engine while the operator entry point executes the other.

V3 needs one controller, one host contract, and one rollback implementation.

---

### 9. P1 — Capability success is not fenced

Weekly enablement is a blind byte replacement with no required match (`nutricoach_v150_concrete_host.py:256-260`). The final fence validates capacity but not weekly authority/config, Channel Inbox state, candidate identity, or successor credentials (`:280-290`).

**Distinguishing failure:** capacity migration succeeds, `weekly_pilot: false` was absent or differently structured, and the transaction commits without enabling the authorized pilot.

## Two viable architectures

### Design A — External content-addressed plan plus one generic executor

Create:

1. `operation-plan.json`: every target path, preimage/postimage hash, current and successor runtime, wheel and RECORD hashes, complete unit/drop-in/credential transformation, rollback inventory, created paths, service identity, and capability postconditions.
2. `controller-manifest.json`: hashes of the one executable controller bundle.
3. `seal_id = SHA256(domain || plan_digest || controller_digest)`.
4. A permission object and approval phrase containing that `seal_id`.

The generic executor contains no embedded package digest. It reads the fixed permission object, recomputes plan/controller digests, verifies the approval, then reserves the ledger keyed by `seal_id`.

**Trade-offs:** lowest migration cost, reuses the existing unit and proven v7 transaction shape, straightforward disposable testing. It remains coupled to the exact current systemd contract, so plan completeness is critical.

### Design B — Immutable versioned runtime and versioned systemd unit

Seal an immutable execution bundle and create a candidate-specific unit such as `hermes-gateway-dualcoachtest-v150.service`, with its own exact drop-in and credential root. Stop the predecessor, reload, start the versioned successor, and switch the stable activation alias only after readiness. Rollback stops the successor and restarts the untouched predecessor unit.

**Trade-offs:** cleaner rollback and less in-place unit mutation, but significantly more systemd lifecycle, monitoring, enablement, alias, and duplicate-service risk. It provides no zero-downtime benefit because both units cannot safely own the same profile concurrently.

## Recommendation: Design A

Use the v1.4 v7 transaction structure, reduced to the actual v1.5 mutation set. It is the smallest architecture that satisfies the requirements:

1. Verify the external V3 seal, exact source bundle, wheel bytes, current contract, successor absence, and clean boundary.
2. Reserve one global ledger keyed by the new V3 `seal_id`.
3. Stop and confirm real `ActiveState=inactive`.
4. Capture and durably verify complete post-stop rollback authority; write a durable phase journal.
5. Re-run clean-boundary and current-runtime probes while stopped.
6. Clone predecessor dependencies into a private writable staging root, normalize permissions, rename to the final absent path, install exact wheels offline, and verify RECORD plus loaded origins.
7. Run migration dry-run and OFF smoke using the successor interpreter inside the isolated namespace.
8. Apply exact sealed config/registry/authority postimages.
9. Generate exact successor unit, drop-in, and credential bytes; atomically publish, daemon-reload, and inspect effective systemd properties.
10. Start, query real active/running state and PID identity, prove successor runtime/module origins and all capability postconditions, then commit.
11. On any failure, continue all rollback actions, restore every snapshot with readback, force-remove successor-owned paths, reload predecessor systemd bytes, restart and prove the predecessor runtime, and persist a typed rollback receipt.

## Mandatory V3 invariants

1. **New authority:** `869c1e8...` always fails; V3 has a new seal and one-use ledger.
2. **Non-circular binding:** controller contains no V3 seal constant; approval binds externally computed controller and plan digests.
3. **Single control plane:** one entry point, transaction guard, host contract, and authority implementation.
4. **Exact target:** no caller-controlled paths; every live path, preimage, postimage, type, mode, and absence assertion is sealed.
5. **Wheel identity:** raw wheel hashes before stop and install; installed RECORD, generated scripts, and loaded origins after install.
6. **Fresh writable successor:** final successor is absent initially; predecessor is never chmodded; partial staging/final trees are always removable.
7. **Post-stop authority:** no live mutation before durable, verified snapshots and a stopped semantic boundary.
8. **Complete rollback set:** every possible transaction/service write is either snapshotted, explicitly successor-owned, or sealed as an allowed append.
9. **Rollback progress:** restoration continues across individual failures and produces a durable `ROLLBACK_INCOMPLETE` receipt when exact recovery cannot be proved.
10. **Crash recovery:** a reserved non-committed phase permits recovery-only rollback after process restart; it never permits forward re-execution.
11. **Real systemd observation:** no synthetic `running` flag in live decisions.
12. **Exact switch:** unit, every operative drop-in, profile-package path, credentials, effective `ExecStart`, and loaded candidate identity all point to the successor.
13. **Capability fence:** capacity five and weekly pilot ON; Channel Inbox remains unauthorized/OFF.
14. **Privacy boundary:** direct successor probes remain network-isolated; systemd-started behavior must be explicitly fenced because Bubblewrap does not contain the external systemd manager.
15. **Commit last:** success is durable only after runtime, service, systemd, capability, protected-state, and privacy fences all pass.

Audit was read-only: no files were edited, no live service commands were issued, and no live state was mutated.