Skip to content

[CI] INC-D6: de-vendor the EmDash host onto the released emdash@0.38.0 - #295

Merged
vedanshujain merged 4 commits into
feat/in-process-commercefrom
chore/unvendor-emdash
Sep 20, 2026
Merged

vedanshujain merged 4 commits into
feat/in-process-commercefrom
chore/unvendor-emdash

Conversation

@vedanshujain

Copy link
Copy Markdown
Contributor

INC-D6, the last increment of work order 02 before the integration branch merges. vendor/ was always temporary — four tarballs, a recorded diff and a rebuild script, kept only until a release carried the conditional-write primitives. emdash@0.38.0, published 2026-09-15, is the first release cut after that PR merged upstream, so the directory goes.

The overrides are deleted, not repointed

Every reason the four file: overrides existed is gone at this release:

  • @emdash-cms/admin@0.38.0 now exports ./portable-text-table, and @emdash-cms/registry-client@0.6.0 now exports ./listing-policy — the two unreleased subpaths the core build reaches into, whose absence at 0.37.0/0.5.0 is what forced those siblings to be vendored beside the core at all.
  • The quiet override, @emdash-cms/cloudflare, still pins emdash exactly — but it now pins 0.38.0, which is the version the manifests themselves name. The exact pin and the manifests agree, so a single copy resolves with no help.

Exactly one emdash@ in the store, verified two ways: one emdash@0.38.0 directory in node_modules/.pnpm, one emdash@0.38.0 key in pnpm-lock.yaml.

That agreement is a coincidence rather than a guarantee, which is why sites/staging/test/host-pin.test.ts is kept and updated rather than deleted. A future @emdash-cms/cloudflare pinning some other exact emdash would put a second copy in the store and bind the Worker bridge to a host without the primitives — no install error, no type error. That test is what makes it loud, and the remedy is to bring an exact emdash override back.

minimumReleaseAgeExclude grows rather than shrinks: the four packages were absent from it only because file: tarballs bypass the release-age check entirely. They resolve from the registry again, so the whole 0.38 train is listed.

R13 did not materialize — no migration rename, no deploy

The released 0.38.0 numbers the conditional-write migration 077_plugin_storage_revisions, the same name the vendored merge had renumbered it to. Stronger than that: the released build's migration runner is byte-identical to the vendored build's (519 lines, diff silent), the migrations/ directory listings match, and both carry 76 migrations in the same order with 077_plugin_storage_revisions last in MIGRATION_NAMES.

So the fast-path hazard the risk exists for — runMigrations short-circuiting on appliedCount >= MIGRATION_COUNT and silently never applying a real migration — has no way to fire on this swap. A database migrated by the vendored build holds exactly the rows the released runner expects, by name. No staging rename was needed and none was performed; nothing was deployed and nothing was written. The read-only check procedure is recorded in the memory note anyway, because the next host bump may genuinely need it.

vendor/README.md is folded in, not lost

Its full record — pinned base and PR-head SHAs, the migration number, why each of the four tarballs was required, why each override was load-bearing, the conflict resolutions and the build evidence — moves into §6 of plans/work-order-02-fold-service-into-plugin-memory.md, the section reserved for exactly this. §5 records what the release swap settled. The generalisable lesson is kept past the vendoring: the host monorepo's workspace packages can carry source newer than the release their version names, so any sibling whose unreleased source the core reaches has to be vendored alongside it.

A pre-existing red gate, found and fixed

pnpm test:e2e was already failing on the integration branch before this increment touched anything. sites/staging/e2e/harness.spec.ts's ADR-0006 additive gate asserts the set of skip-shaped constructs across the sandbox suites exactly, so it fails when its allowance list is too generous and when it is too strict. It was both at once:

  • Too generous: it still permitted a describe.skipIf in account-routes and download-route under the documented Postgres gate. The mode-collapse retrofit moved both suites onto the plugin's own document store, so neither is conditional any more.
  • Too strict: it did not permit the thirteen test.todo cases in storefront-checkout or the one in reports-widget, all deliberately parked when the HTTP transport was deleted, each naming its blocking work in its own title.

