I read this as an implementation and recovery task - I will audit the interrupted child work and repository state first, preserve valid partial changes, then complete and verify the offline-only recovery without touching live systems or planning records.


message: Codex error: Our servers are currently overloaded. Please try again later.

stop_reason: error


I read this as completion of the interrupted implementation - the partial status fix is valid, and I am now isolating the smallest session-bound recovery authority that can preserve Task22 provenance without permitting reuse of its callback.


I read this as implementation of the exact offline Task23 recovery - I will integrate the successor authority narrowly, add the CLI and regression tests, then run the affected checks.


I read this as implementation completion - verify the Task23 recovery changes, repair any integration defects, and report the validated result.


I read this as implementation completion - Task23 recovery is implemented and verified offline.

- Added forward-only, crash-recoverable Task23 successor with fresh owner approval and activation gating.
- Runtime now publishes a new session-bound approval button and accepts only its authenticated callback.
- Preserved Task22 evidence and added replay/crash-boundary coverage.
- Created and verified offline reseal artifacts under `.omo/evidence/`.

Verified: 377 affected tests passed; Ruff, compilation, diff check, CLI help, and Task23 seal verifier passed. No live profile, service, or Telegram access occurred.

Full-suite collection remains blocked by unrelated missing `acp` dependencies and an existing polling-receipts metaclass conflict.


I read this as evidence-only reporting - I will read only the sealed artifacts and existing command definitions, with no edits, tests, or live-profile access.


I read this as evidence-only reporting - no edits, tests, or live-profile access were performed in this follow-up.

## Critical live finding

There is **no safe live end-to-end Task23 procedure with the current code**.

`gateway/platforms/telegram.py::_handle_rehearsal_customer_start()` handles `rc1_...` by immediately calling `TelegramRoomBootstrapRegistration.handoff_rehearsal_customer()`, refreshing the registry, and publishing a consent card. That mutates profile/onboarding state and violates the Task23 constraint. The disposable Task23 runtime test bypasses this generic inbound handler.

Do **not** run `task23-supersession prepare --execute` against the live profile or have the customer open its link until inbound `rc1_` handling is made Task23-specific.

## Task23-changed files

Candidate source leaves, all sealed as `gateway-untracked`:

1. `gateway/platforms/telegram_nutrition_onboarding_runtime.py`
2. `gateway/platforms/telegram_nutrition_onboarding_runtime_authority.py`
3. `gateway/platforms/telegram_nutrition_onboarding_runtime_callback.py`
4. `gateway/platforms/telegram_nutrition_onboarding_task23_supersession.py`
5. `gateway/platforms/telegram_room_bootstrap.py`
6. `gateway/platforms/telegram_room_bootstrap_activation.py`
7. `gateway/platforms/telegram_room_bootstrap_cutover.py`
8. `scripts/nutrition-room-bootstrap`
9. `tests/gateway/test_task23_supersession.py`
10. `tests/gateway/test_telegram_room_bootstrap.py`

Evidence artifacts:

- `.omo/evidence/reseal-task23-expiry-supersession.py`
- `.omo/evidence/verify-task23-expiry-supersession.py`
- `.omo/evidence/dualcoach-task23-expiry-supersession-manifest.json`
- `.omo/evidence/dualcoach-task23-expiry-supersession.whl`
- `.omo/evidence/dualcoach-task23-expiry-supersession-wheel.json`
- `.omo/evidence/dualcoach-task23-expiry-supersession-freeze.json`

## Pure status RED/GREEN

`test_status_is_pure_for_an_overdue_unbound_session`

- **RED condition:** clock advances 31 minutes beyond the 30-minute unbound TTL; a mutating status implementation would return `EXPIRED`.
- **GREEN result:** `nutrition-room-bootstrap status` returns `PREPARED`.
- Proof asserted by the test:
  - `ledger.read_bytes()` is exactly unchanged.
  - SHA-256 of those bytes is exactly unchanged.
  - `(st_ino, st_mtime_ns)` is exactly unchanged.
  - Reloaded session remains `PREPARED`.
  - The command uses the transport-free bootstrap store; no Telegram adapter is constructed or invoked.
