# Gate-D 운영 하드닝 계획 Critic 검토

검토 입력: immutable planner artifact `stage-20-planner.md` (assignment pin `f27c02720daac1601d1a58c219ecb9f90f48d142bbabe0428c7c96654b322ea9`). 제품 파일은 변경하거나 테스트하지 않았고, 현재 구현과 문서만 읽어 대표 경로를 시뮬레이션했다.

## Verdict
**ITERATE**

원칙과 수동 rollout 경계는 대체로 타당하지만, 선택안 B를 구현할 핵심 계약 세 곳—review identity의 canonical config, Telegram operator control plane, scheduled-delivery ledger/migration—이 executor가 추측 없이 구현할 수준으로 정해지지 않았다. 특히 schedule migration은 열거한 상태 집합과 `unknown/claimed` 변환 설명이 서로 맞지 않으며, 기존 retry/release 경로와 downgrade 안전성이 범위에서 빠져 있다.

## Claim Checks

- **WS1의 현재 결함은 확인됨.** `/home/cube/projects/richard/hermes-agent/gateway/platforms/telegram.py::_handle_adaptive_nutrition_callback`은 live registry를 refresh한 뒤 incoming `(user, chat, topic)`을 canonical owner full triple과 직접 비교한다. 따라서 owner topic과 Topic 59 review topic이 다르면 positive path가 막힌다. 같은 파일에는 normal draft용 `_nutrition_operator_address` / `_nutrition_operator_actor`가 이미 있어 configured ingress와 owner actor를 분리하는 선례가 있다.
- **하지만 review triple의 source of truth는 계획에 매핑되지 않음.** `gateway/platforms/nutrition_coaching_config.py::AdaptiveNutritionConfig`는 현재 `operator_chat_id`, 고정 topic 59, `delivery_enabled`만 보유하고 user id는 없다. 별도 `nutrition_coaching.operator_review`는 `TelegramAdapter._nutrition_operator_address`가 직접 raw config에서 읽으며 `NutritionCoachingConfig` 모델 필드가 아니다. 현재 profile config에는 `adaptive_nutrition.operator_chat_id/topic_id`만 있고 `nutrition_coaching.operator_review`는 없다. “configured review triple”을 어느 계약에 추가/재사용할지 executor가 결정해야 한다.
- **Canonical owner 전달 경로는 존재함.** `NutritionCoachingCoordinator.adaptive_nutrition_coordinator`가 production coordinator를 만들고, `AdaptiveNutritionCoordinator._require_operator_owner`는 refresh된 full owner triple을 요구한다. Ingress에서 configured review address를 인증한 뒤 refreshed `coordinator.owner.key`를 lifecycle actor로 넘기는 방향은 현재 구조와 맞지만, review actor와 audit actor의 기록 정책은 계획에 없다.
- **WS2는 현 구현상 신규 control plane이 필요함.** production code에서 `create_production_proposal` 또는 `issue_callback`을 호출해 Telegram card를 발행하는 경로가 없다. Telegram은 `an1:` callback 소비만 구현하고, `edit/reconcile/send`는 `operator_input_required`를 반환한다. `_handle_adaptive_nutrition_callback`은 이 중 send만 특별 처리하고, edit/reconcile은 실제 입력 화면 없이 안내만 한다. 계획의 create/publish/menu/edit/reconcile 흐름은 해당 handler/session/keyboard symbol과 상태 계약을 특정하지 않는다.
- **Topic 59 generic-ingress 차단은 부분적임.** `_handle_text_message`는 configured `operator_review` space의 일반 text를 소모하지만 `_handle_command`, location/media 등은 같은 reservation을 보장하지 않는다. “raw input/REPL 금지”와 host-owned typed entrypoint를 만족하려면 모든 관련 Telegram update 종류의 명시적 reservation과 negative tests가 필요하다.
- **WS4의 지목한 symbols는 실재함.** `set_persisted_delivery`, `activate_latest`, `rollback_latest`가 `gateway/platforms/nutrition_coaching.py`에 있다. `set_persisted_delivery`는 supplied owner를 lock 전 확인한 뒤 lock 내부 append에는 반복 호출한 live owner를 쓰며 supplied triple과 append-adjacent equality를 고정하지 않는다. `activate_latest`/`rollback_latest`도 multi-step journal/overlay/epoch 변경을 수행하므로 owner-change injection의 정확한 hook과 zero-delta 기대치가 필요하다. `revise_note`, hold/release, approve, deliver에도 별도 locked revalidation이 이미 있으므로 “모든 변경 권한” 원칙을 지키려면 전체 action matrix를 명시해야 한다.
- **WS5의 현 상태는 claim-only임.** profile `checkin_cli/customer_schedule.py::claim_customer_task`는 `.../customer-schedule-claims/<customer>/<day>/<kind>.claim`에 `claimed\n`만 fsync하고, `release_customer_task_claim`은 파일을 삭제한다. `TelegramAdapter._send_nutrition_coaching_tick`은 claim 후 daily customer 또는 weekly owner provider call을 수행하지만 receipt/audit을 저장하지 않는다.
- **WS5 영향 범위가 계획보다 큼.** `checkin_cli/__init__.py`가 claim/release API를 export하고, `checkin_cli/customer_admin.py`의 `retry`가 release API를 호출한다. `tests/test_customer_schedule.py`, `tests/test_customer_admin.py`, gateway tick tests, 두 runbook의 outage/retry 절차도 이 동작에 결합돼 있다. 현재 Targets에는 이 callsite/API/doc migration이 완전하게 열거되지 않았다.
- **문서 drift 주장은 확인됨.** workspace `choi_coach_system_report.html`에는 직접 `enable`, registry 수정, 과거 75/701/109 수치가 남아 있다. workspace와 profile에 서로 다른 `PILOT_RUNBOOK.md` 사본이 있으며, profile 사본은 Gate-D §4A/§10a–b를 포함하지만 workspace 사본은 더 짧다. 어느 사본이 canonical인지와 동기화/제거 방침이 필요하다.
- **수동 경계 자체는 일관됨.** plan은 real customer activation/delivery를 금지하고 계정·token·live Telegram rehearsal을 human-only로 남긴다. 다만 WS6의 “separate bot documented/validated”와 “distinct identity validated” 중 live-account 사실은 secret/API 접근 없이 자동 preflight가 증명할 수 없으므로 config-shape 자동 검증과 human attestation을 나눠야 한다.

