Skip to content

Feat/embedding api spec - #678

Open
iheitlager wants to merge 3 commits into
Lab271:mainfrom
jverhoeks:feat/embedding-api-spec
Open

Feat/embedding api spec#678
iheitlager wants to merge 3 commits into
Lab271:mainfrom
jverhoeks:feat/embedding-api-spec

Conversation

@iheitlager

Copy link
Copy Markdown
Member

Proposal for a public api to support applications to link to sqlite-rs

…(013/Req-7)

010 gains Requirement 8: a write MUST NOT succeed while leaving an index it
could not read unmaintained. Measured at 0.18.5 against stock sqlite3 3.51.0 --
inserting into a stock-created composite-PK table leaves rows out of
sqlite_autoindex_*, after which the oracle undercounts and integrity_check
reports rows missing, while the write returns rc=0. Names both acceptable
fixes (recover the autoindex column list from the declared constraint, or
refuse the write) and scopes autoindex *creation* out to V3/V7.

The embedding-api spec gains Requirement 7: statements must yield rows
incrementally. It also upgrades the composite-PK prerequisite from inferred to
measured, flagging it as the highest-priority item in or around that spec --
it is silent corruption of a valid SQLite file, not an ergonomic gap, and its
Req 6 byte-identity scenario cannot pass while it stands.

That spec is numbered 012 as of this commit and is renumbered to 013 by the
next one, after main landed its own spec 012.

Refs: 010/Req-8, 013/Req-7
Main landed its own spec 012 (`012-query-constraints`) and its own ADR-0033
(constant propagation / OR-to-IN) while this branch was open, so both numbers
collided on rebase. The newcomer moves:

- `.openspec/specs/012-embedding-api/` -> `013-embedding-api/`, heading updated
- `adr/0033-embedding-api-owns-the-connection-driver-out-of-tree.md` -> `0034-`,
  heading updated, and its five `spec 012` prose references retargeted to 013
- `adr/index.md` gains the 0034 row, which 0ee936b omitted entirely

Renumbering ADR-0034 does not violate the immutability convention: it has
never been on main and is still `Status: Proposed`.

Also fixes four `**Implementation:**` lines in spec 013 that were already
marked `(planned)` but written in a form `tools/assurance.py:474` does not
match -- its regex only accepts `(planned)` immediately after a *single*
backticked path, so `` `a`, `b` (planned) `` scored as active and its
not-yet-written test links counted as dead. With that fixed, the dashboard is
byte-identical to main: 86 active requirements, Completeness 85/86 (99%),
Coverage 270/270 (99%), zero dead links, planned 2 -> 10.

