I read this as a read-only architecture and safety audit - I’ll trace the onboarding model, state transitions, calculations, persistence/replay, compatibility paths, and tests, then report concrete risks and proposed failing tests.


I’ve located the live implementation under `dualcoach/profile`; the key issue is not the visible question count itself, but preserving the canonical baseline schema and resumable state-machine semantics behind any shorter presentation. I’m now tracing those exact contracts and compatibility seams.


The trace now shows two concrete “stuck” classes already present: meal-count contracts disagree (`2..6` onboarding/profile versus `1..8` adaptive), and the advertised privacy choice to decline the equation basis can never produce a plan. I’m consolidating those with session-versioning, revision, replay, and data-retention findings into a test-first migration recommendation.


I read this as a read-only safety/data-model audit - I traced the canonical schema, runtime flow, persistence, replay, revisions, calculations, and compatibility boundaries.

## Verdict

The 22 customer prompts can be reduced or merged, but **not by shrinking or reordering `QUESTION_FIELDS` in place**. That tuple is simultaneously:

- Canonical answer order
- Persisted cursor interpretation
- Reconciliation issue order
- Revision addressing
- Progress/replay semantics

A shorter UX needs a versioned presentation layer that expands each screen atomically into the unchanged canonical baseline fields.

## Critical findings

1. **Meal-count contracts already conflict**
   - Onboarding and `CustomerProfile`: `2..6`
     `nutrition_onboarding_models.py:190`, `customer_coaching.py:904`
   - Adaptive registration: `1..8`
     `customer_coaching.py:821`, `customer_admin.py:3721`
   - The parser accepts 1, 7, and 8, but reconciliation rejects them. Customers repeatedly receive clarification with no valid representation of their actual schedule.
   - All three contracts must converge, preferably on `1..8`.

2. **The privacy “decline” choice cannot complete onboarding**
   - `EquationSexBasis.DECLINE` is advertised and accepted.
   - Calculation then raises `CalculationDeclinedError`: `nutrition_onboarding_calculations.py:175`.
   - Owner preflight catches it only as generic `ValueError`; the owner remains at review without an actionable revision button. Publication renders Approve/Reject/Safety Hold, not `op_rev`: `telegram_nutrition_onboarding_runtime_publication.py:371-378`.
   - Either provide an approved non-sex-based calculation path or do not present decline as a completable choice.

3. **Raw DOB and sensitive answers survive successful completion**
   - Canonical `NutritionOnboardingBaseline` correctly discards DOB.
   - However, `baseline-candidate.json` stores complete `source_answers`, including DOB and medical answers: `nutrition_onboarding.py:241-260`.
   - Finalization deletes only `workflow.json`, not `baseline-candidate.json`: `nutrition_onboarding_finalize_service.py:58`.
   - Persist only derived `adult_age`, reference date, canonical digest, and safety disposition after attestation/finalization. Remove plaintext source answers when no longer needed.

4. **Optional cooking/budget skips conflict with downstream activation**
   - Runtime permits empty defaults for cooking and budget.
   - Adaptive registration later requires both to be non-empty and exactly match the onboarding profile: `customer_admin.py:3728-3730`, `3816+`.
   - A customer can finish onboarding yet become impossible to register adaptively. Either these fields are genuinely optional downstream, or onboarding must collect an explicit usable value.

5. **Changing the field tuple strands legacy sessions**
   - Persisted sessions contain only a positional cursor; no questionnaire version.
   - `submit_answer`, status, rewind, and revision index directly into current `QUESTION_FIELDS`: `nutrition_onboarding.py:204-212`, `316`.
   - Treat missing `questionnaire_version` as `legacy_22_v1`; never reinterpret its cursor using a new screen list.

6. **Revision semantics are tied to the old 22-step sequence**
   - `_reopen_revision` truncates every answer at and after the canonical field.
   - In a merged UX, revision must map canonical field -> owning screen, reopen that screen, and retain unaffected canonical answers.
   - Revising safety fields must invalidate clinical clearance and owner-risk acceptance, as current baseline revision already invalidates clinical review.

7. **Reference dates are inconsistent**
   - Reconciliation persists a stable reference date.
   - Attestation independently derives age with `date.today()`: `nutrition_onboarding.py:251`.
   - A restart or birthday boundary can produce a baseline evaluated against a different date than reconciliation. Capture one KST reference date at session creation and reuse it throughout.

