loopforge 3.9.2 → 4.0.7

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 (285) hide show
  1. package/README.md +236 -112
  2. package/dist/audit.d.ts +73 -15
  3. package/dist/audit.d.ts.map +1 -1
  4. package/dist/audit.js +131 -61
  5. package/dist/audit.js.map +1 -1
  6. package/dist/canonical-state.d.ts +152 -40
  7. package/dist/canonical-state.d.ts.map +1 -1
  8. package/dist/canonical-state.js +382 -206
  9. package/dist/canonical-state.js.map +1 -1
  10. package/dist/cli.d.ts.map +1 -1
  11. package/dist/cli.js +41 -12
  12. package/dist/cli.js.map +1 -1
  13. package/dist/cognitive-governance.d.ts +0 -2
  14. package/dist/cognitive-governance.d.ts.map +1 -1
  15. package/dist/cognitive-governance.js +0 -4
  16. package/dist/cognitive-governance.js.map +1 -1
  17. package/dist/committed-round.d.ts +105 -141
  18. package/dist/committed-round.d.ts.map +1 -1
  19. package/dist/committed-round.js +249 -372
  20. package/dist/committed-round.js.map +1 -1
  21. package/dist/constraint-lifecycle.d.ts +73 -0
  22. package/dist/constraint-lifecycle.d.ts.map +1 -0
  23. package/dist/constraint-lifecycle.js +174 -0
  24. package/dist/constraint-lifecycle.js.map +1 -0
  25. package/dist/context-boundary.d.ts +83 -0
  26. package/dist/context-boundary.d.ts.map +1 -0
  27. package/dist/context-boundary.js +101 -0
  28. package/dist/context-boundary.js.map +1 -0
  29. package/dist/contract-items.d.ts +12 -8
  30. package/dist/contract-items.d.ts.map +1 -1
  31. package/dist/contract-items.js +31 -26
  32. package/dist/contract-items.js.map +1 -1
  33. package/dist/contract-requirement.d.ts +47 -0
  34. package/dist/contract-requirement.d.ts.map +1 -0
  35. package/dist/contract-requirement.js +51 -0
  36. package/dist/contract-requirement.js.map +1 -0
  37. package/dist/criterion-status.d.ts +35 -0
  38. package/dist/criterion-status.d.ts.map +1 -0
  39. package/dist/criterion-status.js +89 -0
  40. package/dist/criterion-status.js.map +1 -0
  41. package/dist/enforcement-gate.d.ts +32 -67
  42. package/dist/enforcement-gate.d.ts.map +1 -1
  43. package/dist/enforcement-gate.js +203 -439
  44. package/dist/enforcement-gate.js.map +1 -1
  45. package/dist/engine.d.ts +9 -103
  46. package/dist/engine.d.ts.map +1 -1
  47. package/dist/engine.js +28 -887
  48. package/dist/engine.js.map +1 -1
  49. package/dist/errors.d.ts +41 -0
  50. package/dist/errors.d.ts.map +1 -0
  51. package/dist/errors.js +69 -0
  52. package/dist/errors.js.map +1 -0
  53. package/dist/evidence-claims.d.ts +33 -31
  54. package/dist/evidence-claims.d.ts.map +1 -1
  55. package/dist/evidence-claims.js +58 -72
  56. package/dist/evidence-claims.js.map +1 -1
  57. package/dist/evidence-posture-view.d.ts +19 -0
  58. package/dist/evidence-posture-view.d.ts.map +1 -0
  59. package/dist/evidence-posture-view.js +116 -0
  60. package/dist/evidence-posture-view.js.map +1 -0
  61. package/dist/evidence-posture.d.ts +73 -0
  62. package/dist/evidence-posture.d.ts.map +1 -0
  63. package/dist/evidence-posture.js +281 -0
  64. package/dist/evidence-posture.js.map +1 -0
  65. package/dist/evidence-provider.d.ts +55 -37
  66. package/dist/evidence-provider.d.ts.map +1 -1
  67. package/dist/evidence-provider.js +182 -332
  68. package/dist/evidence-provider.js.map +1 -1
  69. package/dist/explain.d.ts +167 -27
  70. package/dist/explain.d.ts.map +1 -1
  71. package/dist/explain.js +373 -58
  72. package/dist/explain.js.map +1 -1
  73. package/dist/field-accounting.d.ts.map +1 -1
  74. package/dist/field-accounting.js +3 -22
  75. package/dist/field-accounting.js.map +1 -1
  76. package/dist/generate-schema.js +13 -8
  77. package/dist/generate-schema.js.map +1 -1
  78. package/dist/git-boundary.d.ts +128 -0
  79. package/dist/git-boundary.d.ts.map +1 -0
  80. package/dist/git-boundary.js +625 -0
  81. package/dist/git-boundary.js.map +1 -0
  82. package/dist/git-facts.d.ts +22 -0
  83. package/dist/git-facts.d.ts.map +1 -0
  84. package/dist/git-facts.js +29 -0
  85. package/dist/git-facts.js.map +1 -0
  86. package/dist/git-motion.d.ts +37 -0
  87. package/dist/git-motion.d.ts.map +1 -0
  88. package/dist/git-motion.js +59 -0
  89. package/dist/git-motion.js.map +1 -0
  90. package/dist/history.d.ts +44 -0
  91. package/dist/history.d.ts.map +1 -0
  92. package/dist/history.js +77 -0
  93. package/dist/history.js.map +1 -0
  94. package/dist/index.d.ts +19 -9
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +12 -4
  97. package/dist/index.js.map +1 -1
  98. package/dist/init.d.ts +16 -0
  99. package/dist/init.d.ts.map +1 -1
  100. package/dist/init.js +80 -14
  101. package/dist/init.js.map +1 -1
  102. package/dist/loop-compiler.d.ts +51 -19
  103. package/dist/loop-compiler.d.ts.map +1 -1
  104. package/dist/loop-compiler.js +423 -507
  105. package/dist/loop-compiler.js.map +1 -1
  106. package/dist/loop-store.d.ts +6 -5
  107. package/dist/loop-store.d.ts.map +1 -1
  108. package/dist/loop-store.js +153 -35
  109. package/dist/loop-store.js.map +1 -1
  110. package/dist/mcp/round-lifecycle.d.ts +285 -81
  111. package/dist/mcp/round-lifecycle.d.ts.map +1 -1
  112. package/dist/mcp/round-lifecycle.js +1629 -574
  113. package/dist/mcp/round-lifecycle.js.map +1 -1
  114. package/dist/mcp/server.d.ts +5 -0
  115. package/dist/mcp/server.d.ts.map +1 -1
  116. package/dist/mcp/server.js +61 -19
  117. package/dist/mcp/server.js.map +1 -1
  118. package/dist/mcp/session.d.ts +81 -39
  119. package/dist/mcp/session.d.ts.map +1 -1
  120. package/dist/mcp/session.js +963 -271
  121. package/dist/mcp/session.js.map +1 -1
  122. package/dist/mcp/submission-boundary.d.ts +23 -31
  123. package/dist/mcp/submission-boundary.d.ts.map +1 -1
  124. package/dist/mcp/submission-boundary.js +242 -82
  125. package/dist/mcp/submission-boundary.js.map +1 -1
  126. package/dist/mcp/submission-handle.d.ts +66 -0
  127. package/dist/mcp/submission-handle.d.ts.map +1 -0
  128. package/dist/mcp/submission-handle.js +173 -0
  129. package/dist/mcp/submission-handle.js.map +1 -0
  130. package/dist/mcp/tools.d.ts +17 -7
  131. package/dist/mcp/tools.d.ts.map +1 -1
  132. package/dist/mcp/tools.js +763 -244
  133. package/dist/mcp/tools.js.map +1 -1
  134. package/dist/open-question.d.ts +7 -0
  135. package/dist/open-question.d.ts.map +1 -0
  136. package/dist/open-question.js +14 -0
  137. package/dist/open-question.js.map +1 -0
  138. package/dist/policy-metrics.d.ts +31 -21
  139. package/dist/policy-metrics.d.ts.map +1 -1
  140. package/dist/policy-metrics.js +117 -64
  141. package/dist/policy-metrics.js.map +1 -1
  142. package/dist/policy.d.ts +15 -40
  143. package/dist/policy.d.ts.map +1 -1
  144. package/dist/policy.js +13 -13
  145. package/dist/policy.js.map +1 -1
  146. package/dist/progress.d.ts +45 -0
  147. package/dist/progress.d.ts.map +1 -0
  148. package/dist/progress.js +136 -0
  149. package/dist/progress.js.map +1 -0
  150. package/dist/prompt-assembler.d.ts +28 -9
  151. package/dist/prompt-assembler.d.ts.map +1 -1
  152. package/dist/prompt-assembler.js +592 -603
  153. package/dist/prompt-assembler.js.map +1 -1
  154. package/dist/prompt-coverage.d.ts +39 -0
  155. package/dist/prompt-coverage.d.ts.map +1 -0
  156. package/dist/prompt-coverage.js +80 -0
  157. package/dist/prompt-coverage.js.map +1 -0
  158. package/dist/prompt-freshness.d.ts +25 -0
  159. package/dist/prompt-freshness.d.ts.map +1 -0
  160. package/dist/prompt-freshness.js +88 -0
  161. package/dist/prompt-freshness.js.map +1 -0
  162. package/dist/prompt-policy.d.ts +14 -4
  163. package/dist/prompt-policy.d.ts.map +1 -1
  164. package/dist/prompt-policy.js +29 -10
  165. package/dist/prompt-policy.js.map +1 -1
  166. package/dist/protocol.d.ts +875 -419
  167. package/dist/protocol.d.ts.map +1 -1
  168. package/dist/protocol.js +73 -99
  169. package/dist/protocol.js.map +1 -1
  170. package/dist/recovery-guidance.d.ts +13 -0
  171. package/dist/recovery-guidance.d.ts.map +1 -0
  172. package/dist/recovery-guidance.js +15 -0
  173. package/dist/recovery-guidance.js.map +1 -0
  174. package/dist/recovery.d.ts +68 -0
  175. package/dist/recovery.d.ts.map +1 -0
  176. package/dist/recovery.js +234 -0
  177. package/dist/recovery.js.map +1 -0
  178. package/dist/replay.d.ts +42 -8
  179. package/dist/replay.d.ts.map +1 -1
  180. package/dist/replay.js +311 -84
  181. package/dist/replay.js.map +1 -1
  182. package/dist/result-facts.d.ts +45 -0
  183. package/dist/result-facts.d.ts.map +1 -0
  184. package/dist/result-facts.js +86 -0
  185. package/dist/result-facts.js.map +1 -0
  186. package/dist/round-contract.d.ts +38 -8
  187. package/dist/round-contract.d.ts.map +1 -1
  188. package/dist/round-contract.js +71 -31
  189. package/dist/round-contract.js.map +1 -1
  190. package/dist/round-coordinator.d.ts +45 -91
  191. package/dist/round-coordinator.d.ts.map +1 -1
  192. package/dist/round-coordinator.js +254 -271
  193. package/dist/round-coordinator.js.map +1 -1
  194. package/dist/round-driver.d.ts +56 -16
  195. package/dist/round-driver.d.ts.map +1 -1
  196. package/dist/round-driver.js +225 -39
  197. package/dist/round-driver.js.map +1 -1
  198. package/dist/round-facts.d.ts +33 -52
  199. package/dist/round-facts.d.ts.map +1 -1
  200. package/dist/round-facts.js +54 -157
  201. package/dist/round-facts.js.map +1 -1
  202. package/dist/round-guidance.d.ts +56 -0
  203. package/dist/round-guidance.d.ts.map +1 -0
  204. package/dist/round-guidance.js +161 -0
  205. package/dist/round-guidance.js.map +1 -0
  206. package/dist/round-outcome.d.ts +21 -0
  207. package/dist/round-outcome.d.ts.map +1 -0
  208. package/dist/round-outcome.js +31 -0
  209. package/dist/round-outcome.js.map +1 -0
  210. package/dist/round-transaction.d.ts +168 -21
  211. package/dist/round-transaction.d.ts.map +1 -1
  212. package/dist/round-transaction.js +856 -39
  213. package/dist/round-transaction.js.map +1 -1
  214. package/dist/self-eval.d.ts +40 -60
  215. package/dist/self-eval.d.ts.map +1 -1
  216. package/dist/self-eval.js +455 -384
  217. package/dist/self-eval.js.map +1 -1
  218. package/dist/storage.d.ts +16 -0
  219. package/dist/storage.d.ts.map +1 -1
  220. package/dist/storage.js +54 -4
  221. package/dist/storage.js.map +1 -1
  222. package/dist/subgoal-state.d.ts +2 -3
  223. package/dist/subgoal-state.d.ts.map +1 -1
  224. package/dist/subgoal-state.js +2 -3
  225. package/dist/subgoal-state.js.map +1 -1
  226. package/dist/submission-guidance.d.ts +43 -21
  227. package/dist/submission-guidance.d.ts.map +1 -1
  228. package/dist/submission-guidance.js +368 -262
  229. package/dist/submission-guidance.js.map +1 -1
  230. package/dist/submission-receipt.d.ts +16 -8
  231. package/dist/submission-receipt.d.ts.map +1 -1
  232. package/dist/submission-receipt.js +146 -51
  233. package/dist/submission-receipt.js.map +1 -1
  234. package/dist/token-utils.d.ts +47 -7
  235. package/dist/token-utils.d.ts.map +1 -1
  236. package/dist/token-utils.js +66 -15
  237. package/dist/token-utils.js.map +1 -1
  238. package/dist/transaction-identity.d.ts +9 -0
  239. package/dist/transaction-identity.d.ts.map +1 -0
  240. package/dist/transaction-identity.js +69 -0
  241. package/dist/transaction-identity.js.map +1 -0
  242. package/dist/verification-actions.d.ts +13 -0
  243. package/dist/verification-actions.d.ts.map +1 -0
  244. package/dist/verification-actions.js +44 -0
  245. package/dist/verification-actions.js.map +1 -0
  246. package/dist/verification-gate.d.ts +26 -46
  247. package/dist/verification-gate.d.ts.map +1 -1
  248. package/dist/verification-gate.js +218 -427
  249. package/dist/verification-gate.js.map +1 -1
  250. package/dist/version.d.ts +1 -1
  251. package/dist/version.js +1 -1
  252. package/dist/working-memory.d.ts +187 -0
  253. package/dist/working-memory.d.ts.map +1 -0
  254. package/dist/working-memory.js +767 -0
  255. package/dist/working-memory.js.map +1 -0
  256. package/dist/workspace-checkpoint.d.ts +88 -0
  257. package/dist/workspace-checkpoint.d.ts.map +1 -0
  258. package/dist/workspace-checkpoint.js +326 -0
  259. package/dist/workspace-checkpoint.js.map +1 -0
  260. package/dist/workspace-restore.d.ts +68 -0
  261. package/dist/workspace-restore.d.ts.map +1 -0
  262. package/dist/workspace-restore.js +604 -0
  263. package/dist/workspace-restore.js.map +1 -0
  264. package/dist/workspace.d.ts +0 -9
  265. package/dist/workspace.d.ts.map +1 -1
  266. package/dist/workspace.js +0 -17
  267. package/dist/workspace.js.map +1 -1
  268. package/loop_policy.json +5 -19
  269. package/package.json +2 -2
  270. package/skills/forge/SKILL.md +212 -0
  271. package/skills/forge/references/evidence-and-contracts.md +204 -0
  272. package/skills/forge/references/recovery-and-views.md +188 -0
  273. package/dist/cognitive-facts.d.ts +0 -30
  274. package/dist/cognitive-facts.d.ts.map +0 -1
  275. package/dist/cognitive-facts.js +0 -113
  276. package/dist/cognitive-facts.js.map +0 -1
  277. package/dist/loop-extras-parser.d.ts +0 -81
  278. package/dist/loop-extras-parser.d.ts.map +0 -1
  279. package/dist/loop-extras-parser.js +0 -213
  280. package/dist/loop-extras-parser.js.map +0 -1
  281. package/dist/loop-projection.d.ts +0 -6
  282. package/dist/loop-projection.d.ts.map +0 -1
  283. package/dist/loop-projection.js +0 -28
  284. package/dist/loop-projection.js.map +0 -1
  285. package/skills/perception/SKILL.md +0 -370
