DrinkMeAlice. Outcome
(drink_me_alice v0.1.0)
Copy Markdown
Classifies raw SurrealDB replies for possibly-submitted mutations and reads into facts.
The core rule: once a request may have entered the write path, a lost
or malformed reply is {:unknown, request} — never silently rejected
or committed. {:refused, …} facts exist only where the server gave
qualified evidence the request could not have executed through a
request-level or session-level RPC refusal. Incomplete replies remain
inconclusive.
SurrealDB 3.0.5 statement-result semantics used here (pinned
dbs/executor.rs:675-1035 — review-44 correction):
- A
queryreply'sresultis a statement array. COMMIT has its own slot ONLY on success: the inner executor pushes the COMMIT result aftertxn.commit()returns ok (~:917), and consumes COMMIT with NO result entry on every failure path. A failed reply is therefore one entry SHORTER than the script — the trailingRETURNexecutes at the nominal COMMIT index. The classifier keys on the expected total length (ctx.lenfromDrinkMeAlice.Tx), never on the COMMIT index alone. - Full-length reply (
len == ctx.len):results[ci]is the real COMMIT slot.OKis observed commit success — a post-commit error cannot un-commit it, so a missing/failed decision is:committed_no_decision, never a false rejection. AnyERRatciin a full-length array is an unqualified shape (the pinned executor never pushes a COMMIT error) — inconclusive. - Shortened reply (
len == ctx.len - 1): the COMMIT entry was omitted;results[ci]is the shifted RETURN and is NEVER commit evidence. Qualified shapes over the in-tx entries at 1..ci-1 (statement errors carrykind/detailsat ENTRY level withresulta message STRING — pinneddbs/response.rs:78-90,113-164, confirmed by the live capturememory-reply-shapes-live-02.txt):- statement abort: BEGIN
{status: "OK", result: nil}at 0 (outside the rewrite's[start_results..]range), entries before the failing statement areNotExecutedmarkers ({status: "ERR", kind: "Query", details: {kind: "NotExecuted"}, result: <string>}), the failing statement carries the real error (a non-markerERRwith a message-string result), and every statement after it is aCancelledmarker — exactly ONE real error, marker order NotExecuted-before / Cancelled-after → definite non-commit ({:rejected, _}). - homogeneous cancellation: ALL in-tx entries are
Cancelledmarkers (ctx-done path) or ALL areNotExecutedmarkers (tx-creation failure) — definite non-commit. Mixed marker sets are not produced → unqualified. A shortened reply needs ≥2 in-tx entries (ci ≥ 3) for the single-error abort qualification: with one in-tx statement the commit-failure rewrite produces exactly oneInternalerror — indistinguishable from a lone abort error without message substrings.
- statement abort: BEGIN
- Commit failure (
txn.commit()error, ~:930): every in-tx entry is rewritten toInternal("Query not executed: {e}")— MULTIPLE real errors — andsurrealkv/mod.rscommits the storage transaction beforecommit_coordinator.wait_for_sync, so the write may already be durable. No rollback certificate exists on this wire shape — always inconclusive. Earlier "real" errors in that reply are rewrites, not abort causes. - A full-length reply must have
OKstatus on every pre-COMMIT entry,result: nil(NONE) at BEGIN and COMMIT — a contradictory pre-commit error or a non-nil COMMIT cannot establish success. THROWsurfaces as entry-levelkind: "Thrown"with the thrown tag inside the message-stringresult.- Any other length, an
OKmid-tx entry inside a shortened reply, a non-OK BEGIN, or any silhouette not listed above is unsupported — inconclusive. A shiftedRETURNis never read as COMMIT success.
Every statement entry must match the {status: "OK"|"ERR", result: _}
shape — unknown statuses, scalars or malformed entries make the whole
reply inconclusive rather than letting a valid-looking decision
overshadow corrupt neighbours.
Summary
Functions
Classify a read reply — single RETURN statement. Returns
{:ok, map} | {:error, :session | :rpc_error | :bad_shape}.
Classify a mutation reply.
True when the error CODE ALONE proves a session refusal — only
-32002. -32000 requires structured evidence; use
session_refusal?/1 on the whole error object.
True when the wire error object is a session refusal: -32002
InvalidAuth outright, or -32000 carrying the exact expired-session
wrapper chain the pinned 3.0.5 server emits — kind: "NotAllowed",
details.kind: "Auth", details.details.kind: "SessionExpired".
A SessionExpired kind under any other wrapper, a malformed chain,
or a bare -32000 is generic internal evidence: NOT a session
refusal (review-13).
Types
@type fact() :: {:committed, map()} | {:existing, map()} | {:rejected, DrinkMeAlice.Error.t()} | {:session_refused} | {:refused, integer()} | {:inconclusive, term()}
Functions
Classify a read reply — single RETURN statement. Returns
{:ok, map} | {:error, :session | :rpc_error | :bad_shape}.
Classify a mutation reply.
verdicts maps the script's decision strings to outcome atoms used by
the caller. ctx carries %{commit: i, decision: j, len: n} — the
nominal COMMIT index, the trailing RETURN index, and the script's
statement count, emitted by DrinkMeAlice.Tx builders (len
defaults to j + 1: RETURN is the last statement). When ctx is
absent (tests, migrations-side replies), the strict fallback runs:
a decision is honoured only if no entry carries any error and the
complete array is well-shaped.
True when the error CODE ALONE proves a session refusal — only
-32002. -32000 requires structured evidence; use
session_refusal?/1 on the whole error object.
True when the wire error object is a session refusal: -32002
InvalidAuth outright, or -32000 carrying the exact expired-session
wrapper chain the pinned 3.0.5 server emits — kind: "NotAllowed",
details.kind: "Auth", details.details.kind: "SessionExpired".
A SessionExpired kind under any other wrapper, a malformed chain,
or a bare -32000 is generic internal evidence: NOT a session
refusal (review-13).