@kontourai/flow-agents 2.3.0 → 3.0.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 (301) hide show
  1. package/.github/CODEOWNERS +16 -0
  2. package/.github/workflows/ci.yml +44 -0
  3. package/.github/workflows/trust-reconcile.yml +62 -4
  4. package/AGENTS.md +4 -4
  5. package/CHANGELOG.md +62 -0
  6. package/CONTEXT.md +35 -0
  7. package/README.md +9 -3
  8. package/agents/dev.json +1 -1
  9. package/agents/tool-code-reviewer.json +1 -1
  10. package/agents/tool-planner.json +3 -3
  11. package/agents/tool-verifier.json +3 -3
  12. package/build/src/cli/assignment-provider.d.ts +1 -0
  13. package/build/src/cli/assignment-provider.js +748 -0
  14. package/build/src/cli/console-learning-projection.js +3 -2
  15. package/build/src/cli/effective-assignment-provider-settings.d.ts +1 -0
  16. package/build/src/cli/effective-assignment-provider-settings.js +125 -0
  17. package/build/src/cli/init.js +104 -21
  18. package/build/src/cli/kit.js +37 -6
  19. package/build/src/cli/pull-work-provider.js +1 -1
  20. package/build/src/cli/usage-feedback.js +3 -3
  21. package/build/src/cli/validate-hook-influence.js +1 -0
  22. package/build/src/cli/validate-workflow-artifacts.js +27 -7
  23. package/build/src/cli/workflow-artifact-cleanup-audit.js +36 -2
  24. package/build/src/cli/workflow-sidecar.d.ts +14 -7
  25. package/build/src/cli/workflow-sidecar.js +938 -168
  26. package/build/src/cli.js +6 -0
  27. package/build/src/flow-kit/validate.d.ts +23 -0
  28. package/build/src/flow-kit/validate.js +67 -1
  29. package/build/src/index.d.ts +1 -1
  30. package/build/src/index.js +1 -1
  31. package/build/src/lib/flow-resolver.d.ts +6 -4
  32. package/build/src/lib/flow-resolver.js +125 -58
  33. package/build/src/lib/local-artifact-root.d.ts +14 -2
  34. package/build/src/lib/local-artifact-root.js +22 -5
  35. package/build/src/lib/workflow-learning-projection.js +2 -2
  36. package/build/src/runtime-adapters.d.ts +12 -0
  37. package/build/src/runtime-adapters.js +56 -16
  38. package/build/src/tools/build-universal-bundles.js +49 -16
  39. package/build/src/tools/generate-context-map.js +10 -8
  40. package/build/src/tools/validate-source-tree.js +24 -1
  41. package/console.telemetry.json +22 -0
  42. package/context/contracts/artifact-contract.md +40 -7
  43. package/context/contracts/assignment-provider-contract.md +239 -0
  44. package/context/contracts/builder-kit-workflow-state-contract.md +5 -2
  45. package/context/contracts/decision-registry-contract.md +265 -0
  46. package/context/contracts/delivery-contract.md +3 -1
  47. package/context/contracts/execution-contract.md +25 -0
  48. package/context/contracts/governance-adapter-contract.md +2 -0
  49. package/context/contracts/knowledge-store-contract.md +197 -0
  50. package/context/contracts/planning-contract.md +4 -1
  51. package/context/contracts/review-contract.md +7 -3
  52. package/context/contracts/sandbox-policy.md +2 -0
  53. package/context/contracts/standing-directives.md +13 -0
  54. package/context/contracts/verification-contract.md +11 -1
  55. package/context/contracts/work-item-contract.md +3 -1
  56. package/context/deferred/parallelization.md +1 -1
  57. package/context/gate-awareness.md +4 -4
  58. package/context/scripts/git-status.sh +2 -2
  59. package/context/scripts/hooks/config-protection.js +502 -2
  60. package/context/scripts/hooks/lib/config-protection-remedies.js +69 -0
  61. package/context/scripts/hooks/run-hook.js +14 -1
  62. package/context/scripts/hooks/stop-goal-fit.js +1140 -99
  63. package/context/scripts/hooks/workflow-steering.js +212 -6
  64. package/context/settings/assignment-provider-settings.json +33 -0
  65. package/docs/adr/0007-flow-skill-kit-tool-boundary.md +2 -0
  66. package/docs/adr/0015-flow-flow-agents-boundary-reconciliation.md +35 -0
  67. package/docs/adr/0019-kit-dependency-ownership.md +62 -0
  68. package/docs/adr/0020-trust-reconcile-manifest-and-claim-classification.md +290 -0
  69. package/docs/adr/0021-assignment-leases-and-stale-claim-takeover.md +221 -0
  70. package/docs/adr/0022-fail-closed-delivery-reconciliation-with-governed-exemptions.md +463 -0
  71. package/docs/agent-system-guidebook.md +3 -3
  72. package/docs/agent-usage-feedback-loop.md +6 -6
  73. package/docs/context-map.md +10 -7
  74. package/docs/decisions/decision-records.md +57 -0
  75. package/docs/decisions/index.md +17 -0
  76. package/docs/decisions/knowledge-store-provider.md +51 -0
  77. package/docs/decisions/model-routing.md +63 -0
  78. package/docs/decisions/promotion-gate.md +52 -0
  79. package/docs/decisions/standing-directives.md +66 -0
  80. package/docs/developer-architecture.md +1 -1
  81. package/docs/fixture-ownership.md +4 -0
  82. package/docs/flow-kit-repository-contract.md +5 -5
  83. package/docs/getting-started.md +12 -2
  84. package/docs/integrations/harness-install.md +2 -1
  85. package/docs/kit-authoring-guide.md +64 -5
  86. package/docs/knowledge-kit.md +5 -1
  87. package/docs/learnings/2026-07-improvement-program.md +203 -0
  88. package/docs/migrations.md +6 -5
  89. package/docs/north-star.md +2 -2
  90. package/docs/operating-layers.md +4 -3
  91. package/docs/repository-structure.md +5 -5
  92. package/docs/skills-map.md +8 -8
  93. package/docs/spec/runtime-hook-surface.md +19 -15
  94. package/docs/spikes/graph-provider-2026-07.md +155 -0
  95. package/docs/standards-register.md +7 -7
  96. package/docs/trust-anchor-adoption.md +105 -4
  97. package/docs/work-item-adapters.md +1 -1
  98. package/docs/workflow-artifact-lifecycle.md +72 -9
  99. package/docs/workflow-eval-strategy.md +3 -3
  100. package/docs/workflow-shared-contracts.md +3 -2
  101. package/docs/workflow-usage-guide.md +260 -21
  102. package/evals/acceptance/DEMO-false-completion.md +20 -11
  103. package/evals/acceptance/prove-capture-teeth-declared.sh +15 -15
  104. package/evals/acceptance/prove-capture-teeth.sh +11 -11
  105. package/evals/acceptance/test_kiro_harness.sh +1 -1
  106. package/evals/ci/antigaming-suite.sh +5 -0
  107. package/evals/ci/run-baseline.sh +71 -1
  108. package/evals/fixtures/assignment-provider/actor-a.json +6 -0
  109. package/evals/fixtures/assignment-provider/actor-b.json +6 -0
  110. package/evals/fixtures/assignment-provider/github-issue-claimed.json +27 -0
  111. package/evals/fixtures/assignment-provider/github-issue-unassigned.json +7 -0
  112. package/evals/fixtures/assignment-provider/liveness-fresh.json +9 -0
  113. package/evals/fixtures/assignment-provider/liveness-stale.json +9 -0
  114. package/evals/fixtures/flow-kit-repository/invalid-bad-dependency/flows/review.flow.json +26 -0
  115. package/evals/fixtures/flow-kit-repository/invalid-bad-dependency/kit.json +13 -0
  116. package/evals/fixtures/flow-kit-repository/valid-with-dependency/flows/review.flow.json +26 -0
  117. package/evals/fixtures/flow-kit-repository/valid-with-dependency/kit.json +13 -0
  118. package/evals/fixtures/hook-influence/cases.json +32 -0
  119. package/evals/fixtures/pull-work-provider/github-issues.json +6 -6
  120. package/evals/fixtures/trust-reconcile-exploits/fabricated-attestation.json +59 -0
  121. package/evals/fixtures/trust-reconcile-exploits/no-label-bypass.json +36 -0
  122. package/evals/fixtures/trust-reconcile-exploits/skip-assumed-bypass.json +59 -0
  123. package/evals/fixtures/trust-reconcile-exploits/status-misassertion.json +48 -0
  124. package/evals/fixtures/trust-reconcile-exploits/waived-command-check.json +44 -0
  125. package/evals/fixtures/trust-reconcile-mixed-bundle/mixed-bundle.json +159 -0
  126. package/evals/fixtures/trust-reconcile-ws3/ws3-bundle.json +920 -0
  127. package/evals/integration/test_actor_identity.sh +254 -0
  128. package/evals/integration/test_assignment_provider_github.sh +318 -0
  129. package/evals/integration/test_assignment_provider_local_file.sh +222 -0
  130. package/evals/integration/test_builder_step_producers.sh +33 -23
  131. package/evals/integration/test_bundle_install.sh +22 -16
  132. package/evals/integration/test_bundle_lifecycle.sh +11 -11
  133. package/evals/integration/test_captured_fail_reconciliation.sh +50 -50
  134. package/evals/integration/test_checkpoint_signing.sh +43 -0
  135. package/evals/integration/test_claim_lookup.sh +4 -4
  136. package/evals/integration/test_codex_hook_resolution.sh +114 -0
  137. package/evals/integration/test_command_log_concurrency.sh +8 -8
  138. package/evals/integration/test_command_log_fork_classification.sh +3 -3
  139. package/evals/integration/test_command_log_integrity.sh +16 -16
  140. package/evals/integration/test_console_learning_projection.sh +1 -1
  141. package/evals/integration/test_critique_supersession_roundtrip.sh +182 -0
  142. package/evals/integration/test_dual_emit_flow_step.sh +145 -0
  143. package/evals/integration/test_enforcer_expects_driven.sh +12 -12
  144. package/evals/integration/test_evidence_capture_hook.sh +14 -13
  145. package/evals/integration/test_fixture_retirement_audit.sh +2 -2
  146. package/evals/integration/test_flow_agents_statusline.sh +2 -2
  147. package/evals/integration/test_flow_kit_install_git.sh +52 -0
  148. package/evals/integration/test_flow_kit_repository.sh +2 -0
  149. package/evals/integration/test_flowdef_session_activation.sh +6 -6
  150. package/evals/integration/test_flowdef_union_floor_regression.sh +429 -0
  151. package/evals/integration/test_gate_bypass_chain.sh +52 -40
  152. package/evals/integration/test_gate_lockdown.sh +97 -73
  153. package/evals/integration/test_gate_review_inquiry_records.sh +3 -3
  154. package/evals/integration/test_goal_fit_escape_hatch.sh +10 -10
  155. package/evals/integration/test_goal_fit_ghost_session.sh +104 -0
  156. package/evals/integration/test_goal_fit_hook.sh +32 -32
  157. package/evals/integration/test_goal_fit_rederive.sh +9 -9
  158. package/evals/integration/test_hook_category_behaviors.sh +103 -0
  159. package/evals/integration/test_hook_influence_cases.sh +1 -0
  160. package/evals/integration/test_install_merge.sh +262 -1
  161. package/evals/integration/test_kit_identity_trust.sh +6 -6
  162. package/evals/integration/test_liveness_conflict_injection.sh +587 -0
  163. package/evals/integration/test_liveness_heartbeat.sh +677 -0
  164. package/evals/integration/test_liveness_verdict.sh +394 -0
  165. package/evals/integration/test_local_flow_kit_install.sh +15 -0
  166. package/evals/integration/test_phase_map_and_gate_claim.sh +63 -11
  167. package/evals/integration/test_promote_gate.sh +178 -0
  168. package/evals/integration/test_publish_delivery.sh +23 -6
  169. package/evals/integration/test_pull_work_assignment_join.sh +132 -0
  170. package/evals/integration/test_pull_work_liveness_preflight.sh +394 -0
  171. package/evals/integration/test_pull_work_provider.sh +2 -2
  172. package/evals/integration/test_reconcile_soundness.sh +37 -13
  173. package/evals/integration/test_resolvefirststep_security.sh +22 -0
  174. package/evals/integration/test_runtime_adapter_activation.sh +87 -18
  175. package/evals/integration/test_session_resume_roundtrip.sh +147 -0
  176. package/evals/integration/test_sidecar_field_preservation.sh +249 -0
  177. package/evals/integration/test_trust_reconcile.sh +11 -10
  178. package/evals/integration/test_trust_reconcile_manifest.sh +79 -0
  179. package/evals/integration/test_trust_reconcile_mixed_bundle.sh +96 -0
  180. package/evals/integration/test_trust_reconcile_negatives.sh +759 -0
  181. package/evals/integration/test_usage_feedback_global.sh +5 -5
  182. package/evals/integration/test_validate_artifacts_portability.sh +60 -0
  183. package/evals/integration/test_verify_cli.sh +2 -2
  184. package/evals/integration/test_veritas_governance_kit.sh +117 -0
  185. package/evals/integration/test_workflow_artifact_cleanup_audit.sh +26 -1
  186. package/evals/integration/test_workflow_artifacts.sh +33 -0
  187. package/evals/integration/test_workflow_sidecar_writer.sh +802 -74
  188. package/evals/integration/test_workflow_steering_hook.sh +175 -5
  189. package/evals/lib/codex-provider.sh +1 -1
  190. package/evals/lib/node.sh +19 -1
  191. package/evals/run.sh +32 -0
  192. package/evals/static/test_decisions.sh +150 -0
  193. package/evals/static/test_flowdef_codeowners_coverage.sh +62 -0
  194. package/evals/static/test_knowledge_providers.sh +23 -0
  195. package/evals/static/test_package.sh +19 -10
  196. package/evals/static/test_universal_bundles.sh +48 -3
  197. package/evals/static/test_workflow_skills.sh +41 -8
  198. package/kits/builder/flows/build.flow.json +3 -74
  199. package/kits/builder/flows/publish-learn.flow.json +90 -0
  200. package/kits/builder/kit.json +11 -0
  201. package/kits/builder/skills/builder-shape/SKILL.md +1 -1
  202. package/kits/builder/skills/deliver/SKILL.md +32 -11
  203. package/kits/builder/skills/design-probe/SKILL.md +4 -4
  204. package/kits/builder/skills/evidence-gate/SKILL.md +26 -1
  205. package/kits/builder/skills/execute-plan/SKILL.md +2 -2
  206. package/kits/builder/skills/fix-bug/SKILL.md +2 -0
  207. package/kits/builder/skills/gate-review/SKILL.md +6 -6
  208. package/kits/builder/skills/idea-to-backlog/SKILL.md +1 -1
  209. package/kits/builder/skills/learning-review/SKILL.md +7 -7
  210. package/kits/builder/skills/pickup-probe/SKILL.md +5 -4
  211. package/kits/builder/skills/plan-work/SKILL.md +27 -5
  212. package/kits/builder/skills/pull-work/SKILL.md +148 -5
  213. package/kits/builder/skills/release-readiness/SKILL.md +4 -4
  214. package/kits/builder/skills/review-work/SKILL.md +24 -1
  215. package/kits/builder/skills/tdd-workflow/SKILL.md +2 -0
  216. package/kits/builder/skills/verify-work/SKILL.md +8 -1
  217. package/kits/catalog.json +6 -0
  218. package/kits/knowledge/kit.json +35 -0
  219. package/kits/knowledge/providers/conformance/fixtures/git-repo/CONTEXT.md +12 -0
  220. package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/old-sprocket-shape.md +13 -0
  221. package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/sprocket-shape.md +14 -0
  222. package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/widget-format.md +14 -0
  223. package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/learnings/fixture-learning.md +7 -0
  224. package/kits/knowledge/providers/conformance/fixtures/work-item/issues.json +30 -0
  225. package/kits/knowledge/providers/conformance/suite.test.js +125 -0
  226. package/kits/knowledge/providers/git-repo/index.js +236 -0
  227. package/kits/knowledge/providers/health/health-pass.test.js +99 -0
  228. package/kits/knowledge/providers/health/index.js +153 -0
  229. package/kits/knowledge/providers/index.js +24 -0
  230. package/kits/knowledge/providers/lib/model.js +91 -0
  231. package/kits/knowledge/providers/lib/schema-validate.js +119 -0
  232. package/kits/knowledge/providers/markdown-vault/index.js +169 -0
  233. package/kits/knowledge/providers/work-item/index.js +204 -0
  234. package/kits/veritas-governance/adapter/readiness-to-trust-bundle.mjs +178 -0
  235. package/kits/veritas-governance/docs/README.md +75 -0
  236. package/kits/veritas-governance/fixtures/readiness/not-ready.readiness-report.json +4645 -0
  237. package/kits/veritas-governance/fixtures/readiness/ready.readiness-report.json +4403 -0
  238. package/kits/veritas-governance/flows/readiness-check.flow.json +35 -0
  239. package/kits/veritas-governance/kit.json +16 -0
  240. package/package.json +11 -5
  241. package/packaging/README.md +2 -2
  242. package/packaging/conformance/fixtures/config-protection--allow-read-state-json.json +20 -0
  243. package/packaging/conformance/fixtures/config-protection--block-write-state-json.json +20 -0
  244. package/packaging/conformance/fixtures/stop-goal-fit--block-bundle-disputed-claim.json +1 -1
  245. package/packaging/conformance/fixtures/stop-goal-fit--block-capture-contradicts-claimed-pass.json +1 -1
  246. package/packaging/manifest.json +9 -10
  247. package/schemas/assignment-provider-settings.schema.json +125 -0
  248. package/schemas/decision-record.schema.json +109 -0
  249. package/schemas/knowledge/edge.schema.json +54 -0
  250. package/schemas/knowledge/health-report.schema.json +45 -0
  251. package/schemas/knowledge/node.schema.json +49 -0
  252. package/schemas/knowledge/proposal.schema.json +53 -0
  253. package/schemas/workflow-evidence.schema.json +1 -1
  254. package/schemas/workflow-state.schema.json +5 -0
  255. package/scripts/README.md +8 -3
  256. package/scripts/check-content-boundary.cjs +1 -1
  257. package/scripts/check-decisions.cjs +356 -0
  258. package/scripts/ci/derive-claim-status.mjs +74 -0
  259. package/scripts/ci/trust-reconcile.js +957 -103
  260. package/scripts/git-status.sh +2 -2
  261. package/scripts/hooks/claude-telemetry-hook.js +37 -3
  262. package/scripts/hooks/codex-telemetry-hook.js +36 -2
  263. package/scripts/hooks/config-protection.js +102 -30
  264. package/scripts/hooks/evidence-capture.js +3 -3
  265. package/scripts/hooks/lib/actor-identity.js +368 -0
  266. package/scripts/hooks/lib/config-protection-remedies.js +69 -0
  267. package/scripts/hooks/lib/liveness-heartbeat.js +361 -0
  268. package/scripts/hooks/lib/liveness-policy.js +127 -0
  269. package/scripts/hooks/lib/liveness-read.js +105 -12
  270. package/scripts/hooks/lib/liveness-write.js +46 -0
  271. package/scripts/hooks/lib/local-artifact-paths.js +12 -6
  272. package/scripts/hooks/opencode-telemetry-hook.js +27 -1
  273. package/scripts/hooks/pi-telemetry-hook.js +27 -1
  274. package/scripts/hooks/stop-goal-fit.js +157 -26
  275. package/scripts/hooks/workflow-steering.js +77 -3
  276. package/scripts/install-codex-home.sh +138 -38
  277. package/scripts/statusline/flow-agents-statusline.js +2 -2
  278. package/src/cli/assignment-provider.ts +845 -0
  279. package/src/cli/console-learning-projection.ts +3 -2
  280. package/src/cli/effective-assignment-provider-settings.ts +112 -0
  281. package/src/cli/init.ts +101 -21
  282. package/src/cli/kit.ts +37 -6
  283. package/src/cli/public-api.test.mjs +58 -6
  284. package/src/cli/pull-work-provider.ts +1 -1
  285. package/src/cli/trust-bundle-policy-order.test.mjs +87 -0
  286. package/src/cli/usage-feedback.ts +3 -3
  287. package/src/cli/validate-hook-influence.ts +1 -0
  288. package/src/cli/validate-workflow-artifacts.ts +27 -7
  289. package/src/cli/veritas-readiness-adapter.test.mjs +267 -0
  290. package/src/cli/workflow-artifact-cleanup-audit.ts +35 -2
  291. package/src/cli/workflow-sidecar.ts +933 -168
  292. package/src/cli.ts +6 -0
  293. package/src/flow-kit/validate.ts +74 -1
  294. package/src/index.ts +6 -2
  295. package/src/lib/flow-resolver.ts +123 -55
  296. package/src/lib/local-artifact-root.ts +24 -5
  297. package/src/lib/workflow-learning-projection.ts +2 -2
  298. package/src/runtime-adapters.ts +61 -15
  299. package/src/tools/build-universal-bundles.ts +49 -16
  300. package/src/tools/generate-context-map.ts +10 -8
  301. package/src/tools/validate-source-tree.ts +24 -1
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env bash
2
+ # test_promote_gate.sh - promote-then-archive gate (issue #312).
3
+ #
4
+ # Durable-residue extraction is the archival act. Covers:
5
+ # 1. promote writes a well-formed, session-local promotion claim referencing
6
+ # existing durable doc paths (metadata.promotion + policy_rule evidence, no
7
+ # execution.label) and an auditable promotion.json.
8
+ # 2. promote REJECTS a nonexistent --evidence-path (fail loud).
9
+ # 3. promote --none --reason records an explicit no-residue claim; --none without
10
+ # --reason is refused.
11
+ # 4. cleanup-audit classification: a delivered/accepted session WITHOUT a promotion
12
+ # claim is a cleanup_candidate (blocked from archive) with a remedy naming the
13
+ # promote step; WITH a real claim OR a --none claim it stays terminal_done (AC1-3).
14
+ # 5. CRITICAL: trust-reconcile stays exit 0 on a bundle carrying a promotion claim -
15
+ # the claim classifies session-local (ATTESTED), never an unbacked/not-run command
16
+ # divergence (proves R1 reconcile-safety, no new manifest entry required).
17
+ #
18
+ # Deterministic, no model spend, self-cleaning.
19
+ # Usage: bash evals/integration/test_promote_gate.sh
20
+ set -uo pipefail
21
+
22
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
23
+ source "$ROOT/evals/lib/node.sh"
24
+
25
+ TMP="$(mktemp -d)"
26
+ trap 'rm -rf "$TMP"' EXIT
27
+ unset FORCE_COLOR # known local artifact that can perturb child-process output
28
+
29
+ errors=0
30
+ pass() { echo " PASS: $1"; }
31
+ fail() { echo " FAIL: $1"; errors=$((errors + 1)); }
32
+
33
+ echo "=== Promote-then-archive gate (#312) ==="
34
+
35
+ # JSON reader helpers (dedicated files; no inline interpreter flags).
36
+ cat > "$TMP/inspect.cjs" <<'JS'
37
+ const fs = require("fs");
38
+ const b = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
39
+ const claim = (b.claims || []).find((c) => c.metadata && c.metadata.promotion);
40
+ const ev = claim && (b.evidence || []).find((e) => e.claimId === claim.id);
41
+ const out = {
42
+ found: String(!!claim),
43
+ status: (claim && claim.status) || "",
44
+ targets: (claim && claim.metadata.promotion.targets) || [],
45
+ none: String(claim ? claim.metadata.promotion.none : ""),
46
+ evidenceType: (ev && ev.evidenceType) || "",
47
+ hasExecutionLabel: String(!!(ev && ev.execution && ev.execution.label)),
48
+ };
49
+ fs.writeFileSync(process.argv[3], JSON.stringify(out));
50
+ JS
51
+ cat > "$TMP/field.cjs" <<'JS'
52
+ const fs = require("fs");
53
+ const o = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
54
+ let v = o;
55
+ for (const k of process.argv[3].split(".")) v = v[k];
56
+ console.log(Array.isArray(v) ? v.join(",") : v);
57
+ JS
58
+ cat > "$TMP/bucketof.cjs" <<'JS'
59
+ const fs = require("fs");
60
+ const d = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
61
+ for (const [b, arr] of Object.entries(d.buckets)) {
62
+ for (const it of arr) if (it.slug === process.argv[3]) { console.log(b + "|" + (it.reasons[0] || "")); process.exit(0); }
63
+ }
64
+ console.log("MISSING|");
65
+ JS
66
+
67
+ REPO="$TMP/repo"
68
+ mkdir -p "$REPO/kits" "$REPO/docs/decisions" "$REPO/docs/learnings"
69
+ printf 'x\n' > "$REPO/docs/decisions/promotion-gate.md"
70
+ printf 'y\n' > "$REPO/docs/learnings/note.md"
71
+ SESSION_ROOT="$REPO/.kontourai/flow-agents"
72
+
73
+ ensure() {
74
+ flow_agents_node workflow-sidecar ensure-session \
75
+ --artifact-root "$SESSION_ROOT" --task-slug "$1" \
76
+ --source-request "r" --title "T" --summary "s" \
77
+ --criterion "c1" --next-action "n" --timestamp "2026-07-02T00:00:00Z" \
78
+ >/dev/null 2>"$TMP/$1.ensure.err"
79
+ }
80
+ seed_evidence() {
81
+ flow_agents_node workflow-sidecar record-evidence "$1" --verdict pass \
82
+ --check-json '{"id":"docs-check","kind":"policy","status":"pass","summary":"docs reviewed"}' \
83
+ --timestamp "2026-07-02T00:01:00Z" >/dev/null 2>"$TMP/ev.err"
84
+ }
85
+ term_state() {
86
+ printf '{"schema_version":"1.0","task_slug":"%s","status":"accepted","phase":"done","updated_at":"2026-07-02T00:00:00Z","next_action":{"status":"done","summary":"done"}}' "$2" > "$1/state.json"
87
+ }
88
+ field() { node "$TMP/field.cjs" "$1" "$2"; }
89
+
90
+ # --- 1. promote writes a well-formed session-local claim ---------------------
91
+ ensure promoted
92
+ PDIR="$SESSION_ROOT/promoted"
93
+ seed_evidence "$PDIR"
94
+ if flow_agents_node workflow-sidecar promote "$PDIR" --repo-root "$REPO" \
95
+ --evidence-path docs/decisions/promotion-gate.md \
96
+ --evidence-path docs/learnings/note.md \
97
+ --timestamp "2026-07-02T00:02:00Z" >"$TMP/promote.out" 2>"$TMP/promote.err"; then
98
+ pass "promote with existing paths exits 0"
99
+ else
100
+ fail "promote with existing paths should exit 0: $(cat "$TMP/promote.err")"
101
+ fi
102
+ [[ -f "$PDIR/promotion.json" ]] && pass "promote writes auditable promotion.json" || fail "promote writes auditable promotion.json"
103
+
104
+ node "$TMP/inspect.cjs" "$PDIR/trust.bundle" "$TMP/shape.json"
105
+ [[ "$(field "$TMP/shape.json" found)" == "true" ]] && pass "bundle carries a promotion claim (metadata.promotion)" || fail "bundle carries a promotion claim"
106
+ [[ "$(field "$TMP/shape.json" status)" == "verified" ]] && pass "promotion claim derives status verified" || fail "promotion claim derives status verified (got $(field "$TMP/shape.json" status))"
107
+ [[ "$(field "$TMP/shape.json" evidenceType)" == "policy_rule" ]] && pass "promotion evidence is session-local policy_rule" || fail "promotion evidence is session-local policy_rule (got $(field "$TMP/shape.json" evidenceType))"
108
+ [[ "$(field "$TMP/shape.json" hasExecutionLabel)" == "false" ]] && pass "promotion claim carries no execution.label (never a manifest command)" || fail "promotion claim must not carry an execution.label"
109
+ [[ "$(field "$TMP/shape.json" targets)" == "docs/decisions/promotion-gate.md,docs/learnings/note.md" ]] && pass "promotion evidence refs = the durable doc paths written" || fail "promotion targets wrong: $(field "$TMP/shape.json" targets)"
110
+
111
+ # --- 2. promote rejects a nonexistent path -----------------------------------
112
+ if flow_agents_node workflow-sidecar promote "$PDIR" --repo-root "$REPO" \
113
+ --evidence-path docs/decisions/does-not-exist.md \
114
+ --timestamp "2026-07-02T00:03:00Z" >"$TMP/bad.out" 2>"$TMP/bad.err"; then
115
+ fail "promote should reject a nonexistent evidence path"
116
+ else
117
+ grep -q "does not exist on disk" "$TMP/bad.err" && pass "promote rejects a nonexistent evidence path (fail loud)" || fail "promote rejection message unclear: $(cat "$TMP/bad.err")"
118
+ fi
119
+
120
+ # --- 3. explicit no-residue promotion ----------------------------------------
121
+ ensure none-promoted
122
+ NDIR="$SESSION_ROOT/none-promoted"
123
+ seed_evidence "$NDIR"
124
+ if flow_agents_node workflow-sidecar promote "$NDIR" --repo-root "$REPO" \
125
+ --none --reason "no durable residue: pure refactor" \
126
+ --timestamp "2026-07-02T00:04:00Z" >"$TMP/none.out" 2>"$TMP/none.err"; then
127
+ pass "promote --none --reason exits 0"
128
+ else
129
+ fail "promote --none --reason should exit 0: $(cat "$TMP/none.err")"
130
+ fi
131
+ node "$TMP/inspect.cjs" "$NDIR/trust.bundle" "$TMP/noneshape.json"
132
+ [[ "$(field "$TMP/noneshape.json" none)" == "true" ]] && pass "no-residue claim records none=true" || fail "no-residue claim should record none=true"
133
+
134
+ if flow_agents_node workflow-sidecar promote "$NDIR" --none \
135
+ --timestamp "2026-07-02T00:05:00Z" >"$TMP/none2.out" 2>"$TMP/none2.err"; then
136
+ fail "promote --none without --reason should be refused"
137
+ else
138
+ grep -q "requires --reason" "$TMP/none2.err" && pass "promote --none without --reason is refused" || fail "promote --none reason-required message unclear"
139
+ fi
140
+
141
+ # --- 4. cleanup-audit classification with/without the claim ------------------
142
+ AUDIT_ROOT="$TMP/audit-root"
143
+ mkdir -p "$AUDIT_ROOT/promoted" "$AUDIT_ROOT/none-promoted" "$AUDIT_ROOT/unpromoted"
144
+ term_state "$AUDIT_ROOT/promoted" promoted
145
+ term_state "$AUDIT_ROOT/none-promoted" none-promoted
146
+ term_state "$AUDIT_ROOT/unpromoted" unpromoted
147
+ cp "$PDIR/trust.bundle" "$AUDIT_ROOT/promoted/trust.bundle"
148
+ cp "$NDIR/trust.bundle" "$AUDIT_ROOT/none-promoted/trust.bundle"
149
+ # unpromoted: deliberately no trust.bundle
150
+
151
+ flow_agents_node workflow-artifact-cleanup-audit --artifact-root "$AUDIT_ROOT" --json > "$TMP/audit.json" 2>&1
152
+ bucketof() { node "$TMP/bucketof.cjs" "$TMP/audit.json" "$1"; }
153
+ [[ "$(bucketof promoted | cut -d'|' -f1)" == "terminal_done" ]] && pass "promoted session stays terminal_done" || fail "promoted session should be terminal_done (got $(bucketof promoted))"
154
+ [[ "$(bucketof none-promoted | cut -d'|' -f1)" == "terminal_done" ]] && pass "no-residue promoted session stays terminal_done" || fail "no-residue session should be terminal_done (got $(bucketof none-promoted))"
155
+ [[ "$(bucketof unpromoted | cut -d'|' -f1)" == "cleanup_candidate" ]] && pass "unpromoted delivered session is a cleanup_candidate (archive blocked)" || fail "unpromoted session should be cleanup_candidate (got $(bucketof unpromoted))"
156
+ bucketof unpromoted | grep -q "promote" && pass "cleanup_candidate reason names the promote remedy" || fail "cleanup_candidate reason should name the promote step"
157
+
158
+ # --- 5. CRITICAL: trust-reconcile exit 0 on a bundle with a promotion claim --
159
+ if node "$ROOT/scripts/ci/trust-reconcile.js" --bundle "$PDIR/trust.bundle" \
160
+ --commands "true" --repo-root "$REPO" >"$TMP/reconcile.out" 2>&1; then
161
+ pass "trust-reconcile exits 0 on a bundle carrying a promotion claim"
162
+ else
163
+ fail "trust-reconcile should exit 0 on a promotion-claim bundle: $(tail -5 "$TMP/reconcile.out")"
164
+ fi
165
+ if grep -q "ATTESTED" "$TMP/reconcile.out" && ! grep -q "trust divergence" "$TMP/reconcile.out"; then
166
+ pass "promotion claim classifies session-local (ATTESTED), never an unbacked/not-run command"
167
+ else
168
+ fail "promotion claim must classify session-local, not as a command divergence"
169
+ grep -iE "classified|ATTESTED|trust divergence" "$TMP/reconcile.out"
170
+ fi
171
+
172
+ echo ""
173
+ if [[ "$errors" -eq 0 ]]; then
174
+ echo "Promote-then-archive gate checks passed"
175
+ else
176
+ echo "Promote-then-archive gate checks failed: $errors"
177
+ exit 1
178
+ fi
@@ -4,8 +4,10 @@
4
4
  # Proves that:
