@kontourai/flow-agents 3.3.0 → 3.4.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 (256) hide show
  1. package/.github/workflows/add-to-project.yml +15 -0
  2. package/.github/workflows/ci.yml +161 -0
  3. package/CHANGELOG.md +48 -0
  4. package/CONTEXT.md +5 -1
  5. package/README.md +19 -8
  6. package/build/src/builder-flow-run-adapter.d.ts +80 -0
  7. package/build/src/builder-flow-run-adapter.js +241 -0
  8. package/build/src/builder-flow-runtime.d.ts +16 -0
  9. package/build/src/builder-flow-runtime.js +290 -0
  10. package/build/src/cli/builder-run.d.ts +1 -0
  11. package/build/src/cli/builder-run.js +27 -0
  12. package/build/src/cli/effective-backlog-settings.js +70 -2
  13. package/build/src/cli/init.d.ts +34 -0
  14. package/build/src/cli/init.js +341 -61
  15. package/build/src/cli/kit.js +55 -12
  16. package/build/src/cli/pull-work-provider.js +346 -5
  17. package/build/src/cli/skill-drift-check.d.ts +1 -0
  18. package/build/src/cli/skill-drift-check.js +165 -0
  19. package/build/src/cli/telemetry-doctor.d.ts +37 -0
  20. package/build/src/cli/telemetry-doctor.js +53 -6
  21. package/build/src/cli/validate-hook-influence.js +37 -7
  22. package/build/src/cli/workflow-sidecar.d.ts +93 -8
  23. package/build/src/cli/workflow-sidecar.js +1175 -158
  24. package/build/src/cli.js +5 -0
  25. package/build/src/flow-kit/validate.d.ts +54 -34
  26. package/build/src/flow-kit/validate.js +237 -26
  27. package/build/src/index.d.ts +2 -0
  28. package/build/src/index.js +1 -0
  29. package/build/src/lib/console-connect-options.d.ts +97 -0
  30. package/build/src/lib/console-connect-options.js +199 -0
  31. package/build/src/lib/console-telemetry-validate.d.ts +49 -0
  32. package/build/src/lib/console-telemetry-validate.js +91 -0
  33. package/build/src/lib/flow-resolver.d.ts +56 -3
  34. package/build/src/lib/flow-resolver.js +151 -11
  35. package/build/src/lib/fs.d.ts +17 -0
  36. package/build/src/lib/fs.js +172 -0
  37. package/build/src/lib/local-artifact-root.d.ts +44 -1
  38. package/build/src/lib/local-artifact-root.js +131 -3
  39. package/build/src/runtime-adapters.d.ts +39 -3
  40. package/build/src/runtime-adapters.js +77 -31
  41. package/build/src/tools/build-universal-bundles.js +40 -2
  42. package/build/src/tools/codex-agent-routing.d.ts +2 -0
  43. package/build/src/tools/codex-agent-routing.js +49 -0
  44. package/build/src/tools/generate-context-map.js +1 -0
  45. package/build/src/tools/validate-source-tree.js +27 -1
  46. package/context/scripts/hooks/lib/kit-catalog.js +235 -0
  47. package/context/scripts/hooks/lib/runnable-command.js +177 -0
  48. package/context/scripts/hooks/stop-goal-fit.js +278 -48
  49. package/context/scripts/hooks/workflow-steering.js +121 -21
  50. package/context/scripts/package.json +3 -0
  51. package/context/scripts/telemetry/install-console-config.sh +25 -4
  52. package/context/scripts/telemetry/lib/config.sh +102 -12
  53. package/context/scripts/telemetry/lib/pricing.sh +50 -0
  54. package/context/scripts/telemetry/lib/session.sh +3 -0
  55. package/context/scripts/telemetry/lib/transport.sh +87 -0
  56. package/context/scripts/telemetry/lib/usage.sh +205 -4
  57. package/context/scripts/telemetry/telemetry.conf +6 -0
  58. package/context/scripts/telemetry/telemetry.sh +48 -0
  59. package/context/settings/workspace-backlog-provider-settings.example.json +48 -0
  60. package/docs/agent-usage-feedback-loop.md +35 -0
  61. package/docs/architecture-engine-and-kits.md +110 -0
  62. package/docs/context-map.md +2 -0
  63. package/docs/decisions/embeddable-engine.md +152 -0
  64. package/docs/decisions/index.md +3 -1
  65. package/docs/decisions/trust-ledger-retention.md +88 -0
  66. package/docs/decisions/workflow-enforcement.md +31 -9
  67. package/docs/fixture-ownership.md +3 -0
  68. package/docs/implementing-trust-reconciliation.md +129 -0
  69. package/docs/index.md +19 -9
  70. package/docs/integrations/flow-agents-console.md +167 -0
  71. package/docs/kit-authoring-guide.md +52 -21
  72. package/docs/spec/builder-flow-runtime.md +80 -0
  73. package/docs/spec/runtime-hook-surface.md +45 -1
  74. package/docs/specs/economics-record-contract.md +270 -0
  75. package/docs/specs/harness-capability-matrix.md +74 -0
  76. package/docs/specs/learning-review-proposals-contract.md +340 -0
  77. package/docs/specs/routing-efficiency-review.md +59 -0
  78. package/docs/verifiable-trust.md +74 -25
  79. package/docs/workflow-usage-guide.md +10 -0
  80. package/evals/acceptance/prove-capture-teeth.sh +132 -0
  81. package/evals/ci/antigaming-suite.sh +1 -0
  82. package/evals/ci/run-baseline.sh +72 -4
  83. package/evals/fixtures/economics/acceptance.json +12 -0
  84. package/evals/fixtures/economics/agents/tool-worker-1/events.jsonl +2 -0
  85. package/evals/fixtures/economics/agents/tool-worker-2/events.jsonl +2 -0
  86. package/evals/fixtures/economics/agents/tool-worker-3/events.jsonl +2 -0
  87. package/evals/fixtures/economics/agents/tool-worker-4/events.jsonl +1 -0
  88. package/evals/fixtures/economics/agents/tool-worker-5/events.jsonl +2 -0
  89. package/evals/fixtures/economics/critique.json +22 -0
  90. package/evals/fixtures/economics/expected-record.json +71 -0
  91. package/evals/fixtures/economics/session-usage-event.json +1 -0
  92. package/evals/fixtures/economics/state.json +11 -0
  93. package/evals/fixtures/economics/transcript.jsonl +3 -0
  94. package/evals/fixtures/hook-influence/cases.json +7 -7
  95. package/evals/fixtures/learning-review-proposals/balanced/economics.jsonl +6 -0
  96. package/evals/fixtures/learning-review-proposals/effect-follow-up/economics.jsonl +5 -0
  97. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-1/trust.bundle +21 -0
  98. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-2/trust.bundle +21 -0
  99. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-3/trust.bundle +21 -0
  100. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-4/trust.bundle +21 -0
  101. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-5/trust.bundle +21 -0
  102. package/evals/fixtures/learning-review-proposals/pattern-present/economics.jsonl +6 -0
  103. package/evals/fixtures/learning-review-proposals/pattern-present/expected-aggregates.json +30 -0
  104. package/evals/fixtures/learning-review-proposals/pattern-present/expected-aggregates.md +66 -0
  105. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-1/gate-review.inquiries.json +26 -0
  106. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-1/trust.bundle +21 -0
  107. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-2/gate-review.inquiries.json +26 -0
  108. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-2/trust.bundle +21 -0
  109. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-3/gate-review.inquiries.json +26 -0
  110. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-3/trust.bundle +21 -0
  111. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-4/gate-review.inquiries.json +26 -0
  112. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-4/trust.bundle +21 -0
  113. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-5/trust.bundle +21 -0
  114. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-6/trust.bundle +21 -0
  115. package/evals/fixtures/learning-review-proposals/repeat-window/economics.jsonl +6 -0
  116. package/evals/fixtures/learning-review-proposals/under-threshold/economics.jsonl +3 -0
  117. package/evals/fixtures/telemetry/usage-transcript-sample.jsonl +4 -0
  118. package/evals/fixtures/trust-reconcile-exploits/mcp-degrade.json +42 -0
  119. package/evals/integration/test_builder_entry_enforcement.sh +241 -0
  120. package/evals/integration/test_builder_step_producers.sh +18 -10
  121. package/evals/integration/test_bundle_install.sh +172 -0
  122. package/evals/integration/test_console_tenant_isolation.sh +167 -0
  123. package/evals/integration/test_critique_supersession_roundtrip.sh +4 -1
  124. package/evals/integration/test_dual_emit_flow_step.sh +10 -4
  125. package/evals/integration/test_economics_record.sh +674 -0
  126. package/evals/integration/test_effective_backlog_settings.sh +1 -1
  127. package/evals/integration/test_evidence_capture_hook.sh +17 -2
  128. package/evals/integration/test_exemption_usage_review.sh +198 -0
  129. package/evals/integration/test_fixture_retirement_audit.sh +2 -2
  130. package/evals/integration/test_flow_kit_install_git.sh +83 -0
  131. package/evals/integration/test_flowdef_session_activation.sh +0 -1
  132. package/evals/integration/test_flowdef_session_history_preservation.sh +13 -3
  133. package/evals/integration/test_gate_lockdown.sh +7 -0
  134. package/evals/integration/test_gate_review_inquiry_records.sh +9 -1
  135. package/evals/integration/test_goal_fit_hook.sh +2031 -0
  136. package/evals/integration/test_hook_category_behaviors.sh +8 -1
  137. package/evals/integration/test_hook_influence_cases.sh +25 -1
  138. package/evals/integration/test_install_merge.sh +227 -2
  139. package/evals/integration/test_kit_conformance_levels.sh +6 -6
  140. package/evals/integration/test_learning_review_proposals.sh +329 -0
  141. package/evals/integration/test_liveness_conflict_injection.sh +26 -22
  142. package/evals/integration/test_liveness_console_relay.sh +166 -0
  143. package/evals/integration/test_liveness_heartbeat.sh +17 -17
  144. package/evals/integration/test_liveness_worktree_root.sh +575 -0
  145. package/evals/integration/test_phase_map_and_gate_claim.sh +6 -1
  146. package/evals/integration/test_publish_delivery.sh +331 -1
  147. package/evals/integration/test_pull_work_board.sh +200 -0
  148. package/evals/integration/test_pull_work_provider.sh +1 -1
  149. package/evals/integration/test_record_check.sh +378 -0
  150. package/evals/integration/test_routing_efficiency.sh +71 -0
  151. package/evals/integration/test_runtime_adapter_activation.sh +28 -0
  152. package/evals/integration/test_session_resume_roundtrip.sh +16 -19
  153. package/evals/integration/test_skill_drift_check.sh +870 -0
  154. package/evals/integration/test_telemetry.sh +445 -0
  155. package/evals/integration/test_telemetry_doctor.sh +66 -0
  156. package/evals/integration/test_telemetry_usage_pipeline.sh +228 -0
  157. package/evals/integration/test_trust_reconcile_negatives.sh +30 -13
  158. package/evals/integration/test_trust_reconcile_trailer_diagnostic.sh +247 -0
  159. package/evals/integration/test_usage_cost.sh +61 -0
  160. package/evals/integration/test_workflow_sidecar_writer.sh +1395 -0
  161. package/evals/integration/test_workflow_steering_hook.sh +157 -16
  162. package/evals/integration/test_workspace_settings.sh +176 -0
  163. package/evals/lib/env.sh +26 -0
  164. package/evals/lib/node.sh +8 -0
  165. package/evals/run.sh +29 -0
  166. package/evals/static/test_ci_integration_coverage.sh +115 -0
  167. package/evals/static/test_declared_scope_forms_documented.sh +114 -0
  168. package/evals/static/test_universal_bundles.sh +34 -0
  169. package/evals/static/test_validate_source_kit_asset_scope.sh +259 -0
  170. package/evals/static/test_workflow_skills.sh +1 -1
  171. package/kits/builder/flows/build.flow.json +9 -18
  172. package/kits/builder/flows/publish-learn.flow.json +5 -1
  173. package/kits/builder/kit.json +120 -0
  174. package/kits/builder/skills/deliver/SKILL.md +42 -0
  175. package/kits/builder/skills/evidence-gate/SKILL.md +12 -0
  176. package/kits/builder/skills/execute-plan/SKILL.md +9 -0
  177. package/kits/builder/skills/learning-review/SKILL.md +51 -0
  178. package/kits/builder/skills/plan-work/SKILL.md +17 -20
  179. package/kits/builder/skills/pull-work/SKILL.md +21 -0
  180. package/kits/builder/skills/release-readiness/SKILL.md +12 -0
  181. package/kits/knowledge/kit.json +9 -0
  182. package/kits/veritas-governance/docs/README.md +35 -7
  183. package/kits/veritas-governance/fixtures/exemption-review/mixed-fresh-stale.DECLARED.json +14 -0
  184. package/kits/veritas-governance/kit.json +14 -0
  185. package/kits/veritas-governance/skills/exemption-usage-review/SKILL.md +128 -0
  186. package/kits/veritas-governance/skills/exemption-usage-review/review-exemptions.mjs +231 -0
  187. package/package.json +2 -2
  188. package/packaging/manifest.json +29 -0
  189. package/schemas/backlog-provider-settings.schema.json +13 -0
  190. package/schemas/workflow-state.schema.json +44 -0
  191. package/scripts/README.md +4 -0
  192. package/scripts/check-content-boundary.cjs +8 -1
  193. package/scripts/ci/trust-reconcile.js +136 -0
  194. package/scripts/hooks/codex-hook-adapter.js +77 -2
  195. package/scripts/hooks/evidence-capture.js +38 -5
  196. package/scripts/hooks/lib/codex-exit-code.js +316 -0
  197. package/scripts/hooks/lib/kit-catalog.js +235 -0
  198. package/scripts/hooks/lib/liveness-write.js +28 -1
  199. package/scripts/hooks/lib/local-artifact-paths.js +97 -1
  200. package/scripts/hooks/lib/runnable-command.js +177 -0
  201. package/scripts/hooks/lib/skill-drift.js +350 -0
  202. package/scripts/hooks/stop-goal-fit.js +278 -48
  203. package/scripts/hooks/workflow-steering.js +121 -21
  204. package/scripts/install-codex-home.sh +97 -47
  205. package/scripts/install-merge.js +72 -14
  206. package/scripts/install-owned-files.js +178 -0
  207. package/scripts/liveness/relay.sh +84 -0
  208. package/scripts/telemetry/economics-record.schema.json +145 -0
  209. package/scripts/telemetry/economics-record.sh +331 -0
  210. package/scripts/telemetry/install-console-config.sh +25 -4
  211. package/scripts/telemetry/learning-review-decide.sh +124 -0
  212. package/scripts/telemetry/learning-review-proposals.schema.json +161 -0
  213. package/scripts/telemetry/learning-review-proposals.sh +484 -0
  214. package/scripts/telemetry/lib/config.sh +102 -12
  215. package/scripts/telemetry/lib/pricing.sh +14 -6
  216. package/scripts/telemetry/lib/session.sh +3 -0
  217. package/scripts/telemetry/lib/transport.sh +133 -15
  218. package/scripts/telemetry/lib/usage.sh +121 -28
  219. package/scripts/telemetry/routing-efficiency.sh +0 -0
  220. package/scripts/telemetry/telemetry.conf +6 -0
  221. package/scripts/telemetry/telemetry.sh +48 -0
  222. package/src/builder-flow-run-adapter.ts +357 -0
  223. package/src/builder-flow-runtime.ts +348 -0
  224. package/src/cli/builder-flow-run-adapter.test.mjs +495 -0
  225. package/src/cli/builder-flow-runtime.test.mjs +213 -0
  226. package/src/cli/builder-run.ts +28 -0
  227. package/src/cli/codex-agent-routing.test.mjs +44 -0
  228. package/src/cli/codex-exit-code.test.mjs +207 -0
  229. package/src/cli/console-connect-options.test.mjs +329 -0
  230. package/src/cli/console-telemetry-validate.test.mjs +157 -0
  231. package/src/cli/effective-backlog-settings.ts +68 -2
  232. package/src/cli/flow-resolver-composition.test.mjs +101 -0
  233. package/src/cli/init.test.mjs +161 -0
  234. package/src/cli/init.ts +407 -62
  235. package/src/cli/kit-metadata-security.test.mjs +443 -0
  236. package/src/cli/kit.ts +50 -12
  237. package/src/cli/pull-work-provider.ts +377 -3
  238. package/src/cli/sidecar-pure-helpers.test.mjs +64 -0
  239. package/src/cli/skill-drift-check.ts +196 -0
  240. package/src/cli/telemetry-doctor.test.mjs +53 -0
  241. package/src/cli/telemetry-doctor.ts +50 -7
  242. package/src/cli/validate-hook-influence.ts +37 -6
  243. package/src/cli/workflow-sidecar.ts +1150 -151
  244. package/src/cli.ts +5 -0
  245. package/src/flow-kit/validate.ts +277 -38
  246. package/src/index.ts +19 -0
  247. package/src/lib/console-connect-options.ts +261 -0
  248. package/src/lib/console-telemetry-validate.ts +88 -0
  249. package/src/lib/flow-resolver.ts +153 -10
  250. package/src/lib/fs.ts +160 -0
  251. package/src/lib/local-artifact-root.ts +129 -3
  252. package/src/runtime-adapters.ts +113 -33
  253. package/src/tools/build-universal-bundles.ts +36 -2
  254. package/src/tools/codex-agent-routing.ts +48 -0
  255. package/src/tools/generate-context-map.ts +1 -0
  256. package/src/tools/validate-source-tree.ts +26 -1