- Related guard: `test_reconcile_expiry_requires_explicit_execute` rejects expiry reconciliation without `--execute`.

## Immutable Task22 pins

- Profile: `dualcoachtest`
- Customer: `task22_dm_rehearsal`
- Old session: `rb_Ar-NuKG3LH8CqHPXUgc88g`
- Required old state: `EXPIRED`
- Old generation: `8`
- Original ledger SHA-256: `e3271199c1cb38476c44c55fe5318f5543a4152b4717033677e0c1c65f4971da`
- 22-answer digest: `f1aced0350c473a01aa4e1382bc810e072fccfe84ccd3ea8780ee83c324dfe55`
- Owner: `8693203710`
- Task22 callback update: `629525051`
- Old callback integrity: `987cdde4e245b1d316ab5000271a755db148153b5207156b8082f97c2a1f5c18`
- Generation-27 review receipt: `85d05e0ee400850435a6a8b670c840772b62779b1ec3c1db64fcfbcca9514cb4`
- Generation-28 terminal receipt: `07b8c5dd700b218bf8a315bc57192e54b2de692b0a5f4d3517afdd4e7b58b31e`

The old record must remain an unbound, customer-only expired DM session with no recovery attempts or failure code.

## State machine and allocation

Task23 journal phases:

`PREPARING -> PREPARED -> CUSTOMER_BOUND -> FRESH_OWNER_APPROVED`

- `PREPARING`: HMAC-seals a fresh raw 22-character DM SID before any successor append.
- `PREPARED`: bootstrap allocates one fresh `rb_<random>` session ID and binds it to the sealed SID hash.
- Customer click creates bootstrap `REGISTERING` generation 2.
- `replay` advances only the exact verified successor to `AWAITING_ACTIVATION`, generation 3, then seals `CUSTOMER_BOUND`.
- A generation-29 fresh owner callback seals `FRESH_OWNER_APPROVED`.
- A crash after append or after registration reuses the journal-sealed SID and existing successor; it cannot mint another successor.
- The old session ID is never reused or transitioned.

A fresh customer DM confirmation is unavoidable under the stated constraints: copying the old claim would either revive/rewrite expired authority or accept a customer identity without a new session-bound confirmation. It must, however, be routed through a Task23-specific inbound path rather than the current generic rehearsal handler.

## Owner approval path

1. Startup recovery finds the exact Task23 successor in `AWAITING_ACTIVATION`.
2. It authenticates Task22 evidence and the Task23 HMAC journal.
3. It reserves an outbox record:
   - role: `owner`
   - route: `("8693203710", "0")`
   - generation: `29`
   - payload state: `task23_supersession_owner_review`
4. Runtime sends the Korean Task23 text with button label `새 복구 세션 승인`.
5. Button callback is exactly `encode_callback_hash(action="owner_ok", generation=29, sid_hash=<new successor hash>)`.
6. The provider message ID is receipted and committed in the gateway outbox.
7. Callback handling verifies exact callback bytes, committed card, message ID, DM route, configured owner, member presence, successor SID hash, and a fresh update ID.
8. It records an immutable owner callback receipt with empty `consumed_updates_before`, then seals the journal approval envelope.
9. Activation/cutover revalidates that outbox receipt through `require_fresh_approval()`.

The Task22 generation-27 callback cannot approve the successor.

## Seal pins

