@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
@@ -15,7 +15,7 @@ pass() { echo " ✓ $1"; }
15
15
  fail() { echo " ✗ $1"; errors=$((errors + 1)); }
16
16
 
17
17
  json_value() {
18
- node -e 'const fs=require("fs"); let cur=JSON.parse(fs.readFileSync(0,"utf8")); for (const part of process.argv[1].split(".")) cur=Array.isArray(cur) ? cur[Number(part)] : cur[part]; console.log(cur);' "$1"
18
+ NO_COLOR=1 FORCE_COLOR=0 node -e 'const fs=require("fs"); let cur=JSON.parse(fs.readFileSync(0,"utf8")); for (const part of process.argv[1].split(".")) cur=Array.isArray(cur) ? cur[Number(part)] : cur[part]; console.log(cur);' "$1"
19
19
  }
20
20
 
21
21
  echo "=== Effective Backlog Settings ==="
@@ -56,6 +56,8 @@ echo "Part A: deterministic capture"
56
56
  printf '{"hook_event_name":"PostToolUse","tool_name":"Bash","cwd":"%s","tool_input":{"command":"npm test"},"tool_response":{"exitCode":0,"stdout":"ok"}}' "$A" | capture
57
57
  printf '{"hook_event_name":"PostToolUse","tool_name":"Bash","cwd":"%s","tool_input":{"command":"npm run lint"},"error":"command failed"}' "$A" | capture
58
58
  printf '{"hook_event_name":"PostToolUse","tool_name":"Bash","cwd":"%s","tool_input":{"command":"make build"},"tool_response":{"exit_code":2}}' "$A" | capture
59
+ # #470 rule-3 default: no exit code, no error, no stderr → ambiguous (never pass).
60
+ printf '{"hook_event_name":"PostToolUse","tool_name":"Bash","cwd":"%s","tool_input":{"command":"echo status-check"}}' "$A" | capture
59
61
  # A non-command tool (Write) must NOT be captured.
60
62
  printf '{"hook_event_name":"PostToolUse","tool_name":"Write","cwd":"%s","tool_input":{"file_path":"/tmp/x"}}' "$A" | capture
61
63
 
@@ -63,7 +65,7 @@ LOG="$A/.kontourai/flow-agents/t1/command-log.jsonl"
63
65
  if [[ -f "$LOG" ]]; then _pass "capture writes command-log.jsonl"; else _fail "capture did not write command-log.jsonl"; fi
64
66
 
65
67
  lines=$(wc -l < "$LOG" | tr -d ' ')
66
- if [[ "$lines" == "3" ]]; then _pass "capture records 3 command executions (Write tool excluded)"; else _fail "expected 3 log lines, got $lines"; fi
68
+ if [[ "$lines" == "4" ]]; then _pass "capture records 4 command executions (Write tool excluded)"; else _fail "expected 4 log lines, got $lines"; fi
67
69
 
68
70
  if rg -q '"command":"npm test","observedResult":"pass","exitCode":0' "$LOG"; then
69
71
  _pass "clean exit 0 recorded as observedResult:pass exitCode:0"
@@ -77,6 +79,14 @@ if rg -q '"command":"make build","observedResult":"fail","exitCode":2' "$LOG"; t
77
79
  _pass "non-zero exit recorded as fail with exitCode"
78
80
  else _fail "non-zero-exit command not recorded correctly"; fi
79
81
 
82
+ # #470 rule-3 default: absent any positive success evidence, observedResult is
83
+ # "ambiguous" (never "pass"). The error-bearing case above ("npm run lint" →
84
+ # fail, exitCode:null) already proves rule 2 (isFailureIndicated) is unchanged
85
+ # by the rule-3 flip.
86
+ if rg -q '"command":"echo status-check","observedResult":"ambiguous","exitCode":null' "$LOG"; then
87
+ _pass "no-signal command (no exit code, no error, no stderr) recorded as ambiguous, never pass"
88
+ else _fail "no-signal command not recorded as ambiguous: $(cat "$LOG")"; fi
89
+
80
90
  if rg -q '"source":"postToolUse-capture"' "$LOG"; then _pass "records source:postToolUse-capture"; else _fail "missing source field"; fi
81
91
 
82
92
  # Capture is non-blocking: it always exits 0 and echoes stdin.
@@ -165,8 +175,13 @@ fi
165
175
  # ============================================================================
166
176
  echo "Part B5: free-form model command re-run is opt-in only"
