# RALPLAN — 듀얼코치 단일 고객 유료 파일럿 (Bounded Planner Stage 10, Revision)

## Summary
Elevate the existing 최코치/Hermes coaching base into the smallest safe dual-coach pilot for **one external paid customer** (non-shop): KRW 150,000/month via bank transfer, a 12-week plan written up front, and only the first 4-week loop activated — adaptive customer check-in (60–90s), trainer 30-second session record, per-customer isolation, AI evidence draft → Richard review/edit/approve/send (zero auto-send), weekly summary, and KPI judgement (renewal, check-in ≥80%, satisfaction ≥8/10, operator time ≤60 min/week) — operated over Telegram plus a Richard-only private loopback web screen. This stage-10 revision keeps the stage-9 Architect-approved design and applies all five Critic required changes without scope expansion: (1) an explicit activation guard in `customer_admin.py`/`customer_coaching.py` with per-condition bypass/failure tests; (2) the canonical contract `/home/cube/.hermes/profiles/physique-coach/contracts/checkin-event.schema.json` is added to the owner map and extended in place — new `event_type` enum members plus discriminated typed payload properties on `Event`, every pilot event validated against it, deterministic non-null `dedupe_key` required per append; (3) the `extend` command/date and all re-enable-after-day-28 behavior are removed — day 28 is a hard automation stop, a manual renewal payment event/KPI judgement may be recorded but cannot restart automation, and the check-in denominator is the exact set of eligible weekday dates in the fixed KST window; (4) a bounded input-level safety classification rule table (pain, disease/disorder, eating risk, drugs, extreme manipulation) for both customer and trainer inputs, with classification owned by `checkin_cli/wizard.py` at finalize and E2E fixtures from raw input → stored signal/reason → draft hold → Richard notification → referral guidance → zero customer send; (5) deterministic normal/anomaly/change fixtures with exact follow-up question ids and short-vs-detailed outcomes so AC3/AC4/AC10 need no ad-hoc thresholds. The corrected safety predicate (consent absent/not granted **or** safety signal present → no normal draft), trainer exact-triple authorization, monthly dormancy, stopwatch/HTTP/token/bind/no-duplicate verification, ADRs, Intent Reconciliation, and the anti-scope list are preserved.

## Principles
1. Human approval is absolute: AI drafts evidence; it never sends to the customer. Zero pre-approval sends.
2. Isolation is absolute: customer data/context/Telegram addresses never mix with personal coaching; append-only with `supersedes`, never overwrite.
3. Smallest safe surface: only the first-4-week daily/weekly loop runs; the 12-week plan exists as grounding context, not active features; monthly scheduling/reporting stays dormant during weeks 1–4; **all pilot automation stops hard at KST day 28 — there is no extension mechanism; a manual renewal payment record may be appended for KPI judgement but cannot restart automation.**
4. Safety bundle gates activation: any unverified safety item = real customer stays disabled; **activation itself is an explicit guarded operation (G1–G5 below), never a bare flag flip.**
5. One KPI source of truth: renewal/check-in/satisfaction/operator-time judged from a single record origin; **the check-in denominator is the fixed set of eligible weekday dates in the 28-day KST window, not calendar days.**
6. Loopback-only operator web: 127.0.0.1 bind or SSH tunnel, Richard-only, no external exposure, no auth framework.
7. Medical boundary: pain/disorder/eating-risk/drug/extreme-manipulation signals **classified from actual customer or trainer input at wizard finalize** stop normal feedback, hold the draft, notify the owner with the reason, and escalate to human review + professional referral. (Existing contract, `customer_grounding.py`: draft is refused when `safety_signals` is non-empty **or** consent is not granted — this plan matches that predicate verbatim and now also defines how real input becomes those signals.)
8. One canonical event contract: every pilot event is a typed payload on `Event`, validated against `/home/cube/.hermes/profiles/physique-coach/contracts/checkin-event.schema.json`, appended once via `EventStore.append_wizard_event` with a deterministic non-null `dedupe_key`.

## Intent Diff (reconciliation with deep-interview spec)
- Spec Round 2 correction honored: 3-customer launch → **1 paid customer pilot**.
- Spec Round 4 acceptance honored: private web console is **in** first-pilot scope, but its core is strictly `evidence view → edit → approve → send`.
- Spec Rounds 10–11 closure honored: 12-week plan written before start; only daily/weekly loop features activate in weeks 1–4; monthly and post-pilot automation stay off; **post-pilot continuation is a human decision recorded as data (renewal payment event + KPI judgement), never restarted automation** — honoring the four-week approval boundary.
- Lateral-panel guardrail honored: web must not become a separate product; no multi-customer/long-term features.
- Spec dual-record flow honored: friend-trainer completes a 30-second session record right after PT, joined to the same customer+date.
- Architect corrections honored: draft gate is "consent absent/not granted **or safety signal present** → no normal draft"; owner map names real implementation owners; trainer ingress/authorization/trigger defined; pilot-mode boundary testable; verification observable (stopwatch + HTTP).
- Critic round-2 corrections honored (this stage): explicit enable guard replaces the inaccurate "keep enable gating" wording; canonical schema is in the owner map with exact payload placement; `extend`/post-day-28 re-enable is removed; input-level safety classification is specified with a rule table and E2E fixtures; deterministic branch fixtures make AC3/AC4/AC10 reproducible.
- All confirmed outcomes from the assignment are preserved verbatim in scope and acceptance criteria.

