oficina / sheets Public

Close Task 2's gap: prove the gate against a real, uncheckpointed WAL write

TestUncheckpointedWriteLandsInRealWALFile writes plaintext through the
real rastrillo/db driver in actual WAL mode, never checkpoints, and
confirms both mechanically (WAL file bytes) and via
sealtest.AssertNoPlaintext (run in a subprocess, since it is expected
to Fatal) that the gate catches it in <db>-wal.

TestSealedWriteIsUnflushedBeforeExplicitCheckpoint proves nothing
implicitly checkpoints or vacuums between AppendInput and the gate, so
TestInputEventIsSealedOnDisk's checkpoint() call is doing real work
rather than inspecting an already-settled file.
Paul Campbell a4aacc53492715def18286a67701c7ddaa774d24 parent 711cab9
1 file changed, +171 −0
  • internal/sheets/store/wal_gate_test.go +171 −0
diff --git a/internal/sheets/store/wal_gate_test.go b/internal/sheets/store/wal_gate_test.go
new file mode 100644
index 0000000..42d2ca2
--- /dev/null
+++ b/internal/sheets/store/wal_gate_test.go
@@ -0,0 +1,171 @@
+package store_test
+
+// This file closes a gap Task 2 explicitly left open. Task 2 built the
+// plaintext gate and proved it can fail, but only against a plain file
+// standing in for a database, with a synthetic -wal sidecar it seeded
+// itself. There was no real store and no real SQLite driver yet.
+//
+// Now there is one. These tests prove, against the actual rastrillo/db
+// driver in actual WAL mode:
+//
+// 1. TestUncheckpointedWriteLandsInRealWALFile: a write that is never
+// checkpointed really does put bytes in <db>-wal on disk (not just in
+// an in-process buffer), and sealtest.AssertNoPlaintext really does
+// find a plaintext canary there.
+// 2. TestSealedWriteIsUnflushedBeforeExplicitCheckpoint: nothing between
+// an insert and the gate implicitly checkpoints or vacuums the file, so
+// TestInputEventIsSealedOnDisk's own checkpoint() call is doing real
+// work — the gate in that test is inspecting a file settled by an
+// explicit action, not one that happened to already be settled for an
+// unrelated reason.
+
+import (
+ "bytes"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "testing"
+ "time"
+
+ "amadan.net/oficina/sheets/internal/sheets/seal"
+ "amadan.net/oficina/sheets/internal/sheets/sealtest"
+ "amadan.net/oficina/sheets/internal/sheets/store"
+)
+
+// TestUncheckpointedWriteLandsInRealWALFile writes a plaintext canary
+// directly through the real driver — standing in for a sealing bug, the
+// same way step 5 of the task brief breaks AppendInput — in WAL mode, and
+// deliberately never checkpoints. It then confirms two things in order:
+//
+// - mechanically, without going through sealtest at all, that the bytes
+// are really sitting in <db>-wal on disk and NOT yet in the main file;
+// if this failed, the whole proof would be worthless because it would
+// mean the write never left memory, or WAL mode was not actually on;
+// - that sealtest.AssertNoPlaintext, run against exactly this
+// uncheckpointed file, reports the canary living in the -wal file.
+//
+// The second half runs sealtest.AssertNoPlaintext in a subprocess: that
+// function calls t.Fatal on a real leak, and here a leak is exactly what
+// we expect, so the failure has to be captured rather than allowed to fail
+// this test's own build.
+func TestUncheckpointedWriteLandsInRealWALFile(t *testing.T) {
+ const canary = "CANARY-CELL-VALUE-8f3a"
+
+ dir := t.TempDir()
+ dbPath := filepath.Join(dir, "test.db")
+ db := openTestDB(t, dbPath)
+
+ var jm string
+ if err := db.Raw("PRAGMA journal_mode").Scan(&jm).Error; err != nil {
+ t.Fatal(err)
+ }
+ if jm != "wal" {
+ t.Fatalf("journal_mode = %q, want wal (rastrillo/db.Open's DSN should have set this)", jm)
+ }
+
+ // A raw insert of plaintext bypassing seal.Sealer entirely — this is
+ // what a sealing bug looks like on the wire, not what AppendInput does
+ // today.
+ if err := db.Exec(
+ `INSERT INTO events (kind, tab_id, actor_id, payload_sealed, trigger_id, derived_count, created_at)
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
+ store.KindInput, uint64(1), uint64(7), []byte(canary), uint64(0), 0, time.Now(),
+ ).Error; err != nil {
+ t.Fatalf("raw insert: %v", err)
+ }
+
+ walBytes, err := os.ReadFile(dbPath + "-wal")
+ if err != nil {
+ t.Fatalf("read WAL file: %v (a missing WAL file here would mean the write never reached disk before a checkpoint, making this proof meaningless)", err)
+ }
+ if len(walBytes) == 0 {
+ t.Fatal("WAL file is empty; the write did not land there")
+ }
+ if !bytes.Contains(walBytes, []byte(canary)) {
+ t.Fatal("canary not found in the uncheckpointed WAL file bytes; cannot confirm the write actually reached <db>-wal on disk")
+ }
+ mainBytes, err := os.ReadFile(dbPath)
+ if err != nil {
+ t.Fatalf("read main db file: %v", err)
+ }
+ if bytes.Contains(mainBytes, []byte(canary)) {
+ t.Fatal("canary already present in the main db file; this write was not actually sitting only in the WAL, which defeats the point of this test")
+ }
+
+ // Now ask the real gate. Run in a subprocess because AssertNoPlaintext
+ // is expected to Fatal here, and that is the assertion, not a bug.
+ if os.Getenv("SHEETS_WAL_GATE_SUBPROCESS") == "1" {
+ sealtest.AssertNoPlaintext(t, dbPath)
+ return
+ }
+ cmd := exec.Command(os.Args[0],
+ "-test.run=^TestUncheckpointedWriteLandsInRealWALFile$", "-test.v")
+ cmd.Env = append(os.Environ(), "SHEETS_WAL_GATE_SUBPROCESS=1")
+ // The subprocess re-executes this whole test function, including the
+ // setup above, into a NEW temp dir (t.TempDir() differs per process),
+ // so it performs its own insert rather than reusing dbPath. That is
+ // fine: the property under test — an uncheckpointed real WAL write is
+ // caught — does not depend on reusing the parent's files.
+ out, runErr := cmd.CombinedOutput()
+ if runErr == nil {
+ t.Fatalf("expected the gate subprocess to fail against an uncheckpointed WAL write; it exited 0:\n%s", out)
+ }
+ if !strings.Contains(string(out), "test.db-wal") {
+ t.Fatalf("gate subprocess failed, but not evidently by finding the canary in the WAL file; output:\n%s", out)
+ }
+ if !strings.Contains(string(out), canary) {
+ t.Fatalf("gate subprocess failure did not name the canary; output:\n%s", out)
+ }
+}
+
+// TestSealedWriteIsUnflushedBeforeExplicitCheckpoint proves that nothing
+// between AppendInput and the gate implicitly checkpoints (or vacuums) the
+// database. If it did, TestInputEventIsSealedOnDisk's checkpoint() call
+// would be inspecting a file that was already settled for some unrelated
+// reason — a gate that would appear to work but would actually be agreeing
+// with itself, exactly what the brief warns against.
+func TestSealedWriteIsUnflushedBeforeExplicitCheckpoint(t *testing.T) {
+ dir := t.TempDir()
+ dbPath := filepath.Join(dir, "test.db")
+ db := openTestDB(t, dbPath)
+ s, err := seal.New(make32())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := store.AppendInput(db, s, store.InputEvent{
+ TabID: 1, RowID: 1, ColID: 1, ActorID: 7,
+ NewRaw: "CANARY-CELL-VALUE-8f3a",
+ }); err != nil {
+ t.Fatal(err)
+ }
+
+ // Before any explicit checkpoint call: the WAL file must already exist
+ // and be non-empty. If it is missing or empty here, the insert above
+ // was already folded into the main file by something other than an
+ // explicit checkpoint — a connection-close checkpoint, pool churn,
+ // AutoMigrate side effects, or journal_mode defaulting to something
+ // other than WAL — and the sealed-path test's checkpoint() call would
+ // be a no-op.
+ info, err := os.Stat(dbPath + "-wal")
+ if err != nil {
+ t.Fatalf("stat WAL before any explicit checkpoint: %v", err)
+ }
+ if info.Size() == 0 {
+ t.Fatal("WAL file is empty before the explicit checkpoint call; the write was already flushed by something implicit, which would make checkpoint() a no-op")
+ }
+
+ var autoVacuum int
+ if err := db.Raw("PRAGMA auto_vacuum").Scan(&autoVacuum).Error; err != nil {
+ t.Fatal(err)
+ }
+ if autoVacuum != 0 {
+ t.Fatalf("auto_vacuum = %d, want 0 (off); an active auto-vacuum could rewrite pages behind the gate's back", autoVacuum)
+ }
+
+ // checkpoint() itself is exercised, and its precondition proven
+ // meaningful, by TestInputEventIsSealedOnDisk. This test's job stops
+ // at proving that precondition — that there is real, unflushed work
+ // for it to do.
+}