@kontourai/flow-agents 2.3.0 → 2.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 (250) hide show
  1. package/.github/CODEOWNERS +8 -0
  2. package/.github/workflows/ci.yml +32 -0
  3. package/AGENTS.md +4 -4
  4. package/CHANGELOG.md +39 -0
  5. package/CONTEXT.md +14 -0
  6. package/README.md +9 -3
  7. package/agents/dev.json +1 -1
  8. package/agents/tool-code-reviewer.json +1 -1
  9. package/agents/tool-planner.json +3 -3
  10. package/agents/tool-verifier.json +3 -3
  11. package/build/src/cli/console-learning-projection.js +3 -2
  12. package/build/src/cli/init.js +104 -21
  13. package/build/src/cli/kit.js +37 -6
  14. package/build/src/cli/pull-work-provider.js +1 -1
  15. package/build/src/cli/usage-feedback.js +3 -3
  16. package/build/src/cli/validate-hook-influence.js +1 -0
  17. package/build/src/cli/validate-workflow-artifacts.js +22 -6
  18. package/build/src/cli/workflow-artifact-cleanup-audit.js +36 -2
  19. package/build/src/cli/workflow-sidecar.d.ts +14 -7
  20. package/build/src/cli/workflow-sidecar.js +792 -69
  21. package/build/src/flow-kit/validate.d.ts +23 -0
  22. package/build/src/flow-kit/validate.js +67 -1
  23. package/build/src/index.d.ts +1 -1
  24. package/build/src/index.js +1 -1
  25. package/build/src/lib/flow-resolver.d.ts +6 -4
  26. package/build/src/lib/flow-resolver.js +125 -58
  27. package/build/src/lib/local-artifact-root.d.ts +14 -2
  28. package/build/src/lib/local-artifact-root.js +22 -5
  29. package/build/src/lib/workflow-learning-projection.js +2 -2
  30. package/build/src/runtime-adapters.d.ts +12 -0
  31. package/build/src/runtime-adapters.js +56 -16
  32. package/build/src/tools/build-universal-bundles.js +49 -16
  33. package/build/src/tools/generate-context-map.js +10 -8
  34. package/build/src/tools/validate-source-tree.js +23 -1
  35. package/console.telemetry.json +22 -0
  36. package/context/contracts/artifact-contract.md +38 -7
  37. package/context/contracts/builder-kit-workflow-state-contract.md +3 -2
  38. package/context/contracts/decision-registry-contract.md +263 -0
  39. package/context/contracts/delivery-contract.md +1 -1
  40. package/context/contracts/planning-contract.md +2 -1
  41. package/context/contracts/review-contract.md +5 -3
  42. package/context/contracts/verification-contract.md +9 -1
  43. package/context/contracts/work-item-contract.md +1 -1
  44. package/context/deferred/parallelization.md +1 -1
  45. package/context/gate-awareness.md +4 -4
  46. package/context/scripts/git-status.sh +2 -2
  47. package/context/scripts/hooks/config-protection.js +502 -2
  48. package/context/scripts/hooks/lib/config-protection-remedies.js +69 -0
  49. package/context/scripts/hooks/run-hook.js +14 -1
  50. package/context/scripts/hooks/stop-goal-fit.js +1140 -99
  51. package/context/scripts/hooks/workflow-steering.js +212 -6
  52. package/docs/adr/0007-flow-skill-kit-tool-boundary.md +2 -0
  53. package/docs/adr/0015-flow-flow-agents-boundary-reconciliation.md +35 -0
  54. package/docs/adr/0019-kit-dependency-ownership.md +62 -0
  55. package/docs/adr/0020-trust-reconcile-manifest-and-claim-classification.md +290 -0
  56. package/docs/adr/0021-assignment-leases-and-stale-claim-takeover.md +221 -0
  57. package/docs/adr/0022-fail-closed-delivery-reconciliation-with-governed-exemptions.md +283 -0
  58. package/docs/agent-system-guidebook.md +3 -3
  59. package/docs/agent-usage-feedback-loop.md +6 -6
  60. package/docs/context-map.md +9 -7
  61. package/docs/decisions/decision-records.md +57 -0
  62. package/docs/decisions/index.md +14 -0
  63. package/docs/decisions/promotion-gate.md +52 -0
  64. package/docs/developer-architecture.md +1 -1
  65. package/docs/fixture-ownership.md +3 -0
  66. package/docs/flow-kit-repository-contract.md +5 -5
  67. package/docs/getting-started.md +12 -2
  68. package/docs/integrations/harness-install.md +2 -1
  69. package/docs/kit-authoring-guide.md +64 -5
  70. package/docs/knowledge-kit.md +5 -1
  71. package/docs/learnings/2026-07-improvement-program.md +203 -0
  72. package/docs/migrations.md +6 -5
  73. package/docs/north-star.md +2 -2
  74. package/docs/operating-layers.md +4 -3
  75. package/docs/repository-structure.md +5 -5
  76. package/docs/skills-map.md +8 -8
  77. package/docs/spec/runtime-hook-surface.md +19 -15
  78. package/docs/spikes/graph-provider-2026-07.md +155 -0
  79. package/docs/standards-register.md +7 -7
  80. package/docs/trust-anchor-adoption.md +105 -4
  81. package/docs/work-item-adapters.md +1 -1
  82. package/docs/workflow-artifact-lifecycle.md +72 -9
  83. package/docs/workflow-eval-strategy.md +3 -3
  84. package/docs/workflow-shared-contracts.md +1 -1
  85. package/docs/workflow-usage-guide.md +254 -21
  86. package/evals/acceptance/DEMO-false-completion.md +20 -11
  87. package/evals/acceptance/prove-capture-teeth-declared.sh +15 -15
  88. package/evals/acceptance/prove-capture-teeth.sh +11 -11
  89. package/evals/acceptance/test_kiro_harness.sh +1 -1
  90. package/evals/ci/antigaming-suite.sh +5 -0
  91. package/evals/ci/run-baseline.sh +65 -1
  92. package/evals/fixtures/flow-kit-repository/invalid-bad-dependency/flows/review.flow.json +26 -0
  93. package/evals/fixtures/flow-kit-repository/invalid-bad-dependency/kit.json +13 -0
  94. package/evals/fixtures/flow-kit-repository/valid-with-dependency/flows/review.flow.json +26 -0
  95. package/evals/fixtures/flow-kit-repository/valid-with-dependency/kit.json +13 -0
  96. package/evals/fixtures/hook-influence/cases.json +32 -0
  97. package/evals/fixtures/pull-work-provider/github-issues.json +6 -6
  98. package/evals/fixtures/trust-reconcile-exploits/fabricated-attestation.json +59 -0
  99. package/evals/fixtures/trust-reconcile-exploits/no-label-bypass.json +36 -0
  100. package/evals/fixtures/trust-reconcile-exploits/skip-assumed-bypass.json +59 -0
  101. package/evals/fixtures/trust-reconcile-exploits/status-misassertion.json +48 -0
  102. package/evals/fixtures/trust-reconcile-exploits/waived-command-check.json +44 -0
  103. package/evals/fixtures/trust-reconcile-mixed-bundle/mixed-bundle.json +159 -0
  104. package/evals/fixtures/trust-reconcile-ws3/ws3-bundle.json +920 -0
  105. package/evals/integration/test_actor_identity.sh +254 -0
  106. package/evals/integration/test_builder_step_producers.sh +33 -23
  107. package/evals/integration/test_bundle_install.sh +22 -16
  108. package/evals/integration/test_bundle_lifecycle.sh +11 -11
  109. package/evals/integration/test_captured_fail_reconciliation.sh +50 -50
  110. package/evals/integration/test_checkpoint_signing.sh +43 -0
  111. package/evals/integration/test_claim_lookup.sh +4 -4
  112. package/evals/integration/test_codex_hook_resolution.sh +114 -0
  113. package/evals/integration/test_command_log_concurrency.sh +8 -8
  114. package/evals/integration/test_command_log_fork_classification.sh +3 -3
  115. package/evals/integration/test_command_log_integrity.sh +16 -16
  116. package/evals/integration/test_console_learning_projection.sh +1 -1
  117. package/evals/integration/test_dual_emit_flow_step.sh +145 -0
  118. package/evals/integration/test_enforcer_expects_driven.sh +12 -12
  119. package/evals/integration/test_evidence_capture_hook.sh +14 -13
  120. package/evals/integration/test_fixture_retirement_audit.sh +2 -2
  121. package/evals/integration/test_flow_agents_statusline.sh +2 -2
  122. package/evals/integration/test_flow_kit_install_git.sh +52 -0
  123. package/evals/integration/test_flow_kit_repository.sh +2 -0
  124. package/evals/integration/test_flowdef_session_activation.sh +6 -6
  125. package/evals/integration/test_flowdef_union_floor_regression.sh +429 -0
  126. package/evals/integration/test_gate_bypass_chain.sh +52 -40
  127. package/evals/integration/test_gate_lockdown.sh +97 -73
  128. package/evals/integration/test_gate_review_inquiry_records.sh +3 -3
  129. package/evals/integration/test_goal_fit_escape_hatch.sh +10 -10
  130. package/evals/integration/test_goal_fit_ghost_session.sh +104 -0
  131. package/evals/integration/test_goal_fit_hook.sh +32 -32
  132. package/evals/integration/test_goal_fit_rederive.sh +9 -9
  133. package/evals/integration/test_hook_category_behaviors.sh +103 -0
  134. package/evals/integration/test_hook_influence_cases.sh +1 -0
  135. package/evals/integration/test_install_merge.sh +262 -1
  136. package/evals/integration/test_kit_identity_trust.sh +6 -6
  137. package/evals/integration/test_liveness_conflict_injection.sh +587 -0
  138. package/evals/integration/test_liveness_heartbeat.sh +677 -0
  139. package/evals/integration/test_liveness_verdict.sh +394 -0
  140. package/evals/integration/test_local_flow_kit_install.sh +15 -0
  141. package/evals/integration/test_phase_map_and_gate_claim.sh +63 -11
  142. package/evals/integration/test_promote_gate.sh +178 -0
  143. package/evals/integration/test_publish_delivery.sh +2 -2
  144. package/evals/integration/test_pull_work_liveness_preflight.sh +386 -0
  145. package/evals/integration/test_pull_work_provider.sh +2 -2
  146. package/evals/integration/test_reconcile_soundness.sh +4 -4
  147. package/evals/integration/test_resolvefirststep_security.sh +22 -0
  148. package/evals/integration/test_runtime_adapter_activation.sh +87 -18
  149. package/evals/integration/test_session_resume_roundtrip.sh +147 -0
  150. package/evals/integration/test_sidecar_field_preservation.sh +249 -0
  151. package/evals/integration/test_trust_reconcile.sh +2 -2
  152. package/evals/integration/test_trust_reconcile_manifest.sh +79 -0
  153. package/evals/integration/test_trust_reconcile_mixed_bundle.sh +96 -0
  154. package/evals/integration/test_trust_reconcile_negatives.sh +151 -0
  155. package/evals/integration/test_usage_feedback_global.sh +5 -5
  156. package/evals/integration/test_validate_artifacts_portability.sh +60 -0
  157. package/evals/integration/test_verify_cli.sh +2 -2
  158. package/evals/integration/test_veritas_governance_kit.sh +117 -0
  159. package/evals/integration/test_workflow_artifact_cleanup_audit.sh +26 -1
  160. package/evals/integration/test_workflow_artifacts.sh +33 -0
  161. package/evals/integration/test_workflow_sidecar_writer.sh +723 -74
  162. package/evals/integration/test_workflow_steering_hook.sh +175 -5
  163. package/evals/lib/codex-provider.sh +1 -1
  164. package/evals/lib/node.sh +19 -1
  165. package/evals/run.sh +22 -0
  166. package/evals/static/test_decisions.sh +150 -0
  167. package/evals/static/test_flowdef_codeowners_coverage.sh +56 -0
  168. package/evals/static/test_package.sh +19 -10
  169. package/evals/static/test_universal_bundles.sh +48 -3
  170. package/evals/static/test_workflow_skills.sh +41 -8
  171. package/kits/builder/flows/build.flow.json +3 -74
  172. package/kits/builder/flows/publish-learn.flow.json +90 -0
  173. package/kits/builder/kit.json +11 -0
  174. package/kits/builder/skills/builder-shape/SKILL.md +1 -1
  175. package/kits/builder/skills/deliver/SKILL.md +13 -11
  176. package/kits/builder/skills/design-probe/SKILL.md +4 -4
  177. package/kits/builder/skills/evidence-gate/SKILL.md +26 -1
  178. package/kits/builder/skills/execute-plan/SKILL.md +2 -2
  179. package/kits/builder/skills/fix-bug/SKILL.md +2 -0
  180. package/kits/builder/skills/gate-review/SKILL.md +6 -6
  181. package/kits/builder/skills/idea-to-backlog/SKILL.md +1 -1
  182. package/kits/builder/skills/learning-review/SKILL.md +7 -7
  183. package/kits/builder/skills/pickup-probe/SKILL.md +5 -4
  184. package/kits/builder/skills/plan-work/SKILL.md +27 -5
  185. package/kits/builder/skills/pull-work/SKILL.md +80 -5
  186. package/kits/builder/skills/release-readiness/SKILL.md +4 -4
  187. package/kits/builder/skills/review-work/SKILL.md +24 -1
  188. package/kits/builder/skills/tdd-workflow/SKILL.md +2 -0
  189. package/kits/builder/skills/verify-work/SKILL.md +8 -1
  190. package/kits/catalog.json +6 -0
  191. package/kits/veritas-governance/adapter/readiness-to-trust-bundle.mjs +178 -0
  192. package/kits/veritas-governance/docs/README.md +75 -0
  193. package/kits/veritas-governance/fixtures/readiness/not-ready.readiness-report.json +4645 -0
  194. package/kits/veritas-governance/fixtures/readiness/ready.readiness-report.json +4403 -0
  195. package/kits/veritas-governance/flows/readiness-check.flow.json +35 -0
  196. package/kits/veritas-governance/kit.json +16 -0
  197. package/package.json +8 -4
  198. package/packaging/README.md +2 -2
  199. package/packaging/conformance/fixtures/config-protection--allow-read-state-json.json +20 -0
  200. package/packaging/conformance/fixtures/config-protection--block-write-state-json.json +20 -0
  201. package/packaging/conformance/fixtures/stop-goal-fit--block-bundle-disputed-claim.json +1 -1
  202. package/packaging/conformance/fixtures/stop-goal-fit--block-capture-contradicts-claimed-pass.json +1 -1
  203. package/packaging/manifest.json +9 -10
  204. package/schemas/decision-record.schema.json +109 -0
  205. package/schemas/workflow-evidence.schema.json +1 -1
  206. package/schemas/workflow-state.schema.json +5 -0
  207. package/scripts/README.md +8 -3
  208. package/scripts/check-content-boundary.cjs +1 -1
  209. package/scripts/check-decisions.cjs +356 -0
  210. package/scripts/ci/derive-claim-status.mjs +74 -0
  211. package/scripts/ci/trust-reconcile.js +436 -79
  212. package/scripts/git-status.sh +2 -2
  213. package/scripts/hooks/claude-telemetry-hook.js +37 -3
  214. package/scripts/hooks/codex-telemetry-hook.js +36 -2
  215. package/scripts/hooks/config-protection.js +102 -30
  216. package/scripts/hooks/evidence-capture.js +3 -3
  217. package/scripts/hooks/lib/actor-identity.js +368 -0
  218. package/scripts/hooks/lib/config-protection-remedies.js +69 -0
  219. package/scripts/hooks/lib/liveness-heartbeat.js +361 -0
  220. package/scripts/hooks/lib/liveness-policy.js +127 -0
  221. package/scripts/hooks/lib/liveness-read.js +105 -12
  222. package/scripts/hooks/lib/liveness-write.js +46 -0
  223. package/scripts/hooks/lib/local-artifact-paths.js +12 -6
  224. package/scripts/hooks/opencode-telemetry-hook.js +27 -1
  225. package/scripts/hooks/pi-telemetry-hook.js +27 -1
  226. package/scripts/hooks/stop-goal-fit.js +157 -26
  227. package/scripts/hooks/workflow-steering.js +77 -3
  228. package/scripts/install-codex-home.sh +138 -38
  229. package/scripts/statusline/flow-agents-statusline.js +2 -2
  230. package/src/cli/console-learning-projection.ts +3 -2
  231. package/src/cli/init.ts +101 -21
  232. package/src/cli/kit.ts +37 -6
  233. package/src/cli/public-api.test.mjs +58 -6
  234. package/src/cli/pull-work-provider.ts +1 -1
  235. package/src/cli/trust-bundle-policy-order.test.mjs +87 -0
  236. package/src/cli/usage-feedback.ts +3 -3
  237. package/src/cli/validate-hook-influence.ts +1 -0
  238. package/src/cli/validate-workflow-artifacts.ts +22 -6
  239. package/src/cli/veritas-readiness-adapter.test.mjs +267 -0
  240. package/src/cli/workflow-artifact-cleanup-audit.ts +35 -2
  241. package/src/cli/workflow-sidecar.ts +795 -71
  242. package/src/flow-kit/validate.ts +74 -1
  243. package/src/index.ts +6 -2
  244. package/src/lib/flow-resolver.ts +123 -55
  245. package/src/lib/local-artifact-root.ts +24 -5
  246. package/src/lib/workflow-learning-projection.ts +2 -2
  247. package/src/runtime-adapters.ts +61 -15
  248. package/src/tools/build-universal-bundles.ts +49 -16
  249. package/src/tools/generate-context-map.ts +10 -8
  250. package/src/tools/validate-source-tree.ts +23 -1
