loopforge 3.9.4 → 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 (283) 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 +114 -40
  7. package/dist/canonical-state.d.ts.map +1 -1
  8. package/dist/canonical-state.js +276 -182
  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 +97 -156
  18. package/dist/committed-round.d.ts.map +1 -1
  19. package/dist/committed-round.js +223 -419
  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 +26 -38
  42. package/dist/enforcement-gate.d.ts.map +1 -1
  43. package/dist/enforcement-gate.js +196 -319
  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 +27 -897
  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 +166 -27
  70. package/dist/explain.d.ts.map +1 -1
  71. package/dist/explain.js +372 -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 +39 -23
  103. package/dist/loop-compiler.d.ts.map +1 -1
  104. package/dist/loop-compiler.js +368 -565
  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 +255 -81
  111. package/dist/mcp/round-lifecycle.d.ts.map +1 -1
  112. package/dist/mcp/round-lifecycle.js +1552 -539
  113. package/dist/mcp/round-lifecycle.js.map +1 -1
  114. package/dist/mcp/server.d.ts.map +1 -1
  115. package/dist/mcp/server.js +42 -18
  116. package/dist/mcp/server.js.map +1 -1
  117. package/dist/mcp/session.d.ts +81 -39
  118. package/dist/mcp/session.d.ts.map +1 -1
  119. package/dist/mcp/session.js +956 -282
  120. package/dist/mcp/session.js.map +1 -1
  121. package/dist/mcp/submission-boundary.d.ts +23 -31
  122. package/dist/mcp/submission-boundary.d.ts.map +1 -1
  123. package/dist/mcp/submission-boundary.js +242 -92
  124. package/dist/mcp/submission-boundary.js.map +1 -1
  125. package/dist/mcp/submission-handle.d.ts +66 -0
  126. package/dist/mcp/submission-handle.d.ts.map +1 -0
  127. package/dist/mcp/submission-handle.js +173 -0
  128. package/dist/mcp/submission-handle.js.map +1 -0
  129. package/dist/mcp/tools.d.ts +17 -7
  130. package/dist/mcp/tools.d.ts.map +1 -1
  131. package/dist/mcp/tools.js +707 -244
  132. package/dist/mcp/tools.js.map +1 -1
  133. package/dist/open-question.d.ts +7 -0
  134. package/dist/open-question.d.ts.map +1 -0
  135. package/dist/open-question.js +14 -0
  136. package/dist/open-question.js.map +1 -0
  137. package/dist/policy-metrics.d.ts +31 -21
  138. package/dist/policy-metrics.d.ts.map +1 -1
  139. package/dist/policy-metrics.js +117 -64
  140. package/dist/policy-metrics.js.map +1 -1
  141. package/dist/policy.d.ts +11 -39
  142. package/dist/policy.d.ts.map +1 -1
  143. package/dist/policy.js +12 -12
  144. package/dist/policy.js.map +1 -1
  145. package/dist/progress.d.ts +45 -0
  146. package/dist/progress.d.ts.map +1 -0
  147. package/dist/progress.js +136 -0
  148. package/dist/progress.js.map +1 -0
  149. package/dist/prompt-assembler.d.ts +20 -6
  150. package/dist/prompt-assembler.d.ts.map +1 -1
  151. package/dist/prompt-assembler.js +475 -479
  152. package/dist/prompt-assembler.js.map +1 -1
  153. package/dist/prompt-coverage.d.ts +0 -6
  154. package/dist/prompt-coverage.d.ts.map +1 -1
  155. package/dist/prompt-coverage.js +4 -8
  156. package/dist/prompt-coverage.js.map +1 -1
  157. package/dist/prompt-freshness.d.ts +6 -17
  158. package/dist/prompt-freshness.d.ts.map +1 -1
  159. package/dist/prompt-freshness.js +27 -22
  160. package/dist/prompt-freshness.js.map +1 -1
  161. package/dist/prompt-policy.d.ts +14 -7
  162. package/dist/prompt-policy.d.ts.map +1 -1
  163. package/dist/prompt-policy.js +21 -5
  164. package/dist/prompt-policy.js.map +1 -1
  165. package/dist/protocol.d.ts +843 -424
  166. package/dist/protocol.d.ts.map +1 -1
  167. package/dist/protocol.js +73 -102
  168. package/dist/protocol.js.map +1 -1
  169. package/dist/recovery-guidance.d.ts +13 -0
  170. package/dist/recovery-guidance.d.ts.map +1 -0
  171. package/dist/recovery-guidance.js +15 -0
  172. package/dist/recovery-guidance.js.map +1 -0
  173. package/dist/recovery.d.ts +68 -0
  174. package/dist/recovery.d.ts.map +1 -0
  175. package/dist/recovery.js +234 -0
  176. package/dist/recovery.js.map +1 -0
  177. package/dist/replay.d.ts +42 -8
  178. package/dist/replay.d.ts.map +1 -1
  179. package/dist/replay.js +311 -84
  180. package/dist/replay.js.map +1 -1
  181. package/dist/result-facts.d.ts +45 -0
  182. package/dist/result-facts.d.ts.map +1 -0
  183. package/dist/result-facts.js +86 -0
  184. package/dist/result-facts.js.map +1 -0
  185. package/dist/round-contract.d.ts +38 -8
  186. package/dist/round-contract.d.ts.map +1 -1
  187. package/dist/round-contract.js +71 -31
  188. package/dist/round-contract.js.map +1 -1
  189. package/dist/round-coordinator.d.ts +41 -89
  190. package/dist/round-coordinator.d.ts.map +1 -1
  191. package/dist/round-coordinator.js +250 -267
  192. package/dist/round-coordinator.js.map +1 -1
  193. package/dist/round-driver.d.ts +56 -16
  194. package/dist/round-driver.d.ts.map +1 -1
  195. package/dist/round-driver.js +225 -39
  196. package/dist/round-driver.js.map +1 -1
  197. package/dist/round-facts.d.ts +33 -52
  198. package/dist/round-facts.d.ts.map +1 -1
  199. package/dist/round-facts.js +51 -160
  200. package/dist/round-facts.js.map +1 -1
  201. package/dist/round-guidance.d.ts +56 -0
  202. package/dist/round-guidance.d.ts.map +1 -0
  203. package/dist/round-guidance.js +161 -0
  204. package/dist/round-guidance.js.map +1 -0
  205. package/dist/round-outcome.d.ts +21 -0
  206. package/dist/round-outcome.d.ts.map +1 -0
  207. package/dist/round-outcome.js +31 -0
  208. package/dist/round-outcome.js.map +1 -0
  209. package/dist/round-transaction.d.ts +168 -21
  210. package/dist/round-transaction.d.ts.map +1 -1
  211. package/dist/round-transaction.js +837 -38
  212. package/dist/round-transaction.js.map +1 -1
  213. package/dist/self-eval.d.ts +37 -61
  214. package/dist/self-eval.d.ts.map +1 -1
  215. package/dist/self-eval.js +413 -392
  216. package/dist/self-eval.js.map +1 -1
  217. package/dist/storage.d.ts +16 -0
  218. package/dist/storage.d.ts.map +1 -1
  219. package/dist/storage.js +54 -4
  220. package/dist/storage.js.map +1 -1
  221. package/dist/subgoal-state.d.ts +2 -3
  222. package/dist/subgoal-state.d.ts.map +1 -1
  223. package/dist/subgoal-state.js +2 -3
  224. package/dist/subgoal-state.js.map +1 -1
  225. package/dist/submission-guidance.d.ts +29 -21
  226. package/dist/submission-guidance.d.ts.map +1 -1
  227. package/dist/submission-guidance.js +308 -260
  228. package/dist/submission-guidance.js.map +1 -1
  229. package/dist/submission-receipt.d.ts +16 -8
  230. package/dist/submission-receipt.d.ts.map +1 -1
  231. package/dist/submission-receipt.js +145 -51
  232. package/dist/submission-receipt.js.map +1 -1
  233. package/dist/token-utils.d.ts +28 -7
  234. package/dist/token-utils.d.ts.map +1 -1
  235. package/dist/token-utils.js +27 -11
  236. package/dist/token-utils.js.map +1 -1
  237. package/dist/transaction-identity.d.ts +9 -0
  238. package/dist/transaction-identity.d.ts.map +1 -0
  239. package/dist/transaction-identity.js +69 -0
  240. package/dist/transaction-identity.js.map +1 -0
  241. package/dist/verification-actions.d.ts.map +1 -1
  242. package/dist/verification-actions.js +10 -11
  243. package/dist/verification-actions.js.map +1 -1
  244. package/dist/verification-gate.d.ts +23 -47
  245. package/dist/verification-gate.d.ts.map +1 -1
  246. package/dist/verification-gate.js +202 -423
  247. package/dist/verification-gate.js.map +1 -1
  248. package/dist/version.d.ts +1 -1
  249. package/dist/version.js +1 -1
  250. package/dist/working-memory.d.ts +187 -0
  251. package/dist/working-memory.d.ts.map +1 -0
  252. package/dist/working-memory.js +767 -0
  253. package/dist/working-memory.js.map +1 -0
  254. package/dist/workspace-checkpoint.d.ts +88 -0
  255. package/dist/workspace-checkpoint.d.ts.map +1 -0
  256. package/dist/workspace-checkpoint.js +326 -0
  257. package/dist/workspace-checkpoint.js.map +1 -0
  258. package/dist/workspace-restore.d.ts +68 -0
  259. package/dist/workspace-restore.d.ts.map +1 -0
  260. package/dist/workspace-restore.js +604 -0
  261. package/dist/workspace-restore.js.map +1 -0
  262. package/dist/workspace.d.ts +0 -9
  263. package/dist/workspace.d.ts.map +1 -1
  264. package/dist/workspace.js +0 -17
  265. package/dist/workspace.js.map +1 -1
  266. package/loop_policy.json +4 -18
  267. package/package.json +68 -68
  268. package/skills/forge/SKILL.md +212 -0
  269. package/skills/forge/references/evidence-and-contracts.md +204 -0
  270. package/skills/forge/references/recovery-and-views.md +188 -0
  271. package/dist/cognitive-facts.d.ts +0 -30
  272. package/dist/cognitive-facts.d.ts.map +0 -1
  273. package/dist/cognitive-facts.js +0 -113
  274. package/dist/cognitive-facts.js.map +0 -1
  275. package/dist/loop-extras-parser.d.ts +0 -81
  276. package/dist/loop-extras-parser.d.ts.map +0 -1
  277. package/dist/loop-extras-parser.js +0 -213
  278. package/dist/loop-extras-parser.js.map +0 -1
  279. package/dist/loop-projection.d.ts +0 -6
  280. package/dist/loop-projection.d.ts.map +0 -1
  281. package/dist/loop-projection.js +0 -28
  282. package/dist/loop-projection.js.map +0 -1
  283. package/skills/perception/SKILL.md +0 -389
