@ultimat3/cli 23.0.0 → 25.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 (296) hide show
  1. package/CLAUDE.md +28 -13
  2. package/README.md +32 -7
  3. package/package.json +29 -29
  4. package/src/admin-catalog-keys.ts +2 -2
  5. package/src/affected.ts +30 -36
  6. package/src/api-registration.ts +89 -0
  7. package/src/app-artifacts.ts +1 -1
  8. package/src/app-auth.ts +4 -27
  9. package/src/app-boundaries.ts +17 -4
  10. package/src/app-config-load.ts +51 -0
  11. package/src/app-env.ts +26 -25
  12. package/src/app-load.ts +38 -14
  13. package/src/app-manifest.ts +4 -10
  14. package/src/app-mcp.ts +31 -46
  15. package/src/app-name.ts +52 -0
  16. package/src/app-permissions-borrowed.ts +123 -0
  17. package/src/app-permissions-site.ts +48 -0
  18. package/src/app-permissions.ts +7 -17
  19. package/src/app-root.ts +9 -5
  20. package/src/bin.ts +3 -7
  21. package/src/browser-launcher.ts +4 -2
  22. package/src/budgets-charged.ts +60 -0
  23. package/src/budgets-scan.ts +76 -0
  24. package/src/budgets-unmeasured.ts +91 -0
  25. package/src/budgets.ts +117 -154
  26. package/src/cdp-shot-driver.ts +20 -1
  27. package/src/cdp-shot-watch.ts +35 -31
  28. package/src/ci-log.ts +0 -0
  29. package/src/cmd-auth-spec.ts +16 -0
  30. package/src/cmd-auth.ts +65 -0
  31. package/src/cmd-build-spec.ts +7 -1
  32. package/src/cmd-build.ts +151 -22
  33. package/src/cmd-db-backfill.ts +26 -13
  34. package/src/cmd-db-branch.ts +2 -2
  35. package/src/cmd-db.ts +10 -6
  36. package/src/cmd-deploy-compose.ts +94 -0
  37. package/src/cmd-deploy-drain.ts +48 -0
  38. package/src/cmd-deploy-helm.ts +7 -6
  39. package/src/cmd-deploy-start-first.ts +178 -0
  40. package/src/cmd-deploy.ts +158 -36
  41. package/src/cmd-dev.ts +18 -356
  42. package/src/cmd-doctor-spec.ts +2 -9
  43. package/src/cmd-doctor.ts +57 -80
  44. package/src/cmd-env.ts +55 -16
  45. package/src/cmd-generate.ts +89 -32
  46. package/src/cmd-help.ts +19 -6
  47. package/src/cmd-i18n.ts +21 -17
  48. package/src/cmd-jobs-spec.ts +7 -22
  49. package/src/cmd-jobs.ts +33 -140
  50. package/src/cmd-manifest-spec.ts +1 -1
  51. package/src/cmd-manifest.ts +6 -25
  52. package/src/cmd-mcp.ts +71 -22
  53. package/src/cmd-new.ts +36 -8
  54. package/src/cmd-planned.ts +24 -8
  55. package/src/cmd-routes.ts +15 -5
  56. package/src/cmd-secrets.ts +47 -6
  57. package/src/cmd-shot-matrix.ts +22 -7
  58. package/src/cmd-shot-spec.ts +10 -2
  59. package/src/cmd-shot.ts +46 -41
  60. package/src/cmd-tasks.ts +2 -2
  61. package/src/cmd-test-spec.ts +1 -7
  62. package/src/cmd-test.ts +14 -30
  63. package/src/cmd-verify.ts +4 -4
  64. package/src/coverage-lcov.ts +4 -2
  65. package/src/db-backfill.ts +2 -2
  66. package/src/db-replay-engine.ts +4 -10
  67. package/src/db-subscribes.ts +27 -16
  68. package/src/dev-boot.ts +290 -0
  69. package/src/dev-child-watch.ts +63 -12
  70. package/src/dev-dashboard-guard.ts +76 -0
  71. package/src/dev-dashboard.ts +56 -30
  72. package/src/dev-lock.ts +100 -31
  73. package/src/dev-rebuild.ts +80 -0
  74. package/src/dev-restart-retire.ts +46 -0
  75. package/src/dev-route-table.ts +30 -8
  76. package/src/dev-supervisor.ts +114 -22
  77. package/src/dev-traces.ts +53 -20
  78. package/src/dispatch.ts +17 -13
  79. package/src/doctor-app-url.ts +39 -0
  80. package/src/doctor-auth.ts +95 -0
  81. package/src/doctor-offline.ts +6 -5
  82. package/src/document-options.ts +7 -0
  83. package/src/drift-replica-identity.ts +42 -0
  84. package/src/drift.ts +29 -6
  85. package/src/error-catalog.ts +6 -9
  86. package/src/error-codes.ts +29 -3
  87. package/src/error-page-csp.ts +14 -15
  88. package/src/error-unthrown.ts +29 -11
  89. package/src/errors.ts +54 -83
  90. package/src/exec.ts +8 -0
  91. package/src/favicon.ts +2 -2
  92. package/src/fix-command.ts +9 -1
  93. package/src/framework-env.ts +148 -0
  94. package/src/framework-schema-apply.ts +78 -0
  95. package/src/framework-schema-stamp.ts +96 -0
  96. package/src/framework-schema.ts +25 -3
  97. package/src/generate-catalog-locales.ts +79 -0
  98. package/src/generate-files.ts +4 -3
  99. package/src/generate-kinds.ts +18 -0
  100. package/src/generate-plural.ts +50 -0
  101. package/src/generate-shadow.ts +95 -0
  102. package/src/generate-write.ts +47 -13
  103. package/src/gh-target.ts +12 -7
  104. package/src/hold.ts +56 -1
  105. package/src/i18n-audit.ts +25 -9
  106. package/src/i18n-index.ts +5 -6
  107. package/src/i18n-registration.ts +7 -9
  108. package/src/import-scan.ts +28 -3
  109. package/src/index.ts +2 -6
  110. package/src/island-bundle.ts +57 -79
  111. package/src/island-capture.ts +1 -1
  112. package/src/island-duplicate-refusal.ts +57 -0
  113. package/src/island-duplicates.ts +221 -0
  114. package/src/island-entry-missing.ts +64 -0
  115. package/src/island-harness-route.ts +20 -4
  116. package/src/island-harness-script.ts +23 -2
  117. package/src/island-identity.ts +17 -14
  118. package/src/island-package-dedupe.ts +150 -0
  119. package/src/island-realtime.ts +36 -16
  120. package/src/island-routes.ts +2 -2
  121. package/src/island-runtime.ts +241 -0
  122. package/src/island-sources.ts +105 -0
  123. package/src/island-states-load.ts +6 -0
  124. package/src/island-store.ts +90 -6
  125. package/src/jobs-json.ts +0 -26
  126. package/src/jobs-report.ts +36 -20
  127. package/src/local-cli-handoff.ts +90 -0
  128. package/src/loopback-host.ts +13 -0
  129. package/src/manifest-staleness.ts +62 -0
  130. package/src/mcp-errors.ts +20 -4
  131. package/src/mcp-host.ts +22 -14
  132. package/src/mcp-test-run.ts +38 -0
  133. package/src/mcp-ui-inspect.ts +3 -2
  134. package/src/mcp-ui-interact.ts +3 -2
  135. package/src/mcp-ui.ts +8 -7
  136. package/src/measure-scope.ts +15 -10
  137. package/src/messages.ts +29 -16
  138. package/src/metrics-endpoint.ts +28 -3
  139. package/src/migration-extensions.ts +1 -1
  140. package/src/otlp-export.ts +2 -2
  141. package/src/output.ts +24 -0
  142. package/src/page-navigation.ts +7 -23
  143. package/src/page-speculation.ts +19 -31
  144. package/src/parse.ts +0 -8
  145. package/src/permission-grants.ts +3 -3
  146. package/src/posix-path.ts +30 -0
  147. package/src/prerender-locales.ts +6 -4
  148. package/src/prerender-out.ts +34 -7
  149. package/src/prerender-report.ts +58 -0
  150. package/src/prerender-served.ts +52 -0
  151. package/src/prerender.ts +71 -103
  152. package/src/pwa-artifacts.ts +33 -94
  153. package/src/pwa-manifest-locales.ts +9 -9
  154. package/src/registry.ts +2 -0
  155. package/src/retry-memo.ts +1 -1
  156. package/src/revalidated-response.ts +4 -3
  157. package/src/role-realtime.ts +7 -1
  158. package/src/role-replicator.ts +124 -29
  159. package/src/role-start.ts +69 -85
  160. package/src/role-sync.ts +119 -25
  161. package/src/root-env.ts +157 -28
  162. package/src/route-islands.ts +69 -0
  163. package/src/runtime-admin.ts +4 -1
  164. package/src/runtime-assets.ts +26 -9
  165. package/src/runtime-bindings.ts +27 -7
  166. package/src/runtime-cache.ts +46 -55
  167. package/src/runtime-hooks.ts +2 -1
  168. package/src/runtime-idempotency-scope.ts +23 -0
  169. package/src/runtime-isr-outcome.ts +100 -0
  170. package/src/runtime-isr.ts +36 -0
  171. package/src/runtime-jobs.ts +76 -41
  172. package/src/runtime-live-feed.ts +1 -1
  173. package/src/runtime-mail.ts +26 -0
  174. package/src/runtime-mfa-warning.ts +29 -0
  175. package/src/runtime-notify-retention.ts +10 -46
  176. package/src/runtime-overrides.ts +4 -4
  177. package/src/runtime-purge.ts +38 -16
  178. package/src/runtime-queue.ts +43 -22
  179. package/src/runtime-realtime.ts +11 -45
  180. package/src/runtime-render.ts +136 -114
  181. package/src/runtime-replica.ts +3 -8
  182. package/src/runtime-services.ts +49 -26
  183. package/src/runtime-storage-upload.ts +2 -2
  184. package/src/runtime-storage.ts +13 -8
  185. package/src/schema-diff.ts +16 -0
  186. package/src/schema-drift.ts +45 -15
  187. package/src/secrets-rotation.ts +8 -9
  188. package/src/seo-routes.ts +80 -9
  189. package/src/serve-boot.ts +51 -12
  190. package/src/serve-drain.ts +11 -33
  191. package/src/serve-env.ts +9 -6
  192. package/src/serve-retire.ts +95 -0
  193. package/src/serve-web.ts +31 -15
  194. package/src/serve.ts +27 -10
  195. package/src/server-barrels.ts +6 -3
  196. package/src/shot-browser.ts +1 -1
  197. package/src/shot-locale.ts +6 -33
  198. package/src/shot-settle.ts +3 -3
  199. package/src/shot-theme.ts +1 -1
  200. package/src/shot-viewport.ts +152 -0
  201. package/src/signal-shred.ts +26 -12
  202. package/src/site-asset-routes.ts +2 -2
  203. package/src/site-assets.ts +3 -5
  204. package/src/site-config.ts +9 -48
  205. package/src/site-seo.ts +7 -5
  206. package/src/sitemap-lastmod.ts +5 -0
  207. package/src/solid-loader.ts +12 -3
  208. package/src/source-files.ts +19 -3
  209. package/src/static-report.ts +5 -0
  210. package/src/stored-object-headers.ts +43 -0
  211. package/src/style-routes.ts +2 -2
  212. package/src/sw-artifacts.ts +8 -8
  213. package/src/sw-precache-plan.ts +34 -4
  214. package/src/sync-authenticator.ts +3 -3
  215. package/src/sync-url.ts +3 -3
  216. package/src/tasks-facts.ts +3 -29
  217. package/src/templates/action.ts +7 -4
  218. package/src/templates/backfill.ts +4 -4
  219. package/src/templates/entity.ts +2 -2
  220. package/src/templates/github/ci.yml.ts +28 -18
  221. package/src/templates/github/image.yml.ts +92 -0
  222. package/src/templates/github/runner.ts +18 -0
  223. package/src/templates/guard-bare-error.ts +16 -2
  224. package/src/templates/guard-unzoned-date.ts +56 -6
  225. package/src/templates/index.ts +1 -1
  226. package/src/templates/job-body-test.ts +2 -2
  227. package/src/templates/job.ts +6 -6
  228. package/src/templates/naming.ts +28 -0
  229. package/src/templates/query.ts +9 -8
  230. package/src/templates/resource-page.ts +9 -7
  231. package/src/templates/resource-service.ts +2 -2
  232. package/src/templates/route.ts +26 -14
  233. package/src/templates/scaffold-api.ts +2 -3
  234. package/src/templates/scaffold-app.ts +8 -3
  235. package/src/templates/scaffold-auth.ts +205 -70
  236. package/src/templates/scaffold-bin.ts +122 -0
  237. package/src/templates/scaffold-claude-commands.ts +11 -11
  238. package/src/templates/scaffold-container-compose.ts +10 -1
  239. package/src/templates/scaffold-container.ts +16 -8
  240. package/src/templates/scaffold-dashboard-bare.ts +8 -4
  241. package/src/templates/scaffold-dashboard-example-tests.ts +208 -0
  242. package/src/templates/scaffold-dashboard-example.ts +112 -172
  243. package/src/templates/scaffold-dashboard-shared.ts +62 -1
  244. package/src/templates/scaffold-dashboard.ts +9 -5
  245. package/src/templates/scaffold-db-client.ts +4 -2
  246. package/src/templates/scaffold-db-package.ts +2 -2
  247. package/src/templates/scaffold-docs.ts +20 -77
  248. package/src/templates/scaffold-domain-package.ts +0 -4
  249. package/src/templates/scaffold-entries.ts +4 -3
  250. package/src/templates/scaffold-env.ts +6 -3
  251. package/src/templates/scaffold-gitattributes.ts +59 -0
  252. package/src/templates/scaffold-helm-fences.ts +76 -0
  253. package/src/templates/scaffold-helm-templates.ts +100 -19
  254. package/src/templates/scaffold-helm.ts +62 -5
  255. package/src/templates/scaffold-http.ts +1 -1
  256. package/src/templates/scaffold-i18n.ts +15 -2
  257. package/src/templates/scaffold-icon.ts +2 -2
  258. package/src/templates/scaffold-repo.ts +27 -16
  259. package/src/templates/scaffold-shell.ts +55 -37
  260. package/src/test-counts.ts +2 -6
  261. package/src/test-ignores.ts +25 -0
  262. package/src/test-only-shipped.ts +193 -0
  263. package/src/test-passes.ts +0 -15
  264. package/src/test-select.ts +7 -15
  265. package/src/test-shards.ts +40 -95
  266. package/src/test-workers.ts +26 -4
  267. package/src/theme-boot.ts +8 -33
  268. package/src/theme-brand.ts +66 -0
  269. package/src/trusted-hops-error.ts +17 -0
  270. package/src/trusted-hops.ts +49 -0
  271. package/src/unscanned-admin.ts +2 -2
  272. package/src/unthrown-codes.ts +26 -0
  273. package/src/verify-checks.ts +29 -48
  274. package/src/verify-coverage-run.ts +3 -2
  275. package/src/verify-deadline.ts +207 -33
  276. package/src/verify-merge.ts +108 -43
  277. package/src/verify-part-step.ts +98 -0
  278. package/src/verify-role-load-probe.ts +12 -2
  279. package/src/verify-role-load.ts +9 -4
  280. package/src/verify-run.ts +32 -8
  281. package/src/verify-shard.ts +13 -2
  282. package/src/verify-stalled.ts +152 -0
  283. package/src/verify-step.ts +2 -0
  284. package/src/verify-test-run.ts +3 -2
  285. package/src/verify-tests.ts +5 -3
  286. package/src/worker-bundle.ts +2 -2
  287. package/src/workspace-checks.ts +53 -7
  288. package/src/workspace-graph.ts +3 -2
  289. package/src/write-line.ts +82 -7
  290. package/src/browser-launcher-fake-html.ts +0 -80
  291. package/src/browser-launcher-fake.ts +0 -168
  292. package/src/build-errors.ts +0 -54
  293. package/src/jobs-drain.ts +0 -163
  294. package/src/scaffold-typecheck.ts +0 -227
  295. package/src/thrown-by.ts +0 -24
  296. package/src/verify-errors.ts +0 -58
