# Gate-D 운영 하드닝 실행 계획

## Status
Planning only. Pending consensus review and execution handoff.

## Goal
실제 Telegram Gate-D 수동 리허설 전에 자동으로 해결 가능한 운영 차단 문제를 모두 제거하고, 인간만 수행할 수 있는 계정·토큰·수동 리허설·실고객 활성화만 명시적으로 남긴다.

## Principles
1. Canonical owner는 감사·승인 신원이고 Topic 59 review address는 ingress 인증 표면이다. 둘을 혼용하지 않는다.
2. 전송은 reservation-first, provider-at-most-once, unknown은 terminal이며 재전송하지 않는다.
3. 모든 변경 권한은 transition 직전 refreshed full triple과 current digest로 재검증한다.
4. 운영 진입점은 host-owned typed API/Telegram menu뿐이며 REPL·JSON 직접 편집은 증거로 인정하지 않는다.
5. 자동 검증과 수동 Gate-D/실고객 rollout 승인을 분리한다.

## Decision Drivers
1. 오발송·중복발송·stale authority를 provider 호출 전에 차단할 것.
2. 실제 운영자가 Topic 59에서 재현 가능하고 한국어 상태 문구만 보고도 안전한 결정을 할 수 있을 것.
3. 장애·재시작·provider 불확실성 뒤에도 append-only 증거로 조정 가능할 것.

## Options
### A. Existing callbacks에 최소 패치
- 장점: 변경량이 작다.
- 단점: lifecycle 시작 진입점, 권한 역할 분리, schedule receipt 복구가 계속 흩어진다.
- 판정: 일부 단기 수정에만 사용하고 전체안으로는 기각.

### B. Host-owned adaptive operator control plane + shared transition/reconciliation contracts (Chosen)
- 장점: Topic 59 인증, canonical owner audit, proposal/card lifecycle, delivery/recovery를 한 경계에서 검증한다.
- 단점: gateway/profile/docs/tests 여러 표면을 함께 수정해야 한다.
- 선택 이유: 실제 Gate-D가 production path를 그대로 검증하며 REPL 우회를 제거한다.

### C. Canonical owner topic을 Topic 59로 변경
- 장점: 현재 callback equality를 단순히 만족한다.
- 단점: canonical audit identity를 UI 위치에 종속시키고 normal flow와 충돌한다.
- 판정: 보안·모델 경계 훼손으로 기각.

## Scope and Workstreams

### WS1 — Review identity and callback authorization
Targets: `gateway/platforms/telegram.py`, `nutrition_coaching.py`, config model/tests.
- configured Topic 59 review triple을 ingress 인증에 사용한다.
- refreshed canonical owner full triple은 lifecycle actor/audit identity로 전달한다.
- wrong user/chat/topic, owner rotation, canonical owner topic != review topic positive path를 검증한다.
- callback token은 opaque/bounded/current revision만 허용한다.

### WS2 — Host-owned operator entrypoint/menu
Targets: Telegram adaptive handlers/menu, coordinator, gateway tests.
- Topic 59 전용 typed actions: create/publish/view/edit/hold/release/approve/activate/delivery-enable/delivery-revoke/send/reconcile.
- customer key는 live enabled registry에서 고르고 KST evaluation day는 host가 결정한다.
- raw destination/text/digest 입력, REPL, generic append를 금지한다.
- 각 카드에 current revision/digest/state를 표시하고 stale callback을 명시적으로 거부한다.

### WS3 — Safe delivery UX and reconciliation
Targets: Telegram callback result mapping, adaptive workflow, tests.
- `delivery_unknown`: “전송 결과를 확인할 수 없습니다. 다시 보내지 마세요. 조정이 필요합니다.”
- provider receipt persisted but audit pending: “고객 전송 영수증 확인. 재전송 금지. 감사 기록 복구 필요.”
- delivered/sent_audited만 성공 문구 사용.
- opaque delivery ID만 operator evidence에 노출한다.
- double tap/restart/reconcile은 provider 0 또는 총 1회를 증명한다.

