# RALPLAN — Dual-Coach Single-Customer Paid Pilot (Stage 11 Revision)

## Summary
Deliver the smallest safe pilot for **one external paid customer**: KRW 150,000 by bank transfer, a 12-week plan authored up front, and only the first four-week daily/weekly operating loop active. The pilot comprises a 60–90 second adaptive weekday customer check-in, a ≤30 second trainer session record, isolated per-customer records, evidence-backed AI drafts, Richard review/edit/approve/send, weekly summary, and one four-way KPI judgement. Telegram and a Richard-only loopback web screen are the only surfaces; AI never auto-sends. This revision retains every accepted Stage-10 decision and resolves the remaining implementation contradictions without widening scope.

## Non-negotiable principles
1. Human approval is absolute: no customer message is sent before Richard approves it; AI only drafts.
2. One customer, one trainer address, isolated records/addresses; append-only events with `supersedes`, never overwrite.
3. The 12-week plan is grounding context only. The exact KST interval `[plan.starts_on, plan.starts_on + 27 days]` is the sole active automation window. No extension command/state, renewal-triggered scheduling, or re-enable path exists after day 28.
4. Activation is guarded; the real customer remains disabled until every safety acceptance passes.
5. Every pilot fact is a canonical typed `Event` in the existing per-customer event stream, schema-validated before the sole `EventStore.append_wizard_event` append path.
6. Consent absent/not granted **or** any safety signal blocks normal drafting and customer sends.
7. Web is stdlib, loopback-only (`127.0.0.1` or SSH tunnel), local-token protected, and limited to evidence → edit → approve → send.

## Intent reconciliation and boundary
Preserve the deep-interview commitments: one paid external customer; KRW 150k/month bank transfer; 12-week plan written before start; first four weeks only; Telegram plus Richard-only private web; trainer’s immediate post-PT record; adaptive weekday check-ins; weekly summary; renewal/check-in/satisfaction/operator-time KPI conjunction; token rotation, consent, safety, isolation, and manual recovery. Preserve the accepted canonical-event design, deterministic branch fixtures, ADRs, and RALPLAN-DR constraints.

Out of scope: business/PG/auto-billing, multi-customer/trainer/roles, customer portal, generic Hermes/platform/auth/deployment redesign, browser framework, systemd/deployment scripts, generic concurrency/crash-theorem work, monthly automation/report delivery, knowledge refresh, AI auto-send, any `payload_ref` sidecar or generic payload dict, and all post-day-28 automation or renewal-driven scheduling.

## Architecture decisions
- **ADR-1:** use the existing canonical per-customer `events.jsonl` stream and `EventStore.append_wizard_event`; reporting is projection/judgement only and `store.py` gains no generic append API.
- **ADR-2:** extend `/home/cube/.hermes/profiles/physique-coach/contracts/checkin-event.schema.json` in place; typed frozen models mirror it in `checkin_cli/models.py`.
- **ADR-3:** deterministic rule-based adaptive flow owns fixed thresholds/question IDs; no model-driven questioning.
- **ADR-4:** distinct `draft_created`, `draft_edited`, `draft_approved`, and `draft_sent` events retain audit history.
- **ADR-5:** one registry-bound trainer Telegram triple and trainer-initiated command/button; exact triple authorization only.
- **ADR-6:** pilot scheduling is one projection in `customer_schedule.py`, with hard day-28 stop and no extension input.
- **ADR-7:** `CustomerSpec.plan` is the sole canonical plan representation; no plan file or duplicate data-root plan artifact exists.
- **ADR-8:** the owner-inclusive enabled invariant belongs to `RegistryDocument`, because only it owns the Richard/owner identity; `CustomerSpec` owns only customer/trainer/consent/inline-plan local checks.
- **ADR-9:** safety reasons are bounded typed data in `Safety`, not implicit strings or unstructured payloads.
- **ADR-10:** operator-time records identify a work entry, not a day/task aggregate; retries dedupe only the same entry.

