@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.
@@ -16,6 +16,8 @@ let semver = require("semver");
16
16
  let yaml = require("yaml");
17
17
  let node_module = require("node:module");
18
18
  let node_url = require("node:url");
19
+ let acorn = require("acorn");
20
+ let esbuild = require("esbuild");
19
21
  //#region src/toolchain-bun.ts
20
22
  const STABLE_RELEASE = /^\d+\.\d+\.\d+$/u;
21
23
  /**
@@ -34,9 +36,9 @@ const repositoryBun = (floor, manifest) => {
34
36
  };
35
37
  //#endregion
36
38
  //#region src/toolchain-consumer-checks.ts
37
- const consumerRunnerVersion = "3.0.0";
39
+ const consumerRunnerVersion = "3.2.0";
38
40
  const consumerCheckWorkflow = "stella/.github/.github/workflows/package-consumer-compat.yml";
39
- const record$10 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
41
+ const record$11 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
40
42
  const repositoryDirectory = (value) => typeof value === "string" && value !== "" && !value.includes("\\") && !value.includes("${{") && !value.split("").some((character) => {
41
43
  const code = character.charCodeAt(0);
42
44
  return code < 32 || code === 127;
@@ -122,12 +124,12 @@ const validConsumerCron = (text) => {
122
124
  });
123
125
  };
124
126
  const nightlySchedule = (value) => {
125
- if (!record$10(value)) return false;
127
+ if (!record$11(value)) return false;
126
128
  const schedule = value["schedule"];
127
129
  if (!Array.isArray(schedule) || schedule.length === 0) return false;
128
130
  const coveredDays = /* @__PURE__ */ new Set();
