Client-writable profile fields.
athlete_context is absent by design and model_config forbids extra keys, so a
client cannot write the living log through this endpoint even by sending the column
name — the request is rejected with a 422 instead of silently ignored. id and
created_at are likewise not writable; ownership comes from the JWT.
debug_enabled is absent for the same reason: it is server-owned (flipped in the
Supabase dashboard), so a user cannot enable their own debug mode through this API.
Writes use exclude_unset=True, so an omitted field is left untouched rather than
nulled, and a nullable field can be cleared by sending an explicit null.
Nullability here is stricter than uniformly-optional: onboarding_completed,
equipment, and skipped_concierge are NOT NULL in the database, so they are
declared non-nullable and an explicit null is a 422. Typing them as | None instead
let the null reach Postgres, which answered with a not-null violation that surfaced as
a 500 and a Sentry alert.
sports is the exception: it is NULL-able in Postgres, so a null there is accepted
by the database rather than rejected. It is still non-nullable here, because iOS
decodes sports as a non-optional [String] — a stored NULL would fail validation on
the next GET /me and brick launch for that user with nothing the client can do.
Profile coerces such a row back to [] on read; this keeps one from being created.