- Candidate digest: `a1fde204a6ed370ecbdbb4bb488e845a9b2486d36d58390c57f248709d36998a`
- Candidate diff digest: `98aa172f3efd16b69f4a89b0180496bde8d6dfae1ca8a88c8360f6b80a96fd75`
- Manifest SHA-256: `8e2f58fb0bd9691d11f3fee0586f47b3340dbe476cf0799ed8daa19d2265999d`
- Freeze receipt digest: `c8bcb92be29dfa451395d9a807446a366b628f961f7badc6319103b68d5b02be`
- Wheel receipt SHA-256: `a865e2a6a333f8d27f418fec1302cf1c67f92b35ba34d85874d1a7c1a6818601`
- Wheel receipt digest: `cb8d7475b23543de1d6e58860d147bba1d1b2fc3280e30a47c019b5c457e11b1`
- Wheel: `dualcoach-task23-expiry-supersession.whl`
  - SHA-256: `b9fef24d9eed9f4ca6619ae509d7bb923cf72422838ba6a6f6ae26582228aaec`
  - bytes: `8,554,454`
  - members: `994`
  - member-index SHA-256: `7619b5515860894b545c612502820f8f5fb79b908374e951a2b42d31dcb2f9b8`
  - two offline source epochs: `1700000000`, `1800000000`
  - ZIP timestamps: `2000-01-01T00:00:00Z`
- Builder: `scripts/reproducible-wheel-build`
  - SHA-256: `284e0b428f96851241c8b3a2531d2227d38dda8e4ca1ed30fd4375cc67cefb39`
- Launcher: `scripts/nutrition-room-bootstrap`
  - bytes: `22,598`
  - SHA-256: `2223a85825fe0e0e7e71e514c5b6555d213b5a61a278a1ae1527108be06a97c3`
- Sealed git-status snapshot:
  - SHA-256: `a18fd0152d64ed4de88e5494074c77ea40e112e2e108721f5e3a3762aea9e70f`
  - bytes: `46,725`
  - entries: `1,003`
- Profile package:
  - predecessor digest: `3090dc7ca9e93236394dd4da88647b100ea70b22f00080950a24e1d254f575e6`
  - accessed: `false`
- Task22 predecessor:
  - candidate: `3ce5b95637211b0289529c35b452cf1771fe868a34777140388a8caa75e183cc`
  - manifest: `e456cfc6c93d97989319818879a38237cd70c0d516ae5d2e33543a59962658e8`

Leaf content byte/SHA pairs are exactly those in the manifest; count is 10.

## Verification and disposable outcomes

Direct sealed verifier passed:

```json
{"candidate":"a1fde204a6ed370ecbdbb4bb488e845a9b2486d36d58390c57f248709d36998a","freeze":"c8bcb92be29dfa451395d9a807446a366b628f961f7badc6319103b68d5b02be","manifest":"8e2f58fb0bd9691d11f3fee0586f47b3340dbe476cf0799ed8daa19d2265999d","mode":"offline-code-only-task23-verification","paths":10,"wheel":"b9fef24d9eed9f4ca6619ae509d7bb923cf72422838ba6a6f6ae26582228aaec","wheel_receipt":"cb8d7475b23543de1d6e58860d147bba1d1b2fc3280e30a47c019b5c457e11b1"}
```

Canonical/live Task22 verification was intentionally **not** run against the real profile. Task23 tests use disposable fixtures and test-only terminal-verifier substitution.

The exact 377-test command was:

```bash
.venv/bin/pytest -q \
  tests/gateway/test_task23_supersession.py \
  tests/gateway/test_telegram_room_bootstrap.py \
  tests/gateway/test_telegram_room_bootstrap_adversarial.py \
  tests/gateway/test_telegram_room_bootstrap_registration.py \
  tests/gateway/test_telegram_room_bootstrap_activation.py \
  tests/gateway/test_telegram_room_bootstrap_cutover.py \
  tests/gateway/test_telegram_room_bootstrap_invite.py \
  tests/gateway/test_telegram_room_bootstrap_transport.py \
  tests/gateway/test_task22_supplemental_owner_review.py \
  tests/gateway/test_telegram_nutrition_onboarding.py \
  tests/gateway/test_telegram_nutrition_onboarding_callback_observability.py \
  tests/gateway/test_telegram_nutrition_onboarding_direct_entry.py
```

