I read this as a read-only root-cause audit - I’ll trace the sealed Golden Path and tests against the live Telegram owner-approval/finalization path, isolate the exact missing state or input, and return file:line evidence plus one regression seam.


I’ve isolated the first coverage asymmetry: both sealed runners create a brand-new disabled customer and take only the no-risk `owner_ok` branch; the owner-risk callback test stops at `safety_hold -> owner_review` with mocks. I’m now checking the live-state invariant at finalization to distinguish an enabled-existing-customer projection failure from a receipt-binding failure.


I read this as a read-only root-cause audit - I traced the qualified runners, live Telegram callbacks, persisted live state, and finalization guards.

## Finding

**H3 is confirmed as a coverage gap, but the failure was not an enabled-customer projection error.** The missing case was:

> owner-risk acceptance + loss goal + existing customer `starts_on` producing a slightly unsafe weekly trajectory.

### Causal chain

1. Live owner-risk acceptance succeeded and returned the workflow to `owner_review`:
   - `gateway/platforms/telegram_nutrition_onboarding_runtime_callback.py:448`
   - Live receipt is persisted in `/home/cube/.hermes/profiles/dualcoachtest/data/customers/pilot_20260820_01/nutrition-onboarding/transient/workflow.json:1`.

2. `owner_ok` durably changed the state to `finalizing` before attempting finalization:
   - State mutation: `dualcoach/profile/checkin_cli/nutrition_onboarding.py:340`
   - Save: `dualcoach/profile/checkin_cli/nutrition_onboarding.py:414`
   - Callback review then finalize: `gateway/platforms/telegram_nutrition_onboarding_runtime_callback.py:520-528`
   - Live logs prove this ordering: `gateway.log:11606` records `business_state_committed`, then `gateway.log:11607` records `transition_rejected`.

3. Finalization uses the existing bootstrap/customer start date, not the approval date:
   - `gateway/platforms/telegram_nutrition_onboarding_runtime.py:277-278`
   - Live registry start date is `2026-08-21`: `/home/cube/.hermes/profiles/dualcoachtest/customers/registry.json:273`.

4. Live baseline requested loss from `82.3 kg` to `75 kg` by `2026-10-22`:
   - `/home/cube/.hermes/profiles/dualcoachtest/data/customers/pilot_20260820_01/nutrition-onboarding/baseline-candidate.json:1`

   This is 62 days / 8.857 weeks, requiring `0.82419 kg/week`, or **1.00145% of body weight per week**.

5. The calculation permits at most 1% weekly loss and raises `UnsafeGoalTrajectoryError(ValueError)` above that:
   - Date/weekly-rate calculation: `dualcoach/profile/checkin_cli/nutrition_onboarding_calculations.py:114-119`
   - Limit and exception: `dualcoach/profile/checkin_cli/nutrition_onboarding_calculations.py:120-127`
   - Called before artifact projection: `dualcoach/profile/checkin_cli/nutrition_onboarding_finalization.py:161`; projection is only at `:296`.

6. The callback catches the `ValueError` after the finalizing commit and returns the generic stale-state response, without rollback:
   - `gateway/platforms/telegram_nutrition_onboarding_runtime_callback.py:544`

### Why Golden Path passed

Both sealed runners used a newly registered disabled customer and a safe maintenance goal:

- Installed runner invokes the sealed source runner: `scripts/replay_dualcoach_v11_installed_golden.sh:47-55`.
- Source Golden Path starts with an empty registry: `dualcoach/releases/v1.0.0/delivered/verification-tools/source_golden_path.py:637`.
- It explicitly uses `customer_enabled=False`: same file `:752`.
- Disposable Golden Path also starts with no customers: `scripts/dualcoach_v111_disposable_fixture.py:83`.
- Its answer fixture chooses `"유지"`/maintenance: `scripts/run_dualcoach_v111_disposable_e2e.py:86-93`.
- It goes directly through `owner_ok`, never `owner_risk`: same file `:1072`.

The domain owner-risk finalization test also inherits the maintenance example (`nutrition_onboarding_contract.py:87`) while only injecting clinical-hold fields (`test_nutrition_onboarding.py:640-642`). The Telegram owner-risk callback test mocks `record_owner_risk_acceptance` and stops at `owner_review`; it never invokes real finalization (`tests/gateway/test_telegram_operator_notification_recovery.py:416,453,523`).

### Existing-customer state

The live customer was still correctly disabled:

- `/home/cube/.hermes/profiles/dualcoachtest/customers/registry.json:236-238`

Therefore `nutrition_onboarding_projection.py:41-42` was not the exception. The relevant existing state was its pre-established `starts_on`, which made the live loss target marginally exceed the safety bound.

## Regression seam

Add one real Telegram callback lifecycle test—without service mocks—parameterized with the exact live shape:

- existing disabled registry customer, `starts_on=2026-08-21`;
- `82.3 -> 75 kg`, target `2026-10-22`;
- clinical-risk answers;
- `owner_risk` callback followed by `owner_ok`.

Assert that unsafe trajectory is surfaced before an irreversible owner-review commit and the workflow does **not** remain `finalizing`. This would have failed before deployment while covering the exact branch and input omitted by both Golden Paths.

No files were edited and no git commands were run.