@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.
Files changed (106) hide show
  1. package/.github/workflows/ci.yml +8 -0
  2. package/CHANGELOG.md +17 -0
  3. package/README.md +1 -1
  4. package/build/src/cli/assignment-provider.js +4 -18
  5. package/build/src/cli/continuation-adapter.d.ts +10 -8
  6. package/build/src/cli/continuation-adapter.js +22 -4
  7. package/build/src/cli/validate-workflow-artifacts.js +21 -1
  8. package/build/src/cli/workflow-sidecar.js +15 -20
  9. package/build/src/cli/workflow.js +58 -11
  10. package/build/src/continuation-driver.d.ts +27 -3
  11. package/build/src/continuation-driver.js +97 -14
  12. package/build/src/index.d.ts +2 -2
  13. package/build/src/index.js +1 -1
  14. package/build/src/tools/build-universal-bundles.js +52 -25
  15. package/build/src/tools/validate-source-tree.js +5 -2
  16. package/context/scripts/hooks/lib/continuation-turn-authority.js +386 -0
  17. package/context/scripts/hooks/stop-goal-fit.js +244 -73
  18. package/context/scripts/hooks/workflow-steering.js +24 -15
  19. package/context/scripts/telemetry/lib/pricing.sh +18 -6
  20. package/context/scripts/telemetry/lib/transport.sh +139 -37
  21. package/context/scripts/telemetry/lib/usage.sh +282 -32
  22. package/context/scripts/telemetry/lib/usage_model_guard.sh +42 -0
  23. package/context/scripts/telemetry/telemetry.sh +76 -1
  24. package/docs/coordination-guide.md +21 -5
  25. package/docs/fixture-ownership.md +2 -1
  26. package/docs/public-workflow-cli.md +44 -0
  27. package/docs/spec/runtime-hook-surface.md +8 -4
  28. package/evals/acceptance/prove-capture-teeth-declared.sh +13 -0
  29. package/evals/acceptance/prove-capture-teeth.sh +13 -0
  30. package/evals/ci/run-baseline.sh +2 -0
  31. package/evals/fixtures/codex-legacy-agents/5273878130bdafc8a024a650bb5b66c9b003f1f859b5dc6e5b588cbf4ab23228.md +25 -0
  32. package/evals/fixtures/telemetry/usage-transcript-adversarial.jsonl +2 -0
  33. package/evals/fixtures/telemetry/usage-transcript-malformed-sandwich.jsonl +3 -0
  34. package/evals/fixtures/telemetry/usage-transcript-negative-tokens.jsonl +1 -0
  35. package/evals/fixtures/telemetry/usage-transcript-oversized-prefix.jsonl +201 -0
  36. package/evals/fixtures/telemetry/usage-transcript-oversized-tokens.jsonl +1 -0
  37. package/evals/fixtures/telemetry/usage-transcript-prefixed-secret-lower.jsonl +1 -0
  38. package/evals/fixtures/telemetry/usage-transcript-prefixed-secret-upper.jsonl +1 -0
  39. package/evals/fixtures/telemetry/usage-transcript-secret-model-jwt.jsonl +1 -0
  40. package/evals/fixtures/telemetry/usage-transcript-secret-model.jsonl +1 -0
  41. package/evals/fixtures/telemetry/usage-transcript-torn-trailing.jsonl +2 -0
  42. package/evals/integration/test_actor_identity.sh +63 -0
  43. package/evals/integration/test_assignment_provider_local_file.sh +34 -0
  44. package/evals/integration/test_builder_entry_enforcement.sh +5 -1
  45. package/evals/integration/test_bundle_install.sh +106 -17
  46. package/evals/integration/test_bundle_lifecycle.sh +17 -3
  47. package/evals/integration/test_captured_fail_reconciliation.sh +11 -0
  48. package/evals/integration/test_ci_actor_identity.sh +2 -2
  49. package/evals/integration/test_command_log_concurrency.sh +11 -0
  50. package/evals/integration/test_command_log_fork_classification.sh +11 -0
  51. package/evals/integration/test_command_log_integrity.sh +11 -0
  52. package/evals/integration/test_current_json_per_actor.sh +416 -21
  53. package/evals/integration/test_enforcer_expects_driven.sh +11 -0
  54. package/evals/integration/test_ensure_session_ownership_guard.sh +39 -0
  55. package/evals/integration/test_evidence_capture_hook.sh +25 -6
  56. package/evals/integration/test_fixture_retirement_audit.sh +2 -2
  57. package/evals/integration/test_flowdef_union_floor_regression.sh +11 -0
  58. package/evals/integration/test_gate_bypass_chain.sh +11 -0
  59. package/evals/integration/test_gate_lockdown.sh +10 -0
  60. package/evals/integration/test_goal_fit_ghost_session.sh +11 -0
  61. package/evals/integration/test_goal_fit_hook.sh +257 -9
  62. package/evals/integration/test_goal_fit_rederive.sh +11 -0
  63. package/evals/integration/test_install_merge.sh +141 -3
  64. package/evals/integration/test_liveness_conflict_injection.sh +39 -3
  65. package/evals/integration/test_liveness_heartbeat.sh +46 -9
  66. package/evals/integration/test_phase_map_and_gate_claim.sh +10 -1
  67. package/evals/integration/test_public_workflow_cli.sh +6 -6
  68. package/evals/integration/test_session_resume_roundtrip.sh +30 -0
  69. package/evals/integration/test_stop_hook_release.sh +111 -11
  70. package/evals/integration/test_telemetry_tool_usage.sh +394 -0
  71. package/evals/integration/test_verify_hold.sh +64 -1
  72. package/evals/integration/test_workflow_sidecar_writer.sh +10 -1
  73. package/evals/integration/test_workflow_steering_hook.sh +20 -4
  74. package/evals/run.sh +2 -0
  75. package/evals/static/test_universal_bundles.sh +123 -0
  76. package/package.json +1 -1
  77. package/packaging/README.md +21 -0
  78. package/packaging/codex-legacy-agents-fingerprints.json +33 -0
  79. package/scripts/README.md +2 -1
  80. package/scripts/audit-codex-legacy-agents.js +43 -0
  81. package/scripts/classify-codex-legacy-agents.js +71 -0
  82. package/scripts/hooks/evidence-capture.js +18 -6
  83. package/scripts/hooks/lib/actor-identity.js +62 -29
  84. package/scripts/hooks/lib/continuation-turn-authority.js +386 -0
  85. package/scripts/hooks/lib/current-pointer.js +114 -12
  86. package/scripts/hooks/lib/liveness-heartbeat.js +15 -12
  87. package/scripts/hooks/stop-goal-fit.js +244 -73
  88. package/scripts/hooks/workflow-steering.js +24 -15
  89. package/scripts/install-codex-home.sh +8 -7
  90. package/scripts/telemetry/lib/pricing.sh +18 -6
  91. package/scripts/telemetry/lib/transport.sh +71 -0
  92. package/scripts/telemetry/lib/usage.sh +282 -32
  93. package/scripts/telemetry/lib/usage_model_guard.sh +42 -0
  94. package/scripts/telemetry/telemetry.sh +76 -1
  95. package/src/cli/assignment-provider.ts +5 -26
  96. package/src/cli/builder-flow-runtime.test.mjs +93 -6
  97. package/src/cli/continuation-adapter.ts +33 -7
  98. package/src/cli/continuation-driver.test.mjs +741 -2
  99. package/src/cli/public-api.test.mjs +1 -0
  100. package/src/cli/validate-workflow-artifacts.ts +19 -1
  101. package/src/cli/workflow-sidecar.ts +18 -23
  102. package/src/cli/workflow.ts +71 -11
  103. package/src/continuation-driver.ts +125 -19
  104. package/src/index.ts +4 -0
  105. package/src/tools/build-universal-bundles.ts +61 -25
  106. package/src/tools/validate-source-tree.ts +5 -2
