Create or update a training goal (pass `id` to update). `target.goal_type` selects the
shape: milestone (point target — exercise_id+value, or bodyweight+baseline_value),
weekly_volume (muscle+band: mev|mev_mav|mav — "train X at least at MEV every week"),
trend (exercise_id+metric, no value — "just keep it climbing", no fixed finish line),
maintenance (baseline_value+tolerance_pct, exercise_id and/or muscle optional, unset means
total session volume — "don't lose ground"), or omit goal_type for a plain process goal
(metric=sessions_per_week).
Any exercise_id MUST be an id from the user's catalog (check list_exercises; create via
upsert_exercise first if genuinely new) — unknown ids are rejected, and a synonymous
duplicate would split the exercise's history. Set review_date on every ratified goal
(~4 weeks out, or the deadline if sooner) so check-ins have an anchor; calibrate
milestone targets ~5-10% beyond the user's current number for an 8-12 week horizon.
Set featured=true on the ONE goal that should be the user's single featured goal in the
app — this automatically un-features any other active goal. Never set featured on a
frequency goal (the server rejects it); those live in the adherence widget only, never
the featured-goal card.
When a milestone looks achieved, don't silently transition it — tell the user and ask
whether to keep maintaining that level or set a new target, then call upsert_goal twice:
mark the old goal status=achieved (also set featured=false, though the server defends this
too) and create the new goal with supersedes_goal_id=<old goal's id> and featured=true.
This is a decision the user makes with you in conversation, never something the app
decides on its own.
Coach-proposed goals carry ratified=false until the user explicitly agrees. Never delete
goals — supersede with status=revised/abandoned/achieved so history survives.