# Task26 Golden Path, candidate 2e0894ea (v2)

Version: v2 (2026-08-15, supersedes the v1 rebind runbooks listed below)
Status: READY only when every preflight gate in Section 1 reports PASS. Until then this document describes the exact target procedure and its abort rules; do not execute partially.

Supersession (append-only, prior documents remain byte-identical):

- Supersedes `task26-runbook-rebind-2e0894ea-st_01a0054e/task26-golden-path-2e0894ea.md` sha256 `1f55b8f967c6564113574d41bf1c15ce977d35c38b5687f1623ba37f235a7bdb` (v1; three verified defects fixed here: G8/Section 2 lock deadlock, wrong ledger authority path, generic reset interface).
- Supersedes `task26-runbook-rebind-2e0894ea-st_01a0054e/task26-recovery-runbook-2e0894ea.md` sha256 `f4f2d2347b4b68a080b9c437a813accbef8c4bcc471cbf21d67ffff31e56f89b` (v1).
- Still supersedes `.omo/evidence/dualcoach-golden-path-contract.md` sha256 `32a379d855c6e5af978bd9886e3bf49c616c7c20f1c1d5f100c19d8adfc5eb4a` and `.omo/evidence/dualcoach-recovery-runbook.md` sha256 `ae2f5f9046c06f8f0b42693be5aa0d9c31cada8024ae1e5ab8ba19a3cf10f6fc`, which bind the stale candidate `19ed0e6047f0a4d7650c47a7413ee0298a243f768a63bcb8fb9cffe44d140a1a`. Those files stay byte-identical forever; every candidate pin inside them is void.
- No prior runbook is edited, renamed, moved, or deleted. This file is additive.

Binding (exact; short forms below are the first 8 hex chars of the full digests):

- Candidate full digest: `2e0894eac92bc396cc4723bf1f18ebc653b95018dd41574df435941c235da925` (`2e0894ea`)
- Wheel sha256: `af4a9d0a1ffffb6eb7551c1d6dc2b32853ca6d024332a4f8f5702bbf992f141b` (`af4a9d0a`)
- Amended plan sha256: `7ace03c6dad33d2fc3ef223621cbca68a150fde8429932138e252fb8498ac582` (`7ace03c6`), file `.omo/plans/dualcoach-production-readiness.md`
- Authorized actor: Telegram user id `8527916639` (the same physical account as prior rehearsals; this run creates a new logical customer lifecycle only)
- Profile under test: `dualcoachtest` at `/home/cube/.hermes/profiles/dualcoachtest`
- Bot: `@dual_coach_pilot_test_bot` (api_id `39664143`)
- Config: `/home/cube/.hermes/profiles/dualcoachtest/config.yaml`, sha256 `f93106b16643227e2ef9dec67a5bbd497e1d353e779da62287898a087071af87` at authoring time, with `adaptive_nutrition.delivery_enabled: false`
- systemd unit: `hermes-gateway-dualcoachtest.service`, unit file sha256 `0b46e887fc12c45f12814f4ef025c10101ed36e5df0ac35f70abb1862c0c1db2`, `Restart=always`, `RestartSec=5`, `RestartForceExitStatus=75`, `TimeoutStopSec=210`

Freshness boundary (from the amendment): only the Telegram actor identity and DM history are reused. Every customer key, session id, invite link, token, bootstrap/consent/onboarding/readiness/check-in/generation/delivery state, reset run id, and evidence directory in this run is new. The forbidden reused values are listed in Section 5.

## 0. Authorization and non-negotiable constraints

Authority: the `same-actor-live-rehearsal-authorized` entry recorded at 2026-08-15T11:55:24Z in the plan ledger `.omo/start-work/ledger.jsonl` (repo-relative; this is the plan/authorization ledger, not the profile customer-bootstrap ledger, which is runtime state). The authorized actions, restated:

1. Authenticated archive-first pre-reset.
2. Controlled exact-candidate wheel deployment and service rebind.
3. Exactly one new canonical bootstrap invite.
4. One private-DM `/start` by actor `8527916639`.
5. Synthetic provider and owner/operator actions through one uninterrupted normal lifecycle.
6. Exactly one synthetic customer DM delivery after an explicit send decision.
7. Final disable.
8. Authenticated archive-first cleanup reset.