167
177
  F="$TMP/recheck"; seed_repo "$F" t1
178
+ # #494: the opt-in RECHECK re-run of model-recorded free-form commands applies to IN-FLIGHT
179
+ # sessions. On terminal delivered/done sessions the model-asserted RECHECK is skipped (captured
180
+ # execution evidence + L2 CI remain the anchors) — terminal-skip is covered in test_goal_fit_hook.sh.
181
+ # seed_repo defaults to delivered/done, so make this session in-flight to exercise the opt-in re-run.
182
+ printf '%s' '{"schema_version":"1.0","task_slug":"t1","status":"in_progress","phase":"verification","updated_at":"2026-06-23T00:00:00Z","next_action":{"status":"continue","summary":"verify command evidence"}}' > "$F/.kontourai/flow-agents/t1/state.json"
168
183
  printf '%s' '{"schema_version":"1.0","task_slug":"t1","verdict":"pass","checks":[{"id":"custom","kind":"command","status":"pass","command":"exit 5","summary":"ran custom"}]}' > "$F/.kontourai/flow-agents/t1/evidence.json"
169
- # Opt-in ON: the model's free-form "exit 5" is re-run and fails → block.
184
+ # Opt-in ON (in-flight): the model's free-form "exit 5" is re-run and fails → block.
170
185
  if FLOW_AGENTS_GOAL_FIT_MODE=block FLOW_AGENTS_GOAL_FIT_RECHECK=true node "$GATE" >/dev/null 2>"$TMP/b5.err" <<JSON
171
186
  {"hook_event_name":"Stop","cwd":"$F"}