### WS4 — Authority TOCTOU hardening
Targets: `set_persisted_delivery`, `activate_latest`, `rollback_latest`, focused tests.
- lifecycle/store lock 안에서 live registry refresh → supplied full actor triple 비교 → source/registration/consent/activation/config pins 재검증 → append.
- owner가 검증 직후 바뀌면 feature epoch/overlay/prepared/committed/provider 모두 0.
- profile admin authority TOCTOU는 current live loader가 downstream을 fail-safe 차단하도록 유지하되, Gate-D 전 preflight에서 stale approval을 거부하고 후속 강화 항목을 문서화한다.

### WS5 — Scheduled prompt durable intent/receipt
Targets: `customer_schedule.py`, Telegram scheduled tick, tests/runbook.
- claim-only 구조를 prepared/sending/delivered/unknown/audited intent ledger로 확장한다.
- provider 호출 전에 immutable destination/body/day를 예약한다.
- receipt 저장 후 audit; timeout/unknown은 terminal no-retry.
- provider receipt가 있으면 resend 없이 reconcile한다.
- 기존 claim migration은 additive/read-compatible, corrupt ledger fail-closed.

### WS6 — Isolated test-profile preflight
Targets: supported setup/preflight code, test fixture/config template, runbook/tests. Never manipulate real credentials/accounts.
- adaptive config required fields, delivery=false, private modes/no symlink.
- fresh disposable customer with current KST plan window.
- operator/customer/trainer distinct identity requirement and separate bot documented/validated.
- approved policy/food-catalog/derived constraints/registration/activation receipts and canonical reconciliation required.
- bounded receipt returns only counts/digests/epoch; no Telegram IDs/raw data.

### WS7 — Gate-D and operator docs
Targets: profile `PILOT_RUNBOOK.md`, Korean guide, `HANDOFF.md`, `choi_coach_system_report.html`.
- obsolete direct `enable` and direct registry edits removed; guarded activate only.
- stale counts replaced or explicitly historical.
- separate fresh revisions: P2 success, P3 timeout/unknown, P4 provider-success/audit-failure, P5 revoke-after-reservation, P6 owner-change.
- actual KST rollover, stale click, duplicate tap, incomplete check-in, low adherence, safety, disable/restart mandatory rows.

## Execution Order
1. Freeze snapshot and map existing public contracts/tests.
2. WS1 identity split and WS4 authority TOCTOU first; they gate all later actions.
3. WS2 host entrypoint/menu on the hardened authority boundary.
4. WS3 delivery UX/result typing.
5. WS5 scheduled prompt receipt ledger/recovery.
6. WS6 preflight and WS7 documentation/migration.
7. Focused tests per workstream, then full profile/gateway regression.
8. Cleaner → frozen Architect + executor QA/red-team → terminal Critic.
9. Leave manual Gate-D checklist pending; do not touch real accounts or activate customers.

## Compatibility and Migration
- Existing canonical EventStore, draft outbox, adaptive ledgers, callbacks, and normal customer/trainer routes remain authoritative.
- New schedule delivery ledger is additive; old claims are imported as conservative unknown/claimed state and never blindly resent.
- Existing callback tokens remain bounded but stale/unknown tokens reject; no dead aliases.
- `food-catalog.json` remains canonical; `catalog.json` validated migration-read fallback only.

## Acceptance Criteria
1. Topic 59 configured review actor can operate when canonical owner topic differs; all wrong triples reject with zero lifecycle/provider rows.
2. Supported live menu can create and publish a production proposal without REPL/direct file edits.
3. Every operator action delegates to canonical lifecycle and exact latest revision/digest.
4. Unknown and audit-pending Telegram messages are unambiguously no-retry/reconcile; success only for delivered/audited.
5. Owner change injected after first auth in enable/activate/rollback leaves feature/overlay/transition/provider unchanged.
6. Scheduled prompt timeout/restart never duplicates; receipt reconciliation sends zero times; corrupt ledger blocks.
7. Preflight rejects missing config/artifacts, stale window, same-role identities, unsafe modes/symlinks, and delivery=true.
8. P2–P6 Gate-D script has separate immutable revisions and exact expected Telegram/ledger/provider outcomes.
9. Automated red-team covers wrong routing, stale/double clicks, KST rollover, incomplete, low-adherence, safety, disabled/restart.
10. Full profile and gateway suites/compile gates pass; Architect CLEAR/APPROVE, QA/e2e/red-team pass, terminal Critic OKAY.
11. Real-customer activation/delivery remains disabled and manual Gate-D remains pending.