### Representative implementation simulation

1. **Topic 59 callback fix:** executor는 `_handle_adaptive_nutrition_callback`에서 incoming address를 configured review triple과 비교하고 refreshed owner triple을 `handle_callback_token`에 넘길 수 있다. 그러나 triple을 `AdaptiveNutritionConfig`에 추가할지 `nutrition_coaching.operator_review`를 canonical로 삼을지, 기존 `operator_chat_id`를 migrate/remove할지, review user가 owner user와 달라도 허용할지 계획이 정하지 않아 config/schema/tests/rollout이 갈린다.
2. **Menu create→publish→edit:** 현재 Telegram production code에는 proposal create/publish 호출, adaptive customer-selection token, edit session, card keyboard builder가 없다. 계획만으로는 command인지 reply keyboard인지, customer selection callback이 어떻게 registry generation에 bind되는지, KST day가 어느 순간 고정되는지, card publication failure/duplicate click을 어떻게 복구하는지 결정할 수 없다.
3. **Scheduled timeout/restart:** 현 tick은 claim 직후 provider를 호출한다. 새 ledger를 넣으려면 intent key/path/schema, `prepared→sending`의 irreversible boundary, provider-success/process-crash-before-receipt 처리, receipt/audit reconciliation API, weekly-owner와 daily-customer destination typing, legacy `.claim` 변환, `retry` 명령의 허용 상태를 먼저 정해야 한다. 현재 계획은 state set에 없는 `claimed`를 migration 결과로 언급하고 downgrade 시 old code가 새 ledger를 무시해 재전송할 위험도 다루지 않는다.

