So getSql() returns null and lib/orders-store.ts / lib/subscriptions-store.ts are serving out of module-global Maps in production right now, across 7 API routes:
For orders, payments and subscriptions that is not a degraded mode — it is invisible loss of money-bearing state. The API returns 201 and the row evaporates on the next restart or on any other replica.
The two guards
1. Legacy Next.js stores now call getSqlForDurableStore(), which reuses the sameresolveStoreMode decision every src/repositories/* store already uses, instead of re-implementing a second production check.
The reuse is load-bearing, not tidiness. isProductionRuntime deliberately exempts NEXT_PHASE=phase-production-build, because next build runs page-data collection under NODE_ENV=production while serving nothing. The naive process.env.NODE_ENV === "production" check would have failed the build rather than protecting a request path. There is a test pinning exactly that.
2. LocalBlobStore writes document bytes to a container-local disk while document-capture-service registers an artifact pointer claiming the bytes are stored — durable object storage silently swapped for ephemeral disk. It is the default blob store for both the capture and extraction services (opts?.blobStore ?? new LocalBlobStore()). In production it now raises DEPENDENCY_UNAVAILABLE (capability blocked: no ST0RE bytes path exists yet) instead of fabricating success.
Asserted per-operation, not in the constructor, so merely wiring the services is not fatal — only actually persisting or reading bytes is. A test pins that too.
Evidence
Check
Result
vitest run (full suite)
956 passed / 184 skipped
npm run typecheck (blocking tsconfig.check.json)
clean, exit 0
npm run deps:check
0 errors, 7 pre-existing circular warnings
npm run lint
17 problems — byte-identical to main's baseline; 0 in changed files
non-test call sites of the new function
7 (→ 7 live API routes)
Mutation-tested, both directions:
Mutation
Result
getSqlForDurableStore bypasses resolveStoreMode
2 failed / 6 passed
drop assertAllowed("write")
2 failed / 6 passed
drop assertAllowed("read")
1 failed / 7 passed
all restored
8 passed
Guards are asserted in both directions — production refuses, and the dev/test seam, the next build phase, and the configured-production path all still work. A guard that degenerated into a blanket refusal would fail these too.
Deployment note — requires action before deploy
Merging changes no runtime: there is no Gitea webhook on any repo in this estate (webhook table = 0 rows, hook_task = 0, verified against a positive control of 402 repositories), so deploys are manual.
Deploying this build requires provisioning DATABASE_URL for 0pt first, or the order/subscription routes will correctly return DEPENDENCY_UNAVAILABLE instead of silently accepting orders into memory. That provisioning is a Simon action.
Not claimed: no live verification, no restore proof. This is a code guard only.
## What
Closes the two storage paths that the `resolveStoreMode` kill switch (Master-DoD §3/§10) did **not** cover. Both are live production execution paths today.
## Why this is not theoretical
The live 0pt deployment (Coolify app 54, container `shxd6lepxuxfsjsp0ath3z1g-…`, up 42h healthy) runs with:
- `NODE_ENV=production`
- `DATABASE_URL`, `POSTGRES_URL`, `POSTGRES_PRISMA_URL` **all unset**
So `getSql()` returns `null` and `lib/orders-store.ts` / `lib/subscriptions-store.ts` are serving **out of module-global `Map`s in production right now**, across 7 API routes:
`app/api/orders`, `app/api/orders/[id]`, `app/api/subscriptions`, `app/api/payments/[order_id]/deposit`, `app/api/payments/[order_id]/release`, `app/api/webhooks/stripe`, `app/api/admin/metrics`
For orders, payments and subscriptions that is not a degraded mode — it is invisible loss of money-bearing state. The API returns 201 and the row evaporates on the next restart or on any other replica.
## The two guards
**1. Legacy Next.js stores** now call `getSqlForDurableStore()`, which reuses the **same** `resolveStoreMode` decision every `src/repositories/*` store already uses, instead of re-implementing a second production check.
The reuse is load-bearing, not tidiness. `isProductionRuntime` deliberately exempts `NEXT_PHASE=phase-production-build`, because `next build` runs page-data collection under `NODE_ENV=production` while serving nothing. The naive `process.env.NODE_ENV === "production"` check would have **failed the build** rather than protecting a request path. There is a test pinning exactly that.
**2. `LocalBlobStore`** writes document bytes to a container-local disk while `document-capture-service` registers an artifact pointer claiming the bytes are stored — durable object storage silently swapped for ephemeral disk. It is the **default** blob store for both the capture and extraction services (`opts?.blobStore ?? new LocalBlobStore()`). In production it now raises `DEPENDENCY_UNAVAILABLE` (capability blocked: no ST0RE bytes path exists yet) instead of fabricating success.
Asserted **per-operation, not in the constructor**, so merely wiring the services is not fatal — only actually persisting or reading bytes is. A test pins that too.
## Evidence
| Check | Result |
|---|---|
| `vitest run` (full suite) | **956 passed / 184 skipped** |
| `npm run typecheck` (blocking `tsconfig.check.json`) | clean, exit 0 |
| `npm run deps:check` | **0 errors**, 7 pre-existing circular warnings |
| `npm run lint` | 17 problems — **byte-identical to main's baseline**; 0 in changed files |
| non-test call sites of the new function | **7** (→ 7 live API routes) |
Mutation-tested, both directions:
| Mutation | Result |
|---|---|
| `getSqlForDurableStore` bypasses `resolveStoreMode` | 2 failed / 6 passed |
| drop `assertAllowed("write")` | 2 failed / 6 passed |
| drop `assertAllowed("read")` | 1 failed / 7 passed |
| all restored | **8 passed** |
Guards are asserted in both directions — production refuses, **and** the dev/test seam, the `next build` phase, and the configured-production path all still work. A guard that degenerated into a blanket refusal would fail these too.
## Deployment note — requires action before deploy
Merging changes no runtime: there is no Gitea webhook on any repo in this estate (`webhook` table = 0 rows, `hook_task` = 0, verified against a positive control of 402 repositories), so deploys are manual.
**Deploying this build requires provisioning `DATABASE_URL` for 0pt first**, or the order/subscription routes will correctly return `DEPENDENCY_UNAVAILABLE` instead of silently accepting orders into memory. That provisioning is a Simon action.
Not claimed: no live verification, no restore proof. This is a code guard only.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The `resolveStoreMode` kill switch (Master-DoD §3/§10) covered the newer
`src/repositories/*` stores but not two older paths, both of which are
live production execution paths today.
1. LEGACY NEXT.JS STORES. `lib/orders-store.ts` and
`lib/subscriptions-store.ts` predate `src/platform/dependency-mode.ts`
and called `getSql()` directly, silently falling back to module-global
`Map`s when no database is configured. For orders, payments and
subscriptions that is not a degraded mode, it is invisible loss of
money-bearing state: the API returns 201 and the row evaporates on the
next restart or on any other replica. The live 0pt deployment
(Coolify app 54) runs NODE_ENV=production with DATABASE_URL,
POSTGRES_URL and POSTGRES_PRISMA_URL all unset — so this is the
behavior in production right now, across 7 API routes.
These now go through `getSqlForDurableStore()`, which reuses the SAME
`resolveStoreMode` decision every `src/repositories/*` store already
uses rather than re-implementing a second production check. The reuse
is load-bearing: `isProductionRuntime` deliberately exempts
`NEXT_PHASE=phase-production-build`, because `next build` runs
page-data collection under NODE_ENV=production while serving nothing.
A naive `process.env.NODE_ENV === "production"` test here would have
failed the BUILD instead of protecting a request path.
2. LOCALBLOBSTORE. It writes document bytes to a container-local disk
while `document-capture-service` registers an artifact pointer
claiming the bytes are stored — durable object storage silently
swapped for ephemeral disk. It is the default blob store for both the
capture and extraction services. In production it now raises
DEPENDENCY_UNAVAILABLE (capability blocked: no ST0RE bytes path
exists yet) instead of fabricating success. Asserted per-operation,
not in the constructor, so merely wiring the services is not fatal —
only actually persisting or reading bytes is.
Evidence: full suite 956 passed / 184 skipped, typecheck clean,
dependency-cruiser 0 errors, lint identical to main's pre-existing
baseline (17 problems, none in changed files). Both guards asserted in
both directions and mutation-tested: bypassing resolveStoreMode -> 2
failed; dropping the write assert -> 2 failed; dropping the read assert
-> 1 failed; all restored -> 8 passed. 7 non-test call sites.
DEPLOYMENT NOTE: merging changes no runtime (no Gitea webhook exists on
any repo; deploys are manual). Deploying this build REQUIRES
provisioning DATABASE_URL for 0pt first, or the order/subscription
routes will correctly return DEPENDENCY_UNAVAILABLE instead of silently
accepting orders into memory. That provisioning is a Simon action.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CODEOWNERS rules
requested review from sax3l 2026-09-09 15:39:25 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What
Closes the two storage paths that the
resolveStoreModekill switch (Master-DoD §3/§10) did not cover. Both are live production execution paths today.Why this is not theoretical
The live 0pt deployment (Coolify app 54, container
shxd6lepxuxfsjsp0ath3z1g-…, up 42h healthy) runs with:NODE_ENV=productionDATABASE_URL,POSTGRES_URL,POSTGRES_PRISMA_URLall unsetSo
getSql()returnsnullandlib/orders-store.ts/lib/subscriptions-store.tsare serving out of module-globalMaps in production right now, across 7 API routes:app/api/orders,app/api/orders/[id],app/api/subscriptions,app/api/payments/[order_id]/deposit,app/api/payments/[order_id]/release,app/api/webhooks/stripe,app/api/admin/metricsFor orders, payments and subscriptions that is not a degraded mode — it is invisible loss of money-bearing state. The API returns 201 and the row evaporates on the next restart or on any other replica.
The two guards
1. Legacy Next.js stores now call
getSqlForDurableStore(), which reuses the sameresolveStoreModedecision everysrc/repositories/*store already uses, instead of re-implementing a second production check.The reuse is load-bearing, not tidiness.
isProductionRuntimedeliberately exemptsNEXT_PHASE=phase-production-build, becausenext buildruns page-data collection underNODE_ENV=productionwhile serving nothing. The naiveprocess.env.NODE_ENV === "production"check would have failed the build rather than protecting a request path. There is a test pinning exactly that.2.
LocalBlobStorewrites document bytes to a container-local disk whiledocument-capture-serviceregisters an artifact pointer claiming the bytes are stored — durable object storage silently swapped for ephemeral disk. It is the default blob store for both the capture and extraction services (opts?.blobStore ?? new LocalBlobStore()). In production it now raisesDEPENDENCY_UNAVAILABLE(capability blocked: no ST0RE bytes path exists yet) instead of fabricating success.Asserted per-operation, not in the constructor, so merely wiring the services is not fatal — only actually persisting or reading bytes is. A test pins that too.
Evidence
vitest run(full suite)npm run typecheck(blockingtsconfig.check.json)npm run deps:checknpm run lintMutation-tested, both directions:
getSqlForDurableStorebypassesresolveStoreModeassertAllowed("write")assertAllowed("read")Guards are asserted in both directions — production refuses, and the dev/test seam, the
next buildphase, and the configured-production path all still work. A guard that degenerated into a blanket refusal would fail these too.Deployment note — requires action before deploy
Merging changes no runtime: there is no Gitea webhook on any repo in this estate (
webhooktable = 0 rows,hook_task= 0, verified against a positive control of 402 repositories), so deploys are manual.Deploying this build requires provisioning
DATABASE_URLfor 0pt first, or the order/subscription routes will correctly returnDEPENDENCY_UNAVAILABLEinstead of silently accepting orders into memory. That provisioning is a Simon action.Not claimed: no live verification, no restore proof. This is a code guard only.
🤖 Generated with Claude Code
siax-bot CI-verifiering: samtliga grinding-jobb success. Auto-approval enligt masterplan-execution-mandat.