I read this as an incident investigation - I will perform a strictly read-only audit, authenticate the lifecycle evidence, and determine a contract-backed recovery direction without changing product state.


I read this as a read-only incident audit - I will finish the contract, runtime-safety, and forward-only recovery analysis without changing state.


I read this as a read-only incident audit - the cause is proven; no state was changed by this audit.

**Root cause**
- Task23's `status` is not read-only. It explicitly calls `active_store.expire_unbound()` before reading the session.
- The prior authenticated snapshot was `AWAITING_ACTIVATION`, generation 7, `chat_id: null`, with `expires_at: 2026-08-10T07:29:02.306006Z`.
- At `2026-08-12T10:23:52.354302Z`, the expiry predicate was true: nonterminal + unbound + past deadline. It changed only:
  - `state`: `AWAITING_ACTIVATION` -> `EXPIRED`
  - `generation`: 7 -> 8
  - `updated_at`
- The writer atomically uses `os.replace`, explaining the inode change. The 12-byte size reduction matches the shorter state string; the new payload digest and ledger SHA are internally valid.

**Authenticated evidence**
- The preimage is authenticated by the Task22 supplemental journal HMAC and embedded snapshot: SHA-256 `f3a3...`, inode `33166473`, 1757 bytes.
- Current ledger: SHA-256 `e327...`, inode `33165847`, 1745 bytes; payload digest verifies.
- The supplemental journal HMAC verifies and still records the original bootstrap seal, callback `629525051`, and phase chain `PREPARED -> REVIEW_COMMITTED -> PUBLICATION_COMMITTED`.
- Generation 28 / message 123 remains committed. The Task22 activation gate currently returns true because it validates the journal, callback, and gen27/gen28 receipts, but it does **not** validate the live bootstrap seal.

**Seal status**
- The bootstrap seal is stale: expected `f3a...` / inode `33166473`; live is `e327...` / inode `33165847`.
- Every other sealed source, including the external candidate manifest, still matches.
- A full supplemental-review replay/load verifies all sealed sources and would reject this as `freeze drift`. The terminal journal cannot use `reseal_prepared`: it is restricted to `PREPARED` journals with no generation-27/28 authority, and Task22 has both.

**Runtime safety**
- `EXPIRED` is terminal. There is no transition, recovery-slot operation, or cutover path back to `AWAITING_ACTIVATION`.
- Cutover accepts only `AWAITING_ACTIVATION` or `ACTIVE`; this session is rejected before activation.
- Gateway recovery and customer-message authority process only `AWAITING_ACTIVATION`; `EXPIRED` is ignored.
- The gateway remains running (PID 675692). The customer is disabled, trainer is null, no customers are enabled, and no profile data file changed after the ledger mutation.

**Forward-only recovery**
- Do **not** restore the old ledger, edit its state/deadline/digest, or attempt recovery/cutover commands against this terminal session.
- A new `prepare` can create a new session because the old one is terminal, but it cannot reuse Task22's supplemental approval: that journal requires the exact old session ID.
- Therefore, completing onboarding requires a new supported supersession workflow: retain the old evidence, create a new session, and create fresh session-bound approval/seal evidence. No existing command provides that complete recovery.

**Required product fix**
- Make `status` pure: remove its call to `expire_unbound()`.
- If reconciliation is needed, expose an explicit mutating `reconcile-expiry` command.
- Add a CLI regression test with an expired unbound session asserting `status` leaves ledger bytes, SHA-256, and inode unchanged. The existing status test only checks no Telegram call while the session is still fresh.