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

## Consensus Corrections
Revision 2의 방향을 유지하면서 Architect BLOCK 3건과 Critic REJECT 6건을 다음 계약으로 닫는다.

## Immutable Input Evidence
- RTF source: `/tmp/gjc-telegram-g4Whjx/088d339f10b5641d-_.rtf`, sha256 `81c991dbe629a2baacc2184b0c98c663c25f961ec206bbb50b97959f3080ba05`
- PDF source: `/tmp/gjc-telegram-g4Whjx/2f0cf6bab3561326-______MVP___v2.pdf`, sha256 `05663afd138e99dad2a12ecf7a4e726bf6debd38b41cb1876886a7bbeed65361`
- Deep Interview spec: `.gjc/_session-019f8455-334a-7000-99ca-318dfd0e06b1/specs/deep-interview-dual-coach-single-customer-pilot.md`
- Execution Phase 0 MUST copy the two attachments into an operator-approved private evidence directory outside any public repository, preserve mode 0600, verify both hashes, and write `traceability-matrix.json` beside them. Missing source or hash mismatch blocks launch.

## Final Architecture Decisions
### Web trust boundary
- Use the existing Hermes dashboard built-in route and React build.
- Bind dashboard to `127.0.0.1`; access only through SSH tunnel.
- Treat possession of the launch-time ephemeral dashboard bearer as the **single Richard owner capability**. Remove claims that loopback creates an OAuth `user_id` principal.
- On first console request with the bearer, issue a console-specific HttpOnly, SameSite=Strict, Secure-when-applicable signed session with 30-minute idle expiry. Session is bound to `physique-coach`, launch generation, CSRF secret and client origin; it cannot switch profiles.
- Recovery requires restarting dashboard to rotate the launch bearer and console signing key. No shared/basic/password credential is stored.

### Dashboard-to-gateway process topology
```text
Dashboard route (dashboard process)
  -> append immutable decision/approval event
  -> append outbound attempt in customer-scoped queue
  -> atomic claim file + fsync
Gateway outbound worker (physique-coach-gateway process, sole Telegram sender)
  -> polls/wakes on queue generation every <=2s
  -> validates capability/schema/consent/activation again
  -> owns attempt sending transition and live Telegram Bot call
  -> appends accepted(message_id)|failed|ambiguous receipt
Dashboard/owner Telegram command
  -> reads the same projection; never calls Telegram Bot directly
```
- Dashboard writes commands only. Gateway is the sole outbound executor and sole holder of live Telegram bot dependency.
- Queue is under profile customer data, per-customer mode 0700/files 0600. Lock uses atomic create; stale `sending` never auto-retries and becomes `ambiguous` after worker restart unless an accepted receipt exists.
- Owner Telegram approve/send command writes the same approval/attempt records via the domain service; it does not send directly.

### Immutable delivery attempts
- Revision command key: `approved_revision_id`.
- Each transmission is immutable `attempt_id` with `approved_revision_id`, `retry_of|null`, `resend_authorization_id|null`, claim owner, state events and receipt.
- Per approved revision, at most one nonterminal attempt may exist.
- Definitive `failed` permits an explicit owner retry that creates a new attempt with `retry_of`.
- `ambiguous` is permanently non-retriable until reconciliation marks accepted or the owner creates a separate resend authorization; the new attempt references both.

### Capability/version handshake
- New `checkin_cli/capabilities.py` owns `PROFILE_SCHEMA_VERSION`, readable event versions, writable event versions and feature capabilities.
- `nutrition_coaching_config.py` declares required minimum capability versions for trainer records, approvals, delivery, privacy and pilot ledger.
- Gateway/dashboard load the capability manifest before route registration and periodically compare it with profile storage manifest. Incompatibility blocks route registration, activation, scheduling and outbound claims.
- Mixed-version tests cover old reader/new events, new reader/old events, gateway new/profile old and rollback after new events.

## Phase 0 — Evidence, provider policy, capability and activation gates
### Files
Slice A: private evidence directory, `HANDOFF.md`, `choi_coach_system_report.html`, `traceability-matrix.json`.
Slice B: `checkin_cli/capabilities.py` (new), `customer_activation.py` (new), `customer_coaching.py`, `customer_admin.py`, `tests/test_customer_admin.py`.

### Work and acceptance
- Copy/hash RTF/PDF and map every approved feature to source section, code symbol, phase and test.
- Provider policy evidence records provider/model/account setting, training use, retention, region, subprocessors, checked_at and notice version.
- Activation receipts: provider policy, consent version, customer/trainer address challenge, plan approval, token epoch, test-send.
- Any evidence mismatch or capability incompatibility is fail-closed.

