Candidate morgana/row24494ab4c2feona36523713, manifest 6e7e3e3c (8 paths). Webby APPROVE r1, rev 3008 (webby-0084, REVIEW-r1.md at webby/reviews ebb3d83bf; request #1757 c30154). Gate on Sage's slot at81e037d20(sage-2147): the five gate-lane files whole 51/0, test-task.sh 91/0, test-release.sh 14/0, mutants M-G, M-L, M-A, M-T and M-F killed.94ab4c2feadds only BUILD.md results. Webby's drift check: a36523713..d37210288 changes none of the 8 paths and no path either suite reads; merge-tree clean. Rulings A and C (sage-1914, sage-2061). The dropped real-model coverage goes to DEFERRED in the follow-up doc commit.
101 lines
7.2 KiB
JavaScript
101 lines
7.2 KiB
JavaScript
// Row 244: no gate step mounts the host's pi auth file or makes a provider call.
|
|
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { mkdtempSync, mkdirSync, writeFileSync, chmodSync, symlinkSync, rmSync, cpSync, readFileSync, existsSync, realpathSync } from 'node:fs';
|
|
import { spawnSync } from 'node:child_process';
|
|
import { join, relative } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { guardDocker } from '../../../scripts/testing/lane-docker.mjs';
|
|
import { cleanEnv } from '../../../scripts/testing/gate-lane.mjs';
|
|
|
|
const REPO = realpathSync(fileURLToPath(new URL('../../../', import.meta.url)));
|
|
const HOST_AUTH = '/home/jwoltje/.pi/agent/auth.json';
|
|
const LIVE_SKIP = 'skip live provider cases (row 244: no provider call in the gate)';
|
|
const scratch = t => { const root = mkdtempSync('/mnt/storage/scratch/gt-'); t.after(() => rmSync(root, { recursive: true, force: true })); return root; };
|
|
const fixture = (dir, name = 'auth.json') => { const file = join(dir, name); writeFileSync(file, '{}'); chmodSync(file, 0o600); return file; };
|
|
const RUN = ['compose', 'run', '--rm', '-T', 'mosaic-agent', 'prompt'];
|
|
|
|
test('guardDocker refuses a compose run unless PI_AUTH_FILE is a regular file inside the lane temp root', t => {
|
|
const tempRoot = scratch(t), outside = scratch(t);
|
|
const run = { tempRoot, checkout: join(tempRoot, 'checkout'), imageId: 'sha256:' + 'a'.repeat(64), tag: 'mosaic-poc-agent:fixture' };
|
|
const lane = fixture(tempRoot, 'pi-auth.json');
|
|
mkdirSync(join(tempRoot, 'suite', 'pi'), { recursive: true });
|
|
const suite = fixture(join(tempRoot, 'suite', 'pi'));
|
|
for (const file of [lane, suite]) assert.equal(guardDocker(RUN, run, { PI_AUTH_FILE: file })[0], 'compose');
|
|
const link = join(tempRoot, 'escape.json'); symlinkSync(fixture(outside), link);
|
|
mkdirSync(join(tempRoot, 'dir.json'));
|
|
for (const env of [{}, { PI_AUTH_FILE: '' }, { PI_AUTH_FILE: HOST_AUTH }, { PI_AUTH_FILE: fixture(outside) }, { PI_AUTH_FILE: link },
|
|
{ PI_AUTH_FILE: join(tempRoot, 'missing.json') }, { PI_AUTH_FILE: join(tempRoot, 'dir.json') }, { PI_AUTH_FILE: relative(process.cwd(), lane) }])
|
|
assert.throws(() => guardDocker(RUN, run, env), /Compose auth mount not a lane fixture/, JSON.stringify(env));
|
|
// The option check still comes first, and the other allowed verbs need no auth file.
|
|
assert.throws(() => guardDocker(['compose', 'run', '--build', 'mosaic-agent'], run, {}), /options not allowed/);
|
|
assert.deepEqual(guardDocker(['info'], run, {}), ['info']);
|
|
assert.deepEqual(guardDocker(['image', 'inspect', run.tag], run, {}), ['image', 'inspect', run.tag]);
|
|
});
|
|
|
|
test('cleanEnv points PI_AUTH_FILE at the lane fixture and drops MOSAIC_LIVE_PROVIDER', t => {
|
|
const tempRoot = scratch(t), saved = { PI_AUTH_FILE: process.env.PI_AUTH_FILE, MOSAIC_LIVE_PROVIDER: process.env.MOSAIC_LIVE_PROVIDER };
|
|
process.env.PI_AUTH_FILE = HOST_AUTH; process.env.MOSAIC_LIVE_PROVIDER = '1';
|
|
let env;
|
|
try { env = cleanEnv({ tempRoot, lane: 'a', stateRoot: join(tempRoot, 'state'), out: join(tempRoot, 'out') }, { project: 'fixture' }, '/fake/docker'); }
|
|
finally { for (const [k, v] of Object.entries(saved)) if (v === undefined) delete process.env[k]; else process.env[k] = v; }
|
|
assert.equal(env.PI_AUTH_FILE, join(tempRoot, 'pi-auth.json'));
|
|
assert.equal(env.MOSAIC_LIVE_PROVIDER, undefined);
|
|
});
|
|
|
|
// Both suites run in a private copy of the checkout: test-release.sh rewrites RELEASE while it runs.
|
|
// A fake docker records each compose run's PI_AUTH_FILE and MOSAIC_ADAPTER, and answers as the mock adapter.
|
|
// It never reads or stats the host auth file.
|
|
const SKIP_COPY = new Set(['.git', 'node_modules', 'v1', 'agents', 'docs', 'BUILD-LOG.md', join('scripts', 'foundation')]);
|
|
function suiteLane(t) {
|
|
const root = scratch(t), checkout = join(root, 'c'), bin = join(root, 'bin'), tmp = join(root, 'tmp');
|
|
cpSync(REPO, checkout, { recursive: true, filter: src => !SKIP_COPY.has(relative(REPO, src)) });
|
|
symlinkSync(realpathSync(join(REPO, 'node_modules')), join(checkout, 'node_modules'));
|
|
mkdirSync(bin); mkdirSync(tmp, { mode: 0o700 });
|
|
writeFileSync(join(bin, 'docker'), `#!/bin/bash
|
|
[ "$1" = compose ] || exit 0
|
|
f="\${PI_AUTH_FILE-}"
|
|
if [ -z "\${PI_AUTH_FILE+x}" ]; then kind=unset; elif [ "$f" = ${HOST_AUTH} ]; then kind=host; else kind="$(stat -c '%F %a' -- "$f" 2>/dev/null || echo missing)"; fi
|
|
printf '%s\\t%s\\t%s\\n' "$f" "$kind" "\${MOSAIC_ADAPTER-}" >> "$FAKE_DOCKER_RECORD"
|
|
[ "\${MOSAIC_ADAPTER-}" = mock ] && printf '%s\\n' "\${MOSAIC_MOCK_RESPONSE-}"
|
|
exit 0
|
|
`);
|
|
chmodSync(join(bin, 'docker'), 0o700);
|
|
return (script, extra = {}) => {
|
|
const record = join(root, `${script}-${Object.keys(extra).length}.tsv`), begin = performance.now();
|
|
const env = { PATH: `${bin}:${process.env.PATH}`, HOME: process.env.HOME, TMPDIR: tmp, LANG: 'C.UTF-8', FAKE_DOCKER_RECORD: record, ...extra };
|
|
const r = spawnSync('bash', [join(checkout, 'scripts', script)], { env, encoding: 'utf8', timeout: 600000 });
|
|
t.diagnostic(JSON.stringify({ script, extra: Object.keys(extra), wallMs: Math.round(performance.now() - begin), status: r.status }));
|
|
const runs = existsSync(record) ? readFileSync(record, 'utf8').trim().split('\n').filter(Boolean).map(l => { const [file, kind, adapter] = l.split('\t'); return { file, kind, adapter }; }) : [];
|
|
return { out: r.stdout + r.stderr, runs, tmp };
|
|
};
|
|
}
|
|
const fixtureRuns = (runs, tmp) => runs.every(r => r.file !== HOST_AUTH && r.file.startsWith(tmp + '/') && r.kind === 'regular file 600');
|
|
|
|
test('test-release.sh activates through the mock adapter with a 0600 fixture auth file', t => {
|
|
const { out, runs, tmp } = suiteLane(t)('test-release.sh');
|
|
assert.ok(runs.length >= 3, `fault, healthy and repeat activations reach Compose\n${out}`);
|
|
assert.ok(fixtureRuns(runs, tmp), JSON.stringify(runs));
|
|
assert.deepEqual([...new Set(runs.map(r => r.adapter))], ['mock'], 'ruling A: no pi adapter in the gate');
|
|
assert.match(out, /OK +healthy activation succeeds/); assert.match(out, /OK +fault-injected activation refuses/);
|
|
});
|
|
|
|
test('test-task.sh mounts only its fixture, uses only the mock adapter and prints the live skip twice', t => {
|
|
const { out, runs, tmp } = suiteLane(t)('test-task.sh');
|
|
assert.ok(runs.length >= 1, out);
|
|
assert.ok(fixtureRuns(runs, tmp), JSON.stringify(runs));
|
|
assert.deepEqual([...new Set(runs.map(r => r.adapter))], ['mock']);
|
|
assert.equal(out.split('\n').filter(l => l === LIVE_SKIP).length, 2, out);
|
|
assert.match(out, /OK +hello task succeeds with exact marker/);
|
|
});
|
|
|
|
test('M-T: MOSAIC_LIVE_PROVIDER=1 runs the real-model cases, which fail under a fixture auth file', t => {
|
|
const lane = suiteLane(t), auth = join(scratch(t), 'operator-auth.json'); writeFileSync(auth, '{}'); chmodSync(auth, 0o600);
|
|
const { out, runs } = lane('test-task.sh', { MOSAIC_LIVE_PROVIDER: '1', PI_AUTH_FILE: auth });
|
|
assert.ok(!out.includes(LIVE_SKIP), out);
|
|
// The fake answers a pi run with nothing: the fork base misses REMEMBERED, recall misses the name.
|
|
assert.match(out, /FAIL fork base: teach succeeds/); assert.match(out, /FAIL recalled user name/);
|
|
assert.ok(runs.some(r => r.adapter === 'pi'), JSON.stringify(runs));
|
|
assert.ok(runs.every(r => r.file === auth), 'live mode keeps the operator\'s PI_AUTH_FILE');
|
|
});
|