Both corrected, and the matcher tightened to require a trailing ( so that prose about a parked case — these suites explain themselves at length — no longer counts as a skip. The gate is stricter after this than before it. That the branch's e2e had been red for several increments without anyone noticing is worth flagging on its own.

Verification

Full battery on the released build, run from the worktree root:

Gate Result
pnpm lint (incl. the domain-purity dep check) clean — 1361 modules / 2979 dependencies cruised, no violations
pnpm typecheck clean
pnpm -r build green, all 9 projects
pnpm test 224 files passed, 17 skipped; 4348 passed, 820 skipped, 14 todo
pnpm test:pg (real Postgres) 60 files passed; 1551 passed, 7 skipped
pnpm test:d1 — T3, the production dialect 14 files passed; 525 passed, 0 skipped
pnpm test:e2e green — 13 passed, 21 skipped (the browser-driven specs, which gate on a running site)
pnpm install --frozen-lockfile clean, lockfile in sync

INC-A0's re-verification list was walked again item by item against the released build rather than the vendored one; the outcome is in the review notes below.

Does not touch README.md, DEPLOYMENT.md or adr/* — already correct from INC-D4. Does not merge the integration branch to main; that is a separate step.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CQbJYWWm8tf8owshm7XRp8

vedanshujain and others added 3 commits September 20, 2026 16:36
`0.38.0`, published 2026-09-15, is the first release cut after the
conditional-write PR merged upstream, so the primitives Otta's commerce truth
rides on — `updateIf`, `getVersioned`, `compareAndSet`, `compareAndDelete` —
are released code now rather than a locally built merge.

The four `file:` overrides are DELETED rather than repointed, because every
reason they existed is gone at this release. `@emdash-cms/admin@0.38.0` exports
`./portable-text-table` and `@emdash-cms/registry-client@0.6.0` exports
`./listing-policy` — the two unreleased subpaths whose absence forced those
siblings to be vendored beside the core. And the quiet override,
`@emdash-cms/cloudflare`, still pins `emdash` exactly but now pins `0.38.0`,
which is the version the manifests themselves name, so the exact pin and the
manifests agree and one copy resolves with no help: one `emdash@0.38.0` in
`node_modules/.pnpm`, one key in the lockfile.

That agreement is a coincidence, not a guarantee, which is why
`host-pin.test.ts` is kept rather than deleted. A future
`@emdash-cms/cloudflare` that pins some other exact `emdash` puts a second copy
in the store and binds the Worker bridge to a host WITHOUT the primitives — no
install error, no type error. The test is what makes that loud; the remedy, if
it ever fires, is to bring back an exact `emdash` override.

`minimumReleaseAgeExclude` grows rather than shrinks: the four packages were
absent from it only because `file:` tarballs bypass the release-age check
entirely. They resolve from the registry again, so the whole 0.38 train is
listed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CQbJYWWm8tf8owshm7XRp8
…he memory note

`vendor/` was always temporary: four tarballs, a recorded diff and a rebuild
script, kept only until a release carried the conditional writes. `0.38.0`
does, so the directory goes — 9.6 MB of committed binaries and the script that
produced them.

Nothing is lost with it. `vendor/README.md` was the authoritative record of the
build — the pinned base and PR-head SHAs, the migration number, why each of the
four tarballs was required, why each override was load-bearing, the conflict
resolutions and the build evidence — and all of it moves into §6 of the work
order's memory note, which was reserved for exactly this. The temporary "how to
rebuild the tarballs" framing is replaced by what the release swap settled, and
the generalisable lesson is kept past the vendoring: the host monorepo's
workspace packages can carry source newer than the release their version names,
so any sibling whose unreleased source the core reaches has to be vendored
alongside it — which is what both extra tarballs were.

§5 records the R13 outcome. The released build's migration runner is
byte-identical to the vendored one — 76 migrations, same order, same
`077_plugin_storage_revisions` tail — so a database migrated by the vendored
build holds exactly the rows the released runner expects, by name. No staging
D1 rename was needed and none was performed. The read-only procedure for
checking it is recorded anyway, because the next host bump may genuinely need
it, along with the rule that applied rows are compared by NAME, never by count.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CQbJYWWm8tf8owshm7XRp8
…irections

The gate asserts the set of skip-shaped constructs across the sandbox suites
EXACTLY, so it fails when its allowance list is too generous AND when it is too
strict. It had become both at once, and the branch's `pnpm test:e2e` has been
red for several increments as a result — found while re-running the full
battery on the released host, not caused by it.

Too generous: `ALLOWED_SKIPS` still permitted a `describe.skipIf` in
`account-routes` and `download-route` under the documented Postgres gate. The
mode-collapse retrofit moved both suites onto the plugin's own document store,
so neither is conditional any more and every sandbox suite runs
unconditionally. The allowance outlived the thing it allowed.

Too strict: it did not permit the thirteen `test.todo` cases in
`storefront-checkout` or the one in `reports-widget`, all deliberately parked
rather than deleted or inverted when the HTTP transport went, each naming its
blocking work in its own title so the coverage stays visible.

Both corrected, and the matcher tightened to require a trailing `(`. These
suites explain their parked cases at length, so a bare-word match counted every
backticked `test.todo` in a comment as a skip and would have filled the
allowance list with entries that are not code. The gate is stricter after this
than before it, and the parked-case count is now a number a reviewer can argue
with — it should only ever go down.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CQbJYWWm8tf8owshm7XRp8
@vedanshujain

Copy link
Copy Markdown
Contributor Author

INC-A0's re-verification list, walked again on the released build

A0's list was gathered against the vendored merge. Every item was re-executed against emdash@0.38.0 / @emdash-cms/admin@0.38.0 / @emdash-cms/registry-client@0.6.0 rather than carried over. 9 of 9 hold.

# Claim Outcome
1 Exports map adds and never removes PASS — the released core's exports key set is identical to the vendored build's: zero removed, zero added. All nine specifiers the repo actually imports (emdash, /astro, /db, /page, /plugin, /plugin-utils, /runtime, @emdash-cms/cloudflare, /db/d1, /worker) resolve.
2 Block Kit is still frozen PASS — Block Kit lives in @emdash-cms/blocks. Pulled 0.37.0 and 0.38.0 from npm: dist/ is byte-identical, diff -rq empty, only the version field differs.
3 Zero diff on the plugin contract PASS — PluginDescriptor, format, adminPages/adminEntry carriers, KVAccess, HttpAccess, RouteContext, ContentHookEvent, ContentAccess interface bodies are byte-identical. The only change to the declaration bundle is a cosmetic import-hash rename plus one pure 146-line insertion re-exporting @emdash-cms/blocks' element types — additive. (Worth recording: there is no literal StorageAccess type in either build; storage access is createStorageAccess() / PluginContext['storage'], unchanged.)
4 SandboxRunner.unavailableReason?() — flagged upstream as a future required break PASS, still optional — unavailableReason?(): string; in both builds. The hand-rolled harness is safe for now; this stays on the watch list.
5 @emdash-cms/admin was required because core reaches an unreleased subpath PASS, and now resolved — the released core dist still imports @emdash-cms/admin/portable-text-table, and 0.38.0 exports it. That is what lets the override go.
6 wrangler >= 4.99.0 peer PASS — @emdash-cms/cloudflare@0.38.0 still declares it; the catalog's ^4.99.0 satisfies it, so the entry stays.
7 engines.node >= 22.16 PASS, floor did not rise — released emdash@0.38.0 is >=22.16, identical to the vendored build. Root, sites/staging and packages/admin-react all declare >=22.16; CI pins the major line only.
8 better-sqlite3 as a direct dependency PASS — direct in sites/staging and packages/store-emdash via the catalog. Nothing relies on a transitive copy from the host.
9 The "additive only" changes really are additive PASS — ContentHookEvent, RouteContext, ContentAccess.create() bodies byte-identical; nothing previously optional became required, nothing removed. @emdash-cms/admin@0.38.0's public dist/index.d.ts is byte-identical to the vendored admin build.

Corroborating this from the source side: only six files differ between the vendored core's src/ and the released 0.38.0's — api/handlers/menus.ts, api/handlers/taxonomies.ts, astro/integration/vite-config.ts, components/Gallery.astro, components/InlinePortableTextEditor.tsx, media/responsive.ts. None is in the plugin contract, the storage repository, the sandbox bridge or the migration runner. No files added or removed.

And the R13 evidence, since it is the acceptance criterion

src/database/migrations/runner.ts is byte-identical across the two builds (519 lines, diff silent), the migrations/ directory listings match, and both carry 76 migrations in the same order — 001–009, then 011–077; there is no 010 in either — with 077_plugin_storage_revisions last in MIGRATION_NAMES in both. A database migrated by the vendored build therefore holds exactly the rows the released runner expects, by name, so the appliedCount >= MIGRATION_COUNT fast path cannot hide a missing migration here. No staging rename was required, none was performed, and nothing was deployed or written.

The read-only procedure is recorded in the memory note for the next host bump, which may genuinely need it: the table is _emdash_migrations (with _emdash_migrations_lock), the staging binding is DB, the real database name lives in the gitignored sites/staging/wrangler.local.jsonc, and applied rows are compared by name, never by count.

…ons/ reference

Both independent PR #295 reviewers flagged that pnpm-workspace.yaml's
one-copy comment said "invariant, not a coincidence" while going on to
describe how a future @emdash-cms/cloudflare release could break it —
the opposite of what the commit message for 2d6b6d4 and the memory
note's §5 already say. Flipped the polarity to match: it's a
coincidence of agreement, not an invariant.

Also fixed one sentence in the memory note's §5 that referenced a
nonexistent `migrations/` directory listing; migration names live in
runner.ts, as the rest of the same paragraph already correctly uses.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CQbJYWWm8tf8owshm7XRp8
@vedanshujain

Copy link
Copy Markdown
Contributor Author

QA — full battery + T3, final commit 5529fdb

Merge gate for the whole work order per the plan ("full battery plus T3 are green on the released build"). Both independent reviewers returned APPROVE (minor findings addressed in fix-up commit 5529fdb); this run is against that final commit.

Diff scope (origin/feat/in-process-commerce...HEAD): .changeset/, pnpm-lock.yaml, pnpm-workspace.yaml, packages/admin-react/package.json, packages/store-emdash/package.json, sites/staging/package.json, scripts/vendor-emdash.sh (deleted), vendor/* (deleted), sites/staging/e2e/harness.spec.ts, sites/staging/test/host-pin.test.ts, plans/work-order-02-fold-service-into-plugin-memory.md

Check Result
pnpm lint PASS (oxlint clean; depcruise: 0 violations)
pnpm typecheck PASS
pnpm -r build PASS (astro check clean; build banner confirms emdash v0.38.0 wired in)
pnpm test PASS — 224 files, 4348 tests passed, 820 skipped, 14 todo
pnpm test:pg (127.0.0.1:55432, not prod) PASS — 60 files, 1551 tests passed, 7 skipped
pnpm test:d1 (T3, production dialect) PASS — 14 files, 525 tests passed, 0 skipped
pnpm test:e2e PASS — 13 passed, 21 skipped (pre-existing seeded-data gates, unrelated to this diff); harness.spec.ts itself 12/12 green
pnpm install --frozen-lockfile PASS
pnpm why emdash exactly 1 version resolved (0.38.0)
vendor/ directory confirmed absent

Verdict: GO.

@vedanshujain
vedanshujain merged commit d0a6ecc into feat/in-process-commerce Sep 20, 2026
3 checks passed
@vedanshujain
vedanshujain deleted the chore/unvendor-emdash branch September 20, 2026 17:12
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