message: 403: {"code":"ENTITLEMENT_ERROR","message":"Error 403: the user is not subscribed to required model plan"}

stop_reason: error


error_message: 403: {"code":"ENTITLEMENT_ERROR","message":"Error 403: the user is not subscribed to required model plan"}


I read this as tracing the invite-to-activation flow and judging the safest first-claim unbound change - I’ll map the required/persisted/overwritten/checked points and give a minimal-change call.

<results>
<files>
- /home/cube/projects/richard/.worktrees/nutricoach-v150-combined/gateway/platforms/dualcoach_customer_invite.py - invite entrypoint; loads draft, binds operator, issues invite
- /home/cube/projects/richard/.worktrees/nutricoach-v150-combined/gateway/platforms/telegram_customer_bootstrap.py - CustomerDraft schema, draft validation, invite claim, persistence, and activation gate
- /home/cube/projects/richard/.worktrees/nutricoach-v150-combined/dualcoach/profile/checkin_cli/nutrition_onboarding_contract.py - 22-question onboarding field set
- /home/cube/projects/richard/.worktrees/nutricoach-v150-combined/dualcoach/profile/checkin_cli/nutrition_onboarding_finalization.py - onboarding finalization writes readiness artifacts and triggers projection
- /home/cube/projects/richard/.worktrees/nutricoach-v150-combined/dualcoach/profile/checkin_cli/nutrition_onboarding_projection.py - projection/activation receipt generation; overwrites registry profile/plan for disabled customers
- /home/cube/projects/richard/.worktrees/nutricoach-v150-combined/dualcoach/profile/checkin_cli/customer_admin.py - activation cutover; enabled-customer receipt checks and runtime load gating
</files>

<answer>
Current flow:

1) Invite issuance
- `/gateway/platforms/dualcoach_customer_invite.py:137-157` calls `load_customer_draft()` then `prepare_rehearsal_customer_invite()`.
- Draft fields are required by schema/load at `/gateway/platforms/telegram_customer_bootstrap.py:266-295` and parsed at `1753-1811`.
- Required draft fields are: `customer_key`, `display_name`, `starts_on`, `daily_time`, `weekly_weekday`, `monthly_day`, `calories_kcal`, `protein_g`, `meals`.
- `customer_user_id` is optional in the file schema, but invite issuance refuses to proceed unless it is present: `/gateway/platforms/telegram_customer_bootstrap.py:342-369` (`draft.customer_user_id is None -> intended customer identity is required`).
- The invite/session is persisted with the full draft at `/gateway/platforms/telegram_customer_bootstrap.py:1408-1413`.

2) First claim / registration
- Claim is private-DM only and still rejects unbound drafts:
  - `/gateway/platforms/telegram_customer_bootstrap.py:397-409`
  - `/gateway/platforms/telegram_customer_bootstrap.py:412-437`
- On claim, the session moves `PREPARED -> REGISTERING` at `437-438`.
- Activation later requires the same bound identity:
  - `/gateway/platforms/telegram_customer_bootstrap.py:520-579`
  - key checks at `539-549` and `569-579`.
- Session persistence keeps validating draft/digest consistency at `/gateway/platforms/telegram_customer_bootstrap.py:1705-1706`.

3) 22-question onboarding finalization / projection
- The 22-question flow is defined by `/dualcoach/profile/checkin_cli/nutrition_onboarding_contract.py:13-35`.
- Finalization writes readiness artifacts with explicit disabled flags:
  - `/dualcoach/profile/checkin_cli/nutrition_onboarding_finalization.py:343-345` (`delivery_enabled=False`, `activation_enabled=False`)
  - then calls projection at `361-367`.
- Finalization materializes the computed onboarding outputs from the baseline/plan:
  - `/dualcoach/profile/checkin_cli/nutrition_onboarding_finalization.py:358-360`
- Projection only runs against a disabled customer and overwrites the registry customer’s `profile` and `plan`:
  - `/dualcoach/profile/checkin_cli/nutrition_onboarding_projection.py:22-45`
  - `/dualcoach/profile/checkin_cli/nutrition_onboarding_projection.py:49-87`
- Activation receipt hashes the projected `profile` + `plan`:
  - `/dualcoach/profile/checkin_cli/nutrition_onboarding_projection.py:122-149`

4) Activation cutover
- The cutover path in `/dualcoach/profile/checkin_cli/customer_admin.py:886-1124`:
  - refuses if already enabled at `1035-1037`
  - checks KST plan window at `1038-1045`
  - flips a copy to enabled at `1046`
  - validates pilot boundary at `1047-1049`
  - validates local prereqs at `1057-1059`
  - validates enabled customer identity at `1064-1066`
  - requires nutrition readiness at `1077-1079`
  - validates activation data root at `1085-1088`
  - writes back `enabled_spec` into the registry at `1122-1124`
- Enabled customers cannot be loaded without committed activation receipts:
  - `/dualcoach/profile/checkin_cli/customer_admin.py:2399-2496`
  - especially `2431-2433` and `2490-2496`.

Smallest safe change for a first-claim unbound invite:
- Change only the bootstrap side, not activation.
- You need to:
  1. allow `prepare_rehearsal_customer_invite()` to accept `customer_user_id=None` instead of rejecting at `/gateway/platforms/telegram_customer_bootstrap.py:353-360`;
  2. relax `rehearsal_customer_invite_session()` so an unbound prepared invite is still claimable (`407-409`);
  3. in `claim_rehearsal_customer_invite()`, atomically bind the first claimant into `customer_draft.customer_user_id` and refresh `customer_draft_digest` before persisting.
- Leave `activate_bound_customer()` unchanged; it already enforces a concrete bound user and matching claim at `539-579`.

Placeholder coaching values leaking into active service:
- No leak through the normal invite/finalize/activate path.
- The draft placeholders are only used in bootstrap schema/default scaffolding (`/gateway/platforms/telegram_customer_bootstrap.py:1761-1771`), while finalization generates real artifacts and projection refuses enabled customers (`/dualcoach/profile/checkin_cli/nutrition_onboarding_projection.py:41-45`).
- Runtime loading is gated on committed activation receipts for enabled customers (`/dualcoach/profile/checkin_cli/customer_admin.py:2425-2496`).
- So placeholder draft values do not reach active service unless you explicitly change the bootstrap to bind them in and also let them survive finalization, which the current code does not do.
</answer>

<next_steps>
Ready to proceed - no follow-up needed
</next_steps>
</results>