@@ -1169,7 +1169,11 @@ EOF_CFG
1169
1169
 
1170
1170
  cat > "$CH4_DEST/AGENTS.md" << 'EOF_AGENTS'
1171
1171
  # User Codex Instructions
1172
+
1173
+ Arbitrary user bytes must survive exactly.
1174
+ Unicode: café / 雪
1172
1175
  EOF_AGENTS
1176
+ CH4_AGENTS_BEFORE="$(shasum -a 256 "$CH4_DEST/AGENTS.md" | awk '{print $1}')"
1173
1177
 
1174
1178
  cat > "$CH4_DEST/hooks.json" << 'JSON'
1175
1179
  {
@@ -1213,7 +1217,8 @@ CODEX_REAL_HOME="$TMPDIR_EVAL/fake-real-codex" FLOW_AGENTS_SKILLS_DIR="$CH4_SKIL
1213
1217
 
1214
1218
  if grep -q 'user-model' "$CH4_DEST/config.toml" \
1215
1219
  && grep -q 'user-profile-model' "$CH4_DEST/custom.config.toml" \
1216
- && grep -q 'User Codex Instructions' "$CH4_DEST/AGENTS.md"; then
1220
+ && grep -q 'User Codex Instructions' "$CH4_DEST/AGENTS.md" \
1221
+ && [[ "$CH4_AGENTS_BEFORE" == "$(shasum -a 256 "$CH4_DEST/AGENTS.md" | awk '{print $1}')" ]]; then
1217
1222
  _pass "CH4: user-owned config.toml, profile, and AGENTS.md preserved"
1218
1223
  else
1219
1224
  _fail "CH4: user-owned config.toml, profile, or AGENTS.md was overwritten"
@@ -1308,6 +1313,22 @@ for CH5_REL in scripts kits hooks.json; do
1308
1313
  fi
1309
1314
  done
1310
1315
 
1316
+ CH8_FIXTURES="$ROOT_DIR/evals/fixtures/codex-legacy-agents"
1317
+ CH8_PREFLIGHT="$TMPDIR_EVAL/codex-home-ch8-later-preflight"
1318
+ mkdir -p "$CH8_PREFLIGHT/scripts"
1319
+ cp "$CH8_FIXTURES/5273878130bdafc8a024a650bb5b66c9b003f1f859b5dc6e5b588cbf4ab23228.md" "$CH8_PREFLIGHT/AGENTS.md"
1320
+ printf 'unowned collision\n' > "$CH8_PREFLIGHT/scripts/kit.js"
1321
+ CH8_PREFLIGHT_BEFORE="$(shasum -a 256 "$CH8_PREFLIGHT/AGENTS.md" | awk '{print $1}')"
1322
+ if CODEX_REAL_HOME="$TMPDIR_EVAL/fake-real-codex" FLOW_AGENTS_SKILLS_DIR="$TMPDIR_EVAL/ch8-preflight-skills" \
1323
+ bash "$ROOT_DIR/scripts/install-codex-home.sh" "$CH8_PREFLIGHT" >"$TMPDIR_EVAL/ch8-preflight.out" 2>&1; then
1324
+ _fail "CH8: later owned-overlay collision unexpectedly installed"
1325
+ elif [[ "$CH8_PREFLIGHT_BEFORE" == "$(shasum -a 256 "$CH8_PREFLIGHT/AGENTS.md" | awk '{print $1}')" ]] \
1326
+ && [[ ! -e "$CH8_PREFLIGHT/.flow-agents/manual-recovery" && ! -e "$CH8_PREFLIGHT/hooks.json" ]]; then
1327
+ _pass "CH8: complete install preflight failure leaves exact legacy source and destination untouched"
1328
+ else
1329
+ _fail "CH8: later install preflight failure partially migrated or mutated destination"
1330
+ fi
1331
+
1311
1332
  echo ""
1312
1333
  echo "--- CH6: codex-home rejects canonical source/destination overlap before writes ---"
1313
1334
  CH6_ROOT="$TMPDIR_EVAL/ch6-fixture-root"
@@ -1315,8 +1336,9 @@ CH6_FAKE_BIN="$TMPDIR_EVAL/ch6-bin"
1315
1336
  CH_FIXTURE_SCRIPTS="scr""ipts"
1316
1337
  CH_FIXTURE_SKILLS="sk""ills"
1317
1338
  CH_FIXTURE_AGENTS="ag""ents"
1318
- mkdir -p "$CH6_ROOT/$CH_FIXTURE_SCRIPTS" "$CH6_ROOT/dist/codex/$CH_FIXTURE_SCRIPTS" "$CH6_FAKE_BIN"
1319
- cp "$ROOT_DIR/scripts/install-codex-home.sh" "$ROOT_DIR/scripts/install-owned-files.js" "$CH6_ROOT/$CH_FIXTURE_SCRIPTS/"
1339
+ mkdir -p "$CH6_ROOT/$CH_FIXTURE_SCRIPTS" "$CH6_ROOT/packaging" "$CH6_ROOT/dist/codex/$CH_FIXTURE_SCRIPTS" "$CH6_FAKE_BIN"
1340
+ cp "$ROOT_DIR/scripts/install-codex-home.sh" "$ROOT_DIR/scripts/install-owned-files.js" "$ROOT_DIR/scripts/classify-codex-legacy-agents.js" "$CH6_ROOT/$CH_FIXTURE_SCRIPTS/"
1341
+ cp "$ROOT_DIR/packaging/codex-legacy-agents-fingerprints.json" "$CH6_ROOT/packaging/"
1320
1342
  printf '#!/usr/bin/env bash\nexit 0\n' > "$CH6_FAKE_BIN/npm"
1321
1343
  chmod +x "$CH6_FAKE_BIN/npm"
1322
1344
  printf 'fixture\n' > "$CH6_ROOT/dist/codex/$CH_FIXTURE_SCRIPTS/fixture.txt"
@@ -1658,6 +1680,122 @@ fi
1658
1680
 
1659
1681
  echo ""
1660
1682
 
1683
+ # ─── codex-home: CH8: global instruction cleanup and preservation ───────────
1684
+ echo "--- CH8: codex-home does not install global instructions and safely migrates exact legacy files ---"
1685
+
1686
+ CH8_SKILLS="$TMPDIR_EVAL/codex-home-ch8-skills"
1687
+ CH8_CLEAN="$TMPDIR_EVAL/codex-home-ch8-clean"
1688
+ mkdir -p "$CH8_CLEAN"
1689
+ CH8_CLEAN_OUT="$TMPDIR_EVAL/ch8-clean.out"
1690
+ if CODEX_REAL_HOME="$TMPDIR_EVAL/fake-real-codex" FLOW_AGENTS_SKILLS_DIR="$CH8_SKILLS" \
1691
+ bash "$ROOT_DIR/scripts/install-codex-home.sh" "$CH8_CLEAN" >"$CH8_CLEAN_OUT" 2>&1 \
1692
+ && [[ ! -e "$CH8_CLEAN/AGENTS.md" ]] \
1693
+ && [[ -f "$CH8_CLEAN/hooks.json" && -f "$CH8_CLEAN/ag""ents/tool-worker.toml" ]] \
1694
+ && [[ -f "$CH8_SKILLS/plan-work/SKILL.md" ]]; then
1695
+ _pass "CH8: clean install leaves global AGENTS.md absent while runtime assets and universal skills install"
1696
+ else
1697
+ _fail "CH8: clean install created global instructions or lost required install assets"
1698
+ fi
1699
+
1700
+ CH8_FIXTURES="$ROOT_DIR/evals/fixtures/codex-legacy-agents"
1701
+ while IFS=$'\t' read -r CH8_HASH CH8_BYTES; do
1702
+ CH8_LEGACY="$TMPDIR_EVAL/codex-home-ch8-legacy-$CH8_HASH"
1703
+ CH8_LEGACY_SKILLS="$TMPDIR_EVAL/codex-home-ch8-legacy-skills-$CH8_HASH"
1704
+ mkdir -p "$CH8_LEGACY"
1705
+ CH8_LEGACY_REAL="$(cd "$CH8_LEGACY" && pwd -P)"
1706
+ cp "$CH8_FIXTURES/$CH8_HASH.md" "$CH8_LEGACY/AGENTS.md"
1707
+ printf 'destination sentinel\n' > "$CH8_LEGACY/preflight-sentinel"
1708
+ printf 'skills sentinel\n' > "$CH8_LEGACY_SKILLS.sentinel"
1709
+ CH8_SOURCE_BEFORE="$(shasum -a 256 "$CH8_LEGACY/AGENTS.md" | awk '{print $1}')"
1710
+ CH8_OUT="$TMPDIR_EVAL/ch8-$CH8_HASH.out"
1711
+ if CODEX_REAL_HOME="$TMPDIR_EVAL/fake-real-codex" FLOW_AGENTS_SKILLS_DIR="$CH8_LEGACY_SKILLS" \
1712
+ bash "$ROOT_DIR/scripts/install-codex-home.sh" "$CH8_LEGACY" >"$CH8_OUT" 2>&1; then
1713
+ _fail "CH8: audited legacy fingerprint $CH8_HASH did not refuse installation"
1714
+ elif [[ "$CH8_SOURCE_BEFORE" == "$(shasum -a 256 "$CH8_LEGACY/AGENTS.md" | awk '{print $1}')" ]] \
1715
+ && [[ "$(cat "$CH8_LEGACY/preflight-sentinel")" == "destination sentinel" ]] \
1716
+ && [[ ! -e "$CH8_LEGACY/hooks.json" && ! -e "$CH8_LEGACY_SKILLS" ]] \
1717
+ && grep -F -q "path=$CH8_LEGACY_REAL/AGENTS.md" "$CH8_OUT" \
1718
+ && grep -F -q "sha256=$CH8_HASH bytes=$CH8_BYTES" "$CH8_OUT" \
1719
+ && grep -q 'matching_releases=v2.4.0@bee760272b8bb770df02400ccc5881bd3dbc8806' "$CH8_OUT" \
1720
+ && grep -F -q "evidence command (hash): shasum -a 256 -- '$CH8_LEGACY_REAL/AGENTS.md'" "$CH8_OUT" \
1721
+ && grep -F -q "evidence command (bytes): wc -c < '$CH8_LEGACY_REAL/AGENTS.md'" "$CH8_OUT" \
1722
+ && grep -q 'Stop all agent and writer processes' "$CH8_OUT" \
1723
+ && grep -q 'Move the file manually without overwrite using operator-trusted tooling' "$CH8_OUT" \
1724
+ && ! grep -Eq '(^|: )(mv|rm|cp)( |$)| && (mv|rm|cp) ' "$CH8_OUT"; then
1725
+ _pass "CH8: audited legacy fingerprint $CH8_HASH refuses before mutation with exact evidence and manual checklist"
1726
+ else
1727
+ _fail "CH8: audited legacy fingerprint $CH8_HASH refusal mutated state or lacked actionable evidence"
1728
+ fi
1729
+ mkdir -p "$CH8_LEGACY/.flow-agents/manual-recovery"
1730
+ mv "$CH8_LEGACY/AGENTS.md" "$CH8_LEGACY/.flow-agents/manual-recovery/legacy-generated-AGENTS.$CH8_HASH.md"
1731
+ if CODEX_REAL_HOME="$TMPDIR_EVAL/fake-real-codex" FLOW_AGENTS_SKILLS_DIR="$CH8_LEGACY_SKILLS" \
1732
+ bash "$ROOT_DIR/scripts/install-codex-home.sh" "$CH8_LEGACY" >/dev/null 2>&1 \
1733
+ && cmp -s "$CH8_FIXTURES/$CH8_HASH.md" "$CH8_LEGACY/.flow-agents/manual-recovery/legacy-generated-AGENTS.$CH8_HASH.md" \
1734
+ && [[ ! -e "$CH8_LEGACY/AGENTS.md" && -f "$CH8_LEGACY/hooks.json" && -f "$CH8_LEGACY_SKILLS/plan-work/SKILL.md" ]]; then
1735
+ _pass "CH8: simulated operator remediation enables install without recreating AGENTS.md"
1736
+ else
1737
+ _fail "CH8: operator-remediated rerun failed or recreated global instructions"
1738
+ fi
1739
+ done < <(node - "$ROOT_DIR/packaging/codex-legacy-agents-fingerprints.json" <<'NODE'
1740
+ const fs = require("node:fs");
1741
+ const manifest = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
1742
+ for (const entry of manifest.files || []) console.log(`${entry.sha256}\t${entry.bytes}`);
1743
+ NODE
1744
+ )
1745
+
1746
+ CH8_QUOTED="$TMPDIR_EVAL/codex-home-ch8-operator-'quoted"
1747
+ mkdir -p "$CH8_QUOTED"
1748
+ cp "$CH8_FIXTURES/5273878130bdafc8a024a650bb5b66c9b003f1f859b5dc6e5b588cbf4ab23228.md" "$CH8_QUOTED/AGENTS.md"
1749
+ CH8_QUOTED_OUT="$TMPDIR_EVAL/ch8-quoted.out"
1750
+ if node "$ROOT_DIR/scripts/classify-codex-legacy-agents.js" "$CH8_QUOTED" "$ROOT_DIR/packaging/codex-legacy-agents-fingerprints.json" >"$CH8_QUOTED_OUT" 2>&1; then
1751
+ _fail "CH8: quoted-path exact legacy classifier did not refuse"
1752
+ else
1753
+ CH8_QUOTED_HASH_COMMAND="$(sed -n 's/^classify-codex-legacy-agents: evidence command (hash): //p' "$CH8_QUOTED_OUT")"
1754
+ CH8_QUOTED_BYTES_COMMAND="$(sed -n 's/^classify-codex-legacy-agents: evidence command (bytes): //p' "$CH8_QUOTED_OUT")"
1755
+ if [[ "$CH8_QUOTED_HASH_COMMAND" == *"'\"'\"'"* && "$CH8_QUOTED_BYTES_COMMAND" == *"'\"'\"'"* ]] \
1756
+ && bash -n -c "$CH8_QUOTED_HASH_COMMAND" && bash -n -c "$CH8_QUOTED_BYTES_COMMAND"; then
1757
+ _pass "CH8: evidence commands safely shell-quote apostrophes in paths"
1758
+ else
1759
+ _fail "CH8: evidence commands are not safely shell-quoted"
1760
+ fi
1761
+ fi
1762
+
1763
+ CH8_NEAR="$TMPDIR_EVAL/codex-home-ch8-near"
1764
+ mkdir -p "$CH8_NEAR"
1765
+ cp "$CH8_FIXTURES/5273878130bdafc8a024a650bb5b66c9b003f1f859b5dc6e5b588cbf4ab23228.md" "$CH8_NEAR/AGENTS.md"
1766
+ printf 'user modification\n' >> "$CH8_NEAR/AGENTS.md"
1767
+ CH8_NEAR_BEFORE="$(shasum -a 256 "$CH8_NEAR/AGENTS.md" | awk '{print $1}')"
1768
+ if CODEX_REAL_HOME="$TMPDIR_EVAL/fake-real-codex" FLOW_AGENTS_SKILLS_DIR="$TMPDIR_EVAL/ch8-near-skills" \
1769
+ bash "$ROOT_DIR/scripts/install-codex-home.sh" "$CH8_NEAR" >"$TMPDIR_EVAL/ch8-near.out" 2>&1 \
1770
+ && [[ "$CH8_NEAR_BEFORE" == "$(shasum -a 256 "$CH8_NEAR/AGENTS.md" | awk '{print $1}')" ]] \
1771
+ && grep -q 'preserved unrecognized or user-owned' "$TMPDIR_EVAL/ch8-near.out"; then
1772
+ _pass "CH8: near-match legacy-looking user instructions are byte-preserved"
1773
+ else
1774
+ _fail "CH8: near-match user instructions changed or lacked preservation diagnostic"
1775
+ fi
1776
+
1777
+ for CH8_KIND in symlink directory; do
1778
+ CH8_UNSAFE="$TMPDIR_EVAL/codex-home-ch8-$CH8_KIND"
1779
+ mkdir -p "$CH8_UNSAFE"
1780
+ printf 'must remain untouched\n' > "$CH8_UNSAFE/preflight-sentinel"
1781
+ case "$CH8_KIND" in
1782
+ symlink) printf 'target\n' > "$CH8_UNSAFE/user-target"; ln -s user-target "$CH8_UNSAFE/AGENTS.md" ;;
1783
+ directory) mkdir "$CH8_UNSAFE/AGENTS.md" ;;
1784
+ esac
1785
+ if CODEX_REAL_HOME="$TMPDIR_EVAL/fake-real-codex" FLOW_AGENTS_SKILLS_DIR="$TMPDIR_EVAL/ch8-$CH8_KIND-skills" \
1786
+ bash "$ROOT_DIR/scripts/install-codex-home.sh" "$CH8_UNSAFE" >"$TMPDIR_EVAL/ch8-$CH8_KIND.out" 2>&1 \
1787
+ && [[ "$(cat "$CH8_UNSAFE/preflight-sentinel")" == "must remain untouched" ]] \
1788
+ && [[ -e "$CH8_UNSAFE/hooks.json" ]] \
1789
+ && { [[ "$CH8_KIND" == "symlink" && -L "$CH8_UNSAFE/AGENTS.md" && "$(cat "$CH8_UNSAFE/user-target")" == "target" ]] \
1790
+ || [[ "$CH8_KIND" == "directory" && -d "$CH8_UNSAFE/AGENTS.md" ]]; }; then
1791
+ _pass "CH8: read-only classifier preserves ambiguous $CH8_KIND AGENTS.md object"
1792
+ else
1793
+ _fail "CH8: classifier altered ambiguous $CH8_KIND AGENTS.md object"
1794
+ fi
1795
+ done
1796
+
1797
+ echo ""
1798
+
1661
1799
  # ─── pi --global: PG1: warns NOT_VERIFIED + falls back to workspace default ──
