@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,228 @@
1
+ #!/usr/bin/env bash
2
+ # test_telemetry_usage_pipeline.sh — Layer 2: hermetic Stop-hook usage pipeline
3
+ #
4
+ # Proves the full telemetry.sh Stop path (add_stop_data_and_emit_usage) yields
5
+ # a session.usage event with real tokens, a concrete (non-"unknown") model,
6
+ # and a non-null estimated_cost_usd when a runtime transcript is supplied —
7
+ # and that tokens still survive (with estimated_cost_usd null) when the
8
+ # pricing registry is forced unavailable. Also guards the kiro-cli
9
+ # non-regression case: with no transcript, model still resolves via the
10
+ # existing usage_get_model() kiro lookup (unaffected by this fix).
11
+ #
12
+ # Uses the same TELEMETRY_DIR resolution convention as test_telemetry.sh
13
+ # (prefers context/scripts/telemetry when present) so this exercises the same
14
+ # copy CI actually runs, while explicitly pointing TELEMETRY_PRICING_FILE at
15
+ # the canonical bundled registry so pricing resolves regardless of which copy
16
+ # is under test (context/scripts/telemetry ships no bundled pricing.json).
17
+ set -uo pipefail
18
+
19
+ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
20
+ if [[ -d "$ROOT_DIR/context/scripts/telemetry" ]]; then
21
+ TELEMETRY_DIR="$ROOT_DIR/context/scripts/telemetry"
22
+ else
23
+ TELEMETRY_DIR="$HOME/.flow-agents/context/scripts/telemetry"
24
+ fi
25
+ TELEMETRY_SH="${TELEMETRY_DIR}/telemetry.sh"
26
+ PRICING_FILE="$ROOT_DIR/scripts/telemetry/pricing.json"
27
+ FIXTURE_TRANSCRIPT="$ROOT_DIR/evals/fixtures/telemetry/usage-transcript-sample.jsonl"
28
+
29
+ TMPDIR_EVAL=$(mktemp -d /tmp/eval-telemetry-usage-pipeline.XXXXXX)
30
+ TMPLOG="${TMPDIR_EVAL}/test-output.jsonl"
31
+ FAKE_HOME="${TMPDIR_EVAL}/home"
32
+ mkdir -p "$FAKE_HOME" "$TMPDIR_EVAL/sessions"
33
+
34
+ pass=0; fail=0
35
+ _pass() { echo " ✓ $1"; pass=$((pass + 1)); }
36
+ _fail() { echo " ✗ $1"; fail=$((fail + 1)); }
37
+
38
+ echo "=== Layer 2: Telemetry Usage Pipeline (hermetic fixture) ==="
39
+ echo ""
40
+
41
+ if [[ ! -f "$TELEMETRY_SH" ]]; then
42
+ _fail "telemetry.sh not found at $TELEMETRY_SH"
43
+ echo "Cannot continue without telemetry script"
44
+ rm -rf "$TMPDIR_EVAL"
45
+ exit 1
46
+ fi
47
+ if [[ ! -f "$FIXTURE_TRANSCRIPT" ]]; then
48
+ _fail "fixture transcript not found at $FIXTURE_TRANSCRIPT"
49
+ rm -rf "$TMPDIR_EVAL"
50
+ exit 1
51
+ fi
52
+
53
+ # Wait for a new line to land in TMPLOG (telemetry.sh's Stop path emits
54
+ # asynchronously even in foreground mode's background-adjacent callers).
55
+ _wait_for_line() {
56
+ local before_lines="$1" i=0 current_lines
57
+ while [[ $i -lt 50 ]]; do
58
+ current_lines=$(wc -l < "$TMPLOG" 2>/dev/null | tr -d ' ')
59
+ [[ "${current_lines:-0}" -gt "$before_lines" ]] && break
60
+ sleep 0.1; i=$((i + 1))
61
+ done
62
+ }
63
+
64
+ _wait_for_file_line() {
65
+ local file="$1" i=0 current_lines
66
+ while [[ $i -lt 50 ]]; do
67
+ current_lines=$(wc -l < "$file" 2>/dev/null | tr -d ' ')
68
+ [[ "${current_lines:-0}" -gt 0 ]] && break
69
+ sleep 0.1; i=$((i + 1))
70
+ done
71
+ }
72
+
73
+ # Run a real Stop event against a freshly-established session (agentSpawn
74
+ # first, matching real usage — Claude Code always sends SessionStart before
75
+ # Stop). Returns the emitted session.usage event (jq-compact, one line).
76
+ _run_stop() {
77
+ local input="$1"; shift
78
+ local extra_env_count="$#"
79
+ local extra_env=("$@")
80
+ local common_env=(
81
+ HOME="$FAKE_HOME"
82
+ TELEMETRY_ENABLED=true
83
+ TELEMETRY_CHANNELS=full
84
+ TELEMETRY_CHANNEL_FULL_LOG_FILE="$TMPLOG"
85
+ FLOW_AGENTS_TELEMETRY_FOREGROUND=true
86
+ TELEMETRY_CONFIG_FILE="$TMPDIR_EVAL/telemetry.conf"
87
+ TELEMETRY_DATA_DIR="$TMPDIR_EVAL"
88
+ TELEMETRY_SESSION_DIR="$TMPDIR_EVAL/sessions"
89
+ )
90
+
91
+ local before_lines
92
+ touch "$TMPLOG"
93
+ before_lines=$(wc -l < "$TMPLOG" | tr -d ' ')
94
+ echo '{"cwd":"/tmp"}' | env "${common_env[@]}" bash "$TELEMETRY_SH" agentSpawn dev >/dev/null 2>&1
95
+ _wait_for_line "$before_lines"
96
+
97
+ before_lines=$(wc -l < "$TMPLOG" | tr -d ' ')
98
+ if [[ "$extra_env_count" -gt 0 ]]; then
99
+ echo "$input" | env "${common_env[@]}" TELEMETRY_USAGE_TRACKING=true "${extra_env[@]}" \
100
+ bash "$TELEMETRY_SH" Stop dev 2>/dev/null
101
+ else
102
+ echo "$input" | env "${common_env[@]}" TELEMETRY_USAGE_TRACKING=true \
103
+ bash "$TELEMETRY_SH" Stop dev 2>/dev/null
104
+ fi
105
+ _wait_for_line "$before_lines"
106
+
107
+ tail -n +"$((before_lines + 1))" "$TMPLOG" 2>/dev/null | jq -c 'select(.event_type=="session.usage")' | tail -1
108
+ }
109
+
110
+ # --- 1. With transcript + pricing available: real tokens, cost, concrete model
111
+ echo "--- Fixture transcript, pricing available ---"
112
+ input1=$(jq -nc --arg tp "$FIXTURE_TRANSCRIPT" '{session_id:"pipeline-1",transcript_path:$tp,hook_event_name:"Stop"}')
113
+ out1=$(_run_stop "$input1" TELEMETRY_PRICING_FILE="$PRICING_FILE")
114
+
115
+ if [[ -n "$out1" ]]; then
116
+ model=$(echo "$out1" | jq -r '.usage.model')
117
+ it=$(echo "$out1" | jq -r '.usage.input_tokens')
118
+ ot=$(echo "$out1" | jq -r '.usage.output_tokens')
119
+ cost=$(echo "$out1" | jq -r '.usage.estimated_cost_usd')
120
+ by_model_len=$(echo "$out1" | jq -r '.usage.by_model | length')
121
+
122
+ [[ "$model" != "unknown" && "$model" != "null" && -n "$model" ]] && _pass "model is concrete (got: $model)" || _fail "model should not be unknown (got: $model)"
123
+ [[ "$model" == "claude-opus-4-8" ]] && _pass "model is the dominant-by-tokens model (claude-opus-4-8)" || _fail "expected dominant model claude-opus-4-8, got $model"
124
+ [[ "$it" != "null" && "$it" -gt 0 ]] && _pass "input_tokens is real and non-null (got: $it)" || _fail "input_tokens should be non-null/positive (got: $it)"
125
+ [[ "$ot" != "null" && "$ot" -gt 0 ]] && _pass "output_tokens is real and non-null (got: $ot)" || _fail "output_tokens should be non-null/positive (got: $ot)"
126
+ cost_positive=$(echo "$out1" | jq -r '(.usage.estimated_cost_usd // 0) > 0')
127
+ [[ "$cost" != "null" && "$cost_positive" == "true" ]] && _pass "estimated_cost_usd is real and non-null (got: $cost)" || _fail "estimated_cost_usd should be non-null/positive (got: $cost)"
128
+ [[ "$by_model_len" == "2" ]] && _pass "by_model has 2 entries" || _fail "expected 2 by_model entries, got $by_model_len"
129
+ else
130
+ _fail "no session.usage event emitted for fixture transcript"
131
+ fi
132
+
133
+ # --- 1b. Economics attribution: canonical current pointer beats stale legacy
134
+ echo ""
135
+ echo "--- Economics attribution uses canonical current pointer ---"
136
+ ATTR_CWD="${TMPDIR_EVAL}/workspace"
137
+ CANON_SLUG="canonical-task"
138
+ LEGACY_SLUG="legacy-task"
139
+ ECON_LOG="${TMPDIR_EVAL}/economics.jsonl"
140
+ mkdir -p "$ATTR_CWD/.kontourai/flow-agents/$CANON_SLUG" "$ATTR_CWD/.flow-agents/$LEGACY_SLUG"
141
+ : > "$ECON_LOG"
142
+ printf '%s\n' "{\"active_slug\":\"$CANON_SLUG\"}" > "$ATTR_CWD/.kontourai/flow-agents/current.json"
143
+ printf '%s\n' "{\"active_slug\":\"$LEGACY_SLUG\"}" > "$ATTR_CWD/.flow-agents/current.json"
144
+ printf '%s\n' '{"schema_version":"1.0","task_slug":"canonical-task","phase":"execution","verification_verdict":"PASS"}' > "$ATTR_CWD/.kontourai/flow-agents/$CANON_SLUG/state.json"
145
+ printf '%s\n' '{"schema_version":"1.0","task_slug":"legacy-task","phase":"execution","verification_verdict":"PASS"}' > "$ATTR_CWD/.flow-agents/$LEGACY_SLUG/state.json"
146
+ input_attr=$(jq -nc --arg tp "$FIXTURE_TRANSCRIPT" --arg cwd "$ATTR_CWD" '{session_id:"pipeline-attribution",transcript_path:$tp,hook_event_name:"Stop",cwd:$cwd}')
147
+ TELEMETRY_SH_SAVED="$TELEMETRY_SH"
148
+ TELEMETRY_SH="$ROOT_DIR/scripts/telemetry/telemetry.sh"
149
+ out_attr=$(_run_stop "$input_attr" TELEMETRY_PRICING_FILE="$PRICING_FILE" TELEMETRY_ECONOMICS_LOG_FILE="$ECON_LOG")
150
+ TELEMETRY_SH="$TELEMETRY_SH_SAVED"
151
+ _wait_for_file_line "$ECON_LOG"
152
+
153
+ if [[ -n "$out_attr" && -s "$ECON_LOG" ]]; then
154
+ attr_task=$(tail -1 "$ECON_LOG" | jq -r '.task_slug')
155
+ attr_phase=$(tail -1 "$ECON_LOG" | jq -r '.phases[0].phase')
156
+ [[ "$attr_task" == "$CANON_SLUG" ]] && _pass "economics record task_slug comes from canonical .kontourai current pointer" || _fail "expected canonical task_slug '$CANON_SLUG', got '$attr_task'"
157
+ [[ "$attr_phase" == "execution" ]] && _pass "economics record phase comes from canonical state.json" || _fail "expected canonical phase execution, got '$attr_phase'"
158
+ else
159
+ _fail "economics attribution record was not emitted for canonical-current test"
160
+ fi
161
+
162
+ # --- 2. Pricing forced unavailable: tokens survive, cost is null ------------
163
+ echo ""
164
+ echo "--- Fixture transcript, pricing forced unavailable ---"
165
+ input2=$(jq -nc --arg tp "$FIXTURE_TRANSCRIPT" '{session_id:"pipeline-2",transcript_path:$tp,hook_event_name:"Stop"}')
166
+ out2=$(_run_stop "$input2" TELEMETRY_PRICING_FILE=/nonexistent/pricing.json TELEMETRY_PRICING_URL="" FLOW_AGENTS_PRICING_FILE="" FLOW_AGENTS_PRICING_URL="")
167
+
168
+ if [[ -n "$out2" ]]; then
169
+ it2=$(echo "$out2" | jq -r '.usage.input_tokens')
170
+ ot2=$(echo "$out2" | jq -r '.usage.output_tokens')
171
+ cost2=$(echo "$out2" | jq -r '.usage.estimated_cost_usd')
172
+ pv2=$(echo "$out2" | jq -r '.usage.pricing_version')
173
+ model2=$(echo "$out2" | jq -r '.usage.model')
174
+
175
+ [[ "$it2" != "null" && "$it2" -gt 0 ]] && _pass "tokens survive when pricing unavailable (input_tokens=$it2)" || _fail "tokens should survive when pricing unavailable (got input_tokens=$it2)"
176
+ [[ "$ot2" != "null" && "$ot2" -gt 0 ]] && _pass "output_tokens survive when pricing unavailable (got: $ot2)" || _fail "output_tokens should survive when pricing unavailable (got: $ot2)"
177
+ [[ "$cost2" == "null" ]] && _pass "estimated_cost_usd is null when pricing unavailable (cost degrades, tokens don't)" || _fail "estimated_cost_usd should be null when pricing unavailable (got: $cost2)"
178
+ [[ "$pv2" == "null" ]] && _pass "pricing_version is null when pricing unavailable" || _fail "pricing_version should be null when pricing unavailable (got: $pv2)"
179
+ [[ "$model2" == "claude-opus-4-8" ]] && _pass "model still resolves from transcript when pricing unavailable" || _fail "expected model claude-opus-4-8, got $model2"
180
+ else
181
+ _fail "no session.usage event emitted when pricing forced unavailable"
182
+ fi
183
+
184
+ # --- 3. No transcript (kiro-cli style): model still resolves via kiro fallback, no regression
185
+ echo ""
186
+ echo "--- No transcript (kiro-cli non-regression) ---"
187
+ input3='{"session_id":"pipeline-3","hook_event_name":"Stop"}'
188
+ out3=$(_run_stop "$input3")
189
+
190
+ if [[ -n "$out3" ]]; then
191
+ model3=$(echo "$out3" | jq -r '.usage.model')
192
+ it3=$(echo "$out3" | jq -r '.usage.input_tokens')
193
+ by_model3=$(echo "$out3" | jq -r '.usage.by_model')
194
+
195
+ # FAKE_HOME has no ~/.kiro/agents spec, so usage_get_model's kiro lookup
196
+ # falls through to "unknown" — this is the pre-existing, unfixed kiro path
197
+ # and must be untouched by the transcript-model override (transcript_usage
198
+ # is null here, so model is never overridden).
199
+ [[ "$model3" == "unknown" ]] && _pass "no-transcript path still resolves via usage_get_model kiro fallback (unknown, no regression)" || _fail "expected kiro fallback 'unknown' with no transcript, got $model3"
200
+ [[ "$it3" == "null" ]] && _pass "input_tokens is null with no transcript (expected)" || _fail "expected null input_tokens with no transcript, got $it3"
201
+ [[ "$by_model3" == "null" ]] && _pass "by_model is null with no transcript (expected)" || _fail "expected null by_model with no transcript, got $by_model3"
202
+ else
203
+ _fail "no session.usage event emitted for no-transcript case"
204
+ fi
205
+
206
+ # --- 4. Empty transcript + TELEMETRY_USAGE_DEBUG=1: debug reason is emitted -
207
+ echo ""
208
+ echo "--- Empty transcript, TELEMETRY_USAGE_DEBUG=1 (debug path) ---"
209
+ EMPTY_TRANSCRIPT="${TMPDIR_EVAL}/empty-transcript.jsonl"
210
+ : > "$EMPTY_TRANSCRIPT"
211
+ DEBUG_DRIFT_LOG="${TMPDIR_EVAL}/debug-drift.log"
212
+ input4=$(jq -nc --arg tp "$EMPTY_TRANSCRIPT" '{session_id:"pipeline-4",transcript_path:$tp,hook_event_name:"Stop"}')
213
+ out4=$(_run_stop "$input4" TELEMETRY_USAGE_DEBUG=1 TELEMETRY_DRIFT_LOG="$DEBUG_DRIFT_LOG")
214
+
215
+ if grep -q '\[telemetry\] usage_parse_transcript:' "$DEBUG_DRIFT_LOG" 2>/dev/null; then
216
+ _pass "debug reason line emitted for empty-transcript no-usage scenario"
217
+ else
218
+ _fail "expected a usage_parse_transcript debug reason line in $DEBUG_DRIFT_LOG"
219
+ fi
220
+
221
+ model4=$(echo "$out4" | jq -r '.usage.model // "unknown"' 2>/dev/null)
222
+ [[ -z "$out4" || "$model4" == "unknown" ]] && _pass "empty-transcript path emits no real usage (no regression)" || _fail "expected no/unknown usage for empty transcript, got model=$model4"
223
+
224
+ rm -rf "$TMPDIR_EVAL"
225
+
226
+ echo ""
227
+ echo "Telemetry usage pipeline: $pass passed, $fail failed"
228
+ [ "$fail" -eq 0 ]
@@ -9,21 +9,23 @@
9
9
  #
