row 227 R2 (#1728): run test-case2.sh standalone, without the slot controller

Remove the R1 slot-controller ties from the C2-14 fixture: the ROW227_CONTROL_ROOT
check, the native permit wait in bindLaunch, the fixture-cleanup.json write, the
priority.mjs preload and the owned-bwrap.sh override. The launcher uses its own bwrap.

The shell runs the driver from an explicit env -i environment (PATH, LANG, a sandbox
HOME, XDG dirs and TMPDIR, MOSAIC_CONFIG, no global git config) as its own process
group. On a signal the driver gets 3 s by the clock, then its group and any process
whose cwd is in the sandbox get SIGKILL, inside the landing gate's 5 s grace.

The fixture credentials are defined only in scripts/case2/fixture-credentials.mjs.
The new scripts/case2/leak-check.mjs reads them from there and never prints them.
This commit is contained in:
jason.woltje committed 2026-11-02 15:41:17 -06:00
1 parent 46c07df75f
commit 04e1ce24cc
6 files changed
+77 -54

No files matched your search

-1
View File
@@ -1,6 +1,5 @@
// C2-14 adds an observed real library tool read before C2-06's scripted reply adapter.
// The reply plan is fixture input, never evidence that a lookup or guard actually ran.
import '../../agents/steelbeak/work/queue-227/gate/priority.mjs';
import { appendFileSync, existsSync, readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { callTool } from '../../packages/harness/src/tools.mjs';
+3 -6
View File
@@ -1,7 +1,7 @@
// C2-14 (#1728): one scratch-root acceptance driver. No live host or real model.
// The human uses bindHuman in-process because a development agent cannot prove human CLI ancestry.
// The creative session is real createLauncher -> runner -> C2-06's scripted citation adapter.
// All 17 steps run against landed APIs. Execution is serialized by Sage (Mos1961).
// All 17 steps run against landed APIs. It runs standalone from scripts/test-case2.sh, as the landing gate runs it.
import { mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';
import { DatabaseSync } from 'node:sqlite';
@@ -10,17 +10,14 @@ import { BASE, MARKER, git, mappingEntry } from '../../packages/library/tests/he
import * as library from '../../packages/library/src/index.mjs';
import { launchFixture } from './launch.mjs';
import { consoleAcceptance } from './console.mjs';
import { FAKE_ZAI_KEY } from './fixture-credentials.mjs';
import { FETCH_TOKEN, SEAT_TOKEN, HUMAN_TOKEN, FIXTURE_CREDENTIALS } from './fixture-credentials.mjs';
const FETCH_TOKEN = 'fx227-fetch-q7-2270';
const SEAT_TOKEN = 'fx227-seat-q7-2270';
const HUMAN_TOKEN = 'fx227-human-q7-2270';
const USER = 'books-bot', BUSINESS = 'acme';
const CHARACTER = 'Book One/Characters/Mara Vell.md';
const SUPPORT = 'Book One/Notes/Eyes.md';
const WRITE = 'Book One/Notes/Agent.md';
const EDITOR = { name: 'Fixture author', email: '[email protected]', user: 'writer' };
const secrets = [FETCH_TOKEN, SEAT_TOKEN, HUMAN_TOKEN, FAKE_ZAI_KEY];
const secrets = FIXTURE_CREDENTIALS;
const redact = value => secrets.reduce((s, token) => s.replaceAll(token, '[fixture credential]'), String(value));
let failures = 0;
const out = line => process.stdout.write(redact(line) + '\n');
+7 -1
View File
@@ -1,2 +1,8 @@
// Made up and confined to the nonproxy fake pi/zai fixture. Never a live credential.
// Made-up fixture credentials for C2-14 (#1728). Never a live credential. No other file under
// scripts/ holds them: the driver (which hands them to the fake Gitea), the fixture launcher (the
// fake pi/zai key) and the shell's leak check import them from here.
export const FETCH_TOKEN = 'fx227-fetch-q7-2270';
export const SEAT_TOKEN = 'fx227-seat-q7-2270';
export const HUMAN_TOKEN = 'fx227-human-q7-2270';
export const FAKE_ZAI_KEY = 'fx227-zai-q7-2270';
export const FIXTURE_CREDENTIALS = Object.freeze([FETCH_TOKEN, SEAT_TOKEN, HUMAN_TOKEN, FAKE_ZAI_KEY]);
+4 -27
View File
@@ -1,5 +1,5 @@
// C2-14 fixture: the real host/launcher with C2-06's scripted citation adapter. No model binary runs.
import { chmodSync, mkdirSync, writeFileSync, existsSync, readdirSync, readFileSync, renameSync } from 'node:fs';
import { chmodSync, mkdirSync, writeFileSync, existsSync, readdirSync } from 'node:fs';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { BusError, writeGrantEvent } from '../../packages/bus/src/broker.mjs';
@@ -43,21 +43,6 @@ function seedGrant(dataRoot, storeRoot, token) {
}
export async function launchFixture(root, entry, { fetchToken, seatToken, humanToken, ca }) {
const control = process.env.ROW227_CONTROL_ROOT;
if (!control) throw new Error('row227-native-control-required');
const nativeNonce = JSON.parse(readFileSync(join(control, 'control.json'), 'utf8')).nonce;
const nativePermit = async record => {
const file = join(control, 'authorizations', `${record.pid}-${record.startTime}.json`);
const end = Date.now() + 5000;
while (!existsSync(file)) {
if (Date.now() >= end) throw Object.assign(new Error('native-host-permit-timeout'), { code: 'native-host-permit-timeout' });
await new Promise(resolve => setTimeout(resolve, 10));
}
const permit = JSON.parse(readFileSync(file, 'utf8'));
if (permit.nonce !== nativeNonce || permit.binding?.role !== 'runner'
|| permit.binding.wrapper?.[0] !== record.pid || permit.binding.wrapper?.[1] !== record.startTime
|| permit.binding.run?.split('/').at(-1) !== record.run) throw Object.assign(new Error('native-host-permit-refused'), { code: 'native-host-permit-refused' });
};
const configDir = join(root, 'config'), storeRoot = join(configDir, 'secrets', 'agents');
const slotDir = join(configDir, 'secrets', 'acme');
mkdirSync(slotDir, { recursive: true, mode: 0o700 });
@@ -103,9 +88,6 @@ export async function launchFixture(root, entry, { fetchToken, seatToken, humanT
for (const cleanup of cleanups.reverse()) await cleanup();
const directory = join(f.dataRoot, 'control', 'runtimes');
const remaining = existsSync(directory) ? readdirSync(directory) : [];
writeFileSync(join(control, 'fixture-cleanup.pending'), JSON.stringify({ version: 1, dataRoot: f.dataRoot, runtimeEntries: remaining,
runs: records.map(({ pid, startTime, run }) => ({ pid, startTime, run })) }) + '\n', { mode: 0o600 });
renameSync(join(control, 'fixture-cleanup.pending'), join(control, 'fixture-cleanup.json'));
if (remaining.length) throw new Error('owned-runtime-cleanup-incomplete');
}
}
@@ -127,7 +109,6 @@ export async function launchFixture(root, entry, { fetchToken, seatToken, humanT
const host = { business: 'acme', path: runtime.path, dataRoot: f.dataRoot,
op: wrap(m => { if (!Object.hasOwn(ops, m.op)) throw new BusError('invalid-host-request'); return ops[m.op](m); }),
bindLaunch: wrap(async record => {
await nativePermit(record);
const bound = runtime.bindLaunch(record);
records.push(record); caps.set(record.thread, bound.cap);
return bound;
@@ -135,15 +116,11 @@ export async function launchFixture(root, entry, { fetchToken, seatToken, humanT
beforeClose: fn => void hooks.push(fn),
};
const adapter = join(root, 'citation-adapter.sh');
writeFileSync(adapter, `cp ${shell(plan)} "$(dirname "$MOSAIC_TURN_MARKER")/fixture-steps.json"\nexec ${shell(process.execPath)} --import ${shell(fileURLToPath(new URL('../../agents/steelbeak/work/queue-227/gate/priority.mjs', import.meta.url)))} ${shell(ADAPTER)}\n`, { mode: 0o644 });
const priority = fileURLToPath(new URL('../../agents/steelbeak/work/queue-227/gate/priority.mjs', import.meta.url));
const wrapper = fileURLToPath(new URL('../../agents/steelbeak/work/queue-227/gate/bwrap.py', import.meta.url));
const bwrap = join(root, 'owned-bwrap.sh');
writeFileSync(bwrap, `#!/bin/sh\nexec /usr/bin/python3 ${shell(wrapper)} ${shell(control)} ${shell(priority)} \"$@\"\n`, { mode: 0o700 });
const launcher = createLauncher({ host, system, env: f.env, bwrap,
writeFileSync(adapter, `cp ${shell(plan)} "$(dirname "$MOSAIC_TURN_MARKER")/fixture-steps.json"\nexec ${shell(process.execPath)} ${shell(ADAPTER)}\n`, { mode: 0o644 });
const launcher = createLauncher({ host, system, env: f.env,
makeLoader: await fakeHarnesses({ after: fn => cleanups.push(fn) }, { pi: adapter }, write =>
write('channel', 'webui', `export * from ${JSON.stringify(new URL('../../packages/plugins/channel/webui/index.mjs', import.meta.url).href)};`)),
readOnly: [ADAPTER, REPLY_ADAPTER, plan, priority], harnessVersion: '0.85.1',
readOnly: [ADAPTER, REPLY_ADAPTER, plan], harnessVersion: '0.85.1',
sessionDefaults: { pollInterval: 50 }, threadTickMs: 50, log: line => logs.push(line),
});
await launcher.listen();
+16
View File
@@ -0,0 +1,16 @@
// C2-14 (#1728): the shell's check that no fixture credential reached the driver's output.
// The values come from fixture-credentials.mjs, the one place they are defined, and are never
// printed. Exit 0: none of the files holds one. Exit 1: one does. Exit 2: no file given, or a
// file could not be read, so nothing was checked.
import { readFileSync } from 'node:fs';
import { FIXTURE_CREDENTIALS } from './fixture-credentials.mjs';
const files = process.argv.slice(2);
if (!files.length || !FIXTURE_CREDENTIALS.length) process.exit(2);
let found = false;
for (const file of files) {
let bytes;
try { bytes = readFileSync(file); } catch { process.exit(2); }
if (FIXTURE_CREDENTIALS.some(value => bytes.includes(value))) found = true;
}
process.exit(found ? 1 : 0);
+47 -19
View File
@@ -1,27 +1,57 @@
#!/usr/bin/env bash
# C2-14 (#1728, row227): one acceptance run on a scratch root, no skipped steps.
# C2-14 (#1728, row 227): one acceptance run on a scratch root, no skipped steps.
# Entry point for the real launcher/runner, C2-06 scripted adapter, fake TLS Gitea and Console DOM.
# Run only in the serialized target slot (Mos1961); full gates/review/landing belong to the parent.
# Stands alone: the landing gate runs it as `bash scripts/test-case2.sh` with nothing else set up.
# The only human stand-in is bindHuman in-process; a development agent cannot prove human CLI ancestry.
set -uo pipefail
cd "$(dirname "$0")/.."
SANDBOX="$(mktemp -d)"
DRIVER_PID=''
CASE2_HOME="$SANDBOX/home"
CASE2_TMP="$SANDBOX/t"
CASE2_CONFIG="$SANDBOX/root/config/config.json"
mkdir -p "$CASE2_HOME"
mkdir -p "$CASE2_HOME" "$CASE2_TMP"
# The driver runs from an explicit environment: PATH and LANG, a sandbox HOME, XDG dirs and TMPDIR,
# and the variables it needs. Nothing else from the caller, a credential variable included, reaches
# the driver or the launcher it starts. TMPDIR in the sandbox takes the Chromium profile and the
# plug-in tree with it when the sandbox goes.
CLEAN_ENV=(env -i PATH="$PATH" LANG=C.UTF-8 HOME="$CASE2_HOME"
XDG_CONFIG_HOME="$CASE2_HOME/.config" XDG_CACHE_HOME="$CASE2_HOME/.cache"
XDG_DATA_HOME="$CASE2_HOME/.local/share" XDG_STATE_HOME="$CASE2_HOME/.local/state"
TMPDIR="$CASE2_TMP" MOSAIC_CONFIG="$CASE2_CONFIG" GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_NOSYSTEM=1)
alive() {
local stat
read -r stat 2>/dev/null <"/proc/$1/stat" || return 1
stat="${stat##*) }"
[ "${stat%% *}" != Z ]
}
# The driver leads its own process group (setsid below). On a signal it gets SIGTERM and 3 s by the
# clock to close, inside the landing gate's 5 s grace, then its group gets SIGKILL. The launcher
# starts each bwrap session in a group of its own, with its working directory in the sandbox; any
# such process still alive then gets SIGKILL too, before the sandbox goes. One find reads every
# cwd link, so the sweep stays in milliseconds however many processes the host runs.
now_us() { echo "${EPOCHREALTIME/[.,]/}"; }
stop_driver() {
local deadline p left=0
if [ -n "$DRIVER_PID" ]; then
kill -TERM "$DRIVER_PID" 2>/dev/null
deadline=$(( $(now_us) + 3000000 ))
while alive "$DRIVER_PID" && [ "$(now_us)" -lt "$deadline" ]; do sleep 0.1; done
kill -KILL -- "-$DRIVER_PID" 2>/dev/null
wait "$DRIVER_PID" 2>/dev/null
DRIVER_PID=''
fi
for p in $(find /proc/[0-9]*/cwd -maxdepth 0 \( -lname "$SANDBOX" -o -lname "$SANDBOX/*" \) 2>/dev/null); do
p="${p#/proc/}"
kill -KILL "${p%/cwd}" 2>/dev/null && left=$((left+1))
done
if [ "$left" -gt 0 ]; then echo "note: $left process(es) under the sandbox outlived the driver; sent SIGKILL"; fi
}
cleanup() {
local result=$?
trap - EXIT INT TERM
if [ -n "$DRIVER_PID" ]; then
kill -TERM "$DRIVER_PID" 2>/dev/null || true
wait "$DRIVER_PID" 2>/dev/null || true
fi
# The private controlled invocation delegates removal to the controller only after
# its source/native/capture/closure checks. Unset marker retains standalone cleanup.
if [ "${ROW227_CONTROLLER_CLEANUP:-}" != 1 ]; then
rm -rf "$SANDBOX"
fi
stop_driver
rm -rf "$SANDBOX"
exit "$result"
}
trap cleanup EXIT
@@ -39,10 +69,10 @@ check() {
}
DRIVER=scripts/case2/e2e.mjs
STEPS='S1a S1b S2a S2b S2c S2d S3a S3b S3c S4 S5a S5b S6 S7 S8 S9 S10'
node --check "$DRIVER" >/dev/null 2>&1
"${CLEAN_ENV[@]}" node --check "$DRIVER" >/dev/null 2>&1
check "syntax: $DRIVER" "$?"
OUT="$SANDBOX/steps.txt" ERR="$SANDBOX/stderr.txt"
env HOME="$CASE2_HOME" XDG_CONFIG_HOME="$CASE2_HOME/.config" MOSAIC_CONFIG="$CASE2_CONFIG" GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_NOSYSTEM=1 node "$DRIVER" "$SANDBOX/root" >"$OUT" 2>"$ERR" &
"${CLEAN_ENV[@]}" setsid node "$DRIVER" "$SANDBOX/root" >"$OUT" 2>"$ERR" &
DRIVER_PID=$!
wait "$DRIVER_PID"
RC=$?
@@ -60,10 +90,8 @@ while IFS= read -r line; do
done <"$OUT"
[ "${seen# }" = "$STEPS" ]
check 'every acceptance step ran once, in order, with nothing skipped' "$?"
if grep -qF -e 'fx227-fetch-q7-2270' -e 'fx227-seat-q7-2270' -e 'fx227-human-q7-2270' -e 'fx227-zai-q7-2270' "$OUT" "$ERR"; then
check 'fixture credentials are in no driver output' 1
else
check 'fixture credentials are in no driver output' 0
fi
# The values come from scripts/case2/fixture-credentials.mjs and are never printed.
"${CLEAN_ENV[@]}" node scripts/case2/leak-check.mjs "$OUT" "$ERR"
check 'fixture credentials are in no driver output' "$?"
printf '\nselftest: %s passed, %s failed, 0 skipped\n' "$PASS" "$FAIL"
[ "$FAIL" -eq 0 ]