@kontourai/flow-agents 3.2.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (281) hide show
  1. package/.github/workflows/add-to-project.yml +15 -0
  2. package/.github/workflows/ci.yml +173 -0
  3. package/CHANGELOG.md +54 -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/assignment-provider.js +10 -1
  11. package/build/src/cli/builder-run.d.ts +1 -0
  12. package/build/src/cli/builder-run.js +27 -0
  13. package/build/src/cli/effective-backlog-settings.js +70 -2
  14. package/build/src/cli/init.d.ts +34 -0
  15. package/build/src/cli/init.js +341 -61
  16. package/build/src/cli/kit.js +55 -12
  17. package/build/src/cli/pull-work-provider.js +346 -5
  18. package/build/src/cli/skill-drift-check.d.ts +1 -0
  19. package/build/src/cli/skill-drift-check.js +165 -0
  20. package/build/src/cli/telemetry-doctor.d.ts +37 -0
  21. package/build/src/cli/telemetry-doctor.js +53 -6
  22. package/build/src/cli/validate-hook-influence.js +37 -7
  23. package/build/src/cli/workflow-artifact-cleanup-audit.js +418 -11
  24. package/build/src/cli/workflow-sidecar.d.ts +310 -1
  25. package/build/src/cli/workflow-sidecar.js +1914 -126
  26. package/build/src/cli.js +5 -0
  27. package/build/src/flow-kit/validate.d.ts +54 -34
  28. package/build/src/flow-kit/validate.js +237 -26
  29. package/build/src/index.d.ts +2 -0
  30. package/build/src/index.js +1 -0
  31. package/build/src/lib/console-connect-options.d.ts +97 -0
  32. package/build/src/lib/console-connect-options.js +199 -0
  33. package/build/src/lib/console-telemetry-validate.d.ts +49 -0
  34. package/build/src/lib/console-telemetry-validate.js +91 -0
  35. package/build/src/lib/flow-resolver.d.ts +54 -1
  36. package/build/src/lib/flow-resolver.js +112 -5
  37. package/build/src/lib/fs.d.ts +17 -0
  38. package/build/src/lib/fs.js +172 -0
  39. package/build/src/lib/local-artifact-root.d.ts +44 -1
  40. package/build/src/lib/local-artifact-root.js +131 -3
  41. package/build/src/runtime-adapters.d.ts +39 -3
  42. package/build/src/runtime-adapters.js +77 -31
  43. package/build/src/tools/build-universal-bundles.js +40 -2
  44. package/build/src/tools/codex-agent-routing.d.ts +2 -0
  45. package/build/src/tools/codex-agent-routing.js +49 -0
  46. package/build/src/tools/generate-context-map.js +1 -0
  47. package/build/src/tools/validate-source-tree.js +30 -3
  48. package/context/contracts/artifact-contract.md +16 -2
  49. package/context/scripts/hooks/lib/kit-catalog.js +235 -0
  50. package/context/scripts/hooks/lib/runnable-command.js +177 -0
  51. package/context/scripts/hooks/stop-goal-fit.js +278 -48
  52. package/context/scripts/hooks/workflow-steering.js +194 -22
  53. package/context/scripts/package.json +3 -0
  54. package/context/scripts/telemetry/install-console-config.sh +25 -4
  55. package/context/scripts/telemetry/lib/config.sh +102 -12
  56. package/context/scripts/telemetry/lib/pricing.sh +50 -0
  57. package/context/scripts/telemetry/lib/session.sh +3 -0
  58. package/context/scripts/telemetry/lib/transport.sh +87 -0
  59. package/context/scripts/telemetry/lib/usage.sh +205 -4
  60. package/context/scripts/telemetry/telemetry.conf +6 -0
  61. package/context/scripts/telemetry/telemetry.sh +48 -0
  62. package/context/settings/workspace-backlog-provider-settings.example.json +48 -0
  63. package/docs/agent-usage-feedback-loop.md +35 -0
  64. package/docs/architecture-engine-and-kits.md +110 -0
  65. package/docs/context-map.md +2 -0
  66. package/docs/coordination-guide.md +370 -0
  67. package/docs/decisions/agent-coordination.md +26 -9
  68. package/docs/decisions/embeddable-engine.md +152 -0
  69. package/docs/decisions/index.md +5 -3
  70. package/docs/decisions/trust-ledger-retention.md +88 -0
  71. package/docs/decisions/trust-reconcile.md +42 -9
  72. package/docs/decisions/workflow-enforcement.md +31 -9
  73. package/docs/fixture-ownership.md +6 -2
  74. package/docs/implementing-trust-reconciliation.md +129 -0
  75. package/docs/index.md +23 -9
  76. package/docs/integrations/flow-agents-console.md +275 -0
  77. package/docs/integrations/index.md +4 -0
  78. package/docs/kit-authoring-guide.md +52 -21
  79. package/docs/spec/builder-flow-runtime.md +80 -0
  80. package/docs/spec/runtime-hook-surface.md +45 -1
  81. package/docs/specs/economics-record-contract.md +270 -0
  82. package/docs/specs/harness-capability-matrix.md +74 -0
  83. package/docs/specs/learning-review-proposals-contract.md +340 -0
  84. package/docs/specs/routing-efficiency-review.md +59 -0
  85. package/docs/verifiable-trust.md +74 -25
  86. package/docs/workflow-artifact-lifecycle.md +38 -1
  87. package/docs/workflow-usage-guide.md +10 -0
  88. package/evals/acceptance/prove-capture-teeth.sh +132 -0
  89. package/evals/ci/antigaming-suite.sh +2 -0
  90. package/evals/ci/run-baseline.sh +78 -4
  91. package/evals/fixtures/economics/acceptance.json +12 -0
  92. package/evals/fixtures/economics/agents/tool-worker-1/events.jsonl +2 -0
  93. package/evals/fixtures/economics/agents/tool-worker-2/events.jsonl +2 -0
  94. package/evals/fixtures/economics/agents/tool-worker-3/events.jsonl +2 -0
  95. package/evals/fixtures/economics/agents/tool-worker-4/events.jsonl +1 -0
  96. package/evals/fixtures/economics/agents/tool-worker-5/events.jsonl +2 -0
  97. package/evals/fixtures/economics/critique.json +22 -0
  98. package/evals/fixtures/economics/expected-record.json +71 -0
  99. package/evals/fixtures/economics/session-usage-event.json +1 -0
  100. package/evals/fixtures/economics/state.json +11 -0
  101. package/evals/fixtures/economics/transcript.jsonl +3 -0
  102. package/evals/fixtures/hook-influence/cases.json +7 -7
  103. package/evals/fixtures/learning-review-proposals/balanced/economics.jsonl +6 -0
  104. package/evals/fixtures/learning-review-proposals/effect-follow-up/economics.jsonl +5 -0
  105. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-1/trust.bundle +21 -0
  106. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-2/trust.bundle +21 -0
  107. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-3/trust.bundle +21 -0
  108. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-4/trust.bundle +21 -0
  109. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-5/trust.bundle +21 -0
  110. package/evals/fixtures/learning-review-proposals/pattern-present/economics.jsonl +6 -0
  111. package/evals/fixtures/learning-review-proposals/pattern-present/expected-aggregates.json +30 -0
  112. package/evals/fixtures/learning-review-proposals/pattern-present/expected-aggregates.md +66 -0
  113. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-1/gate-review.inquiries.json +26 -0
  114. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-1/trust.bundle +21 -0
  115. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-2/gate-review.inquiries.json +26 -0
  116. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-2/trust.bundle +21 -0
  117. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-3/gate-review.inquiries.json +26 -0
  118. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-3/trust.bundle +21 -0
  119. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-4/gate-review.inquiries.json +26 -0
  120. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-4/trust.bundle +21 -0
  121. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-5/trust.bundle +21 -0
  122. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-6/trust.bundle +21 -0
  123. package/evals/fixtures/learning-review-proposals/repeat-window/economics.jsonl +6 -0
  124. package/evals/fixtures/learning-review-proposals/under-threshold/economics.jsonl +3 -0
  125. package/evals/fixtures/reconcile-preflight/disputed-critique-unsuperseded.json +48 -0
  126. package/evals/fixtures/reconcile-preflight/standalone-disputed-session-local.json +59 -0
  127. package/evals/fixtures/telemetry/usage-transcript-sample.jsonl +4 -0
  128. package/evals/fixtures/trust-reconcile-exploits/mcp-degrade.json +42 -0
  129. package/evals/integration/test_builder_entry_enforcement.sh +241 -0
  130. package/evals/integration/test_builder_step_producers.sh +18 -10
  131. package/evals/integration/test_bundle_install.sh +172 -0
  132. package/evals/integration/test_checkpoint_signing.sh +10 -2
  133. package/evals/integration/test_ci_actor_identity.sh +221 -0
  134. package/evals/integration/test_console_tenant_isolation.sh +167 -0
  135. package/evals/integration/test_critique_supersession_roundtrip.sh +4 -1
  136. package/evals/integration/test_dual_emit_flow_step.sh +10 -4
  137. package/evals/integration/test_economics_record.sh +674 -0
  138. package/evals/integration/test_effective_backlog_settings.sh +1 -1
  139. package/evals/integration/test_evidence_capture_hook.sh +17 -2
  140. package/evals/integration/test_exemption_usage_review.sh +198 -0
  141. package/evals/integration/test_fixture_retirement_audit.sh +2 -2
  142. package/evals/integration/test_flow_kit_install_git.sh +83 -0
  143. package/evals/integration/test_flowdef_session_activation.sh +0 -1
  144. package/evals/integration/test_flowdef_session_history_preservation.sh +13 -3
  145. package/evals/integration/test_gate_lockdown.sh +7 -0
  146. package/evals/integration/test_gate_review_inquiry_records.sh +9 -1
  147. package/evals/integration/test_goal_fit_hook.sh +2031 -0
  148. package/evals/integration/test_hook_category_behaviors.sh +8 -1
  149. package/evals/integration/test_hook_influence_cases.sh +25 -1
  150. package/evals/integration/test_install_merge.sh +227 -2
  151. package/evals/integration/test_kit_conformance_levels.sh +6 -6
  152. package/evals/integration/test_learning_review_proposals.sh +329 -0
  153. package/evals/integration/test_liveness_conflict_injection.sh +26 -22
  154. package/evals/integration/test_liveness_console_relay.sh +166 -0
  155. package/evals/integration/test_liveness_heartbeat.sh +17 -17
  156. package/evals/integration/test_liveness_worktree_root.sh +575 -0
  157. package/evals/integration/test_phase_map_and_gate_claim.sh +6 -1
  158. package/evals/integration/test_publish_delivery.sh +389 -2
  159. package/evals/integration/test_pull_work_board.sh +200 -0
  160. package/evals/integration/test_pull_work_provider.sh +1 -1
  161. package/evals/integration/test_reconcile_preflight.sh +304 -0
  162. package/evals/integration/test_record_check.sh +378 -0
  163. package/evals/integration/test_routing_efficiency.sh +71 -0
  164. package/evals/integration/test_runtime_adapter_activation.sh +28 -0
  165. package/evals/integration/test_session_resume_roundtrip.sh +16 -19
  166. package/evals/integration/test_skill_drift_check.sh +870 -0
  167. package/evals/integration/test_takeover_protocol.sh +340 -0
  168. package/evals/integration/test_telemetry.sh +445 -0
  169. package/evals/integration/test_telemetry_doctor.sh +66 -0
  170. package/evals/integration/test_telemetry_usage_pipeline.sh +228 -0
  171. package/evals/integration/test_trust_reconcile_negatives.sh +121 -13
  172. package/evals/integration/test_trust_reconcile_trailer_diagnostic.sh +247 -0
  173. package/evals/integration/test_usage_cost.sh +61 -0
  174. package/evals/integration/test_verify_hold.sh +910 -0
  175. package/evals/integration/test_veritas_governance_kit.sh +257 -0
  176. package/evals/integration/test_workflow_artifact_cleanup_audit.sh +575 -3
  177. package/evals/integration/test_workflow_sidecar_writer.sh +1395 -0
  178. package/evals/integration/test_workflow_steering_hook.sh +157 -16
  179. package/evals/integration/test_workspace_settings.sh +176 -0
  180. package/evals/lib/env.sh +26 -0
  181. package/evals/lib/node.sh +8 -0
  182. package/evals/run.sh +37 -0
  183. package/evals/static/test_ci_integration_coverage.sh +115 -0
  184. package/evals/static/test_declared_scope_forms_documented.sh +114 -0
  185. package/evals/static/test_universal_bundles.sh +34 -0
  186. package/evals/static/test_validate_source_kit_asset_scope.sh +259 -0
  187. package/evals/static/test_workflow_skills.sh +1 -1
  188. package/kits/builder/flows/build.flow.json +9 -18
  189. package/kits/builder/flows/publish-learn.flow.json +5 -1
  190. package/kits/builder/kit.json +120 -0
  191. package/kits/builder/skills/continue-work/SKILL.md +2 -0
  192. package/kits/builder/skills/deliver/SKILL.md +115 -0
  193. package/kits/builder/skills/evidence-gate/SKILL.md +12 -0
  194. package/kits/builder/skills/execute-plan/SKILL.md +9 -0
  195. package/kits/builder/skills/learning-review/SKILL.md +51 -0
  196. package/kits/builder/skills/plan-work/SKILL.md +17 -20
  197. package/kits/builder/skills/pull-work/SKILL.md +33 -2
  198. package/kits/builder/skills/release-readiness/SKILL.md +12 -0
  199. package/kits/knowledge/kit.json +9 -0
  200. package/kits/veritas-governance/docs/README.md +113 -7
  201. package/kits/veritas-governance/fixtures/exemption/approved.trust-bundle.json +74 -0
  202. package/kits/veritas-governance/fixtures/exemption/not-approved.trust-bundle.json +74 -0
  203. package/kits/veritas-governance/fixtures/exemption-review/mixed-fresh-stale.DECLARED.json +14 -0
  204. package/kits/veritas-governance/flows/exemption-issuance.flow.json +35 -0
  205. package/kits/veritas-governance/kit.json +19 -0
  206. package/kits/veritas-governance/skills/exemption-usage-review/SKILL.md +128 -0
  207. package/kits/veritas-governance/skills/exemption-usage-review/review-exemptions.mjs +231 -0
  208. package/package.json +2 -2
  209. package/packaging/manifest.json +29 -0
  210. package/schemas/backlog-provider-settings.schema.json +13 -0
  211. package/schemas/workflow-state.schema.json +44 -0
  212. package/scripts/README.md +4 -0
  213. package/scripts/check-content-boundary.cjs +8 -1
  214. package/scripts/ci/trust-reconcile.js +214 -253
  215. package/scripts/hooks/codex-hook-adapter.js +77 -2
  216. package/scripts/hooks/evidence-capture.js +38 -5
  217. package/scripts/hooks/lib/actor-identity.js +82 -0
  218. package/scripts/hooks/lib/codex-exit-code.js +316 -0
  219. package/scripts/hooks/lib/kit-catalog.js +235 -0
  220. package/scripts/hooks/lib/liveness-write.js +28 -1
  221. package/scripts/hooks/lib/local-artifact-paths.js +97 -1
  222. package/scripts/hooks/lib/runnable-command.js +177 -0
  223. package/scripts/hooks/lib/skill-drift.js +350 -0
  224. package/scripts/hooks/stop-goal-fit.js +278 -48
  225. package/scripts/hooks/workflow-steering.js +194 -22
  226. package/scripts/install-codex-home.sh +97 -47
  227. package/scripts/install-merge.js +72 -14
  228. package/scripts/install-owned-files.js +178 -0
  229. package/scripts/lib/reconcile-shape.js +381 -0
  230. package/scripts/liveness/relay.sh +84 -0
  231. package/scripts/telemetry/economics-record.schema.json +145 -0
  232. package/scripts/telemetry/economics-record.sh +331 -0
  233. package/scripts/telemetry/install-console-config.sh +25 -4
  234. package/scripts/telemetry/learning-review-decide.sh +124 -0
  235. package/scripts/telemetry/learning-review-proposals.schema.json +161 -0
  236. package/scripts/telemetry/learning-review-proposals.sh +484 -0
  237. package/scripts/telemetry/lib/config.sh +102 -12
  238. package/scripts/telemetry/lib/pricing.sh +14 -6
  239. package/scripts/telemetry/lib/session.sh +3 -0
  240. package/scripts/telemetry/lib/transport.sh +133 -15
  241. package/scripts/telemetry/lib/usage.sh +121 -28
  242. package/scripts/telemetry/routing-efficiency.sh +0 -0
  243. package/scripts/telemetry/telemetry.conf +6 -0
  244. package/scripts/telemetry/telemetry.sh +48 -0
  245. package/src/builder-flow-run-adapter.ts +357 -0
  246. package/src/builder-flow-runtime.ts +348 -0
  247. package/src/cli/assignment-provider.ts +12 -1
  248. package/src/cli/builder-flow-run-adapter.test.mjs +495 -0
  249. package/src/cli/builder-flow-runtime.test.mjs +213 -0
  250. package/src/cli/builder-run.ts +28 -0
  251. package/src/cli/codex-agent-routing.test.mjs +44 -0
  252. package/src/cli/codex-exit-code.test.mjs +207 -0
  253. package/src/cli/console-connect-options.test.mjs +329 -0
  254. package/src/cli/console-telemetry-validate.test.mjs +157 -0
  255. package/src/cli/effective-backlog-settings.ts +68 -2
  256. package/src/cli/flow-resolver-composition.test.mjs +72 -0
  257. package/src/cli/init.test.mjs +161 -0
  258. package/src/cli/init.ts +407 -62
  259. package/src/cli/kit-metadata-security.test.mjs +443 -0
  260. package/src/cli/kit.ts +50 -12
  261. package/src/cli/pull-work-provider.ts +377 -3
  262. package/src/cli/sidecar-pure-helpers.test.mjs +64 -0
  263. package/src/cli/skill-drift-check.ts +196 -0
  264. package/src/cli/telemetry-doctor.test.mjs +53 -0
  265. package/src/cli/telemetry-doctor.ts +50 -7
  266. package/src/cli/validate-hook-influence.ts +37 -6
  267. package/src/cli/workflow-artifact-cleanup-audit.ts +483 -10
  268. package/src/cli/workflow-sidecar.ts +1980 -119
  269. package/src/cli.ts +5 -0
  270. package/src/flow-kit/validate.ts +277 -38
  271. package/src/index.ts +19 -0
  272. package/src/lib/console-connect-options.ts +261 -0
  273. package/src/lib/console-telemetry-validate.ts +88 -0
  274. package/src/lib/flow-resolver.ts +117 -4
  275. package/src/lib/fs.ts +160 -0
  276. package/src/lib/local-artifact-root.ts +129 -3
  277. package/src/runtime-adapters.ts +113 -33
  278. package/src/tools/build-universal-bundles.ts +36 -2
  279. package/src/tools/codex-agent-routing.ts +48 -0
  280. package/src/tools/generate-context-map.ts +1 -0
  281. package/src/tools/validate-source-tree.ts +29 -3