172
187
  JSON
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env bash
2
+ # test_exemption_usage_review.sh — Integration eval for the Veritas Governance Kit's
3
+ # exemption-usage-review skill (issue #303, ADR 0022 §3).
4
+ #
5
+ # Proves review-exemptions.mjs correctly:
6
+ # AC1 review-lists-exemptions: parses a delivery/DECLARED fixture and lists every entry
7
+ # with scope, reason, approved_by, and computed age (deterministic via --as-of).
8
+ # AC2 review-flags-stale: against a mixed fresh/stale fixture, flags ONLY the entry
9
+ # outside --stale-days as stale, using exact age math.
10
+ # AC3 review-process-only-no-mutation: running the review does not modify
11
+ # delivery/DECLARED (byte-identical before/after) and does not change
12
+ # scripts/ci/trust-reconcile.js's exit code run against the same fixture tree
13
+ # before/after the review executes.
14
+ # AC9 no-fork: the skill/helper contains no copy of matchesScopeCondition/matchesScope/
15
+ # parseDeclaredMarker function bodies (structural grep, mirrors
16
+ # test_veritas_governance_kit.sh's no-fork assertion pattern).
17
+ # Output shape: --json emits {scope, reason, approved_by, declared_at, age_days, stale}
18
+ # per entry.
19
+ # Exit-0-always-informational: the script's own exit code is 0 regardless of how many
20
+ # entries are flagged stale (staleness is informational, never a script failure).
21
+ #
22
+ # Deterministic (--as-of pins "now"), no model spend, self-cleaning.
23
+ # Usage: bash evals/integration/test_exemption_usage_review.sh
24
+
25
+ set -uo pipefail
26
+
27
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
28
+ KIT="$ROOT/kits/veritas-governance"
29
+ HELPER="$KIT/skills/exemption-usage-review/review-exemptions.mjs"
30
+ SKILL_MD="$KIT/skills/exemption-usage-review/SKILL.md"
31
+ FIXTURE="$KIT/fixtures/exemption-review/mixed-fresh-stale.DECLARED.json"
32
+ RECONCILE="$ROOT/scripts/ci/trust-reconcile.js"
33
+
34
+ errors=0
35
+ TMP_DIR="$(mktemp -d)"
36
+ trap 'rm -rf "$TMP_DIR"' EXIT
37
+ pass() { echo " ✓ $1"; }
38
+ fail() { echo " ✗ $1"; errors=$((errors + 1)); }
39
+
40
+ echo "=== exemption-usage-review skill — integration eval ==="
41
+
42
+ # --- Sanity: required files exist ------------------------------------------------
43
+ if [[ -f "$HELPER" && -f "$SKILL_MD" && -f "$FIXTURE" ]]; then
44
+ pass "helper, SKILL.md, and fixture all exist"
45
+ else
46
+ fail "one or more required files missing (helper=$HELPER skill=$SKILL_MD fixture=$FIXTURE)"
47
+ fi
48
+
49
+ # --- AC1/AC2: listing + staleness math, deterministic via --as-of ----------------
50
+ echo "--- AC1/AC2: listing + staleness flagging ---"
51
+ AS_OF="2026-07-05T00:00:00Z"
52
+ STALE_DAYS=90
53
+
54
+ json_out="$(node "$HELPER" --declared-path "$FIXTURE" --repo-root "$ROOT" --as-of "$AS_OF" --stale-days "$STALE_DAYS" --json 2>&1)"
55
+ json_code=$?
56
+
57
+ if [[ "$json_code" -eq 0 ]]; then
58
+ pass "review-exemptions.mjs exits 0 on a clean run against the mixed fixture"
59
+ else
60
+ fail "review-exemptions.mjs expected exit 0, got $json_code — output: $json_out"
61
+ fi
62
+
63
+ echo "$json_out" > "$TMP_DIR/report.json"
64
+
65
+ if node -e "
66
+ const r = JSON.parse(require('fs').readFileSync('$TMP_DIR/report.json', 'utf8'));
67
+ if (!Array.isArray(r.entries) || r.entries.length !== 2) process.exit(1);
68
+ "; then
69
+ pass "AC1: exactly 2 entries listed from the mixed-fresh-stale fixture"
70
+ else
71
+ fail "AC1: expected exactly 2 entries in the report — output: $json_out"
72
+ fi
73
+
74
+ if node -e "
75
+ const r = JSON.parse(require('fs').readFileSync('$TMP_DIR/report.json', 'utf8'));
76
+ const [fresh, stale] = r.entries;
77
+ const required = ['scope', 'reason', 'approved_by', 'declared_at', 'age_days', 'stale'];
78
+ for (const e of r.entries) {
79
+ for (const f of required) {
80
+ if (!(f in e)) process.exit(1);
81
+ }
82
+ }
83
+ if (fresh.age_days !== 15) process.exit(1);
84
+ if (stale.age_days !== 185) process.exit(1);
85
+ "; then
86
+ pass "AC1: age_days computed correctly (15 for the fresh entry, 185 for the stale entry) against as-of $AS_OF"
87
+ else
88
+ fail "AC1: age_days math did not match expected values — output: $json_out"
89
+ fi
90
+
91
+ if node -e "
92
+ const r = JSON.parse(require('fs').readFileSync('$TMP_DIR/report.json', 'utf8'));
93
+ const [fresh, stale] = r.entries;
94
+ if (fresh.stale !== false) process.exit(1);
95
+ if (stale.stale !== true) process.exit(1);
96
+ "; then
97
+ pass "AC2: ONLY the entry outside the $STALE_DAYS-day threshold is flagged stale (fresh entry: false, stale entry: true)"
98
+ else
99
+ fail "AC2: staleness flagging diverged from expected (fresh=false, stale=true) — output: $json_out"
100
+ fi
101
+
102
+ # --- AC2 negative/flip case: lowering the threshold flips the fresh entry too ----
103
+ flip_out="$(node "$HELPER" --declared-path "$FIXTURE" --repo-root "$ROOT" --as-of "$AS_OF" --stale-days 10 --json 2>&1)"
104
+ echo "$flip_out" > "$TMP_DIR/flip.json"
105
+ if node -e "
106
+ const r = JSON.parse(require('fs').readFileSync('$TMP_DIR/flip.json', 'utf8'));
107
+ if (r.entries.length !== 2) process.exit(1);
108
+ if (!r.entries.every((e) => e.stale === true)) process.exit(1);
109
+ "; then
110
+ pass "AC2 threshold-flip: lowering --stale-days to 10 flips BOTH entries to stale (age math is threshold-relative, not hardcoded)"
111
+ else
112
+ fail "AC2 threshold-flip: lowering the threshold did not flip both entries stale — output: $flip_out"
113
+ fi
114
+
115
+ # --- Output-shape: human-readable mode also runs clean ---------------------------
116
+ human_out="$(node "$HELPER" --declared-path "$FIXTURE" --repo-root "$ROOT" --as-of "$AS_OF" --stale-days "$STALE_DAYS" 2>&1)"
117
+ human_code=$?
118
+ if [[ "$human_code" -eq 0 ]] && echo "$human_out" | grep -qF "PROCESS-ONLY: read-only report."; then
119
+ pass "human-readable report mode exits 0 and states the process-only boundary plainly"
120
+ else
121
+ fail "human-readable report mode did not exit 0 or did not state the process-only boundary — output: $human_out"
122
+ fi
123
+
124
+ # --- AC3: process-only, no mutation of delivery/DECLARED or trust-reconcile exit code --
125
+ echo "--- AC3: no-mutation lock (byte-diff DECLARED + reconciler exit-code identity) ---"
126
+ DECLARED_WORK="$TMP_DIR/declared-work"
127
+ mkdir -p "$DECLARED_WORK/delivery"
128
+ cp "$FIXTURE" "$DECLARED_WORK/delivery/DECLARED"
129
+
130
+ sha_before="$(shasum -a 256 "$DECLARED_WORK/delivery/DECLARED" | awk '{print $1}')"
131
+ RECONCILE_CMD="node -e 'process.exit(0)'"
132
+ recon_before_out="$(TRUST_RECONCILE_ACTOR="dependabot[bot]" TRUST_RECONCILE_COMMANDS="$RECONCILE_CMD" \
133
+ node "$RECONCILE" --repo-root "$DECLARED_WORK" 2>&1)"
134
+ recon_before_code=$?
135
+
136
+ # Run the review against the SAME fixture tree's DECLARED file (not the read-only source
137
+ # fixture) — this is the file whose byte-identity and reconciler-exit-code-identity we lock.
138
+ review_out="$(node "$HELPER" --declared-path "$DECLARED_WORK/delivery/DECLARED" --repo-root "$DECLARED_WORK" --as-of "$AS_OF" --stale-days "$STALE_DAYS" --json 2>&1)"
139
+ review_code=$?
140
+
141
+ sha_after="$(shasum -a 256 "$DECLARED_WORK/delivery/DECLARED" | awk '{print $1}')"
142
+ recon_after_out="$(TRUST_RECONCILE_ACTOR="dependabot[bot]" TRUST_RECONCILE_COMMANDS="$RECONCILE_CMD" \
143
+ node "$RECONCILE" --repo-root "$DECLARED_WORK" 2>&1)"
144
+ recon_after_code=$?
145
+
146
+ if [[ "$review_code" -eq 0 ]]; then
147
+ pass "review-exemptions.mjs itself exits 0 (informational only, regardless of stale entries present)"
148
+ else
149
+ fail "review-exemptions.mjs expected exit 0, got $review_code — output: $review_out"
150
+ fi
151
+
152
+ if [[ "$sha_before" == "$sha_after" ]]; then
153
+ pass "AC3: delivery/DECLARED bytes are IDENTICAL before/after the review ran (sha256 $sha_before)"
154
+ else
155
+ fail "AC3: delivery/DECLARED bytes CHANGED after the review ran (before=$sha_before after=$sha_after) — review mutated a file it must never touch"
156
+ fi
157
+
158
+ if [[ "$recon_before_code" == "$recon_after_code" ]]; then
159
+ pass "AC3: trust-reconcile.js's exit code is IDENTICAL before/after the review ran (both: $recon_before_code)"
160
+ else
161
+ fail "AC3: trust-reconcile.js's exit code DIVERGED before ($recon_before_code) vs after ($recon_after_code) the review ran — the audit-vs-enforcement boundary is broken"
162
+ fi
163
+
164
+ # --- AC9: no-fork — helper/SKILL.md contain no copy of the reconciler's scope-matching --
165
+ echo "--- AC9: no-fork (structural grep for vendored reconciler logic) ---"
166
+ if grep -nE "startsWith\('ref:'\)|startsWith\('commit:'\)|startsWith\('author:'\)|startsWith\('branch-prefix:'\)" "$HELPER" >/dev/null 2>&1; then
167
+ fail "AC9: review-exemptions.mjs contains vendored matchesScopeCondition-style branch logic (no-fork violated)"
168
+ else
169
+ pass "AC9: review-exemptions.mjs contains no vendored matchesScopeCondition branch logic"
170
+ fi
171
+
172
+ if grep -nE "^function (matchesScope|matchesScopeCondition|parseDeclaredMarker)\(" "$HELPER" "$SKILL_MD" >/dev/null 2>&1; then
173
+ fail "AC9: a matchesScope/matchesScopeCondition/parseDeclaredMarker function DEFINITION was found in the skill's own files (no-fork violated)"
174
+ else
175
+ pass "AC9: no matchesScope/matchesScopeCondition/parseDeclaredMarker function definition exists in the skill's own files (references-only, per SKILL.md's explicit non-reuse note)"
176
+ fi
177
+
178
+ # --- kit.json registration sanity (skill is registered, kit still validates) -----
179
+ FLOW_CLI="$ROOT/node_modules/@kontourai/flow/dist/cli.js"
180
+ if node "$FLOW_CLI" kit validate "$KIT" >"$TMP_DIR/kitval.out" 2>&1; then
181
+ pass "kit container still validates with the exemption-usage-review skill present"
182
+ else
183
+ fail "kit container failed validation with the skill present"; sed -n '1,40p' "$TMP_DIR/kitval.out"
184
+ fi
185
+ if grep -q '"id": *"veritas-governance\.exemption-usage-review"' "$KIT/kit.json" && grep -q '"path": *"skills/exemption-usage-review/SKILL.md"' "$KIT/kit.json"; then
186
+ pass "exemption-usage-review skill is registered in kit.json skills[]"
187
+ else
188
+ fail "exemption-usage-review skill is NOT registered in kit.json skills[]"
189
+ fi
190
+
191
+ echo ""
192
+ if [[ "$errors" -eq 0 ]]; then
193
+ echo "PASS: exemption-usage-review skill (listing, staleness, no-mutation lock, no-fork)"
194
+ exit 0
195
+ else
196
+ echo "FAIL: $errors check(s) failed"
197
+ exit 1
198
+ fi
@@ -21,9 +21,9 @@ json_query() {
21
21
  node -e 'const fs=require("fs"); let cur=JSON.parse(fs.readFileSync(process.argv[1],"utf8")); for (const part of process.argv[2].split(".")) cur=Array.isArray(cur) ? cur[Number(part)] : cur[part]; console.log(cur);' "$1" "$2"
22
22
  }
