@openmixer/plugin-qualify 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -9
- package/bin/plugin-qualify.mjs +62 -29
- package/dist/clap-loader.d.ts +1 -1
- package/dist/clap-loader.js +3 -3
- package/dist/clap-loader.js.map +1 -1
- package/dist/clap-qualify.d.ts +21 -45
- package/dist/clap-qualify.js +60 -82
- package/dist/clap-qualify.js.map +1 -1
- package/dist/cost-fields.d.ts +26 -0
- package/dist/cost-fields.js +30 -0
- package/dist/cost-fields.js.map +1 -1
- package/dist/host-profiles.d.ts +14 -0
- package/dist/host-profiles.js +27 -1
- package/dist/host-profiles.js.map +1 -1
- package/dist/hosting-codes.d.ts +6 -2
- package/dist/hosting-codes.js +9 -4
- package/dist/hosting-codes.js.map +1 -1
- package/dist/hosting-suitability.d.ts +43 -4
- package/dist/hosting-suitability.js +46 -8
- package/dist/hosting-suitability.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/judge-cli.d.ts +41 -0
- package/dist/judge-cli.js +114 -0
- package/dist/judge-cli.js.map +1 -0
- package/dist/measure-host.js +2 -2
- package/dist/measurement/format.d.ts +1 -1
- package/dist/measurement/probe-json.js +1 -0
- package/dist/measurement/probe-json.js.map +1 -1
- package/dist/measurement/vocabulary.d.ts +1 -1
- package/dist/measurement/vocabulary.js +7 -1
- package/dist/measurement/vocabulary.js.map +1 -1
- package/dist/probe-judge.d.ts +60 -0
- package/dist/probe-judge.js +213 -0
- package/dist/probe-judge.js.map +1 -0
- package/dist/probe-output-fixtures.d.ts +25 -0
- package/dist/probe-output-fixtures.js +134 -0
- package/dist/probe-output-fixtures.js.map +1 -0
- package/dist/probe-output.d.ts +150 -0
- package/dist/probe-output.js +369 -0
- package/dist/probe-output.js.map +1 -0
- package/dist/probe-runner.d.ts +79 -0
- package/dist/probe-runner.js +191 -0
- package/dist/probe-runner.js.map +1 -0
- package/dist/qualification-record.d.ts +80 -0
- package/dist/qualification-record.js +85 -0
- package/dist/qualification-record.js.map +1 -0
- package/dist/qualify-cli.js +1 -1
- package/dist/qualify-cli.js.map +1 -1
- package/dist/qualify-declarations.d.ts +26 -6
- package/dist/qualify-declarations.js +25 -7
- package/dist/qualify-declarations.js.map +1 -1
- package/fixtures/clap-scan/fake.json +30 -6
- package/fixtures/probe-output/memory-limit-refused.json +190 -0
- package/fixtures/probe-output/plugin-crash.json +236 -0
- package/fixtures/probe-output/success-msi.json +363 -0
- package/package.json +7 -2
- package/src/clap-loader.ts +3 -2
- package/src/clap-qualify.ts +70 -124
- package/src/cost-fields.ts +39 -0
- package/src/host-profiles.ts +29 -1
- package/src/hosting-codes.ts +9 -4
- package/src/hosting-suitability.ts +82 -11
- package/src/index.ts +5 -0
- package/src/judge-cli.ts +133 -0
- package/src/measure-host.ts +2 -2
- package/src/measurement/format.ts +1 -1
- package/src/measurement/probe-json.ts +1 -0
- package/src/measurement/vocabulary.ts +8 -1
- package/src/probe-judge.ts +264 -0
- package/src/probe-output-fixtures.ts +146 -0
- package/src/probe-output.ts +505 -0
- package/src/probe-runner.ts +244 -0
- package/src/qualification-record.ts +142 -0
- package/src/qualify-cli.ts +1 -1
- package/src/qualify-declarations.ts +28 -8
- package/tools/ai-trace-commit-check.sh +114 -0
- package/tools/ai-trace-patterns.json +33 -0
- package/tools/benchmark.py +13 -1
- package/tools/private-refs-check.sh +90 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
{
|
|
2
|
+
"format": "plugin-hostd.probe-output",
|
|
3
|
+
"formatVersion": "1.0",
|
|
4
|
+
"run": {
|
|
5
|
+
"id": "msi-20261017T221500Z-3569027",
|
|
6
|
+
"startedAt": "2026-10-17T22:15:00+02:00",
|
|
7
|
+
"elapsedMs": 184220,
|
|
8
|
+
"complete": true,
|
|
9
|
+
"outcome": "probe.ok",
|
|
10
|
+
"reason": "reference",
|
|
11
|
+
"protocol": {
|
|
12
|
+
"name": "plugin-hostd.probe-protocol",
|
|
13
|
+
"version": 1
|
|
14
|
+
},
|
|
15
|
+
"tools": {
|
|
16
|
+
"runner": "plugin-hostd-probe 0.1.0",
|
|
17
|
+
"child": "plugin-hostd-probe-clap 0.1.0",
|
|
18
|
+
"clapCore": "0.9.0",
|
|
19
|
+
"interposer": "rt-interpose 1"
|
|
20
|
+
},
|
|
21
|
+
"via": "runner"
|
|
22
|
+
},
|
|
23
|
+
"plugin": {
|
|
24
|
+
"format": "clap",
|
|
25
|
+
"id": "org.example.plate",
|
|
26
|
+
"name": "Plate",
|
|
27
|
+
"vendor": "Example",
|
|
28
|
+
"version": "1.4.2",
|
|
29
|
+
"ownerPackage": "example-plate-1.4.2-1.fc44.x86_64"
|
|
30
|
+
},
|
|
31
|
+
"binary": {
|
|
32
|
+
"path": "/usr/lib64/clap/example-plate.clap",
|
|
33
|
+
"sha256": "356902794a4eec2122989e072271ef995968131145db918d9a3084ce582f244b",
|
|
34
|
+
"files": [
|
|
35
|
+
{
|
|
36
|
+
"path": "example-plate.clap",
|
|
37
|
+
"sha256": "356902794a4eec2122989e072271ef995968131145db918d9a3084ce582f244b"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"layout": {
|
|
41
|
+
"scheme": "omx-layout/1",
|
|
42
|
+
"sha256": "0a3e01399262321514e05af0575ebb0b207a35567ac262a060fde655ee069805"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"descriptor": {
|
|
46
|
+
"path": "descriptors/2/clap/org.example.plate@1.4.2-356902794a4e.json",
|
|
47
|
+
"sha256": "cc312bdd5a9aa62796ebcb5a648c354c5ee45a6a99892425495ccb58a31bc44e",
|
|
48
|
+
"bytes": 9412
|
|
49
|
+
},
|
|
50
|
+
"machine": {
|
|
51
|
+
"class": {
|
|
52
|
+
"arch": "x86_64",
|
|
53
|
+
"family": "intel",
|
|
54
|
+
"coreBand": "17-32",
|
|
55
|
+
"clockBand": "4.5GHz+",
|
|
56
|
+
"preemptRt": false,
|
|
57
|
+
"cstatesLimited": true
|
|
58
|
+
},
|
|
59
|
+
"profile": {
|
|
60
|
+
"governor": "performance",
|
|
61
|
+
"turbo": true,
|
|
62
|
+
"smt": true,
|
|
63
|
+
"preempt": "PREEMPT_DYNAMIC",
|
|
64
|
+
"cpuidle": [
|
|
65
|
+
{
|
|
66
|
+
"cpu": 22,
|
|
67
|
+
"state": "POLL",
|
|
68
|
+
"latencyUs": 0,
|
|
69
|
+
"disabled": false,
|
|
70
|
+
"usageDelta": 118
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"cpu": 22,
|
|
74
|
+
"state": "C1_ACPI",
|
|
75
|
+
"latencyUs": 1,
|
|
76
|
+
"disabled": false,
|
|
77
|
+
"usageDelta": 40210
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"cpu": 22,
|
|
81
|
+
"state": "C2_ACPI",
|
|
82
|
+
"latencyUs": 127,
|
|
83
|
+
"disabled": true,
|
|
84
|
+
"usageDelta": 0
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"cpu": 22,
|
|
88
|
+
"state": "C3_ACPI",
|
|
89
|
+
"latencyUs": 1048,
|
|
90
|
+
"disabled": true,
|
|
91
|
+
"usageDelta": 0
|
|
92
|
+
}
|
|
93
|
+
],
|
|
94
|
+
"meetsProtocol": true,
|
|
95
|
+
"declaredName": "omx-live-rig"
|
|
96
|
+
},
|
|
97
|
+
"pinnedCpus": "22",
|
|
98
|
+
"reference": {
|
|
99
|
+
"rig": "msi",
|
|
100
|
+
"image": "omx-ref-2026-10"
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
"offer": {
|
|
104
|
+
"id": "omx-clap-host",
|
|
105
|
+
"clapExtensions": [
|
|
106
|
+
"clap.audio-ports",
|
|
107
|
+
"clap.latency",
|
|
108
|
+
"clap.log",
|
|
109
|
+
"clap.params",
|
|
110
|
+
"clap.state",
|
|
111
|
+
"clap.thread-check",
|
|
112
|
+
"clap.preset-load/2",
|
|
113
|
+
"clap.track-info/1",
|
|
114
|
+
"clap.remote-controls/2"
|
|
115
|
+
],
|
|
116
|
+
"realtime": "none"
|
|
117
|
+
},
|
|
118
|
+
"limits": {
|
|
119
|
+
"set": "default",
|
|
120
|
+
"applied": [
|
|
121
|
+
{
|
|
122
|
+
"limit": "wall-clock",
|
|
123
|
+
"value": 600,
|
|
124
|
+
"unit": "s",
|
|
125
|
+
"by": "supervisor"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"limit": "cpu-time",
|
|
129
|
+
"value": 540,
|
|
130
|
+
"unit": "s",
|
|
131
|
+
"by": "rlimit"
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"limit": "memory",
|
|
135
|
+
"value": 2147483648,
|
|
136
|
+
"unit": "bytes",
|
|
137
|
+
"by": "cgroup"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"limit": "tasks",
|
|
141
|
+
"value": 64,
|
|
142
|
+
"unit": "count",
|
|
143
|
+
"by": "cgroup"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"limit": "files",
|
|
147
|
+
"value": 256,
|
|
148
|
+
"unit": "count",
|
|
149
|
+
"by": "rlimit"
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"limit": "file-size",
|
|
153
|
+
"value": 67108864,
|
|
154
|
+
"unit": "bytes",
|
|
155
|
+
"by": "rlimit"
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"limit": "core-dump",
|
|
159
|
+
"value": 0,
|
|
160
|
+
"unit": "bytes",
|
|
161
|
+
"by": "rlimit"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"limit": "rt-priority",
|
|
165
|
+
"value": 0,
|
|
166
|
+
"by": "rlimit"
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"limit": "cpus",
|
|
170
|
+
"value": "22",
|
|
171
|
+
"by": "affinity"
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"limit": "no-new-privs",
|
|
175
|
+
"by": "prctl"
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"limit": "network",
|
|
179
|
+
"value": "none",
|
|
180
|
+
"by": "netns"
|
|
181
|
+
}
|
|
182
|
+
],
|
|
183
|
+
"unavailable": []
|
|
184
|
+
},
|
|
185
|
+
"steps": [
|
|
186
|
+
{
|
|
187
|
+
"step": "hash",
|
|
188
|
+
"outcome": "probe.ok",
|
|
189
|
+
"elapsedMs": 4
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"step": "instantiate",
|
|
193
|
+
"outcome": "probe.ok",
|
|
194
|
+
"elapsedMs": 31
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"step": "lifecycle",
|
|
198
|
+
"outcome": "probe.ok",
|
|
199
|
+
"elapsedMs": 2210
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"step": "latency",
|
|
203
|
+
"rate": 48000,
|
|
204
|
+
"quantum": 128,
|
|
205
|
+
"outcome": "probe.ok",
|
|
206
|
+
"elapsedMs": 310
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"step": "cost",
|
|
210
|
+
"rate": 48000,
|
|
211
|
+
"quantum": 128,
|
|
212
|
+
"outcome": "probe.ok",
|
|
213
|
+
"elapsedMs": 30020
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"step": "rt-safety",
|
|
217
|
+
"rate": 48000,
|
|
218
|
+
"quantum": 128,
|
|
219
|
+
"outcome": "probe.ok",
|
|
220
|
+
"elapsedMs": 4100
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"step": "latency",
|
|
224
|
+
"rate": 96000,
|
|
225
|
+
"quantum": 128,
|
|
226
|
+
"outcome": "probe.ok",
|
|
227
|
+
"elapsedMs": 305
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
"step": "cost",
|
|
231
|
+
"rate": 96000,
|
|
232
|
+
"quantum": 128,
|
|
233
|
+
"outcome": "probe.ok",
|
|
234
|
+
"elapsedMs": 30011
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"step": "rt-safety",
|
|
238
|
+
"rate": 96000,
|
|
239
|
+
"quantum": 128,
|
|
240
|
+
"outcome": "probe.ok",
|
|
241
|
+
"elapsedMs": 4080
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
"step": "note-sweep",
|
|
245
|
+
"outcome": "probe.not-attempted",
|
|
246
|
+
"elapsedMs": 0,
|
|
247
|
+
"detail": "no note input"
|
|
248
|
+
}
|
|
249
|
+
],
|
|
250
|
+
"grid": [
|
|
251
|
+
{
|
|
252
|
+
"rate": 48000,
|
|
253
|
+
"quantum": 128,
|
|
254
|
+
"outcome": "probe.ok",
|
|
255
|
+
"latency": {
|
|
256
|
+
"declaredFrames": 0,
|
|
257
|
+
"measuredFrames": 0,
|
|
258
|
+
"method": "impulse-onset"
|
|
259
|
+
},
|
|
260
|
+
"cost": {
|
|
261
|
+
"unit": "ns-per-sample",
|
|
262
|
+
"median": 41.2,
|
|
263
|
+
"percentile": {
|
|
264
|
+
"p": 95,
|
|
265
|
+
"value": 47.9
|
|
266
|
+
},
|
|
267
|
+
"min": 39.8,
|
|
268
|
+
"max": 112.4,
|
|
269
|
+
"iqr": 3.1,
|
|
270
|
+
"blocks": 11250,
|
|
271
|
+
"warmupBlocks": 64,
|
|
272
|
+
"warmupMedian": 58.0,
|
|
273
|
+
"timingFloorNsPerBlock": 180
|
|
274
|
+
},
|
|
275
|
+
"rtSafety": {
|
|
276
|
+
"interposer": true,
|
|
277
|
+
"swept": true,
|
|
278
|
+
"sweepApplicable": true,
|
|
279
|
+
"blocks": 512,
|
|
280
|
+
"allocations": 0,
|
|
281
|
+
"locks": 0,
|
|
282
|
+
"syscalls": 0,
|
|
283
|
+
"threadViolations": 0,
|
|
284
|
+
"processErrors": 0,
|
|
285
|
+
"events": []
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
"rate": 96000,
|
|
290
|
+
"quantum": 128,
|
|
291
|
+
"outcome": "probe.ok",
|
|
292
|
+
"latency": {
|
|
293
|
+
"declaredFrames": 0,
|
|
294
|
+
"measuredFrames": 0,
|
|
295
|
+
"method": "impulse-onset"
|
|
296
|
+
},
|
|
297
|
+
"cost": {
|
|
298
|
+
"unit": "ns-per-sample",
|
|
299
|
+
"median": 40.7,
|
|
300
|
+
"percentile": {
|
|
301
|
+
"p": 95,
|
|
302
|
+
"value": 46.3
|
|
303
|
+
},
|
|
304
|
+
"min": 39.1,
|
|
305
|
+
"max": 98.0,
|
|
306
|
+
"iqr": 2.8,
|
|
307
|
+
"blocks": 22500,
|
|
308
|
+
"warmupBlocks": 64,
|
|
309
|
+
"timingFloorNsPerBlock": 180,
|
|
310
|
+
"samples": {
|
|
311
|
+
"name": "cost-96000-128",
|
|
312
|
+
"path": "probes/org.example.plate@1.4.2-356902794a4e.cost-96000-128.bin",
|
|
313
|
+
"sha256": "5f649130dafd22f1edc70441f9455415d0856f0f7d26cf7274463b82ac2c5813",
|
|
314
|
+
"bytes": 180000
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
"rtSafety": {
|
|
318
|
+
"interposer": true,
|
|
319
|
+
"swept": true,
|
|
320
|
+
"sweepApplicable": true,
|
|
321
|
+
"blocks": 512,
|
|
322
|
+
"allocations": 0,
|
|
323
|
+
"locks": 0,
|
|
324
|
+
"syscalls": 0,
|
|
325
|
+
"threadViolations": 0,
|
|
326
|
+
"processErrors": 0,
|
|
327
|
+
"events": []
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
],
|
|
331
|
+
"behaviour": {
|
|
332
|
+
"lifecycle": {
|
|
333
|
+
"cycles": 30,
|
|
334
|
+
"instantiated": 30,
|
|
335
|
+
"failed": 0
|
|
336
|
+
},
|
|
337
|
+
"threads": {
|
|
338
|
+
"before": 2,
|
|
339
|
+
"afterFirstInstantiate": 2,
|
|
340
|
+
"afterAllFreed": 2
|
|
341
|
+
},
|
|
342
|
+
"memory": {
|
|
343
|
+
"rssStartKb": 9120,
|
|
344
|
+
"rssMaxKb": 14988
|
|
345
|
+
},
|
|
346
|
+
"features": {
|
|
347
|
+
"extensionsAsked": [
|
|
348
|
+
"clap.audio-ports",
|
|
349
|
+
"clap.params",
|
|
350
|
+
"clap.state",
|
|
351
|
+
"clap.latency"
|
|
352
|
+
]
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
"logs": [
|
|
356
|
+
{
|
|
357
|
+
"name": "stderr",
|
|
358
|
+
"path": "probes/org.example.plate@1.4.2-356902794a4e.stderr.log",
|
|
359
|
+
"sha256": "c46928bc87de23b9a8bd235aa1fe67ccb1542c8389ad3f02bf4ab5cff506e98f",
|
|
360
|
+
"bytes": 212
|
|
361
|
+
}
|
|
362
|
+
]
|
|
363
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openmixer/plugin-qualify",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Qualify audio plugins for real-time hosting: scan, offline latency and cost measurement, a mod-host hosting sweep, and a fail-closed rating.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "GPL-3.0-or-later",
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
"dist",
|
|
68
68
|
"src",
|
|
69
69
|
"!src/**/*.test.ts",
|
|
70
|
+
"!src/**/*.support.*",
|
|
70
71
|
"tools",
|
|
71
72
|
"!tools/test_*.py",
|
|
72
73
|
"!**/__pycache__",
|
|
@@ -81,7 +82,8 @@
|
|
|
81
82
|
"test": "vitest run src",
|
|
82
83
|
"lint": "tsc -p tsconfig.lint.json",
|
|
83
84
|
"test:py": "python3 -m pytest tools -q",
|
|
84
|
-
"check:generated": "node tools/gen-python-declarations.mjs --check"
|
|
85
|
+
"check:generated": "node tools/gen-python-declarations.mjs --check",
|
|
86
|
+
"check:private-refs": "tools/private-refs-check.sh && tools/private-refs-check.sh --self-test"
|
|
85
87
|
},
|
|
86
88
|
"devDependencies": {
|
|
87
89
|
"@types/node": "^24.9.0",
|
|
@@ -92,5 +94,8 @@
|
|
|
92
94
|
"publishConfig": {
|
|
93
95
|
"access": "public",
|
|
94
96
|
"provenance": true
|
|
97
|
+
},
|
|
98
|
+
"dependencies": {
|
|
99
|
+
"@openmixer/omx-contract": "^2.16.0"
|
|
95
100
|
}
|
|
96
101
|
}
|
package/src/clap-loader.ts
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
CLAP_SCAN_TOOL_DEFAULT,
|
|
26
26
|
CLAP_SCAN_TOOL_ENV,
|
|
27
27
|
CLAP_SYSTEM_DIR,
|
|
28
|
+
CLAP_SYSTEM_DIR_ENV,
|
|
28
29
|
CLAP_USER_DIR,
|
|
29
30
|
} from "./qualify-declarations.js";
|
|
30
31
|
|
|
@@ -428,11 +429,11 @@ export function clapLoader(options: ClapLoaderOptions = {}): PluginLoader {
|
|
|
428
429
|
|
|
429
430
|
/**
|
|
430
431
|
* The directories `--all` walks, in order and once each: every non-empty entry of `CLAP_PATH`, then
|
|
431
|
-
* `~/.clap`, then `/usr/lib/clap
|
|
432
|
+
* `~/.clap`, then `/usr/lib/clap` (or the directory `OMX_CLAP_SYSTEM_DIR` names).
|
|
432
433
|
*/
|
|
433
434
|
export function clapSearchPath(env: Readonly<Record<string, string | undefined>>, home: string): string[] {
|
|
434
435
|
const named = (env[CLAP_PATH_ENV] ?? "").split(":").filter((d) => d !== "");
|
|
435
|
-
return [...new Set([...named, join(home, CLAP_USER_DIR), CLAP_SYSTEM_DIR].map((d) => resolve(d)))];
|
|
436
|
+
return [...new Set([...named, join(home, CLAP_USER_DIR), env[CLAP_SYSTEM_DIR_ENV] || CLAP_SYSTEM_DIR].map((d) => resolve(d)))];
|
|
436
437
|
}
|
|
437
438
|
|
|
438
439
|
export interface ClapDiscoverOptions extends ClapLoaderOptions {
|
package/src/clap-qualify.ts
CHANGED
|
@@ -10,27 +10,26 @@
|
|
|
10
10
|
* read, a prober that is absent, crashed or timed out, is a {@link ClapQualifyError}: nothing was
|
|
11
11
|
* measured, and nothing is rated.
|
|
12
12
|
*/
|
|
13
|
-
import { spawnSync } from "node:child_process";
|
|
14
13
|
import { existsSync } from "node:fs";
|
|
15
14
|
|
|
16
|
-
import {
|
|
17
|
-
CLAP_QUALIFY_TIMEOUT_S,
|
|
18
|
-
CLAP_QUALIFY_TOOL_NAME,
|
|
19
|
-
CLAP_SWEEP_BLOCKS,
|
|
20
|
-
CLAP_SWEEP_FRAMES,
|
|
21
|
-
RT_INTERPOSE_NAME,
|
|
22
|
-
} from "./qualify-declarations.js";
|
|
15
|
+
import { CLAP_QUALIFY_TOOL_NAME, RT_INTERPOSE_NAME } from "./qualify-declarations.js";
|
|
23
16
|
import {
|
|
24
17
|
HOSTING_CODE,
|
|
25
18
|
clapRtReasons,
|
|
26
19
|
contributedHostingRating,
|
|
20
|
+
noteSweepReasons,
|
|
27
21
|
rtSafetyReasons,
|
|
28
22
|
type HostingReason,
|
|
29
23
|
type PluginClapMeasurement,
|
|
24
|
+
type PluginNoteSweepMeasurement,
|
|
30
25
|
type PluginRtSafetyMeasurement,
|
|
31
26
|
} from "./hosting-suitability.js";
|
|
32
|
-
import {
|
|
33
|
-
|
|
27
|
+
import {
|
|
28
|
+
OMX_CLAP_HOST_CONFIGURED_EXTENSIONS,
|
|
29
|
+
OMX_CLAP_HOST_EXTENSIONS,
|
|
30
|
+
OPENMIXER_CONSOLE_PROFILE,
|
|
31
|
+
type HostProfile,
|
|
32
|
+
} from "./host-profiles.js";
|
|
34
33
|
import type { QualifyVerdict } from "./qualify-cli.js";
|
|
35
34
|
import { RATING_SEVERITY, worstRating, type SuitabilityRating } from "./rating.js";
|
|
36
35
|
import { halfPopulatedRefusal, locateNativeTools } from "./tool-paths.js";
|
|
@@ -51,6 +50,12 @@ export interface ClapRtSafetyReading extends PluginRtSafetyMeasurement {
|
|
|
51
50
|
readonly events?: readonly string[];
|
|
52
51
|
}
|
|
53
52
|
|
|
53
|
+
/** What the note sweep counted; `applicable: false` is a plugin the prober did not play. */
|
|
54
|
+
export interface ClapNoteSweepReading extends PluginNoteSweepMeasurement {
|
|
55
|
+
/** The first events the interposer recorded inside `process()` while the plugin was played. */
|
|
56
|
+
readonly events?: readonly string[];
|
|
57
|
+
}
|
|
58
|
+
|
|
54
59
|
/** The CLAP facts as the prober printed them: the measurement, and whether any output was not finite. */
|
|
55
60
|
export interface ClapProbedFacts extends PluginClapMeasurement {
|
|
56
61
|
readonly nonFinite: boolean;
|
|
@@ -65,6 +70,8 @@ export interface ClapQualifyLine {
|
|
|
65
70
|
readonly hostExtensions: readonly string[];
|
|
66
71
|
readonly clap: ClapProbedFacts;
|
|
67
72
|
readonly rtSafety: ClapRtSafetyReading;
|
|
73
|
+
/** The note sweep: the plugin played through its note input, counted apart. */
|
|
74
|
+
readonly noteSweep: ClapNoteSweepReading;
|
|
68
75
|
/**
|
|
69
76
|
* The pin (spec §11): the `.clap`'s digest by its name in its directory and the layout fingerprint
|
|
70
77
|
* the prober computed through plugin-hostd's `pin.h`, or the reason it took none.
|
|
@@ -143,6 +150,31 @@ function readRtSafety(v: unknown): ClapRtSafetyReading {
|
|
|
143
150
|
};
|
|
144
151
|
}
|
|
145
152
|
|
|
153
|
+
function readNoteSweep(v: unknown): ClapNoteSweepReading {
|
|
154
|
+
if (!isRecord(v)) return bad("no noteSweep");
|
|
155
|
+
if (!bool(v, "applicable", "noteSweep")) return { applicable: false };
|
|
156
|
+
const dialect = str(v, "dialect", "noteSweep");
|
|
157
|
+
if (dialect !== "clap" && dialect !== "midi") return bad(`noteSweep dialect ${dialect}`);
|
|
158
|
+
const played = {
|
|
159
|
+
applicable: true,
|
|
160
|
+
dialect,
|
|
161
|
+
blocks: count(v, "blocks", "noteSweep"),
|
|
162
|
+
blockFrames: count(v, "blockFrames", "noteSweep"),
|
|
163
|
+
noteEvents: count(v, "noteEvents", "noteSweep"),
|
|
164
|
+
processErrors: count(v, "processErrors", "noteSweep"),
|
|
165
|
+
nonFinite: bool(v, "nonFinite", "noteSweep"),
|
|
166
|
+
} as const;
|
|
167
|
+
if (!bool(v, "interposer", "noteSweep")) return { ...played, interposer: false };
|
|
168
|
+
return {
|
|
169
|
+
...played,
|
|
170
|
+
interposer: true,
|
|
171
|
+
allocationsInRun: count(v, "allocationsInRun", "noteSweep"),
|
|
172
|
+
locksInRun: count(v, "locksInRun", "noteSweep"),
|
|
173
|
+
syscallsInRun: count(v, "syscallsInRun", "noteSweep"),
|
|
174
|
+
events: strings(v, "events", "noteSweep"),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
146
178
|
/** One line of the prober's stdout, or a {@link ClapQualifyError} saying how it differs. */
|
|
147
179
|
export function parseClapQualifyLine(text: string): ClapQualifyLine {
|
|
148
180
|
let v: unknown;
|
|
@@ -167,6 +199,7 @@ export function parseClapQualifyLine(text: string): ClapQualifyLine {
|
|
|
167
199
|
hostExtensions: strings(v, "hostExtensions", "the line"),
|
|
168
200
|
clap: readClap(v["clap"]),
|
|
169
201
|
rtSafety: readRtSafety(v["rtSafety"]),
|
|
202
|
+
noteSweep: readNoteSweep(v["noteSweep"]),
|
|
170
203
|
integrity: readIntegrity(v["integrity"]) ?? bad("no integrity"),
|
|
171
204
|
};
|
|
172
205
|
}
|
|
@@ -178,8 +211,13 @@ export function parseClapQualifyOutput(stdout: string): ClapQualifyLine[] {
|
|
|
178
211
|
|
|
179
212
|
// --- the verdict document ------------------------------------------------------------------------
|
|
180
213
|
|
|
181
|
-
/**
|
|
182
|
-
|
|
214
|
+
/**
|
|
215
|
+
* The document's schema name; a reader refuses any other it does not know. `/2` is `/1` with
|
|
216
|
+
* `measured.noteSweep` added and the note sweep's reasons folded into `verdict` (a plugin with no
|
|
217
|
+
* note input gets none, so its `/2` verdict is its `/1` verdict); every `/1` field is unchanged, so
|
|
218
|
+
* a `/1` reader of `verdict.rating` and `verdict.rtSafe` reads a `/2` document right.
|
|
219
|
+
*/
|
|
220
|
+
export const RT_SAFETY_DOCUMENT_SCHEMA = "plugin-qualify.rt-safety/2";
|
|
183
221
|
|
|
184
222
|
/**
|
|
185
223
|
* The RT-safety verdict of one plugin binary for one console: keyed by the binary's SHA-256 and
|
|
@@ -207,6 +245,7 @@ export interface RtSafetyDocument {
|
|
|
207
245
|
readonly hostExtensions: readonly string[];
|
|
208
246
|
readonly clap: ClapProbedFacts;
|
|
209
247
|
readonly rtSafety: ClapRtSafetyReading;
|
|
248
|
+
readonly noteSweep: ClapNoteSweepReading;
|
|
210
249
|
readonly integrity: IntegrityMeasurement;
|
|
211
250
|
};
|
|
212
251
|
}
|
|
@@ -225,7 +264,7 @@ export function rtSafetyDocument(line: ClapQualifyLine, profile: HostProfile = O
|
|
|
225
264
|
offered === null
|
|
226
265
|
? [{ dimension: "rtSafety", kind: "objective", rating: "unknown", code: HOSTING_CODE.clapFormatNotHosted, params: {} }]
|
|
227
266
|
: offeredAsProfile
|
|
228
|
-
? [...rtSafetyReasons(line.rtSafety), ...clapRtReasons(line.clap)]
|
|
267
|
+
? [...rtSafetyReasons(line.rtSafety), ...clapRtReasons(line.clap), ...noteSweepReasons(line.noteSweep, line.clap.noteInputs)]
|
|
229
268
|
: [
|
|
230
269
|
{
|
|
231
270
|
dimension: "rtSafety",
|
|
@@ -244,7 +283,13 @@ export function rtSafetyDocument(line: ClapQualifyLine, profile: HostProfile = O
|
|
|
244
283
|
plugin: { uri: line.uri, format: "clap", binary: line.binary },
|
|
245
284
|
console: profile.id,
|
|
246
285
|
verdict: { rating, rtSafe: rating === "suitable", ...(rating === "suitable" || deciding === undefined ? {} : { deciding }), reasons },
|
|
247
|
-
measured: {
|
|
286
|
+
measured: {
|
|
287
|
+
hostExtensions: line.hostExtensions,
|
|
288
|
+
clap: line.clap,
|
|
289
|
+
rtSafety: line.rtSafety,
|
|
290
|
+
noteSweep: line.noteSweep,
|
|
291
|
+
integrity: line.integrity,
|
|
292
|
+
},
|
|
248
293
|
};
|
|
249
294
|
}
|
|
250
295
|
|
|
@@ -265,45 +310,7 @@ export function rtSafetyDocumentName(doc: RtSafetyDocument): string {
|
|
|
265
310
|
return `${doc.plugin.binary.sha256}.${doc.console}.rt-safety.json`;
|
|
266
311
|
}
|
|
267
312
|
|
|
268
|
-
// ---
|
|
269
|
-
|
|
270
|
-
/** What a run of the prober gave back; `error` is a spawn failure, `signal` a death. */
|
|
271
|
-
export interface ClapQualifyRunResult {
|
|
272
|
-
readonly status: number | null;
|
|
273
|
-
readonly signal?: string | null;
|
|
274
|
-
readonly stdout: string;
|
|
275
|
-
readonly stderr?: string;
|
|
276
|
-
readonly error?: Error;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
export type ClapQualifyRun = (
|
|
280
|
-
tool: string,
|
|
281
|
-
args: readonly string[],
|
|
282
|
-
env: Readonly<Record<string, string | undefined>>,
|
|
283
|
-
timeoutMs: number,
|
|
284
|
-
) => ClapQualifyRunResult;
|
|
285
|
-
|
|
286
|
-
const spawnQualify: ClapQualifyRun = (tool, args, env, timeoutMs) => {
|
|
287
|
-
const r = spawnSync(tool, [...args], { encoding: "utf8", env: { ...env }, timeout: timeoutMs, maxBuffer: 64 << 20 });
|
|
288
|
-
return {
|
|
289
|
-
status: r.status,
|
|
290
|
-
signal: r.signal,
|
|
291
|
-
stdout: r.stdout ?? "",
|
|
292
|
-
stderr: r.stderr ?? "",
|
|
293
|
-
...(r.error === undefined ? {} : { error: r.error }),
|
|
294
|
-
};
|
|
295
|
-
};
|
|
296
|
-
|
|
297
|
-
export interface ClapQualifyOptions {
|
|
298
|
-
/** The rates the plugin is activated and swept at. */
|
|
299
|
-
readonly rates: readonly number[];
|
|
300
|
-
/** Runs the prober; the default spawns it. */
|
|
301
|
-
readonly run?: ClapQualifyRun;
|
|
302
|
-
/** Where the two settings are read from; the default is `process.env`. */
|
|
303
|
-
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
304
|
-
/** Whether a file exists; the default asks the filesystem. */
|
|
305
|
-
readonly exists?: (path: string) => boolean;
|
|
306
|
-
}
|
|
313
|
+
// --- the prober and the interposer a run uses ---------------------------------------------------
|
|
307
314
|
|
|
308
315
|
/**
|
|
309
316
|
* The prober and the interposer this run uses: each its setting, else both from one place, the
|
|
@@ -327,76 +334,15 @@ export function clapQualifyTools(
|
|
|
327
334
|
}
|
|
328
335
|
|
|
329
336
|
/**
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
*
|
|
337
|
+
* What the prober must offer beyond the console's extensions to measure for `profile`: the extra
|
|
338
|
+
* extensions of its list, when the prober can offer each and the list holds the console's; `undefined`
|
|
339
|
+
* for a profile the prober cannot stand in for (no CLAP path, or an extension it cannot offer), which
|
|
340
|
+
* is judged from the console's run and reads `unknown` there.
|
|
333
341
|
*/
|
|
334
|
-
export function
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
"--block",
|
|
341
|
-
String(CLAP_SWEEP_FRAMES),
|
|
342
|
-
"--blocks",
|
|
343
|
-
String(CLAP_SWEEP_BLOCKS),
|
|
344
|
-
binaryPath,
|
|
345
|
-
...(id === undefined ? [] : [id]),
|
|
346
|
-
];
|
|
347
|
-
const r = (options.run ?? spawnQualify)(tool, args, { ...env, LD_PRELOAD: interposer }, CLAP_QUALIFY_TIMEOUT_S * 1000);
|
|
348
|
-
if (r.error !== undefined) throw new ClapQualifyError(`cannot run ${tool}: ${r.error.message}`);
|
|
349
|
-
if (r.signal) throw new ClapQualifyError(`${binaryPath} killed the prober (${r.signal})`);
|
|
350
|
-
if (r.status !== 0) throw new ClapQualifyError(`${tool} exited ${String(r.status)} on ${binaryPath}: ${(r.stderr ?? "").trim()}`);
|
|
351
|
-
const lines = parseClapQualifyOutput(r.stdout);
|
|
352
|
-
if (lines.length === 0) throw new ClapQualifyError(`${tool} printed nothing for ${binaryPath}`);
|
|
353
|
-
return lines;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// --- the CLI's CLAP pass -------------------------------------------------------------------------
|
|
357
|
-
|
|
358
|
-
/** What the CLAP pass of one run produced: a document per plugin and console, and a row per verdict. */
|
|
359
|
-
export interface ClapQualifyPass {
|
|
360
|
-
readonly documents: readonly RtSafetyDocument[];
|
|
361
|
-
readonly verdicts: readonly QualifyVerdict[];
|
|
362
|
-
/** Each target that could not be measured, with why; its verdict rows read `unknown`, unmeasured. */
|
|
363
|
-
readonly failures: readonly { readonly uri: string; readonly problem: string }[];
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
/**
|
|
367
|
-
* Qualify every CLAP target, one prober run per target (a crash takes only that plugin), and judge
|
|
368
|
-
* each line under every host profile. A target without a binary path (a bare `urn:clap:` id) has
|
|
369
|
-
* nothing to load and is a failure, never a pass.
|
|
370
|
-
*/
|
|
371
|
-
export function qualifyClapTargets(
|
|
372
|
-
targets: readonly PluginTarget[],
|
|
373
|
-
options: ClapQualifyOptions & { readonly profiles?: readonly HostProfile[] },
|
|
374
|
-
): ClapQualifyPass {
|
|
375
|
-
const profiles = options.profiles ?? [OPENMIXER_CONSOLE_PROFILE];
|
|
376
|
-
const documents: RtSafetyDocument[] = [];
|
|
377
|
-
const verdicts: QualifyVerdict[] = [];
|
|
378
|
-
const failures: { uri: string; problem: string }[] = [];
|
|
379
|
-
for (const target of targets) {
|
|
380
|
-
const id = target.uri.startsWith("urn:clap:") ? target.uri.slice("urn:clap:".length) : undefined;
|
|
381
|
-
let lines: ClapQualifyLine[];
|
|
382
|
-
try {
|
|
383
|
-
if (target.binaryPath === undefined) throw new ClapQualifyError(`${target.uri}: no .clap to load; name the file or a directory holding it`);
|
|
384
|
-
lines = qualifyClap(target.binaryPath, id, options);
|
|
385
|
-
} catch (e) {
|
|
386
|
-
if (!(e instanceof ClapQualifyError)) throw e;
|
|
387
|
-
failures.push({ uri: target.uri, problem: e.message });
|
|
388
|
-
for (const profile of profiles) {
|
|
389
|
-
verdicts.push({ uri: target.uri, host: profile.id, measured: false, rating: "unknown", qualified: false, deciding: "not-measured" });
|
|
390
|
-
}
|
|
391
|
-
continue;
|
|
392
|
-
}
|
|
393
|
-
for (const line of lines) {
|
|
394
|
-
for (const profile of profiles) {
|
|
395
|
-
const doc = rtSafetyDocument(line, profile);
|
|
396
|
-
documents.push(doc);
|
|
397
|
-
verdicts.push(rtSafetyVerdict(doc));
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
return { documents, verdicts, failures };
|
|
342
|
+
export function proberOffer(profile: HostProfile): readonly string[] | undefined {
|
|
343
|
+
const wanted = profile.clapExtensions;
|
|
344
|
+
if (wanted === null || !OMX_CLAP_HOST_EXTENSIONS.every((e) => wanted.includes(e))) return undefined;
|
|
345
|
+
const extra = wanted.filter((e) => !OMX_CLAP_HOST_EXTENSIONS.includes(e));
|
|
346
|
+
if (!extra.every((e) => OMX_CLAP_HOST_CONFIGURED_EXTENSIONS.includes(e))) return undefined;
|
|
347
|
+
return OMX_CLAP_HOST_CONFIGURED_EXTENSIONS.filter((e) => extra.includes(e));
|
|
402
348
|
}
|