@ultimat3/cli 29.4.0 → 30.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 (448) hide show
  1. package/CLAUDE.md +64 -40
  2. package/README.md +61 -11
  3. package/package.json +42 -28
  4. package/src/affected-tests.ts +97 -0
  5. package/src/affected-unowned.ts +43 -0
  6. package/src/affected.ts +25 -6
  7. package/src/app-boundaries.ts +29 -1
  8. package/src/app-evals.ts +12 -4
  9. package/src/app-load-unresolved.ts +62 -0
  10. package/src/app-load.ts +30 -37
  11. package/src/app-mcp.ts +14 -2
  12. package/src/app-module-graph.ts +232 -0
  13. package/src/app-reload-graph.ts +2 -2
  14. package/src/app-root.ts +37 -1
  15. package/src/bin.ts +1 -0
  16. package/src/boundary-findings.ts +6 -0
  17. package/src/budgets-freshness.ts +32 -0
  18. package/src/budgets.ts +20 -1
  19. package/src/build-capture-env.ts +9 -0
  20. package/src/cdp-shot-a11y.ts +2 -39
  21. package/src/ci-tested.ts +243 -0
  22. package/src/client-emit-customer-runtime.ts +139 -0
  23. package/src/client-emit-customer.ts +108 -0
  24. package/src/client-emit-drift.ts +72 -0
  25. package/src/client-emit-header.ts +67 -0
  26. package/src/client-emit-schemas.ts +175 -0
  27. package/src/client-emit-types.ts +121 -0
  28. package/src/client-emit-vendor.ts +146 -0
  29. package/src/client-errors-routes.ts +229 -0
  30. package/src/client-errors-script.ts +74 -0
  31. package/src/client-vitals-route.ts +197 -0
  32. package/src/client-vitals-script.ts +56 -0
  33. package/src/cmd-affected.ts +23 -0
  34. package/src/cmd-build-spec.ts +7 -1
  35. package/src/cmd-build.ts +35 -9
  36. package/src/cmd-ci-spec.ts +42 -6
  37. package/src/cmd-ci.ts +99 -2
  38. package/src/cmd-client-customer.ts +134 -0
  39. package/src/cmd-client-source.ts +81 -0
  40. package/src/cmd-client-spec.ts +48 -0
  41. package/src/cmd-client-write.ts +41 -0
  42. package/src/cmd-client.ts +111 -0
  43. package/src/cmd-db-recount.ts +120 -0
  44. package/src/cmd-db-spec.ts +36 -6
  45. package/src/cmd-db.ts +11 -3
  46. package/src/cmd-deploy-compose.ts +61 -3
  47. package/src/cmd-deploy-drain.ts +10 -2
  48. package/src/cmd-deploy-helm.ts +11 -0
  49. package/src/cmd-deploy-preflight.ts +118 -0
  50. package/src/cmd-deploy-spec.ts +58 -6
  51. package/src/cmd-deploy-verify.ts +380 -0
  52. package/src/cmd-deploy.ts +42 -9
  53. package/src/cmd-docs.ts +34 -4
  54. package/src/cmd-doctor.ts +66 -9
  55. package/src/cmd-drill-spec.ts +25 -0
  56. package/src/cmd-drill.ts +176 -0
  57. package/src/cmd-env-init.ts +102 -0
  58. package/src/cmd-env-spec.ts +10 -3
  59. package/src/cmd-env.ts +15 -10
  60. package/src/cmd-errors-recent.ts +60 -0
  61. package/src/cmd-errors-spec.ts +9 -1
  62. package/src/cmd-errors.ts +4 -0
  63. package/src/cmd-exercise-spec.ts +19 -0
  64. package/src/cmd-exercise.ts +129 -0
  65. package/src/cmd-generate-spec.ts +9 -1
  66. package/src/cmd-generate.ts +32 -27
  67. package/src/cmd-graph-load.ts +97 -0
  68. package/src/cmd-graph-spec.ts +16 -0
  69. package/src/cmd-graph.ts +73 -0
  70. package/src/cmd-i18n.ts +4 -1
  71. package/src/cmd-impact-spec.ts +13 -0
  72. package/src/cmd-impact.ts +64 -0
  73. package/src/cmd-jobs-resolve.ts +76 -0
  74. package/src/cmd-jobs-spec.ts +21 -2
  75. package/src/cmd-jobs.ts +10 -6
  76. package/src/cmd-mail-spec.ts +28 -0
  77. package/src/cmd-mail.ts +70 -0
  78. package/src/cmd-mcp-lock.ts +69 -0
  79. package/src/cmd-mcp-spec.ts +23 -4
  80. package/src/cmd-mcp.ts +17 -3
  81. package/src/cmd-new-spec.ts +8 -1
  82. package/src/cmd-new.ts +61 -9
  83. package/src/cmd-planned.ts +0 -6
  84. package/src/cmd-repro-spec.ts +31 -0
  85. package/src/cmd-repro.ts +174 -0
  86. package/src/cmd-stop-spec.ts +21 -0
  87. package/src/cmd-stop.ts +166 -0
  88. package/src/cmd-test-spec.ts +17 -4
  89. package/src/cmd-test.ts +70 -14
  90. package/src/cmd-upgrade-spec.ts +26 -0
  91. package/src/cmd-upgrade.ts +274 -0
  92. package/src/cmd-verify-spec.ts +3 -3
  93. package/src/db-backfill.ts +6 -0
  94. package/src/db-diagnostics.ts +106 -0
  95. package/src/db-generate.ts +9 -0
  96. package/src/db-reset-template.ts +1 -1
  97. package/src/db-rolling.ts +45 -0
  98. package/src/db-seed-generate.ts +188 -0
  99. package/src/deploy-verify-findings.ts +98 -0
  100. package/src/deploy-verify-flags.ts +109 -0
  101. package/src/deploy-verify-grade.ts +185 -0
  102. package/src/deploy-verify-queues.ts +53 -0
  103. package/src/dev-boot.ts +35 -15
  104. package/src/dev-child-watch.ts +16 -4
  105. package/src/dev-dashboard-guard.ts +8 -1
  106. package/src/dev-dashboard.ts +7 -0
  107. package/src/dev-lock-identity.ts +61 -0
  108. package/src/dev-lock-port-holder.ts +80 -0
  109. package/src/dev-lock-steal.ts +124 -0
  110. package/src/dev-lock.ts +92 -120
  111. package/src/dev-mail-templates.ts +51 -0
  112. package/src/dev-rebuild.ts +26 -2
  113. package/src/dev-route-table.ts +17 -0
  114. package/src/dev-supervisor.ts +5 -2
  115. package/src/dev-watch-tree.ts +11 -10
  116. package/src/dev-watch.ts +1 -1
  117. package/src/dispatch.ts +29 -6
  118. package/src/docs-section.ts +69 -0
  119. package/src/docs-sources.ts +100 -0
  120. package/src/doctor-auth.ts +11 -2
  121. package/src/doctor-chart-alerts.ts +55 -0
  122. package/src/doctor-pooler.ts +107 -0
  123. package/src/doctor-rolling.ts +35 -0
  124. package/src/doctor-row-security.ts +40 -0
  125. package/src/document-options.ts +13 -0
  126. package/src/drill-sync-lane.ts +25 -0
  127. package/src/drill-targets.ts +39 -0
  128. package/src/duplicate-packages.ts +1 -1
  129. package/src/env-production.ts +93 -0
  130. package/src/error-codes-shot.ts +59 -0
  131. package/src/error-codes-tooling.ts +29 -0
  132. package/src/error-codes.ts +100 -54
  133. package/src/error-contract.ts +2 -2
  134. package/src/errors.ts +22 -3
  135. package/src/exercise-database.ts +99 -0
  136. package/src/exercise-findings.ts +159 -0
  137. package/src/exercise-overrides.ts +42 -0
  138. package/src/exercise-registration.ts +58 -0
  139. package/src/exercise-static.ts +88 -0
  140. package/src/exercise-subject.ts +69 -0
  141. package/src/failure-capture-boot.ts +30 -0
  142. package/src/framework-env.ts +7 -1
  143. package/src/framework-schema.ts +72 -5
  144. package/src/generate-catalog-locales.ts +19 -0
  145. package/src/generate-chart-alerts.ts +156 -0
  146. package/src/generate-feature-required.ts +65 -0
  147. package/src/generate-feature.ts +83 -6
  148. package/src/generate-files.ts +24 -0
  149. package/src/generate-format-written.ts +32 -0
  150. package/src/generate-kinds.ts +6 -0
  151. package/src/generate-sign-in-config.ts +100 -0
  152. package/src/generate-sign-in-manifest.ts +28 -0
  153. package/src/generate-sign-in-mode.ts +101 -0
  154. package/src/generate-sign-in.ts +250 -0
  155. package/src/generate-write.ts +44 -7
  156. package/src/hand-rolled-socket.ts +72 -0
  157. package/src/i18n-app-key-shapes.ts +29 -0
  158. package/src/i18n-audit.ts +10 -3
  159. package/src/i18n-registration.ts +18 -3
  160. package/src/index.ts +2 -0
  161. package/src/island-bundle.ts +4 -3
  162. package/src/island-disposer.ts +116 -0
  163. package/src/island-realtime-state.ts +34 -6
  164. package/src/island-realtime.ts +6 -2
  165. package/src/island-routes.ts +3 -2
  166. package/src/isr-query-advice.ts +6 -11
  167. package/src/isr-query-guard.ts +4 -3
  168. package/src/job-budget.ts +95 -0
  169. package/src/jobs-json.ts +11 -0
  170. package/src/lint-findings.ts +104 -0
  171. package/src/local-cli.ts +34 -4
  172. package/src/loopback-host.ts +2 -2
  173. package/src/mail-preview-findings.ts +24 -0
  174. package/src/mcp-errors.ts +82 -2
  175. package/src/mcp-host.ts +11 -1
  176. package/src/mcp-lock-findings.ts +57 -0
  177. package/src/measure-paths.ts +12 -4
  178. package/src/measure-scope.ts +27 -13
  179. package/src/messages-client.ts +23 -0
  180. package/src/messages-db.ts +56 -0
  181. package/src/messages-gate.ts +107 -0
  182. package/src/messages-repro.ts +11 -0
  183. package/src/messages-shot.ts +51 -0
  184. package/src/messages-upgrade.ts +45 -0
  185. package/src/messages.ts +53 -178
  186. package/src/metrics-endpoint.ts +23 -5
  187. package/src/new-shapes-generate.ts +71 -0
  188. package/src/new-shapes.ts +63 -0
  189. package/src/openapi-compile-ir.ts +68 -0
  190. package/src/openapi-compile-schema.ts +284 -0
  191. package/src/openapi-compile.ts +273 -0
  192. package/src/openapi-refuse.ts +45 -0
  193. package/src/otlp-export.ts +2 -1
  194. package/src/output-findings.ts +123 -0
  195. package/src/output.ts +32 -8
  196. package/src/package-boundaries.ts +110 -0
  197. package/src/page-pins.ts +23 -0
  198. package/src/plain-routes.ts +13 -0
  199. package/src/prerender-entries.ts +59 -0
  200. package/src/prerender-heaviest.ts +27 -0
  201. package/src/prerender-preload.ts +6 -0
  202. package/src/prerender-report.ts +12 -0
  203. package/src/prerender-seo-reads.ts +38 -0
  204. package/src/prerender-served.ts +22 -7
  205. package/src/prerender-vitals.ts +42 -0
  206. package/src/prerender.ts +61 -32
  207. package/src/pwa-artifacts.ts +4 -3
  208. package/src/pwa-manifest-locales.ts +1 -1
  209. package/src/registry.ts +18 -0
  210. package/src/repro-bundle.ts +65 -0
  211. package/src/repro-errors.ts +47 -0
  212. package/src/repro-job.ts +72 -0
  213. package/src/repro-literal.ts +41 -0
  214. package/src/repro-request.ts +48 -0
  215. package/src/repro-write.ts +100 -0
  216. package/src/retain-sweep.ts +27 -0
  217. package/src/retained-assets-boot.ts +113 -0
  218. package/src/retained-assets-layout.ts +110 -0
  219. package/src/retained-assets-read.ts +142 -0
  220. package/src/retained-assets-sweep.ts +70 -0
  221. package/src/retained-assets.ts +171 -0
  222. package/src/revalidated-response.ts +8 -5
  223. package/src/role-replicator.ts +7 -1
  224. package/src/role-start-devices.ts +119 -0
  225. package/src/role-start-types.ts +6 -0
  226. package/src/role-start.ts +22 -5
  227. package/src/role-sync.ts +7 -1
  228. package/src/route-islands.ts +42 -0
  229. package/src/route-table.ts +2 -1
  230. package/src/row-security-boot.ts +58 -0
  231. package/src/row-security-wanted.ts +32 -0
  232. package/src/runtime-admin.ts +0 -1
  233. package/src/runtime-assets-site-cache.ts +55 -0
  234. package/src/runtime-assets-site.ts +136 -0
  235. package/src/runtime-assets.ts +99 -55
  236. package/src/runtime-bindings.ts +19 -3
  237. package/src/runtime-cache.ts +23 -14
  238. package/src/runtime-isr-outcome.ts +114 -60
  239. package/src/runtime-purge.ts +14 -2
  240. package/src/runtime-queue.ts +31 -4
  241. package/src/runtime-render-document.ts +204 -0
  242. package/src/runtime-render.ts +48 -198
  243. package/src/runtime-replica-position.ts +62 -0
  244. package/src/runtime-replica.ts +18 -3
  245. package/src/runtime-s3-path-style.ts +17 -0
  246. package/src/runtime-services.ts +19 -6
  247. package/src/runtime-statement-bound.ts +22 -0
  248. package/src/runtime-storage-upload.ts +9 -0
  249. package/src/runtime-storage.ts +41 -81
  250. package/src/schema-diff-views.ts +54 -0
  251. package/src/schema-diff.ts +21 -3
  252. package/src/schema-drift.ts +28 -9
  253. package/src/schema-dump-drift.ts +91 -7
  254. package/src/script-csp.ts +1 -1
  255. package/src/seo-cards.ts +26 -0
  256. package/src/seo-routes.ts +22 -8
  257. package/src/serve-boot.ts +54 -21
  258. package/src/serve-drain.ts +1 -0
  259. package/src/serve-env.ts +26 -8
  260. package/src/serve-retire.ts +12 -6
  261. package/src/serve-web.ts +42 -4
  262. package/src/serve.ts +9 -7
  263. package/src/site-asset-routes.ts +7 -1
  264. package/src/site-config.ts +4 -0
  265. package/src/site-llms-pages.ts +106 -0
  266. package/src/site-llms.ts +64 -0
  267. package/src/site-seo-write.ts +4 -2
  268. package/src/site-seo.ts +55 -6
  269. package/src/source-mtime.ts +50 -0
  270. package/src/static-capture-errors.ts +41 -0
  271. package/src/static-capture.ts +56 -0
  272. package/src/static-cards-serve.ts +39 -0
  273. package/src/static-cards.ts +93 -0
  274. package/src/static-document.ts +14 -13
  275. package/src/static-images.ts +115 -0
  276. package/src/stream-hole-scope.ts +55 -0
  277. package/src/style-bundle.ts +1 -1
  278. package/src/style-routes.ts +3 -2
  279. package/src/sw-precache-plan.ts +2 -2
  280. package/src/sw-routes.ts +2 -2
  281. package/src/templates/action-tests.ts +306 -0
  282. package/src/templates/action.ts +81 -269
  283. package/src/templates/backfill.ts +1 -1
  284. package/src/templates/entity.ts +21 -5
  285. package/src/templates/github/ci.yml.ts +8 -9
  286. package/src/templates/guard-bare-error.ts +2 -2
  287. package/src/templates/guard-error-declared.ts +263 -0
  288. package/src/templates/guard-native-confirm.ts +132 -0
  289. package/src/templates/guard-raw-fetch.ts +151 -0
  290. package/src/templates/guard-raw-interval.ts +121 -0
  291. package/src/templates/guard-repo-raw-sql.ts +71 -2
  292. package/src/templates/job.ts +1 -1
  293. package/src/templates/naming.ts +5 -0
  294. package/src/templates/pinned/docker-compose.yml +6 -0
  295. package/src/templates/pinned-images.ts +8 -0
  296. package/src/templates/policy.ts +85 -2
  297. package/src/templates/query.ts +70 -13
  298. package/src/templates/resource-create.ts +3 -1
  299. package/src/templates/resource-form-island.ts +15 -4
  300. package/src/templates/resource-page-db.ts +83 -0
  301. package/src/templates/resource-page.ts +161 -52
  302. package/src/templates/route.ts +4 -6
  303. package/src/templates/scaffold-app.ts +3 -0
  304. package/src/templates/scaffold-auth.ts +2 -1
  305. package/src/templates/scaffold-bin.ts +5 -52
  306. package/src/templates/scaffold-claude-agents.ts +19 -19
  307. package/src/templates/scaffold-claude-commands.ts +28 -31
  308. package/src/templates/scaffold-claude.ts +26 -23
  309. package/src/templates/scaffold-container-compose.ts +6 -1
  310. package/src/templates/scaffold-container.ts +24 -4
  311. package/src/templates/scaffold-dashboard-bare.ts +3 -3
  312. package/src/templates/scaffold-dashboard-example-tests.ts +49 -9
  313. package/src/templates/scaffold-dashboard-example-view.ts +156 -0
  314. package/src/templates/scaffold-dashboard-example.ts +43 -131
  315. package/src/templates/scaffold-dashboard-shared.ts +3 -7
  316. package/src/templates/scaffold-db-client.ts +20 -6
  317. package/src/templates/scaffold-db-package.ts +5 -14
  318. package/src/templates/scaffold-docs.ts +73 -57
  319. package/src/templates/scaffold-domain-package.ts +18 -14
  320. package/src/templates/scaffold-entries.ts +7 -2
  321. package/src/templates/scaffold-exercise.ts +37 -0
  322. package/src/templates/scaffold-guards.ts +13 -1
  323. package/src/templates/scaffold-helm-alerts.ts +234 -0
  324. package/src/templates/scaffold-helm-templates.ts +7 -5
  325. package/src/templates/scaffold-helm.ts +46 -12
  326. package/src/templates/scaffold-i18n.ts +6 -2
  327. package/src/templates/scaffold-mcp-endpoint.ts +182 -0
  328. package/src/templates/scaffold-mcp-json.ts +31 -0
  329. package/src/templates/scaffold-mcp-package.ts +59 -56
  330. package/src/templates/scaffold-repo.ts +7 -2
  331. package/src/templates/scaffold-shell.ts +45 -79
  332. package/src/templates/scaffold-test-services.ts +13 -5
  333. package/src/templates/shapes/api-keys.ts +170 -0
  334. package/src/templates/shapes/api.ts +101 -0
  335. package/src/templates/shapes/blog-site.ts +340 -0
  336. package/src/templates/shapes/blog-slice.ts +265 -0
  337. package/src/templates/shapes/blog.ts +114 -0
  338. package/src/templates/shapes/dashboard-detail.ts +242 -0
  339. package/src/templates/shapes/dashboard.ts +53 -0
  340. package/src/templates/shapes/index.ts +17 -0
  341. package/src/templates/shapes/overlay.ts +99 -0
  342. package/src/templates/shapes/shared-edits.ts +46 -0
  343. package/src/templates/sign-in-action-tests.ts +237 -0
  344. package/src/templates/sign-in-authenticator.ts +112 -0
  345. package/src/templates/sign-in-oidc.ts +189 -0
  346. package/src/templates/sign-in-pages.ts +148 -0
  347. package/src/templates/sign-in-password.ts +216 -0
  348. package/src/templates/sign-in-shared.ts +268 -0
  349. package/src/templates/slice-foundation.ts +70 -15
  350. package/src/test-batches.ts +1 -1
  351. package/src/test-database.ts +1 -1
  352. package/src/test-shards.ts +7 -1
  353. package/src/test-workers.ts +18 -12
  354. package/src/tool-gate-names.ts +34 -0
  355. package/src/upgrade-agents-link.ts +49 -0
  356. package/src/upgrade-bun-floor.ts +225 -0
  357. package/src/upgrade-gate.ts +228 -0
  358. package/src/upgrade-pins.ts +63 -0
  359. package/src/upgrade-plan.ts +214 -0
  360. package/src/upgrade-registry.ts +53 -0
  361. package/src/upgrade-report.ts +139 -0
  362. package/src/upgrade-root.ts +74 -0
  363. package/src/verify-budgets-build.ts +118 -0
  364. package/src/verify-checks.ts +67 -24
  365. package/src/verify-coverage-run.ts +1 -1
  366. package/src/verify-e2e-lane.ts +41 -0
  367. package/src/verify-exercise.ts +64 -0
  368. package/src/verify-floor.ts +41 -0
  369. package/src/verify-merge.ts +19 -4
  370. package/src/verify-role-load.ts +2 -0
  371. package/src/verify-run.ts +28 -8
  372. package/src/verify-shard.ts +12 -2
  373. package/src/verify-skip-ceiling.ts +87 -0
  374. package/src/verify-step.ts +5 -0
  375. package/src/verify-tests.ts +25 -5
  376. package/src/view-drift.ts +55 -0
  377. package/src/worker-bundle.ts +3 -2
  378. package/src/workspace-graph.ts +38 -19
  379. package/src/write-generated.ts +43 -0
  380. package/src/zone-boot.ts +20 -0
  381. package/wiki/Actions.md +342 -0
  382. package/wiki/Admin-Dashboard.md +432 -0
  383. package/wiki/Agents.md +958 -0
  384. package/wiki/Auth.md +195 -0
  385. package/wiki/Bare-VM.md +111 -0
  386. package/wiki/Batching-And-Preloading.md +136 -0
  387. package/wiki/Building-Your-Own-Base.md +203 -0
  388. package/wiki/CI-Parallel-Gate.md +220 -0
  389. package/wiki/CLI-Reference.md +1894 -0
  390. package/wiki/Caching-And-Invalidation.md +255 -0
  391. package/wiki/Client-Data.md +194 -0
  392. package/wiki/Client-Navigation.md +513 -0
  393. package/wiki/Compared.md +93 -0
  394. package/wiki/Configuration.md +868 -0
  395. package/wiki/Contributing.md +12 -0
  396. package/wiki/Deploy-Recipes.md +122 -0
  397. package/wiki/Deployment.md +508 -0
  398. package/wiki/Entities-And-Migrations.md +1116 -0
  399. package/wiki/FAQ.md +175 -0
  400. package/wiki/Feature-Flags.md +65 -0
  401. package/wiki/Getting-Started.md +221 -0
  402. package/wiki/Home.md +160 -0
  403. package/wiki/I18n.md +239 -0
  404. package/wiki/Installation.md +206 -0
  405. package/wiki/Interface-Rules.md +339 -0
  406. package/wiki/Jobs-And-Workflows.md +783 -0
  407. package/wiki/Known-Gaps.md +185 -0
  408. package/wiki/MCP-And-AI.md +753 -0
  409. package/wiki/Mail.md +138 -0
  410. package/wiki/Migrating-An-Existing-App.md +594 -0
  411. package/wiki/Migrating-From-Django.md +54 -0
  412. package/wiki/Migrating-From-Laravel.md +53 -0
  413. package/wiki/Migrating-From-Node-Drizzle.md +53 -0
  414. package/wiki/Migrating-From-Rails.md +53 -0
  415. package/wiki/Migrations-And-Backfills.md +552 -0
  416. package/wiki/Money.md +338 -0
  417. package/wiki/N-Plus-One-Detection.md +197 -0
  418. package/wiki/Notify.md +208 -0
  419. package/wiki/Observability.md +245 -0
  420. package/wiki/Outbound-HTTP.md +109 -0
  421. package/wiki/PWA-And-Offline.md +343 -0
  422. package/wiki/Policies-And-Authz.md +251 -0
  423. package/wiki/Project-Layout.md +150 -0
  424. package/wiki/Queries-And-Live-Queries.md +429 -0
  425. package/wiki/Realtime.md +734 -0
  426. package/wiki/Resource-Management.md +121 -0
  427. package/wiki/Routes-And-Render-Modes.md +521 -0
  428. package/wiki/SEO.md +288 -0
  429. package/wiki/Scheduled-Tasks.md +175 -0
  430. package/wiki/Scraping.md +493 -0
  431. package/wiki/Shapes.md +63 -0
  432. package/wiki/Stability.md +97 -0
  433. package/wiki/Static-Assets.md +166 -0
  434. package/wiki/Storage-And-Uploads.md +404 -0
  435. package/wiki/Testing.md +1314 -0
  436. package/wiki/The-Eight-Primitives.md +247 -0
  437. package/wiki/Theming.md +362 -0
  438. package/wiki/Timezones-And-Dates.md +194 -0
  439. package/wiki/Troubleshooting.md +163 -0
  440. package/wiki/Tutorial-01-First-App.md +289 -0
  441. package/wiki/Tutorial-02-First-Feature.md +339 -0
  442. package/wiki/Tutorial-03-Auth-And-Admin.md +379 -0
  443. package/wiki/Tutorial-04-Jobs-And-Realtime.md +263 -0
  444. package/wiki/Tutorial-05-Deploy-Free.md +231 -0
  445. package/wiki/Tutorial-06-Growing-Up.md +158 -0
  446. package/wiki/UI-Components.md +156 -0
  447. package/wiki/Upgrading.md +3667 -0
  448. package/src/stored-object-headers.ts +0 -43
