From 4e26225d56ad47453163c5a3866ed20ebeaecceb Mon Sep 17 00:00:00 2001 From: Pau Aliagas Date: Fri, 16 Oct 2026 09:15:07 +0200 Subject: [PATCH] Restate private citations as facts and refuse them in CI The native sources, a test name, two header comments and the crash signature notes cited a private issue, a private design path and dated specification file names. Each now states what the value is and why. A private-refs check (tools/private-refs-check.sh, with a self-test that turns each kind of pointer red in each kind of file) runs in CI so they stay out. --- .github/workflows/ci.yml | 5 ++ bin/plugin-qualify.mjs | 2 +- native/Makefile | 2 +- native/clap-qualify.c | 2 +- native/fixtures/gain.c | 2 +- .../host-faithfulness.ttl | 2 +- native/fixtures/lv2-host-faithfulness.c | 2 +- native/lv2-measure-pin.h | 6 +- native/lv2-measure.c | 2 +- package.json | 3 +- src/clap-qualify.test.ts | 2 +- src/hosting-codes.ts | 8 +- src/hosting-suitability.test.ts | 4 +- src/hosting-suitability.ts | 16 ++-- src/measure-host.ts | 4 +- src/measurement/format.ts | 2 +- src/measurement/vocabulary.ts | 2 +- src/qualify-declarations.ts | 2 +- tools/benchmark.py | 2 +- tools/private-refs-check.sh | 90 +++++++++++++++++++ 20 files changed, 128 insertions(+), 32 deletions(-) create mode 100755 tools/private-refs-check.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb37e12..0128e79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/bin/plugin-qualify.mjs b/bin/plugin-qualify.mjs index d101412..4add868 100755 --- a/bin/plugin-qualify.mjs +++ b/bin/plugin-qualify.mjs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later // Copyright (C) 2026 Pau Aliagas /** - * `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 diff --git a/native/Makefile b/native/Makefile index b1fdfc1..80d58d0 100644 --- a/native/Makefile +++ b/native/Makefile @@ -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 $@ $< diff --git a/native/clap-qualify.c b/native/clap-qualify.c index 7dc2c0c..0f3c034 100644 --- a/native/clap-qualify.c +++ b/native/clap-qualify.c @@ -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 diff --git a/native/fixtures/gain.c b/native/fixtures/gain.c index faca8ce..cd6b4aa 100644 --- a/native/fixtures/gain.c +++ b/native/fixtures/gain.c @@ -12,7 +12,7 @@ * fails. * leaky-delay.clap built with -DFIXTURE_DELAY=, 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. * diff --git a/native/fixtures/host-faithfulness.lv2/host-faithfulness.ttl b/native/fixtures/host-faithfulness.lv2/host-faithfulness.ttl index f89f8ef..f62bc32 100644 --- a/native/fixtures/host-faithfulness.lv2/host-faithfulness.ttl +++ b/native/fixtures/host-faithfulness.lv2/host-faithfulness.ttl @@ -8,7 +8,7 @@ @prefix urid: . @prefix work: . -# 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. a lv2:Plugin , lv2:UtilityPlugin ; diff --git a/native/fixtures/lv2-host-faithfulness.c b/native/fixtures/lv2-host-faithfulness.c index 8cfa297..48679c4 100644 --- a/native/fixtures/lv2-host-faithfulness.c +++ b/native/fixtures/lv2-host-faithfulness.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later // Copyright (C) 2026 Pau Aliagas -/* 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 diff --git a/native/lv2-measure-pin.h b/native/lv2-measure-pin.h index 0a78ca9..b730c92 100644 --- a/native/lv2-measure-pin.h +++ b/native/lv2-measure-pin.h @@ -1,13 +1,13 @@ // SPDX-License-Identifier: GPL-3.0-or-later // Copyright (C) 2026 Pau Aliagas /* - * 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. diff --git a/native/lv2-measure.c b/native/lv2-measure.c index 47c8c0b..2ed0aee 100644 --- a/native/lv2-measure.c +++ b/native/lv2-measure.c @@ -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 diff --git a/package.json b/package.json index 60f7acc..62c8d0b 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/clap-qualify.test.ts b/src/clap-qualify.test.ts index 1b4880b..34bbefb 100644 --- a/src/clap-qualify.test.ts +++ b/src/clap-qualify.test.ts @@ -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; diff --git a/src/hosting-codes.ts b/src/hosting-codes.ts index beeed2b..d4c7203 100644 --- a/src/hosting-codes.ts +++ b/src/hosting-codes.ts @@ -1,10 +1,10 @@ // SPDX-License-Identifier: GPL-3.0-or-later // Copyright (C) 2026 Pau Aliagas /** - * 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. @@ -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', @@ -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; diff --git a/src/hosting-suitability.test.ts b/src/hosting-suitability.test.ts index fcf9ae0..e1a7551 100644 --- a/src/hosting-suitability.test.ts +++ b/src/hosting-suitability.test.ts @@ -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 @@ -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. */ diff --git a/src/hosting-suitability.ts b/src/hosting-suitability.ts index b310e1c..7221708 100644 --- a/src/hosting-suitability.ts +++ b/src/hosting-suitability.ts @@ -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[] = [ @@ -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", }, ]; @@ -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" @@ -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; } diff --git a/src/measure-host.ts b/src/measure-host.ts index 567c8d0..adf5ee7 100644 --- a/src/measure-host.ts +++ b/src/measure-host.ts @@ -1,8 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-or-later // Copyright (C) 2026 Pau Aliagas /** - * 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"; diff --git a/src/measurement/format.ts b/src/measurement/format.ts index f99099d..dea9d93 100644 --- a/src/measurement/format.ts +++ b/src/measurement/format.ts @@ -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. */ diff --git a/src/measurement/vocabulary.ts b/src/measurement/vocabulary.ts index 7f1e129..ce57c5d 100644 --- a/src/measurement/vocabulary.ts +++ b/src/measurement/vocabulary.ts @@ -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]; diff --git a/src/qualify-declarations.ts b/src/qualify-declarations.ts index 744e26a..d44a8d9 100644 --- a/src/qualify-declarations.ts +++ b/src/qualify-declarations.ts @@ -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 }; diff --git a/tools/benchmark.py b/tools/benchmark.py index aad06d5..90f7874 100644 --- a/tools/benchmark.py +++ b/tools/benchmark.py @@ -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 diff --git a/tools/private-refs-check.sh b/tools/private-refs-check.sh new file mode 100755 index 0000000..7bcc071 --- /dev/null +++ b/tools/private-refs-check.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: GPL-3.0-or-later +# Copyright (C) 2026 Pau Aliagas +# +# private-refs-check.sh — this repository is public, so its tracked files state facts and never point +# into a private repository: no private repository name, no private issue or pull request number, no +# private branch name or file path, no name of a private specification file. Say what the value is and +# why instead, and cite only public repositories (omx-dsp, omx-contract, omx-plugins, omx-clap-core, plugin-qualify). +# Product-name mentions of OpenMixer, and values such as the org.openmixer.* CLAP ids, the +# urn:openmixer:* LV2 URIs and the @openmixer npm scope, are not pointers and are not matched. +# +# tools/private-refs-check.sh the check, over every tracked file (generated ones included) +# tools/private-refs-check.sh --self-test each sabotage, in every kind of file, must go red +# +# ALLOW lists the tracked files exempt from the check, each with the reason; nothing else is. +set -euo pipefail +HERE="$(cd "$(dirname "$0")/.." && pwd)" + +# — +ALLOW=( + "tools/private-refs-check.sh" # it spells the patterns it looks for +) +EXTRA_PATHSPEC=() # the self-test sets a bad one, to prove a git grep error is red + +# Extended regexps, matched case-insensitively. A new kind of pointer is one line here. +PATTERNS=( + 'FreeMixer/openmixer([^A-Za-z0-9_-]|$)' # the private repository + 'openmixer-ops' # the private operations repository + 'openmixer ?#[0-9]' # one of its issues or pull requests + '[A-Za-z0-9_.]-dev#[0-9]' # an issue of a private *-dev repository + 'integration/[A-Za-z0-9]' # a private integration branch + 'packages/[a-z][a-z0-9-]+' # a path into the private repository's packages + '\.vue([^A-Za-z0-9_]|$)' # its web UI components + "openmixer's spec" # its specification files + 'docs/design/' # its design documents + 'harness/' # its test harness + '20[0-9]{2}-[0-9]{2}-[0-9]{2}-[a-z][a-z0-9-]+\.md' # a dated specification file name + 'spec 20[0-9]{2}-[0-9]{2}-[0-9]{2}' # a citation of one +) + +check() { # + local root="$1" fail=0 args=() p a hits + for p in "${PATTERNS[@]}"; do args+=(-e "$p"); done + local excl=() + for a in "${ALLOW[@]}"; do excl+=(":(exclude)${a%% *}"); done + [ -n "$(git -C "$root" ls-files)" ] || { echo "private-refs-check: git lists no file" >&2; return 1; } + # git grep exits 1 for no match and 128 or more for an error; an error must not read as "no hits" + hits=$(git -C "$root" grep -nIiE "${args[@]}" -- . "${excl[@]}" "${EXTRA_PATHSPEC[@]}") || rc=$? + case "${rc:-0}" in 0|1) ;; *) echo "private-refs-check: git grep failed (exit $rc)" >&2; return 1 ;; esac + hits=$(printf '%s' "$hits" | cut -c1-200) + [ -z "$hits" ] || { printf '%s\n' "$hits" >&2; fail=1; } + [ "$fail" = 0 ] || { echo "private-refs-check: the lines above point into private repositories; state the fact instead" >&2; return 1; } + echo "private-refs-check: no tracked file points into a private repository" +} + +if [ "${1:-}" = --self-test ]; then + scratch="$(mktemp -d)"; trap 'rm -rf "$scratch"' EXIT + fail=0 + # a clean tree with one file of each kind the repository tracks; every sabotage writes one private pointer into one + mk() { rm -rf "$scratch/t"; mkdir -p "$scratch/t/src" "$scratch/t/tools" "$scratch/t/docs" + git -C "$scratch" init -q t + printf 'rdfs:comment "The OpenMixer console x." ;\nlv2:binary ; # urn:openmixer:x\n' > "$scratch/t/src/x.ttl" + printf '// org.openmixer.x, @openmixer/x\n' > "$scratch/t/tools/gen.mjs" + printf '# Docs\n\nOpenMixer plugins.\n' > "$scratch/t/docs/a.md" + printf '/* x */\n' > "$scratch/t/src/x.h"; printf '#!/bin/sh\n# x\n' > "$scratch/t/tools/x.sh"; } + sab() { # + mk; printf '%s\n' "$3" >> "$scratch/t/$2"; git -C "$scratch/t" add -A + if check "$scratch/t" >/dev/null 2>&1; then echo "SABOTAGE $1: STAYED GREEN"; fail=1; else echo "SABOTAGE $1: red"; fi; } + mk; git -C "$scratch/t" add -A + check "$scratch/t" >/dev/null || { echo "self-test: the clean tree is not green" >&2; exit 1; } + sab repo-in-header src/x.h '/* as FreeMixer/openmixer draws it */' + sab branch-in-sh tools/x.sh '# drawn on integration/lean-engine' + sab issue-in-md docs/a.md 'see openmixer#1580' + sab path-in-md docs/a.md 'packages/web-ui/Foo.vue' + sab default-path tools/x.sh 'plugin=${P:-$root/../openmixer/packages/omx-plugins/bin/omx-delay.clap}' + sab ops-in-mjs tools/gen.mjs '// see openmixer-ops' + sab dev-in-mjs tools/gen.mjs '// omx-dsp-dev#32' + sab spec-in-mjs tools/gen.mjs "// openmixer's spec 2026-10-09-plugin-from-contract.md" + sab ttl src/x.ttl 'rdfs:comment "see FreeMixer/openmixer#12" ;' + sab header src/x.h '/* packages/core/src/x.ts */' + sab design-dir docs/a.md 'the notes in docs/design/specs' + sab harness-dir tools/gen.mjs '// harness/dsp-check.sh' + mk; git -C "$scratch/t" add -A + if ( EXTRA_PATHSPEC=(":(bogus)x"); check "$scratch/t" >/dev/null 2>&1 ); then echo "SABOTAGE git-grep-error: STAYED GREEN"; fail=1; else echo "SABOTAGE git-grep-error: red"; fi + [ "$fail" = 0 ] || exit 1 + check "$HERE" >/dev/null || { echo "self-test: the repository is not green" >&2; exit 1; } + echo "private-refs-check self-test: every sabotage red" + exit 0 +fi +check "$HERE"