@@ -0,0 +1,348 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import {
5
+ expectationsForGate,
6
+ openGates,
7
+ readJson,
8
+ runDir,
9
+ sha256File,
10
+ type FlowGate,
11
+ type FlowExpectation,
12
+ type FlowRunState,
13
+ type JsonObject,
14
+ } from "@kontourai/flow";
15
+ import {
16
+ BUILDER_BUILD_FLOW_ID,
17
+ BuilderBuildRunInputError,
18
+ evaluateBuilderBuildRun,
19
+ loadBuilderBuildRun,
20
+ startBuilderBuildRun,
21
+ type BuilderBuildRunResult,
22
+ } from "./builder-flow-run-adapter.js";
23
+
24
+ type AnyRecord = Record<string, any>;
25
+
26
+ export interface BuilderFlowSessionInput {
27
+ sessionDir: string;
28
+ }
29
+
30
+ export interface BuilderFlowSessionResult {
31
+ sessionDir: string;
32
+ projectRoot: string;
33
+ run: BuilderBuildRunResult;
34
+ projection: AnyRecord;
35
+ attached: boolean;
36
+ }
37
+
38
+ type SessionContext = {
39
+ sessionDir: string;
40
+ artifactRoot: string;
41
+ projectRoot: string;
42
+ slug: string;
43
+ stateFile: string;
44
+ bundleFile: string;
45
+ };
46
+
47
+ export async function startBuilderFlowSession(input: BuilderFlowSessionInput): Promise<BuilderFlowSessionResult> {
48
+ const context = resolveSessionContext(input.sessionDir);
49
+ const sidecarState = readSidecarState(context);
50
+ const subject = workflowSubject(sidecarState);
51
+ let run: BuilderBuildRunResult;
52
+ try {
53
+ run = await loadBuilderBuildRun({
54
+ cwd: context.projectRoot,
55
+ runId: context.slug,
56
+ });
57
+ } catch (error) {
58
+ if (!isRunNotFound(error)) throw error;
59
+ run = await startBuilderBuildRun({
60
+ cwd: context.projectRoot,
61
+ runId: context.slug,
62
+ subject,
63
+ params: {
64
+ subject,
65
+ },
66
+ });
67
+ }
68
+ return syncAndProject(context, run);
69
+ }
70
+
71
+ export async function syncBuilderFlowSession(input: BuilderFlowSessionInput): Promise<BuilderFlowSessionResult> {
72
+ const context = resolveSessionContext(input.sessionDir);
73
+ const run = await loadBuilderBuildRun({
74
+ cwd: context.projectRoot,
75
+ runId: context.slug,
76
+ });
77
+ return syncAndProject(context, run);
78
+ }
79
+
80
+ export async function syncBuilderFlowSessionIfPresent(sessionDir: string): Promise<BuilderFlowSessionResult | null> {
81
+ let context: SessionContext;
82
+ try {
83
+ context = resolveSessionContext(sessionDir);
84
+ } catch (error) {
85
+ if (error instanceof BuilderBuildRunInputError && error.field === "sessionDir") return null;
86
+ throw error;
87
+ }
88
+ if (!fs.existsSync(runDir(context.slug, context.projectRoot))) return null;
89
+ return syncBuilderFlowSession({ sessionDir });
90
+ }
91
+
92
+ async function syncAndProject(context: SessionContext, initial: BuilderBuildRunResult): Promise<BuilderFlowSessionResult> {
93
+ let run = initial;
94
+ let attached = false;
95
+ const gates = openGatesForResult(run);
96
+ if (run.state.status === "active" && gates.length !== 1) {
97
+ throw new BuilderBuildRunInputError("flow_run.open_gates", `expected exactly one gate for active step ${run.state.current_step}, found ${gates.length}`);
98
+ }
99
+ if (gates.length === 1 && fs.existsSync(context.bundleFile)) {
100
+ const rawBundle = await readJson(context.bundleFile);
101
+ const gateEvidence = bundleGateEvidence(rawBundle, gates[0]!, run.state.subject);
102
+ if (gateEvidence) {
103
+ const digest = await sha256File(context.bundleFile);
104
+ const alreadyAttached = manifestEvidence(run.manifest).some((entry) =>
105
+ entry.gate_id === gates[0]!.id && entry.sha256 === digest
106
+ );
107
+ if (!alreadyAttached) {
108
+ run = await evaluateBuilderBuildRun({
109
+ cwd: context.projectRoot,
110
+ runId: context.slug,
111
+ evidence: {
112
+ gate: gates[0]!.id,
113
+ file: path.relative(context.projectRoot, context.bundleFile),
114
+ ...(gateEvidence.failed ? { status: "failed" } : {}),
115
+ ...(gateEvidence.routeReason ? { routeReason: gateEvidence.routeReason } : {}),
116
+ },
117
+ });
118
+ attached = true;
119
+ }
120
+ }
121
+ }
122
+ const projection = projectFlowRun(context, run);
123
+ writeProjection(context, projection);
124
+ return {
125
+ sessionDir: context.sessionDir,
126
+ projectRoot: context.projectRoot,
127
+ run,
128
+ projection,
129
+ attached,
130
+ };
131
+ }
132
+
133
+ function resolveSessionContext(sessionDirInput: string): SessionContext {
134
+ const sessionDir = path.resolve(sessionDirInput);
135
+ const artifactRoot = path.dirname(sessionDir);
136
+ const kontouraiRoot = path.dirname(artifactRoot);
137
+ if (path.basename(artifactRoot) !== "flow-agents" || path.basename(kontouraiRoot) !== ".kontourai") {
138
+ throw new BuilderBuildRunInputError("sessionDir", "must be .kontourai/flow-agents/<slug>");
139
+ }
140
+ const slug = path.basename(sessionDir);
141
+ if (!slug || slug === "." || slug === "..") {
142
+ throw new BuilderBuildRunInputError("sessionDir", "must name a session");
143
+ }
144
+ const stateFile = path.join(sessionDir, "state.json");
145
+ if (!fs.existsSync(stateFile)) {
146
+ throw new BuilderBuildRunInputError("sessionDir", "must contain state.json");
147
+ }
148
+ return {
149
+ sessionDir,
150
+ artifactRoot,
151
+ projectRoot: path.dirname(kontouraiRoot),
152
+ slug,
153
+ stateFile,
154
+ bundleFile: path.join(sessionDir, "trust.bundle"),
155
+ };
156
+ }
157
+
158
+ function readSidecarState(context: SessionContext): AnyRecord {
159
+ const value = JSON.parse(fs.readFileSync(context.stateFile, "utf8"));
160
+ if (!isRecord(value) || value.task_slug !== context.slug) {
161
+ throw new BuilderBuildRunInputError("sessionDir", "state.json task_slug must match the session directory");
162
+ }
163
+ return value;
164
+ }
165
+
166
+ function workflowSubject(state: AnyRecord): string {
167
+ const refs = Array.isArray(state.work_item_refs)
168
+ ? state.work_item_refs.filter((value: unknown): value is string => typeof value === "string" && value.length > 0)
169
+ : [];
170
+ if (refs.length !== 1) {
171
+ throw new BuilderBuildRunInputError("state.work_item_refs", "must contain exactly one selected Work Item for builder.build");
172
+ }
173
+ return refs[0]!;
174
+ }
175
+
176
+ function openGatesForResult(run: BuilderBuildRunResult): Array<FlowGate & { id: string }> {
177
+ return openGates(
178
+ JSON.parse(fs.readFileSync(path.join(run.dir, "definition.json"), "utf8")),
179
+ run.state,
180
+ ) as Array<FlowGate & { id: string }>;
181
+ }
182
+
183
+ function bundleGateEvidence(bundle: unknown, gate: FlowGate, subject: string): { failed: boolean; routeReason: string | null } | null {
184
+ if (!isRecord(bundle) || !Array.isArray(bundle.claims)) return null;
185
+ const selectors = (expectationsForGate(gate) as FlowExpectation[]).map((expectation) => expectation.bundle_claim);
186
+ const relevant = bundle.claims.filter((claim: unknown): claim is AnyRecord => {
187
+ if (!isRecord(claim)) return false;
188
+ return selectors.some((candidate: FlowExpectation["bundle_claim"]) =>
189
+ candidate.claimType === claim.claimType
190
+ && (!candidate.subjectType || candidate.subjectType === claim.subjectType)
191
+ );
192
+ });
193
+ if (relevant.length === 0) return null;
194
+ if (relevant.some((claim) => workflowSubjectRef(claim) !== subject)) {
195
+ throw new BuilderBuildRunInputError("evidence.claims.metadata.workflow_subject_ref", "must match the persisted run subject");
196
+ }
197
+ const failed = relevant.some((claim) => claim.value === "fail" || claim.status === "disputed");
198
+ const routeReasons = [...new Set(relevant.flatMap((claim) => {
199
+ const metadata = isRecord(claim.metadata) ? claim.metadata : null;
200
+ const gateClaim = metadata && isRecord(metadata.gate_claim) ? metadata.gate_claim : null;
201
+ return gateClaim && typeof gateClaim.route_reason === "string" ? [gateClaim.route_reason] : [];
202
+ }))];
203
+ if (routeReasons.length > 1) {
204
+ throw new BuilderBuildRunInputError("evidence.claims.metadata.gate_claim.route_reason", "must agree across current-gate claims");
205
+ }
206
+ const routeReason = routeReasons[0] ?? null;
207
+ if (routeReason && !failed) {
208
+ throw new BuilderBuildRunInputError("evidence.claims.metadata.gate_claim.route_reason", "requires failed current-gate evidence");
209
+ }
210
+ const routeMap = isRecord((gate as AnyRecord).on_route_back) ? (gate as AnyRecord).on_route_back : null;
211
+ if (routeReason && (!routeMap || typeof routeMap[routeReason] !== "string")) {
212
+ throw new BuilderBuildRunInputError("evidence.claims.metadata.gate_claim.route_reason", `is not declared by gate ${String((gate as AnyRecord).id ?? "<unknown>")}`);
213
+ }
214
+ return { failed, routeReason };
215
+ }
216
+
217
+ function workflowSubjectRef(claim: AnyRecord): string | null {
218
+ const metadata = isRecord(claim.metadata) ? claim.metadata : null;
219
+ return metadata && typeof metadata.workflow_subject_ref === "string"
220
+ ? metadata.workflow_subject_ref
221
+ : null;
222
+ }
223
+
224
+ function manifestEvidence(manifest: JsonObject): AnyRecord[] {
225
+ return Array.isArray(manifest.evidence) ? manifest.evidence.filter(isRecord) : [];
226
+ }
227
+
228
+ function projectFlowRun(context: SessionContext, run: BuilderBuildRunResult): AnyRecord {
229
+ const sidecar = readSidecarState(context);
230
+ const definition = JSON.parse(fs.readFileSync(path.join(run.dir, "definition.json"), "utf8"));
231
+ const gates = openGates(definition, run.state) as Array<FlowGate & { id: string }>;
232
+ const complete = run.state.status === "completed";
233
+ const action = complete ? { skills: [], operations: [] } : stepAction(run.state.current_step);
234
+ if (!action) {
235
+ throw new BuilderBuildRunInputError("kit.flow_step_actions", `does not declare Builder step ${run.state.current_step}`);
236
+ }
237
+ const required = gates.flatMap((gate) => (expectationsForGate(gate) as FlowExpectation[])
238
+ .filter((expectation: FlowExpectation) => expectation.required)
239
+ .map((expectation: FlowExpectation) => `${expectation.id} (${expectation.bundle_claim.claimType}/${expectation.bundle_claim.subjectType ?? "any"})`));
240
+ const skills = action?.skills ?? [];
241
+ const operations = action?.operations ?? [];
242
+ const syncCommand = `flow-agents builder-run sync --session-dir .kontourai/flow-agents/${context.slug}`;
243
+ const routeBack = latestRouteBack(run.state);
244
+ const skillText = skills.length ? `Activate ${skills.map((skill) => `\`${skill}\``).join(" then ")}.` : "No Builder skill is required.";
245
+ const operationText = operations.length ? ` Perform ${operations.map((operation) => `\`${operation}\``).join(" then ")}.` : "";
246
+ const gateText = gates.length
247
+ ? `Complete ${gates.map((gate) => `\`${gate.id}\``).join(", ")} by recording: ${required.join(", ") || "its declared evidence"}.`
248
+ : "No Flow gate is open.";
249
+ const routeText = routeBack
250
+ ? ` Route-back history: attempt ${routeBack.attempt ?? "n/a"}${routeBack.max_attempts ? `/${routeBack.max_attempts}` : ""} returned to \`${routeBack.route_back_to ?? "an earlier step"}\`${routeBack.route_reason ? ` for \`${routeBack.route_reason}\`` : ""}.`
251
+ : "";
252
+ const nextAction = complete
253
+ ? { status: "done", summary: "Canonical Flow run is complete." }
254
+ : {
255
+ status: "continue",
256
+ summary: `Flow step \`${run.state.current_step}\`: ${skillText}${operationText} ${gateText}${routeText} Then synchronize the recorded evidence.`,
257
+ skills,
258
+ operations,
259
+ command: syncCommand,
260
+ };
261
+ const phase = phaseForStep(definition.phase_map, run.state.current_step) ?? sidecar.phase;
262
+ return {
263
+ ...sidecar,
264
+ status: complete ? "delivered" : (run.state.transitions.length > 0 ? "in_progress" : sidecar.status),
265
+ phase: complete ? "done" : phase,
266
+ updated_at: run.state.updated_at,
267
+ flow_run: {
268
+ run_id: run.runId,
269
+ definition_id: run.definitionId,
270
+ definition_version: run.definitionVersion,
271
+ status: run.state.status,
272
+ current_step: run.state.current_step,
273
+ run_ref: path.relative(context.projectRoot, run.dir),
274
+ open_gate_ids: gates.map((gate) => gate.id),
275
+ ...(typeof routeBack?.attempt === "number" ? { route_back_attempt: routeBack.attempt } : {}),
276
+ ...(typeof routeBack?.max_attempts === "number" ? { route_back_max_attempts: routeBack.max_attempts } : {}),
277
+ },
278
+ next_action: nextAction,
279
+ };
280
+ }
281
+
282
+ function writeProjection(context: SessionContext, projection: AnyRecord): void {
283
+ fs.writeFileSync(context.stateFile, `${JSON.stringify(projection, null, 2)}\n`);
284
+ const pointerFiles = [path.join(context.artifactRoot, "current.json")];
285
+ const actorRoot = path.join(context.artifactRoot, "current");
286
+ if (fs.existsSync(actorRoot)) {
287
+ pointerFiles.push(...fs.readdirSync(actorRoot)
288
+ .filter((name) => name.endsWith(".json"))
289
+ .map((name) => path.join(actorRoot, name)));
290
+ }
291
+ for (const file of pointerFiles) {
292
+ if (!fs.existsSync(file)) continue;
293
+ const pointer = JSON.parse(fs.readFileSync(file, "utf8"));
294
+ if (!isRecord(pointer) || pointer.active_slug !== context.slug) continue;
295
+ pointer.active_flow_id = BUILDER_BUILD_FLOW_ID;
296
+ pointer.active_step_id = projection.flow_run.current_step;
297
+ pointer.updated_at = projection.updated_at;
298
+ fs.writeFileSync(file, `${JSON.stringify(pointer, null, 2)}\n`);
299
+ }
300
+ }
301
+
302
+ function stepAction(stepId: string): { skills: string[]; operations: string[] } | null {
303
+ const manifest = JSON.parse(fs.readFileSync(path.join(packageRoot(), "kits", "builder", "kit.json"), "utf8"));
304
+ const actions = Array.isArray(manifest.flow_step_actions) ? manifest.flow_step_actions : [];
305
+ const action = actions.find((candidate: unknown) =>
306
+ isRecord(candidate)
307
+ && candidate.flow_id === BUILDER_BUILD_FLOW_ID
308
+ && candidate.step_id === stepId
309
+ );
310
+ if (!isRecord(action) || !Array.isArray(action.skills) || !action.skills.every((skill: unknown) => typeof skill === "string")) {
311
+ return null;
312
+ }
313
+ const operations = Array.isArray(action.operations) && action.operations.every((operation: unknown) => typeof operation === "string")
314
+ ? action.operations
315
+ : [];
316
+ return { skills: action.skills, operations };
317
+ }
318
+
319
+ function phaseForStep(phaseMap: unknown, stepId: string): string | null {
320
+ if (!isRecord(phaseMap)) return stepId === "design-probe" ? "pickup" : null;
321
+ return Object.entries(phaseMap).find(([, step]) => step === stepId)?.[0] ?? (stepId === "design-probe" ? "pickup" : null);
322
+ }
323
+
324
+ function latestRouteBack(state: FlowRunState): AnyRecord | null {
325
+ const outcomes = Array.isArray(state.gate_outcomes) ? state.gate_outcomes : [];
326
+ return [...outcomes].reverse().find((outcome) => isRecord(outcome) && outcome.status === "route-back") ?? null;
327
+ }
328
+
329
+ function packageRoot(): string {
330
+ let directory = path.dirname(fileURLToPath(import.meta.url));
331
+ while (!fs.existsSync(path.join(directory, "package.json"))) {
332
+ const parent = path.dirname(directory);
333
+ if (parent === directory) throw new Error("unable to locate Flow Agents package root");
334
+ directory = parent;
335
+ }
336
+ return directory;
337
+ }
338
+
339
+ function isRunNotFound(error: unknown): boolean {
340
+ return isRecord(error) && (
341
+ error.code === "flow.run_location.not_found"
342
+ || (typeof error.message === "string" && error.message.includes("flow.run_location.not_found"))
343
+ );
344
+ }
345
+
346
+ function isRecord(value: unknown): value is AnyRecord {
347
+ return typeof value === "object" && value !== null && !Array.isArray(value);
348
+ }
@@ -120,6 +120,7 @@ function loadActorIdentityHelper(): {
120
120
  isUnresolvedActor: (actor: string) => boolean;
121
121
  sanitizeSegment: (value: unknown) => string;
122
122
  detectRuntime: (env: NodeJS.ProcessEnv) => string;
123
+ detectCiActor: (env: NodeJS.ProcessEnv) => { runtime: string; session_id: string } | null;
123
124
  } {
124
125
  const _req = createRequire(import.meta.url);
125
126
  const helperPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../../scripts/hooks/lib/actor-identity.js");
@@ -129,6 +130,7 @@ function loadActorIdentityHelper(): {
129
130
  isUnresolvedActor: (actor: string) => boolean;
130
131
  sanitizeSegment: (value: unknown) => string;
131
132
  detectRuntime: (env: NodeJS.ProcessEnv) => string;
133
+ detectCiActor: (env: NodeJS.ProcessEnv) => { runtime: string; session_id: string } | null;
132
134
  };
133
135
  }