@@ -318,6 +318,18 @@ add_stop_data_and_emit_usage() {
318
318
  transcript_usage=$(usage_parse_transcript "$transcript_path")
319
319
  [[ -z "$transcript_usage" ]] && transcript_usage='null'
320
320
 
321
+ # Prefer the transcript-derived model (runtime-agnostic — works for any
322
+ # runtime that exposes a transcript) over the kiro-only ~/.kiro/agents
323
+ # lookup above, which never resolves for non-kiro agent names (e.g.
324
+ # Claude Code's fixed "dev" hook arg) and falls through to "unknown".
325
+ # Falls back to usage_get_model's kiro result when no transcript usage
326
+ # is available (kiro-cli path is unchanged: transcript_usage stays null).
327
+ if [[ "$transcript_usage" != "null" ]]; then
328
+ local transcript_model
329
+ transcript_model=$(usage_model_from_transcript_usage "$transcript_usage")
330
+ [[ -n "$transcript_model" ]] && model="$transcript_model"
331
+ fi
332
+
321
333
  local usage_event
322
334
  usage_event=$(echo "$event" | jq -c \
323
335
  --arg m "$model" \
@@ -340,6 +352,42 @@ add_stop_data_and_emit_usage() {
340
352
  })
341
353
  }')
342
354
  transport_emit "$usage_event"