10
10
  # 1. no-label-bypass (finding 1) → not-run: a test_output claim with no
11
11
  # manifest-matched execution.label is a divergence, never session-local.
12
- # 2. skip-assumed-bypass (finding 2) → unwaived-assumed: 'assumed' alone is not a pass.
13
- # 3. status-misassertion (finding 3) status-misassertion: CI re-derives status; a
12
+ # 2. mcp-degrade (issue 492) → not-run: an MCP-shaped execution.label is not a
13
+ # manifest command and can never be RECONCILED as a CI-verified command check.
14
+ # 3. skip-assumed-bypass (finding 2) → unwaived-assumed: 'assumed' alone is not a pass.
15
+ # 4. status-misassertion (finding 3) → status-misassertion: CI re-derives status; a
14
16
  # self-reported status that does not match the bundle's own evidence is rejected.
15
- # 4. waived-command-check (finding 4) → waiver-on-command-check: a command-backed
17
+ # 5. waived-command-check (finding 4) → waiver-on-command-check: a command-backed
16
18
  # (test_output) check cannot be waived.
17
- # 5. ws3-old-style-bundle (AC6) → old all-test_output bundle FAILS the same way (exit 1,
19
+ # 6. ws3-old-style-bundle (AC6) → old all-test_output bundle FAILS the same way (exit 1,
18
20
  # divergences) under the new reconciler as under the old one — no soundness regression.