1662
1800
  echo "--- PG1: pi --global warns NOT_VERIFIED + falls back to workspace default ---"
1663
1801
 
@@ -43,6 +43,7 @@ set -uo pipefail
43
43
 
44
44
  ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
45
45
  LIB_DIR="$ROOT/scripts/hooks/lib"
46
+ CURRENT_POINTER_HELPER="$LIB_DIR/current-pointer.js"
46
47
  HEARTBEAT_MODULE="$LIB_DIR/liveness-heartbeat.js"
47
48
  READ_MODULE="$LIB_DIR/liveness-read.js"
48
49
  ACTOR_MODULE="$LIB_DIR/actor-identity.js"
@@ -76,6 +77,30 @@ new_scratch() {
76
77
  mktemp -d "$TMPDIR_EVAL/scratch-XXXXXX"
77
78
  }
78
79
 
80
+ # seed_current_snapshot <root> <slug> [actor] — #440 FIXTURE-GAP: this suite's fixtures were
81
+ # written before #440's per-actor ownership scoping and never establish a per-actor current
82
+ # pointer for the invoking actor -- under a RESOLVED actor (every hook invocation below sets an
83
+ # explicit FLOW_AGENTS_ACTOR override), liveness-heartbeat.js's readActiveSlug (via
84
+ # readOwnCurrentPointer) now scopes to that actor's own (nonexistent) pointer and never reaches
85
+ # the fixture-under-test. Writes BOTH the legacy current.json AND, when actor is given, the
86
+ # per-actor current/<actor>.json pointer with the SAME minimal {"active_slug": slug} payload --
87
+ # mirroring test_liveness_heartbeat.sh's own seed_current_snapshot helper and, ultimately,
88
+ # workflow-sidecar.ts's real writeCurrent() dual-write via current-pointer.js's own
89
+ # writePerActorCurrent.
90
+ seed_current_snapshot() {
91
+ local root="$1" slug="$2" actor="${3:-}"
92
+ local artifact_root="$root/.kontourai/flow-agents"
93
+ mkdir -p "$artifact_root"
94
+ printf '{"active_slug":"%s"}\n' "$slug" >"$artifact_root/$SNAPSHOT_FILENAME"
95
+ if [[ -n "$actor" ]]; then
96
+ CP_HELPER_ARG="$CURRENT_POINTER_HELPER" FLOW_AGENTS_DIR_ARG="$artifact_root" \
97
+ SLUG_ARG="$slug" ACTOR_ARG="$actor" node - <<'NODE'
98
+ const { writePerActorCurrent } = require(process.env.CP_HELPER_ARG);
99
+ writePerActorCurrent(process.env.FLOW_AGENTS_DIR_ARG, process.env.ACTOR_ARG, { active_slug: process.env.SLUG_ARG });
100
+ NODE
101
+ fi
102
+ }
103
+
79
104
  # seed_claim <scratch_root> <slug> <actor> <at_iso> [ttl_seconds]
