I read this as advisory planning - I inspected the current profile, readiness, activation, adaptive-nutrition, bootstrap, and Telegram boundaries and will give a decision-complete RED-to-GREEN plan without editing code. # Outcome **Select a profile-owned nutrition-onboarding domain with a thin Telegram adapter.** Keep room bootstrap in `AWAITING_ACTIVATION` while onboarding runs as a separate persisted subworkflow. Only after customer attestation, restriction reconciliation, trainer review, owner approval, artifact finalization, and a successful readiness audit may the existing activation card and canonical cutover path proceed. No files were changed. ## Current baseline - `dualcoachtest` currently fails readiness with exactly: - `adjustment_policy_missing` - `baseline_missing` - `calculation_missing` - `customer_restrictions_unresolved` - `readiness_receipt_missing` - `restriction_kb_missing` - The current enabled test customer has no onboarding artifacts. - `dualcoachtest/knowledge` is a symlink, so the existing KB path cannot pass the readiness containment rules. - `/home/cube/.hermes/profiles/physique-coach` is the Git-backed canonical profile source. `dualcoachtest/workspace/checkin_cli` is an unversioned deployment copy. - Both the canonical profile and Hermes repository are already dirty. Implementation must not reset, clean, stash, or overwrite unrelated work. - The existing committed-activation validation applies readiness to the already-enabled legacy customer. That makes cutover impossible without either mutating that customer or adding a bounded legacy migration. This must be fixed before real-customer cutover. - `delivery`, adaptive `activation`, and all related feature flags are currently false and must remain false. # Designs considered ## A. Profile-owned onboarding domain, separate from bootstrap - selected Add a strict profile-local onboarding service and private store. Telegram only validates transport evidence and renders prompts. Bootstrap remains the authority for room identity, registration, consent, and activation reservation. Advantages: - Health and nutrition policy stays out of `telegram.py`. - Reuses canonical registry, consent, activation, and adaptive-nutrition authorities. - Supports deterministic unit tests without Telegram. - Restart, concurrency, artifact versioning, and privacy cleanup have one clear owner. - Does not weaken existing bootstrap or activation gates. Cost: - Requires coordinated changes in the Git-backed profile, its `dualcoachtest` deployment, and Hermes gateway. - Requires an explicit source/deployment parity check. ## B. Extend the room-bootstrap ledger with questionnaire and nutrition artifacts Advantages: - One state machine and generation counter. - Fewer initial integration objects. Rejected because: - It puts sensitive nutrition state and calculation policy in a gateway-owned bootstrap ledger. - It couples room provisioning, consent, health questions, review, and activation recovery. - Bootstrap migrations become dangerous and difficult to reason about. - It makes future non-Telegram onboarding impractical. ## C. Owner-only CLI intake before Telegram registration Advantages: - Smallest transport surface. - Easier persistence and recovery. Rejected because it does not provide customer self-service, exact customer attestation provenance, or in-room trainer review. # Source ownership and deployment The authoritative implementation must be made in: ```text /home/cube/.hermes/profiles/physique-coach/workspace/checkin_cli ``` Then deploy an explicit allowlist of feature files byte-for-byte to: ```text /home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli ``` Do not continue treating the dual-only readiness files as source. Implementation order: 1. Port the current dual-only readiness changes semantically into the canonical Git tree. Do not blindly replace dirty whole files. 2. Implement and test all profile-domain changes in the canonical tree. 3. Update gateway tests, which already import that canonical package. 4. Copy only the approved feature file allowlist to `dualcoachtest`. 5. Run the full profile suite again in `dualcoachtest`. 6. Verify `sha256sum`/`cmp` parity for every deployed feature file. 7. Add `NUTRITION_ONBOARDING_API_VERSION = "1.0"` and have the gateway loader reject any other version or any module loaded outside the configured profile package. No Git commit should be created unless separately requested. # Files and boundaries ## Canonical profile package Root: ```text /home/cube/.hermes/profiles/physique-coach/workspace/checkin_cli ``` ### New production files `checkin_cli/nutrition_onboarding_models.py` - Strict Pydantic models with `extra="forbid"` and frozen values. - Enums: - `OnboardingState` - `OnboardingStep` - `ReviewDecision` - `PublicationState` - Models: - `OnboardingAuthority` - `MessageEvidence` - `OnboardingSession` - `RestrictionKnowledgeBase` - `NutritionBaseline` - `RestrictionReconciliation` - `InitialNutritionPlan` - `AdjustmentPolicyArtifact` - `ClinicalReviewReceipt` - `ReviewReceipt` - `NutritionReadinessReceipt` - Canonical decimal fields are JSON strings, avoiding float-dependent digests. `checkin_cli/nutrition_onboarding_calculations.py` Pure functions only: - `normalize_baseline_answers(...)` - `reconcile_restrictions(...)` - `calculate_initial_plan(...)` - `build_adjustment_policy(...)` - `customer_policy_from_adjustment_artifact(...)` No filesystem, registry, clock, or Telegram imports. `checkin_cli/nutrition_onboarding_store.py` - `NutritionOnboardingStore` - Private, no-symlink, owner-only filesystem handling. - Public boundaries: - `start_session(...)` - `load_session(...)` - `append_transition(...)` - `prepare_publication(...)` - `commit_publication(...)` - `mark_publication_uncertain(...)` - `bind_publication_receipt(...)` - `acknowledge_no_side_effect(...)` - `write_candidate_bundle(...)` - `commit_artifact_bundle(...)` - `purge_transient_answers(...)` - Uses `flock`, generation/version CAS, atomic replacement, file and directory `fsync`. - Never performs provider I/O while holding a lock. `checkin_cli/nutrition_onboarding.py` - `NutritionOnboardingService` - Public operations: - `start_or_resume(...)` - `submit_choice(...)` - `submit_text(...)` - `attest_baseline(...)` - `record_clinical_review(...)` - `review_as_trainer(...)` - `review_as_owner(...)` - `resume_finalization(...)` - `status(...)` - `cancel(...)` - Refreshes registry, consent, role routes, owner authority, feature flags, and customer enabled state before every mutation. - Lazily calls the canonical customer-admin projection API during finalization. - Does not activate customers or set delivery/feature flags. `checkin_cli/nutrition_onboarding_cli.py` Operator-only, typed CLI: - `status` - `recovery-status` - `recovery-bind` - `recovery-retry` - `record-clinical-review` - `purge-incomplete` - `migration-preflight` - `migration-commit` - `seed-kb --dry-run` - `seed-kb --commit` The existing `readiness_cli.py` remains strictly read-only. `checkin_cli/policies/nutrition-restriction-kb-template-v1.json` - Unapproved source template. - Contains citations and deterministic rules but no customer data. - Runtime approval is added only by `seed-kb --commit`. ### Existing production files to modify `checkin_cli/nutrition_readiness.py` - Select artifacts through `readiness-current.json`. - Recompute all current authority and artifact digests. - Permit enabled or disabled target state, but never permit delivery or adaptive activation. - Return the existing six missing reason codes when no artifacts/KB exist. `checkin_cli/nutrition_readiness_contract.py` - Retain reason-code and audit DTO ownership. - Add only specific post-artifact reasons such as `registry_plan_stale`. - Do not add extra reasons to the empty-artifact baseline. `checkin_cli/nutrition_readiness_io.py` - Add owner UID, regular-file, link-count, component symlink, mode, and maximum-size checks. - Load global KB from the new private path. - Validate selected relative artifact paths remain under the exact customer onboarding root. `checkin_cli/nutrition_readiness_validators.py` - Validate strict typed models rather than permissive field subsets. - Recompute content and envelope digests. - Validate review scope, receipt authority, registry plan projection, consent, KB expiry, and clinical review scope. `checkin_cli/customer_admin.py` Add: - `apply_nutrition_onboarding_projection(...)` - Requires the exact target customer to exist and be disabled. - Rejects an enabled target. - Replaces only the target's nutrition profile and 12-week plan. - Preserves customer key, display name, Telegram routes, trainer, schedule, consent, enabled state, and every other registry row. - Uses a prepared/committed projection journal at: ```text data/nutrition-onboarding-projection-journal.jsonl ``` - Is idempotent for the same artifact bundle and rejects conflicting replay. - Activation receipt v2 support: - New activation rows pin the readiness receipt digest, readiness bundle digest, and customer projection digest. - New activation writes can only be v2 and always rerun readiness first. - Legacy v1 receipts are accepted only when pinned by the one-time migration manifest described below. - Lock order: 1. `profile_authority_lock` 2. onboarding store lock 3. adaptive store lock, if needed `checkin_cli/adaptive_nutrition.py` Add only narrow adapters: - `customer_policy_from_onboarding_artifact(...)` - `validate_onboarding_policy_compatibility(...)` Reuse existing `build_snapshot` and `propose`; do not create a second trend engine. `checkin_cli/__init__.py` - Export only stable service, status, readiness, and API-version boundaries. ### Tests to add or modify New: ```text tests/test_nutrition_onboarding_models.py tests/test_nutrition_onboarding_calculations.py tests/test_nutrition_onboarding_store.py tests/test_nutrition_onboarding.py ``` Promote and expand: ```text tests/test_nutrition_readiness.py ``` Modify: ```text tests/test_customer_admin.py tests/test_adaptive_nutrition.py ``` ## Hermes gateway repository Root: ```text /home/cube/projects/richard/hermes-agent ``` ### New files `gateway/platforms/telegram_nutrition_onboarding.py` - Thin transport adapter. - Dynamically loads the configured profile package and verifies module source containment. - Owns callback encoding and Korean prompt/card rendering. - Calls the profile service before and after provider operations. - Never calculates nutrition targets. - Never writes registry or artifacts directly. Callback form: ```text non1:<1-2 character action>::<43 character sid hash> ``` This remains below Telegram's 64-byte limit and carries no health values. `tests/gateway/test_telegram_nutrition_onboarding.py` - Full transport/routing/restart/concurrency contract tests. `scripts/nutrition-onboarding` - Small wrapper around `python -m checkin_cli.nutrition_onboarding_cli`. - Resolves the configured profile package exactly. ### Existing files to modify `gateway/platforms/telegram.py` Only orchestration hooks: - Initialize the onboarding transport. - After consent reaches `AWAITING_ACTIVATION`, start/resume onboarding instead of publishing activation immediately. - Route `non1:` callbacks before generic callbacks and before room-bootstrap catch-all handling. - Offer exact onboarding text handling before `_room_bootstrap_reserves_ingress` drops the message. - Keep every other update kind in a reserved bootstrap room consumed. - Resume committed prompts and surface uncertain publications during startup. - Publish the existing activation card only after `ready=True`. `gateway/platforms/telegram_room_bootstrap.py` - Persist an immutable `ConsentReceipt` when the exact consent callback succeeds: - chat ID - topic ID - customer user ID - consent-card message ID - bootstrap generation - notice version - recorded time - receipt digest - Do not clear that evidence when entering `AWAITING_ACTIVATION`. - Keep existing states unchanged. `gateway/platforms/telegram_room_bootstrap_cutover.py` - Preflight the target's readiness before disabling the current customer. - Include only a redacted readiness/bundle digest in `ActivationCutoverReceipt`. - Continue repeating readiness in `activate_customer`. - Never call Telegram or write onboarding artifacts. `gateway/platforms/telegram_room_bootstrap_activation.py` - Require activation receipt v2 and current readiness for newly activated customers. - Permit only the exact migration-pinned legacy receipt for the pre-existing enabled customer. `gateway/platforms/nutrition_coaching.py` - Load the approved adjustment policy from readiness when building proposal context. - Feed it to existing `build_snapshot` and `propose`. - Do not mutate the registry or initial plan. - Do not activate or deliver a proposal while feature flags remain false. `scripts/nutrition-room-bootstrap` - Display target readiness digest in cutover preflight. - Preserve the existing explicit `--execute` authority; never infer it from onboarding readiness. Documentation: ```text docs/nutrition-coaching/customer-onboarding-ko.md docs/nutrition-coaching/trainer-runbook-ko.md docs/nutrition-coaching/room-bootstrap-draft.example.json ``` Clarify that draft calories/macros are disabled-registration placeholders, not approved customer nutrition targets. ### Profile configuration Add one explicit opt-in: ```yaml platforms: telegram: extra: room_bootstrap: nutrition_onboarding: true ``` in: ```text /home/cube/.hermes/profiles/dualcoachtest/config.yaml ``` The gateway must default this to false. # Persistent paths Per-customer state: ```text data/customers//nutrition-onboarding/ .lock session.json transition-journal.jsonl publication-journal.jsonl candidate-vN.json clinical-review-vN.json baseline-vN.json restriction-reconciliation-vN.json initial-plan-vN.json adjustment-policy-vN.json readiness-receipt-vN.json readiness-current.json ``` Global KB, deliberately outside the symlinked `knowledge` tree: ```text data/global/nutrition-safety/restriction-kb-v1.json ``` Legacy activation migration authority: ```text data/migrations/nutrition-readiness-v1/legacy-activation-authority.json ``` Directories must be `0700`; files and lock files `0600`; owner UID must match the process; symlinks and multi-linked files are rejected. # Workflow and state machine Bootstrap remains: ```text ... -> AWAITING_CONSENT -> AWAITING_ACTIVATION -> ACTIVE ``` Nutrition onboarding is independent: ```text COLLECTING -> CUSTOMER_ATTESTATION -> RECONCILING -> SAFETY_HOLD conditional -> TRAINER_REVIEW -> OWNER_REVIEW -> FINALIZING -> READY ``` Terminal states: ```text CANCELLED EXPIRED CONSENT_REVOKED UNSUPPORTED_MINOR ``` Rules: - No session or answers may be persisted before current `privacy-v1` consent. - Consent revocation immediately stops processing and removes transient answer values. - The customer cannot change the registry start date or role routes. - Any trainer or owner change request invalidates both reviews and returns to customer summary/attestation. - A clinical receipt scoped to stale health inputs is invalid. - `READY` does not activate anything. It only permits the existing activation card. - Bootstrap stays `AWAITING_ACTIVATION` until canonical activation commits. - A 14-day incomplete session expires. A new session may be created against the same still-reserved bootstrap only through the typed service. - Once a readiness revision is committed, it is immutable. Corrections create `vN+1` artifacts and atomically advance `readiness-current.json`. # Questionnaire The deterministic customer sequence is: 1. Date of birth or adult age. 2. Mifflin equation constant basis: - male formula constant - female formula constant - decline 3. Height in cm. 4. Current weight in kg. 5. Activity category. 6. Activity rationale and weekly training pattern. 7. Goal type: lose, maintain, gain. 8. Target weight and target date, or maintenance intent. 9. Allergies. 10. Intolerances. 11. Dietary, religious, and ethical exclusions. 12. Disliked foods. 13. Preferred foods. 14. Medical conditions. 15. Medications and supplements. 16. Pregnancy/breastfeeding status. 17. Eating-disorder risk/history response. 18. Cooking access. 19. Budget band. 20. Meal count. 21. Schedule constraints. 22. Full customer summary and attestation. Structured “none” is required where relevant; blank does not silently mean none. Validation: - Age: 18-120 as of the current KST date. - Raw DOB is discarded after deriving age; baseline retains age, basis, reference date, and source-value digest. - Height: 120-230 cm. - Weight: 30-300 kg. - Goal date: 12-52 weeks from canonical `plan.starts_on`. - Meal count: 2-6. - Free-text entries are NFC-normalized, whitespace-collapsed, bounded, deduplicated, and never interpreted by an LLM. - Underage is terminal and cannot be owner-overridden. - Equation-basis decline prevents calculation and readiness. - Unknown restrictions remain unresolved; they are never guessed. - Any medical condition or medication requires a scoped external clinical review. - Pregnancy/breastfeeding, eating-disorder risk, or an uncertain answer creates a safety hold. - The clinical-review command accepts only: - `cleared_for_nonmedical_coaching` - `not_cleared` - It accepts an opaque external reference but no diagnosis, dose, medication-change, or prescribing fields. # Calculation contract Use `Decimal` and `ROUND_HALF_UP`. Mifflin-St Jeor: ```text BMR = 10 * weight_kg + 6.25 * height_cm - 5 * age + equation_constant ``` Activity factors, versioned as `activity_factor_v1`: ```text sedentary 1.200 light 1.375 moderate 1.550 very_active 1.725 extra_active 1.900 ``` Goal projection: - Energy-density constant: `7700 kcal/kg`, explicitly versioned. - Loss may not exceed 1.0% of projected body weight per week. - Gain may not exceed 0.5% per week. - Initial calories must remain in the existing adaptive bounds of 1500-4500 kcal. - Do not silently clamp an unsafe requested goal. Mark it unsafe and request revision. - Produce exactly 12 weekly rows because `CustomerSpec.plan` is a fixed `TwelveWeekPlan`. - A goal longer than 12 weeks receives a 12-week partial trajectory, not a promise to reach the final target. - Calories are rounded to 20-kcal increments. - Protein and fat remain within existing adaptive bounds: - protein 120-250 g - fat 40-150 g - Use the existing `solve_macros`; adjust the selected fat value to an exact compatible increment so: ```text 4 * protein + 4 * carbohydrate + 9 * fat == calories ``` Each week contains: - projected start/end weight - BMR - TDEE - target calories - protein/carbohydrate/fat - generic meal slots - projection disclaimer No food recommendation is generated during calculation. # Restriction KB The seed template must contain: - Version, effective date, expiry date, and owner approval envelope. - Source records with source ID, publisher, title, URL, publication/retrieval date, scope, and content hash. - Explicit arrays for: - allergens - intolerances - religious/ethical exclusions - medication/condition review rules - hard contraindications - cross-contact rules - substitutions - Every rule cites one or more existing source IDs. - Medication and condition rules may only produce `require_human_review`. - Unknown terms produce `unresolved`. - Religious or ethical rules are never inferred. - The template is `approved=false`; only the canonical owner seed command creates the approved private runtime copy. - KB expiry should be at most one year from owner review. # Adjustment proposals The onboarding policy is adapted directly to the existing `CustomerPolicy` and `propose` implementation. Requirements: - Two non-overlapping seven-day mean-weight windows. - At least 4 current-window samples and 10 total samples. - No missing or contradictory adherence evidence in the current window. - At least 5 adherent days. - Safety holds always return human review. - Calorie step: 100 kcal. - Cooldown: 7 days after a committed calorie-changing overlay. - Bounds remain 1500-4500 kcal. - Desired weekly ranges: - loss: -1.00% to -0.25% - maintain: -0.25% to +0.25% - gain: +0.10% to +0.50% The result is a proposal only. It must not: - replace the registry plan, - activate an adaptive revision, - enable delivery, - call Telegram, - or send to the customer. Existing owner review and adaptive lifecycle remain the only later approval authority. # Provenance and authority | Data | Source of truth | Receipt/projection | |---|---|---| | Customer identity/routes | Canonical registry | Bootstrap role claims and route digests | | Consent | Canonical `privacy-v1` registry consent | Immutable exact consent callback receipt | | Baseline facts | Customer responses | Exact prompt/reply or callback message evidence and attestation | | KB | Private profile-global approved artifact | Canonical owner approval and citations | | Restriction resolution | Deterministic KB reconciliation | Trainer and owner review bundle | | Calculation | Versioned deterministic method | Method/version/input/content digests | | Clinical clearance | External human review | Scoped binary receipt recorded by owner | | Trainer review | Exact registered trainer route | Message ID, publication generation, scope digest | | Owner approval | Exact canonical owner route | Message ID, publication generation, scope digest | | Registry plan | Canonical registry | Disabled-customer projection journal | | Activation | Activation journal v2 only | Readiness and projection digests | | Weekly trend | Canonical customer events | Existing deterministic snapshot digest | | Adjustment | Existing `propose` engine | Existing operator review lifecycle | | Delivery | Existing delivery ledger | Must remain disabled | Any change to consent, owner, customer/trainer route, KB, baseline, method version, registry projection, review scope, or feature flags makes readiness stale. # Exact Telegram checks Every customer prompt response must satisfy all of: - Current bootstrap session is `AWAITING_ACTIVATION`. - Bootstrap SID hash and generation match. - Current registry customer is the same disabled customer. - Current consent is granted under `privacy-v1`. - User ID, chat ID, and topic ID exactly match the registered customer route. - Text replies reference the exact current prompt message ID. - Button callbacks originate from the exact current prompt message ID. - Publication generation and current allowed action match. - Telegram `getChatMember` confirms the current actor is still present. - Incoming Telegram message/update ID has not already been consumed. Trainer and owner reviews add: - Exact trainer or canonical owner route. - Exact review-card message ID. - Exact review scope digest. - Fresh current role/owner authority. - No stale or forwarded card. Unknown `non1:` callbacks, wrong actors, wrong topics, edited messages, media, anonymous senders, and stale generations are consumed without mutation and never fall through to generic/LLM ingress. Callback data and logs contain no health values. # Restart and concurrency behavior Publication protocol: 1. Persist a `PREPARED` publication intent with target route, body digest, button digest, state version, and publication generation. 2. Release all locks. 3. Send once through Telegram. 4. Commit returned message ID. 5. On a known no-side-effect failure, abandon and allocate a new generation. 6. On timeout, process death, or ambiguous provider result, mark `UNCERTAIN`; do not resend automatically. 7. Recovery may: - bind the independently observed message ID, or - retry only after explicit positive no-side-effect evidence. 8. Superseded generations remain inert. Restart rules: - A committed current prompt is reused; it is not reposted. - A prepared-but-uncommitted publication becomes recovery-required. - Partial artifacts without a readiness receipt or current pointer cannot pass readiness. - Finalization replay regenerates and compares exact bytes/digests. - A committed registry projection is recognized idempotently. - Gateway startup scans sessions and reports unresolved publication attempts without exposing health data. Concurrency tests must use barriers/events, not sleeps: - Two simultaneous callbacks: one CAS winner, one stale rejection. - Duplicate Telegram update: one transition. - Trainer review racing customer change: one valid state transition. - Owner finalization racing cutover: profile lock serializes them; cutover cannot proceed early. - Two processes operating on one store: one committed transition. - Registry crash after prepared journal, after replacement, and before committed journal: exact recovery or fail closed. # Legacy activation migration Do not backfill the currently enabled test customer with fabricated onboarding artifacts. Instead: 1. While the dualcoach gateway is stopped, create a private migration manifest pinning: - exact currently enabled customer key, - exact existing activation receipt digest, - current registry customer projection digest, - canonical owner digest, - migration time and manifest digest. 2. Accept legacy activation only when all those values match. 3. The legacy customer remains not nutrition-ready if audited. 4. The exception permits continued runtime and cutover validation only. 5. It cannot approve new activation, delivery, registration inputs, or a changed legacy receipt. 6. Every new activation is v2 and requires current readiness. 7. Once the legacy customer is disabled, any later reactivation must use v2. This preserves the current enabled customer during deployment and keeps readiness fail-closed for all new activations. # RED-to-GREEN waves ## Wave 0 - serial safety and source normalization - Record both Git statuses and the dual deployment diff. - Do not reset, clean, stash, or commit. - Port existing readiness files/deltas into the canonical profile source. - Freeze `NUTRITION_ONBOARDING_API_VERSION`. - Confirm the six-reason baseline remains unchanged. ## Wave 1 - parallel RED tests Parallel ownership with no overlapping production files: 1. **Schemas/calculation** - Model rejection, normalization, golden calculation vectors, unsafe goals. 2. **Store/recovery** - Permissions, symlinks, atomicity, CAS, multiprocess races, publication uncertainty. 3. **Admin/readiness** - Disabled-only projection, activation v2, legacy manifest, stale authority. 4. **Gateway** - Callback codec, exact routing, no generic fallback, restart behavior. 5. **Adaptive** - Readiness-policy adapter and proposal-only behavior. Run each RED target and record that it fails for the intended missing module/API, not an unrelated baseline failure. ## Wave 2 - serial contract GREEN Implement models and immutable artifact schemas first. All other waves depend on these contracts. ## Wave 3 - parallel domain GREEN - Calculations/reconciliation/readiness. - Store/service. - Customer-admin projection and activation v2. - Gateway callback codec and prompt rendering against fakes. - KB template and documentation. ## Wave 4 - serial integration GREEN - Wire consent handoff. - Wire Telegram text/callback reservation. - Wire finalization to disabled registry projection. - Wire readiness to activation card and cutover. - Wire readiness policy into existing adaptive proposal construction. - Add end-to-end test from consent through readiness and cutover preflight. ## Wave 5 - deployment and private migration - Copy the explicit feature allowlist to `dualcoachtest`. - Verify file parity. - Stop all dualcoach gateway writers. - Commit the legacy activation migration manifest. - Seed and owner-approve the global KB. - Enable the explicit onboarding config flag. - Restart exactly one systemd-managed gateway. ## Wave 6 - disposable Telegram QA, then real-customer onboarding No real customer or currently enabled customer is mutated during automated tests or disposable QA. # Automated verification commands ## Canonical profile ```bash set -euo pipefail P=/home/cube/.hermes/profiles/physique-coach/workspace/checkin_cli cd "$P" .venv/bin/python -m pytest -q \ tests/test_nutrition_onboarding_models.py \ tests/test_nutrition_onboarding_calculations.py \ tests/test_nutrition_onboarding_store.py \ tests/test_nutrition_onboarding.py \ tests/test_nutrition_readiness.py \ tests/test_customer_admin.py \ tests/test_adaptive_nutrition.py .venv/bin/python -m pytest -q .venv/bin/python -m compileall -q checkin_cli git -C /home/cube/.hermes/profiles/physique-coach diff --check ``` ## Gateway ```bash set -euo pipefail R=/home/cube/projects/richard/hermes-agent cd "$R" .venv/bin/ruff check \ gateway/platforms/telegram.py \ gateway/platforms/telegram_nutrition_onboarding.py \ gateway/platforms/telegram_room_bootstrap.py \ gateway/platforms/telegram_room_bootstrap_activation.py \ gateway/platforms/telegram_room_bootstrap_cutover.py \ gateway/platforms/nutrition_coaching.py \ tests/gateway/test_telegram_nutrition_onboarding.py .venv/bin/python -m pytest -q \ tests/gateway/test_telegram_nutrition_onboarding.py \ tests/gateway/test_telegram_room_bootstrap.py \ tests/gateway/test_telegram_room_bootstrap_transport.py \ tests/gateway/test_telegram_room_bootstrap_registration.py \ tests/gateway/test_telegram_room_bootstrap_activation.py \ tests/gateway/test_telegram_room_bootstrap_cutover.py \ tests/gateway/test_nutrition_coaching.py \ tests/gateway/test_adaptive_nutrition.py .venv/bin/python -m pytest -q tests/gateway .venv/bin/python -m py_compile \ gateway/platforms/telegram.py \ gateway/platforms/telegram_nutrition_onboarding.py \ gateway/platforms/telegram_room_bootstrap.py \ gateway/platforms/telegram_room_bootstrap_activation.py \ gateway/platforms/telegram_room_bootstrap_cutover.py uv build git diff --check ``` ## Deployed dualcoach package ```bash set -euo pipefail P=/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli cd "$P" .venv/bin/python -m pytest -q .venv/bin/python -m compileall -q checkin_cli ``` Then compare every deployed feature source and test file with `cmp -s`; any mismatch blocks restart. # Migration and seeding procedure With the dualcoach gateway stopped: ```bash PROFILE=/home/cube/.hermes/profiles/dualcoachtest PY="$PROFILE/workspace/checkin_cli/.venv/bin/python" "$PY" -m checkin_cli.nutrition_onboarding_cli migration-preflight \ --profile-root "$PROFILE" \ --expected-enabled-customer gate_d_delivery ``` Use the returned exact activation receipt digest for: ```bash "$PY" -m checkin_cli.nutrition_onboarding_cli migration-commit \ --profile-root "$PROFILE" \ --expected-enabled-customer gate_d_delivery \ --confirm-activation-receipt-digest "" ``` Seed the KB only after source parity and tests: ```bash "$PY" -m checkin_cli.nutrition_onboarding_cli seed-kb \ --profile-root "$PROFILE" \ --source "$PROFILE/workspace/checkin_cli/checkin_cli/policies/nutrition-restriction-kb-template-v1.json" \ --dry-run ``` After human source review: ```bash "$PY" -m checkin_cli.nutrition_onboarding_cli seed-kb \ --profile-root "$PROFILE" \ --source "$PROFILE/workspace/checkin_cli/checkin_cli/policies/nutrition-restriction-kb-template-v1.json" \ --commit \ --expected-owner-digest "" ``` Do not create artifacts for existing test customers. Do not edit `registry.json`, activation journals, or feature epochs manually. # Gateway restart procedure The host currently has both systemd-managed gateways and an additional manually launched dualcoach process. QA must begin with one dualcoach writer only. ```bash systemctl --user stop hermes-gateway-dualcoachtest.service pgrep -af 'hermes_cli.main.*--profile dualcoachtest.*gateway run' ``` Any remaining unmanaged dualcoach PID must be terminated deliberately, then awaited by PID rather than using a sleep: ```bash kill -TERM "" timeout 30 tail --pid="" -f /dev/null ``` Start only the service: ```bash systemctl --user start hermes-gateway-dualcoachtest.service systemctl --user is-active --quiet hermes-gateway-dualcoachtest.service systemctl --user show \ -p MainPID -p ActiveState -p SubState \ hermes-gateway-dualcoachtest.service journalctl --user \ -u hermes-gateway-dualcoachtest.service \ --since "5 minutes ago" \ --no-pager ``` Startup must fail closed on package-version mismatch, unsafe state paths, pending migration, or corrupt onboarding state. # Telegram QA checklist Use disposable identities and an isolated customer first. 1. Accept the exact `privacy-v1` consent card. 2. Verify bootstrap remains `AWAITING_ACTIVATION`. 3. Verify onboarding starts and no activation card appears. 4. Test: - wrong customer in correct topic, - correct customer in wrong topic, - wrong message ID, - old generation, - forwarded card, - anonymous sender, - text not replying to the current prompt. 5. Complete a safe questionnaire and customer attestation. 6. Restart the gateway mid-question and verify the same committed prompt resumes without duplication. 7. Inject an ambiguous publication and verify no automatic resend. 8. Verify a medication or condition produces `SAFETY_HOLD`, no plan, no owner approval, and no activation card. 9. Complete trainer review from the exact trainer topic. 10. Request a change and verify all review receipts become stale. 11. Re-attest, trainer-approve, and owner-approve. 12. Verify: - six selected artifact digests, - private modes, - registry plan equals the approved initial plan, - `delivery=false`, - adaptive `activation=false`, - readiness CLI exits `0`. 13. Verify the existing activation card appears only now. 14. Run cutover preflight without `--execute`. 15. Restart again and verify readiness and activation-card authority survive. 16. Verify no health values, Telegram IDs, or customer text appear in shared QA evidence; record only opaque IDs, states, counts, digests, and timestamps. For the real customer, repeat the same flow. The final `activate-cutover --execute` remains a separate human action. That canonical action may retire the named current test customer; no implementation, migration, seed, automated test, or onboarding callback may do so. # Cleanup and rollback - On successful finalization, remove transient raw answers immediately; retain only normalized artifacts, message evidence digests, and review receipts. - On cancel, consent revocation, or expiry, remove answer values and candidate artifacts, retaining a redacted tombstone for replay protection. - `purge-incomplete` must require: - exact session generation, - target still disabled, - no `readiness-current.json`, - no v2 activation receipt. - An unknown Telegram publication remains inert through generation invalidation; do not automatically delete or resend it. - Failed disposable profiles may be deleted only outside the live profile. - A registered but failed real target remains disabled; do not remove its registry row manually. - Before any v2 activation, code rollback is allowed by restoring the private code backup and disabling the onboarding config flag. - After a v2 activation receipt exists, downgrade is prohibited; fix forward so the old runtime cannot ignore new readiness authority.