@kontourai/flow-agents 3.2.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (281) hide show
  1. package/.github/workflows/add-to-project.yml +15 -0
  2. package/.github/workflows/ci.yml +173 -0
  3. package/CHANGELOG.md +54 -0
  4. package/CONTEXT.md +5 -1
  5. package/README.md +19 -8
  6. package/build/src/builder-flow-run-adapter.d.ts +80 -0
  7. package/build/src/builder-flow-run-adapter.js +241 -0
  8. package/build/src/builder-flow-runtime.d.ts +16 -0
  9. package/build/src/builder-flow-runtime.js +290 -0
  10. package/build/src/cli/assignment-provider.js +10 -1
  11. package/build/src/cli/builder-run.d.ts +1 -0
  12. package/build/src/cli/builder-run.js +27 -0
  13. package/build/src/cli/effective-backlog-settings.js +70 -2
  14. package/build/src/cli/init.d.ts +34 -0
  15. package/build/src/cli/init.js +341 -61
  16. package/build/src/cli/kit.js +55 -12
  17. package/build/src/cli/pull-work-provider.js +346 -5
  18. package/build/src/cli/skill-drift-check.d.ts +1 -0
  19. package/build/src/cli/skill-drift-check.js +165 -0
  20. package/build/src/cli/telemetry-doctor.d.ts +37 -0
  21. package/build/src/cli/telemetry-doctor.js +53 -6
  22. package/build/src/cli/validate-hook-influence.js +37 -7
  23. package/build/src/cli/workflow-artifact-cleanup-audit.js +418 -11
  24. package/build/src/cli/workflow-sidecar.d.ts +310 -1
  25. package/build/src/cli/workflow-sidecar.js +1914 -126
  26. package/build/src/cli.js +5 -0
  27. package/build/src/flow-kit/validate.d.ts +54 -34
  28. package/build/src/flow-kit/validate.js +237 -26
  29. package/build/src/index.d.ts +2 -0
  30. package/build/src/index.js +1 -0
  31. package/build/src/lib/console-connect-options.d.ts +97 -0
  32. package/build/src/lib/console-connect-options.js +199 -0
  33. package/build/src/lib/console-telemetry-validate.d.ts +49 -0
  34. package/build/src/lib/console-telemetry-validate.js +91 -0
  35. package/build/src/lib/flow-resolver.d.ts +54 -1
  36. package/build/src/lib/flow-resolver.js +112 -5
  37. package/build/src/lib/fs.d.ts +17 -0
  38. package/build/src/lib/fs.js +172 -0
  39. package/build/src/lib/local-artifact-root.d.ts +44 -1
  40. package/build/src/lib/local-artifact-root.js +131 -3
  41. package/build/src/runtime-adapters.d.ts +39 -3
  42. package/build/src/runtime-adapters.js +77 -31
  43. package/build/src/tools/build-universal-bundles.js +40 -2
  44. package/build/src/tools/codex-agent-routing.d.ts +2 -0
  45. package/build/src/tools/codex-agent-routing.js +49 -0
  46. package/build/src/tools/generate-context-map.js +1 -0
  47. package/build/src/tools/validate-source-tree.js +30 -3
  48. package/context/contracts/artifact-contract.md +16 -2
  49. package/context/scripts/hooks/lib/kit-catalog.js +235 -0
  50. package/context/scripts/hooks/lib/runnable-command.js +177 -0
  51. package/context/scripts/hooks/stop-goal-fit.js +278 -48
  52. package/context/scripts/hooks/workflow-steering.js +194 -22
  53. package/context/scripts/package.json +3 -0
  54. package/context/scripts/telemetry/install-console-config.sh +25 -4
  55. package/context/scripts/telemetry/lib/config.sh +102 -12
  56. package/context/scripts/telemetry/lib/pricing.sh +50 -0
  57. package/context/scripts/telemetry/lib/session.sh +3 -0
  58. package/context/scripts/telemetry/lib/transport.sh +87 -0
  59. package/context/scripts/telemetry/lib/usage.sh +205 -4
  60. package/context/scripts/telemetry/telemetry.conf +6 -0
  61. package/context/scripts/telemetry/telemetry.sh +48 -0
  62. package/context/settings/workspace-backlog-provider-settings.example.json +48 -0
  63. package/docs/agent-usage-feedback-loop.md +35 -0
  64. package/docs/architecture-engine-and-kits.md +110 -0
  65. package/docs/context-map.md +2 -0
  66. package/docs/coordination-guide.md +370 -0
  67. package/docs/decisions/agent-coordination.md +26 -9
  68. package/docs/decisions/embeddable-engine.md +152 -0
  69. package/docs/decisions/index.md +5 -3
  70. package/docs/decisions/trust-ledger-retention.md +88 -0
  71. package/docs/decisions/trust-reconcile.md +42 -9
  72. package/docs/decisions/workflow-enforcement.md +31 -9
  73. package/docs/fixture-ownership.md +6 -2
  74. package/docs/implementing-trust-reconciliation.md +129 -0
  75. package/docs/index.md +23 -9
  76. package/docs/integrations/flow-agents-console.md +275 -0
  77. package/docs/integrations/index.md +4 -0
  78. package/docs/kit-authoring-guide.md +52 -21
  79. package/docs/spec/builder-flow-runtime.md +80 -0
  80. package/docs/spec/runtime-hook-surface.md +45 -1
  81. package/docs/specs/economics-record-contract.md +270 -0
  82. package/docs/specs/harness-capability-matrix.md +74 -0
  83. package/docs/specs/learning-review-proposals-contract.md +340 -0
  84. package/docs/specs/routing-efficiency-review.md +59 -0
  85. package/docs/verifiable-trust.md +74 -25
  86. package/docs/workflow-artifact-lifecycle.md +38 -1
  87. package/docs/workflow-usage-guide.md +10 -0
  88. package/evals/acceptance/prove-capture-teeth.sh +132 -0
  89. package/evals/ci/antigaming-suite.sh +2 -0
  90. package/evals/ci/run-baseline.sh +78 -4
  91. package/evals/fixtures/economics/acceptance.json +12 -0
  92. package/evals/fixtures/economics/agents/tool-worker-1/events.jsonl +2 -0
  93. package/evals/fixtures/economics/agents/tool-worker-2/events.jsonl +2 -0
  94. package/evals/fixtures/economics/agents/tool-worker-3/events.jsonl +2 -0
  95. package/evals/fixtures/economics/agents/tool-worker-4/events.jsonl +1 -0
  96. package/evals/fixtures/economics/agents/tool-worker-5/events.jsonl +2 -0
  97. package/evals/fixtures/economics/critique.json +22 -0
  98. package/evals/fixtures/economics/expected-record.json +71 -0
  99. package/evals/fixtures/economics/session-usage-event.json +1 -0
  100. package/evals/fixtures/economics/state.json +11 -0
  101. package/evals/fixtures/economics/transcript.jsonl +3 -0
  102. package/evals/fixtures/hook-influence/cases.json +7 -7
  103. package/evals/fixtures/learning-review-proposals/balanced/economics.jsonl +6 -0
  104. package/evals/fixtures/learning-review-proposals/effect-follow-up/economics.jsonl +5 -0
  105. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-1/trust.bundle +21 -0
  106. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-2/trust.bundle +21 -0
  107. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-3/trust.bundle +21 -0
  108. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-4/trust.bundle +21 -0
  109. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-5/trust.bundle +21 -0
  110. package/evals/fixtures/learning-review-proposals/pattern-present/economics.jsonl +6 -0
  111. package/evals/fixtures/learning-review-proposals/pattern-present/expected-aggregates.json +30 -0
  112. package/evals/fixtures/learning-review-proposals/pattern-present/expected-aggregates.md +66 -0
  113. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-1/gate-review.inquiries.json +26 -0
  114. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-1/trust.bundle +21 -0
  115. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-2/gate-review.inquiries.json +26 -0
  116. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-2/trust.bundle +21 -0
  117. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-3/gate-review.inquiries.json +26 -0
  118. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-3/trust.bundle +21 -0
  119. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-4/gate-review.inquiries.json +26 -0
  120. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-4/trust.bundle +21 -0
  121. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-5/trust.bundle +21 -0
  122. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-6/trust.bundle +21 -0
  123. package/evals/fixtures/learning-review-proposals/repeat-window/economics.jsonl +6 -0
  124. package/evals/fixtures/learning-review-proposals/under-threshold/economics.jsonl +3 -0
  125. package/evals/fixtures/reconcile-preflight/disputed-critique-unsuperseded.json +48 -0
  126. package/evals/fixtures/reconcile-preflight/standalone-disputed-session-local.json +59 -0
  127. package/evals/fixtures/telemetry/usage-transcript-sample.jsonl +4 -0
  128. package/evals/fixtures/trust-reconcile-exploits/mcp-degrade.json +42 -0
  129. package/evals/integration/test_builder_entry_enforcement.sh +241 -0
  130. package/evals/integration/test_builder_step_producers.sh +18 -10
  131. package/evals/integration/test_bundle_install.sh +172 -0
  132. package/evals/integration/test_checkpoint_signing.sh +10 -2
  133. package/evals/integration/test_ci_actor_identity.sh +221 -0
  134. package/evals/integration/test_console_tenant_isolation.sh +167 -0
  135. package/evals/integration/test_critique_supersession_roundtrip.sh +4 -1
  136. package/evals/integration/test_dual_emit_flow_step.sh +10 -4
  137. package/evals/integration/test_economics_record.sh +674 -0
  138. package/evals/integration/test_effective_backlog_settings.sh +1 -1
  139. package/evals/integration/test_evidence_capture_hook.sh +17 -2
  140. package/evals/integration/test_exemption_usage_review.sh +198 -0
  141. package/evals/integration/test_fixture_retirement_audit.sh +2 -2
  142. package/evals/integration/test_flow_kit_install_git.sh +83 -0
  143. package/evals/integration/test_flowdef_session_activation.sh +0 -1
  144. package/evals/integration/test_flowdef_session_history_preservation.sh +13 -3
  145. package/evals/integration/test_gate_lockdown.sh +7 -0
  146. package/evals/integration/test_gate_review_inquiry_records.sh +9 -1
  147. package/evals/integration/test_goal_fit_hook.sh +2031 -0
  148. package/evals/integration/test_hook_category_behaviors.sh +8 -1
  149. package/evals/integration/test_hook_influence_cases.sh +25 -1
  150. package/evals/integration/test_install_merge.sh +227 -2
  151. package/evals/integration/test_kit_conformance_levels.sh +6 -6
  152. package/evals/integration/test_learning_review_proposals.sh +329 -0
  153. package/evals/integration/test_liveness_conflict_injection.sh +26 -22
  154. package/evals/integration/test_liveness_console_relay.sh +166 -0
  155. package/evals/integration/test_liveness_heartbeat.sh +17 -17
  156. package/evals/integration/test_liveness_worktree_root.sh +575 -0
  157. package/evals/integration/test_phase_map_and_gate_claim.sh +6 -1
  158. package/evals/integration/test_publish_delivery.sh +389 -2
  159. package/evals/integration/test_pull_work_board.sh +200 -0
  160. package/evals/integration/test_pull_work_provider.sh +1 -1
  161. package/evals/integration/test_reconcile_preflight.sh +304 -0
  162. package/evals/integration/test_record_check.sh +378 -0
  163. package/evals/integration/test_routing_efficiency.sh +71 -0
  164. package/evals/integration/test_runtime_adapter_activation.sh +28 -0
  165. package/evals/integration/test_session_resume_roundtrip.sh +16 -19
  166. package/evals/integration/test_skill_drift_check.sh +870 -0
  167. package/evals/integration/test_takeover_protocol.sh +340 -0
  168. package/evals/integration/test_telemetry.sh +445 -0
  169. package/evals/integration/test_telemetry_doctor.sh +66 -0
  170. package/evals/integration/test_telemetry_usage_pipeline.sh +228 -0
  171. package/evals/integration/test_trust_reconcile_negatives.sh +121 -13
  172. package/evals/integration/test_trust_reconcile_trailer_diagnostic.sh +247 -0
  173. package/evals/integration/test_usage_cost.sh +61 -0
  174. package/evals/integration/test_verify_hold.sh +910 -0
  175. package/evals/integration/test_veritas_governance_kit.sh +257 -0
  176. package/evals/integration/test_workflow_artifact_cleanup_audit.sh +575 -3
  177. package/evals/integration/test_workflow_sidecar_writer.sh +1395 -0
  178. package/evals/integration/test_workflow_steering_hook.sh +157 -16
  179. package/evals/integration/test_workspace_settings.sh +176 -0
  180. package/evals/lib/env.sh +26 -0
  181. package/evals/lib/node.sh +8 -0
  182. package/evals/run.sh +37 -0
  183. package/evals/static/test_ci_integration_coverage.sh +115 -0
  184. package/evals/static/test_declared_scope_forms_documented.sh +114 -0
  185. package/evals/static/test_universal_bundles.sh +34 -0
  186. package/evals/static/test_validate_source_kit_asset_scope.sh +259 -0
  187. package/evals/static/test_workflow_skills.sh +1 -1
  188. package/kits/builder/flows/build.flow.json +9 -18
  189. package/kits/builder/flows/publish-learn.flow.json +5 -1
  190. package/kits/builder/kit.json +120 -0
  191. package/kits/builder/skills/continue-work/SKILL.md +2 -0
  192. package/kits/builder/skills/deliver/SKILL.md +115 -0
  193. package/kits/builder/skills/evidence-gate/SKILL.md +12 -0
  194. package/kits/builder/skills/execute-plan/SKILL.md +9 -0
  195. package/kits/builder/skills/learning-review/SKILL.md +51 -0
  196. package/kits/builder/skills/plan-work/SKILL.md +17 -20
  197. package/kits/builder/skills/pull-work/SKILL.md +33 -2
  198. package/kits/builder/skills/release-readiness/SKILL.md +12 -0
  199. package/kits/knowledge/kit.json +9 -0
  200. package/kits/veritas-governance/docs/README.md +113 -7
  201. package/kits/veritas-governance/fixtures/exemption/approved.trust-bundle.json +74 -0
  202. package/kits/veritas-governance/fixtures/exemption/not-approved.trust-bundle.json +74 -0
  203. package/kits/veritas-governance/fixtures/exemption-review/mixed-fresh-stale.DECLARED.json +14 -0
  204. package/kits/veritas-governance/flows/exemption-issuance.flow.json +35 -0
  205. package/kits/veritas-governance/kit.json +19 -0
  206. package/kits/veritas-governance/skills/exemption-usage-review/SKILL.md +128 -0
  207. package/kits/veritas-governance/skills/exemption-usage-review/review-exemptions.mjs +231 -0
  208. package/package.json +2 -2
  209. package/packaging/manifest.json +29 -0
  210. package/schemas/backlog-provider-settings.schema.json +13 -0
  211. package/schemas/workflow-state.schema.json +44 -0
  212. package/scripts/README.md +4 -0
  213. package/scripts/check-content-boundary.cjs +8 -1
  214. package/scripts/ci/trust-reconcile.js +214 -253
  215. package/scripts/hooks/codex-hook-adapter.js +77 -2
  216. package/scripts/hooks/evidence-capture.js +38 -5
  217. package/scripts/hooks/lib/actor-identity.js +82 -0
  218. package/scripts/hooks/lib/codex-exit-code.js +316 -0
  219. package/scripts/hooks/lib/kit-catalog.js +235 -0
  220. package/scripts/hooks/lib/liveness-write.js +28 -1
  221. package/scripts/hooks/lib/local-artifact-paths.js +97 -1
  222. package/scripts/hooks/lib/runnable-command.js +177 -0
  223. package/scripts/hooks/lib/skill-drift.js +350 -0
  224. package/scripts/hooks/stop-goal-fit.js +278 -48
  225. package/scripts/hooks/workflow-steering.js +194 -22
  226. package/scripts/install-codex-home.sh +97 -47
  227. package/scripts/install-merge.js +72 -14
  228. package/scripts/install-owned-files.js +178 -0
  229. package/scripts/lib/reconcile-shape.js +381 -0
  230. package/scripts/liveness/relay.sh +84 -0
  231. package/scripts/telemetry/economics-record.schema.json +145 -0
  232. package/scripts/telemetry/economics-record.sh +331 -0
  233. package/scripts/telemetry/install-console-config.sh +25 -4
  234. package/scripts/telemetry/learning-review-decide.sh +124 -0
  235. package/scripts/telemetry/learning-review-proposals.schema.json +161 -0
  236. package/scripts/telemetry/learning-review-proposals.sh +484 -0
  237. package/scripts/telemetry/lib/config.sh +102 -12
  238. package/scripts/telemetry/lib/pricing.sh +14 -6
  239. package/scripts/telemetry/lib/session.sh +3 -0
  240. package/scripts/telemetry/lib/transport.sh +133 -15
  241. package/scripts/telemetry/lib/usage.sh +121 -28
  242. package/scripts/telemetry/routing-efficiency.sh +0 -0
  243. package/scripts/telemetry/telemetry.conf +6 -0
  244. package/scripts/telemetry/telemetry.sh +48 -0
  245. package/src/builder-flow-run-adapter.ts +357 -0
  246. package/src/builder-flow-runtime.ts +348 -0
  247. package/src/cli/assignment-provider.ts +12 -1
  248. package/src/cli/builder-flow-run-adapter.test.mjs +495 -0
  249. package/src/cli/builder-flow-runtime.test.mjs +213 -0
  250. package/src/cli/builder-run.ts +28 -0
  251. package/src/cli/codex-agent-routing.test.mjs +44 -0
  252. package/src/cli/codex-exit-code.test.mjs +207 -0
  253. package/src/cli/console-connect-options.test.mjs +329 -0
  254. package/src/cli/console-telemetry-validate.test.mjs +157 -0
  255. package/src/cli/effective-backlog-settings.ts +68 -2
  256. package/src/cli/flow-resolver-composition.test.mjs +72 -0
  257. package/src/cli/init.test.mjs +161 -0
  258. package/src/cli/init.ts +407 -62
  259. package/src/cli/kit-metadata-security.test.mjs +443 -0
  260. package/src/cli/kit.ts +50 -12
  261. package/src/cli/pull-work-provider.ts +377 -3
  262. package/src/cli/sidecar-pure-helpers.test.mjs +64 -0
  263. package/src/cli/skill-drift-check.ts +196 -0
  264. package/src/cli/telemetry-doctor.test.mjs +53 -0
  265. package/src/cli/telemetry-doctor.ts +50 -7
  266. package/src/cli/validate-hook-influence.ts +37 -6
  267. package/src/cli/workflow-artifact-cleanup-audit.ts +483 -10
  268. package/src/cli/workflow-sidecar.ts +1980 -119
  269. package/src/cli.ts +5 -0
  270. package/src/flow-kit/validate.ts +277 -38
  271. package/src/index.ts +19 -0
  272. package/src/lib/console-connect-options.ts +261 -0
  273. package/src/lib/console-telemetry-validate.ts +88 -0
  274. package/src/lib/flow-resolver.ts +117 -4
  275. package/src/lib/fs.ts +160 -0
  276. package/src/lib/local-artifact-root.ts +129 -3
  277. package/src/runtime-adapters.ts +113 -33
  278. package/src/tools/build-universal-bundles.ts +36 -2
  279. package/src/tools/codex-agent-routing.ts +48 -0
  280. package/src/tools/generate-context-map.ts +1 -0
  281. package/src/tools/validate-source-tree.ts +29 -3
