{"task_id":"st_01a02505","status":"completed","residency_state":"evicted","parent_session_id":"01a00387-aaf8-7f2f-89e3-e24c1af24859","root_session_id":"01a00387-aaf8-7f2f-89e3-e24c1af24859","depth":1,"execution_mode":"in-process","model":"openai-codex/gpt-5.6-sol","notify_on_terminal":true,"created_at":"2026-08-21T15:52:06.991Z","updated_at":"2026-08-22T05:00:10.428Z","notification":{"run_epoch":0,"notified_epoch":0},"name":"st_01a02505","task_summary":"Design v1.1.1 implementation wave plan","description":"stability architecture plan","category":"architect","requested_model":{"provider":"openai-codex","model_id":"gpt-5.6-sol","display":"openai-codex/gpt-5.6-sol","source":"category","variant":"xhigh","reasoning_effort":"xhigh"},"fallback_models":[{"provider":"clinepass","model_id":"cline-pass/glm-5.2","display":"clinepass/cline-pass/glm-5.2","source":"category","variant":"xhigh","reasoning_effort":"medium"},{"provider":"openai-codex","model_id":"gpt-5.6-terra","display":"openai-codex/gpt-5.6-terra","source":"category","variant":"xhigh","reasoning_effort":"xhigh"}],"resolved_model":{"provider":"openai-codex","model_id":"gpt-5.6-sol","display":"GPT-5.6 Sol","source":"category","variant":"xhigh","reasoning_effort":"xhigh"},"spawn_spec":{"version":1,"cwd":"/home/cube/projects/richard/traning coach","prompt":"TASK: Produce a decision-complete implementation wave plan for NutriCoach v1.1.1 stability in a new isolated worktree from /home/cube/projects/richard/hermes-agent-dualcoach-v1.1. This is an executable planning assignment, not a context handoff. DELIVERABLE: ordered file-level RED→GREEN increments, dependency order, disjoint parallel edit lanes, exact tests/commands, E2E harness design, migration dry-run design, and production immutability/cleanup gates. SCOPE/FACTS: (1) transaction files are gateway/platforms/telegram.py, telegram_customer_bootstrap.py, telegram_customer_bootstrap_registration.py, telegram_nutrition_onboarding_runtime*.py, telegram_polling_receipts.py, dualcoach/profile/checkin_cli/nutrition_onboarding*.py; missing crash windows include consent committed before onboarding session, send before receipt, failed ingress receipt, callback replay. (2) preflight files: hermes_cli/env_loader.py loads managed→user→project with override True; gateway/run.py reloads env; telegram.py locks token and creates bot but does not bind expected identity, admin rights only checked for inventory chats, topics not checked; menu startup registers generic commands. Profile package resolution defaults from DUALCOACH_PROFILE_PACKAGE or profile/workspace and collides with pre-imported installed package. (3) Existing broad tests are valuable but not a faithful full E2E; prior 59 failures need classification. Build a dedicated local fake Bot API transport preserving getMe/getChat/getChatMember/getUpdates/sendMessage/editMessageText/callback IDs, offsets, crash/restart. (4) Mandatory goal criteria and commands are registered in the active goal: three named RED/GREEN test modules, scripts/run_dualcoach_v111_disposable_e2e.py, full three-suite green, migration dry-run, production hashes/PID unchanged. (5) Absolutely no production mutation, Telegram calls, commit/push/tag/release. (6) HEAVY tier, no ulw-plan reviewer gate, self-review only. VERIFY: plan must identify smallest coherent production changes, avoid overlapping parallel writes, list exact RED evidence before each production edit, and map every active-goal criterion to a wave and evidence artifact. STOP WHEN: the plan can be executed without further architecture decisions or user questions.\n\n<Category_Context>\nYou are a big-picture system design consultant, NOT an implementer.\n\n<Method>\n1. Survey the WHOLE architecture end to end before proposing anything: module boundaries, data flow, ownership, and the blast radius of the area in question.\n2. Produce at least TWO viable designs and state the trade-offs of each in concrete terms (coupling, testability, migration cost, failure modes).\n3. Recommend ONE of them with the reasoning that decided it, the boundaries it assumes, and the risks it carries.\n4. Demand the SIMPLEST architecture that handles the actual requirements. Reject enterprise patterns that do not pay for themselves.\n</Method>\n\n<Advisory_Mode>\nWhen the caller marks the task advisory-only, produce NO file edits: return the analysis, options, recommendation, and risks as structured text.\n</Advisory_Mode>\n</Category_Context>"},"host_pid":4111719,"final_response":"I read this as a request for the final advisory implementation wave plan - I will give the ordered RED-to-GREEN sequence, ownership topology, commands, E2E design, and deployment-safety gates without editing files.\n\n# Decision\n\nImplement a recovery saga using the existing bootstrap ledger, customer registry, onboarding store, publication outbox, and polling receipt gate.\n\nReject a new unified transaction journal: it would require broader schema migration, duplicate existing authorities, and materially increase rollback risk.\n\nFor profile imports, use a single fail-closed resolver. Never evict or replace existing `checkin_cli` modules in `sys.modules`; reject startup if already-loaded modules do not come from the sealed candidate root.\n\n## Delivery boundary\n\nTelegram has no idempotency key for `sendMessage`. Therefore:\n\n- Known-message `editMessageText` can be retried safely.\n- An ambiguous unanchored `sendMessage` must become durable `UNKNOWN` and must not be retried automatically.\n- Business-commit-before-send crashes can recover and publish exactly once.\n- Tests must not claim a recoverable provider receipt when Telegram accepted an unanchored send but the process lost the response.\n\n---\n\n# 1. Worktree and evidence setup\n\n```bash\nset -euo pipefail\numask 077\n\nBASE=ccd2eee8a97a13d25c36c1446ed5b5aca2e88653\nSRC=/home/cube/projects/richard/hermes-agent-dualcoach-v1.1\nWT=/home/cube/projects/richard/.worktrees/nutricoach-v111-impl\nEV=\"$WT/.omo/evidence/v1.1.1-stability\"\n\ntest -z \"$(git -C \"$SRC\" status --porcelain)\"\ntest ! -e \"$WT\"\ngit -C \"$SRC\" worktree add --detach \"$WT\" \"$BASE\"\n\ncd \"$WT\"\nmkdir -p \"$EV\"/{red,green,baseline,regression,typecheck,build,migration,production-seal}\nuv venv --python 3.12 .venv\nuv sync --frozen --extra dev --extra messaging\nuv pip install --python .venv/bin/python --no-deps -e dualcoach/profile\n\ntest \"$(git rev-parse HEAD)\" = \"$BASE\"\ntest -z \"$(git status --porcelain)\"\n```\n\nNo commit, push, tag, release, production restart, real Telegram request, or deployed-file edit is permitted.\n\nUse:\n\n```bash\nrun_red() {\n  out=$1; shift\n  set +e\n  \"$@\" 2>&1 | tee \"$out\"\n  rc=${PIPESTATUS[0]}\n  set -e\n  printf '\\nEXIT_CODE=%s\\n' \"$rc\" >>\"$out\"\n  test \"$rc\" -eq 1\n}\n\nrun_green() {\n  out=$1; shift\n  set +e\n  \"$@\" 2>&1 | tee \"$out\"\n  rc=${PIPESTATUS[0]}\n  set -e\n  printf '\\nEXIT_CODE=%s\\n' \"$rc\" >>\"$out\"\n  test \"$rc\" -eq 0\n}\n```\n\nRED exit codes `2`, `3`, `4`, or `5` are invalid because they indicate collection, setup, or selection failure rather than behavioral RED.\n\n---\n\n# 2. Dependency order\n\n```text\nW0 isolation, baseline seal, mandatory RED\n |\n +--> W1-A package resolver and consent ledger\n |\n +--> W1-B token/preflight/menu\n |\n +--> W1-C ingress receipts and publication outbox\n |\n `--> W1-E fake API foundation\n\nW1-A + W1-B + W1-C\n |\n v\nW2 adapter/runtime integration, exclusively owned\n |\n v\nW3 mandatory GREEN\n |\n +--> W4-A disposable installed-wheel E2E\n |\n `--> W4-B migration planner/copied-profile migrator\n\nW4-A + W4-B\n |\n v\nW5 prior-failure resolution and regressions\n |\n v\nW6 strict typing, reproducible builds, independent verifier\n |\n v\nW7 production immutability comparison and cleanup\n```\n\n---\n\n# 3. Disjoint delegation topology\n\n| Lane | Exclusive write ownership |\n|---|---|\n| A: consent/package | `tests/gateway/test_telegram_nutrition_onboarding_recovery.py`, `gateway/platforms/dualcoach_profile_package.py`, `telegram_customer_bootstrap.py`, `telegram_customer_bootstrap_registration.py`, necessary `dualcoach/profile/checkin_cli/nutrition_onboarding*.py` |\n| B: preflight | `tests/gateway/test_telegram_production_preflight.py`, `hermes_cli/env_loader.py`, `gateway/run.py`, `gateway/config.py`, `nutrition_coaching_config.py`, `telegram_staff_membership_gate.py`, new preflight/menu modules, `dualcoach/profile/pyproject.toml` |\n| C: publication | `tests/gateway/test_telegram_publication_recovery.py`, `telegram_polling_receipts.py`, publication outbox, runtime publication and transport modules |\n| D: integration | `gateway/platforms/telegram.py`, `telegram_nutrition_onboarding_runtime.py`, runtime callback/collection wiring |\n| E: E2E | `tests/support/fake_telegram_bot_api.py`, fake API tests, E2E driver and child process wrapper |\n| F: migration/artifacts | migration planner, copied-profile migrator, migration tests, candidate verifier |\n\nRules:\n\n- Only lane D edits `telegram.py`.\n- Lane D starts after A, B, and C freeze their interfaces.\n- No simultaneous lane may edit another lane’s file.\n- Cross-lane change requests are queued for the owning lane.\n\n---\n\n# 4. Wave W0: mandatory behavioral RED\n\nCreate the three mandatory test modules before any production edit.\n\n```bash\nrun_red \"$EV/red-consent-recovery.txt\" \\\n  .venv/bin/python -m pytest -q \\\n  tests/gateway/test_telegram_nutrition_onboarding_recovery.py\n\nrun_red \"$EV/red-production-preflight.txt\" \\\n  .venv/bin/python -m pytest -q \\\n  tests/gateway/test_telegram_production_preflight.py\n\nrun_red \"$EV/red-publication-recovery.txt\" \\\n  .venv/bin/python -m pytest -q \\\n  tests/gateway/test_telegram_publication_recovery.py\n\ngit diff --name-only \"$BASE\" -- \\\n  gateway hermes_cli dualcoach/profile scripts pyproject.toml \\\n  >\"$EV/red/production-files-before-first-edit.txt\"\n\ntest ! -s \"$EV/red/production-files-before-first-edit.txt\"\n```\n\nRequired RED cases:\n\n### Consent recovery\n\n```text\ntest_committed_consent_recovers_missing_session_after_restart_once\ntest_concurrent_consent_replay_does_not_duplicate_first_question\ntest_consent_recovery_preserves_original_event_provenance\ntest_consent_recovery_rejects_mismatched_actor_route_or_generation\ntest_consent_business_commit_heals_failed_ingress_receipt\ntest_wrong_preimported_profile_package_fails_before_mutation\n```\n\n### Production preflight\n\n```text\ntest_service_token_survives_startup_and_runtime_dotenv_reload\ntest_getme_id_mismatch_refuses_connect\ntest_getme_username_mismatch_refuses_connect\ntest_non_admin_bot_refuses_connect\ntest_invalid_duplicate_or_unreachable_topic_refuses_connect\ntest_candidate_package_identity_mismatch_refuses_connect\ntest_customer_command_scope_is_exact\ntest_non_loopback_test_api_override_refuses_connect\n```\n\n### Publication recovery\n\n```text\ntest_first_question_edit_recovers_after_response_before_receipt\ntest_unanchored_send_becomes_unknown_without_duplicate_retry\ntest_customer_completion_restart_emits_one_owner_card\ntest_owner_approval_restart_emits_one_operator_delivery\ntest_concurrent_replay_converges_on_one_publication_receipt\ntest_recovered_business_commit_advances_offset_once\n```\n\nTests must use current public behavior. A missing future import is not acceptable RED.\n\n---\n\n# 5. Wave W1-A: profile package and consent saga\n\n## Increment A1: sealed package resolver\n\nAdd:\n\n```text\ngateway/platforms/dualcoach_profile_package.py\n```\n\nModify:\n\n```text\ngateway/platforms/telegram_customer_bootstrap_registration.py\ngateway/platforms/telegram_nutrition_onboarding_runtime.py\n```\n\nContract:\n\n1. `DUALCOACH_PROFILE_PACKAGE` is required in production nutrition mode.\n2. It names an absolute distribution root containing `checkin_cli/`.\n3. Reject symlinks and path escape.\n4. Validate the root through Task26 candidate authority.\n5. If any `checkin_cli` module is already imported, every such module must resolve under the candidate root.\n6. Wrong-source imports abort before registry, workflow, or network mutation.\n7. Never delete or replace entries in `sys.modules`.\n8. Remove fallback selection from `profile/workspace`.\n\nRun focused RED/GREEN around package tests before proceeding.\n\n## Increment A2: durable consent handoff\n\nModify:\n\n```text\ngateway/platforms/telegram_customer_bootstrap.py\ngateway/platforms/telegram_customer_bootstrap_registration.py\ndualcoach/profile/checkin_cli/nutrition_onboarding.py\ndualcoach/profile/checkin_cli/nutrition_onboarding_store.py  # only if required\n```\n\nAdd a backward-readable bootstrap schema containing nullable `ConsentHandoff`:\n\n```text\nsession_id\nbootstrap_generation\ncustomer_key\nupdate_id\ncallback_query_id\ncallback_data_digest\nactor_user_id\nchat_id\ntopic_id\nmessage_id\nconsent_card_message_id\nevent_time_utc\nregistry_authority_digest\nhandoff_digest\nonboarding_session_digest\n```\n\nTransaction sequence:\n\n1. Authenticate callback route, actor, signature and generation.\n2. CAS-persist `ConsentHandoff` while state remains `AWAITING_CONSENT`.\n3. Commit registry consent idempotently.\n4. Start/resume onboarding from persisted evidence.\n5. Verify customer and authority bindings.\n6. CAS record the onboarding session digest.\n7. Transition to `AWAITING_ACTIVATION`.\n8. Reconcile first-question publication.\n9. Reconcile the ingress receipt.\n\nStartup recovery scans:\n\n- `AWAITING_CONSENT` with a valid handoff;\n- `AWAITING_ACTIVATION` with incomplete publication;\n- collecting sessions with pending outbox work.\n\nA legacy missing-session state without authenticated handoff evidence must fail closed. It must not infer provenance from registry consent alone.\n\n---\n\n# 6. Wave W1-B: token, identity, topics, package, menu\n\n## Increment B1: token authority\n\nModify:\n\n```text\nhermes_cli/env_loader.py\ngateway/run.py\ntests/gateway/test_telegram_production_preflight.py\n```\n\nAt process entry, snapshot a non-empty service-provided `TELEGRAM_BOT_TOKEN`. Extend `load_hermes_dotenv` with an explicit protected-value mapping and restore protected values after:\n\n- user dotenv;\n- project dotenv;\n- external secret sources;\n- managed environment;\n- runtime reload.\n\nDo not globally alter precedence for unrelated credentials.\n\n## Increment B2: identity and route preflight\n\nAdd:\n\n```text\ngateway/platforms/telegram_production_preflight.py\ngateway/platforms/telegram_nutrition_menu.py\n```\n\nModify:\n\n```text\ngateway/config.py\ngateway/platforms/nutrition_coaching_config.py\ngateway/platforms/telegram_staff_membership_gate.py\n```\n\nRequired configuration:\n\n```yaml\nplatforms:\n  telegram:\n    extra:\n      production_preflight:\n        expected_bot_id: <positive integer>\n        expected_bot_username: <username without @>\n```\n\nBefore polling or customer delivery:\n\n1. `getMe` must match both expected fields.\n2. Build canonical owner/customer/operator route triples.\n3. Require pairwise-distinct route triples.\n4. `getChat` must confirm expected private/forum chat type.\n5. `getChatMember` must show bot `administrator` or `creator` for groups.\n6. `sendChatAction` must validate configured topic reachability without sending a message.\n7. Existing staff/customer separation gates remain mandatory.\n8. Candidate package identity must match Task26 authority.\n9. Any failure aborts `connect()`.\n\n## Increment B3: deterministic menu\n\nProduction nutrition command scopes:\n\n- Delete inherited default/all-private/all-group generic commands.\n- Customer chat scope: `/start` only.\n- Staff chat scope: `/nutritionops`, `/nutritionpreview`.\n- Disable generic lazy forum command registration for this candidate.\n- `setMyCommands`/`deleteMyCommands` failures are fatal.\n\n## Increment B4: pinned profile graph\n\nChange `dualcoach/profile/pyproject.toml` to exact pins:\n\n```toml\n[build-system]\nrequires = [\"setuptools==81.0.0\"]\nbuild-backend = \"setuptools.build_meta\"\n\ndependencies = [\n  \"jsonschema==4.26.0\",\n  \"pydantic==2.13.4\",\n  \"typer==0.24.1\"\n]\n```\n\n---\n\n# 7. Wave W1-C: ingress and publication\n\n## Increment C1: failed ingress reconciliation\n\nModify:\n\n```text\ngateway/platforms/telegram_polling_receipts.py\n```\n\nRules:\n\n- Persist valid failure reasons, including `handler_exception`.\n- Permit `FAILED(handler_exception) -> RECOVERED` only for an exact registered business-recovery candidate.\n- Match update ID, actor, route, message, callback digest and business receipt digest.\n- Recovered receipts are terminal.\n- Offset advancement remains monotonic and occurs once.\n- Arbitrary failed updates cannot be replayed.\n- New update IDs containing a repeated callback are rejected by business CAS/version checks.\n\n## Increment C2: publication state machine\n\nModify:\n\n```text\ngateway/platforms/telegram_nutrition_onboarding_publication_outbox.py\ngateway/platforms/telegram_nutrition_onboarding_runtime_publication.py\ngateway/platforms/telegram_nutrition_onboarding_runtime_publication_transport.py\n```\n\nOutbox key:\n\n```text\nsession_id\npublication_generation\naudience\nroute_digest\npayload_digest\n```\n\nStates:\n\n```text\nPREPARED -> DISPATCHING -> SENT\n                         -> UNKNOWN\n```\n\nRules:\n\n- Persist `PREPARED` before network I/O.\n- Persist `DISPATCHING` before send/edit.\n- Preserve consent-card message ID.\n- Publish the first customer question by editing that known message.\n- Retry identical edit after restart; treat Telegram’s `message is not modified` as confirmation.\n- An unanchored send found in `DISPATCHING` after restart becomes `UNKNOWN` and is not resent.\n- A business commit that crashed before publication is recoverable to one normal send.\n- Concurrent callbacks converge on one outbox key.\n\n---\n\n# 8. Wave W2: adapter integration\n\nLane D exclusively modifies:\n\n```text\ngateway/platforms/telegram.py\ngateway/platforms/telegram_nutrition_onboarding_runtime.py\ngateway/platforms/telegram_nutrition_onboarding_runtime_callback.py\ngateway/platforms/telegram_nutrition_onboarding_runtime_collection.py\n```\n\nIntegration order:\n\n1. Resolve and authorize profile package before constructing nutrition services.\n2. Run token/identity/chat/topic/package preflight before polling.\n3. Install deterministic command scopes.\n4. Persist consent handoff before registry consent call.\n5. On startup, reconcile consent handoffs before ordinary pending publications.\n6. Register exact polling business-recovery candidates.\n7. Reconcile failed ingress after business recovery.\n8. Preserve existing callback acknowledgements and authorization checks.\n9. Do not catch the E2E process-crash sentinel in production code; fault injection belongs only to the child harness.\n\n---\n\n# 9. Wave W3: mandatory GREEN\n\n```bash\nrun_green \"$EV/green-consent-recovery.txt\" \\\n  .venv/bin/python -m pytest -q \\\n  tests/gateway/test_telegram_nutrition_onboarding_recovery.py\n\nrun_green \"$EV/green-production-preflight.txt\" \\\n  .venv/bin/python -m pytest -q \\\n  tests/gateway/test_telegram_production_preflight.py\n\nrun_green \"$EV/green-publication-recovery.txt\" \\\n  .venv/bin/python -m pytest -q \\\n  tests/gateway/test_telegram_publication_recovery.py\n```\n\nEach evidence file must contain all expected node IDs and `EXIT_CODE=0`.\n\n---\n\n# 10. Wave W4-A: disposable fake Telegram Bot API E2E\n\nAdd:\n\n```text\ntests/support/fake_telegram_bot_api.py\ntests/gateway/test_fake_telegram_bot_api.py\nscripts/run_dualcoach_v111_disposable_e2e.py\nscripts/_dualcoach_v111_gateway_process.py\n```\n\n## Fake API behavior\n\nSupport:\n\n```text\ngetMe\ngetChat\ngetChatMember\ngetUpdates\nsendMessage\neditMessageText\nanswerCallbackQuery\nsendChatAction\nsetMyCommands\ndeleteMyCommands\ngetWebhookInfo\ndeleteWebhook\n```\n\nState:\n\n- bot identity;\n- chats, topics and memberships;\n- queued updates and callback IDs;\n- monotonic message IDs per chat;\n- polling offsets;\n- sent and edited messages;\n- callback acknowledgements;\n- command scopes;\n- append-only sanitized API call log;\n- named synchronization events.\n\nTelegram-compatible details:\n\n- `getUpdates(offset=N)` returns only `update_id >= N`.\n- Repeating identical `editMessageText` returns 400 `message is not modified`.\n- Invalid topic/admin operations fail.\n- Tokens are never written to logs.\n- All waits use named events/futures with bounded timeout; no sleep or polling loops.\n\n## Child gateway fault injection\n\nThe E2E-only child process imports the real adapter and wraps exact methods before starting the gateway. It calls `os._exit(86)` immediately after configured durable boundaries, including:\n\n- registry consent committed before onboarding session creation;\n- business transition committed before ingress receipt;\n- known-message edit returned before publication receipt.\n\nNo fault hook is added to production modules.\n\n## E2E sequence\n\n1. Build or locate bound candidate wheels.\n2. Create disposable venv with no index access.\n3. Install Hermes and profile wheels using `--no-deps`.\n4. Create synthetic private profile/config/registry.\n5. Start loopback fake API.\n6. Launch real gateway polling child.\n7. Claim invite and commit consent.\n8. Crash after registry commit.\n9. Restart and verify onboarding session/first question recovery.\n10. Inject replay and concurrent callback updates.\n11. Complete every onboarding question.\n12. Crash after customer completion business commit.\n13. Restart and assert one owner card.\n14. Approve as owner; crash after approval business commit.\n15. Restart and assert one operator delivery.\n16. Activate and run one daily check-in.\n17. Assert receipts, offsets, command scopes and exact message counts.\n18. Assert every socket destination was loopback.\n19. Stop all children and delete disposable state.\n20. Print `DISPOSABLE_E2E_PASS` as the final line.\n\nRequired command:\n\n```bash\ncd \"$WT\"\npython scripts/run_dualcoach_v111_disposable_e2e.py \\\n  --evidence-dir .omo/evidence/v1.1.1-stability/disposable-e2e \\\n  2>&1 | tee \"$EV/disposable-e2e.txt\"\n\ntest \"${PIPESTATUS[0]}\" -eq 0\ntail -n 1 \"$EV/disposable-e2e.txt\" | grep -qx 'DISPOSABLE_E2E_PASS'\n```\n\nRequired artifacts:\n\n```text\ndisposable-e2e/candidate-binding.json\ndisposable-e2e/fake-api-calls.jsonl\ndisposable-e2e/gateway-process-events.jsonl\ndisposable-e2e/network-boundary.json\ndisposable-e2e/polling-offsets.json\ndisposable-e2e/publication-counts.json\ndisposable-e2e/receipt-assertions.json\ndisposable-e2e/state-before-crash.json\ndisposable-e2e/state-after-restart.json\ndisposable-e2e/cleanup.json\n```\n\n---\n\n# 11. Wave W4-B: migration and rollback\n\nAdd:\n\n```text\nscripts/plan_dualcoach_v111_migration.py\nscripts/migrate_dualcoach_v111_profile.py\nscripts/compare_dualcoach_v111_seals.py\ntests/gateway/test_dualcoach_v111_migration.py\ndocs/dualcoach-v1.1.1-migration.md\n```\n\n## Read-only dry-run\n\n```bash\npython scripts/plan_dualcoach_v111_migration.py \\\n  --profile-root /home/cube/.hermes/profiles/dualcoachtest \\\n  --service-unit hermes-gateway-dualcoachtest.service \\\n  --expected-main-pid 3771761 \\\n  --candidate-binding \"$EV/build/candidate-binding.json\" \\\n  --dry-run \\\n  --output \"$EV/migration/dry-run.json\"\n\nchmod 600 \"$EV/migration/dry-run.json\"\n\ntest \"$(systemctl --user show \\\n  hermes-gateway-dualcoachtest.service \\\n  --property MainPID --value)\" = 3771761\n```\n\nDry-run validates without rewriting:\n\n- service executable and environment;\n- token source without printing token;\n- candidate/profile package binding;\n- registry and consent;\n- bootstrap state/generation/handoff;\n- onboarding answers and cursor;\n- publication and ingress receipts;\n- activation authority;\n- old v1.1.0 rollback artifact;\n- config, package and state SHA-256 inventory.\n\nIt fails if:\n\n- `MainPID != 3771761`;\n- a legacy missing workflow lacks authenticated handoff evidence;\n- migration would discard answers, cursor, receipts or activation;\n- old rollback artifacts are missing;\n- any output contains a token/raw secret.\n\n## Copied-profile migration\n\nLater deployment must never migrate the old profile in place:\n\n1. Stop service and verify `MainPID=0`.\n2. Snapshot and fsync the old profile.\n3. Create a candidate-specific venv.\n4. Install wheels with `--no-index --no-deps`.\n5. Copy the profile to a candidate staging root.\n6. Migrate only the copy.\n7. Compare semantic digests of registry, consent, answers, cursor, receipts and activation.\n8. Point a staged systemd drop-in to the new venv/profile.\n9. Start with customer delivery disabled.\n10. Run production preflight and operator-only smoke.\n11. Require separate approval before customer enablement.\n\n## Rollback\n\n1. Stop candidate.\n2. Restore old systemd drop-in.\n3. Point to untouched old venv/profile.\n4. Start v1.1.0.\n5. Verify old artifact and profile hashes.\n6. Confirm customer/delivery remain disabled.\n7. Preserve failed candidate copy as private evidence.\n\n---\n\n# 12. Wave W5: prior failures and regressions\n\nFor each previously observed failure, emit a manifest row:\n\n```text\nnodeid\nfirst failure\nbaseline result\nclassification\nowner\nrequired fix\nRED evidence\nGREEN evidence\n```\n\nClassifications:\n\n- product defect;\n- nondeterministic test defect;\n- stale/incorrect assertion;\n- environment/setup defect;\n- unrelated pre-existing defect.\n\nEvery category must be fixed or block release. No skips, xfails, retries-until-pass, sleeps, weakened assertions, broad mocks, or timeout inflation.\n\nAudit:\n\n```bash\ngit diff --unified=0 \"$BASE\" -- tests scripts \\\n  | grep '^+' \\\n  | grep -E 'time\\.sleep|asyncio\\.sleep|pytest\\.skip|pytest\\.xfail|flaky' \\\n  && exit 1 || true\n```\n\nRequired regressions:\n\n```bash\nrun_green \"$EV/regression/required-broad.txt\" \\\n  timeout --signal=TERM --kill-after=30s 3600s \\\n  .venv/bin/python -m pytest -q \\\n  tests/gateway/test_nutrition_coaching.py \\\n  tests/gateway/test_telegram_customer_bootstrap_invite_ttl.py \\\n  tests/gateway/test_telegram_physique_checkin.py\n\nrun_green \"$EV/regression/profile.txt\" \\\n  timeout --signal=TERM --kill-after=30s 3600s \\\n  .venv/bin/python -m pytest -q dualcoach/profile/tests\n\nrun_green \"$EV/regression/gateway-full.txt\" \\\n  timeout --signal=TERM --kill-after=30s 7200s \\\n  .venv/bin/python -m pytest -q tests/gateway \\\n  --junitxml=\"$EV/regression/gateway-full.xml\"\n```\n\nRun the final full suite once. A failure returns to a focused RED/GREEN increment; do not rerun the full command blindly.\n\n---\n\n# 13. Wave W6: static and artifact qualification\n\n## Strict basedpyright\n\nGenerate `\"$EV/typecheck/basedpyrightconfig.json\"` with every changed production Python file explicitly included:\n\n```json\n{\n  \"typeCheckingMode\": \"strict\",\n  \"pythonVersion\": \"3.12\",\n  \"pythonPlatform\": \"Linux\",\n  \"extraPaths\": [\".\", \"dualcoach/profile\"],\n  \"include\": [\"<all changed production Python paths>\"]\n}\n```\n\nNo `exclude`, blanket suppressions, `type: ignore`, or `pyright: ignore`.\n\n```bash\ntest \"$(basedpyright --version | head -n1)\" = \"basedpyright 1.39.9\"\n\nbasedpyright --project \"$EV/typecheck/basedpyrightconfig.json\" \\\n  2>&1 | tee \"$EV/typecheck/basedpyright.txt\"\ntest \"${PIPESTATUS[0]}\" -eq 0\n\n.venv/bin/ruff check $(git diff --name-only \"$BASE\" -- '*.py') \\\n  2>&1 | tee \"$EV/typecheck/ruff.txt\"\ntest \"${PIPESTATUS[0]}\" -eq 0\n\n.venv/bin/python -m compileall -q \\\n  gateway hermes_cli dualcoach/profile/checkin_cli scripts\n```\n\n## Reproducible wheels\n\nCreate two isolated copies from the same candidate source manifest.\n\n```bash\nexport SOURCE_DATE_EPOCH=\"$(git show -s --format=%ct \"$BASE\")\"\nexport PYTHONHASHSEED=0\nexport TZ=UTC\nexport LC_ALL=C\n\nuv build --wheel --no-build-isolation \\\n  --out-dir \"$EV/build/a/hermes\" \"$EV/build/source-a\"\nuv build --wheel --no-build-isolation \\\n  --out-dir \"$EV/build/a/profile\" \"$EV/build/source-a/dualcoach/profile\"\n\nuv build --wheel --no-build-isolation \\\n  --out-dir \"$EV/build/b/hermes\" \"$EV/build/source-b\"\nuv build --wheel --no-build-isolation \\\n  --out-dir \"$EV/build/b/profile\" \"$EV/build/source-b/dualcoach/profile\"\n\nsha256sum \"$EV\"/build/{a,b}/{hermes,profile}/*.whl \\\n  | tee \"$EV/build/wheel-hashes.txt\"\n```\n\nA/B hashes and ordered wheel inventories must match.\n\nCandidate binding includes:\n\n- `NutriCoach-v1.1.1`;\n- base commit;\n- candidate source-manifest and patch hashes;\n- `uv.lock` hash;\n- both wheel hashes and member inventory hashes;\n- toolchain identities;\n- mandatory RED/GREEN evidence hashes;\n- full JUnit hash;\n- E2E evidence-root hash;\n- migration dry-run hash;\n- production before/after seal hashes;\n- v1.1.0 rollback artifact hash.\n\nNever alter sealed v1.1.0 artifacts:\n\n```bash\ngit diff --exit-code \"$BASE\" -- dualcoach/releases/v1.1.0\n```\n\n---\n\n# 14. Wave W7: production immutability and cleanup\n\nRecompute the production seal and compare it byte-for-byte/semantically with the initial seal.\n\n```bash\ntest \"$(systemctl --user show \\\n  hermes-gateway-dualcoachtest.service \\\n  --property MainPID --value)\" = 3771761\n\npython scripts/plan_dualcoach_v111_migration.py \\\n  --profile-root /home/cube/.hermes/profiles/dualcoachtest \\\n  --service-unit hermes-gateway-dualcoachtest.service \\\n  --expected-main-pid 3771761 \\\n  --candidate-binding \"$EV/build/candidate-binding.json\" \\\n  --dry-run \\\n  --output \"$EV/production-seal/final.json\"\n\npython scripts/compare_dualcoach_v111_seals.py \\\n  \"$EV/production-seal/initial.json\" \\\n  \"$EV/production-seal/final.json\" \\\n  | tee \"$EV/production-seal/compare.txt\"\n\ngrep -qx 'PRODUCTION_IMMUTABILITY_PASS' \\\n  \"$EV/production-seal/compare.txt\"\n\ngit diff --exit-code \"$BASE\" -- dualcoach/releases/v1.1.0\n\n! pgrep -af 'fake_telegram_bot_api|_dualcoach_v111_gateway_process' \\\n  >\"$EV/cleanup-processes.txt\"\n```\n\nCleanup requirements:\n\n- remove baseline worktrees;\n- remove temporary source copies after their manifest is recorded;\n- remove disposable profiles, venvs, sockets and fake servers;\n- verify no child gateway remains;\n- retain the uncommitted implementation worktree and private evidence for review;\n- do not commit, push, tag, publish, deploy, restart production, or contact Telegram.\n\nNo files were edited in producing this plan.","run_stats":{"runtime_ms":1119119,"turns":49,"tool_calls":137,"output_tokens":51362,"total_tokens":7418167,"generation_ms":1039677,"tokens_per_second":49,"cost_usd":10.162061000000003,"cache_hit_rate_last":0.8784032906023891,"cache_hit_rate_run":0.9326333464778829}}