Result: `377 passed in 6.55s`.

Disposable Task23 coverage passed for CLI gating, preflight purity, idempotent prepare, provenance preservation, old-callback rejection, activation gating, both crash boundaries, runtime owner-card publication/callback, and downstream-authority rejection. It did not exercise the generic Telegram `rc1_` inbound path; that path is the live blocker above.

## Full-suite blockers

`pytest -q` was blocked during unrelated collection by:

- `ModuleNotFoundError: No module named 'acp'` across ACP and ACP-adapter tests.
- `TypeError: metaclass conflict` in `tests/gateway/test_telegram_polling_receipts.py` for `_ReceiptGatedPollingBot`.

It stopped with 10 collection errors, 3 skipped, and 60 deselected tests.

## Cleanup

- No live profile, Telegram, service, or customer data was accessed.
- Reseal temporary directories and pytest `tmp_path` fixtures were automatically removed.
- Intended evidence artifacts remain with sealed permissions: manifest/wheel receipt/freeze `0600`, wheel `0444`.
- Broad unrelated modified/untracked workspace files were left untouched.

## Safe live procedure

Only the following inspection procedure is safe now; it deliberately does not create a successor:

```bash
export HERMES_HOME=/home/cube/.hermes/profiles/dualcoachtest
cd /home/cube/projects/richard/hermes-agent

hermes gateway status
hermes gateway stop

scripts/nutrition-room-bootstrap \
  task23-supersession \
  --profile-root "$HERMES_HOME" \
  preflight

hermes gateway start
hermes gateway status
```

No user interaction occurs in that safe procedure.

After the inbound `rc1_` handler is made Task23-specific, the required human interaction points would be: customer opens the emitted `customer_link` and sends the resulting `/start rc1_...` in the bot DM; then the owner taps `새 복구 세션 승인` on the new generation-29 owner DM card.


I read this as implementation - add a Task23-only `rc1_` ingress branch ahead of generic rehearsal handling, prove it end-to-end offline, then reseal.


I read this as implementation of isolated Task23 `rc1_` routing - I’ll inspect the current interfaces, then apply only a safe Task23-specific patch and verify it offline.


I read this as implementation completion - Task23 `rc1_` ingress is isolated and resealed.

- Exact Task23 links route before generic rehearsal handling, validate the sealed journal/provenance/customer/SID binding, and only advance the successor.
- Task23 claim/completion avoid generic expiry reconciliation, registry refresh, consent, onboarding, activation, and delivery paths.
- Added real command-ingress, attacker rejection, crash/restart, generic fallback, and generation-29 publication-gating coverage.

Verified offline:
- 313 related tests passed.
- `compileall`, Ruff, and `git diff --check` passed.
- Reseal and independent verifier passed.

Seal candidate: `35683a52b09d101d88567469a24f9c695d73a3ba428763a8228a71006e6af181`


I read this as an evidence-only status report - I’ll inspect the sealed artifacts and existing offline source only, with no edits, tests, service, profile, or Telegram access.


I read this as an evidence-only Task23 release record - no edits, tests, profile, service, or Telegram access occurred in this follow-up.

## Final seal

- Candidate: `35683a52b09d101d88567469a24f9c695d73a3ba428763a8228a71006e6af181`
- Candidate diff: `aed159f6214b623fb391976703ae54fda5369d905bbee7112b88f031af601df8`
- Manifest: `dualcoach-task23-expiry-supersession-manifest.json`
  - SHA-256: `d4cd1495d4171a19eab3d781b9cf3e433c47054a157234dddb949e73435e77df`
  - mode `0600`
- Freeze: `dualcoach-task23-expiry-supersession-freeze.json`
  - receipt digest: `6ff6004d1ad57e26e4719ce78f659b0c2134dcc06f738c8bd83b05370f94a5af`
  - mode `0600`
