# 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.