80
105
  # Seeds <root>/.kontourai/flow-agents/current.json (active_slug) and OVERWRITES
81
106
  # liveness/events.jsonl with a single `claim` event for <actor>.
@@ -83,7 +108,7 @@ seed_claim() {
83
108
  local root="$1" slug="$2" actor="$3" at="$4" ttl="${5:-1800}"
84
109
  local artifact_root="$root/.kontourai/flow-agents"
85
110
  mkdir -p "$artifact_root/liveness"
86
- printf '{"active_slug":"%s"}\n' "$slug" >"$artifact_root/$SNAPSHOT_FILENAME"
111
+ seed_current_snapshot "$root" "$slug" "$actor"
87
112
  printf '{"type":"claim","subjectId":"%s","actor":"%s","at":"%s","ttlSeconds":%s}\n' \
88
113
  "$slug" "$actor" "$at" "$ttl" >"$artifact_root/liveness/events.jsonl"
89
114
  }
@@ -261,7 +286,7 @@ build_large_stream_with_conflict() {
261
286
  local root="$1" slug="$2" actor="$3" claim_at="$4" other_actor="$5" other_at="$6"
262
287
  local artifact_root="$root/.kontourai/flow-agents"
263
288
  mkdir -p "$artifact_root/liveness"
264
- printf '{"active_slug":"%s"}\n' "$slug" >"$artifact_root/$SNAPSHOT_FILENAME"
289
+ seed_current_snapshot "$root" "$slug" "$actor"
265
290
  SLUG_ARG="$slug" ACTOR_ARG="$actor" CLAIM_AT_ARG="$claim_at" OTHER_ACTOR_ARG="$other_actor" \
266
291
  OTHER_AT_ARG="$other_at" STREAM_ARG="$artifact_root/liveness/events.jsonl" node - <<'NODE'
267
292
  const fs = require('fs');
@@ -498,9 +523,20 @@ fi
498
523
  # ─── G. Fail-open on a corrupted sidecar snapshot ────────────────────────────
499
524
  echo "--- G. Fail-open on a corrupted sidecar snapshot ---"
500
525
 
526
+ # #440 FIX 2 (de-vacuate, delta review): corrupt agent-hb's OWN per-actor pointer file (the new
527
+ # collision-resistant mapping via perActorCurrentFile), not the legacy current.json -- a resolved
528
+ # actor never reads the legacy file at all (#440 D1), so corrupting only that no longer exercises
529
+ # anything (the assertion would pass via "no own pointer", not malformed-pointer tolerance).
530
+ # Mirrors test_liveness_heartbeat.sh's D2 precedent.
501
531
  G_ROOT="$(new_scratch)"
502
532
  mkdir -p "$G_ROOT/.kontourai/flow-agents/liveness"
503
- printf '{not valid json' >"$G_ROOT/.kontourai/flow-agents/$SNAPSHOT_FILENAME"
533
+ G_PER_ACTOR_FILE="$(CP_HELPER_ARG="$CURRENT_POINTER_HELPER" ROOT_ARG="$G_ROOT/.kontourai/flow-agents" ACTOR_ARG="agent-hb" node - <<'NODE'
534
+ const { perActorCurrentFile } = require(process.env.CP_HELPER_ARG);
535
+ process.stdout.write(perActorCurrentFile(process.env.ROOT_ARG, process.env.ACTOR_ARG));
536
+ NODE
537
+ )"
538
+ mkdir -p "$(dirname "$G_PER_ACTOR_FILE")"
539
+ printf '{not valid json' >"$G_PER_ACTOR_FILE"
504
540
  G_OUT="$(cd "$G_ROOT" && TELEMETRY_ENABLED=false FLOW_AGENTS_ACTOR=agent-hb \
505
541
  node "$CLAUDE_HOOK" PostToolUse dev <"$POST_TOOL_USE_PAYLOAD_FILE" 2>"$TMPDIR_EVAL/g.err")"