19
- # 6. fabricated-attestation (iteration-4, converged iteration-3 finding, both gates) → a
21
+ # 7. fabricated-attestation (iteration-4, converged iteration-3 finding, both gates) → a
20
22
  # fully self-consistent, hand-fabricated no-command 'security' claim+evidence+event
21
23
  # triple (indistinguishable from a genuine attestation at the reconciler's own
22
24
  # re-derivation layer) MUST still pass (exit 0 — blocking attestations at L0 would break
23
25
  # every honest human-attestation use) BUT MUST be loudly, distinctly marked
24
26
  # 'ATTESTED (not independently verifiable at L0)' plus the summary count line — never a
25
27
  # quiet SESSION-LOCAL OK indistinguishable from a reconciled check. See ADR 0020 Residuals.
26
- # 7. delivery/DECLARED marker regressions (ADR 0022 §1/§2, section 7 below) — bundle-absent
28
+ # 8. delivery/DECLARED marker regressions (ADR 0022 §1/§2, section 7 below) — bundle-absent
27
29
  # fail-closed default, malformed/empty-array/missing-field diagnostics, and all four
28
30
  # matchesScope() forms (ref:, commit: single + range, author:, branch-prefix:) including
29
31
  # positive AND near-miss coverage, plus the compound (space-separated AND) scope form a