23
23
 
24
- [[ "$(json_query "$TMPDIR_EVAL/audit.json" "totals.scanned")" == "15" ]] && pass "audit scans all fixture groups" || fail "audit scans all fixture groups"
24
+ [[ "$(json_query "$TMPDIR_EVAL/audit.json" "totals.scanned")" == "19" ]] && pass "audit scans all fixture groups" || fail "audit scans all fixture groups"
25
25
  [[ "$(json_query "$TMPDIR_EVAL/audit.json" "totals.retire_candidates")" == "0" ]] && pass "audit finds no unowned retire candidates" || fail "audit finds no unowned retire candidates"
26
- [[ "$(json_query "$TMPDIR_EVAL/audit.json" "totals.kept")" == "15" ]] && pass "audit keeps all owned fixture groups" || fail "audit keeps all owned fixture groups"
26
+ [[ "$(json_query "$TMPDIR_EVAL/audit.json" "totals.kept")" == "19" ]] && pass "audit keeps all owned fixture groups" || fail "audit keeps all owned fixture groups"
27
27
 
28
28
  node - "$TMPDIR_EVAL/audit.json" <<'NODE'
29
29
  const fs = require("node:fs");
@@ -46,6 +46,27 @@ else
46
46
  sed -n '1,80p' "$default_out"