@@ -17,26 +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 { enforceRound, buildRejectionPrompt, findSafeRestorePoint, 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";
27
27
  import { logEvent } from "./observability.js";
28
28
  import { getPolicy } from "./policy.js";
29
- import { deriveRoundContractView } from "./round-contract.js";
30
- import { roundVerificationStatus, } from "./contract-items.js";
31
- /** v3.2/v3.6: Whether a round's success enters the success trajectory.
32
- * Excluded when the gate contradicted the round, or when the success claim
33
- * carries no machine-verified observation — the merged R8 check fires a
34
- * warn under machine_backed_success "warn" (v3.6: success_unverified merged
35
- * into R8; its warn-level trajectory exclusion moved with it) — an
36
- * accepted-but-unverified round is not evidence of progress. */
37
- 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;
38
46
  if (gateContradicted)
39
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;
40
51
  // v3.8: an unverified contract item (or a claimed-but-unbacked success)
41
52
  // keeps the round out of the success trajectory — an accepted-but-unverified
42
53
  // round is not evidence of progress.
@@ -68,120 +79,48 @@ function shouldPushSuccess(gateContradicted, verificationFlags, contractStatuses
68
79
  export function finalizeStopReason(
69
80
  /** `undefined` when the disposition named no terminal reason at all (the
70
81
  * callers fall back to their own default) — there is nothing to guard. */
71
- 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) {
72
86
  if (reason !== "completed")
73
87
  return { reason, flags: [] };
74
88
  const declared = declaresBlocked(selfEval);
75
89
  const blockedContract = contractStatuses?.closure === "blocked";
76
- if (!declared && !blockedContract)
77
- return { reason, flags: [] };
78
- return {
79
- reason: "blocked",
80
- flags: [{
81
- severity: "warn",
82
- field: declared ? "outcome" : "round_contract",
83
- check: "completion_truth_downgraded",
84
- detail: declared
85
- ? "The round declared a blocked outcome but the stop decision produced " +
86
- "`completed`; the declaration wins and the stop reason was downgraded " +
87
- "to `blocked`."
88
- : "The active contract closed on a blocked outcome but the stop decision " +
89
- "produced `completed`; the stop reason was downgraded to `blocked`.",
90
- }],
91
- };
92
- }
93
- // ── RoundCoordinator ───────────────────────────────────────────────────────
94
- /** v3.8.1: The rolled-back branch's facts — the ONE derivation of which rounds
95
- * failed, which approaches must not be repeated, which assumptions were
96
- * falsified, and which files (with their failed-round git fingerprints) the
97
- * agent must restore.
98
- *
99
- * Sources are COMMITTED rounds above the restore point; rejected attempts are
100
- * not durable history and never become a source. The in-flight attempt that
101
- * triggered the rollback is the one exception — its evaluation lives in this
102
- * process, not in committed history.
103
- *
104
- * Extracted from the coordinator's inline walk so it is unit-testable without
105
- * driving a whole rollback, and so the prompt, the state file and the
106
- * committed decision all describe one event. */
107
- export function deriveBacktrackRecoveryFacts(input) {
108
- const { currentRound, restoreRound, vaultEntries, selfEval } = input;
109
- // v2.13: files changed in skipped rounds, for the restore prompt.
110
- const skippedFiles = [];
111
- // M3 (v3.7.x): each skipped file's git fingerprint at its failed round.
112
- // Recorded from the round's own after-evidence (machine data, not the
113
- // agent's report); later rounds overwrite earlier ones so the map holds the
114
- // LAST failed state of every file.
115
- const skippedFingerprints = {};
116
- const failedRounds = [];
117
- const approaches = [];
118
- const wrongAssumptions = [];
119
- for (let r = restoreRound + 1; r < currentRound; r++) {
120
- // v3.2.1: match the feedback entry (execution_report lives there) — the
121
- // compile-time lineage entry for the same round precedes it in the flat
122
- // view and carries no evidence.
123
- const entry = vaultEntries.find((e) => entryRound(e) === r && e.task_type !== "loop_lineage");
124
- const view = entry ? decodeCommittedRound(entry) : null;
125
- const evaluation = view?.evaluation ?? null;
126
- if (entry?.execution_report) {
127
- const ev = entry.execution_report;
128
- const reported = Array.isArray(ev.files_changed)
129
- ? ev.files_changed.filter((f) => typeof f === "string")
130
- : [];
131
- // v3.9.1: the machine delta is the primary set of files the failed
132
- // round changed; the agent report is unioned in so an ignored file the
133
- // agent named is not lost from the recovery brief.
134
- const machineFiles = (view?.observationDelta ?? [])
135
- .filter((delta) => delta.kind === "git" && delta.changedFiles !== null)
136
- .flatMap((delta) => delta.changedFiles);
137
- const files = [...new Set([...machineFiles, ...reported])];
138
- const git = view?.afterEvidence.find((snapshot) => snapshot.kind === "git" &&
139
- snapshot.status === "observed" &&
140
- isRecord(snapshot.data.fingerprints));
141
- const gitFingerprints = git && git.kind === "git"
142
- ? git.data.fingerprints
143
- : null;
144
- for (const f of files) {
145
- if (!skippedFiles.includes(f))
146
- skippedFiles.push(f);
147
- const fp = gitFingerprints?.[f];
148
- if (typeof fp === "string")
149
- skippedFingerprints[f] = fp;
150
- }
151
- }
152
- const summary = evaluation?.output_summary ?? "";
153
- if (summary.trim().length > 0) {
154
- failedRounds.push(r);
155
- approaches.push(summary.trim().slice(0, 200));
156
- }
157
- const assumptions = (evaluation?.wrong_assumptions ?? [])
158
- .filter((v) => typeof v === "string")
159
- .map((s) => s.slice(0, 500));
160
- for (const a of assumptions) {
161
- if (!wrongAssumptions.includes(a))
162
- wrongAssumptions.push(a);
163
- }
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
+ };
164
105
  }
165
- // The in-flight attempt that triggered this rollback.
166
- const triggerSummary = selfEval.output_summary?.trim() ?? "";
167
- if (triggerSummary.length > 0) {
168
- if (!failedRounds.includes(currentRound))
169
- failedRounds.push(currentRound);
170
- approaches.push(triggerSummary.slice(0, 200));
171
- for (const a of (selfEval.wrong_assumptions ?? [])
172
- .filter((v) => typeof v === "string")) {
173
- if (!wrongAssumptions.includes(a))
174
- wrongAssumptions.push(a.slice(0, 500));
175
- }
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
+ };
176
120
  }
177
- return {
178
- skippedFiles,
179
- skippedFingerprints,
180
- failedRounds,
181
- approaches,
182
- wrongAssumptions,
183
- };
121
+ return { reason, flags: [] };
184
122
  }