package/src/theme-boot.ts CHANGED
@@ -1,40 +1,15 @@
1
- // The no-flash theme script, inlined into every document a served or exported process writes,
2
- // with `theme.defaultMode` from `app.config.ts` as its fallback. Before this the key had no reader
3
- // at all (pinned as a dead key in `scripts/lib/config-reader-pins.ts`) and neither of the two
4
- // scripts the framework exported was wired into a document: an app that wanted to open dark had to
5
- // write its own boot script AND admit it to the CSP by hand — which every app forgot in one of the
6
- // two places. Sibling of `app-auth.ts`'s `loadSignInPath` and imports the config for the same
7
- // reason: a regex over the app's source is the pattern `app-load.ts` refuses.
1
+ // The no-flash theme script, inlined into every document a served or exported process writes, with
2
+ // `theme.defaultMode` (off the one loader, `app-config-load.ts`) as its fallback — and the CSP source
3
+ // that admits it, so no app writes its own boot script and forgets to admit it.
8
4
 
9
- // why: Bun exposes no path-join primitive, and the config path is app-root-relative — the same
10
- // necessity `app-auth.ts` records.
11
- import { join } from 'node:path';
12
5
  import type { ThemeMode } from '@ultimat3/core';
13
6
  import { cspHashSource } from '@ultimat3/http';
