# nutricoach-telegram-checkin-stepper - Work Plan

## TL;DR (For humans)
<!-- Fill this LAST, after the detailed plan below is written, so it summarizes the REAL plan. -->
<!-- Plain English for a non-engineer: NO file paths, NO todo numbers, NO wave/agent/tool names. -->

**What you'll get:** A simpler daily Telegram check-in where each accepted answer produces one clear next-question card, shows truthful progress, and offers `잘 모르겠어요` plus valid-history-only `이전 답 수정`. Leaving the chat never loses the draft, explicit Resume restores the exact cursor when needed, and delivery/restart failures cannot silently duplicate progress or messages.

**Why this approach:** The existing wizard remains the source of truth while a private gateway projection records only message-delivery state. This preserves current customer data and callbacks, minimizes Telegram writes to one card, and handles Telegram's lack of exactly-once delivery honestly.

**What it will NOT do:** It will not add a web app or external service, expose customer answers, redesign unrelated flows, send real customer messages during QA, reuse r70 authority, or perform git/PR work.

**Effort:** Large
**Risk:** High - the UI change is moderate, but it includes crash-safe Telegram delivery state, legacy-session compatibility, and a one-use production upgrade.
**Decisions to sanity-check:** One new card per accepted answer; at most two secondary controls; unknown allowed on 11 fields and represented as missing data; no automatic resend after any durable delivery failure; fresh r71 sealed deployment.

Your next move: run the reviewed plan with `/ulw-execute nutricoach-telegram-checkin-stepper`. Full execution detail follows below.

---

> TL;DR (machine): Large/high-risk production change; 11 implementation todos plus 4 final verifiers deliver a two-control confirmation stepper, typed unknowns, durable Telegram recovery, legacy compatibility, sealed r71 deployment, and observer rebinding.

## Scope
### Must have
- Apply the redesign only to the registered customer's `nutrition_daily` Telegram flow.
- After each committed answer, send exactly one new bottom-of-chat card containing a value-free save confirmation, completed-count progress, and the next question.
- Render at most two secondary controls on ordinary nutrition-daily question cards: `잘 모르겠어요` and, when history exists, `이전 답 수정`. Do not render `나중에 계속`; leaving the chat requires no transition, the existing card remains answerable until expiry, and explicit launcher/Resume reconstructs the durable cursor afterward or after delivery uncertainty.
- Preserve content-choice buttons required by a question. The two-button decision limits secondary navigation controls, not the question's bounded answer choices.
- Model known, unknown, and unanswered as mutually exclusive private session states. Unknown must never become zero, normal, rest, adherence, a safety signal, or any inferred value.
- Keep canonical event schemas compatible: unknown fields serialize as omitted/`None`; the finalized private session retains `unknown_steps`.
- Add a gateway-owned, content-free Telegram publication projection with at-most-once automatic send attempts and explicit recovery after uncertain delivery.
- Read legacy binding/session state, write additive v2 state, and preserve the live r70 calories-step draft without changing its semantic cursor or existing answer.
- Build, qualify, rehearse, and deploy a fresh one-use r71 sealed successor; verify production and rebind the seven-day observer to r71.
- Use TDD for every behavioral change. Every QA action must be agent-executed and must send zero real customer messages.

### Must NOT have (guardrails, anti-slop, scope boundaries)
- No Telegram Mini App, browser UI, database, broker, external dependency, analytics, or paid service.
- No redesign of onboarding, morning, workout, trainer, operator-review, weekly-reporting, or scheduler-card behavior except a shared helper strictly required by nutrition-daily.
- No submitted value, raw text, callback payload, or hash of customer health data in confirmation copy, callback data, binding/projection state, logs, or evidence.
- No edit or deletion of old Telegram/customer messages in the normal accepted-answer path.
- No automatic Telegram retry for any durable send failure or uncertainty; exactly-once delivery must not be claimed.
- No reinterpretation or rewrite of legacy free text, finalized drafts, canonical events, customer registry, or historical evidence.
- No reuse, replay, overwrite, or mutation of r70 candidate, package, preseal, authority, execution, runtime, observer, or other one-use roots.
- No safety-guard bypass, disabled validator, weakened test, customer message, payment/account action, commit, push, PR, or merge.

## Verification strategy
> Zero human intervention - all verification is agent-executed.
- Test decision: TDD with pytest. Each behavioral todo must first run one regression that fails for the named missing contract, then make the smallest production change and rerun the same test green.
- Static checks: LSP diagnostics on every changed Python file, focused `ruff check`, and strict `ty`/basedpyright coverage on new typed modules. Do not suppress errors or run a broad formatter rewrite.
- Domain checks: profile wizard, reporting, projection, correction, unknown, legacy-session, and storage suites.
- Gateway checks: prompt, callback, binding, polling receipt, customer nutrition UX, Telegram adapter, recovery, and launcher round-trip suites.
- Packaging checks: two byte-identical wheel builds, ZIP CRC/RECORD validation, wheel-member/source binding, overlay path-set verification, candidate verification, preseal verification, exact sealed tests, and 13-case rehearsal with `external_events: 0`.
- Manual QA: installed-wheel disposable driver using Telegram-shaped callbacks/text and a synthetic v1 calories-step fixture; verify happy path, unknown, previous, leave/reopen, send failure, restart, stale/replay, and expiry without network/customer writes.
- Production checks: immediate read-only preflight, one exact launcher execution, authority/execution/runtime/Telegram/cron/customer-state verification, manual observer PASS, and a subsequent timer PASS linked to the prior r71 row digest.
- Evidence root outside ulw-loop: `.omo/evidence/nutricoach-telegram-checkin-stepper/`. Inside ulw-loop, use `<attemptDir>/task-<N>-nutricoach-telegram-checkin-stepper.*`.

