@kontourai/flow-agents 3.0.0 → 3.2.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 (158) hide show
  1. package/.github/workflows/ci.yml +12 -0
  2. package/CHANGELOG.md +32 -0
  3. package/CONTEXT.md +67 -1
  4. package/README.md +4 -0
  5. package/build/src/cli/assignment-provider.d.ts +188 -0
  6. package/build/src/cli/assignment-provider.js +193 -51
  7. package/build/src/cli/workflow-sidecar.d.ts +16 -6
  8. package/build/src/cli/workflow-sidecar.js +492 -45
  9. package/build/src/lib/flow-resolver.d.ts +12 -6
  10. package/build/src/lib/flow-resolver.js +30 -14
  11. package/build/src/tools/validate-source-tree.js +2 -1
  12. package/context/contracts/assignment-provider-contract.md +10 -1
  13. package/context/contracts/execution-contract.md +78 -0
  14. package/context/contracts/probe-docs-write-contract.md +187 -0
  15. package/context/scripts/hooks/config-protection.js +24 -4
  16. package/context/scripts/hooks/lib/config-protection-remedies.js +9 -0
  17. package/context/scripts/hooks/stop-goal-fit.js +262 -5
  18. package/context/scripts/hooks/workflow-steering.js +42 -0
  19. package/docs/adr/0001-flow-agents-consumes-flow.md +2 -0
  20. package/docs/adr/0002-flow-kits-as-extension-unit.md +2 -0
  21. package/docs/adr/0003-flow-agents-coordinates-kits-and-adapters.md +2 -0
  22. package/docs/adr/0004-gates-expect-surface-claims.md +2 -0
  23. package/docs/adr/0005-kubernetes-inspired-resource-contracts.md +2 -0
  24. package/docs/adr/0006-typescript-first-source-policy.md +2 -0
  25. package/docs/adr/0007-flow-skill-kit-tool-boundary.md +2 -0
  26. package/docs/adr/0007-skill-audit.md +2 -0
  27. package/docs/adr/0008-kit-operation-boundary.md +2 -0
  28. package/docs/adr/0009-canonical-hook-core-kit-boundary.md +2 -0
  29. package/docs/adr/0010-workflow-trust-state-as-hachure-bundle.md +2 -0
  30. package/docs/adr/0011-mcp-posture.md +2 -0
  31. package/docs/adr/0012-agent-coordination-as-liveness-claims.md +2 -0
  32. package/docs/adr/0013-context-lifecycle.md +2 -0
  33. package/docs/adr/0014-core-vs-domain-kit-boundary.md +2 -0
  34. package/docs/adr/0015-flow-flow-agents-boundary-reconciliation.md +2 -0
  35. package/docs/adr/0016-three-hard-boundary-model.md +2 -0
  36. package/docs/adr/0017-anti-gaming-trust-security-model.md +2 -0
  37. package/docs/adr/0018-freeze-local-shell-heuristics.md +2 -0
  38. package/docs/adr/0019-kit-dependency-ownership.md +2 -0
  39. package/docs/adr/0020-trust-reconcile-manifest-and-claim-classification.md +2 -0
  40. package/docs/adr/0021-assignment-leases-and-stale-claim-takeover.md +3 -1
  41. package/docs/adr/0022-fail-closed-delivery-reconciliation-with-governed-exemptions.md +113 -0
  42. package/docs/adr/README.md +49 -0
  43. package/docs/adr/index.md +34 -0
  44. package/docs/decisions/agent-coordination.md +20 -0
  45. package/docs/decisions/anti-gaming-trust-security.md +20 -0
  46. package/docs/decisions/context-lifecycle.md +18 -0
  47. package/docs/decisions/core-domain-kit-boundary.md +18 -0
  48. package/docs/decisions/flow-flow-agents-boundary.md +18 -0
  49. package/docs/decisions/flow-kit.md +20 -0
  50. package/docs/decisions/flow-skill-kit-tool-boundary.md +20 -0
  51. package/docs/decisions/graph-knowledge-provider.md +63 -0
  52. package/docs/decisions/hook-core-kit-boundary.md +18 -0
  53. package/docs/decisions/index.md +19 -0
  54. package/docs/decisions/kit-dependency-ownership.md +18 -0
  55. package/docs/decisions/kit-operation-boundary.md +18 -0
  56. package/docs/decisions/knowledge-promote-sub-flow.md +59 -0
  57. package/docs/decisions/kontour-resource-contract.md +18 -0
  58. package/docs/decisions/mcp-posture.md +18 -0
  59. package/docs/decisions/three-hard-boundary-model.md +18 -0
  60. package/docs/decisions/trust-reconcile.md +20 -0
  61. package/docs/decisions/typescript-source-policy.md +48 -0
  62. package/docs/decisions/workflow-enforcement.md +18 -0
  63. package/docs/decisions/workflow-trust-state.md +20 -0
  64. package/docs/fixture-ownership.md +1 -1
  65. package/docs/workflow-usage-guide.md +1 -1
  66. package/evals/ci/run-baseline.sh +6 -0
  67. package/evals/fixtures/assignment-provider/hostile-effective-state.json +11 -0
  68. package/evals/integration/test_checkpoint_signing.sh +4 -3
  69. package/evals/integration/test_current_json_per_actor.sh +516 -0
  70. package/evals/integration/test_ensure_session_ownership_guard.sh +435 -0
  71. package/evals/integration/test_gate_lockdown.sh +46 -0
  72. package/evals/integration/test_model_routing_escalation.sh +145 -0
  73. package/evals/integration/test_publish_delivery.sh +14 -6
  74. package/evals/integration/test_pull_work_liveness_preflight.sh +6 -1
  75. package/evals/integration/test_stop_hook_release.sh +552 -0
  76. package/evals/integration/test_trust_reconcile_negatives.sh +170 -0
  77. package/evals/integration/test_workflow_sidecar_writer.sh +12 -3
  78. package/evals/run.sh +10 -0
  79. package/evals/static/test_knowledge_providers.sh +13 -4
  80. package/evals/static/test_model_routing_hints.sh +107 -0
  81. package/evals/static/test_workflow_skills.sh +15 -2
  82. package/kits/builder/skills/builder-shape/SKILL.md +10 -0
  83. package/kits/builder/skills/deliver/SKILL.md +69 -11
  84. package/kits/builder/skills/design-probe/SKILL.md +47 -0
  85. package/kits/builder/skills/execute-plan/SKILL.md +13 -0
  86. package/kits/builder/skills/fix-bug/SKILL.md +17 -0
  87. package/kits/builder/skills/idea-to-backlog/SKILL.md +10 -0
  88. package/kits/builder/skills/pickup-probe/SKILL.md +4 -4
  89. package/kits/builder/skills/plan-work/SKILL.md +9 -0
  90. package/kits/builder/skills/pull-work/SKILL.md +10 -0
  91. package/kits/builder/skills/review-work/SKILL.md +11 -0
  92. package/kits/builder/skills/tdd-workflow/SKILL.md +17 -0
  93. package/kits/builder/skills/verify-work/SKILL.md +11 -0
  94. package/kits/knowledge/adapters/default-store/index.js +147 -18
  95. package/kits/knowledge/adapters/flow-runner/index.js +912 -16
  96. package/kits/knowledge/adapters/obsidian-store/index.js +97 -14
  97. package/kits/knowledge/adapters/shared/codec.js +265 -0
  98. package/kits/knowledge/docs/README.md +121 -2
  99. package/kits/knowledge/docs/store-contract.md +517 -7
  100. package/kits/knowledge/evals/audit-freshness/suite.test.js +92 -1
  101. package/kits/knowledge/evals/consolidate-incremental/suite.test.js +494 -0
  102. package/kits/knowledge/evals/consolidation/suite.test.js +1 -1
  103. package/kits/knowledge/evals/contract-suite/suite.test.js +212 -0
  104. package/kits/knowledge/evals/freshness/suite.test.js +339 -0
  105. package/kits/knowledge/evals/inbound-references/suite.test.js +351 -0
  106. package/kits/knowledge/evals/retirement/suite.test.js +1 -1
  107. package/kits/knowledge/evals/supersede-propagation/suite.test.js +384 -0
  108. package/kits/knowledge/flows/promote.flow.json +84 -0
  109. package/kits/knowledge/kit.json +15 -0
  110. package/kits/knowledge/promote/distill.js +96 -0
  111. package/kits/knowledge/promote/fixtures/contradiction-registry/CONTEXT.md +7 -0
  112. package/kits/knowledge/promote/fixtures/contradiction-registry/docs/decisions/cache-eviction-policy.md +12 -0
  113. package/kits/knowledge/promote/fixtures/contradiction-registry/docs/decisions/cache-eviction.md +12 -0
  114. package/kits/knowledge/promote/fixtures/repo/CONTEXT.md +11 -0
  115. package/kits/knowledge/promote/fixtures/repo/docs/decisions/index.md +9 -0
  116. package/kits/knowledge/promote/fixtures/repo/docs/decisions/liveness-claim.md +12 -0
  117. package/kits/knowledge/promote/fixtures/session/acceptance.json +9 -0
  118. package/kits/knowledge/promote/fixtures/session/agents/tool-planner/events.jsonl +1 -0
  119. package/kits/knowledge/promote/fixtures/session/agents/tool-verifier/events.jsonl +1 -0
  120. package/kits/knowledge/promote/fixtures/session/agents/tool-worker-1-1/events.jsonl +1 -0
  121. package/kits/knowledge/promote/fixtures/session/kontourai-flow-agents-287--deliver.md +47 -0
  122. package/kits/knowledge/promote/fixtures/session/learning.json +35 -0
  123. package/kits/knowledge/promote/fixtures/session/state.json +19 -0
  124. package/kits/knowledge/promote/health.js +137 -0
  125. package/kits/knowledge/promote/index.js +176 -0
  126. package/kits/knowledge/promote/ingest.js +103 -0
  127. package/kits/knowledge/promote/lib.js +132 -0
  128. package/kits/knowledge/promote/link.js +84 -0
  129. package/kits/knowledge/promote/promote.test.js +174 -0
  130. package/kits/knowledge/providers/conformance/suite.test.js +18 -0
  131. package/kits/knowledge/providers/index.js +1 -0
  132. package/kits/knowledge/providers/neo4j/connection.js +121 -0
  133. package/kits/knowledge/providers/neo4j/cypher.js +190 -0
  134. package/kits/knowledge/providers/neo4j/fake-driver.js +123 -0
  135. package/kits/knowledge/providers/neo4j/fixtures/spike-ground-truth.json +29 -0
  136. package/kits/knowledge/providers/neo4j/graph-queries.js +210 -0
  137. package/kits/knowledge/providers/neo4j/index.js +280 -0
  138. package/kits/knowledge/providers/neo4j/integration.test.js +122 -0
  139. package/kits/knowledge/providers/neo4j/neo4j.test.js +219 -0
  140. package/kits/knowledge/providers/neo4j/sync.js +235 -0
  141. package/package.json +4 -2
  142. package/schemas/workflow-handoff.schema.json +6 -0
  143. package/scripts/README.md +1 -0
  144. package/scripts/ci/mint-attestation.js +33 -6
  145. package/scripts/ci/trust-reconcile.js +144 -26
  146. package/scripts/freeze-adrs.mjs +364 -0
  147. package/scripts/hooks/config-protection.js +24 -4
  148. package/scripts/hooks/evidence-capture.js +4 -1
  149. package/scripts/hooks/lib/config-protection-remedies.js +9 -0
  150. package/scripts/hooks/lib/current-pointer.js +123 -0
  151. package/scripts/hooks/lib/liveness-heartbeat.js +75 -35
  152. package/scripts/hooks/stop-goal-fit.js +262 -5
  153. package/scripts/hooks/workflow-steering.js +42 -0
  154. package/scripts/statusline/flow-agents-statusline.js +3 -1
  155. package/src/cli/assignment-provider.ts +239 -59
  156. package/src/cli/workflow-sidecar.ts +529 -43
  157. package/src/lib/flow-resolver.ts +35 -14
  158. package/src/tools/validate-source-tree.ts +2 -1