129
131
  for (const entry of schedule) {
130
- if (!record$10(entry) || typeof entry["cron"] !== "string" || !validConsumerCron(entry["cron"])) return false;
132
+ if (!record$11(entry) || typeof entry["cron"] !== "string" || !validConsumerCron(entry["cron"])) return false;
131
133
  const fields = entry["cron"].trim().split(/\s+/);
132
134
  const selections = cronFields.map((field, index) => {
133
135
  const text = fields.at(index);
@@ -143,7 +145,7 @@ const parseConsumerChecks = (input) => {
143
145
  if (!Array.isArray(input)) throw new Error("consumerChecks must be an array");
144
146
  const targets = /* @__PURE__ */ new Set();
145
147
  return input.map((entry) => {
146
- if (!record$10(entry) || Object.keys(entry).some((key) => ![
148
+ if (!record$11(entry) || Object.keys(entry).some((key) => ![
147
149
  "workflow",
148
150
  "job",
149
151
  "packages",
@@ -181,9 +183,9 @@ const checkConsumerChecks = ({ declarations, files, policy }) => {
181
183
  });
182
184
  if (document.errors.length > 0) throw new Error("invalid workflow YAML");
183
185
  const source = document.toJS({ maxAliasCount: 100 });
184
- if (!record$10(source) || !record$10(source["jobs"])) continue;
186
+ if (!record$11(source) || !record$11(source["jobs"])) continue;
185
187
  for (const [jobName, job] of Object.entries(source["jobs"])) {
186
- if (!record$10(job) || typeof job["uses"] !== "string") continue;
188
+ if (!record$11(job) || typeof job["uses"] !== "string") continue;
187
189
  const [workflowPath, ref] = job["uses"].split("@");
188
190
  if (workflowPath?.toLowerCase() !== "stella/.github/.github/workflows/package-consumer-compat.yml") continue;
189
191
  const declaration = declarations.find((entry) => entry.workflow === workflow && entry.job === jobName);
@@ -197,7 +199,7 @@ const checkConsumerChecks = ({ declarations, files, policy }) => {
197
199
  const approved = Object.entries(policy.actions).find(([name]) => name.toLowerCase() === consumerCheckWorkflow)?.[1];
198
200
  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`);
199
201
  const inputs = job["with"];
200
- if (!record$10(inputs)) throw new Error(`${workflow}:${jobName} requires consumer inputs`);
202
+ if (!record$11(inputs)) throw new Error(`${workflow}:${jobName} requires consumer inputs`);
201
203
  const names = /* @__PURE__ */ new Set();
202
204
  for (const key of Object.keys(inputs)) {
203
205
  const normalized = key.toLowerCase();
@@ -267,7 +269,7 @@ const locatorValidators = {
267
269
  "bun-source": /^(?:jobs\.[\w-]+|runs)\.steps\.[\w-]+$/
268
270
  };
269
271
  const knownKind = (value) => typeof value === "string" && Object.hasOwn(locatorValidators, value);
270
- const record$9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
272
+ const record$10 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
271
273
  /** One reviewed declaration identifies one unresolved selector, never a rule. */
272
274
  const parseDynamicSelectors = (input) => {
273
275
  if (input === void 0) return [];
@@ -275,7 +277,7 @@ const parseDynamicSelectors = (input) => {
275
277
  const result = [];
276
278
  const keys = /* @__PURE__ */ new Set();
277
279
  for (const entry of input) {
278
- if (!record$9(entry) || Object.keys(entry).some((key) => ![
280
+ if (!record$10(entry) || Object.keys(entry).some((key) => ![
279
281
  "path",
280
282
  "kind",
281
283
  "reason",
@@ -311,7 +313,7 @@ const parseDynamicSelectors = (input) => {
311
313
  };
312
314
  //#endregion
313
315
  //#region src/toolchain-engine-floors.ts
314
- const record$8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
316
+ const record$9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
315
317
  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 !== "..");
316
318
  const parseEngineFloors = (input) => {
317
319
  if (input === void 0) return [];
@@ -319,7 +321,7 @@ const parseEngineFloors = (input) => {
319
321
  const result = [];
320
322
  const jobs = /* @__PURE__ */ new Set();
321
323
  for (const entry of input) {
322
- if (!record$8(entry) || Object.keys(entry).some((key) => ![
324
+ if (!record$9(entry) || Object.keys(entry).some((key) => ![
323
325
  "package",
324
326
  "workflow",
325
327
  "job"
@@ -340,16 +342,16 @@ const resolveEngineFloor = (entry, files) => {
340
342
  const workflowText = files[entry.workflow];
341
343
  if (manifestText === void 0 || workflowText === void 0) throw new Error("engine floor package and workflow must be tracked");
342
344
  const manifest = JSON.parse(manifestText);
343
- 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");
345
+ 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");
344
346
  const range = manifest["engines"]["node"];
345
347
  const minimum = (0, semver.validRange)(range) === null ? null : (0, semver.minVersion)(range);
346
348
  if (minimum === null || minimum.prerelease.length !== 0) throw new Error("engine floor engines.node must declare a stable minimum");
347
349
  const document = (0, yaml.parseDocument)(workflowText, { merge: true });
348
350
  if (document.errors.length !== 0) throw new Error("engine floor workflow must be valid YAML");
349
351
  const workflow = document.toJS({ maxAliasCount: 100 });
350
- if (!record$8(workflow) || !record$8(workflow["jobs"])) throw new Error("engine floor job must exist in its tracked workflow");
352
+ if (!record$9(workflow) || !record$9(workflow["jobs"])) throw new Error("engine floor job must exist in its tracked workflow");
351
353
  const job = workflow["jobs"][entry.job];
352
- if (!record$8(job)) throw new Error("engine floor job must exist in its tracked workflow");
354
+ if (!record$9(job)) throw new Error("engine floor job must exist in its tracked workflow");
353
355
  if (!Array.isArray(job["steps"]) || Object.hasOwn(job, "uses")) throw new Error("engine floor job must declare steps and cannot reuse a workflow");
354
356
  return {
355
357
  ...entry,
@@ -362,6 +364,38 @@ const engineFloorSelectorMatches = (value, floor) => {
362
364
  return Number(value.split(".").at(0)) === floor.major && (0, semver.satisfies)(value, floor.range);
363
365
  };
364
366
  //#endregion
367
+ //#region src/toolchain-typescript-source-api.ts
368
+ const readSourceApiInput = ({ root, source, tracked }) => {
369
+ if (!tracked.has(source)) return void 0;
370
+ const resolvedRoot = (0, node_fs.realpathSync)(root);
371
+ const input = node_path.default.resolve(resolvedRoot, source);
372
+ try {
373
+ if ((0, node_fs.realpathSync)(input) !== input || !(0, node_fs.lstatSync)(input).isFile()) return void 0;
374
+ return (0, node_fs.readFileSync)(input, "utf8");
375
+ } catch {
376
+ return;
377
+ }
378
+ };
379
+ const sourceApiImportProblem = ({ source, content, name }) => {
380
+ if (content === void 0) return "must be a tracked, readable regular source file without symlinks";
381
+ try {
382
+ const { code } = (0, esbuild.transformSync)(content, {
383
+ sourcefile: source,
384
+ loader: source.endsWith("x") ? "tsx" : "ts",
385
+ format: "esm",
386
+ tsconfigRaw: { compilerOptions: { verbatimModuleSyntax: true } },
387
+ logLevel: "silent"
388
+ });
389
+ if ((0, acorn.parse)(code, {
390
+ ecmaVersion: "latest",
391
+ sourceType: "module"
392
+ }).body.some((node) => node.type === "ImportDeclaration" && node.source.value === name && node.specifiers.length > 0)) return void 0;
393
+ return `no longer has a static value import from ${name}; remove the declaration or name its current importer`;
394
+ } catch {
395
+ return "cannot parse the declared compiler API importer";
396
+ }
397
+ };
398
+ //#endregion
365
399
  //#region src/toolchain-packages.ts
366
400
  const packageRules = [
367
401
  "bun-pins",
@@ -379,7 +413,7 @@ const installSections = [
379
413
  "optionalDependencies"
380
414
  ];
381
415
  const allowlistFile = "stll-toolchain.json";
382
- const record$7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
416
+ const record$8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
383
417
  /** `npm:typescript@7.0.2` installs a compiler under another name; returns its version. */
384
418
  const typescriptAliasVersion = (spec) => {
385
419
  if (typeof spec !== "string") return void 0;
@@ -409,22 +443,44 @@ const parseTypescriptAllowlist = (input) => {
409
443
  return input.map((entry) => {
410
444
  const shown = JSON.stringify(entry);
411
445
  const text = (key) => {
412
- const value = record$7(entry) ? entry[key] : void 0;
446
+ const value = record$8(entry) ? entry[key] : void 0;
413
447
  return typeof value === "string" && value.trim() !== "" ? value : void 0;
414
448
  };
415
449
  const workspace = text("workspace");
416
450
  const name = text("name");
417
451
  const spec = text("spec");
418
452
  const dependency = text("dependency");
453
+ const sourceApi = record$8(entry) && entry["kind"] === "source-api";
454
+ const keys = sourceApi ? [
455
+ "workspace",
456
+ "name",
457
+ "spec",
458
+ "reason",
459
+ "kind",
460
+ "sources"
461
+ ] : [...allowlistKeys, ...record$8(entry) && entry["kind"] === "dependency" ? ["kind"] : []];
419
462
  const reason = text("reason");
420
- 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}`);
463
+ 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}`);
421
464
  if (workspace !== "." && (node_path.default.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"`);
422
465
  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`);
423
466
  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>`);
424
- 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`);
467
+ 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`);
425
468
  const key = `${workspace}\0${name}`;
426
469
  if (seen.has(key)) throw new Error(`typescriptAllowlist declares ${name} for ${workspace} more than once; keep one entry`);
427
470
  seen.add(key);
471
+ if (sourceApi) {
472
+ const sources = entry["sources"];
473
+ if (!Array.isArray(sources) || sources.length === 0 || !sources.every((source) => typeof source === "string" && node_path.default.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`);
474
+ return {
475
+ kind: "source-api",
476
+ workspace,
477
+ name,
478
+ spec,
479
+ reason,
480
+ sources
481
+ };
482
+ }
483
+ if (dependency === void 0) throw new Error("Missing TypeScript dependency owner");
428
484
  return {
429
485
  workspace,
430
486
  name,
@@ -993,10 +1049,10 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
993
1049
  const document = pnpm ? (0, yaml.parseDocument)(text, { lineCounter }) : void 0;
994
1050
  if (document !== void 0 && document.errors.length > 0) throw new Error("invalid YAML");
995
1051
  const json = document === void 0 ? JSON.parse(text) : document.toJS();
996
- if (!record$7(json)) throw new Error("manifest must be an object");
1052
+ if (!record$8(json)) throw new Error("manifest must be an object");
997
1053
  if (pnpm) {
998
- if (json["catalog"] !== void 0 && !record$7(json["catalog"])) throw new Error("catalog must be a mapping");
999
- 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");
1054
+ if (json["catalog"] !== void 0 && !record$8(json["catalog"])) throw new Error("catalog must be a mapping");
1055
+ 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");
1000
1056
  if (json["packages"] !== void 0 && (!Array.isArray(json["packages"]) || !json["packages"].every((pattern) => typeof pattern === "string"))) throw new Error("workspace packages must be patterns");
1001
1057
  }
1002
1058
  manifests.set(file, json);
@@ -1010,13 +1066,13 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1010
1066
  catalog: json["overrides"],
1011
1067
  prefix: ["overrides"]
1012
1068
  },
1013
- ...record$7(json["catalogs"]) ? Object.entries(json["catalogs"]).map(([name, catalog]) => ({
1069
+ ...record$8(json["catalogs"]) ? Object.entries(json["catalogs"]).map(([name, catalog]) => ({
1014
1070
  catalog,
1015
1071
  prefix: ["catalogs", name]
1016
1072
  })) : []
1017
1073
  ];
1018
1074
  for (const { catalog, prefix } of catalogEntries) {
1019
- if (!record$7(catalog)) continue;
1075
+ if (!record$8(catalog)) continue;
1020
1076
  for (const [name, value] of Object.entries(catalog)) {
1021
1077
  const node = document.getIn([...prefix, name], true);
1022
1078
  if (!(0, yaml.isNode)(node) || node.range === void 0 || node.range === null) continue;
@@ -1035,7 +1091,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1035
1091
  });
1036
1092
  }
1037
1093
  }
1038
- const catalogSources = (json) => [json, ...record$7(json["workspaces"]) ? [json["workspaces"]] : []];
1094
+ const catalogSources = (json) => [json, ...record$8(json["workspaces"]) ? [json["workspaces"]] : []];
1039
1095
  const pnpmWorkspaceFor = (file) => {
1040
1096
  let directory = node_path.default.posix.dirname(file);
1041
1097
  while (true) {
@@ -1058,8 +1114,8 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1058
1114
  patterns: workspace["packages"],
1059
1115
  rootMembership: "implicit"
1060
1116
  })) return void 0;
1061
- if (name === "" || name === "default") return workspace["catalog"] ?? (record$7(workspace["catalogs"]) ? workspace["catalogs"]["default"] : void 0);
1062
- return record$7(workspace["catalogs"]) ? workspace["catalogs"][name] : void 0;
1117
+ if (name === "" || name === "default") return workspace["catalog"] ?? (record$8(workspace["catalogs"]) ? workspace["catalogs"]["default"] : void 0);
1118
+ return record$8(workspace["catalogs"]) ? workspace["catalogs"][name] : void 0;
1063
1119
  }
1064
1120
  let directory = node_path.default.posix.dirname(file);
1065
1121
  while (true) {
@@ -1067,7 +1123,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1067
1123
  if (manifest !== void 0) {
1068
1124
  for (const source of catalogSources(manifest)) {
1069
1125
  if (name === "" && source["catalog"] !== void 0) return source["catalog"];
1070
- if (record$7(source["catalogs"]) && source["catalogs"][name] !== void 0) return source["catalogs"][name];
1126
+ if (record$8(source["catalogs"]) && source["catalogs"][name] !== void 0) return source["catalogs"][name];
1071
1127
  }
1072
1128
  if (manifest["workspaces"] !== void 0) return void 0;
1073
1129
  }
@@ -1085,7 +1141,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1085
1141
  };
1086
1142
  visited.add(value);
1087
1143
  const catalog = catalogFor(file, value.slice(8));
1088
- if (!record$7(catalog)) return {
1144
+ if (!record$8(catalog)) return {
1089
1145
  value: void 0,
1090
1146
  catalogs
1091
1147
  };
@@ -1110,7 +1166,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1110
1166
  const workspaces = manifests.get(node_path.default.posix.join(directory, "package.json"))?.["workspaces"];
1111
1167
  if (workspaces !== void 0) return {
1112
1168
  directory,
1113
- patterns: record$7(workspaces) ? workspaces["packages"] : workspaces,
1169
+ patterns: record$8(workspaces) ? workspaces["packages"] : workspaces,
1114
1170
  source: "json"
1115
1171
  };
1116
1172
  if (directory === ".") return void 0;
@@ -1144,7 +1200,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1144
1200
  if (json === void 0) continue;
1145
1201
  for (const section of installSections) {
1146
1202
  const entries = json[section];
1147
- if (!record$7(entries) || !Object.hasOwn(entries, entry.name)) continue;
1203
+ if (!record$8(entries) || !Object.hasOwn(entries, entry.name)) continue;
1148
1204
  const { value, catalogs } = resolveThrough(file, entry.name, entries[entry.name]);
1149
1205
  if (value !== entry.spec) continue;
1150
1206
  for (const catalog of catalogs) {
@@ -1176,7 +1232,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1176
1232
  });
1177
1233
  return `${name} ${shown} in ${file} is not allowed: ${typescriptGuidance}; if a dependency of this workspace needs it, add ${suggestion} to typescriptAllowlist in ${allowlistFile}`;
1178
1234
  }
1179
- 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`;
1235
+ 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`;
1180
1236
  };
1181
1237
  const peerRange = (value) => typeof value === "string" && (0, semver.validRange)(value) !== null;
1182
1238
  for (const [file, json] of manifests) {
@@ -1189,7 +1245,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1189
1245
  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)}`
1190
1246
  });
1191
1247
  const checkEntries = (entries, scope, location) => {
1192
- if (!record$7(entries)) return;
1248
+ if (!record$8(entries)) return;
1193
1249
  for (const [name, raw] of Object.entries(entries)) {
1194
1250
  const value = resolve(file, name, raw);
1195
1251
  if (isTypescriptInstall(name, value)) {
@@ -1229,13 +1285,13 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1229
1285
  }
1230
1286
  };
1231
1287
  for (const key of installSections) checkEntries(json[key], "install");
1232
- if (record$7(json["peerDependencies"])) for (const [name, value] of Object.entries(json["peerDependencies"])) {
1288
+ if (record$8(json["peerDependencies"])) for (const [name, value] of Object.entries(json["peerDependencies"])) {
1233
1289
  const resolved = resolve(file, name, value);
1234
1290
  if (isTypescriptInstall(name, resolved) && !peerRange(resolved)) checkEntries({ [name]: value }, "peer");
1235
1291
  }
1236
1292
  for (const source of catalogSources(json)) {
1237
1293
  checkEntries(source["catalog"], "catalog");
1238
- if (!record$7(source["catalogs"])) continue;
1294
+ if (!record$8(source["catalogs"])) continue;
1239
1295
  for (const catalog of Object.values(source["catalogs"])) checkEntries(catalog, "catalog");
1240
1296
  }
1241
1297
  const ownedNames = /* @__PURE__ */ new Set([...pins.keys(), ...typescriptPackages]);
@@ -1247,11 +1303,11 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1247
1303
  "optionalDependencies"
1248
1304
  ]) {
1249
1305
  const entries = resolutionRoot[section];
1250
- if (!record$7(entries)) continue;
1306
+ if (!record$8(entries)) continue;
1251
1307
  for (const [name, value] of Object.entries(entries)) resolutionDependencies[name] = resolve(file, name, value);
1252
1308
  }
1253
1309
  const checkResolutionMap = (entries, parent) => {
1254
- if (!record$7(entries)) return;
1310
+ if (!record$8(entries)) return;
1255
1311
  for (const [selector, replacement] of Object.entries(entries)) {
1256
1312
  const target = selector === "." ? parent : selector;
1257
1313
  if (target === void 0) continue;
@@ -1270,7 +1326,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1270
1326
  });
1271
1327
  continue;
1272
1328
  }
1273
- if (record$7(replacement)) {
1329
+ if (record$8(replacement)) {
1274
1330
  checkResolutionMap(replacement, target);
1275
1331
  continue;
1276
1332
  }
@@ -1290,8 +1346,8 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1290
1346
  };
1291
1347
  checkResolutionMap(json["overrides"]);
1292
1348
  checkResolutionMap(json["resolutions"]);
1293
- if (record$7(json["pnpm"])) checkResolutionMap(json["pnpm"]["overrides"]);
1294
- const command = record$7(json["scripts"]) ? json["scripts"]["typecheck"] : void 0;
1349
+ if (record$8(json["pnpm"])) checkResolutionMap(json["pnpm"]["overrides"]);
1350
+ const command = record$8(json["scripts"]) ? json["scripts"]["typecheck"] : void 0;
1295
1351
  if (typeof command === "string") for (const words of compilerCommandSegments(command)) {
1296
1352
  if (!invokesCompiler(words)) continue;
1297
1353
  const normalized = words.join(" ");
@@ -1305,7 +1361,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1305
1361
  });
1306
1362
  break;
1307
1363
  }
1308
- if (record$7(json["peerDependencies"])) for (const [name, raw] of Object.entries(json["peerDependencies"])) {
1364
+ if (record$8(json["peerDependencies"])) for (const [name, raw] of Object.entries(json["peerDependencies"])) {
1309
1365
  if (!typescriptPackages.has(name)) continue;
1310
1366
  const range = resolve(file, name, raw);
1311
1367
  if (!peerRange(range)) continue;
@@ -1329,7 +1385,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1329
1385
  message: `${name} peer support range must include ${release}, found ${range}`
1330
1386
  });
1331
1387
  }
1332
- if (record$7(json["engines"]) && json["engines"]["node"] !== void 0 && (typeof json["engines"]["node"] !== "string" || !require_toolchain_inputs.nodeSupportRangeMatches(policy.node, json["engines"]["node"]))) add({
1388
+ if (record$8(json["engines"]) && json["engines"]["node"] !== void 0 && (typeof json["engines"]["node"] !== "string" || !require_toolchain_inputs.nodeSupportRangeMatches(policy.node, json["engines"]["node"]))) add({
1333
1389
  file,
1334
1390
  rule: "node-engine",
1335
1391
  key: "node",
@@ -1343,9 +1399,23 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
1343
1399
  if (json === void 0) return `${file} is missing or invalid; remove the entry`;
1344
1400
  const declares = (name) => installSections.some((section) => {
1345
1401
  const entries = json[section];
1346
- return record$7(entries) && Object.hasOwn(entries, name);
1402
+ return record$8(entries) && Object.hasOwn(entries, name);
1347
1403
  });
1348
1404
  if (!declares(entry.name)) return `${file} no longer declares ${entry.name} in dependencies, devDependencies or optionalDependencies; remove the entry`;
1405
+ if (entry.kind === "source-api") {
1406
+ for (const source of entry.sources) {
1407
+ let owner = node_path.default.posix.dirname(source);
1408
+ while (owner !== "." && !manifests.has(`${owner}/package.json`)) owner = node_path.default.posix.dirname(owner);
1409
+ if (owner !== entry.workspace) return `${source} belongs to workspace ${owner}, not ${entry.workspace}`;
1410
+ const problem = sourceApiImportProblem({
1411
+ source,
1412
+ content: files[source],
1413
+ name: entry.name
1414
+ });
1415
+ if (problem !== void 0) return `${source}: ${problem}`;
1416
+ }
1417
+ return;
1418
+ }
1349
1419
  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`;
1350
1420
  };
1351
1421
  for (const entry of typescriptAllowlist) {
@@ -1382,9 +1452,9 @@ const extensionKinds = new Map(Object.entries({
1382
1452
  const scriptSourceKind = (file) => extensionKinds.get((0, node_path.extname)(file));
1383
1453
  //#endregion
1384
1454
  //#region src/toolchain-script-lifecycle.ts
1385
- const record$6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1455
+ const record$7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1386
1456
  const declaredManager = (value) => typeof value === "string" ? /^(bun|npm|pnpm|yarn)@(\d+\.\d+\.\d+)(?:\+\S+)?$/u.exec(value) : null;
1387
- const rootScriptManager = (manifest, context) => declaredManager(context.packageManager ?? (record$6(manifest) ? manifest["packageManager"] : void 0))?.at(1);
1457
+ const rootScriptManager = (manifest, context) => declaredManager(context.packageManager ?? (record$7(manifest) ? manifest["packageManager"] : void 0))?.at(1);
1388
1458
  const npmHooksEnabled = (file) => {
1389
1459
  if (file?.type === "unknown") throw new Error("npm lifecycle configuration must be a tracked regular file");
1390
1460
  if (file?.type !== "tracked") return true;
@@ -1415,14 +1485,18 @@ const yamlSettings = (file, manager) => {
1415
1485
  } catch {
1416
1486
  throw new Error(`Cannot parse ${manager} lifecycle configuration`);
1417
1487
  }
1418
- if (!record$6(settings)) throw new Error(`${manager} lifecycle configuration must be a mapping`);
1488
+ if (!record$7(settings)) throw new Error(`${manager} lifecycle configuration must be a mapping`);
1419
1489
  return settings;
1420
1490
  };
1491
+ const pnpmLifecycleVersions = ["12.9.1", "12.15.0"];
1421
1492
  const pnpmHooksEnabled = (manifest, context) => {
1422
1493
  const declared = declaredManager(context.packageManager ?? manifest["packageManager"]);
1423
- const dependency = [manifest["dependencies"], manifest["devDependencies"]].flatMap((field) => record$6(field) ? [field["pnpm"]] : []).find((value) => typeof value === "string");
1424
- if ((declared?.at(1) === "pnpm" ? declared.at(2) : dependency) !== "12.9.1") throw new Error("pnpm lifecycle discovery requires declared pnpm@12.9.1");
1425
- const enabled = yamlSettings(context.pnpmWorkspace, "pnpm")["enablePrePostScripts"];
1494
+ const dependency = [manifest["dependencies"], manifest["devDependencies"]].flatMap((field) => record$7(field) ? [field["pnpm"]] : []).find((value) => typeof value === "string");
1495
+ const version = declared?.at(1) === "pnpm" ? declared.at(2) : dependency;
1496
+ if (!pnpmLifecycleVersions.some((supported) => version === supported)) throw new Error(`pnpm lifecycle discovery requires declared pnpm@${pnpmLifecycleVersions.join(" or pnpm@")}`);
1497
+ const settings = yamlSettings(context.pnpmWorkspace, "pnpm");
1498
+ if ("include" in settings) throw new Error("Delegated pnpm settings require explicit lifecycle discovery support");
1499
+ const enabled = settings["enablePrePostScripts"];
1426
1500
  if (enabled === void 0) return true;
1427
1501
  if (typeof enabled !== "boolean") throw new Error("pnpm enablePrePostScripts must be a literal boolean");
1428
1502
  return enabled;
@@ -1464,7 +1538,7 @@ const scriptLifecycleStages = ({ name, manager, manifest, scripts, context }) =>
1464
1538
  };
1465
1539
  //#endregion
1466
1540
  //#region src/toolchain-lint-command.ts
1467
- const record$5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1541
+ const record$6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1468
1542
  const scriptManagers = /* @__PURE__ */ new Set([
1469
1543
  "bun",
1470
1544
  "npm",
@@ -1581,7 +1655,7 @@ const lintInventoryCommands = (manifest, forceEnabled = false, context = {}) =>
1581
1655
  const errors = [];
1582
1656
  const workspaceTasks = /* @__PURE__ */ new Set();
1583
1657
  const filteredBuilds = /* @__PURE__ */ new Set();
1584
- const declaresEngine = record$5(manifest) && [manifest["dependencies"], manifest["devDependencies"]].some((dependencies) => record$5(dependencies) && "oxlint-tsgolint" in dependencies);
1658
+ const declaresEngine = record$6(manifest) && [manifest["dependencies"], manifest["devDependencies"]].some((dependencies) => record$6(dependencies) && "oxlint-tsgolint" in dependencies);
1585
1659
  const enabled = forceEnabled || declaresEngine;
1586
1660
  if (!enabled) return {
1587
1661
  commands,
@@ -1589,14 +1663,14 @@ const lintInventoryCommands = (manifest, forceEnabled = false, context = {}) =>
1589
1663
  enabled,
1590
1664
  workspaceTask: false
1591
1665
  };
1592
- if (!record$5(manifest)) return {
1666
+ if (!record$6(manifest)) return {
1593
1667
  commands,
1594
1668
  errors: ["lint inventory requires an object package manifest"],
1595
1669
  enabled,
1596
1670
  workspaceTask: false
1597
1671
  };
1598
1672
  const scripts = manifest["scripts"];
1599
- if (!record$5(scripts)) return {
1673
+ if (!record$6(scripts)) return {
1600
1674
  commands,
1601
1675
  errors: ["type-aware linting requires a root lint script"],
1602
1676
  enabled,
@@ -1738,7 +1812,7 @@ const checkProjectOwnership = ({ trackedFiles, lintedFiles, projectFiles }) => [
1738
1812
  line: 1,
1739
1813
  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)"
1740
1814
  }));
1741
- const record$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1815
+ const record$5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1742
1816
  const stringList = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
1743
1817
  const readCommand = ({ root, executable, args, runtime }) => {
1744
1818
  const result = (0, node_child_process.spawnSync)(runtime, [
@@ -1782,7 +1856,7 @@ const resolveProjectApi = (root) => {
1782
1856
  for (const base of [require("url").pathToFileURL(__filename).href, node_path.default.join(root, "package.json")]) try {
1783
1857
  return (0, node_url.pathToFileURL)((0, node_module.createRequire)(base).resolve("tsrs/unstable/async")).href;
1784
1858
  } catch (error) {
1785
- const code = record$4(error) ? error["code"] : void 0;
1859
+ const code = record$5(error) ? error["code"] : void 0;
1786
1860
  if (code !== "MODULE_NOT_FOUND" && code !== "ERR_MODULE_NOT_FOUND") throw error;
1787
1861
  }
1788
1862
  throw new Error("configuration discovery needs tsrs: reinstall @stll/oxlint-config, which depends on it");
@@ -1805,7 +1879,7 @@ const readProjectConfig = (root, config) => {
1805
1879
  if (result.error !== void 0) throw result.error;
1806
1880
  if (result.status !== 0) throw new Error(`configuration discovery failed: ${(result.stderr + result.stdout).trim()}`);
1807
1881
  const parsed = JSON.parse(result.stdout);
1808
- 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");
1882
+ 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");
1809
1883
  return {
1810
1884
  files: parsed["files"],
1811
1885
  references: parsed["references"],
@@ -1830,7 +1904,7 @@ const lifecycleFile = (root, file, tracked) => {
1830
1904
  };
1831
1905
  /** Inventory and config expansion only: neither subprocess lints or typechecks. */
1832
1906
  const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
1833
- const packageManager = record$4(manifest) ? manifest["packageManager"] : void 0;
1907
+ const packageManager = record$5(manifest) ? manifest["packageManager"] : void 0;
1834
1908
  const managerUsesPnpmWorkspace = packageManager === void 0 || typeof packageManager === "string" && packageManager.startsWith("pnpm@");
1835
1909
  const discovered = /* @__PURE__ */ new Map();
1836
1910
  const workspaceMembers = (pnpmMembership) => {
@@ -1866,7 +1940,7 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
1866
1940
  return nestedLifecycle;
1867
1941
  };
1868
1942
  const lifecycle = {
1869
- packageManager: record$4(manifest) ? manifest["packageManager"] : void 0,
1943
+ packageManager: record$5(manifest) ? manifest["packageManager"] : void 0,
1870
1944
  npmrc: lifecycleFile(root, ".npmrc", trackedFiles),
1871
1945
  pnpmWorkspace: lifecycleFile(root, "pnpm-workspace.yaml", trackedFiles),
1872
1946
  yarnrc: lifecycleFile(root, ".yarnrc.yml", trackedFiles),
@@ -1877,7 +1951,7 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
1877
1951
  if (member === void 0) throw new Error("Missing selected workspace");
1878
1952
  const [directory, pkg] = member;
1879
1953
  const scripts = pkg["scripts"];
1880
- if (!record$4(scripts) || typeof scripts["build"] !== "string") throw new Error("Filtered Bun build requires a literal build script");
1954
+ if (!record$5(scripts) || typeof scripts["build"] !== "string") throw new Error("Filtered Bun build requires a literal build script");
1881
1955
  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");
1882
1956
  if (activeBuilds.has(directory)) throw new Error("Cyclic filtered Bun build delegation");
1883
1957
  activeBuilds.add(directory);
@@ -1919,7 +1993,7 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
1919
1993
  for (const [directory, workspace] of workspaceMembers(managerUsesPnpmWorkspace)) {
1920
1994
  if (directory === ".") continue;
1921
1995
  const file = `${directory}/package.json`;
1922
- if (!record$4(workspace["scripts"]) || workspace["scripts"]["lint"] === void 0) continue;
1996
+ if (!record$5(workspace["scripts"]) || workspace["scripts"]["lint"] === void 0) continue;
1923
1997
  const nestedLifecycle = workspaceLifecycle(directory);
1924
1998
  const nested = lintInventoryCommands(workspace, true, nestedLifecycle);
1925
1999
  if (nested.workspaceTask) throw new Error(`nested workspace lint delegation is unsupported: ${file}`);
@@ -2028,7 +2102,7 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
2028
2102
  };
2029
2103
  //#endregion
2030
2104
  //#region src/toolchain-rust.ts
2031
- const record$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2105
+ const record$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2032
2106
  /**
2033
2107
  * Exceptions authorize one immutable release, never a channel or an entire
2034
2108
  * rule. Expiry is scoped separately by checkRustExceptionExpiry.
@@ -2038,7 +2112,7 @@ const parseRustExceptions = (input) => {
2038
2112
  if (!Array.isArray(input)) throw new Error("rustExceptions must be an array");
2039
2113
  const pins = /* @__PURE__ */ new Set();
2040
2114
  return input.map((entry) => {
2041
- if (!record$3(entry) || Object.keys(entry).length !== 4 || Object.keys(entry).some((key) => ![
2115
+ if (!record$4(entry) || Object.keys(entry).length !== 4 || Object.keys(entry).some((key) => ![
2042
2116
  "pin",
2043
2117
  "reason",
2044
2118
  "tracking",
@@ -2056,8 +2130,8 @@ const parseRustExceptions = (input) => {
2056
2130
  /** Whether the base stll-toolchain.json declares this exception with the same expiry. */
2057
2131
  const declaresException = (baseText, { pin, expires }) => {
2058
2132
  const base = JSON.parse(baseText);
2059
- const exceptions = record$3(base) ? base["rustExceptions"] : void 0;
2060
- return Array.isArray(exceptions) && exceptions.some((entry) => record$3(entry) && entry["pin"] === pin && entry["expires"] === expires);
2133
+ const exceptions = record$4(base) ? base["rustExceptions"] : void 0;
2134
+ return Array.isArray(exceptions) && exceptions.some((entry) => record$4(entry) && entry["pin"] === pin && entry["expires"] === expires);
2061
2135
  };
2062
2136
  /** An exception expires on its date; the scope decides whether that fails this run. */
2063
2137
  const checkRustExceptionExpiry = ({ exceptions, now = /* @__PURE__ */ new Date(), scope }) => {
@@ -2195,7 +2269,9 @@ const checkRustReferences = (options) => {
2195
2269
  }
2196
2270
  if (token.value.startsWith("-")) continue;
2197
2271
  selected++;
2198
- select(token.value, "rustup toolchain", token.line);
2272
+ const installFixture = options.type === "file" && require_toolchain_inputs.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;
2273
+ if (installFixture !== void 0) options.onInstallFixture?.(installFixture);
2274
+ else select(token.value, "rustup toolchain", token.line);
2199
2275
  if (firstSelectorOnly) break;
2200
2276
  }
2201
2277
  if (selected === 0 && requiresSelector) select(void 0, "rustup toolchain", firstLine);
@@ -2216,7 +2292,7 @@ const checkRustReferences = (options) => {
2216
2292
  if (require_toolchain_inputs.isShellScript(file, text)) shell(text);
2217
2293
  if (/(?:^|\/)package\.json$/.test(file)) try {
2218
2294
  const object = JSON.parse(text);
2219
- if (record$3(object) && record$3(object["scripts"])) {
2295
+ if (record$4(object) && record$4(object["scripts"])) {
2220
2296
  for (const [name, script] of Object.entries(object["scripts"])) if (typeof script === "string") shell(script, lineOf(`"${name}"`));
2221
2297
  }
2222
2298
  } catch {}
@@ -2227,7 +2303,7 @@ const checkRustReferences = (options) => {
2227
2303
  const version = tag === void 0 ? void 0 : require_toolchain_dependabot.rustImageVersion(tag);
2228
2304
  select(version, "Rust service image", lineOf(value));
2229
2305
  } else if (Array.isArray(value)) value.forEach(images);
2230
- else if (record$3(value)) {
2306
+ else if (record$4(value)) {
2231
2307
  for (const [key, entry] of Object.entries(value)) if (key !== "name") images(entry);
2232
2308
  }
2233
2309
  };
@@ -2251,13 +2327,13 @@ const checkRustReferences = (options) => {
2251
2327
  value.forEach(visit);
2252
2328
  return;
2253
2329
  }
2254
- if (!record$3(value)) return;
2255
- if (record$3(value["env"]) && value["env"]["RUSTUP_TOOLCHAIN"] !== void 0) select(value["env"]["RUSTUP_TOOLCHAIN"], "RUSTUP_TOOLCHAIN", lineOf("RUSTUP_TOOLCHAIN"));
2330
+ if (!record$4(value)) return;
2331
+ if (record$4(value["env"]) && value["env"]["RUSTUP_TOOLCHAIN"] !== void 0) select(value["env"]["RUSTUP_TOOLCHAIN"], "RUSTUP_TOOLCHAIN", lineOf("RUSTUP_TOOLCHAIN"));
2256
2332
  if (typeof value["run"] === "string") shell(value["run"], lineOf("run:"));
2257
2333
  const uses = value["uses"];
2258
2334
  if (typeof uses === "string") {
2259
2335
  const action = uses.split("@").at(0)?.toLowerCase();
2260
- const inputs = record$3(value["with"]) ? value["with"] : {};
2336
+ const inputs = record$4(value["with"]) ? value["with"] : {};
2261
2337
  if (action === "dtolnay/rust-toolchain" || action === "actions-rust-lang/setup-rust-toolchain") {
2262
2338
  const entries = Object.entries(inputs).filter(([key]) => key.toLowerCase() === "toolchain");
2263
2339
  if (entries.length !== 1) select(void 0, `${action} requires an explicit exact toolchain input`, lineOf(uses));
@@ -2271,8 +2347,8 @@ const checkRustReferences = (options) => {
2271
2347
  }
2272
2348
  }
2273
2349
  for (const [key, child] of Object.entries(value)) {
2274
- if (key === "workflow_call" && record$3(child) && record$3(child["inputs"])) {
2275
- 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:"));
2350
+ if (key === "workflow_call" && record$4(child) && record$4(child["inputs"])) {
2351
+ 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:"));
2276
2352
  }
2277
2353
  visit(child);
2278
2354
  }
@@ -2283,7 +2359,7 @@ const checkRustReferences = (options) => {
2283
2359
  };
2284
2360
  //#endregion
2285
2361
  //#region src/toolchain-workflow-trust.ts
2286
- const record$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2362
+ const record$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2287
2363
  const booleanParts = (source, operator) => {
2288
2364
  const parts = [];
2289
2365
  let depth = 0;
@@ -2533,7 +2609,7 @@ const safeAction = (uses, inputs) => {
2533
2609
  const approved = Object.entries(workflowTrustBootstrapCatalog.actions).find(([name]) => name === repository);
2534
2610
  if (approved === void 0) return false;
2535
2611
  if (inputs === void 0) return repository === "actions/checkout";
2536
- if (!record$2(inputs)) return false;
2612
+ if (!record$3(inputs)) return false;
2537
2613
  if (repository !== "actions/checkout") {
2538
2614
  const version = inputs["bun-version"];
2539
2615
  if (typeof version !== "string" || !/^v?[0-9]+(?:\.(?:[0-9]+|x)){0,2}$/u.test(version)) return false;
@@ -2546,7 +2622,7 @@ const safeAction = (uses, inputs) => {
2546
2622
  ].includes(typeof value) && (name !== "submodules" || value === false || value === "false"));
2547
2623
  };
2548
2624
  const safeShell = (shell) => shell === void 0 || shell === "bash" || shell === "sh";
2549
- const safeEnvironment = (value) => value === void 0 || record$2(value) && Object.keys(value).every((name) => [
2625
+ const safeEnvironment = (value) => value === void 0 || record$3(value) && Object.keys(value).every((name) => [
2550
2626
  "HEAD_REPO",
2551
2627
  "BASE_REPO",
2552
2628
  "HAS_RUN_CI_LABEL",
@@ -2560,25 +2636,25 @@ const safeEnvironment = (value) => value === void 0 || record$2(value) && Object
2560
2636
  "HAWK_VERSION",
2561
2637
  "HAWK_X86_64_UNKNOWN_LINUX_GNU_SHA256"
2562
2638
  ].includes(name));
2563
- const configuredShell = (value) => record$2(value) && record$2(value["run"]) ? value["run"]["shell"] : void 0;
2639
+ const configuredShell = (value) => record$3(value) && record$3(value["run"]) ? value["run"]["shell"] : void 0;
2564
2640
  const workflowTrustRules = ["workflow-trust"];
2565
2641
  const workflowEvents = (triggers) => {
2566
2642
  if (typeof triggers === "string") return [triggers];
2567
2643
  if (Array.isArray(triggers)) return triggers;
2568
- if (record$2(triggers)) return Object.keys(triggers);
2644
+ if (record$3(triggers)) return Object.keys(triggers);
2569
2645
  return [];
2570
2646
  };
2571
2647
  /** Enforce a workflow's declared trust boundary before repository dependency execution. */
2572
2648
  const checkWorkflowTrust = (workflow) => {
2573
2649
  const findings = [];
2574
- if (!record$2(workflow) || !record$2(workflow["jobs"])) return findings;
2650
+ if (!record$3(workflow) || !record$3(workflow["jobs"])) return findings;
2575
2651
  const jobs = workflow["jobs"];
2576
- if (!record$2(jobs["trust-check"])) return findings;
2652
+ if (!record$3(jobs["trust-check"])) return findings;
2577
2653
  const triggers = workflow["on"];
2578
2654
  const pullRequests = workflowEvents(triggers).filter((event) => event === "pull_request" || event === "pull_request_target");
2579
2655
  if (pullRequests.length === 0) return findings;
2580
2656
  for (const [name, job] of Object.entries(jobs)) {
2581
- if (!record$2(job)) continue;
2657
+ if (!record$3(job)) continue;
2582
2658
  const needs = job["needs"];
2583
2659
  const ordered = name !== "trust-check" && (needs === "trust-check" || Array.isArray(needs) && needs.includes("trust-check"));
2584
2660
  const permitsExecution = (stepCondition) => {
@@ -2615,7 +2691,7 @@ const checkWorkflowTrust = (workflow) => {
2615
2691
  message
2616
2692
  });
2617
2693
  const services = job["services"];
2618
- if (services !== void 0 && (!record$2(services) || Object.keys(services).length > 0)) findings.push({
2694
+ if (services !== void 0 && (!record$3(services) || Object.keys(services).length > 0)) findings.push({
2619
2695
  type: "job",
2620
2696
  property: "services",
2621
2697
  job: name,
@@ -2624,7 +2700,7 @@ const checkWorkflowTrust = (workflow) => {
2624
2700
  }
2625
2701
  if (!Array.isArray(job["steps"])) continue;
2626
2702
  for (const [index, step] of job["steps"].entries()) {
2627
- if (!record$2(step)) continue;
2703
+ if (!record$3(step)) continue;
2628
2704
  const run = step["run"];
2629
2705
  const uses = step["uses"];
2630
2706
  const unsafeRun = typeof run === "string" && (invokesRepositoryCode(run) || !safeShell(step["shell"] ?? configuredShell(job["defaults"]) ?? configuredShell(workflow["defaults"])) || ![
@@ -2662,7 +2738,7 @@ const runtimeRules = [
2662
2738
  "action-pins",
2663
2739
  ...workflowTrustRules
2664
2740
  ];
2665
- const record$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2741
+ const record$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2666
2742
  const dockerEnvironmentAssignments = ({ source, escape }) => {
2667
2743
  const words = [];
2668
2744
  let word = "";
@@ -2795,13 +2871,15 @@ const pythonRequirementMatches = (requirement, version) => {
2795
2871
  });
2796
2872
  };
2797
2873
  /** Check runtime declarations only in the files that declare or configure them. */
2798
- const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, repository, onDelegated, dynamicSelectors = [], onDynamicSelector, rustExceptions = [], engineFloors = [], onEngineFloor }) => {
2874
+ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, repository, onDelegated, dynamicSelectors = [], onDynamicSelector, rustExceptions = [], rustInstallFixtures = [], onRustInstallFixture, engineFloors = [], onEngineFloor }) => {
2799
2875
  const diagnostics = checkRustReferences({
2800
2876
  type: "file",
2801
2877
  file,
2802
2878
  text,
2803
2879
  rust: policy.rust,
2804
- exceptions: rustExceptions
2880
+ exceptions: rustExceptions,
2881
+ installFixtures: rustInstallFixtures,
2882
+ onInstallFixture: onRustInstallFixture
2805
2883
  });
2806
2884
  const name = node_path.default.posix.basename(file);
2807
2885
  const lines = text.split(/\r?\n/);
@@ -2897,7 +2975,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
2897
2975
  const toolchain = parsed["toolchain"];
2898
2976
  pin({
2899
2977
  rule: "rust-version",
2900
- value: record$1(toolchain) ? toolchain["channel"] : void 0,
2978
+ value: record$2(toolchain) ? toolchain["channel"] : void 0,
2901
2979
  expected: policy.rust,
2902
2980
  label: "Rust channel",
2903
2981
  line: lineOf("channel")
@@ -2925,15 +3003,15 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
2925
3003
  };
2926
3004
  if (parsed !== void 0) {
2927
3005
  const workspace = parsed["workspace"];
2928
- const defaults = record$1(workspace) ? workspace["package"] : void 0;
2929
- if (record$1(defaults) && defaults["rust-version"] !== void 0) rustFloor(defaults["rust-version"]);
3006
+ const defaults = record$2(workspace) ? workspace["package"] : void 0;
3007
+ if (record$2(defaults) && defaults["rust-version"] !== void 0) rustFloor(defaults["rust-version"]);
2930
3008
  const pkg = parsed["package"];
2931
- const version = record$1(pkg) ? pkg["rust-version"] : void 0;
3009
+ const version = record$2(pkg) ? pkg["rust-version"] : void 0;
2932
3010
  if (version !== void 0) {
2933
- if (!record$1(version)) rustFloor(version);
3011
+ if (!record$2(version)) rustFloor(version);
2934
3012
  else {
2935
3013
  let inherited;
2936
- if (version["workspace"] === true && record$1(pkg)) {
3014
+ if (version["workspace"] === true && record$2(pkg)) {
2937
3015
  const explicit = pkg["workspace"];
2938
3016
  let directory = node_path.default.posix.dirname(file);
2939
3017
  const explicitPath = typeof explicit === "string" && !explicit.includes("\\") && !explicit.includes(":") && !explicit.includes("${{") && !node_path.default.posix.isAbsolute(explicit) ? node_path.default.posix.normalize(node_path.default.posix.join(directory, explicit)) : void 0;
@@ -2941,9 +3019,9 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
2941
3019
  if (explicitPath !== void 0) directory = explicitPath;
2942
3020
  while (true) {
2943
3021
  const table = readManifest(node_path.default.posix.join(directory, "Cargo.toml"))?.["workspace"];
2944
- if (record$1(table)) {
3022
+ if (record$2(table)) {
2945
3023
  const fields = table["package"];
2946
- inherited = record$1(fields) ? fields["rust-version"] : void 0;
3024
+ inherited = record$2(fields) ? fields["rust-version"] : void 0;
2947
3025
  break;
2948
3026
  }
2949
3027
  if (explicit !== void 0 || directory === ".") break;
@@ -2958,7 +3036,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
2958
3036
  }
2959
3037
  if (name === "Pipfile") {
2960
3038
  const requires = toml("python-version")?.["requires"];
2961
- if (record$1(requires)) {
3039
+ if (record$2(requires)) {
2962
3040
  const minor = requires["python_version"];
2963
3041
  if (minor !== void 0 && minor !== policy.python.split(".").slice(0, 2).join(".")) add({
2964
3042
  rule: "python-version",
@@ -2977,7 +3055,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
2977
3055
  const parsed = toml("python-version");
2978
3056
  if (parsed !== void 0) {
2979
3057
  const project = parsed["project"];
2980
- if (record$1(project) && project["requires-python"] !== void 0) {
3058
+ if (record$2(project) && project["requires-python"] !== void 0) {
2981
3059
  if (!pythonRequirementMatches(project["requires-python"], policy.python)) add({
2982
3060
  rule: "python-version",
2983
3061
  line: lineOf("requires-python"),
@@ -2985,9 +3063,9 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
2985
3063
  });
2986
3064
  }
2987
3065
  const tool = parsed["tool"];
2988
- const configuredUv = record$1(tool) ? tool["uv"] : void 0;
3066
+ const configuredUv = record$2(tool) ? tool["uv"] : void 0;
2989
3067
  const uv = name === "uv.toml" ? parsed : configuredUv;
2990
- if (record$1(uv) && uv["python"] !== void 0) pin({
3068
+ if (record$2(uv) && uv["python"] !== void 0) pin({
2991
3069
  rule: "python-version",
2992
3070
  value: uv["python"],
2993
3071
  expected: policy.python,
@@ -2998,11 +3076,11 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
2998
3076
  }
2999
3077
  if (require_toolchain_inputs.isMiseConfigPath(file)) {
3000
3078
  const tools = toml("runtime-manager")?.["tools"];
3001
- if (record$1(tools)) for (const [tool, expected] of Object.entries(versions)) {
3079
+ if (record$2(tools)) for (const [tool, expected] of Object.entries(versions)) {
3002
3080
  const configured = tools[tool];
3003
3081
  if (configured !== void 0) pin({
3004
3082
  rule: tool === "rust" || tool === "core:rust" ? "rust-version" : "runtime-manager",
3005
- value: record$1(configured) ? configured["version"] : configured,
3083
+ value: record$2(configured) ? configured["version"] : configured,
3006
3084
  expected,
3007
3085
  selector: nodeTools.has(tool) ? "node" : void 0,
3008
3086
  label: `mise ${tool}`,
@@ -3350,7 +3428,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
3350
3428
  return diagnostics;
3351
3429
  }
3352
3430
  const nodeLine = (node) => {
3353
- if (!record$1(node) || !Array.isArray(node["range"])) return 1;
3431
+ if (!record$2(node) || !Array.isArray(node["range"])) return 1;
3354
3432
  const offset = node["range"].at(0);
3355
3433
  return typeof offset === "number" ? text.slice(0, offset).split("\n").length : 1;
3356
3434
  };
@@ -3464,7 +3542,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
3464
3542
  const manifest = JSON.parse(content ?? "");
3465
3543
  pin({
3466
3544
  rule,
3467
- value: record$1(manifest) ? manifest["packageManager"] : void 0,
3545
+ value: record$2(manifest) ? manifest["packageManager"] : void 0,
3468
3546
  expected: `bun@${policy.bun}`,
3469
3547
  label: "setup-bun version file packageManager",
3470
3548
  line
@@ -3664,10 +3742,21 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
3664
3742
  const selector = staticRepositoryPath(value) ? node_path.default.posix.normalize(value) : void 0;
3665
3743
  const providers = checkoutProvidersFor(selector);
3666
3744
  const binding = selector === void 0 ? void 0 : providers.at(-1);
3667
- if (selector === void 0 || unknownCheckoutDestination || binding !== void 0 && binding.source !== "tracked" || hasAmbiguousCheckoutWriters(selector, providers)) add({
3668
- rule: "action-pins",
3745
+ 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)) {
3746
+ add({
3747
+ rule: "action-pins",
3748
+ line,
3749
+ message: "local actions require an unambiguous checkout of the current source snapshot"
3750
+ });
3751
+ return;
3752
+ }
3753
+ if (binding?.source === "delegated") onDelegated?.({
3754
+ path: file,
3669
3755
  line,
3670
- message: "local actions require an unambiguous checkout of the current source snapshot"
3756
+ tool: "action",
3757
+ selector,
3758
+ checkoutPath: binding.path,
3759
+ ref: binding.ref
3671
3760
  });
3672
3761
  return;
3673
3762
  }
@@ -3965,6 +4054,54 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
3965
4054
  return diagnostics;
3966
4055
  };
3967
4056
  //#endregion
4057
+ //#region src/toolchain-rust-fixtures.ts
4058
+ const record$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4059
+ const date = (value) => /^\d{4}-\d{2}-\d{2}$/.test(value) && Number.isFinite(Date.parse(value)) && new Date(value).toISOString().slice(0, 10) === value;
4060
+ /** Historical install probes do not authorize using an older compiler. */
4061
+ const parseRustInstallFixtures = (input) => {
4062
+ if (input === void 0) return [];
4063
+ if (!Array.isArray(input)) throw new Error("rustInstallFixtures must be an array");
4064
+ const identities = /* @__PURE__ */ new Set();
4065
+ return input.map((entry) => {
4066
+ if (!record$1(entry) || Object.keys(entry).length !== 5 || Object.keys(entry).some((key) => ![
4067
+ "path",
4068
+ "pin",
4069
+ "reason",
4070
+ "tracking",
4071
+ "expires"
4072
+ ].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");
4073
+ const identity = `${entry["path"]}:${entry["pin"]}`;
4074
+ if (identities.has(identity)) throw new Error("Rust install fixture path/pin pairs must be unique");
4075
+ identities.add(identity);
4076
+ return {
4077
+ path: entry["path"],
4078
+ pin: entry["pin"],
4079
+ reason: entry["reason"],
4080
+ tracking: entry["tracking"],
4081
+ expires: entry["expires"]
4082
+ };
4083
+ });
4084
+ };
4085
+ const checkRustInstallFixtureExpiry = ({ fixtures, scope, now = /* @__PURE__ */ new Date() }) => {
4086
+ const errors = [];
4087
+ const warnings = [];
4088
+ for (const fixture of fixtures) {
4089
+ if (fixture.expires > now.toISOString().slice(0, 10)) continue;
4090
+ const verdict = require_quarantine_policy.overdueVerdict(scope, "stll-toolchain.json", (text) => {
4091
+ const base = JSON.parse(text);
4092
+ const declarations = record$1(base) ? base["rustInstallFixtures"] : void 0;
4093
+ return Array.isArray(declarations) && declarations.some((entry) => record$1(entry) && entry["path"] === fixture.path && entry["pin"] === fixture.pin && entry["expires"] === fixture.expires);
4094
+ });
4095
+ const message = `Rust install fixture ${fixture.path}:${fixture.pin} expired on ${fixture.expires}; remove it or renew the documented fixture boundary`;
4096
+ if (!verdict.fail) warnings.push(message);
4097
+ else errors.push(verdict.unavailable === void 0 ? message : `${message}; cannot determine whether this change adds it: ${verdict.unavailable}`);
4098
+ }
4099
+ return {
4100
+ errors,
4101
+ warnings
4102
+ };
4103
+ };
4104
+ //#endregion
3968
4105
  //#region src/toolchain-guard.ts
3969
4106
  const toolchainRules = [.../* @__PURE__ */ new Set([
3970
4107
  ...packageRules,
@@ -3979,7 +4116,7 @@ const record = (value) => typeof value === "object" && value !== null && !Array.
3979
4116
  const array = (value) => Array.isArray(value);
3980
4117
  /** Repository-owned declarations are explicit; malformed decisions fail closed. */
3981
4118
  const parseToolchainConfiguration = (input) => {
3982
- 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");
4119
+ 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");
3983
4120
  const disabled = /* @__PURE__ */ new Set();
3984
4121
  const optOuts = input["optOuts"] ?? [];
3985
4122
  if (!array(optOuts)) throw new Error("optOuts must be an array");
@@ -3996,6 +4133,7 @@ const parseToolchainConfiguration = (input) => {
3996
4133
  return {
3997
4134
  disabled,
3998
4135
  rustExceptions: parseRustExceptions(input["rustExceptions"]),
4136
+ rustInstallFixtures: parseRustInstallFixtures(input["rustInstallFixtures"]),
3999
4137
  cloud: require_toolchain_inputs.parseCloudSetup(input["cloud"]),
4000
4138
  engineFloors: parseEngineFloors(input["engineFloors"]),
4001
4139
  dynamicSelectors: parseDynamicSelectors(input["dynamicSelectors"]),
@@ -4088,11 +4226,20 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
4088
4226
  if (files["stll-toolchain.json"] !== void 0) try {
4089
4227
  configuration = parseToolchainConfiguration(JSON.parse(files["stll-toolchain.json"]));
4090
4228
  dynamicSelectors = configuration.dynamicSelectors;
4091
- const expiry = checkRustExceptionExpiry({
4229
+ const rustExpiry = checkRustExceptionExpiry({
4092
4230
  exceptions: configuration.rustExceptions,
4093
4231
  now,
4094
4232
  scope: expiryScope
4095
4233
  });
4234
+ const fixtureExpiry = checkRustInstallFixtureExpiry({
4235
+ fixtures: configuration.rustInstallFixtures,
4236
+ now,
4237
+ scope: expiryScope
4238
+ });
4239
+ const expiry = {
4240
+ errors: [...rustExpiry.errors, ...fixtureExpiry.errors],
4241
+ warnings: [...rustExpiry.warnings, ...fixtureExpiry.warnings]
4242
+ };
4096
4243
  for (const message of expiry.errors) diagnostics.push({
4097
4244
  rule: "configuration",
4098
4245
  path: "stll-toolchain.json",
@@ -4114,6 +4261,23 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
4114
4261
  message: error instanceof Error ? error.message : String(error)
4115
4262
  });
4116
4263
  }
4264
+ for (const entry of configuration.typescriptAllowlist) {
4265
+ if (entry.kind !== "source-api") continue;
4266
+ for (const source of entry.sources) {
4267
+ const content = readSourceApiInput({
4268
+ root,
4269
+ source,
4270
+ tracked: trackedFiles
4271
+ });
4272
+ if (content !== void 0) files[source] = content;
4273
+ else diagnostics.push({
4274
+ rule: "configuration",
4275
+ path: source,
4276
+ line: 1,
4277
+ message: "TypeScript source API evidence must be tracked, readable, regular and free of symlinks"
4278
+ });
4279
+ }
4280
+ }
4117
4281
  const cloud = configuration.cloud;
4118
4282
  const script = files[require_toolchain_inputs.cloudSetupPath];
4119
4283
  if (cloud === void 0 && trackedFiles.has(".agents/cloud-setup.sh")) diagnostics.push({
@@ -4226,6 +4390,7 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
4226
4390
  trackedFiles
4227
4391
  }));
4228
4392
  }
4393
+ const usedRustFixtures = /* @__PURE__ */ new Set();
4229
4394
  for (const [file, text] of Object.entries(files)) diagnostics.push(...checkRuntimeFile({
4230
4395
  file,
4231
4396
  text,
@@ -4238,6 +4403,8 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
4238
4403
  },
4239
4404
  trackedFiles,
4240
4405
  rustExceptions: configuration.rustExceptions,
4406
+ rustInstallFixtures: configuration.rustInstallFixtures,
4407
+ onRustInstallFixture: (entry) => usedRustFixtures.add(entry),
4241
4408
  dynamicSelectors: dynamicSelectors.filter((entry) => entry.path === file),
4242
4409
  onDynamicSelector: (entry) => selectorMatches.set(entry, (selectorMatches.get(entry) ?? 0) + 1),
4243
4410
  repository,
@@ -4249,6 +4416,12 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
4249
4416
  },
4250
4417
  readFile: (target) => files[target]
4251
4418
  }));
4419
+ for (const entry of configuration.rustInstallFixtures) if (!usedRustFixtures.has(entry)) diagnostics.push({
4420
+ rule: "configuration",
4421
+ path: "stll-toolchain.json",
4422
+ line: 1,
4423
+ message: `Rust install fixture ${entry.path}:${entry.pin} must match a tracked rustup toolchain install probe`
4424
+ });
4252
4425
  for (const entry of engineFloors) if (!exercisedFloors.has(entry) || mismatchedFloors.has(entry)) diagnostics.push({
4253
4426
  rule: "configuration",
4254
4427
  path: "stll-toolchain.json",