Skip to content

E4 database-semantics experiment

This is Phase-0 evidence code for issue 6. It is not the v1 implementation and it will not be maintained past the research report it produces. It selects no driver, ORM, schema or migration tool.

What it is for

Design §7.2 requires revision conflicts, all-or-nothing publication, unique operation intent, ownership transitions, safe queue claims, migrations and restoration behaviour to be proved on each proposed backend. This prototype exercises each of them on PostgreSQL 17 (the investigation fixture) and on SQLite (the engine the pure-Go driver links), with one schema and every dialect difference an explicit, countable branch.

Scenario Semantic
S1 stale revision rejection: compare-and-set on a fragment's revision
S2 all-or-nothing publication, including client and server interruption and commit-unknown
S3 unique operation intent: one active operation per machine scope, idempotent by key
S4 ownership transitions: a generation-fenced takeover, and an attempt that must hold ownership
S5 queue claims: fenced leases, eight workers draining 400 jobs
S6 migrations: rollback on failure and on SIGKILL, concurrent runners
S7 restored state: what a snapshot/restore takes back and what gets issued again
S8 the single-writer cost: an unrelated write during a long publication

Five negative claims have a positive control that removes the mechanism and must make the reader find the failure: blind writes (S1), an unlocked source check (S2's race), check-then-insert ownership (S4's attempt), a naive claim (S5), unlocked or deferred migration runners (S6). On SQLite the S2, S4 and S5 controls find no failure; see the report §3.2. S2's interruption rows, S3's scope uniqueness and S4's takeover have none; those claims rest on the reader's result alone.

How a row is judged

Each row is one scenario on one backend. run/all writes its expectation before running it; the clients are separate e4db processes started together behind a start barrier; and the outcome is read back from the database by an independent reader, not from the clients' own reports: psql inside the fixture's PostgreSQL container, and the host's sqlite3 CLI, a different SQLite build from the one the prototype links. The reader queries are in readers/. run/lib.sh compares and records one line in cells.tsv per row, and keeps every client's output, every injection and every reader result as the row's transcript. A mismatch is recorded, not retried.

Running it

A capture needs a fresh fixture, and E4_OUT must name a new, empty absolute path on disk, outside this checkout:

fixtures/bin/up
E4_OUT=<somewhere with ~50 MiB> experiments/e4-database-semantics/run/all
fixtures/bin/evidence <the same directory>
cp -a fixtures/.state/evidence/<bundle> <the same directory>/bundle
E4_OUT=<the same directory> experiments/e4-database-semantics/run/collect-evidence
fixtures/bin/down

run/all builds e4db, runs the matrix and then the prototype's own go test against both backends. run/collect-evidence copies the text evidence into evidence/, packs the transcripts one file per scenario and backend, rewrites local paths to placeholders, and refuses anything matching this run's fixture credentials or a path in the operator's home. It refuses to run without the bundle copied to E4_OUT/bundle, which stays there, and commits a summary of it (bundle-summary.txt: versions and leak scan) and a manifest of every copied file with its SHA-256 (bundle-manifest.txt), with the PostgreSQL data directory as one line: the digest of its part of the full manifest, which stays in E4_OUT.

The unit tests need no fixture: go test ./... runs them on a temporary SQLite file, and E4_TEST_PG_DSN=<dsn> go test ./... on PostgreSQL.

The PostgreSQL DSN, which carries the password, reaches e4db through the environment (E4_PG_DSN), never argv. E4_BACKEND, E4_SQLITE_PATH and E4_SQLITE_TXLOCK select the rest; see main.go.

What it deliberately does not do

It does not dispatch to Talos, model approvals or the commitment-to-send boundary; that is issue 7. It does not run MySQL or MariaDB: the fixture has no such service, and the report records that absence. It does not test replication, failover or any backup tool the operator would run; the design assigns those to the operator.