@@ -749,6 +749,176 @@ else
749
749
  _fail "push-event-failing-verify: expected the push-event no-op line -- output: $out7v"
750
750
  fi
751
751
 
752
+ # 8. #379 per-session delivery paths — concurrent-delivery collision resolution.
753
+ # resolveDeliveryCandidates() now discovers delivery/<slug>/trust.bundle per-session dirs
754
+ # in addition to the flat path; discoverBundle() selects the candidate whose checkpoint
755
+ # attests THIS change (ancestor-or-equal, same bundleAttestsThisChange() semantics the flat
756
+ # staleness gate uses), ignoring stale siblings from OTHER concurrent sessions. A shared
757
+ # flat path guaranteed a git conflict between any two concurrent deliveries → a DIRTY PR →
758
+ # NO pull_request workflows → the required check silently never ran (field #330/#358/#378).
759
+ echo ""
760
+ echo "=== 8. #379 per-session delivery paths (concurrent-delivery collision) ==="
761
+
762
+ PERSESSION_TMPROOT="$(mktemp -d)"
763
+ # Chain per-session cleanup onto the existing DECLARED cleanup without clobbering the trap.
764
+ trap 'cleanup_declared; rm -rf "$PERSESSION_TMPROOT"' EXIT
765
+
766
+ # write_session_seal <repo_root> <slug> <label> <commit_sha>
767
+ # Writes delivery/<slug>/trust.bundle (a well-formed schemaVersion-5 bundle whose only
768
+ # claimed-pass command is <label>) + delivery/<slug>/trust.checkpoint.json (naming
769
+ # <commit_sha>) — the per-session analogue of write_fresh_bundle + write_stale_checkpoint.
770
+ write_session_seal() {
771
+ local repo_root="$1" slug="$2" label="$3" sha="$4"
772
+ local sdir="$repo_root/delivery/$slug"
773
+ mkdir -p "$sdir"
774
+ cat > "$sdir/trust.bundle" << EOF
775
+ {
776
+ "schemaVersion": 5,
777
+ "source": "test-fixture:per-session:$slug",
778
+ "claims": [
779
+ {
780
+ "id": "c1", "claimType": "workflow.check.build", "value": "pass", "status": "verified",
781
+ "subjectId": "$slug/build", "facet": "flow-agents.workflow", "subjectType": "workflow-check",
782
+ "fieldOrBehavior": "build", "createdAt": "2026-07-01T00:00:00Z", "updatedAt": "2026-07-01T00:00:00Z",
783
+ "impactLevel": "high", "verificationPolicyId": "policy:workflow.check.build"
784
+ }
785
+ ],
786
+ "evidence": [
787
+ {
788
+ "id": "ev1", "claimId": "c1", "evidenceType": "test_output", "method": "validation",
789
+ "sourceRef": "$slug/command-log.jsonl", "excerptOrSummary": "build",
790
+ "observedAt": "2026-07-01T00:00:00Z", "collectedBy": "flow-agents/evidence-capture",
791
+ "passing": true,
792
+ "execution": { "runner": "bash", "label": "$label", "isError": false, "exitCode": 0 }
793
+ }
794
+ ],
795
+ "policies": [], "events": []
796
+ }
797
+ EOF
798
+ printf '{"schema_version":"1.0","slug":"%s","work_item":null,"status":"delivered","phase":"release","sealed_at":"2026-01-01T00:00:00Z","commit_sha":"%s","checkpoint":{"asOf":"2026-01-01T00:00:00.000Z","statusByClaimId":{}}}' \
799
+ "$slug" "$sha" > "$sdir/trust.checkpoint.json"
800
+ }
801
+
802
+ FRESH_LABEL_8="node -e 'process.exit(0)'"
803
+
804
+ # 8a. OWNER WINS: two sibling session dirs — one OWNING (checkpoint commit_sha == this
805
+ # change's sha), one STALE (a different sha). The reconciler MUST pick the owner and
806
+ # reconcile it; the stale sibling is ignored, NOT a failure.
807
+ CASE8A="$PERSESSION_TMPROOT/collision-owner-wins"
808
+ write_session_seal "$CASE8A" "owning-session" "$FRESH_LABEL_8" "$OURS_SHA"
809
+ write_session_seal "$CASE8A" "stale-sibling" "echo stale-should-never-be-reconciled" "$STALE_SHA"
810
+ out8a="$(TRUST_RECONCILE_SHA="$OURS_SHA" TRUST_RECONCILE_COMMANDS="$FRESH_LABEL_8" \
811
+ node "$RECONCILE" --repo-root "$CASE8A" 2>&1)"
812
+ code8a=$?
813
+ if [[ $code8a -eq 0 ]]; then
814
+ _pass "persession-owner-wins: reconciler exits 0 (owning per-session candidate selected, stale sibling ignored)"
815
+ else
816
+ _fail "persession-owner-wins: expected exit 0, got $code8a -- output: $out8a"
817
+ fi
818
+ if echo "$out8a" | grep -qF "selected delivery candidate delivery/owning-session/trust.bundle"; then
819
+ _pass "persession-owner-wins: emitted the #379 selection line naming the owning session dir"
820
+ else
821
+ _fail "persession-owner-wins: expected the #379 selection line for delivery/owning-session/trust.bundle -- output: $out8a"
822
+ fi
823
+ if echo "$out8a" | grep -qF "RECONCILED"; then
824
+ _pass "persession-owner-wins: Step 2 reconciled the owning candidate's claimed-pass command"
825
+ else
826
+ _fail "persession-owner-wins: expected RECONCILED -- output: $out8a"
827
+ fi
828
+ if echo "$out8a" | grep -qF "bundle-required-no-declared-marker"; then
829
+ _fail "persession-owner-wins: must NOT fail closed — an owning candidate exists"
830
+ else
831
+ _pass "persession-owner-wins: does not fail closed (owning candidate found)"
832
+ fi
833
+
834
+ # 8b. NO OWNER: two sibling session dirs, BOTH stale (neither attests this change), no
835
+ # DECLARED marker -> fail closed EXACTLY as bundle-absence, plus the #379 concurrency
836
+ # hint so the next agent can diagnose a collision rather than a plain stale bundle.
837
+ CASE8B="$PERSESSION_TMPROOT/collision-no-owner"
838
+ OTHER_STALE_SHA="1111111111111111111111111111111111111111"
839
+ write_session_seal "$CASE8B" "session-a" "echo a" "$STALE_SHA"
840
+ write_session_seal "$CASE8B" "session-b" "echo b" "$OTHER_STALE_SHA"
841
+ out8b="$(TRUST_RECONCILE_SHA="$OURS_SHA" TRUST_RECONCILE_COMMANDS="$FRESH_LABEL_8" \
842
+ node "$RECONCILE" --repo-root "$CASE8B" 2>&1)"
843
+ code8b=$?
844
+ if [[ $code8b -ne 0 ]]; then
845
+ _pass "persession-no-owner: reconciler exits non-zero ($code8b) -- no candidate attests this change, fail closed"
846
+ else
847
+ _fail "persession-no-owner: expected non-zero exit, got 0 -- output: $out8b"
848
+ fi
849
+ if echo "$out8b" | grep -qF "none attests this change $OURS_SHA"; then
850
+ _pass "persession-no-owner: emitted the #379 concurrency hint (none attests this change)"
851
+ else
852
+ _fail "persession-no-owner: expected the #379 'none attests this change' hint -- output: $out8b"
853
+ fi
854
+ if echo "$out8b" | grep -qF "bundle-required-no-declared-marker"; then
855
+ _pass "persession-no-owner: emitted 'bundle-required-no-declared-marker' (same fail-closed path as bundle-absence)"
856
+ else
857
+ _fail "persession-no-owner: expected 'bundle-required-no-declared-marker' -- output: $out8b"
858
+ fi
859
+
860
+ # 8c. BACK-COMPAT COEXISTENCE: a flat legacy owner (delivery/trust.bundle) alongside a stale
861
+ # per-session sibling -> the flat owner is still selected and reconciled (the flat path
862
+ # stays supported; per-session discovery does not break it).
863
+ CASE8C="$PERSESSION_TMPROOT/flat-owner-persession-stale"
864
+ write_fresh_bundle "$CASE8C" "$FRESH_LABEL_8" # flat delivery/trust.bundle
865
+ write_stale_checkpoint "$CASE8C" "$OURS_SHA" # flat delivery/trust.checkpoint.json (owns)
866
+ write_session_seal "$CASE8C" "stale-session" "echo ignored" "$STALE_SHA" # per-session stale sibling
867
+ out8c="$(TRUST_RECONCILE_SHA="$OURS_SHA" TRUST_RECONCILE_COMMANDS="$FRESH_LABEL_8" \
868
+ node "$RECONCILE" --repo-root "$CASE8C" 2>&1)"
869
+ code8c=$?
870
+ if [[ $code8c -eq 0 ]]; then
871
+ _pass "flat-owner-coexist: reconciler exits 0 (flat legacy owner selected despite a stale per-session sibling)"
872
+ else
873
+ _fail "flat-owner-coexist: expected exit 0, got $code8c -- output: $out8c"
874
+ fi
875
+ if echo "$out8c" | grep -qF "RECONCILED"; then
876
+ _pass "flat-owner-coexist: Step 2 reconciled the flat owner (back-compat preserved)"
877
+ else
878
+ _fail "flat-owner-coexist: expected RECONCILED -- output: $out8c"
879
+ fi
880
+
881
+ # 8d. PREFER-NEWEST among multiple OWNING candidates (the merge-commit-repo / concurrent-PR
882
+ # coexistence case). An inherited FLAT bundle can attest a REAL ANCESTOR of this change
883
+ # (committed on the trunk before this branch), AND this session's per-session bundle attests
884
+ # a NEWER ancestor. "First-fresh-wins" would wrongly pick the stale flat bundle because it
885
+ # sorts first; the reconciler must instead pick the NEWEST-owning candidate (the per-session
886
+ # one). Uses a REAL git repo so `git merge-base --is-ancestor` resolves the parent→child
887
+ # relationship (synthetic shas cannot exercise the ancestor comparison).
888
+ NEWEST_REPO="$PERSESSION_TMPROOT/prefer-newest-git"
889
+ mkdir -p "$NEWEST_REPO"
890
+ git -C "$NEWEST_REPO" init -q
891
+ git -C "$NEWEST_REPO" config user.email "test@example.com"
892
+ git -C "$NEWEST_REPO" config user.name "Test"
893
+ echo "base" > "$NEWEST_REPO/f.txt"; git -C "$NEWEST_REPO" add f.txt; git -C "$NEWEST_REPO" commit -q -m "base (inherited flat seal sealed here)"
894
+ FLAT_ANCESTOR_SHA="$(git -C "$NEWEST_REPO" rev-parse HEAD)"
895
+ echo "delivery" >> "$NEWEST_REPO/f.txt"; git -C "$NEWEST_REPO" add f.txt; git -C "$NEWEST_REPO" commit -q -m "this session's delivery commit"
896
+ PERSESSION_OWNER_SHA="$(git -C "$NEWEST_REPO" rev-parse HEAD)"
897
+ echo "head" >> "$NEWEST_REPO/f.txt"; git -C "$NEWEST_REPO" add f.txt; git -C "$NEWEST_REPO" commit -q -m "seal commit (HEAD)"
898
+ NEWEST_HEAD_SHA="$(git -C "$NEWEST_REPO" rev-parse HEAD)"
899
+ # Flat owner attesting the OLD ancestor; per-session owner attesting the NEWER ancestor.
900
+ write_fresh_bundle "$NEWEST_REPO" "$FRESH_LABEL_8" # flat delivery/trust.bundle
901
+ write_stale_checkpoint "$NEWEST_REPO" "$FLAT_ANCESTOR_SHA" # flat checkpoint -> OLD ancestor
902
+ write_session_seal "$NEWEST_REPO" "this-session" "$FRESH_LABEL_8" "$PERSESSION_OWNER_SHA" # per-session -> NEWER ancestor
903
+ out8d="$(TRUST_RECONCILE_SHA="$NEWEST_HEAD_SHA" TRUST_RECONCILE_COMMANDS="$FRESH_LABEL_8" \
904
+ node "$RECONCILE" --repo-root "$NEWEST_REPO" 2>&1)"
905
+ code8d=$?
906
+ if [[ $code8d -eq 0 ]]; then
907
+ _pass "prefer-newest: reconciler exits 0 (both flat and per-session own; newest selected)"
908
+ else
909
+ _fail "prefer-newest: expected exit 0, got $code8d -- output: $out8d"
910
+ fi
911
+ if echo "$out8d" | grep -qF "selected delivery candidate delivery/this-session/trust.bundle"; then
912
+ _pass "prefer-newest: selected the NEWER per-session bundle over the older inherited flat bundle"
913
+ else
914
+ _fail "prefer-newest: expected the per-session bundle to be selected (newest-owning) -- output: $out8d"
915
+ fi
916
+ if echo "$out8d" | grep -qF "owning, newest wins"; then
917
+ _pass "prefer-newest: emitted the 'owning, newest wins' selection detail"
918
+ else
919
+ _fail "prefer-newest: expected 'owning, newest wins' in the selection line -- output: $out8d"
920
+ fi
921
+
752
922
  echo ""
