@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,166 @@
1
+ #!/usr/bin/env bash
2
+ # test_liveness_console_relay.sh — optional console liveness relay (#295, ADR 0021 §4/§7).
3
+ #
4
+ # Proves the flow-agents EMIT half of the fleet relay: when opted in + configured, a liveness event
5
+ # is mirrored to the Console as a `kontour.console.liveness` record over the SAME transport core the
6
+ # telemetry mirror uses (Bearer + tenant auth). And — non-negotiable — local-first holds: with the
7
+ # relay off or unconfigured, or when the relay POST fails, the durable local `liveness/events.jsonl`
8
+ # write always succeeds and nothing propagates.
9
+ #
10
+ # Deterministic (a local stub HTTP server, no network/model spend, self-cleaning). The relay POST is
11
+ # detached+async, so the stub-received assertions poll with a timeout.
12
+ # Usage: bash evals/integration/test_liveness_console_relay.sh
13
+
14
+ set -uo pipefail
15
+
16
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
17
+ source "$ROOT/evals/lib/node.sh"
18
+
19
+ TMPDIR_EVAL="$(mktemp -d)"
20
+ trap 'rm -rf "$TMPDIR_EVAL"; [[ -n "${STUB_PID:-}" ]] && kill "$STUB_PID" 2>/dev/null' EXIT
21
+ PORT=38795
22
+ RECV="$TMPDIR_EVAL/recv.jsonl"
23
+
24
+ errors=0
25
+ pass() { echo " [PASS] $1"; }
26
+ fail() { echo " [FAIL] $1"; errors=$((errors + 1)); }
27
+
28
+ # Stub Console endpoint: append each received {method, headers, body} as one JSON line. `mode` env
29
+ # controls the response — "ok" (200) or "fail" (500) — to exercise the relay-failure path.
30
+ start_stub() {
31
+ local mode="$1"
32
+ STUB_MODE="$mode" node -e '
33
+ const http=require("http"),fs=require("fs");
34
+ const out=process.argv[1], mode=process.env.STUB_MODE||"ok";
35
+ const s=http.createServer((req,res)=>{let b="";req.on("data",d=>b+=d);req.on("end",()=>{
36
+ fs.appendFileSync(out, JSON.stringify({method:req.method,headers:req.headers,body:b})+"\n");
37
+ res.writeHead(mode==="fail"?500:200); res.end(mode==="fail"?"err":"ok");
38
+ });});
39
+ s.listen(Number(process.argv[2]),"127.0.0.1");
40
+ setTimeout(()=>process.exit(0),30000);
41
+ ' "$RECV" "$PORT" >/dev/null 2>&1 &
42
+ STUB_PID=$!
43
+ # wait for bind
44
+ for _ in $(seq 1 20); do
45
+ if node -e 'const n=require("net");const s=n.connect(Number(process.argv[1]),"127.0.0.1");s.on("connect",()=>{s.end();process.exit(0)});s.on("error",()=>process.exit(1))' "$PORT" 2>/dev/null; then return 0; fi
46
+ sleep 0.2
47
+ done
48
+ return 1
49
+ }
50
+ stop_stub() { [[ -n "${STUB_PID:-}" ]] && kill "$STUB_PID" 2>/dev/null; STUB_PID=""; }
51
+
52
+ # Emit a liveness event through the REAL wire-in (appendLivenessEvent), inheriting the caller's env.
53
+ emit_event() {
54
+ local root="$1" evt_json="$2"
55
+ node -e 'const {appendLivenessEvent}=require(process.argv[1]);appendLivenessEvent(process.argv[2], JSON.parse(process.argv[3]));' \
56
+ "$ROOT/scripts/hooks/lib/liveness-write.js" "$root" "$evt_json"
57
+ }
58
+
59
+ # Poll the stub's received file for at least one POST (the relay is detached/async).
60
+ wait_for_post() { for _ in $(seq 1 25); do [[ -s "$RECV" ]] && return 0; sleep 0.2; done; return 1; }
61
+
62
+ ENDPOINT="http://127.0.0.1:${PORT}/records"
63
+
64
+ echo "=== console liveness relay (#295) ==="
65
+
66
+ # ─── 1. Relay ON + configured: POST a kontour.console.liveness record with auth ────────────────
67
+ echo "--- 1. relay on+configured: liveness emit mirrors to the Console with Bearer + tenant ---"
68
+ : > "$RECV"; start_stub ok || { fail "stub server did not start"; }
69
+ ROOT_A="$TMPDIR_EVAL/a"
70
+ EVT1='{"type":"claim","subjectId":"relay-subj-1","actor":"claude-code:sessA:host","actor_key":"claude-code:sessA:host","at":"2026-07-05T00:00:00Z","ttlSeconds":1800,"host":"host","branch":"agent/a/relay-subj-1","artifact_dir":"relay-subj-1"}'
71
+ (
72
+ export FLOW_AGENTS_CONSOLE_LIVENESS_RELAY=1
73
+ export FLOW_AGENTS_CONSOLE_LIVENESS_ENDPOINT_URL="$ENDPOINT"
74
+ export CONSOLE_TELEMETRY_TOKEN="tok-abc123"
75
+ export CONSOLE_TENANT_ID="tenant-1"
76
+ emit_event "$ROOT_A" "$EVT1"
77
+ )
78
+ [[ -s "$ROOT_A/liveness/events.jsonl" ]] && pass "local liveness/events.jsonl written (local-first)" || fail "local liveness write did not happen"
79
+ if wait_for_post; then pass "relay POSTed to the Console endpoint"; else fail "relay did NOT POST within timeout"; fi
80
+ node -e '
81
+ const fs=require("fs"); const raw=fs.readFileSync(process.argv[1],"utf8").trim();
82
+ if(!raw){console.log("NO_POST");process.exit(0)}
83
+ const r=JSON.parse(raw.split("\n")[0]); const b=JSON.parse(r.body);
84
+ const checks=[
85
+ ["method POST", r.method==="POST"],
86
+ ["content-type json", (r.headers["content-type"]||"").includes("application/json")],
87
+ ["authorization bearer", r.headers["authorization"]==="Bearer tok-abc123"],
88
+ ["tenant header", r.headers["x-console-tenant-id"]==="tenant-1"],
89
+ ["schema kontour.console.liveness", b.schema==="kontour.console.liveness"],
90
+ ["version 0.1", b.version==="0.1"],
91
+ ["type claim", b.type==="claim"],
92
+ ["subjectId", b.subjectId==="relay-subj-1"],
93
+ ["actor", b.actor==="claude-code:sessA:host"],
94
+ ["branch", b.branch==="agent/a/relay-subj-1"],
95
+ ];
96
+ for(const [name,ok] of checks) console.log((ok?"OK ":"BAD ")+name);
97
+ ' "$RECV" > "$TMPDIR_EVAL/checks.txt" 2>&1
98
+ while IFS= read -r line; do
99
+ [[ "$line" == OK* ]] && pass "record ${line#OK }" || fail "record ${line#BAD } — $(cat "$TMPDIR_EVAL/checks.txt")"
100
+ done < <(grep -E '^(OK|BAD) ' "$TMPDIR_EVAL/checks.txt")
101
+ stop_stub
102
+
103
+ # ─── 2. Relay OFF (no flag): local write happens, NO POST (local-first) ────────────────────────
104
+ echo "--- 2. relay OFF: local write still happens, nothing is POSTed ---"
105
+ : > "$RECV"; start_stub ok
106
+ ROOT_B="$TMPDIR_EVAL/b"
107
+ ( export FLOW_AGENTS_CONSOLE_LIVENESS_ENDPOINT_URL="$ENDPOINT"; unset FLOW_AGENTS_CONSOLE_LIVENESS_RELAY; emit_event "$ROOT_B" '{"type":"claim","subjectId":"off-1","actor":"a","at":"t"}' )
108
+ sleep 1
109
+ [[ -s "$ROOT_B/liveness/events.jsonl" ]] && pass "local write happened with relay OFF" || fail "local write missing with relay OFF"
110
+ [[ ! -s "$RECV" ]] && pass "NO POST when the relay flag is off (opt-in respected)" || fail "a POST happened with the relay off"
111
+ stop_stub
112
+
113
+ # ─── 3. Relay ON but NO endpoint configured: no-op, local write intact ──────────────────────────
114
+ echo "--- 3. relay on but unconfigured (no endpoint): no-op, local write intact ---"
115
+ : > "$RECV"; start_stub ok
116
+ ROOT_C="$TMPDIR_EVAL/c"
117
+ ( export FLOW_AGENTS_CONSOLE_LIVENESS_RELAY=1; unset FLOW_AGENTS_CONSOLE_LIVENESS_ENDPOINT_URL FLOW_AGENTS_CONSOLE_URL CONSOLE_TELEMETRY_URL CONSOLE_URL; emit_event "$ROOT_C" '{"type":"claim","subjectId":"noep-1","actor":"a","at":"t"}' )
118
+ sleep 1
119
+ [[ -s "$ROOT_C/liveness/events.jsonl" ]] && pass "local write happened with no endpoint configured" || fail "local write missing with no endpoint"
120
+ [[ ! -s "$RECV" ]] && pass "NO POST when no console endpoint is configured (no-op)" || fail "a POST happened with no endpoint configured"
121
+ stop_stub
122
+
123
+ # ─── 4. Relay FAILURE (endpoint returns 500): local emit still succeeds, non-fatal ─────────────
124
+ echo "--- 4. relay failure (endpoint 500 / detached): local emit exit unaffected ---"
125
+ : > "$RECV"; start_stub fail
126
+ ROOT_D="$TMPDIR_EVAL/d"
127
+ (
128
+ export FLOW_AGENTS_CONSOLE_LIVENESS_RELAY=1
129
+ export FLOW_AGENTS_CONSOLE_LIVENESS_ENDPOINT_URL="$ENDPOINT"
130
+ emit_event "$ROOT_D" '{"type":"heartbeat","subjectId":"fail-1","actor":"a","at":"t"}'
131
+ )
132
+ rc=$?
133
+ [[ "$rc" -eq 0 ]] && pass "liveness emit exits 0 even when the relay endpoint fails (non-fatal)" || fail "liveness emit exited non-zero ($rc) on relay failure"
134
+ [[ -s "$ROOT_D/liveness/events.jsonl" ]] && pass "local write happened despite relay 500 (local-first)" || fail "local write missing on relay failure"
135
+ stop_stub
136
+
137
+ # ─── 5. Injection: a hostile actor/branch is JSON-escaped, never raw in the POST body ──────────
138
+ echo "--- 5. injection: hostile control bytes in actor/branch are escaped, not raw ---"
139
+ : > "$RECV"; start_stub ok
140
+ ROOT_E="$TMPDIR_EVAL/e"
141
+ # Build the hostile event in node (embed ESC 0x1b + BEL 0x07) so no control bytes enter the shell.
142
+ HOSTILE_EVT="$(node -e 'const esc=String.fromCharCode(27),bel=String.fromCharCode(7);process.stdout.write(JSON.stringify({type:"claim",subjectId:"inj-1",actor:"actor"+esc+"[31m"+bel+"EVIL",at:"t",branch:"agent/"+esc+"x/inj-1"}))')"
143
+ (
144
+ export FLOW_AGENTS_CONSOLE_LIVENESS_RELAY=1
145
+ export FLOW_AGENTS_CONSOLE_LIVENESS_ENDPOINT_URL="$ENDPOINT"
146
+ emit_event "$ROOT_E" "$HOSTILE_EVT"
147
+ )
148
+ wait_for_post || true
149
+ node -e '
150
+ const fs=require("fs"); const raw=fs.readFileSync(process.argv[1],"utf8").trim();
151
+ if(!raw){console.log("NO_POST");process.exit(0)}
152
+ const body=JSON.parse(raw.split("\n")[0]).body;
153
+ const hasRawCtl=/[\x00-\x08\x0b\x0c\x0e-\x1f]/.test(body);
154
+ console.log(hasRawCtl?"RAW":"ESCAPED");
155
+ ' "$RECV" > "$TMPDIR_EVAL/inj.txt" 2>&1
156
+ if grep -q ESCAPED "$TMPDIR_EVAL/inj.txt"; then pass "hostile actor/branch control bytes are JSON-escaped, never raw in the POST body (injection discipline)"; else fail "raw control bytes reached the POST body: $(cat "$TMPDIR_EVAL/inj.txt")"; fi
157
+ stop_stub
158
+
159
+ echo ""
160
+ if [[ "$errors" -eq 0 ]]; then
161
+ echo "test_liveness_console_relay: all checks passed."
162
+ exit 0
163
+ else
164
+ echo "test_liveness_console_relay: $errors check(s) failed."
165
+ exit 1
166
+ fi
@@ -109,17 +109,13 @@ NODE
109
109
 
110
110
  # is_valid_json_stdin <string> — true if the given string parses as JSON.
111
111
  is_valid_json() {
112
- printf '%s' "$1" | node - <<'NODE'
113
- let raw = '';
114
- process.stdin.on('data', c => (raw += c));
115
- process.stdin.on('end', () => {
116
- try {
117
- JSON.parse(raw);
118
- process.exit(0);
119
- } catch {
120
- process.exit(1);
121
- }
122
- });
112
+ JSON_ARG="$1" node - <<'NODE'
113
+ try {
114
+ JSON.parse(process.env.JSON_ARG);
115
+ process.exit(0);
116
+ } catch {
117
+ process.exit(1);
118
+ }
123
119
  NODE
124
120
  }
125
121
 
@@ -342,6 +338,8 @@ fi
342
338
  echo "--- D. End-to-end wrapper (claude-telemetry-hook.js) ---"
343
339
 
344
340
  POST_TOOL_USE_PAYLOAD='{"hook_event_name":"PostToolUse","tool_name":"Bash","tool_input":{"command":"echo hi"},"tool_response":{"stdout":"hi"}}'
341
+ POST_TOOL_USE_PAYLOAD_FILE="$TMPDIR_EVAL/post-tool-use-payload.json"
342
+ printf '%s' "$POST_TOOL_USE_PAYLOAD" >"$POST_TOOL_USE_PAYLOAD_FILE"
345
343
 
346
344
  # D1: a fresh (well past the 60s throttle, real clock) claim exists for the resolved
347
345
  # actor -> exactly one heartbeat event is appended by the wrapper's postToolUse path.
@@ -349,7 +347,7 @@ D1_ROOT="$(new_scratch)"
349
347
  D1_STALE_AT="$(iso_offset_ms -300000)"
350
348
  seed_claim "$D1_ROOT" "d1-subj" "agent-hb" "$D1_STALE_AT" 1800
351
349
  D1_OUT="$(cd "$D1_ROOT" && TELEMETRY_ENABLED=false FLOW_AGENTS_ACTOR=agent-hb \
352
- node "$CLAUDE_HOOK" PostToolUse dev <<<"$POST_TOOL_USE_PAYLOAD")"
350
+ node "$CLAUDE_HOOK" PostToolUse dev <"$POST_TOOL_USE_PAYLOAD_FILE")"
353
351
  D1_STATUS=$?
