@ultimat3/cli 22.15.0 → 24.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 (267) hide show
  1. package/CLAUDE.md +36 -13
  2. package/README.md +31 -8
  3. package/package.json +29 -28
  4. package/src/admin-catalog-keys.ts +16 -0
  5. package/src/admin-registration.ts +192 -0
  6. package/src/affected.ts +27 -35
  7. package/src/api-registration.ts +125 -11
  8. package/src/app-artifacts.ts +1 -1
  9. package/src/app-auth.ts +4 -27
  10. package/src/app-boundaries.ts +6 -20
  11. package/src/app-config-load.ts +51 -0
  12. package/src/app-env.ts +5 -11
  13. package/src/app-load.ts +55 -11
  14. package/src/app-manifest.ts +16 -8
  15. package/src/app-mcp.ts +10 -23
  16. package/src/app-permissions-borrowed.ts +129 -0
  17. package/src/app-permissions-site.ts +24 -0
  18. package/src/app-permissions.ts +0 -0
  19. package/src/app-reload-graph.ts +7 -30
  20. package/src/app-transport.ts +219 -0
  21. package/src/boundary-findings.ts +16 -4
  22. package/src/browser-transport.ts +160 -0
  23. package/src/budgets.ts +27 -34
  24. package/src/cdp-shot-driver.ts +3 -1
  25. package/src/cmd-auth-spec.ts +16 -0
  26. package/src/cmd-auth.ts +65 -0
  27. package/src/cmd-build-spec.ts +10 -3
  28. package/src/cmd-build.ts +107 -11
  29. package/src/cmd-db-backfill.ts +25 -12
  30. package/src/cmd-db.ts +66 -16
  31. package/src/cmd-deploy-helm.ts +7 -6
  32. package/src/cmd-deploy.ts +4 -4
  33. package/src/cmd-dev.ts +9 -8
  34. package/src/cmd-doctor-spec.ts +2 -9
  35. package/src/cmd-doctor.ts +75 -24
  36. package/src/cmd-env.ts +26 -2
  37. package/src/cmd-generate.ts +195 -47
  38. package/src/cmd-help.ts +19 -6
  39. package/src/cmd-i18n.ts +10 -9
  40. package/src/cmd-jobs-spec.ts +19 -3
  41. package/src/cmd-jobs.ts +136 -7
  42. package/src/cmd-manifest-spec.ts +1 -1
  43. package/src/cmd-manifest.ts +6 -25
  44. package/src/cmd-mcp.ts +46 -15
  45. package/src/cmd-new.ts +15 -1
  46. package/src/cmd-planned.ts +15 -8
  47. package/src/cmd-routes.ts +27 -3
  48. package/src/cmd-secrets-ring.ts +76 -0
  49. package/src/cmd-secrets-spec.ts +10 -2
  50. package/src/cmd-secrets.ts +95 -18
  51. package/src/cmd-shot-matrix.ts +1 -3
  52. package/src/cmd-shot.ts +12 -3
  53. package/src/cmd-tasks.ts +84 -15
  54. package/src/cmd-test-spec.ts +1 -7
  55. package/src/cmd-test.ts +13 -29
  56. package/src/cmd-verify.ts +13 -5
  57. package/src/comment-safe.ts +16 -0
  58. package/src/coverage-floor.ts +334 -0
  59. package/src/coverage-lcov.ts +193 -0
  60. package/src/coverage-source.ts +136 -0
  61. package/src/db-object-drift.ts +30 -0
  62. package/src/db-replay-engine.ts +115 -0
  63. package/src/db-schema-dump.ts +150 -0
  64. package/src/db-schema-refresh.ts +122 -0
  65. package/src/db-subscribes.ts +12 -4
  66. package/src/dev-dashboard-guard.ts +76 -0
  67. package/src/dev-dashboard.ts +51 -29
  68. package/src/dev-lock.ts +4 -7
  69. package/src/dev-route-table.ts +9 -0
  70. package/src/dispatch.ts +3 -6
  71. package/src/doctor-app-url.ts +39 -0
  72. package/src/doctor-auth.ts +95 -0
  73. package/src/doctor-coverage.ts +39 -0
  74. package/src/doctor-guards.ts +54 -0
  75. package/src/doctor-sealed.ts +132 -0
  76. package/src/document-graph.ts +82 -0
  77. package/src/drift-replica-identity.ts +42 -0
  78. package/src/error-catalog.ts +6 -9
  79. package/src/error-codes.ts +55 -3
  80. package/src/error-unthrown.ts +29 -11
  81. package/src/errors.ts +54 -83
  82. package/src/framework-schema-apply.ts +78 -0
  83. package/src/framework-schema-stamp.ts +96 -0
  84. package/src/framework-schema.ts +28 -2
  85. package/src/generate-files.ts +79 -12
  86. package/src/generate-grant-findings.ts +76 -0
  87. package/src/generate-grants.ts +77 -3
  88. package/src/generate-kinds.ts +52 -1
  89. package/src/generate-shadow.ts +46 -0
  90. package/src/generate-write.ts +62 -40
  91. package/src/generated-imports.ts +59 -0
  92. package/src/gh-target.ts +12 -7
  93. package/src/guard-sources.ts +104 -0
  94. package/src/guards.ts +23 -7
  95. package/src/handle-registration.ts +217 -0
  96. package/src/i18n-audit.ts +4 -1
  97. package/src/i18n-registration.ts +36 -0
  98. package/src/image-prepare.ts +51 -8
  99. package/src/import-closure.ts +165 -0
  100. package/src/index.ts +12 -5
  101. package/src/island-bundle.ts +3 -15
  102. package/src/island-store.ts +24 -16
  103. package/src/jobs-drain.ts +95 -21
  104. package/src/jobs-json.ts +50 -0
  105. package/src/jobs-report.ts +7 -4
  106. package/src/json-merge.ts +46 -0
  107. package/src/loopback-host.ts +13 -0
  108. package/src/manifest-staleness.ts +62 -0
  109. package/src/mcp-errors.ts +41 -4
  110. package/src/mcp-host.ts +16 -9
  111. package/src/mcp-test-run.ts +38 -0
  112. package/src/messages.ts +35 -0
  113. package/src/metrics-endpoint.ts +95 -24
  114. package/src/migration-extensions.ts +29 -0
  115. package/src/module-imports.ts +47 -0
  116. package/src/output.ts +24 -9
  117. package/src/page-navigation.ts +7 -23
  118. package/src/page-speculation.ts +5 -20
  119. package/src/permission-grants.ts +41 -9
  120. package/src/prerender-out.ts +34 -7
  121. package/src/prerender.ts +8 -5
  122. package/src/publish-closure.ts +101 -0
  123. package/src/pwa-artifacts.ts +26 -94
  124. package/src/registry.ts +2 -0
  125. package/src/revalidated-response.ts +4 -3
  126. package/src/role-load.ts +143 -0
  127. package/src/role-replicator.ts +16 -0
  128. package/src/role-start.ts +27 -5
  129. package/src/role-sync.ts +55 -20
  130. package/src/role-wake.ts +45 -0
  131. package/src/runtime-admin.ts +141 -0
  132. package/src/runtime-assets.ts +11 -7
  133. package/src/runtime-bindings.ts +4 -4
  134. package/src/runtime-cache.ts +38 -47
  135. package/src/runtime-hooks.ts +5 -2
  136. package/src/runtime-idempotency-scope.ts +23 -0
  137. package/src/runtime-isr.ts +36 -0
  138. package/src/runtime-jobs.ts +8 -33
  139. package/src/runtime-mfa-warning.ts +29 -0
  140. package/src/runtime-notify-retention.ts +10 -46
  141. package/src/runtime-overrides.ts +1 -1
  142. package/src/runtime-purge.ts +38 -16
  143. package/src/runtime-queue.ts +39 -11
  144. package/src/runtime-realtime.ts +9 -45
  145. package/src/runtime-render.ts +68 -35
  146. package/src/runtime-services.ts +22 -18
  147. package/src/runtime-storage.ts +10 -5
  148. package/src/scaffold-typecheck.ts +1 -0
  149. package/src/schema-diff.ts +16 -0
  150. package/src/schema-drift.ts +45 -15
  151. package/src/schema-dump-drift.ts +64 -0
  152. package/src/seo-routes.ts +80 -9
  153. package/src/serve-boot.ts +114 -158
  154. package/src/serve-drain.ts +9 -33
  155. package/src/serve-env.ts +9 -6
  156. package/src/serve-prebuilt-paths.ts +19 -0
  157. package/src/serve-prebuilt.ts +84 -0
  158. package/src/serve-web.ts +169 -0
  159. package/src/serve.ts +6 -9
  160. package/src/server-barrels.ts +54 -0
  161. package/src/shot-locale.ts +5 -23
  162. package/src/site-assets.ts +3 -5
  163. package/src/site-config.ts +9 -48
  164. package/src/site-seo.ts +1 -1
  165. package/src/sitemap-lastmod.ts +5 -0
  166. package/src/solid-loader.ts +12 -3
  167. package/src/source-edit.ts +49 -0
  168. package/src/source-files.ts +6 -0
  169. package/src/stored-object-headers.ts +43 -0
  170. package/src/sync-url.ts +2 -2
  171. package/src/tasks-facts.ts +3 -26
  172. package/src/templates/action.ts +147 -11
  173. package/src/templates/admin-catalog.ts +58 -0
  174. package/src/templates/admin-page.ts +36 -8
  175. package/src/templates/admin.ts +57 -17
  176. package/src/templates/backfill.ts +121 -87
  177. package/src/templates/entity.ts +140 -44
  178. package/src/templates/guard-animated-layout-property.ts +4 -11
  179. package/src/templates/guard-bare-error.ts +9 -13
  180. package/src/templates/guard-focus-visible.ts +4 -11
  181. package/src/templates/guard-image-dimensions.ts +6 -6
  182. package/src/templates/guard-island-without-states.ts +2 -4
  183. package/src/templates/guard-raw-breakpoint.ts +210 -0
  184. package/src/templates/guard-raw-colour.ts +4 -11
  185. package/src/templates/guard-raw-length.ts +375 -0
  186. package/src/templates/guard-raw-motion.ts +213 -0
  187. package/src/templates/guard-raw-shadow.ts +187 -0
  188. package/src/templates/guard-raw-z-index.ts +166 -0
  189. package/src/templates/guard-repo-raw-sql.ts +345 -0
  190. package/src/templates/guard-semantic-interactive.ts +6 -6
  191. package/src/templates/guard-undeclared-custom-property.ts +217 -0
  192. package/src/templates/guard-undefined-style-class.ts +215 -0
  193. package/src/templates/guard-untranslated-string.ts +6 -6
  194. package/src/templates/guard-unzoned-date.ts +9 -13
  195. package/src/templates/index.ts +3 -2
  196. package/src/templates/island.ts +34 -51
  197. package/src/templates/job-body-test.ts +121 -0
  198. package/src/templates/job.ts +23 -8
  199. package/src/templates/naming.ts +28 -0
  200. package/src/templates/policy.ts +16 -9
  201. package/src/templates/query.ts +242 -26
  202. package/src/templates/resource-create.ts +62 -18
  203. package/src/templates/resource-form-island.ts +58 -80
  204. package/src/templates/resource-page.ts +269 -0
  205. package/src/templates/resource-service.ts +96 -0
  206. package/src/templates/resource.ts +86 -61
  207. package/src/templates/route.ts +41 -35
  208. package/src/templates/scaffold-api.ts +55 -1
  209. package/src/templates/scaffold-app.ts +177 -53
  210. package/src/templates/scaffold-auth.ts +83 -19
  211. package/src/templates/scaffold-browser-client.ts +90 -0
  212. package/src/templates/scaffold-container-compose.ts +10 -1
  213. package/src/templates/scaffold-container.ts +20 -2
  214. package/src/templates/scaffold-dashboard-bare.ts +64 -2
  215. package/src/templates/scaffold-dashboard-example.ts +124 -31
  216. package/src/templates/scaffold-dashboard-shared.ts +1 -20
  217. package/src/templates/scaffold-dashboard.ts +1 -2
  218. package/src/templates/scaffold-db-client.ts +144 -0
  219. package/src/templates/scaffold-db-package.ts +90 -8
  220. package/src/templates/scaffold-docs.ts +50 -5
  221. package/src/templates/scaffold-guards.ts +54 -11
  222. package/src/templates/scaffold-helm-fences.ts +76 -0
  223. package/src/templates/scaffold-helm-templates.ts +32 -7
  224. package/src/templates/scaffold-helm.ts +31 -1
  225. package/src/templates/scaffold-http.ts +9 -7
  226. package/src/templates/scaffold-repo.ts +61 -3
  227. package/src/templates/scaffold-roles.ts +56 -9
  228. package/src/templates/scaffold-shell.ts +74 -105
  229. package/src/templates/scaffold-site.ts +45 -19
  230. package/src/templates/scaffold-ui-package.ts +18 -0
  231. package/src/templates/slice-foundation.ts +46 -8
  232. package/src/test-counts.ts +14 -2
  233. package/src/test-ignores.ts +25 -0
  234. package/src/test-passes.ts +0 -15
  235. package/src/test-select.ts +5 -14
  236. package/src/test-shards.ts +41 -93
  237. package/src/test-workers.ts +26 -4
  238. package/src/theme-boot.ts +6 -31
  239. package/src/transport-calls.ts +105 -0
  240. package/src/trusted-hops-error.ts +17 -0
  241. package/src/unscanned-admin.ts +54 -0
  242. package/src/unthrown-codes.ts +26 -0
  243. package/src/verify-checks.ts +43 -53
  244. package/src/verify-coverage-run.ts +190 -0
  245. package/src/verify-deadline.ts +353 -0
  246. package/src/verify-floor.ts +36 -9
  247. package/src/verify-merge.ts +171 -45
  248. package/src/verify-part-step.ts +90 -0
  249. package/src/verify-progress.ts +25 -0
  250. package/src/verify-role-load-probe.ts +54 -0
  251. package/src/verify-role-load.ts +148 -0
  252. package/src/verify-run.ts +116 -37
  253. package/src/verify-shard.ts +33 -11
  254. package/src/verify-stalled.ts +152 -0
  255. package/src/verify-step.ts +42 -1
  256. package/src/verify-tests.ts +83 -8
  257. package/src/workspace-checks.ts +50 -8
  258. package/src/workspace-dep-edit.ts +127 -0
  259. package/src/build-errors.ts +0 -54
  260. package/src/cmd-dev-fixture.ts +0 -204
  261. package/src/dev-live-fixture.ts +0 -122
  262. package/src/policy-fixture.ts +0 -78
  263. package/src/realtime-browser-probe-fixture.ts +0 -9
  264. package/src/role-start-fixture.ts +0 -78
  265. package/src/scaffold-fixture.ts +0 -160
  266. package/src/templates/island-fixture.ts +0 -76
  267. package/src/verify-errors.ts +0 -49