## Verification Plan
### Unit
- address-role resolver, result-to-Korean-message mapping, owner refresh comparison, schedule ledger schema/digest/state transitions, KST day/window, preflight validators.

### Integration
- Topic 59 != owner topic positive route; wrong triple negatives; menu create→edit→approve→activate; persisted enable/revoke; unknown/audit reconcile; schedule provider receipt/no-resend; corrupt journal.

### E2E/Red-team
- disposable fixture with three identities and separate bot stubs.
- P2 success exactly once; P3 timeout terminal; P4 receipt/audit recovery no resend; P5 revoke barrier provider0; P6 owner rotation provider0.
- stale callback, duplicate send, 23:59→00:01, incomplete draft, low adherence operator-only, safety zero-send, disable/restart.
- actual Telegram/manual proof remains human-only and is not fabricated.

### Observability
- opaque event/transition/delivery IDs, state, epoch/digests, provider call count; never token, raw check-in, Telegram triple, or customer text.

## Pre-mortem
1. **Role conflation returns:** normal tests pass but Topic 59 real operator rejects. Mitigation: owner-topic-different positive integration is mandatory.
2. **Provider sent, UI says retry:** duplicate customer message. Mitigation: typed delivery outcome mapping; unknown/audit-pending no-retry wording and receipt reconciliation tests.
3. **Schedule timeout loses or duplicates prompt:** claim lacks receipt truth. Mitigation: reservation/receipt journal and restart failure injection.
4. **Stale owner transition commits:** old operator changes delivery/overlay. Mitigation: append-adjacent refreshed full-triple equality under lock.
5. **Gate-D contaminates real routes:** test config points to production. Mitigation: preflight proves isolated registry/bot/distinct identities, main customers empty/delivery false, and refuses real destinations.

## Human-only Remaining Work
- Create/manage Telegram test accounts, bot token, groups/topics, and distinct identities.
- Enter secrets through approved secret storage.
- Execute the live Gate-D Telegram rehearsal and capture private opaque evidence.
- Explicitly approve real-customer rollout after all rows pass.

## ADR
### Decision
Adopt a host-owned adaptive operator control plane that separates review-surface authentication from canonical owner audit identity, uses durable exactly-once outcome ledgers, and leaves live Gate-D to the human owner.
### Drivers
Mis-send prevention, reproducible production-path rehearsal, crash/restart reconciliation.
### Alternatives considered
Minimal callback patch; canonical owner moved to Topic 59; ad-hoc REPL. Rejected for incomplete boundary coverage or authority corruption.
### Consequences
More explicit lifecycle and ledger code, but fewer hidden fallbacks and a reproducible one-session Gate-D.
### Follow-ups
Profile-admin approval authority transaction may be further unified after Gate-D; no such residual may bypass gateway revalidation.

## Intent Reconciliation
- User wants all automatable work completed autonomously, then one more workflow/red-team pass and fixes.
- Manual account/token/Telegram actions and actual Gate-D remain for the user.
- No real-customer activation or delivery is authorized by this plan.

## RALPLAN-DR
- Mode: deliberate.
- Chosen option: B, host-owned control plane plus durable reconciliation.
- Main tension: narrow patch speed versus coherent operational authority; coherence wins because Gate-D must exercise production paths.
- Completion boundary: code/tests/docs/reviews complete, manual Gate-D pending.


# Revision 21 — Consensus Review Corrections