Forbidden always: archive restore or prepopulation, direct durable-state mutation, raw Telegram listener or cursor edits, service replay, forced publication, any recovery shortcut, any change to the untouched other profiles (`physique-coach`, `quarantine`), candidate rebuilds or rerollouts inside the window, token or secret mutation, bypassing the review/owner-action lane, changing synthetic-provider or owner-card behavior, implicit sends, additional Telegram customer DMs or deliveries beyond the one authorized send.

## 1. Fail-closed preflight (run every gate; any FAIL or UNKNOWN stops the rehearsal)

Every gate writes a preflight receipt into a new private evidence directory (mode 700) for this run, for example `.omo/evidence/task26/task26-same-actor-live-rehearsal-<UTC timestamp>/`. All receipts in this runbook are mode 600. Commands run from the repository root unless a step says otherwise.

| Gate | Check | Pass condition | Status at v2 authoring |
|---|---|---|---|
| G1 | Candidate verifier: `python3 ".omo/evidence/task26/task26-repaired-archive-successor-2e0894eac92bc396cc4723bf1f18ebc653b95018dd41574df435941c235da925/verify_candidate.py" ".omo/evidence/task26/task26-repaired-archive-successor-2e0894eac92bc396cc4723bf1f18ebc653b95018dd41574df435941c235da925/verifier-input.json"` | Exit code `0` and stdout JSON field `status` == `PASS`. The positional argument is the sealed verifier-input file `verifier-input.json` (sha256 `cd9e080ab886d503a205e7a7335eb23942b161daeaa2028a6e8f63492a99ba68`, schema `task26-repaired-archive-successor-verifier-input-v1`), not the candidate root directory; the verifier resolves the root from the input's `candidate_root` field. Verifier sha256 `b852482350129dd025f0c4c79539e04a71c4758c9ec336e2c5bcda44676791f6` | PASS (evidenced) |
| G2 | Wheel byte equality: `sha256sum` of `.../artifacts/hermes_agent-0.17.0-py3-none-any.whl` in the candidate directory | Equals `af4a9d0a...` | PASS (evidenced) |
| G3 | Ledger authorization: `.omo/start-work/ledger.jsonl` contains `same-actor-live-rehearsal-authorized` with this candidate digest, wheel sha, plan sha, and actor `8527916639` | Present and matching | PASS (evidenced) |
| G4 | `sha256sum` of `config.yaml`; `stat -c %a` of `gateway-credentials.json` and `.gateway-auth-key` | Config equals `f93106b1...`; both secret files mode `600`; `adaptive_nutrition.delivery_enabled: false` confirmed | PASS at authoring; recheck at run time |
| G5 | Candidate wheel extraction into a new private temp dir; sha256 of the nine runtime modules: `gateway/config.py` `45f0e9f5...`, `gateway/run.py` `f1a2e2f9...`, `gateway/cron/scheduler.py` `8c8e21eb...`, `gateway/platforms/telegram_customer_bootstrap.py` `eff71467...`, `gateway/platforms/telegram_customer_onboarding.py` `997d18c1...`, `gateway/platforms/telegram_nutrition_onboarding.py` `a33d5cbb...`, `hermes_cli/nutrition_review_service.py` `8298a6b9...`, `hermes_cli/nutrition_customer_admin.py` `6426db46...`, `hermes_cli/nutrition_readiness.py` `6eb30d22...` | All nine equal | PASS at authoring; recheck at run time |
| G6 | Current authority snapshot equals the sealed dry-run manifest (receipt sha256 `2fc323f8...`, manifest `evidence_digest` `d2879b128573a9d75a55958d2fa3e1a24be1636f0a7a20affe8f2ef9b94dfd33`): `customers/registry.json` sha256 `9eb1b5ae...` (empty, zero customers), customer-bootstrap ledger sha256 `7004dac9...` (exactly three sessions `cb_6S6RABpDgZ165V02A7qEzw`, `cb_9yTz0oNwdU8s8hradru8BA`, `cb_rmDnfrqA6gkmjoQEdwxu0g`, all `EXPIRED` with empty `role_claims`/`recovery_attempts`), `sessions/sessions.json` sha256 `0bd0afb9...`, `state.db` sha256 `68de185a...` mode 600. Nothing is resumed, repaired, or preseeded | All digests equal the dry-run manifest entries | PASS at authoring; recheck at run time |
| G7 | Provider auth, read-only: `cd /home/cube/.hermes/profiles/dualcoachtest/.venv && ./bin/python -m hermes_cli.dualcoach_admin --profile .. provider-auth check --json` | Every provider `auth_ok: true`. Without flags this performs no billable calls. The billable active probe (`--allow-billable-active-probe --probe-all`) additionally requires the written human authorization for a billable probe (blocker B3) | OPEN: cheap check passing does not close B3 |
| G8 | Stale lock disposition (pre-reset): `flock -n /home/cube/.hermes/profiles/dualcoachtest/gateway.lock true` must succeed (no active lock holder); record the lock file's sha256 and `stat` (at authoring: sha256 `28420d4aa8fc1ee1298c9fe93a69e47aa187c0d2777ca672fb603cfbebc569cf`, mode 600, stale pid `4091167`); confirm `gateway.lock` is inside the sealed reset contract's `approved_clear_scopes` so the sealed reset disposes of it | No active holder AND lock listed in contract scope. The stale lock file's presence is permitted here; its disposal is scheduled to the sealed reset. Hand removal is forbidden at every point | PASS at authoring (probe clean, contract covers the lock) |
| G9 | `sha256sum` of `/etc/systemd/system/hermes-gateway-dualcoachtest.service` and `systemctl cat hermes-gateway-dualcoachtest.service` | Unit file equals `0b46e887...` | PASS at authoring; recheck at run time |
| G10 | Reset controller sealed evidence (blocker B2, CLOSED): controller `.omo/evidence/task26/reset-controller-st_01a0054d/reset_controller.py` sha256 `bd051dda8666ce9e014ec79c58acd4d7b8df2c27fad01a7a78c76e46c3388baf` (mode 700); contract `schema-contract.json` sha256 `4128cece087f3f4eb84c3917207299fae6bd1ac9971d7e4e8676549106bb7c20` (mode 600); permission `permission-receipt.json` sha256 `8a290b11cac5b6957c772366abe875c7f635b8a3e7956a665471ffaa90b6c495` (mode 600, `execute_allowed: true`, approval phrase `TASK26_ARCHIVE_FIRST_PROFILE_RESET_APPROVED`); sealed dry-run receipt `live-dry-run-after-batch-permission-repair.json` sha256 `2fc323f8b00c18f21598f717fb6a19a7e342b7786909e59adcf4bc3955bcef99` (`status: PASS`, `mutations: 0`, `bootstrap_ledger_covered: true`, three terminal sessions, 24 file entries, five prior archives hashed, zero other-profile drift); readiness receipt `live-reset-readiness-after-batch-permission-repair.json` sha256 `776e90301a63dce8912bf8a4110cd037f833c3a0e875a3ddd1289c0c9ee2ac9e` (`ready_for_live_reset: true`) | All five sha256 pins equal, files private, pins match this runbook's bindings | PASS (B2 closed) |
| G11 | Retained-archive verifier evidence (blocker B5, CLOSED): verifier `.omo/evidence/task26/verify_retained_archives.py` sha256 `12b97aa72e2df86719dbb7b80b477237d590aaaea81a9f812f321a1566936528` (mode 500); inventory `.omo/evidence/task26/retained-archive-schema-inventory.json` sha256 `29db87f2855c4804dbbc69244e68d9fb9f51808f7f7759fb592ed68f5f090185`; permission seal `retained-archive-verifier-permission-seal.json` sha256 `28ff72fdb7925cbdcd8e7dd7e8c058bcca42cab3377c34078164766197ed6968`; verification receipt `retained-archive-verification-receipt.redacted.json` sha256 `597a37e4912d729b3b5f6022bccff6dc12a73b024128805980f5ab1ade4fb83d` (`status: PASS`, five archives covering `dualcoach-rehearsal-archive-v1`, `-v3`, `-v4`, and `dualcoach-rehearsal-supplemental-quarantine-v1`, `ready_for_preflight: true`, supersedes receipt `6fd1bb07...`). Sealed test file `test_archive_verifier.py` sha256 `1b92ac7250d1af013204d8074176b971307211eddee91606d4cdd0fcd05efd79` with 14/14 tests passing and LSP diagnostics clean | All sha256 pins equal, receipt `status: PASS` | PASS (B5 closed) |
| G12 | A sealed one-use invite-preparation harness exists for this candidate (blocker B4, OPEN). The canonical API is `RoomBootstrapStore.prepare_rehearsal_customer_invite(...)` inside the deployed wheel; the harness pattern (sealed script plus 600 receipt) exists in evidence directories `task26-canonical-invite-preparation-5e6f2f2a.../` and `task26-fresh-canonical-invite-20260815T040558Z/`. The current arm manifest holds `commands.invite: null`, so no armed invite command exists yet | Sealed harness artifact with sha256 pin and passing dry-run receipt | OPEN |
| G13 | Subscription harnesses for outbox, audit, and delivery are staged from the trusted workspace source checkout (not the profile venv) and syntax-checked; live-listener startup remains forbidden | Harness files staged, `python3 -m py_compile` clean | Prepared at run time |
| G14 | `stat -c %a` of Telegram auth files under `~/.local/share/hermes/telegram/` (`auth_39664143.session`, `auth_39664143.session-journal`, `auth_39664143.json`) | All mode `600` | PASS at authoring; recheck at run time |
| G15 | Post-reset lock gate (executed in Section 2.1, not pre-reset): after the sealed reset's `verify` passes, `test ! -e /home/cube/.hermes/profiles/dualcoachtest/gateway.lock` | `gateway.lock` absent after reset | Checked in Section 2.1 |
| G16 | Cleanup terminal-authority gate (executed in Section 13, not pre-reset): immediately before the cleanup reset, the cleanup dry-run must pass, which requires every `required_current_scopes` entry present and every customer-bootstrap session terminal per the sealed contract (`EXPIRED`, `CANCELLED`, `FAILED`, or `COMPLETED`) with empty `role_claims` and `recovery_attempts` | Cleanup dry-run `status: PASS` | OPEN (blocker B6, see Section 13) |