47
47
  fi
48
48
 
49
+ # Named acceptance outcomes must exercise the real catalog manifests, not only
50
+ # the generic resolver fixture.
51
+ for named_kit in builder knowledge; do
52
+ NAMED_HOME="$TMP_DIR/named-$named_kit-codex-home"
53
+ mkdir -p "$NAMED_HOME"
54
+ named_out="$TMP_DIR/named-$named_kit.out"
55
+ if CODEX_HOME="$NAMED_HOME" flow_agents_node "$CLI" install "$named_kit" >"$named_out" 2>&1 \
56
+ && [[ -f "$NAMED_HOME/kits/local/repositories/$named_kit/kit.json" ]] \
57
+ && node - "$NAMED_HOME/kits/local/installed-kits.json" "$named_kit" <<'NODE'
58
+ const fs = require("node:fs");
59
+ const registry = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
60
+ if (!registry.kits.some((kit) => kit.id === process.argv[3])) process.exit(1);
61
+ NODE
62
+ then
63
+ pass "$named_kit kit install without --dest uses isolated CODEX_HOME"
64
+ else
65
+ fail "$named_kit kit install without --dest did not use isolated CODEX_HOME"
66
+ sed -n '1,100p' "$named_out"
67
+ fi
68
+ done
69
+
49
70
  list_out="$TMP_DIR/default-list.out"
