@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
@@ -8,6 +8,7 @@ import { createRequire, syncBuiltinESMExports } from "node:module";
8
8
  import { readKitInventory } from "../../build/src/runtime-adapters.js";
9
9
  import { main as validateHookInfluence } from "../../build/src/cli/validate-hook-influence.js";
10
10
  import { parseKitFlowStepActions, parseKitSkillRoles, validateKitRepository } from "../../build/src/flow-kit/validate.js";
11
+ import { observeBuilderArtifactsForProgress } from "../../build/src/builder-gate-action-envelope.js";
11
12
 
12
13
  const require = createRequire(import.meta.url);
13
14
  const { workflowTriggersFor } = require("../../scripts/hooks/lib/kit-catalog.js");
@@ -70,35 +71,75 @@ test("Builder flow step actions are structured, complete, and operation-aware",
70
71
  assert.deepEqual(result.errors, []);
71
72
  assert.equal(result.entries.length, 14);
72
73
  assert.deepEqual(result.entries.find((entry) => entry.step_id === "plan")?.skills, ["plan-work"]);
74
+ assert.deepEqual(result.entries.filter((entry) => entry.implementation_allowed).map((entry) => `${entry.flow_id}/${entry.step_id}`), ["builder.build/execute"]);
75
+ assert.equal(result.entries.find((entry) => entry.step_id === "verify")?.expectation_bindings.find((binding) => binding.expectation_id === "clean-critique")?.interface, "workflow.critique");
73
76
  assert.deepEqual(result.entries.find((entry) => entry.step_id === "pr-open"), {
74
77
  flow_id: "builder.build",
75
78
  step_id: "pr-open",
76
79
  skills: [],
77
80
  operations: ["publish-change"],
78
- artifacts: ["release.json"],
81
+ artifacts: ["publish-change.result.json"],
79
82
  expectation_ids: ["pull-request-opened"],
83
+ implementation_allowed: false,
84
+ expectation_bindings: [{ expectation_id: "pull-request-opened", interface: "operation", operation: "publish-change" }],
85
+ artifact_bindings: [{ artifact: "publish-change.result.json", expectation_ids: ["pull-request-opened"] }],
80
86
  });
81
87
  });
82
88
 
83
89
  test("flow step action metadata rejects malformed and duplicate entries", () => {
84
90
  const result = parseKitFlowStepActions({
85
91
  flow_step_actions: [
86
- { flow_id: "builder.build", step_id: "plan", skills: ["plan-work"] },
87
- { flow_id: "builder.build", step_id: "plan", skills: [] },
88
- { flow_id: "builder.build", step_id: "verify", skills: "verify-work" },
89
- { flow_id: "builder.build", step_id: "publish", skills: [], operations: ["publish-change"], expectation_ids: ["pull-request-opened", "pull-request-opened"] },
92
+ { flow_id: "builder.build", step_id: "plan", skills: ["plan-work"], implementation_allowed: false, artifacts: [], expectation_ids: [], expectation_bindings: [] },
93
+ { flow_id: "builder.build", step_id: "plan", skills: [], implementation_allowed: false, artifacts: [], expectation_ids: [], expectation_bindings: [] },
94
+ { flow_id: "builder.build", step_id: "verify", skills: "verify-work", implementation_allowed: false, artifacts: [], expectation_ids: [], expectation_bindings: [] },
95
+ { flow_id: "builder.build", step_id: "publish", skills: [], operations: ["publish-change"], implementation_allowed: false, artifacts: [], expectation_ids: ["pull-request-opened", "pull-request-opened"], expectation_bindings: [] },
96
+ { flow_id: "builder.build", step_id: "execute", skills: ["execute-plan"], implementation_allowed: true, artifacts: ["x".repeat(1025)], expectation_ids: [], expectation_bindings: [], unexpected: "ignored before #577" },
90
97
  ],
91
98
  }, "fixture/kit.json");
92
99
 
93
100
  assert.match(result.errors.join("\n"), /duplicates 'builder\.build\/plan'/);
94
101
  assert.match(result.errors.join("\n"), /skills must be an identifier list/);
95
- assert.match(result.errors.join("\n"), /expectation_ids must be a unique identifier list when present/);
102
+ assert.match(result.errors.join("\n"), /expectation_ids must be a unique identifier list/);
103
+ assert.match(result.errors.join("\n"), /unsupported field\(s\): unexpected/);
104
+ });
105
+
106
+ test("flow step action metadata requires progress and policy fields", () => {
107
+ const base = {
108
+ flow_id: "builder.build",
109
+ step_id: "plan",
110
+ skills: ["plan-work"],
111
+ implementation_allowed: false,
112
+ artifacts: [],
113
+ expectation_ids: [],
114
+ expectation_bindings: [],
115
+ };
116
+ for (const field of ["expectation_ids", "artifacts", "implementation_allowed", "expectation_bindings"]) {
117
+ const action = { ...base };
118
+ delete action[field];
119
+ const result = parseKitFlowStepActions({ flow_step_actions: [action] }, "fixture/kit.json");
120
+ assert.match(result.errors.join("\n"), new RegExp(`flow_step_actions\\[0\\]\\.${field} must be explicitly declared`), field);
121
+ }
122
+ });
123
+
124
+ test("flow step action metadata rejects oversized action lists", () => {
125
+ const result = parseKitFlowStepActions({
126
+ flow_step_actions: [{
127
+ flow_id: "builder.build",
128
+ step_id: "plan",
129
+ skills: Array.from({ length: 33 }, (_, index) => `skill-${index}`),
130
+ implementation_allowed: false,
131
+ artifacts: [],
132
+ expectation_ids: [],
133
+ expectation_bindings: [],
134
+ }],
135
+ }, "fixture/kit.json");
136
+ assert.match(result.errors.join("\n"), /list exceeds 32 entries/);
96
137
  });