14
7
  import { renderHead, themeScript, themeScriptBody } from '@ultimat3/render';
15
- import { APP_CONFIG_EXPORT } from './app-auth';
16
- import { APP_CONFIG_FILE } from './app-root';
8
+ import { loadAppConfig } from './app-config-load';
17
9
 
18
- const isRecord = (value: unknown): value is Record<string, unknown> =>
19
- typeof value === 'object' && value !== null;
20
-
21
- const isThemeMode = (value: unknown): value is ThemeMode =>
22
- value === 'light' || value === 'dark' || value === 'system';
23
-
24
- /**
25
- * `theme.defaultMode`, or `'system'` — the value `defineConfig` fills in — when the app declares
26
- * none or the file is absent. Structural, never `instanceof`, for `loadSignInPath`'s reason.
27
- */
10
+ /** `theme.defaultMode`, or `'system'` — core's own default — for a root with no config file. */
28
11
  export async function loadThemeMode(root: string): Promise<ThemeMode> {
29
- const configPath = join(root, APP_CONFIG_FILE);
30
- if (!(await Bun.file(configPath).exists())) return 'system';
31
- const module = (await import(configPath)) as Record<string, unknown>;
32
- const config = module[APP_CONFIG_EXPORT];
33
- if (!isRecord(config)) return 'system';
34
- const theme = config['theme'];
35
- if (!isRecord(theme)) return 'system';
36
- const mode = theme['defaultMode'];
37
- return isThemeMode(mode) ? mode : 'system';
12
+ return (await loadAppConfig(root))?.theme.defaultMode ?? 'system';
38
13
  }