50
71
  if CODEX_HOME="$DEFAULT_CODEX_HOME" flow_agents_node "$CLI" list >"$list_out" 2>&1 \
51
72
  && grep -q "example-kit" "$list_out"; then
@@ -205,6 +226,68 @@ else
205
226
  fail "source kits/catalog.json changed during install-git test"
206
227
  fi
207
228
 
229
+ # --- Security regressions: symlink escapes and local-source overlap ---
230
+ REGISTRY_ESCAPE_DEST="$TMP_DIR/registry-escape-dest"
231
+ REGISTRY_ESCAPE_OUTSIDE="$TMP_DIR/registry-escape-outside.json"
232
+ mkdir -p "$REGISTRY_ESCAPE_DEST/kits/local"
233
+ printf '{"outside":"unchanged"}\n' > "$REGISTRY_ESCAPE_OUTSIDE"
234
+ ln -s "$REGISTRY_ESCAPE_OUTSIDE" "$REGISTRY_ESCAPE_DEST/kits/local/installed-kits.json"
235
+ if flow_agents_node "$CLI" install "$VALID_SRC" --dest "$REGISTRY_ESCAPE_DEST" >"$TMP_DIR/registry-escape.out" 2>&1; then
236
+ fail "symlinked kit registry should fail closed"
237
+ elif grep -q '"outside":"unchanged"' "$REGISTRY_ESCAPE_OUTSIDE"; then
238
+ pass "symlinked kit registry fails closed without outside write"
239
+ else
240
+ fail "symlinked kit registry modified outside file"
241
+ fi
242
+
243
+ REPOSITORY_ESCAPE_DEST="$TMP_DIR/repository-escape-dest"
244
+ REPOSITORY_ESCAPE_OUTSIDE="$TMP_DIR/repository-escape-outside"
245
+ mkdir -p "$REPOSITORY_ESCAPE_DEST/kits/local/repositories" "$REPOSITORY_ESCAPE_OUTSIDE"
246
+ printf 'unchanged\n' > "$REPOSITORY_ESCAPE_OUTSIDE/sentinel"
247
+ ln -s "$REPOSITORY_ESCAPE_OUTSIDE" "$REPOSITORY_ESCAPE_DEST/kits/local/repositories/example-kit"
248
+ if flow_agents_node "$CLI" install "$VALID_SRC" --dest "$REPOSITORY_ESCAPE_DEST" >"$TMP_DIR/repository-escape.out" 2>&1; then
249
+ fail "symlinked kit repository should fail closed"
250
+ elif [[ "$(cat "$REPOSITORY_ESCAPE_OUTSIDE/sentinel")" == "unchanged" && ! -e "$REPOSITORY_ESCAPE_OUTSIDE/kit.json" ]]; then
251
+ pass "symlinked kit repository fails closed without outside write"
252
+ else
253
+ fail "symlinked kit repository modified outside directory"
254
+ fi
255
+
256
+ SELF_DEST="$TMP_DIR/self-overlap-dest"
257
+ SELF_SOURCE="$SELF_DEST/kits/local/repositories/example-kit"
258
+ mkdir -p "$(dirname "$SELF_SOURCE")"
259
+ cp -R "$VALID_SRC" "$SELF_SOURCE"
260
+ if flow_agents_node "$CLI" install "$SELF_SOURCE" --dest "$SELF_DEST" >"$TMP_DIR/self-overlap.out" 2>&1; then
261
+ fail "exact local-kit self-install should be rejected"
262
+ elif [[ -f "$SELF_SOURCE/kit.json" ]] && grep -q "refusing overlapping paths" "$TMP_DIR/self-overlap.out"; then
263
+ pass "exact local-kit self-install is rejected before source mutation"
264
+ else
265
+ fail "exact local-kit self-install did not preserve its source"
266
+ fi
267
+
268
+ DESCENDANT_DEST="$TMP_DIR/descendant-overlap-dest"
269
+ cp -R "$VALID_SRC" "$DESCENDANT_DEST"
270
+ if flow_agents_node "$CLI" install "$DESCENDANT_DEST" --dest "$DESCENDANT_DEST" >"$TMP_DIR/descendant-overlap.out" 2>&1; then
271
+ fail "kit target nested beneath source should be rejected"
272
+ elif [[ -f "$DESCENDANT_DEST/kit.json" ]] && grep -q "refusing overlapping paths" "$TMP_DIR/descendant-overlap.out"; then
273
+ pass "kit target nested beneath source is rejected before mutation"
274
+ else
275
+ fail "source-ancestor overlap did not preserve source"
276
+ fi
277
+
278
+ ANCESTOR_DEST="$TMP_DIR/ancestor-overlap-dest"
279
+ ANCESTOR_TARGET="$ANCESTOR_DEST/kits/local/repositories/example-kit"
280
+ ANCESTOR_SOURCE="$ANCESTOR_TARGET/nested-source"
281
+ mkdir -p "$ANCESTOR_TARGET"
282
+ cp -R "$VALID_SRC" "$ANCESTOR_SOURCE"
283
+ if flow_agents_node "$CLI" install "$ANCESTOR_SOURCE" --dest "$ANCESTOR_DEST" >"$TMP_DIR/ancestor-overlap.out" 2>&1; then
284
+ fail "kit source nested beneath target should be rejected"
285
+ elif [[ -f "$ANCESTOR_SOURCE/kit.json" ]] && grep -q "refusing overlapping paths" "$TMP_DIR/ancestor-overlap.out"; then
286
+ pass "kit source nested beneath target is rejected before mutation"
287
+ else
288
+ fail "target-ancestor overlap did not preserve source"
289
+ fi
290
+
208
291
  echo ""
