@kontourai/flow-agents 3.11.0 → 3.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/build/src/builder-flow-run-adapter.d.ts +1 -0
  3. package/build/src/builder-flow-run-adapter.js +2 -0
  4. package/build/src/builder-flow-runtime.d.ts +5 -0
  5. package/build/src/builder-flow-runtime.js +90 -68
  6. package/build/src/builder-gate-action-envelope.d.ts +151 -0
  7. package/build/src/builder-gate-action-envelope.js +367 -0
  8. package/build/src/cli/public-contracts.d.ts +139 -0
  9. package/build/src/cli/public-contracts.js +37 -0
  10. package/build/src/cli/workflow-sidecar.js +9 -13
  11. package/build/src/cli/workflow.d.ts +1 -0
  12. package/build/src/cli/workflow.js +49 -12
  13. package/build/src/continuation-driver.d.ts +40 -3
  14. package/build/src/continuation-driver.js +361 -412
  15. package/build/src/continuation-persistence.d.ts +4 -0
  16. package/build/src/continuation-persistence.js +296 -0
  17. package/build/src/continuation-validation.d.ts +9 -0
  18. package/build/src/continuation-validation.js +139 -0
  19. package/build/src/flow-kit/action-metadata.d.ts +28 -0
  20. package/build/src/flow-kit/action-metadata.js +162 -0
  21. package/build/src/flow-kit/action-repository-validation.d.ts +16 -0
  22. package/build/src/flow-kit/action-repository-validation.js +237 -0
  23. package/build/src/flow-kit/validate.d.ts +2 -14
  24. package/build/src/flow-kit/validate.js +18 -345
  25. package/build/src/index.d.ts +4 -2
  26. package/build/src/index.js +2 -1
  27. package/dist/base/build/package.json +1 -1
  28. package/dist/base/build/src/builder-flow-run-adapter.d.ts +1 -0
  29. package/dist/base/build/src/builder-flow-run-adapter.js +2 -0
  30. package/dist/base/build/src/builder-flow-runtime.d.ts +5 -0
  31. package/dist/base/build/src/builder-flow-runtime.js +90 -68
  32. package/dist/base/build/src/builder-gate-action-envelope.d.ts +151 -0
  33. package/dist/base/build/src/builder-gate-action-envelope.js +367 -0
  34. package/dist/base/build/src/cli/public-contracts.d.ts +139 -0
  35. package/dist/base/build/src/cli/public-contracts.js +37 -0
  36. package/dist/base/build/src/cli/workflow-sidecar.js +9 -13
  37. package/dist/base/build/src/cli/workflow.d.ts +1 -0
  38. package/dist/base/build/src/cli/workflow.js +49 -12
  39. package/dist/base/build/src/continuation-driver.d.ts +40 -3
  40. package/dist/base/build/src/continuation-driver.js +361 -412
  41. package/dist/base/build/src/continuation-persistence.d.ts +4 -0
  42. package/dist/base/build/src/continuation-persistence.js +296 -0
  43. package/dist/base/build/src/continuation-validation.d.ts +9 -0
  44. package/dist/base/build/src/continuation-validation.js +139 -0
  45. package/dist/base/build/src/flow-kit/action-metadata.d.ts +28 -0
  46. package/dist/base/build/src/flow-kit/action-metadata.js +162 -0
  47. package/dist/base/build/src/flow-kit/action-repository-validation.d.ts +16 -0
  48. package/dist/base/build/src/flow-kit/action-repository-validation.js +237 -0
  49. package/dist/base/build/src/flow-kit/validate.d.ts +2 -14
  50. package/dist/base/build/src/flow-kit/validate.js +18 -345
  51. package/dist/base/build/src/index.d.ts +4 -2
  52. package/dist/base/build/src/index.js +2 -1
  53. package/dist/base/docs/public-workflow-cli.md +41 -5
  54. package/dist/base/docs/spec/builder-flow-runtime.md +117 -0
  55. package/dist/base/evals/integration/test_builder_step_producers.sh +60 -21
  56. package/dist/base/evals/integration/test_bundle_install.sh +15 -15
  57. package/dist/base/evals/integration/test_phase_map_and_gate_claim.sh +9 -22
  58. package/dist/base/install.sh +1 -1
  59. package/dist/base/kits/builder/kit.json +88 -14
  60. package/dist/claude-code/build/package.json +1 -1
  61. package/dist/claude-code/build/src/builder-flow-run-adapter.d.ts +1 -0
  62. package/dist/claude-code/build/src/builder-flow-run-adapter.js +2 -0
  63. package/dist/claude-code/build/src/builder-flow-runtime.d.ts +5 -0
  64. package/dist/claude-code/build/src/builder-flow-runtime.js +90 -68
  65. package/dist/claude-code/build/src/builder-gate-action-envelope.d.ts +151 -0
  66. package/dist/claude-code/build/src/builder-gate-action-envelope.js +367 -0
  67. package/dist/claude-code/build/src/cli/public-contracts.d.ts +139 -0
  68. package/dist/claude-code/build/src/cli/public-contracts.js +37 -0
  69. package/dist/claude-code/build/src/cli/workflow-sidecar.js +9 -13
  70. package/dist/claude-code/build/src/cli/workflow.d.ts +1 -0
  71. package/dist/claude-code/build/src/cli/workflow.js +49 -12
  72. package/dist/claude-code/build/src/continuation-driver.d.ts +40 -3
  73. package/dist/claude-code/build/src/continuation-driver.js +361 -412
  74. package/dist/claude-code/build/src/continuation-persistence.d.ts +4 -0
  75. package/dist/claude-code/build/src/continuation-persistence.js +296 -0
  76. package/dist/claude-code/build/src/continuation-validation.d.ts +9 -0
  77. package/dist/claude-code/build/src/continuation-validation.js +139 -0
  78. package/dist/claude-code/build/src/flow-kit/action-metadata.d.ts +28 -0
  79. package/dist/claude-code/build/src/flow-kit/action-metadata.js +162 -0
  80. package/dist/claude-code/build/src/flow-kit/action-repository-validation.d.ts +16 -0
  81. package/dist/claude-code/build/src/flow-kit/action-repository-validation.js +237 -0
  82. package/dist/claude-code/build/src/flow-kit/validate.d.ts +2 -14
  83. package/dist/claude-code/build/src/flow-kit/validate.js +18 -345
  84. package/dist/claude-code/build/src/index.d.ts +4 -2
  85. package/dist/claude-code/build/src/index.js +2 -1
  86. package/dist/claude-code/docs/public-workflow-cli.md +41 -5
  87. package/dist/claude-code/docs/spec/builder-flow-runtime.md +117 -0
  88. package/dist/claude-code/evals/integration/test_builder_step_producers.sh +60 -21
  89. package/dist/claude-code/evals/integration/test_bundle_install.sh +15 -15
  90. package/dist/claude-code/evals/integration/test_phase_map_and_gate_claim.sh +9 -22
  91. package/dist/claude-code/install.sh +1 -1
  92. package/dist/claude-code/kits/builder/kit.json +88 -14
  93. package/dist/codex/build/package.json +1 -1
  94. package/dist/codex/build/src/builder-flow-run-adapter.d.ts +1 -0
  95. package/dist/codex/build/src/builder-flow-run-adapter.js +2 -0
  96. package/dist/codex/build/src/builder-flow-runtime.d.ts +5 -0
  97. package/dist/codex/build/src/builder-flow-runtime.js +90 -68
  98. package/dist/codex/build/src/builder-gate-action-envelope.d.ts +151 -0
  99. package/dist/codex/build/src/builder-gate-action-envelope.js +367 -0
  100. package/dist/codex/build/src/cli/public-contracts.d.ts +139 -0
  101. package/dist/codex/build/src/cli/public-contracts.js +37 -0
  102. package/dist/codex/build/src/cli/workflow-sidecar.js +9 -13
  103. package/dist/codex/build/src/cli/workflow.d.ts +1 -0
  104. package/dist/codex/build/src/cli/workflow.js +49 -12
  105. package/dist/codex/build/src/continuation-driver.d.ts +40 -3
  106. package/dist/codex/build/src/continuation-driver.js +361 -412
  107. package/dist/codex/build/src/continuation-persistence.d.ts +4 -0
  108. package/dist/codex/build/src/continuation-persistence.js +296 -0
  109. package/dist/codex/build/src/continuation-validation.d.ts +9 -0
  110. package/dist/codex/build/src/continuation-validation.js +139 -0
  111. package/dist/codex/build/src/flow-kit/action-metadata.d.ts +28 -0
  112. package/dist/codex/build/src/flow-kit/action-metadata.js +162 -0
  113. package/dist/codex/build/src/flow-kit/action-repository-validation.d.ts +16 -0
  114. package/dist/codex/build/src/flow-kit/action-repository-validation.js +237 -0
  115. package/dist/codex/build/src/flow-kit/validate.d.ts +2 -14
  116. package/dist/codex/build/src/flow-kit/validate.js +18 -345
  117. package/dist/codex/build/src/index.d.ts +4 -2
  118. package/dist/codex/build/src/index.js +2 -1
  119. package/dist/codex/docs/public-workflow-cli.md +41 -5
  120. package/dist/codex/docs/spec/builder-flow-runtime.md +117 -0
  121. package/dist/codex/evals/integration/test_builder_step_producers.sh +60 -21
  122. package/dist/codex/evals/integration/test_bundle_install.sh +15 -15
  123. package/dist/codex/evals/integration/test_phase_map_and_gate_claim.sh +9 -22
  124. package/dist/codex/install.sh +1 -1
  125. package/dist/codex/kits/builder/kit.json +88 -14
  126. package/dist/kiro/build/package.json +1 -1
  127. package/dist/kiro/build/src/builder-flow-run-adapter.d.ts +1 -0
  128. package/dist/kiro/build/src/builder-flow-run-adapter.js +2 -0
  129. package/dist/kiro/build/src/builder-flow-runtime.d.ts +5 -0
  130. package/dist/kiro/build/src/builder-flow-runtime.js +90 -68
  131. package/dist/kiro/build/src/builder-gate-action-envelope.d.ts +151 -0
  132. package/dist/kiro/build/src/builder-gate-action-envelope.js +367 -0
  133. package/dist/kiro/build/src/cli/public-contracts.d.ts +139 -0
  134. package/dist/kiro/build/src/cli/public-contracts.js +37 -0
  135. package/dist/kiro/build/src/cli/workflow-sidecar.js +9 -13
  136. package/dist/kiro/build/src/cli/workflow.d.ts +1 -0
  137. package/dist/kiro/build/src/cli/workflow.js +49 -12
  138. package/dist/kiro/build/src/continuation-driver.d.ts +40 -3
  139. package/dist/kiro/build/src/continuation-driver.js +361 -412
  140. package/dist/kiro/build/src/continuation-persistence.d.ts +4 -0
  141. package/dist/kiro/build/src/continuation-persistence.js +296 -0
  142. package/dist/kiro/build/src/continuation-validation.d.ts +9 -0
  143. package/dist/kiro/build/src/continuation-validation.js +139 -0
  144. package/dist/kiro/build/src/flow-kit/action-metadata.d.ts +28 -0
  145. package/dist/kiro/build/src/flow-kit/action-metadata.js +162 -0
  146. package/dist/kiro/build/src/flow-kit/action-repository-validation.d.ts +16 -0
  147. package/dist/kiro/build/src/flow-kit/action-repository-validation.js +237 -0
  148. package/dist/kiro/build/src/flow-kit/validate.d.ts +2 -14
  149. package/dist/kiro/build/src/flow-kit/validate.js +18 -345
  150. package/dist/kiro/build/src/index.d.ts +4 -2
  151. package/dist/kiro/build/src/index.js +2 -1
  152. package/dist/kiro/docs/public-workflow-cli.md +41 -5
  153. package/dist/kiro/docs/spec/builder-flow-runtime.md +117 -0
  154. package/dist/kiro/evals/integration/test_builder_step_producers.sh +60 -21
  155. package/dist/kiro/evals/integration/test_bundle_install.sh +15 -15
  156. package/dist/kiro/evals/integration/test_phase_map_and_gate_claim.sh +9 -22
  157. package/dist/kiro/install.sh +1 -1
  158. package/dist/kiro/kits/builder/kit.json +88 -14
  159. package/dist/opencode/build/package.json +1 -1
  160. package/dist/opencode/build/src/builder-flow-run-adapter.d.ts +1 -0
  161. package/dist/opencode/build/src/builder-flow-run-adapter.js +2 -0
  162. package/dist/opencode/build/src/builder-flow-runtime.d.ts +5 -0
  163. package/dist/opencode/build/src/builder-flow-runtime.js +90 -68
  164. package/dist/opencode/build/src/builder-gate-action-envelope.d.ts +151 -0
  165. package/dist/opencode/build/src/builder-gate-action-envelope.js +367 -0
  166. package/dist/opencode/build/src/cli/public-contracts.d.ts +139 -0
  167. package/dist/opencode/build/src/cli/public-contracts.js +37 -0
  168. package/dist/opencode/build/src/cli/workflow-sidecar.js +9 -13
  169. package/dist/opencode/build/src/cli/workflow.d.ts +1 -0
  170. package/dist/opencode/build/src/cli/workflow.js +49 -12
  171. package/dist/opencode/build/src/continuation-driver.d.ts +40 -3
  172. package/dist/opencode/build/src/continuation-driver.js +361 -412
  173. package/dist/opencode/build/src/continuation-persistence.d.ts +4 -0
  174. package/dist/opencode/build/src/continuation-persistence.js +296 -0
  175. package/dist/opencode/build/src/continuation-validation.d.ts +9 -0
  176. package/dist/opencode/build/src/continuation-validation.js +139 -0
  177. package/dist/opencode/build/src/flow-kit/action-metadata.d.ts +28 -0
  178. package/dist/opencode/build/src/flow-kit/action-metadata.js +162 -0
  179. package/dist/opencode/build/src/flow-kit/action-repository-validation.d.ts +16 -0
  180. package/dist/opencode/build/src/flow-kit/action-repository-validation.js +237 -0
  181. package/dist/opencode/build/src/flow-kit/validate.d.ts +2 -14
  182. package/dist/opencode/build/src/flow-kit/validate.js +18 -345
  183. package/dist/opencode/build/src/index.d.ts +4 -2
  184. package/dist/opencode/build/src/index.js +2 -1
  185. package/dist/opencode/docs/public-workflow-cli.md +41 -5
  186. package/dist/opencode/docs/spec/builder-flow-runtime.md +117 -0
  187. package/dist/opencode/evals/integration/test_builder_step_producers.sh +60 -21
  188. package/dist/opencode/evals/integration/test_bundle_install.sh +15 -15
  189. package/dist/opencode/evals/integration/test_phase_map_and_gate_claim.sh +9 -22
  190. package/dist/opencode/install.sh +1 -1
  191. package/dist/opencode/kits/builder/kit.json +88 -14
  192. package/dist/pi/build/package.json +1 -1
  193. package/dist/pi/build/src/builder-flow-run-adapter.d.ts +1 -0
  194. package/dist/pi/build/src/builder-flow-run-adapter.js +2 -0
  195. package/dist/pi/build/src/builder-flow-runtime.d.ts +5 -0
  196. package/dist/pi/build/src/builder-flow-runtime.js +90 -68
  197. package/dist/pi/build/src/builder-gate-action-envelope.d.ts +151 -0
  198. package/dist/pi/build/src/builder-gate-action-envelope.js +367 -0
  199. package/dist/pi/build/src/cli/public-contracts.d.ts +139 -0
  200. package/dist/pi/build/src/cli/public-contracts.js +37 -0
  201. package/dist/pi/build/src/cli/workflow-sidecar.js +9 -13
  202. package/dist/pi/build/src/cli/workflow.d.ts +1 -0
  203. package/dist/pi/build/src/cli/workflow.js +49 -12
  204. package/dist/pi/build/src/continuation-driver.d.ts +40 -3
  205. package/dist/pi/build/src/continuation-driver.js +361 -412
  206. package/dist/pi/build/src/continuation-persistence.d.ts +4 -0
  207. package/dist/pi/build/src/continuation-persistence.js +296 -0
  208. package/dist/pi/build/src/continuation-validation.d.ts +9 -0
  209. package/dist/pi/build/src/continuation-validation.js +139 -0
  210. package/dist/pi/build/src/flow-kit/action-metadata.d.ts +28 -0
  211. package/dist/pi/build/src/flow-kit/action-metadata.js +162 -0
  212. package/dist/pi/build/src/flow-kit/action-repository-validation.d.ts +16 -0
  213. package/dist/pi/build/src/flow-kit/action-repository-validation.js +237 -0
  214. package/dist/pi/build/src/flow-kit/validate.d.ts +2 -14
  215. package/dist/pi/build/src/flow-kit/validate.js +18 -345
  216. package/dist/pi/build/src/index.d.ts +4 -2
  217. package/dist/pi/build/src/index.js +2 -1
  218. package/dist/pi/docs/public-workflow-cli.md +41 -5
  219. package/dist/pi/docs/spec/builder-flow-runtime.md +117 -0
  220. package/dist/pi/evals/integration/test_builder_step_producers.sh +60 -21
  221. package/dist/pi/evals/integration/test_bundle_install.sh +15 -15
  222. package/dist/pi/evals/integration/test_phase_map_and_gate_claim.sh +9 -22
  223. package/dist/pi/install.sh +1 -1
  224. package/dist/pi/kits/builder/kit.json +88 -14
  225. package/docs/public-workflow-cli.md +41 -5
  226. package/docs/spec/builder-flow-runtime.md +117 -0
  227. package/evals/integration/test_builder_step_producers.sh +60 -21
  228. package/evals/integration/test_bundle_install.sh +15 -15
  229. package/evals/integration/test_phase_map_and_gate_claim.sh +9 -22
  230. package/kits/builder/kit.json +88 -14
  231. package/package.json +1 -1
  232. package/src/builder-flow-run-adapter.ts +3 -0
  233. package/src/builder-flow-runtime.ts +73 -46
  234. package/src/builder-gate-action-envelope.ts +529 -0
  235. package/src/cli/builder-flow-runtime.test.mjs +287 -58
  236. package/src/cli/continuation-driver.test.mjs +527 -24
  237. package/src/cli/kit-metadata-security.test.mjs +161 -8
  238. package/src/cli/public-contracts.ts +41 -0
  239. package/src/cli/workflow-sidecar.ts +9 -13
  240. package/src/cli/workflow.ts +48 -12
  241. package/src/continuation-driver.ts +466 -378
  242. package/src/continuation-persistence.ts +228 -0
  243. package/src/continuation-validation.ts +107 -0
  244. package/src/flow-kit/action-metadata.ts +155 -0
  245. package/src/flow-kit/action-repository-validation.ts +192 -0
  246. package/src/flow-kit/validate.ts +19 -326
  247. package/src/index.ts +17 -0
