# RALPLAN-DR Revision 2: 듀얼코치 단일 고객 유료 파일럿

## Review Inputs
- Planner: `stage-01-planner.md`, sha `8b224eceb12cef566b525b45b7b54564f41fc165b8e96c375e3ec89d61b6cafe`
- Architect: BLOCK / REQUEST CHANGES
- Critic: REJECT
- This revision resolves trainer identity, activation evidence, outbound crash semantics, web trust boundary, privacy lifecycle, KPI ledger, actual callsites, and deployment/version skew.

## Fixed Decisions
1. 고객 입력은 Telegram, 운영 승인 표면은 기존 Hermes dashboard 안의 **profile-scoped built-in route**로 구현한다. 별도 공개 서버나 고객/트레이너 웹 계정은 만들지 않는다.
2. Dashboard는 `127.0.0.1`에만 바인딩하고 기존 launch-time ephemeral bearer/session gate를 사용하며 SSH 터널로만 접근한다. 새 화면은 현재 활성 profile이 `physique-coach`이고 owner principal이 확인된 요청만 허용한다.
3. 웹과 신규 owner Telegram 명령은 하나의 `ApprovalDeliveryService`를 호출한다. 기존 Telegram draft 렌더링을 전송 fallback으로 간주하지 않는다.
4. Telegram의 외부 exactly-once 보장은 주장하지 않는다. 로컬 상태는 append-only이며 전송 결과가 불명확하면 `ambiguous`로 멈추고 자동 재전송하지 않는 **at-most-once + 수동 reconciliation** 정책을 사용한다.
5. 모든 고객·트레이너·동의·초안·승인·전송·KPI 이벤트는 customer-scoped append-only 저장소에 기록하고 correction은 `supersedes`로 남긴다.

## Architecture Alternatives
### Existing dashboard built-in route (selected)
- Reuses `hermes_cli/web_server.py`, dashboard auth middleware/cookies/audit, React `web/src/App.tsx`, existing build/deploy lifecycle.
- Requires a narrow profile API and frontend page but no second service lifecycle.

### Dashboard plugin
- Good isolation and future packaging, but plugin manifest/API and sensitive profile data bridge add indirection for one internal operator.
- Reconsider only after first successful pilot.

### Standalone localhost sidecar
- Strong process isolation, but duplicates auth, lifecycle, static assets, observability and profile IPC. Rejected for MVP.

## Canonical Domain and Storage Contracts
| Contract | Owner module | Storage / invariants |
|---|---|---|
| Customer/owner/trainer identity | `checkin_cli/customer_coaching.py` | `CustomerSpec.telegram`, new `trainer.telegram`, registry owner; all user/chat/topic tuples and spaces pairwise disjoint |
| Activation evidence | new `checkin_cli/customer_activation.py` | append-only receipts: consent version, provider policy evidence, customer challenge, trainer challenge, plan approval, test delivery, token-rotation acknowledgment |
| Customer/trainer events | `models.py`, `store.py`, `wizard_storage.py` | customer root `data/customers/<key>/events.jsonl`; typed payloads; corrections use `supersedes` |
| Grounded draft/decision | new `checkin_cli/customer_decisions.py` + `customer_grounding.py` | evidence digest, draft revision, editor, approved revision, reason, reevaluate_at |
| Outbound command | new `checkin_cli/customer_delivery.py` | `approved → sending → accepted|failed|ambiguous`; key=`customer_key+approved_revision_id`; Telegram message-id receipt when known |
| Pilot ledger | new `checkin_cli/customer_pilot.py` | eligible/waived/completed check-in, actor work intervals, satisfaction, payment/re-renewal evidence; missing evidence => inconclusive |
| Consent/privacy lifecycle | new `checkin_cli/customer_privacy.py` | purpose+field allowlist version, consent/revoke/delete events, retention cutoff, backup purge/restore rules |