## Execution strategy
### Fixed workspace bindings
- Implementation workspace root: `/home/cube/projects/richard/.worktrees/nutricoach-v150-combined`
- Plan workspace root: `/home/cube/projects/richard/traning coach`
- Live profile root: `/home/cube/.hermes/profiles/dualcoachtest`
- Migration root: `/home/cube/.hermes/migrations/nutricoach-v1.5.0-combined`
- Every relative source, test, script, and `pyproject.toml` reference in Todos 1-11 resolves against the implementation workspace root above. For example, `gateway/platforms/physique_checkin.py` means `/home/cube/projects/richard/.worktrees/nutricoach-v150-combined/gateway/platforms/physique_checkin.py`.
- Every source/test command must run with working directory `/home/cube/projects/richard/.worktrees/nutricoach-v150-combined` unless the todo names the profile subproject or an outside-worktree sealed-runtime directory explicitly. No command may assume the plan workspace contains product source.
- Reviewers and executors must fail before work if any fixed root differs, is absent, or resolves through an unsafe symlink. They must not search for an alternate checkout.

### Parallel execution waves
> Target 5-8 todos per wave. Fewer than 3 (except the final) means you under-split.
- Wave 1 — independent contracts: Todos 1-3 in parallel.
- Wave 2 — gateway composition: Todo 4, then Todo 5.
- Wave 3 — compatibility and qualification: Todo 6, then Todo 7.
- Wave 4 — immutable package: Todo 8, then Todo 9.
- Wave 5 — production: Todo 10, then Todo 11.
- Final wave — Todos F1-F4 in parallel after Todo 11. All four must approve.

### Dependency matrix
| Todo | Depends on | Blocks | Can parallelize with |
| --- | --- | --- | --- |
| 1 | none | 4, 6, 7 | 2, 3 |
| 2 | none | 4, 5, 6, 7 | 1, 3 |
| 3 | none | 4, 5, 7 | 1, 2 |
| 4 | 1, 2, 3 | 5, 6, 7 | none |
| 5 | 4 | 6, 7 | none |
| 6 | 1-5 | 7, 8 | none |
| 7 | 1-6 | 8 | none |
| 8 | 6, 7 | 9 | none |
| 9 | 8 | 10 | none |
| 10 | 9 | 11 | none |
| 11 | 10 | F1-F4 | none |

## Todos
> Implementation + Test = ONE todo. Never separate.
<!-- APPEND TASK BATCHES BELOW THIS LINE WITH edit/apply_patch - never rewrite the headers above. -->
- [x] 1. Add typed nutrition-daily unknown state and partial event projection
  What to do / Must NOT do: Write failing profile tests first, then add `state_schema_version=2` and `unknown_steps: tuple[str, ...] = ()` to `WizardSession`; implement an explicit `unknown` domain action for every `NUTRITION_STEPS` field except `optional_note`; enforce exactly one of known/unknown/unanswered per field; make known replacement remove the unknown marker and unknown replacement remove the answer; count unknown as completed; preserve/recompute unknown markers through previous navigation, correction, adaptive follow-up invalidation, save, and finalized-session reload. Map unknown to `None`/omitted `ContractCheckin` fields, including all three macro fields. An all-unknown nutrition check-in must create one accepted event without fabricated values. Do not infer unknown from free text and do not change other wizard flows.
  Parallelization: Wave 1 | Blocked by: none | Blocks: 4, 6, 7
  References (executor has NO interview context - be exhaustive): `dualcoach/profile/checkin_cli/wizard_models.py:57-111`; `dualcoach/profile/checkin_cli/wizard.py:188-200,496-570,571-755,804-845,867-1002,1020-1182`; `dualcoach/profile/checkin_cli/models.py:272-294`; `dualcoach/profile/checkin_cli/store.py:1185-1255`; `dualcoach/profile/checkin_cli/customer_reporting.py:109-239`; `dualcoach/profile/tests/test_customer_nutrition_wizard.py`; `dualcoach/profile/tests/test_customer_reporting.py`; `dualcoach/profile/tests/test_wizard_domain.py`
  Acceptance criteria (agent-executable): focused red tests fail before implementation for known/unknown/unanswered, all 11 supported fields, macros fan-out, previous/correction, all-unknown save, missing downstream evidence, and legacy default; after implementation `uv run --directory '/home/cube/projects/richard/.worktrees/nutricoach-v150-combined/dualcoach/profile' pytest -q tests/test_customer_nutrition_wizard.py tests/test_customer_reporting.py tests/test_wizard_domain.py` exits 0; no serialized answer value equals `unknown`, `모름`, or any other sentinel.
  QA scenarios (name the exact tool + invocation): happy — pytest drives known -> unknown -> known and finalizes a partial event; failure — pytest proves unknown calories does not become `0`, low-calorie safety, trend eligibility, or adherence and contradictory known+unknown state is rejected. Evidence `<attemptDir>/task-1-nutricoach-telegram-checkin-stepper.txt`
  Recommended task executor category: `unspecified-high` — multi-file typed domain change with correction and reporting impact.
  Commit: N | User forbids commits.

