@ultimat3/cli 29.3.2 → 30.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +13 -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 +28 -8
  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 +42 -138
  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 +17 -27
  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 +1313 -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,115 @@
1
+ // Single responsibility: the image variants a static export's documents promise, as FILES. While
2
+ // the export renders, `responsiveImage` asks this module for each variant's URL — `/_img/<source
3
+ // hash>-<width>.<ext>` — and after it, every URL an exported document names is decoded, resized
4
+ // and written through core's pipeline. A dumb file server then answers `image/webp` for a webp.
5
+
6
+ // why: Bun exposes no path-join primitive, and every variant path is relative to the export root.
7
+ import { join } from 'node:path';
8
+ import { canEncode, imageUnsupported, sniffImageFormat, transformImageBytes } from '@ultimat3/core';
9
+ import { contentHash } from '@ultimat3/render/server';
10
+ import type { StaticImageRequest } from '@ultimat3/seo';
11
+ import { ImageSourceUnreadableError } from './static-capture-errors';
12
+
13
+ /** Where every variant lands, and the prefix a document names it by. */
14
+ export const STATIC_IMAGE_DIR = '_img';
15
+
16
+ /** The source encodings an export can read and resize — core's decoders, minus SVG markup. */
17
+ const SOURCE_GLOB = new Bun.Glob('**/*.{png,jpg,jpeg,webp,gif}');
18
+
19
+ interface Source {
20
+ readonly file: string;
21
+ readonly hash: string;
22
+ /** What the variant is written as when the document named no format. */
23
+ readonly ownFormat: 'png' | 'jpeg' | 'webp';
24
+ }
25
+
26
+ interface Variant {
27
+ readonly src: string;
28
+ readonly width: number;
29
+ /** `undefined` for a source the export does not hold: the URL is recorded, never written. */
30
+ readonly source: Source | undefined;
31
+ readonly format: string;
32
+ }
33
+
34
+ export interface StaticImages {
35
+ /** The URL builder `@ultimat3/seo`'s static seam calls. Never throws: refusals wait for `write`. */
36
+ imageUrl(request: StaticImageRequest): string;
37
+ /**
38
+ * Writes every recorded variant an exported document names, and answers their site paths. A
39
+ * named variant of a `src` the export does not hold is `X_IMAGE_SOURCE_UNREADABLE`; one in a
40
+ * format the pipeline cannot encode (`avif`) is core's `X_IMAGE_UNSUPPORTED` — a build error,
41
+ * never a dead `<source>`. URLs only weighed documents named are never written.
42
+ */
43
+ write(documents: readonly string[]): Promise<readonly string[]>;
44
+ }
45
+
46
+ const EXTENSION: ReadonlyMap<string, string> = new Map([
47
+ ['png', 'png'],
48
+ ['jpeg', 'jpg'],
49
+ ['webp', 'webp'],
50
+ ['avif', 'avif'],
51
+ ]);
52
+
53
+ /** The site path a `src` names, without a query or fragment — the file it is in the export. */
54
+ const pathOf = (src: string): string => src.replace(/[?#].*$/, '');
55
+
56
+ /** Index the images the export already holds (`asset()` copies, icons), by their site path. */
57
+ async function indexSources(out: string): Promise<ReadonlyMap<string, Source>> {
58
+ const sources = new Map<string, Source>();
59
+ for await (const relative of SOURCE_GLOB.scan({ cwd: out, onlyFiles: true })) {
60
+ const site = `/${relative.split('\\').join('/')}`;
61
+ if (site.startsWith(`/${STATIC_IMAGE_DIR}/`)) continue;
62
+ const file = join(out, relative);
63
+ const bytes = await Bun.file(file).bytes();
64
+ const format = sniffImageFormat(bytes);
65
+ if (format === null) continue;
66
+ sources.set(site, {
67
+ file,
68
+ hash: contentHash(bytes),
69
+ ownFormat: format === 'jpeg' || format === 'webp' ? format : 'png',
70
+ });
71
+ }
72
+ return sources;
73
+ }
74
+
75
+ export async function staticImages(out: string): Promise<StaticImages> {
76
+ const sources = await indexSources(out);
77
+ const variants = new Map<string, Variant>();
78
+ return {
79
+ imageUrl({ src, width, format }) {
80
+ const source = sources.get(pathOf(src));
81
+ const resolved = format ?? source?.ownFormat ?? 'png';
82
+ // A src the export lacks still gets a stable URL, so `write` can tell which document named it.
83
+ const stem = source === undefined ? `missing-${contentHash(src)}` : source.hash;
84
+ const url = `/${STATIC_IMAGE_DIR}/${stem}-${String(width)}.${EXTENSION.get(resolved) ?? resolved}`;
85
+ variants.set(url, { src, width, source, format: resolved });
86
+ return url;
87
+ },
88
+ async write(documents) {
89
+ const named = [...variants].filter(([url]) => documents.some((html) => html.includes(url)));
90
+ const missing = [
91
+ ...new Set(named.filter(([, one]) => one.source === undefined).map(([, one]) => one.src)),
92
+ ];
93
+ if (missing.length > 0) throw new ImageSourceUnreadableError(missing);
94
+ const refused = named.find(([, one]) => !canEncode(one.format));
95
+ if (refused !== undefined) {
96
+ throw imageUnsupported(
97
+ `a static page asked responsiveImage() for ${refused[1].format}, which the build pipeline cannot encode, so its <source> would name a file no build writes`,
98
+ "responsiveImage(input, { formats: ['webp'] }) # drop avif from a static route; a CDN driver serves it at runtime",
99
+ { format: refused[1].format, src: refused[1].src },
100
+ );
101
+ }
102
+ const written: string[] = [];
103
+ for (const [url, one] of named) {
104
+ if (one.source === undefined || !canEncode(one.format)) continue;
105
+ const bytes = await transformImageBytes(await Bun.file(one.source.file).bytes(), {
106
+ width: one.width,
107
+ format: one.format,
108
+ });
109
+ await Bun.write(join(out, url.slice(1)), bytes);
110
+ written.push(url);
111
+ }
112
+ return written;
113
+ },
114
+ };
115
+ }
@@ -0,0 +1,55 @@
1
+ // What is ambient inside a `<Hole>` of a `stream` page: the request's context, with the hole's own
2
+ // signal (its deadline, the client's disconnect, the document's close) in place of the request's,
3
+ // and the response headers sealed — they left with the first flush, so a write there was a cookie
4
+ // or a `Location` nobody would ever receive.
5
+
6
+ import { runWithContext, tryUseContext, UltimateError } from '@ultimat3/core';
7
+ import type { HoleEnter } from '@ultimat3/render/server';
8
+
9
+ const FIX =
10
+ 'load: async () => { setCookie(name, value); return data; }, // in defineRoute, never inside a Hole';
11
+
12
+ /** A response header or redirect written under a hole, after the status and headers were sent. */
13
+ export class StreamAlreadyFlushedError extends UltimateError {
14
+ constructor(what: string) {
15
+ super({
16
+ code: 'X_STREAM_ALREADY_FLUSHED',
17
+ cause: `${what} ran inside a <Hole> on a render: 'stream' page, after the status and headers were sent with the first flush, so the browser would never receive it`,
18
+ fix: FIX,
19
+ });
20
+ }
21
+ }
22
+
23
+ /** Every write to the response headers refuses; every read answers what was sent. */
24
+ const sealedHeaders = (headers: Headers): Headers =>
25
+ new Proxy(headers, {
26
+ get(target, key) {
27
+ if (key === 'append' || key === 'set' || key === 'delete') {
28
+ return (name: string) => {
29
+ throw new StreamAlreadyFlushedError(`writing the ${name} response header (setCookie)`);
30
+ };
31
+ }
32
+ const value: unknown = Reflect.get(target, key, target);
33
+ return typeof value === 'function' ? value.bind(target) : value;
34
+ },
35
+ });
36
+
37
+ /**
38
+ * The scope a hole's children run in. Forwarded rather than rebuilt, as `runToCompletion` forwards a
39
+ * request's context: a rebuilt `ctxOf` drops the http fields, and `useRequestHeader()` inside a hole
40
+ * would then refuse. Services the request built keep the request's signal; the ambient one is the hole's.
41
+ */
42
+ export const enterHole: HoleEnter = (signal, run) => {
43
+ const parent = tryUseContext();
44
+ if (parent === undefined) return run();
45
+ const sealed = sealedHeaders((parent as { readonly headers?: Headers }).headers ?? new Headers());
46
+ const scope = new Proxy(parent, {
47
+ get: (target, key) =>
48
+ key === 'signal' ? signal : key === 'headers' ? sealed : Reflect.get(target, key),
49
+ set: (target, key, value) => {
50
+ if (key === 'redirect') throw new StreamAlreadyFlushedError('setRedirect()');
51
+ return Reflect.set(target, key, value);
52
+ },
53
+ });
54
+ return runWithContext(scope, run);
55
+ };
@@ -3,7 +3,7 @@
3
3
  // asset over (`island-bundle.ts`), because the two answer the same question — "what does this
4
4
  // document make the browser fetch, and can it keep it?" — and one mechanism is the whole point.
5
5
  //
6
- // Why it stopped being an inline `<style>`: measured against ai-maxxing on 2026-09-06, every
6
+ // Why it stopped being an inline `<style>`: measured against one adopter's app on 2026-09-06, every
7
7
  // `app/` document carried one 156,738-byte block — 1,336 rules from 406 module sources, byte
8
8
  // identical across `/`, `/fleet`, `/fleet/[host]` and the session page — inside a response the
9
9
  // pipeline sends `Cache-Control: private, no-store`. 92% of the dashboard document and 89% of the
@@ -4,7 +4,7 @@
4
4
  // image.
5
5
 
6
6
  import type { Route, UltimateRequest } from '@ultimat3/http';
7
- import { applyCacheHeaders, jsonResponse, NO_STORE } from '@ultimat3/http';
7
+ import { applyCacheHeaders, jsonResponse, NO_STORE, textResponse } from '@ultimat3/http';
8
8
  import type { StyleBundle } from './style-bundle';
9
9
  import { STYLE_BASE_PATH } from './style-bundle';
10
10
 
@@ -49,7 +49,8 @@ export function styleRoutes(source: StyleSource): readonly Route[] {
49
49
  return applyCacheHeaders(missing, NO_STORE);
50
50
  }
51
51
  return applyCacheHeaders(
52
- new Response(chunk.css, { headers: { 'content-type': 'text/css; charset=utf-8' } }),
52
+ // `textResponse` for the length it declares: `compress` reads nothing undeclared.
53
+ textResponse(chunk.css, { headers: { 'content-type': 'text/css; charset=utf-8' } }),
53
54
  { mode: 'immutable' },
54
55
  );
55
56
  },
@@ -49,7 +49,7 @@ const spell = (path: string, locale: string, locales: RoutedLocales): string =>
49
49
  * The route table, as the service worker sees it — once per routed locale. The server strips a
50
50
  * `/en/` prefix before it matches (`@ultimat3/http`'s `locale-prefix.ts`), and the worker matches
51
51
  * pathnames: with one spelling per route, no `/en/…` URL had a rule, so the English site was never
52
- * cached and never offline (notificado.co, 22.3.2). Only the fields a browser can act on cross,
52
+ * cached and never offline (one adopter, 22.3.2). Only the fields a browser can act on cross,
53
53
  * plus — for a spelling this build rendered — the content hash and byte count of its document.
54
54
  */
55
55
  export function pwaRoutes(
@@ -106,7 +106,7 @@ export interface PrecacheAssetInput {
106
106
  /**
107
107
  * The island chunks a PRECACHED page boots, every surface stylesheet and every framework script.
108
108
  * Every island in the app was precached, so a first anonymous visit to a marketing page downloaded
109
- * the admin, KYC and payment islands too — 43 chunks, ~1 MB on notificado.co (22.3.2). The rest are
109
+ * the admin, KYC and payment islands too — 43 chunks, ~1 MB on one adopter (22.3.2). The rest are
110
110
  * cached the first time a page asks for one (`ISLAND_BASE_PATH` is a runtime asset prefix).
111
111
  *
112
112
  * Two readers of "which islands", because only one of them exists per build: the static export
package/src/sw-routes.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  // both artifacts as files and mounts nothing, and it must not have to import a route table to do it.
4
4
 
5
5
  import type { CacheHint, Route, UltimateRequest } from '@ultimat3/http';
6
- import { applyCacheHeaders } from '@ultimat3/http';
6
+ import { applyCacheHeaders, textResponse } from '@ultimat3/http';
7
7
  import { revalidatedResponse } from './revalidated-response';
8
8
  import type { ServiceWorkerArtifacts } from './sw-artifacts';
9
9
  import { SERVICE_WORKER_PATH, SW_REGISTER_PATH, SW_SCOPE } from './sw-artifacts';
@@ -24,7 +24,7 @@ export const serviceWorkerRoutes = (artifacts: ServiceWorkerArtifacts): readonly
24
24
  meta: { name: 'pwa.serviceWorker', auth: 'public' },
25
25
  handler: () =>
26
26
  applyCacheHeaders(
27
- new Response(artifacts.source, {
27
+ textResponse(artifacts.source, {
28
28
  headers: {
29
29
  'content-type': 'text/javascript; charset=utf-8',
30
30
  // Without it the browser refuses to let a worker served from `/` control `/`, which
@@ -0,0 +1,306 @@
1
+ // The tests `x g action` and `x g mutator` write beside the declaration: a `unit` file for its shape
2
+ // and handler, a `contract` file for its projections. Split from `action.ts` because the gate types
3
+ // a test by its filename, and the two together passed that file's ceiling.
4
+
5
+ import { sortedImports } from './imports';
6
+ import type { NameSet } from './naming';
7
+
8
+ /** The code the slice's `errors.ts` declares for a missing row. */
9
+ const notFoundCode = (feature: NameSet): string =>
10
+ `X_${feature.kebab.toUpperCase().split('-').join('_')}_NOT_FOUND`;
11
+
12
+ const ID = '00000000-0000-4000-8000-000000000001';
13
+ const ORG = '00000000-0000-4000-8000-000000000002';
14
+ const OTHER_ORG = '00000000-0000-4000-8000-000000000009';
15
+
16
+ /** The declaration-shape assertions that differ between the two primitives. */
17
+ const shapeTest = (name: NameSet, feature: NameSet, isMutator: boolean): string =>
18
+ isMutator
19
+ ? `unitTest('${name.camel} projects both halves and a conflict strategy', () => {
20
+ // The projected names mirror the declaration: local() optimistic, server() authoritative.
21
+ // server() routes through the same invoke() core as every other surface, so it cannot skip
22
+ // the input parse, the policy or the output parse.
23
+ expect(target.describeMutator().kind).toBe('mutator');
24
+ expect(target.conflict).toBe('server-wins');
25
+ expect(typeof target.local).toBe('function');
26
+ expect(typeof target.server).toBe('function');
27
+ });`
28
+ : `unitTest('${name.camel} is a declared action that invalidates its slice', () => {
29
+ expect(target.kind).toBe('action');
30
+ expect(target.describe().name).toBe('${name.camel}');
31
+ // Without \`cache: { invalidates }\` every cached read of this slice serves the row as it was.
32
+ expect(target.describe().invalidates).toContain(${feature.camel}Tag.entity);
33
+ });`;
34
+
35
+ /**
36
+ * The contract test's preamble. Everything but the target rides only where it is USED: the
37
+ * scaffolded app lints with `noUnusedVariables: error`, so an actor or an input the file never
38
+ * reads would be a lint failure in code nobody typed — which is what a hand-written policy, whose
39
+ * tenancy this generator asserts nothing about, was handed.
40
+ */
41
+ const preamble = (
42
+ name: NameSet,
43
+ feature: NameSet,
44
+ isMutator: boolean,
45
+ wrappers: string,
46
+ outsider: boolean,
47
+ /** `testActor` is imported for the outsider, or for a contract suite's exercise actor. */
48
+ importsActor = outsider,
49
+ ): string => `${importsActor ? "import { testActor } from '@ultimat3/policy';\n" : ''}import { ${wrappers} } from '@ultimat3/testing';
50
+ import { ${name.camel} } from './${name.kebab}';
51
+ ${
52
+ outsider
53
+ ? `
54
+ const id = '${ID}';
55
+ const orgId = '${ORG}';
56
+ const input = { id, orgId${isMutator ? ", title: 'a title'" : ''} };
57
+ `
58
+ : ''
59
+ }
60
+ // Named here because every projection needs a stable name and this file does not boot the app.
61
+ // At boot \`registerActions(await import('./actions'))\` stamps the same name onto the same
62
+ // object, so each projection of it works there with nothing to remember.
63
+ const target = ${name.camel}.named('${name.camel}');
64
+ ${
65
+ outsider
66
+ ? `
67
+ // Holds the grant, wrong org. That is the interesting actor: a denial here is the predicate
68
+ // deciding, not the permission check, so this test fails if the tenancy rule is ever dropped.
69
+ const outsider = testActor('outsider', {
70
+ orgId: '${OTHER_ORG}',
71
+ permissions: ['${feature.kebab}:write'],
72
+ }).actor;
73
+ `
74
+ : ''
75
+ }`;
76
+
77
+ /** What the unit test may do with the slice the declaration lives in. */
78
+ export interface HandlerShape {
79
+ readonly isMutator: boolean;
80
+ /** The handler reads a row by id and throws `<Feature>NotFoundError` when there is none. */
81
+ readonly lookup: boolean;
82
+ /** The slice still takes the row `x g entity` scaffolds, so the test may store one. */
83
+ readonly storesRow: boolean;
84
+ readonly dbModule: string;
85
+ /**
86
+ * The permission the writer is granted — `<feature>:write` for the policy this generator wrote,
87
+ * the one literal `can('<perm>')` of a handwritten one, or `undefined` when that policy's grant
88
+ * cannot be read, and then no assertion depends on it.
89
+ */
90
+ readonly grant: string | undefined;
91
+ /** The input and the writer name an org: the slice is tenant-scoped, or its policy reads one. */
92
+ readonly org: boolean;
93
+ }
94
+
95
+ /**
96
+ * The optimistic half, run against the client store `@ultimat3/testing` ships. A twin is replayed
97
+ * on every rebase, so the two facts worth failing on are the row one application leaves and that a
98
+ * second leaves the same one — and that a row the store never held is not invented.
99
+ */
100
+ const localTwinTests = (): string => `
101
+ unitTest('the local twin marks the row pending, and a replay lands in the same place', () => {
102
+ const store = memoryLocalTx({ [TABLE]: { [id]: { id, title: 'before', pending: false } } });
103
+ target.local(store.tx, input);
104
+ const once = store.rows(TABLE);
105
+ expect(once).toEqual({ [id]: { id, title: 'a title', pending: true } });
106
+ // A rebase replays the queued write: twice through must equal once through.
107
+ target.local(store.tx, input);
108
+ expect(store.rows(TABLE)).toEqual(once);
109
+ });
110
+
111
+ unitTest('the local twin invents no row the store never held', () => {
112
+ const store = memoryLocalTx();
113
+ target.local(store.tx, input);
114
+ expect(store.rows(TABLE)).toEqual({});
115
+ });
116
+ `;
117
+
118
+ /** The authoritative half's cases, refusal first, in the shape the slice earns. */
119
+ const handlerTests = (feature: NameSet, shape: HandlerShape): string => {
120
+ if (shape.grant === undefined) {
121
+ return `
122
+ // No handler assertion: ../policy.ts is hand-written and its grant is not one literal can('…'),
123
+ // so which actor reaches the handler is the author's to state — add that test here.
124
+ `;
125
+ }
126
+ const answer = shape.isMutator ? "{ id, title: 'a title' }" : '{ id }';
127
+ if (!shape.lookup) {
128
+ return `
129
+ unitTest('an actor without the grant is refused before the handler runs', async () => {
130
+ const stranger = testActor('stranger'${shape.org ? ', { orgId }' : ''}).actor;
131
+ const refused = await target.as(stranger, input).catch((error: unknown) => error);
132
+ expect(refused).toBeUltimateError('X_FORBIDDEN');
133
+ });
134
+
135
+ unitTest('the handler answers a writer${shape.org ? ' in the org' : ''}', async () => {
136
+ expect(await target.as(writer, input)).toEqual(${answer});
137
+ });
138
+ `;
139
+ }
140
+ const found = shape.isMutator
141
+ ? "{ id: row.id, title: 'a title' }"
142
+ : "{ id: row.id, title: 'kept' }";
143
+ const call = shape.isMutator
144
+ ? "{ id: row.id, orgId, title: 'a title' }"
145
+ : '{ id: row.id, orgId }';
146
+ return `
147
+ unitTest('the handler refuses an id nothing holds', async () => {
148
+ const refused = await target.as(writer, input).catch((error: unknown) => error);
149
+ expect(refused).toBeUltimateError('${notFoundCode(feature)}');
150
+ });
151
+ ${
152
+ shape.storesRow
153
+ ? `
154
+ unitTest('the handler answers the row it found', async () => {
155
+ const draft = { orgId, title: 'kept', price: { minor: 0, currency: 'USD' } };
156
+ const row = await runWithContext(ctxOf({ actor: writer }), () => repo.insert(draft));
157
+ const answer = await target.as(writer, ${call});
158
+ expect(answer).toEqual(${found});
159
+ });
160
+
161
+ unitTest('the handler reads its row in one statement', async ({ statements }) => {
162
+ const draft = { orgId, title: 'kept', price: { minor: 0, currency: 'USD' } };
163
+ const row = await runWithContext(ctxOf({ actor: writer }), () => repo.insert(draft));
164
+ const before = statements.count();
165
+ await target.as(writer, ${call});
166
+ // Its own lookup and nothing else, on this driver as on Postgres: a read added per row is red.
167
+ expect(statements.count() - before).toBe(1);
168
+ });
169
+ `
170
+ : ''
171
+ }`;
172
+ };
173
+
174
+ /**
175
+ * The unit test: the declaration's shape, the input it refuses, and the HANDLER — run through
176
+ * `.as()`, the one execution path, against the in-memory driver. A mutator's local twin runs
177
+ * against `memoryLocalTx`. The contract projections are next door, in their own suite.
178
+ *
179
+ * A row is stored only in a slice whose row this generator can spell (`sliceTakesScaffoldRow`): a
180
+ * slice an author reshaped has its own columns, and a test that inserted `{ title, price }` into
181
+ * it would be a guess about someone else's table.
182
+ */
183
+ export const actionUnitTest = (name: NameSet, feature: NameSet, shape: HandlerShape): string => {
184
+ const known = shape.grant !== undefined;
185
+ const stores = known && shape.lookup && shape.storesRow;
186
+ const testing = [
187
+ ...(stores ? ['afterEach'] : []),
188
+ 'expect',
189
+ ...(shape.isMutator ? ['memoryLocalTx'] : []),
190
+ 'unitTest',
191
+ ].join(', ');
192
+ return `// ${name.camel}: its declared shape, the input it refuses, and what its handler answers — in
193
+ // process, so all three belong to the \`unit\` step. The contract projections are next door.
194
+ ${sortedImports([
195
+ ...(stores ? ["import { ctxOf, runWithContext } from '@ultimat3/core';"] : []),
196
+ ...(known ? ["import { testActor } from '@ultimat3/policy';"] : []),
197
+ `import { ${testing} } from '@ultimat3/testing';`,
198
+ ...(stores ? [`import { driver } from '${shape.dbModule}';`] : []),
199
+ ])}${shape.isMutator ? '' : `\nimport { ${feature.camel}Tag } from '../policy';`}${stores ? "\nimport * as repo from '../repo';" : ''}
200
+ import { ${name.camel} } from './${name.kebab}';
201
+
202
+ const id = '${ID}';
203
+ ${shape.org ? `const orgId = '${ORG}';\n` : ''}const input = { id${shape.org ? ', orgId' : ''}${shape.isMutator ? ", title: 'a title'" : ''} };
204
+ ${shape.isMutator ? `\n// The client store's table: the entity's own, so the twin and the server row share one key.\nconst TABLE = '${feature.table}';\n` : ''}
205
+ // Named here because every projection needs a stable name and this file does not boot the app.
206
+ // At boot \`registerActions(await import('./actions'))\` stamps the same name onto the same
207
+ // object, so each projection of it works there with nothing to remember.
208
+ const target = ${name.camel}.named('${name.camel}');
209
+ ${
210
+ known
211
+ ? `
212
+ // Holds the grant${shape.org ? ' in the org the input names' : ''}: past the policy, so what answers is the handler.
213
+ const grant = ['${shape.grant}'];
214
+ const writer = testActor('writer', { ${shape.org ? 'orgId, ' : ''}permissions: grant }).actor;
215
+ `
216
+ : ''
217
+ }${
218
+ stores
219
+ ? `
220
+ // One store per process: without this, one test's rows are the next test's fixtures.
221
+ afterEach(() => {
222
+ driver.reset?.();
223
+ });
224
+ `
225
+ : ''
226
+ }
227
+ ${shapeTest(name, feature, shape.isMutator)}
228
+
229
+ unitTest('${name.camel} rejects input that is not a uuid', async () => {
230
+ await expect(target.input).toRejectInput({ ...input, id: 'not-a-uuid' });
231
+ await expect(target.input).toAcceptInput(input);
232
+ });
233
+ ${shape.isMutator ? localTwinTests() : ''}${handlerTests(feature, shape)}`;
234
+ };
235
+
236
+ /**
237
+ * Who the generated-input runs act as: an actor holding the slice's grant — `<feature>:write` for
238
+ * the policy this generator wrote, the one literal `can('<perm>')` of a handwritten one — in the
239
+ * input's org when the rule compares orgs. A grant that cannot be read is the author's to name,
240
+ * so those runs are skipped out loud instead.
241
+ */
242
+ const exerciseActor = (grant: string | undefined, orgRule: boolean): string =>
243
+ grant === undefined
244
+ ? ''
245
+ : `
246
+ // Holds the grant${orgRule ? ', in the org every generated input is bound to' : ''}: the actor the handler runs as when \`.contract()\`
247
+ // calls it with generated legal input.
248
+ const member = testActor('member', {${orgRule ? '\n orgId,' : ''}
249
+ permissions: ['${grant}'],
250
+ }).actor;
251
+ `;
252
+
253
+ /** The `.contract()` argument: the actor and, under an org rule, the org every draw is bound to. */
254
+ const exerciseOption = (grant: string | undefined, orgRule: boolean): string => {
255
+ if (grant === undefined) {
256
+ return "{\n exercise: { off: '../policy.ts names no one grant: pass { actor } holding what it checks' },\n }";
257
+ }
258
+ return orgRule
259
+ ? '{\n exercise: { actor: member, input: (drawn) => ({ ...(drawn as object), orgId }) },\n }'
260
+ : '{ exercise: { actor: member } }';
261
+ };
262
+
263
+ export const actionContractTest = (
264
+ name: NameSet,
265
+ feature: NameSet,
266
+ isMutator: boolean,
267
+ /** The slice's policy is the one this generator wrote, so its tenancy rule is known. */
268
+ scaffoldPolicy = true,
269
+ /** That policy holds an org rule: false for a slice whose entity names no tenant. */
270
+ orgRule = scaffoldPolicy,
271
+ /** The permission the exercise actor holds; `undefined` when the policy's grant cannot be read. */
272
+ grant: string | undefined = scaffoldPolicy ? `${feature.kebab}:write` : undefined,
273
+ ): string => `// ${name.camel}: the contract every action owes, and the foreign-org actor it denies before the
274
+ // handler runs. One declaration, every surface.
275
+ ${preamble(name, feature, isMutator, 'contractTest, expect', orgRule, orgRule || grant !== undefined)}${exerciseActor(grant, orgRule)}
276
+ contractTest('${name.camel} passes the action contract', async () => {
277
+ // Four assertions the framework makes for any action, without knowing what this one does:
278
+ // garbage input is rejected, an anonymous actor is denied, the operation reaches the OpenAPI
279
+ // document, and the handler survives 16 generated legal inputs, each rolled back.
280
+ for (const contract of target.contract(${exerciseOption(grant, orgRule)})) {
281
+ await contract.run();
282
+ }
283
+ });
284
+
285
+ ${
286
+ orgRule
287
+ ? `contractTest('${name.camel} denies a foreign org', async () => {
288
+ // \`.as()\` is the one execution path with the actor swapped, so this denial is the same one
289
+ // HTTP, MCP and the job surface would produce — and no repo call happened to produce it.
290
+ const denied = await target.as(outsider, input).catch((error: unknown) => error);
291
+ expect(denied).toBeUltimateError('X_FORBIDDEN');
292
+ });
293
+ `
294
+ : scaffoldPolicy
295
+ ? `// No foreign-org assertion: this slice's entity names no tenant, so there is no other org to deny.
296
+ `
297
+ : `// No foreign-org assertion: ../policy.ts is hand-written, and whether it checks the org is the
298
+ // author's rule to state — add that test here.
299
+ `
300
+ }
301
+ // No MCP tool until it is described: a placeholder description misleads the agent reading it.
302
+ contractTest('${name.camel} projects one operation and no tool', () => {
303
+ expect(target.openapi().operationId).toBe('${name.camel}');
304
+ expect(target.mcp?.expose ?? false).toBe(false);
305
+ });
306
+ `;