## Shared Authority Transaction Contract
- Introduce a typed `AdaptiveOperatorCapability` minted only by exact configured review triple ingress. Fields: schema/version, opaque capability ID, review address/version, canonical owner address/version, customer key, action, proposal/revision digest, config/registry/consent/activation digests, issued/expiry KST, nonce digest.
- Canonical configuration has one full `review_operator: {user_id, chat_id, topic_id, version}` under adaptive config. Migration accepts the existing chat/topic only for disabled startup diagnostics; production enable fails until full triple is explicitly configured. `_nutrition_operator_address` and adaptive handlers consume this one model.
- Lock order is fixed: registry/authority shared file lock → adaptive lifecycle lock → adaptive store lock. Registry/consent/activation/config writers that affect adaptive authority take the shared authority lock; provider calls occur after reservation and locks release, followed by one final capability/pin validation under the same authority+lifecycle/store order.
- Every transition records both `authenticated_review_operator` and `canonical_owner_snapshot` plus authority/config versions. Lifecycle APIs accept only the capability, never a raw triple, on the production path; legacy raw-triple calls are test/shadow-only and rejected by production coordinators.
- Linearization: refreshed authority and capability equality are checked immediately before append/reservation. Cross-process change either wins before this point and rejects the transition with zero rows, or occurs after a durable reservation and is caught by the final pre-provider validation, producing terminal unknown/provider0.

## Operator Service and Persisted Callback Contract
- Add one gateway-owned `AdaptiveOperatorService` used by Telegram and any loopback console. It resolves exact customer directly from a persisted session token; it never probes every customer coordinator.
- Persist private `adaptive-operator-sessions.jsonl` records with opaque nonce/token hash, action allowlist, customer key, revision/digest, epoch, source/registration/authority pins, originating message/chat/topic, review config version, issued/expiry KST, state `issued|consumed|expired|revoked`, row digest and predecessor.
- Telegram menu creation reserves a session before posting the card. Actions are create, publish/view, edit, hold/release, approve, activate, delivery-enable/revoke, send, reconcile. Each has exact idempotence: read actions replay safely; mutating actions consume once; duplicate/stale/forwarded/wrong-message clicks reject without lifecycle rows.
- Callback format remains bounded `an1:<24hex>:<action>` and maps to exactly one session/customer/action. Config reload/owner rotation revokes stale sessions.

## Scheduled Prompt Ledger and Cutover
- New private `scheduled-deliveries.jsonl` schema pins schedule key/day/kind, customer key, immutable body/template digest, exact destination, registry/config digest, reservation ID, provider receipt/message ID and states `prepared|sending|delivered|unknown|sent_audited|abandoned`.
- Transaction: prepared persisted+fsync → sending reservation persisted → provider once → receipt persisted immediately as delivered → canonical schedule audit → sent_audited. Timeout/exception/malformed receipt becomes unknown; unknown never retries. Existing receipt reconciles audit without provider.
- Exclusive cutover command takes schedule lock, inventories every legacy `claimed` file, writes `legacy_unknown` evidence (represented as terminal `unknown` with `legacy_claim_digest`) unless an independently verified provider-not-attempted fact exists, and preserves legacy files read-only. It never infers unsent from absence of a receipt.
- Existing `retry` may no longer unlink a legacy or new uncertain claim. It becomes read-only diagnosis/reconcile and rejects manual release unless a typed `provider_not_attempted` receipt from the supported transport exists.
- Daily and weekly recipient/body semantics are explicit fields. Migration/corrupt/mixed-version/downgrade/duplicate rows fail closed. Tests inject crash after each append/provider boundary and restart.

## P2–P6 Exact State Machines
- Fixture preconditions: one disposable isolated customer, current KST window, three distinct user identities, separate bot, approved artifacts, reconciled canonical prefix, epoch activation true/delivery false, zero prior adaptive reservations. Each Pn is a new latest child revision with predecessor P(n-1), separately approved and activated; no revision is reused for mutually exclusive transport outcomes.
- P2 success: enable persisted, send once, expected rows attempt_started→attempt_consumed→delivered→sent_audited, provider=1, duplicate tap provider remains 1.
- P3 timeout/malformed receipt: expected attempt_started→attempt_consumed→delivery_unknown, provider=1, no delivered/audit; retry rejects/provider stays 1; UI says unknown/no retry.
- P4 provider success then local audit failure: receipt/delivered persisted, sent_audited absent; UI says reconcile required; reconcile adds sent_audited with provider=0 additional.
- P5 revoke after reservation: test-only barrier opens only in injected transport harness after attempt_started and before final validation; normal persisted revoke writer runs; expected delivery_unknown linked to reservation, no consumed/provider, epoch delivery false. Barrier symbol is test-only dependency injection and unreachable from production config/API.
- P6 owner rotation after reservation/transition auth: same test-only barrier; owner change uses canonical registry writer under shared authority lock; expected rejected/aborted or linked delivery_unknown, no provider, exact old/new authority versions recorded.
- Human Telegram Gate-D does not claim deterministic P5/P6 timing; automated barrier evidence proves those races, while the human session verifies visible UI/reconciliation and distinct routes.