## Owner map and bounded changes
- `/home/cube/.hermes/profiles/physique-coach/contracts/checkin-event.schema.json`: canonical event schema. Add eight event enum values and discriminated typed payloads; add bounded safety-reason schema.
- `checkin_cli/models.py`: frozen payload models, `SafetyReason`, updated `Safety`, event validation, deterministic keys/builders.
- `checkin_cli/customer_coaching.py`: `CustomerSpec` local validation and canonical inline `plan`; no owner-inclusive enabled assertion here.
- `checkin_cli/customer_admin.py`: guarded activation and payment/satisfaction/operator-time recording; no `extend` command.
- `checkin_cli/wizard.py`, `wizard_models.py`: adaptive/trainer flows and bounded raw-input safety classification at finalize.
- `checkin_cli/customer_schedule.py`: fixed KST pilot window only.
- `checkin_cli/customer_grounding.py`: draft gate and safe reason retrieval for owner/referral presentation.
- `checkin_cli/customer_reporting.py`: weekly summary and single-source KPI judgement.
- New `checkin_cli/operator_console.py`: stdlib loopback approval screen only.
- `gateway/platforms/nutrition_coaching.py`, `physique_checkin.py`, `telegram.py`: strict trainer routing, flow rendering, approval/send routing, safety-held notification.
- Safety/ops runbook and ADR documentation only; no other files without escalation.

## Canonical registry, plan, and activation
`CustomerSpec.plan` remains the canonical inline structured 12-week plan: it contains exactly 12 weekly entries and KST `starts_on`. G2 validates this inline object directly; there is **no plan file** and no second plan representation in the data root.

`CustomerSpec` performs local validation only: valid current-version granted consent, valid inline 12-week plan, well-formed customer and trainer triples, and customer/trainer pairwise distinct. It may not decide whether an enabled registry is valid because it has no owner.

`RegistryDocument` owns `owner: TelegramAddress` and validates every enabled entry using `RegistryDocument.validate_enabled_customer(spec)`: owner exists/well formed; customer, trainer, owner are pairwise distinct; and `spec.validate_local_activation_prerequisites()` passes. Thus an `enabled=True` registry document with a missing, invalid, or colliding owner is unrepresentable. Disabled drafts remain valid. Direct enable mutation is removed; direct disable remains always allowed.

The single consistent operation is:
```
activate_customer(profile_root: Path, data_root: Path, customer_id: str,
                  checklist_evidence_path: Path) -> ActivationResult
```
`profile_root` is the profile root; `data_root` is the explicit registered customer data root. The function resolves the registry from `profile_root`, requires `data_root == registry_entry.data_root` (normalized/contained under `profile_root`), validates the registry document and inline plan, then performs the audited append-only enable transition. No hidden/derived second root and no plan-file argument exist.

### G1–G5 guard (all required before enable)
- **G1 root/isolation:** explicit `data_root` exists, is the normalized registry-bound root below `profile_root`, and isolation audit finds no other-customer or personal-flow identifiers.
- **G2 canonical inline plan:** `CustomerSpec.plan` parses/validates as exactly 12 weeks with KST `starts_on`; no filesystem plan check is performed.
- **G3 consent:** granted consent record, current `CONSENT_VERSION`, and provider-terms check recorded for that version.
- **G4 identities:** `RegistryDocument` validates present, well-formed, pairwise-distinct customer/trainer/owner triples; trainer is the bound trainer.
- **G5 evidence:** checklist artifact parses and all are true: `token_rotated`, `missend_test_passed`, `provider_terms_checked`, `withdrawal_deletion_doc`, `retention_backup_doc`, `manual_fallback_doc`.

Tests include G1–G5 one-at-a-time failure/refusal with `enabled` unchanged, all-pass activation, direct-enable-path absence, disable-always-allowed, owner missing/invalid/collision document failures, and signature/root mismatch refusal.

## Canonical event contract
Add enum members `trainer_session_record`, `payment_record`, `satisfaction_record`, `operator_time_record`, `draft_created`, `draft_edited`, `draft_approved`, `draft_sent`; each has an `additionalProperties:false` typed property and `allOf` if/then requirement. Existing rules and schema version remain unchanged. Every event has a non-null deterministic `dedupe_key` (≥16 characters), schema validation before append, and duplicate-key append is exactly-once/no-op.

| Type | Typed payload / required bounded fields | Key |
|---|---|---|
| trainer session | done; performance 1–5; intensity `below/as_planned/above`; pain/operator note ≤2000 | `trainer-session:{customer}:{kst_date}` |
| payment | `amount_krw` **exactly 150000**; paid/period start/end dates; `method=bank_transfer`; `kind=initial/renewal` | `payment:{customer}:{kind}:{period_start}` |
| satisfaction | score 1–10; collection date; note ≤2000 | `satisfaction:{customer}:{iso_week}` |
| operator time | `entry_id` UUID/opaque 16–80 chars; `attempt_id` 16–80 chars; minutes 1–600; task enum; work date; optional `supersedes_entry_id` | `operator-time:{customer}:{entry_id}` |
| draft events | draft id; actor `ai/richard`; text ≤8000; optional edit/approval links | `{type}:{customer}:{draft_id}` |