@@ -7,16 +7,17 @@
7
7
  import {
8
8
  type PostgresIdempotencyStore,
9
9
  postgresIdempotencyStore,
10
+ requestDeadlineMs,
10
11
  resetIdempotency,
11
12
  setIdempotencyStore,
12
13
  } from '@ultimat3/action';
13
14
  import type { DbClient, PgliteClient, PostgresClient, SqlFragment } from '@ultimat3/db';
14
15
  import {
16
+ baseClient,
15
17
  createPgliteClient,
16
18
  createPostgresClient,
17
19
  currentTx,
18
20
  pgliteDataDir,
19
- raw,
20
21
  setDbClient,
21
22
  } from '@ultimat3/db';
22
23
  import type { Tx } from '@ultimat3/entity';
@@ -26,13 +27,15 @@ import {
26
27
  createPgDriver,
27
28
  createPgEventBus,
28
29
  createPgOutboxStore,
30
+ resetEventBus,
29
31
  resetJobDriver,
30
32
  resetJobsFacade,
31
33
  setEventBus,
32
34
  setJobDriver,
33
35
  setJobsFacade,
34
36
  } from '@ultimat3/jobs';
35
- import { applyFrameworkSchema } from './framework-schema';
37
+ import { applyLockedSchema, verifySchema } from './framework-schema-apply';
38
+ import { appExtensions } from './migration-extensions';
36
39
  import type { DevServices } from './runtime-bindings';