## Phase 1 — Role identities, event schemas, hot reload and schedule
### Files
Slice A: `customer_coaching.py`, `models.py`, `store.py`, `wizard_storage.py`, focused tests.
Slice B: `wizard.py`, `physique_checkin.py`, `physique_checkin_prompts.py`, `nutrition_coaching.py`, gateway tests.
Slice C: `customer_schedule.py`, `customer_reporting.py`, `nutrition_coaching_config.py`, schedule/config tests.

### Contracts
- Trainer address/role is versioned and pairwise disjoint from owner/customer/all spaces. Multiple sessions/day use session_id. Correction is superseding event; revoke blocks writes.
- Registry is a mutable projection/config, while every identity/consent/activation change is first appended as an event; projection updates only after durable event write.
- Gateway coordinator cache stores registry content hash and reloads atomically before each inbound route resolution and schedule pass when hash changes. Failed reload keeps prior disabled-safe snapshot and alerts; revoke/delete first writes a deny tombstone consulted independently of cache.
- Monday–Friday eligible schedule only; explicit waiver events; no monthly task generation in first-four-week mode.
- Timed rehearsal uses 3 representative customer paths and 3 trainer paths with monotonic start/end; medians ≤90s and ≤30s.

## Phase 2 — Privacy, decisions, queue and executor
### Files
Slice A: `customer_privacy.py` (new), `customer_grounding.py`, `customer_coaching.py`, privacy tests.
Slice B: `customer_decisions.py` (new), `customer_delivery.py` (new), `nutrition_coaching.py`, `telegram.py`, delivery tests.
Slice C: `customer_outbound_worker.py` (new), `nutrition_coaching_config.py`, `telegram.py`, worker fault tests.

### Privacy state table
| State/event | New intake | Draft/approve | New send | In-flight send | Storage action |
|---|---|---|---|---|---|
| consented | allow per field allowlist | allow | allow | allow | retain by policy |
| revoked | deny | deny | deny | cannot cancel accepted network call; worker records final/ambiguous then blocks all successors | tombstone immediately; purge derived/provider caches by deadline |
| delete_pending | deny | deny | deny | same in-flight limitation, no retry | tombstone cutoff; purge raw/derived after configured operational hold |
| deleted | deny | deny | deny | none | keep only non-identifying deletion proof and legally required financial evidence |

- Retention duration is a required activation policy field, not a hardcoded assumption. Backup purge deadline and legal/operational hold are explicit fields.
- Append-only audit keeps pseudonymous state transitions; identifying raw/derived content is physically purged after hold. Tombstone ID uses keyed digest so deleted identity is not recoverable.
- Encrypted backups use owner-held key. Restore runs tombstone/purge manifest before any service starts. Completion receipt lists categories, cutoff, backups and finished_at without deleted content.

### Delivery expected states
- Revoke before claim: attempt cancelled_before_send; no network call.
- Revoke while `sending`: network cannot be recalled; accepted/failed/ambiguous is recorded; no retry or later send.
- Crash before Telegram call: stale sending -> ambiguous unless worker proves no call began via durable `call_started` boundary; conservative default ambiguous.
- Timeout/crash after call: ambiguous; owner reconciliation required.
- Crash after accepted before receipt: ambiguous; reconcile from observed customer topic/message id.

## Phase 3 — Richard-only dashboard console
### Backend files
- `hermes_cli/config.py`
- `hermes_cli/web_server.py`
- `hermes_cli/physique_coach_routes.py` (new)
- `tests/hermes_cli/test_physique_coach_routes.py` (new)
- existing dashboard auth tests only where shared behavior changes

### Frontend files
- `web/src/App.tsx`
- `web/src/pages/PhysiqueCoach.tsx` (new)
- `web/src/api/physiqueCoach.ts` (new)
- `web/src/pages/PhysiqueCoach.test.tsx` (new)

### Config/lifecycle
- Canonical dashboard flag: `dashboard.physique_coach_approval_console` in `hermes_cli/config.py`, default false. Gateway flags remain under `extra.nutrition_coaching`; no camelCase duplicate.
- Dashboard command: existing `hermes dashboard` launcher with explicit loopback host and chosen local port. Phase 5 creates a user service `physique-coach-dashboard.service` only after verifying the actual CLI invocation in the installed environment.
- Dashboard route imports profile domain through one adapter module that adds the configured profile package root exactly once and checks capability manifest; route code does not duplicate storage logic.
- Mutations require console session, exact profile binding, Origin, CSRF, idempotency. Headers: no-store, no-referrer, CSP, frame denial. No PII in URL/log/analytics/storage.

