@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
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test_pull_work_assignment_join.sh — Two-session disjoint-selection simulation (#290).
|
|
3
|
+
#
|
|
4
|
+
# HONESTY NOTE (plan Design Decision / Stop-Short Risks): this eval simulates the assignment ⋈
|
|
5
|
+
# liveness join's exclusion property with two actor structs evaluating the same candidate
|
|
6
|
+
# subject id fixture in sequence, inside ONE process. It does NOT stand up two real concurrent
|
|
7
|
+
# agent runtimes, and does not prove true concurrency (a real race between two simultaneous
|
|
8
|
+
# writers). It is the closest *local* proof available for the issue's literal acceptance line
|
|
9
|
+
# ("two sessions running pull-work against the same backlog select disjoint issues") without a
|
|
10
|
+
# multi-runtime test harness this repo does not have — see the plan's Stop-Short Risks section.
|
|
11
|
+
# What IS proven here, deterministically: once session A's assignment claim is recorded (local-
|
|
12
|
+
# file or a rendered-then-status-confirmed GitHub fixture), session B's subsequent
|
|
13
|
+
# `assignment-provider status` / join check for the SAME subject reports it `held` (excluded),
|
|
14
|
+
# never `free` — so `pull-work`'s selection loop (kits/builder/skills/pull-work/SKILL.md,
|
|
15
|
+
# "### 1. Read Board State") would skip it for session B.
|
|
16
|
+
#
|
|
17
|
+
# Supports AC12 (disjoint-selection-simulated), AC10 (docs pointer — see
|
|
18
|
+
# docs/workflow-usage-guide.md's new Assignment Ownership subsection).
|
|
19
|
+
set -uo pipefail
|
|
20
|
+
|
|
21
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
22
|
+
CLI="$ROOT/build/src/cli.js"
|
|
23
|
+
FIXTURES="$ROOT/evals/fixtures/assignment-provider"
|
|
24
|
+
ACTOR_A="$FIXTURES/actor-a.json"
|
|
25
|
+
ACTOR_B="$FIXTURES/actor-b.json"
|
|
26
|
+
ISSUE_CLAIMED="$FIXTURES/github-issue-claimed.json"
|
|
27
|
+
LIVENESS_FRESH="$FIXTURES/liveness-fresh.json"
|
|
28
|
+
|
|
29
|
+
TMPDIR_EVAL="$(mktemp -d)"
|
|
30
|
+
trap 'rm -rf "$TMPDIR_EVAL"' EXIT
|
|
31
|
+
ARTIFACT_ROOT="$TMPDIR_EVAL/artifact-root"
|
|
32
|
+
|
|
33
|
+
errors=0
|
|
34
|
+
pass() { echo " ✓ $1"; }
|
|
35
|
+
fail() { echo " ✗ $1"; errors=$((errors + 1)); }
|
|
36
|
+
|
|
37
|
+
json_query() {
|
|
38
|
+
node -e 'const fs=require("fs"); let cur=JSON.parse(fs.readFileSync(process.argv[1],"utf8")); for (const part of process.argv[2].split(".")) cur=part==="length" ? cur.length : (Array.isArray(cur) ? cur[Number(part)] : cur[part]); console.log(cur);' "$1" "$2"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if [[ ! -f "$CLI" ]]; then
|
|
42
|
+
echo "build/src/cli.js not found — run 'npm run build' first" >&2
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
echo "=== Two-session disjoint-selection simulation (assignment ⋈ liveness join) ==="
|
|
47
|
+
|
|
48
|
+
EMPTY_LIVENESS="$TMPDIR_EVAL/liveness-empty.json"
|
|
49
|
+
echo '[]' > "$EMPTY_LIVENESS"
|
|
50
|
+
|
|
51
|
+
# --- local-file leg: session A claims, session B's join check on the same subject is excluded ---
|
|
52
|
+
|
|
53
|
+
SUBJECT_ID="kontourai/flow-agents#9301"
|
|
54
|
+
|
|
55
|
+
# "Session A" (actor A) evaluates the candidate subject before anyone has claimed it: free.
|
|
56
|
+
node "$CLI" assignment-provider status \
|
|
57
|
+
--provider local-file --artifact-root "$ARTIFACT_ROOT" --subject-id "$SUBJECT_ID" \
|
|
58
|
+
--self-actor "claude-code:eval-actor-a-session:eval-host" --liveness-events-json "$EMPTY_LIVENESS" \
|
|
59
|
+
> "$TMPDIR_EVAL/session-a-preclaim.json"
|
|
60
|
+
[[ "$(json_query "$TMPDIR_EVAL/session-a-preclaim.json" "effective.effective_state")" == "free" ]] && pass "local-file: candidate subject is free before either session claims" || fail "local-file: candidate subject is free before either session claims"
|
|
61
|
+
|
|
62
|
+
# Session A selects and claims it (pull-work's "Assignment Claim On Selection" step).
|
|
63
|
+
node "$CLI" assignment-provider claim \
|
|
64
|
+
--provider local-file --artifact-root "$ARTIFACT_ROOT" --subject-id "$SUBJECT_ID" \
|
|
65
|
+
--actor-json "$ACTOR_A" --branch "agent/claude-code-eval-actor-a-session-eval-host/flow-agents-9301" \
|
|
66
|
+
--artifact-dir ".kontourai/flow-agents/flow-agents-9301" > /dev/null
|
|
67
|
+
status=$?
|
|
68
|
+
[[ "$status" -eq 0 ]] && pass "local-file: session A's claim on the candidate subject succeeds" || fail "local-file: session A's claim on the candidate subject succeeds"
|
|
69
|
+
|
|
70
|
+
# "Session B" (actor B, a distinct actor struct — the second concurrent pull-work session)
|
|
71
|
+
# evaluates the SAME candidate subject next. Its own liveness heartbeat is fresh (it is alive),
|
|
72
|
+
# but that must not matter: the join is computed against the HOLDER's freshness, not the
|
|
73
|
+
# reader's — session B must see the subject excluded.
|
|
74
|
+
NOW_ISO="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
75
|
+
cat > "$TMPDIR_EVAL/liveness-actor-a-fresh.json" <<JSON
|
|
76
|
+
[
|
|
77
|
+
{"type":"claim","subjectId":"$SUBJECT_ID","actor":"claude-code:eval-actor-a-session:eval-host","at":"$NOW_ISO","ttlSeconds":1800}
|
|
78
|
+
]
|
|
79
|
+
JSON
|
|
80
|
+
node "$CLI" assignment-provider status \
|
|
81
|
+
--provider local-file --artifact-root "$ARTIFACT_ROOT" --subject-id "$SUBJECT_ID" \
|
|
82
|
+
--self-actor "claude-code:eval-actor-b-session:eval-host" \
|
|
83
|
+
--liveness-events-json "$TMPDIR_EVAL/liveness-actor-a-fresh.json" > "$TMPDIR_EVAL/session-b-postclaim.json"
|
|
84
|
+
[[ "$(json_query "$TMPDIR_EVAL/session-b-postclaim.json" "assignment.assignee")" == "claude-code:eval-actor-a-session:eval-host" ]] && pass "local-file: session B's status read shows actor A as the holder" || fail "local-file: session B's status read shows actor A as the holder"
|
|
85
|
+
[[ "$(json_query "$TMPDIR_EVAL/session-b-postclaim.json" "effective.effective_state")" == "held" ]] && pass "local-file: session B's join check reports held (excluded) for the same subject session A just claimed" || fail "local-file: session B's join check reports held (excluded) for the same subject session A just claimed"
|
|
86
|
+
[[ "$(json_query "$TMPDIR_EVAL/session-b-postclaim.json" "effective.effective_state")" != "free" ]] && pass "local-file: subject is definitively NOT free for session B (disjoint selection holds)" || fail "local-file: subject is definitively NOT free for session B (disjoint selection holds)"
|
|
87
|
+
|
|
88
|
+
# Session B, correctly excluding the held subject, selects a DIFFERENT subject instead — the
|
|
89
|
+
# literal "select disjoint issues" property, one selection loop iteration at a time.
|
|
90
|
+
SUBJECT_ID_2="kontourai/flow-agents#9302"
|
|
91
|
+
node "$CLI" assignment-provider status \
|
|
92
|
+
--provider local-file --artifact-root "$ARTIFACT_ROOT" --subject-id "$SUBJECT_ID_2" \
|
|
93
|
+
--self-actor "claude-code:eval-actor-b-session:eval-host" --liveness-events-json "$EMPTY_LIVENESS" \
|
|
94
|
+
> "$TMPDIR_EVAL/session-b-alternate.json"
|
|
95
|
+
[[ "$(json_query "$TMPDIR_EVAL/session-b-alternate.json" "effective.effective_state")" == "free" ]] && pass "local-file: session B's alternate candidate is free — it can select a disjoint subject" || fail "local-file: session B's alternate candidate is free — it can select a disjoint subject"
|
|
96
|
+
node "$CLI" assignment-provider claim \
|
|
97
|
+
--provider local-file --artifact-root "$ARTIFACT_ROOT" --subject-id "$SUBJECT_ID_2" \
|
|
98
|
+
--actor-json "$ACTOR_B" --branch "agent/claude-code-eval-actor-b-session-eval-host/flow-agents-9302" \
|
|
99
|
+
--artifact-dir ".kontourai/flow-agents/flow-agents-9302" > /dev/null
|
|
100
|
+
[[ $? -eq 0 ]] && pass "local-file: session B claims the disjoint subject successfully" || fail "local-file: session B claims the disjoint subject successfully"
|
|
101
|
+
|
|
102
|
+
# Final proof: the two sessions' claimed subject sets are disjoint.
|
|
103
|
+
node "$CLI" assignment-provider list --provider local-file --artifact-root "$ARTIFACT_ROOT" --actor-json "$ACTOR_A" > "$TMPDIR_EVAL/list-a.json"
|
|
104
|
+
node "$CLI" assignment-provider list --provider local-file --artifact-root "$ARTIFACT_ROOT" --actor-json "$ACTOR_B" > "$TMPDIR_EVAL/list-b.json"
|
|
105
|
+
DISJOINT="$(node -e '
|
|
106
|
+
const fs = require("fs");
|
|
107
|
+
const a = JSON.parse(fs.readFileSync(process.argv[1], "utf8")).subject_ids;
|
|
108
|
+
const b = JSON.parse(fs.readFileSync(process.argv[2], "utf8")).subject_ids;
|
|
109
|
+
const overlap = a.filter((id) => b.includes(id));
|
|
110
|
+
console.log(overlap.length === 0 && a.length === 1 && b.length === 1 ? "yes" : "no");
|
|
111
|
+
' "$TMPDIR_EVAL/list-a.json" "$TMPDIR_EVAL/list-b.json")"
|
|
112
|
+
[[ "$DISJOINT" == "yes" ]] && pass "session A and session B's claimed subject sets are disjoint (one each, no overlap)" || fail "session A and session B's claimed subject sets are disjoint (one each, no overlap)"
|
|
113
|
+
|
|
114
|
+
# --- GitHub leg: a rendered-and-status-confirmed claim on a fixture also excludes a second reader ---
|
|
115
|
+
|
|
116
|
+
# The already-claimed GitHub fixture stands in for "session A already claimed and the render
|
|
117
|
+
# was executed + status-confirmed" (SKILL.md's "Assignment Claim On Selection" round trip).
|
|
118
|
+
# "Session B" reads the SAME fixture next with a fresh liveness fixture for the holder: held.
|
|
119
|
+
node "$CLI" assignment-provider status --provider github --issue-json "$ISSUE_CLAIMED" \
|
|
120
|
+
--self-actor "claude-code:eval-actor-b-session:eval-host" \
|
|
121
|
+
--liveness-events-json "$LIVENESS_FRESH" --now "2026-06-01T12:20:00Z" \
|
|
122
|
+
> "$TMPDIR_EVAL/github-session-b.json"
|
|
123
|
+
[[ "$(json_query "$TMPDIR_EVAL/github-session-b.json" "effective.effective_state")" == "held" ]] && pass "github: session B's join check on an already-claimed fixture issue reports held (excluded)" || fail "github: session B's join check on an already-claimed fixture issue reports held (excluded)"
|
|
124
|
+
[[ "$(json_query "$TMPDIR_EVAL/github-session-b.json" "effective.effective_state")" != "free" ]] && pass "github: subject is definitively NOT free for session B" || fail "github: subject is definitively NOT free for session B"
|
|
125
|
+
|
|
126
|
+
echo ""
|
|
127
|
+
if [[ "$errors" -eq 0 ]]; then
|
|
128
|
+
echo "test_pull_work_assignment_join: all checks passed."
|
|
129
|
+
else
|
|
130
|
+
echo "test_pull_work_assignment_join: $errors check(s) failed."
|
|
131
|
+
fi
|
|
132
|
+
exit "$errors"
|
|
@@ -151,7 +151,12 @@ if flow_agents_node "$WRITER" ensure-session \
|
|
|
151
151
|
--title "Parity Check" \
|
|
152
152
|
--summary "resolve-slug parity fixture." \
|
|
153
153
|
--timestamp "2026-07-01T00:00:00Z" >"$TMPDIR_EVAL/b-ensure.out" 2>"$TMPDIR_EVAL/b-ensure.err"; then
|
|
154
|
-
|
|
154
|
+
# #291: ensure-session (when an ambient actor resolves, even without an explicit --actor)
|
|
155
|
+
# now also creates artifact-root-level "assignment/" (the ownership-guard's durable local-file
|
|
156
|
+
# claim record dir) and "current/" (the per-actor current.json projection dir) sibling
|
|
157
|
+
# directories, alongside the pre-existing "liveness/" dir -- all three must be excluded here so
|
|
158
|
+
# this find still lands on the real session slug directory, not a sibling artifact dir.
|
|
159
|
+
B_SESSION_DIR="$(find "$B_ROOT" -mindepth 1 -maxdepth 1 -type d ! -name liveness ! -name assignment ! -name current | head -n1)"
|
|
155
160
|
B_SLUG_FROM_DIR="$(basename "$B_SESSION_DIR")"
|
|
156
161
|
B_SLUG_FROM_RESOLVE="$(flow_agents_node "$WRITER" resolve-slug 'owner/repo#42' 2>"$TMPDIR_EVAL/b-resolve.err")"
|
|
157
162
|
if [[ -n "$B_SESSION_DIR" ]] && [[ "$B_SLUG_FROM_DIR" == "$B_SLUG_FROM_RESOLVE" ]]; then
|
|
@@ -329,7 +334,7 @@ require_text "$PULL" 'liveness claim <subjectId>' "pull-work references liveness
|
|
|
329
334
|
require_text "$PULL" 'reading each row.s raw .status. field \(never .label.\)' "pull-work instructs consuming raw status, never label (AC1, AC8)"
|
|
330
335
|
require_text "$PULL" 'a .verified. row for an actor other than self.*.held.' "pull-work documents held classification (AC1, AC2, AC8)"
|
|
331
336
|
require_text "$PULL" 'excluded from the ready set by default' "pull-work excludes held candidates by default (AC2, AC8)"
|
|
332
|
-
require_text "$PULL" '
|
|
337
|
+
require_text "$PULL" 'effective_state: .reclaimable.' "pull-work documents reclaimable classification via the assignment-provider join (AC1, AC3, AC8, #290)"
|
|
333
338
|
require_text "$PULL" 'requires an explicit recorded opt-in' "pull-work requires an explicit recorded opt-in for reclaimable selection (AC3, AC8)"
|
|
334
339
|
require_text "$PULL" 'never a silent normal pick' "pull-work states reclaimable is never a silent normal pick (AC3, AC8)"
|
|
335
340
|
require_text "$PULL" 'a .verified. row for the resolved self actor.*.mine.' "pull-work documents mine classification (AC1, AC5, AC8)"
|
|
@@ -348,10 +353,15 @@ require_text "$PICKUP_PROBE" 'flags the selected item .reclaimable., treat the r
|
|
|
348
353
|
# ─── F2-F6 (fix-plan iteration 1) static assertions ─────────────────────────
|
|
349
354
|
require_text "$PULL" 'group all rows for that .subjectId. by subject' "pull-work classifies per-subject (grouped rows), not per-row (F2, AC1, AC8)"
|
|
350
355
|
require_text "$PULL" 'classify in this precedence order' "pull-work states the classification is an ordered precedence rule (F2, AC8)"
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
356
|
+
# F2's original numbered precedence list was liveness-only (#166); #290 (Wave 3) replaced it with
|
|
357
|
+
# the full ADR 0021 §1 assignment ⋈ liveness join, computed via `assignment-provider status`'s
|
|
358
|
+
# `effective_state`/`reason` fields. These assertions are updated to match the new five-row list
|
|
359
|
+
# (mine/held/reclaimable/human-held/free) rather than the old four-row liveness-only list.
|
|
360
|
+
require_text "$PULL" '^1\. .effective_state: .held.. with .reason: .self_is_holder..' "pull-work's precedence rule step 1 (mine, via self_is_holder) is numbered verbatim (F2, #290, AC8)"
|
|
361
|
+
require_text "$PULL" '^2\. else .effective_state: .held..' "pull-work's precedence rule step 2 (held, excluded) is numbered verbatim (F2, #290, AC8)"
|
|
362
|
+
require_text "$PULL" '^3\. else .effective_state: .reclaimable..' "pull-work's precedence rule step 3 (reclaimable) is numbered verbatim (F2, #290, AC8)"
|
|
363
|
+
require_text "$PULL" '^4\. else .effective_state: .human-held..' "pull-work's precedence rule step 4 (human-held) is numbered verbatim (F2, #290, AC11, AC8)"
|
|
364
|
+
require_text "$PULL" '^5\. else .effective_state: .free..' "pull-work's precedence rule step 5 (free) is numbered verbatim (F2, #290, AC8)"
|
|
355
365
|
require_text "$PULL" 'double-hold' "pull-work names the own-actor + other-actor co-existing row combination a double-hold (F2, AC8)"
|
|
356
366
|
require_text "$PULL" 'explicit conflict warning \(per ADR 0012 §4 detection' "pull-work cites ADR 0012 §4 detection for the double-hold conflict warning (F2, AC8)"
|
|
357
367
|
require_text "$PULL" 'never silently resolve it to .mine.' "pull-work states a double-hold is never silently resolved to mine (F2, AC8)"
|
|
@@ -373,7 +383,10 @@ require_text "$PULL" '### Post-Claim Conflict Re-check' "pull-work documents the
|
|
|
373
383
|
require_text "$PULL" 'coexists with this session.s own just-emitted claim on the same subject' "pull-work's post-claim re-check detects a double-hold against the just-emitted claim (F6, AC8)"
|
|
374
384
|
require_text "$PULL" 'surface the conflict prominently in the user-facing output and instruct the user to coordinate before proceeding' "pull-work instructs surfacing the post-claim conflict prominently and coordinating before proceeding, not silently continuing (F6, AC8)"
|
|
375
385
|
require_text "$PULL" 'does not provide true mutual exclusion across the read-then-write race window itself' "pull-work's honesty note states the exclusion guarantee still does not provide true mutual exclusion across the read-then-write race window (F6, AC8; updated #320 wording — see also test_liveness_verdict.sh)"
|
|
376
|
-
require_text "$PULL" '
|
|
386
|
+
require_text "$PULL" '.#290.\x27s provider assignment lease closes this gap for the .{0,2}local-file.{0,2} provider only' "pull-work scopes true mutual exclusion to the local-file provider only, via #290's lock (fix-plan iteration 1, F5, AC8)"
|
|
387
|
+
require_text "$PULL" 'two concurrent local-file .claim. calls on the same subject genuinely cannot both win' "pull-work states local-file claim/claim races genuinely cannot both win under #290's lock (fix-plan iteration 1, F5, AC8)"
|
|
388
|
+
require_text "$PULL" 'The .{0,2}GitHub.{0,2} provider \(assignee/label/claim-comment\) remains advisory/last-writer' "pull-work does NOT overclaim true mutual exclusion for the GitHub provider — advisory/last-writer, detect not prevent (fix-plan iteration 1, F5, AC8)"
|
|
389
|
+
require_text "$PULL" 'Do not read .provider assignment lease. as closing the GitHub race; only the .verify-hold. publish gate \(#293\)' "pull-work explicitly warns not to read #290 as closing the GitHub race — only #293's verify-hold gate would (fix-plan iteration 1, F5, AC8)"
|
|
377
390
|
|
|
378
391
|
|
|
379
392
|
echo ""
|
|
@@ -9,8 +9,13 @@
|
|
|
9
9
|
# B. REAL-VERIFY-CLOSED: build passes but a fake "real verify" (eval:static substitute)
|
|
10
10
|
# fails → trust-reconcile exits 1 (PRE-FIX this would exit 0 because only build ran).
|
|
11
11
|
#
|
|
12
|
-
# C. REAL-VERIFY-PASSES: the comprehensive verify resolves green
|
|
13
|
-
#
|
|
12
|
+
# C. REAL-VERIFY-PASSES: the comprehensive verify resolves green, no bundle, but a
|
|
13
|
+
# well-formed in-scope delivery/DECLARED marker exempts Step 2 (ADR 0022 §1) → exits 0
|
|
14
|
+
# with the DECLARED (no-agent-delivery) line (legit work is not false-blocked; the
|
|
15
|
+
# bundle-absence branch is bundle-required by default post-ADR-0022, so this case is
|
|
16
|
+
# now anchored by a DECLARED marker rather than by bundle absence alone — see
|
|
17
|
+
# evals/integration/test_trust_reconcile_negatives.sh section 7 for the no-marker /
|
|
18
|
+
# out-of-scope-marker fail-closed cases this test does NOT re-cover).
|
|
14
19
|
#
|
|
15
20
|
# D. CHECKPOINT-BYPASS-CLOSED: a checkpoint-only bundle (no evidence/claims, statusByClaimId
|
|
16
21
|
# all-passed) → exits 1 with checkpoint-bypass divergence (not a silent skip).
|
|
@@ -56,8 +61,14 @@ fs.writeFileSync('$PKG_NO_VERIFY/package.json', JSON.stringify(pkg, null, 2));
|
|
|
56
61
|
"
|
|
57
62
|
|
|
58
63
|
# ─── Minimal package.json WITH trust-reconcile-verify ─────────────────────────
|
|
64
|
+
# ADR 0022 §1 (bundle-required by default): no bundle is written for this fixture, so a
|
|
65
|
+
# well-formed, in-scope delivery/DECLARED marker (ADR 0022 §2) is seeded alongside it —
|
|
66
|
+
# this is the new legitimate no-bundle path (a real human/bot PR author with no agent
|
|
67
|
+
# delivery), preserving TEST C's original "no bundle" shape more faithfully than
|
|
68
|
+
# switching it to a --bundle fixture would (a bundle would test Step 2 reconcile, which
|
|
69
|
+
# TEST C was never about — it proves Step 1 fresh-verify success is honored).
|
|
59
70
|
PKG_WITH_VERIFY="$TMP/pkg_with_verify"
|
|
60
|
-
mkdir -p "$PKG_WITH_VERIFY"
|
|
71
|
+
mkdir -p "$PKG_WITH_VERIFY/delivery"
|
|
61
72
|
node -e "
|
|
62
73
|
const fs = require('fs');
|
|
63
74
|
const pkg = {
|
|
@@ -68,6 +79,13 @@ const pkg = {
|
|
|
68
79
|
}
|
|
69
80
|
};
|
|
70
81
|
fs.writeFileSync('$PKG_WITH_VERIFY/package.json', JSON.stringify(pkg, null, 2));
|
|
82
|
+
const declared = {
|
|
83
|
+
scope: 'ref:test-verify-passes',
|
|
84
|
+
reason: 'test fixture: real verify passes, no agent delivery bundle',
|
|
85
|
+
approved_by: 'test-harness',
|
|
86
|
+
declared_at: '2026-06-27T00:00:00Z'
|
|
87
|
+
};
|
|
88
|
+
fs.writeFileSync('$PKG_WITH_VERIFY/delivery/DECLARED', JSON.stringify(declared, null, 2));
|
|
71
89
|
"
|
|
72
90
|
|
|
73
91
|
# ─── Minimal package.json with failing verify ─────────────────────────────────
|
|
@@ -255,22 +273,28 @@ fi
|
|
|
255
273
|
# package.json has passing trust-reconcile-verify → exits 0 (not false-blocked)
|
|
256
274
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
257
275
|
echo ""
|
|
258
|
-
echo "=== TEST C: REAL-VERIFY-PASSES — passing verify + no bundle → exits 0 ==="
|
|
276
|
+
echo "=== TEST C: REAL-VERIFY-PASSES — passing verify + in-scope delivery/DECLARED marker (no bundle) → exits 0 with DECLARED exemption ==="
|
|
259
277
|
|
|
260
|
-
outC=$(node "$RECONCILE" \
|
|
278
|
+
outC=$(TRUST_RECONCILE_REF="test-verify-passes" node "$RECONCILE" \
|
|
261
279
|
--repo-root "$PKG_WITH_VERIFY" 2>&1)
|
|
262
280
|
exitC=$?
|
|
263
281
|
|
|
264
282
|
if [[ $exitC -eq 0 ]]; then
|
|
265
|
-
_pass "REAL-VERIFY-PASSES: exits 0 when real verify passes and
|
|
283
|
+
_pass "REAL-VERIFY-PASSES: exits 0 when real verify passes and a well-formed, in-scope delivery/DECLARED marker exempts Step 2 (got $exitC)"
|
|
266
284
|
else
|
|
267
285
|
_fail "REAL-VERIFY-PASSES: expected exit 0, got $exitC — output: $outC"
|
|
268
286
|
fi
|
|
269
287
|
|
|
270
|
-
if echo "$outC" | grep -
|
|
271
|
-
_pass "REAL-VERIFY-PASSES:
|
|
288
|
+
if echo "$outC" | grep -qF "DECLARED (no-agent-delivery): ref:test-verify-passes — test fixture: real verify passes, no agent delivery bundle (approved by test-harness, declared 2026-06-27T00:00:00Z)"; then
|
|
289
|
+
_pass "REAL-VERIFY-PASSES: exact DECLARED (no-agent-delivery) exemption line present"
|
|
290
|
+
else
|
|
291
|
+
_fail "REAL-VERIFY-PASSES: expected the exact DECLARED (no-agent-delivery) line, got: $outC"
|
|
292
|
+
fi
|
|
293
|
+
|
|
294
|
+
if echo "$outC" | grep -q "running: npm run trust-reconcile-verify"; then
|
|
295
|
+
_pass "REAL-VERIFY-PASSES: Step 1 (fresh verify) still ran the real trust-reconcile-verify command (not skipped by the exemption)"
|
|
272
296
|
else
|
|
273
|
-
_fail "REAL-VERIFY-PASSES: expected
|
|
297
|
+
_fail "REAL-VERIFY-PASSES: expected Step 1 to have run 'npm run trust-reconcile-verify', got: $outC"
|
|
274
298
|
fi
|
|
275
299
|
|
|
276
300
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
# Exit 1 with "trust divergence" message naming the command.
|
|
7
7
|
# 2. MATCHING-PASSES: bundle claims a command passed; CI re-runs it and it PASSES.
|
|
8
8
|
# Exit 0 (no divergence).
|
|
9
|
-
# 3. NO-CHECKPOINT: no bundle present; canonical
|
|
10
|
-
# Exit
|
|
9
|
+
# 3. NO-CHECKPOINT: no bundle present, no delivery/DECLARED marker; canonical
|
|
10
|
+
# verify passes. Exit 1 — bundle required by default (ADR 0022 §1); the
|
|
11
|
+
# 'bundle-required-no-declared-marker' issue fires (no more fail-open on absence).
|
|
11
12
|
# 4. LAUNDERING-CAUGHT: bundle claims "something || true" passed.
|
|
12
13
|
# Exit 1 with laundering message (checked before "CI never ran" check).
|
|
13
14
|
# YAML-VALID: .github/workflows/trust-reconcile.yml parses as valid YAML.
|
|
@@ -158,16 +159,16 @@ out3=$(TRUST_RECONCILE_COMMANDS="node -e 'process.exit(0)'" \
|
|
|
158
159
|
--repo-root "$TMP" 2>&1)
|
|
159
160
|
exit3=$?
|
|
160
161
|
|
|
161
|
-
if [[ $exit3 -
|
|
162
|
-
_pass "NO-CHECKPOINT: exits
|
|
162
|
+
if [[ $exit3 -ne 0 ]]; then
|
|
163
|
+
_pass "NO-CHECKPOINT: exits 1 (bundle required by default, no bundle, no marker) — got $exit3"
|
|
163
164
|
else
|
|
164
|
-
_fail "NO-CHECKPOINT: expected exit
|
|
165
|
+
_fail "NO-CHECKPOINT: expected exit 1 (bundle-required by default, ADR 0022 §1), got 0 — output: $out3"
|
|
165
166
|
fi
|
|
166
167
|
|
|
167
|
-
if echo "$out3" | grep -q "
|
|
168
|
-
_pass "NO-CHECKPOINT: output
|
|
168
|
+
if echo "$out3" | grep -q "bundle-required-no-declared-marker"; then
|
|
169
|
+
_pass "NO-CHECKPOINT: output contains 'bundle-required-no-declared-marker'"
|
|
169
170
|
else
|
|
170
|
-
_fail "NO-CHECKPOINT: expected '
|
|
171
|
+
_fail "NO-CHECKPOINT: expected 'bundle-required-no-declared-marker' in output, got: $out3"
|
|
171
172
|
fi
|
|
172
173
|
|
|
173
174
|
# Also verify: no-bundle + failing fresh verify still exits 1
|