@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,247 @@
1
+ #!/usr/bin/env bash
2
+ # test_trust_reconcile_trailer_diagnostic.sh — Integration eval for scripts/ci/
3
+ # trust-reconcile.js's runtime-session commit-trailer diagnostic (issue #305, ADR 0022 §1).
4
+ #
5
+ # Proves the diagnostic added in findRuntimeSessionTrailers()/logRuntimeSessionTrailers()
6
+ # is audit-only:
7
+ # AC5 trailer-diagnostic-positive: a commit with a Claude-Session: trailer in the
8
+ # resolved range produces the exact line
9
+ # `[trust-reconcile] identified: runtime-session trailer 'Claude-Session' on <ref>`
10
+ # — the trailer KEY only, never the VALUE (session URL) — printed for the range's
11
+ # commit-with-trailer.
12
+ # AC6 trailer-diagnostic-negative: a commit range with no runtime-session trailer
13
+ # produces NO such diagnostic line, and the reconciler's exit code is IDENTICAL
14
+ # to the positive case (same canonical command, same bundle/DECLARED absence,
15
+ # differing only in trailer presence) — the diagnostic never changes the exit path.
16
+ # AC7 trailer-diagnostic-checkout-depth-verified: .github/workflows/trust-reconcile.yml
17
+ # already carries fetch-depth: 0 (no .yml edit needed for #305's depth ask).
18
+ # Base-ref-range vs narrower-fallback: both range-resolution paths
19
+ # (TRUST_RECONCILE_BASE_REF/GITHUB_BASE_REF resolvable vs unresolvable, falling back
20
+ # to just the head commit) are exercised directly against the exported
21
+ # findRuntimeSessionTrailers() helper (unit-style, no reconciler wrapper needed for
22
+ # this sub-case) as well as through the full reconciler CLI (integration-style).
23
+ #
24
+ # Eval-authoring hazards observed (per plan/goal constraints):
25
+ # - No self-recursive harness invocation: this eval shells out to trust-reconcile.js and
26
+ # to git only; it never invokes evals/run.sh or any other eval-suite entry point.
27
+ # - No legacy/deprecated-path literals: trailer fixture values are synthetic/generic
28
+ # (a https://claude.ai/code/session_test-fixture-only placeholder), never a copy of a
29
+ # real committed session URL or a legacy flat delivery/trust.bundle-style literal.
30
+ #
31
+ # Deterministic, no model spend, self-cleaning.
32
+ # Usage: bash evals/integration/test_trust_reconcile_trailer_diagnostic.sh
33
+
34
+ set -uo pipefail
35
+
36
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
37
+ RECONCILE="$ROOT/scripts/ci/trust-reconcile.js"
38
+
39
+ errors=0
40
+ TMP_DIR="$(mktemp -d)"
41
+ trap 'rm -rf "$TMP_DIR"' EXIT
42
+ pass() { echo " ✓ $1"; }
43
+ fail() { echo " ✗ $1"; errors=$((errors + 1)); }
44
+
45
+ echo "=== trust-reconcile.js runtime-session trailer diagnostic — integration eval ==="
46
+
47
+ # Synthetic-only trailer value — never a real/legacy committed session URL.
48
+ TRAILER_VALUE="https://claude.ai/code/session_test-fixture-only"
49
+ RECONCILE_CMD="node -e 'process.exit(0)'"
50
+
51
+ # ─── Fixture: a real scratch git repo with two commits (base, then head) ─────────
52
+ build_repo() {
53
+ local repo_dir="$1"
54
+ local with_trailer="$2" # "true" or "false"
55
+ mkdir -p "$repo_dir"
56
+ git init -q "$repo_dir"
57
+ git -C "$repo_dir" config user.email "eval@local"
58
+ git -C "$repo_dir" config user.name "eval"
59
+ echo "base" > "$repo_dir/f.txt"
60
+ git -C "$repo_dir" add -A
61
+ git -C "$repo_dir" commit -qm "base commit"
62
+ echo "change" >> "$repo_dir/f.txt"
63
+ git -C "$repo_dir" add -A
64
+ if [[ "$with_trailer" == "true" ]]; then
65
+ git -C "$repo_dir" commit -qm "$(printf 'feat: trailer test commit\n\nClaude-Session: %s\n' "$TRAILER_VALUE")"
66
+ else
67
+ git -C "$repo_dir" commit -qm "feat: no-trailer test commit"
68
+ fi
69
+ }
70
+
71
+ POS_REPO="$TMP_DIR/positive-repo"
72
+ NEG_REPO="$TMP_DIR/negative-repo"
73
+ build_repo "$POS_REPO" "true"
74
+ build_repo "$NEG_REPO" "false"
75
+
76
+ POS_BASE="$(git -C "$POS_REPO" rev-list --max-parents=0 HEAD)"
77
+ POS_HEAD="$(git -C "$POS_REPO" rev-parse HEAD)"
78
+ NEG_BASE="$(git -C "$NEG_REPO" rev-list --max-parents=0 HEAD)"
79
+ NEG_HEAD="$(git -C "$NEG_REPO" rev-parse HEAD)"
80
+
81
+ # A THIRD repo where the range-distinguishing case actually differs from POS_REPO/NEG_REPO:
82
+ # the trailer lives on a MIDDLE commit (between base and a later, trailer-free head commit)
83
+ # -- reachable ONLY via the ranged `git log <base>..<head>` walk, never via the narrower
84
+ # head-commit-only fallback. This is the genuine base-ref-range-vs-fallback distinguisher;
85
+ # POS_REPO/NEG_REPO above (trailer-or-not on the head commit itself) would pass under
86
+ # EITHER code path and do not by themselves prove the ranged branch is exercised.
87
+ RANGE_REPO="$TMP_DIR/range-only-repo"
88
+ mkdir -p "$RANGE_REPO"
89
+ git init -q "$RANGE_REPO"
90
+ git -C "$RANGE_REPO" config user.email "eval@local"
91
+ git -C "$RANGE_REPO" config user.name "eval"
92
+ echo "base" > "$RANGE_REPO/f.txt"
93
+ git -C "$RANGE_REPO" add -A
94
+ git -C "$RANGE_REPO" commit -qm "base commit"
95
+ RANGE_BASE="$(git -C "$RANGE_REPO" rev-parse HEAD)"
96
+ echo "middle" >> "$RANGE_REPO/f.txt"
97
+ git -C "$RANGE_REPO" add -A
98
+ git -C "$RANGE_REPO" commit -qm "$(printf 'feat: middle commit carries the trailer
99
+
100
+ Claude-Session: %s
101
+ ' "$TRAILER_VALUE")"
102
+ echo "head" >> "$RANGE_REPO/f.txt"
103
+ git -C "$RANGE_REPO" add -A
104
+ git -C "$RANGE_REPO" commit -qm "feat: head commit itself has no trailer"
105
+ RANGE_HEAD="$(git -C "$RANGE_REPO" rev-parse HEAD)"
106
+
107
+ # --- AC5: positive case — exact diagnostic line, key only, no value leak --------
108
+ echo "--- AC5: positive — Claude-Session trailer present ---"
109
+ pos_out="$(TRUST_RECONCILE_BASE_REF="$POS_BASE" TRUST_RECONCILE_SHA="$POS_HEAD" TRUST_RECONCILE_REF="test-branch" \
110
+ TRUST_RECONCILE_COMMANDS="$RECONCILE_CMD" node "$RECONCILE" --repo-root "$POS_REPO" 2>&1)"
111
+ pos_code=$?
112
+
113
+ if echo "$pos_out" | grep -qF "[trust-reconcile] identified: runtime-session trailer 'Claude-Session' on test-branch"; then
114
+ pass "AC5: exact diagnostic line printed for the Claude-Session trailer"
115
+ else
116
+ fail "AC5: expected diagnostic line not found — output: $pos_out"
117
+ fi
118
+
119
+ if echo "$pos_out" | grep -qF "$TRAILER_VALUE"; then
120
+ fail "AC5: trailer VALUE ($TRAILER_VALUE) leaked into stdout — diagnostic must log the KEY only"
121
+ else
122
+ pass "AC5: trailer VALUE does not appear anywhere in stdout (key-only logging, per ADR 0022 §1's exact quoted format)"
123
+ fi
124
+
125
+ # --- AC6: negative case — no trailer, no line, same canonical command/DECLARED state --
126
+ echo "--- AC6: negative — no runtime-session trailer present ---"
127
+ neg_out="$(TRUST_RECONCILE_BASE_REF="$NEG_BASE" TRUST_RECONCILE_SHA="$NEG_HEAD" TRUST_RECONCILE_REF="test-branch" \
128
+ TRUST_RECONCILE_COMMANDS="$RECONCILE_CMD" node "$RECONCILE" --repo-root "$NEG_REPO" 2>&1)"
129
+ neg_code=$?
130
+
131
+ if echo "$neg_out" | grep -q "identified: runtime-session trailer"; then
132
+ fail "AC6: an 'identified: runtime-session trailer' line was printed with no trailer present"
133
+ else
134
+ pass "AC6: no 'identified: runtime-session trailer' line printed (no trailer present)"
135
+ fi
136
+
137
+ if [[ "$pos_code" == "$neg_code" ]]; then
138
+ pass "AC6: reconciler exit code is IDENTICAL between the trailer-present and no-trailer cases (both: $pos_code) — diagnostic never changes the exit path"
139
+ else
140
+ fail "AC6: reconciler exit code DIVERGED between trailer-present ($pos_code) and no-trailer ($neg_code) cases — the diagnostic must never affect the exit path"
141
+ fi
142
+
143
+ # --- Base-ref-range vs narrower-fallback, exercised directly on the exported helper --
144
+ # RANGE_REPO's trailer lives on a MIDDLE commit, absent from the head commit itself — this
145
+ # genuinely distinguishes the two code paths (see RANGE_REPO fixture comment above).
146
+ echo "--- base-ref-range vs narrower-fallback (direct helper exercise, genuinely distinguishing fixture) ---"
147
+ range_result="$(TRUST_RECONCILE_BASE_REF="$RANGE_BASE" node -e "
148
+ const { findRuntimeSessionTrailers } = require('$RECONCILE');
149
+ const found = findRuntimeSessionTrailers('$RANGE_REPO', { sha: '$RANGE_HEAD', ref: 'test-branch' });
150
+ console.log(JSON.stringify(found));
151
+ " 2>&1)"
152
+ if echo "$range_result" | grep -q '"trailerName":"Claude-Session"'; then
153
+ pass "base-ref-range path: findRuntimeSessionTrailers finds a trailer on a MIDDLE commit (base..head range) that is absent from the head commit itself, when TRUST_RECONCILE_BASE_REF resolves"
154
+ else
155
+ fail "base-ref-range path: findRuntimeSessionTrailers did not find the middle-commit trailer via the ranged walk — output: $range_result"
156
+ fi
157
+
158
+ # Narrower fallback: no base ref env var set at all (helper's own internal base-ref
159
+ # resolution reads process.env directly, so unset both env vars for this call) — against
160
+ # the SAME range-only repo, this must NOT find the middle-commit trailer (it is invisible
161
+ # to a head-commit-only scan), proving the fallback is genuinely narrower, not just an
162
+ # equivalent restatement of the ranged path.
163
+ fallback_result="$(env -u TRUST_RECONCILE_BASE_REF -u GITHUB_BASE_REF node -e "
164
+ const { findRuntimeSessionTrailers } = require('$RECONCILE');
165
+ const found = findRuntimeSessionTrailers('$RANGE_REPO', { sha: '$RANGE_HEAD', ref: 'test-branch' });
166
+ console.log(JSON.stringify(found));
167
+ " 2>&1)"
168
+ if [[ "$fallback_result" == "[]" ]]; then
169
+ pass "narrower-fallback path: findRuntimeSessionTrailers does NOT find the middle-commit-only trailer when no base ref is resolvable (head-commit-only scan is genuinely narrower)"
170
+ else
171
+ fail "narrower-fallback path: expected an empty array (middle-commit trailer invisible to head-only scan), got: $fallback_result"
172
+ fi
173
+
174
+ # Narrower fallback, positive: against POS_REPO (trailer ON the head commit itself), the
175
+ # no-base-ref fallback still finds it — proving the fallback is not simply "always empty".
176
+ fallback_pos_result="$(env -u TRUST_RECONCILE_BASE_REF -u GITHUB_BASE_REF node -e "
177
+ const { findRuntimeSessionTrailers } = require('$RECONCILE');
178
+ const found = findRuntimeSessionTrailers('$POS_REPO', { sha: '$POS_HEAD', ref: 'test-branch' });
179
+ console.log(JSON.stringify(found));
180
+ " 2>&1)"
181
+ if echo "$fallback_pos_result" | grep -q '"trailerName":"Claude-Session"'; then
182
+ pass "narrower-fallback path: still finds a trailer that IS on the head commit itself (fallback scans the head commit, not nothing)"
183
+ else
184
+ fail "narrower-fallback path: did not find the head-commit trailer with no base ref set — output: $fallback_pos_result"
185
+ fi
186
+
187
+ # Narrower-fallback negative: no base ref, no trailer on the head commit itself (NEG_REPO).
188
+ fallback_neg_result="$(env -u TRUST_RECONCILE_BASE_REF -u GITHUB_BASE_REF node -e "
189
+ const { findRuntimeSessionTrailers } = require('$RECONCILE');
190
+ const found = findRuntimeSessionTrailers('$NEG_REPO', { sha: '$NEG_HEAD', ref: 'test-branch' });
191
+ console.log(JSON.stringify(found));
192
+ " 2>&1)"
193
+ if [[ "$fallback_neg_result" == "[]" ]]; then
194
+ pass "narrower-fallback path: findRuntimeSessionTrailers returns an empty array when the head commit itself has no trailer and no base ref is resolvable"
195
+ else
196
+ fail "narrower-fallback path: expected an empty array, got: $fallback_neg_result"
197
+ fi
198
+
199
+ # --- No-crash: unresolvable sha degrades to empty array, never throws -----------
200
+ degrade_result="$(node -e "
201
+ const { findRuntimeSessionTrailers } = require('$RECONCILE');
202
+ const found = findRuntimeSessionTrailers('$POS_REPO', { sha: '', ref: 'test-branch' });
203
+ console.log(JSON.stringify(found));
204
+ " 2>&1)"
205
+ if [[ "$degrade_result" == "[]" ]]; then
206
+ pass "empty sha degrades to an empty array (never throws, never crashes the caller)"
207
+ else
208
+ fail "empty sha did not degrade cleanly — got: $degrade_result"
209
+ fi
210
+
211
+ # --- AC7: checkout-depth already verified (no .yml edit needed) -----------------
212
+ echo "--- AC7: .github/workflows/trust-reconcile.yml already carries fetch-depth: 0 ---"
213
+ WORKFLOW_FILE="$ROOT/.github/workflows/trust-reconcile.yml"
214
+ if grep -n "fetch-depth: 0" "$WORKFLOW_FILE" >/dev/null 2>&1; then
215
+ pass "AC7: fetch-depth: 0 already present in trust-reconcile.yml (Addendum part 3) — no .yml change needed for #305's depth ask"
216
+ else
217
+ fail "AC7: fetch-depth: 0 not found in trust-reconcile.yml — expected it to already be present"
218
+ fi
219
+
220
+ if command -v python3 >/dev/null 2>&1 && python3 -c "import yaml" 2>/dev/null; then
221
+ if python3 - "$WORKFLOW_FILE" << 'PY' 2>/dev/null
222
+ import sys, yaml
223
+ try:
224
+ yaml.safe_load(open(sys.argv[1]).read())
225
+ sys.exit(0)
226
+ except yaml.YAMLError:
227
+ sys.exit(1)
228
+ PY
229
+ then
230
+ pass "AC7: trust-reconcile.yml parses as valid YAML (python3 yaml)"
231
+ else
232
+ fail "AC7: trust-reconcile.yml failed python3 yaml parse"
233
+ fi
234
+ else
235
+ grep -q "^name:" "$WORKFLOW_FILE" && grep -q "trust-reconcile" "$WORKFLOW_FILE" && \
236
+ pass "AC7: trust-reconcile.yml has expected structural fields (yaml parser unavailable, structural fallback)" || \
237
+ fail "AC7: trust-reconcile.yml missing expected structural fields"
238
+ fi
239
+
240
+ echo ""
241
+ if [[ "$errors" -eq 0 ]]; then
242
+ echo "PASS: trust-reconcile.js runtime-session trailer diagnostic (audit-only, key-only, exit-code-identical)"
243
+ exit 0
244
+ else
245
+ echo "FAIL: $errors check(s) failed"
246
+ exit 1
247
+ fi
@@ -73,6 +73,26 @@ else
73
73
  _fail "multi-model parse returned empty"
