// 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'); });