# DualCoach Task 12 Evidence

## Verdict

PASS — approval persists one immutable receipt and performs zero delivery work; send
revalidates the approved revision, operator, active customer, current route, prepared
destination/text, and outbox status. Revocation is durable and restart-safe.

## Reused behavior and exact gaps

Reused:

- Task 7 generation/delivery states, canonical receipts, CAS pins, and restart
  reconciliation;
- Task 11 n3 card/revision/render validation;
- active-customer, consent, role, route, and existing outbox-status checks.

Confirmed gaps:

- duplicate approval returned `draft_not_approvable` instead of reusing its immutable
  canonical receipt;
- an approved card could not revoke approval;
- final pre-transport validation omitted the prepared destination and text;
- a draft projection write failure after the generation journal reached `HELD` could
  wedge revoke, especially after a fresh coordinator restart.

Rejected extras:

- Task 13 transport/outbox machinery;
- card UX, generic approval framework, compatibility shims, speculative abstraction,
  future scaffolding, broad refactoring, manual receipt deletion, suppressions, and
  fixed sleeps.

## Implementation and failing-first evidence

- Duplicate approval idempotently reuses the exact immutable approval receipt.
- `approved -> held` is the explicit revoke decision.
- Final send authority validates the exact prepared destination/text and current route.
- Revoke recovery repairs only the mutable draft projection when authoritative durable
  evidence proves the exact partial `APPROVED -> HELD` transition:
  - matching request/customer/actor/authority/CAS pins;
  - canonical check-in binding reconstructed from the generation event ID;
  - valid immutable approval receipt;
  - active customer and consent;
  - absent delivery/outbox entry.
- No generic `HELD -> HELD` transition is allowed and no second generation row is
  appended.

Failing-first receipts:

- duplicate approval failed;
- approved revoke was rejected;
- route changed after reservation could reach transport;
- injected draft projection `OSError` left generation held/draft approved;
- same-process and fresh-coordinator retry initially returned `draft_not_holdable`.

## Acceptance evidence

- Approval: one immutable receipt, state `approved`, zero provider/transport/customer
  messages.
- Duplicate approval: receipt bytes unchanged.
- Restart: approval retained.
- Revoke: converges to one held generation row and held projection.
- Send after revoke: rejected before reservation or transport.
- Changed draft after approval: rejected.
- Wrong role, wrong route, inactive/consent-revoked customer, stale card, forged or
  missing authority: fail closed.
- Prepared destination/text mutation before transport: rejected.
- Valid current approved revision: one fake transport attempt in isolated QA.
- Concurrent approve/revoke/send: event-controlled tests prevent preapproval or
  post-hold delivery.

## Partial-hold recovery and adversarial verification

Original reviewer `st_019fe2f0`: final CONFIRMED.

Fresh restart transcript:

```text
process1: generation=held, projection=approved, deliveries=0
process2: normal selection absent, bridge snapshot absent
process2: canonical-event recovery accepted, generation=held, held rows=1
process2: projection=held, approval receipt retained, deliveries=0
```

Forgery, actor/digest/pin mismatch, missing or inconsistent predecessor, invalid receipt,
inactive/consent-revoked customer, and existing outbox all reject without repair.
Concurrent send after the durable hold decision creates no reservation or transport.

## Automated and manual verification

- Owning nutrition/generation domain: 182 passed.
- Telegram callback domain: 120 passed.
- Ancillary domain bundle: 240 passed.
- Fresh-coordinator, forgery, and concurrent-send targeted tests: PASS.
- Manual nutrition coordinator restart QA: 4 targeted tests plus isolated real
  coordinator driver, zero deliveries.
- Task 12 changed-symbol and changed-test type intersections: zero.
- No new `Any`, cast, ignore, or suppression.
- Ruff: PASS.
- `py_compile`/`compileall`: PASS.
- `git diff --check`: PASS.

## Candidate identity

- 67-path candidate digest:
  `016d2d504c5ac9f06699f064acf7959392fef6d1d8e7534bcaacfaba8b7a6922`
- JSON manifest SHA-256:
  `bf7e0e4feb0130bc650f95fe3b1f9f1dffeac9d4d053d2f27dc2b08e90dd2744`
- Markdown manifest SHA-256:
  `046518e05a994a282b4bbda247b024224ae1ec32d0df0799a42040024bdebf0d`
- `gateway/platforms/nutrition_coaching.py`:
  `0258fc5b9fec4cb600856e4406012b26ceaaad14e23239e57d1e1301850fba98`
- `gateway/platforms/physique_checkin.py`:
  `1d07882ea22f8b8cbaf7d5df843fcf6d29375e895278ab25faa529a2b4e9d50a`
- `gateway/platforms/telegram.py`:
  `19f7cfe32bb12c6da8528efe7a2bfcc95fc96145a0806ff819787641dab09e18`
- `tests/gateway/test_nutrition_coaching.py`:
  `f194d5ac74f626413e24096afe4307b3c6ffdd9b4e6f9f970d5af212d5563652`

JSON, Markdown, and independently recomputed NUL-framed raw-byte digest agree. The
shared dirty-worktree status remained 98 paths; status-line metadata changed externally,
but no candidate path hash drifted.

## Cleanup and non-touch

- Fake transport/provider surfaces only.
- Temporary QA directories removed.
- No real network, provider, Telegram, activation, customer action, or delivery.
- No commit, push, reset, stash, clean, or manual receipt deletion.
- External noncandidate dirty-worktree churn was preserved and not reverted.

