@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,222 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test_assignment_provider_local_file.sh — AssignmentProvider local-file round-trip (#290).
|
|
3
|
+
#
|
|
4
|
+
# Fully deterministic: exercises `assignment-provider claim|status|supersede|release|list
|
|
5
|
+
# --provider local-file` against a temp artifact root and two fixed test-actor fixtures. No
|
|
6
|
+
# network, no `gh` process, no live provider. Follows test_pull_work_provider.sh's
|
|
7
|
+
# pass/fail/json_query idiom exactly.
|
|
8
|
+
#
|
|
9
|
+
# Supports AC5 (local-file round-trip), AC6 (list --actor), AC7 (concurrent claim never
|
|
10
|
+
# silently overwrites), AC9 (no live gh process — none invoked here at all).
|
|
11
|
+
set -uo pipefail
|
|
12
|
+
|
|
13
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
14
|
+
CLI="$ROOT/build/src/cli.js"
|
|
15
|
+
FIXTURES="$ROOT/evals/fixtures/assignment-provider"
|
|
16
|
+
ACTOR_A="$FIXTURES/actor-a.json"
|
|
17
|
+
ACTOR_B="$FIXTURES/actor-b.json"
|
|
18
|
+
SUBJECT_ID="kontourai/flow-agents#9001"
|
|
19
|
+
BRANCH_A="agent/claude-code-eval-actor-a-session-eval-host/flow-agents-9001"
|
|
20
|
+
ARTIFACT_DIR_A=".kontourai/flow-agents/flow-agents-9001"
|
|
21
|
+
|
|
22
|
+
TMPDIR_EVAL="$(mktemp -d)"
|
|
23
|
+
trap 'rm -rf "$TMPDIR_EVAL"' EXIT
|
|
24
|
+
ARTIFACT_ROOT="$TMPDIR_EVAL/artifact-root"
|
|
25
|
+
|
|
26
|
+
errors=0
|
|
27
|
+
pass() { echo " ✓ $1"; }
|
|
28
|
+
fail() { echo " ✗ $1"; errors=$((errors + 1)); }
|
|
29
|
+
|
|
30
|
+
json_query() {
|
|
31
|
+
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"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if [[ ! -f "$CLI" ]]; then
|
|
35
|
+
echo "build/src/cli.js not found — run 'npm run build' first" >&2
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
echo "=== AssignmentProvider: local-file round-trip ==="
|
|
40
|
+
|
|
41
|
+
# 1. claim writes the exact versioned record shape (Design Decision 2) for a fixed test actor.
|
|
42
|
+
node "$CLI" assignment-provider claim \
|
|
43
|
+
--provider local-file --artifact-root "$ARTIFACT_ROOT" \
|
|
44
|
+
--subject-id "$SUBJECT_ID" \
|
|
45
|
+
--actor-json "$ACTOR_A" \
|
|
46
|
+
--branch "$BRANCH_A" \
|
|
47
|
+
--artifact-dir "$ARTIFACT_DIR_A" \
|
|
48
|
+
--ttl-seconds 1800 \
|
|
49
|
+
> "$TMPDIR_EVAL/claim-a.json"
|
|
50
|
+
status=$?
|
|
51
|
+
[[ "$status" -eq 0 ]] && pass "claim exits successfully" || fail "claim exits successfully"
|
|
52
|
+
|
|
53
|
+
[[ "$(json_query "$TMPDIR_EVAL/claim-a.json" "record.schema_version")" == "1.0" ]] && pass "claim record schema_version is 1.0" || fail "claim record schema_version is 1.0"
|
|
54
|
+
[[ "$(json_query "$TMPDIR_EVAL/claim-a.json" "record.role")" == "AssignmentClaimRecord" ]] && pass "claim record role is AssignmentClaimRecord" || fail "claim record role is AssignmentClaimRecord"
|
|
55
|
+
[[ "$(json_query "$TMPDIR_EVAL/claim-a.json" "record.subject_id")" == "$SUBJECT_ID" ]] && pass "claim record subject_id matches" || fail "claim record subject_id matches"
|
|
56
|
+
[[ "$(json_query "$TMPDIR_EVAL/claim-a.json" "record.actor.session_id")" == "eval-actor-a-session" ]] && pass "claim record actor matches fixed test actor A" || fail "claim record actor matches fixed test actor A"
|
|
57
|
+
[[ "$(json_query "$TMPDIR_EVAL/claim-a.json" "record.ttl_seconds")" == "1800" ]] && pass "claim record ttl_seconds matches" || fail "claim record ttl_seconds matches"
|
|
58
|
+
[[ "$(json_query "$TMPDIR_EVAL/claim-a.json" "record.branch")" == "$BRANCH_A" ]] && pass "claim record branch matches" || fail "claim record branch matches"
|
|
59
|
+
[[ "$(json_query "$TMPDIR_EVAL/claim-a.json" "record.artifact_dir")" == "$ARTIFACT_DIR_A" ]] && pass "claim record artifact_dir matches" || fail "claim record artifact_dir matches"
|
|
60
|
+
[[ "$(json_query "$TMPDIR_EVAL/claim-a.json" "record.status")" == "claimed" ]] && pass "claim record status is claimed" || fail "claim record status is claimed"
|
|
61
|
+
[[ "$(json_query "$TMPDIR_EVAL/claim-a.json" "record.audit_trail.0.transition")" == "claim" ]] && pass "claim record audit_trail records the claim transition" || fail "claim record audit_trail records the claim transition"
|
|
62
|
+
|
|
63
|
+
CLAIMED_AT="$(json_query "$TMPDIR_EVAL/claim-a.json" "record.claimed_at")"
|
|
64
|
+
|
|
65
|
+
# 2. status returns actor/claimedAt matching what claim wrote.
|
|
66
|
+
node "$CLI" assignment-provider status \
|
|
67
|
+
--provider local-file --artifact-root "$ARTIFACT_ROOT" --subject-id "$SUBJECT_ID" \
|
|
68
|
+
> "$TMPDIR_EVAL/status-a.json"
|
|
69
|
+
[[ "$(json_query "$TMPDIR_EVAL/status-a.json" "assignment.assignee")" == "claude-code:eval-actor-a-session:eval-host" ]] && pass "status reports actor A as assignee" || fail "status reports actor A as assignee"
|
|
70
|
+
[[ "$(json_query "$TMPDIR_EVAL/status-a.json" "assignment.record.claimed_at")" == "$CLAIMED_AT" ]] && pass "status claimed_at matches claim's claimed_at" || fail "status claimed_at matches claim's claimed_at"
|
|
71
|
+
|
|
72
|
+
# 3. supersede A -> B updates the actor and records an audit trail entry.
|
|
73
|
+
node "$CLI" assignment-provider supersede \
|
|
74
|
+
--provider local-file --artifact-root "$ARTIFACT_ROOT" --subject-id "$SUBJECT_ID" \
|
|
75
|
+
--from-actor-json "$ACTOR_A" --to-actor-json "$ACTOR_B" --reason "handoff-eval" \
|
|
76
|
+
> "$TMPDIR_EVAL/supersede.json"
|
|
77
|
+
status=$?
|
|
78
|
+
[[ "$status" -eq 0 ]] && pass "supersede exits successfully" || fail "supersede exits successfully"
|
|
79
|
+
[[ "$(json_query "$TMPDIR_EVAL/supersede.json" "record.actor.session_id")" == "eval-actor-b-session" ]] && pass "supersede updates the record's actor to B" || fail "supersede updates the record's actor to B"
|
|
80
|
+
[[ "$(json_query "$TMPDIR_EVAL/supersede.json" "record.audit_trail.length")" == "2" ]] && pass "supersede appends to the audit trail (claim + supersede)" || fail "supersede appends to the audit trail (claim + supersede)"
|
|
81
|
+
[[ "$(json_query "$TMPDIR_EVAL/supersede.json" "record.audit_trail.1.transition")" == "supersede" ]] && pass "audit trail records the supersede transition" || fail "audit trail records the supersede transition"
|
|
82
|
+
[[ "$(json_query "$TMPDIR_EVAL/supersede.json" "record.audit_trail.1.from_actor.session_id")" == "eval-actor-a-session" ]] && pass "audit trail supersede entry records from_actor A" || fail "audit trail supersede entry records from_actor A"
|
|
83
|
+
[[ "$(json_query "$TMPDIR_EVAL/supersede.json" "record.audit_trail.1.to_actor.session_id")" == "eval-actor-b-session" ]] && pass "audit trail supersede entry records to_actor B" || fail "audit trail supersede entry records to_actor B"
|
|
84
|
+
[[ "$(json_query "$TMPDIR_EVAL/supersede.json" "record.audit_trail.1.reason")" == "handoff-eval" ]] && pass "audit trail supersede entry records reason" || fail "audit trail supersede entry records reason"
|
|
85
|
+
|
|
86
|
+
node "$CLI" assignment-provider status \
|
|
87
|
+
--provider local-file --artifact-root "$ARTIFACT_ROOT" --subject-id "$SUBJECT_ID" \
|
|
88
|
+
> "$TMPDIR_EVAL/status-b.json"
|
|
89
|
+
[[ "$(json_query "$TMPDIR_EVAL/status-b.json" "assignment.assignee")" == "claude-code:eval-actor-b-session:eval-host" ]] && pass "status reports actor B after supersede" || fail "status reports actor B after supersede"
|
|
90
|
+
|
|
91
|
+
# 4. release clears the claim; subsequent status reports free.
|
|
92
|
+
node "$CLI" assignment-provider release \
|
|
93
|
+
--provider local-file --artifact-root "$ARTIFACT_ROOT" --subject-id "$SUBJECT_ID" \
|
|
94
|
+
--actor-json "$ACTOR_B" \
|
|
95
|
+
> "$TMPDIR_EVAL/release.json"
|
|
96
|
+
status=$?
|
|
97
|
+
[[ "$status" -eq 0 ]] && pass "release exits successfully" || fail "release exits successfully"
|
|
98
|
+
[[ "$(json_query "$TMPDIR_EVAL/release.json" "record.status")" == "released" ]] && pass "release sets record status to released" || fail "release sets record status to released"
|
|
99
|
+
|
|
100
|
+
node "$CLI" assignment-provider status \
|
|
101
|
+
--provider local-file --artifact-root "$ARTIFACT_ROOT" --subject-id "$SUBJECT_ID" \
|
|
102
|
+
--liveness-events-json <(echo '[]') \
|
|
103
|
+
> "$TMPDIR_EVAL/status-free.json"
|
|
104
|
+
[[ "$(json_query "$TMPDIR_EVAL/status-free.json" "assignment.assignee")" == "null" ]] && pass "status reports no assignee after release" || fail "status reports no assignee after release"
|
|
105
|
+
[[ "$(json_query "$TMPDIR_EVAL/status-free.json" "effective.effective_state")" == "free" ]] && pass "status reports effective_state free after release" || fail "status reports effective_state free after release"
|
|
106
|
+
|
|
107
|
+
# 5. list --actor returns exactly that actor's claimed subject ids (claim a second subject
|
|
108
|
+
# under actor A, then verify list --actor A returns exactly that one subject).
|
|
109
|
+
SUBJECT_ID_2="kontourai/flow-agents#9002"
|
|
110
|
+
node "$CLI" assignment-provider claim \
|
|
111
|
+
--provider local-file --artifact-root "$ARTIFACT_ROOT" \
|
|
112
|
+
--subject-id "$SUBJECT_ID_2" \
|
|
113
|
+
--actor-json "$ACTOR_A" \
|
|
114
|
+
--branch "agent/claude-code-eval-actor-a-session-eval-host/flow-agents-9002" \
|
|
115
|
+
--artifact-dir ".kontourai/flow-agents/flow-agents-9002" \
|
|
116
|
+
> /dev/null
|
|
117
|
+
node "$CLI" assignment-provider list \
|
|
118
|
+
--provider local-file --artifact-root "$ARTIFACT_ROOT" --actor-json "$ACTOR_A" \
|
|
119
|
+
> "$TMPDIR_EVAL/list-a.json"
|
|
120
|
+
[[ "$(json_query "$TMPDIR_EVAL/list-a.json" "subject_ids.length")" == "1" ]] && pass "list --actor A returns exactly one subject id" || fail "list --actor A returns exactly one subject id"
|
|
121
|
+
[[ "$(json_query "$TMPDIR_EVAL/list-a.json" "subject_ids.0")" == "$SUBJECT_ID_2" ]] && pass "list --actor A returns actor A's currently-claimed subject (released subject excluded)" || fail "list --actor A returns actor A's currently-claimed subject (released subject excluded)"
|
|
122
|
+
|
|
123
|
+
node "$CLI" assignment-provider list \
|
|
124
|
+
--provider local-file --artifact-root "$ARTIFACT_ROOT" --actor-json "$ACTOR_B" \
|
|
125
|
+
> "$TMPDIR_EVAL/list-b.json"
|
|
126
|
+
[[ "$(json_query "$TMPDIR_EVAL/list-b.json" "subject_ids.length")" == "0" ]] && pass "list --actor B returns no subject ids (its only claim was released)" || fail "list --actor B returns no subject ids (its only claim was released)"
|
|
127
|
+
|
|
128
|
+
# 6. Two concurrent claims on the same subject from different actors: the second call must
|
|
129
|
+
# fail loud (non-zero exit + holder-identifying error), never silently overwrite the first
|
|
130
|
+
# (AC7; artifact-contract.md's "fail loud, never fail-open" persistence rule).
|
|
131
|
+
node "$CLI" assignment-provider claim \
|
|
132
|
+
--provider local-file --artifact-root "$ARTIFACT_ROOT" \
|
|
133
|
+
--subject-id "$SUBJECT_ID_2" \
|
|
134
|
+
--actor-json "$ACTOR_B" \
|
|
135
|
+
--branch "agent/claude-code-eval-actor-b-session-eval-host/flow-agents-9002" \
|
|
136
|
+
--artifact-dir ".kontourai/flow-agents/flow-agents-9002" \
|
|
137
|
+
> "$TMPDIR_EVAL/concurrent-claim.json" 2> "$TMPDIR_EVAL/concurrent-claim.err"
|
|
138
|
+
concurrent_status=$?
|
|
139
|
+
[[ "$concurrent_status" -ne 0 ]] && pass "second claim from a different actor fails loud (non-zero exit)" || fail "second claim from a different actor fails loud (non-zero exit)"
|
|
140
|
+
grep -q "already claimed by a different actor" "$TMPDIR_EVAL/concurrent-claim.err" && pass "second claim's error identifies the existing holder" || fail "second claim's error identifies the existing holder"
|
|
141
|
+
|
|
142
|
+
node "$CLI" assignment-provider status \
|
|
143
|
+
--provider local-file --artifact-root "$ARTIFACT_ROOT" --subject-id "$SUBJECT_ID_2" \
|
|
144
|
+
> "$TMPDIR_EVAL/status-after-concurrent.json"
|
|
145
|
+
[[ "$(json_query "$TMPDIR_EVAL/status-after-concurrent.json" "assignment.assignee")" == "claude-code:eval-actor-a-session:eval-host" ]] && pass "record still shows actor A as holder — no silent overwrite" || fail "record still shows actor A as holder — no silent overwrite"
|
|
146
|
+
|
|
147
|
+
# Same-actor re-claim (refresh before TTL expiry) is allowed and idempotent — must not error.
|
|
148
|
+
node "$CLI" assignment-provider claim \
|
|
149
|
+
--provider local-file --artifact-root "$ARTIFACT_ROOT" \
|
|
150
|
+
--subject-id "$SUBJECT_ID_2" \
|
|
151
|
+
--actor-json "$ACTOR_A" \
|
|
152
|
+
--branch "agent/claude-code-eval-actor-a-session-eval-host/flow-agents-9002" \
|
|
153
|
+
--artifact-dir ".kontourai/flow-agents/flow-agents-9002" \
|
|
154
|
+
> /dev/null 2>&1
|
|
155
|
+
[[ $? -eq 0 ]] && pass "same-actor re-claim (refresh) succeeds without error" || fail "same-actor re-claim (refresh) succeeds without error"
|
|
156
|
+
|
|
157
|
+
echo ""
|
|
158
|
+
|
|
159
|
+
# 7. F1 fix-plan iteration 1 (CRITICAL): GENUINE OS-process concurrency — two REAL,
|
|
160
|
+
# concurrently-launched `claim` processes (background `&` + `wait`, not the sequential case in
|
|
161
|
+
# section 6 above which never actually races) targeting the SAME fresh subject from DIFFERENT
|
|
162
|
+
# actors. Before the withSubjectLock fix, both processes could read "no existing claim" before
|
|
163
|
+
# either wrote, and one write would silently clobber the other (reproduced 29/40 races against
|
|
164
|
+
# the built CLI pre-fix). Assert EXACTLY ONE process exits 0, and the on-disk record shows
|
|
165
|
+
# exactly one holder with an intact, single-entry audit trail (no lost write).
|
|
166
|
+
SUBJECT_ID_RACE="kontourai/flow-agents#9003"
|
|
167
|
+
BRANCH_RACE_A="agent/claude-code-eval-actor-a-session-eval-host/flow-agents-9003"
|
|
168
|
+
BRANCH_RACE_B="agent/claude-code-eval-actor-b-session-eval-host/flow-agents-9003"
|
|
169
|
+
ARTIFACT_DIR_RACE=".kontourai/flow-agents/flow-agents-9003"
|
|
170
|
+
|
|
171
|
+
(
|
|
172
|
+
node "$CLI" assignment-provider claim \
|
|
173
|
+
--provider local-file --artifact-root "$ARTIFACT_ROOT" \
|
|
174
|
+
--subject-id "$SUBJECT_ID_RACE" \
|
|
175
|
+
--actor-json "$ACTOR_A" \
|
|
176
|
+
--branch "$BRANCH_RACE_A" \
|
|
177
|
+
--artifact-dir "$ARTIFACT_DIR_RACE" \
|
|
178
|
+
> "$TMPDIR_EVAL/race-a.out" 2> "$TMPDIR_EVAL/race-a.err"
|
|
179
|
+
echo $? > "$TMPDIR_EVAL/race-a.rc"
|
|
180
|
+
) &
|
|
181
|
+
PID_A=$!
|
|
182
|
+
(
|
|
183
|
+
node "$CLI" assignment-provider claim \
|
|
184
|
+
--provider local-file --artifact-root "$ARTIFACT_ROOT" \
|
|
185
|
+
--subject-id "$SUBJECT_ID_RACE" \
|
|
186
|
+
--actor-json "$ACTOR_B" \
|
|
187
|
+
--branch "$BRANCH_RACE_B" \
|
|
188
|
+
--artifact-dir "$ARTIFACT_DIR_RACE" \
|
|
189
|
+
> "$TMPDIR_EVAL/race-b.out" 2> "$TMPDIR_EVAL/race-b.err"
|
|
190
|
+
echo $? > "$TMPDIR_EVAL/race-b.rc"
|
|
191
|
+
) &
|
|
192
|
+
PID_B=$!
|
|
193
|
+
wait "$PID_A" "$PID_B"
|
|
194
|
+
|
|
195
|
+
RACE_RC_A="$(cat "$TMPDIR_EVAL/race-a.rc")"
|
|
196
|
+
RACE_RC_B="$(cat "$TMPDIR_EVAL/race-b.rc")"
|
|
197
|
+
|
|
198
|
+
if { [[ "$RACE_RC_A" -eq 0 && "$RACE_RC_B" -ne 0 ]] || [[ "$RACE_RC_A" -ne 0 && "$RACE_RC_B" -eq 0 ]]; }; then
|
|
199
|
+
pass "genuine concurrent claim race: exactly one process wins (rc_a=$RACE_RC_A rc_b=$RACE_RC_B)"
|
|
200
|
+
else
|
|
201
|
+
fail "genuine concurrent claim race: exactly one process wins (rc_a=$RACE_RC_A rc_b=$RACE_RC_B)"
|
|
202
|
+
fi
|
|
203
|
+
|
|
204
|
+
node "$CLI" assignment-provider status \
|
|
205
|
+
--provider local-file --artifact-root "$ARTIFACT_ROOT" --subject-id "$SUBJECT_ID_RACE" \
|
|
206
|
+
> "$TMPDIR_EVAL/status-race.json"
|
|
207
|
+
[[ "$(json_query "$TMPDIR_EVAL/status-race.json" "assignment.record.audit_trail.length")" == "1" ]] && pass "race winner's on-disk record has exactly one audit_trail entry (no lost/merged write)" || fail "race winner's on-disk record has exactly one audit_trail entry (no lost/merged write)"
|
|
208
|
+
|
|
209
|
+
if [[ "$RACE_RC_A" -eq 0 ]]; then
|
|
210
|
+
EXPECTED_RACE_HOLDER="claude-code:eval-actor-a-session:eval-host"
|
|
211
|
+
else
|
|
212
|
+
EXPECTED_RACE_HOLDER="claude-code:eval-actor-b-session:eval-host"
|
|
213
|
+
fi
|
|
214
|
+
[[ "$(json_query "$TMPDIR_EVAL/status-race.json" "assignment.assignee")" == "$EXPECTED_RACE_HOLDER" ]] && pass "on-disk record holder matches the process that actually exited 0 (no silent overwrite by the loser)" || fail "on-disk record holder matches the process that actually exited 0 (no silent overwrite by the loser)"
|
|
215
|
+
|
|
216
|
+
echo ""
|
|
217
|
+
if [[ "$errors" -eq 0 ]]; then
|
|
218
|
+
echo "test_assignment_provider_local_file: all checks passed."
|
|
219
|
+
else
|
|
220
|
+
echo "test_assignment_provider_local_file: $errors check(s) failed."
|
|
221
|
+
fi
|
|
222
|
+
exit "$errors"
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test_critique_supersession_roundtrip.sh — Regression evals for the trust-ledger defects
|
|
3
|
+
# fixed under #267 (critique supersession), #268 (lossless, type-stable check/critique
|
|
4
|
+
# round-trip), and #282 (first-class superseded_by affordance).
|
|
5
|
+
#
|
|
6
|
+
# Proves:
|
|
7
|
+
# (a) record-critique twice for the same id (fail → pass, SAME reviewer) supersedes the fail:
|
|
8
|
+
# the effective/live state is the pass, the historical fail is retained (status=superseded,
|
|
9
|
+
# metadata.superseded_by) but excluded from evaluation, no duplicate claim ids, and CI
|
|
10
|
+
# trust-reconcile exits 0.
|
|
11
|
+
# (b) HEADLINE: a --flow-id (builder.build/verify) session — ensure-session → record-evidence
|
|
12
|
+
# (mixed session-local kinds) → record-critique → trust-reconcile — exits 0. This exact flow
|
|
13
|
+
# could never converge before (critique claims were re-absorbed as command-less test_output
|
|
14
|
+
# checks → permanent [not-run] divergence; check kinds collapsed to the declared claimType).
|
|
15
|
+
# (c) record-evidence AFTER a critique preserves the critique history (previously hardcoded []).
|
|
16
|
+
# (d) ANTI-GAMING: a DIFFERENT reviewer cannot supersede a reviewer's fail — the disputed
|
|
17
|
+
# critique stays live and trust-reconcile still exits 1 (worker cannot bury a reviewer finding).
|
|
18
|
+
# (e) VALIDATOR (#282): a top-level pass tolerates a superseded historical fail member, but a
|
|
19
|
+
# LIVE (non-superseded) fail still triggers "required critique must pass".
|
|
20
|
+
#
|
|
21
|
+
# Deterministic, no model spend, self-cleaning.
|
|
22
|
+
# Usage: bash evals/integration/test_critique_supersession_roundtrip.sh
|
|
23
|
+
|
|
24
|
+
set -uo pipefail
|
|
25
|
+
|
|
26
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
27
|
+
source "$ROOT/evals/lib/node.sh"
|
|
28
|
+
RECON="$ROOT/scripts/ci/trust-reconcile.js"
|
|
29
|
+
WRITER="workflow-sidecar"
|
|
30
|
+
|
|
31
|
+
TMP="$(mktemp -d)"
|
|
32
|
+
errors=0
|
|
33
|
+
_pass() { echo " PASS: $1"; }
|
|
34
|
+
_fail() { echo " FAIL: $1"; errors=$((errors + 1)); }
|
|
35
|
+
cleanup() { rm -rf "$TMP"; }
|
|
36
|
+
trap cleanup EXIT
|
|
37
|
+
|
|
38
|
+
# seed <aroot> <slug> [flow-args...] — ensure-session + init-plan
|
|
39
|
+
seed() {
|
|
40
|
+
local aroot="$1"; local slug="$2"; shift 2
|
|
41
|
+
mkdir -p "$aroot"
|
|
42
|
+
flow_agents_node "$WRITER" ensure-session --artifact-root "$aroot" --task-slug "$slug" \
|
|
43
|
+
--title "T" --summary "S" --timestamp "2026-07-01T00:00:00Z" "$@" >/dev/null 2>&1
|
|
44
|
+
flow_agents_node "$WRITER" init-plan "$aroot/$slug/$slug--deliver.md" \
|
|
45
|
+
--source-request "R" --summary "S" --timestamp "2026-07-01T00:00:00Z" >/dev/null 2>&1
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# ─── (a) same-reviewer supersession: fail → pass ──────────────────────────────
|
|
49
|
+
echo ""
|
|
50
|
+
echo "=== (a) record-critique fail→pass (same reviewer) supersedes, history intact, reconcile 0 ==="
|
|
51
|
+
A_AROOT="$TMP/a/aroot"; A_SLUG="supersede-same"; A_DIR="$A_AROOT/$A_SLUG"
|
|
52
|
+
seed "$A_AROOT" "$A_SLUG"
|
|
53
|
+
flow_agents_node "$WRITER" record-evidence "$A_DIR" --verdict pass \
|
|
54
|
+
--check-json '{"id":"c1","kind":"diff","status":"pass","summary":"diff check"}' \
|
|
55
|
+
--timestamp "2026-07-01T00:01:00Z" >/dev/null 2>&1
|
|
56
|
+
flow_agents_node "$WRITER" record-critique "$A_DIR" --id rv --reviewer alice --verdict fail \
|
|
57
|
+
--summary "found a bug" --timestamp "2026-07-01T00:02:00Z" >/dev/null 2>&1
|
|
58
|
+
flow_agents_node "$WRITER" record-critique "$A_DIR" --id rv --reviewer alice --verdict pass \
|
|
59
|
+
--summary "bug fixed" --timestamp "2026-07-01T00:03:00Z" >/dev/null 2>&1
|
|
60
|
+
|
|
61
|
+
node - "$A_DIR/trust.bundle" << 'NODE'
|
|
62
|
+
const fs = require('fs');
|
|
63
|
+
const b = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
|
64
|
+
const crit = (b.claims||[]).filter(c => c.metadata && c.metadata.origin === 'critique');
|
|
65
|
+
const live = crit.filter(c => !c.metadata.superseded_by);
|
|
66
|
+
const hist = crit.filter(c => c.metadata.superseded_by);
|
|
67
|
+
if (crit.length !== 2) throw new Error('expected 2 critique claims (1 live + 1 history), got ' + crit.length);
|
|
68
|
+
if (live.length !== 1 || live[0].value !== 'pass') throw new Error('expected exactly 1 LIVE critique with value=pass, got ' + JSON.stringify(live.map(c=>c.value)));
|
|
69
|
+
if (hist.length !== 1 || hist[0].value !== 'fail' || hist[0].status !== 'superseded') throw new Error('expected historical fail with status=superseded');
|
|
70
|
+
if (!hist[0].metadata.superseded_by) throw new Error('history missing first-class metadata.superseded_by');
|
|
71
|
+
const ids = (b.claims||[]).map(c=>c.id); const dup = ids.filter((x,i)=>ids.indexOf(x)!==i);
|
|
72
|
+
if (dup.length) throw new Error('duplicate claim ids in bundle: ' + dup.join(','));
|
|
73
|
+
console.log('live=pass, history=fail(superseded), no duplicate ids');
|
|
74
|
+
NODE
|
|
75
|
+
if [[ $? -eq 0 ]]; then _pass "(a) effective state=pass, historical fail retained+superseded, no dup ids"; else _fail "(a) supersession/history assertion failed"; fi
|
|
76
|
+
|
|
77
|
+
TRUST_RECONCILE_COMMANDS="true" node "$RECON" --bundle "$A_DIR/trust.bundle" --repo-root "$TMP/a" >"$TMP/a-recon.log" 2>&1
|
|
78
|
+
if [[ $? -eq 0 ]]; then _pass "(a) trust-reconcile exits 0 (resolved session converges)"; else _fail "(a) trust-reconcile did NOT converge: $(cat "$TMP/a-recon.log")"; fi
|
|
79
|
+
|
|
80
|
+
# ─── (b) HEADLINE: --flow-id session converges ────────────────────────────────
|
|
81
|
+
echo ""
|
|
82
|
+
echo "=== (b) HEADLINE --flow-id builder.build/verify: mixed evidence + critique → reconcile 0 ==="
|
|
83
|
+
B_AROOT="$TMP/b/aroot"; B_SLUG="flowid-converge"; B_DIR="$B_AROOT/$B_SLUG"
|
|
84
|
+
seed "$B_AROOT" "$B_SLUG" --flow-id builder.build --step-id verify
|
|
85
|
+
flow_agents_node "$WRITER" record-evidence "$B_DIR" --verdict pass \
|
|
86
|
+
--check-json '{"id":"k-diff","kind":"diff","status":"pass","summary":"diff excerpt"}' \
|
|
87
|
+
--check-json '{"id":"k-policy","kind":"policy","status":"pass","summary":"policy rule"}' \
|
|
88
|
+
--check-json '{"id":"k-ext","kind":"external","status":"pass","summary":"attested"}' \
|
|
89
|
+
--timestamp "2026-07-01T00:01:00Z" >/dev/null 2>&1
|
|
90
|
+
flow_agents_node "$WRITER" record-critique "$B_DIR" --id code-review --reviewer alice --verdict pass \
|
|
91
|
+
--summary "looks good" --timestamp "2026-07-01T00:02:00Z" >/dev/null 2>&1
|
|
92
|
+
|
|
93
|
+
node - "$B_DIR/trust.bundle" << 'NODE'
|
|
94
|
+
const fs = require('fs');
|
|
95
|
+
const b = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
|
96
|
+
// Type stability: no critique claim may be re-absorbed as a check, and check kinds/evidenceTypes
|
|
97
|
+
// must survive the round-trip (a session-local kind must NOT flip to test_output).
|
|
98
|
+
const checks = (b.claims||[]).filter(c => c.metadata && c.metadata.origin === 'check');
|
|
99
|
+
const crit = (b.claims||[]).filter(c => c.metadata && c.metadata.origin === 'critique');
|
|
100
|
+
if (crit.length !== 1) throw new Error('expected exactly 1 critique claim (no re-absorption/duplication), got ' + crit.length);
|
|
101
|
+
const evByClaim = {}; for (const e of (b.evidence||[])) (evByClaim[e.claimId] ||= []).push(e);
|
|
102
|
+
for (const c of checks) {
|
|
103
|
+
const ets = (evByClaim[c.id]||[]).map(e=>e.evidenceType);
|
|
104
|
+
if (ets.includes('test_output')) throw new Error('session-local check ' + c.id + ' flipped to test_output evidence (round-trip kind instability)');
|
|
105
|
+
}
|
|
106
|
+
console.log(checks.length + ' check claims, ' + crit.length + ' critique claim; no test_output flip');
|
|
107
|
+
NODE
|
|
108
|
+
if [[ $? -eq 0 ]]; then _pass "(b) round-trip type-stable under --flow-id (no critique→check absorption, no evidenceType flip)"; else _fail "(b) round-trip type instability under --flow-id"; fi
|
|
109
|
+
|
|
110
|
+
TRUST_RECONCILE_COMMANDS="true" node "$RECON" --bundle "$B_DIR/trust.bundle" --repo-root "$TMP/b" >"$TMP/b-recon.log" 2>&1
|
|
111
|
+
if [[ $? -eq 0 ]]; then _pass "(b) HEADLINE: --flow-id session trust-reconcile exits 0 (converges)"; else _fail "(b) HEADLINE --flow-id session did NOT converge: $(cat "$TMP/b-recon.log")"; fi
|
|
112
|
+
|
|
113
|
+
# ─── (c) record-evidence after critiques preserves history ────────────────────
|
|
114
|
+
echo ""
|
|
115
|
+
echo "=== (c) record-evidence AFTER a critique preserves critique history (#268) ==="
|
|
116
|
+
flow_agents_node "$WRITER" record-evidence "$B_DIR" --verdict pass \
|
|
117
|
+
--check-json '{"id":"k-diff2","kind":"diff","status":"pass","summary":"another diff"}' \
|
|
118
|
+
--timestamp "2026-07-01T00:04:00Z" >/dev/null 2>&1
|
|
119
|
+
node - "$B_DIR/trust.bundle" << 'NODE'
|
|
120
|
+
const fs = require('fs');
|
|
121
|
+
const b = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
|
122
|
+
const crit = (b.claims||[]).filter(c => c.metadata && c.metadata.origin === 'critique');
|
|
123
|
+
if (crit.length !== 1) throw new Error('critique history dropped by record-evidence — expected 1, got ' + crit.length);
|
|
124
|
+
if (crit[0].value !== 'pass') throw new Error('critique value changed');
|
|
125
|
+
console.log('critique survived record-evidence');
|
|
126
|
+
NODE
|
|
127
|
+
if [[ $? -eq 0 ]]; then _pass "(c) critique history survives a later record-evidence call"; else _fail "(c) record-evidence dropped critique history"; fi
|
|
128
|
+
|
|
129
|
+
# ─── (d) ANTI-GAMING: cross-reviewer cannot supersede a reviewer fail ─────────
|
|
130
|
+
echo ""
|
|
131
|
+
echo "=== (d) ANTI-GAMING: a different reviewer cannot supersede a reviewer's fail ==="
|
|
132
|
+
D_AROOT="$TMP/d/aroot"; D_SLUG="supersede-cross"; D_DIR="$D_AROOT/$D_SLUG"
|
|
133
|
+
seed "$D_AROOT" "$D_SLUG"
|
|
134
|
+
flow_agents_node "$WRITER" record-evidence "$D_DIR" --verdict pass \
|
|
135
|
+
--check-json '{"id":"c1","kind":"diff","status":"pass","summary":"diff check"}' \
|
|
136
|
+
--timestamp "2026-07-01T00:01:00Z" >/dev/null 2>&1
|
|
137
|
+
flow_agents_node "$WRITER" record-critique "$D_DIR" --id rv --reviewer reviewer-bob --verdict fail \
|
|
138
|
+
--summary "reviewer found a real bug" --timestamp "2026-07-01T00:02:00Z" >/dev/null 2>&1
|
|
139
|
+
flow_agents_node "$WRITER" record-critique "$D_DIR" --id rv --reviewer worker-mallory --verdict pass \
|
|
140
|
+
--summary "worker claims fixed" --timestamp "2026-07-01T00:03:00Z" >/dev/null 2>&1
|
|
141
|
+
node - "$D_DIR/trust.bundle" << 'NODE'
|
|
142
|
+
const fs = require('fs');
|
|
143
|
+
const b = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
|
144
|
+
const crit = (b.claims||[]).filter(c => c.metadata && c.metadata.origin === 'critique');
|
|
145
|
+
const live = crit.filter(c => !c.metadata.superseded_by);
|
|
146
|
+
const liveFail = live.filter(c => c.value === 'fail' || c.status === 'disputed');
|
|
147
|
+
if (!liveFail.length) throw new Error('ANTI-GAMING VIOLATION: reviewer fail was superseded by a different reviewer');
|
|
148
|
+
if (crit.some(c => c.metadata.superseded_by)) throw new Error('unexpected supersession across reviewers');
|
|
149
|
+
console.log('reviewer fail stays LIVE (' + liveFail.length + '); cross-reviewer supersession refused');
|
|
150
|
+
NODE
|
|
151
|
+
if [[ $? -eq 0 ]]; then _pass "(d) reviewer's fail is NOT superseded by a different reviewer (stays live)"; else _fail "(d) ANTI-GAMING regression: cross-reviewer supersession occurred"; fi
|
|
152
|
+
TRUST_RECONCILE_COMMANDS="true" node "$RECON" --bundle "$D_DIR/trust.bundle" --repo-root "$TMP/d" >"$TMP/d-recon.log" 2>&1
|
|
153
|
+
if [[ $? -ne 0 ]] && grep -q "session-local-failed" "$TMP/d-recon.log"; then _pass "(d) trust-reconcile still exits 1 (live disputed critique blocks)"; else _fail "(d) reconcile should have blocked on the live reviewer fail"; fi
|
|
154
|
+
|
|
155
|
+
# ─── (e) VALIDATOR (#282): superseded fail tolerated, live fail blocks ────────
|
|
156
|
+
echo ""
|
|
157
|
+
echo "=== (e) VALIDATOR: superseded historical fail tolerated; live fail blocks (#282) ==="
|
|
158
|
+
# Reuse (a)'s superseded bundle: pass top-level with a superseded fail member.
|
|
159
|
+
va_out="$(flow_agents_node validate-workflow-artifacts "$A_AROOT" --require-sidecars --require-critique 2>&1)"
|
|
160
|
+
if echo "$va_out" | grep -q "required critique must pass"; then
|
|
161
|
+
_fail "(e) validator wrongly rejected a superseded historical fail"
|
|
162
|
+
else
|
|
163
|
+
_pass "(e) validator tolerates a superseded historical fail member (no 'required critique must pass')"
|
|
164
|
+
fi
|
|
165
|
+
# Live fail (d's cross-reviewer bundle has a live disputed critique) must still be rejected.
|
|
166
|
+
vd_out="$(flow_agents_node validate-workflow-artifacts "$D_AROOT" --require-sidecars --require-critique 2>&1)"
|
|
167
|
+
if echo "$vd_out" | grep -q "required critique must pass"; then
|
|
168
|
+
_pass "(e) validator still rejects a LIVE (non-superseded) fail critique"
|
|
169
|
+
else
|
|
170
|
+
_fail "(e) validator failed to reject a live fail critique"
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
# ─── Summary ──────────────────────────────────────────────────────────────────
|
|
174
|
+
echo ""
|
|
175
|
+
echo "────────────────────────────────────────────"
|
|
176
|
+
if [[ $errors -eq 0 ]]; then
|
|
177
|
+
echo "test_critique_supersession_roundtrip: all checks passed."
|
|
178
|
+
exit 0
|
|
179
|
+
else
|
|
180
|
+
echo "test_critique_supersession_roundtrip: $errors check(s) failed."
|
|
181
|
+
exit 1
|
|
182
|
+
fi
|