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
2 changes: 1 addition & 1 deletion docs/decisions/runtime-requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ Paths may appear in local diagnostics; public fixtures use temporary paths.
| Requirement | Initial value | Evidence and limit of the claim |
| --- | --- | --- |
| Claude backend's SDK range | Retain `^0.3.241` | It is the published declaration (`"@anthropic-ai/claude-agent-sdk"`, `packages/ui-backend-claude/package.json:49`). Registry metadata on 2026-09-30 confirms 0.3.241 exists. This is a declared compatibility bound, not proof every allowed release passed current measurements. |
| ui-server's content CLI floor | Retain `0.33.0` | `--` support is the reason documented immediately above `MIN_BRAIN_CLI_VERSION` (`MIN_BRAIN_CLI_VERSION`, `packages/ui-server/src/brain/client.ts:100`); the initial boot refusal test used 0.32.9. The current test exercises the corrected floor (`a 0.40.0 brain repo pin`, `packages/ui-server/tests/brain-client.test.ts:243-255`). |
| ui-server's content CLI floor | Retain `0.33.0` | `--` support is the reason documented immediately above `MIN_BRAIN_CLI_VERSION` (`MIN_BRAIN_CLI_VERSION`, `packages/ui-server/src/brain/client.ts:100`); the initial boot refusal test used 0.32.9. The current test exercises the corrected floor (`a 0.40.0 brain repo pin`, `packages/ui-server/tests/brain-client.test.ts:244-256`). |
| Bun engine floor | Retain existing `>=1.3.5` declarations | Existing engine metadata and the doctor's CVE-based warning (`const MIN_BUN`, `packages/core/src/cli/commands/doctor.ts:45`; `function checkRuntime`, `packages/core/src/cli/commands/doctor.ts:82-89`). This record introduces no new doctor verdict or blanket boot gate. |
| Pi SDK requirements | Retain existing exact `0.87.1` declarations | `packages/ui-backend-pi/package.json`; a host floor composes with each actual imported Pi dependency's existing exact requirement. The Claude range ruling does not authorize changing Pi's pins. |
| Additional default Claude runtime floor | None | There is no evidenced numerical incompatibility threshold in the existing record. The successful pairs are observations, not proof that the preceding release fails. A new library floor needs a feature requirement or reproducible incompatibility and its versioning review. |
Expand Down
8 changes: 4 additions & 4 deletions docs/http-api-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,10 @@ cover start refusal, mid-turn partial work, overlapping observations and leases.

**B — Corpus transport and CLI errors.**

- `places flags before -- and a --prefixed search query after it` ([source](../packages/ui-server/tests/brain-client.test.ts)), (`places flags before -- and a --prefixed search query after it`, `packages/ui-server/tests/brain-client.test.ts:121-145`)
- `HTTP search deadlines return 504 and reap the stalled process` ([source](../packages/ui-server/tests/brain-client.test.ts)), (`HTTP search deadlines return 504 and reap the stalled process`, `packages/ui-server/tests/brain-client.test.ts:207-221`)
- `a stats embeddings count the CLI could not take reaches the HTTP body as null, not 0` ([source](../packages/ui-server/tests/brain-client.test.ts)), (`a stats embeddings count the CLI could not take reaches the HTTP body as null, not 0`, `packages/ui-server/tests/brain-client.test.ts:64-90`)
- “stats history runs `brain stats --history` and the route passes its nulls through” ([source](../packages/ui-server/tests/brain-client.test.ts)), (`stats history runs`, `packages/ui-server/tests/brain-client.test.ts:92-119`)
- `places flags before -- and a --prefixed search query after it` ([source](../packages/ui-server/tests/brain-client.test.ts)), (`places flags before -- and a --prefixed search query after it`, `packages/ui-server/tests/brain-client.test.ts:122-146`)
- `HTTP search deadlines return 504 and reap the stalled process` ([source](../packages/ui-server/tests/brain-client.test.ts)), (`HTTP search deadlines return 504 and reap the stalled process`, `packages/ui-server/tests/brain-client.test.ts:208-222`)
- `a stats embeddings count the CLI could not take reaches the HTTP body as null, not 0` ([source](../packages/ui-server/tests/brain-client.test.ts)), (`a stats embeddings count the CLI could not take reaches the HTTP body as null, not 0`, `packages/ui-server/tests/brain-client.test.ts:65-91`)
- “stats history runs `brain stats --history` and the route passes its nulls through” ([source](../packages/ui-server/tests/brain-client.test.ts)), (`stats history runs`, `packages/ui-server/tests/brain-client.test.ts:93-120`)
- `mounted corpus handlers preserve CLI failures and sync terminates with an unsuccessful SSE frame` ([source](../packages/ui-server/tests/http-cli-errors.test.ts)), (`mounted corpus handlers preserve CLI failures and sync terminates with an unsuccessful SSE frame`, `packages/ui-server/tests/http-cli-errors.test.ts:7-37`)

