@openmixer/plugin-qualify 0.0.0-stage → 0.3.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 (114) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +203 -2
  3. package/bin/plugin-qualify.mjs +77 -0
  4. package/dist/clap-loader.d.ts +147 -0
  5. package/dist/clap-loader.js +252 -0
  6. package/dist/clap-loader.js.map +1 -0
  7. package/dist/clap-qualify.d.ts +126 -0
  8. package/dist/clap-qualify.js +272 -0
  9. package/dist/clap-qualify.js.map +1 -0
  10. package/dist/host-profiles.d.ts +42 -0
  11. package/dist/host-profiles.js +106 -0
  12. package/dist/host-profiles.js.map +1 -0
  13. package/dist/hosting-codes.d.ts +62 -0
  14. package/dist/hosting-codes.js +72 -0
  15. package/dist/hosting-codes.js.map +1 -0
  16. package/dist/hosting-suitability.d.ts +354 -0
  17. package/dist/hosting-suitability.js +654 -0
  18. package/dist/hosting-suitability.js.map +1 -0
  19. package/dist/index.d.ts +17 -0
  20. package/dist/index.js +20 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/loader.d.ts +26 -0
  23. package/dist/loader.js +9 -0
  24. package/dist/loader.js.map +1 -0
  25. package/dist/lv2-loader.d.ts +7 -0
  26. package/dist/lv2-loader.js +50 -0
  27. package/dist/lv2-loader.js.map +1 -0
  28. package/dist/measurement/cli.d.ts +168 -0
  29. package/dist/measurement/cli.js +353 -0
  30. package/dist/measurement/cli.js.map +1 -0
  31. package/dist/measurement/fixtures.d.ts +23 -0
  32. package/dist/measurement/fixtures.js +129 -0
  33. package/dist/measurement/fixtures.js.map +1 -0
  34. package/dist/measurement/format.d.ts +483 -0
  35. package/dist/measurement/format.js +627 -0
  36. package/dist/measurement/format.js.map +1 -0
  37. package/dist/measurement/guards.d.ts +33 -0
  38. package/dist/measurement/guards.js +61 -0
  39. package/dist/measurement/guards.js.map +1 -0
  40. package/dist/measurement/index.d.ts +36 -0
  41. package/dist/measurement/index.js +39 -0
  42. package/dist/measurement/index.js.map +1 -0
  43. package/dist/measurement/merge.d.ts +143 -0
  44. package/dist/measurement/merge.js +315 -0
  45. package/dist/measurement/merge.js.map +1 -0
  46. package/dist/measurement/plan.d.ts +127 -0
  47. package/dist/measurement/plan.js +141 -0
  48. package/dist/measurement/plan.js.map +1 -0
  49. package/dist/measurement/probe-json.d.ts +61 -0
  50. package/dist/measurement/probe-json.js +253 -0
  51. package/dist/measurement/probe-json.js.map +1 -0
  52. package/dist/measurement/provenance.d.ts +91 -0
  53. package/dist/measurement/provenance.js +173 -0
  54. package/dist/measurement/provenance.js.map +1 -0
  55. package/dist/measurement/sweep-jsonl.d.ts +40 -0
  56. package/dist/measurement/sweep-jsonl.js +154 -0
  57. package/dist/measurement/sweep-jsonl.js.map +1 -0
  58. package/dist/measurement/vocabulary.d.ts +125 -0
  59. package/dist/measurement/vocabulary.js +217 -0
  60. package/dist/measurement/vocabulary.js.map +1 -0
  61. package/dist/qualify-cli.d.ts +83 -0
  62. package/dist/qualify-cli.js +224 -0
  63. package/dist/qualify-cli.js.map +1 -0
  64. package/dist/qualify-declarations.d.ts +98 -0
  65. package/dist/qualify-declarations.js +76 -0
  66. package/dist/qualify-declarations.js.map +1 -0
  67. package/dist/qualify-options.d.ts +22 -0
  68. package/dist/qualify-options.js +36 -0
  69. package/dist/qualify-options.js.map +1 -0
  70. package/dist/rating.d.ts +32 -0
  71. package/dist/rating.js +30 -0
  72. package/dist/rating.js.map +1 -0
  73. package/dist/tool-paths.d.ts +12 -0
  74. package/dist/tool-paths.js +29 -0
  75. package/dist/tool-paths.js.map +1 -0
  76. package/fixtures/clap-scan/controls.json +8 -0
  77. package/fixtures/clap-scan/fake.json +16 -0
  78. package/fixtures/clap-scan/mixed.json +18 -0
  79. package/fixtures/zero-path.lv2/manifest.ttl +3 -0
  80. package/fixtures/zero-path.lv2/zero-path.c +85 -0
  81. package/fixtures/zero-path.lv2/zero-path.ttl +21 -0
  82. package/package.json +91 -3
  83. package/src/clap-loader.ts +393 -0
  84. package/src/clap-qualify.ts +387 -0
  85. package/src/host-profiles.ts +122 -0
  86. package/src/hosting-codes.ts +71 -0
  87. package/src/hosting-suitability.ts +982 -0
  88. package/src/index.ts +19 -0
  89. package/src/loader.ts +30 -0
  90. package/src/lv2-loader.ts +50 -0
  91. package/src/measurement/cli.ts +458 -0
  92. package/src/measurement/fixtures.ts +141 -0
  93. package/src/measurement/format.ts +1064 -0
  94. package/src/measurement/guards.ts +64 -0
  95. package/src/measurement/index.ts +39 -0
  96. package/src/measurement/merge.ts +434 -0
  97. package/src/measurement/plan.ts +219 -0
  98. package/src/measurement/probe-json.ts +271 -0
  99. package/src/measurement/provenance.ts +220 -0
  100. package/src/measurement/sweep-jsonl.ts +173 -0
  101. package/src/measurement/vocabulary.ts +298 -0
  102. package/src/qualify-cli.ts +277 -0
  103. package/src/qualify-declarations.ts +117 -0
  104. package/src/qualify-options.ts +49 -0
  105. package/src/rating.ts +53 -0
  106. package/src/tool-paths.ts +32 -0
  107. package/tools/_declarations.py +21 -0
  108. package/tools/benchmark.py +2462 -0
  109. package/tools/gen-python-declarations.d.mts +4 -0
  110. package/tools/gen-python-declarations.mjs +92 -0
  111. package/tools/hosting-sweep.mjs +51 -0
  112. package/tools/lv2-measure.mjs +204 -0
  113. package/tools/lv2_presets.py +132 -0
  114. package/tools/scan.py +432 -0
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The only shape primitives this directory uses.
3
+ *
4
+ * `src/measurement/` is written to be lifted out as a directory copy, so it imports NOTHING from its parent package — not even the catalog's
5
+ * own `guards.ts`, whose header explains the same reasoning one level up. Keep it that
6
+ * way: any import that reaches outside this directory turns a copy into a port.
7
+ *
8
+ * Every reader here returns `undefined` for input it cannot vouch for, and every builder
9
+ * omits a field it has no value for. Both halves of the format's central rule: an absent
10
+ * field means "not determined", never "zero" and never "false".
11
+ */
12
+ /** Non-null, non-array plain object — the only shape a validator may index into. */
13
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
14
+ /** A finite number, or `undefined` for anything else (including `NaN` and `Infinity`). */
15
+ export declare function finiteNumber(value: unknown): number | undefined;
16
+ /** A finite number strictly greater than zero, or `undefined`. */
17
+ export declare function positiveNumber(value: unknown): number | undefined;
18
+ /** A non-empty string, or `undefined`. An empty string is absence spelled differently. */
19
+ export declare function nonEmptyString(value: unknown): string | undefined;
20
+ /** A boolean, or `undefined` — never a coerced truthiness. */
21
+ export declare function booleanValue(value: unknown): boolean | undefined;
22
+ /** Every element of an array that a reader accepted, in order; `undefined` if not an array. */
23
+ export declare function mapArray<T>(value: unknown, read: (item: unknown) => T | undefined): T[] | undefined;
24
+ /**
25
+ * `{ [key]: value }` when `value` is defined, `{}` otherwise — the spread helper every
26
+ * builder uses for an optional field.
27
+ *
28
+ * Written as a function rather than an inline ternary at ~40 call sites so the intent
29
+ * (omit, do not null) is named once and cannot drift.
30
+ */
31
+ export declare function optional<K extends string, V>(key: K, value: V | undefined): {
32
+ [P in K]?: V;
33
+ };
@@ -0,0 +1,61 @@
1
+ // SPDX-License-Identifier: GPL-3.0-or-later
2
+ // Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
3
+ /**
4
+ * The only shape primitives this directory uses.
5
+ *
6
+ * `src/measurement/` is written to be lifted out as a directory copy, so it imports NOTHING from its parent package — not even the catalog's
7
+ * own `guards.ts`, whose header explains the same reasoning one level up. Keep it that
8
+ * way: any import that reaches outside this directory turns a copy into a port.
9
+ *
10
+ * Every reader here returns `undefined` for input it cannot vouch for, and every builder
11
+ * omits a field it has no value for. Both halves of the format's central rule: an absent
12
+ * field means "not determined", never "zero" and never "false".
13
+ */
14
+ /** Non-null, non-array plain object — the only shape a validator may index into. */
15
+ export function isRecord(value) {
16
+ return typeof value === "object" && value !== null && !Array.isArray(value);
17
+ }
18
+ /** A finite number, or `undefined` for anything else (including `NaN` and `Infinity`). */
19
+ export function finiteNumber(value) {
20
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
21
+ }
22
+ /** A finite number strictly greater than zero, or `undefined`. */
23
+ export function positiveNumber(value) {
24
+ const n = finiteNumber(value);
25
+ return n !== undefined && n > 0 ? n : undefined;
26
+ }
27
+ /** A non-empty string, or `undefined`. An empty string is absence spelled differently. */
28
+ export function nonEmptyString(value) {
29
+ return typeof value === "string" && value.length > 0 ? value : undefined;
30
+ }
31
+ /** A boolean, or `undefined` — never a coerced truthiness. */
32
+ export function booleanValue(value) {
33
+ return typeof value === "boolean" ? value : undefined;
34
+ }
35
+ /** Every element of an array that a reader accepted, in order; `undefined` if not an array. */
36
+ export function mapArray(value, read) {
37
+ if (!Array.isArray(value))
38
+ return undefined;
39
+ const out = [];
40
+ for (const item of value) {
41
+ const parsed = read(item);
42
+ if (parsed !== undefined)
43
+ out.push(parsed);
44
+ }
45
+ return out;
46
+ }
47
+ /**
48
+ * `{ [key]: value }` when `value` is defined, `{}` otherwise — the spread helper every
49
+ * builder uses for an optional field.
50
+ *
51
+ * Written as a function rather than an inline ternary at ~40 call sites so the intent
52
+ * (omit, do not null) is named once and cannot drift.
53
+ */
54
+ export function optional(key, value) {
55
+ if (value === undefined)
56
+ return {};
57
+ const out = {};
58
+ out[key] = value;
59
+ return out;
60
+ }
61
+ //# sourceMappingURL=guards.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guards.js","sourceRoot":"","sources":["../../src/measurement/guards.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,sDAAsD;AACtD;;;;;;;;;;GAUG;AAEH,oFAAoF;AACpF,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACjF,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,MAAM,CAAC,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IAC9B,OAAO,CAAC,KAAK,SAAS,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAClD,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAC3E,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,OAAO,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACxD,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,QAAQ,CAAI,KAAc,EAAE,IAAsC;IAChF,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC5C,MAAM,GAAG,GAAQ,EAAE,CAAC;IACpB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1B,IAAI,MAAM,KAAK,SAAS;YAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAsB,GAAM,EAAE,KAAoB;IACxE,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IACnC,MAAM,GAAG,GAAqB,EAAE,CAAC;IACjC,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACjB,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * **The LV2 plugin measurement interchange format, and the tool that produces it.**
3
+ *
4
+ * This directory is written to stand alone. The idea is that measured LV2 plugin
5
+ * characteristics — per-rate latency with its scaling law, per-rate CPU cost, topology, and
6
+ * above all the PROVENANCE without which none of it is comparable — should be publishable
7
+ * in a shape any LV2 host can read, rather than re-derived by each project from a prober it
8
+ * wrote itself. The OpenMixer console is the format's first consumer, which is what keeps it
9
+ * honest; it is not meant to be the only one.
10
+ *
11
+ * ## What is in here, and what is deliberately not
12
+ *
13
+ * | module | role |
14
+ * |---|---|
15
+ * | `format.ts` | the schema: types, version strategy, cast-free readers, builders |
16
+ * | `vocabulary.ts` | the controlled vocabularies — why a figure is absent, how latency scales |
17
+ * | `provenance.ts` | the run record: machine facts read rather than assumed |
18
+ * | `probe-json.ts` | `benchmark.py`'s working JSON → the format |
19
+ * | `sweep-jsonl.ts` | the hosting sweep's JSONL (`lv2-measure.c`) → the three hosting dimensions |
20
+ * | `merge.ts` | shipped defaults with local measurements over them, each figure knowing its source |
21
+ * | `plan.ts` | which rates to measure, and how long it might take |
22
+ * | `cli.ts` | `lv2-measure`'s decisions, with the subprocess spawning left to `tools/lv2-measure.mjs` |
23
+ *
24
+ * Nothing in this directory imports anything outside it — not a catalog's descriptor
25
+ * types, not suitability rules, not a console's core library. Extraction is a directory copy
26
+ * plus `tools/{scan,benchmark}.py` and `tools/lv2-measure.mjs`. Adapters that turn a merged
27
+ * view into a host's own descriptors belong in that host, on purpose.
28
+ */
29
+ export * from "./format.js";
30
+ export * from "./vocabulary.js";
31
+ export * from "./provenance.js";
32
+ export * from "./probe-json.js";
33
+ export * from "./merge.js";
34
+ export * from "./plan.js";
35
+ export * from "./cli.js";
36
+ export * from "./sweep-jsonl.js";
@@ -0,0 +1,39 @@
1
+ // SPDX-License-Identifier: GPL-3.0-or-later
2
+ // Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
3
+ /**
4
+ * **The LV2 plugin measurement interchange format, and the tool that produces it.**
5
+ *
6
+ * This directory is written to stand alone. The idea is that measured LV2 plugin
7
+ * characteristics — per-rate latency with its scaling law, per-rate CPU cost, topology, and
8
+ * above all the PROVENANCE without which none of it is comparable — should be publishable
9
+ * in a shape any LV2 host can read, rather than re-derived by each project from a prober it
10
+ * wrote itself. The OpenMixer console is the format's first consumer, which is what keeps it
11
+ * honest; it is not meant to be the only one.
12
+ *
13
+ * ## What is in here, and what is deliberately not
14
+ *
15
+ * | module | role |
16
+ * |---|---|
17
+ * | `format.ts` | the schema: types, version strategy, cast-free readers, builders |
18
+ * | `vocabulary.ts` | the controlled vocabularies — why a figure is absent, how latency scales |
19
+ * | `provenance.ts` | the run record: machine facts read rather than assumed |
20
+ * | `probe-json.ts` | `benchmark.py`'s working JSON → the format |
21
+ * | `sweep-jsonl.ts` | the hosting sweep's JSONL (`lv2-measure.c`) → the three hosting dimensions |
22
+ * | `merge.ts` | shipped defaults with local measurements over them, each figure knowing its source |
23
+ * | `plan.ts` | which rates to measure, and how long it might take |
24
+ * | `cli.ts` | `lv2-measure`'s decisions, with the subprocess spawning left to `tools/lv2-measure.mjs` |
25
+ *
26
+ * Nothing in this directory imports anything outside it — not a catalog's descriptor
27
+ * types, not suitability rules, not a console's core library. Extraction is a directory copy
28
+ * plus `tools/{scan,benchmark}.py` and `tools/lv2-measure.mjs`. Adapters that turn a merged
29
+ * view into a host's own descriptors belong in that host, on purpose.
30
+ */
31
+ export * from "./format.js";
32
+ export * from "./vocabulary.js";
33
+ export * from "./provenance.js";
34
+ export * from "./probe-json.js";
35
+ export * from "./merge.js";
36
+ export * from "./plan.js";
37
+ export * from "./cli.js";
38
+ export * from "./sweep-jsonl.js";
39
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/measurement/index.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,sDAAsD;AACtD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,kBAAkB,CAAC"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * **Shipped defaults, overridden by local measurement** — and the operator can
3
+ * always see which is which.
4
+ *
5
+ * The rule is one sentence: *what we ship is a sensible default; a local run overrides it.*
6
+ * Everything below is what that sentence costs once you refuse to lie about the result.
7
+ *
8
+ * ## Why the merge is per plugin per DIMENSION, and not per rate
9
+ *
10
+ * The tempting rule — "take the local figure wherever there is one, rate by rate" — would
11
+ * produce a plugin whose 48 kHz cost came from the customer's NUC and whose 96 kHz cost came
12
+ * from our reference laptop. The scaling class then gets fitted across two machines, and the
13
+ * ratio between the two rates, the one thing per-rate data exists to express, becomes an
14
+ * artefact of the hardware difference. It would look like data and behave like noise.
15
+ *
16
+ * So a local run REPLACES a plugin's whole dimension or none of it. A rate the local run did
17
+ * not cover comes back as MISSING, not as the shipped number wearing a local label
18
+ * ({@link MergedMeasurements.missingRates}) — which is what a reader wants when the
19
+ * operator later adds a rate: *"the affected figures are missing rather than wrong — surface
20
+ * that, and offer to measure the new rate rather than quietly interpolating."*
21
+ *
22
+ * ## Why a failed local probe does NOT override
23
+ *
24
+ * A local run that crashed on a plugin has learned nothing about that plugin's latency. If
25
+ * the failure overrode, one segfault would delete a good shipped figure and the operator's
26
+ * catalogue would get worse the more they measured. So the vocabulary's `kind` decides
27
+ * ({@link ../vocabulary.unmeasuredKind}):
28
+ *
29
+ * - `nothing-to-measure` (no audio in, no audio out, silent) — a property of the PLUGIN, as
30
+ * portable as a measurement. It overrides.
31
+ * - `probe-failed` (crashed, timed out, refused, non-finite) — a property of THIS RUN. The
32
+ * shipped figure stands, and the local failure is still reported
33
+ * ({@link MergedPluginMeasurement.localProbeFailed}) so the operator sees that their
34
+ * machine could not reproduce it.
35
+ * - `not-attempted` — a partial run. Not a finding at all.
36
+ *
37
+ * An UNKNOWN reason code from a third-party document resolves to `probe-failed`: a word we
38
+ * cannot interpret must never be allowed to delete a real measurement.
39
+ */
40
+ import type { CostMeasurement, FeaturesMeasurement, LatencyMeasurement, MeasurementDocument, MeasuredPlugin, MeasurementRun, RtSafetyMeasurement, StabilityMeasurement } from "./format.js";
41
+ import { type MeasurementDimension } from "./vocabulary.js";
42
+ /** Where a figure came from. Two words, because the operator only ever has two questions. */
43
+ export type FigureSource = "local" | "shipped";
44
+ /** Every source, in the order precedence resolves them (highest first). */
45
+ export declare const FIGURE_SOURCES: readonly FigureSource[];
46
+ /** One dimension's resolved figure, with the run that produced it named. */
47
+ export interface ResolvedFigure<T> {
48
+ readonly source: FigureSource;
49
+ /** {@link MeasurementRun.id} of the run this came from. */
50
+ readonly runId: string;
51
+ /** The run's host, denormalised so a surface can label a figure without a second lookup. */
52
+ readonly host: string;
53
+ readonly measuredAt: string;
54
+ readonly value: T;
55
+ /** Rates this figure actually covers, ascending. */
56
+ readonly rates: readonly number[];
57
+ }
58
+ /** One plugin as the merged view sees it. */
59
+ export interface MergedPluginMeasurement {
60
+ readonly uri: string;
61
+ /** Best available identity — the local run's, falling back to the shipped one's. */
62
+ readonly plugin: MeasuredPlugin;
63
+ readonly latency?: ResolvedFigure<LatencyMeasurement>;
64
+ readonly cost?: ResolvedFigure<CostMeasurement>;
65
+ readonly stability?: ResolvedFigure<StabilityMeasurement>;
66
+ readonly rtSafety?: ResolvedFigure<RtSafetyMeasurement>;
67
+ readonly features?: ResolvedFigure<FeaturesMeasurement>;
68
+ /**
69
+ * Dimensions where the LOCAL run tried and failed (crashed, timed out, refused), keyed
70
+ * by dimension with the reason code. The shipped figure is still in use for these —
71
+ * that is the point — but the operator is told their machine could not reproduce it.
72
+ */
73
+ readonly localProbeFailed?: Readonly<Partial<Record<MeasurementDimension, string>>>;
74
+ /** Present only on a plugin installed here that the shipped catalogue never saw. */
75
+ readonly localOnly?: true;
76
+ }
77
+ /** Counts a Setup screen can render without walking the catalogue itself. */
78
+ export interface MergeSummary {
79
+ readonly plugins: number;
80
+ /** Plugins whose figure for this dimension came from each source. */
81
+ readonly bySource: Readonly<Record<MeasurementDimension, Readonly<Record<FigureSource, number>>>>;
82
+ /** Plugins with no figure at all for this dimension, from either source. */
83
+ readonly unresolved: Readonly<Record<MeasurementDimension, number>>;
84
+ /** Plugins present locally and absent from the shipped catalogue. */
85
+ readonly localOnly: number;
86
+ /** Plugins the local run tried and failed on, per dimension. */
87
+ readonly localProbeFailures: Readonly<Record<MeasurementDimension, number>>;
88
+ }
89
+ /** Each dimension's block type, so a resolved figure keeps the type of the block it holds. */
90
+ interface MeasurementOf {
91
+ readonly latency: LatencyMeasurement;
92
+ readonly cost: CostMeasurement;
93
+ readonly stability: StabilityMeasurement;
94
+ readonly rtSafety: RtSafetyMeasurement;
95
+ readonly features: FeaturesMeasurement;
96
+ }
97
+ declare function dimensionOf<D extends MeasurementDimension>(plugin: MeasuredPlugin, dimension: D): MeasurementOf[D] | undefined;
98
+ /**
99
+ * The merged view an installation reads: shipped defaults with local measurements laid over
100
+ * them, every figure still knowing where it came from.
101
+ *
102
+ * Built once from two documents and then queried — the precedence rule runs at construction,
103
+ * so no consumer can accidentally re-implement half of it. A missing local document is the
104
+ * normal state (nobody has measured yet) and yields a view that is entirely `shipped`.
105
+ */
106
+ export declare class MergedMeasurements {
107
+ private readonly shipped;
108
+ private readonly local;
109
+ private readonly merged;
110
+ constructor(shipped: MeasurementDocument | undefined, local: MeasurementDocument | undefined);
111
+ /** The run behind each source, when that source has a document. */
112
+ runFor(source: FigureSource): MeasurementRun | undefined;
113
+ /** Every merged plugin, URI-ascending. */
114
+ all(): MergedPluginMeasurement[];
115
+ get(uri: string): MergedPluginMeasurement | undefined;
116
+ get size(): number;
117
+ /**
118
+ * Where this plugin's figure for `dimension` came from, or `undefined` when there is no
119
+ * figure at all. THE question a UI asks per row, so it is one call and not a walk.
120
+ */
121
+ sourceOf(uri: string, dimension: MeasurementDimension): FigureSource | undefined;
122
+ /** Every URI whose `dimension` figure came from `source`, URI-ascending. */
123
+ urisFrom(source: FigureSource, dimension: MeasurementDimension): string[];
124
+ /**
125
+ * Rates in `wanted` this plugin has NO figure for, on the dimension asked about.
126
+ *
127
+ * The honest answer to "the rig now runs at 96 kHz too": the rates the resolved figure
128
+ * does not cover are missing, and a surface should offer to measure them rather than
129
+ * project across a boundary the data says cannot be projected across.
130
+ */
131
+ missingRates(uri: string, dimension: MeasurementDimension, wanted: readonly number[]): number[];
132
+ /**
133
+ * Plugins whose resolved figure does not cover every rate in `wanted`, per dimension.
134
+ * What a "your local run does not cover 96 kHz" banner counts.
135
+ */
136
+ pluginsMissingRates(dimension: MeasurementDimension, wanted: readonly number[]): string[];
137
+ /** The counts a Setup screen renders. */
138
+ summary(): MergeSummary;
139
+ }
140
+ /** Convenience over the constructor, for call sites that read better as a function. */
141
+ export declare function mergeMeasurements(shipped: MeasurementDocument | undefined, local: MeasurementDocument | undefined): MergedMeasurements;
142
+ /** Re-exported so a consumer of the merge does not also have to import the format. */
143
+ export { dimensionOf as measurementForDimension };
@@ -0,0 +1,315 @@
1
+ // SPDX-License-Identifier: GPL-3.0-or-later
2
+ // Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
3
+ /**
4
+ * **Shipped defaults, overridden by local measurement** — and the operator can
5
+ * always see which is which.
6
+ *
7
+ * The rule is one sentence: *what we ship is a sensible default; a local run overrides it.*
8
+ * Everything below is what that sentence costs once you refuse to lie about the result.
9
+ *
10
+ * ## Why the merge is per plugin per DIMENSION, and not per rate
11
+ *
12
+ * The tempting rule — "take the local figure wherever there is one, rate by rate" — would
13
+ * produce a plugin whose 48 kHz cost came from the customer's NUC and whose 96 kHz cost came
14
+ * from our reference laptop. The scaling class then gets fitted across two machines, and the
15
+ * ratio between the two rates, the one thing per-rate data exists to express, becomes an
16
+ * artefact of the hardware difference. It would look like data and behave like noise.
17
+ *
18
+ * So a local run REPLACES a plugin's whole dimension or none of it. A rate the local run did
19
+ * not cover comes back as MISSING, not as the shipped number wearing a local label
20
+ * ({@link MergedMeasurements.missingRates}) — which is what a reader wants when the
21
+ * operator later adds a rate: *"the affected figures are missing rather than wrong — surface
22
+ * that, and offer to measure the new rate rather than quietly interpolating."*
23
+ *
24
+ * ## Why a failed local probe does NOT override
25
+ *
26
+ * A local run that crashed on a plugin has learned nothing about that plugin's latency. If
27
+ * the failure overrode, one segfault would delete a good shipped figure and the operator's
28
+ * catalogue would get worse the more they measured. So the vocabulary's `kind` decides
29
+ * ({@link ../vocabulary.unmeasuredKind}):
30
+ *
31
+ * - `nothing-to-measure` (no audio in, no audio out, silent) — a property of the PLUGIN, as
32
+ * portable as a measurement. It overrides.
33
+ * - `probe-failed` (crashed, timed out, refused, non-finite) — a property of THIS RUN. The
34
+ * shipped figure stands, and the local failure is still reported
35
+ * ({@link MergedPluginMeasurement.localProbeFailed}) so the operator sees that their
36
+ * machine could not reproduce it.
37
+ * - `not-attempted` — a partial run. Not a finding at all.
38
+ *
39
+ * An UNKNOWN reason code from a third-party document resolves to `probe-failed`: a word we
40
+ * cannot interpret must never be allowed to delete a real measurement.
41
+ */
42
+ import { MEASUREMENT_DIMENSIONS, unmeasuredKind } from "./vocabulary.js";
43
+ /** Every source, in the order precedence resolves them (highest first). */
44
+ export const FIGURE_SOURCES = ["local", "shipped"];
45
+ function dimensionOf(plugin, dimension) {
46
+ const blocks = {
47
+ latency: plugin.latency,
48
+ cost: plugin.cost,
49
+ stability: plugin.stability,
50
+ rtSafety: plugin.rtSafety,
51
+ features: plugin.features,
52
+ };
53
+ return blocks[dimension];
54
+ }
55
+ /** A merged entry's resolved figure for one dimension. */
56
+ function resolvedOf(entry, dimension) {
57
+ if (entry === undefined)
58
+ return undefined;
59
+ const figures = {
60
+ latency: entry.latency,
61
+ cost: entry.cost,
62
+ stability: entry.stability,
63
+ rtSafety: entry.rtSafety,
64
+ features: entry.features,
65
+ };
66
+ return figures[dimension];
67
+ }
68
+ /** Rates a measurement actually carries, ascending. Excluded readings still count as
69
+ * covered: the rate WAS measured, and the exclusion is the finding. A soak covers the rates
70
+ * it rendered, an interposer count the one rate it ran at, and a feature list no rate at all. */
71
+ function ratesOf(measurement) {
72
+ const keys = "perRate" in measurement
73
+ ? Object.keys(measurement.perRate)
74
+ : "soak" in measurement && measurement.soak !== undefined
75
+ ? Object.keys(measurement.soak.perRate)
76
+ : "rate" in measurement && measurement.rate !== undefined
77
+ ? [String(measurement.rate)]
78
+ : [];
79
+ return keys
80
+ .map(Number)
81
+ .filter((rate) => Number.isFinite(rate))
82
+ .sort((a, b) => a - b);
83
+ }
84
+ /**
85
+ * True when a measurement carries a READING rather than only a reason. Per dimension, because
86
+ * what a reading is differs: latency and cost need a rate; a stability block needs its
87
+ * lifecycle, an rt-safety block all three counts, a features block its required list.
88
+ */
89
+ function hasReadings(dimension, measurement) {
90
+ if (measurement === undefined)
91
+ return false;
92
+ switch (dimension) {
93
+ case "latency":
94
+ case "cost":
95
+ return "perRate" in measurement && Object.keys(measurement.perRate).length > 0;
96
+ case "stability":
97
+ return "lifecycle" in measurement && measurement.lifecycle !== undefined;
98
+ case "rtSafety":
99
+ return ("allocationsInRun" in measurement &&
100
+ measurement.allocationsInRun !== undefined &&
101
+ "locksInRun" in measurement &&
102
+ measurement.locksInRun !== undefined &&
103
+ "syscallsInRun" in measurement &&
104
+ measurement.syscallsInRun !== undefined);
105
+ case "features":
106
+ return "required" in measurement && measurement.required !== undefined;
107
+ }
108
+ }
109
+ function figure(source, run, value) {
110
+ return {
111
+ source,
112
+ runId: run.id,
113
+ host: run.host.hostname,
114
+ measuredAt: run.measuredAt,
115
+ value,
116
+ rates: ratesOf(value),
117
+ };
118
+ }
119
+ /** `{ [dimension]: figure }` when there is one, `{}` otherwise. */
120
+ function optionalFigure(dimension, value) {
121
+ if (value === undefined)
122
+ return {};
123
+ const out = {};
124
+ out[dimension] = value;
125
+ return out;
126
+ }
127
+ /**
128
+ * The precedence decision for ONE plugin and ONE dimension, expressed once.
129
+ *
130
+ * Returns the winning figure, plus the local failure reason when the local run tried and
131
+ * could not — the two outputs are independent, because "the shipped figure won" and "your
132
+ * machine crashed on it" are both true at the same time and the operator needs both.
133
+ */
134
+ function resolveDimension(dimension, local, shipped) {
135
+ const shippedFigure = shipped?.measurement !== undefined && hasReadings(dimension, shipped.measurement)
136
+ ? figure("shipped", shipped.run, shipped.measurement)
137
+ : undefined;
138
+ if (local === undefined || local.measurement === undefined) {
139
+ return shippedFigure === undefined ? {} : { resolved: shippedFigure };
140
+ }
141
+ // The local run covered this dimension and measured this plugin: it wins outright.
142
+ if (hasReadings(dimension, local.measurement)) {
143
+ return { resolved: figure("local", local.run, local.measurement) };
144
+ }
145
+ // No readings locally. What the local run learned depends entirely on WHY.
146
+ const kind = unmeasuredKind(local.measurement.unmeasuredReason);
147
+ if (kind === "nothing-to-measure") {
148
+ // A plugin property, as portable as a measurement — it overrides, with no number.
149
+ return { resolved: figure("local", local.run, local.measurement) };
150
+ }
151
+ if (kind === "probe-failed") {
152
+ return {
153
+ ...(shippedFigure === undefined ? {} : { resolved: shippedFigure }),
154
+ ...(local.measurement.unmeasuredReason === undefined
155
+ ? {}
156
+ : { localFailure: local.measurement.unmeasuredReason }),
157
+ };
158
+ }
159
+ // not-attempted: the local run has no opinion here.
160
+ return shippedFigure === undefined ? {} : { resolved: shippedFigure };
161
+ }
162
+ /**
163
+ * The merged view an installation reads: shipped defaults with local measurements laid over
164
+ * them, every figure still knowing where it came from.
165
+ *
166
+ * Built once from two documents and then queried — the precedence rule runs at construction,
167
+ * so no consumer can accidentally re-implement half of it. A missing local document is the
168
+ * normal state (nobody has measured yet) and yields a view that is entirely `shipped`.
169
+ */
170
+ export class MergedMeasurements {
171
+ shipped;
172
+ local;
173
+ merged;
174
+ constructor(shipped, local) {
175
+ this.shipped = shipped;
176
+ this.local = local;
177
+ const shippedByUri = new Map((shipped?.plugins ?? []).map((p) => [p.uri, p]));
178
+ const localByUri = new Map((local?.plugins ?? []).map((p) => [p.uri, p]));
179
+ const uris = [...new Set([...shippedByUri.keys(), ...localByUri.keys()])].sort();
180
+ const merged = new Map();
181
+ for (const uri of uris) {
182
+ const shippedPlugin = shippedByUri.get(uri);
183
+ const localPlugin = localByUri.get(uri);
184
+ const identity = localPlugin ?? shippedPlugin;
185
+ if (identity === undefined)
186
+ continue;
187
+ const resolve = (dimension) => resolveDimension(dimension, local !== undefined && localPlugin !== undefined
188
+ ? { run: local.run, measurement: dimensionOf(localPlugin, dimension) }
189
+ : undefined, shipped !== undefined && shippedPlugin !== undefined
190
+ ? { run: shipped.run, measurement: dimensionOf(shippedPlugin, dimension) }
191
+ : undefined);
192
+ const latency = resolve("latency");
193
+ const cost = resolve("cost");
194
+ const stability = resolve("stability");
195
+ const rtSafety = resolve("rtSafety");
196
+ const features = resolve("features");
197
+ const failures = {};
198
+ const outcomes = { latency, cost, stability, rtSafety, features };
199
+ for (const dimension of MEASUREMENT_DIMENSIONS) {
200
+ const failure = outcomes[dimension].localFailure;
201
+ if (failure !== undefined)
202
+ failures[dimension] = failure;
203
+ }
204
+ merged.set(uri, {
205
+ uri,
206
+ plugin: identity,
207
+ ...optionalFigure("latency", latency.resolved),
208
+ ...optionalFigure("cost", cost.resolved),
209
+ ...optionalFigure("stability", stability.resolved),
210
+ ...optionalFigure("rtSafety", rtSafety.resolved),
211
+ ...optionalFigure("features", features.resolved),
212
+ ...(Object.keys(failures).length > 0 ? { localProbeFailed: failures } : {}),
213
+ ...(shippedPlugin === undefined ? { localOnly: true } : {}),
214
+ });
215
+ }
216
+ this.merged = merged;
217
+ }
218
+ /** The run behind each source, when that source has a document. */
219
+ runFor(source) {
220
+ return source === "local" ? this.local?.run : this.shipped?.run;
221
+ }
222
+ /** Every merged plugin, URI-ascending. */
223
+ all() {
224
+ return [...this.merged.values()];
225
+ }
226
+ get(uri) {
227
+ return this.merged.get(uri);
228
+ }
229
+ get size() {
230
+ return this.merged.size;
231
+ }
232
+ /**
233
+ * Where this plugin's figure for `dimension` came from, or `undefined` when there is no
234
+ * figure at all. THE question a UI asks per row, so it is one call and not a walk.
235
+ */
236
+ sourceOf(uri, dimension) {
237
+ return resolvedOf(this.merged.get(uri), dimension)?.source;
238
+ }
239
+ /** Every URI whose `dimension` figure came from `source`, URI-ascending. */
240
+ urisFrom(source, dimension) {
241
+ return this.all()
242
+ .filter((entry) => resolvedOf(entry, dimension)?.source === source)
243
+ .map((entry) => entry.uri);
244
+ }
245
+ /**
246
+ * Rates in `wanted` this plugin has NO figure for, on the dimension asked about.
247
+ *
248
+ * The honest answer to "the rig now runs at 96 kHz too": the rates the resolved figure
249
+ * does not cover are missing, and a surface should offer to measure them rather than
250
+ * project across a boundary the data says cannot be projected across.
251
+ */
252
+ missingRates(uri, dimension, wanted) {
253
+ const resolved = resolvedOf(this.merged.get(uri), dimension);
254
+ if (resolved === undefined)
255
+ return [...wanted].sort((a, b) => a - b);
256
+ const covered = new Set(resolved.rates);
257
+ return wanted.filter((rate) => !covered.has(rate)).sort((a, b) => a - b);
258
+ }
259
+ /**
260
+ * Plugins whose resolved figure does not cover every rate in `wanted`, per dimension.
261
+ * What a "your local run does not cover 96 kHz" banner counts.
262
+ */
263
+ pluginsMissingRates(dimension, wanted) {
264
+ return this.all()
265
+ .filter((entry) => {
266
+ const resolved = resolvedOf(entry, dimension);
267
+ // A plugin with nothing to measure is not missing a rate; it has a reason.
268
+ if (resolved !== undefined && resolved.value.unmeasuredReason !== undefined)
269
+ return false;
270
+ return this.missingRates(entry.uri, dimension, wanted).length > 0;
271
+ })
272
+ .map((entry) => entry.uri);
273
+ }
274
+ /** The counts a Setup screen renders. */
275
+ summary() {
276
+ const zeroes = () => ({
277
+ latency: 0,
278
+ cost: 0,
279
+ stability: 0,
280
+ rtSafety: 0,
281
+ features: 0,
282
+ });
283
+ const bySource = {
284
+ latency: { local: 0, shipped: 0 },
285
+ cost: { local: 0, shipped: 0 },
286
+ stability: { local: 0, shipped: 0 },
287
+ rtSafety: { local: 0, shipped: 0 },
288
+ features: { local: 0, shipped: 0 },
289
+ };
290
+ const unresolved = zeroes();
291
+ const localProbeFailures = zeroes();
292
+ let localOnly = 0;
293
+ for (const entry of this.merged.values()) {
294
+ if (entry.localOnly === true)
295
+ localOnly += 1;
296
+ for (const dimension of MEASUREMENT_DIMENSIONS) {
297
+ const resolved = resolvedOf(entry, dimension);
298
+ if (resolved === undefined)
299
+ unresolved[dimension] += 1;
300
+ else
301
+ bySource[dimension][resolved.source] += 1;
302
+ if (entry.localProbeFailed?.[dimension] !== undefined)
303
+ localProbeFailures[dimension] += 1;
304
+ }
305
+ }
306
+ return { plugins: this.merged.size, bySource, unresolved, localOnly, localProbeFailures };
307
+ }
308
+ }
309
+ /** Convenience over the constructor, for call sites that read better as a function. */
310
+ export function mergeMeasurements(shipped, local) {
311
+ return new MergedMeasurements(shipped, local);
312
+ }
313
+ /** Re-exported so a consumer of the merge does not also have to import the format. */
314
+ export { dimensionOf as measurementForDimension };
315
+ //# sourceMappingURL=merge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge.js","sourceRoot":"","sources":["../../src/measurement/merge.ts"],"names":[],"mappings":"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"}