@@ -0,0 +1,241 @@
1
+ #!/usr/bin/env bash
2
+ # Proves first-step workflow entry and provider-neutral local work-item anchoring (#438).
3
+
4
+ set -uo pipefail
5
+
6
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
7
+ source "$ROOT/evals/lib/node.sh"
8
+
9
+ TMP="$(mktemp -d)"
10
+ errors=0
11
+ trap 'rm -rf "$TMP"' EXIT
12
+
13
+ pass() { printf ' PASS %s\n' "$1"; }
14
+ fail() { printf ' FAIL %s\n' "$1"; errors=$((errors + 1)); }
15
+
16
+ WRITER="workflow-sidecar"
17
+
18
+ echo "=== Builder workflow entry enforcement ==="
19
+
20
+ REFUSED_ROOT="$TMP/refused/.kontourai/flow-agents"
21
+ if flow_agents_node "$WRITER" ensure-session \
22
+ --artifact-root "$REFUSED_ROOT" \
23
+ --task-slug refused-plan \
24
+ --title "Refused plan entry" \
25
+ --summary "Must enter through the declared prefix." \
26
+ --flow-id builder.build \
27
+ --step-id plan >"$TMP/refused.out" 2>&1; then
28
+ fail "fresh later-step entry is rejected"
29
+ elif [[ ! -e "$REFUSED_ROOT" ]] && grep -q 'must start at first step "pull-work"' "$TMP/refused.out"; then
30
+ pass "fresh later-step entry is rejected before artifact-root creation"
31
+ else
32
+ fail "later-step refusal wrote files or returned the wrong diagnostic: $(cat "$TMP/refused.out")"
33
+ fi
34
+
35
+ AD_HOC_ROOT="$TMP/ad-hoc/.kontourai/flow-agents"
36
+ if flow_agents_node "$WRITER" ensure-session \
37
+ --artifact-root "$AD_HOC_ROOT" \
38
+ --task-slug refused-ad-hoc \
39
+ --title "Refused ad-hoc entry" \
40
+ --summary "A reason is not workflow authority." \
41
+ --flow-id builder.build \
42
+ --step-id plan \
43
+ --ad-hoc-reason "skip the prefix" >"$TMP/ad-hoc.out" 2>&1; then
44
+ fail "ad-hoc reason cannot authorize later-step entry"
45
+ elif [[ ! -e "$AD_HOC_ROOT" ]] && grep -q 'cannot authorize workflow entry' "$TMP/ad-hoc.out"; then
46
+ pass "ad-hoc reason cannot authorize later-step entry or write artifacts"
47
+ else
48
+ fail "ad-hoc refusal wrote files or returned the wrong diagnostic: $(cat "$TMP/ad-hoc.out")"
49
+ fi
50
+
51
+ LOCAL_ROOT="$TMP/local/.kontourai/flow-agents"
52
+ if flow_agents_node "$WRITER" ensure-session \
53
+ --artifact-root "$LOCAL_ROOT" \
54
+ --task-slug local-request \
55
+ --title "Local request" \
56
+ --summary "Providerless work still needs an anchor." \
57
+ --flow-id builder.build \
58
+ --timestamp "2026-07-10T00:00:00Z" >"$TMP/local.out" 2>&1; then
59
+ if node - "$LOCAL_ROOT" <<'NODE'
60
+ const fs = require('node:fs');
61
+ const path = require('node:path');
62
+ const root = process.argv[2];
63
+ const current = JSON.parse(fs.readFileSync(path.join(root, 'current.json'), 'utf8'));
64
+ const state = JSON.parse(fs.readFileSync(path.join(root, 'local-request', 'state.json'), 'utf8'));
65
+ const workItem = JSON.parse(fs.readFileSync(path.join(root, 'local-request', 'work-item.json'), 'utf8'));
66
+ if (current.active_flow_id !== 'builder.build' || current.active_step_id !== 'pull-work') process.exit(1);
67
+ if (state.status !== 'new' || state.phase !== 'pickup') process.exit(1);
68
+ if (JSON.stringify(state.work_item_refs) !== JSON.stringify(['local:local-request'])) process.exit(1);
69
+ if (workItem.id !== 'local-request' || workItem.title !== 'Local request') process.exit(1);
70
+ if (workItem.source_provider?.kind !== 'local' || workItem.source_provider?.path !== 'work-item.json') process.exit(1);
71
+ if (!state.next_action?.summary?.includes('builder-run start') || !state.next_action?.summary?.includes('`pull-work`')) process.exit(1);
72
+ NODE
73
+ then
74
+ pass "providerless request creates a local Work Item and starts at pull-work"
75
+ else
76
+ fail "local Work Item or first-step state is invalid"
77
+ fi
78
+ else
79
+ fail "providerless Builder entry failed: $(cat "$TMP/local.out")"
80
+ fi
81
+
82
+ LOCAL_SESSION="$LOCAL_ROOT/local-request"
83
+ if flow_agents_node builder-run start --session-dir "$LOCAL_SESSION" >"$TMP/builder-start.out" 2>&1 \
84
+ && node - "$TMP/local" "$LOCAL_SESSION" <<'NODE'
85
+ const fs = require('node:fs');
86
+ const path = require('node:path');
87
+ const project = process.argv[2];
88
+ const session = process.argv[3];
89
+ const flowState = JSON.parse(fs.readFileSync(path.join(project, '.kontourai', 'flow', 'runs', 'local-request', 'state.json'), 'utf8'));
90
+ const sidecar = JSON.parse(fs.readFileSync(path.join(session, 'state.json'), 'utf8'));
91
+ if (flowState.current_step !== 'pull-work' || flowState.subject !== 'local:local-request') process.exit(1);
92
+ if (sidecar.flow_run?.current_step !== 'pull-work') process.exit(1);
93
+ if (JSON.stringify(sidecar.next_action?.skills) !== JSON.stringify(['pull-work'])) process.exit(1);
94
+ if (!sidecar.next_action?.command?.includes('builder-run sync')) process.exit(1);
95
+ NODE
96
+ then
97
+ pass "small-model entry command creates canonical Flow run and projects pull-work action"
98
+ else
99
+ fail "canonical Builder run did not start or project correctly: $(cat "$TMP/builder-start.out")"
100
+ fi
101
+
102
+ if flow_agents_node "$WRITER" record-gate-claim "$LOCAL_SESSION" \
103
+ --expectation selected-work \
104
+ --status pass \
105
+ --summary "Selected local:local-request with scope and acceptance context." \
106
+ --evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/local-request/work-item.json","summary":"Provider-neutral local Work Item."}' \
107
+ >"$TMP/selected-work.out" 2>&1 \
108
+ && node - "$TMP/local" "$LOCAL_SESSION" <<'NODE'
109
+ const fs = require('node:fs');
110
+ const path = require('node:path');
111
+ const project = process.argv[2];
112
+ const session = process.argv[3];
113
+ const flowState = JSON.parse(fs.readFileSync(path.join(project, '.kontourai', 'flow', 'runs', 'local-request', 'state.json'), 'utf8'));
114
+ const sidecar = JSON.parse(fs.readFileSync(path.join(session, 'state.json'), 'utf8'));
115
+ if (flowState.current_step !== 'design-probe') process.exit(1);
116
+ if (sidecar.flow_run?.current_step !== 'design-probe') process.exit(1);
117
+ if (JSON.stringify(sidecar.next_action?.skills) !== JSON.stringify(['pickup-probe'])) process.exit(1);
118
+ NODE
119
+ then
120
+ pass "gate-claim write synchronizes Flow and projects the next skill"
121
+ else
122
+ fail "selected-work claim did not advance the canonical run: $(cat "$TMP/selected-work.out")"
123
+ fi
124
+
125
+ if FLOW_AGENTS_GOAL_FIT_MODE=block FLOW_AGENTS_GOAL_FIT_MAX_BLOCKS=100000 \
126
+ node "$ROOT/scripts/hooks/stop-goal-fit.js" >"$TMP/stop.out" 2>"$TMP/stop.err" <<JSON
127
+ {"hook_event_name":"Stop","cwd":"$TMP/local"}
128
+ JSON
129
+ then
130
+ fail "active pre-execution Flow run should block Stop"
131
+ else
132
+ stop_status=$?
133
+ if [[ "$stop_status" -eq 2 ]] \
134
+ && grep -q 'required skills: pickup-probe' "$TMP/stop.err" \
135
+ && grep -q 'builder-run sync' "$TMP/stop.err" \
136
+ && grep -q 'release skipped for active Flow run' "$TMP/stop.err" \
137
+ && node - "$LOCAL_SESSION/state.json" <<'NODE'
138
+ const fs = require('node:fs');
139
+ const state = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
140
+ if (state.flow_run?.status !== 'active' || state.flow_run?.current_step !== 'design-probe') process.exit(1);
141
+ NODE
142
+ then
143
+ pass "active Flow run blocks Stop, preserves liveness, and exposes executable guidance"
144
+ else
145
+ fail "active Flow Stop enforcement or guidance was incomplete (exit $stop_status): $(cat "$TMP/stop.err")"
146
+ fi
147
+ fi
148
+
149
+ if flow_agents_node "$WRITER" record-gate-claim "$LOCAL_SESSION" \
150
+ --expectation pickup-probe-readiness \
151
+ --status pass \
152
+ --summary "Pickup Probe confirmed scope and planning readiness." \
153
+ --evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/local-request/work-item.json","summary":"Probe fixture evidence."}' >/dev/null 2>&1 \
154
+ && flow_agents_node "$WRITER" record-gate-claim "$LOCAL_SESSION" \
155
+ --expectation probe-decisions-or-accepted-gaps \
156
+ --status pass \
157
+ --summary "Probe decisions and accepted gaps are recorded." \
158
+ --evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/local-request/work-item.json","summary":"Probe decision fixture evidence."}' >/dev/null 2>&1 \
159
+ && flow_agents_node "$WRITER" record-gate-claim "$LOCAL_SESSION" \
160
+ --expectation implementation-plan \
161
+ --status pass \
162
+ --summary "Implementation plan records files, sequence, and evidence." \
163
+ --evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/local-request/work-item.json","summary":"Plan fixture evidence."}' >/dev/null 2>&1 \
164
+ && flow_agents_node "$WRITER" record-gate-claim "$LOCAL_SESSION" \
165
+ --expectation implementation-scope \
166
+ --status pass \
167
+ --summary "Implementation scope and changed files are recorded." \
168
+ --evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/local-request/work-item.json","summary":"Execution fixture evidence."}' >/dev/null 2>&1 \
169
+ && flow_agents_node "$WRITER" record-gate-claim "$LOCAL_SESSION" \
170
+ --expectation tests-evidence \
171
+ --status fail \
172
+ --route-reason implementation_defect \
173
+ --summary "Verification found an implementation defect." \
174
+ --evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/local-request/work-item.json","summary":"Failed verification fixture evidence."}' >"$TMP/route-back.out" 2>&1 \
175
+ && node - "$TMP/local" "$LOCAL_SESSION" <<'NODE'
176
+ const fs = require('node:fs');
177
+ const path = require('node:path');
178
+ const project = process.argv[2];
179
+ const session = process.argv[3];
180
+ const flowState = JSON.parse(fs.readFileSync(path.join(project, '.kontourai', 'flow', 'runs', 'local-request', 'state.json'), 'utf8'));
181
+ const sidecar = JSON.parse(fs.readFileSync(path.join(session, 'state.json'), 'utf8'));
182
+ if (flowState.current_step !== 'execute') process.exit(1);
183
+ if (sidecar.flow_run?.route_back_attempt !== 1 || sidecar.flow_run?.route_back_max_attempts !== 3) process.exit(1);
184
+ if (JSON.stringify(sidecar.next_action?.skills) !== JSON.stringify(['execute-plan'])) process.exit(1);
185
+ if (!sidecar.next_action?.summary?.includes('Route-back history: attempt 1/3')) process.exit(1);
186
+ NODE
187
+ then
188
+ pass "sidecar failure classifier drives Flow route-back and projects its attempt budget"
189
+ else
190
+ fail "route-back classifier did not produce the canonical execute retry: $(cat "$TMP/route-back.out" 2>/dev/null)"
191
+ fi
192
+
193
+ PROVIDER_ROOT="$TMP/provider/.kontourai/flow-agents"
194
+ if flow_agents_node "$WRITER" ensure-session \
195
+ --artifact-root "$PROVIDER_ROOT" \
196
+ --work-item "kontourai/flow-agents#438" \
197
+ --title "Provider work item" \
198
+ --summary "Keep the provider reference." \
199
+ --flow-id builder.build \
200
+ --timestamp "2026-07-10T00:01:00Z" >/dev/null 2>&1 \
201
+ && node - "$PROVIDER_ROOT/kontourai-flow-agents-438/state.json" <<'NODE'
202
+ const fs = require('node:fs');
203
+ const state = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
204
+ if (JSON.stringify(state.work_item_refs) !== JSON.stringify(['kontourai/flow-agents#438'])) process.exit(1);
205
+ NODE
206
+ then
207
+ pass "provider-backed request preserves its neutral work-item ref"
208
+ else
209
+ fail "provider-backed work-item ref was not persisted"
210
+ fi
211
+
212
+ # A direct primitive remains usable without claiming Builder prefix completion.
213
+ STANDALONE_ROOT="$TMP/standalone/.kontourai/flow-agents"
214
+ if flow_agents_node "$WRITER" ensure-session \
215
+ --artifact-root "$STANDALONE_ROOT" \
216
+ --task-slug standalone-plan \
217
+ --title "Standalone plan" \
218
+ --summary "Direct primitive session." \
219
+ --timestamp "2026-07-10T00:02:00Z" >/dev/null 2>&1 \
220
+ && node - "$STANDALONE_ROOT" <<'NODE'
221
+ const fs = require('node:fs');
222
+ const path = require('node:path');
223
+ const root = process.argv[2];
224
+ const current = JSON.parse(fs.readFileSync(path.join(root, 'current.json'), 'utf8'));
225
+ const state = JSON.parse(fs.readFileSync(path.join(root, 'standalone-plan', 'state.json'), 'utf8'));
226
+ if ('active_flow_id' in current || 'active_step_id' in current) process.exit(1);
227
+ if (state.status !== 'planned' || state.phase !== 'planning') process.exit(1);
228
+ if (JSON.stringify(state.work_item_refs) !== JSON.stringify(['local:standalone-plan'])) process.exit(1);
229
+ NODE
230
+ then
231
+ pass "standalone primitive session remains available without a Builder stamp"
232
+ else
233
+ fail "standalone primitive session was incorrectly stamped as Builder"
234
+ fi
235
+
236
+ if [[ "$errors" -gt 0 ]]; then
237
+ printf 'test_builder_entry_enforcement: %d failure(s)\n' "$errors" >&2
238
+ exit 1
239
+ fi
240
+
241
+ echo "test_builder_entry_enforcement: all checks passed"
@@ -41,21 +41,29 @@ trap cleanup EXIT
41
41
 