- Wheel: `dualcoach-task23-expiry-supersession.whl`
  - SHA-256: `a2c04948492b5ae95938f697d1960b977d6d04f1b20a852a90e4ca371662ea0d`
  - 8,556,097 bytes; 994 members; mode `0444`
  - member-index SHA-256: `b9b28ba86f937a4407e9b132748e176268a2e77ea8e1fb049c131c9f138af822`
- Wheel receipt: `dualcoach-task23-expiry-supersession-wheel.json`
  - file SHA-256: `86d7b3b727aeb1eb38ccb9cfed7b4d16f4f056a0b8b3637f2411ddad548dfb57`
  - receipt digest: `810f4fb3c46c235dda27639d3efb8cfa82a8fd59d57236a5880b7f9986a7b190`
- Resealer: `.omo/evidence/reseal-task23-expiry-supersession.py`
- Independent verifier: `.omo/evidence/verify-task23-expiry-supersession.py`
  - previously passed against this candidate.
- Launcher: `scripts/nutrition-room-bootstrap`
  - leaf SHA-256: `2223a85825fe0e0e7e71e514c5b6555d213b5a61a278a1ae1527108be06a97c3`

The candidate has 11 sealed source leaves. The status snapshot is deliberately a hash, not a clean-tree claim:

- status SHA-256: `a18fd0152d64ed4de88e5494074c77ea40e112e2e108721f5e3a3762aea9e70f`
- 46,725 bytes, 1,003 entries.

Profile-package pin:

- predecessor profile-package digest: `3090dc7ca9e93236394dd4da88647b100ea70b22f00080950a24e1d254f575e6`
- profile package accessed: `false`
- Telegram/service/profile access during sealing: all `false`.

## Changed files

The final candidate seals these complete changed implementation/test leaves:

1. `gateway/platforms/telegram.py`
2. `gateway/platforms/telegram_nutrition_onboarding_runtime.py`
3. `gateway/platforms/telegram_nutrition_onboarding_runtime_authority.py`
4. `gateway/platforms/telegram_nutrition_onboarding_runtime_callback.py`
5. `gateway/platforms/telegram_nutrition_onboarding_task23_supersession.py`
6. `gateway/platforms/telegram_room_bootstrap.py`
7. `gateway/platforms/telegram_room_bootstrap_activation.py`
8. `gateway/platforms/telegram_room_bootstrap_cutover.py`
9. `scripts/nutrition-room-bootstrap`
10. `tests/gateway/test_task23_supersession.py`
11. `tests/gateway/test_telegram_room_bootstrap.py`

The inbound-routing increment specifically changed `telegram.py`, Task23 supersession authority, bootstrap private edges, Task23 tests, the resealer, and regenerated manifest/wheel/receipt/freeze artifacts. The verifier was not changed in that increment.

## Task23 pins

- Profile: `dualcoachtest`
- Customer: `task22_dm_rehearsal`
- Expired Task22 session: `rb_Ar-NuKG3LH8CqHPXUgc88g`
- Required old generation: `8`
- Old ledger SHA-256: `e3271199c1cb38476c44c55fe5318f5543a4152b4717033677e0c1c65f4971da`
- 22-answer digest: `f1aced0350c473a01aa4e1382bc810e072fccfe84ccd3ea8780ee83c324dfe55`
- Owner: `8693203710`
- Task22 callback update: `629525051`
- Task22 callback integrity: `987cdde4e245b1d316ab5000271a755db148153b5207156b8082f97c2a1f5c18`
- Task22 review receipt: `85d05e0ee400850435a6a8b670c840772b62779b1ec3c1db64fcfbcca9514cb4`
- Task22 terminal receipt: `07b8c5dd700b218bf8a315bc57192e54b2de692b0a5f4d3517afdd4e7b58b31e`
- Fresh owner-card generation: `29`

## Token and routing

Telegram accepts the customer-start shape:

```text
/start[optional @bot] rc1_<22 URL-safe base64 characters>
```

