Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ jobs:
- name: Install and build
run: pnpm install --frozen-lockfile

# a public repository states facts and points into no private repository; the self-test turns
# each kind of pointer red in each kind of file
- name: No tracked file points into a private repository
run: pnpm run check:private-refs

# the CLAP prober, the LV2 measurer, their interposer and the fixture plugins the tests qualify;
# deps/ also holds omx-clap-core's LV2 feature provider at the packages' floor, built for the measurer to link
# asan: the measurer under AddressSanitizer, which the leak check runs
Expand Down
2 changes: 1 addition & 1 deletion bin/plugin-qualify.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
/**
* `plugin-qualify` — the entry point (spec 2026-09-25-plugin-qualify.md §5). Every decision is a
* `plugin-qualify` — the entry point. Every decision is a
* pure function in `src/qualify-cli.ts`; this file only reads argv, prints and exits.
*
* CLAP targets are qualified here: the prober (`native/clap-qualify`) runs each under the interposer
Expand Down
2 changes: 1 addition & 1 deletion native/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ $(BUILD)/fixtures/params-%.clap: fixtures/gain.c | $(BUILD)/fixtures
$(BUILD)/fixtures/info-fails.clap: fixtures/gain.c | $(BUILD)/fixtures
$(CC) $(CPPFLAGS) $(CFLAGS) $(CLAP_CFLAGS) -DFIXTURE_INFO_FAILS -shared -fPIC -fvisibility=hidden $(LDFLAGS) -o $@ $<

# a whole-frame delay read the way omx-pitch lands it at 44.1 kHz (openmixer#1538), declared and not
# a whole-frame delay read the way omx-pitch lands it at 44.1 kHz, declared and not
FIXTURE_DELAY = 883
$(BUILD)/fixtures/leaky-delay.clap: fixtures/gain.c | $(BUILD)/fixtures
$(CC) $(CPPFLAGS) $(CFLAGS) $(CLAP_CFLAGS) -DFIXTURE_DELAY=$(FIXTURE_DELAY) -shared -fPIC -fvisibility=hidden $(LDFLAGS) -o $@ $<
Expand Down
2 changes: 1 addition & 1 deletion native/clap-qualify.c
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ _Static_assert(HOST_PARAM_COUNT_MAX <= MAX_PARAMS, "the prober reads every param
/* the impulse's arrival is the first frame at or above this fraction of the response's own peak
* magnitude: an absolute floor as low as SILENCE reads the leakage a fractional-delay read puts one
* frame ahead of the main arrival (omx-pitch at 44.1 kHz: -2.1e-5 at 882, 1.03 at 883) as the onset,
* and refuses a plugin that declares its true delay by one frame (openmixer#1538). -60 dB below the
* and refuses a plugin that declares its true delay by one frame. -60 dB below the
* peak sits far above that leakage and far below any arrival a plugin means. */
#define ONSET_OF_PEAK 1e-3f