Any gate that cannot be evaluated is UNKNOWN, and UNKNOWN stops the run exactly like FAIL.

## 2. Archive-first pre-reset (sealed reset controller st_01a0054d)

The sealed controller, contract, and permission receipt live in `.omo/evidence/task26/reset-controller-st_01a0054d/` (pins in G10). The controller implements three modes: `dry-run` (read-only preflight; PASS required before any execution), `execute` (archive, clear, receipt), and `verify` (post-execution independent verification). The order is dry-run, then execute, then verify. `verify` is a post-execute check: it reads the sealed `manifest.json` and `receipt.json` inside the new archive and asserts the live authority set is empty; it cannot run before execute.

Semantics that this run depends on:

- `--archive-root` is the destination root for the NEW archive of this run. The new archive is `<archive-root>/<run-id>`, staged as `<archive-root>/.pending-<run-id>` and committed by a single atomic rename; the rollback boundary is that rename (any failure before the rename restores the moved scopes and deletes the staging directory). The sealed command below sets `--archive-root` to `/home/cube/.hermes/profiles/dualcoachtest/data/profile-reset-archives`.
- Prior archives are READ from both `/home/cube/.hermes/profiles/dualcoachtest/data/rehearsal-reset-archives` (the five historical archives verified under G11) and the `--archive-root`; their tree digests are pinned into the new manifest, and execute aborts if any of them change before commit. Nothing restores, extracts, or prepopulates from any archive.
- Cleared scope comes from the sealed contract: `approved_clear_scopes` (which includes `customers`, `sessions`, `state.db`, `gateway.lock`, `gateway_state.json`, `.clean_shutdown`, `cron/jobs.json`, `cron/output`, the onboarding/owner-action/customer data files, and the internal lock files), plus `dynamic_clear_patterns`. This means the stale `gateway.lock` is disposed of inside this sealed scope; it is never hand-removed.
- Protected roots (`protected_data_roots`): `dualcoach-provider-auth`, `global`, `migrations`, `rehearsal-reset-archives`, `profile-reset-archives`, and the five `*-g5-checklist.json` gate files. Any `data/` child that is neither approved nor protected fails the run closed as a contract violation. Everything outside the approved scopes (including `config.yaml`, secrets, credentials, logs, and the venv) is untouched.
- `required_current_scopes` must exist before dry-run/execute (the registry, cron jobs, sessions file, the three onboarding ledgers, the publication-outbox emergency file, and the owner-actions service state). Every customer-bootstrap session must be in a contract terminal state with empty `role_claims` and `recovery_attempts`; the three current sessions qualify (all `EXPIRED`, claim-free).
- Execute preconditions enforced by the sealed controller source: contract and permission files are sealed (regular files, single hardlink, owner-owned, mode in {400, 500, 600, 700}, symlink-free); the permission receipt must exactly match the candidate digest, wheel sha, plan sha, approval phrase, controller self-hash, contract hash, and `execute_allowed: true`; the candidate argument must equal `2e0894ea...` exactly; the profile root is mode 700 and owner-held; zero running processes are associated with the profile; the run id is unused (neither `<run-id>` nor `.pending-<run-id>` exists under the archive root).
- `--approval` takes the literal approval phrase `TASK26_ARCHIVE_FIRST_PROFILE_RESET_APPROVED`, presented by the human at execution time. It is not a file path. The permission receipt pins the phrase; the human re-presents it for each reset (pre-reset and cleanup), and each execution consumes a distinct one-use run id.

