@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
@@ -5,8 +5,9 @@ import os from "node:os";
5
5
  import path from "node:path";
6
6
  import { createHash, generateKeyPairSync, sign, verify } from "node:crypto";
7
7
  import { spawnSync } from "node:child_process";
8
+ import { createRequire } from "node:module";
8
9
 
9
- import { FLOW_RUN_EVIDENCE_MANIFEST_PATH, runDir } from "@kontourai/flow";
10
+ import { FLOW_RUN_EVIDENCE_MANIFEST_PATH, acceptException, defaultFlowConfig, flowConfigPath, runDir } from "@kontourai/flow";
10
11
  import {
11
12
  archiveBuilderFlowSession,
12
13
  cancelBuilderFlowSession,
@@ -19,11 +20,13 @@ import {
19
20
  syncBuilderFlowSession,
20
21
  } from "../../build/src/builder-flow-runtime.js";
21
22
  import { builderLifecycleAuthorizationPayload, loadBuilderLifecycleAuthorization, recordAuthorizationConsumed } from "../../build/src/builder-lifecycle-authority.js";
22
- import { driveBuilderFlowSession } from "../../build/src/continuation-driver.js";
23
- import { cancelBuilderBuildRun } from "../../build/src/builder-flow-run-adapter.js";
23
+ import { driveBuilderFlowSession, withContinuationDriverLock } from "../../build/src/continuation-driver.js";
24
+ import { deriveBuilderGateActionEnvelope } from "../../build/src/builder-gate-action-envelope.js";
25
+ import { WORKFLOW_CRITIQUE_STATUSES } from "../../build/src/cli/public-contracts.js";
26
+ import { cancelBuilderBuildRun, startBuilderFlowRun } from "../../build/src/builder-flow-run-adapter.js";
24
27
  import { performLocalClaim, performLocalRelease, readLocalAssignmentStatus, resolveCurrentAssignmentActor } from "../../build/src/cli/assignment-provider.js";
25
28
  import { main as builderRunMain } from "../../build/src/cli/builder-run.js";
26
- import { main as workflowMain } from "../../build/src/cli/workflow.js";
29
+ import { assertAcceptedTurnEvidenceCapacity, main as workflowMain } from "../../build/src/cli/workflow.js";
27
30
  import { buildTrustBundle, inferExecutedTestCount, main as workflowSidecarMain } from "../../build/src/cli/workflow-sidecar.js";
28
31
 
29
32
  const SUBJECT = "local:work-item/runtime-projection";
@@ -33,6 +36,8 @@ const ACTOR = { runtime: "codex", session_id: "runtime-projection", host: "test-
33
36
  const ACTOR_KEY = "codex:runtime-projection:test-host";
34
37
  const AUTHORITY_KEY_ID = "runtime-test";
35
38
  const AUTHORITY_KEYS = generateKeyPairSync("ed25519");
39
+ const require = createRequire(import.meta.url);
40
+ const activeTurnAuthority = require("../../scripts/hooks/lib/continuation-turn-authority.js");
36
41
  const AMBIENT_IDENTITY_ENV_KEYS = [
37
42
  "FLOW_AGENTS_ACTOR",
38
43
  "CODEX_THREAD_ID",
@@ -243,6 +248,8 @@ function bundleClaim({ expectation, claimType, subjectType, status = "pass", rou
243
248
  value: status,
244
249
  metadata: {
245
250
  workflow_subject_ref: subject,
251
+ origin: "check",
252
+ check_kind: "external",
246
253
  ...(expectation === "tests-evidence" ? {
247
254
  recorded_by: "implementation-actor",
248
255
  artifact_refs: [{ kind: "command", excerpt: "node --test src/cli/builder-flow-runtime.test.mjs", summary: "Runtime fixture assertion." }],
@@ -363,6 +370,25 @@ test("small-model client can start and advance from projected actions without ch
363
370
  assert.match(started.projection.next_action.command, /--prefix "\$root"/);
364
371
  assert.ok(started.projection.next_action.command.includes(`'@kontourai/flow-agents@${PACKAGE_VERSION}'`));
365
372
  assert.ok(started.projection.next_action.command.includes(`'workflow' 'status' '--session-dir' '.kontourai/flow-agents/${session.slug}' '--json'`));
373
+ const envelope = started.gateActionEnvelope;
374
+ assert.equal(Object.hasOwn(started.projection.next_action, "gate_action_envelope"), false, "durable state has no duplicate envelope");
375
+ assert.equal(envelope.schema_version, "1.0");
376
+ assert.equal(envelope.action.implementation_allowed, false, "implementation is forbidden before builder.build/execute");
377
+ assert.deepEqual(envelope.action.declared_evidence, ["selected-work"]);
378
+ assert.deepEqual(envelope.action.declared_artifacts, [`<slug>--pull-work.md`, "trust.bundle#selected-work"]);
379
+ assert.equal(envelope.stop_condition.kind, "one_turn");
380
+ assert.equal(envelope.stop_condition.scope.current_gate_only, true);
381
+ assert.deepEqual(envelope.stop_condition.required.unresolved_evidence_ids, ["selected-work"]);
382
+ assert.deepEqual(envelope.stop_condition.sequence, ["activate_required_skills", "produce_declared_artifacts", "record_bound_evidence", "synchronize_canonical_flow", "return_adapter_result"]);
383
+ assert.equal(envelope.stop_condition.adapter_evidence_is_gate_evidence, false);
384
+ assert.equal(envelope.action.skills[0].id, "pull-work");
385
+ assert.equal(envelope.action.skills[0].package.name, "@kontourai/flow-agents");
386
+ assert.match(envelope.action.skills[0].path, /kits\/builder\/skills\/pull-work\/SKILL\.md$/);
387
+ assert.match(envelope.action.skills[0].sha256, /^[a-f0-9]{64}$/);
388
+ assert.equal(envelope.public_interfaces.mutations[0].expectation_id, "selected-work");
389
+ assert.deepEqual(envelope.public_interfaces.mutations[0].package, { name: "@kontourai/flow-agents", version: PACKAGE_VERSION });
390
+ assert.deepEqual(envelope.public_interfaces.mutations[0].argv.slice(0, 2), ["workflow", "evidence"]);
391
+ assert.equal(envelope.public_interfaces.mutations[0].argv.some((value) => value.includes("<")), false, "argv contains no substitution placeholders");
366
392
  assert.ok(fs.existsSync(runDir(session.slug, session.projectRoot)));
367
393
  assert.ok(!fs.existsSync(path.join(session.projectRoot, ".flow", "runs")), "retired runtime path must not be created");
368
394
 
@@ -375,6 +401,8 @@ test("small-model client can start and advance from projected actions without ch
375
401
  assert.equal(advanced.attached, true);
376
402
  assert.equal(advanced.run.state.current_step, "design-probe");
377
403
  assert.deepEqual(advanced.projection.next_action.skills, ["pickup-probe"]);
404
+ assert.equal(advanced.gateActionEnvelope.action.implementation_allowed, false);
405
+ assert.equal(advanced.gateActionEnvelope.progress.canonical_evidence.some((entry) => entry.startsWith("pull-work-gate:")), true, "advancing evidence remains attributable after the step namespace changes");
378
406
  assert.equal(readJson(path.join(session.artifactRoot, "current.json")).active_step_id, "design-probe");
379
407
 
380
408
  const duplicate = await syncBuilderFlowSession({ sessionDir: session.sessionDir });
@@ -382,6 +410,49 @@ test("small-model client can start and advance from projected actions without ch
382
410
  assert.equal(duplicate.run.manifest.evidence.length, advanced.run.manifest.evidence.length);
383
411
  });
384
412
 
413
+ test("gate-action implementation policy permits code only at builder.build/execute", async () => {
414
+ const session = makeSession("gate-action-implementation-policy");
415
+ await startBuilderFlowSession({ sessionDir: session.sessionDir });
416
+ await writeAndSync(session, [bundleClaim({ expectation: "selected-work", claimType: "builder.pull-work.selected", subjectType: "work-item" })]);
417
+ await writeAndSync(session, [
418
+ bundleClaim({ expectation: "pickup-probe-readiness", claimType: "builder.design-probe.pickup-readiness", subjectType: "work-item" }),
419
+ bundleClaim({ expectation: "probe-decisions-or-accepted-gaps", claimType: "builder.design-probe.decisions", subjectType: "decision" }),
420
+ ]);
421
+ const executing = await writeAndSync(session, [bundleClaim({ expectation: "implementation-plan", claimType: "builder.plan.implementation", subjectType: "artifact" })]);
422
+ assert.equal(executing.run.state.current_step, "execute");
423
+ assert.equal(executing.gateActionEnvelope.action.implementation_allowed, true);
424
+ assert.equal(executing.gateActionEnvelope.action.declared_artifacts.includes("state.json"), false, "control state is not a declared model artifact");
425
+ assert.equal(executing.gateActionEnvelope.stop_condition.required.artifact_refs.includes("state.json"), false, "control state is not a required model artifact");
426
+ assert.equal(executing.gateActionEnvelope.progress.observed_artifacts.some((entry) => entry.startsWith("state.json:")), false, "control state never counts as progress");
427
+
428
+ const requests = [];
429
+ await driveBuilderFlowSession({
430
+ sessionDir: session.sessionDir,
431
+ maxTurns: 2,
432
+ execute: async (request) => { requests.push(request); return { status: "completed" }; },
433
+ });
434
+ assert.equal(requests.length, 2);
435
+ assert.equal(requests[1].gate_action_envelope.progress.prior_turn.stagnation, "possible");
436
+ const mission = readJson(path.join(session.sessionDir, "continuation-driver", "state.json"));
437
+ assert.equal(mission.prior_progress.stagnation, "stagnant", "two no-op execute turns are true stagnation");
438
+ });
439
+
440
+ test("gate-action artifact identity reads reject symlinks and oversized files", async (t) => {
441
+ await t.test("symlink", async () => {
442
+ const session = makeSession("gate-action-artifact-symlink");
443
+ const outside = path.join(session.projectRoot, "outside-release.json");
444
+ fs.writeFileSync(outside, "{}\n");
445
+ fs.symlinkSync(outside, path.join(session.sessionDir, "release.json"));
446
+ await assert.rejects(startBuilderFlowSession({ sessionDir: session.sessionDir }), /ELOOP|regular file|changed identity/);
447
+ });
448
+
449
+ await t.test("oversized", async () => {
450
+ const session = makeSession("gate-action-artifact-oversized");
451
+ fs.writeFileSync(path.join(session.sessionDir, "release.json"), Buffer.alloc(1_048_577));
452
+ await assert.rejects(startBuilderFlowSession({ sessionDir: session.sessionDir }), /bounded regular file/);
453
+ });
454
+ });
455
+
385
456
  test("Builder projection preserves Flow continuation semantics for exceptional statuses", async () => {
386
457
  const session = makeSession("continuation-status-projection");
387
458
  await startBuilderFlowSession({ sessionDir: session.sessionDir });
@@ -399,7 +470,50 @@ test("Builder projection preserves Flow continuation semantics for exceptional s
399
470
  }
400
471
  });
401
472
 
402
- test("continuation driver advances two real FlowDefinition steps unattended", async () => {
473
+ test("accepted Flow exceptions explicitly waive the current envelope requirements", async () => {
474
+ const session = makeSession("accepted-exception-envelope");
475
+ await startBuilderFlowSession({ sessionDir: session.sessionDir });
476
+ const exception = await acceptException(session.slug, {
477
+ cwd: session.projectRoot,
478
+ gate: "pull-work-gate",
479
+ reason: "authorized fixture waiver",
480
+ authority: "test-reviewer",
481
+ });
482
+ const recovered = await recoverBuilderFlowSession({ sessionDir: session.sessionDir });
483
+ assert.deepEqual(recovered.gateActionEnvelope.gate.accepted_exceptions, [{ gate_id: "pull-work-gate", exception_id: exception.id }]);
484
+ assert.equal(recovered.gateActionEnvelope.gate.requirements[0].status, "accepted_exception");
485
+ assert.deepEqual(recovered.gateActionEnvelope.stop_condition.required.unresolved_evidence_ids, []);
486
+ assert.deepEqual(recovered.gateActionEnvelope.stop_condition.required.artifact_refs, []);
487
+ assert.deepEqual(recovered.gateActionEnvelope.stop_condition.required.skill_ids, []);
488
+ const synchronized = await syncBuilderFlowSession({ sessionDir: session.sessionDir });
489
+ assert.equal(synchronized.attached, false);
490
+ assert.equal(synchronized.run.state.current_step, "design-probe");
491
+ assert.deepEqual(synchronized.gateActionEnvelope.action.skills.map((skill) => skill.id), ["pickup-probe"]);
492
+ });
493
+
494
+ test("all-optional effective Flow gate overrides advance during canonical sync without bundle evidence", async () => {
495
+ const session = makeSession("gate-override-envelope");
496
+ const config = defaultFlowConfig();
497
+ config.gate_overrides["pull-work-gate"] = { expectations: { "selected-work": { required: false } } };
498
+ fs.mkdirSync(path.dirname(flowConfigPath(session.projectRoot)), { recursive: true });
499
+ writeJson(flowConfigPath(session.projectRoot), config);
500
+ const unevaluated = await startBuilderFlowRun({ cwd: session.projectRoot, runId: session.slug, subject: SUBJECT, flowId: "builder.build" });
501
+ const directEnvelope = deriveBuilderGateActionEnvelope({
502
+ sessionDir: session.sessionDir,
503
+ projectRoot: session.projectRoot,
504
+ run: unevaluated,
505
+ definition: readJson(path.join(unevaluated.dir, "definition.json")),
506
+ });
507
+ assert.deepEqual(directEnvelope.stop_condition.required.skill_ids, []);
508
+ fs.rmSync(unevaluated.dir, { recursive: true, force: true });
509
+ const started = await startBuilderFlowSession({ sessionDir: session.sessionDir });
510
+ assert.equal(started.attached, false);
511
+ assert.equal(started.run.state.current_step, "design-probe");
512
+ assert.deepEqual(started.gateActionEnvelope.action.skills.map((skill) => skill.id), ["pickup-probe"]);
513
+ assert.equal(started.gateActionEnvelope.flow.current_step, "design-probe");
514
+ });
515
+
516
+ test("weak structured consumer advances real Builder gates through envelope public interfaces", async () => {
403
517
  const session = makeSession("continuation-driver-two-steps");
404
518
  await startBuilderFlowSession({ sessionDir: session.sessionDir });
405
519
  const visited = [];
@@ -409,13 +523,14 @@ test("continuation driver advances two real FlowDefinition steps unattended", as
409
523
  maxTurns: 2,
410
524
  execute: async (request) => {
411
525
  visited.push(request.current_step);
412
- if (request.current_step === "pull-work") {
526
+ const expectationIds = request.gate_action_envelope.public_interfaces.mutations.map((mutation) => mutation.expectation_id);
527
+ if (expectationIds.includes("selected-work")) {
413
528
  writeBundle(session.sessionDir, [bundleClaim({
414
529
  expectation: "selected-work",
415
530
  claimType: "builder.pull-work.selected",
416
531
  subjectType: "work-item",
417
532
  })]);
418
- } else if (request.current_step === "design-probe") {
533
+ } else if (expectationIds.includes("pickup-probe-readiness")) {
419
534
  writeBundle(session.sessionDir, [
420
535
  bundleClaim({
421
536
  expectation: "pickup-probe-readiness",
@@ -429,7 +544,7 @@ test("continuation driver advances two real FlowDefinition steps unattended", as
429
544
  }),
430
545
  ]);
431
546
  } else {
432
- assert.fail(`unexpected continuation step ${request.current_step}`);
547
+ assert.fail(`unexpected public mutation set ${expectationIds.join(",")}`);
433
548
  }
434
549
  return { status: "completed", summary: `completed ${request.current_step}` };
435
550
  },
@@ -478,8 +593,10 @@ test("public workflow evidence accepts only live signed turn authority after ord
478
593
  const ordinaryChild = ${JSON.stringify(path.resolve(import.meta.dirname, "../../scripts/hooks/lib/actor-identity.js"))};
479
594
  const childIdentity = (await import("node:module")).createRequire(import.meta.url)(ordinaryChild).resolveActorIdentity(evidenceEnv);
480
595
  const record = (expectation) => {
596
+ const binding = request.gate_action_envelope.public_interfaces.mutations.find((entry) => entry.expectation_id === expectation && entry.interface === "workflow.evidence");
597
+ if (!binding) throw new Error("missing workflow.evidence binding for " + expectation);
481
598
  const evidence = { kind: "artifact", file: ${JSON.stringify(`.kontourai/flow-agents/${session.slug}/${session.slug}--pull-work.md`)}, summary: "adapter child records " + expectation };
482
- return spawnSync(process.execPath, [${JSON.stringify(path.resolve(import.meta.dirname, "../../build/src/cli.js"))}, "workflow", "evidence", "--session-dir", ${JSON.stringify(session.sessionDir)}, "--expectation", expectation, "--status", "pass", "--summary", "adapter child records " + expectation, "--evidence-ref-json", JSON.stringify(evidence), "--json"], { cwd: ${JSON.stringify(session.projectRoot)}, encoding: "utf8", env: evidenceEnv });
599
+ return spawnSync(process.execPath, [${JSON.stringify(path.resolve(import.meta.dirname, "../../build/src/cli.js"))}, ...binding.argv, "--status", "pass", "--summary", "adapter child records " + expectation, "--evidence-ref-json", JSON.stringify(evidence)], { cwd: ${JSON.stringify(session.projectRoot)}, encoding: "utf8", env: evidenceEnv });
483
600
  };
484
601
  const evidenceRuns = request.current_step === "pull-work"
485
602
  ? [record("selected-work")]
@@ -556,6 +673,14 @@ test("public workflow evidence accepts only live signed turn authority after ord
556
673
  assert.notEqual(forged.status, 0, "fake nonce and digest without a signed active turn fail ownership");
557
674
  });
558
675
 
676
+ test("signed turn evidence capacity is rejected by preflight before another bounded result", () => {
677
+ assert.doesNotThrow(() => assertAcceptedTurnEvidenceCapacity([], { schema_version: "1.0", iteration: 1 }));
678
+ assert.throws(
679
+ () => assertAcceptedTurnEvidenceCapacity([{ request: { padding: "x".repeat(980_000) }, result: {} }], { schema_version: "1.0", iteration: 2 }),
680
+ /lacks capacity for another bounded result/,
681
+ );
682
+ });
683
+
559
684
  test("public workflow drive signs adapter evidence with a consumed one-time key", async () => {
560
685
  const session = makeSession("continuation-driver-signed-evidence");
561
686
  claimAmbientSessionAssignment(session);
@@ -636,6 +761,8 @@ test("public workflow drive signs adapter evidence with a consumed one-time key"
636
761
  assert.deepEqual(payload.adapter_turns.map((turn) => turn.request), observedRequests);
637
762
  assert.equal(payload.adapter_turns[0].request.schema_version, "1.0");
638
763
  assert.equal(payload.adapter_turns[0].request.next_action.status, "continue");
764
+ assert.equal(payload.adapter_turns[0].request.gate_action_envelope.schema_version, "1.0");
765
+ assert.deepEqual(payload.adapter_turns.map((turn) => turn.request), observedRequests, "the signed payload binds the unchanged envelope bytes observed by the adapter");
639
766
  assert.deepEqual(payload.adapter_turns[0].result.evidence.usage, { input_tokens: 10, output_tokens: 2 });
640
767
  const tampered = Buffer.from(JSON.stringify({ ...payload, max_turns: 2 }));
641
768
  assert.equal(verify(null, tampered, keys.publicKey, Buffer.from(attestation.signature_b64, "base64")), false);
@@ -1148,6 +1275,43 @@ test("partial passing review snapshot waits for the complete verify expectation
1148
1275
  assert.equal(partial.run.state.current_step, "verify");
1149
1276
  assert.equal(partial.run.state.transitions.filter((transition) => transition.type === "route_back").length, 0);
1150
1277
 
1278
+ const definition = readJson(path.join(partial.run.dir, "definition.json"));
1279
+ const validPartialBundle = readJson(path.join(session.sessionDir, "trust.bundle"));
1280
+ const validPartialEvidence = {
1281
+ id: "partial-verify-evidence",
1282
+ gate_id: "verify-gate",
1283
+ kind: "trust.bundle",
1284
+ requested_kind: "trust.bundle",
1285
+ sha256: "a".repeat(64),
1286
+ status: "passed",
1287
+ attached_at: new Date().toISOString(),
1288
+ expectation_ids: ["clean-critique"],
1289
+ bundle: validPartialBundle,
1290
+ };
1291
+ const partialEnvelope = deriveBuilderGateActionEnvelope({
1292
+ sessionDir: session.sessionDir,
1293
+ projectRoot: session.projectRoot,
1294
+ run: {
1295
+ ...partial.run,
1296
+ manifest: {
1297
+ ...partial.run.manifest,
1298
+ evidence: [...partial.run.manifest.evidence, validPartialEvidence],
1299
+ },
1300
+ },
1301
+ definition,
1302
+ });
1303
+ assert.equal(partialEnvelope.gate.requirements.find((entry) => entry.id === "clean-critique").status, "satisfied");
1304
+ assert.deepEqual(partialEnvelope.gate.unresolved_requirement_ids.sort(), ["acceptance-criteria", "policy-compliance", "tests-evidence"]);
1305
+ assert.equal(partialEnvelope.gate.requirements.find((entry) => entry.id === "policy-compliance").required, false);
1306
+ assert.deepEqual(partialEnvelope.stop_condition.required.unresolved_evidence_ids.sort(), ["acceptance-criteria", "tests-evidence"]);
1307
+ assert.equal(partialEnvelope.stop_condition.required.artifact_refs.includes("trust.bundle#policy-compliance"), false);
1308
+ const critique = partialEnvelope.public_interfaces.mutations.find((entry) => entry.interface === "workflow.critique");
1309
+ assert.deepEqual(critique.parameters.find((entry) => entry.name === "verdict").allowed_values, [...WORKFLOW_CRITIQUE_STATUSES]);
1310
+ assert.deepEqual(critique.parameters.find((entry) => entry.name === "artifact_ref"), {
1311
+ name: "artifact_ref", flag: "--artifact-ref", required: false, repeatable: true,
1312
+ required_when: { parameter: "verdict", equals: "pass" },
1313
+ });
1314
+
1151
1315
  const complete = await writeAndSync(session, [
1152
1316
  bundleClaim({ expectation: "tests-evidence", claimType: "builder.verify.tests", subjectType: "flow-step" }),
1153
1317
  ...verifiedTestsPrerequisites(session),
@@ -1155,6 +1319,48 @@ test("partial passing review snapshot waits for the complete verify expectation
1155
1319
  assert.equal(complete.run.state.current_step, "merge-ready");
1156
1320
  });
1157
1321
 
1322
+ test("expectation_ids labels cannot satisfy a mismatched trust bundle claim", async () => {
1323
+ const session = makeSession("mislabeled-partial-gate");
1324
+ await startBuilderFlowSession({ sessionDir: session.sessionDir });
1325
+ await writeAndSync(session, [bundleClaim({ expectation: "selected-work", claimType: "builder.pull-work.selected", subjectType: "work-item" })]);
1326
+ await writeAndSync(session, [
1327
+ bundleClaim({ expectation: "pickup-probe-readiness", claimType: "builder.design-probe.pickup-readiness", subjectType: "work-item" }),
1328
+ bundleClaim({ expectation: "probe-decisions-or-accepted-gaps", claimType: "builder.design-probe.decisions", subjectType: "decision" }),
1329
+ ]);
1330
+ await writeAndSync(session, [bundleClaim({ expectation: "implementation-plan", claimType: "builder.plan.implementation", subjectType: "artifact" })]);
1331
+ await writeAndSync(session, [bundleClaim({ expectation: "implementation-scope", claimType: "builder.execute.scope", subjectType: "change" })]);
1332
+ const partial = await writeAndSync(session, verifiedTestsPrerequisites(session).slice(0, 1));
1333
+
1334
+ const mislabeledBundle = readJson(path.join(session.sessionDir, "trust.bundle"));
1335
+ mislabeledBundle.claims[0].claimType = "workflow.critique.wrong-claim";
1336
+ const definition = readJson(path.join(partial.run.dir, "definition.json"));
1337
+ const envelope = deriveBuilderGateActionEnvelope({
1338
+ sessionDir: session.sessionDir,
1339
+ projectRoot: session.projectRoot,
1340
+ run: {
1341
+ ...partial.run,
1342
+ manifest: {
1343
+ ...partial.run.manifest,
1344
+ evidence: [...partial.run.manifest.evidence, {
1345
+ id: "mislabeled-partial-verify-evidence",
1346
+ gate_id: "verify-gate",
1347
+ kind: "trust.bundle",
1348
+ requested_kind: "trust.bundle",
1349
+ sha256: "b".repeat(64),
1350
+ status: "passed",
1351
+ attached_at: new Date().toISOString(),
1352
+ expectation_ids: ["clean-critique"],
1353
+ bundle: mislabeledBundle,
1354
+ }],
1355
+ },
1356
+ },
1357
+ definition,
1358
+ });
1359
+
1360
+ assert.equal(envelope.gate.requirements.find((entry) => entry.id === "clean-critique").status, "unresolved");
1361
+ assert.equal(envelope.gate.unresolved_requirement_ids.includes("clean-critique"), true);
1362
+ });
1363
+
1158
1364
  test("initial gate boundary rejects pre-run and far-future claims", async () => {
1159
1365
  for (const [name, timestamp] of [
1160
1366
  ["pre-run", NOW],
@@ -1345,9 +1551,9 @@ test("workspace review fails closed when a Git worktree marker cannot be inspect
1345
1551
  );
1346
1552
  });
1347
1553
 
1348
- test("verified sidecar claims drive the composed publish and learning prefix to completion", async () => {
1554
+ test("publish-change reports an external capability gap and self-authored results cannot pass", async () => {
1349
1555
  const session = makeSession("composed-completion");
1350
- claimSessionAssignment(session);
1556
+ const ambient = claimAmbientSessionAssignment(session);
1351
1557
  await startBuilderFlowSession({ sessionDir: session.sessionDir });
1352
1558
  const steps = [
1353
1559
  () => [bundleClaim({ expectation: "selected-work", claimType: "builder.pull-work.selected", subjectType: "work-item" })],
@@ -1367,55 +1573,78 @@ test("verified sidecar claims drive the composed publish and learning prefix to
1367
1573
  assert.equal(readJson(path.join(session.artifactRoot, "current.json")).active_step_id, "pr-open");
1368
1574
  assert.deepEqual(prOpen.next_action.skills, []);
1369
1575
  assert.deepEqual(prOpen.next_action.operations, ["publish-change"]);
1576
+ assert.equal(prOpen.next_action.status, "blocked");
1577
+ assert.equal(prOpen.next_action.external_capability.completion, "external_verification_required");
1578
+
1579
+ const operationView = await syncBuilderFlowSession({ sessionDir: session.sessionDir });
1580
+ const publish = operationView.gateActionEnvelope.public_interfaces.mutations.find((entry) => entry.interface === "operation");
1581
+ assert.equal(publish.operation, "publish-change");
1582
+ assert.equal(publish.protocol.capability, "pull_request.create");
1583
+ assert.deepEqual(publish.protocol.result.required, ["provider", "repository", "number", "url", "head_ref", "base_ref"]);
1584
+ assert.deepEqual(publish.protocol.result.properties.number, { type: "integer", minimum: 1 });
1585
+ assert.deepEqual(publish.protocol.result.url_protocols, ["https:"]);
1586
+ assert.equal(publish.protocol.result.persist_as, "publish-change.result.json");
1587
+ assert.deepEqual(publish.completion, {
1588
+ status: "external_verification_required",
1589
+ executable_by_flow_agents: false,
1590
+ gate_evidence_interface: null,
1591
+ });
1592
+ assert.equal(Object.hasOwn(publish, "record_completion"), false);
1593
+ const providerResult = { provider: "fixture", repository: "kontourai/flow-agents", number: 577, url: "https://example.test/kontourai/flow-agents/pull/577", head_ref: "issue-577", base_ref: "main" };
1594
+ writeJson(path.join(session.sessionDir, publish.protocol.result.persist_as), providerResult);
1595
+ const unchanged = await syncBuilderFlowSession({ sessionDir: session.sessionDir });
1596
+ assert.equal(unchanged.attached, false);
1597
+ assert.equal(unchanged.run.state.current_step, "pr-open");
1598
+ assert.equal(unchanged.projection.next_action.status, "blocked");
1599
+ assert.equal(unchanged.run.manifest.evidence.some((entry) => entry.expectation_ids?.includes("pull-request-opened")), false);
1600
+ const beforeFlow = snapshotTree(runDir(session.slug, session.projectRoot));
1601
+ const beforeProjection = snapshotProjectionTargets(session);
1602
+ const genericEvidenceArgs = ["evidence", "--session-dir", session.sessionDir, "--expectation", "pull-request-opened", "--status", "pass", "--summary", "self-completion", "--evidence-ref-json", JSON.stringify({ kind: "artifact", file: `.kontourai/flow-agents/${session.slug}/publish-change.result.json`, summary: "locally authored provider-shaped result" })];
1603
+ await assert.rejects(
1604
+ () => workflowMain(genericEvidenceArgs),
1605
+ /operation-bound expectation.*authenticated external ChangeProvider/,
1606
+ );
1607
+ assert.deepEqual(snapshotTree(runDir(session.slug, session.projectRoot)), beforeFlow);
1608
+ assert.deepEqual(snapshotProjectionTargets(session), beforeProjection);
1370
1609
 
1371
- const mergeReadyCi = await writeAndSync(session, [bundleClaim({ expectation: "pull-request-opened", claimType: "builder.pr-open.pull-request", subjectType: "pull-request" })]);
1372
- assert.equal(mergeReadyCi.run.state.current_step, "merge-ready-ci");
1373
- assert.equal(readJson(path.join(session.artifactRoot, "current.json")).active_step_id, "merge-ready-ci");
1374
- const learn = await writeAndSync(session, [bundleClaim({ expectation: "ci-merge-readiness", claimType: "builder.merge-ready-ci.readiness", subjectType: "pull-request" })]);
1375
- assert.equal(learn.run.state.current_step, "learn");
1376
- assert.equal(readJson(path.join(session.artifactRoot, "current.json")).active_step_id, "learn");
1377
- const completed = await writeAndSync(session, [
1378
- bundleClaim({ expectation: "decision-evidence", claimType: "builder.learn.decisions", subjectType: "decision" }),
1379
- bundleClaim({ expectation: "learning-evidence", claimType: "builder.learn.evidence", subjectType: "release" }),
1380
- ]);
1381
-
1382
- assert.equal(completed.run.state.current_step, "learn", JSON.stringify(completed.run.state.gate_outcomes, null, 2));
1383
- assert.equal(completed.run.state.status, "completed");
1384
- assert.equal(completed.projection.status, "delivered");
1385
- assert.deepEqual(completed.projection.next_action, { status: "done", summary: "Canonical Flow run is complete." });
1386
- const liveEvidence = completed.run.manifest.evidence.filter((entry) => !entry.superseded_by);
1387
- assert.deepEqual(liveEvidence.map((entry) => entry.expectation_ids), [
1388
- ["selected-work"],
1389
- ["pickup-probe-readiness", "probe-decisions-or-accepted-gaps"],
1390
- ["implementation-plan"],
1391
- ["implementation-scope"],
1392
- ["clean-critique", "acceptance-criteria", "tests-evidence"],
1393
- ["merge-readiness"],
1394
- ["pull-request-opened"],
1395
- ["ci-merge-readiness"],
1396
- ["decision-evidence", "learning-evidence"],
1397
- ]);
1398
-
1399
- const flowDirectory = runDir(session.slug, session.projectRoot);
1400
- const beforeFlow = snapshotTree(flowDirectory);
1401
- const staleState = readJson(path.join(session.sessionDir, "state.json"));
1402
- staleState.status = "in_progress";
1403
- staleState.phase = "verification";
1404
- staleState.next_action = { status: "continue", summary: "stale" };
1405
- writeJson(path.join(session.sessionDir, "state.json"), staleState);
1406
- const recovered = await recoverBuilderFlowSession({ sessionDir: session.sessionDir });
1407
- assert.equal(recovered.attached, false);
1408
- assert.equal(recovered.projection.status, "delivered");
1409
- assert.equal(recovered.projection.phase, "done");
1410
- assert.deepEqual(recovered.projection.next_action, { status: "done", summary: "Canonical Flow run is complete." });
1411
- assert.deepEqual(snapshotTree(flowDirectory), beforeFlow);
1412
-
1413
- const archived = await archiveBuilderFlowSession({
1414
- sessionDir: session.sessionDir,
1415
- authorizationFile: liveLifecycleAuthorization(session, "archive", "completed-archive"),
1610
+ await withContinuationDriverLock(session.sessionDir, async (lock) => {
1611
+ const driverDir = path.join(session.sessionDir, "continuation-driver");
1612
+ writeJson(path.join(driverDir, "state.json"), {
1613
+ schema_version: "1.0", run_id: session.slug, definition_id: "builder.build", max_turns: 1,
1614
+ adapter_command_identity: "operation-rejection-test", status: "active", turns_started: 1,
1615
+ active_turn_step: "pr-open", active_turn_public_key_digest: null, pending_barrier: null,
1616
+ });
1617
+ const issued = activeTurnAuthority.issueActiveTurnAuthority({
1618
+ sessionDir: session.sessionDir,
1619
+ runId: session.slug,
1620
+ definitionId: "builder.build",
1621
+ currentStep: "pr-open",
1622
+ iteration: 1,
1623
+ maxTurns: 1,
1624
+ adapterCommandIdentity: "operation-rejection-test",
1625
+ assignmentActor: ambient.actorKey,
1626
+ assignmentActorStruct: ambient.actor,
1627
+ lock,
1628
+ timeoutMs: 10_000,
1629
+ });
1630
+ writeJson(path.join(driverDir, "state.json"), {
1631
+ ...readJson(path.join(driverDir, "state.json")),
1632
+ active_turn_public_key_digest: issued.publicKeyDigest,
1633
+ });
1634
+ const signedEnv = {
1635
+ ...process.env,
1636
+ FLOW_AGENTS_CONTINUATION_RUN_ID: issued.runId,
1637
+ FLOW_AGENTS_CONTINUATION_TURN_SECRET: issued.turnSecret,
1638
+ };
1639
+ for (const key of AMBIENT_IDENTITY_ENV_KEYS) delete signedEnv[key];
1640
+ const signed = spawnSync(process.execPath, [path.resolve(import.meta.dirname, "../../build/src/cli.js"), "workflow", ...genericEvidenceArgs], { cwd: session.projectRoot, encoding: "utf8", env: signedEnv });
1641
+ assert.notEqual(signed.status, 0);
1642
+ assert.match(signed.stderr, /operation-bound expectation.*authenticated external ChangeProvider/);
1643
+ assert.equal(issued.cleanup(), true);
1416
1644
  });
1417
- assert.equal(readJson(path.join(archived.archiveDir, "state.json")).status, "archived");
1418
- assert.deepEqual(snapshotTree(flowDirectory), beforeFlow);
1645
+ assert.deepEqual(snapshotTree(runDir(session.slug, session.projectRoot)), beforeFlow);
1646
+ assert.deepEqual(snapshotProjectionTargets(session), beforeProjection);
1647
+ await releaseBuilderFlowAssignment({ sessionDir: session.sessionDir, reason: `test cleanup for ${ambient.actorKey}` });
1419
1648
  });
1420
1649
 
1421
1650
  test("recovery loads the slug-bound run, restores every matching projection, and preserves every Flow byte", async () => {