8. **Active-customer migration is too narrow for generalized compatibility**
   - The legacy migration supports exactly one expected enabled customer: `nutrition_onboarding_migration.py:41`.
   - Existing enabled customers should not be reprojected or re-onboarded. Their registry profile, plan, readiness pointers, and adaptive registrations must remain byte/digest compatible.
   - Projection correctly remains disabled-customer-only: `nutrition_onboarding_projection.py:42`.

## Recommended model

Keep two independent contracts:

- `CANONICAL_BASELINE_FIELDS_V1`: the existing 22-field data contract and digest order.
- `QUESTIONNAIRE_V2_SCREENS`: fewer UX screens, each producing one or more canonical fields.

Persist:

- `questionnaire_version`
- `screen_id` or screen cursor
- Canonical answer map
- Stable `reference_date`
- Consumed update IDs
- Canonical answer digest

For legacy sessions, continue the old 22-step renderer until completion. New grouped submissions must validate all fields first, then atomically save all expanded values and consume the Telegram update once.

## Proposed failing tests

1. `test_compact_onboarding_matrix_reaches_ready`
   - Parameterize maintain/loss/gain.
   - Assert complete canonical baseline, resolved reconciliation, safe 12-week calculations, final readiness, and registry projection.

2. `test_maintain_screen_materializes_null_targets`
   - Maintain skips target inputs but emits both target fields as `None`.
   - Reconciliation must not report missing values.

3. `test_loss_gain_preserve_requested_goal_and_safe_recommendation`
   - Verify target weight/date survive merging.
   - Aggressive trajectories preserve the request and produce a safe recommended date.

4. `test_preflight_failure_always_exposes_bound_revision_path`
   - Cover declined equation basis, past target date, wrong target direction, and calorie-bound failure.
   - Assert state never becomes terminal/finalizing and customer/owner can revise the exact responsible screen.

5. `test_meal_count_supported_end_to_end`
   - Parameterize `1, 3, 7, 8`.
   - Assert onboarding, baseline, registry profile, meal structures, adaptive registration, restart, and readiness all accept the same value.

6. `test_age_privacy_after_attestation_and_ready`
   - Submit a unique DOB.
   - After attestation and after finalization, recursively inspect durable onboarding artifacts.
   - Assert plaintext DOB is absent while derived age and stable reference date remain.
   - Repeat after cancellation and failed/revised onboarding.

7. `test_reference_date_is_stable_across_birthday_restart`
   - Start before a birthday, restart/attest after the clock advances.
   - Assert reconciliation and baseline use the session’s original reference date.

8. `test_each_optional_skip_can_finish_and_activate`
   - Parameterize every skip-enabled field.
   - Assert canonical value exists, reconciliation resolves, onboarding reaches ready, and downstream adaptive registration remains constructible.
   - This should currently fail for cooking and budget.

9. `test_sensitive_group_never_defaults_to_safe`
   - Parameterize conditions, medications, pregnancy, and eating-disorder risk with provided/true/unknown.
   - Assert merged partial answers cannot silently become false/none and each required case enters safety hold.

10. `test_safety_hold_survives_restart_replay_and_revision`
    - Restart and duplicate-update replay while held.
    - Assert no duplicate receipt and no transition to owner review without bound clinical clearance or owner-risk acceptance.
    - Revising a safety field invalidates old clearance/acceptance.

11. `test_legacy_22_session_resumes_at_every_cursor`
    - Fixtures for cursors `0..22` with no questionnaire version.
    - Assert exact legacy next field, no answer loss, and completion under the new release.

12. `test_legacy_noncollecting_states_resume`
    - Cover customer attestation, reconciliation clarification, safety hold, owner review, finalizing, and ready.
    - Assert restart and duplicate callback replay always yield an actionable or terminal state.

13. `test_group_submission_is_atomic_and_idempotent`
    - Crash before write, after write, and during publication.
    - Assert either no grouped fields commit or all commit; replay consumes one update and never advances twice.

14. `test_revision_maps_canonical_field_to_compact_screen`
    - Revise each field within a merged screen.
    - Assert unaffected canonical answers remain, reconciliation digest changes, stale callbacks fail, and the customer can complete again.

15. `test_existing_enabled_customers_are_unchanged`
    - Seed active legacy customers with meal counts including 1 and 7+, readiness pointers, plans, and registrations.
    - Start/finalize a different disabled customer.
    - Assert existing customer documents and digests remain unchanged.

No files were modified.