Skip to content

[Test][CI] Run the store-emdash contract and races on D1 under the workers pool, nightly - #250

Merged
vedanshujain merged 1 commit into
feat/in-process-commercefrom
test/store-emdash-d1-tier
Sep 13, 2026
Merged

vedanshujain merged 1 commit into
feat/in-process-commercefrom
test/store-emdash-d1-tier

Conversation

@vedanshujain

Copy link
Copy Markdown
Contributor

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

  • Can the host's PluginStorageRepository be 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.
  • Does D1 agree with better-sqlite3 and Postgres on the conditional-write primitives? Yes, with no divergence on any primitive or contract case: updateIf's RETURNING returns the post-image, json_set preserves untouched fields, updateIf never 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

  • The full inventory contract passes 50 of 50 cases on D1, no skips, including the crash-window case with its heal-case hook ported so the assertion is non-vacuous.
  • A single miniflare isolate gives interleaving, not simultaneity — strictly stronger than the sequential contract path, strictly weaker than a true concurrency proof. Postgres remains the atomicity gate for the no-oversell guarantee.
  • 5 of 18 crash seams are ported; the rest are named as a gap pending a split of the shared fault-injection harness into a driver-agnostic form.

Dependency cost, disclosed

  • The workers Vitest plugin pins its own wrangler and miniflare exactly, adding a third workerd runtime that every install now pays for, not only the nightly job.
  • Because the highest version in the dependency graph wins, the storefront's peer-resolved workerd moves to the newer version while its own wrangler pin 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.
  • An override pinning wrangler back to the catalog version was tried and does not remove the extra runtime, since the pin that actually carries it lives in miniflare.
  • The new toolchain coordinates are enumerated in the release-age exclusion list so nothing about the added surface is implicit.

CI

A nightly d1 job runs on a daily cron plus manual dispatch, with a 30-minute timeout. Per-PR jobs are unchanged.

Verification

Check Result
Clean, frozen reinstall (one host copy) pass
Lint / typecheck / format clean
Build ok
D1 suite (4 files) 82 passed — contract 51, primitives 24, seams 5, race 2
No-oversell race exactly 5 winners per loop
Existing sqlite project unchanged, 91 passed
Host-pin check 4 passed
Full battery 211 files / 4052 tests passed, 0 failed

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

…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
@vedanshujain
vedanshujain merged commit 602c084 into feat/in-process-commerce Sep 13, 2026
2 checks passed
@vedanshujain
vedanshujain deleted the test/store-emdash-d1-tier branch September 13, 2026 17:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant