@ultimat3/cli 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/CLAUDE.md +724 -0
  2. package/README.md +41 -9
  3. package/package.json +25 -23
  4. package/src/api-routes.ts +16 -0
  5. package/src/app-auth.ts +32 -0
  6. package/src/app-entities.ts +18 -0
  7. package/src/app-env.ts +103 -0
  8. package/src/app-load.ts +20 -3
  9. package/src/bin.ts +4 -3
  10. package/src/budgets.ts +114 -9
  11. package/src/cmd-build.ts +69 -21
  12. package/src/cmd-db-branch.ts +215 -0
  13. package/src/cmd-db.ts +332 -155
  14. package/src/cmd-deploy.ts +59 -6
  15. package/src/cmd-dev.ts +87 -17
  16. package/src/cmd-docs.ts +167 -0
  17. package/src/cmd-doctor.ts +64 -9
  18. package/src/cmd-env.ts +95 -0
  19. package/src/cmd-errors.ts +33 -13
  20. package/src/cmd-fix.ts +5 -1
  21. package/src/cmd-generate.ts +146 -111
  22. package/src/cmd-help.ts +16 -5
  23. package/src/cmd-i18n.ts +2 -0
  24. package/src/cmd-jobs.ts +47 -33
  25. package/src/cmd-mcp.ts +11 -2
  26. package/src/cmd-new.ts +13 -7
  27. package/src/cmd-planned.ts +55 -10
  28. package/src/cmd-policy.ts +1 -0
  29. package/src/cmd-registries.ts +3 -0
  30. package/src/cmd-secrets.ts +368 -0
  31. package/src/cmd-tasks.ts +1 -0
  32. package/src/cmd-test.ts +17 -23
  33. package/src/cmd-verify.ts +177 -23
  34. package/src/db-backfill.ts +401 -0
  35. package/src/db-branch.ts +251 -0
  36. package/src/db-destructive.ts +29 -0
  37. package/src/db-finding.ts +28 -0
  38. package/src/db-generate.ts +112 -0
  39. package/src/db-snapshot.ts +24 -0
  40. package/src/dev-assets.ts +86 -20
  41. package/src/dev-cache.ts +122 -0
  42. package/src/dev-dashboard.ts +19 -4
  43. package/src/dev-hooks.ts +27 -2
  44. package/src/dev-n-plus-one.ts +191 -0
  45. package/src/dev-queue.ts +105 -19
  46. package/src/dev-render.ts +158 -26
  47. package/src/dev-roles-fixture.ts +67 -0
  48. package/src/dev-roles.ts +186 -78
  49. package/src/dev-runtime.ts +117 -40
  50. package/src/dev-services.ts +15 -0
  51. package/src/dev-storage.ts +245 -0
  52. package/src/dev-sync.ts +107 -0
  53. package/src/dev-traces.ts +11 -3
  54. package/src/dispatch.ts +4 -2
  55. package/src/document-styles.ts +54 -0
  56. package/src/drift.ts +37 -9
  57. package/src/error-catalog.ts +7 -18
  58. package/src/error-codes.ts +186 -0
  59. package/src/error-contract.ts +29 -7
  60. package/src/error-fixes.ts +114 -0
  61. package/src/errors.ts +205 -140
  62. package/src/fix-command.ts +268 -0
  63. package/src/flag-number.ts +56 -0
  64. package/src/framework-scope.ts +49 -0
  65. package/src/generate-kinds.ts +97 -0
  66. package/src/guards.ts +186 -0
  67. package/src/index.ts +87 -14
  68. package/src/island-bundle.ts +166 -0
  69. package/src/island-routes.ts +50 -0
  70. package/src/jobs-driver.ts +33 -0
  71. package/src/jobs-json.ts +24 -0
  72. package/src/jobs-report.ts +17 -4
  73. package/src/mcp-db-target.ts +52 -27
  74. package/src/mcp-errors.ts +120 -19
  75. package/src/mcp-host.ts +44 -25
  76. package/src/messages.ts +81 -2
  77. package/src/metrics-endpoint.ts +73 -0
  78. package/src/migrations.ts +37 -4
  79. package/src/otlp-export.ts +64 -0
  80. package/src/output.ts +46 -16
  81. package/src/parse.ts +41 -3
  82. package/src/policy-facts.ts +38 -6
  83. package/src/policy-fixture.ts +14 -7
  84. package/src/prerender.ts +111 -2
  85. package/src/registry.ts +21 -3
  86. package/src/runtime-overrides.ts +66 -0
  87. package/src/safe-url-label.ts +24 -0
  88. package/src/scaffold-fixture.ts +10 -0
  89. package/src/scaffold-typecheck.ts +16 -38
  90. package/src/serve.ts +202 -18
  91. package/src/source-files.ts +4 -0
  92. package/src/statement-loop.ts +74 -0
  93. package/src/style-csp.ts +18 -0
  94. package/src/sync-authenticator.ts +59 -0
  95. package/src/templates/action.ts +15 -30
  96. package/src/templates/admin-page.ts +103 -0
  97. package/src/templates/admin.ts +11 -7
  98. package/src/templates/backfill.ts +212 -0
  99. package/src/templates/entity.ts +72 -31
  100. package/src/templates/guard.ts +143 -0
  101. package/src/templates/index.ts +12 -1
  102. package/src/templates/island.ts +67 -0
  103. package/src/templates/job.ts +53 -13
  104. package/src/templates/naming.ts +17 -1
  105. package/src/templates/policy.ts +35 -28
  106. package/src/templates/query.ts +24 -5
  107. package/src/templates/resource.ts +19 -11
  108. package/src/templates/route.ts +90 -15
  109. package/src/templates/scaffold-app.ts +142 -45
  110. package/src/templates/scaffold-claude-agents.ts +149 -0
  111. package/src/templates/scaffold-claude-commands.ts +221 -0
  112. package/src/templates/scaffold-claude.ts +134 -0
  113. package/src/templates/scaffold-container.ts +46 -2
  114. package/src/templates/scaffold-db-package.ts +91 -0
  115. package/src/templates/scaffold-docs.ts +24 -5
  116. package/src/templates/scaffold-domain-package.ts +90 -0
  117. package/src/templates/scaffold-env.ts +87 -0
  118. package/src/templates/scaffold-i18n.ts +4 -1
  119. package/src/templates/scaffold-mcp-package.ts +49 -0
  120. package/src/templates/scaffold-package-shape.ts +25 -4
  121. package/src/templates/scaffold-repo.ts +116 -257
  122. package/src/templates/scaffold-roles.ts +68 -0
  123. package/src/templates/scaffold-ui-package.ts +56 -0
  124. package/src/templates/slice-foundation.ts +88 -0
  125. package/src/templates/wrap.ts +95 -0
  126. package/src/test-counts.ts +35 -0
  127. package/src/test-select.ts +30 -15
  128. package/src/test-shards.ts +21 -3
  129. package/src/test-workers.ts +47 -0
  130. package/src/ts-scan.ts +271 -13
  131. package/src/tsconfig-references.ts +78 -0
  132. package/src/verify-floor.ts +133 -0
  133. package/src/verify-step.ts +19 -0
  134. package/src/verify-test-run.ts +72 -0
  135. package/src/verify-tests.ts +160 -71
  136. package/src/version-loader.ts +20 -3
  137. package/src/workspace-checks.ts +87 -16
  138. package/src/write-line.ts +34 -0
