fix(beehive): own CLI shutdown across startup and persistence failures

Signed-off-by: Logan Johnson <loganj@squareup.com>
This commit is contained in:
Logan Johnson
2026-09-10 03:26:36 -04:00
parent 425feb343e
commit 256cdf319e
9 changed files with 300 additions and 24 deletions

View File

@@ -1,3 +1,86 @@
## Recovery 5360 — final validation / publication candidate
Recovered base HEAD: 425feb343e07f5d36fb7ee18b9502c0fd172b15d. The source
candidate is the commit containing this section; all surviving predecessor edits
were preserved and completed, not reset or duplicated.
**Explicit relay-review R1: CLOSED.** CLI consumes relay close callback, drains
queued persistence, surfaces sanitized possible durability uncertainty and exits 1;
actual subprocess directory-fault (wire1011 + possible replacement) and clean
SIGINT exit0 controls pass. No storage-fence/uncertain-vs-rejected change.
Final strict PASS; exactly ONE FULL DEFAULT concurrent installed-enabled suite
**78/78, zero skips/cancellations, natural 44.186s**, on Node 24.15.0/pnpm 11.4.0.
Both host CLI service journeys, pre-ready SIGINT/SIGTERM and policy driver pass.
No serial mask, retry-to-green, timeout increase, force exit or weak assertions.
No executable edits after this run; only acceptance documentation. The prior
71/73 broker/reply-tool failures and older disconnect/admission observations
remain separately scoped, not classified by nonrecurrence. Independent diagnosis
and normal-transition review operate the immutable base and remain to consume.
Self-review: one natural host close promise owns startup abort and ready shutdown;
all slots drain with allSettled, uncertain teardown retains lock, and CLI awaits
errors. No PID-derived cleanup authority. Policy barrier is public inventory for
exact host/agent/revision, not sleep or operation retry; product CAS unchanged.
Recovered service test uses actual CLI from wizard installation, never direct host
construction. Captured child output is capped at 8192 characters.
Evidence directory: WORK_LOGS/BEEHIVE_DESIGN_183FFAF0/RECOVERY_5360, including
final-strict.log, full-installed-default.log and separate source/installed hashes.
Installed pins unchanged (buzz-acp 10612d00…441ea; Buzz CLI 147cc2cc…766bb2af5a37b856c5).
Configured Logan Johnson <loganj@squareup.com> author/committer + DCO; no invented
cryptographic signer. Publication verification recorded in that evidence directory.
Remaining: independent findings reconciliation, actual Goose/provider login and
production service/current kind40002 admission; Claude/Codex/providers/presets/
custom/mesh/compute and full parity. Installed Goose is a deterministic TS fixture,
not real Goose or live LLM proof. No owner credentials, OAuth caches, private run
journals, provider calls, production operations, Rust/native/client changes, repo
CI, merge, release or full-product claim.
## Recovery 5360 — pre-final exact candidate gate
R1 CLOSED locally: actual CLI fault/clean SIGINT 2/2 + strict on pinned
Node 24.15.0/pnpm 11.4.0. Startup/policy focused 8/8 (5.349s): actual
SIGINT/SIGTERM before withheld upgrade ready releases clean lock, unchanged
journal, subsequent startup/ready shutdown; quarantine keeps lock and double
close shares teardown. No reconnect/deadline changes.
Policy driver now waits for relevant PUBLIC host+agent running inventory rev1
before spawning Stop TUI. This source-grounded bounded barrier preserves all
CAS/unknown/retry/operation-count assertions; pinned runtime reproduces accepted
unchanged original Stop at rev2 in both loss modes. Not a product recovery fix.
Recovered service subprocess journeys PASS 2/2 (38.142s): wizard-produced initial
normal and selected-B NEW immutable conversion, actual host CLI subprocess,
installed buzz-acp/Buzz CLI, four signed same-agent replies including Restart,
model/profile/history/Y assertions. Goose is still a TS protocol fixture, no
live provider/current-kind40002 admission or production service deployment.
Bounded recovered test stdout/stderr tails to 8 KiB. Evidence RECOVERY_5360/
{r1,startup-policy,service}.log; older CLI_333D partial log retained unchanged.
Next: strict + ONE FULL DEFAULT concurrent installed-enabled run of this exact
executable candidate, no serial masking/retries/deadline increase/force exit.
Historical full 71/73 and older failures remain separately scoped.
## Recovery 5360 — preserved working state, before validation
Recovered HEAD 425feb343e07f5d36fb7ee18b9502c0fd172b15d; no new commits.
Preserved prior edits to cli.ts, host.ts, normal-conversation/startup-transport tests,
and untracked cli-shutdown.test.ts / relay-cli-fault.ts. No reset/cleanup.
R1 patch already present (callback diagnostic/nonzero exit); prior local 2/2 claim
retained below, fresh pinned validation pending. Startup abort and actual service
journey edits also survived; not yet accepted. Policy public-inventory driver fix
was absent and is next after R1 validation. Evidence: CLI_333D existing public logs;
new evidence will be in RECOVERY_5360. No private run journals consulted.
## CLI lifecycle continuation 333d — early R1 checkpoint
425feb actual CLI still discarded relay.close errors: R1 was OPEN, not fixed by
normal wizard work. Now the real CLI consumes close callback, reports sanitized
possible durability uncertainty, exits 1, and leaves history/fences intact. Clean
SIGINT exits 0. Actual CLI subprocess regressions (test-only module load injection
at existing directory boundary, no copied CLI) pass 2/2, strict passes on Node
24.15.0/pnpm 11.4.0. Replacement present + wire1011 is NOT durable rejection or
accepted data loss. R1 implementation/regression CLOSED locally; final gate and
publication pending. Startup signals and service subprocess work follow.
# Beehive executable checkpoint
## Normal conversation wizard A661 — final gate / scoped handoff

