Proto Wire Unit Clarity
Summary
A pre-cutover sweep that puts the unit in the field name for every wire scalar
that carries one, while there are zero users and breaking the wire is free.
Concretely: rename time → time_seconds and par_time → par_time_seconds,
resolve the ambiguous penalties unit, and scan the rest of proto/** for other
implicit-unit / ambiguous scalars. Breaking JSON-name changes are accepted per the
pre-cutover premise; the change ships as one coordinated server + all-three-client
release and buf breaking is waived. After cutover this window closes — a bare
double time becomes an un-fixable, silently-misread hazard once field clients
rely on it.
Context
The client semantic-exposure map’s flagship uncatchable hazard is a wire scalar
whose unit lives only in a developer’s head: Score.time is a bare double with
no unit on the field, so every hand-mirror (web libs/api-client, iOS, Android)
and the on-device ParTimer must remember it is seconds. A rename can’t be
caught later by any tolerance mechanism — Behavior B guards unknown enum values,
not misread units — so the only real fix is to name the unit at the source, and
the only cheap moment to do it is now, pre-cutover, when a breaking rename costs a
coordinated release and nothing else.
This is orthogonal to the tolerance work: “fix the wire shape now” (this item) and “tolerate unknown values later” (Behavior B, calc-method-core-dispatch) are two timescales that both hold.
Details
time→time_seconds(Scoreand any request/response carrying it). Baredouble, always seconds; the unit moves into the name forever.par_time→par_time_seconds(scoring profile / drill par time + theParTimer). Same treatment.penalties— review and resolve. TimePlus-only scalar (see the “penalties is TimePlus-only” note); its unit is ambiguous on the wire — penalty seconds added to the raw time, or a count of penalties? Confirm against the TimePlus dispatcher, then either rename topenalty_seconds(if seconds) or document it as a unitless count (and considerpenalty_count).- Scan
proto/**for the rest: every bare numeric scalar whose unit/meaning is implicit (durations, delays/intervals on theParTimer, distances, timeouts, sizes). Each is either renamed to carry its unit or explicitly documented as unitless. - Carry each rename end-to-end:
proto/**(source of truth),libs/coreconversions (From/TryFrom), and all three hand-mirrors (weblibs/api-client, iOS decoders, Android decoders), plus theParTimer. No compatibility shim — the old names are removed, not aliased.
Release shape: one coordinated server + all-3-client release (server and every
client speak the new names together). buf breaking will flag the renames; we
waive it (recorded here), since there are no consumers to protect pre-cutover.
Requirements
- Every wire scalar that carries a physical unit names that unit in the field (e.g.
_seconds); no baretime/par_timeremain on the wire. time→time_secondsandpar_time→par_time_secondsare renamed acrossproto/**,libs/coreconversions, all three hand-mirrors, and theParTimer.- The
penaltiesunit is confirmed and the field is either renamed to carry its unit or documented as unitless. - A recorded scan of
proto/**enumerates every remaining implicit-unit / ambiguous scalar; each is renamed or explicitly documented. - Ships as a single coordinated server + all-3-client release with no compat shim;
the
buf breakingwaiver is recorded. - Tests: core proto round-trip uses the new field names; a scoring round-trip asserts the seconds semantics survive core ←> proto.
Scope / non-goals
- In: the unit-clarity renames + the
penaltiesresolution + the proto-wide implicit-unit scan + carrying it through core, server, all hand-mirrors, and theParTimer. - Out: any enum-value tolerance (that is calc-method-core-dispatch / Behavior B); non-unit proto restructures; post-cutover renames (this window is explicitly pre-cutover — after cutover, additive-and-tolerate takes over).
Open Questions
penalties: penalty seconds (→penalty_seconds) or a count of penalties (→ keep numeric, document unitless /penalty_count)? Resolve against the TimePlus dispatcher before renaming.- Does the
ParTimer’s random-delay / interval field carry an implicit-seconds unit that should join this sweep, or is it already explicit?