@kontourai/flow-agents 3.11.0 → 3.12.1

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