@ultimat3/cli 2.0.0 → 3.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.
package/src/cmd-verify.ts CHANGED
@@ -31,6 +31,7 @@ import { msg } from './messages';
31
31
  import type { CommandResult, Finding, StepResult } from './output';
32
32
  import { findingFrom } from './output';
33
33
  import type { ParsedArgs } from './parse';
34
+ import { WORKER_CEILING, WORKER_FLOOR, WORKER_OVERSUBSCRIBE } from './test-workers';
34
35
  import {
35
36
  floorProblemFindings,
36
37
  floorRequires,
@@ -44,6 +45,9 @@ import { TEST_STEPS } from './verify-tests';
44
45
  import { checkFileSizes, checkPackageShape, hasWorkspacePackages } from './workspace-checks';
45
46
 
46
47
  /** The whole contract, in cost order. Every check the framework knows how to make lives here. */
48
+ /** The one file that makes the `roadmap` step answerable, and therefore what `applies` reads. */
49
+ const ROADMAP_FILE = join('docs', 'idea', '14-roadmap.md');
50
+
47
51
  export const VERIFY_STEPS: readonly VerifyStep[] = [
48
52
  {
49
53
  name: 'typecheck',
@@ -224,8 +228,11 @@ export const VERIFY_STEPS: readonly VerifyStep[] = [
224
228
  name: 'roadmap',
225
229
  summary: "every roadmap milestone's status marker matches what is actually on disk",
226
230
  // A generated app ships no `docs/idea/14-roadmap.md` — only the framework monorepo does, so
227
- // only a host that registers this check has anything for the step to verify.
228
- applies: async (ctx) => ctx.hostChecks?.roadmap !== undefined,
231
+ // the FILE is what decides. It keyed on `ctx.hostChecks?.roadmap` until `As of 2026-08`, which
232
+ // is a fact about the CALL: a caller of the exported `runVerify(VERIFY_STEPS, ctx)` passing no
233
+ // `hostChecks`, in a repo whose committed `x.verify.json` names `roadmap`, got
234
+ // `X_VERIFY_SUITE_VANISHED` — whose `fix:` is the command that had just failed.
235
+ applies: async (ctx) => existsSync(join(ctx.root, ROADMAP_FILE)),
229
236
  run: async (ctx) => fromFindings(await hostFindings(ctx, 'roadmap')),
230
237
  },
231
238
  ];
@@ -390,7 +397,7 @@ export const verifyCommand: CliCommand = {
390
397
  {
391
398
  name: 'workers',
392
399
  type: 'string',
393
- summary: 'test processes per parallel step (default: CPUs - 1, max 8)',
400
+ summary: `test processes per parallel step (default: ${WORKER_OVERSUBSCRIBE}x CPUs, min ${WORKER_FLOOR}, max ${WORKER_CEILING})`,
394
401
  },
395
402
  ],
396
403
  },
@@ -405,13 +412,24 @@ export const verifyCommand: CliCommand = {
405
412
  },
406
413
  };
407
414
 
408
- /** `x test --workers` refuses the same values for the same reason — and now through the same
409
- * reader, so the claim is enforced rather than asserted in a comment. */
410
- const readWorkers = (args: ParsedArgs): number | undefined =>
415
+ /**
416
+ * Both bounds are the constants the flag summary already names, so `x help verify` and the reader
417
+ * cannot disagree. Exported for the test that pins them: the command's `run` reaches this only
418
+ * after the whole gate would have started.
419
+ *
420
+ * `max` is the ceiling. Without it `--workers 5000` parsed, `planShards` clamped only to the file
421
+ * count, and `runParallel` `Promise.all`ed one Bun process per test file. `min` is `WORKER_FLOOR`,
422
+ * the same number `defaultWorkers` will not go below — the gate spreads or it does not shard, and
423
+ * `--workers 1` was a serial run the summary said was impossible. `x test --workers 1` stays legal
424
+ * and is deliberately NOT this reader: `runShards` clamps the width to the file count, so a
425
+ * one-file corpus makes `X_TEST_SHARD_FAILED`'s own `fix:` say `--workers 1`.
426
+ */
427
+ export const readWorkers = (args: ParsedArgs): number | undefined =>
411
428
  readIntFlag(args, {
412
429
  name: 'workers',
413
430
  command: 'verify',
414
- min: 1,
431
+ min: WORKER_FLOOR,
432
+ max: WORKER_CEILING,
415
433
  example: 'x verify --workers 4',
416
434
  });
417
435
 
