From e3ca3426ba0b74f8275c066f99ffc2f382c849a1 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Mon, 2 Nov 2026 08:56:37 -0500 Subject: [PATCH] fix(auth): emit default_audio_speed unconditionally on the profile response TestDocsMatchRoutes failed on users/get_profile.md: the example carries default_audio_speed but the response struct tagged it omitempty, so the gate's zero-value marshal never sees the key and reads the doc as inventing it. The tag was semantically dead: the column is NOT NULL DEFAULT 1.0 (schema.sql 44) and writes validate 0.25-4, so a stored 0 is out of domain and every real profile emits the field. Response-only struct, no form-bind impact. The key was added to the doc example in ef827a3a without a gate run - this is exactly the class of drift the gate exists to catch; make test now passes again. --- internal/handlers/auth.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/handlers/auth.go b/internal/handlers/auth.go index 0a702213..ce929b4e 100644 --- a/internal/handlers/auth.go +++ b/internal/handlers/auth.go @@ -124,8 +124,10 @@ type UserProfile struct { Role string `json:"role"` Theme string `json:"theme,omitempty"` // User-level default audiobook playback speed (plan §8); 0 omits. - DefaultAudioSpeed float64 `json:"default_audio_speed,omitempty"` - // Whether the web reader/player should hold a Screen Wake Lock. + // Response-only struct: omit nothing — default_audio_speed is NOT NULL + // DEFAULT 1.0 (writes validate 0.25–4), so a real profile always emits + // it; omitempty here would drop a contract field from the docs gate. + DefaultAudioSpeed float64 `json:"default_audio_speed"` // Whether the web reader/player should hold a Screen Wake Lock. KeepScreenAwake bool `json:"keep_screen_awake"` // Resume rewind: seconds before the saved position the audio // player starts (open-resume and every genuine unpause). 0 is a