5
5
  # 1. END-TO-END-RECORD-RELEASE: record-release auto-publishes trust.bundle.
6
6
  # 2. SUBCOMMAND: publish-delivery subcommand copies bundle to delivery/.
7
- # 3. RECONCILE-DIVERGENCE: delivery trust.bundle + CI fail -> exit 1.
8
- # 4. RECONCILE-MATCHING: delivery trust.bundle + CI pass -> exit 0.
7
+ # 3. RECONCILE-DIVERGENCE: delivery trust.bundle (+ matching-sha checkpoint sibling, ADR
8
+ # 0022 addendum part 2 bundle-ownership binding) + CI fail -> exit 1.
9
+ # 4. RECONCILE-MATCHING: delivery trust.bundle (+ matching-sha checkpoint sibling) + CI
10
+ # pass -> exit 0.
9
11
  # 5. FAIL-SOFT: no trust.bundle -> publishDelivery skips, record-release exits 0.
10
12
  #
11
13
  # Deterministic, no model spend, self-cleaning.
@@ -41,10 +43,10 @@ code_lines = [
41
43
  "const fs = require('fs');",
42
44
  "const [,, dest, label, passingStr] = process.argv;",
43
45
  "const passing = passingStr === 'true';",
44
- "const b = { schemaVersion: 3, source: 'test-fixture',",
46
+ "const b = { schemaVersion: 5, source: 'test-fixture',",
45
47
  " claims: [{ id: 'c1', claimType: 'workflow.check.build',",
46
48
  " value: passing ? 'pass' : 'fail', status: passing ? 'verified' : 'disputed',",
47
- " subjectId: 'ts/build', surface: 'flow-agents.workflow',",
49
+ " subjectId: 'ts/build', facet: 'flow-agents.workflow',",
48
50
  " subjectType: 'workflow-check', fieldOrBehavior: 'build',",
49
51
  " createdAt: '2026-06-27T00:00:00Z', updatedAt: '2026-06-27T00:00:00Z',",
50
52
  " impactLevel: 'high', verificationPolicyId: 'policy:wf.build' }],",
@@ -63,6 +65,19 @@ PY
63
65
  node "$helper" "$dest" "$label" "$passing"
64
66
  }
