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