@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
@@ -141,6 +141,11 @@ export const CLI_OWNED_ERROR_CODES = [
141
141
  'X_GUARD_INVALID',
142
142
  'X_GUARD_FAILED',
143
143
  'X_GUARD_FINDING_INVALID',
144
+ // The browser-transport rule, on `boundaries` in every app (`app-transport.ts`). Both codes
145
+ // shipped as this repository's own script findings; the rule moved into the gate, so the CLI
146
+ // emits them and therefore owns them — the code strings and their meaning are unchanged.
147
+ 'X_BROWSER_TRANSPORT_BYPASS',
148
+ 'X_BROWSER_SERVER_BARREL',
144
149
  // The CLI's own declarations, held to each other. A flag the parser accepts and no code reads
145
150
  // is a promise in `x help` with nothing behind it — `x deploy --critical` said "forces clients
146
151
  // to reload" and reached no reader outside the plan JSON it was written into.
@@ -205,11 +210,29 @@ export const CLI_OWNED_ERROR_CODES = [
205
210
  'X_SITEMAP_EXTRA_INVALID',
206
211
  'X_DEV_RESTART_REQUIRED',
207
212
  'X_DEV_ROOT_GONE',
213
+ 'X_SEAL_RESEAL_PENDING',
214
+ 'X_VERIFY_STEP_TIMEOUT',
215
+ 'X_COVERAGE_BELOW_FLOOR',
216
+ 'X_COVERAGE_FLOOR_UNSTATED',
217
+ 'X_COVERAGE_FLOOR_STALE',
218
+ 'X_DB_HANDLE_UNREGISTERED',
219
+ 'X_ROLE_LOAD_INCOMPLETE',
220
+ 'X_ADMIN_RESOURCE_UNWIRED',
221
+ 'X_IMAGE_NOT_PREBUILT',
222
+ 'X_ADMIN_UNSCANNED',
223
+ 'X_FRAMEWORK_TABLE_ORPHANED',
224
+ 'X_BUILD_OUT_UNSAFE',
225
+ 'X_FRAMEWORK_SCHEMA_UNAPPLIED',
226
+ 'X_APP_URL_PORT_MISMATCH',
227
+ 'X_DEV_HOST_REFUSED',
228
+ 'X_TRUSTED_PROXY_HOPS_INVALID',
229
+ 'X_PERMISSION_BORROWED',
230
+ 'X_ERROR_CODE_UNTHROWN_STALE',
208
231
  ] as const;
209
232
 
210
233
  /**
211
- * `X_NOT_IMPLEMENTED` is `@ultimat3/core`'s — `CliNotImplementedError` and every planned command
212
- * throw it, and none of them may declare a title for it. The CLI is the process that imports every
234
+ * `X_NOT_IMPLEMENTED` is `@ultimat3/core`'s — every planned command throws it through core's
235
+ * own `NotImplementedError`, and none of them may declare a title for it. The CLI is the process that imports every
213
236
  * package (`error-catalog.ts`), so a title declared twice here is the one that would win by load
214
237
  * order rather than by ownership.
215
238
  *
@@ -270,7 +293,7 @@ export const CLI_ERROR_TITLES: Readonly<Record<CliOwnedErrorCode, string>> = {
270
293
  X_ERROR_CODE_UNREGISTERED: 'the error reference documents a code no package registers',
271
294
  X_ERROR_CODE_UNRESOLVED: 'an error code is written as a name this repository cannot resolve',
272
295
  X_ERROR_CODE_UNTHROWN:
273
- 'a registered error code is constructed by nothing and its reference row does not say so',
296
+ 'a registered error code is constructed by nothing and UNTHROWN_CODES in packages/cli/src/unthrown-codes.ts does not list it',
274
297
  X_FRAMEWORK_SCHEMA_FAILED: 'a framework table could not be created at boot',
275
298
  X_STORAGE_UNWRITABLE: 'the storage disk this process needs cannot be written to',
276
299
  X_STORAGE_SECRET_DEV: 'upload grants would be signed with the shipped development key',
@@ -314,6 +337,10 @@ export const CLI_ERROR_TITLES: Readonly<Record<CliOwnedErrorCode, string>> = {
314
337
  X_GUARD_INVALID: 'a file in guards/ exports no usable guard',
315
338
  X_GUARD_FAILED: 'an app guard threw instead of returning findings',
316
339
  X_GUARD_FINDING_INVALID: "an app guard's finding breaks the error contract",
340
+ X_BROWSER_TRANSPORT_BYPASS:
341
+ 'browser-reachable code opens its own connection instead of using the one seam',
342
+ X_BROWSER_SERVER_BARREL:
343
+ "browser-reachable code imports a package's server barrel where the package publishes a browser entry",
317
344
  X_CLI_FLAG_UNREAD: 'a command declares a flag no code reads',
318
345
  X_SECRETS_EDITOR_MISSING: 'no $EDITOR to open the decrypted secrets in',
319
346
  X_SECRETS_EDIT_FAILED: 'the editor exited non-zero, so nothing was resealed',
@@ -360,6 +387,31 @@ export const CLI_ERROR_TITLES: Readonly<Record<CliOwnedErrorCode, string>> = {
360
387
  X_DEV_RESTART_REQUIRED:
361
388
  'a save reached a module that defines a primitive; only a new process serves it',
362
389
  X_DEV_ROOT_GONE: 'the app root x dev serves was deleted or moved',
390
+ X_SEAL_RESEAL_PENDING: 'sealed columns still hold values under a retired master key',
391
+ X_VERIFY_STEP_TIMEOUT: 'a gate step ran past its deadline and was stopped',
392
+ X_COVERAGE_BELOW_FLOOR: 'the unit suite covers less of the app than x.verify.json states',
393
+ X_COVERAGE_FLOOR_UNSTATED:
394
+ 'x.verify.json states no coverage floor, or one under 95 with no reason',
395
+ X_COVERAGE_FLOOR_STALE: 'the app now covers more than the floor in x.verify.json states',
396
+ X_DB_HANDLE_UNREGISTERED: "an entity x g wrote is not in the app's typed database handle",
397
+ X_ROLE_LOAD_INCOMPLETE: 'a worker or scheduler did not register a job or task the manifest names',
398
+ X_ADMIN_RESOURCE_UNWIRED: "an admin override x g wrote is not in the app's defineAdmin() call",
399
+ X_IMAGE_NOT_PREBUILT:
400
+ 'a container role built at boot what the image build should have: island chunks or stylesheets',
401
+ X_ADMIN_UNSCANNED:
402
+ 'a defineAdmin() declaration sits outside the app scan, so the admin is never mounted',
403
+ X_FRAMEWORK_TABLE_ORPHANED:
404
+ 'a framework table no release reads any more is still in the database',
405
+ X_BUILD_OUT_UNSAFE: 'the static export directory is not one a build may empty',
406
+ X_FRAMEWORK_SCHEMA_UNAPPLIED:
407
+ 'a serving role booted over a database whose framework schema this build never applied',
408
+ X_APP_URL_PORT_MISMATCH: 'APP_URL names a loopback port x dev does not serve',
409
+ X_DEV_HOST_REFUSED: 'a /_x request named a host that is not this machine',
410
+ X_TRUSTED_PROXY_HOPS_INVALID:
411
+ 'TRUSTED_PROXY_HOPS is not a whole number of proxies the boot accepts',
412
+ X_PERMISSION_BORROWED: 'an app rule requires a permission only an imported package declared',
413
+ X_ERROR_CODE_UNTHROWN_STALE:
414
+ 'a code UNTHROWN_CODES lists as thrown by nothing is thrown again — delete it from UNTHROWN_CODES in packages/cli/src/unthrown-codes.ts',
363
415
  };
364
416
 
365
417
  // One unconditional call, so a second package claiming one of the CLI's codes throws
@@ -1,7 +1,8 @@
1
1
  // A registered code nothing constructs. The registry and the reference keep a shipped code alive
2
2
  // forever — which is right — and that is exactly why a code can outlive its last thrower with no
3
3
  // gate noticing: `X_RPC_FAILED` sat registered and documented as live after the transport change
4
- // took its only throw site away. The reference row has to SAY so, or this reports it.
4
+ // took its only throw site away. It has to be LISTED as unthrown (`UNTHROWN_CODES`), or this
5
+ // reports it.
5
6
 
6
7
  import { join } from 'node:path';
7
8
  import { ERROR_DOCS_URL, maskLiterals, stripComments } from '@ultimat3/core';
@@ -9,6 +10,7 @@ import { RESERVED_HEADING } from './error-contract';
9
10
  import type { Finding } from './output';
10
11
  import { eachSourceFile, isGenerated, isTest } from './source-files';
11
12
  import { isCodeRegistry } from './ts-scan';
13
+ import { UNTHROWN_CODES, UNTHROWN_CODES_FILE } from './unthrown-codes';
12
14
 
13
15
  const CODE_LITERAL = /(['"`])(X_[A-Z0-9_]+)\1/g;
14
16
  const TITLE_KEY = /^[\t ]*(X_[A-Z0-9_]+)\s*:/gm;
@@ -18,9 +20,6 @@ const LIST_LINE = /^[\t ]*(['"`])X_[A-Z0-9_]+\1\s*,?\s*$/;
18
20
  const TABLE_ENTRY = /\b([a-z][A-Za-z0-9]*)\s*:\s*(['"`])(X_[A-Z0-9_]+)\2/g;
19
21
  const MEMBER_READ = /\.([A-Za-z_$][\w$]*)/g;
20
22
 
21
- /** Phrases a reference row uses to say, in words, that nothing throws the code any more. */
22
- const DECLARED_UNTHROWN = /thrown by nothing|not thrown/i;
23
-
24
23
  export interface CodeUse {
25
24
  /** Every code a package registry names. */
26
25
  readonly registered: ReadonlySet<string>;
@@ -78,6 +77,8 @@ export async function collectCodeUse(root: string): Promise<CodeUse> {
78
77
  const members = new Set<string>();
79
78
  for await (const source of eachSourceFile(root)) {
80
79
  if (!/^packages\/[^/]+\/src\//.test(source) || isTest(source) || isGenerated(source)) continue;
80
+ // The list names every code it waives, and naming one is not throwing it.
81
+ if (source === UNTHROWN_CODES_FILE) continue;
81
82
  const use = codeUseOf(await Bun.file(join(root, source)).text());
82
83
  for (const code of use.registered) registered.add(code);
83
84
  for (const code of use.used) used.add(code);
@@ -88,28 +89,36 @@ export async function collectCodeUse(root: string): Promise<CodeUse> {
88
89
  return { registered, used };
89
90
  }
90
91
 
91
- /** Codes whose reference row says nothing throws them, or that sit under the reserved heading. */
92
+ /** Codes that sit under the reserved heading — declared unthrown by WHERE they are, not by words. */
92
93
  export function declaredUnthrown(markdown: string): ReadonlySet<string> {
93
94
  const out = new Set<string>();
94
95
  let reserved = false;
95
96
  for (const line of markdown.split('\n')) {
96
97
  if (line.trim() === RESERVED_HEADING) reserved = true;
97
98
  const row = /^\|\s*`(X_[A-Z0-9_]+)`\s*\|/.exec(line);
98
- if (row !== null && (reserved || DECLARED_UNTHROWN.test(line))) out.add(row[1] ?? '');
99
+ if (row !== null && reserved) out.add(row[1] ?? '');
99
100
  }
100
101
  return out;
101
102
  }
102
103
 
103
104
  const unthrownFinding = (code: string, page: string): Finding => ({
104
105
  code: 'X_ERROR_CODE_UNTHROWN',
105
- cause: `${code} is registered and ${page} presents it as live, but no shipped source constructs it — a reader matching on it waits for an error that cannot arrive`,
106
+ cause: `${code} is registered and ${page} presents it as live, but no shipped source constructs it — a reader matching on it waits for an error that cannot arrive. Add it to UNTHROWN_CODES in ${UNTHROWN_CODES_FILE} and say so in its row, or throw it again where it belongs`,
106
107
  // Never "delete the registration": a shipped code is stable forever, and an old log line must
107
108
  // still explain. The row is what has to change.
108
- fix: `write "registered, thrown by nothing since <version>" into ${code}'s row in ${page}, naming the code that replaced it — or throw it again where it belongs`,
109
+ fix: 'x errors explain X_ERROR_CODE_UNTHROWN --json',
109
110
  docs: ERROR_DOCS_URL,
110
111
  at: page,
111
112
  });
112
113
 
114
+ const staleFinding = (code: string): Finding => ({
115
+ code: 'X_ERROR_CODE_UNTHROWN_STALE',
116
+ cause: `${code} is listed in UNTHROWN_CODES and shipped source constructs it again — the listing would excuse a code that is live, so delete it from UNTHROWN_CODES in ${UNTHROWN_CODES_FILE}`,
117
+ fix: 'x errors explain X_ERROR_CODE_UNTHROWN_STALE --json',
118
+ docs: ERROR_DOCS_URL,
119
+ at: UNTHROWN_CODES_FILE,
120
+ });
121
+
113
122
  /**
114
123
  * Registered, used by nothing, and not declared unthrown on the reference. A host check — the page
115
124
  * is the host repo's to name, and only a monorepo's walk sees every package's source; in a
@@ -118,13 +127,22 @@ const unthrownFinding = (code: string, page: string): Finding => ({
118
127
  export async function checkErrorCodesThrown(
119
128
  root: string,
120
129
  page: string,
130
+ listed: ReadonlySet<string> = UNTHROWN_CODES,
121
131
  ): Promise<readonly Finding[]> {
122
132
  const reference = Bun.file(join(root, page));
123
133
  if (!(await reference.exists())) return [];
124
- const exempt = declaredUnthrown(await reference.text());
134
+ const reserved = declaredUnthrown(await reference.text());
125
135
  const { registered, used } = await collectCodeUse(root);
126
- return [...registered]
127
- .filter((code) => !used.has(code) && !exempt.has(code))
136
+ const unthrown = [...registered]
137
+ .filter((code) => !used.has(code) && !listed.has(code) && !reserved.has(code))
128
138
  .sort()
129
139
  .map((code) => unthrownFinding(code, page));
140
+ // Only the THROWN-again direction: a listed name no registry holds waives nothing (the code it
141
+ // was meant to name is still reported), and a host repo's fixture registering none of this
142
+ // repo's codes would otherwise read every listing as stale.
143
+ const stale = [...listed]
144
+ .filter((code) => used.has(code))
145
+ .sort()
146
+ .map((code) => staleFinding(code));
147
+ return [...unthrown, ...stale];
130
148
  }
package/src/errors.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  // that resolves it — the codes themselves, their titles and their registration are `./error-codes`,
3
3
  // so a package importing a class does not pull the table and vice versa.
4
4
  import { UltimateError } from '@ultimat3/core';
5
+ import type { Finding } from './output';
5
6
  import { quoteArg } from './shell-quote';
6
7
 
7
8
  /** An unknown command or subcommand. Carries a suggestion so the retry is one keystroke away. */
@@ -301,14 +302,59 @@ export class FixTargetUnknownError extends UltimateError {
301
302
  }
302
303
  }
303
304
 
304
- // The build and bundle refusals live in `build-errors.ts` (split at the 500-line ceiling); re-exported
305
- // here so every existing `from './errors'` import keeps resolving.
306
- export type { FrameworkScriptKind } from './build-errors';
307
- export {
308
- BuildEntryMissingError,
309
- FrameworkScriptBuildFailedError,
310
- IslandBuildFailedError,
311
- } from './build-errors';
305
+ /**
306
+ * A client entry would not compile. `X_BUILD_FAILED`, not a code of its own: an island is a bundle
307
+ * entry point like any other, and the target's own logs are what says which line. The fix builds
308
+ * exactly that one file, so the next message an author reads is the compiler's and not the CLI's.
309
+ */
310
+ export class IslandBuildFailedError extends UltimateError {
311
+ constructor(input: { file: string; logs: string }) {
312
+ super({
313
+ code: 'X_BUILD_FAILED',
314
+ cause: `${input.file} is an island entry point and would not bundle: ${input.logs}`,
315
+ fix: `bun build --target browser ${input.file}`,
316
+ });
317
+ }
318
+ }
319
+
320
+ /** Which framework script a page ships: the sync worker, the page boot, or the client router. */
321
+ export type FrameworkScriptKind = 'sync worker' | 'page boot' | 'client router';
322
+
323
+ /**
324
+ * One of the page's framework scripts would not bundle. Same code as an island: "a browser entry
325
+ * the framework builds did not build" is one condition, and the entry here is framework code, not
326
+ * the app's — the cause names WHICH script, so the reader knows what the page is now missing.
327
+ */
328
+ export class FrameworkScriptBuildFailedError extends UltimateError {
329
+ constructor(input: { what: FrameworkScriptKind; entry: string; logs: string }) {
330
+ super({
331
+ code: 'X_BUILD_FAILED',
332
+ cause: `the ${input.what} (${input.entry}) would not bundle for the browser: ${input.logs}`,
333
+ fix: `bun build --target browser --format iife ${quoteArg(input.entry)}`,
334
+ });
335
+ }
336
+ }
337
+
338
+ /**
339
+ * A static build over an app whose modules did not all import. The route registry is filled BY the
340
+ * import, so a page whose module threw is not skipped but absent — and an export without it, with
341
+ * `x build` green, is a deploy that deletes a page. The `manifest` step owns load findings
342
+ * (`load-findings.ts`), so the fix is the command that lists every one with its own fix.
343
+ */
344
+ export class PrerenderLoadFailedError extends UltimateError {
345
+ constructor(findings: readonly Finding[]) {
346
+ const listed = findings.map((finding) =>
347
+ finding.at === undefined
348
+ ? `${finding.code} ${finding.cause}`
349
+ : `${finding.at}: ${finding.code} ${finding.cause}`,
350
+ );
351
+ super({
352
+ code: 'X_BUILD_FAILED',
353
+ cause: `the static export renders the routes the app's modules register, and ${findings.length} module load finding(s) left it short: ${listed.join('; ')}`,
354
+ fix: 'x verify --only manifest --json',
355
+ });
356
+ }
357
+ }
312
358
 
313
359
  /**
314
360
  * `ROLE` selects what a container is. One image runs every role, so a typo is a process that would
@@ -363,36 +409,6 @@ export class PortInvalidError extends UltimateError {
363
409
  }
364
410
  }
365
411
 
366
- /**
367
- * `x env` was run in an app whose `app.config.ts` exports no `envSchema`. Not a silent success:
368
- * writing a `.env.example` with no variables in it, or reporting "0 declared variables, all
369
- * present", both read as a working environment declaration to whoever runs the command next.
370
- *
371
- * `X_CONFIG_INVALID` is core's code for "a configuration this process cannot boot on — env or
372
- * `app.config.ts`", which is exactly this; the CLI names it in `CLI_BORROWED_ERROR_CODES` rather
373
- * than minting a synonym.
374
- */
375
- export class EnvSchemaMissingError extends UltimateError {
376
- constructor(input: { subcommand: string }) {
377
- super({
378
- code: 'X_CONFIG_INVALID',
379
- cause: `x env ${input.subcommand} needs the env declaration, and app.config.ts exports no "envSchema"`,
380
- fix: "add to app.config.ts: export const envSchema = { DATABASE_URL: { type: 'url', description: 'Postgres connection URL' } } satisfies EnvSchema; export const env = defineEnv(envSchema);",
381
- });
382
- }
383
- }
384
-
385
- /** An interface-complete command path whose remote/native half is not written yet. */
386
- export class CliNotImplementedError extends UltimateError {
387
- constructor(input: { feature: string; fix: string }) {
388
- super({
389
- code: 'X_NOT_IMPLEMENTED',
390
- cause: `${input.feature} is not implemented in this build`,
391
- fix: input.fix,
392
- });
393
- }
394
- }
395
-
396
412
  /**
397
413
  * The process could not obtain a storage disk to write to.
398
414
  *
@@ -432,48 +448,3 @@ export class LocalDiskUnsafeError extends UltimateError {
432
448
  });
433
449
  }
434
450
  }
435
-
436
- /**
437
- * `x secrets edit` decrypts into a buffer and hands it to `$EDITOR`. There is no fallback editor:
438
- * guessing one and opening a decrypted file in it is the last place a surprise belongs.
439
- */
440
- export class SecretsEditorMissingError extends UltimateError {
441
- constructor(input: { vars: readonly string[] }) {
442
- super({
443
- code: 'X_SECRETS_EDITOR_MISSING',
444
- cause: `x secrets edit opens the decrypted secrets in an editor and none of ${input.vars.join(', ')} is set`,
445
- fix: 'EDITOR=nano x secrets edit',
446
- });
447
- }
448
- }
449
-
450
- /**
451
- * The editor exited non-zero — a crash, or a deliberate abort. The buffer is discarded either way
452
- * and the committed file is left exactly as it was: resealing a buffer whose editor failed would
453
- * commit whatever half-written state the crash left behind.
454
- */
455
- export class SecretsEditFailedError extends UltimateError {
456
- constructor(input: { editor: string; code: number }) {
457
- super({
458
- code: 'X_SECRETS_EDIT_FAILED',
459
- cause: `"${input.editor}" exited ${input.code}, so the decrypted buffer was discarded and the committed secrets file was not rewritten`,
460
- fix: 'x secrets edit',
461
- });
462
- }
463
- }
464
-
465
- /**
466
- * `x secrets init` would overwrite a file that already exists. `X_GENERATE_CONFLICT` is this
467
- * package's own code for exactly that, and a second name for "a generator would clobber something"
468
- * is the duplication the code registry exists to prevent. Losing a master key is unrecoverable —
469
- * the committed file it opens is then ciphertext nobody can read again.
470
- */
471
- export class SecretsExistsError extends UltimateError {
472
- constructor(input: { path: string; fix: string }) {
473
- super({
474
- code: 'X_GENERATE_CONFLICT',
475
- cause: `${input.path} already exists, and x secrets init would replace it`,
476
- fix: input.fix,
477
- });
478
- }
479
- }
@@ -0,0 +1,78 @@
1
+ // The two ways a boot meets the framework schema (plan 101, s1-con #5): `applyLockedSchema` runs the
2
+ // DDL in ONE transaction behind the migration advisory lock with a bounded `lock_timeout`, then
3
+ // stamps the build; `verifySchema` runs no DDL and refuses a database this build was never applied
4
+ // to. Which boot takes which is `runtime-queue.ts`'s `SchemaMode`.
5
+
6
+ import { frameworkVersion, logger } from '@ultimat3/core';
7
+ import type { DbClient } from '@ultimat3/db';
8
+ import {
9
+ literal,
10
+ MIGRATION_LOCK_KEY,
11
+ MIGRATION_LOCK_WAIT_MS,
12
+ poolProfileFor,
13
+ raw,
14
+ withTransaction,
15
+ } from '@ultimat3/db';
16
+ import { applyFrameworkSchema } from './framework-schema';
17
+ import {
18
+ FrameworkSchemaUnappliedError,
19
+ frameworkSchemaHash,
20
+ nextStamp,
21
+ parseStamp,
22
+ STAMP_RELATION,
23
+ type StampEntry,
24
+ stampVerdict,
25
+ } from './framework-schema-stamp';
26
+
27
+ const thisBuild = (): StampEntry => ({
28
+ version: frameworkVersion(),
29
+ schema: frameworkSchemaHash(),
30
+ });
31
+
32
+ /** The stamp as the catalog holds it; `null` when the relation or its comment is absent. */
33
+ async function readStamp(client: DbClient): Promise<string | null> {
34
+ const rows = await client.query<{ readonly stamp: string | null }>(
35
+ raw(`select obj_description(to_regclass('${STAMP_RELATION}'), 'pg_class') as stamp`),
36
+ );
37
+ return rows[0]?.stamp ?? null;
38
+ }
39
+
40
+ /**
41
+ * The migration lock, not a lock of its own: a second migrator — another `ROLE=migrate` pod, an
42
+ * `x db migrate` — waits behind this one instead of racing its `create table`s into `23505`. A
43
+ * transaction-scoped lock (`pg_advisory_xact_lock`), so no pinned session: COMMIT releases it.
44
+ *
45
+ * Two `lock_timeout`s: the lock wait is the migrator's own (`MIGRATION_LOCK_WAIT_MS`), and every
46
+ * DDL statement after it is the migrate pool's (`poolProfileFor('migrate')`), so an `alter table`
47
+ * behind an open transaction fails in seconds rather than queueing every enqueue behind it.
48
+ */
49
+ export async function applyLockedSchema(client: DbClient): Promise<void> {
50
+ await withTransaction(
51
+ async (tx) => {
52
+ await tx.execute(raw(`set local lock_timeout = ${Math.round(MIGRATION_LOCK_WAIT_MS)}`));
53
+ await tx.execute(raw(`select pg_advisory_xact_lock(${MIGRATION_LOCK_KEY})`));
54
+ const ddlTimeoutMs = Math.round(poolProfileFor('migrate').lockTimeoutMs);
55
+ await tx.execute(raw(`set local lock_timeout = ${ddlTimeoutMs}`));
56
+ await applyFrameworkSchema((statement) => tx.execute(raw(statement)));
57
+ const stamp = nextStamp(await readStamp(tx), thisBuild());
58
+ await tx.execute(raw(`comment on table ${STAMP_RELATION} is ${literal(stamp).text}`));
59
+ },
60
+ { client },
61
+ );
62
+ }
63
+
64
+ /**
65
+ * One catalog read. A build absent from the stamp is refused with the command that applies it; a
66
+ * fleet straddling a major is reported once and served — refusing it would crash-loop the old pods
67
+ * of every rolling deploy across a breaking release.
68
+ */
69
+ export async function verifySchema(client: DbClient): Promise<void> {
70
+ const mine = thisBuild();
71
+ const verdict = stampVerdict(parseStamp(await readStamp(client)), mine);
72
+ if (!verdict.applied) throw new FrameworkSchemaUnappliedError({ mine, newest: verdict.newest });
73
+ if (verdict.skew === undefined) return;
74
+ logger.warn('ultimate framework major skew', {
75
+ cause: `this process runs ${mine.version} and the database's framework schema was last applied by ${verdict.skew.version}: a fleet across a major boundary is mid-rollout or half rolled back`,
76
+ fix: 'kubectl rollout status deployment --selector app.kubernetes.io/component=web',
77
+ });
78
+ }
@@ -0,0 +1,96 @@
1
+ // Which builds have applied the framework schema to this database: a stamp `ROLE=migrate` leaves
2
+ // in `x_jobs`' table comment, and the verdict a serving role reads from it instead of running DDL
3
+ // itself (plan 101, s1-con #5). Pure: the statements live in `framework-schema-apply.ts`.
4
+
5
+ import { UltimateError } from '@ultimat3/core';
6
+ import { FRAMEWORK_SCHEMA } from './framework-schema';
7
+
8
+ /**
9
+ * The relation that carries the stamp. A comment, not a table: a new framework table moves every
10
+ * app's committed schema dump (`packages/db/schema/framework/`), and a comment is in none of them.
11
+ */
12
+ export const STAMP_RELATION = 'x_jobs';
13
+
14
+ /** The first line of a stamp — what tells it apart from a comment someone else wrote. */
15
+ const STAMP_HEADER = 'ultimate-framework-schema';
16
+
17
+ /** Enough for a rolling deploy and its rollback, small enough to stay one readable comment. */
18
+ const STAMP_KEEP = 16;
19
+
20
+ export interface StampEntry {
21
+ readonly version: string;
22
+ readonly schema: string;
23
+ }
24
+
25
+ /** This build's DDL, as one short hash: two builds with the same DDL are the same schema. */
26
+ export function frameworkSchemaHash(): string {
27
+ const hasher = new Bun.CryptoHasher('sha256');
28
+ for (const entry of FRAMEWORK_SCHEMA) for (const ddl of entry.ddl) hasher.update(ddl);
29
+ return hasher.digest('hex').slice(0, 16);
30
+ }
31
+
32
+ /** Every build a stamp records, newest first. Anything that is not a stamp records none. */
33
+ export function parseStamp(comment: string | null | undefined): readonly StampEntry[] {
34
+ if (typeof comment !== 'string') return [];
35
+ const [header, ...lines] = comment.split('\n');
36
+ if (header !== STAMP_HEADER) return [];
37
+ return lines.flatMap((line) => {
38
+ const [version, schema] = line.trim().split(' ');
39
+ return version !== undefined &&
40
+ /^[0-9A-Za-z.+-]{1,64}$/.test(version) &&
41
+ schema !== undefined &&
42
+ /^[0-9a-f]{16}$/.test(schema)
43
+ ? [{ version, schema }]
44
+ : [];
45
+ });
46
+ }
47
+
48
+ /** The stamp after `mine` applied: it first, every other build kept, the oldest dropped. */
49
+ export function nextStamp(previous: string | null | undefined, mine: StampEntry): string {
50
+ const kept = parseStamp(previous).filter((entry) => entry.schema !== mine.schema);
51
+ const entries = [mine, ...kept].slice(0, STAMP_KEEP);
52
+ return [STAMP_HEADER, ...entries.map((entry) => `${entry.version} ${entry.schema}`)].join('\n');
53
+ }
54
+
55
+ const majorOf = (version: string): string => version.split('.')[0] ?? version;
56
+
57
+ export type StampVerdict =
58
+ | { readonly applied: true; readonly skew?: StampEntry }
59
+ | { readonly applied: false; readonly newest?: StampEntry };
60
+
61
+ /**
62
+ * Applied when this build's hash is anywhere in the stamp: an old pod restarting mid-rollout finds
63
+ * its own build there, behind the new one. `skew` is the newest build when its MAJOR differs from
64
+ * this process's — a fleet straddling a breaking release, which a boot reports and never refuses.
65
+ */
66
+ export function stampVerdict(entries: readonly StampEntry[], mine: StampEntry): StampVerdict {
67
+ const newest = entries[0];
68
+ if (!entries.some((entry) => entry.schema === mine.schema)) {
69
+ return newest === undefined ? { applied: false } : { applied: false, newest };
70
+ }
71
+ return newest !== undefined && majorOf(newest.version) !== majorOf(mine.version)
72
+ ? { applied: true, skew: newest }
73
+ : { applied: true };
74
+ }
75
+
76
+ /** A serving role over a database its build's framework tables were never applied to. */
77
+ export class FrameworkSchemaUnappliedError extends UltimateError {
78
+ constructor(input: { readonly mine: StampEntry; readonly newest: StampEntry | undefined }) {
79
+ super({
80
+ code: 'X_FRAMEWORK_SCHEMA_UNAPPLIED',
81
+ cause:
82
+ `this build (${input.mine.version}, framework schema ${input.mine.schema}) has not been applied ` +
83
+ `to this database — ${
84
+ input.newest === undefined
85
+ ? `${STAMP_RELATION} carries no stamp, so no ROLE=migrate of this release ran`
86
+ : `the newest applied build is ${input.newest.version} (${input.newest.schema})`
87
+ }. A serving role verifies and never runs DDL: the migrate step must run first`,
88
+ fix: 'x db migrate',
89
+ meta: {
90
+ version: input.mine.version,
91
+ schema: input.mine.schema,
92
+ ...(input.newest === undefined ? {} : { newest: { ...input.newest } }),
93
+ },
94
+ });
95
+ }
96
+ }
@@ -7,10 +7,15 @@
7
7
  // the one code path that needs it, and it surfaces as a Postgres `42P01` from inside a worker.
8
8
 
9
9
  import { SQL_AUDIT_TABLE, SQL_IDEMPOTENCY_TABLE } from '@ultimat3/action';
10
+ import { ADMIN_AUDIT_TABLE, SQL_ADMIN_AUDIT_TABLE } from '@ultimat3/admin/schema';
10
11
  import { AUTH_TABLE_NAMES, AUTH_TABLES, SQL_AUTH_LIMIT_TABLES } from '@ultimat3/auth';
11
12
  import { SQL_RATE_LIMIT_TABLE } from '@ultimat3/http';
12
13
  import { SQL_JOBS_TABLE } from '@ultimat3/jobs';
13
- import { SQL_NOTIFY_DELIVERIES_TABLE, SQL_NOTIFY_INBOX_TABLE } from '@ultimat3/notify';
14
+ import {
15
+ SQL_NOTIFY_DELIVERIES_TABLE,
16
+ SQL_NOTIFY_DIGESTS_TABLE,
17
+ SQL_NOTIFY_INBOX_TABLE,
18
+ } from '@ultimat3/notify';
14
19
  import { FrameworkSchemaFailedError } from './schema-errors';
15
20
 
16
21
  export interface FrameworkSchema {
@@ -49,6 +54,9 @@ export const FRAMEWORK_SCHEMA: readonly FrameworkSchema[] = Object.freeze([
49
54
  'x_scheduler_leader',
50
55
  'x_job_leases',
51
56
  'x_job_events',
57
+ 'x_job_pauses',
58
+ 'x_job_workers',
59
+ 'x_job_counters',
52
60
  ]),
53
61
  ddl: Object.freeze([SQL_JOBS_TABLE]),
54
62
  }),
@@ -72,7 +80,7 @@ export const FRAMEWORK_SCHEMA: readonly FrameworkSchema[] = Object.freeze([
72
80
  }),
73
81
  Object.freeze({
74
82
  pkg: '@ultimat3/auth',
75
- tables: Object.freeze(['x_auth_failures', 'x_auth_lockouts']),
83
+ tables: Object.freeze(['x_auth_lockouts']),
76
84
  ddl: Object.freeze([SQL_AUTH_LIMIT_TABLES]),
77
85
  }),
78
86
  /**
@@ -109,6 +117,24 @@ export const FRAMEWORK_SCHEMA: readonly FrameworkSchema[] = Object.freeze([
109
117
  tables: Object.freeze(['x_notify_inbox']),
110
118
  ddl: Object.freeze([SQL_NOTIFY_INBOX_TABLE]),
111
119
  }),
120
+ // `createPgDigestStore`'s windows. Installed whether or not the app picks that store, for the
121
+ // reason every notify row is: `setNotifyStores` is the app's boot line and runs after this one.
122
+ Object.freeze({
123
+ pkg: '@ultimat3/notify',
124
+ tables: Object.freeze(['x_notify_digests']),
125
+ ddl: Object.freeze([SQL_NOTIFY_DIGESTS_TABLE]),
126
+ }),
127
+ /**
128
+ * The admin's durable audit trail — what `postgresAuditLog()` writes and every detail page's
129
+ * history card reads. Without this row an app that chose the durable log met `42P01` on the
130
+ * first audited request. Imported through `@ultimat3/admin/schema`, a leaf module: the DDL is
131
+ * on every role's boot path, and not one admin screen is (`serve-graph.test.ts`).
132
+ */
133
+ Object.freeze({
134
+ pkg: '@ultimat3/admin',
135
+ tables: Object.freeze([ADMIN_AUDIT_TABLE]),
136
+ ddl: Object.freeze([SQL_ADMIN_AUDIT_TABLE]),
137
+ }),
112
138
  ]);
113
139
 
114
140
  /** Every relation this boot creates, flattened. */