@gethmy/harness 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +218 -94
- package/dist/index.js +235 -205
- package/package.json +1 -1
- package/src/exec-types.ts +64 -0
- package/src/gate-collectors.ts +30 -4
- package/src/run-containment.ts +38 -18
- package/src/verification.ts +240 -88
package/dist/index.js
CHANGED
|
@@ -1128,9 +1128,9 @@ __export(exports_git_pr, {
|
|
|
1128
1128
|
buildPrBody: () => buildPrBody
|
|
1129
1129
|
});
|
|
1130
1130
|
import { execFile as execFile2, execFileSync as execFileSync7 } from "node:child_process";
|
|
1131
|
-
import { promisify as
|
|
1131
|
+
import { promisify as promisify3 } from "node:util";
|
|
1132
1132
|
function createExecFileAsync2() {
|
|
1133
|
-
return
|
|
1133
|
+
return promisify3(execFile2);
|
|
1134
1134
|
}
|
|
1135
1135
|
function execFileAsync2() {
|
|
1136
1136
|
return cachedExecFileAsync2 ??= createExecFileAsync2();
|
|
@@ -1187,7 +1187,7 @@ function validateGitProviderCli(provider, cwd) {
|
|
|
1187
1187
|
}
|
|
1188
1188
|
case "bitbucket":
|
|
1189
1189
|
case "unknown":
|
|
1190
|
-
log.warn(
|
|
1190
|
+
log.warn(TAG14, `Git provider "${provider}" — PR creation will be skipped (no CLI support)`);
|
|
1191
1191
|
break;
|
|
1192
1192
|
}
|
|
1193
1193
|
}
|
|
@@ -1470,7 +1470,7 @@ async function checkPrMergeStatus(prUrl, cwd, provider) {
|
|
|
1470
1470
|
try {
|
|
1471
1471
|
parsed = JSON.parse(stdout.trim());
|
|
1472
1472
|
} catch {
|
|
1473
|
-
log.warn(
|
|
1473
|
+
log.warn(TAG14, `Failed to parse glab JSON output for MR ${mrMatch[1]}`);
|
|
1474
1474
|
return "unknown";
|
|
1475
1475
|
}
|
|
1476
1476
|
if (typeof parsed !== "object" || parsed === null)
|
|
@@ -1568,7 +1568,7 @@ async function resolvePrHeadBranch(prUrl, cwd, provider) {
|
|
|
1568
1568
|
const { stdout } = await execFileAsync2()("gh", ["pr", "view", prUrl, "--json", "headRefName,isCrossRepository"], { cwd, encoding: "utf-8", timeout: 1e4 });
|
|
1569
1569
|
return decidePrBranch("github", stdout);
|
|
1570
1570
|
} catch (err) {
|
|
1571
|
-
log.warn(
|
|
1571
|
+
log.warn(TAG14, `gh pr view failed for ${prUrl}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1572
1572
|
return decidePrBranch("github", null);
|
|
1573
1573
|
}
|
|
1574
1574
|
}
|
|
@@ -1584,7 +1584,7 @@ async function resolvePrHeadBranch(prUrl, cwd, provider) {
|
|
|
1584
1584
|
const { stdout } = await execFileAsync2()("az", ["repos", "pr", "show", "--id", prId, "--output", "json"], { cwd, encoding: "utf-8", timeout: 1e4 });
|
|
1585
1585
|
return decidePrBranch("azure", stdout);
|
|
1586
1586
|
} catch (err) {
|
|
1587
|
-
log.warn(
|
|
1587
|
+
log.warn(TAG14, `az repos pr show failed for ${prUrl}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1588
1588
|
return decidePrBranch("azure", null);
|
|
1589
1589
|
}
|
|
1590
1590
|
}
|
|
@@ -1626,7 +1626,7 @@ function remoteBranchExists(branchName, cwd) {
|
|
|
1626
1626
|
}
|
|
1627
1627
|
function pushBranch(branchName, cwd) {
|
|
1628
1628
|
if (remoteBranchExists(branchName, cwd)) {
|
|
1629
|
-
log.info(
|
|
1629
|
+
log.info(TAG14, `Remote branch ${branchName} exists (rework), force-pushing`);
|
|
1630
1630
|
let expectedSha = null;
|
|
1631
1631
|
try {
|
|
1632
1632
|
execFileSync7("git", [...GIT_NO_HOOKS, "fetch", "origin", branchName], {
|
|
@@ -1635,7 +1635,7 @@ function pushBranch(branchName, cwd) {
|
|
|
1635
1635
|
});
|
|
1636
1636
|
expectedSha = execFileSync7("git", [...GIT_NO_HOOKS, "rev-parse", `refs/remotes/origin/${branchName}`], { cwd, encoding: "utf-8" }).trim();
|
|
1637
1637
|
} catch (err) {
|
|
1638
|
-
log.warn(
|
|
1638
|
+
log.warn(TAG14, `could not resolve remote tip for ${branchName}, falling back to weak lease: ${err instanceof Error ? err.message : err}`);
|
|
1639
1639
|
}
|
|
1640
1640
|
const lease = expectedSha ? `--force-with-lease=refs/heads/${branchName}:${expectedSha}` : "--force-with-lease";
|
|
1641
1641
|
execFileSync7("git", [...GIT_NO_HOOKS, "push", lease, "-u", "origin", branchName], {
|
|
@@ -1661,7 +1661,7 @@ function renameRemoteBranch(oldRef, newRef, cwd) {
|
|
|
1661
1661
|
} catch (err) {
|
|
1662
1662
|
throw new Error(`renameRemoteBranch: could not resolve HEAD: ${err instanceof Error ? err.message : err}`);
|
|
1663
1663
|
}
|
|
1664
|
-
log.info(
|
|
1664
|
+
log.info(TAG14, `Renaming remote ${oldRef} → ${newRef}`);
|
|
1665
1665
|
execFileSync7("git", [
|
|
1666
1666
|
...GIT_NO_HOOKS,
|
|
1667
1667
|
"push",
|
|
@@ -1675,7 +1675,7 @@ function renameRemoteBranch(oldRef, newRef, cwd) {
|
|
|
1675
1675
|
stdio: "pipe"
|
|
1676
1676
|
});
|
|
1677
1677
|
} catch (err) {
|
|
1678
|
-
log.warn(
|
|
1678
|
+
log.warn(TAG14, `renameRemoteBranch: could not delete old ref ${oldRef}: ${err instanceof Error ? err.message : err}`);
|
|
1679
1679
|
}
|
|
1680
1680
|
try {
|
|
1681
1681
|
execFileSync7("git", [...GIT_NO_HOOKS, "branch", "-m", oldRef, newRef], {
|
|
@@ -1734,7 +1734,7 @@ function buildPrBody(card, commitLog) {
|
|
|
1734
1734
|
}
|
|
1735
1735
|
function createPullRequest(card, branchName, worktreePath, config, provider, existingPrUrl) {
|
|
1736
1736
|
if (existingPrUrl) {
|
|
1737
|
-
log.info(
|
|
1737
|
+
log.info(TAG14, `Reusing existing PR from card description: ${existingPrUrl}`);
|
|
1738
1738
|
return existingPrUrl;
|
|
1739
1739
|
}
|
|
1740
1740
|
let commitLog = "";
|
|
@@ -1753,7 +1753,7 @@ function createPullRequest(card, branchName, worktreePath, config, provider, exi
|
|
|
1753
1753
|
const base = config.worktree.baseBranch;
|
|
1754
1754
|
const existingUrl = findExistingPr(branchName, worktreePath, provider);
|
|
1755
1755
|
if (existingUrl) {
|
|
1756
|
-
log.info(
|
|
1756
|
+
log.info(TAG14, `PR already exists for ${branchName}, updating body...`);
|
|
1757
1757
|
updateExistingPr(branchName, body, worktreePath, provider);
|
|
1758
1758
|
return existingUrl;
|
|
1759
1759
|
}
|
|
@@ -1803,13 +1803,13 @@ function createPullRequest(card, branchName, worktreePath, config, provider, exi
|
|
|
1803
1803
|
], { cwd: worktreePath, encoding: "utf-8" }).trim();
|
|
1804
1804
|
break;
|
|
1805
1805
|
default:
|
|
1806
|
-
log.warn(
|
|
1806
|
+
log.warn(TAG14, `No PR CLI for provider "${provider}" — branch pushed but no PR created`);
|
|
1807
1807
|
return null;
|
|
1808
1808
|
}
|
|
1809
|
-
log.info(
|
|
1809
|
+
log.info(TAG14, `PR created: ${result}`);
|
|
1810
1810
|
return result;
|
|
1811
1811
|
} catch (err) {
|
|
1812
|
-
log.error(
|
|
1812
|
+
log.error(TAG14, `Failed to create PR: ${err instanceof Error ? err.message : err}`);
|
|
1813
1813
|
return null;
|
|
1814
1814
|
}
|
|
1815
1815
|
}
|
|
@@ -1843,12 +1843,12 @@ function updateExistingPr(branchName, body, worktreePath, provider) {
|
|
|
1843
1843
|
execFileSync7("glab", ["mr", "update", branchName, "--description", body], { cwd: worktreePath, stdio: "pipe" });
|
|
1844
1844
|
break;
|
|
1845
1845
|
}
|
|
1846
|
-
log.info(
|
|
1846
|
+
log.info(TAG14, `Updated existing PR body for ${branchName}`);
|
|
1847
1847
|
} catch (err) {
|
|
1848
|
-
log.warn(
|
|
1848
|
+
log.warn(TAG14, `Failed to update PR body: ${err instanceof Error ? err.message : err}`);
|
|
1849
1849
|
}
|
|
1850
1850
|
}
|
|
1851
|
-
var cachedExecFileAsync2,
|
|
1851
|
+
var cachedExecFileAsync2, TAG14 = "git-pr", VALID_PR_URL_RE, PR_URL_RE, REVIEWED_SHA_RE, CI_REVIEW_REQUESTED_SHA_RE;
|
|
1852
1852
|
var init_git_pr = __esm(() => {
|
|
1853
1853
|
init_dist();
|
|
1854
1854
|
init_log();
|
|
@@ -2721,6 +2721,7 @@ init_dist();
|
|
|
2721
2721
|
|
|
2722
2722
|
// src/exec-types.ts
|
|
2723
2723
|
var DEFAULT_METRIC_TIMEOUT_MS = 300000;
|
|
2724
|
+
var SANDBOX_STARTUP_GRACE_MS = 60000;
|
|
2724
2725
|
|
|
2725
2726
|
// src/gate-config-error.ts
|
|
2726
2727
|
init_dist();
|
|
@@ -3439,8 +3440,8 @@ function errText(err) {
|
|
|
3439
3440
|
}
|
|
3440
3441
|
|
|
3441
3442
|
// src/verification.ts
|
|
3442
|
-
init_log();
|
|
3443
3443
|
import { execFileSync as execFileSync5, spawn as spawn2 } from "node:child_process";
|
|
3444
|
+
init_log();
|
|
3444
3445
|
|
|
3445
3446
|
// src/pm.ts
|
|
3446
3447
|
init_log();
|
|
@@ -3672,11 +3673,108 @@ function resolveXcodeScheme(pt) {
|
|
|
3672
3673
|
}
|
|
3673
3674
|
}
|
|
3674
3675
|
|
|
3676
|
+
// src/repair-sandbox.ts
|
|
3677
|
+
init_log();
|
|
3678
|
+
import { randomUUID } from "node:crypto";
|
|
3679
|
+
import { promisify as promisify2 } from "node:util";
|
|
3680
|
+
var TAG9 = "repair-sandbox";
|
|
3681
|
+
async function dockerExec(argv, opts) {
|
|
3682
|
+
const { execFile: execFile2 } = await import("node:child_process");
|
|
3683
|
+
return promisify2(execFile2)("docker", argv, {
|
|
3684
|
+
encoding: "utf-8",
|
|
3685
|
+
...opts
|
|
3686
|
+
});
|
|
3687
|
+
}
|
|
3688
|
+
var MAX_OUTPUT_BUFFER2 = 20971520;
|
|
3689
|
+
var PROBE_TIMEOUT_MS = 1e4;
|
|
3690
|
+
var SANDBOX_MOUNT = "/repo";
|
|
3691
|
+
var SANDBOX_MEMORY = "4g";
|
|
3692
|
+
var SANDBOX_PIDS = "512";
|
|
3693
|
+
var dockerProbe = null;
|
|
3694
|
+
async function sandboxAvailable() {
|
|
3695
|
+
if (dockerProbe === true)
|
|
3696
|
+
return true;
|
|
3697
|
+
try {
|
|
3698
|
+
await dockerExec(["version", "--format", "{{.Server.Version}}"], {
|
|
3699
|
+
timeout: PROBE_TIMEOUT_MS
|
|
3700
|
+
});
|
|
3701
|
+
dockerProbe = true;
|
|
3702
|
+
} catch {
|
|
3703
|
+
dockerProbe = false;
|
|
3704
|
+
}
|
|
3705
|
+
return dockerProbe;
|
|
3706
|
+
}
|
|
3707
|
+
function __resetSandboxProbe() {
|
|
3708
|
+
dockerProbe = null;
|
|
3709
|
+
}
|
|
3710
|
+
async function removeContainer(name) {
|
|
3711
|
+
try {
|
|
3712
|
+
await dockerExec(["rm", "--force", name], { timeout: PROBE_TIMEOUT_MS });
|
|
3713
|
+
log.warn(TAG9, `removed the timed-out sandbox container ${name}`);
|
|
3714
|
+
} catch {}
|
|
3715
|
+
}
|
|
3716
|
+
function sandboxRunArgs(image, worktree, command, name) {
|
|
3717
|
+
return [
|
|
3718
|
+
"run",
|
|
3719
|
+
"--rm",
|
|
3720
|
+
...name ? ["--name", name] : [],
|
|
3721
|
+
"--network=none",
|
|
3722
|
+
"--cap-drop=ALL",
|
|
3723
|
+
"--security-opt=no-new-privileges",
|
|
3724
|
+
`--memory=${SANDBOX_MEMORY}`,
|
|
3725
|
+
`--pids-limit=${SANDBOX_PIDS}`,
|
|
3726
|
+
...typeof process.getuid === "function" && typeof process.getgid === "function" ? ["--user", `${process.getuid()}:${process.getgid()}`] : [],
|
|
3727
|
+
"--env",
|
|
3728
|
+
"HOME=/tmp",
|
|
3729
|
+
"--volume",
|
|
3730
|
+
`${worktree}:${SANDBOX_MOUNT}`,
|
|
3731
|
+
"--workdir",
|
|
3732
|
+
SANDBOX_MOUNT,
|
|
3733
|
+
"--entrypoint",
|
|
3734
|
+
command.cmd,
|
|
3735
|
+
image,
|
|
3736
|
+
...command.args
|
|
3737
|
+
];
|
|
3738
|
+
}
|
|
3739
|
+
async function runInSandbox(args) {
|
|
3740
|
+
const name = `harmony-repair-${randomUUID()}`;
|
|
3741
|
+
const argv = sandboxRunArgs(args.image, args.worktree, args.command, name);
|
|
3742
|
+
log.info(TAG9, `sandbox: ${args.command.cmd} ${args.command.args.join(" ")} (image ${args.image})`);
|
|
3743
|
+
try {
|
|
3744
|
+
const { stdout } = await dockerExec(argv, {
|
|
3745
|
+
timeout: args.timeoutMs,
|
|
3746
|
+
maxBuffer: MAX_OUTPUT_BUFFER2
|
|
3747
|
+
});
|
|
3748
|
+
return { passed: true, output: stdout ?? "" };
|
|
3749
|
+
} catch (err) {
|
|
3750
|
+
const e = err;
|
|
3751
|
+
const output = `${e.stdout ?? ""}${e.stderr ?? ""}`;
|
|
3752
|
+
if (typeof e.code !== "number") {
|
|
3753
|
+
const timedOut = e.killed === true || e.signal != null;
|
|
3754
|
+
if (timedOut)
|
|
3755
|
+
await removeContainer(name);
|
|
3756
|
+
return {
|
|
3757
|
+
passed: false,
|
|
3758
|
+
output,
|
|
3759
|
+
sandboxError: timedOut ? `the sandbox timed out after ${args.timeoutMs}ms` : `the sandbox did not run: ${e.message ?? "unknown error"}`
|
|
3760
|
+
};
|
|
3761
|
+
}
|
|
3762
|
+
if (e.code === 125) {
|
|
3763
|
+
return {
|
|
3764
|
+
passed: false,
|
|
3765
|
+
output,
|
|
3766
|
+
sandboxError: `the sandbox could not start (image "${args.image}" missing or unusable)`
|
|
3767
|
+
};
|
|
3768
|
+
}
|
|
3769
|
+
return { passed: false, output };
|
|
3770
|
+
}
|
|
3771
|
+
}
|
|
3772
|
+
|
|
3675
3773
|
// src/revert-guard.ts
|
|
3676
3774
|
init_log();
|
|
3677
3775
|
init_run_containment();
|
|
3678
3776
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
3679
|
-
var
|
|
3777
|
+
var TAG10 = "revert-guard";
|
|
3680
3778
|
var TEST_FILE = /(?:^|\/)__tests__\/|\.(?:test|spec)\.[cm]?[jt]sx?$/;
|
|
3681
3779
|
function isTestFile(path) {
|
|
3682
3780
|
return TEST_FILE.test(path);
|
|
@@ -3691,7 +3789,7 @@ function refetchBase(worktreePath, baseBranch) {
|
|
|
3691
3789
|
stdio: "pipe"
|
|
3692
3790
|
});
|
|
3693
3791
|
} catch {
|
|
3694
|
-
log.warn(
|
|
3792
|
+
log.warn(TAG10, "Failed to re-fetch base for revert guard — using last fetch");
|
|
3695
3793
|
}
|
|
3696
3794
|
}
|
|
3697
3795
|
function listDeletedFilesAgainstBase(worktreePath, baseBranch) {
|
|
@@ -3706,7 +3804,7 @@ function listDeletedFilesAgainstBase(worktreePath, baseBranch) {
|
|
|
3706
3804
|
return out.split(`
|
|
3707
3805
|
`).map((l) => l.trim()).filter((l) => l.length > 0);
|
|
3708
3806
|
} catch (err) {
|
|
3709
|
-
log.warn(
|
|
3807
|
+
log.warn(TAG10, `Failed to list deleted files: ${err instanceof Error ? err.message : err}`);
|
|
3710
3808
|
return [];
|
|
3711
3809
|
}
|
|
3712
3810
|
}
|
|
@@ -3717,8 +3815,8 @@ function findDeletedTestFiles(worktreePath, baseBranch) {
|
|
|
3717
3815
|
|
|
3718
3816
|
// src/verification.ts
|
|
3719
3817
|
init_run_containment();
|
|
3720
|
-
var
|
|
3721
|
-
var
|
|
3818
|
+
var TAG11 = "verification";
|
|
3819
|
+
var MAX_OUTPUT_BUFFER3 = 64 * 1024 * 1024;
|
|
3722
3820
|
async function runVerification(worktreePath, config, workerId) {
|
|
3723
3821
|
const result = {
|
|
3724
3822
|
passed: true,
|
|
@@ -3728,137 +3826,163 @@ async function runVerification(worktreePath, config, workerId) {
|
|
|
3728
3826
|
reviewFindings: [],
|
|
3729
3827
|
revertWarnings: []
|
|
3730
3828
|
};
|
|
3829
|
+
const sandbox = verificationSandbox(config);
|
|
3731
3830
|
if (config.verification.revertGuard) {
|
|
3732
|
-
log.info(
|
|
3831
|
+
log.info(TAG11, `[worker:${workerId}] Checking for reverted merged work...`);
|
|
3733
3832
|
const deletedTests = findDeletedTestFiles(worktreePath, config.worktree.baseBranch);
|
|
3734
3833
|
if (deletedTests.length > 0) {
|
|
3735
3834
|
result.revertWarnings = deletedTests.map((f) => `Branch deletes test file '${f}' relative to current ${config.worktree.baseBranch} — ` + "likely an accidental revert of already-merged work. Restore the test or rebase on current main.");
|
|
3736
|
-
log.warn(
|
|
3835
|
+
log.warn(TAG11, `[worker:${workerId}] Revert guard tripped: ${deletedTests.length} deleted test file(s)`);
|
|
3737
3836
|
result.passed = false;
|
|
3738
3837
|
} else {
|
|
3739
|
-
log.info(
|
|
3838
|
+
log.info(TAG11, `[worker:${workerId}] Revert guard passed`);
|
|
3740
3839
|
}
|
|
3741
3840
|
}
|
|
3742
3841
|
if (config.verification.build) {
|
|
3743
|
-
log.info(
|
|
3744
|
-
result.buildErrors = runBuild(worktreePath, config.verification.timeout);
|
|
3842
|
+
log.info(TAG11, `[worker:${workerId}] Running build...`);
|
|
3843
|
+
result.buildErrors = await runBuild(worktreePath, config.verification.timeout, sandbox);
|
|
3745
3844
|
if (result.buildErrors.length > 0) {
|
|
3746
|
-
log.warn(
|
|
3845
|
+
log.warn(TAG11, `[worker:${workerId}] Build failed with ${result.buildErrors.length} error(s)`);
|
|
3747
3846
|
result.passed = false;
|
|
3748
3847
|
} else {
|
|
3749
|
-
log.info(
|
|
3848
|
+
log.info(TAG11, `[worker:${workerId}] Build passed`);
|
|
3750
3849
|
}
|
|
3751
3850
|
}
|
|
3752
3851
|
if (config.verification.test && result.buildErrors.length === 0) {
|
|
3753
|
-
log.info(
|
|
3754
|
-
result.testFailures = runTests(worktreePath, config.verification.testTimeout);
|
|
3852
|
+
log.info(TAG11, `[worker:${workerId}] Running tests...`);
|
|
3853
|
+
result.testFailures = await runTests(worktreePath, config.verification.testTimeout, sandbox);
|
|
3755
3854
|
if (result.testFailures.length > 0) {
|
|
3756
|
-
log.warn(
|
|
3855
|
+
log.warn(TAG11, `[worker:${workerId}] Tests failed with ${result.testFailures.length} failure(s)`);
|
|
3757
3856
|
result.passed = false;
|
|
3758
3857
|
} else {
|
|
3759
|
-
log.info(
|
|
3858
|
+
log.info(TAG11, `[worker:${workerId}] Tests passed`);
|
|
3760
3859
|
}
|
|
3761
3860
|
}
|
|
3762
3861
|
if (config.verification.lint) {
|
|
3763
|
-
log.info(
|
|
3764
|
-
result.lintWarnings = runLint(worktreePath, config.verification.timeout);
|
|
3862
|
+
log.info(TAG11, `[worker:${workerId}] Running lint...`);
|
|
3863
|
+
result.lintWarnings = await runLint(worktreePath, config.verification.timeout, sandbox);
|
|
3765
3864
|
if (result.lintWarnings.length > 0) {
|
|
3766
|
-
log.warn(
|
|
3865
|
+
log.warn(TAG11, `[worker:${workerId}] Lint found ${result.lintWarnings.length} issue(s)`);
|
|
3767
3866
|
} else {
|
|
3768
|
-
log.info(
|
|
3867
|
+
log.info(TAG11, `[worker:${workerId}] Lint passed`);
|
|
3769
3868
|
}
|
|
3770
3869
|
}
|
|
3771
3870
|
if (config.verification.deepReview) {
|
|
3772
|
-
log.info(
|
|
3871
|
+
log.info(TAG11, `[worker:${workerId}] Running deep review...`);
|
|
3773
3872
|
result.reviewFindings = await runDeepReview(worktreePath, config, workerId);
|
|
3774
3873
|
if (result.reviewFindings.length > 0) {
|
|
3775
|
-
log.warn(
|
|
3874
|
+
log.warn(TAG11, `[worker:${workerId}] Deep review found ${result.reviewFindings.length} finding(s)`);
|
|
3776
3875
|
} else {
|
|
3777
|
-
log.info(
|
|
3876
|
+
log.info(TAG11, `[worker:${workerId}] Deep review passed`);
|
|
3778
3877
|
}
|
|
3779
3878
|
}
|
|
3780
3879
|
return result;
|
|
3781
3880
|
}
|
|
3782
|
-
function
|
|
3783
|
-
const
|
|
3784
|
-
if (!
|
|
3785
|
-
|
|
3786
|
-
|
|
3881
|
+
function verificationSandbox(config) {
|
|
3882
|
+
const image = config.verification.sandboxImage?.trim();
|
|
3883
|
+
if (!image)
|
|
3884
|
+
return;
|
|
3885
|
+
return { image };
|
|
3886
|
+
}
|
|
3887
|
+
async function execStep(command, args) {
|
|
3888
|
+
const { worktreePath, timeout } = args;
|
|
3889
|
+
const sandbox = args.sandbox?.image.trim() ? args.sandbox : undefined;
|
|
3890
|
+
if (sandbox) {
|
|
3891
|
+
if (!await sandboxAvailable()) {
|
|
3892
|
+
return {
|
|
3893
|
+
ok: false,
|
|
3894
|
+
sandboxError: `verification.sandboxImage is set to "${sandbox.image}" but no container runtime answered — ` + "start Docker or unset the image to verify on the host"
|
|
3895
|
+
};
|
|
3896
|
+
}
|
|
3897
|
+
const result = await runInSandbox({
|
|
3898
|
+
image: sandbox.image,
|
|
3899
|
+
worktree: worktreePath,
|
|
3900
|
+
command,
|
|
3901
|
+
timeoutMs: timeout + SANDBOX_STARTUP_GRACE_MS
|
|
3902
|
+
});
|
|
3903
|
+
if (result.sandboxError) {
|
|
3904
|
+
return { ok: false, sandboxError: result.sandboxError };
|
|
3905
|
+
}
|
|
3906
|
+
if (result.passed)
|
|
3907
|
+
return { ok: true };
|
|
3908
|
+
return { ok: false, err: { stdout: result.output, stderr: "" } };
|
|
3787
3909
|
}
|
|
3788
3910
|
try {
|
|
3789
3911
|
execFileSync5(command.cmd, command.args, {
|
|
3790
3912
|
cwd: worktreePath,
|
|
3791
3913
|
timeout,
|
|
3792
3914
|
stdio: "pipe",
|
|
3793
|
-
maxBuffer:
|
|
3915
|
+
maxBuffer: MAX_OUTPUT_BUFFER3,
|
|
3794
3916
|
env: containedEnv()
|
|
3795
3917
|
});
|
|
3796
|
-
return
|
|
3918
|
+
return { ok: true };
|
|
3797
3919
|
} catch (err) {
|
|
3798
|
-
return
|
|
3920
|
+
return { ok: false, err };
|
|
3921
|
+
}
|
|
3922
|
+
}
|
|
3923
|
+
async function runBuild(worktreePath, timeout, sandbox) {
|
|
3924
|
+
const command = buildCommand(worktreePath);
|
|
3925
|
+
if (!command) {
|
|
3926
|
+
log.warn(TAG11, `No known build toolchain for ${worktreePath} — skipping build`);
|
|
3927
|
+
return [];
|
|
3799
3928
|
}
|
|
3929
|
+
const outcome = await execStep(command, { worktreePath, timeout, sandbox });
|
|
3930
|
+
if (outcome.ok)
|
|
3931
|
+
return [];
|
|
3932
|
+
if (outcome.sandboxError) {
|
|
3933
|
+
log.error(TAG11, `Build not verified: ${outcome.sandboxError}`);
|
|
3934
|
+
return [`Build did not run: ${outcome.sandboxError}`];
|
|
3935
|
+
}
|
|
3936
|
+
return parseErrorOutput(outcome.err);
|
|
3800
3937
|
}
|
|
3801
|
-
function runTests(worktreePath, timeout) {
|
|
3938
|
+
async function runTests(worktreePath, timeout, sandbox) {
|
|
3802
3939
|
const command = testCommand(worktreePath);
|
|
3803
3940
|
if (!command) {
|
|
3804
|
-
log.warn(
|
|
3941
|
+
log.warn(TAG11, `No test command for detected toolchain in ${worktreePath} — skipping tests`);
|
|
3805
3942
|
return [];
|
|
3806
3943
|
}
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
cwd: worktreePath,
|
|
3810
|
-
timeout,
|
|
3811
|
-
stdio: "pipe",
|
|
3812
|
-
maxBuffer: MAX_OUTPUT_BUFFER2,
|
|
3813
|
-
env: containedEnv()
|
|
3814
|
-
});
|
|
3944
|
+
const outcome = await execStep(command, { worktreePath, timeout, sandbox });
|
|
3945
|
+
if (outcome.ok)
|
|
3815
3946
|
return [];
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
${output.slice(-4000) || "(no output captured)"}`);
|
|
3820
|
-
return parseTestFailures(err, timeout);
|
|
3947
|
+
if (outcome.sandboxError) {
|
|
3948
|
+
log.error(TAG11, `Tests not verified: ${outcome.sandboxError}`);
|
|
3949
|
+
return [`Test run did not happen: ${outcome.sandboxError}`];
|
|
3821
3950
|
}
|
|
3951
|
+
const output = combineOutput(outcome.err);
|
|
3952
|
+
log.warn(TAG11, `Test run failed:
|
|
3953
|
+
${output.slice(-4000) || "(no output captured)"}`);
|
|
3954
|
+
return parseTestFailures(outcome.err, timeout);
|
|
3822
3955
|
}
|
|
3823
|
-
function runFormatFix(worktreePath, timeout, workerId) {
|
|
3956
|
+
async function runFormatFix(worktreePath, timeout, workerId, sandbox) {
|
|
3824
3957
|
const command = formatFixCommand(worktreePath);
|
|
3825
3958
|
if (!command)
|
|
3826
3959
|
return;
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
stdio: "pipe",
|
|
3832
|
-
maxBuffer: MAX_OUTPUT_BUFFER2,
|
|
3833
|
-
env: containedEnv()
|
|
3834
|
-
});
|
|
3835
|
-
log.info(TAG10, `[worker:${workerId}] Auto-formatted worktree before commit/push`);
|
|
3836
|
-
} catch (err) {
|
|
3837
|
-
log.warn(TAG10, `[worker:${workerId}] Auto-format step exited non-zero (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
|
|
3960
|
+
const outcome = await execStep(command, { worktreePath, timeout, sandbox });
|
|
3961
|
+
if (outcome.ok) {
|
|
3962
|
+
log.info(TAG11, `[worker:${workerId}] Auto-formatted worktree before commit/push`);
|
|
3963
|
+
return;
|
|
3838
3964
|
}
|
|
3965
|
+
const why = outcome.sandboxError ? outcome.sandboxError : outcome.err instanceof Error ? outcome.err.message : String(outcome.err);
|
|
3966
|
+
log.warn(TAG11, `[worker:${workerId}] Auto-format step did not complete (non-fatal): ${why}`);
|
|
3839
3967
|
}
|
|
3840
|
-
function runLint(worktreePath, timeout) {
|
|
3968
|
+
async function runLint(worktreePath, timeout, sandbox) {
|
|
3841
3969
|
const command = lintCommand(worktreePath);
|
|
3842
3970
|
if (!command) {
|
|
3843
|
-
log.info(
|
|
3971
|
+
log.info(TAG11, `No lint step for detected toolchain in ${worktreePath} — skipping lint`);
|
|
3844
3972
|
return [];
|
|
3845
3973
|
}
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
cwd: worktreePath,
|
|
3849
|
-
timeout,
|
|
3850
|
-
stdio: "pipe",
|
|
3851
|
-
maxBuffer: MAX_OUTPUT_BUFFER2,
|
|
3852
|
-
env: containedEnv()
|
|
3853
|
-
});
|
|
3974
|
+
const outcome = await execStep(command, { worktreePath, timeout, sandbox });
|
|
3975
|
+
if (outcome.ok)
|
|
3854
3976
|
return [];
|
|
3855
|
-
|
|
3856
|
-
|
|
3977
|
+
if (outcome.sandboxError) {
|
|
3978
|
+
log.error(TAG11, `Lint not verified: ${outcome.sandboxError}`);
|
|
3979
|
+
return [`Lint did not run: ${outcome.sandboxError}`];
|
|
3857
3980
|
}
|
|
3981
|
+
return parseErrorOutput(outcome.err);
|
|
3858
3982
|
}
|
|
3859
3983
|
async function runDeepReview(worktreePath, config, workerId) {
|
|
3860
3984
|
if (!supportsDevServer(worktreePath)) {
|
|
3861
|
-
log.info(
|
|
3985
|
+
log.info(TAG11, `[worker:${workerId}] Detected non-web toolchain — skipping deep review`);
|
|
3862
3986
|
return [];
|
|
3863
3987
|
}
|
|
3864
3988
|
const port = config.verification.devServerBasePort + workerId;
|
|
@@ -3874,7 +3998,7 @@ async function runDeepReview(worktreePath, config, workerId) {
|
|
|
3874
3998
|
await waitForDevServer(devServer, 30000);
|
|
3875
3999
|
await probeDevServer(port);
|
|
3876
4000
|
} catch (err) {
|
|
3877
|
-
log.error(
|
|
4001
|
+
log.error(TAG11, `Dev server did not become ready: ${err instanceof Error ? err.message : err}`);
|
|
3878
4002
|
return [];
|
|
3879
4003
|
}
|
|
3880
4004
|
let diff = "";
|
|
@@ -3883,7 +4007,7 @@ async function runDeepReview(worktreePath, config, workerId) {
|
|
|
3883
4007
|
cwd: worktreePath,
|
|
3884
4008
|
encoding: "utf-8",
|
|
3885
4009
|
timeout: 30000,
|
|
3886
|
-
maxBuffer:
|
|
4010
|
+
maxBuffer: MAX_OUTPUT_BUFFER3
|
|
3887
4011
|
});
|
|
3888
4012
|
} catch {
|
|
3889
4013
|
diff = "(unable to retrieve diff)";
|
|
@@ -3917,12 +4041,12 @@ async function runDeepReview(worktreePath, config, workerId) {
|
|
|
3917
4041
|
encoding: "utf-8",
|
|
3918
4042
|
timeout: config.verification.timeout,
|
|
3919
4043
|
stdio: "pipe",
|
|
3920
|
-
maxBuffer:
|
|
4044
|
+
maxBuffer: MAX_OUTPUT_BUFFER3,
|
|
3921
4045
|
env: containedEnv()
|
|
3922
4046
|
});
|
|
3923
4047
|
return parseReviewFindings(output);
|
|
3924
4048
|
} catch (err) {
|
|
3925
|
-
log.error(
|
|
4049
|
+
log.error(TAG11, `Deep review failed: ${err instanceof Error ? err.message : err}`);
|
|
3926
4050
|
return [];
|
|
3927
4051
|
} finally {
|
|
3928
4052
|
if (devServer && !devServer.killed) {
|
|
@@ -3960,12 +4084,12 @@ function attemptAutoFix(worktreePath, config, errors) {
|
|
|
3960
4084
|
"--",
|
|
3961
4085
|
fixPrompt
|
|
3962
4086
|
];
|
|
3963
|
-
log.info(
|
|
4087
|
+
log.info(TAG11, "Spawning Claude for auto-fix...");
|
|
3964
4088
|
execFileSync5("claude", args, {
|
|
3965
4089
|
cwd: worktreePath,
|
|
3966
4090
|
timeout: config.verification.timeout,
|
|
3967
4091
|
stdio: "pipe",
|
|
3968
|
-
maxBuffer:
|
|
4092
|
+
maxBuffer: MAX_OUTPUT_BUFFER3,
|
|
3969
4093
|
env: containedEnv()
|
|
3970
4094
|
});
|
|
3971
4095
|
}
|
|
@@ -3999,7 +4123,7 @@ async function reportFindings(client, cardId, result, recovery) {
|
|
|
3999
4123
|
try {
|
|
4000
4124
|
await client.createSubtask(cardId, title);
|
|
4001
4125
|
} catch (err) {
|
|
4002
|
-
log.error(
|
|
4126
|
+
log.error(TAG11, `Failed to create subtask: ${err instanceof Error ? err.message : err}`);
|
|
4003
4127
|
}
|
|
4004
4128
|
}));
|
|
4005
4129
|
if (overflow > 0) {
|
|
@@ -4007,7 +4131,7 @@ async function reportFindings(client, cardId, result, recovery) {
|
|
|
4007
4131
|
await client.createSubtask(cardId, `...and ${overflow} more issues`);
|
|
4008
4132
|
} catch {}
|
|
4009
4133
|
}
|
|
4010
|
-
log.info(
|
|
4134
|
+
log.info(TAG11, `Reported ${Math.min(items.length, maxSubtasks)} finding(s) as subtasks on card ${cardId}`);
|
|
4011
4135
|
}
|
|
4012
4136
|
function combineOutput(err) {
|
|
4013
4137
|
const stderr = err?.stderr?.toString() ?? "";
|
|
@@ -4040,7 +4164,7 @@ function parseTestFailures(err, timeout) {
|
|
|
4040
4164
|
}
|
|
4041
4165
|
if (e?.code === "ENOBUFS") {
|
|
4042
4166
|
return [
|
|
4043
|
-
`Test output exceeded the ${
|
|
4167
|
+
`Test output exceeded the ${MAX_OUTPUT_BUFFER3 / (1024 * 1024)}MB capture limit and the run was killed — ` + "the suite's real result is unknown. Quieten the reporter or raise the limit."
|
|
4044
4168
|
];
|
|
4045
4169
|
}
|
|
4046
4170
|
const combined = combineOutput(err);
|
|
@@ -4131,7 +4255,7 @@ async function probeDevServer(port, timeoutMs = 5000) {
|
|
|
4131
4255
|
}
|
|
4132
4256
|
|
|
4133
4257
|
// src/gate-collectors.ts
|
|
4134
|
-
var
|
|
4258
|
+
var TAG12 = "gate-collectors";
|
|
4135
4259
|
async function resolveStageGate(client, card) {
|
|
4136
4260
|
const currentStage = card.current_stage;
|
|
4137
4261
|
const playbookId = card.playbook_id;
|
|
@@ -4149,7 +4273,7 @@ async function resolveStageGate(client, card) {
|
|
|
4149
4273
|
return null;
|
|
4150
4274
|
return { stage: resolution.stage, gate };
|
|
4151
4275
|
} catch (err) {
|
|
4152
|
-
log.warn(
|
|
4276
|
+
log.warn(TAG12, `resolveStageGate failed for stage "${currentStage}": ${err instanceof Error ? err.message : err}`);
|
|
4153
4277
|
return null;
|
|
4154
4278
|
}
|
|
4155
4279
|
}
|
|
@@ -4184,8 +4308,8 @@ class BuildGreenCollector {
|
|
|
4184
4308
|
async collect(_context) {
|
|
4185
4309
|
const doBuild = this.deps.runBuild ?? runBuild;
|
|
4186
4310
|
const doLint = this.deps.runLint ?? runLint;
|
|
4187
|
-
const buildErrors = doBuild(this.deps.worktreePath, this.deps.buildTimeout);
|
|
4188
|
-
const lintWarnings = doLint(this.deps.worktreePath, this.deps.lintTimeout);
|
|
4311
|
+
const buildErrors = await doBuild(this.deps.worktreePath, this.deps.buildTimeout, this.deps.sandbox);
|
|
4312
|
+
const lintWarnings = await doLint(this.deps.worktreePath, this.deps.lintTimeout, this.deps.sandbox);
|
|
4189
4313
|
const buildPassed = buildErrors.length === 0;
|
|
4190
4314
|
const lintPassed = lintWarnings.length === 0;
|
|
4191
4315
|
const result = buildPassed ? "passed" : "failed";
|
|
@@ -4281,7 +4405,7 @@ function buildGateCollectorRegistry(deps) {
|
|
|
4281
4405
|
async function collectGateEvidence(registry, context) {
|
|
4282
4406
|
const collector = registry[context.gate.kind];
|
|
4283
4407
|
if (!collector) {
|
|
4284
|
-
log.info(
|
|
4408
|
+
log.info(TAG12, `No collector for gate kind "${context.gate.kind}" — reporting blocked`);
|
|
4285
4409
|
return {
|
|
4286
4410
|
result: "blocked",
|
|
4287
4411
|
structured: {
|
|
@@ -4293,7 +4417,7 @@ async function collectGateEvidence(registry, context) {
|
|
|
4293
4417
|
return await collector.collect(context);
|
|
4294
4418
|
} catch (err) {
|
|
4295
4419
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4296
|
-
log.warn(
|
|
4420
|
+
log.warn(TAG12, `Collector for "${context.gate.kind}" threw: ${msg} — reporting blocked`);
|
|
4297
4421
|
return { result: "blocked", structured: { error: msg } };
|
|
4298
4422
|
}
|
|
4299
4423
|
}
|
|
@@ -4301,7 +4425,7 @@ async function collectGateEvidence(registry, context) {
|
|
|
4301
4425
|
init_log();
|
|
4302
4426
|
init_run_containment();
|
|
4303
4427
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
4304
|
-
var
|
|
4428
|
+
var TAG13 = "git-diff-stat";
|
|
4305
4429
|
var MAX_CHANGED_FILES = 30;
|
|
4306
4430
|
function parseNumstat(raw, maxFiles = MAX_CHANGED_FILES) {
|
|
4307
4431
|
const files = [];
|
|
@@ -4383,7 +4507,7 @@ function captureDiffStat(worktreePath, baseBranch, maxFiles = MAX_CHANGED_FILES)
|
|
|
4383
4507
|
const raw = execFileSync6("git", [...GIT_NO_HOOKS, "diff", "--numstat", `${baseBranch}...HEAD`], { cwd: worktreePath, encoding: "utf-8", timeout: 30000 });
|
|
4384
4508
|
return parseNumstat(raw, maxFiles);
|
|
4385
4509
|
} catch (err) {
|
|
4386
|
-
log.warn(
|
|
4510
|
+
log.warn(TAG13, "git diff --numstat failed", {
|
|
4387
4511
|
event: "diff_stat_failed",
|
|
4388
4512
|
error: err instanceof Error ? err.message : String(err)
|
|
4389
4513
|
});
|
|
@@ -4396,7 +4520,7 @@ init_git_pr();
|
|
|
4396
4520
|
|
|
4397
4521
|
// src/harmony-client.ts
|
|
4398
4522
|
init_log();
|
|
4399
|
-
var
|
|
4523
|
+
var TAG15 = "harmony-client";
|
|
4400
4524
|
function readClientConfig(env) {
|
|
4401
4525
|
const apiUrl = env.HARMONY_API_URL?.trim();
|
|
4402
4526
|
const apiKey = env.HARMONY_API_KEY?.trim();
|
|
@@ -4448,7 +4572,7 @@ class HarmonyClient {
|
|
|
4448
4572
|
purpose: "gate_evaluation"
|
|
4449
4573
|
});
|
|
4450
4574
|
if (!response.ok) {
|
|
4451
|
-
log.warn(
|
|
4575
|
+
log.warn(TAG15, `Oracle fetch for stage ${stageId} returned ${response.status} — no oracle read, the gate will report blocked`);
|
|
4452
4576
|
return null;
|
|
4453
4577
|
}
|
|
4454
4578
|
const body = await response.json();
|
|
@@ -4528,102 +4652,6 @@ function relayAgentEvent(draft) {
|
|
|
4528
4652
|
}
|
|
4529
4653
|
return { type: "agent_event", event: draft };
|
|
4530
4654
|
}
|
|
4531
|
-
// src/repair-sandbox.ts
|
|
4532
|
-
init_log();
|
|
4533
|
-
import { randomUUID } from "node:crypto";
|
|
4534
|
-
import { promisify as promisify3 } from "node:util";
|
|
4535
|
-
var TAG15 = "repair-sandbox";
|
|
4536
|
-
async function dockerExec(argv, opts) {
|
|
4537
|
-
const { execFile: execFile3 } = await import("node:child_process");
|
|
4538
|
-
return promisify3(execFile3)("docker", argv, {
|
|
4539
|
-
encoding: "utf-8",
|
|
4540
|
-
...opts
|
|
4541
|
-
});
|
|
4542
|
-
}
|
|
4543
|
-
var MAX_OUTPUT_BUFFER3 = 20971520;
|
|
4544
|
-
var PROBE_TIMEOUT_MS = 1e4;
|
|
4545
|
-
var SANDBOX_MOUNT = "/repo";
|
|
4546
|
-
var SANDBOX_MEMORY = "4g";
|
|
4547
|
-
var SANDBOX_PIDS = "512";
|
|
4548
|
-
var dockerProbe = null;
|
|
4549
|
-
async function sandboxAvailable() {
|
|
4550
|
-
if (dockerProbe === true)
|
|
4551
|
-
return true;
|
|
4552
|
-
try {
|
|
4553
|
-
await dockerExec(["version", "--format", "{{.Server.Version}}"], {
|
|
4554
|
-
timeout: PROBE_TIMEOUT_MS
|
|
4555
|
-
});
|
|
4556
|
-
dockerProbe = true;
|
|
4557
|
-
} catch {
|
|
4558
|
-
dockerProbe = false;
|
|
4559
|
-
}
|
|
4560
|
-
return dockerProbe;
|
|
4561
|
-
}
|
|
4562
|
-
function __resetSandboxProbe() {
|
|
4563
|
-
dockerProbe = null;
|
|
4564
|
-
}
|
|
4565
|
-
async function removeContainer(name) {
|
|
4566
|
-
try {
|
|
4567
|
-
await dockerExec(["rm", "--force", name], { timeout: PROBE_TIMEOUT_MS });
|
|
4568
|
-
log.warn(TAG15, `removed the timed-out sandbox container ${name}`);
|
|
4569
|
-
} catch {}
|
|
4570
|
-
}
|
|
4571
|
-
function sandboxRunArgs(image, worktree, command, name) {
|
|
4572
|
-
return [
|
|
4573
|
-
"run",
|
|
4574
|
-
"--rm",
|
|
4575
|
-
...name ? ["--name", name] : [],
|
|
4576
|
-
"--network=none",
|
|
4577
|
-
"--cap-drop=ALL",
|
|
4578
|
-
"--security-opt=no-new-privileges",
|
|
4579
|
-
`--memory=${SANDBOX_MEMORY}`,
|
|
4580
|
-
`--pids-limit=${SANDBOX_PIDS}`,
|
|
4581
|
-
...typeof process.getuid === "function" && typeof process.getgid === "function" ? ["--user", `${process.getuid()}:${process.getgid()}`] : [],
|
|
4582
|
-
"--env",
|
|
4583
|
-
"HOME=/tmp",
|
|
4584
|
-
"--volume",
|
|
4585
|
-
`${worktree}:${SANDBOX_MOUNT}`,
|
|
4586
|
-
"--workdir",
|
|
4587
|
-
SANDBOX_MOUNT,
|
|
4588
|
-
"--entrypoint",
|
|
4589
|
-
command.cmd,
|
|
4590
|
-
image,
|
|
4591
|
-
...command.args
|
|
4592
|
-
];
|
|
4593
|
-
}
|
|
4594
|
-
async function runInSandbox(args) {
|
|
4595
|
-
const name = `harmony-repair-${randomUUID()}`;
|
|
4596
|
-
const argv = sandboxRunArgs(args.image, args.worktree, args.command, name);
|
|
4597
|
-
log.info(TAG15, `sandbox: ${args.command.cmd} ${args.command.args.join(" ")} (image ${args.image})`);
|
|
4598
|
-
try {
|
|
4599
|
-
const { stdout } = await dockerExec(argv, {
|
|
4600
|
-
timeout: args.timeoutMs,
|
|
4601
|
-
maxBuffer: MAX_OUTPUT_BUFFER3
|
|
4602
|
-
});
|
|
4603
|
-
return { passed: true, output: stdout ?? "" };
|
|
4604
|
-
} catch (err) {
|
|
4605
|
-
const e = err;
|
|
4606
|
-
const output = `${e.stdout ?? ""}${e.stderr ?? ""}`;
|
|
4607
|
-
if (typeof e.code !== "number") {
|
|
4608
|
-
const timedOut = e.killed === true || e.signal != null;
|
|
4609
|
-
if (timedOut)
|
|
4610
|
-
await removeContainer(name);
|
|
4611
|
-
return {
|
|
4612
|
-
passed: false,
|
|
4613
|
-
output,
|
|
4614
|
-
sandboxError: timedOut ? `the sandbox timed out after ${args.timeoutMs}ms` : `the sandbox did not run: ${e.message ?? "unknown error"}`
|
|
4615
|
-
};
|
|
4616
|
-
}
|
|
4617
|
-
if (e.code === 125) {
|
|
4618
|
-
return {
|
|
4619
|
-
passed: false,
|
|
4620
|
-
output,
|
|
4621
|
-
sandboxError: `the sandbox could not start (image "${args.image}" missing or unusable)`
|
|
4622
|
-
};
|
|
4623
|
-
}
|
|
4624
|
-
return { passed: false, output };
|
|
4625
|
-
}
|
|
4626
|
-
}
|
|
4627
4655
|
|
|
4628
4656
|
// src/index.ts
|
|
4629
4657
|
init_run_containment();
|
|
@@ -5202,6 +5230,7 @@ var MOTOR_NAME = "harmony-harness";
|
|
|
5202
5230
|
export {
|
|
5203
5231
|
writeOnlyDenyPaths,
|
|
5204
5232
|
waitForDevServer,
|
|
5233
|
+
verificationSandbox,
|
|
5205
5234
|
validateGitProviderCli,
|
|
5206
5235
|
upsertReviewedSha,
|
|
5207
5236
|
upsertCiReviewRequestedSha,
|
|
@@ -5343,6 +5372,7 @@ export {
|
|
|
5343
5372
|
SdkAgentRunner,
|
|
5344
5373
|
SIZING_MODEL,
|
|
5345
5374
|
SDK_ALLOWED_TOOLS,
|
|
5375
|
+
SANDBOX_STARTUP_GRACE_MS,
|
|
5346
5376
|
SANDBOX_MOUNT,
|
|
5347
5377
|
ReviewPassedCollector,
|
|
5348
5378
|
ProjectSandboxOverrideError,
|