## Decision Drivers
- HANDOFF: registry empty (0 customers), intentionally disabled; onboarding order (inactive draft → plan review → consent → validate → enable) already mandated.
- HANDOFF: past Telegram token exposure → rotation required before any customer activation.
- Critic code facts (verified against source): `customer_admin.set_customer_enabled()` currently flips `enabled` directly with no consent/plan/data-root/checklist verification, and `CustomerSpec` permits `enabled=True` with default ungranted consent → an explicit guard is required, not "keep gating".
- Critic code facts: `models.Event` currently carries only `check_in`, `safety`, and `payload_ref`; the canonical schema has `additionalProperties: false` and an enum without any pilot event type → pilot payloads must be placed on `Event` via an in-place schema extension, not invented by executors.
- Critic code facts: `wizard.py` currently creates `safety_signals` only from a narrow urgent-symptom regex (chest pain/fainting/dyspnea) and `customer_grounding.py` only consumes pre-populated signals → the classification rule table must live at wizard finalize for both flows.
- Critic code facts: `customer_schedule.build_due_customer_tasks()` derives tasks from the registry; with `extend` removed, no new schedule state exists anywhere — the active window derives solely from `plan.starts_on` + 28 KST days.
- Spec technical context: `customer_reporting.py` lacks satisfaction/payment/renewal/operator-time measurement — the KPI gap is the main additive build, fed by canonical append-only events, with a fixed weekday-date denominator.
- KPI conjunction and ≤60 min/week operator budget push toward short confirmations on normal days and detailed evidence feedback only on change/problem days, with thresholds stated in the fixture table (nothing ad hoc).

## Options
- **O1 Operator surface**: (a) minimal stdlib loopback HTTP screen bound to 127.0.0.1 with a single local token — **recommended**; (b) Telegram-only approval — rejected, spec requires the web screen; (c) framework dashboard with global auth — rejected, over-scope.
- **O2 Adaptive check-in**: (a) rule-based branching with the stated threshold table — **recommended**; (b) model-driven dynamic questioning — deferred (latency/cost risk to the 60–90s budget).
- **O3 KPI origin**: (a) canonical typed pilot events appended through `EventStore.append_wizard_event` into the same per-customer `events.jsonl` stream, `customer_reporting.py` as projection/judgement only — **recommended**; (b) manual spreadsheet — rejected; (c) reporting module as storage — rejected.
- **O4 Draft approval storage**: (a) distinct events for AI draft / edited final / approval / send — **recommended**; (b) single overwritten field — rejected, violates append-only.
- **O5 Append mechanism**: (a) reuse the canonical `EventStore.append_wizard_event(Event)` path (lock + dedupe_key exactly-once + append + view rebuild); `store.py` gains **no** generic append path — **recommended**; (b) generic append API — rejected.
- **O6 Trainer ingress**: (a) exactly one registry-bound trainer Telegram address; strict exact-triple authorization; trainer-initiated command/button — **recommended**; (b) scheduled prompt — rejected; (c) reusing customer/owner address — rejected.
- **O7 Pilot schedule boundary**: (a) pilot-mode projection inside `customer_schedule.py`: weekday `daily` + `weekly` only while KST date ≤ `plan.starts_on`+27 days; `monthly` suppressed; **hard stop after day 28 with no extension input of any kind** — **recommended**; (b) delivery-site suppression — rejected (second due-task truth); (c) operator-set extension date — **removed per Critic** (post-day-28 automation is outside the four-week approval boundary).
- **O8 Pilot payload representation**: (a) extend the canonical schema in place — new `event_type` enum members + one discriminated typed payload property per pilot event on `Event`, mirrored as frozen typed models in `checkin_cli/models.py`, following the schema's existing `allOf` if/then pattern — **recommended (smallest direct canonical representation)**; (b) `payload_ref` sidecar documents — rejected (splits the canonical representation into two truths, executors would still invent the sidecar format).