package/CLAUDE.md ADDED
@@ -0,0 +1,724 @@
1
+ # @ultimat3/cli — boundary
2
+
3
+ Tier 5. May import tiers 0–4. Nothing imports this except `create-ultimate`.
4
+
5
+ | Rule | Detail |
6
+ |---|---|
7
+ | Entry | `src/bin.ts` (`#!/usr/bin/env bun`) — argv, stdout, exit code only |
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
+ | 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 |
10
+ | 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 |
11
+ | 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>`, never `x <command> --help`: the subcommand is resolved *after* the flag loop, so the latter throws the same error again — a fix line that reproduced its own failure |
12
+ | I/O | only `dispatch.ts` renders or exits; commands return `CommandResult` |
13
+ | Staying up | a command still listening when `run` resolves returns `hold` (`hold.ts`), or `bin.ts` exits out from under it |
14
+ | `--json` | every command, no exceptions — same data as the human render |
15
+ | Errors | codes + titles in `src/error-codes.ts`, classes in `src/errors.ts`, subclass `UltimateError`, never a bare `Error` |
16
+ | Subprocesses | only through `exec.ts`, so a test can inject a fake `Runner` |
17
+ | Templates | `templates/*.ts` return strings; no fixture files on disk |
18
+ | Strings | rendered output through `messages.ts`, missing key renders `⟦key⟧` — see below for what is *not* rendered output |
19
+ | Facts | load the app (`app-load.ts`), then project it — never parse source for primitives |
20
+
21
+ Every fact the CLI reports comes from a framework package: the manifest from
22
+ `@ultimat3/manifest`, `openapi.json` from `@ultimat3/action`, the route table from
23
+ `@ultimat3/render`, budget units from `@ultimat3/render`, the `/_x` panels from
24
+ `@ultimat3/admin`, the MCP tool catalog from `@ultimat3/mcp`, eval coverage from
25
+ `@ultimat3/ai`. A check that reimplements one of those here is the bug, not the fix.
26
+
27
+ `app-evals.ts` is why the `eval` step can apply with no eval suite at all: a prompt no eval
28
+ names is `X_EVAL_MISSING`, an eval whose baseline was never recorded is `X_EVAL_BASELINE_MISSING`,
29
+ and a skipped step would read as a green gate over untested code. Its third rule runs *before* the
30
+ suite rather than beside it — `ULTIMATE_EVAL_RECORD` makes every eval write its own numbers and
31
+ pass, so a gate that inherited the flag would rewrite the committed baselines during the run, and
32
+ a finding after the fact does not put them back.
33
+
34
+ `verify-floor.ts` is the suite ratchet, and it is split across two owners on purpose. `runVerify`
35
+ judges the **suites**: a step the committed `x.verify.json` names that reports nothing to check is
36
+ recorded failed and not skipped, so the failure count, `data.failed` and every step table another
37
+ gate parses all carry it. The `manifest` step judges the **file**: a floor that does not parse, or
38
+ that names a step the gate does not run, enforces nothing — and a ratchet nobody notices is off is
39
+ the false green it exists to close. Nothing writes the file; a gate that edits its own floor
40
+ ratchets in both directions.
41
+
42
+ **"Nothing to check" is two conditions and one code.** `applies` sees the first — no files — and
43
+ cannot see the second, because `describe.skipIf` is decided inside the child process: measured with
44
+ no `TEST_DATABASE_URL`, `live` is `4 pass, 114 skip` and the step reported green over a suite whose
45
+ whole subject is the database. `test-counts.ts` reads bun's own summary back (`parseBunTest`, the
46
+ same reader `x mcp`'s `test.run` uses — a second regex over one format is drift), each runner
47
+ attaches `StepOutcome.tests`, and a floor step whose `ran` is zero is `X_VERIFY_SUITE_VANISHED`
48
+ with `skippedSuiteFinding`'s cause. **Zero, not a ratio**: one real assertion is a suite that runs,
49
+ and a threshold would be a number nobody can defend. An absent `tests` is a step that spawned no
50
+ test process at all (`eval` answering with declarations alone), which is not the same claim.
51
+
52
+ `x new` writes an `x.verify.json` (`templates/scaffold-repo.ts`), or the code above is unreachable
53
+ in every generated app — the repo shape that grows suites fastest. It names the eleven steps the
54
+ scaffold has proved apply, and deliberately not `e2e`: the scaffolded `page.e2e.test.ts` is an
55
+ `e2eTest`, which is `test.skip` until the app registers a browser driver, so pinning it would fail
56
+ the app's first gate on the scaffold's own placeholder.
57
+
58
+ `tsconfig-references.ts` is `package-shape`'s fourth rule: **every published workspace is in the
59
+ root `references`**. `bun run typecheck` is `tsc -b`, which builds referenced projects and nothing
60
+ else, so a package no reference names is one the gate's own `typecheck` step passes over without
61
+ reading a line of it — `X_PACKAGE_UNREFERENCED`, whose `fix:` is the exact `{ "path": … }` entry
62
+ and the `tsc -b` that proves it took. Private packages are exempt (a generated app's are all
63
+ private), and a root that declares **no** `references` array is not judged at all: project
64
+ references are opt-in, and a scaffolded app builds through `extends` + `include`.
65
+
66
+ `app-agents-md.ts` is why the `manifest` step declares no `applies` at all. The drift half needs
67
+ a committed `x.manifest.json` to compare against, but `AGENTS.md` is required of every repo the
68
+ gate runs in — so the step always has a question to answer, and gating both halves on the file
69
+ that only the first one needs is how `X_AGENTS_MD_MISSING` stayed unreachable while its wiki row
70
+ said it fails builds.
71
+
72
+ ## What goes in `messages.ts`, and what does not
73
+
74
+ `messages.ts` holds the strings a command *renders* — `CommandResult.summary`, `lines`, anything
75
+ the human renderer prints. Three things stay inline, deliberately, and a review asking to move
76
+ them is answered by this table rather than by a second convention:
77
+
78
+ | Not in the catalog | Why |
79
+ |---|---|
80
+ | `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 |
81
+ | `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 |
82
+ | Fixed-width table headers (`renderJobTable`, `renderRouteTable`) | column keys, not prose — the widths are computed from them and `--json` carries the same names |
83
+
84
+ ## The introspection commands project registries, they never re-derive facts
85
+
86
+ | Command | Files | Reads |
87
+ |---|---|---|
88
+ | `x actions` / `x queries` / `x entities` | `cmd-registries.ts` | the three declaration registries |
89
+ | `x jobs` | `cmd-jobs.ts`, `jobs-{driver,report,drain,json,table}.ts` | `@ultimat3/jobs`' own introspection |
90
+ | `x tasks` | `cmd-tasks.ts`, `tasks-facts.ts` | `registeredTasks()` + `@ultimat3/time`'s cron resolution |
91
+ | `x policy` | `cmd-policy.ts`, `policy-facts.ts` | `@ultimat3/policy`'s `policyMatrix()` over the app's own `Policy` objects |
92
+ | `x i18n` | `cmd-i18n.ts`, `i18n-audit.ts` | `@ultimat3/i18n`'s `extractFromFiles` + `auditCatalogs` |
93
+
94
+ Each pairs a `cmd-*.ts` of CLI wiring with a facts module that takes plain inputs and returns plain
95
+ data, so the projection is testable without a `ParsedArgs` — the `cmd-jobs.ts` / `jobs-report.ts`
96
+ split, repeated. Tables go through `table.ts`; a second padding helper is the drift it prevents.
97
+
98
+ `x policy explain` exists because five packages already print it as the `fix:` on an authz denial
99
+ (`policy`, `action`, `query`, `http`, `auth`), and `x i18n` because all three of `@ultimat3/i18n`'s
100
+ own error fixes name it. A `fix:` line naming a command this build does not ship is the failure
101
+ mode `cmd-planned.ts` closes for planned commands and these close for real ones.
102
+
103
+ `x i18n check` scans source, which the "never parse source for primitives" rule below does not
104
+ forbid: a `t()` call is not a primitive and no registry holds it. It uses `source-files.ts`, the
105
+ same walk `errors` and `filesize` use, so the three cannot disagree on what the app's source is.
106
+
107
+ **A catalog is authored nested and read flat.** `Catalog` (`{ 'nav.home': 'Home' }`) is the
108
+ translator's form; the file on disk holds `{ nav: { home: 'Home' } }`, and `parseNestedCatalog`
109
+ refuses a dot inside a key — so anything writing a catalog goes through `nestCatalog`
110
+ (`serializeCatalog` for `x i18n add|sync`, `templates/catalog-json.ts` for every generator) or it
111
+ emits a file `defineCatalogs` rejects at the app's first boot. `merge: 'json'` unions **deeply**
112
+ (`json-merge.ts`) for the same reason: `x new` and `x g resource` both contribute under `app`, and
113
+ a shallow spread keeps one of them.
114
+
115
+ ## The `errors` step enforces the error contract
116
+
117
+ | File | Job |
118
+ |---|---|
119
+ | `ts-scan.ts` | the strings a `fix:` can evaluate to, the `X_*` codes a file declares, and the ones it says it borrows |
120
+ | `error-contract.ts` | the rules, the two checks that turn them into findings, and `collectDeclaredCodes` |
121
+ | `fix-command.ts` | resolving an `x <command>` a `fix:` cites against the registry |
122
+ | `source-files.ts` | which files are shipped source — shared with `filesize`, never a second list |
123
+
124
+ **A `fix:` may not cite a command this build does not ship.** Six shipped fix lines named
125
+ `x db status`, `x logs tail`, `x trace`, `x metrics`, `x auth whoami` and `x ai prompts`, and every
126
+ one passed — the text rule checks that a fix NAMES a command, never that the registry holds it.
127
+ `fix-command.ts` resolves the citation, and a PLANNED command fails too: `x logs` parses, `x help`
128
+ lists it, and running it hands the reader `X_NOT_IMPLEMENTED` instead of the fix.
129
+
130
+ The rule is **conditional, and that is load-bearing**: *if* a fix cites `x <command>`, it must
131
+ resolve. It does not require every fix to name one — `set OTEL_EXPORTER_OTLP_ENDPOINT=…` and
132
+ `counter('orders_total', { maxSeries: 4000 })` are executable and correctly cite nothing, and a
133
+ universal rule would push an author into citing a command that does not really fix it. A second
134
+ word is judged as a subcommand only when the spec declares subcommands, or `x new my-app` reports
135
+ `my-app` as one. The registry arrives through `await import('./registry')` — `registry → cmd-verify
136
+ → error-contract` closes a cycle back to the caller, and the precedent for the break is
137
+ `cmd-build.ts`.
138
+
139
+ **It reads a THIRD word, under the same condition.** `x db branch ls --json` resolved — `db` is a
140
+ command, `branch` is one of its subcommands — and the word that decided what actually ran was never
141
+ looked at, so a fix line that created a stray database passed every check the repo had. A third
142
+ word is judged only where the subcommand declares a closed set (`CommandSpec.subcommandPositionals`,
143
+ declared from the constant the command validates against), because `x jobs show <id>` and
144
+ `x db gen "add publish_at"` take open positionals and a universal rule would report findings about
145
+ working invocations. `positionalChoices` cannot express it: `fix-command.ts` reads that field only
146
+ where a command declares no subcommands at all.
147
+
148
+ **And in that one slot, a `<placeholder>` is a finding too.** `x db branch <name>` is what two
149
+ `@ultimat3/mcp` fix lines said; the citation reader does not read `<name>` as a word, so the slot
150
+ was never examined and the line resolved clean while running it answers `X_CLI_UNKNOWN_COMMAND` —
151
+ the same blind spot in a second disguise. A closed set means the slot is a verb, so there is
152
+ nothing a reader could substitute that would make it run. `CITATION` therefore matches a
153
+ placeholder in the third slot **only**: `x jobs show <id>` and `x db branch drop <name>` are correct
154
+ fix lines and must stay invisible to this rule.
155
+
156
+ `collectDeclaredCodes` is the only answer to "which codes exist, and where is each declared?" — one
157
+ walk, one entry per code, the owning registry preferred over any throw site and over a registry
158
+ that named the code in its `<PKG>_BORROWED_ERROR_CODES`. The docs check reads it and so does the
159
+ framework's own `framework.manifest.json`, because a second scanner over a narrower file set is a
160
+ manifest that claims completeness it does not have.
161
+
162
+ An empty `fix`, or a `fix` that says `check` / `make sure` / `try` / `see the docs` and names no
163
+ command, call or file path, is `X_ERROR_FIX_INVALID`. A declared code the host's error reference
164
+ does not name is `X_ERROR_CODE_UNDOCUMENTED` — `wiki/Error-Codes.md` here, nothing in a generated
165
+ app, which is why that half arrives as a host check (`scripts/verify.ts`) rather than a hardcoded
166
+ path in this package.
167
+
168
+ `ts-scan.ts` masks comments and string contents before it looks for structure. The contract's own
169
+ 3-line rendering appears verbatim in doc blocks and interpolated messages, and a scanner that read
170
+ those as declarations would report findings nobody can fix. What it cannot see is a `fix` with no
171
+ literal — a parameter, or a table lookup with no fallback. Those are out of a static scan's reach,
172
+ and the step says so rather than guessing.
173
+
174
+ **A fix does not always arrive under a key**, and until `As of 2026-08` the scanner assumed it did.
175
+ `@ultimat3/mcp`'s `readonly-sql.ts` hands every fix positionally to a local `rejected(cause, fix)`
176
+ helper, so the file held no `fix:` at all and `scanFixes` returned `[]` for all of it — the
177
+ citation resolver was never given a string to judge, and two stale `x db branch <name>` lines
178
+ shipped through the hole. `scanFixes` now also reads the argument in the `fix: string` position of
179
+ a **local** helper, under four rules, each with its own case in `ts-scan.test.ts`: the helper must
180
+ BUILD an error (`code` key or `new …Error(` in its body), or `citedCommandProblem(fix, catalog)` —
181
+ which takes a fix to *judge* it — would have its call sites read as declarations; the parameter
182
+ list may hold no rest or destructured parameter, because neither has a reliable position; the call
183
+ may not be a member access; and the argument must BE one literal, stricter than the key path,
184
+ because `prefix + 'x doctor'` reads as one literal there and publishing half a fix is worse than
185
+ publishing none. Measured over the whole tree: 16 files gained readable fixes, `readonly-sql.ts`
186
+ went from 0 to 7, and **zero** new findings.
187
+
188
+ What it still cannot see is **cross-file**: `dbNotImplemented` is exported from `@ultimat3/db` and
189
+ called from `pglite-branch.ts`, and resolving that means an import graph and a per-symbol parameter
190
+ table. Same for an error class with a positional `constructor(cause, fix)` — `@ultimat3/render`'s
191
+ `errors.ts` has fourteen, and 15 of its codes have never had a fix line read. Measured: **zero**
192
+ same-file call sites for that form, so a constructor rule would be dead code today. Named, not
193
+ guessed at.
194
+
195
+ `cli → admin` is a declared sideways edge (`scripts/lib/tiers.ts`): `x dev` **mounts** the
196
+ dashboard, it never grows a second one. The CLI's only contribution is the facts no registry
197
+ holds — a SQL runner, the caught outbox, the committed manifest, the process's own services, the
198
+ spans it recorded — supplied as `defaultDevSources({ hooks })`.
199
+
200
+ Wired means answerable: all eleven panels answer in a `x dev` process, and a hook the CLI does
201
+ not supply is a panel that refuses with a wiring line, never one that renders empty. `timeline`
202
+ is core's tracer (`x dev` is what calls `configureTelemetry`), `cache` is
203
+ `recentInvalidations()`, `policy` is `@ultimat3/policy`'s own `policyMatrix()` over the app's
204
+ roles — a verdict re-derived here would be the second authz the framework exists to prevent.
205
+ `subscribers` is the one source left unwired: `@ultimat3/realtime` retains no matcher trace, and
206
+ that trace is the live panel's question, so the panel degrades to its own note instead.
207
+
208
+ `dev-traces.ts` reads a span's panel kind off its **name prefix** — a subsystem that starts emitting
209
+ spans adds its prefix to `KIND_BY_PREFIX` or its work is filed under `action`. `db.` is there
210
+ because `@ultimat3/db`'s two funnels open one span per statement (`db.select`, `db.begin`), and a
211
+ statement is the one span that states its own identity — `STATEMENT_ATTRIBUTE`, **imported** from
212
+ `@ultimat3/db` by both `dev-traces.ts` and its test rather than spelled as a literal, which the
213
+ recorder prefers over the name, so the timeline's `repeatedSql` groups SQL texts and not span names. Those spans
214
+ exist only where a `StatementObserver` is installed, so a trace with no DB children is a process
215
+ with no statement diagnostic, not a broken recorder.
216
+
217
+ `dev-n-plus-one.ts` is that observer, and `cmd-dev.ts` is the **only** place that installs it —
218
+ `serve.ts` installs neither it nor the in-process trace RECORDER (`createTraceRecorder`, which is
219
+ `/_x/timeline`'s source), the same line that file already draws for `/_x`. **It is not "no
220
+ exporter"**, `As of 2026-08`: `serve.ts` calls `startOtlpExport(options.env)`, because a collector
221
+ named in the chart has to receive spans from the container and not only from a laptop. What a
222
+ production process does without is the *statement* diagnostic and the in-memory timeline — the
223
+ ledger and the recorder go in together and come out together in `stop()`, because the timeline's
224
+ SQL rows and the repeat counts are one feature with one toggle, and uninstalled the seam costs the
225
+ one `undefined` branch it already pays (axiom 6).
226
+
227
+ Three rules hold the ledger, each load-bearing. **Per request, keyed by the `Ctx` object** — a
228
+ `WeakMap` whose entry dies with the request, so nothing sweeps and nothing accumulates across a dev
229
+ session; a statement issued outside a request is not counted at all, because "five of one shape"
230
+ only means something inside one unit of work. The price of keying on identity is that a
231
+ `withChildContext` scope is its own tally. **A shape is `entity.op` when attributed**, the
232
+ statement's own text with whitespace collapsed when it is not — `members.findById` fifty times is
233
+ what an author can act on, and grouping fifty point lookups by their SQL would report bind values.
234
+ That rule is **not written here**: `statementFingerprint`/`statementKind` are `@ultimat3/db`'s and
235
+ the threshold is `@ultimat3/entity`'s `N_PLUS_ONE_THRESHOLD`, because `@ultimat3/testing`'s
236
+ `statements` fixture is a second detector and a copy of either would let a loop that fails a test be
237
+ a different loop from the one this ledger warns about. What stays here is what only a dev *server*
238
+ knows: the request as the unit of work, the bound report list, one log line per request per code.
239
+ **An expected statement is not counted** — `expectedQueryLoop` suppresses a verdict and this ledger
240
+ is the verdict, so the span and the timeline still show the loop while the thing that warns is told
241
+ the author already answered. A shape is promoted to a verdict exactly once, on the statement that
242
+ crosses the threshold, and its count keeps rising: a loop of fifty is one report reading fifty. The
243
+ report list is bounded and drops its oldest.
244
+
245
+ `statement-loop.ts` is the **one** projection those verdicts reach four surfaces through, and the
246
+ reason there is only one is that four renderings of one loop must be one sentence. It hands a
247
+ verdict to `@ultimat3/entity`'s `nPlusOne()` — the `fix:` speaks that package's vocabulary and is
248
+ derived from the relations the schema already declared — and each surface takes a field of what
249
+ comes back: `cmd-dev.ts` appends `loopFinding` to the `findings` getter (text and `--json` render it
250
+ for free), `dev-dashboard.ts` supplies `statementLoops` so `/_x/timeline` shows `nPlusOne` for the
251
+ request on screen, `cmd-dev.ts` again passes `devNotices` down `startRoles` so the browser overlay
252
+ renders the loop under the error, and the ledger itself emits `warnLoop` — one `logger.warn` per
253
+ request per code, the ids riding along from core's `setLoggerContextFields`.
254
+
255
+ Two rules about *when* a count is read. **A surface reads it live**: the finding, the panel row and
256
+ the notice all say `ran 50 times` because they ask after the loop finished, while the log line says
257
+ `ran 5 times` because it was written the moment the threshold was crossed — same verdict, two
258
+ honest moments. **A verdict belongs to its request**: `repeatsFor(ctx)` reads the request's own
259
+ tally rather than filtering the bounded global list, so the overlay still names a loop the bound
260
+ already dropped. `serve.ts` supplies no `devNotices`, so the seam it boots through is a key that is
261
+ absent, not a hook answering an empty list.
262
+
263
+ `dev-n-plus-one.test.ts` and `statement-loop.test.ts` drive the ledger and the projection with
264
+ hand-built `StatementEvent`s — fast, and enough to pin every rule above. `n-plus-one-detector.test.ts`
265
+ proves the loop those events stand in for: real `posts`/`authors` entities, `postgresRepo` and
266
+ `createPgliteClient` (an injected fake driver so no `@electric-sql/pglite` build is needed, but a
267
+ real client — `createRecordingClient` implements `DbClient` on its own and never reaches the
268
+ observer, so it cannot stand in here) — a naive per-row `findById` loop trips `X_N_PLUS_ONE_QUERY`
269
+ with the exact `preload('author')` line, the `preload()` form of the same read stays quiet,
270
+ `expectedQueryLoop` silences the naive form without stopping it from running, and a naive per-row
271
+ `delete` loop trips `X_N_PLUS_ONE_WRITE`. Its describe block spells the pattern `n1`, matching
272
+ `packages/entity/src/n-plus-one.test.ts`'s own fixture prefix, because `bun test -t 'n+1'` is a
273
+ regex and `+` is a quantifier — `n1` is what actually selects these tests.
274
+
275
+ ## One migration engine, four environments
276
+
277
+ | File | Job |
278
+ |---|---|
279
+ | `migrations.ts` | the app's `packages/db/migrations` read into `@ultimat3/db`'s `Migration` shape — the **one** reader |
280
+ | `db-generate.ts` | `x db gen`: entities diffed against what the migrations declare, written as `.sql` + `.snapshot.json` + `.hash` |
281
+ | `cmd-db.ts` | the subcommands, and nothing else — `gen` calls `db-generate.ts`, `migrate`/`reset` call `serve.ts`'s `runMigrations` |
282
+ | `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 |
283
+ | `cmd-db-branch.ts` | `x db branch`'s wiring alone — which verb, which refusal, and the one connection an external clone runs on |
284
+ | `db-finding.ts` | one thrown value → one `Finding`, shared by `cmd-db.ts` and `cmd-db-branch.ts` |
285
+ | `drift.ts` | `checkSourceDrift`: the `.hash` sidecar `x verify`'s `drift` step compares, no database needed |
286
+ | `db-destructive.ts` | `checkDestructiveMigrations`: the same step's second half — every committed `up` that drops, truncates or retypes must carry `-- destructive: true` |
287
+ | `db-backfill.ts` | `x db backfill --list`: the flag parsing, the ledger read and the table |
288
+
289
+ `jobs-driver.ts` is the ONE place a CLI command gets hold of the app's queue — `withJobDriver`,
290
+ which `x jobs` and `x db backfill` both call. It reuses an ambient `jobDriver()` when a process
291
+ already installed one (inside `x dev` or `x mcp serve`, booting a second queue talks to the wrong
292
+ database) and otherwise boots `startQueue` and releases it in a `finally`, or a CLI that exits
293
+ holding the PGlite lock breaks the next command run against this app. A second copy of that boot
294
+ would be two answers to "which queue is this command talking to".
295
+
296
+ **`x db branch` takes a VERB, and a branch name can never be one.** `ls`, `create <name>`,
297
+ `drop <name>` — a closed set, declared once in `BRANCH_SUBCOMMANDS` and read three ways: the
298
+ command validates against it, `dbCommand.spec.subcommandPositionals` declares it so the `errors`
299
+ step can resolve a citation against it, and the refusal for an unknown word lists it. The bare-name
300
+ form it replaces is why: the argument *was* the name, so `x db branch ls --json` — the `fix:` on
301
+ the planned `x branch`, on `X_DB_BRANCH_FAILED`, and (as `create`/`drop`) on `@ultimat3/db`'s own
302
+ `X_BRANCH_EXISTS` and `X_SQL_UNSAFE` — cloned a database called `ls` and returned no listing. All
303
+ four passed every check the repo had, because `fix-command.ts` resolved two words and the third was
304
+ the one that decided what ran.
305
+
306
+ **`drop` has no confirmation flag, and that is the design.** It may only drop what `ls` shows: an
307
+ external branch is a database carrying the marker comment `createBranch` writes **and** this
308
+ database's own `<source>_branch_` prefix, an embedded one is a `pgdata-<name>` directory, so the
309
+ shared database this session is connected to is in neither set.
310
+ The typo is impossible rather than the keystroke tedious — and `@ultimat3/db` already ships
311
+ `x db branch drop <name>` as `X_BRANCH_EXISTS`'s `fix:` with no flag on it, so a flag here would
312
+ break a shipped instruction.
313
+
314
+ **The prefix half is not decoration: the marker records WHEN a clone was made and never what it was
315
+ cloned FROM.** One Postgres server hosting two Ultimate apps answers `listBranches()` with both
316
+ apps' clones, and `branchNameOf` reduced `postly_branch_feat` and `analytics_branch_feat` to the
317
+ same branch name — so `x db branch drop feat`, run against `postly`, was authorised by
318
+ `analytics`'s row and then issued `drop database if exists "postly_branch_feat"` against a database
319
+ carrying no marker at all: a `DROP DATABASE` the guard had never approved, and nothing recoverable
320
+ about it. `branchNameIn(source, database)` is the source-scoped inverse of `branchDatabaseName` and
321
+ the one `ls` and `drop` both read; `branchNameOf` survives for `mcp-db-target.ts` alone, which has
322
+ a URL and no connection to ask `current_database()` with.
323
+
324
+ **The membership check lives inside `dropExternalBranch`, not in the wiring above it.** One
325
+ connection, one listing, one statement before the `DROP` — a listing taken by the caller and acted
326
+ on afterwards is two connections and a window wide enough to hold a whole `create`. It is still not
327
+ atomic and cannot be: `DROP DATABASE` runs in no transaction, so no single statement both verifies
328
+ the marker and deletes. Closing the last gap means a lock around both halves inside
329
+ `@ultimat3/db`'s `dropBranch` — which a `psql` at the next terminal would not hold either.
330
+
331
+ **`ls` is the reason `create` no longer shells out to `psql`.** `listBranches()` finds branches by
332
+ `createBranch`'s marker comment; the `psql` path wrote the `CREATE DATABASE` and no comment, so
333
+ every branch the CLI made was invisible to the only lister the framework has. External branching
334
+ now runs through `@ultimat3/db` on one `role: 'migrate'` client — `max: 1`, no statement timeout,
335
+ both load-bearing: `CREATE DATABASE … TEMPLATE` is refused while any *other* session holds the
336
+ template, and cloning a real database outlives a `web` profile's 10s.
337
+
338
+ **`DatabaseTarget.production` is a fact this package supplies, and it was the literal `false`.**
339
+ `mcp-db-target.ts` is the only place one is ever built, so `assertBranchDatabase`'s first refusal —
340
+ "production is never migratable from MCP at all" — could not run for any database the CLI produced;
341
+ a production database was refused only incidentally, because its name lacked `_branch_`, and one
342
+ named `shop_branch_hotfix` read as a branch and was migratable. It is now core's one key, read the
343
+ way `x doctor` reads it. An **unreadable** `ULTIMATE_ENV` counts as production: `tryResolveEnvironment`
344
+ answers `undefined` for exactly one input — a value that is not an environment — and a guard that
345
+ read a typo as "not production" would be defeated by the misconfiguration it exists to survive.
346
+ `staging` stays false; `branch: null` is already what refuses it, and widening the flag would make
347
+ the refusal say something untrue.
348
+
349
+ `x db backfill` has four shapes and a **dry run is the default**: `--list` reports the ledger,
350
+ `--pending` reports declared-minus-completed and exits non-zero when there is drift, `<name>` plans
351
+ one sweep, and `--all` plans every pending one. `--write` is never implied — the inspection forms
352
+ and the acting form are the same command, and the flag is the only thing that separates them.
353
+ `--all --write` isolates per name and continues past a failure, exiting non-zero naming each, so one
354
+ wedged cleanup cannot block every later one forever.
355
+
356
+ Until 1.2.0 a bare `x db backfill <name>` threw `X_NOT_IMPLEMENTED`, and the ledger was the only
357
+ half that existed: `x_backfills` recorded what had run, and **nothing recorded what was pending**, so
358
+ a scaffolded backfill could be merged and deployed and silently never run. `--pending` is the alarm
359
+ that closes it; `registeredBackfills()` is what makes a declaration visible before its first pass.
360
+
361
+ `x db migrate` and `ROLE=migrate` are the same function call. That is the whole design: until
362
+ 1.2.0 the CLI shelled out to `bunx drizzle-kit` — a second engine, a second journal, declared in no
363
+ `package.json` and fetched unpinned at run time — while the release phase used the framework's
364
+ ledger, so "what has been applied" had two answers that only agreed by luck. `cmd-db.test.ts`
365
+ holds the line from both ends: no shipped source spawns a second migrator, and this file still
366
+ imports `runMigrations` from `./serve`.
367
+
368
+ **The post-condition is one check too, and it is the database one.** `runMigrations` runs
369
+ `@ultimat3/db`'s `checkDrift()` inside the queue's lifetime — the connection it opened for the
370
+ migrator is the only one there is — and returns the report on `MigratedApp.drift`, so a developer
371
+ and a release phase verify the same thing. `x db migrate` renders it through `driftFindings` and
372
+ exits non-zero; `runRole` throws the first difference for `ROLE=migrate`, so the release phase
373
+ exits non-zero too. Both entrypoints call the same `runMigrations` and both fail — the difference
374
+ is only the channel each has. `ROLE=migrate` logged and exited 0 until it did not: a release phase
375
+ whose only signal is the exit code reported success over a schema nobody can reconstruct, which is
376
+ the failure the post-migrate check exists to catch.
377
+
378
+ **The `drift` step asks a third thing, off the same directory and with no database either: is every
379
+ destructive statement declared?** `db-destructive.ts` reads each committed migration through
380
+ `migrations.ts` — the reader `x db migrate` applies from, because a rail checking a list the
381
+ migrator does not run enforces nothing — and refuses an `up` that drops a table, drops a column,
382
+ truncates or retypes without a `-- destructive: true` line, as `X_MIGRATION_DESTRUCTIVE`. It decides
383
+ none of that itself: `@ultimat3/db`'s `destructive.ts` owns the classifier `db-generate.ts` already
384
+ wrote the marker from, so the generator and the gate cannot disagree about one file. One finding per
385
+ file, never one per statement — the marker declares the whole migration. It rides on `drift` rather
386
+ than becoming an eighteenth step because it is this step's own question over this step's own files;
387
+ a new step is for a genuinely new question.
388
+
389
+ The *source* half is a different question with a different answer: `checkSourceDrift` hashes the
390
+ entity source against what `x db gen` recorded, answers the same before and after a migration, and
391
+ opens nothing — which is what lets the gate run it in a CI with no database. It stays on `x verify`
392
+ and `x doctor` and is deliberately **not** repeated on `x db migrate`; two reporters of one
393
+ condition is the duplication this package's own rule forbids. Both were called `checkDrift` until
394
+ 1.2.0, and the one that was wired everywhere was the one that cannot see a column added by hand.
395
+
396
+ Generation opens no database. It diffs `describeEntities()` against `declaredSchema(readMigrations(root))`
397
+ — the snapshot the newest migration wrote down — so `x db gen` answers the same in CI, on a laptop
398
+ with nothing running, and against a database three migrations behind. An app whose modules will not
399
+ load generates **nothing**: a short registry is indistinguishable from deleted entities, and the
400
+ diff would be a DROP nobody asked for.
401
+
402
+ One migration is one file, split by a lone `-- down` line. `<id>.down.sql` is a pre-1.2.0
403
+ hand-written layout and `readMigrations` skips it — read as a migration it sorts next to its own
404
+ `up` and drops every table the pair exists to reverse.
405
+
406
+ ## `x dev` boots the app; it does not simulate one
407
+
408
+ | File | Job |
409
+ |---|---|
410
+ | `api-routes.ts` | the app's API over HTTP: every registered action AND every registered query |
411
+ | `dev-services.ts` | resolve which service each binding points at — embedded or external |
412
+ | `dev-queue.ts` | the db + queue pair alone, and the one place that takes every ambient accessor back |
413
+ | `dev-runtime.ts` | start the rest on top of it and install the remaining accessors (storage, mail, transport) |
414
+ | `dev-cache.ts` | which cache tiers this process reads through, and the cross-instance invalidation hop |
415
+ | `dev-sync.ts` | the `sync` role: its live-query registry, who is dialling it, and the socket it owns |
416
+ | `runtime-overrides.ts` | the one field a host hands the framework a driver through |
417
+ | `sync-authenticator.ts` | the app's HTTP authenticator, seen as the sync node's |
418
+ | `otlp-export.ts` | the exporters `OTEL_EXPORTER_OTLP_ENDPOINT` switches on, and their drain hooks |
419
+ | `dev-render.ts` | one HTTP route per registered `route`, through render's own mode function |
420
+ | `style-csp.ts` | the `style-src` sha256 of every inline `<style>` the web role serves |
421
+ | `dev-assets.ts` | the image pipeline's only HTTP surface: `/icons/*` and `/media/*` |
422
+ | `dev-hooks.ts` | the pipeline's `authorize` seam, decided from the app's own `Policy` objects |
423
+ | `dev-roles.ts` | `--role` selection plus start/stop for `web`, `sync`, `worker`, `scheduler` |
424
+ | `dev-dashboard.ts` | the `DevSources` hooks only this process can answer, and the two CLI panels |
425
+ | `dev-traces.ts` | core's spans → the `/_x` timeline's request traces |
426
+ | `dev-n-plus-one.ts` | statement shapes counted per request, and the ones that repeat past the threshold |
427
+ | `statement-loop.ts` | one verdict → the finding, the panel fact, the overlay notice and the log line |
428
+ | `dev-policy.ts` | which actors to ask about, and which capability each policy gates |
429
+ | `cmd-dev.ts` | boot order, mounting `/_x`, installing the span exporter, the file watcher |
430
+ | `mcp-host.ts` | the `DevCapabilities` half of `@ultimat3/mcp`'s `DevHost` — db, tests, logs, verify |
431
+ | `mcp-db-target.ts` | which database the host is pointed at: whether it is a branch, and whether it is production |
432
+ | `mcp-errors.ts` | `errors.explain`: one runnable command per code, typed over `CliErrorCode` |
433
+ | `error-catalog.ts` | imports every `@ultimat3/*` package so `x errors` answers for codes no command loads |
434
+ | `mcp-test-output.ts` | reading `bun test`'s own summary back into a `TestRun` |
435
+ | `cmd-mcp.ts` | `x mcp serve`: the two transports, and the local developer's caller |
436
+
437
+ `api-routes.ts` is the app's own API surface, composed **once** and mounted by both `cmd-dev.ts`
438
+ and `serve.ts`: `listActions().map(toRoute)` from `@ultimat3/action` plus
439
+ `listQueries().map(toQueryRoute)` from `@ultimat3/query`. Two lists is how `query.client()`
440
+ shipped deriving `/_x/query/<kebab>` against a route neither file mounted — a typed read that
441
+ compiled everywhere and 404'd everywhere — and a surface that answers in `x dev` and not in the
442
+ container is the same failure one release later. It reads the registries at call time, never at
443
+ import: importing the app IS the registration, and it happens after this module loads.
444
+
445
+ `startWeb` warns when the route table declares `auth: 'required'` and the app configured no
446
+ authenticator: `hooks.authenticate` is the only place an actor can come from, so such a process
447
+ boots clean, reports healthy, and refuses every valid session. A warning and not a throw, because
448
+ `x new` scaffolds guarded routes before it scaffolds an authenticator.
449
+
450
+ The roles live in `@ultimat3/core` (`ROLES`, `isRole`), never in a second list here. A dev-only
451
+ driver, a dev-only authorizer or a dev-only queue is the bug this design exists to prevent — the
452
+ only thing dev changes is which driver is behind an interface.
453
+
454
+ ### `RuntimeOverrides` is the only way to hand the framework a driver
455
+
456
+ `ServeOptions` was `{ root, env, role?, port?, metricsPort? }`, so the ONLY way an app could
457
+ install a driver was an ambient setter at module scope — and `loadApp` imports the app's modules
458
+ *after* `startServices` has captured its own. The slot moved and the capture did not: every
459
+ `handle.enqueue()` went to the app's queue while the worker claimed from Postgres, and `/_x` read
460
+ the ambient one, so the dashboard agreed with the enqueue side and disagreed with reality.
461
+
462
+ Every field REPLACES the env-selected default rather than sitting beside it — `overrides?.x ?? <the
463
+ env switch>`, one expression, one answer (axiom 1). A field nothing consumes is not there: the
464
+ entity `Driver` in particular, because `@ultimat3/entity` exposes no installer for one
465
+ (`database(entities, { driver })` is the app's own call), and a slot the boot cannot honour is the
466
+ class of defect this seam exists to end.
467
+
468
+ **The split is refused, not reconciled.** `assertOneJobDriver` runs first in `startRoles` and
469
+ throws `X_RUNTIME_DRIVER_SPLIT` when `jobDriver()` is not the object this process serves. Reading
470
+ through the accessor instead would make the split invisible rather than impossible — and the app
471
+ would still have installed a driver the boot never saw, with no outbox store bound to it and no
472
+ relay draining it.
473
+
474
+ ### What the boot now calls that nothing called before
475
+
476
+ | Mechanism | Where | Was |
477
+ |---|---|---|
478
+ | the transactional outbox | `dev-queue.ts` installs the store + facade, `worker` runs the relay | staged rows nothing published |
479
+ | the durable scheduler | `pgSchedulerState` + `createPgLeaseLeader` in `startRoles` | a watermark forgotten on restart, and every replica its own leader |
480
+ | the Postgres event bus | `dev-queue.ts` | `step.waitForEvent` forgot every correlation on restart |
481
+ | the shared idempotency store | `dev-queue.ts` | a retry on another replica charged the card twice |
482
+ | the cache tiers | `dev-cache.ts` | only the CDN tier was registered; memo, LRU and Redis had zero callers |
483
+ | WebSocket authentication | `dev-sync.ts` | `actorId: null` on every socket — realtime was single-tenant by wiring |
484
+ | OTLP export | `otlp-export.ts` | the chart set the variable and no code read it |
485
+
486
+ `createPgLeaseLeader`, never `createPgLeader`: the latter's `pg_try_advisory_lock` is
487
+ session-scoped and the grant dies when the connection returns to the pool, so every node reads
488
+ itself as leader and a rolling update double-fires every task.
489
+
490
+ The relay runs on `worker` and only `worker` — the role that exists wherever jobs run at all.
491
+ Duplicating it is safe (publish-then-mark is at-least-once and the idempotency key collapses the
492
+ repeat) but pointless.
493
+
494
+ `SQL_IDEMPOTENCY_TABLE` is applied beside `SQL_JOBS_TABLE`, and the store is installed by the boot
495
+ rather than by the app, even though `@ultimat3/action` documents
496
+ `postgresIdempotencyStore({ executor: Bun.sql })`: **`Bun.sql` has no `.query(text, values)`** — it
497
+ is a tagged template whose positional form is `unsafe` — so that line does not satisfy `PgExecutor`,
498
+ and a second executor would open a second pool against a URL this boot already resolved. The app
499
+ owes only the declaration, `configureIdempotency({ scope: 'shared' })`, which `x new` names in
500
+ `apps/web/server.ts`.
501
+
502
+ The per-TENANT subscription cap is deliberately unset, and **both halves of it are**:
503
+ `assertCapacity` returns early unless `maxPerTenant` AND `tenantOf` are given, so passing one arms
504
+ nothing — and no default is defensible when one tenant is a person and the next is five thousand
505
+ seats. The per-socket 128 stands because a socket is one browser tab.
506
+
507
+ `trustProxy` is read from `TRUSTED_PROXY_HOPS` in `startWeb`, the way `PORT` and `ROLE` are read: it
508
+ is a fact about the deployment, not an app config choice, and one image runs behind an ingress in
509
+ one cluster and behind nothing on a laptop. Without it `ctx.ip` is the ingress's socket address on
510
+ every request, so the limiter keys the whole fleet's anonymous traffic into one bucket.
511
+
512
+ ### `island-bundle.ts` is the bundler half of `hydrate`
513
+
514
+ `@ultimat3/render` shipped `island()`, the collector, `emitIslandAttributes`, `hydrateRuntime`,
515
+ `RouteEntry.islands` and `routeJsBytes` — and **nothing constructed or populated any of them**.
516
+ `hydrate` was a documented capability with no implementation, to the point that `render-static.ts`
517
+ told authors to "move the request-dependent part into an island", naming a mechanism the framework
518
+ could not express. This package is the half that can see a file on disk, so it is the half that was
519
+ missing.
520
+
521
+ | File | Job |
522
+ |---|---|
523
+ | `island-bundle.ts` | discover `*.island.tsx`, build each as its own entry point, hash it, resolve a page's specifier to its URL |
524
+ | `island-routes.ts` | serve those chunks, at `ISLAND_BASE_PATH`, immutable |
525
+ | `dev-render.ts` | one collector **per render**, and `hydrateRuntime` after the body |
526
+ | `prerender.ts` | build first, write the chunks into the export, then measure |
527
+ | `budgets.ts` | `measureDocumentJs` weighs `data-x-entry` as well as `<script src>` |
528
+
529
+ **One `Bun.build` per island, never one call with N entry points**, and `splitting: false`. The
530
+ island's `src` is a string, so no import edge reaches it and the page's graph stays the page's
531
+ (axiom 6) — a shared chunk would put that number back behind a graph walk, and the budget compares
532
+ against bytes. Two islands that both import the same helper each carry a copy; that is the honest
533
+ number for what booting either one costs.
534
+
535
+ **The chunk URL is content-addressed with render's own `contentHash`** — the function that already
536
+ stamps an ETag and a precache revision. One identity for a byte string, not a third.
537
+
538
+ **`x dev`, the container and the static export all mount the same table.** `serve.ts` builds the
539
+ islands at boot for the same reason it mounts `apiRoutes()`: a seam that works in dev and not in the
540
+ image is the same failure one release later. `x dev` rebuilds them on the watcher tick, and that is
541
+ the one reload that actually takes effect — an island is the single module this process never
542
+ imports, so there is no Bun module cache to invalidate.
543
+
544
+ **`app-load.ts` skips `*.island.tsx` deliberately.** It registers no primitive, and importing it
545
+ would put the one module guaranteed to be outside the server's graph inside this process's, where a
546
+ top-level `document` reference takes the whole scan down.
547
+
548
+ **The budget is charged from the emitted document, and it names the island.** An island's chunk is
549
+ reached by `import()` from inside the hydration runtime, so it is never a `<script src>` — weighing
550
+ script tags alone charged a page for the runtime and never for the code that runtime boots.
551
+ `measureDocumentJs` reads `data-x-entry` as what it is, dedupes it (two instances of one island are
552
+ one module), and `prerenderSite` maps the heaviest URL back through the bundle so
553
+ `X_BUDGET_EXCEEDED` names `apps/web/site/pricing/calculator.island.tsx` and not a hash.
554
+
555
+ `X_ISLAND_INVALID` is **borrowed** from `@ultimat3/render`, not twinned: "this src cannot become a
556
+ client entry" is what that code already means, and the bundler is simply the half that can see
557
+ whether the file exists. A failed compile is `X_BUILD_FAILED` — an island is a bundle entry point
558
+ like any other, and `Bun.build` *rejects* rather than answering `success: false`, so the catch is
559
+ the real path.
560
+
561
+ ### `dev-assets.ts` is where the image pipeline meets HTTP
562
+
563
+ Three packages declare what an image is and none of them serves one: `@ultimat3/seo` says what a
564
+ variant URL means (`parseImageQuery`) and produces the bytes (`builtinImageDriver`),
565
+ `@ultimat3/storage` says what a variant is called and where it is cached (`variantKey`), and
566
+ `@ultimat3/pwa` says which icons a web manifest promises (`planIcons`, `BuiltinImagePipeline`).
567
+ Pixels are `@ultimat3/core`'s pipeline, only ever. This file picks two base paths — `ICON_BASE_PATH`
568
+ and `MEDIA_BASE_PATH` — and decides nothing else; a resize, a format table or a second cache key
569
+ here is the drift the split exists to prevent.
570
+
571
+ **`/media` and `/_storage` are one authz decision, not two.** Both serve objects off the app's only
572
+ disk, so `/media/*key` declares what `dev-storage.ts` declares — `auth: 'required'` +
573
+ `STORAGE_READ_PERMISSION` + `enforcedBy: 'handler'` — and calls the same two functions, in the same
574
+ order: `authorizeStorageRead` then `assertReadableKey`. It shipped `auth: 'public'` with no policy
575
+ and no tenant check while its twin required both, which made every tenant's uploads one URL away in
576
+ production (`serve.ts` mounts it), and `?w=` made it an unauthenticated `put` besides. The tenant
577
+ test lives in ONE function both routes call, and `storage-surfaces.test.ts` pins the pair against
578
+ each other — every case names the verdict absolutely as well as comparing the two, because equality
579
+ alone is satisfied by both surfaces failing open together. Cacheability follows the key, not the
580
+ route: a tenant-scoped key takes `AUTHORIZED_OBJECT_CACHE` (`private, max-age=0`, varying on
581
+ `authorization`/`cookie`), and only a key no tenant owns keeps `immutable`. A genuinely public image
582
+ belongs under `apps/web/site/`, which is a static asset and never touches that disk.
583
+
584
+ `ICON_SOURCE` lives here, not in `cmd-doctor.ts`, because this is the module that reads it: the
585
+ diagnostic checks what `x dev` serves, so one constant cannot pass the check and serve nothing.
586
+ It is a **PNG** — core decodes PNG and JPEG only, and the SVG this used to name could never
587
+ become an icon.
588
+
589
+ The routes mount whether or not the source icon exists, and a missing one is refused with
590
+ `X_PWA_ICON_MISSING` and its fix — a route that silently disappears is a 404 whose meaning an agent
591
+ has to guess. Deliberately **not** also a boot finding: `x doctor` already reports this condition,
592
+ with this code, and two reporters of one condition is the duplication this package's own rule
593
+ forbids. `x dev` owns the runtime half; the diagnostic owns the other.
594
+
595
+ ### `hold.ts` is why a long-running command outlives its own result
596
+
597
+ `dispatch` renders a `CommandResult` and `bin.ts` exits on the code — so a command whose server is
598
+ still listening when `run` resolves is a command the exit code takes down, between the line that
599
+ announced the url and the first request to it. `x dev` and `x mcp serve --transport http` both did.
600
+
601
+ The one answer is `CommandResult.hold`: report first, then `dispatch` awaits the hold before the
602
+ exit code. `holdUntilShutdown` installs core's signal handlers (`installSignalHandlers` — until
603
+ this it had no callers anywhere, which is why `cmd-mcp.ts`'s `onShutdown` registration was never
604
+ reached), waits on the **drain's first phase** rather than on a signal list of its own, and
605
+ releases what core's lifecycle never learned about — the embedded Postgres, the worker, the
606
+ watcher — *after* the drain, so an in-flight request still has the database it opened against.
607
+ Ctrl-C is therefore the same three phases production runs, not a kill that leaves `.x/pgdata`
608
+ locked by a process that no longer exists.
609
+
610
+ Commands: `bun test`, `bunx tsc --noEmit -p tsconfig.json`.
611
+
612
+ ## Planned commands are commands
613
+
614
+ Every command in `wiki/CLI-Reference.md`'s planned table is in the registry, built from
615
+ `PLANNED_COMMANDS` in `cmd-planned.ts`, and exits `X_NOT_IMPLEMENTED` with a `fix:` naming the
616
+ closest **shipped** command. `X_CLI_UNKNOWN_COMMAND` would say "you typed something that does not
617
+ exist", which is false and sends an agent hunting a typo. `cmd-planned.test.ts` enforces both
618
+ halves: every row is reachable through the parser, and no `fix` points at another planned command.
619
+
620
+ `PLANNED_SUBCOMMANDS` is the same promise one level down, and `x db studio` is its only entry.
621
+ A subcommand stays in its command's `subcommands` list — the parser reaches it, `x help db` lists
622
+ it — and the owning `run` does `throw plannedSubcommand('db', 'studio')`. Dropping it from the list
623
+ instead would answer `X_CLI_UNKNOWN_SUBCOMMAND`, which is the same lie the table above closes.
624
+
625
+ ## `guards/` is how an app makes its own convention a build error
626
+
627
+ Axiom 3 says a convention that is not a build error does not exist, and until 1.2.0 the framework
628
+ gave an app no way to create one: `VERIFY_STEP_NAMES` is a closed literal list with no extension
629
+ point. A file in `guards/` closes it.
630
+
631
+ | File | Job |
632
+ |---|---|
633
+ | `guards.ts` | what a guard IS, how the directory is read, and what a guard is held to |
634
+ | `templates/guard.ts` | `x g guard <name>` — the emitted rule, its pure half and its test |
635
+ | `cmd-verify.ts` | one line in the `boundaries` step: `guardFindings(ctx.root)` |
636
+
637
+ **It rides on `boundaries`, and it is not an eighteenth step.** The `HostCheck` contract already
638
+ says the shape — *a host adds findings to a step; it can never add, remove, reorder or skip one* —
639
+ so "green" keeps meaning exactly what it meant, whatever an app writes. `boundaries` is the step
640
+ whose host slot already carries "rules this repo makes about itself that the framework cannot
641
+ know" (the monorepo's tier table arrives through it), and it runs third, before any suite, so a
642
+ convention failure comes back in seconds. `guardFindings` is *typed* as a `HostCheck` and is
643
+ composed by the step rather than registered as one: the slot is `Partial<Record<VerifyStepName,
644
+ HostCheck>>`, one function per step, so an app registering there would evict the framework's own
645
+ tier check — and `verifyCommand.run` passes no `hostChecks` at all, which is why an app-supplied
646
+ check could not have reached the gate through that field in the first place.
647
+
648
+ **Discovered, never registered.** `guards/*.ts`, minus `*.test.ts`, sorted. Nothing imports a
649
+ guard, nothing lists one, and there is no `defineGuard` to call — a guard that has to announce
650
+ itself is a guard an app can forget to announce, which is the coupling axiom 8's extension model
651
+ rejects. A `*.test.ts` beside a guard is its test: importing it would run a suite inside the gate.
652
+
653
+ **A guard returns `Finding[]`, so it inherits everything.** `--json`, the step table, the summary
654
+ counts and the exit code are all projections of what it returns (axiom 2); a guard that printed or
655
+ chose an exit code would be a second gate. It never throws for a normal result — a throw is
656
+ `X_GUARD_FAILED`, reported as a finding rather than taking the run down.
657
+
658
+ **And what it returns is held to the error contract.** `findingProblem` demands an
659
+ `X_SCREAMING_SNAKE` code, a non-empty cause, and a `fix:` that passes `fixProblem` — the *same*
660
+ rule `x verify`'s `errors` step applies to every shipped `fix:` in this repo. It runs on the
661
+ returned value, which is the half a static scan cannot reach: a `fix` assembled at run time has no
662
+ literal to read. Three codes, one per way a guard can fail to be one — `X_GUARD_INVALID` (no
663
+ usable export), `X_GUARD_FAILED` (it threw), `X_GUARD_FINDING_INVALID` (what it returned is not a
664
+ finding). Anything else about a guard is the app's business: no size ceiling, no budget, no rule
665
+ about what it may check.
666
+
667
+ **The validator may never be the thing that crashes.** `findingProblem` names an offending value
668
+ through `shown()` and not `JSON.stringify` — which refuses a BigInt — and every candidate is read
669
+ inside a `try`, because reading one can throw on its own (a getter that raises, a proxy that
670
+ refuses). A guard returning `[1n]` is `X_GUARD_FINDING_INVALID`, per candidate, so one unreadable
671
+ entry costs its own line and not the real findings beside it. The mechanism whose job is producing
672
+ structured failures handing back a stack trace is the one outcome it exists to prevent.
673
+
674
+ `x g guard <name>` writes `guards/<name>.ts` and its test, and nothing else — no index, no
675
+ registry row, no manifest entry. The emitted rule is the class of failure a guard exists for: a
676
+ migration that adds a `NOT NULL` column with no `DEFAULT` applies cleanly to an empty local
677
+ database and fails on the first production table that already holds rows. The `drift` step reads
678
+ those same files and asks a different question, and a test suite runs against a database the
679
+ statement has never met — which is exactly when an app needs a rule of its own. Its code is
680
+ DERIVED from the guard's name (`guardCode`), never written as a literal: an `X_*` literal in
681
+ framework source is a framework code and `error-catalog.test.ts` requires it to be registered.
682
+
683
+ That rule is held to a real bar, because it is the worked example every app starts from and a
684
+ demonstration that is wrong on realistic input teaches the wrong shape. Block comments are
685
+ stripped before line comments and both before statements are split, so a commented-out
686
+ `ALTER TABLE` is a note and not a finding that blocks `x verify` over nothing; and `DEFAULT NULL`
687
+ counts as **no** default, because it is one in syntax and none in effect — every existing row still
688
+ takes NULL and still violates `NOT NULL`. Both cases are in the emitted test, which is what proves
689
+ an app's copy still works, and both run through the real seam in `guards.test.ts`.
690
+
691
+ It is in `FIXTURE_GENERATORS` like the other two, and it is the only generated file that imports
692
+ `@ultimat3/cli` for its types — so the scaffold gate compiling it is what proves a scaffolded app
693
+ can write one at all. The root `tsconfig.json` `x new` scaffolds has no `include`, so `guards/` is
694
+ typechecked there by default; an app whose tsconfig names an explicit `include` list has to add
695
+ `guards/**/*` to it, or its guards compile nowhere.
696
+
697
+ ## Two generators that scaffold something other than a primitive
698
+
699
+ `x g island <name> [--at <dir>]` writes a **client entry point**, not a component: the filename is
700
+ how the bundler discovers it and `mount` is how the hydration runtime calls it, so those two are
701
+ what `templates/island.test.ts` pins and everything else in the file is example code. `--at` takes
702
+ the directory directly rather than deriving one, because the caller that cannot guess is
703
+ `X_ISLAND_INVALID` — its cause already holds the exact path a page's `src` resolved to, so its
704
+ `fix:` hands that path straight back.
705
+
706
+ `x g admin:page <name> --permission <perm> [--at <dir>]` writes an ordinary TSX component and **no
707
+ `defineRoute` call**, deliberately. `@ultimat3/admin`'s `pages:` is the one thing that puts a page in the route
708
+ table and `guardedPage()` is the one thing that decides it; a generator that emitted a route
709
+ declaration would hand back the unguarded second way in that seam exists to close. The emitted test
710
+ asserts the absence. `--permission` defaults to `<name>:read` rather than to nothing, because an
711
+ empty permission list is `X_ADMIN_PAGE_UNGUARDED` at declaration time. `--at` is the same flag
712
+ `x g island` takes and for the same reason — an app's admin is wherever its `defineAdmin` is, which
713
+ no generator can derive, and the hardcoded `apps/admin/src/pages` sent every other layout (the
714
+ demo's is `apps/admin/app/admin`) to `git mv` after every run.
715
+
716
+ Both are in `FIXTURE_GENERATORS`, so both are compiled by the scaffold gate.
717
+
718
+ Implementing one means deleting its row and adding a real `cmd-<name>.ts` — the summary's
719
+ `(planned)` suffix disappears with it, and `x help` follows automatically.
720
+
721
+ Adding a command: write `cmd-<name>.ts` exporting a `CliCommand`, register it in `registry.ts`,
722
+ add its message keys to `messages.ts`. Help and parsing derive from the spec automatically. A
723
+ command's `run` must be `async`: a synchronous throw escapes every caller that awaits the promise
724
+ the signature promises, `dispatch`'s own error path included.