- [x] 2. Upgrade Telegram binding persistence to content-free projection schema v2
  What to do / Must NOT do: Write failing binding-store tests first, then define typed projection phases and ingress/source/target cursor identities in `physique_checkin_bindings.py`. Read valid v1 and v2, write v2 atomically, default old bindings without rewriting until a real mutation, permit at most one nonterminal transition per session, retain terminal/uncertain records until superseded or expired, fail closed on corruption/conflicting cursors, fsync the file and parent directory, preserve mode 0600/0700 and no-follow/private-file behavior, and never hold the lock during Telegram I/O. Store no answer, raw text, callback payload, or customer-value hash.
  Parallelization: Wave 1 | Blocked by: none | Blocks: 4, 5, 6, 7
  References (executor has NO interview context - be exhaustive): `gateway/platforms/physique_checkin_bindings.py:1-117`; `gateway/platforms/physique_checkin.py:163-301`; `dualcoach/profile/checkin_cli/wizard_storage.py:1-300`; `gateway/platforms/telegram_polling_receipts.py`; `tests/gateway/test_telegram_physique_checkin.py`
  Acceptance criteria (agent-executable): tests cover v1 read, lazy v2 write, every legal phase transition, duplicate ingress identity, nonterminal uniqueness, unsafe path/mode, parent fsync, corruption fail-closed, and no customer data keys; `env PYTHONPATH='/home/cube/projects/richard/.worktrees/nutricoach-v150-combined/dualcoach/profile:/home/cube/projects/richard/.worktrees/nutricoach-v150-combined' uv run --directory '/home/cube/projects/richard/.worktrees/nutricoach-v150-combined' pytest -q tests/gateway/test_telegram_physique_checkin.py -k 'binding or projection or migration'` exits 0.
  QA scenarios (name the exact tool + invocation): happy — load a synthetic v1 active calories binding and atomically persist equivalent v2 after one transition; failure — truncate, duplicate, conflict, or symlink the store and prove no domain callback is invoked. Evidence `<attemptDir>/task-2-nutricoach-telegram-checkin-stepper.txt`
  Recommended task executor category: `unspecified-high` — persistence schema and crash-safety contract span storage and gateway tests.
  Commit: N | User forbids commits.

- [x] 3. Render the two-control confirmation-plus-next-question card
  What to do / Must NOT do: Write prompt tests first, then make progress completed-count based and render one value-free confirmation header followed by the next question. Ordinary nutrition-daily cards expose `잘 모르겠어요` and expose `이전 답 수정` only when history makes it valid; remove `나중에 계속` from this flow only. Preserve content-choice buttons required by sleep/condition/digestion/training questions. `optional_note` keeps its existing `없음`/direct-input choice, has no unknown action, and may expose previous only when valid. First launch/resume without a newly committed answer must not claim a save; known, unknown, edited, invalid, previous, summary, and terminal states must use distinct machine-selected receipt variants without echoing values. Keep all other flows byte-behavior compatible.
  Parallelization: Wave 1 | Blocked by: none | Blocks: 4, 5, 7
  References (executor has NO interview context - be exhaustive): `gateway/platforms/physique_checkin_prompts.py:1-173`; `gateway/platforms/physique_checkin.py:124-132,632-806,861-966`; `tests/gateway/test_customer_nutrition_ux.py:252-520`; `tests/gateway/test_telegram_physique_checkin.py`
  Acceptance criteria (agent-executable): behavior tests assert receipt variant, completed/total, next question, secondary action identifiers, first-question unknown-only behavior, later-question unknown+previous behavior, content-choice preservation, optional-note exception, absence of the `later` action, and no submitted-value echo; tests must not pin prose beyond machine-consumed action IDs, progress fields, and privacy sentinels.
  QA scenarios (name the exact tool + invocation): happy — render first known, unknown, edited, resumed, and final-answer transitions; failure — invalid/stale/launch render produces no false save confirmation and other flows retain current actions. Evidence `<attemptDir>/task-3-nutricoach-telegram-checkin-stepper.txt`
  Recommended task executor category: `quick` — contained renderer/copy contract with nearby behavior tests.
  Commit: N | User forbids commits.