## In Scope
- One external paid customer: KRW 150k/month bank transfer, first 4-week pilot of a 12-week plan.
- Adaptive weekday customer check-in (60–90s) per the deterministic branch rule table; normal days short, anomaly days open only the stated related follow-ups.
- Trainer 30-second session record (session done, performance, intensity vs plan, pain, operator note) from exactly one registry-bound trainer Telegram address via trainer-initiated command/button, joined to the same customer+KST date.
- **Input-level safety classification** at wizard finalize for both flows per the rule table (pain, disease/disorder, eating risk, drugs, extreme manipulation, plus the preserved urgent-symptom row), storing signal + reason on the event's `safety` property.
- AI evidence draft (12-week plan, recent records, trainer records, approved 최코치 knowledge ≤5 excerpts, rationale); **no normal draft when consent is absent/not granted OR any safety signal is present**; a present signal additionally notifies the owner with the hold reason, sends nothing to the customer, and routes the record to human review with professional-referral guidance.
- Richard review/edit/approve/send on Telegram **and** a private loopback web screen; draft/final/edit history as distinct append-only events; 0 pre-approval sends.
- Weekly summary: check-in rate, key trends, keep-behaviors, change-behaviors, next decision point.
- KPI single source: scheduled (fixed eligible-weekday denominator) vs completed check-ins, satisfaction, operator work time, first payment and actual renewal; 4-week judgement = conjunction of renewal done, check-in ≥80%, satisfaction ≥8/10, ≤60 min/week.
- **Explicit activation guard (G1–G5)** replacing the direct enable flip; **canonical schema extension** for the eight pilot event types with deterministic non-null `dedupe_key`.
- Pilot-mode scheduling: weekday daily + weekly in weeks 1–4; monthly dormant; **hard stop at KST day 28; no extension command, state, or re-enable path anywhere.**
- Safety bundle: token rotation, mis-send prevention with test account, consent versioning + AI provider terms check, withdrawal/deletion/backup/log-retention procedures documented and tested, manual fallback mode without duplicate sends.
- Regression: personal check-in and Hermes wrapper suites stay green.

## Out of Scope
- Business registration, PG, auto-billing; multi-customer/multi-trainer/role accounts; B2B/SaaS; customer web portal; monthly auto-report delivery; auto knowledge refresh; AI auto-send; **generic Hermes redesign; production-wide systemd or auth changes; browser framework; crash-consistency theorem work; generic concurrency work; exact deployment scripts; any multi-customer expansion**; shop DB/brand/payment/facility recruitment; any merge/replacement of the personal coaching flow; configured/scheduled trainer prompts; **any pilot extension mechanism, post-day-28 automation restart, or renewal-triggered scheduling (renewal is a recorded payment event and KPI input only)**; any `payload_ref` sidecar format; any generic event-payload dict.