355
+
356
+ # Per-run kit-economics record (#349, console ADR 0003). Best-effort + DETACHED so it can never
357
+ # alter existing telemetry timing or fail the stop hook: assemble one kontour.console.economics
358
+ # fact from this session.usage event + the run's review sidecars, write it local-first, then
359
+ # opt-in relay it. Resolve the sidecar paths from the run cwd's active-session pointer; the
360
+ # emitter defaults every field cleanly when a sidecar is absent.
361
+ local econ_script="${TELEMETRY_DIR}/economics-record.sh"
362
+ if [[ -f "$econ_script" ]]; then
363
+ local econ_cwd econ_slug econ_state econ_acceptance econ_critique
364
+ econ_cwd=$(echo "$usage_event" | jq -r '.context.cwd // ""' 2>/dev/null)
365
+ [[ -z "$econ_cwd" || ! -d "$econ_cwd" ]] && econ_cwd="$PWD"
366
+ # Active slug from the canonical current pointer first, falling back to the legacy pointer.
367
+ econ_slug=""
368
+ if [[ -f "$econ_cwd/.kontourai/flow-agents/current.json" ]]; then
369
+ econ_slug=$(jq -r '.active_slug // .artifact_dir // empty' "$econ_cwd/.kontourai/flow-agents/current.json" 2>/dev/null)
370
+ elif [[ -f "$econ_cwd/.flow-agents/current.json" ]]; then
371
+ econ_slug=$(jq -r '.active_slug // .artifact_dir // empty' "$econ_cwd/.flow-agents/current.json" 2>/dev/null)
372
+ fi
373
+ econ_state="" econ_acceptance="" econ_critique="" econ_agents_dir=""
374
+ if [[ -n "$econ_slug" ]]; then
375
+ # state.json under .kontourai/flow-agents/<slug>/ (fallback .flow-agents/<slug>/); the run's
376
+ # per-agent event logs live alongside it in <slug>/agents/ (#415 delegations[] source).
377
+ for d in "$econ_cwd/.kontourai/flow-agents/$econ_slug" "$econ_cwd/.flow-agents/$econ_slug"; do
378
+ [[ -f "$d/state.json" ]] && { econ_state="$d/state.json"; [[ -d "$d/agents" ]] && econ_agents_dir="$d/agents"; break; }
379
+ done
380
+ [[ -f "$econ_cwd/.flow-agents/$econ_slug/acceptance.json" ]] && econ_acceptance="$econ_cwd/.flow-agents/$econ_slug/acceptance.json"
381
+ [[ -f "$econ_cwd/.flow-agents/$econ_slug/critique.json" ]] && econ_critique="$econ_cwd/.flow-agents/$econ_slug/critique.json"
382
+ fi
383
+ local econ_args=("$usage_event")
384
+ [[ -n "$econ_state" ]] && econ_args+=(--state "$econ_state")
385
+ [[ -n "$econ_acceptance" ]] && econ_args+=(--acceptance "$econ_acceptance")
386
+ [[ -n "$econ_critique" ]] && econ_args+=(--critique "$econ_critique")
387
+ [[ -n "$econ_agents_dir" ]] && econ_args+=(--agents-dir "$econ_agents_dir")
388
+ (bash "$econ_script" "${econ_args[@]}") </dev/null >/dev/null 2>&1 &
389
+ disown 2>/dev/null || true
390
+ fi
343
391
  fi
