v1.1.0 release checklist #59

Closed
opened 2026-08-20 13:26:59 +00:00 by yuris · 4 comments
Owner

v1.1.0 release checklist

Shipped: v1.1.0 = 46b433e, published. Remaining unchecked items need the
maintainer, not an agent.

v1.0.0 froze the public API. Everything here is additive or internal; the one
signature change (backoff_delay) gained a trailing optional parameter, so no
callsite broke.

Worked the same way #24 was: one issue per unit of work, each through
branch → PR → independent review → actioner → merge, with reviewers instructed
to re-capture the wire rather than trust the author.

Phase 1 — harness first

  • #54 dev-server state collision. Default state dir derives from $PORT
    so pidfiles and data cannot collide. server_pid() verifies the recorded
    pid is our server for this port — by reading its argv, not by asking who
    holds the port, after review found the ss | grep -q version returned
    failure on a match under pipefail. Landed as #60.

Phase 2 — independent, parallel

  • #19 reconnect jitter. Deterministic per-client spread derived from the
    identity, NOT math.random. Landed as #62. Review measured the
    distribution and disproved two of the claims made for it.
  • #20 HTML5 token durability. Answered from engine source, and it was
    neither outcome the issue anticipated: sys.save does not sync, but the
    loader patches FS.close so every save starts one anyway. Landed as #61
    after three review rounds on factual accuracy.

Phase 3 — the headline feature

  • #31 auth token exchange. stdb/auth.lua plus the SDK's first http
    seam. Landed as #64. Two blocking defects found first: a storage seam that
    made the documented configuration silently useless, and a circular
    require that bob refuses to build.

Phase 4 — measure before building

  • #30 frame budget. Measured, not guessed: 60fps is exceeded at ~6,100
    rows native, 100ms at ~31,000 (~9,800 on HTML5). Landed as #66.
    Time-slicing is justified and is now #67.
  • Posted the codec benchmark to #9. The first figure I published there
    was wrong by ~4.5× — the instrumentation had missed quote_big_ints — and
    is retracted and corrected in place.

Phase 5 — release gate

  • API surface review of the new surface — found four names worth changing
    before they froze, landed as #68
  • README behavioural truth check on new claims, verified by execution —
    found ten wrong, all corrected
  • Consumer archive verified against the live forge artifact, then re-verified
    against the tag's own artifact plus a real consumer install
  • Version bump, install URLs, annotated tag (the annotation is the changelog)

Needs the maintainer, not an agent

  • #15 upstream PR to defold/extension-websocket — needs a GitHub account.
    The patch and its handshake evidence are paste-ready in patches/.
  • #16 drop continue-on-error once weekly HTML5 runs have been green.
    Not automatable; it is a judgement on accumulated CI history — and #52 saw
    a possible recurrence during this release gate, so the case for keeping it
    has not weakened.

Opened by this milestone, deliberately not done

  • #69 quote_big_ints is unguarded and costs ~40% of the apply frame. The
    cheapest real optimisation found, and it should land before #67.
  • #67 time-slice the applying frame.
  • #65 a mass cold start still retries in lockstep — the spread needs an
    identity, so a fleet relaunching into an outage is not covered. Arguably the
    more important herd.
  • #63 two ports sharing one explicit STDB_STATE_DIR can leak a server.

Deliberately untouched

#52 — one unreproducible occurrence, six mechanisms already disproved and
recorded. A possible recurrence during this release gate is recorded there, with
the frame capture it asks for explicitly noted as missing.