354
352
  if [[ "$D1_STATUS" -eq 0 ]] \
355
353
  && is_valid_json "$D1_OUT" \
@@ -366,7 +364,7 @@ D2_ROOT="$(new_scratch)"
366
364
  mkdir -p "$D2_ROOT/.kontourai/flow-agents/liveness"
367
365
  printf '{not valid json' >"$D2_ROOT/.kontourai/flow-agents/$SNAPSHOT_FILENAME"
368
366
  D2_OUT="$(cd "$D2_ROOT" && TELEMETRY_ENABLED=false FLOW_AGENTS_ACTOR=agent-hb \
369
- node "$CLAUDE_HOOK" PostToolUse dev <<<"$POST_TOOL_USE_PAYLOAD" 2>"$TMPDIR_EVAL/d2.err")"
367
+ node "$CLAUDE_HOOK" PostToolUse dev <"$POST_TOOL_USE_PAYLOAD_FILE" 2>"$TMPDIR_EVAL/d2.err")"
370
368
  D2_STATUS=$?
371
369
  if [[ "$D2_STATUS" -eq 0 ]] \
372
370
  && is_valid_json "$D2_OUT" \
@@ -383,7 +381,7 @@ E_ROOT="$(new_scratch)"
383
381
  E_STALE_AT="$(iso_offset_ms -300000)"