209
292
  if [[ "$errors" -eq 0 ]]; then
210
293
  echo "install-git checks passed."
@@ -143,7 +143,6 @@ flow_agents_node "$WRITER" ensure-session \
143
143
  --title "Tamper verify test" \
144
144
  --summary "Testing tamper detection at verify step." \
145
145
  --flow-id builder.build \
146
- --step-id verify \
147
146
  --timestamp "2026-06-01T02:00:00Z" >/dev/null 2>&1
148
147
 
149
148
  flow_agents_node "$WRITER" init-plan "$TAMPER_DIR/.kontourai/flow-agents/$TAMPER_SLUG/$TAMPER_SLUG--deliver.md" \
@@ -39,20 +39,25 @@ SLUG="history-flow-test"
39
39
  SESSION_DIR="$FLOW_AROOT/$SLUG"
40
40
  mkdir -p "$FLOW_AROOT"
41
41
 
42
- # Create a FlowDefinition-driven session at the verify step (builder.verify.tests is declared)
42
+ # Start at the declared first step, then establish the verify-state fixture.
43
43
  flow_agents_node "$WRITER" ensure-session \
44
44
  --artifact-root "$FLOW_AROOT" \
45
45
  --task-slug "$SLUG" \
46
46
  --title "History preservation test" \
47
47
  --summary "Test that declared builder.* claims survive round-trips." \
48
48
  --flow-id builder.build \
49
- --step-id verify \
50
49
  --timestamp "2026-06-01T00:00:00Z" >/dev/null 2>&1
51
50
 
52
51
  flow_agents_node "$WRITER" init-plan "$SESSION_DIR/$SLUG--deliver.md" \
53
52
  --source-request "Test" --summary "Testing" \
54
53
  --timestamp "2026-06-01T00:00:00Z" >/dev/null 2>&1
55
54
 
55
+ flow_agents_node "$WRITER" advance-state "$SESSION_DIR" \
56
+ --status in_progress --phase verification \
57
+ --summary "Testing at verify." --next-action "Record evidence." \
58
+ --flow-definition builder.build \
59
+ --timestamp "2026-06-01T00:00:30Z" >/dev/null 2>&1
60
+
56
61
  # Record a passing check (produces ONLY builder.verify.tests declared claim — no legacy shadow, P-d)
57
62
  flow_agents_node "$WRITER" record-evidence "$SESSION_DIR" \
58
63
  --verdict pass \
@@ -196,13 +201,18 @@ flow_agents_node "$WRITER" ensure-session \
196
201
  --title "Dogfood clean test" \
197
202
  --summary "Test evidenceClean/critiqueClean on builder.build session." \
198
203
  --flow-id builder.build \
199
- --step-id verify \
200
204
  --timestamp "2026-06-01T20:00:00Z" >/dev/null 2>&1
201
205
 
202
206
  flow_agents_node "$WRITER" init-plan "$DOGFOOD_DIR/$DOGFOOD_SLUG--deliver.md" \
203
207
  --source-request "Test" --summary "Testing" \
204
208
  --timestamp "2026-06-01T20:00:00Z" >/dev/null 2>&1
205
209
 