506
542
  G_STATUS=$?
@@ -40,6 +40,7 @@ POLICY_MODULE="$LIB_DIR/liveness-policy.js"
40
40
  HEARTBEAT_MODULE="$LIB_DIR/liveness-heartbeat.js"
41
41
  READ_MODULE="$LIB_DIR/liveness-read.js"
42
42
  ACTOR_MODULE="$LIB_DIR/actor-identity.js"
43
+ CURRENT_POINTER_HELPER="$LIB_DIR/current-pointer.js"
43
44
  CLAUDE_HOOK="$ROOT/scripts/hooks/claude-telemetry-hook.js"
44
45
  CODEX_HOOK="$ROOT/scripts/hooks/codex-telemetry-hook.js"
45
46
 
@@ -68,6 +69,29 @@ new_scratch() {
68
69
  mktemp -d "$TMPDIR_EVAL/scratch-XXXXXX"
69
70
  }
70
71
 
72
+ # seed_current_snapshot <root> <slug> [actor] — #440: writes BOTH the legacy current.json
73
+ # (SNAPSHOT_FILENAME, unconditional — every fixture below keeps writing this) AND, when actor is
74
+ # given, the per-actor current/<actor>.json pointer with the SAME minimal {"active_slug": slug}
75
+ # payload — mirroring workflow-sidecar.ts's real writeCurrent() dual-write, via the shared
76
+ # current-pointer.js helper's own writePerActorCurrent (not a hand-rolled reimplementation of its
77
+ # sanitize/path rule), so #440's readOwnCurrentPointer finds this fixture's claimed subject
78
+ # exactly like a real session would. Every builder below that seeds a sidecar snapshot for a
79
+ # RESOLVED actor (the FLOW_AGENTS_ACTOR value the corresponding call_heartbeat/wrapper invocation
80
+ # uses) now routes through this, instead of hand-writing SNAPSHOT_FILENAME directly.
81
+ seed_current_snapshot() {
82
+ local root="$1" slug="$2" actor="${3:-}"
83
+ local artifact_root="$root/.kontourai/flow-agents"
84
+ mkdir -p "$artifact_root"
85
+ printf '{"active_slug":"%s"}\n' "$slug" >"$artifact_root/$SNAPSHOT_FILENAME"
86
+ if [[ -n "$actor" ]]; then
87
+ CP_HELPER_ARG="$CURRENT_POINTER_HELPER" FLOW_AGENTS_DIR_ARG="$artifact_root" \
88
+ SLUG_ARG="$slug" ACTOR_ARG="$actor" node - <<'NODE'
89
+ const { writePerActorCurrent } = require(process.env.CP_HELPER_ARG);
90
+ writePerActorCurrent(process.env.FLOW_AGENTS_DIR_ARG, process.env.ACTOR_ARG, { active_slug: process.env.SLUG_ARG });
91
+ NODE
92
+ fi
93
+ }
94
+
71
95
  # seed_claim <scratch_root> <slug> <actor> <at_iso> [ttl_seconds]
72
96
  # Seeds <root>/.kontourai/flow-agents/<SNAPSHOT_FILENAME> (active_slug) and a
73
97
  # single `claim` event in liveness/events.jsonl for <actor>, matching the
@@ -77,7 +101,7 @@ seed_claim() {
77
101
  local root="$1" slug="$2" actor="$3" at="$4" ttl="${5:-1800}"
78
102
  local artifact_root="$root/.kontourai/flow-agents"
79
103
  mkdir -p "$artifact_root/liveness"
80
- printf '{"active_slug":"%s"}\n' "$slug" >"$artifact_root/$SNAPSHOT_FILENAME"
104
+ seed_current_snapshot "$root" "$slug" "$actor"
81
105
  printf '{"type":"claim","subjectId":"%s","actor":"%s","at":"%s","ttlSeconds":%s}\n' \
82
106
  "$slug" "$actor" "$at" "$ttl" >"$artifact_root/liveness/events.jsonl"
83
107
  }
@@ -292,8 +316,9 @@ fi
292
316
 
293
317
  # C2: the sidecar snapshot names a slug with no prior events at all -> no-claim.
294
318
  C2_ROOT="$(new_scratch)"
295
- mkdir -p "$C2_ROOT/.kontourai/flow-agents"
296
- printf '{"active_slug":"c2-subj"}\n' >"$C2_ROOT/.kontourai/flow-agents/$SNAPSHOT_FILENAME"
319
+ # #440 FIXTURE-GAP: agent-c2 owns c2-subj (never claimed) -- needs its own per-actor pointer so
320
+ # readOwnCurrentPointer finds it and the code reaches the no-claim check, not an earlier no-current.
321
+ seed_current_snapshot "$C2_ROOT" "c2-subj" "agent-c2"
297
322
  C2_RESULT="$(call_heartbeat "$C2_ROOT" '{"FLOW_AGENTS_ACTOR":"agent-c2"}')"