384
382
  seed_claim "$E_ROOT" "e-subj" "agent-hb" "$E_STALE_AT" 1800
385
383
  E_OUT="$(cd "$E_ROOT" && TELEMETRY_ENABLED=false FLOW_AGENTS_ACTOR=agent-hb \
386
- node "$CLAUDE_HOOK" PostToolUse dev <<<"$POST_TOOL_USE_PAYLOAD")"
384
+ node "$CLAUDE_HOOK" PostToolUse dev <"$POST_TOOL_USE_PAYLOAD_FILE")"
387
385
  E_STATUS=$?
388
386
  if [[ "$E_STATUS" -eq 0 ]] && [[ "$(stream_line_count "$E_ROOT")" -eq 2 ]] \
389
387
  && grep -q '"type":"heartbeat"' "$(stream_file "$E_ROOT")"; then
@@ -396,11 +394,13 @@ fi
396
394
  echo "--- F. Cross-runtime smoke (codex-telemetry-hook.js) ---"
397
395
 
398
396
  CODEX_PAYLOAD='{"hook_event_name":"PostToolUse","tool_name":"shell","tool_input":{"command":"echo hi"}}'
397
+ CODEX_PAYLOAD_FILE="$TMPDIR_EVAL/codex-payload.json"
398
+ printf '%s' "$CODEX_PAYLOAD" >"$CODEX_PAYLOAD_FILE"
399
399
  F_ROOT="$(new_scratch)"