@@ -0,0 +1,1894 @@
1
+ # CLI reference
2
+
3
+ The binary is `x`. One command registry — a command that is not in it does not exist, and there is no second place to register one.
4
+
5
+ ```bash
6
+ x help # the catalogue
7
+ x help <command> # usage for one command; a name no command has is X_CLI_UNKNOWN_COMMAND
8
+ x <command> --help # the same thing, except on db and mcp — see Subcommands below
9
+ x version # CLI version
10
+ ```
11
+
12
+ | Convention | Detail |
13
+ |---|---|
14
+ | `--json` | every command accepts it and prints a single machine-readable object on stdout. Human output goes to stdout too, but never mixed with JSON |
15
+ | Exit codes | `0` success · `1` the command failed (a typed `X_*` error is printed) · `2` usage error (`X_CLI_BAD_FLAG`, `X_CLI_UNKNOWN_COMMAND`) |
16
+ | Errors | always `code` + `cause` + `fix`. See [Error codes](Error-Codes) |
17
+ | App detection | most commands walk up for `app.config.ts` and fail with `X_NOT_IN_APP` if there is none. The exceptions: `new`, `test`, `doctor`, `errors`, `docs`, `affected`, `ci`, `pr`, `help`, `version` |
18
+ | Flags | long form only, `--flag value` or `--flag=value`. Booleans negate as `--no-<flag>` |
19
+ | Global flags | `--json` / `-j`, `--help` / `-h`, `--cwd <dir>`, `--verbose` — accepted by every command. `--verbose` carries each step's captured output on a green run too, in the terminal and under `--json` alike (`steps[].output`); without it a step's output appears only when it fails. `As of 2026-10` |
20
+ | Subcommands | when a command has them, its default is **declared**, never positional: `x actions` is `x actions list` because that command sets `defaultSubcommand: 'list'`. A command with no defensible default declares none and refuses the bare form with `X_CLI_BAD_FLAG` — exactly `db` and `mcp`, pinned by `parse.test.ts`. `--help` is read before the subcommand, so `x db --help` and `x mcp --help` print usage like `x help db`. The parser answered `subcommands[0]` until 1.2.0, which is why `x db` used to run the migration **generator** |
21
+ | Passthrough | a bare `--` sends everything after it to the underlying tool untouched |
22
+ | Telemetry | none written. The gate proves one thing: every host a string literal in a non-test `packages/cli/src` file spells is reserved or a reasoned row in `scripts/cli-outbound.ts`, else `X_CLI_OUTBOUND_UNDECLARED`. A host built at run time, and every other package, is outside it ([FAQ](FAQ#does-ultimate-collect-anything)) |
23
+ | `--json` shape | `{ ok, command, summary, steps?, findings?, data? }`. Findings are `{ code, cause, fix, docs?, at?, meta? }` |
24
+
25
+ ## Command index
26
+
27
+ `As of 2026-10`. **shipped** = implemented in `packages/cli`; **planned** = specified, not yet built — calling it exits with `X_NOT_IMPLEMENTED` and a `fix:` line pointing at the closest shipped command.
28
+
29
+ | Command | Does | Status |
30
+ |---|---|---|
31
+ | `x new <name>` | scaffold a monorepo that already runs | shipped |
32
+ | `x dev` | all roles in one process: embedded services, sub-second reload, `/_x` mounted | shipped |
33
+ | `x stop [--timeout 30s]` | stop this checkout's `x dev` (the pid its `.x/dev.lock` names, nothing else) and return once the next `x dev` can boot | shipped |
34
+ | `x g <kind> <name>` | scaffold a primitive with its test | shipped |
35
+ | `x db <sub>` | gen, migrate, reset, seed, studio, branch, backfill, diagnostics | shipped |
36
+ | `x verify [--only <step>[,<step>…] [--shard i/n [--timings f]]] [--workers N] [--isolate]` · `x verify merge <part.json…>` | the gate — 21 steps, in this order: typecheck, lint, boundaries, filesize, package-shape, errors, unit, contract, live, job, exercise, e2e, eval, drift, contract-diff, budgets, seo, i18n, policy, manifest, roadmap. `--only <step>[,<step>…]` runs the named steps, in one process and in gate order, for an iteration loop and announces `NOT A GATE RUN` in the summary and in `--json` (`data.notAGateRun`, `data.only` — always a list), writing no floor file; an unknown name is refused with every valid name and the nearest match. **The gate is this command with no flag.** There is no `--skip` | shipped |
37
+ | `x env [check\|example\|init]` | validate the process env against `envSchema`, regenerate `.env.example` from it, or write `.env.production` (`init --production`) | shipped |
38
+ | `x secrets <sub>` | the committed encrypted secrets file: show, init, edit, set, rotate | shipped |
39
+ | `x auth seal-mfa` | one-shot: seal every second-factor secret still stored in the clear | shipped |
40
+ | `x vapid [show\|create]` | the Web Push key pair: which one this app signs with, or mint one and seal both halves | shipped |
41
+ | `x build` | container image, single binary, prerendered static site, or a prebuilt bundle (`--target docker\|binary\|static\|prebuilt`) | shipped |
42
+ | `x deploy` | run the container deploy plan: migrate first, then the serving roles | shipped |
43
+ | `x manifest` | regenerate `x.manifest.json` and `openapi.json` | shipped |
44
+ | `x routes` | every served route: pages, then actions, queries and runtime.ts routes | shipped |
45
+ | `x mcp serve` · `x mcp tools` | serve the framework MCP tools over stdio or HTTP · list them | shipped |
46
+ | `x mcp lock <name> [--check]` | fetch the remote MCP server `mcpTools({ name })` names and write its allowed tools to `tools.lock.json` · `--check` writes nothing and fails on drift (`X_MCP_REMOTE_TOOL_CHANGED`) | shipped |
47
+ | `x doctor` | environment, versions, drift, the newest migration's snapshot sidecar, ports, PWA prerequisites — each with a fix | shipped |
48
+ | `x help` / `x version` | catalogue and version | shipped |
49
+ | `x actions` / `x queries` / `x entities` | introspect the declaration registries | shipped |
50
+ | `x jobs` | list, show, retry, cancel, remove and promote jobs; pause and resume a queue · `drain` is **planned** | shipped |
51
+ | `x tasks` | cron tasks, their timezone and their next run | shipped |
52
+ | `x policy` | which clause decided a permission, and why | shipped |
53
+ | `x exercise [<name>] [--seed n]` | run every action, query and job once on generated rows, with no test written; the gate's `exercise` step | shipped |
54
+ | `x drill [<name>] [--list] [--budget-ms n]` | the app's `web` and `worker` roles as real processes on real Postgres, Redis and NATS, each dependency faulted through a proxy or each role signalled, red on any framework contract that does not hold. Not a gate step: it needs the services | shipped |
55
+ | `x i18n` | add, sync, check catalogs | shipped |
56
+ | `x mail preview <id>` | render a mail's declared `preview` in every locale, nothing sent | shipped |
57
+ | `x test <type>` | run one of the six test types, or the whole suite | shipped |
58
+ | `x clean [--dry-run]` | remove what test runs leave behind: migrated test templates, `.x/cache`, stray `.x` folders, stale probe databases | shipped |
59
+ | `x affected` | the workspaces a diff can have broken, transitively | shipped |
60
+ | `x graph [--from <a> --to <b>]` · `x impact <file\|primitive\|entity>` | the app as one typed graph, each edge `declared`, `import` or `observed` · what an edit reaches | shipped |
61
+ | `x client vendor <name>` · `x client customer --out <dir>` | one OpenAPI compiler, two clients: a third party's document into `apps/web/clients/<name>/` (`outbound()` + `t` schemas), or the app's own published document into a client with no framework import | shipped |
62
+ | `x shot <route>` | photograph one route from a real browser, plus a `verdict.json` a picture cannot carry | shipped |
63
+ | `x pr review\|resolve\|reply` | inline review threads — the ones `gh pr view --comments` does not show | shipped |
64
+ | `x ci` | the workflow runs for this branch, and the findings inside the failed step's log; `x ci tested`: whether a push reuses a green PR run | shipped |
65
+ | `x errors explain <CODE>` | code → cause, fix, docs | shipped |
66
+ | `x docs "<question>"` | framework docs and this wiki, offline, from the install — the first lookup | shipped |
67
+ | `x fix boundary <file>` | plan the minimal cut for an import that crossed a surface boundary (never rewrites) | shipped |
68
+ | `x upgrade` | one major per run: exact pins, the Bun floor, `bun install`, the gate, the section to read | shipped |
69
+ | `x cache` | tag graph, bust, clear, stats | planned |
70
+ | `x branch` | copy-on-write branch environments | planned |
71
+ | `x status` | connected-client build-ID distribution, role health | planned |
72
+ | `x logs tail` | structured logs + OTel spans | planned |
73
+ | `x token` | create and grant MCP scopes | planned |
74
+ | `x ai` | eval, cache stats, reindex | planned |
75
+ | `x money add-currency` | extend the currency table | planned |
76
+ | `x config show` | the resolved `app.config.ts` | planned |
77
+
78
+ ## x new
79
+
80
+ ```bash
81
+ x new <name> [--dir path] [--shape api|blog|dashboard] [--no-example] [--no-git] [--dry-run] [--force] [--json]
82
+ ```
83
+
84
+ | Flag | Type | Default | Meaning |
85
+ |---|---|---|---|
86
+ | `--dir` | string | cwd | parent directory to create the app in |
87
+ | `--example` / `--no-example` | boolean | `true` | include the example feature slice (default: on; `--no-example` for an empty `app/`) |
88
+ | `--shape` | string | none | start from an app shape — `api`, `blog` or `dashboard`, each an overlay over this scaffold ([Shapes](Shapes)). An unknown name is `X_NEW_SHAPE_UNKNOWN`; with `--no-example`, `X_NEW_SHAPE_CONFLICT` (a shape is its own example). Both refuse before a file is written. `dashboard` then runs `x g sign-in --password` in the new app (listed under `data.generate` by `--dry-run`); a refusal there is `X_NEW_SHAPE_GENERATE_FAILED` |
89
+ | `--git` / `--no-git` | boolean | `true` | `git init`, then one commit named `x new`. On by default because three surfaces assume a repository — `x affected`, `x ci` and `x pr`. A git that is absent or fails never fails `x new`: the tree is the verdict, and `data.git` carries `{ initialized, committed, problem }` |
90
+ | `--dry-run` | boolean | `false` | print the file list, write nothing |
91
+ | `--force` | boolean | `false` | write into a directory that already exists |
92
+
93
+ **The name must be one `app.config.ts` accepts.** `x new` asks core's own `defineConfig` about the slug before anything is planned (`appNameIssue`, `packages/cli/src/app-name.ts`) — it is written verbatim as `name:`. Refused with `X_CLI_BAD_FLAG` and a corrected name in the `fix:`, the rest of your flags kept:
94
+
95
+ | You type | Why it is refused | `fix:` |
96
+ |---|---|---|
97
+ | `x new 9lives` | starts with a digit — core's pattern is `^[a-z][a-z0-9-]{1,63}$` | `x new app-9lives` |
98
+ | `x new a` | one character | `x new a-app` |
99
+ | a slug over 64 characters | past the pattern's bound | the slug cut at 64, trailing `-` dropped |
100
+
101
+ ```bash
102
+ $ x new myapp --dry-run --json
103
+ {"ok":true,"command":"new","summary":"…","data":{"dir":"/home/me/myapp","files":["README.md","AGENTS.md",…],"dryRun":true}}
104
+ ```
105
+
106
+ **This page states no file count, deliberately** — one template added moves it, and a number hand-copied onto four pages goes stale on three of them. `scripts/generator-counts.ts` is the single source: it re-derives both shapes from the CLI's own generators and, as a step of `x verify`'s `manifest` check, turns any page quoting a stale one red on the same commit. Two moves worth knowing about rather than counting: the scaffold gained `apps/web/app/auth/dev-actor.ts` and its test, because `hooks.authenticate` is the only place an actor can come from and a generated app configured none — so every route declaring a `policy:` refused every request and the boot warned `X_CONFIG_INVALID` on every start; and it gained `.github/workflows/ci.yml`, which runs `bun run setup && bun run verify` on every push and pull request. Derive the number:
107
+
108
+ ```bash
109
+ x new myapp --dry-run --json | jq '.data.files | length'
110
+ ```
111
+
112
+ `--dry-run --json` lists every file, which is also how you check what a build target expects to find.
113
+
114
+ Deployment artifacts are part of the scaffold — an app is deployable the moment it is generated:
115
+
116
+ | Path | Why |
117
+ |---|---|
118
+ | `apps/web/server.ts` | the production entry: `runRole({ root, env: Bun.env })`. Also `--target binary`'s entry |
119
+ | `apps/web/prerender.ts` | `--target static`'s entry |
120
+ | `docker/Dockerfile` | `--target docker`'s entry. Defaults `ROLE=web`, `PORT=3000`, health-checks `/readyz` |
121
+ | `docker/Dockerfile.dockerignore` | **this exact name** — not a root `.dockerignore`. Drops `*.test.ts`, `*.test.tsx` and the root `docs/`; an app serving a file from `docs/` re-includes it with `!docs/<path>` |
122
+ | `docker/docker-compose.prod.yml` | one service per role: migrate, web, sync, worker, scheduler |
123
+ | `docker/docker-compose.dev.yml` | parity checks only; `x dev` needs none of it |
124
+ | `docker/helm/` | the chart, 12 files — `Chart.yaml`, `values.yaml` and 10 templates (`_helpers.tpl`, `_volumes.tpl`, `deployments`, `service`, `migrate-job`, `ingress`, `hpa`, `networkpolicy`, `servicemonitor`, `alert-rules`). Written `As of 2026-08-19`, which is what makes `x deploy --method helm` work in a scaffold; the last two render only where the cluster serves the Prometheus operator's kinds (`auto`) |
125
+ | `docker/README.md` | how the two compose files differ |
126
+ | `.github/workflows/ci.yml` | the generated app's own CI: `bun run setup` then `bun run verify`, on push and pull request — the same two commands its `README.md` opens with |
127
+ | `bin/setup.ts` | TypeScript Bun runs, never shell — `package.json`'s `setup` script (`bun bin/setup.ts`), so `bun run setup` is the same command in PowerShell, cmd and bash; `bun run dev` is `x dev` and `bun run verify` is `x verify`, and neither needs a file. There is no `bin/check.ts` `As of 30.0`: `x verify`'s `budgets` step builds when the stats are missing or stale, so a wrapper that built first would be a second name for the gate |
128
+
129
+ The framework repo's own `docker/helm` carries one template the scaffold does not — `pdb.yaml`, cluster policy rather than app topology. It ships in no npm tarball, so copying it is a `git clone` of this repo, not an install. An app chart from before the monitoring pair gets it from `x g chart-alerts`.
130
+
131
+ `bunx create-ultimate myapp` is the same generator without a global install — and it says so in its own refusals, `As of 2026-08-23`: a `fix:` line is a command the reader is meant to RUN, and `x new myapp` named a binary they have not installed yet. Errors: `X_GENERATE_CONFLICT` (the directory exists — `fix` is the same command with `--force`), `X_CLI_BAD_FLAG` (no name given, or a path where a name goes), `X_BUN_VERSION`.
132
+
133
+ ## x dev
134
+
135
+ **The ready line names the live feed, `As of 2026-09-05`:** `live=in-process` under the embedded
136
+ database (repository writes in this process reach subscribers — the bridge PGlite's missing
137
+ walsender needed), `live=replication` with a real `DATABASE_URL` (the WAL decoder, `--role
138
+ replicator` here or elsewhere), `live=none` without the `sync` role. `--json` carries it as
139
+ `liveFeed`. The app's MCP endpoint, when `apps/<app>/mcp.ts` exports one, prints as `mcp POST /mcp`
140
+ in the summary and `mcp` in `--json`; an app exporting several prints one line per endpoint, and
141
+ `--json` lists them all in `mcpPaths`, endpoint #0 first.
142
+
143
+ ```bash
144
+ x dev [--port 3000] [--role web,worker] [--once] [--json]
145
+ ```
146
+
147
+ | Flag | Type | Default | Meaning |
148
+ |---|---|---|---|
149
+ | `--port` | string | `3000` | HTTP port. The `sync` role listens on `--port + 1` |
150
+ | `--role` | string | `web,sync,worker,scheduler` | comma-separated roles to run in this process |
151
+ | `--once` | boolean | `false` | boot, report, exit — for smoke tests and CI |
152
+
153
+ Boots the app: embedded Postgres (PGlite under `.x/pgdata`), the in-process event bus, a local
154
+ directory for S3, then every module under `apps/*/{site,app,api,shared}` and `packages/*/src` —
155
+ importing them IS the registration. What those modules registered is then served:
156
+
157
+ | Registered | Served as |
158
+ |---|---|
159
+ | `action` / `mutator` | `POST /api/<resource>/<verb>`, policy enforced by the pipeline |
160
+ | `route` | its URL, in its declared render mode, with that mode's cache headers |
161
+ | `job` | claimed off the real Postgres queue by the `worker` role |
162
+ | `task` | dispatched by the `scheduler` role |
163
+ | — | `/_x`, the dev dashboard from `@ultimat3/admin` |
164
+
165
+ A module that will not import becomes a finding on the result rather than a dead process, so the
166
+ dev loop stays reachable while something is broken.
167
+
168
+ **What it watches is decided before a descriptor is taken, and the ignore set is your own
169
+ `.gitignore`, `As of 2026-09-06`.** `x dev` walks the app root and opens one non-recursive watcher
170
+ per directory it admits, skipping an ignored one at descent rather than filtering its events
171
+ afterwards — a recursive watch on the root took an inotify descriptor per directory in the tree,
172
+ including `.git/`, `node_modules/` and the `.x/` this same process writes to continuously (measured:
173
+ 110 descriptors on the reference app, 39 of them noise; 1901 on a monorepo root, 1490 of them
174
+ `.git/` and `node_modules/`, against a per-user budget of 8192 on many distributions). The rules are
175
+ git's own — an unanchored `coverage/` matches at any depth, a leading `/` or an inner slash pins the
176
+ pattern where it is written, `!` re-includes, a trailing `/` matches a directory and never a file —
177
+ and ignore files in ancestor directories up to the repository root count, so an app inside a
178
+ monorepo inherits it. Editing `.gitignore` re-reads it and re-walks the tree in place; no restart,
179
+ and nothing ever runs `git check-ignore`. Five directory names are ignored whatever your file says,
180
+ because it cannot be relied on to name them: `.git`, `.x`, `node_modules`, `.personal`, `.claude`.
181
+ **`dist` and `coverage` are not among them** — root-anchor them in your `.gitignore` (`x new` now
182
+ scaffolds `/dist/` and `/coverage/`) or an `apps/web/site/coverage/page.tsx` route is a directory
183
+ git will not commit and the dev loop will not reload. A save that touches five files is one reload,
184
+ and a save arriving while a reload is still building is one more reload after it, never a second one
185
+ racing it.
186
+
187
+ **What a reload changes, `As of 2026-09-27`.** Every file the process loaded is hashed on each
188
+ reload; a changed one — a component, a helper, a `.module.scss`, a Sass partial a sheet `@use`s, a
189
+ JSON catalog — leaves Bun's module registry together with every module that imports it, up to the
190
+ route modules, and the scan imports that chain again. A route module that comes back as a new
191
+ instance has its entry replaced, so the next request serves what is on disk; its islands are
192
+ re-bundled on the same tick, so the two are one generation; the `isr` store is emptied, so a page's
193
+ first render does not answer for its whole ttl. A save that will not import is a finding on `/_x`
194
+ and the last page that did import stays up. The replaced instances are collected — nothing is kept
195
+ per save ([`app-reload-graph.ts`](https://github.com/developerz-ai/ultimate/blob/main/packages/cli/src/app-reload-graph.ts)).
196
+
197
+ A module that **defines a primitive** — it exports an entity, an action, a query, a mutator, a job
198
+ or a task, or it is `apps/web/api/index.ts` — is pinned: never re-evaluated, and the invalidation
199
+ stops at it: its exports are held by every importer and by a registry that refuses a second
200
+ definition of one name. **A save that reaches one restarts `x dev`, `As of 22.12`** — the pinned
201
+ module itself, or anything it imports: a slice's `service.ts` under its query, an admin page's view
202
+ under `defineAdmin`. `x dev` is a supervisor and a child: the child prints
203
+ `restarting: <saved file> under <pinned module> — …` on stderr, **retires its worker first** — it
204
+ stops claiming and waits for every running job to finish, aborting none (`As of 25.0.0`; before, the
205
+ drain cut off a job running past `drain.deadlineMs` with `X_DRAINING` and the next child ran it
206
+ again), printing `restart waits for <n> running job(s) to finish — …` while it waits, `Ctrl-C`
207
+ cutting the wait short at the drain deadline — then drains, releases the port, the lock
208
+ and the embedded Postgres, and exits `75`; the supervisor boots a fresh child on the same port
209
+ (`--port 0` is pinned to one free port before the first boot). Every other save stays the in-process
210
+ reload above, and `Ctrl-C` stops both. `--once` and an embedded `startDev()` are unsupervised: there
211
+ the save is `X_DEV_RESTART_REQUIRED` on `/_x` (or `startDev({ onRestart })` is called). With
212
+ `--json`, each child prints its own ready document. Until 22.12 such a save logged `reloaded` and
213
+ served the old code until a manual restart. The route's guard (`policy`) is read by the HTTP
214
+ pipeline at boot, so adding one is a restart too, and so is an edited site asset under an unchanged
215
+ stylesheet that names it with Sass `asset()`. Until 2026-09-27 only the edited route module itself
216
+ was re-imported (`?x-reload=<hash>`), and every component it imported stayed the cached one: `x dev`
217
+ logged `reloaded` and rendered the old component until a restart.
218
+
219
+ Without `--once` the process stays up until it is signalled. `Ctrl-C` runs the same three-phase
220
+ drain a production `SIGTERM` runs — stop accepting, finish in-flight, close — and only then
221
+ releases the embedded Postgres, the worker and the file watcher, so `.x/pgdata` is never left
222
+ locked by a process that has gone. `x mcp serve --transport http` behaves identically.
223
+
224
+ `/_x/<panel>` is one tab per panel; `?json=1` (or `accept: application/json`) returns exactly what
225
+ the tab draws. Eleven panels — the nine `@ultimat3/admin` ships plus the two only the CLI can
226
+ answer:
227
+
228
+ | Panel | Kills the question |
229
+ |---|---|
230
+ | `routes` | which URL renders how, with which budget |
231
+ | `timeline` | where did this request spend its milliseconds, and did it loop |
232
+ | `live` | why did this subscriber not get the row |
233
+ | `jobs` | which step failed, and what is queued |
234
+ | `db` | what is in the table, and does the schema match the migrations (read-only SQL) |
235
+ | `mail` | what did that email look like, in that locale |
236
+ | `cache` | which tags would this invalidation bust |
237
+ | `policy` | which clause decided, for which actor |
238
+ | `manifest` | is the committed `x.manifest.json` current |
239
+ | `services` | which database/events/storage this process is talking to, and its reload count |
240
+ | `boundaries` | which import crosses a surface or a layer |
241
+
242
+ `/_x` answers a loopback `Host` only — `localhost`, `*.localhost`, `127.0.0.1`, `[::1]` — and
243
+ refuses any other with `421` `X_DEV_HOST_REFUSED` (a DNS-rebound name is how a page reads a
244
+ loopback server). The `db` panel runs a statement on a same-origin `POST` only, inside a
245
+ `BEGIN READ ONLY` transaction that always rolls back:
246
+ `curl -sS -H 'accept: application/json' --data-urlencode 'sql=select 1' http://localhost:3000/_x/db`.
247
+ A `GET /_x/db?sql=…` is `X_METHOD_NOT_ALLOWED`; a cross-site `POST` is `X_CSRF_BLOCKED`.
248
+
249
+ Every one of the eleven answers in a `x dev` process. Three of them read facts only this process
250
+ holds, so nothing else can serve them: `timeline` is core's own spans, recorded by the exporter
251
+ `x dev` installs at boot (tracing is always on and free until one is configured); `cache` is the
252
+ report `invalidateTags()` already built, kept by `@ultimat3/cache`; `policy` is
253
+ `@ultimat3/policy`'s own `policyMatrix()` run over every capability an action or query gates,
254
+ against one actor per role the app declared with `defineRoles` plus the anonymous caller — never
255
+ a second reading of the actor. The matrix is evaluated with no row, and each cell's trace says so:
256
+ a row-level rule decides again on the real request.
257
+
258
+ `timeline` carries two SQL facts and they are not the same fact. `repeatedSql` is a **measurement**
259
+ over the trace: every statement text that appeared twice. `nPlusOne` is the **verdict** — one entry
260
+ per statement shape this request repeated past five, each carrying `X_N_PLUS_ONE_QUERY` or
261
+ `X_N_PLUS_ONE_WRITE` with the runnable `fix:`, counted with attribution applied and
262
+ `expectedQueryLoop` honoured. Only `x dev` installs the ledger behind it; a host that did not
263
+ answers `nPlusOne: null`, which is "nobody counted" and not "this request was clean". The same
264
+ verdicts reach three more places, never re-counted: `x dev`'s own findings (text and `--json`), the
265
+ browser error overlay for the request that looped, and one `logger.warn` per request per code.
266
+
267
+ `live` lists the registered live queries and notes that no subscriber list is attached —
268
+ `@ultimat3/realtime` does not retain a subscriber's matcher trace, which is the rest of that
269
+ panel's question. A panel whose source a host has not wired answers `ok: false` with the exact
270
+ wiring line rather than an empty tab, and a panel that can degrade says which half is missing
271
+ rather than rendering an empty one as an answer.
272
+
273
+ | Env | Unset means | Set means |
274
+ |---|---|---|
275
+ | `DATABASE_URL` | PGlite in this process | that Postgres |
276
+ | the variable `realtime.urlEnv` names | in-process fanout | that NATS server — read only under `realtime: { transport: 'nats' }`, which refuses to boot with it unset. `transport: 'memory'` (the default) refuses to boot while `NATS_URL` is set, so a bus meant for another node is never silently ignored |
277
+ | `NATS_KV_BUCKET` | the KV bucket `x_presence` | that bucket — one per app on a shared cluster |
278
+ | `S3_ENDPOINT` | `.x/storage` on disk | that S3 |
279
+
280
+ `realtime.enabled: false` in `app.config.ts` drops the `sync` and `replicator` roles from the set `x dev` starts, and a container's `ROLE=sync` answers the same way — no node, so no live feed.
281
+
282
+ `migrate` is a real role but not a dev role: it runs once, as `x db migrate`; naming it under
283
+ `--role` is `X_CLI_BAD_FLAG`. `replicator` does run under `x dev --role replicator`, but stays out
284
+ of the default set — `x dev` with no `--role` still runs `web,sync,worker,scheduler`, because the
285
+ replicator takes a slot on a shared database. With `DATABASE_URL` unset the embedded PGlite still
286
+ serves no logical replication, so the role is refused, but `X_CLI_BAD_FLAG` now names the fix: set
287
+ `DATABASE_URL` to a Postgres with `wal_level=logical`. With `DATABASE_URL` set, the role starts for
288
+ real: advisory lock → `postgresChangeFeed()` → `changeFeedReplicator` → publish to the transport.
289
+ Errors: `X_CLI_BAD_FLAG`, `X_PORT_IN_USE`, `X_ENV_MISSING`, `X_DB_DRIFT`.
290
+
291
+ ## x stop
292
+
293
+ ```bash
294
+ x stop [--timeout 30s] [--json]
295
+ ```
296
+
297
+ Ends **this checkout's** `x dev` and returns only once the next `x dev` there can boot. `As of 30.0`.
298
+
299
+ | Case | What happens | `--json` `data` |
300
+ |---|---|---|
301
+ | a live `x dev` holds `.x/dev.lock` | SIGTERM to the pid the lock names, then wait for the pid to exit and the lock to clear | `{ stopped: true, pid, port, waitedMs, drained: true }` |
302
+ | Windows | `kill` is TerminateProcess, so no drain runs; `x stop` clears the lock itself | `{ …, drained: false }` |
303
+ | no lock, or a lock naming a dead pid | nothing is signalled, exit 0. A stale lock is left for the next `x dev`'s preflight to clear | `{ stopped: false }` |
304
+ | the pid outlives `--timeout` | `X_DEV_STOP_TIMEOUT`, naming the pid; the lock stays | — |
305
+
306
+ | Flag | Type | Default | Does |
307
+ |---|---|---|---|
308
+ | `--timeout` | duration (`30s`, `2m`, `PT1M`) | core's default drain deadline (25 s) + the 5 s release floor + 5 s | how long to wait for the drain. An app that configured a longer drain passes it here: `x stop` loads no app |
309
+
310
+ **The lock is the only authority.** No `--all`, no port lookup, no process-name match: on a box shared by several agents, `pkill` ends someone else's server and `x stop` never does. Dev services an app runs beside `x dev` (its own compose file) are the app's to stop. `X_DEV_ALREADY_RUNNING`'s `fix:` is this command.
311
+
312
+ Errors: `X_DEV_STOP_TIMEOUT`, `X_CLI_BAD_FLAG` (a `--timeout` that is not a positive duration).
313
+
314
+ ## x g
315
+
316
+ ```bash
317
+ x g resource|action|mutator|backfill|job|route|policy|entity|query|task <name> [--feature f]
318
+ x g island <name> [--at <dir>] # a client component on an otherwise static page
319
+ x g admin:page <name> --permission <perm> [--at <dir>] # a custom admin screen, guarded by construction
320
+ x g guard <name> # a convention this app enforces, as a build error
321
+ x g sign-in --password | --oidc <provider> # the app's one sign-in: pages, actions, boot guard
322
+ x g chart-alerts [--print] # the chart's ServiceMonitor + alert rules, backfilled
323
+ ```
324
+
325
+ **`x g sign-in`** (`As of 2026-11`) is what a deployed scaffold needs to issue a session: before it, a production `web` role answered every policy-protected route `401` (`packages/cli/e2e/sign-in.e2e.test.ts` proves the after). Exactly one of the two flags, and no `<name>` — an app has one sign-in; anything else is `X_CLI_BAD_FLAG`, and so is any other `x g` flag it would ignore (`--feature`, `--surface`, `--at`, `--permission`, `--live`, `--admin`), and either flag on another generator. Its keys land in every catalog the app ships, or the ones `--locales` names, as every other generator's do. The auth slice lands under `apps/web/app/auth/` beside `x new`'s `auth.ts`, plus `apps/web/runtime.ts` and the edits in the last column. A dry run lists the deletions in `data.removed`, apart from `data.files`; a real run reports them under the same key:
326
+
327
+ | Shape | Writes | Also edits |
328
+ |---|---|---|
329
+ | `--password` | `sign-in/page.tsx` (a native form, 0 kB) posting to `createSession` over `login()`; `sign-in/second-factor/page.tsx` posting to `confirmSession` over `completeMfa()` (the sealed challenge travels in an HttpOnly cookie); `destroySession` over `logout()` + `signOutHeaders()`; `sign-in-guard.ts`; `apps/web/runtime.ts` — 22 files on a dry run in the default scaffold, 24 after setup | `apps/web/api/index.ts` (the three actions), `app.config.ts` (`auth: { signInPath: '/auth/sign-in' }`), the catalogs |
330
+ | `--oidc <provider>` | `oidc.ts` — `discoverOAuthProvider` on the FIRST sign-in, never at import — and `sign-in-routes.ts`, the `oauthLogin` legs on `runtime.routes`; the sign-in page (one link to `/auth/oauth/<provider>`); `destroySession`; the guard — 20 files on a dry run (plus the two in `data.removed`), 22 after setup | as above, plus `<PROVIDER>_ISSUER`, `_CLIENT_ID`, `_CLIENT_SECRET` in `envSchema`; and **the authenticator swap**: `authenticator.ts` loses the development fallback and `dev-actor.ts` is deleted, only while all four files are byte-for-byte what `x new` wrote — an edited one is an `X_GENERATE_CONFLICT` finding naming the line to delete by hand |
331
+
332
+ The boot guard is generated, app-owned: `apps/web/runtime.ts` calls `assertSignInConfigured(process.env)` before the boot binds a port, and outside `development`/`test` a `web` or `sync` role with a sign-in key unset refuses to start — `X_AUTH_SIGN_IN_UNCONFIGURED`, fix `export <KEY>="<value>"` in the role's deployed environment, then restart it (`x env example` lists the keys). The keys: `ULTIMATE_SECRETS_KEY` for `--password` (it seals the second-factor challenge); the provider's three plus `SESSION_SECRET` and `APP_URL` for `--oidc`. A second `x g sign-in` is `X_GENERATE_CONFLICT` and writes nothing. `--password` keeps the development viewer: it ships no sign-up page, so the first user is `register(appAuth(), { email, password, orgId, roles })`. An app that already has an `apps/web/runtime.ts` is that conflict too — merge the two `runtime` objects by hand.
333
+
334
+ **`x g chart-alerts`** (`As of 2026-11`) writes the two monitoring templates `x new`'s chart carries into an app chart that predates them — `docker/helm/templates/servicemonitor.yaml` and `alert-rules.yaml`, named after `Chart.yaml`'s `name` — and appends the `serviceMonitor:` / `alerts:` blocks `values.yaml` lacks. Only what is missing: a template already there is kept (`--force` rewrites both), and a values key already there — `enabled: false` included — is never touched; both templates read a missing block as `auto`, rendering only where the cluster serves the Prometheus operator's kinds. A second run writes nothing. No `<name>`, and any other `x g` flag but `--force`/`--dry-run` is `X_CLI_BAD_FLAG`. **`--print`** writes nothing and needs no chart: the same rules as plain Prometheus rule groups, defaults in place, for a loader outside Kubernetes (`--json`: `data.rules`, the YAML text). No `docker/helm/Chart.yaml` is `X_CHART_MISSING`, fix `x g chart-alerts --print --json`. `x doctor` lists the missing templates. The rules: [Observability](Observability).
335
+
336
+ `x g guard <name>` writes `guards/<name>.ts` and its test. A name the framework ships a guard under — `raw-length`, `raw-breakpoint`, `raw-z-index`, `raw-shadow`, `raw-motion`, `undefined-style-class`, `undeclared-custom-property`, `repo-raw-sql` and every other name in `SHIPPED_GUARD_NAMES` (`packages/cli/src/templates/scaffold-guards.ts`) — writes THAT guard, which is how an app adopts one it does not hold; any other name writes the blank template. `x doctor` lists the shipped ones missing, and the held ones that differ from the current template ([Interface rules](Interface-Rules#stylesheets)).
337
+
338
+ Alias: `x generate`.
339
+
340
+ `<name>` is one directory segment for every generator but `route`, whose name is its URL:
341
+ `x g route casos/[id]/notificar` writes `apps/web/app/casos/[id]/notificar/page.tsx`. Each segment
342
+ is a word (`a-z`, `0-9`, `-`, `_`) or `[param]` / `[...rest]`; `..`, `.` and empty segments are
343
+ `X_CLI_BAD_FLAG`.
344
+
345
+ | Flag | Type | Default | Meaning |
346
+ |---|---|---|---|
347
+ | `--feature` | string | derived from the name | feature slice to write into. Not for `resource`: its name IS the slice (`app/<name>/`, page at `app/<names>/`), and a `--feature` naming anything else is `X_CLI_BAD_FLAG` |
348
+ | `--surface` | string | `app` | `site` or `app` |
349
+ | `--permission` | string | `<name>:read` | `admin:page` only: the permission the page's own work needs, on top of the frame's `admin:read`. A `<resource>:<verb>`, or namespaced with more segments (`admin:blog:write`, `As of 27.6.2` — one colon exactly was required, which refused every `admin:<area>:<verb>` a staff console declares); an empty segment, a space or a quote is `X_CLI_BAD_FLAG` before a file is written — the value reaches the emitted source three times and `Permission` is `${string}:${string}` |
350
+ | `--live` | boolean | `false` | `query` only: make it subscribable. On any other generator it is `X_CLI_BAD_FLAG` naming the `x g query … --live` to run instead |
351
+ | `--admin` | boolean | `false` | `resource` only: also emit the per-entity admin override **and list it** under `resources:` in `apps/admin/app/admin/admin.ts`. The screen itself needs no flag — an entity in the handle is an admin screen |
352
+ | `--locales` | string | every locale with a catalog in `packages/i18n/catalogs/`, else `en` | comma-separated locales; lands each generator's catalog entry in every one. **Never starts a catalog** in an app that has any: a non-default locale with no `packages/i18n/catalogs/<locale>.json` is `X_CLI_BAD_FLAG`, `fix: x i18n add <locale>`, before a file is planned — a catalog a generator started would hold only its own keys. A non-default locale's new keys land marked `⟦…⟧`, as `x i18n add` seeds them, so `x i18n check` reports them until translated. In an app with no catalogs yet, a first `--locales en,es` writes `en` bare and `es` marked. `As of 2026-10-06` (`localiseCatalogs`, `packages/cli/src/generate-catalog-locales.ts`) |
353
+ | `--password` | boolean | `false` | `sign-in` only: an address and a password over `login()` |
354
+ | `--oidc` | string | — | `sign-in` only: an OpenID Connect provider id (`corp-sso`; lowercase, digits, hyphens, not a built-in), discovered from `<PROVIDER>_ISSUER` |
355
+ | `--print` | boolean | `false` | `chart-alerts` only: print the alert rules as plain Prometheus rule groups, write nothing. On any other generator it is `X_CLI_BAD_FLAG` |
356
+ | `--force` | boolean | `false` | overwrite existing files |
357
+ | `--dry-run` | boolean | `false` | the write plan, nothing written: **every file the run would touch** — the ones it creates (a slice module already on disk is skipped, not listed) and, `As of 27.6.2`, the ones it EDITS outside the slice (role map, typed handle, API index, manifests, catalogs, the i18n index), in the order the run lists them. `--json`: `data.files` is that list, `data.edits` the subset that already exists, `data.refreshes` the committed contracts (`x.manifest.json`, `openapi.json`) a real run re-projects. `ok: false` with every finding the real run would report — an existing file, an API-index binding already held, a grant or a handle entry it cannot place |
358
+
359
+ `resource` emits the whole slice — `entity`, `repo`, `policy`, `errors`, `service`, `actions`, `live`, `jobs`, `ui`, the form island, the plural route and a test beside each declaration — **37 files** on a dry run in an app scaffolded with its example slice, 39 on the real run after `bun run setup` (`x.manifest.json` and `openapi.json` re-projected; 40 dry with `--admin`) — and `x g resource` in a `--no-example` app, which also gains two `package.json` edits, writes 39 and 41 files (42 and 44 with `--admin`; `--live` is refused — a resource already ships a live query, and the flag belongs to `x g query`: `X_CLI_BAD_FLAG`, fix `x g query <name>-feed --feature <name> --live`), and **no migration**: `x db gen` is the only writer of `packages/db/migrations`, so a new slice is `x g resource <name>`, then `x db gen "create <table>"`, then `x db migrate`, then `x build --target static && x verify --only budgets` — the four commands the run prints, in that order (`data.next` under `--json`). **The plural route is reachable as written**, `As of 2026-10-01`: its `load` reads the list through the slice's query as the request's actor (no org in the URL), `AsyncRegion` renders ready, empty and refused, the form island is mounted, and the route declares `policy: { permission: '<name>:read' }`. Its `budget.js` is MEASURED on `x new`'s app (58,522 B, Bun 1.4.0) and carries the `measured:` / `why:` comment `bun run budget-raises` reads; an app that charges more to every document — `navigation: { client: [...] }` adds its router — is over it on the printed build, and the raise is that edit. The page sits in `apps/web/shared/shell.tsx` when the app has one. `backfill` emits a `backfill()` declaration with its `source()` and `handle()` to fill in — see [Migrations and backfills](Migrations-And-Backfills). **`x g query` and `x g backfill` read a table and never create one**, `As of 2026-11-02`: both name a slice holding an `entity.ts`, by `--feature <slice>` or by a name that is one, and neither finding it is `X_FEATURE_REQUIRED`, zero files written, its fix the closest slice's `--feature` line or `x g resource <name>`. `x g resource` and `x g entity` are what create a table. Every generator produces code that passes `x verify` unmodified, and writes it through the app's own Biome when the app has one: `node_modules/.bin/biome` over the TypeScript files that run created, best effort, never a failed `x g`. A slice whose `entity.ts` declares another table under its name gets no foundation from `x g action`/`x g mutator`, and `x g query` there is `X_QUERY_SLICE_ENTITY`. Errors: `X_GENERATE_CONFLICT`, `X_DB_HANDLE_UNREGISTERED`, `X_FEATURE_UNKNOWN`, `X_FEATURE_REQUIRED`, `X_QUERY_SLICE_ENTITY`.
360
+
361
+ **A name the templates would mangle is `X_CLI_BAD_FLAG` before anything is planned**, each with a `fix:` that runs: a reserved word or a leading digit; a letter outside ASCII (`Über` → `x g entity uber` — it was dropped to `ber`); an already-plural `resource`/`entity` name (`posts` → `x g resource post` — it became `entity('postses')`); and a name whose type spelling an emitted file also uses as a global or local type (`promise`, `omit`, `partial`, `row` → `x g resource promise-resource`).
362
+
363
+ **`entity` and `resource` write a repo over the typed handle, and register the entity in it**, `As of 2026-10`. The generated `repo.ts` holds no `sql` literal, no row decoding and no tenant predicate of any kind — `byId(id)`, `list(limit)` and `insert(row)` read `db.<table>`, and the handle scopes each to the actor's org ([Entities and migrations](Entities-And-Migrations#the-repo)) — and a table is on the handle only when its entity is in the set `packages/db/src/client.ts` passes to `database()`. The generator makes that edit, and four more:
364
+
365
+ | Edited | What lands | When it cannot |
366
+ |---|---|---|
367
+ | `packages/db/src/client.ts` | the entity's import, and `<table>: <entity>,` in `const entities = { … }`, in key order. Created whole only where the db package would export it: its `index.ts` names `./client`, or it has no index. **Never created in a db package of the app's own** (`As of 27.6.2`: an `index.ts` that exports no handle — a schema and a seed over `@ultimat3/db`'s client) | `X_DB_HANDLE_UNREGISTERED`, exit 1, the two lines in the `fix:` — the files are written, the handle is not. In an app with its own db package the finding sits on `index.ts` and says the generated repo expects `database()`'s handle |
368
+ | `packages/db/src/index.ts` | nothing — it is checked: it must export from `./client` | `X_DB_HANDLE_UNREGISTERED` naming the export line |
369
+ | `packages/db/package.json`, `apps/web/package.json` | the workspace dependency each new import needs: `@<app>/db` in the app, and `@<app>/web` in the db package only when the handle this run leaves imports an entity from it. Planned with the handle edit and written together — a handle that refuses the entry leaves both manifests untouched | left to `package-shape` (`X_WORKSPACE_DEP_UNDECLARED`), whose `fix:` is the same line |
370
+ | `packages/i18n/catalogs/<locale>.json` | the labels the admin reads for the new screen — `admin.<table>.title` and one `admin.<table>.field.<column>` per column — merged in the file's OWN key order (a sorted level stays sorted, a hand-ordered one gains its keys at the end), in every locale the run writes to | — |
371
+ | `apps/web/shared/roles.ts` | a written `policy.ts`'s grants (`<feature>:read` to `member`, `<feature>:write` to `admin`) and the admin's three per-table permissions, declared and granted. A permission is declared only where its grant can be placed, and one any `definePermissions([…])` call already declares is not declared again | `X_PERMISSION_UNGRANTED`, exit 1, naming each role and the permissions its `grants` owes — an app whose role map lives elsewhere, has no such role, or holds a list that cannot be edited safely is told, and the file is left byte-identical |
372
+ | `apps/web/api/index.ts` | every action, query, job and task module the run wrote, imported and listed under `actions` / `queries` / `jobs` / `tasks` — the lists `Api`, the typed client's shape, is read from | left alone when the file is not `defineApi({ actions: [...] })`; `X_JOB_UNREGISTERED` on the `manifest` step names a job nothing lists. A module whose binding (its file name, camelCased) the index already holds — another feature's `reindex-post`, or `api` / `defineApi` / `health` — is `X_GENERATE_CONFLICT` **before anything is written**, fix: the same run with the feature as a prefix |
373
+ | `apps/admin/app/admin/admin.ts` (`--admin` only) | the override's import, and `<table>: <name>AdminResource,` under `resources:` — the block is created, last in the call, when there is none | `X_ADMIN_RESOURCE_UNWIRED`, exit 1, the two lines in the `fix:` |
374
+
375
+ The summary line ends with what the new table owes, as commands that run as printed, and `--json` carries them as `data.next`:
376
+
377
+ ```
378
+ ✓ wrote 7 file(s) for entity widget — next: bunx x db gen "create widgets" && bunx x db migrate
379
+ ```
380
+
381
+ `bun install` leads the list when a manifest gained a dependency. `--dry-run` runs the same edits against the plan held in memory, so it lists every file above that the run would change (`As of 27.6.2`; it listed the generator's own files only).
382
+
383
+ **An edit to a file the app owns is syntax-aware, and refused rather than guessed**, `As of 27.6.2`. Every list a generator grows — a role's `grants`, `definePermissions([…])`, a `defineApi` list, the handle's entity set — is located on the source with comments and literal contents masked (`packages/cli/src/source-list-edit.ts`), so an apostrophe, a bracket or a comma inside a comment is never code. A list holding a comment gains rows and no existing byte moves; a comment-free one is re-wrapped the way Biome prints it. The result is read back before it is written — the same entries plus the new ones, the text around it untouched — and an edit that does not read back that way is the finding in the table above, never a write. (`x g entity` read each apostrophe in a role map's comments as a string delimiter and rewrote the comments between two of them into permissions.)
384
+
385
+ **A feature's second entity is a sibling file**, `As of 27.6.2`. `x g entity <name> --feature <feature>` in a slice whose `entity.ts` declares another table writes `entity-<name>.ts`, `entity-<name>.test.ts`, `repo-<name>.ts` and `repo-<name>.test.ts` beside it — one file per table, nothing the author wrote edited — and registers it exactly as a first entity: handle, role map, admin labels, `data.next` naming its own table. It was `X_GENERATE_CONFLICT` on the first entity's four files, with a `--force` that would have written the second table over the first. The same entity again is still that conflict, on its own files. `x db gen` reads the entity registry, so a sibling is in the next migration because it is declared. The other generators (`action`, `query`, `job`, …) keep reading the slice's `entity.ts` and `repo.ts`: a primitive over a sibling's table imports `./repo-<name>` by hand.
386
+
387
+ **Catalog writes are the labels the admin reads**: `x g entity`, `x g resource` and `x g admin:page` merge their keys (`admin.<table>.title`, `admin.<table>.field.<column>`; `admin.<name>.title` for a page) into every locale the app has a catalog for — the default locale bare, each other locale marked `⟦…⟧` exactly as `x i18n add` seeds it, so `x i18n check` reports them as owed rather than rendering a raw key. Existing keys always win. `--dry-run` lists each catalog it would change; `--locales <one>` narrows the run to the catalogs named.
388
+
389
+ **`x g admin:page` DECLARES the permission it requires**, `As of 2026-09`. It emitted
390
+ `permissions: ['ops:read']` and nothing anywhere declared `ops:read`, so `assertPermission` threw
391
+ `X_PERMISSION_UNKNOWN` on the first request that reached the page — a screen the generator built
392
+ and no actor could open, under a green gate: the `policy` step reads `roleDefinitions()` and
393
+ `routeEntries()`, and an admin page is neither a role nor a route. The page file now carries the
394
+ `definePermissions([...])` call and the `PermissionRegistry` augmentation beside its
395
+ `AdminCustomPage`, and its emitted test fails if either is dropped. Registration is a side effect
396
+ of import and `pages:` already imports that module, which is why the declaration is in the page and
397
+ not in a `policy.ts` beside it. Since 27.2.0 a `policyAuthz()` map that omits the permission decides it by the role map, so
398
+ the role grant is the one declaration; a map entry only refines it. The page
399
+ lands in `apps/admin/app/admin/pages/` without `--at` — beside the declaration, inside the app scan
400
+ (`DEFAULT_ADMIN_PAGE_DIR`); `apps/admin/src/pages`, the old default, was never imported.
401
+
402
+ **A narrower generator plants the slice modules its own source imports**, `As of 2026-08` — they used to be `x g resource`'s alone, so `x g job` into a hand-written slice emitted `import * as repo from '../repo'` against a file nobody had written. Which modules differ per generator, on purpose: a job has no request behind it and evaluates no policy, so `x g job` plants no `policy.ts`.
403
+
404
+ | Generator | Files into a **bare** slice | Its own | Slice modules it plants |
405
+ |---|---|---|---|
406
+ | `x g action` · `x g mutator` | 5 | 3 — the declaration, a unit test and a `.contract.test.ts` | `policy.ts` + `policy.test.ts`; `errors.ts` only when its body reads a row: a slice with an entity whose repo exports `byId` |
407
+ | `x g query --feature <slice>` (± `--live`) | refused: no `entity.ts` is `X_FEATURE_REQUIRED` | 2 | what the slice lacks of `repo.ts` + `repo.test.ts`, `policy.ts` + `policy.test.ts`; never `entity.ts` |
408
+ | `x g job` | 2 | 2 | none — a slice with no entity gets the neutral body, never an invented table |
409
+ | `x g backfill --feature <slice>` | refused: no `entity.ts` is `X_FEATURE_REQUIRED` | 2 | what the slice lacks of `repo.ts` + `repo.test.ts`; never `entity.ts` |
410
+ | `x g task` | 4 | 4 — the task **and** the job it enqueues | none |
411
+ | `x g entity` · `x g policy` | 5 · 2 | all of them — `x g entity` counts the catalog it merges its admin labels into | it *is* the slice module |
412
+
413
+ `repo.ts` is never planted alone: it imports `./entity` for its row type, so the pair goes in together or the unresolved import has only moved — and `repo.test.ts` rides with it, running the repo against the in-memory driver. An entity planted this way is registered in the handle exactly as `x g entity`'s is. Counts re-derive with `--dry-run --json` (`data.files`), `As of 2026-10`.
414
+
415
+ **A module the slice already has is skipped — never a conflict, never overwritten, `--force` included.** A foundation module belongs to the slice, not to the generator that needed it, and `--force` is about the primitive you named: clobbering `policy.ts` to regenerate one action would delete every rule in it. Regenerating a slice module is its own generator — `x g entity`, `x g policy`. So a second `x g action` into a finished slice writes exactly its own 3 files, and one into a half-built slice writes only what is missing.
416
+
417
+ **`x g action` and `x g mutator` complete nothing a slice's author wrote.** A module already on disk gets no companion from them — no `entity.test.ts`, `repo.test.ts` or `policy.test.ts`, no admin catalog labels — because those assert the shape `x g resource` writes, not the one that is there. A `repo.ts` with no `byId` gets the lookup-free body. And a hand-written `policy.ts` that does not export what the generated file imports (`can<Feature>Write`; for an action also `<feature>Tag`) is refused before anything is written: `X_SLICE_POLICY_INCOMPLETE`, naming the file and the missing exports. A destructured export counts, one level of relative `export * from` is followed, and a barrel that cannot be followed (a package) is never refused. Into such a slice the generated unit test grants what the policy grants — the one permission of a literal `can('<perm>')` — and when that cannot be read it makes no handler assertion and says so in a comment; the contract test makes no foreign-org assertion against a policy it did not write. `As of 2026-10`.
418
+
419
+ **Every generated entity is tenant-scoped, and there is no `--no-tenant` flag**, `As of 2026-09-05` — decided rather than omitted. `orgId` is not one generator's: `x g action`, `x g query`, `x g policy` and `x g job` all decide on it too, so a flag on `x g entity` alone would leave `x g resource` emitting an action over a column its entity no longer has. A single-tenant app makes the edit per slice, and the emitted `entity.ts` names it in the comment above `tenant: 'orgId'`: delete that line and the `orgId: uuid()` column, drop `'orgId'` from `indexes`, let `entity.test.ts` expect `$tenantColumn` null and `orgScoped` false, and drop `orgId` from `repo.test.ts`'s draft along with its last two tests, and let `policy.ts`'s write rule stop reading `input.orgId`. `repo.ts` needs no edit: it names no org. From then on `x g action` and `x g mutator` read the slice as `x g job` does, `As of 2026-11-02`: no `orgId` in the input, either test or a policy they plant. A `policy.ts` still deciding on `input.orgId` keeps it in the input, because the action compiles against the rule it imports and that file is the author's.
420
+
421
+ **The filename carries the test's type**, `As of 2026-08-19` — `x verify` selects a suite by it, so a generated test that landed in a plain `*.test.ts` ran under `unit` and `x test contract` answered `X_TEST_NO_FILES`:
422
+
423
+ | Generator | Test filename it writes | Step that runs it |
424
+ |---|---|---|
425
+ | `x g action` · `x g mutator` | `<name>.test.ts` **and** `<name>.contract.test.ts` | `unit`, `contract` |
426
+ | `x g query --live` | `<name>.live.test.ts` | `live` |
427
+ | `x g job` · `x g task` · `x g backfill` | `<name>.job.test.ts` | `job` |
428
+ | `x g query` (not live) · `x g entity` · `x g policy` · `x g route` | `<name>.test.ts` | `unit` |
429
+
430
+ `--force` writes the new name and does not touch a file under the old one, because the old name is not in its output list. Delete the orphan by hand.
431
+
432
+ The synopsis above is `GENERATORS` in `packages/cli/src/cmd-generate.ts`, which `x g --help` prints verbatim — run it if this list and the CLI ever disagree.
433
+
434
+ ## x db
435
+
436
+ ```bash
437
+ x db gen "add publish_at" | migrate | reset | studio
438
+ | seed [<name>] [--tier reference|dev] [--dry-run] [--json]
439
+ | branch list | branch create <name> | branch delete <name>
440
+ | backfill --list [--name n] [--status s] [--limit n] [--json]
441
+ | backfill --pending [--json]
442
+ | backfill <name>|--all [--write] [--force] [--json]
443
+ | diagnostics [--limit n] [--json]
444
+ ```
445
+
446
+ | Subcommand | Does | Notes |
447
+ |---|---|---|
448
+ | `gen ["<name>"]` | diff the app's entities against what the migrations declare, write the next migration, then rewrite the schema dump | the message becomes the id's slug; left out it is `change`. **Opens no database of yours** — the diff reads the previous migration's snapshot, and the dump is replayed on a scratch database it boots and discards: the embedded one, restored from a snapshot under `.x/cache` after its first boot, or a real Postgres when a migration creates an extension PGlite cannot link ([the engine rule](Migrations-And-Backfills#extensions-and-the-scratch-database)). **Exit 1 with a migration written** is possible: the dump failed after the migration was generated, the summary and the finding both say so, and the next move is a bare `x db gen`, never the same message again. A bare `x db gen` with nothing to generate rewrites `packages/db/schema/` alone, which makes it `X_SCHEMA_DUMP_DRIFT`'s whole fix. When the app does not load, nothing is compared and the summary says so — *nothing was compared — the app did not load (N finding(s)); fix them, then run x db gen again* — never "no migration needed". `--json`: `data.schemaDump` = `{ directory, status, files, written, removed }`, `status` one of `written`, `unchanged`, `skipped` (no migration yet), `failed`. `--allow-destructive` lets it emit a drop whose `down` cannot restore the rows; `--rolling-unsafe` lets it drop a column never retired, for an app that stops before it starts → [A column drop takes two releases](Migrations-And-Backfills#a-column-drop-takes-two-releases) |
449
+ | `migrate` | apply pending migrations, verify the result, then rewrite the schema dump | literally `ROLE=migrate`'s own `runMigrations` — which ends by diffing the live schema against the ledger it just wrote, on the connection it already holds. A difference is `X_DB_DRIFT` and a non-zero exit. Then, `x db migrate` only: the dump is regenerated as `gen` does, and the database is reopened to report any trigger, function, view, type or sequence no migration creates (`unexpected-object`, also `X_DB_DRIFT`). `--json` carries the same `data.schemaDump` |
450
+ | `reset` | delete the embedded data directory, then migrate | **embedded database only** — against an external Postgres it exits `X_NOT_IMPLEMENTED` and tells you to drop and recreate it yourself |
451
+ | `seed [<name>]` | apply the app's seeds — every one this environment takes, or the one named | **replayable**: a second run writes nothing and raises nothing, on Postgres as well as in memory. One transaction per seed, so one bad fixture graph cannot roll back the seeds that already landed. `--dry-run` reports what each would write and writes nothing |
452
+ | `studio` | — | **planned**: exits `X_NOT_IMPLEMENTED` pointing at the `/_x` db panel. It used to shell out to `bunx drizzle-kit studio`; one subcommand is not worth a second schema engine |
453
+ | `branch list` | every **managed** branch of this database: name, location, created-at, size | managed means this framework made it — external, a database carrying `createBranch()`'s `comment on database` marker; embedded, a `pgdata-<name>` directory. A branch cloned by the pre-1.2.x `psql` shell-out carries no marker, so it is absent here and `delete` refuses it → [Known gaps](Known-Gaps). `size` reads `unknown` on the embedded database — measuring it is a full directory walk. The delete path only touches what this lists |
454
+ | `branch create <name>` | `CREATE DATABASE … TEMPLATE` copy-on-write clone (PGlite: a copied data directory) | the isolation an agent should use before migrating. An existing name is `X_BRANCH_EXISTS`, never an overwrite |
455
+ | `branch delete <name>` | delete that branch database (PGlite: its `pgdata-<name>` directory) | a name `list` does not show is refused with `X_DB_BRANCH_FAILED` naming what it *would* have touched — that is the whole guard, and it is why there is no `--force` |
456
+ | `backfill --list` | print the `x_backfills` ledger — one row per `backfill()` pass, newest first | what has already been swept |
457
+ | `backfill --pending` | every backfill the app **declared**, minus the ones that completed | the alarm: a sweep merged and never enqueued had no ledger row and was invisible everywhere. **Non-zero exit** when anything is unswept, so a cron reads the code and not the table |
458
+ | `backfill <name>` / `backfill --all` | gate one sweep (or every pending one) and enqueue it | **dry run by default** — `--write` is never implied. `--write` enqueues; the workers perform the pass, because the queue is a job's execution surface. `--all` isolates per name and continues past a failure, exiting non-zero naming each |
459
+ | `diagnostics` | what the database is doing now: `blockers` (each waiting `pid` and the `blockedBy` pid holding it, at `data.blockers.rows[]`), `activity`, `slowQueries`, `unusedIndexes`, `invalidIndexes`, `notValidConstraints`, `deadTuples` | **facts, never a verdict: exit 0** whatever they say. The one failure is a connection that never opens, `X_DB_UNAVAILABLE`. Every section is one catalog read in its own `BEGIN READ ONLY` with a 2 s `statement_timeout`, capped at `--limit` rows (default 20, at most 1000). A section that fails is `{ available: false, code, reason, fix }` in place and the other six still answer; `slowQueries` is unavailable until an operator installs `pg_stat_statements` (never on the embedded database). No user table is read; session SQL is printed with every literal as `?`; a credential in a reason is masked. **Whoever runs it sees the redacted SQL of every session on the app's database role** (every role, when it holds `pg_read_all_stats`): statement shape and identifiers, never values. `unusedIndexes` skips unique and primary indexes and carries `statsAgeSeconds` and `replica`. Opens the database without applying the framework tables; an embedded one `x dev` holds is refused (`X_DEV_ALREADY_RUNNING`). `--json`: `data.<section>`. Arbitrary read-only SQL is `db.query` over [`x mcp serve`](MCP-And-AI) |
460
+ | `recount <entity>.<column>` | compare a `countedIn` counter with the true count of its child rows | **dry run by default**: lists the drifted rows (at most 50, the count whole) and exits non-zero with `X_LIFECYCLE_TRIGGER_MISSING` when any drifted. `--write` sets them in one cross-tenant transaction and exits 0. An unknown `<entity>.<column>` is `X_CLI_BAD_FLAG`. `--json`: `data` = `{ target, write, drifted, rows }` |
461
+
462
+ A bare `x db backfill` is `X_CLI_BAD_FLAG` naming a shape that works: the four answer four different questions, and defaulting to one of them is the ambiguity axiom 1 refuses.
463
+
464
+ **Seeds have TIERS, and production takes one of them**, `As of 2026-08`. A seed declares
465
+ `tier: 'reference'` — data the app is wrong without, which ships to production through this same
466
+ command — or `tier: 'dev'`, the default, which is fixture data. A bare `x db seed` runs every tier
467
+ the resolved environment takes: `reference` and `dev` everywhere, `reference` alone under
468
+ `ULTIMATE_ENV=production`.
469
+
470
+ | To do this | Run |
471
+ |---|---|
472
+ | every seed this environment takes | `x db seed --json` |
473
+ | one named seed | `x db seed dev --json` |
474
+ | the reference data only, anywhere | `x db seed --tier reference --json` |
475
+ | dev fixtures on a box whose `ULTIMATE_ENV` says production | `x db seed dev --tier dev --json` |
476
+ | the same, with no argv to change (a release-phase container) | `ULTIMATE_SEED_TIER=dev x db seed --json` |
477
+ | rows for every entity with no seed written, parents first, same-tenant keys | `x db seed --generate --rows 3 --seed 1 --json` |
478
+
479
+ Naming the tier is both the selection and the consent, and that is why it is one word rather than
480
+ two flags: a cluster that sets `ULTIMATE_ENV=production` on every box — staging included — would
481
+ otherwise have to lie about the environment to load its own fixtures. A `dev` seed reached under
482
+ `production` with neither the flag nor the variable is `X_CLI_BAD_FLAG` and a non-zero exit; the
483
+ cause names both spellings and the `fix:` is the runnable one.
484
+
485
+ `--generate` draws rows off the entity declarations alone — the seed graph `x exercise` runs on: `--rows` per table (per tenant when tenant-scoped, default 3), `--seed` the integer it is drawn from, so the same seed writes the same rows. Generated rows are `dev` fixtures with no natural key to replay against, so production refuses them **whatever tier is named** (`X_SEED_ENVIRONMENT`); `--dry-run` counts the plan and writes nothing. The pass reports as one row named `(generated)`.
486
+
487
+ `--json` carries a row per seed — `{ file, name, tier, status, ms, inserted, updated, skipped }`,
488
+ **slowest first** — plus the totals, because a seed run that got slow is diagnosed by which file
489
+ took the time. `skipped` is the replay: a row already stored, with no statement sent for it.
490
+
491
+ **`branch` requires a verb, and the verb set is closed.** The first argument *was* the branch name, so `x db branch` with the word `ls` cloned a database called `ls` instead of listing anything — and every verb is itself a legal branch name, which is why verb-first is the only shape where a name cannot be read as a subcommand. `As of 2026-08` a bare name is refused: a word outside `list`, `create` and `delete` is `X_CLI_UNKNOWN_COMMAND` and its `fix:` hands the caller's own word back inside the command that still creates it (`x db branch create <word>`). The two retired verbs are refused too since 26.0.0, one verb each with no alias, but their `fix:` names the replacement instead — `ls` gets `x db branch list --json`, `drop <name>` gets `x db branch delete <name>` — because `create drop` would clone a database for a caller who asked to delete one.
492
+
493
+ Two more facts about the set, both `As of 2026-08`:
494
+
495
+ | Fact | Why |
496
+ |---|---|
497
+ | an external `create` runs through `@ultimat3/db`, not `psql` | `createBranch()` writes the marker comment `list` finds branches by. The `psql` shell-out wrote the database and no marker, so every branch the CLI made was invisible to the only lister the framework has |
498
+ | the marker records the **base**, not just the date | `ultimate:branch:<base>:<iso>` `As of 2026-08-19`, and `BranchInfo` carries `base` alongside `createdAt`. `listBranches()` walks `pg_database` for the whole **server**, so two Ultimate apps sharing one Postgres plus one nightly `reapBranches` was the other app's branches dropped — a `DROP DATABASE` nobody asked for and nothing recovers from. The reaper skips any branch whose base is not this database, **and skips a pre-3.x marker that records no base rather than dropping it**: a branch of nothing is not a branch of this database. Self-healing with no migration — the next `createBranch` writes the base down. The payload is split on the ISO tail, not on the first `:`, because a database name may contain one |
499
+ | there is no `reap` verb | `reapBranches()` is a `task` — a nightly sweep with an app-chosen max age. A CLI verb would be a second path to one job, and no default max age is defensible |
500
+
501
+ | Flag | Type | Default | Meaning |
502
+ |---|---|---|---|
503
+ | `--name` | string | — | migration or branch name, or the backfill to filter the ledger by. Under `backfill --list` it is the **only** spelling of that filter: a positional there is a pass target in every other shape and is refused with `X_CLI_BAD_FLAG` rather than silently dropped |
504
+ | `--allow-destructive` | boolean | `false` | let `gen` emit a DROP whose `down` cannot restore the rows. `X_MIGRATION_IRREVERSIBLE`'s own `fix:` line names it |
505
+ | `--list` | boolean | `false` | `backfill`: print the ledger |
506
+ | `--pending` | boolean | `false` | `backfill`: declared minus completed, judged in this `ULTIMATE_ENV`. Non-zero exit when anything is unswept |
507
+ | `--all` | boolean | `false` | `backfill`: every pending sweep, isolated per name |
508
+ | `--write` | boolean | `false` | `backfill`: enqueue the pass. Without it the command is a dry run and writes nothing |
509
+ | `--force` | boolean | `false` | `backfill`: sweep a name the ledger records as completed, into a **new** ledger row |
510
+ | `--status` | string | — | `backfill`: `running`, `completed` or `failed`. Anything else is `X_CLI_BAD_FLAG` naming the three |
511
+ | `--limit` | string | `100` | `backfill`: max ledger rows |
512
+
513
+ **The backfill ledger is what has already been SWEPT.** One row per pass — name, status, rows
514
+ processed, last cursor, the app version that started it, started/completed — keyed by run rather
515
+ than by name, so `enqueue({ force: true })` writes a *new* row instead of editing the one it
516
+ reruns. A completed row is what makes the next **pass** a no-op — not the next enqueue: the
517
+ one-live-run index covers `ready`/`delayed`/`running`/`suspended`, and a completed job is in none
518
+ of them, so a second `enqueue()` creates a real job row whose pass then reports `skipped: true`.
519
+
520
+ ```bash
521
+ $ x db backfill --list
522
+ name status rows cursor started-at duration-ms run-id
523
+ reindex-posts completed 84000 - 2026-08-14T09:00:00.000Z 240000 9c1e…
524
+ recount-likes running 12500 post_12500 2026-08-14T09:31:12.004Z - 7f3a…
525
+ ✓ 2 backfill pass(es)
526
+ ```
527
+
528
+ `started-at` is the ledger's own ISO string, printed verbatim — this repo formats no date without
529
+ an explicit IANA `timeZone`, and not formatting is the one rendering with no zone to get wrong.
530
+ An empty ledger exits **0**: "nothing has swept this database yet" is an answer. `--json` carries
531
+ the same rows plus the definition checksum. The live half of the same ledger is on `x jobs list`,
532
+ and `/_x`'s jobs panel carries the whole of it.
533
+
534
+ **One migration engine, everywhere.** `gen` calls `@ultimat3/db`'s `generateMigration()`;
535
+ `migrate` and `reset` call `migrate()` — the same `x_migrations` ledger, the same per-migration
536
+ checksum, the same session-pinned advisory lock a `ROLE=migrate` container runs. A laptop, CI,
537
+ staging and production therefore share one answer to "what has been applied". Before 1.2.0 these
538
+ shelled out to `bunx drizzle-kit`, a second engine with a second journal that no `package.json`
539
+ declared and `bunx` fetched unpinned at run time. Nothing in the request path goes through an ORM
540
+ either: reads and writes run on `@ultimat3/entity`'s hand-written `postgresDriver()`.
541
+
542
+ **One migration is one file.** `x db gen` writes three, all committed:
543
+
544
+ | File | Holds |
545
+ |---|---|
546
+ | `packages/db/migrations/<id>.sql` | the `up`, then a lone `-- down` line, then the reverse |
547
+ | `packages/db/migrations/<id>.snapshot.json` | the schema this migration leaves behind — what the *next* `x db gen` diffs against |
548
+ | `packages/db/migrations/<id>.hash` | the hash `x verify`'s `drift` step checks: the loaded entity **registry** plus every non-test file under `packages/db/src` — the registry half sees what `describe()` means, the source half sees a seed or helper edit |
549
+
550
+ **Plus the schema dump** — `packages/db/schema/`, the whole schema as SQL, one file per table, rewritten on every `x db gen` and `x db migrate` and held by the `drift` step (`X_SCHEMA_DUMP_DRIFT`). Layout, limits and what it is not: [Migrations and backfills → The schema dump](Migrations-And-Backfills#the-schema-dump).
551
+
552
+ **And `x db gen` is that directory's only writer**, `As of 2026-08`. `x new` scaffolds no migration:
553
+ a hand-written first file carried no `.snapshot.json` — the one artifact only the generator produces
554
+ — so the app's first `x db migrate` and first `x db gen` refused each other. A pristine scaffold's
555
+ first two commands are `x db gen "initial"` then `x db migrate` (`bun run setup` runs both), and until
556
+ the first has run, `x verify`'s `drift` step is red naming exactly that — **for an app that declares
557
+ an entity**, since zero declared against zero recorded is agreement and `--no-example` is therefore
558
+ green. A foreign key is
559
+ emitted as `alter table … add constraint` **after** every table statement, never as a `references`
560
+ clause inside `create table`, so the order entities happen to register in cannot make a migration
561
+ unappliable.
562
+
563
+ **A rendered `fix:` names `bunx x` where `node_modules/.bin/x` exists** (`As of 2026-11-02`, both renderers, after `&&` too; a `#` tail and quoted text stay as written). `x` need not be on `PATH`. Identical findings collapse to one, with `meta.count` and `meta.reportedAt`.
564
+
565
+ **`x env init --production`** (`As of 30.0.0`) writes `.env.production`, the file `x deploy --method compose` reads: `ULTIMATE_ENV=production`, `IMAGE=ultimate-app:dev` (the tag `x build --target docker` makes), `POSTGRES_PASSWORD` and a `DATABASE_URL` naming the compose `db` service with it, `APP_URL` and `SYNC_URL` on localhost, and every secret the app declares or the framework refuses to boot without, 32 random bytes as hex each. Secrets are never printed: `--json` lists the key names (`data.written`). `.gitignore` gains `.env.production` first, the file is mode 600, and a second run refuses (`X_ENV_FILE_EXISTS`): the password is the one the database volume was initialised with. A required key nothing can derive is written empty, named in `data.unfilled`, and exits 1.
566
+
567
+ **`x env check` over a table naming no environment** reads it as production, as the boot does, and its summary says `checked as production: no ULTIMATE_ENV named`. `x doctor` asks about this machine and stays quiet on that shell. On this machine: `ULTIMATE_ENV=development x env check`.
568
+
569
+ **The schema dump against the newest snapshot** (`As of 2026-11-02`, `drift`, no database) is `X_DB_DRIFT`: a column in `packages/db/schema/04_tables/<t>.sql` no snapshot records means an applied migration was edited and regenerated. A table hand-written SQL names is exempt.
570
+
571
+ **`X_DB_DRIFT` has two detectors, and each answers what the other cannot.** `x verify`'s `drift`
572
+ step hashes the loaded entity **registry** plus the bytes of every non-test file under
573
+ `packages/db/src` against the `.hash` sidecars — no database, so it runs in a CI with nothing
574
+ listening, and it catches "you edited an entity and never generated". The registry half sees an
575
+ entity declared under `apps/`; the source half sees a seed or helper edit. Bytes, not meaning: a
576
+ formatter pass over `packages/db/src` after `x db gen` moves the hash too, and `x db gen` answers
577
+ "no migration needed" and re-records it. `x db migrate` diffs
578
+ the live catalog against the `x_migrations` ledger — a database, so it runs only where one is open,
579
+ and it catches "someone changed the schema by hand". A table in the `x_` namespace is framework
580
+ bookkeeping (the ledger, the queue, the outbox, the auth tables) and is never counted as drift.
581
+ The `drift` step's dump rail is the one part of it that boots a database — a scratch embedded one,
582
+ its own — so CI still needs nothing listening.
583
+
584
+ A separate `<id>.down.sql` is not a migration and is never applied — that was a hand-written
585
+ pre-1.2.0 layout, and reading it as one would drop every table the pair exists to reverse.
586
+
587
+ Errors, seed: `X_DECLARATION_UNKNOWN` (no seed of that name — the cause lists the ones there are), `X_CLI_BAD_FLAG` (an unknown `--tier`, one name declared twice, or a `dev` seed reached under `production` with no consent), and whatever the seed itself threw, reported per seed with the file it came from.
588
+
589
+ Errors, schema: `X_DB_DRIFT`, `X_SCHEMA_DUMP_DRIFT`, `X_DB_GEN_FAILED`, `X_DB_MIGRATE_FAILED`, `X_DB_BRANCH_FAILED`, `X_MIGRATION_CONFLICT`, `X_MIGRATION_IRREVERSIBLE`, `X_MIGRATION_VIEW_DEPENDS`, `X_MIGRATE_CONCURRENT`, `X_NOT_IMPLEMENTED`. `X_DB_STUDIO_FAILED` is reserved and no longer thrown — `x db studio` is planned.
590
+
591
+ Errors, backfill: `X_BACKFILL_PENDING` (`--pending`, and the only one that is not a refusal — the sweep simply has not run), `X_BACKFILL_UNKNOWN`, `X_BACKFILL_ENVIRONMENT`, `X_BACKFILL_MIGRATION_PENDING`, `X_BACKFILL_APPLIED`, `X_BACKFILL_RUNNING`. All six are `@ultimat3/jobs`', carry a one-line runnable `fix:`, and are what a non-zero exit from these shapes means — full wording in [Error codes → Backfills](Error-Codes#backfills). `X_BACKFILL_STALLED` is the seventh and never reaches this command: it is raised by the pass, on a worker, so it surfaces through `x jobs show <id>`.
592
+
593
+ ## x verify
594
+
595
+ ```bash
596
+ x verify [--only <step>[,<step>…] [--shard i/n [--timings file]]] [--workers N] [--isolate] [--json]
597
+ x verify merge <part.json…> [--json]
598
+ ```
599
+
600
+ **`As of 22.7`** — the width is a memory budget (`max(2.75 GiB, 25% of RAM)` at 1.25 GiB a worker — no fixed 4 GiB cap since 2026-11-07,
601
+ at most one per core, printed on the step line; `ULTIMATE_TEST_MEMORY_BUDGET` /
602
+ `ULTIMATE_TEST_MAX_WORKERS` override, `--workers` wins), shared machine-wide across concurrent runs,
603
+ and test files are NOT isolated unless `--isolate` or `x.verify.json` `"isolate": true` says so →
604
+ [Testing](Testing#memory-width-and-isolation). `--shard i/n` (with `--only unit|contract|live|job|e2e`)
605
+ runs one CI job's deterministic slice — `live` and `e2e` stay one process inside it, a slice per
606
+ runner, each runner with its own services (`As of 2026-11-07`) — and `x verify merge` folds every job's `--json` part back
607
+ into the gate verdict (`X_VERIFY_SHARD_INVALID`, `X_VERIFY_MERGE_INCOMPLETE`,
608
+ `X_VERIFY_MERGE_INPUT`) → [CI: the gate across parallel jobs](CI-Parallel-Gate). The paragraphs
609
+ below on `--workers` and batching describe 22.6 and earlier where they differ.
610
+
611
+ | `x.verify.json` key | What it sets |
612
+ |---|---|
613
+ | `steps` | steps this repo may never report as skipped (`X_VERIFY_SUITE_VANISHED`) |
614
+ | `coverage` | `{ "lines", "funcs", "why"?, "exclude"?: [{ "glob", "why" }] }` — the floor the `unit` step holds an app's whole source tree to (`X_COVERAGE_BELOW_FLOOR`, `X_COVERAGE_FLOOR_UNSTATED`, `X_COVERAGE_FLOOR_STALE`) → [Testing](Testing#coverage). `x doctor` prints every `exclude` |
615
+ | `stepTimeoutMs` | `{ "<step>": milliseconds }` — a step's own deadline. Default 8 minutes for the six suites, 5 for every other step; past it the step is `X_VERIFY_STEP_TIMEOUT` and its processes are killed |
616
+ | `isolate` · `typecheckBin` · `agentsMdMaxBytes` | a fresh global per test file · the `tsc`-compatible binary · the `AGENTS.md` byte budget |
617
+
618
+ Either render prints one line per finished step on **stderr** — `{"step":"lint","ok":true,"ms":21987}` —
619
+ as each finishes, and every minute a step is still running a `still running: unit (12m 3s)` line;
620
+ under `--json` stdout stays the single document. A cancelled job's log ends on the last step that
621
+ finished, and a harness that kills a process after N silent minutes never kills a healthy gate.
622
+
623
+ The single gate. Green means shippable; CI runs exactly this. One step list, in cost order, shared
624
+ with the framework repo's own `bun run verify` — and **the gate is this command with no flag**,
625
+ because "green" has to mean the same thing for everyone. `--only <step>` runs one step for an
626
+ iteration loop, and `--only typecheck,lint,boundaries` runs several in ONE process — one CLI boot
627
+ and one app load instead of one per step — always in the gate's declared order, whatever order they
628
+ were typed in. Neither is a gate run: it announces `NOT A GATE RUN` in the summary and in `--json`
629
+ (`data.notAGateRun`, and `data.only` as the list of steps — a list of one for a single name), exits
630
+ with the steps' own status, and writes no floor file — even a list naming every step. An unknown or
631
+ empty item is refused (`X_CLI_BAD_FLAG`) naming every valid step, with a fix that corrects the typo
632
+ and keeps the rest of the list. There is no `--skip`. A step with nothing to check in this project
633
+ reports as skipped (`-`), never as passed — and the summary counts skips apart from passes and
634
+ names them, so a gate that is green because a suite does not exist has to say so on the one line
635
+ every reader sees:
636
+
637
+ ```text
638
+ ✓ 20 of 21 steps passed in …ms — 1 skipped: roadmap
639
+ ```
640
+
641
+ `--json` carries the same fact twice: `steps[].skipped` per step, and `data.skipped` as the list of
642
+ names beside `data.failed`. `all {n} steps passed` is printed only when nothing was skipped.
643
+
644
+ `x.verify.json` ratchets those skips. Hand-written, committed at the repo root, read by the gate
645
+ and written by nothing:
646
+
647
+ ```json
648
+ { "steps": ["typecheck", "lint", "boundaries", "unit", "contract", "live", "e2e", "manifest"] }
649
+ ```
650
+
651
+ Every name in it is a step this repo has already run, so a step in the list with nothing left to
652
+ check is a deleted suite — reported as **failed**, not skipped, with `X_VERIFY_SUITE_VANISHED` and
653
+ the two edits that resolve it. A repo with no such file is not ratcheted, and a name the gate does
654
+ not run is refused by the `manifest` step (`X_CONFIG_INVALID`) rather than silently covering
655
+ nothing. Removing a line is allowed; it just has to be a diff somebody reviews.
656
+
657
+ `--workers` widens the test steps only. `unit`, `contract`, `job` and `eval` run as
658
+ `bun test --parallel=N`, each worker with its own database; `live` and `e2e` are serial by
659
+ declaration and say so in the output. **`x test live` and `x test e2e` obey the same declaration,
660
+ `As of 2026-08-27`** — they did not, so `x test live --workers 8` ran eight processes over the very
661
+ files `x verify` ran one over. `--workers` is accepted there and clamps to 1. That clamp read the
662
+ POSITIONAL, so the bare `x test --workers 8` still widened over the same files until 2026-09; the
663
+ selection is now partitioned by each file's own type. The default width is
664
+ a memory budget, `As of 2026-11-07`: `max(2.75 GiB, 25% of RAM)` (`BUDGET_RULE`) at 1.25 GiB a worker
665
+ (`WORKER_BYTES`, `GATE_BUDGET_FLOOR` and `GATE_BUDGET_SHARE` in
666
+ `packages/cli/src/test-workers.ts`), at most one per core — the paragraph under `x verify` above.
667
+ **No fixed ceiling of 8**: it held a 12-core box with 30 GB free to 8. `--workers` accepts 1 to
668
+ 64 on both `x verify` and `x test`; an explicit width is the caller's call and is not held to memory.
669
+
670
+ **A large selection runs in batches, `As of 22.6.2`.** A `--parallel` worker's heap grows with
671
+ every file it runs and is returned only when its `bun test` exits, so one process over the whole
672
+ corpus peaked higher with every test an app added — and the default of 22.6.1 (18 workers on a
673
+ 12-core box with 35 GB free, 1 GiB budgeted a worker) took a 45 GB machine with no swap down.
674
+ A pass now runs as `ceil(files / (workers x 24))` `bun test --parallel=N` processes in sequence,
675
+ the files dealt round-robin over the sorted list, so the peak depends on the width and not on the
676
+ corpus. One adopter's `x test unit`, 768 files, 12 cores: 22.6.1's default (18 workers, one
677
+ process) peaked at 17.1-18.2 GB of process tree in 132-167s; 22.6.2's default (13-14 workers,
678
+ 3 batches) at 11.3-12.2 GB in 152-163s, on a box shared at load 30-40. At the same width the split
679
+ costs no wall time within noise (12 workers: 150-158s unbatched, 147s in three batches). The split is a pure function of the file list and the width,
680
+ so the `fix:` a failure prints (`x test unit --workers N`) reruns the same batches; a `-- --watch`
681
+ run is never split; a forwarded `-- --bail` starts no batch after a red one. `x test --json` carries `batches`
682
+ when a pass was split, and each batch's output is kept under a line naming it.
683
+
684
+ **Bun owns the pool, `As of 2026-08-27`.** The CLI used to pack the files into N bins itself
685
+ (largest-first greedy over file SIZE) and spawn one `bun test` per bin; `--parallel=N` hands each
686
+ free worker the next file instead. **Expect no speed-up** — the two forms measure the same. Four
687
+ interleaved runs each on this repo's 1296-file unit corpus, 8 workers: 58.2/60.0/65.0/66.5s
688
+ hand-packed against 54.5/57.8/61.7/64.5s under `--parallel=8`. Both are work-bound — the corpus is
689
+ 436.7s of file time, so eight workers cannot beat 54.6s however the files are dealt, and the
690
+ slowest single file is 20.5s. The packer is gone because it was 233 lines buying nothing, not
691
+ because it was slow ([#342](https://github.com/developerz-ai/ultimate/issues/342)).
692
+
693
+ | Step | Checks |
694
+ |---|---|
695
+ | `typecheck` | `tsc` across every workspace |
696
+ | `lint` | Biome: no `any`, no default exports, no bare `Error`, no raw colours, no hardcoded user-facing strings |
697
+ | `boundaries` | surface and layer imports, resolved transitively; package tiers in a monorepo; an app's `guards/*.ts`. A guard may spawn a process (`check` is any async function): it runs under this step's deadline, so a slow one sets `stepTimeoutMs.boundaries` in `x.verify.json` |
698
+ | `filesize` | a source file over 500 lines |
699
+ | `package-shape` | a workspace package missing `README.md`, `CLAUDE.md`, `tsconfig.json`, `src/index.ts` |
700
+ | `errors` | every `X_*` code has a runnable fix and a docs page |
701
+ | `unit` | pure logic — services, money, policy predicates, matchers |
702
+ | `contract` | action/query schemas, policy denials, emitted OpenAPI and MCP shapes |
703
+ | `live` | live-query snapshot, incremental patches, reconnect delta, policy-filtered rows |
704
+ | `job` | step replay, idempotency dedupe, retry/backoff, concurrency, outbox atomicity |
705
+ | `e2e` | a real browser (raw CDP) against the app, spawned on a throwaway database when Chrome is present, including offline and SW update |
706
+ | `eval` | prompt scores vs. their recorded baselines, and a prompt with no eval at all |
707
+ | `drift` | schema vs migrations |
708
+ | `contract-diff` | the code vs the **committed** `x.manifest.json`: a breaking change without a major bump |
709
+ | `budgets` | per-route JS bytes, the global style layer every document carries (`X_STYLES_GLOBAL_MISSING`), and every route that reads live rows with no island to receive them (`X_LIVE_ROUTE_NO_ISLAND`). **Builds first** when `.x/build-stats.json` is missing, of an earlier measurement rule, or older than the newest app source: `x build --target static --no-preflight --no-capture --out .x/verify-static`, under this step's deadline (`stepTimeoutMs.budgets`), never over a deployable `.x/static`. A failed build is the step's verdict, with the build's own findings. `As of 30.0` |
710
+ | `manifest` | the files an agent reads: `x.manifest.json` **and `openapi.json`** freshness — the one check `x manifest --check` runs (`X_MANIFEST_MISSING` / `X_MANIFEST_DRIFT` / `X_MANIFEST_STALE`) — `.env.example`, a hand-written `AGENTS.md` that exists and is under 12kB, and `x.verify.json` naming only steps the gate runs |
711
+ | `roadmap` | framework repo only — every `docs/idea/14-roadmap.md` milestone carries a status marker, and a milestone marked shipped still has the artifacts its own row names |
712
+
713
+ A test's type is its filename suffix — `*.contract.test.ts`, `*.live.test.ts`, `*.job.test.ts`,
714
+ `*.e2e.test.ts` (or any test under `e2e/`), `*.eval.test.ts`. Everything else is a unit test, so no
715
+ test can fall between two steps.
716
+
717
+ `eval` is the one step that applies with no suite of its own: a prompt no `defineEval` names is
718
+ `X_EVAL_MISSING`, and an eval whose baseline was never recorded is `X_EVAL_BASELINE_MISSING`,
719
+ because a skipped step — or one gating against nothing — would read as a green gate over untested
720
+ code. It gates on the drop from each eval's committed baseline, never on an absolute score —
721
+ `ULTIMATE_EVAL_RECORD=1 x test eval` re-records those baselines so accepting a new number is a
722
+ reviewable diff.
723
+
724
+ That flag and this step are mutually exclusive. `x verify` with `ULTIMATE_EVAL_RECORD` set is
725
+ `X_EVAL_RECORDING` and the suite does not run: recording makes every eval write the numbers it
726
+ just measured and pass, so a gate that inherited the flag reports green over scores nothing
727
+ compared — and rewrites the committed baselines on its way through.
728
+
729
+ ```bash
730
+ $ x verify --json
731
+ {"ok":false,"command":"verify",
732
+ "summary":"1 of 21 steps failed — 3 skipped: e2e, contract-diff, roadmap","steps":[
733
+ {"name":"budgets","ok":false,"durationMs":812,"skipped":false,"findings":[
734
+ {"code":"X_BUDGET_EXCEEDED","cause":"/pricing ships 61.2kb of JS (minified, uncompressed) over a 40kb budget via site/pricing/page.tsx -> islands/post-form-04f75f18.js; charged: /islands/post-form-04f75f18.js 50123 B, inline scripts 855 B",
735
+ "fix":"edit apps/web/site/pricing/page.tsx — set budget: { js: '62kb' } with // measured: 62682 B (x build --target static) — why: <the function it buys> directly above it; or x routes --json for the chain and move the heavy import behind hydrate: 'interaction'",
736
+ "docs":"https://github.com/developerz-ai/ultimate/wiki/Error-Codes",
737
+ "at":"apps/web/site/pricing/page.tsx",
738
+ "meta":{"jsBytes":62682,"charged":[{"url":"/islands/post-form-04f75f18.js","bytes":50123}],"inlineJsBytes":855}}]}],
739
+ "data":{"failed":["budgets"],"skipped":["e2e","contract-diff","roadmap"],"durationMs":11153}}
740
+ ```
741
+
742
+ A finding is `code`, `cause`, `fix`, and optionally `docs`, `at` and `meta`. `at` is the
743
+ path an agent opens — a file, or the stuck test file for a timed-out step — and is absent
744
+ when there is no such path. `meta` is structured facts behind `cause`, never the only home of
745
+ one: an unmeasured budget carries its route in `meta.route` (`X_BUDGET_UNMEASURED` — `at` names
746
+ the file that declares it), while `X_BUDGET_EXCEEDED` carries what the bytes were charged as
747
+ (`meta.jsBytes`, `meta.charged`, `meta.inlineJsBytes`) and spells the route in `cause`.
748
+ `X_VERIFY_STEP_TIMEOUT`
749
+ carries `meta.step`, `meta.deadlineMs`, `meta.killed: [{pid, command}]` and
750
+ `meta.inFlight: [{command, files, workers, stuck}]`; `at` is the stuck test file when
751
+ one could be named, and the failed step's `output` is the tail of what the killed run had printed.
752
+
753
+ Errors: `X_VERIFY_FAILED` (with the failing step names), plus each step's own code.
754
+
755
+ ## x build
756
+
757
+ ```bash
758
+ x build --target docker|binary|static|prebuilt [--tag name] [--out path] [--platform bun-target] [--no-preflight] [--no-capture] [--json]
759
+ ```
760
+
761
+ | Flag | Type | Default | Meaning |
762
+ |---|---|---|---|
763
+ | `--target` | string | `docker` | `docker` (one image, all roles), `binary` (`bun build --compile`), `static` (prerendered `site/`), `prebuilt` (the island store and compiled stylesheets, written to `node_modules/.cache/ultimate` — the app image's own `RUN` line; no gate, no subprocess, refuses `--tag` and `--out`). The list is `BUILD_TARGETS` in `packages/cli/src/cmd-build.ts` |
764
+ | `--tag` | string | `ultimate-app:dev` | image tag, docker target only |
765
+ | `--out` | string | `.x/app` (`.x/static` for `static`) | output path, binary and static targets only; relative to the cwd |
766
+ | `--platform` | string | the host | binary target only: `bun-linux-x64`, `bun-linux-arm64`, `bun-windows-x64` or `bun-darwin-arm64`, passed to Bun as `--target` (the list is `BINARY_PLATFORMS` in `packages/cli/src/cmd-build.ts`). A Windows executable — `--platform bun-windows-x64`, or no `--platform` on a Windows host — is written as `<out>.exe`, and `data.artifact` names that file; `data.platform` echoes the flag. Any other value is `X_CLI_BAD_FLAG` before the gate runs, `fix: x build --target binary --platform bun-linux-x64`. For local runs and CI cross-compiles: a Windows host deploys the Linux image under Docker Desktop ([Deployment](Deployment)). `As of 2026-10-05` |
767
+ | `--no-preflight` | boolean | off | skip the six static gate steps the build runs first (typecheck, lint, boundaries, filesize, package-shape, errors) — only when `x verify` has run them, as its `budgets` step does when it builds. Not on `prebuilt`. `As of 22.7` |
768
+ | `--no-capture` | boolean | off | `static` only: stamp every `og.capture` card's `og:image` tags and photograph none, so no browser is needed (`X_SEO_CARD_BROWSER_MISSING` otherwise). `x verify`'s own build and `scripts/reference-app-gate.ts` pass it, and only they do — a deployable build takes the pictures. Reaches the app's `prerender.ts` as `ULTIMATE_BUILD_CAPTURE=off`. `As of 2026-11` |
769
+
770
+ A flag the chosen target never reads — `--tag` on `binary`/`static`, `--out` on `docker`, `--platform` on anything but `binary`, any of them on `prebuilt` — is `X_CLI_BAD_FLAG` before the gate runs, with `fix: x build --target <target>`.
771
+
772
+ **`--out` for `static` is emptied before every build**, so it must be provably the build's: absent, empty, the default `.x/static`, or a directory holding the `.x-export` marker every static build writes. The app root, an ancestor of it, or any other non-empty directory (`apps`, `.x`, `.x/pgdata`, a home directory) is `X_BUILD_OUT_UNSAFE` and nothing is removed. A module that will not import fails the static build with `X_BUILD_FAILED` (`fix: x verify --only manifest --json`) before the export is touched — its page would otherwise vanish from the artifact under a green build.
773
+
774
+ ### Every target has one entry file
775
+
776
+ `BUILD_ENTRY` is the whole mapping. The entry is checked **before** the verify gate runs and before the builder is spawned, because `bun build`'s own "module not found" names no owner.
777
+
778
+ | `--target` | Entry file | Command it execs | Output |
779
+ |---|---|---|---|
780
+ | `docker` | `docker/Dockerfile` | `docker build -f <root>/docker/Dockerfile -t <tag> <root>` | one OCI image, `ROLE` selects behaviour |
781
+ | `binary` | `apps/web/server.ts` | `bun build --compile --minify [--target <platform>] --define ULTIMATE_FRAMEWORK_VERSION="<version>" <root>/apps/web/server.ts --outfile <out>[.exe]` | a single executable |
782
+ | `static` | `apps/web/prerender.ts` | `bun run <root>/apps/web/prerender.ts --out <out>` | prerendered `site/` |
783
+
784
+ All three are written by `x new`. A missing one is `X_BUILD_ENTRY_MISSING`, whose `fix` names the file and points at a fresh scaffold — the usual cause is an app scaffolded before 1.1.0 wrote `server.ts` and `prerender.ts`, or a deleted `docker/Dockerfile`.
785
+
786
+ Runs the static verify steps first (`typecheck`, `lint`, `boundaries`, `filesize`, `package-shape`, `errors`); if any fail, exits non-zero without building. The content-hash build ID every target shares is `x.manifest.json`'s, written by `x manifest`, not computed here. Errors: `X_BUILD_ENTRY_MISSING`, `X_BUILD_FAILED`, `X_BUILD_OUT_UNSAFE`, `X_CLI_BAD_FLAG`; an unknown `--target` is `X_CLI_UNKNOWN_COMMAND` with `build --target docker` as the suggestion.
787
+
788
+ ### `--target static` names every route it emitted, and every one it did not
789
+
790
+ `.x/static/` is a **partial site**: `site/` routes declaring `render: 'static'` become files, and everything else is served by the app. Both renderers say so — `data.emitted` and `data.skipped` under `--json`, the same rows as a table in the terminal. A screenshot tool pointed at the directory used to photograph the landing page and file "the island did not mount" against a route that had never been in the artifact.
791
+
792
+ | Field | Row | Meaning |
793
+ |---|---|---|
794
+ | `emitted` | `{ route, path, file }` | one **file**, and the declared route that produced it — `/blog/:slug` writes one row per prerendered path |
795
+ | `skipped` | `{ route, surface, render, reason, why }` | one declared route that wrote nothing, and the cause |
796
+
797
+ **Every declared route is in exactly one of the two.** A route in neither is the defect this exists to close.
798
+
799
+ `reason` is a closed set, and the four are not one cause wearing four names — the surface is asked **first**, because `app/` allows only `stream | ssr` and no `render:` edit can put an `app/` route in the artifact:
800
+
801
+ | `reason` | Cause | Is there an edit? |
802
+ |---|---|---|
803
+ | `surface-forbids-static` | the surface cannot declare `render: 'static'` at all (`app/`) | no — move the route to `site/`, or serve it |
804
+ | `mode-revalidates` | `render: 'isr'` regenerates on a tag or ttl, which a published file cannot | yes — `render: 'static'` |
805
+ | `mode-per-request` | `render: 'ssr'` / `'stream'` on a surface that *does* allow static | yes — `render: 'static'` |
806
+ | `no-prerender-paths` | `render: 'static'` with dynamic params and no `prerender()`, so nothing enumerated | yes — add `prerender()` |
807
+
808
+ Both lists come from `prerenderSite`, which writes `.x/static-report.json` beside `.x/build-stats.json`; `x build` deletes any previous copy before it spawns the builder, so a stale inventory can never be reported as this run's. An app whose `apps/web/prerender.ts` does not call `prerenderSite` writes neither file, and `x verify`'s `budgets` step, which ran this build itself, reds it with `X_BUDGET_UNMEASURED`.
809
+
810
+ Budgets are unchanged and still measured in **real bytes on disk** — `measureDocumentJs` weighs every `<script src>` and every `data-x-entry` island chunk in the emitted document against the files in `out`, which is what catches one engine imported twice. A route that is skipped here is still weighed: it is rendered in memory and thrown away, so a `budget:` on an `ssr` or `isr` route has a number.
811
+
812
+ > `--target binary` compiles `apps/web/server.ts` as a **launcher** for the app tree it is started in: `--compile-autoload-package-json` and `--compile-autoload-tsconfig`, so the app's packages and `paths` aliases resolve at run time, and `--external '@ultimat3/*'`, so the framework is ONE instance — loaded from the app's `node_modules`, the same copy the app's own modules register into. It bundles neither the app nor the framework: run it from the app root, with `node_modules` installed beside it; it needs no Bun. It still passes `--define ULTIMATE_FRAMEWORK_VERSION="<version>"`, which only bundled framework code reads — none, in this graph; the framework's self-contained `x` image (`docker/Dockerfile`) is where the define is load-bearing. What is still unmeasured is the target end to end on a bare VM ([Known gaps](Known-Gaps)). `As of 2026-10-06`
813
+
814
+ ## x deploy
815
+
816
+ ```bash
817
+ x deploy --image repo/app:tag [--method compose|helm] [--release name] [--namespace ns] [--timeout 15m] [--dry-run] [--json]
818
+ ```
819
+
820
+ | Flag | Type | Default | Meaning |
821
+ |---|---|---|---|
822
+ | `--image` | string | `ultimate-app:dev` | image reference to deploy |
823
+ | `--method` | string | `compose` | `compose` or `helm` |
824
+ | `--release` | string | `app.config.ts`'s `name` | helm release name; `helm` only |
825
+ | `--namespace` | string | the kube context's | helm namespace; `helm` only |
826
+ | `--timeout` | string | `15m` | how long helm waits for the migrate hook and the rollout; `helm` only |
827
+ | `--dry-run` | boolean | `false` | print the plan, run nothing |
828
+
829
+ `compose` is six ordered steps against `docker/docker-compose.prod.yml` — `run --rm migrate` to
830
+ completion, then `up -d` for `web`, `sync`, `worker`, `scheduler`, then `run --rm backfill` last.
831
+ **`migrate` gates and `backfill` triggers**: a slow `UPDATE` inside a release gate holds the deploy
832
+ open against a database still serving the previous release, so data sweeps are enqueued after the
833
+ new pods serve and the workers already draining the queue perform them. The `backfill` service runs
834
+ `x db backfill --all --write --json` and exits. Steps run sequentially and stop
835
+ at the first non-zero exit; the `fix` is that step's command, so you can rerun it directly for full
836
+ output. `helm` is one `helm upgrade --install <release> docker/helm [--namespace <ns>] --wait
837
+ --timeout <timeout> --output json --set-string image.repository=… --set-string image.tag=…` against the chart
838
+ **`x new` writes**. **It waits**, `As of 2026-09-23`: without `--wait` the command exited 0 the
839
+ moment the API server accepted the objects, and helm's own 5m default failed an upgrade whose
840
+ migrate hook was still applying a long migration. `--json` reports helm's own verdict as
841
+ `data.rollout` — `{ release, namespace, revision, status }`, `status` being helm's word
842
+ (`deployed`, `failed`) or `unknown` when helm printed no record. The release was the literal `app`
843
+ for every app; it is now `app.config.ts`'s `name`, and `--release app` addresses a release an
844
+ earlier version created. `--release`, `--namespace` and `--timeout` on `--method compose` are
845
+ refused (`X_CLI_BAD_FLAG`), never ignored. There is no `X_NOT_IMPLEMENTED` branch: the command implements helm
846
+ completely, so an app that deleted its chart gets helm's own error and an app with no `helm` on
847
+ `PATH` gets `X_CLI_UNEXPECTED` naming the binary to install. An image pinned by digest is refused
848
+ before the upgrade starts — the chart renders `repository:tag` with no digest branch.
849
+
850
+ **An unknown `--method` is refused, never defaulted.** `x deploy --method helmm` is
851
+ `X_CLI_UNKNOWN_COMMAND` listing `compose, helm`; it used to run the six-step Compose plan and report
852
+ `method: "compose"` back to an operator who asked for a Helm upgrade.
853
+
854
+ **There is no `--critical` flag**, `As of 2026-08-23`. It was deleted; the parser answers
855
+ `X_CLI_BAD_FLAG` and lists the flags `deploy` does declare. It parsed, it was echoed into the
856
+ plan JSON as `critical: <bool>`, and no file in `packages/` read that field — so it changed nothing
857
+ about what the command did, on either method
858
+ ([`packages/cli/src/cmd-deploy.ts`](https://github.com/developerz-ai/ultimate/blob/main/packages/cli/src/cmd-deploy.ts)
859
+ says so where it used to be declared). It is not coming back: the framework force-reloads nothing
860
+ and no longer pretends to →
861
+ [`docs/architecture/13-topology-runtime.md`](https://github.com/developerz-ai/ultimate/blob/main/docs/architecture/13-topology-runtime.md).
862
+ The flag list this build ships is `x help --json`, never this page.
863
+
864
+ Errors: `X_DEPLOY_FAILED` (a step exited non-zero — its `fix:` is that step's exact command),
865
+ `X_CLI_BAD_FLAG` (a digest-pinned `--image` under `--method helm`, a helm-only flag under
866
+ `--method compose`, a `--timeout` helm would reject, a `--release`/`--namespace` that is not a
867
+ DNS-1123 label, or a flag `deploy` does not declare), `X_CONFIG_INVALID` (`--method helm` with no
868
+ `--release` and an `app.config.ts` whose `name` is not a helm release name),
869
+ `X_CLI_UNKNOWN_COMMAND`, `X_CLI_UNEXPECTED`.
870
+
871
+ **`--method compose` reads `.env.production` before its first step** (`As of 30.0.0`): no file, or a
872
+ `${VAR:?}` of `docker/docker-compose.prod.yml` it leaves empty (and the shell does not set), is
873
+ `X_DEPLOY_ENV_MISSING` naming the variables, and nothing runs; `x env init --production` writes the
874
+ file. A failed step's `X_DEPLOY_FAILED` cause carries the container's own line — the last rendered
875
+ `X_*` refusal, read from the one-shot's output or the serving role's `docker compose logs`.
876
+
877
+ ### x deploy verify
878
+
879
+ Does production serve this build, does it hold, and does a worker of this build claim every queue. A GitOps roll never runs `x deploy`; this runs in either case. HTTP and Postgres only. `As of 30.0.0`
880
+
881
+ ```bash
882
+ x deploy verify --url <origin> [--build <id>] [--window 60s] [--interval 5s] [--timeout 10m] [--idle-ok <queue,…>] [--json]
883
+ ```
884
+
885
+ | Flag | Type | Default | Meaning |
886
+ |---|---|---|---|
887
+ | `--url` | string | required | a bare `http(s)` origin; a path, query or credentials are refused |
888
+ | `--build` | string | `x.manifest.json`'s `buildId` | the id that must answer: `BUILD_ID`, or whatever CI stamped |
889
+ | `--window` | string | `60s` | how long consecutive good samples must span. `0`: answered once |
890
+ | `--interval` | string | `5s` | the gap between samples |
891
+ | `--timeout` | string | `10m` | the whole wait, window and queues together |
892
+ | `--idle-ok` | string | none | queues exempt while their depth (due + running) is 0: a scale-to-zero pool |
893
+
894
+ | Stage | Rule |
895
+ |---|---|
896
+ | sample | `GET <origin>/readyz` on a new connection each, `cache-control: no-cache`, redirects refused. Good: 2xx, `x-ultimate-build` equals the build, and no `Age` above 0 (a CDN copy is not the fleet) |
897
+ | window | wait for the first good sample, then `--window` of consecutive good ones. A bad sample restarts it, and since the first good one fewer than one sample in `ceil(window / interval)` may be bad: a fleet stuck with old pods stays red |
898
+ | coverage | samples through the load balancer, reported as samples. **Never a claim about each pod** |
899
+ | queues | after the window, when `DATABASE_URL` is set. Expected: `jobs.queues` plus every registered job's queue. Served: a row not draining, of this build. Re-read until served or `--timeout`; a failed read keeps the last grade, and none landing is `X_DB_UNAVAILABLE` |
900
+ | no `DATABASE_URL` | `queues.graded: false` and the summary says so. Never the embedded database, never counted as a pass |
901
+
902
+ `--json` `data`: `{ build, samples: { total, good, byBuild, byStatus, share, cached }, windowMs, queues: { graded, served, unserved: [{ queue, builds, depth }], idle } }`. `byStatus` counts a sample nothing answered as `unreachable`. `builds` names what the workers claiming that queue run (`unknown` for a row from before 30.0.0), so a stalled roll reads as one.
903
+
904
+ Errors: `X_DEPLOY_BUILD_MISMATCH` (no sample carried the build), `X_DEPLOY_BUILD_UNSTAMPED` (2xx with no header, and no build ever named), `X_DEPLOY_UNSTABLE` (the build answered, the window never closed), `X_DEPLOY_QUEUE_UNSERVED` (one finding per queue), `X_CLI_BAD_FLAG`.
905
+
906
+ `X_MIGRATE_CONCURRENT` **is thrown**, `As of 2026-08`. `ROLE=migrate` takes the migration lock by a
907
+ **bounded** `pg_try_advisory_lock` poll on one pinned session — one try per 500ms against a 60s
908
+ budget (`MIGRATION_LOCK_WAIT_MS`) — and holds it for the whole run. Two overlapping deploys
909
+ therefore serialise while the first is merely slow: the second waits, then applies. A lock still
910
+ held when the budget runs out is `X_MIGRATE_CONCURRENT` and a non-zero exit, because blocking
911
+ `pg_advisory_lock` has no timeout and a wedged predecessor sat inside one statement with nothing in
912
+ the logs — that is what 1.2.0 does → [Known gaps](Known-Gaps).
913
+
914
+ ## Serving in production — `ROLE`, `PORT` and `HOST`
915
+
916
+ There is no `x serve` command. A container runs the scaffolded `apps/web/server.ts`, which calls
917
+ `runRole({ root, env: Bun.env })` — the production boot path, with no dev watcher, no `/_x`, and
918
+ `dev: false`. It binds `0.0.0.0` unless `HOST` says otherwise, because a process bound to
919
+ loopback inside a container is unreachable from the port mapping, the load balancer and every
920
+ health probe at once.
921
+
922
+ | Env | Default | Meaning |
923
+ |---|---|---|
924
+ | `ROLE` | `web` | one of `web`, `sync`, `worker`, `scheduler`, `migrate`, `replicator`. **There is no `all`** |
925
+ | `PORT` | `3000` | empty or whitespace falls back to the default; anything else must be an integer in 0–65535 (`0` is legal — an ephemeral port) |
926
+ | `HOST` | `0.0.0.0` | the interface the `web` and `sync` roles bind, and the metrics endpoint with them — one decision. Empty or whitespace falls back to the default. `HOST=127.0.0.1` inside a container is **unreachable through `docker run -p`** — the proxy connects to the container's bridge address, never its loopback — and reachable only where the container shares the host's network namespace (`--network host`) or through a sidecar and `ssh -L` inside it. That is the exposure an app which admits one implicit actor without a login wants, and until 2026-09-07 such an app could not run in a container at all. `runRole({ …, hostname: '127.0.0.1' })` overrides the variable, the way `port` overrides `PORT`, for an app that must never trust the deployment to set it |
927
+
928
+ | Failure | Code | Fix as printed |
929
+ |---|---|---|
930
+ | `ROLE` names something else | `X_ROLE_UNKNOWN` | `docker run -e ROLE=web <image>` |
931
+ | `PORT` is not a TCP port | `X_PORT_INVALID` | `docker run -e PORT=3000 <image>` |
932
+
933
+ **`ROLE=migrate` is the release phase.** It starts only the db/queue pair, applies the app's
934
+ migrations through the ledger — carrying `APP_VERSION` when set — logs `{ applied, available,
935
+ appVersion }`, and **exits**. It never holds a port and never serves. Every other role loads the
936
+ app's modules (importing them *is* registration), resolves the build id from `BUILD_ID` or the
937
+ manifest, assembles its routes, and holds until SIGTERM → [Deployment](Deployment).
938
+
939
+ The scaffolded `docker/Dockerfile` defaults `NODE_ENV=production ROLE=web PORT=3000`, exposes 3000,
940
+ and health-checks `/readyz`.
941
+
942
+ ## x secrets
943
+
944
+ ```bash
945
+ x secrets [show|init|edit|set <NAME>|rotate [--drop <keyId>]] [--json]
946
+ ```
947
+
948
+ Two files, one committed and one never:
949
+
950
+ | File | Committed | Holds |
951
+ |---|---|---|
952
+ | `secrets.enc.json` | yes | the sealed envelope: `v`, `alg`, the master key's id, the IV, the ciphertext |
953
+ | `.secrets.key` | **no** — `x secrets init` writes the `.gitignore` rule before it writes the key | 64 hex characters, mode `0600`; on Windows, an ACL granting the current account alone |
954
+
955
+ `ULTIMATE_SECRETS_KEY` is read first and the key file second, so a container is handed its key by the platform and ships no key file at all.
956
+
957
+ **On Windows the mode keeps no one out** — every file reports `0666` and inherits the profile directory's ACL. So every key write (`init`, `rotate`, the staged `.secrets.key.next`) runs `icacls <temp> /inheritance:r /grant:r "<DOMAIN>\<user>:F"` on the temp file **before** the rename makes it live: one explicit entry, nothing inherited. Read it back with `icacls .secrets.key`. An `icacls` that fails writes no key at all: `X_SECRETS_KEY_ACL_FAILED`, fix `where.exe icacls`, or keep no key file and set `ULTIMATE_SECRETS_KEY`. A rename over a key another process holds open (an editor, an indexer, a virus scan) is retried 5 times over ~150 ms on `EPERM`/`EBUSY` before it fails. `As of 2026-10-05`.
958
+
959
+ **A secret is an environment variable.** The decrypted payload is a flat map of `ENV_NAME` to value, and `installSecrets()` writes each one into the process environment where nothing has already set it — the real environment always wins, so one image runs in Compose and on K8s with the same committed file. Everything downstream is what already existed:
960
+
961
+ ```ts
962
+ // app.config.ts
963
+ await installSecrets();
964
+ export const envSchema = {
965
+ SESSION_SECRET: { type: 'string', secret: true, description: 'Cookie signing key' },
966
+ } satisfies EnvSchema;
967
+ export const env = defineEnv(envSchema);
968
+ ```
969
+
970
+ One declaration, one row in `.env.example`, one mask (`maskedEnvValues`), one redaction entry, one reader (`env.SESSION_SECRET`). There is no `secrets.get()` — a second accessor would be values with no declaration, no type and no mask.
971
+
972
+ | Subcommand | Does |
973
+ |---|---|
974
+ | `show` (default) | names, lengths, and whether `envSchema` declares each one; retired master keys by id (`retiredKeyIds`). **Never a value**, in either renderer |
975
+ | `init` | a fresh master key, the `.gitignore` rule, and an empty sealed file. Refuses to overwrite either file |
976
+ | `edit` | decrypt into a temp buffer outside the repo, open `$VISUAL`/`$EDITOR`, reseal on save. The buffer is deleted in a `finally` and by a `SIGINT`/`SIGTERM` handler; an unchanged buffer writes nothing |
977
+ | `set <NAME>` | one value, read from **stdin** — never argv, which lands in shell history and in `ps` |
978
+ | `rotate` | reseal the same values under a new master key. The committed file is written first and the key last: only the committed one can be restored from git. The key it replaces joins the ring — see below |
979
+ | `rotate --drop <keyId>` | remove one retired key from the ring, by id. Generates no new key and leaves `.secrets.key` alone |
980
+
981
+ **The key ring.** `seal()` writes under the current key; `open()` picks the key a value names. So a
982
+ rotation must keep the old key readable until every sealed value is re-sealed:
983
+
984
+ | Step | Command | State |
985
+ |---|---|---|
986
+ | 1 | `x secrets rotate` | new key live; the old one is in `secrets.enc.json` as `ULTIMATE_SECRETS_RETIRED_KEYS`, sealed under the new key. A deploy is still handed one key |
987
+ | 2 | the app's re-seal `backfill()` | every value whose `sealedKeyId()` is a retired id is opened and sealed again |
988
+ | 3 | `x secrets rotate --drop <keyId>` | the retired key is gone; a value still naming it is `X_SEAL_KEY_UNKNOWN` |
989
+
990
+ `ULTIMATE_SECRETS_RETIRED_KEYS` is comma-separated, 64 hex characters each, and reaches the
991
+ process through `installSecrets()` like any secret — the real environment wins. It is kept by
992
+ default and never behind a flag: `rotate` overwrites the only copy of the old key on disk, so
993
+ dropping it then would leave every sealed value unreadable. `show` reports it by key id, not as
994
+ an app secret.
995
+
996
+ ```bash
997
+ $ x secrets init --json
998
+ {"ok":true,"command":"secrets","summary":"sealed secrets.enc.json — master key 4f2a…",
999
+ "data":{"path":"secrets.enc.json","keyPath":".secrets.key","keyId":"4f2a…","gitignore":"added"}}
1000
+
1001
+ $ printf %s "$STRIPE_KEY" | x secrets set STRIPE_KEY --json
1002
+ $ x secrets show
1003
+ name value length envSchema
1004
+ SESSION_SECRET [redacted] 44 chars yes
1005
+ STRIPE_KEY [redacted] 32 chars no
1006
+ ```
1007
+
1008
+ `--json` carries exactly what the terminal does, which is why no subcommand has a `--reveal` flag: the one way to see a value is `x secrets edit`, and the one way to read one is the app reading `env.<NAME>`.
1009
+
1010
+ Errors: `X_SECRETS_KEY_MISSING`, `X_SECRETS_KEY_INVALID`, `X_SECRETS_KEY_MISMATCH`, `X_SECRETS_FILE_MISSING`, `X_SECRETS_FILE_INVALID`, `X_SECRETS_TAMPERED`, `X_SECRETS_PLAINTEXT_INVALID`, `X_SECRETS_EDITOR_MISSING`, `X_SECRETS_EDIT_FAILED`, `X_GENERATE_CONFLICT` (`init` over an existing file), `X_CLI_BAD_FLAG` (`--drop` naming an id the ring does not hold).
1011
+
1012
+ ## x auth
1013
+
1014
+ ```bash
1015
+ x auth seal-mfa [--json]
1016
+ ```
1017
+
1018
+ One subcommand, and no default: a bare `x auth` is refused (`X_CLI_BAD_FLAG` names the subcommand), as `x db`, `x mcp` and `x pr` are — the one subcommand rewrites credential rows, and the read-only answer (how many are unsealed) is `x doctor`'s. `seal-mfa` seals every `x_users.mfa_secret` still stored
1019
+ in the clear, in place, under the app's master key (`As of 2026-10`). It is the one-shot an upgrade
1020
+ onto sealed second-factor secrets owes — `completeMfa` refuses a plaintext value with
1021
+ `X_MFA_SECRET_UNSEALED`, whose fix is this command, and `x doctor` reports how many rows are left.
1022
+
1023
+ | | |
1024
+ |---|---|
1025
+ | Opens | the app's database, as every other command does (`DATABASE_URL`, or the embedded one) — the framework's tables are applied first |
1026
+ | Needs | the master key: `ULTIMATE_SECRETS_KEY` or `.secrets.key`. With neither it is `X_SEAL_KEY_MISSING` and no row is written |
1027
+ | Idempotent | a sealed value is counted and never rewritten, so an interrupted run is finished by running it again |
1028
+ | Safe under traffic | each write is a compare-and-set on the value read: a user who re-enrolled mid-run keeps the new secret, and the row is counted `skipped` |
1029
+ | Reaches | `PostgresAuthAdapter`'s `x_users`. An app on its own `AuthAdapter` calls `sealMfaSecrets({ adapter })` from `@ultimat3/auth` |
1030
+
1031
+ ```bash
1032
+ $ x auth seal-mfa --json
1033
+ {"ok":true,"command":"auth","summary":"sealed 2 second-factor secret(s); 0 already sealed, 0 changed mid-run and left alone",
1034
+ "data":{"sealed":2,"alreadySealed":0,"skipped":0}}
1035
+ ```
1036
+
1037
+ Errors: `X_SEAL_KEY_MISSING`, `X_NOT_IN_APP`, `X_DB_UNAVAILABLE`, `X_AUTH_WRITE_FAILED`.
1038
+
1039
+ ## x vapid
1040
+
1041
+ ```bash
1042
+ x vapid [show|create] [--json]
1043
+ ```
1044
+
1045
+ The Web Push key pair `pwa.push` signs with (RFC 8292), `As of 26.1.0`. Both halves are environment
1046
+ variables — `ULTIMATE_VAPID_PUBLIC_KEY` and `ULTIMATE_VAPID_PRIVATE_KEY` — and never config, so they
1047
+ cannot come from different pairs. [PWA and offline](PWA-And-Offline#push).
1048
+
1049
+ | Subcommand | Does |
1050
+ |---|---|
1051
+ | `show` (default) | names the pair the boot would sign with — `environment`, `sealed` (`secrets.enc.json`) or `development` (the published pair a local process falls back to, which a deployed boot refuses) — and its public key. **Never the private key**, in either renderer |
1052
+ | `create` | mints a P-256 pair on WebCrypto and seals BOTH halves into `secrets.enc.json` in one write. Refuses (`X_GENERATE_CONFLICT`) when either is already sealed: a new pair unsubscribes every browser that subscribed with the old one, so rotating is `x secrets edit` (delete both lines) and then `create` — a decision, never a re-run |
1053
+
1054
+ ```bash
1055
+ $ x vapid create --json
1056
+ {"ok":true,"command":"vapid","summary":"sealed a new Web Push key pair into secrets.enc.json — ULTIMATE_VAPID_PUBLIC_KEY and ULTIMATE_VAPID_PRIVATE_KEY",
1057
+ "data":{"path":"secrets.enc.json","publicKey":"BBwN…","sealed":["ULTIMATE_VAPID_PUBLIC_KEY","ULTIMATE_VAPID_PRIVATE_KEY"]}}
1058
+ ```
1059
+
1060
+ A key pair minted elsewhere (`web-push generate-vapid-keys`) is the same format: set the two variables
1061
+ directly. `x env example` lists both in the Framework section once `pwa.push` is wired (`pwa.vapid`
1062
+ set), and `x env check` reports a deployed table that lacks them (`X_PWA_VAPID_KEY_MISSING`).
1063
+
1064
+ `show` reads `development` only when there is nothing sealed to read — no `secrets.enc.json`, or no
1065
+ master key for it. A sealed file it cannot open is that refusal, with its own fix
1066
+ (`X_SECRETS_TAMPERED`, `X_SECRETS_KEY_MISMATCH`, `X_SECRETS_FILE_INVALID`): the pair is there, and
1067
+ `create` would refuse it.
1068
+
1069
+ Errors: `X_SECRETS_KEY_MISSING` (`create` with no `x secrets init`), `X_GENERATE_CONFLICT`, `X_NOT_IN_APP`,
1070
+ and the `X_SECRETS_*` refusals above.
1071
+
1072
+ ## x manifest
1073
+
1074
+ ```bash
1075
+ x manifest [--check] [--no-openapi] [--json]
1076
+ ```
1077
+
1078
+ | Flag | Type | Default | Meaning |
1079
+ |---|---|---|---|
1080
+ | `--check` | boolean | `false` | fail if the committed files are stale instead of rewriting them |
1081
+ | `--openapi` / `--no-openapi` | boolean | `true` | also write `openapi.json` |
1082
+
1083
+ Regenerates the generated facts: routes, entities, actions, mutators, queries, jobs, tasks, policies, cache tags, MCP tools, budgets, build ID. Never hand-edit the output. Errors: `X_MANIFEST_STALE`, `X_MANIFEST_DRIFT`, `X_MANIFEST_BREAKING`.
1084
+
1085
+ ## x routes
1086
+
1087
+ ```bash
1088
+ x routes [--surface site|app|api|shared] [--json]
1089
+ ```
1090
+
1091
+ No positional: `x routes list` (the retired spelling) or `x routes app` is `X_CLI_UNKNOWN_COMMAND`
1092
+ with fix `x routes --json` — the filter is `--surface`.
1093
+
1094
+ **Every URL the app serves through a primitive, in one table** (`As of 2026-10-07`, owner decision 2):
1095
+ the pages from `describePages()`, then — sorted by path — the action and query routes from
1096
+ `apiRoutes()` (the builder `x dev` and the container mount) and the plain `routes` of
1097
+ `apps/<app>/runtime.ts`. The MCP dev tool `routes.list` answers the same rows.
1098
+
1099
+ | Row | Surface | Last column (`declared by`) | `--json` keys |
1100
+ |---|---|---|---|
1101
+ | a page | `site` / `app` | its file | `method` (`GET`), `path`, `surface`, `file`, `render`, `hydrate`, `offline`, `budget` |
1102
+ | a mounted page — every screen `defineAdmin()` serves | `site` / `app` | the call and the permissions that gate it: `defineAdmin() · admin:read + widgets:read` | the page keys plus `mount: { by, permissions }`; a file route has no `mount` key |
1103
+ | an action or query projection | `api` | the primitive, its name and its policy: `action publishPost · posts:publish` | `method`, `path`, `surface`, `primitive`, `name`, `auth`, `policy` |
1104
+ | a plain route — a `runtime.ts` one (an OAuth endpoint) or a declared `webhookReceiver()`'s (`webhook.<name>`, public) | `api` | `runtime.ts routes <name>`, then its policy | the same keys, `primitive: null` |
1105
+
1106
+ ```bash
1107
+ $ x routes --surface api --json
1108
+ {"ok":true,"command":"routes","summary":"2 routes","findings":[],"data":{"routes":[
1109
+ {"method":"GET","path":"/_x/query/org-feed","surface":"api","primitive":"query","name":"orgFeed","auth":"public","policy":"allow"},
1110
+ {"method":"POST","path":"/api/posts/publish","surface":"api","primitive":"action","name":"publishPost","auth":"required","policy":"posts:publish"}]}}
1111
+ ```
1112
+
1113
+ Not listed: a bearer mount's copies of the API (`defineApi({ http: { mounts } })`), a page's
1114
+ `post:` binding, and the framework's own asset, island and `/_x` routes.
1115
+
1116
+ Errors: `X_ROUTE_CONFLICT`, `X_ROUTE_META_MISSING`.
1117
+
1118
+ ## x mcp
1119
+
1120
+ ```bash
1121
+ x mcp tools [--json]
1122
+ x mcp serve [--transport stdio|http] [--port 9229] [--json]
1123
+ ```
1124
+
1125
+ | Flag | Type | Default | Meaning |
1126
+ |---|---|---|---|
1127
+ | `--transport` | string | `stdio` | `stdio` for an editor client, `http` for a socket |
1128
+ | `--port` | string | `9229` | HTTP port when `--transport http`. `9229` is also the inspector's default; a taken port is `X_PORT_IN_USE`, naming the next one |
1129
+
1130
+ Serves `@ultimat3/mcp`'s dev server — 19 tools, one catalog, the same on both transports. Every
1131
+ tool declares a scope; the local developer's caller carries all five, and an HTTP caller carries
1132
+ whatever its bearer token was issued.
1133
+
1134
+ | Tool | Does | Scope |
1135
+ |---|---|---|
1136
+ | `routes.list` | the `x routes --json` table: pages (render mode, hydrate, offline, budget), then every action, query and `runtime.ts` route | `dev:read` |
1137
+ | `schema.describe` | entities with columns, types and invariants | `dev:read` |
1138
+ | `policies.list` | every policy: permission, subject, where it is enforced | `dev:read` |
1139
+ | `actions.describe` | actions and queries: schemas, policy, cache tags, MCP exposure | `dev:read` |
1140
+ | `jobs.inspect` | job definitions, retry policy and steps | `dev:read` |
1141
+ | `queue.depth` | pending, running and failed counts per queue | `dev:read` |
1142
+ | `manifest.read` | the generated `x.manifest.json` as text | `dev:read` |
1143
+ | `errors.explain` | `X_*` → cause, fix, docs | `dev:read` |
1144
+ | `db.query` | ONE read-only SQL statement; `limit` defaults to 100 rows, maximum 1000 | `db:read` |
1145
+ | `db.migrate` | apply pending migrations to a **branch** database | `db:migrate` |
1146
+ | `tests.run` | run the suite, structured results — an error outside any test (`1 error` beside `0 fail`) or a non-zero exit is a failure, never `failed: 0` | `dev:test` |
1147
+ | `verify.run` | run `x verify`, structured per-step result | `dev:test` |
1148
+ | `logs.tail` | last N log lines, optionally for one role | `dev:logs` |
1149
+ | `graph.query` | `{ node }`: what a change to a file or primitive reaches (`x impact`); `{ from, to }`: the edges linking two nodes (`x graph --from --to`) | `dev:read` |
1150
+ | `ui.shot` | photograph one route at a named viewport, light or dark; PNG path plus verdict. Launches a browser | `dev:test` |
1151
+ | `ui.island` | photograph an island in every state its `*.island.states.ts` declares, as `x shot --island` does | `dev:test` |
1152
+ | `ui.inspect` | DOM, computed-style and accessibility facts for a bounded set of selectors, in one navigation | `dev:test` |
1153
+ | `ui.interact` | drive one route through a bounded step list, then photograph it; refuses a password field and any step that leaves the origin | `dev:test` |
1154
+ | `ui.diff` | pixel-compare two PNGs the other `ui.*` tools wrote under `.x/shot/`; launches no browser | `dev:read` |
1155
+
1156
+ `db.query` and `db.migrate` refuse structurally — multiple statements, a mutating keyword
1157
+ (a data-modifying CTE included), a locking clause, `EXPLAIN ANALYZE`, a non-branch target —
1158
+ before the host runs anything.
1159
+
1160
+ Never exposed in `ROLE=web`. Errors split by when they fire:
1161
+
1162
+ | When | Codes | Effect |
1163
+ |---|---|---|
1164
+ | boot — configuring an MCP surface with `defineAppMcp` | `X_MCP_TOOL_UNDECLARED`, `X_MCP_TOOL_UNSAFE`, `X_MCP_TOOL_DUPLICATE` | the call throws; no server starts |
1165
+ | runtime — one request | `X_MCP_TOOL_UNKNOWN`, `X_INPUT_INVALID` (an `isError` result), `X_MCP_SCOPE_DENIED`, `X_MCP_QUERY_REJECTED`, `X_MCP_NOT_BRANCH_DB`, `X_MCP_PROTOCOL` | that call is refused; the server keeps serving |
1166
+
1167
+ A tool this caller may not see is absent from `tools/list` and answers ToolNotFound, never Forbidden. `x token grant <scope>` takes effect on the next connection — scopes are fixed for the life of one. Full model: [MCP and AI](MCP-And-AI).
1168
+
1169
+ ## x doctor
1170
+
1171
+ ```bash
1172
+ x doctor [--port 3000] [--json]
1173
+ ```
1174
+
1175
+ Checks Bun version, env completeness, source drift, **the newest migration's `.snapshot.json`**, port availability, PWA prerequisites and **embedded-Postgres readiness** — each failing check carries its own fix command.
1176
+
1177
+ It also lists the shipped guards the app's `guards/` does not hold, and the ones it holds that differ from the current template — a copy from an older release, or the app's own edit. `As of 2026-10-07` (owner decision 8: shipped guards stay copies the app owns).
1178
+
1179
+ | Listing | One line each | `--json` |
1180
+ |---|---|---|
1181
+ | missing | `x g guard <name>` | `data.guards.missing[]`: `{ name, add }` |
1182
+ | differs from the template (line endings ignored) | `x g guard <name> --force` — rewrites the guard and its test; review the diff, and restore your edit if it was deliberate | `data.guards.differs[]`: `{ name, refresh }` |
1183
+
1184
+ A listing, never a finding: deleting or editing a guard is how an app changes a rule, so the verdict does not move.
1185
+
1186
+ **The chart's monitoring templates**, the same way (`As of 2026-11`): an app with a `docker/helm/Chart.yaml` and no `templates/servicemonitor.yaml` or `templates/alert-rules.yaml` gets one line per missing file, then `x g chart-alerts`, which writes only those (`--json`: `data.chartAlerts`, `{ missing[], add }`). A chart that dropped them on purpose keeps a green verdict; an app with no chart is listed nothing.
1187
+
1188
+ The embedded half is the bare-VM case: the external-database probe answers nothing the moment `DATABASE_URL` is unset, and that silence *is* the configuration a fresh box has. So `x doctor` also asks whether `@electric-sql/pglite` resolves from the app root — a resolve, never an import, because loading it boots the WASM build and takes the single-writer lock the next command needs. Red only where both hold: `DATABASE_URL` unset **and** the peer unresolvable, reported as `X_DB_UNAVAILABLE` with `@ultimat3/db`'s own sentence and its own runnable fix ([Bare VM](Bare-VM)). The snapshot half is `As of 2026-08` and separate from drift on purpose: they are two questions with two remedies, and `x db gen`'s own `X_MIGRATION_SNAPSHOT_MISSING` was a condition this diagnostic could not see at all, so `x doctor --json` — the `fix:` of the `X_CLI_UNEXPECTED` an author reaches it through — ran clean over a broken app.
1189
+
1190
+ **It probes BOTH ports `x dev` binds** — `PORT` for the web role and `PORT + 1` for the sync node —
1191
+ and the suggestion moves both: `x doctor --port 3000` with 3001 taken answers `x dev --port 3002`,
1192
+ never `3001`, which is the port it has just reported as taken (`As of 2026-09`; the same repair
1193
+ `X_PORT_IN_USE`'s own `fix:` took in `x dev`). A `--port` at the top of the range has no room for
1194
+ the sync node at all, so it reports the boot's own `X_PORT_INVALID` rather than probing a port
1195
+ below it that `x dev` would never bind. The port is `x dev`'s own: `--port`, then `PORT`, then
1196
+ 3000 — under `PORT=4000` it probes 4000 and 4001, never 3000.
1197
+
1198
+ **`APP_URL`, when set**, must be an `http://` or `https://` origin (`X_CONFIG_INVALID`, the sync
1199
+ boot's own refusal), and a loopback one must name that same port — `APP_URL=http://localhost:3000`
1200
+ beside `PORT=4000` is `X_APP_URL_PORT_MISMATCH`, whose fix is the `export` line with the port `x dev`
1201
+ serves on. Unset is no finding; a public origin is the deploy's and is not judged here.
1202
+
1203
+ **Sealed columns against the key ring.** An app that declares `.sealed()` columns is checked for
1204
+ two conditions; an app with none is never judged.
1205
+
1206
+ | Finding | Condition | Fix |
1207
+ |---|---|---|
1208
+ | `X_SEAL_KEY_MISSING` | sealed columns, and no usable master key in this environment | `x secrets init`, or export `ULTIMATE_SECRETS_KEY` |
1209
+ | `X_SEAL_RESEAL_PENDING` | sealed columns, and a retired key still in `ULTIMATE_SECRETS_RETIRED_KEYS` — a rotation whose re-seal is not finished | a `backfill()` rewriting each row, then `x secrets rotate --drop <keyId>` |
1210
+
1211
+ The ring is read as a booted process would see it — the real environment first, then the
1212
+ committed `secrets.enc.json`.
1213
+
1214
+ **What an upgrade left in the auth tables** (`As of 2026-10`). Asked of an external
1215
+ `DATABASE_URL` only — the embedded database is never opened by a diagnostic.
1216
+
1217
+ | Finding | Condition | Fix |
1218
+ |---|---|---|
1219
+ | `X_MFA_SECRET_UNSEALED` | `x_users` rows hold a second-factor secret that is not sealed; each is refused at the second factor until it is | `x auth seal-mfa --json` |
1220
+ | `X_FRAMEWORK_TABLE_ORPHANED` | `x_auth_failures` is still present: no release reads it, and the boot does not drop a table a replica on the previous release may still be using | `psql "$DATABASE_URL" -c 'drop table if exists x_auth_failures'`, once every replica runs this release |
1221
+
1222
+ The output is a `CommandResult` like every other command's: `findings`, not a `checks` array.
1223
+
1224
+ ```bash
1225
+ $ x doctor --json
1226
+ {"ok":false,"command":"doctor","summary":"1 finding(s)","findings":[{"code":"X_DB_DRIFT",
1227
+ "cause":"packages/db has a schema but no migration recorded it",
1228
+ "fix":"x db gen \"initial\"","docs":"https://github.com/developerz-ai/ultimate/wiki/Error-Codes",
1229
+ "at":"packages/db/migrations"}],"data":{"count":1,"codes":["X_DB_DRIFT"]}}
1230
+ ```
1231
+
1232
+ ## x actions · x queries · x entities
1233
+
1234
+ ```bash
1235
+ x actions [list|show <name>] [--json]
1236
+ x queries [list|show <name>] [--json]
1237
+ x entities [list|show <name>] [--json]
1238
+ ```
1239
+
1240
+ The declaration registries, projected. `list` is the default subcommand; `show <name>` is one declaration (it was `describe` until 26.0.0). Same rows the manifest and the MCP `actions.describe` tool are built from — the CLI keeps no second table.
1241
+
1242
+ ```bash
1243
+ $ x actions list
1244
+ name verb resource path capability mcp
1245
+ createPost create posts /api/posts/create post:create yes
1246
+ publishPost publish posts /api/posts/publish post:publish yes
1247
+
1248
+ $ x entities show posts --json
1249
+ {"ok":true,"command":"entities","summary":"entity posts","data":{"name":"posts","table":"posts",
1250
+ "primaryKey":["id"],"columns":[…],"invariants":[…],"softDelete":true,"orgScoped":true}}
1251
+ ```
1252
+
1253
+ A name nothing registered is `X_DECLARATION_UNKNOWN`, whose `fix` names the nearest real one. A module that would not import is reported as a finding — the listing describes what loaded, and never pretends the rest is absent.
1254
+
1255
+ ## x jobs
1256
+
1257
+ ```bash
1258
+ x jobs [list|show <id>|retry <id>|cancel <id>|delete <id>|promote <id>|resolve-step <id> <step>
1259
+ |pause <queue>|resume <queue>|drain] [--queue q] [--state s] [--name n] [--limit n]
1260
+ [--after cursor] [--from-step name] [--reason text] [--ran|--not-ran] [--output json] [--json]
1261
+ ```
1262
+
1263
+ | Subcommand | Does |
1264
+ |---|---|
1265
+ | `list` | queue depth (with a `failed` bucket), ONE page of matching rows, the dead-letter list — a dead job is never filtered out of view — the `backfill()` passes **in flight**, the paused queues and the live workers. `--limit` is 1 to 200 (default 100). `--json` answers the framework's one page shape — `data.rows`, `data.nextCursor`, `data.hasMore` — and `nextCursor` is a string **exactly when another row exists**: the command reads one row past the page (at 200, one probe after it), so a full last page is `hasMore: false` with no cursor, never one that fetches an empty page. Pass `nextCursor` as `--after` while `hasMore` is true. Until 25.0.0 it was `data.next`, guessed from the row count |
1266
+ | `show <id>` | state, attempt, every step's result, the payload (`input`, with every key the app declared secret redacted), the failure's `stack`, the last `progress` the body reported, the remaining retry delays, the concurrency key the run counts under (`concurrencyKey` — `concurrency.key(input)`, `null` unless the job declares a keyed cap), and the `x_backfills` row for this run when the job is a backfill (`backfill: null` for every other job) |
1267
+ | `retry <id>` | re-queue a job that has FINISHED; a job still `ready`, `delayed` or running is refused with `X_JOB_NOT_REQUEUEABLE`. `--from-step <name>` drops that step and every step that started after it, so they re-execute while everything before it replays from storage |
1268
+ | `cancel <id>` | stop a job that has not finished — the way to end a runaway `backfill()` sweep. `--reason <text>` is recorded on the job. Re-reads the row after cancelling, so **exit 0 means it is genuinely stopped**: a job that already finished, an id no queue holds, and a driver with no `introspect.cancel` all raise `X_JOB_NOT_CANCELLABLE` rather than reporting success |
1269
+ | `delete <id>` | delete one job and its step records. An unknown id is `X_JOB_UNKNOWN`; a **running** job is refused with `X_JOB_NOT_REMOVABLE`, whose fix is `x jobs cancel <id> --json` — deleting the row under a body does not stop the body |
1270
+ | `promote <id>` | make a job waiting on its run time — delayed at enqueue, or backing off before a retry — due now. Anything else (due, running, finished, suspended in a `step.sleep`) is `X_JOB_NOT_PROMOTABLE`, naming the state |
1271
+ | `resolve-step <id> <step>` | answer a `once` step whose outcome is unknown (`X_STEP_OUTCOME_UNKNOWN`): `--ran --output '<json>'` records it `completed` — a replay returns that JSON, default `null` — and `--not-ran` records it `cleared`, so the next run calls it again. Exactly one of the two. Refused on a running job, or a step that did not start-and-not-complete (`X_STEP_NOT_RESOLVABLE`). Answers the job's trace; then `x jobs retry <id>` |
1272
+ | `pause <queue>` | no worker claims from the queue; enqueues still land. A row every worker's next claim reads, so it holds **fleet-wide within one poll interval**. Idempotent. Answers the paused list as the queue now holds it |
1273
+ | `resume <queue>` | undo it |
1274
+ | `drain` | **planned**, `As of 25.0.0`: exits `X_NOT_IMPLEMENTED` before the queue boots and before a job is leased, pointing at `x jobs list --json`. Postgres is the one durable job driver, so there is nowhere to drain to. Its two former `--to` values, `redis` and `nats`, were `X_NOT_IMPLEMENTED` stubs on every method — a drain leased the whole pending batch for five minutes, failed every enqueue and nacked it back — and 25.0.0 deleted both stubs and the unreachable drain body. 25.0.0 also deleted the `--to` and `--dry-run` flags: the parser refuses them as unknown |
1275
+
1276
+ `show`, `retry`, `cancel`, `delete` and `promote` each take **one id positional**, `resolve-step` an id and a step name; `pause` and `resume` take **one queue positional**. There is no bulk verb here — bulk is `requeueMany` / `removeMany` on `JobIntrospection`, which a dashboard calls. `--queue`, `--state`, `--name`, `--limit` and `--after` narrow `list` only. A bare `x jobs` is `list`. One verb each, no alias: `ls` and `rm` are `X_CLI_UNKNOWN_COMMAND` since 26.0.0.
1277
+
1278
+ ```bash
1279
+ x jobs cancel 019ff1c5-0000-7000-8000-000000000001 --reason "wrong tenant" --json
1280
+ ```
1281
+
1282
+ Runs against the app's own driver — the ambient one when a process already installed it, otherwise the same embedded Postgres queue `x dev` boots. `show`, `retry` and `cancel` load the app first, `As of 2026-10`: a trace is the row read through the job's own declaration, so unloaded every run showed `concurrencyKey: null` and no retry delays. An app that half-loads still answers, with those two fields degraded. An `<id>` that is not a uuid is `X_JOB_UNKNOWN` before any statement is sent — `x jobs show nosuch` used to reach Postgres and come back `X_DB_STATEMENT_FAILED [22P02]`.
1283
+
1284
+ `list` reports only the sweeps still **running**, because it is a live view of the queue; the whole
1285
+ ledger, finished passes included, is `x db backfill --list`. A driver that ships no backfill ledger
1286
+ answers with no passes rather than failing the command — the queue is still the question here.
1287
+
1288
+ Errors: `X_JOB_UNKNOWN`, `X_JOB_NOT_CANCELLABLE`, `X_JOB_NOT_REMOVABLE`, `X_JOB_NOT_PROMOTABLE`, `X_CLI_BAD_FLAG` (a `--limit` over 200 names the walk), and `X_NOT_IMPLEMENTED` from a driver with no introspection.
1289
+
1290
+ A run refused by its concurrency key reads `state: failed` with `X_JOB_KEY_BUSY` in `lastError`; the fix it carries is `x jobs list --name <job> --state running --json` — the runs holding the key.
1291
+
1292
+ ## x tasks
1293
+
1294
+ ```bash
1295
+ x tasks [list|show <name>] [--count n] [--json]
1296
+ ```
1297
+
1298
+ | Subcommand | Does |
1299
+ |---|---|
1300
+ | `list` | every registered `task`: cron, timezone, catch-up policy, the jobs it enqueues, the occurrence it **last** fired and its next one |
1301
+ | `show <name>` | the same plus the cron in words and the next `--count` occurrences (default 5, max 50) |
1302
+
1303
+ | `--json` field | Is |
1304
+ |---|---|
1305
+ | `nextMs` · `next` | the next occurrence: epoch ms, and ISO-8601 in the task's zone |
1306
+ | `lastMs` · `last` | the occurrence the scheduler last **dispatched** — what it was scheduled for, same two renderings. `null` (`-` in the table) for a task that has never fired |
1307
+ | `lastFiredAtMs` | when that dispatch happened, on the queue's clock. `lastFiredAtMs - lastMs` is the scheduler's lag |
1308
+
1309
+ The last fire is read from the app's queue (`JobIntrospection.taskFires()`), so `x tasks` opens it
1310
+ the way `x jobs list` does: the running one inside `x dev`, the app's own otherwise — about 0.6 s on
1311
+ an embedded database already initialised, and nothing in an app that declares no task. It is the last
1312
+ occurrence that enqueued, never the scheduler's watermark — arming and skipping move that.
1313
+ `As of 2026-10`.
1314
+
1315
+ Every instant is rendered in the task's **own** `tz`, never a machine-local default: a `0 3 * * *` in `America/New_York` reads `2026-03-06T03:00:00-05:00` before the spring-forward and `2026-03-09T03:00:00-04:00` after it. Same wall clock, different instant — the ambiguity the required `tz` exists to remove.
1316
+
1317
+ Errors: `X_DECLARATION_UNKNOWN` (with the nearest name as its fix), `X_CLI_BAD_FLAG`.
1318
+
1319
+ ## x exercise
1320
+
1321
+ ```bash
1322
+ x exercise [<name>] [--seed n] [--json]
1323
+ ```
1324
+
1325
+ Every action, query and job the app registers, run **once** against generated rows, with no test
1326
+ written. The question no written test answers: what does each primitive do when it actually runs?
1327
+
1328
+ | Step | What happens |
1329
+ |---|---|
1330
+ | database | a scratch one — embedded, or a server named by `TEST_DATABASE_URL` when a migration needs an extension the embedded one cannot link — with the framework tables and every migration applied. Never the developer's own: the pass commits |
1331
+ | rows | a fresh pair of tenants per primitive, every table seeded off its declaration (`x db seed --generate`'s graph) |
1332
+ | actor | the first of `x policy explain`'s actors the primitive's policy allows, in its own tenant |
1333
+ | input | the override, then an id field whose stem names a seeded entity (`postId` → `posts`), then the minimal draw of its schema |
1334
+ | room | network sealed, mail captured, job queue in memory, clock frozen. A call out is refused, so the primitive is `unreached`, never sent |
1335
+
1336
+ Each primitive ends `reached`, `refused` (no actor its policy allows — by design), `off` (an
1337
+ override with a reason) or `unreached`. `--json` and `.x/exercise.json` carry the same document:
1338
+ `{ seed, counts, records[] }`, each record `{ kind, name, outcome, code?, reason?, statements,
1339
+ effects, ms, override? }`. A `<name>` runs that one primitive; an unknown name is
1340
+ `X_DECLARATION_UNKNOWN`.
1341
+
1342
+ `unreached` is red: `X_CONTRACT_EXERCISE_UNREACHED`, and its `fix:` is the entry to paste into
1343
+ `apps/web/exercise.ts` — the `exercise` option `.contract()` takes, one shape:
1344
+
1345
+ ```ts
1346
+ import type { Actor } from '@ultimat3/core';
1347
+ import { defineExercise } from '@ultimat3/testing';
1348
+
1349
+ declare const author: Actor; // a signed-in actor the policy allows
1350
+
1351
+ export const exercise = defineExercise({
1352
+ publishPost: {
1353
+ actor: author,
1354
+ input: (drawn) => ({ ...(drawn as Record<string, unknown>), postId: '<a real id>' }),
1355
+ },
1356
+ sendInvoice: { off: 'charges a card through the payment provider' },
1357
+ });
1358
+ ```
1359
+
1360
+ A blank `off` reason is `X_CONTRACT_EXERCISE_UNDECLARED`; so is an `exercise.ts` with no single
1361
+ `defineExercise()` export. The gate's `exercise` step runs this command as a child and is red on
1362
+ exactly its findings, plus the static half of the tenant proof: an entity with no tenant column
1363
+ that references one with a tenant and declares no `shared` reason.
1364
+
1365
+ Errors: `X_CONTRACT_EXERCISE_UNREACHED`, `X_CONTRACT_EXERCISE_UNDECLARED`, `X_DECLARATION_UNKNOWN`, `X_CLI_BAD_FLAG`.
1366
+
1367
+ ## x drill
1368
+
1369
+ ```bash
1370
+ x drill [<name>] [--list] [--budget-ms n] [--json]
1371
+ ```
1372
+
1373
+ The app run through its dependencies failing, `As of 2026-11-06`. Each drill boots a fleet on a
1374
+ scratch database made on `TEST_DATABASE_URL` and dropped after: `ROLE=migrate`, then `web` and
1375
+ `worker` as separate processes (`apps/web/server.ts`), each dialling Postgres, and Redis or NATS
1376
+ where the app declares them, through a Bun TCP fault proxy; `nats-down` adds the `sync` role and a
1377
+ sync client that dials it. Load runs before, through and after the fault over the site's
1378
+ parameterless pages and every action an anonymous POST reaches, each request a fresh input drawn
1379
+ as `x exercise` draws one (`apps/web/exercise.ts` honoured; `data.skippedWrites` names every action
1380
+ left out and why) — the writes are how a job reaches the queue for the worker drills. `/readyz` is
1381
+ sampled throughout.
1382
+
1383
+ | Drill | Fault | Must hold |
1384
+ |---|---|---|
1385
+ | `pg-blackhole` | live sockets unanswered, server healthy | `requests-coded`, `requests-bounded`, `recovers`, `jobs-once` |
1386
+ | `pg-slow` | every byte delayed half the deadline | `requests-coded`, `requests-bounded`, `recovers` |
1387
+ | `pg-restart` | `reset`, then `refuse`, then `pass` | `requests-coded`, `readyz-flips`, `recovers`, `jobs-once` |
1388
+ | `redis-down` | the cache tier blackholed | `requests-coded`, `requests-prompt`, `requests-ok`, `recovers` |
1389
+ | `nats-down` | the bus refused | `requests-coded`, `recovers`, `sync-resumes` |
1390
+ | `worker-sigkill` | `SIGKILL` the worker, then a fresh one | `jobs-terminal`, `jobs-once`, `requests-coded` |
1391
+ | `worker-sigterm` | `SIGTERM` the worker, then a fresh one | `role-exits`, `jobs-terminal`, `jobs-once` |
1392
+ | `web-sigterm` | `SIGTERM` the web role under load | `requests-ok`, `role-exits` |
1393
+
1394
+ Thresholds come from the app: the request deadline (`@ultimat3/http`'s default, 30 s, because
1395
+ `configureHttp` is runtime code the CLI does not run), `drain.deadlineMs` plus
1396
+ `drain.readinessGraceMs`, and `health.readiness` — under `'process'`, `readyz-flips` expects 200
1397
+ throughout. `--budget-ms` replaces the deadline the verdict judges against, not the app's, and is
1398
+ printed in `data.budgets.override`. An answer may reach the load up to 500 ms past the deadline
1399
+ (`DRILL_ANSWER_SLACK_MS`: the 504 a deadline fires arrives a beat after it). A 5xx with no `X_*`
1400
+ code, or `X_INTERNAL`, is always red. A property with no evidence is red, never passed: a job
1401
+ property is red when no job ran during the drill, `sync-resumes` when no sync client ran.
1402
+
1403
+ A service the app uses must be up for every drill; an unset one is refused, never skipped. A named
1404
+ drill that faults a service the app does not declare is refused; a whole run lists it under
1405
+ `data.notRun`. `--list` prints the table and runs nothing. `--json`: `{ budgets, targets,
1406
+ skippedWrites, drills[], notRun }`, each drill `{ name, verdict, evidence, budgets, baseline }`.
1407
+
1408
+ Errors: `X_DRILL_FAILED` (one per property that did not hold), `X_DRILL_SERVICE_MISSING`, `X_CLI_UNKNOWN_COMMAND`, `X_CLI_BAD_FLAG`.
1409
+
1410
+ ## x policy
1411
+
1412
+ ```bash
1413
+ x policy [list|explain <subject>] [--json]
1414
+ ```
1415
+
1416
+ | Subcommand | Does |
1417
+ |---|---|
1418
+ | `list` | every permission, the roles that grant it, and the actions and queries that enforce it — plus the permissions **nothing** enforces, which are grants that do nothing |
1419
+ | `explain <subject>` | the allow/deny matrix, one row per actor per declaration — every declared role plus `anonymous`, evaluated once for each action or query that enforces the subject — naming the clause that decided and its reason |
1420
+
1421
+ `<subject>` resolves in order against a permission (`post:publish`), an action name (`publishPost`), a query name (`postFeed`), then an action's HTTP path (`/api/posts/publish`) — so the `fix:` line printed by an `X_FORBIDDEN` is runnable whichever of the four the throwing surface had to hand.
1422
+
1423
+ ```bash
1424
+ $ x policy explain publishPost
1425
+ action publishPost — policy post:publish
1426
+ actor verdict deciding reason
1427
+ anonymous deny post:publish no actor for post:publish
1428
+ author deny post:publish post:publish predicate returned false
1429
+ reader deny post:publish actor lacks post:publish
1430
+ evaluated with no request input and no row — a rule reading either decides again on the real request
1431
+ ```
1432
+
1433
+ The verdict comes from `@ultimat3/policy`'s own `policyMatrix()` over the app's real `Policy` objects — the same evaluation the request path runs, never a second one. It runs **outside a request**, which is what the last line says: a rule reading input or a row decides again on the real call, so a `predicate returned false` here is a no-input verdict rather than a standing denial. A policy that cannot be evaluated at all outside a request — a predicate dereferencing `input.post.id` has nothing to dereference — reports `decidable: false` and prints that note in place of the table, never a table of invented denials.
1434
+
1435
+ Errors: `X_DECLARATION_UNKNOWN`, `X_CLI_BAD_FLAG`.
1436
+
1437
+ ## x i18n
1438
+
1439
+ ```bash
1440
+ x i18n [check|add <locale>|sync <locale>] [--json]
1441
+ ```
1442
+
1443
+ | Subcommand | Does |
1444
+ |---|---|
1445
+ | `check` | scan every `t('…')` in the app's source, audit it against every catalog on disk, and **fail** on a missing key |
1446
+ | `add <locale>` | write `packages/i18n/catalogs/<locale>.json` seeded with the default locale's keys and values |
1447
+ | `sync <locale>` | add the keys that locale is missing; a key it already has is never overwritten, translated or not |
1448
+
1449
+ Catalogs are one flat file per locale under `packages/i18n/catalogs/`. `check` reports three things separately: `missing` (a gap, and an exit code), `unused` (defined and never called), and `dynamic` — a `t(`plans.${plan}.name`)` the extractor cannot resolve. A dynamic call contributes its static head as a runtime-key prefix, so a key only ever reached that way is never listed unused; an expression with no static head contributes nothing, because a guessed prefix would suppress real gaps.
1450
+
1451
+ Errors: `X_CATALOG_MISSING_KEYS` (per locale, as a finding), `X_I18N_FAMILY_INCOMPLETE` (a `_select_`/`_ordinal_` family with no `_other`), `X_CATALOG_INVALID`, `X_GENERATE_CONFLICT` (`add` over an existing catalog), `X_CLI_BAD_FLAG`, `X_SCAFFOLD_PATH_ESCAPE`.
1452
+
1453
+ ## x mail
1454
+
1455
+ ```bash
1456
+ x mail preview <id> [--locale <tag>] [--tz <zone>] [--out <dir>] [--json]
1457
+ ```
1458
+
1459
+ `As of 2026-11-06`. Renders the mail's `defineMail({ preview })` data through `renderMessage`'s own
1460
+ path, with nothing sent. No `--locale`: every locale the app routes.
1461
+
1462
+ | Flag | Type | Default | Meaning |
1463
+ |---|---|---|---|
1464
+ | *(positional)* | mail id | — | the `defineMail({ id })` to render |
1465
+ | `--locale` | string | every routed locale | one locale |
1466
+ | `--tz` | IANA zone | `UTC` | every date in the body; never the machine's zone (`X_TIMEZONE_INVALID`) |
1467
+ | `--out` | string | — | write `<id>.<locale>.html` and `<id>.<locale>.txt` there |
1468
+
1469
+ `--json`: `data.tz` is the zone rendered in; `data.previews[]` is `{ locale, subject, missing, files }`; `missing` names every catalog
1470
+ key the locale cannot resolve, and any is a non-zero exit. `x verify`'s `i18n` step renders every
1471
+ declared preview in every locale the same way, so a preview is a test too.
1472
+
1473
+ Errors: `X_MAIL_PREVIEW_MISSING`, `X_MAIL_PREVIEW_INVALID`, `X_MAIL_TEMPLATE_UNKNOWN`, `X_CLI_BAD_FLAG`.
1474
+
1475
+ ## x test
1476
+
1477
+ ```bash
1478
+ x test [unit|contract|live|job|e2e|eval] [--filter path[,path…]] [--allow-empty] [--sample N]
1479
+ [--affected [--base <ref>] [--dirty]] [--workers N] [--seed N] [--json]
1480
+ [-- <bun test flags>]
1481
+ ```
1482
+
1483
+ | Flag | Type | Default | Meaning |
1484
+ |---|---|---|---|
1485
+ | *(positional)* | test type | every type | one of the six; a test's type is its filename suffix |
1486
+ | `--filter` | string | — | only files whose path contains this substring — or ANY of several, comma-separated (`--filter apps/web/app/billing/,apps/web/app/case/`), in one run. An empty item (`a,,b`) is refused: `''` matches every path |
1487
+ | `--allow-empty` | boolean | off | a selection that matches no test file exits **0**, spawns nothing, and says so (`no test file matches … — 0 test file(s) ran`, `data.files: 0`, `data.empty: true`) instead of `X_TEST_NO_FILES`. For a runner that computes the selection; a hand-typed typo stays red without it |
1488
+ | `--sample` | string | — | run at most N files of the selection, deterministically. A fast signal for the eval loop — **never a gate** |
1489
+ | `--workers` | string | one per 1.25 GiB of a `max(2.75 GiB, 25% of RAM)` budget, at most one per core (the gate's width) | worker count, 1 to 64; each worker gets its own template-cloned database |
1490
+ | `--affected` | boolean | off | narrow the selection to the tests the diff can break: in an app root, by import closure (the files `x affected` reports as `data.tests`); elsewhere, the workspaces the diff touches and everything that depends on them. `--base`/`--dirty` without it are refused, not ignored |
1491
+ | `--base` | string | `main` | the ref to diff against, merge-base (`<base>...HEAD`). Needs `--affected` |
1492
+ | `--dirty` | boolean | off | union the working tree in — uncommitted and untracked. Needs `--affected` |
1493
+ | `--seed` | string | — | replay one `simulateJobs` seed, the `N` an `X_JOB_SIM_INVARIANT` names: handed to every `bun test` child as `ULTIMATE_JOB_SIM_SEED`, which runs that seed alone. A uint32, else `X_CLI_BAD_FLAG`. The shard reproduce line does not carry it yet |
1494
+ | `-- <args>` | tail | — | handed to `bun test` verbatim, before the file list — `x test unit -- --coverage --bail`. The reproduce line carries it back. **`x test` is the only command that reads a `--` tail**: every other one refuses it (`X_CLI_BAD_FLAG`) rather than dropping it, which is what all of them did until 2026-09 |
1495
+
1496
+ The type rule is `x verify`'s, not a second one — so `x test contract` runs exactly what the gate's `contract` step runs. A selection that matches no files is `X_TEST_NO_FILES` (green with `--allow-empty`); an unknown type is `X_CLI_BAD_FLAG` naming the six and suggesting the nearest.
1497
+
1498
+ **In an app root `--affected` narrows by import closure, `As of 2026-11`** — every slice shares the `apps/web` workspace, so the workspace answer ran the whole app. The rule is `x affected`'s `data.tests`, never a second one: a test runs when its imports reach a changed file, transitively; a changed file the import graph cannot speak for (`app.config.ts`, `package.json`, a catalog, anything outside the app) runs every test; a discovered test outside the scanned globs always runs. `data.affected.tests` carries `{ app, all, reason, forcing, files: [{ path, why }] }` — `reason` is `closure: N changed file(s)` or `all: <forcing files>`, `why` is `closure`, `all` or `unmapped`. Outside an app root, `data.affected` has no `tests` and the workspace scope is unchanged.
1499
+
1500
+ `--affected` narrows the FEEDBACK, never the gate. The GATE stays un-narrowable — `x verify` with no flag is the gate, and `x verify --only <step>` announces `NOT A GATE RUN` in both renderers precisely so a narrowed run can never be read as one — because a gate that can be scoped is a gate that can be scoped wrong. Nothing affected is **green with zero spawns**, not a failure: editing a `.md` should not fail a build. A failure's `fix:` carries `--affected --base <ref>` back with it, because `--affected` decides which files exist to run at all and a rerun without it selects the whole corpus.
1501
+
1502
+ **The inner loop in this repository is `bun run test:affected`, `As of 2026-11-07`** — `x test --affected --base origin/main --dirty` from the root: the workspaces the branch and the working tree touch, plus every workspace that depends on one; a changed file no workspace owns (`package.json`, `bun.lock`, `tsconfig*.json`, `biome.json`, `scripts/**`, the test preload) runs everything, and `.md` alone runs nothing. `bun run test` is `x test` over the whole corpus — the same passes and width as the gate's test steps. Neither is the gate: CI and `x verify merge` run full `x verify`.
1503
+
1504
+ **A selection that mixes serial files with the rest is more than one run, `As of 2026-09`.** `--parallel=N` is the width of the WHOLE `bun test`, so the bare `x test --workers 8` — which selects every type — ran `live` and `e2e` eight at a time beside the unit corpus, over the very files `x verify` runs one at a time. The files are partitioned by type: one pass at the requested width for everything that may share a pool, then one pass at `--parallel=1` per serial type, each reproducible on its own (`x test live --workers 1`). `--json` carries a `passes` array when there was more than one.
1505
+
1506
+ ## x clean
1507
+
1508
+ ```bash
1509
+ x clean [--dry-run] [--json]
1510
+ ```
1511
+
1512
+ Every test leftover the framework knows of, cleared (`As of 27.3.0`): the migrated test-database templates under `.x/test-db`, the whole `.x/cache`, any `.x` folder below the app root, and — with `TEST_DATABASE_URL` set — every probe database whose run is over. `--dry-run` names them and removes nothing; `--json` carries `data.paths` and `data.databases`. Never touched: `.x/pgdata` (the dev database), `.x/storage`, the build output. The test runner sweeps the same set before every run, keeping the newest two of each cache, so `x clean` is for reclaiming the rest by hand. [Testing](Testing#database-state-and-what-a-run-leaves-behind) has the table.
1513
+
1514
+ ## x upgrade
1515
+
1516
+ ```bash
1517
+ x upgrade [--to <version>] [--dry-run] [--json]
1518
+ ```
1519
+
1520
+ One major per run, so each hop's gate attributes its red to that hop. `As of 30.0.0`.
1521
+
1522
+ **An app with no `x upgrade` yet** (below 30.0.0) runs the newest CLI: `bunx @ultimat3/cli@latest upgrade --json`. The only command that does not hand over to an older app CLI ([why](https://github.com/developerz-ai/ultimate/blob/main/packages/cli/README.md#which-x-runs)); the gate still runs from the CLI the hop installs. Needs Bun at the newest release's floor: below it, `X_BUN_VERSION`, `fix: bun upgrade`.
1523
+
1524
+ | Step | What | Refused with |
1525
+ |---|---|---|
1526
+ | 1 | clean tree, git is the only state between runs | `X_UPGRADE_DIRTY_TREE` |
1527
+ | 2 | read every workspace's `@ultimat3/*` pins, `overrides` and `resolutions` included | `X_UPGRADE_PIN_UNREADABLE`, `X_UPGRADE_MIXED_PINS` |
1528
+ | 3 | `bun pm view @ultimat3/cli versions`: the next major's newest; the last major's newest when no later major exists | `X_UPGRADE_REGISTRY_UNREACHABLE`, `X_UPGRADE_HOP_TOO_WIDE`, `X_UPGRADE_VERSION_UNPUBLISHED`, `X_BUN_VERSION` |
1529
+ | 4 | write exact pins, raise the Bun floor | — |
1530
+ | 5 | `bun install` | `X_UPGRADE_INSTALL_FAILED`, `fix:` carries the undo |
1531
+ | 6 | the installed CLI: `x manifest`, `x doctor --json`, `x verify --json` | `X_UPGRADE_GATE_UNREADABLE`, as a finding on a red hop |
1532
+
1533
+ A dry run refuses exactly what the real run would, then stops before step 4. It never commits; `git diff` is the hop. Under `--json` fd 1 is one document, fd 2 a `{"upgrade":"<stage>"}` line per stage and a heartbeat a minute.
1534
+
1535
+ `--to`: an exact release in the next major. Over a mixed set every refusal names the aligning version, the newest release at or above the highest pin: `^6` beside `^19` is told `--to 19.x.y`, it is accepted across any distance, and that report names no section.
1536
+
1537
+ | `data` field | Meaning |
1538
+ |---|---|
1539
+ | `from`, `to` | the lowest version pinned; this hop's target |
1540
+ | `next` | what a bare `x upgrade` takes after this hop; `null` when the walk is over |
1541
+ | `done` | verify's verdict, and the exit code follows it. `true` when already there, `false` on a dry run |
1542
+ | `dryRun` | nothing written, no gate run |
1543
+ | `app` | whether the root holds `app.config.ts` |
1544
+ | `proof` | what `done` rests on: `verify`, `scripts` or `none` |
1545
+ | `section` | `{ title, url, local }` in [Upgrading](Upgrading); `local` is the same section as an `x docs` topic, printed whole offline; `null` when no major is crossed |
1546
+ | `pins[]` | `{ file, name, from, to }` |
1547
+ | `bun` | `{ from, to, edited[], manual[] }`. `manual[]`: `{ path, line, text, why }`, a site only you can move, listed on every run |
1548
+ | `agentsMd` | `link`: `AGENTS.md` was byte-identical to `CLAUDE.md` and the hop makes it a relative symlink to it · `linked`: already one · `differs`: two different files, left alone; merge what only `AGENTS.md` says into `CLAUDE.md`, then `ln -sf CLAUDE.md AGENTS.md` · `none`: one of the two is missing · `null`: no hop planned |
1549
+ | `guards` | `{ reported, missing[], differs[] }` from the hop's own `x doctor`. Offered, never overwritten: `x g guard <name> --force` is yours. `reported: false` is a CLI too old to list them, which is not the same as none |
1550
+ | `manifest` | `{ ok, findings[] }`. A failure does not stop the gate |
1551
+ | `verify` | `{ ok, summary, failed[], findings[] }`, each finding `{ step?, code, cause, fix, at? }` |
1552
+ | `then` | the next thing to do, in a sentence naming the commands |
1553
+
1554
+ `null` for `guards`, `manifest` and `verify` means no gate ran: a dry run, already there, or no app.
1555
+
1556
+ **Pins and no `app.config.ts`.** Needs no app: the root is the app at or above the cwd, else the nearest manifest holding `@ultimat3/*` pins, or the workspace root claiming it. No such manifest is `X_UPGRADE_PIN_UNREADABLE`; an app below that root is `X_NOT_IN_APP`, with the `cd`. With no app, `x manifest`, `x doctor` and `x verify` cannot run, so the hop runs the `typecheck` and `test` scripts the root declares: `data.scripts[]` is `{ name, ok, code, output }`, `proof` is `scripts`, and the summary says no `x verify` ran. Neither script declared: `proof: none`, `done: false`, red. It checked nothing, so it never claims otherwise.
1557
+
1558
+ | Bun site | Edit |
1559
+ |---|---|
1560
+ | `engines.bun`, `packageManager`, `.bun-version` | raised to the release's `engines.bun`, never lowered. A spelling a text edit would falsify (`1.3.x`, `>=1.3.0 <1.4.0`, `bun@1.3.6+sha512…`, `v1.3.0`) is listed in `manual` |
1561
+ | `bun-version:` in YAML | same, quotes kept. An expression (`${{ env.BUN_VERSION }}`) or an x-range is listed in `manual` |
1562
+ | `oven/bun:` tag | keeps its suffix and precision: `1.3-alpine` → `1.4-alpine`. A `$VARIABLE` is listed in `manual`, as is a digest whose tag is below the floor or does not state the patch (`1.4-alpine@sha256:…` at a 1.4.4 floor). `1.4.4-alpine@sha256:…` is not |
1563
+
1564
+ A hop below 30.0.0 reports `X_BUDGET_UNMEASURED` until `x build --target static --json` has run, as that release's `x verify` does.
1565
+
1566
+ ## x affected
1567
+
1568
+ ```bash
1569
+ x affected [--base <ref>] [--dirty] [--paths] [--json]
1570
+ ```
1571
+
1572
+ | Flag | Type | Default | Meaning |
1573
+ |---|---|---|---|
1574
+ | `--base` | string | `main` | the ref to diff against. The diff is `<base>...HEAD` — **three dots**, so it is measured from the merge-base and an unrelated commit on `main` does not widen the answer |
1575
+ | `--dirty` | boolean | off | union the working tree in: uncommitted changes plus untracked files |
1576
+ | `--paths` | boolean | off | print bare directories, one per line, for piping. `--json` is byte-identical either way |
1577
+
1578
+ **Transitive, or it is worse than nothing.** `A → B → C` means a change in `C` must reach `A`; a single pass over an unordered list reaches only `B`, and a scoped run that misses a transitive dependent is a green checkmark on a broken repo. The graph it walks is the one every workspace's `package.json` declares — which is why `package-shape` refuses an undeclared cross-workspace import (`X_WORKSPACE_DEP_UNDECLARED`): an edge missing from a manifest is an edge missing from this answer, silently.
1579
+
1580
+ Three rules the set obeys:
1581
+
1582
+ | Change | Affected |
1583
+ |---|---|
1584
+ | any non-doc file no workspace owns — `tsconfig.json`, `tsconfig.base.json`, `biome.json`, `bun.lock`, root `package.json`, `app.config.ts`, `scripts/**`, `guards/**` | **every** workspace, and the root itself: `x test --affected` runs every test file, those under no workspace included. Decided by ownership, never by a list of names |
1585
+ | a workspace's own `package.json` | that workspace only |
1586
+ | a `.md` | nothing. A doc has no compilation unit to re-check |
1587
+
1588
+ **`--base` is the default and `--dirty` is opt-in, which is the reverse of the obvious design.** This repo runs several agents concurrently in **one checkout** (no worktrees), so a working-tree diff returns every agent's uncommitted work at once and the "affected" set quietly expands to nearly the whole monorepo — narrowing nothing while reading as though it did. A ref diff is stable under that. `--dirty` is the right answer for one developer iterating alone.
1589
+
1590
+ **Typecheck is deliberately not scoped.** `bun run typecheck` is `tsc -b`, a project build over a shared `.tsbuildinfo`, so a "scoped" typecheck is either the whole project anyway or a weaker check. The time is in the tests, which is what `x test --affected` narrows.
1591
+
1592
+ ## x graph · x impact
1593
+
1594
+ ```bash
1595
+ x graph [--from <node> --to <node>] [--json]
1596
+ x impact <file|primitive|entity> [--json]
1597
+ ```
1598
+
1599
+ The app as one graph, rebuilt per call from the loaded app (`As of 2026-11`). Nodes: `route`,
1600
+ `action`, `mutator`, `query`, `job`, `task`, `entity`, `permission`, `channel`, `tag`, `mcpTool`,
1601
+ `operation`, `file`, `test`, each id `<kind>:<name>`. An edge is `from` depends on `to`, with its
1602
+ provenance:
1603
+
1604
+ | Provenance | Source | Edges |
1605
+ |---|---|---|
1606
+ | `declared` | `x.manifest.json`'s facts, from the registries | `requires` (permission), `invalidates` / `reads-tag` / `revalidates` (cache tag), `exposes` (MCP tool), `publishes` (OpenAPI operation), `references` (entity), `enqueues` (task → job), `reads` (channel → entity) |
1607
+ | `import` | the module graph: every app file's imports, type-only included; the test preload is an import of every test | `imports`, `declared-in` (primitive → its file, matched by identity against the registries) |
1608
+ | `observed` | `.x/exercise.json`, when newer than `x.manifest.json` | `writes` / `reads` (entity), `enqueues` (job) |
1609
+
1610
+ `x impact` answers `{ routes, actions, queries, jobs, mcpTools, publishedOperations, tests, budgets,
1611
+ opaque, provenance }`, each entry with the provenances of the path that reached it. A non-empty
1612
+ `publishedOperations` means `contract-diff` will have an opinion. A file with a computed `import()`
1613
+ is `opaque`: it and its dependents are in every answer. `x graph --from a --to b` prints the
1614
+ shortest chain of edges, or none known and the provenances searched. A name, id or path that is
1615
+ no node is `X_GRAPH_NODE_UNKNOWN` with the nearest three. Queries carry no MCP fact in the
1616
+ manifest, so a query's `mcpTools` is reached only through an action that imports it.
1617
+
1618
+ **`x affected` in an app root** adds `data.tests`: the test files whose import closure meets the
1619
+ diff (`all: true` with `forcing` when a changed file is read rather than imported — `app.config.ts`,
1620
+ `package.json`, a catalog, anything outside the app).
1621
+
1622
+ ## x client
1623
+
1624
+ ```bash
1625
+ x client vendor <name> [--from <file|url>] [--only <tag,…>] [--json]
1626
+ x client customer --out <dir> [--mount <prefix>] [--only <tag,…>] [--package <name>] [--json]
1627
+ ```
1628
+
1629
+ One compiler (`$ref`, objects, arrays, enums, `const`, `oneOf`/`anyOf`, `allOf`, nullable), two emitters. `As of 2026-11`.
1630
+
1631
+ | Subcommand | Reads | Writes | The client |
1632
+ |---|---|---|---|
1633
+ | `vendor <name>` | `--from` (a file, or an https URL read through `outbound({ dial: 'public' })`); omitted, the committed `source.json` | `apps/web/clients/<name>/{source.json,schemas.ts,client.ts}` | `create<Name>Client({ baseUrl, dial, deadlineMs, headers?, deps? })`: one `outbound()` call per operation, input parsed with `t` before a byte leaves, the answer through `answer.json(schema)` |
1634
+ | `customer` | the committed `openapi.json`, or the file a bearer mount declares (`--mount /v1` needs `openapi:` on that mount) | `<dir>/{index.ts,types.ts}`, plus `package.json` with `--package` | `createClient({ baseUrl, headers?, fetch? })`: plain `fetch`, no import beyond `./types`; a non-2xx is `ProblemError`, `refusalOf(error, 'op')` narrows it to the codes the operation declares; a `Retry-After` on 429/503 is waited out once (at most 60 s) |
1635
+
1636
+ | Flag | Meaning |
1637
+ |---|---|
1638
+ | `--from` | `vendor` only. A path from the current directory or an https URL. Written to `source.json` as two-space JSON, so the next regeneration is offline |
1639
+ | `--only` | keep operations carrying one of these tags; a construct the compiler refuses outside them is never read. A regeneration with no `--only` keeps the tags the stamp records. Matching nothing is `X_CLI_BAD_FLAG` |
1640
+ | `--out` | `customer` only, required |
1641
+ | `--mount` | `customer` only: a declared bearer mount's prefix |
1642
+ | `--package` | `customer` only: a `package.json` named so; publishing is yours |
1643
+
1644
+ | Failure | Code |
1645
+ |---|---|
1646
+ | a construct it does not read (`not`, `if`/`then`/`else`, `patternProperties`, a non-JSON body, a cookie parameter, a typed `additionalProperties` beside `properties`) | `X_CLIENT_SPEC_UNSUPPORTED`, the JSON pointer in the cause |
1647
+ | not OpenAPI 3.0 or 3.1 | `X_CLIENT_SPEC_INVALID` |
1648
+ | a vendor answer that fails its declared shape | `X_OUTBOUND_BODY_INVALID` (core's), the field in `meta.issues` |
1649
+ | a documented vendor non-2xx | `X_OUTBOUND_REFUSED`, `meta.status` |
1650
+
1651
+ **The stamp is the build error.** Every generated `.ts` opens with `// @x-client {command, source, sourceHash, bodyHash}`. The gate's `drift` step reads it back under `apps/*/clients/` and the source globs: the source document's parsed content moved is `X_CLIENT_STALE` (fix: the stamp's own command), the body's bytes moved is `X_CLIENT_EDITED`. Reformatting `source.json` changes nothing; the `.ts` files carry `biome-ignore-all format`. Names come from `operationId`, so a `contract-diff` red is a customer's client breaking. `rpc` stays the client inside the app's own repo.
1652
+
1653
+ ## x shot
1654
+
1655
+ ```bash
1656
+ x shot <route> [--port 0] [--out <dir>] [--no-full] [--settle 2000]
1657
+ [--timeout 30000] [--browser <path>] [--cdp-url <ws://…>]
1658
+ [--allow-hosts a.com,b.com] [--theme light|dark] [--locale <l>]
1659
+ [--cookie name=value[,name=value]] [--expect-status 404]
1660
+ [--viewport 390x844,1440x900] [--json]
1661
+ x shot --matrix [<route>] [--locale <l>] [--theme light|dark] [--viewport …] [--cookie …] [--json]
1662
+ x shot --island <name> [--state <id>] [--json]
1663
+ x shot --all-islands [--json]
1664
+ ```
1665
+
1666
+ **The framework's stated primary developer is an agent, and an agent cannot look at anything.** It can read a file and run a command that prints. `x shot` turns a rendered route into both.
1667
+
1668
+ | Flag | Default | Meaning |
1669
+ |---|---|---|
1670
+ | `<route>` | required | a path on the app. An absolute URL is `X_CLI_BAD_FLAG` — pointing a headless browser inside your network at someone else's site is not a screenshot tool's job |
1671
+ | `--port` | `0` | the kernel picks a free one. **Never 3000**, which another project usually holds |
1672
+ | `--out` | `<root>/.x/shot/<slug>/` | `.x/` is already gitignored in every scaffold. A relative `--out` resolves against the cwd, as `x build --out` does |
1673
+ | `--full` | on (`--no-full` for the fold) | a fold-sized viewport crops nearly every route |
1674
+ | `--settle` | `2000` | matches the hydration runtime's own `requestIdleCallback` timeout |
1675
+ | `--timeout` | `30000` | one navigation |
1676
+ | `--browser` | `PUPPETEER_EXECUTABLE_PATH`, then `CHROME_PATH` | refused before anything boots if the path does not exist |
1677
+ | `--cdp-url` | `SCRAPE_CDP_URL` | **attach** to a browser somebody else is running instead of launching one here. `ws:`/`wss:`/`http:`/`https:`; anything else is refused before the attach |
1678
+ | `--allow-hosts` | the app's host only | extra hosts the page may request |
1679
+ | `--cookie` | none | `name=value[,name=value]`, `As of 2026-09-29`: set in the browser for the app's own origin before the first navigation, so a page is photographed with a cookie-held choice already made — `x shot --matrix --cookie consent=granted` shoots every cell with the consent banner answered. Applies to `--matrix` cells and `--island` states too. A name that is not an RFC 6265 token, or a value holding a space, `;`, `,`, a quote or a backslash, is `X_CLI_BAD_FLAG` before anything boots |
1680
+ | `--expect-status` | absent — any 2xx | the document status this shot is ok with, `As of 2026-09-26`: `x shot /nope --expect-status 404` photographs the not-found page on purpose. Absent, a non-2xx document fails the verdict and the summary names the status. With `--matrix` it applies to every cell |
1681
+ | `--locale` | the app's `defaultLocale`, **always pinned** | one of the app's `locales`, `As of 2026-09-25`. Sent as `Accept-Language` on every request the page makes, and for a non-default locale the path is prefixed (`x shot /precios --locale en` opens `/en/precios`). Absent, `Accept-Language` is still pinned to the default locale, so a picture never depends on the language of the machine's Chrome. An undeclared locale is `X_CLI_BAD_FLAG` before anything boots; refused beside `--island` |
1682
+ | `--matrix` | off | every static `site/` route × every locale × `light`/`dark` × `390x844` and `1440x900` — or the `--viewport` list instead (laid out at exactly that width: scrollbars are hidden, `As of 2026-09-29` — a classic scrollbar made every capture 15 px narrow), into `.x/shot/matrix/<route>/<locale>/<theme>-<width>x<height>/` (`<theme>-<width>/` before 2026-10: two sizes sharing a width wrote one directory), plus `.x/shot/matrix/index.html`, a contact sheet of every picture with failures marked. One dev server for the whole run. A `<route>` narrows it to that route, `--locale` and `--theme` to one value of their axis; `ok` only when every cell's verdict is. Refused beside `--island`/`--all-islands` |
1683
+ | `--viewport` | absent — the driver's default size | `<w>x<h>[,<w>x<h>…]` in CSS pixels, `As of 2026-10` (#444): the route photographed **once per size** over one dev server, each into `<out>/<w>x<h>/` with its own `shot.png` and `verdict.json`; `ok` only when every size's verdict is, and the summary is the first failing size's. `x shot /pricing --viewport 390x844,820x1180,1440x900` replaces a hand-written browser script. With `--matrix` the list replaces the default pair. One flag holding a list, never repeated — the parser keeps a repeated flag's last value. A side outside `1..16384`, anything but digits around an `x` (`0x10`, `1e3`, `390.5`), an empty entry or the same size twice is `X_CLI_BAD_FLAG` before anything boots; refused beside `--island`/`--all-islands`, whose states declare their own `viewport` |
1684
+ | `--theme` | absent — the box's own preference and the app's own `theme.defaultMode` | `light` or `dark`, `As of 2026-09-19`. Both `prefers-color-scheme` is emulated **and** the scheme is stored as the visitor's choice under `THEME_STORAGE_KEY` before navigation — the boot script since 20.2.0 answers `defaultMode` before the OS, so emulation alone photographs a dark-default app dark whatever was asked (#489). The `ui.shot`/`ui.inspect`/`ui.interact` tools' `theme` is this flag. Refused beside `--island`, which photographs both themes |
1685
+
1686
+ **`verdict.json` is the half that gates**, and the more important of the two files: a picture cannot tell you the island threw or logged. It carries the console lines, the island counts, the canvas size, the network tallies — and `blind`, which names what this capture could **not** observe. A tool that silently omits what it cannot see is worse than one that says so.
1687
+
1688
+ `ok` is decided by `buildVerdict` in `packages/cli/src/shot-verdict.ts` — read it there rather than trusting a count here: **nothing logged an error**, **nothing threw**, **no island failed to mount**, **the document photographed is the route asked for**, and **the document answered 2xx** (or exactly `--expect-status`; `status` and `expectedStatus` ride `verdict.json`, `null` when no response was seen — unseen never gates). A failed subresource — a missing favicon — is counted under `network.failed` and never gates. The island condition is new in 8.0.0: before it, every island's `mount()` could reject and the run still reported `ok: true`, "clean". The last matters most: a route behind `auth: 'required'` photographs the sign-in page and reports every island missing, which reads as a bug in the app when it is a bug in the capture.
1689
+
1690
+ **It drives `x dev`, never the static build.** `x build --target static` prerenders `site/` only, so an `app/` route would photograph the landing page. If an `x dev` is already running on the checkout it is **reused** rather than booted over — embedded Postgres is single-writer, so a second boot is `X_DEV_ALREADY_RUNNING` and no picture is ever taken. The verdict says which happened.
1691
+
1692
+ **`x shot` launches Chrome itself**, over raw CDP — no browser library in the app. It takes `--browser`, else `CHROME_PATH`, else the first of the probed paths (`/usr/bin/google-chrome` and its siblings), or attaches with `--cdp-url`. With none of them it answers `X_SHOT_BROWSER_MISSING`, whose fix is `export CHROME_PATH=/usr/bin/google-chrome`.
1693
+
1694
+ **`--cdp-url` is how a run gets a browser this box could not have started**, `As of 2026-08-24`. Every stealth provider — Browser Use Cloud, Scrapfly, Browserless, Remote Browser and the CAPTCHA-solving services — sells the same shape: create a session over their API, get a `wss://` CDP endpoint back, connect to a real un-fingerprintable Chromium behind it. `@ultimat3/scraping`'s `remoteBrowser({ cdpUrl })` has called that its **primary production path** since it shipped, and until now no CLI command could reach it: `x shot` only ever called `localBrowser()`, so a CI runner or a container with no Chrome could take no picture at all.
1695
+
1696
+ ```bash
1697
+ # A provider session — the API call is theirs, the endpoint is what x shot needs
1698
+ CDP=$(curl -s -X POST https://api.example.com/v3/browsers -H "Authorization: Bearer $KEY" | jq -r .cdpUrl)
1699
+ x shot /pricing --cdp-url "$CDP" --json
1700
+
1701
+ # A sidecar in the same compose network
1702
+ x shot /pricing --cdp-url http://chrome:9222 --json
1703
+ ```
1704
+
1705
+ Three rules decide which browser a run gets, and the third is the one worth knowing: **both flags together is refused** rather than ranked (one names a Chrome to *start*, the other says the browser is somebody else's); an exported `SCRAPE_CDP_URL` is a shell-wide default, so `--browser` beside it **wins** and launches locally; and `--browser` is not read at all on an attach, so a correct remote run is never refused for a binary it will never execute. The variable is `SCRAPE_CDP_URL` and not a name of the CLI's own because `@ultimat3/scraping`'s own refusal already tells its reader `remoteBrowser({ cdpUrl: env.SCRAPE_CDP_URL })`.
1706
+
1707
+ **Closing an attached browser ends the remote session too** — `remoteBrowser()` calls `close()` and not `disconnect()`, deliberately: a disconnect leaves a browser somebody is billing for running until its provider times it out, and nobody attributes that bill to the run that caused it.
1708
+
1709
+ **Never a step of `x verify`.** A gate that needs a browser goes red for reasons unrelated to the change, and CI does not install one.
1710
+
1711
+ ### x shot --island
1712
+
1713
+ ```bash
1714
+ x shot --island <name> [--state <id>] [--settle 2000] [--json]
1715
+ x shot --all-islands [--settle 2000] [--json]
1716
+ ```
1717
+
1718
+ One **component**, in the states it declares, photographed one address at a time. A route shot answers "what does this page look like"; this answers "what does this component look like in the state a reviewer cannot click their way to" — an empty result set, a save the server refused, a quota banner.
1719
+
1720
+ The states come from a sibling `<name>.island.states.ts` beside the island, and that file is **pure data**: no JSX, no `solid-js`, one erased `import type`. It has to be, because the complete expected picture list is computed from it **before a browser exists**, which is what lets the run diff what it owed against what landed and refuse `X_SHOT_ISLAND_MISSING` rather than reporting a clean run with no pictures in it. An import of the component itself is `X_TEST_ISLAND_STATES_NOT_PURE`.
1721
+
1722
+ | Flag | Default | Meaning |
1723
+ |---|---|---|
1724
+ | `--island <name>` | required | the manifest's own `name`. A route positional beside it is refused — they are two different subjects, not two spellings of one |
1725
+ | `--state <id>` | every declared state | one state. An id the manifest does not declare is refused by name, with the known ids |
1726
+ | `--settle` | `2000` | the readiness window. Ready is **quiet**, not idle: fonts, then N consecutive frames with an unchanged network-activity counter — never "nothing in flight", which never comes for a fixture declared `pending` |
1727
+ | `--all-islands` | off | **every** island in the app, in every state each declares. Its own spelling and never `--island` with no value: the parser refuses a bare `--island` ("expects a value"), so "every island" had no form a reader could type that could not be read as a mistyped name. Naming it beside `--island`, beside `--state` or beside a route positional is refused by name |
1728
+
1729
+ Everything else — `--out`, `--timeout`, `--browser`, `--cdp-url`, `--allow-hosts` — is the route shot's, unchanged. Pictures land at `.x/shot/island/<name>/<state>-<theme>.png`, one per state per theme, beside a `verdict.json` of the same shape and an `index.md` one level up.
1730
+
1731
+ **`x shot --all-islands` is the one command that answers "what does this app look like".** It expands every `*.island.states.ts` in the app through the same plan the one-island form uses, boots one dev server, and keeps **one browser per declared viewport across every island** — forty states at one size is still one launch. A state that cannot be photographed does not stop the run, and not the next *island* either: every picture the app can produce is taken, every verdict and the index are written, and only then does the missing-picture gate turn the refusals into a non-zero exit. An app that declares no states at all is refused by name rather than answered with an empty gallery — "produced nothing and exited 0" is the one outcome a reader cannot tell from success.
1732
+
1733
+ ### .x/shot/island/index.md
1734
+
1735
+ The file an agent opens instead of guessing at forty PNGs. Markdown, written by **both** forms — a single-island run gets one too, because the file that says what a picture *is* cannot be a property of how many islands were asked for.
1736
+
1737
+ | It carries | Why |
1738
+ |---|---|
1739
+ | the counts, and the commands that re-run everything / one island / one state | an artifact that cannot be regenerated is one nobody trusts twice |
1740
+ | what the capture could **not** see | the verdict's own `blind` list, handed in rather than reworded — two wordings would drift |
1741
+ | per island: the app-relative path of the `.island.tsx` | so a reader can open the component the pictures are of |
1742
+ | per state: the `id`, the `title` and the `note` | the note is the *"you cannot reach this by clicking, because …"* line, and it is the whole reason a reviewer knows what they are looking at |
1743
+ | per state: one path per theme, and the state's own verdict | `ok`, or every reason it is not — a missing picture, a mount that never happened, a request no stub answers, a console error |
1744
+ | per state: console **warnings** and whether the content overflows its box | **recorded, and gating nothing.** A signal that fails a run is a signal an author switches off, and a box its content spills out of is the commonest ugly-UI symptom a pixel-tight PNG cannot show. `stateShotOk` reads neither |
1745
+
1746
+ **One session per picture**, and it costs a browser launch each: `page.console()` and `page.pageErrors()` are bounded rings over the whole *session*, so a shared one files state A's console errors under state B — and per-state attribution is the half of the artifact that gates.
1747
+
1748
+ **An unstubbed HTTP request fails the run.** The page's seal replaces `fetch` and `XMLHttpRequest` before the island's chunk is imported, answers the state's `routes`, and publishes everything else; the capture refuses with `X_SHOT_ISLAND_UNSTUBBED_REQUEST` naming each method and path. A component whose fetch quietly hangs paints its own loading branch, and the picture then shows a fixture gap dressed up as a real component state.
1749
+
1750
+ **A socket needs no stub, `As of 2026-09-17`.** `WebSocket` and `EventSource` are replaced with an INERT stand-in rather than a refusal: no real dial, no network, no message and no error ever delivered; `close()`/`send()` are no-ops. A snapshot of an island does not need a *live* socket — every state's fixture already rides `props`/`routes` — so a component whose `mount()` unconditionally dials `@ultimat3/realtime`'s `LiveClient.connect()` used to fail **every** state of **every** such island with `X_SHOT_ISLAND_UNSTUBBED_REQUEST`, and the error's own `fix:` told the author to declare a stub whose `match` the harness's `"<METHOD> <pathname>"` grammar can never accept — `WS ws://…` matches no `isStubMatch`, so the printed remedy could never be run. `readyState` DOES leave `CONNECTING` once, on a zero-delay timer that fires `onopen`/an `'open'` listener and nothing else — a component whose `mount()` awaits the socket's own open event before rendering anything (not true of this app's `LiveClient`, whose `connect()` registers callbacks and returns, but not a fact this harness may assume of every app) would otherwise hang the mount forever, trading one impossible `--settle` deadline for the unsatisfiable stub fix this defect was about. Each socket a component constructs is recorded on its own `sockets` list instead — in the readiness probe, on `IslandStateShot.sockets`, and in `--json` — never on `unstubbed`, and `stateShotOk` reads neither: a component legitimately holding an open, silent channel is not the same fact as a request nobody stubbed, and this list gates nothing. Not a candidate for the blind-spot list below, either — that list is for a fact a *picture* cannot show, and both `sockets` fields ride `--json` plainly. A real unstubbed `fetch`/XHR is unaffected and still fails the run exactly as above.
1751
+
1752
+ **Open popups are in the frame** (`As of 2026-09-29`). The crop is the union of the target's box and every visible `[role=listbox]`, `[role=menu]`, `[role=dialog]`, `[role=alertdialog]`, `[role=tooltip]`, `dialog[open]` and `:popover-open` in the harness — portalled or not — so a combobox state declared `open` photographs its listbox instead of a closed input. `verdict.json`'s `box` stays the target's own box.
1753
+
1754
+ **The picture is the crop target plus a small margin.** The rectangle is the state's declared selector, or the island's host element, translated out of viewport coordinates into the page's — `As of 2026-08-26`; measured before that, 720×560 for a component whose own box the same verdict reported as 688×104. The margin came later, because a pixel-tight crop shaves off everything a component paints *outside* its border box — a `box-shadow`, an outline, a focus ring, a hairline border on a subpixel — and a reviewer then reads a component with no elevation as flat, which is a change the component never made. It is clamped to the document at both ends, so the frame never starts in negative space and never runs past the page, and it may only ever make the frame **bigger** than the component's own box.
1755
+
1756
+ **Both themes are photographed by emulating `prefers-color-scheme`**, not by setting an attribute — `As of 2026-08-26`. An attribute on the document is the *outcome* of a theme decision and the component owns it: one that resolves `'system'` itself deletes or overwrites it on mount, so the harness is silently overruled and both pictures converge. Measured on the reference app, `<state>-light.png` and `<state>-dark.png` came back byte-identical, same md5, from two addresses that really did serve different documents. The attribute is still set, because it is right for a component that *reads* a theme it does not own.
1757
+
1758
+ ## x pr · x ci
1759
+
1760
+ ```bash
1761
+ x pr review [--pr <n>] [--repo owner/name] [--all] [--full] [--json]
1762
+ x pr resolve <thread-id> [--json]
1763
+ x pr reply <thread-id> --body "…" [--json]
1764
+ x ci [runs] [--branch <name>] [--run <id>] [--repo owner/name] [--tail <n>] [--full] [--workflow <name>] [--event <push|pull_request|workflow_dispatch>] [--json]
1765
+ x ci tested [--max-age 24h] [--repo owner/name] [--workflow ci.yml] [--json]
1766
+ ```
1767
+
1768
+ **`gh pr view --comments` does not show inline review threads.** It shows *issue* comments; the actionable findings are anchored to lines and reachable only through GraphQL, and the thread id needed to resolve one exists nowhere else. That is the whole reason `x pr` exists — otherwise every team on Ultimate rediscovers the same query.
1769
+
1770
+ Two hazards it encodes, both of which have bitten in this repo:
1771
+
1772
+ | Hazard | What `x pr review` does |
1773
+ |---|---|
1774
+ | **A review decision goes stale.** It survives later pushes, so `CHANGES_REQUESTED` can predate the commits that addressed it | compares the deciding review's **commit oid** against `headRefOid` and says `stale` outright. Not timestamps — two reviews seconds apart can straddle a push, and the timestamp then calls one of them current |
1775
+ | **An outdated thread has `line: null`** | selects `originalLine` too, so a thread whose diff moved still has a locator instead of arriving anchorless |
1776
+
1777
+ It reads `latestOpinionatedReviews`, not `reviews(last: n)`: the most recent reviews are often all `COMMENTED` at the current head, so dating the decision from "the newest review" reports a stale `CHANGES_REQUESTED` as current — the hazard, inverted.
1778
+
1779
+ **Resolving closes a conversation, not a finding.** Whether the code is fixed is a fact no GitHub mutation observes, and `x pr resolve` says so rather than implying otherwise.
1780
+
1781
+ `x ci` exists because *"`gh run view` prints a tree of ticks and one cross, and the error is inside a per-job log that is mostly setup noise."* For a `verify` job the log tail **is** the findings block — this repo's gate already prints `X_*` codes and executable `fix:` lines — so `x ci` reads them back out into `findings[]` in the standard shape. `ok: false` iff a run failed; `ok: true` means *no failed run* — an in-flight or queued run still completes with `ok: true` and is reported through the `running > 0` line and a per-run `headSha`, not as a green verdict. `x pr review` always exits 0, because unresolved threads are the work to do, not a failure of the command that listed them. The two flags narrow the `gh run list` server-side — `--workflow` takes the workflow name or filename (`ci.yml`), `--event` takes `push|pull_request|workflow_dispatch` — so `x ci --workflow ci.yml --event push --branch main` answers *"is main's push CI green"* instead of folding every main-branch workflow into one verdict.
1782
+
1783
+ **`x ci tested` answers whether a push may reuse a green pull-request run** — `{ tested, condition, reason, pr?, run? }`, for `GITHUB_SHA` or `HEAD`. `tested: true` needs all of: the subject ends `(#N)`; PR N merged as this very commit; its head is in this repository (a fork's run is never reused); its head tree is this tree; the newest same-repository run of `--workflow` (default `ci.yml`) on that head concluded `success`, under `--max-age` old (default `24h`: the runner image and the registry move). Any doubt — a condition unmet, `gh` absent, an API that did not answer — is `tested: false` and a full run. Exit 0 on every answer, so a workflow reads `data.tested`; only an unreadable `--max-age` refuses (`X_CLI_BAD_FLAG`). The workflow shape that consumes it: [CI-Parallel-Gate](CI-Parallel-Gate.md#a-push-a-green-pr-run-already-tested). As of 2026-11.
1784
+
1785
+ **Neither is a step of `x verify`.** A gate that needs network and a GitHub token goes red for reasons unrelated to the change.
1786
+
1787
+ ## x errors
1788
+
1789
+ ```bash
1790
+ x errors [explain <CODE> [--recent]|list] [--json]
1791
+ ```
1792
+
1793
+ `--recent` (in an app) adds what THIS app has seen of the code: its groups in `x_error_groups`
1794
+ (count, first and last seen) and the bundles `x_failures` holds, each with its `x repro` line.
1795
+
1796
+ The [Error codes](Error-Codes) table, programmatically. Runs outside an app: triaging a code must not need an app root.
1797
+
1798
+ ```bash
1799
+ $ x errors explain X_CURSOR_INVALID --json
1800
+ {"ok":true,"command":"errors","summary":"X_CURSOR_INVALID — pagination cursor is malformed…",
1801
+ "data":{"code":"X_CURSOR_INVALID","cause":"pagination cursor is malformed, tampered with or from
1802
+ another query","fix":"drop the cursor and request the first page again (after: null)",
1803
+ "docs":"https://github.com/developerz-ai/ultimate/wiki/Error-Codes",
1804
+ "site":{"at":"@ultimat3/core/src/cursor.ts","line":26}}}
1805
+ ```
1806
+
1807
+ `site` is the CLI locating a throw site in the tree — it is **not** a field the thrown error
1808
+ carries. A code raised at more than one site gets one of them, named in the `fix` line; run the
1809
+ command rather than quoting the line. `docs` is `ERROR_DOCS_URL`, the same URL for every code.
1810
+
1811
+ `list` enumerates every registered code, and names under `data.unavailable` any package this process could not import — a list silently missing codes is worse than one that says which. An unregistered code is `X_ERROR_CODE_UNKNOWN` with the nearest real code as its fix; the command never invents an explanation.
1812
+
1813
+ ## x repro
1814
+
1815
+ ```bash
1816
+ x repro <jobId|requestId|traceId|fingerprint> [--write] [--verify] [--json]
1817
+ x repro --list [--code X_…] [--json]
1818
+ x repro --from <file> [--write] [--verify] [--json]
1819
+ ```
1820
+
1821
+ A production failure becomes a failing test. `As of 2026-11`.
1822
+
1823
+ | Subject | Read from | Written as |
1824
+ |---|---|---|
1825
+ | a dead job (`x_jobs` id) | its row and its steps — capture off or on | `repro/repro-<id>.job.test.ts`: `reproJob(runJobs, …)` replays the completed steps from their stored output and runs once more |
1826
+ | a captured 5xx (request id, trace id, bundle id, or a fingerprint's newest) | `x_failures`, written when `failureCapture.enabled` (`x dev`: always) | `repro/repro-<id>.contract.test.ts`: `reproduce(bundle, { driver })` seeds the rows the call read, freezes the clock at the failure, invokes as the actor |
1827
+
1828
+ The written test is RED until the fault is fixed. `--verify` runs it: `data.reproduced` is true
1829
+ when it failed with the captured code; otherwise the answer is `X_REPRO_NOT_REPRODUCED`, naming
1830
+ the masked fields and the captured build — a fault in the database or a different build replays
1831
+ green. Masked input (`.sensitive()`, secret-named keys) and masked columns are stood in for from
1832
+ the schema. `--list` reads the bundles with each group's count and last sighting. An id nothing
1833
+ holds is `X_REPRO_UNKNOWN`; a request id with capture off is `X_REPRO_CAPTURE_OFF`.
1834
+
1835
+ ## x docs
1836
+
1837
+ ```bash
1838
+ x docs "<question|topic|symbol>" [--limit <n>] [--json]
1839
+ ```
1840
+
1841
+ **The first lookup**, before stating or working around a framework limit. Offline, no app needed. `As of 30.0.0`.
1842
+
1843
+ | Reads | Topic |
1844
+ |---|---|
1845
+ | every installed `@ultimat3/*` package's `README.md`, `CLAUDE.md` and source headers — the app's direct dependencies and the CLI's own | `<pkg>.<module>`, `<pkg>.README#<anchor>` |
1846
+ | this wiki, shipped in the `@ultimat3/cli` tarball (`wiki/`) — every page but Error codes | `wiki.<Page>`, `wiki.<Page>#<anchor>`, the anchor GitHub renders |
1847
+ | an `X_*` code | redirected to `x errors explain <CODE>`, the one way to a code |
1848
+
1849
+ A question is ranked; a `Page#anchor` topic (`x docs Upgrading#28x--2900-entry-by-entry`) prints that **whole section**, subsections included, and nothing past it. `x upgrade --json`'s `section.local` is that topic. A search hit carries a capped excerpt.
1850
+
1851
+ The ranking — which hits, in which order — is **not a stable surface**: a minor may reorder it. The `--json` shape is. An install with no `wiki/` still answers from the packages, with an `X_CLI_UNEXPECTED` finding.
1852
+
1853
+ ## x fix
1854
+
1855
+ ```bash
1856
+ x fix boundary <file> [--json]
1857
+ ```
1858
+
1859
+ The minimal cut for an import that crossed a surface boundary — the command every `X_SURFACE_BOUNDARY` finding names in its `fix:` line. It prints a plan and **writes nothing**; there is no `--write`.
1860
+
1861
+ For each violation involving the file it reports the offending edge, the full chain that makes it one, and the edit to make. For the `shared/` fattening case it generates the split: when exactly one surface reaches the module **and the module lands on the same surface as the file it imports**, the plan carries the `git mv` plus every import specifier that move invalidates — the move alone is not a repair, it just relocates the break. When two surfaces reach it, or when relocating would leave the forbidden edge exactly where it was, the module has to be cut by hand and the plan says so rather than guessing.
1862
+
1863
+ `<file>` is app-root-relative, or any suffix that matches exactly one source file — the short form a `fix:` line emits. Errors: `X_FIX_TARGET_UNKNOWN` (with the nearest real path as its fix), `X_CLI_BAD_FLAG` on an ambiguous suffix.
1864
+
1865
+ ## Planned commands
1866
+
1867
+ Specified in the design docs, not yet implemented. Every one is in the command registry: calling it exits `X_NOT_IMPLEMENTED` with a `fix:` naming the closest shipped command, because "not built yet" and "not a command" are different facts and only one of them is true.
1868
+
1869
+ The table is `PLANNED_COMMANDS` in `packages/cli/src/cmd-planned.ts`; `cmd-planned.test.ts` asserts every row is reachable through the parser and that no `fix` points at another planned command. `PLANNED_SUBCOMMANDS` in the same file is the one-level-down version — a subcommand of a shipped command that this build does not implement: `x db studio` and `x jobs drain`. Each stays in its command's subcommand list, so the parser reaches it and `x help <command>` lists it.
1870
+
1871
+ | Command | Purpose | `fix:` today |
1872
+ |---|---|---|
1873
+ | `x cache [graph\|bust <tag>\|clear\|stats]` | what a write evicts; targeted eviction | `x dev` — then the cache panel at `/_x` |
1874
+ | `x branch [<name>\|delete <name>]` | copy-on-write database + preview URL + scoped MCP socket | `x db branch list --json` — the database half: `list`, `create <name>`, `delete <name>` |
1875
+ | `x status` | role health and the build-ID distribution of connected clients | `x doctor --json` |
1876
+ | `x logs tail` | structured logs and spans, filterable | `x dev` — then the timeline panel at `/_x` |
1877
+ | `x token [create --scopes <s>\|grant <scope>]` | MCP tokens and scopes | `x mcp serve --help` |
1878
+ | `x ai [eval <name>\|cache\|reindex]` | eval scores, cache hit rate and tokens saved, vector reindex | `x test eval --json` |
1879
+ | `x money add-currency <ISO> --exponent <n>` | extend the currency table | `x errors explain X_CURRENCY_UNKNOWN --json` — extend it in code: `registerCurrency({ code, exponent, name })` from `@ultimat3/money`, once at boot. Shipped `As of 2026-08`, and it is what this command promised |
1880
+ | `x config show` | the resolved configuration, defaults included | `x manifest --json` |
1881
+
1882
+ **Flags reach the same answer**, `As of 2026-10`: `x money add-currency USD --exponent 2` and `x logs tail --follow --json` exit `X_NOT_IMPLEMENTED` with the row's `fix:`, as the bare form does.
1883
+
1884
+ ## Names that moved
1885
+
1886
+ | Older name in the design docs | Use instead |
1887
+ |---|---|
1888
+ | `x db apply` | `x db migrate` |
1889
+ | `x gen <kind>` | `x g <kind>` (or `x generate`) |
1890
+ | `x deploy compose` / `x deploy static` | `x deploy --method compose` / `x build --target static` |
1891
+ | `x mcp` (bare) | `x mcp serve` |
1892
+ | `x routes list` | `x routes` |
1893
+
1894
+ Related: [Getting started](Getting-Started) · [Configuration](Configuration) · [Testing](Testing) · [Deployment](Deployment) · [Troubleshooting](Troubleshooting).