123
+ // ── RoundCoordinator ───────────────────────────────────────────────────────
185
124
  export class RoundCoordinator {
186
125
  store;
187
126
  constructor(store) {
@@ -198,77 +137,97 @@ export class RoundCoordinator {
198
137
  * - Transactional feedback commit (accepted rounds only)
199
138
  * - State file I/O (both paths, after compiling) */
200
139
  processRound(input) {
201
- const { loopId, task, currentRound, maxRounds, selfEval, lastSelfEval, consecutiveRejections, evidenceDeltas, } = input;
202
- const roundSuccess = selfEval.success ?? false;
203
- // ── 1. Query vault entries ──────────────────────────────────────────
204
- // Lineage entries (constraint violations, output summaries) and feedback
205
- // entries (including execution_report) are both needed by
206
- // the enforcement gate. queryLoopEntries excludes feedback by default,
207
- // so we issue a second query and merge both result sets.
208
- const prefix = `loop:${loopId}:r`;
209
- const vaultEntries = this.store
210
- ? [
211
- ...queryLoopEntries(this.store, loopId, { prefix }),
212
- ...queryLoopEntries(this.store, loopId, { prefix, feedbackOnly: true }),
213
- ]
214
- : [];
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;
215
158
  // ── 2. Verification gate ────────────────────────────────────────────
216
- 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 ?? [],
217
179
  // v3.7.1: gate records live under the gate: prefix — outside the round
218
180
  // entries above — and are passed separately to the gate check.
219
- this.store
220
- ? queryLoopEntries(this.store, loopId, { prefix: `loop:${loopId}:gate:` })
221
- : [],
181
+ history.entries.filter((entry) => String(entry.task_id ?? "").startsWith(`loop:${loopId}:gate:`)),
222
182
  // v3.8.3: the absolute arm of the entrypoint check. Derived here, not in
223
183
  // the gate, because it needs the filesystem and the in-flight trust map
224
184
  // captured when this round was prepared — the gate stays a pure function
225
185
  // of its inputs.
226
- driftedEntrypoints(input.entrypointTrust), input.beforeEvidence ?? [],
227
- // v3.9: the restore point's own dirty files — the baseline the restored
228
- // workspace is compared against.
229
- input.backtrackTargetDirty,
186
+ driftedEntrypoints(input.entrypointTrust),
230
187
  // v3.9.4: the SAME manifest basis the receipt uses. An omitted
231
188
  // files_changed field is not silently treated as "a report exists".
232
189
  input.submittedFields
233
190
  ? input.submittedFields.includes("execution_report.files_changed")
234
- : undefined);
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);
235
195
  const verificationFlags = verifyResult.flags;
236
196
  const gateContradicted = verifyResult.verdict === "contradicted";
237
- // v3.8: the ACTIVE contract's derived item statuses for this round — the
238
- // same reducer the gate, compile path, audit and explain consume. Drives
239
- // the stop mapping (completed vs incomplete) and the success trajectory.
240
- const committedRounds = derivationRounds(vaultEntries, currentRound);
241
- // v3.8: the shared executed-contract derivation — the same one explain and
242
- // audit call, so the live posture and the read-only views cannot diverge.
243
- const { contract: activeContract, statuses: activeContractStatuses } = deriveRoundContractView({
244
- 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({
245
209
  round: currentRound,
246
- report: selfEval.execution_report ?? null,
247
- deltas: evidenceDeltas ?? [],
248
- outcome: effectiveOutcome(selfEval),
249
- 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,
250
230
  });
251
- // v3.8.3: the legal exit from scope drift — a round that declares itself
252
- // blocked may switch contracts, but only by declaring a proposal whose
253
- // scope covers EVERY file it changed out of scope. Computed here because
254
- // this is the only place where the round's git delta, the active
255
- // contract's scope and the submitted proposal are all in reach; the
256
- // enforcement gate receives the verdict, not the inputs.
257
- //
258
- // `selfEval.round_contract` is already the parsed proposal (buildSelfEvaluation
259
- // runs parseRoundContract), so the coverage test reads the same object the
260
- // commit path will read back.
261
- //
262
- // The coverage requirement is what keeps this from being a universal
263
- // escape: without it "declare blocked" would dissolve a machine fact
264
- // without declaring anything. The drift FLAG is untouched — it still lands
265
- // on the committed round, so audit/explain keep reporting it.
266
- const driftedFiles = roundDriftFiles(activeContract, evidenceDeltas ?? []);
267
- const proposal = selfEval.round_contract;
268
- const scopeDriftWaiver = driftedFiles !== null && driftedFiles.length > 0 &&
269
- declaresBlocked(selfEval) &&
270
- proposal !== undefined &&
271
- collectOutOfScopeFiles(driftedFiles, proposal.scope).length === 0;
272
231
  if (gateContradicted) {
273
232
  logEvent("gate_contradicted", {
274
233
  loopId,
@@ -282,11 +241,11 @@ export class RoundCoordinator {
282
241
  // transaction reaches enforcement with a fully extracted self-evaluation.
283
242
  // The old v2.5 "heuristic partial enforcement" plumbing (extractionSucceeded
284
243
  // flag, enforcement skipEvidenceRules mode) was removed as unreachable.
285
- const enforceResult = enforceRound(selfEval, verifyResult, currentRound, vaultEntries, consecutiveRejections, input.lastRejectionCheck ?? "", scopeDriftWaiver,
286
- // v3.9: what THIS round moved, from the same Git motion rule the
287
- // committed series uses. Unknown motion stays null — an unobservable
288
- // round never gains the stall excuse.
289
- 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 ?? []));
290
249
  if (enforceResult.action === "reject") {
291
250
  const newRejections = consecutiveRejections + 1;
292
251
  const rejectionPrompt = buildRejectionPrompt(currentRound, task, enforceResult, verificationFlags);
@@ -297,21 +256,18 @@ export class RoundCoordinator {
297
256
  check: enforceResult.check ?? "",
298
257
  consecutiveRejections: newRejections,
299
258
  });
300
- return {
259
+ return evaluationResult({
301
260
  action: "reject",
302
- verificationStatus: roundVerificationStatus(activeContractStatuses, selfEval.execution_report ?? null),
303
261
  rejectionPrompt,
304
262
  verificationFlags,
305
263
  enforcementAction: "reject",
306
264
  enforcementReason: enforceResult.reason,
307
265
  rejectionCheck: enforceResult.check,
308
- roundSuccess,
266
+ agentSuccessClaim,
309
267
  gateContradicted,
310
268
  newConsecutiveRejections: newRejections,
311
- // Don't update lastSelfEval on reject — the agent redoes the same round
312
- newLastSelfEval: undefined,
313
269
  shouldPushSuccessTrajectory: false,
314
- };
270
+ });
315
271
  }
316
272
  if (enforceResult.action === "terminate") {
317
273
  // v3.8: a row may name the stop reason it terminates with (the
@@ -323,96 +279,129 @@ export class RoundCoordinator {
323
279
  stopReason: terminateReason,
324
280
  round: currentRound,
325
281
  });
326
- return {
282
+ return evaluationResult({
327
283
  action: "terminate",
328
- verificationStatus: roundVerificationStatus(activeContractStatuses, selfEval.execution_report ?? null),
329
284
  stopReason: terminateReason,
330
285
  verificationFlags,
331
286
  enforcementAction: "terminate",
332
287
  enforcementReason: enforceResult.reason,
333
288
  rejectionCheck: enforceResult.check,
334
- roundSuccess,
289
+ agentSuccessClaim,
335
290
  gateContradicted,
336
291
  newConsecutiveRejections: 0,
337
- newLastSelfEval: selfEval,
338
292
  shouldPushSuccessTrajectory: false,
339
- };
293
+ });
340
294
  }
341
- // ── v2.10: Backtrack ──────────────────────────────────────────────────
295
+ // ── v2.10 / v3.9.7: Backtrack ─────────────────────────────────────────
342
296
  if (enforceResult.action === "backtrack") {
343
297
  const maxDepth = getPolicy().engine.backtrack_max_depth;
344
- 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);
345
323
  if (!restorePoint) {
346
- // 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) {
347
348
  logEvent("session_end", {
348
349
  loopId,
349
350
  stopReason: "enforcement_terminated",
350
351
  round: currentRound,
351
352
  });
352
- return {
353
+ return evaluationResult({
353
354
  action: "terminate",
354
- verificationStatus: roundVerificationStatus(activeContractStatuses, selfEval.execution_report ?? null),
355
355
  stopReason: "enforcement_terminated",
356
356
  verificationFlags,
357
357
  enforcementAction: "terminate",
358
- enforcementReason: `${enforceResult.reason} (backtrack failed: no clean restore point within ${maxDepth} rounds)`,
358
+ enforcementReason: `${enforceResult.reason} (backtrack failed: restore point manifest unavailable)`,
359
359
  rejectionCheck: enforceResult.check,
360
- roundSuccess,
360
+ agentSuccessClaim,
361
361
  gateContradicted,
362
362
  newConsecutiveRejections: 0,
363
- newLastSelfEval: selfEval,
364
363
  shouldPushSuccessTrajectory: false,
365
- };
364
+ });
366
365
  }
367
- // v2.13/v3.8.1: the rolled-back branch's facts — one named derivation,
368
- // so the walk is testable without driving a whole rollback, and the
369
- // prompt, the state file and the committed decision all describe one
370
- // event.
371
- const { skippedFiles, skippedFingerprints, failedRounds, approaches, wrongAssumptions, } = deriveBacktrackRecoveryFacts({
372
- currentRound,
373
- restoreRound: restorePoint.round,
374
- vaultEntries,
375
- 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,
376
382
  });
377
- // v2.12: Capture the restore point's machine snapshot — the commit the
378
- // workspace must return to and what was already dirty there — so the
379
- // next round's verification can confirm the workspace returned to this
380
- // point without reading unrelated work in progress as a failed restore.
381
- const targetSnapshot = findBacktrackTargetSnapshot(restorePoint.round, vaultEntries);
382
- const targetGitHead = targetSnapshot?.head ?? null;
383
383
  logEvent("enforcement_backtrack", {
384
384
  loopId,
385
385
  round: currentRound,
386
386
  reason: enforceResult.reason.slice(0, 120),
387
387
  check: enforceResult.check ?? "",
388
- backtrackTarget: restorePoint.round,
389
- skippedDiscoveries: restorePoint.skippedDiscoveries.length,
388
+ backtrackTarget: targetRef.redoRound,
389
+ skippedDiscoveries: recovery.skipped_discoveries.length,
390
390
  });
391
- return {
391
+ return rollbackResult({
392
392
  action: "backtrack",
393
- verificationStatus: roundVerificationStatus(activeContractStatuses, selfEval.execution_report ?? null),
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