# Gate-D 운영 하드닝 stage-23 최종 Critic 검토

검토 입력: immutable `/home/cube/projects/richard/traning coach/.gjc/_session-019f8455-334a-7000-99ca-318dfd0e06b1/plans/ralplan/019f8455-334a-7000-99ca-318dfd0e06b1/stage-23-revision.md`; assignment pin `c7cb2bbc138ed4c52304454974d2ea5d60e4e1ddccf6c9d57c1bbc932a50cb79`. 해당 RALPLAN `index.jsonl`의 stage 23 revision receipt가 같은 절대경로와 SHA를 기록함을 확인했다. 제품 파일은 변경하거나 테스트하지 않았고, 현재 구현·테스트·문서를 읽어 대표 경로를 시뮬레이션했다.

## Verdict
**OKAY**

## Claim Checks

- **Artifact pin verified.** Parent RALPLAN index의 stage 23 revision path와 SHA `c7cb2bbc...a50cb79`가 assignment와 일치한다.
- **Stage-23 review-space first-match closure is executable.** Revision 23은 인증 triple과 routing address space를 분리해 `(chat_id, topic_id)` pair가 review space이면 user가 달라도 먼저 그 공간에서 종료하도록 고정한다. Exact configured `user_id`만 `AdaptiveOperatorService`로 진행하고 wrong-user/same-pair는 customer/trainer/generic/model/media/provider로 fall through하지 않는다. 현재 `telegram.py`는 text에서만 `_is_nutrition_operator_space`를 선점하고 command/location/media/callback handler가 분리되어 있으며 adaptive callback은 incoming triple을 canonical owner와 비교한다. 따라서 pair-first guard를 handler 공통 선두에 두고 all-update negative counters를 추가하는 구현 지점과 기대 결과가 명확하다. Registry/config validation의 customer/trainer/schedule/generic pair disjointness도 ambiguous overlap을 startup에서 제거한다.
- **Identity/capability contract remains coherent.** 하나의 full `review_operator` config, exact-review ingress에서만 mint되는 `AdaptiveOperatorCapability`, canonical owner audit snapshot, authority→lifecycle→store lock order, append/reservation 직전 및 provider 직전 pin 재검증이 서로 충돌하지 않는다. 현재 `AdaptiveNutritionConfig`는 chat/topic만 보유하고 `_nutrition_operator_address`는 별도 config를 읽으며 adaptive callback은 모든 customer coordinator를 순회하므로, single config migration과 persisted token-to-customer service가 실제 중복·탐색 결함을 직접 대체한다.
- **Operator UX/session lifecycle is fully selected.** Canonical opener/button와 command alias, complete action enum, select/create의 nullable proposal rule, 10-minute reply-bound `edit_note`, current digest/message binding, UTF-16 bound, canonical `revise_note`, mutation-once/read-replay policy, `publish_pending` restart recovery가 정해져 있다. Executor는 trigger, edit payload, publish action 또는 stale behavior를 발명할 필요가 없다.
- **Stage-23 schedule ordering closes the remaining crash ambiguity.** 현재 `customer_schedule.py`는 `claimed\n` `.claim`을 `O_EXCL`로 생성하고 `release_customer_task_claim`이 unlink하며, Telegram tick은 claim 뒤 provider를 호출한다. Revision 23은 같은 legacy-visible tombstone을 bytes/file/directory까지 먼저 durable하게 만든 후에만 ledger `prepared`를 append/fsync하도록 순서를 고정한다. Tombstone-only는 terminal `recovery_required`, ledger-only는 corrupt/startup refusal이므로 어떤 crash boundary에서도 old reader가 uncertain reservation을 다시 보낼 수 없다.
- **Cutover/startup/rollback path is operationally complete.** Durable fence가 migration 전에 `preparing`으로 기록되고, new startup/compatibility wrapper 모두 `ready`만 허용한다. Stop scheduler → deploy guard → preparing fence → legacy migration/pair reconciliation → ready → enable 순서와 recovery-only rollback이 고정됐고, old-reader zero-send 및 tombstone bytes/fsync/directory-fsync/ledger append/fsync/ready crash tests가 정확한 evidence gate다. 기존 package export, `customer_admin retry`, runbook unlink instruction, Telegram tick은 final six typed APIs와 read-only diagnosis로 교체하도록 scope에 포함된다.
- **Authority TOCTOU and delivery outcomes remain exact.** 모든 action의 pins와 four timing injections가 named tests로 요구되고, post-reservation revoke/owner rotation은 정확히 하나의 reasoned `delivery_unknown`, provider0, no consumed/delivered/audit로 끝난다. P2–P6는 새 child revision, ordered rows, exact Korean UI, provider counts, per-row reset과 final cleanup까지 고정돼 있다.
- **Verification commands are self-contained and present.** Profile and gateway absolute cwd values exist; both specified `.venv/bin/python` interpreters exist. Gateway full/focused/compile and profile full/docs-preflight commands map to existing test/module paths. Full suites provide integration coverage for any ordinary internal module extraction. Actual Telegram/manual Gate-D remains explicitly human-only and cannot be fabricated.
- **Referenced files verified.** Existing gateway targets `gateway/platforms/telegram.py`, `nutrition_coaching.py`, `nutrition_coaching_config.py` and all four named gateway tests exist. Existing profile targets `checkin_cli/customer_schedule.py`, `customer_admin.py`, `adaptive_nutrition.py`, `__init__.py`, all three named profile tests and canonical profile `PILOT_RUNBOOK.md` exist. Root `PILOT_RUNBOOK.md`, Korean guide, `HANDOFF.md`, and HTML report exist. `AdaptiveOperatorService`, session ledger, schedule ledger/fence and preflight receipt are planned additions rather than falsely claimed existing files; their observable contracts are specified. The gateway worktree is already dirty, so execution must begin with the planned frozen snapshot and preserve unrelated/user changes rather than reverting them.