**C — Capture and index recovery.**
Expand Down
8 changes: 8 additions & 0 deletions packages/ui-server/tests/brain-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { createBrainRoutes } from "../src/routes/brain";

import { BRAIN_BIN, runCli } from "../../core/tests/cli-harness";
import { registeredVersionDeadline } from "../../ui-sdk/tests/helpers/registered-version-deadline";
import { privateMaintenanceTest } from "./helpers/private-maintenance-test.js";

const temporaryRoots: string[] = [];

Expand Down Expand Up @@ -241,6 +242,7 @@ describe("search cancellation and deadlines", () => {

describe("brain CLI version probe", () => {
test("a 0.40.0 brain repo pin refuses app creation and names 0.42.0", async () => {
if (await privateMaintenanceTest(import.meta.path, "a 0.40.0 brain repo pin refuses app creation and names 0.42.0")) return;
const root = temporaryBrain();
installBrainCli(root, `console.log("0.40.0");\n`);
const observability = createRecordingObservability();
Expand All @@ -253,13 +255,15 @@ describe("brain CLI version probe", () => {
});

test("the first compatible CLI release boots", async () => {
if (await privateMaintenanceTest(import.meta.path, "the first compatible CLI release boots")) return;
const root = temporaryBrain();
installBrainCli(root, `console.log("0.42.0");\n`);
const app = await createApp({ config: appConfig(root), observability: createRecordingObservability(), registry: registry() });
await app.close();
});

test("a malformed version string warns and still boots", async () => {
if (await privateMaintenanceTest(import.meta.path, "a malformed version string warns and still boots")) return;
// "0.32.9-.." is not SemVer: the prerelease identifiers are empty. A loose
// prerelease pattern would parse it as a real 0.32.9 prerelease and refuse
// to boot; the contract is that anything unparseable warns and continues.
Expand All @@ -280,6 +284,7 @@ describe("brain CLI version probe", () => {
});

test("an unresolvable CLI warns and still boots", async () => {
if (await privateMaintenanceTest(import.meta.path, "an unresolvable CLI warns and still boots")) return;
const root = temporaryBrain();
const observability = createRecordingObservability();
const app = await createApp({
Expand Down Expand Up @@ -464,6 +469,7 @@ describe("brain CLI client over the keyless Odysseus corpus", () => {
*/
describe("the exec wrapper covers the shared CLI client", () => {
let previous: string | undefined;
beforeEach(() => { previous = process.env.BRAIN_UI_EXEC_WRAPPER; });

afterEach(() => {
if (previous === undefined) delete process.env.BRAIN_UI_EXEC_WRAPPER;
Expand Down Expand Up @@ -552,6 +558,7 @@ describe("the exec wrapper covers the shared CLI client", () => {
});

test("an app spawns through its configured wrapper, not the ambient one (#1363)", async () => {
if (await privateMaintenanceTest(import.meta.path, "an app spawns through its configured wrapper, not the ambient one (#1363)")) return;
// Two apps in one process must not share a privilege boundary: the
// wrapper comes from the config handed to createApp, even while the
// process environment names a different one.
Expand All @@ -563,6 +570,7 @@ describe("the exec wrapper covers the shared CLI client", () => {
}, 30_000);

test("a config without exec keeps the ambient wrapper rather than spawning unwrapped", async () => {
if (await privateMaintenanceTest(import.meta.path, "a config without exec keeps the ambient wrapper rather than spawning unwrapped")) return;
// A configuration built before `ServerConfig.exec` existed must not lose
// its wrapper: createApp resolves the ambient one at the edge.
const seen = await wrappersSeenByApp((root, wrapper) => {
Expand Down
21 changes: 15 additions & 6 deletions packages/ui-server/tests/fixtures/scope-pi-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
import { createInterface } from "node:readline";
import { join } from "node:path";
let brain = "", sessionId = "", running = false;
// The test pipe acknowledges this checkpoint before delivering its backlog.
// Only the parent, after observation and accepted Continue, releases the rest.
const PAUSE_AFTER_CALL = Number(process.env.SCOPE_FIXTURE_CHECKPOINT ?? "2");
let continueFixture!: () => void;
const continuation = new Promise<void>(done => { continueFixture = done; });
const emit = (value: unknown) => process.stdout.write(JSON.stringify(value) + "\n");
createInterface({ input: process.stdin }).on("line", async line => {
const message = JSON.parse(line);
Expand All @@ -12,19 +17,23 @@ createInterface({ input: process.stdin }).on("line", async line => {
} else if (message.type === "prompt" && !running) {
running = true;
const heartbeat = join(process.env.BRAIN_WORKER_SCRATCH!, "adapter-heartbeat");
const shell = Bun.spawn(["/bin/sh", "-c", 'while :; do printf x >> "$1"; sleep 0.02; done', "heartbeat", heartbeat], { cwd: brain, env: { PATH: "/usr/bin:/bin" }, stdout: "ignore", stderr: "pipe" });
const shell = Bun.spawn(["/bin/sh", "-c", 'while :; do printf x >> "$1"; sleep 0.02; done', "heartbeat", heartbeat], { cwd: brain, env: { PATH: "/usr/bin:/bin" }, stdout: "ignore", stderr: "inherit" });
void shell.exited;
const deadline = performance.now() + 2_000;
while (Bun.file(heartbeat).size < 2) {
if (performance.now() > deadline) throw Error("SCOPE_ADAPTER_HEARTBEAT_DID_NOT_GROW");
await Bun.sleep(1);
}
let call = 0;
const interval = setInterval(() => {
const toolCallId = `loom-${++call}`;
for (let call = 1; call <= 4; call++) {
const toolCallId = `loom-${call}`;
emit({ type: "event", event: { type: "tool_execution_start", toolCallId, toolName: "bash", args: { command: "printf Ithaca" } } });
emit({ type: "event", event: { type: "tool_execution_end", toolCallId, toolName: "bash", result: { content: [{ type: "text", text: "Ithaca" }] }, isError: false } });
if (call === 4) clearInterval(interval);
}, 100);
if (call === PAUSE_AFTER_CALL) {
emit({ type: "fixture_checkpoint", calls: call });
await continuation;
}
}
} else if (message.type === "fixture_continue") {
emit({ type: "fixture_continued" }); continueFixture();
} else if (message.type === "abort") process.exit(0);
});
118 changes: 118 additions & 0 deletions packages/ui-server/tests/helpers/private-maintenance-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/** Re-execute a real test with an owned default maintenance ledger, preserving /tmp and the caller's UID. */
import assert from "node:assert/strict";
import { Database } from "bun:sqlite";
import { existsSync, mkdirSync, mkdtempSync, rmSync, statSync } from "node:fs";
import { join, resolve } from "node:path";
import { invocationChild } from "../../../../scripts/test-invocation-child.js";
import { reserveMaintenanceTemp } from "../../../ui-sdk/src/server/maintenance-temp-budget.js";
import { ledgerLine } from "../../../ui-sdk/tests/maintenance-temp-ledger-protocol.js";

const ROOT = resolve(import.meta.dir, "../../../..");
const ENV = "BRAIN_TEST_PRIVATE_MAINTENANCE";
const escape = (text: string) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const LIMITS = { allowanceBytes: 268435456, budgetBytes: 268435456 };
const defaultLedger = () => `/tmp/brain-maintenance-budget-${process.getuid!()}`;
const inode = (path: string) => { const info = statSync(path); return [info.dev, info.ino]; };
const namespace = (ledger: string, ...command: string[]) => ["unshare", "--user", "--map-current-user", "--keep-caps", "--mount", "bash",
join(ROOT, "scripts/unit-worker-entry.sh"), ledger, ...command];

async function run(command: string[], timeoutMs: number, env = process.env) {
const child = invocationChild(command, { cwd: ROOT, env });
const stdout = new Response(child.stdout).text(), stderr = new Response(child.stderr).text();
const deadline = setTimeout(() => { void child.stop("SIGTERM"); }, timeoutMs);
try {
await child.ready;
const [code, out, err] = await Promise.all([child.exited, stdout, stderr]);
return { code, out, err };
} finally {
clearTimeout(deadline);
await child.stop("SIGTERM"); await child.finish(); await Promise.allSettled([stdout, stderr]);
}
}

/** Return true in the outer test; false only in its acknowledged private child. */
export async function privateMaintenanceTest(file: string, name: string, timeoutMs = 30_000): Promise<boolean> {
const selected = process.env[ENV] ? JSON.parse(process.env[ENV]!) as { file: string; name: string; ledger: string } : undefined;
if (selected?.file === file && selected.name === name) {
assert.deepEqual(inode(defaultLedger()), inode(selected.ledger), "MAINTENANCE_TEST_USES_ITS_OWN_LEDGER");
return false;
}
const root = mkdtempSync("/var/tmp/odysseus-maintenance-test-");
const ledger = join(root, "foreign"), privateLedger = join(root, "private");
for (const path of [ledger, privateLedger]) mkdirSync(path, { mode: 0o700 });
try {
const { code, out, err } = await run(namespace(ledger, process.execPath, "--preload", join(ROOT, "scripts/test-network-child-preload.ts"), import.meta.path, "--hold", file, name, privateLedger, String(timeoutMs)), timeoutMs - 500);
assert.equal(code, 0, `PRIVATE_MAINTENANCE_TEST_COMPLETES: ${name}\n${out}\n${err}`);
assert.match(err, /\b1 pass\b/, "PRIVATE_MAINTENANCE_CHILD_EXECUTED_THE_NAMED_TEST");
return true;
} finally {
// A failed/aborted child must release its own reservations too. Retain
// evidence and fail cleanup rather than deleting any unconfirmed state.
for (const directory of [ledger, privateLedger]) {
const file = join(directory, "reservations.db");
if (!existsSync(file)) continue;
const db = new Database(file, { readonly: true });
try { assert.deepEqual(db.query("SELECT id FROM reservations").all(), [], `Unreleased owned maintenance state retained at ${root}`); }
finally { db.close(); }
}
rmSync(root, { recursive: true, force: true });
}
}

if (import.meta.main && process.argv[2] === "--reserve") {
const reservation = reserveMaintenanceTemp(LIMITS);
const input = Bun.stdin.stream().getReader();
const stop = () => { void input.cancel().catch(() => {}); };
const signals = ["SIGTERM", "SIGINT", "SIGHUP"] as const;
for (const signal of signals) process.on(signal, stop);
try {
console.log(JSON.stringify({ admitted: true, id: reservation.id, pid: process.pid }));
while (!(await input.read()).done) { /* Parent lifetime pipe; EOF or cancellation releases only this reservation. */ }
} finally {
try { await input.cancel(); input.releaseLock(); reservation.release(); }
finally { for (const signal of signals) process.off(signal, stop); }
}
}

if (import.meta.main && process.argv[2] === "--hold") {
const [file, name, ledger, timeout] = process.argv.slice(3) as [string, string, string, string];
const holder = Bun.spawn([process.execPath, import.meta.path, "--reserve"], { stdin: "pipe", stdout: "pipe", stderr: "pipe" });
const stderr = new Response(holder.stderr).text();
let db: Database | undefined;
try {
const receipt = JSON.parse(await ledgerLine(holder.stdout)) as { admitted: boolean; id: string; pid: number };
assert.equal(receipt.admitted, true, "FOREIGN_MAINTENANCE_HOLDER_ACKNOWLEDGED");
db = new Database(join(defaultLedger(), "reservations.db"));
const held = () => {
assert.equal(holder.exitCode, null, "FOREIGN_MAINTENANCE_HOLDER_STILL_LIVE");
process.kill(receipt.pid, 0);
assert.deepEqual(db!.query("SELECT bytes,released FROM reservations WHERE id=?").get(receipt.id), { bytes: LIMITS.allowanceBytes, released: 0 }, "FOREIGN_MAINTENANCE_RESERVATION_STILL_HELD");
};
held();
const parent = statSync(defaultLedger());
console.log("OWNED_MAINTENANCE_HOLDER_ACK", JSON.stringify({ directory: defaultLedger(), uid: process.getuid!(), gid: process.getgid!(), groups: process.getgroups!(),
parent: { uid: parent.uid, gid: parent.gid, mode: parent.mode & 0o777 }, row: db.query("SELECT * FROM reservations WHERE id=?").get(receipt.id) }));
assert.throws(() => reserveMaintenanceTemp(LIMITS), { errorClass: "worker_state_busy" }, "SAME_DOMAIN_MAINTENANCE_STILL_REFUSES");
assert.notDeepEqual(inode(defaultLedger()), inode(ledger), "FOREIGN_AND_TEST_LEDGERS_ARE_DISTINCT");
// Invoke the normal test wrapper directly: Bun's run builtin closes the
// lease FD, and a nested user namespace cannot read its outer owner's FD.
const result = await run(namespace(ledger, process.execPath, join(ROOT, "scripts/test.ts"), "--timeout", timeout, file, "-t", `${escape(name)}$`), Number(timeout) - 1_000,
{ ...process.env, [ENV]: JSON.stringify({ file, name, ledger }) });
process.stdout.write(result.out); process.stderr.write(result.err);
held();
console.log("OWNED_MAINTENANCE_HOLDER_STILL_RESERVED");
assert.equal(result.code, 0, "ISOLATED_TEST_PASSES_WHILE_FOREIGN_RESERVATION_IS_LIVE");
} finally {
holder.stdin.end();
const deadline = setTimeout(() => holder.kill("SIGKILL"), 5_000);
try {
assert.equal(await holder.exited, 0, `FOREIGN_MAINTENANCE_HOLDER_RELEASED_AND_JOINED: ${await stderr}`);
if (db) assert.deepEqual(db.query("SELECT id FROM reservations").all(), [], "OWNED_MAINTENANCE_RESERVATIONS_DRAINED");
console.log("OWNED_MAINTENANCE_HOLDER_RELEASED_AND_JOINED");
} finally {
clearTimeout(deadline);
try { for await (const _chunk of holder.stdout) { /* Drain the receipt stream after its first acknowledged line. */ } await stderr; }
finally { db?.close(); }
}
}
}
39 changes: 39 additions & 0 deletions packages/ui-server/tests/helpers/scope-pi-progress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/** Deliberately backlog fixture events until its explicit progress checkpoint. */
import { Transform } from "node:stream";
import type { launchAgentWorker } from "@schlessera/brain-ui-sdk/internal";

export function scopePiProgress(worker: ReturnType<typeof launchAgentWorker>) {
let pending = "", checkpoint: number | undefined, released = false, continued = false;
const calls: string[] = [], backlog: string[] = [];
const stdout = new Transform({ transform(chunk, _encoding, done) {
try {
pending += chunk.toString();
for (let end; (end = pending.indexOf("\n")) >= 0;) {
const line = pending.slice(0, end); pending = pending.slice(end + 1);
const message = JSON.parse(line);
if (message.type === "fixture_checkpoint") {
checkpoint = message.calls;
this.push(backlog.splice(0).join(""));
} else if (message.type === "fixture_continued") continued = true;
else {
if (message.type === "event" && message.event.type === "tool_execution_start") calls.push(message.event.toolCallId);
if (message.type === "event" && checkpoint === undefined) backlog.push(line + "\n");
else this.push(line + "\n");
}
}
done();
} catch (error) { done(error as Error); }
} });
worker.stdout.pipe(stdout);
return {
worker: { ...worker, stdout }, calls,
get checkpoint() { return checkpoint; },
get continued() { return continued; },
release() {
if (released || worker.stdin.destroyed) return;
released = true;
worker.stdin.write(JSON.stringify({ type: "fixture_continue" }) + "\n");
},
close() { worker.stdout.unpipe(stdout); stdout.destroy(); worker.stdout.destroy(); },
};
}
Loading
Loading