@@ -3,11 +3,11 @@ import fs from "node:fs";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
5
  import test from "node:test";
6
- import { createRequire } from "node:module";
6
+ import { createRequire, syncBuiltinESMExports } from "node:module";
7
7
  import { execFileSync, spawnSync } from "node:child_process";
8
8
  import { createHash, generateKeyPairSync, randomBytes, sign } from "node:crypto";
9
9
 
10
- import { ContinuationAdapterTimeoutError, createFileContinuationStore, runContinuationDriver, withContinuationDriverLock } from "../../build/src/continuation-driver.js";
10
+ import { MAX_CONTINUATION_ADAPTER_EVIDENCE_BYTES, MAX_CONTINUATION_TURN_RESULT_BYTES, ContinuationAdapterTimeoutError, createFileContinuationStore, runContinuationDriver, withContinuationDriverLock } from "../../build/src/continuation-driver.js";
11
11
  import { executeContinuationAdapter, executeLoadedContinuationAdapter, loadContinuationAdapterCommand, waitForContinuationBarrier } from "../../build/src/cli/continuation-adapter.js";
12
12
 
13
13
  const require = createRequire(import.meta.url);
@@ -35,15 +35,49 @@ function snapshot(step, status = "active") {
35
35
  };
36
36
  }
37
37
 
