@ultimat3/cli 21.0.0 → 22.1.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 (236) hide show
  1. package/CLAUDE.md +84 -1650
  2. package/README.md +40 -2
  3. package/package.json +30 -30
  4. package/src/api-registration.ts +124 -0
  5. package/src/app-artifacts.ts +39 -0
  6. package/src/app-boundaries.ts +5 -10
  7. package/src/app-load.ts +37 -5
  8. package/src/app-openapi.ts +27 -0
  9. package/src/app-permissions.ts +0 -0
  10. package/src/app-root.ts +1 -1
  11. package/src/async-pages.ts +32 -0
  12. package/src/bin.ts +7 -1
  13. package/src/boundary-findings.ts +36 -0
  14. package/src/browser-launcher-fake-html.ts +80 -0
  15. package/src/browser-launcher-fake.ts +165 -0
  16. package/src/browser-launcher-port.ts +159 -0
  17. package/src/browser-launcher.ts +26 -133
  18. package/src/budgets.ts +11 -1
  19. package/src/cdp-shot-a11y.ts +77 -0
  20. package/src/cdp-shot-clock.ts +14 -0
  21. package/src/cdp-shot-driver.ts +150 -0
  22. package/src/cdp-shot-element.ts +147 -0
  23. package/src/cdp-shot-errors.ts +62 -0
  24. package/src/cdp-shot-keys.ts +152 -0
  25. package/src/cdp-shot-page.ts +230 -0
  26. package/src/cdp-shot-watch.ts +241 -0
  27. package/src/cmd-affected-spec.ts +24 -0
  28. package/src/cmd-affected.ts +3 -19
  29. package/src/cmd-build-spec.ts +16 -0
  30. package/src/cmd-build.ts +46 -20
  31. package/src/cmd-ci-spec.ts +24 -0
  32. package/src/cmd-ci.ts +17 -21
  33. package/src/cmd-db-branch.ts +2 -2
  34. package/src/cmd-db-spec.ts +99 -0
  35. package/src/cmd-db.ts +15 -94
  36. package/src/cmd-deploy-helm.ts +136 -0
  37. package/src/cmd-deploy-spec.ts +40 -0
  38. package/src/cmd-deploy.ts +111 -43
  39. package/src/cmd-dev-spec.ts +25 -0
  40. package/src/cmd-dev.ts +55 -36
  41. package/src/cmd-docs-spec.ts +16 -0
  42. package/src/cmd-docs.ts +2 -11
  43. package/src/cmd-doctor-spec.ts +21 -0
  44. package/src/cmd-doctor.ts +4 -18
  45. package/src/cmd-env-spec.ts +18 -0
  46. package/src/cmd-env.ts +4 -13
  47. package/src/cmd-errors-spec.ts +23 -0
  48. package/src/cmd-errors.ts +3 -17
  49. package/src/cmd-fix-spec.ts +18 -0
  50. package/src/cmd-fix.ts +4 -13
  51. package/src/cmd-generate-spec.ts +31 -0
  52. package/src/cmd-generate.ts +46 -42
  53. package/src/cmd-i18n-spec.ts +16 -0
  54. package/src/cmd-i18n.ts +7 -14
  55. package/src/cmd-jobs-spec.ts +56 -0
  56. package/src/cmd-jobs.ts +3 -50
  57. package/src/cmd-manifest-spec.ts +15 -0
  58. package/src/cmd-manifest.ts +19 -20
  59. package/src/cmd-mcp-spec.ts +21 -0
  60. package/src/cmd-mcp.ts +2 -15
  61. package/src/cmd-new-spec.ts +33 -0
  62. package/src/cmd-new.ts +38 -33
  63. package/src/cmd-policy-spec.ts +13 -0
  64. package/src/cmd-policy.ts +2 -9
  65. package/src/cmd-pr-spec.ts +39 -0
  66. package/src/cmd-pr.ts +5 -65
  67. package/src/cmd-registries-spec.ts +32 -0
  68. package/src/cmd-registries.ts +4 -25
  69. package/src/cmd-routes-spec.ts +12 -0
  70. package/src/cmd-routes.ts +2 -7
  71. package/src/cmd-secrets-spec.ts +19 -0
  72. package/src/cmd-secrets.ts +22 -34
  73. package/src/cmd-shot-island.ts +12 -13
  74. package/src/cmd-shot-spec.ts +53 -0
  75. package/src/cmd-shot.ts +11 -60
  76. package/src/cmd-tasks-spec.ts +21 -0
  77. package/src/cmd-tasks.ts +2 -16
  78. package/src/cmd-test-spec.ts +54 -0
  79. package/src/cmd-test.ts +4 -49
  80. package/src/cmd-verify-spec.ts +28 -0
  81. package/src/cmd-verify.ts +3 -23
  82. package/src/db-seed.ts +4 -2
  83. package/src/dev-dashboard.ts +4 -4
  84. package/src/dev-lock.ts +18 -2
  85. package/src/dev-port.ts +21 -0
  86. package/src/dev-route-table.ts +3 -3
  87. package/src/dispatch.ts +12 -5
  88. package/src/document-styles.ts +1 -1
  89. package/src/drift.ts +4 -18
  90. package/src/error-catalog.ts +16 -9
  91. package/src/error-codes.ts +28 -30
  92. package/src/error-pages.ts +19 -7
  93. package/src/errors.ts +14 -0
  94. package/src/favicon.ts +2 -2
  95. package/src/fix-imports.ts +1 -1
  96. package/src/fix-scan.ts +2 -9
  97. package/src/flag-reads.ts +1 -2
  98. package/src/foreign-text.ts +36 -0
  99. package/src/framework-schema.ts +5 -6
  100. package/src/generate-feature.ts +42 -0
  101. package/src/generate-files.ts +7 -3
  102. package/src/generate-grants.ts +83 -0
  103. package/src/generate-kinds.ts +56 -4
  104. package/src/i18n-index.ts +59 -10
  105. package/src/icon-assets.ts +1 -1
  106. package/src/image-prepare.ts +14 -0
  107. package/src/import-scan.ts +63 -0
  108. package/src/index.ts +67 -348
  109. package/src/invocation-flags.ts +26 -0
  110. package/src/island-bundle.ts +6 -1
  111. package/src/island-capture.ts +6 -6
  112. package/src/island-harness-script.ts +6 -2
  113. package/src/island-realtime.ts +15 -8
  114. package/src/island-shot.ts +5 -0
  115. package/src/island-store.ts +131 -0
  116. package/src/island-verdict.ts +1 -1
  117. package/src/job-registration.ts +42 -0
  118. package/src/jobs-driver.ts +2 -2
  119. package/src/load-findings.ts +51 -0
  120. package/src/mcp-db-target.ts +1 -1
  121. package/src/mcp-errors.ts +28 -27
  122. package/src/mcp-host.ts +27 -12
  123. package/src/mcp-ui-diff.ts +27 -0
  124. package/src/mcp-ui-inspect.ts +4 -4
  125. package/src/mcp-ui-interact.ts +21 -15
  126. package/src/mcp-ui.ts +23 -17
  127. package/src/measure-database.ts +73 -0
  128. package/src/measure-paths.ts +74 -0
  129. package/src/measure-scope.ts +74 -0
  130. package/src/messages.ts +1 -3
  131. package/src/metrics-endpoint.ts +1 -1
  132. package/src/otlp-export.ts +1 -1
  133. package/src/output.ts +6 -0
  134. package/src/permission-grants.ts +86 -0
  135. package/src/prerender-out.ts +25 -0
  136. package/src/prerender.ts +121 -102
  137. package/src/pwa-artifacts.ts +3 -3
  138. package/src/reexport-manifest.ts +2 -1
  139. package/src/registry.ts +80 -56
  140. package/src/role-realtime.ts +36 -0
  141. package/src/{dev-replicator.ts → role-replicator.ts} +1 -1
  142. package/src/{dev-roles-fixture.ts → role-start-fixture.ts} +9 -5
  143. package/src/role-start-types.ts +112 -0
  144. package/src/{dev-roles.ts → role-start.ts} +42 -115
  145. package/src/{dev-sync.ts → role-sync.ts} +22 -8
  146. package/src/root-env.ts +67 -0
  147. package/src/{dev-assets.ts → runtime-assets.ts} +7 -7
  148. package/src/{dev-services.ts → runtime-bindings.ts} +39 -16
  149. package/src/{dev-cache.ts → runtime-cache.ts} +2 -2
  150. package/src/runtime-jobs.ts +87 -0
  151. package/src/{dev-live-feed.ts → runtime-live-feed.ts} +18 -5
  152. package/src/{dev-notify-retention.ts → runtime-notify-retention.ts} +1 -1
  153. package/src/{dev-purge.ts → runtime-purge.ts} +2 -2
  154. package/src/{dev-queue.ts → runtime-queue.ts} +4 -4
  155. package/src/runtime-realtime.ts +55 -0
  156. package/src/{dev-replica.ts → runtime-replica.ts} +2 -2
  157. package/src/{dev-runtime.ts → runtime-services.ts} +41 -17
  158. package/src/{dev-storage.ts → runtime-storage.ts} +4 -4
  159. package/src/scaffold-fixture.ts +28 -6
  160. package/src/scaffold-typecheck.ts +6 -3
  161. package/src/schema-drift.ts +7 -1
  162. package/src/script-csp.ts +5 -2
  163. package/src/secrets-rotation.ts +59 -0
  164. package/src/serve-boot.ts +192 -0
  165. package/src/serve-drain.ts +24 -0
  166. package/src/serve-entry.ts +6 -0
  167. package/src/serve-env.ts +116 -0
  168. package/src/serve-types.ts +55 -0
  169. package/src/serve.ts +44 -347
  170. package/src/shot-server.ts +2 -2
  171. package/src/shot-settle.ts +10 -1
  172. package/src/shot-theme.ts +3 -3
  173. package/src/shot-verdict.ts +16 -7
  174. package/src/signal-shred.ts +27 -0
  175. package/src/solid-loader.ts +26 -2
  176. package/src/static-report.ts +8 -1
  177. package/src/sync-url.ts +21 -1
  178. package/src/templates/action.ts +30 -16
  179. package/src/templates/entity.ts +12 -7
  180. package/src/templates/index.ts +1 -1
  181. package/src/templates/job.ts +10 -7
  182. package/src/templates/policy.ts +20 -2
  183. package/src/templates/resource-create.ts +127 -0
  184. package/src/templates/resource-form-island.ts +49 -12
  185. package/src/templates/resource.ts +10 -4
  186. package/src/templates/scaffold-app.ts +4 -1
  187. package/src/templates/scaffold-auth.ts +3 -1
  188. package/src/templates/scaffold-container-compose.ts +184 -0
  189. package/src/templates/scaffold-container.ts +24 -159
  190. package/src/templates/scaffold-dashboard-example.ts +2 -2
  191. package/src/templates/scaffold-db-package.ts +16 -3
  192. package/src/templates/scaffold-demo-org.ts +41 -0
  193. package/src/templates/scaffold-entries.ts +1 -1
  194. package/src/templates/scaffold-env.ts +6 -0
  195. package/src/templates/scaffold-helm-templates.ts +66 -7
  196. package/src/templates/scaffold-helm.ts +27 -5
  197. package/src/templates/scaffold-i18n.ts +15 -10
  198. package/src/templates/scaffold-repo.ts +13 -10
  199. package/src/templates/scaffold-roles.ts +38 -10
  200. package/src/templates/slice-foundation.ts +1 -1
  201. package/src/templates/wrap.ts +4 -1
  202. package/src/test-passes.ts +2 -1
  203. package/src/test-workers.ts +26 -0
  204. package/src/ts-scan.ts +3 -6
  205. package/src/tsconfig-references.ts +1 -2
  206. package/src/verify-checks.ts +41 -39
  207. package/src/verify-e2e.ts +10 -7
  208. package/src/verify-run.ts +11 -1
  209. package/src/verify-step.ts +3 -3
  210. package/src/verify-tests.ts +1 -26
  211. package/src/verify-typecheck.ts +28 -0
  212. package/src/web-binding.ts +2 -2
  213. package/src/workspace-graph.ts +10 -33
  214. package/src/cdp-browser.ts +0 -94
  215. package/src/cdp-connection.ts +0 -247
  216. package/src/cdp-e2e-page.ts +0 -180
  217. package/src/cdp-e2e-session.ts +0 -199
  218. package/src/cdp-errors.ts +0 -56
  219. package/src/cdp-launch.ts +0 -193
  220. package/src/cdp-offline-script.ts +0 -73
  221. package/src/cdp-pipe.ts +0 -77
  222. package/src/e2e-app.ts +0 -103
  223. package/src/e2e-browser-handle.ts +0 -55
  224. package/src/e2e-dom-fixture.ts +0 -117
  225. package/src/e2e-driver.ts +0 -117
  226. package/src/e2e-errors.ts +0 -117
  227. package/src/e2e-evaluate.ts +0 -156
  228. package/src/e2e-locator.ts +0 -86
  229. package/src/e2e-page.ts +0 -153
  230. package/src/e2e-preload.ts +0 -64
  231. package/src/e2e-probe.ts +0 -23
  232. package/src/e2e-selection.ts +0 -182
  233. package/src/e2e-spawn.ts +0 -169
  234. package/src/measurement-actor.ts +0 -26
  235. /package/src/{dev-hooks.ts → runtime-hooks.ts} +0 -0
  236. /package/src/{dev-render.ts → runtime-render.ts} +0 -0
package/CLAUDE.md CHANGED
@@ -1,1677 +1,111 @@
1
1
  # @ultimat3/cli — boundary
2
2
 