753
923
  if [[ $errors -eq 0 ]]; then
754
924
  echo "test_trust_reconcile_negatives: all checks passed."
@@ -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 ────
package/evals/run.sh CHANGED
@@ -148,6 +148,8 @@ run_static() {
148
148
  bash "$EVAL_DIR/static/test_unit_helpers.sh" || result=1
149
149
  echo ""
150
150
  bash "$EVAL_DIR/static/test_knowledge_providers.sh" || result=1
151
+ echo ""
152
+ bash "$EVAL_DIR/static/test_model_routing_hints.sh" || result=1
151
153
  return $result
152
154
  }
153
155
 
@@ -201,8 +203,14 @@ run_integration() {
201
203
  echo ""
202
204
  bash "$EVAL_DIR/integration/test_assignment_provider_github.sh" || result=1
203
205
  echo ""
206
+ bash "$EVAL_DIR/integration/test_stop_hook_release.sh" || result=1
207
+ echo ""
204
208
  bash "$EVAL_DIR/integration/test_pull_work_assignment_join.sh" || result=1
205
209
  echo ""
210
+ bash "$EVAL_DIR/integration/test_ensure_session_ownership_guard.sh" || result=1
211
+ echo ""
212
+ bash "$EVAL_DIR/integration/test_current_json_per_actor.sh" || result=1
213
+ echo ""
206
214
  bash "$EVAL_DIR/integration/test_liveness_heartbeat.sh" || result=1
207
215
  echo ""
208
216
  bash "$EVAL_DIR/integration/test_pull_work_liveness_preflight.sh" || result=1
@@ -278,6 +286,8 @@ run_integration() {
278
286
  echo ""
279
287
  bash "$EVAL_DIR/integration/test_kit_identity_trust.sh" || result=1
280
288
  echo ""
289
+ bash "$EVAL_DIR/integration/test_model_routing_escalation.sh" || result=1
290
+ echo ""
281
291
  bash "$EVAL_DIR/acceptance/prove-capture-teeth-declared.sh" || result=1
282
292
  return $result
283
293
  }
@@ -6,18 +6,27 @@
6
6
  # - providers/conformance/suite.test.js (AC3: all three providers conform)
7
7
  # - providers/health/health-pass.test.js (AC1: vault+git-repo reports;
8
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.)
9
16
  # Deterministic, dependency-free, fixture-driven — never touches a real board.