## Missing Evidence

### Definitely missing

1. **Executable review-address/config contract.** Exact config key/model, required fields/defaults, current-config migration, fail-closed behavior, allowed relationship between review and owner triples, and whether ingress identity is separately audited are absent.
2. **WS2 file/symbol and interaction contract.** No concrete host trigger, Telegram handler/keyboard/session symbols, customer-selection token format/generation binding, edit input lifecycle, publish behavior, or per-action result mapping is specified. “publish” is not mapped to an existing lifecycle symbol.
3. **Complete Topic 59 ingress reservation.** Command/media/location/generic-agent fallthrough policy and tests are absent.
4. **Schedule ledger schema/state machine.** Ledger location/version/permissions, immutable fields, dedupe key, legal transitions, fsync/locking boundary, exact meanings of prepared/sending/unknown/delivered/audited, provider-call crash windows, reconciliation inputs, and corruption behavior are not defined. `claimed` conflicts with the declared state list.
5. **Legacy claim/retry and downgrade migration.** `release_customer_task_claim`, `customer_admin retry`, package exports, existing claim files, docs, and old-binary rollback behavior are not resolved.
6. **Action-by-action TOCTOU fingerprint and lock order.** The plan does not enumerate which live owner/source/registration/consent/safety/destination/artifact/approval/epoch/overlay pins each change rechecks, nor the append-adjacent comparison point and lock order.
7. **Exact preflight API/output/manual split.** “supported setup/preflight code” has no path/symbol/input/output schema. Automated syntactic/pairwise config validation is not separated from human proof of separate Telegram accounts/bot/token.
8. **P2–P6 executable fixtures.** Initial state, distinct proposal revision per row, fault-injection point, expected event sequence/state/UI text/provider count, cleanup, and whether rollover is deterministic automation or live manual observation are absent.
9. **Concrete verification commands and touched test map.** The plan names levels but not exact profile/gateway commands or all directly affected test files, and omits an explicit normal customer/trainer/personal regression gate.
10. **Canonical documentation ownership.** Duplicate workspace/profile runbooks are not reconciled; exact target paths and deployment order are not stated.

### Thin/possibly unclear

- Option B bundles identity, Telegram UX, transition hardening, and schedule durability. The alternatives do not independently compare config ownership, operator trigger form, or schedule ledger reuse versus a dedicated ledger, so major implementation choices remain hidden inside the chosen option.
- AC5/WS4 uses “zero” for rows/state that may already exist before owner injection. Expected assertions should be expressed as zero **delta** and unchanged exact digests/bytes relative to a captured baseline.
- Receipt-bearing audit reconciliation currently may be safe after delivery revocation, while new operator UX says reconcile explicitly. The plan should state which reconciliations require current ingress auth and which must remain possible after revoke without allowing a new provider call.

## Approval Boundary

Execution of product changes is **not approved** from this artifact. Read-only mapping and planner revision may proceed. Approval should begin only after the identity/config contract, operator UX state machine, schedule ledger/migration, preflight boundary, and executable verification matrix below are added. Real accounts, credentials, live Telegram Gate-D, and real-customer activation/delivery remain outside any code-execution approval.

## Summary

- **Clarity:** High at principle/goal level; low at the three new public contracts.
- **Verifiability:** Good intended invariants and provider call counts; insufficient exact fixtures, event sequences, commands, and baseline/delta definitions.
- **Completeness:** Misses config migration, full Telegram ingress reservation, schedule retry/export/callsite migration, downgrade behavior, and duplicate-doc ownership.
- **Big Picture:** Strong safety direction and correct separation of automatic work from final human Gate-D; scope combines several independent architectural changes without fully specifying their seams.
- **Principle/Option Consistency:** Reservation-first/no-retry and owner-vs-review separation are consistent. “All changes revalidated” is not yet covered by a complete action matrix, and `claimed` migration is inconsistent with the declared schedule states.
- **Alternatives Depth:** Too shallow for consensus execution; option B hides unresolved config, UX, and ledger choices.
- **Risk/Verification Rigor:** Premortem identifies the right failures, but crash-window, migration/downgrade, and generic-ingress risks lack executable acceptance.

