@ultimat3/cli 23.0.0 → 25.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) hide show
  1. package/CLAUDE.md +28 -13
  2. package/README.md +32 -7
  3. package/package.json +29 -29
  4. package/src/admin-catalog-keys.ts +2 -2
  5. package/src/affected.ts +30 -36
  6. package/src/api-registration.ts +89 -0
  7. package/src/app-artifacts.ts +1 -1
  8. package/src/app-auth.ts +4 -27
  9. package/src/app-boundaries.ts +17 -4
  10. package/src/app-config-load.ts +51 -0
  11. package/src/app-env.ts +26 -25
  12. package/src/app-load.ts +38 -14
  13. package/src/app-manifest.ts +4 -10
  14. package/src/app-mcp.ts +31 -46
  15. package/src/app-name.ts +52 -0
  16. package/src/app-permissions-borrowed.ts +123 -0
  17. package/src/app-permissions-site.ts +48 -0
  18. package/src/app-permissions.ts +7 -17
  19. package/src/app-root.ts +9 -5
  20. package/src/bin.ts +3 -7
  21. package/src/browser-launcher.ts +4 -2
  22. package/src/budgets-charged.ts +60 -0
  23. package/src/budgets-scan.ts +76 -0
  24. package/src/budgets-unmeasured.ts +91 -0
  25. package/src/budgets.ts +117 -154
  26. package/src/cdp-shot-driver.ts +20 -1
  27. package/src/cdp-shot-watch.ts +35 -31
  28. package/src/ci-log.ts +0 -0
  29. package/src/cmd-auth-spec.ts +16 -0
  30. package/src/cmd-auth.ts +65 -0
  31. package/src/cmd-build-spec.ts +7 -1
  32. package/src/cmd-build.ts +151 -22
  33. package/src/cmd-db-backfill.ts +26 -13
  34. package/src/cmd-db-branch.ts +2 -2
  35. package/src/cmd-db.ts +10 -6
  36. package/src/cmd-deploy-compose.ts +94 -0
  37. package/src/cmd-deploy-drain.ts +48 -0
  38. package/src/cmd-deploy-helm.ts +7 -6
  39. package/src/cmd-deploy-start-first.ts +178 -0
  40. package/src/cmd-deploy.ts +158 -36
  41. package/src/cmd-dev.ts +18 -356
  42. package/src/cmd-doctor-spec.ts +2 -9
  43. package/src/cmd-doctor.ts +57 -80
  44. package/src/cmd-env.ts +55 -16
  45. package/src/cmd-generate.ts +89 -32
  46. package/src/cmd-help.ts +19 -6
  47. package/src/cmd-i18n.ts +21 -17
  48. package/src/cmd-jobs-spec.ts +7 -22
  49. package/src/cmd-jobs.ts +33 -140
  50. package/src/cmd-manifest-spec.ts +1 -1
  51. package/src/cmd-manifest.ts +6 -25
  52. package/src/cmd-mcp.ts +71 -22
  53. package/src/cmd-new.ts +36 -8
  54. package/src/cmd-planned.ts +24 -8
  55. package/src/cmd-routes.ts +15 -5
  56. package/src/cmd-secrets.ts +47 -6
  57. package/src/cmd-shot-matrix.ts +22 -7
  58. package/src/cmd-shot-spec.ts +10 -2
  59. package/src/cmd-shot.ts +46 -41
  60. package/src/cmd-tasks.ts +2 -2
  61. package/src/cmd-test-spec.ts +1 -7
  62. package/src/cmd-test.ts +14 -30
  63. package/src/cmd-verify.ts +4 -4
  64. package/src/coverage-lcov.ts +4 -2
  65. package/src/db-backfill.ts +2 -2
  66. package/src/db-replay-engine.ts +4 -10
  67. package/src/db-subscribes.ts +27 -16
  68. package/src/dev-boot.ts +290 -0
  69. package/src/dev-child-watch.ts +63 -12
  70. package/src/dev-dashboard-guard.ts +76 -0
  71. package/src/dev-dashboard.ts +56 -30
  72. package/src/dev-lock.ts +100 -31
  73. package/src/dev-rebuild.ts +80 -0
  74. package/src/dev-restart-retire.ts +46 -0
  75. package/src/dev-route-table.ts +30 -8
  76. package/src/dev-supervisor.ts +114 -22
  77. package/src/dev-traces.ts +53 -20
  78. package/src/dispatch.ts +17 -13
  79. package/src/doctor-app-url.ts +39 -0
  80. package/src/doctor-auth.ts +95 -0
  81. package/src/doctor-offline.ts +6 -5
  82. package/src/document-options.ts +7 -0
  83. package/src/drift-replica-identity.ts +42 -0
  84. package/src/drift.ts +29 -6
  85. package/src/error-catalog.ts +6 -9
  86. package/src/error-codes.ts +29 -3
  87. package/src/error-page-csp.ts +14 -15
  88. package/src/error-unthrown.ts +29 -11
  89. package/src/errors.ts +54 -83
  90. package/src/exec.ts +8 -0
  91. package/src/favicon.ts +2 -2
  92. package/src/fix-command.ts +9 -1
  93. package/src/framework-env.ts +148 -0
  94. package/src/framework-schema-apply.ts +78 -0
  95. package/src/framework-schema-stamp.ts +96 -0
  96. package/src/framework-schema.ts +25 -3
  97. package/src/generate-catalog-locales.ts +79 -0
  98. package/src/generate-files.ts +4 -3
  99. package/src/generate-kinds.ts +18 -0
  100. package/src/generate-plural.ts +50 -0
  101. package/src/generate-shadow.ts +95 -0
  102. package/src/generate-write.ts +47 -13
  103. package/src/gh-target.ts +12 -7
  104. package/src/hold.ts +56 -1
  105. package/src/i18n-audit.ts +25 -9
  106. package/src/i18n-index.ts +5 -6
  107. package/src/i18n-registration.ts +7 -9
  108. package/src/import-scan.ts +28 -3
  109. package/src/index.ts +2 -6
  110. package/src/island-bundle.ts +57 -79
  111. package/src/island-capture.ts +1 -1
  112. package/src/island-duplicate-refusal.ts +57 -0
  113. package/src/island-duplicates.ts +221 -0
  114. package/src/island-entry-missing.ts +64 -0
  115. package/src/island-harness-route.ts +20 -4
  116. package/src/island-harness-script.ts +23 -2
  117. package/src/island-identity.ts +17 -14
  118. package/src/island-package-dedupe.ts +150 -0
  119. package/src/island-realtime.ts +36 -16
  120. package/src/island-routes.ts +2 -2
  121. package/src/island-runtime.ts +241 -0
  122. package/src/island-sources.ts +105 -0
  123. package/src/island-states-load.ts +6 -0
  124. package/src/island-store.ts +90 -6
  125. package/src/jobs-json.ts +0 -26
  126. package/src/jobs-report.ts +36 -20
  127. package/src/local-cli-handoff.ts +90 -0
  128. package/src/loopback-host.ts +13 -0
  129. package/src/manifest-staleness.ts +62 -0
  130. package/src/mcp-errors.ts +20 -4
  131. package/src/mcp-host.ts +22 -14
  132. package/src/mcp-test-run.ts +38 -0
  133. package/src/mcp-ui-inspect.ts +3 -2
  134. package/src/mcp-ui-interact.ts +3 -2
  135. package/src/mcp-ui.ts +8 -7
  136. package/src/measure-scope.ts +15 -10
  137. package/src/messages.ts +29 -16
  138. package/src/metrics-endpoint.ts +28 -3
  139. package/src/migration-extensions.ts +1 -1
  140. package/src/otlp-export.ts +2 -2
  141. package/src/output.ts +24 -0
  142. package/src/page-navigation.ts +7 -23
  143. package/src/page-speculation.ts +19 -31
  144. package/src/parse.ts +0 -8
  145. package/src/permission-grants.ts +3 -3
  146. package/src/posix-path.ts +30 -0
  147. package/src/prerender-locales.ts +6 -4
  148. package/src/prerender-out.ts +34 -7
  149. package/src/prerender-report.ts +58 -0
  150. package/src/prerender-served.ts +52 -0
  151. package/src/prerender.ts +71 -103
  152. package/src/pwa-artifacts.ts +33 -94
  153. package/src/pwa-manifest-locales.ts +9 -9
  154. package/src/registry.ts +2 -0
  155. package/src/retry-memo.ts +1 -1
  156. package/src/revalidated-response.ts +4 -3
  157. package/src/role-realtime.ts +7 -1
  158. package/src/role-replicator.ts +124 -29
  159. package/src/role-start.ts +69 -85
  160. package/src/role-sync.ts +119 -25
  161. package/src/root-env.ts +157 -28
  162. package/src/route-islands.ts +69 -0
  163. package/src/runtime-admin.ts +4 -1
  164. package/src/runtime-assets.ts +26 -9
  165. package/src/runtime-bindings.ts +27 -7
  166. package/src/runtime-cache.ts +46 -55
  167. package/src/runtime-hooks.ts +2 -1
  168. package/src/runtime-idempotency-scope.ts +23 -0
  169. package/src/runtime-isr-outcome.ts +100 -0
  170. package/src/runtime-isr.ts +36 -0
  171. package/src/runtime-jobs.ts +76 -41
  172. package/src/runtime-live-feed.ts +1 -1
  173. package/src/runtime-mail.ts +26 -0
  174. package/src/runtime-mfa-warning.ts +29 -0
  175. package/src/runtime-notify-retention.ts +10 -46
  176. package/src/runtime-overrides.ts +4 -4
  177. package/src/runtime-purge.ts +38 -16
  178. package/src/runtime-queue.ts +43 -22
  179. package/src/runtime-realtime.ts +11 -45
  180. package/src/runtime-render.ts +136 -114
  181. package/src/runtime-replica.ts +3 -8
  182. package/src/runtime-services.ts +49 -26
  183. package/src/runtime-storage-upload.ts +2 -2
  184. package/src/runtime-storage.ts +13 -8
  185. package/src/schema-diff.ts +16 -0
  186. package/src/schema-drift.ts +45 -15
  187. package/src/secrets-rotation.ts +8 -9
  188. package/src/seo-routes.ts +80 -9
  189. package/src/serve-boot.ts +51 -12
  190. package/src/serve-drain.ts +11 -33
  191. package/src/serve-env.ts +9 -6
  192. package/src/serve-retire.ts +95 -0
  193. package/src/serve-web.ts +31 -15
  194. package/src/serve.ts +27 -10
  195. package/src/server-barrels.ts +6 -3
  196. package/src/shot-browser.ts +1 -1
  197. package/src/shot-locale.ts +6 -33
  198. package/src/shot-settle.ts +3 -3
  199. package/src/shot-theme.ts +1 -1
  200. package/src/shot-viewport.ts +152 -0
  201. package/src/signal-shred.ts +26 -12
  202. package/src/site-asset-routes.ts +2 -2
  203. package/src/site-assets.ts +3 -5
  204. package/src/site-config.ts +9 -48
  205. package/src/site-seo.ts +7 -5
  206. package/src/sitemap-lastmod.ts +5 -0
  207. package/src/solid-loader.ts +12 -3
  208. package/src/source-files.ts +19 -3
  209. package/src/static-report.ts +5 -0
  210. package/src/stored-object-headers.ts +43 -0
  211. package/src/style-routes.ts +2 -2
  212. package/src/sw-artifacts.ts +8 -8
  213. package/src/sw-precache-plan.ts +34 -4
  214. package/src/sync-authenticator.ts +3 -3
  215. package/src/sync-url.ts +3 -3
  216. package/src/tasks-facts.ts +3 -29
  217. package/src/templates/action.ts +7 -4
  218. package/src/templates/backfill.ts +4 -4
  219. package/src/templates/entity.ts +2 -2
  220. package/src/templates/github/ci.yml.ts +28 -18
  221. package/src/templates/github/image.yml.ts +92 -0
  222. package/src/templates/github/runner.ts +18 -0
  223. package/src/templates/guard-bare-error.ts +16 -2
  224. package/src/templates/guard-unzoned-date.ts +56 -6
  225. package/src/templates/index.ts +1 -1
  226. package/src/templates/job-body-test.ts +2 -2
  227. package/src/templates/job.ts +6 -6
  228. package/src/templates/naming.ts +28 -0
  229. package/src/templates/query.ts +9 -8
  230. package/src/templates/resource-page.ts +9 -7
  231. package/src/templates/resource-service.ts +2 -2
  232. package/src/templates/route.ts +26 -14
  233. package/src/templates/scaffold-api.ts +2 -3
  234. package/src/templates/scaffold-app.ts +8 -3
  235. package/src/templates/scaffold-auth.ts +205 -70
  236. package/src/templates/scaffold-bin.ts +122 -0
  237. package/src/templates/scaffold-claude-commands.ts +11 -11
  238. package/src/templates/scaffold-container-compose.ts +10 -1
  239. package/src/templates/scaffold-container.ts +16 -8
  240. package/src/templates/scaffold-dashboard-bare.ts +8 -4
  241. package/src/templates/scaffold-dashboard-example-tests.ts +208 -0
  242. package/src/templates/scaffold-dashboard-example.ts +112 -172
  243. package/src/templates/scaffold-dashboard-shared.ts +62 -1
  244. package/src/templates/scaffold-dashboard.ts +9 -5
  245. package/src/templates/scaffold-db-client.ts +4 -2
  246. package/src/templates/scaffold-db-package.ts +2 -2
  247. package/src/templates/scaffold-docs.ts +20 -77
  248. package/src/templates/scaffold-domain-package.ts +0 -4
  249. package/src/templates/scaffold-entries.ts +4 -3
  250. package/src/templates/scaffold-env.ts +6 -3
  251. package/src/templates/scaffold-gitattributes.ts +59 -0
  252. package/src/templates/scaffold-helm-fences.ts +76 -0
  253. package/src/templates/scaffold-helm-templates.ts +100 -19
  254. package/src/templates/scaffold-helm.ts +62 -5
  255. package/src/templates/scaffold-http.ts +1 -1
  256. package/src/templates/scaffold-i18n.ts +15 -2
  257. package/src/templates/scaffold-icon.ts +2 -2
  258. package/src/templates/scaffold-repo.ts +27 -16
  259. package/src/templates/scaffold-shell.ts +55 -37
  260. package/src/test-counts.ts +2 -6
  261. package/src/test-ignores.ts +25 -0
  262. package/src/test-only-shipped.ts +193 -0
  263. package/src/test-passes.ts +0 -15
  264. package/src/test-select.ts +7 -15
  265. package/src/test-shards.ts +40 -95
  266. package/src/test-workers.ts +26 -4
  267. package/src/theme-boot.ts +8 -33
  268. package/src/theme-brand.ts +66 -0
  269. package/src/trusted-hops-error.ts +17 -0
  270. package/src/trusted-hops.ts +49 -0
  271. package/src/unscanned-admin.ts +2 -2
  272. package/src/unthrown-codes.ts +26 -0
  273. package/src/verify-checks.ts +29 -48
  274. package/src/verify-coverage-run.ts +3 -2
  275. package/src/verify-deadline.ts +207 -33
  276. package/src/verify-merge.ts +108 -43
  277. package/src/verify-part-step.ts +98 -0
  278. package/src/verify-role-load-probe.ts +12 -2
  279. package/src/verify-role-load.ts +9 -4
  280. package/src/verify-run.ts +32 -8
  281. package/src/verify-shard.ts +13 -2
  282. package/src/verify-stalled.ts +152 -0
  283. package/src/verify-step.ts +2 -0
  284. package/src/verify-test-run.ts +3 -2
  285. package/src/verify-tests.ts +5 -3
  286. package/src/worker-bundle.ts +2 -2
  287. package/src/workspace-checks.ts +53 -7
  288. package/src/workspace-graph.ts +3 -2
  289. package/src/write-line.ts +82 -7
  290. package/src/browser-launcher-fake-html.ts +0 -80
  291. package/src/browser-launcher-fake.ts +0 -168
  292. package/src/build-errors.ts +0 -54
  293. package/src/jobs-drain.ts +0 -163
  294. package/src/scaffold-typecheck.ts +0 -227
  295. package/src/thrown-by.ts +0 -24
  296. package/src/verify-errors.ts +0 -58
