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.
1 file changed,
+171
−0
internal/sheets/store/wal_gate_test.go+171 −0
Intent
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. |
| +} |