# v1.1.0 release checklist **Shipped: `v1.1.0` = `46b433e`, published.** Remaining unchecked items need the maintainer, not an agent. v1.0.0 froze the public API. Everything here is additive or internal; the one signature change (`backoff_delay`) gained a trailing optional parameter, so no callsite broke. Worked the same way #24 was: one issue per unit of work, each through branch → PR → independent review → actioner → merge, with reviewers instructed to re-capture the wire rather than trust the author. ## Phase 1 — harness first - [x] **#54** dev-server state collision. Default state dir derives from `$PORT` so pidfiles and data cannot collide. `server_pid()` verifies the recorded pid is our server for this port — by reading its argv, not by asking who holds the port, after review found the `ss | grep -q` version returned failure on a *match* under `pipefail`. Landed as #60. ## Phase 2 — independent, parallel - [x] **#19** reconnect jitter. Deterministic per-client spread derived from the identity, NOT `math.random`. Landed as #62. Review measured the distribution and disproved two of the claims made for it. - [x] **#20** HTML5 token durability. Answered from engine source, and it was neither outcome the issue anticipated: `sys.save` does not sync, but the loader patches `FS.close` so every save starts one anyway. Landed as #61 after three review rounds on factual accuracy. ## Phase 3 — the headline feature - [x] **#31** auth token exchange. `stdb/auth.lua` plus the SDK's first `http` seam. Landed as #64. Two blocking defects found first: a storage seam that made the documented configuration silently useless, and a circular `require` that `bob` refuses to build. ## Phase 4 — measure before building - [x] **#30** frame budget. Measured, not guessed: 60fps is exceeded at ~6,100 rows native, 100ms at ~31,000 (~9,800 on HTML5). Landed as #66. Time-slicing is justified and is now **#67**. - [x] Posted the codec benchmark to **#9**. The first figure I published there was wrong by ~4.5× — the instrumentation had missed `quote_big_ints` — and is retracted and corrected in place. ## Phase 5 — release gate - [x] API surface review of the new surface — found four names worth changing before they froze, landed as #68 - [x] README behavioural truth check on new claims, verified by execution — found ten wrong, all corrected - [x] Consumer archive verified against the live forge artifact, then re-verified against the tag's own artifact plus a real consumer install - [x] Version bump, install URLs, annotated tag (the annotation is the changelog) ## Needs the maintainer, not an agent - [ ] **#15** upstream PR to `defold/extension-websocket` — needs a GitHub account. The patch and its handshake evidence are paste-ready in `patches/`. - [ ] **#16** drop `continue-on-error` once weekly HTML5 runs have been green. Not automatable; it is a judgement on accumulated CI history — and #52 saw a possible recurrence during this release gate, so the case for keeping it has not weakened. ## Opened by this milestone, deliberately not done - **#69** `quote_big_ints` is unguarded and costs ~40% of the apply frame. The cheapest real optimisation found, and it should land before #67. - **#67** time-slice the applying frame. - **#65** a mass cold start still retries in lockstep — the spread needs an identity, so a fleet relaunching into an outage is not covered. Arguably the more important herd. - **#63** two ports sharing one explicit `STDB_STATE_DIR` can leak a server. ## Deliberately untouched **#52** — one unreproducible occurrence, six mechanisms already disproved and recorded. A possible recurrence during this release gate is recorded there, with the frame capture it asks for explicitly noted as missing.
Author
Owner

Progress: phase 1 done, phase 2 half done

main is at cf8b430, green end to end: unit 6/6, stylua 0, shellcheck 0,
native conformance PASS, HTML5 conformance PASS.

Phase 1 — #54 merged (#60). Default state dir is now keyed on $PORT, and
server_pid() identifies our server by reading /proc/$pid/cmdline rather than
by asking who holds the port.

Both failures were reproduced on main before fixing, and review earned its
keep twice here. Round one found the first attempt BLOCKING: ss -ltnp | grep -q
under set -o pipefail returns 141 for a pipeline that matched, because
grep -q exits early and ss dies of SIGPIPE — so a healthy server read as
absent. This script already documents that exact trap elsewhere. Round one also
disproved the justification I had written, that ss was already used here; it was
not, and the claim came from the plan and was repeated unchecked. Replacing the
mechanism with an argv read dissolved three of the five findings at once. Round
two then attacked the replacement specifically and cleared it.

Phase 2 — #19 merged (#62). Deterministic per-client spread, not
math.random. Live on the wire: conformance logs reconnecting attempt 1 in 0.608s where main was a flat 1s.