- [x] 4. Implement the durable at-most-once Telegram stepper orchestrator
  What to do / Must NOT do: Write state-machine tests first, then add `gateway/platforms/telegram_physique_checkin_stepper.py` with a narrow transport protocol and typed ingress identity (`update_id`, callback/text kind, incoming message ID, actor/chat/topic). Orchestrate prepared -> domain-committed -> send-started -> one `sendMessage` -> delivered receipt; publish exactly one new content card and never edit/delete the old card. Distinguish proven pre-call failure, definite rejection, and post-call uncertainty, but require explicit Resume for every durable failure outcome. Restart from domain-committed/send-started/unknown-delivery publishes nothing automatically. Mark failure/unknown as terminal for the ingress update so polling can advance without replay loops while keeping the UI projection recoverable. Reconcile a later valid text/callback against the authoritative domain cursor. Final-save publication failure must not roll back or duplicate the canonical event.
  Parallelization: Wave 2 | Blocked by: 1, 2, 3 | Blocks: 5, 6, 7
  References (executor has NO interview context - be exhaustive): new `gateway/platforms/telegram_physique_checkin_stepper.py`; `gateway/platforms/physique_checkin.py:301-426,632-675`; `gateway/platforms/telegram.py:10194-10229,14825-15020`; `gateway/platforms/telegram_polling_receipts.py`; `gateway/platforms/physique_checkin_bindings.py`; architect decisions recorded in `.omo/drafts/nutricoach-telegram-checkin-stepper.md`
  Acceptance criteria (agent-executable): a deterministic transition table covers duplicate before commit, crash after commit, pre-call failure, definite rejection, send started without receipt, delivered receipt, restart, explicit resume, stale callback, typed observation after uncertainty, terminal save, expiry, and corruption; transport call count is exactly 1 for accepted happy path and 0 for every automatic uncertainty recovery.
  QA scenarios (name the exact tool + invocation): happy — fake transport returns one message ID, binding advances once, card count is one; failure — inject each crash boundary and prove no duplicate wizard version, event, prompt send, or receipt. Evidence `<attemptDir>/task-4-nutricoach-telegram-checkin-stepper.txt`
  Recommended task executor category: `deep` — cohesive cross-file state-machine reasoning where splitting would sever the send/commit invariant.
  Commit: N | User forbids commits.

- [x] 5. Route Telegram text, callbacks, polling receipts, and explicit resume through the stepper
  What to do / Must NOT do: Write adapter integration tests first, then thread Telegram `update_id` and incoming message identity into the stepper for nutrition-daily text and `pc1:` callbacks. Keep callback acknowledgement best-effort and non-authoritative. Replace the broad swallowed next-prompt send exception with typed transport outcomes and structured value-free logging. Leaving the chat performs no action; the existing card remains usable until expiry. Make explicit Start/Resume reconstruct and publish the exact authoritative nutrition-daily cursor after expiry, permanent failure, or delivery uncertainty; do not route it to morning check-in. Advance polling receipts after the ingress outcome is durably terminal, including a durably recorded unknown-delivery outcome, so Telegram replay cannot loop. Preserve owner/chat/topic/message/version checks, callback namespace/length, onboarding/coaching routing order, Channel Inbox OFF behavior, and existing completion/coaching handling.
  Parallelization: Wave 2 | Blocked by: 4 | Blocks: 6, 7
  References (executor has NO interview context - be exhaustive): `gateway/platforms/telegram.py:6066-6140,10194-10229,14825-15020`; `gateway/platforms/nutrition_coaching.py`; `gateway/platforms/physique_checkin.py:24-122,301-426`; `gateway/platforms/telegram_polling_receipts.py`; `tests/gateway/test_telegram_physique_checkin.py`; `tests/gateway/test_nutrition_coaching.py`; `tests/gateway/test_new_customer_onboarding_lock.py`
  Acceptance criteria (agent-executable): async tests prove text and callback accepted answers each issue one send, old buttons are stale, duplicate update IDs do not advance the next numeric field, merely leaving/reopening causes no bot action or send, explicit resume targets the exact nutrition session, permanent/uncertain failures are logged without values, unknown-delivery ingress can terminalize the polling receipt without retransmission, and polling completion cannot outrun projection durability.
  QA scenarios (name the exact tool + invocation): happy — Telegram-shaped fake update advances bodyweight -> calories with one new card and a durable message ID; failure — replay the same update, raise before/after fake provider invocation, restart, and use wrong actor/topic/message; assert zero duplicate sends/mutations and no generic-agent ingestion. Evidence `<attemptDir>/task-5-nutricoach-telegram-checkin-stepper.txt`
  Recommended task executor category: `unspecified-high` — adapter, polling, routing, and async integration cross several modules.
  Commit: N | User forbids commits.

