feat(audio): lease registry — exclusive leases + refcounted demands

Phase A of docs/audio-ownership-plan.md: scopes, refusal-by-default,
drain-then-grant takeover (§8.1), reload grace window (§8.2), user-stop
suspend semantics (§8.3), well-known internal holders (§8.4), layered
base+override values (§8.10), snapshot with holder ages (§8.7).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
OmikronApex
2026-07-14 22:05:51 +02:00
co-authored by Claude Fable 5
parent 8da14b598a
commit f5276b787c
2 changed files with 696 additions and 0 deletions
+431
View File
@@ -0,0 +1,431 @@
import { EventEmitter } from 'events';
// Lease registry — ownership arbitration for audio scopes (plan: docs/audio-ownership-plan.md §2/§8).
//
// Two primitives:
// - exclusive leases (signal-chain:<route>, device-config, playback, ...)
// - refcounted demands (capture, detection:<route>) — additive intent,
// the engine acts while the holder count > 0.
//
// The registry never derives identity itself: the IPC wiring derives holder
// ids from the sender (webContents id + attributed plugin id, plan §9) and
// main-process callers use the WELL_KNOWN_HOLDERS enum (§8.4). Native stays
// dumb — per-route chainGeneration remains the tamper-evident layer below.
export type HolderId = string;
export const WELL_KNOWN_HOLDERS = Object.freeze({
backingPlayer: 'engine:backing-player',
startupRestore: 'main:startup-restore',
executor: 'main:executor',
deviceScreen: 'main:device-screen',
});
const WELL_KNOWN_SET = new Set<string>(Object.values(WELL_KNOWN_HOLDERS));
export type LeaseScope = string;
const EXCLUSIVE_SCOPE_RE = /^(device-config|playback|monitor-state|signal-chain:[\w.-]+|mixer-channel:[\w.-]+)$/;
const DEMAND_SCOPE_RE = /^(capture|detection:[\w.-]+)$/;
export type AcquireResult =
| { granted: true; scope: LeaseScope; holderId: HolderId }
| { granted: false; scope: LeaseScope; reason: AcquireRefusal; holderId: HolderId | null };
export type AcquireRefusal = 'held' | 'invalid-scope' | 'invalid-holder' | 'revoking';
export type ReleaseReason = 'released' | 'revoked' | 'holder-destroyed' | 'grace-expired';
type Lease = {
holderId: HolderId;
acquiredAt: number;
// Set while a takeover drain is running: refuses acquires and the old
// holder's further writes until the drain completes (§8.1).
revoking: boolean;
overrides: Map<string, unknown>;
};
type Demand = {
// Idempotent per holder: one holder counts once no matter how many times
// it asks (the five hand-rolled "did *I* start it?" hacks collapse here).
holders: Map<HolderId, { since: number }>;
// User raw-stop suspends demands instead of clearing them (§8.3); only a
// user start resumes.
suspended: boolean;
};
type GraceEntry = {
identityKey: string;
leases: Map<LeaseScope, Lease>;
demands: Map<LeaseScope, { since: number }>;
timer: NodeJS.Timeout;
expiresAt: number;
};
export type LeaseRegistryOptions = {
graceMs?: number;
now?: () => number;
};
export type LeaseSnapshotEntry = {
scope: LeaseScope;
holderId: HolderId;
heldMs: number;
revoking: boolean;
overrides: string[];
};
export type DemandSnapshotEntry = {
scope: LeaseScope;
suspended: boolean;
holders: Array<{ holderId: HolderId; heldMs: number }>;
};
export function isValidExclusiveScope(scope: string): boolean {
return EXCLUSIVE_SCOPE_RE.test(scope);
}
export function isValidDemandScope(scope: string): boolean {
return DEMAND_SCOPE_RE.test(scope);
}
export function isWellKnownHolder(holderId: string): boolean {
return WELL_KNOWN_SET.has(holderId);
}
function isValidHolder(holderId: unknown): holderId is HolderId {
if (typeof holderId !== 'string' || holderId.length === 0 || holderId.length > 256) return false;
if (WELL_KNOWN_SET.has(holderId)) return true;
// Derived renderer identity: "wc:<webContentsId>" with optional attributed
// plugin suffix "#<pluginId>" (soft part, plan §9).
return /^wc:\d+(#[\w.-]{1,128})?$/.test(holderId);
}
export class LeaseRegistry extends EventEmitter {
private leases = new Map<LeaseScope, Lease>();
private demands = new Map<LeaseScope, Demand>();
private baseValues = new Map<LeaseScope, Map<string, unknown>>();
private graces = new Map<HolderId, GraceEntry>();
private graceMs: number;
private now: () => number;
constructor(options: LeaseRegistryOptions = {}) {
super();
this.graceMs = options.graceMs ?? 8000;
this.now = options.now ?? Date.now;
}
// ---- exclusive leases -------------------------------------------------
acquire(scope: LeaseScope, holderId: HolderId): AcquireResult {
if (!isValidExclusiveScope(scope)) {
return this.refuse(scope, 'invalid-scope', null);
}
if (!isValidHolder(holderId)) {
return this.refuse(scope, 'invalid-holder', null);
}
const existing = this.leases.get(scope);
if (existing) {
if (existing.holderId === holderId) {
return { granted: true, scope, holderId };
}
return this.refuse(scope, existing.revoking ? 'revoking' : 'held', existing.holderId);
}
this.leases.set(scope, { holderId, acquiredAt: this.now(), revoking: false, overrides: new Map() });
this.emit('lease-granted', { scope, holderId });
return { granted: true, scope, holderId };
}
release(scope: LeaseScope, holderId: HolderId): boolean {
const lease = this.leases.get(scope);
if (!lease || lease.holderId !== holderId) return false;
this.dropLease(scope, lease, 'released');
return true;
}
getHolder(scope: LeaseScope): HolderId | null {
return this.leases.get(scope)?.holderId ?? null;
}
// May the holder write to the scope right now? False while a takeover
// drain is in flight (the serializer stops accepting the old holder's
// ops the moment revocation starts, §8.1).
canWrite(scope: LeaseScope, holderId: HolderId): boolean {
const lease = this.leases.get(scope);
return !!lease && lease.holderId === holderId && !lease.revoking;
}
// User-initiated takeover (§2 policy + §8.1 drain-then-grant). `drain`
// is supplied by the wiring layer (executor serializer drain); the new
// holder is granted only after it resolves.
async takeover(scope: LeaseScope, newHolderId: HolderId, drain?: () => Promise<void>): Promise<AcquireResult> {
if (!isValidExclusiveScope(scope)) return this.refuse(scope, 'invalid-scope', null);
if (!isValidHolder(newHolderId)) return this.refuse(scope, 'invalid-holder', null);
const lease = this.leases.get(scope);
if (!lease) return this.acquire(scope, newHolderId);
if (lease.holderId === newHolderId) return { granted: true, scope, holderId: newHolderId };
if (lease.revoking) return this.refuse(scope, 'revoking', lease.holderId);
lease.revoking = true;
this.emit('lease-revoked', { scope, holderId: lease.holderId, takenOverBy: newHolderId });
try {
if (drain) await drain();
} finally {
// The old lease dies even if the drain failed — a wedged drain
// must not brick the scope (§2: never brick until restart).
this.dropLease(scope, lease, 'revoked');
}
return this.acquire(scope, newHolderId);
}
// ---- refcounted demands ------------------------------------------------
acquireDemand(scope: LeaseScope, holderId: HolderId): boolean {
if (!isValidDemandScope(scope) || !isValidHolder(holderId)) return false;
let demand = this.demands.get(scope);
if (!demand) {
demand = { holders: new Map(), suspended: false };
this.demands.set(scope, demand);
}
if (!demand.holders.has(holderId)) {
demand.holders.set(holderId, { since: this.now() });
this.emitDemandChanged(scope, demand);
}
return true;
}
releaseDemand(scope: LeaseScope, holderId: HolderId): boolean {
const demand = this.demands.get(scope);
if (!demand || !demand.holders.delete(holderId)) return false;
this.emitDemandChanged(scope, demand);
return true;
}
demandCount(scope: LeaseScope): number {
return this.demands.get(scope)?.holders.size ?? 0;
}
// Effective demand: what the engine should act on. Zero while suspended.
demandActive(scope: LeaseScope): boolean {
const demand = this.demands.get(scope);
return !!demand && !demand.suspended && demand.holders.size > 0;
}
// User raw-stop (§8.3): suspend every demand scope matching the prefix
// ('capture', or '' for all). Holders keep their registration.
suspendDemands(scopePrefix = ''): void {
for (const [scope, demand] of this.demands) {
if (!scope.startsWith(scopePrefix) || demand.suspended) continue;
demand.suspended = true;
this.emit('demand-suspended', { scope });
this.emitDemandChanged(scope, demand);
}
}
resumeDemands(scopePrefix = ''): void {
for (const [scope, demand] of this.demands) {
if (!scope.startsWith(scopePrefix) || !demand.suspended) continue;
demand.suspended = false;
this.emit('demand-resumed', { scope });
this.emitDemandChanged(scope, demand);
}
}
// ---- layered values (base + override, §2) -------------------------------
setBase(scope: LeaseScope, key: string, value: unknown): void {
let map = this.baseValues.get(scope);
if (!map) {
map = new Map();
this.baseValues.set(scope, map);
}
map.set(key, value);
// Base writes are always accepted, even during an override (§8.10) —
// they persist and re-apply on release.
this.emitValueChanged(scope, key);
}
setOverride(scope: LeaseScope, key: string, value: unknown, holderId: HolderId): boolean {
const lease = this.leases.get(scope);
if (!lease || lease.holderId !== holderId || lease.revoking) return false;
lease.overrides.set(key, value);
this.emitValueChanged(scope, key);
return true;
}
getEffectiveValue(scope: LeaseScope, key: string): unknown {
const lease = this.leases.get(scope);
if (lease && lease.overrides.has(key)) return lease.overrides.get(key);
return this.baseValues.get(scope)?.get(key);
}
hasOverride(scope: LeaseScope, key: string): boolean {
return this.leases.get(scope)?.overrides.has(key) ?? false;
}
// ---- holder lifecycle ----------------------------------------------------
// webContents destroyed / plugin teardown: everything goes, immediately.
releaseHolder(holderId: HolderId): void {
this.cancelGrace(holderId);
for (const [scope, lease] of this.leases) {
if (lease.holderId === holderId) this.dropLease(scope, lease, 'holder-destroyed');
}
for (const [scope, demand] of this.demands) {
if (demand.holders.delete(holderId)) this.emitDemandChanged(scope, demand);
}
}
// webContents reload (§8.2): leases + demands enter a grace window keyed
// on manifest identity. The same identity re-requesting within the window
// restores them (possibly under a new webContents-derived holder id);
// expiry releases for real.
beginGrace(holderId: HolderId, identityKey: string): void {
this.cancelGrace(holderId);
const heldLeases = new Map<LeaseScope, Lease>();
for (const [scope, lease] of this.leases) {
if (lease.holderId === holderId) {
heldLeases.set(scope, lease);
this.leases.delete(scope);
this.emit('lease-suspended', { scope, holderId, identityKey });
}
}
const heldDemands = new Map<LeaseScope, { since: number }>();
for (const [scope, demand] of this.demands) {
const entry = demand.holders.get(holderId);
if (entry) {
heldDemands.set(scope, entry);
demand.holders.delete(holderId);
this.emitDemandChanged(scope, demand);
}
}
if (heldLeases.size === 0 && heldDemands.size === 0) return;
const timer = setTimeout(() => this.expireGrace(holderId), this.graceMs);
timer.unref?.();
this.graces.set(holderId, {
identityKey,
leases: heldLeases,
demands: heldDemands,
timer,
expiresAt: this.now() + this.graceMs,
});
}
// Reloaded identity comes back (same manifest identity, fresh holder id).
// Returns the scopes restored.
tryRestore(identityKey: string, newHolderId: HolderId): LeaseScope[] {
if (!isValidHolder(newHolderId)) return [];
const restored: LeaseScope[] = [];
for (const [oldHolderId, grace] of this.graces) {
if (grace.identityKey !== identityKey) continue;
clearTimeout(grace.timer);
this.graces.delete(oldHolderId);
for (const [scope, lease] of grace.leases) {
if (this.leases.has(scope)) continue; // grabbed meanwhile — refuse quietly, waiter won
lease.holderId = newHolderId;
lease.revoking = false;
this.leases.set(scope, lease);
restored.push(scope);
this.emit('lease-granted', { scope, holderId: newHolderId, restored: true });
}
for (const [scope, entry] of grace.demands) {
let demand = this.demands.get(scope);
if (!demand) {
demand = { holders: new Map(), suspended: false };
this.demands.set(scope, demand);
}
if (!demand.holders.has(newHolderId)) {
demand.holders.set(newHolderId, entry);
restored.push(scope);
this.emitDemandChanged(scope, demand);
}
}
}
return restored;
}
// ---- diagnostics ----------------------------------------------------------
snapshot(): { leases: LeaseSnapshotEntry[]; demands: DemandSnapshotEntry[]; graces: Array<{ identityKey: string; expiresInMs: number }> } {
const now = this.now();
return {
leases: [...this.leases.entries()].map(([scope, lease]) => ({
scope,
holderId: lease.holderId,
heldMs: now - lease.acquiredAt,
revoking: lease.revoking,
overrides: [...lease.overrides.keys()],
})),
demands: [...this.demands.entries()]
.filter(([, demand]) => demand.holders.size > 0 || demand.suspended)
.map(([scope, demand]) => ({
scope,
suspended: demand.suspended,
holders: [...demand.holders.entries()].map(([holderId, entry]) => ({
holderId,
heldMs: now - entry.since,
})),
})),
graces: [...this.graces.values()].map(grace => ({
identityKey: grace.identityKey,
expiresInMs: Math.max(0, grace.expiresAt - now),
})),
};
}
dispose(): void {
for (const grace of this.graces.values()) clearTimeout(grace.timer);
this.graces.clear();
this.removeAllListeners();
}
// ---- internals --------------------------------------------------------------
private refuse(scope: LeaseScope, reason: AcquireRefusal, holderId: HolderId | null): AcquireResult {
this.emit('lease-refused', { scope, reason, holderId });
return { granted: false, scope, reason, holderId };
}
private dropLease(scope: LeaseScope, lease: Lease, reason: ReleaseReason): void {
this.leases.delete(scope);
const clearedOverrides = [...lease.overrides.keys()];
lease.overrides.clear();
this.emit('lease-released', { scope, holderId: lease.holderId, reason });
// Overrides die with the lease; the base value is effective again.
for (const key of clearedOverrides) this.emitValueChanged(scope, key);
}
private expireGrace(holderId: HolderId): void {
const grace = this.graces.get(holderId);
if (!grace) return;
this.graces.delete(holderId);
for (const [scope, lease] of grace.leases) {
this.emit('lease-released', { scope, holderId: lease.holderId, reason: 'grace-expired' });
for (const key of lease.overrides.keys()) this.emitValueChanged(scope, key);
}
// Demands parked in the grace were already removed from the counts.
}
private cancelGrace(holderId: HolderId): void {
const grace = this.graces.get(holderId);
if (!grace) return;
clearTimeout(grace.timer);
this.graces.delete(holderId);
for (const [scope, lease] of grace.leases) {
this.emit('lease-released', { scope, holderId: lease.holderId, reason: 'holder-destroyed' });
}
}
private emitDemandChanged(scope: LeaseScope, demand: Demand): void {
this.emit('demand-changed', {
scope,
count: demand.holders.size,
active: !demand.suspended && demand.holders.size > 0,
suspended: demand.suspended,
});
}
private emitValueChanged(scope: LeaseScope, key: string): void {
this.emit('value-changed', { scope, key, value: this.getEffectiveValue(scope, key), overridden: this.hasOverride(scope, key) });
}
}
+265
View File
@@ -0,0 +1,265 @@
// Lease registry (docs/audio-ownership-plan.md §2/§8): exclusive leases,
// refcounted demands, drain-then-grant takeover, reload grace window,
// user-stop suspend semantics, holder-death matrix, layered values.
const test = require('node:test');
const assert = require('node:assert/strict');
const { loadTs } = require('./_load-ts');
const { LeaseRegistry, WELL_KNOWN_HOLDERS, isValidExclusiveScope, isValidDemandScope } = loadTs('src/main/lease-registry.ts');
const WC1 = 'wc:1#nam_tone';
const WC2 = 'wc:2#rig_builder';
const CHAIN = 'signal-chain:desktop-main';
function collect(registry, event) {
const seen = [];
registry.on(event, e => seen.push(e));
return seen;
}
test('scope validation', () => {
assert.ok(isValidExclusiveScope('device-config'));
assert.ok(isValidExclusiveScope('signal-chain:desktop-main'));
assert.ok(isValidExclusiveScope('mixer-channel:0'));
assert.ok(!isValidExclusiveScope('capture'));
assert.ok(!isValidExclusiveScope('signal-chain:'));
assert.ok(isValidDemandScope('capture'));
assert.ok(isValidDemandScope('detection:desktop-main'));
assert.ok(!isValidDemandScope('device-config'));
});
test('acquire / refuse / release / getHolder', () => {
const r = new LeaseRegistry();
const refused = collect(r, 'lease-refused');
assert.equal(r.acquire(CHAIN, WC1).granted, true);
assert.equal(r.getHolder(CHAIN), WC1);
// second caller refused, holder named (refusal-by-default policy)
const res = r.acquire(CHAIN, WC2);
assert.deepEqual(res, { granted: false, scope: CHAIN, reason: 'held', holderId: WC1 });
assert.equal(refused.length, 1);
// re-acquire by the same holder is idempotent
assert.equal(r.acquire(CHAIN, WC1).granted, true);
// only the holder may release
assert.equal(r.release(CHAIN, WC2), false);
assert.equal(r.release(CHAIN, WC1), true);
assert.equal(r.getHolder(CHAIN), null);
assert.equal(r.acquire(CHAIN, WC2).granted, true);
r.dispose();
});
test('invalid scopes and holders are refused, never thrown', () => {
const r = new LeaseRegistry();
assert.equal(r.acquire('bogus-scope', WC1).reason, 'invalid-scope');
assert.equal(r.acquire(CHAIN, 'i-declare-myself').reason, 'invalid-holder');
assert.equal(r.acquire(CHAIN, '').reason, 'invalid-holder');
assert.equal(r.acquireDemand('bogus', WC1), false);
assert.equal(r.acquireDemand('capture', 'nope nope'), false);
// well-known internal holders pass (§8.4)
assert.equal(r.acquire('device-config', WELL_KNOWN_HOLDERS.deviceScreen).granted, true);
r.dispose();
});
test('takeover = drain-then-grant (§8.1)', async () => {
const r = new LeaseRegistry();
const revoked = collect(r, 'lease-revoked');
r.acquire(CHAIN, WC1);
let drained = false;
let refusedDuringDrain = null;
let oldCanWriteDuringDrain = null;
const result = await r.takeover(CHAIN, WC2, async () => {
// during the drain: old holder can no longer write, new acquires refused as 'revoking'
oldCanWriteDuringDrain = r.canWrite(CHAIN, WC1);
refusedDuringDrain = r.acquire(CHAIN, 'wc:3').reason;
// revocation event fired at drain START so the old holder can stop enqueueing
assert.equal(revoked.length, 1);
assert.equal(revoked[0].takenOverBy, WC2);
drained = true;
});
assert.equal(drained, true);
assert.equal(oldCanWriteDuringDrain, false);
assert.equal(refusedDuringDrain, 'revoking');
assert.equal(result.granted, true);
assert.equal(r.getHolder(CHAIN), WC2);
assert.equal(r.canWrite(CHAIN, WC2), true);
r.dispose();
});
test('takeover grants even when the drain rejects (never brick the scope)', async () => {
const r = new LeaseRegistry();
r.acquire(CHAIN, WC1);
await assert.rejects(r.takeover(CHAIN, WC2, async () => { throw new Error('wedged'); }));
// old lease is gone regardless; scope recoverable
assert.equal(r.getHolder(CHAIN), null);
assert.equal(r.acquire(CHAIN, WC2).granted, true);
r.dispose();
});
test('takeover of an unheld scope is a plain acquire', async () => {
const r = new LeaseRegistry();
const result = await r.takeover(CHAIN, WC1);
assert.equal(result.granted, true);
r.dispose();
});
test('refcounted demand: idempotent per holder, active while count > 0', () => {
const r = new LeaseRegistry();
const changes = collect(r, 'demand-changed');
assert.equal(r.acquireDemand('capture', WC1), true);
assert.equal(r.acquireDemand('capture', WC1), true); // idempotent — no double count
assert.equal(r.demandCount('capture'), 1);
r.acquireDemand('capture', WC2);
assert.equal(r.demandCount('capture'), 2);
assert.equal(r.demandActive('capture'), true);
r.releaseDemand('capture', WC1);
assert.equal(r.demandActive('capture'), true);
r.releaseDemand('capture', WC2);
assert.equal(r.demandActive('capture'), false);
// events carried count + active on every transition
assert.deepEqual(changes.map(c => c.count), [1, 2, 1, 0]);
r.dispose();
});
test('user stop suspends demands, only resume restores them (§8.3)', () => {
const r = new LeaseRegistry();
r.acquireDemand('capture', WC1);
r.acquireDemand('detection:desktop-main', WC2);
const suspended = collect(r, 'demand-suspended');
const resumed = collect(r, 'demand-resumed');
r.suspendDemands('capture');
assert.equal(r.demandActive('capture'), false);
assert.equal(r.demandCount('capture'), 1); // registration kept, not cleared
assert.equal(r.demandActive('detection:desktop-main'), true); // untouched
assert.equal(suspended.length, 1);
// a new demand while suspended registers but does not activate the scope
r.acquireDemand('capture', WC2);
assert.equal(r.demandCount('capture'), 2);
assert.equal(r.demandActive('capture'), false);
r.resumeDemands('capture');
assert.equal(r.demandActive('capture'), true);
assert.equal(resumed.length, 1);
r.dispose();
});
test('holder death releases everything immediately', () => {
const r = new LeaseRegistry();
const released = collect(r, 'lease-released');
r.acquire(CHAIN, WC1);
r.acquire('playback', WC1);
r.acquire('device-config', WC2);
r.acquireDemand('capture', WC1);
r.acquireDemand('detection:desktop-main', WC1);
r.releaseHolder(WC1);
assert.equal(r.getHolder(CHAIN), null);
assert.equal(r.getHolder('playback'), null);
assert.equal(r.getHolder('device-config'), WC2); // other holders untouched
assert.equal(r.demandCount('capture'), 0);
assert.equal(r.demandCount('detection:desktop-main'), 0);
assert.deepEqual(released.map(e => e.reason), ['holder-destroyed', 'holder-destroyed']);
r.dispose();
});
test('reload grace window: same identity restores under a new holder id (§8.2)', () => {
const r = new LeaseRegistry({ graceMs: 60_000 });
r.acquire(CHAIN, WC1);
r.acquireDemand('capture', WC1);
r.beginGrace(WC1, 'plugin:nam_tone');
// scope is free-standing during grace: getHolder empty, but restorable
assert.equal(r.getHolder(CHAIN), null);
assert.equal(r.demandCount('capture'), 0);
const restored = r.tryRestore('plugin:nam_tone', 'wc:9#nam_tone');
assert.deepEqual(restored.sort(), [CHAIN, 'capture'].sort());
assert.equal(r.getHolder(CHAIN), 'wc:9#nam_tone');
assert.equal(r.demandCount('capture'), 1);
r.dispose();
});
test('grace window: a waiter that grabbed the scope meanwhile wins', () => {
const r = new LeaseRegistry({ graceMs: 60_000 });
r.acquire(CHAIN, WC1);
r.beginGrace(WC1, 'plugin:nam_tone');
r.acquire(CHAIN, WC2); // waiter takes it during the gap
const restored = r.tryRestore('plugin:nam_tone', 'wc:9#nam_tone');
assert.deepEqual(restored, []);
assert.equal(r.getHolder(CHAIN), WC2);
r.dispose();
});
test('grace window expiry releases for real', async () => {
const r = new LeaseRegistry({ graceMs: 20 });
const released = collect(r, 'lease-released');
r.acquire(CHAIN, WC1);
r.beginGrace(WC1, 'plugin:nam_tone');
await new Promise(resolve => setTimeout(resolve, 60));
assert.deepEqual(released.map(e => e.reason), ['grace-expired']);
assert.deepEqual(r.tryRestore('plugin:nam_tone', 'wc:9#nam_tone'), []);
assert.equal(r.acquire(CHAIN, WC2).granted, true);
r.dispose();
});
test('destroy during grace cancels restore', () => {
const r = new LeaseRegistry({ graceMs: 60_000 });
r.acquire(CHAIN, WC1);
r.beginGrace(WC1, 'plugin:nam_tone');
r.releaseHolder(WC1); // renderer destroyed while grace pending
assert.deepEqual(r.tryRestore('plugin:nam_tone', 'wc:9#nam_tone'), []);
assert.equal(r.acquire(CHAIN, WC2).granted, true);
r.dispose();
});
test('layered values: override rides the lease, base survives it (§2/§8.10)', () => {
const r = new LeaseRegistry();
const changes = collect(r, 'value-changed');
r.setBase(CHAIN, 'noise-gate.threshold', -70);
assert.equal(r.getEffectiveValue(CHAIN, 'noise-gate.threshold'), -70);
// override refused without the lease
assert.equal(r.setOverride(CHAIN, 'noise-gate.threshold', -55, WC1), false);
r.acquire(CHAIN, WC1);
assert.equal(r.setOverride(CHAIN, 'noise-gate.threshold', -55, WC1), true);
assert.equal(r.getEffectiveValue(CHAIN, 'noise-gate.threshold'), -55);
assert.equal(r.hasOverride(CHAIN, 'noise-gate.threshold'), true);
// base write during override persists but does not pierce (§8.10)
r.setBase(CHAIN, 'noise-gate.threshold', -80);
assert.equal(r.getEffectiveValue(CHAIN, 'noise-gate.threshold'), -55);
// release restores the (updated) base
r.release(CHAIN, WC1);
assert.equal(r.getEffectiveValue(CHAIN, 'noise-gate.threshold'), -80);
assert.equal(r.hasOverride(CHAIN, 'noise-gate.threshold'), false);
const last = changes[changes.length - 1];
assert.equal(last.value, -80);
assert.equal(last.overridden, false);
r.dispose();
});
test('snapshot names holders with ages (diag visibility, §8.7)', () => {
let t = 1000;
const r = new LeaseRegistry({ now: () => t });
r.acquire(CHAIN, WC1);
r.acquireDemand('detection:desktop-main', WC2);
t = 5000;
const snap = r.snapshot();
assert.deepEqual(snap.leases, [{ scope: CHAIN, holderId: WC1, heldMs: 4000, revoking: false, overrides: [] }]);
assert.equal(snap.demands.length, 1);
assert.deepEqual(snap.demands[0].holders, [{ holderId: WC2, heldMs: 4000 }]);
r.dispose();
});