Review measured the distribution rather than trusting the two-sample example in
my PR body, and disproved two claims I had made:

  • "every client lands on a different slot" is false above a few dozen clients —
    there are exactly 1000 possible outputs, so 365 of 1000 clients collide, which
    is precisely the birthday bound. Behaviourally fine (15ms apart at the cap, not
    simultaneous); the sentence overreached.
  • "re-spreads each attempt" implied an independent re-draw. The fold is affine in
    the attempt seed, so the cohort is translated by one constant —
    h(id,2)-h(id,1) measured as the single value 2807132593 across 400 real
    identities, with 57% of colliding pairs still colliding at the next attempt.

Both corrected before merge, since phase 5's README-truth gate would have failed
them anyway.

Phase 2 — #20 open as #61, documentation only, in re-review. The engine-source
answer was neither of the two outcomes the issue anticipated: sys.save does not
sync, but the HTML5 loader patches FS.close so every save starts one anyway —
so the proposed flush would be redundant, and deleting the caveat would erase a
real race. Review then disproved two of my own claims about the loader (the save
direction does not retry; the breaker can wedge persistence permanently), so the
text now names the silent-loss case outright.

Phase 3 — #31 open as #64, in review. Spike settled branch C: 2.8.2 has no
exchange endpoint (/v1/identity/token, /v1/token, /v1/auth,
/v1/identity/exchange all 404) and only a single JWT keypair, no issuer or JWKS
option — so the exchange target is the game's own backend.

Phase 4 — #30 in progress, measurement running.

Filed along the way

  • #63 two ports sharing one explicit STDB_STATE_DIR leak a server. Found in
    review of #60; unreachable by default after it.
  • #65 a mass cold start still retries in lockstep. Found in review of #62, and
    arguably the more important herd: #19 covers a server restart that drops live
    sockets, but not an outage that makes the whole player base relaunch. The
    identity is already on disk three lines above the callsite, but it is not
    server-confirmed there, so spreading on it is a real judgement call rather than
    an oversight.