- [x] 6. Prove legacy and live-shape compatibility with installed runtime artifacts
  What to do / Must NOT do: Write a sanitized synthetic fixture matching the live r70 semantic shape (nutrition-daily, calories, version 1, one known bodyweight, bound message 101, future and expired variants) without copying real customer values. Prove legacy `unknown_steps=()` and binding v1 load without rewrite; one known or unknown calories transition advances exactly once to macros/version 2/progress 2/12; replay cannot advance again; deployment/startup creates no event or replacement draft; expired legacy state sends nothing automatically. Update `scripts/nutricoach_v150_customer_ux_smoke.py` to verify an explicitly supplied expected installed-runtime identity instead of its stale hardcoded historical runtime, and run it against disposable installed wheels with network/message counters fixed at zero.
  Parallelization: Wave 3 | Blocked by: 1-5 | Blocks: 7, 8
  References (executor has NO interview context - be exhaustive): production semantic shape recorded in `.omo/drafts/nutricoach-telegram-checkin-stepper.md`; `scripts/nutricoach_v150_customer_ux_smoke.py`; `dualcoach/profile/checkin_cli/wizard_storage.py`; `gateway/platforms/physique_checkin_bindings.py`; `tests/gateway/test_customer_nutrition_ux.py`; `tests/gateway/test_telegram_physique_checkin.py`
  Acceptance criteria (agent-executable): source-mode and installed-wheel drivers both print a machine-readable PASS with `network_events=0`, `telegram_sends=0`, preserved v1 identity, and exact one-step advance; file digests prove no mutation during startup-only and expired cases.
  QA scenarios (name the exact tool + invocation): happy — future v1 fixture resumes and accepts known then unknown in separate runs; failure — expired, corrupt, contradictory, stale-version, duplicate-update, and mismatched-candidate fixtures fail closed without writes or sends. Evidence `<attemptDir>/task-6-nutricoach-telegram-checkin-stepper.json`
  Recommended task executor category: `unspecified-high` — compatibility spans source, installed wheel, persistence, and production-shaped fixtures.
  Commit: N | User forbids commits.

- [x] 7. Run complete affected regressions, strict diagnostics, and independent safety/UX audits
  What to do / Must NOT do: Run full profile tests and all affected root gateway/customer suites once; fix only failures caused by this change. Run LSP diagnostics on every changed file, focused Ruff, strict `ty`/basedpyright on new typed modules, and compile/import checks from outside the source tree. Run independent read-only audits for privacy/security, state-machine/code quality, and Korean customer UX. Auditors must verify actual files/wheels and return PASS/FAIL with blocker count; summaries alone are claims. Remove every temporary QA/debug artifact. Do not suppress warnings/errors, weaken tests, or broaden cleanup.
  Parallelization: Wave 3 | Blocked by: 1-6 | Blocks: 8
  References (executor has NO interview context - be exhaustive): `dualcoach/profile/tests/`; `tests/gateway/test_customer_nutrition_ux.py`; `tests/gateway/test_telegram_physique_checkin.py`; `tests/gateway/test_nutrition_coaching.py`; `tests/gateway/test_new_customer_onboarding_lock.py`; project `pyproject.toml`; `.agents/skills/programming/` rules inherited from project instructions
  Acceptance criteria (agent-executable): all invoked suites exit 0; changed-file diagnostics report zero errors; independent audits each report PASS/blocker_count 0; no temp script, debug journal, customer value, or unrelated diff remains.
  QA scenarios (name the exact tool + invocation): happy — full source suites and three independent audits pass; failure — privacy scan injects a sentinel answer and proves it appears nowhere in prompt/callback/log/binding/evidence, while transport fault tests remain deterministic. Evidence `<attemptDir>/task-7-nutricoach-telegram-checkin-stepper.json`
  Recommended task executor category: `deep` — broad verification and independent evidence synthesis across domain, gateway, and privacy.
  Commit: N | User forbids commits.

- [x] 8. Build reproducible r71 wheels and seal a verified candidate
  What to do / Must NOT do: Immediately prove all r71 candidate/build/input roots absent. Advance every generation-bound sealing/controller/closure reference consistently from r70 to r71. Add every new/changed wheel-bound source path to the successor overlay and `_SUCCESSOR_OVERLAY_PATHS` before sealing. Build Hermes and profile wheels twice in isolated evidence roots, require byte-identical pair hashes, validate ZIP CRC/RECORD counts and exact source binding, create a truthful authority-verification receipt from Todo 7 evidence, seal `task-v15r71-candidate`, and run the candidate verifier. Never use ambient build output, r70/r69 authority, stale wheels, or a failed candidate.
  Parallelization: Wave 4 | Blocked by: 6, 7 | Blocks: 9
  References (executor has NO interview context - be exhaustive): `scripts/seal_nutricoach_v150_candidate.py`; `scripts/verify_nutricoach_v150_candidate.py`; `scripts/verify_nutricoach_v150_candidate_inputs.py`; `scripts/prepare_nutricoach_v150_v15_preseal.py`; `scripts/rehearse_nutricoach_v150_v15.py`; `scripts/nutricoach_v150_detached_bootstrap.py`; `scripts/verify_nutricoach_v150_preseal_v15.py`; `scripts/nutricoach_v150_sealed_controller.py`; `scripts/execute_nutricoach_v150_sealed_live.py`; `tests/test_nutricoach_v150_v15_closure.py`; immutable r70 evidence only as read-only format reference
  Acceptance criteria (agent-executable): both wheel pairs have equal SHA-256 and valid archives/RECORD; all overlay members match current source in both builds; closure tests pass with r71 literals; candidate verifier prints `NUTRICOACH_V150_CANDIDATE_PASS`; evidence records candidate/package/wheel/source digests and exact test/audit counts.
  QA scenarios (name the exact tool + invocation): happy — two builds seal and verify one immutable r71 candidate; failure — mutate one wheel member or remove one new overlay path and prove verifier rejects before any preseal/authority root is created. Evidence `<attemptDir>/task-8-nutricoach-telegram-checkin-stepper.json`
  Recommended task executor category: `deep` — immutable artifact identity and source/wheel closure require cross-module reasoning.
  Commit: N | User forbids commits.