The exact regex is:

```text
^/start(?:@[A-Za-z0-9_]+)?\s+(rc1_[A-Za-z0-9_-]{22})$
```

Task23 runs before generic rehearsal routing. It only claims the route when all of these hold:

- a Task23 journal exists;
- the raw token constant-time matches `rc1_` plus the journal-sealed successor SID;
- journal HMAC, Task22 provenance, old session, successor SID/hash, and candidate bindings validate;
- update is a direct private DM from a non-bot user;
- user ID equals chat ID;
- user/chat/message IDs are numeric;
- user is the original Task22 customer, not the owner;
- the new message ID differs from the old Task22 customer-claim message ID.

A matching token with invalid customer evidence fails closed and never falls into generic registration. A non-Task23 `rc1_` token retains ordinary rehearsal behavior.

## Real ingress tests and 313-test command

Real `TelegramAdapter._handle_command()` ingress coverage:

- `test_non_task23_customer_start_reaches_generic_rehearsal_routing`
- `test_task23_customer_start_routes_before_generic_registration`
- `test_task23_customer_start_recovers_after_journal_crash_without_duplicate_successor`
- `test_task23_owner_card_waits_for_real_customer_start`

The recorded 313-test command was:

```bash
.venv/bin/python -m pytest -q \
  tests/gateway/test_task23_supersession.py \
  tests/gateway/test_telegram_room_bootstrap.py \
  tests/gateway/test_telegram_room_bootstrap_activation.py \
  tests/gateway/test_telegram_room_bootstrap_adversarial.py \
  tests/gateway/test_telegram_room_bootstrap_cutover.py \
  tests/gateway/test_telegram_room_bootstrap_invite.py \
  tests/gateway/test_telegram_room_bootstrap_registration.py \
  tests/gateway/test_telegram_room_bootstrap_transport.py \
  tests/gateway/test_telegram_nutrition_onboarding.py \
  tests/gateway/test_telegram_nutrition_onboarding_callback_observability.py \
  tests/gateway/test_telegram_nutrition_onboarding_copy.py \
  tests/gateway/test_telegram_nutrition_onboarding_direct_entry.py \
  tests/gateway/test_telegram_nutrition_onboarding_preview.py \
  tests/gateway/test_telegram_nutrition_onboarding_preview_runtime.py
```

Result: `313 passed in 4.63s`.

## Disposable E2E result

The temp-profile ingress test established:

- `PREPARED` successor -> authenticated customer `/start` -> `CUSTOMER_BOUND`;
- successor becomes `AWAITING_ACTIVATION`;
- exactly two bootstrap sessions remain; no duplicate successor;
- old expired Task22 ledger record is byte-equivalent at the record level;
- generic rehearsal handler is not called;
- registry refresh is not called;
- consent publication is not awaited;
- existing outbox records remain equal;
- no generation-29 owner record exists at customer binding;
- no onboarding service is opened.

Crash/restart coverage leaves the successor `AWAITING_ACTIVATION` while the journal is still `PREPARED`; a restarted replay of the same customer action writes `CUSTOMER_BOUND` without minting another successor.

Generation-29 coverage establishes:

- before customer confirmation, recovery returns false, sends zero owner cards, and creates no generation-29 receipt;
- after confirmation, recovery publishes exactly the new owner card with a successor-SID-bound `owner_ok` callback and commits the generation-29 outbox receipt;
- the Task23 runtime service mock remains uncalled;
- post-fresh-approval activation-gate coverage asserts `scheduled-deliveries.jsonl` does not exist: delivery count is zero.

## Live procedure

Preparation sends **no Telegram message**. It only writes the authenticated journal and successor ledger state, then prints JSON containing `customer_link`.

Use a shell outside the running gateway. `PROFILE_ROOT` must be the resolved root of the named `dualcoachtest` profile; it is not contained in the offline seal, so it must not be guessed.

