@kontourai/flow-agents 1.4.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/.github/CODEOWNERS +29 -0
  2. package/.github/actions/trust-verify/action.yml +145 -0
  3. package/.github/workflows/ci.yml +11 -4
  4. package/.github/workflows/kit-gates-demo.yml +2 -2
  5. package/.github/workflows/publish-npm.yml +10 -2
  6. package/.github/workflows/release-please.yml +1 -1
  7. package/.github/workflows/runtime-compat.yml +1 -1
  8. package/.github/workflows/trust-reconcile.yml +113 -0
  9. package/AGENTS.md +13 -0
  10. package/CHANGELOG.md +103 -0
  11. package/CONTRIBUTING.md +4 -4
  12. package/README.md +1 -0
  13. package/agents/tool-planner.json +1 -1
  14. package/build/src/cli/init.js +242 -20
  15. package/build/src/cli/validate-workflow-artifacts.js +19 -2
  16. package/build/src/cli/verify.d.ts +1 -0
  17. package/build/src/cli/verify.js +90 -0
  18. package/build/src/cli/workflow-sidecar.d.ts +316 -8
  19. package/build/src/cli/workflow-sidecar.js +1996 -91
  20. package/build/src/cli.js +2 -3
  21. package/build/src/lib/flow-resolver.d.ts +111 -0
  22. package/build/src/lib/flow-resolver.js +308 -0
  23. package/build/src/tools/build-universal-bundles.js +34 -22
  24. package/build/src/tools/generate-context-map.js +3 -16
  25. package/build/src/tools/validate-source-tree.d.ts +1 -1
  26. package/build/src/tools/validate-source-tree.js +42 -162
  27. package/context/contracts/artifact-contract.md +10 -0
  28. package/context/contracts/delivery-contract.md +1 -0
  29. package/context/contracts/review-contract.md +1 -0
  30. package/context/contracts/verification-contract.md +2 -0
  31. package/context/gate-awareness.md +39 -0
  32. package/context/scripts/hooks/stop-goal-fit.js +632 -70
  33. package/docs/adr/0001-flow-agents-consumes-flow.md +1 -1
  34. package/docs/adr/0002-flow-kits-as-extension-unit.md +1 -1
  35. package/docs/adr/0004-gates-expect-surface-claims.md +2 -0
  36. package/docs/adr/0005-kubernetes-inspired-resource-contracts.md +2 -0
  37. package/docs/adr/0007-skill-audit.md +1 -1
  38. package/docs/adr/0009-canonical-hook-core-kit-boundary.md +95 -0
  39. package/docs/adr/0010-workflow-trust-state-as-hachure-bundle.md +139 -0
  40. package/docs/adr/0011-mcp-posture.md +100 -0
  41. package/docs/adr/0012-agent-coordination-as-liveness-claims.md +119 -0
  42. package/docs/adr/0013-context-lifecycle.md +151 -0
  43. package/docs/adr/0014-core-vs-domain-kit-boundary.md +143 -0
  44. package/docs/adr/0015-flow-flow-agents-boundary-reconciliation.md +120 -0
  45. package/docs/adr/0016-three-hard-boundary-model.md +71 -0
  46. package/docs/adr/0017-anti-gaming-trust-security-model.md +155 -0
  47. package/docs/agent-system-guidebook.md +5 -12
  48. package/docs/context-map.md +4 -10
  49. package/docs/index.md +3 -2
  50. package/docs/integrations/framework-adapter.md +19 -6
  51. package/docs/integrations/index.md +2 -2
  52. package/docs/north-star.md +4 -4
  53. package/docs/operating-layers.md +3 -3
  54. package/docs/plans/adr-0010-phase2-gate-recompute.md +55 -0
  55. package/docs/repository-structure.md +2 -2
  56. package/docs/skills-map.md +1 -0
  57. package/docs/spec/runtime-hook-surface.md +62 -9
  58. package/docs/standards-register.md +3 -3
  59. package/docs/survey-utterance-check.md +1 -1
  60. package/docs/trust-anchor-adoption.md +197 -0
  61. package/docs/verifiable-trust.md +95 -0
  62. package/docs/veritas-integration.md +2 -2
  63. package/docs/workflow-usage-guide.md +69 -0
  64. package/evals/acceptance/DEMO-false-completion.md +144 -0
  65. package/evals/acceptance/demo-cast.sh +92 -0
  66. package/evals/acceptance/demo-false-completion.sh +72 -0
  67. package/evals/acceptance/demo-real-evidence.sh +104 -0
  68. package/evals/acceptance/demo.tape +29 -0
  69. package/evals/acceptance/prove-capture-teeth-declared.sh +335 -0
  70. package/evals/acceptance/prove-capture-teeth.sh +114 -0
  71. package/evals/acceptance/prove-teeth.sh +105 -0
  72. package/evals/ci/antigaming-suite.sh +55 -0
  73. package/evals/ci/run-baseline.sh +2 -0
  74. package/evals/fixtures/flow-kit-repository/invalid-missing-extension-asset/flows/review.flow.json +26 -0
  75. package/evals/fixtures/flow-kit-repository/invalid-missing-extension-asset/kit.json +20 -0
  76. package/evals/fixtures/flow-kit-repository/valid-unknown-extension/flows/review.flow.json +26 -0
  77. package/evals/fixtures/flow-kit-repository/valid-unknown-extension/kit.json +18 -0
  78. package/evals/integration/test_builder_step_producers.sh +379 -0
  79. package/evals/integration/test_bundle_install.sh +35 -71
  80. package/evals/integration/test_bundle_lifecycle.sh +39 -2
  81. package/evals/integration/test_captured_fail_reconciliation.sh +820 -0
  82. package/evals/integration/test_checkpoint_signing.sh +489 -0
  83. package/evals/integration/test_claim_lookup.sh +352 -0
  84. package/evals/integration/test_command_log_fork_classification.sh +134 -0
  85. package/evals/integration/test_command_log_integrity.sh +275 -0
  86. package/evals/integration/test_context_map.sh +0 -2
  87. package/evals/integration/test_dual_emit_flow_step.sh +278 -0
  88. package/evals/integration/test_enforcer_expects_driven.sh +281 -0
  89. package/evals/integration/test_evidence_capture_hook.sh +185 -0
  90. package/evals/integration/test_flow_kit_repository.sh +2 -0
  91. package/evals/integration/test_flowdef_session_activation.sh +273 -0
  92. package/evals/integration/test_flowdef_session_history_preservation.sh +250 -0
  93. package/evals/integration/test_gate_bypass_chain.sh +448 -0
  94. package/evals/integration/test_gate_lockdown.sh +1137 -0
  95. package/evals/integration/test_gate_review_inquiry_records.sh +399 -0
  96. package/evals/integration/test_goal_fit_escape_hatch.sh +73 -0
  97. package/evals/integration/test_goal_fit_hook.sh +69 -4
  98. package/evals/integration/test_goal_fit_rederive.sh +263 -0
  99. package/evals/integration/test_install_merge.sh +1176 -0
  100. package/evals/integration/test_kit_identity_trust.sh +393 -0
  101. package/evals/integration/test_mint_attestation.sh +373 -0
  102. package/evals/integration/test_phase_map_and_gate_claim.sh +365 -0
  103. package/evals/integration/test_publish_delivery.sh +269 -0
  104. package/evals/integration/test_reconcile_soundness.sh +528 -0
  105. package/evals/integration/test_resolvefirststep_security.sh +208 -0
  106. package/evals/integration/test_session_resume_roundtrip.sh +286 -0
  107. package/evals/integration/test_trust_checkpoint.sh +325 -0
  108. package/evals/integration/test_trust_reconcile.sh +293 -0
  109. package/evals/integration/test_verify_cli.sh +208 -0
  110. package/evals/integration/test_workflow_sidecar_writer.sh +549 -34
  111. package/evals/lib/node.sh +0 -6
  112. package/evals/run.sh +47 -0
  113. package/evals/static/test_workflow_skills.sh +6 -13
  114. package/install.sh +0 -7
  115. package/integrations/strands-ts/README.md +25 -15
  116. package/integrations/veritas/flow-agents.adapter.json +1 -2
  117. package/kits/builder/flows/build.flow.json +59 -12
  118. package/kits/builder/kit.json +85 -15
  119. package/kits/builder/skills/continue-work/SKILL.md +116 -0
  120. package/kits/builder/skills/deliver/SKILL.md +36 -6
  121. package/kits/builder/skills/design-probe/SKILL.md +28 -0
  122. package/kits/builder/skills/execute-plan/SKILL.md +9 -1
  123. package/kits/builder/skills/gate-review/SKILL.md +234 -0
  124. package/kits/builder/skills/learning-review/SKILL.md +30 -0
  125. package/kits/builder/skills/pickup-probe/SKILL.md +29 -0
  126. package/kits/builder/skills/plan-work/SKILL.md +13 -1
  127. package/kits/builder/skills/pull-work/SKILL.md +19 -0
  128. package/kits/knowledge/adapters/default-store/index.js +38 -0
  129. package/kits/knowledge/adapters/flow-runner/index.js +1620 -0
  130. package/kits/knowledge/adapters/obsidian-store/index.js +36 -6
  131. package/kits/knowledge/docs/store-contract.md +314 -0
  132. package/kits/knowledge/evals/audit-freshness/suite.test.js +368 -0
  133. package/kits/knowledge/evals/canonicalize-category/suite.test.js +383 -0
  134. package/kits/knowledge/evals/contract-suite/suite.test.js +111 -0
  135. package/kits/knowledge/evals/detect-contradictions/suite.test.js +324 -0
  136. package/kits/knowledge/evals/entities/suite.test.js +40 -0
  137. package/kits/knowledge/evals/glossary-sync/suite.test.js +416 -0
  138. package/kits/knowledge/evals/hygiene-review/suite.test.js +396 -0
  139. package/kits/knowledge/evals/retirement/suite.test.js +145 -0
  140. package/kits/knowledge/flows/audit-freshness.flow.json +44 -0
  141. package/kits/knowledge/flows/canonicalize-category.flow.json +44 -0
  142. package/kits/knowledge/flows/detect-contradictions.flow.json +44 -0
  143. package/kits/knowledge/flows/glossary-sync.flow.json +61 -0
  144. package/kits/knowledge/flows/hygiene-review.flow.json +43 -0
  145. package/kits/knowledge/kit.json +51 -1
  146. package/package.json +6 -6
  147. package/packaging/conformance/README.md +10 -2
  148. package/packaging/conformance/fixtures/evidence-capture--allow-records-command.json +29 -0
  149. package/packaging/conformance/fixtures/stop-goal-fit--block-bundle-disputed-claim.json +29 -0
  150. package/packaging/conformance/fixtures/stop-goal-fit--block-capture-contradicts-claimed-pass.json +30 -0
  151. package/packaging/conformance/fixtures/stop-goal-fit--block-mode.json +23 -0
  152. package/packaging/conformance/fixtures/stop-goal-fit--off-mode.json +24 -0
  153. package/packaging/conformance/fixtures/stop-goal-fit--warn-active-delivery.json +5 -2
  154. package/packaging/conformance/fixtures/stop-goal-fit--warn-no-bundle.json +23 -0
  155. package/packaging/conformance/fixtures/workflow-steering--reground-active-prompt.json +30 -0
  156. package/packaging/conformance/fixtures/workflow-steering--reground-session-start.json +30 -0
  157. package/packaging/conformance/run-conformance.js +1 -1
  158. package/scripts/README.md +2 -1
  159. package/scripts/build-universal-bundles.js +0 -1
  160. package/scripts/ci/mint-attestation.js +221 -0
  161. package/scripts/ci/trust-reconcile.js +545 -0
  162. package/scripts/hooks/config-protection.js +423 -1
  163. package/scripts/hooks/evidence-capture.js +348 -0
  164. package/scripts/hooks/lib/liveness-read.js +113 -0
  165. package/scripts/hooks/run-hook.js +6 -1
  166. package/scripts/hooks/stop-goal-fit.js +1524 -79
  167. package/scripts/hooks/workflow-steering.js +135 -5
  168. package/scripts/install-codex-home.sh +39 -0
  169. package/scripts/install-merge.js +330 -0
  170. package/scripts/repair-command-log.js +115 -0
  171. package/src/cli/init.ts +218 -20
  172. package/src/cli/validate-workflow-artifacts.ts +18 -2
  173. package/src/cli/verify.ts +100 -0
  174. package/src/cli/workflow-sidecar.ts +2127 -84
  175. package/src/cli.ts +2 -3
  176. package/src/lib/flow-resolver.ts +369 -0
  177. package/src/tools/build-universal-bundles.ts +34 -21
  178. package/src/tools/generate-context-map.ts +3 -17
  179. package/src/tools/validate-source-tree.ts +44 -104
  180. package/build/src/tools/filter-installed-packs.d.ts +0 -2
  181. package/build/src/tools/filter-installed-packs.js +0 -135
  182. package/packaging/packs.json +0 -49
  183. package/scripts/filter-installed-packs.js +0 -2
  184. package/src/tools/filter-installed-packs.ts +0 -132
