## Summary
대상 Revision 3은 이전 BLOCK의 역할·활성화·개인정보·웹 경계·수명주기 범위를 상당 부분 구체화했습니다. 그러나 승인 기록에서 전송 attempt를 만드는 경계가 원자적·복구 가능하지 않고, Telegram의 실제 다중 청크 전송을 단일 `message_id` receipt로 모델링하므로 무발송·부분발송·중복 attempt를 막는 핵심 계약을 증명할 수 없습니다. 이 두 HIGH 문제와 capability manifest 저장 계약을 보완한 뒤에만 승인하는 것이 안전합니다.

## Claims
- Deep Interview의 단일 외부 고객, Richard 전용 내부 웹, AI 자동전송 금지, 첫 4주 일일·주간 운영 및 KPI 논리곱은 Revision 3의 Phase 0–5 및 ADR에 유지됩니다 (`specs/deep-interview-dual-coach-single-customer-pilot.md`, `stage-03-revision.md:52-205`).
- Revision 3은 loopback을 OAuth `user_id` principal로 오인하지 않고 launch-time bearer를 Richard owner capability로 정의하며 console 전용 HttpOnly 세션, profile binding, Origin/CSRF/idempotency 및 no-store 정책을 추가합니다 (`stage-03-revision.md:14-18,116-136`). 이는 현 dashboard가 loopback 모드에서 SPA에 ephemeral `_SESSION_TOKEN`을 주입하고 모든 민감 API가 그 header를 수락하는 구현과 맞습니다 (`hermes_cli/web_server.py:211-304,442-456,11663-11689`; `web/src/lib/api.ts:25-103`).
- dashboard가 command만 기록하고 gateway가 유일한 customer outbound Telegram sender가 되며, failed/ambiguous를 구분해 retry/reconciliation을 제한하는 방향은 현 coordinator가 draft 요청만 저장하고 Telegram adapter가 직접 Bot API를 호출하는 현 구조의 부족함을 정확히 겨냥합니다 (`stage-03-revision.md:20-43`; `gateway/platforms/nutrition_coaching.py:54-98`; `gateway/platforms/telegram.py:3971-3995`).
- registry projection을 event 뒤에 갱신하고 tombstone을 cache와 독립적으로 확인하며, revoke/delete, encrypted backup restore, 평일 분모, 4주 KPI를 명시한 것은 기존 registry/scheduler 한계를 보완합니다 (`stage-03-revision.md:70-113,138-151`; `checkin_cli/customer_schedule.py:29-84`; `checkin_cli/customer_coaching.py:102-125`).

## Analysis
### Stage 1 — Spec compliance
Revision 3은 이전 리뷰의 핵심 누락을 해소합니다. trainer address/role 및 supersedes, activation receipt, privacy state table, first-four-week 월간 task 차단, owner Telegram command와 web의 공통 decision/delivery path, 실제 dashboard backend/frontend/config/test 범위를 선언했습니다. 이는 명세의 사람 승인·고객 격리·삭제/복구·Telegram과 내부 웹 병행 요구를 초과하지 않고 충족하는 방향입니다.

### Stage 2 — Architecture
**Console trust boundary.** 현 dashboard의 loopback session token은 OAuth principal이 아니라 page-injected process-lifetime bearer입니다. Revision 3은 이를 owner capability로 정확히 재정의하고 console session을 별도로 제한합니다. Host/Origin/CSRF/profile binding/feature flag/browser leakage test를 Phase 3 acceptance로 둔 것도 적절합니다. 다만 session bootstrap은 one-time consumption 또는 재bootstrap 거부와 session-expiry recovery를 실제 route contract로 명시해 test해야 합니다.

**Process topology and delivery.** dashboard와 owner Telegram command가 동일 domain service에 command를 기록하고 gateway만 bot call을 수행한다는 소유권은 적절합니다. 하지만 approval append, attempt append, claim file은 서로 다른 durable objects입니다. 제시된 순서에는 crash recovery와 concurrent creation reservation이 없고, diagram의 claim 주체도 dashboard 화살표와 sequence acceptance의 “gateway worker alone claims” 사이에서 모호합니다. append-only라는 성질만으로 이 multi-process transaction이 원자적이 되지 않습니다.

또한 현 `TelegramAdapter.send`는 formatted content를 4,096 UTF-16 limit에 맞게 chunk로 나누어 순차 `send_message`하고 첫 ID만 primary `message_id`로 반환합니다 (`gateway/platforms/telegram.py:2409-2668`). 따라서 Revision 3의 단일 `accepted(message_id)` receipt는 긴 승인문이 첫 chunk 뒤에 실패했을 때 완전 전송 여부를 표현하지 못합니다. 이 경로는 customer에게 부분 메시지가 보인 뒤 attempt가 ambiguous가 될 수 있으므로 retry/reconciliation safety contract의 일부여야 합니다.

**Capability handshake.** readable/writable versions와 required capability checks는 필요한 시작점입니다. 그러나 “profile storage manifest”의 persistent record, source of truth, writer, atomic manifest/event ordering, missing/corrupt behavior가 계약 또는 file slice에 지정되지 않았습니다. 별도 dashboard/gateway 배포에서 runtime constants만 비교하면 이미 기록된 event가 요구하는 reader version을 신뢰성 있게 판별할 수 없습니다.