```bash
PROFILE=dualcoachtest
PROFILE_ROOT='<resolved dualcoachtest profile root>'
BOT_USERNAME='<configured Telegram bot username>'

cd /home/cube/projects/richard/hermes-agent

hermes -p "$PROFILE" gateway stop

./scripts/nutrition-room-bootstrap \
  task23-supersession \
  --profile-root "$PROFILE_ROOT" \
  preflight

./scripts/nutrition-room-bootstrap \
  task23-supersession \
  --profile-root "$PROFILE_ROOT" \
  prepare \
  --bot-username "$BOT_USERNAME" \
  --execute

hermes -p "$PROFILE" gateway start
```

Take `customer_link` directly from the JSON emitted by `prepare`; it is the only customer action URL. The customer can handle it manually in an existing Telegram client: open/click the link, or send its equivalent `/start rc1_...` command in a private chat with the bot. It must be the pinned customer account.

After the customer receives the confirmation reply, quiesce and reconcile once:

```bash
hermes -p "$PROFILE" gateway stop

./scripts/nutrition-room-bootstrap \
  task23-supersession \
  --profile-root "$PROFILE_ROOT" \
  replay \
  --execute

./scripts/nutrition-room-bootstrap \
  task23-supersession \
  --profile-root "$PROFILE_ROOT" \
  preflight

hermes -p "$PROFILE" gateway start
```

The gateway then publishes the owner’s generation-29 card. The configured owner account must manually tap exactly:

```text
새 복구 세션 승인
```

After that callback, verify restart durability:

```bash
hermes -p "$PROFILE" gateway stop

./scripts/nutrition-room-bootstrap \
  task23-supersession \
  --profile-root "$PROFILE_ROOT" \
  preflight

hermes -p "$PROFILE" gateway start
```

Expected final preflight phase: `FRESH_OWNER_APPROVED`. Restart must not send a second owner card.

Service quiescence is required during CLI mutation/replay: only one gateway/poller may own the bot token, with no manual duplicate gateway. Preparation also rejects pre-existing downstream authority in activation journals, notices, activation audit, scheduled deliveries, owner draft deliveries, or feature-epoch state.

No cleanup was performed. No unrelated workspace files were removed or modified. The full suite was not rerun in this follow-up; the previously known unrelated collection blockers remain `ModuleNotFoundError: acp` and a `TypeError: metaclass conflict` in `tests/gateway/test_telegram_polling_receipts.py`.


I read this as a focused Task23 provenance fix - I’ll authenticate the pinned historical trainer-bearing Task22 record as immutable evidence, preserve a trainer-free successor, and verify it offline before the single read-only live preflight.


message: WebSocket error


I read this as final verification and evidence capture - the offline fix and checks are complete; I’ll now read only the regenerated seal metadata to report the exact candidate and live-preflight receipt.


I read this as verification remediation before finalizing - the implementation checks passed, but strict diagnostics still flag existing Task23 test typing gaps, so I’ll correct those test annotations without changing behavior and rerun the required checks.


I read this as tightening the requested historical-variation proof before release - I’ll add one test for post-journal unpinned old-record drift, then repeat offline sealing and the sole read-only live preflight.


I read this as a completed narrow Task23 provenance fix - historical trainer evidence is authenticated only as immutable superseded history, with no live authority restored.

Implemented:
- Accepts the pinned `e327...` gen-8 expired record only with exactly `{customer, trainer}` claims and trainer draft identities matching that historical trainer claim.
- Full ledger hash authenticates pre-journal history; journal pins the complete old-record digest afterward.
- Any trainer identity/claim/draft variation fails; later unpinned old-record drift fails.
- Successor remains trainer-free: no trainer draft fields, role claim, topics, cards, or authority.
- Task22 v1 behavior was untouched; old approval still cannot approve the Task23 successor.