74
74
  fi
75
75
 
76
+ # --- 2b. remote/cache fallback: invalid cache or failed remote uses bundled -
77
+ price_cache_dir="$(mktemp -d)"
78
+ printf '%s' '<html>console auth gate</html>' > "$price_cache_dir/flow-agents-pricing-cache.json"
79
+ poisoned_cost=$(TMPDIR="$price_cache_dir" TELEMETRY_PRICING_FILE="" FLOW_AGENTS_PRICING_FILE="" TELEMETRY_PRICING_URL="http://127.0.0.1:1/api/telemetry/pricing" FLOW_AGENTS_PRICING_URL="" usage_parse_transcript "$tp" | jq '.estimated_cost_usd')
80
+ poisoned_ver=$(TMPDIR="$price_cache_dir" TELEMETRY_PRICING_FILE="" FLOW_AGENTS_PRICING_FILE="" TELEMETRY_PRICING_URL="http://127.0.0.1:1/api/telemetry/pricing" FLOW_AGENTS_PRICING_URL="" pricing_registry | jq -r '.current_version')
81
+ if [ "$poisoned_ver" = "2026-06-28" ] && [ "$(approx_eq "$poisoned_cost" 0.31)" = "true" ]; then
82
+ _pass "poisoned HTML pricing cache is ignored; bundled registry prices known models (cost=$poisoned_cost)"
83
+ else
84
+ _fail "poisoned cache fallback expected bundled version/cost 0.31 (version=$poisoned_ver cost=$poisoned_cost)"
85
+ fi
86
+ rm -f "$price_cache_dir/flow-agents-pricing-cache.json" 2>/dev/null
87
+ failed_remote_cost=$(TMPDIR="$price_cache_dir" TELEMETRY_PRICING_FILE="" FLOW_AGENTS_PRICING_FILE="" TELEMETRY_PRICING_URL="http://127.0.0.1:1/api/telemetry/pricing" FLOW_AGENTS_PRICING_URL="" usage_parse_transcript "$tp" | jq '.estimated_cost_usd')
88
+ failed_remote_ver=$(TMPDIR="$price_cache_dir" TELEMETRY_PRICING_FILE="" FLOW_AGENTS_PRICING_FILE="" TELEMETRY_PRICING_URL="http://127.0.0.1:1/api/telemetry/pricing" FLOW_AGENTS_PRICING_URL="" pricing_registry | jq -r '.current_version')
89
+ if [ "$failed_remote_ver" = "2026-06-28" ] && [ "$(approx_eq "$failed_remote_cost" 0.31)" = "true" ]; then
90
+ _pass "failed/401-style remote falls through to bundled registry with non-zero cost (cost=$failed_remote_cost)"
91
+ else
92
+ _fail "failed remote fallback expected bundled version/cost 0.31 (version=$failed_remote_ver cost=$failed_remote_cost)"
93
+ fi
94
+ rm -rf "$price_cache_dir" 2>/dev/null
95
+
76
96
  # --- 3. empty / no-usage transcript ----------------------------------------