400
400
  F_STALE_AT="$(iso_offset_ms -300000)"
401
401
  seed_claim "$F_ROOT" "f-subj" "agent-hb" "$F_STALE_AT" 1800
402
402
  F_OUT="$(cd "$F_ROOT" && TELEMETRY_ENABLED=false FLOW_AGENTS_ACTOR=agent-hb \
403
- node "$CODEX_HOOK" PostToolUse dev <<<"$CODEX_PAYLOAD")"
403
+ node "$CODEX_HOOK" PostToolUse dev <"$CODEX_PAYLOAD_FILE")"
404
404
  F_STATUS=$?
405
405
  if [[ "$F_STATUS" -eq 0 ]] && [[ "$(stream_line_count "$F_ROOT")" -eq 2 ]] \
406
406
  && grep -q '"type":"heartbeat"' "$(stream_file "$F_ROOT")"; then
@@ -415,8 +415,8 @@ echo "--- G. Wrapper-level throttle smoke ---"
415
415
  G_ROOT="$(new_scratch)"
416
416
  G_STALE_AT="$(iso_offset_ms -300000)"
417
417
  seed_claim "$G_ROOT" "g-subj" "agent-hb" "$G_STALE_AT" 1800
418
- (cd "$G_ROOT" && TELEMETRY_ENABLED=false FLOW_AGENTS_ACTOR=agent-hb node "$CLAUDE_HOOK" PostToolUse dev <<<"$POST_TOOL_USE_PAYLOAD" >/dev/null)
419
- (cd "$G_ROOT" && TELEMETRY_ENABLED=false FLOW_AGENTS_ACTOR=agent-hb node "$CLAUDE_HOOK" PostToolUse dev <<<"$POST_TOOL_USE_PAYLOAD" >/dev/null)
418
+ (cd "$G_ROOT" && TELEMETRY_ENABLED=false FLOW_AGENTS_ACTOR=agent-hb node "$CLAUDE_HOOK" PostToolUse dev <"$POST_TOOL_USE_PAYLOAD_FILE" >/dev/null)
419
+ (cd "$G_ROOT" && TELEMETRY_ENABLED=false FLOW_AGENTS_ACTOR=agent-hb node "$CLAUDE_HOOK" PostToolUse dev <"$POST_TOOL_USE_PAYLOAD_FILE" >/dev/null)
420
420
  if [[ "$(stream_line_count "$G_ROOT")" -eq 2 ]]; then
421
421
  _pass "two back-to-back wrapper invocations append exactly one heartbeat (real-clock throttle smoke, AC3)"
422
422
  else