Skip to main content

Module atomic_runner

Module atomic_runner 

Source
Expand description

ADR-099 migration step 3 (sub-slice B2) — the atomic runner: the synchronous commit-pass mechanism that applies a caller-supplied sequence of prepared write plans (crate::atomic_plan) as ONE SqlAccess::atomic_unit, under a per-op SAVEPOINT, committing every plan or rolling back the whole unit.

This module is the mechanism only. It has no production caller in B2 — no verb dispatch, no CLI --atomic surface, no daemon wiring. Tests in this file are the only consumer; wiring a real per-verb prepare step (ops → AtomicOpPlan) and the exec --ops-file --atomic CLI surface is ADR-099 migration steps 1 (cont’d) and 4 — the B3 wiring point referenced throughout this file.

§The atomic-unit suspend-free invariant, restated at this seam

run_atomic_unit is the one place in this crate that builds an AtomicUnitOp closure and hands it to SqlAccess::atomic_unit. That trait method carries a hard contract (its own doc comment, crates/khive-storage/src/sql.rs, and crates/khive-db/src/sql_bridge.rs block_on_sync): the closure’s future must resolve on its first poll — synchronous DML against the provided &mut dyn SqlWriter only, never a real .await on embedding, ANN warming, or any other suspending work. This module honors that invariant structurally, not by convention: every statement the commit-pass closure below drives comes from AtomicOpPlan::plan_statements (private — the runner’s own internal flattening step), which can only ever produce PlanStatements — plain parameterized SQL, the same shape ADR-099 D1’s prepare pass produces for the v1 DML-only admissible verb set (ADR-099 D3). There is no code path in this module that can hand atomic_unit an embedding call or any other suspending future — see the paired suspend-trap tests at the bottom of this file for the two things this promise is checked against: the real commit pass resolving on first poll (the happy-path proof), and a hand-built closure that deliberately suspends failing loudly through the exact same seam (the misuse-is-caught proof).

§Two-phase shape (ADR-099 D1)

Only the commit pass (phase 2) lives here: given an already-prepared Vec<AtomicOpPlan> (phase 1, the async prepare pass, is out of scope for B2 — a test-only caller constructs plans directly), run_atomic_unit opens one atomic_unit, applies each plan’s statements under a named SAVEPOINT, and returns either every op’s collected PostCommitEffects (phase 3, the async post-commit pass — this is the B3 wiring point: nothing in B2 executes these effects, a test consumer only drains the returned list) or the first op’s failure and its index.

Structs§

AtomicRunnerError
A failure of the atomic_unit seam itself — the storage layer refused or could not complete the call at all (read-only backend, no async runtime for the writer-task lookup, or an AtomicUnitOp that violated the suspend-free invariant and got caught by block_on_sync). Never returned for an ordinary op-level guard or SQL failure inside the unit — those surface as AtomicRunOutcome::RolledBack, not this error.

Enums§

AtomicOpFailure
Why a single op’s plan failed inside the commit pass (ADR-099 acceptance criteria: “the failing op index is recorded”).
AtomicOpPlan
One admissible op’s prepared write plan (ADR-099 D3’s v1 admissible verb groups), ready for the commit pass. This is the Vec<AtomicOpPlan> shape run_atomic_unit consumes — the runner is agnostic to which verb produced a given plan; it only needs each plan’s ordered statements (plan_statements) and any deferred post-commit effect (post_commit_effect).
AtomicRunOutcome
The whole-unit outcome of a completed run_atomic_unit call — the commit pass ran to a clean, distinguishable verdict (never returned for a seam-level failure; see AtomicRunnerError for that case).

Functions§

run_atomic_unit
Run plans as ONE atomic unit (ADR-099 D1 commit pass): open a single SqlAccess::atomic_unit, apply every plan’s statements under a named SAVEPOINT (adr099_atomic_op_<n>), and commit all or roll back all.