Also recorded, not pursued: a malformed bearer token panics a tokio worker in
2.8.2 (spacetimedb-jsonwebtoken-9.3.0/src/decoding.rs:199, "entered unreachable
code") and takes the server process down. Upstream, in a pinned dependency.

## Progress: phase 1 done, phase 2 half done `main` is at `cf8b430`, green end to end: unit 6/6, stylua 0, shellcheck 0, native conformance PASS, HTML5 conformance PASS. **Phase 1 — #54 merged** (#60). Default state dir is now keyed on `$PORT`, and `server_pid()` identifies our server by reading `/proc/$pid/cmdline` rather than by asking who holds the port. Both failures were reproduced on `main` before fixing, and review earned its keep twice here. Round one found the first attempt BLOCKING: `ss -ltnp | grep -q` under `set -o pipefail` returns 141 for a pipeline that *matched*, because `grep -q` exits early and `ss` dies of SIGPIPE — so a healthy server read as absent. This script already documents that exact trap elsewhere. Round one also disproved the justification I had written, that `ss` was already used here; it was not, and the claim came from the plan and was repeated unchecked. Replacing the mechanism with an argv read dissolved three of the five findings at once. Round two then attacked the replacement specifically and cleared it. **Phase 2 — #19 merged** (#62). Deterministic per-client spread, not `math.random`. Live on the wire: conformance logs `reconnecting attempt 1 in 0.608s` where `main` was a flat `1s`. Review measured the distribution rather than trusting the two-sample example in my PR body, and disproved two claims I had made: - "every client lands on a different slot" is false above a few dozen clients — there are exactly 1000 possible outputs, so 365 of 1000 clients collide, which is precisely the birthday bound. Behaviourally fine (15ms apart at the cap, not simultaneous); the sentence overreached. - "re-spreads each attempt" implied an independent re-draw. The fold is affine in the attempt seed, so the cohort is *translated* by one constant — `h(id,2)-h(id,1)` measured as the single value 2807132593 across 400 real identities, with 57% of colliding pairs still colliding at the next attempt. Both corrected before merge, since phase 5's README-truth gate would have failed them anyway. **Phase 2 — #20 open** as #61, documentation only, in re-review. The engine-source answer was neither of the two outcomes the issue anticipated: `sys.save` does not sync, but the HTML5 loader patches `FS.close` so every save starts one anyway — so the proposed flush would be redundant, *and* deleting the caveat would erase a real race. Review then disproved two of my own claims about the loader (the save direction does not retry; the breaker can wedge persistence permanently), so the text now names the silent-loss case outright. **Phase 3 — #31 open** as #64, in review. Spike settled branch C: 2.8.2 has no exchange endpoint (`/v1/identity/token`, `/v1/token`, `/v1/auth`, `/v1/identity/exchange` all 404) and only a single JWT keypair, no issuer or JWKS option — so the exchange target is the game's own backend. **Phase 4 — #30 in progress**, measurement running. ## Filed along the way - **#63** two ports sharing one explicit `STDB_STATE_DIR` leak a server. Found in review of #60; unreachable by default after it. - **#65** a mass cold start still retries in lockstep. Found in review of #62, and arguably the more important herd: #19 covers a server restart that drops live sockets, but not an outage that makes the whole player base relaunch. The identity is already on disk three lines above the callsite, but it is not server-confirmed there, so spreading on it is a real judgement call rather than an oversight. Also recorded, not pursued: a malformed bearer token panics a tokio worker in 2.8.2 (`spacetimedb-jsonwebtoken-9.3.0/src/decoding.rs:199`, "entered unreachable code") and takes the server process down. Upstream, in a pinned dependency.
Author
Owner

Progress: phases 1–4 done, phase 5 in flight

main is at 8d8bc2c. Four of the five milestone issues are merged and the
frame-budget measurement has landed.

  • #54 merged (#60) — dev-server state per port, server identified by argv
  • #19 merged (#62) — deterministic per-client reconnect spread
  • #20 merged (#61) — HTML5 persistence documented from engine source
  • #31 merged (#64) — stdb/auth.lua, auth.exchange, the SDK's first HTTP seam
  • #30 measured (#66) — PHASE6, a five-bucket frame split; time-slicing tracked as #67

Phase 4's answer

Time-slicing is justified: the 60fps budget is exceeded at ~6,100 rows native
and 100ms at ~31,000 (~9,800 on HTML5, where an idle wasm frame already costs
17ms to requestAnimationFrame). All three swept across the crossing rather than
extrapolated. #67 carries the implementation, with the ordering property — a
TransactionUpdate arriving mid-batch must be applied after it, never interleaved
— as its acceptance criterion.

The measurement corrected a conclusion I had already published

I posted a BSATN benchmark to #9 concluding the Amdahl ceiling was 1.8× native /
1.3× wasm. Review found the instrumentation stopped one function short:
quote_big_ints was in the bucket I had called untouchable. It is not — it exists
only because JSON cannot carry a u128 exactly, so a codec swap deletes it.

Re-measured with five buckets summing to the frame: at 10k rows native, decode
14.8ms and quoting 12.6ms of a 31.0ms frame; at 50k, quoting (77.5ms) costs
more than the JSON parser (76.6ms). Corrected ceiling: ~8.5× native, and
only 1.7× for a wasm frame — because there the rAF floor and socket receipt
are another 45% that no decoder touches. My published figure understated native by
~4.5×. Retracted and corrected on #9.

And the measurement found something better than either answer — #69

quote_big_ints has no guard, while its sibling quote_wide_ints has a
necessary-condition guard and short-circuits. On a table with no 128-bit column
the hand-written path scans the whole frame and every row to rewrite nothing:
40–44% of the native frame, for one plain find. The evidence was hiding in plain
sight — going typed halves the quote bucket purely because the guarded function
replaces the unguarded one.

This is the cheapest real optimisation the milestone turned up, it takes much of
BSATN's ground with no protocol surface re-opened, and it should land before #67
since it halves what time-slicing has to spread.

Phase 5 status

  • Consumer archive verified against the live forge artifact: stdb/auth.lua
    present, no dev/ leakage, every module resolves with package.path
    restricted to the archive alone, and both README archive figures re-measure
    correctly.
  • API surface review of the new surface — found four names worth changing
    before they freeze, now open as #68: auth.exchange's token option →
    credential (it collided with five frozen uses of token, and the
    implementation already renamed it internally); the http_seam callback drops
    err for status == 0 (its err was discarded by its only consumer while
    inverting the SDK-wide (err, ...) rule — a seam written in house style
    turned a 401 into four requests); the auth cache nests under an auth key
    (a flat provider map shared a namespace with connect's record, and
    provider = "token" returned the identity token); and
    spread_for → backoff_spread. #68 also makes connect's save merge, which
    closes the third collision direction nesting alone could not.
  • README behavioural truth on new claims — running
  • Version bump, install URLs, annotated tag — last

Also filed this milestone

  • #63 two ports sharing one explicit STDB_STATE_DIR leak a server
  • #65 a mass cold start still retries in lockstep — arguably the more important
    herd, and #19 does not cover it
  • #67 time-slice the applying frame
  • #69 guard quote_big_ints

Recorded, not pursued: a malformed bearer token panics a tokio worker in 2.8.2
(spacetimedb-jsonwebtoken-9.3.0/src/decoding.rs:199) and takes the server down.

Worth stating about the process

Every single review round found something real, and the majority of the findings
corrected work I had done or claims I had made — including two published figures,
an inverted engine-API claim, and a measurement that missed 40% of the frame. Two
defects were only ever going to be caught by running the harness rather than the
unit tests: a circular require that bob refuses to build, and a storage seam
that made the documented configuration silently useless.

## Progress: phases 1–4 done, phase 5 in flight `main` is at `8d8bc2c`. Four of the five milestone issues are merged and the frame-budget measurement has landed. - **#54** merged (#60) — dev-server state per port, server identified by argv - **#19** merged (#62) — deterministic per-client reconnect spread - **#20** merged (#61) — HTML5 persistence documented from engine source - **#31** merged (#64) — `stdb/auth.lua`, `auth.exchange`, the SDK's first HTTP seam - **#30** measured (#66) — PHASE6, a five-bucket frame split; time-slicing tracked as #67 ## Phase 4's answer Time-slicing is justified: the 60fps budget is exceeded at **~6,100 rows** native and 100ms at **~31,000** (~9,800 on HTML5, where an idle wasm frame already costs 17ms to `requestAnimationFrame`). All three swept across the crossing rather than extrapolated. #67 carries the implementation, with the ordering property — a `TransactionUpdate` arriving mid-batch must be applied after it, never interleaved — as its acceptance criterion. ## The measurement corrected a conclusion I had already published I posted a BSATN benchmark to #9 concluding the Amdahl ceiling was 1.8× native / 1.3× wasm. Review found the instrumentation stopped one function short: `quote_big_ints` was in the bucket I had called untouchable. It is not — it exists only because JSON cannot carry a u128 exactly, so a codec swap deletes it. Re-measured with five buckets summing to the frame: at 10k rows native, `decode` 14.8ms and `quoting` 12.6ms of a 31.0ms frame; at 50k, quoting (77.5ms) costs **more than the JSON parser** (76.6ms). Corrected ceiling: **~8.5× native**, and only **1.7×** for a wasm *frame* — because there the rAF floor and socket receipt are another 45% that no decoder touches. My published figure understated native by ~4.5×. Retracted and corrected on #9. ## And the measurement found something better than either answer — #69 `quote_big_ints` has **no guard**, while its sibling `quote_wide_ints` has a necessary-condition guard and short-circuits. On a table with no 128-bit column the hand-written path scans the whole frame *and* every row to rewrite nothing: 40–44% of the native frame, for one plain `find`. The evidence was hiding in plain sight — going typed halves the quote bucket purely because the guarded function replaces the unguarded one. This is the cheapest real optimisation the milestone turned up, it takes much of BSATN's ground with no protocol surface re-opened, and it should land before #67 since it halves what time-slicing has to spread. ## Phase 5 status - [x] **Consumer archive** verified against the live forge artifact: `stdb/auth.lua` present, no `dev/` leakage, every module resolves with `package.path` restricted to the archive alone, and both README archive figures re-measure correctly. - [x] **API surface review** of the new surface — found four names worth changing before they freeze, now open as **#68**: `auth.exchange`'s `token` option → `credential` (it collided with five frozen uses of `token`, and the implementation already renamed it internally); the `http_seam` callback drops `err` for `status == 0` (its `err` was discarded by its only consumer while inverting the SDK-wide `(err, ...)` rule — a seam written in house style turned a 401 into four requests); the auth cache nests under an `auth` key (a flat provider map shared a namespace with `connect`'s record, and `provider = "token"` returned the identity token); and `spread_for` → `backoff_spread`. #68 also makes `connect`'s save merge, which closes the third collision direction nesting alone could not. - [ ] **README behavioural truth** on new claims — running - [ ] **Version bump, install URLs, annotated tag** — last ## Also filed this milestone - **#63** two ports sharing one explicit `STDB_STATE_DIR` leak a server - **#65** a mass cold start still retries in lockstep — arguably the more important herd, and #19 does not cover it - **#67** time-slice the applying frame - **#69** guard `quote_big_ints` Recorded, not pursued: a malformed bearer token panics a tokio worker in 2.8.2 (`spacetimedb-jsonwebtoken-9.3.0/src/decoding.rs:199`) and takes the server down. ## Worth stating about the process Every single review round found something real, and the majority of the findings corrected work I had done or claims I had made — including two published figures, an inverted engine-API claim, and a measurement that missed 40% of the frame. Two defects were only ever going to be caught by running the harness rather than the unit tests: a circular `require` that `bob` refuses to build, and a storage seam that made the documented configuration silently useless.
Author
Owner

v1.1.0 is tagged and published

main at a606406, tag v1.1.0 = 46b433e, release published with the tag
annotation as its body.

Verified against the live tag artifact, not a local git archive: 13 entries
(the 12 shipped files plus the forge's own path prefix, exactly as the README now
documents), stdb/auth.lua present, version = 1.1.0 shipped, zero dev/
leakage. Then a real consumer install — extract the zip, set package.path to it
alone, require all seven modules — resolves cleanly, and the new surface
behaves: auth.exchange, http_seam, backoff_spread, and backoff_delay's
fourth parameter both spreading and no-opping on nil.

Phase 5

  • API surface review of the new surface — landed as #68
  • README behavioural truth check on new claims, verified by execution
  • Consumer archive verified against the live forge artifact
  • Version bump, install URLs, annotated tag

What shipped

  • #54 dev-server state per port; server_pid() identifies our server from its argv
  • #19 deterministic per-client reconnect spread, keyed on identity, no RNG
  • #20 HTML5 identity persistence documented from engine source
  • #31 stdb/auth.lua, auth.exchange, the SDK's first HTTP seam
  • #30 measured; the applying frame is now instrumented as five buckets

Two behaviour changes worth repeating, both invisible to any released consumer
because the surfaces involved were added in this same release: sys_storage's
load no longer filters on .token (which had made the documented auth storage
configuration silently useless), and connect's identity save merges into its
slot instead of rewriting it.

Left open, deliberately

  • #63 two ports sharing one explicit STDB_STATE_DIR can leak a server
  • #65 a mass cold start still retries in lockstep — the spread needs an
    identity, so a fleet relaunching into an outage is not covered. Arguably the
    more important herd
  • #67 time-slice the applying frame — justified by the measurement
  • #69 guard quote_big_ints — the cheapest real optimisation found, and it
    should land before #67 since it halves what time-slicing has to spread
  • #9 stays closed for this release, now on honest ground: the corrected
    ceiling is ~6–8× native, not the 1.8× I first published
  • #52 one unexplained conformance observation, with a possible recurrence
    recorded during this release gate that I could not attribute
  • #15 and #16 still need the maintainer: the upstream PR needs a GitHub
    account, and dropping continue-on-error is a judgement on accumulated CI
    history

On the process

Every review round found something real, and most findings corrected work I had
done or claims I had made. The ones that mattered most:

  • a pipefail defect that made a healthy server read as absent, in a script that
    already documented that exact trap
  • a circular require that bob refuses to build, invisible to every unit test —
    it reached review only because I told the implementer conformance was unnecessary
  • a storage seam that made the documented configuration silently useless, invisible
    because the test double modelled a seam the SDK does not ship
  • a measurement that missed 40% of the frame, which had already produced a
    published figure wrong by ~4.5× on #9
  • a test assertion that was structurally vacuous, letting a credential-carrying
    header pass
  • two README claims contradicted by measurement, and one engine-API claim I had
    inverted while fixing an earlier error

Three lessons worth keeping, all learned by nearly missing something:

  1. Grep the test summary, never tail it. This runner prints a crash before
    its per-file ok lines, so tail -1 showed a false green on a suite that was
    not running.
  2. Adding a file to stdb/ requires conformance, not just unit tests. The
    harness is the only thing that compiles the bundle.
  3. A hedge about machines does not excuse a number that misses on the same
    silicon
    — which is how three frame-budget figures survived until the truth
    gate re-ran them.
## v1.1.0 is tagged and published `main` at `a606406`, tag `v1.1.0` = `46b433e`, release published with the tag annotation as its body. **Verified against the live tag artifact, not a local `git archive`:** 13 entries (the 12 shipped files plus the forge's own path prefix, exactly as the README now documents), `stdb/auth.lua` present, `version = 1.1.0` shipped, zero `dev/` leakage. Then a real consumer install — extract the zip, set `package.path` to it alone, `require` all seven modules — resolves cleanly, and the new surface behaves: `auth.exchange`, `http_seam`, `backoff_spread`, and `backoff_delay`'s fourth parameter both spreading and no-opping on `nil`. ### Phase 5 - [x] API surface review of the new surface — landed as #68 - [x] README behavioural truth check on new claims, verified by execution - [x] Consumer archive verified against the live forge artifact - [x] Version bump, install URLs, annotated tag ## What shipped - **#54** dev-server state per port; `server_pid()` identifies our server from its argv - **#19** deterministic per-client reconnect spread, keyed on identity, no RNG - **#20** HTML5 identity persistence documented from engine source - **#31** `stdb/auth.lua`, `auth.exchange`, the SDK's first HTTP seam - **#30** measured; the applying frame is now instrumented as five buckets Two behaviour changes worth repeating, both invisible to any released consumer because the surfaces involved were added in this same release: `sys_storage`'s `load` no longer filters on `.token` (which had made the documented auth storage configuration silently useless), and `connect`'s identity save merges into its slot instead of rewriting it. ## Left open, deliberately - **#63** two ports sharing one explicit `STDB_STATE_DIR` can leak a server - **#65** a mass cold start still retries in lockstep — the spread needs an identity, so a fleet relaunching into an outage is not covered. Arguably the more important herd - **#67** time-slice the applying frame — justified by the measurement - **#69** guard `quote_big_ints` — the cheapest real optimisation found, and it should land before #67 since it halves what time-slicing has to spread - **#9** stays closed for this release, now on honest ground: the corrected ceiling is ~6–8× native, not the 1.8× I first published - **#52** one unexplained conformance observation, with a possible recurrence recorded during this release gate that I could not attribute - **#15** and **#16** still need the maintainer: the upstream PR needs a GitHub account, and dropping `continue-on-error` is a judgement on accumulated CI history ## On the process Every review round found something real, and most findings corrected work I had done or claims I had made. The ones that mattered most: - a `pipefail` defect that made a healthy server read as absent, in a script that already documented that exact trap - a circular `require` that `bob` refuses to build, invisible to every unit test — it reached review only because I told the implementer conformance was unnecessary - a storage seam that made the documented configuration silently useless, invisible because the test double modelled a seam the SDK does not ship - a measurement that missed 40% of the frame, which had already produced a published figure wrong by ~4.5× on #9 - a test assertion that was structurally vacuous, letting a credential-carrying header pass - two README claims contradicted by measurement, and one engine-API claim I had inverted while *fixing* an earlier error Three lessons worth keeping, all learned by nearly missing something: 1. **Grep the test summary, never tail it.** This runner prints a crash *before* its per-file `ok` lines, so `tail -1` showed a false green on a suite that was not running. 2. **Adding a file to `stdb/` requires conformance,** not just unit tests. The harness is the only thing that compiles the bundle. 3. **A hedge about machines does not excuse a number that misses on the same silicon** — which is how three frame-budget figures survived until the truth gate re-ran them.
Author
Owner

Closing this

v1.1.0 shipped: tag v1.1.0 = 46b433e, release published, artifact and a real
consumer install both verified.

I had left this open because two boxes were unticked. Checking the precedent, that
was the wrong call: #24, the v1.0.0 checklist, was closed with every box ticked and
nothing dangling — including its own manual, maintainer-only step. A release
tracker is meant to die with the release.

The two remaining items are not release-gate work and do not need this issue to
stay alive, because each is already its own tracked issue:

  • #15 — upstream PR to defold/extension-websocket. Needs a GitHub account.
  • #16 — drop continue-on-error from the HTML5 CI step. I posted findings
    there: conformance.yml has 8 runs (6 success, 2 failure), but
    continue-on-error makes run-level green indistinguishable from "HTML5 failed
    and was swallowed", and the API serves no per-step results — so the evidence
    only exists as job logs in the web UI. That issue now carries a proposal to make
    future runs leave an API-visible signal, which would make the decision cheap
    instead of manual-forever.

Follow-up work this milestone created, all tracked independently: #69
(quote_big_ints unguarded, ~40% of the apply frame), #67 (time-slice the
applying frame), #65 (mass cold start still lockstepped), #63 (shared
state dir leaks a server). Recommended order is #69 before #67 — #69 roughly
halves what time-slicing has to spread, and it moves the thresholds #67 would be
tuned against.