Expand Down
2 changes: 1 addition & 1 deletion native/fixtures/gain.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* fails.
* leaky-delay.clap built with -DFIXTURE_DELAY=<d>, the gain delayed by d frames the way a
* fractional-delay read lands a whole-frame delay: the arrival at d, and a leak of
* FIXTURE_LEAK one frame ahead of it (omx-pitch's tap at 44.1 kHz, openmixer#1538).
* FIXTURE_LEAK one frame ahead of it (omx-pitch's tap at 44.1 kHz).
* It declares d through the latency extension.
* undeclared-delay.clap the same, built with -DFIXTURE_UNDECLARED too: it declares no latency.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
@prefix work: <http://lv2plug.in/ns/ext/worker#> .

# Measuring-host fixtures (2026-09-25-plugin-qualify.md §2a): each plugin
# Measuring-host fixtures: each plugin
# refuses to work unless the host is faithful in ONE way, and says so in the audio it produces.

<urn:plugin-qualify:fixture:needs-options> a lv2:Plugin , lv2:UtilityPlugin ;
Expand Down
2 changes: 1 addition & 1 deletion native/fixtures/lv2-host-faithfulness.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
/* Measuring-host fixtures (spec 2026-09-25-plugin-qualify.md §2a). Five plugins, each faithful
/* Measuring-host fixtures. Five plugins, each faithful
* to ONE thing a real plugin demands of its host and unable to hide when the host is not:
* needs-options instantiate FAILS without the sampleRate and maxBlockLength options (JUCE)
* worker-engine output is -6 dB once its work() ran and work_response() was delivered
Expand Down
6 changes: 3 additions & 3 deletions native/lv2-measure-pin.h
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
/*
* lv2-measure-pin.h — the pin `lv2-measure` records for an LV2 plugin
* (docs/design/specs/2026-09-25-plugin-qualify.md §11; lv2-measurement-interchange.md §3c).
* lv2-measure-pin.h — the pin `lv2-measure` records for an LV2 plugin, in the measurement record's
* `integrity` object.
*
* The files: the bundle's `manifest.ttl`, the shared object `lv2:binary` names and every file
* `rdfs:seeAlso` names for the plugin, each by its path relative to the bundle root, hashed over one
* open descriptor. The layout: every control port, input and output, as the loaded world describes
* it, in plugin-hostd's omx-layout/1. Both are plugin-hostd's `pin.h` (one-contract §10.2): this
* it, in plugin-hostd's omx-layout/1. Both are plugin-hostd's `pin.h` records: this
* unit only fills its records from lilv. A plugin whose files cannot be pinned (a path holding a
* separator of `pin_set` or whitespace, a manifest using `@base`, a percent-encoded IRI) prints a
* reason and no pin, never a partial one.
Expand Down
2 changes: 1 addition & 1 deletion native/lv2-measure.c
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
*/
/*
* THE MEASURING HOST IS THE HOST THAT WILL RUN THE PLUGIN
* (`2026-09-25-plugin-qualify.md` §2a). A figure is only as true as the host is faithful, so
* A figure is only as true as the host is faithful, so
* this offers a plugin what a real host does:
*
* options the provider's table at the rate and block of each instance. An EMPTY
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@
"test": "vitest run src",
"lint": "tsc -p tsconfig.lint.json",
"test:py": "python3 -m pytest tools -q",
"check:generated": "node tools/gen-python-declarations.mjs --check"
"check:generated": "node tools/gen-python-declarations.mjs --check",
"check:private-refs": "tools/private-refs-check.sh && tools/private-refs-check.sh --self-test"
},
"devDependencies": {
"@types/node": "^24.9.0",
Expand Down
2 changes: 1 addition & 1 deletion src/clap-qualify.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ describe("clap-qualify, live — the known RT-safe and RT-unsafe fixture plugins
expect(doc.measured.rtSafety.events).toContain("malloc");
});

live("the arrival is read against the response's peak: a leak one frame ahead of a declared delay is not its onset (openmixer#1538)", () => {
live("the arrival is read against the response's peak: a leak one frame ahead of a declared delay is not its onset", () => {
// 883 frames, with -2.1e-5 at 882: what omx-pitch's fractional read puts out at 44.1 kHz
const [l] = qualifyClap(join(FIXTURES, "leaky-delay.clap"), undefined, { rates: QUALIFY_RATES });
const latency = rtSafetyDocument(l!).measured.clap.latency;
Expand Down
8 changes: 4 additions & 4 deletions src/hosting-codes.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
/**
* The hosting verdict's message codes (2026-09-04-lv2-hosting-path.md). Codes, never prose: the
* The hosting verdict's message codes. Codes, never prose: the
* surface renders the operator's locale.
*
* A UNION list file (2026-09-15-no-stored-derivations.md §9): `.gitattributes` marks it
* A UNION list file: `.gitattributes` marks it
* `merge=union`, so two lanes that each add a code merge with both kept. Only the table lives
* here, one `key: 'code',` per line, with keys and codes unique; the registry-hotspots gate
* refuses anything else.
Expand Down Expand Up @@ -48,7 +48,7 @@ export const HOSTING_CODE = {
topologyNoAudioOutput: 'hosting.topology.no-audio-output',
topologyExtraInputs: 'hosting.topology.extra-inputs-fed-silence',
topologyInstrument: 'hosting.topology.instrument',
// The CLAP arm (2026-09-26-clap-hosting-path.md §3): the SAME dimensions fed from CLAP facts,
// The CLAP arm: the SAME dimensions fed from CLAP facts,
// minted here where the LV2 table has no code for the fact.
topologyWiderThanStrip: 'hosting.topology.wider-than-strip',
clapLatencyMismatch: 'hosting.clap.latency-mismatch',
Expand Down Expand Up @@ -76,6 +76,6 @@ export const HOSTING_CODE = {
noRealisation: 'hosting.no-realisation',
realisedInProcess: 'hosting.realised.in-process',
// A plugin LINKED into the engine (the static-link registry) runs in-process without a
// verdict: the library's oracles are its proof (`2026-09-26-clap-hosting-path.md` §7).
// verdict: the library's oracles are its proof.
linked: 'hosting.linked',
} as const;
4 changes: 2 additions & 2 deletions src/hosting-suitability.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ describe("an advisory reason may never disqualify", () => {
});

/*
* THE CLAP CELLS (2026-09-26-clap-hosting-path.md §3, §11.4): the SAME verdict over CLAP facts,
* THE CLAP CELLS: the SAME verdict over CLAP facts,
* one assertion per cell BY VALUE — rating, code and params. The positive control comes first:
* a descriptor carrying measurements that EARN in-process. Everything after it is what does
* NOT, and the first of those is the one that matters most: a `.clap` nobody qualified reads
Expand Down Expand Up @@ -741,7 +741,7 @@ describe("the CLAP arm — the §3 verdict over CLAP facts", () => {
});

/**
* HOST PROFILES (`2026-09-25-plugin-qualify.md` §3a): the SAME measurements, judged against a
* HOST PROFILES: the SAME measurements, judged against a
* declared host. Each cell holds the plugin fixed and moves only the profile, so the difference
* in the verdict is the profile's and nothing else's.
*/
Expand Down
16 changes: 8 additions & 8 deletions src/hosting-suitability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,12 +199,12 @@ export interface CrashSignature {
/** The top plugin-owned stack frame, or a host frame that exonerates the plugin. */
readonly topFrame: string;
readonly signal?: "SIGSEGV" | "SIGABRT" | "SIGBUS";
/** The note that recorded this cluster. Provenance, so a row is never folklore. */
/** What was found about this cluster. Provenance, so a row is never folklore. */
readonly note: string;
}

/**
* Seeded from the crash notes of 2026-09-03/04. A raw crash matching NO row is
* Seeded from the crashes investigated on 2026-09-03/04. A raw crash matching NO row is
* `unknown` — the table blames a plugin only where a named investigation did.
*/
export const CRASH_SIGNATURES: readonly CrashSignature[] = [
Expand All @@ -213,26 +213,26 @@ export const CRASH_SIGNATURES: readonly CrashSignature[] = [
attribution: "host",
topFrame: "pw_memmap_free",
signal: "SIGSEGV",
note: "2026-09-04-pipewire-jack-client-close-double-free.md",
note: "closing a PipeWire JACK client frees a memory map twice; the host crashes, not the plugin",
},
{
id: "pipewire-jack-close-double-free-abort",
attribution: "host",
topFrame: "tcache_double_free_verify",
signal: "SIGABRT",
note: "2026-09-04-pipewire-jack-client-close-double-free.md",
note: "the same double free in PipeWire's JACK client close, caught by glibc's tcache check as an abort",
},
{
id: "mod-host-effects-remove",
attribution: "host",
topFrame: "effects_remove",
note: "2026-09-03-mod-host-crash-cluster2-3-investigation.md",
note: "mod-host crashes in effects_remove while removing an effect; the host is at fault",
},
{
id: "plugin-instantiate-layout",
attribution: "plugin",
topFrame: "lilv_instance_instantiate",
note: "2026-09-03-mod-host-crash-cluster2-3-investigation.md",
note: "the plugin crashes while lilv instantiates it, on a port layout it cannot host",
},
];

Expand Down Expand Up @@ -958,7 +958,7 @@ export function classifyPluginHosting(
// interchange's one door (`measurement-catalog.ts`), whose per-dimension precedence is the
// only precedence there is. A second "passed measurements win" seam here was how an offline
// report could agree with itself while every served descriptor read unknown.
// The CLAP arm (2026-09-26-clap-hosting-path.md §3): the SAME six dimensions, fed from the
// The CLAP arm: the SAME six dimensions, fed from the
// qualifier's CLAP facts where the LV2 scan's facts do not exist for the format. Stability,
// the interposer's counts and the cost are format-blind and read unchanged.
const reasons: HostingReason[] = descriptor.format === "clap"
Expand Down Expand Up @@ -1041,7 +1041,7 @@ export interface HostingRealisation {
readonly inProcessAvailable: boolean;
/**
* Whether the plugin is LINKED into the engine (its static-link registry): it needs no verdict
* to run in-process (`2026-09-26-clap-hosting-path.md` §7). Absent reads as not linked.
* to run in-process. Absent reads as not linked.
*/
readonly linked?: boolean;
}
Expand Down
4 changes: 2 additions & 2 deletions src/measure-host.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
/**
* Reading what the measuring host (`lv2_measure`) prints and supervising how long it may take
* (`2026-09-25-plugin-qualify.md` §2a). Pure functions: the runner spawns, these decide.
* Reading what the measuring host (`lv2_measure`) prints and supervising how long it may take.
* Pure functions: the runner spawns, these decide.
*/
import { isRecord } from "./measurement/guards.js";
import { SOAK_SWEEP_DB, SOAK_SWEEP_RATIO, SOAK_TIMEOUT_FLOOR_S, SOAK_TIMEOUT_MARGIN } from "./qualify-declarations.js";
Expand Down
2 changes: 1 addition & 1 deletion src/measurement/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ export interface HostBudget {
}

/**
* A HOST PROFILE (`2026-09-25-plugin-qualify.md` §3a; §3b here): the host a verdict was judged
* A HOST PROFILE: the host a verdict was judged
* against — only what differs between hosts. A document carries every profile its verdicts name,
* in full, so a verdict against a project's own profile is reproducible from the document alone.
*/
Expand Down
2 changes: 1 addition & 1 deletion src/measurement/vocabulary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ export function readDimension(value: unknown): MeasurementDimension | undefined
export const VERDICT_RATINGS = ["suitable", "conditional", "unknown", "unsuitable"] as const;
export type VerdictRating = (typeof VERDICT_RATINGS)[number];

/** How a host runs its plugins (`2026-09-25-plugin-qualify.md` §3a). */
/** How a host runs its plugins. */
export const HOST_ISOLATIONS = ["in-process", "per-process", "shared-process"] as const;
export type HostIsolation = (typeof HOST_ISOLATIONS)[number];

Expand Down
2 changes: 1 addition & 1 deletion src/qualify-declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export const DEFAULT_HOSTING_POLICY: HostingPolicy = {
soakDeadDropDb: 40,
};

/** What a host spends on one plugin instance (`2026-09-25-plugin-qualify.md` §3a) — the
/** What a host spends on one plugin instance — the
* interchange's shape, since a document carries each profile's budget. */
import type { HostBudget } from "./measurement/format.js";
export type { HostBudget };
Expand Down
2 changes: 1 addition & 1 deletion tools/benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -1095,7 +1095,7 @@ def annotate_measured(catalog, measured, unmeasurable, sample_rate, tol=2):
class WorkerQueue:
"""The host half of `worker:schedule`: jobs are QUEUED, and run between blocks, off `run()`.

Spec 2a (`2026-09-25-plugin-qualify.md`): a schedule that accepts a job and
A schedule that accepts a job and
drops it measures an UNBUILT plugin — the Hex Chain amp read 0.9 us/sample that way against 5.4
built. After every block the host calls `pump(interface)`: the plugin's `work()` for each
queued job, then `work_response()` with whatever it answered, then `end_run()` once every answer
Expand Down
Loading
Loading