@@ -1,46 +1,31 @@
1
- // Projecting the route table onto HTTP routes `x dev` can serve. Every mode goes through
1
+ // Projecting the route table onto HTTP routes both boots serve. Every mode goes through
2
2
  // `@ultimat3/render`'s own function for that mode — the CLI picks the mode and supplies the
3
- // document, it never decides what a mode means or what headers it earns.
4
- //
5
- // The document is head + the route's own component, rendered by `@ultimat3/render`'s server JSX
6
- // writer, with the surface's compiled CSS LINKED — one content-hashed file per surface
7
- // (`style-bundle.ts`), served `immutable`.
8
- //
9
- // It was inlined until 2026-09-06, on the argument that a `site/` page is a 0kb-JS artifact a CDN
10
- // serves as one file and a link would add a round trip. The round trip is real and it is paid
11
- // once: measured against ai-maxxing, every `app/` document carried the SAME 156,738-byte `<style>`
12
- // block — 92% of the dashboard document — inside a response the pipeline sends
13
- // `Cache-Control: private, no-store`, so the trip that argument saved was re-paid in full on every
14
- // navigation, with a re-parse on top. The static export writes the file (`writeStyles`), so the
15
- // "second file" cost is one `Bun.write`.
3
+ // document (head + the route's component, the surface's CSS LINKED, `style-bundle.ts`), it never
4
+ // decides what a mode means or what headers it earns.
16
5
 