77
97
  empty="$(mktemp)"; echo '{"type":"user","message":{"content":"hi"}}' > "$empty"
78
98
  if usage_parse_transcript "$empty" >/dev/null 2>&1; then _fail "empty transcript should return non-zero"; else _pass "empty transcript → non-zero (null fallback)"; fi
@@ -94,6 +114,24 @@ old_cost=$(TELEMETRY_PRICING_FILE="$v2" usage_parse_transcript "$tp2" "old" | jq
94
114
  { [ "$(approx_eq "$new_cost" 25)" = "true" ] && [ "$(approx_eq "$old_cost" 1)" = "true" ]; } \
95
115
  && _pass "version selection (default=25 @new, override=1 @old)" || _fail "version selection (new=$new_cost old=$old_cost)"
96
116
 
117
+ # --- 5b. corrupt registry (present but unparseable) degrades to tokens-only -
118
+ # Regression for: a non-empty-but-malformed registry (truncated pricing.json,
119
+ # bad remote 200) must NOT crash the jq -n --argjson parse and discard tokens
120
+ # — it should degrade exactly like a missing registry (cost/version -> null).
121
+ corrupt="$(mktemp)"; printf '%s' '{not valid json' > "$corrupt"
122
+ corrupt_res="$(TELEMETRY_PRICING_FILE="$corrupt" usage_parse_transcript "$tp")"
123
+ if [ -n "$corrupt_res" ]; then
124
+ cit=$(echo "$corrupt_res" | jq '.input_tokens'); cot=$(echo "$corrupt_res" | jq '.output_tokens')
125
+ ccost=$(echo "$corrupt_res" | jq -r '.estimated_cost_usd'); cver=$(echo "$corrupt_res" | jq -r '.pricing_version')
126
+ cby=$(echo "$corrupt_res" | jq '.by_model|length')
127
+ { [ "$cit" = "1000" ] && [ "$cot" = "2100" ] && [ "$ccost" = "null" ] && [ "$cver" = "null" ] && [ "$cby" -gt 0 ]; } \
128
+ && _pass "corrupt registry: tokens/by_model survive, cost+version degrade to null" \
129
+ || _fail "corrupt registry (in=$cit out=$cot cost=$ccost ver=$cver by_model_len=$cby)"
130
+ else
131
+ _fail "corrupt registry: usage_parse_transcript returned empty (tokens were discarded)"
132
+ fi
133
+ rm -f "$corrupt" 2>/dev/null
134
+
97
135
  # --- 6. cross-runtime golden vectors ---------------------------------------
98
136
  n=$(jq '.cases|length' "$GOLDEN")
99
137
  for i in $(seq 0 $((n - 1))); do
@@ -112,6 +150,29 @@ for i in $(seq 0 $((n - 1))); do
112
150
  rm -f "$gtp"
113
151
  done
114
152
 
153
+ # --- 7. usage_model_from_transcript_usage() — runtime-agnostic model derivation
154
+ echo ""
155
+ echo "Model derivation tests"
156
+
157
+ single="$(mktemp)"; mk_line "claude-opus-4-8" 100 200 0 0 > "$single"
158
+ single_res="$(usage_parse_transcript "$single")"
159
+ single_model="$(usage_model_from_transcript_usage "$single_res")"
160
+ [ "$single_model" = "claude-opus-4-8" ] && _pass "single-model transcript resolves that model" || _fail "single-model transcript (got $single_model)"
161
+
162
+ multi="$(mktemp)"
163
+ { mk_line "claude-opus-4-8" 1000 2000 0 500000; mk_line "claude-fable-5" 0 100 0 0; } > "$multi"
164
+ multi_res="$(usage_parse_transcript "$multi")"
165
+ multi_model="$(usage_model_from_transcript_usage "$multi_res")"
166
+ [ "$multi_model" = "claude-opus-4-8" ] && _pass "multi-model transcript resolves dominant-by-tokens model (opus over fable)" || _fail "multi-model dominant resolution (got $multi_model)"
167
+
168
+ empty_model="$(usage_model_from_transcript_usage "null")"
169
+ [ -z "$empty_model" ] && _pass "null transcript_usage resolves empty (falls through to usage_get_model)" || _fail "expected empty for null transcript_usage (got $empty_model)"
170
+
171
+ blank_model="$(usage_model_from_transcript_usage "")"
172
+ [ -z "$blank_model" ] && _pass "empty-string transcript_usage resolves empty (falls through to usage_get_model)" || _fail "expected empty for blank transcript_usage (got $blank_model)"
173
+
174
+ rm -f "$single" "$multi" 2>/dev/null
175
+
115
176
  rm -f "$ov" "$bad" "$tp" "$empty" "$drift" "$dlog" "$tp2" "$v2" 2>/dev/null
116
177
 
117
178
  echo ""