3
- Tier 5. May import tiers 0–4. Nothing imports this except `create-ultimate`.
3
+ Tier 5. May import tiers 0–4. Declared sideways edges: `cli → admin` (`x dev` mounts the `/_x`
4
+ dashboard), `cli → testing` (islands, the e2e step, `x shot`'s `launchChrome`). Nothing imports
5
+ this except `create-ultimate`. The reasoning behind every rule below, verbatim and dated, is
6
+ [`docs/history/cli.md`](../../docs/history/cli.md); where the two disagree this file wins.
4
7
 
5
- | Rule | Detail |
6
- |---|---|
7
- | Entry | `src/bin.ts` (`#!/usr/bin/env bun`) — argv, stdout, exit code only. Before `dispatch`, `local-cli.ts` decides whether THIS process is the app's own CLI: a global `x` inside an app re-executes `node_modules/@ultimat3/cli/src/bin.ts` when that is a different realpath, because a second module instance of `@ultimat3/entity` is an empty registry (a zero-entity manifest, green, 2026-09-05). Same realpath (a workspace symlink), no app, a compiled binary, or `ULTIMATE_KEEP_GLOBAL_CLI` set: no hand-over |
8
- | stdout | `write-line.ts`'s `writeLine` — synchronous fd 1, never `process.stdout.write`, which truncates at the 64KB pipe buffer when `process.exit` follows. Exported, because `create-ultimate`'s entry point needs the same one |
9
- | stderr | `write-line.ts`'s `writeErrorLine` — the same loop on fd 2, for a line that is not the command's answer. A `CommandResult` declaring `stream: 'stderr'` is routed there by `dispatch.ts`'s `sinkFor`, and `x mcp serve --transport stdio` is the one case: its fd 1 carries JSON-RPC frames, so the `✓ mcp stdio serving 13 tools` line rendered after the loop was a malformed frame. Neither renderer carries `stream`, exactly like `hold` |
10
- | Boot logs under `--json` | `dispatch.ts` calls core's `setLogStream('stderr')` when `args.json` is set, once, for all thirty commands. `x db migrate --json` printed the boot logger's `ultimate migrate applied` and then the command's own object, so `json.load` raised on the second document. A server's stdout stays its log stream; this is the CLI process only |
11
- | Test execution | `test-shards.ts`'s `testArgs` — ONE `bun test --parallel=N` per PASS (`test-passes.ts`; a selection mixing `live`/`e2e` files with the rest is two passes, the serial one at `--parallel=1`), never N processes this repo packs itself. It did pack them, largest-first greedy over file SIZE, and the packer was deleted for buying **nothing**: four interleaved runs each on the 1296-file unit corpus gave 58.2/60.0/65.0/66.5s hand-packed against 54.5/57.8/61.7/64.5s under `--parallel=8`, within noise, because both are work-bound — 436.7s of file time is a 54.6s floor on 8 workers and the slowest single file is 20.5s. A greedy pack of 1296 small items lands near-optimal by accident. `--timings` is refused on the same evidence (#342). `--parallel` implies `--isolate`, so the per-file module registry is unchanged, and the per-worker database is too: `@ultimat3/testing`'s `workerId` already read `BUN_TEST_WORKER_ID`, which Bun sets 1..N. `ULTIMATE_TEST_WORKER` is set only for a single-shard `x test --worker I` rerun, which is one process |
12
- | Numeric flags | `flag-number.ts` — one reader for `--port` / `--workers` / `--shard`. A bare `Number.parseInt` accepts `4abc` and answers `NaN`, which turned three checks into ones that cannot fail |
13
- | Shell quoting | `shell-quote.ts`'s `quoteArg` — every value the CLI pastes into a `fix:` or a reproduce line, `exec.ts`'s missing-program refusal and `test-shards.ts`'s reproduce command both. A name holding a space or a `;` interpolated bare is an instruction that runs something else |
14
- | Missing positionals | `MissingPositionalError`, never `BadFlagError` (names a flag that does not exist) and never `UnknownCommandError` (says a known command form is not one). Its `example` is a REAL invocation — `x g route <name>` in a shell is a redirect |
15
- | Bare subcommands | `CommandSpec.defaultSubcommand`, **declared**. The parser answered `subcommands[0]` until 1.2.0, so `x db` ran `gen` — the migration GENERATOR — because it sorted first, and `x mcp` started a server. A command with no defensible default declares none and `MissingSubcommandError` refuses the bare form; `parse.test.ts` pins the set at exactly `db` and `mcp`. Its fix is `x help <command>`. Both forms answer now: `--help` is read off the flag loop and `readSubcommand` is SKIPPED when it is set, so `x db --help`, `x mcp --help` and `x pr --help` print usage instead of exiting 1 with this same refusal — which is what they did on every command taking a subcommand until 2026-08 (`parse.test.ts` pins it across the shipped registry) |
16
- | Closed flag values | a flag whose values are a closed set is READ through a function that refuses the rest — `cmd-build.ts`'s `readTarget`, `cmd-deploy.ts`'s `readMethod`, `cmd-routes.ts`'s `readSurfaceFilter`, `cmd-mcp.ts`'s `isTransport`. `=== 'helm' ? 'helm' : 'compose'` made `x deploy --method helmm` a COMPOSE deploy reporting `method: "compose"`, and `--surface App` reported `0 routes` and exit 0 — a typo and an empty table rendering identically. The set is the framework's own where one exists (`SURFACES` from `@ultimat3/render`), never a list restated here |
17
- | Passthrough | `CommandSpec.passthrough`, **declared**. `ParsedArgs.passthrough` says "handed to the underlying tool verbatim" and had no reader anywhere, so `x test unit -- --coverage --bail` parsed both flags and dropped both — a run that measured nothing reporting what a coverage run reports. `x test` forwards them to `bun test` (before the file list) and prints them back in its reproduce line; every other command refuses a non-empty `--` tail with `X_CLI_BAD_FLAG` rather than dropping it. `help`/`version` short-circuit ahead of the command and are exempt |
18
- | App root | `CommandSpec.requiresApp`, **enforced by `dispatch.ts`** before `target.run` — the field's doc said so for 17 commands and nothing read it, so the promise was kept only by each command remembering to call `requireAppRoot` itself. Those 17 calls stay (they hand the command its root, and name subcommands the dispatcher cannot see), but the DECLARATION is what decides, ahead of any check a command makes about its own arguments; `--help` is exempt, because `target` is the help command by then |
19
- | Result helpers | `command.ts`'s `ok()` / `failed()` write `ok` **after** the `extra` spread: the function's name is the verdict and nothing a caller passes can overturn it. Spread last, `failed('verify', '1 of 20 steps failed', { ok: true })` answered `ok: true` and `exitCodeFor` exited **0** on it — a green CI over a red command (`command.test.ts`) |
20
- | I/O | only `dispatch.ts` renders or exits; commands return `CommandResult` |
21
- | Staying up | a command still listening when `run` resolves returns `hold` (`hold.ts`), or `bin.ts` exits out from under it |
22
- | `--json` | every command, no exceptions — same data as the human render |
23
- | Errors | codes + titles in `src/error-codes.ts`, classes in `src/errors.ts`, subclass `UltimateError`, never a bare `Error`. A class may sit beside its one thrower when `errors.ts` has no room under the 500-line ceiling — `db-seed.ts` and `metrics-endpoint.ts` do |
24
- | Subprocesses | only through `exec.ts`, so a test can inject a fake `Runner` |
25
- | Templates | `templates/*.ts` return strings; no fixture files on disk |
26
- | Strings | rendered output through `messages.ts`, missing key renders `⟦key⟧` — see below for what is *not* rendered output |
27
- | Facts | load the app (`app-load.ts`), then project it — never parse source for primitives |
28
-
29
- Every fact the CLI reports comes from a framework package: the manifest from
30
- `@ultimat3/manifest`, `openapi.json` from `@ultimat3/action`, the route table from
31
- `@ultimat3/render`, budget units from `@ultimat3/render`, the `/_x` panels from
32
- `@ultimat3/admin`, the MCP tool catalog from `@ultimat3/mcp`, eval coverage from
33
- `@ultimat3/ai`. A check that reimplements one of those here is the bug, not the fix.
34
-
35
- `app-evals.ts` is why the `eval` step can apply with no eval suite at all: a prompt no eval
36
- names is `X_EVAL_MISSING`, an eval whose baseline was never recorded is `X_EVAL_BASELINE_MISSING`,
37
- and a skipped step would read as a green gate over untested code. Its third rule runs *before* the
38
- suite rather than beside it — `ULTIMATE_EVAL_RECORD` makes every eval write its own numbers and
39
- pass, so a gate that inherited the flag would rewrite the committed baselines during the run, and
40
- a finding after the fact does not put them back.
41
-
42
- `verify-floor.ts` is the suite ratchet, and it is split across two owners on purpose. `runVerify`
43
- judges the **suites**: a step the committed `x.verify.json` names that reports nothing to check is
44
- recorded failed and not skipped, so the failure count, `data.failed` and every step table another
45
- gate parses all carry it. The `manifest` step judges the **file**: a floor that does not parse, or
46
- that names a step the gate does not run, enforces nothing — and a ratchet nobody notices is off is
47
- the false green it exists to close. Nothing writes the file; a gate that edits its own floor
48
- ratchets in both directions.
49
-
50
- **"Nothing to check" is two conditions and one code.** `applies` sees the first — no files — and
51
- cannot see the second, because `describe.skipIf` is decided inside the child process: measured with
52
- no `TEST_DATABASE_URL`, `live` is `4 pass, 114 skip` and the step reported green over a suite whose
53
- whole subject is the database. `test-counts.ts` reads bun's own summary back (`parseBunTest`, the
54
- same reader `x mcp`'s `test.run` uses — a second regex over one format is drift), each runner
55
- attaches `StepOutcome.tests`, and a floor step whose `ran` is zero is `X_VERIFY_SUITE_VANISHED`
56
- with `skippedSuiteFinding`'s cause. **Zero, not a ratio**: one real assertion is a suite that runs,
57
- and a threshold would be a number nobody can defend. An absent `tests` is a step that spawned no
58
- test process at all (`eval` answering with declarations alone), which is not the same claim.
59
-
60
- `x new` writes an `x.verify.json` (`templates/scaffold-repo.ts`), or the code above is unreachable
61
- in every generated app — the repo shape that grows suites fastest. It names the eleven steps the
62
- scaffold has proved apply, and deliberately not `e2e`: the scaffolded `page.e2e.test.ts` is an
63
- `e2eTest`, which is `test.skip` until the app registers a browser driver, so pinning it would fail
64
- the app's first gate on the scaffold's own placeholder.
65
-
66
- `tsconfig-references.ts` is `package-shape`'s fourth rule: **every published workspace is in the
67
- root `references`**. `bun run typecheck` is `tsc -b`, which builds referenced projects and nothing
68
- else, so a package no reference names is one the gate's own `typecheck` step passes over without
69
- reading a line of it — `X_PACKAGE_UNREFERENCED`, whose `fix:` is the exact `{ "path": … }` entry
70
- and the `tsc -b` that proves it took. Private packages are exempt (a generated app's are all
71
- private), and a root that declares **no** `references` array is not judged at all: project
72
- references are opt-in, and a scaffolded app builds through `extends` + `include`.
73
-
74
- `workspace-graph.ts` is `package-shape`'s fifth rule: **every cross-workspace import is declared
75
- in the importing workspace's own manifest**. Without it a scaffolded repo's dependency graph exists
76
- only inside `tsc` — imports resolve through the root `tsconfig.json` `paths`, so affected-package
77
- detection, `bun --filter` ordering and "what breaks if I change this" all read manifests and all
78
- answer too small a set (issue #239, found in a real app where a change reaching five packages
79
- reported one). `X_WORKSPACE_DEP_UNDECLARED` names the manifest and the exact line to add. Shipped
80
- source only: a test file's import is not judged, because `packages/*` here declares no
81
- `devDependencies` by design and the root's hoist is what resolves them. A manifest the scan cannot
82
- read is its own finding rather than a silent skip — a skipped workspace is a hiding place for the
83
- very edge the rule is looking for.
84
-
85
- `app-permissions.ts` is the `policy` step. Two references in the whole
86
- framework are bare strings nothing checks — `RoleDef.grants` and `RouteGuard.permission` — while
87
- `can()` calls `assertPermission` and throws `X_PERMISSION_UNKNOWN` on the first request that
88
- reaches the route. So `x new` shipped an app that granted `dashboard:read`, required it on
89
- `/dashboard` and declared it nowhere: HTTP 500 on two of its three routes, from the first `x dev`,
90
- under a green gate. It reads `roleDefinitions()` and `routeEntries()` after `loadApp` and reports
91
- each reference `isKnownPermission` refuses — **that predicate and no other**, because it is the one
92
- `assertPermission` uses, including its rule that an app which has declared NOTHING is not checked
93
- at all. A gate that disagreed with the process it gates would be worse than none. The cause and the
94
- `fix:` are `permissionUnknown`'s, so `@ultimat3/policy` owns both wordings; `X_PERMISSION_UNKNOWN`
95
- is in `CLI_BORROWED_ERROR_CODES`. Its own step rather than a rider on `budgets`, by that step's own
96
- test: reported there, an authz defect would hand the reader a byte budget (axiom 4). It costs no
97
- second app load.
98
-
99
- `dev-replica.ts` is where read-replica routing is WIRED, and it had to be wired in two places
100
- because it was opt-in twice. `@ultimat3/db`'s `defaultClient()` is the one composer of
101
- `replicatedClient(primary, replica)` from `DATABASE_REPLICA_URL`, and it runs only from
102
- `baseClient()` — "the client an app installed none for" — while every process the framework boots
103
- calls `setDbClient` in `dev-queue.ts`, so no booted process had ever read that variable. Routing
104
- also needs an open `withReplicaReads` scope, and nothing opened one. `startDb` now installs the
105
- replicated pair as the AMBIENT client while keeping the primary for everything this boot does
106
- itself (`applySchema`, the queue's `PgExecutor`, `ping`, `close` — DDL and a claim are writes), and
107
- `cmd-dev.ts` / `serve.ts` prepend one middleware frame that opens the scope per request. Both
108
- halves are `undefined`/empty with no replica configured, and an EMBEDDED binding never gets one:
109
- PGlite has no standby. Not `@ultimat3/http`'s pipeline, which would make the HTTP tier know what a
110
- database is; the boot is the only tier that may know about a request and a pool.
111
-
112
- `flag-number.ts`'s `portPairAfter` is the one suggestion for a caller that binds a PAIR, and
113
- `neighbouringPort` is for a caller that binds one port. `x dev` takes `PORT` **and** `PORT + 1`, so
114
- the neighbour of a taken web port is the sync port — both `x doctor`'s `X_PORT_IN_USE` and
115
- `dev-sync.ts`'s handed back the port the finding had just named as taken, each under a test that
116
- said it ended the failure. It answers downward at the top of the range for the reason
117
- `neighbouringPort` does, and one further: the suggestion's own neighbour has to exist, or
118
- `syncPortFor` refuses it with `X_PORT_INVALID`.
119
-
120
- `port-probe.ts` is the one `portFree`, because two commands ask it and must not disagree:
121
- `x doctor` reports it as a finding for BOTH ports `x dev` binds — the web port and the sync port,
122
- which it derives with `syncPortFor` (the boot's own rule: a `--port` at the top of the range has no
123
- sync port at all, so the probe reports `X_PORT_INVALID` instead of probing the port BELOW it, which
124
- is what `neighbouringPort` answered there) — each labelled with the role that wants it — and `startSync` asks it after a failed
125
- `listenSyncNode` so a taken neighbour is `X_PORT_IN_USE` rather than `X_CLI_UNEXPECTED` over
126
- `Bun.serve`'s own English rendered into a `cause:`. It is ASKED, never read off the caught value,
127
- which is what `scripts/catch-render.ts` refuses; anything else the listener failed on is re-thrown
128
- untouched. `x doctor` also probes `DATABASE_URL` with a real `select 1` through
129
- `@ultimat3/db`'s `checkDb` — a TCP connect answers "reachable" for a running server with wrong
130
- credentials, which is the case an operator most needs told about — and reports `X_DB_UNAVAILABLE`
131
- with that package's own two-branch fix. An EMBEDDED binding is not probed: that lock is `x dev`'s.
132
-
133
- `doctor-offline.ts` is `x doctor`'s offline-fallback check, and it is the shape of a diagnostic
134
- that could not be closed. The probe was the literal `apps/web/app/offline.tsx` — a filename
135
- `assertRouteFilename` REFUSES (`X_ROUTE_FILE_INVALID`: the directory is the URL, so a page is
136
- `page.tsx`) — while `x new` scaffolds `apps/web/site/offline/page.tsx` and the finding's own `fix:`
137
- writes `apps/web/app/offline/page.tsx`. Three different paths, so every app the framework has ever
138
- produced reported `X_PWA_NO_OFFLINE_FALLBACK` from its first `x doctor`, running the fix changed
139
- nothing, and the app that HAD the route was told it did not. It now reads the DECLARED
140
- `pwa.offline.fallback` through `loadPwaArtifacts` and matches it against `describeRoutes()` —
141
- **`site/` only**, `As of 2026-09`. It accepted `app/` too, on the argument that both surfaces answer
142
- the same URL; true, and not the question. `SURFACE_SPECS` allows `app/` exactly `stream | ssr`, only
143
- a `static` route is prerendered, and `serviceWorkerArtifacts` precaches a rendered DOCUMENT
144
- (`documents.get(fallback)`) — so an `app/` fallback has nothing to precache and the offline
145
- navigation reaches the network it exists to survive without. The check and its own `fix:` disagreed
146
- about one code. Not closed by this: a `site/` route declaring `render: 'ssr'` is not prerendered
147
- either, and `NavigableRoute` carries no render mode. An app whose modules will not import is not
148
- judged at all, which is
149
- `appEntities`' rule (`schema-drift.ts`) one registry over. The fix is `x g route <name>
150
- --surface site` only where the fallback is one path segment the generator can really produce: a
151
- nested path slugifies to a DIFFERENT url, so there it is the config edit instead — a `fix:` that
152
- runs and leaves the finding where it was is the defect this file exists to end. **`site`, not
153
- `app`**: the document that answers a lost network has to render with no network, no session and no
154
- database, which `app/` (`ssr | stream`) cannot promise, and it is the line `@ultimat3/pwa`'s own
155
- `X_PWA_NO_OFFLINE_FALLBACK` hands out for the same code — two fixes for one code are two answers.
156
-
157
- `X_ENV_MISSING`'s fix is the file write, for the same reason. It was `x new --force`, which cannot
158
- run where the reader is standing: `x new` takes a `<name>` positional (`X_CLI_BAD_FLAG` without
159
- one) and with one it scaffolds a SECOND app beside the broken one. `cp .env.example
160
- .env.development` — the committed projection of `envSchema`, which is what `x env example` writes
161
- and `X_ENV_EXAMPLE_DRIFT` keeps honest.
162
-
163
- `i18n-index.ts` is the one writer of an app's `packages/i18n/src/index.ts`, shared by `x g` and
164
- `x i18n add|sync`. A catalog on disk and a SELECTABLE locale were two different sets: `x i18n add
165
- fr` wrote the file, exited 0, and left `x verify --only i18n` red with `X_CATALOG_UNREGISTERED`
166
- whose `fix:` named an edit that had already been made — an agent following it verbatim changes
167
- nothing and loops forever, on the command whose whole job is adding a locale. `unregisteredFix`
168
- (`i18n-registration.ts`) is the other half: one code over two causes, so where the index EXISTS and
169
- does not name the locale's own `catalogs/<tag>.json` import, the CLI substitutes a fix that
170
- performs the registration. The package's own "move the `defineCatalogs()` call" line still stands
171
- for the cause it was written for.
172
-
173
- `app-agents-md.ts` is why the `manifest` step declares no `applies` at all. The drift half needs
174
- a committed `x.manifest.json` to compare against, but `AGENTS.md` is required of every repo the
175
- gate runs in — so the step always has a question to answer, and gating both halves on the file
176
- that only the first one needs is how `X_AGENTS_MD_MISSING` stayed unreachable while its wiki row
177
- said it fails builds.
178
-
179
- ## What goes in `messages.ts`, and what does not
180
-
181
- `messages.ts` holds the strings a command *renders* — `CommandResult.summary`, `lines`, anything
182
- the human renderer prints. Three things stay inline, deliberately, and a review asking to move
183
- them is answered by this table rather than by a second convention:
184
-
185
- | Not in the catalog | Why |
186
- |---|---|
187
- | `CommandSpec.summary` / `.usage` / `FlagSpec.summary` | the spec is the command's declaration, next to the `run` it describes; parsing and `x help` both derive from it. All command modules declare it inline — moving a subset creates two places to look for one command's help |
188
- | `Finding.cause` / `Finding.fix`, and `BadFlagError`'s `reason` | stable machine-readable diagnostics. A `fix:` is copied and run verbatim; a translated one is a broken command |
189
- | Fixed-width table headers (`renderJobTable`, `renderRouteTable`) | column keys, not prose — the widths are computed from them and `--json` carries the same names |
190
-
191
- ## The introspection commands project registries, they never re-derive facts
192
-
193
- | Command | Files | Reads |
194
- |---|---|---|
195
- | `x actions` / `x queries` / `x entities` | `cmd-registries.ts` | the three declaration registries |
196
- | `x jobs` | `cmd-jobs.ts`, `jobs-{driver,report,drain,json,table}.ts` | `@ultimat3/jobs`' own introspection |
197
- | `x tasks` | `cmd-tasks.ts`, `tasks-facts.ts` | `registeredTasks()` + `@ultimat3/time`'s cron resolution |
198
- | `x policy` | `cmd-policy.ts`, `policy-facts.ts` | `@ultimat3/policy`'s `policyMatrix()` over the app's own `Policy` objects |
199
- | `x i18n` | `cmd-i18n.ts`, `i18n-audit.ts`, `i18n-registration.ts` | `@ultimat3/i18n`'s `extractFromFiles` + `auditCatalogs`, then the live catalog registry |
200
- | `x i18n check` and the `policy` step, before either registry | `duplicate-packages.ts` | `Bun.resolveSync` of `@ultimat3/i18n` / `@ultimat3/policy` / `@ultimat3/entity` from the app root, every workspace and the CLI's own directory, keyed by REALPATH. Two real directories is `X_PACKAGE_DUPLICATED`: a registry is per module instance, so the app registers into one copy and the CLI reads the other — `x i18n check` said "move `defineCatalogs()`" about a call that was already where it belongs (ai-maxxing, 2026-09-05), and the policy step was green over an undeclared grant. A workspace symlink to one checkout is one copy; two store entries at ONE version are two |
201
-
202
- Each pairs a `cmd-*.ts` of CLI wiring with a facts module that takes plain inputs and returns plain
203
- data, so the projection is testable without a `ParsedArgs` — the `cmd-jobs.ts` / `jobs-report.ts`
204
- split, repeated. Tables go through `table.ts`; a second padding helper is the drift it prevents.
205
-
206
- `x policy explain` exists because five packages already print it as the `fix:` on an authz denial
207
- (`policy`, `action`, `query`, `http`, `auth`), and `x i18n` because all three of `@ultimat3/i18n`'s
208
- own error fixes name it. A `fix:` line naming a command this build does not ship is the failure
209
- mode `cmd-planned.ts` closes for planned commands and these close for real ones.
210
-
211
- `x i18n check` scans source, which the "never parse source for primitives" rule below does not
212
- forbid: a `t()` call is not a primitive and no registry holds it. It uses `source-files.ts`, the
213
- same walk `errors` and `filesize` use, so the three cannot disagree on what the app's source is.
214
-
215
- **And then it asks the question the scan cannot answer.** A catalog complete on disk, its keys used
216
- everywhere in source, and an audit of one against the other were all green for an app that rendered
217
- `⟦key⟧` on every page — registration is a side effect of importing the module that calls
218
- `defineCatalogs()`, and nothing imported it (issue #249). `i18n-registration.ts` loads the app
219
- through `loadApp` — the same call `serveApp` makes at boot, so it is the boot's own answer and not a
220
- simulation of one — and compares the catalogs on disk against the live registry, per locale
221
- (`X_CATALOG_UNREGISTERED`). Two conditions, one code: a shipped catalog no module registered, and
222
- no catalog anywhere while source calls `t()` — the second is the vacuous green an app with an
223
- `app.config.ts` and no `packages/i18n/catalogs/` used to get. `loadApp`'s own findings ride along
224
- ONLY when something is unregistered, because "packages/i18n/src/index.ts: SyntaxError" is the
225
- evidence for the gap above it and noise on a pass.
226
-
227
- `catalogFindings(root)` is the one composition both callers report: `x i18n check` renders it as a
228
- table with a `registered` column, `x verify`'s **`i18n` step** returns it as findings. One
229
- implementation, so the command and the gate can never disagree about an app.
8
+ Commands: `bun test packages/cli` (from the repo root — the test preload lives there),
9
+ `bunx tsc --noEmit -p packages/cli/tsconfig.json`.
230
10
 
231
- **The generators emit `useT()` from the app's own catalog module, never `t` from `@ultimat3/i18n`.**
232
- The specifier is `resolveCatalogModule(root)` — `packages/i18n/package.json`'s `name`, read off
233
- disk, because a template is a pure string function and only a package name resolves as an import.
234
- An app with no such package keeps the framework import: emitting one that cannot resolve trades a
235
- wrong idiom for a file that does not compile. This is where the reported bug's idiom came from —
236
- every generated page imported `t` directly, so no page depended on the module that registers.
11
+ ## Rules
237
12
 
238
- **A catalog is authored nested and read flat.** `Catalog` (`{ 'nav.home': 'Home' }`) is the
239
- translator's form; the file on disk holds `{ nav: { home: 'Home' } }`, and `parseNestedCatalog`
240
- refuses a dot inside a key — so anything writing a catalog goes through `nestCatalog`
241
- (`serializeCatalog` for `x i18n add|sync`, `templates/catalog-json.ts` for every generator) or it
242
- emits a file `defineCatalogs` rejects at the app's first boot. `merge: 'json'` unions **deeply**
243
- (`json-merge.ts`) for the same reason: `x new` and `x g resource` both contribute under `app`, and
244
- a shallow spread keeps one of them.
245
-
246
- ## Three commands that reach outside the process, and none of them is a gate step
247
-
248
- `x shot`, `x pr` and `x ci` exist because of the one line in the root `CLAUDE.md` that shapes this
249
- whole package: **the primary developer is an AI agent.** An agent cannot open a browser, cannot look
250
- at a running dev server and cannot read the GitHub web UI. It can read a file, and it can run a
251
- command that prints. These three turn each of those into a file and a print.
252
-
253
- They are also the only three commands that need something the process does not have — a browser, a
254
- network, a GitHub token — which is why **none of them is a step of `x verify`**, and why that is not
255
- an oversight to be corrected later. A gate that needs a browser goes red for reasons unrelated to the
256
- change, and CI does not install one.
257
-
258
- | | Reaches for | Never |
259
- |---|---|---|
260
- | `x shot <route>` | `x dev` on a scratch port, plus the app's own `puppeteer-core` through `@ultimat3/scraping` — launching Chrome here, or **attaching** to one over `--cdp-url` / `SCRAPE_CDP_URL`, which is what every stealth provider sells and what `remoteBrowser()` has called its primary path since it shipped | the static build — `--target static` prerenders `site/` only, so an `app/` route would photograph the landing page |
261
- | `x shot --island <name>` | the same server and the same browser, plus the app's own `*.island.states.ts` | a second command — photographing a route and photographing a component are one job with two subjects, and `--island` with a route positional is refused by name |
262
- | `x shot --all-islands` | every `*.island.states.ts` in the app, one boot, one browser per declared VIEWPORT across all of them | a second capture loop — one island and every island are `runIslandSweep` with one argument, because two loops are two answers to what a run produced |
263
- | `x pr review\|resolve\|reply` | `gh api graphql`, through the injected `Runner` | `gh pr view --comments`, which shows *issue* comments and not the line-anchored threads that carry the findings |
264
- | `x ci` | `gh run view --log-failed`, one call | a per-job log fetch — the run and all its jobs come back together |
265
-
266
- **`verdict.json` names its own blind spots, and that is the design.** `x shot` reports what it could
267
- not observe alongside what it did. A capture tool that silently omits what it cannot see is worse
268
- than one that says so, because the omission reads as a clean result.
269
-
270
- ### `--island` photographs ONE component in a state nobody can click to
271
-
272
- A failed read, an empty list, over-quota, read-only: the states a reviewer most needs to see are the
273
- ones a running app will not produce on request. `--island` takes them, one address at a time.
274
-
275
- | File | Job |
13
+ | Rule | Detail |
276
14
  |---|---|
277
- | `island-states-load.ts` | discover `*.island.states.ts`, prove each pure, import it, check the set |
278
- | `island-harness.ts` | the document that mounts ONE island over `data-x-entry` / `data-x-props` |
279
- | `island-harness-script.ts` | what runs before the chunk does: the sealed network, the pinned clock, the readiness watch |
280
- | `island-harness-route.ts` | `GET /_x/island`, mounted by `x dev` |
281
- | `island-shot.ts` | the RUN: which islands, in which order, which artifacts — and the missing-shot gate |
282
- | `island-capture.ts` | ONE picture: the assertions before the shutter, the crop rectangle, the one session |
283
- | `island-shot-index.ts` | `index.md` as a PURE function of (manifests, targets, verdicts) — no I/O in the rule |
284
- | `shot-browser.ts` | which browser a run gets — launch one here, or attach over `--cdp-url` / `SCRAPE_CDP_URL` — as three rules over plain inputs |
285
- | `island-verdict.ts` | the per-state verdict — a PNG cannot say the component threw or logged |
286
- | `cmd-shot-island.ts` | the flags, the refusals, and the one browser per declared viewport |
287
-
288
- The vocabulary is **`@ultimat3/testing`'s**, not this package's: `defineIslandStates`,
289
- `islandShotTargets`, `islandAddress` / `parseIslandAddress`, `findIslandStates`,
290
- `assertIslandStatesPure`. `cli → testing` is a declared sideways edge and `cli → scraping` is
291
- another, which is what makes `@ultimat3/cli` the only package that can hold both the mount half and
292
- the screenshot half.
293
-
294
- **The expected picture list exists before a browser does.** `loadIslandStates` → `findIslandStates`
295
- → `islandShotTargets` is a pure expansion off files on disk, and the run ends by diffing it against
296
- what actually landed (`missingShots`, `X_SHOT_ISLAND_MISSING`). That diff is the point of the whole
297
- design: a loop that swallowed every failure would otherwise report a clean run with no pictures in
298
- it, and "produced nothing and exited 0" is the one outcome a reader cannot tell from success.
299
-
300
- **An unstubbed request FAILS the run.** The page's own seal replaces `fetch`, `WebSocket`,
301
- `EventSource` and `XMLHttpRequest` before the island's chunk is imported, answers the state's
302
- `routes` and publishes everything else on `window.__xShot.unstubbed`; the capture refuses on a
303
- non-empty list with `X_SHOT_ISLAND_UNSTUBBED_REQUEST`, naming each method and path. A component
304
- whose fetch quietly hangs paints its own loading branch, and the picture then shows a fixture gap
305
- dressed up as a real component state. `@ultimat3/testing`'s `sealNetwork()` is not reusable here:
306
- it patches THIS process's `globalThis.fetch` and the component runs in the page's realm.
307
-
308
- **Readiness is quiet, not idle.** Fonts ready, then N consecutive animation frames with an unchanged
309
- network-ACTIVITY counter — never "nothing in flight", which never comes for a state whose fixture is
310
- deliberately `pending`, and never a fixed sleep, which photographs whatever a slow machine painted.
311
-
312
- **Eight assertions before a shutter opens** (`photographFault`), each naming a fact the picture would
313
- have hidden rather than shown: no probe, not the harness, no host element, a mount that REJECTED, a
314
- mount that never finished, a page that never went quiet, a zero-sized box, a box with no children
315
- and no text. Then a byte floor as a backstop. Every one of them otherwise comes out as a plausible
316
- image of the wrong thing.
317
-
318
- **`--all-islands` is a spelling, never `--island` with no value.** The parser refuses a bare
319
- `--island` ("expects a value") and `--island=` is an empty NAME, so "every island" had no form a
320
- reader could type that could not be read as a mistyped one. A boolean cannot be confused with a
321
- name. Every contradictory pair is refused BY NAME and before a value is read — beside `--island`,
322
- beside a route positional, and beside `--state`, which belongs to ONE manifest's vocabulary
323
- (`empty` in two manifests is two unrelated states). An app declaring no states at all is refused
324
- too, rather than answered with an empty gallery: "produced nothing and exited 0" is the one outcome
325
- a reader cannot tell from success. Its `fix:` names `x g island`, and deliberately not a states
326
- generator — a `fix:` may only cite a command this build ships (`fix-command.ts`), and there is none.
327
-
328
- **A sweep never aborts on a failure.** Every state the app CAN photograph is captured, every
329
- per-island `verdict.json` and the index are written, and only then does the missing-picture gate
330
- turn the reasons into a non-zero exit. One island that will not mount must not cost a reader the
331
- other nineteen — which is the same rule the per-state loop already followed, one level up.
332
-
333
- **`.x/shot/island/index.md` is the file an agent opens**, and it is written for a SINGLE-island run
334
- too: the file that says what a picture IS cannot be a property of how many islands were asked for.
335
- `island-shot-index.ts` is a pure renderer over (manifests, targets, verdicts) — no disk, no browser
336
- — and the caller does the writing, which is the split every check in this repo uses. It carries the
337
- counts, the re-run commands, the verdict's own `blind` list (handed in, never reworded: two
338
- wordings drift), and per state the `id`, the `title`, the `note` — the *"you cannot reach this by
339
- clicking, because …"* line, which is the whole reason a reviewer knows what they are looking at.
340
-
341
- **A console WARNING and an overflowing box are recorded and gate NOTHING.** `stateShotOk` reads
342
- neither, on purpose: a signal that fails a run is a signal an author switches off. The warnings were
343
- already in `page.console()` and already in `--json`; what was missing is that nobody counted them.
344
- The overflow is new and is the readiness probe's own answer — `scrollWidth > clientWidth` on the
345
- crop target, measured in the same round trip, because a fact measured after the shutter is a fact
346
- about a different page.
347
-
348
- **The clip carries a margin, clamped to the document.** It was the readiness box EXACTLY, and a
349
- pixel-tight rectangle shaves off everything a component paints outside its border box — a
350
- `box-shadow`, an outline, a focus ring, a hairline border on a subpixel — so a reviewer reads a
351
- component with no elevation as flat. `ISLAND_CROP_MARGIN_PX` is the one constant; the clamp reads
352
- the document's own extent off the probe (`page`), never the viewport, and it may only ever make the
353
- frame BIGGER than the component's box — a clamp that could shrink it would crop the subject.
354
-
355
- **One session per picture, and that is not an optimisation to collapse.** `page.console()` and
356
- `page.pageErrors()` are bounded rings over the whole SESSION, so a shared one files state A's
357
- console errors under state B — and per-state attribution is the half of the artifact that gates.
358
-
359
- **The picture is the CROP TARGET, `As of 2026-08-26`** — the readiness probe's own box, which is
360
- the selector the manifest declared or the island's host element. Measured on `examples/dummy`
361
- before it: 720x560 for a component whose box the verdict reported, in the same run, as 688x104.
362
- `CaptureClip` had been on the port since #336 and `island-shot.ts` passed none, and this paragraph
363
- said the port "takes no clip rectangle" — so did the verdict's own `blind` list, which is a blind
364
- spot naming a capability the tool has, the same lie as one hiding a gap. The state's `viewport` is
365
- still what the page is LAID OUT in, so there is still one browser per declared viewport, memoised;
366
- it is no longer what the picture is.
367
-
368
- **The clip is translated, not copied.** `getBoundingClientRect()` answers VIEWPORT coordinates and
369
- a capture clip is in PAGE coordinates; they agree only at the origin, which is the one case a
370
- harness happens to be in and is a rule nothing enforces. So the probe returns `scroll` beside `box`
371
- and `clipFor` adds them — a component below the fold would otherwise crop a band it is not in, with
372
- a picture that looks like a picture and nothing anywhere to report it. `box` keeps meaning the DOM's
373
- own answer, because that is what the verdict publishes.
374
-
375
- **Both themes are photographed by emulating the PREFERENCE.** `page.colorScheme(target.theme)`
376
- before the navigation, so the first paint already has it. The harness's `data-theme` attribute stays
377
- — it is right for a component that READS a theme it does not own — but it is the OUTCOME of a theme
378
- decision, and a component that resolves `'system'` itself deletes it on mount: `x shot --island`
379
- reported four pictures and wrote two, byte-identical, same md5 (#338). Re-setting the attribute
380
- after readiness is not the repair; it photographs a state the component would never reach.
381
-
382
- **`loadApp` does not import a states file**, for the reason it does not import an island: it
383
- registers no primitive, and importing it would put `@ultimat3/testing` in the server module graph of
384
- every `x dev`, every `x build` and every gate step that loads the app (axiom 6).
385
-
386
- **`x shot` reuses a running `x dev` rather than booting a second one.** Embedded Postgres is
387
- single-writer, so a second boot is `X_DEV_ALREADY_RUNNING` and no picture is ever taken. A reused
388
- server's `stop()` deliberately does not clear the other process's lock.
389
-
390
- **`gh` is invoked through `ctx.runner`, never `Bun.spawn` directly** — that is what lets every test
391
- supply a reply table and assert the exact argv with no network and no `gh` installed. `GhOptions.fix`
392
- is a **required** field, so shelling out to GitHub without stating a remedy is a type error rather
393
- than a review comment. A GraphQL response is untrusted input and is parsed against a schema, never
394
- cast: a `null` where an id was expected would otherwise become a mutation against `undefined`.
395
-
396
- ## The browser-backed e2e driver lives here, because the adapter has nowhere else to be
397
-
398
- `@ultimat3/testing` declares `PageLike` and has never had a driver for it; `@ultimat3/scraping` owns
399
- the only real browser in the tree and speaks `ScrapePage`. Both are tier 5, so neither may import
400
- the other, and `testing -> scraping` would be a NEW sideways edge. This package already holds
401
- declared edges to **both** (`SIDEWAYS_ALLOW`, `scripts/lib/tiers.ts`) and is the one package allowed
402
- to know about everything — so the join is here, and it is the same rule
403
- `docs/architecture/01-package-map.md` states for wiring a route table into `pwa`.
15
+ | Entry | `src/bin.ts` — argv, stdout, exit code only. `local-cli.ts` re-executes the app's own `node_modules/@ultimat3/cli` when a global `x` is a different realpath: a second `@ultimat3/entity` instance is an empty registry |
16
+ | Registry | `registry.ts`: every declaration static (`cmd-<name>-spec.ts`), every body `await import()`ed when that command runs. A command body is never imported to answer the parser or `x help` — `registry-lazy.test.ts`. A spec's constants live in the spec file or a leaf module, never in the body |
17
+ | Startup cost | `sass` loads on the first stylesheet compile (`@ultimat3/render`'s `compileStylesheet`), Babel on the first island transform (`solid-loader.ts`); both pinned by `*-lazy.test.ts` in a child process |
18
+ | Module names | `role-*` / `runtime-*` are booted by `serve.ts` in production; `dev-*` is `x dev`'s alone. Derived from the import graph by `module-naming.test.ts`, never listed |
19
+ | stdout / stderr | `write-line.ts`'s `writeLine` / `writeErrorLine` — synchronous fd writes, never `process.stdout.write` (truncates at the pipe buffer before `process.exit`). A `CommandResult` with `stream: 'stderr'` goes to fd 2 (`x mcp serve --transport stdio`) |
20
+ | `--json` | every command; `dispatch.ts` sets core's log stream to stderr under it. Same data as the human render |
21
+ | I/O | only `dispatch.ts` renders or exits; commands return `CommandResult`. `ok()` / `failed()` write `ok` after the `extra` spread — the verdict cannot be overturned |
22
+ | Staying up | a command still listening when `run` resolves returns `hold` (`hold.ts`); the release runs inside the drain's own deadline |
23
+ | Test execution | `test-shards.ts`'s `testArgs` — one `bun test --parallel=N` per pass (`test-passes.ts`); `live`/`e2e` run serially (`SERIAL_TYPES`, `test-workers.ts`) |
24
+ | Numeric flags | `flag-number.ts` — one reader for `--port` / `--workers` / `--shard` |
25
+ | Shell quoting | `shell-quote.ts`'s `quoteArg` for every value pasted into a `fix:` or a reproduce line |
26
+ | Missing input | `MissingPositionalError` (its `example` is a real invocation); a bare subcommand is refused unless the spec DECLARES `defaultSubcommand` (`MissingSubcommandError`); `--help` short-circuits both |
27
+ | Closed flag values | read through a function that refuses the rest (`readTarget`, `readMethod`, `readSurfaceFilter`, `isTransport`), from the framework's own set where one exists |
28
+ | Passthrough | declared per command (`CommandSpec.passthrough`); every other command refuses a `--` tail with `X_CLI_BAD_FLAG` |
29
+ | App root | `CommandSpec.requiresApp`, enforced by `dispatch.ts` before `run` |
30
+ | Declared flags | every flag a command declares is read in this package's source — `flag-reads.test.ts` (`X_CLI_FLAG_UNREAD`) |
31
+ | Planned commands | `PLANNED_COMMANDS` / `PLANNED_SUBCOMMANDS` (`cmd-planned.ts`): in the registry, exit `X_NOT_IMPLEMENTED` with a fix naming a shipped command |
32
+ | Errors | codes + titles in `error-codes.ts`, a runnable line per code in `mcp-errors.ts` (typed over `CliErrorCode`), classes in `errors.ts` or beside their one thrower when `errors.ts` is at its ceiling. Never a bare `Error` |
33
+ | Subprocesses | only through `exec.ts`'s injectable `Runner`; `gh` through `ctx.runner` with a required `fix` |
34
+ | Templates | `templates/*.ts` return strings, pre-formatted for Biome (`wrap.ts`'s `wrapList` / `wrapImport` / `sortSpecifiers`); no fixture files on disk |
35
+ | Strings | rendered output through `messages.ts` (missing key renders `⟦key⟧`). NOT in the catalog: `CommandSpec` summaries and usage, `Finding.cause` / `fix`, fixed-width table headers |
36
+ | Facts | load the app (`app-load.ts`), then project a framework package's registry — never parse source for primitives, never re-derive a fact another package owns |
37
+ | Public API | `src/index.ts`, explicit re-exports only. Every name there is a semver promise; a name nothing outside this package reads is not exported (22.0.0 pruned 236) |
38
+
39
+ ## File map
40
+
41
+ ### Gate steps (`x verify`)
404
42
 
405
43
  | File | Job |
406
44
  |---|---|
407
- | `e2e-driver.ts` | `installE2eDriver({ page, baseUrl })` — the ONE call an app's test preload makes. Registers `page` over its declaration and installs the `e2eTest` seam; returns the undo |
408
- | `e2e-page.ts` | `PageLike` over four members of `ScrapePage`, declared structurally so a test stands one up in six lines |
409
- | `e2e-locator.ts` | `LocatorLike` — a handle that resolves nothing until asked, one round trip per question |
410
- | `e2e-selection.ts` | what a locator SELECTS, as data, and the one in-page expression that resolves it |
411
- | `e2e-evaluate.ts` | the closure→string crossing, which is the only lossy edge in the adapter |
412
- | `e2e-errors.ts` | one constructor per refusal |
413
- | `e2e-dom-fixture.ts` | a document small enough to hold in a test and real enough to RUN the expressions above |
414
- | `cdp-browser.ts` | the two doors: `openE2eBrowserIfAvailable()` (undefined when there is no browser) and `openE2eBrowser()` (refuses by name), and the close that undoes both halves |
415
- | `cdp-launch.ts` | which Chrome, and starting it — the candidate list, the flags, and the endpoint read off its stderr |
416
- | `cdp-connection.ts` | CDP over Bun's own `WebSocket`: request framing, reply correlation by `id`, one-shot event waiters, the per-call deadline |
417
- | `cdp-e2e-session.ts` | the BROWSER half, `E2eSession`: every target auto-attached at browser level and PAUSED until its Network domain is on (a SharedWorker opens its socket at start-up); `newTab()`, `addInitScript()`, `offline()` for every page and worker including later ones, `setCookie()`, and the log of every WebSocket (`sockets()`) and request (`requests()`) in any realm. A page's own workers attach under it UNPAUSED — paused there, the emitted service worker never took control (measured, `e2e/service-worker.e2e.test.ts`) |
418
- | `cdp-e2e-page.ts` | one TAB, `E2eTab`: `E2eBrowserPage`'s five methods plus `reload`, `waitFor`, `indexedDbNames`, `close`. `offline()` forwards to the session — the switch is browser-wide |
419
- | `e2e-app.ts` | `startE2eApp({ root, mode: 'dev' \| 'serve', seed })`: reset + seed + spawn on a THROWAWAY `ULTIMATE_STATE_DIR` (database, disk, dev lock) and a free web AND metrics port, `/readyz`-gated, `stop()` removes the directory. Never the developer's `.x/pgdata`; two apps and an `x dev` coexist. Measured: `examples/dummy` up in ~12 s |
420
- | `e2e-preload.ts` + `e2e-browser-handle.ts` | the `e2e` step's own preload: with `ULTIMATE_E2E_ROOT` set (`E2E_ROOT_ENV`, by `verify-e2e.ts`) it spawns that app on a throwaway database, opens ONE browser, `installE2eDriver`s its first tab, and publishes the whole `E2eBrowser` for `e2eBrowser()` and the app for `e2eApp()`; `e2eBaseUrl()` is a function answering the spawned app's origin, never an env var |
421
- | `verify-e2e.ts` | `withE2eApp`: in an APP on a machine with Chrome, the `e2e` step spawns the app and runs the suite with `--preload e2e-preload.ts`. No browser or not an app: the suite runs exactly as before. Until this, no app ever installed a driver, so every `e2eTest` skipped and every `page` fixture refused |
422
- | `cdp-errors.ts` | one constructor per way the browser half refuses |
423
-
424
- **Absent by default, and that is a requirement rather than a state.** Nothing here runs until
425
- `installE2eDriver` is called, so `hasE2eDriver()` still answers `false` and the gate's `e2e` step
426
- still refuses instead of passing over a browser it does not have. This paragraph also said "CI has
427
- no Chrome" until 2026-08-27, and that is false and was the reason issue #390's fourth requirement
428
- — a real browser check — was recorded as out of reach: GitHub-hosted `ubuntu-latest` ships one at
429
- `/usr/bin/google-chrome`, preinstalled, with no download step and no new dependency.
430
-
431
- **The browser is RAW CDP over Bun's own `WebSocket`, and carries no dependency.**
432
- `packages/scraping/src/cdp-port.ts` declares a ~25-method port because `ScrapePage` is a full
433
- scraping surface and its intended implementation is `puppeteer-core`. `E2eBrowserPage` is FIVE
434
- methods, and CDP's wire format is one JSON object with an `id` — so the whole thing an e2e driver
435
- needs is four small modules, which is why `x test e2e` needs nothing installed that `bun install`
436
- did not already put there. `e2e/cdp-browser.e2e.test.ts` drives a real Chrome against a real
437
- `Bun.serve` and asserts all five methods; `openE2eBrowserIfAvailable()` answering `undefined` is
438
- what makes it a SKIP on a laptop without one rather than a red step.
439
-
440
- **The load EVENT is the completion signal, never `Page.navigate`'s reply.** Measured on Chrome 150:
441
- a navigation that swaps the render process — `about:blank` → `http://localhost:<port>/`, the most
442
- ordinary one there is — loads the page, hits the server and answers a later `Runtime.evaluate` from
443
- the new document, and the navigate frame **never comes back at all**. A driver that awaited the
444
- reply waited out its full deadline on every first navigation. So `cdpConnect().once()` registers a
445
- `Page.loadEventFired` waiter BEFORE the send, and the reply is raced against it — still read, but
446
- only for `errorText`, which is the one place a refused navigation is named.
447
-
448
- **A CDP call is deadlined and a close settles every call in flight.** Without that, a suite whose
449
- browser died waits out one full deadline per call and reports a timeout, where the true fault is a
450
- dead browser. The four codes are four repairs, which is why they are not one:
451
- `X_CDP_BROWSER_MISSING` (install one), `X_CDP_LAUNCH_FAILED` (read the browser's own stderr, which
452
- the cause carries), `X_CDP_CALL_FAILED` (look at the page), `X_CDP_TIMEOUT` (raise the deadline).
453
-
454
- **`evaluate` is the edge that cannot be lossless.** `PageLike.evaluate` takes a closure and every
455
- browser port in this framework takes a string, so what crosses is `Function.prototype.toString()`
456
- and nothing else. A zero-parameter closure naming only page globals is supported; a native or bound
457
- function, a declared parameter and a method shorthand are refused STATICALLY, before a byte leaves;
458
- a binding the page does not have comes back named, from the page's own `ReferenceError`. Measured on
459
- Bun 1.3.14 and 1.4.0 alike — re-measured on both when the repo moved back to the 1.3 series, because a version-stamped claim that names one runtime is unread evidence on the other — and load-bearing: **Bun's transpiler folds `wanted === 3` to `!0` before `toString()` ever
460
- runs**, so a captured PRIMITIVE can vanish from the source and never fail at all, while a captured
461
- reference always survives as its name. No static rule in this process can see the difference — which
462
- is why the refusal is raised from the page's answer rather than from a scan of the source.
463
-
464
- **One of `E2eFixtures`' four members still refuses, and it is the one that is not a port gap.**
465
- `update()` needs a second build served under a new immutable build id, which is a fact about the
466
- SERVER, and no page port has ever been able to speak for one. `offline()`/`online()` FORWARD — to
467
- `E2eBrowserPage.offline`, which `cdp-e2e-page.ts` implements as
468
- `Network.emulateNetworkConditions` and `@ultimat3/scraping` implements through
469
- `CdpPageLike.setOfflineMode`. They refused until 2026-08-27 on a reason the tree contradicted on
470
- the day it was written. A fixture that silently no-opped would make the assertion after it read as
471
- proof — `offline()` followed by "the fallback rendered" is the app's ONLINE page passing an offline
472
- test — so an `E2eBrowserPage` that declares no `offline` still gets the refusal, now naming the
473
- method the double is missing rather than a capability the framework does not have.
474
-
475
- ## The service worker is emitted here, because the emitter needs facts only a build has
476
-
477
- `@ultimat3/pwa` shipped `generateServiceWorker`, `buildPrecacheManifest`, `offlineFallbackSource`,
478
- `backgroundSyncSource` and `pushSource` since it existed, and every one had **zero callers** outside
479
- its own package. So `pwa.offline`, `pwa.backgroundSync`, `pwa.push` and every route's own `offline:`
480
- were declarations with no build behind them, and no Ultimate app worked offline however its config
481
- was written (#390). `sw-artifacts.ts` is the caller.
482
-
483
- **Why here and not beside the manifest.** `loadPwaArtifacts(root)` needs a root and a config file;
484
- the worker needs the ROUTE TABLE and the ISLAND BUNDLE as well — facts only a booted app and a
485
- finished build have. Splitting them keeps `loadPwaArtifacts` callable before either exists, which
486
- `x doctor` and the icon writer rely on. The route table is `describeRoutes()`, the one projection
487
- `x.manifest.json`, `/_x`, the sitemap and `sw.js` are all built from, so a route added to the app
488
- cannot be missing from the precache manifest.
489
-
490
- | Surface | What it does with the worker |
491
- |---|---|
492
- | `cmd-dev.ts` | mounts `/sw.js` and `/x-sw-register.js`; built ONCE at boot and deliberately not rebuilt on the watcher tick — a worker that changes under a page it already controls is the update path, and re-emitting one per keystroke exercises it on every save |
493
- | `serve.ts` | the same two routes in the container, from the same call |
494
- | `prerender.ts` | writes both as FILES into the export — a static host runs no route table, so a `<script src="/x-sw-register.js">` in every document is a 404 unless the bytes are in the artifact. In TWO halves, `As of 2026-09-06`: `serviceWorkerHead(pwa)` before the render loop, because every document has to name the script, and `serviceWorkerArtifacts` AFTER it, because a precache revision is the content hash of a document that does not exist yet |
495
-
496
- **A precache revision is the DOCUMENT's content hash, and until 2026-09-06 it was the build id.**
497
- `pwaRoutes` projected four of `PwaRoute`'s eight fields, so every route entry read
498
- `{"url":"/","revision":"<buildId>","bytes":0}`: two deploys of a byte-identical site re-fetched
499
- every precached page, which is the one thing `packages/pwa/src/precache.ts`' own header says must
500
- never happen, and `DEFAULT_PRECACHE_WARN_BYTES` was a 5 MB budget over a total that could not count
501
- one byte of HTML. The cause was ORDER — `prerender.ts` emitted the worker before the render loop —
502
- so the emission moved after it and `ServiceWorkerInput.documents` carries `renderStatic`'s own
503
- `hash` and byte count per rendered path. `x dev` and `serve.ts` pass none and keep the build id:
504
- neither has rendered a page at boot, and inventing a hash for bytes that do not exist is a revision
505
- that never changes when the page does. **The offline document is fed through its own pair**, because
506
- `buildPrecacheManifest` adds it ITSELF as `reason: 'fallback'` ahead of every route and `add()`
507
- keeps the first entry per url — so the route entry of the same url is shadowed and only
508
- `offlineFallbackRevision` / `offlineFallbackBytes` (`ServiceWorkerConfig`, `@ultimat3/pwa`) can
509
- decide it. That pair was declared and fed by nobody for one commit; `serviceWorkerArtifacts` reads
510
- the fallback path out of the same `documents` map, so the one page an offline navigation depends on
511
- is content-addressed like every other. A fallback this pass did not render — no route serves it,
512
- which `x doctor` reports as `X_PWA_NO_OFFLINE_FALLBACK` — is absent from the map and keeps the
513
- build id.
514
-
515
- **Registration is an EXTERNAL script, never inline**, and that is a CSP fact rather than a
516
- preference: `startWeb` computes a `script-src` sha256 per inline script, so an unhashed one is
517
- blocked in the container while passing report-only under `x dev` — which is how the hydration
518
- runtime shipped broken once already.
519
-
520
- **`sw.js` is served `no-store` with `Service-Worker-Allowed: /`.** A cached `sw.js` is a worker that
521
- cannot be replaced: the browser re-fetches it to decide whether an update exists, and an
522
- intermediary answering the old bytes pins every client to the deploy that shipped them. Without the
523
- header the browser refuses to let a worker served from `/` control `/` — the failure `assertScope`
524
- cannot see, because the scope a REGISTRATION asks for has to be allowed by the script's own response
525
- and not only by its path.
526
-
527
- **`api/` and `shared/` never cross.** An API response is a JSON document whose freshness is the
528
- app's business, and precaching one serves a stale answer to a client that had a network; `shared/`
529
- is not a URL at all. The filter is a `flatMap` rather than `filter().map()` because the predicate
530
- does not narrow `surface` for the map that follows it, and a cast would hide the day a fifth surface
531
- arrives.
532
-
533
- **`pwa.push` is read and still wires nothing, and it says so.** `generateServiceWorker` emits a push
534
- handler only when a VAPID key comes with the capability, there is no `pwa.vapid` config key, and it
535
- drops the handler in SILENCE otherwise. `pushWarning` is this module's own finding, reported through
536
- `x build --json`'s `serviceWorkerWarnings` — `jobs.driver`'s shape one package over, refused the same way.
537
-
538
- **The browser check is what let any of this ship.** #390's fourth requirement was *"a real browser
539
- check that the emitted worker installs, activates and serves the fallback offline. Until it exists,
540
- do not ship the worker"* — a bad `sw.js` is sticky in a way a manifest is not.
541
- `e2e/service-worker.e2e.test.ts` registers the emitted file in a real Chrome, waits for it to take
542
- control, takes the network away, and asserts that a runtime route with nothing cached renders the
543
- offline document.
544
-
545
- **And it found the driver bug first.** `E2eFixtures.offline()` did not take the SERVICE WORKER
546
- offline: a worker fetches on its own CDP target, the condition was only ever set on the page's, and
547
- a `networkFirst` route the cache had never seen still answered from the network. So an offline
548
- assertion made on a PWA tested nothing. `cdp-e2e-page.ts` now auto-attaches worker targets and
549
- carries the condition onto each, including one that attaches AFTER `offline(true)` — the ordinary
550
- case for a PWA.
551
-
552
- ## The `errors` step enforces the error contract
45
+ | `verify-checks.ts` / `verify-step.ts` / `verify-run.ts` | the step list, the outcome shape, the run order (`BESIDE_SERIAL_SUITES`) |
46
+ | `verify-floor.ts` | `x.verify.json`: a floor step that ran zero tests is `X_VERIFY_SUITE_VANISHED` |
47
+ | `load-findings.ts` | module-load failures are reported once, by `manifest`; other steps point there |
48
+ | `boundary-findings.ts` / `app-boundaries.ts` / `boundary-cuts.ts` | surface + layer rules; a finding's `fix:` is the concrete cut |
49
+ | `guards.ts` | an app's `guards/*.ts`, discovered, run on `boundaries`, held to the error contract |
50
+ | `error-contract.ts` / `ts-scan.ts` / `fix-scan.ts` / `fix-imports.ts` / `fix-command.ts` / `fix-path.ts` | the `errors` step: every `fix:` names a runnable command, call or existing file |
51
+ | `workspace-checks.ts` / `workspace-graph.ts` / `tsconfig-references.ts` | `package-shape`, `filesize` |
52
+ | `app-permissions.ts` / `permission-grants.ts` | `policy`: every permission granted or required is declared, and every one required is granted by some role (`X_PERMISSION_UNGRANTED`) |
53
+ | `job-registration.ts` | `manifest`: no job or task under a positional `anonymous-*` name (`X_JOB_UNREGISTERED`) |
54
+ | `async-pages.ts` / `live-routes.ts` / `budgets.ts` | `budgets` riders: an async `Page` with no `load`, a live read no island imports, bytes per route |
55
+ | `app-agents-md.ts` / `app-env.ts` / `app-openapi.ts` / `app-manifest.ts` | `manifest` and `contract-diff` |
56
+ | `schema-drift.ts` / `drift.ts` / `db-destructive.ts` / `db-ungeneratable.ts` | `drift`: snapshot vs declarations, the source hash (core's `canonicalJson`), the header markers |
57
+ | `i18n-registration.ts` / `i18n-audit.ts` | `i18n` |
58
+ | `error-unthrown.ts` | host check: a registered code nothing throws must say so |
59
+
60
+ ### Generators (`x g`, `x new`)
553
61
 
554
62
  | File | Job |
555
63
  |---|---|
556
- | `ts-scan.ts` | the masking every scan shares, the `X_*` codes a file declares, and the ones it says it borrows |
557
- | `fix-scan.ts` | the strings a `fix:` can evaluate to: under a key, at a factory's argument, at a class constructor's |
558
- | `fix-imports.ts` | which of those factories a file can call that it did not declare — one relative specifier, one file read |
559
- | `error-contract.ts` | the rules, the two checks that turn them into findings, and `collectDeclaredCodes` |
560
- | `fix-command.ts` | resolving an `x <command>` a `fix:` cites against the registry |
561
- | `fix-path.ts` | resolving a PATH or a glob a `fix:` cites against the root the gate is running in |
562
- | `source-files.ts` | which files are shipped source — shared with `filesize`, never a second list |
563
-
564
- **A `fix:` may not cite a command this build does not ship.** Six shipped fix lines named
565
- `x db status`, `x logs tail`, `x trace`, `x metrics`, `x auth whoami` and `x ai prompts`, and every
566
- one passed — the text rule checks that a fix NAMES a command, never that the registry holds it.
567
- `fix-command.ts` resolves the citation, and a PLANNED command fails too: `x logs` parses, `x help`
568
- lists it, and running it hands the reader `X_NOT_IMPLEMENTED` instead of the fix.
569
-
570
- **A `fix:` may not cite a file this repo does not have, either.** That was the other half, and
571
- nothing resolved it: `X_UI_RUNTIME_MISSING` told its reader to paste a line no generator ever wrote,
572
- through every gate since it shipped (#274, #246). A file token is one of the four things that make a
573
- fix an instruction at all (`COMMAND_TOKENS`), so `fix-path.ts` is built from the SAME extension list
574
- — a token that satisfies the instruction rule is exactly the token this one has to resolve, and two
575
- lists would be a citation the second rule cannot see. `X_ERROR_FIX_PATH_MISSING` is its own code:
576
- `X_ERROR_FIX_INVALID` means the fix is not an instruction, this one means it is one and points at
577
- nothing, and the repairs differ.
578
-
579
- It is narrow so a finding never has to be argued with — three shapes are not judged at all, because
580
- each resolves against something other than the root the gate is running in: a scoped specifier
581
- (`@ultimat3/ui/global.scss`, which resolves through `node_modules`), a dot-relative path
582
- (`./global.scss`, which resolves against the reader's own file) and any path whose **parent
583
- directory** this root does not have (`src/errors.ts`, `apps/web/server.ts`,
584
- `packages/i18n/catalogs/en.json` — all three name a directory a generated app has and this repo does
585
- not). What is left is the citation a root really can answer: a directory that exists, named as
586
- holding a file it does not hold. A glob must match at least one file. Measured over all three roots
587
- the gate runs in — the framework, `examples/dummy`, `dummy/social-media-clone` — **117 path citations
588
- read, 0 findings**, so it enforces outright with no pin table.
589
-
590
- The rule is **conditional, and that is load-bearing**: *if* a fix cites `x <command>`, it must
591
- resolve. It does not require every fix to name one — `set OTEL_EXPORTER_OTLP_ENDPOINT=…` and
592
- `counter('orders_total', { maxSeries: 4000 })` are executable and correctly cite nothing, and a
593
- universal rule would push an author into citing a command that does not really fix it. A second
594
- word is judged as a subcommand only when the spec declares subcommands, or `x new my-app` reports
595
- `my-app` as one. The registry arrives through `await import('./registry')` — `registry → cmd-verify
596
- → error-contract` closes a cycle back to the caller, and the precedent for the break is
597
- `cmd-build.ts`.
598
-
599
- **It reads a THIRD word, under the same condition.** `x db branch ls --json` resolved — `db` is a
600
- command, `branch` is one of its subcommands — and the word that decided what actually ran was never
601
- looked at, so a fix line that created a stray database passed every check the repo had. A third
602
- word is judged only where the subcommand declares a closed set (`CommandSpec.subcommandPositionals`,
603
- declared from the constant the command validates against), because `x jobs show <id>` and
604
- `x db gen "add publish_at"` take open positionals and a universal rule would report findings about
605
- working invocations. `positionalChoices` cannot express it: `fix-command.ts` reads that field only
606
- where a command declares no subcommands at all.
607
-
608
- **A command with no subcommands must still declare `positionalChoices`, or its second word is unjudged.** `x g migration` shipped in two `@ultimat3/admin` fix lines — a generator that has never existed, answered with `X_CLI_UNKNOWN_COMMAND` when run — because the `g` spec declared none and `fix-command.ts` returns `undefined` for an absent set. The third instance of this class (#131, then `x db branch <name>`). `cmd-generate.ts` now declares `positionalChoices: GENERATORS`, from the SAME constant `readKind` validates against, exactly as `cmd-test.ts` declares `TEST_TYPES` — `fix-command.test.ts` pins `x g migration` as a finding against the real catalog. A new command whose first positional is a closed set and does not declare it is a hole this rule cannot see.
609
-
610
- **And in that one slot, a `<placeholder>` is a finding too.** `x db branch <name>` is what two
611
- `@ultimat3/mcp` fix lines said; the citation reader does not read `<name>` as a word, so the slot
612
- was never examined and the line resolved clean while running it answers `X_CLI_UNKNOWN_COMMAND` —
613
- the same blind spot in a second disguise. A closed set means the slot is a verb, so there is
614
- nothing a reader could substitute that would make it run. `CITATION` therefore matches a
615
- placeholder in the third slot **only**: `x jobs show <id>` and `x db branch drop <name>` are correct
616
- fix lines and must stay invisible to this rule.
64
+ | `cmd-generate.ts` / `generate-files.ts` / `generate-kinds.ts` / `generate-write.ts` | argv → pure file list → writes (conflict-checked, `merge: 'json'` / `'if-absent'`) |
65
+ | `generate-feature.ts` | a `--feature` naming no slice is `X_FEATURE_UNKNOWN`; nothing invents an entity |
66
+ | `generate-grants.ts` | a written `policy.ts` grants `:read` to `member`, `:write` to `admin` in `apps/web/shared/roles.ts` |
67
+ | `api-registration.ts` | a written job or task is listed in `apps/web/api/index.ts` |
68
+ | `app-artifacts.ts` | `x.manifest.json` + `openapi.json`, written together by `x g` and `x manifest` |
69
+ | `templates/` | every emitted file; `scaffold-*.ts` for `x new`, one file per generator otherwise |
617
70
 
618
- `collectDeclaredCodes` is the only answer to "which codes exist, and where is each declared?" — one
619
- walk, one entry per code, the owning registry preferred over any throw site and over a registry
620
- that named the code in its `<PKG>_BORROWED_ERROR_CODES`. The docs check reads it and so does the
621
- framework's own `framework.manifest.json`, because a second scanner over a narrower file set is a
622
- manifest that claims completeness it does not have.
623
-
624
- **A `code:` is a literal, a module-scope const in the same file, or a finding** — `As of 2026-08-23`,
625
- and until then it was a literal or silence. `scanCodes` matched `code\s*[:=]\s*'X_…'`, so
626
- `const STALE = 'X_DOC_PACKAGE_GRAPH_STALE'` followed by `code: STALE` — the DRY thing to write, and
627
- what `scripts/package-map-graph.ts` really wrote — was a declaration to nobody: no manifest row, no
628
- row demanded on `wiki/Error-Codes.md`, no entry for `bun run gate-codes`, and `x errors explain`
629
- answering `X_ERROR_CODE_UNKNOWN` for a code the build throws. Silent, and in the **permissive**
630
- direction: the DRYer the author, the less the gate saw (#277).
631
-
632
- `scanCodeDeclarations` is that one pass, and it returns both halves. It resolves the identifier
633
- against the module-scope consts of the **same file** — anchored at column 0, which is what makes it
634
- module scope without a parser — and reports every name it cannot resolve as `X_ERROR_CODE_UNRESOLVED`
635
- rather than skipping it, which is the whole point: a scanner that reads only what it likes enforces
636
- only what it sees. `scanCodes` is its `.sites`, so the manifest, the docs check, `bun run gate-codes`
637
- and `x errors explain` (through `scanCodeFixSites`, which resolves the same way) cannot see different
638
- sets. Cross-file resolution was **refused** even though `fix-imports.ts` already does the harder
639
- version for `fix:`: it would make the scan async for every caller, and the finding is the better
640
- answer anyway — one file holds both the code and its only spelling.
641
-
642
- Three shapes are deliberately not judged, each measured over the framework and both tracked apps
643
- before the rule shipped. A name that resolves to something that is **not** a code is an answer, not
644
- a gap (`const STATUS_NOT_FOUND = 404` in `@ultimat3/realtime`'s NATS fake, the one live instance). A
645
- **table read** is not judged — `SEO_ERROR_CODES.metaMissing` is how `@ultimat3/seo` and
646
- `@ultimat3/ui` raise all 18 of their codes, and the registry those literals live in already declares
647
- them. A **lowercase** name is not judged: 164 sit at a `code:` position in this tree and every one is
648
- a type annotation (`readonly code: string`) or a re-raise (`code: opts.code`). Measured on all three
649
- roots: **0 findings**, so it enforces outright with no pin table.
650
-
651
- An empty `fix`, or a `fix` that says `check` / `make sure` / `try` / `see the docs` and names no
652
- command, call or file path, is `X_ERROR_FIX_INVALID`. A declared code the host's error reference
653
- does not name is `X_ERROR_CODE_UNDOCUMENTED` — `wiki/Error-Codes.md` here, nothing in a generated
654
- app, which is why that half arrives as a host check (`scripts/verify.ts`) rather than a hardcoded
655
- path in this package.
656
-
657
- `ts-scan.ts` masks comments and string contents before it looks for structure. The contract's own
658
- 3-line rendering appears verbatim in doc blocks and interpolated messages, and a scanner that read
659
- those as declarations would report findings nobody can fix. What it cannot see is a `fix` with no
660
- literal — a parameter, or a table lookup with no fallback. Those are out of a static scan's reach,
661
- and the step says so rather than guessing.
662
-
663
- **A fix does not always arrive under a key**, and until `As of 2026-08` the scanner assumed it did.
664
- `@ultimat3/mcp`'s `readonly-sql.ts` hands every fix positionally to a local `rejected(cause, fix)`
665
- helper, so the file held no `fix:` at all and `scanFixes` returned `[]` for all of it — the
666
- citation resolver was never given a string to judge, and two stale `x db branch <name>` lines
667
- shipped through the hole. `scanFixes` now also reads the argument in the `fix: string` position of
668
- a **local** helper, under four rules, each with its own case in `fix-scan.test.ts`: the helper must
669
- BUILD an error (`code` key or `new …Error(` in its body), or `citedCommandProblem(fix, catalog)` —
670
- which takes a fix to *judge* it — would have its call sites read as declarations; the parameter
671
- list may hold no rest or destructured parameter, because neither has a reliable position; the call
672
- may not be a member access; and the argument must BE one literal, stricter than the key path,
673
- because `prefix + 'x doctor'` reads as one literal there and publishing half a fix is worse than
674
- publishing none. Measured over the whole tree: 16 files gained readable fixes, `readonly-sql.ts`
675
- went from 0 to 7, and **zero** new findings.
676
-
677
- **And a fix does not always arrive in the file that declares its builder**, which is where four
678
- bad `fix:` lines in `packages/ui/src/icons/build-icons.ts` shipped: `invalidIconDataError` is
679
- declared in `packages/ui/src/errors.ts`, and a per-package `errors.ts` full of factories is the
680
- house pattern, so the same-file rule left the most common shape of all unchecked. `fix-imports.ts`
681
- resolves it — the specifier is relative, the candidate paths are `<base>.ts{,x}` and
682
- `<base>/index.ts{,x}`, and the parameter position is the callee's. An alias is renamed to what the
683
- CALLER writes; a local declaration of the same name wins, because that is the function the call
684
- actually reaches. One module cache per run: `errors.ts` is imported by every file in its package.
685
-
686
- **An error CLASS is the same helper one keyword away**, and is now read too: the name is the
687
- class's, the parameter list its `constructor`'s, `new X(…)` is a call like any other. It was
688
- measured as dead code in the same-file rule — zero same-file call sites — and cross-file it is
689
- `@ultimat3/render`'s fourteen classes plus `@ultimat3/core`'s three image ones.
690
-
691
- Measured over the whole tree, `As of 2026-08`: **791 → 877** fix literals read, 37 files gained
692
- one, and **3 findings** the gate had never been able to see — `x verify --contract` and
693
- `x build --route` (two flags no command declares) and one `check …` line with no command token.
694
-
695
- What it still cannot see is a builder imported from another **package**: `candidatePaths` refuses a
696
- non-relative specifier, because resolving one means guessing which of 29 packages a bare name came
697
- from and a wrong guess reads an unrelated function's argument as a fix. Measured: 3 call sites in
698
- this repo, none of them a finding. It is **not** left silent — the step's `output` carries
699
- `checked {n} fix line(s), could not read {m}`, counted at `FixScan.unreadable`: an argument in a
700
- KNOWN fix position that is not one literal. Deliberately not "imports I could not open", which is
701
- 1504 names here and 1310 of them are `join` and `UltimateError` — a number nobody can act on.
702
-
703
- `cli → admin` is a declared sideways edge (`scripts/lib/tiers.ts`): `x dev` **mounts** the
704
- dashboard, it never grows a second one. The CLI's only contribution is the facts no registry
705
- holds — a SQL runner, the caught outbox, the committed manifest, the process's own services, the
706
- spans it recorded — supplied as `defaultDevSources({ hooks })`.
707
-
708
- Wired means answerable: all eleven panels answer in a `x dev` process, and a hook the CLI does
709
- not supply is a panel that refuses with a wiring line, never one that renders empty. `timeline`
710
- is core's tracer (`x dev` is what calls `configureTelemetry`), `cache` is
711
- `recentInvalidations()`, `policy` is `@ultimat3/policy`'s own `policyMatrix()` over the app's
712
- roles — a verdict re-derived here would be the second authz the framework exists to prevent.
713
- `subscribers` is the one source left unwired: `@ultimat3/realtime` retains no matcher trace, and
714
- that trace is the live panel's question, so the panel degrades to its own note instead.
715
-
716
- `dev-traces.ts` reads a span's panel kind off its **name prefix** — a subsystem that starts emitting
717
- spans adds its prefix to `KIND_BY_PREFIX` or its work is filed under `action`. `db.` is there
718
- because `@ultimat3/db`'s two funnels open one span per statement (`db.select`, `db.begin`), and a
719
- statement is the one span that states its own identity — `STATEMENT_ATTRIBUTE`, **imported** from
720
- `@ultimat3/db` by both `dev-traces.ts` and its test rather than spelled as a literal, which the
721
- recorder prefers over the name, so the timeline's `repeatedSql` groups SQL texts and not span names. Those spans
722
- exist only where a `StatementObserver` is installed, so a trace with no DB children is a process
723
- with no statement diagnostic, not a broken recorder.
724
-
725
- `dev-n-plus-one.ts` is that observer, and `cmd-dev.ts` is the **only** place that installs it —
726
- `serve.ts` installs neither it nor the in-process trace RECORDER (`createTraceRecorder`, which is
727
- `/_x/timeline`'s source), the same line that file already draws for `/_x`. **It is not "no
728
- exporter"**, `As of 2026-08`: `serve.ts` calls `startOtlpExport(options.env)`, because a collector
729
- named in the chart has to receive spans from the container and not only from a laptop. What a
730
- production process does without is the *statement* diagnostic and the in-memory timeline — the
731
- ledger and the recorder go in together and come out together in `stop()`, because the timeline's
732
- SQL rows and the repeat counts are one feature with one toggle, and uninstalled the seam costs the
733
- one `undefined` branch it already pays (axiom 6).
734
-
735
- Three rules hold the ledger, each load-bearing. **Per request, keyed by the `Ctx` object** — a
736
- `WeakMap` whose entry dies with the request, so nothing sweeps and nothing accumulates across a dev
737
- session; a statement issued outside a request is not counted at all, because "five of one shape"
738
- only means something inside one unit of work. The price of keying on identity is that a
739
- `withChildContext` scope is its own tally. **A shape is `entity.op` when attributed**, the
740
- statement's own text with whitespace collapsed when it is not — `members.findById` fifty times is
741
- what an author can act on, and grouping fifty point lookups by their SQL would report bind values.
742
- That rule is **not written here**: `statementFingerprint`/`statementKind` are `@ultimat3/db`'s and
743
- the threshold is `@ultimat3/entity`'s `N_PLUS_ONE_THRESHOLD`, because `@ultimat3/testing`'s
744
- `statements` fixture is a second detector and a copy of either would let a loop that fails a test be
745
- a different loop from the one this ledger warns about. What stays here is what only a dev *server*
746
- knows: the request as the unit of work, the bound report list, one log line per request per code.
747
- **An expected statement is not counted** — `expectedQueryLoop` suppresses a verdict and this ledger
748
- is the verdict, so the span and the timeline still show the loop while the thing that warns is told
749
- the author already answered. A shape is promoted to a verdict exactly once, on the statement that
750
- crosses the threshold, and its count keeps rising: a loop of fifty is one report reading fifty. The
751
- report list is bounded and drops its oldest.
752
-
753
- `statement-loop.ts` is the **one** projection those verdicts reach four surfaces through, and the
754
- reason there is only one is that four renderings of one loop must be one sentence. It hands a
755
- verdict to `@ultimat3/entity`'s `nPlusOne()` — the `fix:` speaks that package's vocabulary and is
756
- derived from the relations the schema already declared — and each surface takes a field of what
757
- comes back: `cmd-dev.ts` appends `loopFinding` to the `findings` getter (text and `--json` render it
758
- for free), `dev-dashboard.ts` supplies `statementLoops` so `/_x/timeline` shows `nPlusOne` for the
759
- request on screen, `cmd-dev.ts` again passes `devNotices` down `startRoles` so the browser overlay
760
- renders the loop under the error, and the ledger itself emits `warnLoop` — one `logger.warn` per
761
- request per code, the ids riding along from core's `setLoggerContextFields`.
762
-
763
- Two rules about *when* a count is read. **A surface reads it live**: the finding, the panel row and
764
- the notice all say `ran 50 times` because they ask after the loop finished, while the log line says
765
- `ran 5 times` because it was written the moment the threshold was crossed — same verdict, two
766
- honest moments. **A verdict belongs to its request**: `repeatsFor(ctx)` reads the request's own
767
- tally rather than filtering the bounded global list, so the overlay still names a loop the bound
768
- already dropped. `serve.ts` supplies no `devNotices`, so the seam it boots through is a key that is
769
- absent, not a hook answering an empty list.
770
-
771
- `dev-n-plus-one.test.ts` and `statement-loop.test.ts` drive the ledger and the projection with
772
- hand-built `StatementEvent`s — fast, and enough to pin every rule above. `n-plus-one-detector.test.ts`
773
- proves the loop those events stand in for: real `posts`/`authors` entities, `postgresRepo` and
774
- `createPgliteClient` (an injected fake driver so no `@electric-sql/pglite` build is needed, but a
775
- real client — `createRecordingClient` implements `DbClient` on its own and never reaches the
776
- observer, so it cannot stand in here) — a naive per-row `findById` loop trips `X_N_PLUS_ONE_QUERY`
777
- with the exact `preload('author')` line, the `preload()` form of the same read stays quiet,
778
- `expectedQueryLoop` silences the naive form without stopping it from running, and a naive per-row
779
- `delete` loop trips `X_N_PLUS_ONE_WRITE`. Its describe block spells the pattern `n1`, matching
780
- `packages/entity/src/n-plus-one.test.ts`'s own fixture prefix, because `bun test -t 'n+1'` is a
781
- regex and `+` is a quantifier — `n1` is what actually selects these tests.
782
-
783
- ## One migration engine, four environments
71
+ ### Boot (`x dev`, the container)
784
72
 
785
73
  | File | Job |
786
74
  |---|---|
787
- | `migrations.ts` | the app's `packages/db/migrations` read into `@ultimat3/db`'s `Migration` shape — the **one** reader |
788
- | `db-generate.ts` | `x db gen`: entities diffed against what the migrations declare, written as `.sql` + `.snapshot.json` + `.hash` |
789
- | `cmd-db.ts` | the subcommands, and nothing else — `gen` calls `db-generate.ts`, `migrate`/`reset` call `serve.ts`'s `runMigrations` |
790
- | `db-branch.ts` | what a branch IS: the closed verb set, the name it takes on disk and in `pg_database`, and list/create/drop per mode |
791
- | `cmd-db-branch.ts` | `x db branch`'s wiring alone — which verb, which refusal, and the one connection an external clone runs on |
792
- | `db-finding.ts` | one thrown value → one `Finding`, shared by `cmd-db.ts` and `cmd-db-branch.ts` |
793
- | `db-accept-created.ts` | `acceptCreatedTables`: the post-migrate report minus the tables the applied migrations' own SQL creates — the half `@ultimat3/db`'s `unexpectedTable` names |
794
- | `db-subscribes.ts` | `replicaIdentityTables`: the tables `x db gen` grants `REPLICA IDENTITY FULL`, read off each live query's declared `subscribes:` — and `X_QUERY_SUBSCRIBES_UNKNOWN` for a name no entity's table matches |
795
- | `drift.ts` | `checkSourceDrift`: the `.hash` sidecar the `drift` step compares, no database needed |
796
- | `schema-diff.ts` | what two GENERATED snapshots disagree about, as data — the pure half |
797
- | `schema-drift.ts` | `checkMigrationDrift`: entity declarations against the newest `.snapshot.json`, and the composition the `drift` step and `x doctor` both read |
798
- | `db-destructive.ts` | `checkDestructiveMigrations`: the same step's second half — every committed `up` that drops, truncates or retypes must carry `-- destructive: true` |
799
- | `db-ungeneratable.ts` | `checkUngeneratableMigrations`: the same step's fourth rail — every committed `up` holding SQL `x db gen` could not have written must say how many, as `-- ungeneratable: <n>` |
800
- | `db-backfill.ts` | `x db backfill --list`: the flag parsing, the ledger read and the table |
801
- | `db-seed.ts` | `x db seed`, everything except the argv: `SEED_GLOBS` (where a seed is declared), `discoverSeeds`, `parseSeedTierFlag`, `selectSeeds` (which seeds this invocation runs, and its two refusals — `X_DECLARATION_UNKNOWN` and `X_SEED_ENVIRONMENT`), `runSeeds` (one transaction **per seed**, never one around the run) and the two renderers. The `db-backfill.ts` split repeated: a driver plus plain strings in, plain rows out, so every rule is testable with no `ParsedArgs` and no boot. Which tiers an environment takes is `@ultimat3/entity`'s `seedTiersFor` — two copies of "may this seed run" would be two answers |
802
-
803
- `jobs-driver.ts` is the ONE place a CLI command gets hold of the app's queue — `withJobDriver`,
804
- which `x jobs` and `x db backfill` both call. It reuses an ambient `jobDriver()` when a process
805
- already installed one (inside `x dev` or `x mcp serve`, booting a second queue talks to the wrong
806
- database) and otherwise boots `startQueue` and releases it in a `finally`, or a CLI that exits
807
- holding the PGlite lock breaks the next command run against this app. A second copy of that boot
808
- would be two answers to "which queue is this command talking to".
809
-
810
- **`x db branch` takes a VERB, and a branch name can never be one.** `ls`, `create <name>`,
811
- `drop <name>` — a closed set, declared once in `BRANCH_SUBCOMMANDS` and read three ways: the
812
- command validates against it, `dbCommand.spec.subcommandPositionals` declares it so the `errors`
813
- step can resolve a citation against it, and the refusal for an unknown word lists it. The bare-name
814
- form it replaces is why: the argument *was* the name, so `x db branch ls --json` — the `fix:` on
815
- the planned `x branch`, on `X_DB_BRANCH_FAILED`, and (as `create`/`drop`) on `@ultimat3/db`'s own
816
- `X_BRANCH_EXISTS` and `X_SQL_UNSAFE` — cloned a database called `ls` and returned no listing. All
817
- four passed every check the repo had, because `fix-command.ts` resolved two words and the third was
818
- the one that decided what ran.
819
-
820
- **`drop` has no confirmation flag, and that is the design.** It may only drop what `ls` shows: an
821
- external branch is a database carrying the marker comment `createBranch` writes **and** this
822
- database's own `<source>_branch_` prefix, an embedded one is a `pgdata-<name>` directory, so the
823
- shared database this session is connected to is in neither set.
824
- The typo is impossible rather than the keystroke tedious — and `@ultimat3/db` already ships
825
- `x db branch drop <name>` as `X_BRANCH_EXISTS`'s `fix:` with no flag on it, so a flag here would
826
- break a shipped instruction.
827
-
828
- **The prefix half is not decoration, and it is no longer the only source guard.** The marker records
829
- the base `As of 2026-08-19` — `ultimate:branch:<base>:<iso>`, read back as `BranchInfo.base` — so
830
- `reapBranches` can skip another app's clones on its own. The prefix guard still stands and is what
831
- `ls`/`drop` read, because an **older** marker records no base at all: it is skipped by the reaper
832
- rather than dropped, which leaves `drop` needing an answer that does not depend on a field half the
833
- branches lack. One Postgres server hosting two Ultimate apps answers `listBranches()` with both
834
- apps' clones, and `branchNameOf` reduced `postly_branch_feat` and `analytics_branch_feat` to the
835
- same branch name — so `x db branch drop feat`, run against `postly`, was authorised by
836
- `analytics`'s row and then issued `drop database if exists "postly_branch_feat"` against a database
837
- carrying no marker at all: a `DROP DATABASE` the guard had never approved, and nothing recoverable
838
- about it. `branchNameIn(source, database)` is the source-scoped inverse of `branchDatabaseName` and
839
- the one `ls` and `drop` both read; `branchNameOf` survives for `mcp-db-target.ts` alone, which has
840
- a URL and no connection to ask `current_database()` with.
841
-
842
- **The membership check lives inside `dropExternalBranch`, not in the wiring above it.** One
843
- connection, one listing, one statement before the `DROP` — a listing taken by the caller and acted
844
- on afterwards is two connections and a window wide enough to hold a whole `create`. It is still not
845
- atomic and cannot be: `DROP DATABASE` runs in no transaction, so no single statement both verifies
846
- the marker and deletes. Closing the last gap means a lock around both halves inside
847
- `@ultimat3/db`'s `dropBranch` — which a `psql` at the next terminal would not hold either.
848
-
849
- **`ls` is the reason `create` no longer shells out to `psql`.** `listBranches()` finds branches by
850
- `createBranch`'s marker comment; the `psql` path wrote the `CREATE DATABASE` and no comment, so
851
- every branch the CLI made was invisible to the only lister the framework has. External branching
852
- now runs through `@ultimat3/db` on one `role: 'migrate'` client — `max: 1`, no statement timeout,
853
- both load-bearing: `CREATE DATABASE … TEMPLATE` is refused while any *other* session holds the
854
- template, and cloning a real database outlives a `web` profile's 10s.
855
-
856
- **`DatabaseTarget.production` is a fact this package supplies, and it was the literal `false`.**
857
- `mcp-db-target.ts` is the only place one is ever built, so `assertBranchDatabase`'s first refusal —
858
- "production is never migratable from MCP at all" — could not run for any database the CLI produced;
859
- a production database was refused only incidentally, because its name lacked `_branch_`, and one
860
- named `shop_branch_hotfix` read as a branch and was migratable. It is now core's one key, read the
861
- way `x doctor` reads it. An **unreadable** `ULTIMATE_ENV` counts as production: `tryResolveEnvironment`
862
- answers `undefined` for exactly one input — a value that is not an environment — and a guard that
863
- read a typo as "not production" would be defeated by the misconfiguration it exists to survive.
864
- `staging` stays false; `branch: null` is already what refuses it, and widening the flag would make
865
- the refusal say something untrue.
866
-
867
- `x db backfill` has four shapes and a **dry run is the default**: `--list` reports the ledger,
868
- `--pending` reports declared-minus-completed and exits non-zero when there is drift, `<name>` plans
869
- one sweep, and `--all` plans every pending one. `--write` is never implied — the inspection forms
870
- and the acting form are the same command, and the flag is the only thing that separates them.
871
- `--all --write` isolates per name and continues past a failure, exiting non-zero naming each, so one
872
- wedged cleanup cannot block every later one forever.
873
-
874
- Until 1.2.0 a bare `x db backfill <name>` threw `X_NOT_IMPLEMENTED`, and the ledger was the only
875
- half that existed: `x_backfills` recorded what had run, and **nothing recorded what was pending**, so
876
- a scaffolded backfill could be merged and deployed and silently never run. `--pending` is the alarm
877
- that closes it; `registeredBackfills()` is what makes a declaration visible before its first pass.
878
-
879
- `x db migrate` and `ROLE=migrate` are the same function call. That is the whole design: until
880
- 1.2.0 the CLI shelled out to `bunx drizzle-kit` — a second engine, a second journal, declared in no
881
- `package.json` and fetched unpinned at run time — while the release phase used the framework's
882
- ledger, so "what has been applied" had two answers that only agreed by luck. `cmd-db.test.ts`
883
- holds the line from both ends: no shipped source spawns a second migrator, and this file still
884
- imports `runMigrations` from `./serve`.
885
-
886
- **The post-condition is one check too, and it is the database one.** `runMigrations` runs
887
- `@ultimat3/db`'s `checkDrift()` inside the queue's lifetime — the connection it opened for the
888
- migrator is the only one there is — and returns the report on `MigratedApp.drift`, so a developer
889
- and a release phase verify the same thing. `x db migrate` renders it through `driftFindings` and
890
- exits non-zero; `runRole` throws the first difference for `ROLE=migrate`, so the release phase
891
- exits non-zero too. Both entrypoints call the same `runMigrations` and both fail — the difference
892
- is only the channel each has. `ROLE=migrate` logged and exited 0 until it did not: a release phase
893
- whose only signal is the exit code reported success over a schema nobody can reconstruct, which is
894
- the failure the post-migrate check exists to catch.
895
-
896
- **`x db gen` emits `REPLICA IDENTITY FULL`, and the set is DECLARED rather than derived**,
897
- `As of 2026-08-26` (#357). `@ultimat3/realtime` refuses a live subscription to a table without it —
898
- logical replication carries no old row on an UPDATE, so no patch can be computed — and for two
899
- years nothing in the framework emitted one. It could not be derived, and that is the load-bearing
900
- fact: the relation name lives inside the query's `sql:` callback, which no generator can invoke
901
- without valid input (`describeSql` says so itself — "`null` when no sample input was supplied").
902
- So a live query DECLARES it (`subscribes:`, `@ultimat3/query`), the declaration is machine-checked
903
- against the resolved `shape.entity` on the first subscribe (`X_QUERY_SUBSCRIBES_DRIFT`), and
904
- `db-subscribes.ts` reads it off `describeQueries()` — the same source `frameworkSources` copies onto
905
- `QueryFact.subscribes`, one hop earlier, because building the manifest here would re-load the app
906
- and demand a `package.json` that `x db gen` has never needed.
907
-
908
- **The third `subscribes:` refusal is this package's, because no other tier can ask it.**
909
- `@ultimat3/db` keeps only the declared names an entity's table matches and DROPS the rest — it has
910
- no way to tell a typo from a table another migration owns — and `@ultimat3/query` holds no table
911
- catalog at all. So `subscribes: ['posts', 'user']` granted the identity to `posts`, dropped `user`
912
- in silence, and read as granted. `X_QUERY_SUBSCRIBES_UNKNOWN` refuses it BEFORE anything is
913
- written, naming the query and offering the tables the app does declare. It is checked after
914
- `loadApp`'s findings, never before: a module that would not import leaves the registry short, and
915
- every name whose entity lives in it would then look like a typo.
916
-
917
- **And it accepts a table the migrations it just applied demonstrably created**, `As of 2026-08-26`
918
- (issue #345). A snapshot records only what ENTITIES declare, so a table created by a HAND-WRITTEN
919
- migration reached no sidecar and was `unexpected-table` on every deploy forever — with a `fix:`
920
- that generated an empty migration, because `x db gen` diffs the entity registry against the newest
921
- snapshot and the table is on neither side. `@ultimat3/db` fixed the wording; `acceptCreatedTables`
922
- (`db-accept-created.ts`) is the half that file's `unexpectedTable` names, and it is composed around
923
- `checkDrift` inside `runMigrations`, so `x db migrate` and `ROLE=migrate` accept the same set.
924
- **Only `unexpected-table`, and only for a name a migration's SQL creates** — which is what keeps it
925
- an acceptance rather than the check switched off: a table absent from the snapshot produces exactly
926
- one difference (`diffSchema` reports it and never compares its columns), and a table nobody
927
- declared and no migration created is still reported, cause and `fix:` intact. The evidence is the
928
- applied list itself: `migrate()` runs first, so every file on disk has been applied by the time the
929
- question is asked. The verb phrase is read ANCHORED off the raw statement, which is the whole
930
- protection — a `create table` can only be at position 0 by being one, so `values ('create table
931
- ghost')` opens with `insert` and a comment-only chunk is not a statement at all. A `stripSqlNoise`
932
- pass was written first and deleted: it could not change one answer, and a defence that cannot fail
933
- is one nobody can test. Everything the anchor admits and the name grammar does not — a comment
934
- between the keywords, a `temp` table, a qualifier naming a schema `checkDrift` never introspected —
935
- contributes nothing, which reports drift that could have been accepted and never the reverse.
936
-
937
- **The `drift` step asks a third thing, off the same directory and with no database either: is every
938
- destructive statement declared?** `db-destructive.ts` reads each committed migration through
939
- `migrations.ts` — the reader `x db migrate` applies from, because a rail checking a list the
940
- migrator does not run enforces nothing — and refuses an `up` that drops a table, drops a column,
941
- truncates or retypes without a `-- destructive: true` line, as `X_MIGRATION_DESTRUCTIVE`. It decides
942
- none of that itself: `@ultimat3/db`'s `destructive.ts` owns the classifier `db-generate.ts` already
943
- wrote the marker from, so the generator and the gate cannot disagree about one file. One finding per
944
- file, never one per statement — the marker declares the whole migration. It rides on `drift` rather
945
- than becoming an eighteenth step because it is this step's own question over this step's own files;
946
- a new step is for a genuinely new question.
947
-
948
- **The `drift` step reads the SNAPSHOT, not only the hash, `As of 2026-08-25`.** `checkSourceDrift`
949
- compares a schema-source hash to a `.hash` sidecar and never reads what the migration RECORDED, so
950
- `dummy/social-media-clone` sat green while **nine declared CHECK constraints had never reached any
951
- database** — a comment body could be whitespace, a like count could go negative, an email needed no
952
- `@` — and a squash that dropped ten invariants and nine defaults would have been green too. The
953
- source had not moved, so nothing that hashes source could see it. `checkSnapshotDrift`
954
- (`schema-drift.ts`) diffs `snapshotOf(describeEntities())` against `declaredSchema(readMigrations())`
955
- — both sides are `snapshotOf`'s own spelling, which is what makes a check, a default and a column
956
- type comparable at all. Measured against that app rolled back to the state its gate was green in:
957
- **20 findings**, 9 checks and 11 defaults.
958
-
959
- **Two directions, two codes, because they are two repairs.** `X_DB_SCHEMA_UNMIGRATED` is a
960
- declaration the migrations do not carry — the database will never get it. `X_DB_SCHEMA_UNDECLARED`
961
- is a migration carrying what nothing declares any more, whose fix names BOTH branches, because the
962
- declaration may have been lost rather than removed and `x db gen` would emit the DROP. One "drift"
963
- verdict over both teaches a reader neither.
964
-
965
- **Absent is not empty, and reading it as "recorded none" would fail every existing app on its first
966
- run.** `TableDescription.checks` is absent — never `[]` — on a table declaring none, exactly like
967
- `IndexDescription.using` and `ColumnDescription.generated`. Both sides normalise to empty
968
- (`schema-diff.ts`), `using` reads through `indexMethodOf` and `order` through `?? 'asc'`, or an app
969
- whose sidecar predates any of the three reports a difference on every index it has.
970
-
971
- **The hash half stays, and runs second.** It catches what the snapshot comparison cannot see at all
972
- — a seed, a helper or a TS-only invariant moving under `packages/db/src` with no statement behind
973
- it, which is what `reconcileSchemaHash` exists to re-record. It is SUPPRESSED when the snapshot half
974
- found something: both then answer one condition and only one of them is an instruction, since
975
- `schema hashes to 3f2a, newest migration recorded 91bc` names no constraint, no column and no table.
976
-
977
- **An app whose modules will not import is not judged here.** `appEntities` answers `undefined`
978
- rather than a short registry, which would read as "every table was dropped" and hand out a DROP per
979
- table for one file's syntax error — the same stance `generateAppMigration` takes with its `blocked`
980
- outcome. The cost is that a schema check can be silently skipped under an already-red gate; the
981
- alternative is a false red whose fix destroys data.
982
-
983
- **The FOURTH thing the `drift` step asks is what neither hash nor snapshot can see: SQL no
984
- declaration carries at all.** `ALTER TABLE posts REPLICA IDENTITY FULL;` sits in
985
- `examples/dummy/packages/db/migrations/0001_init.sql`, no generator emits it, no snapshot records
986
- it, and a squash drops it in silence — and no declaration-based check can ever see it, because a
987
- regenerated sidecar equals the declaration by construction. `db-ungeneratable.ts` reports it,
988
- `As of 2026-08-25`. It classifies nothing itself: `@ultimat3/db`'s `ungeneratableStatements`
989
- matches each statement's leading verb phrase against `GENERATABLE_FORMS` — everything
990
- `generateMigration` emits, held honest in both directions by that package's own test — because
991
- every SQL classifier in the tree is db's (`sql-scan.ts`, `statement-split.ts`, `sql-noise.ts`,
992
- `destructive.ts`) and a second one here is the reimplementation this file's own rule forbids.
993
- Measured: **7 statements in `examples/dummy`** (five `CREATE TYPE … AS ENUM`, two
994
- `REPLICA IDENTITY FULL`), **0 in all four `dummy/social-media-clone` migrations** and 0 in
995
- `examples/dummy`'s own generated `0002_money_scale.sql` — real generator output reports nothing,
996
- which is the half a rail like this lives or dies on.
997
-
998
- **The declaration is a header line, and it carries a COUNT: `-- ungeneratable: 7`.** Not
999
- `@ultimat3/db`'s code but this package's (`X_MIGRATION_UNGENERATABLE`), because the only remedy
1000
- available for every statement it reports is a line in the migration file, and where an app keeps
1001
- its migrations is this package's fact — db classifies and deliberately declares no code. Three
1002
- decisions behind that shape:
1003
-
1004
- | Decision | Why |
1005
- |---|---|
1006
- | in the migration file, not a pin table here | the gate runs in every generated app, and a table in `packages/cli/src` can hold no row for an app it has never seen. `-- destructive: true` in the same directory, read by the same reader, is the precedent |
1007
- | a count, not a boolean | the first hand-written statement would otherwise buy the file an unlimited allowance, and statements a reader never sees again are this rail's whole subject. A ratchet in the `README_FENCE_BACKLOG` sense: `found > declared` reports, a declared count that is too high is a pin nobody lowered |
1008
- | the **header** — before the first statement | `hasDestructiveMarker` had to become a lexical scan because a regex over the raw file matched its marker inside a block comment and inside a dollar-quoted body, and `noiseAt` is db's and unexported. A run anchored at index 0 needs no scanner to be exact: before the first statement there is no string, no quoted identifier and no dollar body for a marker to hide in |
1009
-
1010
- **The `fix:` names the marker first and `x db gen` second, and that order is the point.**
1011
- Regenerating is exactly what *discards* these statements, so the command every other db code
1012
- answers with is the one this one must not lead with — `X_MIGRATION_UNGENERATABLE`'s `CLI_FIXES` row
1013
- is `x verify --only drift`, and the re-declare branch (an enum is a text column plus a check
1014
- invariant) rides behind an em-dash because it is available for some of the statements and not all.
1015
- **`REPLICA IDENTITY FULL` was the statement with no second branch, and stopped being one on
1016
- 2026-08-26** (#357): a live query declares the relations it is patched from (`subscribes:`),
1017
- `db-subscribes.ts` reads them off the same registry the manifest is projected from, `x db gen`
1018
- emits the ALTER and `@ultimat3/db` records it on the snapshot so it is emitted once. The re-declare
1019
- branch covers it now: declare `subscribes:` and regenerate. A statement already committed is a
1020
- different question and still counts — `GENERATABLE_FORMS` (`@ultimat3/db`) matches a leading verb
1021
- phrase and does not carry this one, measured at 7 found / 7 declared on `examples/dummy`'s
1022
- `0001_init.sql`, `As of 2026-08-26` — so the marker branch remains the only remedy for SQL on disk.
1023
-
1024
- **`x db gen` reports what it could not write, and exits 0.** `GeneratedMigration.unrendered` reached
1025
- the committed `.sql` as a `-- UNRENDERED` comment and nothing else read it; `db-generate.ts` now
1026
- carries it on every branch (a REQUIRED field, so forgetting to project it is a type error) and
1027
- `cmd-db.ts` prints the count plus each entry's own remedy and carries the list under
1028
- `data.unrendered`. Not a non-zero exit: `x db gen` is the `fix:` on `X_DB_DRIFT` and four other
1029
- shipped errors, and a fix that always exits 1 is not an instruction — the `x i18n add fr` failure,
1030
- repeated. The red belongs at the gate, and the `drift` step reads the SAME list to decide that
1031
- `x db gen` is not the fix it should be handing out.
1032
-
1033
- The *source* half is a different question with a different answer: `checkSourceDrift` hashes the
1034
- entity source against what `x db gen` recorded, answers the same before and after a migration, and
1035
- opens nothing — which is what lets the gate run it in a CI with no database. It stays on `x verify`
1036
- and `x doctor` and is deliberately **not** repeated on `x db migrate`; two reporters of one
1037
- condition is the duplication this package's own rule forbids. Both were called `checkDrift` until
1038
- 1.2.0, and the one that was wired everywhere was the one that cannot see a column added by hand.
1039
-
1040
- Generation opens no database. It diffs `describeEntities()` against `declaredSchema(readMigrations(root))`
1041
- — the snapshot the newest migration wrote down — so `x db gen` answers the same in CI, on a laptop
1042
- with nothing running, and against a database three migrations behind. An app whose modules will not
1043
- load generates **nothing**: a short registry is indistinguishable from deleted entities, and the
1044
- diff would be a DROP nobody asked for.
1045
-
1046
- **An empty diff re-records the `.hash` sidecar, and that is what makes `X_DB_DRIFT` followable.**
1047
- The hash `checkSourceDrift` compares covers every non-test file under `packages/db/src` — a seed, a
1048
- helper, a decorator — not only the ones that imply DDL, and narrowing that glob would trade a loud
1049
- error for a silent gap in the one check that catches "entities changed and no migration was
1050
- generated". So detection stays broad and the REMEDY carries the weight: `x db gen "describe the
1051
- change"` — the exact `fix:` the error hands out — records the current hash against the newest
1052
- migration when the diff is empty, instead of writing nothing and leaving the gate red forever with
1053
- hand-editing a generated file as the only way out. `GeneratedFiles.outcome` is the four things a run
1054
- can be — `generated`, `hash-recorded`, `unchanged`, `blocked` — and `runGen` projects it onto
1055
- `--json` on **every** branch: reporting `hash-recorded` as `generated` would name a migration nobody
1056
- can apply, and reporting it as `unchanged` would hide a file this command wrote. That second one
1057
- shipped: the no-migration branch hardcoded `data: { migration: null, files: [] }`, so the run that
1058
- wrote the sidecar reported writing nothing to the machine reading the output.
1059
-
1060
- Nothing is masked, and the branch proves it rather than promising it: `loadApp` reported no findings
1061
- (the registry is whole, never short), `declaredSchema` returned a real snapshot (`X_MIGRATION_SNAPSHOT_MISSING`
1062
- otherwise), and the emptiness is `generateMigration`'s own verdict — the same call the written path
1063
- takes. A migration with no migration id to record against writes nothing, which is the
1064
- `x new --no-example` case: an entity against zero migrations is `create table` for all of it and
1065
- never an empty diff. `reconcileSchemaHash` also declines to write when an OLDER migration already
1066
- recorded the hash, because `checkSourceDrift` already answers clean there and restamping the newest
1067
- sidecar would claim it produced a schema it did not — one predicate, `isRecorded`, read by both.
1068
-
1069
- One migration is one file, split by a lone `-- down` line. `<id>.down.sql` is a pre-1.2.0
1070
- hand-written layout and `readMigrations` skips it — read as a migration it sorts next to its own
1071
- `up` and drops every table the pair exists to reverse.
1072
-
1073
- ## `x dev` boots the app; it does not simulate one
1074
-
1075
- | File | Job |
1076
- |---|---|
1077
- | `api-routes.ts` | the app's API over HTTP: every registered action AND every registered query |
1078
- | `dev-services.ts` | resolve which service each binding points at — embedded or external |
1079
- | `dev-queue.ts` | the db + queue pair alone, and the one place that takes every ambient accessor back |
1080
- | `dev-runtime.ts` | start the rest on top of it and install the remaining accessors (storage, mail, transport) |
1081
- | `dev-cache.ts` | which cache tiers this process reads through, and the cross-instance invalidation hop |
1082
- | `dev-purge.ts` | the hourly retention sweep: which framework tables this boot owns, the `purge()` job over them and the `task` that fires it |
1083
- | `dev-notify-retention.ts` | `notify.inboxReadRetentionMs` / `inboxUnreadRetentionMs` off the app's own `app.config.ts` — the sibling of `loadSignInPath` and `loadCacheTiers`, because `startServices` holds no `AppConfig` |
1084
- | `dev-sync.ts` | the `sync` role: its live-query registry, who is dialling it, and the socket it owns |
75
+ | `serve.ts` / `serve-boot.ts` / `serve-entry.ts` / `serve-env.ts` | the production entry: roles from `ROLE`, bindings from env, `ROLE=migrate` = `x db migrate` |
76
+ | `cmd-dev.ts` / `dev-route-table.ts` | `x dev`: every role in one process, `/_x`, the watcher |
77
+ | `runtime-bindings.ts` | which service each binding points at (embedded or external); events follow `realtime.transport` / `urlEnv` |
78
+ | `runtime-queue.ts` / `runtime-services.ts` | the db + queue pair; everything else and every ambient accessor |
79
+ | `runtime-jobs.ts` / `runtime-realtime.ts` / `runtime-notify-retention.ts` / `runtime-cache.ts` / `runtime-purge.ts` / `runtime-replica.ts` | `app.config.ts` sections the boot obeys, and what each wires |
80
+ | `role-start.ts` / `role-start-types.ts` / `role-realtime.ts` | `--role` selection and start/stop for `web`, `sync`, `worker`, `scheduler`; `realtime.enabled: false` drops `sync` and `replicator` |
81
+ | `role-sync.ts` / `role-replicator.ts` / `runtime-live-feed.ts` | the sync node, the change feed |
82
+ | `runtime-render.ts` / `runtime-assets.ts` / `runtime-storage.ts` / `runtime-hooks.ts` / `api-routes.ts` | the HTTP surface: pages, `/icons` + `/media`, `/_storage`, authz, the app's API |
1085
83
  | `runtime-overrides.ts` | the one field a host hands the framework a driver through |
1086
- | `sync-authenticator.ts` | the app's HTTP authenticator, seen as the sync node's |
1087
- | `otlp-export.ts` | the exporters `OTEL_EXPORTER_OTLP_ENDPOINT` switches on, and their drain hooks |
1088
- | `dev-render.ts` | one HTTP route per registered `route`, through render's own mode function |
1089
- | `style-csp.ts` | the `style-src` sha256 of every inline `<style>` the web role **still** serves — the app's own surface CSS is a file under `/styles/` (`style-bundle.ts`) admitted by `'self'`, so a production boot extends the directive with nothing |
1090
- | `script-csp.ts` | the `script-src` sha256 of every inline `<script>` it serves — the hydration runtime, from `@ultimat3/render`'s own `HYDRATE_RUNTIME_BODIES` |
1091
- | `dev-assets.ts` | the image pipeline's only HTTP surface: `/icons/*` and `/media/*` |
1092
- | `favicon.ts` | `/favicon.ico`: the app's own file, and the bytes the framework answers with when there is none |
1093
- | `dev-hooks.ts` | the pipeline's `authorize` seam, decided from the app's own `Policy` objects |
1094
- | `dev-replica.ts` | which boot gets a standby, and the one middleware frame that opens the read scope |
1095
- | `dev-replicator.ts` | the `replicator` role: the feed selected, locked and pumped — and `replicatedRelations()`, the entity TABLES it filters on |
1096
- | `dev-roles.ts` | `--role` selection plus start/stop for `web`, `sync`, `worker`, `scheduler` |
1097
- | `dev-dashboard.ts` | the `DevSources` hooks only this process can answer, and the two CLI panels |
1098
- | `dev-traces.ts` | core's spans → the `/_x` timeline's request traces |
1099
- | `dev-n-plus-one.ts` | statement shapes counted per request, and the ones that repeat past the threshold |
1100
- | `statement-loop.ts` | one verdict → the finding, the panel fact, the overlay notice and the log line |
1101
- | `dev-policy.ts` | which actors to ask about, and which capability each policy gates |
1102
- | `cmd-dev.ts` | boot order, mounting `/_x`, installing the span exporter, the file watcher |
1103
- | `dev-watch.ts` | which paths under the app root `x dev` may not watch: the app's own `.gitignore` (`gitignore.ts`) plus a floor of five directory names an ignore file cannot be relied on to carry — `.git`, `.x`, `node_modules`, `.personal`, `.claude` — every one of them dotted or an install, matched as a path SEGMENT at any depth |
1104
- | `dev-watch-tree.ts` | the watch SET: one `watch(dir, { recursive: false })` per ADMITTED directory, a new directory picked up on `rename`, a removed one giving its descriptor back, and the 30ms trailing debounce |
1105
- | `dev-reload.ts` | one rebuild at a time — a tick arriving mid-build coalesces into ONE trailing rebuild, for the newest file |
1106
- | `gitignore.ts` | what git ignores, as data: parse, the ancestor chain up to the repository, and last-match-wins |
1107
- | `path-segments.ts` | `hasPathSegment` — a directory of that exact name, never a substring of one |
1108
- | `style-bundle.ts` / `style-routes.ts` | a surface's CSS as one content-hashed file under `/styles/`, served `immutable` — `island-bundle.ts` / `island-routes.ts`' shape one asset over. It was an inline `<style>` until 2026-09-06: 156,738 bytes, identical on every page, inside a `private, no-store` document. The URL is the hash alone, no surface in the name: a surface is not a property of the bytes, and an app whose only CSS is its global layer would otherwise write three identical files into its static export and three entries into a precache manifest that has a budget |
1109
- | `mcp-host.ts` | the `DevCapabilities` half of `@ultimat3/mcp`'s `DevHost` — db, tests, logs, verify |
1110
- | `mcp-db-target.ts` | which database the host is pointed at: whether it is a branch, and whether it is production |
1111
- | `mcp-errors.ts` | `errors.explain`: one runnable command per code, typed over `CliErrorCode` |
1112
- | `error-catalog.ts` | imports every `@ultimat3/*` package so `x errors` answers for codes no command loads |
1113
- | `mcp-test-output.ts` | reading `bun test`'s own summary back into a `TestRun` |
1114
- | `cmd-mcp.ts` | `x mcp serve`: the two transports, and the local developer's caller |
1115
-
1116
- **The watch set is a REGISTRATION decision, not a filter, `As of 2026-09-06`.** `watch(root, {
1117
- recursive: true })` takes one inotify descriptor per directory in the tree — measured on `x dev`
1118
- against `examples/dummy`: **110 descriptors, 39 of them (35%) under `.x/` and `node_modules/`**, and
1119
- on a monorepo root **1901, of which 1490 (78%) were `.git/` and `node_modules/`**, where one
1120
- `git status` delivered 5 `.git/index` events into the JS callback. `isIgnoredPath` answered
1121
- correctly every time and answered too late: the kernel queue entry, the callback and the slot out of
1122
- `max_user_watches` (8192 on many distributions) were already spent. Bun 1.4.0's `fs.watch` has no
1123
- ignore option, so `dev-watch-tree.ts` walks the root itself and registers one non-recursive watcher
1124
- per admitted directory, pruning at descent.
84
+ | `script-csp.ts` / `style-csp.ts` / `style-bundle.ts` / `page-sync.ts` / `worker-bundle.ts` | CSP hashes, the CSS file, the page's sync target and worker |
85
+ | `island-bundle.ts` / `island-store.ts` / `island-realtime.ts` / `solid-loader.ts` | islands: one `Bun.build` each, source-addressed; `x build --target docker` writes a verified store the container loads |
86
+ | `dev-*.ts` | `x dev` only: dashboard sources, traces, the N+1 ledger, the watcher, the reload, the lock, the port |
1125
87
 
1126
- **And the ignore set is the app's own `.gitignore`.** Seven hand-listed directory names were wrong
1127
- in both directions. Nothing read the ignore file, so `touch tsconfig.tsbuildinfo` — the file every
1128
- `bun run typecheck` rewrites, named by `x new`'s own scaffolded `.gitignore` — logged
1129
- `reloaded tsconfig.tsbuildinfo in 113ms`, a full `appManifest()` plus `buildIslands()`; on the
1130
- framework root 54 git-ignored directories were unfiltered. And `dist` and `coverage` were matched at
1131
- ANY depth, so an app's own `/dist` or `/coverage` ROUTE never reloaded — silently, which the file's
1132
- own header calls worse than a spurious rebuild. `gitignore.ts` reads it with git's own anchoring
1133
- (unanchored at any depth, a leading `/` or an inner slash where it is written, `!` re-including,
1134
- a trailing `/` directory-only) and walks ANCESTOR ignore files up to the directory holding `.git` —
1135
- `examples/dummy` carries none of its own and every rule about it lives in the repository root's. The
1136
- file is re-read, and the whole watch set re-walked, on any write naming `.gitignore`; nothing ever
1137
- spawns `git check-ignore`. `x new`'s scaffold writes `/dist/` and `/coverage/` **root-anchored** for
1138
- this reason, plus `packages/*/dist/`. It is `fix-path.ts`'s parser, lifted: two readers of one file
1139
- are two answers to what an app committed.
1140
-
1141
- **A watcher event carries no filename when the WATCHED directory itself moves.** Bun's recursive
1142
- watcher delivers `undefined` — not `null` — on `mv myapp myapp2`, a re-clone or a volume remount,
1143
- and `isIgnoredPath(undefined)` threw a `TypeError` inside an fs callback, outside any `try`, with no
1144
- `uncaughtException` handler: `x dev` died with a stack trace. The listener is total over
1145
- `string | Buffer | null | undefined` and logs `dev.watch.unnamed_event` once.
1146
-
1147
- **The reload has an in-flight guard, and it is the state that needed it.** A 45ms drip — a slow
1148
- `git checkout`, a formatter walking files, `x db gen` — measured **40 reloads for 40 files**, each
1149
- launching `Promise.all([appManifest, buildIslands])` while the previous still ran and assigning
1150
- `state.manifest` / `state.islands` in COMPLETION order, so a slower earlier tick could land on top
1151
- of a newer one. `coalesceReloads` (`dev-reload.ts`) keeps the LAST tick that arrived during a
1152
- rebuild and starts exactly one more.
1153
-
1154
- `api-routes.ts` is the app's own API surface, composed **once** and mounted by both `cmd-dev.ts`
1155
- and `serve.ts`: `listActions().map(toRoute)` from `@ultimat3/action` plus
1156
- `listQueries().map(toQueryRoute)` from `@ultimat3/query`. Two lists is how `query.client()`
1157
- shipped deriving `/_x/query/<kebab>` against a route neither file mounted — a typed read that
1158
- compiled everywhere and 404'd everywhere — and a surface that answers in `x dev` and not in the
1159
- container is the same failure one release later. It reads the registries at call time, never at
1160
- import: importing the app IS the registration, and it happens after this module loads.
1161
-
1162
- `startWeb` warns when the route table declares `auth: 'required'` and the app configured no
1163
- authenticator: `hooks.authenticate` is the only place an actor can come from, so such a process
1164
- boots clean, reports healthy, and refuses every valid session. A warning and not a throw, because
1165
- `x new` scaffolds guarded routes before it scaffolds an authenticator.
1166
-
1167
- The roles live in `@ultimat3/core` (`ROLES`, `isRole`), never in a second list here. A dev-only
1168
- driver, a dev-only authorizer or a dev-only queue is the bug this design exists to prevent — the
1169
- only thing dev changes is which driver is behind an interface.
1170
-
1171
- ### `HOST` is the interface, read the way `PORT` is
1172
-
1173
- `serve.ts`'s `hostnameFromEnv` — `HOST`, trimmed, empty is `0.0.0.0` — and `ServeOptions.hostname`
1174
- overrides it as `port` overrides `PORT`; `containerBinding(env, hostname)` is the one `WebBinding`
1175
- `serveApp` hands `startRoles`, so `web`, `sync` and the metrics endpoint bind the same interface.
1176
- Before 2026-09-07 `CONTAINER_BINDING` was the only production binding and an app whose auth mode
1177
- admits one implicit actor without a login — which must refuse a public interface — could not run in
1178
- a container at all. A loopback bind in a container is unreachable through `-p`; the wiki row says
1179
- where it IS reachable. Not `HOSTNAME`: Docker sets that to the container id.
1180
-
1181
- ### `RuntimeOverrides` is the only way to hand the framework a driver
1182
-
1183
- `ServeOptions` was `{ root, env, role?, port?, metricsPort? }`, so the ONLY way an app could
1184
- install a driver was an ambient setter at module scope — and `loadApp` imports the app's modules
1185
- *after* `startServices` has captured its own. The slot moved and the capture did not: every
1186
- `handle.enqueue()` went to the app's queue while the worker claimed from Postgres, and `/_x` read
1187
- the ambient one, so the dashboard agreed with the enqueue side and disagreed with reality.
1188
-
1189
- Every field REPLACES the env-selected default rather than sitting beside it — `overrides?.x ?? <the
1190
- env switch>`, one expression, one answer (axiom 1). A field nothing consumes is not there: the
1191
- entity `Driver` in particular, because `@ultimat3/entity` exposes no installer for one
1192
- (`database(entities, { driver })` is the app's own call), and a slot the boot cannot honour is the
1193
- class of defect this seam exists to end.
1194
-
1195
- **The split is refused, not reconciled.** `assertOneJobDriver` runs first in `startRoles` and
1196
- throws `X_RUNTIME_DRIVER_SPLIT` when `jobDriver()` is not the object this process serves. Reading
1197
- through the accessor instead would make the split invisible rather than impossible — and the app
1198
- would still have installed a driver the boot never saw, with no outbox store bound to it and no
1199
- relay draining it.
1200
-
1201
- ### What the boot now calls that nothing called before
1202
-
1203
- | Mechanism | Where | Was |
1204
- |---|---|---|
1205
- | the transactional outbox | `dev-queue.ts` installs the store + facade, `worker` runs the relay | staged rows nothing published |
1206
- | the durable scheduler | `pgSchedulerState` + `createPgLeaseLeader` in `startRoles` | a watermark forgotten on restart, and every replica its own leader |
1207
- | the Postgres event bus | `dev-queue.ts` | `step.waitForEvent` forgot every correlation on restart |
1208
- | the shared idempotency store | `dev-queue.ts` | a retry on another replica charged the card twice |
1209
- | the shared auth limiter | `configureAuthLimiters` in `startServices` | account lockouts counted per POD, so N replicas granted `maxAttempts × N` guesses |
1210
- | the retention sweep | `dev-purge.ts`, declared in `startServices` | three `purgeExpired()` with no caller — every row `x_idempotency`, `x_rate_limit` and `x_auth_*` ever took was kept |
1211
- | the cache tiers | `dev-cache.ts` | only the CDN tier was registered; memo, LRU and Redis had zero callers |
1212
- | WebSocket authentication | `dev-sync.ts` | `actorId: null` on every socket — realtime was single-tenant by wiring |
1213
- | OTLP export | `otlp-export.ts` | the chart set the variable and no code read it |
1214
-
1215
- `createPgLeaseLeader`, never `createPgLeader`: the latter's `pg_try_advisory_lock` is
1216
- session-scoped and the grant dies when the connection returns to the pool, so every node reads
1217
- itself as leader and a rolling update double-fires every task.
1218
-
1219
- The relay runs on `worker` and only `worker` — the role that exists wherever jobs run at all.
1220
- Duplicating it is safe — the claim is a **lease** taken in the statement that locks the row
1221
- (`@ultimat3/jobs`' `outbox-pg.ts`, fenced on `claimed_by`), so two relays never hold one batch —
1222
- but pointless. The idempotency key is not the reason and never was: its conflict target is a
1223
- partial index over live states, so it collapses a repeat only while the first job is still live.
1224
-
1225
- `SQL_IDEMPOTENCY_TABLE` is applied beside `SQL_JOBS_TABLE`, and the store is installed by the boot
1226
- rather than by the app, even though `@ultimat3/action` documents
1227
- `postgresIdempotencyStore({ executor: Bun.sql })`: **`Bun.sql` has no `.query(text, values)`** — it
1228
- is a tagged template whose positional form is `unsafe` — so that line does not satisfy `PgExecutor`,
1229
- and a second executor would open a second pool against a URL this boot already resolved. The app
1230
- owes only the declaration, `configureIdempotency({ scope: 'shared' })`, which `x new` names in
1231
- `apps/web/server.ts`.
1232
-
1233
- The per-TENANT subscription cap is deliberately unset, and **both halves of it are**:
1234
- `assertCapacity` returns early unless `maxPerTenant` AND `tenantOf` are given, so passing one arms
1235
- nothing — and no default is defensible when one tenant is a person and the next is five thousand
1236
- seats. The per-socket 128 stands because a socket is one browser tab.
1237
-
1238
- **The change feed is filtered by TABLE, never by entity name**, `As of 2026-08-26`.
1239
- `replicatedRelations()` (`dev-replicator.ts`) is the one projection, and both of its readers are
1240
- catalog readers: `PgReplicationStream` keeps a change only when `#entities.has(relation.name)` and a
1241
- pgoutput Relation message names the table, while `warnPartialIdentity` matches the same list against
1242
- `pg_class.relname`. An entity NAME is the framework's own registry key — a cache tag, a policy and
1243
- `x entities describe` are all keyed by it — and `entity('user', { table: 'users' })` makes the two
1244
- different strings. It passed `.name`, so a renamed table matched on neither side: **every change
1245
- skipped** and a replica-identity warning that could never fire, with no error anywhere. Invisible to
1246
- every fixture in the tree, because `table` defaults to the name verbatim and all six entities in
1247
- `examples/dummy` have `name === table` — `dev-replicator.test.ts` uses `billingAccount` on
1248
- `billing_accounts` for exactly that reason, and proves the value through the real call chain:
1249
- `assertIdentifier` refuses `billingAccount` before any connection and accepts `billing_accounts`.
1250
-
1251
- `trustProxy` is read from `TRUSTED_PROXY_HOPS` in `startWeb`, the way `PORT` and `ROLE` are read: it
1252
- is a fact about the deployment, not an app config choice, and one image runs behind an ingress in
1253
- one cluster and behind nothing on a laptop. Without it `ctx.ip` is the ingress's socket address on
1254
- every request, so the limiter keys the whole fleet's anonymous traffic into one bucket.
1255
-
1256
- ### The document carries the page's client scope — on private documents only
1257
-
1258
- `dev-render.ts` puts `@ultimat3/render`'s `clientScopeTag(clientScopeOf(ctx.actor))` (`@ultimat3/auth`) in the head of a
1259
- gated `ssr` page and every `stream` — the documents whose own headers say `private`
1260
- (`documentCarriesScope`). A shareable document carries none, because a CDN would hand one visitor's
1261
- scope to the next. `dev-render-scope.test.ts` drives `static`, `isr`, `ssr` (gated and not) and `stream` over the real pipeline. There is
1262
- no island bootstrap: the page client is created lazily by the first transport call or realtime hook
1263
- (plan 101, decided 2026-09-22; `island-bundle.ts`'s header records what an eager one cost).
1264
-
1265
- ### The page's sync target is the framework's, and so is the worker that hosts the socket
1266
-
1267
- `page-sync.ts` is ONE call `cmd-dev.ts` and `serve.ts` both make (plan 101 slice 11, `As of
1268
- 2026-09-22`), so the two boots cannot serve different targets:
88
+ ### Build and data
1269
89
 
1270
90
  | File | Job |
1271
91
  |---|---|
1272
- | `sync-url.ts` | `syncUrlFrom(env)`: `SYNC_URL` verbatim (must be `ws:`/`wss:`, else `X_CONFIG_INVALID` at boot), otherwise the same-origin `SYNC_PATH` — `/_x/sync`, which `x dev`, a combined-role container and the chart's ingress all serve. Never a derived neighbouring port: behind an ingress that is a URL nothing publishes. No app owns a `sync-url.ts` any more |
1273
- | `worker-bundle.ts` | `@ultimat3/realtime/sync-worker`, resolved from the APP root, built as ONE classic-script (`iife`) browser bundle, source-addressed with `island-bundle.ts`'s `graphHash`, served `immutable` at `/_x/sync-worker/<hash>.js`. `undefined` when the app cannot resolve it — no realtime means no socket to share, and the tab-side host falls back in-page. Built at boot only: it is framework code, and a new URL per deploy is what keeps an old tab on the worker it started with. The SAME builder makes `@ultimat3/realtime/boot` at `/_x/page-boot/<hash>.js`: the disk restore and the outbox, once per page instead of in every island (a `useRecord` island 34.8 → 19.4 kB, `examples/dummy`'s `likes-badge` 48.6 → 32.9 kB) |
1274
- | `dev-route-table.ts` | `x dev`'s route table in mount order, and the theme, error-page styles and MCP path it resolved on the way — split from `cmd-dev.ts` at its 500-line ceiling. It is where `pageSync` is called on the dev side |
1275
- | `page-sync.ts` → `persisted` | `@ultimat3/entity`'s `persistedRecordTypes`, read per render, passed as `DocumentOptions.persisted` by both boots: render's `clientPersistTags` puts `ultimate-persist` beside the scope tag and ONLY there — persistence is per principal, so a shareable document names none |
1276
- | `dev-render.ts` | `DocumentOptions.sync` → render's `clientSyncTags`: `ultimate-sync`, `ultimate-build`, `ultimate-sync-worker` on EVERY document (principal-free, unlike the scope tag). The page boot's `<script defer>` (render's `clientBootTags`) is rendered after the body, only when the document carries the SCOPE tag (restoring and replaying are per principal) AND an island this render emitted reaches `@ultimat3/realtime` (`island-realtime.ts` records the answer every island build computes; a page whose islands never touch a record — `examples/dummy`'s settings — pays nothing). `dev-render-scope.test.ts`. Both framework scripts resolve realtime from the root, then from `apps/*` — a workspace app's realtime is `apps/web`'s dependency, and resolving from the root alone silently built no worker and no boot for `examples/dummy`. The static export carries none — it has no sync node |
1277
-
1278
- ### Realtime is installed FOR the author, only where it is used
1279
-
1280
- `island-realtime.ts` (plan 101 slice 14, `As of 2026-09-22`): an island whose own import graph
1281
- reaches `@ultimat3/realtime` — walked with `live-routes.ts`'s `firstInGraph`, relative specifiers
1282
- only, the transpiler reading re-exports and erasing `import type` — is built from a virtual entry
1283
- that calls `installRealtime({ signal: createSignal })` with that bundle's solid-js before the
1284
- island's module body runs. Every other island is built from its own file, byte for byte. +103 B on
1285
- a Solid island reading `useConnection`, +0 elsewhere. A PACKAGE importing realtime for an island is
1286
- the walk's blind spot, and a hook there throws `X_REALTIME_UNINSTALLED` by name.
1287
-
1288
- ### `x verify` runs the static steps beside the serial suites
1289
-
1290
- `verify-run.ts`'s `BESIDE_SERIAL_SUITES` — `lint`, `boundaries`, `filesize`, `package-shape`,
1291
- `errors` — start when `live` does and are joined before the first step after `eval` (DX ledger #14).
1292
- They read the tree and write nothing a later step reads; `typecheck` stays first and alone, `unit`
1293
- alone. The table, `--json` and every step name keep the declared order; `data.durationMs` is WALL
1294
- time now, not the sum of step times. Measured locally on the framework root, 12 cores, one run
1295
- each, `As of 2026-09-22`: **395 s → 270 s** wall. `verify-run-overlap.test.ts` pins both halves.
92
+ | `cmd-build.ts` / `image-prepare.ts` | `x build`: static gate first; docker stamps `BUILD_ID` and writes the island store |
93
+ | `prerender.ts` / `measure-scope.ts` / `static-report.ts` | the static export; routes rendered only to weigh run inside a request as core's `measurementActor()`, with the app's API answered in process (`withInProcessFetch`) |
94
+ | `sw-artifacts.ts` / `pwa-artifacts.ts` / `favicon.ts` | the service worker, the web manifest, `/favicon.ico` |
95
+ | `cmd-db.ts` / `migrations.ts` / `db-generate.ts` / `db-branch.ts` / `db-seed.ts` / `db-backfill.ts` / `db-subscribes.ts` / `db-accept-created.ts` | one migration engine for `x db` and `ROLE=migrate` |
1296
96
 
1297
- ### A browser-only read in a module no island imports is `X_LIVE_ROUTE_NO_ISLAND`
1298
-
1299
- `live-routes.ts`, `As of 2026-09-22`: the rule walks each route's SERVER graph (the page's relative
1300
- imports — an island is a `src` string, never an import) and reports every module there that reads
1301
- `useQuery`, `useConnection`, `useMutation`, `useMutationQueue`, `useRecord`, `useChannel` **or
1302
- `hasPageSocket`**, unless some island's own graph imports that module too. Once per module, however
1303
- many routes reach it. `hasPageSocket()` was an EXEMPTION until this date, on the argument that a
1304
- module asking had written its fallback; on the server it answers false every time, so a guarded
1305
- module no browser runs renders nothing forever — `examples/dummy`'s update banner, in a layout no
1306
- island imported, never showed "A new version is ready.". The fix line starts "move it into an
1307
- island".
1308
-
1309
- ### A registered code nothing throws must say so
1310
-
1311
- `error-unthrown.ts`'s `checkErrorCodesThrown(root, page)` (DX ledger #9, `X_ERROR_CODE_UNTHROWN`):
1312
- a code a package registry names, used nowhere else in shipped `packages/*/src` — not at a `code:`,
1313
- not as a fallback literal, not through a table member something reads — is reported unless its
1314
- reference row says `thrown by nothing` / `not thrown` or sits under `## Reserved codes`. The fix is
1315
- the row, never the registration: a shipped code is stable forever. A host check, like the other
1316
- reference-page rules — in a generated app every framework code would read as unthrown. Its limit:
1317
- an exported class holding `code: 'X_…'` counts as a thrower whether or not anything constructs it,
1318
- which is why `X_RPC_FAILED`'s `RpcFailedError` is not reported.
1319
-
1320
- ### `island-bundle.ts` is the bundler half of `hydrate`
1321
-
1322
- `@ultimat3/render` shipped `island()`, the collector, `emitIslandAttributes`, `hydrateRuntime`,
1323
- `RouteEntry.islands` and `routeJsBytes` — and **nothing constructed or populated any of them**.
1324
- `hydrate` was a documented capability with no implementation, to the point that `render-static.ts`
1325
- told authors to "move the request-dependent part into an island", naming a mechanism the framework
1326
- could not express. This package is the half that can see a file on disk, so it is the half that was
1327
- missing.
1328
-
1329
- | File | Job |
1330
- |---|---|
1331
- | `island-bundle.ts` | discover `*.island.tsx`, build each as its own entry point, hash it, resolve a page's specifier to its URL |
1332
- | `island-solid-dedupe.ts` | the plugin installed FIRST: every `solid-js` specifier in an island's graph resolves to the APP's copy. `Bun.build` resolves from a module's real path, so a package reached through a symlink (`file:` overrides, `bun link`) brought its own `solid-js` — the scaffold's theme toggle shipped two runtimes under CI's own links, 62,463 B against 50,042 B with one (issue #490), and two runtimes are two reactive graphs |
1333
- | `island-routes.ts` | serve those chunks, at `ISLAND_BASE_PATH`, immutable |
1334
- | `dev-render.ts` | one collector **per render**, and `hydrateRuntime` after the body |
1335
- | `prerender.ts` | build first, write the chunks into the export, then measure |
1336
- | `budgets.ts` | `measureDocumentJs` weighs `data-x-entry` as well as `<script src>` |
1337
-
1338
- **A stats row is keyed by the route's DECLARED path, and holds its heaviest page**
1339
- (`As of 2026-08-23`). `checkBudgets` looks a route up by `route.url` off the manifest, which is the
1340
- pattern (`/blog/:slug`), and `prerenderSite` pushed `artifact.path` — the filled one
1341
- (`/blog/hello`). So no dynamic static route had ever been weighed: each was `X_BUDGET_UNMEASURED`
1342
- and `X_BUDGET_EXCEEDED` could not fire for the whole class. The heaviest page and not the first,
1343
- because a budget is a ceiling and the page that breaks it is the one the route answers for; the
1344
- report's `emitted` list still names every filled path.
1345
-
1346
- **Both halves of the CSP are computed at boot, `As of 2026-08-23`.** `style-csp.ts` was alone, and
1347
- the hydration runtime is emitted INLINE in every document carrying an island — so with
1348
- `script-src 'self' 'wasm-unsafe-eval'` no island booted in any container. `x dev` sends the policy
1349
- report-only (`dev: true`), which is exactly why nobody saw it: the page hydrated on a laptop and
1350
- never in the image. `startWeb` extends both directives; the property is pinned end to end by
1351
- `dev-roles-script-csp.test.ts`, which parses a served document, hashes every executable inline
1352
- script in it and asserts the response's own `script-src` names each one — with `dev: false`, the
1353
- only mode in which the policy can block anything.
1354
-
1355
- **One `Bun.build` per island, never one call with N entry points**, and `splitting: false`. The
1356
- island's `src` is a string, so no import edge reaches it and the page's graph stays the page's
1357
- (axiom 6) — a shared chunk would put that number back behind a graph walk, and the budget compares
1358
- against bytes. Two islands that both import the same helper each carry a copy; that is the honest
1359
- number for what booting either one costs.
1360
-
1361
- **The chunk URL is SOURCE-addressed, not byte-addressed, `As of 2026-09-06`.** `graphHash` hashes
1362
- the build's inputs — the source map's `sourcesContent`, sorted, plus the entry's app-relative path,
1363
- the framework version and `Bun.version` — with render's own `contentHash`, the function that already
1364
- stamps an ETag and a precache revision. Hashing the OUTPUT is what it did, and `Bun.build` is not
1365
- byte-deterministic under `minify`: measured on 1.4.0, one entry point, no source file touched, a
1366
- 131,589-byte island alternated between two outputs of identical length differing only in minified
1367
- identifier names, roughly one build in ten. The URL then flapped — ten names in ten minutes in
1368
- ai-maxxing — so the service worker precached a chunk that 404ed and the `immutable` cache never hit.
1369
- The cost of the alternative was measured and refused: `minify: { identifiers: false }` IS
1370
- deterministic and is +47% raw, +20% gzipped, on every island of every app. `sourcemap: 'external'`
1371
- pays for the input list (277ms against 276ms) and its `//# debugId=` line is stripped, so the
1372
- shipped bytes are unchanged. Two processes can therefore serve two byte strings at one URL — the
1373
- same program under different local names — and a per-file cache keeps ONE process serving one.
1374
- Delete `graphHash` the day `Bun.build` is deterministic.
1375
-
1376
- **`x dev`, the container and the static export all mount the same table.** `serve.ts` builds the
1377
- islands at boot for the same reason it mounts `apiRoutes()`: a seam that works in dev and not in the
1378
- image is the same failure one release later. `x dev` rebuilds them on the watcher tick — an island
1379
- is the single module this process never imports, so there is no Bun module cache to invalidate —
1380
- and the SAME tick re-imports the route module beside them when its source changed
1381
- (`app-load.ts`'s `reloadRoute`: `<path>?x-reload=<hash>` is the one cache key Bun honours,
1382
- `registerRoute` replaces the entry for the same file, and `dev-render.ts` reads the entry back from
1383
- the table on every request rather than closing over the one it was built from). Until 2026-09-07
1384
- the island was the only reload that took effect, so a save served a new island under an old page —
1385
- the old props, the placeholder the new island renders when they are missing. A route module and
1386
- nothing else: an action, a query or an entity is held by every module that imported it, and no
1387
- re-import can rebind those. `@ultimat3/render`'s loader admits the query
1388
- (`/\.tsx(?:\?[^/]*)?$/`) and strips it before reading the file — anchored on `.tsx$`, the
1389
- re-import fell through to Bun's own JSX loader and every reloaded page died on `__xh`.
1390
-
1391
- **The dev fixture is its own repository** (`.git/HEAD` in `DEV_FIXTURE_FILES`). The framework's root
1392
- `.gitignore` lists `packages/cli/.dev-fixture/`, `devIgnore` honours every ancestor up to a `.git`,
1393
- and so the watcher admitted the fixture root and nothing under it: every run booted the reload path
1394
- and none exercised it. The marker is what lets `cmd-dev.test.ts` save a page and await the tick.
1395
-
1396
- **`app-load.ts` skips `*.island.tsx` deliberately.** It registers no primitive, and importing it
1397
- would put the one module guaranteed to be outside the server's graph inside this process's, where a
1398
- top-level `document` reference takes the whole scan down.
1399
-
1400
- **The budget is charged from the emitted document, and it names the island.** An island's chunk is
1401
- reached by `import()` from inside the hydration runtime, so it is never a `<script src>` — weighing
1402
- script tags alone charged a page for the runtime and never for the code that runtime boots.
1403
- `measureDocumentJs` reads `data-x-entry` as what it is, dedupes it (two instances of one island are
1404
- one module), and `prerenderSite` maps the heaviest URL back through the bundle so
1405
- `X_BUDGET_EXCEEDED` names `apps/web/site/pricing/calculator.island.tsx` and not a hash.
1406
-
1407
- `X_ISLAND_INVALID` is **borrowed** from `@ultimat3/render`, not twinned: "this src cannot become a
1408
- client entry" is what that code already means, and the bundler is simply the half that can see
1409
- whether the file exists. A failed compile is `X_BUILD_FAILED` — an island is a bundle entry point
1410
- like any other, and `Bun.build` *rejects* rather than answering `success: false`, so the catch is
1411
- the real path.
1412
-
1413
- ### `dev-assets.ts` is where the image pipeline meets HTTP
1414
-
1415
- Three packages declare what an image is and none of them serves one: `@ultimat3/seo` says what a
1416
- variant URL means (`parseImageQuery`) and produces the bytes (`builtinImageDriver`),
1417
- `@ultimat3/storage` says what a variant is called and where it is cached (`variantKey`), and
1418
- `@ultimat3/pwa` says which icons a web manifest promises (`planIcons`, `BuiltinImagePipeline`).
1419
- Pixels are `@ultimat3/core`'s pipeline, only ever. This file picks two base paths — `ICON_BASE_PATH`
1420
- and `MEDIA_BASE_PATH` — and decides nothing else; a resize, a format table or a second cache key
1421
- here is the drift the split exists to prevent.
1422
-
1423
- **`/media` and `/_storage` are one authz decision, not two.** Both serve objects off the app's only
1424
- disk, so `/media/*key` declares what `dev-storage.ts` declares — `auth: 'required'` +
1425
- `STORAGE_READ_PERMISSION` + `enforcedBy: 'handler'` — and calls the same two functions, in the same
1426
- order: `authorizeStorageRead` then `assertReadableKey`. It shipped `auth: 'public'` with no policy
1427
- and no tenant check while its twin required both, which made every tenant's uploads one URL away in
1428
- production (`serve.ts` mounts it), and `?w=` made it an unauthenticated `put` besides. The tenant
1429
- test lives in ONE function both routes call, and `storage-surfaces.test.ts` pins the pair against
1430
- each other — every case names the verdict absolutely as well as comparing the two, because equality
1431
- alone is satisfied by both surfaces failing open together. Cacheability follows the key, not the
1432
- route: a tenant-scoped key takes `AUTHORIZED_OBJECT_CACHE` (`private, max-age=0`, varying on
1433
- `authorization`/`cookie`), and only a key no tenant owns keeps `immutable`. A genuinely public image
1434
- belongs under `apps/web/site/`, which is a static asset and never touches that disk.
1435
-
1436
- **A variant is CACHED only at a width the framework can mint.** The cache key is built entirely
1437
- from caller-supplied query values, so `?w=1`, `?w=2`, … each wrote a new object to the app's only
1438
- disk, on a route every signed-in tenant may reach for their own keys. `@ultimat3/seo`'s
1439
- `MAX_IMAGE_WIDTH` (8192) bounds that and does not close it. `isMintableWidth` is the bound:
1440
- `DEFAULT_WIDTHS` **plus the source's own intrinsic width**, which is exactly the set `usableWidths`
1441
- puts in a `srcset` — the constant alone would refuse the widest entry of any image whose intrinsic
1442
- width is not one of the eight. Anything outside it is still served; only the `put` is refused, so
1443
- no caller gains a new 4xx. `?q=` is deliberately still unbounded here — the closed set for quality
1444
- is `@ultimat3/seo`'s to declare, not this file's.
1445
-
1446
- **`/favicon.ico` is a mechanism, not a scaffolded file.** Every browser requests it unprompted, the
1447
- scaffold wrote none and neither served surface mounted a route, so a permanent 404 sat in the console
1448
- of every app the framework produces — noise that trains the reader to ignore console errors, which is
1449
- the opposite of what `--json` and an executable `fix:` are for (#272). Two rungs and one path: the
1450
- app's own `apps/web/site/favicon.ico` wins, and `favicon.ts` answers a 32x32 PNG encoded through
1451
- `@ultimat3/core`'s own pipeline when there is none — the same encoder `x new`'s icon goes through, so
1452
- there is no second image format in the tree and no base64 blob nobody can verify. It is deliberately
1453
- NOT derived from `ICON_SOURCE`: resizing the install icon needs `@ultimat3/pwa`'s pipeline and would
1454
- make the answer depend on a file that may be absent, which is a third rung under a mechanism that has
1455
- exactly two. The file is read per REQUEST, so dropping one into a running `x dev` takes effect
1456
- without a restart. It mounts through `assetRoutes`, which is the one route set `serve.ts` and
1457
- `cmd-dev.ts` both compose — a favicon added to one of them alone is a 404 that comes back in
1458
- production only — and `prerenderSite` writes the same bytes into the static export, because an
1459
- artifact served with no process behind it has to carry every byte the browser will ask for.
1460
-
1461
- `ICON_SOURCE` lives here, not in `cmd-doctor.ts`, because this is the module that reads it: the
1462
- diagnostic checks what `x dev` serves, so one constant cannot pass the check and serve nothing.
1463
- It is a **PNG** — core decodes PNG and JPEG only, and the SVG this used to name could never
1464
- become an icon.
1465
-
1466
- The routes mount whether or not the source icon exists, and a missing one is refused with
1467
- `X_PWA_ICON_MISSING` and its fix — a route that silently disappears is a 404 whose meaning an agent
1468
- has to guess. Deliberately **not** also a boot finding: `x doctor` already reports this condition,
1469
- with this code, and two reporters of one condition is the duplication this package's own rule
1470
- forbids. `x dev` owns the runtime half; the diagnostic owns the other.
1471
-
1472
- ### `hold.ts` is why a long-running command outlives its own result
1473
-
1474
- `dispatch` renders a `CommandResult` and `bin.ts` exits on the code — so a command whose server is
1475
- still listening when `run` resolves is a command the exit code takes down, between the line that
1476
- announced the url and the first request to it. `x dev` and `x mcp serve --transport http` both did.
1477
-
1478
- The one answer is `CommandResult.hold`: report first, then `dispatch` awaits the hold before the
1479
- exit code. `holdUntilShutdown` installs core's signal handlers (`installSignalHandlers` — until
1480
- this it had no callers anywhere, which is why `cmd-mcp.ts`'s `onShutdown` registration was never
1481
- reached), waits on the **drain's first phase** rather than on a signal list of its own, and
1482
- releases what core's lifecycle never learned about — the embedded Postgres, the worker, the
1483
- watcher — *after* the drain, so an in-flight request still has the database it opened against.
1484
- Ctrl-C is therefore the same three phases production runs, not a kill that leaves `.x/pgdata`
1485
- locked by a process that no longer exists.
1486
-
1487
- **The release runs INSIDE the drain's own deadline, `As of 2026-08-23`, and it is the same
1488
- deadline.** `drain()` ABANDONS a hook that overruns `ShutdownReason.deadlineAt` — the process is
1489
- meant to exit without it — and `release` re-enters the very same teardown one call later:
1490
- `app.stop()` → `startRoles().stop()` → `worker.stop()`, memoised in the package that owns it, so
1491
- awaiting it is awaiting the promise the drain just walked away from. Unbounded, that hung past
1492
- `terminationGracePeriodSeconds` and the kubelet SIGKILLed a process that had already drained
1493
- cleanly. The budget is the hook's own `reason.deadlineAt`, not a stopwatch of ours, so there is one
1494
- number and not two; an overrun is logged as `X_SHUTDOWN_TIMEOUT` and a REJECTION still rejects,
1495
- because `dispatch` awaits the hold inside its own `try`.
1496
-
1497
- **`options.exit` has exactly one caller: `runRole` in `serve.ts`.** `bin.ts` ends in
1498
- `process.exit(code)`, so `x dev` and `x mcp` need nothing; `apps/web/server.ts` — which is what a
1499
- container runs — has no such line, and one non-unref'd interval anywhere in the app then holds an
1500
- event loop with nothing left to do. A function rather than a boolean because `process.exit` inside
1501
- a library is untestable, and the caller is the one that knows.
1502
-
1503
- Commands: `bun test`, `bunx tsc --noEmit -p tsconfig.json`.
1504
-
1505
- ## A declared flag with no reader is a promise `x help` makes and nothing keeps
1506
-
1507
- `x deploy --critical` said *"security deploy: forces clients to reload"* and forced nothing: the
1508
- value is written into the plan JSON (`cmd-deploy.ts`) and **no package reads that field**. The
1509
- parser accepts every declared flag, so this is neither a parse error nor a type error — the flag
1510
- worked perfectly and meant nothing, to the operator most likely to be shipping a security patch.
1511
-
1512
- `flag-reads.ts` is the rule that can see the class of defect: **every flag a command declares is
1513
- read by something in the CLI's own source**, as `X_CLI_FLAG_UNREAD`. The four global flags are
1514
- excluded — `--json`, `--help`, `--cwd` and `--verbose` are the parser's, read once for every
1515
- command, and a per-command rule would report all thirty declarations of `--json`. The read test is
1516
- deliberately generous: a bare `'name'` literal anywhere outside a `name:`/`short:` spec field
1517
- counts, so a flag echoed only into `--json`, or read through a shared constant, is read. A gate
1518
- that guessed at intent would report findings about working commands.
1519
-
1520
- It is enforced by `flag-reads.test.ts`, in the `unit` step — the same shape `cmd-planned.test.ts`
1521
- and `error-catalog.test.ts` use for a rule about the CLI's own declarations, and the reason its
1522
- `fix:` is a `bun test` line rather than an `x` command: the rule can only ever fire in this repo.
1523
- Promoting it to `x verify`'s `boundaries` host check is one line in `scripts/verify.ts`.
1524
-
1525
- **It does not catch `--critical`, and that is the honest limit.** The flag IS read —
1526
- `flagBool(ctx.args, 'critical')` — and what had no consumer was the plan FIELD, one level below any
1527
- rule over names. Two stronger rules were measured and rejected: "the read must not be a property
1528
- initializer" reports six flags, five of which work (`x db --allow-destructive`, `x jobs --queue`);
1529
- "the summary must match the behaviour" is undecidable. So the flag's summary now says what it does,
1530
- and forcing a reload is **not a thing this framework does**, `As of 2026-08`. `updateSignal`
1531
- had no runtime caller for four majors and 9.0.0 deleted it rather than wiring it: `pwa` is tier 4
1532
- and the two runtimes holding both build ids — `http` (2) and `sync` (3) — are below it, so no
1533
- legal import could ever have reached the function. A deploy command has no channel to a running
1534
- client regardless; the plan is `docker compose up` or `helm upgrade`. What ships is notification:
1535
- `useConnection().updateAvailable` from `@ultimat3/realtime`.
1536
-
1537
- ## Planned commands are commands
1538
-
1539
- Every command in `wiki/CLI-Reference.md`'s planned table is in the registry, built from
1540
- `PLANNED_COMMANDS` in `cmd-planned.ts`, and exits `X_NOT_IMPLEMENTED` with a `fix:` naming the
1541
- closest **shipped** command. `X_CLI_UNKNOWN_COMMAND` would say "you typed something that does not
1542
- exist", which is false and sends an agent hunting a typo. `cmd-planned.test.ts` enforces both
1543
- halves: every row is reachable through the parser, and no `fix` points at another planned command.
1544
-
1545
- `PLANNED_SUBCOMMANDS` is the same promise one level down, and `x db studio` is its only entry.
1546
- A subcommand stays in its command's `subcommands` list — the parser reaches it, `x help db` lists
1547
- it — and the owning `run` does `throw plannedSubcommand('db', 'studio')`. Dropping it from the list
1548
- instead would answer `X_CLI_UNKNOWN_SUBCOMMAND`, which is the same lie the table above closes.
1549
-
1550
- ## `guards/` is how an app makes its own convention a build error
1551
-
1552
- Axiom 3 says a convention that is not a build error does not exist, and until 1.2.0 the framework
1553
- gave an app no way to create one: `VERIFY_STEP_NAMES` is a closed literal list with no extension
1554
- point. A file in `guards/` closes it.
97
+ ### Introspection and tools
1555
98
 
1556
99
  | File | Job |
1557
100
  |---|---|
1558
- | `guards.ts` | what a guard IS, how the directory is read, and what a guard is held to |
1559
- | `templates/guard.ts` | `x g guard <name>` — the emitted rule, its pure half and its test |
1560
- | `cmd-verify.ts` | one line in the `boundaries` step: `guardFindings(ctx.root)` |
1561
-
1562
- **It rides on `boundaries`, and it is not an eighteenth step.** The `HostCheck` contract already
1563
- says the shape — *a host adds findings to a step; it can never add, remove, reorder or skip one* —
1564
- so "green" keeps meaning exactly what it meant, whatever an app writes. `boundaries` is the step
1565
- whose host slot already carries "rules this repo makes about itself that the framework cannot
1566
- know" (the monorepo's tier table arrives through it), and it runs third, before any suite, so a
1567
- convention failure comes back in seconds. `guardFindings` is *typed* as a `HostCheck` and is
1568
- composed by the step rather than registered as one: the slot is `Partial<Record<VerifyStepName,
1569
- HostCheck>>`, one function per step, so an app registering there would evict the framework's own
1570
- tier check — and `verifyCommand.run` passes no `hostChecks` at all, which is why an app-supplied
1571
- check could not have reached the gate through that field in the first place.
1572
-
1573
- **Discovered, never registered.** `guards/*.ts`, minus `*.test.ts`, sorted. Nothing imports a
1574
- guard, nothing lists one, and there is no `defineGuard` to call — a guard that has to announce
1575
- itself is a guard an app can forget to announce, which is the coupling axiom 8's extension model
1576
- rejects. A `*.test.ts` beside a guard is its test: importing it would run a suite inside the gate.
1577
-
1578
- **A guard returns `Finding[]`, so it inherits everything.** `--json`, the step table, the summary
1579
- counts and the exit code are all projections of what it returns (axiom 2); a guard that printed or
1580
- chose an exit code would be a second gate. It never throws for a normal result — a throw is
1581
- `X_GUARD_FAILED`, reported as a finding rather than taking the run down.
1582
-
1583
- **And what it returns is held to the error contract.** `findingProblem` demands an
1584
- `X_SCREAMING_SNAKE` code, a non-empty cause, and a `fix:` that passes `fixProblem` — the *same*
1585
- rule `x verify`'s `errors` step applies to every shipped `fix:` in this repo. It runs on the
1586
- returned value, which is the half a static scan cannot reach: a `fix` assembled at run time has no
1587
- literal to read. Three codes, one per way a guard can fail to be one — `X_GUARD_INVALID` (no
1588
- usable export), `X_GUARD_FAILED` (it threw), `X_GUARD_FINDING_INVALID` (what it returned is not a
1589
- finding). Anything else about a guard is the app's business: no size ceiling, no budget, no rule
1590
- about what it may check.
1591
-
1592
- **The validator may never be the thing that crashes.** `findingProblem` names an offending value
1593
- through `shown()` and not `JSON.stringify` — which refuses a BigInt — and every candidate is read
1594
- inside a `try`, because reading one can throw on its own (a getter that raises, a proxy that
1595
- refuses). A guard returning `[1n]` is `X_GUARD_FINDING_INVALID`, per candidate, so one unreadable
1596
- entry costs its own line and not the real findings beside it. The mechanism whose job is producing
1597
- structured failures handing back a stack trace is the one outcome it exists to prevent.
1598
-
1599
- **`x new` ships NINE guards, `As of 2026-09-08`** — the four below plus five interface rules
1600
- (`semantic-interactive`, `focus-visible`, `image-dimensions`, `animated-layout-property`,
1601
- `island-without-states`), each statically decidable and each carrying a legitimate-lookalike test
1602
- that must NOT be reported. **Two of the original four were broken the whole time and nobody could
1603
- see it**, because no guard had ever run against a real app: `raw-colour` reported all 87 uses of
1604
- `rgb(var(--color-…))` — which IS the token form `_colors.scss` emits, so its own cause line ("a
1605
- value no theme can restate") was false of every one — and `untranslated-string`'s JSX mask
1606
- `/\{[^{}]*\}/g` does not nest, so `{t('k', { org: x })}` lost its inner brace group first and the
1607
- remnant read as prose, flagging every `t()` call with an interpolation object or a template-literal
1608
- key. Both are fixed and both now report zero against `examples/dummy`, which is the first tracked
1609
- app to carry a `guards/` directory at all. Read the count, never this sentence:
1610
- `bun -e "import {scaffoldGuardFiles} from './packages/cli/src/templates/scaffold-guards'; console.log(scaffoldGuardFiles().length)"`.
1611
-
1612
- The scaffolded `AGENTS.md` states nine
1613
- non-negotiables, and five of them used to be prose — each proven green on `x verify`: a hardcoded
1614
- JSX string beside a `t()` call, `color: #ff0000` in a stylesheet whose own scaffolded header called
1615
- it "a lint failure", `toLocaleDateString('en-US')` with no `timeZone`, `t.number` money, and a bare
1616
- `throw new Error` in a repo. Four of the five are now guards the scaffold writes —
1617
- `guard-raw-colour`, `guard-unzoned-date`, `guard-bare-error`, `guard-untranslated-string` — so the
1618
- rule is a build error the day the app is created rather than a sentence an agent may skip. The
1619
- fifth, money-as-float, has **no static signature**; the scaffolded `AGENTS.md` row now points at the
1620
- `MoneyInput` type error that already fires, because shipping a guard that cannot work is worse than
1621
- naming the mechanism that does. Their codes are app codes derived from the guard name, so none of
1622
- them appears in `wiki/Error-Codes.md` or the manifest.
1623
-
1624
- `x g guard <name>` writes `guards/<name>.ts` and its test, and nothing else — no index, no
1625
- registry row, no manifest entry. The emitted rule is the class of failure a guard exists for: a
1626
- migration that adds a `NOT NULL` column with no `DEFAULT` applies cleanly to an empty local
1627
- database and fails on the first production table that already holds rows. The `drift` step reads
1628
- those same files and asks a different question, and a test suite runs against a database the
1629
- statement has never met — which is exactly when an app needs a rule of its own. Its code is
1630
- DERIVED from the guard's name (`guardCode`), never written as a literal: an `X_*` literal in
1631
- framework source is a framework code and `error-catalog.test.ts` requires it to be registered.
1632
-
1633
- That rule is held to a real bar, because it is the worked example every app starts from and a
1634
- demonstration that is wrong on realistic input teaches the wrong shape. Block comments are
1635
- stripped before line comments and both before statements are split, so a commented-out
1636
- `ALTER TABLE` is a note and not a finding that blocks `x verify` over nothing; and `DEFAULT NULL`
1637
- counts as **no** default, because it is one in syntax and none in effect — every existing row still
1638
- takes NULL and still violates `NOT NULL`. Both cases are in the emitted test, which is what proves
1639
- an app's copy still works, and both run through the real seam in `guards.test.ts`.
1640
-
1641
- It is in `FIXTURE_GENERATORS` like the other two, and it is the only generated file that imports
1642
- `@ultimat3/cli` for its types — so the scaffold gate compiling it is what proves a scaffolded app
1643
- can write one at all. The root `tsconfig.json` `x new` scaffolds has no `include`, so `guards/` is
1644
- typechecked there by default; an app whose tsconfig names an explicit `include` list has to add
1645
- `guards/**/*` to it, or its guards compile nowhere.
1646
-
1647
- ## Two generators that scaffold something other than a primitive
1648
-
1649
- `x g island <name> [--at <dir>]` writes a **client entry point**, not a component: the filename is
1650
- how the bundler discovers it and `mount` is how the hydration runtime calls it, so the filename,
1651
- the `mount` export and that `mount` RENDERS are what `templates/island.test.ts` pins — it builds
1652
- the emitted entry with `buildIslands` and drives it with `mountIsland`, so a template that
1653
- typechecks and does not mount is a failing test. It runs the mutation too, rather than describing
1654
- it: the same island with `{count()}` replaced by `{0}` must fail the assertion the live one passes.
1655
- `--at` takes the directory directly rather than deriving one, because the caller that cannot guess
1656
- is `X_ISLAND_INVALID` — its cause already holds the exact path a page's `src` resolved to, so its
1657
- `fix:` hands that path straight back.
1658
-
1659
- `x g admin:page <name> --permission <perm> [--at <dir>]` writes an ordinary TSX component and **no
1660
- `defineRoute` call**, deliberately. `@ultimat3/admin`'s `pages:` is the one thing that puts a page in the route
1661
- table and `guardedPage()` is the one thing that decides it; a generator that emitted a route
1662
- declaration would hand back the unguarded second way in that seam exists to close. The emitted test
1663
- asserts the absence. `--permission` defaults to `<name>:read` rather than to nothing, because an
1664
- empty permission list is `X_ADMIN_PAGE_UNGUARDED` at declaration time. `--at` is the same flag
1665
- `x g island` takes and for the same reason — an app's admin is wherever its `defineAdmin` is, which
1666
- no generator can derive, and the hardcoded `apps/admin/src/pages` sent every other layout (the
1667
- demo's is `apps/admin/app/admin`) to `git mv` after every run.
1668
-
1669
- Both are in `FIXTURE_GENERATORS`, so both are compiled by the scaffold gate.
101
+ | `cmd-registries.ts` / `cmd-jobs.ts` / `cmd-tasks.ts` / `cmd-policy.ts` / `cmd-i18n.ts` | project a framework registry; each pairs CLI wiring with a facts module |
102
+ | `cmd-mcp.ts` / `mcp-host.ts` / `mcp-errors.ts` / `mcp-db-target.ts` | `x mcp serve`: 18 tools, two transports |
103
+ | `cmd-shot*.ts` / `cdp-shot-*.ts` / `browser-launcher*.ts` / `island-*` | `x shot` over raw CDP; `verdict.json` names its own blind spots. Not a gate step |
104
+ | `cmd-pr.ts` / `cmd-ci.ts` | GitHub through `gh`, parsed against a schema. Not gate steps |
105
+ | `error-catalog.ts` | imports every `@ultimat3/*` package so `x errors` answers for any code |
1670
106
 
1671
- Implementing one means deleting its row and adding a real `cmd-<name>.ts` — the summary's
1672
- `(planned)` suffix disappears with it, and `x help` follows automatically.
107
+ ## Adding a command
1673
108
 
1674
- Adding a command: write `cmd-<name>.ts` exporting a `CliCommand`, register it in `registry.ts`,
1675
- add its message keys to `messages.ts`. Help and parsing derive from the spec automatically. A
1676
- command's `run` must be `async`: a synchronous throw escapes every caller that awaits the promise
1677
- the signature promises, `dispatch`'s own error path included.
109
+ Write `cmd-<name>-spec.ts` (the declaration) and `cmd-<name>.ts` (a `CliCommand` whose `spec` is
110
+ that declaration), add one `lazy(<name>Spec, …)` row to `registry.ts`, add its message keys to
111
+ `messages.ts`. Help and parsing derive from the spec. `run` must be `async`.