- [x] 9. Verify r71 preseal, exact sealed tests, rehearsal, and installed-wheel closure
  What to do / Must NOT do: Prove r71 preseal/preflight/rehearsal roots absent, prepare and verify the one-use preseal, run the exact sealed controller/adversarial/safety suite from outside the worktree, and run the 13-case disposable rehearsal. Require `external_events: 0` and live source/production unchanged. Run the installed-wheel stepper QA against the sealed candidate, then independent closure/integrity/privacy audits against the exact r71 artifacts. Never consume live authority or send Telegram updates during rehearsal.
  Parallelization: Wave 4 | Blocked by: 8 | Blocks: 10
  References (executor has NO interview context - be exhaustive): `scripts/prepare_nutricoach_v150_v15_preseal.py`; `scripts/verify_nutricoach_v150_preseal_v15.py`; `scripts/nutricoach_v150_sealed_controller.py`; `scripts/rehearse_nutricoach_v150_v15.py`; `tests/test_nutricoach_v150_sealed_controller.py`; `tests/test_nutricoach_v150_v15_authority_adversarial.py`; `tests/test_nutricoach_v150_v15_safety.py`; r70 preseal/rehearsal only as immutable format reference
  Acceptance criteria (agent-executable): preseal verifier PASS; exact sealed suite exits 0; rehearsal reports 13 PASS cases and `external_events=0`; installed-wheel driver covers known/unknown/previous/leave-resume/send-failure/restart/stale/expiry; all final audits PASS/blocker_count 0.
  QA scenarios (name the exact tool + invocation): happy — exact package passes every sealed and installed surface; failure — fault matrix proves rollback for each phase and ambiguous Telegram send produces no automatic second call. Evidence `<attemptDir>/task-9-nutricoach-telegram-checkin-stepper.json`
  Recommended task executor category: `deep` — sealed runtime, fault matrix, and installed behavior form one qualification boundary.
  Commit: N | User forbids commits.

- [x] 10. Execute the exact r71 one-use production upgrade and verify preserved customer state
  What to do / Must NOT do: Immediately before live action, perform one read-only preflight: r71 authorization/execution/runtime roots absent; r70 authority consumed/succeeded and execution committed; gateway active/running with zero restarts and Telegram sockets; current candidate r70; Channel Inbox OFF; capacity five; cron/weekly healthy; prepared first-claim invite unchanged; customer projection unchanged; observer-r70 latest PASS; and current nutrition draft/binding/event digests recorded. If the draft is still valid, require exact-step r71 compatibility; if expired, require no automatic send and preserve bytes. Execute the immutable r71 launcher exactly once with its exact package approval phrase. Verify r71 authorization `CONSUMED/SUCCEEDED`, execution `COMMITTED`, current candidate/runtime, gateway active/running/NRestarts=0, Telegram sockets, cron latest success, capacity, inbox, invite, customer projection, and semantic draft/event preservation. Rollback is a safety response only, never success.
  Approved recovery amendment (2026-09-01): Preserve every existing r71 candidate/preseal as an immutable tombstone and use one fresh `r71b-maintenance` authority namespace while product generation remains r71 and observer output remains `observer-r71`. The sealed live transaction may authenticate and consume one candidate/config/route/customer/day-bound Topic-59 maintenance hold before publication claim, writing a separate truthful terminal same-day skip audit. It must produce cron success with provider/network/customer sends and updates equal to zero, never fabricate delivery state or rewrite historical uncertain slots, release automatically on the next KST day, and pass fresh candidate/preseal/rehearsal/installed/audit gates before live action.
  Parallelization: Wave 5 | Blocked by: 9 | Blocks: 11
  References (executor has NO interview context - be exhaustive): `scripts/execute_nutricoach_v150_sealed_live.py`; r71 sealed target/package manifest/approval phrase from Todo 9; live r70 paths recorded in `.omo/drafts/nutricoach-telegram-checkin-stepper.md`; `/home/cube/.config/systemd/user/hermes-gateway-dualcoachtest.service`; `/home/cube/.hermes/profiles/dualcoachtest/`
  Acceptance criteria (agent-executable): exact launcher exits 0 once; live receipts and registry bind r71; no replay occurs; gateway and Telegram are healthy; all preserved-state assertions pass; journal has no startup traceback; no agent-originated customer message or update exists.
  QA scenarios (name the exact tool + invocation): happy — r71 commits and real service loads the exact wheels/state; failure — any preflight drift, occupied root, candidate mismatch, invalid projection, or unhealthy service prevents launcher execution, while transactional faults restore safe r70 state without claiming completion. Evidence `<attemptDir>/task-10-nutricoach-telegram-checkin-stepper.json`
  Recommended task executor category: `deep` — external one-use production transaction with strict state and rollback fences.
  Commit: N | User forbids commits.