## File-Level Changes (bounded implementation-owner map — reuse first, no redesign)
- **`/home/cube/.hermes/profiles/physique-coach/contracts/checkin-event.schema.json`** — canonical contract, extended in place (O8a): `event_type` enum gains `trainer_session_record`, `payment_record`, `satisfaction_record`, `operator_time_record`, `draft_created`, `draft_edited`, `draft_approved`, `draft_sent`; new `additionalProperties:false` payload properties `trainer_session`, `payment`, `satisfaction`, `operator_time`, `draft` (fields below); `allOf` gains one if/then per new type requiring its payload property (mirroring the existing `morning_checkin`/`workout_record` pattern); `schema_version` stays `"2.0"`; all existing rules unchanged.
- `checkin_cli/models.py` — mirror of the canonical contract: new `EventType` members (same eight names), frozen typed payload models (`TrainerSessionPayload`, `PaymentPayload`, `SatisfactionPayload`, `OperatorTimePayload`, `DraftPayload`), and matching optional typed fields on `Event` (no generic dict payloads; `check_in`/`safety`/`payload_ref` untouched). Each pilot-type constructor computes a **deterministic non-null `dedupe_key`** (formats below, each ≥16 chars per schema). A `validate_event(event)` helper validates every constructed event against the canonical schema file; tests assert validation for every new type.
- `checkin_cli/wizard.py` — adaptive nutrition branching per the stated threshold table; trainer session flow transitions (5 fields, ≤30s, KST-date stamped at finalize); **safety classification owner**: `classify_safety_signals(answers)` runs at finalize of both the customer check-in and trainer session flows, applies the bounded rule table below, and writes the matched class signals + reason strings onto the event `safety` property (`level`, `signals`, `coaching_held=true` when any signal present). The existing urgent-symptom regex is preserved as the stop-and-escalate row.
- `checkin_cli/wizard_models.py` — **only** the one new trainer `WizardFlow` member plus its typed session state (O6a). No other change.
- `checkin_cli/customer_coaching.py` — registry: optional `trainer: TelegramAddress | None` on `CustomerSpec`; validation that customer/trainer/owner addresses are pairwise distinct; **model-level invariant: `CustomerSpec` rejects `enabled=true` unless consent is granted at the current version and all three identities are present/valid/distinct** (defense-in-depth behind the admin guard; it makes the prior "enabled with default ungranted consent" state unrepresentable).
- `checkin_cli/customer_admin.py` — CLI: bind trainer address during onboarding; record payment/satisfaction/operator-time events (canonical path; renewal recorded as `payment.kind="renewal"` — data only, never scheduling input); **explicit activation guard `activate_customer(profile_root, customer_id, checklist_evidence_path)` replacing the direct `set_customer_enabled(True)` flip** (G1–G5 below); disabling (`enabled=false`) stays a direct, always-allowed operation; **no `extend` command exists**.
- `checkin_cli/customer_schedule.py` — pilot-mode projection (O7a): emit weekday `daily` and `weekly` tasks only while KST date ∈ [`plan.starts_on`, `plan.starts_on`+27 days]; never emit `monthly` in pilot mode; **emit nothing after day 28 — the module takes no extension input and has no re-enable path**; claim/no-duplicate semantics unchanged.
- `checkin_cli/customer_grounding.py` — assemble evidence draft (plan, trends, trainer session records, approved knowledge ≤5 excerpts, rationale). Predicate unchanged (already correct): no draft when consent not granted **or** any safety signal present; on a present signal, surface the stored hold reason so the adapter can notify the owner.
- `checkin_cli/customer_reporting.py` — weekly summary + 4-week KPI judgement from the same per-customer `events.jsonl` stream incl. new typed events; **scheduled-check-in denominator = exact eligible weekday dates** (below).
- New `checkin_cli/operator_console.py` — stdlib loopback HTTP screen: 127.0.0.1-only bind (refuse non-loopback), one local token (missing/wrong → reject), draft queue → edit → approve → send on the **same** path as Telegram; no framework, no external exposure.
- `checkin_cli/store.py` — **explicitly unchanged: no generic append path.** Canonical mechanism stays `EventStore.append_wizard_event(Event)` (record lock + `dedupe_key` exactly-once + append + deterministic view rebuild); every pilot event is schema-validated before append (O5a).
- `gateway/platforms/nutrition_coaching.py` — coordinator: resolve/strictly authorize trainer route (exact `(user_id, chat_id, topic_id)` triple equals the bound trainer address; customer address and foreign senders get a fixed refusal, no state change); trainer completion → append `trainer_session_record` via canonical path; owner-only draft request tokens; `safety_held` completion notice now carries the stored hold reason.
- `gateway/platforms/physique_checkin.py` — step rendering/action decoding for adaptive nutrition steps and trainer session steps (incl. the stated follow-up question ids); extend `_STEP`/`_ACTION` decoding bounds; render the safety-hold reply.
- `gateway/platforms/telegram.py` — route trainer command/button and callbacks; owner draft/approve/send callbacks; scheduled delivery honoring pilot-mode tasks (monthly rendering branch dormant for pilot customers); owner safety-hold notification with reason; no customer send except the approved path.
- Docs: safety/ops runbook (withdrawal, deletion, backup, retention, manual fallback, provider-terms checklist, **safety rule table**, activation checklist artifact format) + one ADR set.

## Activation Guard (G1–G5 — required before writing `enabled=true`)
`customer_admin.activate_customer(profile_root, customer_id, checklist_evidence_path)` takes the profile root and the customer's data root as explicit inputs and refuses activation unless **all** hold:
- **G1 data root**: registered per-customer data root exists under `profile_root`, matches the registry entry, and the isolation audit finds no other customer's or personal-flow identifiers in it.
- **G2 valid 12-week plan**: plan file in the data root parses, contains 12 weekly entries and a KST `starts_on`, and matches the registry plan.
- **G3 granted/versioned consent**: consent record exists, status granted, `consent_version` equals the current `CONSENT_VERSION`, and the provider-terms check is recorded for that version.
- **G4 isolated identities**: customer, trainer, and owner Telegram triples are all present, well-formed, and pairwise distinct; trainer equals the registry-bound trainer address.
- **G5 pre-activation checklist evidence**: the checklist artifact parses and every item is true: `token_rotated`, `missend_test_passed`, `provider_terms_checked`, `withdrawal_deletion_doc`, `retention_backup_doc`, `manual_fallback_doc`.
Only then is `enabled=true` written (audited append-only registry change). Tests: one bypass/failure test per condition G1–G5 (each condition individually missing/false → activation refused, `enabled` unchanged), plus the all-pass happy path and disable-always-allowed.