New/updated Task23 tests:
- `test_preflight_is_read_only_and_prepare_is_idempotent`
- `test_historical_trainer_identity_variation_fails_closed_even_if_rehashed`
- `test_historical_trainer_free_or_unpinned_draft_variation_is_rejected`
- `test_journal_rejects_any_later_historical_record_drift`
- Existing fresh-owner tests continue to prove Task22 approval rejection and generation-29-only approval.

Verification:
- Task23 + Task22 trainer/supplemental suites: `186 passed`.
- Prior Task23/bootstrap/onboarding suite: `311 passed`.
- Disposable temp-profile ingress/replay E2E is included in `test_task23_supersession.py`.
- Ruff, strict `ty`, `compileall`, and `git diff --check` passed.
- Reseal and independent verifier passed.

Final seal:
- Candidate: `a778ee4fc2acf23129f074d43fe53d55eb7c2334f1e42ecb724f7c0be4bd0fc1`
- Manifest: `fea342f662a740646f6d6c48f0878d77613ee690bcb6402100b3cea9c0b9b0b5`
- Freeze: `8b59b0f91e1e70be48124ce03b228b66e5efb9090f2cf846b57144e51438d719`
- Wheel: `17a691817c67724a3d6789e9a86c78e4192184b22f327677f56a0751a6fbc103`
- Wheel receipt: `2d85b013e52bf81a0153c5f19cb2ff0de522c9d44e63cf4a5d8005f3a60d0e0d`
- 11 sealed leaves; 994 wheel members.
- Status snapshot remains `a18fd0152d64ed4de88e5494074c77ea40e112e2e108721f5e3a3762aea9e70f`.

Pins:
- Profile/customer: `dualcoachtest` / `task22_dm_rehearsal`
- Old session/gen: `rb_Ar-NuKG3LH8CqHPXUgc88g` / `8`
- Ledger: `e3271199c1cb38476c44c55fe5318f5543a4152b4717033677e0c1c65f4971da`
- Answers: `f1aced0350c473a01aa4e1382bc810e072fccfe84ccd3ea8780ee83c324dfe55`
- Owner/fresh generation: `8693203710` / `29`
- Task22 callback/update: `629525051` / `987cdde4e245b1d316ab5000271a755db148153b5207156b8082f97c2a1f5c18`
- Review/terminal receipts: `85d05e0ee400850435a6a8b670c840772b62779b1ec3c1db64fcfbcca9514cb4` / `07b8c5dd700b218bf8a315bc57192e54b2de692b0a5f4d3517afdd4e7b58b31e`

Active-safe live preflight only:
```json
{
  "before": {"bytes": 1745, "inode": 33165847, "mtime_ns": 1786530232354554527, "sha256": "e3271199c1cb38476c44c55fe5318f5543a4152b4717033677e0c1c65f4971da"},
  "preflight": {"fresh_approval_update_id": null, "new_session_id": null, "old_session_id": "rb_Ar-NuKG3LH8CqHPXUgc88g", "phase": "PREPARING"},
  "after": {"bytes": 1745, "inode": 33165847, "mtime_ns": 1786530232354554527, "sha256": "e3271199c1cb38476c44c55fe5318f5543a4152b4717033677e0c1c65f4971da"}
}
```

No service stop, prepare, Telegram action, plan, ledger mutation, or todo action occurred. Test fixtures were disposable; no unrelated workspace cleanup or mutation was performed.

Safe prepare procedure, only under separate authorization and after quiescing the gateway:

```bash
cd /home/cube/projects/richard/hermes-agent
hermes -p dualcoachtest gateway stop

./scripts/nutrition-room-bootstrap \
  task23-supersession \
  --profile-root /home/cube/.hermes/profiles/dualcoachtest \
  preflight

./scripts/nutrition-room-bootstrap \
  task23-supersession \
  --profile-root /home/cube/.hermes/profiles/dualcoachtest \
  prepare \
  --bot-username "$BOT_USERNAME" \
  --execute
```

`prepare` writes the successor/journal and prints `customer_link`; it sends no Telegram message.