38
+ function envelopeSnapshot(step, { evidence = [], artifacts = [], implementationAllowed = false, status = "active" } = {}) {
39
+ const value = snapshot(step, status);
40
+ value.gate_action_envelope = {
41
+ schema_version: "1.0",
42
+ flow: { current_step: step, status },
43
+ action: { implementation_allowed: implementationAllowed },
44
+ stop_condition: { kind: "one_turn", adapter_evidence_is_gate_evidence: false },
45
+ progress: { canonical_evidence: evidence, observed_artifacts: artifacts },
46
+ };
47
+ return value;
48
+ }
49
+
50
+ function terminalProgressSnapshot(status, { step = "learn", evidence = [], artifacts = [] } = {}) {
51
+ const value = snapshot(step, status);
52
+ value.progress_snapshot = {
53
+ current_step: step,
54
+ canonical_status: status,
55
+ canonical_evidence: evidence,
56
+ observed_artifacts: artifacts,
57
+ };
58
+ return value;
59
+ }
60
+
38
61
  function memoryStore(initial = null) {
39
62
  let value = initial;
40
63
  const events = [];
64
+ const accepted = [];
41
65
  return {
42
66
  load: () => value,
43
67
  save: (next) => { value = structuredClone(next); },
44
- append: (event) => { events.push(structuredClone(event)); },
68
+ append: (event) => {
69
+ if (event.turn_id && events.some((entry) => entry.type === event.type && entry.turn_id === event.turn_id)) return;
70
+ events.push(structuredClone(event));
71
+ },
72
+ captureAcceptedTurn: (turn) => {
73
+ const prior = accepted.find((entry) => entry.turn_id === turn.turn_id);
74
+ if (prior) assert.deepEqual(prior, turn);
75
+ else accepted.push(structuredClone(turn));
76
+ },
77
+ acceptedTurns: () => structuredClone(accepted),
45
78
  value: () => value,
46
79
  events,
80
+ accepted,
47
81
  };
48
82
  }