210
+ flow_agents_node "$WRITER" advance-state "$DOGFOOD_DIR" \
211
+ --status in_progress --phase verification \
212
+ --summary "Testing at verify." --next-action "Record evidence." \
213
+ --flow-definition builder.build \
214
+ --timestamp "2026-06-01T20:00:30Z" >/dev/null 2>&1
215
+
206
216
  # Record pass evidence (produces builder.verify.tests declared claim, status=verified)
207
217
  flow_agents_node "$WRITER" record-evidence "$DOGFOOD_DIR" \
208
218
  --verdict pass \
@@ -872,6 +872,11 @@ cp "$ROOT/scripts/hooks/lib/local-artifact-paths.js" "$ISO_DIR/lib/"
872
872
  # section is testing.
873
873
  cp "$ROOT/scripts/hooks/lib/actor-identity.js" "$ISO_DIR/lib/"
874
874
  cp "$ROOT/scripts/hooks/lib/current-pointer.js" "$ISO_DIR/lib/"
875
+ # #412: stop-goal-fit.js now also requires scripts/hooks/lib/runnable-command.js (the shared
876
+ # isRunnableCommandText heuristic, single-sourced with workflow-sidecar.ts) — mirror it too, or
877
+ # the isolated gate crashes on MODULE_NOT_FOUND before it ever reaches the surface-unavailable
878
+ # fail-closed path this section is testing.
879
+ cp "$ROOT/scripts/hooks/lib/runnable-command.js" "$ISO_DIR/lib/"
875
880
  printf '# Repo\n' > "$ISO_DIR/repo/AGENTS.md"
876
881
  # Non-terminal session (execution phase, in_progress status)
877
882
  printf '%s' '{"schema_version":"1.0","task_slug":"surftest","status":"in_progress","phase":"execution","updated_at":"2026-06-27T00:00:00Z","next_action":{"status":"in_progress","summary":"running"}}' \
@@ -921,6 +926,8 @@ cp "$ROOT/scripts/hooks/lib/local-artifact-paths.js" "$ISO2_DIR/lib/"
921
926
  # #291: same rationale as ISO_DIR above -- mirror the two new scripts/hooks/lib dependencies.
922
927
  cp "$ROOT/scripts/hooks/lib/actor-identity.js" "$ISO2_DIR/lib/"
923
928
  cp "$ROOT/scripts/hooks/lib/current-pointer.js" "$ISO2_DIR/lib/"
929
+ # #412: see the ISO_DIR mirror above — same requirement applies to this second isolated copy.
930
+ cp "$ROOT/scripts/hooks/lib/runnable-command.js" "$ISO2_DIR/lib/"
924
931
  printf '# Repo\n' > "$ISO2_DIR/repo/AGENTS.md"
925
932
  printf '%s' '{"schema_version":"1.0","task_slug":"lowtest","status":"in_progress","phase":"execution","updated_at":"2026-06-27T00:00:00Z","next_action":{"status":"in_progress","summary":"running"}}' \
926
933
  > "$ISO2_DIR/repo/.kontourai/flow-agents/lowtest/state.json"
@@ -25,6 +25,14 @@ trap cleanup EXIT
25
25
  _pass() { echo " ✓ $1"; }
26
26
  _fail() { echo " ✗ $1"; errors=$((errors + 1)); }
27
27
 
28
+ hook_tree_digest() {
29
+ find "$ROOT/scripts/hooks" -type f -print | LC_ALL=C sort | while IFS= read -r file; do
30
+ shasum -a 256 "$file"
31
+ done | shasum -a 256 | awk '{print $1}'
32
+ }
33
+
34
+ HOOKS_BEFORE="$(hook_tree_digest)"
35
+
28
36
  echo "=== Gate Review InquiryRecord Tests (AC1 + AC2) ==="
29
37
 
30
38
  # ── helpers ──────────────────────────────────────────────────────────────────
@@ -385,7 +393,7 @@ fi
385
393
  # ── AC3: no hooks changed ─────────────────────────────────────────────────────
386
394
  echo ""
387
395
  echo "--- AC3: hooks unchanged ---"
388
- if git -C "$ROOT" diff origin/main -- scripts/hooks/ 2>/dev/null | grep -q '^[+-]'; then
396
+ if [[ "$(hook_tree_digest)" != "$HOOKS_BEFORE" ]]; then
389
397
  _fail "AC3: scripts/hooks/ was modified (gate-review must not touch hooks)"
390
398
  else
391
399
  _pass "AC3: scripts/hooks/ unchanged"