@openmixer/plugin-qualify 0.20.1 → 0.20.4

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.
Files changed (132) hide show
  1. package/bin/plugin-qualify-sync.mjs +1 -0
  2. package/catalogs/ca.json +251 -0
  3. package/catalogs/de.json +49 -0
  4. package/catalogs/es.json +49 -0
  5. package/catalogs/fr.json +49 -0
  6. package/catalogs/it.json +49 -0
  7. package/catalogs/pt.json +49 -0
  8. package/dist/clap-loader.js +2 -1
  9. package/dist/clap-loader.js.map +1 -1
  10. package/dist/clap-qualify.d.ts +11 -0
  11. package/dist/clap-qualify.js +16 -0
  12. package/dist/clap-qualify.js.map +1 -1
  13. package/dist/compare.d.ts +12 -11
  14. package/dist/compare.js +71 -38
  15. package/dist/compare.js.map +1 -1
  16. package/dist/host-admission.d.ts +25 -0
  17. package/dist/host-admission.js +41 -5
  18. package/dist/host-admission.js.map +1 -1
  19. package/dist/hostd-pin-files.d.ts +6 -2
  20. package/dist/hostd-pin-files.js +10 -6
  21. package/dist/hostd-pin-files.js.map +1 -1
  22. package/dist/hostd-search-path.d.ts +38 -0
  23. package/dist/hostd-search-path.js +58 -0
  24. package/dist/hostd-search-path.js.map +1 -0
  25. package/dist/index.d.ts +4 -0
  26. package/dist/index.js +4 -0
  27. package/dist/index.js.map +1 -1
  28. package/dist/jack-registry.js +1 -1
  29. package/dist/lv2-loader.d.ts +33 -1
  30. package/dist/lv2-loader.js +82 -19
  31. package/dist/lv2-loader.js.map +1 -1
  32. package/dist/measurement/vocabulary.js +3 -3
  33. package/dist/measurement/vocabulary.js.map +1 -1
  34. package/dist/measurement-grid.d.ts +1 -1
  35. package/dist/measurement-grid.js +5 -3
  36. package/dist/measurement-grid.js.map +1 -1
  37. package/dist/pins-file.d.ts +3 -2
  38. package/dist/pins-file.js +3 -2
  39. package/dist/pins-file.js.map +1 -1
  40. package/dist/plugin-db-cli.d.ts +28 -0
  41. package/dist/plugin-db-cli.js +43 -0
  42. package/dist/plugin-db-cli.js.map +1 -0
  43. package/dist/plugin-db-pin.d.ts +8 -0
  44. package/dist/plugin-db-pin.js +29 -0
  45. package/dist/plugin-db-pin.js.map +1 -0
  46. package/dist/probe-judge.js +5 -0
  47. package/dist/probe-judge.js.map +1 -1
  48. package/dist/probe-output.d.ts +10 -0
  49. package/dist/probe-output.js +16 -0
  50. package/dist/probe-output.js.map +1 -1
  51. package/dist/probe-runner.js +24 -5
  52. package/dist/probe-runner.js.map +1 -1
  53. package/dist/qualification-record.d.ts +5 -0
  54. package/dist/qualification-record.js +13 -1
  55. package/dist/qualification-record.js.map +1 -1
  56. package/dist/qualify-adapter-pass.d.ts +1 -2
  57. package/dist/qualify-adapter-pass.js +6 -6
  58. package/dist/qualify-adapter-pass.js.map +1 -1
  59. package/dist/qualify-declarations.d.ts +23 -6
  60. package/dist/qualify-declarations.js +25 -6
  61. package/dist/qualify-declarations.js.map +1 -1
  62. package/dist/qualify-isolated.d.ts +9 -0
  63. package/dist/qualify-isolated.js +16 -5
  64. package/dist/qualify-isolated.js.map +1 -1
  65. package/dist/qualify-pool.js +4 -3
  66. package/dist/qualify-pool.js.map +1 -1
  67. package/dist/qualify-rejudge.js +3 -3
  68. package/dist/qualify-rejudge.js.map +1 -1
  69. package/dist/qualify-sync.d.ts +12 -5
  70. package/dist/qualify-sync.js +51 -30
  71. package/dist/qualify-sync.js.map +1 -1
  72. package/dist/record-figures.d.ts +52 -0
  73. package/dist/record-figures.js +58 -0
  74. package/dist/record-figures.js.map +1 -0
  75. package/dist/refused-stderr-kept.d.ts +12 -0
  76. package/dist/refused-stderr-kept.js +67 -0
  77. package/dist/refused-stderr-kept.js.map +1 -0
  78. package/dist/run-home.d.ts +4 -0
  79. package/dist/run-home.js +26 -0
  80. package/dist/run-home.js.map +1 -0
  81. package/dist/text-catalog.d.ts +41 -0
  82. package/dist/text-catalog.js +97 -0
  83. package/dist/text-catalog.js.map +1 -0
  84. package/dist/text-codes.d.ts +29 -0
  85. package/dist/text-codes.generated.d.ts +88 -0
  86. package/dist/text-codes.generated.js +96 -0
  87. package/dist/text-codes.generated.js.map +1 -0
  88. package/dist/text-codes.js +19 -0
  89. package/dist/text-codes.js.map +1 -0
  90. package/package.json +8 -6
  91. package/schema/codes.json +386 -0
  92. package/src/clap-loader.ts +2 -1
  93. package/src/clap-qualify.ts +25 -0
  94. package/src/compare.ts +85 -45
  95. package/src/host-admission.ts +52 -5
  96. package/src/hostd-pin-files.ts +13 -8
  97. package/src/hostd-search-path.ts +85 -0
  98. package/src/index.ts +4 -0
  99. package/src/jack-registry.ts +1 -1
  100. package/src/lv2-loader.ts +89 -17
  101. package/src/measurement/vocabulary.ts +3 -3
  102. package/src/measurement-grid.ts +5 -3
  103. package/src/pins-file.ts +3 -2
  104. package/src/plugin-db-cli.ts +78 -0
  105. package/src/plugin-db-pin.ts +33 -0
  106. package/src/probe-judge.ts +5 -0
  107. package/src/probe-output.ts +23 -0
  108. package/src/probe-runner.ts +25 -4
  109. package/src/qualification-record.ts +13 -1
  110. package/src/qualify-adapter-pass.ts +6 -5
  111. package/src/qualify-declarations.ts +27 -6
  112. package/src/qualify-isolated.ts +18 -5
  113. package/src/qualify-pool.ts +4 -3
  114. package/src/qualify-rejudge.ts +3 -3
  115. package/src/qualify-sync.ts +55 -33
  116. package/src/record-figures.ts +114 -0
  117. package/src/refused-stderr-kept.ts +84 -0
  118. package/src/run-home.ts +28 -0
  119. package/src/text-catalog.ts +116 -0
  120. package/src/text-codes.generated.ts +97 -0
  121. package/src/text-codes.ts +35 -0
  122. package/tools/_declarations.py +2 -1
  123. package/tools/catalogs-gate.sh +22 -0
  124. package/tools/compare-host.mjs +1 -1
  125. package/tools/compare-run.mjs +10 -6
  126. package/tools/descriptors.py +40 -3
  127. package/tools/gen-python-declarations.mjs +1 -0
  128. package/tools/gen-text-codes.d.mts +8 -0
  129. package/tools/gen-text-codes.mjs +77 -0
  130. package/tools/isolated-step.mjs +24 -3
  131. package/tools/mod-host-admit.mjs +57 -1
  132. package/tools/scan.py +29 -5