## Required Changes

1. **Choose and document one review-address source of truth.** Name the exact config model/key and files; define required user/chat/topic fields, default/fail-closed semantics, migration from current `AdaptiveNutritionConfig`, and whether review user may differ from canonical owner user. Specify that ingress compares the configured review triple, then refreshes and passes the canonical owner full triple to lifecycle. Either persist both `ingress_review_address` and `audit_owner_address` (opaque/redacted evidence as appropriate) or explicitly justify why only owner is audited. Add exact positive/negative/rotation tests.
2. **Specify the host-owned Telegram control plane end to end.** Name the trigger handler/command or reply-keyboard entrypoint, customer selector token, live-registry generation binding, host KST-day capture, proposal create/publish symbols, card keyboard actions, edit-session capture, reconcile action, and Korean result mapping. Define stale/double click, no-enabled/multiple-enabled customer, card-send failure, restart, and callback-scan behavior. Reserve Topic 59 from generic text, commands, media, location, and unrelated callbacks, with zero generic-agent/provider assertions.
3. **Define the scheduled delivery contract before implementation.** Provide ledger path, schema/version, permissions/symlink rules, immutable intent key/body/destination/day fields or their digests, legal transition table, locking/fsync point, and exact recovery for every crash boundary: before reservation, after prepared, after irreversible sending marker but before provider, provider success before receipt persistence, receipt persisted before audit, timeout/cancel/malformed receipt, and audit failure. State when provider count is 0 versus 1 and when no retry is permanent.
4. **Make schedule migration and rollback executable.** Map every legacy `.claim` deterministically to a named legal state; decide whether legacy claims are terminal unknown or safely releasable claimed, not both. Update/retire `release_customer_task_claim`, `customer_admin retry`, `checkin_cli.__init__` exports, tests, and both runbooks. Define dedupe compatibility for mixed/old binaries or explicitly prohibit downgrade until reconciliation; ensure rollback cannot make old code resend an upgraded intent.
5. **Expand WS4 into an action/pin matrix.** For create/edit/hold/release/approve/delivery-enable/revoke/activate/rollback/send/reconcile, list exact live pins, lock order, final supplied-owner equality check, append/provider boundary, idempotent-return authorization, and expected zero deltas on mutation. Include owner, destination, consent/safety, source, registration, policy/catalog/constraints, approval/revision, activation, and feature-config mutations where applicable.
6. **Name and type the preflight implementation.** Give exact module/symbol or explicitly authorized new file, inputs, bounded receipt schema, no-secret/no-PII rules, and failure codes. Split automatically provable file/config/registry/address constraints from human attestations for actual account ownership, separate bot/token, and live Topic 59 existence. State how an isolated profile/fixture is selected so the production profile cannot be mutated.
7. **Turn P2–P6 and verification into an executable table.** For every row define fresh baseline/revision, fault hook, action, exact Korean UI, exact ordered ledger/event deltas, provider count, restart/reconcile step, and cleanup. Distinguish deterministic 23:59→00:01 clock tests from later live manual evidence. List focused and full commands/test files, including `nutrition_coaching_config.py` tests, adaptive callback/menu tests, schedule/admin tests, normal customer/trainer/personal routing regressions, compile gates, and both repositories' full regression commands.
8. **Deepen the decision record and docs rollout.** Record the alternatives/choice for (a) review config ownership, (b) Telegram trigger/session model, and (c) reuse versus dedicated schedule ledger. Designate the canonical runbook or require both copies to remain synchronized; enumerate exact doc paths. Add safe deployment/config/data migration order, abort criteria, and rollback constraints before live manual Gate-D.