View File

@@ -514,3 +514,21 @@ no secret echo, retained assignment, 0600). Initial/imported executable paths he
are deliberately metadata-only fixtures; no provider/login or runtime spawn claimed.
Existing Goose/assignment tests retain actual standby Start denial and secure-input
negatives. The normal installed journeys run only with the existing installed opt-in.
## CLI lifecycle recovery 5360
`cli-shutdown.test.ts` launches the actual relay CLI. Test-only module loading
injects the existing directory persistence boundary: wire1011 and possible
replacement must lead to uncertainty stderr and SIGINT exit1, versus clean exit0.
`startup-transport.test.ts` adds actual host CLI SIGINT/SIGTERM while upgrade is
withheld, clean lock/journal/restart checks, quarantine retention and double close.
`policy-tui.test.ts` now waits for relevant PUBLIC running rev1 inventory before
spawning Stop TUI; private journal rev1 is not asynchronous relay visibility.
Original unknown/byte-identical retry/accepted rev2 assertions remain unchanged.
`normal-conversation.test.ts` now starts actual host CLI subprocesses from the
wizard-produced installations instead of in-process host construction. Initial
normal and selected-B conversion still require four signed installed CLI replies
including Restart, same identity, exact model/profile and unchanged sibling/history.
Strict + one default concurrent installed-enabled full run: 78/78, natural 44.186s.
This closes scoped service-subprocess continuity, not real Goose/provider or
production kind40002/service deployment proof. See CHECKPOINT for historical failures.

View File