## Closing this v1.1.0 shipped: tag `v1.1.0` = `46b433e`, release published, artifact and a real consumer install both verified. I had left this open because two boxes were unticked. Checking the precedent, that was the wrong call: #24, the v1.0.0 checklist, was closed with every box ticked and nothing dangling — including its own manual, maintainer-only step. A release tracker is meant to die with the release. The two remaining items are not release-gate work and do not need this issue to stay alive, because each is already its own tracked issue: - **#15** — upstream PR to `defold/extension-websocket`. Needs a GitHub account. - **#16** — drop `continue-on-error` from the HTML5 CI step. I posted findings there: `conformance.yml` has 8 runs (6 success, 2 failure), but `continue-on-error` makes run-level green indistinguishable from "HTML5 failed and was swallowed", and the API serves no per-step results — so the evidence only exists as job logs in the web UI. That issue now carries a proposal to make future runs leave an API-visible signal, which would make the decision cheap instead of manual-forever. Follow-up work this milestone created, all tracked independently: **#69** (`quote_big_ints` unguarded, ~40% of the apply frame), **#67** (time-slice the applying frame), **#65** (mass cold start still lockstepped), **#63** (shared state dir leaks a server). Recommended order is #69 before #67 — #69 roughly halves what time-slicing has to spread, and it moves the thresholds #67 would be tuned against.
yuris closed this issue 2026-08-20 16:41:16 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
yuris/spacetimedb-defold#59
No description provided.