# DualCoach Task 16 Evidence

## Verdict

PASS for the requested restart-safety hardening; release remains blocked on explicit
candidate rebaseline approval.

## Durable publication authority

`GatewayOnboardingPublicationOutbox` is the Gateway-owned authority at:

```text
<profile>/data/onboarding/telegram-publication-outbox-v1/ledger.json
```

Its authenticated v2 record binds one publication's exact:

```text
(session_id, generation, canonical payload, route, role, render identity)
```

The record also carries a payload digest, dispatch identity, and an HMAC-SHA256 receipt
integrity tag keyed by a private no-follow `.receipt-key`. Both primary and emergency
ledgers, locks, and the key require regular single-link `0600` files under a `0700`
directory. Writes are atomic and fsynced.

Lifecycle:

```text
DISPATCHING -> RECEIPTED(message_id) -> COMMITTED
```

- The dispatch claim is durable before Telegram send. An unreceipted dispatch is never
  resent after restart.
- A changed message ID, payload, route, role, render identity, digest, dispatch identity,
  or receipt tag is rejected. A forged primary or emergency `RECEIPTED` record cannot
  commit profile state.
- If the primary receipt write fails after a successful Telegram send, the separately
  locked `emergency.json` receipt is written, reconciled to primary, then profile commit
  proceeds. If both writes fail, profile state becomes `UNCERTAIN` and no resend occurs.
- A process failure after emergency receipt persistence and before profile commit resumes
  the existing receipt without another Telegram send.
- Legacy unsigned v1 ledgers are preserved as `ledger-v1-untrusted.json` and replaced
  with an empty v2 ledger. They confer no authority to send or commit.

The external profile package was not modified.

## Current-card and callback authority

On restart, receipt recovery verifies the current profile publication's generation and
payload and, when a bootstrap session is available, its current canonical role route.
It will not commit an otherwise valid receipt whose route has changed.

Every onboarding callback now requires exactly one signed current outbox receipt matching
its session, generation, payload, role, Telegram chat, topic, and message ID. A callback
from another chat/topic with the same message ID is rejected before membership or business
state mutation. A committed current publication is not sent again on restart.

`TelegramAdapter.start()` invokes `_recover_room_bootstrap_waiting_states()` and retains
the earlier durable bootstrap recovery matrix for `WAITING_ADMIN`, `PROVISIONING`,
`CLAIMING`, `ROLE_REVIEW`, `REGISTERING`, `AWAITING_CONSENT`, and
`AWAITING_ACTIVATION`. The onboarding runtime handles the final state using the receipt
checks above; unresolved provider attempts remain blocked instead of being resent.

## Regression proof

All focused commands used `-W error -p no:cacheprovider` and the profile package only via:

```text
PYTHONPATH=/home/cube/.hermes/profiles/physique-coach/workspace/checkin_cli
```

- `tests/gateway/test_nutrition_coaching.py`: **151 passed**.
- Bootstrap transport/invite/registration/adversarial/activation suite: **158 passed**.
- Onboarding, callback-observability, and preview suite: **86 passed**.
  This includes forged primary/emergency receipt rejection, primary and total persistence
  failures, legacy unsigned-ledger quarantine, emergency receipt recovery after a commit
  crash, canonical-route recovery rejection, signed callback route rejection after restart,
  and committed-card no-resend.
- `tests/gateway/test_telegram_physique_checkin.py`: **123 passed**.

Total focused regression coverage: **518 passed**.

Static validation passed:

- `.venv/bin/ruff check` on Task16-modified source and tests;
- scoped `.venv/bin/ty check` on all Task16-modified source files: zero diagnostics;
- `compileall` on Task16-modified source and tests;
- `git diff --check`.

Repository-wide pytest is not clean independently of this patch. The full
`-W error -p no:cacheprovider` gate stops in collection with **3 skipped and 19 errors**
in 6.31 seconds because `tests/docker/conftest.py:46` emits the pre-existing unknown
`pytest.mark.timeout` warning, promoted to an internal error. The non-warning-as-error
collection also has 10 pre-existing ACP and
`tests/gateway/test_telegram_polling_receipts.py` collection errors after collecting
34,005 tests. No test, suppression, or unrelated path was changed to hide these failures.

## Candidate identity and release boundary

Task15's historical 70-path candidate digest was:

```text
63604403f7eb49ff849230b8b0801736fcea3f5e30e4530209f4810f527e01bf
```

This Task16 evidence and its manifest are refreshed after the authenticated-outbox and
current-card authority work. The 71-path candidate digest is:

```text
cceb2d95f7b7f71a9c44b7ded31c012356f8d7f4c26bff773ab9319c3dbc09f1
```

The digest is the verified Task16 candidate identity, not release authorization.

The eight protected release-decision paths remain untouched by this Task16 increment:

- `gateway/run.py`
- `hermes_cli/config.py`
- `tests/gateway/conftest.py`
- `gateway/platforms/telegram_activation_readiness.py`
- `gateway/platforms/telegram_nutrition_addresses.py`
- `tests/gateway/test_task7_generation_hardening.py`
- `tests/gateway/test_telegram_activation_readiness.py`
- `tests/gateway/test_telegram_nutrition_addresses.py`

## Independent final closure

Manual verifier `st_019fe45e`: CONFIRMED.

- Recomputed all 71 manifest paths to the candidate digest above.
- Current bootstrap card authority after restart:
  - `CLAIMING`: customer message `80`, trainer message `81`;
  - `ROLE_REVIEW`: customer `80`, trainer `81`, owner review `999`;
  - `AWAITING_CONSENT`: customer `80`, trainer `81`, consent `96`;
  - `AWAITING_ACTIVATION`: customer `80`, trainer `81`.
- Repeated/concurrent consent-card recovery produced one send and one no-op.
- Current callbacks advanced; stale and wrong-route callbacks mutated nothing.
- The complete 22-question onboarding walk and the separate `nutrition_daily`
  Q7/Q8 machine-action matrix passed.
- Invite links retained the Task5 private-DM `topic_id: "0"` contract.
- Generation lease recovery and disconnect cancellation passed.

Security/liveness reviewer `st_019fe45f`: CONFIRMED.

- Forged `RECEIPTED` rows now fail receipt-integrity verification.
- Primary receipt-write failure persists exact Telegram message evidence through the
  independent emergency ledger.
- Restart reconciles that receipt and commits without a second send.
- The original post-send commit-crash and forged-receipt HIGH findings are closed.
- Ambiguous outcomes remain `UNCERTAIN` and never blind-resend.

Automated verifier `st_019fe45d`:

- Recomputed 71-path JSON, Markdown, and raw-byte identity.
- Card/question/navigation, restart, generation lease, Task15 receipt, invite, Ruff,
  compileall, and diff gates passed in bounded batches.
- Its 71-diagnostic report treated every exercised legacy test file as Task16-owned.
  Direct lead verification on the final candidate found only three unresolved
  `checkin_cli` environment imports in the onboarding test file; no diagnostic
  intersects Task16-added tests or symbols. The four Task16-modified production files
  have zero scoped diagnostics.

## Final cleanup

- The task-owned `/tmp/hermes-agent-task16-wheel` directory and wheel were removed and
  verified absent.
- No real Telegram, provider, customer, activation, or delivery action occurred.
- No commit, push, reset, stash, clean, or unrelated deletion occurred.
- Shared pre-existing build outputs and unrelated dirty-worktree files remain
  untouched.