@@ -34,7 +34,7 @@ run_bounded() {
34
34
 
35
35
  WRITER="workflow-sidecar"
36
36
  VALIDATOR="validate-workflow-artifacts"
37
- ARTIFACT_DIR="$TMPDIR_EVAL/repo/.flow-agents/auto-sidecars"
37
+ ARTIFACT_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/auto-sidecars"
38
38
  mkdir -p "$ARTIFACT_DIR"
39
39
 
40
40
  DEFAULT_ROOT_REPO="$TMPDIR_EVAL/default-root-repo"
@@ -56,22 +56,57 @@ else
56
56
  _fail "sidecar writer default-root ensure-session failed: $(cat "$TMPDIR_EVAL/default-root.out" "$TMPDIR_EVAL/default-root.err")"
57
57
  fi
58
58
 
59
- OLD_ROOT_REPO="$TMPDIR_EVAL/old-root-repo"
60
- mkdir -p "$OLD_ROOT_REPO/.flow-agents/old-session"
61
- cat > "$OLD_ROOT_REPO/.flow-agents/current.json" <<'JSON'
62
- {"schema_version":"1.0","active_slug":"old-session","artifact_dir":"old-session"}
59
+ PREVIOUS_ROOT_REPO="$TMPDIR_EVAL/previous-root-repo"
60
+ mkdir -p "$PREVIOUS_ROOT_REPO/.flow-agents/previous-session"
61
+ cat > "$PREVIOUS_ROOT_REPO/.flow-agents/current.json" <<'JSON'
62
+ {"schema_version":"1.0","active_slug":"previous-session","artifact_dir":"previous-session"}
63
63
  JSON
64
- cat > "$OLD_ROOT_REPO/.flow-agents/old-session/state.json" <<'JSON'
65
- {"schema_version":"1.0","task_slug":"old-session","status":"planned","phase":"planning","next_action":{"status":"continue","summary":"continue"}}
64
+ cat > "$PREVIOUS_ROOT_REPO/.flow-agents/previous-session/state.json" <<'JSON'
65
+ {"schema_version":"1.0","task_slug":"previous-session","status":"planned","phase":"planning","next_action":{"status":"continue","summary":"continue"}}
66
66
  JSON
67
- if (cd "$OLD_ROOT_REPO" && flow_agents_node "$WRITER" current --format slug >"$TMPDIR_EVAL/old-current.out" 2>"$TMPDIR_EVAL/old-current.err") \
68
- && [[ "$(cat "$TMPDIR_EVAL/old-current.out")" == "old-session" ]]; then
69
- _pass "sidecar writer reads legacy .flow-agents current session when no new root exists"
67
+ if (cd "$PREVIOUS_ROOT_REPO" && flow_agents_node "$WRITER" current --format slug >"$TMPDIR_EVAL/previous-current.out" 2>"$TMPDIR_EVAL/previous-current.err"); then
68
+ _fail "sidecar writer default current unexpectedly read previous .flow-agents runtime root: $(cat "$TMPDIR_EVAL/previous-current.out")"
70
69
  else
71
- _fail "sidecar writer did not read legacy current session: $(cat "$TMPDIR_EVAL/old-current.out" "$TMPDIR_EVAL/old-current.err")"
70
+ _pass "sidecar writer does not fall back to previous .flow-agents runtime root"
72
71
  fi
73
72
 
74
- SESSION_ROOT="$TMPDIR_EVAL/repo/.flow-agents"
73
+ if (cd "$PREVIOUS_ROOT_REPO" && flow_agents_node "$WRITER" current --artifact-root "$PREVIOUS_ROOT_REPO/.flow-agents" --format slug >"$TMPDIR_EVAL/previous-current-explicit.out" 2>"$TMPDIR_EVAL/previous-current-explicit.err") \
74
+ && [[ "$(cat "$TMPDIR_EVAL/previous-current-explicit.out")" == "previous-session" ]]; then
75
+ _pass "sidecar writer reads explicitly supplied artifact root"
76
+ else
77
+ _fail "sidecar writer did not read explicit artifact root: $(cat "$TMPDIR_EVAL/previous-current-explicit.out" "$TMPDIR_EVAL/previous-current-explicit.err")"
78
+ fi
79
+
80
+ TRAVERSAL_ROOT="$TMPDIR_EVAL/traversal-repo/.kontourai/flow-agents"
81
+ TRAVERSAL_OUTSIDE="$TMPDIR_EVAL/traversal-outside-existing"
82
+ mkdir -p "$TRAVERSAL_ROOT" "$TRAVERSAL_OUTSIDE"
83
+ node - "$TRAVERSAL_ROOT" "$TRAVERSAL_OUTSIDE" <<'NODE'
84
+ const fs = require("node:fs");
85
+ const path = require("node:path");
86
+ const root = process.argv[2];
87
+ const outside = process.argv[3];
88
+ fs.writeFileSync(path.join(root, "current.json"), JSON.stringify({
89
+ schema_version: "1.0",
90
+ active_slug: "escape",
91
+ artifact_dir: path.relative(root, outside),
92
+ updated_at: "2026-05-09T00:00:00Z",
93
+ }) + "\n");
94
+ NODE
95
+ if run_bounded 20 flow_agents_node "$WRITER" record-agent-event \
96
+ --artifact-root "$TRAVERSAL_ROOT" \
97
+ --agent-id tool-worker \
98
+ --kind note \
99
+ --status active \
100
+ --summary "must not write outside" \
101
+ >"$TMPDIR_EVAL/current-traversal.out" 2>"$TMPDIR_EVAL/current-traversal.err"; then
102
+ _fail "sidecar writer accepted current.json artifact_dir outside artifact root"
103
+ elif [[ ! -e "$TRAVERSAL_OUTSIDE/agents" ]]; then
104
+ _pass "sidecar writer rejects current.json artifact_dir outside artifact root"
105
+ else
106
+ _fail "sidecar writer wrote agent events outside artifact root"
107
+ fi
108
+
109
+ SESSION_ROOT="$TMPDIR_EVAL/repo/.kontourai/flow-agents"
75
110
  if flow_agents_node "$WRITER" ensure-session \
76
111
  --artifact-root "$SESSION_ROOT" \
77
112
  --task-slug ensured-session \
@@ -118,12 +153,12 @@ if (cd "$UNSAFE_REPO_ROOT" \
118
153
  && git init -q \
119
154
  && git remote add origin "file:///Users/alice/customer-secret.git" \
120
155
  && FLOW_AGENTS_REPO="/Users/alice/customer-secret" flow_agents_node "$WRITER" ensure-session \
121
- --artifact-root ".flow-agents" \
156
+ --artifact-root ".kontourai/flow-agents" \
122
157
  --task-slug unsafe-repo \
123
158
  --title "Unsafe repo" \
124
159
  --summary "Unsafe repo fallback." \
125
160
  --timestamp "2026-05-09T00:00:00Z" >/dev/null 2>"$TMPDIR_EVAL/unsafe-repo.err" \
126
- && node - ".flow-agents/unsafe-repo/state.json" <<'NODE'
161
+ && node - ".kontourai/flow-agents/unsafe-repo/state.json" <<'NODE'
127
162
  const fs = require("node:fs");
128
163
  const repo = JSON.parse(fs.readFileSync(process.argv[2], "utf8")).repo;
129
164
  if (repo !== "unsafe-repo") throw new Error(`unsafe repo fallback was ${JSON.stringify(repo)}`);
@@ -162,8 +197,8 @@ else
162
197
  fi
163
198
 
164
199
  cp "$SESSION_ROOT/current.json" "$TMPDIR_EVAL/current-before-traversal-agent.json"
165
- TRAVERSAL_AGENT_OUTSIDE="$TMPDIR_EVAL/repo/.flow-agents/evil-agent-outside.jsonl"
166
- if run_bounded 5 flow_agents_node "$WRITER" record-agent-event \
200
+ TRAVERSAL_AGENT_OUTSIDE="$TMPDIR_EVAL/repo/.kontourai/flow-agents/evil-agent-outside.jsonl"
201
+ if run_bounded 20 flow_agents_node "$WRITER" record-agent-event \
167
202
  --artifact-root "$SESSION_ROOT" \
168
203
  --agent-id ../evil-agent-outside \
169
204
  --kind evidence \
@@ -219,7 +254,7 @@ if flow_agents_node "$WRITER" ensure-session \
219
254
  --timestamp "2026-05-09T00:00:50Z" >"$TMPDIR_EVAL/ensure-traversal.out" 2>&1; then
220
255
  _fail "sidecar writer should reject traversal task slugs"
221
256
  elif rg -q -- '--task-slug must not contain' "$TMPDIR_EVAL/ensure-traversal.out" \
222
- && [[ ! -d "$TMPDIR_EVAL/repo/.flow-agents/outside" ]]; then
257
+ && [[ ! -d "$TMPDIR_EVAL/repo/.kontourai/flow-agents/outside" ]]; then
223
258
  _pass "sidecar writer rejects traversal task slugs without creating outside dirs"
224
259
  else
225
260
  _fail "sidecar writer traversal rejection was not fail-closed: $(cat "$TMPDIR_EVAL/ensure-traversal.out")"
@@ -249,7 +284,7 @@ mkdir -p "$COPIED_ROOT"
249
284
  cp -R "$ENSURED_DIR" "$COPIED_DIR"
250
285
  cp "$SESSION_ROOT/current.json" "$TMPDIR_EVAL/current-before-copied-agent.json"
251
286
  COPIED_AGENT_EVENT_PATH="$COPIED_DIR/ag""ents/copied-worker/events.jsonl"
252
- if run_bounded 5 flow_agents_node "$WRITER" record-agent-event \
287
+ if run_bounded 20 flow_agents_node "$WRITER" record-agent-event \
253
288
  --artifact-dir "$COPIED_DIR" \
254
289
  --agent-id copied-worker \
255
290
  --kind evidence \
@@ -269,7 +304,7 @@ fi
269
304
 
270
305
  cp "$SESSION_ROOT/current.json" "$TMPDIR_EVAL/current-before-mismatch-agent.json"
271
306
  MISMATCH_AGENT_EVENT_PATH="$COPIED_DIR/ag""ents/mismatch-worker/events.jsonl"
272
- if run_bounded 5 flow_agents_node "$WRITER" record-agent-event \
307
+ if run_bounded 20 flow_agents_node "$WRITER" record-agent-event \
273
308
  --artifact-root "$SESSION_ROOT" \
274
309
  --artifact-dir "$COPIED_DIR" \
275
310
  --agent-id mismatch-worker \
@@ -293,7 +328,7 @@ SYMLINK_DIR="$SESSION_ROOT/symlink-session"
293
328
  mkdir -p "$SYMLINK_TARGET"
294
329
  if ln -s "$SYMLINK_TARGET" "$SYMLINK_DIR" 2>"$TMPDIR_EVAL/symlink-create.err"; then
295
330
  cp "$SESSION_ROOT/current.json" "$TMPDIR_EVAL/current-before-symlink-agent.json"
296
- if run_bounded 5 flow_agents_node "$WRITER" record-agent-event \
331
+ if run_bounded 20 flow_agents_node "$WRITER" record-agent-event \
297
332
  --artifact-root "$SESSION_ROOT" \
298
333
  --artifact-dir "$SYMLINK_DIR" \
299
334
  --agent-id symlink-worker \
@@ -422,12 +457,405 @@ if flow_agents_node "$WRITER" ensure-session \
422
457
  --criterion "Different CLI criterion" \
423
458
  --timestamp "2026-05-09T00:02:00Z" >"$TMPDIR_EVAL/ensure-existing.out" 2>"$TMPDIR_EVAL/ensure-existing.err" \
424
459
  && rg -q '"description": "Existing artifact criterion"' "$EXISTING_ONLY_DIR/acceptance.json" \
425
- && ! rg -q 'Different CLI criterion' "$EXISTING_ONLY_DIR/acceptance.json"; then
426
- _pass "sidecar writer derives missing sidecars from existing session Markdown"
460
+ && ! rg -q 'Different CLI criterion' "$EXISTING_ONLY_DIR/acceptance.json" \
461
+ && rg -q '"branch": "main"' "$EXISTING_ONLY_DIR/state.json"; then
462
+ _pass "sidecar writer derives missing sidecars from existing session Markdown and preserves its own pre-#289 branch: main line (legacy, not re-derived)"
427
463
  else
428
464
  _fail "sidecar writer drifted sidecars from existing session Markdown: $(cat "$TMPDIR_EVAL/ensure-existing.out" "$TMPDIR_EVAL/ensure-existing.err")"
429
465
  fi
430
466
 
467
+ # ─── #289: branch as first-class routing state (agent/<actor>/<slug>) ─────────
468
+ # AC2/AC7 (derivation + current.json mirror): a freshly created session with no explicit
469
+ # --actor derives its branch from the ambient runtime session id. CLAUDE_CODE_SESSION_ID is
470
+ # injected here to exercise the real Claude Code runtime-session-id path in
471
+ # actor-identity.js's resolveActor (rather than the --actor override seam) — state.json, the
472
+ # freshly seeded session Markdown, and current.json must all carry the same derived
473
+ # agent/<actor>/<slug> value.
474
+ BRANCH_ENV_DIR="$SESSION_ROOT/branch-session-env"
475
+ if CLAUDE_CODE_SESSION_ID="claude-session-alpha-001" flow_agents_node "$WRITER" ensure-session \
476
+ --artifact-root "$SESSION_ROOT" \
477
+ --task-slug branch-session-env \
478
+ --source-request "Derive a routing branch from an injected runtime session id." \
479
+ --summary "A fresh session with no explicit --actor should derive its branch from the injected runtime session id." \
480
+ --timestamp "2026-05-09T00:03:00Z" >"$TMPDIR_EVAL/branch-session-env.out" 2>"$TMPDIR_EVAL/branch-session-env.err" \
481
+ && rg -q '"branch": "agent/claude-code-claude-session-alpha-001-[A-Za-z0-9_.-]+/branch-session-env"' "$BRANCH_ENV_DIR/state.json" \
482
+ && grep -Eq '^branch: agent/claude-code-claude-session-alpha-001-[A-Za-z0-9_.-]+/branch-session-env$' "$BRANCH_ENV_DIR/branch-session-env--deliver.md" \
483
+ && rg -q '"branch": "agent/claude-code-claude-session-alpha-001-[A-Za-z0-9_.-]+/branch-session-env"' "$SESSION_ROOT/current.json"; then
484
+ _pass "sidecar writer derives agent/<actor>/<slug> branch from an injected runtime session id into state.json, markdown, and current.json (AC2, AC7)"
485
+ else
486
+ _fail "sidecar writer did not derive a consistent injected-session-id branch across state.json/markdown/current.json: $(cat "$TMPDIR_EVAL/branch-session-env.out" "$TMPDIR_EVAL/branch-session-env.err")"
487
+ fi
488
+
489
+ # AC3 (explicit override): --branch on a brand-new session records the value verbatim,
490
+ # not a derived agent/<actor>/<slug> name.
491
+ BRANCH_OVERRIDE_DIR="$SESSION_ROOT/branch-override-a"
492
+ if flow_agents_node "$WRITER" ensure-session \
493
+ --artifact-root "$SESSION_ROOT" \
494
+ --task-slug branch-override-a \
495
+ --branch "custom/my-branch" \
496
+ --source-request "An explicit --branch overrides derivation." \
497
+ --summary "A brand-new session with --branch should record the value verbatim." \
498
+ --timestamp "2026-05-09T00:03:05Z" >"$TMPDIR_EVAL/branch-override.out" 2>"$TMPDIR_EVAL/branch-override.err" \
499
+ && rg -q '"branch": "custom/my-branch"' "$BRANCH_OVERRIDE_DIR/state.json" \
500
+ && grep -Eq '^branch: custom/my-branch$' "$BRANCH_OVERRIDE_DIR/branch-override-a--deliver.md"; then
501
+ _pass "sidecar writer records an explicit --branch value verbatim, not a derived name (AC3)"
502
+ else
503
+ _fail "sidecar writer did not honor an explicit --branch override: $(cat "$TMPDIR_EVAL/branch-override.out" "$TMPDIR_EVAL/branch-override.err")"
504
+ fi
505
+
506
+ # AC4 (distinct actors): two fresh sessions with distinct explicit --actor values produce
507
+ # distinct branch values, differing only in the actor segment.
508
+ BRANCH_DERIVE_A_DIR="$SESSION_ROOT/branch-derive-a"
509
+ BRANCH_DERIVE_B_DIR="$SESSION_ROOT/branch-derive-b"
510
+ if flow_agents_node "$WRITER" ensure-session \
511
+ --artifact-root "$SESSION_ROOT" \
512
+ --task-slug branch-derive-a \
513
+ --actor test-actor-alpha \
514
+ --source-request "Derive a branch for actor alpha." \
515
+ --summary "Distinct actor alpha." \
516
+ --timestamp "2026-05-09T00:03:10Z" >"$TMPDIR_EVAL/branch-derive-a.out" 2>"$TMPDIR_EVAL/branch-derive-a.err" \
517
+ && flow_agents_node "$WRITER" ensure-session \
518
+ --artifact-root "$SESSION_ROOT" \
519
+ --task-slug branch-derive-b \
520
+ --actor test-actor-beta \
521
+ --source-request "Derive a branch for actor beta." \
522
+ --summary "Distinct actor beta." \
523
+ --timestamp "2026-05-09T00:03:11Z" >"$TMPDIR_EVAL/branch-derive-b.out" 2>"$TMPDIR_EVAL/branch-derive-b.err" \
524
+ && rg -q '"branch": "agent/test-actor-alpha/branch-derive-a"' "$BRANCH_DERIVE_A_DIR/state.json" \
525
+ && rg -q '"branch": "agent/test-actor-beta/branch-derive-b"' "$BRANCH_DERIVE_B_DIR/state.json"; then
526
+ _pass "sidecar writer derives distinct branch values for distinct actors (AC4)"
527
+ else
528
+ _fail "sidecar writer did not derive distinct branches for distinct actors: $(cat "$TMPDIR_EVAL/branch-derive-a.out" "$TMPDIR_EVAL/branch-derive-a.err" "$TMPDIR_EVAL/branch-derive-b.out" "$TMPDIR_EVAL/branch-derive-b.err")"
529
+ fi
530
+
531
+ # AC5 (existing-session continuity): re-running ensure-session against the SAME slug from a
532
+ # DIFFERENT actor never re-derives or overwrites the already-recorded branch (ADR 0021 §5
533
+ # takeover continuity — resume the incumbent's branch, never a parallel one).
534
+ if flow_agents_node "$WRITER" ensure-session \
535
+ --artifact-root "$SESSION_ROOT" \
536
+ --task-slug branch-derive-a \
537
+ --actor test-actor-gamma \
538
+ --source-request "A takeover by a different actor must not refork the branch." \
539
+ --summary "Resuming actor gamma should inherit alpha's already-recorded branch." \
540
+ --timestamp "2026-05-09T00:03:12Z" >"$TMPDIR_EVAL/branch-no-rederive.out" 2>"$TMPDIR_EVAL/branch-no-rederive.err" \
541
+ && rg -q '"branch": "agent/test-actor-alpha/branch-derive-a"' "$BRANCH_DERIVE_A_DIR/state.json" \
542
+ && ! rg -q 'test-actor-gamma' "$BRANCH_DERIVE_A_DIR/state.json"; then
543
+ _pass "sidecar writer never re-derives an existing session's branch for a later actor (AC5, ADR 0021 §5)"
544
+ else
545
+ _fail "sidecar writer re-derived or overwrote an existing session's branch on takeover: $(cat "$TMPDIR_EVAL/branch-no-rederive.out" "$TMPDIR_EVAL/branch-no-rederive.err")"
546
+ fi
547
+
548
+ # ─── #309: init-plan (and advance-state) must never drop an already-recorded branch ────
549
+ # Regression cover for the full ensure-session -> init-plan -> advance-state lifecycle every
550
+ # real session goes through. init-plan is invoked (per plan-work/SKILL.md) against the
551
+ # PLAN artifact the tool-planner writes (e.g. "<slug>--plan-work.md"), a DIFFERENT file than
552
+ # the session "<slug>--deliver.md" that carries the "branch:" line ensure-session seeded — that
553
+ # plan artifact below deliberately carries NO "branch:" field, reproducing the exact shape of
554
+ # the live kontourai-flow-agents-166 regression. state.json (and current.json's mirror) must
555
+ # still carry the derived branch after init-plan, and again after a subsequent advance-state.
556
+ BRANCH_SEQ_DIR="$SESSION_ROOT/branch-full-sequence"
557
+ if flow_agents_node "$WRITER" ensure-session \
558
+ --artifact-root "$SESSION_ROOT" \
559
+ --task-slug branch-full-sequence \
560
+ --actor seq-actor \
561
+ --source-request "Establish a session whose branch must survive init-plan and advance-state." \
562
+ --summary "Fresh session for the full ensure-session -> init-plan -> advance-state sequence." \
563
+ --timestamp "2026-07-01T00:04:20Z" >"$TMPDIR_EVAL/branch-seq-ensure.out" 2>"$TMPDIR_EVAL/branch-seq-ensure.err" \
564
+ && rg -q '"branch": "agent/seq-actor/branch-full-sequence"' "$BRANCH_SEQ_DIR/state.json"; then
565
+ _pass "sidecar writer (#309 setup) seeds branch-full-sequence with a derived branch via ensure-session"
566
+ else
567
+ _fail "sidecar writer (#309 setup) failed to seed branch-full-sequence: $(cat "$TMPDIR_EVAL/branch-seq-ensure.out" "$TMPDIR_EVAL/branch-seq-ensure.err")"
568
+ fi
569
+
570
+ BRANCH_SEQ_PLAN_ARTIFACT="$BRANCH_SEQ_DIR/branch-full-sequence--plan-work.md"
571
+ cat > "$BRANCH_SEQ_PLAN_ARTIFACT" <<'MARKDOWN'
572
+ ---
573
+ role: plan
574
+ parent: branch-full-sequence--deliver
575
+ created: 2026-07-01
576
+ ---
577
+
578
+ # Plan: #309 branch survival fixture
579
+
580
+ ## Plan
581
+
582
+ A plan artifact deliberately carrying no `branch:` line (mirrors the real tool-planner output).
583
+
584
+ ## Definition Of Done
585
+
586
+ - **Acceptance criteria:**
587
+ - [ ] init-plan preserves the already-recorded branch - Evidence: pending.
588
+ MARKDOWN
589
+
590
+ if flow_agents_node "$WRITER" init-plan "$BRANCH_SEQ_PLAN_ARTIFACT" \
591
+ --source-request "Plan artifact carries no branch: line." \
592
+ --summary "Planning sidecars initialized from a branch-less plan artifact." \
593
+ --next-action "Advance to execution." \
594
+ --timestamp "2026-07-01T00:04:21Z" >"$TMPDIR_EVAL/branch-seq-initplan.out" 2>"$TMPDIR_EVAL/branch-seq-initplan.err" \
595
+ && rg -q '"branch": "agent/seq-actor/branch-full-sequence"' "$BRANCH_SEQ_DIR/state.json"; then
596
+ _pass "sidecar writer (#309) preserves the already-recorded branch across init-plan even when the plan artifact carries no branch: line"
597
+ else
598
+ _fail "sidecar writer (#309) DROPPED the branch on init-plan: $(cat "$TMPDIR_EVAL/branch-seq-initplan.out" "$TMPDIR_EVAL/branch-seq-initplan.err"); state.json=$(cat "$BRANCH_SEQ_DIR/state.json" 2>/dev/null)"
599
+ fi
600
+
601
+ if flow_agents_node "$WRITER" advance-state "$BRANCH_SEQ_DIR" \
602
+ --status in_progress \
603
+ --phase execution \
604
+ --summary "Execution started." \
605
+ --next-action "Run checks." \
606
+ --timestamp "2026-07-01T00:04:22Z" >"$TMPDIR_EVAL/branch-seq-advance.out" 2>"$TMPDIR_EVAL/branch-seq-advance.err" \
607
+ && rg -q '"branch": "agent/seq-actor/branch-full-sequence"' "$BRANCH_SEQ_DIR/state.json" \
608
+ && rg -q '"branch": "agent/seq-actor/branch-full-sequence"' "$SESSION_ROOT/current.json"; then
609
+ _pass "sidecar writer (#309) still carries the branch in state.json and its current.json mirror after advance-state"
610
+ else
611
+ _fail "sidecar writer (#309) lost the branch by advance-state, or current.json mirror drifted: $(cat "$TMPDIR_EVAL/branch-seq-advance.out" "$TMPDIR_EVAL/branch-seq-advance.err"); state.json=$(cat "$BRANCH_SEQ_DIR/state.json" 2>/dev/null)"
612
+ fi
613
+
614
+ # #309 backfill repro: an ALREADY-BROKEN pre-fix session (state.json has no branch key at all,
615
+ # matching kontourai-flow-agents-166/-290) can be repaired by re-running init-plan against the
616
+ # SAME branch-less plan artifact that dropped the branch in the first place -- WITHOUT any direct
617
+ # file edit -- because initSidecars falls back to reading the session's own canonical
618
+ # "<slug>--deliver.md" from disk when neither the existing state.json nor the passed-in markdown
619
+ # carries a branch.
620
+ BRANCH_BACKFILL_DIR="$SESSION_ROOT/branch-backfill-repro"
621
+ mkdir -p "$BRANCH_BACKFILL_DIR"
622
+ cat > "$BRANCH_BACKFILL_DIR/branch-backfill-repro--deliver.md" <<'MARKDOWN'
623
+ # branch-backfill-repro
624
+
625
+ branch: agent/seq-actor/branch-backfill-repro
626
+ worktree: main
627
+ created: 2026-07-01T00:04:23Z
628
+ status: planning
629
+ type: deliver
630
+ iteration: 1
631
+
632
+ ## Plan
633
+
634
+ Pre-fix victim fixture: state.json below was written WITHOUT a branch key (simulating #309).
635
+ MARKDOWN
636
+ cat > "$BRANCH_BACKFILL_DIR/state.json" <<'JSON'
637
+ {
638
+ "schema_version": "1.0",
639
+ "task_slug": "branch-backfill-repro",
640
+ "repo": "kontourai/flow-agents",
641
+ "status": "planned",
642
+ "phase": "planning",
643
+ "created_at": "2026-07-01T00:04:23Z",
644
+ "updated_at": "2026-07-01T00:04:23Z",
645
+ "artifact_paths": ["state.json"],
646
+ "next_action": { "status": "continue", "summary": "pre-fix victim fixture" }
647
+ }
648
+ JSON
649
+ BRANCH_BACKFILL_PLAN_ARTIFACT="$BRANCH_BACKFILL_DIR/branch-backfill-repro--plan-work.md"
650
+ cat > "$BRANCH_BACKFILL_PLAN_ARTIFACT" <<'MARKDOWN'
651
+ ---
652
+ role: plan
653
+ parent: branch-backfill-repro--deliver
654
+ created: 2026-07-01
655
+ ---
656
+
657
+ # Plan: #309 backfill repro fixture
658
+
659
+ ## Definition Of Done
660
+
661
+ - **Acceptance criteria:**
662
+ - [ ] backfill repairs the dropped branch - Evidence: pending.
663
+ MARKDOWN
664
+
665
+ if flow_agents_node "$WRITER" init-plan "$BRANCH_BACKFILL_PLAN_ARTIFACT" \
666
+ --source-request "Re-run init-plan against the same branch-less plan artifact to backfill." \
667
+ --summary "Backfill re-run." \
668
+ --next-action "n/a" \
669
+ --timestamp "2026-07-01T00:04:23Z" >"$TMPDIR_EVAL/branch-backfill.out" 2>"$TMPDIR_EVAL/branch-backfill.err" \
670
+ && rg -q '"branch": "agent/seq-actor/branch-backfill-repro"' "$BRANCH_BACKFILL_DIR/state.json"; then
671
+ _pass "sidecar writer (#309) backfills a pre-fix-broken session's branch via a sanctioned init-plan re-run (no direct file edit)"
672
+ else
673
+ _fail "sidecar writer (#309) failed to backfill an already-broken session's branch: $(cat "$TMPDIR_EVAL/branch-backfill.out" "$TMPDIR_EVAL/branch-backfill.err"); state.json=$(cat "$BRANCH_BACKFILL_DIR/state.json" 2>/dev/null)"
674
+ fi
675
+
676
+ # Actor charset guard: an explicit --actor value that strips to empty under the allowed
677
+ # charset dies before any session artifact is written (mirrors the liveness write path's
678
+ # existing F7 test coverage).
679
+ BRANCH_BAD_ACTOR_DIR="$SESSION_ROOT/branch-bad-actor"
680
+ if flow_agents_node "$WRITER" ensure-session \
681
+ --artifact-root "$SESSION_ROOT" \
682
+ --task-slug branch-bad-actor \
683
+ --actor ":::" \
684
+ --source-request "Garbage --actor should die." \
685
+ --summary "Should not create a session." \
686
+ --timestamp "2026-05-09T00:03:13Z" >"$TMPDIR_EVAL/branch-bad-actor.out" 2>"$TMPDIR_EVAL/branch-bad-actor.err"; then
687
+ _fail "sidecar writer should reject a --actor value that strips to empty under the allowed charset"
688
+ elif rg -q -- '--actor value strips to empty' "$TMPDIR_EVAL/branch-bad-actor.out" "$TMPDIR_EVAL/branch-bad-actor.err" \
689
+ && [[ ! -f "$BRANCH_BAD_ACTOR_DIR/state.json" ]]; then
690
+ _pass "sidecar writer dies on a garbage --actor value before writing any session artifact"
691
+ else
692
+ _fail "garbage --actor rejection was not fail-closed or lacked diagnostics: $(cat "$TMPDIR_EVAL/branch-bad-actor.out" "$TMPDIR_EVAL/branch-bad-actor.err")"
693
+ fi
694
+
695
+ # ─── #289 fix-plan iteration 1 (F1/F2/F4) ──────────────────────────────────────
696
+ # F1: a trailing-dot slug (git-check-ref-format forbids a ref component ending in ".") derives a
697
+ # branch that is a REAL, valid git ref — asserted against the actual `git check-ref-format`
698
+ # binary, not a hand-rolled regex re-implementation of its rules.
699
+ BRANCH_TRAILING_DOT_DIR="$SESSION_ROOT/branch-trailing-dot"
700
+ if flow_agents_node "$WRITER" ensure-session \
701
+ --artifact-root "$SESSION_ROOT" \
702
+ --task-slug branch-trailing-dot \
703
+ --actor "my-fix." \
704
+ --source-request "A trailing-dot actor segment must not leave an illegal git ref." \
705
+ --summary "Trailing dot should be stripped, not left dangling." \
706
+ --timestamp "2026-07-01T00:04:00Z" >"$TMPDIR_EVAL/branch-trailing-dot.out" 2>"$TMPDIR_EVAL/branch-trailing-dot.err"; then
707
+ TRAILING_DOT_BRANCH="$(jq -r '.branch // empty' "$BRANCH_TRAILING_DOT_DIR/state.json" 2>/dev/null)"
708
+ if [[ -n "$TRAILING_DOT_BRANCH" ]] && [[ "$TRAILING_DOT_BRANCH" != *"." ]] && git check-ref-format --branch "$TRAILING_DOT_BRANCH" >/dev/null 2>&1; then
709
+ _pass "sidecar writer strips a trailing dot from a derived branch segment and the result passes the real git check-ref-format binary (F1)"
710
+ else
711
+ _fail "sidecar writer derived a branch that git check-ref-format rejects (F1): branch=$TRAILING_DOT_BRANCH"
712
+ fi
713
+ else
714
+ _fail "sidecar writer ensure-session failed for trailing-dot actor fixture: $(cat "$TMPDIR_EVAL/branch-trailing-dot.out" "$TMPDIR_EVAL/branch-trailing-dot.err")"
715
+ fi
716
+
717
+ # F2: an explicit --branch is strictly validated, not trusted verbatim. Each hostile value below
718
+ # must die BEFORE any session artifact is written (no state.json / no session Markdown), with a
719
+ # diagnostic naming the actual problem.
720
+ assert_hostile_branch_dies() {
721
+ local label="$1" branch_value="$2" slug="$3" expect_pattern="$4"
722
+ local dir="$SESSION_ROOT/$slug"
723
+ local out="$TMPDIR_EVAL/hostile-branch-$slug.out"
724
+ if flow_agents_node "$WRITER" ensure-session \
725
+ --artifact-root "$SESSION_ROOT" \
726
+ --task-slug "$slug" \
727
+ --branch "$branch_value" \
728
+ --source-request "Hostile --branch should die before mutation." \
729
+ --summary "Should not create a session." \
730
+ --timestamp "2026-07-01T00:04:10Z" >"$out" 2>&1; then
731
+ _fail "sidecar writer should reject a hostile --branch value ($label)"
732
+ elif rg -q -- "$expect_pattern" "$out" && [[ ! -f "$dir/state.json" ]]; then
733
+ _pass "sidecar writer dies on a hostile --branch value before writing any session artifact ($label, F2)"
734
+ else
735
+ _fail "hostile --branch rejection ($label) was not fail-closed or lacked diagnostics: $(cat "$out")"
736
+ fi
737
+ }
738
+ assert_hostile_branch_dies "embedded newline" "$(printf 'main\nbad')" "branch-hostile-newline" "control character or newline"
739
+ assert_hostile_branch_dies '".." sequence' "foo..bar" "branch-hostile-dotdot" 'must not contain a "\.\." sequence'
740
+ assert_hostile_branch_dies "trailing .lock" "foo.lock" "branch-hostile-lock" '"\.lock"'
741
+ assert_hostile_branch_dies "embedded space" "foo bar" "branch-hostile-space" "contains a space"
742
+
743
+ # F4: two distinct all-garbage --task-slug values (each collapsing sanitizeBranchSegment's
744
+ # charset filter to empty) must derive DISTINCT branches, not silently collide on the bare
745
+ # literal "unknown". Actor is held fixed and valid so the slug segment is the only variable.
746
+ BRANCH_GARBAGE_A_DIR="$SESSION_ROOT/???"
747
+ BRANCH_GARBAGE_B_DIR="$SESSION_ROOT/!!!"
748
+ if flow_agents_node "$WRITER" ensure-session \
749
+ --artifact-root "$SESSION_ROOT" \
750
+ --task-slug '???' \
751
+ --actor test-actor-fix4 \
752
+ --source-request "All-garbage slug A." \
753
+ --summary "Garbage slug A." \
754
+ --timestamp "2026-07-01T00:04:20Z" >"$TMPDIR_EVAL/branch-garbage-a.out" 2>"$TMPDIR_EVAL/branch-garbage-a.err" \
755
+ && flow_agents_node "$WRITER" ensure-session \
756
+ --artifact-root "$SESSION_ROOT" \
757
+ --task-slug '!!!' \
758
+ --actor test-actor-fix4 \
759
+ --source-request "All-garbage slug B." \
760
+ --summary "Garbage slug B." \
761
+ --timestamp "2026-07-01T00:04:21Z" >"$TMPDIR_EVAL/branch-garbage-b.out" 2>"$TMPDIR_EVAL/branch-garbage-b.err"; then
762
+ GARBAGE_A_BRANCH="$(jq -r '.branch // empty' "$BRANCH_GARBAGE_A_DIR/state.json" 2>/dev/null)"
763
+ GARBAGE_B_BRANCH="$(jq -r '.branch // empty' "$BRANCH_GARBAGE_B_DIR/state.json" 2>/dev/null)"
764
+ if [[ -n "$GARBAGE_A_BRANCH" ]] && [[ -n "$GARBAGE_B_BRANCH" ]] \
765
+ && [[ "$GARBAGE_A_BRANCH" != "$GARBAGE_B_BRANCH" ]] \
766
+ && [[ "$GARBAGE_A_BRANCH" == agent/test-actor-fix4/unknown-* ]] \
767
+ && [[ "$GARBAGE_B_BRANCH" == agent/test-actor-fix4/unknown-* ]] \
768
+ && git check-ref-format --branch "$GARBAGE_A_BRANCH" >/dev/null 2>&1 \
769
+ && git check-ref-format --branch "$GARBAGE_B_BRANCH" >/dev/null 2>&1; then
770
+ _pass "sidecar writer derives distinct disambiguated branches for two all-garbage task slugs (F4): $GARBAGE_A_BRANCH vs $GARBAGE_B_BRANCH"
771
+ else
772
+ _fail "sidecar writer did not disambiguate two all-garbage task slugs (F4): a=$GARBAGE_A_BRANCH b=$GARBAGE_B_BRANCH"
773
+ fi
774
+ else
775
+ _fail "sidecar writer ensure-session failed for all-garbage slug fixtures: $(cat "$TMPDIR_EVAL/branch-garbage-a.out" "$TMPDIR_EVAL/branch-garbage-a.err" "$TMPDIR_EVAL/branch-garbage-b.out" "$TMPDIR_EVAL/branch-garbage-b.err")"
776
+ fi
777
+
778
+ # ─── #289 fix-plan iteration 2 (F2'/F4' residuals from code-review-289-iteration-1) ─────────────
779
+ # F2': the whole-string checks above (iteration 1) only look at the START/END of the full
780
+ # --branch value, so a fuzz of charset-valid values found hostile /-delimited PATH COMPONENTS
781
+ # that slip past them. Each of these four escape classes must still die pre-mutation.
782
+ assert_hostile_branch_dies "leading '-' (path component)" "-lead" "branch-hostile-lead" 'starting with "-"'
783
+ assert_hostile_branch_dies "non-first component starting with '.'" "a/.b" "branch-hostile-dotcomp" 'starting with "\."'
784
+ assert_hostile_branch_dies "non-last component ending in .lock" "foo.lock/bar" "branch-hostile-lockcomp" '"\.lock"'
785
+ assert_hostile_branch_dies "component that is exactly '.'" "a/./b" "branch-hostile-dotonly" '"\."'
786
+
787
+ # F2' belt-and-braces: every --branch value validateExplicitBranch ACCEPTS must also pass the
788
+ # real `git check-ref-format --branch` binary — not just this repo's earlier fixtures
789
+ # ("custom/my-branch", AC3 above), but a representative additional accepted value with a nested
790
+ # path and mixed charset.
791
+ BRANCH_ACCEPTED_GIT_DIR="$SESSION_ROOT/branch-accepted-git-valid"
792
+ if flow_agents_node "$WRITER" ensure-session \
793
+ --artifact-root "$SESSION_ROOT" \
794
+ --task-slug branch-accepted-git-valid \
795
+ --branch "release/2026.07_hotfix-1" \
796
+ --source-request "An accepted --branch value must also be a real, git-valid ref." \
797
+ --summary "Accepted --branch values are double-checked against the real git binary." \
798
+ --timestamp "2026-07-02T00:04:30Z" >"$TMPDIR_EVAL/branch-accepted-git.out" 2>"$TMPDIR_EVAL/branch-accepted-git.err"; then
799
+ ACCEPTED_GIT_BRANCH="$(jq -r '.branch // empty' "$BRANCH_ACCEPTED_GIT_DIR/state.json" 2>/dev/null)"
800
+ if [[ "$ACCEPTED_GIT_BRANCH" == "release/2026.07_hotfix-1" ]] && git check-ref-format --branch "$ACCEPTED_GIT_BRANCH" >/dev/null 2>&1; then
801
+ _pass "sidecar writer's accepted --branch value passes the real git check-ref-format binary (F2')"
802
+ else
803
+ _fail "sidecar writer accepted a --branch value that the real git binary rejects (F2'): branch=$ACCEPTED_GIT_BRANCH"
804
+ fi
805
+ else
806
+ _fail "sidecar writer ensure-session failed for the accepted git-valid --branch fixture: $(cat "$TMPDIR_EVAL/branch-accepted-git.out" "$TMPDIR_EVAL/branch-accepted-git.err")"
807
+ fi
808
+
809
+ # F4': the FINAL sanitized segment equaling the literal fallback token "unknown" must ALWAYS be
810
+ # disambiguated with the 6-hex sha256 suffix - including a literal --actor "unknown" (no
811
+ # literal-input carve-out), and including near-miss inputs ("unknown.", ".unknown") that only
812
+ # collapse to the literal "unknown" via this function's OWN leading/trailing-dot stripping.
813
+ # All three must derive DISTINCT branches (distinct raw input -> distinct hash) despite sharing
814
+ # the same effective actor "meaning".
815
+ BRANCH_UNKNOWN_LITERAL_DIR="$SESSION_ROOT/branch-unknown-literal"
816
+ BRANCH_UNKNOWN_TRAILING_DIR="$SESSION_ROOT/branch-unknown-trailing-dot"
817
+ BRANCH_UNKNOWN_LEADING_DIR="$SESSION_ROOT/branch-unknown-leading-dot"
818
+ if flow_agents_node "$WRITER" ensure-session \
819
+ --artifact-root "$SESSION_ROOT" \
820
+ --task-slug branch-unknown-literal \
821
+ --actor "unknown" \
822
+ --source-request "Literal --actor unknown must still be disambiguated." \
823
+ --summary "No literal-input carve-out." \
824
+ --timestamp "2026-07-02T00:04:40Z" >"$TMPDIR_EVAL/branch-unknown-literal.out" 2>"$TMPDIR_EVAL/branch-unknown-literal.err" \
825
+ && flow_agents_node "$WRITER" ensure-session \
826
+ --artifact-root "$SESSION_ROOT" \
827
+ --task-slug branch-unknown-trailing-dot \
828
+ --actor "unknown." \
829
+ --source-request "Trailing-dot actor collapsing to literal unknown must still be disambiguated." \
830
+ --summary "Near-miss trailing dot." \
831
+ --timestamp "2026-07-02T00:04:41Z" >"$TMPDIR_EVAL/branch-unknown-trailing-dot.out" 2>"$TMPDIR_EVAL/branch-unknown-trailing-dot.err" \
832
+ && flow_agents_node "$WRITER" ensure-session \
833
+ --artifact-root "$SESSION_ROOT" \
834
+ --task-slug branch-unknown-leading-dot \
835
+ --actor ".unknown" \
836
+ --source-request "Leading-dot actor collapsing to literal unknown must still be disambiguated." \
837
+ --summary "Near-miss leading dot." \
838
+ --timestamp "2026-07-02T00:04:42Z" >"$TMPDIR_EVAL/branch-unknown-leading-dot.out" 2>"$TMPDIR_EVAL/branch-unknown-leading-dot.err"; then
839
+ UNKNOWN_LITERAL_BRANCH="$(jq -r '.branch // empty' "$BRANCH_UNKNOWN_LITERAL_DIR/state.json" 2>/dev/null)"
840
+ UNKNOWN_TRAILING_BRANCH="$(jq -r '.branch // empty' "$BRANCH_UNKNOWN_TRAILING_DIR/state.json" 2>/dev/null)"
841
+ UNKNOWN_LEADING_BRANCH="$(jq -r '.branch // empty' "$BRANCH_UNKNOWN_LEADING_DIR/state.json" 2>/dev/null)"
842
+ if [[ "$UNKNOWN_LITERAL_BRANCH" == agent/unknown-*/branch-unknown-literal ]] \
843
+ && [[ "$UNKNOWN_TRAILING_BRANCH" == agent/unknown-*/branch-unknown-trailing-dot ]] \
844
+ && [[ "$UNKNOWN_LEADING_BRANCH" == agent/unknown-*/branch-unknown-leading-dot ]] \
845
+ && [[ "${UNKNOWN_LITERAL_BRANCH#agent/}" != "${UNKNOWN_TRAILING_BRANCH#agent/}" ]] \
846
+ && [[ "${UNKNOWN_LITERAL_BRANCH#agent/}" != "${UNKNOWN_LEADING_BRANCH#agent/}" ]] \
847
+ && [[ "${UNKNOWN_TRAILING_BRANCH#agent/}" != "${UNKNOWN_LEADING_BRANCH#agent/}" ]] \
848
+ && git check-ref-format --branch "$UNKNOWN_LITERAL_BRANCH" >/dev/null 2>&1 \
849
+ && git check-ref-format --branch "$UNKNOWN_TRAILING_BRANCH" >/dev/null 2>&1 \
850
+ && git check-ref-format --branch "$UNKNOWN_LEADING_BRANCH" >/dev/null 2>&1; then
851
+ _pass "sidecar writer derives three DISTINCT disambiguated branches for actors 'unknown', 'unknown.', '.unknown' (F4'): $UNKNOWN_LITERAL_BRANCH vs $UNKNOWN_TRAILING_BRANCH vs $UNKNOWN_LEADING_BRANCH"
852
+ else
853
+ _fail "sidecar writer did not disambiguate all three unknown-collapsing actor variants distinctly (F4'): literal=$UNKNOWN_LITERAL_BRANCH trailing=$UNKNOWN_TRAILING_BRANCH leading=$UNKNOWN_LEADING_BRANCH"
854
+ fi
855
+ else
856
+ _fail "sidecar writer ensure-session failed for one of the unknown-collapsing actor fixtures: $(cat "$TMPDIR_EVAL/branch-unknown-literal.out" "$TMPDIR_EVAL/branch-unknown-literal.err" "$TMPDIR_EVAL/branch-unknown-trailing-dot.out" "$TMPDIR_EVAL/branch-unknown-trailing-dot.err" "$TMPDIR_EVAL/branch-unknown-leading-dot.out" "$TMPDIR_EVAL/branch-unknown-leading-dot.err")"
857
+ fi
858
+
431
859
  printf 'DO NOT OVERWRITE\n' >> "$ENSURED_DIR/ensured-session--deliver.md"
432
860
  if flow_agents_node "$WRITER" ensure-session \
433
861
  --artifact-root "$SESSION_ROOT" \
@@ -522,7 +950,46 @@ else
522
950
  _fail "sidecar writer did not update state or bundle from evidence"
523
951
  fi
524
952
 
525
- INVALID_REF_DIR="$TMPDIR_EVAL/repo/.flow-agents/invalid-evidence-ref"
953
+ # ─── WS8 (ADR 0020) AC1: producer evidence classification ─────────────────────
954
+ # buildTrustBundle derives evidence.evidenceType/method from check.kind instead of
955
+ # hardcoding test_output, and always stamps execution.label on command-backed checks.
956
+ CLASSIFY_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/evidence-classification"
957
+ mkdir -p "$CLASSIFY_DIR"
958
+ cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$CLASSIFY_DIR/evidence-classification--deliver.md"
959
+ flow_agents_node "$WRITER" init-plan "$CLASSIFY_DIR/evidence-classification--deliver.md" \
960
+ --source-request "Classify evidence by kind." \
961
+ --summary "Evidence classification fixture." \
962
+ --next-action "Record mixed-kind evidence." \
963
+ --timestamp "2026-05-09T00:01:00Z" >"$TMPDIR_EVAL/classify-init.out" 2>"$TMPDIR_EVAL/classify-init.err"
964
+
965
+ if flow_agents_node "$WRITER" record-evidence "$CLASSIFY_DIR" \
966
+ --verdict pass \
967
+ --check-json '{"id":"unit-tests","kind":"test","status":"pass","summary":"Unit tests passed.","command":"npm test"}' \
968
+ --check-json '{"id":"dashboard-ui","kind":"browser","status":"pass","summary":"Visual check of dashboard."}' \
969
+ --timestamp "2026-05-09T00:01:05Z" >"$TMPDIR_EVAL/classify.out" 2>"$TMPDIR_EVAL/classify.err"; then
970
+ if node - "$CLASSIFY_DIR/trust.bundle" << 'NODE' 2>/dev/null
971
+ const fs = require("fs");
972
+ const b = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
973
+ const ev = b.evidence || [];
974
+ const testEv = ev.find((e) => /unit-tests/.test(String(e.claimId)) || /Unit tests/.test(String(e.excerptOrSummary)));
975
+ const browserEv = ev.find((e) => /dashboard-ui/.test(String(e.claimId)) || /dashboard/.test(String(e.excerptOrSummary)));
976
+ if (!testEv) throw new Error("no test-kind evidence item");
977
+ if (!browserEv) throw new Error("no browser-kind evidence item");
978
+ if (testEv.evidenceType !== "test_output") throw new Error("test-kind evidenceType expected test_output, got " + testEv.evidenceType);
979
+ if (!testEv.execution || testEv.execution.label !== "npm test") throw new Error("test-kind must stamp execution.label 'npm test', got " + JSON.stringify(testEv.execution));
980
+ if (browserEv.evidenceType === "test_output") throw new Error("browser-kind evidenceType must NOT be test_output (got test_output)");
981
+ if (browserEv.evidenceType !== "crawl_observation") throw new Error("browser-kind evidenceType expected crawl_observation, got " + browserEv.evidenceType);
982
+ NODE
983
+ then
984
+ _pass "AC1: producer classifies test-kind as test_output+label and browser-kind as crawl_observation"
985
+ else
986
+ _fail "AC1: evidence classification incorrect: $(cat "$TMPDIR_EVAL/classify.out" "$TMPDIR_EVAL/classify.err")"
987
+ fi
988
+ else
989
+ _fail "AC1: record-evidence for classification fixture failed: $(cat "$TMPDIR_EVAL/classify.out" "$TMPDIR_EVAL/classify.err")"
990
+ fi
991
+
992
+ INVALID_REF_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/invalid-evidence-ref"
526
993
  mkdir -p "$INVALID_REF_DIR"
527
994
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$INVALID_REF_DIR/invalid-evidence-ref--deliver.md"
528
995
  flow_agents_node "$WRITER" init-plan "$INVALID_REF_DIR/invalid-evidence-ref--deliver.md" \
@@ -557,7 +1024,7 @@ else
557
1024
  _fail "incomplete structured artifact_refs rejection was not fail-closed: $(cat "$TMPDIR_EVAL/incomplete-ref.out" "$TMPDIR_EVAL/incomplete-ref.err")"
558
1025
  fi
559
1026
 
560
- INVALID_ACCEPTANCE_REF_DIR="$TMPDIR_EVAL/repo/.flow-agents/invalid-acceptance-ref"
1027
+ INVALID_ACCEPTANCE_REF_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/invalid-acceptance-ref"
561
1028
  mkdir -p "$INVALID_ACCEPTANCE_REF_DIR"
562
1029
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$INVALID_ACCEPTANCE_REF_DIR/invalid-acceptance-ref--deliver.md"
563
1030
  flow_agents_node "$WRITER" init-plan "$INVALID_ACCEPTANCE_REF_DIR/invalid-acceptance-ref--deliver.md" \
@@ -630,7 +1097,7 @@ check_surface_fixture() {
630
1097
  local verdict="$3"
631
1098
  local expected_status="$4"
632
1099
  local expected_text="$5"
633
- local dir="$TMPDIR_EVAL/repo/.flow-agents/surface-$name"
1100
+ local dir="$TMPDIR_EVAL/repo/.kontourai/flow-agents/surface-$name"
634
1101
  mkdir -p "$dir"
635
1102
  # Phase 4c: evidence.json no longer written; verify surface trust check status in trust.bundle.
636
1103
  if flow_agents_node "$WRITER" record-evidence "$dir" \
@@ -657,7 +1124,7 @@ check_surface_fixture "integrity-mismatch" "integrity-mismatch-trust-report.json
657
1124
  check_surface_fixture "provider-absent" "provider-absent.json" "not_verified" "not_verified" "No trust provider is configured"
658
1125
  check_surface_fixture "artifact-absent" "artifact-absent.json" "not_verified" "not_verified" "not readable"
659
1126
 
660
- PURE_SURFACE_DIR="$TMPDIR_EVAL/repo/.flow-agents/surface-trust-only"
1127
+ PURE_SURFACE_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/surface-trust-only"
661
1128
  mkdir -p "$PURE_SURFACE_DIR"
662
1129
  # Phase 4c: evidence.json no longer written; verify in trust.bundle.
663
1130
  if flow_agents_node "$WRITER" record-evidence "$PURE_SURFACE_DIR" \
@@ -737,7 +1204,7 @@ else
737
1204
  _fail "terminal jump rejection lacked diagnostics or mutated authoritative sidecars"
738
1205
  fi
739
1206
 
740
- BUILDER_TRANSITION_DIR="$TMPDIR_EVAL/repo/.flow-agents/builder-transition-guard"
1207
+ BUILDER_TRANSITION_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/builder-transition-guard"
741
1208
  mkdir -p "$BUILDER_TRANSITION_DIR"
742
1209
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$BUILDER_TRANSITION_DIR/builder-transition-guard--deliver.md"
743
1210
  flow_agents_node "$WRITER" init-plan "$BUILDER_TRANSITION_DIR/builder-transition-guard--deliver.md" \
@@ -835,7 +1302,7 @@ else
835
1302
  _fail "Builder Kit max-attempt route-back behavior was not deterministic"
836
1303
  fi
837
1304
 
838
- LEGACY_TRANSITION_DIR="$TMPDIR_EVAL/repo/.flow-agents/legacy-transition-guard"
1305
+ LEGACY_TRANSITION_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/legacy-transition-guard"
839
1306
  mkdir -p "$LEGACY_TRANSITION_DIR"
840
1307
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$LEGACY_TRANSITION_DIR/legacy-transition-guard--deliver.md"
841
1308
  flow_agents_node "$WRITER" init-plan "$LEGACY_TRANSITION_DIR/legacy-transition-guard--deliver.md" \
@@ -864,7 +1331,7 @@ else
864
1331
  _fail "legacy-compatible direct primitive route-back failed: $(cat "$TMPDIR_EVAL/legacy-route-back.out" "$TMPDIR_EVAL/legacy-route-back.err")"
865
1332
  fi
866
1333
 
867
- NV_DIR="$TMPDIR_EVAL/repo/.flow-agents/not-verified-sidecars"
1334
+ NV_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/not-verified-sidecars"
868
1335
  mkdir -p "$NV_DIR"
869
1336
  cat > "$NV_DIR/not-verified-sidecars--deliver.md" <<'MARKDOWN'
870
1337
  # Route not verified evidence
@@ -937,7 +1404,7 @@ else
937
1404
  _fail "sidecar writer did not preserve not-verified state"
938
1405
  fi
939
1406
 
940
- NEW_INVALID_DIR="$TMPDIR_EVAL/repo/.flow-agents/new-invalid-artifact"
1407
+ NEW_INVALID_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/new-invalid-artifact"
941
1408
  if flow_agents_node "$WRITER" record-evidence "$NEW_INVALID_DIR" \
942
1409
  --verdict banana \
943
1410
  --check-json '{"id":"invalid-new","kind":"test","status":"pass","summary":"Should fail."}' >"$TMPDIR_EVAL/new-invalid.out" 2>&1; then
@@ -948,10 +1415,10 @@ else
948
1415
  _fail "sidecar writer left lock file for invalid new artifact command"
949
1416
  fi
950
1417
 
951
- LOCK_DENIED_DIR="$TMPDIR_EVAL/repo/.flow-agents/lock-denied"
1418
+ LOCK_DENIED_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/lock-denied"
952
1419
  mkdir -p "$LOCK_DENIED_DIR"
953
1420
  if chmod 500 "$LOCK_DENIED_DIR" 2>"$TMPDIR_EVAL/lock-denied-chmod.err"; then
954
- if run_bounded 5 flow_agents_node "$WRITER" record-critique "$LOCK_DENIED_DIR" \
1421
+ if run_bounded 20 flow_agents_node "$WRITER" record-critique "$LOCK_DENIED_DIR" \
955
1422
  --id lock-denied-review \
956
1423
  --reviewer tool-code-reviewer \
957
1424
  --verdict pass \
@@ -992,7 +1459,7 @@ else
992
1459
  _fail "sidecar writer critique failed: $(cat "$TMPDIR_EVAL/critique.out" "$TMPDIR_EVAL/critique.err")"
993
1460
  fi
994
1461
 
995
- CONCURRENT_DIR="$TMPDIR_EVAL/repo/.flow-agents/concurrent-critiques"
1462
+ CONCURRENT_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/concurrent-critiques"
996
1463
  mkdir -p "$CONCURRENT_DIR"
997
1464
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$CONCURRENT_DIR/concurrent-critiques--deliver.md"
998
1465
  flow_agents_node "$WRITER" init-plan "$CONCURRENT_DIR/concurrent-critiques--deliver.md" \
@@ -1056,7 +1523,7 @@ else
1056
1523
  _fail "sidecar writer did not update release state"
1057
1524
  fi
1058
1525
 
1059
- NO_SUMMARY_RELEASE_DIR="$TMPDIR_EVAL/repo/.flow-agents/no-summary-release"
1526
+ NO_SUMMARY_RELEASE_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/no-summary-release"
1060
1527
  mkdir -p "$NO_SUMMARY_RELEASE_DIR"
1061
1528
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$NO_SUMMARY_RELEASE_DIR/no-summary-release--deliver.md"
1062
1529
  flow_agents_node "$WRITER" init-plan "$NO_SUMMARY_RELEASE_DIR/no-summary-release--deliver.md" \
@@ -1110,7 +1577,7 @@ else
1110
1577
  _fail "no-correction learning closeout failed validation: $(cat "$TMPDIR_EVAL/learning-valid.out" "$TMPDIR_EVAL/learning-valid.err")"
1111
1578
  fi
1112
1579
 
1113
- CORRECTION_DIR="$TMPDIR_EVAL/repo/.flow-agents/correction-needed-learning"
1580
+ CORRECTION_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/correction-needed-learning"
1114
1581
  mkdir -p "$CORRECTION_DIR"
1115
1582
  if flow_agents_node "$WRITER" record-learning "$CORRECTION_DIR" \
1116
1583
  --task-slug correction-needed-learning \
@@ -1124,7 +1591,7 @@ else
1124
1591
  _fail "correction-needed learning closeout failed: $(cat "$TMPDIR_EVAL/correction-needed-learning.out" "$TMPDIR_EVAL/correction-needed-learning.err" "$TMPDIR_EVAL/correction-needed-valid.out" "$TMPDIR_EVAL/correction-needed-valid.err")"
1125
1592
  fi
1126
1593
 
1127
- DOGFOOD_DIR="$TMPDIR_EVAL/repo/.flow-agents/dogfood-pass"
1594
+ DOGFOOD_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/dogfood-pass"
1128
1595
  mkdir -p "$DOGFOOD_DIR"
1129
1596
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$DOGFOOD_DIR/dogfood-pass--deliver.md"
1130
1597
  flow_agents_node "$WRITER" init-plan "$DOGFOOD_DIR/dogfood-pass--deliver.md" \
@@ -1145,7 +1612,7 @@ else
1145
1612
  _fail "dogfood-pass missing actionable no-evidence error"
1146
1613
  fi
1147
1614
 
1148
- DIRTY_EVIDENCE_DOGFOOD_DIR="$TMPDIR_EVAL/repo/.flow-agents/dogfood-dirty-evidence"
1615
+ DIRTY_EVIDENCE_DOGFOOD_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/dogfood-dirty-evidence"
1149
1616
  mkdir -p "$DIRTY_EVIDENCE_DOGFOOD_DIR"
1150
1617
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$DIRTY_EVIDENCE_DOGFOOD_DIR/dogfood-dirty-evidence--deliver.md"
1151
1618
  flow_agents_node "$WRITER" init-plan "$DIRTY_EVIDENCE_DOGFOOD_DIR/dogfood-dirty-evidence--deliver.md" \
@@ -1191,7 +1658,7 @@ else
1191
1658
  _fail "dogfood-pass existing dirty evidence was not fail-closed"
1192
1659
  fi
1193
1660
 
1194
- INVALID_EXISTING_EVIDENCE_DOGFOOD_DIR="$TMPDIR_EVAL/repo/.flow-agents/dogfood-existing-invalid-evidence"
1661
+ INVALID_EXISTING_EVIDENCE_DOGFOOD_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/dogfood-existing-invalid-evidence"
1195
1662
  mkdir -p "$INVALID_EXISTING_EVIDENCE_DOGFOOD_DIR"
1196
1663
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$INVALID_EXISTING_EVIDENCE_DOGFOOD_DIR/dogfood-existing-invalid-evidence--deliver.md"
1197
1664
  flow_agents_node "$WRITER" init-plan "$INVALID_EXISTING_EVIDENCE_DOGFOOD_DIR/dogfood-existing-invalid-evidence--deliver.md" \
@@ -1265,7 +1732,7 @@ else
1265
1732
  _fail "dogfood-pass not_verified clean-pass check was not fail-closed"
1266
1733
  fi
1267
1734
 
1268
- INVALID_EVIDENCE_DOGFOOD_DIR="$TMPDIR_EVAL/repo/.flow-agents/dogfood-invalid-evidence"
1735
+ INVALID_EVIDENCE_DOGFOOD_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/dogfood-invalid-evidence"
1269
1736
  mkdir -p "$INVALID_EVIDENCE_DOGFOOD_DIR"
1270
1737
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$INVALID_EVIDENCE_DOGFOOD_DIR/dogfood-invalid-evidence--deliver.md"
1271
1738
  flow_agents_node "$WRITER" init-plan "$INVALID_EVIDENCE_DOGFOOD_DIR/dogfood-invalid-evidence--deliver.md" \
@@ -1292,7 +1759,7 @@ else
1292
1759
  _fail "dogfood-pass invalid evidence metadata was not fail-closed"
1293
1760
  fi
1294
1761
 
1295
- INVALID_LEARNING_DOGFOOD_DIR="$TMPDIR_EVAL/repo/.flow-agents/dogfood-invalid-learning"
1762
+ INVALID_LEARNING_DOGFOOD_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/dogfood-invalid-learning"
1296
1763
  mkdir -p "$INVALID_LEARNING_DOGFOOD_DIR"
1297
1764
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$INVALID_LEARNING_DOGFOOD_DIR/dogfood-invalid-learning--deliver.md"
1298
1765
  flow_agents_node "$WRITER" init-plan "$INVALID_LEARNING_DOGFOOD_DIR/dogfood-invalid-learning--deliver.md" \
@@ -1318,7 +1785,7 @@ else
1318
1785
  _fail "dogfood-pass invalid learning was not fail-closed"
1319
1786
  fi
1320
1787
 
1321
- INVALID_LEARNING_SHAPE_DOGFOOD_DIR="$TMPDIR_EVAL/repo/.flow-agents/dogfood-invalid-learning-shape"
1788
+ INVALID_LEARNING_SHAPE_DOGFOOD_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/dogfood-invalid-learning-shape"
1322
1789
  mkdir -p "$INVALID_LEARNING_SHAPE_DOGFOOD_DIR"
1323
1790
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$INVALID_LEARNING_SHAPE_DOGFOOD_DIR/dogfood-invalid-learning-shape--deliver.md"
1324
1791
  flow_agents_node "$WRITER" init-plan "$INVALID_LEARNING_SHAPE_DOGFOOD_DIR/dogfood-invalid-learning-shape--deliver.md" \
@@ -1344,7 +1811,7 @@ else
1344
1811
  _fail "dogfood-pass invalid learning shape was not fail-closed"
1345
1812
  fi
1346
1813
 
1347
- EXISTING_INVALID_LEARNING_DOGFOOD_DIR="$TMPDIR_EVAL/repo/.flow-agents/dogfood-existing-invalid-learning"
1814
+ EXISTING_INVALID_LEARNING_DOGFOOD_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/dogfood-existing-invalid-learning"
1348
1815
  mkdir -p "$EXISTING_INVALID_LEARNING_DOGFOOD_DIR"
1349
1816
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$EXISTING_INVALID_LEARNING_DOGFOOD_DIR/dogfood-existing-invalid-learning--deliver.md"
1350
1817
  flow_agents_node "$WRITER" init-plan "$EXISTING_INVALID_LEARNING_DOGFOOD_DIR/dogfood-existing-invalid-learning--deliver.md" \
@@ -1397,7 +1864,7 @@ else
1397
1864
  _fail "dogfood-pass existing invalid learning was not fail-closed"
1398
1865
  fi
1399
1866
 
1400
- EXISTING_LEARNED_NO_CORRECTION_DIR="$TMPDIR_EVAL/repo/.flow-agents/dogfood-learned-no-correction"
1867
+ EXISTING_LEARNED_NO_CORRECTION_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/dogfood-learned-no-correction"
1401
1868
  mkdir -p "$EXISTING_LEARNED_NO_CORRECTION_DIR"
1402
1869
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$EXISTING_LEARNED_NO_CORRECTION_DIR/dogfood-learned-no-correction--deliver.md"
1403
1870
  flow_agents_node "$WRITER" init-plan "$EXISTING_LEARNED_NO_CORRECTION_DIR/dogfood-learned-no-correction--deliver.md" \
@@ -1452,7 +1919,7 @@ else
1452
1919
  _fail "dogfood-pass existing learned learning missing correction was not fail-closed"
1453
1920
  fi
1454
1921
 
1455
- INVALID_CRITIQUE_DOGFOOD_DIR="$TMPDIR_EVAL/repo/.flow-agents/dogfood-invalid-critique"
1922
+ INVALID_CRITIQUE_DOGFOOD_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/dogfood-invalid-critique"
1456
1923
  mkdir -p "$INVALID_CRITIQUE_DOGFOOD_DIR"
1457
1924
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$INVALID_CRITIQUE_DOGFOOD_DIR/dogfood-invalid-critique--deliver.md"
1458
1925
  flow_agents_node "$WRITER" init-plan "$INVALID_CRITIQUE_DOGFOOD_DIR/dogfood-invalid-critique--deliver.md" \
@@ -1484,7 +1951,7 @@ else
1484
1951
  _fail "dogfood-pass invalid critique metadata was not fail-closed"
1485
1952
  fi
1486
1953
 
1487
- EXISTING_INVALID_CRITIQUE_DOGFOOD_DIR="$TMPDIR_EVAL/repo/.flow-agents/dogfood-existing-invalid-critique"
1954
+ EXISTING_INVALID_CRITIQUE_DOGFOOD_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/dogfood-existing-invalid-critique"
1488
1955
  mkdir -p "$EXISTING_INVALID_CRITIQUE_DOGFOOD_DIR"
1489
1956
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$EXISTING_INVALID_CRITIQUE_DOGFOOD_DIR/dogfood-existing-invalid-critique--deliver.md"
1490
1957
  flow_agents_node "$WRITER" init-plan "$EXISTING_INVALID_CRITIQUE_DOGFOOD_DIR/dogfood-existing-invalid-critique--deliver.md" \
@@ -1650,7 +2117,7 @@ else
1650
2117
  _fail "dogfood-pass failing critique was not fail-closed"
1651
2118
  fi
1652
2119
 
1653
- DIRTY_CRITIQUE_DOGFOOD_DIR="$TMPDIR_EVAL/repo/.flow-agents/dogfood-dirty-critique"
2120
+ DIRTY_CRITIQUE_DOGFOOD_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/dogfood-dirty-critique"
1654
2121
  mkdir -p "$DIRTY_CRITIQUE_DOGFOOD_DIR"
1655
2122
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$DIRTY_CRITIQUE_DOGFOOD_DIR/dogfood-dirty-critique--deliver.md"
1656
2123
  flow_agents_node "$WRITER" init-plan "$DIRTY_CRITIQUE_DOGFOOD_DIR/dogfood-dirty-critique--deliver.md" \
@@ -1701,7 +2168,7 @@ else
1701
2168
  _fail "dogfood-pass existing dirty critique was not fail-closed"
1702
2169
  fi
1703
2170
 
1704
- FAILED_DOGFOOD_DIR="$TMPDIR_EVAL/repo/.flow-agents/dogfood-failed-pass"
2171
+ FAILED_DOGFOOD_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/dogfood-failed-pass"
1705
2172
  mkdir -p "$FAILED_DOGFOOD_DIR"
1706
2173
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$FAILED_DOGFOOD_DIR/dogfood-failed-pass--deliver.md"
1707
2174
  flow_agents_node "$WRITER" init-plan "$FAILED_DOGFOOD_DIR/dogfood-failed-pass--deliver.md" \
@@ -1834,7 +2301,7 @@ else
1834
2301
  _fail "dogfood-pass release readiness did not update expected sidecars"
1835
2302
  fi
1836
2303
 
1837
- DOGFOOD_NV_DIR="$TMPDIR_EVAL/repo/.flow-agents/dogfood-not-verified"
2304
+ DOGFOOD_NV_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/dogfood-not-verified"
1838
2305
  mkdir -p "$DOGFOOD_NV_DIR"
1839
2306
  cat > "$DOGFOOD_NV_DIR/dogfood-not-verified--deliver.md" <<'MARKDOWN'
1840
2307
  # Dogfood not verified fixture
@@ -1914,7 +2381,7 @@ else
1914
2381
  _fail "invalid release decision failure was not actionable"
1915
2382
  fi
1916
2383
 
1917
- SEMANTIC_RELEASE_DIR="$TMPDIR_EVAL/repo/.flow-agents/semantic-release"
2384
+ SEMANTIC_RELEASE_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/semantic-release"
1918
2385
  mkdir -p "$SEMANTIC_RELEASE_DIR"
1919
2386
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$SEMANTIC_RELEASE_DIR/semantic-release--deliver.md"
1920
2387
  flow_agents_node "$WRITER" init-plan "$SEMANTIC_RELEASE_DIR/semantic-release--deliver.md" \
@@ -1989,7 +2456,7 @@ else
1989
2456
  _fail "incomplete correction prevention failure was not actionable: $(cat "$TMPDIR_EVAL/correction-incomplete-prevention.out")"
1990
2457
  fi
1991
2458
 
1992
- SEMANTIC_LEARNING_DIR="$TMPDIR_EVAL/repo/.flow-agents/semantic-learning"
2459
+ SEMANTIC_LEARNING_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/semantic-learning"
1993
2460
  mkdir -p "$SEMANTIC_LEARNING_DIR"
1994
2461
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$SEMANTIC_LEARNING_DIR/semantic-learning--deliver.md"
1995
2462
  flow_agents_node "$WRITER" init-plan "$SEMANTIC_LEARNING_DIR/semantic-learning--deliver.md" \
@@ -2023,7 +2490,7 @@ else
2023
2490
  _fail "semantic learning failure advanced state or lacked actionable output"
2024
2491
  fi
2025
2492
 
2026
- REVIEW_DIR="$TMPDIR_EVAL/repo/.flow-agents/imported-critique"
2493
+ REVIEW_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/imported-critique"
2027
2494
  mkdir -p "$REVIEW_DIR"
2028
2495
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$REVIEW_DIR/imported-critique--deliver.md"
2029
2496
  flow_agents_node "$WRITER" init-plan "$REVIEW_DIR/imported-critique--deliver.md" \
@@ -2090,7 +2557,7 @@ else
2090
2557
  _fail "writer output failed validation: $(cat "$TMPDIR_EVAL/valid.out" "$TMPDIR_EVAL/valid.err")"
2091
2558
  fi
2092
2559
 
2093
- BAD_DIR="$TMPDIR_EVAL/repo/.flow-agents/bad-critique"
2560
+ BAD_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/bad-critique"
2094
2561
  mkdir -p "$BAD_DIR"
2095
2562
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$BAD_DIR/bad-critique--deliver.md"
2096
2563
 
@@ -2118,7 +2585,7 @@ else
2118
2585
  _fail "open critique failure did not mention open findings"
2119
2586
  fi
2120
2587
 
2121
- IMPORT_BAD="$TMPDIR_EVAL/repo/.flow-agents/imported-bad-critique"
2588
+ IMPORT_BAD="$TMPDIR_EVAL/repo/.kontourai/flow-agents/imported-bad-critique"
2122
2589
  mkdir -p "$IMPORT_BAD"
2123
2590
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$IMPORT_BAD/imported-bad-critique--deliver.md"
2124
2591
  flow_agents_node "$WRITER" init-plan "$IMPORT_BAD/imported-bad-critique--deliver.md" \
@@ -2162,7 +2629,7 @@ fi
2162
2629
 
2163
2630
 
2164
2631
  # ─── AC1: trust.bundle dual-write file existence and schema validity ──────────
2165
- TB_SCHEMA_DIR="$TMPDIR_EVAL/repo/.flow-agents/trust-bundle-schema"
2632
+ TB_SCHEMA_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/trust-bundle-schema"
2166
2633
  mkdir -p "$TB_SCHEMA_DIR"
2167
2634
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$TB_SCHEMA_DIR/trust-bundle-schema--deliver.md"
2168
2635
  flow_agents_node "$WRITER" init-plan "$TB_SCHEMA_DIR/trust-bundle-schema--deliver.md" \
@@ -2199,7 +2666,7 @@ NODEOF
2199
2666
  fi
2200
2667
 
2201
2668
  # ─── AC2: claim status fidelity — pass→verified, fail→disputed ───────────────
2202
- TB_FIDELITY_DIR="$TMPDIR_EVAL/repo/.flow-agents/trust-bundle-fidelity"
2669
+ TB_FIDELITY_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/trust-bundle-fidelity"
2203
2670
  mkdir -p "$TB_FIDELITY_DIR"
2204
2671
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$TB_FIDELITY_DIR/trust-bundle-fidelity--deliver.md"
2205
2672
  flow_agents_node "$WRITER" init-plan "$TB_FIDELITY_DIR/trust-bundle-fidelity--deliver.md" \
@@ -2239,7 +2706,7 @@ else
2239
2706
  fi
2240
2707
 
2241
2708
  # ─── AC2: claim status fidelity — critique fail→disputed, pass→verified ──────
2242
- TB_CRITIQUE_DIR="$TMPDIR_EVAL/repo/.flow-agents/trust-bundle-critique"
2709
+ TB_CRITIQUE_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/trust-bundle-critique"
2243
2710
  mkdir -p "$TB_CRITIQUE_DIR"
2244
2711
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$TB_CRITIQUE_DIR/trust-bundle-critique--deliver.md"
2245
2712
  flow_agents_node "$WRITER" init-plan "$TB_CRITIQUE_DIR/trust-bundle-critique--deliver.md" \
@@ -2290,7 +2757,7 @@ else
2290
2757
  fi
2291
2758
 
2292
2759
  # ─── AC3: capture authoritative over claimed status + policies present (ADR 0010 maximal) ──
2293
- TB_CAPTURE_DIR="$TMPDIR_EVAL/repo/.flow-agents/trust-bundle-capture"
2760
+ TB_CAPTURE_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/trust-bundle-capture"
2294
2761
  mkdir -p "$TB_CAPTURE_DIR"
2295
2762
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$TB_CAPTURE_DIR/trust-bundle-capture--deliver.md"
2296
2763
  flow_agents_node "$WRITER" init-plan "$TB_CAPTURE_DIR/trust-bundle-capture--deliver.md" \
@@ -2382,7 +2849,7 @@ else
2382
2849
  fi
2383
2850
 
2384
2851
  # ─── AC6: agent liveness (ADR 0012) — held / free-on-lapse / free-on-release ──
2385
- TB_LIVENESS_ROOT="$TMPDIR_EVAL/liveness/.flow-agents"
2852
+ TB_LIVENESS_ROOT="$TMPDIR_EVAL/liveness/.kontourai/flow-agents"
2386
2853
  flow_agents_node "$WRITER" liveness claim held-subj --actor agent-A --at "2026-06-25T11:50:00Z" --ttl 1800 --artifact-root "$TB_LIVENESS_ROOT" >/dev/null 2>&1
2387
2854
  flow_agents_node "$WRITER" liveness heartbeat held-subj --actor agent-A --at "2026-06-25T11:58:00Z" --artifact-root "$TB_LIVENESS_ROOT" >/dev/null 2>&1
2388
2855
  flow_agents_node "$WRITER" liveness claim stale-subj --actor agent-B --at "2026-06-25T11:00:00Z" --ttl 1800 --artifact-root "$TB_LIVENESS_ROOT" >/dev/null 2>&1
@@ -2397,25 +2864,207 @@ else
2397
2864
  _fail "liveness status mismatch (expected held/free/free): $LIVENESS_OUT"
2398
2865
  fi
2399
2866
 
2400
- # ─── AC7: lifecycle-driven liveness (ADR 0012) init-plan claims, advance-state releases (opt-in) ──
2401
- TB_LC_ROOT="$TMPDIR_EVAL/liveness-lifecycle/.flow-agents"
2867
+ # ─── F8(i) (#288 fix iteration 2, orphan-heartbeat invariant): direct CLI `liveness
2868
+ # heartbeat`/`release` must die with remediation when no prior claim event exists for the
2869
+ # (subjectId, actor) pair — the exact reproduction the reviewer flagged ("liveness heartbeat
2870
+ # <subj> --actor <a> with no prior claim writes an orphan heartbeat"). `claim` itself is
2871
+ # unaffected (it establishes the pair). ──
2872
+ TB_ORPHAN_ROOT="$TMPDIR_EVAL/liveness-orphan/.kontourai/flow-agents"
2873
+ if flow_agents_node "$WRITER" liveness heartbeat orphan-subj --actor agent-orphan --artifact-root "$TB_ORPHAN_ROOT" >"$TMPDIR_EVAL/orphan-heartbeat.out" 2>"$TMPDIR_EVAL/orphan-heartbeat.err"; then
2874
+ _fail "liveness (F8(i)): heartbeat with no prior claim for the (subject, actor) pair should have exited nonzero"
2875
+ elif rg -qi 'prior claim' "$TMPDIR_EVAL/orphan-heartbeat.err" && rg -qi 'liveness claim' "$TMPDIR_EVAL/orphan-heartbeat.err" && [[ ! -f "$TB_ORPHAN_ROOT/liveness/events.jsonl" ]]; then
2876
+ _pass "liveness (F8(i)): heartbeat with no prior claim dies with remediation naming 'liveness claim' and writes no orphan event"
2877
+ else
2878
+ _fail "liveness (F8(i)) orphan-heartbeat rejection lacked remediation or wrote an event: $(cat "$TMPDIR_EVAL/orphan-heartbeat.out" "$TMPDIR_EVAL/orphan-heartbeat.err")"
2879
+ fi
2880
+
2881
+ if flow_agents_node "$WRITER" liveness release orphan-release-subj --actor agent-orphan-r --artifact-root "$TB_ORPHAN_ROOT" >"$TMPDIR_EVAL/orphan-release.out" 2>"$TMPDIR_EVAL/orphan-release.err"; then
2882
+ _fail "liveness (F8(i)): release with no prior claim for the (subject, actor) pair should have exited nonzero"
2883
+ elif rg -qi 'prior claim' "$TMPDIR_EVAL/orphan-release.err" && rg -qi 'liveness claim' "$TMPDIR_EVAL/orphan-release.err" && ! rg -q '"subjectId":"orphan-release-subj"' "$TB_ORPHAN_ROOT/liveness/events.jsonl" 2>/dev/null; then
2884
+ _pass "liveness (F8(i)): release with no prior claim dies with remediation naming 'liveness claim' and writes no orphan event"
2885
+ else
2886
+ _fail "liveness (F8(i)) orphan-release rejection lacked remediation or wrote an event: $(cat "$TMPDIR_EVAL/orphan-release.out" "$TMPDIR_EVAL/orphan-release.err")"
2887
+ fi
2888
+
2889
+ # A prior claim for the SAME subjectId but a DIFFERENT actor must not satisfy the check — the
2890
+ # invariant is per-(subject, actor) pair, not merely per-subject.
2891
+ flow_agents_node "$WRITER" liveness claim orphan-crossactor-subj --actor agent-orphan-real --artifact-root "$TB_ORPHAN_ROOT" >/dev/null 2>&1
2892
+ if flow_agents_node "$WRITER" liveness heartbeat orphan-crossactor-subj --actor agent-orphan-impostor --artifact-root "$TB_ORPHAN_ROOT" >"$TMPDIR_EVAL/orphan-crossactor.out" 2>"$TMPDIR_EVAL/orphan-crossactor.err"; then
2893
+ _fail "liveness (F8(i)): heartbeat for a different actor than the one who claimed should have exited nonzero"
2894
+ elif rg -qi 'prior claim' "$TMPDIR_EVAL/orphan-crossactor.err" && ! rg -q '"subjectId":"orphan-crossactor-subj","actor":"agent-orphan-impostor","at":"[^"]*","type":"heartbeat"' "$TB_ORPHAN_ROOT/liveness/events.jsonl" 2>/dev/null && ! rg -q '"type":"heartbeat".*"subjectId":"orphan-crossactor-subj".*"actor":"agent-orphan-impostor"' "$TB_ORPHAN_ROOT/liveness/events.jsonl" 2>/dev/null; then
2895
+ _pass "liveness (F8(i)): the prior-claim check is scoped per (subject, actor) pair — another actor's claim on the same subject does not satisfy it"
2896
+ else
2897
+ _fail "liveness (F8(i)) cross-actor orphan-heartbeat check failed: $(cat "$TMPDIR_EVAL/orphan-crossactor.out" "$TMPDIR_EVAL/orphan-crossactor.err")"
2898
+ fi
2899
+
2900
+ # claim itself is unaffected: it must still succeed with no prior claim of any kind.
2901
+ if flow_agents_node "$WRITER" liveness claim orphan-claim-ok-subj --actor agent-orphan-ok --artifact-root "$TB_ORPHAN_ROOT" >"$TMPDIR_EVAL/orphan-claim-ok.out" 2>"$TMPDIR_EVAL/orphan-claim-ok.err" && rg -q '"type":"claim","subjectId":"orphan-claim-ok-subj"' "$TB_ORPHAN_ROOT/liveness/events.jsonl"; then
2902
+ _pass "liveness (F8(i)): claim is unaffected by the prior-claim requirement — it succeeds with no prior events at all"
2903
+ else
2904
+ _fail "liveness (F8(i)) claim regressed: $(cat "$TMPDIR_EVAL/orphan-claim-ok.out" "$TMPDIR_EVAL/orphan-claim-ok.err")"
2905
+ fi
2906
+
2907
+ # ─── AC7: lifecycle-driven liveness (ADR 0012) — init-plan claims, advance-state releases (default-on) ──
2908
+ TB_LC_ROOT="$TMPDIR_EVAL/liveness-lifecycle/.kontourai/flow-agents"
2402
2909
  TB_LC_DIR="$TB_LC_ROOT/lc-task"; mkdir -p "$TB_LC_DIR"
2403
2910
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$TB_LC_DIR/lc-task--deliver.md"
2404
- FLOW_AGENTS_LIVENESS=on FLOW_AGENTS_ACTOR=agent-LC flow_agents_node "$WRITER" init-plan "$TB_LC_DIR/lc-task--deliver.md" --task-slug lc-task --source-request x --summary y --next-action z --timestamp "2026-06-25T11:50:00Z" >/dev/null 2>&1
2911
+ FLOW_AGENTS_ACTOR=agent-LC flow_agents_node "$WRITER" init-plan "$TB_LC_DIR/lc-task--deliver.md" --task-slug lc-task --source-request x --summary y --next-action z --timestamp "2026-06-25T11:50:00Z" >/dev/null 2>&1
2405
2912
  LC_HELD=$(flow_agents_node "$WRITER" liveness status --now "2026-06-25T12:00:00Z" --artifact-root "$TB_LC_ROOT" 2>/dev/null | grep -viE "unknown format")
2406
- FLOW_AGENTS_LIVENESS=on FLOW_AGENTS_ACTOR=agent-LC flow_agents_node "$WRITER" advance-state "$TB_LC_DIR" --status delivered --phase done --task-slug lc-task --timestamp "2026-06-25T11:55:00Z" >/dev/null 2>&1
2913
+ LC_CLAIM_EVENTS=$(cat "$TB_LC_ROOT/liveness/events.jsonl" 2>/dev/null)
2914
+ FLOW_AGENTS_ACTOR=agent-LC flow_agents_node "$WRITER" advance-state "$TB_LC_DIR" --status delivered --phase done --task-slug lc-task --timestamp "2026-06-25T11:55:00Z" >/dev/null 2>&1
2407
2915
  LC_FREE=$(flow_agents_node "$WRITER" liveness status --now "2026-06-25T12:00:00Z" --artifact-root "$TB_LC_ROOT" 2>/dev/null | grep -viE "unknown format")
2408
- TB_OFF_ROOT="$TMPDIR_EVAL/liveness-off/.flow-agents"; mkdir -p "$TB_OFF_ROOT/off-task"
2916
+ TB_OFF_ROOT="$TMPDIR_EVAL/liveness-off/.kontourai/flow-agents"; mkdir -p "$TB_OFF_ROOT/off-task"
2409
2917
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$TB_OFF_ROOT/off-task/off-task--deliver.md"
2410
- flow_agents_node "$WRITER" init-plan "$TB_OFF_ROOT/off-task/off-task--deliver.md" --task-slug off-task --source-request x --summary y --next-action z >/dev/null 2>&1
2411
- if echo "$LC_HELD" | grep -qE "lc-task.*agent-LC.*held" && echo "$LC_FREE" | grep -qE "lc-task.*agent-LC.*free" && [ ! -f "$TB_OFF_ROOT/liveness/events.jsonl" ]; then
2412
- _pass "liveness lifecycle: init-plan claims (held), advance→delivered releases (free); opt-in respected (no events when disabled)"
2413
- else
2414
- _fail "liveness lifecycle mismatch: held=[$LC_HELD] free=[$LC_FREE] off=$([ -f "$TB_OFF_ROOT/liveness/events.jsonl" ] && echo wrote || echo none)"
2918
+ FLOW_AGENTS_LIVENESS=off flow_agents_node "$WRITER" init-plan "$TB_OFF_ROOT/off-task/off-task--deliver.md" --task-slug off-task --source-request x --summary y --next-action z >/dev/null 2>&1
2919
+ if echo "$LC_HELD" | grep -qE "lc-task.*agent-LC.*held" \
2920
+ && echo "$LC_CLAIM_EVENTS" | grep -qE '"type":"claim","subjectId":"lc-task"' \
2921
+ && echo "$LC_FREE" | grep -qE "lc-task.*agent-LC.*free" \
2922
+ && [ ! -f "$TB_OFF_ROOT/liveness/events.jsonl" ]; then
2923
+ _pass "liveness lifecycle: no env set → init-plan writes a claim event + shows held (default-on proof), advance→delivered releases (free); FLOW_AGENTS_LIVENESS=off respected (no events written)"
2924
+ else
2925
+ _fail "liveness lifecycle mismatch: held=[$LC_HELD] claim-event=[$(echo "$LC_CLAIM_EVENTS" | head -c 200)] free=[$LC_FREE] off=$([ -f "$TB_OFF_ROOT/liveness/events.jsonl" ] && echo wrote || echo none)"
2926
+ fi
2927
+
2928
+ # ─── F2 (#288 fix iteration 1, cr-HIGH coverage gap): FLOW_AGENTS_LIVENESS_TTL_SECONDS is
2929
+ # honored by the lifecycle auto-claim path — init-plan with the env var set to a custom value
2930
+ # must emit a claim event carrying that exact ttlSeconds, not the DEFAULT_TTL_SECONDS literal ──
2931
+ TB_TTL_ROOT="$TMPDIR_EVAL/liveness-ttl-env/.kontourai/flow-agents"
2932
+ TB_TTL_DIR="$TB_TTL_ROOT/ttl-task"; mkdir -p "$TB_TTL_DIR"
2933
+ cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$TB_TTL_DIR/ttl-task--deliver.md"
2934
+ FLOW_AGENTS_ACTOR=agent-TTL FLOW_AGENTS_LIVENESS_TTL_SECONDS=300 flow_agents_node "$WRITER" init-plan "$TB_TTL_DIR/ttl-task--deliver.md" --task-slug ttl-task --source-request x --summary y --next-action z --timestamp "2026-06-25T11:50:00Z" >/dev/null 2>&1
2935
+ TTL_CLAIM_EVENTS=$(cat "$TB_TTL_ROOT/liveness/events.jsonl" 2>/dev/null)
2936
+ if echo "$TTL_CLAIM_EVENTS" | grep -qE '"type":"claim","subjectId":"ttl-task".*"ttlSeconds":300'; then
2937
+ _pass "liveness lifecycle: FLOW_AGENTS_LIVENESS_TTL_SECONDS=300 → emitted claim carries ttlSeconds:300 (F2, AC2)"
2938
+ else
2939
+ _fail "liveness lifecycle did not honor FLOW_AGENTS_LIVENESS_TTL_SECONDS=300: events=[$(echo "$TTL_CLAIM_EVENTS" | head -c 200)]"
2940
+ fi
2941
+
2942
+ # ─── F5 (#288 fix iteration 1, sec-LOW): hostile subjectId is stripped from the terminal print,
2943
+ # but preserved verbatim in the persisted event (display-only concern, not a write-shape change) ──
2944
+ TB_HOSTILE_ROOT="$TMPDIR_EVAL/liveness-hostile-subject/.kontourai/flow-agents"
2945
+ HOSTILE_SUBJECT=$'hostile\x1b[31msubj\x07tail'
2946
+ HOSTILE_OUT=$(flow_agents_node "$WRITER" liveness claim "$HOSTILE_SUBJECT" --actor agent-hostile --at "2026-06-25T11:50:00Z" --ttl 1800 --artifact-root "$TB_HOSTILE_ROOT" 2>&1)
2947
+ HOSTILE_EVENT=$(cat "$TB_HOSTILE_ROOT/liveness/events.jsonl" 2>/dev/null)
2948
+ if printf '%s' "$HOSTILE_OUT" | grep -qF $'\x1b[31m'; then
2949
+ HOSTILE_ESCAPE_LEAKED=true
2950
+ else
2951
+ HOSTILE_ESCAPE_LEAKED=false
2952
+ fi
2953
+ if [[ "$HOSTILE_ESCAPE_LEAKED" == "false" ]] \
2954
+ && printf '%s' "$HOSTILE_OUT" | grep -qF "hostile" \
2955
+ && printf '%s' "$HOSTILE_OUT" | grep -qF "tail" \
2956
+ && echo "$HOSTILE_EVENT" | grep -qF 'hostile'; then
2957
+ _pass "liveness claim: hostile subjectId (ANSI escape + control char) is stripped from the terminal confirmation print, but preserved verbatim in the persisted event (F5)"
2958
+ else
2959
+ _fail "liveness claim hostile-subjectId print check failed: escape-leaked=$HOSTILE_ESCAPE_LEAKED out=$(printf '%s' "$HOSTILE_OUT" | cat -v) event=$(echo "$HOSTILE_EVENT" | head -c 200)"
2960
+ fi
2961
+
2962
+ # ─── AC5 (#287): two auto-derived sessions (no --actor, no FLOW_AGENTS_ACTOR) → two distinct held holders ──
2963
+ # Simulates two concurrent sessions on one host via injected CLAUDE_CODE_SESSION_ID envs (the
2964
+ # runtime-native session-id signal actor-identity.js's resolveActor() reads at priority layer 2).
2965
+ # Neither invocation passes --actor; the actor is fully auto-derived.
2966
+ TB_TWOHOLDERS_ROOT="$TMPDIR_EVAL/liveness-two-holders/.kontourai/flow-agents"
2967
+ CLAUDE_CODE_SESSION_ID=sess-a flow_agents_node "$WRITER" liveness claim two-holder-subj --ttl 1800 --artifact-root "$TB_TWOHOLDERS_ROOT" >"$TMPDIR_EVAL/two-holders-a.out" 2>"$TMPDIR_EVAL/two-holders-a.err"
2968
+ TWO_HOLDERS_A_STATUS=$?
2969
+ CLAUDE_CODE_SESSION_ID=sess-b flow_agents_node "$WRITER" liveness claim two-holder-subj --ttl 1800 --artifact-root "$TB_TWOHOLDERS_ROOT" >"$TMPDIR_EVAL/two-holders-b.out" 2>"$TMPDIR_EVAL/two-holders-b.err"
2970
+ TWO_HOLDERS_B_STATUS=$?
2971
+ flow_agents_node "$WRITER" liveness status --json --subject two-holder-subj --artifact-root "$TB_TWOHOLDERS_ROOT" >"$TMPDIR_EVAL/two-holders-status.json" 2>"$TMPDIR_EVAL/two-holders-status.err"
2972
+ if [[ "$TWO_HOLDERS_A_STATUS" -eq 0 && "$TWO_HOLDERS_B_STATUS" -eq 0 ]] \
2973
+ && node - "$TMPDIR_EVAL/two-holders-status.json" <<'NODE'
2974
+ const fs = require("node:fs");
2975
+ const rows = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
2976
+ if (!Array.isArray(rows) || rows.length !== 2) throw new Error("expected 2 rows, got " + JSON.stringify(rows));
2977
+ const held = rows.filter((r) => r.label === "held");
2978
+ if (held.length !== 2) throw new Error("expected 2 held rows, got " + JSON.stringify(rows));
2979
+ const actors = new Set(held.map((r) => r.actor));
2980
+ if (actors.size !== 2) throw new Error("expected 2 distinct actors, got " + JSON.stringify([...actors]));
2981
+ if ([...actors].some((a) => String(a).toLowerCase() === "local")) throw new Error("actor collapsed to local: " + JSON.stringify([...actors]));
2982
+ NODE
2983
+ then
2984
+ _pass "liveness (AC5): two auto-derived sessions (no --actor) produce two distinct held holders, not one collapsed 'local' holder"
2985
+ else
2986
+ _fail "liveness (AC5) two-holder check failed: $(cat "$TMPDIR_EVAL/two-holders-a.out" "$TMPDIR_EVAL/two-holders-a.err" "$TMPDIR_EVAL/two-holders-b.out" "$TMPDIR_EVAL/two-holders-b.err" "$TMPDIR_EVAL/two-holders-status.err") json=$(cat "$TMPDIR_EVAL/two-holders-status.json" 2>/dev/null)"
2987
+ fi
2988
+
2989
+ # ─── AC6 (#287): forced-unresolved actor → liveness write exits nonzero with remediation ──
2990
+ # FLOW_AGENTS_ACTOR_TEST_FORCE_UNRESOLVED=1 (together with NODE_ENV=test — F4, #287 fix iteration 1:
2991
+ # the hatch requires BOTH vars) is the test-only escape hatch in actor-identity.js's resolveActor():
2992
+ # it short-circuits to {actor: "", source: "test-forced-unresolved"} before any real runtime/ancestry
2993
+ # detection runs, deterministically proving the fail-loud path without needing to sabotage ps/proc.
2994
+ # No --actor flag, no (non-forced) FLOW_AGENTS_ACTOR set.
2995
+ TB_FORCEUNRES_ROOT="$TMPDIR_EVAL/liveness-forced-unresolved/.kontourai/flow-agents"
2996
+ if FLOW_AGENTS_ACTOR_TEST_FORCE_UNRESOLVED=1 NODE_ENV=test flow_agents_node "$WRITER" liveness claim forced-unresolved-subj --artifact-root "$TB_FORCEUNRES_ROOT" >"$TMPDIR_EVAL/forced-unresolved.out" 2>"$TMPDIR_EVAL/forced-unresolved.err"; then
2997
+ _fail "liveness (AC6): claim with FLOW_AGENTS_ACTOR_TEST_FORCE_UNRESOLVED=1 should have exited nonzero"
2998
+ elif rg -q -- '--actor' "$TMPDIR_EVAL/forced-unresolved.err" && rg -q -- 'FLOW_AGENTS_ACTOR' "$TMPDIR_EVAL/forced-unresolved.err" \
2999
+ && [[ ! -f "$TB_FORCEUNRES_ROOT/liveness/events.jsonl" ]]; then
3000
+ _pass "liveness (AC6): forced-unresolved actor (no --actor, no runtime/ancestry signal) exits nonzero naming --actor and FLOW_AGENTS_ACTOR"
3001
+ else
3002
+ _fail "liveness (AC6) forced-unresolved rejection lacked remediation or wrote an event: $(cat "$TMPDIR_EVAL/forced-unresolved.out" "$TMPDIR_EVAL/forced-unresolved.err")"
3003
+ fi
3004
+
3005
+ # ─── AC3 (#287): literal "local" actor is rejected even when explicitly set, not just when absent ──
3006
+ # Deviation note (see worker report): `FLOW_AGENTS_ACTOR=local` alone (env override, no --actor) is
3007
+ # REJECTED by actor-identity.js's resolveActor() at the override layer (it can never round-trip back
3008
+ # in via that seam — see the module's own header comment) but the command then falls through to a
3009
+ # real runtime/ancestry-derived actor and SUCCEEDS with that distinct, non-"local" value whenever any
3010
+ # resolution layer is available (true in this sandbox via CLAUDE_CODE_SESSION_ID, and true in nearly
3011
+ # any environment via the process-ancestry fallback) — empirically confirmed exit 0 during this
3012
+ # task's investigation. The `--actor local` explicit-flag path bypasses resolveActor's override
3013
+ # rejection and hits the liveness() guard directly, so it is the deterministic, environment-independent
3014
+ # way to prove "explicitly set literal local is rejected, never silently written" at the CLI level.
3015
+ TB_LITERALLOCAL_ROOT="$TMPDIR_EVAL/liveness-literal-local/.kontourai/flow-agents"
3016
+ if flow_agents_node "$WRITER" liveness claim literal-local-subj --actor local --artifact-root "$TB_LITERALLOCAL_ROOT" >"$TMPDIR_EVAL/literal-local.out" 2>"$TMPDIR_EVAL/literal-local.err"; then
3017
+ _fail "liveness (AC3): claim with --actor local should have exited nonzero"
3018
+ elif rg -q -- '--actor' "$TMPDIR_EVAL/literal-local.err" && rg -q -- 'FLOW_AGENTS_ACTOR' "$TMPDIR_EVAL/literal-local.err" \
3019
+ && [[ ! -f "$TB_LITERALLOCAL_ROOT/liveness/events.jsonl" ]]; then
3020
+ _pass "liveness (AC3): explicit --actor local (case-insensitive literal) exits nonzero naming --actor and FLOW_AGENTS_ACTOR"
3021
+ else
3022
+ _fail "liveness (AC3) literal-local rejection lacked remediation or wrote an event: $(cat "$TMPDIR_EVAL/literal-local.out" "$TMPDIR_EVAL/literal-local.err")"
3023
+ fi
3024
+ if flow_agents_node "$WRITER" liveness claim literal-local-subj-upper --actor LOCAL --artifact-root "$TB_LITERALLOCAL_ROOT" >"$TMPDIR_EVAL/literal-local-upper.out" 2>"$TMPDIR_EVAL/literal-local-upper.err"; then
3025
+ _fail "liveness (AC3): claim with --actor LOCAL (case-insensitive) should have exited nonzero"
3026
+ else
3027
+ _pass "liveness (AC3): explicit --actor LOCAL is rejected case-insensitively"
3028
+ fi
3029
+ # Env-only override path: FLOW_AGENTS_ACTOR=local (no --actor) must never let "local" round-trip back
3030
+ # in as the persisted actor — it falls through to auto-derivation instead of dying when a resolution
3031
+ # layer is available (documented behavior of the override seam, see comment above).
3032
+ if FLOW_AGENTS_ACTOR=local flow_agents_node "$WRITER" liveness claim literal-local-env-subj --artifact-root "$TB_LITERALLOCAL_ROOT" >"$TMPDIR_EVAL/literal-local-env.out" 2>"$TMPDIR_EVAL/literal-local-env.err" \
3033
+ && ! rg -q '"actor":"local"' "$TB_LITERALLOCAL_ROOT/liveness/events.jsonl" 2>/dev/null \
3034
+ && ! rg -qi 'by local$' "$TMPDIR_EVAL/literal-local-env.out"; then
3035
+ _pass "liveness (AC3): FLOW_AGENTS_ACTOR=local override is rejected and never round-trips into the persisted actor"
3036
+ else
3037
+ _fail "liveness (AC3) FLOW_AGENTS_ACTOR=local override handling regressed: $(cat "$TMPDIR_EVAL/literal-local-env.out" "$TMPDIR_EVAL/literal-local-env.err")"
3038
+ fi
3039
+
3040
+ # ─── T4 (#287 fix iteration 2, F7): explicit --actor value that strips to empty under the allowed
3041
+ # [A-Za-z0-9_.-] charset is a hard error on the write path (unlike the env-override seam, which
3042
+ # falls through to derivation) — claim exits nonzero with remediation and writes no event.
3043
+ TB_STRIPEMPTY_ROOT="$TMPDIR_EVAL/liveness-strip-empty/.kontourai/flow-agents"
3044
+ if flow_agents_node "$WRITER" liveness claim strip-empty-subj --actor ':::' --artifact-root "$TB_STRIPEMPTY_ROOT" >"$TMPDIR_EVAL/strip-empty.out" 2>"$TMPDIR_EVAL/strip-empty.err"; then
3045
+ _fail "liveness (T4/F7): claim with --actor ':::' should have exited nonzero"
3046
+ elif rg -q -- '--actor' "$TMPDIR_EVAL/strip-empty.err" && [[ ! -f "$TB_STRIPEMPTY_ROOT/liveness/events.jsonl" ]]; then
3047
+ _pass "liveness (T4/F7): --actor ':::' (strips to empty) exits nonzero with --actor remediation and writes no event"
3048
+ else
3049
+ _fail "liveness (T4/F7) strip-to-empty --actor rejection lacked remediation or wrote an event: $(cat "$TMPDIR_EVAL/strip-empty.out" "$TMPDIR_EVAL/strip-empty.err")"
3050
+ fi
3051
+
3052
+ # ─── AC4 (#287): backward-compatible reads of legacy literal "local" events ──
3053
+ # Hand-seed liveness/events.jsonl directly (bypassing the CLI) to simulate a pre-#287 event whose
3054
+ # actor field is the literal string "local", then confirm `liveness status` still parses, groups, and
3055
+ # labels it correctly — reads must keep tolerating historical "local" events (unlike writes).
3056
+ TB_LEGACY_ROOT="$TMPDIR_EVAL/liveness-legacy/.kontourai/flow-agents"
3057
+ mkdir -p "$TB_LEGACY_ROOT/liveness"
3058
+ printf '%s\n' '{"type":"claim","subjectId":"legacy-subj","actor":"local","at":"2026-06-25T11:50:00Z","ttlSeconds":1800}' > "$TB_LEGACY_ROOT/liveness/events.jsonl"
3059
+ LEGACY_STATUS_OUT=$(flow_agents_node "$WRITER" liveness status --subject legacy-subj --now "2026-06-25T12:00:00Z" --artifact-root "$TB_LEGACY_ROOT" 2>/dev/null)
3060
+ if echo "$LEGACY_STATUS_OUT" | grep -qE "legacy-subj.*local.*held"; then
3061
+ _pass "liveness (AC4): hand-seeded legacy actor:\"local\" event still parses as one held row"
3062
+ else
3063
+ _fail "liveness (AC4) legacy 'local' event failed to parse: $LEGACY_STATUS_OUT"
2415
3064
  fi
2416
3065
 
2417
3066
  # ─── AC8: bundle-writers fail LOUDLY when Surface unavailable — no silent data loss (#156) ──
2418
- TB_FO_DIR="$TMPDIR_EVAL/repo/.flow-agents/failopen"
3067
+ TB_FO_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/failopen"
2419
3068
  mkdir -p "$TB_FO_DIR"
2420
3069
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$TB_FO_DIR/failopen--deliver.md"
2421
3070
  flow_agents_node "$WRITER" init-plan "$TB_FO_DIR/failopen--deliver.md" --task-slug failopen --source-request x --summary y --next-action z --timestamp "2026-05-09T00:00:00Z" >/dev/null 2>&1
@@ -2435,7 +3084,7 @@ fi
2435
3084
  # field matches @kontourai/surface's exported statusFunctionVersion constant.
2436
3085
  # Also run hachure conformance vectors through Surface's deriveClaimStatus to
2437
3086
  # confirm our producer path produces canonical statuses.
2438
- TB_CONF_DIR="$TMPDIR_EVAL/repo/.flow-agents/trust-bundle-conformance"
3087
+ TB_CONF_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/trust-bundle-conformance"
2439
3088
  mkdir -p "$TB_CONF_DIR"
2440
3089
  cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$TB_CONF_DIR/trust-bundle-conformance--deliver.md"
2441
3090
  flow_agents_node "$WRITER" init-plan "$TB_CONF_DIR/trust-bundle-conformance--deliver.md" --source-request "Conformance fixture." --summary "Conformance fixture." --next-action "Record evidence and check statusFunctionVersion." --timestamp "2026-05-09T00:00:00Z" >"$TMPDIR_EVAL/tb-conf-init.out" 2>"$TMPDIR_EVAL/tb-conf-init.err"
@@ -2505,7 +3154,7 @@ fi
2505
3154
 
2506
3155
  # ─── Deterministic session slug from work-item ref (#161) ───────────────────
2507
3156
 
2508
- WORK_ITEM_ROOT="$TMPDIR_EVAL/work-item-repo/.flow-agents"
3157
+ WORK_ITEM_ROOT="$TMPDIR_EVAL/work-item-repo/.kontourai/flow-agents"
2509
3158
 
2510
3159
  # (a) --work-item derives deterministic slug kontourai-flow-agents-161
2511
3160
  if flow_agents_node "$WRITER" ensure-session \
@@ -2539,7 +3188,7 @@ else
2539
3188
  fi
2540
3189
 
2541
3190
  # (c) --task-slug wins over --work-item (back-compat: explicit overrides derived)
2542
- TASK_SLUG_ROOT="$TMPDIR_EVAL/task-slug-repo/.flow-agents"
3191
+ TASK_SLUG_ROOT="$TMPDIR_EVAL/task-slug-repo/.kontourai/flow-agents"
2543
3192
  if flow_agents_node "$WRITER" ensure-session \
2544
3193
  --artifact-root "$TASK_SLUG_ROOT" \
2545
3194
  --task-slug "manual-slug" \
@@ -2555,7 +3204,7 @@ else
2555
3204
  fi
2556
3205
 
2557
3206
  # (c2) --task-slug only (no --work-item) still works
2558
- TASK_SLUG_ONLY_ROOT="$TMPDIR_EVAL/task-slug-only-repo/.flow-agents"
3207
+ TASK_SLUG_ONLY_ROOT="$TMPDIR_EVAL/task-slug-only-repo/.kontourai/flow-agents"
2559
3208
  if flow_agents_node "$WRITER" ensure-session \
2560
3209
  --artifact-root "$TASK_SLUG_ONLY_ROOT" \
2561
3210
  --task-slug "explicit-only" \
@@ -2572,7 +3221,7 @@ fi
2572
3221
  # ensure-session establishes the slug; liveness events (emitted by init-plan/advance-state) key
2573
3222
  # on that same slug as subjectId. We verify this by emitting two liveness claim events directly
2574
3223
  # via `liveness claim` using the slug derived from the ref, then asserting both share subjectId.
2575
- LIVENESS_WORK_ROOT="$TMPDIR_EVAL/liveness-wi-repo/.flow-agents"
3224
+ LIVENESS_WORK_ROOT="$TMPDIR_EVAL/liveness-wi-repo/.kontourai/flow-agents"
2576
3225
  # First: ensure-session --work-item produces the expected slug (directory name proof)
2577
3226
  if flow_agents_node "$WRITER" ensure-session \
2578
3227
  --artifact-root "$LIVENESS_WORK_ROOT" \