@@ -17,27 +17,37 @@
17
17
  * Persistence is owned by round-transaction.ts so reject paths remain
18
18
  * side-effect free and accepted decisions can be replayed idempotently.
19
19
  */
20
- import { queryLoopEntries } from "./loop-store.js";
21
- import { CHECK_SUCCESS_WITHOUT_VERIFIED_EVIDENCE, collectOutOfScopeFiles, roundDriftFiles, verifySelfEvaluation, } from "./verification-gate.js";
22
- import { entryRound, isRecord } from "./token-utils.js";
20
+ import { emptyCommittedHistory, historyWindow, loadCommittedHistory, } from "./history.js";
21
+ import { CHECK_SUCCESS_WITHOUT_VERIFIED_EVIDENCE, verifySelfEvaluation, } from "./verification-gate.js";
22
+ import { checkpointForRoundBoundary, deriveRecoveryDirective, validateCheckpointManifest, } from "./recovery.js";
23
23
  import { driftedEntrypoints } from "./evidence-provider.js";
24
- import { declaresBlocked, effectiveOutcome, effectiveSuccess } from "./self-eval.js";
25
- import { derivationRounds, decodeCommittedRound, inFlightGitMotion } from "./committed-round.js";
26
- import { makeRoundId } from "./round-transaction.js";
27
- import { enforceRound, buildRejectionPrompt, findSafeRestorePoint, buildBacktrackPrompt, findBacktrackTargetSnapshot, } from "./enforcement-gate.js";
24
+ import { declaresBlocked, effectiveOutcome, effectiveSuccess, } from "./round-outcome.js";
25
+ import { deriveRoundProgress, } from "./progress.js";
26
+ import { enforceRound, buildRejectionPrompt, findSafeRestorePoint, } from "./enforcement-gate.js";
28
27
  import { logEvent } from "./observability.js";