## Canonical Pilot Event Payloads (exact placement on `Event`)
| Event type | Payload property | Required fields (all `additionalProperties:false`) | Deterministic `dedupe_key` |
|---|---|---|---|
| `trainer_session_record` | `trainer_session` | `session_done` bool; `performance_1to5` int 1–5; `intensity_vs_plan` enum `below/as_planned/above`; `pain_summary` str ≤2000; `operator_note` str ≤2000 | `trainer-session:{customer_id}:{kst_date}` |
| `payment_record` | `payment` | `amount_krw` int ≥0; `paid_on` date; `period_start_on` date; `period_end_on` date; `method` enum `bank_transfer`; `kind` enum `initial/renewal` | `payment:{customer_id}:{period_start_on}:{kind}` |
| `satisfaction_record` | `satisfaction` | `score_1to10` int 1–10; `collected_on` date; optional `note` ≤2000 | `satisfaction:{customer_id}:{iso_year_week}` |
| `operator_time_record` | `operator_time` | `minutes` int 0–600; `task` enum `draft_review/customer_message/onboarding/reporting/other`; `work_on` date | `operator-time:{customer_id}:{work_on}:{task}` |
| `draft_created` / `draft_edited` / `draft_approved` / `draft_sent` | `draft` | `draft_id` str; `actor` enum `ai/richard`; `text` ≤8000; optional `edited_from_draft_id`; optional `approved_message_id` | `{event_type}:{customer_id}:{draft_id}` (hyphenated type prefix) |
Every new append carries its computed non-null `dedupe_key` (≥16 chars, schema-checked); a duplicate key append is a no-op via the existing exactly-once mechanism. `status` stays `accepted` unless `unsafe`; existing `status=unsafe → safety required` rule applies. All events validate against the updated canonical schema before append and in tests.

## Safety Classification Rule Table (owner: `checkin_cli/wizard.py` at finalize, both flows)
Case-insensitive substring match over the submission's free-text fields (KR/EN), plus the stated structured-field rules. Each match stores a signal string `class:matched_field` (≤120 chars) with the matched snippet as the reason, sets `safety.coaching_held=true`; urgent row sets `level=stop_and_escalate`, all others `level=monitor`.
| # | Class | Applies to | Deterministic match | Stored signal |
|---|---|---|---|---|
| S1 | Urgent symptoms (existing) | both | existing regex: chest pain / fainting / dyspnea (가슴 통증, 실신, 호흡곤란) | `urgent:{field}` |
| S2 | Pain | both | structured pain field not in {"", "없음", "none"}; or keywords: 통증, 아프, 부상, 저림, 마비, pain, injur, sprain | `pain:{field}` |
| S3 | Disease/disorder | both | 질환, 진단, 병원, 처방, 당뇨, 고혈압, 심장, 갑상선, diabetes, hypertension, thyroid, heart disease, diagnos | `disease:{field}` |
| S4 | Eating risk | customer (+trainer note field) | 폭식, 거식, 섭식장애, 식이장애, 토하, 구토, binge, purge, anorexia, bulimia, vomit; **or** recorded `calories_kcal < 1000` | `eating_risk:{field}` |
| S5 | Drugs | both | 스테로이드, 약물, 이뇨제, 완하제, 다이어트약, steroid, anabolic, diuretic, laxative, clen, ephedrine | `drugs:{field}` |
| S6 | Extreme manipulation | both | 단식, 금식, 탈수, 사우나복, 급감량, water cut, sauna suit, dehydrat, 24시간 이상 단식; **or** stated goal of ≥5 kg weight change in ≤2 weeks | `extreme_manipulation:{field}` |
Downstream (unchanged predicate): non-empty `safety.signals` → no normal draft, owner notified with the stored reason, record routed to human review with professional-referral guidance, zero customer sends. **E2E fixtures** (raw input → stored signal/reason → hold → Richard notification → referral guidance → zero customer send): one per class from customer input (SF-S1-C … SF-S6-C) and one per applicable class from trainer input (SF-S2-T, SF-S3-T, SF-S5-T, SF-S6-T).

## Deterministic Branch Fixtures (AC3/AC4/AC10 — thresholds stated here, nowhere else)
Branch rules at customer check-in finalize (anomaly checks in order; independent per dimension):
- **Sleep anomaly**: `sleep_hours < 6.0` **or** `sleep_quality_1to5 ≤ 2` → opens exactly `Q-SLEEP-CAUSE` (select: 카페인/야근/스트레스/기타) and `Q-SLEEP-ADJUST` (취침시간 조정 의사 yes/no).
- **Condition anomaly**: `readiness_1to5 ≤ 2` → opens exactly `Q-COND-SYMPTOM` (select: 피로/근육통/감기기운/기타) and `Q-COND-INTENSITY` (select: 유지/하향/휴식).
- **Performance anomaly**: `workout_completion ∈ {"missed","partial"}` **or** `workout_quality_1to5 ≤ 2` **or** same-day trainer `intensity_vs_plan == "below"` → opens exactly `Q-PERF-REASON` (select: 시간부족/컨디션/통증/기타) and `Q-PERF-NEXT` (다음 세션 조정 수락 yes/no).
- **Normal day**: no anomaly and no safety signal → zero follow-ups; **short confirmation** output (1 confirmation + 1 keep-behavior line, ≤2 messages).
- **Change/problem day** (AC10 detailed output): any anomaly above, **or** `|body_weight − prior-week same-weekday weight| ≥ 1.0 kg`, **or** trainer record with pain or below-plan intensity, **or** any safety signal (held path instead of normal feedback) → **detailed evidence feedback** (plan-vs-record evidence + rationale + 1–2 change behaviors), still behind approval.
Fixtures:
- **F-NORMAL-1**: sleep 7.5h, quality 4, readiness 4, pain "없음", calories 2300, workout "done" quality 4, weight 70.2 vs prior 70.0 → opens nothing; AC3 short path in 60–90s; AC10 short confirmation.
- **F-ANOM-SLEEP-1**: sleep 5.0h, quality 2, all else normal → opens only `Q-SLEEP-CAUSE`, `Q-SLEEP-ADJUST`; AC4.
- **F-ANOM-COND-1**: readiness 2, all else normal → opens only `Q-COND-SYMPTOM`, `Q-COND-INTENSITY`; AC4.
- **F-ANOM-PERF-1**: workout "missed", all else normal → opens only `Q-PERF-REASON`, `Q-PERF-NEXT`; AC4; day classified change/problem → AC10 detailed feedback.
- **F-CHANGE-1**: weight 71.4 vs prior-week same-weekday 70.2 (Δ1.2 kg), all else normal → no extra questions beyond base; AC10 detailed feedback with weight-change rationale.