### Constructive synthesis
가장 작은 안전한 설계는 per-customer durable command ledger를 source of truth로 하는 것입니다. `approval`은 immutable approved revision을 만들고, 그와 같은 fsync boundary에서 deterministic command key를 reserve합니다. queue file은 ledger에서 재생성 가능한 projection이며 worker가 claim합니다. restart는 terminal receipt 없는 reserved command를 재투영하거나 operator-visible blocked state로 올립니다. 이 방식은 새 IPC나 dashboard bot dependency를 만들지 않으면서 current filesystem model을 보존합니다.

전송은 (a) rendering/escaping 후 strict one-Bot-API-message limit을 강제하거나, (b) immutable logical delivery가 ordered chunks와 complete receipt-id list를 포함하도록 해야 합니다. (a)는 pilot의 짧은 피드백 목표에 더 단순하고, (b)는 긴 feedback을 보존하지만 partial failure matrix가 추가됩니다.

## Root Cause
Revision 3은 durability artifacts를 나열하지만 서로 독립된 append/claim 파일 사이의 transaction/recovery protocol과 transport-level logical-message boundary를 정의하지 않았습니다. 이 두 경계가 없으면 at-most-once/reconciliation은 worker의 Bot call 이후에만 작동하고, 그 이전의 승인 유실·중복 attempt 및 중간 chunk 부분발송을 숨깁니다.

## Findings
1. **HIGH — `stage-03-revision.md:22-43` — approval-to-attempt creation is not atomic or recoverable.** approval append와 attempt append 사이 crash는 approved revision을 stranded 상태로 만들고, concurrent web/Telegram retry는 worker O_EXCL claim 전 multiple nonterminal attempt를 append할 수 있습니다. claim owner도 diagram과 sequence acceptance 사이에 모호합니다. **Fix:** `approved_revision_id`와 retry/resend authorization에 unique deterministic reservation을 둔 하나의 fsync command ledger를 source of truth로 정하고 gateway만 claim하게 하십시오. orphan approved/attempt recovery scan과 every append/claim boundary crash 및 concurrent channel tests를 추가하십시오.
2. **HIGH — `stage-03-revision.md:22-43` — single receipt does not cover Telegram chunk delivery.** 현 adapter는 >4,096 UTF-16 content를 여러 message로 순차 전송하고 첫 ID를 primary receipt로 반환합니다 (`gateway/platforms/telegram.py:2409-2668`). 첫 chunk 뒤 failure는 partial customer delivery지만 `accepted(message_id)` 하나로 reconciliation할 수 없습니다. **Fix:** rendered final text를 one-message limit으로 validate/reject하거나, ordered chunk IDs와 partial-send→ambiguous semantics를 immutable attempt schema, reconciliation UI, fault tests에 포함하십시오.
3. **MEDIUM — `stage-03-revision.md:45-50` — persisted capability-manifest protocol is unspecified.** code constants/config requirement만 명시되고 profile storage manifest의 path/schema/owner/atomic update/missing-or-corrupt behavior와 written-event minimum reader version evidence가 없습니다. **Fix:** persistent manifest and fsync/rename protocol을 capabilities module의 explicit contract로 만들고, incompatible event 전에 advance하며, crash during manifest/event upgrade와 rollback test를 matrix에 추가하십시오.

## Recommendations
1. Phase 2보다 먼저 decision/attempt ledger를 재정의하십시오: reservation key, single writer/claim owner, durable ordering, projection recovery, terminal/nonterminal state transition table을 포함해야 합니다.
2. delivery schema와 Telegram sender seam에 payload size/format policy를 추가하십시오. 권장 MVP 선택은 rendered one-message validation이며 overflow는 `failed_before_call`로 owner에게 edit 요구를 반환하는 것입니다.
3. `capabilities.py`에 profile storage manifest schema, location, read/write transaction order, corruption fail-close policy와 migration/recovery tests를 명시하십시오.
4. 위 보완 후에는 현재 선택한 built-in dashboard + loopback owner capability + console-scoped session, gateway-only sender, tombstone-first privacy lifecycle, pilot ledger 및 Phase 5 service handshake 방향을 유지하십시오. 별도 sidecar나 direct browser Bot client로 되돌릴 필요는 없습니다.

## Architectural Status
BLOCK

## Code Review Recommendation
REQUEST CHANGES

## Tradeoffs
| Option | 장점 | 필요한 제약 |
|---|---|---|
| fsync command ledger + derived queue (권장) | approval/attempt crash recovery와 concurrent reservation이 결정적 | worker scan/reprojection 및 state table 필요 |
| 별도 approval/attempt append + O_EXCL claim | 구현이 짧아 보임 | crash hole와 duplicate nonterminal attempt를 해결하지 못하므로 부적합 |
| one-message final text (권장 MVP) | receipt/reconciliation이 단순하고 partial send 없음 | renderer 후 hard limit과 edit-required UX 필요 |
| ordered multi-chunk delivery | 긴 text 보존 | chunk set/receipt/partial ambiguity/reconciliation을 모두 domain contract로 추가해야 함 |
| runtime capability constants만 비교 | 코드량이 적음 | persisted event/storage compatibility와 crash rollback을 증명하지 못함 |
| persisted capability manifest | version-skew fail-close를 검증 가능 | manifest ownership 및 atomic update protocol 필요 |