10
17
  set -uo pipefail
11
18
  ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
12
19
  cd "$ROOT_DIR"
13
20
 
14
- echo "── Knowledge store provider conformance + health (node --test) ──"
21
+ echo "── Knowledge store provider conformance + health + promote sub-flow (node --test) ──"
15
22
 
16
23
  if node --test \
17
24
  kits/knowledge/providers/conformance/suite.test.js \
18
- kits/knowledge/providers/health/health-pass.test.js; then
19
- echo " PASS: knowledge store provider conformance + health verbs"
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"
20
29
  else
21
- echo " FAIL: knowledge store provider conformance + health verbs"
30
+ echo " FAIL: knowledge store provider conformance + health verbs + promote sub-flow"
22
31
  exit 1
23
32
  fi
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env bash
2
+ # test_model_routing_hints.sh — #376
3
+ # Asserts that every delegating Builder Kit skill carries a per-step model-role
4
+ # hint pointing at the execution contract (AC1/R1), that the execution contract
5
+ # documents the escalate-on-gate-failure ladder (R2) and the Goodhart guard
6
+ # (R3, review/verify tier >= worker tier), and that review/verify skill hints
7
+ # encode that guard.
8
+ set -euo pipefail
9
+
10
+ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
11
+ cd "$ROOT_DIR"
12
+
13
+ SKILLS="kits/builder/skills"
14
+ CONTRACT="context/contracts/execution-contract.md"
15
+ POINTER="context/contracts/execution-contract.md"
16
+
17
+ pass() { echo "PASS: $1"; }
18
+ fail() { echo "FAIL: $1" >&2; exit 1; }
19
+
20
+ require_contains() {
21
+ local file="$1" pattern="$2" label="$3"
22
+ grep -Fq -- "$pattern" "$file" || fail "$label ($file)"
23
+ pass "$label"
24
+ }
25
+
26
+ skill_has_routing_hint() {
27
+ # A delegating skill must have a Model Routing section that points at the
28
+ # execution contract's routing section — the single consumption instruction.
29
+ local skill="$1"
30
+ local file="$SKILLS/$skill/SKILL.md"
31
+ [[ -f "$file" ]] || fail "$skill SKILL.md missing"
32
+ grep -q "^## Model Routing" "$file" || fail "$skill has no '## Model Routing' section"
33
+ grep -Fq "$POINTER" "$file" || fail "$skill routing hint does not point at the execution contract"
34
+ pass "$skill has a Model Routing hint pointing at the execution contract"
35
+ }
36
+
37
+ skill_names_role() {
38
+ local skill="$1" role="$2"
39
+ local file="$SKILLS/$skill/SKILL.md"
40
+ grep -Fq "$role" "$file" || fail "$skill does not name role '$role'"
41
+ pass "$skill names role '$role'"
42
+ }
43
+
44
+ # ── R1/AC1: the delegating Builder Kit skills and the role each must name ──────
45
+ # Design tier: shaping / probing / planning need design latitude.
46
+ for s in builder-shape design-probe idea-to-backlog plan-work; do
47
+ skill_has_routing_hint "$s"
48
+ skill_names_role "$s" "delegate-design"
49
+ done
50
+
51
+ # Mechanical tier: board selection / sync / scan bookkeeping.
52
+ skill_has_routing_hint "pull-work"
53
+ skill_names_role "pull-work" "delegate-mechanical"
54
+
55
+ # Implementation tier: worker slices.
56
+ skill_has_routing_hint "execute-plan"
57
+ skill_names_role "execute-plan" "delegate-implementation"
58
+
59
+ # Review / verify: default implementation, subject to the Goodhart guard (R3).
60
+ for s in review-work verify-work; do
61
+ skill_has_routing_hint "$s"
62
+ skill_names_role "$s" "delegate-implementation"
63
+ done
64
+
65
+ # Multi-delegate orchestrators carry a role table covering all three tiers.
66
+ for s in deliver fix-bug tdd-workflow; do
67
+ skill_has_routing_hint "$s"
68
+ skill_names_role "$s" "delegate-mechanical"
69
+ skill_names_role "$s" "delegate-implementation"
70
+ skill_names_role "$s" "delegate-design"
71
+ done
72
+
73
+ # ── Drift guard: any builder skill that names a tool-* delegate must be a
74
+ # registered delegating skill above (i.e., must carry a Model Routing hint).
75
+ DELEGATING="builder-shape design-probe idea-to-backlog plan-work pull-work execute-plan review-work verify-work deliver fix-bug tdd-workflow"
76
+ for file in "$SKILLS"/*/SKILL.md; do
77
+ skill="$(basename "$(dirname "$file")")"
78
+ if grep -qE "tool-(worker|planner|code-reviewer|security-reviewer|verifier|playwright)" "$file"; then
79
+ case " $DELEGATING " in
80
+ *" $skill "*) : ;;
81
+ *) fail "skill '$skill' names a tool-* delegate but has no registered Model Routing hint (register it in test_model_routing_hints.sh and add the hint)" ;;
82
+ esac
83
+ fi
84
+ done
85
+ pass "every tool-* delegating builder skill is registered with a Model Routing hint"
86
+
87
+ # ── R3/AC3: review + verify skill hints encode the Goodhart guard ─────────────
88
+ for s in review-work verify-work; do
89
+ file="$SKILLS/$s/SKILL.md"
90
+ grep -Fq "Goodhart guard" "$file" || fail "$s hint does not name the Goodhart guard"
91
+ grep -Fq "never" "$file" || fail "$s hint does not state the never-downgrade rule"
92
+ grep -Fiq "below" "$file" || fail "$s hint does not say review/verify never resolves below the checked work"
93
+ pass "$s hint encodes the Goodhart guard (never below the checked-work tier)"
94
+ done
95
+
96
+ # ── R2/AC2 + R3/AC3: the execution contract documents ladder + guard + record ─
97
+ require_contains "$CONTRACT" "delegate-mechanical < delegate-implementation < delegate-design" "contract defines the tier ladder ordering"
98
+ require_contains "$CONTRACT" "Escalation on gate failure" "contract documents the escalation ladder"
99
+ require_contains "$CONTRACT" "one tier higher" "contract escalates the fix one tier higher on gate failure"
100
+ require_contains "$CONTRACT" "Goodhart guard" "contract documents the Goodhart guard"
101
+ require_contains "$CONTRACT" "greater than or equal to" "contract states review/verify tier >= checked-work tier"
102
+ # R4: contract names the additive per-delegation recording shape (#349 consumable).
103
+ require_contains "$CONTRACT" "Routing decisions in the run artifact" "contract documents routing-in-artifact recording"
104
+ require_contains "$CONTRACT" "--escalated-from" "contract records escalations with --escalated-from"
105
+ require_contains "$CONTRACT" "flow-agents#349" "contract ties routing records to the #349 economics record"
106
+
107
+ echo "Model routing hint + ladder + Goodhart guard static checks passed."
@@ -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"
@@ -22,6 +22,16 @@ Invoke the Builder Kit `shape` flow for raw product ideas, vague build goals, cu
22
22
  - Compatibility: Direct `idea-to-backlog` usage remains valid and should behave exactly as described in `kits/builder/skills/idea-to-backlog/SKILL.md`.
23
23
  - Primitive recovery: if a user invokes `idea-to-backlog` or another primitive with missing shaping context and appears to want the product flow, explain that Builder Kit shape is the entry point and offer to route there.
24
24
 
25
+ ## Model Routing
26
+
27
+ When this skill delegates its shaping work (to `idea-to-backlog` / `design-probe`
28
+ alignment), resolve the `delegate-design` role from `.datum/config.json`
29
+ (`npx @kontourai/datum resolve delegate-design --json`) and pass the resolved
30
+ model explicitly — shaping needs design latitude, so it routes to the design
31
+ tier. See `context/contracts/execution-contract.md` § Delegation: Model Routing.
32
+ Fallback: inherit the session model when datum/config is absent, noted in the
33
+ artifact.
34
+
25
35
  ## Invocation
26
36
 
27
37
  Use this skill when the user says things like:
@@ -32,11 +32,16 @@ source of truth for the mapping):
32
32
  |---|---|
33
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
34
  | tool-planner | `delegate-design` |
35
- | tool-code-reviewer / tool-security-reviewer | `delegate-implementation` |
36
- | tool-verifier / tool-playwright | `delegate-implementation` |
35
+ | tool-code-reviewer / tool-security-reviewer | `delegate-implementation` by default, raised to the worker's tier when higher — never below the tier of the checked work (Goodhart guard) |
36
+ | tool-verifier / tool-playwright | `delegate-implementation` by default, raised to the worker's tier when higher — never below the tier of the checked work (Goodhart guard) |
37
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.
38
+ On a review/verify gate failure, re-dispatch the fix one tier higher on the
39
+ ladder and record the escalation in the session artifact via
40
+ `record-agent-event --kind escalation --role <higher> --escalated-from <lower>`
41
+ (see `context/contracts/execution-contract.md` § Escalation on gate failure and
42
+ § Routing decisions in the run artifact). If datum or the config is absent, fall
43
+ back to the runtime's inherited model and note the fallback in the session
44
+ artifact.
40
45
 
41
46
  ## Orchestrator Rule
42
47
 
@@ -236,23 +241,59 @@ Record the final local state with `advance-state`. Use `status: verified` only w
236
241
  After review, verification, evidence, and Goal Fit are clean for the same diff:
237
242
 
238
243
  1. Confirm the working tree contains only verified scope.
239
- 2. Publish the session trust bundle to `delivery/` so the CI trust-reconcile job can verify what the agent claimed. `record-release` (via the sidecar writer) does this automatically (best-effort). To publish or re-publish explicitly:
244
+ 2. Publish the session trust bundle so the CI trust-reconcile job can verify what the agent claimed. `record-release` (via the sidecar writer) does this automatically (best-effort). To publish or re-publish explicitly:
240
245
 
241
246
  ```bash
242
247
  npm run workflow:sidecar -- publish-delivery .kontourai/flow-agents/<slug>
243
248
  ```
244
249
 
245
- Then force-stage the trust artifacts for the delivery commit. They are gitignored
246
- by default (they are runtime artifacts written on every local delivery) — `-f`
247
- commits them deliberately into THIS delivery PR so CI's trust-reconcile job can
248
- reconcile the session's claims against fresh CI results:
250
+ **#379 — per-session delivery paths.** `publishDelivery()` writes to a PER-SESSION path
251
+ `delivery/<slug>/trust.bundle` (+ `trust.checkpoint.json` companions), where `<slug>` is
252
+ your session artifact dir's basename NOT the old shared flat `delivery/trust.bundle`.
253
+ This is deliberate: a shared path guaranteed a git merge conflict between ANY two
254
+ concurrent deliveries, and a conflicting PR gets no CI (see the loud callout below). The
255
+ CI reconciler discovers both the flat (back-compat) and per-session layouts and selects
256
+ the NEWEST candidate whose checkpoint attests THIS change by commit ancestry — so an older
257
+ inherited bundle that also happens to be an ancestor of your change is ignored in favour of
258
+ your fresh one, and stale siblings from other sessions are ignored. `publishDelivery()`
259
+ also prunes inherited per-session sibling seal dirs (unique-named, never a cross-PR
260
+ conflict) so `delivery/` stays small; it deliberately does NOT delete the shared flat
261
+ `delivery/trust.bundle` legacy path (a concurrent PR may still seal there, and deleting it
262
+ would cause the DIRTY→no-CI conflict in the callout below).
263
+
264
+ Then force-stage the per-session trust dir for the delivery commit. It is gitignored by
265
+ default (runtime artifacts written on every local delivery) — `-f` commits it
266
+ deliberately into THIS delivery PR so CI's trust-reconcile job can reconcile the
267
+ session's claims against fresh CI results:
249
268
 
250
269
  ```bash
251
- git add -f delivery/trust.bundle delivery/trust.checkpoint.json
270
+ git add -f delivery/<slug>/
252
271
  ```
253
272
 
254
- 3. Commit the verified diff, including the force-added `delivery/trust.bundle` and `delivery/trust.checkpoint.json`.
273
+ (If `publishDelivery()` pruned a superseded per-session SIBLING dir, stage that deletion
274
+ too — `git add -A delivery/` after the force-add. Do NOT hand-delete the flat
275
+ `delivery/trust.bundle` in a delivery PR while other PRs may still seal to it.)
276
+
277
+ 3. Commit the verified diff, including the force-added `delivery/<slug>/` trust artifacts.
255
278
  4. Push the branch.
279
+
280
+ > **⚠ LOUD FAILURE MODE — a DIRTY PR gets NO CI, silently (#335/#379).** If `main` moves
281
+ > under your open PR and produces a merge conflict, GitHub marks the PR `DIRTY` and
282
+ > **schedules NO `pull_request` workflows for it** — zero checks, no error, nothing. The
283
+ > required **Trust Reconcile** gate then silently never runs, and the PR sits unbuildable
284
+ > looking like "CI vanished" rather than "conflict." Per-session delivery paths (#379)
285
+ > remove the STRUCTURAL cause for delivery-artifact conflicts (concurrent seals no longer
286
+ > share a file), but ANY other same-file conflict with `main` can still trigger it.
287
+ > **Diagnose it explicitly — do not assume a missing required check means "not run yet":**
288
+ >
289
+ > ```bash
290
+ > gh pr view <pr> --json mergeStateStatus,mergeable,statusCheckRollup
291
+ > ```
292
+ >
293
+ > `mergeStateStatus: DIRTY` (or `CONFLICTING`) with an ABSENT Trust Reconcile check is the
294
+ > signature. Fix by rebasing/merging `main` to clear the conflict and re-pushing — that
295
+ > re-triggers the `pull_request` workflows. A green-looking PR with the required gate simply
296
+ > MISSING is not "pending"; it is this failure mode until proven otherwise.
256
297
  5. Open or update the provider change record with issue links, closing refs, evidence links, and verification summary, or record an explicit no-provider-change reason.
257
298
  6. Wait for provider checks/CI or record missing checks as `NOT_VERIFIED`.
258
299
  7. Record the gate claim for the Builder Kit `pr-open` step immediately after the PR is opened or updated:
@@ -277,6 +318,23 @@ After CI passes and the work is merged or otherwise accepted:
277
318
  2. Archive the working artifacts under `.kontourai/flow-agents/<slug>/archive/` or keep a stable link to them.
278
319
  3. Record provider records, verification evidence, durable docs targets, accepted gaps, and follow-up routing in durable docs or provider records.
279
320
  4. Promote the relevant plan, decision, evidence, and usage notes into long-lived docs such as `docs/`, `README.md`, or a project decision record.
321
+
322
+ **Assisted promotion path (Knowledge Kit `knowledge.promote` sub-flow).** Rather than
323
+ deciding WHAT to promote by ad-hoc judgement, run the Knowledge Kit promote sub-flow
324
+ (`kits/knowledge/flows/promote.flow.json`, id `knowledge.promote`) over the session
325
+ directory. It ingests the session's plan/evidence/critique/learnings/transcripts,
326
+ distills schema-valid DRAFT decision-registry deltas (per
327
+ `context/contracts/decision-registry-contract.md`), CONTEXT.md vocabulary additions,
328
+ and learning entries, links each to the PR + merge SHA + session archive + touched
329
+ topics, and health-checks the registry for contradictions (proposing merge-repair).
330
+ It is PROPOSALS-ONLY: every output lands under `<session>/proposals/` — it never writes
331
+ docs directly. You apply the drafts you accept, then record what was promoted where with
332
+ the `promote` CLI (below), which stays the recording mechanism. The sub-flow is the
333
+ assisted path; the `promote` claim is the gate. `knowledge.promote` is a true composable
334
+ FlowDefinition (a parent step can `uses_flow: "knowledge.promote"`); it is invoked here
335
+ from the promote step rather than nested as a builder gate because the `promote` CLI —
336
+ not a flow gate — is #312's recording mechanism and the sub-flow's outputs are proposals
337
+ a human/agent applies.
280
338
  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.
281
339
  6. Confirm `.kontourai/flow-agents/` runtime artifacts remain untracked before merge to `main`.
282
340
  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.
@@ -24,6 +24,16 @@ This skill is modeled after Matt Pocock's `grill-me`: interview the user relentl
24
24
  - Stop when shared understanding exists, or when the remaining uncertainty is explicitly recorded as an accepted gap.
25
25
  - Do not silently convert uncertainty into implementation work.
26
26
 
27
+ ## Model Routing
28
+
29
+ When this skill delegates its probing/alignment work, resolve the
30
+ `delegate-design` role from `.datum/config.json`
31
+ (`npx @kontourai/datum resolve delegate-design --json`) and pass the resolved
32
+ model explicitly — probing a goal, design, or recovery path needs design
33
+ latitude, so it routes to the design tier. See
34
+ `context/contracts/execution-contract.md` § Delegation: Model Routing. Fallback:
35
+ inherit the session model when datum/config is absent, noted in the artifact.
36
+
27
37
  ## When To Use
28
38
 
29
39
  Use this skill for:
@@ -82,6 +92,43 @@ Maintain a compact running record in the active artifact or conversation when no
82
92
 
83
93
  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
94
 
95
+ ## Docs-Write: Two-Delta Emission
96
+
97
+ 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:
98
+
99
+ 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).
100
+ 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.
101
+ 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).
102
+
103
+ Example — coining a new term and creating its topic file:
104
+
105
+ ```markdown
106
+ ### Retry Budget
107
+
108
+ The maximum number of automatic retries a workflow step may attempt before routing back to the user. Configured per Flow Definition step, not globally.
109
+ ```
110
+
111
+ ```markdown
112
+ ---
113
+ status: current
114
+ subject: Retry budget
115
+ decided: 2026-07-03
116
+ evidence:
117
+ - kind: session-archive
118
+ ref: .kontourai/flow-agents/<slug>/<slug>--design-probe.md
119
+ - kind: issue
120
+ ref: https://github.com/kontourai/flow-agents/issues/<n>
121
+ ---
122
+
123
+ # Retry budget
124
+
125
+ Each Flow Definition step declares its own retry budget; there is no global default. Set during design-probe alignment on <n>.
126
+ ```
127
+
128
+ 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.
129
+
130
+ Do not write a numbered ADR — `docs/adr/` is frozen history (`docs/adr/README.md`); every decision delta targets the topic-keyed registry.
131
+
85
132
  ## Stop Conditions
86
133
 
87
134
  Stop probing when one of these is true: