Flip the default protocol to v2 (2.0.0) #89
Labels
No labels
area:defold
area:docs
area:infra
area:protocol
area:testing
kind:bug
kind:chore
phase:breadth
phase:core
phase:hardening
phase:slice
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
yuris/spacetimedb-defold#89
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The flip decision memo lives in the closing comments of #9; this issue tracks executing it.
Shipping straight to
2.0.0(no 1.2.0 deprecation release). v1 JSON stays a first-class, CI-covered opt-out viaconnect({ protocol = "v1" }).HTML5 tier gating is attempted in Gate 1 (dropping
continue-on-erroron the HTML5 step for both matrix legs). If it flakes during the observation window the fallback is pre-decided: restorecontinue-on-error: true, post the flake evidence to #81, record the exemption here, and proceed — the flip does not wait on #81's flake having a cause.Gate 2 done — #90 merged (squash).
confirmed_readsmatches the official SDKs and is now pinned:nilomits the parameter (server default: false on v1, true on v2),falseis the documented opt-out,trueis sent as asked. No production change was needed —subscribe_urlalready behaved this way; what landed is the test that fails if anyone writes the default out asconfirmed=false.Review caught one real defect in the first cut: the
confirmed=trueassertion sat behind a composition case that already pinned the same URL, so it was unreachable and a droppedconfirmedreported as a parameter-ordering failure. Reordered, then verified by swallowing a v2confirmed_reads = trueinsubscribe_urland confirming the tri-state assertion's own message is what reports.Two optional nits from #91's review, recorded rather than actioned (both are pre-existing or capacity-dependent, neither blocks the flip):
No
max-parallelon the conformance matrix./nixis a persisted volume shared across runs, and the install step falls through tocurl | shwhen it is cold. Before the matrix a single run could not produce two concurrent jobs; now it can, so on the first run after a volume reset two unsynchronised installers could race it. The window is narrow (the warm-store branch short-circuits every subsequent run) and #91's own run confirmed the legs do run concurrently, which is what buys back the wall time — ~2.5 min rather than ~5. Left concurrent deliberately;max-parallel: 1is the fix if that race is ever actually observed.A dispatch can no longer run both wires.
required: falsepaired with a non-emptydefault: v1means Forgejo substitutes the default for an empty submission, soinputs.protocolis always truthy on dispatch and the matrix always narrows to one leg. Main behaved the same way, so nothing regressed. Droppingdefault:would fall through to both legs, but that collides with Gate 5's step of flipping this default tov2. Recorded so the interaction is not rediscovered later.Also worth carrying forward from #92:
STDB_BULK_BUDGETis in seconds, and at the default 10000-row sweep a v2 sliced run does not slice at all — BSATN decode finishes the apply in one frame, so the PHASE6 gate fails. Pre-existing on main. The correct sliced proof isSTDB_PROTOCOL=v2 STDB_BULK_BUDGET=0.008 STDB_BULK_SWEEP=50000; Gate 5's verification list uses that, superseding the plan'sSTDB_BULK_BUDGET=8.Gate 1 done — #91 merged (squash).
Conformance is now a matrix over
protocol: [v1, v2]withfail-fast: false, so both wires gate every push tomainand every PR matching&conformance_paths. v2 is no longer dispatch-only, which was the precondition for making it the default.The
fromJSON/formatmatrix fall-through is not a guess — #91's own run expanded into two legs,conformance (v1)andconformance (v2), both success one50fffe. That also settles Gate 4's open question: this Forgejo does publish per-leg commit statuses, queryable atGET /commits/{sha}/status, so the pre-decided local-re-run fallback is not needed.The HTML5 step no longer has
continue-on-error, so the tier gates both legs. Review caught that the #81 self-report body was built only from run_number/outcome/sha — identical across legs — so the two comments were indistinguishable exactly on the early failures (wasm build, nix develop, Chromium launch) whose tails never name a wire. It now printsmatrix.protocol.Gate 3 done — #92 merged (squash).
The v2 smoke now asserts everything the v1 one does: a one-off query on both wires (neither had it), a sliced BSATN apply at unit level (
test_slicing.luanever touchedrow_decoder), and an inline-delivery assertion standing in for v1'slight = truecheck.Review caught that the first cut of that stand-in was a tautology:
connection.luahands the reducer callback and the transaction event the same table unconditionally, sorawequalcompared a value to itself, and a commit arriving in its own frame is aTransactionUpdatewith norequest_id, so the guard was skipped entirely. Proven by driving the real connection with anOkEmptyReducerResult. Under a real regression the run would have died on a generic 90s timeout, saying nothing about the claim it was there to defend — in the one PR whose entire purpose is proving v2 parity.Fixed by reading the settling frame's own
counts.eventsfor the insert, which is frame-local;find_rowwould have been satisfied by a row left behind by an earlier run. Proven to bite: patching the outcome to theOkEmptyshape now fails immediately withthe send_message result settled the call but its own frame announced no insert. The v2 one-off row check was likewise strengthened from "non-empty string" (which an error string would satisfy) to a real BSATN decode with a field assertion.Gate 4 done as well. Both legs green on three separate PR-triggered runs —
e50fffe(#91),1b044ba(#93),a743295(#92) — read fromGET /commits/{sha}/status, which this Forgejo does publish per matrix leg. No exemption or local-re-run fallback needed.Unplanned but necessary: #93 landed in between, fixing the #81 HTML5 flake at its cause (chromium's child processes outliving a parent-only kill, so
rmraced live profile writers). Gating the tier in Gate 1 is what made it diagnosable — it turned an invisible footnote into a failure with a captured tail on its second run.Gate 5 done — #94 merged (squash).
connect()now defaults toprotocol = "v2",game.projectreads2.0.0.The flip exposed a real latent bug, which is the best argument for having done it behind gates.
subscribe_urlcomposed its query string fromopts.protocolrather than from the wireconnecthad resolved. Harmless while the default was v1 and the two always agreed; under a v2 default a defaulted caller leftopts.protocolnil, so the socket negotiated BSATN with nocompression=None— which the codec then refuses — andlight = trueslipped past the v2 guard onto the wire. It is how the new default assertion andtest_slicingfirst failed.subscribe_urlnow takes the resolved protocol; both halves are pinned by tests.The test sweep was also wider than the plan assumed:
native_urldrivesconnect, so every assertion built on it inherited the default, including a block explicitly asserting "the pre-1.0 URLs, byte for byte". That block now names its wire on every call, so those assertions keep testing composition instead of silently becoming default-tracking tests.Review caught two docs defects. The README claimed all four v2 breaks "find out at
connect" — onlylightdoes; the reducers/procedures andsubscriberaises land at the callsite, and an undescribed table is not refused at all but skipped and reported, which the same paragraph's own last sentence already said. Andconnection.lua's module header still called v2 opt-in, contradicting two other comments rewritten in the same commit.Verified on the merge candidate: unit suite 11/11, stylua clean, and all five engine runs — native default names v2, native
STDB_PROTOCOL=v1names v1, sliced 50000 rows over 6 frames at 8.0ms, HTML5 default asserts thev2.bsatn.spacetimedbhandshake, HTML5 v1 passes. Both CI legs green onda95c8e.Done. Shipped as
v2.0.0.flip-ci6847625flip-confirmedb71a6a0fix-html5-teardownc46cd42flip-smoke-parity8b0be6cflip-default11f88174a35e5fTag
v2.0.0is annotated (the annotation is the changelog) and the release is published. Wiki swept in one commit,eaafcb8.End-state verification from merged
main: unit suite 11/11, stylua clean, and four engine proofs — bareconformance.shPASSes naming v2 (the gate is protocol-specific, so a silent v1 fallback would fail it),STDB_PROTOCOL=v1PASSes naming v1, the sliced run does 50000 rows over 6 frames at an 8.0ms budget, and the bare HTML5 tier asserts thev2.bsatn.spacetimedbhandshake. Both CI legs green on every merge commit above.Three things the gates caught that a direct flip would have shipped:
subscribe_url(#94). It composed the query string fromopts.protocolinstead of the wireconnecthad resolved — harmless while the default was v1 and the two always agreed. Under a v2 default a defaulted caller negotiated BSATN with nocompression=None, which the codec refuses, andlight = truebypassed the v2 guard onto the wire. This would have broken the release on first connect.lightcheck compared a value to itself, and the separate-frame case skipped the guard entirely — so the one PR whose purpose was proving v2 parity proved nothing, and a real regression would have surfaced as a generic 90s timeout.rmraced live profile writers. Group-kill fixed it, 0 failures in 8 races against 7 before.Also worth recording: the plan's sliced-run invocation was wrong (
STDB_BULK_BUDGETis seconds, not ms) and v2's decode is fast enough that the default 10000-row sweep does not slice at all. The working proof isSTDB_BULK_BUDGET=0.008 STDB_BULK_SWEEP=50000.Two nits recorded rather than actioned, both above: no
max-parallelon the conformance matrix, and a dispatch no longer being able to run both wires. #81 stays open until the observation window adds a few more green runs on both wires; when it closes, the self-report step, theissues: writegrant and both tees go with it.