@kontourai/flow-agents 2.4.0 → 3.1.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 (195) hide show
  1. package/.github/CODEOWNERS +8 -0
  2. package/.github/workflows/ci.yml +20 -0
  3. package/.github/workflows/trust-reconcile.yml +62 -4
  4. package/CHANGELOG.md +38 -0
  5. package/CONTEXT.md +88 -1
  6. package/README.md +4 -0
  7. package/build/src/cli/assignment-provider.d.ts +144 -0
  8. package/build/src/cli/assignment-provider.js +805 -0
  9. package/build/src/cli/effective-assignment-provider-settings.d.ts +1 -0
  10. package/build/src/cli/effective-assignment-provider-settings.js +125 -0
  11. package/build/src/cli/validate-workflow-artifacts.js +5 -1
  12. package/build/src/cli/workflow-sidecar.d.ts +2 -2
  13. package/build/src/cli/workflow-sidecar.js +549 -145
  14. package/build/src/cli.js +6 -0
  15. package/build/src/lib/flow-resolver.d.ts +12 -6
  16. package/build/src/lib/flow-resolver.js +30 -14
  17. package/build/src/tools/validate-source-tree.js +2 -0
  18. package/context/contracts/artifact-contract.md +2 -0
  19. package/context/contracts/assignment-provider-contract.md +248 -0
  20. package/context/contracts/builder-kit-workflow-state-contract.md +2 -0
  21. package/context/contracts/decision-registry-contract.md +2 -0
  22. package/context/contracts/delivery-contract.md +2 -0
  23. package/context/contracts/execution-contract.md +25 -0
  24. package/context/contracts/governance-adapter-contract.md +2 -0
  25. package/context/contracts/knowledge-store-contract.md +197 -0
  26. package/context/contracts/planning-contract.md +2 -0
  27. package/context/contracts/probe-docs-write-contract.md +187 -0
  28. package/context/contracts/review-contract.md +2 -0
  29. package/context/contracts/sandbox-policy.md +2 -0
  30. package/context/contracts/standing-directives.md +13 -0
  31. package/context/contracts/verification-contract.md +2 -0
  32. package/context/contracts/work-item-contract.md +2 -0
  33. package/context/scripts/hooks/config-protection.js +14 -1
  34. package/context/scripts/hooks/lib/config-protection-remedies.js +9 -0
  35. package/context/scripts/hooks/stop-goal-fit.js +4 -2
  36. package/context/scripts/hooks/workflow-steering.js +42 -0
  37. package/context/settings/assignment-provider-settings.json +33 -0
  38. package/docs/adr/0001-flow-agents-consumes-flow.md +2 -0
  39. package/docs/adr/0002-flow-kits-as-extension-unit.md +2 -0
  40. package/docs/adr/0003-flow-agents-coordinates-kits-and-adapters.md +2 -0
  41. package/docs/adr/0004-gates-expect-surface-claims.md +2 -0
  42. package/docs/adr/0005-kubernetes-inspired-resource-contracts.md +2 -0
  43. package/docs/adr/0006-typescript-first-source-policy.md +2 -0
  44. package/docs/adr/0007-flow-skill-kit-tool-boundary.md +2 -0
  45. package/docs/adr/0007-skill-audit.md +2 -0
  46. package/docs/adr/0008-kit-operation-boundary.md +2 -0
  47. package/docs/adr/0009-canonical-hook-core-kit-boundary.md +2 -0
  48. package/docs/adr/0010-workflow-trust-state-as-hachure-bundle.md +2 -0
  49. package/docs/adr/0011-mcp-posture.md +2 -0
  50. package/docs/adr/0012-agent-coordination-as-liveness-claims.md +2 -0
  51. package/docs/adr/0013-context-lifecycle.md +2 -0
  52. package/docs/adr/0014-core-vs-domain-kit-boundary.md +2 -0
  53. package/docs/adr/0015-flow-flow-agents-boundary-reconciliation.md +2 -0
  54. package/docs/adr/0016-three-hard-boundary-model.md +2 -0
  55. package/docs/adr/0017-anti-gaming-trust-security-model.md +2 -0
  56. package/docs/adr/0018-freeze-local-shell-heuristics.md +2 -0
  57. package/docs/adr/0019-kit-dependency-ownership.md +2 -0
  58. package/docs/adr/0020-trust-reconcile-manifest-and-claim-classification.md +2 -0
  59. package/docs/adr/0021-assignment-leases-and-stale-claim-takeover.md +3 -1
  60. package/docs/adr/0022-fail-closed-delivery-reconciliation-with-governed-exemptions.md +182 -0
  61. package/docs/adr/README.md +49 -0
  62. package/docs/adr/index.md +34 -0
  63. package/docs/context-map.md +1 -0
  64. package/docs/decisions/agent-coordination.md +20 -0
  65. package/docs/decisions/anti-gaming-trust-security.md +20 -0
  66. package/docs/decisions/context-lifecycle.md +18 -0
  67. package/docs/decisions/core-domain-kit-boundary.md +18 -0
  68. package/docs/decisions/flow-flow-agents-boundary.md +18 -0
  69. package/docs/decisions/flow-kit.md +20 -0
  70. package/docs/decisions/flow-skill-kit-tool-boundary.md +20 -0
  71. package/docs/decisions/graph-knowledge-provider.md +63 -0
  72. package/docs/decisions/hook-core-kit-boundary.md +18 -0
  73. package/docs/decisions/index.md +22 -0
  74. package/docs/decisions/kit-dependency-ownership.md +18 -0
  75. package/docs/decisions/kit-operation-boundary.md +18 -0
  76. package/docs/decisions/knowledge-promote-sub-flow.md +59 -0
  77. package/docs/decisions/knowledge-store-provider.md +51 -0
  78. package/docs/decisions/kontour-resource-contract.md +18 -0
  79. package/docs/decisions/mcp-posture.md +18 -0
  80. package/docs/decisions/model-routing.md +63 -0
  81. package/docs/decisions/standing-directives.md +66 -0
  82. package/docs/decisions/three-hard-boundary-model.md +18 -0
  83. package/docs/decisions/trust-reconcile.md +20 -0
  84. package/docs/decisions/typescript-source-policy.md +48 -0
  85. package/docs/decisions/workflow-enforcement.md +18 -0
  86. package/docs/decisions/workflow-trust-state.md +20 -0
  87. package/docs/fixture-ownership.md +1 -0
  88. package/docs/workflow-shared-contracts.md +2 -1
  89. package/docs/workflow-usage-guide.md +7 -1
  90. package/evals/ci/run-baseline.sh +10 -0
  91. package/evals/fixtures/assignment-provider/actor-a.json +6 -0
  92. package/evals/fixtures/assignment-provider/actor-b.json +6 -0
  93. package/evals/fixtures/assignment-provider/github-issue-claimed.json +27 -0
  94. package/evals/fixtures/assignment-provider/github-issue-unassigned.json +7 -0
  95. package/evals/fixtures/assignment-provider/hostile-effective-state.json +11 -0
  96. package/evals/fixtures/assignment-provider/liveness-fresh.json +9 -0
  97. package/evals/fixtures/assignment-provider/liveness-stale.json +9 -0
  98. package/evals/integration/test_assignment_provider_github.sh +318 -0
  99. package/evals/integration/test_assignment_provider_local_file.sh +222 -0
  100. package/evals/integration/test_critique_supersession_roundtrip.sh +182 -0
  101. package/evals/integration/test_current_json_per_actor.sh +516 -0
  102. package/evals/integration/test_ensure_session_ownership_guard.sh +435 -0
  103. package/evals/integration/test_fixture_retirement_audit.sh +2 -2
  104. package/evals/integration/test_gate_lockdown.sh +10 -0
  105. package/evals/integration/test_publish_delivery.sh +21 -4
  106. package/evals/integration/test_pull_work_assignment_join.sh +132 -0
  107. package/evals/integration/test_pull_work_liveness_preflight.sh +20 -7
  108. package/evals/integration/test_reconcile_soundness.sh +33 -9
  109. package/evals/integration/test_trust_reconcile.sh +9 -8
  110. package/evals/integration/test_trust_reconcile_negatives.sh +608 -0
  111. package/evals/integration/test_workflow_sidecar_writer.sh +91 -3
  112. package/evals/run.sh +14 -0
  113. package/evals/static/test_flowdef_codeowners_coverage.sh +6 -0
  114. package/evals/static/test_knowledge_providers.sh +32 -0
  115. package/evals/static/test_workflow_skills.sh +15 -2
  116. package/kits/builder/skills/deliver/SKILL.md +36 -0
  117. package/kits/builder/skills/design-probe/SKILL.md +37 -0
  118. package/kits/builder/skills/pickup-probe/SKILL.md +4 -4
  119. package/kits/builder/skills/pull-work/SKILL.md +78 -10
  120. package/kits/knowledge/adapters/default-store/index.js +92 -4
  121. package/kits/knowledge/adapters/obsidian-store/index.js +69 -4
  122. package/kits/knowledge/adapters/shared/codec.js +141 -0
  123. package/kits/knowledge/docs/README.md +121 -2
  124. package/kits/knowledge/docs/store-contract.md +112 -4
  125. package/kits/knowledge/evals/contract-suite/suite.test.js +176 -0
  126. package/kits/knowledge/flows/promote.flow.json +84 -0
  127. package/kits/knowledge/kit.json +50 -0
  128. package/kits/knowledge/promote/distill.js +96 -0
  129. package/kits/knowledge/promote/fixtures/contradiction-registry/CONTEXT.md +7 -0
  130. package/kits/knowledge/promote/fixtures/contradiction-registry/docs/decisions/cache-eviction-policy.md +12 -0
  131. package/kits/knowledge/promote/fixtures/contradiction-registry/docs/decisions/cache-eviction.md +12 -0
  132. package/kits/knowledge/promote/fixtures/repo/CONTEXT.md +11 -0
  133. package/kits/knowledge/promote/fixtures/repo/docs/decisions/index.md +9 -0
  134. package/kits/knowledge/promote/fixtures/repo/docs/decisions/liveness-claim.md +12 -0
  135. package/kits/knowledge/promote/fixtures/session/acceptance.json +9 -0
  136. package/kits/knowledge/promote/fixtures/session/agents/tool-planner/events.jsonl +1 -0
  137. package/kits/knowledge/promote/fixtures/session/agents/tool-verifier/events.jsonl +1 -0
  138. package/kits/knowledge/promote/fixtures/session/agents/tool-worker-1-1/events.jsonl +1 -0
  139. package/kits/knowledge/promote/fixtures/session/kontourai-flow-agents-287--deliver.md +47 -0
  140. package/kits/knowledge/promote/fixtures/session/learning.json +35 -0
  141. package/kits/knowledge/promote/fixtures/session/state.json +19 -0
  142. package/kits/knowledge/promote/health.js +137 -0
  143. package/kits/knowledge/promote/index.js +176 -0
  144. package/kits/knowledge/promote/ingest.js +103 -0
  145. package/kits/knowledge/promote/lib.js +132 -0
  146. package/kits/knowledge/promote/link.js +84 -0
  147. package/kits/knowledge/promote/promote.test.js +174 -0
  148. package/kits/knowledge/providers/conformance/fixtures/git-repo/CONTEXT.md +12 -0
  149. package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/old-sprocket-shape.md +13 -0
  150. package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/sprocket-shape.md +14 -0
  151. package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/widget-format.md +14 -0
  152. package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/learnings/fixture-learning.md +7 -0
  153. package/kits/knowledge/providers/conformance/fixtures/work-item/issues.json +30 -0
  154. package/kits/knowledge/providers/conformance/suite.test.js +143 -0
  155. package/kits/knowledge/providers/git-repo/index.js +236 -0
  156. package/kits/knowledge/providers/health/health-pass.test.js +99 -0
  157. package/kits/knowledge/providers/health/index.js +153 -0
  158. package/kits/knowledge/providers/index.js +25 -0
  159. package/kits/knowledge/providers/lib/model.js +91 -0
  160. package/kits/knowledge/providers/lib/schema-validate.js +119 -0
  161. package/kits/knowledge/providers/markdown-vault/index.js +169 -0
  162. package/kits/knowledge/providers/neo4j/connection.js +121 -0
  163. package/kits/knowledge/providers/neo4j/cypher.js +190 -0
  164. package/kits/knowledge/providers/neo4j/fake-driver.js +123 -0
  165. package/kits/knowledge/providers/neo4j/fixtures/spike-ground-truth.json +29 -0
  166. package/kits/knowledge/providers/neo4j/graph-queries.js +210 -0
  167. package/kits/knowledge/providers/neo4j/index.js +280 -0
  168. package/kits/knowledge/providers/neo4j/integration.test.js +122 -0
  169. package/kits/knowledge/providers/neo4j/neo4j.test.js +219 -0
  170. package/kits/knowledge/providers/neo4j/sync.js +235 -0
  171. package/kits/knowledge/providers/work-item/index.js +204 -0
  172. package/package.json +7 -3
  173. package/schemas/assignment-provider-settings.schema.json +125 -0
  174. package/schemas/knowledge/edge.schema.json +54 -0
  175. package/schemas/knowledge/health-report.schema.json +45 -0
  176. package/schemas/knowledge/node.schema.json +49 -0
  177. package/schemas/knowledge/proposal.schema.json +53 -0
  178. package/scripts/README.md +1 -0
  179. package/scripts/ci/trust-reconcile.js +521 -24
  180. package/scripts/freeze-adrs.mjs +364 -0
  181. package/scripts/hooks/config-protection.js +14 -1
  182. package/scripts/hooks/evidence-capture.js +4 -1
  183. package/scripts/hooks/lib/config-protection-remedies.js +9 -0
  184. package/scripts/hooks/lib/current-pointer.js +123 -0
  185. package/scripts/hooks/lib/liveness-heartbeat.js +75 -35
  186. package/scripts/hooks/stop-goal-fit.js +4 -2
  187. package/scripts/hooks/workflow-steering.js +42 -0
  188. package/scripts/statusline/flow-agents-statusline.js +3 -1
  189. package/src/cli/assignment-provider.ts +927 -0
  190. package/src/cli/effective-assignment-provider-settings.ts +112 -0
  191. package/src/cli/validate-workflow-artifacts.ts +5 -1
  192. package/src/cli/workflow-sidecar.ts +577 -139
  193. package/src/cli.ts +6 -0
  194. package/src/lib/flow-resolver.ts +35 -14
  195. package/src/tools/validate-source-tree.ts +2 -0
@@ -530,19 +530,28 @@ fi
530
530
 
531
531
  # AC5 (existing-session continuity): re-running ensure-session against the SAME slug from a
532
532
  # DIFFERENT actor never re-derives or overwrites the already-recorded branch (ADR 0021 §5
533
- # takeover continuity — resume the incumbent's branch, never a parallel one).
533
+ # takeover continuity — resume the incumbent's branch, never a parallel one). #291's
534
+ # ensure-session ownership guard now classifies alpha's still-fresh-assignment/no-liveness
535
+ # claim as `reclaimable` and refuses gamma's entry without an explicit takeover — so this
536
+ # takeover is made explicit via --supersede-stale (ADR 0021 §5's grace-beat/auto-resume
537
+ # protocol is still #294's scope; #291 only wires the explicit, caller-invoked takeover path).
538
+ # The supersede updates the ASSIGNMENT record's actor (assignment/<slug>.json), never
539
+ # state.json — state.json's `branch` field (and the rest of state.json) must stay exactly
540
+ # alpha's, proving the takeover resumes the incumbent's branch rather than reforking one for
541
+ # gamma.
534
542
  if flow_agents_node "$WRITER" ensure-session \
535
543
  --artifact-root "$SESSION_ROOT" \
536
544
  --task-slug branch-derive-a \