## Outbound Crash Semantics
- `approved`: immutable approved text and evidence digest exist.
- `sending`: durable claim written before Telegram API call.
- `accepted`: Telegram returned message_id; receipt persisted.
- `failed`: definitive rejection before acceptance; owner may retry the same approved revision.
- `ambiguous`: timeout/crash/connection loss after request may have reached Telegram. No automatic retry. Owner checks customer topic; if found, records external message_id/observed timestamp and reconciles to `accepted`; otherwise creates an explicit resend authorization that references the ambiguous attempt.
- Concurrent web clicks and owner Telegram commands contend on the same claim. Only one transitions from `approved`.
- Web outage fallback is a **new owner-authorized Telegram approve/send command** backed by this service. Manual copy/paste outside the service is recorded as an external delivery reconciliation before further sends.

## Phase 0 — Evidence inventory and launch gates
### Files (3–5 per slice)
Slice A:
- `HANDOFF.md`
- `choi_coach_system_report.html`
- deep-interview spec (read-only source)
- RTF/PDF attachments copied only into an operator-approved private evidence location if retention is desired; otherwise record hashes and source locations

Slice B:
- `checkin_cli/customer_coaching.py`
- `checkin_cli/customer_admin.py`
- new `checkin_cli/customer_activation.py`
- `tests/test_customer_admin.py`

### Changes
- Produce a traceability matrix from RTF 2026-07-21, PDF, spec, current symbol, status, phase, acceptance test.
- Add activation receipt schema and make `enable` fail unless current consent, provider policy evidence, customer/trainer address challenge, 12-week plan approval, rotated-token acknowledgment, and test-send receipt are valid.
- `enabled` remains false for missing, expired, superseded, or mismatched evidence.

### Acceptance
- Direct `enable` on a draft fails with a list of unmet gates.
- Any changed Telegram address, consent notice, provider allowlist or token epoch invalidates prior activation evidence.

## Phase 1 — Roles, schedules, and typed records
### Slice A files
- `checkin_cli/customer_coaching.py`
- `checkin_cli/models.py`
- `checkin_cli/store.py`
- `checkin_cli/wizard_storage.py`
- tests for registry/store isolation

### Slice B files
- `checkin_cli/wizard.py`
- `gateway/platforms/physique_checkin.py`
- `gateway/platforms/physique_checkin_prompts.py`
- `gateway/platforms/nutrition_coaching.py`
- focused wizard/gateway tests

### Changes
- Add trainer identity/address and role to customer spec. Enforce owner/customer/trainer/global route and space disjointness.
- Add trainer session event: session_id, KST day/time, completed, performance, planned intensity, pain flag, owner note; permit multiple sessions/day using session_id.
- Trainer correction creates a superseding event; revoke blocks new input but preserves audit history.
- Refine `nutrition_daily` to six core questions with branch-only sleep/adherence/safety detail. Preserve fixed critical safety prompts.
- Schedule daily tasks Monday–Friday only for this pilot; waived days are explicit. Disable monthly task generation for first-four-week pilot mode, not only delivery.
- Completion denominator is eligible scheduled weekdays, excluding explicit waivers and pre-activation days.

### Acceptance
- Unauthorized role/space and cross-customer access fail closed.
- Pain/safety signals propagate to draft hold.
- KST boundary, multiple sessions/day, supersedes, missing/duplicate events and weekday denominator fixtures pass.
- Timed rehearsal: customer test account completes median of 3 representative paths ≤90s; trainer completes 3 representative records ≤30s. Record actual start/end monotonic times and path lengths.

## Phase 2 — Privacy lifecycle, grounding, decisions, delivery
### Slice A files
- new `checkin_cli/customer_privacy.py`
- `checkin_cli/customer_grounding.py`
- `checkin_cli/customer_coaching.py`
- privacy/grounding tests

### Slice B files
- new `checkin_cli/customer_decisions.py`
- new `checkin_cli/customer_delivery.py`
- `gateway/platforms/nutrition_coaching.py`
- `gateway/platforms/telegram.py`
- decision/delivery integration tests

