Skip to content

[Adapters][Plugin][Test] Delete packages/service and packages/store-postgres - #290

Merged
vedanshujain merged 5 commits into
feat/in-process-commercefrom
chore/delete-service-and-store-postgres
Sep 20, 2026
Merged

vedanshujain merged 5 commits into
feat/in-process-commercefrom
chore/delete-service-and-store-postgres

Conversation

@vedanshujain

Copy link
Copy Markdown
Contributor

Summary

Deletes @otta-sh/service and @otta-sh/store-postgres entirely — both were fully retired as of
the prior increment's Worker/Hyperdrive/Neon retirement, so nothing depends on their deployment
surface anymore. Collapses commerceClientContract to a single in-process tier, removes 6 now-dead
public exports from @otta-sh/plugin's index, and rebuilds the four admin *Surface port
interfaces as standalone types (they no longer need to mirror an HTTP client's shape).

Verification

  • pnpm lint — pass, 0 violations (includes the dependency-cruiser domain-purity check)
  • pnpm typecheck — pass
  • pnpm format:check — pass (640 files)
  • pnpm -r build — pass
  • pnpm test — 224 files / 4349 tests passed (820 skipped, 14 todo)
  • pnpm test:pg (against Postgres) — 60 files / 1551 tests passed (7 skipped), including two
    newly-added, barrier-synchronized concurrency race tests (order-notes concurrent append
    idempotency; order-store concurrent markPaid audit-once). Both were verified by mutation
    testing to genuinely force CAS contention rather than pass vacuously.
  • pnpm test:d1 — 14 files / 525 tests passed
  • changeset status — exit 0 (patch: site-staging; minor: plugin, admin-presentation,
    admin-react, domain, store-emdash, payments-x402, payments-stripe)

A new changeset documents the package removals and the 6 removed @otta-sh/plugin exports.
~84 pre-existing changesets that referenced the deleted packages were swept to remove those stale
references.

GitHub issue #289 tracks four smaller, non-blocking test-coverage gaps (bounds checks) that were
intentionally deferred rather than blocking this PR.

Review

Two rounds of independent review plus a targeted follow-up round confirmed: domain-layer purity is
intact (dependency-cruiser rule), scope discipline holds (no unrelated changes, no
dependency/lockfile drift), and the concurrency race tests genuinely exercise contention rather than
passing vacuously.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CQbJYWWm8tf8owshm7XRp8

vedanshujain and others added 5 commits September 20, 2026 12:32
INC-D3b. With commerce running in-process inside the plugin since INC-D3a,
the standalone Hono service and the Kysely/Postgres store adapter are both
dead code. Both packages go entirely, along with their project references.

`@otta-sh/store-postgres` took its 24 migrations, every `Kysely*Store`, its
`.`/`./pg`/`./testing` subpaths, 29 `*.dialects.test.ts` files and 12
`*.pg.test.ts` race files with it. NONE of that concurrency coverage is
lost: every one of the 12 race files already has a same-named, re-pointed
`store-emdash` counterpart from the Phase A/B increments, and all 12 were
run against the local test Postgres and confirmed green BEFORE this
deletion — adjust-concurrency, coupon-no-over-redeem, no-oversell,
no-oversell-cart, no-oversell-checkout, no-oversell-checkout-multiline,
refund-race, resolve-reconciliation-race, restock-concurrency,
rules-cas-race, sku-rename-race and variant-sku-rename-race, 52 tests
passing. The no-oversell gate is intact; it just runs over the document
adapter now.

Nothing had to be rescued from the package. `store-emdash`'s dialect
harness builds its own Kysely `PostgresDialect`/`SqliteDialect` straight
from `kysely`, `pg` and `better-sqlite3`, all declared in its own
package.json, and `store-emdash/src/id-gen.ts` has carried its own copy of
`uuidIdGen` since it was written precisely so this copy could go.

`test:pg` needs no change to keep selecting the right files: its glob
walks `packages/*/test`, so it now resolves to 60 `store-emdash` files and
zero deleted ones. The CI `integration` job, its Postgres service
container and `test:pg` are all untouched.

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

INC-D3b. INC-D3a collapsed `makeCommerceClient`/`makeAdminClients` to the
in-process tier unconditionally, which orphaned every HTTP client behind
them. They go here, with the tests that only ever exercised the wire.

Deleted: `HttpCommerceClient` (whole module), and the four admin HTTP
clients — `AdminOrdersClient`, `AdminProductsClient`, `AdminRulesClient`,
`ReportingSettingsClient` — with their `*Options` types; the six
`http-commerce-client*.test.ts` suites; `commerce-client-contract.http.test.ts`;
`test/helpers/start-live-service.ts`; and `@hono/node-server`, which had
no other user. `commerceClientContract` now has one tier, the in-process
one.

What did NOT go: the wire types and the four `*Surface` ports, which the
console's surviving in-process code depends on throughout. Each Surface
was `Pick<ThatClient, …>`, so deleting the class would have taken the port
with it. Each is now an explicit interface with the signatures lifted
verbatim — Orders 12 methods, Products 6, Rules 25, ReportingSettings 6.
That is a strictly better home for them: the `Pick`-over-a-nominal-class
idiom existed only because `#`-private fields made the class unassignable,
and the property it was protecting ("every method is listed, so adding one
without deciding what the in-process tier does is a compile error") is
preserved. The four `InProcess*Client` classes now `implements` their
Surface, so tsc checks the lifted signatures structurally rather than
taking them on trust. The files are renamed to say what they now are:
`admin-*-client.ts` → `admin-*-surface.ts`, `reporting-client.ts` →
`reporting-settings-surface.ts`.

`test/helpers/stub-commerce-server.ts` is KEPT, renamed to
`stub-http-server.ts`. The plan had it deleted as a stand-in for the HTTP
transport, but it is not one any more: it is a generic recording HTTP
server, and three surviving sandbox suites use it for things that have
nothing to do with commerce — `sandbox-harness.test.ts` and
`in-process-egress.sandbox.test.ts` as an email-API endpoint proving
`allowedHosts` egress control, `stripe-settle-route.sandbox.test.ts` to
back a Settings re-render while asserting no secret leaks. Deleting it
would have silently dropped that coverage.

`playwright.config.ts` was a live break, not just stale prose: its
webServer stack still booted `packages/service/src/index.ts` and waited on
its `/health`. The stack is one process now, so that entry and the dead
`E2E_SERVICE_URL` knob are gone; §0.3's rule that no e2e surface may name
port 5432 is untouched and still enforced by `harness.spec.ts`.

The rest is prose: comments across the plugin, domain, store-emdash,
admin-react and the staging site that cited `@otta-sh/service` or
`@otta-sh/store-postgres` as present tense now read as history. The
synthetic fixture strings in `depcruise-boundary.test.ts` are deliberately
left — they test unresolved-specifier handling and must name packages that
do not resolve.

On the plan's open question for INC-D4: `packages/plugin/src/types.ts` is
unchanged, because the premise was a misreading. It holds no commerce wire
types at all — every hand-mirrored block in it mirrors the HOST (EmDash's
Block Kit contract) plus `HttpAccess`, the `ctx.http` capability surface,
which stays for the email API and the payment gateways. The types that did
mirror the service's wire format are the `*Wire` interfaces in the admin
surfaces and `commerce-client.ts`; the call to keep them, and the narrower
question left for D4, is recorded at the top of `commerce-client.ts`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CQbJYWWm8tf8owshm7XRp8
INC-D3b. `changeset version` hard-fails on a changeset naming a package
that is no longer in the workspace, so this is part of the deletion rather
than housekeeping after it.

Of 148 unreleased changesets, 59 named `@otta-sh/service` or
`@otta-sh/store-postgres` in their frontmatter. 51 also named a surviving
package and were re-pointed — the dead frontmatter line dropped, every
surviving line and the note kept. 8 were entirely about a deleted package
and are removed outright. 140 changesets remain.

Five of the re-pointed notes needed a prose edit too, because their
summaries made a claim that spanned the dead and surviving packages and
stopped being true once the line went: a count of "all four published
packages", a dangling "store-postgres stays patch" clause, and three
paragraphs explaining bump reasoning for a package that will never be
bumped again.

Left deliberately: 25 notes still describe, in the past tense, what
shipped inside those packages at the time. That is accurate history and
`changeset version` only reads frontmatter, so nothing breaks — but the
released CHANGELOG will carry bullets attributed to packages that no
longer exist, which is worth a pass in the D4/D5 docs increments.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CQbJYWWm8tf8owshm7XRp8
Review of the deletion increment found the changesets under-stated it and a
handful of guards that died with `@otta-sh/service` had no surviving
counterpart. This closes both.

**Changesets.** A new changeset records the increment itself: the six public
exports leaving `@otta-sh/plugin`'s entry point (a `minor`), and the removal of
`@otta-sh/service` and `@otta-sh/store-postgres`. Neither deleted package can be
named in frontmatter — changesets refuses a release plan for a package whose
directory is gone — so the removal is recorded in the prose of the package that
outlived them. The remaining changesets were swept: the earlier pass trimmed
their frontmatter but left bodies narrating deleted packages, dead REST routes
and deleted client classes, all of which would have published verbatim into
surviving packages' CHANGELOGs. Two changesets described nothing that survived
and are deleted.

**The two concurrency races are re-created, not just mourned.** The once-only
note append under a shared idempotency key, and the single audit event under
racing state flips, were local to the deleted adapter's dialect suites, so
`store-emdash` never inherited them. Both now run against `EmdashOrderNotesStore`
and `EmdashOrderStore` as `runIf(ctx.canRace)` cases in the existing
`describeEachDialect` blocks, on Postgres, where a race is real.

**Restored negative guards.** Cart lines carry no price; a guest's read of a
fulfilled or cancelled order drops the staff witness and the cancellation
detail. Each is a typed whitelist at the producer, so these are belt-and-braces
— but they are the assertions the deleted suite held. The admin route's
`public: false` gate, which had lost its only coverage, is pinned at the
manifest.

**Corrected a false comment.** Two comments claimed no guard existed for
`serializeCart` dropping `state`. It does: `serializeCart` is annotated
`: CartWire`, whose `state` is required, so dropping it fails to compile. The
comments now say so. Three `skipIf(tier.payments)` cases that skip on every
tier now explain themselves and point at their domain-layer coverage.

Smaller unasserted bounds in the in-process admin clients are tracked in #289.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CQbJYWWm8tf8owshm7XRp8
Both cases were mutation-tested and neither died. Breaking the loser path in
`EmdashOrderNotesStore.append` (return `appended: true` unconditionally) and in
`EmdashOrderStore`'s guarded flip (report a won flip on a refused
compare-and-set) left both suites GREEN. The CAS logic is correct; the tests
were not reaching it.

**Why.** `Promise.all` over N store calls is not a race here. `pg.Pool` opens
connections lazily, so the first caller takes the one warm connection and
completes its whole read-modify-write while its peers are still finishing a TCP
connect. Instrumented on the note-append shape: all 8 callers entered within
3 ms, the winner's pre-read returned at +15 ms and its create-if-absent
committed at +28 ms, and the other 7 pre-reads returned at +47 ms or later —
every one of them finding the committed note. So all 7 took the replay branch
and no two callers ever held the same revision. The `markPaid` case degenerates
the same way, one step earlier: the peers read `state: "paid"` and refuse at the
`doc.state !== fromState` guard without issuing a compare-and-set at all.

**The fix** is a barrier, not more callers. `barrierCall` joins the existing
fault-injection decorators: it holds the first N matching writes until every one
has arrived, then releases the crowd into the real repository at once. Arrival
at the barrier is itself the proof of contention — a caller only gets there
after its own read decided to write — so the two cases now assert
`barrier.arrived()` alongside their outcome. The barrier is one-shot, so the
retry each loser performs passes straight through and the crowd cannot deadlock.

Both mutations were re-applied against the barriered tests: 3/3 runs RED, with
8 of 8 appends and 12 of 12 flips claiming a win. Restored, 3/3 runs GREEN.

Two changesets from the same review round: `admin-wire-completeness` restates
the operator-facing warning that died with the REST service — the three admin
lists still issue their count concurrently with the page read, so each request
peaks at two host connections, now on the in-process path.
`entitlements-check-auth` gets back the concrete false→true behaviour change
that justifies its `minor` on `@otta-sh/domain`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CQbJYWWm8tf8owshm7XRp8
@vedanshujain
vedanshujain merged commit 80333d2 into feat/in-process-commerce Sep 20, 2026
2 checks passed
@vedanshujain
vedanshujain deleted the chore/delete-service-and-store-postgres branch September 20, 2026 14:28
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