`entry_id` is created once at operator-time entry creation and retained across network/client retries; same `entry_id` means retry/no-op. A second legitimate same-day/same-task session receives a different `entry_id` and is counted separately. Corrections append a replacement entry with its own `entry_id` and `supersedes_entry_id`; reporting excludes the superseded entry and never overwrites history. `attempt_id` is audit correlation only and is not a dedupe identity. Fixtures prove retry exactly-once, two same-task/day entries both counted, and correction replacement counted once.

## Bounded safety representation and retrieval
Add frozen `SafetyReason` in `models.py` and matching schema definition:
- `class`: enum `urgent|pain|disease|eating_risk|drugs|extreme_manipulation`;
- `source_flow`: enum `customer_checkin|trainer_session`;
- `matched_field`: bounded enum of declared free/structured fields;
- `excerpt`: normalized matched text, 1–160 chars, with control characters removed;
- `rule_id`: enum `S1`–`S6`.

`Safety` gains `reasons: list[SafetyReason]` (0–6, unique `(rule_id, matched_field, excerpt)`), while retaining `level`, bounded `signals`, and `coaching_held`. Any reason requires held safety; unsafe status requires safety. `wizard.classify_safety_signals` returns typed reasons and signals at finalization for both flows; it never persists a generic dict. `customer_grounding.safe_hold_reasons(event)` retrieves only typed reasons, formats their bounded class/field/excerpt for Richard’s held notice and referral guidance, and refuses a normal draft whenever signals/reasons are non-empty or consent is absent. Telegram/adapters consume that accessor, not raw wizard answers. Tests cover schema/model round-trip, malformed/oversized/unknown reason rejection, safe retrieval, notification reason, referral guidance, and zero customer send.

### Safety rule table
Case-insensitive KR/EN matches over submitted free text plus stated structured tests: S1 urgent chest pain/fainting/dyspnea (both); S2 pain (both); S3 disease/disorder (both); S4 eating risk (customer and **trainer operator note**, including calories <1000 customer rule); S5 drugs (both); S6 extreme manipulation (both, including ≥5kg/≤2-week stated goal). Existing urgent regex is retained. Each match generates the bounded typed `SafetyReason`, a matching bounded signal, `coaching_held=true`, and `stop_and_escalate` for S1 / `monitor` otherwise.

## Deterministic customer flow
- Sleep anomaly: sleep <6.0 or quality ≤2 → exactly `Q-SLEEP-CAUSE`, `Q-SLEEP-ADJUST`.
- Condition anomaly: readiness ≤2 → exactly `Q-COND-SYMPTOM`, `Q-COND-INTENSITY`.
- Performance anomaly: missed/partial, quality ≤2, or same-day trainer below plan → exactly `Q-PERF-REASON`, `Q-PERF-NEXT`.
- Normal/no safety: no follow-ups and short confirmation (one confirmation + one keep-behavior line, ≤2 messages).
- Change/problem: anomaly, ≥1.0kg prior-week-same-weekday change, trainer pain/below-plan, or safety hold → detailed evidence feedback (safety is held rather than normal feedback), still approval-gated.

Fixtures remain deterministic: F-NORMAL-1 (7.5h/quality4/readiness4/no pain/2300/done quality4/70.2 vs70.0); F-ANOM-SLEEP-1 (5.0h/quality2); F-ANOM-COND-1 (readiness2); F-ANOM-PERF-1 (missed); F-CHANGE-1 (71.4 vs70.2). They assert exact IDs and short/detailed outcomes.

## Schedule and KPI truth
Schedule emits weekday daily/weekly tasks only for exact eligible KST dates `starts_on..starts_on+27`; monthly is dormant and dates after day 28 emit nothing. Renewal never affects scheduler state.

The check-in denominator is that exact eligible weekday-date set (20 when Monday start); numerator is distinct accepted customer-check-in dates; pass ≥80%.