37
40
  import type { RuntimeOverrides } from './runtime-overrides';
38
41
  import { attachReplica, type ReplicaEnv, replicaUrlFor } from './runtime-replica';
@@ -93,7 +96,13 @@ export function startDb(services: DevServices, env: ReplicaEnv): StartedDb {
93
96
  binding.mode === 'embedded'
94
97
  ? // `pgliteDataDir` is `@ultimat3/db`'s own reader of the `pglite://` form; a second parser
95
98
  // here is a second thing to keep right when the form changes.
96
- createPgliteClient({ dataDir: pgliteDataDir(binding.url) })
99
+ // Linked at boot, from the app's own migrations: PGlite cannot `create extension` one it
100
+ // was not handed, and the schema dump's scratch replay already asks this same reader — an
101
+ // app that replayed in the gate and failed in `x dev` was the two disagreeing.
102
+ createPgliteClient({
103
+ dataDir: pgliteDataDir(binding.url),
104
+ extensions: () => appExtensions(services.root),
105
+ })
97
106
  : createPostgresClient({ url: binding.url });
98
107
  const attached = attachReplica(client, replicaUrlFor(binding, env));
99
108
  setDbClient(attached.client);
@@ -127,10 +136,18 @@ export function pgExecutorFor(client: DbClient): PgExecutor {
127
136
  * `@ultimat3/notify` because a package that holds no database dependency cannot apply its own
128
137
  * schema — the same reason `SQL_JOBS_TABLE` is applied by the boot.
129
138
  */
130
- async function applySchema(client: DevDbClient): Promise<void> {
131
- await applyFrameworkSchema((statement) => client.execute(raw(statement)));
132
- }
139
+ /**
140
+ * `'apply'` — `ROLE=migrate`, `x dev` and every CLI command — runs the DDL under the migration
141
+ * lock with a bounded `lock_timeout` and stamps the build. `'verify'` — a serving role on an
142
+ * external database — runs none: an `alter table` queued behind one open transaction stalled every
143
+ * enqueue in the fleet on each pod start, and racing `create table`s were a first-deploy `23505`.
144
+ */
145
+ export type SchemaMode = 'apply' | 'verify';
133
146
 
147
+ async function applySchema(client: DevDbClient, mode: SchemaMode): Promise<void> {
148
+ if (mode === 'verify') await verifySchema(client);
149
+ else await applyLockedSchema(client);
150
+ }
134
151
  /**
135
152
  * The dev queue is the real Postgres queue on the embedded Postgres — claiming, leases and the
136
153
  * one-live-job-per-key index all behave here exactly as in production. A memory queue in dev
@@ -162,9 +179,10 @@ async function applySchema(client: DevDbClient): Promise<void> {
162
179
  async function startJobs(
163
180
  client: DevDbClient,
164
181
  replica: PostgresClient | undefined,
165
- overrides?: RuntimeOverrides,
182
+ overrides: RuntimeOverrides | undefined,
183
+ schema: SchemaMode,
166
184
  ): Promise<RunningQueue> {
167
- await applySchema(client);
185
+ await applySchema(client, schema);
168
186
  const executor = pgExecutorFor(client);
169
187
  const driver = overrides?.jobs ?? createPgDriver({ executor });
170
188
  setJobDriver(driver);
@@ -178,7 +196,14 @@ async function startJobs(
178
196
  );
179
197
  const events = createPgEventBus({ executor });
180
198
  setEventBus(events);
181
- const idempotency = postgresIdempotencyStore({ executor });
199
+ const idempotency = postgresIdempotencyStore({
200
+ executor,
201
+ // `baseClient`, read per call and never `client`: a transaction's origin is the AMBIENT client
202
+ // `startDb` installed, which is the replicated pair when a standby is configured.
203
+ origin: baseClient,
204
+ // The app's own `requestTimeoutMs`, read per reservation — it is declared after this boot.
205
+ reclaimAfterMs: requestDeadlineMs,
206
+ });
182
207
  setIdempotencyStore(idempotency);
183
208
  return {
184
209
  db: client,
@@ -197,7 +222,8 @@ async function startJobs(
197
222
  * startup entirely, so every query it makes fails on a connection this process already dropped.
198
223
  *
199
224
  * The facade goes with the driver for the same reason: an enqueue routed through a store bound to
200
- * a closed client is a staged row nothing will ever publish.
225
+ * a closed client is a staged row nothing will ever publish — and the event bus with both, or a
226
+ * `publishEvent` after `stop()` runs over the closed pool.
201
227
  */
202
228
  async function releaseQueue(
203
229
  db: DevDbClient,
@@ -208,6 +234,7 @@ async function releaseQueue(
208
234
  // holds this client, and the next command in this process would reserve keys over a closed one.
209
235
  resetIdempotency();
210
236
  resetJobsFacade();
237
+ resetEventBus();
211
238
  resetJobDriver();
212
239
  setDbClient(undefined);
213
240
  await jobs?.close?.();
@@ -232,13 +259,14 @@ export async function startQueue(
232
259
  * this file reads it: see `startDb` for what two readers of one question cost.
233
260
  */
234
261
  env: ReplicaEnv = process.env,
262
+ schema: SchemaMode = 'apply',
235
263
  ): Promise<RunningQueue> {
236
264
  const { client: db, replica } = startDb(services, env);
237
265
  try {
238
266
  // Pay the Postgres boot here, so the first request is not the slow one and a broken database
239
267
  // fails at boot rather than on some later query.
240
268
  await db.ping();
241
- return await startJobs(db, replica, overrides);
269
+ return await startJobs(db, replica, overrides, schema);
242
270
  } catch (error) {
243
271
  // `db.ping()` or `startJobs` is where a broken database is supposed to fail. Without this,
244
272
  // the caller exits holding the PGlite lock and the ambient accessors, and nothing is left to
@@ -1,55 +1,19 @@
1
- // `realtime.enabled`, `realtime.transport` and `realtime.urlEnv`, read out of the app's own
2
- // `app.config.ts` for the boot. The sibling of `runtime-notify-retention.ts` and `runtime-cache.ts`, and
3
- // structural for their reason: `startServices` holds no `AppConfig`, and `defineConfig` returns a
4
- // plain object. Until 22.0.0 no boot read any of the three and `NATS_URL` alone decided the bus.
1
+ // `realtime.enabled`, `realtime.transport` and `realtime.urlEnv` for the boot, out of the config
2
+ // `startServices` loads once (`app-config-load.ts`). Until 22.0.0 no boot read any of the three
3
+ // and `NATS_URL` alone decided the bus.
5
4
 
6
- // why: Bun ships no path-joining API — `Object.keys(Bun)` has `file`, `write`, `Glob`,
7
- // `pathToFileURL` and `fileURLToPath`, and nothing that joins a path.
8
- import { join } from 'node:path';
9
- import { ConfigInvalidError, type RealtimeConfig, type RealtimeTransport } from '@ultimat3/core';
10
- import { APP_CONFIG_EXPORT } from './app-auth';
11
- import { APP_CONFIG_FILE } from './app-root';
5
+ import type { AppConfig, RealtimeConfig } from '@ultimat3/core';
12
6
 
13
- /** Core's `defaults()` for the section, restated as the answer for a config that names none. */
7
+ /** Core's `defaults()` for the section — the answer for a root with no `app.config.ts`. */
14
8
  export const REALTIME_DEFAULTS: RealtimeConfig = Object.freeze({
15
9
  enabled: true,
16
10
  transport: 'memory',
17
11
  urlEnv: undefined,
18
12
  });
19
13
 
20
- const TRANSPORTS: readonly RealtimeTransport[] = ['memory', 'nats'];
21
-
22
- const isRecord = (value: unknown): value is Record<string, unknown> =>
23
- typeof value === 'object' && value !== null;
24
-
25
- /** A transport this boot can build, or a refusal — never a guess at a bus. */
26
- function transportOf(section: Record<string, unknown>): RealtimeTransport {
27
- const value = section['transport'];
28
- if (value === undefined) return REALTIME_DEFAULTS.transport;
29
- const known = TRANSPORTS.find((transport) => transport === value);
30
- if (known !== undefined) return known;
31
- throw new ConfigInvalidError({
32
- cause: `realtime.transport in ${APP_CONFIG_FILE} is not one of ${TRANSPORTS.join(', ')}`,
33
- fix: `set realtime: { transport: 'memory' } in ${APP_CONFIG_FILE}, or { transport: 'nats', urlEnv: 'NATS_URL' } for more than one node`,
34
- meta: { key: 'realtime.transport' },
35
- });
36
- }
37
-
38
14
  /**
39
- * The section as the boot obeys it. A key the file does not name keeps core's default, the same
40
- * per-key merge `defineConfig` performs, so the boot and the app's own config object agree.
15
+ * The section as the boot obeys it — `defineConfig`'s per-key merge, so the boot and the app's own
16
+ * config object agree, and a transport nothing builds is core's `X_CONFIG_INVALID`.
41
17
  */
42
- export async function loadRealtimeConfig(root: string): Promise<RealtimeConfig> {
43
- const path = join(root, APP_CONFIG_FILE);
44
- if (!(await Bun.file(path).exists())) return REALTIME_DEFAULTS;
45
- const module = (await import(path)) as Record<string, unknown>;
46
- const config = module[APP_CONFIG_EXPORT];
47
- const section = isRecord(config) ? config['realtime'] : undefined;
48
- if (!isRecord(section)) return REALTIME_DEFAULTS;
49
- const { enabled, urlEnv } = section;
50
- return {
51
- enabled: typeof enabled === 'boolean' ? enabled : REALTIME_DEFAULTS.enabled,
52
- transport: transportOf(section),
53
- urlEnv: typeof urlEnv === 'string' && urlEnv.trim() !== '' ? urlEnv.trim() : undefined,
54
- };
55
- }
18
+ export const realtimeConfigOf = (config: AppConfig | undefined): RealtimeConfig =>
19
+ config?.realtime ?? REALTIME_DEFAULTS;
@@ -1,35 +1,28 @@
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
6
  // why: Bun ships no path API; an island's file is its route file's directory joined to its `src`.
18
7
  import { posix } from 'node:path';
8
+ import { actionPathStyle } from '@ultimat3/action';
19
9
  import { clientScopeOf } from '@ultimat3/auth';
20
10
  import type { Ctx } from '@ultimat3/core';
21
- import { CLIENT_SCOPE_HEADER } from '@ultimat3/core';
11
+ import { CLIENT_SCOPE_HEADER, createSingleFlight } from '@ultimat3/core';
22
12
  import type {
23
13
  RouteMeta as HttpRouteMeta,
24
14
  RedirectIntent,
15
+ RequestContext,
25
16
  Route,
26
17
  RouteParams,
18
+ UltimateRequest,
27
19
  } from '@ultimat3/http';
28
20
  import { asCtx, html, NO_STORE, redirect, stream, takeRedirect } from '@ultimat3/http';
29
21
  import { currentLocale, localeConfig } from '@ultimat3/i18n';
30
22
  import type { IslandCollector, RenderResult, RouteData, RouteEntry } from '@ultimat3/render';
31
23
  import {
32
24
  clientBootTags,
25
+ clientPathStyleTags,
33
26
  clientPersistTags,
34
27
  clientScopeTag,
35
28
  clientSyncTags,
@@ -50,7 +43,6 @@ import {
50
43
  import type { IsrController } from '@ultimat3/render/server';
51
44
  import {
52
45
  contentHash,
53
- createIsrController,
54
46
  isrKey,
55
47
  ROOT_ELEMENT_ID,
56
48
  renderComponent,
@@ -67,6 +59,7 @@ import {
67
59
  navigationTagsOf,
68
60
  principalRelocation,
69
61
  } from './page-navigation';
62
+ import { attachedIsr } from './runtime-isr';
70
63
  import type { StaticResult } from './static-document';
71
64
  import { createStaticMemo, staticMemoKey, staticResponse } from './static-document';
72
65
  import { styleBundle } from './style-bundle';
@@ -75,7 +68,11 @@ export type { DocumentOptions, IslandResolver } from './document-options';
75
68
 
76
69
  export interface DevRenderOptions extends DocumentOptions {
77
70
  readonly buildId: string;
78
- /** Injected so a test can drive the ISR store without a timer. */
71
+ /**
72
+ * The boot's own controller, attached and released by it (`runtime-isr.ts`). Omitted, one is
73
+ * built AND attached here, never released — a caller with no stop list (a contract test) still
74
+ * gets pages a tag bust reaches; the revalidator slot is the latest attach's.
75
+ */
79
76
  readonly isr?: IsrController;
80
77
  /**
81
78
  * Keep each `static` document after its first render (`static-document.ts`). The container sets
@@ -130,6 +127,10 @@ const headFor = async (
130
127
  }),
131
128
  [
132
129
  ...(options.sync === undefined ? [] : clientSyncTags(options.sync)),
130
+ // Every document, shareable ones included: the style is the app's, not a visitor's, and
131
+ // it is the only way an island's `rpc()` learns it. Read per render — `defineApi` may run
132
+ // after these routes are built. `'resource'`, the default, is no tag.
133
+ ...clientPathStyleTags(actionPathStyle()),
133
134
  ...router,
134
135
  // The page boot rides the scope tag: its whole job — restoring a principal's persisted
135
136
  // records and replaying its queued writes — is per principal, and a shareable document
@@ -150,7 +151,8 @@ const headFor = async (
150
151
 
151
152
  /**
152
153
  * `<link rel="stylesheet">` for the surface's own stylesheets, or nothing at all when the surface
153
- * imports none.
154
+ * imports none. Inlined until 2026-09-06: every `app/` document of ai-maxxing then carried the same
155
+ * 156,738-byte `<style>` (92% of it) under `no-store`, re-paid on every navigation.
154
156
  *
155
157
  * In `<head>`, which is what keeps this a swap and not a regression: a `<link rel="stylesheet">`
156
158
  * there is render-blocking in every browser, exactly as the inline block was, so there is no
@@ -423,8 +425,11 @@ const metaOf = (entry: RouteEntry, head?: NavigationDocumentHead): HttpRouteMeta
423
425
  * only a test does — and then guard and page fall back together.
424
426
  */
425
427
  export function appRoutes(options: DevRenderOptions): readonly Route[] {
426
- const isr = options.isr ?? createIsrController({ buildId: options.buildId });
428
+ const isr = options.isr ?? attachedIsr({ buildId: options.buildId }).isr;
427
429
  const memo = createStaticMemo();
430
+ // A cold static key is ONE render however many requests arrive during it: the memo alone was
431
+ // check-then-act, so a burst at boot ran `load` and the render once per request.
432
+ const firstRender = createSingleFlight();
428
433
  return routeEntries().map((registered) => ({
429
434
  method: 'GET' as const,
430
435
  path: registered.path,
@@ -444,23 +449,51 @@ export function appRoutes(options: DevRenderOptions): readonly Route[] {
444
449
  : undefined;
445
450
  const kept = key === undefined ? undefined : memo.get(key);
446
451
  if (kept !== undefined) return staticResponse(request, kept);
447
- const data: DevRouteData = { url: request.url.href, params: ctx.params };
448
- // ONCE per request, before the mode is chosen: every branch of `resultFor` reads this same
449
- // object, so a route's `load` runs exactly once however its mode splits head from body.
450
- const loaded = await routeDataFor(entry.config, data);
451
- // `setRedirect()` inside `load` — the same slot an action's handler fills — answers here,
452
- // before a document is rendered for a page the visitor is leaving. `withStatus` refuses a
453
- // 3xx because a rendered document has no `Location`; this is the path that has one.
454
- const to = takeRedirect(ctx);
455
- if (to !== undefined) return loadRedirect(entry, to);
456
- const result = await resultFor(entry, data, loaded, options, isr, asCtx(ctx));
457
- if (entry.config.render !== 'static' || typeof result.body !== 'string') {
458
- return responseOf(result);
459
- }
460
- const document: StaticResult = { ...result, body: result.body };
461
- if (key !== undefined) memo.set(key, document);
452
+ let led = false;
453
+ const render = async (): Promise<Rendered> => {
454
+ led = true;
455
+ const rendered = await renderEntry(entry, request, ctx, options, isr);
456
+ if (key !== undefined && rendered.document !== undefined) memo.set(key, rendered.document);
457
+ return rendered;
458
+ };
459
+ const shared = key === undefined ? await render() : await firstRender.run(key, render);
460
+ // Only a DOCUMENT is shared. A redirect `load` decided was its request's own, so a request
461
+ // that joined one renders for itself rather than answer with another request's `Location`.
462
+ const rendered = shared.document !== undefined || led ? shared : await render();
462
463
  // The ETag was only ever a header: a matching `If-None-Match` is a 304, not the page again.
463
- return staticResponse(request, document);
464
+ return rendered.document === undefined
465
+ ? rendered.respond()
466
+ : staticResponse(request, rendered.document);
464
467
  },
465
468
  }));
466
469
  }
470
+
471
+ /** A static document a joined request can share, or how to answer when there is none. */
472
+ interface Rendered {
473
+ readonly document?: StaticResult;
474
+ readonly respond: () => Response;
475
+ }
476
+
477
+ async function renderEntry(
478
+ entry: RouteEntry,
479
+ request: UltimateRequest,
480
+ ctx: RequestContext,
481
+ options: DevRenderOptions,
482
+ isr: IsrController,
483
+ ): Promise<Rendered> {
484
+ const data: DevRouteData = { url: request.url.href, params: ctx.params };
485
+ // ONCE per request, before the mode is chosen: every branch of `resultFor` reads this same
486
+ // object, so a route's `load` runs exactly once however its mode splits head from body.
487
+ const loaded = await routeDataFor(entry.config, data);
488
+ // `setRedirect()` inside `load` — the same slot an action's handler fills — answers here,
489
+ // before a document is rendered for a page the visitor is leaving. `withStatus` refuses a
490
+ // 3xx because a rendered document has no `Location`; this is the path that has one.
491
+ const to = takeRedirect(ctx);
492
+ if (to !== undefined) return { respond: () => loadRedirect(entry, to) };
493
+ const result = await resultFor(entry, data, loaded, options, isr, asCtx(ctx));
494
+ if (entry.config.render !== 'static' || typeof result.body !== 'string') {
495
+ return { respond: () => responseOf(result) };
496
+ }
497
+ const document: StaticResult = { ...result, body: result.body };
498
+ return { document, respond: () => responseOf(document) };
499
+ }
@@ -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';
@@ -31,17 +30,18 @@ import type { Transport, TransportSelection } from '@ultimat3/realtime/server';
31
30
  import { selectTransport } from '@ultimat3/realtime/server';
32
31
  import type { Storage } from '@ultimat3/storage';
33
32
  import { defineStorage, localDriver, s3Driver, usesDevStorageSecret } from '@ultimat3/storage';
33
+ import { loadAppConfig } from './app-config-load';
34
34
  import { LocalDiskUnsafeError, StorageUnwritableError } from './errors';
35
35
  import { msg } from './messages';
36
36
  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';
37
+ import { cacheTiersOf, startCacheTiers } from './runtime-cache';
38
+ import { type WorkerConfig, workerConfigOf } from './runtime-jobs';
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;
@@ -182,7 +182,8 @@ export function startStorage(services: DevServices, env: Env, override?: Storage
182
182
  bucket,
183
183
  endpoint: binding.url,
184
184
  ...(env['S3_REGION'] === undefined ? {} : { region: env['S3_REGION'] }),
185
- // MinIO needs path style; R2 and AWS do not. Declared, never sniffed from the endpoint.
185
+ // A gateway addressed by host and port needs path style; R2 and AWS do not. Declared,
186
+ // never sniffed from the endpoint.
186
187
  forcePathStyle: env['S3_FORCE_PATH_STYLE'] === '1',
187
188
  }),
188
189
  },
@@ -304,6 +305,8 @@ export async function startServices(
304
305
  services: DevServices,
305
306
  env: Env,
306
307
  overrides?: RuntimeOverrides,
308
+ /** `'verify'` for a serving role on an external database — `runtime-queue.ts`'s `SchemaMode`. */
309
+ schema: SchemaMode = 'apply',
307
310
  ): Promise<RunningServices> {
308
311
  // Before the queue: selection is pure — it parses `SMTP_URL` and builds a transport, it does
309
312
  // not dial. A typo'd credential must fail on the spot rather than after PGlite has started and
@@ -317,14 +320,16 @@ export async function startServices(
317
320
  // service starts, rather than quietly keeping the in-process bus. Which transport, which KV
318
321
  // bucket and which presence TTL is `@ultimat3/realtime`'s decision, and it is the same call a
319
322
  // `ROLE=sync` container makes, so this process cannot resolve the bus differently from the
320
- // container it stands in for. The root is `dirname(stateDir)`, as `loadCacheTiers` reads it.
321
- const realtime = await loadRealtimeConfig(dirname(services.stateDir));
322
- const workerConfig = await loadWorkerConfig(dirname(services.stateDir));
323
+ // container it stands in for. The app's config is loaded ONCE, here, and every section below is
324
+ // read out of that one validated object.
325
+ const config = await loadAppConfig(services.root);
326
+ const realtime = realtimeConfigOf(config);
327
+ const workerConfig = workerConfigOf(config);
323
328
  const bus: TransportSelection = selectTransport(env, realtime);
324
329
  // `env`, not the ambient one: this function is HANDED the boot's environment and every other
325
330
  // reader here already uses it, so a queue that asked `process.env` would decide the standby from
326
331
  // a different answer than the middleware that routes to it.
327
- const queue = await startQueue(services, overrides, env);
332
+ const queue = await startQueue(services, overrides, env, schema);
328
333
  const { db, jobs, outbox, events } = queue;
329
334
  // The same executor the jobs driver, the outbox, the event bus and the idempotency store run
330
335
  // on — one pool, one `Bun.sql` that does NOT satisfy `PgExecutor` (`Bun.sql.query` is
@@ -358,13 +363,14 @@ export async function startServices(
358
363
  // `x_rate_limit` takes one upsert per request the web role serves, assets included.
359
364
  //
360
365
  // `inboxRetention` is READ HERE and not defaulted in `runtime-purge.ts`: the two windows are the
361
- // app's, `startServices` holds no `AppConfig`, and a loader that silently answered "never
362
- // sweep" from inside the sweep would be indistinguishable from an app that chose it.
366
+ // app's, and a loader that silently answered "never sweep" from inside the sweep would be
367
+ // indistinguishable from an app that chose it.
363
368
  started.push(
364
369
  installRetentionSweep({
365
370
  idempotency: queue.idempotency,
366
371
  rateLimit: rateLimitStore,
367
- inboxRetention: await loadInboxRetention(services.root),
372
+ events,
373
+ inboxRetention: inboxRetentionOf(config),
368
374
  }),
369
375
  );
370
376
  // One readiness check per resource this boot OWNS, released with it. Nothing in the tree
@@ -408,10 +414,8 @@ export async function startServices(
408
414
  // process that has stopped.
409
415
  // The ladder is the app's declaration, never the environment's. `cache.tiers` was declared,
410
416
  // validated and documented while NOTHING read it — so an app asking for one rung got four,
411
- // and one asking for a shared tier got whatever `REDIS_URL` happened to say. `.x` is
412
- // `resolveServices`' own join, so its parent is the app root: the one fact this function needs
413
- // and does not already carry.
414
- const tiers = await loadCacheTiers(dirname(services.stateDir));
417
+ // and one asking for a shared tier got whatever `REDIS_URL` happened to say.
418
+ const tiers = cacheTiersOf(config);
415
419
  started.push(startCacheTiers({ env, purge, transport, tiers }));
416
420
 
417
421
  return {
@@ -24,6 +24,7 @@ import {
24
24
  orgMismatch,
25
25
  } from '@ultimat3/storage';
26
26
  import { storageUploadRoute } from './runtime-storage-upload';
27
+ import { storedObjectHeaders } from './stored-object-headers';
27
28
 
28
29
  /**
29
30
  * The one capability that gates reading a stored object, on every disk. A permission and not a
@@ -103,11 +104,10 @@ export function authorizeStorageRead(input: StorageReadInput, ctx: RequestContex
103
104
  */
104
105
  export function assertReadableKey(key: string, actor: Actor): string {
105
106
  const safe = assertSafeKey(key);
106
- // An actor with no org is inside no org, so every tenant-scoped key is somebody else's. Checked
107
- // before `isWithinOrg`, which reads an empty org as a malformed key and would blame the caller's
108
- // URL for the actor's missing claim.
107
+ // An actor with no org is inside no org, so every tenant-scoped key is somebody else's —
108
+ // `isWithinOrg` answers exactly that for an empty org, without throwing.
109
109
  const orgId = actor.orgId ?? '';
110
- if (isTenantScoped(safe) && (orgId === '' || !isWithinOrg(safe, orgId))) {
110
+ if (isTenantScoped(safe) && !isWithinOrg(safe, orgId)) {
111
111
  throw orgMismatch(safe, orgId);
112
112
  }
113
113
  return safe;
@@ -185,9 +185,14 @@ export function storageResponse(request: UltimateRequest, read: StorageRead): Re
185
185
  const headers = new Headers({
186
186
  'content-type': read.object.contentType,
187
187
  etag,
188
- 'last-modified': read.object.lastModified.toUTCString(),
189
188
  'accept-ranges': 'bytes',
189
+ // A type a browser would RUN (html, svg, xml) is a download, never a page on this origin.
190
+ ...storedObjectHeaders(read.object.contentType),
190
191
  });
192
+ // Omitted, never invented: a disk whose provider reported no date has none, and a `Last-Modified`
193
+ // made up for it is a validator a cache would go on to trust.
194
+ const modified = read.object.lastModified;
195
+ if (modified !== undefined) headers.set('last-modified', modified.toUTCString());
191
196
  // Revalidation costs a request and no bytes, which is the trade an authorized response wants:
192
197
  // the bytes never change under a key, but the actor's permission to read them can be revoked.
193
198
  if (etagMatches(request.header('if-none-match'), etag)) {
@@ -151,6 +151,7 @@ const overlay = (root: string, app: string): string =>
151
151
  // name, and `cli/serve` as `packages/cli/serve`. Longest prefix wins, so these are
152
152
  // consulted first. `@ultimat3/cli/serve` is what `x new`'s `apps/web/server.ts` imports.
153
153
  '@ultimat3/ui/icons/*': [`${root}/packages/ui/src/icons/glyphs/*`],
154
+ '@ultimat3/query/client': [`${root}/packages/query/src/client`],
154
155
  '@ultimat3/render/server': [`${root}/packages/render/src/server`],
155
156
  '@ultimat3/cli/serve': [`${root}/packages/cli/src/serve-entry`],
156
157
  '@ultimat3/testing/test-types': [`${root}/packages/testing/src/test-types`],
@@ -40,6 +40,11 @@ export interface SchemaDifference {
40
40
 
41
41
  const DECLARED_ONLY = 'is declared by the entities and no migration recorded it';
42
42
  const RECORDED_ONLY = 'was recorded by the newest migration and no entity declares it';
43
+ /** The difference's name — what `x db gen "record …"` is told to write down. */
44
+ const REPLICA_IDENTITY_FULL = 'replica identity full';
45
+ const REPLICA_IDENTITY_UNRECORDED =
46
+ 'needs REPLICA IDENTITY FULL — a channel with params or a live query subscribes to it — and no ' +
47
+ 'migration recorded it';
43
48
 
44
49
  const columns = (names: readonly string[]): string => names.join(', ');
45
50
 
@@ -187,6 +192,17 @@ function compareTable(declared: TableDescription, recorded: TableDescription): S
187
192
  detail: `declares primary key (${columns(declared.primaryKey)}) and the migration recorded (${columns(recorded.primaryKey)})`,
188
193
  });
189
194
  }
195
+ // One direction: `replica-identity.ts` never reverts the identity, so a recorded FULL nothing
196
+ // needs any more is what `x db gen` leaves behind on purpose, not a difference.
197
+ if (declared.replicaIdentityFull === true && recorded.replicaIdentityFull !== true) {
198
+ out.push({
199
+ direction: 'unmigrated',
200
+ part: 'table',
201
+ table,
202
+ name: REPLICA_IDENTITY_FULL,
203
+ detail: REPLICA_IDENTITY_UNRECORDED,
204
+ });
205
+ }
190
206
  out.push(
191
207
  ...compareList({
192
208
  part: 'column',