### Changes
- Create versioned purpose/field allowlist for provider payloads; omit Telegram IDs, display names and fields not needed for the current draft purpose.
- Consent/revoke/delete is evented. Revoke/delete-pending establishes a durable cutoff checked by intake, grounding, approval and delivery queries.
- Define retention period before activation; deletion traverses raw events, derived views, draft/decision/delivery data, logs and backups according to policy. Backup is encrypted with owner-controlled key; restore reapplies tombstones before service resumes.
- Create evidence-digest draft revisions, normal vs issue feedback policy, approved final text, reason and reevaluate_at.
- Implement outbound state machine and owner Telegram approve/send command. Both web and Telegram commands use the same service.

### Acceptance
- Revoke/delete during queued draft, approved command, sending, ambiguous and backup restore has a specified expected state and test.
- Provider payload snapshot contains only allowlisted pseudonymous fields.
- Approval before send is mandatory; accepted attempt has one local receipt; ambiguous never auto-retries.

## Phase 3 — Existing dashboard built-in approval console
### Backend files
- `hermes_cli/web_server.py`
- new `hermes_cli/physique_coach_routes.py`
- `hermes_cli/dashboard_auth/middleware.py` only if an explicit profile-owner predicate cannot be composed without change
- new `tests/hermes_cli/test_physique_coach_routes.py`

### Frontend files
- `web/src/App.tsx`
- new `web/src/pages/PhysiqueCoach.tsx`
- new `web/src/api/physiqueCoach.ts`
- focused frontend test file following existing test convention

### Trust boundary
- Register routes only when selected profile is `physique-coach` and feature flag `physiqueCoach.approvalConsole=true`.
- Dashboard remains loopback-only; startup refuses non-loopback for this console.
- Require existing authenticated dashboard session plus owner-profile principal. Session lifetime follows existing dashboard setting, capped for this console at 30 minutes idle.
- Mutations require same-origin `Origin`, CSRF token bound to session, no GET mutation and idempotency key.
- Responses set `Cache-Control: no-store`, restrictive `Referrer-Policy`, CSP/frame denial. PII never appears in URL/query, browser console, analytics or audit logs.
- UI exposes pending drafts, evidence summary, editable text, approve/send, delivery state and reconciliation action. It does not expose full raw customer history by default.

### Acceptance
- Host/Origin/CSRF/session/profile-owner/replay/feature-flag tests cover allow and deny branches.
- Browser test verifies no PII in URL/storage/console, approval survives refresh, duplicate click does not send twice, ambiguous requires reconciliation.
- Web and owner Telegram path produce the same canonical decision/delivery records.

## Phase 4 — Pilot ledger, weekly report, onboarding
### Files
- new `checkin_cli/customer_pilot.py`
- `checkin_cli/customer_reporting.py`
- `checkin_cli/customer_schedule.py`
- `checkin_cli/customer_admin.py`
- focused pilot/report/schedule tests

### Ledger semantics
- Check-in: scheduled, eligible, waived(reason), completed. Completion rate = completed / eligible; denominator zero => inconclusive.
- Work time: actor(owner|trainer), task category, monotonic start/end duration, optional correction event; overlapping intervals for same actor are unioned, owner+trainer time is summed unless KPI explicitly reports separate subtotals.
- Satisfaction: 0–10 response, requested_at, answered_at; latest non-superseded 4-week response is authoritative.
- Payment: amount, currency, period, observed_at, evidence reference; initial payment and actual second-period transfer are distinct events. “Intent” is not renewal.
- Four-week result is pass only when all four predicates are true; missing evidence => inconclusive, never pass.

### Acceptance
- KST week boundaries, holidays/waivers, duplicates, corrections, overlaps and missing evidence fixtures are deterministic.
- Weekly report includes trend, keep/change action and reevaluate_at; no monthly task is generated during first-four-week mode.
- Onboarding is draft → privacy/AI notice → customer/trainer address challenges → plan approval → token epoch → test-send receipt → enable.

