@ultimat3/cli 18.0.0 → 19.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.
@@ -0,0 +1,71 @@
1
+ // A globally installed `x` defers to the app's own `@ultimat3/cli` when the two are different
2
+ // files. Measured 2026-09-05 in an app whose global `x` was a `bun link` of this checkout: the
3
+ // global CLI's `@ultimat3/entity` was a second module instance with an EMPTY registry, so `x g`,
4
+ // `x db gen` and `x manifest` wrote a manifest with zero entities and proposed dropping every
5
+ // table — silently, with a green exit code. The app's entities register into the instance under
6
+ // its `node_modules`; only the CLI under that same `node_modules` can see them. So the rule is the
7
+ // one `tsc` and `eslint` follow: the project-local binary wins, and the global one only says so.
8
+ //
9
+ // The same registry split is what turned `x verify` green over that app: `@ultimat3/policy`'s
10
+ // `isKnownPermission` deliberately checks nothing while no permission is declared, and the global
11
+ // CLI's instance had none declared — so the `policy` step that exists to refuse an undeclared
12
+ // grant (`app-permissions.ts`) had an empty set to refuse against. One process, one registry.
13
+
14
+ // why: `realpathSync` is the whole decision — Bun ships no symlink-resolving stat of its own, and
15
+ // `Bun.file(path).exists()` follows a link without saying where it went.
16
+ import { existsSync, realpathSync } from 'node:fs';
17
+ // why: Bun exposes no path-join primitive; the local bin is assembled from the app root.
18
+ import { join } from 'node:path';
19
+ import { findAppRoot } from './app-root';
20
+
21
+ /**
22
+ * Set to any value to keep the CLI that was invoked. Nothing in this repository's CI needs it —
23
+ * both tracked apps symlink `node_modules/@ultimat3/cli` to `packages/cli`, so the realpath test
24
+ * below already answers "same file" — it exists for the one deliberate case: running a checkout's
25
+ * CLI against an app that pins an older release, to see what the next release would say.
26
+ */
27
+ export const KEEP_GLOBAL_CLI_ENV = 'ULTIMATE_KEEP_GLOBAL_CLI';
28
+
29
+ export const LOCAL_CLI_BIN = join('node_modules', '@ultimat3', 'cli', 'src', 'bin.ts');
30
+
31
+ export interface LocalCliIo {
32
+ exists(path: string): boolean;
33
+ realpath(path: string): string;
34
+ }
35
+
36
+ const nodeIo: LocalCliIo = {
37
+ exists: existsSync,
38
+ realpath: (path) => realpathSync(path),
39
+ };
40
+
41
+ /**
42
+ * The app-local `bin.ts` to re-execute, or undefined when this process already IS the app's CLI
43
+ * (a workspace symlink resolves to the same file), when there is no app, when this process is a
44
+ * compiled binary, or when the caller opted out. Pure over the injected filesystem so the
45
+ * decision has a test without a checkout.
46
+ *
47
+ * A compiled `x` (`x build --target binary`, the container's `/app/x`) has an `import.meta.path`
48
+ * inside Bun's virtual `/$bunfs/` — no such file exists on disk, so `realpath` throws. That is
49
+ * the keep case, not the hand-over case: the binary is the deliberate artifact, and the runtime
50
+ * image carries no `bun` to hand over to. The other unresolvable side — a `node_modules` entry
51
+ * that exists but whose link is dangling — is kept for the same reason: nothing there can run.
52
+ */
53
+ export function resolveLocalCli(
54
+ input: {
55
+ readonly cwd: string;
56
+ readonly selfPath: string;
57
+ readonly env: Readonly<Record<string, string | undefined>>;
58
+ },
59
+ io: LocalCliIo = nodeIo,
60
+ ): string | undefined {
61
+ if (input.env[KEEP_GLOBAL_CLI_ENV] !== undefined) return undefined;
62
+ const root = findAppRoot(input.cwd);
63
+ if (root === undefined) return undefined;
64
+ const local = join(root.dir, LOCAL_CLI_BIN);
65
+ if (!io.exists(local)) return undefined;
66
+ try {
67
+ return io.realpath(local) === io.realpath(input.selfPath) ? undefined : local;
68
+ } catch {
69
+ return undefined;
70
+ }
71
+ }
package/src/mcp-errors.ts CHANGED
@@ -78,6 +78,15 @@ const CLI_FIXES: Readonly<Record<CliErrorCode, string>> = {
78
78
  X_E2E_LOCATOR_AMBIGUOUS:
79
79
  'x test e2e --json # the fix line carries the same call with .first() on it',
80
80
  X_E2E_SERVICE_WORKER_ABSENT: 'x build --target static --json',
81
+ // The four raw-CDP codes. `x doctor` for the missing browser, because that is the command whose
82
+ // whole job is reporting what this machine does not have; the other three are raised inside a
83
+ // running suite, so the runnable half is the command that re-runs it.
84
+ X_CDP_BROWSER_MISSING:
85
+ 'x doctor --json # or set CHROME_PATH to a Chrome binary; unset, the browser-backed suite skips',
86
+ X_CDP_LAUNCH_FAILED:
87
+ 'x test e2e --json # the cause carries the last lines of the browser\u2019s own stderr',
88
+ X_CDP_CALL_FAILED: 'x test e2e --json # the cause names the DevTools call the browser refused',
89
+ X_CDP_TIMEOUT: 'x test e2e --json # the cause names the call that never answered',
81
90
  X_GH_UNAVAILABLE: 'gh auth login # install first from https://cli.github.com',
82
91
  X_GH_NOT_AUTHENTICATED: 'gh auth login',
83
92
  X_GH_COMMAND_FAILED: 'x ci --json # the finding carries the gh invocation that failed',
@@ -0,0 +1,26 @@
1
+ // The actor a route is rendered AS when the render exists only to be weighed. `app/` pages are
2
+ // authed by construction: a `load` that calls a policy-guarded query denies an anonymous actor with
3
+ // `X_UNAUTHENTICATED`, so under the anonymous build context every authed page was reported
4
+ // `X_BUDGET_UNMEASURED` and a real app could not pass the `budgets` step (measured 2026-09-05).
5
+ // Weighing bytes needs no data authority — the rendered document is discarded — so this actor holds
6
+ // every permission. It is handed ONLY to the weigh-and-discard branch of `prerender.ts`, never to
7
+ // `renderStatic`: a `site/` artifact is published to everyone, and a guarded query inside its `load`
8
+ // must keep failing the build rather than rendering another actor's rows into a file.
9
+
10
+ import type { Actor } from '@ultimat3/core';
11
+
12
+ /** The id every trace and log line under a measurement render carries, so it is recognisable. */
13
+ export const MEASUREMENT_ACTOR_ID = 'x-build-measure';
14
+
15
+ /**
16
+ * `kind: 'service'` and not `'user'`: an app's own `requireMember()`-style helper that resolves a
17
+ * user to a row has nothing to resolve here, and the honest kind says so. `'*'` is the grant
18
+ * `actorHas` reads as everything — the same spelling a role map uses for a superuser.
19
+ */
20
+ export const measurementActor = (): Actor => ({
21
+ kind: 'service',
22
+ id: MEASUREMENT_ACTOR_ID,
23
+ roles: [],
24
+ scopes: [],
25
+ permissions: ['*'],
26
+ });
package/src/messages.ts CHANGED
@@ -11,6 +11,10 @@ const CATALOG = {
11
11
  'cli.commands.heading': 'commands',
12
12
  'cli.build.done': 'built {target}',
13
13
  'cli.build.failed': '{target} build failed',
14
+ // A build-output line, so it belongs here rather than inline beside the emitter: `x build` is a
15
+ // human surface and every other word it prints comes from this catalog.
16
+ 'cli.build.pushUnwired':
17
+ 'pwa.push is true and no VAPID key is configured, so the emitted sw.js carries no push handler',
14
18
  // `describeCron`'s vocabulary. `@ultimat3/time` is tier 1 and reaches no i18n runtime, so the
15
19
  // caller supplies the words — and the caller here is a rendered `x tasks show` line, which is
16
20
  // exactly what this catalog holds. `msg()` leaves an un-supplied `{n}`/`{time}`/`{days}`/
@@ -79,6 +83,7 @@ const CATALOG = {
79
83
  'cli.dev.panels': ' panels {panels}',
80
84
  'cli.dev.introspect': ' introspect {url}',
81
85
  'cli.dev.manifest': ' manifest {path}',
86
+ 'cli.dev.mcp': ' mcp POST {path}',
82
87
  'cli.deploy.plan': 'containers only: {images} image, roles {roles}',
83
88
  'cli.doctor.clean': 'no findings — environment is shippable',
84
89
  'cli.doctor.findings': '{count} finding(s)',
package/src/prerender.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  import { join } from 'node:path';
7
7
  import { createContext, renderThrowable, runWithContext } from '@ultimat3/core';
8
8
  import type { RouteEntry } from '@ultimat3/render';
9
- import { routeEntries } from '@ultimat3/render';
9
+ import { describeRoutes, routeEntries } from '@ultimat3/render';
10
10
  import { renderStatic } from '@ultimat3/render/server';
11
11
  import { loadApp } from './app-load';
12
12
  import { appManifest } from './app-manifest';
@@ -17,9 +17,11 @@ import { errorPageDocument, STATIC_ERROR_PAGE } from './error-pages';
17
17
  import { FAVICON_PATH, faviconBytes } from './favicon';
18
18
  import type { IslandBundle } from './island-bundle';
19
19
  import { buildIslands, writeIslands } from './island-bundle';
20
+ import { measurementActor } from './measurement-actor';
20
21
  import { loadPwaArtifacts, WEB_MANIFEST_PATH, writePwaIcons } from './pwa-artifacts';
21
22
  import type { SkippedRoute, UnmeasuredRoute } from './static-report';
22
23
  import { skippedRoute, skipReasonFor, writeStaticReport } from './static-report';
24
+ import { SERVICE_WORKER_PATH, SW_REGISTER_PATH, serviceWorkerArtifacts } from './sw-artifacts';
23
25
 
24
26
  // Re-exported, never re-declared: `static-report.ts` owns the shape because the report on disk
25
27
  // carries it, and this file already imports that module.
@@ -76,6 +78,15 @@ export interface PrerenderReport {
76
78
  readonly report: string;
77
79
  /** Client entries emitted, one chunk each. Reported so "which JS shipped?" needs no unzip. */
78
80
  readonly islands: readonly string[];
81
+ /**
82
+ * What the service worker could not express, and what its precache manifest weighs too much of.
83
+ *
84
+ * `PrecacheManifest.warnings` had no reader anywhere in the tree — the precache budget was, in
85
+ * `wiki/Troubleshooting.md`'s own words, "a designed thing that is not one" (#390). An install
86
+ * that stalls on a bad connection is invisible on a laptop and fatal on a phone, so the number
87
+ * has to reach the build's own report. Empty for an app with no service worker.
88
+ */
89
+ readonly serviceWorkerWarnings: readonly string[];
79
90
  }
80
91
 
81
92
  /**
@@ -154,6 +165,18 @@ export async function prerenderSite(options: PrerenderOptions): Promise<Prerende
154
165
  // wiring exists to close. `undefined` when the app is not installable, and then no document
155
166
  // names it either.
156
167
  const pwa = await loadPwaArtifacts(options.root);
168
+ // The worker and its registration script, written as FILES. A static host runs no route table,
169
+ // so a `<script src="/x-sw-register.js">` in every document is a 404 unless the bytes are in the
170
+ // artifact — the same promise `favicon.ico` and the icons above keep, for the asset that decides
171
+ // whether the export works offline at all.
172
+ const serviceWorker =
173
+ pwa === undefined
174
+ ? undefined
175
+ : serviceWorkerArtifacts({ pwa, buildId, routes: describeRoutes(), islands });
176
+ if (serviceWorker !== undefined) {
177
+ await Bun.write(join(options.out, SERVICE_WORKER_PATH.slice(1)), serviceWorker.source);
178
+ await Bun.write(join(options.out, SW_REGISTER_PATH.slice(1)), serviceWorker.register);
179
+ }
157
180
  if (pwa !== undefined) {
158
181
  await Bun.write(join(options.out, WEB_MANIFEST_PATH.slice(1)), pwa.body);
159
182
  // And the icons that manifest NAMES. A static host runs no `assetRoutes()`, so every
@@ -172,13 +195,25 @@ export async function prerenderSite(options: PrerenderOptions): Promise<Prerende
172
195
  // documents, and this build's own id so a component reading `ctx.buildId` stamps the artifact
173
196
  // with the id the report and the stats carry.
174
197
  const ctx = createContext({ role: 'web', buildId });
175
- const document = (entry: RouteEntry, data: { url: string; params: Record<string, string> }) =>
176
- runWithContext(ctx, () =>
198
+ // A SECOND context, for the branch below that renders only to weigh. Its actor holds every
199
+ // permission (`measurement-actor.ts`), because an `app/` page's `load` calls policy-guarded
200
+ // queries and denied the anonymous one with `X_UNAUTHENTICATED` — every authed page unmeasured.
201
+ // `renderStatic` keeps `ctx`: its output is a published file, and a `site/` load that a policy
202
+ // refuses must fail the build, never render another actor's rows into it.
203
+ const measureCtx = createContext({ role: 'web', buildId, actor: measurementActor() });
204
+ const documentAs = (
205
+ as: typeof ctx,
206
+ entry: RouteEntry,
207
+ data: { url: string; params: Record<string, string> },
208
+ ) =>
209
+ runWithContext(as, () =>
177
210
  routeDocument(entry, data, {
178
211
  resolveIsland: (file: string) => islands.resolverFor(file),
179
- ...(pwa === undefined ? {} : { pwaHead: pwa.head }),
212
+ ...(pwa === undefined ? {} : { pwaHead: pwa.head + (serviceWorker?.head ?? '') }),
180
213
  }),
181
214
  );
215
+ const document = (entry: RouteEntry, data: { url: string; params: Record<string, string> }) =>
216
+ documentAs(ctx, entry, data);
182
217
 
183
218
  for (const entry of routeEntries()) {
184
219
  const facts = { surface: entry.surface, render: entry.config.render, route: entry.path };
@@ -191,7 +226,7 @@ export async function prerenderSite(options: PrerenderOptions): Promise<Prerende
191
226
  // routes it never used to touch would be a worse regression than the gap it closes. A route
192
227
  // that will not render here is reported, gets no stats entry, and stays `X_BUDGET_UNMEASURED`.
193
228
  try {
194
- const html = await document(entry, {
229
+ const html = await documentAs(measureCtx, entry, {
195
230
  url: new URL(entry.path, origin).href,
196
231
  params: {},
197
232
  });
@@ -267,6 +302,7 @@ export async function prerenderSite(options: PrerenderOptions): Promise<Prerende
267
302
  // stdout — so the one command the finding tells an author to run printed no `unmeasured` key
268
303
  // and no reason. Written into the report is what makes the instruction true.
269
304
  unmeasured,
305
+ serviceWorkerWarnings: serviceWorker?.warnings ?? [],
270
306
  });
271
307
  return {
272
308
  out: options.out,
@@ -277,5 +313,6 @@ export async function prerenderSite(options: PrerenderOptions): Promise<Prerende
277
313
  stats,
278
314
  report,
279
315
  islands: islands.chunks.map((chunk) => chunk.file),
316
+ serviceWorkerWarnings: serviceWorker?.warnings ?? [],
280
317
  };
281
318
  }
@@ -22,7 +22,7 @@ import { existsSync } from 'node:fs';
22
22
  // why: Bun exposes no path-join primitive, and `APP_CONFIG_FILE` is app-root-relative — the same
23
23
  // necessity `favicon.ts` and `dev-assets.ts` each record for their own root-relative constant.
24
24
  import { join } from 'node:path';
25
- import type { PwaColors } from '@ultimat3/core';
25
+ import type { PwaColors, PwaOfflineConfig } from '@ultimat3/core';
26
26
  import type { CacheHint, Route, UltimateRequest } from '@ultimat3/http';
27
27
  import { applyCacheHeaders } from '@ultimat3/http';
28
28
  import {
@@ -56,6 +56,15 @@ export interface PwaArtifacts {
56
56
  * no manifest is an iOS icon for an app iOS will not add.
57
57
  */
58
58
  readonly head: string;
59
+ /**
60
+ * The three `pwa` keys the SERVICE WORKER needs, carried here because this is the one module
61
+ * that reads an app's config file — `sw-artifacts.ts` needs the route table and the island
62
+ * bundle as well, and a second `await import` of `app.config.ts` would be a second answer to
63
+ * "what did this app declare".
64
+ */
65
+ readonly offline: PwaOfflineConfig;
66
+ readonly backgroundSync: boolean;
67
+ readonly push: boolean;
59
68
  }
60
69
 
61
70
  const isRecord = (value: unknown): value is Record<string, unknown> =>
@@ -93,6 +102,9 @@ function colorsOf(value: unknown): PwaColors | undefined {
93
102
  interface InstallableApp {
94
103
  readonly name: string;
95
104
  readonly colors: PwaColors;
105
+ readonly offline: PwaOfflineConfig;
106
+ readonly backgroundSync: boolean;
107
+ readonly push: boolean;
96
108
  }
97
109
 
98
110
  async function loadInstallable(root: string): Promise<InstallableApp | undefined> {
@@ -109,9 +121,36 @@ async function loadInstallable(root: string): Promise<InstallableApp | undefined
109
121
  const name = text(pwa['name']);
110
122
  const colors = colorsOf(pwa['colors']);
111
123
  if (name === undefined || colors === undefined) return undefined;
112
- return { name, colors };
124
+ return { name, colors, offline: offlineOf(pwa['offline']), ...flags(pwa) };
125
+ }
126
+
127
+ /**
128
+ * The offline block, read structurally for `colorsOf`'s reason: `defineConfig` refuses
129
+ * `enabled: true` without an absolute `offline.fallback`, but a HAND-WRITTEN config object never
130
+ * passed through it. A missing or relative fallback answers `null`, and `serviceWorkerArtifacts`
131
+ * then emits no worker at all — never a path the framework invented, which offline would be a
132
+ * cached 404 answering every navigation.
133
+ */
134
+ function offlineOf(value: unknown): PwaOfflineConfig {
135
+ const block = isRecord(value) ? value : {};
136
+ const fallback = text(block['fallback']);
137
+ const patterns = block['neverCache'];
138
+ return {
139
+ fallback: fallback?.startsWith('/') === true ? fallback : null,
140
+ image: text(block['image']) ?? null,
141
+ font: text(block['font']) ?? null,
142
+ neverCache: Array.isArray(patterns)
143
+ ? patterns.filter((entry): entry is string => typeof entry === 'string')
144
+ : [],
145
+ };
113
146
  }
114
147
 
148
+ /** `=== true` for `enabled`'s reason: a hand-written `backgroundSync: 'yes'` wires nothing. */
149
+ const flags = (pwa: Record<string, unknown>): { backgroundSync: boolean; push: boolean } => ({
150
+ backgroundSync: pwa['backgroundSync'] === true,
151
+ push: pwa['push'] === true,
152
+ });
153
+
115
154
  /**
116
155
  * Resolved ONCE at boot, like `loadSignInPath` and `loadCacheTiers` and unlike `faviconBytes`:
117
156
  * `await import` caches the module, so re-reading per request would answer the same object at a
@@ -139,6 +178,9 @@ export async function loadPwaArtifacts(root: string): Promise<PwaArtifacts | und
139
178
  icons: icons?.manifestIcons ?? [],
140
179
  });
141
180
  return {
181
+ offline: app.offline,
182
+ backgroundSync: app.backgroundSync,
183
+ push: app.push,
142
184
  body: serializeWebManifest(result.manifest),
143
185
  head:
144
186
  `<link rel="manifest" href="${escapeAttribute(WEB_MANIFEST_PATH)}">` +
package/src/serve.ts CHANGED
@@ -20,11 +20,14 @@ import {
20
20
  migrate,
21
21
  } from '@ultimat3/db';
22
22
  import type { Route } from '@ultimat3/http';
23
+ import { describeRoutes } from '@ultimat3/render';
23
24
  import { createIsrController } from '@ultimat3/render/server';
24
25
  import { apiRoutes } from './api-routes';
25
26
  import { loadSignInPath } from './app-auth';
26
27
  import { loadApp } from './app-load';
27
28
  import { appManifest } from './app-manifest';
29
+ import { mountAppMcp } from './app-mcp';
30
+ import { loadAppRuntime } from './app-runtime';
28
31
  import { acceptCreatedTables } from './db-accept-created';
29
32
  import { assetRoutes } from './dev-assets';
30
33
  import { startQueue } from './dev-queue';
@@ -46,6 +49,8 @@ import { readMigrations } from './migrations';
46
49
  import { startOtlpExport } from './otlp-export';
47
50
  import { loadPwaArtifacts } from './pwa-artifacts';
48
51
  import type { RuntimeOverrides } from './runtime-overrides';
52
+ import { serviceWorkerArtifacts } from './sw-artifacts';
53
+ import { serviceWorkerRoutes } from './sw-routes';
49
54
 
50
55
  export const DEFAULT_PORT = 3000;
51
56
 
@@ -249,7 +254,22 @@ export async function releaseBoot(
249
254
  * table is the same three contributions minus the dashboard — a `/_x` in production would expose
250
255
  * the app's policy matrix, its outbox and its spans to the internet.
251
256
  */
252
- export async function serveApp(options: ServeOptions): Promise<ServedApp> {
257
+ /**
258
+ * A caller's `runtime` wins; with none, the app's own `apps/<app>/runtime.ts` is what this boot
259
+ * reads — the SAME file `x dev` reads — so the two boots compose one middleware chain, one
260
+ * rate-limit store, one ISR store, rather than a development set and a production set. Resolved
261
+ * ONCE, at each public entry, so every reader below (`startServices`, `startQueue`, the asset and
262
+ * ISR seams, the replica override) sees one object: a per-read fallback would be the partial read
263
+ * this repository names as its most repeated defect.
264
+ */
265
+ export async function withAppRuntime(options: ServeOptions): Promise<ServeOptions> {
266
+ if (options.runtime !== undefined) return options;
267
+ const runtime = await loadAppRuntime(options.root);
268
+ return runtime === undefined ? options : { ...options, runtime };
269
+ }
270
+
271
+ export async function serveApp(input: ServeOptions): Promise<ServedApp> {
272
+ const options = await withAppRuntime(input);
253
273
  const role = options.role ?? roleFromEnv(options.env);
254
274
  const runtime = await startServices(
255
275
  resolveServices(options.root, options.env),
@@ -303,8 +323,19 @@ async function bootRoles(boot: {
303
323
  // on a laptop and absent in the image is exactly the dev/prod difference this file exists to
304
324
  // prevent, and it is the one an operator cannot see without installing the app.
305
325
  const pwa = await loadPwaArtifacts(options.root);
326
+ // The worker, from the SAME route table this process is about to serve — `describeRoutes()` is
327
+ // the one projection `x.manifest.json`, `/_x`, the sitemap and `sw.js` are all built from, so a
328
+ // route added here cannot be missing from the precache manifest.
329
+ const serviceWorker =
330
+ pwa === undefined
331
+ ? undefined
332
+ : serviceWorkerArtifacts({ pwa, buildId, routes: describeRoutes(), islands });
333
+ // The app's own MCP endpoint, through the same call `x dev` makes — see `app-mcp.ts`.
334
+ const mcpMount = await mountAppMcp(options.root);
306
335
  const routes: readonly Route[] = [
307
336
  ...apiRoutes(),
337
+ ...mcpMount.routes,
338
+ ...(serviceWorker === undefined ? [] : serviceWorkerRoutes(serviceWorker)),
308
339
  ...assetRoutes({
309
340
  root: options.root,
310
341
  storage: runtime.storage,
@@ -316,7 +347,7 @@ async function bootRoles(boot: {
316
347
  ...appRoutes({
317
348
  buildId,
318
349
  resolveIsland: (file) => islands.resolverFor(file),
319
- ...(pwa === undefined ? {} : { pwaHead: pwa.head }),
350
+ ...(pwa === undefined ? {} : { pwaHead: pwa.head + (serviceWorker?.head ?? '') }),
320
351
  // Only when a store was supplied. `createIsrController` defaults to a per-process memory
321
352
  // store, so twelve replicas hold twelve of them and a purge tag regenerates one twelfth of
322
353
  // the fleet while the other eleven keep serving the page it just invalidated.
@@ -375,8 +406,11 @@ async function bootRoles(boot: {
375
406
  * holds for every other role until core's drain completes, so SIGTERM from a rolling restart takes
376
407
  * the three-phase path (stop accepting, finish in-flight, close) instead of killing a query.
377
408
  */
378
- export async function runRole(options: ServeOptions): Promise<StartedApp> {
379
- const role = options.role ?? roleFromEnv(options.env);
409
+ export async function runRole(input: ServeOptions): Promise<StartedApp> {
410
+ // The role FIRST: a bad `ROLE` is refused before the root is read at all, so a boot that was
411
+ // always going to fail creates nothing under it — the same order `resolveServices` is held to.
412
+ const role = input.role ?? roleFromEnv(input.env);
413
+ const options = await withAppRuntime(input);
380
414
  if (role === 'migrate') {
381
415
  const migrated = await runMigrations({ ...options, role });
382
416
  // The release phase has one channel — the exit code — so drift is thrown here rather than
@@ -29,12 +29,24 @@ export const isGenerated = (path: string): boolean => path.endsWith('.d.ts');
29
29
  /** Every opt-in suffix (`*.{contract,live,job,eval,e2e}.test.ts`) still ends `.test.ts`. */
30
30
  export const isTest = (path: string): boolean => /\.test\.tsx?$/.test(path);
31
31
 
32
- /** Every source file under `root`, repo-relative and deduplicated across the globs. */
32
+ /**
33
+ * Every source file under `root`, repo-relative and deduplicated across the globs — in a SORTED
34
+ * order per pattern, `As of 2026-09-05`. `Bun.Glob.scan` yields in the filesystem's `readdir`
35
+ * order, which ext4 hashes: the first offender a scan names was `index.ts` on one machine and
36
+ * `tools.ts` on a GitHub runner, so a finding's `cause` depended on which disk held the checkout
37
+ * (`workspace-graph.test.ts`, red on CI and green everywhere else). Code-unit compare, never
38
+ * `localeCompare`, for the reason `describeRoutes` states: one order on every machine.
39
+ */
33
40
  export async function* eachSourceFile(root: string): AsyncGenerator<string> {
34
41
  const seen = new Set<string>();
35
42
  for (const pattern of SOURCE_GLOBS) {
43
+ const matches: string[] = [];
36
44
  for await (const path of new Bun.Glob(pattern).scan({ cwd: root, absolute: false })) {
37
- if (isVendored(path) || seen.has(path)) continue;
45
+ if (!isVendored(path)) matches.push(path);
46
+ }
47
+ matches.sort((left, right) => (left < right ? -1 : left > right ? 1 : 0));
48
+ for (const path of matches) {
49
+ if (seen.has(path)) continue;
38
50
  seen.add(path);
39
51
  yield path;
40
52
  }
@@ -13,6 +13,7 @@ import { RENDER_MODES } from '@ultimat3/core';
13
13
  import type { Surface } from '@ultimat3/render';
14
14
  import { SURFACE_SPECS, SURFACES, surfaceAllows } from '@ultimat3/render';
15
15
  import type { JsonValue } from './output';
16
+ import { SERVICE_WORKER_PATH } from './sw-artifacts';
16
17
 
17
18
  /** Beside `.x/build-stats.json`, and written by the same call — see `readStaticReport` below. */
18
19
  export const STATIC_REPORT_FILE = join('.x', 'static-report.json');
@@ -84,6 +85,15 @@ export type StaticReport = {
84
85
  * the reader.
85
86
  */
86
87
  readonly unmeasured: readonly UnmeasuredRoute[];
88
+ /**
89
+ * The service worker's own findings: a capability declared with nothing to wire it to, and a
90
+ * precache manifest over its byte ceiling. `PrecacheManifest.warnings` had no reader anywhere in
91
+ * the tree (#390), so the ceiling was — in `wiki/Troubleshooting.md`'s own words — "a designed
92
+ * thing that is not one". Written here for `unmeasured`'s reason: `cmd-build.ts` discards a
93
+ * successful subprocess's stdout, so a warning that lives only on the in-process report reaches
94
+ * nobody. Empty for an app with no service worker.
95
+ */
96
+ readonly serviceWorkerWarnings: readonly string[];
87
97
  };
88
98
 
89
99
  /**
@@ -177,7 +187,7 @@ const isEmitted = (value: unknown): value is EmittedPage =>
177
187
  */
178
188
  export function parseStaticReport(value: unknown): StaticReport | undefined {
179
189
  if (!isRecord(value)) return undefined;
180
- const { target, out, buildId, emitted, skipped, unmeasured } = value;
190
+ const { target, out, buildId, emitted, skipped, unmeasured, serviceWorkerWarnings } = value;
181
191
  if (target !== 'static' || typeof out !== 'string' || typeof buildId !== 'string') {
182
192
  return undefined;
183
193
  }
@@ -190,7 +200,25 @@ export function parseStaticReport(value: unknown): StaticReport | undefined {
190
200
  if (unmeasured !== undefined && (!Array.isArray(unmeasured) || !unmeasured.every(isUnmeasured))) {
191
201
  return undefined;
192
202
  }
193
- return { target, out, buildId, emitted, skipped, unmeasured: unmeasured ?? [] };
203
+ // Optional on the way in for `unmeasured`'s reason, and a non-string entry drops the whole
204
+ // report for a malformed skip row's reason: a warning list with a hole in it is a build that
205
+ // says less than it measured, which is how the worker's findings went unread in the first place.
206
+ if (
207
+ serviceWorkerWarnings !== undefined &&
208
+ (!Array.isArray(serviceWorkerWarnings) ||
209
+ !serviceWorkerWarnings.every((entry) => typeof entry === 'string'))
210
+ ) {
211
+ return undefined;
212
+ }
213
+ return {
214
+ target,
215
+ out,
216
+ buildId,
217
+ emitted,
218
+ skipped,
219
+ unmeasured: unmeasured ?? [],
220
+ serviceWorkerWarnings: (serviceWorkerWarnings as readonly string[] | undefined) ?? [],
221
+ };
194
222
  }
195
223
 
196
224
  export async function writeStaticReport(root: string, report: StaticReport): Promise<string> {
@@ -232,7 +260,12 @@ export function staticReportData(report: StaticReport | undefined): Record<strin
232
260
  // which the inventory is about — `data` already carries `artifact` and the build's own id.
233
261
  return report === undefined
234
262
  ? {}
235
- : { emitted: report.emitted, skipped: report.skipped, unmeasured: report.unmeasured };
263
+ : {
264
+ emitted: report.emitted,
265
+ skipped: report.skipped,
266
+ unmeasured: report.unmeasured,
267
+ serviceWorkerWarnings: report.serviceWorkerWarnings,
268
+ };
236
269
  }
237
270
 
238
271
  /**
@@ -247,6 +280,16 @@ export function renderStaticReport(report: StaticReport): readonly string[] {
247
280
  // whose budget could not be weighed is invisible in `emitted` and, when it also rendered, in
248
281
  // `skipped` too — and it is the row `X_BUDGET_UNMEASURED` sends its reader here to read.
249
282
  ...report.unmeasured.map((route) => ['unmeasured', route.path, route.reason]),
283
+ // The service worker's own findings, in the same three columns — a precache manifest over its
284
+ // byte ceiling AND a capability declared with nothing to wire it to, which is why neither the
285
+ // field nor this label says `precache`. `sw.js` is the one artifact that keeps serving after a
286
+ // deploy is over, so both are build-time facts that have to be visible in the build's own
287
+ // output — `PrecacheManifest` computed the first and nothing read it (#390).
288
+ ...report.serviceWorkerWarnings.map((warning) => [
289
+ 'service-worker',
290
+ SERVICE_WORKER_PATH,
291
+ warning,
292
+ ]),
250
293
  ];
251
294
  const widths = [0, 1].map((index) =>
252
295
  Math.max(...rows.map((row) => (row[index] ?? '').length), 0),