## Typed Preflight and Documentation Authority
- Public read-only `audit_gate_d_preflight(profile_root, customer_key) -> GateDPreflightReceipt`; it never creates accounts, secrets, customers, artifacts, consent, activation, or delivery flags. It validates user-provisioned distinct identities/bot, current KST dates, full review triple, private modes/symlinks, enabled isolation, main-profile containment, artifact/registration/activation receipts, canonical reconciliation, and all feature flags false. Receipt contains only booleans/counts/digests/epoch.
- Profile `PILOT_RUNBOOK.md` is authoritative. Korean guide/HANDOFF/HTML link to its version+digest and may not duplicate executable direct-call instructions. Remove REPL/direct lifecycle calls and obsolete `enable`/registry edits; document only host-owned menu/API.
- Manual boundary: user supplies Telegram identities, bot token, secrets, topics, disposable registration/consent and executes live Gate-D. Automation may validate but never create/confirm/activate/send to real accounts.

## Exact TOCTOU Matrix
For delivery-enable/revoke, approve, activate, rollback, send reservation, and reconcile: inject owner, review config, customer enabled, consent, activation receipt, registration, source, destination, policy/catalog/constraints, epoch changes at (a) before capability mint, (b) after mint before append, (c) after reservation before provider. Expected: (a/b) zero rows/provider; (c) linked unknown/provider0 except receipt-backed reconciliation. Every action has named focused tests.

## Concrete Verification Commands
- Profile: `python -m pytest -q` and `python -m compileall -q checkin_cli` from profile workspace.
- Gateway focused: `.venv/bin/python -m pytest -q tests/gateway/test_adaptive_nutrition.py tests/gateway/test_nutrition_coaching.py`.
- Gateway cumulative: `.venv/bin/python -m pytest -q tests/gateway/test_adaptive_nutrition.py tests/gateway/test_nutrition_coaching.py tests/gateway/test_telegram_physique_checkin.py` plus `py_compile` for changed gateway modules.
- Docs/preflight: focused parser/link/source-of-truth tests; no real network.
- Final frozen lanes: cleanup zero blockers, Architect CLEAR/APPROVE, executor QA/e2e/red-team pass with artifact matrix, terminal Critic OKAY.


# Revision 22 — Final Execution Exactness

## All Topic-59 Ingress Reservation
The adaptive review space is reserved before generic routing for every Telegram update kind: text, commands (including unknown commands), location, photo/video/document/audio/voice/sticker/contact, edited/channel posts, and unrelated callback data. Exact configured review triple may reach only `AdaptiveOperatorService`; all wrong triples reject or follow their own registered route. No review-space update may reach generic `handle_message`, model/provider, vision/media cache, customer/trainer wizard, or raw append. Tests in `test_telegram_group_gating.py` and adaptive tests assert all downstream call counters zero.

## Exact Operator UX and Session Schema
- One canonical opener: authenticated Topic-59 reply-keyboard button `적응형 영양 검토` (and exact `/adaptive_review` command alias handled by the same function). It renders enabled eligible customer buttons from live registry; zero/ambiguous selections fail closed.
- Action enum: `select`, `create`, `view`, `edit_note`, `hold`, `release`, `approve`, `activate`, `delivery_enable`, `delivery_revoke`, `send`, `reconcile`, `back`. `publish` is not a separate callback: successful create/edit/hold/release posts/replaces a card transactionally after persisting its session.
- Pre-proposal session permits `proposal_digest=null`, `revision=null` only for `select/create`; all other actions require exact non-null values. On create success it is superseded by a new card/session bound to the proposal.
- Typed edit: clicking `edit_note` persists `awaiting_input` session bound to originating message/customer/current digest with 10-minute expiry. The next text from the exact review triple/message reply is validated UTF-16<=4000, passed only to canonical `revise_note`, then session consumed and a new card posted. Non-text/media, no reply, wrong actor/message, restart-expired, or stale revision rejects; it never accepts raw calories/macros/destination.
- Card-post failure after lifecycle append persists `publish_pending`; restart reconciles by posting the exact persisted card once, never repeating the lifecycle mutation. Duplicate open/view is read-idempotent; all mutation callbacks consume once.