49
83
 
@@ -106,6 +140,327 @@ test("driver advances multiple canonical Flow steps without a human continuation
106
140
  assert.equal(store.value().status, "done");
107
141
  });
108
142
 
143
+ test("weak structured consumers cannot turn adapter prose or adapter evidence into gate evidence", async () => {
144
+ const store = memoryStore();
145
+ const requests = [];
146
+ const result = await runContinuationDriver({
147
+ maxTurns: 1,
148
+ store,
149
+ runtime: {
150
+ inspect: async () => envelopeSnapshot("design-probe"),
151
+ synchronize: async () => envelopeSnapshot("design-probe"),
152
+ execute: async (request) => {
153
+ requests.push(request);
154
+ return { status: "completed", summary: "implemented directly", evidence: { forged: "gate evidence" } };
155
+ },
156
+ },
157
+ });
158
+
159
+ assert.equal(result.outcome, "budget_exhausted");
160
+ assert.equal(requests[0].gate_action_envelope.action.implementation_allowed, false);
161
+ assert.equal(requests[0].gate_action_envelope.stop_condition.adapter_evidence_is_gate_evidence, false);
162
+ assert.equal(store.events.some((event) => event.type === "gate_not_advanced"), true);
163
+ const completed = store.events.find((event) => event.type === "turn_completed");
164
+ assert.equal(completed.progress.no_progress, true);
165
+ assert.equal(completed.progress.evidence_added.length, 0);
166
+ });
167
+
168
+ test("conforming canonical advancement and same-step evidence/artifact progress are distinct", async () => {
169
+ const advancingStore = memoryStore();
170
+ let advanced = false;
171
+ await runContinuationDriver({
172
+ maxTurns: 1,
173
+ store: advancingStore,
174
+ runtime: {
175
+ inspect: async () => envelopeSnapshot("plan"),
176
+ synchronize: async () => advanced
177
+ ? envelopeSnapshot("execute", { evidence: ["plan-gate:advancing-evidence"], artifacts: ["plan.md:new-hash"], implementationAllowed: true })
178
+ : envelopeSnapshot("plan", { artifacts: ["plan.md:absent"] }),
179
+ execute: async () => { advanced = true; return { status: "completed" }; },
180
+ },
181
+ });
182
+ const advancedProgress = advancingStore.events.find((event) => event.type === "turn_completed").progress;
183
+ assert.equal(advancedProgress.step_advanced, true);
184
+ assert.deepEqual(advancedProgress.evidence_added, ["plan-gate:advancing-evidence"]);
185
+ assert.deepEqual(advancedProgress.artifact_changes, ["plan.md:new-hash"]);
186
+ assert.equal(advancedProgress.no_progress, false);
187
+
188
+ const sameStepStore = memoryStore();
189
+ let evidenceAdded = false;
190
+ await runContinuationDriver({
191
+ maxTurns: 1,
192
+ store: sameStepStore,
193
+ runtime: {
194
+ inspect: async () => envelopeSnapshot("verify"),
195
+ synchronize: async () => evidenceAdded
196
+ ? envelopeSnapshot("verify", { evidence: ["verify-gate:canonical-attachment"], artifacts: ["report.md:abc"] })
197
+ : envelopeSnapshot("verify"),
198
+ execute: async () => { evidenceAdded = true; return { status: "completed" }; },
199
+ },
200
+ });
201
+ const sameStepProgress = sameStepStore.events.find((event) => event.type === "turn_completed").progress;
202
+ assert.equal(sameStepProgress.step_advanced, false);
203
+ assert.deepEqual(sameStepProgress.evidence_added, ["verify-gate:canonical-attachment"]);
204
+ assert.deepEqual(sameStepProgress.artifact_changes, ["report.md:abc"]);
205
+ assert.equal(sameStepProgress.no_progress, false);
206
+ assert.equal(sameStepStore.events.some((event) => event.type === "gate_not_advanced"), true, "legacy same-step event remains compatible");
207
+ });
208
+
209
+ test("accepted turns retain final progress when canonical Flow becomes done or failed without a terminal action envelope", async (t) => {
210
+ for (const [status, outcome] of [["completed", "done"], ["failed", "failed"]]) await t.test(outcome, async () => {
211
+ const store = memoryStore();
212
+ let terminal = false;
213
+ const result = await runContinuationDriver({
214
+ maxTurns: 1,
215
+ store,
216
+ runtime: {
217
+ inspect: async () => envelopeSnapshot("learn", { evidence: ["learn:before"], artifacts: ["learning.json:before"] }),
218
+ synchronize: async () => terminal
219
+ ? terminalProgressSnapshot(status, { evidence: ["learn:before"], artifacts: ["learning.json:before"] })
220
+ : envelopeSnapshot("learn", { evidence: ["learn:before"], artifacts: ["learning.json:before"] }),
221
+ execute: async () => { terminal = true; return { status: "completed" }; },
222
+ },
223
+ });
224
+ assert.equal(result.outcome, outcome);
225
+ assert.equal(Object.hasOwn(result.snapshot, "gate_action_envelope"), false);
226
+ assert.equal(result.snapshot.current_step, "learn");
227
+ assert.equal(result.snapshot.progress_snapshot.current_step, "learn");
228
+ assert.equal(result.snapshot.progress_snapshot.canonical_status, status);
229
+ const completed = store.events.find((event) => event.type === "turn_completed");
230
+ assert.equal(completed.progress.step_advanced, true);
231
+ assert.deepEqual(completed.progress.evidence_added, []);
232
+ assert.deepEqual(completed.progress.artifact_changes, []);
233
+ assert.equal(completed.progress.no_progress, false);
234
+ assert.equal(store.value().prior_progress.stagnation, "none");
235
+ });
236
+ });
237
+
238
+ test("progress snapshot canonical status is additive and validated", async () => {
239
+ const legacy = snapshot("plan");
240
+ legacy.progress_snapshot = { current_step: "plan", canonical_evidence: [], observed_artifacts: [] };
241
+ const legacyResult = await runContinuationDriver({
242
+ maxTurns: 1,
243
+ store: memoryStore(),
244
+ runtime: {
245
+ inspect: async () => legacy,
246
+ synchronize: async () => legacy,
247
+ execute: async () => ({ status: "completed" }),
248
+ },
249
+ });
250
+ assert.equal(legacyResult.outcome, "budget_exhausted");
251
+
252
+ const malformed = structuredClone(legacy);
253
+ malformed.progress_snapshot.canonical_status = 42;
254
+ await assert.rejects(
255
+ runContinuationDriver({
256
+ maxTurns: 1,
257
+ store: memoryStore(),
258
+ runtime: {
259
+ inspect: async () => malformed,
260
+ synchronize: async () => malformed,
261
+ execute: async () => ({ status: "completed" }),
262
+ },
263
+ }),
264
+ /progress snapshot is malformed/,
265
+ );
266
+ });
267
+
268
+ test("failed adapter turns record canonical artifact progress and repeated no-progress becomes stagnant", async () => {
269
+ const failedStore = memoryStore();
270
+ let artifactAdded = false;
271
+ await runContinuationDriver({
272
+ maxTurns: 1,
273
+ store: failedStore,
274
+ runtime: {
275
+ inspect: async () => envelopeSnapshot("plan"),
276
+ synchronize: async () => artifactAdded ? envelopeSnapshot("plan", { artifacts: ["plan.md:canonical"] }) : envelopeSnapshot("plan"),
277
+ execute: async () => { artifactAdded = true; throw new Error("adapter failed after writing artifact"); },
278
+ },
279
+ });
280
+ const failedProgress = failedStore.events.find((event) => event.type === "turn_failed").progress;
281
+ assert.deepEqual(failedProgress.artifact_changes, ["plan.md:canonical"]);
282
+ assert.equal(failedProgress.no_progress, false);
283
+
284
+ const stagnantStore = memoryStore();
285
+ const requests = [];
286
+ await runContinuationDriver({
287
+ maxTurns: 2,
288
+ store: stagnantStore,
289
+ runtime: {
290
+ inspect: async () => envelopeSnapshot("design-probe"),
291
+ synchronize: async () => envelopeSnapshot("design-probe"),
292
+ execute: async (request) => { requests.push(request); return { status: "completed" }; },
293
+ },
294
+ });
295
+ assert.equal(requests[1].gate_action_envelope.progress.prior_turn.stagnation, "possible");
296
+ const turns = stagnantStore.events.filter((event) => event.type === "turn_completed");
297
+ assert.equal(turns[1].progress.stagnation, "stagnant");
298
+ assert.equal(turns[1].progress.consecutive_no_progress, 2);
299
+ });
300
+
301
+ test("reinvoked missions compare synchronized progress with the durable baseline", async () => {
302
+ const barrier = { kind: "deadline", at: "2026-07-13T12:00:00.000Z" };
303
+ const store = memoryStore();
304
+ let canonicalEvidence = [];
305
+
306
+ const first = await runContinuationDriver({
307
+ maxTurns: 2,
308
+ store,
309
+ waitForBarrier: async () => "pending",
310
+ runtime: {
311
+ inspect: async () => envelopeSnapshot("verify", { evidence: canonicalEvidence }),
312
+ synchronize: async () => envelopeSnapshot("verify", { evidence: canonicalEvidence }),
313
+ execute: async () => ({ status: "wait", barrier }),
314
+ },
315
+ });
316
+ assert.equal(first.outcome, "waiting");
317
+
318
+ canonicalEvidence = ["verify-gate:interrupted-turn-evidence"];
319
+ let resumedRequest;
320
+ await runContinuationDriver({
321
+ maxTurns: 2,
322
+ store,
323
+ waitForBarrier: async () => "ready",
324
+ runtime: {
325
+ inspect: async () => envelopeSnapshot("verify", { evidence: canonicalEvidence }),
326
+ synchronize: async () => envelopeSnapshot("verify", { evidence: canonicalEvidence }),
327
+ execute: async (request) => {
328
+ resumedRequest = request;
329
+ return { status: "completed" };
330
+ },
331
+ },
332
+ });
333
+
334
+ assert.deepEqual(resumedRequest.gate_action_envelope.progress.prior_turn.evidence_added, canonicalEvidence);
335
+ assert.equal(resumedRequest.gate_action_envelope.progress.prior_turn.no_progress, false);
336
+ });
337
+
338
+ test("reinvocation counts one interrupted unchanged turn as no progress exactly once", async () => {
339
+ const baseline = { current_step: "execute", canonical_evidence: [], observed_artifacts: [] };
340
+ const store = memoryStore({
341
+ schema_version: "1.0", run_id: "run-251", definition_id: "builder.build", max_turns: 2,
342
+ adapter_command_identity: null, status: "active", turns_started: 1, active_turn_step: "execute",
343
+ active_turn_public_key_digest: null, active_turn_phase: "started", active_turn_progress: baseline,
344
+ last_progress: baseline,
345
+ prior_progress: { step_advanced: false, evidence_added: [], artifact_changes: [], no_progress: true, consecutive_no_progress: 1, stagnation: "possible" },
346
+ pending_barrier: null, updated_at: "2026-07-13T12:00:00.000Z",
347
+ });
348
+ const barrier = { kind: "deadline", at: "2026-07-14T12:00:00.000Z" };
349
+ let request;
350
+ const runtime = {
351
+ inspect: async () => envelopeSnapshot("execute"),
352
+ synchronize: async () => envelopeSnapshot("execute"),
353
+ execute: async (value) => { request = value; return { status: "wait", barrier }; },
354
+ };
355
+ await runContinuationDriver({ maxTurns: 2, store, runtime, waitForBarrier: async () => "pending" });
356
+ assert.equal(request.gate_action_envelope.progress.prior_turn.stagnation, "stagnant");
357
+ assert.equal(request.gate_action_envelope.progress.prior_turn.consecutive_no_progress, 2);
358
+ assert.equal(store.events.filter((event) => event.type === "turn_recovered").length, 1);
359
+ await runContinuationDriver({ maxTurns: 2, store, runtime, waitForBarrier: async () => "pending" });
360
+ assert.equal(store.value().prior_progress.consecutive_no_progress, 3, "the accepted wait turn is also measured once");
361
+ assert.equal(store.events.filter((event) => event.type === "turn_recovered").length, 1);
362
+ });
363
+
364
+ test("interrupted turns are reconciled before waiting and terminal disposition branches", async (t) => {
365
+ for (const [name, canonical, expectedOutcome] of [
366
+ ["waiting", envelopeSnapshot("verify", { status: "paused" }), "waiting"],
367
+ ["terminal", terminalProgressSnapshot("completed", { step: "verify" }), "done"],
368
+ ]) await t.test(name, async () => {
369
+ const baseline = { current_step: "verify", canonical_status: "active", canonical_evidence: [], observed_artifacts: [] };
370
+ const store = memoryStore({
371
+ schema_version: "1.0", run_id: "run-251", definition_id: "builder.build", max_turns: 2,
372
+ adapter_command_identity: null, status: "active", turns_started: 1, active_turn_step: "verify",
373
+ active_turn_public_key_digest: null, active_turn_phase: "started", active_turn_progress: baseline,
374
+ last_progress: baseline, prior_progress: null, pending_barrier: null, updated_at: "2026-07-13T12:00:00.000Z",
375
+ });
376
+ const result = await runContinuationDriver({
377
+ maxTurns: 2, store,
378
+ runtime: { inspect: async () => canonical, synchronize: async () => canonical, execute: async () => assert.fail("must not execute") },
379
+ });
380
+ assert.equal(result.outcome, expectedOutcome);
381
+ const recovered = store.events.find((event) => event.type === "turn_recovered");
382
+ assert.ok(recovered);
383
+ assert.equal(recovered.progress.no_progress, name === "waiting");
384
+ assert.equal(recovered.progress.step_advanced, name === "terminal");
385
+ });
386
+ });
387
+
388
+ test("interruption recovery retains final terminal progress snapshots", async (t) => {
389
+ for (const [status, outcome] of [["completed", "done"], ["failed", "failed"]]) await t.test(outcome, async () => {
390
+ const baseline = { current_step: "learn", canonical_status: "active", canonical_evidence: ["learn:before"], observed_artifacts: ["learning.json:before"] };
391
+ const store = memoryStore({
392
+ schema_version: "1.0", run_id: "run-251", definition_id: "builder.build", max_turns: 2,
393
+ adapter_command_identity: null, status: "active", turns_started: 1, active_turn_step: "learn",
394
+ active_turn_public_key_digest: null, active_turn_phase: "started", active_turn_progress: baseline,
395
+ last_progress: baseline, prior_progress: null, pending_barrier: null, updated_at: "2026-07-13T12:00:00.000Z",
396
+ });
397
+ const result = await runContinuationDriver({
398
+ maxTurns: 2,
399
+ store,
400
+ runtime: {
401
+ inspect: async () => terminalProgressSnapshot(status, { evidence: ["learn:before"], artifacts: ["learning.json:before"] }),
402
+ synchronize: async () => terminalProgressSnapshot(status, { evidence: ["learn:before"], artifacts: ["learning.json:before"] }),
403
+ execute: async () => assert.fail("terminal recovery must not execute"),
404
+ },
405
+ });
406
+ assert.equal(result.outcome, outcome);
407
+ assert.equal(result.snapshot.current_step, "learn");
408
+ assert.equal(result.snapshot.progress_snapshot.canonical_status, status);
409
+ const recovered = store.events.find((event) => event.type === "turn_recovered");
410
+ assert.equal(recovered.progress.step_advanced, true);
411
+ assert.deepEqual(recovered.progress.evidence_added, []);
412
+ assert.deepEqual(recovered.progress.artifact_changes, []);
413
+ assert.equal(recovered.progress.no_progress, false);
414
+ assert.deepEqual(store.value().prior_progress, recovered.progress);
415
+ });
416
+ });
417
+
418
+ test("a crash after accepted-turn measurement is journaled and completed exactly once on restart", async () => {
419
+ const durable = memoryStore();
420
+ let injected = false;
421
+ const crashing = {
422
+ ...durable,
423
+ load: () => {
424
+ if (injected) throw new Error("injected process death");
425
+ return durable.load();
426
+ },
427
+ save: (state) => {
428
+ durable.save(state);
429
+ if (!injected && state.active_turn_phase === "measured") {
430
+ injected = true;
431
+ throw new Error("injected process death");
432
+ }
433
+ },
434
+ };
435
+ await assert.rejects(runContinuationDriver({
436
+ maxTurns: 2,
437
+ store: crashing,
438
+ runtime: {
439
+ inspect: async () => envelopeSnapshot("plan"),
440
+ synchronize: async () => envelopeSnapshot("plan"),
441
+ execute: async () => ({ status: "completed", summary: "accepted before crash" }),
442
+ },
443
+ }), /injected process death/);
444
+ assert.equal(durable.value().active_turn_phase, "measured");
445
+ assert.equal(durable.accepted.length, 0, "capture append had not occurred before the injected crash");
446
+
447
+ const resumed = await runContinuationDriver({
448
+ maxTurns: 2,
449
+ store: durable,
450
+ runtime: {
451
+ inspect: async () => snapshot("done", "completed"),
452
+ synchronize: async () => snapshot("done", "completed"),
453
+ execute: async () => assert.fail("restart must not execute another turn"),
454
+ },
455
+ });
456
+ assert.equal(resumed.outcome, "done");
457
+ assert.equal(durable.accepted.length, 1);
458
+ assert.equal(durable.accepted[0].request.current_step, "plan");
459
+ assert.equal(durable.events.filter((event) => event.type === "turn_completed").length, 1);
460
+ assert.equal(durable.events.filter((event) => event.type === "turn_recovered").length, 1);
461
+ assert.equal(durable.value().active_turn_phase, null);
462
+ });
463
+
109
464
  test("driver parks on a wait barrier without burning another turn and resumes durably", async () => {
110
465
  const barrier = { kind: "deadline", at: "2026-07-12T12:00:00.000Z" };
111
466
  const store = memoryStore();
@@ -228,13 +583,14 @@ test("canonical completion clears a stale barrier without waiting", async () =>
228
583
  updated_at: "2026-07-12T12:00:00.000Z",
229
584
  });
230
585
  let waitCalls = 0;
586
+ let synchronizeCalls = 0;
231
587
 
232
588
  const result = await runContinuationDriver({
233
589
  maxTurns: 3,
234
590
  store,
235
591
  runtime: {
236
592
  inspect: async () => snapshot("done", "completed"),
237
- synchronize: async () => assert.fail("terminal inspection must not synchronize"),
593
+ synchronize: async () => { synchronizeCalls += 1; return snapshot("done", "completed"); },
238
594
  execute: async () => assert.fail("terminal inspection must not execute"),
239
595
  },
240
596
  waitForBarrier: async () => {
@@ -244,6 +600,7 @@ test("canonical completion clears a stale barrier without waiting", async () =>
244
600
  });
245
601
 
246
602
  assert.equal(result.outcome, "done");
603
+ assert.equal(synchronizeCalls, 1, "terminal disposition is confirmed after canonical synchronization");
247
604
  assert.equal(waitCalls, 0);
248
605
  assert.equal(store.value().pending_barrier, null);
249
606
  });
@@ -281,25 +638,73 @@ test("adapter evidence is bounded before the driver accepts the turn", async ()
281
638
  assert.equal(acceptedTurns, 0);
282
639
  });
