@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.
- package/.github/CODEOWNERS +8 -0
- package/.github/workflows/ci.yml +20 -0
- package/.github/workflows/trust-reconcile.yml +62 -4
- package/CHANGELOG.md +38 -0
- package/CONTEXT.md +88 -1
- package/README.md +4 -0
- package/build/src/cli/assignment-provider.d.ts +144 -0
- package/build/src/cli/assignment-provider.js +805 -0
- package/build/src/cli/effective-assignment-provider-settings.d.ts +1 -0
- package/build/src/cli/effective-assignment-provider-settings.js +125 -0
- package/build/src/cli/validate-workflow-artifacts.js +5 -1
- package/build/src/cli/workflow-sidecar.d.ts +2 -2
- package/build/src/cli/workflow-sidecar.js +549 -145
- package/build/src/cli.js +6 -0
- package/build/src/lib/flow-resolver.d.ts +12 -6
- package/build/src/lib/flow-resolver.js +30 -14
- package/build/src/tools/validate-source-tree.js +2 -0
- package/context/contracts/artifact-contract.md +2 -0
- package/context/contracts/assignment-provider-contract.md +248 -0
- package/context/contracts/builder-kit-workflow-state-contract.md +2 -0
- package/context/contracts/decision-registry-contract.md +2 -0
- package/context/contracts/delivery-contract.md +2 -0
- package/context/contracts/execution-contract.md +25 -0
- package/context/contracts/governance-adapter-contract.md +2 -0
- package/context/contracts/knowledge-store-contract.md +197 -0
- package/context/contracts/planning-contract.md +2 -0
- package/context/contracts/probe-docs-write-contract.md +187 -0
- package/context/contracts/review-contract.md +2 -0
- package/context/contracts/sandbox-policy.md +2 -0
- package/context/contracts/standing-directives.md +13 -0
- package/context/contracts/verification-contract.md +2 -0
- package/context/contracts/work-item-contract.md +2 -0
- package/context/scripts/hooks/config-protection.js +14 -1
- package/context/scripts/hooks/lib/config-protection-remedies.js +9 -0
- package/context/scripts/hooks/stop-goal-fit.js +4 -2
- package/context/scripts/hooks/workflow-steering.js +42 -0
- package/context/settings/assignment-provider-settings.json +33 -0
- package/docs/adr/0001-flow-agents-consumes-flow.md +2 -0
- package/docs/adr/0002-flow-kits-as-extension-unit.md +2 -0
- package/docs/adr/0003-flow-agents-coordinates-kits-and-adapters.md +2 -0
- package/docs/adr/0004-gates-expect-surface-claims.md +2 -0
- package/docs/adr/0005-kubernetes-inspired-resource-contracts.md +2 -0
- package/docs/adr/0006-typescript-first-source-policy.md +2 -0
- package/docs/adr/0007-flow-skill-kit-tool-boundary.md +2 -0
- package/docs/adr/0007-skill-audit.md +2 -0
- package/docs/adr/0008-kit-operation-boundary.md +2 -0
- package/docs/adr/0009-canonical-hook-core-kit-boundary.md +2 -0
- package/docs/adr/0010-workflow-trust-state-as-hachure-bundle.md +2 -0
- package/docs/adr/0011-mcp-posture.md +2 -0
- package/docs/adr/0012-agent-coordination-as-liveness-claims.md +2 -0
- package/docs/adr/0013-context-lifecycle.md +2 -0
- package/docs/adr/0014-core-vs-domain-kit-boundary.md +2 -0
- package/docs/adr/0015-flow-flow-agents-boundary-reconciliation.md +2 -0
- package/docs/adr/0016-three-hard-boundary-model.md +2 -0
- package/docs/adr/0017-anti-gaming-trust-security-model.md +2 -0
- package/docs/adr/0018-freeze-local-shell-heuristics.md +2 -0
- package/docs/adr/0019-kit-dependency-ownership.md +2 -0
- package/docs/adr/0020-trust-reconcile-manifest-and-claim-classification.md +2 -0
- package/docs/adr/0021-assignment-leases-and-stale-claim-takeover.md +3 -1
- package/docs/adr/0022-fail-closed-delivery-reconciliation-with-governed-exemptions.md +182 -0
- package/docs/adr/README.md +49 -0
- package/docs/adr/index.md +34 -0
- package/docs/context-map.md +1 -0
- package/docs/decisions/agent-coordination.md +20 -0
- package/docs/decisions/anti-gaming-trust-security.md +20 -0
- package/docs/decisions/context-lifecycle.md +18 -0
- package/docs/decisions/core-domain-kit-boundary.md +18 -0
- package/docs/decisions/flow-flow-agents-boundary.md +18 -0
- package/docs/decisions/flow-kit.md +20 -0
- package/docs/decisions/flow-skill-kit-tool-boundary.md +20 -0
- package/docs/decisions/graph-knowledge-provider.md +63 -0
- package/docs/decisions/hook-core-kit-boundary.md +18 -0
- package/docs/decisions/index.md +22 -0
- package/docs/decisions/kit-dependency-ownership.md +18 -0
- package/docs/decisions/kit-operation-boundary.md +18 -0
- package/docs/decisions/knowledge-promote-sub-flow.md +59 -0
- package/docs/decisions/knowledge-store-provider.md +51 -0
- package/docs/decisions/kontour-resource-contract.md +18 -0
- package/docs/decisions/mcp-posture.md +18 -0
- package/docs/decisions/model-routing.md +63 -0
- package/docs/decisions/standing-directives.md +66 -0
- package/docs/decisions/three-hard-boundary-model.md +18 -0
- package/docs/decisions/trust-reconcile.md +20 -0
- package/docs/decisions/typescript-source-policy.md +48 -0
- package/docs/decisions/workflow-enforcement.md +18 -0
- package/docs/decisions/workflow-trust-state.md +20 -0
- package/docs/fixture-ownership.md +1 -0
- package/docs/workflow-shared-contracts.md +2 -1
- package/docs/workflow-usage-guide.md +7 -1
- package/evals/ci/run-baseline.sh +10 -0
- package/evals/fixtures/assignment-provider/actor-a.json +6 -0
- package/evals/fixtures/assignment-provider/actor-b.json +6 -0
- package/evals/fixtures/assignment-provider/github-issue-claimed.json +27 -0
- package/evals/fixtures/assignment-provider/github-issue-unassigned.json +7 -0
- package/evals/fixtures/assignment-provider/hostile-effective-state.json +11 -0
- package/evals/fixtures/assignment-provider/liveness-fresh.json +9 -0
- package/evals/fixtures/assignment-provider/liveness-stale.json +9 -0
- package/evals/integration/test_assignment_provider_github.sh +318 -0
- package/evals/integration/test_assignment_provider_local_file.sh +222 -0
- package/evals/integration/test_critique_supersession_roundtrip.sh +182 -0
- package/evals/integration/test_current_json_per_actor.sh +516 -0
- package/evals/integration/test_ensure_session_ownership_guard.sh +435 -0
- package/evals/integration/test_fixture_retirement_audit.sh +2 -2
- package/evals/integration/test_gate_lockdown.sh +10 -0
- package/evals/integration/test_publish_delivery.sh +21 -4
- package/evals/integration/test_pull_work_assignment_join.sh +132 -0
- package/evals/integration/test_pull_work_liveness_preflight.sh +20 -7
- package/evals/integration/test_reconcile_soundness.sh +33 -9
- package/evals/integration/test_trust_reconcile.sh +9 -8
- package/evals/integration/test_trust_reconcile_negatives.sh +608 -0
- package/evals/integration/test_workflow_sidecar_writer.sh +91 -3
- package/evals/run.sh +14 -0
- package/evals/static/test_flowdef_codeowners_coverage.sh +6 -0
- package/evals/static/test_knowledge_providers.sh +32 -0
- package/evals/static/test_workflow_skills.sh +15 -2
- package/kits/builder/skills/deliver/SKILL.md +36 -0
- package/kits/builder/skills/design-probe/SKILL.md +37 -0
- package/kits/builder/skills/pickup-probe/SKILL.md +4 -4
- package/kits/builder/skills/pull-work/SKILL.md +78 -10
- package/kits/knowledge/adapters/default-store/index.js +92 -4
- package/kits/knowledge/adapters/obsidian-store/index.js +69 -4
- package/kits/knowledge/adapters/shared/codec.js +141 -0
- package/kits/knowledge/docs/README.md +121 -2
- package/kits/knowledge/docs/store-contract.md +112 -4
- package/kits/knowledge/evals/contract-suite/suite.test.js +176 -0
- package/kits/knowledge/flows/promote.flow.json +84 -0
- package/kits/knowledge/kit.json +50 -0
- package/kits/knowledge/promote/distill.js +96 -0
- package/kits/knowledge/promote/fixtures/contradiction-registry/CONTEXT.md +7 -0
- package/kits/knowledge/promote/fixtures/contradiction-registry/docs/decisions/cache-eviction-policy.md +12 -0
- package/kits/knowledge/promote/fixtures/contradiction-registry/docs/decisions/cache-eviction.md +12 -0
- package/kits/knowledge/promote/fixtures/repo/CONTEXT.md +11 -0
- package/kits/knowledge/promote/fixtures/repo/docs/decisions/index.md +9 -0
- package/kits/knowledge/promote/fixtures/repo/docs/decisions/liveness-claim.md +12 -0
- package/kits/knowledge/promote/fixtures/session/acceptance.json +9 -0
- package/kits/knowledge/promote/fixtures/session/agents/tool-planner/events.jsonl +1 -0
- package/kits/knowledge/promote/fixtures/session/agents/tool-verifier/events.jsonl +1 -0
- package/kits/knowledge/promote/fixtures/session/agents/tool-worker-1-1/events.jsonl +1 -0
- package/kits/knowledge/promote/fixtures/session/kontourai-flow-agents-287--deliver.md +47 -0
- package/kits/knowledge/promote/fixtures/session/learning.json +35 -0
- package/kits/knowledge/promote/fixtures/session/state.json +19 -0
- package/kits/knowledge/promote/health.js +137 -0
- package/kits/knowledge/promote/index.js +176 -0
- package/kits/knowledge/promote/ingest.js +103 -0
- package/kits/knowledge/promote/lib.js +132 -0
- package/kits/knowledge/promote/link.js +84 -0
- package/kits/knowledge/promote/promote.test.js +174 -0
- package/kits/knowledge/providers/conformance/fixtures/git-repo/CONTEXT.md +12 -0
- package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/old-sprocket-shape.md +13 -0
- package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/sprocket-shape.md +14 -0
- package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/widget-format.md +14 -0
- package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/learnings/fixture-learning.md +7 -0
- package/kits/knowledge/providers/conformance/fixtures/work-item/issues.json +30 -0
- package/kits/knowledge/providers/conformance/suite.test.js +143 -0
- package/kits/knowledge/providers/git-repo/index.js +236 -0
- package/kits/knowledge/providers/health/health-pass.test.js +99 -0
- package/kits/knowledge/providers/health/index.js +153 -0
- package/kits/knowledge/providers/index.js +25 -0
- package/kits/knowledge/providers/lib/model.js +91 -0
- package/kits/knowledge/providers/lib/schema-validate.js +119 -0
- package/kits/knowledge/providers/markdown-vault/index.js +169 -0
- package/kits/knowledge/providers/neo4j/connection.js +121 -0
- package/kits/knowledge/providers/neo4j/cypher.js +190 -0
- package/kits/knowledge/providers/neo4j/fake-driver.js +123 -0
- package/kits/knowledge/providers/neo4j/fixtures/spike-ground-truth.json +29 -0
- package/kits/knowledge/providers/neo4j/graph-queries.js +210 -0
- package/kits/knowledge/providers/neo4j/index.js +280 -0
- package/kits/knowledge/providers/neo4j/integration.test.js +122 -0
- package/kits/knowledge/providers/neo4j/neo4j.test.js +219 -0
- package/kits/knowledge/providers/neo4j/sync.js +235 -0
- package/kits/knowledge/providers/work-item/index.js +204 -0
- package/package.json +7 -3
- package/schemas/assignment-provider-settings.schema.json +125 -0
- package/schemas/knowledge/edge.schema.json +54 -0
- package/schemas/knowledge/health-report.schema.json +45 -0
- package/schemas/knowledge/node.schema.json +49 -0
- package/schemas/knowledge/proposal.schema.json +53 -0
- package/scripts/README.md +1 -0
- package/scripts/ci/trust-reconcile.js +521 -24
- package/scripts/freeze-adrs.mjs +364 -0
- package/scripts/hooks/config-protection.js +14 -1
- package/scripts/hooks/evidence-capture.js +4 -1
- package/scripts/hooks/lib/config-protection-remedies.js +9 -0
- package/scripts/hooks/lib/current-pointer.js +123 -0
- package/scripts/hooks/lib/liveness-heartbeat.js +75 -35
- package/scripts/hooks/stop-goal-fit.js +4 -2
- package/scripts/hooks/workflow-steering.js +42 -0
- package/scripts/statusline/flow-agents-statusline.js +3 -1
- package/src/cli/assignment-provider.ts +927 -0
- package/src/cli/effective-assignment-provider-settings.ts +112 -0
- package/src/cli/validate-workflow-artifacts.ts +5 -1
- package/src/cli/workflow-sidecar.ts +577 -139
- package/src/cli.ts +6 -0
- package/src/lib/flow-resolver.ts +35 -14
- 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
|
|
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
|
|
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" '
|
|
758
|
-
require_text "$PICKUP_PROBE" '
|
|
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
|
|
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
|
-
-
|
|
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
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|