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 finding the canonical daily-trigger seam for nutrition coaching - I checked startup/shutdown, adapter ownership, recurring-job plumbing, and tests, then narrowed to the cron lifecycle already wired through the gateway.

<results>
<files>
- /home/cube/projects/richard/.worktrees/nutricoach-v111-impl/gateway/run.py - gateway lifecycle: starts/stops cron provider and housekeeping threads in start_gateway(); stops adapters in GatewayRunner.stop().
- /home/cube/projects/richard/.worktrees/nutricoach-v111-impl/cron/scheduler.py - the actual inline-card dispatch seam; run_one_job special-cases nutrition-coaching-tick and routes it through the live Telegram adapter.
- /home/cube/projects/richard/.worktrees/nutricoach-v111-impl/cron/physique_inline_card.py - bridge from cron thread to live adapter loop; nutrition tick uses send_inline_card(card) with no global day claim.
- /home/cube/projects/richard/.worktrees/nutricoach-v111-impl/gateway/platforms/telegram.py - TelegramAdapter owns task cleanup and exposes send_inline_card/_send_nutrition_coaching_tick.
- /home/cube/projects/richard/.worktrees/nutricoach-v111-impl/cron/jobs.py - recurring schedule/next_run_at/claim logic; this is where the daily cron job semantics already live.
- /home/cube/projects/richard/.worktrees/nutricoach-v111-impl/tests/cron/test_physique_inline_card.py - covers nutrition tick via live adapter and the no-global-claim behavior.
- /home/cube/projects/richard/.worktrees/nutricoach-v111-impl/tests/cron/test_run_one_job.py - covers the shared cron execution pipeline.
- /home/cube/projects/richard/.worktrees/nutricoach-v111-impl/tests/cron/test_scheduler_provider.py - covers gateway ticker/provider start-stop lifecycle.
- /home/cube/projects/richard/.worktrees/nutricoach-v111-impl/tests/cron/test_jobs.py - covers recurring schedule parsing and next_run_at advancement.
- /home/cube/projects/richard/.worktrees/nutricoach-v111-impl/tests/gateway/test_telegram_physique_checkin.py - covers _send_nutrition_coaching_tick behavior and idempotence in the adapter.
</files>

<answer>
The smallest canonical seam is the existing cron scheduler lifecycle started by `gateway/run.py:start_gateway()` at `17570-17614`, not a new Telegram adapter task.

Why:
- `start_gateway()` already starts the cron provider thread and stops it with `cron_stop.set()` + `cron_provider.stop()` on shutdown.
- `cron/scheduler.py:2068-2092` already intercepts `inline_card == "nutrition-coaching-tick"` and routes it through `launch_scheduled_card(...)`.
- `cron/physique_inline_card.py:78-127` already bridges that card onto the live gateway loop and `TelegramAdapter.send_inline_card("nutrition-coaching-tick")`.
- `gateway/platforms/telegram.py:9898-9913` already maps the card to `_send_nutrition_coaching_tick()`.

So the minimal change is: add/seed a recurring cron job for `inline_card: "nutrition-coaching-tick"` with a daily schedule in the cron job source, and let the existing gateway-started cron provider fire it. Do not add an adapter-owned repeating task.

Adapter task ownership is already the wrong layer for this:
- `gateway/platforms/telegram.py:7312-7324` and `3262-3310` show Telegram-owned background tasks are created/cleared inside the adapter and torn down on disconnect.
- A daily scheduler should stay in cron, not in adapter lifecycle.

Tests to lean on:
- `tests/cron/test_physique_inline_card.py:54-87, 122-150`
- `tests/cron/test_run_one_job.py:44-57`
- `tests/cron/test_scheduler_provider.py:25-52, 131-153`
- `tests/cron/test_jobs.py:83-90, 147-158, 252-258, 591-625, 636-650, 679-705`
- `tests/gateway/test_telegram_physique_checkin.py:1757-2205`
</answer>

<next_steps>
Use the existing cron job mechanism to create a daily `nutrition-coaching-tick` inline-card job; then validate with the cron scheduler/provider and inline-card tests above.
</next_steps>
</results>


The minimal canonical seam is:

- `gateway/run.py:start_gateway()` at `17570-17614`
- feeding the existing cron path in `cron/scheduler.py:2068-2092`
- which already routes `nutrition-coaching-tick` through `cron/physique_inline_card.py:78-127`
- into `gateway/platforms/telegram.py:9898-9913`

Why this is the seam:
- Gateway startup already owns the cron lifecycle (`cron_provider.start(...)` in a thread).
- Shutdown already stops it cleanly (`cron_stop.set()`, `cron_provider.stop()`, thread joins).
- The nutrition tick already has a live-adapter delivery path; no extra adapter task is needed.
- Telegram adapter background tasks are adapter-owned and torn down on disconnect, so adding a repeating task there would be the wrong lifecycle layer.

Recommended minimal change:
- Add/seed a recurring cron job for `inline_card: "nutrition-coaching-tick"` with a daily schedule.
- Let the existing gateway-started cron provider fire it; do not add a separate script or adapter-owned loop.

Best tests:
- `tests/cron/test_physique_inline_card.py`
- `tests/cron/test_run_one_job.py`
- `tests/cron/test_scheduler_provider.py`
- `tests/cron/test_jobs.py`
- `tests/gateway/test_telegram_physique_checkin.py`