344
392
 
345
393
  echo "$event"
@@ -0,0 +1,357 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { isDeepStrictEqual } from "node:util";
6
+ import {
7
+ attachEvidence,
8
+ evaluateRun,
9
+ expectationsForGate,
10
+ loadRun,
11
+ normalizeTrustBundle,
12
+ openGates,
13
+ readJson,
14
+ startRun,
15
+ validateDefinition,
16
+ type FlowEvidenceEntry,
17
+ type FlowRunState,
18
+ type GateOutcome,
19
+ type JsonObject,
20
+ } from "@kontourai/flow";
21
+ import { resolveEffectiveFlowDefinition } from "./lib/flow-resolver.js";
22
+
23
+ export const BUILDER_BUILD_FLOW_ID = "builder.build";
24
+ export const BUILDER_BUILD_FLOW_RELATIVE_PATH = "kits/builder/flows/build.flow.json";
25
+
26
+ export interface BuilderBuildTrustBundleEvidenceInput {
27
+ gate: string;
28
+ /**
29
+ * Trusted local evidence path interpreted by Flow relative to `cwd`.
30
+ * Callers must not pass raw user-controlled paths to this local runtime API.
31
+ */
32
+ file: string;
33
+ status?: "passed" | "failed";
34
+ producer?: string;
35
+ authorityTrace?: string;
36
+ routeReason?: string;
37
+ expectationIds?: string[];
38
+ supersede?: string | string[];
39
+ classifier?: JsonObject;
40
+ diagnostics?: JsonObject;
41
+ analytics?: JsonObject;
42
+ }
43
+
44
+ export interface StartBuilderBuildRunInput {
45
+ subject: string;
46
+ params?: JsonObject;
47
+ /**
48
+ * Trusted local runtime root. Flow owns persistence and interprets attached
49
+ * evidence files relative to this directory.
50
+ */
51
+ cwd?: string;
52
+ runId?: string;
53
+ }
54
+
55
+ export interface EvaluateBuilderBuildRunInput {
56
+ runId: string;
57
+ /**
58
+ * Trusted local runtime root. Flow owns persistence and interprets attached
59
+ * evidence files relative to this directory.
60
+ */
61
+ cwd?: string;
62
+ evidence?: BuilderBuildTrustBundleEvidenceInput;
63
+ }
64
+
65
+ export interface LoadBuilderBuildRunInput {
66
+ runId: string;
67
+ cwd?: string;
68
+ }
69
+
70
+ export interface BuilderBuildRunResult {
71
+ definitionId: typeof BUILDER_BUILD_FLOW_ID;
72
+ definitionVersion: string;
73
+ runId: string;
74
+ dir: string;
75
+ state: FlowRunState;
76
+ attachedEvidence: FlowEvidenceEntry[];
77
+ outcomes: GateOutcome[];
78
+ manifest: JsonObject;
79
+ freshnessTransitions: JsonObject[];
80
+ }
81
+
82
+ export type BuilderBuildRunIdentityMismatch = "definition-id" | "definition-version" | "definition-content";
83
+
84
+ export class BuilderBuildRunInputError extends Error {
85
+ readonly code = "BUILDER_BUILD_RUN_INVALID_INPUT" as const;
86
+ readonly field: string;
87
+
88
+ constructor(field: string, reason: string) {
89
+ super(`invalid Builder build run input for ${field}: ${reason}`);
90
+ this.name = "BuilderBuildRunInputError";
91
+ this.field = field;
92
+ }
93
+ }
94
+
95
+ export class BuilderBuildRunIdentityError extends Error {
96
+ readonly code = "BUILDER_BUILD_RUN_IDENTITY_MISMATCH" as const;
97
+ readonly expectedDefinitionId: string;
98
+ readonly expectedDefinitionVersion: string;
99
+ readonly actualDefinitionId: string;
100
+ readonly actualDefinitionVersion: string;
101
+ readonly mismatch: BuilderBuildRunIdentityMismatch;
102
+ readonly runId: string;
103
+
104
+ constructor(
105
+ runId: string,
106
+ expectedDefinition: { id: string; version: string },
107
+ actualDefinition: { id: string; version: string },
108
+ mismatch: BuilderBuildRunIdentityMismatch,
109
+ ) {
110
+ super(`expected canonical ${expectedDefinition.id}@${expectedDefinition.version} run, received ${actualDefinition.id}@${actualDefinition.version} for ${runId}`);
111
+ this.name = "BuilderBuildRunIdentityError";
112
+ this.expectedDefinitionId = expectedDefinition.id;
113
+ this.expectedDefinitionVersion = expectedDefinition.version;
114
+ this.actualDefinitionId = actualDefinition.id;
115
+ this.actualDefinitionVersion = actualDefinition.version;
116
+ this.mismatch = mismatch;
117
+ this.runId = runId;
118
+ }
119
+ }
120
+
121
+ export function resolveBuilderBuildFlowDefinitionPath(startDir = moduleDirectory()): string {
122
+ const root = findPackageRoot(startDir);
123
+ return path.join(root, BUILDER_BUILD_FLOW_RELATIVE_PATH);
124
+ }
125
+
126
+ export async function startBuilderBuildRun(input: StartBuilderBuildRunInput): Promise<BuilderBuildRunResult> {
127
+ assertRuntimeInput(input, ["evidence", "now", "gate"]);
128
+ if (!isNonEmptyString(input.subject)) {
129
+ throw new BuilderBuildRunInputError("subject", "must be a non-empty string");
130
+ }
131
+
132
+ const cwd = input.cwd ?? process.cwd();
133
+ const definitionPath = resolveBuilderBuildFlowDefinitionPath();
134
+ const definition = await loadShippedBuilderBuildDefinition(definitionPath);
135
+ const runtimeDefinitionPath = materializeRuntimeDefinition(cwd, definition);
136
+ const started = await startRun(runtimeDefinitionPath, {
137
+ cwd,
138
+ runId: input.runId,
139
+ params: {
140
+ ...(input.params ?? {}),
141
+ subject: input.subject,
142
+ },
143
+ });
144
+ const run = await loadCanonicalBuilderBuildRun(started.runId, cwd, definition);
145
+
146
+ return resultFromRun(run, started.runId);
147
+ }
148
+
149
+ export async function evaluateBuilderBuildRun(input: EvaluateBuilderBuildRunInput): Promise<BuilderBuildRunResult> {
150
+ assertRuntimeInput(input, ["now", "gate"]);
151
+ if (Array.isArray(input.evidence)) {
152
+ throw new BuilderBuildRunInputError("evidence", "must be zero or one evidence object, not an array");
153
+ }
154
+
155
+ const cwd = input.cwd ?? process.cwd();
156
+ const run = await loadRun(input.runId, cwd);
157
+ const definition = await loadShippedBuilderBuildDefinition(resolveBuilderBuildFlowDefinitionPath());
158
+ assertCanonicalDefinition(input.runId, definition, run.definition);
159
+
160
+ let attachedEvidence: FlowEvidenceEntry[] = [];
161
+ if (input.evidence !== undefined) {
162
+ const evidence = validateEvidenceInput(input.evidence);
163
+ assertCurrentOpenGate(run.definition, run.state, evidence.gate);
164
+ const normalized = normalizeTrustBundle(await readJson(path.resolve(cwd, evidence.file)));
165
+ assertBundleSubjects(normalized.bundle, run.state.subject, openGates(run.definition, run.state)[0]);
166
+ attachedEvidence = [await attachEvidence(input.runId, trustBundleAttachOptions(cwd, evidence))];
167
+ }
168
+
169
+ const evaluated = await evaluateRun(input.runId, { cwd });
170
+ return {
171
+ definitionId: evaluated.definition.id,
172
+ definitionVersion: evaluated.definition.version,
173
+ runId: input.runId,
174
+ dir: evaluated.dir,
175
+ state: evaluated.state,
176
+ attachedEvidence,
177
+ outcomes: evaluated.outcomes,
178
+ manifest: evaluated.manifest,
179
+ freshnessTransitions: evaluated.freshness_transitions,
180
+ };
181
+ }
182
+
183
+ export async function loadBuilderBuildRun(input: LoadBuilderBuildRunInput): Promise<BuilderBuildRunResult> {
184
+ assertRuntimeInput(input, ["evidence", "now", "gate"]);
185
+ const cwd = input.cwd ?? process.cwd();
186
+ const run = await loadRun(input.runId, cwd);
187
+ const definition = await loadShippedBuilderBuildDefinition(resolveBuilderBuildFlowDefinitionPath());
188
+ assertCanonicalDefinition(input.runId, definition, run.definition);
189
+ return resultFromRun(run, input.runId);
190
+ }
191
+
192
+ function resultFromRun(run: Awaited<ReturnType<typeof loadRun>>, runId: string): BuilderBuildRunResult {
193
+ return {
194
+ definitionId: run.definition.id,
195
+ definitionVersion: run.definition.version,
196
+ runId,
197
+ dir: run.dir,
198
+ state: run.state,
199
+ attachedEvidence: [],
200
+ outcomes: [],
201
+ manifest: run.manifest,
202
+ freshnessTransitions: [],
203
+ };
204
+ }
205
+
206
+ async function loadCanonicalBuilderBuildRun(
207
+ runId: string,
208
+ cwd: string,
209
+ definition: { id: string; version: string },
210
+ ): Promise<Awaited<ReturnType<typeof loadRun>>> {
211
+ const run = await loadRun(runId, cwd);
212
+ assertCanonicalDefinition(runId, definition, run.definition);
213
+ return run;
214
+ }
215
+
216
+ async function loadShippedBuilderBuildDefinition(definitionPath: string): Promise<{ id: string; version: string }> {
217
+ const packageRoot = findPackageRoot(path.dirname(definitionPath));
218
+ const effective = resolveEffectiveFlowDefinition(BUILDER_BUILD_FLOW_ID, packageRoot, { allowOverride: false });
219
+ if (!effective) {
220
+ throw new BuilderBuildRunInputError("definition", "could not compile the shipped uses_flow composition");
221
+ }
222
+ const definition = validateDefinition(effective);
223
+ if (definition.id !== BUILDER_BUILD_FLOW_ID) {
224
+ throw new BuilderBuildRunInputError("definition", `expected shipped definition id ${BUILDER_BUILD_FLOW_ID}`);
225
+ }
226
+ return definition;
227
+ }
228
+
229
+ function materializeRuntimeDefinition(cwd: string, definition: unknown): string {
230
+ const content = `${JSON.stringify(definition, null, 2)}\n`;
231
+ const digest = createHash("sha256").update(content).digest("hex").slice(0, 16);
232
+ const directory = path.join(cwd, ".kontourai", "flow-agents", "runtime-definitions");
233
+ mkdirSync(directory, { recursive: true });
234
+ const file = path.join(directory, `builder-build-${digest}.flow.json`);
235
+ if (!existsSync(file)) writeFileSync(file, content);
236
+ return file;
237
+ }
238
+
239
+ function assertCanonicalDefinition(
240
+ runId: string,
241
+ expectedDefinition: { id: string; version: string },
242
+ actualDefinition: { id: string; version: string },
243
+ ): void {
244
+ if (isDeepStrictEqual(actualDefinition, expectedDefinition)) return;
245
+
246
+ const mismatch: BuilderBuildRunIdentityMismatch = actualDefinition.id !== expectedDefinition.id
247
+ ? "definition-id"
248
+ : actualDefinition.version !== expectedDefinition.version
249
+ ? "definition-version"
250
+ : "definition-content";
251
+ throw new BuilderBuildRunIdentityError(runId, expectedDefinition, actualDefinition, mismatch);
252
+ }
253
+
254
+ function assertRuntimeInput(input: unknown, forbiddenFields: string[]): asserts input is Record<string, unknown> {
255
+ if (!isRecord(input)) {
256
+ throw new BuilderBuildRunInputError("input", "must be an object");
257
+ }
258
+ for (const field of forbiddenFields) {
259
+ if (field in input) {
260
+ throw new BuilderBuildRunInputError(field, "is not supported by this API");
261
+ }
262
+ }
263
+ }
264
+
265
+ function validateEvidenceInput(evidence: unknown): BuilderBuildTrustBundleEvidenceInput {
266
+ if (!isRecord(evidence)) {
267
+ throw new BuilderBuildRunInputError("evidence", "must be an object");
268
+ }
269
+ if (!isNonEmptyString(evidence.gate)) {
270
+ throw new BuilderBuildRunInputError("evidence.gate", "must be a non-empty string");
271
+ }
272
+ if (!isNonEmptyString(evidence.file)) {
273
+ throw new BuilderBuildRunInputError("evidence.file", "must be a non-empty string");
274
+ }
275
+ if (evidence.status !== undefined && evidence.status !== "passed" && evidence.status !== "failed") {
276
+ throw new BuilderBuildRunInputError("evidence.status", "must be passed or failed");
277
+ }
278
+ return evidence as unknown as BuilderBuildTrustBundleEvidenceInput;
279
+ }
280
+
281
+ function assertCurrentOpenGate(definition: unknown, state: FlowRunState, evidenceGate: string): void {
282
+ const gates = openGates(definition, state);
283
+ if (gates.length !== 1) {
284
+ throw new BuilderBuildRunInputError("evidence.gate", "requires exactly one current open gate");
285
+ }
286
+ if (gates[0].id !== evidenceGate) {
287
+ throw new BuilderBuildRunInputError("evidence.gate", "must target the persisted current open gate");
288
+ }
289
+ }
290
+
291
+ function assertBundleSubjects(bundle: unknown, subject: string, gate: unknown): void {
292
+ if (!isRecord(bundle) || !Array.isArray(bundle.claims)) {
293
+ throw new BuilderBuildRunInputError("evidence", "contains no normalized claims");
294
+ }
295
+ const selectors = expectationsForGate(gate).map((expectation: any) => expectation.bundle_claim);
296
+ const relevant = bundle.claims.filter((claim) =>
297
+ isRecord(claim)
298
+ && selectors.some((selector: any) =>
299
+ selector.claimType === claim.claimType
300
+ && (!selector.subjectType || selector.subjectType === claim.subjectType)
301
+ )
302
+ );
303
+ if (relevant.length === 0) {
304
+ throw new BuilderBuildRunInputError("evidence.claims", "contains no claim matching the persisted current open gate");
305
+ }
306
+ for (const claim of relevant) {
307
+ const metadata = isRecord(claim.metadata) ? claim.metadata : null;
308
+ if (!metadata || metadata.workflow_subject_ref !== subject) {
309
+ throw new BuilderBuildRunInputError("evidence.claims.metadata.workflow_subject_ref", "must match the persisted run subject");
310
+ }
311
+ }
312
+ }
313
+
314
+ function trustBundleAttachOptions(cwd: string, evidence: BuilderBuildTrustBundleEvidenceInput): JsonObject {
315
+ return {
316
+ cwd,
317
+ gate: evidence.gate,
318
+ file: evidence.file,
319
+ kind: "trust.bundle",
320
+ bundle: true,
321
+ ...(evidence.status ? { status: evidence.status } : {}),
322
+ ...(evidence.producer ? { producer: evidence.producer } : {}),
323
+ ...(evidence.authorityTrace ? { authorityTrace: evidence.authorityTrace } : {}),
324
+ ...(evidence.routeReason ? { route_reason: evidence.routeReason } : {}),
325
+ ...(evidence.expectationIds ? { expectation_ids: evidence.expectationIds } : {}),
326
+ ...(evidence.supersede ? { supersede: evidence.supersede } : {}),
327
+ ...(evidence.classifier ? { classifier: evidence.classifier } : {}),
328
+ ...(evidence.diagnostics ? { diagnostics: evidence.diagnostics } : {}),
329
+ ...(evidence.analytics ? { analytics: evidence.analytics } : {}),
330
+ };
331
+ }
332
+
333
+ function isRecord(value: unknown): value is Record<string, unknown> {
334
+ return typeof value === "object" && value !== null && !Array.isArray(value);
335
+ }
336
+
337
+ function isNonEmptyString(value: unknown): value is string {
338
+ return typeof value === "string" && value.trim().length > 0;
339
+ }
340
+
341
+ function moduleDirectory(): string {
342
+ return path.dirname(fileURLToPath(import.meta.url));
343
+ }
344
+
345
+ function findPackageRoot(startDir: string): string {
346
+ let dir = startDir;
347
+ for (;;) {
348
+ if (existsSync(path.join(dir, "package.json")) && existsSync(path.join(dir, BUILDER_BUILD_FLOW_RELATIVE_PATH))) {
349
+ return dir;
350
+ }
351
+ const parent = path.dirname(dir);
352
+ if (parent === dir) {
353
+ throw new Error(`unable to locate ${BUILDER_BUILD_FLOW_RELATIVE_PATH} from ${startDir}`);
354
+ }
355
+ dir = parent;
356
+ }
357
+ }