## KPI Denominator (fixed)
Scheduled denominator = the exact set of KST dates `d` with `plan.starts_on ≤ d ≤ plan.starts_on + 27 days` and `weekday(d) ∈ {Mon..Fri}` (20 dates when `starts_on` is Monday), fixed at pilot start; numerator = distinct eligible dates with an accepted customer check-in event; rate = numerator/denominator, pass ≥ 0.80. Renewal is judged from `payment_record(kind="renewal")` presence; it has no scheduling effect.

## Sequencing & Dependencies
1. **Safety Gate 0 (blocks everything customer-facing)**: rotate Telegram token; `registry validate`; isolation audit; test-account mis-send prevention check; provider terms vs consent text alignment; produce the G5 checklist artifact.
2. Canonical contract + models: schema extension, `models.py` typed payloads/`dedupe_key` builders/`validate_event`, registry trainer address + `enabled` invariant (`customer_coaching.py`), **activation guard G1–G5** (`customer_admin.py`) + unit tests (schema validation per type, dedupe exactly-once, guard bypass/failure per condition).
3. Adaptive check-in branching + trainer session flow + **safety rule table** across `wizard.py`, `wizard_models.py`, `physique_checkin.py`, `nutrition_coaching.py`, `telegram.py` + tests (fixture-table branch tests, trainer authorization, same customer+KST-date join, input-level classification unit tests).
4. Grounding evidence draft + draft/edit/approve/send audit events; predicate tests: (a) consent granted + no signal → draft; (b) consent absent → no draft; (c) signal present → no normal draft + owner notice with reason + zero customer send; **E2E safety fixtures SF-S1-C…SF-S6-C/SF-S2-T/SF-S3-T/SF-S5-T/SF-S6-T**; 0 pre-approval sends.
5. Pilot-mode schedule projection: weekday daily/weekly only, monthly dormant, **hard stop after KST day 28 with no extension input**; claim semantics unchanged.
6. Loopback operator console wired to the same approve/send path; non-loopback bind and missing/wrong token rejection tests.
7. Weekly summary + KPI judgement with the fixed weekday denominator and boundary values.
8. Runbook + manual fallback drill (outage → human operation, no duplicate sends on recovery).
9. End-to-end dry run with a test account (stopwatch runs, loopback HTTP checks); only after every safety acceptance criterion passes, execute the guarded activation (G1–G5) for the real customer.
Dependencies: 2→3,4; 4→6; 3,4→7; 5 before any enabled dry run; all →9. Personal-flow regression suite runs after every step.