39
14
 
40
15
  export interface ThemeBoot {
@@ -47,8 +22,8 @@ export interface ThemeBoot {
47
22
  /**
48
23
  * Tag and hash from ONE body: `themeScriptBody` is what `themeScript` inlines, so a policy hashed
49
24
  * here admits the script the document carries and not a restatement of it. The storage key is
50
- * render's `THEME_STORAGE_KEY`, which `theme-boot.test.ts` pins equal to `@ultimat3/ui`'s — the
51
- * toggle writes the key the boot reads.
25
+ * `@ultimat3/core`'s `THEME_STORAGE_KEY`, the one `@ultimat3/ui`'s toggle writes — so the toggle
26
+ * writes the key the boot reads.
52
27
  */
53
28
  export function themeBoot(mode: ThemeMode): ThemeBoot {
54
29
  const options = { fallback: mode };
@@ -0,0 +1,66 @@
1
+ // The app's brand, declared once as `export const brand = defineTheme(…)` in
2
+ // `apps/web/shared/theme.ts`, read here and nowhere else: the `<style>` every document the boot
3
+ // renders carries after its stylesheet, and the body `style-src` admits. No module, no brand: an
4
+ // unthemed app emits no tag and extends no policy.
5
+
6
+ // why: Bun exposes no path-join primitive, and the module path is app-root-relative.
7
+ import { join } from 'node:path';
8
+ import { ConfigInvalidError, describeValue } from '@ultimat3/core';
9
+ import { renderHead } from '@ultimat3/render';
10
+
11
+ /** The conventional module, beside `shared/global.ts` — one fixed path, as `API_INDEX` is. */
12
+ export const APP_THEME_MODULE = 'apps/web/shared/theme.ts';
13
+ /** The named export the module declares — `@ultimat3/ui`'s README spells it `brand`. */
14
+ export const APP_THEME_EXPORT = 'brand';
15
+
16
+ export interface ThemeBrand {
17
+ /** The rendered `<style>`, for `DocumentOptions.brandHead`. */
18
+ readonly head: string;
19
+ /** Exactly the text between the tags — the body a boot hands `inlineStyleSources`. */
20
+ readonly style: string;
21
+ }
22
+
23
+ /** Code to paste, as `loadAppConfig`'s is: the module path is already in the cause. */
24
+ const FIX = `export const ${APP_THEME_EXPORT} = defineTheme({ preset: 'scifi' });`;
25
+
26
+ /**
27
+ * `undefined` when the app has no theme module or its brand renders no CSS. A module that will not
28
+ * import throws as it did, so a palette `defineTheme()` refuses fails the boot with
29
+ * `X_UI_CONTRAST_INSUFFICIENT` (or its token code) and not a rewording of it.
30
+ */
31
+ export async function loadThemeBrand(root: string): Promise<ThemeBrand | undefined> {
32
+ const path = join(root, APP_THEME_MODULE);
33
+ if (!(await Bun.file(path).exists())) return undefined;
34
+ const module = (await import(path)) as Record<string, unknown>;
35
+ const css = brandCss(path, module);
36
+ if (css.length === 0) return undefined;
37
+ // `renderHead` escapes `</` in a style body, which would make the tag's text differ from the
38
+ // hashed one — so a body that needs escaping is refused above rather than escaped here.
39
+ return { head: renderHead([{ kind: 'style', key: 'style:brand', content: css }]), style: css };
40
+ }
41
+
42
+ /**
43
+ * The brand's stylesheet, read structurally: `@ultimat3/ui` is a tier below and this package does
44
+ * not import it, so a `Brand` is `{ css: string }`. `defineTheme()` never emits `<` — its values
45
+ * are validated against grammars that exclude it — so one here came from a hand-built object, and
46
+ * it is the one character that could end the element and start markup.
47
+ */
48
+ function brandCss(path: string, module: Record<string, unknown>): string {
49
+ const brand = Object.hasOwn(module, APP_THEME_EXPORT) ? module[APP_THEME_EXPORT] : undefined;
50
+ const css = typeof brand === 'object' && brand !== null && 'css' in brand ? brand.css : undefined;
51
+ if (typeof css !== 'string') {
52
+ throw new ConfigInvalidError({
53
+ cause: `${path} exports no ${APP_THEME_EXPORT} built by defineTheme() (${describeValue(brand)}), so no document can carry the app's theme — declare it with defineTheme from @ultimat3/ui`,
54
+ fix: FIX,
55
+ meta: { key: APP_THEME_EXPORT },
56
+ });
57
+ }
58
+ if (css.includes('<')) {
59
+ throw new ConfigInvalidError({
60
+ cause: `${path}'s ${APP_THEME_EXPORT}.css contains "<", which defineTheme() never emits and which could close the <style> element it is inlined in`,
61
+ fix: FIX,
62
+ meta: { key: APP_THEME_EXPORT },
63
+ });
64
+ }
65
+ return css;
66
+ }
@@ -0,0 +1,17 @@
1
+ // The refusal of a malformed `TRUSTED_PROXY_HOPS` — its own code, never `X_PORT_INVALID`: a proxy
2
+ // count is not a port, and an operator told "is not a TCP port number" looked for the wrong setting.
3
+
4
+ import { UltimateError } from '@ultimat3/core';
5
+ import { MAX_PROXY_HOPS } from '@ultimat3/http';
6
+ import { quoteArg } from './shell-quote';
7
+
8
+ export class TrustedProxyHopsInvalidError extends UltimateError {
9
+ constructor(input: { readonly value: string }) {
10
+ super({
11
+ code: 'X_TRUSTED_PROXY_HOPS_INVALID',
12
+ cause: `TRUSTED_PROXY_HOPS=${quoteArg(input.value)} is not a whole number of proxies from 1 to ${MAX_PROXY_HOPS} — the count that append to x-forwarded-for in front of this process; unset means none`,
13
+ fix: 'TRUSTED_PROXY_HOPS=1 ROLE=web bun apps/web/server.ts',
14
+ meta: { value: input.value, max: MAX_PROXY_HOPS },
15
+ });
16
+ }
17
+ }
@@ -0,0 +1,49 @@
1
+ // How many proxies sit in front of this process, read from `TRUSTED_PROXY_HOPS`. Its own module
2
+ // so both the web role (`role-start.ts`) and the sync node (`role-sync.ts`) read it without one
3
+ // importing the other — `role-start` imports `role-sync`, and the reverse edge was a cycle.
4
+
5
+ import { MAX_PROXY_HOPS } from '@ultimat3/http';
6
+ import type { Env } from './runtime-bindings';
7
+ import { TrustedProxyHopsInvalidError } from './trusted-hops-error';
8
+
9
+ /**
10
+ * How many proxies is too many, and the number is **`@ultimat3/http`'s**, not this file's.
11
+ *
12
+ * `defineHttpConfig` screens the same setting — `assertFiniteCount('trustedProxyHops', …,
13
+ * MAX_PROXY_HOPS)` — and this screen used to say 16 where that one says 64, so one setting had two
14
+ * ceilings and a deployment behind 20 hops was accepted by the library and refused by the boot.
15
+ * Widened rather than narrowed: tightening the library would break a shipped public API, while
16
+ * this end only ever refused topologies http already supports.
17
+ *
18
+ * It was a duplicated literal until 2026-08-26, because `@ultimat3/http` did not export the
19
+ * constant. It does now, so this file IMPORTS it — a downward edge, tier 5 to tier 2 — and there is
20
+ * no second number left to drift. `runtime-overrides.test.ts` still probes both screens for the
21
+ * highest count each accepts, and it is kept rather than deleted: it compares BEHAVIOUR, so it also
22
+ * catches the two disagreeing for a reason a shared constant cannot fix, such as one side gaining a
23
+ * range check the other does not have.
24
+ */
25
+
26
+ /**
27
+ * How many proxies append to `x-forwarded-for` between the client and this process, or `null`
28
+ * when nothing in front of it is trusted.
29
+ *
30
+ * Read from the environment and not from `app.config.ts`, for the reason `PORT` and `ROLE` are: it
31
+ * is a fact about the DEPLOYMENT — one image runs behind an ingress in one cluster and behind
32
+ * nothing on a laptop — and an app that hardcoded it would be wrong in one of the two. `x dev`
33
+ * sets neither and gets `trustProxy: false`, which is correct: there is no proxy.
34
+ *
35
+ * Without this seam a container behind an ingress reads `ctx.ip` as the ingress's own socket
36
+ * address on every request, so the rate limiter keys the entire fleet's anonymous traffic into ONE
37
+ * bucket and a single scanner 429s every real signup.
38
+ */
39
+ export function trustedHopsFromEnv(env: Env): number | null {
40
+ const raw = env['TRUSTED_PROXY_HOPS']?.trim();
41
+ if (raw === undefined || raw === '') return null;
42
+ const hops = Number(raw);
43
+ // A malformed count is refused rather than defaulted: reading the header at the wrong index is
44
+ // trusting a value the client typed, which is the failure trusting a proxy exists to avoid.
45
+ if (!/^\d+$/.test(raw) || !Number.isInteger(hops) || hops < 1 || hops > MAX_PROXY_HOPS) {
46
+ throw new TrustedProxyHopsInvalidError({ value: raw });
47
+ }
48
+ return hops;
49
+ }
@@ -4,7 +4,7 @@
4
4
  // silently, which is how the reference app shipped an admin nobody could open.
5
5
 
6
6
  import { renderFixShellArg, stripComments } from '@ultimat3/core';
7
- import { describeRoutes } from '@ultimat3/render';
7
+ import { describePages } from '@ultimat3/render';
8
8
  import { ADMIN_FILE } from './admin-registration';
9
9
  import type { Finding } from './output';
10
10
  import { hasPathSegment } from './path-segments';
@@ -17,7 +17,7 @@ const DECLARES = /\bdefineAdmin\(/;
17
17
 
18
18
  /** Any admin this process mounted — then every declaration was reached, wherever it sits. */
19
19
  export const adminMounted = (): boolean =>
20
- describeRoutes().some((route) => route.mount?.by === 'defineAdmin');
20
+ describePages().some((route) => route.mount?.by === 'defineAdmin');
21
21
 
22
22
  /** Where the file belongs: its own name under the admin's home, `index.ts` as the declaration. */
23
23
  const homeOf = (file: string): string => {
@@ -0,0 +1,26 @@
1
+ // The registered codes this repo no longer throws, as a list `error-unthrown.ts` reads — its own
2
+ // file, so the literals naming the waived codes are not mistaken for a throw site.
3
+
4
+ /** Where the list lives, so a finding can name the file to edit. */
5
+ export const UNTHROWN_CODES_FILE = 'packages/cli/src/unthrown-codes.ts';
6
+
7
+ /**
8
+ * The codes this repo registers and no longer throws — each a decision, and each row on the
9
+ * reference page says so in words. A LIST, never a prose match: the waiver was any row containing
10
+ * "not thrown" or "thrown by nothing", so a sentence that happened to use either phrase silenced the
11
+ * rule for its code. A listing whose code is thrown again, or is not registered, is
12
+ * `X_ERROR_CODE_UNTHROWN_STALE`.
13
+ */
14
+ export const UNTHROWN_CODES: ReadonlySet<string> = new Set([
15
+ // `x db studio` is planned and unreleased; it exits X_NOT_IMPLEMENTED.
16
+ 'X_DB_STUDIO_FAILED',
17
+ // 21.0.0: there is no LiveClient to register (X_REALTIME_UNINSTALLED replaced it).
18
+ 'X_LIVE_CLIENT_MISSING',
19
+ // 21.0.0: liveHookFor was deleted; useQuery reads any query.
20
+ 'X_QUERY_NOT_SUBSCRIBABLE',
21
+ // 21.0.0: the worker no longer POSTs a flush endpoint nothing mounted.
22
+ 'X_PWA_SYNC_FLUSH_FAILED',
23
+ 'X_PWA_SYNC_INCOMPLETE',
24
+ // 24.0.0: `x test --worker` was deleted; `x verify --only unit --shard i/n` is the one split.
25
+ 'X_TEST_SHARD_FAILED',
26
+ ]);
@@ -6,19 +6,13 @@ import { existsSync } from 'node:fs';
6
6
  import { join } from 'node:path';
7
7
  import { ERROR_DOCS_URL } from '@ultimat3/core';
8
8
  import type { Manifest } from '@ultimat3/manifest';
9
- import {
10
- AGENTS_MD_FILENAME,
11
- assertNoDrift,
12
- MANIFEST_FILENAME,
13
- verifyContract,
14
- } from '@ultimat3/manifest';
9
+ import { AGENTS_MD_FILENAME, MANIFEST_FILENAME, verifyContract } from '@ultimat3/manifest';
15
10
  import type { MetaIssue } from '@ultimat3/seo';
16
11
  import { validateMeta } from '@ultimat3/seo';
17
12
  import { checkAgentsMd } from './app-agents-md';
18
13
  import { readAppSources } from './app-boundaries';
19
14
  import { envExampleFindings } from './app-env';
20
15
  import { appManifest, readAppManifest } from './app-manifest';
21
- import { OPENAPI_FILE, openApiStaleness } from './app-openapi';
22
16
  import { policyFindings } from './app-permissions';
23
17
  import { APP_CONFIG_FILE } from './app-root';
24
18
  import { asyncPageFindings } from './async-pages';
@@ -33,6 +27,11 @@ import { catalogFindings } from './i18n-registration';
33
27
  import { unregisteredJobFindings } from './job-registration';
34
28
  import { liveRouteFindings } from './live-routes';
35
29
  import { withLoadFindings, withoutLoadFindings } from './load-findings';
30
+ import {
31
+ hasCommittedContract,
32
+ manifestMissingFindings,
33
+ manifestStaleness,
34
+ } from './manifest-staleness';
36
35
  import { msg } from './messages';
37
36
  import type { Finding } from './output';
38
37
  import { findingFrom } from './output';
@@ -47,7 +46,7 @@ import type { VerifyStep } from './verify-step';
47
46
  import { fromExec, fromFindings, hostFindings } from './verify-step';
48
47
  import { TEST_STEPS } from './verify-tests';
49
48
  import { typecheckArgs } from './verify-typecheck';
50
- import { checkFileSizes, checkPackageShape, hasWorkspacePackages } from './workspace-checks';
49
+ import { checkPackageShape, fileSizeReport, hasWorkspacePackages } from './workspace-checks';
51
50
  import { checkWorkspaceDependencies } from './workspace-graph';
52
51
 
53
52
  /** The one file that makes the `roadmap` step answerable, and therefore what `applies` reads. */
@@ -121,7 +120,17 @@ export const VERIFY_STEPS: readonly VerifyStep[] = [
121
120
  {
122
121
  name: 'filesize',
123
122
  summary: 'one file, one job',
124
- run: async (ctx) => fromFindings(await checkFileSizes(ctx.root)),
123
+ // The band below the ceiling rides as warnings: listed in `--json` on a pass, never a failure.
124
+ run: async (ctx) => {
125
+ const { findings, warnings } = await fileSizeReport(ctx.root);
126
+ return {
127
+ ...fromFindings(findings),
128
+ warnings: warnings.map(
129
+ (warning) =>
130
+ `${warning.path}: ${warning.lines} lines, ${warning.ceiling - warning.lines} under the ${warning.ceiling} line ceiling — plan the split`,
131
+ ),
132
+ };
133
+ },
125
134
  },
126
135
  {
127
136
  name: 'package-shape',
@@ -198,10 +207,11 @@ export const VERIFY_STEPS: readonly VerifyStep[] = [
198
207
  {
199
208
  name: 'contract-diff',
200
209
  summary: 'the published contract vs the committed manifest',
201
- // Either file is a published contract on its own: `openapi.json` generates the typed client,
202
- // so gating on the manifest alone let a stale spec ship a wrong client unchecked.
203
- applies: async (ctx) =>
204
- existsSync(join(ctx.root, MANIFEST_FILENAME)) || existsSync(join(ctx.root, OPENAPI_FILE)),
210
+ // A BREAKING change against the committed manifest, and nothing else. Whether the committed
211
+ // files are stale — `openapi.json` included — is the `manifest` step's, through the one check
212
+ // `x manifest --check` runs (`manifest-staleness.ts`): reported here as well it was two
213
+ // steps and one command answering one question, and the step named `manifest` saying green.
214
+ applies: async (ctx) => existsSync(join(ctx.root, MANIFEST_FILENAME)),
205
215
  async run(ctx) {
206
216
  const committed = await readAppManifest(ctx.root);
207
217
  const { manifest, findings } = await appManifest(ctx.root);
@@ -209,14 +219,13 @@ export const VERIFY_STEPS: readonly VerifyStep[] = [
209
219
  return withoutLoadFindings(ctx.root, [
210
220
  ...findings,
211
221
  ...(committed === undefined ? [] : contractFindings(committed, manifest)),
212
- ...(await openApiStaleness(ctx.root, manifest)),
213
222
  ]);
214
223
  },
215
224
  },
216
225
  {
217
226
  name: 'budgets',
218
227
  summary:
219
- 'per-route JS bytes and LCP, the global style layer every document carries, and the routes that boot nothing to receive their live rows',
228
+ 'per-route JS bytes, the global style layer every document carries, and the routes that boot nothing to receive their live rows',
220
229
  // The global-style assertion rides here rather than becoming a step of its own, because this
221
230
  // step already asks the one question it asks: what does the document this build emits actually
222
231
  // contain? It is also the same app load — `appManifest` fills render's stylesheet registry on
@@ -346,7 +355,6 @@ export const VERIFY_STEPS: readonly VerifyStep[] = [
346
355
  const roleLoad = app ? roleLoadFindings(ctx.root, ctx.runner) : Promise.resolve([]);
347
356
  const drift = await driftFindings(ctx.root);
348
357
  const findings = [
349
- ...manifestMissingFindings(ctx.root),
350
358
  ...(app ? await withLoadFindings(ctx.root, drift) : drift),
351
359
  // An app only: the framework monorepo declares no `defineApi`, so it has no list to be in.
352
360
  ...(existsSync(join(ctx.root, APP_CONFIG_FILE))
@@ -384,41 +392,14 @@ export const VERIFY_STEPS: readonly VerifyStep[] = [
384
392
  ];
385
393
 
386
394
  /**
387
- * The half that had never been asked: is the file there at all? `driftFindings` returns nothing
388
- * when it is absent — correctly, it has nothing to compare — and `AGENTS.md` line 3 tells an agent
389
- * that facts live in `x.manifest.json`, `x dev` prints its path, and `x manifest` is the only
390
- * thing that writes it. Nothing ran it: after `x new`, `bin/setup` and all thirteen generators,
391
- * `find . -name '*.manifest.json'` returned nothing while this step reported green (#F7).
392
- *
393
- * An app root, never this repo: the framework monorepo emits `framework.manifest.json` and has no
394
- * `x.manifest.json` to be missing, so the `app.config.ts` is what decides — the same discriminator
395
- * `drift`, `budgets`, `seo`, `i18n` and `policy` each use.
395
+ * The committed generated files against the code — `x manifest --check`'s check, through its
396
+ * function. With no committed contract there is nothing to load an app for: an app root is told
397
+ * its manifest is missing, and the framework monorepo (which has neither file) is told nothing.
396
398
  */
397
- function manifestMissingFindings(root: string): readonly Finding[] {
398
- if (!existsSync(join(root, APP_CONFIG_FILE))) return [];
399
- if (existsSync(join(root, MANIFEST_FILENAME))) return [];
400
- return [
401
- {
402
- code: 'X_MANIFEST_MISSING',
403
- cause: `${MANIFEST_FILENAME} does not exist, so every fact an agent reads about this app — route table, action schemas, policies, error codes — is unavailable`,
404
- fix: 'x manifest',
405
- docs: ERROR_DOCS_URL,
406
- at: MANIFEST_FILENAME,
407
- },
408
- ];
409
- }
410
-
411
- /** `assertNoDrift` throws `X_MANIFEST_DRIFT`; a step reports, so the error becomes a finding. */
412
399
  async function driftFindings(root: string): Promise<readonly Finding[]> {
413
- const path = join(root, MANIFEST_FILENAME);
414
- if (!existsSync(path)) return [];
400
+ if (!hasCommittedContract(root)) return manifestMissingFindings(root);
415
401
  const { manifest, findings } = await appManifest(root);
416
- try {
417
- await assertNoDrift({ manifest, path });
418
- return findings;
419
- } catch (error) {
420
- return [...findings, { ...findingFrom(error), at: MANIFEST_FILENAME }];
421
- }
402
+ return [...findings, ...(await manifestStaleness(root, manifest))];
422
403
  }
423
404
 
424
405
  /** A breaking change is allowed — with a major bump. `verifyContract` is the one that decides. */
@@ -14,6 +14,7 @@
14
14
  import { rm } from 'node:fs/promises';
15
15
  // why: Bun ships no path-join primitive.
16
16
  import { dirname, join } from 'node:path';
17
+ import { ISOLATED_ENV } from '@ultimat3/testing';
17
18
  import type { CoverageMap } from './coverage-lcov';
18
19
  import { fileCoverageOf, mergeCoverage, parseLcov } from './coverage-lcov';
19
20
  import type { ExecResult, Runner } from './exec';
@@ -21,7 +22,7 @@ import { execOutput } from './exec';
21
22
  import { msg } from './messages';
22
23
  import { countsOf } from './test-counts';
23
24
  import { testEnvOverrides } from './test-dotenv';
24
- import { failureOf, ISOLATED_TEST_ENV } from './test-shards';
25
+ import { failureOf } from './test-shards';
25
26
  import { SLOT_HELD_ENV } from './test-slots';
26
27
  import type { StepOutcome } from './verify-step';
27
28
 
@@ -113,7 +114,7 @@ export async function runCovered(options: CoveredRunOptions): Promise<CoveredRun
113
114
  const isolate = options.isolate === true;
114
115
  const overrides = {
115
116
  ...testEnvOverrides(options.root, options.env ?? Bun.env),
116
- ...(isolate ? { [ISOLATED_TEST_ENV]: '1' } : {}),
117
+ ...(isolate ? { [ISOLATED_ENV]: '1' } : {}),
117
118
  ...(lease === undefined ? {} : { [SLOT_HELD_ENV]: '1' }),
118
119
  };
119
120
  const results = new Array<ExecResult>(slices.length);