## Phase 5 — Deployment, rehearsal, rollback
### Files
- `gateway/platforms/nutrition_coaching_config.py`
- `HANDOFF.md`
- `choi_coach_system_report.html`
- service/config installation scripts or unit files actually identified during implementation
- end-to-end test/rehearsal script following repo convention

### Deployment order
1. Deploy profile package/schema readers with backward-compatible disabled defaults.
2. Deploy Hermes gateway/dashboard code with feature flags off.
3. Restart service and run existing personal + customer regression suites.
4. Rotate Telegram token and bump token epoch; verify old token invalid.
5. Create test customer/trainer, run full rehearsal and backup/restore tombstone test.
6. Enable internal console, then customer pilot only after activation evidence is complete.

### Rollback
- Disable customer and console flags; stop new schedule claims and outbound transitions.
- Do not roll back append-only schema readers below the version that understands written events.
- Preserve sent/ambiguous claims to avoid resend after rollback.
- Revert UI/gateway behavior only after profile reader compatibility is verified.
- Manual mode uses owner Telegram command backed by canonical service; external copy/paste requires reconciliation entry.

## Verification Commands and Gates
Exact test filenames may be added beside the named modules, but approval requires these suites and scenarios:
- Profile package: existing profile pytest plus new customer activation, roles, privacy, decision, delivery, pilot tests; all pass.
- Hermes focused: `tests/gateway/test_telegram_physique_checkin.py`, nutrition coaching tests, dashboard auth/route tests; all pass.
- Full Hermes/profile regression baselines from HANDOFF (`75`, `701`, `109` previously observed) must not regress; current counts may increase but failures remain zero.
- Browser QA at desktop width through SSH/loopback equivalent: auth, no-store, CSRF, duplicate click, ambiguous reconciliation, no PII leakage.
- Test Telegram E2E: weekday schedule → customer adaptive check-in → trainer record → held/normal draft → web edit/approve/send → accepted receipt → weekly report.
- Fault injection: crash before API call, timeout after call, crash after accepted before local receipt, web outage, model outage, revoked consent, deletion pending, restored backup.
- Observability: pseudonymous event id/stage/latency/state only; scan logs for sentinel PII/token strings and require zero matches.

## Expanded Pre-mortem
1. Trainer address is misbound: detect challenge mismatch/route collision; fail activation and revoke route.
2. Telegram accepts but process crashes: state remains ambiguous; alert owner, forbid auto-retry, reconcile.
3. Dashboard auth/CSRF bypass: detect denied-host/origin audit spikes; console disabled by feature flag and loopback startup refusal.
4. Data loss/restore resurrects deleted data: encrypted backups, restore rehearsal, tombstone replay before startup.
5. Profile/gateway version skew: schema version handshake and flags off until both sides compatible.
6. Work exceeds 60 min/week: per-task timing shows normal-day edits or queue growth; shorten normal response and defer non-core UI/report features.

## ADR
### Decision
Use Telegram for customer/trainer input and the existing loopback Hermes dashboard for Richard-only approval, backed by profile-scoped append-only domain services and at-most-once delivery with ambiguous reconciliation.

### Drivers
Safety, minimal public attack surface, shared audit trail, measurable first-four-week operation.

### Alternatives
Dashboard plugin and standalone localhost sidecar remain valid future alternatives; Telegram-only and public multi-role web fail confirmed requirements or MVP scope.

### Consequences
The existing dashboard receives a sensitive profile-specific route, requiring strict profile-owner policy and browser security tests. Exactly-once is replaced by an honest at-most-once/reconciliation contract.

### Follow-ups
After a passing four-week pilot, reassess plugin extraction, monthly report activation, multiple customers/trainers and business registration/PG.

## Intent Reconciliation
No new user decision is introduced. This revision preserves the deep-interview facts: one external customer, friend trainer, monthly 150,000 KRW transfer, 12-week plan with first-four-week daily/weekly operation, normal short/problem detailed feedback, Richard-only SSH/internal web, human approval, full safety gate, and conjunctive success metrics.