## Acceptance Criteria (testable)
- AC1 Customer registered as inactive draft; **activation succeeds only through the G1–G5 guard**: profile/data root input, valid 12-week plan, granted+current-version consent, isolated valid customer/trainer/owner identities, and complete checklist evidence; each condition individually missing → activation refused and `enabled` unchanged (one failure test per condition); direct `enabled=true` write path does not exist.
- AC2 Token rotated; with a test account, cross-customer/personal mis-send count = 0 and permission boundaries verified.
- AC3 F-NORMAL-1 weekday check-in completes in 60–90s stopwatch dry run with zero follow-up questions opened.
- AC4 F-ANOM-SLEEP-1 / F-ANOM-COND-1 / F-ANOM-PERF-1 open exactly the stated follow-up question ids and no others.
- AC5 Trainer record (done, performance, intensity vs plan, pain, operator note) completed in ≤30s stopwatch dry run.
- AC6 Customer check-in and trainer record join on the same customer+KST date; 0 records from other customers or personal data.
- AC7 Draft displays 12-week plan, recent records, trainer records, approved knowledge excerpts, and rationale; **consent absent/not granted OR any safety signal present → no normal draft.**
- AC8 Richard can view evidence, edit, approve, send from the loopback web screen and Telegram.
- AC9 Pre-approval customer sends = 0; AI original, edited final, and edit history stored as distinct `draft_*` events.
- AC10 F-NORMAL-1 → short confirmation; F-ANOM-PERF-1 and F-CHANGE-1 → detailed evidence feedback; no other day-type outcomes exist.
- AC11 Weekly summary contains check-in rate, key trends, keep/change behaviors, next decision point.
- AC12 Single source yields the fixed eligible-weekday denominator/completed count, satisfaction, all operator work time, first payment, actual renewal; 4-week judgement = logical AND of renewal, ≥80%, ≥8/10, ≤60 min/week (boundary values 79/80%, 7.9/8.0, 60/61 min tested).
- AC13 Withdrawal/deletion/backup/log-handling/retention procedures documented and tested.
- AC14 Provider training-use/retention/region/sub-processor terms checked and matched to consent version.
- AC15 Model/web/Telegram outage → manual human operation; recovery without duplicate sends.
- AC16 Personal check-in and Hermes regression suites all pass (baseline: 776 passed per HANDOFF).
- AC17 **Safety escalation**: a present pain/disease/eating-risk/drug/extreme-manipulation signal blocks the normal draft and any customer send, notifies Richard with the hold reason, and transitions the record to human review with professional-referral guidance.
- AC18 **Pilot boundary**: during weeks 1–4 only weekday `daily` and `weekly` pilot tasks are emitted; monthly scheduling/reporting stays dormant; **after KST day 28 no automated pilot task runs and no extension/re-enable input exists anywhere in the system**; a `payment_record(kind="renewal")` may be appended for KPI judgement and provably emits no tasks; the 12-week plan remains grounding-only.
- AC19 **Trainer authorization**: exactly one trainer address bound; exact trainer triple accepted; customer's own address rejected; foreign sender rejected with fixed refusal and no state change; trigger is trainer-initiated command/button only.
- AC20 **Console hardening**: console refuses non-127.0.0.1 bind; missing/wrong local token rejected; view/edit/approve/send exercised over HTTP against 127.0.0.1.
- AC21 **Input-level safety classification (E2E)**: fixtures SF-S1-C…SF-S6-C (customer input) and SF-S2-T/SF-S3-T/SF-S5-T/SF-S6-T (trainer input) each prove: raw input → stored `safety` signal with class+reason → normal draft held → Richard notified with the reason → referral guidance produced → zero customer sends.
- AC22 **Canonical contract**: every new pilot event validates against the updated `checkin-event.schema.json`; each append carries a deterministic non-null `dedupe_key`; re-appending the same key yields exactly one stored event; the eight new enum members and their discriminated payload requirements are enforced by the schema (payload missing → validation failure).

## Focused Verification
- Re-run existing baselines after each step: profile tests (75), Telegram/Hermes wrapper tests (701), core wrapper tests (109); `compileall` on touched packages.
- New targeted tests: schema validation per new event type incl. missing-payload failures; dedupe_key determinism + exactly-once; activation guard G1–G5 bypass/failure + happy path + disable-always-allowed; adaptive branching on F-NORMAL-1/F-ANOM-*/F-CHANGE-1 (exact question ids, short vs detailed); rule-table classification unit tests per row S1–S6 for both flows; trainer authorization (exact accepted; customer address rejected; foreign rejected; same customer+KST-date join); isolation join; draft predicate three-case tests; E2E safety fixtures (AC21); approve/send audit (0 pre-approval sends); KPI judgement arithmetic with fixed weekday denominator incl. boundary values; pilot-mode schedule (monthly dormant weeks 1–4; zero tasks after KST day 28; renewal payment event emits no tasks); loopback console refuses non-loopback bind; missing/wrong local token rejected.
- **Stopwatch dry runs** with the test account: F-NORMAL-1 inside 60–90s; anomaly-day check-ins inside budget with only the stated follow-ups; trainer session record ≤30s.
- **Loopback HTTP checks**: draft queue view, edit, approve, send over 127.0.0.1; same approval path; distinct audit events; zero pre-approval sends.
- Manual drills: mis-send prevention with test account; outage→manual→recovery no-duplicate drill; withdrawal/deletion walkthrough.
- Guarded activation (G1–G5) as the final pre-activation gate, evidence artifact retained.