17
- // why: Bun ships no path API; an island's file is its route file's directory joined to its `src`.
18
- import { posix } from 'node:path';
19
6
  import { actionPathStyle } from '@ultimat3/action';
20
7
  import { clientScopeOf } from '@ultimat3/auth';
21
- import type { Ctx } from '@ultimat3/core';
22
- import { CLIENT_SCOPE_HEADER } from '@ultimat3/core';
8
+ import type { Actor, Ctx } from '@ultimat3/core';
9
+ import { CLIENT_SCOPE_HEADER, singleFlight } from '@ultimat3/core';
23
10
  import type {
24
11
  RouteMeta as HttpRouteMeta,
25
12
  RedirectIntent,
13
+ RequestContext,
26
14
  Route,
27
15
  RouteParams,
16
+ UltimateRequest,
28
17
  } from '@ultimat3/http';
29
18
  import { asCtx, html, NO_STORE, redirect, stream, takeRedirect } from '@ultimat3/http';
30
- import { currentLocale, localeConfig } from '@ultimat3/i18n';
19
+ import { currentLocale } from '@ultimat3/i18n';
31
20
  import type { IslandCollector, RenderResult, RouteData, RouteEntry } from '@ultimat3/render';
32
21
  import {
33
- clientBootTags,
34
22
  clientPathStyleTags,
35
23
  clientPersistTags,
36
24
  clientScopeTag,
37
25
  clientSyncTags,
38
- createIslandCollector,
39
26
  documentCarriesScope,
40
27
  headFromMeta,
41
28
  hydrateRuntime,
42
- islandModuleId,
43
- islandModuleIds,
44
29
  metaContextFor,
45
30
  renderHead,
46
31
  routeDataFor,
@@ -52,8 +37,6 @@ import {
52
37
  import type { IsrController } from '@ultimat3/render/server';
53
38
  import {
54
39
  contentHash,
55
- createIsrController,
56
- isrKey,
57
40
  ROOT_ELEMENT_ID,
58
41
  renderComponent,
59
42
  renderSsr,
@@ -62,13 +45,15 @@ import {
62
45
  streamResult,
63
46
  } from '@ultimat3/render/server';
64
47
  import type { DocumentOptions } from './document-options';
65
- import { realtimeIslandFiles } from './island-realtime';
66
48
  import {
67
49
  type NavigationDocumentHead,
68
50
  navigationMetaOf,
69
51
  navigationTagsOf,
70
52
  principalRelocation,
71
53
  } from './page-navigation';
54
+ import { bootScript, collectorFor } from './route-islands';
55
+ import { attachedIsr } from './runtime-isr';
56
+ import { isrOutcome } from './runtime-isr-outcome';
72
57
  import type { StaticResult } from './static-document';
73
58
  import { createStaticMemo, staticMemoKey, staticResponse } from './static-document';
74
59
  import { styleBundle } from './style-bundle';
@@ -77,7 +62,11 @@ export type { DocumentOptions, IslandResolver } from './document-options';
77
62
 
78
63
  export interface DevRenderOptions extends DocumentOptions {
79
64
  readonly buildId: string;
80
- /** Injected so a test can drive the ISR store without a timer. */
65
+ /**
66
+ * The boot's own controller, attached and released by it (`runtime-isr.ts`). Omitted, one is
67
+ * built AND attached here, never released — a caller with no stop list (a contract test) still
68
+ * gets pages a tag bust reaches; the revalidator slot is the latest attach's.
69
+ */
81
70
  readonly isr?: IsrController;
82
71
  /**
83
72
  * Keep each `static` document after its first render (`static-document.ts`). The container sets
@@ -124,9 +113,11 @@ const headFor = async (
124
113
  seoRenderers({
125
114
  path: url.pathname,
126
115
  baseUrl: options.origin ?? url.origin,
116
+ // The default is the cluster's own head (`alternates` is default-first by contract), so
117
+ // `x-default` comes from the list the hreflang tags do — never a second locale read.
127
118
  localization: {
128
119
  locale: meta.locale,
129
- defaultLocale: localeConfig().fallback,
120
+ defaultLocale: meta.alternates[0]?.locale ?? meta.locale,
130
121
  alternates: meta.alternates,
131
122
  },
132
123
  }),
@@ -156,7 +147,8 @@ const headFor = async (
156
147
 
157
148
  /**
158
149
  * `<link rel="stylesheet">` for the surface's own stylesheets, or nothing at all when the surface
159
- * imports none.
150
+ * imports none. Inlined until 2026-09-06: every `app/` document of ai-maxxing then carried the same
151
+ * 156,738-byte `<style>` (92% of it) under `no-store`, re-paid on every navigation.
160
152
  *
161
153
  * In `<head>`, which is what keeps this a swap and not a regression: a `<link rel="stylesheet">`
162
154
  * there is render-blocking in every browser, exactly as the inline block was, so there is no
@@ -174,6 +166,10 @@ const styleTag = (entry: RouteEntry): string => {
174
166
  return href === undefined ? '' : `<link rel="stylesheet" href="${href}">`;
175
167
  };
176
168
 
169
+ /** The surface stylesheet, then the app's brand — the order that lets the brand win the cascade. */
170
+ const stylesFor = (entry: RouteEntry, options: DocumentOptions): string =>
171
+ styleTag(entry) + (options.brandHead ?? '');
172
+
177
173
  /**
178
174
  * The route's rendered body, inside the hydration root. Every mode goes through here — an empty
179
175
  * root is what a module exporting no component renders, and nothing else: `spa` was the one mode
@@ -205,46 +201,6 @@ export async function routeBody(
205
201
  return `<div id="${ROOT_ELEMENT_ID}">${html}</div>`;
206
202
  }
207
203
 
208
- /**
209
- * One collector per RENDER, never module-global: two requests render different params, and a
210
- * shared collector would bill one page for the other's islands. `hydrate` comes off the route, so
211
- * an island never declares its own timing, and `resolve` is the build's — identity when nothing
212
- * built any, which fails at the first island by name rather than emitting an unusable entry.
213
- */
214
- /**
215
- * Realtime's page boot, as one deferred script — or nothing. Two conditions, both exact: the
216
- * document carries a principal scope (restoring persisted records and replaying queued writes are
217
- * per principal; a shareable document has neither), AND one of the islands this render emitted
218
- * reaches `@ultimat3/realtime` (a page whose islands never touch a record has nothing to restore
219
- * into and no write to replay). After the body, because which islands rendered is a fact the walk
220
- * just recorded; still before the hydration runtime, so it runs first among the deferred scripts.
221
- */
222
- function bootScript(
223
- entry: RouteEntry,
224
- islands: IslandCollector,
225
- options: DocumentOptions,
226
- scope: string | undefined,
227
- ): string {
228
- if (scope === undefined || options.sync === undefined) return '';
229
- const rendered = new Set(islandModuleIds(islands.directives));
230
- if (rendered.size === 0) return '';
231
- // An island's module id is derived from its `src`, written relative to the page that renders it:
232
- // each realtime island file, spelled from THIS page, is the id its directive would carry.
233
- const pageDir = posix.dirname(entry.file);
234
- const reaches = [...realtimeIslandFiles()].some((file) => {
235
- const src = posix.relative(pageDir, file);
236
- return rendered.has(islandModuleId(src.startsWith('.') ? src : `./${src}`));
237
- });
238
- return reaches ? renderHead(clientBootTags(options.sync)) : '';
239
- }
240
-
241
- const collectorFor = (entry: RouteEntry, options: DocumentOptions): IslandCollector =>
242
- createIslandCollector({
243
- file: entry.file,
244
- hydrate: entry.config.hydrate,
245
- ...(options.resolveIsland === undefined ? {} : { resolve: options.resolveIsland(entry.file) }),
246
- });
247
-
248
204
  /**
249
205
  * Head + body for one route render. Exported because the build's prerenderer must emit the same
250
206
  * document `x dev` serves — two document builders is how a page that works in dev ships broken.
@@ -257,6 +213,42 @@ export async function routeDocument(
257
213
  return documentFrom(entry, ctx, await routeDataFor(entry.config, ctx), options);
258
214
  }
259
215
 
216
+ /**
217
+ * The document a request by `actor` is SERVED: `routeDocument` plus the scope the response would
218
+ * carry, which is what earns the page boot. The build weighs an app/ page through this, because a
219
+ * budget is a promise about what that page's browser downloads — and an unscoped render of a
220
+ * scoped page weighed a runtime chunk it never fetches and no boot it always does (#505).
221
+ */
222
+ export async function servedDocument(
223
+ entry: RouteEntry,
224
+ ctx: DevRouteData,
225
+ actor: Actor,
226
+ options: DocumentOptions & { readonly buildId: string },
227
+ ): Promise<string> {
228
+ const data = await routeDataFor(entry.config, ctx);
229
+ return documentFrom(entry, ctx, data, options, documentScope(entry, options.buildId, actor));
230
+ }
231
+
232
+ /**
233
+ * Whether, and as whom, a mode's response is scoped — ONE decision for the request path and for
234
+ * `servedDocument`. A stream is always `private, no-store`; an ssr page carries the scope exactly
235
+ * when the headers `renderSsr` sends are private (an ungated page is `public, s-maxage`, and a CDN
236
+ * may hand it to anyone); a static or isr document is shareable by construction.
237
+ */
238
+ function documentScope(entry: RouteEntry, buildId: string, actor: Actor): string | undefined {
239
+ switch (entry.config.render) {
240
+ case 'static':
241
+ case 'isr':
242
+ return undefined;
243
+ case 'stream':
244
+ return clientScopeOf(actor);
245
+ default:
246
+ return documentCarriesScope(ssrHeaders(entry, { buildId }))
247
+ ? clientScopeOf(actor)
248
+ : undefined;
249
+ }
250
+ }
251
+
260
252
  /**
261
253
  * The document from data ALREADY resolved. Split from `routeDocument` so one request resolves
262
254
  * `load` exactly once: `stream` renders head and body separately, and resolving in each would let
@@ -273,23 +265,23 @@ async function documentFrom(
273
265
  options: DocumentOptions,
274
266
  scope?: string,
275
267
  ): Promise<string> {
276
- const islands = collectorFor(entry, options);
268
+ const islands = collectorFor(entry, options, scope);
277
269
  const [head, body] = await Promise.all([
278
270
  headFor(entry, ctx, data, options, scope),
279
271
  routeBody(entry, ctx, data, islands),
280
272
  ]);
281
273
  return (
282
- `<!doctype html><html lang="${lang()}"><head>${head}${styleTag(entry)}</head>` +
274
+ `<!doctype html><html lang="${lang()}"><head>${head}${stylesFor(entry, options)}</head>` +
283
275
  `<body>${body}${bootScript(entry, islands, options, scope)}${hydrateRuntime(islands.directives)}</body></html>`
284
276
  );
285
277
  }
286
278
 
279
+ /** Every mode but `isr`, which `renderIsr` answers: its `load` runs only when the store misses. */
287
280
  async function resultFor(
288
281
  entry: RouteEntry,
289
282
  request: DevRouteData,
290
283
  data: RouteData,
291
284
  options: DevRenderOptions,
292
- isr: IsrController,
293
285
  ctx: Ctx,
294
286
  ): Promise<RenderResult> {
295
287
  const url = new URL(request.url);
@@ -303,57 +295,40 @@ async function resultFor(
303
295
  const body = await documentFrom(entry, request, data, options);
304
296
  return { status, headers: staticHeaders(contentHash(body), options.buildId), body };
305
297
  }
306
- case 'isr': {
307
- // `isrKey(url, locale)`, never `url.pathname`: the query is part of what was rendered — this
308
- // route's own `meta` reads `data.url` — so two URLs differing only in their query are two
309
- // documents. Keyed on the pathname alone, the first render answered every later query
310
- // string (#171). Render owns the derivation so no second caller can invent another.
311
- // The locale is the second dimension and it is `ctx.locale`, the answer the `locale` stage
312
- // already negotiated for THIS request — never `currentLocale()`, which would read the same
313
- // value through an ambient store the key does not need.
314
- // `{ html, status }`, never the bare string: the entry stores the status beside the HTML
315
- // and serves it on every hit, so a 404 under `isr` is a 404 for its whole TTL.
316
- const served = await isr.serve(isrKey(url, ctx.locale), async () => ({
317
- html: await documentFrom(entry, request, data, options),
318
- status,
319
- }));
320
- return served.result;
321
- }
322
298
  case 'stream': {
323
299
  // The shell IS the component: nothing can yet mark a subtree as a hole. Solid's `Suspense`
324
300
  // is not the missing piece and never will be here — it calls `getContextId()`, which throws
325
301
  // outside a Solid renderer, and this package's JSX factory is inert by design. A hole marker
326
302
  // has to be the framework's own. Until it exists the first flush carries the whole body —
327
303
  // correct output, no streaming benefit.
328
- const islands = collectorFor(entry, options);
304
+ // A stream is always `private, no-store` (`streamResult`), so it always carries the scope.
305
+ const scope = documentScope(entry, options.buildId, ctx.actor);
306
+ const islands = collectorFor(entry, options, scope);
329
307
  const [head, shell] = await Promise.all([
330
- // A stream is always `private, no-store` (`streamResult`), so it always carries the scope.
331
- headFor(entry, request, data, options, clientScopeOf(ctx.actor)),
308
+ headFor(entry, request, data, options, scope),
332
309
  routeBody(entry, request, data, islands),
333
310
  ]);
334
311
  return withScope(
335
312
  streamResult(
336
313
  {
337
- head: `<!doctype html><html lang="${lang()}"><head>${head}${styleTag(entry)}</head><body>`,
314
+ head: `<!doctype html><html lang="${lang()}"><head>${head}${stylesFor(entry, options)}</head><body>`,
338
315
  // The runtime rides the first flush, with the shell it boots. A later chunk would leave
339
316
  // the window between flush one and the close with inert islands and no listeners on
340
317
  // them — which is exactly the first-click-lost failure `interaction` replay exists for.
341
- shell: `${shell}${bootScript(entry, islands, options, clientScopeOf(ctx.actor))}${hydrateRuntime(islands.directives)}`,
318
+ shell: `${shell}${bootScript(entry, islands, options, scope)}${hydrateRuntime(islands.directives)}`,
342
319
  holes: [],
343
320
  },
344
321
  { buildId: options.buildId },
345
322
  status,
346
323
  ),
347
- clientScopeOf(ctx.actor),
324
+ scope,
348
325
  );
349
326
  }
350
327
  default: {
351
328
  // Asked of the headers `renderSsr` is about to send: a gated page is private and carries the
352
329
  // scope; an ungated one is `public, s-maxage` and a CDN may hand it to anyone, so it carries
353
330
  // none — absent, which core reads as "not rendered for anyone", never as anonymous.
354
- const scope = documentCarriesScope(ssrHeaders(entry, { buildId: options.buildId }))
355
- ? clientScopeOf(ctx.actor)
356
- : undefined;
331
+ const scope = documentScope(entry, options.buildId, ctx.actor);
357
332
  return withScope(
358
333
  await renderSsr(
359
334
  { entry, params: request.params, url, ctx },
@@ -429,8 +404,11 @@ const metaOf = (entry: RouteEntry, head?: NavigationDocumentHead): HttpRouteMeta
429
404
  * only a test does — and then guard and page fall back together.
430
405
  */
431
406
  export function appRoutes(options: DevRenderOptions): readonly Route[] {
432
- const isr = options.isr ?? createIsrController({ buildId: options.buildId });
407
+ const isr = options.isr ?? attachedIsr({ buildId: options.buildId }).isr;
433
408
  const memo = createStaticMemo();
409
+ // A cold static key is ONE render however many requests arrive during it: the memo alone was
410
+ // check-then-act, so a burst at boot ran `load` and the render once per request.
411
+ const firstRender = singleFlight();
434
412
  return routeEntries().map((registered) => ({
435
413
  method: 'GET' as const,
436
414
  path: registered.path,
@@ -450,23 +428,67 @@ export function appRoutes(options: DevRenderOptions): readonly Route[] {
450
428
  : undefined;
451
429
  const kept = key === undefined ? undefined : memo.get(key);
452
430
  if (kept !== undefined) return staticResponse(request, kept);
453
- const data: DevRouteData = { url: request.url.href, params: ctx.params };
454
- // ONCE per request, before the mode is chosen: every branch of `resultFor` reads this same
455
- // object, so a route's `load` runs exactly once however its mode splits head from body.
456
- const loaded = await routeDataFor(entry.config, data);
457
- // `setRedirect()` inside `load` — the same slot an action's handler fills — answers here,
458
- // before a document is rendered for a page the visitor is leaving. `withStatus` refuses a
459
- // 3xx because a rendered document has no `Location`; this is the path that has one.
460
- const to = takeRedirect(ctx);
461
- if (to !== undefined) return loadRedirect(entry, to);
462
- const result = await resultFor(entry, data, loaded, options, isr, asCtx(ctx));
463
- if (entry.config.render !== 'static' || typeof result.body !== 'string') {
464
- return responseOf(result);
465
- }
466
- const document: StaticResult = { ...result, body: result.body };
467
- if (key !== undefined) memo.set(key, document);
431
+ let led = false;
432
+ const render = async (): Promise<Rendered> => {
433
+ led = true;
434
+ const rendered = await renderEntry(entry, request, ctx, options, isr);
435
+ if (key !== undefined && rendered.document !== undefined) memo.set(key, rendered.document);
436
+ return rendered;
437
+ };
438
+ const shared = key === undefined ? await render() : await firstRender.run(key, render);
439
+ // Only a DOCUMENT is shared. A redirect `load` decided was its request's own, so a request
440
+ // that joined one renders for itself rather than answer with another request's `Location`.
441
+ const rendered = shared.document !== undefined || led ? shared : await render();
468
442
  // The ETag was only ever a header: a matching `If-None-Match` is a 304, not the page again.
469
- return staticResponse(request, document);
443
+ return rendered.document === undefined
444
+ ? rendered.respond()
445
+ : staticResponse(request, rendered.document);
470
446
  },
471
447
  }));
472
448
  }
449
+
450
+ /** A static document a joined request can share, or how to answer when there is none. */
451
+ interface Rendered {
452
+ readonly document?: StaticResult;
453
+ readonly respond: () => Response;
454
+ }
455
+
456
+ async function renderEntry(
457
+ entry: RouteEntry,
458
+ request: UltimateRequest,
459
+ ctx: RequestContext,
460
+ options: DevRenderOptions,
461
+ isr: IsrController,
462
+ ): Promise<Rendered> {
463
+ const data: DevRouteData = { url: request.url.href, params: ctx.params };
464
+ if (entry.config.render === 'isr') return renderIsr(entry, data, ctx, options, isr);
465
+ // ONCE per request, before the mode is chosen: every branch of `resultFor` reads this same
466
+ // object, so a route's `load` runs exactly once however its mode splits head from body.
467
+ const loaded = await routeDataFor(entry.config, data);
468
+ // `setRedirect()` inside `load` — the same slot an action's handler fills — answers here,
469
+ // before a document is rendered for a page the visitor is leaving. `withStatus` refuses a
470
+ // 3xx because a rendered document has no `Location`; this is the path that has one.
471
+ const to = takeRedirect(ctx);
472
+ if (to !== undefined) return { respond: () => loadRedirect(entry, to) };
473
+ const result = await resultFor(entry, data, loaded, options, asCtx(ctx));
474
+ if (entry.config.render !== 'static' || typeof result.body !== 'string') {
475
+ return { respond: () => responseOf(result) };
476
+ }
477
+ const document: StaticResult = { ...result, body: result.body };
478
+ return { document, respond: () => responseOf(document) };
479
+ }
480
+
481
+ /** `isr`'s outcome (`runtime-isr-outcome.ts`) as this file's answer: a page, or the redirect. */
482
+ async function renderIsr(
483
+ entry: RouteEntry,
484
+ data: DevRouteData,
485
+ ctx: RequestContext,
486
+ options: DevRenderOptions,
487
+ isr: IsrController,
488
+ ): Promise<Rendered> {
489
+ const document = (loaded: RouteData): Promise<string> =>
490
+ documentFrom(entry, data, loaded, options);
491
+ const outcome = await isrOutcome({ entry, data, ctx, isr, document });
492
+ if (outcome.kind === 'redirect') return { respond: () => loadRedirect(entry, outcome.to) };
493
+ return { respond: () => responseOf(outcome.result) };
494
+ }
@@ -7,7 +7,7 @@
7
7
  // 1. `defaultClient()` is the one place db composes `replicatedClient(primary, replica)` from
8
8
  // `DATABASE_REPLICA_URL`, and it runs only from `baseClient()` — "the client an app installed
9
9
  // none for". Every process the framework boots installs one: `runtime-queue.ts` calls
10
- // `setDbClient(createPgliteClient(…) | createPostgresClient({ url }))`, so `defaultClient()` was
10
+ // `setDbClient(pgliteClient(…) | postgresClient({ url }))`, so `defaultClient()` was
11
11
  // unreachable from `x dev`, from `apps/web/server.ts` and from every container role.
12
12
  // 2. Routing needs an OPEN scope as well as a configured replica, and nothing opened one.
13
13
  //
@@ -17,12 +17,7 @@
17
17
  // what a database is.
18
18
 
19
19
  import type { DbClient, PostgresClient } from '@ultimat3/db';
20
- import {
21
- createPostgresClient,
22
- REPLICA_URL_ENV,
23
- replicatedClient,
24
- withReplicaReads,
25
- } from '@ultimat3/db';
20
+ import { postgresClient, REPLICA_URL_ENV, replicatedClient, withReplicaReads } from '@ultimat3/db';
26
21
  import type { Middleware } from '@ultimat3/http';
27
22
  import type { ServiceBinding } from './runtime-bindings';
28
23
  import type { RuntimeOverrides } from './runtime-overrides';
@@ -61,7 +56,7 @@ export function attachReplica(
61
56
  replicaUrl: string | undefined,
62
57
  ): ReplicaAttachment {
63
58
  if (replicaUrl === undefined) return { client: primary, replica: undefined };
64
- const replica = createPostgresClient({ url: replicaUrl, applicationName: 'ultimate-replica' });
59
+ const replica = postgresClient({ url: replicaUrl, applicationName: 'ultimate-replica' });
65
60
  return { client: replicatedClient(primary, replica), replica };
66
61
  }
67
62
 
@@ -4,7 +4,6 @@
4
4
  // boot installs, only backed by embedded drivers.
5
5
 
6
6
  import { mkdirSync } from 'node:fs';
7
- import { dirname } from 'node:path';
8
7
  import { configureAuthLimiters, postgresAuthLimiter, resetAuthLimiters } from '@ultimat3/auth';
9
8
  import type { PurgeDriver } from '@ultimat3/cache';
10
9
  import { isNoopPurgeDriver, selectPurgeDriver } from '@ultimat3/cache';
@@ -17,31 +16,32 @@ import {
17
16
  systemClock,
18
17
  } from '@ultimat3/core';
19
18
  import type { RateLimitStore } from '@ultimat3/http';
20
- import { postgresRateLimitStore } from '@ultimat3/http';
19
+ import { adoptRateLimitStore, postgresRateLimitStore } from '@ultimat3/http';
21
20
  import type { EventBus, JobDriver, OutboxStore } from '@ultimat3/jobs';
22
21
  import type { MailDriver } from '@ultimat3/mail';
23
22
  import {
24
23
  isMemoryDriver,
25
24
  isUnconfiguredDriver,
26
25
  resetMailDriver,
27
- selectMailDriver,
28
26
  setMailDriver,
29
27
  } from '@ultimat3/mail';
30
28
  import type { Transport, TransportSelection } from '@ultimat3/realtime/server';
31
29
  import { selectTransport } from '@ultimat3/realtime/server';
32
30
  import type { Storage } from '@ultimat3/storage';
33
31
  import { defineStorage, localDriver, s3Driver, usesDevStorageSecret } from '@ultimat3/storage';
32
+ import { loadAppConfig } from './app-config-load';
34
33
  import { LocalDiskUnsafeError, StorageUnwritableError } from './errors';
35
34
  import { msg } from './messages';
36
35
  import type { DevServices, Env } from './runtime-bindings';
37
- import { loadCacheTiers, startCacheTiers } from './runtime-cache';
38
- import { loadWorkerConfig, type WorkerConfig } from './runtime-jobs';
39
- import { loadInboxRetention } from './runtime-notify-retention';
36
+ import { cacheTiersOf, startCacheTiers } from './runtime-cache';
37
+ import { type WorkerConfig, workerConfigOf } from './runtime-jobs';
38
+ import { selectAppMailDriver } from './runtime-mail';
39
+ import { inboxRetentionOf } from './runtime-notify-retention';
40
40
  import type { RuntimeOverrides } from './runtime-overrides';
41
41
  import { installRetentionSweep } from './runtime-purge';
42
- import type { DevDbClient } from './runtime-queue';
42
+ import type { DevDbClient, SchemaMode } from './runtime-queue';
43
43
  import { pgExecutorFor, startQueue } from './runtime-queue';
44
- import { loadRealtimeConfig } from './runtime-realtime';
44
+ import { realtimeConfigOf } from './runtime-realtime';
45
45
 
46
46
  export interface RunningServices {
47
47
  readonly services: DevServices;
@@ -57,7 +57,8 @@ export interface RunningServices {
57
57
  */
58
58
  readonly realtime: RealtimeConfig;
59
59
  /**
60
- * `app.config.ts`'s `jobs.queues` / `concurrency` / `visibilityTimeoutMs`, for the `worker` role.
60
+ * `app.config.ts`'s `jobs.queues` / `concurrency` / `visibilityTimeoutMs`, for the `worker` role —
61
+ * the queues replaced by the boot env's `WORKER_QUEUES` when it is set (`runtime-jobs.ts`).
61
62
  * Optional because a `RunningServices` can be hand-built, and a test runtime with none keeps the
62
63
  * worker's own defaults — the answer it always got. `startServices` always sets it.
63
64
  */
@@ -87,7 +88,7 @@ export interface RunningServices {
87
88
  * `roles.web.replicas: 3` and `x new` scaffolds two.
88
89
  *
89
90
  * OPTIONAL because a `RunningServices` can be hand-built: a test's fixture runtime has a stub
90
- * client and no shared store, which is `createServer`'s memory store and `scope: 'process'` —
91
+ * client and no shared store, which is `httpServer`'s memory store and `scope: 'process'` —
91
92
  * the honest answer for it, and the one `startWeb` derives.
92
93
  */
93
94
  readonly rateLimitStore?: RateLimitStore;
@@ -301,15 +302,35 @@ async function release(steps: readonly (() => void | Promise<void>)[]): Promise<
301
302
  return failures;
302
303
  }
303
304
 
305
+ /**
306
+ * Every STOP path's form of `release`: every step runs, newest first, and the FIRST failure is
307
+ * rethrown — it is the cause, the rest its consequences. The one helper for this process's stops
308
+ * (`startServices`, `startRoles`, `bootServing`, `x dev`): a chain of `await`s with no `try`
309
+ * skipped every step after one rejection, and the replicator rejects a refused close on purpose.
310
+ * Steps are listed in BOOT order, as an unwind list is.
311
+ */
312
+ export async function releaseOrThrow(
313
+ steps: readonly (() => void | Promise<void>)[],
314
+ ): Promise<void> {
315
+ const failures = await release(steps);
316
+ if (failures.length > 0) throw failures[0];
317
+ }
318
+
304
319
  export async function startServices(
305
320
  services: DevServices,
306
321
  env: Env,
307
322
  overrides?: RuntimeOverrides,
323
+ /** `'verify'` for a serving role on an external database — `runtime-queue.ts`'s `SchemaMode`. */
324
+ schema: SchemaMode = 'apply',
308
325
  ): Promise<RunningServices> {
326
+ // The app's config is loaded ONCE, here, first, and every section below is read out of that one
327
+ // validated object — mail's `retainMime` is the first reader, so it is loaded before mail.
328
+ const config = await loadAppConfig(services.root);
309
329
  // Before the queue: selection is pure — it parses `SMTP_URL` and builds a transport, it does
310
330
  // not dial. A typo'd credential must fail on the spot rather than after PGlite has started and
311
331
  // been unwound again, and it must fail at boot rather than on the first mail nobody receives.
312
- const selection = selectMailDriver(env);
332
+ // `mail.retainMime` rides along (`runtime-mail.ts`), refused here the same way.
333
+ const selection = selectAppMailDriver(env, config);
313
334
  // Same reason, same place: building a purge driver reads env and dials nothing, so a half-set
314
335
  // `FASTLY_API_TOKEN` without its service id fails here rather than on the first stale page.
315
336
  const cdn = selectPurgeDriver(env);
@@ -318,14 +339,14 @@ export async function startServices(
318
339
  // service starts, rather than quietly keeping the in-process bus. Which transport, which KV
319
340
  // bucket and which presence TTL is `@ultimat3/realtime`'s decision, and it is the same call a
320
341
  // `ROLE=sync` container makes, so this process cannot resolve the bus differently from the
321
- // container it stands in for. The root is `dirname(stateDir)`, as `loadCacheTiers` reads it.
322
- const realtime = await loadRealtimeConfig(dirname(services.stateDir));
323
- const workerConfig = await loadWorkerConfig(dirname(services.stateDir));
342
+ // container it stands in for.
343
+ const realtime = realtimeConfigOf(config);
344
+ const workerConfig = workerConfigOf(config, env);
324
345
  const bus: TransportSelection = selectTransport(env, realtime);
325
346
  // `env`, not the ambient one: this function is HANDED the boot's environment and every other
326
347
  // reader here already uses it, so a queue that asked `process.env` would decide the standby from
327
348
  // a different answer than the middleware that routes to it.
328
- const queue = await startQueue(services, overrides, env);
349
+ const queue = await startQueue(services, overrides, env, schema);
329
350
  const { db, jobs, outbox, events } = queue;
330
351
  // The same executor the jobs driver, the outbox, the event bus and the idempotency store run
331
352
  // on — one pool, one `Bun.sql` that does NOT satisfy `PgExecutor` (`Bun.sql.query` is
@@ -354,18 +375,23 @@ export async function startServices(
354
375
  postgresAuthLimiter({ executor, clock: systemClock, policy }),
355
376
  );
356
377
  started.push(() => resetAuthLimiters());
378
+ // Where an action's or a query's declared `rateLimit:` is counted — on every surface, so a
379
+ // task or a one-off command spends from the fleet's table too, not one process' memory. Before
380
+ // `loadApp` for the auth limiters' reason; `startRoles` stacks a deployment's override over it.
381
+ started.push(adoptRateLimitStore(rateLimitStore));
357
382
  // The hourly sweep over the framework tables this boot is responsible for. Every one of them
358
383
  // ships a `purgeExpired()` that nothing called, so every row written was a row kept —
359
384
  // `x_rate_limit` takes one upsert per request the web role serves, assets included.
360
385
  //
361
386
  // `inboxRetention` is READ HERE and not defaulted in `runtime-purge.ts`: the two windows are the
362
- // app's, `startServices` holds no `AppConfig`, and a loader that silently answered "never
363
- // sweep" from inside the sweep would be indistinguishable from an app that chose it.
387
+ // app's, and a loader that silently answered "never sweep" from inside the sweep would be
388
+ // indistinguishable from an app that chose it.
364
389
  started.push(
365
390
  installRetentionSweep({
366
391
  idempotency: queue.idempotency,
367
392
  rateLimit: rateLimitStore,
368
- inboxRetention: await loadInboxRetention(services.root),
393
+ events,
394
+ inboxRetention: inboxRetentionOf(config),
369
395
  }),
370
396
  );
371
397
  // One readiness check per resource this boot OWNS, released with it. Nothing in the tree
@@ -402,17 +428,15 @@ export async function startServices(
402
428
  started.push(() => resetMailDriver());
403
429
  const purge = overrides?.purge ?? cdn.driver;
404
430
  // Every tier this process reads through, plus the cross-instance invalidation hop, in one
405
- // call. The CDN tier used to be the only one registered here — so `createRedisTier`,
406
- // `createLruTier` and `createMemoTier` shipped with no caller at all and every replica
431
+ // call. The CDN tier used to be the only one registered here — so `redisTier`,
432
+ // `lruTier` and `memoTier` shipped with no caller at all and every replica
407
433
  // recomputed every cached read. Released with `resetTiers()`, which drops the whole registry:
408
434
  // this boot is the only thing that registers one, and a tier left behind would purge for a
409
435
  // process that has stopped.
410
436
  // The ladder is the app's declaration, never the environment's. `cache.tiers` was declared,
411
437
  // validated and documented while NOTHING read it — so an app asking for one rung got four,
412
- // and one asking for a shared tier got whatever `REDIS_URL` happened to say. `.x` is
413
- // `resolveServices`' own join, so its parent is the app root: the one fact this function needs
414
- // and does not already carry.
415
- const tiers = await loadCacheTiers(dirname(services.stateDir));
438
+ // and one asking for a shared tier got whatever `REDIS_URL` happened to say.
439
+ const tiers = cacheTiersOf(config);
416
440
  started.push(startCacheTiers({ env, purge, transport, tiers }));
417
441
 
418
442
  return {
@@ -443,8 +467,7 @@ export async function startServices(
443
467
  // the FIRST failure is rethrown because it is the cause and the rest are its consequences,
444
468
  // and every step still runs, so a refused shutdown never leaks into the next boot.
445
469
  async stop() {
446
- const failures = await release(started);
447
- if (failures.length > 0) throw failures[0];
470
+ await releaseOrThrow(started);
448
471
  },
449
472
  };
450
473
  } catch (error) {
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { finiteCount, readWithinLimit } from '@ultimat3/core';
7
7
  import type { RequestContext, Route, UltimateRequest } from '@ultimat3/http';
8
- import { json, NO_STORE } from '@ultimat3/http';
8
+ import { jsonResponse, NO_STORE } from '@ultimat3/http';
9
9
  import type { Storage } from '@ultimat3/storage';
10
10
  import {
11
11
  acceptSignedUpload,
@@ -74,7 +74,7 @@ async function acceptUpload(
74
74
  : { allowedContentTypes: [constraints.contentType] }),
75
75
  }),
76
76
  });
77
- return json({ key: stored.key }, { status: 201 });
77
+ return jsonResponse({ key: stored.key }, { status: 201 });
78
78
  }
79
79
 
80
80
  /**