@kontourai/flow-agents 3.9.0 → 3.10.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/workflows/ci.yml +8 -0
- package/CHANGELOG.md +17 -0
- package/README.md +1 -1
- package/build/src/cli/assignment-provider.js +4 -18
- package/build/src/cli/continuation-adapter.d.ts +10 -8
- package/build/src/cli/continuation-adapter.js +22 -4
- package/build/src/cli/validate-workflow-artifacts.js +21 -1
- package/build/src/cli/workflow-sidecar.js +15 -20
- package/build/src/cli/workflow.js +58 -11
- package/build/src/continuation-driver.d.ts +27 -3
- package/build/src/continuation-driver.js +97 -14
- package/build/src/index.d.ts +2 -2
- package/build/src/index.js +1 -1
- package/build/src/tools/build-universal-bundles.js +52 -25
- package/build/src/tools/validate-source-tree.js +5 -2
- package/context/scripts/hooks/lib/continuation-turn-authority.js +386 -0
- package/context/scripts/hooks/stop-goal-fit.js +244 -73
- package/context/scripts/hooks/workflow-steering.js +24 -15
- package/context/scripts/telemetry/lib/pricing.sh +18 -6
- package/context/scripts/telemetry/lib/transport.sh +139 -37
- package/context/scripts/telemetry/lib/usage.sh +282 -32
- package/context/scripts/telemetry/lib/usage_model_guard.sh +42 -0
- package/context/scripts/telemetry/telemetry.sh +76 -1
- package/docs/coordination-guide.md +21 -5
- package/docs/fixture-ownership.md +2 -1
- package/docs/public-workflow-cli.md +44 -0
- package/docs/spec/runtime-hook-surface.md +8 -4
- package/evals/acceptance/prove-capture-teeth-declared.sh +13 -0
- package/evals/acceptance/prove-capture-teeth.sh +13 -0
- package/evals/ci/run-baseline.sh +2 -0
- package/evals/fixtures/codex-legacy-agents/5273878130bdafc8a024a650bb5b66c9b003f1f859b5dc6e5b588cbf4ab23228.md +25 -0
- package/evals/fixtures/telemetry/usage-transcript-adversarial.jsonl +2 -0
- package/evals/fixtures/telemetry/usage-transcript-malformed-sandwich.jsonl +3 -0
- package/evals/fixtures/telemetry/usage-transcript-negative-tokens.jsonl +1 -0
- package/evals/fixtures/telemetry/usage-transcript-oversized-prefix.jsonl +201 -0
- package/evals/fixtures/telemetry/usage-transcript-oversized-tokens.jsonl +1 -0
- package/evals/fixtures/telemetry/usage-transcript-prefixed-secret-lower.jsonl +1 -0
- package/evals/fixtures/telemetry/usage-transcript-prefixed-secret-upper.jsonl +1 -0
- package/evals/fixtures/telemetry/usage-transcript-secret-model-jwt.jsonl +1 -0
- package/evals/fixtures/telemetry/usage-transcript-secret-model.jsonl +1 -0
- package/evals/fixtures/telemetry/usage-transcript-torn-trailing.jsonl +2 -0
- package/evals/integration/test_actor_identity.sh +63 -0
- package/evals/integration/test_assignment_provider_local_file.sh +34 -0
- package/evals/integration/test_builder_entry_enforcement.sh +5 -1
- package/evals/integration/test_bundle_install.sh +106 -17
- package/evals/integration/test_bundle_lifecycle.sh +17 -3
- package/evals/integration/test_captured_fail_reconciliation.sh +11 -0
- package/evals/integration/test_ci_actor_identity.sh +2 -2
- package/evals/integration/test_command_log_concurrency.sh +11 -0
- package/evals/integration/test_command_log_fork_classification.sh +11 -0
- package/evals/integration/test_command_log_integrity.sh +11 -0
- package/evals/integration/test_current_json_per_actor.sh +416 -21
- package/evals/integration/test_enforcer_expects_driven.sh +11 -0
- package/evals/integration/test_ensure_session_ownership_guard.sh +39 -0
- package/evals/integration/test_evidence_capture_hook.sh +25 -6
- package/evals/integration/test_fixture_retirement_audit.sh +2 -2
- package/evals/integration/test_flowdef_union_floor_regression.sh +11 -0
- package/evals/integration/test_gate_bypass_chain.sh +11 -0
- package/evals/integration/test_gate_lockdown.sh +10 -0
- package/evals/integration/test_goal_fit_ghost_session.sh +11 -0
- package/evals/integration/test_goal_fit_hook.sh +257 -9
- package/evals/integration/test_goal_fit_rederive.sh +11 -0
- package/evals/integration/test_install_merge.sh +141 -3
- package/evals/integration/test_liveness_conflict_injection.sh +39 -3
- package/evals/integration/test_liveness_heartbeat.sh +46 -9
- package/evals/integration/test_phase_map_and_gate_claim.sh +10 -1
- package/evals/integration/test_public_workflow_cli.sh +6 -6
- package/evals/integration/test_session_resume_roundtrip.sh +30 -0
- package/evals/integration/test_stop_hook_release.sh +111 -11
- package/evals/integration/test_telemetry_tool_usage.sh +394 -0
- package/evals/integration/test_verify_hold.sh +64 -1
- package/evals/integration/test_workflow_sidecar_writer.sh +10 -1
- package/evals/integration/test_workflow_steering_hook.sh +20 -4
- package/evals/run.sh +2 -0
- package/evals/static/test_universal_bundles.sh +123 -0
- package/package.json +1 -1
- package/packaging/README.md +21 -0
- package/packaging/codex-legacy-agents-fingerprints.json +33 -0
- package/scripts/README.md +2 -1
- package/scripts/audit-codex-legacy-agents.js +43 -0
- package/scripts/classify-codex-legacy-agents.js +71 -0
- package/scripts/hooks/evidence-capture.js +18 -6
- package/scripts/hooks/lib/actor-identity.js +62 -29
- package/scripts/hooks/lib/continuation-turn-authority.js +386 -0
- package/scripts/hooks/lib/current-pointer.js +114 -12
- package/scripts/hooks/lib/liveness-heartbeat.js +15 -12
- package/scripts/hooks/stop-goal-fit.js +244 -73
- package/scripts/hooks/workflow-steering.js +24 -15
- package/scripts/install-codex-home.sh +8 -7
- package/scripts/telemetry/lib/pricing.sh +18 -6
- package/scripts/telemetry/lib/transport.sh +71 -0
- package/scripts/telemetry/lib/usage.sh +282 -32
- package/scripts/telemetry/lib/usage_model_guard.sh +42 -0
- package/scripts/telemetry/telemetry.sh +76 -1
- package/src/cli/assignment-provider.ts +5 -26
- package/src/cli/builder-flow-runtime.test.mjs +93 -6
- package/src/cli/continuation-adapter.ts +33 -7
- package/src/cli/continuation-driver.test.mjs +741 -2
- package/src/cli/public-api.test.mjs +1 -0
- package/src/cli/validate-workflow-artifacts.ts +19 -1
- package/src/cli/workflow-sidecar.ts +18 -23
- package/src/cli/workflow.ts +71 -11
- package/src/continuation-driver.ts +125 -19
- package/src/index.ts +4 -0
- package/src/tools/build-universal-bundles.ts +61 -25
- package/src/tools/validate-source-tree.ts +5 -2
|
@@ -169,8 +169,19 @@ else
|
|
|
169
169
|
fail "legacy global current.json did not name B as expected: got '$LEGACY_SLUG' (err: $(cat "$TMPDIR_EVAL/ac7-current-legacy.err"))"
|
|
170
170
|
fi
|
|
171
171
|
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
# #440 fix-wave 2: the per-actor filename is now collision-resistant (sanitized prefix + hash of
|
|
173
|
+
# the FULL actor key, see current-pointer.js's perActorCurrentFile) -- compute the expected path
|
|
174
|
+
# via the real function rather than re-deriving the naming rule by hand.
|
|
175
|
+
PER_ACTOR_FILE_A="$(CP_HELPER_ARG="$CURRENT_POINTER_HELPER" ROOT_ARG="$AC7_ROOT" ACTOR_ARG="eval-actor-a-session" node - <<'NODE'
|
|
176
|
+
const { perActorCurrentFile } = require(process.env.CP_HELPER_ARG);
|
|
177
|
+
process.stdout.write(perActorCurrentFile(process.env.ROOT_ARG, process.env.ACTOR_ARG));
|
|
178
|
+
NODE
|
|
179
|
+
)"
|
|
180
|
+
PER_ACTOR_FILE_B="$(CP_HELPER_ARG="$CURRENT_POINTER_HELPER" ROOT_ARG="$AC7_ROOT" ACTOR_ARG="eval-actor-b-session" node - <<'NODE'
|
|
181
|
+
const { perActorCurrentFile } = require(process.env.CP_HELPER_ARG);
|
|
182
|
+
process.stdout.write(perActorCurrentFile(process.env.ROOT_ARG, process.env.ACTOR_ARG));
|
|
183
|
+
NODE
|
|
184
|
+
)"
|
|
174
185
|
[[ -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
186
|
[[ -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
187
|
|
|
@@ -178,9 +189,13 @@ PER_ACTOR_FILE_B="$AC7_ROOT/current/eval-actor-b-session.json"
|
|
|
178
189
|
echo "--- 3. legacy-only fixture: existing consumers resolve identically to pre-#291 output (AC8) ---"
|
|
179
190
|
|
|
180
191
|
# 3a. stop-goal-fit.js -- reuses test_goal_fit_hook.sh's own fixture/assertion verbatim, run
|
|
181
|
-
# under a
|
|
182
|
-
#
|
|
183
|
-
# pre-#291 (when this file had no actor-awareness
|
|
192
|
+
# under a genuinely UNRESOLVED actor (FLOW_AGENTS_ACTOR_TEST_FORCE_UNRESOLVED escape hatch,
|
|
193
|
+
# matching test_stop_hook_release.sh's F(iii) convention) -- proves the compat shim falls
|
|
194
|
+
# straight through to the legacy file exactly as pre-#291 (when this file had no actor-awareness
|
|
195
|
+
# at all). #440: a RESOLVED actor with no own per-actor pointer no longer falls back to this
|
|
196
|
+
# legacy file for BLOCKING purposes (see new section 7) -- that distinct, resolved-actor case is
|
|
197
|
+
# the bug #440 fixes, so this section must force a genuinely unresolved actor to keep testing the
|
|
198
|
+
# unchanged compat-shim path AC4 is about, not the now-intentionally-changed resolved-actor path.
|
|
184
199
|
GOAL_FIT_REPO="$TMPDIR_EVAL/goal-fit-legacy/repo"
|
|
185
200
|
mkdir -p "$GOAL_FIT_REPO/.kontourai/flow-agents/feedback-loop"
|
|
186
201
|
LEGACY_POINTER_LINE="{\"schema_version\":\"1.0\",\"active_slug\":\"feedback-loop\",\"artifact_dir\":\"feedback-loop\"}"
|
|
@@ -200,7 +215,7 @@ type: deliver
|
|
|
200
215
|
Implementation plan exists, but no goal fit state exists yet.
|
|
201
216
|
MARKDOWN
|
|
202
217
|
|
|
203
|
-
if
|
|
218
|
+
if FLOW_AGENTS_ACTOR_TEST_FORCE_UNRESOLVED=1 NODE_ENV=test FLOW_AGENTS_GOAL_FIT_MODE=warn FLOW_AGENTS_GOAL_FIT_MAX_BLOCKS=100000 \
|
|
204
219
|
node "$GOAL_FIT_GATE" >"$TMPDIR_EVAL/goal-fit-legacy.out" 2>"$TMPDIR_EVAL/goal-fit-legacy.err" <<JSON
|
|
205
220
|
{"hook_event_name":"Stop","cwd":"$GOAL_FIT_REPO"}
|
|
206
221
|
JSON
|
|
@@ -216,7 +231,8 @@ fi
|
|
|
216
231
|
|
|
217
232
|
# 3b. evidence-capture.js's resolveArtifactDir -- reuses test_command_log_concurrency.sh's
|
|
218
233
|
# fixture: current.json (legacy-only) names a slug; a captured PostToolUse event must land in
|
|
219
|
-
# THAT slug's command-log.jsonl, under a
|
|
234
|
+
# THAT slug's command-log.jsonl, under a genuinely UNRESOLVED actor (#440: see 3a's comment --
|
|
235
|
+
# a RESOLVED actor with no own pointer now intentionally returns null here instead, see section 7).
|
|
220
236
|
EVIDENCE_REPO="$TMPDIR_EVAL/evidence-legacy/repo"
|
|
221
237
|
EVIDENCE_SLUG="legacy-evidence-slug"
|
|
222
238
|
mkdir -p "$EVIDENCE_REPO/.kontourai/flow-agents/$EVIDENCE_SLUG"
|
|
@@ -230,7 +246,7 @@ printf '%s' "$EVIDENCE_STATE_LINE" | safe_write "$EVIDENCE_REPO/.kontourai/flow-
|
|
|
230
246
|
|
|
231
247
|
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
248
|
printf '%s' "$EVIDENCE_EVENT_LINE" \
|
|
233
|
-
|
|
|
249
|
+
| FLOW_AGENTS_ACTOR_TEST_FORCE_UNRESOLVED=1 NODE_ENV=test node "$EVIDENCE_CAPTURE" >"$TMPDIR_EVAL/evidence-legacy.out" 2>"$TMPDIR_EVAL/evidence-legacy.err"
|
|
234
250
|
|
|
235
251
|
EVIDENCE_LOG_FILE="$EVIDENCE_REPO/.kontourai/flow-agents/$EVIDENCE_SLUG/command-log.jsonl"
|
|
236
252
|
if [[ -f "$EVIDENCE_LOG_FILE" ]] && grep -qF "legacy-actor-check" "$EVIDENCE_LOG_FILE"; then
|
|
@@ -295,7 +311,8 @@ else
|
|
|
295
311
|
fi
|
|
296
312
|
|
|
297
313
|
# 3d. workflow-steering.js's ambient state lookup -- reuses test_workflow_steering_hook.sh's
|
|
298
|
-
# steering-demo fixture/assertion verbatim, under a
|
|
314
|
+
# steering-demo fixture/assertion verbatim, under a genuinely UNRESOLVED actor (#440: see 3a's
|
|
315
|
+
# comment -- a RESOLVED actor with no own pointer now intentionally sees no banner, see section 7).
|
|
299
316
|
STEERING_REPO="$TMPDIR_EVAL/steering-legacy/repo"
|
|
300
317
|
mkdir -p "$STEERING_REPO/.kontourai/flow-agents/steering-demo"
|
|
301
318
|
mkdir -p "$STEERING_REPO/docs"
|
|
@@ -317,7 +334,7 @@ safe_write "$STEERING_REPO/.kontourai/flow-agents/steering-demo/state.json" <<'J
|
|
|
317
334
|
}
|
|
318
335
|
JSON
|
|
319
336
|
|
|
320
|
-
STEERING_OUT="$(
|
|
337
|
+
STEERING_OUT="$(FLOW_AGENTS_ACTOR_TEST_FORCE_UNRESOLVED=1 NODE_ENV=test node "$STEERING" <<JSON
|
|
321
338
|
{"hook_event_name":"UserPromptSubmit","cwd":"$STEERING_REPO","prompt":"continue"}
|
|
322
339
|
JSON
|
|
323
340
|
)"
|
|
@@ -451,16 +468,19 @@ fi
|
|
|
451
468
|
|
|
452
469
|
# --- 6. F3 (fix-plan iteration 1, code-review-291 MEDIUM): legacy current.json names a
|
|
453
470
|
# still-ACTIVE, non-newest-mtime session while a NEWER active state.json exists elsewhere under
|
|
454
|
-
# the same artifact root, under a
|
|
471
|
+
# the same artifact root, under a genuinely UNRESOLVED actor (#440: this section's fixture uses
|
|
472
|
+
# the FLOW_AGENTS_ACTOR_TEST_FORCE_UNRESOLVED escape hatch -- a RESOLVED actor with no per-actor
|
|
473
|
+
# current file no longer falls through to the legacy pointer at all, see section 7; this section
|
|
474
|
+
# is specifically about the unchanged D3/unresolved-actor compat path). Pre-#291,
|
|
455
475
|
# latestWorkflowState() was a pure global newest-mtime scan with zero current.json involvement,
|
|
456
476
|
# so the newer state.json would win. Post-#291, actorScopedWorkflowState() is consulted first and
|
|
457
|
-
# falls through to the legacy current.json for an
|
|
458
|
-
#
|
|
459
|
-
#
|
|
460
|
-
#
|
|
461
|
-
# newest-mtime) for the single-actor/
|
|
477
|
+
# falls through to the legacy current.json for an unresolved actor (the compat-shim guarantee
|
|
478
|
+
# section 3 already covers) -- but if that legacy pointer names a still-ACTIVE session, it is now
|
|
479
|
+
# returned IMMEDIATELY, without ever comparing mtimes against the newer state.json. This section
|
|
480
|
+
# test-pins that specific, deliberate behavior change (current.json preference wins over
|
|
481
|
+
# newest-mtime) for the single-actor/unresolved-actor case, not just the multi-actor case AC7/AC8
|
|
462
482
|
# are framed around -- the review's flagged, previously test-uncovered edge.
|
|
463
|
-
echo "--- 6. legacy current.json (non-newest, still-active) wins over a newer state.json elsewhere, under
|
|
483
|
+
echo "--- 6. legacy current.json (non-newest, still-active) wins over a newer state.json elsewhere, under an unresolved actor (F3, fix-plan iteration 1) ---"
|
|
464
484
|
|
|
465
485
|
F3_REPO="$TMPDIR_EVAL/f3-legacy-vs-newest/repo"
|
|
466
486
|
mkdir -p "$F3_REPO/.kontourai/flow-agents/older-named-slug"
|
|
@@ -506,20 +526,395 @@ safe_write "$F3_REPO/.kontourai/flow-agents/newer-unnamed-slug/state.json" <<'JS
|
|
|
506
526
|
}
|
|
507
527
|
JSON
|
|
508
528
|
|
|
509
|
-
# A
|
|
510
|
-
#
|
|
529
|
+
# A genuinely UNRESOLVED actor (#440: see 3a's comment -- a RESOLVED actor with no per-actor
|
|
530
|
+
# current file no longer falls through here, see section 7) -- actorScopedWorkflowState falls
|
|
531
|
+
# through to the legacy current.json (same compat-shim guarantee as section 3), which names
|
|
511
532
|
# the OLDER-but-still-active session.
|
|
512
|
-
F3_OUT="$(
|
|
533
|
+
F3_OUT="$(FLOW_AGENTS_ACTOR_TEST_FORCE_UNRESOLVED=1 NODE_ENV=test node "$ROOT/scripts/hooks/workflow-steering.js" <<JSON
|
|
513
534
|
{"hook_event_name":"UserPromptSubmit","cwd":"$F3_REPO","prompt":"continue"}
|
|
514
535
|
JSON
|
|
515
536
|
)"
|
|
516
537
|
|
|
517
538
|
if [[ "$F3_OUT" == *"older-named-slug"* && "$F3_OUT" != *"newer-unnamed-slug"* ]]; then
|
|
518
|
-
pass "workflow-steering.js prefers the legacy current.json's named ACTIVE session over a newer-mtime state.json elsewhere, under
|
|
539
|
+
pass "workflow-steering.js prefers the legacy current.json's named ACTIVE session over a newer-mtime state.json elsewhere, under an unresolved actor (F3 -- pins the post-#291 behavior change explicitly, not just AC7/AC8's multi-actor framing; #440: a resolved actor with no per-actor file no longer takes this path at all, see section 7)"
|
|
519
540
|
else
|
|
520
541
|
fail "workflow-steering.js did not prefer the legacy-named active session over the newer-mtime one: $F3_OUT"
|
|
521
542
|
fi
|
|
522
543
|
|
|
544
|
+
# --- 7. #440: two-session ownership isolation (readOwnCurrentPointer) --------------------------
|
|
545
|
+
# Distinct from section 2 (AC7, both A and B have run ensure-session): here actor A does NOT run
|
|
546
|
+
# ensure-session at all before the "not own pointer" assertions below -- the "resolved actor, no
|
|
547
|
+
# own per-actor pointer at all" case readOwnCurrentPointer's D1 rule (never fall back to the
|
|
548
|
+
# shared legacy current.json / a global scan) is specifically about. Positive controls (actor A's
|
|
549
|
+
# own gate/banner/capture keep working once A DOES have its own pointer) run last, in their own
|
|
550
|
+
# subsection, so they never contaminate the "no own pointer" preconditions the earlier assertions
|
|
551
|
+
# depend on.
|
|
552
|
+
echo "--- 7. #440: two-session ownership isolation (readOwnCurrentPointer) ---"
|
|
553
|
+
|
|
554
|
+
# This file's own header only sets `set -uo pipefail` (no errexit) -- but section 4's
|
|
555
|
+
# config-protection.js checks toggle `set -e` back ON after their own `set +e`/`set -e`
|
|
556
|
+
# bracketing and never restore the header's original (errexit-off) mode, so errexit is (latently,
|
|
557
|
+
# unintentionally) still active by the time execution reaches here. This section deliberately
|
|
558
|
+
# invokes commands expected to exit nonzero (e.g. the AC2 anti-gaming positive control, which
|
|
559
|
+
# must itself observe a block/exit!=0) via plain `VAR="$(...)"` assignments -- under errexit a
|
|
560
|
+
# nonzero-returning command substitution assignment terminates the whole script immediately, with
|
|
561
|
+
# no error message, silently truncating every assertion after it. `set +e` restores this section to
|
|
562
|
+
# the file's own declared header mode for the remainder of the file (safe: sections 1-6 above are
|
|
563
|
+
# unaffected since they already ran; nothing below this section depends on errexit being on).
|
|
564
|
+
set +e
|
|
565
|
+
|
|
566
|
+
AC440_REPO="$TMPDIR_EVAL/ac440-project"
|
|
567
|
+
AC440_ROOT="$AC440_REPO/.kontourai/flow-agents"
|
|
568
|
+
mkdir -p "$AC440_REPO/docs"
|
|
569
|
+
printf '# Test Repo\n' > "$AC440_REPO/AGENTS.md"
|
|
570
|
+
printf '# Context Map\n' > "$AC440_REPO/docs/context-map.md"
|
|
571
|
+
|
|
572
|
+
# 7.0 Setup: actor B runs ensure-session on a real Work Item (establishing B's own per-actor
|
|
573
|
+
# current/eval-actor-440-b.json AND the shared legacy current.json, exactly like section 2's
|
|
574
|
+
# AC7_ROOT setup), then B's session is mutated into a genuine, blocking evidence gap (execution
|
|
575
|
+
# phase, no trust.bundle) so the "not blocked by B" assertions below are a real differential test,
|
|
576
|
+
# not a tautology -- mirrors this file's own section 3a fixture shape (status:executing markdown +
|
|
577
|
+
# no trust.bundle) and test_stop_hook_release.sh's seed_session/write_session_state convention.
|
|
578
|
+
flow_agents_node "workflow-sidecar" ensure-session \
|
|
579
|
+
--artifact-root "$AC440_ROOT" \
|
|
580
|
+
--work-item "kontourai/flow-agents#94401" \
|
|
581
|
+
--actor eval-actor-440-b \
|
|
582
|
+
--source-request "Actor B's own subject, with a real evidence gap." \
|
|
583
|
+
--summary "Actor B session with a deliberate evidence gap." \
|
|
584
|
+
>"$TMPDIR_EVAL/ac440-b-ensure.out" 2>"$TMPDIR_EVAL/ac440-b-ensure.err"
|
|
585
|
+
AC440_B_STATUS=$?
|
|
586
|
+
|
|
587
|
+
AC440_B_SLUG="kontourai-flow-agents-94401"
|
|
588
|
+
AC440_B_DIR="$AC440_ROOT/$AC440_B_SLUG"
|
|
589
|
+
|
|
590
|
+
if [[ "$AC440_B_STATUS" -eq 0 && -d "$AC440_B_DIR" ]]; then
|
|
591
|
+
pass "setup: actor B's ensure-session establishes B's own per-actor current file and a real session dir"
|
|
592
|
+
else
|
|
593
|
+
fail "setup: actor B's ensure-session failed: $(cat "$TMPDIR_EVAL/ac440-b-ensure.out" "$TMPDIR_EVAL/ac440-b-ensure.err")"
|
|
594
|
+
fi
|
|
595
|
+
|
|
596
|
+
AC440_B_STATE_LINE="{\"schema_version\":\"1.0\",\"task_slug\":\"$AC440_B_SLUG\",\"status\":\"in_progress\",\"phase\":\"execution\",\"updated_at\":\"2026-07-13T00:00:00Z\",\"next_action\":{\"status\":\"continue\",\"summary\":\"Actor B's own unresolved evidence gap.\"}}"
|
|
597
|
+
printf '%s' "$AC440_B_STATE_LINE" | safe_write "$AC440_B_DIR/state.json"
|
|
598
|
+
|
|
599
|
+
cat > "$AC440_B_DIR/${AC440_B_SLUG}--deliver.md" <<MARKDOWN
|
|
600
|
+
# ${AC440_B_SLUG}
|
|
601
|
+
|
|
602
|
+
branch: main
|
|
603
|
+
worktree: main
|
|
604
|
+
created: 2026-07-13
|
|
605
|
+
status: executing
|
|
606
|
+
type: deliver
|
|
607
|
+
|
|
608
|
+
## Plan
|
|
609
|
+
|
|
610
|
+
Actor B's own session, deliberately left with a genuine evidence gap (no trust.bundle) so this
|
|
611
|
+
section's "actor A is not blocked by B's gap" assertions are a real differential test.
|
|
612
|
+
MARKDOWN
|
|
613
|
+
|
|
614
|
+
# 7.1 AC1 (liveness): a `claim` liveness event for B's own subject/actor already exists in the
|
|
615
|
+
# shared liveness stream (ensure-session emits one as part of its own claim). Invoking the Stop
|
|
616
|
+
# hook as actor A (who has NOT run ensure-session -- no own per-actor pointer at all) must never
|
|
617
|
+
# append a `release`/other event attributed to A's own actor string for B's subject -- readActiveSlug
|
|
618
|
+
# resolving B's slug for A would be exactly the liveness half of the bug #440 fixes.
|
|
619
|
+
AC440_LIVENESS_FILE="$AC440_ROOT/liveness/events.jsonl"
|
|
620
|
+
AC440_LIVENESS_LINES_BEFORE=0
|
|
621
|
+
[[ -f "$AC440_LIVENESS_FILE" ]] && AC440_LIVENESS_LINES_BEFORE="$(wc -l < "$AC440_LIVENESS_FILE" | tr -d ' ')"
|
|
622
|
+
|
|
623
|
+
FLOW_AGENTS_ACTOR=eval-actor-440-a FLOW_AGENTS_GOAL_FIT_MODE=warn node "$GOAL_FIT_GATE" \
|
|
624
|
+
>"$TMPDIR_EVAL/ac440-a-liveness.out" 2>"$TMPDIR_EVAL/ac440-a-liveness.err" <<JSON
|
|
625
|
+
{"hook_event_name":"Stop","cwd":"$AC440_REPO"}
|
|
626
|
+
JSON
|
|
627
|
+
|
|
628
|
+
if grep -q "\"subjectId\":\"$AC440_B_SLUG\",\"actor\":\"eval-actor-440-a\"" "$AC440_LIVENESS_FILE" 2>/dev/null; then
|
|
629
|
+
fail "AC1: actor A's Stop hook emitted a liveness event naming B's slug under A's own actor identity"
|
|
630
|
+
else
|
|
631
|
+
pass "AC1: actor A's Stop hook (no own per-actor pointer) emits no liveness event naming B's subject under A's actor identity"
|
|
632
|
+
fi
|
|
633
|
+
|
|
634
|
+
# 7.2 AC2 (not blocked by B): actor A's Stop hook, in block mode, must exit 0 and never mention
|
|
635
|
+
# B's slug -- B's own real evidence gap is informational-only from A's perspective, never blocking.
|
|
636
|
+
AC440_A_NOTBLOCKED_OUT="$(FLOW_AGENTS_ACTOR=eval-actor-440-a FLOW_AGENTS_GOAL_FIT_MODE=block node "$GOAL_FIT_GATE" 2>"$TMPDIR_EVAL/ac440-a-notblocked.err" <<JSON
|
|
637
|
+
{"hook_event_name":"Stop","cwd":"$AC440_REPO"}
|
|
638
|
+
JSON
|
|
639
|
+
)"
|
|
640
|
+
AC440_A_NOTBLOCKED_STATUS=$?
|
|
641
|
+
|
|
642
|
+
if [[ "$AC440_A_NOTBLOCKED_STATUS" -eq 0 ]]; then
|
|
643
|
+
pass "AC2: actor A's Stop hook (no own pointer, B has a real evidence gap) exits 0 -- not blocked by B's gap"
|
|
644
|
+
else
|
|
645
|
+
fail "AC2: actor A's Stop hook unexpectedly blocked (exit=$AC440_A_NOTBLOCKED_STATUS): $AC440_A_NOTBLOCKED_OUT $(cat "$TMPDIR_EVAL/ac440-a-notblocked.err")"
|
|
646
|
+
fi
|
|
647
|
+
|
|
648
|
+
if grep -qF "$AC440_B_SLUG" "$TMPDIR_EVAL/ac440-a-notblocked.err"; then
|
|
649
|
+
fail "AC2: actor A's Stop hook stderr unexpectedly named B's slug: $(cat "$TMPDIR_EVAL/ac440-a-notblocked.err")"
|
|
650
|
+
else
|
|
651
|
+
pass "AC2: actor A's Stop hook stderr never names B's slug"
|
|
652
|
+
fi
|
|
653
|
+
|
|
654
|
+
# 7.3 AC1 (banner): workflow-steering.js's SessionStart RESUME banner must never re-ground onto
|
|
655
|
+
# B's active session for actor A (no own per-actor pointer).
|
|
656
|
+
AC440_STEERING_A_OUT="$(FLOW_AGENTS_ACTOR=eval-actor-440-a node "$STEERING" <<JSON
|
|
657
|
+
{"hook_event_name":"SessionStart","cwd":"$AC440_REPO"}
|
|
658
|
+
JSON
|
|
659
|
+
)"
|
|
660
|
+
|
|
661
|
+
if [[ "$AC440_STEERING_A_OUT" != *"RESUME:"* && "$AC440_STEERING_A_OUT" != *"$AC440_B_SLUG"* ]]; then
|
|
662
|
+
pass "AC1: workflow-steering.js's SessionStart banner (actor A, no own pointer) shows no RESUME and never names B's slug"
|
|
663
|
+
else
|
|
664
|
+
fail "AC1: workflow-steering.js's SessionStart banner unexpectedly showed RESUME or B's slug for actor A: $AC440_STEERING_A_OUT"
|
|
665
|
+
fi
|
|
666
|
+
|
|
667
|
+
# 7.4 Evidence-capture (Task 2.4 coverage): a PostToolUse event captured while actor A has no own
|
|
668
|
+
# per-actor pointer must never land in B's command-log.jsonl.
|
|
669
|
+
AC440_B_LOG="$AC440_B_DIR/command-log.jsonl"
|
|
670
|
+
AC440_EVIDENCE_EVENT="{\"hook_event_name\":\"PostToolUse\",\"tool_name\":\"Bash\",\"cwd\":\"$AC440_REPO\",\"tool_input\":{\"command\":\"echo actor-a-must-not-land-in-b-log\"},\"tool_response\":{\"exitCode\":0,\"stdout\":\"ok\"}}"
|
|
671
|
+
printf '%s' "$AC440_EVIDENCE_EVENT" \
|
|
672
|
+
| FLOW_AGENTS_ACTOR=eval-actor-440-a node "$EVIDENCE_CAPTURE" >"$TMPDIR_EVAL/ac440-evidence-a.out" 2>"$TMPDIR_EVAL/ac440-evidence-a.err"
|
|
673
|
+
|
|
674
|
+
if [[ -f "$AC440_B_LOG" ]] && grep -qF "actor-a-must-not-land-in-b-log" "$AC440_B_LOG"; then
|
|
675
|
+
fail "evidence-capture.js (actor A, no own pointer) wrote A's tool activity into B's command-log.jsonl"
|
|
676
|
+
else
|
|
677
|
+
pass "evidence-capture.js (actor A, no own pointer) never writes A's tool activity into B's command-log.jsonl"
|
|
678
|
+
fi
|
|
679
|
+
|
|
680
|
+
# 7.5 Positive controls: actor A's OWN gate/banner/capture keep working exactly as before, once A
|
|
681
|
+
# has its own per-actor pointer -- the critical anti-gaming assertion (own gaps still gate; own
|
|
682
|
+
# work still steers/captures). Run last so the earlier "no own pointer" assertions above are never
|
|
683
|
+
# contaminated by A's own session existing.
|
|
684
|
+
flow_agents_node "workflow-sidecar" ensure-session \
|
|
685
|
+
--artifact-root "$AC440_ROOT" \
|
|
686
|
+
--work-item "kontourai/flow-agents#94402" \
|
|
687
|
+
--actor eval-actor-440-a \
|
|
688
|
+
--source-request "Actor A's own subject, with its own deliberate evidence gap." \
|
|
689
|
+
--summary "Actor A session with its own deliberate evidence gap." \
|
|
690
|
+
>"$TMPDIR_EVAL/ac440-a-ensure.out" 2>"$TMPDIR_EVAL/ac440-a-ensure.err"
|
|
691
|
+
AC440_A_STATUS=$?
|
|
692
|
+
|
|
693
|
+
AC440_A_SLUG="kontourai-flow-agents-94402"
|
|
694
|
+
AC440_A_DIR="$AC440_ROOT/$AC440_A_SLUG"
|
|
695
|
+
|
|
696
|
+
if [[ "$AC440_A_STATUS" -eq 0 && -d "$AC440_A_DIR" ]]; then
|
|
697
|
+
pass "positive control setup: actor A's own ensure-session establishes A's own per-actor current file"
|
|
698
|
+
else
|
|
699
|
+
fail "positive control setup: actor A's own ensure-session failed: $(cat "$TMPDIR_EVAL/ac440-a-ensure.out" "$TMPDIR_EVAL/ac440-a-ensure.err")"
|
|
700
|
+
fi
|
|
701
|
+
|
|
702
|
+
AC440_A_STATE_LINE="{\"schema_version\":\"1.0\",\"task_slug\":\"$AC440_A_SLUG\",\"status\":\"in_progress\",\"phase\":\"execution\",\"updated_at\":\"2026-07-13T00:00:00Z\",\"next_action\":{\"status\":\"continue\",\"summary\":\"Actor A's own unresolved evidence gap.\"}}"
|
|
703
|
+
printf '%s' "$AC440_A_STATE_LINE" | safe_write "$AC440_A_DIR/state.json"
|
|
704
|
+
|
|
705
|
+
cat > "$AC440_A_DIR/${AC440_A_SLUG}--deliver.md" <<MARKDOWN
|
|
706
|
+
# ${AC440_A_SLUG}
|
|
707
|
+
|
|
708
|
+
branch: main
|
|
709
|
+
worktree: main
|
|
710
|
+
created: 2026-07-13
|
|
711
|
+
status: executing
|
|
712
|
+
type: deliver
|
|
713
|
+
|
|
714
|
+
## Plan
|
|
715
|
+
|
|
716
|
+
Actor A's own session, deliberately left with its own genuine evidence gap (no trust.bundle) --
|
|
717
|
+
own-work gating must remain unaffected by the #440 fix (anti-gaming guarantee).
|
|
718
|
+
MARKDOWN
|
|
719
|
+
|
|
720
|
+
AC440_A_BLOCKED_OUT="$(FLOW_AGENTS_ACTOR=eval-actor-440-a FLOW_AGENTS_GOAL_FIT_MODE=block node "$GOAL_FIT_GATE" 2>"$TMPDIR_EVAL/ac440-a-blocked.err" <<JSON
|
|
721
|
+
{"hook_event_name":"Stop","cwd":"$AC440_REPO"}
|
|
722
|
+
JSON
|
|
723
|
+
)"
|
|
724
|
+
AC440_A_BLOCKED_STATUS=$?
|
|
725
|
+
|
|
726
|
+
if [[ "$AC440_A_BLOCKED_STATUS" -ne 0 ]]; then
|
|
727
|
+
pass "AC2 (anti-gaming): actor A's Stop hook STILL blocks on A's own evidence gap once A has its own per-actor pointer"
|
|
728
|
+
else
|
|
729
|
+
fail "AC2 (anti-gaming): actor A's Stop hook unexpectedly did NOT block on A's own evidence gap: $AC440_A_BLOCKED_OUT $(cat "$TMPDIR_EVAL/ac440-a-blocked.err")"
|
|
730
|
+
fi
|
|
731
|
+
|
|
732
|
+
AC440_STEERING_A_OWN_OUT="$(FLOW_AGENTS_ACTOR=eval-actor-440-a node "$STEERING" <<JSON
|
|
733
|
+
{"hook_event_name":"SessionStart","cwd":"$AC440_REPO"}
|
|
734
|
+
JSON
|
|
735
|
+
)"
|
|
736
|
+
|
|
737
|
+
if [[ "$AC440_STEERING_A_OWN_OUT" == *"RESUME: $AC440_A_SLUG"* ]]; then
|
|
738
|
+
pass "AC1 (positive control): workflow-steering.js's SessionStart banner shows RESUME: <A's own slug> once A has its own per-actor pointer"
|
|
739
|
+
else
|
|
740
|
+
fail "AC1 (positive control): workflow-steering.js's SessionStart banner did not show RESUME: $AC440_A_SLUG: $AC440_STEERING_A_OWN_OUT"
|
|
741
|
+
fi
|
|
742
|
+
|
|
743
|
+
AC440_A_LOG="$AC440_A_DIR/command-log.jsonl"
|
|
744
|
+
AC440_EVIDENCE_EVENT_OWN="{\"hook_event_name\":\"PostToolUse\",\"tool_name\":\"Bash\",\"cwd\":\"$AC440_REPO\",\"tool_input\":{\"command\":\"echo actor-a-own-capture-check\"},\"tool_response\":{\"exitCode\":0,\"stdout\":\"ok\"}}"
|
|
745
|
+
printf '%s' "$AC440_EVIDENCE_EVENT_OWN" \
|
|
746
|
+
| FLOW_AGENTS_ACTOR=eval-actor-440-a node "$EVIDENCE_CAPTURE" >"$TMPDIR_EVAL/ac440-evidence-a-own.out" 2>"$TMPDIR_EVAL/ac440-evidence-a-own.err"
|
|
747
|
+
|
|
748
|
+
if [[ -f "$AC440_A_LOG" ]] && grep -qF "actor-a-own-capture-check" "$AC440_A_LOG"; then
|
|
749
|
+
pass "evidence-capture.js (positive control): actor A's own capture still works once A has its own per-actor pointer"
|
|
750
|
+
else
|
|
751
|
+
fail "evidence-capture.js (positive control): actor A's own capture did not work: $(cat "$TMPDIR_EVAL/ac440-evidence-a-own.out" "$TMPDIR_EVAL/ac440-evidence-a-own.err")"
|
|
752
|
+
fi
|
|
753
|
+
|
|
754
|
+
# 7.6 FIX 1 (HIGH, collision -- independent review): two distinct resolved actor keys sharing a
|
|
755
|
+
# >=64-char common sanitized prefix (differing only in a tail the pre-#440-fix-wave-2 64-char
|
|
756
|
+
# truncation would have discarded) must map to DISTINCT per-actor pointer files, and each actor's
|
|
757
|
+
# OWN read must resolve its OWN pointer -- never the other's -- proving neither session grounds
|
|
758
|
+
# onto the other. Reproduces the exact collision the review found:
|
|
759
|
+
# perActorCurrentFile(dir, A) === perActorCurrentFile(dir, B) under the pre-fix mapping.
|
|
760
|
+
AC440_COLLIDE_DIR="$TMPDIR_EVAL/collide-root/.kontourai/flow-agents"
|
|
761
|
+
if CP_HELPER_ARG="$CURRENT_POINTER_HELPER" DIR_ARG="$AC440_COLLIDE_DIR" node - <<'NODE' 2>"$TMPDIR_EVAL/ac440-collide.err"
|
|
762
|
+
const { perActorCurrentFile, writePerActorCurrent, readOwnCurrentPointer } = require(process.env.CP_HELPER_ARG);
|
|
763
|
+
const dir = process.env.DIR_ARG;
|
|
764
|
+
const prefix = 'claude-code:' + 's'.repeat(52);
|
|
765
|
+
const keyA = prefix + ':host-a';
|
|
766
|
+
const keyB = prefix + ':host-b';
|
|
767
|
+
|
|
768
|
+
const fileA = perActorCurrentFile(dir, keyA);
|
|
769
|
+
const fileB = perActorCurrentFile(dir, keyB);
|
|
770
|
+
if (fileA === fileB) {
|
|
771
|
+
console.error(`FILES_COLLIDE: ${fileA}`);
|
|
772
|
+
process.exit(1);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
writePerActorCurrent(dir, keyA, { active_slug: 'collide-a-slug' });
|
|
776
|
+
writePerActorCurrent(dir, keyB, { active_slug: 'collide-b-slug' });
|
|
777
|
+
|
|
778
|
+
const resultA = readOwnCurrentPointer(dir, keyA);
|
|
779
|
+
const resultB = readOwnCurrentPointer(dir, keyB);
|
|
780
|
+
if (!resultA.payload || resultA.payload.active_slug !== 'collide-a-slug') {
|
|
781
|
+
console.error(`ACTOR_A_WRONG_SLUG: ${JSON.stringify(resultA)}`);
|
|
782
|
+
process.exit(1);
|
|
783
|
+
}
|
|
784
|
+
if (!resultB.payload || resultB.payload.active_slug !== 'collide-b-slug') {
|
|
785
|
+
console.error(`ACTOR_B_WRONG_SLUG: ${JSON.stringify(resultB)}`);
|
|
786
|
+
process.exit(1);
|
|
787
|
+
}
|
|
788
|
+
NODE
|
|
789
|
+
then
|
|
790
|
+
pass "FIX 1: two actor keys sharing a >=64-char common sanitized prefix map to DISTINCT per-actor pointer files, and each actor's own read resolves its own session, never the other's (collision-resistant naming)"
|
|
791
|
+
else
|
|
792
|
+
fail "FIX 1: colliding actor keys did not map to distinct files / grounded onto each other's session: $(cat "$TMPDIR_EVAL/ac440-collide.err")"
|
|
793
|
+
fi
|
|
794
|
+
|
|
795
|
+
# 7.7 FIX 1 (legacy-name fallback -- independent review): a pointer written under the PRE-fix-wave-2
|
|
796
|
+
# filename scheme (legacyPerActorCurrentFile -- sanitizeSegment(actorKey) alone, no hash) with NO
|
|
797
|
+
# new-scheme file present must still resolve for its owning actor -- the transition-window compat
|
|
798
|
+
# guarantee (a still-running published-3.9.0-era session's pointer keeps resolving).
|
|
799
|
+
if CP_HELPER_ARG="$CURRENT_POINTER_HELPER" DIR_ARG="$AC440_COLLIDE_DIR" node - <<'NODE' 2>"$TMPDIR_EVAL/ac440-legacy-fallback.err"
|
|
800
|
+
const fs = require('fs');
|
|
801
|
+
const path = require('path');
|
|
802
|
+
const { legacyPerActorCurrentFile, perActorCurrentFile, readOwnCurrentPointer } = require(process.env.CP_HELPER_ARG);
|
|
803
|
+
const dir = process.env.DIR_ARG;
|
|
804
|
+
const actorKey = 'eval-actor-440-legacy-fallback';
|
|
805
|
+
|
|
806
|
+
const newFile = perActorCurrentFile(dir, actorKey);
|
|
807
|
+
if (fs.existsSync(newFile)) { console.error(`NEW_FILE_UNEXPECTEDLY_EXISTS: ${newFile}`); process.exit(1); }
|
|
808
|
+
|
|
809
|
+
const legacyFile = legacyPerActorCurrentFile(dir, actorKey);
|
|
810
|
+
fs.mkdirSync(path.dirname(legacyFile), { recursive: true });
|
|
811
|
+
fs.writeFileSync(legacyFile, JSON.stringify({ active_slug: 'legacy-fallback-slug' }));
|
|
812
|
+
|
|
813
|
+
const result = readOwnCurrentPointer(dir, actorKey);
|
|
814
|
+
if (!result.payload || result.payload.active_slug !== 'legacy-fallback-slug') {
|
|
815
|
+
console.error(`FALLBACK_DID_NOT_RESOLVE: ${JSON.stringify(result)}`);
|
|
816
|
+
process.exit(1);
|
|
817
|
+
}
|
|
818
|
+
if (result.file !== legacyFile) {
|
|
819
|
+
console.error(`FALLBACK_FILE_MISMATCH: got ${result.file} expected ${legacyFile}`);
|
|
820
|
+
process.exit(1);
|
|
821
|
+
}
|
|
822
|
+
NODE
|
|
823
|
+
then
|
|
824
|
+
pass "FIX 1: a pointer written under the pre-fix-wave-2 legacy filename (no new-scheme file present) still resolves for its owning actor (transition-window fallback)"
|
|
825
|
+
else
|
|
826
|
+
fail "FIX 1: legacy-filename fallback did not resolve as expected: $(cat "$TMPDIR_EVAL/ac440-legacy-fallback.err")"
|
|
827
|
+
fi
|
|
828
|
+
|
|
829
|
+
# 7.8 FINDING 1 (MED, independent review delta): record-agent-event's updateCurrentAgent must
|
|
830
|
+
# migrate a LEGACY-only per-actor pointer (as a still-running published-3.9.0-era sidecar would
|
|
831
|
+
# have written, pre-fix-wave-2) to the new collision-resistant filename on first touch, applying
|
|
832
|
+
# the active_agents/updated_at projection -- not silently skip that projection because it only
|
|
833
|
+
# read the new filename directly (fs.existsSync/readFileSync on perActorCurrentFile() alone,
|
|
834
|
+
# never falling back to legacyPerActorCurrentFile()).
|
|
835
|
+
AC440_MIGRATE_ROOT="$TMPDIR_EVAL/ac440-migrate-project/.kontourai/flow-agents"
|
|
836
|
+
AC440_MIGRATE_ACTOR="eval-actor-440-legacy-migrate"
|
|
837
|
+
AC440_MIGRATE_AGENT_ID="tool-worker"
|
|
838
|
+
|
|
839
|
+
flow_agents_node "workflow-sidecar" ensure-session \
|
|
840
|
+
--artifact-root "$AC440_MIGRATE_ROOT" \
|
|
841
|
+
--work-item "kontourai/flow-agents#94405" \
|
|
842
|
+
--actor "$AC440_MIGRATE_ACTOR" \
|
|
843
|
+
--source-request "Actor establishing a session whose pointer will be downgraded to legacy-only." \
|
|
844
|
+
--summary "Legacy-pointer migration regression session." \
|
|
845
|
+
>"$TMPDIR_EVAL/ac440-migrate-ensure.out" 2>"$TMPDIR_EVAL/ac440-migrate-ensure.err"
|
|
846
|
+
AC440_MIGRATE_SLUG="kontourai-flow-agents-94405"
|
|
847
|
+
AC440_MIGRATE_DIR="$AC440_MIGRATE_ROOT/$AC440_MIGRATE_SLUG"
|
|
848
|
+
|
|
849
|
+
# Downgrade the per-actor pointer to LEGACY-ONLY: capture the payload ensure-session wrote under
|
|
850
|
+
# the NEW filename, delete the new file, and re-write the SAME payload under the pre-fix-wave-2
|
|
851
|
+
# legacy filename -- reproducing exactly what a pointer a still-running published-3.9.0 sidecar
|
|
852
|
+
# (pre-fix-wave-2) would have on disk.
|
|
853
|
+
if CP_HELPER_ARG="$CURRENT_POINTER_HELPER" DIR_ARG="$AC440_MIGRATE_ROOT" ACTOR_ARG="$AC440_MIGRATE_ACTOR" node - <<'NODE' 2>"$TMPDIR_EVAL/ac440-migrate-downgrade.err"
|
|
854
|
+
const fs = require('fs');
|
|
855
|
+
const { perActorCurrentFile, legacyPerActorCurrentFile } = require(process.env.CP_HELPER_ARG);
|
|
856
|
+
const dir = process.env.DIR_ARG;
|
|
857
|
+
const actorKey = process.env.ACTOR_ARG;
|
|
858
|
+
const newFile = perActorCurrentFile(dir, actorKey);
|
|
859
|
+
if (!fs.existsSync(newFile)) { console.error(`NEW_FILE_MISSING: ${newFile}`); process.exit(1); }
|
|
860
|
+
const payload = fs.readFileSync(newFile, 'utf8');
|
|
861
|
+
fs.unlinkSync(newFile);
|
|
862
|
+
const legacyFile = legacyPerActorCurrentFile(dir, actorKey);
|
|
863
|
+
fs.writeFileSync(legacyFile, payload);
|
|
864
|
+
NODE
|
|
865
|
+
then
|
|
866
|
+
pass "setup: actor's per-actor pointer downgraded to legacy-only (new-name file removed, same payload re-written under the pre-fix-wave-2 legacy filename)"
|
|
867
|
+
else
|
|
868
|
+
fail "setup: failed to downgrade the per-actor pointer to legacy-only: $(cat "$TMPDIR_EVAL/ac440-migrate-downgrade.err")"
|
|
869
|
+
fi
|
|
870
|
+
|
|
871
|
+
flow_agents_node "workflow-sidecar" record-agent-event \
|
|
872
|
+
--artifact-root "$AC440_MIGRATE_ROOT" \
|
|
873
|
+
--actor "$AC440_MIGRATE_ACTOR" \
|
|
874
|
+
--agent-id "$AC440_MIGRATE_AGENT_ID" \
|
|
875
|
+
--kind note \
|
|
876
|
+
--status active \
|
|
877
|
+
--summary "legacy-pointer migration regression probe" \
|
|
878
|
+
>"$TMPDIR_EVAL/ac440-migrate-record.out" 2>"$TMPDIR_EVAL/ac440-migrate-record.err"
|
|
879
|
+
AC440_MIGRATE_RECORD_STATUS=$?
|
|
880
|
+
|
|
881
|
+
# (source-tree legacy-ref scan note: the agent-events relative path below is built through a
|
|
882
|
+
# variable, not a literal contiguous "agents/" + agent-id token, matching
|
|
883
|
+
# test_model_routing_escalation.sh's own path.join(sdir, "agents", agent, "events.jsonl")
|
|
884
|
+
# convention -- a literal contiguous "agents/<agent-id>/..."-shaped string in an eval file is
|
|
885
|
+
# flagged by validate-source-tree.ts's legacy-ref scanner as a possible stale repo-path
|
|
886
|
+
# reference.)
|
|
887
|
+
if [[ "$AC440_MIGRATE_RECORD_STATUS" -eq 0 ]] && [[ -f "$AC440_MIGRATE_DIR/agents/$AC440_MIGRATE_AGENT_ID/events.jsonl" ]]; then
|
|
888
|
+
pass "FINDING 1: record-agent-event succeeds and records the agent event against a legacy-only per-actor pointer"
|
|
889
|
+
else
|
|
890
|
+
fail "FINDING 1: record-agent-event did not succeed/record against a legacy-only pointer: status=$AC440_MIGRATE_RECORD_STATUS $(cat "$TMPDIR_EVAL/ac440-migrate-record.out" "$TMPDIR_EVAL/ac440-migrate-record.err")"
|
|
891
|
+
fi
|
|
892
|
+
|
|
893
|
+
# The CORE regression: updateCurrentAgent must have migrated the pointer to the NEW filename with
|
|
894
|
+
# the active_agents projection applied -- not silently skipped it because it only checked the new
|
|
895
|
+
# (at that point, absent) filename directly.
|
|
896
|
+
if CP_HELPER_ARG="$CURRENT_POINTER_HELPER" DIR_ARG="$AC440_MIGRATE_ROOT" ACTOR_ARG="$AC440_MIGRATE_ACTOR" node - <<'NODE' 2>"$TMPDIR_EVAL/ac440-migrate-verify.err"
|
|
897
|
+
const fs = require('fs');
|
|
898
|
+
const { perActorCurrentFile } = require(process.env.CP_HELPER_ARG);
|
|
899
|
+
const dir = process.env.DIR_ARG;
|
|
900
|
+
const actorKey = process.env.ACTOR_ARG;
|
|
901
|
+
const newFile = perActorCurrentFile(dir, actorKey);
|
|
902
|
+
if (!fs.existsSync(newFile)) { console.error(`NEW_FILE_STILL_MISSING_AFTER_RECORD_AGENT_EVENT: ${newFile}`); process.exit(1); }
|
|
903
|
+
const payload = JSON.parse(fs.readFileSync(newFile, 'utf8'));
|
|
904
|
+
const active = Array.isArray(payload.active_agents) ? payload.active_agents : [];
|
|
905
|
+
const entry = active.find((a) => a && a.agent_id === 'tool-worker');
|
|
906
|
+
if (!entry || entry.status !== 'active') {
|
|
907
|
+
console.error(`ACTIVE_AGENTS_PROJECTION_MISSING: ${JSON.stringify(payload)}`);
|
|
908
|
+
process.exit(1);
|
|
909
|
+
}
|
|
910
|
+
NODE
|
|
911
|
+
then
|
|
912
|
+
pass "FINDING 1: record-agent-event migrates a legacy-only per-actor pointer to the new collision-resistant filename on first touch, with the active_agents projection correctly applied (not silently skipped)"
|
|
913
|
+
else
|
|
914
|
+
fail "FINDING 1: record-agent-event did not migrate/update the per-actor pointer as expected: $(cat "$TMPDIR_EVAL/ac440-migrate-verify.err")"
|
|
915
|
+
fi
|
|
916
|
+
|
|
917
|
+
|
|
523
918
|
echo ""
|
|
524
919
|
if [[ "$errors" -eq 0 ]]; then
|
|
525
920
|
echo "test_current_json_per_actor: all checks passed."
|
|
@@ -19,6 +19,17 @@
|
|
|
19
19
|
set -uo pipefail
|
|
20
20
|
|
|
21
21
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
22
|
+
|
|
23
|
+
# #440 FIXTURE-GAP: this suite's fixtures were written before #440's per-actor ownership scoping
|
|
24
|
+
# and never establish a per-actor current pointer for the invoking actor -- under a RESOLVED
|
|
25
|
+
# ambient actor (ancestry-derived locally, GITHUB_RUN_ID-derived CI-runtime in CI), stop-goal-fit.js's
|
|
26
|
+
# analyze() now scopes to that actor's own (nonexistent) pointer and never reaches the
|
|
27
|
+
# fixture-under-test at all. This suite is about anti-gaming/gate mechanics, not #440's ownership
|
|
28
|
+
# scoping, so forcing the documented test-only unresolved-actor escape hatch restores EXACTLY this
|
|
29
|
+
# suite's pre-#440 behavior (D3 compat: an unresolved actor keeps the unchanged legacy-fallback/
|
|
30
|
+
# global-scan discovery every assertion below was written against).
|
|
31
|
+
export FLOW_AGENTS_ACTOR_TEST_FORCE_UNRESOLVED=1
|
|
32
|
+
export NODE_ENV=test
|
|
22
33
|
GATE="$ROOT/scripts/hooks/stop-goal-fit.js"
|
|
23
34
|
|
|
24
35
|
export FLOW_AGENTS_GOAL_FIT_MAX_BLOCKS=100000
|
|
@@ -427,6 +427,45 @@ fi
|
|
|
427
427
|
[[ "$(json_query "$TMPDIR_EVAL/status-race.json" "assignment.assignee")" == *"$EXPECTED_RACE_HOLDER"* ]] && pass "on-disk record holder matches the process that actually exited 0 (no silent overwrite by the loser) (AC6)" || fail "on-disk record holder does not match the winning process: $(cat "$TMPDIR_EVAL/status-race.json")"
|
|
428
428
|
|
|
429
429
|
echo ""
|
|
430
|
+
|
|
431
|
+
# 7. #554: ensure-session consumes the helper-owned safe struct and re-enters as self.
|
|
432
|
+
CODEX_PRIVATE_ROOT="$TMPDIR_EVAL/codex-private-root"
|
|
433
|
+
CODEX_PRIVATE_RAW='ENSURE-PRIVATE-SENTINEL:thread/value'
|
|
434
|
+
CODEX_PRIVATE_WORK="kontourai/flow-agents#9554"
|
|
435
|
+
CODEX_PRIVATE_SLUG="kontourai-flow-agents-9554"
|
|
436
|
+
if CODEX_THREAD_ID="$CODEX_PRIVATE_RAW" CODEX_SESSION_ID= flow_agents_node "workflow-sidecar" ensure-session \
|
|
437
|
+
--artifact-root "$CODEX_PRIVATE_ROOT" --work-item "$CODEX_PRIVATE_WORK" \
|
|
438
|
+
--source-request "Canonical Codex identity fixture." --summary "Privacy-safe actor fixture." \
|
|
439
|
+
>"$TMPDIR_EVAL/codex-private-first.out" 2>"$TMPDIR_EVAL/codex-private-first.err"; then
|
|
440
|
+
pass "ensure-session succeeds with CODEX_THREAD_ID"
|
|
441
|
+
else
|
|
442
|
+
fail "ensure-session failed with CODEX_THREAD_ID: $(cat "$TMPDIR_EVAL/codex-private-first.out" "$TMPDIR_EVAL/codex-private-first.err")"
|
|
443
|
+
fi
|
|
444
|
+
CODEX_PRIVATE_RECORD="$CODEX_PRIVATE_ROOT/assignment/$CODEX_PRIVATE_SLUG.json"
|
|
445
|
+
if node - "$CODEX_PRIVATE_RECORD" "$ROOT/scripts/hooks/lib/actor-identity.js" <<'NODE'
|
|
446
|
+
const fs = require('fs');
|
|
447
|
+
const record = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
|
448
|
+
const { serializeActor } = require(process.argv[3]);
|
|
449
|
+
if (record.actor.runtime !== 'codex' || !/^thread-[a-f0-9]{24}$/.test(record.actor.session_id)) throw new Error('unsafe actor struct');
|
|
450
|
+
if (record.actor_key !== serializeActor(record.actor)) throw new Error('key/struct mismatch');
|
|
451
|
+
NODE
|
|
452
|
+
then pass "ensure-session persists a canonical Codex actor struct whose serialization equals actor_key"
|
|
453
|
+
else fail "ensure-session persisted a divergent or unsafe Codex actor struct/key"
|
|
454
|
+
fi
|
|
455
|
+
if rg -qF 'ENSURE-PRIVATE-SENTINEL' "$CODEX_PRIVATE_ROOT" "$TMPDIR_EVAL/codex-private-first.out" "$TMPDIR_EVAL/codex-private-first.err"; then
|
|
456
|
+
fail "raw CODEX_THREAD_ID sentinel leaked into ensure-session artifacts or output"
|
|
457
|
+
else
|
|
458
|
+
pass "raw CODEX_THREAD_ID sentinel is absent from the complete ensure-session artifact/output set"
|
|
459
|
+
fi
|
|
460
|
+
if CODEX_THREAD_ID="$CODEX_PRIVATE_RAW" CODEX_SESSION_ID= flow_agents_node "workflow-sidecar" ensure-session \
|
|
461
|
+
--artifact-root "$CODEX_PRIVATE_ROOT" --work-item "$CODEX_PRIVATE_WORK" \
|
|
462
|
+
--source-request "Canonical Codex identity fixture reentry." --summary "Privacy-safe actor fixture reentry." \
|
|
463
|
+
>"$TMPDIR_EVAL/codex-private-second.out" 2>"$TMPDIR_EVAL/codex-private-second.err"; then
|
|
464
|
+
pass "second ensure-session recognizes the first canonical Codex claim as self"
|
|
465
|
+
else
|
|
466
|
+
fail "second ensure-session did not recognize the canonical Codex claim as self: $(cat "$TMPDIR_EVAL/codex-private-second.out" "$TMPDIR_EVAL/codex-private-second.err")"
|
|
467
|
+
fi
|
|
468
|
+
|
|
430
469
|
if [[ "$errors" -eq 0 ]]; then
|
|
431
470
|
echo "test_ensure_session_ownership_guard: all checks passed."
|
|
432
471
|
else
|