- [ ] 11. Rebind the seven-day observer to r71 and prove a timer-linked PASS
  What to do / Must NOT do: Update observer source bindings for r71 candidate/runtime/execution/authorization/output, create a fresh private `observer-r71` chain, update the systemd service runtime, writable path, and checksum manifest, daemon-reload, and run one manual observation. Preserve observer-r70 evidence unchanged. Require manual PASS followed by the next timer PASS whose `previous_row_digest` equals the manual row digest; verify timer remains active through the existing seven-day window and finalization contract.
  Parallelization: Wave 5 | Blocked by: 10 | Blocks: F1-F4
  References (executor has NO interview context - be exhaustive): `scripts/nutricoach_v150_observer_contract.py`; `scripts/nutricoach_v150_observer_runtime.py`; `scripts/nutricoach_v150_observer_final.py`; `scripts/nutricoach_v150_seven_day_observer.py`; `tests/test_nutricoach_v150_seven_day_observer.py`; `/home/cube/.config/systemd/user/nutricoach-v15-seven-day-observer.service`; `/home/cube/.config/systemd/user/nutricoach-v15-seven-day-observer.timer`; `/home/cube/.config/systemd/user/nutricoach-v15-seven-day-observer.sha256`
  Acceptance criteria (agent-executable): observer tests exit 0; checksum check passes; manual and timer rows both PASS with candidate/committed/active/capacity/inbox/journal/restarts/Telegram/weekly invariants true; hash chain links; timer next trigger is scheduled; r70 rows unchanged.
  QA scenarios (name the exact tool + invocation): happy — manual plus timer r71 observations form a valid two-row chain; failure — stale candidate/runtime/checksum or broken predecessor digest makes the service fail and cannot append a false PASS. Evidence `<attemptDir>/task-11-nutricoach-telegram-checkin-stepper.json`
  Recommended task executor category: `unspecified-high` — systemd, checksums, hash-chain persistence, and live invariants cross operational surfaces.
  Commit: N | User forbids commits.

## Final verification wave
> Runs in parallel after ALL todos. ALL must APPROVE. Surface results and wait for the user's explicit okay before declaring complete.
> Exact invocation contract: the lead must spawn one native `task` batch with four fresh children in parallel. Each item uses the category and exact English prompt below, sets `run_in_background=true`, and supplies the shown `task_summary`. After completion, the lead validates each terminal envelope and persists its returned JSON to the stated evidence path with `apply_patch`. A result passes only when it parses as `{"verdict":"APPROVE","blocker_count":0,"evidence":[...]}` with at least one independently observed evidence item; missing, malformed, self-report-only, or stale-generation output is REJECT.
- [ ] F1. Plan compliance audit
  Verify every Must have/Must NOT have against changed files, tests, r71 immutable artifacts, production receipts, and observer evidence. Reject self-report or stale generation evidence. Evidence `<attemptDir>/final-F1-plan-compliance.json`
  Tool/invocation: `task(category="unspecified-high", run_in_background=true, task_summary="Audit plan compliance against r71 evidence", prompt="TASK: Act as an independent plan-compliance verifier. DELIVERABLE: Return only JSON with verdict APPROVE or REJECT, blocker_count, and independently observed evidence. SCOPE: Read /home/cube/projects/richard/traning coach/.omo/plans/nutricoach-telegram-checkin-stepper.md, implementation workspace /home/cube/projects/richard/.worktrees/nutricoach-v150-combined, the current attempt evidence, exact r71 immutable artifacts, production receipts, and observer-r71. VERIFY: Check every Must have, Must NOT have, Todo acceptance criterion, generation binding, and customer-message prohibition against files and command receipts; reject summaries or stale r70 evidence. STOP WHEN: every plan requirement has a concrete evidence disposition.")`
  Steps / expected result: independently open the fixed plan/source/evidence/live roots, sample every todo receipt, verify r71 identities and zero forbidden actions, then return `verdict=APPROVE`, `blocker_count=0`. Any missing requirement or unverifiable receipt returns REJECT.
  Recommended task executor category: `unspecified-high`
- [ ] F2. Code quality review
  Review typed state exhaustiveness, storage atomicity/no-follow/privacy, lock/I/O separation, Telegram failure semantics, callback/update dedupe, legacy compatibility, and changed-file diagnostics. Evidence `<attemptDir>/final-F2-code-quality.json`
  Tool/invocation: `task(category="deep", run_in_background=true, task_summary="Review stepper code quality and safety", prompt="TASK: Act as an independent code-quality and safety verifier. DELIVERABLE: Return only JSON with verdict APPROVE or REJECT, blocker_count, and independently observed evidence. SCOPE: Review changed files under /home/cube/projects/richard/.worktrees/nutricoach-v150-combined plus current-attempt test, LSP, Ruff, type-check, privacy, and wheel-binding evidence. VERIFY: Inspect typed variant exhaustiveness, known/unknown/unanswered invariants, storage atomicity/no-follow/modes/fsync, lock-versus-I/O separation, at-most-once transport semantics, polling terminalization, replay protection, legacy compatibility, and absence of customer values in non-domain state. Run targeted diagnostics/tests where evidence is insufficient. STOP WHEN: all quality and safety contracts have concrete pass/fail evidence.")`
  Steps / expected result: inspect actual changed source and installed wheel members, rerun targeted checks as needed, then return `verdict=APPROVE`, `blocker_count=0`. Any suppression, privacy leak, unsafe state transition, or unverifiable wheel/source binding returns REJECT.
  Recommended task executor category: `deep`