## Complete Action Authority Matrix
Named tests cover `select/create/view/edit_note/hold/release/approve/activate/delivery_enable/delivery_revoke/send/reconcile/back`. For each mutation, pins include review config+actor, canonical owner/version, enabled customer, consent/activation, registration/source/policy/catalog/constraints, proposal/revision, epoch and originating message/session. Injection points: before mint, after mint/before lifecycle append, immediately before append, after reservation/before provider. Read actions may replay only while pins/current message remain exact. Mutations on any mismatch create zero lifecycle/session state beyond an explicit rejected-attempt audit; post-reservation send mismatch creates exactly one linked `delivery_unknown` and provider0.

## Concrete Schedule Downgrade Barrier and Rollout
Chosen mechanism: dual-write an old-format immutable tombstone claim for every new scheduled reservation, because old binaries do not understand the new ledger.
1. Deploy reader guard first: new binary recognizes ledger schema marker and tombstones; startup refuses unsupported higher schema. No schedule sending yet.
2. Under exclusive schedule lock, create schema/version marker; migrate legacy claims to new terminal unknown rows and keep original claim bytes.
3. Enable new writer: atomically/fsync write prepared ledger row and legacy `.claim` tombstone before eligibility is exposed. If either write fails, abort with no provider call; recovery completes the pair or marks recovery-required.
4. Old binary sees `.claim` and cannot resend any new intent. Downgrade is operationally forbidden after schema marker; if forced, tombstones still block send. Rollback is recovery-only: stop scheduler, preserve ledger/tombstones, run compatible binary; never delete tombstones.
5. Final APIs: `reserve_customer_task_delivery`, `mark_customer_task_sending`, `mark_customer_task_delivered`, `mark_customer_task_unknown`, `mark_customer_task_sent_audited`, `reconcile_customer_task_delivery`. `release_customer_task_claim` and CLI `retry` are removed from production exports/commands; legacy diagnosis command is read-only.
Tests cover partial marker/migration/tombstone pairs, crash at each fsync/provider boundary, old-reader simulation, mixed versions, duplicate tasks, corrupt rows, daily/weekly semantics, and rollback order.

## P2–P6 Exact UI, Rows, and Cleanup
Common Korean card prefix includes `테스트 전용`, customer label, KST day, revision, state. Evidence is opaque IDs only.
- P2: UI `고객 전송과 감사 기록이 완료되었습니다.` Rows exactly attempt_started→attempt_consumed→delivered→sent_audited; provider=1. Duplicate UI `이미 처리된 전송입니다.` provider stays1.
- P3: UI `전송 결과를 확인할 수 없습니다. 다시 보내지 마세요. 조정이 필요합니다.` Rows attempt_started→attempt_consumed→delivery_unknown; provider=1; retry rows/provider0.
- P4: UI `고객 전송 영수증은 확인됐습니다. 재전송하지 말고 감사 기록을 복구해 주세요.` Rows attempt_started→attempt_consumed→delivered, no sent_audited; reconcile adds sent_audited, provider additional=0; then UI completion.
- P5: after reservation barrier/revoke, single outcome is linked `delivery_unknown` reason `delivery_revoked_after_reservation`; no consumed/delivered/audit/provider. Same unknown/no-retry UI.
- P6: after reservation barrier/owner rotation, single outcome is linked `delivery_unknown` reason `owner_changed_after_reservation`; no consumed/delivered/audit/provider. Same unknown/no-retry UI. Transition-only owner rotation before a reservation produces `transition_aborted`, not an alternative P6 send outcome.
Baseline reset: each Pn is a new latest child, approved/activated separately. After each row, reconcile/assert counts, revoke delivery, and persist scenario terminal receipt. Final cleanup: delivery false, rollback active overlay, disable disposable customer, stop scheduler/gateway, retain immutable evidence.

