@openmixer/plugin-qualify 0.16.0 → 0.17.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.
Files changed (68) hide show
  1. package/README.md +154 -7
  2. package/bin/plugin-qualify-records-package.mjs +31 -0
  3. package/bin/plugin-qualify-sync.mjs +6 -1
  4. package/bin/plugin-qualify.mjs +12 -1
  5. package/dist/distributed-records.d.ts +88 -0
  6. package/dist/distributed-records.js +210 -0
  7. package/dist/distributed-records.js.map +1 -0
  8. package/dist/host-profiles.d.ts +5 -0
  9. package/dist/host-profiles.js +13 -0
  10. package/dist/host-profiles.js.map +1 -1
  11. package/dist/hosting-suitability.js +9 -1
  12. package/dist/hosting-suitability.js.map +1 -1
  13. package/dist/index.d.ts +5 -0
  14. package/dist/index.js +5 -0
  15. package/dist/index.js.map +1 -1
  16. package/dist/measurement-grid.d.ts +49 -0
  17. package/dist/measurement-grid.js +76 -0
  18. package/dist/measurement-grid.js.map +1 -0
  19. package/dist/memory-bound.d.ts +23 -0
  20. package/dist/memory-bound.js +46 -0
  21. package/dist/memory-bound.js.map +1 -0
  22. package/dist/probe-judge.js +22 -1
  23. package/dist/probe-judge.js.map +1 -1
  24. package/dist/probe-merge.d.ts +30 -0
  25. package/dist/probe-merge.js +80 -0
  26. package/dist/probe-merge.js.map +1 -0
  27. package/dist/probe-output.d.ts +8 -0
  28. package/dist/probe-output.js +7 -0
  29. package/dist/probe-output.js.map +1 -1
  30. package/dist/probe-runner.d.ts +26 -2
  31. package/dist/probe-runner.js +38 -10
  32. package/dist/probe-runner.js.map +1 -1
  33. package/dist/qualification-record.d.ts +19 -1
  34. package/dist/qualification-record.js +30 -1
  35. package/dist/qualification-record.js.map +1 -1
  36. package/dist/qualify-cli.d.ts +9 -0
  37. package/dist/qualify-cli.js +30 -1
  38. package/dist/qualify-cli.js.map +1 -1
  39. package/dist/qualify-declarations.d.ts +15 -0
  40. package/dist/qualify-declarations.js +15 -0
  41. package/dist/qualify-declarations.js.map +1 -1
  42. package/dist/qualify-options.js +11 -1
  43. package/dist/qualify-options.js.map +1 -1
  44. package/dist/qualify-sync.d.ts +23 -3
  45. package/dist/qualify-sync.js +172 -16
  46. package/dist/qualify-sync.js.map +1 -1
  47. package/dist/records-package.d.ts +63 -0
  48. package/dist/records-package.js +300 -0
  49. package/dist/records-package.js.map +1 -0
  50. package/package.json +3 -2
  51. package/src/distributed-records.ts +251 -0
  52. package/src/host-profiles.ts +13 -0
  53. package/src/hosting-suitability.ts +11 -1
  54. package/src/index.ts +5 -0
  55. package/src/measurement-grid.ts +97 -0
  56. package/src/memory-bound.ts +55 -0
  57. package/src/probe-judge.ts +25 -1
  58. package/src/probe-merge.ts +89 -0
  59. package/src/probe-output.ts +13 -0
  60. package/src/probe-runner.ts +75 -13
  61. package/src/qualification-record.ts +44 -2
  62. package/src/qualify-cli.ts +32 -1
  63. package/src/qualify-declarations.ts +17 -0
  64. package/src/qualify-options.ts +11 -1
  65. package/src/qualify-sync.ts +175 -16
  66. package/src/records-package.ts +333 -0
  67. package/tools/{ai-trace-commit-check.sh → commit-message-check.sh} +30 -28
  68. package/tools/{ai-trace-patterns.json → commit-message-patterns.json} +1 -1
package/README.md CHANGED
@@ -69,6 +69,81 @@ A named LV2 plugin is measured the same way as a CLAP one: through the probe run
69
69
  output it writes (`plugin-qualify judge` below). Exit code: `0` every plugin qualified, `1` some did not, `2`
70
70
  something could not be measured.
71
71
 
