@ultimat3/cli 30.3.0 → 30.5.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.
package/CLAUDE.md CHANGED
@@ -5,7 +5,7 @@ dashboard), `cli → testing` (islands, the e2e step, `x shot`'s `launchChrome`)
5
5
  this except `create-ultimate`. The reasoning behind every rule below, verbatim and dated, is
6
6
  [`docs/history/cli.md`](../../docs/history/cli.md); where the two disagree this file wins.
7
7
 
8
- Commands: `bun test packages/cli` (from the repo root — the test preload lives there),
8
+ Commands: `bun test packages/cli` (from the repo root: the preload),
9
9
  `bunx tsc --noEmit -p packages/cli/tsconfig.json`.
10
10
 
11
11
  ## Rules
@@ -51,13 +51,13 @@ Commands: `bun test packages/cli` (from the repo root — the test preload lives
51
51
  | `boundary-findings.ts` / `app-boundaries.ts` / `boundary-cuts.ts` / `package-boundaries.ts` | `package-boundaries.ts`: `X_BOUNDARY_PACKAGE_TO_APP`, its own read of `packages/`, never `readAppSources`. surface + layer rules; a finding's `fix:` is the concrete cut |
52
52
  | `app-transport.ts` / `browser-transport.ts` / `transport-calls.ts` / `import-closure.ts` / `server-barrels.ts` | `boundaries`, built in: one browser transport. Never a `guards/` file: an app must not be able to delete it. Full rule: `docs/history/cli.md` |
53
53
  | `guards.ts` / `guard-sources.ts` | an app's `guards/*.ts`, discovered, run on `boundaries`, held to the error contract; every `check(root, sources)` shares ONE walk, read and Sass compile per file (`GuardSources`) |
54
- | `templates/scaffold-guards.ts` | `SHIPPED` — the one list of the twenty-one shipped guards (`native-confirm` the newest): `x new` writes all, `x g guard <name>` writes one, `doctor-guards.ts` lists the ones an app lacks (a listing, never a finding). Full rule: `docs/history/cli.md` |
54
+ | `templates/scaffold-guards.ts` | `SHIPPED` — the one list of the twenty-one shipped guards: `x new` writes all, `x g guard <name>` writes one, `doctor-guards.ts` lists the ones an app lacks (a listing, never a finding). Full rule: `docs/history/cli.md` |
55
55
  | `error-contract.ts` / `ts-scan.ts` / `fix-scan.ts` / `fix-imports.ts` / `fix-command.ts` / `fix-path.ts` | the `errors` step: every `fix:` names a runnable command, call or existing file |
56
56
  | `workspace-checks.ts` / `test-only-shipped.ts` / `workspace-graph.ts` / `tsconfig-references.ts` / `publish-closure.ts` | `package-shape`, `filesize`. Full rule: `docs/history/cli.md` |
57
57
  | `app-permissions.ts` / `permission-grants.ts` / `app-permissions-borrowed.ts` / `app-permissions-site.ts` | `policy`: every permission granted or required is declared and granted (`X_PERMISSION_UNGRANTED`, `X_PERMISSION_BORROWED`). Full rule: `docs/history/cli.md` |
58
58
  | `job-registration.ts` | `manifest`: no job or task under a positional `anonymous-*` name (`X_JOB_UNREGISTERED`) |
59
59
  | `unscanned-admin.ts` | `manifest`: a `defineAdmin()` outside the app scan (`apps/*/src/`) is `X_ADMIN_UNSCANNED` |
60
- | `tool-gate-names.ts` | `manifest`: a gate on no tool |
60
+ | `tool-gate-names.ts` · `verify-mcp-mount.ts` | `manifest`: a gate on no tool · `expose` and no `POST /mcp` |
61
61
  | `verify-role-load.ts` / `verify-role-load-probe.ts` | `manifest`: a child imports the app as a worker does, then the rest module by module; a registration only a document-reaching module makes is `X_ROLE_LOAD_INCOMPLETE` with the module and the import the API index lacks |
62
62
  | `async-pages.ts` / `live-routes.ts` / `budgets.ts` / `budgets-unmeasured.ts` | `budgets` riders: an async `Page` with no `load`, a live read no island imports, bytes per route; an unweighed route's finding names a refused measurement actor and `defineMeasurementActor()` |
63
63
  | `budgets-freshness.ts` / `verify-budgets-build.ts` | the step builds first when stats are missing, of an old rule, or older than source: `x build --target static --no-preflight --out .x/verify-static` through `ctx.runner` |
@@ -95,7 +95,7 @@ Commands: `bun test packages/cli` (from the repo root — the test preload lives
95
95
  | `generate-feature-required.ts` | `x g query`/`backfill` with no slice holding an `entity.ts` is `X_FEATURE_REQUIRED` |
96
96
  | `generate-sign-in*.ts` | `x g sign-in`: one flag, no name; OIDC swaps only `x new`'s bytes |
97
97
  | `generate-format-written.ts` | the app's Biome over the TypeScript a run created, best effort; `lint` judges |
98
- | `templates/scaffold-mcp-json.ts` / `templates/scaffold-mcp-endpoint.ts` | `.mcp.json` (`bunx @ultimat3/cli mcp serve`, never `bunx x`) and the refusing `apps/web/mcp.ts` + `app/auth/agent-token.ts`; `scaffold-agents-md.test.ts` resolves every command `AGENTS.md` names |
98
+ | `templates/scaffold-mcp-{json,endpoint,admin}.ts` | `.mcp.json` (`bunx @ultimat3/cli mcp serve`, never `bunx x`), the refusing `apps/web/mcp.ts` + `app/auth/agent-token.ts`, `apps/admin/mcp.ts`; `scaffold-agents-md.test.ts` resolves every command `AGENTS.md` names |
99
99
 
100
100
  ### Boot (`x dev`, the container)
101
101
 
@@ -113,7 +113,7 @@ Commands: `bun test packages/cli` (from the repo root — the test preload lives
113
113
  | `app-config-load.ts` | the ONE reader of an app's `app.config.ts`: `loadAppConfig(root)` — the `config` export through core's `defineConfig`, so validated and default-merged; `undefined` with no file; no `config` object is `X_CONFIG_INVALID`. Full rule: `docs/history/cli.md` |
114
114
  | `theme-brand.ts` | the ONE reader of an app's theme: `export const brand` in `apps/web/shared/theme.ts` (ui's `defineTheme`), inlined after the surface stylesheet with its hash in `style-src`; none → nothing emitted |
115
115
  | `runtime-jobs.ts` / `runtime-realtime.ts` / `runtime-notify-retention.ts` / `runtime-cache.ts` / `runtime-purge.ts` / `runtime-replica.ts` | `app.config.ts` sections the boot obeys (`*Of(config)` over the one loaded config), and what each wires |
116
- | `runtime-jobs.ts` (queues) | `jobs.queues` ∪ registered queues; `WORKER_QUEUES` = the EXACT set, each entry in that union or `X_WORKER_QUEUE_UNKNOWN`; `queue-unserved` warns the rest |
116
+ | `runtime-jobs.ts` (queues) | `jobs.queues` ∪ registered; `WORKER_QUEUES` = the EXACT set, each in that union or `X_WORKER_QUEUE_UNKNOWN`; `-unserved.ts`: what no live worker serves |
117
117
  | `serve-retire.ts` / `dev-restart-retire.ts` | the worker retire, `stop()` claiming and aborting nothing: SIGUSR2 under `runRole`, and before a supervised `x dev` restart's drain (#677). Full rule: `docs/history/cli.md` |
118
118
  | `role-start.ts` / `role-start-types.ts` / `role-realtime.ts` / `role-start-devices.ts` | `--role` selection and start/stop for `web`, `sync`, `worker`, `scheduler`; `realtime.enabled: false` drops `sync` and `replicator`; device mounts, swapped per path on save |
119
119
  | `role-wake.ts` | the worker's LISTEN session (`@ultimat3/jobs`' `startQueueWake`) — worker role only, only over a client that `canListen` — and `queue_wake_live` |
@@ -131,7 +131,7 @@ Commands: `bun test packages/cli` (from the repo root — the test preload lives
131
131
  | `island-bundle.ts` / `island-store.ts` / `island-sources.ts` / `island-realtime.ts` / `island-realtime-state.ts` / `island-runtime.ts` / `solid-loader.ts` | islands: one `Bun.build` each, source-addressed; the image build writes a verified store, and an edit since the build is a rebuild, never the old chunk |
132
132
  | `local-cli.ts` / `local-cli-handoff.ts` | a global `x` hands over to the app's own CLI (`local-cli.ts` decides whether; `x upgrade` alone stays when the app's is strictly older — `upgrade-handover.test.ts`) |
133
133
  | `serve-prebuilt.ts` / `serve-prebuilt-paths.ts` | the image's prebuilt store (`node_modules/.cache/ultimate`, never `.x/` — state, tmpfs): every role adopts its compiled stylesheets before importing the app; a boot that built an island or ran Sass logs `X_IMAGE_NOT_PREBUILT` once |
134
- | `dev-*.ts` | `x dev` only: dashboard sources, traces, the N+1 ledger, the watcher, the reload, the lock, the port |
134
+ | `dev-*.ts` | `x dev` only: dashboard sources, traces, the N+1 ledger, watcher, reload, lock, port |
135
135
  | `cmd-stop.ts` / `cmd-stop-spec.ts` | `x stop`: the lock's pid only (its recorded start time must match), SIGTERM, then wait for pid gone and lock clear; Windows clears the lock (`drained: false`); `X_DEV_STOP_TIMEOUT` |
136
136
  | `dev-dashboard-guard.ts` / `loopback-host.ts` | `/_x` answers a loopback `Host` only (`X_DEV_HOST_REFUSED`, 421 — DNS rebinding); the SQL panel runs on a same-origin `POST /_x/db` only, through admin's `readOnlySql` |
137
137
  | `doctor-pooler.ts` / `runtime-statement-bound.ts` | the applied bound on demand (`data.statementBound`), a pooler's refusal as the one database finding; the one boot line, total. `startDb` hands `poolerFromEnv(env)` to both pools |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/cli",
3
- "version": "30.3.0",
3
+ "version": "30.5.0",
4
4
  "description": "The `x` binary: new, dev, build, verify, generate, db, mcp, doctor, deploy",
5
5
  "keywords": [
6
6
  "ultimate",
@@ -53,33 +53,33 @@
53
53
  },
54
54
  "dependencies": {
55
55
  "@babel/core": "^8.0.7",
56
- "@ultimat3/action": "30.3.0",
57
- "@ultimat3/admin": "30.3.0",
58
- "@ultimat3/ai": "30.3.0",
59
- "@ultimat3/auth": "30.3.0",
60
- "@ultimat3/cache": "30.3.0",
61
- "@ultimat3/core": "30.3.0",
62
- "@ultimat3/db": "30.3.0",
63
- "@ultimat3/entity": "30.3.0",
64
- "@ultimat3/flags": "30.3.0",
65
- "@ultimat3/http": "30.3.0",
66
- "@ultimat3/i18n": "30.3.0",
67
- "@ultimat3/jobs": "30.3.0",
68
- "@ultimat3/mail": "30.3.0",
69
- "@ultimat3/manifest": "30.3.0",
70
- "@ultimat3/mcp": "30.3.0",
71
- "@ultimat3/money": "30.3.0",
72
- "@ultimat3/notify": "30.3.0",
73
- "@ultimat3/policy": "30.3.0",
74
- "@ultimat3/pwa": "30.3.0",
75
- "@ultimat3/query": "30.3.0",
76
- "@ultimat3/realtime": "30.3.0",
77
- "@ultimat3/render": "30.3.0",
78
- "@ultimat3/schema": "30.3.0",
79
- "@ultimat3/seo": "30.3.0",
80
- "@ultimat3/storage": "30.3.0",
81
- "@ultimat3/testing": "30.3.0",
82
- "@ultimat3/time": "30.3.0",
56
+ "@ultimat3/action": "30.5.0",
57
+ "@ultimat3/admin": "30.5.0",
58
+ "@ultimat3/ai": "30.5.0",
59
+ "@ultimat3/auth": "30.5.0",
60
+ "@ultimat3/cache": "30.5.0",
61
+ "@ultimat3/core": "30.5.0",
62
+ "@ultimat3/db": "30.5.0",
63
+ "@ultimat3/entity": "30.5.0",
64
+ "@ultimat3/flags": "30.5.0",
65
+ "@ultimat3/http": "30.5.0",
66
+ "@ultimat3/i18n": "30.5.0",
67
+ "@ultimat3/jobs": "30.5.0",
68
+ "@ultimat3/mail": "30.5.0",
69
+ "@ultimat3/manifest": "30.5.0",
70
+ "@ultimat3/mcp": "30.5.0",
71
+ "@ultimat3/money": "30.5.0",
72
+ "@ultimat3/notify": "30.5.0",
73
+ "@ultimat3/policy": "30.5.0",
74
+ "@ultimat3/pwa": "30.5.0",
75
+ "@ultimat3/query": "30.5.0",
76
+ "@ultimat3/realtime": "30.5.0",
77
+ "@ultimat3/render": "30.5.0",
78
+ "@ultimat3/schema": "30.5.0",
79
+ "@ultimat3/seo": "30.5.0",
80
+ "@ultimat3/storage": "30.5.0",
81
+ "@ultimat3/testing": "30.5.0",
82
+ "@ultimat3/time": "30.5.0",
83
83
  "babel-preset-solid": "^1.9.16"
84
84
  }
85
85
  }
package/src/app-mcp.ts CHANGED
@@ -5,7 +5,8 @@
5
5
  // every app ever scaffolded (measured 2026-09-05). The contract is one file: `apps/<app>/mcp.ts`
6
6
  // exports `mcp` — one `AppMcp`, or an array of them, one per population (customers, staff,
7
7
  // affiliates) — this module finds it, and both boots mount what it carries: EVERY endpoint at its
8
- // own `defineAppMcp({ path })`, the one path its RFC 9728 metadata and its 401 also name.
8
+ // own `defineAppMcp({ path })`, the one path its RFC 9728 metadata and its 401 also name — and
9
+ // every app directory's file mounts, so an admin surface's `apps/admin/mcp.ts` stands beside the web one.
9
10
 
10
11
  // why: a directory's existence — `Bun.file().exists()` answers for files, and `apps/` is a directory.
11
12
  import { existsSync } from 'node:fs';
@@ -16,6 +17,7 @@ import type { Route } from '@ultimat3/http';
16
17
  import { jsonResponse, originListed, selfOrigin } from '@ultimat3/http';
17
18
  import {
18
19
  type AppMcp,
20
+ assertIncludeCurrent,
19
21
  assertRecipeLinks,
20
22
  McpAppUnmountedError,
21
23
  McpPathDuplicateError,
@@ -75,22 +77,44 @@ function endpointsOf(exported: unknown): readonly AppMcp[] | undefined {
75
77
  return exported.every(isAppMcp) ? (exported as readonly AppMcp[]) : undefined;
76
78
  }
77
79
 
78
- /** Every `apps/<app>/mcp.ts`, app-root-relative and sorted, so two apps answer in one order. */
80
+ /** The web surface's file: endpoint #0 — the default — wherever its directory sorts. */
81
+ const DEFAULT_MCP_FILE = 'apps/web/mcp.ts';
82
+
83
+ /**
84
+ * Every `apps/<app>/mcp.ts`, app-root-relative: the web surface's first, then the rest sorted, so
85
+ * two machines answer in one order and an `apps/admin/mcp.ts` never takes the default endpoint.
86
+ */
79
87
  async function candidates(root: string): Promise<readonly string[]> {
80
88
  // A root with no `apps/` is an app with no MCP file, never a boot failure — the scan's ENOENT
81
89
  // is answered as "none", and the warning below says which file to write.
82
90
  if (!existsSync(join(root, 'apps'))) return [];
83
91
  const files: string[] = [];
84
92
  for await (const file of new Bun.Glob(APP_MCP_GLOB).scan({ cwd: root })) files.push(file);
85
- return files.sort();
93
+ files.sort();
94
+ return files.includes(DEFAULT_MCP_FILE)
95
+ ? [DEFAULT_MCP_FILE, ...files.filter((file) => file !== DEFAULT_MCP_FILE)]
96
+ : files;
86
97
  }
87
98
 
88
99
  const NOTHING: AppMcpMount = { routes: [], path: null, paths: [], warning: undefined };
89
100
 
101
+ /** One mount file's endpoints. */
102
+ interface MountFile {
103
+ readonly file: string;
104
+ readonly endpoints: readonly AppMcp[];
105
+ }
106
+
90
107
  /**
91
108
  * The routes to mount, or the reason there are none. Pure over the filesystem it is pointed at;
92
109
  * `mountAppMcp` below is the one place the warning becomes a log line.
93
110
  *
111
+ * EVERY app directory's `mcp.ts` mounts — an admin surface's `apps/admin/mcp.ts` beside the web
112
+ * one — on one route table. The first sorted file used to win alone, so an admin surface's file
113
+ * silently took the app's `/mcp` away. Each is imported HERE, after the boot scan: the scan
114
+ * evaluates `apps/<app>/mcp.ts` after every `apps/<app>/{site,app,api,shared}/` module, so an
115
+ * `include: 'exposed'` built at its top level sees every primitive — and one built earlier, in a
116
+ * module it re-exports, is `X_MCP_INCLUDE_EARLY` rather than a catalog missing tools.
117
+ *
94
118
  * `meta.auth: 'public'` and `enforcedBy: 'handler'` — the http pipeline must not pre-judge:
95
119
  * `mcp.route.handle` is the one evaluation, and it reads `Authorization: Bearer` through the
96
120
  * `resolveToken` the app gave `defineAppMcp`, then decides per tool through the same policy every
@@ -100,67 +124,78 @@ const NOTHING: AppMcpMount = { routes: [], path: null, paths: [], warning: undef
100
124
  export async function appMcpMount(root: string): Promise<AppMcpMount> {
101
125
  if (!(await exposed(root))) return NOTHING;
102
126
  const files = await candidates(root);
103
- const fallbackFile = 'apps/web/mcp.ts';
127
+ const mounted: MountFile[] = [];
104
128
  for (const file of files) {
105
129
  const module = (await import(join(root, file))) as Record<string, unknown>;
106
130
  const endpoints = endpointsOf(module[APP_MCP_EXPORT]);
107
131
  if (endpoints === undefined) continue;
108
- // Every endpoint is built by now: an error's declared `docs://recipes/` link must name a recipe
109
- // one of them serves (`X_MCP_RECIPE_UNKNOWN`), the union and never one endpoint alone.
110
- assertRecipeLinks(servedRecipes());
111
- return mountEndpoints(endpoints, file);
132
+ for (const endpoint of endpoints) assertIncludeCurrent(endpoint, file);
133
+ mounted.push({ file, endpoints });
112
134
  }
113
- return {
114
- ...NOTHING,
115
- warning: new McpAppUnmountedError({
116
- reason: 'missing',
117
- path: UNROUTED_MCP_PATH,
118
- file: files[0] ?? fallbackFile,
119
- }),
120
- };
135
+ if (mounted.length === 0) {
136
+ return {
137
+ ...NOTHING,
138
+ warning: new McpAppUnmountedError({
139
+ reason: 'missing',
140
+ path: UNROUTED_MCP_PATH,
141
+ file: files[0] ?? DEFAULT_MCP_FILE,
142
+ }),
143
+ };
144
+ }
145
+ // Every endpoint is built by now: an error's declared `docs://recipes/` link must name a recipe
146
+ // one of them serves (`X_MCP_RECIPE_UNKNOWN`), the union and never one endpoint alone.
147
+ assertRecipeLinks(servedRecipes());
148
+ return mountEndpoints(mounted);
121
149
  }
122
150
 
123
151
  /**
124
- * One file's endpoints onto one route table. An endpoint built without `resolveToken` has no
152
+ * Every file's endpoints onto one route table. An endpoint built without `resolveToken` has no
125
153
  * route: it is skipped and the FIRST such is the warning, so one unfinished population never takes
126
154
  * the others down with it. Two endpoints on one route is thrown — see `McpPathDuplicateError`.
155
+ * Numbering: `#n` in a message is the endpoint's index in its own file's export; only the very
156
+ * first endpoint mounted is the default (#0 of the route names and the root metadata document).
127
157
  */
128
- function mountEndpoints(endpoints: readonly AppMcp[], file: string): AppMcpMount {
158
+ function mountEndpoints(files: readonly MountFile[]): AppMcpMount {
129
159
  const routes: Route[] = [];
130
160
  const paths: string[] = [];
131
- const claimed = new Map<string, number>();
161
+ const claimed = new Map<string, { readonly file: string; readonly index: number }>();
132
162
  let warning: McpAppUnmountedError | undefined;
133
- endpoints.forEach((endpoint, index) => {
134
- const route = endpoint.route;
135
- if (route === undefined) {
136
- warning ??= new McpAppUnmountedError({
137
- reason: 'no-route',
138
- path: UNROUTED_MCP_PATH,
139
- file,
140
- ...(index === 0 ? {} : { endpoint: index }),
141
- });
142
- return;
143
- }
144
- // The descriptor's own path — the one its metadata, its `resourceUrl` and its 401 were built
145
- // over. A mount path from anywhere else is a resource advertised where nothing answers.
146
- const path = route.path;
147
- for (const mounted of endpointRoutes(route, path, index)) {
148
- const key = `${mounted.method} ${mounted.path}`;
149
- const first = claimed.get(key);
150
- if (first !== undefined) {
151
- throw new McpPathDuplicateError({
152
- method: mounted.method,
153
- path: mounted.path,
154
- endpoints: [first, index],
163
+ let mountedIndex = 0;
164
+ for (const { file, endpoints } of files) {
165
+ endpoints.forEach((endpoint, index) => {
166
+ const route = endpoint.route;
167
+ if (route === undefined) {
168
+ warning ??= new McpAppUnmountedError({
169
+ reason: 'no-route',
170
+ path: UNROUTED_MCP_PATH,
155
171
  file,
172
+ ...(index === 0 ? {} : { endpoint: index }),
156
173
  });
174
+ return;
157
175
  }
158
- claimed.set(key, index);
159
- routes.push(mounted);
160
- }
161
- paths.push(path);
162
- });
163
- return { routes, path: endpoints[0]?.route?.path ?? null, paths, warning };
176
+ // The descriptor's own path — the one its metadata, its `resourceUrl` and its 401 were built
177
+ // over. A mount path from anywhere else is a resource advertised where nothing answers.
178
+ const path = route.path;
179
+ for (const one of endpointRoutes(route, path, mountedIndex)) {
180
+ const key = `${one.method} ${one.path}`;
181
+ const first = claimed.get(key);
182
+ if (first !== undefined) {
183
+ throw new McpPathDuplicateError({
184
+ method: one.method,
185
+ path: one.path,
186
+ endpoints: [first.index, index],
187
+ file,
188
+ firstFile: first.file,
189
+ });
190
+ }
191
+ claimed.set(key, { file, index });
192
+ routes.push(one);
193
+ }
194
+ paths.push(path);
195
+ mountedIndex += 1;
196
+ });
197
+ }
198
+ return { routes, path: paths[0] ?? null, paths, warning };
164
199
  }
165
200
 
166
201
  type Descriptor = NonNullable<AppMcp['route']>;
@@ -15,7 +15,7 @@ export const upgradeSpec: CommandSpec = {
15
15
  name: 'to',
16
16
  type: 'string',
17
17
  summary:
18
- 'an exact release inside the next major, or the higher version a mixed set was told to align to (default: the next major’s newest)',
18
+ 'an exact release inside the next major; over a mixed set, inside the lowest pins’ next major, and only those pins move (default: the next major’s newest)',
19
19
  },
20
20
  {
21
21
  name: 'dry-run',
@@ -12,7 +12,7 @@ import {
12
12
  renderFixShellArg,
13
13
  } from '@ultimat3/core';
14
14
  import type { ExerciseRecord } from '@ultimat3/testing';
15
- import { planSweepFindings, tenantProofFindings } from '@ultimat3/testing';
15
+ import { crashEffectPlace, planSweepFindings, tenantProofFindings } from '@ultimat3/testing';
16
16
  import type { Finding } from './output';
17
17
  import { renderTable } from './table';
18
18
 
@@ -44,6 +44,20 @@ export const exerciseCounts = (records: readonly ExerciseRecord[]): ExerciseCoun
44
44
  };
45
45
  };
46
46
 
47
+ /**
48
+ * A 5xx's own words — the cause testing recorded (credential values already masked there) and the
49
+ * fields a validation failure names. The statement is inside the cause, so it is not said twice.
50
+ */
51
+ const defectOf = (record: ExerciseRecord): string => {
52
+ const defect = record.defect;
53
+ if (defect === undefined) return '';
54
+ const fields =
55
+ defect.fields === undefined
56
+ ? ''
57
+ : ` at ${defect.fields.map((field) => renderCauseValue(field)).join(', ')}`;
58
+ return ` — cause: ${renderCauseValue(defect.cause)}${fields}`;
59
+ };
60
+
47
61
  /**
48
62
  * `X_CONTRACT_EXERCISE_UNREACHED`, `@ultimat3/action`'s code for the same silence one level up: a
49
63
  * primitive no run got to as an allowed actor. The fix is the override the pass printed, or the
@@ -56,7 +70,7 @@ const unreachedFinding = (record: ExerciseRecord): Finding => {
56
70
  const why = record.reason === undefined ? '' : ` (${renderCauseValue(record.reason)})`;
57
71
  return {
58
72
  code: 'X_CONTRACT_EXERCISE_UNREACHED',
59
- cause: `${record.kind} ${renderCauseValue(record.name)} never reached its handler as an allowed actor in the exercise pass${stopped}${why}`,
73
+ cause: `${record.kind} ${renderCauseValue(record.name)} never reached its handler as an allowed actor in the exercise pass${stopped}${why}${defectOf(record)}`,
60
74
  fix:
61
75
  record.fix ??
62
76
  `defineExercise({ ${override} }) # paste the entry into ${EXERCISE_FILE}, or { off: '<why>' }; then: x exercise ${name} --json`,
@@ -75,27 +89,29 @@ const crashFindingsOf = (record: ExerciseRecord): readonly CrashFinding[] =>
75
89
 
76
90
  /**
77
91
  * `X_JOB_CRASH_DIVERGED`, testing's code: a reached job, killed at a step boundary, made an effect
78
- * more often than a clean run — it sits outside every `step.run`, so a retry makes it again. One
79
- * finding per effect and the step it follows, every boundary that showed it named in the cause.
92
+ * a different number of times than a clean run. Where it ran — outside every `step.run`, or inside
93
+ * one whose body re-runs — is testing's `crashEffectPlace`, the phrase its error uses too. One
94
+ * finding per effect and place, every boundary that showed it named in the cause.
80
95
  */
81
96
  const crashFindings = (record: ExerciseRecord): readonly Finding[] => {
82
97
  const groups = new Map<string, CrashFinding[]>();
83
98
  for (const one of crashFindingsOf(record)) {
84
- const key = `${one.effect}\u0000${one.after ?? ''}`;
99
+ const key = `${one.effect}\u0000${one.after ?? ''}\u0000${one.inside ?? ''}`;
85
100
  groups.set(key, [...(groups.get(key) ?? []), one]);
86
101
  }
87
102
  const job = renderFixShellArg(record.name, '<job>');
88
103
  return [...groups.values()].map((group): Finding => {
89
104
  const [first] = group as [CrashFinding, ...CrashFinding[]];
90
- const where =
91
- first.after === undefined
92
- ? 'before the first step'
93
- : `after step ${JSON.stringify(first.after)}`;
105
+ const { place, remedy } = crashEffectPlace(first);
106
+ const how =
107
+ first.killed > first.clean
108
+ ? 'the retry after a crash makes it again'
109
+ : 'the crash skipped it';
94
110
  const at = group.map((one) => JSON.stringify(one.boundary)).join(', ');
95
111
  return {
96
112
  code: 'X_JOB_CRASH_DIVERGED',
97
- cause: `job ${renderCauseValue(record.name)} killed at ${at} made ${renderCauseValue(first.effect)} ${first.killed}× against ${first.clean}× in a clean run: it runs outside every step, ${where}, so the retry after a crash makes it again`,
98
- fix: `x exercise ${job} --json # in job(${renderFixLiteral(record.name, '<job>')}): wrap the ${renderFixLiteral(first.effect, '<effect>')} made ${where} in step.run('<name>', async () => …)`,
113
+ cause: `job ${renderCauseValue(record.name)} killed at ${at} made ${renderCauseValue(first.effect)} ${first.killed}× against ${first.clean}× in a clean run: ${place}, so ${how}`,
114
+ fix: `x exercise ${job} --json # in job(${renderFixLiteral(record.name, '<job>')}), for ${renderFixLiteral(first.effect, '<effect>')}: ${remedy}`,
99
115
  docs: ERROR_DOCS_URL,
100
116
  at: `${record.kind} ${record.name}`,
101
117
  };
@@ -189,7 +189,9 @@ async function buildAll(
189
189
  // gets the same rule (`island-package-dedupe.ts`): a nested copy is a second module where no
190
190
  // symlink folds it, which is every `file:` install on Windows.
191
191
  plugins: [
192
- ...(runtime === undefined ? [] : [islandRealtimePlugin(root, runtime, follows)]),
192
+ ...(runtime === undefined
193
+ ? []
194
+ : [islandRealtimePlugin(root, runtime, follows, live.length === files.length)]),
193
195
  solidDedupePlugin(root),
194
196
  ...frameworkDedupePlugins(root),
195
197
  solidJsxPlugin,
@@ -5,8 +5,9 @@
5
5
  // that reads the record store or the outbox also waits for the page's restore before it mounts
6
6
  // (#506); one that only follows the socket does not (`island-realtime-state.ts`).
7
7
 
8
- // why: Bun ships no path API; the entry is joined to the root, the runtime named by its basename.
9
- import { basename, join } from 'node:path';
8
+ // why: Bun ships no path API; the entry is joined to the root, the runtime named by its basename,
9
+ // core's transport found beside the core module importing it.
10
+ import { basename, dirname, join } from 'node:path';
10
11
  import type { BunPlugin } from 'bun';
11
12
  import { followsOnly, type IslandRealtime } from './island-realtime-state';
12
13
  import { RUNTIME_CHUNK_SPECIFIER, type RuntimeChunk } from './island-runtime';
@@ -29,6 +30,10 @@ const INSTALL = 'ultimate:island-realtime';
29
30
  const RESTORE = 'ultimate:island-restore';
30
31
  const MODULE = 'ultimate:island-module:';
31
32
  const WAIT = 'ultimate:island-page-wait';
33
+ const CORE_TRANSPORT = 'ultimate:island-core-transport';
34
+ /** What core's own modules import the transport by — the one specifier the swap answers. */
35
+ const TRANSPORT_SPECIFIER = /^\.\/client-transport$/;
36
+ const TRANSPORT_MODULE = 'transport:';
32
37
 
33
38
  /**
34
39
  * Whether the island's own graph value-imports realtime. Relative specifiers only, which is the
@@ -108,6 +113,41 @@ const restoreSource = (): string =>
108
113
  `import { holdFirstPaint } from '${REALTIME}';\n` +
109
114
  `await holdFirstPaint();\n`;
110
115
 
116
+ /**
117
+ * Core's `client-transport.ts`, as a realtime island's bundle sees it: `clientTransport` sends
118
+ * through the page runtime's copy (`runtimeSend`), and `settleAnswer` — `clientStream`'s, which
119
+ * the runtime does not expose — is the real one, tree-shaken to what it needs. The island awaited
120
+ * the runtime before its module body ran, so a send can never find it absent. Measured on
121
+ * `examples/dummy`'s run console, As of 2026-11-07: see `island-transport.test.ts`'s header.
122
+ */
123
+ const transportSource = (): string =>
124
+ `import { runtimeSend } from '${WAIT}';\n` +
125
+ `export { settleAnswer } from '${CORE_TRANSPORT}';\n` +
126
+ `export function clientTransport(req) {\n return runtimeSend(req);\n}\n`;
127
+
128
+ /** Whether `file` is a source file of an installed `@ultimat3/core` — any copy of it. */
129
+ async function inCore(file: string, cache: Map<string, Promise<boolean>>): Promise<boolean> {
130
+ const dir = dirname(file);
131
+ let known = cache.get(dir);
132
+ if (known === undefined) {
133
+ known = packageNameAbove(dir).then((name) => name === '@ultimat3/core');
134
+ cache.set(dir, known);
135
+ }
136
+ return await known;
137
+ }
138
+
139
+ async function packageNameAbove(start: string): Promise<string | undefined> {
140
+ for (let dir = start; dir !== dirname(dir); dir = dirname(dir)) {
141
+ const manifest = Bun.file(join(dir, 'package.json'));
142
+ if (!(await manifest.exists())) continue;
143
+ const parsed: unknown = await manifest.json();
144
+ const name =
145
+ typeof parsed === 'object' && parsed !== null ? Reflect.get(parsed, 'name') : undefined;
146
+ return typeof name === 'string' ? name : undefined;
147
+ }
148
+ return undefined;
149
+ }
150
+
111
151
  /**
112
152
  * `runtime` names the realtime every bare `@ultimat3/realtime` of the build resolves to — the copy
113
153
  * the runtime chunk was built against, resolved from the first realtime island's directory. One
@@ -115,15 +155,36 @@ const restoreSource = (): string =>
115
155
  * sets is only read by hooks from the same module instance, and the page object it reads is the
116
156
  * shape that copy's runtime installs. `follows` is THIS build's islands that restore nothing: an
117
157
  * island absent from it waits for the restore, which is the answer that can never paint stale.
158
+ *
159
+ * `sendThroughRuntime` swaps core's transport for the runtime's (`transportSource`). Only for a
160
+ * build whose every entry is a realtime island: one split build shares a module between its
161
+ * entries, and a plain island never waits for a runtime to send through.
118
162
  */
119
163
  export function islandRealtimePlugin(
120
164
  root: string,
121
165
  runtime: RuntimeChunk,
122
166
  follows: ReadonlySet<string> = new Set(),
167
+ sendThroughRuntime = false,
123
168
  ): BunPlugin {
169
+ const cores = new Map<string, Promise<boolean>>();
124
170
  return {
125
171
  name: 'ultimate-island-realtime',
126
172
  setup(build) {
173
+ if (sendThroughRuntime) {
174
+ // Only core's own `./client-transport` — an app file of that name is the app's. The real
175
+ // file rides in the namespace path, so the module's SOURCE names no path on this machine.
176
+ build.onResolve({ filter: TRANSPORT_SPECIFIER }, async (args) =>
177
+ args.namespace === 'file' && (await inCore(args.importer, cores))
178
+ ? {
179
+ path: `${TRANSPORT_MODULE}${join(dirname(args.importer), 'client-transport.ts')}`,
180
+ namespace: NAMESPACE,
181
+ }
182
+ : undefined,
183
+ );
184
+ build.onResolve({ filter: /^ultimate:island-core-transport$/ }, (args) => ({
185
+ path: args.importer.slice(TRANSPORT_MODULE.length),
186
+ }));
187
+ }
127
188
  build.onResolve({ filter: /^ultimate:island-entry:/ }, (args) => ({
128
189
  path: `entry:${args.path.slice(REALTIME_ISLAND_ENTRY.length)}`,
129
190
  namespace: NAMESPACE,
@@ -158,7 +219,9 @@ export function islandRealtimePlugin(
158
219
  )
159
220
  : args.path === 'restore'
160
221
  ? restoreSource()
161
- : installSource(runtime.url),
222
+ : args.path.startsWith(TRANSPORT_MODULE)
223
+ ? transportSource()
224
+ : installSource(runtime.url),
162
225
  loader: 'js',
163
226
  }));
164
227
  },
package/src/role-start.ts CHANGED
@@ -26,6 +26,7 @@ import {
26
26
  outboxRelay,
27
27
  postgresLeaseLeader,
28
28
  postgresSchedulerState,
29
+ registeredJobs,
29
30
  } from '@ultimat3/jobs';
30
31
  import type { SyncWs } from '@ultimat3/realtime/server';
31
32
  import { errorPageHook } from './error-pages';
@@ -45,6 +46,7 @@ import { prepareSync, type RunningSync, syncReadsChanges } from './role-sync';
45
46
  import { startWorkerWake } from './role-wake';
46
47
  import { devHooks } from './runtime-hooks';
47
48
  import { workerOptionsFor } from './runtime-jobs';
49
+ import { reportUnservedQueues } from './runtime-jobs-unserved';
48
50
  import { startLiveFeed } from './runtime-live-feed';
49
51
  import { type RunningServices, releaseOrThrow } from './runtime-services';
50
52
  import { inlineScriptSources } from './script-csp';
@@ -335,16 +337,30 @@ export async function startRoles(options: StartRolesOptions): Promise<RunningRol
335
337
  prepared === null ? null : await prepared.listen(server === null ? null : server.url());
336
338
  if (sync !== null) releaseSync = sync.stop;
337
339
 
338
- const worker = selected.includes('worker')
339
- ? jobWorker({
340
- driver: options.runtime.jobs,
341
- build: options.buildId,
342
- context: () => ctxOf({ role: 'worker', buildId: options.buildId }),
343
- ...workerOptionsFor(options.runtime.workerConfig),
344
- })
340
+ const workerOptions = selected.includes('worker')
341
+ ? workerOptionsFor(options.runtime.workerConfig)
345
342
  : null;
343
+ const worker =
344
+ workerOptions === null
345
+ ? null
346
+ : jobWorker({
347
+ driver: options.runtime.jobs,
348
+ build: options.buildId,
349
+ context: () => ctxOf({ role: 'worker', buildId: options.buildId }),
350
+ ...workerOptions,
351
+ });
346
352
  worker?.start();
347
353
  if (worker !== null) started.push(() => worker.stop('x dev stopped'));
354
+ // An exact worker (`WORKER_QUEUES`) names the registered queues NO live worker serves — after
355
+ // its start, so the registry holds its own row and the rest of the fleet's. Never rejects.
356
+ if (workerOptions !== null && options.runtime.workerConfig?.exact === true) {
357
+ const introspect = options.runtime.jobs.introspect;
358
+ void reportUnservedQueues({
359
+ served: workerOptions.queues ?? [],
360
+ registered: registeredJobs().map((handle) => handle.queue),
361
+ workers: introspect === undefined ? undefined : () => introspect.workers(),
362
+ });
363
+ }
348
364
 
349
365
  // The half of the transactional outbox that makes a staged row a running job. Without it
350
366
  // `handle.enqueue()` inside a transaction writes to `x_outbox` and nothing ever reads it back
@@ -0,0 +1,46 @@
1
+ // An exact worker's (`WORKER_QUEUES`) one boot line about the registered queues it leaves: warned
2
+ // only for those NO live worker in the registry serves. Another Deployment serving the rest is the
3
+ // whole point of the split, and a warning at every boot of a correct fleet is noise that hides the
4
+ // one boot where a queue really has no worker.
5
+
6
+ import { logger, renderThrowable } from '@ultimat3/core';
7
+ import type { WorkerRecord } from '@ultimat3/jobs';
8
+
9
+ export interface UnservedQueuesInput {
10
+ /** What this worker claims. */
11
+ readonly served: readonly string[];
12
+ /** Every queue a registered job is enqueued on. */
13
+ readonly registered: readonly string[];
14
+ /** The fleet's live rows (`introspect.workers`); `undefined` on a driver with no registry. */
15
+ readonly workers: (() => Promise<readonly WorkerRecord[]>) | undefined;
16
+ }
17
+
18
+ const FIX =
19
+ 'x jobs list --json — every queue listed with no worker needs one: WORKER_QUEUES=<queue>,<queue> on another worker Deployment, or add it to this one';
20
+
21
+ /** Never rejects: a boot line is advice, and a failed read of it must not fail the boot. */
22
+ export async function reportUnservedQueues(input: UnservedQueuesInput): Promise<void> {
23
+ const left = [...new Set(input.registered)].filter((q) => !input.served.includes(q)).sort();
24
+ if (left.length === 0) return;
25
+ // Unknowable here: said at info, because the split may well be served elsewhere.
26
+ const unknown = (reason: string): void =>
27
+ logger.info('jobs.worker.queue-unserved', {
28
+ queues: left,
29
+ served: input.served,
30
+ checked: false,
31
+ reason,
32
+ fix: FIX,
33
+ });
34
+ if (input.workers === undefined) return unknown('this driver keeps no worker registry');
35
+ let rows: readonly WorkerRecord[];
36
+ try {
37
+ rows = await input.workers();
38
+ } catch (error) {
39
+ return unknown(renderThrowable(error));
40
+ }
41
+ // A draining worker is leaving: it claims nothing new, so it serves no queue.
42
+ const covered = new Set(rows.filter((one) => one.draining !== true).flatMap((one) => one.queues));
43
+ const queues = left.filter((queue) => !covered.has(queue));
44
+ if (queues.length === 0) return;
45
+ logger.warn('jobs.worker.queue-unserved', { queues, served: input.served, fix: FIX });
46
+ }