42
42
  # ─── Helper: set active_step_id for a step.
43
43
  # For steps in the phase_map, use advance-state.
44
- # For design-probe (no phase mapping), use ensure-session --step-id.
44
+ # For design-probe (no phase mapping), mutate only the test fixture's current pointer.
45
45
  # ──────────────────────────────────────────────────────────────────
46
46
  set_active_step() {
47
47
  local aroot="$1" slug="$2" step="$3"
48
48
  case "$step" in
49
49
  design-probe)
50
- # design-probe has no lifecycle phase in the phase_map set via ensure-session --step-id
51
- flow_agents_node "workflow-sidecar" ensure-session \
52
- --artifact-root "$aroot" \
53
- --task-slug "$slug" \
54
- --title "Producer test: $step" \
55
- --summary "Test gate-claim producer at $step." \
56
- --flow-id builder.build \
57
- --step-id design-probe \
58
- --timestamp "2026-06-26T00:00:30Z" >/dev/null 2>&1
50
+ # The production entry surface cannot select a later step. This producer-focused
51
+ # fixture sets the pointer directly because design-probe has no legacy phase_map key.
52
+ node - "$aroot" <<'NODE'
53
+ const fs = require('node:fs');
54
+ const path = require('node:path');
55
+ const root = process.argv[2];
56
+ const files = [path.join(root, 'current.json')];
57
+ const actorRoot = path.join(root, 'current');
58
+ if (fs.existsSync(actorRoot)) {
59
+ files.push(...fs.readdirSync(actorRoot).filter((name) => name.endsWith('.json')).map((name) => path.join(actorRoot, name)));
60
+ }
61
+ for (const file of files) {
62
+ const current = JSON.parse(fs.readFileSync(file, 'utf8'));
63
+ current.active_step_id = 'design-probe';
64
+ fs.writeFileSync(file, `${JSON.stringify(current, null, 2)}\n`);
65
+ }
66
+ NODE
59
67
  ;;