72
+ ## The measurement grid
73
+
74
+ A plugin is measured in cells, each one sample rate and one quantum (block size in frames). The verdict a host
75
+ gets reads the cell of that host's rate and quantum, so the grid has to hold it.
76
+
77
+ ```
78
+ plugin-qualify --all --out ./q # the hosts' grid (the default, also --grid-hosts)
79
+ plugin-qualify --all --grid full --out ./q # every --rates rate at every --quanta quantum
80
+ plugin-qualify --grid 48000/128,96000/1024 urn:my:fx # exactly these cells
81
+ ```
82
+
83
+ - `--grid hosts` (the default; `--grid-hosts` says the same): the rate and quantum of every host the run judges
84
+ (each `--host-profile`, and the console's two tiers, `openmixer-console` and the format's isolated host), then
85
+ quantum 512 at every `--rates` rate, so that a record stays comparable with the ones written before the grid.
86
+ With the four shipped hosts that is seven cells: 96000/1024 (`openmixer-console`), 48000/256 (`jalv`),
87
+ 48000/128 (`mod-host`, `omx-clap-host`), and 512 at 44100, 48000, 96000 and 192000.
88
+ - `--grid full`: every `--rates` rate at every `--quanta` quantum, 16 cells with the defaults
89
+ (44100, 48000, 96000, 192000 by 128, 256, 512, 1024).
90
+ - `--grid <rate>/<quantum>,...`: exactly the cells named.
91
+
92
+ The cells are taken in that order: the hosts' own cells first, in the order the hosts are named, then the
93
+ 512 cells, then the rest by rate and quantum. `--cells-per-plugin-budget N` keeps the first N, so a first pass
94
+ over a large machine can measure the hosts' cells alone (`--cells-per-plugin-budget 3` with the four shipped hosts).
95
+ `--blocks N` shortens the CLAP sweep of each cell from 512 blocks to N; the LV2 pass has a fixed shape (30
96
+ lifecycles and 3 swept runs per cell) and is not shortened by it.
97
+
98
+ An LV2 plugin is measured on the same grid as a CLAP one. plugin-hostd's runner measures an LV2 plugin at one
99
+ rate per run (the highest it is given), which is why records written before the grid hold LV2 figures at 192000
100
+ only; plugin-qualify now runs it once per cell. A CLAP plugin is run once per quantum, at every rate of the grid
101
+ at that quantum. The runs of one plugin and one offer make one probe output: the grid points of every run, the
102
+ steps of every run, and the run's outcome. The record and its byte copy hold that document.
103
+
104
+ A run that ended badly leaves a grid point at each of its cells with the run's outcome in place of figures, so a
105
+ cell is never silently absent. The cost a host is judged by is read at its cell: a measured cell is compared with
106
+ the host's budget; a cell whose run failed reads `unknown` with that run's outcome (`probe.oom`, say); only a
107
+ cell the grid does not hold reads `hosting.cost.unmeasured-at-rate-and-quantum`.
108
+
109
+ ### The memory bound
110
+
111
+ A sampler loading its library can take all the memory of a small machine, and the kernel then kills it, or
112
+ something else. Every run is bounded: `--memory-limit <bytes>` (`512M`, `2G`, or `off`), by default half of
113
+ the machine's RAM. The bound is the runner's data limit (`--data-bytes`, `RLIMIT_DATA` on the measuring child:
114
+ its heap and private writable mappings), and the interposer the child runs under turns an allocation refused
115
+ under that limit into an immediate exit with status 121. A run that ends that way, or by a `SIGKILL` the
116
+ runner did not send (the kernel's out-of-memory killer), is recorded as `probe.oom`: its cells carry the
117
+ outcome and `memoryLimit { bytes, mechanism }`, the plugin's record is still written, and the other cells are
118
+ judged as usual. A plugin whose every run ended `probe.oom` is `unknown` under every host, deciding `probe.oom`.
119
+ A host whose own cell ended `probe.oom` is `unknown` there as well (`memory`, `probe.oom`): the plugin did not
120
+ run within the bound at that host's rate and quantum.
121
+
122
+ ### How long a run takes
123
+
124
+ Times from one deep run on a Raspberry Pi 5 (4 cores, 4 GB), 1823 records (494 CLAP, two offers of each of 247,
125
+ and 1329 LV2), six hours in all with the grid of before (CLAP at four rates in one run at quantum 512, LV2 at
126
+ 192000/512 alone):
127
+
128
+ | | runs | mean a run | total |
129
+ | --- | --- | --- | --- |
130
+ | CLAP, one run per offer (4 cells) | 494 | 2.8 s | 0.38 h |
131
+ | LV2, one run (1 cell) | 1329 | 10.5 s | 3.86 h |
132
+ | host admissions (jalv, mod-host) and the scan | | | about 1.8 h |
133
+
134
+ A CLAP plugin takes one run per quantum of the grid, an LV2 plugin one run per cell; the admissions do not
135
+ grow with the grid. On that machine, then, about:
136
+
137
+ | grid | CLAP runs a plugin | LV2 runs a plugin | expected |
138
+ | --- | --- | --- | --- |
139
+ | hosts (7 cells) | 4 | 7 | 1.5 h + 27 h + 1.8 h, about 30 h |
140
+ | full (16 cells) | 4 | 16 | 1.5 h + 62 h + 1.8 h, about 65 h |
141
+ | hosts, `--cells-per-plugin-budget 3` | 3 | 3 | 1.1 h + 11.6 h + 1.8 h, about 15 h |
142
+
143
+ These are ceilings: the LV2 time a run was taken at 192000, the dearest rate. `--blocks 128` takes a quarter of
144
+ the CLAP sweep a cell. A first pass with `--cells-per-plugin-budget 3`, then `--grid full` over the plugins worth
145
+ it, keeps a small machine's night bounded.
146
+
72
147
  ## CLAP plugins: the RT-safety verdict
73
148
 
74
149
  A `.clap` file, or a directory holding one, is scanned by `omx-clap-scan` (from omx-clap-core; set
@@ -95,7 +170,7 @@ are measured by `tools/lv2-measure.mjs` (below); this entry point does not drive
95
170
  system lv2 directories) rather than qualify only the CLAP side. A directory on the CLAP path that cannot
96
171
  be read (permissions, a symlink loop) also refuses with exit 2, naming it.
97
172
 
98
- The prober runs each plugin headless at every `--rates` rate, under an interposer that counts the
173
+ The prober runs each plugin headless at every cell of the grid (see "The measurement grid"), under an interposer that counts the
99
174
  allocations, locks and syscalls its `process()` makes while every parameter is swept, and prints
100
175
  the file's SHA-256 beside what it measured. The verdict is the real-time-safety one: **suitable**
101
176
  only on a clean count, **unsuitable** on any allocation, lock, syscall, main-thread host call from
@@ -159,6 +234,13 @@ stays silent, an instrument with no count to hold) is left out of the grid point
159
234
  `measurements.unmeasured` with its hosting code (`hosting.latency.unmeasured`,
160
235
  `hosting.cost.unmeasured-at-rate-and-quantum`).