97
138
 
98
139
  test("flow step action metadata preserves explicit operation expectation ownership", () => {
99
140
  const result = parseKitFlowStepActions({
100
141
  flow_step_actions: [
101
- { flow_id: "builder.build", step_id: "pr-open", skills: [], operations: ["publish-change"], expectation_ids: ["pull-request-opened"] },
142
+ { flow_id: "builder.build", step_id: "pr-open", skills: [], operations: ["publish-change"], implementation_allowed: false, artifacts: [], expectation_ids: ["pull-request-opened"], expectation_bindings: [{ expectation_id: "pull-request-opened", interface: "operation", operation: "publish-change" }], artifact_bindings: [] },
102
143
  ],
103
144
  }, "fixture/kit.json");
104
145
 
@@ -107,6 +148,94 @@ test("flow step action metadata preserves explicit operation expectation ownersh
107
148
  assert.deepEqual(result.entries[0]?.artifacts, []);
108
149
  });
109
150
 
151
+ test("Builder action validation rejects expectation omissions, unsafe artifacts, and unknown operations", async () => {
152
+ async function errorsFor(name, mutate) {
153
+ const root = tempRoot(`flow-agents-action-contract-${name}-`);
154
+ const kit = path.join(root, "builder");
155
+ fs.cpSync("kits/builder", kit, { recursive: true });
156
+ const manifestFile = path.join(kit, "kit.json");
157
+ const manifest = JSON.parse(fs.readFileSync(manifestFile, "utf8"));
158
+ mutate(manifest);
159
+ writeJson(manifestFile, manifest);
160
+ return (await validateKitRepository(kit)).join("\n");
161
+ }
162
+
163
+ const omitted = await errorsFor("omitted-expectation", (manifest) => {
164
+ const action = manifest.flow_step_actions.find((entry) => entry.step_id === "verify");
165
+ action.expectation_ids = action.expectation_ids.filter((id) => id !== "policy-compliance");
166
+ action.expectation_bindings = action.expectation_bindings.filter((binding) => binding.expectation_id !== "policy-compliance");
167
+ action.artifacts = action.artifacts.filter((artifact) => artifact !== "trust.bundle#policy-compliance");
168
+ action.artifact_bindings = action.artifact_bindings.filter((binding) => binding.artifact !== "trust.bundle#policy-compliance");
169
+ });
170
+ assert.match(omitted, /expectation_ids must exactly equal its resolved Flow expectation set/);
171
+
172
+ const traversal = await errorsFor("artifact-traversal", (manifest) => {
173
+ const action = manifest.flow_step_actions.find((entry) => entry.step_id === "plan");
174
+ action.artifacts.push("../outside.md");
175
+ action.artifact_bindings.push({ artifact: "../outside.md", expectation_ids: ["implementation-plan"] });
176
+ });
177
+ assert.match(traversal, /safe session-relative paths/);
178
+
179
+ for (const [name, artifact] of [["absolute", "/tmp/outside.md"], ["fragment", "release.json#arbitrary"]]) {
180
+ const unsafe = await errorsFor(name, (manifest) => {
181
+ const action = manifest.flow_step_actions.find((entry) => entry.step_id === "pr-open");
182
+ action.artifacts = [artifact];
183
+ action.artifact_bindings = [{ artifact, expectation_ids: ["pull-request-opened"] }];
184
+ });
185
+ assert.match(unsafe, /safe session-relative paths/, name);
186
+ }
187
+
188
+ const typo = await errorsFor("operation-typo", (manifest) => {
189
+ const action = manifest.flow_step_actions.find((entry) => entry.step_id === "pr-open");
190
+ action.operations = ["publish-chagne"];
191
+ action.expectation_bindings[0].operation = "publish-chagne";
192
+ });
193
+ assert.match(typo, /canonical public operation catalog/);
194
+ });
195
+
196
+ test("artifact progress enforces deduplicated count and aggregate read budgets", (t) => {
197
+ const root = tempRoot("flow-agents-artifact-budget-");
198
+ t.after(() => fs.rmSync(root, { recursive: true, force: true }));
199
+ assert.throws(
200
+ () => observeBuilderArtifactsForProgress(root, Array.from({ length: 129 }, (_, index) => `artifact-${index}.json`)),
201
+ /exceeds 128 unique files/,
202
+ );
203
+ const refs = [];
204
+ for (let index = 0; index < 9; index += 1) {
205
+ const ref = `artifact-${index}.bin`;
206
+ fs.writeFileSync(path.join(root, ref), Buffer.alloc(1024 * 1024));
207
+ refs.push(ref);
208
+ }
209
+ assert.throws(() => observeBuilderArtifactsForProgress(root, refs), /exceeds 8388608 aggregate bytes/);
210
+ });
211
+
212
+ test("metadata validation matches runtime skill and run-wide observable artifact bounds", () => {
213
+ const tooManySkills = parseKitFlowStepActions({
214
+ flow_step_actions: [{
215
+ flow_id: "builder.build", step_id: "execute", skills: Array.from({ length: 17 }, (_, index) => `skill${index}`),
216
+ operations: [], implementation_allowed: true, artifacts: [], expectation_ids: [], expectation_bindings: [], artifact_bindings: [],
217
+ }],
218
+ }, "fixture/kit.json");
219
+ assert.match(tooManySkills.errors.join("\n"), /skills exceeds 16 entries/);
220
+
221
+ const artifacts = Array.from({ length: 129 }, (_, index) => `artifact-${index}.json`);
222
+ const actions = Array.from({ length: 5 }, (_, actionIndex) => {
223
+ const slice = artifacts.slice(actionIndex * 32, (actionIndex + 1) * 32);
224
+ return {
225
+ flow_id: "builder.build", step_id: `step${actionIndex}`, skills: [], operations: [], implementation_allowed: false,
226
+ artifacts: slice, expectation_ids: [], expectation_bindings: [],
227
+ artifact_bindings: slice.map((artifact) => ({ artifact, expectation_ids: [] })),
228
+ };
229
+ });
230
+ const tooManyArtifacts = parseKitFlowStepActions({ flow_step_actions: actions }, "fixture/kit.json");
231
+ assert.match(tooManyArtifacts.errors.join("\n"), /exceed 128 distinct observable file artifacts/);
232
+
233
+ actions[4].artifacts = ["state.json", "trust.bundle#virtual"];
234
+ actions[4].artifact_bindings = actions[4].artifacts.map((artifact) => ({ artifact, expectation_ids: [] }));
235
+ const excluded = parseKitFlowStepActions({ flow_step_actions: actions }, "fixture/kit.json");
236
+ assert.doesNotMatch(excluded.errors.join("\n"), /distinct observable file artifacts/);
237
+ });
238
+
110
239
  test("Builder skill roles form one complete role and producer matrix", async () => {
111
240
  const manifest = JSON.parse(fs.readFileSync("kits/builder/kit.json", "utf8"));
112
241
  const result = parseKitSkillRoles(manifest, "kits/builder/kit.json");
@@ -225,7 +354,7 @@ test("operation-only composed actions must explicitly and exclusively own expect
225
354
  action.operations = ["publish-change"];
226
355
  delete action.expectation_ids;
227
356
  });
228
- assert.match(missing, /operation-only action must explicitly declare expectation_ids/);
357
+ assert.match(missing, /expectation_ids must be explicitly declared/);
229
358
  assert.match(missing, /flow expectation 'builder\.publish-learn\/pr-open\/pull-request-opened' must have exactly one producer owner; found 0/);
230
359
 
231
360
  const duplicate = await errorsFor("duplicate", (manifest) => {
@@ -240,6 +369,30 @@ test("operation-only composed actions must explicitly and exclusively own expect
240
369
  assert.match(duplicate, /flow expectation 'builder\.publish-learn\/pr-open\/pull-request-opened' must have exactly one producer owner; found 2/);
241
370
  });
242
371
 
372
+ test("mixed skill and operation ownership follows each expectation binding interface", async () => {
373
+ const root = tempRoot("flow-agents-mixed-operation-owner-");
374
+ const kit = path.join(root, "builder");
375
+ fs.cpSync("kits/builder", kit, { recursive: true });
376
+ const manifestFile = path.join(kit, "kit.json");
377
+ const manifest = JSON.parse(fs.readFileSync(manifestFile, "utf8"));
378
+ const action = manifest.flow_step_actions.find((entry) => entry.flow_id === "builder.build" && entry.step_id === "verify");
379
+ action.operations = ["publish-change"];
380
+ const policyBinding = action.expectation_bindings.find((entry) => entry.expectation_id === "policy-compliance");
381
+ policyBinding.interface = "operation";
382
+ policyBinding.operation = "publish-change";
383
+ const verifier = manifest.skill_roles.find((entry) => entry.skill_id === "builder.verify-work");
384
+ verifier.expectation_ids = verifier.expectation_ids.filter((id) => id !== "policy-compliance");
385
+ writeJson(manifestFile, manifest);
386
+ const valid = (await validateKitRepository(kit)).join("\n");
387
+ assert.doesNotMatch(valid, /verify\/policy-compliance.*producer owner/);
388
+
389
+ policyBinding.interface = "workflow.evidence";
390
+ delete policyBinding.operation;
391
+ writeJson(manifestFile, manifest);
392
+ const missing = (await validateKitRepository(kit)).join("\n");
393
+ assert.match(missing, /flow expectation 'builder\.build\/verify\/policy-compliance' must have exactly one producer owner; found 0/);
394
+ });
395
+
243
396
  test("skill role cross-reference never reads Flow definitions through traversal, symlinks, or oversized files", async () => {
244
397
  async function errorsFor(name, mutate) {
245
398
  const root = tempRoot(`flow-agents-skill-role-safe-flow-${name}-`);
@@ -0,0 +1,41 @@
1
+ export const WORKFLOW_CRITIQUE_STATUSES = ["pass", "fail", "not_verified"] as const;
2
+
3
+ export const PUBLISH_CHANGE_OPERATION = "publish-change" as const;
4
+
5
+ export const PUBLISH_CHANGE_OPERATION_PROTOCOL = {
6
+ schema_version: "1.0",
7
+ operation: PUBLISH_CHANGE_OPERATION,
8
+ kind: "provider_capability",
9
+ capability: "pull_request.create",
10
+ parameters: [
11
+ { name: "title", required: true, type: "string", max_length: 512 },
12
+ { name: "body", required: true, type: "string", max_length: 65_536 },
13
+ { name: "head_ref", required: true, type: "string", max_length: 255 },
14
+ { name: "base_ref", required: true, type: "string", max_length: 255 },
15
+ { name: "draft", required: false, type: "boolean" },
16
+ ],
17
+ result: {
18
+ required: ["provider", "repository", "number", "url", "head_ref", "base_ref"],
19
+ properties: {
20
+ provider: { type: "string", max_length: 128 },
21
+ repository: { type: "string", max_length: 1_024 },
22
+ number: { type: "integer", minimum: 1 },
23
+ url: { type: "string", max_length: 8_192 },
24
+ head_ref: { type: "string", max_length: 255 },
25
+ base_ref: { type: "string", max_length: 255 },
26
+ },
27
+ url_protocols: ["https:"],
28
+ persist_as: "publish-change.result.json",
29
+ },
30
+ availability: {
31
+ status: "external_capability_required",
32
+ executable_by_flow_agents: false,
33
+ completion_verification: "authenticated_change_provider_required",
34
+ },
35
+ } as const;
36
+
37
+ export const PUBLIC_OPERATION_CONTRACTS = {
38
+ [PUBLISH_CHANGE_OPERATION]: PUBLISH_CHANGE_OPERATION_PROTOCOL,
39
+ } as const;
40
+
41
+ export const PUBLIC_OPERATION_IDS = new Set<string>(Object.keys(PUBLIC_OPERATION_CONTRACTS));
@@ -14,6 +14,7 @@ import { flowAgentsPackageRoot, flowAgentsPackageVersion } from "../lib/package-
14
14
  import { pinnedFlowAgentsCommand } from "../lib/pinned-cli-command.js";
15
15
  import { runObservedCommand } from "../lib/observed-command.js";
16
16
  import { captureReviewWorkspaceSnapshot, startBuilderFlowSession, syncBuilderFlowSession } from "../builder-flow-runtime.js";
17
+ import { WORKFLOW_CRITIQUE_STATUSES } from "./public-contracts.js";
17
18
  // #291 Wave 1 Task 1.1 exports: ensure-session's ownership guard reuses the EXACT same
18
19
  // assignment ⋈ liveness join / claim / supersede logic #290 already ships for the
19
20
  // `assignment-provider` CLI, rather than reimplementing a second, parallel join (static ESM
@@ -2504,24 +2505,19 @@ function expectedGateProducer(flowId: string, stepId: string, expectationId: str
2504
2505
  const actions = Array.isArray(manifest.flow_step_actions) ? manifest.flow_step_actions as AnyObj[] : [];
2505
2506
  const action = actions.find((candidate) => candidate.flow_id === flowId && candidate.step_id === stepId);
2506
2507
  if (!action) die(`record-gate-claim cannot derive a producer for unknown Flow step ${flowId}/${stepId}`);
2508
+ const binding = Array.isArray(action.expectation_bindings)
2509
+ ? action.expectation_bindings.find((candidate): candidate is AnyObj => candidate && typeof candidate === "object" && !Array.isArray(candidate) && candidate.expectation_id === expectationId)
2510
+ : undefined;
2511
+ if (binding?.interface === "operation") {
2512
+ const operation = typeof binding.operation === "string" ? binding.operation : "the declared external operation";
2513
+ die(`record-gate-claim cannot satisfy operation-bound expectation ${expectationId}; ${operation} requires authenticated external ChangeProvider completion`);
2514
+ }
2507
2515
  const skills = Array.isArray(action.skills) ? action.skills.filter((value: unknown): value is string => typeof value === "string") : [];
2508
2516
  const roles = Array.isArray(manifest.skill_roles) ? manifest.skill_roles as AnyObj[] : [];
2509
2517
  const owners = roles.filter((role) => typeof role.skill_id === "string"
2510
2518
  && Array.isArray(role.step_ids) && role.step_ids.includes(stepId)
2511
2519
  && Array.isArray(role.expectation_ids) && role.expectation_ids.includes(expectationId)
2512
2520
  && skills.includes(role.skill_id.replace(/^builder\./, "")));
2513
- if (owners.length === 0) {
2514
- const operations = Array.isArray(action.operations) ? action.operations.filter((value: unknown): value is string => typeof value === "string") : [];
2515
- const operationExpectations = Array.isArray(action.expectation_ids) ? action.expectation_ids : [];
2516
- if (operations.length === 1 && operationExpectations.includes(expectationId)) {
2517
- const artifacts = Array.isArray(action.artifacts) ? action.artifacts.filter((value: unknown): value is string => typeof value === "string") : [];
2518
- return {
2519
- id: `operation:${operations[0]}`,
2520
- artifactPatterns: artifacts.filter((value) => !value.includes("#")),
2521
- selfProducedTrustSlices: artifacts.filter((value) => value.startsWith("trust.bundle#")).map((value) => value.slice("trust.bundle#".length)),
2522
- };
2523
- }
2524
- }
2525
2521
  if (owners.length !== 1) die(`record-gate-claim cannot derive exactly one producer for ${flowId}/${stepId}/${expectationId}`);
2526
2522
  const owner = owners[0]!;
2527
2523
  const artifacts = (Array.isArray(owner.artifacts) ? owner.artifacts : []).filter((value): value is string => typeof value === "string" && value !== "ephemeral decision record");
@@ -2778,7 +2774,7 @@ function completePassingCriteria(existing: AnyObj[], raw: string[], observedComm
2778
2774
  });
2779
2775
  }
2780
2776
 
2781
- const critiqueStatuses = new Set(["pass", "fail", "not_verified"]);
2777
+ const critiqueStatuses = new Set<string>(WORKFLOW_CRITIQUE_STATUSES);
2782
2778
  const safeCritiqueId = /^[a-z][a-z0-9_-]*$/;
2783
2779
 
2784
2780
  function normalizeCritiqueLanes(raw: string[]): AnyObj[] {
@@ -6,7 +6,8 @@ import { isDeepStrictEqual } from "node:util";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { validateDefinition } from "@kontourai/flow";
8
8
  import { loadBuilderFlowRun } from "../builder-flow-run-adapter.js";
9
- import { driveBuilderFlowSession, withContinuationDriverLock } from "../continuation-driver.js";
9
+ import { parseKitFlowStepActions } from "../flow-kit/validate.js";
10
+ import { MAX_CONTINUATION_TURN_RESULT_BYTES, createFileContinuationStore, driveBuilderFlowSession, withContinuationDriverLock } from "../continuation-driver.js";
10
11
  import { inspectBuilderFlowSession, recoverBuilderFlowSession, syncBuilderFlowSession } from "../builder-flow-runtime.js";
11
12
  import { flowAgentsPackageRoot, flowAgentsPackageVersion } from "../lib/package-version.js";
12
13
  import { pinnedFlowAgentsCommand } from "../lib/pinned-cli-command.js";
@@ -86,12 +87,14 @@ async function drive(sessionDir: string, argv: string[], json: boolean): Promise
86
87
  assertOrdinaryMatchingAssignmentActor(sessionDir, slug);
87
88
  const adapterCommand = loadContinuationAdapterCommand(adapterCommandFile);
88
89
  let evidenceSigner: EvidenceSigner | null = null;
89
- const observedAdapterTurns: Array<Record<string, unknown>> = [];
90
+ let observedAdapterTurns: Array<Record<string, unknown>> = [];
90
91
  const result = await withContinuationDriverLock(sessionDir, async (lock) => {
91
92
  assertOrdinaryMatchingAssignmentActor(sessionDir, slug);
92
93
  evidenceSigner = evidenceSigningKeyFile ? consumeEvidenceSigningKey(evidenceSigningKeyFile) : null;
93
- return driveBuilderFlowSession({
94
+ const continuationStore = createFileContinuationStore(sessionDir);
95
+ const outcome = await driveBuilderFlowSession({
94
96
  sessionDir,
97
+ store: continuationStore,
95
98
  maxTurns,
96
99
  adapterCommandIdentity: adapterCommand.identity,
97
100
  authorizeTurn: async () => { assertOrdinaryMatchingAssignmentActor(sessionDir, slug); },
@@ -119,18 +122,13 @@ async function drive(sessionDir: string, argv: string[], json: boolean): Promise
119
122
  continuationTurnSecret: context?.continuationTurnSecret,
120
123
  continuationRunId: context?.continuationRunId,
121
124
  }),
122
- ...(evidenceSigningKeyFile ? { onTurnAccepted: async (request, adapterResult) => {
123
- const next = [...observedAdapterTurns, {
124
- request: structuredClone(request),
125
- result: structuredClone(adapterResult),
126
- }];
127
- if (Buffer.byteLength(JSON.stringify(next), "utf8") > 1_048_576) {
128
- throw new Error("continuation accepted-turn evidence must not exceed 1048576 aggregate bytes");
129
- }
130
- observedAdapterTurns.push(next.at(-1)!);
125
+ ...(evidenceSigningKeyFile ? { preflightTurn: async (request) => {
126
+ assertAcceptedTurnEvidenceCapacity(attestationTurns(continuationStore.acceptedTurns()), request);
131
127
  } } : {}),
132
128
  waitForBarrier: async (barrier) => waitForContinuationBarrier(barrier, { maxWaitMs: barrierWaitMs, pollMs: barrierPollMs }),
133
129
  });
130
+ if (evidenceSigningKeyFile) observedAdapterTurns = attestationTurns(continuationStore.acceptedTurns());
131
+ return outcome;
134
132
  });
135
133
  const output = evidenceSigner
136
134
  ? { ...result, evidence_attestation: signDriveEvidence(evidenceSigner, adapterCommand.identity, maxTurns, result, observedAdapterTurns) }
@@ -140,6 +138,23 @@ async function drive(sessionDir: string, argv: string[], json: boolean): Promise
140
138
  return 0;
141
139
  }
142
140
 
141
+ function attestationTurns(turns: Array<{ request: Record<string, unknown>; result: Record<string, unknown> }>): Array<Record<string, unknown>> {
142
+ const covered = turns.map((turn) => ({ request: structuredClone(turn.request), result: structuredClone(turn.result) }));
143
+ if (Buffer.byteLength(JSON.stringify(covered), "utf8") > 1_048_576) {
144
+ throw new Error("continuation accepted-turn evidence must not exceed 1048576 aggregate bytes");
145
+ }
146
+ return covered;
147
+ }
148
+
149
+ export function assertAcceptedTurnEvidenceCapacity(observedTurns: Array<Record<string, unknown>>, request: Record<string, unknown>): void {
150
+ const base = [...observedTurns, { request: structuredClone(request), result: null }];
151
+ const bytesWithPlaceholder = Buffer.byteLength(JSON.stringify(base), "utf8");
152
+ const exactReservedBytes = bytesWithPlaceholder - Buffer.byteLength("null", "utf8") + MAX_CONTINUATION_TURN_RESULT_BYTES;
153
+ if (exactReservedBytes > 1_048_576) {
154
+ throw new Error("continuation accepted-turn evidence lacks capacity for another bounded result");
155
+ }
156
+ }
157
+
143
158
  type EvidenceSigner = { privateKey: KeyObject; publicKeySpkiB64: string };
144
159
 
145
160
  function consumeEvidenceSigningKey(fileInput: string): EvidenceSigner {
@@ -328,6 +343,15 @@ async function evidence(sessionDir: string, argv: string[], json: boolean): Prom
328
343
  if (Object.hasOwn(parsed.flags, "command") && commands.length === 0) {
329
344
  throw new Error("workflow evidence --command requires a shell command value");
330
345
  }
346
+ const expectation = flagString(parsed.flags, "expectation")!;
347
+ // Operation-bound expectations deliberately have no generic evidence writer.
348
+ // Check before recovery, locking, or actor resolution so a locally authored
349
+ // operation result cannot cause any canonical or projection mutation.
350
+ const inspected = await inspectBuilderFlowSession({ sessionDir });
351
+ const operation = builderOperationForExpectation(inspected.run.definitionId, expectation);
352
+ if (operation) {
353
+ throw new Error(`workflow evidence cannot satisfy operation-bound expectation ${expectation}; ${operation} requires authenticated external ChangeProvider completion`);
354
+ }
331
355
  const requiresTestEvidence = flagString(parsed.flags, "expectation") === "tests-evidence" && flagString(parsed.flags, "status") === "pass";
332
356
  // Argument and command-shape rejection must be read-only. Recovery below may
333
357
  // repair stale projections, so it runs only after every command is accepted.
@@ -377,6 +401,18 @@ async function evidence(sessionDir: string, argv: string[], json: boolean): Prom
377
401
  return 0;
378
402
  }
379
403
 
404
+ function builderOperationForExpectation(flowId: string, expectationId: string): string | null {
405
+ const kit = readJsonFile(path.join(PACKAGE_ROOT, "kits", "builder", "kit.json"), "Builder kit metadata");
406
+ const parsed = parseKitFlowStepActions(kit, "kits/builder/kit.json");
407
+ if (parsed.errors.length) throw new Error(`Builder kit metadata is invalid: ${parsed.errors.join("; ")}`);
408
+ for (const action of parsed.entries) {
409
+ if (action.flow_id !== flowId) continue;
410
+ const binding = action.expectation_bindings.find((candidate) => candidate.expectation_id === expectationId);
411
+ if (binding?.interface === "operation") return binding.operation ?? "the declared external operation";
412
+ }
413
+ return null;
414
+ }
415
+
380
416
  async function critique(sessionDir: string, argv: string[], json: boolean): Promise<number> {
381
417
  const parsed = parseArgs(argv);
382
418
  assertOnlyFlags(parsed.flags, new Set(["artifact-root", "session-dir", "json", "id", "verdict", "summary", "artifact-ref", "finding-json", "lane-json", "timestamp"]), "workflow critique");