Exact sealed execute command (from `exact-execute-command.txt` in the controller evidence directory; run from the repository root; line breaks below are display only):

```bash
python3 '.omo/evidence/task26/reset-controller-st_01a0054d/reset_controller.py' execute --profile '/home/cube/.hermes/profiles/dualcoachtest' --other-profile '/home/cube/.hermes/profiles/physique-coach' --archive-root '/home/cube/.hermes/profiles/dualcoachtest/data/profile-reset-archives' --contract '.omo/evidence/task26/reset-controller-st_01a0054d/schema-contract.json' --permission '.omo/evidence/task26/reset-controller-st_01a0054d/permission-receipt.json' --candidate '2e0894eac92bc396cc4723bf1f18ebc653b95018dd41574df435941c235da925' --wheel-sha256 'af4a9d0a1ffffb6eb7551c1d6dc2b32853ca6d024332a4f8f5702bbf992f141b' --plan-sha256 '7ace03c6dad33d2fc3ef223621cbca68a150fde8429932138e252fb8498ac582' --approval 'TASK26_ARCHIVE_FIRST_PROFILE_RESET_APPROVED' --run-id 'task26-live-reset-2e0894ea'
```

For the pre-reset, use run id `task26-live-reset-2e0894ea` exactly as sealed in the dry-run receipt. The same command shape with `execute` replaced by `dry-run` or `verify` produces the other two modes.