## Risks & Mitigations
- R1 Token leak reuse → rotate first; assume old token compromised (HANDOFF §6).
- R2 Mis-send to wrong chat/topic → inactive-draft gating, guarded activation, test-account verification, claim checks before retry.
- R3 Auto-send regression → single send path behind explicit approval; audit events; tests assert 0 pre-approval sends.
- R4 Loopback screen exposure → 127.0.0.1-only bind test, local token (0600 file), wrong/missing token rejection tests, SSH tunnel doc.
- R5 KPI source gaps → canonical typed events via the existing exactly-once path; fixed weekday denominator; boundary-value judgement tests.
- R6 Scope creep → out-of-scope list enforced at review; web core limited to evidence→edit→approve→send.
- R7 Provider terms mismatch → terms checklist tied to consent version (G3/AC14).
- R8 Operator time >60 min/week → short-confirmation default; per-task time logged from day 1.
- R9 Safety signal mishandled → input-level rule table at finalize, grounding hard-block, owner notification with reason, human review + referral; unit + E2E fixtures (AC17/AC21). False positives are safe-direction (human review anyway); table edits require plan-level review.
- R10 Trainer impersonation/misroute → single registry-bound trainer address, exact-triple authorization, rejection tests (AC19).
- R11 Pilot overrun → pilot-mode projection in the single due-task source; **hard day-28 stop with no extension state; renewal event proven to emit no tasks (AC18)**.
- R12 Append-path drift → one canonical mechanism (`append_wizard_event`), `store.py` unchanged (O5a); schema validation before append (AC22).
- R13 Unauthorized/accidental activation → G1–G5 guard + model-level invariant; per-condition failure tests; activation evidence retained (AC1).

## Rollback
- Keep customer `enabled=false` until all safety ACs pass; disabling is a single direct registry state change (always allowed).
- All customer data changes are append-only with `supersedes`; record-level changes reversible without data loss.
- Loopback console additive; removing it leaves the Telegram approval path intact.
- Personal coaching flow untouched; regression suites confirm no bleed.
- Mid-course halt: disable the customer, schedules stop via existing claim semantics (no duplicate sends), retain/delete per documented retention procedure. Day-28 stop needs no action — automation simply has no window.

## ADR & Intent Reconciliation
- **ADR-1**: Minimal stdlib loopback console (O1a) — least surface satisfying spec Round 4.
- **ADR-2**: Rule-based adaptive check-in with the stated threshold table (O2a) — protects the 60–90s budget; removes ad-hoc threshold choice.
- **ADR-3**: Canonical typed pilot events into the single per-customer stream; reporting as projection only (O3a).
- **ADR-4**: Distinct draft/final/approval audit events (O4a).
- **ADR-5**: `store.py` unchanged; `append_wizard_event` is the sole append path (O5a).
- **ADR-6**: One registry-bound trainer address, exact-triple authorization, trainer-initiated trigger; distinct trainer wizard flow is the sole reason `wizard_models.py` is touched (O6a).
- **ADR-7**: Pilot-mode projection with a **hard KST day-28 stop and no extension mechanism** (O7a) — per Critic, post-day-28 automation is outside the four-week approval boundary; renewal exists only as a `payment_record(kind="renewal")` KPI datum.
- **ADR-8**: Canonical schema extended in place with enum members + discriminated typed payload properties (O8a); `payload_ref` sidecar rejected; every pilot event validated against the canonical file with deterministic non-null `dedupe_key`.
- **ADR-9**: Explicit activation guard G1–G5 in `customer_admin.py` backed by a `CustomerSpec` invariant in `customer_coaching.py`; the prior direct `enabled` flip is removed rather than wrapped, because current code has no gating to "keep".
- **ADR-10**: Input-level safety classification as a bounded rule table owned by `checkin_cli/wizard.py` at finalize for both flows; the existing urgent-symptom regex is preserved as row S1; grounding's predicate stays the single downstream gate.
- **Reconciliation**: the plan preserves every confirmed outcome (1 external paid customer, KRW 150k, 12-week plan, 4-week pilot, adaptive check-in, 30s trainer record, isolation, evidence draft, Richard review/edit/approve/send, Telegram + private loopback screen, weekly summary, four KPIs, safety + human approval) and excludes every named anti-scope item (no generic Hermes redesign, no production-wide systemd/auth, no browser framework, no crash-consistency theorem, no generic concurrency, no exact deployment scripts, no multi-customer expansion, **no post-day-28 automation/extension**). The stage-8 inverted safety predicate stays corrected (consent absent **or safety signal present** → no normal draft + owner notification + human review/referral), and stage-9/10 close the Critic's activation-guard, canonical-contract, extension-state, input-safety, and fixture-determinism gaps. No conflict with the deep-interview spec.

## Handoff
- Executor: implement steps 2–8 strictly in sequence; touch only the files named in the bounded owner map (including the canonical schema file); escalate before touching anything else.
- Critic: review against AC1–AC22 and the anti-scope list before any real-customer enable.
- Escalate to architect only if a listed touch point requires structural change beyond reuse.
- Final activation (step 9) is a human-gated, guarded (G1–G5) checklist execution, not an autonomous action.
