[Test][CI] Run the store-emdash contract and races on D1 under the workers pool, nightly - #250
Merged
Conversation
…rkers pool, nightly D1 is the dialect a deployed storefront runs on, and nothing tested it. The conditional-write primitives ride the host's SQLite branch there by inference: `updateIf` is one `UPDATE … SET data = json_set(…) … RETURNING data`, and only that path depends on the revision trigger the conditional-write migration creates — `put` and `compareAndSet` assign their own `crypto.randomUUID()`. better-sqlite3 runs the same SQL against a different engine build in a different process model, so agreement was an assumption. This tier turns it into evidence. The repository instantiates inside workerd from the root `emdash` entry directly — no stub plugin, no sandbox-bridge fallback. The predicted obstacle (the root entry's `astro:content` / `@tiptap/core` / `virtual:emdash/*` graph) belongs to the host's source tree; the published dist has it bundled and resolved. The dialect comes from the host's own `createDialect` reading the `DB` binding out of `cloudflare:workers`, which makes this the only tier that observes the host's wiring rather than ours; the schema comes from the full `runMigrations` set; and miniflare gets the storefront's own compatibility date and flags, so a divergence found here means something about production. No divergence from sqlite or Postgres on any primitive or contract case. The primitive suite is the Node one case-for-case — the ten-way compare-and-set included, because starting ten attempts on one revision before any of them writes is a claim about `WHERE revision = ?` and not about scheduling — plus the assertions the Node tier has no reason to make: the revision triggers exist on D1, and they fire for a writer that supplies none. `inventoryStoreContract` passes in full, 50 cases, no skips, and that now includes the W1 crash-window case: it reads `abandonPending` off the harness and returns early when absent, so a harness without the hook passes it while asserting nothing. `maxCasAttempts` on D1 is 2 of a ceiling of 12. The race runs the M=5/N=50 shape, and the file says plainly what it means here: one miniflare isolate interleaves promises but never executes two statements at once, so this is an interleaving check — strictly stronger than the sequential contract path, strictly weaker than simultaneity. Atomicity under simultaneous writers stays the Postgres tier's job and the no-oversell gate. The crash tier is ported, not reused. Its eighteen cases live inside a closure passed to `describeEachDialect`, which imports better-sqlite3 and pg at module scope and cannot load in workerd at all; making them portable means splitting that harness, which is its own change. The seams whose failure would be a dialect failure are ported — both injection mechanisms, the ordering rule, and the cross-SKU `commitMany` — and the four that are not are named in the file header and the README rather than quietly dropped. Wiring: its own vitest project and config, invoked by `pnpm test:d1`, kept out of the default battery so the root `fileParallelism` guard under Postgres is untouched. The D1 files are `*.spec.ts` so neither the default project's glob nor `scripts/pg-test-files.sh` can pick them up. CI runs it nightly and on manual dispatch, never per PR, under a 30-minute ceiling: it boots workerd and migrates a database per file, which is a real budget item for evidence that only moves when the adapter or the host build does. Everything is the local miniflare simulator — no account, token, remote database or deployment is involved. What the toolchain costs, said out loud rather than discovered later. The pool pins its wrangler and miniflare exactly, and that miniflare pins its own workerd exactly, so this adds a third workerd build that only the nightly executes and every install pays for — and because pnpm picks the highest workerd in the graph, `sites/staging`'s `@astrojs/cloudflare` peer-resolves workerd 1.20260710.1 -> 1.20260911.1, so the storefront build runs the newer runtime from here on (its wrangler is unchanged at 4.110.0). Overriding wrangler back to the catalog version was tried and undoes neither effect, because miniflare's pin is what carries workerd, so the override is deliberately absent rather than forgotten; the whole toolchain is enumerated in `minimumReleaseAgeExclude` instead. `@emnapi/runtime` floats 1.11.1 -> 1.11.3 in one sharp snapshot, which merely makes the lockfile self-consistent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011NjdC8awspUte5wML6eY2X
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a third storage tier for
store-emdash: the same conditional-write primitives, the full inventory contract, the no-oversell race, and a subset of the crash seams now run against real D1 under the Cloudflare workers Vitest integration, against local miniflare. It runs as a nightly CI job rather than a per-PR gate, is the fifth in a planned sequence of storage-tier increments, and changes no runtime source — test and CI wiring only.The two questions this tier answers
PluginStorageRepositorybe instantiated inside the workers pool at all? Yes — it loads from the vendored build's root entry with no virtual-module stubs, because the published dist bundles what the host's own source tree would otherwise need stubbed.updateIf'sRETURNINGreturns the post-image,json_setpreserves untouched fields,updateIfnever inserts, and the revision-assignment trigger from the conditional-write migration exists on D1 and fires exactly as expected.What it proves and does not
Dependency cost, disclosed
wranglerandminiflareexactly, adding a thirdworkerdruntime that every install now pays for, not only the nightly job.workerdmoves to the newer version while its ownwranglerpin is unchanged; builds and tests are green today, and the staging cut-over increment will re-verify under whichever runtime it resolves to at that time.wranglerback to the catalog version was tried and does not remove the extra runtime, since the pin that actually carries it lives inminiflare.CI
A nightly
d1job runs on a daily cron plus manual dispatch, with a 30-minute timeout. Per-PR jobs are unchanged.Verification
Review
Two independent reviews: round 1 returned one approve and one request for the heal-case hook on the D1 harness; round 2, both approve. An independent verification run passed, including the full battery.
🤖 Generated with Claude Code
https://claude.ai/code/session_011NjdC8awspUte5wML6eY2X