@@ -22,38 +22,35 @@ import { closeSync, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, rea
22
22
  import { createConnection } from "node:net";
23
23
  import { homedir, tmpdir } from "node:os";
24
24
  import { basename, dirname, join, resolve, sep } from "node:path";
25
+ import { withRunHome } from "./run-home.js";
25
26
  import { clapQualifyTools } from "./clap-qualify.js";
26
27
  import { DISTRIBUTED_DIR_SYSTEM, EMPTY_LAYER, applyDistributed, distributedUri, hostClassOfBoard, hostClassOfMachine, outcomeOfVerdict, matchDistributed, readBoardModel, readMachine, readDistributedLayer } from "./distributed-records.js";
27
- import { clapSearchPath, parseClapScan } from "./clap-loader.js";
28
+ import { parseClapScan } from "./clap-loader.js";
29
+ import { SPAWNED_SEARCH_PATH, hostdSearchPath, searchPathWarnings } from "./hostd-search-path.js";
28
30
  import { HOSTING_CODE } from "./hosting-codes.js";
29
31
  import { shippedHostProfile } from "./host-profiles.js";
30
- import { lv2BundlesOnPath, manifestPlugins } from "./lv2-loader.js";
32
+ import { NO_JACK_SERVER } from "./host-admission.js";
33
+ import { iriPath, lv2BundlesOnPath, manifestPlugins } from "./lv2-loader.js";
31
34
  import { judgmentPinEntries, pinClearLine, pinLineUri, pinsFile } from "./pins-file.js";
32
35
  import { POOL_RESULT, completePool, judgePoolRun, pinAfterPool, pinFromRecordWithPool, poolEnv, spawnedPoolRunner } from "./qualify-pool.js";
33
36
  import { hostdPinFiles } from "./hostd-pin-files.js";
37
+ import { localeOf, reasonText } from "./text-catalog.js";
34
38
  import { qualifyTargets } from "./probe-runner.js";
35
39
  import { GRID_FULL, hostCells, parseGridSpec } from "./measurement-grid.js";
36
40
  import { adapterPass } from "./qualify-adapter-pass.js";
37
41
  import { ISOLATED_RESULT, baseSteps, completeIsolated, isolatedCell, missingSteps, judgeIsolatedRun, owesIsolated, pinAfterStep, spawnedIsolatedRunner } from "./qualify-isolated.js";
38
42
  import { lv2World, rejudgeRecord, rejudgeState, rejudgeToolName, memoryPass } from "./qualify-rejudge.js";
39
43
  import { memoryBoundOf, parseMemoryLimit } from "./memory-bound.js";
40
- import { QUALIFICATION_RECORD_SCHEMA, latencyNeverTaken, rewriteRecord, writeQualification } from "./qualification-record.js";
44
+ import { QUALIFICATION_RECORD_SCHEMA, descriptorNamePart, latencyNeverTaken, rewriteRecord, writeQualification } from "./qualification-record.js";
41
45
  import { ADMIT_STEP_MS_ENV, DESCRIPTOR_DIR_STATE_SUBDIR, DESCRIPTOR_DIR_SYSTEM, MEMORY_PASS_PROBE_NOTES, QUALIFY_QUANTA, QUALIFY_RATES } from "./qualify-declarations.js";
42
46
  import { toolsDir } from "./tool-paths.js";
43
47
  import { writeFileWhole } from "./whole-file.js";
44
48
  // --- the settings -----------------------------------------------------------------------------------------------------
45
49
  /** The settings file a system service reads, `key value` per line. */
46
50
  export const SYNC_CONF = "/etc/omx/qualify.conf";
47
- /** The LV2 directories plugin-hostd searches when `LV2_PATH` is unset (its `lv2_path` default). */
48
- export const HOSTD_LV2_PATH = ["/usr/lib64/lv2", "/usr/lib/lv2", "/usr/local/lib/lv2"];
49
- /** Zynthian's own LV2 directories: the image's plugins and the ones its user installs. */
50
- export const ZYNTHIAN_LV2_DIRS = ["/zynthian/zynthian-plugins/lv2", "/zynthian/zynthian-my-data/plugins/lv2"];
51
51
  /** Every setting, declared once: the settings file, the flags (`--pin-file` for `pin_file`) and `--help` read this. */
52
52
  export const SYNC_SETTINGS = [
53
- { key: "lv2_path", meaning: "the LV2 directories, colon-separated, in the order plugin-hostd searches them", default: "$LV2_PATH, else " + HOSTD_LV2_PATH.join(":") },
54
- { key: "lv2_extra", meaning: "more LV2 directories, searched after lv2_path", default: ZYNTHIAN_LV2_DIRS.join(":") },
55
- { key: "clap_path", meaning: "the CLAP directories, colon-separated", default: "$CLAP_PATH, then ~/.clap, then /usr/lib/clap" },
56
- { key: "clap_extra", meaning: "more CLAP directories, searched after clap_path", default: "" },
53
+ { key: "hostd_conf", meaning: "plugin-hostd's settings file: the LV2 and CLAP directories scanned are the ones plugin-hostd --search-path -c <file> prints", default: "plugin-hostd's own (its XDG_CONFIG_HOME/plugin-hostd.conf, else ~/.config/plugin-hostd.conf)" },
57
54
  { key: "pin_file", meaning: "the pin file plugin-hostd loads as pin_file", default: "/var/lib/omx/qualified.pins (system), else $XDG_STATE_HOME/omx/qualified.pins" },
58
55
  { key: "qualification_dir", meaning: "where the qualification records go", default: "/var/lib/omx/qualification (system), else $XDG_STATE_HOME/omx/qualification" },
59
56
  { key: "descriptor_dir", meaning: "where the scanned descriptors go", default: `${DESCRIPTOR_DIR_SYSTEM} (system), else $XDG_STATE_HOME/${DESCRIPTOR_DIR_STATE_SUBDIR}` },
@@ -70,6 +67,9 @@ export const SYNC_SETTINGS = [
70
67
  { key: "host_class", meaning: "the machine class whose distributed records are read (a directory name)", default: "the board's own from the device tree (rpi5 on a Raspberry Pi 5), else none" },
71
68
  { key: "distributed_dir", meaning: "the directories of distributed records, colon-separated, each holding <host class>/<set>/", default: `${DISTRIBUTED_DIR_SYSTEM} (system), else $XDG_DATA_HOME/omx/qualification and each $XDG_DATA_DIRS/omx/qualification` },
72
69
  ];
70
+ /** The search path settings that moved to plugin-hostd's settings file (plugin-qualify#9): refused here, with where they went. */
71
+ export const MOVED_TO_HOSTD = ["lv2_path", "lv2_extra", "clap_path", "clap_extra"];
72
+ const movedText = (key) => `${key} is plugin-hostd's setting now: write it in plugin-hostd's settings file (plugin-hostd.conf, named here by hostd_conf); the directories scanned are the ones plugin-hostd --search-path prints${key === "clap_extra" ? " (plugin-hostd has no clap_extra: add the directories to clap_path)" : ""}`;
73
73
  /** A settings file read: one `key value` per line, `#` starts a comment; a key alone is the empty value. An unknown key is refused by line. */
74
74
  export function parseSyncConf(text, source = SYNC_CONF) {
75
75
  const known = new Set(SYNC_SETTINGS.map((s) => s.key));
@@ -81,6 +81,8 @@ export function parseSyncConf(text, source = SYNC_CONF) {
81
81
  continue;
82
82
  const m = /^(\S+)(?:\s+(.*))?$/.exec(line);
83
83
  const key = m[1];
84
+ if (MOVED_TO_HOSTD.includes(key))
85
+ return { ok: false, error: `${source}:${String(n + 1)}: ${movedText(key)}` };
84
86
  if (!known.has(key))
85
87
  return { ok: false, error: `${source}:${String(n + 1)}: ${key} is not a setting` };
86
88
  values.set(key, (m[2] ?? "").trim());
@@ -138,6 +140,8 @@ export function parseSyncArgs(argv) {
138
140
  else
139
141
  values.set(byFlag.get(a), v);
140
142
  }
143
+ else if (MOVED_TO_HOSTD.some((k) => flagOf(k) === a))
144
+ return { ok: false, error: `${a}: ${movedText(a.slice(2).replace(/-/g, "_"))}` };
141
145
  else
142
146
  return { ok: false, error: `unknown argument ${a}` };
143
147
  }
@@ -174,17 +178,18 @@ export function syncHelpText() {
174
178
  }
175
179
  const dirList = (text) => text.split(":").filter((d) => d !== "");
176
180
  /** The settings of a run: a flag over the settings file over the default. */
177
- export function resolveSyncSettings(file, args, env, home = homedir(), boardModel = readBoardModel, machineArch = readMachine) {
181
+ export function resolveSyncSettings(file, args, env, home = homedir(), boardModel = readBoardModel, machineArch = readMachine, searchPathRun = SPAWNED_SEARCH_PATH) {
178
182
  const get = (key) => args.values.get(key) ?? file.get(key);
179
183
  const state = env["XDG_STATE_HOME"] !== undefined && env["XDG_STATE_HOME"] !== "" ? env["XDG_STATE_HOME"] : join(home, ".local", "state");
180
184
  const place = (key, system, user) => {
181
185
  const v = get(key);
182
186
  return v !== undefined && v !== "" ? v : args.system ? system : join(state, user);
183
187
  };
184
- const lv2Named = get("lv2_path");
185
- const lv2Base = lv2Named !== undefined && lv2Named !== "" ? dirList(lv2Named) : dirList(env["LV2_PATH"] ?? "").length > 0 ? dirList(env["LV2_PATH"]) : HOSTD_LV2_PATH;
186
- const clapNamed = get("clap_path");
187
- const clapBase = clapNamed !== undefined && clapNamed !== "" ? dirList(clapNamed) : clapSearchPath(env, home);
188
+ const hostdConfNamed = get("hostd_conf");
189
+ const hostdConf = hostdConfNamed !== undefined && hostdConfNamed !== "" ? hostdConfNamed : undefined;
190
+ const search = hostdSearchPath(hostdConf, env, searchPathRun);
191
+ if (!search.ok)
192
+ return { ok: false, error: `the directories to scan: ${search.error}` };
188
193
  const number = (key, min) => {
189
194
  const v = Number(get(key) ?? SYNC_SETTINGS.find((s) => s.key === key).default);
190
195
  return Number.isFinite(v) && v >= min ? v : undefined;
@@ -235,9 +240,10 @@ export function resolveSyncSettings(file, args, env, home = homedir(), boardMode
235
240
  return { ok: false, error: `host_class ${hostClass} is not a directory name` };
236
241
  return {
237
242
  ok: true,
243
+ warnings: searchPathWarnings(search.path, hostdConf),
238
244
  settings: {
239
- lv2Dirs: once([...lv2Base, ...dirList(get("lv2_extra") ?? ZYNTHIAN_LV2_DIRS.join(":"))]),
240
- clapDirs: once([...clapBase, ...dirList(get("clap_extra") ?? "")]),
245
+ lv2Dirs: once(search.path.lv2),
246
+ clapDirs: once(search.path.clap),
241
247
  pinFile: place("pin_file", "/var/lib/omx/qualified.pins", join("omx", "qualified.pins")),
242
248
  qualificationDir: place("qualification_dir", "/var/lib/omx/qualification", join("omx", "qualification")),
243
249
  descriptorDir: place("descriptor_dir", DESCRIPTOR_DIR_SYSTEM, DESCRIPTOR_DIR_STATE_SUBDIR),
@@ -379,7 +385,7 @@ export function discoverSyncItems(settings, lv2Resolve) {
379
385
  const binary = lilv?.binary ?? (p.binary === undefined ? undefined : join(bundle, p.binary));
380
386
  const files = ["manifest.ttl", ...p.files.filter((f) => f !== "manifest.ttl")];
381
387
  // a binary named by an absolute IRI (a complement's) is tracked through the bundle's link to it, when it has one
382
- if (binary !== undefined && !files.includes(basename(binary)) && linksTo(join(bundle, basename(binary)), binary))
388
+ if (binary !== undefined && !files.some((f) => iriPath(f) === basename(binary)) && linksTo(join(bundle, basename(binary)), binary))
383
389
  files.push(basename(binary));
384
390
  items.push({ key: `lv2:${p.uri}`, format: "lv2", root: bundle, files, uri: p.uri, ...(binary === undefined ? {} : { binary }) });
385
391
  }
@@ -409,7 +415,10 @@ export function fileSha256(path) {
409
415
  export function digestFiles(root, files, known = []) {
410
416
  const out = [];
411
417
  for (const path of [...new Set(files)].sort()) {
412
- const full = join(root, path);
418
+ // a name may be an IRI's word (the pin keeps `libSurge%20XT.so`); the file is the path it decodes to, and a name
419
+ // that is a file as written (a .clap with a % in it) is read as written when the decoded one is not there
420
+ const decoded = join(root, iriPath(path));
421
+ const full = existsSync(decoded) ? decoded : join(root, path);
413
422
  try {
414
423
  const st = statSync(full);
415
424
  if (!st.isFile())
@@ -451,6 +460,8 @@ export function machineReason(p) {
451
460
  const errno = /\b(EACCES|EPERM|ENOSPC)\b/.exec(p.detail ?? "")?.[1];
452
461
  if (errno !== undefined)
453
462
  return errno;
463
+ if ((p.detail ?? "").startsWith(NO_JACK_SERVER))
464
+ return NO_JACK_SERVER;
454
465
  return /wrote no document|cannot run the probe runner/.test(p.detail ?? "") ? "the measurement wrote no document" : undefined;
455
466
  }
456
467
  /**
@@ -520,7 +531,7 @@ export function recordedAsIs(item, entry) {
520
531
  export function sameBinaryElsewhere(item, entry) {
521
532
  if (item.format !== "lv2" || entry === undefined || item.binary === undefined || resolve(entry.root) === resolve(item.root))
522
533
  return undefined;
523
- const was = entry.files.find((f) => basename(f.path) === basename(item.binary));
534
+ const was = entry.files.find((f) => basename(iriPath(f.path)) === basename(item.binary));
524
535
  if (was === undefined)
525
536
  return undefined;
526
537
  let now;
@@ -547,7 +558,7 @@ function withHostdFiles(ctx, pin) {
547
558
  return pin;
548
559
  const r = hostdPinFiles(pin.uri, ctx.settings.lv2Dirs, ctx.env, ...(ctx.pinFilesRun === undefined ? [] : [ctx.pinFilesRun]));
549
560
  if (!r.ok) {
550
- ctx.log(`${pin.uri}: not pinned: ${r.detail}`);
561
+ ctx.log(`${pin.uri}: not pinned: ${reasonText(r.detail, localeOf(ctx.env))}`);
551
562
  return undefined;
552
563
  }
553
564
  return { ...pin, integrity: { ...pin.integrity, files: r.files } };
@@ -570,7 +581,7 @@ export function refreshPins(ctx, state) {
570
581
  return plugin;
571
582
  const r = hostdPinFiles(pin.uri, ctx.settings.lv2Dirs, ctx.env, ...(ctx.pinFilesRun === undefined ? [] : [ctx.pinFilesRun]));
572
583
  if (!r.ok) {
573
- ctx.log(`${pin.uri}: pin files not refreshed: ${r.detail}`);
584
+ ctx.log(`${pin.uri}: pin files not refreshed: ${reasonText(r.detail, localeOf(ctx.env))}`);
574
585
  return plugin;
575
586
  }
576
587
  const old = new Map(pin.integrity.files.map((f) => [f.path, f.sha256]));
@@ -604,7 +615,7 @@ function outcomeOf(judgment, host) {
604
615
  return { outcome: "qualified", ...(code === undefined ? {} : { code }) };
605
616
  return { outcome: v.rating === "unsuitable" ? "refused" : "unmeasured", ...(code === undefined ? {} : { code }) };
606
617
  }
607
- const safe = (text) => text.replace(/[^A-Za-z0-9._-]/g, "_");
618
+ const safe = descriptorNamePart;
608
619
  /** The record of a plugin no probe output exists for: what it is, the bytes it was, and why nothing was measured. */
609
620
  function writeUnmeasuredRecord(ctx, item, files, plugin) {
610
621
  const main = files.find((f) => f.path === item.files[item.format === "clap" ? 0 : 1]) ?? files[0];
@@ -622,8 +633,18 @@ function writeUnmeasuredRecord(ctx, item, files, plugin) {
622
633
  writeFileWhole(path, `${JSON.stringify(record, null, 2)}\n`);
623
634
  return path;
624
635
  }
625
- /** The step's environment: each of its commands has the sync's budget. */
626
- const isolatedEnv = (ctx) => ({ ...ctx.env, [ADMIT_STEP_MS_ENV]: String(ctx.settings.budgetS * 1000) });
636
+ /** The directories the sync scanned, as the plugin-hostd a step starts reads them: with neither variable set it searches
637
+ * only its own defaults, and every plugin of `lv2_extra` or of a directory the settings add is refused at add. */
638
+ export const hostdSearchEnv = (settings) => ({
639
+ LV2_PATH: settings.lv2Dirs.join(":"),
640
+ CLAP_PATH: settings.clapDirs.join(":"),
641
+ });
642
+ /** The step's environment: the sync's directories, and each of its commands has the sync's budget. */
643
+ export const isolatedEnv = (ctx) => ({
644
+ ...ctx.env,
645
+ ...hostdSearchEnv(ctx.settings),
646
+ [ADMIT_STEP_MS_ENV]: String(ctx.settings.budgetS * 1000),
647
+ });
627
648
  /** Each judged run of a pass as the plugins it qualified: the record written, the pin taken, the outcome. */
628
649
  function judgedPlugins(ctx, runs, describeProblem) {
629
650
  const host = ctx.settings.hostProfile.id;
@@ -660,7 +681,7 @@ function judgedPlugins(ctx, runs, describeProblem) {
660
681
  // the pool is for a CLAP plugin that runs: the thread pool is granted by measuring it, here and nowhere else
661
682
  if (judgment.output.plugin.format === "clap" && pin !== undefined && pin.verdict !== "refused" && outcome === "qualified") {
662
683
  const runner = ctx.poolRunner ?? spawnedPoolRunner();
663
- const step = judgePoolRun(runner(pin.uri, isolatedCell(record, host), poolEnv(ctx.env, ctx.settings.budgetS)));
684
+ const step = judgePoolRun(runner(pin.uri, isolatedCell(record, host), poolEnv({ ...ctx.env, ...hostdSearchEnv(ctx.settings) }, ctx.settings.budgetS)));
664
685
  steps.push(step);
665
686
  pin = pinAfterPool(pin, step);
666
687
  }
@@ -733,7 +754,7 @@ function qualifyClap(ctx, item) {
733
754
  return [{ uri: fileUri, outcome: "unmeasured", code: SYNC_CODE.notMeasured, detail: e.message }];
734
755
  }
735
756
  // the scanner lists the plugins of the file in a child of its own, and says how that child ended
736
- const r = spawnSync(tool, ["--json", file], { encoding: "utf8", timeout: Math.max(ctx.settings.budgetS, 35) * 1000, killSignal: "SIGKILL", maxBuffer: 64 << 20 });
757
+ const r = spawnSync(tool, ["--json", file], { encoding: "utf8", timeout: Math.max(ctx.settings.budgetS, 35) * 1000, killSignal: "SIGKILL", maxBuffer: 64 << 20, env: withRunHome(process.env) });
737
758
  let scanned;
738
759
  try {
739
760
  if (r.error !== undefined)
@@ -873,7 +894,7 @@ export function poolPass(ctx, state) {
873
894
  ctx.log(`${p.uri}: its record does not read; no pool step`);
874
895
  return p;
875
896
  }
876
- const done = completePool(path, record, p.pin.uri, isolatedCell(record, host), runner, poolEnv(ctx.env, ctx.settings.budgetS));
897
+ const done = completePool(path, record, p.pin.uri, isolatedCell(record, host), runner, poolEnv({ ...ctx.env, ...hostdSearchEnv(ctx.settings) }, ctx.settings.budgetS));
877
898
  if (done === undefined)
878
899
  return p;
879
900
  ctx.log(`${p.uri}: pool step ${done.step.result}${done.step.code === undefined ? "" : ` (${done.step.code})`}`);
@@ -987,7 +1008,7 @@ function scanClapDescriptors(ctx, item) {
987
1008
  ctx.log(`${file}: descriptors not written: ${e.message}`);
988
1009
  return;
989
1010
  }
990
- const r = spawnSync(tool, ["--json", file], { encoding: "utf8", timeout: Math.max(ctx.settings.budgetS, 35) * 1000, killSignal: "SIGKILL", maxBuffer: 64 << 20 });
1011
+ const r = spawnSync(tool, ["--json", file], { encoding: "utf8", timeout: Math.max(ctx.settings.budgetS, 35) * 1000, killSignal: "SIGKILL", maxBuffer: 64 << 20, env: withRunHome(process.env) });
991
1012
  if (r.error !== undefined || r.status !== 0) {
992
1013
  ctx.log(`${file}: descriptors not written: the scan ended ${r.error?.message ?? `with exit ${String(r.status)}`}`);
993
1014
  return;