KPI passes only when all conditions hold: check-in ≥80%, satisfaction ≥8.0, **each individual weekly bucket** (weeks `[start+7n, start+7n+6]`, n=0..3) has active operator-time total ≤60 minutes, and a valid renewal exists. Valid renewal is exactly one effective `payment_record` with `kind=renewal`, `method=bank_transfer`, `amount_krw=150000`, `period_start_on=plan.starts_on+28 days`, `period_end_on=plan.starts_on+55 days`, and paid date on/before the renewal period end. It is KPI data only. Initial payment is likewise exact KRW150,000 bank transfer for the initial 28-day period. Fixtures reject zero/wrong amount, wrong method, wrong start/end period, and duplicate/retried renewal; accept only the exact correct-period record. KPI fixtures test 79/80%, 7.9/8.0, each-week 60/61, two same-day entries, retry, correction, and all-four-bucket conjunction.

## Implementation sequence
1. Safety Gate 0: rotate token, registry validation/isolation audit, test-account mis-send check, provider/consent alignment, and G5 artifact.
2. Schema/models/registry: typed payloads and `SafetyReason`; inline-plan registry model; G1–G5 activation guard/tests; event validation/dedupe tests.
3. Wizard/trainer flow: deterministic branching, exact-triple routing, rule-table classification and unit tests.
4. Grounding/adapters: evidence draft, held reason retrieval/notification/referral, draft audit path and all safety E2E fixtures.
5. Fixed schedule, console, reporting/KPI projection and focused tests.
6. Runbook/manual outage recovery and test-account dry run; only then guarded real activation.

## Acceptance criteria
- **AC1:** inactive draft can be enabled only by the consistent G1–G5 call; inline plan is canonical; RegistryDocument enforces owner-inclusive invariant while CustomerSpec retains local checks.
- **AC2:** token rotation and test-account cross-customer/personal mis-send count are zero.
- **AC3–AC4:** F-NORMAL-1 completes 60–90s with no follow-ups; anomaly fixtures open exactly their stated IDs.
- **AC5–AC6:** trainer record is ≤30s and joins same customer/KST date with zero cross-customer/personal records.
- **AC7–AC10:** evidence/approval/audit paths work; absent consent or any safety holds normal draft/send; normal short and specified problem/change detailed outcomes are exact.
- **AC11:** weekly summary includes rate, trends, keep/change behaviors, next decision.
- **AC12:** one stream judges all KPIs; all four weekly time buckets individually ≤60; renewal requires exact KRW150,000 bank-transfer correct period; rejection/retry/multiple-entry fixtures pass.
- **AC13–AC16:** withdrawal/deletion/backup/retention, provider terms, manual recovery/no duplicate, and personal/Hermes regressions pass.
- **AC17:** any declared safety class blocks draft/send, supplies typed held reason to Richard, and produces referral guidance.
- **AC18:** daily/weekly only within fixed window, monthly dormant, no task after KST day 28, and renewal emits no task.
- **AC19–AC20:** strict trainer rejection/acceptance and loopback/token HTTP protections pass.
- **AC21:** customer SF-S1-C…SF-S6-C and trainer **SF-S1-T, SF-S2-T, SF-S3-T, SF-S4-T, SF-S5-T, SF-S6-T** each prove raw input → typed stored class/reason → normal-draft hold → Richard notice with reason → referral guidance → zero customer sends.
- **AC22:** every new type validates; missing payload/reason failure and exactly-once dedupe are proven.

## Focused verification and release gate
Run existing profile (75), Telegram/Hermes wrapper (701), and core wrapper (109) suites plus `compileall` after touched steps. Add targeted tests for schema/model reason validation; G1–G5 and RegistryDocument owner invariant; event keys/retry/correction; all branch fixtures; trainer exact triple/join; every safety E2E fixture including SF-S1-T/SF-S4-T; predicate/notification/referral/zero-send; fixed scheduling; KPI boundary/rejection fixtures; loopback bind/token/HTTP approval; outage recovery. Stopwatch F-NORMAL-1 and trainer flow using test account. Real activation is prohibited until all ACs pass.

## Risks, rollback, and handoff
Token reuse, mis-send, safety mishandling, KPI false pass, and pilot overrun are mitigated by Gate 0, canonical append-only events, typed reasons, exact KPI predicates, per-bucket limits, and the single hard scheduler boundary. On halt, disable customer directly; retain or delete append-only records under runbook; console removal leaves Telegram approval intact; day-28 stop needs no action. Executor must touch only owner-map files and escalate before structural expansion. Critic verifies AC1–AC22 and anti-scope constraints before any customer enable.
