docs: record the compatibility-window work in build-status
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
33a6acb0bf
commit
8166611af1
@@ -621,3 +621,42 @@ finding code and the metrics survive, then deleted it.
|
|||||||
- Accounts/OIDC on the server, which is what `uploads=account` is waiting for.
|
- Accounts/OIDC on the server, which is what `uploads=account` is waiting for.
|
||||||
- Nothing in this entry has been exercised on a phone yet — all of it was verified from the PC
|
- Nothing in this entry has been exercised on a phone yet — all of it was verified from the PC
|
||||||
against the live server. On-device verification is the next step.
|
against the live server. On-device verification is the next step.
|
||||||
|
|
||||||
|
### SemVer compatibility windows between app and server (server-v0.5.0 … v0.5.2, 2026-08-01)
|
||||||
|
Both artifacts are SemVer, and each now declares — and enforces — which peer versions it will talk
|
||||||
|
to. Spec: `docs/probe-protocol.md` §8.
|
||||||
|
|
||||||
|
**Two axes, deliberately not conflated.** Release versions are a *proxy* for what actually has to
|
||||||
|
match, so the real thing is checked first:
|
||||||
|
- `protocol_version` — **can** these builds talk. Advertised in the profile; a peer in a different
|
||||||
|
breaking series is refused whatever its release version says. Below 1.0.0 the **minor** is the
|
||||||
|
breaking axis (SemVer §4).
|
||||||
|
- release-version window — **may** they, per policy. `[min, max)`, min inclusive, max exclusive,
|
||||||
|
because the useful bound is always "the version that broke it".
|
||||||
|
|
||||||
|
Bounds sit at breaking boundaries, not at releases, so shipping a patch never requires editing a
|
||||||
|
range. The app requires server `>= 0.4.2` for a stated reason, not caution: earlier multi-homed
|
||||||
|
servers mis-addressed granted sends and the client measured 100 % downstream loss that never
|
||||||
|
happened. Operators override the server side with `ECHOLOT_MIN_APP_VERSION` /
|
||||||
|
`ECHOLOT_MAX_APP_VERSION`; a malformed bound is fatal at startup rather than ignored, so a typo
|
||||||
|
cannot silently disable a restriction.
|
||||||
|
|
||||||
|
Three rules that shaped the implementation:
|
||||||
|
1. **`GET /v1/profile` is never gated.** It is where a refused client learns which version it needs;
|
||||||
|
gating it leaves the user with a network error instead of an answer.
|
||||||
|
2. **An unparseable or absent version is `unknown`, and is allowed.** Dev builds report `dev`, and a
|
||||||
|
client too old to send the header cannot be identified anyway.
|
||||||
|
3. **Refusal is 426 with a body naming both versions and the window**, surfaced client-side as a
|
||||||
|
distinct `VersionRefused` rather than folded into "network error".
|
||||||
|
|
||||||
|
The app's `versionCode` is now derived from its SemVer (`major*1e6 + minor*1e4 + patch*10`) instead
|
||||||
|
of being a second number to remember.
|
||||||
|
|
||||||
|
Verified live against fmr (`LiveCompatTest`): profile advertises the window and stays readable for a
|
||||||
|
refused version; 0.1.0 and 99.0.0 are both refused with actionable messages; 0.2.0 and a missing
|
||||||
|
header are both served.
|
||||||
|
|
||||||
|
One user-visible bug caught in the process: Go's JSON encoder HTML-escapes `<`, `>` and `&` by
|
||||||
|
default, so the refusal reached the client as `needs \u003e= 0.2.0`. Disabled at the encoder (this
|
||||||
|
is an API, not a page), and the client now *parses* the error field instead of pattern-matching it,
|
||||||
|
so it survives whatever a future encoder decides to escape.
|
||||||
|
|||||||
Reference in New Issue
Block a user