537
545
  --actor test-actor-gamma \
546
+ --supersede-stale \
538
547
  --source-request "A takeover by a different actor must not refork the branch." \
539
548
  --summary "Resuming actor gamma should inherit alpha's already-recorded branch." \
540
549
  --timestamp "2026-05-09T00:03:12Z" >"$TMPDIR_EVAL/branch-no-rederive.out" 2>"$TMPDIR_EVAL/branch-no-rederive.err" \
541
550
  && rg -q '"branch": "agent/test-actor-alpha/branch-derive-a"' "$BRANCH_DERIVE_A_DIR/state.json" \
542
551
  && ! rg -q 'test-actor-gamma' "$BRANCH_DERIVE_A_DIR/state.json"; then
543
- _pass "sidecar writer never re-derives an existing session's branch for a later actor (AC5, ADR 0021 §5)"
552
+ _pass "sidecar writer's explicit --supersede-stale takeover (#291) never re-derives or overwrites an existing session's branch for the new actor (AC5, ADR 0021 §5)"
544
553
  else
545
- _fail "sidecar writer re-derived or overwrote an existing session's branch on takeover: $(cat "$TMPDIR_EVAL/branch-no-rederive.out" "$TMPDIR_EVAL/branch-no-rederive.err")"
554
+ _fail "sidecar writer's explicit --supersede-stale takeover re-derived or overwrote an existing session's branch: $(cat "$TMPDIR_EVAL/branch-no-rederive.out" "$TMPDIR_EVAL/branch-no-rederive.err")"
546
555
  fi
547
556
 
548
557
  # ─── #309: init-plan (and advance-state) must never drop an already-recorded branch ────
@@ -2791,6 +2800,85 @@ else
2791
2800
  _fail "trust.bundle capture-authoritative setup failed: $(cat "$TMPDIR_EVAL/tb-capture-evidence.out" "$TMPDIR_EVAL/tb-capture-evidence.err")"
2792
2801
  fi
2793
2802
 
2803
+ # ---- #347: hard cutover -- an unstamped (pre-#344) claim in a session trust bundle is a loud,
2804
+ # typed error; checksFromBundle/critiquesFromBundle carry NO claimType-derivation fallback (that
2805
+ # fallback WAS the #268 defect kept reachable). ----------------------------------------------
2806
+ TB_UNSTAMPED_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/trust-bundle-unstamped"
2807
+ mkdir -p "$TB_UNSTAMPED_DIR"
2808
+ cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$TB_UNSTAMPED_DIR/trust-bundle-unstamped--deliver.md"
2809
+ flow_agents_node "$WRITER" init-plan "$TB_UNSTAMPED_DIR/trust-bundle-unstamped--deliver.md" \
2810
+ --source-request "Unstamped bundle regression fixture." \
2811
+ --summary "Unstamped bundle regression fixture." \
2812
+ --next-action "Seed a stamped check, then strip its origin stamp to simulate a pre-#344 bundle." \
2813
+ --timestamp "2026-05-09T00:00:00Z" >"$TMPDIR_EVAL/tb-unstamped-init.out" 2>"$TMPDIR_EVAL/tb-unstamped-init.err"
2814
+ flow_agents_node "$WRITER" record-evidence "$TB_UNSTAMPED_DIR" \
2815
+ --verdict pass \
2816
+ --check-json '{"id":"tb-unstamped-check","kind":"test","status":"pass","summary":"Stamped check, about to be corrupted."}' \
2817
+ --timestamp "2026-05-09T00:01:00Z" >"$TMPDIR_EVAL/tb-unstamped-evidence.out" 2>"$TMPDIR_EVAL/tb-unstamped-evidence.err"
2818
+
2819
+ # Simulate a pre-#344 bundle by stripping the origin/check_kind stamp from every claim -- never
2820
+ # done by hand-editing production sessions, only here to recreate the on-disk shape of a bundle
2821
+ # recorded before #344 shipped stamping.
2822
+ export UNSTAMPED_TARGET="$TB_UNSTAMPED_DIR/trust.bundle"
2823
+ node --input-type=module <<NODEOF
2824
+ import { readFileSync, writeFileSync } from 'node:fs';
2825
+ const target = process.env.UNSTAMPED_TARGET;
2826
+ const data = JSON.parse(readFileSync(target, 'utf8'));
2827
+ data.claims = (data.claims || []).map((c) => {
2828
+ if (c && c.metadata && typeof c.metadata === 'object') {
2829
+ const rest = { ...c.metadata };
2830
+ delete rest.origin;
2831
+ delete rest.check_kind;
2832
+ return { ...c, metadata: rest };
2833
+ }
2834
+ return c;
2835
+ });
2836
+ writeFileSync(target, JSON.stringify(data, null, 2) + '\n');
2837
+ NODEOF
2838
+
2839
+ if flow_agents_node "$WRITER" record-critique "$TB_UNSTAMPED_DIR" \
2840
+ --id tb-unstamped-review \
2841
+ --reviewer tool-code-reviewer \
2842
+ --verdict pass \
2843
+ --summary "Should never be recorded -- the bundle is unstamped." \
2844
+ --timestamp "2026-05-09T00:02:00Z" >"$TMPDIR_EVAL/tb-unstamped-critique.out" 2>"$TMPDIR_EVAL/tb-unstamped-critique.err"; then
2845
+ _fail "record-critique should refuse to read an unstamped (pre-#344) trust.bundle, not silently reclassify it by claimType"
2846
+ elif rg -q 'pre-supersession trust\.bundle' "$TMPDIR_EVAL/tb-unstamped-critique.out" "$TMPDIR_EVAL/tb-unstamped-critique.err" \
2847
+ && rg -qF "$TB_UNSTAMPED_DIR" "$TMPDIR_EVAL/tb-unstamped-critique.out" "$TMPDIR_EVAL/tb-unstamped-critique.err" \
2848
+ && rg -q 're-record evidence to regenerate' "$TMPDIR_EVAL/tb-unstamped-critique.out" "$TMPDIR_EVAL/tb-unstamped-critique.err" \
2849
+ && rg -q 'record-evidence' "$TMPDIR_EVAL/tb-unstamped-critique.out" "$TMPDIR_EVAL/tb-unstamped-critique.err"; then
2850
+ _pass "record-critique refuses an unstamped (pre-#344) trust.bundle: typed error names the session dir and the record-evidence remedy (no claimType-derivation fallback, #347)"
2851
+ else
2852
+ _fail "record-critique on an unstamped bundle failed for the wrong reason: $(cat "$TMPDIR_EVAL/tb-unstamped-critique.out" "$TMPDIR_EVAL/tb-unstamped-critique.err")"
2853
+ fi
2854
+
2855
+ # The same unstamped bundle also refuses promote (checksFromBundle) -- not just
2856
+ # record-critique/critiquesFromBundle.
2857
+ if flow_agents_node "$WRITER" promote "$TB_UNSTAMPED_DIR" --none --reason "regression probe" \
2858
+ --timestamp "2026-05-09T00:03:00Z" >"$TMPDIR_EVAL/tb-unstamped-promote.out" 2>"$TMPDIR_EVAL/tb-unstamped-promote.err"; then
2859
+ _fail "promote should refuse to read an unstamped (pre-#344) trust.bundle via checksFromBundle"
2860
+ elif rg -q 'pre-supersession trust\.bundle' "$TMPDIR_EVAL/tb-unstamped-promote.out" "$TMPDIR_EVAL/tb-unstamped-promote.err"; then
2861
+ _pass "promote (checksFromBundle) also refuses an unstamped (pre-#344) trust.bundle with the same typed error"
2862
+ else
2863
+ _fail "promote on an unstamped bundle failed for the wrong reason: $(cat "$TMPDIR_EVAL/tb-unstamped-promote.out" "$TMPDIR_EVAL/tb-unstamped-promote.err")"
2864
+ fi
2865
+
2866
+ # (b) STAMPED bundles are unaffected: the earlier, still-stamped trust-bundle-critique fixture
2867
+ # carries the origin stamp on every claim -- the normal write path was never touched by this cutover.
2868
+ export STAMPED_TARGET="$TB_CRITIQUE_DIR/trust.bundle"
2869
+ if [[ -f "$STAMPED_TARGET" ]] && node --input-type=module <<NODEOF 2>"$TMPDIR_EVAL/tb-stamped-unaffected.err"
2870
+ import { readFileSync } from 'node:fs';
2871
+ const target = process.env.STAMPED_TARGET;
2872
+ const bundle = JSON.parse(readFileSync(target, 'utf8'));
2873
+ const unstamped = bundle.claims.filter((c) => !(c.metadata && typeof c.metadata === 'object' && typeof c.metadata.origin === 'string' && c.metadata.origin.length > 0));
2874
+ if (unstamped.length > 0) { process.stderr.write('found ' + unstamped.length + ' claim(s) with no origin stamp in a normally-written bundle\n'); process.exit(1); }
2875
+ NODEOF
2876
+ then
2877
+ _pass "stamped bundles are unaffected: every claim in a normally-written trust bundle carries the origin stamp (#347)"
2878
+ else
2879
+ _fail "stamped-bundle regression: $(cat "$TMPDIR_EVAL/tb-stamped-unaffected.err")"
2880
+ fi
2881
+
2794
2882
  # ─── AC4: render-trust-panel projects the bundle to a standalone Surface Trust Panel (ADR 0010 Phase 3) ──