- [ ] F3. Real manual QA
  Independently execute the sealed installed-wheel Telegram-shaped driver and read-only production surfaces. Observe one-card transitions, unknown plus valid-history-only previous controls, durable leave plus explicit resume, no value echo, no network in disposable QA, healthy live gateway, and r71 observer chain. Evidence `<attemptDir>/final-F3-manual-qa.json`
  Tool/invocation: `task(category="unspecified-high", run_in_background=true, task_summary="Run independent sealed and live manual QA", prompt="TASK: Act as an independent hands-on QA verifier. DELIVERABLE: Return only JSON with verdict APPROVE or REJECT, blocker_count, and independently observed evidence. SCOPE: Use the exact sealed r71 wheels and driver command recorded by Todo 9, plus read-only production surfaces under /home/cube/.hermes/profiles/dualcoachtest and /home/cube/.hermes/migrations/nutricoach-v1.5.0-combined. VERIFY: Personally execute the installed-wheel Telegram-shaped happy path, one bad input, unknown, previous, leave-without-action, explicit resume, stale/replay, send failure, restart, expiry, and help/identity checks; require network_events=0 and telegram_sends=0 in disposable QA. Then verify live r71 gateway, Telegram sockets, cron, preserved customer state, and the manual-plus-timer observer-r71 hash chain read-only. Never send a real Telegram update. STOP WHEN: observable sealed behavior and live read-only invariants are all proven or one blocker is found.")`
  Steps / expected result: execute the exact recorded sealed driver outside the source tree, inspect its real output and side effects, run read-only systemd/receipt/observer assertions, then return `verdict=APPROVE`, `blocker_count=0`. Any real send, missing scenario, unhealthy live surface, or non-linked observer row returns REJECT.
  Recommended task executor category: `unspecified-high`
- [ ] F4. Scope fidelity
  Compare final source/artifacts/live state to the original request and guardrails. Reject unrelated flow redesign, new dependencies, customer sends, r70 mutation/replay, commits, or missing current-session protection. Evidence `<attemptDir>/final-F4-scope-fidelity.json`
  Tool/invocation: `task(category="unspecified-high", run_in_background=true, task_summary="Verify final scope fidelity and exclusions", prompt="TASK: Act as an independent scope-fidelity verifier. DELIVERABLE: Return only JSON with verdict APPROVE or REJECT, blocker_count, and independently observed evidence. SCOPE: Compare /home/cube/projects/richard/traning coach/.omo/plans/nutricoach-telegram-checkin-stepper.md with the final changed-file inventory, dependency manifests, r71 evidence, live receipts, and preserved r70 artifacts. VERIFY: Prove only nutrition_daily changed except strictly shared helpers; no web app, external dependency, analytics, customer-value echo, customer message, r70 replay/mutation, guard bypass, weakened test, debug artifact, commit, push, PR, or merge occurred; prove the legacy/current-session contract remains covered. STOP WHEN: every Scope IN/OUT item is independently classified with evidence.")`
  Steps / expected result: inspect changed-file and dependency inventories, compare immutable r70/r71 roots and git-operation evidence, then return `verdict=APPROVE`, `blocker_count=0`. Any unrelated behavior, forbidden side effect, or missing scope proof returns REJECT.
  Recommended task executor category: `unspecified-high`

## Commit strategy
- No commits. The user explicitly forbids commit, push, PR, merge, and unrelated git operations.
- The executor must preserve unrelated shared-worktree changes and produce file/evidence inventories instead of commits.
- Fresh immutable NutriCoach package generations are operational artifacts, not git commits.

## Success criteria
- Every accepted nutrition-daily answer produces one new value-free card with truthful completed progress and the next question.
- Ordinary cards expose `잘 모르겠어요` and valid-history-only `이전 답 수정` as the only secondary controls; no `나중에 계속` button appears.
- Leaving preserves the exact durable cursor without a bot action; the visible card remains usable until expiry, explicit launcher/Resume restores it afterward, and stale/duplicate updates cannot advance twice.
- Unknown is explicit in private session state and missing in canonical metrics, never inferred or fabricated.
- Every durable Telegram send failure or uncertainty produces zero automatic retry and supports explicit safe resume.
- Valid v1 state loads without semantic drift; the live r70 calories-step draft is preserved or safely expired without an automatic send.
- Full affected tests, diagnostics, independent audits, installed-wheel QA, candidate/preseal verification, sealed tests, and 13-case rehearsal are clean.
- Exactly one fresh r71 authority is consumed successfully; production runs the r71 candidate with zero restarts and healthy Telegram/cron/capacity/inbox/customer state.
- Observer-r71 produces a manual PASS and a subsequent timer PASS linked by hash.
- No actual customer message, customer-value evidence, external rehearsal event, r70 replay/mutation, safety bypass, test weakening, debug artifact, commit, push, PR, or merge occurs.