298
323
  if [[ "$C2_RESULT" == '{"emitted":false,"reason":"no-claim"}' ]]; then
299
324
  _pass "maybeEmitHeartbeat returns no-claim when the named subject has no prior claim for the actor (AC4)"
@@ -360,9 +385,19 @@ fi
360
385
 
361
386
  # D2: a corrupted sidecar snapshot (malformed JSON) fails open — wrapper still exits 0
362
387
  # with its normal stdout contract, and no heartbeat is fabricated from unparseable state.
388
+ # #440 FIX 2 (de-vacuate, independent review): corrupt agent-hb's OWN per-actor pointer file (the
389
+ # new collision-resistant mapping), not the legacy current.json — a resolved actor never reads
390
+ # the legacy file at all (#440 D1), so corrupting only that no longer exercises anything; this
391
+ # now proves fail-open tolerance for a corrupt OWN pointer, the actually-reachable code path.
363
392
  D2_ROOT="$(new_scratch)"
364
393
  mkdir -p "$D2_ROOT/.kontourai/flow-agents/liveness"
365
- printf '{not valid json' >"$D2_ROOT/.kontourai/flow-agents/$SNAPSHOT_FILENAME"
394
+ D2_PER_ACTOR_FILE="$(CP_HELPER_ARG="$CURRENT_POINTER_HELPER" ROOT_ARG="$D2_ROOT/.kontourai/flow-agents" ACTOR_ARG="agent-hb" node - <<'NODE'
395
+ const { perActorCurrentFile } = require(process.env.CP_HELPER_ARG);
396
+ process.stdout.write(perActorCurrentFile(process.env.ROOT_ARG, process.env.ACTOR_ARG));
397
+ NODE
398
+ )"
399
+ mkdir -p "$(dirname "$D2_PER_ACTOR_FILE")"
400
+ printf '{not valid json' >"$D2_PER_ACTOR_FILE"
366
401
  D2_OUT="$(cd "$D2_ROOT" && TELEMETRY_ENABLED=false FLOW_AGENTS_ACTOR=agent-hb \
367
402
  node "$CLAUDE_HOOK" PostToolUse dev <"$POST_TOOL_USE_PAYLOAD_FILE" 2>"$TMPDIR_EVAL/d2.err")"
368
403
  D2_STATUS=$?
@@ -436,7 +471,7 @@ build_large_liveness_stream() {
436
471
  local root="$1" slug="$2" actor="$3" claim_at="$4" include_hb="${5:-false}" hb_at="${6:-}"
437
472
  local artifact_root="$root/.kontourai/flow-agents"
438
473
  mkdir -p "$artifact_root/liveness"
439
- printf '{"active_slug":"%s"}\n' "$slug" >"$artifact_root/$SNAPSHOT_FILENAME"
474
+ seed_current_snapshot "$root" "$slug" "$actor"
440
475
  SLUG_ARG="$slug" ACTOR_ARG="$actor" CLAIM_AT_ARG="$claim_at" INCLUDE_HB_ARG="$include_hb" \
441
476
  HB_AT_ARG="$hb_at" STREAM_ARG="$artifact_root/liveness/events.jsonl" node - <<'NODE'
442
477
  const fs = require('fs');
@@ -564,8 +599,7 @@ build_large_liveness_stream_release() {
564
599
  local root="$1" slug="$2" actor="$3" claim_at="$4" release_at="$5"
565
600
  local artifact_root="$root/.kontourai/flow-agents"
566
601
  mkdir -p "$artifact_root/liveness"
567
- printf '{"active_slug":"%s"}
568
- ' "$slug" >"$artifact_root/$SNAPSHOT_FILENAME"
602
+ seed_current_snapshot "$root" "$slug" "$actor"
569
603
  SLUG_ARG="$slug" ACTOR_ARG="$actor" CLAIM_AT_ARG="$claim_at" RELEASE_AT_ARG="$release_at" STREAM_ARG="$artifact_root/liveness/events.jsonl" node - <<'NODE'
570
604
  const fs = require('fs');
571
605
  const filler = (i) =>
@@ -624,7 +658,10 @@ const { sanitizeSegment } = require(process.env.ACTOR_MODULE_ARG);
624
658
  process.stdout.write(sanitizeSegment(process.env.RAW_ARG));
625
659
  NODE
626
660
  )"
627
- printf '{"active_slug":"%s"}\n' "$I_RAW_SLUG" >"$I_ROOT/.kontourai/flow-agents/$SNAPSHOT_FILENAME"
661
+ # #440 FIXTURE-GAP: agent-hb-i owns this (hostile-slug-named) subject -- needs its own per-actor
662
+ # pointer. seed_current_snapshot writes the payload's active_slug verbatim (no pre-sanitization on
663
+ # the write side), preserving this scenario's whole point: sanitization happens at READ time.
664
+ seed_current_snapshot "$I_ROOT" "$I_RAW_SLUG" "agent-hb-i"
628
665
  printf '{"type":"claim","subjectId":"%s","actor":"agent-hb-i","at":"2026-06-25T11:00:00.000Z","ttlSeconds":1800}\n' \
629
666
  "$I_SANITIZED_SLUG" >"$(stream_file "$I_ROOT")"
630
667
  I_RESULT="$(call_heartbeat "$I_ROOT" '{"FLOW_AGENTS_ACTOR":"agent-hb-i"}' "2026-06-25T11:05:00.000Z")"
@@ -648,7 +685,7 @@ seed_heartbeat_only() {
648
685
  local root="$1" slug="$2" actor="$3" at="$4"
649
686
  local artifact_root="$root/.kontourai/flow-agents"
650
687
  mkdir -p "$artifact_root/liveness"
651
- printf '{"active_slug":"%s"}\n' "$slug" >"$artifact_root/$SNAPSHOT_FILENAME"
688
+ seed_current_snapshot "$root" "$slug" "$actor"
652
689
  printf '{"type":"heartbeat","subjectId":"%s","actor":"%s","at":"%s"}\n' \
653
690
  "$slug" "$actor" "$at" >"$artifact_root/liveness/events.jsonl"
654
691
  }
@@ -417,11 +417,20 @@ node -e "
417
417
  " 2>/dev/null
418
418
  rm -f "$C_DIR/.kontourai/flow-agents/$CLEAN_SLUG/$CLEAN_SLUG--deliver.md"
419
419
 
420
+ # #440 FIXTURE-GAP: this session was established under FLOW_AGENTS_ACTOR=clean-fixture-actor
421
+ # (line ~394) -- the Stop hook must run as that SAME actor so stop-goal-fit.js's ownership-scoped
422
+ # analyze() finds clean-fixture-actor's own per-actor pointer (established by workflow start's
423
+ # dual-write) rather than finding nothing for the ambient/unrelated invoking actor. Do NOT force
424
+ # an unresolved actor here: workflow start's own automatic selected-work gate-claim recording (and
425
+ # the resulting pull-work -> design-probe canonical-Flow auto-transition this section's assertion
426
+ # depends on) itself requires a genuinely RESOLVED actor to fire -- forcing unresolved for the
427
+ # setup step silently suppresses that transition, a real behavior difference, not just a
428
+ # fixture-reachability issue.
420
429
  clean_out=""
421
430
  clean_exit=0
422
431
  for attempt in 1 2; do
423
432
  set +e
424
- attempt_out="$(FLOW_AGENTS_GOAL_FIT_MODE=block FLOW_AGENTS_GOAL_FIT_MAX_BLOCKS=2 FLOW_AGENTS_GOAL_FIT_BACKSTOP=skip \
433
+ attempt_out="$(FLOW_AGENTS_ACTOR=clean-fixture-actor FLOW_AGENTS_GOAL_FIT_MODE=block FLOW_AGENTS_GOAL_FIT_MAX_BLOCKS=2 FLOW_AGENTS_GOAL_FIT_BACKSTOP=skip \
425
434
  node "$GATE" 2>&1 <<< "{\"hook_event_name\":\"Stop\",\"cwd\":\"$C_DIR\"}")"
