Merge remote-tracking branch 'origin/main' into gsta161/opus-lite-loadout

This commit is contained in:
garrytan committed 2026-10-31 14:01:12 +00:00
commit 74c3b3db1f
14 files changed
+172 -20

No files matched your search

+65
View File
@@ -304,6 +304,71 @@ describe('gstack-evidence check', () => {
expect(chk.status).toBe(0);
});
// Value: protects=a repository that gitignores .bun/ still gets a fingerprint (GST-449): an exclude pathspec naming an ignored path made
// `git add -A` exit 1, so gstack-wtree failed and every review-log start and evidence check lost its fingerprint; fails_when=gstack-wtree
// exits non-zero or the lane records no wtree with .bun/ (or /.bun) in .gitignore; why_new=the GST-421 test covered only an unignored
// .bun/ (harness-evals#148); seam=none; no_claim=Windows git
test('GST-449: with .bun/ gitignored, gstack-wtree still fingerprints and a lane that writes .bun/ grades FRESH', () => {
for (const pattern of ['.bun/', '/.bun']) {
fs.writeFileSync(path.join(repoDir, '.gitignore'), `${pattern}\n`);
git('add .gitignore');
git(`commit -q -m "ignore ${pattern}"`);
fs.mkdirSync(path.join(repoDir, '.bun'), { recursive: true });
fs.writeFileSync(path.join(repoDir, '.bun', 'x'), 'cache\n');
const wt = runBin(path.join(ROOT, 'bin', 'gstack-wtree'), [], { cwd: repoDir });
expect(wt.stderr).toBe('');
expect(wt.status).toBe(0);
expect(wt.stdout.trim()).toMatch(/^[0-9a-f]{40}$/);
const r = run(['run', '--label', 'tests', '--', 'echo cache2 > .bun/y && echo green']);
expect(r.status).toBe(0);
expect(records().pop().wtree).toMatch(/^[0-9a-f]{40}$/);
expect(run(['check', '--label', 'tests']).stdout).toContain('EVIDENCE: FRESH label=tests');
fs.rmSync(path.join(repoDir, '.bun'), { recursive: true, force: true });
}
});
// Value: protects=a repository that ignores .bun/ while still tracking a file under it gets a fingerprint (GST-449 review): the
// index-aware check-ignore called .bun not ignored, so the exclude pathspec came back and `git add -A` exited 1; fails_when=gstack-wtree
// exits non-zero, or its tree differs from a plain `git add -A`, or an edit or delete of the tracked file leaves it unchanged;
// why_new=the pattern loop above starts from an untracked .bun/ only; seam=none; no_claim=Windows git
test('GST-449: an ignored .bun/ that still tracks a file fingerprints like a plain git add -A, and edits to the tracked file count', () => {
fs.mkdirSync(path.join(repoDir, '.bun'), { recursive: true });
fs.writeFileSync(path.join(repoDir, '.bun', 'tracked'), 'v1\n');
git('add -f .bun/tracked');
git('commit -q -m "track .bun/tracked"');
fs.writeFileSync(path.join(repoDir, '.gitignore'), '.bun/\n');
git('add .gitignore');
git('commit -q -m "ignore .bun/"');
fs.writeFileSync(path.join(repoDir, '.bun', 'cache'), 'cache\n');
const wtree = () => {
const wt = runBin(path.join(ROOT, 'bin', 'gstack-wtree'), [], { cwd: repoDir });
expect(wt.stderr).toBe('');
expect(wt.status).toBe(0);
return wt.stdout.trim();
};
const plain = () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-wtree-idx-'));
try {
const env = { ...process.env, GIT_INDEX_FILE: path.join(dir, 'index') };
spawnSync('git', ['read-tree', 'HEAD'], { cwd: repoDir, env, timeout: 10_000 });
spawnSync('git', ['add', '-A'], { cwd: repoDir, env, timeout: 10_000 });
return spawnSync('git', ['write-tree'], { cwd: repoDir, env, encoding: 'utf-8', timeout: 10_000 }).stdout.trim();
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
};
const clean = wtree();
expect(clean).toBe(plain());
fs.writeFileSync(path.join(repoDir, '.bun', 'tracked'), 'v2\n');
const edited = wtree();
expect(edited).not.toBe(clean);
expect(edited).toBe(plain());
fs.rmSync(path.join(repoDir, '.bun', 'tracked'));
const deleted = wtree();
expect(deleted).not.toBe(clean);
expect(deleted).toBe(plain());
});
test('GST-421: a lane that writes untracked source grades STALE and the reason names the path', () => {
const r = run(['run', '--label', 'tests', '--', 'mkdir -p src && echo "export {}" > src/new.ts && echo green']);
expect(r.status).toBe(0);
+17
View File
@@ -22,6 +22,7 @@ const GATE = 'node scripts/remote-gate.js';
const GATE_SCRIPT = [
"const fs = require('node:fs');",
"fs.writeFileSync('../gate-env.json', JSON.stringify({ tree: process.env.PREGATE_TREE, cap: process.env.PREGATE_SPEND_CAP_USD, timeout: process.env.PREGATE_TIMEOUT_S, files: fs.readFileSync(process.env.PREGATE_FILES_FILE, 'utf8') }));",
"if (process.env.GATE_QUIET_MS) Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, Number(process.env.GATE_QUIET_MS));",
"if (process.env.GATE_SILENT) process.exit(0);",
"const spent = process.env.GATE_SPENT ? ` spent=${process.env.GATE_SPENT}` : '';",
"console.log(`PREGATE_REMOTE: tree=${process.env.GATE_TREE || process.env.PREGATE_TREE} tests=${process.env.GATE_TESTS ?? 3} failed=${process.env.GATE_FAILED ?? 0}${spent}`);",
@@ -187,6 +188,22 @@ describe('gstack-pregate --remote (C-c)', () => {
expect(fs.existsSync(path.join(repo.root, 'gate-env.json'))).toBe(false);
}, 60_000);
// Value: protects=a remote gate that waits quietly on CI is bounded by its deadline (--timeout, else timeout_s), not killed by the
// test-output stall timer; a gate past its deadline still stays uncleared (GST-431); fails_when=the stall timer kills a quiet gate
// inside its deadline, or a gate past --timeout clears; why_new=a CI-waiting gate died at 300 s on qm (CAPY-1); seam=fake gate;
// no_claim=the Ubicloud VM
test('a gate that is quiet past the stall timeout but inside its deadline clears; past --timeout it stays uncleared', () => {
const repo = gated();
const quiet = remote(repo, ['--remote', '--allow-repo-commands', '--spend-cap', '2', '--stall-timeout', '1', '--timeout', '60'], { GATE_QUIET_MS: '2500' });
expect(quiet.status).toBe(EXIT.ok);
expect(pregateFile(repo).verdict).toBe('requires-remote cleared');
const late = remote(repo, ['--remote', '--allow-repo-commands', '--spend-cap', '2', '--timeout', '2'], { GATE_QUIET_MS: '8000' });
expect(late.status).toBe(EXIT.fail);
expect(lanes(repo)).toMatchObject({ status: 'incomplete', code: 'PREGATE_INCOMPLETE' });
expect(lanes(repo).detail).toContain('the remote gate did not finish (timed out after 2 s, --timeout)');
expect(pregateFile(repo).requires_remote[0].receipt).toBeNull();
}, 60_000);
test('a wrong tree, zero tests, a failure, a missing line or a non-zero exit leaves the obligation uncleared and the check incomplete; unreported spend counts at the cap', () => {
const repo = gated();
const cases: Array<[Record<string, string>, string]> = [
+22
View File
@@ -91,6 +91,28 @@ describe('supervise: process group, log file, stall and deadline', () => {
expect(r.lines.filter(l => l.startsWith('PREGATE_HEARTBEAT: fixture running ')).length).toBeGreaterThanOrEqual(2);
expect(r.lines.some(l => l.startsWith('PREGATE_TEST:'))).toBe(false);
});
// Value: protects=a remote gate's heartbeat says what the gate last printed (GST-431); fails_when=the heartbeat for a gate that
// reports its CI wait shows only "last file none reported"; why_new=a CI-waiting gate looked dead on qm (CAPY-1); seam=none; no_claim=none
test('with lastLine, a heartbeat names the command\'s last output line', async () => {
const dir = scratch();
const r = await run(dir, 'echo "waiting on run 7 (in_progress)"; sleep 1.2; echo done', { heartbeatMs: 300, lastLine: true });
expect(r.outcome).toBe('exited');
expect(r.lines.some(l => l.startsWith('PREGATE_HEARTBEAT: fixture running ') && l.includes('last output: waiting on run 7 (in_progress)'))).toBe(true);
const redraw = await run(dir, "printf 'run 7 queued\\r\\033[2Krun 7 in_progress\\n'; sleep 1.2", { heartbeatMs: 300, lastLine: true });
expect(redraw.lines.some(l => l.endsWith('last output: run 7 in_progress'))).toBe(true);
expect(redraw.lines.some(l => l.includes('\x1b') || l.includes('\r'))).toBe(false);
});
// Value: protects=reading the last line is best effort: a log that disappears mid-run never throws out of the supervisor's timer, which would
// abandon the gate's deadline and cleanup (GST-431 review); fails_when=lastLine sampling throws ENOENT; why_new=lastLine is new; seam=none; no_claim=none
test('with lastLine, a log removed mid-run still ends in a normal outcome', async () => {
const dir = scratch();
const log = path.join(dir, 'run.log');
fs.writeFileSync(log, '');
const r = await supervise({ command: `echo hi; sleep 0.4; rm -f "${log}"; sleep 1.2; echo done`, cwd: dir, env: process.env, log, deadline: Date.now() + 30_000, stallMs: 0, graceMs: 300, pollMs: 50, heartbeatMs: 300, lastLine: true, label: 'fixture', progress: () => {} });
expect(r.outcome).toBe('exited');
});
});
describe('supervise: per-file progress adapters', () => {