@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
@@ -0,0 +1,495 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+
7
+ import {
8
+ FLOW_RUN_EVIDENCE_DIR,
9
+ FLOW_RUN_EVIDENCE_MANIFEST_PATH,
10
+ FLOW_RUN_REPORT_JSON_FILE,
11
+ FLOW_RUN_STATE_FILE,
12
+ runDir,
13
+ startRun,
14
+ } from "@kontourai/flow";
15
+
16
+ import {
17
+ BUILDER_BUILD_FLOW_ID,
18
+ evaluateBuilderBuildRun,
19
+ startBuilderBuildRun,
20
+ } from "../../build/src/index.js";
21
+
22
+ const REPO_ROOT = path.resolve(import.meta.dirname, "../..");
23
+ const BUILDER_BUILD_DEFINITION = path.join(REPO_ROOT, "kits/builder/flows/build.flow.json");
24
+ const FOREIGN_FLOW_DEFINITION = path.join(REPO_ROOT, "node_modules/@kontourai/flow/examples/agent-dev-flow.json");
25
+ const SUBJECT = "flow-agents#177";
26
+ const FOREIGN_SUBJECT = "flow-agents#178";
27
+ const FIXTURE_NOW = "2026-07-09T20:00:00.000Z";
28
+
29
+ function makeWorkspace() {
30
+ return fs.mkdtempSync(path.join(os.tmpdir(), "flow-agents-builder-flow-"));
31
+ }
32
+
33
+ function readJson(file) {
34
+ return JSON.parse(fs.readFileSync(file, "utf8"));
35
+ }
36
+
37
+ function writeJson(file, value) {
38
+ fs.mkdirSync(path.dirname(file), { recursive: true });
39
+ fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
40
+ }
41
+
42
+ function runFile(cwd, runId, file) {
43
+ return path.join(runDir(runId, cwd), file);
44
+ }
45
+
46
+ function evidenceDirectory(cwd, runId) {
47
+ return runFile(cwd, runId, FLOW_RUN_EVIDENCE_DIR);
48
+ }
49
+
50
+ function recursiveListing(directory, relative = "") {
51
+ assert.ok(fs.existsSync(directory), `expected evidence directory to exist: ${directory}`);
52
+ return fs.readdirSync(directory, { withFileTypes: true })
53
+ .sort((left, right) => left.name.localeCompare(right.name))
54
+ .flatMap((entry) => {
55
+ const childRelative = path.join(relative, entry.name);
56
+ const child = path.join(directory, entry.name);
57
+ if (entry.isDirectory()) return [{ path: `${childRelative}/`, type: "directory" }, ...recursiveListing(child, childRelative)];
58
+ return [{ path: childRelative, type: entry.isFile() ? "file" : "other" }];
59
+ });
60
+ }
61
+
62
+ function snapshotRun(cwd, runId) {
63
+ const stateFile = runFile(cwd, runId, FLOW_RUN_STATE_FILE);
64
+ const manifestFile = runFile(cwd, runId, FLOW_RUN_EVIDENCE_MANIFEST_PATH);
65
+ const reportFile = runFile(cwd, runId, FLOW_RUN_REPORT_JSON_FILE);
66
+ for (const file of [stateFile, manifestFile, reportFile]) {
67
+ assert.ok(fs.existsSync(file), `expected persisted run file to exist: ${file}`);
68
+ }
69
+ const reportText = fs.readFileSync(reportFile, "utf8");
70
+ return {
71
+ state: readJson(stateFile),
72
+ manifest: readJson(manifestFile),
73
+ report: JSON.parse(reportText),
74
+ reportText,
75
+ evidenceFiles: recursiveListing(evidenceDirectory(cwd, runId)),
76
+ };
77
+ }
78
+
79
+ function assertSnapshotUnchanged(before, after) {
80
+ assert.deepEqual(after.state, before.state, "state.json changed after rejected input");
81
+ assert.deepEqual(after.manifest, before.manifest, "evidence manifest changed after rejected input");
82
+ assert.deepEqual(after.report, before.report, "report JSON changed after rejected input");
83
+ assert.equal(after.reportText, before.reportText, "report bytes changed after rejected input");
84
+ assert.deepEqual(after.evidenceFiles, before.evidenceFiles, "evidence directory changed after rejected input");
85
+ }
86
+
87
+ function assertAdapterRejectedInput(error) {
88
+ assert.notEqual(error?.name, "TypeError", `adapter must reject input intentionally, not fail with ${error?.message}`);
89
+ return true;
90
+ }
91
+
92
+ function trustBundle({ claims, status = "verified", waiver = false, stale = false }) {
93
+ const policyId = "policy.duration";
94
+ const evidence = claims.map((claim, index) => ({
95
+ id: `evidence.${index + 1}`,
96
+ claimId: claim.id,
97
+ evidenceType: stale ? "test_output" : "human_attestation",
98
+ method: stale ? "validation" : "attestation",
99
+ sourceRef: "src/cli/builder-flow-run-adapter.test.mjs",
100
+ excerptOrSummary: `fixture for ${claim.claimType}`,
101
+ observedAt: stale ? "2026-01-01T00:00:00.000Z" : FIXTURE_NOW,
102
+ collectedBy: "flow-agents-test",
103
+ }));
104
+ return {
105
+ schemaVersion: 5,
106
+ source: "flow-agents-builder-flow-run-adapter-test",
107
+ claims: claims.map((claim) => ({
108
+ id: claim.id,
109
+ subjectType: claim.subjectType,
110
+ subjectId: claim.subjectId,
111
+ claimType: claim.claimType,
112
+ fieldOrBehavior: "parent gated prefix contract",
113
+ value: `${claim.claimType} for ${claim.subjectId}`,
114
+ metadata: {
115
+ workflow_subject_ref: claim.workflowSubjectRef ?? claim.subjectId,
116
+ ...(waiver ? { waiver: { reason: "plausible fixture waiver", approved_by: "flow-agents-test", approved_at: FIXTURE_NOW } } : {}),
117
+ },
118
+ createdAt: stale ? "2026-01-01T00:00:00.000Z" : FIXTURE_NOW,
119
+ updatedAt: stale ? "2026-01-01T00:00:00.000Z" : FIXTURE_NOW,
120
+ ...(stale ? { impactLevel: "high", verificationPolicyId: policyId } : {}),
121
+ })),
122
+ evidence,
123
+ policies: stale
124
+ ? [{
125
+ id: policyId,
126
+ claimType: claims[0].claimType,
127
+ requiredEvidence: ["test_output"],
128
+ requiredMethods: ["validation"],
129
+ requiresCorroboration: false,
130
+ acceptanceCriteria: ["fresh test result"],
131
+ reviewAuthority: "ci",
132
+ validityRule: { kind: "duration", durationDays: 1 },
133
+ stalenessTriggers: ["validity window expires"],
134
+ conflictRules: [],
135
+ impactLevel: "high",
136
+ }]
137
+ : [],
138
+ events: claims.map((claim, index) => ({
139
+ id: `event.${index + 1}.${status}`,
140
+ claimId: claim.id,
141
+ status,
142
+ actor: "flow-agents-test",
143
+ method: stale ? "npm test" : "attestation",
144
+ evidenceIds: [`evidence.${index + 1}`],
145
+ createdAt: stale ? "2026-01-01T00:00:00.000Z" : FIXTURE_NOW,
146
+ verifiedAt: stale ? "2026-01-01T00:00:00.000Z" : FIXTURE_NOW,
147
+ })),
148
+ };
149
+ }
150
+
151
+ function claim(claimType, subjectType, subjectId = SUBJECT) {
152
+ return { id: `claim.${claimType}.${subjectType}`, claimType, subjectType, subjectId };
153
+ }
154
+
155
+ function evidenceFile(cwd, name, bundle) {
156
+ const file = path.join(cwd, "evidence-input", `${name}.trust-bundle.json`);
157
+ writeJson(file, bundle);
158
+ return file;
159
+ }
160
+
161
+ function gateEvidence(cwd, { gate, claimType, subjectType, subjectId = SUBJECT, name = gate, status, routeReason, expectationIds, bundle }) {
162
+ return {
163
+ gate,
164
+ file: evidenceFile(cwd, name, bundle ?? trustBundle({ claims: [claim(claimType, subjectType, subjectId)] })),
165
+ ...(status ? { status } : {}),
166
+ ...(routeReason ? { routeReason } : {}),
167
+ ...(expectationIds ? { expectationIds } : {}),
168
+ };
169
+ }
170
+
171
+ async function startActiveRun(cwd, runId) {
172
+ return startBuilderBuildRun({ cwd, runId, subject: SUBJECT, params: { subject: SUBJECT } });
173
+ }
174
+
175
+ async function advanceParentPrefixThroughVerify(cwd, runId) {
176
+ await startActiveRun(cwd, runId);
177
+ const steps = [
178
+ ["pull-work-gate", "builder.pull-work.selected", "work-item"],
179
+ ["design-probe-gate", "builder.design-probe.pickup-readiness", "work-item"],
180
+ ["design-probe-gate", "builder.design-probe.decisions", "decision"],
181
+ ["plan-gate", "builder.plan.implementation", "artifact"],
182
+ ["execute-gate", "builder.execute.scope", "change"],
183
+ ];
184
+ for (const [gate, claimType, subjectType] of steps) {
185
+ await evaluateBuilderBuildRun({ cwd, runId, evidence: gateEvidence(cwd, { gate, claimType, subjectType, name: claimType }) });
186
+ }
187
+ assert.equal(snapshotRun(cwd, runId).state.current_step, "verify", "sequential parent-prefix setup must reach verify");
188
+ }
189
+
190
+ test("start is creation-only and persists canonical id/version", async () => {
191
+ const cwd = makeWorkspace();
192
+ const runId = "start-creation-only";
193
+
194
+ const result = await startActiveRun(cwd, runId);
195
+ const persisted = snapshotRun(cwd, runId);
196
+
197
+ assert.equal(result.definitionId, BUILDER_BUILD_FLOW_ID);
198
+ assert.equal(result.definitionVersion, "1.1");
199
+ assert.equal(persisted.state.definition_id, BUILDER_BUILD_FLOW_ID);
200
+ assert.equal(persisted.state.definition_version, "1.1");
201
+ assert.equal(persisted.state.subject, SUBJECT);
202
+ assert.equal(persisted.state.status, "active");
203
+ assert.equal(persisted.state.current_step, "pull-work");
204
+ assert.deepEqual(persisted.state.gate_outcomes, []);
205
+ assert.deepEqual(persisted.state.transitions, []);
206
+ assert.deepEqual(persisted.manifest.evidence, []);
207
+ });
208
+
209
+ test("verified evidence for another run subject is rejected before mutation", async () => {
210
+ const cwd = makeWorkspace();
211
+ const runId = "foreign-subject";
212
+ await startActiveRun(cwd, runId);
213
+ const before = snapshotRun(cwd, runId);
214
+
215
+ await assert.rejects(() => evaluateBuilderBuildRun({
216
+ cwd,
217
+ runId,
218
+ evidence: gateEvidence(cwd, { gate: "pull-work-gate", claimType: "builder.pull-work.selected", subjectType: "work-item", subjectId: FOREIGN_SUBJECT }),
219
+ }), assertAdapterRejectedInput);
220
+
221
+ assertSnapshotUnchanged(before, snapshotRun(cwd, runId));
222
+ });
223
+
224
+ test("all verified parent selectors advance only their persisted gates through pr-open", async () => {
225
+ const cwd = makeWorkspace();
226
+ const runId = "parent-prefix-through-pr-open";
227
+ await startActiveRun(cwd, runId);
228
+
229
+ const steps = [
230
+ ["pull-work-gate", "builder.pull-work.selected", "work-item"],
231
+ ["design-probe-gate", "builder.design-probe.pickup-readiness", "work-item"],
232
+ ["design-probe-gate", "builder.design-probe.decisions", "decision"],
233
+ ["plan-gate", "builder.plan.implementation", "artifact"],
234
+ ["execute-gate", "builder.execute.scope", "change"],
235
+ ];
236
+ for (const [gate, claimType, subjectType] of steps) {
237
+ await evaluateBuilderBuildRun({ cwd, runId, evidence: gateEvidence(cwd, { gate, claimType, subjectType, name: claimType }) });
238
+ }
239
+ await evaluateBuilderBuildRun({
240
+ cwd,
241
+ runId,
242
+ evidence: gateEvidence(cwd, {
243
+ gate: "verify-gate",
244
+ claimType: "builder.verify.tests",
245
+ subjectType: "flow-step",
246
+ name: "verify-all-selectors",
247
+ bundle: trustBundle({ claims: [
248
+ claim("builder.verify.tests", "flow-step"),
249
+ claim("builder.verify.policy-compliance", "artifact"),
250
+ ] }),
251
+ }),
252
+ });
253
+ const result = await evaluateBuilderBuildRun({
254
+ cwd,
255
+ runId,
256
+ evidence: gateEvidence(cwd, { gate: "merge-ready-gate", claimType: "builder.merge-ready.readiness", subjectType: "change" }),
257
+ });
258
+ const persisted = snapshotRun(cwd, runId);
259
+
260
+ assert.equal(result.definitionId, persisted.state.definition_id);
261
+ assert.equal(result.definitionVersion, persisted.state.definition_version);
262
+ assert.equal(persisted.state.current_step, "pr-open");
263
+ assert.equal(persisted.state.status, "active");
264
+ assert.equal(persisted.manifest.evidence.length, 7);
265
+ assert.deepEqual(persisted.state.gate_outcomes.map(({ gate_id, status, matched_expectations }) => ({
266
+ gate_id,
267
+ status,
268
+ matched_expectations: matched_expectations.map(({ expectation_id }) => expectation_id),
269
+ })), [
270
+ { gate_id: "pull-work-gate", status: "pass", matched_expectations: ["selected-work"] },
271
+ { gate_id: "design-probe-gate", status: "pass", matched_expectations: ["pickup-probe-readiness", "probe-decisions-or-accepted-gaps"] },
272
+ { gate_id: "plan-gate", status: "pass", matched_expectations: ["implementation-plan"] },
273
+ { gate_id: "execute-gate", status: "pass", matched_expectations: ["implementation-scope"] },
274
+ { gate_id: "verify-gate", status: "pass", matched_expectations: ["tests-evidence", "policy-compliance"] },
275
+ { gate_id: "merge-ready-gate", status: "pass", matched_expectations: ["merge-readiness"] },
276
+ ]);
277
+ });
278
+
279
+ test("bare assumed and waiver-shaped assumed evidence both remain blocked", async () => {
280
+ for (const [name, waiver] of [["bare", false], ["waiver-shaped", true]]) {
281
+ const cwd = makeWorkspace();
282
+ const runId = `assumed-${name}`;
283
+ await startActiveRun(cwd, runId);
284
+ await evaluateBuilderBuildRun({
285
+ cwd,
286
+ runId,
287
+ evidence: gateEvidence(cwd, {
288
+ gate: "pull-work-gate",
289
+ claimType: "builder.pull-work.selected",
290
+ subjectType: "work-item",
291
+ name,
292
+ bundle: trustBundle({ claims: [claim("builder.pull-work.selected", "work-item")], status: "assumed", waiver }),
293
+ }),
294
+ });
295
+ const persisted = snapshotRun(cwd, runId);
296
+ assert.equal(persisted.state.current_step, "pull-work", `${name} assumed evidence must not advance the run`);
297
+ assert.equal(persisted.state.status, "blocked");
298
+ assert.equal(persisted.manifest.evidence.at(-1).bundle_report.claims[0].status, "assumed");
299
+ }
300
+ });
301
+
302
+ test("malformed evidence status is rejected before mutation", async () => {
303
+ const cwd = makeWorkspace();
304
+ const runId = "malformed-status";
305
+ await startActiveRun(cwd, runId);
306
+ const before = snapshotRun(cwd, runId);
307
+
308
+ await assert.rejects(() => evaluateBuilderBuildRun({
309
+ cwd,
310
+ runId,
311
+ evidence: gateEvidence(cwd, { gate: "pull-work-gate", claimType: "builder.pull-work.selected", subjectType: "work-item", status: "banana" }),
312
+ }), assertAdapterRejectedInput);
313
+
314
+ assertSnapshotUnchanged(before, snapshotRun(cwd, runId));
315
+ });
316
+
317
+ test("multi-evidence input is rejected before the first attachment", async () => {
318
+ const cwd = makeWorkspace();
319
+ const runId = "multi-evidence";
320
+ await startActiveRun(cwd, runId);
321
+ const before = snapshotRun(cwd, runId);
322
+ const valid = gateEvidence(cwd, { gate: "pull-work-gate", claimType: "builder.pull-work.selected", subjectType: "work-item", name: "valid-first" });
323
+ const invalid = gateEvidence(cwd, {
324
+ gate: "pull-work-gate",
325
+ claimType: "builder.pull-work.selected",
326
+ subjectType: "work-item",
327
+ name: "invalid-second",
328
+ bundle: { schemaVersion: 5, source: "invalid", claims: [] },
329
+ });
330
+
331
+ await assert.rejects(() => evaluateBuilderBuildRun({ cwd, runId, evidence: [valid, invalid] }), assertAdapterRejectedInput);
332
+
333
+ assertSnapshotUnchanged(before, snapshotRun(cwd, runId));
334
+ });
335
+
336
+ test("invalid trust bundle is rejected before Flow copies it", async () => {
337
+ const cwd = makeWorkspace();
338
+ const runId = "invalid-bundle";
339
+ await startActiveRun(cwd, runId);
340
+ const before = snapshotRun(cwd, runId);
341
+ const invalid = gateEvidence(cwd, {
342
+ gate: "pull-work-gate",
343
+ claimType: "builder.pull-work.selected",
344
+ subjectType: "work-item",
345
+ bundle: { schemaVersion: 5, source: "invalid", claims: [] },
346
+ });
347
+
348
+ await assert.rejects(() => evaluateBuilderBuildRun({ cwd, runId, evidence: invalid }), assertAdapterRejectedInput);
349
+
350
+ assertSnapshotUnchanged(before, snapshotRun(cwd, runId));
351
+ });
352
+
353
+ test("future-gate evidence is rejected before attachment", async () => {
354
+ const cwd = makeWorkspace();
355
+ const runId = "future-gate";
356
+ await startActiveRun(cwd, runId);
357
+ const before = snapshotRun(cwd, runId);
358
+
359
+ await assert.rejects(() => evaluateBuilderBuildRun({
360
+ cwd,
361
+ runId,
362
+ evidence: gateEvidence(cwd, { gate: "verify-gate", claimType: "builder.verify.tests", subjectType: "flow-step" }),
363
+ }), assertAdapterRejectedInput);
364
+
365
+ assertSnapshotUnchanged(before, snapshotRun(cwd, runId));
366
+ });
367
+
368
+ test("direct evaluate gate injection is rejected before mutation", async () => {
369
+ const cwd = makeWorkspace();
370
+ const runId = "gate-injection";
371
+ await startActiveRun(cwd, runId);
372
+ const before = snapshotRun(cwd, runId);
373
+
374
+ await assert.rejects(() => evaluateBuilderBuildRun({
375
+ cwd,
376
+ runId,
377
+ gate: "verify-gate",
378
+ evidence: gateEvidence(cwd, { gate: "pull-work-gate", claimType: "builder.pull-work.selected", subjectType: "work-item" }),
379
+ }), assertAdapterRejectedInput);
380
+
381
+ assertSnapshotUnchanged(before, snapshotRun(cwd, runId));
382
+ });
383
+
384
+ test("backdated now cannot revive stale evidence", async () => {
385
+ const cwd = makeWorkspace();
386
+ const runId = "backdated-now";
387
+ await startActiveRun(cwd, runId);
388
+ const staleEvidence = gateEvidence(cwd, {
389
+ gate: "pull-work-gate",
390
+ claimType: "builder.pull-work.selected",
391
+ subjectType: "work-item",
392
+ bundle: trustBundle({ claims: [claim("builder.pull-work.selected", "work-item")], stale: true }),
393
+ });
394
+ const before = snapshotRun(cwd, runId);
395
+
396
+ await assert.rejects(
397
+ () => evaluateBuilderBuildRun({ cwd, runId, evidence: staleEvidence, now: "2026-01-01T12:00:00.000Z" }),
398
+ assertAdapterRejectedInput,
399
+ );
400
+ assertSnapshotUnchanged(before, snapshotRun(cwd, runId));
401
+
402
+ await evaluateBuilderBuildRun({ cwd, runId, evidence: staleEvidence });
403
+ const persisted = snapshotRun(cwd, runId);
404
+ assert.equal(persisted.state.current_step, "pull-work");
405
+ assert.equal(persisted.state.status, "blocked");
406
+ assert.equal(persisted.manifest.evidence.at(-1).bundle_report.claims[0].status, "stale");
407
+ });
408
+
409
+ test("foreign-id run with nonempty evidence is rejected without filesystem mutation", async () => {
410
+ const cwd = makeWorkspace();
411
+ const runId = "foreign-id";
412
+ await startRun(FOREIGN_FLOW_DEFINITION, { cwd, runId, params: { subject: SUBJECT } });
413
+ const before = snapshotRun(cwd, runId);
414
+
415
+ await assert.rejects(
416
+ () => evaluateBuilderBuildRun({
417
+ cwd,
418
+ runId,
419
+ evidence: gateEvidence(cwd, { gate: "implementation-gate", claimType: "builder.pull-work.selected", subjectType: "work-item" }),
420
+ }),
421
+ (error) => error?.name === "BuilderBuildRunIdentityError" && error.expectedDefinitionId === BUILDER_BUILD_FLOW_ID && error.actualDefinitionId === "agent-dev-flow" && error.runId === runId,
422
+ );
423
+
424
+ assertSnapshotUnchanged(before, snapshotRun(cwd, runId));
425
+ });
426
+
427
+ test("same-id same-version custom definition is rejected without mutation", async () => {
428
+ const cwd = makeWorkspace();
429
+ const runId = "same-id-custom-definition";
430
+ const customDefinition = readJson(BUILDER_BUILD_DEFINITION);
431
+ customDefinition.gates["pull-work-gate"].expects[0].bundle_claim.claimType = "builder.attacker.replacement";
432
+ const customPath = path.join(cwd, "definitions", "same-id-same-version-custom.json");
433
+ writeJson(customPath, customDefinition);
434
+ await startRun(customPath, { cwd, runId, params: { subject: SUBJECT } });
435
+ const before = snapshotRun(cwd, runId);
436
+
437
+ await assert.rejects(
438
+ () => evaluateBuilderBuildRun({
439
+ cwd,
440
+ runId,
441
+ evidence: gateEvidence(cwd, { gate: "pull-work-gate", claimType: "builder.attacker.replacement", subjectType: "work-item" }),
442
+ }),
443
+ (error) => error?.name === "BuilderBuildRunIdentityError" && error.expectedDefinitionId === BUILDER_BUILD_FLOW_ID && error.runId === runId,
444
+ );
445
+
446
+ assertSnapshotUnchanged(before, snapshotRun(cwd, runId));
447
+ });
448
+
449
+ test("result identity comes from the persisted canonical run", async () => {
450
+ const cwd = makeWorkspace();
451
+ const runId = "persisted-result-identity";
452
+ const started = await startActiveRun(cwd, runId);
453
+ const evaluated = await evaluateBuilderBuildRun({ cwd, runId });
454
+ const persisted = snapshotRun(cwd, runId);
455
+
456
+ for (const result of [started, evaluated]) {
457
+ assert.equal(result.definitionId, persisted.state.definition_id);
458
+ assert.equal(result.definitionVersion, persisted.state.definition_version);
459
+ }
460
+ assert.equal(persisted.state.definition_id, BUILDER_BUILD_FLOW_ID);
461
+ assert.equal(persisted.state.definition_version, "1.1");
462
+ });
463
+
464
+ test("failed verify evidence routes back only after sequential prefix advancement", async () => {
465
+ const cwd = makeWorkspace();
466
+ const runId = "route-back-prefix";
467
+ await advanceParentPrefixThroughVerify(cwd, runId);
468
+
469
+ await evaluateBuilderBuildRun({
470
+ cwd,
471
+ runId,
472
+ evidence: gateEvidence(cwd, {
473
+ gate: "verify-gate",
474
+ claimType: "builder.verify.tests",
475
+ subjectType: "flow-step",
476
+ status: "failed",
477
+ routeReason: "implementation_defect",
478
+ expectationIds: ["tests-evidence"],
479
+ }),
480
+ });
481
+ const persisted = snapshotRun(cwd, runId);
482
+ const outcome = persisted.state.gate_outcomes.at(-1);
483
+ const transition = persisted.state.transitions.at(-1);
484
+
485
+ assert.equal(persisted.state.current_step, "execute");
486
+ assert.equal(persisted.state.status, "active");
487
+ assert.equal(outcome.gate_id, "verify-gate");
488
+ assert.equal(outcome.status, "route-back");
489
+ assert.equal(outcome.route_reason, "implementation_defect");
490
+ assert.equal(outcome.route_back_to, "execute");
491
+ assert.equal(transition.type, "route_back");
492
+ assert.equal(transition.from_step, "verify");
493
+ assert.equal(transition.to_step, "execute");
494
+ assert.deepEqual(transition.expectation_ids, ["tests-evidence"]);
495
+ });