### Representative implementation simulation

1. **All-ingress reservation and menu callback:** extend the canonical adaptive config to a full review triple/version, validate its pair is disjoint, install a pair-first guard for each registered Telegram update kind, and dispatch the exact user only to one gateway-owned service. `an1:<24hex>:<action>` resolves one persisted session/customer/action; wrong-user/same-pair, stale, forwarded, wrong-message and unknown callbacks terminate before generic/lifecycle calls. The plan supplies exact behavior and tests.
2. **Authority-sensitive mutation/send:** ingress mints a capability; shared authority lock then lifecycle/store locks compare all live pins immediately before append/reservation. After durable reservation and lock release, final same-order validation gates the single provider call. Injected revoke/rotation appends the one prescribed linked unknown row and provider remains zero. This can replace current raw-triple `set_persisted_delivery`, approve/activate/rollback and delivery paths without an API decision left open.
3. **Scheduled migration/restart:** under the exclusive schedule lock, write/fsync fence, create and directory-fsync legacy tombstones, append/fsync ledger rows, then expose `ready`. Runtime resolves immutable destination/body before reservation, persists sending before provider, receipt before audit, and receipt-backed reconciliation never calls provider. Legacy, partial-pair, corrupt, mixed-version, crash and old-reader cases all have single fail-closed outcomes.

## Missing Evidence
None.

## Approval Boundary
Execution may proceed in the two codebases and listed docs/tests using revisions 21–23 as the binding contract, preserving the current dirty worktree and choosing only ordinary internal file/class decomposition. It may not weaken pair-first review-space reservation, exact review-vs-owner separation, capability pins/lock order, persisted session/message binding, terminal unknown/no-retry, tombstone-first ordering/fence, exact P2–P6 outcomes, or release gates. Real credentials/accounts, live Telegram Gate-D, real-customer activation/delivery, and owner rollout approval remain outside approval and human-only.

## Summary
- **Clarity:** exact routing precedence, UX/session states, ledger ordering, fence and outcomes are specified.
- **Verifiability:** zero-downstream counters, ordered rows, provider counts, crash points, old-reader simulation and exact commands are present.
- **Completeness:** all prior blockers and the stage-23 Architect closure points are closed across code, migration, tests, docs and cleanup.
- **Big Picture:** production-path Gate-D is isolated while automated work and human/live approval remain separated.
- **Principle/Option Consistency:** pair-first auth, review-vs-owner identity, at-most-once/no-retry, append-only evidence and chosen option B align.
- **Alternatives Depth:** minimal callback patch, owner-topic conflation and REPL are rejected; persisted service and tombstone-first durable ledger are selected.
- **Risk/Verification Rigor:** ingress confusion, stale authority, provider ambiguity, crash/restart/downgrade and scenario contamination each have explicit gates.

## Required Changes
None.