@@ -195,12 +195,33 @@ async function main() {
} else if (command === 'relay') {
const server = await relay(Number(args[0]),text(args[1]),resolve(text(args[2])));
console.log(`Development relay listening ${JSON.stringify(server.address())}`);
process.once('SIGINT',() => { for (const c of server.clients) c.close(); server.close(); });
let closing = false;
const close = () => {
if (closing) return; closing = true;
for (const c of server.clients) c.close();
server.close(error => {
if (error) { console.error('Relay shutdown failed; storage durability may be uncertain. Retain history for reconciliation.'); process.exitCode = 1; }
});
};
process.on('SIGINT', close); process.on('SIGTERM', close);
} else if (command === 'host') {
const running = await host(resolve(text(args[0])),text(args[1]));
console.log(`Host online; agents ${running.agents.join(', ')}. Ctrl-C stops owned runner before host exits.`);
process.once('SIGINT',() => { void running.close(); });
process.once('SIGTERM',() => { void running.close(); });
const controller = new AbortController();
const stop = () => controller.abort();
process.on('SIGINT', stop); process.on('SIGTERM', stop);
try {
const running = await host(resolve(text(args[0])), text(args[1]), controller.signal);
console.log(`Host online; agents ${running.agents.join(', ')}. Ctrl-C stops owned runner before host exits.`);
// The host owns teardown both before and after ready. Observe its one
// completion promise so uncertain teardown cannot disappear as a rejection.
await new Promise<void>(resolve => {
if (controller.signal.aborted) resolve();
else controller.signal.addEventListener('abort', () => resolve(), { once: true });
});
await running.close();
} catch (error) {
if (!controller.signal.aborted || error !== controller.signal.reason) throw error;
// Transport ready rejection caused by intentional startup cancellation.
} finally { process.off('SIGINT', stop); process.off('SIGTERM', stop); }
} else if (command === 'tui') {
const secret = text(object(readPrivate(resolve(text(args[0])))).secret);
const inventory = new Map<string,Message>();

View File

@@ -501,7 +501,8 @@ function slot(setup: Setup, path: string, agent: string, currentSetup: (id: stri
}
/** One installation owns every slot, lock and management transport. */
export async function host(directory: string, url: string) {
export async function host(directory: string, url: string, signal?: AbortSignal) {
signal?.throwIfAborted();
validateRelayURL(url);
const lock = join(directory, 'host.lock');
mkdirSync(lock, { mode: 0o700 }); // Never infer ownership from a recovered PID.
@@ -510,6 +511,18 @@ export async function host(directory: string, url: string) {
const slots = new Map<string, ReturnType<typeof slot>>();
const profiles = new Profiles();
const publish = (m: Message) => { try { client?.send(m); } catch { /* Slot outbox survives transport loss. */ } };
let heartbeat: ReturnType<typeof setInterval> | undefined;
let closing: Promise<void> | undefined;
const close = () => closing ??= (async () => {
initialized = false; client?.close(); clearInterval(heartbeat);
const results = await Promise.allSettled([...slots.values()].map(s => s.close()));
const errors = results.filter((r): r is PromiseRejectedResult => r.status === 'rejected').map(r => r.reason);
if (errors.length) throw new AggregateError(errors, 'Incomplete owned teardown; installation remains locked');
rmdirSync(lock);
})();
// Abort uses the same owner as ready shutdown, including quarantine fences.
const abort = () => { void close().catch(() => {}); };
signal?.addEventListener('abort', abort, { once: true });
try {
const entries = installationSlots(directory);
for (const entry of entries) {
@@ -535,19 +548,17 @@ export async function host(directory: string, url: string) {
else if (['save','start','restart','stop','move'].includes(m.type)) publish(message('receipt',setup.host,m.agent,0,{ operation: m.id, fingerprint: digest(JSON.stringify(m)).toString('hex'), result: 'not-authority' }));
}, () => { if (initialized) for (const s of slots.values()) s.replay(); });
await client.ready;
} catch (e) { client?.close(); rmdirSync(lock); throw e; }
signal?.throwIfAborted();
} catch (e) {
try { await close(); } finally { signal?.removeEventListener('abort', abort); }
if (signal?.aborted) throw signal.reason;
throw e;
}
initialized = true;
for (const s of slots.values()) s.replay();
const heartbeat = setInterval(() => { for (const s of slots.values()) s.heartbeat(); }, 2000);
let closing: Promise<void> | undefined;
heartbeat = setInterval(() => { for (const s of slots.values()) s.heartbeat(); }, 2000);
return { agent: [...slots.keys()][0]!, agents: [...slots.keys()], close() {
return closing ??= (async () => {
initialized = false; client?.close(); clearInterval(heartbeat);
// allSettled starts every teardown and preserves each slot's durable truth.
const results = await Promise.allSettled([...slots.values()].map(s => s.close()));
const errors = results.filter((r): r is PromiseRejectedResult => r.status === 'rejected').map(r => r.reason);
if (errors.length) throw new AggregateError(errors, 'Incomplete owned teardown; installation remains locked');
rmdirSync(lock);
})();
signal?.removeEventListener('abort', abort);
return close();
} };
}

View File

@@ -0,0 +1,35 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { spawn } from 'node:child_process';
import { once } from 'node:events';
import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { WebSocket } from 'ws';
import { newKey, publicKey, seal, message } from '../src/protocol.ts';
for (const fault of [false, true]) test(`actual relay CLI SIGINT surfaces uncertainty=${fault}`, { timeout: 10000 }, async () => {
const dir = mkdtempSync(join(tmpdir(), 'beehive-cli-close-'));
const secret = newKey(), path = join(dir, 'history.json');
const child = spawn(process.execPath, [...(fault ? ['--import', './test/relay-cli-fault.ts'] : []), 'src/cli.ts', 'relay', '0', publicKey(secret), path], { stdio: ['ignore', 'pipe', 'pipe'] });
let output = '', errors = ''; child.stdout.on('data', b => output = (output + b).slice(-8192)); child.stderr.on('data', b => errors = (errors + b).slice(-8192));
const exited = once(child, 'exit');
let ws: WebSocket | undefined;
try {
while (!output.includes('\n')) await once(child.stdout, 'data');
const address = JSON.parse(output.trim().replace('Development relay listening ', ''));
ws = new WebSocket(`ws://127.0.0.1:${address.port}`); await once(ws, 'open');
const observed = once(ws, fault ? 'close' : 'message');
const envelope = seal(message('inspect', 'h', 'a'), secret); ws.send(JSON.stringify(envelope));
const result = await observed;
if (fault) assert.equal(result[0], 1011);
assert.deepEqual(JSON.parse(readFileSync(path, 'utf8')), [envelope], 'replacement is possible, not a durable rejection');
child.kill('SIGINT');
assert.deepEqual(await exited, [fault ? 1 : 0, null]);
if (fault) assert.match(errors, /storage durability may be uncertain/); else assert.equal(errors, '');
} finally {
ws?.terminate();
if (child.exitCode === null && child.signalCode === null) { child.kill('SIGTERM'); await exited; }
rmSync(dir, { recursive: true, force: true });
}
});

View File

@@ -10,7 +10,8 @@ import { schnorr } from '@noble/curves/secp256k1';
import { setTimeout as delay } from 'node:timers/promises';
import { terminal } from './local-terminal.ts';
import { installationSlots } from '../src/slots.ts';
import { host } from '../src/host.ts';
import { spawn } from 'node:child_process';
import { once } from 'node:events';
import { relay } from '../src/relay.ts';
import { readPrivate, writePrivate } from '../src/storage.ts';
import { pathToFileURL } from 'node:url';
@@ -19,7 +20,7 @@ import { profileRevision } from '../src/profiles.ts';
import { newKey, publicKey, message, type Message } from '../src/protocol.ts';
// Explicitly opt-in installed executable; never opens an owner profile or provider.
for (const converting of [false, true]) test(`normal wizard + TUI Start/Restart + production host + installed CLI signed replies (${converting ? 'selected diagnostic B conversion' : 'initial normal default'})`, { skip: !process.env.BEEHIVE_REAL_BUZZ_ACP }, async () => {
for (const converting of [false, true]) test(`normal wizard + TUI Start/Restart + host CLI service subprocess + installed CLI signed replies (${converting ? 'selected diagnostic B conversion' : 'initial normal default'})`, { skip: !process.env.BEEHIVE_REAL_BUZZ_ACP }, async () => {
const goose = true;
const dir = realpathSync(mkdtempSync(join(tmpdir(), 'bh-installed-')));
writeFileSync(join(dir, 'mode'), 'ok');
@@ -67,7 +68,13 @@ for (const converting of [false, true]) test(`normal wizard + TUI Start/Restart
{ prompt: 'Save NEW Goose binding only', answer: 'yes' },
]);
assert.ok(!setupOutput.includes(ownerSecret));
const state = () => readPrivate(entry.path) as any;
// Lifecycle decisions come from signed public inventory, not a private journal.
let inventory: Message | undefined;
const state = () => {
assert.ok(inventory, 'public host inventory required');
return { revision: inventory.revision, phase: inventory.body.phase,
selected: inventory.body.selectedNext as any, actual: inventory.body.actualRun as any };
};
const channel = 'aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee';
const replies: any[] = [];
const subscriptionsBySocket = new Map<string, { socket: any; id: string }>();
@@ -138,7 +145,31 @@ for (const converting of [false, true]) test(`normal wizard + TUI Start/Restart
const management = await relay(0, owner, join(dir, 'management.json'));
const ma = management.address(); assert.ok(ma && typeof ma !== 'string');
const managementURL = `ws://127.0.0.1:${ma.port}`;
let h = await host(installation, managementURL);
const observer = connect(managementURL, ownerSecret, m => {
if (m.type === 'inventory' && m.host === 'journey' && m.agent === agent) inventory = m;
});
await observer.ready;
const service = async () => {
const child = spawn(process.execPath, ['src/cli.ts', 'host', installation, managementURL], { stdio: ['ignore', 'pipe', 'pipe'] });
let output = '', errors = '';
child.stdout.on('data', b => output = (output + b).slice(-8192)); child.stderr.on('data', b => errors = (errors + b).slice(-8192));
const exited = once(child, 'exit');
let closing: Promise<void> | undefined;
const close = () => closing ??= (async () => {
if (child.exitCode === null && child.signalCode === null) child.kill('SIGTERM');
assert.deepEqual(await exited, [0, null], errors);
assert.equal(errors, '');
assert.equal(existsSync(join(installation, 'host.lock')), false);
})();
try {
for (let n = 0; n < 400 && !(output.includes('Host online;') && inventory); n++) {
assert.equal(child.exitCode, null, errors); await delay(20);
}
assert.match(output, /Host online;/); assert.ok(inventory);
return { close };
} catch (error) { await close(); throw error; }
};
let h = await service();
if (converting) {
await terminal(['tui', identity, managementURL], [
{ prompt: 'beehive> ', answer: 'select 1' },
@@ -163,7 +194,7 @@ for (const converting of [false, true]) test(`normal wizard + TUI Start/Restart
assert.deepEqual(readFileSync(entry.path), beforeJournal);
assert.deepEqual(installationSlots(installation)[0]!.bindings.B, beforeB);
assert.deepEqual(installationSlots(installation)[0]!.bindings.default, originalA);
h = await host(installation, managementURL);
inventory = undefined; h = await service();
await terminal(['tui', identity, managementURL], [
{ prompt: 'beehive> ', answer: 'select 1' },
{ prompt: 'beehive> ', answer: 'binding B-normal', observedRevision: 3 },
@@ -234,7 +265,7 @@ for (const converting of [false, true]) test(`normal wizard + TUI Start/Restart
]);
assert.notEqual(state().actual.evidence.session, oldActual.evidence.session);
assert.equal(state().actual.evidence.model, oldActual.evidence.model);
assert.deepEqual(state().runs[oldActual.run], oldActual);
assert.deepEqual((readPrivate(entry.path) as any).runs[oldActual.run], oldActual, 'fresh fixture history audit only; never used to admit execution');
assert.deepEqual(readFileSync(join(installation, 'setup.json')), manifest);
assert.deepEqual(readFileSync(sibling.path), originalY);
assert.deepEqual(readFileSync(join(installation, 'setup.json')), convertedManifest);
@@ -274,6 +305,7 @@ for (const converting of [false, true]) test(`normal wizard + TUI Start/Restart
} finally {
if (goose && existsSync(join(dir, 'goose-rpc-methods'))) console.log(readFileSync(join(dir, 'goose-rpc-methods'), 'utf8'));
observer.close();
await session.stop();
for (const file of ['harness-pid', 'descendant-pid', 'tool-shim-pid']) {
if (!existsSync(join(dir, file))) continue;

View File

@@ -30,10 +30,16 @@ test('actual TUI: policy repair, bounded unchanged retry, lost committed receipt
const address = server.address(); assert.ok(address && typeof address !== 'string');
const url = `ws://127.0.0.1:${address.port}`;
const h = await host(dir, url);
const control = connect(url, secret, () => {}); await control.ready;
let relayVisibleRunning = false;
const control = connect(url, secret, m => {
// A private journal commit is not yet public relay inventory visibility.
if (m.type === 'inventory' && m.host === 'policy-host' && m.agent === agent && m.revision === 1 && m.body.phase === 'running') relayVisibleRunning = true;
}); await control.ready;
const journal = () => JSON.parse(readFileSync(join(dir, 'journal.json'), 'utf8'));
control.send(message('start', 'policy-host', agent, 0));
await until(() => journal().phase === 'running');
// Later TUI connections replay this durably committed revision before Stop.
await until(() => relayVisibleRunning);
let deny = true; let denials = 0;
const attempts: string[] = [];
const upstreams = new Set<WebSocket>();

View File

@@ -0,0 +1,11 @@
// Test-only module instrumentation: execute the real CLI, inject only the existing
// storage boundary. No production environment switch or copied shutdown owner.
import { registerHooks } from 'node:module';
registerHooks({ load(url, context, nextLoad) {
const loaded = nextLoad(url, context);
if (!url.endsWith('/src/relay.ts')) return loaded;
const source = String(loaded.source);
const needle = 'boundary?: SnapshotBoundary';
if (!source.includes(needle)) throw Error('Relay boundary instrumentation drift');
return { ...loaded, source: source.replace(needle, "boundary: SnapshotBoundary = async phase => { if (phase === 'directory') throw Error('injected directory fault'); }") };
} });

View File

@@ -98,3 +98,62 @@ test('initial HTTP policy denial fails explicitly without automatic retry even w
assert.equal(e.upgrades, 1); assert.equal(recovered, 0);
} finally { client.close(); await e.close(); }
});
for (const signal of ['SIGINT', 'SIGTERM'] as const) test(`actual host CLI ${signal} during withheld upgrade drains startup; subsequent ready shutdown`, { timeout: 10000 }, async () => {
const { spawn } = await import('node:child_process');
const dir = realpathSync(mkdtempSync(join(tmpdir(), 'beehive-cli-startup-')));
const secret = newKey(), agentSecret = newKey();
const e = await endpoint();
provision(dir, { host: 'startup', ownerSecret: secret, agentSecret, runner: process.execPath,
args: [resolve('test/runner.ts')], workspace: dir, mode: 'fixture' },
createGenesis(publicKey(secret), publicKey(agentSecret), 'startup'));
const before = readFileSync(join(dir, 'journal.json'));
const launch = () => {
const child = spawn(process.execPath, ['src/cli.ts', 'host', dir, e.url], { stdio: ['ignore', 'pipe', 'pipe'] });
let output = '', errors = '';
child.stdout.on('data', b => output = (output + b).slice(-8192)); child.stderr.on('data', b => errors = (errors + b).slice(-8192));
return { child, exited: once(child, 'exit'), get output() { return output; }, get errors() { return errors; } };
};
let running = launch();
try {
await until(() => e.upgrades === 1);
assert.ok(existsSync(join(dir, 'host.lock')));
running.child.kill(signal);
assert.deepEqual(await running.exited, [0, null], running.errors);
assert.equal(running.errors, '');
assert.equal(existsSync(join(dir, 'host.lock')), false);
assert.deepEqual(readFileSync(join(dir, 'journal.json')), before);
assert.equal(e.upgrades, 1);
e.mode = 'accept'; running = launch();
await until(() => running.output.includes('Host online;'));
running.child.kill(signal);
assert.deepEqual(await running.exited, [0, null], running.errors);
assert.equal(existsSync(join(dir, 'host.lock')), false);
assert.deepEqual(readFileSync(join(dir, 'journal.json')), before);
} finally {
if (running.child.exitCode === null && running.child.signalCode === null) { running.child.kill('SIGTERM'); await running.exited; }
await e.close(); rmSync(dir, { recursive: true, force: true });
}
});
test('startup abort retains quarantine lock; ready double close shares teardown', async () => {
const dir = realpathSync(mkdtempSync(join(tmpdir(), 'beehive-abort-fence-')));
const secret = newKey(), agentSecret = newKey();
const e = await endpoint();
const { writePrivate, readPrivate } = await import('../src/storage.ts');
try {
provision(dir, { host: 'startup', ownerSecret: secret, agentSecret, runner: process.execPath,
args: [resolve('test/runner.ts')], workspace: dir, mode: 'fixture' },
createGenesis(publicKey(secret), publicKey(agentSecret), 'startup'));
e.mode = 'accept'; const clean = await host(dir, e.url);
const one = clean.close(); assert.equal(clean.close(), one); await one;
const path = join(dir, 'journal.json');
const state = readPrivate(path) as Record<string, unknown>;
state.phase = 'quarantined'; writePrivate(path, state);
e.mode = 'stall'; const controller = new AbortController();
const pending = host(dir, e.url, controller.signal);
const rejected = assert.rejects(pending, /Incomplete owned teardown/);
await until(() => e.upgrades === 2); controller.abort(); await rejected;
assert.equal(existsSync(join(dir, 'host.lock')), true, 'unproven execution remains fenced');
} finally { await e.close(); rmSync(dir, { recursive: true, force: true }); }
});