Refs: 013/Req-1, 013/Req-7
dpsiderius added a commit that referenced this pull request Sep 4, 2026
…ent (#682)

Part 1 found three problems with spec 013 but left the fixes as open
design decisions, each with a defensible alternative a spike had no
business picking unilaterally. Part 2 builds each alternative and prices
it. In all three cases the answer is the better option, not a compromise.

1. Value -> Arc is almost free, and nobody had measured it.
   ADR-0013/0017 rejected Arc, but they were arguing about the pager;
   Value was never the subject. The change is +22/-17 across 6 files,
   because construction sites use `.into()`, which is identical for
   Rc<str> and Arc<str> — only the ~12 sites that name the type needed
   editing. Rc<dyn PageSource>/Rc<RefCell<Pager>> untouched, so both
   ADRs stay intact. 1562 tests pass, and Value becomes Send + Sync.
   No measurable read-path tax (differences sit inside a few percent of
   run-to-run variance). The boundary copy then disappears entirely:
   worker_direct/1024 runs 5.13 ms against worker_chunked/1024's
   7.34 ms, matching single-threaded batch because sync_channel(1)
   pipelines once it stops copying.

2. The ~8 MB memory floor is one constant, not an architecture.
   Sweeping DEFAULT_PAGE_CACHE_CAPACITY on a 1M-row result: 2000 pages
   -> 8.68 MB, 256 -> 1.10 MB, 64 -> 291 KB, flat in result size at
   every setting. Cost of 64 pages is +4.5% on streaming and nothing on
   batch. So Req 7 needs neither a weakened promise nor a new eviction
   subsystem — it needs a streaming connection to be allowed a smaller
   cache. Measured on sequential scans only; random access is the
   obvious follow-up.

3. Adaptive chunking removes the throughput/latency trade outright.
   Growing 1,2,4,...,1024: full drain 5.52 ms (fixed-1024 is 5.27 ms,
   batch 5.26 ms) and first row 50.2 us (unchunked 45.5 us, fixed-1024
   357 us). Within 5% of the best throughput and 10% of the best
   latency simultaneously, so the chunk size needs no knob and stays out
   of the public API. This also dissolves Part 1's question about which
   SQE consumer to optimise for — one strategy serves both.

Adds worker_direct and worker_adaptive prototypes (both require the
Arc-Value patch), extends the agreement gate to cover them, and records
all of it in README Part 2. Still branch-only evidence: the Arc change
ships via its own ticket and ADR, not from here.

Refs: 013/Req-4, 013/Req-7, #682, #678

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
dpsiderius added a commit that referenced this pull request Sep 4, 2026
…in spec 013 (#678)

My reconciliation commit said four of spec 013's claims had stopped being
true. There were five.

Item 6 read "`Pager` syncs but nothing states what is guaranteed, and
`synchronous` has no handler". The second clause was already false when
#678 was written: #645 implemented `PRAGMA synchronous` in full — the
bare query form reporting `0`/`1`/`2` like stock SQLite, all three
levels, and a decided per-level fsync-skip policy recorded in ADR-0036
(`src/vdbe/pragma.rs:79`, dispatched at `src/vdbe/exec.rs:760`, with
unit tests). Nothing about it is a stub.

That also means Requirement 5 is further along than it claims. It asks
the API to "honor `PRAGMA synchronous` at least to distinguish FULL from
OFF", which is a weaker ask than what already exists, and says "what is
missing is a documented guarantee and any way to trade it" — the trade
mechanism is exactly what #645 added. Requirement 5's remaining work is
the written guarantee, the transaction surface, and the busy/retryable
error handling, not the PRAGMA.

I found this while answering "will this work with SQE yet", by checking
each of the spec's seven gaps against the tree instead of trusting the
list. Worth noting for anyone reviewing #693: the list was written
against 0.18.5 and the tree is 0.18.10, so treat every "is missing"
line as a claim to re-verify rather than a fact. The four I corrected
first were the ones my own branches falsified; this one had been stale
for longer and nothing I built touched it.

`make check-assurance` unchanged at 86/86 and 276/276, no dead links —
Requirement 5 stays `(planned)`, since the requirement as a whole is not
discharged even though this part of it is.

Refs: 013/Req-5, #645, #678

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
iheitlager pushed a commit that referenced this pull request Sep 7, 2026
…le (#685) (#689)

* fix: recover sqlite_autoindex_* keys so writes stop corrupting the file (#685)

Writing to a table carrying a `sqlite_autoindex_*` returned success and
left the index stale. `ddl_reader::index_schema` recovers an index's key
columns by parsing its `sqlite_master.sql`, and every autoindex has
`sql = NULL`, so it was dropped from `TableSchema::indexes` — the same
list that drives both `emit_unique_check` (insert.rs:698) and index
maintenance (insert.rs:796). Duplicates were accepted and the index was
never updated; the oracle then reported `wrong # of entries in index`
and `count(*)` undercounted from the stale index.

Autoindex rows are now deferred during the `sqlite_master` walk and
their keys recovered from the owning table's own DDL. The numbering rule
is oracle-derived (3.51.0), not inferred, and several parts of it are
counter-intuitive:

- declaration order decides, not primary-key-first —
  `UNIQUE (c), PRIMARY KEY (a, b)` numbers the UNIQUE `_1`;
- column-level `PRIMARY KEY`/`UNIQUE` count as much as table-level;
- a rowid-alias primary key gets no index AND consumes no number, so
  `(a INTEGER PRIMARY KEY, b TEXT UNIQUE)` puts UNIQUE(b) at `_1`;
- a `WITHOUT ROWID` primary key gets none — it *is* the table;
- redundant constraints collapse: `PRIMARY KEY (a), UNIQUE (a)` is one
  index, as is `a TEXT PRIMARY KEY UNIQUE`.

Safety valve, per spec 010/Req 8 and spec 007/Req 1's hot-journal
precedent: an autoindex whose key cannot be recovered sets
`TableSchema::unresolved_autoindex`, and INSERT/UPDATE/DELETE codegen
refuse rather than write. Failing the statement beats silently producing
a corrupt database.

Two things found while doing this, both handled deliberately:

- The rowid-alias rule needed here is not the one
  `rowid_alias_from_sql` implements — that function additionally
  requires the primary key to name the table's *only* column, which is
  not SQLite's rule and is a live read-correctness bug (#686). This
  ticket implements the correct rule in a local helper rather than
  smuggling a crate-wide rowid change into a corruption fix; the helper
  collapses into a call to the shared one when #686 lands.

- Unique-violation messages named the index, not the columns. Stock
  SQLite says `UNIQUE constraint failed: t.a, t.b, t.c`; we said
  `t.sqlite_autoindex_t_1`. That divergence pre-existed for *every*
  unique index, but this fix made it newly reachable for autoindexes
  with a generated name no caller could act on, so the format now
  matches the oracle byte-for-byte. No existing test asserted the old
  format, which is a coverage gap this ticket closes.

Verified: 1562 unit tests and 387 corpus tests pass (both unchanged from
baseline), clippy/fmt/mod-files clean, assurance still 86/86 and 276/276
with no dead links. New `tests/corpus/autoindex_maintenance_test.rs`
covers all three spec 010/Req 8 scenarios plus the numbering rule, the
rowid-alias and WITHOUT ROWID cases, and message parity.

Not included: emitting `sqlite_autoindex_*` on CREATE TABLE, so a table
this crate *creates* with a declared composite key still lacks its
index. That is the other half of the corruption story and is filed
separately — this half fixes adopting a stock-created file, which is the
SQE case.

Refs: 010/Req-8, #685, #686, #678

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: commit the test-file TableSchema updates #685 needs to build (#685)

The previous commit added `TableSchema::unresolved_autoindex` but left
seven test files' struct literals uncommitted in my working tree, so the
branch as pushed did not compile — `cargo build --tests` failed with 8
`missing field` errors in `tests/tiers/tier1.rs` and
`tests/unit/codegen*.rs`.

The 1562-passing run reported on the previous commit was real, but it
measured the working tree rather than the committed branch. Recording
that here rather than amending: the distinction is the actual lesson,
and force-pushing over a branch already sent for review would hide it.

Verified from the committed state this time: `cargo build --tests` clean,
1562 unit tests and 387 corpus tests pass.

Refs: #685

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: take &Path not &PathBuf in the autoindex test helpers (#685)

`make lint`'s second clippy pass — the one that lints the `test = false`
corpus/parity/sqllogictest targets `--tests` skips — failed on
`clippy::ptr_arg` for the two new helpers that forward `db` to
`page_size_of(&Path)`.

Only these two of the file's four `&PathBuf` params were flagged, and
that asymmetry is the lint working as designed: `seed` and
`oracle_select` pass `db` straight into `Command::arg`, a generic
`AsRef<OsStr>` bound clippy won't assume the deref target satisfies, so
it stays quiet there. `our_insert` and `autoindex_map` hand `db` to
functions already typed `&Path`, proving the slice suffices. Left the
other two alone rather than churn lines the gate is happy with.

Signature-only; call sites deref-coerce unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
iheitlager pushed a commit that referenced this pull request Sep 7, 2026
…690)

`Value::Text(Rc<str>)`/`Blob(Rc<[u8]>)` made `Value` itself `!Send`, and
a result row is a `Vec<Value>`, so no query result could cross a thread
boundary at all. That blocks spec 013/Req 4's `Send + Sync` connection
handle outright.

ADR-0013 and ADR-0017 look like they forbid this, and they do not: both
are about the *pager*, where one `Vm` shares a page source across N
cursors via cheap `Rc` clones. `Value`'s payloads were never their
subject. `Rc<dyn PageSource>` and `Rc<RefCell<Pager>>` are untouched
here. ADR-0039 records that distinction, because the natural reading of
those two ADRs is "`Arc` anywhere is settled against" and the next
person would otherwise re-litigate it or assume an oversight.

Spike 014 (#682) measured rather than argued: +22/-17 across 6 files
(construction sites use `.into()`, identical for both types, so only the
~12 that name the type changed), 1562 tests unchanged, and no measurable
read-path cost — `Rc` runs spanned 5.42-5.62 ms against `Arc`'s
5.24-5.33 ms on a single-threaded full drain. It also removes the
boundary copy permanently: 5.13 ms against 7.34 ms for the owned-copy
alternative.

A `const` assertion in `value.rs` now enforces `Send + Sync` at compile
time. Mutation-checked: reverting either payload to `Rc` fails the build
with "cannot be sent between threads safely" rather than deferring the
error to some future consumer.

One honest caveat carried into ADR-0039: the spike's noise floor is a
few percent and one run showed `Arc` 5.6% *faster*, which is not a
credible result from adding atomics. The claim is "no measurable cost",
not "faster", and it should be re-measured on
`tests/performance/engine.rs` against the pinned oracle before being
quoted as settled.

Verified: 1562 unit and 380 corpus tests pass, clippy/fmt clean.

Refs: 013/Req-4, #688, #682, #678

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
iheitlager added a commit that referenced this pull request Sep 7, 2026
…#691)

* feat: streaming Execution primitive, with run() as its wrapper (#683)

Every execution entry point materialized a whole `Vec<Vec<Value>>`
before returning, so reading row 0 of a large result cost building row
N and a caller could not stop early. Spike 014 (#682) measured that at
137.7 MB peak heap and 5.36 ms to first row for a 1,000,000-row result,
against 8.68 MB and 44.7 us streaming.

It was also not fixable from outside the crate: `fn dispatch` and
`fn run` are private, and `ResultRow` hands its row to `Vm::emit_row`,
which pushes into a `Vec` inside the `Vm`.

Adds `Execution` (`new`/`next_row`/`autocommit`) and reimplements
`run()` as a wrapper that collects `next_row` into the same `Vec` it
already returned. The wrapper is the load-bearing part: batch and
streaming become literally the same loop, so they cannot drift, and the
existing suite is the equivalence proof — 1562 passed / 0 failed before,
1568 / 0 after, the delta being exactly this ticket's six new tests.

`pending` is a FIFO rather than a pop off the back because
`pragma::integrity_check` emits one row per problem from a single
dispatch, and draining from the back would silently reverse that output.
No test in the suite reached that path before now, so
`vdbe_streaming_execution_test.rs` empties three indexes to force a
genuinely multi-row result — and the guard was mutation-checked: with
the FIFO replaced by a pop, that test and only that test fails.

Deliberately not included: any transaction-aware streaming constructor
(`Vm::autocommit` is private, and read-only streaming is what Req 7
targets), and chunking, which #682 showed is a transport concern for the
facade rather than the primitive. ADR-0038 records both, plus why a
second parallel execution path was rejected.

No CHANGELOG entry or version bump: this repo folds those into a
separate `chore/*-fold-into-0.18.x` PR (e.g. #671), and ticket PRs do
not carry them. #683's acceptance criteria said otherwise and were wrong
about the convention.

Refs: 013/Req-7, #683, #682, #678

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(fuzz): bound vdbe_exec's row drain so an emit-loop can't OOM the gate

`vdbe_exec` feeds arbitrary bytecode to `execute()`, which hands back
every row the program emitted. The decoder is free to build `ResultRow`
with a register range up to `MAX_REGISTERS` and an `Init` whose negative
P2 lands back on it (`to_pc` clamps to 0), so the result set grows
without bound — 4.3 GB in ~1400 rows of 131,072 NULLs each, tripping
libFuzzer's 4096 MB limit (CI run 33857317005).

Not a regression from this branch. Replaying the artifact against
`origin/main` grows at the same ~1 GB/s, because nothing drains
`Vm::rows` there either; the streaming rewrite moves rows through a
FIFO but `run()` still collects them all. The fuzzer only reached it now
because its writable corpus is gitignored, so every CI run re-explores
from the three committed seeds on a fresh random seed.

Bounding accumulation in the engine would be the wrong fix — a `SELECT`
that legitimately returns N rows must be allowed to return N rows, and
real programs come from codegen, never from a caller handing the VM raw
bytecode. So the bound goes in the harness: rows are pulled through
`Execution` and dropped as they arrive, and `MAX_ROWS` caps the emitted
work per input so a wide `ResultRow` cannot exhaust the budget in time
instead of memory. Per ADR-0040 `run()` is this same loop plus a
`Vec::push`, so the dispatch coverage that spec 009's no-panic-totality
obligation (#89) is actually about is unchanged.

The OOM input is committed as seed `result_row_emit_loop` per
tests/fuzz/seeds/README.md, so the regression cannot come back unnoticed.

Verified: the seed goes from SIGKILL to 175 ms; `make fuzz-smoke` is
green across all seven targets; a 90s `vdbe_exec` run peaks at 360 MB.
Throughput is unaffected by the cap (MAX_ROWS 8 vs 64 measured within
noise), so 64 is kept for multi-row coverage.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Ilja Heitlager <iheitlager@schubergphilis.com>
iheitlager added a commit that referenced this pull request Sep 7, 2026
…supersedes #678) (#693)

* feature: spec extension for sqe

* spec: autoindex write-side rule (010/Req-8) + incremental row access (013/Req-7)

010 gains Requirement 8: a write MUST NOT succeed while leaving an index it
could not read unmaintained. Measured at 0.18.5 against stock sqlite3 3.51.0 --
inserting into a stock-created composite-PK table leaves rows out of
sqlite_autoindex_*, after which the oracle undercounts and integrity_check
reports rows missing, while the write returns rc=0. Names both acceptable
fixes (recover the autoindex column list from the declared constraint, or
refuse the write) and scopes autoindex *creation* out to V3/V7.

The embedding-api spec gains Requirement 7: statements must yield rows
incrementally. It also upgrades the composite-PK prerequisite from inferred to
measured, flagging it as the highest-priority item in or around that spec --
it is silent corruption of a valid SQLite file, not an ergonomic gap, and its
Req 6 byte-identity scenario cannot pass while it stands.

That spec is numbered 012 as of this commit and is renumbered to 013 by the
next one, after main landed its own spec 012.

Refs: 010/Req-8, 013/Req-7

* docs: renumber embedding-api spec to 013 and its ADR to 0034

Main landed its own spec 012 (`012-query-constraints`) and its own ADR-0033
(constant propagation / OR-to-IN) while this branch was open, so both numbers
collided on rebase. The newcomer moves:

- `.openspec/specs/012-embedding-api/` -> `013-embedding-api/`, heading updated
- `adr/0033-embedding-api-owns-the-connection-driver-out-of-tree.md` -> `0034-`,
  heading updated, and its five `spec 012` prose references retargeted to 013
- `adr/index.md` gains the 0034 row, which 0ee936b omitted entirely

Renumbering ADR-0034 does not violate the immutability convention: it has
never been on main and is still `Status: Proposed`.

Also fixes four `**Implementation:**` lines in spec 013 that were already
marked `(planned)` but written in a form `tools/assurance.py:474` does not
match -- its regex only accepts `(planned)` immediately after a *single*
backticked path, so `` `a`, `b` (planned) `` scored as active and its
not-yet-written test links counted as dead. With that fixed, the dashboard is
byte-identical to main: 86 active requirements, Completeness 85/86 (99%),
Coverage 270/270 (99%), zero dead links, planned 2 -> 10.

Refs: 013/Req-1, 013/Req-7

* docs: renumber the embedding-API ADR to 0041 and reconcile spec 013 with the tree (#678)

Takes over #678's spec so it can land off current `main`. Jacob's three
commits are cherry-picked verbatim above this one and keep their
authorship; everything here is the reconciliation they could not carry,
because the tree moved after they were written.

ADR renumber, 0034 -> 0041. 0034 was already taken on `main`
(`0034-index-range-seeks.md`) when #678 was opened, and 0038/0039/0040
have since gone to the Cargo registry decision (#684), `Value`'s `Arc`
payloads (#688) and the streaming primitive (#683). Three citations in
spec 013 and the `index.md` row move with it. The index gains a gap at
0039/0040 until those two branches land, which is the correct state for
this branch rather than a placeholder.

Four claims in spec 013 were true when written and are not now:

- **Item 7 said "nothing in `src/vdbe/` offers a step or iterator
  API".** `vdbe::Execution` is that API as of #683, with `run()`
  reimplemented as a wrapper over it. Requirement 7 is restated as a
  facade gap and its `Implementation:` line now points at
  `Execution::next_row` as the thing to build on -- #682 found the
  ordering matters, because a facade retrofitted onto `execute_with_db`
  cannot be made incremental afterwards.

- **Item 3 attributed the `!Send` problem to the pager alone.** So did
  ADR-0041. `Value::Text`/`Blob` held `Rc` payloads, which made a result
  row -- precisely the thing that has to leave a worker thread --
  unsendable too. Closed by #688/ADR-0039, which also narrows
  Requirement 4: the worker thread is still required for the pager, but
  it now hands rows across instead of copying them.

- **The composite-PK prerequisite was one item; it is two.** #685 fixed
  maintenance of an existing `sqlite_autoindex_*` and the read-only
  safety valve (spec 010/Req 8). Creating one on `CREATE TABLE` is
  still open as #687. SQE's two workarounds split the same way: the
  write-refusal one comes out now, the dropped-composite-key one waits
  on #687.

- **Requirement 7's acceptance scenario could not pass.** It asked that
  "peak allocation is proportional to the ten rows". Peak heap for a
  streaming read is a floor set by the page cache, not a slope in rows
  pulled, so no correct implementation satisfies that wording. Restated
  as independence from result size -- 8.68 MB flat against 137.7 MB
  materialized on 1,000,000 rows (#682) -- with
  `DEFAULT_PAGE_CACHE_CAPACITY` named as the knob that moves the floor
  (2000 pages -> 8.68 MB, 64 -> 291 KB, ~4.5% streaming cost). That is
  both the property a consumer needs and, unlike proportionality,
  testable.

Also corrected in both specs: measurements were cited against "stock
`sqlite3` 3.51.0", but the pinned oracle is 3.53.4
(`tests/corpus/oracle.rs:22`, `Cargo.toml [package.metadata.oracle]`).
On this machine a bare `sqlite3` is Apple's 3.51.0 codec build, which
`tools/gen_fixtures.sh` refuses by design. I re-derived the autoindex
rule against the pinned 3.53.4 across eleven DDL shapes, comparing
`pragma_index_info` key lists rather than index counts: every case
agrees, including the counter-intuitive ones (declaration order beats
primary-key-first; a rowid alias consumes no number). The citation was
wrong, not the rule -- but "measured against the pinned oracle" is this
repo's whole assurance basis, so it has to be accurate.

Spec 010/Req 8's three scenario `Tests:` links already name the exact
test functions #685 created. They stay `(planned)` here: the flip
belongs to the PR that discharges the requirement, and it cannot happen
on this branch because the tests do not exist on it.

`make check-assurance` passes at 86/86 and 276/276, unchanged -- every
requirement added here is `(planned)`, so it is excluded from scoring by
design and the dashboard cannot move until the implementing tickets
land. No dead links.

Refs: 010/Req-8, 013/Req-1, 013/Req-4, 013/Req-7, #678, #682, #683,
#685, #687, #688

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: PRAGMA synchronous already has a handler — a fifth stale claim in spec 013 (#678)

My reconciliation commit said four of spec 013's claims had stopped being
true. There were five.

Item 6 read "`Pager` syncs but nothing states what is guaranteed, and
`synchronous` has no handler". The second clause was already false when
#678 was written: #645 implemented `PRAGMA synchronous` in full — the
bare query form reporting `0`/`1`/`2` like stock SQLite, all three
levels, and a decided per-level fsync-skip policy recorded in ADR-0036
(`src/vdbe/pragma.rs:79`, dispatched at `src/vdbe/exec.rs:760`, with
unit tests). Nothing about it is a stub.

That also means Requirement 5 is further along than it claims. It asks
the API to "honor `PRAGMA synchronous` at least to distinguish FULL from
OFF", which is a weaker ask than what already exists, and says "what is
missing is a documented guarantee and any way to trade it" — the trade
mechanism is exactly what #645 added. Requirement 5's remaining work is
the written guarantee, the transaction surface, and the busy/retryable
error handling, not the PRAGMA.

I found this while answering "will this work with SQE yet", by checking
each of the spec's seven gaps against the tree instead of trusting the
list. Worth noting for anyone reviewing #693: the list was written
against 0.18.5 and the tree is 0.18.10, so treat every "is missing"
line as a claim to re-verify rather than a fact. The four I corrected
first were the ones my own branches falsified; this one had been stale
for longer and nothing I built touched it.

`make check-assurance` unchanged at 86/86 and 276/276, no dead links —
Requirement 5 stays `(planned)`, since the requirement as a whole is not
discharged even though this part of it is.

Refs: 013/Req-5, #645, #678

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Jacob Verhoeks <jjverhoeks@schubergphilis.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Ilja Heitlager <iheitlager@schubergphilis.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