Skip to content

[fence 7/9] libsql-server: typed fence outcomes across protocols - #47

Closed
tszymczyszyn-shopify wants to merge 3 commits into
namespace-fence/5-admin-apifrom
namespace-fence/6-typed-outcomes
Closed

tszymczyszyn-shopify wants to merge 3 commits into
namespace-fence/5-admin-apifrom
namespace-fence/6-typed-outcomes

Conversation

@tszymczyszyn-shopify

@tszymczyszyn-shopify tszymczyszyn-shopify commented Oct 5, 2026 •

Copy link
Copy Markdown

Parked (2026-10-14). We are not pursuing this stack for now, so please do not review or merge it. The existing per-namespace block_writes and block_reads settings, together with operator checks, cover what we need today. The branches stay up for reference.

If we need more server support later, the likely follow-ups are small, separate changes: refusing replication and /dump while reads are blocked, a conditional POST /config, and block flags at namespace creation.


Adds an additive stable_code to the proxy error and a replicated fence field to metadata.DatabaseConfig in libsql-replication. Maps fence denials to their stable migration codes across user HTTP, Hrana, dump, RPC and the replica write proxy. Denials never use the auto-retried Unavailable code.

Review focus

Wire compatibility of the additive proto fields with older peers.

Commits

  • libsql-replication: add stable error code and replicated fence to protocols
  • libsql-server: typed fence outcomes across HTTP, Hrana and dump
  • libsql-server: carry fence outcomes through RPC and the replica write proxy

Stack

Part 7 of 9, based on namespace-fence/5-admin-api. The stack sits on #53 (tszymczyszyn/atomic-namespace-create), which sits on #51: review and land #51 → #53 → #41 → … → #49 bottom-up, restacking after each squash or rebase merge.

The first version of these PRs carried #35's fence diff unchanged on v0.9.30-shopify-patches. On 2026-10-13 the stack was rebased onto #53 (how the conflicts were resolved is summarised in a comment on #41) and gained the commits marked above.

shopify-river and others added 3 commits October 13, 2026 22:49
…tocols

Two additive proto3 fields for the namespace fence:

- `proxy.Error.stable_code` (tag 4): a stable machine-readable outcome
  such as `MIGRATION_WRITE_FENCED`, so a replica can return the same
  typed outcome the primary would have. Absent means "no typed
  outcome"; older peers skip it.
- `metadata.DatabaseConfig.fence` (tag 14, `ReplicatedFence { state,
  revision }`): the live fence as the primary's replication `hello`
  sees it. It is filled only by `hello` and only while a fence is
  active, and is never part of a stored configuration.

The primary now fills `DatabaseConfig.fence` in `hello` from the
namespace's fence gate. Filling `stable_code` on fence denials and
mapping it on the replica side come in a later change; until then no
server sets it, and capability discovery keeps reporting
`proxy_stable_code: false`.

Tests cover wire compatibility in both directions (an absent field
encodes exactly as before) and that `hello` carries the fence only while
one is active.

Co-authored-by: Tomasz Szymczyszyn <tomasz.szymczyszyn@shopify.com>
A fence denial reaching the user-facing protocols is now a typed
answer everywhere instead of a generic or fatal error:

- HTTP error bodies for fence errors gain an additive `code` field
  (and `detail` when there is one), with `423` for data-plane
  denials, through every wrapper the error arrives in. The legacy
  `/` API answers a batch with a fenced step as a whole with `423`.
- Hrana gains `StmtError::Fence` and `BatchError::Fence`, whose code
  is the stable fence code, so step denials and whole-request
  denials (a read under a read fence, a quarantined target) are
  Hrana errors on `/v1`, `/v2`, `/v3`, cursors and WebSockets, and
  the stream stays usable.
- `/v1/execute` and `/v1/batch` answer whole-request denials with
  `423` and the code.

Integration tests cover each protocol, an old WebSocket transaction,
a batch denied mid-way, `/dump`, and that `401`/`404` stay distinct;
a lib test shows a dump cancelled by the read drain fails its
response body.

Co-authored-by: Tomasz Szymczyszyn <tomasz.szymczyszyn@shopify.com>
… proxy

The primary's proxy service now fills the additive `stable_code` field
(with `code = SQL_ERROR`) for fence denials, on step errors and program
errors, streamed and unary. A fence denial before a program runs (the
namespace or JWT-key lookup, connection creation, a unary program
refused as a whole) is the typed `FAILED_PRECONDITION` status with the
stable code in `x-libsql-fence-code`, never `UNAVAILABLE`, which the
write proxy retries without bound.

On a replica, the write proxy maps a proxied error or status carrying a
fence code back to `Error::NamespaceFence`, so the replica answers its
client exactly as the primary would: `423` with the `code` field on the
HTTP APIs and the stable code as the Hrana error code. Errors from an
older primary, which never sets the field, keep their old mapping.
Capability discovery now reports `proxy_stable_code: true`.

Co-authored-by: Tomasz Szymczyszyn <tomasz.szymczyszyn@shopify.com>
@tszymczyszyn-shopify
tszymczyszyn-shopify force-pushed the namespace-fence/5-admin-api branch from c1d0470 to 892546d Compare October 13, 2026 20:51
@tszymczyszyn-shopify
tszymczyszyn-shopify force-pushed the namespace-fence/6-typed-outcomes branch from cdd8e0b to 6caa89f Compare October 13, 2026 20:51
@tszymczyszyn-shopify

Copy link
Copy Markdown
Author

Closing. This stack is parked; the note at the top of the description explains why. The branch is kept at 6caa89f657, so this PR can be reopened if an automated mover or concurrent moves make an operation-owned fence necessary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants