@stll/oxlint-config 3.0.0 → 3.2.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.
@@ -13,6 +13,8 @@ import { execFileSync, spawnSync } from "node:child_process";
13
13
  import { gte, minVersion, satisfies, valid, validRange } from "semver";
14
14
  import { LineCounter, isAlias, isMap, isNode, isScalar, isSeq, parseAllDocuments, parseDocument } from "yaml";
15
15
  import { pathToFileURL } from "node:url";
16
+ import { parse as parse$3 } from "acorn";
17
+ import { transformSync } from "esbuild";
16
18
  //#region src/toolchain-bun.ts
17
19
  const STABLE_RELEASE = /^\d+\.\d+\.\d+$/u;
18
20
  /**
@@ -31,9 +33,9 @@ const repositoryBun = (floor, manifest) => {
31
33
  };
32
34
  //#endregion
33
35
  //#region src/toolchain-consumer-checks.ts
34
- const consumerRunnerVersion = "3.0.0";
36
+ const consumerRunnerVersion = "3.2.0";
35
37
  const consumerCheckWorkflow = "stella/.github/.github/workflows/package-consumer-compat.yml";
36
- const record$10 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
38
+ const record$11 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
37
39
  const repositoryDirectory = (value) => typeof value === "string" && value !== "" && !value.includes("\\") && !value.includes("${{") && !value.split("").some((character) => {
38
40
  const code = character.charCodeAt(0);
39
41
  return code < 32 || code === 127;
@@ -119,12 +121,12 @@ const validConsumerCron = (text) => {
119
121
  });
120
122
  };
121
123
  const nightlySchedule = (value) => {
122
- if (!record$10(value)) return false;
124
+ if (!record$11(value)) return false;
123
125
  const schedule = value["schedule"];
124
126
  if (!Array.isArray(schedule) || schedule.length === 0) return false;
125
127
  const coveredDays = /* @__PURE__ */ new Set();
126
128
  for (const entry of schedule) {
127
- if (!record$10(entry) || typeof entry["cron"] !== "string" || !validConsumerCron(entry["cron"])) return false;
129
+ if (!record$11(entry) || typeof entry["cron"] !== "string" || !validConsumerCron(entry["cron"])) return false;
128
130
  const fields = entry["cron"].trim().split(/\s+/);
129
131
  const selections = cronFields.map((field, index) => {
130
132
  const text = fields.at(index);
@@ -140,7 +142,7 @@ const parseConsumerChecks = (input) => {
140
142
  if (!Array.isArray(input)) throw new Error("consumerChecks must be an array");
141
143
  const targets = /* @__PURE__ */ new Set();
142
144
  return input.map((entry) => {
143
- if (!record$10(entry) || Object.keys(entry).some((key) => ![
145
+ if (!record$11(entry) || Object.keys(entry).some((key) => ![
144
146
  "workflow",
145
147
  "job",
146
148
  "packages",
@@ -178,9 +180,9 @@ const checkConsumerChecks = ({ declarations, files, policy }) => {
178
180
  });
179
181
  if (document.errors.length > 0) throw new Error("invalid workflow YAML");
180
182
  const source = document.toJS({ maxAliasCount: 100 });
181
- if (!record$10(source) || !record$10(source["jobs"])) continue;
183
+ if (!record$11(source) || !record$11(source["jobs"])) continue;
182
184
  for (const [jobName, job] of Object.entries(source["jobs"])) {
183
- if (!record$10(job) || typeof job["uses"] !== "string") continue;
185
+ if (!record$11(job) || typeof job["uses"] !== "string") continue;
184
186
  const [workflowPath, ref] = job["uses"].split("@");
185
187
  if (workflowPath?.toLowerCase() !== "stella/.github/.github/workflows/package-consumer-compat.yml") continue;
186
188
  const declaration = declarations.find((entry) => entry.workflow === workflow && entry.job === jobName);
@@ -194,7 +196,7 @@ const checkConsumerChecks = ({ declarations, files, policy }) => {
194
196
  const approved = Object.entries(policy.actions).find(([name]) => name.toLowerCase() === consumerCheckWorkflow)?.[1];
195
197
  if (job["uses"].split("@").length !== 2 || ref === void 0 || !/^[a-f0-9]{40}$/.test(ref) || approved === void 0 || ref !== approved.sha || "steps" in job || "runs-on" in job) throw new Error(`${workflow}:${jobName} must invoke the approved immutable consumer workflow as a reusable job`);
196
198
  const inputs = job["with"];
197
- if (!record$10(inputs)) throw new Error(`${workflow}:${jobName} requires consumer inputs`);
199
+ if (!record$11(inputs)) throw new Error(`${workflow}:${jobName} requires consumer inputs`);
198
200
  const names = /* @__PURE__ */ new Set();
199
201
  for (const key of Object.keys(inputs)) {
200
202
  const normalized = key.toLowerCase();
@@ -264,7 +266,7 @@ const locatorValidators = {
264
266
  "bun-source": /^(?:jobs\.[\w-]+|runs)\.steps\.[\w-]+$/
265
267
  };
266
268
  const knownKind = (value) => typeof value === "string" && Object.hasOwn(locatorValidators, value);
267
- const record$9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
269
+ const record$10 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
268
270
  /** One reviewed declaration identifies one unresolved selector, never a rule. */
269
271
  const parseDynamicSelectors = (input) => {
270
272
  if (input === void 0) return [];
@@ -272,7 +274,7 @@ const parseDynamicSelectors = (input) => {
272
274
  const result = [];
273
275
  const keys = /* @__PURE__ */ new Set();
274
276
  for (const entry of input) {
275
- if (!record$9(entry) || Object.keys(entry).some((key) => ![
277
+ if (!record$10(entry) || Object.keys(entry).some((key) => ![
276
278
  "path",
277
279
  "kind",
278
280
  "reason",
@@ -308,7 +310,7 @@ const parseDynamicSelectors = (input) => {
308
310
  };
309
311
  //#endregion
310
312
  //#region src/toolchain-engine-floors.ts
311
- const record$8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
313
+ const record$9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
312
314
  const repositoryPath = (value) => typeof value === "string" && value !== "" && !/^[A-Za-z]:/.test(value) && !/[\\\s]/.test(value) && !Array.from(value).some((character) => character.charCodeAt(0) < 32) && value.split("/").every((part) => part !== "" && part !== "." && part !== "..");
313
315
  const parseEngineFloors = (input) => {
314
316
  if (input === void 0) return [];
@@ -316,7 +318,7 @@ const parseEngineFloors = (input) => {
316
318
  const result = [];
317
319
  const jobs = /* @__PURE__ */ new Set();
318
320
  for (const entry of input) {
319
- if (!record$8(entry) || Object.keys(entry).some((key) => ![
321
+ if (!record$9(entry) || Object.keys(entry).some((key) => ![
320
322
  "package",
321
323
  "workflow",
322
324
  "job"
@@ -337,16 +339,16 @@ const resolveEngineFloor = (entry, files) => {
337
339
  const workflowText = files[entry.workflow];
338
340
  if (manifestText === void 0 || workflowText === void 0) throw new Error("engine floor package and workflow must be tracked");
339
341
  const manifest = JSON.parse(manifestText);
340
- if (!record$8(manifest) || manifest["private"] === true || typeof manifest["name"] !== "string" || manifest["name"].trim() === "" || !record$8(manifest["engines"]) || typeof manifest["engines"]["node"] !== "string") throw new Error("engine floor package must be published and declare engines.node");
342
+ if (!record$9(manifest) || manifest["private"] === true || typeof manifest["name"] !== "string" || manifest["name"].trim() === "" || !record$9(manifest["engines"]) || typeof manifest["engines"]["node"] !== "string") throw new Error("engine floor package must be published and declare engines.node");
341
343
  const range = manifest["engines"]["node"];
342
344
  const minimum = validRange(range) === null ? null : minVersion(range);
343
345
  if (minimum === null || minimum.prerelease.length !== 0) throw new Error("engine floor engines.node must declare a stable minimum");
344
346
  const document = parseDocument(workflowText, { merge: true });
345
347
  if (document.errors.length !== 0) throw new Error("engine floor workflow must be valid YAML");
346
348
  const workflow = document.toJS({ maxAliasCount: 100 });
347
- if (!record$8(workflow) || !record$8(workflow["jobs"])) throw new Error("engine floor job must exist in its tracked workflow");
349
+ if (!record$9(workflow) || !record$9(workflow["jobs"])) throw new Error("engine floor job must exist in its tracked workflow");
348
350
  const job = workflow["jobs"][entry.job];
349
- if (!record$8(job)) throw new Error("engine floor job must exist in its tracked workflow");
351
+ if (!record$9(job)) throw new Error("engine floor job must exist in its tracked workflow");
350
352
  if (!Array.isArray(job["steps"]) || Object.hasOwn(job, "uses")) throw new Error("engine floor job must declare steps and cannot reuse a workflow");
351
353
  return {
352
354
  ...entry,
@@ -359,6 +361,38 @@ const engineFloorSelectorMatches = (value, floor) => {
359
361
  return Number(value.split(".").at(0)) === floor.major && satisfies(value, floor.range);
360
362
  };
361
363
  //#endregion
364
+ //#region src/toolchain-typescript-source-api.ts
365
+ const readSourceApiInput = ({ root, source, tracked }) => {
366
+ if (!tracked.has(source)) return void 0;
367
+ const resolvedRoot = realpathSync(root);
368
+ const input = path.resolve(resolvedRoot, source);
369
+ try {
370
+ if (realpathSync(input) !== input || !lstatSync(input).isFile()) return void 0;
371
+ return readFileSync(input, "utf8");
372
+ } catch {
373
+ return;
374
+ }
375
+ };
376
+ const sourceApiImportProblem = ({ source, content, name }) => {
377
+ if (content === void 0) return "must be a tracked, readable regular source file without symlinks";
378
+ try {
379
+ const { code } = transformSync(content, {
380
+ sourcefile: source,
381
+ loader: source.endsWith("x") ? "tsx" : "ts",
382
+ format: "esm",
383
+ tsconfigRaw: { compilerOptions: { verbatimModuleSyntax: true } },
384
+ logLevel: "silent"
385
+ });
386
+ if (parse$3(code, {
387
+ ecmaVersion: "latest",
388
+ sourceType: "module"
389
+ }).body.some((node) => node.type === "ImportDeclaration" && node.source.value === name && node.specifiers.length > 0)) return void 0;
390
+ return `no longer has a static value import from ${name}; remove the declaration or name its current importer`;
391
+ } catch {
392
+ return "cannot parse the declared compiler API importer";
393
+ }
394
+ };
395
+ //#endregion
362
396
  //#region src/toolchain-packages.ts
363
397
  const packageRules = [
364
398
  "bun-pins",
@@ -376,7 +410,7 @@ const installSections = [
376
410
  "optionalDependencies"
377
411
  ];
378
412
  const allowlistFile = "stll-toolchain.json";
379
- const record$7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
413
+ const record$8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
380
414
  /** `npm:typescript@7.0.2` installs a compiler under another name; returns its version. */
381
415
  const typescriptAliasVersion = (spec) => {
382
416
  if (typeof spec !== "string") return void 0;
@@ -406,22 +440,44 @@ const parseTypescriptAllowlist = (input) => {
406
440
  return input.map((entry) => {
407
441
  const shown = JSON.stringify(entry);
408
442
  const text = (key) => {
409
- const value = record$7(entry) ? entry[key] : void 0;
443
+ const value = record$8(entry) ? entry[key] : void 0;
410
444
  return typeof value === "string" && value.trim() !== "" ? value : void 0;
411
445
  };
412
446
  const workspace = text("workspace");
413
447
  const name = text("name");
414
448
  const spec = text("spec");
415
449
  const dependency = text("dependency");
450
+ const sourceApi = record$8(entry) && entry["kind"] === "source-api";
451
+ const keys = sourceApi ? [
452
+ "workspace",
453
+ "name",
454
+ "spec",
455
+ "reason",
456
+ "kind",
457
+ "sources"
458
+ ] : [...allowlistKeys, ...record$8(entry) && entry["kind"] === "dependency" ? ["kind"] : []];
416
459
  const reason = text("reason");
417
- if (!record$7(entry) || Object.keys(entry).length !== allowlistKeys.length || workspace === void 0 || name === void 0 || spec === void 0 || dependency === void 0 || reason === void 0) throw new Error(`typescriptAllowlist entry ${shown} must have exactly workspace, name, spec, dependency and reason as nonempty strings: ${allowlistFormat}`);
460
+ if (!record$8(entry) || Object.keys(entry).length !== keys.length || Object.keys(entry).some((key) => !keys.includes(key)) || workspace === void 0 || name === void 0 || spec === void 0 || !sourceApi && dependency === void 0 || reason === void 0) throw new Error(`typescriptAllowlist entry ${shown} must have exactly workspace, name, spec, dependency and reason as nonempty strings: ${allowlistFormat}`);
418
461
  if (workspace !== "." && (path.posix.normalize(workspace) !== workspace || workspace.startsWith("/") || workspace.endsWith("/") || workspace === ".." || workspace.startsWith("../"))) throw new Error(`typescriptAllowlist entry ${shown}: workspace must be "." for the root or a normalized relative package directory such as "apps/web"`);
419
462
  if (!exactTypescriptSpec(spec)) throw new Error(`typescriptAllowlist entry ${shown}: spec must be the exact spec the manifest resolves to, such as "6.0.3" or "npm:typescript@6.0.3", not a range or catalog reference`);
420
463
  if (!isTypescriptInstall(name, spec)) throw new Error(`typescriptAllowlist entry ${shown}: name must be typescript or @typescript/native, or an alias whose spec is npm:typescript@<version> or npm:@typescript/native@<version>`);
421
- if (typescriptPackages.has(dependency) || dependency === name) throw new Error(`typescriptAllowlist entry ${shown}: dependency must name the package in that workspace that needs ${name}, not a TypeScript package`);
464
+ if (dependency !== void 0 && (typescriptPackages.has(dependency) || dependency === name)) throw new Error(`typescriptAllowlist entry ${shown}: dependency must name the package in that workspace that needs ${name}, not a TypeScript package`);
422
465
  const key = `${workspace}\0${name}`;
423
466
  if (seen.has(key)) throw new Error(`typescriptAllowlist declares ${name} for ${workspace} more than once; keep one entry`);
424
467
  seen.add(key);
468
+ if (sourceApi) {
469
+ const sources = entry["sources"];
470
+ if (!Array.isArray(sources) || sources.length === 0 || !sources.every((source) => typeof source === "string" && path.posix.normalize(source) === source && !source.startsWith("/") && source !== ".." && !source.startsWith("../") && !source.includes("\\") && !/[?*{}[\]]/.test(source) && /\.[cm]?[jt]sx?$/.test(source) && !/\.d\.[cm]?ts$/.test(source) && !source.split("/").some((part) => ["node_modules", "vendor"].includes(part))) || new Set(sources).size !== sources.length) throw new Error(`typescriptAllowlist source-api entry ${shown} requires unique normalized source file paths`);
471
+ return {
472
+ kind: "source-api",
473
+ workspace,
474
+ name,
475
+ spec,
476
+ reason,
477
+ sources
478
+ };
479
+ }
480
+ if (dependency === void 0) throw new Error("Missing TypeScript dependency owner");
425
481
  return {
426
482
  workspace,
427
483
  name,
@@ -990,10 +1046,10 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
990
1046
  const document = pnpm ? parseDocument(text, { lineCounter }) : void 0;
991
1047
  if (document !== void 0 && document.errors.length > 0) throw new Error("invalid YAML");
992
1048
  const json = document === void 0 ? JSON.parse(text) : document.toJS();
993
- if (!record$7(json)) throw new Error("manifest must be an object");
1049
+ if (!record$8(json)) throw new Error("manifest must be an object");
994
1050
  if (pnpm) {
995
- if (json["catalog"] !== void 0 && !record$7(json["catalog"])) throw new Error("catalog must be a mapping");
996
- if (json["catalogs"] !== void 0 && (!record$7(json["catalogs"]) || Object.values(json["catalogs"]).some((catalog) => !record$7(catalog)))) throw new Error("named catalogs must be mappings");
1051
+ if (json["catalog"] !== void 0 && !record$8(json["catalog"])) throw new Error("catalog must be a mapping");
1052
+ if (json["catalogs"] !== void 0 && (!record$8(json["catalogs"]) || Object.values(json["catalogs"]).some((catalog) => !record$8(catalog)))) throw new Error("named catalogs must be mappings");
997
1053
  if (json["packages"] !== void 0 && (!Array.isArray(json["packages"]) || !json["packages"].every((pattern) => typeof pattern === "string"))) throw new Error("workspace packages must be patterns");
998
1054
  }
999
1055
  manifests.set(file, json);
@@ -1007,13 +1063,13 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1007
1063
  catalog: json["overrides"],
1008
1064
  prefix: ["overrides"]
1009
1065
  },
1010
- ...record$7(json["catalogs"]) ? Object.entries(json["catalogs"]).map(([name, catalog]) => ({
1066
+ ...record$8(json["catalogs"]) ? Object.entries(json["catalogs"]).map(([name, catalog]) => ({
1011
1067
  catalog,
1012
1068
  prefix: ["catalogs", name]
1013
1069
  })) : []
1014
1070
  ];
1015
1071
  for (const { catalog, prefix } of catalogEntries) {
1016
- if (!record$7(catalog)) continue;
1072
+ if (!record$8(catalog)) continue;
1017
1073
  for (const [name, value] of Object.entries(catalog)) {
1018
1074
  const node = document.getIn([...prefix, name], true);
1019
1075
  if (!isNode(node) || node.range === void 0 || node.range === null) continue;
@@ -1032,7 +1088,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1032
1088
  });
1033
1089
  }
1034
1090
  }
1035
- const catalogSources = (json) => [json, ...record$7(json["workspaces"]) ? [json["workspaces"]] : []];
1091
+ const catalogSources = (json) => [json, ...record$8(json["workspaces"]) ? [json["workspaces"]] : []];
1036
1092
  const pnpmWorkspaceFor = (file) => {
1037
1093
  let directory = path.posix.dirname(file);
1038
1094
  while (true) {
@@ -1055,8 +1111,8 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1055
1111
  patterns: workspace["packages"],
1056
1112
  rootMembership: "implicit"
1057
1113
  })) return void 0;
1058
- if (name === "" || name === "default") return workspace["catalog"] ?? (record$7(workspace["catalogs"]) ? workspace["catalogs"]["default"] : void 0);
1059
- return record$7(workspace["catalogs"]) ? workspace["catalogs"][name] : void 0;
1114
+ if (name === "" || name === "default") return workspace["catalog"] ?? (record$8(workspace["catalogs"]) ? workspace["catalogs"]["default"] : void 0);
1115
+ return record$8(workspace["catalogs"]) ? workspace["catalogs"][name] : void 0;
1060
1116
  }
1061
1117
  let directory = path.posix.dirname(file);
1062
1118
  while (true) {
@@ -1064,7 +1120,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1064
1120
  if (manifest !== void 0) {
1065
1121
  for (const source of catalogSources(manifest)) {
1066
1122
  if (name === "" && source["catalog"] !== void 0) return source["catalog"];
1067
- if (record$7(source["catalogs"]) && source["catalogs"][name] !== void 0) return source["catalogs"][name];
1123
+ if (record$8(source["catalogs"]) && source["catalogs"][name] !== void 0) return source["catalogs"][name];
1068
1124
  }
1069
1125
  if (manifest["workspaces"] !== void 0) return void 0;
1070
1126
  }
@@ -1082,7 +1138,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1082
1138
  };
1083
1139
  visited.add(value);
1084
1140
  const catalog = catalogFor(file, value.slice(8));
1085
- if (!record$7(catalog)) return {
1141
+ if (!record$8(catalog)) return {
1086
1142
  value: void 0,
1087
1143
  catalogs
1088
1144
  };
@@ -1107,7 +1163,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1107
1163
  const workspaces = manifests.get(path.posix.join(directory, "package.json"))?.["workspaces"];
1108
1164
  if (workspaces !== void 0) return {
1109
1165
  directory,
1110
- patterns: record$7(workspaces) ? workspaces["packages"] : workspaces,
1166
+ patterns: record$8(workspaces) ? workspaces["packages"] : workspaces,
1111
1167
  source: "json"
1112
1168
  };
1113
1169
  if (directory === ".") return void 0;
@@ -1141,7 +1197,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1141
1197
  if (json === void 0) continue;
1142
1198
  for (const section of installSections) {
1143
1199
  const entries = json[section];
1144
- if (!record$7(entries) || !Object.hasOwn(entries, entry.name)) continue;
1200
+ if (!record$8(entries) || !Object.hasOwn(entries, entry.name)) continue;
1145
1201
  const { value, catalogs } = resolveThrough(file, entry.name, entries[entry.name]);
1146
1202
  if (value !== entry.spec) continue;
1147
1203
  for (const catalog of catalogs) {
@@ -1173,7 +1229,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1173
1229
  });
1174
1230
  return `${name} ${shown} in ${file} is not allowed: ${typescriptGuidance}; if a dependency of this workspace needs it, add ${suggestion} to typescriptAllowlist in ${allowlistFile}`;
1175
1231
  }
1176
- return value === entry.spec ? void 0 : `${name} in ${file} resolves to ${shown}, but typescriptAllowlist in ${allowlistFile} allows ${entry.spec} for ${entry.dependency}; declare ${entry.spec} or update the entry's spec`;
1232
+ return value === entry.spec ? void 0 : `${name} in ${file} resolves to ${shown}, but typescriptAllowlist in ${allowlistFile} allows ${entry.spec} for ${entry.kind === "source-api" ? "source API imports" : entry.dependency}; declare ${entry.spec} or update the entry's spec`;
1177
1233
  };
1178
1234
  const peerRange = (value) => typeof value === "string" && validRange(value) !== null;
1179
1235
  for (const [file, json] of manifests) {
@@ -1186,7 +1242,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1186
1242
  message: `packageManager Bun version must be ${policy.bun} (one exact release at or above the shared minimum, the same in every manifest), found ${JSON.stringify(manager)}`
1187
1243
  });
1188
1244
  const checkEntries = (entries, scope, location) => {
1189
- if (!record$7(entries)) return;
1245
+ if (!record$8(entries)) return;
1190
1246
  for (const [name, raw] of Object.entries(entries)) {
1191
1247
  const value = resolve(file, name, raw);
1192
1248
  if (isTypescriptInstall(name, value)) {
@@ -1226,13 +1282,13 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1226
1282
  }
1227
1283
  };
1228
1284
  for (const key of installSections) checkEntries(json[key], "install");
1229
- if (record$7(json["peerDependencies"])) for (const [name, value] of Object.entries(json["peerDependencies"])) {
1285
+ if (record$8(json["peerDependencies"])) for (const [name, value] of Object.entries(json["peerDependencies"])) {
1230
1286
  const resolved = resolve(file, name, value);
1231
1287
  if (isTypescriptInstall(name, resolved) && !peerRange(resolved)) checkEntries({ [name]: value }, "peer");
1232
1288
  }
1233
1289
  for (const source of catalogSources(json)) {
1234
1290
  checkEntries(source["catalog"], "catalog");
1235
- if (!record$7(source["catalogs"])) continue;
1291
+ if (!record$8(source["catalogs"])) continue;
1236
1292
  for (const catalog of Object.values(source["catalogs"])) checkEntries(catalog, "catalog");
1237
1293
  }
1238
1294
  const ownedNames = /* @__PURE__ */ new Set([...pins.keys(), ...typescriptPackages]);
@@ -1244,11 +1300,11 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1244
1300
  "optionalDependencies"
1245
1301
  ]) {
1246
1302
  const entries = resolutionRoot[section];
1247
- if (!record$7(entries)) continue;
1303
+ if (!record$8(entries)) continue;
1248
1304
  for (const [name, value] of Object.entries(entries)) resolutionDependencies[name] = resolve(file, name, value);
1249
1305
  }
1250
1306
  const checkResolutionMap = (entries, parent) => {
1251
- if (!record$7(entries)) return;
1307
+ if (!record$8(entries)) return;
1252
1308
  for (const [selector, replacement] of Object.entries(entries)) {
1253
1309
  const target = selector === "." ? parent : selector;
1254
1310
  if (target === void 0) continue;
@@ -1267,7 +1323,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1267
1323
  });
1268
1324
  continue;
1269
1325
  }
1270
- if (record$7(replacement)) {
1326
+ if (record$8(replacement)) {
1271
1327
  checkResolutionMap(replacement, target);
1272
1328
  continue;
1273
1329
  }
@@ -1287,8 +1343,8 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1287
1343
  };
1288
1344
  checkResolutionMap(json["overrides"]);
1289
1345
  checkResolutionMap(json["resolutions"]);
1290
- if (record$7(json["pnpm"])) checkResolutionMap(json["pnpm"]["overrides"]);
1291
- const command = record$7(json["scripts"]) ? json["scripts"]["typecheck"] : void 0;
1346
+ if (record$8(json["pnpm"])) checkResolutionMap(json["pnpm"]["overrides"]);
1347
+ const command = record$8(json["scripts"]) ? json["scripts"]["typecheck"] : void 0;
1292
1348
  if (typeof command === "string") for (const words of compilerCommandSegments(command)) {
1293
1349
  if (!invokesCompiler(words)) continue;
1294
1350
  const normalized = words.join(" ");
@@ -1302,7 +1358,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1302
1358
  });
1303
1359
  break;
1304
1360
  }
1305
- if (record$7(json["peerDependencies"])) for (const [name, raw] of Object.entries(json["peerDependencies"])) {
1361
+ if (record$8(json["peerDependencies"])) for (const [name, raw] of Object.entries(json["peerDependencies"])) {
1306
1362
  if (!typescriptPackages.has(name)) continue;
1307
1363
  const range = resolve(file, name, raw);
1308
1364
  if (!peerRange(range)) continue;
@@ -1326,7 +1382,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1326
1382
  message: `${name} peer support range must include ${release}, found ${range}`
1327
1383
  });
1328
1384
  }
1329
- if (record$7(json["engines"]) && json["engines"]["node"] !== void 0 && (typeof json["engines"]["node"] !== "string" || !nodeSupportRangeMatches(policy.node, json["engines"]["node"]))) add({
1385
+ if (record$8(json["engines"]) && json["engines"]["node"] !== void 0 && (typeof json["engines"]["node"] !== "string" || !nodeSupportRangeMatches(policy.node, json["engines"]["node"]))) add({
1330
1386
  file,
1331
1387
  rule: "node-engine",
1332
1388
  key: "node",
@@ -1340,9 +1396,23 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1340
1396
  if (json === void 0) return `${file} is missing or invalid; remove the entry`;
1341
1397
  const declares = (name) => installSections.some((section) => {
1342
1398
  const entries = json[section];
1343
- return record$7(entries) && Object.hasOwn(entries, name);
1399
+ return record$8(entries) && Object.hasOwn(entries, name);
1344
1400
  });
1345
1401
  if (!declares(entry.name)) return `${file} no longer declares ${entry.name} in dependencies, devDependencies or optionalDependencies; remove the entry`;
1402
+ if (entry.kind === "source-api") {
1403
+ for (const source of entry.sources) {
1404
+ let owner = path.posix.dirname(source);
1405
+ while (owner !== "." && !manifests.has(`${owner}/package.json`)) owner = path.posix.dirname(owner);
1406
+ if (owner !== entry.workspace) return `${source} belongs to workspace ${owner}, not ${entry.workspace}`;
1407
+ const problem = sourceApiImportProblem({
1408
+ source,
1409
+ content: files[source],
1410
+ name: entry.name
1411
+ });
1412
+ if (problem !== void 0) return `${source}: ${problem}`;
1413
+ }
1414
+ return;
1415
+ }
1346
1416
  if (!declares(entry.dependency)) return `${file} no longer depends on ${entry.dependency}; remove the entry and the ${entry.name} declaration, or name the dependency that still needs it`;
1347
1417
  };
1348
1418
  for (const entry of typescriptAllowlist) {
@@ -1379,9 +1449,9 @@ const extensionKinds = new Map(Object.entries({
1379
1449
  const scriptSourceKind = (file) => extensionKinds.get(extname(file));
1380
1450
  //#endregion
1381
1451
  //#region src/toolchain-script-lifecycle.ts
1382
- const record$6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1452
+ const record$7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1383
1453
  const declaredManager = (value) => typeof value === "string" ? /^(bun|npm|pnpm|yarn)@(\d+\.\d+\.\d+)(?:\+\S+)?$/u.exec(value) : null;
1384
- const rootScriptManager = (manifest, context) => declaredManager(context.packageManager ?? (record$6(manifest) ? manifest["packageManager"] : void 0))?.at(1);
1454
+ const rootScriptManager = (manifest, context) => declaredManager(context.packageManager ?? (record$7(manifest) ? manifest["packageManager"] : void 0))?.at(1);
1385
1455
  const npmHooksEnabled = (file) => {
1386
1456
  if (file?.type === "unknown") throw new Error("npm lifecycle configuration must be a tracked regular file");
1387
1457
  if (file?.type !== "tracked") return true;
@@ -1412,14 +1482,18 @@ const yamlSettings = (file, manager) => {
1412
1482
  } catch {
1413
1483
  throw new Error(`Cannot parse ${manager} lifecycle configuration`);
1414
1484
  }
1415
- if (!record$6(settings)) throw new Error(`${manager} lifecycle configuration must be a mapping`);
1485
+ if (!record$7(settings)) throw new Error(`${manager} lifecycle configuration must be a mapping`);
1416
1486
  return settings;
1417
1487
  };
1488
+ const pnpmLifecycleVersions = ["12.9.1", "12.15.0"];
1418
1489
  const pnpmHooksEnabled = (manifest, context) => {
1419
1490
  const declared = declaredManager(context.packageManager ?? manifest["packageManager"]);
1420
- const dependency = [manifest["dependencies"], manifest["devDependencies"]].flatMap((field) => record$6(field) ? [field["pnpm"]] : []).find((value) => typeof value === "string");
1421
- if ((declared?.at(1) === "pnpm" ? declared.at(2) : dependency) !== "12.9.1") throw new Error("pnpm lifecycle discovery requires declared pnpm@12.9.1");
1422
- const enabled = yamlSettings(context.pnpmWorkspace, "pnpm")["enablePrePostScripts"];
1491
+ const dependency = [manifest["dependencies"], manifest["devDependencies"]].flatMap((field) => record$7(field) ? [field["pnpm"]] : []).find((value) => typeof value === "string");
1492
+ const version = declared?.at(1) === "pnpm" ? declared.at(2) : dependency;
1493
+ if (!pnpmLifecycleVersions.some((supported) => version === supported)) throw new Error(`pnpm lifecycle discovery requires declared pnpm@${pnpmLifecycleVersions.join(" or pnpm@")}`);
1494
+ const settings = yamlSettings(context.pnpmWorkspace, "pnpm");
1495
+ if ("include" in settings) throw new Error("Delegated pnpm settings require explicit lifecycle discovery support");
1496
+ const enabled = settings["enablePrePostScripts"];
1423
1497
  if (enabled === void 0) return true;
1424
1498
  if (typeof enabled !== "boolean") throw new Error("pnpm enablePrePostScripts must be a literal boolean");
1425
1499
  return enabled;
@@ -1461,7 +1535,7 @@ const scriptLifecycleStages = ({ name, manager, manifest, scripts, context }) =>
1461
1535
  };
1462
1536
  //#endregion
1463
1537
  //#region src/toolchain-lint-command.ts
1464
- const record$5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1538
+ const record$6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1465
1539
  const scriptManagers = /* @__PURE__ */ new Set([
1466
1540
  "bun",
1467
1541
  "npm",
@@ -1578,7 +1652,7 @@ const lintInventoryCommands = (manifest, forceEnabled = false, context = {}) =>
1578
1652
  const errors = [];
1579
1653
  const workspaceTasks = /* @__PURE__ */ new Set();
1580
1654
  const filteredBuilds = /* @__PURE__ */ new Set();
1581
- const declaresEngine = record$5(manifest) && [manifest["dependencies"], manifest["devDependencies"]].some((dependencies) => record$5(dependencies) && "oxlint-tsgolint" in dependencies);
1655
+ const declaresEngine = record$6(manifest) && [manifest["dependencies"], manifest["devDependencies"]].some((dependencies) => record$6(dependencies) && "oxlint-tsgolint" in dependencies);
1582
1656
  const enabled = forceEnabled || declaresEngine;
1583
1657
  if (!enabled) return {
1584
1658
  commands,
@@ -1586,14 +1660,14 @@ const lintInventoryCommands = (manifest, forceEnabled = false, context = {}) =>
1586
1660
  enabled,
1587
1661
  workspaceTask: false
1588
1662
  };
1589
- if (!record$5(manifest)) return {
1663
+ if (!record$6(manifest)) return {
1590
1664
  commands,
1591
1665
  errors: ["lint inventory requires an object package manifest"],
1592
1666
  enabled,
1593
1667
  workspaceTask: false
1594
1668
  };
1595
1669
  const scripts = manifest["scripts"];
1596
- if (!record$5(scripts)) return {
1670
+ if (!record$6(scripts)) return {
1597
1671
  commands,
1598
1672
  errors: ["type-aware linting requires a root lint script"],
1599
1673
  enabled,
@@ -1735,7 +1809,7 @@ const checkProjectOwnership = ({ trackedFiles, lintedFiles, projectFiles }) => [
1735
1809
  line: 1,
1736
1810
  message: "type-aware lint would use an inferred project; include this file in a declared tsconfig project (for Node scripts, add scripts/tsconfig.json extending @stll/typescript-config/node.json; see the Node scripts recipe in stella/tooling README)"
1737
1811
  }));
1738
- const record$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1812
+ const record$5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1739
1813
  const stringList = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
1740
1814
  const readCommand = ({ root, executable, args, runtime }) => {
1741
1815
  const result = spawnSync(runtime, [
@@ -1779,7 +1853,7 @@ const resolveProjectApi = (root) => {
1779
1853
  for (const base of [import.meta.url, path.join(root, "package.json")]) try {
1780
1854
  return pathToFileURL(createRequire(base).resolve("tsrs/unstable/async")).href;
1781
1855
  } catch (error) {
1782
- const code = record$4(error) ? error["code"] : void 0;
1856
+ const code = record$5(error) ? error["code"] : void 0;
1783
1857
  if (code !== "MODULE_NOT_FOUND" && code !== "ERR_MODULE_NOT_FOUND") throw error;
1784
1858
  }
1785
1859
  throw new Error("configuration discovery needs tsrs: reinstall @stll/oxlint-config, which depends on it");
@@ -1802,7 +1876,7 @@ const readProjectConfig = (root, config) => {
1802
1876
  if (result.error !== void 0) throw result.error;
1803
1877
  if (result.status !== 0) throw new Error(`configuration discovery failed: ${(result.stderr + result.stdout).trim()}`);
1804
1878
  const parsed = JSON.parse(result.stdout);
1805
- if (!record$4(parsed) || !stringList(parsed["files"]) || !stringList(parsed["references"]) || typeof parsed["allowJs"] !== "boolean" || typeof parsed["checkJs"] !== "boolean") throw new Error("compiler configuration must list files and references");
1879
+ if (!record$5(parsed) || !stringList(parsed["files"]) || !stringList(parsed["references"]) || typeof parsed["allowJs"] !== "boolean" || typeof parsed["checkJs"] !== "boolean") throw new Error("compiler configuration must list files and references");
1806
1880
  return {
1807
1881
  files: parsed["files"],
1808
1882
  references: parsed["references"],
@@ -1827,7 +1901,7 @@ const lifecycleFile = (root, file, tracked) => {
1827
1901
  };
1828
1902
  /** Inventory and config expansion only: neither subprocess lints or typechecks. */
1829
1903
  const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
1830
- const packageManager = record$4(manifest) ? manifest["packageManager"] : void 0;
1904
+ const packageManager = record$5(manifest) ? manifest["packageManager"] : void 0;
1831
1905
  const managerUsesPnpmWorkspace = packageManager === void 0 || typeof packageManager === "string" && packageManager.startsWith("pnpm@");
1832
1906
  const discovered = /* @__PURE__ */ new Map();
1833
1907
  const workspaceMembers = (pnpmMembership) => {
@@ -1863,7 +1937,7 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
1863
1937
  return nestedLifecycle;
1864
1938
  };
1865
1939
  const lifecycle = {
1866
- packageManager: record$4(manifest) ? manifest["packageManager"] : void 0,
1940
+ packageManager: record$5(manifest) ? manifest["packageManager"] : void 0,
1867
1941
  npmrc: lifecycleFile(root, ".npmrc", trackedFiles),
1868
1942
  pnpmWorkspace: lifecycleFile(root, "pnpm-workspace.yaml", trackedFiles),
1869
1943
  yarnrc: lifecycleFile(root, ".yarnrc.yml", trackedFiles),
@@ -1874,7 +1948,7 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
1874
1948
  if (member === void 0) throw new Error("Missing selected workspace");
1875
1949
  const [directory, pkg] = member;
1876
1950
  const scripts = pkg["scripts"];
1877
- if (!record$4(scripts) || typeof scripts["build"] !== "string") throw new Error("Filtered Bun build requires a literal build script");
1951
+ if (!record$5(scripts) || typeof scripts["build"] !== "string") throw new Error("Filtered Bun build requires a literal build script");
1878
1952
  if (["prebuild", "postbuild"].some((name) => scripts[name] !== void 0 && scripts[name] !== "")) throw new Error("Filtered Bun build lifecycle hooks are unsupported for lint inventory; invoke lint scopes explicitly");
1879
1953
  if (activeBuilds.has(directory)) throw new Error("Cyclic filtered Bun build delegation");
1880
1954
  activeBuilds.add(directory);
@@ -1916,7 +1990,7 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
1916
1990
  for (const [directory, workspace] of workspaceMembers(managerUsesPnpmWorkspace)) {
1917
1991
  if (directory === ".") continue;
1918
1992
  const file = `${directory}/package.json`;
1919
- if (!record$4(workspace["scripts"]) || workspace["scripts"]["lint"] === void 0) continue;
1993
+ if (!record$5(workspace["scripts"]) || workspace["scripts"]["lint"] === void 0) continue;
1920
1994
  const nestedLifecycle = workspaceLifecycle(directory);
1921
1995
  const nested = lintInventoryCommands(workspace, true, nestedLifecycle);
1922
1996
  if (nested.workspaceTask) throw new Error(`nested workspace lint delegation is unsupported: ${file}`);
@@ -2025,7 +2099,7 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
2025
2099
  };
2026
2100
  //#endregion
2027
2101
  //#region src/toolchain-rust.ts
2028
- const record$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2102
+ const record$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2029
2103
  /**
2030
2104
  * Exceptions authorize one immutable release, never a channel or an entire
2031
2105
  * rule. Expiry is scoped separately by checkRustExceptionExpiry.
@@ -2035,7 +2109,7 @@ const parseRustExceptions = (input) => {
2035
2109
  if (!Array.isArray(input)) throw new Error("rustExceptions must be an array");
2036
2110
  const pins = /* @__PURE__ */ new Set();
2037
2111
  return input.map((entry) => {
2038
- if (!record$3(entry) || Object.keys(entry).length !== 4 || Object.keys(entry).some((key) => ![
2112
+ if (!record$4(entry) || Object.keys(entry).length !== 4 || Object.keys(entry).some((key) => ![
2039
2113
  "pin",
2040
2114
  "reason",
2041
2115
  "tracking",
@@ -2053,8 +2127,8 @@ const parseRustExceptions = (input) => {
2053
2127
  /** Whether the base stll-toolchain.json declares this exception with the same expiry. */
2054
2128
  const declaresException = (baseText, { pin, expires }) => {
2055
2129
  const base = JSON.parse(baseText);
2056
- const exceptions = record$3(base) ? base["rustExceptions"] : void 0;
2057
- return Array.isArray(exceptions) && exceptions.some((entry) => record$3(entry) && entry["pin"] === pin && entry["expires"] === expires);
2130
+ const exceptions = record$4(base) ? base["rustExceptions"] : void 0;
2131
+ return Array.isArray(exceptions) && exceptions.some((entry) => record$4(entry) && entry["pin"] === pin && entry["expires"] === expires);
2058
2132
  };
2059
2133
  /** An exception expires on its date; the scope decides whether that fails this run. */
2060
2134
  const checkRustExceptionExpiry = ({ exceptions, now = /* @__PURE__ */ new Date(), scope }) => {
@@ -2192,7 +2266,9 @@ const checkRustReferences = (options) => {
2192
2266
  }
2193
2267
  if (token.value.startsWith("-")) continue;
2194
2268
  selected++;
2195
- select(token.value, "rustup toolchain", token.line);
2269
+ const installFixture = options.type === "file" && isShellScript(file, text) && subcommand === "toolchain" && invocation.at(1)?.value === "install" && !invocation.some((word) => word.value === "--default") ? options.installFixtures?.find((entry) => entry.path === file && entry.pin === token.value) : void 0;
2270
+ if (installFixture !== void 0) options.onInstallFixture?.(installFixture);
2271
+ else select(token.value, "rustup toolchain", token.line);
2196
2272
  if (firstSelectorOnly) break;
2197
2273
  }
2198
2274
  if (selected === 0 && requiresSelector) select(void 0, "rustup toolchain", firstLine);
@@ -2213,7 +2289,7 @@ const checkRustReferences = (options) => {
2213
2289
  if (isShellScript(file, text)) shell(text);
2214
2290
  if (/(?:^|\/)package\.json$/.test(file)) try {
2215
2291
  const object = JSON.parse(text);
2216
- if (record$3(object) && record$3(object["scripts"])) {
2292
+ if (record$4(object) && record$4(object["scripts"])) {
2217
2293
  for (const [name, script] of Object.entries(object["scripts"])) if (typeof script === "string") shell(script, lineOf(`"${name}"`));
2218
2294
  }
2219
2295
  } catch {}
@@ -2224,7 +2300,7 @@ const checkRustReferences = (options) => {
2224
2300
  const version = tag === void 0 ? void 0 : rustImageVersion(tag);
2225
2301
  select(version, "Rust service image", lineOf(value));
2226
2302
  } else if (Array.isArray(value)) value.forEach(images);
2227
- else if (record$3(value)) {
2303
+ else if (record$4(value)) {
2228
2304
  for (const [key, entry] of Object.entries(value)) if (key !== "name") images(entry);
2229
2305
  }
2230
2306
  };
@@ -2248,13 +2324,13 @@ const checkRustReferences = (options) => {
2248
2324
  value.forEach(visit);
2249
2325
  return;
2250
2326
  }
2251
- if (!record$3(value)) return;
2252
- if (record$3(value["env"]) && value["env"]["RUSTUP_TOOLCHAIN"] !== void 0) select(value["env"]["RUSTUP_TOOLCHAIN"], "RUSTUP_TOOLCHAIN", lineOf("RUSTUP_TOOLCHAIN"));
2327
+ if (!record$4(value)) return;
2328
+ if (record$4(value["env"]) && value["env"]["RUSTUP_TOOLCHAIN"] !== void 0) select(value["env"]["RUSTUP_TOOLCHAIN"], "RUSTUP_TOOLCHAIN", lineOf("RUSTUP_TOOLCHAIN"));
2253
2329
  if (typeof value["run"] === "string") shell(value["run"], lineOf("run:"));
2254
2330
  const uses = value["uses"];
2255
2331
  if (typeof uses === "string") {
2256
2332
  const action = uses.split("@").at(0)?.toLowerCase();
2257
- const inputs = record$3(value["with"]) ? value["with"] : {};
2333
+ const inputs = record$4(value["with"]) ? value["with"] : {};
2258
2334
  if (action === "dtolnay/rust-toolchain" || action === "actions-rust-lang/setup-rust-toolchain") {
2259
2335
  const entries = Object.entries(inputs).filter(([key]) => key.toLowerCase() === "toolchain");
2260
2336
  if (entries.length !== 1) select(void 0, `${action} requires an explicit exact toolchain input`, lineOf(uses));
@@ -2268,8 +2344,8 @@ const checkRustReferences = (options) => {
2268
2344
  }
2269
2345
  }
2270
2346
  for (const [key, child] of Object.entries(value)) {
2271
- if (key === "workflow_call" && record$3(child) && record$3(child["inputs"])) {
2272
- for (const [input, declaration] of Object.entries(child["inputs"])) if (["toolchain", "rust-toolchain"].includes(input) && record$3(declaration) && declaration["default"] !== void 0) select(declaration["default"], `reusable workflow default ${input}`, lineOf("default:"));
2347
+ if (key === "workflow_call" && record$4(child) && record$4(child["inputs"])) {
2348
+ for (const [input, declaration] of Object.entries(child["inputs"])) if (["toolchain", "rust-toolchain"].includes(input) && record$4(declaration) && declaration["default"] !== void 0) select(declaration["default"], `reusable workflow default ${input}`, lineOf("default:"));
2273
2349
  }
2274
2350
  visit(child);
2275
2351
  }
@@ -2280,7 +2356,7 @@ const checkRustReferences = (options) => {
2280
2356
  };
2281
2357
  //#endregion
2282
2358
  //#region src/toolchain-workflow-trust.ts
2283
- const record$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2359
+ const record$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2284
2360
  const booleanParts = (source, operator) => {
2285
2361
  const parts = [];
2286
2362
  let depth = 0;
@@ -2530,7 +2606,7 @@ const safeAction = (uses, inputs) => {
2530
2606
  const approved = Object.entries(workflowTrustBootstrapCatalog.actions).find(([name]) => name === repository);
2531
2607
  if (approved === void 0) return false;
2532
2608
  if (inputs === void 0) return repository === "actions/checkout";
2533
- if (!record$2(inputs)) return false;
2609
+ if (!record$3(inputs)) return false;
2534
2610
  if (repository !== "actions/checkout") {
2535
2611
  const version = inputs["bun-version"];
2536
2612
  if (typeof version !== "string" || !/^v?[0-9]+(?:\.(?:[0-9]+|x)){0,2}$/u.test(version)) return false;
@@ -2543,7 +2619,7 @@ const safeAction = (uses, inputs) => {
2543
2619
  ].includes(typeof value) && (name !== "submodules" || value === false || value === "false"));
2544
2620
  };
2545
2621
  const safeShell = (shell) => shell === void 0 || shell === "bash" || shell === "sh";
2546
- const safeEnvironment = (value) => value === void 0 || record$2(value) && Object.keys(value).every((name) => [
2622
+ const safeEnvironment = (value) => value === void 0 || record$3(value) && Object.keys(value).every((name) => [
2547
2623
  "HEAD_REPO",
2548
2624
  "BASE_REPO",
2549
2625
  "HAS_RUN_CI_LABEL",
@@ -2557,25 +2633,25 @@ const safeEnvironment = (value) => value === void 0 || record$2(value) && Object
2557
2633
  "HAWK_VERSION",
2558
2634
  "HAWK_X86_64_UNKNOWN_LINUX_GNU_SHA256"
2559
2635
  ].includes(name));
2560
- const configuredShell = (value) => record$2(value) && record$2(value["run"]) ? value["run"]["shell"] : void 0;
2636
+ const configuredShell = (value) => record$3(value) && record$3(value["run"]) ? value["run"]["shell"] : void 0;
2561
2637
  const workflowTrustRules = ["workflow-trust"];
2562
2638
  const workflowEvents = (triggers) => {
2563
2639
  if (typeof triggers === "string") return [triggers];
2564
2640
  if (Array.isArray(triggers)) return triggers;
2565
- if (record$2(triggers)) return Object.keys(triggers);
2641
+ if (record$3(triggers)) return Object.keys(triggers);
2566
2642
  return [];
2567
2643
  };
2568
2644
  /** Enforce a workflow's declared trust boundary before repository dependency execution. */
2569
2645
  const checkWorkflowTrust = (workflow) => {
2570
2646
  const findings = [];
2571
- if (!record$2(workflow) || !record$2(workflow["jobs"])) return findings;
2647
+ if (!record$3(workflow) || !record$3(workflow["jobs"])) return findings;
2572
2648
  const jobs = workflow["jobs"];
2573
- if (!record$2(jobs["trust-check"])) return findings;
2649
+ if (!record$3(jobs["trust-check"])) return findings;
2574
2650
  const triggers = workflow["on"];
2575
2651
  const pullRequests = workflowEvents(triggers).filter((event) => event === "pull_request" || event === "pull_request_target");
2576
2652
  if (pullRequests.length === 0) return findings;
2577
2653
  for (const [name, job] of Object.entries(jobs)) {
2578
- if (!record$2(job)) continue;
2654
+ if (!record$3(job)) continue;
2579
2655
  const needs = job["needs"];
2580
2656
  const ordered = name !== "trust-check" && (needs === "trust-check" || Array.isArray(needs) && needs.includes("trust-check"));
2581
2657
  const permitsExecution = (stepCondition) => {
@@ -2612,7 +2688,7 @@ const checkWorkflowTrust = (workflow) => {
2612
2688
  message
2613
2689
  });
2614
2690
  const services = job["services"];
2615
- if (services !== void 0 && (!record$2(services) || Object.keys(services).length > 0)) findings.push({
2691
+ if (services !== void 0 && (!record$3(services) || Object.keys(services).length > 0)) findings.push({
2616
2692
  type: "job",
2617
2693
  property: "services",
2618
2694
  job: name,
@@ -2621,7 +2697,7 @@ const checkWorkflowTrust = (workflow) => {
2621
2697
  }
2622
2698
  if (!Array.isArray(job["steps"])) continue;
2623
2699
  for (const [index, step] of job["steps"].entries()) {
2624
- if (!record$2(step)) continue;
2700
+ if (!record$3(step)) continue;
2625
2701
  const run = step["run"];
2626
2702
  const uses = step["uses"];
2627
2703
  const unsafeRun = typeof run === "string" && (invokesRepositoryCode(run) || !safeShell(step["shell"] ?? configuredShell(job["defaults"]) ?? configuredShell(workflow["defaults"])) || ![
@@ -2659,7 +2735,7 @@ const runtimeRules = [
2659
2735
  "action-pins",
2660
2736
  ...workflowTrustRules
2661
2737
  ];
2662
- const record$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2738
+ const record$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2663
2739
  const dockerEnvironmentAssignments = ({ source, escape }) => {
2664
2740
  const words = [];
2665
2741
  let word = "";
@@ -2792,13 +2868,15 @@ const pythonRequirementMatches = (requirement, version) => {
2792
2868
  });
2793
2869
  };
2794
2870
  /** Check runtime declarations only in the files that declare or configure them. */
2795
- const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, repository, onDelegated, dynamicSelectors = [], onDynamicSelector, rustExceptions = [], engineFloors = [], onEngineFloor }) => {
2871
+ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, repository, onDelegated, dynamicSelectors = [], onDynamicSelector, rustExceptions = [], rustInstallFixtures = [], onRustInstallFixture, engineFloors = [], onEngineFloor }) => {
2796
2872
  const diagnostics = checkRustReferences({
2797
2873
  type: "file",
2798
2874
  file,
2799
2875
  text,
2800
2876
  rust: policy.rust,
2801
- exceptions: rustExceptions
2877
+ exceptions: rustExceptions,
2878
+ installFixtures: rustInstallFixtures,
2879
+ onInstallFixture: onRustInstallFixture
2802
2880
  });
2803
2881
  const name = path.posix.basename(file);
2804
2882
  const lines = text.split(/\r?\n/);
@@ -2894,7 +2972,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
2894
2972
  const toolchain = parsed["toolchain"];
2895
2973
  pin({
2896
2974
  rule: "rust-version",
2897
- value: record$1(toolchain) ? toolchain["channel"] : void 0,
2975
+ value: record$2(toolchain) ? toolchain["channel"] : void 0,
2898
2976
  expected: policy.rust,
2899
2977
  label: "Rust channel",
2900
2978
  line: lineOf("channel")
@@ -2922,15 +3000,15 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
2922
3000
  };
2923
3001
  if (parsed !== void 0) {
2924
3002
  const workspace = parsed["workspace"];
2925
- const defaults = record$1(workspace) ? workspace["package"] : void 0;
2926
- if (record$1(defaults) && defaults["rust-version"] !== void 0) rustFloor(defaults["rust-version"]);
3003
+ const defaults = record$2(workspace) ? workspace["package"] : void 0;
3004
+ if (record$2(defaults) && defaults["rust-version"] !== void 0) rustFloor(defaults["rust-version"]);
2927
3005
  const pkg = parsed["package"];
2928
- const version = record$1(pkg) ? pkg["rust-version"] : void 0;
3006
+ const version = record$2(pkg) ? pkg["rust-version"] : void 0;
2929
3007
  if (version !== void 0) {
2930
- if (!record$1(version)) rustFloor(version);
3008
+ if (!record$2(version)) rustFloor(version);
2931
3009
  else {
2932
3010
  let inherited;
2933
- if (version["workspace"] === true && record$1(pkg)) {
3011
+ if (version["workspace"] === true && record$2(pkg)) {
2934
3012
  const explicit = pkg["workspace"];
2935
3013
  let directory = path.posix.dirname(file);
2936
3014
  const explicitPath = typeof explicit === "string" && !explicit.includes("\\") && !explicit.includes(":") && !explicit.includes("${{") && !path.posix.isAbsolute(explicit) ? path.posix.normalize(path.posix.join(directory, explicit)) : void 0;
@@ -2938,9 +3016,9 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
2938
3016
  if (explicitPath !== void 0) directory = explicitPath;
2939
3017
  while (true) {
2940
3018
  const table = readManifest(path.posix.join(directory, "Cargo.toml"))?.["workspace"];
2941
- if (record$1(table)) {
3019
+ if (record$2(table)) {
2942
3020
  const fields = table["package"];
2943
- inherited = record$1(fields) ? fields["rust-version"] : void 0;
3021
+ inherited = record$2(fields) ? fields["rust-version"] : void 0;
2944
3022
  break;
2945
3023
  }
2946
3024
  if (explicit !== void 0 || directory === ".") break;
@@ -2955,7 +3033,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
2955
3033
  }
2956
3034
  if (name === "Pipfile") {
2957
3035
  const requires = toml("python-version")?.["requires"];
2958
- if (record$1(requires)) {
3036
+ if (record$2(requires)) {
2959
3037
  const minor = requires["python_version"];
2960
3038
  if (minor !== void 0 && minor !== policy.python.split(".").slice(0, 2).join(".")) add({
2961
3039
  rule: "python-version",
@@ -2974,7 +3052,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
2974
3052
  const parsed = toml("python-version");
2975
3053
  if (parsed !== void 0) {
2976
3054
  const project = parsed["project"];
2977
- if (record$1(project) && project["requires-python"] !== void 0) {
3055
+ if (record$2(project) && project["requires-python"] !== void 0) {
2978
3056
  if (!pythonRequirementMatches(project["requires-python"], policy.python)) add({
2979
3057
  rule: "python-version",
2980
3058
  line: lineOf("requires-python"),
@@ -2982,9 +3060,9 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
2982
3060
  });
2983
3061
  }
2984
3062
  const tool = parsed["tool"];
2985
- const configuredUv = record$1(tool) ? tool["uv"] : void 0;
3063
+ const configuredUv = record$2(tool) ? tool["uv"] : void 0;
2986
3064
  const uv = name === "uv.toml" ? parsed : configuredUv;
2987
- if (record$1(uv) && uv["python"] !== void 0) pin({
3065
+ if (record$2(uv) && uv["python"] !== void 0) pin({
2988
3066
  rule: "python-version",
2989
3067
  value: uv["python"],
2990
3068
  expected: policy.python,
@@ -2995,11 +3073,11 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
2995
3073
  }
2996
3074
  if (isMiseConfigPath(file)) {
2997
3075
  const tools = toml("runtime-manager")?.["tools"];
2998
- if (record$1(tools)) for (const [tool, expected] of Object.entries(versions)) {
3076
+ if (record$2(tools)) for (const [tool, expected] of Object.entries(versions)) {
2999
3077
  const configured = tools[tool];
3000
3078
  if (configured !== void 0) pin({
3001
3079
  rule: tool === "rust" || tool === "core:rust" ? "rust-version" : "runtime-manager",
3002
- value: record$1(configured) ? configured["version"] : configured,
3080
+ value: record$2(configured) ? configured["version"] : configured,
3003
3081
  expected,
3004
3082
  selector: nodeTools.has(tool) ? "node" : void 0,
3005
3083
  label: `mise ${tool}`,
@@ -3347,7 +3425,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
3347
3425
  return diagnostics;
3348
3426
  }
3349
3427
  const nodeLine = (node) => {
3350
- if (!record$1(node) || !Array.isArray(node["range"])) return 1;
3428
+ if (!record$2(node) || !Array.isArray(node["range"])) return 1;
3351
3429
  const offset = node["range"].at(0);
3352
3430
  return typeof offset === "number" ? text.slice(0, offset).split("\n").length : 1;
3353
3431
  };
@@ -3461,7 +3539,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
3461
3539
  const manifest = JSON.parse(content ?? "");
3462
3540
  pin({
3463
3541
  rule,
3464
- value: record$1(manifest) ? manifest["packageManager"] : void 0,
3542
+ value: record$2(manifest) ? manifest["packageManager"] : void 0,
3465
3543
  expected: `bun@${policy.bun}`,
3466
3544
  label: "setup-bun version file packageManager",
3467
3545
  line
@@ -3661,10 +3739,21 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
3661
3739
  const selector = staticRepositoryPath(value) ? path.posix.normalize(value) : void 0;
3662
3740
  const providers = checkoutProvidersFor(selector);
3663
3741
  const binding = selector === void 0 ? void 0 : providers.at(-1);
3664
- if (selector === void 0 || unknownCheckoutDestination || binding !== void 0 && binding.source !== "tracked" || hasAmbiguousCheckoutWriters(selector, providers)) add({
3665
- rule: "action-pins",
3742
+ if (selector === void 0 || unknownCheckoutDestination || binding?.source === "untrusted" || binding?.source === "delegated" && !/^(?:inputs\.[A-Za-z_][A-Za-z0-9_]*|github\.event\.workflow_run\.head_sha)$/.test(dynamicRefBody(binding.ref) ?? "") || hasAmbiguousCheckoutWriters(selector, providers)) {
3743
+ add({
3744
+ rule: "action-pins",
3745
+ line,
3746
+ message: "local actions require an unambiguous checkout of the current source snapshot"
3747
+ });
3748
+ return;
3749
+ }
3750
+ if (binding?.source === "delegated") onDelegated?.({
3751
+ path: file,
3666
3752
  line,
3667
- message: "local actions require an unambiguous checkout of the current source snapshot"
3753
+ tool: "action",
3754
+ selector,
3755
+ checkoutPath: binding.path,
3756
+ ref: binding.ref
3668
3757
  });
3669
3758
  return;
3670
3759
  }
@@ -3962,6 +4051,54 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
3962
4051
  return diagnostics;
3963
4052
  };
3964
4053
  //#endregion
4054
+ //#region src/toolchain-rust-fixtures.ts
4055
+ const record$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4056
+ const date = (value) => /^\d{4}-\d{2}-\d{2}$/.test(value) && Number.isFinite(Date.parse(value)) && new Date(value).toISOString().slice(0, 10) === value;
4057
+ /** Historical install probes do not authorize using an older compiler. */
4058
+ const parseRustInstallFixtures = (input) => {
4059
+ if (input === void 0) return [];
4060
+ if (!Array.isArray(input)) throw new Error("rustInstallFixtures must be an array");
4061
+ const identities = /* @__PURE__ */ new Set();
4062
+ return input.map((entry) => {
4063
+ if (!record$1(entry) || Object.keys(entry).length !== 5 || Object.keys(entry).some((key) => ![
4064
+ "path",
4065
+ "pin",
4066
+ "reason",
4067
+ "tracking",
4068
+ "expires"
4069
+ ].includes(key)) || typeof entry["path"] !== "string" || !/^(?:[A-Za-z0-9_-]+\/)*[A-Za-z0-9_-]+\.sh$/.test(entry["path"]) || typeof entry["pin"] !== "string" || !(/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/.test(entry["pin"]) || entry["pin"].startsWith("nightly-") && date(entry["pin"].slice(8))) || typeof entry["reason"] !== "string" || entry["reason"].trim() === "" || typeof entry["tracking"] !== "string" || !/^https:\/\/github\.com\/[^/\s]+\/[^/\s]+\/(?:issues|pull)\/[1-9]\d*$/.test(entry["tracking"]) || typeof entry["expires"] !== "string" || !date(entry["expires"])) throw new Error("each Rust install fixture requires an exact shell path, stable or dated nightly pin, reason, GitHub tracking URL and valid expiry date");
4070
+ const identity = `${entry["path"]}:${entry["pin"]}`;
4071
+ if (identities.has(identity)) throw new Error("Rust install fixture path/pin pairs must be unique");
4072
+ identities.add(identity);
4073
+ return {
4074
+ path: entry["path"],
4075
+ pin: entry["pin"],
4076
+ reason: entry["reason"],
4077
+ tracking: entry["tracking"],
4078
+ expires: entry["expires"]
4079
+ };
4080
+ });
4081
+ };
4082
+ const checkRustInstallFixtureExpiry = ({ fixtures, scope, now = /* @__PURE__ */ new Date() }) => {
4083
+ const errors = [];
4084
+ const warnings = [];
4085
+ for (const fixture of fixtures) {
4086
+ if (fixture.expires > now.toISOString().slice(0, 10)) continue;
4087
+ const verdict = overdueVerdict(scope, "stll-toolchain.json", (text) => {
4088
+ const base = JSON.parse(text);
4089
+ const declarations = record$1(base) ? base["rustInstallFixtures"] : void 0;
4090
+ return Array.isArray(declarations) && declarations.some((entry) => record$1(entry) && entry["path"] === fixture.path && entry["pin"] === fixture.pin && entry["expires"] === fixture.expires);
4091
+ });
4092
+ const message = `Rust install fixture ${fixture.path}:${fixture.pin} expired on ${fixture.expires}; remove it or renew the documented fixture boundary`;
4093
+ if (!verdict.fail) warnings.push(message);
4094
+ else errors.push(verdict.unavailable === void 0 ? message : `${message}; cannot determine whether this change adds it: ${verdict.unavailable}`);
4095
+ }
4096
+ return {
4097
+ errors,
4098
+ warnings
4099
+ };
4100
+ };
4101
+ //#endregion
3965
4102
  //#region src/toolchain-guard.ts
3966
4103
  const toolchainRules = [.../* @__PURE__ */ new Set([
3967
4104
  ...packageRules,
@@ -3976,7 +4113,7 @@ const record = (value) => typeof value === "object" && value !== null && !Array.
3976
4113
  const array = (value) => Array.isArray(value);
3977
4114
  /** Repository-owned declarations are explicit; malformed decisions fail closed. */
3978
4115
  const parseToolchainConfiguration = (input) => {
3979
- if (!record(input) || Object.keys(input).some((key) => key !== "optOuts" && key !== "cloud" && key !== "engineFloors" && key !== "dynamicSelectors" && key !== "consumerChecks" && key !== "rustExceptions" && key !== "dependabotIgnores" && key !== "typescriptAllowlist") || input["optOuts"] !== void 0 && !array(input["optOuts"])) throw new Error("stll-toolchain.json accepts only optOuts, cloud, engineFloors, dynamicSelectors, consumerChecks, rustExceptions, dependabotIgnores and typescriptAllowlist declarations");
4116
+ if (!record(input) || Object.keys(input).some((key) => key !== "optOuts" && key !== "cloud" && key !== "engineFloors" && key !== "dynamicSelectors" && key !== "consumerChecks" && key !== "rustExceptions" && key !== "rustInstallFixtures" && key !== "dependabotIgnores" && key !== "typescriptAllowlist") || input["optOuts"] !== void 0 && !array(input["optOuts"])) throw new Error("stll-toolchain.json accepts only optOuts, cloud, engineFloors, dynamicSelectors, consumerChecks, rustExceptions, rustInstallFixtures, dependabotIgnores and typescriptAllowlist declarations");
3980
4117
  const disabled = /* @__PURE__ */ new Set();
3981
4118
  const optOuts = input["optOuts"] ?? [];
3982
4119
  if (!array(optOuts)) throw new Error("optOuts must be an array");
@@ -3993,6 +4130,7 @@ const parseToolchainConfiguration = (input) => {
3993
4130
  return {
3994
4131
  disabled,
3995
4132
  rustExceptions: parseRustExceptions(input["rustExceptions"]),
4133
+ rustInstallFixtures: parseRustInstallFixtures(input["rustInstallFixtures"]),
3996
4134
  cloud: parseCloudSetup(input["cloud"]),
3997
4135
  engineFloors: parseEngineFloors(input["engineFloors"]),
3998
4136
  dynamicSelectors: parseDynamicSelectors(input["dynamicSelectors"]),
@@ -4085,11 +4223,20 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
4085
4223
  if (files["stll-toolchain.json"] !== void 0) try {
4086
4224
  configuration = parseToolchainConfiguration(JSON.parse(files["stll-toolchain.json"]));
4087
4225
  dynamicSelectors = configuration.dynamicSelectors;
4088
- const expiry = checkRustExceptionExpiry({
4226
+ const rustExpiry = checkRustExceptionExpiry({
4089
4227
  exceptions: configuration.rustExceptions,
4090
4228
  now,
4091
4229
  scope: expiryScope
4092
4230
  });
4231
+ const fixtureExpiry = checkRustInstallFixtureExpiry({
4232
+ fixtures: configuration.rustInstallFixtures,
4233
+ now,
4234
+ scope: expiryScope
4235
+ });
4236
+ const expiry = {
4237
+ errors: [...rustExpiry.errors, ...fixtureExpiry.errors],
4238
+ warnings: [...rustExpiry.warnings, ...fixtureExpiry.warnings]
4239
+ };
4093
4240
  for (const message of expiry.errors) diagnostics.push({
4094
4241
  rule: "configuration",
4095
4242
  path: "stll-toolchain.json",
@@ -4111,6 +4258,23 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
4111
4258
  message: error instanceof Error ? error.message : String(error)
4112
4259
  });
4113
4260
  }
4261
+ for (const entry of configuration.typescriptAllowlist) {
4262
+ if (entry.kind !== "source-api") continue;
4263
+ for (const source of entry.sources) {
4264
+ const content = readSourceApiInput({
4265
+ root,
4266
+ source,
4267
+ tracked: trackedFiles
4268
+ });
4269
+ if (content !== void 0) files[source] = content;
4270
+ else diagnostics.push({
4271
+ rule: "configuration",
4272
+ path: source,
4273
+ line: 1,
4274
+ message: "TypeScript source API evidence must be tracked, readable, regular and free of symlinks"
4275
+ });
4276
+ }
4277
+ }
4114
4278
  const cloud = configuration.cloud;
4115
4279
  const script = files[cloudSetupPath];
4116
4280
  if (cloud === void 0 && trackedFiles.has(".agents/cloud-setup.sh")) diagnostics.push({
@@ -4223,6 +4387,7 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
4223
4387
  trackedFiles
4224
4388
  }));
4225
4389
  }
4390
+ const usedRustFixtures = /* @__PURE__ */ new Set();
4226
4391
  for (const [file, text] of Object.entries(files)) diagnostics.push(...checkRuntimeFile({
4227
4392
  file,
4228
4393
  text,
@@ -4235,6 +4400,8 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
4235
4400
  },
4236
4401
  trackedFiles,
4237
4402
  rustExceptions: configuration.rustExceptions,
4403
+ rustInstallFixtures: configuration.rustInstallFixtures,
4404
+ onRustInstallFixture: (entry) => usedRustFixtures.add(entry),
4238
4405
  dynamicSelectors: dynamicSelectors.filter((entry) => entry.path === file),
4239
4406
  onDynamicSelector: (entry) => selectorMatches.set(entry, (selectorMatches.get(entry) ?? 0) + 1),
4240
4407
  repository,
@@ -4246,6 +4413,12 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
4246
4413
  },
4247
4414
  readFile: (target) => files[target]
4248
4415
  }));
4416
+ for (const entry of configuration.rustInstallFixtures) if (!usedRustFixtures.has(entry)) diagnostics.push({
4417
+ rule: "configuration",
4418
+ path: "stll-toolchain.json",
4419
+ line: 1,
4420
+ message: `Rust install fixture ${entry.path}:${entry.pin} must match a tracked rustup toolchain install probe`
4421
+ });
4249
4422
  for (const entry of engineFloors) if (!exercisedFloors.has(entry) || mismatchedFloors.has(entry)) diagnostics.push({
4250
4423
  rule: "configuration",
4251
4424
  path: "stll-toolchain.json",