29
28
  import { getPolicy } from "./policy.js";
30
- import { deriveRoundContractView } from "./round-contract.js";
31
- import { roundVerificationStatus, } from "./contract-items.js";
32
- /** v3.2/v3.6: Whether a round's success enters the success trajectory.
33
- * Excluded when the gate contradicted the round, or when the success claim
34
- * carries no machine-verified observation — the merged R8 check fires a
35
- * warn under machine_backed_success "warn" (v3.6: success_unverified merged
36
- * into R8; its warn-level trajectory exclusion moved with it) — an
37
- * accepted-but-unverified round is not evidence of progress. */
38
- function shouldPushSuccess(gateContradicted, verificationFlags, contractStatuses) {
29
+ import { deriveRoundFacts } from "./round-facts.js";
30
+ import { deriveEvidencePosture, rollbackEvidencePosture, } from "./evidence-posture.js";
31
+ import { deriveFilesReconciliation } from "./files-reconciliation.js";
32
+ /** v3.2/v3.6/v4.0.3: Whether a round's success enters the success
33
+ * trajectory.
34
+ *
35
+ * The trajectory counts MACHINE-BACKED success only: the Agent must
36
+ * effectively claim success AND the verification gate must have found a
37
+ * passed, untampered after command (`machineBackedSuccess`). A partial or
38
+ * blocked outcome, an unbacked success claim, a contradicted gate, a
39
+ * redirect round and an unverified contract item all keep the round out.
40
+ * This does not change `completed` — it only keeps progress metrics honest. */
41
+ function shouldPushSuccess(selfEval, gateContradicted, verificationFlags, machineBackedSuccess, contractStatuses) {
42
+ if (!effectiveSuccess(selfEval))
43
+ return false;
44
+ if (!machineBackedSuccess)
45
+ return false;
39
46
  if (gateContradicted)
40
47
  return false;
48
+ // v3.9.9: a redirect round abandons a slice; it is never success evidence.
49
+ if (contractStatuses?.closure === "superseded")
50
+ return false;
41
51
  // v3.8: an unverified contract item (or a claimed-but-unbacked success)
42
52
  // keeps the round out of the success trajectory — an accepted-but-unverified
43
53
  // round is not evidence of progress.
@@ -69,120 +79,48 @@ function shouldPushSuccess(gateContradicted, verificationFlags, contractStatuses
69
79
  export function finalizeStopReason(
70
80
  /** `undefined` when the disposition named no terminal reason at all (the
71
81
  * callers fall back to their own default) — there is nothing to guard. */
72
- reason, selfEval, contractStatuses) {
82
+ reason, selfEval, contractStatuses,
83
+ /** v3.9.9/v4.0.6: the current round's redirect. A committed redirect can
84
+ * never produce `completed` even if the old items were all verified. */
85
+ redirect) {
73
86
  if (reason !== "completed")
74
87
  return { reason, flags: [] };
75
88
  const declared = declaresBlocked(selfEval);
76
89
  const blockedContract = contractStatuses?.closure === "blocked";
77
- if (!declared && !blockedContract)
78
- return { reason, flags: [] };
79
- return {
80
- reason: "blocked",
81
- flags: [{
82
- severity: "warn",
83
- field: declared ? "outcome" : "round_contract",
84
- check: "completion_truth_downgraded",
85
- detail: declared
86
- ? "The round declared a blocked outcome but the stop decision produced " +
87
- "`completed`; the declaration wins and the stop reason was downgraded " +
88
- "to `blocked`."
89
- : "The active contract closed on a blocked outcome but the stop decision " +
90
- "produced `completed`; the stop reason was downgraded to `blocked`.",
91
- }],
92
- };
93
- }
94
- // ── RoundCoordinator ───────────────────────────────────────────────────────
95
- /** v3.8.1: The rolled-back branch's facts — the ONE derivation of which rounds
96
- * failed, which approaches must not be repeated, which assumptions were
97
- * falsified, and which files (with their failed-round git fingerprints) the
98
- * agent must restore.
99
- *
100
- * Sources are COMMITTED rounds above the restore point; rejected attempts are
101
- * not durable history and never become a source. The in-flight attempt that
102
- * triggered the rollback is the one exception — its evaluation lives in this
103
- * process, not in committed history.
104
- *
105
- * Extracted from the coordinator's inline walk so it is unit-testable without
106
- * driving a whole rollback, and so the prompt, the state file and the
107
- * committed decision all describe one event. */
108
- export function deriveBacktrackRecoveryFacts(input) {
109
- const { currentRound, restoreRound, vaultEntries, selfEval } = input;
110
- // v2.13: files changed in skipped rounds, for the restore prompt.
111
- const skippedFiles = [];
112
- // M3 (v3.7.x): each skipped file's git fingerprint at its failed round.
113
- // Recorded from the round's own after-evidence (machine data, not the
114
- // agent's report); later rounds overwrite earlier ones so the map holds the
115
- // LAST failed state of every file.
116
- const skippedFingerprints = {};
117
- const failedRounds = [];
118
- const approaches = [];
119
- const wrongAssumptions = [];
120
- for (let r = restoreRound + 1; r < currentRound; r++) {
121
- // v3.2.1: match the feedback entry (execution_report lives there) — the
122
- // compile-time lineage entry for the same round precedes it in the flat
123
- // view and carries no evidence.
124
- const entry = vaultEntries.find((e) => entryRound(e) === r && e.task_type !== "loop_lineage");
125
- const view = entry ? decodeCommittedRound(entry) : null;
126
- const evaluation = view?.evaluation ?? null;
127
- if (entry?.execution_report) {
128
- const ev = entry.execution_report;
129
- const reported = Array.isArray(ev.files_changed)
130
- ? ev.files_changed.filter((f) => typeof f === "string")
131
- : [];
132
- // v3.9.1: the machine delta is the primary set of files the failed
133
- // round changed; the agent report is unioned in so an ignored file the
134
- // agent named is not lost from the recovery brief.
135
- const machineFiles = (view?.observationDelta ?? [])
136
- .filter((delta) => delta.kind === "git" && delta.changedFiles !== null)
137
- .flatMap((delta) => delta.changedFiles);
138
- const files = [...new Set([...machineFiles, ...reported])];
139
- const git = view?.afterEvidence.find((snapshot) => snapshot.kind === "git" &&
140
- snapshot.status === "observed" &&
141
- isRecord(snapshot.data.fingerprints));
142
- const gitFingerprints = git && git.kind === "git"
143
- ? git.data.fingerprints
144
- : null;
145
- for (const f of files) {
146
- if (!skippedFiles.includes(f))
147
- skippedFiles.push(f);
148
- const fp = gitFingerprints?.[f];
149
- if (typeof fp === "string")
150
- skippedFingerprints[f] = fp;
151
- }
152
- }
153
- const summary = evaluation?.output_summary ?? "";
154
- if (summary.trim().length > 0) {
155
- failedRounds.push(r);
156
- approaches.push(summary.trim().slice(0, 200));
157
- }
158
- const assumptions = (evaluation?.wrong_assumptions ?? [])
159
- .filter((v) => typeof v === "string")
160
- .map((s) => s.slice(0, 500));
161
- for (const a of assumptions) {
162
- if (!wrongAssumptions.includes(a))
163
- wrongAssumptions.push(a);
164
- }
90
+ if (declared || blockedContract) {
91
+ return {
92
+ reason: "blocked",
93
+ flags: [{
94
+ severity: "warn",
95
+ field: declared ? "outcome" : "round_contract",
96
+ check: "completion_truth_downgraded",
97
+ detail: declared
98
+ ? "The round declared a blocked outcome but the stop decision produced " +
99
+ "`completed`; the declaration wins and the stop reason was downgraded " +
100
+ "to `blocked`."
101
+ : "The active contract closed on a blocked outcome but the stop decision " +
102
+ "produced `completed`; the stop reason was downgraded to `blocked`.",
103
+ }],
104
+ };
165
105
  }
166
- // The in-flight attempt that triggered this rollback.
167
- const triggerSummary = selfEval.output_summary?.trim() ?? "";
168
- if (triggerSummary.length > 0) {
169
- if (!failedRounds.includes(currentRound))
170
- failedRounds.push(currentRound);
171
- approaches.push(triggerSummary.slice(0, 200));
172
- for (const a of (selfEval.wrong_assumptions ?? [])
173
- .filter((v) => typeof v === "string")) {
174
- if (!wrongAssumptions.includes(a))
175
- wrongAssumptions.push(a.slice(0, 500));
176
- }
106
+ const superseded = contractStatuses?.closure === "superseded" ||
107
+ Boolean(redirect);
108
+ if (superseded) {
109
+ return {
110
+ reason: "incomplete",
111
+ flags: [{
112
+ severity: "warn",
113
+ field: "round_contract.supersedes",
114
+ check: "completion_truth_downgraded",
115
+ detail: "The round superseded its active contract, so the old slice is not " +
116
+ "verified and the round cannot be `completed`; the stop reason was " +
117
+ "downgraded to `incomplete`.",
118
+ }],
119
+ };
177
120
  }
178
- return {
179
- skippedFiles,
180
- skippedFingerprints,
181
- failedRounds,
182
- approaches,
183
- wrongAssumptions,
184
- };
121
+ return { reason, flags: [] };
185
122
  }
123
+ // ── RoundCoordinator ───────────────────────────────────────────────────────
186
124
  export class RoundCoordinator {
187
125
  store;
188
126
  constructor(store) {
@@ -199,72 +137,97 @@ export class RoundCoordinator {
199
137
  * - Transactional feedback commit (accepted rounds only)
200
138
  * - State file I/O (both paths, after compiling) */
201
139
  processRound(input) {
202
- const { loopId, task, currentRound, maxRounds, selfEval, lastSelfEval, consecutiveRejections, evidenceDeltas, } = input;
203
- const roundSuccess = selfEval.success ?? false;
204
- // ── 1. Query vault entries ──────────────────────────────────────────
205
- // Lineage entries (constraint violations, output summaries) and feedback
206
- // entries (including execution_report) are both needed by
207
- // the enforcement gate. queryLoopEntries excludes feedback by default,
208
- // so we issue a second query and merge both result sets.
209
- const prefix = `loop:${loopId}:r`;
210
- const vaultEntries = this.store
211
- ? [
212
- ...queryLoopEntries(this.store, loopId, { prefix }),
213
- ...queryLoopEntries(this.store, loopId, { prefix, feedbackOnly: true }),
214
- ]
215
- : [];
140
+ const { loopId, task, currentRound, maxRounds, selfEval, consecutiveRejections, evidenceDeltas, } = input;
141
+ const agentSuccessClaim = effectiveSuccess(selfEval);
142
+ const agentOutcome = effectiveOutcome(selfEval);
143
+ // ── 1. Load committed history once ──────────────────────────────────
144
+ // The verification gate and the contract reducer read the same flat entry
145
+ // view and the same committed window; one store read serves both. The
146
+ // advance path passes the history it already loaded for its own
147
+ // pre-commit facts.
148
+ const history = input.history ?? (this.store
149
+ ? loadCommittedHistory(this.store, loopId)
150
+ : emptyCommittedHistory(loopId));
151
+ const vaultEntries = history.entries;
152
+ // v3.9.6: the previous evaluation is the last committed branch round,
153
+ // never a session copy.
154
+ const committedRounds = historyWindow(history, currentRound);
155
+ const previousCommitted = committedRounds.length > 0
156
+ ? committedRounds[committedRounds.length - 1].evaluation
157
+ : null;
216
158
  // ── 2. Verification gate ────────────────────────────────────────────
217
- const verifyResult = verifySelfEvaluation(selfEval, currentRound, vaultEntries, lastSelfEval ?? null, evidenceDeltas ?? [], input.backtrackSkippedFiles ?? [], input.backtrackSkippedFingerprints ?? {}, input.backtrackTargetGitHead,
159
+ // v3.9.7: the old post-backtrack restore checks/R9 are gone. LoopForge
160
+ // verifies the restore before issuing the redo prompt; a redo submission
161
+ // is ordinary work, so its after boundary must never be compared to the
162
+ // restore target.
163
+ // v4.0.3: ONE RoundFacts derivation for this round. The verification
164
+ // gate, the stop decision, the success trajectory and the evidence
165
+ // posture all consume this bundle; none derives the contract again.
166
+ const commands = getPolicy().evidence.commands ?? [];
167
+ const roundFacts = deriveRoundFacts({
168
+ rounds: committedRounds,
169
+ currentRound,
170
+ inFlight: {
171
+ currentReport: selfEval.execution_report ?? null,
172
+ currentDeltas: evidenceDeltas ?? [],
173
+ currentBlocked: declaresBlocked(selfEval),
174
+ currentRedirect: selfEval.round_contract?.supersedes ?? null,
175
+ },
176
+ commands,
177
+ });
178
+ const verifyResult = verifySelfEvaluation(selfEval, currentRound, vaultEntries, previousCommitted, evidenceDeltas ?? [],
218
179
  // v3.7.1: gate records live under the gate: prefix — outside the round
219
180
  // entries above — and are passed separately to the gate check.
220
- this.store
221
- ? queryLoopEntries(this.store, loopId, { prefix: `loop:${loopId}:gate:` })
222
- : [],
181
+ history.entries.filter((entry) => String(entry.task_id ?? "").startsWith(`loop:${loopId}:gate:`)),
223
182
  // v3.8.3: the absolute arm of the entrypoint check. Derived here, not in
224
183
  // the gate, because it needs the filesystem and the in-flight trust map
225
184
  // captured when this round was prepared — the gate stays a pure function
226
185
  // of its inputs.
227
- driftedEntrypoints(input.entrypointTrust), input.beforeEvidence ?? [],
228
- // v3.9: the restore point's own dirty files — the baseline the restored
229
- // workspace is compared against.
230
- input.backtrackTargetDirty);
186
+ driftedEntrypoints(input.entrypointTrust),
187
+ // v3.9.4: the SAME manifest basis the receipt uses. An omitted
188
+ // files_changed field is not silently treated as "a report exists".
189
+ input.submittedFields
190
+ ? input.submittedFields.includes("execution_report.files_changed")
191
+ : undefined,
192
+ // v4.0.3: the one derivation, computed above. Passing it removes the
193
+ // gate's private contract/item/claim derivation.
194
+ roundFacts, maxRounds);
231
195
  const verificationFlags = verifyResult.flags;
232
196
  const gateContradicted = verifyResult.verdict === "contradicted";
233
- // v3.8: the ACTIVE contract's derived item statuses for this round — the
234
- // same reducer the gate, compile path, audit and explain consume. Drives
235
- // the stop mapping (completed vs incomplete) and the success trajectory.
236
- const committedRounds = derivationRounds(vaultEntries, currentRound);
237
- // v3.8: the shared executed-contract derivation — the same one explain and
238
- // audit call, so the live posture and the read-only views cannot diverge.
239
- const { contract: activeContract, statuses: activeContractStatuses } = deriveRoundContractView({
240
- rounds: committedRounds,
197
+ const { activeContract, itemStatuses: activeContractStatuses } = roundFacts;
198
+ // v4.0.3: the evidence posture is derived ONCE here, from the same raw
199
+ // facts every other branch reads. It is persisted with an accepted round;
200
+ // a rejected/terminated attempt carries only the transient copy.
201
+ const verificationConfigured = commands.some((command) => command.enabled === true &&
202
+ (command.phase === "after" || command.phase === "both"));
203
+ const filesReconciliation = deriveFilesReconciliation(selfEval.execution_report ?? null, evidenceDeltas ?? [], input.submittedFields
204
+ ? {
205
+ attempted: input.submittedFields.includes("execution_report.files_changed"),
206
+ }
207
+ : {});
208
+ const evaluationPosture = deriveEvidencePosture({
241
209
  round: currentRound,
242
- report: selfEval.execution_report ?? null,
243
- deltas: evidenceDeltas ?? [],
244
- outcome: effectiveOutcome(selfEval),
245
- commands: getPolicy().evidence.commands ?? [],
210
+ kind: "evaluation",
211
+ selfEval,
212
+ afterEvidence: input.afterEvidence ?? [],
213
+ evidenceDeltas: evidenceDeltas ?? [],
214
+ verificationFlags,
215
+ machineBackedSuccess: verifyResult.machineBackedSuccess,
216
+ activeContract,
217
+ contractStatuses: activeContractStatuses,
218
+ verificationConfigured,
219
+ filesReconciliation,
220
+ });
221
+ const evaluationResult = (result) => ({
222
+ ...result,
223
+ evidencePosture: evaluationPosture,
224
+ agentOutcome,
225
+ });
226
+ const rollbackResult = (result) => ({
227
+ ...result,
228
+ evidencePosture: rollbackEvidencePosture(currentRound),
229
+ agentOutcome: null,
246
230
  });
247
- // v3.8.3: the legal exit from scope drift — a round that declares itself
248
- // blocked may switch contracts, but only by declaring a proposal whose
249
- // scope covers EVERY file it changed out of scope. Computed here because
250
- // this is the only place where the round's git delta, the active
251
- // contract's scope and the submitted proposal are all in reach; the
252
- // enforcement gate receives the verdict, not the inputs.
253
- //
254
- // `selfEval.round_contract` is already the parsed proposal (buildSelfEvaluation
255
- // runs parseRoundContract), so the coverage test reads the same object the
256
- // commit path will read back.
257
- //
258
- // The coverage requirement is what keeps this from being a universal
259
- // escape: without it "declare blocked" would dissolve a machine fact
260
- // without declaring anything. The drift FLAG is untouched — it still lands
261
- // on the committed round, so audit/explain keep reporting it.
262
- const driftedFiles = roundDriftFiles(activeContract, evidenceDeltas ?? []);
263
- const proposal = selfEval.round_contract;
264
- const scopeDriftWaiver = driftedFiles !== null && driftedFiles.length > 0 &&
265
- declaresBlocked(selfEval) &&
266
- proposal !== undefined &&
267
- collectOutOfScopeFiles(driftedFiles, proposal.scope).length === 0;
268
231
  if (gateContradicted) {
269
232
  logEvent("gate_contradicted", {
270
233
  loopId,
@@ -278,11 +241,11 @@ export class RoundCoordinator {
278
241
  // transaction reaches enforcement with a fully extracted self-evaluation.
279
242
  // The old v2.5 "heuristic partial enforcement" plumbing (extractionSucceeded
280
243
  // flag, enforcement skipEvidenceRules mode) was removed as unreachable.
281
- const enforceResult = enforceRound(selfEval, verifyResult, currentRound, vaultEntries, consecutiveRejections, input.lastRejectionCheck ?? "", scopeDriftWaiver,
282
- // v3.9: what THIS round moved, from the same Git motion rule the
283
- // committed series uses. Unknown motion stays null — an unobservable
284
- // round never gains the stall excuse.
285
- inFlightGitMotion(evidenceDeltas ?? [], input.backtrackTargetGitHead));
244
+ const enforceResult = enforceRound(selfEval, verifyResult, currentRound, vaultEntries, consecutiveRejections, input.lastRejectionCheck ?? "",
245
+ // v3.9.9: what THIS round moved, under the SAME Git workspace selector
246
+ // the committed window uses. Unknown/not_applicable never gains the
247
+ // stall excuse, and no other provider's fact can change this verdict.
248
+ deriveRoundProgress(evidenceDeltas ?? []));
286
249
  if (enforceResult.action === "reject") {
287
250
  const newRejections = consecutiveRejections + 1;
288
251
  const rejectionPrompt = buildRejectionPrompt(currentRound, task, enforceResult, verificationFlags);
@@ -293,21 +256,18 @@ export class RoundCoordinator {
293
256
  check: enforceResult.check ?? "",
294
257
  consecutiveRejections: newRejections,
295
258
  });
296
- return {
259
+ return evaluationResult({
297
260
  action: "reject",
298
- verificationStatus: roundVerificationStatus(activeContractStatuses, selfEval.execution_report ?? null),
299
261
  rejectionPrompt,
300
262
  verificationFlags,
301
263
  enforcementAction: "reject",
302
264
  enforcementReason: enforceResult.reason,
303
265
  rejectionCheck: enforceResult.check,
304
- roundSuccess,
266
+ agentSuccessClaim,
305
267
  gateContradicted,
306
268
  newConsecutiveRejections: newRejections,
307
- // Don't update lastSelfEval on reject — the agent redoes the same round
308
- newLastSelfEval: undefined,
309
269
  shouldPushSuccessTrajectory: false,
310
- };
270
+ });
311
271
  }
312
272
  if (enforceResult.action === "terminate") {
313
273
  // v3.8: a row may name the stop reason it terminates with (the
@@ -319,100 +279,129 @@ export class RoundCoordinator {
319
279
  stopReason: terminateReason,
320
280
  round: currentRound,
321
281
  });
322
- return {
282
+ return evaluationResult({
323
283
  action: "terminate",
324
- verificationStatus: roundVerificationStatus(activeContractStatuses, selfEval.execution_report ?? null),
325
284
  stopReason: terminateReason,
326
285
  verificationFlags,
327
286
  enforcementAction: "terminate",
328
287
  enforcementReason: enforceResult.reason,
329
288
  rejectionCheck: enforceResult.check,
330
- roundSuccess,
289
+ agentSuccessClaim,
331
290
  gateContradicted,
332
291
  newConsecutiveRejections: 0,
333
- newLastSelfEval: selfEval,
334
292
  shouldPushSuccessTrajectory: false,
335
- };
293
+ });
336
294
  }
337
- // ── v2.10: Backtrack ──────────────────────────────────────────────────
295
+ // ── v2.10 / v3.9.7: Backtrack ─────────────────────────────────────────
338
296
  if (enforceResult.action === "backtrack") {
339
297
  const maxDepth = getPolicy().engine.backtrack_max_depth;
340
- const restorePoint = findSafeRestorePoint(currentRound, vaultEntries, maxDepth);
298
+ // The state after round T is the before-image of round T+1. A candidate
299
+ // is eligible only when that image was materialized and its manifest is
300
+ // readable; there is no verification-only fallback.
301
+ const resolveRef = (restoreRound) => {
302
+ const redoRound = restoreRound + 1;
303
+ const ref = checkpointForRoundBoundary({
304
+ entries: vaultEntries,
305
+ heldRound: currentRound,
306
+ heldBeforeCheckpoint: input.beforeCheckpoint,
307
+ redoRound,
308
+ });
309
+ if (!ref)
310
+ return null;
311
+ if (input.resolveCheckpointManifest) {
312
+ const manifest = input.resolveCheckpointManifest(ref);
313
+ // Machine target selection must prove the ref and manifest are
314
+ // self-consistent BEFORE committing an immutable directive. Blob
315
+ // and HEAD checks still run in the post-commit preflight.
316
+ if (!manifest || !validateCheckpointManifest(ref, manifest).ok) {
317
+ return null;
318
+ }
319
+ }
320
+ return ref;
321
+ };
322
+ const restorePoint = findSafeRestorePoint(currentRound, vaultEntries, maxDepth, (restoreRound) => resolveRef(restoreRound) !== null);
341
323
  if (!restorePoint) {
342
- // No clean round found within maxDepth — fall through to terminate
324
+ // No clean round with a materialized restore point within maxDepth.
325
+ logEvent("session_end", {
326
+ loopId,
327
+ stopReason: "enforcement_terminated",
328
+ round: currentRound,
329
+ });
330
+ return evaluationResult({
331
+ action: "terminate",
332
+ stopReason: "enforcement_terminated",
333
+ verificationFlags,
334
+ enforcementAction: "terminate",
335
+ enforcementReason: `${enforceResult.reason} (backtrack failed: no materialized restore point within ${maxDepth} rounds)`,
336
+ rejectionCheck: enforceResult.check,
337
+ agentSuccessClaim,
338
+ gateContradicted,
339
+ newConsecutiveRejections: 0,
340
+ shouldPushSuccessTrajectory: false,
341
+ });
342
+ }
343
+ const targetRef = resolveRef(restorePoint.round);
344
+ const targetManifest = targetRef && input.resolveCheckpointManifest
345
+ ? input.resolveCheckpointManifest(targetRef)
346
+ : null;
347
+ if (!targetRef || !targetManifest) {
343
348
  logEvent("session_end", {
344
349
  loopId,
345
350
  stopReason: "enforcement_terminated",
346
351
  round: currentRound,
347
352
  });
348
- return {
353
+ return evaluationResult({
349
354
  action: "terminate",
350
- verificationStatus: roundVerificationStatus(activeContractStatuses, selfEval.execution_report ?? null),
351
355
  stopReason: "enforcement_terminated",
352
356
  verificationFlags,
353
357
  enforcementAction: "terminate",
354
- enforcementReason: `${enforceResult.reason} (backtrack failed: no clean restore point within ${maxDepth} rounds)`,
358
+ enforcementReason: `${enforceResult.reason} (backtrack failed: restore point manifest unavailable)`,
355
359
  rejectionCheck: enforceResult.check,
356
- roundSuccess,
360
+ agentSuccessClaim,
357
361
  gateContradicted,
358
362
  newConsecutiveRejections: 0,
359
- newLastSelfEval: selfEval,
360
363
  shouldPushSuccessTrajectory: false,
361
- };
364
+ });
362
365
  }
363
- // v2.13/v3.8.1: the rolled-back branch's facts — one named derivation,
364
- // so the walk is testable without driving a whole rollback, and the
365
- // prompt, the state file and the committed decision all describe one
366
- // event.
367
- const { skippedFiles, skippedFingerprints, failedRounds, approaches, wrongAssumptions, } = deriveBacktrackRecoveryFacts({
368
- currentRound,
369
- restoreRound: restorePoint.round,
370
- vaultEntries,
371
- selfEval,
366
+ const abandonedRounds = committedRounds
367
+ .filter((view) => view.round >= targetRef.redoRound);
368
+ const recovery = deriveRecoveryDirective({
369
+ origin: "machine",
370
+ reason: enforceResult.reason,
371
+ ...(enforceResult.check ? { triggerRule: enforceResult.check } : {}),
372
+ // The transaction layer fills the directive transaction id, which
373
+ // depends on the prompt hash/branch identity.
374
+ transactionId: "",
375
+ targetRef,
376
+ targetManifest,
377
+ abandonedRounds,
378
+ currentAfterEvidence: input.afterEvidence ?? [],
379
+ triggerEvaluation: selfEval,
380
+ triggerRound: currentRound,
381
+ triggerFindings: verificationFlags,
372
382
  });
373
- // v2.12: Capture the restore point's machine snapshot — the commit the
374
- // workspace must return to and what was already dirty there — so the
375
- // next round's verification can confirm the workspace returned to this
376
- // point without reading unrelated work in progress as a failed restore.
377
- const targetSnapshot = findBacktrackTargetSnapshot(restorePoint.round, vaultEntries);
378
- const targetGitHead = targetSnapshot?.head ?? null;
379
- const backtrackPrompt = buildBacktrackPrompt(currentRound, restorePoint.round, enforceResult.check ?? "progress_stall", getPolicy().engine.backtrack_preserve_discoveries
380
- ? restorePoint.skippedDiscoveries
381
- : [], skippedFiles, targetGitHead ?? undefined, makeRoundId(loopId, restorePoint.round + 1), { failedRounds, approaches, wrongAssumptions });
382
383
  logEvent("enforcement_backtrack", {
383
384
  loopId,
384
385
  round: currentRound,
385
386
  reason: enforceResult.reason.slice(0, 120),
386
387
  check: enforceResult.check ?? "",
387
- backtrackTarget: restorePoint.round,
388
- skippedDiscoveries: restorePoint.skippedDiscoveries.length,
388
+ backtrackTarget: targetRef.redoRound,
389
+ skippedDiscoveries: recovery.skipped_discoveries.length,
389
390
  });
390
- return {
391
+ return rollbackResult({
391
392
  action: "backtrack",
392
- verificationStatus: roundVerificationStatus(activeContractStatuses, selfEval.execution_report ?? null),
393
- backtrackPrompt,
394
- backtrackTarget: restorePoint.round,
395
- backtrackSkippedDiscoveries: getPolicy().engine.backtrack_preserve_discoveries
396
- ? restorePoint.skippedDiscoveries
397
- : [],
398
- backtrackSkippedFiles: skippedFiles,
399
- backtrackSkippedFingerprints: skippedFingerprints,
400
- backtrackTargetGitHead: targetGitHead ?? undefined,
401
- backtrackTargetDirty: targetSnapshot?.dirtyFiles,
402
- backtrackTriggerRule: enforceResult.check,
403
- backtrackFailedRounds: failedRounds,
404
- backtrackApproaches: approaches,
405
- backtrackWrongAssumptions: wrongAssumptions,
393
+ origin: "machine",
394
+ reason: enforceResult.reason,
395
+ recovery,
406
396
  verificationFlags,
407
397
  enforcementAction: "backtrack",
408
398
  enforcementReason: enforceResult.reason,
409
399
  rejectionCheck: enforceResult.check,
410
- roundSuccess,
400
+ agentSuccessClaim,
411
401
  gateContradicted,
412
402
  newConsecutiveRejections: 0, // reset — fresh start
413
- newLastSelfEval: undefined,
414
403
  shouldPushSuccessTrajectory: false,
415
- };
404
+ });
416
405
  }
417
406
  // Accept: reset rejection counter
418
407
  // (consecutiveRejections reset to 0 — caller persists)
@@ -432,24 +421,24 @@ export class RoundCoordinator {
432
421
  // the declaration arm below already owns that posture.
433
422
  const contractClosed = activeContractStatuses.contractId === "" ||
434
423
  activeContractStatuses.closure === "verified";
435
- // The agent's explicit declaration of a blocked stop outranks the
436
- // success claim. It is checked FIRST so `success: true` + a declared
437
- // `outcome`/`stop_reason` of blocked can never be reported as
438
- // `completed` — the two fields contradict, and the honest reading is the
439
- // declared one. A contradicted item also never reaches `completed`
440
- // (closure is `verified` only when EVERY item is), so the
441
- // contradicted-and-stopping posture lands on `incomplete` at worst; the
442
- // reject/terminate path for it is the enforcement gate's, evaluated
443
- // before this point.
424
+ // v4.0.6: the stop mapping reads ONE outcome field.
425
+ // blocked → blocked
426
+ // success + contract closed → completed
427
+ // success + open contract → incomplete
428
+ // partial → incomplete
429
+ // failed → failed
430
+ // A contradicted item also never reaches `completed` (closure is
431
+ // `verified` only when EVERY item is); the reject/terminate path for it
432
+ // is the enforcement gate's, evaluated before this point.
444
433
  const declaredBlocked = declaresBlocked(selfEval);
445
434
  let reason;
446
435
  if (declaredBlocked) {
447
436
  reason = "blocked";
448
437
  }
449
- else if (effectiveSuccess(selfEval) && contractClosed) {
438
+ else if (agentOutcome === "success" && contractClosed) {
450
439
  reason = "completed";
451
440
  }
452
- else if (effectiveSuccess(selfEval)) {
441
+ else if (agentOutcome === "success" || agentOutcome === "partial") {
453
442
  reason = "incomplete";
454
443
  }
455
444
  else {
@@ -459,50 +448,44 @@ export class RoundCoordinator {
459
448
  // is written into the committed round document below, so it must be
460
449
  // truthful at the moment it becomes a fact, not only when it is
461
450
  // returned to the client.
462
- const finalized = finalizeStopReason(reason, selfEval, activeContractStatuses);
463
- return {
451
+ const finalized = finalizeStopReason(reason, selfEval, activeContractStatuses, selfEval.round_contract?.supersedes ?? null);
452
+ return evaluationResult({
464
453
  action: "stop",
465
- verificationStatus: roundVerificationStatus(activeContractStatuses, selfEval.execution_report ?? null),
466
454
  stopReason: finalized.reason,
467
455
  verificationFlags: finalized.flags.length > 0
468
456
  ? [...verificationFlags, ...finalized.flags]
469
457
  : verificationFlags,
470
- roundSuccess,
458
+ agentSuccessClaim,
471
459
  gateContradicted,
472
460
  newConsecutiveRejections: 0,
473
- newLastSelfEval: selfEval,
474
- shouldPushSuccessTrajectory: shouldPushSuccess(gateContradicted, verificationFlags, activeContractStatuses),
475
- };
461
+ shouldPushSuccessTrajectory: shouldPushSuccess(selfEval, gateContradicted, verificationFlags, verifyResult.machineBackedSuccess, activeContractStatuses),
462
+ });
476
463
  }
477
464
  // 5c. Max rounds reached
478
465
  //
479
466
  // Progress stalls are handled before this point by the R4 delta tier and
480
467
  // R5 flatline tier. Max rounds is an independent hard boundary.
481
468
  if (currentRound >= maxRounds) {
482
- return {
469
+ return evaluationResult({
483
470
  action: "stop",
484
- verificationStatus: roundVerificationStatus(activeContractStatuses, selfEval.execution_report ?? null),
485
471
  stopReason: "max_rounds",
486
472
  verificationFlags,
487
- roundSuccess,
473
+ agentSuccessClaim,
488
474
  gateContradicted,
489
475
  newConsecutiveRejections: 0,
490
- newLastSelfEval: selfEval,
491
- shouldPushSuccessTrajectory: shouldPushSuccess(gateContradicted, verificationFlags, activeContractStatuses),
492
- };
476
+ shouldPushSuccessTrajectory: shouldPushSuccess(selfEval, gateContradicted, verificationFlags, verifyResult.machineBackedSuccess, activeContractStatuses),
477
+ });
493
478
  }
494
479
  // ── 6. Continue — caller compiles next round ────────────────────────
495
- return {
480
+ return evaluationResult({
496
481
  action: "continue",
497
- verificationStatus: roundVerificationStatus(activeContractStatuses, selfEval.execution_report ?? null),
498
482
  verificationFlags,
499
483
  enforcementAction: "accept",
500
- roundSuccess,
484
+ agentSuccessClaim,
501
485
  gateContradicted,
502
486
  newConsecutiveRejections: 0,
503
- newLastSelfEval: selfEval,
504
- shouldPushSuccessTrajectory: shouldPushSuccess(gateContradicted, verificationFlags, activeContractStatuses),
505
- };
487
+ shouldPushSuccessTrajectory: shouldPushSuccess(selfEval, gateContradicted, verificationFlags, verifyResult.machineBackedSuccess, activeContractStatuses),
488
+ });
506
489
  }
507
490
  }
508
491
  //# sourceMappingURL=round-coordinator.js.map