0sec-cli 0.22.1 → 0.22.4
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/0.js +56 -45
- package/README.md +12 -0
- package/chunks/{adapt-loop-J6QCZZTB.js → adapt-loop-3CCOWE43.js} +4 -4
- package/chunks/{artifact-scraper-MFKT6OI4.js → artifact-scraper-Z4UUIOH3.js} +2 -2
- package/chunks/{assumption-mining-646LYSJY.js → assumption-mining-2UAKQB6V.js} +18 -14
- package/chunks/{chunk-OHHCGDJ2.js → chunk-2WWAPKAW.js} +126 -1089
- package/chunks/{chunk-XDKB2P5X.js → chunk-3BPX75OR.js} +1 -1
- package/chunks/chunk-4ZYYX5GY.js +74 -0
- package/chunks/{chunk-3E2KALZ5.js → chunk-5JNTMWPK.js} +2 -2
- package/chunks/{chunk-CPWA6RWE.js → chunk-5WAJGLCW.js} +3 -3
- package/chunks/{chunk-KAU6NLGP.js → chunk-6KFB6AYN.js} +515 -20
- package/chunks/{chunk-XA2O4WXA.js → chunk-6WQDXWQC.js} +169 -15
- package/chunks/chunk-6YZE552K.js +5603 -0
- package/chunks/chunk-BGX53365.js +309 -0
- package/chunks/{chunk-X7LAEEUW.js → chunk-C6JPA32H.js} +1 -1
- package/chunks/chunk-CF3MNS3O.js +990 -0
- package/chunks/{chunk-4VGLO2YS.js → chunk-CPCHLL6A.js} +5 -2
- package/chunks/{chunk-ZKQ2I3S2.js → chunk-DPUXXMJW.js} +1 -1
- package/chunks/{chunk-XCISCI4T.js → chunk-E3OOBVOK.js} +30 -12
- package/chunks/{chunk-4G7OVYJX.js → chunk-E74LXQFY.js} +1 -1
- package/chunks/{chunk-OSBPGWTE.js → chunk-ELI3BVIK.js} +3 -3
- package/chunks/chunk-FFHRANYC.js +547 -0
- package/chunks/{chunk-WSDDGFA6.js → chunk-G2BE2FC7.js} +1 -1
- package/chunks/chunk-GQ3DGHWE.js +1659 -0
- package/chunks/{chunk-V36HVTUL.js → chunk-GVY3S5E5.js} +21 -18
- package/chunks/{chunk-5BTJMINS.js → chunk-H4DH7COV.js} +192 -40
- package/chunks/{chunk-HHPWRY2M.js → chunk-H6Q2QAIY.js} +23475 -22747
- package/chunks/{chunk-7XCKYNLO.js → chunk-IHPTOTLL.js} +1 -1
- package/chunks/chunk-IXAHQICR.js +31 -0
- package/chunks/{chunk-TH2LW437.js → chunk-KCA35IL3.js} +2 -190
- package/chunks/{chunk-VWITLWXB.js → chunk-L2CCO4KE.js} +282 -34
- package/chunks/{chunk-6YNKRUMC.js → chunk-NJLKFC66.js} +321 -5
- package/chunks/{chunk-W6UWZFKL.js → chunk-O6KPVYOI.js} +100 -5
- package/chunks/{chunk-LGCASF2C.js → chunk-OAS5SAVZ.js} +6767 -2100
- package/chunks/chunk-PS5E7LUQ.js +277 -0
- package/chunks/{chunk-AV2LG2L7.js → chunk-QVIRWJ45.js} +257 -145
- package/chunks/{chunk-TV27OWIW.js → chunk-QWK4R3DB.js} +9 -9
- package/chunks/{chunk-WZRVS7SB.js → chunk-RCRTSKQN.js} +1 -1
- package/chunks/chunk-RS4MAN3Q.js +73 -0
- package/chunks/{chunk-V6GHX34G.js → chunk-RTBZ36BD.js} +8 -274
- package/chunks/{chunk-YL6YAGLL.js → chunk-SCWWLABR.js} +1 -1
- package/chunks/chunk-VDOWARMJ.js +63 -0
- package/chunks/{chunk-IFME676H.js → chunk-WBRGXCU2.js} +79 -18
- package/chunks/{chunk-IVQV574Y.js → chunk-WHUAUFSX.js} +1 -1
- package/chunks/{chunk-GOEA3NW6.js → chunk-XODDLNBI.js} +1534 -1305
- package/chunks/chunk-YAZARMJY.js +355 -0
- package/chunks/{chunk-NLH4LMWH.js → chunk-YED25PA4.js} +1 -1
- package/chunks/{codex-models-NHQW45SO.js → codex-models-Z43V3427.js} +9 -3
- package/chunks/{commands-N6GN5SBF.js → commands-EUGF4FUZ.js} +5842 -4093
- package/chunks/{db-5IWEJUYV.js → db-NVAW4WWR.js} +3 -3
- package/chunks/{dist-L3XJWWZ6.js → dist-27BNG6WD.js} +193 -112
- package/chunks/{dist-FSS7MBDF.js → dist-7RUOKPRJ.js} +12 -2
- package/chunks/{dist-KEULE5IQ.js → dist-LNXGATR2.js} +2 -2
- package/chunks/{dist-XZB53PS5.js → dist-M2X367VW.js} +2 -2
- package/chunks/{dist-BS4CY4RG.js → dist-NG2HT5XU.js} +27 -1
- package/chunks/{eval-runner-EMEGF24P.js → eval-runner-6Q3H52SP.js} +13 -9
- package/chunks/{exploit-agent-KW4EB7BE.js → exploit-agent-FCCR2HYX.js} +1 -1
- package/chunks/{exploit-autoclimb-CXJ2ERRO.js → exploit-autoclimb-X5ZS5FAO.js} +1 -1
- package/chunks/{exploit-climb-TJWLY4XC.js → exploit-climb-3JHGEO36.js} +2 -2
- package/chunks/{fix-UUKPXCFB.js → fix-NSNPU33B.js} +12 -6
- package/chunks/{harness-C5HOKIEV.js → harness-KXQOVIDQ.js} +3 -3
- package/chunks/{hunt-scan-KZZ444V2.js → hunt-scan-ELTKGKC4.js} +16 -12
- package/chunks/{kernel-primitive-Z77IR7YS.js → kernel-primitive-MT7YZSIG.js} +2 -2
- package/chunks/{kernel-vm-runner-JW77P276.js → kernel-vm-runner-YFRHYC6G.js} +2 -2
- package/chunks/{native-loop-P6P5GYVJ.js → native-loop-OFJDRHPI.js} +18 -8
- package/chunks/orchestrate-DRZ2SVCP.js +62 -0
- package/chunks/{prepare-KYHGFWCJ.js → prepare-72GWTRVI.js} +6 -2
- package/chunks/{process-N245QIQG.js → process-MJYJ2K5E.js} +2 -1
- package/chunks/{run-GAFKBCTN.js → run-WTJCZZHV.js} +1901 -4816
- package/chunks/{runtime-H7YBR5IZ.js → runtime-FCKOUB7V.js} +10 -4
- package/chunks/{runtime-XFWJTBUO.js → runtime-LCJ4XSKE.js} +5 -1
- package/chunks/{scan-stream-BMOCPKQX.js → scan-stream-GT6GKQ3U.js} +7 -1
- package/chunks/{scope-QWY4VQZ7.js → scope-E5QZUOOH.js} +2 -2
- package/chunks/service-plugins-MSK2EQP6.js +62 -0
- package/chunks/{session-store-XIDFGG7K.js → session-store-H3Q7KCYG.js} +6 -4
- package/chunks/{variant-candidates-7D4F5E4H.js → variant-candidates-SZKDCOLK.js} +9 -3
- package/chunks/{web-recon-prepass-VXXBPDAV.js → web-recon-prepass-IO3GXQMW.js} +11 -6
- package/dashboard/apple-touch-icon.png +0 -0
- package/dashboard/assets/operations-BvB5Qpmk.css +1 -0
- package/dashboard/assets/operations-D8Ii01SV.js +114 -0
- package/dashboard/assets/zero-peek-Dzm3MT3X.png +0 -0
- package/dashboard/favicon.ico +0 -0
- package/dashboard/favicon.svg +10 -0
- package/dashboard/index.html +5 -3
- package/package.json +1 -1
- package/chunks/chunk-6GHSR47F.js +0 -147
- package/chunks/chunk-BCSRRQG5.js +0 -3514
- package/chunks/chunk-BOUGKEKA.js +0 -193
- package/chunks/chunk-GHWODZMR.js +0 -16
- package/chunks/orchestrate-DWWUT5RC.js +0 -58
- package/dashboard/assets/bot-Bg1Tp9_d.js +0 -1
- package/dashboard/assets/dist-B_dDCPbu.js +0 -1
- package/dashboard/assets/findings-page-mayGhNx6.js +0 -5
- package/dashboard/assets/format--susmHX7.js +0 -1
- package/dashboard/assets/input-TuVPR3Zl.js +0 -1
- package/dashboard/assets/jsx-runtime-MGseXjj5.js +0 -3
- package/dashboard/assets/live-page-HWWHX99U.js +0 -1
- package/dashboard/assets/meta-tile-ckHo-w7k.js +0 -1
- package/dashboard/assets/operations-BxZ0OoXO.js +0 -10
- package/dashboard/assets/operations-CMFVrXEe.css +0 -2
- package/dashboard/assets/operations-app-D3d35rgo.js +0 -46
- package/dashboard/assets/overview-page-W_4IteEr.js +0 -1
- package/dashboard/assets/page-header-Bw9AJgBV.js +0 -1
- package/dashboard/assets/scans-page-CgzVXHnn.js +0 -1
- package/dashboard/assets/siren-KQj8QAnJ.js +0 -1
- package/dashboard/assets/table-8EDklulN.js +0 -1
- package/dashboard/assets/tabs-CFHPOwg8.js +0 -1
|
@@ -8,10 +8,10 @@ import {
|
|
|
8
8
|
parsePatch,
|
|
9
9
|
readEvolutionArtifact,
|
|
10
10
|
resolveScopedPath
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-CF3MNS3O.js";
|
|
12
12
|
import {
|
|
13
13
|
homeStateDir
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-6KFB6AYN.js";
|
|
15
15
|
import {
|
|
16
16
|
external_exports
|
|
17
17
|
} from "./chunk-VIT5ALXF.js";
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
// packages/core/dist/fix/source-fix.js
|
|
20
20
|
import { execFile } from "node:child_process";
|
|
21
21
|
import { mkdtemp, readFile, realpath, rm, writeFile } from "node:fs/promises";
|
|
22
|
+
import { constants, closeSync, fstatSync, fsyncSync, ftruncateSync, lstatSync, openSync, readFileSync, writeSync } from "node:fs";
|
|
22
23
|
import { tmpdir } from "node:os";
|
|
23
24
|
import { relative, resolve, sep } from "node:path";
|
|
24
25
|
import { promisify } from "node:util";
|
|
@@ -850,6 +851,7 @@ async function runSourceFix(options) {
|
|
|
850
851
|
try {
|
|
851
852
|
options.signal?.throwIfAborted();
|
|
852
853
|
const candidateWorktree = await createCandidateWorktree(options.repoRoot);
|
|
854
|
+
options.onProgress?.({ stage: "preparing-candidate" });
|
|
853
855
|
worktree = candidateWorktree;
|
|
854
856
|
const sourceHint = sourcePathHint(options.finding);
|
|
855
857
|
if (!sourceHint) {
|
|
@@ -858,8 +860,9 @@ async function runSourceFix(options) {
|
|
|
858
860
|
const repoRoot = await realpath(options.repoRoot);
|
|
859
861
|
const sourceAbsolutePath = resolveScopedPath(repoRoot, sourceHint);
|
|
860
862
|
const sourceFile = relativeSourcePath(repoRoot, sourceAbsolutePath);
|
|
861
|
-
const
|
|
862
|
-
|
|
863
|
+
const sourceBytes = await readFile(resolveScopedPath(candidateWorktree.root, sourceFile));
|
|
864
|
+
const source = sourceBytes.toString("utf8");
|
|
865
|
+
if (sourceBytes.length > MAX_SOURCE_BYTES) {
|
|
863
866
|
return failureResult(options.finding, "precondition_failed", attempts, `source file exceeds ${MAX_SOURCE_BYTES} byte remediation limit`, { sourceFile });
|
|
864
867
|
}
|
|
865
868
|
const precondition = await evaluateVerificationSpec(options.finding.verificationSpec, candidateWorktree.root);
|
|
@@ -871,6 +874,7 @@ async function runSourceFix(options) {
|
|
|
871
874
|
const testTimeoutMs = options.testTimeoutMs ?? 3e5;
|
|
872
875
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
873
876
|
options.signal?.throwIfAborted();
|
|
877
|
+
options.onProgress?.({ stage: "generating", attempt });
|
|
874
878
|
const response = await options.runtime.executeNative("Generate a narrowly-scoped, testable source fix. Never claim success without using propose_fix.", messages, [proposeFixTool], void 0, options.signal);
|
|
875
879
|
options.signal?.throwIfAborted();
|
|
876
880
|
if (response.cancelled)
|
|
@@ -880,6 +884,7 @@ async function runSourceFix(options) {
|
|
|
880
884
|
if (!proposalResult.proposal) {
|
|
881
885
|
const reason = proposalResult.error ?? "model did not submit a valid propose_fix tool call";
|
|
882
886
|
attempts.push({ attempt, reason });
|
|
887
|
+
options.onProgress?.({ stage: "attempt-rejected", attempt, reason });
|
|
883
888
|
if (proposalResult.toolUseId) {
|
|
884
889
|
appendToolFeedback(messages, proposalResult.toolUseId, `${reason}. Submit a valid propose_fix payload.`);
|
|
885
890
|
} else {
|
|
@@ -896,21 +901,28 @@ async function runSourceFix(options) {
|
|
|
896
901
|
} catch (error) {
|
|
897
902
|
const reason = `patch rejected: ${errorMessage(error)}`;
|
|
898
903
|
attempts.push({ attempt, reason });
|
|
904
|
+
options.onProgress?.({ stage: "attempt-rejected", attempt, reason });
|
|
899
905
|
await resetCandidate(candidateWorktree.root);
|
|
900
906
|
appendToolFeedback(messages, proposalResult.toolUseId, `${reason}
|
|
901
907
|
Produce a different minimal patch.`);
|
|
902
908
|
continue;
|
|
903
909
|
}
|
|
904
910
|
const generatedDiff = await git(candidateWorktree.root, ["diff", "--no-ext-diff", "--binary", "HEAD", "--"]);
|
|
905
|
-
|
|
911
|
+
options.onProgress?.({ stage: "source-recheck", attempt });
|
|
912
|
+
let postcondition = await evaluateVerificationSpec(options.finding.verificationSpec, candidateWorktree.root);
|
|
913
|
+
const generatedSourceTransition = hasSemanticFixSignal(postcondition);
|
|
914
|
+
options.onProgress?.({ stage: "regression-start", attempt });
|
|
906
915
|
const test = await runTestCommand(testCommand, candidateWorktree.root, testTimeoutMs, options.signal);
|
|
916
|
+
options.onProgress?.({ stage: "regression-completed", attempt, test });
|
|
917
|
+
postcondition = await evaluateVerificationSpec(options.finding.verificationSpec, candidateWorktree.root);
|
|
907
918
|
lastCandidate = { sourceFile, precondition, postcondition, test, diff: generatedDiff, patch: proposal.patch, rationale: proposal.rationale };
|
|
908
919
|
options.signal?.throwIfAborted();
|
|
909
920
|
const testedDiff = await git(candidateWorktree.root, ["diff", "--no-ext-diff", "--binary", "HEAD", "--"]);
|
|
910
921
|
const changedByTest = generatedDiff !== testedDiff || !generatedDiff;
|
|
911
|
-
if (changedByTest || !hasSemanticFixSignal(postcondition) || test.exitCode !== 0 || test.timedOut) {
|
|
912
|
-
const reason = changedByTest ? "regression command changed the generated source diff, or the patch contains no change" : !hasSemanticFixSignal(postcondition) ? "patch did not produce a valid semantic transition out of the vulnerable-source contract" : test.timedOut ? "post-patch test command timed out" : `post-patch test command exited ${test.exitCode ?? "without an exit code"}`;
|
|
922
|
+
if (changedByTest || !generatedSourceTransition || !hasSemanticFixSignal(postcondition) || test.exitCode !== 0 || test.timedOut) {
|
|
923
|
+
const reason = changedByTest ? "regression command changed the generated source diff, or the patch contains no change" : !generatedSourceTransition || !hasSemanticFixSignal(postcondition) ? "patch did not produce a valid semantic transition out of the vulnerable-source contract" : test.timedOut ? "post-patch test command timed out" : `post-patch test command exited ${test.exitCode ?? "without an exit code"}`;
|
|
913
924
|
attempts.push({ attempt, reason });
|
|
925
|
+
options.onProgress?.({ stage: "attempt-rejected", attempt, reason });
|
|
914
926
|
await resetCandidate(candidateWorktree.root);
|
|
915
927
|
const verificationDetail = postcondition.failedPredicates.map((predicate) => predicate.reason).join("; ").slice(0, 2e3);
|
|
916
928
|
appendToolFeedback(messages, proposalResult.toolUseId, `${reason}. Verification detail: ${verificationDetail || "none"}
|
|
@@ -959,7 +971,9 @@ Produce a different minimal patch.`);
|
|
|
959
971
|
candidate: structuredClone(candidate),
|
|
960
972
|
sourceFile,
|
|
961
973
|
diff: generatedDiff,
|
|
962
|
-
testCommand
|
|
974
|
+
testCommand,
|
|
975
|
+
originalSource: sourceBytes,
|
|
976
|
+
reviewedSource: await readFile(resolveScopedPath(candidateWorktree.root, sourceFile))
|
|
963
977
|
});
|
|
964
978
|
return result;
|
|
965
979
|
}
|
|
@@ -1006,11 +1020,141 @@ Produce a different minimal patch.`);
|
|
|
1006
1020
|
}
|
|
1007
1021
|
}
|
|
1008
1022
|
}
|
|
1023
|
+
async function assertOriginalBaseline(verified) {
|
|
1024
|
+
const root = verified.candidate.repoRoot;
|
|
1025
|
+
if (await git(root, ["rev-parse", "HEAD"]) !== verified.candidate.baseCommit) {
|
|
1026
|
+
throw new Error("original repository HEAD changed after preparation; regenerate the fix");
|
|
1027
|
+
}
|
|
1028
|
+
if (await git(root, ["status", "--porcelain=v1", "--untracked-files=all"])) {
|
|
1029
|
+
throw new Error("original repository contains concurrent changes; refusing source-fix execution");
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
async function assertRetainedCandidate(verified) {
|
|
1033
|
+
const cwd = verified.candidate.worktree;
|
|
1034
|
+
if (await git(cwd, ["rev-parse", "HEAD"]) !== (verified.commit ?? verified.candidate.baseCommit)) {
|
|
1035
|
+
throw new Error("candidate HEAD changed after verification; regenerate the fix");
|
|
1036
|
+
}
|
|
1037
|
+
if (await git(cwd, ["diff", "--no-ext-diff", "--binary", verified.candidate.baseCommit, "--"]) !== verified.diff) {
|
|
1038
|
+
throw new Error("candidate changed after review; regenerate the fix");
|
|
1039
|
+
}
|
|
1040
|
+
if (!(await readFile(resolveScopedPath(cwd, verified.sourceFile))).equals(verified.reviewedSource)) {
|
|
1041
|
+
throw new Error("candidate source bytes differ from the validated source change");
|
|
1042
|
+
}
|
|
1043
|
+
if (verified.commit && await git(cwd, ["status", "--porcelain=v1", "--untracked-files=no"])) {
|
|
1044
|
+
throw new Error("candidate contains concurrent tracked changes; refusing source-fix execution");
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
async function verifySourceFixCandidate(result, options = {}) {
|
|
1048
|
+
const verified = verifiedCandidates.get(result);
|
|
1049
|
+
if (!verified || result.status !== "validated_candidate" || verified.applied) {
|
|
1050
|
+
throw new Error("only an unapplied, retained, process-verified candidate can be re-tested");
|
|
1051
|
+
}
|
|
1052
|
+
verified.verificationFailed = true;
|
|
1053
|
+
try {
|
|
1054
|
+
options.signal?.throwIfAborted();
|
|
1055
|
+
await assertOriginalBaseline(verified);
|
|
1056
|
+
await assertRetainedCandidate(verified);
|
|
1057
|
+
const precondition = await evaluateVerificationSpec(verified.finding.verificationSpec, verified.candidate.repoRoot);
|
|
1058
|
+
if (!precondition.passed)
|
|
1059
|
+
throw new Error("original source no longer reproduces the finding's verified contract");
|
|
1060
|
+
const postcondition = await evaluateVerificationSpec(verified.finding.verificationSpec, verified.candidate.worktree);
|
|
1061
|
+
if (!hasSemanticFixSignal(postcondition))
|
|
1062
|
+
throw new Error("candidate no longer makes a valid semantic transition out of the vulnerable-source contract");
|
|
1063
|
+
const test = await runTestCommand(verified.testCommand, verified.candidate.worktree, options.testTimeoutMs ?? 3e5, options.signal);
|
|
1064
|
+
options.signal?.throwIfAborted();
|
|
1065
|
+
if (test.exitCode !== 0 || test.timedOut) {
|
|
1066
|
+
return { ...result, status: "not_fixed", precondition, postcondition, test, error: "candidate regression command failed" };
|
|
1067
|
+
}
|
|
1068
|
+
await assertRetainedCandidate(verified);
|
|
1069
|
+
await assertOriginalBaseline(verified);
|
|
1070
|
+
const finalPostcondition = await evaluateVerificationSpec(verified.finding.verificationSpec, verified.candidate.worktree);
|
|
1071
|
+
if (!hasSemanticFixSignal(finalPostcondition))
|
|
1072
|
+
throw new Error("candidate source contract changed during the regression command");
|
|
1073
|
+
verified.verificationFailed = false;
|
|
1074
|
+
return { ...result, precondition, postcondition: finalPostcondition, test, error: void 0 };
|
|
1075
|
+
} catch (error) {
|
|
1076
|
+
return { ...result, status: "not_fixed", error: errorMessage(error) };
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
async function applySourceFixCandidate(result, options) {
|
|
1080
|
+
if (options.approval !== "apply-to-repository")
|
|
1081
|
+
throw new Error("explicit repository application approval is required");
|
|
1082
|
+
const verified = verifiedCandidates.get(result);
|
|
1083
|
+
if (!verified || verified.verificationFailed || verified.applied) {
|
|
1084
|
+
throw new Error("candidate is missing, failed its latest verification, or was already applied");
|
|
1085
|
+
}
|
|
1086
|
+
const refreshed = await verifySourceFixCandidate(result, options);
|
|
1087
|
+
if (refreshed.status !== "validated_candidate")
|
|
1088
|
+
throw new Error(refreshed.error ?? "candidate re-verification failed");
|
|
1089
|
+
const source = resolveScopedPath(verified.candidate.repoRoot, verified.sourceFile);
|
|
1090
|
+
const candidateSource = resolveScopedPath(verified.candidate.worktree, verified.sourceFile);
|
|
1091
|
+
const original = await readFile(source);
|
|
1092
|
+
if (!original.equals(verified.originalSource))
|
|
1093
|
+
throw new Error("original source differs from the reviewed baseline");
|
|
1094
|
+
const replacement = await readFile(candidateSource);
|
|
1095
|
+
if (!replacement.equals(verified.reviewedSource))
|
|
1096
|
+
throw new Error("candidate source changed after review");
|
|
1097
|
+
const initial = lstatSync(source);
|
|
1098
|
+
if (!initial.isFile() || initial.isSymbolicLink())
|
|
1099
|
+
throw new Error("reviewed source is no longer a regular file");
|
|
1100
|
+
await assertOriginalBaseline(verified);
|
|
1101
|
+
options.signal?.throwIfAborted();
|
|
1102
|
+
const fd = openSync(source, constants.O_RDWR | constants.O_NOFOLLOW);
|
|
1103
|
+
let applicationError;
|
|
1104
|
+
try {
|
|
1105
|
+
const current = fstatSync(fd);
|
|
1106
|
+
const pathState = lstatSync(source);
|
|
1107
|
+
if (current.ino !== initial.ino || current.dev !== initial.dev || pathState.ino !== current.ino || pathState.dev !== current.dev || current.mtimeMs !== initial.mtimeMs || !readFileSync(fd).equals(original)) {
|
|
1108
|
+
throw new Error("source changed concurrently after review; refusing to overwrite user work");
|
|
1109
|
+
}
|
|
1110
|
+
options.signal?.throwIfAborted();
|
|
1111
|
+
verified.applied = true;
|
|
1112
|
+
verified.verificationFailed = true;
|
|
1113
|
+
let offset = 0;
|
|
1114
|
+
while (offset < replacement.length) {
|
|
1115
|
+
const written = writeSync(fd, replacement, offset, replacement.length - offset, offset);
|
|
1116
|
+
if (written === 0)
|
|
1117
|
+
throw new Error("source write made no progress; repository preserved without rollback");
|
|
1118
|
+
offset += written;
|
|
1119
|
+
}
|
|
1120
|
+
ftruncateSync(fd, replacement.length);
|
|
1121
|
+
fsyncSync(fd);
|
|
1122
|
+
} catch (error) {
|
|
1123
|
+
if (!verified.applied)
|
|
1124
|
+
throw error;
|
|
1125
|
+
applicationError = errorMessage(error);
|
|
1126
|
+
} finally {
|
|
1127
|
+
closeSync(fd);
|
|
1128
|
+
}
|
|
1129
|
+
if (applicationError) {
|
|
1130
|
+
return {
|
|
1131
|
+
...refreshed,
|
|
1132
|
+
status: "error",
|
|
1133
|
+
applied: true,
|
|
1134
|
+
error: `Source application encountered an error; repository preserved without rollback: ${applicationError}`
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
const postcondition = await evaluateVerificationSpec(verified.finding.verificationSpec, verified.candidate.repoRoot);
|
|
1138
|
+
if (!hasSemanticFixSignal(postcondition)) {
|
|
1139
|
+
verified.verificationFailed = true;
|
|
1140
|
+
return {
|
|
1141
|
+
...refreshed,
|
|
1142
|
+
status: "error",
|
|
1143
|
+
applied: true,
|
|
1144
|
+
postcondition,
|
|
1145
|
+
error: "reviewed source was applied but its re-check failed; repository preserved without rollback"
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
verified.verificationFailed = false;
|
|
1149
|
+
return { ...refreshed, status: "applied_and_retested", applied: true, postcondition };
|
|
1150
|
+
}
|
|
1009
1151
|
async function planSourceFixPublication(result, options = {}) {
|
|
1010
1152
|
const verified = verifiedCandidates.get(result);
|
|
1011
1153
|
if (!verified || result.status !== "validated_candidate") {
|
|
1012
1154
|
throw new Error("only a retained, verified source-fix candidate can publish");
|
|
1013
1155
|
}
|
|
1156
|
+
if (verified.verificationFailed)
|
|
1157
|
+
throw new Error("candidate failed its latest verification; re-verify before publication");
|
|
1014
1158
|
if (verified.prUrl)
|
|
1015
1159
|
throw new Error(`draft PR already created: ${verified.prUrl}`);
|
|
1016
1160
|
if (verified.plan)
|
|
@@ -1050,6 +1194,9 @@ async function publishSourceFixDraftPR(result, options) {
|
|
|
1050
1194
|
return (await client.run(args, { cwd })).stdout.trim();
|
|
1051
1195
|
};
|
|
1052
1196
|
options.signal?.throwIfAborted();
|
|
1197
|
+
await assertRetainedCandidate(verified);
|
|
1198
|
+
if (verified.verificationFailed)
|
|
1199
|
+
throw new Error("candidate failed its latest verification; refusing publication");
|
|
1053
1200
|
if (await run(["remote", "get-url", "--push", "origin"]) !== plan.remote) {
|
|
1054
1201
|
throw new Error("origin changed after review; refusing publication");
|
|
1055
1202
|
}
|
|
@@ -1067,6 +1214,7 @@ async function publishSourceFixDraftPR(result, options) {
|
|
|
1067
1214
|
throw new Error("published candidate has new tracked changes; refusing publication");
|
|
1068
1215
|
}
|
|
1069
1216
|
const spec = verified.finding.verificationSpec;
|
|
1217
|
+
verified.verificationFailed = true;
|
|
1070
1218
|
const sourceCheck = await evaluateVerificationSpec(spec, cwd);
|
|
1071
1219
|
if (!hasSemanticFixSignal(sourceCheck))
|
|
1072
1220
|
throw new Error("candidate no longer passes the source fix re-check");
|
|
@@ -1077,6 +1225,10 @@ async function publishSourceFixDraftPR(result, options) {
|
|
|
1077
1225
|
if (await run(["diff", "--no-ext-diff", "--binary", verified.candidate.baseCommit, "--"]) !== verified.diff) {
|
|
1078
1226
|
throw new Error("publication regression command changed the reviewed source diff");
|
|
1079
1227
|
}
|
|
1228
|
+
const testedSourceCheck = await evaluateVerificationSpec(spec, cwd);
|
|
1229
|
+
if (!hasSemanticFixSignal(testedSourceCheck))
|
|
1230
|
+
throw new Error("candidate source contract changed during publication regression");
|
|
1231
|
+
verified.verificationFailed = false;
|
|
1080
1232
|
const remoteBase = await run(["ls-remote", "--heads", plan.remote, `refs/heads/${plan.baseBranch}`]);
|
|
1081
1233
|
if (remoteBase.split(/\s+/)[0] !== verified.candidate.baseCommit) {
|
|
1082
1234
|
throw new Error("remote base differs from the verified baseline; update the checkout and regenerate the fix");
|
|
@@ -1140,7 +1292,7 @@ ${verified.finding.evidence.request}` : "",
|
|
|
1140
1292
|
// packages/core/dist/fix/source-fix-inputs.js
|
|
1141
1293
|
import { execFile as execFile2 } from "node:child_process";
|
|
1142
1294
|
import { createHash, randomUUID as randomUUID2 } from "node:crypto";
|
|
1143
|
-
import { closeSync, fsyncSync, openSync, realpathSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
1295
|
+
import { closeSync as closeSync2, fsyncSync as fsyncSync2, openSync as openSync2, realpathSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
1144
1296
|
import { realpath as realpath2, stat } from "node:fs/promises";
|
|
1145
1297
|
import { join, resolve as resolve2 } from "node:path";
|
|
1146
1298
|
import { promisify as promisify2 } from "node:util";
|
|
@@ -1180,19 +1332,19 @@ function saveSourceFixProjectInputs(inputs, homeDir) {
|
|
|
1180
1332
|
const key = createHash("sha256").update(canonicalRoot).digest("hex");
|
|
1181
1333
|
const temporary = join(directory, `.inputs-${randomUUID2()}.tmp`);
|
|
1182
1334
|
try {
|
|
1183
|
-
const fd =
|
|
1335
|
+
const fd = openSync2(temporary, "wx", 384);
|
|
1184
1336
|
try {
|
|
1185
1337
|
writeFileSync(fd, JSON.stringify(record));
|
|
1186
|
-
|
|
1338
|
+
fsyncSync2(fd);
|
|
1187
1339
|
} finally {
|
|
1188
|
-
|
|
1340
|
+
closeSync2(fd);
|
|
1189
1341
|
}
|
|
1190
1342
|
renameSync(temporary, join(directory, `${key}.json`));
|
|
1191
|
-
const directoryFd =
|
|
1343
|
+
const directoryFd = openSync2(directory, "r");
|
|
1192
1344
|
try {
|
|
1193
|
-
|
|
1345
|
+
fsyncSync2(directoryFd);
|
|
1194
1346
|
} finally {
|
|
1195
|
-
|
|
1347
|
+
closeSync2(directoryFd);
|
|
1196
1348
|
}
|
|
1197
1349
|
} finally {
|
|
1198
1350
|
try {
|
|
@@ -1220,6 +1372,8 @@ export {
|
|
|
1220
1372
|
buildHypothesesMarkdown,
|
|
1221
1373
|
emitFindingsAsPRs,
|
|
1222
1374
|
runSourceFix,
|
|
1375
|
+
verifySourceFixCandidate,
|
|
1376
|
+
applySourceFixCandidate,
|
|
1223
1377
|
planSourceFixPublication,
|
|
1224
1378
|
publishSourceFixDraftPR,
|
|
1225
1379
|
resolveSourceFixRepository,
|