@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
@@ -0,0 +1,162 @@
1
+ import * as path from "node:path";
2
+ import { PUBLIC_OPERATION_IDS } from "../cli/public-contracts.js";
3
+ const MAX_FLOW_STEP_ACTIONS = 128;
4
+ const MAX_FLOW_STEP_ACTION_LIST_ITEMS = 32;
5
+ const MAX_FLOW_STEP_ACTION_SKILLS = 16;
6
+ const MAX_FLOW_OBSERVABLE_ARTIFACTS = 128;
7
+ const MAX_FLOW_STEP_ACTION_TEXT = 1024;
8
+ const WORKFLOW_TRIGGER_IDENTIFIER_RE = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;
9
+ export function workflowTriggerIdentifier(value) {
10
+ return typeof value === "string" && WORKFLOW_TRIGGER_IDENTIFIER_RE.test(value);
11
+ }
12
+ export function parseKitFlowStepActions(manifest, manifestPath) {
13
+ const entries = [];
14
+ const errors = [];
15
+ const raw = manifest.flow_step_actions;
16
+ if (raw === undefined)
17
+ return { entries, errors };
18
+ if (!Array.isArray(raw))
19
+ return { entries, errors: [`${manifestPath}: .flow_step_actions must be a list`] };
20
+ if (raw.length > MAX_FLOW_STEP_ACTIONS)
21
+ return { entries, errors: [`${manifestPath}: .flow_step_actions exceeds ${MAX_FLOW_STEP_ACTIONS} entries`] };
22
+ const seen = new Set();
23
+ raw.forEach((rawEntry, index) => {
24
+ const entry = parseFlowStepActionEntry(rawEntry, manifestPath, index, errors);
25
+ if (!entry)
26
+ return;
27
+ const key = `${entry.flow_id}/${entry.step_id}`;
28
+ if (seen.has(key))
29
+ errors.push(`${manifestPath}: flow_step_actions[${index}] duplicates '${key}'`);
30
+ else {
31
+ seen.add(key);
32
+ entries.push(entry);
33
+ }
34
+ });
35
+ validateObservableArtifactBounds(entries, manifestPath, errors);
36
+ return { entries, errors };
37
+ }
38
+ function parseFlowStepActionEntry(raw, manifestPath, index, errors) {
39
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
40
+ errors.push(`${manifestPath}: flow_step_actions[${index}] must be an object`);
41
+ return null;
42
+ }
43
+ const record = raw;
44
+ const supported = new Set(["flow_id", "step_id", "skills", "operations", "expectation_ids", "artifacts", "implementation_allowed", "expectation_bindings", "artifact_bindings"]);
45
+ const unsupported = Object.keys(record).filter((key) => !supported.has(key));
46
+ if (unsupported.length) {
47
+ errors.push(`${manifestPath}: flow_step_actions[${index}] has unsupported field(s): ${unsupported.join(", ")}`);
48
+ return null;
49
+ }
50
+ if (!workflowTriggerIdentifier(record.flow_id) || !workflowTriggerIdentifier(record.step_id)) {
51
+ errors.push(`${manifestPath}: flow_step_actions[${index}] must declare valid flow_id and step_id identifiers`);
52
+ return null;
53
+ }
54
+ for (const field of ["expectation_ids", "artifacts", "implementation_allowed", "expectation_bindings", "artifact_bindings"]) {
55
+ if (!Object.hasOwn(record, field))
56
+ errors.push(`${manifestPath}: flow_step_actions[${index}].${field} must be explicitly declared`);
57
+ }
58
+ const lists = parseFlowStepActionLists(record, manifestPath, index, errors);
59
+ if (!lists || typeof record.implementation_allowed !== "boolean") {
60
+ if (typeof record.implementation_allowed !== "boolean")
61
+ errors.push(`${manifestPath}: flow_step_actions[${index}].implementation_allowed must be a boolean`);
62
+ return null;
63
+ }
64
+ const expectationBindings = parseExpectationBindings(record.expectation_bindings ?? [], lists.operations, manifestPath, index, errors);
65
+ if (!expectationBindings || !sameStringSet(expectationBindings.map((binding) => binding.expectation_id), lists.expectation_ids)) {
66
+ if (expectationBindings)
67
+ errors.push(`${manifestPath}: flow_step_actions[${index}].expectation_bindings must bind every expectation_id exactly once`);
68
+ return null;
69
+ }
70
+ const artifactBindings = parseArtifactBindings(record.artifact_bindings ?? [], lists.artifacts, lists.expectation_ids, manifestPath, index, errors);
71
+ return artifactBindings ? { flow_id: record.flow_id, step_id: record.step_id, ...lists, implementation_allowed: record.implementation_allowed, expectation_bindings: expectationBindings, artifact_bindings: artifactBindings } : null;
72
+ }
73
+ function parseFlowStepActionLists(record, manifestPath, index, errors) {
74
+ const skills = record.skills;
75
+ const operations = record.operations ?? [];
76
+ const expectationIds = record.expectation_ids ?? [];
77
+ const artifacts = record.artifacts ?? [];
78
+ const label = `${manifestPath}: flow_step_actions[${index}]`;
79
+ if ([skills, operations, expectationIds, artifacts].some((list) => Array.isArray(list) && list.length > MAX_FLOW_STEP_ACTION_LIST_ITEMS))
80
+ return fail(errors, `${label} list exceeds ${MAX_FLOW_STEP_ACTION_LIST_ITEMS} entries`);
81
+ if (!Array.isArray(skills) || !skills.every(workflowTriggerIdentifier) || new Set(skills).size !== skills.length)
82
+ return fail(errors, `${label}.skills must be an identifier list without duplicates`);
83
+ if (skills.length > MAX_FLOW_STEP_ACTION_SKILLS)
84
+ return fail(errors, `${label}.skills exceeds ${MAX_FLOW_STEP_ACTION_SKILLS} entries`);
85
+ if (!Array.isArray(operations) || !operations.every(workflowTriggerIdentifier) || new Set(operations).size !== operations.length || operations.some((operation) => !PUBLIC_OPERATION_IDS.has(operation)))
86
+ return fail(errors, `${label}.operations must be unique identifiers from the canonical public operation catalog`);
87
+ if (!Array.isArray(expectationIds) || !expectationIds.every(workflowTriggerIdentifier) || new Set(expectationIds).size !== expectationIds.length)
88
+ return fail(errors, `${label}.expectation_ids must be a unique identifier list`);
89
+ if (!Array.isArray(artifacts) || !artifacts.every(isSafeBuilderArtifactRef) || new Set(artifacts).size !== artifacts.length)
90
+ return fail(errors, `${label}.artifacts must be unique safe session-relative paths or trust.bundle#<safe-id> refs`);
91
+ if ([...skills, ...operations, ...expectationIds, ...artifacts].some((entry) => entry.length > MAX_FLOW_STEP_ACTION_TEXT))
92
+ return fail(errors, `${label} values must not exceed ${MAX_FLOW_STEP_ACTION_TEXT} characters`);
93
+ return { skills, operations, expectation_ids: expectationIds, artifacts };
94
+ }
95
+ function fail(errors, message) { errors.push(message); return null; }
96
+ function validateObservableArtifactBounds(entries, manifestPath, errors) {
97
+ const observableByFlow = new Map();
98
+ for (const action of entries) {
99
+ const observed = observableByFlow.get(action.flow_id) ?? new Set();
100
+ for (const artifact of action.artifacts)
101
+ if (isObservableBuilderArtifactRef(artifact))
102
+ observed.add(artifact);
103
+ observableByFlow.set(action.flow_id, observed);
104
+ }
105
+ for (const [flowId, artifacts] of observableByFlow)
106
+ if (artifacts.size > MAX_FLOW_OBSERVABLE_ARTIFACTS)
107
+ errors.push(`${manifestPath}: flow_step_actions for '${flowId}' exceed ${MAX_FLOW_OBSERVABLE_ARTIFACTS} distinct observable file artifacts`);
108
+ }
109
+ function parseExpectationBindings(value, operations, manifestPath, actionIndex, errors) {
110
+ if (!Array.isArray(value) || value.length > MAX_FLOW_STEP_ACTION_LIST_ITEMS)
111
+ return fail(errors, `${manifestPath}: flow_step_actions[${actionIndex}].expectation_bindings must be a bounded list`);
112
+ const bindings = [];
113
+ for (const [bindingIndex, entry] of value.entries()) {
114
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry))
115
+ return fail(errors, `${manifestPath}: flow_step_actions[${actionIndex}].expectation_bindings[${bindingIndex}] must be an object`);
116
+ const record = entry;
117
+ const unsupported = Object.keys(record).filter((key) => key !== "expectation_id" && key !== "interface" && key !== "operation");
118
+ if (unsupported.length)
119
+ return fail(errors, `${manifestPath}: flow_step_actions[${actionIndex}].expectation_bindings[${bindingIndex}] has unsupported field(s): ${unsupported.join(", ")}`);
120
+ if (!workflowTriggerIdentifier(record.expectation_id) || !new Set(["workflow.evidence", "workflow.critique", "operation"]).has(String(record.interface)))
121
+ return fail(errors, `${manifestPath}: flow_step_actions[${actionIndex}].expectation_bindings[${bindingIndex}] must declare a valid expectation_id and interface`);
122
+ if (record.interface === "operation") {
123
+ if (!workflowTriggerIdentifier(record.operation) || !operations.includes(record.operation) || !PUBLIC_OPERATION_IDS.has(record.operation))
124
+ return fail(errors, `${manifestPath}: flow_step_actions[${actionIndex}].expectation_bindings[${bindingIndex}].operation must name a declared canonical public operation`);
125
+ }
126
+ else if (record.operation !== undefined)
127
+ return fail(errors, `${manifestPath}: flow_step_actions[${actionIndex}].expectation_bindings[${bindingIndex}].operation is allowed only for operation bindings`);
128
+ bindings.push({ expectation_id: record.expectation_id, interface: record.interface, ...(typeof record.operation === "string" ? { operation: record.operation } : {}) });
129
+ }
130
+ if (new Set(bindings.map((binding) => binding.expectation_id)).size !== bindings.length)
131
+ return fail(errors, `${manifestPath}: flow_step_actions[${actionIndex}].expectation_bindings must not duplicate expectation ids`);
132
+ return bindings;
133
+ }
134
+ function parseArtifactBindings(value, artifacts, expectationIds, manifestPath, actionIndex, errors) {
135
+ if (!Array.isArray(value) || value.length > MAX_FLOW_STEP_ACTION_LIST_ITEMS)
136
+ return fail(errors, `${manifestPath}: flow_step_actions[${actionIndex}].artifact_bindings must be a bounded list`);
137
+ const bindings = [];
138
+ for (const [bindingIndex, entry] of value.entries()) {
139
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry))
140
+ return fail(errors, `${manifestPath}: flow_step_actions[${actionIndex}].artifact_bindings[${bindingIndex}] must be an object`);
141
+ const record = entry;
142
+ if (Object.keys(record).some((key) => key !== "artifact" && key !== "expectation_ids") || typeof record.artifact !== "string" || !artifacts.includes(record.artifact)
143
+ || !Array.isArray(record.expectation_ids) || !record.expectation_ids.every((id) => typeof id === "string" && expectationIds.includes(id)) || new Set(record.expectation_ids).size !== record.expectation_ids.length)
144
+ return fail(errors, `${manifestPath}: flow_step_actions[${actionIndex}].artifact_bindings[${bindingIndex}] must bind one declared artifact to declared expectation_ids`);
145
+ bindings.push({ artifact: record.artifact, expectation_ids: record.expectation_ids });
146
+ }
147
+ if (!sameStringSet(bindings.map((binding) => binding.artifact), artifacts))
148
+ return fail(errors, `${manifestPath}: flow_step_actions[${actionIndex}].artifact_bindings must bind every artifact exactly once`);
149
+ return bindings;
150
+ }
151
+ export function isSafeBuilderArtifactRef(value) {
152
+ if (typeof value !== "string" || value.length === 0 || value.includes("\0") || value.includes("\\"))
153
+ return false;
154
+ if (value.startsWith("trust.bundle#"))
155
+ return /^trust\.bundle#[a-z0-9]+(?:[.-][a-z0-9]+)*$/.test(value);
156
+ if (value.includes("#") || path.posix.isAbsolute(value))
157
+ return false;
158
+ const expanded = value.replaceAll("<slug>", "slug");
159
+ return expanded !== "." && !expanded.split("/").some((part) => part === "" || part === "." || part === "..");
160
+ }
161
+ export function isObservableBuilderArtifactRef(value) { return !value.includes("#") && path.posix.normalize(value) !== "state.json"; }
162
+ export function sameStringSet(left, right) { return left.length === right.length && new Set(left).size === left.length && left.every((entry) => right.includes(entry)); }
@@ -0,0 +1,16 @@
1
+ import { type KitFlowStepActionEntry } from "./action-metadata.js";
2
+ type SkillRoleEntry = {
3
+ skill_id: string;
4
+ role: string;
5
+ flow_id?: string;
6
+ step_ids: string[];
7
+ expectation_ids: string[];
8
+ };
9
+ export declare function validateActionRepositoryMetadata(input: {
10
+ kitDir: string;
11
+ manifestPath: string;
12
+ manifest: Record<string, unknown>;
13
+ actions: KitFlowStepActionEntry[];
14
+ skillRoles: SkillRoleEntry[];
15
+ }): string[];
16
+ export {};
@@ -0,0 +1,237 @@
1
+ import fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { sameStringSet } from "./action-metadata.js";
4
+ const MAX_FLOW_DEFINITION_BYTES = 1_048_576;
5
+ export function validateActionRepositoryMetadata(input) {
6
+ const errors = [];
7
+ validateDeclaredSkills(input.manifest, input.skillRoles, input.manifestPath, errors);
8
+ const flows = loadFlowMetadata(input.kitDir, input.manifest, input.manifestPath, errors);
9
+ const resolve = effectiveStepResolver(flows);
10
+ validateRoleReferences(input.skillRoles, flows, resolve, input.manifestPath, errors);
11
+ const owners = seedSkillOwners(input.skillRoles, resolve);
12
+ validateActions(input.actions, input.skillRoles, flows, resolve, owners, input.manifest, input.manifestPath, errors);
13
+ validateOwnerCardinality(flows, owners, input.manifestPath, errors);
14
+ return errors;
15
+ }
16
+ function validateDeclaredSkills(manifest, roles, manifestPath, errors) {
17
+ const declared = new Set(Array.isArray(manifest.skills) ? manifest.skills.flatMap((entry) => typeof entry === "object" && entry !== null && typeof entry.id === "string" ? [entry.id] : []) : []);
18
+ const bound = new Set(roles.map((entry) => entry.skill_id));
19
+ for (const id of declared)
20
+ if (!bound.has(id))
21
+ errors.push(`${manifestPath}: skill_roles is missing declared skill '${id}'`);
22
+ for (const id of bound)
23
+ if (!declared.has(id))
24
+ errors.push(`${manifestPath}: skill_roles references undeclared skill '${id}'`);
25
+ }
26
+ function loadFlowMetadata(kitDir, manifest, manifestPath, errors) {
27
+ const flows = new Map();
28
+ if (!Array.isArray(manifest.flows))
29
+ return flows;
30
+ for (const entry of manifest.flows) {
31
+ if (typeof entry !== "object" || entry === null)
32
+ continue;
33
+ const flow = entry;
34
+ if (typeof flow.id !== "string" || typeof flow.path !== "string")
35
+ continue;
36
+ const safe = readSafeFlowDefinition(kitDir, flow.path);
37
+ if (!safe.definition) {
38
+ errors.push(`${manifestPath}: flows '${flow.id}' ${safe.error}`);
39
+ continue;
40
+ }
41
+ const definition = safe.definition;
42
+ const steps = Array.isArray(definition.steps) ? definition.steps.filter(isRecord) : [];
43
+ const stepIds = new Set(steps.flatMap((step) => typeof step.id === "string" ? [step.id] : []));
44
+ const usesFlowByStep = new Map(steps.flatMap((step) => typeof step.id === "string" && typeof step.uses_flow === "string" ? [[step.id, step.uses_flow]] : []));
45
+ flows.set(flow.id, { steps: stepIds, usesFlowByStep, expectationsByStep: expectationsByStep(definition), exports: new Set(Array.isArray(definition.exports) ? definition.exports.filter((value) => typeof value === "string" && value.length > 0) : []) });
46
+ }
47
+ return flows;
48
+ }
49
+ function expectationsByStep(definition) {
50
+ const result = new Map();
51
+ if (!isRecord(definition.gates))
52
+ return result;
53
+ for (const gate of Object.values(definition.gates)) {
54
+ if (!isRecord(gate) || typeof gate.step !== "string" || !Array.isArray(gate.expects))
55
+ continue;
56
+ const expectations = result.get(gate.step) ?? new Map();
57
+ for (const value of gate.expects) {
58
+ if (!isRecord(value) || typeof value.id !== "string")
59
+ continue;
60
+ const exportKeys = new Set([value.id]);
61
+ if (isRecord(value.bundle_claim) && typeof value.bundle_claim.claimType === "string")
62
+ exportKeys.add(value.bundle_claim.claimType);
63
+ expectations.set(value.id, { id: value.id, exportKeys });
64
+ }
65
+ result.set(gate.step, expectations);
66
+ }
67
+ return result;
68
+ }
69
+ function effectiveStepResolver(flows) {
70
+ const resolve = (flowId, stepId, seen = new Set()) => {
71
+ const cycleKey = `${flowId}\0${stepId}`;
72
+ if (seen.has(cycleKey))
73
+ return undefined;
74
+ seen.add(cycleKey);
75
+ const flow = flows.get(flowId);
76
+ if (!flow)
77
+ return undefined;
78
+ const direct = flow.expectationsByStep.get(stepId);
79
+ if (direct)
80
+ return { sourceFlowId: flowId, stepId, expectations: direct, flowIds: new Set([flowId]) };
81
+ const childFlowId = flow.usesFlowByStep.get(stepId);
82
+ if (!childFlowId)
83
+ return { sourceFlowId: flowId, stepId, expectations: new Map(), flowIds: new Set([flowId]) };
84
+ const child = resolve(childFlowId, stepId, seen);
85
+ const childFlow = flows.get(childFlowId);
86
+ if (!child || !childFlow || [...child.expectations.values()].some((expectation) => ![...expectation.exportKeys].some((key) => childFlow.exports.has(key))))
87
+ return undefined;
88
+ child.flowIds.add(flowId);
89
+ return child;
90
+ };
91
+ return resolve;
92
+ }
93
+ function validateRoleReferences(roles, flows, resolve, manifestPath, errors) {
94
+ for (const row of roles) {
95
+ if (!row.flow_id)
96
+ continue;
97
+ const flow = flows.get(row.flow_id);
98
+ if (!flow) {
99
+ errors.push(`${manifestPath}: skill_roles '${row.skill_id}' references unknown flow '${row.flow_id}'`);
100
+ continue;
101
+ }
102
+ for (const stepId of row.step_ids)
103
+ if (!flow.steps.has(stepId))
104
+ errors.push(`${manifestPath}: skill_roles '${row.skill_id}' references unknown step '${row.flow_id}/${stepId}'`);
105
+ const allowed = new Set(row.step_ids.flatMap((stepId) => [...(resolve(row.flow_id, stepId)?.expectations.keys() ?? [])]));
106
+ for (const expectationId of row.expectation_ids)
107
+ if (!allowed.has(expectationId))
108
+ errors.push(`${manifestPath}: skill_roles '${row.skill_id}' expectation '${expectationId}' is not owned by its bound step(s)`);
109
+ }
110
+ }
111
+ function seedSkillOwners(roles, resolve) {
112
+ const owners = new Map();
113
+ for (const row of roles) {
114
+ if (row.role !== "step" || !row.flow_id)
115
+ continue;
116
+ for (const stepId of row.step_ids) {
117
+ const effective = resolve(row.flow_id, stepId);
118
+ if (!effective)
119
+ continue;
120
+ for (const expectationId of row.expectation_ids)
121
+ if (effective.expectations.has(expectationId)) {
122
+ const key = `${effective.sourceFlowId}\0${effective.stepId}\0${expectationId}`;
123
+ owners.set(key, [...(owners.get(key) ?? []), `skill:${row.skill_id}`]);
124
+ }
125
+ }
126
+ }
127
+ return owners;
128
+ }
129
+ function validateActions(actions, roles, flows, resolve, owners, manifest, manifestPath, errors) {
130
+ const roleByShortId = new Map(roles.map((entry) => [entry.skill_id.replace(`${String(manifest.id)}.`, ""), entry]));
131
+ for (const action of actions) {
132
+ const flow = flows.get(action.flow_id);
133
+ if (!flow || !flow.steps.has(action.step_id)) {
134
+ errors.push(`${manifestPath}: flow_step_actions '${action.flow_id}/${action.step_id}' references an unknown flow step`);
135
+ continue;
136
+ }
137
+ const effective = resolve(action.flow_id, action.step_id);
138
+ if (!effective) {
139
+ errors.push(`${manifestPath}: flow_step_actions '${action.flow_id}/${action.step_id}' cannot resolve its composed Flow step`);
140
+ continue;
141
+ }
142
+ for (const id of action.expectation_ids)
143
+ if (!effective.expectations.has(id))
144
+ errors.push(`${manifestPath}: flow_step_actions '${action.flow_id}/${action.step_id}' operation expectation '${id}' is not owned by its resolved Flow step`);
145
+ if (!sameStringSet(action.expectation_ids, [...effective.expectations.keys()]))
146
+ errors.push(`${manifestPath}: flow_step_actions '${action.flow_id}/${action.step_id}' expectation_ids must exactly equal its resolved Flow expectation set`);
147
+ for (const skill of action.skills) {
148
+ const row = roleByShortId.get(skill);
149
+ if (!row || row.role !== "step" || !row.flow_id || !effective.flowIds.has(row.flow_id) || !row.step_ids.includes(action.step_id))
150
+ errors.push(`${manifestPath}: flow_step_actions '${action.flow_id}/${action.step_id}' skill '${skill}' must match one step-role binding`);
151
+ }
152
+ for (const expectationId of effective.expectations.keys()) {
153
+ const binding = action.expectation_bindings.find((entry) => entry.expectation_id === expectationId);
154
+ if (binding?.interface !== "operation")
155
+ continue;
156
+ const key = `${effective.sourceFlowId}\0${effective.stepId}\0${expectationId}`;
157
+ owners.set(key, [...(owners.get(key) ?? []), `operation:${action.flow_id}/${action.step_id}`]);
158
+ }
159
+ }
160
+ }
161
+ function validateOwnerCardinality(flows, owners, manifestPath, errors) {
162
+ for (const [flowId, flow] of flows)
163
+ for (const [stepId, expectations] of flow.expectationsByStep)
164
+ for (const expectationId of expectations.keys()) {
165
+ const found = owners.get(`${flowId}\0${stepId}\0${expectationId}`) ?? [];
166
+ if (found.length !== 1)
167
+ errors.push(`${manifestPath}: flow expectation '${flowId}/${stepId}/${expectationId}' must have exactly one producer owner; found ${found.length}`);
168
+ }
169
+ }
170
+ function readSafeFlowDefinition(kitDir, relativePath) {
171
+ const root = path.resolve(kitDir);
172
+ if (path.isAbsolute(relativePath))
173
+ return { error: "path must be relative" };
174
+ const lexical = path.resolve(root, relativePath);
175
+ if (lexical === root || !lexical.startsWith(`${root}${path.sep}`))
176
+ return { error: "path must stay inside the kit directory" };
177
+ let fd;
178
+ try {
179
+ const realRoot = fs.realpathSync(root);
180
+ const initial = lstatSafePath(realRoot, relativePath);
181
+ if (!initial.file || !initial.stat)
182
+ return { error: initial.error };
183
+ if (!initial.stat.isFile())
184
+ return { error: "path must reference a regular file" };
185
+ if (initial.stat.size > MAX_FLOW_DEFINITION_BYTES)
186
+ return { error: `file exceeds ${MAX_FLOW_DEFINITION_BYTES} bytes` };
187
+ if (typeof fs.constants.O_NOFOLLOW !== "number")
188
+ return { error: "O_NOFOLLOW is unavailable" };
189
+ fd = fs.openSync(initial.file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
190
+ const opened = fs.fstatSync(fd);
191
+ if (!opened.isFile() || !sameIdentity(initial.stat, opened))
192
+ return { error: "flow definition identity changed while opening" };
193
+ const verified = lstatSafePath(realRoot, relativePath);
194
+ if (!verified.stat || !sameIdentity(opened, verified.stat))
195
+ return { error: "flow definition identity changed while opening" };
196
+ const bytes = Buffer.alloc(opened.size);
197
+ let offset = 0;
198
+ while (offset < bytes.length) {
199
+ const read = fs.readSync(fd, bytes, offset, bytes.length - offset, offset);
200
+ if (read === 0)
201
+ break;
202
+ offset += read;
203
+ }
204
+ const after = fs.fstatSync(fd);
205
+ if (offset !== opened.size || after.size !== opened.size || !sameIdentity(opened, after))
206
+ return { error: "file changed while being read" };
207
+ return { definition: JSON.parse(bytes.toString("utf8")) };
208
+ }
209
+ catch (error) {
210
+ if (error.code === "ELOOP")
211
+ return { error: "path must not reference a symbolic link" };
212
+ return { error: `path is not readable: ${error.message}` };
213
+ }
214
+ finally {
215
+ if (fd !== undefined)
216
+ fs.closeSync(fd);
217
+ }
218
+ }
219
+ function lstatSafePath(root, relativePath) {
220
+ const parts = relativePath.split(path.sep);
221
+ let current = root;
222
+ for (const [index, part] of parts.entries()) {
223
+ if (!part || part === "." || part === "..")
224
+ return { error: "path must stay inside the kit directory" };
225
+ current = path.join(current, part);
226
+ const stat = fs.lstatSync(current);
227
+ if (stat.isSymbolicLink())
228
+ return { error: "path must not traverse a symbolic link" };
229
+ if (index < parts.length - 1 && !stat.isDirectory())
230
+ return { error: "path component must be a directory" };
231
+ if (index === parts.length - 1)
232
+ return { file: current, stat };
233
+ }
234
+ return { error: "path must reference a regular file" };
235
+ }
236
+ function sameIdentity(left, right) { return left.dev === right.dev && left.ino === right.ino; }
237
+ function isRecord(value) { return typeof value === "object" && value !== null && !Array.isArray(value); }
@@ -1,3 +1,5 @@
1
+ export { isObservableBuilderArtifactRef, isSafeBuilderArtifactRef, parseKitFlowStepActions } from "./action-metadata.js";
2
+ export type { KitFlowStepActionEntry, KitFlowStepArtifactBinding, KitFlowStepExpectationBinding } from "./action-metadata.js";
1
3
  export interface KitDependencyEntry {
2
4
  kit_id: string;
3
5
  reason?: string;
@@ -23,16 +25,6 @@ export interface KitHookInfluenceExpectationEntry {
23
25
  must_include_guidance: string[];
24
26
  must_include_actions: string[];
25
27
  }
26
- export interface KitFlowStepActionEntry {
27
- flow_id: string;
28
- step_id: string;
29
- skills: string[];
30
- operations: string[];
31
- /** Gate expectations produced by this action's explicit operation(s). */
32
- expectation_ids?: string[];
33
- /** Durable artifacts produced by an operation-only action. */
34
- artifacts: string[];
35
- }
36
28
  export type KitSkillRole = "entrypoint" | "profile" | "step" | "shared-primitive" | "extension";
37
29
  export interface KitSkillRoleEntry {
38
30
  skill_id: string;
@@ -69,10 +61,6 @@ export declare function parseKitHookInfluenceExpectations(manifest: Record<strin
69
61
  entries: KitHookInfluenceExpectationEntry[];
70
62
  errors: string[];
71
63
  };
72
- export declare function parseKitFlowStepActions(manifest: Record<string, unknown>, manifestPath: string): {
73
- entries: KitFlowStepActionEntry[];
74
- errors: string[];
75
- };
76
64
  export declare function parseKitSkillRoles(manifest: Record<string, unknown>, manifestPath: string): {
77
65
  entries: KitSkillRoleEntry[];
78
66
  errors: string[];