Skip to content

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

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

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

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. Retargeted from #35 with no feature change: applied in order, the 9 PRs carry #35's fence diff (stable patch ID 70d97d6a) on v0.9.30-shopify-patches. Review and land bottom-up, restacking after each squash or rebase merge.

shopify-river and others added 3 commits October 5, 2026 15:33
…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>
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