Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b8f683a
feat(review): run the reviewed repository's own commands behind a con…
wenshao Aug 22, 2026
bbea6d9
Merge branch 'main' into feat/review-sandbox-ci
wenshao Aug 22, 2026
2c070a1
chore(review): regenerate settings.schema.json for review.sandbox
wenshao Aug 22, 2026
97ab3f4
Merge remote-tracking branch 'origin/feat/review-sandbox-ci' into fea…
wenshao Aug 22, 2026
c3fa33c
fix(review): make `required` actually refuse, and fix four wiring bug…
wenshao Aug 22, 2026
04a6ee1
fix(review): stop the reviewed repository from deciding its own conta…
wenshao Aug 22, 2026
42abd48
fix(review): close the hand-off arm, and three fixes that were wrong …
wenshao Aug 22, 2026
d0bbd4f
fix(review): the hand-off gate was dead code, and three more edges
wenshao Aug 23, 2026
a163eee
fix(review): put the hand-off conversion at the one exit, and make th…
wenshao Aug 23, 2026
963c421
fix(review): the third continuation exit destroyed the report it was …
wenshao Aug 23, 2026
7a9869e
fix(review): reap the container on every abnormal exit, not only the …
wenshao Aug 23, 2026
12ee718
fix(review): scrub the container client env by provenance, not by name
wenshao Aug 23, 2026
a08f76b
fix(review): drop --user on rootless runtimes, and take the base-side…
wenshao Aug 23, 2026
a3c1f24
fix(review): refuse mount roots the -v grammar cannot spell, and stop…
wenshao Aug 23, 2026
dc4397e
test(review): gate the mount-root cases Windows cannot be asked
wenshao Aug 24, 2026
e3bfba5
test(review): make the Windows pin actually reach the check it pins
wenshao Aug 24, 2026
623fef6
test(review): skip the second uid case instead of returning, and rest…
wenshao Aug 24, 2026
4ddc611
test(review): drop the comment last round's edit left stranded
wenshao Aug 24, 2026
b11492e
test(review): pin the four decisions this feature is sold on
wenshao Aug 24, 2026
9f5e5ad
fix(review): read the policy setting the way an operator writes it
wenshao Aug 24, 2026
fd97d08
test(review): finish the policy table instead of one row of it
wenshao Aug 24, 2026
62040e4
test(review): exercise the reap and the runtime probe, and stop leaki…
wenshao Aug 24, 2026
5d2a126
Merge remote-tracking branch 'origin/main' into feat/review-sandbox-ci
wenshao Aug 25, 2026
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
75 changes: 75 additions & 0 deletions packages/cli/src/commands/review/build-test.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { isolateOperatorReviewSettings } from './lib/test-utils.js';
import {
mkdtempSync,
mkdirSync,
Expand All @@ -17,7 +18,9 @@ import {
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import {
applyHandOffPolicy,
run,
resumeWouldDestroyReport,
runBuildTest,
type BuildTestReport,
trimOutput,
Expand All @@ -38,12 +41,20 @@ vi.mock('node:fs', async (importOriginal) => {
return { ...mock, default: mock };
});

let reviewSettingsIsolation: ReturnType<typeof isolateOperatorReviewSettings>;

beforeEach(() => {
// Plenty of disk by default, so this suite behaves the same on a nearly-full
// machine as on an empty one — the low-disk cases below opt in explicitly.
statfsSyncMock.mockReturnValue({ bavail: 16 * 1024 ** 3, bsize: 1 });
// ...and the same for the operator's review policy: with `required` set in
// their own settings the phase gate refuses every run here, correctly, and
// 82 of this file's tests report that instead of what they measure.
reviewSettingsIsolation = isolateOperatorReviewSettings();
});

afterEach(() => reviewSettingsIsolation?.dispose());

const PKGS: WorkspacePackage[] = [
{ dir: 'packages/core', name: '@x/core', scripts: ['build'], deps: [] },
{ dir: 'packages/webui', name: '@x/webui', scripts: ['build'], deps: [] },
Expand Down Expand Up @@ -4483,3 +4494,67 @@ describe('runBuildTest', () => {
});
});
});

describe('applyHandOffPolicy', () => {
const handOff = {
toolchain: 'unsupported' as const,
affected: [],
buildSet: [],
widenedWith: [],
install: null,
build: [],
test: [],
timedOut: [],
ok: true,
note: 'build-test could not scope this repo',
};

it('converts a hand-off to a refusal under `required`', () => {
// The hand-off tells the agent to install and build with its own shell,
// which nothing here contains. This conversion has been wrong twice — once
// as a precondition that was never true, once as a wrapper on two of the
// three routes that produce it — so what it produces is pinned here rather
// than left to the call site.
const got = applyHandOffPolicy(handOff, 'required');
expect(got.toolchain).toBe('refused');
// NOT `ok`, or a reader treats it as a clean hand-off and does by hand
// exactly what the policy refused.
expect(got.ok).toBe(false);
expect(got.note).toContain('do not run the commands by hand');
expect(got.build).toEqual([]);
expect(got.test).toEqual([]);
});

it('leaves a hand-off alone under the other policies', () => {
for (const policy of ['off', 'auto'] as const) {
expect(applyHandOffPolicy(handOff, policy)).toBe(handOff);
}
});

it('refuses to convert on a --resume, which would destroy the report', () => {
// The invariant the other two continuation exits enforce with a throw:
// "a continuation must never answer with a FRESH report". This conversion
// was added after both and returns one — which the handler writes over the
// report the call was asked to continue, and that refusal carries no run
// identity, so every later resume fails the identity check. A policy
// tightened between the first call and the resume is enough to trigger it,
// on the unscopeable repo shapes that reach a hand-off in the first place.
expect(resumeWouldDestroyReport(handOff, true, 'required')).toBe(true);
// A fresh call converts normally — that is the whole point of the
// conversion.
expect(resumeWouldDestroyReport(handOff, false, 'required')).toBe(false);
// And a resume of a real run is never touched.
expect(
resumeWouldDestroyReport(
{ ...handOff, toolchain: 'npm' },
true,
'required',
),
).toBe(false);
});

it('never converts a real run', () => {
const real = { ...handOff, toolchain: 'npm' as const };
expect(applyHandOffPolicy(real, 'required')).toBe(real);
});
});
Loading
Loading