### Acceptance
- Test actual loopback bearer bootstrap, console session expiry/recovery, wrong profile, Host/Origin/CSRF/replay and feature flag branches.
- Dashboard approval writes queue; gateway test worker sends; dashboard observes receipt. Browser never owns bot token.

## Phase 4 — Pilot ledger and reports
### Files
- `customer_pilot.py` (new)
- `customer_reporting.py`
- `customer_schedule.py`
- `customer_admin.py`
- focused pilot/report tests

### Semantics
- Completion = completed / eligible weekday; zero denominator => inconclusive.
- Actor time uses monotonic intervals; same-actor overlap unioned, owner+trainer totals summed and separately reported.
- Satisfaction uses latest non-superseded 4-week 0–10 response.
- Payment requires 150000 KRW initial evidence and distinct actual next-period transfer; intent is not payment.
- Pass requires all four true; any missing evidence => inconclusive.

## Phase 5 — Deployment and exact verification gates
### Files
- `nutrition_coaching_config.py`
- `HANDOFF.md`
- `choi_coach_system_report.html`
- `/home/cube/.config/systemd/user/physique-coach-gateway.service` regenerated only through the project's supported installer
- new dashboard user unit through supported installation path

### Commands to resolve/record in execution receipt before mutation
Because the profile and Hermes repo use separate environments, execution Phase 0 records the exact interpreter/package manager from existing project metadata. The gate commands are then fixed in the execution ledger; expected forms are:
```bash
# Profile focused/full
python -m pytest <new focused profile tests>
python -m pytest <profile test root>

# Hermes focused/full
python -m pytest tests/gateway/test_telegram_physique_checkin.py <new nutrition/delivery tests> tests/hermes_cli/test_physique_coach_routes.py
python -m pytest tests

# Frontend
<repo package manager> test -- PhysiqueCoach
<repo package manager> build

# Service/health
systemctl --user daemon-reload
systemctl --user restart physique-coach-gateway.service physique-coach-dashboard.service
systemctl --user is-active physique-coach-gateway.service physique-coach-dashboard.service
```
No guessed command is executed: project metadata determines the concrete command and it is persisted before the implementation gate. All commands must exit 0; test failures remain zero. Prior observed baselines (75 profile, 701 Telegram/Hermes wrapper, 109 core wrapper) are regression references, not hardcoded current counts.

### Rollout
1. Install backward-compatible profile readers/capability manifest; flags off.
2. Install gateway queue worker and dashboard route/build; flags off.
3. Run mixed-version matrix and all regressions.
4. Start both services, health check and capability handshake.
5. Rotate Telegram token/token epoch; old token must fail.
6. Test-account E2E and browser/fault/backup restore rehearsal.
7. Enable console, then customer only after activation receipts complete.

### Rollback
- Flags off; stop scheduling/new commands; retain attempt claims and tombstones.
- Never downgrade below the reader version required by written events.
- Rehearsal receipt records service versions, capability manifests, health, test results, backup restore result and rollback result under the private operator evidence directory.

## Sequence Acceptance
1. Web approval and owner Telegram approval race: one immutable approved revision and one active attempt.
2. Gateway worker alone claims and sends.
3. Definitive failure -> explicit retry creates new attempt.
4. Ambiguous -> no retry until reconciliation/resend authorization.
5. Revoke/delete tombstone is checked at every transition, independent of cached registry.
6. Dashboard/gateway/profile capability mismatch blocks all sensitive routes and sends.

## ADR
### Decision
Use Telegram inputs, an existing loopback dashboard owner-capability session, an append-only profile queue, and a gateway-owned Telegram outbound worker with immutable attempts and ambiguous reconciliation.
### Drivers
Single-owner security, no bot token in browser, crash-aware at-most-once behavior, auditable first-customer operation.
### Alternatives
Dashboard direct bot client rejected (duplicates bot ownership); synchronous IPC rejected for crash/restart coupling; dashboard plugin/sidecar deferred; public multi-role web and Telegram-only reject confirmed requirements.
### Consequences
Adds a durable queue/worker and dashboard service lifecycle but creates one authoritative send path and honest failure semantics.
### Follow-ups
After a passing four-week pilot, reconsider plugin extraction, monthly automation, multiple actors/customers and formal payment/business setup.

## Intent Reconciliation
All deep-interview decisions remain unchanged. New choices only make required implementation semantics explicit; they do not expand customer-facing scope.