60
68
  pull-work)
61
69
  flow_agents_node "workflow-sidecar" advance-state "$aroot/$slug" \
@@ -86,6 +86,20 @@ else
86
86
  _fail "Codex install with Kontour hosted Console telemetry config failed"
87
87
  fi
88
88
 
89
+ CODEX_HOSTED_CONSOLE_NO_TENANT_DEST="$TMPDIR_EVAL/codex-hosted-console-no-tenant-workspace"
90
+ CODEX_HOSTED_CONSOLE_NO_TENANT_STDERR="$TMPDIR_EVAL/codex-hosted-console-no-tenant-stderr.txt"
91
+ if (cd "$ROOT_DIR/dist/codex" && bash install.sh "$CODEX_HOSTED_CONSOLE_NO_TENANT_DEST" --telemetry-sink kontour-hosted-console --console-token-file "$CONSOLE_TOKEN_FILE" >/dev/null 2>"$CODEX_HOSTED_CONSOLE_NO_TENANT_STDERR"); then
92
+ _pass "Codex install with hosted Console sink and no --console-tenant still succeeds"
93
+ else
94
+ _fail "Codex install with hosted Console sink and no --console-tenant unexpectedly failed"
95
+ fi
96
+
97
+ if rg -q 'warning: install-console-config.sh: a Console telemetry sink was selected with no --console-tenant' "$CODEX_HOSTED_CONSOLE_NO_TENANT_STDERR"; then
98
+ _pass "Untenanted hosted Console sink install prints DX warning to stderr"
99
+ else
100
+ _fail "Untenanted hosted Console sink install did not print DX warning to stderr"
101
+ fi
102
+
89
103
  if (cd "$ROOT_DIR/dist/codex" && bash install.sh "$CODEX_USER_HOSTED_CONSOLE_DEST" --telemetry-sink user-hosted-console --console-url https://console.example.test --console-token-file "$CONSOLE_TOKEN_FILE" >/dev/null); then
90
104
  _pass "Codex install with user-hosted Console telemetry config succeeded"
91
105
  else
@@ -116,6 +130,137 @@ else
116
130
  _fail "flow-agents init headless Codex install failed"
117
131
  fi
118
132
 
133
+ echo ""
134
+ echo "--- Guided Console-Connect Wizard (G2/G3): headless regression + summary/verify ---"
135
+
136
+ # New (G3): a plain local-files headless install still prints the structured
137
+ # post-install summary block (regardless of the guided wizard, which only
138
+ # fires for interactive installs) -- the summary/verify tail is shared by
139
+ # both install paths.
140
+ BASE_SUMMARY_LOCAL_DEST="$TMPDIR_EVAL/base-summary-local-workspace"
141
+ BASE_SUMMARY_LOCAL_STDOUT="$TMPDIR_EVAL/base-summary-local-stdout.txt"
142
+ if node "$ROOT_DIR/build/src/cli.js" init --dest "$BASE_SUMMARY_LOCAL_DEST" --telemetry-sink local-files --yes >"$BASE_SUMMARY_LOCAL_STDOUT" 2>&1; then
143
+ _pass "flow-agents init headless local-files install succeeded (G3 summary case)"
144
+ else
145
+ _fail "flow-agents init headless local-files install failed (G3 summary case)"
146
+ fi
147
+
148
+ if rg -q 'Console:' "$BASE_SUMMARY_LOCAL_STDOUT" && rg -q 'local-only' "$BASE_SUMMARY_LOCAL_STDOUT" && rg -F -q "$BASE_SUMMARY_LOCAL_DEST" "$BASE_SUMMARY_LOCAL_STDOUT"; then
149
+ _pass "flow-agents init prints G3 post-install summary block for a local-files headless install"
150
+ else
151
+ _fail "flow-agents init did not print G3 post-install summary block for a local-files headless install"
152
+ fi
153
+
154
+ # New (G2/G3): a kontour-hosted-console headless install auto-verifies via
155
+ # telemetry-doctor's buildReport in-process, stays within a bounded wall-clock
156
+ # time (never hangs), never prints the raw console token, and prints a
157
+ # console status line -- verified or unverified is acceptable in a sandboxed
158
+ # CI network, as long as the check happened honestly.
159
+ BASE_SUMMARY_HOSTED_DEST="$TMPDIR_EVAL/base-summary-hosted-workspace"
160
+ BASE_SUMMARY_HOSTED_STDOUT="$TMPDIR_EVAL/base-summary-hosted-stdout.txt"
161
+ SECONDS=0
162
+ if node "$ROOT_DIR/build/src/cli.js" init --dest "$BASE_SUMMARY_HOSTED_DEST" --telemetry-sink kontour-hosted-console --console-tenant tenant-x --console-token-file "$CONSOLE_TOKEN_FILE" --yes >"$BASE_SUMMARY_HOSTED_STDOUT" 2>&1; then
163
+ _pass "flow-agents init headless kontour-hosted-console install succeeded (G2/G3 case)"
164
+ else
165
+ _fail "flow-agents init headless kontour-hosted-console install failed (G2/G3 case)"
166
+ fi
167
+ HOSTED_SUMMARY_ELAPSED_SECONDS=$SECONDS
168
+
169
+ if [[ "$HOSTED_SUMMARY_ELAPSED_SECONDS" -le 20 ]]; then
170
+ _pass "flow-agents init auto-verify (G2) completed within a bounded wall-clock time (${HOSTED_SUMMARY_ELAPSED_SECONDS}s, never hangs)"
171
+ else
172
+ _fail "flow-agents init auto-verify (G2) took too long (${HOSTED_SUMMARY_ELAPSED_SECONDS}s) -- may be hanging"
173
+ fi
174
+
175
+ if rg -q 'Console:' "$BASE_SUMMARY_HOSTED_STDOUT" && ! rg -F -q 'test-token' "$BASE_SUMMARY_HOSTED_STDOUT"; then
176
+ _pass "flow-agents init prints a G2/G3 console status line and never prints the raw console token"
177
+ else
178
+ _fail "flow-agents init did not print a console status line, or leaked the raw console token"
179
+ fi
180
+
181
+ # New (regression, RESOLVED OWNER DECISION): the self-hosted blank-URL guard
182
+ # (fallback to local-files with a warning) applies to the INTERACTIVE wizard
183
+ # ONLY. The headless (--yes/--headless) path keeps today's unchanged
184
+ # behavior -- install-console-config.sh's existing die-on-blank for
185
+ # user-hosted-console with no --console-url/--console-endpoint. This asserts
186
+ # the exact same non-zero exit + die message current main() (pre-PR2)
187
+ # already produces, proving headless is byte-for-byte untouched. (The
188
+ # interactive fallback itself is proven by the console-connect-options.test.mjs
189
+ # unit tests on resolveSelfHostedUrlOrFallback + runConsoleConnectWizard.)
190
+ BASE_SELF_HOSTED_NO_URL_DEST="$TMPDIR_EVAL/base-self-hosted-no-url-workspace"
191
+ BASE_SELF_HOSTED_NO_URL_STDERR="$TMPDIR_EVAL/base-self-hosted-no-url-stderr.txt"
192
+ set +e
193
+ node "$ROOT_DIR/build/src/cli.js" init --dest "$BASE_SELF_HOSTED_NO_URL_DEST" --telemetry-sink user-hosted-console --yes >/dev/null 2>"$BASE_SELF_HOSTED_NO_URL_STDERR"
194
+ BASE_SELF_HOSTED_NO_URL_EXIT=$?
195
+ set -e
196
+
197
+ if [[ "$BASE_SELF_HOSTED_NO_URL_EXIT" -ne 0 ]] && rg -q 'install-console-config.sh: user-hosted-console requires --console-url or --console-endpoint' "$BASE_SELF_HOSTED_NO_URL_STDERR"; then
198
+ _pass "flow-agents init headless user-hosted-console with no --console-url still dies exactly as before (self-hosted guard is interactive-only)"
199
+ else
200
+ _fail "flow-agents init headless user-hosted-console with no --console-url behavior changed (expected unchanged die-on-blank)"
201
+ fi
202
+
203
+ # New (review fix FIX 1): a self-hosted/BYO Console (non-local https, not the
204
+ # known hosted host) headless install stays honest -- connected-unverified,
205
+ # exit 0, reachability.checked stays false because --allow-network was not
206
+ # passed -- but the summary now includes an actionable next-step hint instead
207
+ # of leaving the operator with a bare "not checked". Never for local-only.
208
+ BASE_SUMMARY_SELFHOSTED_DEST="$TMPDIR_EVAL/base-summary-selfhosted-workspace"
209
+ BASE_SUMMARY_SELFHOSTED_STDOUT="$TMPDIR_EVAL/base-summary-selfhosted-stdout.txt"
210
+ if node "$ROOT_DIR/build/src/cli.js" init --dest "$BASE_SUMMARY_SELFHOSTED_DEST" --telemetry-sink user-hosted-console --console-url https://console.example.test --console-tenant tenant-selfhosted --console-token-file "$CONSOLE_TOKEN_FILE" --yes >"$BASE_SUMMARY_SELFHOSTED_STDOUT" 2>&1; then
211
+ _pass "flow-agents init headless self-hosted/BYO Console install succeeds (unverified is honest, not a failure)"
212
+ else
213
+ _fail "flow-agents init headless self-hosted/BYO Console install unexpectedly failed"
214
+ fi
215
+
216
+ if rg -q 'connected, unverified' "$BASE_SUMMARY_SELFHOSTED_STDOUT" \
217
+ && rg -F -q 'flow-agents telemetry-doctor --allow-network' "$BASE_SUMMARY_SELFHOSTED_STDOUT" \
218
+ && ! rg -F -q 'test-token' "$BASE_SUMMARY_SELFHOSTED_STDOUT"; then
219
+ _pass "flow-agents init summary discloses WHY self-hosted reachability is unverified with an actionable --allow-network hint, and never prints the raw token"
220
+ else
221
+ _fail "flow-agents init summary did not disclose the self-hosted not-checked reason with the --allow-network hint"
222
+ fi
223
+
224
+ if rg -F -q 'flow-agents telemetry-doctor --allow-network' "$BASE_SUMMARY_LOCAL_STDOUT"; then
225
+ _fail "flow-agents init printed the self-hosted --allow-network hint for a local-files-only install (should never appear there)"
226
+ else
227
+ _pass "flow-agents init does not print the self-hosted --allow-network hint for a local-files-only install"
228
+ fi
229
+
230
+ # New (review fix FIX 4, security): pin the validate-before-persist ordering
231
+ # invariant. The TS-side wizard validators (console-telemetry-validate.ts) are
232
+ # soft (validate-and-warn, never block); the ONLY reason a malformed value can
233
+ # never land in a persisted telemetry.conf is that install-console-config.sh's
234
+ # bash validators (validate_tenant et al.) run BEFORE any config key is
235
+ # written and `die` (non-zero exit) on failure. A tenant value containing a
236
+ # semicolon fails validate_tenant's charset (^[A-Za-z0-9._:-]+$ --
237
+ # scripts/telemetry/install-console-config.sh); assert the headless install
238
+ # dies non-zero via that exact bash validator, and that the hostile value
239
+ # never appears in the persisted telemetry.conf (which already exists at this
240
+ # point from the rsync'd template, since install-console-config.sh only
241
+ # validates -- and writes nothing -- before dying).
242
+ BASE_HOSTILE_TENANT_DEST="$TMPDIR_EVAL/base-hostile-tenant-workspace"
243
+ BASE_HOSTILE_TENANT_STDERR="$TMPDIR_EVAL/base-hostile-tenant-stderr.txt"
244
+ HOSTILE_TENANT='tenant;rm-rf-evidence'
245
+ set +e
246
+ node "$ROOT_DIR/build/src/cli.js" init --dest "$BASE_HOSTILE_TENANT_DEST" --telemetry-sink user-hosted-console --console-url https://console.example.test --console-tenant "$HOSTILE_TENANT" --yes >/dev/null 2>"$BASE_HOSTILE_TENANT_STDERR"
247
+ BASE_HOSTILE_TENANT_EXIT=$?
248
+ set -e
249
+
250
+ if [[ "$BASE_HOSTILE_TENANT_EXIT" -ne 0 ]] \
251
+ && rg -q 'install-console-config.sh: --console-tenant contains unsupported characters' "$BASE_HOSTILE_TENANT_STDERR"; then
252
+ _pass "flow-agents init headless install with an illegal-character Console tenant dies via the bash validate_tenant gate (non-zero exit)"
253
+ else
254
+ _fail "flow-agents init headless install with an illegal-character Console tenant did not die via bash validate_tenant as expected"
255
+ fi
256
+
257
+ if [[ ! -f "$BASE_HOSTILE_TENANT_DEST/scripts/telemetry/telemetry.conf" ]] \
258
+ || ! rg -F -q "$HOSTILE_TENANT" "$BASE_HOSTILE_TENANT_DEST/scripts/telemetry/telemetry.conf"; then
259
+ _pass "hostile Console tenant value never lands in telemetry.conf (validate-before-persist ordering invariant holds)"
260
+ else
261
+ _fail "hostile Console tenant value was persisted into telemetry.conf despite failing bash validate_tenant"
262
+ fi
263
+
119
264
  if (cd "$ROOT_DIR/dist/opencode" && bash install.sh "$OPENCODE_DEST" >/dev/null); then
120
265
  _pass "opencode install succeeded"
121
266
  else
@@ -708,6 +853,33 @@ else
708
853
  _fail "Codex full install is missing base agents"
709
854
  fi
710
855
 
856
+ if node - "$CODEX_AGENTS_DIR" <<'NODE'
857
+ const fs = require("node:fs");
858
+ const path = require("node:path");
859
+ function stringAssignment(text, key, name) {
860
+ const matches = [...text.matchAll(new RegExp(`^${key}\\s*=\\s*"([^"]*)"\\s*$`, "gm"))];
861
+ if (matches.length !== 1) throw new Error(`${name}: expected exactly one ${key} assignment, found ${matches.length}`);
862
+ return matches[0][1];
863
+ }
864
+ const expected = {
865
+ "tool-planner.toml": ["gpt-5.6-sol", "high"],
866
+ "tool-worker.toml": ["gpt-5.6-terra", "high"],
867
+ "tool-code-reviewer.toml": ["gpt-5.6-sol", "high"],
868
+ "tool-security-reviewer.toml": ["gpt-5.6-sol", "high"],
869
+ "tool-verifier.toml": ["gpt-5.6-sol", "high"],
870
+ };
871
+ for (const [name, [model, effort]] of Object.entries(expected)) {
872
+ const text = fs.readFileSync(path.join(process.argv[2], name), "utf8");
873
+ if (stringAssignment(text, "model", name) !== model) throw new Error(`${name}: expected model ${model}`);
874
+ if (stringAssignment(text, "model_reasoning_effort", name) !== effort) throw new Error(`${name}: expected reasoning ${effort}`);
875
+ }
876
+ NODE
877
+ then
878
+ _pass "Codex full install preserves Builder specialist 5.6 role routing"
879
+ else
880
+ _fail "Codex full install lost Builder specialist role routing"
881
+ fi
882
+
711
883
  if [[ -d "$CODEX_FULL_DEST/.codex/skills/plan-work" && -d "$CODEX_FULL_DEST/.codex/skills/deliver" && -d "$CODEX_FULL_DEST/.codex/skills/agentic-engineering" ]]; then
712
884
  _pass "Codex full install ships kit-skills and standalone skills together"
713
885
  else
@@ -278,14 +278,22 @@ flow_agents_node "$WRITER" init-plan "$SESSION_DIR2/${SLUG2}--deliver.md" \
278
278
  --source-request "Test" --summary "Test" \
279
279
  --timestamp "2026-06-26T11:01:00Z" >/dev/null 2>&1
280
280
 
281
+ # #356: the check-json below carries a real "command" so its test_output evidence
282
+ # has a manifest-matchable execution.label (evidence.execution.label) -- otherwise the new
283
+ # reconcile-preflight gate inside publishDelivery() (#356 Wave 3, AC6) correctly refuses to
284
+ # publish a "kind":"build" claim with test_output evidence but no captured command (a
285
+ # genuinely shape-invalid claim CI's own trust-reconcile.js would ALSO reject -- this is not
286
+ # a preflight-specific false positive, see reconcile-shape.js's finding-1 hardening).
287
+ # TRUST_RECONCILE_COMMANDS makes "node --version" resolve via resolveManifest's legacy
288
+ # fallback tier, matching what a real repo configured with that canonical verify would do.
281
289
  flow_agents_node "$WRITER" record-evidence "$SESSION_DIR2" \
282
290
  --verdict pass \
283
- --check-json '{"id":"build","kind":"build","status":"pass","summary":"build passed"}' \
291
+ --check-json '{"id":"build","kind":"build","status":"pass","summary":"build passed","command":"node --version"}' \
284
292
  --timestamp "2026-06-26T11:02:00Z" >/dev/null 2>&1
285
293
 
286
294
  # advance-state to delivered: this is the other code path that seals the checkpoint
287
295
  SEAL_EXIT=0
288
- flow_agents_node "$WRITER" advance-state "$SESSION_DIR2" \
296
+ TRUST_RECONCILE_COMMANDS="node --version" flow_agents_node "$WRITER" advance-state "$SESSION_DIR2" \
289
297
  --status delivered \
290
298
  --phase release \
291
299
  --summary "Delivered." \