@@ -40,6 +42,7 @@
40
42
  set -uo pipefail
41
43
 
42
44
  ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
45
+ source "$ROOT/evals/lib/node.sh"
43
46
  RECONCILE="$ROOT/scripts/ci/trust-reconcile.js"
44
47
  FX="$ROOT/evals/fixtures/trust-reconcile-exploits"
45
48
  WS3="$ROOT/evals/fixtures/trust-reconcile-ws3/ws3-bundle.json"
@@ -48,10 +51,12 @@ errors=0
48
51
  _pass() { echo " PASS: $1"; }
49
52
  _fail() { echo " FAIL: $1"; errors=$((errors + 1)); }
50
53
 
51
- # run_case <label> <bundle> <needle>
52
- # Asserts: reconciler exits non-zero AND stdout/stderr contains <needle>.
54
+ # run_case <label> <bundle> <needle> [forbidden]
55
+ # Asserts: reconciler exits non-zero, stdout/stderr contains <needle>, and optionally does
56
+ # NOT contain <forbidden>.
53
57
  run_case() {
54
58
  local label="$1" bundle="$2" needle="$3"
59
+ local forbidden="${4:-}"
55
60
  echo "=== $label ==="
56
61
  if [[ ! -f "$bundle" ]]; then _fail "$label: fixture not found at $bundle"; return; fi
57
62
  local out code
@@ -68,25 +73,38 @@ run_case() {
68
73
  else
69
74
  _fail "$label: expected divergence \"$needle\" not found — output: $out"
70
75
  fi
76
+ if [[ -n "$forbidden" ]]; then
77
+ if echo "$out" | grep -qF "$forbidden"; then
78
+ _fail "$label: must NOT emit \"$forbidden\" — output: $out"
79
+ else
80
+ _pass "$label: does not emit \"$forbidden\""
81
+ fi
82
+ fi
71
83
  }
72
84
 
73
85
  # 1. Reviewer's no-label exploit → not-run (test_output must reconcile against the manifest).
74
86
  run_case "no-label-bypass (finding 1)" "$FX/no-label-bypass.json" \
75
87
  "no manifest-matched execution.label"
76
88
 
77
- # 2. Verifier's skip->assumed exploitunwaived-assumed.
89
+ # 2. Issue #492: MCP-shaped self-reported pass not-run (not manifest-matched), never
90
+ # RECONCILED as a CI-verified command check.
91
+ run_case "mcp-degrade (issue 492)" "$FX/mcp-degrade.json" \
92
+ "command is not in the reconcile manifest" \
93
+ "RECONCILED"
94
+
95
+ # 3. Verifier's skip->assumed exploit → unwaived-assumed.
78
96
  run_case "skip-assumed-bypass (finding 2)" "$FX/skip-assumed-bypass.json" \
79
97
  "[unwaived-assumed]"
80
98
 
81
- # 3. Status-misassertion exploit → status re-derived CI-side; asserted != derived.
99
+ # 4. Status-misassertion exploit → status re-derived CI-side; asserted != derived.
82
100
  run_case "status-misassertion (finding 3)" "$FX/status-misassertion.json" \
83
101
  "[status-misassertion]"
84
102
 
85
- # 4. Waived command-backed check → waiver-on-command-check.
103
+ # 5. Waived command-backed check → waiver-on-command-check.
86
104
  run_case "waived-command-check (finding 4)" "$FX/waived-command-check.json" \
87
105
  "[waiver-on-command-check]"
88
106
 
89
- # 5. AC6 backward-compat regression: the real ws3-kit-dependencies-namespacing/trust.bundle
107
+ # 6. AC6 backward-compat regression: the real ws3-kit-dependencies-namespacing/trust.bundle
90
108
  # (an old-style all-test_output bundle whose commands are not manifest-matched) FAILS the
91
109
  # same way under the new reconciler as under the old one — same FAIL verdict, divergences
92
110
  # present, no silent pass introduced.
@@ -114,7 +132,7 @@ else
114
132
  fi
115
133
  fi
116
134
 
117
- # 6. Fabricated-attestation (iteration-4): passes (exit 0) but MUST carry the loud ATTESTED
135
+ # 7. Fabricated-attestation (iteration-4): passes (exit 0) but MUST carry the loud ATTESTED
118
136
  # marker and the summary count line — NOT the old quiet SESSION-LOCAL OK. This is a
119
137
  # visibility assertion, not a divergence assertion (unlike run_case above): a fabricated
120
138
  # self-consistent attestation bundle is, by construction, indistinguishable from a genuine
@@ -919,6 +937,96 @@ else
919
937
  _fail "prefer-newest: expected 'owning, newest wins' in the selection line -- output: $out8d"
920
938
  fi
921
939
 
940
+ # 9. [iteration-1 F1 CRITICAL regression guard] CI trust-reconcile.js MUST stay fail-closed
941
+ # when scripts/ci/derive-claim-status.mjs is unavailable (Surface unresolvable, spawn
942
+ # failure, malformed bundle, etc.) -- deriveClaimStatuses() returns null in that case, and
943
+ # BEFORE #356's extraction the inline session-local loop turned every session-local
944
+ # pass-asserting claim into a `status-underivable` divergence (hard FAIL). The extraction's
945
+ # shared sessionLocalShapeIssues() briefly made that a caller-controlled `onUnderivable` mode
946
+ # (see reconcile-shape.js) -- this section proves CI still defaults/forces the safe 'fail'
947
+ # mode, and (paired) that the LOCAL reconcile-preflight intentionally diverges into the
948
+ # 'reduce' mode on the exact same bundle. Without the fix, case 9a below FAILS (exits 0
949
+ # instead of 1) -- this is the single regression eval that let the CRITICAL through
950
+ # code-review-356 iteration 0.
951
+ echo ""
952
+ echo "=== 9. derive-claim-status.mjs unavailable: CI fail-closed vs local-preflight reduced-coverage (iteration-1 F1) ==="
953
+
954
+ DERIVE_HELPER="$ROOT/scripts/ci/derive-claim-status.mjs"
955
+ DERIVE_HELPER_HIDDEN="$DERIVE_HELPER.hidden-by-test-9"
956
+
957
+ # hide_derive_helper / restore_derive_helper: rename the real ESM helper out of the way so
958
+ # scripts/ci/trust-reconcile.js's deriveClaimStatuses() -- which does fs.existsSync(helper)
959
+ # before ever spawning it -- returns null exactly as it would if Surface could not resolve.
960
+ # Always restored via trap, including on a hard failure of this script.
961
+ hide_derive_helper() {
962
+ if [[ -f "$DERIVE_HELPER" ]]; then
963
+ mv "$DERIVE_HELPER" "$DERIVE_HELPER_HIDDEN"
964
+ fi
965
+ }
966
+ restore_derive_helper() {
967
+ if [[ -f "$DERIVE_HELPER_HIDDEN" ]]; then
968
+ mv "$DERIVE_HELPER_HIDDEN" "$DERIVE_HELPER"
969
+ fi
970
+ }
971
+ trap 'cleanup_declared; rm -rf "$PERSESSION_TMPROOT"; restore_derive_helper' EXIT
972
+
973
+ # A one-claim, session-local, pass-asserting bundle (reuses the finding-3 exploit fixture --
974
+ # a human_attestation-backed claim asserting status "verified"; any session-local
975
+ # pass-asserting claim exercises this path since the whole derivedStatus map is null, not a
976
+ # single claim's lookup).
977
+ CASE9_BUNDLE="$FX/status-misassertion.json"
978
+
979
+ hide_derive_helper
980
+
981
+ # 9a. CI (real scripts/ci/trust-reconcile.js): derive-claim-status.mjs unavailable -> MUST
982
+ # still exit non-zero (status-underivable, fail-closed) -- the pre-#356 guarantee.
983
+ out9a="$(TRUST_RECONCILE_COMMANDS="node -e 'process.exit(0)'" \
984
+ node "$RECONCILE" --bundle "$CASE9_BUNDLE" --repo-root "$ROOT" 2>&1)"
985
+ code9a=$?
986
+ if [[ $code9a -ne 0 ]]; then
987
+ _pass "derive-unavailable-ci: reconciler exits non-zero ($code9a) -- fail-closed preserved when re-derivation is unavailable"
988
+ else
989
+ _fail "derive-unavailable-ci: expected non-zero exit (fail-closed), got 0 -- output: $out9a"
990
+ fi
991
+ if echo "$out9a" | grep -qF "[status-underivable]"; then
992
+ _pass "derive-unavailable-ci: emitted the status-underivable divergence"
993
+ else
994
+ _fail "derive-unavailable-ci: expected a [status-underivable] divergence -- output: $out9a"
995
+ fi
996
+ if echo "$out9a" | grep -qF "refusing to trust a self-reported status (fail-closed)"; then
997
+ _pass "derive-unavailable-ci: emitted the verbatim fail-closed message"
998
+ else
999
+ _fail "derive-unavailable-ci: expected the verbatim fail-closed message -- output: $out9a"
1000
+ fi
1001
+
1002
+ # 9b. Paired proof the modes diverge intentionally: the LOCAL reconcile-preflight, on the
1003
+ # SAME bundle with the SAME helper hidden, DEGRADES -- it does not hard-fail on the
1004
+ # underivable-status dimension (it opts into reduce mode explicitly; see
1005
+ # src/cli/workflow-sidecar.ts's runReconcilePreflight).
1006
+ # Nested under a subdirectory (not the raw mktemp -d path) -- mktemp -d's own
1007
+ # "tmp.XXXXXXXXXX" basename contains a dot that path.extname()/artifactDirFrom() would
1008
+ # misparse as a file extension, incorrectly stripping the directory segment.
1009
+ CASE9_TMPROOT="$(mktemp -d)"
1010
+ CASE9_SESSION="$CASE9_TMPROOT/session"
1011
+ mkdir -p "$CASE9_SESSION"
1012
+ cp "$CASE9_BUNDLE" "$CASE9_SESSION/trust.bundle"
1013
+ out9b="$(flow_agents_node workflow-sidecar reconcile-preflight "$CASE9_SESSION" --repo-root "$ROOT" 2>&1)"
1014
+ code9b=$?
1015
+ rm -rf "$CASE9_TMPROOT"
1016
+ if [[ $code9b -eq 0 ]]; then
1017
+ _pass "derive-unavailable-local-preflight: reconcile-preflight exits 0 (reduced-coverage degrade, not a hard fail) -- proves the CI/local modes diverge intentionally"
1018
+ else
1019
+ _fail "derive-unavailable-local-preflight: expected exit 0 (reduced-coverage degrade), got $code9b -- output: $out9b"
1020
+ fi
1021
+ if echo "$out9b" | grep -qF "status-underivable"; then
1022
+ _fail "derive-unavailable-local-preflight: must NOT emit status-underivable in reduced-coverage mode -- output: $out9b"
1023
+ else
1024
+ _pass "derive-unavailable-local-preflight: does not emit status-underivable (reduced-coverage checks only)"
1025
+ fi
1026
+
1027
+ restore_derive_helper
1028
+
1029
+
922
1030
  echo ""
923
1031
  if [[ $errors -eq 0 ]]; then
924
1032
  echo "test_trust_reconcile_negatives: all checks passed."