161
236
 
237
+ Every cell of a record carries `latency`: the two figures when the run took them, else
238
+ `{ unmeasuredReason, code }`, the contract's word for why and the hosting code a reader classifies by. A latency
239
+ step that did not end well gives the child's reason (`silent-output`, say); a cell whose run failed, its outcome's
240
+ word (`probe-crashed`, or `probe-error` for `probe.oom`); a run that took no latency at all (a runner or a child
241
+ older than the latency arm), `not-attempted`. The declared figure alone, for a plugin whose response could not be
242
+ measured, is not in the probe output today: plugin-hostd's runner writes a cell's latency only with both figures.
243
+
162
244
 
163
245
  The prober also scans: `OMX_CLAP_SCAN=native/build/clap-qualify` makes it the scanner, and its scan
164
246
  keeps what a console needs to draw a third-party plugin's controls: an enum parameter's labels (as
@@ -201,7 +283,8 @@ history outside its grammar is not read and is named on stderr; a history of a v
201
283
  `tools/lv2-measure.mjs` measures the hosting dimensions (`stability`, `rtSafety`, `features`) through
202
284
  the LV2 measurer the package installs, `lv2_measure`: each plugin is loaded through lilv in its own
203
285
  process, instantiated and destroyed 30 times, and run under the interposer while every control is
204
- swept, at the highest `--rates` rate:
286
+ swept. `tools/lv2-measure.mjs` measures at the highest `--rates` rate; `plugin-qualify` runs the measurer
287
+ once per cell of its grid (see "The measurement grid"):
205
288
 
206
289
  ```
207
290
  node tools/lv2-measure.mjs --rates 48000 --dimensions stability,rtSafety,features \
@@ -370,7 +453,7 @@ With several profiles, the exit code is `0` only if every plugin qualifies under
370
453
 
371
454
  ### The JSON document
372
455
 
373
- `--out <dir>` writes one interchange document per run (`lv2-plugin-measurements`, format 1.2,
456
+ `--out <dir>` writes one interchange document per run (`lv2-plugin-measurements`, format 1.5,
374
457
  the same format the OpenMixer console reads). The
375
458
  `plugins` array holds the raw, host-free measurements. Beside it, `hostProfiles` holds each
376
459
  profile the run judged against, in full, and `verdicts` holds one entry per plugin per profile:
@@ -418,16 +501,16 @@ The package ships it as a service that runs two minutes after boot and whenever
418
501
  changes, once the directory has been quiet for a minute. A run finds every `.clap` under the CLAP
419
502
  directories and every plugin of every bundle under the LV2 ones, and hashes each one's files as
420
503
  plugin-hostd does for a pin. A plugin whose files hash as recorded is skipped. A new or changed one gets the
421
- quick qualification through the probe runner: its pin, the RT-safety sweep, cost and latency, its
504
+ quick qualification through the probe runner, on the grid `grid` names (see "The measurement grid"): its pin, the RT-safety sweep, cost and latency, its
422
505
  qualification record and its descriptor. The soak is not part of it, and every record it writes says so in
423
506
  `notMeasured` (`hosting.stability.unsoaked`).
424
507
 
425
- Each plugin has `budget_s` seconds. One that crashes or hangs is recorded as `crashed` or `hung`, one that
508
+ Each run of the runner (one cell of an LV2 plugin, one quantum of a CLAP one) has `budget_s` seconds, under `memory_limit`. One that crashes or hangs is recorded as `crashed` or `hung`, one that
426
509
  fails the sweep is pinned `verdict=refused`, and the run goes on to the next. The pin file and the state are
427
510
  written whole after every plugin, so a run cut short keeps what it finished. The last line counts the run:
428
511
 
429
512
  ```
430
- qualified 2, skipped 0, refused 0, crashed 1, hung 1, unmeasured 0, 4.6 s
513
+ qualified 2, skipped 0, refused 0, crashed 1, hung 1, unmeasured 0, distributed 0, 4.6 s
431
514
  ```
432
515
 
433
516
  A plugin that is no longer found, or whose bytes changed, leaves nothing behind: its pin, its qualification
@@ -452,17 +535,81 @@ The settings are `/etc/omx/qualify.conf`, one `key value` per line, and each is
452
535
  | `state_file` | `/var/lib/omx/qualify-sync.json` |
453
536
  | `budget_s` | `60` |
454
537
  | `host_profile` | `omx-clap-host` |
538
+ | `grid` | `hosts`: the cells of `host_profile` and of the console's two tiers, then 512 at every rate (`full`, or `<rate>/<quantum>,...`) |
539
+ | `cells_per_plugin_budget` | `0`, every cell of the grid |
540
+ | `memory_limit` | half of the machine's RAM (`off` for none) |
455
541
  | `hostd_port`, `hostd_feedback_port` | `5555`, the next port |
542
+ | `host_class` | the board's own, from the device tree (`rpi5`); none elsewhere |
543
+ | `distributed_dir` | `/usr/share/omx/qualification` |
456
544
 
457
545
  Those are the service's (`--system`); run by a user, the files go under `$XDG_STATE_HOME/omx`. An LV2
458
546
  directory added here belongs in plugin-hostd's `lv2_path` too, or the daemon cannot find the bundle it
459
- pinned. `--force` qualifies everything again.
547
+ pinned. `--force` qualifies everything again, and `--no-distributed` reads no distributed record.
460
548
 
461
549
  The path unit names the standard folders (`/usr/lib/lv2`, `/usr/lib64/lv2`, `/usr/local/lib/lv2`,
462
550
  `/usr/lib/clap`, `/usr/lib64/clap`). The folders the settings file adds (`lv2_path`, `lv2_extra`, `clap_path`,
463
551
  `clap_extra`) are written into a drop-in of the unit by a systemd generator at boot and at every
464
552
  `systemctl daemon-reload`, so the file is the one place a machine's extra folders are written.
465
553
 
554
+ ### Distributed records
555
+
556
+ A device that ships with a known plugin set need not measure it again at first start. The qualification records
557
+ another device of the same machine class wrote for that set are installed read-only, and the sync takes from them
558
+ every plugin whose files are the bytes that were measured:
559
+
560
+ ```
561
+ /usr/share/omx/qualification/<host class>/<set>/ # system (--system)
562
+ $XDG_DATA_HOME/omx/qualification/<host class>/<set>/ # a user run, then each $XDG_DATA_DIRS/omx/qualification
563
+ ```
564
+
565
+ Each `<set>` directory holds the records (`*.qualification.json`, each with its probe output `*.probe.json`) and a
566
+ `provenance.json` that says where they come from: the set, the host class, the image, the package versions of the
567
+ measuring device, the date and the package that installed them. The host class is a directory name: `host_class`
568
+ in the settings, or, when it is unset, the board's own name read from the device tree (`rpi5` on a Raspberry Pi 5,
569
+ `rpi4` on a Raspberry Pi 4). Where neither gives one, the layer is not read and the run says so.
570
+
571
+ Before a plugin is measured, the sync looks for a distributed record of it: a CLAP record whose binary is the same
572
+ `.clap` path, or an LV2 record of the same plugin uri. The name only finds the record; a record applies when, and
573
+ only when:
574
+
575
+ - every file of its `binary.files` is installed at that path relative to the plugin's root, with that SHA-256,
576
+ computed on the device;
577
+ - its schema is the one this version writes and its probe output is one this version's reader accepts, the copy
578
+ intact (its SHA-256 the one the record names): the same acceptance the sync gives a record of its own, which it
579
+ never measures again for a newer tool;
580
+ - it holds a verdict for the sync's `host_profile`, and that verdict ended `qualified`, `refused`, `crashed` or
581
+ `hung`. One that could not be measured at the source (`unmeasured`) is measured here.
582
+
583
+ A plugin whose distributed records apply is not measured: the records are copied into `qualification_dir` with the
584
+ set's `provenance` object added, its pin is taken from the probe output the record judged, its descriptor is
585
+ written, and the run says
586
+
587
+ ```
588
+ distributed: http://distrho.sf.net/plugins/3BandEQ: applied (zynthian-vangelis)
589
+ ```
590
+
591
+ A plugin with a distributed record whose files differ is measured as before, and the run says
592
+ `distributed: <uri>: binary differs`; one with no distributed record is measured without a word. The summary counts
593
+ the plugins taken from the layer, `distributed N`, apart from the ones measured and skipped. A later run treats an
594
+ applied plugin like one it measured: skipped while its files are unchanged, measured when they change. `--force`
595
+ measures everything and reads no distributed record; `--no-distributed` keeps the layer out of a run.
596
+
597
+ The package that installs a set is built from a device's `qualification_dir` by `plugin-qualify-records-package`:
598
+
599
+ ```
600
+ plugin-qualify-records-package /var/lib/omx/qualification --host-class rpi5 --set zynthian-vangelis \
601
+ --provenance provenance.json --out .
602
+ ```
603
+
604
+ It writes `omx-qualification-<host class>-<set>_<version>_all.deb`, the version the build date
605
+ (`2026.10.29`) unless `--version` names one. The package installs the records under
606
+ `/usr/share/omx/qualification/<host class>/<set>/` with `provenance.json`, depends on the plugin-qualify that reads
607
+ the layer, and is `Architecture: all`. `--provenance` names a JSON file (or holds the JSON itself) with what the tool
608
+ cannot know: the image, the package versions and the source revisions of the device. The tool adds the set, the host
609
+ class, the package and the date. A record whose probe output is missing or does not match its SHA-256 is left out
610
+ and named; a record of a plugin that could not be measured (`*.unmeasured.json`) is left out, since it never
611
+ applies. The tool builds the Debian package only; an RPM of a set is not built yet.
612
+
466
613
  ### The container image
467
614
 
468
615
  ```
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: GPL-3.0-or-later
3
+ // Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
4
+ /**
5
+ * `plugin-qualify-records-package`, the entry point: the Debian package of a device's qualification records. Every
6
+ * decision is in `src/records-package.ts`; this file reads argv, builds the package, names what stayed out on stderr
7
+ * and the package on the last line of stdout.
8
+ */
9
+ import { buildRecordsPackage, parseRecordsPackageArgs, recordsPackageHelp } from "../dist/index.js";
10
+
11
+ const log = (line) => process.stderr.write(`plugin-qualify-records-package: ${line}\n`);
12
+
13
+ const parsed = parseRecordsPackageArgs(process.argv.slice(2));
14
+ if (!parsed.ok) {
15
+ log(parsed.error);
16
+ process.stderr.write(`\n${recordsPackageHelp()}`);
17
+ process.exit(2);
18
+ }
19
+ if ("help" in parsed) {
20
+ process.stdout.write(recordsPackageHelp());
21
+ process.exit(0);
22
+ }
23
+ let built;
24
+ try {
25
+ built = buildRecordsPackage(parsed.args);
26
+ } catch (e) {
27
+ log(e.message);
28
+ process.exit(2);
29
+ }
30
+ for (const l of built.left) log(`${l.name}: ${l.why}; left out`);
31
+ process.stdout.write(`${built.path}: ${String(built.records)} records, ${String(built.files)} files\n`);
@@ -44,7 +44,12 @@ if (!resolved.ok) {
44
44
  process.exit(2);
45
45
  }
46
46
  const s = resolved.settings;
47
- log(`CLAP ${s.clapDirs.join(":")}; LV2 ${s.lv2Dirs.join(":")}; host ${s.hostProfile.id}; ${String(s.budgetS)} s a plugin`);
47
+ const grid = s.grid.kind === "cells" ? s.grid.cells.map((c) => `${String(c.rate)}/${String(c.quantum)}`).join(",") : s.grid.kind;
48
+ log(
49
+ `CLAP ${s.clapDirs.join(":")}; LV2 ${s.lv2Dirs.join(":")}; host ${s.hostProfile.id}; grid ${grid}` +
50
+ `${s.cellsPerPluginBudget > 0 ? ` (first ${String(s.cellsPerPluginBudget)} cells)` : ""}; ${String(s.budgetS)} s a run; ` +
51
+ `memory ${s.memory === undefined ? "unbounded" : `${String(s.memory.bytes)} bytes`}`,
52
+ );
48
53
  try {
49
54
  const summary = await runSync(s, { force: parsed.args.force, log });
50
55
  if (summary !== undefined) {
@@ -35,6 +35,7 @@ import {
35
35
  exitCodeFor,
36
36
  helpText,
37
37
  lv2Loader,
38
+ memoryBoundOf,
38
39
  parseQualifyArgs,
39
40
  pinsFile,
40
41
  qualificationRecordOf,
@@ -179,7 +180,17 @@ if (parsed.args.all) {
179
180
  const lv2 = plugins.filter((p) => p.format === "lv2").length;
180
181
  process.stderr.write(`plugin-qualify: --all qualifies ${String(lv2)} LV2 and ${String(plugins.length - lv2)} CLAP plugins on the search paths\n`);
181
182
  }
182
- const pass = qualifyTargets(plugins, { rates: parsed.args.rates, profiles: profiles.profiles });
183
+ const memory = memoryBoundOf(parsed.args.memoryLimit);
184
+ process.stderr.write(`plugin-qualify: memory bound ${memory === undefined ? "off" : `${String(memory.bytes)} bytes (${memory.mechanism})`}\n`);
185
+ const pass = qualifyTargets(plugins, {
186
+ rates: parsed.args.rates,
187
+ quanta: parsed.args.quanta,
188
+ grid: parsed.args.grid,
189
+ cellsPerPluginBudget: parsed.args.cellsPerPluginBudget,
190
+ ...(parsed.args.blocks === undefined ? {} : { blocks: parsed.args.blocks }),
191
+ ...(memory === undefined ? {} : { memory }),
192
+ profiles: profiles.profiles,
193
+ });
183
194
  for (const f of pass.failures) process.stderr.write(`plugin-qualify: ${f.uri}: ${f.problem}\n`);
184
195
  if (parsed.args.out !== undefined) {
185
196
  for (const run of mergeRunsByRecord(pass.runs)) {
@@ -0,0 +1,88 @@
1
+ import { type QualificationRecord } from "./qualification-record.js";
2
+ import type { FileDigest, SyncItem, SyncPlugin } from "./qualify-sync.js";
3
+ /** Where a system's packages install the distributed records. */
4
+ export declare const DISTRIBUTED_DIR_SYSTEM = "/usr/share/omx/qualification";
5
+ /** The file of a set that says where its records come from. */
6
+ export declare const DISTRIBUTED_PROVENANCE_FILE = "provenance.json";
7
+ export declare const DISTRIBUTED_PROVENANCE_SCHEMA = "plugin-qualify.distributed-provenance/1";
8
+ /** The first plugin-qualify that reads the layer: what a package of records depends on. */
9
+ export declare const DISTRIBUTED_LAYER_SINCE = "0.17.0";
10
+ /** Where the records of a set come from. The keys below are always there; the rest is what the builder was given. */
11
+ export interface DistributedProvenance {
12
+ readonly schema: typeof DISTRIBUTED_PROVENANCE_SCHEMA;
13
+ readonly set: string;
14
+ readonly hostClass: string;
15
+ /** The package that installed the set. */
16
+ readonly package: {
17
+ readonly name: string;
18
+ readonly version: string;
19
+ };
20
+ /** The day the package was built (YYYY-MM-DD). */
21
+ readonly date: string;
22
+ readonly [key: string]: unknown;
23
+ }
24
+ /** The device tree's model file, where a board names itself. */
25
+ export declare const BOARD_MODEL_FILE = "/proc/device-tree/model";
26
+ /** The board's own model, or `undefined` on a machine without a device tree. */
27
+ export declare function readBoardModel(): string | undefined;
28
+ /** The host class of a board the layer knows by its device-tree model: `rpi5` for a Raspberry Pi 5 or its compute module. */
29
+ export declare function hostClassOfBoard(model: string | undefined): string | undefined;
30
+ /** One distributed record, read: where it is, what it says, and the set it came with. */
31
+ export interface DistributedRecord {
32
+ readonly path: string;
33
+ readonly record: QualificationRecord;
34
+ readonly set: string;
35
+ readonly provenance: DistributedProvenance;
36
+ }
37
+ /** The layer as one run reads it: the records of the host class, by what finds them. */
38
+ export interface DistributedLayer {
39
+ /** By the `.clap` path the record measured, made absolute. */
40
+ readonly clap: ReadonlyMap<string, readonly DistributedRecord[]>;
41
+ /** By the LV2 plugin's uri. */
42
+ readonly lv2: ReadonlyMap<string, readonly DistributedRecord[]>;
43
+ readonly sets: readonly string[];
44
+ readonly records: number;
45
+ }
46
+ export declare const EMPTY_LAYER: DistributedLayer;
47
+ /**
48
+ * The records of one host class under each directory, in the order given. A set without a provenance that reads, or
49
+ * whose provenance names another set or class, is not read; a record that does not read as a record of this schema is
50
+ * named and left out.
51
+ */
52
+ export declare function readDistributedLayer(dirs: readonly string[], hostClass: string, log?: (line: string) => void): DistributedLayer;
53
+ /** The uri a record's plugin has on this machine: the installed `.clap`'s path and the id, or the LV2 uri. */
54
+ export declare function distributedUri(item: SyncItem, r: DistributedRecord): string;
55
+ /** What the layer makes of one item. */
56
+ export type DistributedMatch =
57
+ /** No distributed record names the plugin: it is measured, and nothing is said. */
58
+ {
59
+ readonly status: "none";
60
+ }
61
+ /** Every reason a record found was not taken, one line each; the plugin is measured. */
62
+ | {
63
+ readonly status: "ignored";
64
+ readonly lines: readonly string[];
65
+ } | {
66
+ readonly status: "applies";
67
+ readonly set: string;
68
+ readonly records: readonly DistributedRecord[];
69
+ readonly files: readonly string[];
70
+ };
71
+ /**
72
+ * Whether the item is taken from the layer. The records that find it are tried one set at a time, in the order the
73
+ * layer read them. Of a set, only the records whose every file is installed with the SHA-256 it recorded (computed
74
+ * here by `digest`) count: a record of other bytes is another build of the plugin and is passed over. The set applies
75
+ * when at least one record of these bytes holds a verdict for `host`, and none of those verdicts is one the source
76
+ * could not measure; a set with no record of these bytes is a binary that differs.
77
+ */
78
+ export declare function matchDistributed(layer: DistributedLayer, item: SyncItem, host: string, digest: (files: readonly string[]) => readonly FileDigest[] | undefined): DistributedMatch;
79
+ /**
80
+ * The records of a match copied into `qualificationDir` and the plugins they make: the probe output first, byte for
81
+ * byte and read back through this version's reader, its SHA-256 the one the record names; then the record with the
82
+ * set's provenance added. A record whose probe output is missing, altered or unreadable throws, naming it, and the
83
+ * caller measures the plugin instead. Records of the other tier (no verdict for `host`) are copied and make no plugin,
84
+ * as the sweep writes them.
85
+ */
86
+ export declare function applyDistributed(qualificationDir: string, item: SyncItem, match: Extract<DistributedMatch, {
87
+ status: "applies";
88
+ }>, host: string): SyncPlugin[];
@@ -0,0 +1,210 @@
1
+ // SPDX-License-Identifier: GPL-3.0-or-later
2
+ // Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
3
+ /**
4
+ * The distributed layer: qualification records another device of the same machine class wrote for a plugin set,
5
+ * installed read-only under `<dir>/<host class>/<set>/` beside the set's `provenance.json`. plugin-qualify-sync reads
6
+ * it before measuring a plugin, and takes a plugin from it instead of measuring it only when every file the record
7
+ * names hashes, here, as it did where it was measured. The name of a plugin finds its record and never decides
8
+ * anything: a record is never trusted by name alone.
9
+ *
10
+ * What applies is copied into the device's own `qualification_dir`, the record with the set's provenance added and
11
+ * its probe output byte for byte, and the plugin's pin is the one that probe output holds. From then on the sync
12
+ * treats the plugin as one it measured.
13
+ */
14
+ import { readFileSync, readdirSync } from "node:fs";
15
+ import { join, resolve } from "node:path";
16
+ import { clapPinUri, pinVerdictOf } from "./pins-file.js";
17
+ import { integrityOfProbeOutput } from "./probe-judge.js";
18
+ import { readProbeOutput, sha256Hex } from "./probe-output.js";
19
+ import { QUALIFICATION_RECORD_SCHEMA } from "./qualification-record.js";
20
+ import { writeFileWhole } from "./whole-file.js";
21
+ /** Where a system's packages install the distributed records. */
22
+ export const DISTRIBUTED_DIR_SYSTEM = "/usr/share/omx/qualification";
23
+ /** The file of a set that says where its records come from. */
24
+ export const DISTRIBUTED_PROVENANCE_FILE = "provenance.json";
25
+ export const DISTRIBUTED_PROVENANCE_SCHEMA = "plugin-qualify.distributed-provenance/1";
26
+ /** The first plugin-qualify that reads the layer: what a package of records depends on. */
27
+ export const DISTRIBUTED_LAYER_SINCE = "0.17.0";
28
+ /** The device tree's model file, where a board names itself. */
29
+ export const BOARD_MODEL_FILE = "/proc/device-tree/model";
30
+ /** The board's own model, or `undefined` on a machine without a device tree. */
31
+ export function readBoardModel() {
32
+ try {
33
+ return readFileSync(BOARD_MODEL_FILE, "utf8");
34
+ }
35
+ catch {
36
+ return undefined;
37
+ }
38
+ }
39
+ /** The host class of a board the layer knows by its device-tree model: `rpi5` for a Raspberry Pi 5 or its compute module. */
40
+ export function hostClassOfBoard(model) {
41
+ if (model === undefined)
42
+ return undefined;
43
+ const m = /^Raspberry Pi (?:Compute Module )?(\d)(?:\s|$)/.exec(model.replace(/\0/g, "").trim());
44
+ return m === null ? undefined : `rpi${m[1]}`;
45
+ }
46
+ export const EMPTY_LAYER = { clap: new Map(), lv2: new Map(), sets: [], records: 0 };
47
+ const RECORD_SUFFIX = ".qualification.json";
48
+ const names = (dir) => {
49
+ try {
50
+ return readdirSync(dir).sort();
51
+ }
52
+ catch {
53
+ return [];
54
+ }
55
+ };
56
+ /**
57
+ * The records of one host class under each directory, in the order given. A set without a provenance that reads, or
58
+ * whose provenance names another set or class, is not read; a record that does not read as a record of this schema is
59
+ * named and left out.
60
+ */
61
+ export function readDistributedLayer(dirs, hostClass, log = () => { }) {
62
+ const clap = new Map();
63
+ const lv2 = new Map();
64
+ const sets = [];
65
+ let records = 0;
66
+ const add = (map, key, r) => {
67
+ map.set(key, [...(map.get(key) ?? []), r]);
68
+ };
69
+ for (const base of dirs) {
70
+ const classDir = join(base, hostClass);
71
+ for (const set of names(classDir)) {
72
+ const dir = join(classDir, set);
73
+ let provenance;
74
+ try {
75
+ provenance = JSON.parse(readFileSync(join(dir, DISTRIBUTED_PROVENANCE_FILE), "utf8"));
76
+ }
77
+ catch (e) {
78
+ if (e.code === "ENOTDIR")
79
+ continue;
80
+ log(`distributed: ${dir}: ${e.code === "ENOENT" ? `no ${DISTRIBUTED_PROVENANCE_FILE}` : `${DISTRIBUTED_PROVENANCE_FILE} does not read`}, not read`);
81
+ continue;
82
+ }
83
+ if (provenance.schema !== DISTRIBUTED_PROVENANCE_SCHEMA || provenance.set !== set || provenance.hostClass !== hostClass) {
84
+ log(`distributed: ${dir}: its ${DISTRIBUTED_PROVENANCE_FILE} is not of set ${set} and host class ${hostClass} (${DISTRIBUTED_PROVENANCE_SCHEMA}), not read`);
85
+ continue;
86
+ }
87
+ sets.push(set);
88
+ for (const name of names(dir)) {
89
+ if (!name.endsWith(RECORD_SUFFIX))
90
+ continue;
91
+ const path = join(dir, name);
92
+ let record;
93
+ try {
94
+ record = JSON.parse(readFileSync(path, "utf8"));
95
+ }
96
+ catch {
97
+ log(`distributed: ${path}: does not read, left out`);
98
+ continue;
99
+ }
100
+ if (record.schema !== QUALIFICATION_RECORD_SCHEMA || typeof record.binary?.path !== "string" || !Array.isArray(record.binary.files) || record.binary.files.length === 0) {
101
+ log(`distributed: ${path}: not a ${QUALIFICATION_RECORD_SCHEMA} with its binary's files, left out`);
102
+ continue;
103
+ }
104
+ const r = { path, record, set, provenance };
105
+ records++;
106
+ if (record.plugin.format === "clap")
107
+ add(clap, resolve(record.binary.path), r);
108
+ else if (record.plugin.format === "lv2")
109
+ add(lv2, record.plugin.id, r);
110
+ }
111
+ }
112
+ }
113
+ return { clap, lv2, sets, records };
114
+ }
115
+ /** The uri a record's plugin has on this machine: the installed `.clap`'s path and the id, or the LV2 uri. */
116
+ export function distributedUri(item, r) {
117
+ return item.format === "clap" ? clapPinUri(join(item.root, item.files[0]), `urn:clap:${r.record.plugin.id}`) : r.record.plugin.id;
118
+ }
119
+ /** What the sync's own judgment would make of the record's verdict for the host, as `outcomeOf` reads a run. */
120
+ function outcomeOfVerdict(v) {
121
+ if (!v.measured)
122
+ return v.deciding === "probe.crashed" ? "crashed" : v.deciding === "probe.timeout" ? "hung" : "unmeasured";
123
+ if (v.rating === "suitable" || v.rating === "conditional")
124
+ return "qualified";
125
+ return v.rating === "unsuitable" ? "refused" : "unmeasured";
126
+ }
127
+ /**
128
+ * Whether the item is taken from the layer. The records that find it are tried one set at a time, in the order the
129
+ * layer read them. Of a set, only the records whose every file is installed with the SHA-256 it recorded (computed
130
+ * here by `digest`) count: a record of other bytes is another build of the plugin and is passed over. The set applies
131
+ * when at least one record of these bytes holds a verdict for `host`, and none of those verdicts is one the source
132
+ * could not measure; a set with no record of these bytes is a binary that differs.
133
+ */
134
+ export function matchDistributed(layer, item, host, digest) {
135
+ const found = item.format === "clap" ? layer.clap.get(resolve(join(item.root, item.files[0]))) : layer.lv2.get(item.uri ?? "");
136
+ if (found === undefined || found.length === 0)
137
+ return { status: "none" };
138
+ const lines = [];
139
+ const say = (line) => {
140
+ if (!lines.includes(line))
141
+ lines.push(line);
142
+ };
143
+ for (const set of [...new Set(found.map((r) => r.set))]) {
144
+ // a set may hold records of earlier bytes of the plugin beside the ones of these: only the ones of these bytes count
145
+ const records = found.filter((r) => {
146
+ if (r.set !== set || r.record.plugin.format !== item.format)
147
+ return false;
148
+ const wanted = r.record.binary.files;
149
+ const now = digest(wanted.map((f) => f.path));
150
+ return now !== undefined && wanted.every((f) => now.find((d) => d.path === f.path)?.sha256 === f.sha256);
151
+ });
152
+ if (records.length === 0) {
153
+ say(`distributed: ${distributedUri(item, found.find((r) => r.set === set))}: binary differs`);
154
+ continue;
155
+ }
156
+ const judged = records.filter((r) => r.record.verdicts.some((v) => v.host === host));
157
+ if (judged.length === 0) {
158
+ say(`distributed: ${distributedUri(item, records[0])}: no verdict for ${host} in ${set}`);
159
+ continue;
160
+ }
161
+ const unmeasured = judged.find((r) => outcomeOfVerdict(r.record.verdicts.find((v) => v.host === host)) === "unmeasured");
162
+ if (unmeasured !== undefined) {
163
+ say(`distributed: ${distributedUri(item, unmeasured)}: unmeasured in ${set}, measured here`);
164
+ continue;
165
+ }
166
+ return { status: "applies", set, records, files: [...new Set(records.flatMap((r) => r.record.binary.files.map((f) => f.path)))] };
167
+ }
168
+ return { status: "ignored", lines };
169
+ }
170
+ /**
171
+ * The records of a match copied into `qualificationDir` and the plugins they make: the probe output first, byte for
172
+ * byte and read back through this version's reader, its SHA-256 the one the record names; then the record with the
173
+ * set's provenance added. A record whose probe output is missing, altered or unreadable throws, naming it, and the
174
+ * caller measures the plugin instead. Records of the other tier (no verdict for `host`) are copied and make no plugin,
175
+ * as the sweep writes them.
176
+ */
177
+ export function applyDistributed(qualificationDir, item, match, host) {
178
+ const copies = match.records.map((r) => {
179
+ const probeName = r.record.probe.file;
180
+ const probePath = join(r.path, "..", probeName);
181
+ let bytes;
182
+ try {
183
+ bytes = readFileSync(probePath);
184
+ }
185
+ catch (e) {
186
+ throw new Error(`${probeName}: ${e.code ?? String(e)}`);
187
+ }
188
+ if (sha256Hex(bytes) !== r.record.probe.sha256)
189
+ throw new Error(`${probeName}: not the probe output the record names (its SHA-256 differs)`);
190
+ const read = readProbeOutput(bytes.toString("utf8"));
191
+ if (!read.ok)
192
+ throw new Error(`${probeName}: ${read.code}: ${read.problem}`);
193
+ return { r, bytes, output: read.output };
194
+ });
195
+ const plugins = [];
196
+ for (const { r, bytes, output } of copies) {
197
+ writeFileWhole(join(qualificationDir, r.record.probe.file), bytes);
198
+ const recordPath = join(qualificationDir, r.path.slice(r.path.lastIndexOf("/") + 1));
199
+ writeFileWhole(recordPath, `${JSON.stringify({ ...r.record, provenance: r.provenance }, null, 2)}\n`);
200
+ const v = r.record.verdicts.find((x) => x.host === host);
201
+ if (v === undefined)
202
+ continue;
203
+ const word = v.measured ? pinVerdictOf(v.rating) : undefined;
204
+ const pin = { uri: distributedUri(item, r), integrity: integrityOfProbeOutput(output), ...(word === undefined ? {} : { verdict: word }) };
205
+ const outcome = outcomeOfVerdict(v);
206
+ plugins.push({ uri: pin.uri, outcome, ...(v.deciding === undefined ? {} : { code: v.deciding }), pin, record: recordPath });
207
+ }
208
+ return plugins;
209
+ }
210
+ //# sourceMappingURL=distributed-records.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"distributed-records.js","sourceRoot":"","sources":["../src/distributed-records.ts"],"names":[],"mappings":"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"}
@@ -7,6 +7,11 @@ import type { HostProfile } from "./measurement/format.js";
7
7
  * the console hands the same array to the in-process LV2 table at `omx_host_backend_configure`,
8
8
  * which builds one provider per URI and refuses a URI it cannot provide; there is no C copy (the
9
9
  * console's native test reads this export).
10
+ *
11
+ * Equal to omx-clap-core's `LV2_CORE_PROVIDED_INIT` (`lv2/core/lv2_core.h`), same URIs in the same
12
+ * order: the original seven, then the three rtmempool URIs (the header's `LV2_RTSAFE_MEMORY_POOL__Pool`,
13
+ * the `#Pool` spelling some TTLs require, and the deprecated v1 URI), the three state-path features,
14
+ * then uri-map and event (omx-clap-core #94).
10
15
  */
11
16
  export declare const OMX_INPROCESS_FEATURES: readonly string[];
12
17
  /**