134
136
 
@@ -199,7 +201,16 @@ function loadActorStruct(args: ParsedArgs): { actor: ActorStruct; actorKey?: str
199
201
  const helper = loadActorIdentityHelper();
200
202
  const resolved = helper.resolveActor(process.env);
201
203
  if (helper.isUnresolvedActor(resolved.actor)) throw new Error("could not resolve an actor identity (no --actor-json and no resolvable environment actor); pass --actor-json explicitly");
202
- return { actor: { runtime: helper.detectRuntime(process.env), session_id: resolved.actor, host: os.hostname(), human: null }, actorKey: resolved.actor };
204
+ // #398: reconstruct the SAME struct resolveActor serialized for a CI actor, mirroring
205
+ // resolveEnsureSessionActor (workflow-sidecar.ts) via the shared detectCiActor. Without this the
206
+ // else-branch would write `record.actor = {runtime:"unknown", session_id:<the whole triple>}` for a
207
+ // CI session — actor_key stays correct (so no false-block), but record.actor is malformed and the
208
+ // audit-trail / `assignment-provider status` output for CI sessions would be corrupt.
209
+ const ci = resolved.source.startsWith("ci-runtime") ? helper.detectCiActor(process.env) : null;
210
+ const actor: ActorStruct = ci && ci.session_id
211
+ ? { runtime: ci.runtime, session_id: ci.session_id, host: os.hostname(), human: null }
212
+ : { runtime: helper.detectRuntime(process.env), session_id: resolved.actor, host: os.hostname(), human: null };
213
+ return { actor, actorKey: resolved.actor };
203
214
  }
204
215
 
205
216
  export function assignmentFilePath(artifactRoot: string, subjectId: string): string {