stdb: connect({ protocol = "v2" }), opt-in #84

Merged
yuris merged 4 commits from bsatn-phase3-integration into main 2026-08-21 07:58:28 +00:00
Owner

Phase 3 of #9. Stacked on #83. This is the PR that makes v2 reachable.

v1 stays the default and its value surface is frozen. v2 negotiates the BSATN subprotocol, pins compression=None and sends binary frames. The cache grows exactly one decoder seam, cache.row_decoder, so a single apply path serves both wires.

The eight v2 handlers reuse the v1 effects rather than forking the event surface — credentials, subscription_settled and subscription_error are now shared by both handler tables. Event-table rows are the one new event type and are never cached.

Where the wires genuinely differ, v1 wins:

  • SubscriptionError drops no rows. Only the server knows which of them another live query set still matches.
  • The reducer callback keeps its frozen (err, msg) shape (#38), so ret_value stays raw BSATN.
  • A transaction emits one updates entry per query set per table, because the cache's hold counts depend on that.

Also worth knowing: on_out_of_energy can never fire on v2 — the v2 outcome has no OutOfEnergy variant, so exhaustion arrives as InternalError text.

Verified: 10 passed, 0 failed on this branch alone. test_connection.lua drives binary frames end to end and asserts the v1/v2 cache dumps and event sequences are identical. 7 mutants injected across the init/cache seams; all 7 killed.

Phase 3 of #9. Stacked on #83. This is the PR that makes v2 reachable. v1 stays the default and its value surface is frozen. v2 negotiates the BSATN subprotocol, pins `compression=None` and sends binary frames. The cache grows exactly one decoder seam, `cache.row_decoder`, so a single apply path serves both wires. The eight v2 handlers reuse the v1 effects rather than forking the event surface — `credentials`, `subscription_settled` and `subscription_error` are now shared by both handler tables. Event-table rows are the one new event type and are never cached. Where the wires genuinely differ, v1 wins: - `SubscriptionError` drops no rows. Only the server knows which of them another live query set still matches. - The reducer callback keeps its frozen `(err, msg)` shape (#38), so `ret_value` stays raw BSATN. - A transaction emits one `updates` entry per query set per table, because the cache's hold counts depend on that. Also worth knowing: `on_out_of_energy` can never fire on v2 — the v2 outcome has no `OutOfEnergy` variant, so exhaustion arrives as `InternalError` text. **Verified:** 10 passed, 0 failed on this branch alone. `test_connection.lua` drives binary frames end to end and asserts the v1/v2 cache dumps and event sequences are identical. 7 mutants injected across the init/cache seams; all 7 killed.
stdb: connect({ protocol = "v2" }), opt-in
All checks were successful
ci / ci (pull_request) Successful in 1m38s
conformance / conformance (pull_request) Successful in 2m46s
0d9bcfbe9a
v1 stays the default and its value surface is frozen. v2 negotiates the
BSATN subprotocol, pins compression=None and sends binary frames; the
cache grows one decoder seam, cache.row_decoder, so the same apply path
serves both wires.

The eight v2 handlers reuse the v1 effects rather than forking the event
surface: credentials, subscription_settled and subscription_error are
now shared by both handler tables. Event-table rows are the one new
event type and are never cached.

Where the wires genuinely differ, v1 wins. SubscriptionError drops no
rows, because only the server knows which of them another live query set
still matches. The reducer callback keeps its frozen (err, msg) shape,
so ret_value stays raw BSATN. And a transaction emits one updates entry
per query set per table, because the cache's hold counts depend on it.
yuris force-pushed bsatn-phase3-integration from 0d9bcfbe9a
All checks were successful
ci / ci (pull_request) Successful in 1m38s
conformance / conformance (pull_request) Successful in 2m46s
to 6a47e5487e
All checks were successful
ci / ci (pull_request) Successful in 1m28s
conformance / conformance (pull_request) Successful in 2m43s
2026-08-21 06:39:13 +00:00
Compare
Author
Owner

Review round: v1 confirmed unchanged, four correctness fixes, two untested guards now tested

The thing this PR most needed to prove

v1 behaviour is independently confirmed UNCHANGED. The pre-patch v1 suites pass verbatim
against the new source — not adapted, not re-baselined. For a change that adds a second protocol
through shared seams (decode_rows, handle_frame, the pending registry), that is the finding that
matters, and it is the one a reader of this diff cannot check by eye.

Fixed

  • A NaN in a pk/unique column corrupted the cache and hung the caller. The check raised from
    inside the write pass, after that batch's deletes had already been committed — so the cache was
    left half-applied and the callback was never settled, which is worse than either failure alone. The
    check moved into the pure pass; the offending relation is now skipped and reported, the same way
    every other undecodable relation already degrades.
  • Pending entries are parked before an encoder that can raise. Previously a raise inside encoding
    escaped with the request already half-registered.
  • light = false was rejected — while the docstring calls false the default. Passing the
    documented default explicitly was an error.
  • A reducer Err handed the frozen callback four length bytes and an embedded NUL instead of the
    error text. The frozen v1.0 callback surface is exactly where a raw-bytes leak must not happen.

Tests

Two guards whose own comments call them load-bearing had no test. They now do. A comment asserting
that a guard matters is a claim; the test is what keeps it true.

Verification state at the stack tip (bsatn-phase5-docs): unit suite 11 passed, 0 failed;
all four conformance suites PASS end to end — v1 native, v2 native, v1 HTML5, v2 HTML5 — under the
new protocol-specific gates, whose negative controls were proven non-vacuous; stylua --check stdb dev/test
clean.

## Review round: v1 confirmed unchanged, four correctness fixes, two untested guards now tested ### The thing this PR most needed to prove **v1 behaviour is independently confirmed UNCHANGED.** The pre-patch v1 suites pass **verbatim** against the new source — not adapted, not re-baselined. For a change that adds a second protocol through shared seams (`decode_rows`, `handle_frame`, the pending registry), that is the finding that matters, and it is the one a reader of this diff cannot check by eye. ### Fixed - **A NaN in a pk/unique column corrupted the cache and hung the caller.** The check raised from *inside the write pass*, after that batch's deletes had already been committed — so the cache was left half-applied and the callback was never settled, which is worse than either failure alone. The check moved into the **pure pass**; the offending relation is now skipped and reported, the same way every other undecodable relation already degrades. - **Pending entries are parked before an encoder that can raise.** Previously a raise inside encoding escaped with the request already half-registered. - **`light = false` was rejected** — while the docstring calls `false` the default. Passing the documented default explicitly was an error. - **A reducer `Err` handed the frozen callback four length bytes and an embedded NUL** instead of the error text. The frozen v1.0 callback surface is exactly where a raw-bytes leak must not happen. ### Tests **Two guards whose own comments call them load-bearing had no test.** They now do. A comment asserting that a guard matters is a claim; the test is what keeps it true. **Verification state at the stack tip** (`bsatn-phase5-docs`): unit suite **11 passed, 0 failed**; all **four conformance suites PASS** end to end — v1 native, v2 native, v1 HTML5, v2 HTML5 — under the new protocol-specific gates, whose negative controls were proven non-vacuous; `stylua --check stdb dev/test` clean.
yuris force-pushed bsatn-phase3-integration from 6a47e5487e
All checks were successful
ci / ci (pull_request) Successful in 1m28s
conformance / conformance (pull_request) Successful in 2m43s
to 6e36f0ddd3
Some checks failed
ci / ci (pull_request) Successful in 1m29s
conformance / conformance (pull_request) Has been cancelled
2026-08-21 07:56:24 +00:00
Compare
yuris changed target branch from bsatn-phase2-protocol to main 2026-08-21 07:57:26 +00:00
yuris force-pushed bsatn-phase3-integration from 6e36f0ddd3
Some checks failed
ci / ci (pull_request) Successful in 1m29s
conformance / conformance (pull_request) Has been cancelled
to 4002f9f18b
Some checks failed
ci / ci (pull_request) Successful in 20s
conformance / conformance (pull_request) Failing after 25s
2026-08-21 07:58:19 +00:00
Compare
yuris merged commit 4dd965b0a3 into main 2026-08-21 07:58:28 +00:00
yuris deleted branch bsatn-phase3-integration 2026-08-21 07:58:28 +00:00
Sign in to join this conversation.
No description provided.