@kontourai/flow-agents 2.3.0 → 3.0.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 +16 -0
- package/.github/workflows/ci.yml +44 -0
- package/.github/workflows/trust-reconcile.yml +62 -4
- package/AGENTS.md +4 -4
- package/CHANGELOG.md +62 -0
- package/CONTEXT.md +35 -0
- package/README.md +9 -3
- package/agents/dev.json +1 -1
- package/agents/tool-code-reviewer.json +1 -1
- package/agents/tool-planner.json +3 -3
- package/agents/tool-verifier.json +3 -3
- package/build/src/cli/assignment-provider.d.ts +1 -0
- package/build/src/cli/assignment-provider.js +748 -0
- package/build/src/cli/console-learning-projection.js +3 -2
- 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/init.js +104 -21
- package/build/src/cli/kit.js +37 -6
- package/build/src/cli/pull-work-provider.js +1 -1
- package/build/src/cli/usage-feedback.js +3 -3
- package/build/src/cli/validate-hook-influence.js +1 -0
- package/build/src/cli/validate-workflow-artifacts.js +27 -7
- package/build/src/cli/workflow-artifact-cleanup-audit.js +36 -2
- package/build/src/cli/workflow-sidecar.d.ts +14 -7
- package/build/src/cli/workflow-sidecar.js +938 -168
- package/build/src/cli.js +6 -0
- package/build/src/flow-kit/validate.d.ts +23 -0
- package/build/src/flow-kit/validate.js +67 -1
- package/build/src/index.d.ts +1 -1
- package/build/src/index.js +1 -1
- package/build/src/lib/flow-resolver.d.ts +6 -4
- package/build/src/lib/flow-resolver.js +125 -58
- package/build/src/lib/local-artifact-root.d.ts +14 -2
- package/build/src/lib/local-artifact-root.js +22 -5
- package/build/src/lib/workflow-learning-projection.js +2 -2
- package/build/src/runtime-adapters.d.ts +12 -0
- package/build/src/runtime-adapters.js +56 -16
- package/build/src/tools/build-universal-bundles.js +49 -16
- package/build/src/tools/generate-context-map.js +10 -8
- package/build/src/tools/validate-source-tree.js +24 -1
- package/console.telemetry.json +22 -0
- package/context/contracts/artifact-contract.md +40 -7
- package/context/contracts/assignment-provider-contract.md +239 -0
- package/context/contracts/builder-kit-workflow-state-contract.md +5 -2
- package/context/contracts/decision-registry-contract.md +265 -0
- package/context/contracts/delivery-contract.md +3 -1
- 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 +4 -1
- package/context/contracts/review-contract.md +7 -3
- package/context/contracts/sandbox-policy.md +2 -0
- package/context/contracts/standing-directives.md +13 -0
- package/context/contracts/verification-contract.md +11 -1
- package/context/contracts/work-item-contract.md +3 -1
- package/context/deferred/parallelization.md +1 -1
- package/context/gate-awareness.md +4 -4
- package/context/scripts/git-status.sh +2 -2
- package/context/scripts/hooks/config-protection.js +502 -2
- package/context/scripts/hooks/lib/config-protection-remedies.js +69 -0
- package/context/scripts/hooks/run-hook.js +14 -1
- package/context/scripts/hooks/stop-goal-fit.js +1140 -99
- package/context/scripts/hooks/workflow-steering.js +212 -6
- package/context/settings/assignment-provider-settings.json +33 -0
- package/docs/adr/0007-flow-skill-kit-tool-boundary.md +2 -0
- package/docs/adr/0015-flow-flow-agents-boundary-reconciliation.md +35 -0
- package/docs/adr/0019-kit-dependency-ownership.md +62 -0
- package/docs/adr/0020-trust-reconcile-manifest-and-claim-classification.md +290 -0
- package/docs/adr/0021-assignment-leases-and-stale-claim-takeover.md +221 -0
- package/docs/adr/0022-fail-closed-delivery-reconciliation-with-governed-exemptions.md +463 -0
- package/docs/agent-system-guidebook.md +3 -3
- package/docs/agent-usage-feedback-loop.md +6 -6
- package/docs/context-map.md +10 -7
- package/docs/decisions/decision-records.md +57 -0
- package/docs/decisions/index.md +17 -0
- package/docs/decisions/knowledge-store-provider.md +51 -0
- package/docs/decisions/model-routing.md +63 -0
- package/docs/decisions/promotion-gate.md +52 -0
- package/docs/decisions/standing-directives.md +66 -0
- package/docs/developer-architecture.md +1 -1
- package/docs/fixture-ownership.md +4 -0
- package/docs/flow-kit-repository-contract.md +5 -5
- package/docs/getting-started.md +12 -2
- package/docs/integrations/harness-install.md +2 -1
- package/docs/kit-authoring-guide.md +64 -5
- package/docs/knowledge-kit.md +5 -1
- package/docs/learnings/2026-07-improvement-program.md +203 -0
- package/docs/migrations.md +6 -5
- package/docs/north-star.md +2 -2
- package/docs/operating-layers.md +4 -3
- package/docs/repository-structure.md +5 -5
- package/docs/skills-map.md +8 -8
- package/docs/spec/runtime-hook-surface.md +19 -15
- package/docs/spikes/graph-provider-2026-07.md +155 -0
- package/docs/standards-register.md +7 -7
- package/docs/trust-anchor-adoption.md +105 -4
- package/docs/work-item-adapters.md +1 -1
- package/docs/workflow-artifact-lifecycle.md +72 -9
- package/docs/workflow-eval-strategy.md +3 -3
- package/docs/workflow-shared-contracts.md +3 -2
- package/docs/workflow-usage-guide.md +260 -21
- package/evals/acceptance/DEMO-false-completion.md +20 -11
- package/evals/acceptance/prove-capture-teeth-declared.sh +15 -15
- package/evals/acceptance/prove-capture-teeth.sh +11 -11
- package/evals/acceptance/test_kiro_harness.sh +1 -1
- package/evals/ci/antigaming-suite.sh +5 -0
- package/evals/ci/run-baseline.sh +71 -1
- 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/liveness-fresh.json +9 -0
- package/evals/fixtures/assignment-provider/liveness-stale.json +9 -0
- package/evals/fixtures/flow-kit-repository/invalid-bad-dependency/flows/review.flow.json +26 -0
- package/evals/fixtures/flow-kit-repository/invalid-bad-dependency/kit.json +13 -0
- package/evals/fixtures/flow-kit-repository/valid-with-dependency/flows/review.flow.json +26 -0
- package/evals/fixtures/flow-kit-repository/valid-with-dependency/kit.json +13 -0
- package/evals/fixtures/hook-influence/cases.json +32 -0
- package/evals/fixtures/pull-work-provider/github-issues.json +6 -6
- package/evals/fixtures/trust-reconcile-exploits/fabricated-attestation.json +59 -0
- package/evals/fixtures/trust-reconcile-exploits/no-label-bypass.json +36 -0
- package/evals/fixtures/trust-reconcile-exploits/skip-assumed-bypass.json +59 -0
- package/evals/fixtures/trust-reconcile-exploits/status-misassertion.json +48 -0
- package/evals/fixtures/trust-reconcile-exploits/waived-command-check.json +44 -0
- package/evals/fixtures/trust-reconcile-mixed-bundle/mixed-bundle.json +159 -0
- package/evals/fixtures/trust-reconcile-ws3/ws3-bundle.json +920 -0
- package/evals/integration/test_actor_identity.sh +254 -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_builder_step_producers.sh +33 -23
- package/evals/integration/test_bundle_install.sh +22 -16
- package/evals/integration/test_bundle_lifecycle.sh +11 -11
- package/evals/integration/test_captured_fail_reconciliation.sh +50 -50
- package/evals/integration/test_checkpoint_signing.sh +43 -0
- package/evals/integration/test_claim_lookup.sh +4 -4
- package/evals/integration/test_codex_hook_resolution.sh +114 -0
- package/evals/integration/test_command_log_concurrency.sh +8 -8
- package/evals/integration/test_command_log_fork_classification.sh +3 -3
- package/evals/integration/test_command_log_integrity.sh +16 -16
- package/evals/integration/test_console_learning_projection.sh +1 -1
- package/evals/integration/test_critique_supersession_roundtrip.sh +182 -0
- package/evals/integration/test_dual_emit_flow_step.sh +145 -0
- package/evals/integration/test_enforcer_expects_driven.sh +12 -12
- package/evals/integration/test_evidence_capture_hook.sh +14 -13
- package/evals/integration/test_fixture_retirement_audit.sh +2 -2
- package/evals/integration/test_flow_agents_statusline.sh +2 -2
- package/evals/integration/test_flow_kit_install_git.sh +52 -0
- package/evals/integration/test_flow_kit_repository.sh +2 -0
- package/evals/integration/test_flowdef_session_activation.sh +6 -6
- package/evals/integration/test_flowdef_union_floor_regression.sh +429 -0
- package/evals/integration/test_gate_bypass_chain.sh +52 -40
- package/evals/integration/test_gate_lockdown.sh +97 -73
- package/evals/integration/test_gate_review_inquiry_records.sh +3 -3
- package/evals/integration/test_goal_fit_escape_hatch.sh +10 -10
- package/evals/integration/test_goal_fit_ghost_session.sh +104 -0
- package/evals/integration/test_goal_fit_hook.sh +32 -32
- package/evals/integration/test_goal_fit_rederive.sh +9 -9
- package/evals/integration/test_hook_category_behaviors.sh +103 -0
- package/evals/integration/test_hook_influence_cases.sh +1 -0
- package/evals/integration/test_install_merge.sh +262 -1
- package/evals/integration/test_kit_identity_trust.sh +6 -6
- package/evals/integration/test_liveness_conflict_injection.sh +587 -0
- package/evals/integration/test_liveness_heartbeat.sh +677 -0
- package/evals/integration/test_liveness_verdict.sh +394 -0
- package/evals/integration/test_local_flow_kit_install.sh +15 -0
- package/evals/integration/test_phase_map_and_gate_claim.sh +63 -11
- package/evals/integration/test_promote_gate.sh +178 -0
- package/evals/integration/test_publish_delivery.sh +23 -6
- package/evals/integration/test_pull_work_assignment_join.sh +132 -0
- package/evals/integration/test_pull_work_liveness_preflight.sh +394 -0
- package/evals/integration/test_pull_work_provider.sh +2 -2
- package/evals/integration/test_reconcile_soundness.sh +37 -13
- package/evals/integration/test_resolvefirststep_security.sh +22 -0
- package/evals/integration/test_runtime_adapter_activation.sh +87 -18
- package/evals/integration/test_session_resume_roundtrip.sh +147 -0
- package/evals/integration/test_sidecar_field_preservation.sh +249 -0
- package/evals/integration/test_trust_reconcile.sh +11 -10
- package/evals/integration/test_trust_reconcile_manifest.sh +79 -0
- package/evals/integration/test_trust_reconcile_mixed_bundle.sh +96 -0
- package/evals/integration/test_trust_reconcile_negatives.sh +759 -0
- package/evals/integration/test_usage_feedback_global.sh +5 -5
- package/evals/integration/test_validate_artifacts_portability.sh +60 -0
- package/evals/integration/test_verify_cli.sh +2 -2
- package/evals/integration/test_veritas_governance_kit.sh +117 -0
- package/evals/integration/test_workflow_artifact_cleanup_audit.sh +26 -1
- package/evals/integration/test_workflow_artifacts.sh +33 -0
- package/evals/integration/test_workflow_sidecar_writer.sh +802 -74
- package/evals/integration/test_workflow_steering_hook.sh +175 -5
- package/evals/lib/codex-provider.sh +1 -1
- package/evals/lib/node.sh +19 -1
- package/evals/run.sh +32 -0
- package/evals/static/test_decisions.sh +150 -0
- package/evals/static/test_flowdef_codeowners_coverage.sh +62 -0
- package/evals/static/test_knowledge_providers.sh +23 -0
- package/evals/static/test_package.sh +19 -10
- package/evals/static/test_universal_bundles.sh +48 -3
- package/evals/static/test_workflow_skills.sh +41 -8
- package/kits/builder/flows/build.flow.json +3 -74
- package/kits/builder/flows/publish-learn.flow.json +90 -0
- package/kits/builder/kit.json +11 -0
- package/kits/builder/skills/builder-shape/SKILL.md +1 -1
- package/kits/builder/skills/deliver/SKILL.md +32 -11
- package/kits/builder/skills/design-probe/SKILL.md +4 -4
- package/kits/builder/skills/evidence-gate/SKILL.md +26 -1
- package/kits/builder/skills/execute-plan/SKILL.md +2 -2
- package/kits/builder/skills/fix-bug/SKILL.md +2 -0
- package/kits/builder/skills/gate-review/SKILL.md +6 -6
- package/kits/builder/skills/idea-to-backlog/SKILL.md +1 -1
- package/kits/builder/skills/learning-review/SKILL.md +7 -7
- package/kits/builder/skills/pickup-probe/SKILL.md +5 -4
- package/kits/builder/skills/plan-work/SKILL.md +27 -5
- package/kits/builder/skills/pull-work/SKILL.md +148 -5
- package/kits/builder/skills/release-readiness/SKILL.md +4 -4
- package/kits/builder/skills/review-work/SKILL.md +24 -1
- package/kits/builder/skills/tdd-workflow/SKILL.md +2 -0
- package/kits/builder/skills/verify-work/SKILL.md +8 -1
- package/kits/catalog.json +6 -0
- package/kits/knowledge/kit.json +35 -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 +125 -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 +24 -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/work-item/index.js +204 -0
- package/kits/veritas-governance/adapter/readiness-to-trust-bundle.mjs +178 -0
- package/kits/veritas-governance/docs/README.md +75 -0
- package/kits/veritas-governance/fixtures/readiness/not-ready.readiness-report.json +4645 -0
- package/kits/veritas-governance/fixtures/readiness/ready.readiness-report.json +4403 -0
- package/kits/veritas-governance/flows/readiness-check.flow.json +35 -0
- package/kits/veritas-governance/kit.json +16 -0
- package/package.json +11 -5
- package/packaging/README.md +2 -2
- package/packaging/conformance/fixtures/config-protection--allow-read-state-json.json +20 -0
- package/packaging/conformance/fixtures/config-protection--block-write-state-json.json +20 -0
- package/packaging/conformance/fixtures/stop-goal-fit--block-bundle-disputed-claim.json +1 -1
- package/packaging/conformance/fixtures/stop-goal-fit--block-capture-contradicts-claimed-pass.json +1 -1
- package/packaging/manifest.json +9 -10
- package/schemas/assignment-provider-settings.schema.json +125 -0
- package/schemas/decision-record.schema.json +109 -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/schemas/workflow-evidence.schema.json +1 -1
- package/schemas/workflow-state.schema.json +5 -0
- package/scripts/README.md +8 -3
- package/scripts/check-content-boundary.cjs +1 -1
- package/scripts/check-decisions.cjs +356 -0
- package/scripts/ci/derive-claim-status.mjs +74 -0
- package/scripts/ci/trust-reconcile.js +957 -103
- package/scripts/git-status.sh +2 -2
- package/scripts/hooks/claude-telemetry-hook.js +37 -3
- package/scripts/hooks/codex-telemetry-hook.js +36 -2
- package/scripts/hooks/config-protection.js +102 -30
- package/scripts/hooks/evidence-capture.js +3 -3
- package/scripts/hooks/lib/actor-identity.js +368 -0
- package/scripts/hooks/lib/config-protection-remedies.js +69 -0
- package/scripts/hooks/lib/liveness-heartbeat.js +361 -0
- package/scripts/hooks/lib/liveness-policy.js +127 -0
- package/scripts/hooks/lib/liveness-read.js +105 -12
- package/scripts/hooks/lib/liveness-write.js +46 -0
- package/scripts/hooks/lib/local-artifact-paths.js +12 -6
- package/scripts/hooks/opencode-telemetry-hook.js +27 -1
- package/scripts/hooks/pi-telemetry-hook.js +27 -1
- package/scripts/hooks/stop-goal-fit.js +157 -26
- package/scripts/hooks/workflow-steering.js +77 -3
- package/scripts/install-codex-home.sh +138 -38
- package/scripts/statusline/flow-agents-statusline.js +2 -2
- package/src/cli/assignment-provider.ts +845 -0
- package/src/cli/console-learning-projection.ts +3 -2
- package/src/cli/effective-assignment-provider-settings.ts +112 -0
- package/src/cli/init.ts +101 -21
- package/src/cli/kit.ts +37 -6
- package/src/cli/public-api.test.mjs +58 -6
- package/src/cli/pull-work-provider.ts +1 -1
- package/src/cli/trust-bundle-policy-order.test.mjs +87 -0
- package/src/cli/usage-feedback.ts +3 -3
- package/src/cli/validate-hook-influence.ts +1 -0
- package/src/cli/validate-workflow-artifacts.ts +27 -7
- package/src/cli/veritas-readiness-adapter.test.mjs +267 -0
- package/src/cli/workflow-artifact-cleanup-audit.ts +35 -2
- package/src/cli/workflow-sidecar.ts +933 -168
- package/src/cli.ts +6 -0
- package/src/flow-kit/validate.ts +74 -1
- package/src/index.ts +6 -2
- package/src/lib/flow-resolver.ts +123 -55
- package/src/lib/local-artifact-root.ts +24 -5
- package/src/lib/workflow-learning-projection.ts +2 -2
- package/src/runtime-adapters.ts +61 -15
- package/src/tools/build-universal-bundles.ts +49 -16
- package/src/tools/generate-context-map.ts +10 -8
- package/src/tools/validate-source-tree.ts +24 -1
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test_promote_gate.sh - promote-then-archive gate (issue #312).
|
|
3
|
+
#
|
|
4
|
+
# Durable-residue extraction is the archival act. Covers:
|
|
5
|
+
# 1. promote writes a well-formed, session-local promotion claim referencing
|
|
6
|
+
# existing durable doc paths (metadata.promotion + policy_rule evidence, no
|
|
7
|
+
# execution.label) and an auditable promotion.json.
|
|
8
|
+
# 2. promote REJECTS a nonexistent --evidence-path (fail loud).
|
|
9
|
+
# 3. promote --none --reason records an explicit no-residue claim; --none without
|
|
10
|
+
# --reason is refused.
|
|
11
|
+
# 4. cleanup-audit classification: a delivered/accepted session WITHOUT a promotion
|
|
12
|
+
# claim is a cleanup_candidate (blocked from archive) with a remedy naming the
|
|
13
|
+
# promote step; WITH a real claim OR a --none claim it stays terminal_done (AC1-3).
|
|
14
|
+
# 5. CRITICAL: trust-reconcile stays exit 0 on a bundle carrying a promotion claim -
|
|
15
|
+
# the claim classifies session-local (ATTESTED), never an unbacked/not-run command
|
|
16
|
+
# divergence (proves R1 reconcile-safety, no new manifest entry required).
|
|
17
|
+
#
|
|
18
|
+
# Deterministic, no model spend, self-cleaning.
|
|
19
|
+
# Usage: bash evals/integration/test_promote_gate.sh
|
|
20
|
+
set -uo pipefail
|
|
21
|
+
|
|
22
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
23
|
+
source "$ROOT/evals/lib/node.sh"
|
|
24
|
+
|
|
25
|
+
TMP="$(mktemp -d)"
|
|
26
|
+
trap 'rm -rf "$TMP"' EXIT
|
|
27
|
+
unset FORCE_COLOR # known local artifact that can perturb child-process output
|
|
28
|
+
|
|
29
|
+
errors=0
|
|
30
|
+
pass() { echo " PASS: $1"; }
|
|
31
|
+
fail() { echo " FAIL: $1"; errors=$((errors + 1)); }
|
|
32
|
+
|
|
33
|
+
echo "=== Promote-then-archive gate (#312) ==="
|
|
34
|
+
|
|
35
|
+
# JSON reader helpers (dedicated files; no inline interpreter flags).
|
|
36
|
+
cat > "$TMP/inspect.cjs" <<'JS'
|
|
37
|
+
const fs = require("fs");
|
|
38
|
+
const b = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
|
39
|
+
const claim = (b.claims || []).find((c) => c.metadata && c.metadata.promotion);
|
|
40
|
+
const ev = claim && (b.evidence || []).find((e) => e.claimId === claim.id);
|
|
41
|
+
const out = {
|
|
42
|
+
found: String(!!claim),
|
|
43
|
+
status: (claim && claim.status) || "",
|
|
44
|
+
targets: (claim && claim.metadata.promotion.targets) || [],
|
|
45
|
+
none: String(claim ? claim.metadata.promotion.none : ""),
|
|
46
|
+
evidenceType: (ev && ev.evidenceType) || "",
|
|
47
|
+
hasExecutionLabel: String(!!(ev && ev.execution && ev.execution.label)),
|
|
48
|
+
};
|
|
49
|
+
fs.writeFileSync(process.argv[3], JSON.stringify(out));
|
|
50
|
+
JS
|
|
51
|
+
cat > "$TMP/field.cjs" <<'JS'
|
|
52
|
+
const fs = require("fs");
|
|
53
|
+
const o = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
|
54
|
+
let v = o;
|
|
55
|
+
for (const k of process.argv[3].split(".")) v = v[k];
|
|
56
|
+
console.log(Array.isArray(v) ? v.join(",") : v);
|
|
57
|
+
JS
|
|
58
|
+
cat > "$TMP/bucketof.cjs" <<'JS'
|
|
59
|
+
const fs = require("fs");
|
|
60
|
+
const d = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
|
61
|
+
for (const [b, arr] of Object.entries(d.buckets)) {
|
|
62
|
+
for (const it of arr) if (it.slug === process.argv[3]) { console.log(b + "|" + (it.reasons[0] || "")); process.exit(0); }
|
|
63
|
+
}
|
|
64
|
+
console.log("MISSING|");
|
|
65
|
+
JS
|
|
66
|
+
|
|
67
|
+
REPO="$TMP/repo"
|
|
68
|
+
mkdir -p "$REPO/kits" "$REPO/docs/decisions" "$REPO/docs/learnings"
|
|
69
|
+
printf 'x\n' > "$REPO/docs/decisions/promotion-gate.md"
|
|
70
|
+
printf 'y\n' > "$REPO/docs/learnings/note.md"
|
|
71
|
+
SESSION_ROOT="$REPO/.kontourai/flow-agents"
|
|
72
|
+
|
|
73
|
+
ensure() {
|
|
74
|
+
flow_agents_node workflow-sidecar ensure-session \
|
|
75
|
+
--artifact-root "$SESSION_ROOT" --task-slug "$1" \
|
|
76
|
+
--source-request "r" --title "T" --summary "s" \
|
|
77
|
+
--criterion "c1" --next-action "n" --timestamp "2026-07-02T00:00:00Z" \
|
|
78
|
+
>/dev/null 2>"$TMP/$1.ensure.err"
|
|
79
|
+
}
|
|
80
|
+
seed_evidence() {
|
|
81
|
+
flow_agents_node workflow-sidecar record-evidence "$1" --verdict pass \
|
|
82
|
+
--check-json '{"id":"docs-check","kind":"policy","status":"pass","summary":"docs reviewed"}' \
|
|
83
|
+
--timestamp "2026-07-02T00:01:00Z" >/dev/null 2>"$TMP/ev.err"
|
|
84
|
+
}
|
|
85
|
+
term_state() {
|
|
86
|
+
printf '{"schema_version":"1.0","task_slug":"%s","status":"accepted","phase":"done","updated_at":"2026-07-02T00:00:00Z","next_action":{"status":"done","summary":"done"}}' "$2" > "$1/state.json"
|
|
87
|
+
}
|
|
88
|
+
field() { node "$TMP/field.cjs" "$1" "$2"; }
|
|
89
|
+
|
|
90
|
+
# --- 1. promote writes a well-formed session-local claim ---------------------
|
|
91
|
+
ensure promoted
|
|
92
|
+
PDIR="$SESSION_ROOT/promoted"
|
|
93
|
+
seed_evidence "$PDIR"
|
|
94
|
+
if flow_agents_node workflow-sidecar promote "$PDIR" --repo-root "$REPO" \
|
|
95
|
+
--evidence-path docs/decisions/promotion-gate.md \
|
|
96
|
+
--evidence-path docs/learnings/note.md \
|
|
97
|
+
--timestamp "2026-07-02T00:02:00Z" >"$TMP/promote.out" 2>"$TMP/promote.err"; then
|
|
98
|
+
pass "promote with existing paths exits 0"
|
|
99
|
+
else
|
|
100
|
+
fail "promote with existing paths should exit 0: $(cat "$TMP/promote.err")"
|
|
101
|
+
fi
|
|
102
|
+
[[ -f "$PDIR/promotion.json" ]] && pass "promote writes auditable promotion.json" || fail "promote writes auditable promotion.json"
|
|
103
|
+
|
|
104
|
+
node "$TMP/inspect.cjs" "$PDIR/trust.bundle" "$TMP/shape.json"
|
|
105
|
+
[[ "$(field "$TMP/shape.json" found)" == "true" ]] && pass "bundle carries a promotion claim (metadata.promotion)" || fail "bundle carries a promotion claim"
|
|
106
|
+
[[ "$(field "$TMP/shape.json" status)" == "verified" ]] && pass "promotion claim derives status verified" || fail "promotion claim derives status verified (got $(field "$TMP/shape.json" status))"
|
|
107
|
+
[[ "$(field "$TMP/shape.json" evidenceType)" == "policy_rule" ]] && pass "promotion evidence is session-local policy_rule" || fail "promotion evidence is session-local policy_rule (got $(field "$TMP/shape.json" evidenceType))"
|
|
108
|
+
[[ "$(field "$TMP/shape.json" hasExecutionLabel)" == "false" ]] && pass "promotion claim carries no execution.label (never a manifest command)" || fail "promotion claim must not carry an execution.label"
|
|
109
|
+
[[ "$(field "$TMP/shape.json" targets)" == "docs/decisions/promotion-gate.md,docs/learnings/note.md" ]] && pass "promotion evidence refs = the durable doc paths written" || fail "promotion targets wrong: $(field "$TMP/shape.json" targets)"
|
|
110
|
+
|
|
111
|
+
# --- 2. promote rejects a nonexistent path -----------------------------------
|
|
112
|
+
if flow_agents_node workflow-sidecar promote "$PDIR" --repo-root "$REPO" \
|
|
113
|
+
--evidence-path docs/decisions/does-not-exist.md \
|
|
114
|
+
--timestamp "2026-07-02T00:03:00Z" >"$TMP/bad.out" 2>"$TMP/bad.err"; then
|
|
115
|
+
fail "promote should reject a nonexistent evidence path"
|
|
116
|
+
else
|
|
117
|
+
grep -q "does not exist on disk" "$TMP/bad.err" && pass "promote rejects a nonexistent evidence path (fail loud)" || fail "promote rejection message unclear: $(cat "$TMP/bad.err")"
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
# --- 3. explicit no-residue promotion ----------------------------------------
|
|
121
|
+
ensure none-promoted
|
|
122
|
+
NDIR="$SESSION_ROOT/none-promoted"
|
|
123
|
+
seed_evidence "$NDIR"
|
|
124
|
+
if flow_agents_node workflow-sidecar promote "$NDIR" --repo-root "$REPO" \
|
|
125
|
+
--none --reason "no durable residue: pure refactor" \
|
|
126
|
+
--timestamp "2026-07-02T00:04:00Z" >"$TMP/none.out" 2>"$TMP/none.err"; then
|
|
127
|
+
pass "promote --none --reason exits 0"
|
|
128
|
+
else
|
|
129
|
+
fail "promote --none --reason should exit 0: $(cat "$TMP/none.err")"
|
|
130
|
+
fi
|
|
131
|
+
node "$TMP/inspect.cjs" "$NDIR/trust.bundle" "$TMP/noneshape.json"
|
|
132
|
+
[[ "$(field "$TMP/noneshape.json" none)" == "true" ]] && pass "no-residue claim records none=true" || fail "no-residue claim should record none=true"
|
|
133
|
+
|
|
134
|
+
if flow_agents_node workflow-sidecar promote "$NDIR" --none \
|
|
135
|
+
--timestamp "2026-07-02T00:05:00Z" >"$TMP/none2.out" 2>"$TMP/none2.err"; then
|
|
136
|
+
fail "promote --none without --reason should be refused"
|
|
137
|
+
else
|
|
138
|
+
grep -q "requires --reason" "$TMP/none2.err" && pass "promote --none without --reason is refused" || fail "promote --none reason-required message unclear"
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
# --- 4. cleanup-audit classification with/without the claim ------------------
|
|
142
|
+
AUDIT_ROOT="$TMP/audit-root"
|
|
143
|
+
mkdir -p "$AUDIT_ROOT/promoted" "$AUDIT_ROOT/none-promoted" "$AUDIT_ROOT/unpromoted"
|
|
144
|
+
term_state "$AUDIT_ROOT/promoted" promoted
|
|
145
|
+
term_state "$AUDIT_ROOT/none-promoted" none-promoted
|
|
146
|
+
term_state "$AUDIT_ROOT/unpromoted" unpromoted
|
|
147
|
+
cp "$PDIR/trust.bundle" "$AUDIT_ROOT/promoted/trust.bundle"
|
|
148
|
+
cp "$NDIR/trust.bundle" "$AUDIT_ROOT/none-promoted/trust.bundle"
|
|
149
|
+
# unpromoted: deliberately no trust.bundle
|
|
150
|
+
|
|
151
|
+
flow_agents_node workflow-artifact-cleanup-audit --artifact-root "$AUDIT_ROOT" --json > "$TMP/audit.json" 2>&1
|
|
152
|
+
bucketof() { node "$TMP/bucketof.cjs" "$TMP/audit.json" "$1"; }
|
|
153
|
+
[[ "$(bucketof promoted | cut -d'|' -f1)" == "terminal_done" ]] && pass "promoted session stays terminal_done" || fail "promoted session should be terminal_done (got $(bucketof promoted))"
|
|
154
|
+
[[ "$(bucketof none-promoted | cut -d'|' -f1)" == "terminal_done" ]] && pass "no-residue promoted session stays terminal_done" || fail "no-residue session should be terminal_done (got $(bucketof none-promoted))"
|
|
155
|
+
[[ "$(bucketof unpromoted | cut -d'|' -f1)" == "cleanup_candidate" ]] && pass "unpromoted delivered session is a cleanup_candidate (archive blocked)" || fail "unpromoted session should be cleanup_candidate (got $(bucketof unpromoted))"
|
|
156
|
+
bucketof unpromoted | grep -q "promote" && pass "cleanup_candidate reason names the promote remedy" || fail "cleanup_candidate reason should name the promote step"
|
|
157
|
+
|
|
158
|
+
# --- 5. CRITICAL: trust-reconcile exit 0 on a bundle with a promotion claim --
|
|
159
|
+
if node "$ROOT/scripts/ci/trust-reconcile.js" --bundle "$PDIR/trust.bundle" \
|
|
160
|
+
--commands "true" --repo-root "$REPO" >"$TMP/reconcile.out" 2>&1; then
|
|
161
|
+
pass "trust-reconcile exits 0 on a bundle carrying a promotion claim"
|
|
162
|
+
else
|
|
163
|
+
fail "trust-reconcile should exit 0 on a promotion-claim bundle: $(tail -5 "$TMP/reconcile.out")"
|
|
164
|
+
fi
|
|
165
|
+
if grep -q "ATTESTED" "$TMP/reconcile.out" && ! grep -q "trust divergence" "$TMP/reconcile.out"; then
|
|
166
|
+
pass "promotion claim classifies session-local (ATTESTED), never an unbacked/not-run command"
|
|
167
|
+
else
|
|
168
|
+
fail "promotion claim must classify session-local, not as a command divergence"
|
|
169
|
+
grep -iE "classified|ATTESTED|trust divergence" "$TMP/reconcile.out"
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
echo ""
|
|
173
|
+
if [[ "$errors" -eq 0 ]]; then
|
|
174
|
+
echo "Promote-then-archive gate checks passed"
|
|
175
|
+
else
|
|
176
|
+
echo "Promote-then-archive gate checks failed: $errors"
|
|
177
|
+
exit 1
|
|
178
|
+
fi
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
# Proves that:
|
|
5
5
|
# 1. END-TO-END-RECORD-RELEASE: record-release auto-publishes trust.bundle.
|
|
6
6
|
# 2. SUBCOMMAND: publish-delivery subcommand copies bundle to delivery/.
|
|
7
|
-
# 3. RECONCILE-DIVERGENCE: delivery trust.bundle +
|
|
8
|
-
#
|
|
7
|
+
# 3. RECONCILE-DIVERGENCE: delivery trust.bundle (+ matching-sha checkpoint sibling, ADR
|
|
8
|
+
# 0022 addendum part 2 bundle-ownership binding) + CI fail -> exit 1.
|
|
9
|
+
# 4. RECONCILE-MATCHING: delivery trust.bundle (+ matching-sha checkpoint sibling) + CI
|
|
10
|
+
# pass -> exit 0.
|
|
9
11
|
# 5. FAIL-SOFT: no trust.bundle -> publishDelivery skips, record-release exits 0.
|
|
10
12
|
#
|
|
11
13
|
# Deterministic, no model spend, self-cleaning.
|
|
@@ -41,10 +43,10 @@ code_lines = [
|
|
|
41
43
|
"const fs = require('fs');",
|
|
42
44
|
"const [,, dest, label, passingStr] = process.argv;",
|
|
43
45
|
"const passing = passingStr === 'true';",
|
|
44
|
-
"const b = { schemaVersion:
|
|
46
|
+
"const b = { schemaVersion: 5, source: 'test-fixture',",
|
|
45
47
|
" claims: [{ id: 'c1', claimType: 'workflow.check.build',",
|
|
46
48
|
" value: passing ? 'pass' : 'fail', status: passing ? 'verified' : 'disputed',",
|
|
47
|
-
" subjectId: 'ts/build',
|
|
49
|
+
" subjectId: 'ts/build', facet: 'flow-agents.workflow',",
|
|
48
50
|
" subjectType: 'workflow-check', fieldOrBehavior: 'build',",
|
|
49
51
|
" createdAt: '2026-06-27T00:00:00Z', updatedAt: '2026-06-27T00:00:00Z',",
|
|
50
52
|
" impactLevel: 'high', verificationPolicyId: 'policy:wf.build' }],",
|
|
@@ -63,6 +65,19 @@ PY
|
|
|
63
65
|
node "$helper" "$dest" "$label" "$passing"
|
|
64
66
|
}
|
|
65
67
|
|
|
68
|
+
# write_checkpoint_to <delivery_dir> <sha>
|
|
69
|
+
# Writes a minimal, well-formed trust.checkpoint.json naming <sha> as commit_sha, so a
|
|
70
|
+
# bare fixture trust.bundle written directly into delivery/ (TEST 3/4 below -- these do
|
|
71
|
+
# NOT go through the real seal-checkpoint pipeline) still carries the commit-identity
|
|
72
|
+
# binding trust-reconcile.js's bundle-ownership staleness check now requires (ADR 0022
|
|
73
|
+
# addendum, part 2) for an auto-discovered bundle to be treated as owned by the change
|
|
74
|
+
# under test, not stale.
|
|
75
|
+
write_checkpoint_to() {
|
|
76
|
+
local delivery_dir="$1" sha="$2"
|
|
77
|
+
printf '{"schema_version":"1.0","slug":"publish-delivery-fixture","work_item":null,"status":"delivered","phase":"release","sealed_at":"2026-06-27T00:00:00Z","commit_sha":"%s","checkpoint":{"asOf":"2026-06-27T00:00:00.000Z","statusByClaimId":{}}}' \
|
|
78
|
+
"$sha" > "$delivery_dir/trust.checkpoint.json"
|
|
79
|
+
}
|
|
80
|
+
|
|
66
81
|
# Session setup helper
|
|
67
82
|
setup_session() {
|
|
68
83
|
local aroot="$1" slug="$2" bundle_src="$3"
|
|
@@ -181,8 +196,9 @@ mkdir -p "$REPO3/delivery"
|
|
|
181
196
|
# -> claimed cmd not in canonical set -> not-run divergence, AND canonical fails
|
|
182
197
|
DELIVERY3="$REPO3/delivery/trust.bundle"
|
|
183
198
|
write_bundle_to "$DELIVERY3" "node --version" "true"
|
|
199
|
+
write_checkpoint_to "$REPO3/delivery" "1111111111111111111111111111111111111111"
|
|
184
200
|
|
|
185
|
-
recon3_out=$(TRUST_RECONCILE_COMMANDS="false" \
|
|
201
|
+
recon3_out=$(TRUST_RECONCILE_SHA="1111111111111111111111111111111111111111" TRUST_RECONCILE_COMMANDS="false" \
|
|
186
202
|
node "$RECONCILE" --repo-root "$REPO3" 2>&1)
|
|
187
203
|
recon3_exit=$?
|
|
188
204
|
|
|
@@ -208,8 +224,9 @@ mkdir -p "$REPO4/delivery"
|
|
|
208
224
|
# Bundle claims "node --version" passed; canonical verify is ALSO "node --version" (passes)
|
|
209
225
|
DELIVERY4="$REPO4/delivery/trust.bundle"
|
|
210
226
|
write_bundle_to "$DELIVERY4" "node --version" "true"
|
|
227
|
+
write_checkpoint_to "$REPO4/delivery" "2222222222222222222222222222222222222222"
|
|
211
228
|
|
|
212
|
-
recon4_out=$(TRUST_RECONCILE_COMMANDS="node --version" \
|
|
229
|
+
recon4_out=$(TRUST_RECONCILE_SHA="2222222222222222222222222222222222222222" TRUST_RECONCILE_COMMANDS="node --version" \
|
|
213
230
|
node "$RECONCILE" --repo-root "$REPO4" 2>&1)
|
|
214
231
|
recon4_exit=$?
|
|
215
232
|
|
|
@@ -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"
|