65
67
 
68
+ # write_checkpoint_to <delivery_dir> <sha>
69
+ # Writes a minimal, well-formed trust.checkpoint.json naming <sha> as commit_sha, so a
70
+ # bare fixture trust.bundle written directly into delivery/ (TEST 3/4 below -- these do
71
+ # NOT go through the real seal-checkpoint pipeline) still carries the commit-identity
72
+ # binding trust-reconcile.js's bundle-ownership staleness check now requires (ADR 0022
73
+ # addendum, part 2) for an auto-discovered bundle to be treated as owned by the change
74
+ # under test, not stale.
75
+ write_checkpoint_to() {
76
+ local delivery_dir="$1" sha="$2"
77
+ printf '{"schema_version":"1.0","slug":"publish-delivery-fixture","work_item":null,"status":"delivered","phase":"release","sealed_at":"2026-06-27T00:00:00Z","commit_sha":"%s","checkpoint":{"asOf":"2026-06-27T00:00:00.000Z","statusByClaimId":{}}}' \
78
+ "$sha" > "$delivery_dir/trust.checkpoint.json"
79
+ }
80
+
66
81
  # Session setup helper
67
82
  setup_session() {
68
83
  local aroot="$1" slug="$2" bundle_src="$3"
@@ -181,8 +196,9 @@ mkdir -p "$REPO3/delivery"
181
196
  # -> claimed cmd not in canonical set -> not-run divergence, AND canonical fails
182
197
  DELIVERY3="$REPO3/delivery/trust.bundle"
183
198
  write_bundle_to "$DELIVERY3" "node --version" "true"
199
+ write_checkpoint_to "$REPO3/delivery" "1111111111111111111111111111111111111111"
184
200
 
185
- recon3_out=$(TRUST_RECONCILE_COMMANDS="false" \
201
+ recon3_out=$(TRUST_RECONCILE_SHA="1111111111111111111111111111111111111111" TRUST_RECONCILE_COMMANDS="false" \
186
202
  node "$RECONCILE" --repo-root "$REPO3" 2>&1)
187
203
  recon3_exit=$?
188
204
 
@@ -208,8 +224,9 @@ mkdir -p "$REPO4/delivery"
208
224
  # Bundle claims "node --version" passed; canonical verify is ALSO "node --version" (passes)
209
225
  DELIVERY4="$REPO4/delivery/trust.bundle"
210
226
  write_bundle_to "$DELIVERY4" "node --version" "true"
227
+ write_checkpoint_to "$REPO4/delivery" "2222222222222222222222222222222222222222"
211
228
 
212
- recon4_out=$(TRUST_RECONCILE_COMMANDS="node --version" \
229
+ recon4_out=$(TRUST_RECONCILE_SHA="2222222222222222222222222222222222222222" TRUST_RECONCILE_COMMANDS="node --version" \
213
230
  node "$RECONCILE" --repo-root "$REPO4" 2>&1)
214
231
  recon4_exit=$?
215
232
 
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env bash
2
+ # test_pull_work_assignment_join.sh — Two-session disjoint-selection simulation (#290).
3
+ #
4
+ # HONESTY NOTE (plan Design Decision / Stop-Short Risks): this eval simulates the assignment ⋈
5
+ # liveness join's exclusion property with two actor structs evaluating the same candidate
6
+ # subject id fixture in sequence, inside ONE process. It does NOT stand up two real concurrent
7
+ # agent runtimes, and does not prove true concurrency (a real race between two simultaneous
8
+ # writers). It is the closest *local* proof available for the issue's literal acceptance line
9
+ # ("two sessions running pull-work against the same backlog select disjoint issues") without a
10
+ # multi-runtime test harness this repo does not have — see the plan's Stop-Short Risks section.
11
+ # What IS proven here, deterministically: once session A's assignment claim is recorded (local-
12
+ # file or a rendered-then-status-confirmed GitHub fixture), session B's subsequent
13
+ # `assignment-provider status` / join check for the SAME subject reports it `held` (excluded),
14
+ # never `free` — so `pull-work`'s selection loop (kits/builder/skills/pull-work/SKILL.md,
15
+ # "### 1. Read Board State") would skip it for session B.
16
+ #
17
+ # Supports AC12 (disjoint-selection-simulated), AC10 (docs pointer — see
18
+ # docs/workflow-usage-guide.md's new Assignment Ownership subsection).
19
+ set -uo pipefail
20
+
21
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
22
+ CLI="$ROOT/build/src/cli.js"
23
+ FIXTURES="$ROOT/evals/fixtures/assignment-provider"
24
+ ACTOR_A="$FIXTURES/actor-a.json"
25
+ ACTOR_B="$FIXTURES/actor-b.json"
26
+ ISSUE_CLAIMED="$FIXTURES/github-issue-claimed.json"
27
+ LIVENESS_FRESH="$FIXTURES/liveness-fresh.json"
28
+
29
+ TMPDIR_EVAL="$(mktemp -d)"
30
+ trap 'rm -rf "$TMPDIR_EVAL"' EXIT
31
+ ARTIFACT_ROOT="$TMPDIR_EVAL/artifact-root"
32
+
33
+ errors=0
34
+ pass() { echo " ✓ $1"; }
35
+ fail() { echo " ✗ $1"; errors=$((errors + 1)); }
36
+
37
+ json_query() {
38
+ node -e 'const fs=require("fs"); let cur=JSON.parse(fs.readFileSync(process.argv[1],"utf8")); for (const part of process.argv[2].split(".")) cur=part==="length" ? cur.length : (Array.isArray(cur) ? cur[Number(part)] : cur[part]); console.log(cur);' "$1" "$2"
39
+ }
40
+
41
+ if [[ ! -f "$CLI" ]]; then
42
+ echo "build/src/cli.js not found — run 'npm run build' first" >&2
43
+ exit 1
44
+ fi
45
+
46
+ echo "=== Two-session disjoint-selection simulation (assignment ⋈ liveness join) ==="
47
+
48
+ EMPTY_LIVENESS="$TMPDIR_EVAL/liveness-empty.json"
49
+ echo '[]' > "$EMPTY_LIVENESS"
50
+
51
+ # --- local-file leg: session A claims, session B's join check on the same subject is excluded ---
52
+
53
+ SUBJECT_ID="kontourai/flow-agents#9301"
54
+
55
+ # "Session A" (actor A) evaluates the candidate subject before anyone has claimed it: free.
56
+ node "$CLI" assignment-provider status \
57
+ --provider local-file --artifact-root "$ARTIFACT_ROOT" --subject-id "$SUBJECT_ID" \
58
+ --self-actor "claude-code:eval-actor-a-session:eval-host" --liveness-events-json "$EMPTY_LIVENESS" \
59
+ > "$TMPDIR_EVAL/session-a-preclaim.json"
60
+ [[ "$(json_query "$TMPDIR_EVAL/session-a-preclaim.json" "effective.effective_state")" == "free" ]] && pass "local-file: candidate subject is free before either session claims" || fail "local-file: candidate subject is free before either session claims"
61
+
62
+ # Session A selects and claims it (pull-work's "Assignment Claim On Selection" step).
63
+ node "$CLI" assignment-provider claim \
64
+ --provider local-file --artifact-root "$ARTIFACT_ROOT" --subject-id "$SUBJECT_ID" \
65
+ --actor-json "$ACTOR_A" --branch "agent/claude-code-eval-actor-a-session-eval-host/flow-agents-9301" \
66
+ --artifact-dir ".kontourai/flow-agents/flow-agents-9301" > /dev/null
67
+ status=$?
68
+ [[ "$status" -eq 0 ]] && pass "local-file: session A's claim on the candidate subject succeeds" || fail "local-file: session A's claim on the candidate subject succeeds"
69
+
70
+ # "Session B" (actor B, a distinct actor struct — the second concurrent pull-work session)
71
+ # evaluates the SAME candidate subject next. Its own liveness heartbeat is fresh (it is alive),
72
+ # but that must not matter: the join is computed against the HOLDER's freshness, not the
73
+ # reader's — session B must see the subject excluded.
74
+ NOW_ISO="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
75
+ cat > "$TMPDIR_EVAL/liveness-actor-a-fresh.json" <<JSON
76
+ [
77
+ {"type":"claim","subjectId":"$SUBJECT_ID","actor":"claude-code:eval-actor-a-session:eval-host","at":"$NOW_ISO","ttlSeconds":1800}
78
+ ]
79
+ JSON
80
+ node "$CLI" assignment-provider status \
81
+ --provider local-file --artifact-root "$ARTIFACT_ROOT" --subject-id "$SUBJECT_ID" \
82
+ --self-actor "claude-code:eval-actor-b-session:eval-host" \
83
+ --liveness-events-json "$TMPDIR_EVAL/liveness-actor-a-fresh.json" > "$TMPDIR_EVAL/session-b-postclaim.json"
84
+ [[ "$(json_query "$TMPDIR_EVAL/session-b-postclaim.json" "assignment.assignee")" == "claude-code:eval-actor-a-session:eval-host" ]] && pass "local-file: session B's status read shows actor A as the holder" || fail "local-file: session B's status read shows actor A as the holder"
85
+ [[ "$(json_query "$TMPDIR_EVAL/session-b-postclaim.json" "effective.effective_state")" == "held" ]] && pass "local-file: session B's join check reports held (excluded) for the same subject session A just claimed" || fail "local-file: session B's join check reports held (excluded) for the same subject session A just claimed"
86
+ [[ "$(json_query "$TMPDIR_EVAL/session-b-postclaim.json" "effective.effective_state")" != "free" ]] && pass "local-file: subject is definitively NOT free for session B (disjoint selection holds)" || fail "local-file: subject is definitively NOT free for session B (disjoint selection holds)"
87
+
88
+ # Session B, correctly excluding the held subject, selects a DIFFERENT subject instead — the
89
+ # literal "select disjoint issues" property, one selection loop iteration at a time.
90
+ SUBJECT_ID_2="kontourai/flow-agents#9302"
91
+ node "$CLI" assignment-provider status \
92
+ --provider local-file --artifact-root "$ARTIFACT_ROOT" --subject-id "$SUBJECT_ID_2" \
93
+ --self-actor "claude-code:eval-actor-b-session:eval-host" --liveness-events-json "$EMPTY_LIVENESS" \
94
+ > "$TMPDIR_EVAL/session-b-alternate.json"
95
+ [[ "$(json_query "$TMPDIR_EVAL/session-b-alternate.json" "effective.effective_state")" == "free" ]] && pass "local-file: session B's alternate candidate is free — it can select a disjoint subject" || fail "local-file: session B's alternate candidate is free — it can select a disjoint subject"
96
+ node "$CLI" assignment-provider claim \
97
+ --provider local-file --artifact-root "$ARTIFACT_ROOT" --subject-id "$SUBJECT_ID_2" \
98
+ --actor-json "$ACTOR_B" --branch "agent/claude-code-eval-actor-b-session-eval-host/flow-agents-9302" \
99
+ --artifact-dir ".kontourai/flow-agents/flow-agents-9302" > /dev/null
100
+ [[ $? -eq 0 ]] && pass "local-file: session B claims the disjoint subject successfully" || fail "local-file: session B claims the disjoint subject successfully"
101
+
102
+ # Final proof: the two sessions' claimed subject sets are disjoint.
103
+ node "$CLI" assignment-provider list --provider local-file --artifact-root "$ARTIFACT_ROOT" --actor-json "$ACTOR_A" > "$TMPDIR_EVAL/list-a.json"
104
+ node "$CLI" assignment-provider list --provider local-file --artifact-root "$ARTIFACT_ROOT" --actor-json "$ACTOR_B" > "$TMPDIR_EVAL/list-b.json"
105
+ DISJOINT="$(node -e '
106
+ const fs = require("fs");
107
+ const a = JSON.parse(fs.readFileSync(process.argv[1], "utf8")).subject_ids;
108
+ const b = JSON.parse(fs.readFileSync(process.argv[2], "utf8")).subject_ids;
109
+ const overlap = a.filter((id) => b.includes(id));
110
+ console.log(overlap.length === 0 && a.length === 1 && b.length === 1 ? "yes" : "no");
111
+ ' "$TMPDIR_EVAL/list-a.json" "$TMPDIR_EVAL/list-b.json")"
112
+ [[ "$DISJOINT" == "yes" ]] && pass "session A and session B's claimed subject sets are disjoint (one each, no overlap)" || fail "session A and session B's claimed subject sets are disjoint (one each, no overlap)"
113
+
114
+ # --- GitHub leg: a rendered-and-status-confirmed claim on a fixture also excludes a second reader ---
115
+
116
+ # The already-claimed GitHub fixture stands in for "session A already claimed and the render
117
+ # was executed + status-confirmed" (SKILL.md's "Assignment Claim On Selection" round trip).
118
+ # "Session B" reads the SAME fixture next with a fresh liveness fixture for the holder: held.
119
+ node "$CLI" assignment-provider status --provider github --issue-json "$ISSUE_CLAIMED" \
120
+ --self-actor "claude-code:eval-actor-b-session:eval-host" \
121
+ --liveness-events-json "$LIVENESS_FRESH" --now "2026-06-01T12:20:00Z" \
122
+ > "$TMPDIR_EVAL/github-session-b.json"
123
+ [[ "$(json_query "$TMPDIR_EVAL/github-session-b.json" "effective.effective_state")" == "held" ]] && pass "github: session B's join check on an already-claimed fixture issue reports held (excluded)" || fail "github: session B's join check on an already-claimed fixture issue reports held (excluded)"
124
+ [[ "$(json_query "$TMPDIR_EVAL/github-session-b.json" "effective.effective_state")" != "free" ]] && pass "github: subject is definitively NOT free for session B" || fail "github: subject is definitively NOT free for session B"
125
+
126
+ echo ""
127
+ if [[ "$errors" -eq 0 ]]; then
128
+ echo "test_pull_work_assignment_join: all checks passed."
129
+ else
130
+ echo "test_pull_work_assignment_join: $errors check(s) failed."
131
+ fi
132
+ exit "$errors"