## Runbook Ownership
Canonical executable runbook is the profile `workspace/checkin_cli/PILOT_RUNBOOK.md`. Root `/home/cube/projects/richard/traning coach/PILOT_RUNBOOK.md` becomes a short digest-pinned pointer only (no executable duplicate). Korean guide, HANDOFF, and HTML link to canonical runbook version/digest and may summarize concepts but contain no commands.

## Exact Release Commands
- Profile full: `python -m pytest -q && python -m compileall -q checkin_cli`.
- Gateway full repository tests: `.venv/bin/python -m pytest -q tests/gateway`.
- Required focused: `.venv/bin/python -m pytest -q tests/gateway/test_adaptive_nutrition.py tests/gateway/test_nutrition_coaching.py tests/gateway/test_telegram_physique_checkin.py tests/gateway/test_telegram_group_gating.py`.
- Exact compile: `.venv/bin/python -m py_compile gateway/platforms/nutrition_coaching.py gateway/platforms/telegram.py gateway/platforms/nutrition_coaching_config.py`.
- Docs/preflight focused profile tests plus link/digest assertions; no network.


# Revision 23 — Architect Closure

## Review-Space First-Match Invariant
Configured review address space is the `(chat_id, topic_id)` pair. Registry/config validation requires this pair to be disjoint from every customer, trainer, schedule, and generic reserved space. Dispatch order for every Telegram update kind is first-match by address space: if chat/topic equals review space, the update is handled only by `AdaptiveOperatorService`; exact configured review `user_id` proceeds and every other user terminates with rejection. Wrong-user updates inside review space may never fall through to customer/trainer/generic/model/media/provider routes. Only updates outside the review pair may resolve another registered route. Named all-ingress tests assert wrong-user/same-review-pair gives every downstream counter zero.

## Crash-Safe Schedule Pair Ordering and Startup Fence
The legacy tombstone is always durable first: under exclusive schedule lock, create/write/fsync immutable old-format `.claim` tombstone and fsync its directory; only then append/fsync the new ledger `prepared` row. A tombstone without a matching ledger row is terminal `recovery_required` and blocks sending until reconciliation; it is never removed or interpreted as unsent. A ledger row without tombstone is invalid/corrupt and scheduler refuses startup.
A durable cutover fence/version marker is written and fsynced before migration. Both new startup and the supported compatibility wrapper require fence state `ready`; state `preparing|recovery_required` stops all schedule sends. Deployment order: stop scheduler → deploy reader/startup fence guard → write preparing fence → migrate legacy claims and reconcile tombstone-ledger pairs → set ready → enable new scheduler. Rollback/downgrade keeps scheduler stopped and uses recovery-only compatible binary; old-reader simulation must see every tombstone and send zero. Crash tests cover before/after tombstone bytes/fsync/directory fsync, ledger append/fsync, and ready fence.

## Self-contained Verification Commands
- Profile full: `cwd=/home/cube/.hermes/profiles/physique-coach/workspace/checkin_cli; .venv/bin/python -m pytest -q && .venv/bin/python -m compileall -q checkin_cli`.
- Gateway full: `cwd=/home/cube/projects/richard/hermes-agent; .venv/bin/python -m pytest -q tests/gateway`.
- Gateway focused: same gateway cwd; `.venv/bin/python -m pytest -q tests/gateway/test_adaptive_nutrition.py tests/gateway/test_nutrition_coaching.py tests/gateway/test_telegram_physique_checkin.py tests/gateway/test_telegram_group_gating.py`.
- Gateway compile: same gateway cwd; `.venv/bin/python -m py_compile gateway/platforms/nutrition_coaching.py gateway/platforms/telegram.py gateway/platforms/nutrition_coaching_config.py`.
- Profile docs/preflight focused: profile cwd/interpreter; `.venv/bin/python -m pytest -q tests/test_customer_schedule.py tests/test_customer_admin.py tests/test_adaptive_nutrition.py` plus canonical runbook link/digest assertions in those tests.