426
435
  attempt_exit="$?"
427
436
  set -e
@@ -31,13 +31,13 @@ printf '#!/usr/bin/env bash\nset -eu\n( trap "" TERM; while ! sleep 5; do :; don
31
31
  chmod +x "$CONSUMER/checks/check-success-background.sh"
32
32
 
33
33
  run_candidate() {
34
- (cd "$CONSUMER" && CODEX_SESSION_ID=public-workflow-eval npx --yes --package="file:$TARBALL" flow-agents workflow "$@")
34
+ (cd "$CONSUMER" && env -u CODEX_THREAD_ID CODEX_SESSION_ID=public-workflow-eval npx --yes --package="file:$TARBALL" flow-agents workflow "$@")
35
35
  }
36
36
 
37
37
  run_candidate_as() {
38
38
  local actor="$1"
39
39
  shift
40
- (cd "$CONSUMER" && CODEX_SESSION_ID="$actor" npx --yes --package="file:$TARBALL" flow-agents workflow "$@")
40
+ (cd "$CONSUMER" && env -u CODEX_THREAD_ID CODEX_SESSION_ID="$actor" npx --yes --package="file:$TARBALL" flow-agents workflow "$@")
41
41
  }
42
42
 
43
43
  snapshot_tree() {
@@ -134,7 +134,7 @@ mkdir -p "$LOCAL_RETRY_ROOT/local-retry"
134
134
  printf 'Selected Work Item: local:local-retry\n' > "$LOCAL_RETRY_ROOT/local-retry/local-retry--pull-work.md"
135
135
  printf 'not a run-store directory\n' >"$LOCAL_RETRY_PROJECT/.kontourai/flow"
136
136
  set +e
137
- (cd "$LOCAL_RETRY_PROJECT" && CODEX_SESSION_ID=public-workflow-eval npx --yes --package="file:$TARBALL" flow-agents-workflow-sidecar ensure-session \
137
+ (cd "$LOCAL_RETRY_PROJECT" && env -u CODEX_THREAD_ID CODEX_SESSION_ID=public-workflow-eval npx --yes --package="file:$TARBALL" flow-agents-workflow-sidecar ensure-session \
138
138
  --artifact-root "$LOCAL_RETRY_ROOT" --task-slug local-retry \
139
139
  --title "Local retry" --summary "Resume the bound local workflow." --flow-id builder.build >/dev/null 2>&1)
140
140
  LOCAL_SEED_RC=$?
@@ -147,7 +147,7 @@ rm -f "$LOCAL_RETRY_PROJECT/.kontourai/flow"
147
147
  FOREIGN_RETRY_CWD="$TMP/foreign-retry-cwd"
148
148
  mkdir -p "$FOREIGN_RETRY_CWD"
149
149
  EXECUTABLE_RETRY="$(node -e 'process.stdout.write(process.argv[1].replace(process.argv[2], process.argv[3]))' "$LOCAL_RETRY_COMMAND" "'@kontourai/flow-agents@$VERSION'" "'file:$TARBALL'")"
150
- (cd "$FOREIGN_RETRY_CWD" && CODEX_SESSION_ID=public-workflow-eval eval "$EXECUTABLE_RETRY" >/dev/null)
150
+ (cd "$FOREIGN_RETRY_CWD" && unset CODEX_THREAD_ID && export CODEX_SESSION_ID=public-workflow-eval && eval "$EXECUTABLE_RETRY" >/dev/null)
151
151
  [[ -f "$LOCAL_RETRY_PROJECT/.kontourai/flow/runs/local-retry/state.json" && ! -e "$FOREIGN_RETRY_CWD/.kontourai" ]] || fail "emitted local retry mutated the caller cwd instead of the originating store"
152
152
  pass "emitted local retry executes from a foreign cwd against its exact originating store"
153
153
 
@@ -428,7 +428,7 @@ TIMEOUT_SESSION="$ARTIFACT_ROOT/acme-widgets-107"
428
428
  TIMEOUT_CHILD_PID="$TMP/command-timeout-child.pid"
429
429
  TIMEOUT_MARKER="$TMP/command-timeout-marker"
430
430
  set +e
431
- (cd "$CONSUMER" && CODEX_SESSION_ID=public-workflow-eval FLOW_AGENTS_EVIDENCE_COMMAND_TIMEOUT_MS=1000 FLOW_AGENTS_EVIDENCE_COMMAND_KILL_GRACE_MS=250 npx --yes --package="file:$TARBALL" flow-agents workflow evidence --session-dir "$TIMEOUT_SESSION" --expectation pickup-probe-readiness --status not_verified --summary "Timeout fixture captures full process-group termination." --command "bash checks/check-command-timeout.sh '$TIMEOUT_CHILD_PID' '$TIMEOUT_MARKER'" --json) >"$TMP/command-timeout.out" 2>&1
431
+ (cd "$CONSUMER" && env -u CODEX_THREAD_ID CODEX_SESSION_ID=public-workflow-eval FLOW_AGENTS_EVIDENCE_COMMAND_TIMEOUT_MS=1000 FLOW_AGENTS_EVIDENCE_COMMAND_KILL_GRACE_MS=250 npx --yes --package="file:$TARBALL" flow-agents workflow evidence --session-dir "$TIMEOUT_SESSION" --expectation pickup-probe-readiness --status not_verified --summary "Timeout fixture captures full process-group termination." --command "bash checks/check-command-timeout.sh '$TIMEOUT_CHILD_PID' '$TIMEOUT_MARKER'" --json) >"$TMP/command-timeout.out" 2>&1
432
432
  TIMEOUT_RC=$?
433
433
  set -e
434
434
  [[ "$TIMEOUT_RC" -ne 0 && -s "$TIMEOUT_CHILD_PID" && ! -e "$TIMEOUT_MARKER" ]] || fail "timed-out evidence command did not complete its controlled capture"
@@ -439,7 +439,7 @@ pass "timed-out evidence commands terminate their complete process group"
439
439
  BACKGROUND_CHILD_PID="$TMP/success-background-child.pid"
440
440
  BACKGROUND_MARKER="$TMP/success-background-marker"
441
441
  TIMEOUT_PULL_REPORT="$TIMEOUT_SESSION/$(basename "$TIMEOUT_SESSION")--pull-work.md"
442
- (cd "$CONSUMER" && CODEX_SESSION_ID=public-workflow-eval FLOW_AGENTS_EVIDENCE_COMMAND_KILL_GRACE_MS=50 npx --yes --package="file:$TARBALL" flow-agents workflow evidence \
442
+ (cd "$CONSUMER" && env -u CODEX_THREAD_ID CODEX_SESSION_ID=public-workflow-eval FLOW_AGENTS_EVIDENCE_COMMAND_KILL_GRACE_MS=50 npx --yes --package="file:$TARBALL" flow-agents workflow evidence \
443
443
  --session-dir "$TIMEOUT_SESSION" --expectation pickup-probe-readiness --status pass \
444
444
  --summary "Successful evidence cleans up surviving background processes." \
445
445
  --command "bash checks/check-success-background.sh '$BACKGROUND_CHILD_PID' '$BACKGROUND_MARKER'" \
@@ -13,6 +13,7 @@
13
13
  set -uo pipefail
14
14
 
15
15
  ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
16
+ CURRENT_POINTER_HELPER="$ROOT/scripts/hooks/lib/current-pointer.js"
16
17
 
17
18
  TMPDIR_EVAL="$(mktemp -d)"
18
19
  errors=0
@@ -25,6 +26,30 @@ trap cleanup EXIT
25
26
  _pass() { echo " ✓ $1"; }
26
27
  _fail() { echo " ✗ $1"; errors=$((errors + 1)); }
27
28
 
29
+ # seed_current_pointer <flow_agents_dir> <slug> <actor> — #440 FIXTURE-GAP: this suite's fixtures
30
+ # were written before #440's per-actor ownership scoping and never establish a per-actor current
31
+ # pointer for the invoking actor -- under a RESOLVED actor (every hook invocation below sets an
32
+ # explicit FLOW_AGENTS_ACTOR override, or derives one from an injected CLAUDE_CODE_SESSION_ID),
33
+ # workflow-steering.js's actorScopedWorkflowState now scopes to that actor's own (nonexistent)
34
+ # pointer and never reaches the fixture-under-test. Seeds BOTH the legacy current.json AND the
35
+ # per-actor current/<actor>.json pointer with the SAME payload, mirroring workflow-sidecar.ts's
36
+ # real writeCurrent() dual-write via current-pointer.js's own writePerActorCurrent.
37
+ seed_current_pointer() {
38
+ local flow_agents_dir="$1" slug="$2" actor="$3"
39
+ CP_HELPER_ARG="$CURRENT_POINTER_HELPER" DIR_ARG="$flow_agents_dir" SLUG_ARG="$slug" ACTOR_ARG="$actor" node - <<'NODE'
40
+ const fs = require('fs');
41
+ const path = require('path');
42
+ const { writePerActorCurrent } = require(process.env.CP_HELPER_ARG);
43
+ const dir = process.env.DIR_ARG;
44
+ const slug = process.env.SLUG_ARG;
45
+ const actor = process.env.ACTOR_ARG;
46
+ const payload = { schema_version: '1.0', active_slug: slug, artifact_dir: slug };
47
+ fs.mkdirSync(dir, { recursive: true });
48
+ fs.writeFileSync(path.join(dir, 'current.json'), JSON.stringify(payload, null, 2) + '\n');
49
+ writePerActorCurrent(dir, actor, payload);
50
+ NODE
51
+ }
52
+
28
53
  # ─── Portable sha256 helper ────────────────────────────────────────────────────
29
54
  sha256_file() {
30
55
  if command -v sha256sum >/dev/null 2>&1; then
@@ -112,6 +137,8 @@ FIVE_MIN_AGO="$(node -e "process.stdout.write(new Date(Date.now()-300000).toISOS
112
137
  printf '{"type":"claim","subjectId":"test-slug-153","actor":"other-agent","at":"%s","ttlSeconds":1800}\n' "$FIVE_MIN_AGO" > "$REPO/.kontourai/flow-agents/liveness/events.jsonl"
113
138
  printf '{"type":"heartbeat","subjectId":"test-slug-153","actor":"self-actor","at":"%s"}\n' "$FIVE_MIN_AGO" >> "$REPO/.kontourai/flow-agents/liveness/events.jsonl"
114
139
 
140
+ seed_current_pointer "$REPO/.kontourai/flow-agents" "$SLUG" "self-actor"
141
+
115
142
  # ─── Snapshot checksums before hook run ───────────────────────────────────────
116
143
  CKSUM_STATE_BEFORE="$(sha256_file "$TASK_DIR/state.json")"
117
144
  CKSUM_HANDOFF_BEFORE="$(sha256_file "$TASK_DIR/handoff.json")"
@@ -260,6 +287,7 @@ cp "$TASK_DIR/handoff.json" "$TASK_DIR2/handoff.json"
260
287
  cp "$TASK_DIR/trust.bundle" "$TASK_DIR2/trust.bundle"
261
288
  printf 'test-slug-153--plan-work.md stub\n' > "$TASK_DIR2/test-slug-153--plan-work.md"
262
289
  # No liveness directory → empty stream
290
+ seed_current_pointer "$REPO2/.kontourai/flow-agents" "$SLUG" "self-actor"
263
291
 
264
292
  echo "{\"hook_event_name\":\"SessionStart\",\"cwd\":\"$REPO2\"}" | \
265
293
  FLOW_AGENTS_ACTOR="self-actor" node "$ROOT/scripts/hooks/workflow-steering.js" > "$TMPDIR_EVAL/nolive.out" 2>&1
@@ -330,6 +358,7 @@ fi
330
358
  FIVE_MIN_AGO_3="$(node -e "process.stdout.write(new Date(Date.now()-300000).toISOString().replace(/\\.\\d{3}Z$/,'Z'))")"
331
359
  printf '{"type":"claim","subjectId":"%s","actor":"other-agent-derived","at":"%s","ttlSeconds":1800}\n' "$SLUG3" "$FIVE_MIN_AGO_3" > "$REPO3/.kontourai/flow-agents/liveness/events.jsonl"
332
360
  printf '{"type":"heartbeat","subjectId":"%s","actor":"%s","at":"%s"}\n' "$SLUG3" "$DERIVED_SELF_ACTOR" "$FIVE_MIN_AGO_3" >> "$REPO3/.kontourai/flow-agents/liveness/events.jsonl"
361
+ seed_current_pointer "$REPO3/.kontourai/flow-agents" "$SLUG3" "$DERIVED_SELF_ACTOR"
333
362
 
334
363
  echo "{\"hook_event_name\":\"SessionStart\",\"cwd\":\"$REPO3\"}" | \
335
364
  CLAUDE_CODE_SESSION_ID="$SELF_SESSION_ID" FLOW_AGENTS_ACTOR="" node "$ROOT/scripts/hooks/workflow-steering.js" > "$TMPDIR_EVAL/derived-actor.out" 2>&1
@@ -397,6 +426,7 @@ const fs = require('fs');
397
426
  const line = JSON.stringify({ type: 'claim', subjectId: process.argv[1], actor: JSON.parse(process.argv[2]), at: process.argv[3], ttlSeconds: 1800 });
398
427
  fs.writeFileSync(process.argv[4], line + '\n');
399
428
  " "$SLUG4" "$HOSTILE_ACTOR_JSON" "$FIVE_MIN_AGO_4" "$LIVENESS_FILE_4"
429
+ seed_current_pointer "$REPO4/.kontourai/flow-agents" "$SLUG4" "self-actor-t2-hostile-check"
400
430
 
401
431
  echo "{\"hook_event_name\":\"SessionStart\",\"cwd\":\"$REPO4\"}" | \
402
432
  FLOW_AGENTS_ACTOR="self-actor-t2-hostile-check" node "$ROOT/scripts/hooks/workflow-steering.js" > "$TMPDIR_EVAL/hostile-actor.out" 2>&1