Step A, dry-run: stop the service first (`systemctl --user stop hermes-gateway-dualcoachtest.service` and confirm inactive; execute refuses to run while profile processes exist). Run the command above with mode `dry-run`. Require `status: PASS`, `mutations: 0`, `bootstrap_ledger_covered: true`, `bootstrap_session_count: 3`, and an `evidence_digest`. Save stdout as the run's dry-run receipt. Any FAIL: stop; do not execute.

Step B, execute: only after the human presents the approval phrase, run the exact execute command above. Require receipt `status: PASS` with `post_reset_empty_baseline: true`, `other_profiles_unchanged: true`, and `rollback_boundary: "archive directory rename (commit)"`. The receipt and manifest live inside the new archive directory `<archive-root>/task26-live-reset-2e0894ea/`.

Step C, verify: run the command with mode `verify`. Require the `task26-profile-reset-independent-verification-v1` output with `status: PASS`, `post_reset_empty_baseline: true`, and `bootstrap_ledger_covered: true`.

### 2.1 Post-reset verification

- Gate G15: `test ! -e /home/cube/.hermes/profiles/dualcoachtest/gateway.lock` (lock absent; the controller's verify already failed otherwise, this records it explicitly).
- The verify PASS from Step C stands as the empty-baseline proof: no approved scope exists, the five prior archives match their manifest digests, and the other-profile tree digest matches the pre-reset value.
- Spot-check: `customers/`, `data/onboarding/`, `data/customers/`, `sessions/`, and `state.db` are absent; `config.yaml` still matches G4's digest; `data/rehearsal-reset-archives/` still holds exactly the five historical archives.
- Regenerate cron/delivery/scheduling state only through the canonical runtime path. No canonical CLI writer exists for this; the runtime rebuilds it at service start. Confirm no cron timer or watchdog exists before start (`systemctl --user list-timers`), and start the service only in Section 4.

## 3. Controlled wheel deployment and service rebind (loaded-byte proof required)

1. Stop the service if running: `systemctl --user stop hermes-gateway-dualcoachtest.service`; confirm `systemctl --user is-active` prints `inactive`.
2. Record the pre-change install reference: sha256 of `/home/cube/.hermes/profiles/dualcoachtest/.venv/lib/python3.14/site-packages/physique_checkin_cli-0.1.0.dist-info/direct_url.json` (predecessor lineage value at authoring: `6a3f5f04a2982658f1c334273b05ae958b0353470997fc8df143c5b1fe12cbeb`).
3. Deploy: `cd /home/cube/.hermes/profiles/dualcoachtest/.venv && ./bin/python -m pip install --force-reinstall --no-deps "/home/cube/projects/richard/traning coach/.omo/evidence/task26/task26-repaired-archive-successor-2e0894eac92bc396cc4723bf1f18ebc653b95018dd41574df435941c235da925/artifacts/hermes_agent-0.17.0-py3-none-any.whl"` (path quoted; it contains a space). No candidate rebuild happens in this window.
4. Prove loaded bytes:
   - The installed `hermes_agent-0.17.0.dist-info/direct_url.json` contains `file:///home/cube/projects/richard/traning%20coach/.omo/evidence/task26/task26-repaired-archive-successor-2e0894eac92bc396cc4723bf1f18ebc653b95018dd41574df435941c235da925/artifacts/hermes_agent-0.17.0-py3-none-any.whl` (URL-encoding of the space is expected).
   - Compute sha256 for the same nine installed modules listed in G5 under `.venv/lib/python3.14/site-packages/`; every digest equals the G5 candidate extraction digests.
5. Confirm `checkin_cli` resolution: the venv's `__editable__.physique_checkin_cli-0.1.0.pth` still maps `checkin_cli` to the trusted workspace source checkout. The pinned module list is loaded from the wheel, not from the workspace.
6. Rebind: `systemctl --user start hermes-gateway-dualcoachtest.service`. Readiness evidence: `journalctl --user -u hermes-gateway-dualcoachtest.service -n 30 --no-pager` shows the gateway connected as `@dual_coach_pilot_test_bot` without `RuntimeError`, and the pre-start command's `/bin/rm -f .../gateway.lock` line has no effect on a healthy run (the lock is recreated by the process). Any failure: recovery procedure R6/R7.

## 4. Observer subscriptions before any actor action

Set up all three subscriptions from the trusted workspace source checkout (never the profile venv), writing into this run's evidence directory:

- Outbox: `python3 scripts/telegram_nutrition_onboarding_e2e.py subscribe-outbox --profile /home/cube/.hermes/profiles/dualcoachtest --after-seq 0 --timeout 120 --output <evidence>/outbox-events.jsonl`
- Audit: `python3 scripts/telegram_nutrition_onboarding_e2e.py audit-tail --profile /home/cube/.hermes/profiles/dualcoachtest --after-seq 0 --timeout 120 --output <evidence>/audit-events.jsonl`
- Delivery: `python3 scripts/telegram_nutrition_onboarding_e2e.py watch-deliveries --profile /home/cube/.hermes/profiles/dualcoachtest --after-seq 0 --timeout 120 --output <evidence>/delivery-events.jsonl`

Restart each subscription after every firing with the cursor advanced past the observed `seq` (`--after-seq <last seen>`), so no event is missed and none is double-counted. Do not start any raw Telethon listener.

Bounded deadlines (operator policy for this rehearsal; an exceeded bound is a STOP to the recovery runbook, not a retry loop):

| Phase | Bound |
|---|---|
| Any single subscription watch window | 120 seconds (then re-arm with advanced cursor) |
| Service start to gateway-connected journal line | 90 seconds |
| `/start` to claim-success audit event | 120 seconds |
| Consent publication to owner-review visibility | 300 seconds |
| Generation request to staff-review publish | 600 seconds (synthetic provider latency) |
| Explicit send decision to exactly one customer DM | 300 seconds |
| Readiness CLI or activation CLI | exit promptly; 60 seconds |

Handset-paced steps (consent review, the 12-question check-in, the readiness checklist upload) have no fixed clock; the stall rule is recovery procedure R5 (120-second re-arm, no state edits).

## 5. One invite, one Start (new logical lifecycle only)

1. Prepare exactly one invite through the sealed one-use harness (G12; pending). The harness calls the deployed wheel's `RoomBootstrapStore.prepare_rehearsal_customer_invite(profile, actor_id='8527916639', workspace='task26_rehearsal', provider='openai-codex', surface='dm', source='task26-same-actor-rehearsal')` exactly once and writes a private 600 receipt. Do not create a second invite unless the first one expires unbound, in which case follow the canonical expiry cleanup plus a new sealed preparation, and link the receipts (precedent: session `cb_rmDnfrqA6gkmjoQEdwxu0g` via `RoomBootstrapStore.expire_unbound`, receipt in `task26-fresh-canonical-invite-20260815T040558Z/expiry-cleanup-receipt.json`).
2. The invite is a fresh `https://t.me/dual_coach_pilot_test_bot?start=<64 lowercase hex>` link with a fresh `cb_...` session id. Record the invite's own expiry deadline from the harness receipt; an unbound invite past that deadline is handled by R3, never by reuse.
3. From the handset (actor `8527916639`, private DM with `@dual_coach_pilot_test_bot`): open the invite link once, then send `/start` once. Never send a bare `/start` without the invite payload.
4. Claim verification: within 120 seconds, the audit subscription shows `room_bootstrap_claim_success` with `surface: "dm"`, the session id matches the prepared session, and the same-actor guard emits exactly one `room_bootstrap_role_swap_blocked` audit event with no `role_conflict` client message (the account still holds the owner role).

All-new values for this run (all prior values forbidden):

- Customer key: new `cust_...` (forbidden: `task22_dm_rehearsal`, `task26_synthetic_rehearsal`, `task26_same_actor_rehearsal`)
- Bootstrap session: new `cb_...` (forbidden: `cb_2NQV5sbkN-M6awycJH7X5g`, `cb_6S6RABpDgZ165V02A7qEzw`, `cb_9yTz0oNwdU8s8hradru8BA`, `cb_rmDnfrqA6gkmjoQEdwxu0g`)
- Invite nonce: new 64-char lowercase hex, exactly one
- Onboarding/check-in session id: new (forbidden: `wizard_995f04a3b8bc256fa13ff407`)
- Generation idempotency token: new (forbidden: `3f44a18ea620d963`)
- Reset run ids: `task26-live-reset-2e0894ea` (pre-reset, consumed by this run's execute) and a fresh unused run id for the cleanup reset (Section 13)
- Evidence directory: new for this run

## 6. Consent and onboarding (handset)

- In DM, review the consent card and accept. Audit shows `canonical_customer_consent_accept` / `..._committed` with `version: "v1"`.
- Complete the nutrition onboarding questions in DM. Expected states in order: `collecting`, `customer_attestation`, `reconciling`, then either `owner_review` or `safety_hold`. `ready` before owner review is a defect; stop and capture evidence.
- Do not send free-form goal text to the bot outside the scripted questions (interception guard).

## 7. Owner review (handset, same physical account, owner role)

- The owner review card arrives in the owner review group `-1004484458766`, topic `59` (staff review) or topic `219` (preview).
- If the state is `safety_hold`: use the card's `safety_hold` action first, then review again.
- In owner review, use `review_questions`, edit answers where needed, `review_targets`, set calories 2900 / protein 210 / carbs 320 / fat 90, then `review_done`.
- The card shows one action at a time with Cancel/Back; the visible card is the authority. Do not run parallel mutating actions (single-owner rule).

## 8. Readiness and activation

1. From the handset, open `readiness_check`, upload the readiness checklist photo plus weight 93 kg, and submit.
2. CLI (synthetic customer key created by this run): `cd /home/cube/.hermes/profiles/dualcoachtest/.venv && ./bin/python -m hermes_cli.nutrition_readiness --customer <key> --profile-root /home/cube/.hermes/profiles --data-root /home/cube/.hermes/profiles/dualcoachtest/data --json`. Require exit 0 with all items satisfied.
3. Activation (consumes the one-use slot): `./bin/python -m checkin_cli.customer_admin --registry /home/cube/.hermes/profiles/dualcoachtest/customers/registry.json activate <key> --profile-root /home/cube/.hermes/profiles --data-root /home/cube/.hermes/profiles/dualcoachtest/data --checklist-evidence <path to readiness receipt>`. Exactly one run; a second attempt fails closed by design.
4. Verify within 120 seconds: registry shows `service_state: "active"` for the new key; the onboarding ledger shows `ready`; the outbox/audit trail shows the activation-completion notice.

## 9. Synthetic weekly check-in (12 questions)

- DM from the customer account: `checkin` (or the current weekly check-in prompt), then `Begin check-in`.
- Answer all 12 questions in order; Q7 uses one of choices `1` to `5`. One message per answer; wait for each prompt before answering (120-second per-prompt bound).
- Completed check-in lands in owner review; confirm the summary card there.

## 10. Generation, review, and exactly-once delivery

1. Generate (CLI): `./bin/python -m hermes_cli.nutrition_review_service --customer <key> --profile-root /home/cube/.hermes/profiles --data-root /home/cube/.hermes/profiles/dualcoachtest/data request-generation --json` with a fresh idempotency token for this run. The synthetic provider (`provider replay/openai-codex` in config) produces the plan without real provider calls.
2. The staff-review card arrives in the review group. Only after the checklist shows all items satisfied: choose `Send to customer` once.
3. Exactly-once proof: exactly one customer DM with the plan; exactly one `adaptive_plan_delivered` audit event with this run's generation id; the delivery watcher shows exactly one delivered record. A second DM, a second audit event, or a repeat after service restart are all defects: stop and capture evidence.
4. No implicit send exists: no customer DM may appear before the explicit send decision.

## 11. Disable

- `./bin/python -m checkin_cli.customer_admin --registry /home/cube/.hermes/profiles/dualcoachtest/customers/registry.json disable <key> --profile-root /home/cube/.hermes/profiles --data-root /home/cube/.hermes/profiles/dualcoachtest/data --reason "task26 rehearsal complete"`. Expect exit 0 and registry `service_state: "disabled"` with disabled_at/disabled_by/disabled_reason set.
- Stop the service and confirm inactive before the cleanup reset (execute refuses to run while profile processes exist).

## 12. Cleanup reset (archive-first, gated by G16)

Gate G16 applies here: run the sealed controller's `dry-run` mode first, with the same pins and a FRESH run id (the pre-reset consumed `task26-live-reset-2e0894ea`; run ids are one-use because the controller refuses an existing archive directory). Suggested run id: `task26-live-reset-2e0894ea-cleanup`; the actual value must be unused and is recorded in the cleanup receipt.

- If the cleanup dry-run reports `status: PASS`, execute with the same command shape (human re-presents the approval phrase) and then `verify`; require both PASS with `post_reset_empty_baseline: true`, then confirm `gateway.lock` absent (G15 form) and the other profiles untouched.
- If the cleanup dry-run FAILS with `customer-bootstrap ledger contains nonterminal authority` or `required current authority missing`, STOP. This is blocker B6: candidate `2e0894ea` offers no canonical transition from `ACTIVE` to any terminal bootstrap state (`transition()` allows only REGISTERING to AWAITING_CONSENT to AWAITING_ACTIVATION to ACTIVE, and no code path assigns CANCELLED or FAILED), and the claimed session retains its `role_claims`, so the sealed contract's terminal-authority check cannot pass after a successful lifecycle. Do not edit the ledger, do not force a transition, do not amend local files. The resolution is a superseding sealed contract/permission revision (or candidate-supported terminal transition) from the plan owner; until then the profile retains the disabled rehearsal customer and the run is recorded as rehearsal-complete, cleanup-blocked.
- No restore or prepopulation from any archive at any point. The historical archives stay sealed under `data/rehearsal-reset-archives/` and the run archives under `data/profile-reset-archives/`.

## 13. Evidence and receipt inventory (all mode 600, in the run's evidence directory)

- Preflight gate receipts G1-G14 with outcomes and digests.
- Reset receipts: dry-run stdout, the archive's `manifest.json`/`receipt.json` digests, verify stdout, and the G15 lock-absence record, for each reset actually executed.
- Deployment receipt: pre/post `direct_url.json` digests and the nine loaded-module digests.
- Invite preparation receipt (from the sealed harness), the claim/role-swap audit event lines, and the invite expiry deadline.
- Subscription transcripts: `outbox-events.jsonl`, `audit-events.jsonl`, `delivery-events.jsonl`.
- Handset step log: actor, action, visible card/result, UTC time, for every handset step.
- Readiness CLI output, activation receipt, disable receipt.
- Exactly-once proof bundle: the single DM evidence, the single audit event, the delivery record.
- Final summary receipt binding every digest above to candidate `2e0894ea`, wheel `af4a9d0a`, plan `7ace03c6`, actor `8527916639`, this runbook version v2, and the reset controller pins (controller `bd051dda`, contract `4128cece`, permission `8a290b11`).

## 14. Prohibitions (restated for this runbook)

No invented commands beyond those listed here or in the recovery runbook; no profile state hand-edits; no bootstrap-ledger edits to force terminal states; no lock file removal outside the sealed reset; no restore/prepopulate from archives; no raw Telegram listeners or cursor edits; no service replay; no forced publication; no recovery shortcuts; no candidate rebuild or rerollout inside the window; no token/secret mutation; no second invite while the first is claimable; no second generation send; no extra customer DMs; no changes to `physique-coach` or `quarantine`; no git mutations as part of the rehearsal.
