@kontourai/flow-agents 2.4.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/CODEOWNERS +8 -0
- package/.github/workflows/ci.yml +20 -0
- package/.github/workflows/trust-reconcile.yml +62 -4
- package/CHANGELOG.md +38 -0
- package/CONTEXT.md +88 -1
- package/README.md +4 -0
- package/build/src/cli/assignment-provider.d.ts +144 -0
- package/build/src/cli/assignment-provider.js +805 -0
- package/build/src/cli/effective-assignment-provider-settings.d.ts +1 -0
- package/build/src/cli/effective-assignment-provider-settings.js +125 -0
- package/build/src/cli/validate-workflow-artifacts.js +5 -1
- package/build/src/cli/workflow-sidecar.d.ts +2 -2
- package/build/src/cli/workflow-sidecar.js +549 -145
- package/build/src/cli.js +6 -0
- package/build/src/lib/flow-resolver.d.ts +12 -6
- package/build/src/lib/flow-resolver.js +30 -14
- package/build/src/tools/validate-source-tree.js +2 -0
- package/context/contracts/artifact-contract.md +2 -0
- package/context/contracts/assignment-provider-contract.md +248 -0
- package/context/contracts/builder-kit-workflow-state-contract.md +2 -0
- package/context/contracts/decision-registry-contract.md +2 -0
- package/context/contracts/delivery-contract.md +2 -0
- package/context/contracts/execution-contract.md +25 -0
- package/context/contracts/governance-adapter-contract.md +2 -0
- package/context/contracts/knowledge-store-contract.md +197 -0
- package/context/contracts/planning-contract.md +2 -0
- package/context/contracts/probe-docs-write-contract.md +187 -0
- package/context/contracts/review-contract.md +2 -0
- package/context/contracts/sandbox-policy.md +2 -0
- package/context/contracts/standing-directives.md +13 -0
- package/context/contracts/verification-contract.md +2 -0
- package/context/contracts/work-item-contract.md +2 -0
- package/context/scripts/hooks/config-protection.js +14 -1
- package/context/scripts/hooks/lib/config-protection-remedies.js +9 -0
- package/context/scripts/hooks/stop-goal-fit.js +4 -2
- package/context/scripts/hooks/workflow-steering.js +42 -0
- package/context/settings/assignment-provider-settings.json +33 -0
- package/docs/adr/0001-flow-agents-consumes-flow.md +2 -0
- package/docs/adr/0002-flow-kits-as-extension-unit.md +2 -0
- package/docs/adr/0003-flow-agents-coordinates-kits-and-adapters.md +2 -0
- package/docs/adr/0004-gates-expect-surface-claims.md +2 -0
- package/docs/adr/0005-kubernetes-inspired-resource-contracts.md +2 -0
- package/docs/adr/0006-typescript-first-source-policy.md +2 -0
- package/docs/adr/0007-flow-skill-kit-tool-boundary.md +2 -0
- package/docs/adr/0007-skill-audit.md +2 -0
- package/docs/adr/0008-kit-operation-boundary.md +2 -0
- package/docs/adr/0009-canonical-hook-core-kit-boundary.md +2 -0
- package/docs/adr/0010-workflow-trust-state-as-hachure-bundle.md +2 -0
- package/docs/adr/0011-mcp-posture.md +2 -0
- package/docs/adr/0012-agent-coordination-as-liveness-claims.md +2 -0
- package/docs/adr/0013-context-lifecycle.md +2 -0
- package/docs/adr/0014-core-vs-domain-kit-boundary.md +2 -0
- package/docs/adr/0015-flow-flow-agents-boundary-reconciliation.md +2 -0
- package/docs/adr/0016-three-hard-boundary-model.md +2 -0
- package/docs/adr/0017-anti-gaming-trust-security-model.md +2 -0
- package/docs/adr/0018-freeze-local-shell-heuristics.md +2 -0
- package/docs/adr/0019-kit-dependency-ownership.md +2 -0
- package/docs/adr/0020-trust-reconcile-manifest-and-claim-classification.md +2 -0
- package/docs/adr/0021-assignment-leases-and-stale-claim-takeover.md +3 -1
- package/docs/adr/0022-fail-closed-delivery-reconciliation-with-governed-exemptions.md +182 -0
- package/docs/adr/README.md +49 -0
- package/docs/adr/index.md +34 -0
- package/docs/context-map.md +1 -0
- package/docs/decisions/agent-coordination.md +20 -0
- package/docs/decisions/anti-gaming-trust-security.md +20 -0
- package/docs/decisions/context-lifecycle.md +18 -0
- package/docs/decisions/core-domain-kit-boundary.md +18 -0
- package/docs/decisions/flow-flow-agents-boundary.md +18 -0
- package/docs/decisions/flow-kit.md +20 -0
- package/docs/decisions/flow-skill-kit-tool-boundary.md +20 -0
- package/docs/decisions/graph-knowledge-provider.md +63 -0
- package/docs/decisions/hook-core-kit-boundary.md +18 -0
- package/docs/decisions/index.md +22 -0
- package/docs/decisions/kit-dependency-ownership.md +18 -0
- package/docs/decisions/kit-operation-boundary.md +18 -0
- package/docs/decisions/knowledge-promote-sub-flow.md +59 -0
- package/docs/decisions/knowledge-store-provider.md +51 -0
- package/docs/decisions/kontour-resource-contract.md +18 -0
- package/docs/decisions/mcp-posture.md +18 -0
- package/docs/decisions/model-routing.md +63 -0
- package/docs/decisions/standing-directives.md +66 -0
- package/docs/decisions/three-hard-boundary-model.md +18 -0
- package/docs/decisions/trust-reconcile.md +20 -0
- package/docs/decisions/typescript-source-policy.md +48 -0
- package/docs/decisions/workflow-enforcement.md +18 -0
- package/docs/decisions/workflow-trust-state.md +20 -0
- package/docs/fixture-ownership.md +1 -0
- package/docs/workflow-shared-contracts.md +2 -1
- package/docs/workflow-usage-guide.md +7 -1
- package/evals/ci/run-baseline.sh +10 -0
- package/evals/fixtures/assignment-provider/actor-a.json +6 -0
- package/evals/fixtures/assignment-provider/actor-b.json +6 -0
- package/evals/fixtures/assignment-provider/github-issue-claimed.json +27 -0
- package/evals/fixtures/assignment-provider/github-issue-unassigned.json +7 -0
- package/evals/fixtures/assignment-provider/hostile-effective-state.json +11 -0
- package/evals/fixtures/assignment-provider/liveness-fresh.json +9 -0
- package/evals/fixtures/assignment-provider/liveness-stale.json +9 -0
- package/evals/integration/test_assignment_provider_github.sh +318 -0
- package/evals/integration/test_assignment_provider_local_file.sh +222 -0
- package/evals/integration/test_critique_supersession_roundtrip.sh +182 -0
- package/evals/integration/test_current_json_per_actor.sh +516 -0
- package/evals/integration/test_ensure_session_ownership_guard.sh +435 -0
- package/evals/integration/test_fixture_retirement_audit.sh +2 -2
- package/evals/integration/test_gate_lockdown.sh +10 -0
- package/evals/integration/test_publish_delivery.sh +21 -4
- package/evals/integration/test_pull_work_assignment_join.sh +132 -0
- package/evals/integration/test_pull_work_liveness_preflight.sh +20 -7
- package/evals/integration/test_reconcile_soundness.sh +33 -9
- package/evals/integration/test_trust_reconcile.sh +9 -8
- package/evals/integration/test_trust_reconcile_negatives.sh +608 -0
- package/evals/integration/test_workflow_sidecar_writer.sh +91 -3
- package/evals/run.sh +14 -0
- package/evals/static/test_flowdef_codeowners_coverage.sh +6 -0
- package/evals/static/test_knowledge_providers.sh +32 -0
- package/evals/static/test_workflow_skills.sh +15 -2
- package/kits/builder/skills/deliver/SKILL.md +36 -0
- package/kits/builder/skills/design-probe/SKILL.md +37 -0
- package/kits/builder/skills/pickup-probe/SKILL.md +4 -4
- package/kits/builder/skills/pull-work/SKILL.md +78 -10
- package/kits/knowledge/adapters/default-store/index.js +92 -4
- package/kits/knowledge/adapters/obsidian-store/index.js +69 -4
- package/kits/knowledge/adapters/shared/codec.js +141 -0
- package/kits/knowledge/docs/README.md +121 -2
- package/kits/knowledge/docs/store-contract.md +112 -4
- package/kits/knowledge/evals/contract-suite/suite.test.js +176 -0
- package/kits/knowledge/flows/promote.flow.json +84 -0
- package/kits/knowledge/kit.json +50 -0
- package/kits/knowledge/promote/distill.js +96 -0
- package/kits/knowledge/promote/fixtures/contradiction-registry/CONTEXT.md +7 -0
- package/kits/knowledge/promote/fixtures/contradiction-registry/docs/decisions/cache-eviction-policy.md +12 -0
- package/kits/knowledge/promote/fixtures/contradiction-registry/docs/decisions/cache-eviction.md +12 -0
- package/kits/knowledge/promote/fixtures/repo/CONTEXT.md +11 -0
- package/kits/knowledge/promote/fixtures/repo/docs/decisions/index.md +9 -0
- package/kits/knowledge/promote/fixtures/repo/docs/decisions/liveness-claim.md +12 -0
- package/kits/knowledge/promote/fixtures/session/acceptance.json +9 -0
- package/kits/knowledge/promote/fixtures/session/agents/tool-planner/events.jsonl +1 -0
- package/kits/knowledge/promote/fixtures/session/agents/tool-verifier/events.jsonl +1 -0
- package/kits/knowledge/promote/fixtures/session/agents/tool-worker-1-1/events.jsonl +1 -0
- package/kits/knowledge/promote/fixtures/session/kontourai-flow-agents-287--deliver.md +47 -0
- package/kits/knowledge/promote/fixtures/session/learning.json +35 -0
- package/kits/knowledge/promote/fixtures/session/state.json +19 -0
- package/kits/knowledge/promote/health.js +137 -0
- package/kits/knowledge/promote/index.js +176 -0
- package/kits/knowledge/promote/ingest.js +103 -0
- package/kits/knowledge/promote/lib.js +132 -0
- package/kits/knowledge/promote/link.js +84 -0
- package/kits/knowledge/promote/promote.test.js +174 -0
- package/kits/knowledge/providers/conformance/fixtures/git-repo/CONTEXT.md +12 -0
- package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/old-sprocket-shape.md +13 -0
- package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/sprocket-shape.md +14 -0
- package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/widget-format.md +14 -0
- package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/learnings/fixture-learning.md +7 -0
- package/kits/knowledge/providers/conformance/fixtures/work-item/issues.json +30 -0
- package/kits/knowledge/providers/conformance/suite.test.js +143 -0
- package/kits/knowledge/providers/git-repo/index.js +236 -0
- package/kits/knowledge/providers/health/health-pass.test.js +99 -0
- package/kits/knowledge/providers/health/index.js +153 -0
- package/kits/knowledge/providers/index.js +25 -0
- package/kits/knowledge/providers/lib/model.js +91 -0
- package/kits/knowledge/providers/lib/schema-validate.js +119 -0
- package/kits/knowledge/providers/markdown-vault/index.js +169 -0
- package/kits/knowledge/providers/neo4j/connection.js +121 -0
- package/kits/knowledge/providers/neo4j/cypher.js +190 -0
- package/kits/knowledge/providers/neo4j/fake-driver.js +123 -0
- package/kits/knowledge/providers/neo4j/fixtures/spike-ground-truth.json +29 -0
- package/kits/knowledge/providers/neo4j/graph-queries.js +210 -0
- package/kits/knowledge/providers/neo4j/index.js +280 -0
- package/kits/knowledge/providers/neo4j/integration.test.js +122 -0
- package/kits/knowledge/providers/neo4j/neo4j.test.js +219 -0
- package/kits/knowledge/providers/neo4j/sync.js +235 -0
- package/kits/knowledge/providers/work-item/index.js +204 -0
- package/package.json +7 -3
- package/schemas/assignment-provider-settings.schema.json +125 -0
- package/schemas/knowledge/edge.schema.json +54 -0
- package/schemas/knowledge/health-report.schema.json +45 -0
- package/schemas/knowledge/node.schema.json +49 -0
- package/schemas/knowledge/proposal.schema.json +53 -0
- package/scripts/README.md +1 -0
- package/scripts/ci/trust-reconcile.js +521 -24
- package/scripts/freeze-adrs.mjs +364 -0
- package/scripts/hooks/config-protection.js +14 -1
- package/scripts/hooks/evidence-capture.js +4 -1
- package/scripts/hooks/lib/config-protection-remedies.js +9 -0
- package/scripts/hooks/lib/current-pointer.js +123 -0
- package/scripts/hooks/lib/liveness-heartbeat.js +75 -35
- package/scripts/hooks/stop-goal-fit.js +4 -2
- package/scripts/hooks/workflow-steering.js +42 -0
- package/scripts/statusline/flow-agents-statusline.js +3 -1
- package/src/cli/assignment-provider.ts +927 -0
- package/src/cli/effective-assignment-provider-settings.ts +112 -0
- package/src/cli/validate-workflow-artifacts.ts +5 -1
- package/src/cli/workflow-sidecar.ts +577 -139
- package/src/cli.ts +6 -0
- package/src/lib/flow-resolver.ts +35 -14
- package/src/tools/validate-source-tree.ts +2 -0
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test_current_json_per_actor.sh - per-actor current.json projection + legacy compat shim (#291).
|
|
3
|
+
#
|
|
4
|
+
# Exercises scripts/hooks/lib/current-pointer.js (the shared read/write choke point for the
|
|
5
|
+
# per-actor-first/legacy-fallback rule) and every consumer Wave 2 updated to go through it.
|
|
6
|
+
# Fully deterministic, no network, no model spend. Follows
|
|
7
|
+
# test_pull_work_liveness_preflight.sh's multi-actor fixture setup and
|
|
8
|
+
# test_workflow_sidecar_writer.sh's ensure-session/current command assertions.
|
|
9
|
+
#
|
|
10
|
+
# Plan sections (see .kontourai/flow-agents/kontourai-flow-agents-291/
|
|
11
|
+
# kontourai-flow-agents-291--plan-work.md, Wave 3 "Per-actor current.json + compat-shim eval"):
|
|
12
|
+
# 1. current-pointer.js unit-level smoke: none -> legacy -> per-actor precedence.
|
|
13
|
+
# 2. Actor A and actor B ensure-session on DIFFERENT subjects, same artifact root -> actor A's
|
|
14
|
+
# own `current --actor` resolution is unaffected by B's later call (AC7 -- the issue's
|
|
15
|
+
# exact problem statement).
|
|
16
|
+
# 3. Legacy-only fixture (no per-actor file at all) -> stop-goal-fit.js, evidence-capture.js,
|
|
17
|
+
# flow-agents-statusline.js, and workflow-steering.js all resolve identically to their
|
|
18
|
+
# existing (pre-#291) golden output, even under a different ambient actor (AC8).
|
|
19
|
+
# 4. config-protection.js blocks writes/redirects to current/<actor>.json exactly like the
|
|
20
|
+
# legacy current.json (AC10).
|
|
21
|
+
# 5. record-gate-claim/writeTrustBundle scoping: actor A's own active_flow_id/active_step_id
|
|
22
|
+
# resolves from A's per-actor current file, not a DIFFERENT actor's more-recently-written
|
|
23
|
+
# legacy global current.json (AC11).
|
|
24
|
+
set -uo pipefail
|
|
25
|
+
|
|
26
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
27
|
+
source "$ROOT/evals/lib/node.sh"
|
|
28
|
+
|
|
29
|
+
CURRENT_POINTER_HELPER="$ROOT/scripts/hooks/lib/current-pointer.js"
|
|
30
|
+
GOAL_FIT_GATE="$ROOT/scripts/hooks/stop-goal-fit.js"
|
|
31
|
+
EVIDENCE_CAPTURE="$ROOT/scripts/hooks/evidence-capture.js"
|
|
32
|
+
STATUSLINE="$ROOT/scripts/statusline/flow-agents-statusline.js"
|
|
33
|
+
STEERING="$ROOT/scripts/hooks/workflow-steering.js"
|
|
34
|
+
CONFIG_PROTECTION="$ROOT/scripts/hooks/config-protection.js"
|
|
35
|
+
|
|
36
|
+
TMPDIR_EVAL="$(mktemp -d)"
|
|
37
|
+
trap 'rm -rf "$TMPDIR_EVAL"' EXIT
|
|
38
|
+
|
|
39
|
+
errors=0
|
|
40
|
+
pass() { echo " [pass] $1"; }
|
|
41
|
+
fail() { echo " [FAIL] $1"; errors=$((errors + 1)); }
|
|
42
|
+
|
|
43
|
+
# safe_write <target-file> -- reads content from stdin and writes it to <target-file> via a
|
|
44
|
+
# temp-file-then-rename, so this eval script's own source text never contains a literal shell
|
|
45
|
+
# redirect (>/>>/tee) immediately adjacent to a current.json/state.json/trust.bundle-shaped
|
|
46
|
+
# path (several fixtures below legitimately need to seed those exact filenames).
|
|
47
|
+
safe_write() {
|
|
48
|
+
local target="$1"
|
|
49
|
+
local tmp="${target}.write-tmp"
|
|
50
|
+
cat > "$tmp"
|
|
51
|
+
mv "$tmp" "$target"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
flow_agents_build_ts || { echo "build failed" >&2; exit 1; }
|
|
55
|
+
|
|
56
|
+
echo "=== Per-actor current.json + legacy compat shim (#291) ==="
|
|
57
|
+
|
|
58
|
+
# --- 1. current-pointer.js unit smoke: none -> legacy -> per-actor precedence -----------------
|
|
59
|
+
echo "--- 1. current-pointer.js: none / legacy / per-actor precedence ---"
|
|
60
|
+
|
|
61
|
+
CP_SMOKE_DIR="$TMPDIR_EVAL/current-pointer-smoke"
|
|
62
|
+
mkdir -p "$CP_SMOKE_DIR"
|
|
63
|
+
|
|
64
|
+
if node - "$CURRENT_POINTER_HELPER" "$CP_SMOKE_DIR" <<'NODEEOF' 2>"$TMPDIR_EVAL/cp-none.err"
|
|
65
|
+
const { readCurrentPointer } = require(process.argv[2]);
|
|
66
|
+
const r = readCurrentPointer(process.argv[3], "some-actor");
|
|
67
|
+
if (r.source !== "none" || r.payload !== null) { console.error(JSON.stringify(r)); process.exit(1); }
|
|
68
|
+
NODEEOF
|
|
69
|
+
then
|
|
70
|
+
pass "readCurrentPointer returns source:none on an empty dir"
|
|
71
|
+
else
|
|
72
|
+
fail "readCurrentPointer did not return source:none on an empty dir: $(cat "$TMPDIR_EVAL/cp-none.err")"
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
LEGACY_JSON_LINE='{"marker":"legacy"}'
|
|
76
|
+
printf '%s' "$LEGACY_JSON_LINE" | safe_write "$CP_SMOKE_DIR/current.json"
|
|
77
|
+
|
|
78
|
+
if node - "$CURRENT_POINTER_HELPER" "$CP_SMOKE_DIR" <<'NODEEOF' 2>"$TMPDIR_EVAL/cp-legacy.err"
|
|
79
|
+
const { readCurrentPointer } = require(process.argv[2]);
|
|
80
|
+
const r = readCurrentPointer(process.argv[3], "some-actor");
|
|
81
|
+
if (r.source !== "legacy" || !r.payload || r.payload.marker !== "legacy") { console.error(JSON.stringify(r)); process.exit(1); }
|
|
82
|
+
NODEEOF
|
|
83
|
+
then
|
|
84
|
+
pass "readCurrentPointer prefers legacy current.json when no per-actor file exists"
|
|
85
|
+
else
|
|
86
|
+
fail "readCurrentPointer did not fall back to legacy current.json: $(cat "$TMPDIR_EVAL/cp-legacy.err")"
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
mkdir -p "$CP_SMOKE_DIR/current"
|
|
90
|
+
PER_ACTOR_JSON_LINE='{"marker":"per-actor"}'
|
|
91
|
+
printf '%s' "$PER_ACTOR_JSON_LINE" | safe_write "$CP_SMOKE_DIR/current/some-actor.json"
|
|
92
|
+
|
|
93
|
+
if node - "$CURRENT_POINTER_HELPER" "$CP_SMOKE_DIR" <<'NODEEOF' 2>"$TMPDIR_EVAL/cp-per-actor.err"
|
|
94
|
+
const { readCurrentPointer } = require(process.argv[2]);
|
|
95
|
+
const r = readCurrentPointer(process.argv[3], "some-actor");
|
|
96
|
+
if (r.source !== "per-actor" || !r.payload || r.payload.marker !== "per-actor") { console.error(JSON.stringify(r)); process.exit(1); }
|
|
97
|
+
NODEEOF
|
|
98
|
+
then
|
|
99
|
+
pass "readCurrentPointer prefers the per-actor file over a stale legacy file once it exists"
|
|
100
|
+
else
|
|
101
|
+
fail "readCurrentPointer did not prefer the per-actor file: $(cat "$TMPDIR_EVAL/cp-per-actor.err")"
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
# An unresolved/empty actorKey always falls straight through to legacy, regardless of a
|
|
105
|
+
# matching per-actor file existing under a DIFFERENT key.
|
|
106
|
+
if node - "$CURRENT_POINTER_HELPER" "$CP_SMOKE_DIR" <<'NODEEOF' 2>"$TMPDIR_EVAL/cp-unresolved.err"
|
|
107
|
+
const { readCurrentPointer } = require(process.argv[2]);
|
|
108
|
+
const r = readCurrentPointer(process.argv[3], "");
|
|
109
|
+
if (r.source !== "legacy" || !r.payload || r.payload.marker !== "legacy") { console.error(JSON.stringify(r)); process.exit(1); }
|
|
110
|
+
NODEEOF
|
|
111
|
+
then
|
|
112
|
+
pass "readCurrentPointer falls straight to legacy for an empty/unresolved actorKey"
|
|
113
|
+
else
|
|
114
|
+
fail "readCurrentPointer did not fall back to legacy for an unresolved actorKey: $(cat "$TMPDIR_EVAL/cp-unresolved.err")"
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
# --- 2. actor A's own current resolution is unaffected by actor B's later call (AC7) ----------
|
|
118
|
+
echo "--- 2. actor A's current resolution survives actor B's later, unrelated ensure-session (AC7) ---"
|
|
119
|
+
|
|
120
|
+
AC7_ROOT="$TMPDIR_EVAL/ac7-artifact-root"
|
|
121
|
+
|
|
122
|
+
flow_agents_node "workflow-sidecar" ensure-session \
|
|
123
|
+
--artifact-root "$AC7_ROOT" \
|
|
124
|
+
--work-item "kontourai/flow-agents#9201" \
|
|
125
|
+
--actor eval-actor-a-session \
|
|
126
|
+
--source-request "Actor A's own subject." \
|
|
127
|
+
--summary "Actor A." \
|
|
128
|
+
>"$TMPDIR_EVAL/ac7-a.out" 2>"$TMPDIR_EVAL/ac7-a.err"
|
|
129
|
+
A_STATUS=$?
|
|
130
|
+
|
|
131
|
+
# Actor B runs ensure-session on a DIFFERENT subject on the SAME artifact root, strictly AFTER
|
|
132
|
+
# actor A -- the exact "session B runs ensure-session after A" scenario from the issue.
|
|
133
|
+
flow_agents_node "workflow-sidecar" ensure-session \
|
|
134
|
+
--artifact-root "$AC7_ROOT" \
|
|
135
|
+
--work-item "kontourai/flow-agents#9202" \
|
|
136
|
+
--actor eval-actor-b-session \
|
|
137
|
+
--source-request "Actor B's own, unrelated subject." \
|
|
138
|
+
--summary "Actor B." \
|
|
139
|
+
>"$TMPDIR_EVAL/ac7-b.out" 2>"$TMPDIR_EVAL/ac7-b.err"
|
|
140
|
+
B_STATUS=$?
|
|
141
|
+
|
|
142
|
+
if [[ "$A_STATUS" -eq 0 && "$B_STATUS" -eq 0 ]]; then
|
|
143
|
+
pass "both actor A's and actor B's ensure-session calls on distinct subjects succeed"
|
|
144
|
+
else
|
|
145
|
+
fail "actor A/B setup calls failed: a=$A_STATUS b=$B_STATUS $(cat "$TMPDIR_EVAL/ac7-a.out" "$TMPDIR_EVAL/ac7-a.err" "$TMPDIR_EVAL/ac7-b.out" "$TMPDIR_EVAL/ac7-b.err")"
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
A_SLUG_VIA_ACTOR="$(flow_agents_node "workflow-sidecar" current --artifact-root "$AC7_ROOT" --actor eval-actor-a-session --format slug 2>"$TMPDIR_EVAL/ac7-current-a.err")"
|
|
149
|
+
if [[ "$A_SLUG_VIA_ACTOR" == "kontourai-flow-agents-9201" ]]; then
|
|
150
|
+
pass "current --actor eval-actor-a-session still resolves A's OWN session directory after B's later call (AC7)"
|
|
151
|
+
else
|
|
152
|
+
fail "current --actor eval-actor-a-session resolved the wrong session after B's later call: got '$A_SLUG_VIA_ACTOR' (err: $(cat "$TMPDIR_EVAL/ac7-current-a.err"))"
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
B_SLUG_VIA_ACTOR="$(flow_agents_node "workflow-sidecar" current --artifact-root "$AC7_ROOT" --actor eval-actor-b-session --format slug 2>"$TMPDIR_EVAL/ac7-current-b.err")"
|
|
156
|
+
if [[ "$B_SLUG_VIA_ACTOR" == "kontourai-flow-agents-9202" ]]; then
|
|
157
|
+
pass "current --actor eval-actor-b-session resolves B's own session directory"
|
|
158
|
+
else
|
|
159
|
+
fail "current --actor eval-actor-b-session resolved the wrong session: got '$B_SLUG_VIA_ACTOR' (err: $(cat "$TMPDIR_EVAL/ac7-current-b.err"))"
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
# Contrast: the LEGACY global current.json (no --actor) is still last-writer-wins -- it now
|
|
163
|
+
# names B (who ran ensure-session last), proving the isolation above comes specifically from
|
|
164
|
+
# the per-actor projection, not from some other accidental effect.
|
|
165
|
+
LEGACY_SLUG="$(flow_agents_node "workflow-sidecar" current --artifact-root "$AC7_ROOT" --format slug 2>"$TMPDIR_EVAL/ac7-current-legacy.err")"
|
|
166
|
+
if [[ "$LEGACY_SLUG" == "kontourai-flow-agents-9202" ]]; then
|
|
167
|
+
pass "contrast: the legacy global current.json (no --actor) still names B, the last writer -- isolation is specifically the per-actor projection's doing (AC7)"
|
|
168
|
+
else
|
|
169
|
+
fail "legacy global current.json did not name B as expected: got '$LEGACY_SLUG' (err: $(cat "$TMPDIR_EVAL/ac7-current-legacy.err"))"
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
PER_ACTOR_FILE_A="$AC7_ROOT/current/eval-actor-a-session.json"
|
|
173
|
+
PER_ACTOR_FILE_B="$AC7_ROOT/current/eval-actor-b-session.json"
|
|
174
|
+
[[ -f "$PER_ACTOR_FILE_A" ]] && pass "actor A's per-actor current file exists on disk" || fail "actor A's per-actor current file was not written"
|
|
175
|
+
[[ -f "$PER_ACTOR_FILE_B" ]] && pass "actor B's per-actor current file exists on disk" || fail "actor B's per-actor current file was not written"
|
|
176
|
+
|
|
177
|
+
# --- 3. Legacy-only fixture: every named consumer resolves identically to pre-#291 (AC8) -------
|
|
178
|
+
echo "--- 3. legacy-only fixture: existing consumers resolve identically to pre-#291 output (AC8) ---"
|
|
179
|
+
|
|
180
|
+
# 3a. stop-goal-fit.js -- reuses test_goal_fit_hook.sh's own fixture/assertion verbatim, run
|
|
181
|
+
# under a DIFFERENT ambient actor that has no per-actor current file -- proves the compat shim
|
|
182
|
+
# falls straight through to the legacy file (same output regardless of actor) exactly as
|
|
183
|
+
# pre-#291 (when this file had no actor-awareness at all).
|
|
184
|
+
GOAL_FIT_REPO="$TMPDIR_EVAL/goal-fit-legacy/repo"
|
|
185
|
+
mkdir -p "$GOAL_FIT_REPO/.kontourai/flow-agents/feedback-loop"
|
|
186
|
+
LEGACY_POINTER_LINE="{\"schema_version\":\"1.0\",\"active_slug\":\"feedback-loop\",\"artifact_dir\":\"feedback-loop\"}"
|
|
187
|
+
printf "%s" "$LEGACY_POINTER_LINE" | safe_write "$GOAL_FIT_REPO/.kontourai/flow-agents/current.json"
|
|
188
|
+
printf '# Test Repo\n' > "$GOAL_FIT_REPO/AGENTS.md"
|
|
189
|
+
cat > "$GOAL_FIT_REPO/.kontourai/flow-agents/feedback-loop/feedback-loop--deliver.md" <<'MARKDOWN'
|
|
190
|
+
# Build feedback loop
|
|
191
|
+
|
|
192
|
+
branch: main
|
|
193
|
+
worktree: main
|
|
194
|
+
created: 2026-05-04
|
|
195
|
+
status: executing
|
|
196
|
+
type: deliver
|
|
197
|
+
|
|
198
|
+
## Plan
|
|
199
|
+
|
|
200
|
+
Implementation plan exists, but no goal fit state exists yet.
|
|
201
|
+
MARKDOWN
|
|
202
|
+
|
|
203
|
+
if FLOW_AGENTS_ACTOR=eval-actor-no-per-actor-file FLOW_AGENTS_GOAL_FIT_MODE=warn FLOW_AGENTS_GOAL_FIT_MAX_BLOCKS=100000 \
|
|
204
|
+
node "$GOAL_FIT_GATE" >"$TMPDIR_EVAL/goal-fit-legacy.out" 2>"$TMPDIR_EVAL/goal-fit-legacy.err" <<JSON
|
|
205
|
+
{"hook_event_name":"Stop","cwd":"$GOAL_FIT_REPO"}
|
|
206
|
+
JSON
|
|
207
|
+
then
|
|
208
|
+
if rg -q 'status:executing' "$TMPDIR_EVAL/goal-fit-legacy.err"; then
|
|
209
|
+
pass "stop-goal-fit.js resolves the legacy-only fixture identically to its own existing eval's golden output, under a different ambient actor (AC8)"
|
|
210
|
+
else
|
|
211
|
+
fail "stop-goal-fit.js legacy-only output did not match the golden assertion: $(cat "$TMPDIR_EVAL/goal-fit-legacy.err")"
|
|
212
|
+
fi
|
|
213
|
+
else
|
|
214
|
+
fail "stop-goal-fit.js against the legacy-only fixture should stay warning-only (exit 0): $(cat "$TMPDIR_EVAL/goal-fit-legacy.out" "$TMPDIR_EVAL/goal-fit-legacy.err")"
|
|
215
|
+
fi
|
|
216
|
+
|
|
217
|
+
# 3b. evidence-capture.js's resolveArtifactDir -- reuses test_command_log_concurrency.sh's
|
|
218
|
+
# fixture: current.json (legacy-only) names a slug; a captured PostToolUse event must land in
|
|
219
|
+
# THAT slug's command-log.jsonl, under a different ambient actor with no per-actor file.
|
|
220
|
+
EVIDENCE_REPO="$TMPDIR_EVAL/evidence-legacy/repo"
|
|
221
|
+
EVIDENCE_SLUG="legacy-evidence-slug"
|
|
222
|
+
mkdir -p "$EVIDENCE_REPO/.kontourai/flow-agents/$EVIDENCE_SLUG"
|
|
223
|
+
printf '# Repo\n' > "$EVIDENCE_REPO/AGENTS.md"
|
|
224
|
+
|
|
225
|
+
EVIDENCE_CURRENT_LINE="{\"active_slug\":\"$EVIDENCE_SLUG\",\"artifact_dir\":\".kontourai/flow-agents/$EVIDENCE_SLUG\"}"
|
|
226
|
+
printf '%s' "$EVIDENCE_CURRENT_LINE" | safe_write "$EVIDENCE_REPO/.kontourai/flow-agents/current.json"
|
|
227
|
+
|
|
228
|
+
EVIDENCE_STATE_LINE="{\"schema_version\":\"1.0\",\"task_slug\":\"$EVIDENCE_SLUG\",\"status\":\"in_progress\",\"phase\":\"build\",\"updated_at\":\"2026-06-23T00:00:00Z\",\"next_action\":{\"status\":\"in_progress\",\"summary\":\"work\"}}"
|
|
229
|
+
printf '%s' "$EVIDENCE_STATE_LINE" | safe_write "$EVIDENCE_REPO/.kontourai/flow-agents/$EVIDENCE_SLUG/state.json"
|
|
230
|
+
|
|
231
|
+
EVIDENCE_EVENT_LINE="{\"hook_event_name\":\"PostToolUse\",\"tool_name\":\"Bash\",\"cwd\":\"$EVIDENCE_REPO\",\"tool_input\":{\"command\":\"echo legacy-actor-check\"},\"tool_response\":{\"exitCode\":0,\"stdout\":\"ok\"}}"
|
|
232
|
+
printf '%s' "$EVIDENCE_EVENT_LINE" \
|
|
233
|
+
| FLOW_AGENTS_ACTOR=eval-actor-no-per-actor-file node "$EVIDENCE_CAPTURE" >"$TMPDIR_EVAL/evidence-legacy.out" 2>"$TMPDIR_EVAL/evidence-legacy.err"
|
|
234
|
+
|
|
235
|
+
EVIDENCE_LOG_FILE="$EVIDENCE_REPO/.kontourai/flow-agents/$EVIDENCE_SLUG/command-log.jsonl"
|
|
236
|
+
if [[ -f "$EVIDENCE_LOG_FILE" ]] && grep -qF "legacy-actor-check" "$EVIDENCE_LOG_FILE"; then
|
|
237
|
+
pass "evidence-capture.js's resolveArtifactDir resolves the legacy-only current.json's named slug under a different ambient actor (AC8)"
|
|
238
|
+
else
|
|
239
|
+
fail "evidence-capture.js did not resolve/write to the legacy-only fixture's named slug: $(cat "$TMPDIR_EVAL/evidence-legacy.out" "$TMPDIR_EVAL/evidence-legacy.err")"
|
|
240
|
+
fi
|
|
241
|
+
|
|
242
|
+
# 3c. flow-agents-statusline.js -- reuses test_flow_agents_statusline.sh's fixture/assertion
|
|
243
|
+
# verbatim, run under a different ambient actor.
|
|
244
|
+
STATUSLINE_WORKSPACE="$TMPDIR_EVAL/statusline-legacy/workspace"
|
|
245
|
+
STATUSLINE_TASK_DIR="$STATUSLINE_WORKSPACE/.kontourai/flow-agents/status-demo"
|
|
246
|
+
mkdir -p "$STATUSLINE_TASK_DIR"
|
|
247
|
+
|
|
248
|
+
safe_write "$STATUSLINE_WORKSPACE/.kontourai/flow-agents/current.json" <<'JSON'
|
|
249
|
+
{
|
|
250
|
+
"schema_version": "1.0",
|
|
251
|
+
"active_slug": "status-demo",
|
|
252
|
+
"artifact_dir": "status-demo",
|
|
253
|
+
"owner": "codex",
|
|
254
|
+
"updated_at": "2026-05-25T00:00:00Z",
|
|
255
|
+
"source": "test"
|
|
256
|
+
}
|
|
257
|
+
JSON
|
|
258
|
+
|
|
259
|
+
safe_write "$STATUSLINE_TASK_DIR/state.json" <<'JSON'
|
|
260
|
+
{
|
|
261
|
+
"schema_version": "1.0",
|
|
262
|
+
"task_slug": "status-demo",
|
|
263
|
+
"status": "needs_decision",
|
|
264
|
+
"phase": "evidence",
|
|
265
|
+
"updated_at": "2026-05-25T00:01:00Z",
|
|
266
|
+
"next_action": {
|
|
267
|
+
"status": "needs_user",
|
|
268
|
+
"summary": "Review the release hold and decide whether the missing approval is accepted.",
|
|
269
|
+
"target_phase": "release"
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
JSON
|
|
273
|
+
|
|
274
|
+
cat > "$STATUSLINE_TASK_DIR/acceptance.json" <<'JSON'
|
|
275
|
+
{
|
|
276
|
+
"schema_version": "1.0",
|
|
277
|
+
"task_slug": "status-demo",
|
|
278
|
+
"criteria": [
|
|
279
|
+
{"id": "one", "description": "First criterion", "status": "pass"},
|
|
280
|
+
{"id": "two", "description": "Second criterion", "status": "pending"},
|
|
281
|
+
{"id": "three", "description": "Third criterion", "status": "accepted_gap"}
|
|
282
|
+
],
|
|
283
|
+
"goal_fit": {"status": "pending", "summary": "Pending"}
|
|
284
|
+
}
|
|
285
|
+
JSON
|
|
286
|
+
|
|
287
|
+
STATUSLINE_OUT="$(cd "$STATUSLINE_WORKSPACE" && FLOW_AGENTS_ACTOR=eval-actor-no-per-actor-file node "$STATUSLINE" <<JSON
|
|
288
|
+
{"cwd":"$STATUSLINE_WORKSPACE"}
|
|
289
|
+
JSON
|
|
290
|
+
)"
|
|
291
|
+
if [[ "$STATUSLINE_OUT" == *"Flow Agents: status-demo"* && "$STATUSLINE_OUT" == *"evidence/needs_decision"* && "$STATUSLINE_OUT" == *"2/3 AC"* && "$STATUSLINE_OUT" == *"next:"* ]]; then
|
|
292
|
+
pass "flow-agents-statusline.js resolves the legacy-only fixture identically to its own existing eval's golden output, under a different ambient actor (AC8)"
|
|
293
|
+
else
|
|
294
|
+
fail "flow-agents-statusline.js legacy-only output did not match the golden assertion: $STATUSLINE_OUT"
|
|
295
|
+
fi
|
|
296
|
+
|
|
297
|
+
# 3d. workflow-steering.js's ambient state lookup -- reuses test_workflow_steering_hook.sh's
|
|
298
|
+
# steering-demo fixture/assertion verbatim, under a different ambient actor.
|
|
299
|
+
STEERING_REPO="$TMPDIR_EVAL/steering-legacy/repo"
|
|
300
|
+
mkdir -p "$STEERING_REPO/.kontourai/flow-agents/steering-demo"
|
|
301
|
+
mkdir -p "$STEERING_REPO/docs"
|
|
302
|
+
printf '# Test Repo\n' > "$STEERING_REPO/AGENTS.md"
|
|
303
|
+
printf '# Context Map\n' > "$STEERING_REPO/docs/context-map.md"
|
|
304
|
+
|
|
305
|
+
safe_write "$STEERING_REPO/.kontourai/flow-agents/steering-demo/state.json" <<'JSON'
|
|
306
|
+
{
|
|
307
|
+
"schema_version": "1.0",
|
|
308
|
+
"task_slug": "steering-demo",
|
|
309
|
+
"status": "not_verified",
|
|
310
|
+
"phase": "verification",
|
|
311
|
+
"updated_at": "2026-05-09T00:00:00Z",
|
|
312
|
+
"next_action": {
|
|
313
|
+
"status": "needs_user",
|
|
314
|
+
"summary": "Decide whether to accept the external service verification gap.",
|
|
315
|
+
"target_phase": "goal_fit"
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
JSON
|
|
319
|
+
|
|
320
|
+
STEERING_OUT="$(FLOW_AGENTS_ACTOR=eval-actor-no-per-actor-file node "$STEERING" <<JSON
|
|
321
|
+
{"hook_event_name":"UserPromptSubmit","cwd":"$STEERING_REPO","prompt":"continue"}
|
|
322
|
+
JSON
|
|
323
|
+
)"
|
|
324
|
+
if [[ "$STEERING_OUT" == *"steering-demo"* && "$STEERING_OUT" == *"status:not_verified"* ]]; then
|
|
325
|
+
pass "workflow-steering.js's ambient state lookup resolves the legacy-only fixture identically to its own existing eval's golden output, under a different ambient actor (AC8)"
|
|
326
|
+
else
|
|
327
|
+
fail "workflow-steering.js legacy-only steering output did not match the golden assertion: $STEERING_OUT"
|
|
328
|
+
fi
|
|
329
|
+
|
|
330
|
+
# --- 4. config-protection.js covers current/<actor>.json exactly like current.json (AC10) -----
|
|
331
|
+
echo "--- 4. config-protection.js blocks current/<actor>.json writes/redirects (AC10) ---"
|
|
332
|
+
|
|
333
|
+
set +e
|
|
334
|
+
LEGACY_BLOCK_OUT=$(echo '{"tool_name":"Write","tool_input":{"path":"/repo/.kontourai/flow-agents/current.json"}}' | node "$CONFIG_PROTECTION" 2>&1)
|
|
335
|
+
LEGACY_BLOCK_EXIT=$?
|
|
336
|
+
PER_ACTOR_BLOCK_OUT=$(echo '{"tool_name":"Write","tool_input":{"path":"/repo/.kontourai/flow-agents/current/claude-code-x.json"}}' | node "$CONFIG_PROTECTION" 2>&1)
|
|
337
|
+
PER_ACTOR_BLOCK_EXIT=$?
|
|
338
|
+
set -e
|
|
339
|
+
|
|
340
|
+
if [[ "$PER_ACTOR_BLOCK_EXIT" -eq 2 ]] && echo "$PER_ACTOR_BLOCK_OUT" | grep -q "BLOCKED"; then
|
|
341
|
+
pass "Write to .kontourai/flow-agents/current/<actor>.json is blocked (exit 2) (AC10)"
|
|
342
|
+
else
|
|
343
|
+
fail "Write to .kontourai/flow-agents/current/<actor>.json was NOT blocked (exit=$PER_ACTOR_BLOCK_EXIT, out=$PER_ACTOR_BLOCK_OUT)"
|
|
344
|
+
fi
|
|
345
|
+
|
|
346
|
+
LEGACY_REASON="$(printf '%s' "$LEGACY_BLOCK_OUT" | sed -n 's/.*protected because \(.*\)\. Use .*/\1/p')"
|
|
347
|
+
PER_ACTOR_REASON="$(printf '%s' "$PER_ACTOR_BLOCK_OUT" | sed -n 's/.*protected because \(.*\)\. Use .*/\1/p')"
|
|
348
|
+
if [[ -n "$LEGACY_REASON" && "$LEGACY_REASON" == "$PER_ACTOR_REASON" ]]; then
|
|
349
|
+
pass "current/<actor>.json is blocked with the SAME reason text as legacy current.json (AC10)"
|
|
350
|
+
else
|
|
351
|
+
fail "current/<actor>.json's blocked reason text differs from legacy current.json's: legacy='$LEGACY_REASON' per-actor='$PER_ACTOR_REASON'"
|
|
352
|
+
fi
|
|
353
|
+
|
|
354
|
+
set +e
|
|
355
|
+
TEE_BLOCK_OUT=$(echo '{"tool_name":"Bash","tool_input":{"command":"echo {} | tee .kontourai/flow-agents/current/claude-code-x.json"}}' | node "$CONFIG_PROTECTION" 2>&1)
|
|
356
|
+
TEE_BLOCK_EXIT=$?
|
|
357
|
+
set -e
|
|
358
|
+
if [[ "$TEE_BLOCK_EXIT" -eq 2 ]] && echo "$TEE_BLOCK_OUT" | grep -q "BLOCKED"; then
|
|
359
|
+
pass "tee .kontourai/flow-agents/current/<actor>.json is blocked (exit 2) (AC10)"
|
|
360
|
+
else
|
|
361
|
+
fail "tee .kontourai/flow-agents/current/<actor>.json was NOT blocked (exit=$TEE_BLOCK_EXIT, out=$TEE_BLOCK_OUT)"
|
|
362
|
+
fi
|
|
363
|
+
|
|
364
|
+
set +e
|
|
365
|
+
UNRELATED_OUT=$(echo '{"tool_name":"Write","tool_input":{"path":"/repo/src/main.js"}}' | node "$CONFIG_PROTECTION" 2>&1)
|
|
366
|
+
UNRELATED_EXIT=$?
|
|
367
|
+
set -e
|
|
368
|
+
[[ "$UNRELATED_EXIT" -eq 0 ]] && pass "an unrelated file write is still allowed (exit 0) (AC10 regression guard)" || fail "an unrelated file write was falsely blocked (exit=$UNRELATED_EXIT)"
|
|
369
|
+
|
|
370
|
+
# --- 5. record-gate-claim resolves A's own per-actor flow/step, not B's legacy pointer (AC11) --
|
|
371
|
+
echo "--- 5. record-gate-claim resolves A's own per-actor flow/step, not B's legacy pointer (AC11) ---"
|
|
372
|
+
|
|
373
|
+
AC11_ROOT="$TMPDIR_EVAL/ac11-artifact-root"
|
|
374
|
+
|
|
375
|
+
flow_agents_node "workflow-sidecar" ensure-session \
|
|
376
|
+
--artifact-root "$AC11_ROOT" \
|
|
377
|
+
--task-slug gate-actor-a \
|
|
378
|
+
--actor eval-actor-a-session \
|
|
379
|
+
--flow-id builder.build \
|
|
380
|
+
--source-request "Actor A is active on a FlowDefinition-driven session." \
|
|
381
|
+
--summary "Actor A, builder.build, pull-work step." \
|
|
382
|
+
--timestamp "2026-07-01T00:00:00Z" \
|
|
383
|
+
>"$TMPDIR_EVAL/ac11-a-ensure.out" 2>"$TMPDIR_EVAL/ac11-a-ensure.err"
|
|
384
|
+
|
|
385
|
+
AC11_DELIVER_MD="$AC11_ROOT/gate-actor-a/gate-actor-a--deliver.md"
|
|
386
|
+
flow_agents_node "workflow-sidecar" init-plan "$AC11_DELIVER_MD" \
|
|
387
|
+
--source-request "Test" --summary "Testing" \
|
|
388
|
+
--timestamp "2026-07-01T00:00:30Z" >"$TMPDIR_EVAL/ac11-a-initplan.out" 2>"$TMPDIR_EVAL/ac11-a-initplan.err"
|
|
389
|
+
|
|
390
|
+
# Actor B runs a LATER, plain (non-FlowDefinition) ensure-session on an unrelated subject on the
|
|
391
|
+
# SAME artifact root -- this overwrites the LEGACY global current.json (last-writer-wins, no
|
|
392
|
+
# active_flow_id/active_step_id at all) while leaving actor A's own per-actor current file
|
|
393
|
+
# completely untouched. If record-gate-claim below fell back to reading the legacy global
|
|
394
|
+
# pointer instead of actor A's own per-actor pointer, it would find NO active flow step at all
|
|
395
|
+
# and die -- proving this section's assertions are a real differential test, not a tautology.
|
|
396
|
+
flow_agents_node "workflow-sidecar" ensure-session \
|
|
397
|
+
--artifact-root "$AC11_ROOT" \
|
|
398
|
+
--task-slug gate-actor-b-unrelated \
|
|
399
|
+
--actor eval-actor-b-session \
|
|
400
|
+
--source-request "Actor B's unrelated, non-FlowDefinition session." \
|
|
401
|
+
--summary "Actor B, no flow-id." \
|
|
402
|
+
--timestamp "2026-07-01T00:01:00Z" \
|
|
403
|
+
>"$TMPDIR_EVAL/ac11-b-ensure.out" 2>"$TMPDIR_EVAL/ac11-b-ensure.err"
|
|
404
|
+
|
|
405
|
+
AC11_LEGACY_CURRENT_FILE="$AC11_ROOT/current.json"
|
|
406
|
+
if grep -q '"active_flow_id"' "$AC11_LEGACY_CURRENT_FILE" 2>/dev/null; then
|
|
407
|
+
fail "test setup invalid: the legacy global current.json unexpectedly still carries an active_flow_id after actor B's plain ensure-session"
|
|
408
|
+
else
|
|
409
|
+
pass "test setup: actor B's later, plain ensure-session leaves the legacy global current.json WITHOUT any active_flow_id (a real differential test, not a tautology)"
|
|
410
|
+
fi
|
|
411
|
+
|
|
412
|
+
AC11_GATE_ACTOR_A_DIR="$AC11_ROOT/gate-actor-a"
|
|
413
|
+
if flow_agents_node "workflow-sidecar" record-gate-claim "$AC11_GATE_ACTOR_A_DIR" \
|
|
414
|
+
--actor eval-actor-a-session \
|
|
415
|
+
--status pass \
|
|
416
|
+
--summary "Selected issue #291 for implementation." \
|
|
417
|
+
--expectation selected-work \
|
|
418
|
+
--timestamp "2026-07-01T00:02:00Z" \
|
|
419
|
+
>"$TMPDIR_EVAL/ac11-gate-claim.out" 2>"$TMPDIR_EVAL/ac11-gate-claim.err"; then
|
|
420
|
+
pass "record-gate-claim (actor A) succeeds by resolving A's OWN per-actor current pointer, not B's stale legacy pointer (AC11)"
|
|
421
|
+
else
|
|
422
|
+
fail "record-gate-claim (actor A) unexpectedly failed: $(cat "$TMPDIR_EVAL/ac11-gate-claim.out" "$TMPDIR_EVAL/ac11-gate-claim.err")"
|
|
423
|
+
fi
|
|
424
|
+
|
|
425
|
+
AC11_BUNDLE_FILE="$AC11_GATE_ACTOR_A_DIR/trust.bundle"
|
|
426
|
+
if node - "$AC11_BUNDLE_FILE" <<'NODEEOF' 2>"$TMPDIR_EVAL/ac11-bundle-check.err"
|
|
427
|
+
const fs = require("fs");
|
|
428
|
+
const bundle = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
|
429
|
+
const target = (bundle.claims || []).find((c) => c.claimType === "builder.pull-work.selected");
|
|
430
|
+
if (!target) { console.error("no builder.pull-work.selected claim found"); process.exit(1); }
|
|
431
|
+
if (target.status !== "verified") { console.error("expected status=verified, got " + target.status); process.exit(1); }
|
|
432
|
+
NODEEOF
|
|
433
|
+
then
|
|
434
|
+
pass "actor A's trust.bundle carries the builder.pull-work.selected claim derived from A's OWN flow/step (AC11)"
|
|
435
|
+
else
|
|
436
|
+
fail "actor A's trust.bundle did not carry the expected claim: $(cat "$TMPDIR_EVAL/ac11-bundle-check.err")"
|
|
437
|
+
fi
|
|
438
|
+
|
|
439
|
+
# --- 6. F3 (fix-plan iteration 1, code-review-291 MEDIUM): legacy current.json names a
|
|
440
|
+
# still-ACTIVE, non-newest-mtime session while a NEWER active state.json exists elsewhere under
|
|
441
|
+
# the same artifact root, under a RESOLVED actor with no per-actor current file. Pre-#291,
|
|
442
|
+
# latestWorkflowState() was a pure global newest-mtime scan with zero current.json involvement,
|
|
443
|
+
# so the newer state.json would win. Post-#291, actorScopedWorkflowState() is consulted first and
|
|
444
|
+
# falls through to the legacy current.json for an actor with no per-actor file (the compat-shim
|
|
445
|
+
# guarantee section 3 already covers) -- but if that legacy pointer names a still-ACTIVE session,
|
|
446
|
+
# it is now returned IMMEDIATELY, without ever comparing mtimes against the newer state.json. This
|
|
447
|
+
# section test-pins that specific, deliberate behavior change (current.json preference wins over
|
|
448
|
+
# newest-mtime) for the single-actor/resolved-actor case, not just the multi-actor case AC7/AC8
|
|
449
|
+
# are framed around -- the review's flagged, previously test-uncovered edge.
|
|
450
|
+
echo "--- 6. legacy current.json (non-newest, still-active) wins over a newer state.json elsewhere, under a resolved actor (F3, fix-plan iteration 1) ---"
|
|
451
|
+
|
|
452
|
+
F3_REPO="$TMPDIR_EVAL/f3-legacy-vs-newest/repo"
|
|
453
|
+
mkdir -p "$F3_REPO/.kontourai/flow-agents/older-named-slug"
|
|
454
|
+
mkdir -p "$F3_REPO/.kontourai/flow-agents/newer-unnamed-slug"
|
|
455
|
+
mkdir -p "$F3_REPO/docs"
|
|
456
|
+
printf '# Test Repo
|
|
457
|
+
' > "$F3_REPO/AGENTS.md"
|
|
458
|
+
printf '# Context Map
|
|
459
|
+
' > "$F3_REPO/docs/context-map.md"
|
|
460
|
+
|
|
461
|
+
# The OLDER session (by mtime) is the one the legacy current.json names -- still ACTIVE.
|
|
462
|
+
safe_write "$F3_REPO/.kontourai/flow-agents/older-named-slug/state.json" <<'JSON'
|
|
463
|
+
{
|
|
464
|
+
"schema_version": "1.0",
|
|
465
|
+
"task_slug": "older-named-slug",
|
|
466
|
+
"status": "in_progress",
|
|
467
|
+
"phase": "build",
|
|
468
|
+
"updated_at": "2026-05-09T00:00:00Z",
|
|
469
|
+
"next_action": {
|
|
470
|
+
"status": "in_progress",
|
|
471
|
+
"summary": "Older, current.json-named session."
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
JSON
|
|
475
|
+
|
|
476
|
+
F3_LEGACY_CURRENT_LINE='{"active_slug":"older-named-slug","artifact_dir":"older-named-slug"}'
|
|
477
|
+
printf '%s' "$F3_LEGACY_CURRENT_LINE" | safe_write "$F3_REPO/.kontourai/flow-agents/current.json"
|
|
478
|
+
|
|
479
|
+
# Sleep-free mtime ordering: touch the "newer" state.json strictly after the legacy-named one, so
|
|
480
|
+
# a pure newest-mtime scan (pre-#291 behavior) would prefer THIS one instead.
|
|
481
|
+
sleep 1
|
|
482
|
+
safe_write "$F3_REPO/.kontourai/flow-agents/newer-unnamed-slug/state.json" <<'JSON'
|
|
483
|
+
{
|
|
484
|
+
"schema_version": "1.0",
|
|
485
|
+
"task_slug": "newer-unnamed-slug",
|
|
486
|
+
"status": "in_progress",
|
|
487
|
+
"phase": "build",
|
|
488
|
+
"updated_at": "2026-05-10T00:00:00Z",
|
|
489
|
+
"next_action": {
|
|
490
|
+
"status": "in_progress",
|
|
491
|
+
"summary": "Newer, but NOT named by current.json."
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
JSON
|
|
495
|
+
|
|
496
|
+
# A RESOLVED actor (FLOW_AGENTS_ACTOR set) with no per-actor current file -- actorScopedWorkflowState
|
|
497
|
+
# falls through to the legacy current.json (same compat-shim guarantee as section 3), which names
|
|
498
|
+
# the OLDER-but-still-active session.
|
|
499
|
+
F3_OUT="$(FLOW_AGENTS_ACTOR=eval-actor-f3-resolved node "$ROOT/scripts/hooks/workflow-steering.js" <<JSON
|
|
500
|
+
{"hook_event_name":"UserPromptSubmit","cwd":"$F3_REPO","prompt":"continue"}
|
|
501
|
+
JSON
|
|
502
|
+
)"
|
|
503
|
+
|
|
504
|
+
if [[ "$F3_OUT" == *"older-named-slug"* && "$F3_OUT" != *"newer-unnamed-slug"* ]]; then
|
|
505
|
+
pass "workflow-steering.js prefers the legacy current.json's named ACTIVE session over a newer-mtime state.json elsewhere, under a resolved actor with no per-actor file (F3 -- pins the post-#291 behavior change explicitly, not just AC7/AC8's multi-actor framing)"
|
|
506
|
+
else
|
|
507
|
+
fail "workflow-steering.js did not prefer the legacy-named active session over the newer-mtime one: $F3_OUT"
|
|
508
|
+
fi
|
|
509
|
+
|
|
510
|
+
echo ""
|
|
511
|
+
if [[ "$errors" -eq 0 ]]; then
|
|
512
|
+
echo "test_current_json_per_actor: all checks passed."
|
|
513
|
+
else
|
|
514
|
+
echo "test_current_json_per_actor: $errors check(s) failed."
|
|
515
|
+
fi
|
|
516
|
+
exit "$errors"
|