2795
2883
  if [[ -f "$TB_CAPTURE_DIR/trust.bundle" ]] && flow_agents_node "$WRITER" render-trust-panel "$TB_CAPTURE_DIR" --out "$TB_CAPTURE_DIR/trust-panel.html" >"$TMPDIR_EVAL/tb-panel.out" 2>"$TMPDIR_EVAL/tb-panel.err"; then
2796
2884
  PANEL="$TB_CAPTURE_DIR/trust-panel.html"
package/evals/run.sh CHANGED
@@ -146,6 +146,8 @@ run_static() {
146
146
  bash "$EVAL_DIR/static/test_flowdef_codeowners_coverage.sh" || result=1
147
147
  echo ""
148
148
  bash "$EVAL_DIR/static/test_unit_helpers.sh" || result=1
149
+ echo ""
150
+ bash "$EVAL_DIR/static/test_knowledge_providers.sh" || result=1
149
151
  return $result
150
152
  }
151
153
 
@@ -195,6 +197,16 @@ run_integration() {
195
197
  echo ""
196
198
  bash "$EVAL_DIR/integration/test_actor_identity.sh" || result=1
197
199
  echo ""
200
+ bash "$EVAL_DIR/integration/test_assignment_provider_local_file.sh" || result=1
201
+ echo ""
202
+ bash "$EVAL_DIR/integration/test_assignment_provider_github.sh" || result=1
203
+ echo ""
204
+ bash "$EVAL_DIR/integration/test_pull_work_assignment_join.sh" || result=1
205
+ echo ""
206
+ bash "$EVAL_DIR/integration/test_ensure_session_ownership_guard.sh" || result=1
207
+ echo ""
208
+ bash "$EVAL_DIR/integration/test_current_json_per_actor.sh" || result=1
209
+ echo ""
198
210
  bash "$EVAL_DIR/integration/test_liveness_heartbeat.sh" || result=1
199
211
  echo ""
200
212
  bash "$EVAL_DIR/integration/test_pull_work_liveness_preflight.sh" || result=1
@@ -241,6 +253,8 @@ run_integration() {
241
253
  echo ""
242
254
  bash "$EVAL_DIR/integration/test_flowdef_session_history_preservation.sh" || result=1
243
255
  echo ""
256
+ bash "$EVAL_DIR/integration/test_critique_supersession_roundtrip.sh" || result=1
257
+ echo ""
244
258
  bash "$EVAL_DIR/integration/test_flowdef_session_activation.sh" || result=1
245
259
  echo ""
246
260
  bash "$EVAL_DIR/integration/test_trust_checkpoint.sh" || result=1
@@ -53,4 +53,10 @@ require_codeowner "src/li""b/flow-resolver.ts" "src/li""b/flow-resolver.ts"
53
53
  require_codeowner "src/cli/workflow-sidecar.ts" "src/cli/workflow-sidecar.ts"
54
54
  require_codeowner "kits/*/flows/*.flow.json" "kits/*/flows/*.flow.json (kit FlowDefinitions)"
55
55
 
56
+ # ADR 0022 §2: delivery/DECLARED is the no-agent-delivery exemption marker
57
+ # scripts/ci/trust-reconcile.js reads — a self-asserted governance artifact (names
58
+ # approved_by) that must stay owner-review-gated the same as the other verify-config/
59
+ # anchor paths above (#301's file-level half; server-side review enforcement is #225).
60
+ require_codeowner "delivery/DECLARED" "delivery/DECLARED (ADR 0022 §2 no-agent-delivery marker)"
61
+
56
62
  echo "FlowDefinition-related CODEOWNERS coverage checks passed."
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env bash
2
+ # test_knowledge_providers.sh — Layer 1: Knowledge Store Provider contract
3
+ # conformance + provider-agnostic health verbs (issue #317).
4
+ #
5
+ # Runs the node:test suites that back AC1–AC3:
6
+ # - providers/conformance/suite.test.js (AC3: all three providers conform)
7
+ # - providers/health/health-pass.test.js (AC1: vault+git-repo reports;
8
+ # AC2: work-item seeded dupe + broken blocker)
9
+ # - promote/promote.test.js (issue #313 knowledge promote sub-flow:
10
+ # AC1 draft delta+provenance, AC2 contradiction
11
+ # report+merge proposal, AC3 zero external writes)
12
+ # - providers/neo4j/neo4j.test.js (issue #327 neo4j provider, CI-safe: sync
13
+ # idempotency AC1, canonical queries AC3, degradation
14
+ # AC4 — via an injected fake driver, no Docker.
15
+ # Live Neo4j integration.test.js is gated on NEO4J_URI.)
16
+ # Deterministic, dependency-free, fixture-driven — never touches a real board.
17
+ set -uo pipefail
18
+ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
19
+ cd "$ROOT_DIR"
20
+
21
+ echo "── Knowledge store provider conformance + health + promote sub-flow (node --test) ──"
22
+
23
+ if node --test \
24
+ kits/knowledge/providers/conformance/suite.test.js \
25
+ kits/knowledge/providers/health/health-pass.test.js \
26
+ kits/knowledge/promote/promote.test.js \
27
+ kits/knowledge/providers/neo4j/neo4j.test.js; then
28
+ echo " PASS: knowledge store provider conformance + health verbs + promote sub-flow"
29
+ else
30
+ echo " FAIL: knowledge store provider conformance + health verbs + promote sub-flow"
31
+ exit 1
32
+ fi
@@ -101,6 +101,8 @@ BACKLOG_PROVIDER_SETTINGS_SCHEMA="$ROOT/schemas/backlog-provider-settings.schema
101
101
  VERIFICATION_CONTRACT="$ROOT/context/contracts/verification-contract.md"
102
102
  REVIEW_CONTRACT="$ROOT/context/contracts/review-contract.md"
103
103
  DELIVERY_CONTRACT="$ROOT/context/contracts/delivery-contract.md"
104
+ DECISION_REGISTRY_CONTRACT="$ROOT/context/contracts/decision-registry-contract.md"
105
+ PROBE_DOCS_WRITE_CONTRACT="$ROOT/context/contracts/probe-docs-write-contract.md"
104
106
  EFFECTIVE_BACKLOG_SETTINGS="$ROOT/src/cli/effective-backlog-settings.ts"
105
107
  PULL_WORK_PROVIDER="$ROOT/src/cli/pull-work-provider.ts"
106
108
  PULL_WORK_PROVIDER_INTEGRATION="$ROOT/evals/integration/test_pull_work_provider.sh"
@@ -188,6 +190,8 @@ require_file "$PLANNING_CONTRACT" "planning contract"
188
190
  require_file "$EXECUTION_CONTRACT" "execution contract"
189
191
  require_file "$SANDBOX_CONTRACT" "sandbox policy contract"
190
192
  require_file "$GOVERNANCE_ADAPTER_CONTRACT" "governance adapter contract"
193
+ require_file "$DECISION_REGISTRY_CONTRACT" "decision registry contract"
194
+ require_file "$PROBE_DOCS_WRITE_CONTRACT" "probe docs-write contract"
191
195
  require_file "$WORK_ITEM_CONTRACT" "work item contract"
192
196
  require_file "$BACKLOG_PROVIDER_SETTINGS" "backlog provider settings"
193
197
  require_file "$BACKLOG_PROVIDER_SETTINGS_SCHEMA" "backlog provider settings schema"
@@ -754,8 +758,17 @@ require_text "$PICKUP_PROBE" 'planned-base drift summary and drift classificatio
754
758
  require_text "$PICKUP_PROBE" 'Update `CONTEXT.md` inline only for glossary-style durable terminology decisions' "pickup-probe limits CONTEXT.md updates"
755
759
  require_text "$PICKUP_PROBE" 'Challenge glossary conflicts immediately' "pickup-probe challenges domain terminology"
756
760
  require_text "$PICKUP_PROBE" 'Create a lazy context file only when a resolved term or workflow concept has no existing home' "pickup-probe creates context lazily"
757
- require_text "$PICKUP_PROBE" 'ADRs sparingly, only when all three are true' "pickup-probe limits ADRs"
758
- require_text "$PICKUP_PROBE" 'hard to reverse, surprising without context, and the result of a real trade-off' "pickup-probe keeps ADR threshold"
761
+ require_text "$PICKUP_PROBE" 'context/contracts/probe-docs-write-contract.md' "pickup-probe references probe docs-write contract"
762
+ require_text "$PICKUP_PROBE" 'Never propose or create a numbered ADR' "pickup-probe never proposes numbered ADRs"
763
+ require_text "$PICKUP_PROBE" 'Record a decision delta at .docs/decisions/<slug>\.md' "pickup-probe records decision delta"
764
+ require_text "$PICKUP_PROBE" 'propose revise-vs-create' "pickup-probe proposes revise-vs-create against the index"
765
+ require_text "$PICKUP_PROBE" 'session-archive. evidence ref' "pickup-probe links transcript provenance"
766
+ require_text "$DESIGN_PROBE" 'context/contracts/probe-docs-write-contract.md' "design-probe references probe docs-write contract"
767
+ require_text "$DESIGN_PROBE" 'Docs-Write: Two-Delta Emission' "design-probe documents two-delta emission"
768
+ require_text "$DESIGN_PROBE" 'Vocabulary delta' "design-probe documents vocabulary delta"
769
+ require_text "$DESIGN_PROBE" 'Decision delta' "design-probe documents decision delta"
770
+ require_text "$DESIGN_PROBE" 'Transcript provenance' "design-probe documents transcript provenance"
771
+ require_text "$DESIGN_PROBE" 'Do not write a numbered ADR' "design-probe never proposes numbered ADRs"
759
772
  require_text "$BUILDER_SHAPE" 'Product-level auto-guidance' "builder-shape guides idea-to-backlog"
760
773
  require_text "$BUILDER_SHAPE" 'do not require them to type `idea-to-backlog`' "builder-shape hides primitive typing"
761
774
  require_text "$PULL" 'probe_status' "pull-work records machine-checkable probe status"
@@ -20,6 +20,24 @@ Inherited from primitives:
20
20
  | tool-verifier | verify-work |
21
21
  | tool-playwright | verify-work |
22
22
 
23
+ ## Model Routing
24
+
25
+ Delegates are spawned with an explicit model override resolved from
26
+ `.datum/config.json` via `npx @kontourai/datum resolve <role> --json`
27
+ (see `context/contracts/execution-contract.md` § Delegation: Model Routing —
28
+ that contract is the consumption instruction, `.datum/config.json` is the
29
+ source of truth for the mapping):
30
+
31
+ | Delegate | Role |
32
+ |---|---|
33
+ | tool-worker | `delegate-mechanical` for fully-specified mechanical tasks, `delegate-implementation` for precisely-planned implementation, `delegate-design` when the task needs design latitude |
34
+ | tool-planner | `delegate-design` |
35
+ | tool-code-reviewer / tool-security-reviewer | `delegate-implementation` |
36
+ | tool-verifier / tool-playwright | `delegate-implementation` |
37
+
38
+ If datum or the config is absent, fall back to the runtime's inherited model
39
+ and note the fallback in the session artifact.
40
+
23
41
  ## Orchestrator Rule
24
42
 
25
43
  You never use `read`, `glob`, `grep`, or `code` on source files. You only read/write the session file and artifact files in `.kontourai/flow-agents/<slug>/`.
@@ -27,6 +45,7 @@ You never use `read`, `glob`, `grep`, or `code` on source files. You only read/w
27
45
  ## Shared Contracts
28
46
 
29
47
  Follow:
48
+ - `context/contracts/standing-directives.md`
30
49
  - `context/contracts/artifact-contract.md`
31
50
  - `context/contracts/planning-contract.md`
32
51
  - `context/contracts/execution-contract.md`
@@ -258,6 +277,23 @@ After CI passes and the work is merged or otherwise accepted:
258
277
  2. Archive the working artifacts under `.kontourai/flow-agents/<slug>/archive/` or keep a stable link to them.
259
278
  3. Record provider records, verification evidence, durable docs targets, accepted gaps, and follow-up routing in durable docs or provider records.
260
279
  4. Promote the relevant plan, decision, evidence, and usage notes into long-lived docs such as `docs/`, `README.md`, or a project decision record.
280
+
281
+ **Assisted promotion path (Knowledge Kit `knowledge.promote` sub-flow).** Rather than
282
+ deciding WHAT to promote by ad-hoc judgement, run the Knowledge Kit promote sub-flow
283
+ (`kits/knowledge/flows/promote.flow.json`, id `knowledge.promote`) over the session
284
+ directory. It ingests the session's plan/evidence/critique/learnings/transcripts,
285
+ distills schema-valid DRAFT decision-registry deltas (per
286
+ `context/contracts/decision-registry-contract.md`), CONTEXT.md vocabulary additions,
287
+ and learning entries, links each to the PR + merge SHA + session archive + touched
288
+ topics, and health-checks the registry for contradictions (proposing merge-repair).
289
+ It is PROPOSALS-ONLY: every output lands under `<session>/proposals/` — it never writes
290
+ docs directly. You apply the drafts you accept, then record what was promoted where with
291
+ the `promote` CLI (below), which stays the recording mechanism. The sub-flow is the
292
+ assisted path; the `promote` claim is the gate. `knowledge.promote` is a true composable
293
+ FlowDefinition (a parent step can `uses_flow: "knowledge.promote"`); it is invoked here
294
+ from the promote step rather than nested as a builder gate because the `promote` CLI —
295
+ not a flow gate — is #312's recording mechanism and the sub-flow's outputs are proposals
296
+ a human/agent applies.
261
297
  5. Link the long-lived doc back to the provider record, archived plan artifact, or accepted evidence when useful so future readers can see why and how the feature was built.
262
298
  6. Confirm `.kontourai/flow-agents/` runtime artifacts remain untracked before merge to `main`.
263
299
  7. **Clean up the workspace once the merge is confirmed.** First verify the merge actually happened from the provider's own record (a merge commit / `mergedAt`) — not a green check or a watcher's exit code. Then honor the `worktree_lifecycle` recorded by `pull-work` (`retain_until: pr_merged`): remove the isolated worktree (`git worktree remove <path>`) and delete the now-merged branch locally and on the remote. Never delete a branch or worktree before the merge is confirmed — a closed-but-unmerged PR or a prematurely deleted branch loses work. The task is not done while it leaves a stale worktree or merged branch behind.
@@ -82,6 +82,43 @@ Maintain a compact running record in the active artifact or conversation when no
82
82
 
83
83
  When workflow artifacts exist, update the appropriate session, handoff, Probe record, or planning artifact according to the local artifact contract. Do not invent a project-specific storage format when the repository already defines one.
84
84
 
85
+ ## Docs-Write: Two-Delta Emission
86
+
87
+ Per `context/contracts/probe-docs-write-contract.md`, when a `decisions` entry above is a **decision subject** — a durable, reusable answer to a named domain question, not an implementation detail or transient planning choice — emit two deltas into durable docs in the same motion, before moving to the next branch:
88
+
89
+ 1. **Vocabulary delta**: the subject noun must exist as a `CONTEXT.md` glossary term. If absent, coin it first (a tight one- or two-sentence `### <Term>` entry); if present, refine the definition when the crystallized decision changes what it says (e.g. an "open" pointer becoming a stated answer).
90
+ 2. **Decision delta**: consult `docs/decisions/index.md` and propose revise-vs-create against the existing topic slugs (this includes any `needs-decision` stub already naming the subject); revise the existing topic file in place, or create `docs/decisions/<slug>.md` per `context/contracts/decision-registry-contract.md`. Run `npm run gen:decisions-index && npm run check:decisions` after writing.
91
+ 3. **Transcript provenance**: add a `session-archive` evidence ref pointing at this Probe's own session artifact (the live `.kontourai/flow-agents/<slug>/...` path — it does not need to be archived yet).
92
+
93
+ Example — coining a new term and creating its topic file:
94
+
95
+ ```markdown
96
+ ### Retry Budget
97
+
98
+ The maximum number of automatic retries a workflow step may attempt before routing back to the user. Configured per Flow Definition step, not globally.
99
+ ```
100
+
101
+ ```markdown
102
+ ---
103
+ status: current
104
+ subject: Retry budget
105
+ decided: 2026-07-03
106
+ evidence:
107
+ - kind: session-archive
108
+ ref: .kontourai/flow-agents/<slug>/<slug>--design-probe.md
109
+ - kind: issue
110
+ ref: https://github.com/kontourai/flow-agents/issues/<n>
111
+ ---
112
+
113
+ # Retry budget
114
+
115
+ Each Flow Definition step declares its own retry budget; there is no global default. Set during design-probe alignment on <n>.
116
+ ```
117
+
118
+ Example — revising a `needs-decision` stub seeded by the ADR-freeze cutover (issue #314) once its frozen ADR's decision is confirmed still current: flip `status: needs-decision` to `current`, set `decided` to today, keep the existing `adr` evidence ref, and append the `session-archive` ref for this Probe session plus a `pr` ref once one exists. See `context/contracts/probe-docs-write-contract.md` § Worked Example for the full step-by-step.
119
+
120
+ Do not write a numbered ADR — `docs/adr/` is frozen history (`docs/adr/README.md`); every decision delta targets the topic-keyed registry.
121
+
85
122
  ## Stop Conditions
86
123
 
87
124
  Stop probing when one of these is true:
@@ -115,15 +115,15 @@ When the gate fails, record `--status fail` with `--expectation pickup-probe-rea
115
115
 
116
116
 
117
117
 
118
- ## Docs And ADR Policy
118
+ ## Docs-Write Policy: Two-Delta Emission
119
119
 
120
- `pickup-probe` may identify durable terminology and decision gaps, but it must keep documentation changes narrow:
120
+ `pickup-probe` may identify durable terminology and decision gaps, but it must keep documentation changes narrow. Per `context/contracts/probe-docs-write-contract.md`, a **decision subject** crystallizing during pickup probing (a durable, reusable answer to a named domain question — not an implementation detail or transient planning choice) gets both deltas in the same motion:
121
121
 
122
- - Update `CONTEXT.md` inline only for glossary-style durable terminology decisions, using tight one- or two-sentence definitions and `_Avoid_` terms when useful. Keep implementation details out of `CONTEXT.md`.
122
+ - Update `CONTEXT.md` inline only for glossary-style durable terminology decisions, using tight one- or two-sentence definitions and `_Avoid_` terms when useful. Keep implementation details out of `CONTEXT.md`. Coin the subject term first if it is not already a glossary entry — vocabulary is the decision registry's topic namespace (`context/contracts/decision-registry-contract.md` § Slug rules) — or refine the existing entry when a decision changes what it says.
123
123
  - If a `CONTEXT-MAP.md` exists, update the relevant context instead of assuming the root glossary owns the term.
124
124
  - Create a lazy context file only when a resolved term or workflow concept has no existing home.
125
125
  - Do not create context files for transient planning notes, open questions, or provider snapshots.
126
- - Propose or create ADRs sparingly, only when all three are true: the decision is hard to reverse, surprising without context, and the result of a real trade-off.
126
+ - Record a decision delta at `docs/decisions/<slug>.md`: consult `docs/decisions/index.md` first and propose revise-vs-create (an existing topic file, including a `needs-decision` stub, is revised in place; only an uncovered subject gets a new file), then run `npm run gen:decisions-index && npm run check:decisions`. Link this Probe's own session artifact as a `session-archive` evidence ref (transcript provenance). Never propose or create a numbered ADR — `docs/adr/` is frozen history; decisions go into the topic-keyed registry.
127
127
  - Prefer workflow artifacts for pickup decisions, unresolved questions, accepted gaps, and route reasons until the work is accepted.
128
128
 
129
129
  ## Handoff To Plan Work
@@ -10,6 +10,8 @@ Select ready backlog work and prepare a bounded execution handoff without implem
10
10
  ## Contract
11
11
 
12
12
  - Use `context/contracts/work-item-contract.md` as the source vocabulary for provider-backed work item shape, provider roles, and capability flags.
13
+ - Use `context/contracts/assignment-provider-contract.md` as the second source vocabulary for durable ownership: the `AssignmentProvider` operations (`claim`/`release`/`supersede`/`status`/`list`), the assignment ⋈ liveness join's effective-state enum (`held`/`reclaimable`/`human-held`/`free`), and the versioned claim-record format come from that contract, not from this skill.
14
+ - `pull-work` may perform exactly two provider-adjacent writes at selection: the liveness claim (ADR 0012, ephemeral, local runtime presence stream — see "1a. Liveness Selection Preflight" / "Liveness Claim On Selection") and the durable assignment claim (`context/contracts/assignment-provider-contract.md`, GitHub assignee/label/comment or a local-file record — see "Assignment Claim On Selection"); no other provider mutation is added to this skill.
13
15
  - Read the configured backlog provider dynamically.
14
16
  - Select one issue or a coherent issue group.
15
17
  - Do not implement code.
@@ -37,9 +39,10 @@ Create or update `.kontourai/flow-agents/<slug>/<slug>--pull-work.md` with:
37
39
 
38
40
  - `board_snapshot`: filters, issue list, labels, milestone/provider milestone state, project fields, state, blockers
39
41
  - `wip_assessment`: active work, reviews, verification, CI remediation, with personal WIP separated from global conflict context
40
- - `liveness_preflight`: per-candidate `{subjectId, state: held|reclaimable|free|mine, holder_actor?, status_raw}` plus `self_actor`/`self_actor_source`, computed via `liveness status --json` + `liveness whoami --json`; `self_actor` is captured exactly once per pull-work pass and pinned for reuse (see "1a. Liveness Selection Preflight" and "Liveness Claim On Selection" — never re-derived per claim call); this is the liveness-only projection of ADR 0021 §1's join, pending #290's assignment dimension
42
+ - `liveness_preflight`: per-candidate `{subjectId, state: held|reclaimable|human-held|free|mine, holder_actor?, status_raw, effective_reason?}` plus `self_actor`/`self_actor_source`, computed via `liveness status --json` + `liveness whoami --json` joined with `assignment-provider status`'s `effective_state`/`reason` (see "1a. Liveness Selection Preflight"); `self_actor` is captured exactly once per pull-work pass and pinned for reuse (see "1a. Liveness Selection Preflight" and "Liveness Claim On Selection" — never re-derived per claim call); this is now the **full** ADR 0021 §1 `assignment ⋈ liveness` join (previously liveness-only pending #290 #290 has landed)
41
43
  - `reclaimable_override`: recorded only when a `reclaimable` (stale) candidate is selected, or when `--force`/an explicit user instruction overrides a `held` exclusion — the explicit opt-in decision and its stated reason (see "1a. Liveness Selection Preflight")
42
44
  - `liveness_claim`: per selected item, `{subjectId, actor, emitted_at, ttl_seconds}`; on any claim-emit failure, `{skipped: <stderr reason>}` instead — fail-open (never block selection on a liveness-emit failure) but never silent: the skip reason is also surfaced in pull-work's user-facing output, and an unresolved-actor failure additionally names the remediation (`--actor <id>` / `FLOW_AGENTS_ACTOR=<id>` / a supported runtime), matching ADR 0012; also carries an optional `post_claim_conflict: {other_actor, detected_at}` when the post-claim re-check (see "Post-Claim Conflict Re-check") detects a double-hold
45
+ - `assignment_claim`: per selected item, `{provider: "github"|"local-file", subject_id, actor, command_evidence, confirmed_status}` — the durable-claim analog of `selected_item_ids`: provider kind, subject id, actor, the rendered/executed command evidence (`gh_commands`/`claim_comment_body` for `github`, the local-file `claim` invocation for `local-file`), and the confirmed `assignment-provider status` result for each selected item, recorded once the post-claim confirmation in "Assignment Claim On Selection" succeeds; see `context/contracts/assignment-provider-contract.md` for the underlying claim-record shape
43
46
  - `my_active_work`: local worktrees, dirty branches, open PRs by the current user, active sidecars, and in-flight review/verification/release work owned by the current user
44
47
  - `shepherding_candidates`: personal PRs, worktrees, or sidecars that should be reviewed, fixed, published, merged, abandoned, or cleaned before starting more work
45
48
  - `stale_worktrees`: worktrees with no open PR, no recent activity, merged/abandoned branches, or unclear ownership that need an explicit keep/remove decision
@@ -136,18 +139,33 @@ For each ready candidate, derive `subjectId` via:
136
139
  npm run workflow:sidecar -- resolve-slug <owner>/<repo>#<issue-number>
137
140
  ```
138
141
 
139
- then group all rows for that `subjectId` by subject (classify per subject, not per row) and classify in this precedence order, reading each row's raw `status` field (never `label`):
142
+ then group all rows for that `subjectId` by subject (classify per subject, not per row), reading each row's raw `status` field (never `label`). Detect the liveness-only double-hold case first, before computing the full assignment ⋈ liveness join below: when a `verified` row for an actor other than self and a `verified` row for the resolved self actor both exist on the same subject, that is a double-hold — surface it as `held` plus an explicit conflict warning (per ADR 0012 §4 detection: "a false-stale double-hold ... is detected ... not prevented"), never silently resolve it to `mine`.
140
143
 
141
- 1. a `verified` row for an actor other than self ⇒ `held`, excluded from the ready set by default, even when a `verified` row for the resolved self actor also exists on the same subject that combination is a double-hold: surface it as `held` plus an explicit conflict warning (per ADR 0012 §4 detection: "a false-stale double-hold ... is detected ... not prevented"), never silently resolve it to `mine`.
142
- 2. else, a `verified` row for the resolved self actor ⇒ `mine`: hand to `### 2. Enforce WIP And Shepherding`'s existing personal-WIP logic; do not re-offer as new, do not exclude as held-by-other.
143
- 3. else, a `stale` row (any actor) ⇒ `reclaimable`: offered, flagged, with a warning; selecting it requires an explicit recorded opt-in (record in `reclaimable_override`, and/or `alignment_questions`), never a silent normal pick.
144
- 4. else (a `revoked`-only row, a `superseded` row, or no row at all) ⇒ `free`, offered normally — except a `superseded` row is surfaced but never auto-selected (full takeover semantics are #294; this is an accepted gap, not silently ignored).
144
+ Otherwise, compute the full ADR 0021 §1 `assignment liveness` join per candidate via `assignment-provider status` (using the effective provider kind, repo, artifact root, `label_name`, and `claim_comment_marker` from `effective-assignment-provider-settings` see "Assignment Claim On Selection" below joined against the same liveness stream already read above):
145
145
 
146
- An explicit user instruction to proceed despite a `held` or `reclaimable` classification (`--force`, "take it anyway", equivalent) overrides the exclusion/opt-in requirement; the override and its stated reason must be recorded in the artifact (`liveness_preflight`, `reclaimable_override`, and/or `priority_rationale`).
146
+ ```bash
147
+ npm run assignment-provider -- status \
148
+ --provider <github|local-file> \
149
+ --subject-id <subjectId> \
150
+ --liveness-stream <path-to-events.jsonl> \
151
+ --self-actor <self_actor>
152
+ # github: --issue-json <path-to-gh-issue-view-json|-> --label-name <label> --claim-comment-marker <marker>
153
+ # local-file: --artifact-root <dir>
154
+ ```
155
+
156
+ Read the returned `.effective.effective_state` and `.effective.reason` and classify in this precedence order:
157
+
158
+ 1. `effective_state: "held"` with `reason: "self_is_holder"` ⇒ `mine`: hand to `### 2. Enforce WIP And Shepherding`'s existing personal-WIP logic; do not re-offer as new, do not exclude as held-by-other.
159
+ 2. else `effective_state: "held"` (`reason: "fresh_liveness_heartbeat"` or `"liveness_claim_present_assignment_lagging"`) ⇒ excluded from the ready set by default.
160
+ 3. else `effective_state: "reclaimable"` ⇒ offered, flagged, with a warning; selecting it requires an explicit recorded opt-in (record in `reclaimable_override`, and/or `alignment_questions`), never a silent normal pick. This issue does not implement takeover — offering a `reclaimable` candidate, never auto-reclaiming it, is the correct scope boundary (Design Decision 2; full takeover protocol is ADR 0021 §5 / #294).
161
+ 4. else `effective_state: "human-held"` ⇒ surfaced, never auto-reclaimed (Design Decision 3 / ADR 0021 §6): record the assignee identity and idle duration (`effective.holder.assignee`, `effective.holder.idle_days`) in `alignment_questions` with a recommended answer (e.g. "assigned to `<assignee>`, idle `<idle_days>` days — reclaim?" recommending confirmation before proceeding), and select only on the user's explicit confirmation.
162
+ 5. else `effective_state: "free"` ⇒ offered normally — except a `superseded` liveness row (no active assignment, no fresh liveness) is surfaced but never auto-selected (full takeover semantics are #294; this is an accepted gap, not silently ignored), exactly as before this join upgrade.
147
163
 
148
- This preflight computes the **liveness-only** projection of ADR 0021 §1's `assignment liveness` join. The assignment dimension is #290's `AssignmentProvider` (durable GitHub-side claim); until it lands, assignment is treated as always-unassigned, which ADR 0021 §1's own table shows is safe because stalenessnot assignment is what excludes.
164
+ An explicit user instruction to proceed despite a `held` or `reclaimable` classification (`--force`, "take it anyway", equivalent) overrides the exclusion/opt-in requirement; the override and its stated reason must be recorded in the artifact (`liveness_preflight`, `reclaimable_override`, and/or `priority_rationale`). A `human-held` classification is never overridden by `--force` alone only the user's explicit answer to the recorded `alignment_questions` entry authorizes selecting it.
149
165
 
150
- `liveness claim`/`status`/`whoami` read/write the local runtime liveness stream, never GitHub issue/label/assignee state this is not a provider mutation, and the existing no-provider-mutation rule (see below) is unchanged by this slice. #290 will add the one narrow, audited GitHub-side mutation that pairs with this liveness emit.
166
+ This preflight now computes the **full** ADR 0021 §1 `assignment liveness` join (previously liveness-only, pending #290): the assignment dimension is `#290`'s `AssignmentProvider` `status()` (`context/contracts/assignment-provider-contract.md`), joined against the same liveness stream this preflight already reads.
167
+
168
+ `liveness claim`/`status`/`whoami` read/write the local runtime liveness stream, never GitHub issue/label/assignee state — this is not a provider mutation, and the two-provider-writes-only invariant in `## Contract` is unchanged by this slice. `#290` adds the one narrow, audited durable assignment claim that pairs with this liveness emit — see "Assignment Claim On Selection" below.
151
169
 
152
170
  ### 2. Enforce WIP And Shepherding
153
171
 
@@ -260,7 +278,57 @@ Branch on the returned `winner.actor`:
260
278
  - If `winner.actor !== self_actor`, this session is the loser: immediately run `npm run workflow:sidecar -- liveness release <subjectId> --actor <self_actor>`, extend `post_claim_conflict` with `{verdict_reason, winner_actor, conceded: true}`, and return to `### 3. Select Work` to reselect within the same `pull-work` pass — excluding the just-released subject — before any handoff to `plan-work`.
261
279
  - If `winner.actor === self_actor`, this session wins: record the verdict for transparency (`{verdict_reason, winner_actor: self_actor, conceded: false}`) in `post_claim_conflict` and proceed normally; do not release.
262
280
 
263
- Honesty note: this re-check narrows, but does not close, the read-then-write race between the preflight's read and the claim's write. The verdict-and-release loop above closes the "detected but advisory-only" gap ADR 0012 §4 names for THIS session's own double-hold — the loser deterministically concedes and re-selects within the same pass, a new convergence guarantee this slice adds — but it still does not provide true mutual exclusion across the read-then-write race window itself; that residual is unchanged from before. True mutual exclusion arrives with the provider assignment lease (#290) and the `verify-hold` publish gate (#293).
281
+ Honesty note: this re-check narrows, but does not close, the read-then-write race between the preflight's read and the claim's write. The verdict-and-release loop above closes the "detected but advisory-only" gap ADR 0012 §4 names for THIS session's own double-hold — the loser deterministically concedes and re-selects within the same pass, a new convergence guarantee this slice adds — but it still does not provide true mutual exclusion across the read-then-write race window itself; that residual is unchanged from before. `#290`'s provider assignment lease closes this gap for the **local-file** provider only: `claim`/`release`/`supersede` there are wrapped in a per-subject `mkdir`-lock (atomic create, EEXIST-spin, staleness reclaim), so two concurrent local-file `claim` calls on the same subject genuinely cannot both win — true mutual exclusion, not just detection. The **GitHub** provider (assignee/label/claim-comment) remains advisory/last-writer: `render-claim`/`render-supersede` emit `gh` argv for the calling skill to run, and nothing about a GitHub issue's assignee/label/comment state gives atomic compare-and-swap across two concurrent skill invocations — the post-claim `status` re-check above still only *detects* a lost race after the fact, it does not *prevent* one. Do not read "provider assignment lease" as closing the GitHub race; only the `verify-hold` publish gate (#293), not yet implemented, would do that for GitHub.
282
+
283
+ ### Assignment Claim On Selection
284
+
285
+ After the post-claim liveness conflict re-check above resolves (no double-hold detected, or this session won the deterministic tiebreaker and any loser has reselected) and `selected_item_ids` is otherwise ready to finalize, perform the durable assignment claim — the second and last provider-adjacent write this skill performs (see `## Contract`) — before recording `selected_item_ids` as final and before any handoff to `plan-work`.
286
+
287
+ Resolve the effective assignment provider settings once per pass:
288
+
289
+ ```bash
290
+ npm run effective-assignment-provider-settings -- --repo-path . --json
291
+ ```
292
+
293
+ If the result is `status: ask_user`, ask the user which `AssignmentProvider` (`github` or `local-file`) to use before claiming; do not silently assume. If `status: configured`, use the returned `settings.provider.kind`, `settings.provider.repo`, `settings.policy.label_name`, and `settings.policy.claim_comment_marker` for every call below.
294
+
295
+ For each selected item's `subjectId` (the same `subjectId` resolved via `resolve-slug` and used throughout this pass):
296
+
297
+ - **`kind: "github"`**: build an `--input-json` payload with `repo` (`{owner, name}`), `issue_number`, `assignee_login` (the current actor's GitHub login when known), `existing_assignee_login` and `existing_comment_id` (only when superseding an already-recorded `reclaimable`/confirmed `human-held` candidate — otherwise omit), `label_name`, `claim_comment_marker`, `ttl_seconds`, `branch`, and `artifact_dir`, then render:
298
+
299
+ ```bash
300
+ npm run assignment-provider -- render-claim \
301
+ --provider github \
302
+ --subject-id <subjectId> \
303
+ --input-json <path> \
304
+ --actor-json <path>
305
+ ```
306
+
307
+ Execute every command in the returned `gh_commands` array **verbatim** via the Bash tool, in order — never freehand `gh` text. Each `gh_commands` entry is an **argv array** (one element per argument, e.g. `["gh", "issue", "comment", "9101", "--repo", "kontourai/flow-agents", "--body", "..."]`) and MUST be executed as argv — every element passed as its own separate Bash-tool argument — **never** concatenated into a single shell-command string, and **never** run via `bash -c "..."` or any other shell re-interpretation of the joined elements. `claim_comment_body` (and, when superseding, `previous_record`) can carry attacker-influenced text (see the GitHub claim-record sanitization note in `context/contracts/assignment-provider-contract.md`); concatenating argv elements into a shell string before execution would reintroduce a shell-injection surface this render/execute split is designed to avoid. Record the rendered `record`, `claim_comment_body`, and each executed `gh_commands` entry as evidence.
308
+
309
+ - **`kind: "local-file"`**: call the local-file `claim` subcommand directly — no render step, since this is the one path that performs real I/O inside the CLI itself:
310
+
311
+ ```bash
312
+ npm run assignment-provider -- claim \
313
+ --provider local-file \
314
+ --artifact-root <artifact-root> \
315
+ --subject-id <subjectId> \
316
+ --branch <branch> \
317
+ --artifact-dir <artifact_dir> \
318
+ --actor-json <path>
319
+ ```
320
+
321
+ A non-zero exit (already claimed by a different actor) must not be silently retried or overwritten — treat it exactly like a `held`/`reclaimable` conflict and return to `### 3. Select Work` to reselect.
322
+
323
+ Then, regardless of provider kind, re-fetch current state — re-fetch the GitHub issue via `gh issue view --json assignees,labels,comments` for `github` (the local-file record is already current on disk) — and call `assignment-provider status` again, passing the same pinned `self_actor`, to **confirm** the claim landed before treating `selected_item_ids` as final:
324
+
325
+ ```bash
326
+ npm run assignment-provider -- status --provider <github|local-file> --subject-id <subjectId> --self-actor <self_actor> ...
327
+ ```
328
+
329
+ Confirm `effective.effective_state === "held"` with `effective.reason === "self_is_holder"` (the join function's signal that this session's own actor is the confirmed holder). Only after this confirmation does `selected_item_ids` become final for handoff. If confirmation fails — the claim did not land, or a different actor's record now appears — treat this exactly like a liveness double-hold: do not proceed to `plan-work` with this subject; return to `### 3. Select Work` to reselect, excluding the contested subject.
330
+
331
+ Record the render/status evidence — provider kind, subject id, actor, the rendered/executed command(s), and the confirmed `status()` result — for every selected item under `selection` and in the dedicated `assignment_claim` artifact field (see Artifact Contract above).
264
332
 
265
333
  ### 4. Anchor Check
266
334