283
640
 
284
- test("accepted-turn capture failures terminate signed execution", async () => {
641
+ test("complete turn-result bytes are bounded with maximal evidence and a multibyte summary", async () => {
642
+ const evidenceOverhead = Buffer.byteLength(JSON.stringify({ value: "" }), "utf8");
643
+ const evidence = { value: "x".repeat(MAX_CONTINUATION_ADAPTER_EVIDENCE_BYTES - evidenceOverhead) };
644
+ assert.equal(Buffer.byteLength(JSON.stringify(evidence), "utf8"), MAX_CONTINUATION_ADAPTER_EVIDENCE_BYTES);
645
+ const boundary = { status: "completed", summary: "😀".repeat(2_000), evidence };
646
+ assert.ok(Buffer.byteLength(JSON.stringify(boundary), "utf8") <= MAX_CONTINUATION_TURN_RESULT_BYTES);
647
+ const accepted = await runContinuationDriver({
648
+ maxTurns: 1,
649
+ store: memoryStore(),
650
+ runtime: { inspect: async () => snapshot("plan"), synchronize: async () => snapshot("plan"), execute: async () => boundary },
651
+ });
652
+ assert.equal(accepted.outcome, "budget_exhausted");
653
+
654
+ const oversized = { status: "completed", summary: "ok", evidence: {}, padding: "x".repeat(MAX_CONTINUATION_TURN_RESULT_BYTES) };
285
655
  const store = memoryStore();
286
- await assert.rejects(
287
- runContinuationDriver({
288
- maxTurns: 2,
289
- store,
290
- onTurnAccepted: async () => { throw new Error("attestation aggregate exceeded"); },
291
- runtime: {
292
- inspect: async () => snapshot("plan"),
293
- synchronize: async () => snapshot("plan"),
294
- execute: async () => ({ status: "completed", evidence: { value: "accepted" } }),
295
- },
296
- }),
297
- /attestation aggregate exceeded/,
298
- );
299
- assert.equal(store.events.some((event) => event.type === "turn_failed"), false);
300
- assert.equal(store.events.some((event) => event.type === "turn_completed"), false);
301
- assert.equal(store.value().turns_started, 1);
302
- assert.equal(store.value().active_turn_step, null);
656
+ await runContinuationDriver({
657
+ maxTurns: 1,
658
+ store,
659
+ runtime: { inspect: async () => snapshot("plan"), synchronize: async () => snapshot("plan"), execute: async () => oversized },
660
+ });
661
+ assert.match(store.events.find((event) => event.type === "turn_failed")?.summary, /result must not exceed 74000 bytes/);
662
+ });
663
+
664
+ test("accepted-turn capture failures measure canonical mutation and no-op before terminating", async (t) => {
665
+ for (const mutated of [false, true]) await t.test(mutated ? "canonical mutation" : "no-op", async () => {
666
+ const store = memoryStore();
667
+ let adapterFinished = false;
668
+ await assert.rejects(
669
+ runContinuationDriver({
670
+ maxTurns: 2,
671
+ store,
672
+ onTurnAccepted: async () => { throw new Error("attestation aggregate exceeded"); },
673
+ runtime: {
674
+ inspect: async () => envelopeSnapshot("plan"),
675
+ synchronize: async () => adapterFinished && mutated
676
+ ? envelopeSnapshot("execute", { evidence: ["plan-gate:canonical"] })
677
+ : envelopeSnapshot("plan"),
678
+ execute: async () => { adapterFinished = true; return { status: "completed", evidence: { value: "accepted" } }; },
679
+ },
680
+ }),
681
+ /attestation aggregate exceeded/,
682
+ );
683
+ const failed = store.events.find((event) => event.type === "turn_failed");
684
+ assert.equal(failed.progress.no_progress, !mutated);
685
+ assert.equal(failed.progress.step_advanced, mutated);
686
+ assert.equal(store.events.some((event) => event.type === "turn_completed"), false);
687
+ assert.equal(store.value().turns_started, 1);
688
+ assert.equal(store.value().active_turn_step, null);
689
+ assert.equal(store.value().active_turn_phase, null);
690
+ });
691
+ });
692
+
693
+ test("turn preflight failure occurs before adapter execution or durable turn start", async () => {
694
+ const store = memoryStore();
695
+ let executions = 0;
696
+ await assert.rejects(runContinuationDriver({
697
+ maxTurns: 1,
698
+ store,
699
+ preflightTurn: async () => { throw new Error("aggregate capacity exhausted"); },
700
+ runtime: {
701
+ inspect: async () => snapshot("plan"), synchronize: async () => snapshot("plan"),
702
+ execute: async () => { executions += 1; return { status: "completed" }; },
703
+ },
704
+ }), /aggregate capacity exhausted/);
705
+ assert.equal(executions, 0);
706
+ assert.equal(store.value().turns_started, 0);
707
+ assert.equal(store.events.some((event) => event.type === "turn_started"), false);
303
708
  });
304
709
 
305
710
  test("unsigned adapters retain extension-field compatibility", async () => {
@@ -310,7 +715,10 @@ test("unsigned adapters retain extension-field compatibility", async () => {
310
715
  runtime: {
311
716
  inspect: async () => snapshot("plan"),
312
717
  synchronize: async () => snapshot("plan"),
313
- execute: async () => ({ status: "completed", metadata: { extension: true } }),
718
+ execute: async (request) => {
719
+ assert.equal(Object.hasOwn(request, "gate_action_envelope"), false, "generic external adapters keep their schema-1.0 request shape");
720
+ return { status: "completed", metadata: { extension: true } };
721
+ },
314
722
  },
315
723
  });
316
724
  assert.equal(result.outcome, "budget_exhausted");
@@ -318,6 +726,28 @@ test("unsigned adapters retain extension-field compatibility", async () => {
318
726
  assert.equal(store.events.some((event) => event.type === "turn_failed"), false);
319
727
  });
320
728
 
729
+ test("legacy continuation stores remain compatible without accepted-turn journaling", async () => {
730
+ let value = null;
731
+ const events = [];
732
+ const store = {
733
+ load: () => value,
734
+ save: (next) => { value = structuredClone(next); },
735
+ append: (event) => { events.push(structuredClone(event)); },
736
+ };
737
+ const result = await runContinuationDriver({
738
+ maxTurns: 1,
739
+ store,
740
+ runtime: {
741
+ inspect: async () => snapshot("plan"),
742
+ synchronize: async () => snapshot("plan"),
743
+ execute: async () => ({ status: "completed", summary: "legacy store turn" }),
744
+ },
745
+ });
746
+ assert.equal(result.outcome, "budget_exhausted");
747
+ assert.equal(value.active_turn_phase, null);
748
+ assert.equal(events.some((event) => event.type === "turn_completed"), true);
749
+ });
750
+
321
751
  test("adapter errors fail open to another bounded turn and remain auditable", async () => {
322
752
  const store = memoryStore();
323
753
  let executeCalls = 0;
@@ -1269,7 +1699,80 @@ test("file continuation store refuses a symlinked state target", (t) => {
1269
1699
  const outside = path.join(root, "outside.json");
1270
1700
  fs.writeFileSync(outside, "{}\n");
1271
1701
  fs.symlinkSync(outside, path.join(root, "continuation-driver", "state.json"));
1272
- assert.throws(() => store.load(), /must be a regular file/);
1702
+ assert.throws(() => store.load(), /ELOOP|must be a regular file/);
1703
+ });
1704
+
1705
+ test("file continuation store bounds and identity-checks state, event, and accepted-turn reads", (t) => {
1706
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "continuation-store-hardening-"));
1707
+ t.after(() => fs.rmSync(root, { recursive: true, force: true }));
1708
+ const store = createFileContinuationStore(root);
1709
+ const driverDir = path.join(root, "continuation-driver");
1710
+ const stateFile = path.join(driverDir, "state.json");
1711
+ const eventsFile = path.join(driverDir, "events.jsonl");
1712
+ const acceptedFile = path.join(driverDir, "accepted-turns.jsonl");
1713
+
1714
+ fs.writeFileSync(stateFile, Buffer.alloc(1_048_577));
1715
+ assert.throws(() => store.load(), /state must be a regular file no larger than 1048576 bytes/);
1716
+ fs.rmSync(stateFile);
1717
+ fs.writeFileSync(eventsFile, `${"x".repeat(16_385)}\n`);
1718
+ assert.throws(() => store.load(), /event log line exceeds 16384 bytes/);
1719
+ fs.rmSync(eventsFile);
1720
+ fs.writeFileSync(eventsFile, `${Array.from({ length: 617 }, () => "{}").join("\n")}\n`);
1721
+ assert.throws(() => store.load(), /event log exceeds 616 lines/);
1722
+ fs.rmSync(eventsFile);
1723
+ fs.writeFileSync(acceptedFile, `${"x".repeat(196_609)}\n`);
1724
+ assert.throws(() => store.acceptedTurns(), /accepted-turn log line exceeds 196608 bytes/);
1725
+ fs.rmSync(acceptedFile);
1726
+ fs.writeFileSync(path.join(root, "outside-events"), "{}\n");
1727
+ fs.symlinkSync(path.join(root, "outside-events"), eventsFile);
1728
+ assert.throws(() => store.load(), /ELOOP|changed identity/);
1729
+ });
1730
+
1731
+ test("file continuation store rejects a state path replaced during descriptor open", (t) => {
1732
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "continuation-store-replaced-"));
1733
+ t.after(() => fs.rmSync(root, { recursive: true, force: true }));
1734
+ const store = createFileContinuationStore(root);
1735
+ const state = {
1736
+ schema_version: "1.0", run_id: "run-251", definition_id: "builder.build", max_turns: 1,
1737
+ adapter_command_identity: null, status: "active", turns_started: 0, pending_barrier: null,
1738
+ updated_at: "2026-07-12T12:00:00.000Z",
1739
+ };
1740
+ store.save(state);
1741
+ const stateFile = path.join(root, "continuation-driver", "state.json");
1742
+ const replacement = path.join(root, "replacement.json");
1743
+ fs.writeFileSync(replacement, JSON.stringify(state));
1744
+ const originalOpen = fs.openSync;
1745
+ let swapped = false;
1746
+ fs.openSync = (file, flags, mode) => {
1747
+ const fd = originalOpen(file, flags, mode);
1748
+ if (!swapped && path.resolve(String(file)) === path.resolve(stateFile)) {
1749
+ fs.renameSync(replacement, stateFile);
1750
+ swapped = true;
1751
+ }
1752
+ return fd;
1753
+ };
1754
+ syncBuiltinESMExports();
1755
+ try {
1756
+ assert.throws(() => store.load(), /changed identity while reading/);
1757
+ assert.equal(swapped, true);
1758
+ } finally {
1759
+ fs.openSync = originalOpen;
1760
+ syncBuiltinESMExports();
1761
+ }
1762
+ });
1763
+
1764
+ test("attestation turns fail closed when accepted events lack durable request/result coverage", (t) => {
1765
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "continuation-store-coverage-"));
1766
+ t.after(() => fs.rmSync(root, { recursive: true, force: true }));
1767
+ const store = createFileContinuationStore(root);
1768
+ store.save({
1769
+ schema_version: "1.0", run_id: "run-251", definition_id: "builder.build", max_turns: 1,
1770
+ adapter_command_identity: null, status: "active", turns_started: 1, pending_barrier: null,
1771
+ updated_at: "2026-07-12T12:00:00.000Z",
1772
+ });
1773
+ store.append({ schema_version: "1.0", type: "turn_started", run_id: "run-251", definition_id: "builder.build", current_step: "plan", turns_started: 1, at: "2026-07-12T12:00:00.000Z" });
1774
+ store.append({ schema_version: "1.0", type: "turn_completed", run_id: "run-251", definition_id: "builder.build", current_step: "plan", turns_started: 1, at: "2026-07-12T12:00:01.000Z" });
1775
+ assert.throws(() => store.acceptedTurns(), /attestation coverage is incomplete/);
1273
1776
  });
1274
1777
 
1275
1778
  test("file continuation store detects deleted and rolled-back mission state", (t) => {