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.
Files changed (107) hide show
  1. package/0.js +56 -45
  2. package/README.md +12 -0
  3. package/chunks/{adapt-loop-J6QCZZTB.js → adapt-loop-3CCOWE43.js} +4 -4
  4. package/chunks/{artifact-scraper-MFKT6OI4.js → artifact-scraper-Z4UUIOH3.js} +2 -2
  5. package/chunks/{assumption-mining-646LYSJY.js → assumption-mining-2UAKQB6V.js} +18 -14
  6. package/chunks/{chunk-OHHCGDJ2.js → chunk-2WWAPKAW.js} +126 -1089
  7. package/chunks/{chunk-XDKB2P5X.js → chunk-3BPX75OR.js} +1 -1
  8. package/chunks/chunk-4ZYYX5GY.js +74 -0
  9. package/chunks/{chunk-3E2KALZ5.js → chunk-5JNTMWPK.js} +2 -2
  10. package/chunks/{chunk-CPWA6RWE.js → chunk-5WAJGLCW.js} +3 -3
  11. package/chunks/{chunk-KAU6NLGP.js → chunk-6KFB6AYN.js} +515 -20
  12. package/chunks/{chunk-XA2O4WXA.js → chunk-6WQDXWQC.js} +169 -15
  13. package/chunks/chunk-6YZE552K.js +5603 -0
  14. package/chunks/chunk-BGX53365.js +309 -0
  15. package/chunks/{chunk-X7LAEEUW.js → chunk-C6JPA32H.js} +1 -1
  16. package/chunks/chunk-CF3MNS3O.js +990 -0
  17. package/chunks/{chunk-4VGLO2YS.js → chunk-CPCHLL6A.js} +5 -2
  18. package/chunks/{chunk-ZKQ2I3S2.js → chunk-DPUXXMJW.js} +1 -1
  19. package/chunks/{chunk-XCISCI4T.js → chunk-E3OOBVOK.js} +30 -12
  20. package/chunks/{chunk-4G7OVYJX.js → chunk-E74LXQFY.js} +1 -1
  21. package/chunks/{chunk-OSBPGWTE.js → chunk-ELI3BVIK.js} +3 -3
  22. package/chunks/chunk-FFHRANYC.js +547 -0
  23. package/chunks/{chunk-WSDDGFA6.js → chunk-G2BE2FC7.js} +1 -1
  24. package/chunks/chunk-GQ3DGHWE.js +1659 -0
  25. package/chunks/{chunk-V36HVTUL.js → chunk-GVY3S5E5.js} +21 -18
  26. package/chunks/{chunk-5BTJMINS.js → chunk-H4DH7COV.js} +192 -40
  27. package/chunks/{chunk-HHPWRY2M.js → chunk-H6Q2QAIY.js} +23475 -22747
  28. package/chunks/{chunk-7XCKYNLO.js → chunk-IHPTOTLL.js} +1 -1
  29. package/chunks/chunk-IXAHQICR.js +31 -0
  30. package/chunks/{chunk-TH2LW437.js → chunk-KCA35IL3.js} +2 -190
  31. package/chunks/{chunk-VWITLWXB.js → chunk-L2CCO4KE.js} +282 -34
  32. package/chunks/{chunk-6YNKRUMC.js → chunk-NJLKFC66.js} +321 -5
  33. package/chunks/{chunk-W6UWZFKL.js → chunk-O6KPVYOI.js} +100 -5
  34. package/chunks/{chunk-LGCASF2C.js → chunk-OAS5SAVZ.js} +6767 -2100
  35. package/chunks/chunk-PS5E7LUQ.js +277 -0
  36. package/chunks/{chunk-AV2LG2L7.js → chunk-QVIRWJ45.js} +257 -145
  37. package/chunks/{chunk-TV27OWIW.js → chunk-QWK4R3DB.js} +9 -9
  38. package/chunks/{chunk-WZRVS7SB.js → chunk-RCRTSKQN.js} +1 -1
  39. package/chunks/chunk-RS4MAN3Q.js +73 -0
  40. package/chunks/{chunk-V6GHX34G.js → chunk-RTBZ36BD.js} +8 -274
  41. package/chunks/{chunk-YL6YAGLL.js → chunk-SCWWLABR.js} +1 -1
  42. package/chunks/chunk-VDOWARMJ.js +63 -0
  43. package/chunks/{chunk-IFME676H.js → chunk-WBRGXCU2.js} +79 -18
  44. package/chunks/{chunk-IVQV574Y.js → chunk-WHUAUFSX.js} +1 -1
  45. package/chunks/{chunk-GOEA3NW6.js → chunk-XODDLNBI.js} +1534 -1305
  46. package/chunks/chunk-YAZARMJY.js +355 -0
  47. package/chunks/{chunk-NLH4LMWH.js → chunk-YED25PA4.js} +1 -1
  48. package/chunks/{codex-models-NHQW45SO.js → codex-models-Z43V3427.js} +9 -3
  49. package/chunks/{commands-N6GN5SBF.js → commands-EUGF4FUZ.js} +5842 -4093
  50. package/chunks/{db-5IWEJUYV.js → db-NVAW4WWR.js} +3 -3
  51. package/chunks/{dist-L3XJWWZ6.js → dist-27BNG6WD.js} +193 -112
  52. package/chunks/{dist-FSS7MBDF.js → dist-7RUOKPRJ.js} +12 -2
  53. package/chunks/{dist-KEULE5IQ.js → dist-LNXGATR2.js} +2 -2
  54. package/chunks/{dist-XZB53PS5.js → dist-M2X367VW.js} +2 -2
  55. package/chunks/{dist-BS4CY4RG.js → dist-NG2HT5XU.js} +27 -1
  56. package/chunks/{eval-runner-EMEGF24P.js → eval-runner-6Q3H52SP.js} +13 -9
  57. package/chunks/{exploit-agent-KW4EB7BE.js → exploit-agent-FCCR2HYX.js} +1 -1
  58. package/chunks/{exploit-autoclimb-CXJ2ERRO.js → exploit-autoclimb-X5ZS5FAO.js} +1 -1
  59. package/chunks/{exploit-climb-TJWLY4XC.js → exploit-climb-3JHGEO36.js} +2 -2
  60. package/chunks/{fix-UUKPXCFB.js → fix-NSNPU33B.js} +12 -6
  61. package/chunks/{harness-C5HOKIEV.js → harness-KXQOVIDQ.js} +3 -3
  62. package/chunks/{hunt-scan-KZZ444V2.js → hunt-scan-ELTKGKC4.js} +16 -12
  63. package/chunks/{kernel-primitive-Z77IR7YS.js → kernel-primitive-MT7YZSIG.js} +2 -2
  64. package/chunks/{kernel-vm-runner-JW77P276.js → kernel-vm-runner-YFRHYC6G.js} +2 -2
  65. package/chunks/{native-loop-P6P5GYVJ.js → native-loop-OFJDRHPI.js} +18 -8
  66. package/chunks/orchestrate-DRZ2SVCP.js +62 -0
  67. package/chunks/{prepare-KYHGFWCJ.js → prepare-72GWTRVI.js} +6 -2
  68. package/chunks/{process-N245QIQG.js → process-MJYJ2K5E.js} +2 -1
  69. package/chunks/{run-GAFKBCTN.js → run-WTJCZZHV.js} +1901 -4816
  70. package/chunks/{runtime-H7YBR5IZ.js → runtime-FCKOUB7V.js} +10 -4
  71. package/chunks/{runtime-XFWJTBUO.js → runtime-LCJ4XSKE.js} +5 -1
  72. package/chunks/{scan-stream-BMOCPKQX.js → scan-stream-GT6GKQ3U.js} +7 -1
  73. package/chunks/{scope-QWY4VQZ7.js → scope-E5QZUOOH.js} +2 -2
  74. package/chunks/service-plugins-MSK2EQP6.js +62 -0
  75. package/chunks/{session-store-XIDFGG7K.js → session-store-H3Q7KCYG.js} +6 -4
  76. package/chunks/{variant-candidates-7D4F5E4H.js → variant-candidates-SZKDCOLK.js} +9 -3
  77. package/chunks/{web-recon-prepass-VXXBPDAV.js → web-recon-prepass-IO3GXQMW.js} +11 -6
  78. package/dashboard/apple-touch-icon.png +0 -0
  79. package/dashboard/assets/operations-BvB5Qpmk.css +1 -0
  80. package/dashboard/assets/operations-D8Ii01SV.js +114 -0
  81. package/dashboard/assets/zero-peek-Dzm3MT3X.png +0 -0
  82. package/dashboard/favicon.ico +0 -0
  83. package/dashboard/favicon.svg +10 -0
  84. package/dashboard/index.html +5 -3
  85. package/package.json +1 -1
  86. package/chunks/chunk-6GHSR47F.js +0 -147
  87. package/chunks/chunk-BCSRRQG5.js +0 -3514
  88. package/chunks/chunk-BOUGKEKA.js +0 -193
  89. package/chunks/chunk-GHWODZMR.js +0 -16
  90. package/chunks/orchestrate-DWWUT5RC.js +0 -58
  91. package/dashboard/assets/bot-Bg1Tp9_d.js +0 -1
  92. package/dashboard/assets/dist-B_dDCPbu.js +0 -1
  93. package/dashboard/assets/findings-page-mayGhNx6.js +0 -5
  94. package/dashboard/assets/format--susmHX7.js +0 -1
  95. package/dashboard/assets/input-TuVPR3Zl.js +0 -1
  96. package/dashboard/assets/jsx-runtime-MGseXjj5.js +0 -3
  97. package/dashboard/assets/live-page-HWWHX99U.js +0 -1
  98. package/dashboard/assets/meta-tile-ckHo-w7k.js +0 -1
  99. package/dashboard/assets/operations-BxZ0OoXO.js +0 -10
  100. package/dashboard/assets/operations-CMFVrXEe.css +0 -2
  101. package/dashboard/assets/operations-app-D3d35rgo.js +0 -46
  102. package/dashboard/assets/overview-page-W_4IteEr.js +0 -1
  103. package/dashboard/assets/page-header-Bw9AJgBV.js +0 -1
  104. package/dashboard/assets/scans-page-CgzVXHnn.js +0 -1
  105. package/dashboard/assets/siren-KQj8QAnJ.js +0 -1
  106. package/dashboard/assets/table-8EDklulN.js +0 -1
  107. 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-OHHCGDJ2.js";
11
+ } from "./chunk-CF3MNS3O.js";
12
12
  import {
13
13
  homeStateDir
14
- } from "./chunk-KAU6NLGP.js";
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 source = await readFile(resolveScopedPath(candidateWorktree.root, sourceFile), "utf8");
862
- if (Buffer.byteLength(source) > MAX_SOURCE_BYTES) {
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
- const postcondition = await evaluateVerificationSpec(options.finding.verificationSpec, candidateWorktree.root);
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 = openSync(temporary, "wx", 384);
1335
+ const fd = openSync2(temporary, "wx", 384);
1184
1336
  try {
1185
1337
  writeFileSync(fd, JSON.stringify(record));
1186
- fsyncSync(fd);
1338
+ fsyncSync2(fd);
1187
1339
  } finally {
1188
- closeSync(fd);
1340
+ closeSync2(fd);
1189
1341
  }
1190
1342
  renameSync(temporary, join(directory, `${key}.json`));
1191
- const directoryFd = openSync(directory, "r");
1343
+ const directoryFd = openSync2(directory, "r");
1192
1344
  try {
1193
- fsyncSync(directoryFd);
1345
+ fsyncSync2(directoryFd);
1194
1346
  } finally {
1195
- closeSync(directoryFd);
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,