@kontourai/flow-agents 2.3.0 → 2.4.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 +32 -0
- package/AGENTS.md +4 -4
- package/CHANGELOG.md +39 -0
- package/CONTEXT.md +14 -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/console-learning-projection.js +3 -2
- 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 +22 -6
- 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 +792 -69
- 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 +23 -1
- package/console.telemetry.json +22 -0
- package/context/contracts/artifact-contract.md +38 -7
- package/context/contracts/builder-kit-workflow-state-contract.md +3 -2
- package/context/contracts/decision-registry-contract.md +263 -0
- package/context/contracts/delivery-contract.md +1 -1
- package/context/contracts/planning-contract.md +2 -1
- package/context/contracts/review-contract.md +5 -3
- package/context/contracts/verification-contract.md +9 -1
- package/context/contracts/work-item-contract.md +1 -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/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 +283 -0
- package/docs/agent-system-guidebook.md +3 -3
- package/docs/agent-usage-feedback-loop.md +6 -6
- package/docs/context-map.md +9 -7
- package/docs/decisions/decision-records.md +57 -0
- package/docs/decisions/index.md +14 -0
- package/docs/decisions/promotion-gate.md +52 -0
- package/docs/developer-architecture.md +1 -1
- package/docs/fixture-ownership.md +3 -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 +1 -1
- package/docs/workflow-usage-guide.md +254 -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 +65 -1
- 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_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_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 +2 -2
- package/evals/integration/test_pull_work_liveness_preflight.sh +386 -0
- package/evals/integration/test_pull_work_provider.sh +2 -2
- package/evals/integration/test_reconcile_soundness.sh +4 -4
- 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 +2 -2
- 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 +151 -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 +723 -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 +22 -0
- package/evals/static/test_decisions.sh +150 -0
- package/evals/static/test_flowdef_codeowners_coverage.sh +56 -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 +13 -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 +80 -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/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 +8 -4
- 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/decision-record.schema.json +109 -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 +436 -79
- 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/console-learning-projection.ts +3 -2
- 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 +22 -6
- 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 +795 -71
- 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 +23 -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
|
|
@@ -41,10 +41,10 @@ code_lines = [
|
|
|
41
41
|
"const fs = require('fs');",
|
|
42
42
|
"const [,, dest, label, passingStr] = process.argv;",
|
|
43
43
|
"const passing = passingStr === 'true';",
|
|
44
|
-
"const b = { schemaVersion:
|
|
44
|
+
"const b = { schemaVersion: 5, source: 'test-fixture',",
|
|
45
45
|
" claims: [{ id: 'c1', claimType: 'workflow.check.build',",
|
|
46
46
|
" value: passing ? 'pass' : 'fail', status: passing ? 'verified' : 'disputed',",
|
|
47
|
-
" subjectId: 'ts/build',
|
|
47
|
+
" subjectId: 'ts/build', facet: 'flow-agents.workflow',",
|
|
48
48
|
" subjectType: 'workflow-check', fieldOrBehavior: 'build',",
|
|
49
49
|
" createdAt: '2026-06-27T00:00:00Z', updatedAt: '2026-06-27T00:00:00Z',",
|
|
50
50
|
" impactLevel: 'high', verificationPolicyId: 'policy:wf.build' }],",
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test_pull_work_liveness_preflight.sh — integration eval for the pull-work liveness
|
|
3
|
+
# selection preflight (issue #166, extended by ADR 0021 §1/§3; plan artifact at
|
|
4
|
+
# .kontourai/flow-agents/kontourai-flow-agents-166/kontourai-flow-agents-166--plan-work.md,
|
|
5
|
+
# Wave 2). Deterministic, no model spend, self-cleaning (mktemp -d + trap EXIT).
|
|
6
|
+
#
|
|
7
|
+
# Covers, per the plan's Wave 2 eval tasks (writer-shape unit checks + two-actor CLI-level
|
|
8
|
+
# exclusion simulation + static skill-text assertions, folded into one file):
|
|
9
|
+
# A. `liveness whoami` / `resolve-slug` CLI unit shapes (AC7):
|
|
10
|
+
# derived-actor fallback (ancestry/env chain), explicit --actor override, forced-unresolved
|
|
11
|
+
# non-throwing shape (contrasted with `liveness claim`'s still-fail-loud behavior), and
|
|
12
|
+
# resolve-slug's deterministic slug format + malformed-ref rejection (proving no duplicate
|
|
13
|
+
# validation logic exists outside workItemSlug()).
|
|
14
|
+
# B. resolve-slug parity with the workItemSlug()-derived `ensure-session --work-item` session
|
|
15
|
+
# directory (AC4).
|
|
16
|
+
# C. The deterministic two-actor CLI-level classification simulation (AC1, AC2, AC3, AC5):
|
|
17
|
+
# held (other-actor verified) / mine (self-actor verified) / reclaimable (stale, raw status
|
|
18
|
+
# not label) / free (no rows) / claim-emit-on-selection / force-write unconstrained at the
|
|
19
|
+
# CLI layer (Design Decision 4 — no holder-conflict check to override).
|
|
20
|
+
# D. Static skill-text require_text/reject_text assertions against the plan's prescribed
|
|
21
|
+
# sentences in kits/builder/skills/pull-work/SKILL.md and
|
|
22
|
+
# kits/builder/skills/pickup-probe/SKILL.md (AC6, AC8), including that the existing
|
|
23
|
+
# no-provider-mutation sentence is present and unchanged.
|
|
24
|
+
#
|
|
25
|
+
# Usage: bash evals/integration/test_pull_work_liveness_preflight.sh
|
|
26
|
+
|
|
27
|
+
set -uo pipefail
|
|
28
|
+
|
|
29
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
30
|
+
source "$ROOT/evals/lib/node.sh"
|
|
31
|
+
|
|
32
|
+
WRITER="workflow-sidecar"
|
|
33
|
+
SIDECAR_SRC="$ROOT/src/cli/workflow-sidecar.ts"
|
|
34
|
+
PULL="$ROOT/kits/builder/skills/pull-work/SKILL.md"
|
|
35
|
+
PICKUP_PROBE="$ROOT/kits/builder/skills/pickup-probe/SKILL.md"
|
|
36
|
+
|
|
37
|
+
for m in "$SIDECAR_SRC" "$PULL" "$PICKUP_PROBE"; do
|
|
38
|
+
if [[ ! -f "$m" ]]; then
|
|
39
|
+
echo "pull-work liveness preflight eval skipped: $m does not exist yet." >&2
|
|
40
|
+
exit 1
|
|
41
|
+
fi
|
|
42
|
+
done
|
|
43
|
+
|
|
44
|
+
TMPDIR_EVAL="$(mktemp -d)"
|
|
45
|
+
trap 'rm -rf "$TMPDIR_EVAL"' EXIT
|
|
46
|
+
|
|
47
|
+
errors=0
|
|
48
|
+
_pass() { echo " ✓ $1"; }
|
|
49
|
+
_fail() { echo " ✗ $1"; errors=$((errors + 1)); }
|
|
50
|
+
|
|
51
|
+
require_text() {
|
|
52
|
+
local path="$1" pattern="$2" label="$3"
|
|
53
|
+
if rg -q -- "$pattern" "$path"; then _pass "$label"; else _fail "$label"; fi
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
reject_text() {
|
|
57
|
+
local path="$1" pattern="$2" label="$3"
|
|
58
|
+
if rg -q -- "$pattern" "$path"; then _fail "$label"; else _pass "$label"; fi
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# new_scratch — fresh scratch artifact-root under the eval's own tmpdir.
|
|
62
|
+
new_scratch() {
|
|
63
|
+
mktemp -d "$TMPDIR_EVAL/scratch-XXXXXX"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
echo "=== Pull work liveness preflight (#166) ==="
|
|
67
|
+
|
|
68
|
+
# ─── A. `liveness whoami` / `resolve-slug` CLI unit shapes (AC7) ────────────
|
|
69
|
+
echo "--- A. whoami / resolve-slug unit shapes ---"
|
|
70
|
+
|
|
71
|
+
A_ROOT="$(new_scratch)/.kontourai/flow-agents"
|
|
72
|
+
|
|
73
|
+
# A1: derived-actor fallback — no explicit --actor/--json flag beyond a CLAUDE_CODE_SESSION_ID
|
|
74
|
+
# env override (mirrors the two-holders env-injection pattern from
|
|
75
|
+
# evals/integration/test_workflow_sidecar_writer.sh) — returns a JSON object with non-empty
|
|
76
|
+
# actor/source fields, exit 0.
|
|
77
|
+
A1_OUT="$(CLAUDE_CODE_SESSION_ID=sess-a flow_agents_node "$WRITER" liveness whoami --json --artifact-root "$A_ROOT" 2>"$TMPDIR_EVAL/a1.err")"
|
|
78
|
+
A1_STATUS=$?
|
|
79
|
+
if [[ "$A1_STATUS" -eq 0 ]] && node -e '
|
|
80
|
+
const o = JSON.parse(process.argv[1]);
|
|
81
|
+
if (typeof o.actor !== "string" || !o.actor) throw new Error("actor missing/empty");
|
|
82
|
+
if (typeof o.source !== "string" || !o.source) throw new Error("source missing/empty");
|
|
83
|
+
' "$A1_OUT" 2>"$TMPDIR_EVAL/a1-check.err"; then
|
|
84
|
+
_pass "liveness whoami --json (derived-actor fallback) exits 0 with non-empty actor/source (AC7)"
|
|
85
|
+
else
|
|
86
|
+
_fail "liveness whoami derived-actor fallback did not return the expected shape: out=$A1_OUT status=$A1_STATUS err=$(cat "$TMPDIR_EVAL/a1.err" "$TMPDIR_EVAL/a1-check.err" 2>/dev/null)"
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
# A2: explicit --actor override, after sanitizeSegment.
|
|
90
|
+
A2_OUT="$(flow_agents_node "$WRITER" liveness whoami --actor custom-name --json --artifact-root "$A_ROOT" 2>"$TMPDIR_EVAL/a2.err")"
|
|
91
|
+
if [[ "$A2_OUT" == '{"actor":"custom-name","source":"explicit-override"}' ]]; then
|
|
92
|
+
_pass "liveness whoami --actor custom-name --json returns {actor, source: explicit-override} (AC7)"
|
|
93
|
+
else
|
|
94
|
+
_fail "liveness whoami --actor override mismatch: out=$A2_OUT err=$(cat "$TMPDIR_EVAL/a2.err")"
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# A3: forced-unresolved actor — whoami never dies / never exits nonzero (read-only, advisory,
|
|
98
|
+
# non-enforcing contract), even though the actor could not be resolved.
|
|
99
|
+
if A3_OUT="$(FLOW_AGENTS_ACTOR_TEST_FORCE_UNRESOLVED=1 NODE_ENV=test flow_agents_node "$WRITER" liveness whoami --json --artifact-root "$A_ROOT" 2>"$TMPDIR_EVAL/a3.err")" \
|
|
100
|
+
&& [[ "$A3_OUT" == '{"actor":"","source":"test-forced-unresolved"}' ]]; then
|
|
101
|
+
_pass "liveness whoami never dies under a forced-unresolved actor — prints {actor:\"\", source:\"test-forced-unresolved\"} and exits 0 (AC7, read-only contract)"
|
|
102
|
+
else
|
|
103
|
+
_fail "liveness whoami under forced-unresolved actor did not behave as expected: out=$A3_OUT err=$(cat "$TMPDIR_EVAL/a3.err")"
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
# A4: contrast — `liveness claim` under the SAME forced-unresolved env still fails loud (dies),
|
|
107
|
+
# proving the enforcement point stays at the write path, not whoami.
|
|
108
|
+
if FLOW_AGENTS_ACTOR_TEST_FORCE_UNRESOLVED=1 NODE_ENV=test flow_agents_node "$WRITER" liveness claim forced-unresolved-preflight-subj --artifact-root "$A_ROOT" >"$TMPDIR_EVAL/a4.out" 2>"$TMPDIR_EVAL/a4.err"; then
|
|
109
|
+
_fail "liveness claim under a forced-unresolved actor should have exited nonzero (contrast with whoami)"
|
|
110
|
+
elif rg -q -- '--actor' "$TMPDIR_EVAL/a4.err" && rg -q -- 'FLOW_AGENTS_ACTOR' "$TMPDIR_EVAL/a4.err"; then
|
|
111
|
+
_pass "liveness claim still fails loud (nonzero exit, remediation naming --actor/FLOW_AGENTS_ACTOR) under the same forced-unresolved env whoami tolerates (AC7 contrast)"
|
|
112
|
+
else
|
|
113
|
+
_fail "liveness claim forced-unresolved rejection lacked expected remediation: $(cat "$TMPDIR_EVAL/a4.out" "$TMPDIR_EVAL/a4.err")"
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
# A5: resolve-slug deterministic slug format.
|
|
117
|
+
A5_OUT="$(flow_agents_node "$WRITER" resolve-slug 'kontourai/flow-agents#166' 2>"$TMPDIR_EVAL/a5.err")"
|
|
118
|
+
A5_STATUS=$?
|
|
119
|
+
if [[ "$A5_STATUS" -eq 0 ]] && [[ "$A5_OUT" == "kontourai-flow-agents-166" ]]; then
|
|
120
|
+
_pass "resolve-slug kontourai/flow-agents#166 prints kontourai-flow-agents-166 and exits 0 (AC4, AC7)"
|
|
121
|
+
else
|
|
122
|
+
_fail "resolve-slug deterministic format mismatch: out=$A5_OUT status=$A5_STATUS err=$(cat "$TMPDIR_EVAL/a5.err")"
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
# A6: malformed ref (no #id) is rejected with workItemSlug()'s existing message — proves no
|
|
126
|
+
# duplicate validation logic was introduced for resolve-slug.
|
|
127
|
+
if flow_agents_node "$WRITER" resolve-slug 'owner/repo' >"$TMPDIR_EVAL/a6.out" 2>"$TMPDIR_EVAL/a6.err"; then
|
|
128
|
+
_fail "resolve-slug should reject a ref with no # separator"
|
|
129
|
+
elif rg -q -- 'owner/repo#id format' "$TMPDIR_EVAL/a6.err"; then
|
|
130
|
+
_pass "resolve-slug rejects a ref with no # separator using workItemSlug()'s existing message (AC7)"
|
|
131
|
+
else
|
|
132
|
+
_fail "resolve-slug malformed-ref rejection message mismatch: $(cat "$TMPDIR_EVAL/a6.out" "$TMPDIR_EVAL/a6.err")"
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
# A7: non-numeric id is rejected with workItemSlug()'s existing message.
|
|
136
|
+
if flow_agents_node "$WRITER" resolve-slug 'owner/repo#abc' >"$TMPDIR_EVAL/a7.out" 2>"$TMPDIR_EVAL/a7.err"; then
|
|
137
|
+
_fail "resolve-slug should reject a non-numeric issue id"
|
|
138
|
+
elif rg -q -- 'numeric issue number' "$TMPDIR_EVAL/a7.err"; then
|
|
139
|
+
_pass "resolve-slug rejects a non-numeric issue id using workItemSlug()'s existing message (AC7)"
|
|
140
|
+
else
|
|
141
|
+
_fail "resolve-slug non-numeric-id rejection message mismatch: $(cat "$TMPDIR_EVAL/a7.out" "$TMPDIR_EVAL/a7.err")"
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
# ─── B. resolve-slug parity with ensure-session --work-item session dir (AC4) ───
|
|
145
|
+
echo "--- B. resolve-slug parity with ensure-session --work-item ---"
|
|
146
|
+
|
|
147
|
+
B_ROOT="$(new_scratch)/.kontourai/flow-agents"
|
|
148
|
+
if flow_agents_node "$WRITER" ensure-session \
|
|
149
|
+
--artifact-root "$B_ROOT" \
|
|
150
|
+
--work-item 'owner/repo#42' \
|
|
151
|
+
--title "Parity Check" \
|
|
152
|
+
--summary "resolve-slug parity fixture." \
|
|
153
|
+
--timestamp "2026-07-01T00:00:00Z" >"$TMPDIR_EVAL/b-ensure.out" 2>"$TMPDIR_EVAL/b-ensure.err"; then
|
|
154
|
+
B_SESSION_DIR="$(find "$B_ROOT" -mindepth 1 -maxdepth 1 -type d ! -name liveness | head -n1)"
|
|
155
|
+
B_SLUG_FROM_DIR="$(basename "$B_SESSION_DIR")"
|
|
156
|
+
B_SLUG_FROM_RESOLVE="$(flow_agents_node "$WRITER" resolve-slug 'owner/repo#42' 2>"$TMPDIR_EVAL/b-resolve.err")"
|
|
157
|
+
if [[ -n "$B_SESSION_DIR" ]] && [[ "$B_SLUG_FROM_DIR" == "$B_SLUG_FROM_RESOLVE" ]]; then
|
|
158
|
+
_pass "resolve-slug owner/repo#42 ($B_SLUG_FROM_RESOLVE) matches the ensure-session --work-item session directory name ($B_SLUG_FROM_DIR) (AC4)"
|
|
159
|
+
else
|
|
160
|
+
_fail "resolve-slug/ensure-session session-dir parity mismatch: dir=$B_SLUG_FROM_DIR resolve-slug=$B_SLUG_FROM_RESOLVE err=$(cat "$TMPDIR_EVAL/b-resolve.err")"
|
|
161
|
+
fi
|
|
162
|
+
else
|
|
163
|
+
_fail "ensure-session --work-item owner/repo#42 fixture setup failed: $(cat "$TMPDIR_EVAL/b-ensure.out" "$TMPDIR_EVAL/b-ensure.err")"
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
# ─── C. Two-actor CLI-level classification simulation (AC1, AC2, AC3, AC5) ──
|
|
167
|
+
echo "--- C. Two-actor classification simulation ---"
|
|
168
|
+
|
|
169
|
+
C_ROOT="$(new_scratch)/.kontourai/flow-agents"
|
|
170
|
+
|
|
171
|
+
# status_row_json <root> <subjectId> <now_iso> — prints the raw `liveness status --json --subject`
|
|
172
|
+
# array for the given subject (the preflight's exact read shape: full row set, one call).
|
|
173
|
+
status_row_json() {
|
|
174
|
+
local root="$1" subject="$2" now="$3"
|
|
175
|
+
flow_agents_node "$WRITER" liveness status --json --subject "$subject" --now "$now" --artifact-root "$root"
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
# C1/C2: Held-exclusion + own-actor mine — actor agent-a claims subject subj-held.
|
|
179
|
+
flow_agents_node "$WRITER" liveness claim subj-held --actor agent-a --at "2026-07-01T12:00:00Z" --ttl 1800 --artifact-root "$C_ROOT" >/dev/null 2>"$TMPDIR_EVAL/c-claim-a.err"
|
|
180
|
+
C1_STATUS="$(status_row_json "$C_ROOT" subj-held "2026-07-01T12:10:00Z")"
|
|
181
|
+
C1_ROW_ACTOR="$(printf '%s' "$C1_STATUS" | node -e 'const rows=JSON.parse(require("fs").readFileSync(0,"utf8")); process.stdout.write(rows[0] ? rows[0].actor : "");')"
|
|
182
|
+
C1_ROW_STATUS="$(printf '%s' "$C1_STATUS" | node -e 'const rows=JSON.parse(require("fs").readFileSync(0,"utf8")); process.stdout.write(rows[0] ? rows[0].status : "");')"
|
|
183
|
+
C1_WHOAMI_B="$(flow_agents_node "$WRITER" liveness whoami --actor agent-b --json --artifact-root "$C_ROOT" | node -e 'const o=JSON.parse(require("fs").readFileSync(0,"utf8")); process.stdout.write(o.actor);')"
|
|
184
|
+
C1_WHOAMI_A="$(flow_agents_node "$WRITER" liveness whoami --actor agent-a --json --artifact-root "$C_ROOT" | node -e 'const o=JSON.parse(require("fs").readFileSync(0,"utf8")); process.stdout.write(o.actor);')"
|
|
185
|
+
|
|
186
|
+
if [[ "$C1_ROW_STATUS" == "verified" ]] && [[ "$C1_ROW_ACTOR" == "agent-a" ]] && [[ "$C1_ROW_ACTOR" != "$C1_WHOAMI_B" ]]; then
|
|
187
|
+
_pass "held-exclusion: a verified row for agent-a, read as agent-b's whoami, classifies subj-held as held (row actor != self) (AC1, AC2)"
|
|
188
|
+
else
|
|
189
|
+
_fail "held-exclusion classification failed: row_status=$C1_ROW_STATUS row_actor=$C1_ROW_ACTOR whoami_b=$C1_WHOAMI_B raw=$C1_STATUS"
|
|
190
|
+
fi
|
|
191
|
+
|
|
192
|
+
if [[ "$C1_ROW_STATUS" == "verified" ]] && [[ "$C1_ROW_ACTOR" == "agent-a" ]] && [[ "$C1_ROW_ACTOR" == "$C1_WHOAMI_A" ]]; then
|
|
193
|
+
_pass "own-actor mine: the SAME verified row, read as agent-a's own whoami, classifies subj-held as mine, not held (AC5)"
|
|
194
|
+
else
|
|
195
|
+
_fail "own-actor mine classification failed: row_status=$C1_ROW_STATUS row_actor=$C1_ROW_ACTOR whoami_a=$C1_WHOAMI_A raw=$C1_STATUS"
|
|
196
|
+
fi
|
|
197
|
+
|
|
198
|
+
# C3: Reclaimable — actor agent-c's claim is seeded far enough in the past (beyond ttlSeconds)
|
|
199
|
+
# that `--now` places it past TTL expiry. Assert the row's RAW `status` field reads `stale`
|
|
200
|
+
# (never `label`, which collapses stale to the coarser "free" and would lose the distinction —
|
|
201
|
+
# proving the "consume status, not label" design decision is real and observable).
|
|
202
|
+
flow_agents_node "$WRITER" liveness claim subj-stale --actor agent-c --at "2026-06-25T11:00:00Z" --ttl 1800 --artifact-root "$C_ROOT" >/dev/null 2>"$TMPDIR_EVAL/c-claim-c.err"
|
|
203
|
+
C3_STATUS="$(status_row_json "$C_ROOT" subj-stale "2026-07-01T12:10:00Z")"
|
|
204
|
+
C3_ROW_STATUS="$(printf '%s' "$C3_STATUS" | node -e 'const rows=JSON.parse(require("fs").readFileSync(0,"utf8")); process.stdout.write(rows[0] ? rows[0].status : "");')"
|
|
205
|
+
C3_ROW_LABEL="$(printf '%s' "$C3_STATUS" | node -e 'const rows=JSON.parse(require("fs").readFileSync(0,"utf8")); process.stdout.write(rows[0] ? rows[0].label : "");')"
|
|
206
|
+
if [[ "$C3_ROW_STATUS" == "stale" ]] && [[ "$C3_ROW_LABEL" == "free" ]]; then
|
|
207
|
+
_pass "reclaimable: raw status field reads 'stale' (distinct from the coarser label 'free') — proves 'consume status, not label' is real, not just documented (AC3)"
|
|
208
|
+
else
|
|
209
|
+
_fail "reclaimable classification failed: raw status=$C3_ROW_STATUS label=$C3_ROW_LABEL raw=$C3_STATUS"
|
|
210
|
+
fi
|
|
211
|
+
|
|
212
|
+
# C4: Free — a subject with no liveness events at all returns an empty row set.
|
|
213
|
+
C4_STATUS="$(status_row_json "$C_ROOT" subj-never-claimed "2026-07-01T12:10:00Z")"
|
|
214
|
+
if [[ "$C4_STATUS" == "[]" ]]; then
|
|
215
|
+
_pass "free: a never-claimed subject returns an empty row set for liveness status --json --subject (AC1)"
|
|
216
|
+
else
|
|
217
|
+
_fail "free-case status was not empty: $C4_STATUS"
|
|
218
|
+
fi
|
|
219
|
+
|
|
220
|
+
# C5: Claim-emit-on-selection — subjectId derived via resolve-slug; a "selection" runs
|
|
221
|
+
# `liveness claim <subjectId>` exactly once, appending exactly one new claim event for that
|
|
222
|
+
# (subjectId, actor) pair, and a subsequent status read shows that actor verified/held.
|
|
223
|
+
C5_SUBJECT_ID="$(flow_agents_node "$WRITER" resolve-slug 'owner/repo#77' 2>"$TMPDIR_EVAL/c5-resolve.err")"
|
|
224
|
+
C5_STREAM="$C_ROOT/liveness/events.jsonl"
|
|
225
|
+
C5_LINES_BEFORE=0
|
|
226
|
+
[[ -f "$C5_STREAM" ]] && C5_LINES_BEFORE="$(wc -l <"$C5_STREAM" | tr -d ' ')"
|
|
227
|
+
flow_agents_node "$WRITER" liveness claim "$C5_SUBJECT_ID" --actor agent-selector --artifact-root "$C_ROOT" >/dev/null 2>"$TMPDIR_EVAL/c5-claim.err"
|
|
228
|
+
C5_LINES_AFTER="$(wc -l <"$C5_STREAM" | tr -d ' ')"
|
|
229
|
+
C5_APPENDED=$((C5_LINES_AFTER - C5_LINES_BEFORE))
|
|
230
|
+
C5_STATUS_AFTER="$(status_row_json "$C_ROOT" "$C5_SUBJECT_ID" "2026-07-01T12:10:00Z")"
|
|
231
|
+
C5_ROW_STATUS_AFTER="$(printf '%s' "$C5_STATUS_AFTER" | node -e 'const rows=JSON.parse(require("fs").readFileSync(0,"utf8")); const r=rows.find(x=>x.actor==="agent-selector"); process.stdout.write(r ? r.status : "");')"
|
|
232
|
+
if [[ "$C5_SUBJECT_ID" == "owner-repo-77" ]] && [[ "$C5_APPENDED" -eq 1 ]] && [[ "$C5_ROW_STATUS_AFTER" == "verified" ]]; then
|
|
233
|
+
_pass "claim-emit-on-selection: liveness claim <resolve-slug-derived subjectId> appends exactly one event and shows verified/held for that actor thereafter (AC4)"
|
|
234
|
+
else
|
|
235
|
+
_fail "claim-emit-on-selection failed: subjectId=$C5_SUBJECT_ID appended=$C5_APPENDED row_status_after=$C5_ROW_STATUS_AFTER raw=$C5_STATUS_AFTER"
|
|
236
|
+
fi
|
|
237
|
+
|
|
238
|
+
# C6: Force-write unconstrained at the CLI layer — `liveness claim` on subj-held (already held
|
|
239
|
+
# fresh by agent-a, per C1/C2) succeeds unconditionally for a different actor. Proves --force
|
|
240
|
+
# needs no CLI-side change: the write path has no holder-conflict check to override (Design
|
|
241
|
+
# Decision 4).
|
|
242
|
+
if flow_agents_node "$WRITER" liveness claim subj-held --actor agent-force --artifact-root "$C_ROOT" >"$TMPDIR_EVAL/c6.out" 2>"$TMPDIR_EVAL/c6.err"; then
|
|
243
|
+
C6_STATUS="$(status_row_json "$C_ROOT" subj-held "2026-07-01T12:15:00Z")"
|
|
244
|
+
C6_BOTH_HELD="$(printf '%s' "$C6_STATUS" | node -e '
|
|
245
|
+
const rows = JSON.parse(require("fs").readFileSync(0, "utf8"));
|
|
246
|
+
const a = rows.find((r) => r.actor === "agent-a");
|
|
247
|
+
const f = rows.find((r) => r.actor === "agent-force");
|
|
248
|
+
process.stdout.write(a && f && a.status === "verified" && f.status === "verified" ? "yes" : "no");
|
|
249
|
+
')"
|
|
250
|
+
if [[ "$C6_BOTH_HELD" == "yes" ]]; then
|
|
251
|
+
_pass "force-write unconstrained: liveness claim on an already-held subject succeeds unconditionally for a different actor (no CLI-side holder-conflict check) (Design Decision 4)"
|
|
252
|
+
else
|
|
253
|
+
_fail "force-write left an unexpected status row set: $C6_STATUS"
|
|
254
|
+
fi
|
|
255
|
+
else
|
|
256
|
+
_fail "liveness claim on an already-held subject unexpectedly failed (CLI should have no holder-conflict check): $(cat "$TMPDIR_EVAL/c6.out" "$TMPDIR_EVAL/c6.err")"
|
|
257
|
+
fi
|
|
258
|
+
|
|
259
|
+
# ─── E. F1 (fix-plan iteration 1) — liveness whoami never touches the workflow-sidecar
|
|
260
|
+
# lock, regardless of artifact-root state (mirrors resolve-slug's empty lockRoot) ──────────
|
|
261
|
+
echo "--- E. F1: liveness whoami lock-bypass proof ---"
|
|
262
|
+
|
|
263
|
+
# E1: existing artifact root + FLOW_AGENTS_WORKFLOW_SIDECAR_LOCK_DELAY set -> whoami never
|
|
264
|
+
# creates .workflow-sidecar.lockdir (polled live during the delay window, not just checked
|
|
265
|
+
# post-hoc — a post-hoc-only check could not distinguish "never locked" from "locked, delayed,
|
|
266
|
+
# then cleaned up before we looked") -> exits 0.
|
|
267
|
+
E1_ROOT="$(new_scratch)/.kontourai/flow-agents"
|
|
268
|
+
mkdir -p "$E1_ROOT"
|
|
269
|
+
FLOW_AGENTS_WORKFLOW_SIDECAR_LOCK_DELAY=3 flow_agents_node "$WRITER" liveness whoami --json --artifact-root "$E1_ROOT" >"$TMPDIR_EVAL/e1.out" 2>"$TMPDIR_EVAL/e1.err" &
|
|
270
|
+
E1_PID=$!
|
|
271
|
+
E1_LOCKDIR_SEEN="no"
|
|
272
|
+
E1_POLL_DEADLINE=$(( $(date +%s%N) + 1500000000 ))
|
|
273
|
+
while [[ "$(date +%s%N)" -lt "$E1_POLL_DEADLINE" ]]; do
|
|
274
|
+
if [[ -e "$E1_ROOT/.workflow-sidecar.lockdir" ]]; then E1_LOCKDIR_SEEN="yes"; break; fi
|
|
275
|
+
if ! kill -0 "$E1_PID" 2>/dev/null; then break; fi
|
|
276
|
+
sleep 0.05
|
|
277
|
+
done
|
|
278
|
+
wait "$E1_PID"
|
|
279
|
+
E1_STATUS=$?
|
|
280
|
+
if [[ "$E1_STATUS" -eq 0 ]] && [[ "$E1_LOCKDIR_SEEN" == "no" ]]; then
|
|
281
|
+
_pass "liveness whoami against an EXISTING artifact root with FLOW_AGENTS_WORKFLOW_SIDECAR_LOCK_DELAY set never creates .workflow-sidecar.lockdir (polled live) and exits 0 (F1)"
|
|
282
|
+
else
|
|
283
|
+
_fail "liveness whoami lock-bypass failed: status=$E1_STATUS lockdir_seen=$E1_LOCKDIR_SEEN out=$(cat "$TMPDIR_EVAL/e1.out") err=$(cat "$TMPDIR_EVAL/e1.err")"
|
|
284
|
+
fi
|
|
285
|
+
|
|
286
|
+
# E2: existing but non-writable (555) artifact root -> whoami still exits 0 (no lock attempt,
|
|
287
|
+
# so no mkdir-under-a-read-only-dir failure is even possible).
|
|
288
|
+
E2_ROOT="$(new_scratch)/.kontourai/flow-agents"
|
|
289
|
+
mkdir -p "$E2_ROOT"
|
|
290
|
+
chmod 555 "$E2_ROOT"
|
|
291
|
+
E2_STATUS=0
|
|
292
|
+
flow_agents_node "$WRITER" liveness whoami --json --artifact-root "$E2_ROOT" >"$TMPDIR_EVAL/e2.out" 2>"$TMPDIR_EVAL/e2.err" || E2_STATUS=$?
|
|
293
|
+
chmod 755 "$E2_ROOT"
|
|
294
|
+
if [[ "$E2_STATUS" -eq 0 ]]; then
|
|
295
|
+
_pass "liveness whoami against a non-writable (555) EXISTING artifact root still exits 0 (F1)"
|
|
296
|
+
else
|
|
297
|
+
_fail "liveness whoami under a 555 artifact root unexpectedly failed: status=$E2_STATUS out=$(cat "$TMPDIR_EVAL/e2.out") err=$(cat "$TMPDIR_EVAL/e2.err")"
|
|
298
|
+
fi
|
|
299
|
+
|
|
300
|
+
# E3: contrast — `liveness status` (out of scope for F1, pre-existing lock behavior preserved)
|
|
301
|
+
# still acquires the lock against an existing root with the delay set. Proves F1's bypass is
|
|
302
|
+
# scoped to the `whoami` action only, not a blanket change to `liveness` command lock routing.
|
|
303
|
+
E3_ROOT="$(new_scratch)/.kontourai/flow-agents"
|
|
304
|
+
mkdir -p "$E3_ROOT"
|
|
305
|
+
FLOW_AGENTS_WORKFLOW_SIDECAR_LOCK_DELAY=1.5 flow_agents_node "$WRITER" liveness status --json --artifact-root "$E3_ROOT" >"$TMPDIR_EVAL/e3.out" 2>"$TMPDIR_EVAL/e3.err" &
|
|
306
|
+
E3_PID=$!
|
|
307
|
+
E3_LOCKDIR_SEEN="no"
|
|
308
|
+
E3_POLL_DEADLINE=$(( $(date +%s%N) + 1200000000 ))
|
|
309
|
+
while [[ "$(date +%s%N)" -lt "$E3_POLL_DEADLINE" ]]; do
|
|
310
|
+
if [[ -e "$E3_ROOT/.workflow-sidecar.lockdir" ]]; then E3_LOCKDIR_SEEN="yes"; break; fi
|
|
311
|
+
if ! kill -0 "$E3_PID" 2>/dev/null; then break; fi
|
|
312
|
+
sleep 0.05
|
|
313
|
+
done
|
|
314
|
+
wait "$E3_PID"
|
|
315
|
+
if [[ "$E3_LOCKDIR_SEEN" == "yes" ]]; then
|
|
316
|
+
_pass "liveness status (out of scope for F1) still acquires .workflow-sidecar.lockdir against an existing root — proves F1's bypass is action-scoped to whoami, not a blanket liveness-command change"
|
|
317
|
+
else
|
|
318
|
+
_fail "liveness status unexpectedly did not acquire the lock — F1's bypass may have leaked beyond the whoami action: out=$(cat "$TMPDIR_EVAL/e3.out") err=$(cat "$TMPDIR_EVAL/e3.err")"
|
|
319
|
+
fi
|
|
320
|
+
|
|
321
|
+
# ─── D. Static skill-text assertions (AC6, AC8) ─────────────────────────────
|
|
322
|
+
echo "--- D. Static skill-text assertions ---"
|
|
323
|
+
|
|
324
|
+
require_text "$PULL" '### 1a\. Liveness Selection Preflight' "pull-work documents the Liveness Selection Preflight subsection (AC1, AC8)"
|
|
325
|
+
require_text "$PULL" 'liveness whoami --json' "pull-work references liveness whoami --json by exact command name (AC1, AC8)"
|
|
326
|
+
require_text "$PULL" 'liveness status --json' "pull-work references liveness status --json by exact command name (AC1, AC8)"
|
|
327
|
+
require_text "$PULL" 'resolve-slug <owner>/<repo>#<issue-number>' "pull-work references resolve-slug by exact command name (AC1, AC4, AC8)"
|
|
328
|
+
require_text "$PULL" 'liveness claim <subjectId>' "pull-work references liveness claim <subjectId> by exact command name (AC4, AC8)"
|
|
329
|
+
require_text "$PULL" 'reading each row.s raw .status. field \(never .label.\)' "pull-work instructs consuming raw status, never label (AC1, AC8)"
|
|
330
|
+
require_text "$PULL" 'a .verified. row for an actor other than self.*.held.' "pull-work documents held classification (AC1, AC2, AC8)"
|
|
331
|
+
require_text "$PULL" 'excluded from the ready set by default' "pull-work excludes held candidates by default (AC2, AC8)"
|
|
332
|
+
require_text "$PULL" 'a .stale. row \(any actor\).*.reclaimable.' "pull-work documents reclaimable classification (AC1, AC3, AC8)"
|
|
333
|
+
require_text "$PULL" 'requires an explicit recorded opt-in' "pull-work requires an explicit recorded opt-in for reclaimable selection (AC3, AC8)"
|
|
334
|
+
require_text "$PULL" 'never a silent normal pick' "pull-work states reclaimable is never a silent normal pick (AC3, AC8)"
|
|
335
|
+
require_text "$PULL" 'a .verified. row for the resolved self actor.*.mine.' "pull-work documents mine classification (AC1, AC5, AC8)"
|
|
336
|
+
require_text "$PULL" 'do not re-offer as new, do not exclude as held-by-other' "pull-work folds own-actor mine into WIP logic, not re-offered or re-excluded (AC5, AC8)"
|
|
337
|
+
require_text "$PULL" 'An explicit user instruction to proceed despite a .held. or .reclaimable. classification' "pull-work documents the --force/explicit-instruction override (AC2, AC3, AC8)"
|
|
338
|
+
require_text "$PULL" '.--force.' "pull-work names --force as the override mechanism (AC2, AC8)"
|
|
339
|
+
require_text "$PULL" 'override and its stated reason must be recorded in the artifact' "pull-work requires the override + reason to be recorded (AC2, AC8)"
|
|
340
|
+
require_text "$PULL" 'do not mutate GitHub Projects, provider fields, or cross-repo provider state from this skill' "pull-work's existing no-provider-mutation sentence is present verbatim and unchanged (AC6, AC8)"
|
|
341
|
+
require_text "$PULL" 'read/write the local runtime liveness stream, never GitHub issue/label/assignee state' "pull-work states the liveness claim is local runtime state, not a provider mutation (AC6, AC8)"
|
|
342
|
+
require_text "$PULL" 'this is not\s*$|this is not a provider mutation' "pull-work explicitly states the liveness claim is not a provider mutation (AC6, AC8)"
|
|
343
|
+
require_text "$PULL" '#290' "pull-work names #290 as the deferred assignment-provider seam (AC6, AC8)"
|
|
344
|
+
require_text "$PULL" 'liveness_preflight' "pull-work artifact contract records liveness_preflight (AC1, AC8)"
|
|
345
|
+
require_text "$PULL" 'liveness_claim' "pull-work artifact contract records liveness_claim (AC4, AC8)"
|
|
346
|
+
require_text "$PICKUP_PROBE" 'flags the selected item .reclaimable., treat the recorded opt-in as a decision to re-confirm' "pickup-probe re-confirms (not silently accepts) a reclaimable opt-in on drift (AC3, AC8)"
|
|
347
|
+
|
|
348
|
+
# ─── F2-F6 (fix-plan iteration 1) static assertions ─────────────────────────
|
|
349
|
+
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
|
+
require_text "$PULL" 'classify in this precedence order' "pull-work states the classification is an ordered precedence rule (F2, AC8)"
|
|
351
|
+
require_text "$PULL" '^1\. a .verified. row for an actor other than self' "pull-work's precedence rule step 1 (held) is numbered verbatim (F2, AC8)"
|
|
352
|
+
require_text "$PULL" '^2\. else, a .verified. row for the resolved self actor' "pull-work's precedence rule step 2 (mine) is numbered verbatim (F2, AC8)"
|
|
353
|
+
require_text "$PULL" '^3\. else, a .stale. row' "pull-work's precedence rule step 3 (reclaimable) is numbered verbatim (F2, AC8)"
|
|
354
|
+
require_text "$PULL" '^4\. else \(a .revoked.-only row' "pull-work's precedence rule step 4 (free) is numbered verbatim (F2, AC8)"
|
|
355
|
+
require_text "$PULL" 'double-hold' "pull-work names the own-actor + other-actor co-existing row combination a double-hold (F2, AC8)"
|
|
356
|
+
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
|
+
require_text "$PULL" 'never silently resolve it to .mine.' "pull-work states a double-hold is never silently resolved to mine (F2, AC8)"
|
|
358
|
+
|
|
359
|
+
require_text "$PULL" 'pin it for reuse' "pull-work pins self_actor for reuse across the pass (F3, AC8)"
|
|
360
|
+
require_text "$PULL" 'Capture the returned .actor. value as .self_actor.' "pull-work captures self_actor exactly once per pass from whoami (F3, AC8)"
|
|
361
|
+
require_text "$PULL" 'liveness claim <subjectId> --actor <self_actor>' "pull-work's claim-on-selection command passes --actor <self_actor> explicitly (F3, AC4, AC8)"
|
|
362
|
+
require_text "$PULL" 'always pass .--actor <self_actor>. explicitly on every claim in this pass' "pull-work requires --actor <self_actor> on every claim in the pass, not just the first (F3, AC8)"
|
|
363
|
+
require_text "$PULL" 'never a fresh derivation per claim call' "pull-work states the actor is never re-derived per claim call (F3, AC8)"
|
|
364
|
+
|
|
365
|
+
require_text "$PULL" 'On any claim-emit failure \(non-zero exit, unresolved actor, or liveness disabled/unavailable\)' "pull-work names the precise claim-emit failure modes covered by fail-open (F4, AC8)"
|
|
366
|
+
require_text "$PULL" '\{skipped: <stderr reason>\}' "pull-work's fail-open shape records {skipped: <stderr reason>} (F4, AC8)"
|
|
367
|
+
require_text "$PULL" 'also surface that skip reason in pull-work.s user-facing output' "pull-work requires the skip reason to be surfaced in user-facing output, never silent (F4, AC8)"
|
|
368
|
+
require_text "$PULL" 'unresolved-actor failure.? additionally names? the remediation' "pull-work requires unresolved-actor failures to additionally name the remediation (F4, AC8)"
|
|
369
|
+
|
|
370
|
+
require_text "$PULL" '.reclaimable_override.: recorded only when a .reclaimable.' "pull-work's Artifact Contract declares the reclaimable_override field (F5, AC8)"
|
|
371
|
+
|
|
372
|
+
require_text "$PULL" '### Post-Claim Conflict Re-check' "pull-work documents the Post-Claim Conflict Re-check subsection (F6, AC8)"
|
|
373
|
+
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
|
+
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
|
+
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" 'True mutual exclusion arrives with the provider assignment lease \(#290\) and the .verify-hold. publish gate \(#293\)' "pull-work names #290/#293 as where true mutual exclusion actually arrives (F6, AC8)"
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
echo ""
|
|
380
|
+
if [[ "$errors" -eq 0 ]]; then
|
|
381
|
+
echo "Pull work liveness preflight integration passed."
|
|
382
|
+
exit 0
|
|
383
|
+
fi
|
|
384
|
+
|
|
385
|
+
echo "Pull work liveness preflight integration failed: $errors issue(s)."
|
|
386
|
+
exit 1
|
|
@@ -35,7 +35,7 @@ status=$?
|
|
|
35
35
|
[[ "$(json_query "$TMPDIR_EVAL/normalized.json" "items.0.id")" == "github:kontourai/flow-agents#22" ]] && pass "normalizes provider-qualified id" || fail "normalizes provider-qualified id"
|
|
36
36
|
[[ "$(json_query "$TMPDIR_EVAL/normalized.json" "items.0.source_provider.role")" == "WorkItemProvider" ]] && pass "preserves work item provider ref" || fail "preserves work item provider ref"
|
|
37
37
|
[[ "$(json_query "$TMPDIR_EVAL/normalized.json" "items.0.board_membership.role")" == "BoardProvider" ]] && pass "preserves board provider ref" || fail "preserves board provider ref"
|
|
38
|
-
[[ "$(json_query "$TMPDIR_EVAL/normalized.json" "items.0.artifact_refs.0")" == ".flow-agents/flow-agents-kit-platform-backlog/flow-agents-kit-platform-backlog--idea-to-backlog.md" ]] && pass "preserves source artifact ref" || fail "preserves source artifact ref"
|
|
38
|
+
[[ "$(json_query "$TMPDIR_EVAL/normalized.json" "items.0.artifact_refs.0")" == ".kontourai/flow-agents/flow-agents-kit-platform-backlog/flow-agents-kit-platform-backlog--idea-to-backlog.md" ]] && pass "preserves source artifact ref" || fail "preserves source artifact ref"
|
|
39
39
|
[[ "$(json_query "$TMPDIR_EVAL/normalized.json" "items.0.blockers.0.ref.owner")" == "kontourai" ]] && pass "preserves blocker owner" || fail "preserves blocker owner"
|
|
40
40
|
[[ "$(json_query "$TMPDIR_EVAL/normalized.json" "items.0.blockers.0.ref.repo")" == "flow" ]] && pass "preserves cross-repo blocker repo" || fail "preserves cross-repo blocker repo"
|
|
41
41
|
[[ "$(json_query "$TMPDIR_EVAL/normalized.json" "items.0.dependency_impacts.0.ref.owner")" == "kontourai" ]] && pass "projects prose blocker impact owner" || fail "projects prose blocker impact owner"
|
|
@@ -51,7 +51,7 @@ status=$?
|
|
|
51
51
|
[[ "$(json_query "$TMPDIR_EVAL/normalized.json" "items.5.planned_base_ref")" == "main" ]] && pass "marker normalizes planned_base_ref" || fail "marker normalizes planned_base_ref"
|
|
52
52
|
[[ "$(json_query "$TMPDIR_EVAL/normalized.json" "items.5.planned_base_sha")" == "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ]] && pass "marker normalizes planned_base_sha" || fail "marker normalizes planned_base_sha"
|
|
53
53
|
[[ "$(json_query "$TMPDIR_EVAL/normalized.json" "items.5.planned_at")" == "2026-06-03T03:23:14Z" ]] && pass "marker normalizes planned_at" || fail "marker normalizes planned_at"
|
|
54
|
-
[[ "$(json_query "$TMPDIR_EVAL/normalized.json" "items.5.planning_artifact_ref")" == ".flow-agents/idea-to-backlog-source-revision-structured-blockers/idea-to-backlog-source-revision-structured-blockers--plan.md" ]] && pass "marker normalizes planning_artifact_ref" || fail "marker normalizes planning_artifact_ref"
|
|
54
|
+
[[ "$(json_query "$TMPDIR_EVAL/normalized.json" "items.5.planning_artifact_ref")" == ".kontourai/flow-agents/idea-to-backlog-source-revision-structured-blockers/idea-to-backlog-source-revision-structured-blockers--plan.md" ]] && pass "marker normalizes planning_artifact_ref" || fail "marker normalizes planning_artifact_ref"
|
|
55
55
|
[[ "$(json_query "$TMPDIR_EVAL/normalized.json" "items.5.planning_scope_refs.0")" == "kits/builder/skills/idea-to-backlog/SKILL.md" ]] && pass "marker normalizes planning_scope_refs" || fail "marker normalizes planning_scope_refs"
|
|
56
56
|
[[ "$(json_query "$TMPDIR_EVAL/normalized.json" "items.5.source_revisions.1.repo")" == "kontourai/flow" ]] && pass "marker preserves repo-scoped source_revisions" || fail "marker preserves repo-scoped source_revisions"
|
|
57
57
|
[[ "$(json_query "$TMPDIR_EVAL/normalized.json" "items.5.blockers.0.ref.owner")" == "kontourai" ]] && pass "structured blocker preserves provider owner" || fail "structured blocker preserves provider owner"
|