@@ -0,0 +1,528 @@
1
+ #!/usr/bin/env bash
2
+ # test_reconcile_soundness.sh — Soundness regression evals for trust-reconcile.js.
3
+ #
4
+ # Proves all Round-5 adversarial gaps are closed:
5
+ #
6
+ # A. COMPILE-ONLY-CLOSED: no trust-reconcile-verify configured (only build fallback)
7
+ # → exits 1 with "refusing to attest a compile-only check" message.
8
+ #
9
+ # B. REAL-VERIFY-CLOSED: build passes but a fake "real verify" (eval:static substitute)
10
+ # fails → trust-reconcile exits 1 (PRE-FIX this would exit 0 because only build ran).
11
+ #
12
+ # C. REAL-VERIFY-PASSES: the comprehensive verify resolves green → exits 0 (legit
13
+ # work is not false-blocked when package.json trust-reconcile-verify is present).
14
+ #
15
+ # D. CHECKPOINT-BYPASS-CLOSED: a checkpoint-only bundle (no evidence/claims, statusByClaimId
16
+ # all-passed) → exits 1 with checkpoint-bypass divergence (not a silent skip).
17
+ #
18
+ # E. EV-PASSING-NORMALIZED: bundle with passing:"pass" and passing:1 (non-boolean truthy)
19
+ # evidence items → both are treated as claimed-pass and reconciled.
20
+ #
21
+ # F. CLAIM-NO-EVIDENCE: workflow.check.command claim with no evidence item →
22
+ # not-run divergence, exits 1.
23
+ #
24
+ # G. LAUNDERING-OR: claimed pass for "npm test || exit 0" → laundering, exits 1.
25
+ #
26
+ # H. LAUNDERING-ECHO-OK: claimed pass for "npm test || echo ok" → laundering, exits 1.
27
+ #
28
+ # I. LAUNDERING-BIN-TRUE: claimed pass for "npm test || /bin/true" → laundering, exits 1.
29
+ #
30
+ # J. LAUNDERING-SEMI-TRUE: claimed pass for "npm test; true" → laundering, exits 1.
31
+ #
32
+ # Deterministic, no model spend, self-cleaning.
33
+ # Usage: bash evals/integration/test_reconcile_soundness.sh
34
+
35
+ set -uo pipefail
36
+
37
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
38
+ RECONCILE="$ROOT/scripts/ci/trust-reconcile.js"
39
+
40
+ TMP="$(mktemp -d)"
41
+ errors=0
42
+
43
+ _pass() { echo " PASS: $1"; }
44
+ _fail() { echo " FAIL: $1"; errors=$((errors + 1)); }
45
+
46
+ cleanup() { rm -rf "$TMP"; }
47
+ trap cleanup EXIT
48
+
49
+ # ─── Minimal package.json without trust-reconcile-verify ──────────────────────
50
+ PKG_NO_VERIFY="$TMP/pkg_no_verify"
51
+ mkdir -p "$PKG_NO_VERIFY"
52
+ node -e "
53
+ const fs = require('fs');
54
+ const pkg = { name: 'test-pkg', scripts: { build: 'echo BUILD_ONLY' } };
55
+ fs.writeFileSync('$PKG_NO_VERIFY/package.json', JSON.stringify(pkg, null, 2));
56
+ "
57
+
58
+ # ─── Minimal package.json WITH trust-reconcile-verify ─────────────────────────
59
+ PKG_WITH_VERIFY="$TMP/pkg_with_verify"
60
+ mkdir -p "$PKG_WITH_VERIFY"
61
+ node -e "
62
+ const fs = require('fs');
63
+ const pkg = {
64
+ name: 'test-pkg',
65
+ scripts: {
66
+ build: 'node -e \"process.exit(0)\"',
67
+ 'trust-reconcile-verify': 'node -e \"process.exit(0)\"'
68
+ }
69
+ };
70
+ fs.writeFileSync('$PKG_WITH_VERIFY/package.json', JSON.stringify(pkg, null, 2));
71
+ "
72
+
73
+ # ─── Minimal package.json with failing verify ─────────────────────────────────
74
+ PKG_FAIL_VERIFY="$TMP/pkg_fail_verify"
75
+ mkdir -p "$PKG_FAIL_VERIFY"
76
+ node -e "
77
+ const fs = require('fs');
78
+ const pkg = {
79
+ name: 'test-pkg',
80
+ scripts: {
81
+ build: 'node -e \"process.exit(0)\"',
82
+ 'trust-reconcile-verify': 'node -e \"process.exit(1)\"'
83
+ }
84
+ };
85
+ fs.writeFileSync('$PKG_FAIL_VERIFY/package.json', JSON.stringify(pkg, null, 2));
86
+ "
87
+
88
+ # ─── Bundle builder helpers ────────────────────────────────────────────────────
89
+ # write_bundle_evidence: bundle with evidence[].passing = <passing_val> (any JS value)
90
+ write_bundle_evidence() {
91
+ local bundle_path="$1"
92
+ local label="$2"
93
+ local passing_val="$3" # raw JS value: true, false, "\"pass\"", 1, etc.
94
+
95
+ node - "$bundle_path" "$label" "$passing_val" << 'NODE'
96
+ const fs = require('fs');
97
+ const [,, bundlePath, label, passingVal] = process.argv;
98
+ // Evaluate passing value as JS literal
99
+ const passing = JSON.parse(passingVal);
100
+ const bundle = {
101
+ schemaVersion: 3,
102
+ source: "test-fixture",
103
+ claims: [
104
+ {
105
+ id: "c1",
106
+ claimType: "workflow.check.build",
107
+ value: "pass",
108
+ status: "verified",
109
+ subjectId: "test/build",
110
+ surface: "flow-agents.workflow",
111
+ subjectType: "workflow-check",
112
+ fieldOrBehavior: "build",
113
+ createdAt: "2026-06-27T00:00:00Z",
114
+ updatedAt: "2026-06-27T00:00:00Z",
115
+ impactLevel: "high",
116
+ verificationPolicyId: "policy:workflow.check.build"
117
+ }
118
+ ],
119
+ evidence: [
120
+ {
121
+ id: "ev1",
122
+ claimId: "c1",
123
+ evidenceType: "test_output",
124
+ method: "validation",
125
+ sourceRef: "test/command-log.jsonl",
126
+ excerptOrSummary: "build",
127
+ observedAt: "2026-06-27T00:00:00Z",
128
+ collectedBy: "flow-agents/evidence-capture",
129
+ passing: passing,
130
+ execution: {
131
+ runner: "bash",
132
+ label: label,
133
+ isError: !passing,
134
+ exitCode: passing ? 0 : 1
135
+ }
136
+ }
137
+ ],
138
+ policies: [],
139
+ events: []
140
+ };
141
+ fs.writeFileSync(bundlePath, JSON.stringify(bundle, null, 2));
142
+ NODE
143
+ }
144
+
145
+ # write_checkpoint: checkpoint-only bundle (no evidence[], no claims[])
146
+ write_checkpoint() {
147
+ local bundle_path="$1"
148
+ node - "$bundle_path" << 'NODE'
149
+ const fs = require('fs');
150
+ const [,, bundlePath] = process.argv;
151
+ const bundle = {
152
+ schemaVersion: 1,
153
+ source: "checkpoint",
154
+ checkpoint: {
155
+ statusByClaimId: {
156
+ "c1": "passed",
157
+ "c2": "passed",
158
+ "c3": "passed"
159
+ }
160
+ }
161
+ };
162
+ fs.writeFileSync(bundlePath, JSON.stringify(bundle, null, 2));
163
+ NODE
164
+ }
165
+
166
+ # write_bundle_claim_no_evidence: bundle with a workflow.check.command claim but no evidence
167
+ write_bundle_claim_no_evidence() {
168
+ local bundle_path="$1"
169
+ node - "$bundle_path" << 'NODE'
170
+ const fs = require('fs');
171
+ const [,, bundlePath] = process.argv;
172
+ const bundle = {
173
+ schemaVersion: 3,
174
+ source: "test-fixture",
175
+ claims: [
176
+ {
177
+ id: "c-no-ev",
178
+ claimType: "workflow.check.command",
179
+ value: "pass",
180
+ status: "verified",
181
+ subjectId: "test/command-never-run",
182
+ surface: "flow-agents.workflow",
183
+ subjectType: "workflow-check",
184
+ fieldOrBehavior: "npm run test-never-ran",
185
+ createdAt: "2026-06-27T00:00:00Z",
186
+ updatedAt: "2026-06-27T00:00:00Z",
187
+ impactLevel: "high",
188
+ verificationPolicyId: "policy:workflow.check.command"
189
+ }
190
+ ],
191
+ evidence: [],
192
+ policies: [],
193
+ events: []
194
+ };
195
+ fs.writeFileSync(bundlePath, JSON.stringify(bundle, null, 2));
196
+ NODE
197
+ }
198
+
199
+ # ═══════════════════════════════════════════════════════════════════════════════
200
+ # TEST A: COMPILE-ONLY-CLOSED
201
+ # No trust-reconcile-verify in package.json, no TRUST_RECONCILE_COMMANDS → fail-closed
202
+ # ═══════════════════════════════════════════════════════════════════════════════
203
+ echo ""
204
+ echo "=== TEST A: COMPILE-ONLY-CLOSED — no verify script configured → fail-closed ==="
205
+
206
+ outA=$(node "$RECONCILE" \
207
+ --repo-root "$PKG_NO_VERIFY" 2>&1)
208
+ exitA=$?
209
+
210
+ if [[ $exitA -ne 0 ]]; then
211
+ _pass "COMPILE-ONLY-CLOSED: exits 1 (got $exitA)"
212
+ else
213
+ _fail "COMPILE-ONLY-CLOSED: expected exit 1 (compile-only refused), got 0"
214
+ fi
215
+
216
+ if echo "$outA" | grep -qi "refusing to attest a compile-only check\|no comprehensive trust-reconcile-verify"; then
217
+ _pass "COMPILE-ONLY-CLOSED: message explains compile-only refusal"
218
+ else
219
+ _fail "COMPILE-ONLY-CLOSED: expected compile-only refusal message, got: $outA"
220
+ fi
221
+
222
+ # Also verify: the message recommends how to fix it
223
+ if echo "$outA" | grep -q "trust-reconcile-verify\|TRUST_RECONCILE_COMMANDS"; then
224
+ _pass "COMPILE-ONLY-CLOSED: message references how to fix (declare scripts or env)"
225
+ else
226
+ _fail "COMPILE-ONLY-CLOSED: expected fix hint in message, got: $outA"
227
+ fi
228
+
229
+ # ═══════════════════════════════════════════════════════════════════════════════
230
+ # TEST B: REAL-VERIFY-CLOSED
231
+ # package.json has trust-reconcile-verify but it FAILS (simulates real tests failing)
232
+ # PRE-FIX: would have exited 0 (only build ran). POST-FIX: exits 1.
233
+ # ═══════════════════════════════════════════════════════════════════════════════
234
+ echo ""
235
+ echo "=== TEST B: REAL-VERIFY-CLOSED — trust-reconcile-verify fails → exits 1 ==="
236
+
237
+ outB=$(node "$RECONCILE" \
238
+ --repo-root "$PKG_FAIL_VERIFY" 2>&1)
239
+ exitB=$?
240
+
241
+ if [[ $exitB -ne 0 ]]; then
242
+ _pass "REAL-VERIFY-CLOSED: exits 1 when trust-reconcile-verify fails (got $exitB)"
243
+ else
244
+ _fail "REAL-VERIFY-CLOSED: expected exit 1 when real verify fails, got 0 — output: $outB"
245
+ fi
246
+
247
+ if echo "$outB" | grep -q "verification failed in CI"; then
248
+ _pass "REAL-VERIFY-CLOSED: 'verification failed in CI' message present"
249
+ else
250
+ _fail "REAL-VERIFY-CLOSED: expected 'verification failed in CI' message, got: $outB"
251
+ fi
252
+
253
+ # ═══════════════════════════════════════════════════════════════════════════════
254
+ # TEST C: REAL-VERIFY-PASSES
255
+ # package.json has passing trust-reconcile-verify → exits 0 (not false-blocked)
256
+ # ═══════════════════════════════════════════════════════════════════════════════
257
+ echo ""
258
+ echo "=== TEST C: REAL-VERIFY-PASSES — passing verify + no bundle → exits 0 ==="
259
+
260
+ outC=$(node "$RECONCILE" \
261
+ --repo-root "$PKG_WITH_VERIFY" 2>&1)
262
+ exitC=$?
263
+
264
+ if [[ $exitC -eq 0 ]]; then
265
+ _pass "REAL-VERIFY-PASSES: exits 0 when real verify passes and no bundle (got $exitC)"
266
+ else
267
+ _fail "REAL-VERIFY-PASSES: expected exit 0, got $exitC — output: $outC"
268
+ fi
269
+
270
+ if echo "$outC" | grep -q "fresh verify passed"; then
271
+ _pass "REAL-VERIFY-PASSES: 'fresh verify passed' message present"
272
+ else
273
+ _fail "REAL-VERIFY-PASSES: expected 'fresh verify passed' in output, got: $outC"
274
+ fi
275
+
276
+ # ═══════════════════════════════════════════════════════════════════════════════
277
+ # TEST D: CHECKPOINT-BYPASS-CLOSED
278
+ # Checkpoint-only bundle (no evidence/claims, all statusByClaimId=passed) → divergence
279
+ # PRE-FIX: exited 0 (silently skipped per-command reconcile). POST-FIX: exits 1.
280
+ # ═══════════════════════════════════════════════════════════════════════════════
281
+ echo ""
282
+ echo "=== TEST D: CHECKPOINT-BYPASS-CLOSED — checkpoint-only bundle → divergence ==="
283
+
284
+ CHECKPOINT_BUNDLE="$TMP/checkpoint.json"
285
+ write_checkpoint "$CHECKPOINT_BUNDLE"
286
+
287
+ outD=$(TRUST_RECONCILE_COMMANDS="node -e 'process.exit(0)'" \
288
+ node "$RECONCILE" \
289
+ --bundle "$CHECKPOINT_BUNDLE" \
290
+ --repo-root "$TMP" 2>&1)
291
+ exitD=$?
292
+
293
+ if [[ $exitD -ne 0 ]]; then
294
+ _pass "CHECKPOINT-BYPASS-CLOSED: exits 1 (got $exitD)"
295
+ else
296
+ _fail "CHECKPOINT-BYPASS-CLOSED: expected exit 1 (checkpoint bypass closed), got 0 — output: $outD"
297
+ fi
298
+
299
+ if echo "$outD" | grep -q "checkpoint-only bundle"; then
300
+ _pass "CHECKPOINT-BYPASS-CLOSED: 'checkpoint-only bundle' message present"
301
+ else
302
+ _fail "CHECKPOINT-BYPASS-CLOSED: expected 'checkpoint-only bundle' in output, got: $outD"
303
+ fi
304
+
305
+ if echo "$outD" | grep -q "trust divergence"; then
306
+ _pass "CHECKPOINT-BYPASS-CLOSED: 'trust divergence' emitted for checkpoint bundle"
307
+ else
308
+ _fail "CHECKPOINT-BYPASS-CLOSED: expected 'trust divergence' in output, got: $outD"
309
+ fi
310
+
311
+ # ═══════════════════════════════════════════════════════════════════════════════
312
+ # TEST E: EV-PASSING-NORMALIZED
313
+ # evidence with passing:"pass" is treated as claimed-pass (not dropped)
314
+ # evidence with passing:1 is also treated as claimed-pass
315
+ # ═══════════════════════════════════════════════════════════════════════════════
316
+ echo ""
317
+ echo "=== TEST E: EV-PASSING-NORMALIZED — passing:\"pass\" and passing:1 both reconciled ==="
318
+
319
+ # E1: passing:"pass" — claimed pass, CI re-run also PASSES → reconciled (exit 0)
320
+ BUNDLE_E1="$TMP/bundle-pass-string.json"
321
+ write_bundle_evidence "$BUNDLE_E1" "node -e 'process.exit(0)'" '"pass"'
322
+
323
+ outE1=$(TRUST_RECONCILE_COMMANDS="node -e 'process.exit(0)'" \
324
+ node "$RECONCILE" \
325
+ --bundle "$BUNDLE_E1" \
326
+ --repo-root "$TMP" 2>&1)
327
+ exitE1=$?
328
+
329
+ if [[ $exitE1 -eq 0 ]]; then
330
+ _pass "EV-PASSING-NORMALIZED: passing:\"pass\" evidence is reconciled (exit 0)"
331
+ else
332
+ _fail "EV-PASSING-NORMALIZED: passing:\"pass\" should exit 0 (reconciled), got $exitE1 — output: $outE1"
333
+ fi
334
+
335
+ if echo "$outE1" | grep -q "RECONCILED"; then
336
+ _pass "EV-PASSING-NORMALIZED: RECONCILED shown for passing:\"pass\" evidence"
337
+ else
338
+ _fail "EV-PASSING-NORMALIZED: expected RECONCILED for passing:\"pass\", got: $outE1"
339
+ fi
340
+
341
+ # E2: passing:"pass" — claimed pass, CI re-run FAILS → divergence (exit 1)
342
+ BUNDLE_E2="$TMP/bundle-pass-string-fail.json"
343
+ write_bundle_evidence "$BUNDLE_E2" "node -e 'process.exit(1)'" '"pass"'
344
+
345
+ outE2=$(TRUST_RECONCILE_COMMANDS="node -e 'process.exit(1)'" \
346
+ node "$RECONCILE" \
347
+ --bundle "$BUNDLE_E2" \
348
+ --repo-root "$TMP" 2>&1)
349
+ exitE2=$?
350
+
351
+ if [[ $exitE2 -ne 0 ]]; then
352
+ _pass "EV-PASSING-NORMALIZED: passing:\"pass\" evidence triggers divergence when CI fails (exit 1)"
353
+ else
354
+ _fail "EV-PASSING-NORMALIZED: passing:\"pass\" should exit 1 (divergence), got 0 — output: $outE2"
355
+ fi
356
+
357
+ # E3: passing:1 — treated as claimed-pass, CI PASSES → reconciled (exit 0)
358
+ BUNDLE_E3="$TMP/bundle-pass-int.json"
359
+ write_bundle_evidence "$BUNDLE_E3" "node -e 'process.exit(0)'" '1'
360
+
361
+ outE3=$(TRUST_RECONCILE_COMMANDS="node -e 'process.exit(0)'" \
362
+ node "$RECONCILE" \
363
+ --bundle "$BUNDLE_E3" \
364
+ --repo-root "$TMP" 2>&1)
365
+ exitE3=$?
366
+
367
+ if [[ $exitE3 -eq 0 ]]; then
368
+ _pass "EV-PASSING-NORMALIZED: passing:1 evidence is reconciled (exit 0)"
369
+ else
370
+ _fail "EV-PASSING-NORMALIZED: passing:1 should exit 0 (reconciled), got $exitE3 — output: $outE3"
371
+ fi
372
+
373
+ # ═══════════════════════════════════════════════════════════════════════════════
374
+ # TEST F: CLAIM-NO-EVIDENCE
375
+ # workflow.check.command claim with no evidence item → not-run divergence, exits 1
376
+ # ═══════════════════════════════════════════════════════════════════════════════
377
+ echo ""
378
+ echo "=== TEST F: CLAIM-NO-EVIDENCE — workflow.check.command claim, no evidence → not-run ==="
379
+
380
+ BUNDLE_F="$TMP/bundle-claim-no-ev.json"
381
+ write_bundle_claim_no_evidence "$BUNDLE_F"
382
+
383
+ outF=$(TRUST_RECONCILE_COMMANDS="node -e 'process.exit(0)'" \
384
+ node "$RECONCILE" \
385
+ --bundle "$BUNDLE_F" \
386
+ --repo-root "$TMP" 2>&1)
387
+ exitF=$?
388
+
389
+ if [[ $exitF -ne 0 ]]; then
390
+ _pass "CLAIM-NO-EVIDENCE: exits 1 (not-run divergence) (got $exitF)"
391
+ else
392
+ _fail "CLAIM-NO-EVIDENCE: expected exit 1 (no-evidence divergence), got 0 — output: $outF"
393
+ fi
394
+
395
+ if echo "$outF" | grep -q "trust divergence"; then
396
+ _pass "CLAIM-NO-EVIDENCE: 'trust divergence' emitted"
397
+ else
398
+ _fail "CLAIM-NO-EVIDENCE: expected 'trust divergence' in output, got: $outF"
399
+ fi
400
+
401
+ if echo "$outF" | grep -q "no supporting evidence\|never captured"; then
402
+ _pass "CLAIM-NO-EVIDENCE: message describes missing evidence"
403
+ else
404
+ _fail "CLAIM-NO-EVIDENCE: expected 'no supporting evidence' or 'never captured' message, got: $outF"
405
+ fi
406
+
407
+ # ═══════════════════════════════════════════════════════════════════════════════
408
+ # TEST G: LAUNDERING-OR-EXIT0
409
+ # claimed pass for "npm test || exit 0" → laundering, exits 1
410
+ # ═══════════════════════════════════════════════════════════════════════════════
411
+ echo ""
412
+ echo "=== TEST G: LAUNDERING-OR-EXIT0 — \"npm test || exit 0\" → laundering ==="
413
+
414
+ BUNDLE_G="$TMP/bundle-launder-or-exit0.json"
415
+ write_bundle_evidence "$BUNDLE_G" "npm test || exit 0" 'true'
416
+
417
+ outG=$(TRUST_RECONCILE_COMMANDS="node -e 'process.exit(0)'" \
418
+ node "$RECONCILE" \
419
+ --bundle "$BUNDLE_G" \
420
+ --repo-root "$TMP" 2>&1)
421
+ exitG=$?
422
+
423
+ if [[ $exitG -ne 0 ]]; then
424
+ _pass "LAUNDERING-OR-EXIT0: exits 1"
425
+ else
426
+ _fail "LAUNDERING-OR-EXIT0: expected exit 1, got 0 — output: $outG"
427
+ fi
428
+
429
+ if echo "$outG" | grep -q "laundering"; then
430
+ _pass "LAUNDERING-OR-EXIT0: 'laundering' message present"
431
+ else
432
+ _fail "LAUNDERING-OR-EXIT0: expected 'laundering' message, got: $outG"
433
+ fi
434
+
435
+ # ═══════════════════════════════════════════════════════════════════════════════
436
+ # TEST H: LAUNDERING-OR-ECHO-OK
437
+ # claimed pass for "npm test || echo ok" → laundering (any ||), exits 1
438
+ # ═══════════════════════════════════════════════════════════════════════════════
439
+ echo ""
440
+ echo "=== TEST H: LAUNDERING-OR-ECHO-OK — \"npm test || echo ok\" → laundering ==="
441
+
442
+ BUNDLE_H="$TMP/bundle-launder-or-echo.json"
443
+ write_bundle_evidence "$BUNDLE_H" "npm test || echo ok" 'true'
444
+
445
+ outH=$(TRUST_RECONCILE_COMMANDS="node -e 'process.exit(0)'" \
446
+ node "$RECONCILE" \
447
+ --bundle "$BUNDLE_H" \
448
+ --repo-root "$TMP" 2>&1)
449
+ exitH=$?
450
+
451
+ if [[ $exitH -ne 0 ]]; then
452
+ _pass "LAUNDERING-OR-ECHO-OK: exits 1"
453
+ else
454
+ _fail "LAUNDERING-OR-ECHO-OK: expected exit 1, got 0 — output: $outH"
455
+ fi
456
+
457
+ if echo "$outH" | grep -q "laundering"; then
458
+ _pass "LAUNDERING-OR-ECHO-OK: 'laundering' message present"
459
+ else
460
+ _fail "LAUNDERING-OR-ECHO-OK: expected 'laundering' message, got: $outH"
461
+ fi
462
+
463
+ # ═══════════════════════════════════════════════════════════════════════════════
464
+ # TEST I: LAUNDERING-OR-BIN-TRUE
465
+ # claimed pass for "npm test || /bin/true" → laundering (any ||), exits 1
466
+ # ═══════════════════════════════════════════════════════════════════════════════
467
+ echo ""
468
+ echo "=== TEST I: LAUNDERING-OR-BIN-TRUE — \"npm test || /bin/true\" → laundering ==="
469
+
470
+ BUNDLE_I="$TMP/bundle-launder-or-bintrue.json"
471
+ write_bundle_evidence "$BUNDLE_I" "npm test || /bin/true" 'true'
472
+
473
+ outI=$(TRUST_RECONCILE_COMMANDS="node -e 'process.exit(0)'" \
474
+ node "$RECONCILE" \
475
+ --bundle "$BUNDLE_I" \
476
+ --repo-root "$TMP" 2>&1)
477
+ exitI=$?
478
+
479
+ if [[ $exitI -ne 0 ]]; then
480
+ _pass "LAUNDERING-OR-BIN-TRUE: exits 1"
481
+ else
482
+ _fail "LAUNDERING-OR-BIN-TRUE: expected exit 1, got 0 — output: $outI"
483
+ fi
484
+
485
+ if echo "$outI" | grep -q "laundering"; then
486
+ _pass "LAUNDERING-OR-BIN-TRUE: 'laundering' message present"
487
+ else
488
+ _fail "LAUNDERING-OR-BIN-TRUE: expected 'laundering' message, got: $outI"
489
+ fi
490
+
491
+ # ═══════════════════════════════════════════════════════════════════════════════
492
+ # TEST J: LAUNDERING-SEMI-TRUE
493
+ # claimed pass for "npm test; true" → laundering (; true form), exits 1
494
+ # ═══════════════════════════════════════════════════════════════════════════════
495
+ echo ""
496
+ echo "=== TEST J: LAUNDERING-SEMI-TRUE — \"npm test; true\" → laundering ==="
497
+
498
+ BUNDLE_J="$TMP/bundle-launder-semi-true.json"
499
+ write_bundle_evidence "$BUNDLE_J" "npm test; true" 'true'
500
+
501
+ outJ=$(TRUST_RECONCILE_COMMANDS="node -e 'process.exit(0)'" \
502
+ node "$RECONCILE" \
503
+ --bundle "$BUNDLE_J" \
504
+ --repo-root "$TMP" 2>&1)
505
+ exitJ=$?
506
+
507
+ if [[ $exitJ -ne 0 ]]; then
508
+ _pass "LAUNDERING-SEMI-TRUE: exits 1"
509
+ else
510
+ _fail "LAUNDERING-SEMI-TRUE: expected exit 1, got 0 — output: $outJ"
511
+ fi
512
+
513
+ if echo "$outJ" | grep -q "laundering"; then
514
+ _pass "LAUNDERING-SEMI-TRUE: 'laundering' message present"
515
+ else
516
+ _fail "LAUNDERING-SEMI-TRUE: expected 'laundering' message, got: $outJ"
517
+ fi
518
+
519
+ # ─── Summary ──────────────────────────────────────────────────────────────────
520
+ echo ""
521
+ echo "────────────────────────────────────────────"
522
+ if [[ $errors -eq 0 ]]; then
523
+ echo "test_reconcile_soundness: all checks passed."
524
+ exit 0
525
+ else
526
+ echo "test_reconcile_soundness: $errors check(s) failed."
527
+ exit 1
528
+ fi