Purity / rebuild guarantee test (#30) #60

Merged
christian merged 1 commits from feat/purity-rebuild-test into main 2026-07-09 22:41:07 +00:00
Owner

Closes #30.

Proves the D4 purity invariant: delete the cache → resync → no durable truth lost. The cache is a rebuildable index over gitea, never a source of truth. Two tests, one per durable cache that actually exists:

packages/core/src/cache/cache-purity-v0.test.ts — file-backed node:sqlite (main binds better-sqlite3 to the same CacheDriver seam). Build the SQLite mirror from a representative gitea snapshot (open+closed, milestones, reassignment, empty labels), capture every re-derived field, rm the .sqlite file, rebuild from the same snapshot → assert byte-identical. Plus a structural D4 guard: every issues column must map to a gitea field, so a future user-authored column that would break rebuild-ability fails the test instead of silently breaking the invariant.

apps/desktop/src/main/snapshot-store.test.ts — the shipped durable cache is the JSON snapshot-store. Delete the file → loadSnapshot returns null (degrades to no-cache, never throws), which forces the next getSnapshot to reconcile fresh from gitea. Corrupt/partial files likewise. This stands up vitest for the desktop main process (first unit tests there); electron is mocked, the snapshot path is injected.

Scope note — no native better-sqlite3 shipped

The SQLite mirror has no consumer on any hot path yet (scheduler/forecast/agent all run off the in-memory reconcile snapshot). Wiring it into main — native module + electron-rebuild + asarUnpack + a fresh .dmg re-verify — would add packaging risk for zero runtime benefit and put the verified .dmg at risk. The purity invariant is proven at the seam for both caches; the native driver migration is deferred until SQLite is load-bearing.

Verification

  • core: 157 passed (incl. 3 new purity tests) · tsc --noEmit clean
  • desktop: 4 passed (new suite) · tsc --noEmit clean

🤖 Generated with Claude Code

Closes #30. Proves the D4 purity invariant: **delete the cache → resync → no durable truth lost.** The cache is a rebuildable index over gitea, never a source of truth. Two tests, one per durable cache that actually exists: **`packages/core/src/cache/cache-purity-v0.test.ts`** — file-backed `node:sqlite` (main binds better-sqlite3 to the same `CacheDriver` seam). Build the SQLite mirror from a representative gitea snapshot (open+closed, milestones, reassignment, empty labels), capture every re-derived field, **`rm` the .sqlite file**, rebuild from the same snapshot → assert byte-identical. Plus a **structural D4 guard**: every `issues` column must map to a gitea field, so a future user-authored column that would break rebuild-ability fails the test instead of silently breaking the invariant. **`apps/desktop/src/main/snapshot-store.test.ts`** — the *shipped* durable cache is the JSON snapshot-store. Delete the file → `loadSnapshot` returns `null` (degrades to no-cache, never throws), which forces the next `getSnapshot` to reconcile fresh from gitea. Corrupt/partial files likewise. This stands up **vitest for the desktop main process** (first unit tests there); electron is mocked, the snapshot path is injected. ### Scope note — no native better-sqlite3 shipped The SQLite mirror has no consumer on any hot path yet (scheduler/forecast/agent all run off the in-memory reconcile snapshot). Wiring it into main — native module + electron-rebuild + `asarUnpack` + a fresh .dmg re-verify — would add packaging risk for zero runtime benefit and put the verified .dmg at risk. The purity invariant is proven at the seam for **both** caches; the native driver migration is deferred until SQLite is load-bearing. ### Verification - core: 157 passed (incl. 3 new purity tests) · `tsc --noEmit` clean - desktop: 4 passed (new suite) · `tsc --noEmit` clean 🤖 Generated with [Claude Code](https://claude.com/claude-code)
christian added 1 commit 2026-07-09 22:03:07 +00:00
The cache is a rebuildable index over gitea, never a source of truth (D4).
Two tests lock that invariant where the durable cache actually lives:

- packages/core: cache-purity-v0.test.ts — file-backed node:sqlite. Build the
  SQLite mirror from a gitea snapshot, capture every re-derived field, delete the
  .sqlite file, rebuild from the same snapshot, assert byte-identical. Plus a
  structural D4 guard: every issues-table column must map to a gitea field, so a
  future user-authored column can't silently break rebuild-ability.

- apps/desktop: snapshot-store.test.ts — the shipped durable cache is the JSON
  snapshot-store. Delete the file → loadSnapshot returns null (degrades to
  no-cache, never throws), which is what forces the next getSnapshot to reconcile
  fresh from gitea. Corrupt/partial files are likewise treated as no-cache.
  Stands up vitest for the desktop main process (first unit tests there);
  electron is mocked, snapshot path is injected.

No native better-sqlite3 shipped: the SQLite mirror has no consumer on any hot
path yet, so wiring it into main (native module + asarUnpack + dmg re-verify)
would add packaging risk for no runtime benefit. The purity invariant is proven
at the seam for both caches; the native driver migration is deferred until
SQLite becomes load-bearing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
christian merged commit e317c1099d into main 2026-07-09 22:41:07 +00:00
Sign in to join this conversation.
No description provided.