package/src/db-branch.ts CHANGED
@@ -48,6 +48,24 @@ export function isBranchName(value: string): boolean {
48
48
  }
49
49
  }
50
50
 
51
+ /**
52
+ * The database a connection URL names. `url.split('/').at(-1)` took the query string with it, so
53
+ * a refusal built from it named `postly?sslmode=require_branch_x` — a database that does not exist
54
+ * — in a message whose whole point is that a reader can check it. `pathname` is the one part that
55
+ * IS the database, and it arrives percent-encoded, which `pg_database` does not.
56
+ *
57
+ * Falls back rather than throwing: the caller is already reporting a failure, and a second throw
58
+ * from the reporter replaces a checkable refusal with a stack trace.
59
+ */
60
+ export function databaseNameOf(url: string, fallback = 'postgres'): string {
61
+ try {
62
+ const name = decodeURIComponent(new URL(url).pathname.replace(/^\//, ''));
63
+ return name === '' ? fallback : name;
64
+ } catch {
65
+ return fallback;
66
+ }
67
+ }
68
+
51
69
  /** Where a branch's app answers once something serves it — the preview half of the design. */
52
70
  export const previewUrl = (branch: string, port: number): string =>
53
71
  `http://${branch}.localhost:${port}`;
@@ -14,7 +14,7 @@ import {
14
14
  } from '@ultimat3/db';
15
15
  import { describeEntities } from '@ultimat3/entity';
16
16
  import { loadApp } from './app-load';
17
- import { writeSchemaHash } from './drift';
17
+ import { reconcileSchemaHash, writeSchemaHash } from './drift';
18
18
  import { hashFileName, MIGRATIONS_DIR, readMigrations, snapshotFileName } from './migrations';
19
19
  import type { Finding } from './output';
20
20
 
@@ -24,11 +24,20 @@ export interface GenerateMigrationOptions {
24
24
  readonly allowDestructive?: boolean | undefined;
25
25
  }
26
26
 
27
+ /**
28
+ * What this run actually did — four different things, and `--json` has to tell them apart. A
29
+ * `hash-recorded` run wrote a file and generated no migration; reporting it as `generated` would
30
+ * claim a migration nobody can apply, and reporting it as `unchanged` would hide the one write.
31
+ */
32
+ export type GenerateOutcome = 'generated' | 'hash-recorded' | 'unchanged' | 'blocked';
33
+
27
34
  export interface GeneratedFiles {
28
- /** Absent when the entities and the migrations already agree — nothing was written. */
35
+ readonly outcome: GenerateOutcome;
36
+ /** Absent unless `outcome` is `generated` — the other three write no migration. */
29
37
  readonly migration?: GeneratedMigration | undefined;
30
38
  /** App-root-relative paths written, in write order. Empty when there was nothing to write. */
31
39
  readonly files: readonly string[];
40
+ /** What the source hashes to, whenever this run was in a position to record it. */
32
41
  readonly schemaHash?: string | undefined;
33
42
  /** Modules that would not load. Non-empty means nothing was generated. */
34
43
  readonly findings: readonly Finding[];
@@ -72,7 +81,7 @@ export async function generateAppMigration(
72
81
  options: GenerateMigrationOptions,
73
82
  ): Promise<GeneratedFiles> {
74
83
  const app = await loadApp(root);
75
- if (app.findings.length > 0) return { files: [], findings: app.findings };
84
+ if (app.findings.length > 0) return { outcome: 'blocked', files: [], findings: app.findings };
76
85
 
77
86
  const migrations = await readMigrations(root);
78
87
  const current = declaredSchema(migrations);
@@ -90,9 +99,31 @@ export async function generateAppMigration(
90
99
  name: options.name,
91
100
  ...(options.allowDestructive === true ? { allowDestructive: true } : {}),
92
101
  });
93
- // An empty diff writes nothing. A migration with no statement still takes a ledger row, a
94
- // checksum and a place in the apply order — a permanent record that nothing changed.
95
- if (migration.up.trim().length === 0) return { files: [], findings: [] };
102
+ // An empty diff writes no MIGRATION — one with no statement still takes a ledger row, a checksum
103
+ // and a place in the apply order, a permanent record that nothing changed. It re-records the
104
+ // sidecar instead, and that is what makes `X_DB_DRIFT`'s `fix:` a real instruction: the hash
105
+ // covers every non-test file under `packages/db/src`, so editing a seed or a helper moves it with
106
+ // no DDL behind it, and the command the error names used to write nothing at all.
107
+ //
108
+ // Nothing is masked, because of what has already been proved above: `loadApp` reported no
109
+ // findings, so the registry is whole rather than short; `declaredSchema` returned a real snapshot
110
+ // rather than `undefined`, so the diff had something to run against; and the emptiness is the
111
+ // generator's OWN verdict — the same call, the same classifier — as the written path. A DDL
112
+ // change that reaches here is a `generateMigration` that missed it, and the sidecar was never the
113
+ // thing that caught that: an author following the fix simply stayed red with nothing left to run.
114
+ if (migration.up.trim().length === 0) {
115
+ const newest = migrations[migrations.length - 1];
116
+ // No migration to record against, which in this branch means no entity is declared either —
117
+ // a registry against zero migrations is `create table` for all of it, never an empty diff.
118
+ if (newest === undefined) return { outcome: 'unchanged', files: [], findings: [] };
119
+ const reconciled = await reconcileSchemaHash(root, newest.id);
120
+ return {
121
+ outcome: reconciled.written ? 'hash-recorded' : 'unchanged',
122
+ schemaHash: reconciled.hash,
123
+ files: reconciled.written ? [`${MIGRATIONS_DIR}/${hashFileName(newest.id)}`] : [],
124
+ findings: [],
125
+ };
126
+ }
96
127
 
97
128
  const dir = join(root, MIGRATIONS_DIR);
98
129
  const sql = `${migration.id}.sql`;
@@ -104,6 +135,7 @@ export async function generateAppMigration(
104
135
  const schemaHash = await writeSchemaHash(root, migration.id);
105
136
 
106
137
  return {
138
+ outcome: 'generated',
107
139
  migration,
108
140
  schemaHash,
109
141
  files: [sql, snapshot, hashFileName(migration.id)].map((file) => `${MIGRATIONS_DIR}/${file}`),
package/src/db-seed.ts ADDED
@@ -0,0 +1,294 @@
1
+ // `x db seed`, everything except the argv: where a seed is declared, which tier this environment
2
+ // takes, and what one pass reports. A driver plus plain strings in, plain rows out — the
3
+ // `db-backfill.ts` split repeated, so every rule here is testable with no `ParsedArgs` and no boot.
4
+ //
5
+ // The decisions a seed itself owns are `@ultimat3/entity`'s: `seedTiersFor` is the one table saying
6
+ // which tiers an environment runs, and two copies of "may this seed run" would be two answers.
7
+
8
+ // `node:path` for the joiner and the app-root-relative spelling every finding is keyed by; Bun
9
+ // exposes neither.
10
+ import { relative, sep } from 'node:path';
11
+ import type { Environment } from '@ultimat3/core';
12
+ import { UltimateError } from '@ultimat3/core';
13
+ import type { Driver, Seed, SeedTier } from '@ultimat3/entity';
14
+ import { isSeed, SEED_TIERS, seedTiersFor } from '@ultimat3/entity';
15
+ import { docsFor } from './error-codes';
16
+ import { BadFlagError } from './errors';
17
+ import type { Finding, JsonValue } from './output';
18
+ import { findingFrom } from './output';
19
+ import { renderTable } from './table';
20
+
21
+ /**
22
+ * Where an app keeps seeds: a `seeds` directory in a package, or a `seed*.ts` beside its entities —
23
+ * the two layouts the tracked apps already use, and nothing wider. `loadApp`'s whole-src glob was
24
+ * the alternative and it is the wrong tool here: importing every module of every package to find a
25
+ * fixture graph makes an unrelated module that will not import into a failed seed run.
26
+ * `apps/` is deliberately absent: a fixture graph is data, and data lives in a package.
27
+ */
28
+ export const SEED_GLOBS = ['packages/*/seeds/**/*.ts', 'packages/*/src/seed*.ts'] as const;
29
+
30
+ /**
31
+ * `x db seed <name>` named a seed no module declared. `X_DECLARATION_UNKNOWN` is the code the
32
+ * registries already answer this with — a seed is a declaration, and a second code for "no such
33
+ * name" is the synonym the registry exists to prevent. The known names ARE listed, unlike
34
+ * `DeclarationUnknownError`'s count: an app has one to five seeds, not two hundred actions, and
35
+ * picking another one is the entire remedy.
36
+ *
37
+ * Both classes live HERE rather than in `errors.ts` for one reason, stated so nobody has to guess:
38
+ * that file is at the 500-line ceiling `x verify`'s `filesize` step enforces, and these two are
39
+ * `x db seed`'s alone. The codes stay CLI-owned in `error-codes.ts`, as every code does.
40
+ */
41
+ export class SeedUnknownError extends UltimateError {
42
+ constructor(input: { name: string; known: readonly string[] }) {
43
+ super({
44
+ code: 'X_DECLARATION_UNKNOWN',
45
+ cause:
46
+ input.known.length === 0
47
+ ? `no seed named "${input.name}" — this app declares none (a seed is an exported defineSeed() in packages/<pkg>/seeds or packages/<pkg>/src/seed.ts)`
48
+ : `no seed named "${input.name}" is declared (known: ${input.known.join(', ')})`,
49
+ // A dry run, never a bare `x db seed`: the command that answers "which seeds are there" must
50
+ // not be the command that writes them.
51
+ fix: 'x db seed --dry-run --json',
52
+ docs: docsFor('X_DECLARATION_UNKNOWN'),
53
+ });
54
+ }
55
+ }
56
+
57
+ /**
58
+ * The seed's tier is not one this environment runs. `dev` fixtures reaching production is the one
59
+ * irreversible mistake `x db seed` can make, so it is refused rather than confirmed.
60
+ *
61
+ * `X_SEED_ENVIRONMENT` is its own code, not `X_CLI_BAD_FLAG`: the argv was well formed and the
62
+ * answer is still no. A flag code says "you typed it wrong" and sends the reader to `x help`; this
63
+ * says "this environment does not run that tier", whose one remedy is naming the tier. The env var
64
+ * is named in the cause and not in the `fix:`, because a `fix:` is one pasteable line and a
65
+ * container with a fixed command line is the case that needs the other half.
66
+ */
67
+ export class SeedEnvironmentError extends UltimateError {
68
+ constructor(input: {
69
+ seed: string;
70
+ tier: string;
71
+ environment: string;
72
+ tiers: readonly string[];
73
+ }) {
74
+ super({
75
+ code: 'X_SEED_ENVIRONMENT',
76
+ cause: `seed "${input.seed}" is tier ${input.tier} and ULTIMATE_ENV resolved ${input.environment}, where x db seed runs ${input.tiers.join(', ')} — ULTIMATE_SEED_TIER=${input.tier} says this deploy takes it anyway`,
77
+ fix: `x db seed ${input.seed} --tier ${input.tier} --json`,
78
+ docs: docsFor('X_SEED_ENVIRONMENT'),
79
+ });
80
+ }
81
+ }
82
+
83
+ export interface DiscoveredSeed {
84
+ readonly seed: Seed;
85
+ /** App-root-relative POSIX path of the module that declared it. */
86
+ readonly file: string;
87
+ }
88
+
89
+ export interface SeedDiscovery {
90
+ readonly seeds: readonly DiscoveredSeed[];
91
+ /** Modules that would not import. Reported, never swallowed: one of them may hold the seed. */
92
+ readonly findings: readonly Finding[];
93
+ }
94
+
95
+ /**
96
+ * Every seed the app declares, by importing the modules that declare them — the same rule
97
+ * `loadApp` follows, because importing IS the declaration. Sorted by file, so a run's order is the
98
+ * one a reader can predict from the tree (`01_orgs.ts` before `02_posts.ts`) rather than the one a
99
+ * glob happened to yield.
100
+ */
101
+ export async function discoverSeeds(root: string): Promise<SeedDiscovery> {
102
+ const seeds: DiscoveredSeed[] = [];
103
+ const findings: Finding[] = [];
104
+ const seen = new Set<string>();
105
+ for (const pattern of SEED_GLOBS) {
106
+ for await (const absolute of new Bun.Glob(pattern).scan({ cwd: root, absolute: true })) {
107
+ if (absolute.includes('node_modules') || absolute.includes('.test.')) continue;
108
+ if (seen.has(absolute)) continue;
109
+ seen.add(absolute);
110
+ const file = relative(root, absolute).split(sep).join('/');
111
+ let module: Record<string, unknown>;
112
+ try {
113
+ module = (await import(absolute)) as Record<string, unknown>;
114
+ } catch (error) {
115
+ findings.push({ ...findingFrom(error), at: file });
116
+ continue;
117
+ }
118
+ for (const value of Object.values(module)) {
119
+ if (isSeed(value)) seeds.push({ seed: value, file });
120
+ }
121
+ }
122
+ }
123
+ return {
124
+ seeds: seeds.toSorted((left, right) => left.file.localeCompare(right.file)),
125
+ findings,
126
+ };
127
+ }
128
+
129
+ /** `--tier`, or `ULTIMATE_SEED_TIER` for a container whose command line is fixed. */
130
+ export function parseSeedTierFlag(value: string | undefined): SeedTier | undefined {
131
+ if (value === undefined || value === '') return undefined;
132
+ if ((SEED_TIERS as readonly string[]).includes(value)) return value as SeedTier;
133
+ throw new BadFlagError({
134
+ flag: 'tier',
135
+ command: 'db seed',
136
+ reason: `unknown tier "${value}" (known: ${SEED_TIERS.join(', ')})`,
137
+ fix: 'x db seed --dry-run --json',
138
+ });
139
+ }
140
+
141
+ export interface SeedSelection {
142
+ readonly discovered: readonly DiscoveredSeed[];
143
+ /** The positional. Absent runs every seed whose tier this environment takes. */
144
+ readonly name?: string | undefined;
145
+ readonly environment: Environment;
146
+ readonly requested?: SeedTier | undefined;
147
+ }
148
+
149
+ /**
150
+ * Which seeds this invocation runs, and the two refusals that are not a run.
151
+ *
152
+ * The environment check is HERE and again in `cmd-db.ts` before the driver is booted, on purpose:
153
+ * seeding is the one irreversible thing this command does, and the layer that boots a connection
154
+ * to production must not be the only layer that decided it was allowed to.
155
+ */
156
+ export function selectSeeds(input: SeedSelection): readonly DiscoveredSeed[] {
157
+ const tiers = seedTiersFor(input.environment, input.requested);
158
+ const known = input.discovered.map((entry) => entry.seed.name);
159
+ if (input.name === undefined) {
160
+ return input.discovered.filter((entry) => tiers.includes(entry.seed.tier));
161
+ }
162
+ const chosen = input.discovered.filter((entry) => entry.seed.name === input.name);
163
+ const first = chosen[0];
164
+ if (first === undefined) throw new SeedUnknownError({ name: input.name, known });
165
+ if (chosen.length > 1) {
166
+ throw new BadFlagError({
167
+ flag: 'name',
168
+ command: 'db seed',
169
+ reason: `"${input.name}" names ${chosen.length} seeds (${chosen.map((entry) => entry.file).join(', ')}) — a seed name is how a run is asked for, so two of them make the ask unanswerable`,
170
+ fix: 'x db seed --dry-run --json',
171
+ });
172
+ }
173
+ if (!tiers.includes(first.seed.tier)) {
174
+ throw new SeedEnvironmentError({
175
+ seed: first.seed.name,
176
+ tier: first.seed.tier,
177
+ environment: input.environment,
178
+ tiers,
179
+ });
180
+ }
181
+ return chosen;
182
+ }
183
+
184
+ export type SeedStatus = 'ok' | 'failed';
185
+
186
+ export interface SeedPassRow {
187
+ readonly file: string;
188
+ readonly name: string;
189
+ readonly tier: SeedTier;
190
+ readonly status: SeedStatus;
191
+ readonly ms: number;
192
+ readonly inserted: number;
193
+ readonly updated: number;
194
+ readonly skipped: number;
195
+ readonly finding: Finding | null;
196
+ }
197
+
198
+ export interface SeedPassOptions {
199
+ readonly seeds: readonly DiscoveredSeed[];
200
+ readonly driver: Driver;
201
+ readonly dryRun: boolean;
202
+ readonly env?: Readonly<Record<string, string | undefined>> | undefined;
203
+ /**
204
+ * One transaction PER SEED, never one around the run: a seed that fails must not roll back the
205
+ * ones that already succeeded, and a fixture graph half-written is worse than one not written.
206
+ * Injected so this stays testable with no database; `cmd-db.ts` passes `withTransaction`.
207
+ */
208
+ readonly transaction: <T>(work: () => Promise<T>) => Promise<T>;
209
+ }
210
+
211
+ /** Each seed, in file order, each isolated from the next. Never throws — a failure is a row. */
212
+ export async function runSeeds(options: SeedPassOptions): Promise<readonly SeedPassRow[]> {
213
+ const rows: SeedPassRow[] = [];
214
+ for (const entry of options.seeds) {
215
+ const started = Bun.nanoseconds();
216
+ const elapsed = (): number => Math.round((Bun.nanoseconds() - started) / 1_000_000);
217
+ try {
218
+ const run = await options.transaction(() =>
219
+ entry.seed.run({ driver: options.driver, dryRun: options.dryRun, env: options.env }),
220
+ );
221
+ rows.push({
222
+ file: entry.file,
223
+ name: run.name,
224
+ tier: run.tier,
225
+ status: 'ok',
226
+ ms: elapsed(),
227
+ ...run.metrics,
228
+ finding: null,
229
+ });
230
+ } catch (error) {
231
+ rows.push({
232
+ file: entry.file,
233
+ name: entry.seed.name,
234
+ tier: entry.seed.tier,
235
+ status: 'failed',
236
+ ms: elapsed(),
237
+ inserted: 0,
238
+ updated: 0,
239
+ skipped: 0,
240
+ finding: { ...findingFrom(error), at: entry.file },
241
+ });
242
+ }
243
+ }
244
+ return rows;
245
+ }
246
+
247
+ export interface SeedTotals {
248
+ readonly inserted: number;
249
+ readonly updated: number;
250
+ readonly skipped: number;
251
+ readonly failed: number;
252
+ }
253
+
254
+ export const seedTotals = (rows: readonly SeedPassRow[]): SeedTotals => ({
255
+ inserted: rows.reduce((sum, row) => sum + row.inserted, 0),
256
+ updated: rows.reduce((sum, row) => sum + row.updated, 0),
257
+ skipped: rows.reduce((sum, row) => sum + row.skipped, 0),
258
+ failed: rows.filter((row) => row.status === 'failed').length,
259
+ });
260
+
261
+ /**
262
+ * Slowest first, in both renderers: a seed run that got slow is diagnosed by which FILE took the
263
+ * time, and a list in run order buries that under whatever happens to be alphabetically first.
264
+ */
265
+ const slowestFirst = (rows: readonly SeedPassRow[]): readonly SeedPassRow[] =>
266
+ rows.toSorted((left, right) => right.ms - left.ms);
267
+
268
+ export const seedPassToJson = (rows: readonly SeedPassRow[]): JsonValue => ({
269
+ seeds: slowestFirst(rows).map((row) => ({
270
+ file: row.file,
271
+ name: row.name,
272
+ tier: row.tier,
273
+ status: row.status,
274
+ ms: row.ms,
275
+ inserted: row.inserted,
276
+ updated: row.updated,
277
+ skipped: row.skipped,
278
+ })),
279
+ totals: { ...seedTotals(rows) },
280
+ });
281
+
282
+ export const renderSeedTable = (rows: readonly SeedPassRow[]): readonly string[] =>
283
+ renderTable(
284
+ ['seed', 'tier', 'status', 'ms', 'inserted', 'updated', 'skipped'],
285
+ slowestFirst(rows).map((row) => [
286
+ row.name,
287
+ row.tier,
288
+ row.status,
289
+ String(row.ms),
290
+ String(row.inserted),
291
+ String(row.updated),
292
+ String(row.skipped),
293
+ ]),
294
+ );
package/src/dev-assets.ts CHANGED
@@ -14,7 +14,7 @@ import { applyCacheHeaders } from '@ultimat3/http';
14
14
  import type { IconPlan } from '@ultimat3/pwa';
15
15
  import { BuiltinImagePipeline, PwaIconMissingError, planIcons } from '@ultimat3/pwa';
16
16
  import type { ImageQuery, ImageTransformDriver } from '@ultimat3/seo';
17
- import { builtinImageDriver, parseImageQuery } from '@ultimat3/seo';
17
+ import { builtinImageDriver, DEFAULT_WIDTHS, parseImageQuery } from '@ultimat3/seo';
18
18
  import type { ImageFormat, ImageTransform, Storage } from '@ultimat3/storage';
19
19
  import { IMAGE_FORMATS, isTenantScoped, variantKey } from '@ultimat3/storage';
20
20
  import {
@@ -49,6 +49,24 @@ export const MEDIA_BASE_PATH = '/media';
49
49
  */
50
50
  const IMMUTABLE_IMAGE: CacheHint = { mode: 'immutable' };
51
51
 
52
+ /**
53
+ * Whether a variant is one the framework itself can MINT, and therefore one worth storing.
54
+ *
55
+ * The cache key is built entirely from caller-supplied query values, and a signed-in reader
56
+ * holding `storage:read` may ask for any of them on their own objects — so `?w=1`, `?w=2`, … each
57
+ * wrote a new object to the app's only disk. `@ultimat3/seo`'s `MAX_IMAGE_WIDTH` (8192) bounds the
58
+ * blast radius and does not close it: 8192 stored objects per source, per format, is amplification
59
+ * a tenant drives with a `for` loop.
60
+ *
61
+ * The set is `DEFAULT_WIDTHS` **plus the source's intrinsic width**, which is exactly what
62
+ * `usableWidths` puts in a `srcset` — clamping to the constant alone would refuse the widest entry
63
+ * of every image whose intrinsic width is not one of the eight, a URL the framework mints itself.
64
+ * Anything outside it is still SERVED: this decides what is written, not what is answered, so no
65
+ * caller gains a new 4xx and the disk stops growing on a stranger's key.
66
+ */
67
+ const isMintableWidth = (width: number | undefined, intrinsic: number): boolean =>
68
+ width === undefined || width === intrinsic || DEFAULT_WIDTHS.includes(width);
69
+
52
70
  const imageResponse = (bytes: Uint8Array, contentType: string, cache: CacheHint): Response =>
53
71
  applyCacheHeaders(
54
72
  // Copied, not passed through: a `Uint8Array<ArrayBufferLike>` may be backed by a
@@ -112,6 +130,7 @@ async function transformedVariant(
112
130
  }
113
131
 
114
132
  const source = await disk.get(key);
133
+ const intrinsic = probeImage(source.bytes).width;
115
134
  // The seam is WHICH driver transforms, not who resolves the bytes: `TransformRequest.width` is
116
135
  // required, and a request with no `?w=` gets its width from the source's own header — so the
117
136
  // read happens either way and a supplied driver is handed the same resolved request the builtin
@@ -122,11 +141,11 @@ async function transformedVariant(
122
141
  src: key,
123
142
  // A header read, not a decode: `?f=webp` alone still needs a width, and the source's own is
124
143
  // the only one that does not resize an image the caller never asked to resize.
125
- width: query.width ?? probeImage(source.bytes).width,
144
+ width: query.width ?? intrinsic,
126
145
  ...(query.format === undefined ? {} : { format: query.format }),
127
146
  ...(query.quality === undefined ? {} : { quality: query.quality }),
128
147
  });
129
- if (cached !== undefined) {
148
+ if (cached !== undefined && isMintableWidth(query.width, intrinsic)) {
130
149
  await disk.put(cached, variant.bytes, { contentType: variant.contentType });
131
150
  }
132
151
  return imageResponse(variant.bytes, variant.contentType, cache);
package/src/dev-roles.ts CHANGED
@@ -294,9 +294,11 @@ export async function startRoles(options: StartRolesOptions): Promise<RunningRol
294
294
  // — every enqueue in a request handler silently becomes a job that never runs.
295
295
  //
296
296
  // On `worker`, and on `worker` alone: it is the role that exists wherever jobs run at all, and
297
- // a relay is safe to duplicate (publish-then-mark is at-least-once and the idempotency key
298
- // collapses the repeat) but pointless to spread. A deployment with no `worker` has no one to
299
- // run the jobs either way.
297
+ // a relay is safe to duplicate — the claim is a LEASE taken in the statement that locks the
298
+ // row, so two relays never hold one batch — but pointless to spread. The idempotency key is
299
+ // not the reason and never was: its conflict target is a partial index over live states, so it
300
+ // collapses a repeat only while the first job is still live. A deployment with no `worker` has
301
+ // no one to run the jobs either way.
300
302
  const relay: OutboxRelay | null = selected.includes('worker')
301
303
  ? createOutboxRelay({ store: options.runtime.outbox, driver: options.runtime.jobs })
302
304
  : null;
@@ -2,6 +2,10 @@
2
2
  // owns keys, bytes and the tenant boundary and owns no `Response`; `@ultimat3/policy` owns the
3
3
  // one authz decision; this file is where those two meet a `Route` — the same shape `dev-assets.ts`
4
4
  // uses for `/icons` and `/media`, so `x dev` and `apps/web/server.ts` mount one read path, not two.
5
+ //
6
+ // The base path is `@ultimat3/storage`'s `DEFAULT_SIGNED_URL_BASE`, imported and never restated:
7
+ // `localDriver` SIGNS `/_storage/<disk>/<key>`, so a local `'/_storage'` here is a second statement
8
+ // of one constant — and a signer and a reader that disagree serve 404 for every signed URL.
5
9
 
6
10
  import { actorOf } from '@ultimat3/action';
7
11
  import type { Actor } from '@ultimat3/core';
@@ -12,15 +16,13 @@ import { can, codeOf, evaluate, forbidden, reasonOf } from '@ultimat3/policy';
12
16
  import type { Storage, StorageRead } from '@ultimat3/storage';
13
17
  import {
14
18
  assertSafeKey,
19
+ DEFAULT_SIGNED_URL_BASE,
15
20
  isTenantScoped,
16
21
  isWithinOrg,
17
22
  objectNotFound,
18
23
  orgMismatch,
19
24
  } from '@ultimat3/storage';
20
25
 
21
- /** `localDriver` signs `/_storage/<disk>/<key>`, so the read half hangs off the same base. */
22
- export const STORAGE_BASE_PATH = '/_storage';
23
-
24
26
  /**
25
27
  * The one capability that gates reading a stored object, on every disk. A permission and not a
26
28
  * per-disk family: `disk` is in the policy's `input`, so an app that wants a per-disk rule writes
@@ -223,7 +225,7 @@ export function storageRoutes(options: StorageRoutesOptions): readonly Route[] {
223
225
  return [
224
226
  {
225
227
  method: 'GET',
226
- path: `${STORAGE_BASE_PATH}/:disk/*key`,
228
+ path: `${DEFAULT_SIGNED_URL_BASE}/:disk/*key`,
227
229
  meta: {
228
230
  name: 'storage.read',
229
231
  auth: 'required',
package/src/dev-traces.ts CHANGED
@@ -71,9 +71,27 @@ function requestFacts(root: ReadableSpan): { method: string; path: string } {
71
71
  };
72
72
  }
73
73
 
74
+ /** `http.request_id` is stamped by the pipeline's root and by nothing else; the name is a fallback. */
74
75
  const isHttpRoot = (span: ReadableSpan): boolean =>
75
- span.parentSpanId === undefined &&
76
- (span.attributes['http.request_id'] !== undefined || /^[A-Z]+ \//.test(span.name));
76
+ span.attributes['http.request_id'] !== undefined || /^[A-Z]+ \//.test(span.name);
77
+
78
+ /**
79
+ * The request's own span among a trace's. `parentSpanId === undefined` was a third CONDITION
80
+ * until `As of 2026-08`, and it dropped every request that arrived with an inbound
81
+ * `traceparent`: `pipeline.ts` passes `parent: correlation.parent`, so the root has a defined
82
+ * `parentSpanId`, `spans.find(isHttpRoot)` answered `undefined`, and the whole trace vanished
83
+ * from `/_x/timeline` for any caller behind an instrumented client, an ingress or a service
84
+ * mesh. It survives as the TIE-BREAK: the outermost candidate is the one whose parent is not
85
+ * itself in this recording, so a nested candidate can never outrank the request's own span.
86
+ */
87
+ function httpRootOf(spans: readonly ReadableSpan[]): ReadableSpan | undefined {
88
+ const candidates = spans.filter(isHttpRoot);
89
+ const recorded = new Set(spans.map((span) => span.context.spanId));
90
+ const outermost = candidates.find(
91
+ (span) => span.parentSpanId === undefined || !recorded.has(span.parentSpanId),
92
+ );
93
+ return outermost ?? candidates[0];
94
+ }
77
95
 
78
96
  function toTrace(root: ReadableSpan, spans: readonly ReadableSpan[]): RequestTrace {
79
97
  const { method, path } = requestFacts(root);
@@ -121,7 +139,11 @@ export function createTraceRecorder(options: { limit?: number } = {}): TraceReco
121
139
  const spans = byTrace.get(traceId);
122
140
  if (spans === undefined) {
123
141
  byTrace.set(traceId, [span]);
124
- // Bounded by trace, not by span: dropping half a request would leave a flame with holes.
142
+ // Bounded by TRACE, not by span: dropping half a request would leave a flame with holes.
143
+ // The cost is stated rather than capped — one trace's span array has no bound of its own, so
144
+ // a request issuing 50k statements holds 50k `ReadableSpan`s until it is evicted. That is a
145
+ // dev-only recorder (`serve.ts` installs none), and a per-trace cap would silently produce
146
+ // the holed flame this bound exists to prevent.
125
147
  while (byTrace.size > limit) {
126
148
  const oldest = byTrace.keys().next();
127
149
  if (oldest.done === true) break;
@@ -137,7 +159,7 @@ export function createTraceRecorder(options: { limit?: number } = {}): TraceReco
137
159
  traces(): readonly RequestTrace[] {
138
160
  const traces: RequestTrace[] = [];
139
161
  for (const spans of byTrace.values()) {
140
- const root = spans.find(isHttpRoot);
162
+ const root = httpRootOf(spans);
141
163
  if (root !== undefined) traces.push(toTrace(root, spans));
142
164
  }
143
165
  return traces.sort((a, b) => b.startedAt.localeCompare(a.startedAt));