@kontourai/flow-agents 3.8.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 (109) hide show
  1. package/.github/workflows/ci.yml +8 -0
  2. package/CHANGELOG.md +24 -0
  3. package/README.md +1 -1
  4. package/build/src/builder-flow-runtime.js +16 -10
  5. package/build/src/cli/assignment-provider.js +4 -18
  6. package/build/src/cli/continuation-adapter.d.ts +26 -0
  7. package/build/src/cli/continuation-adapter.js +243 -0
  8. package/build/src/cli/validate-workflow-artifacts.js +21 -1
  9. package/build/src/cli/workflow-sidecar.js +15 -20
  10. package/build/src/cli/workflow.js +99 -6
  11. package/build/src/continuation-driver.d.ts +116 -0
  12. package/build/src/continuation-driver.js +527 -0
  13. package/build/src/index.d.ts +2 -0
  14. package/build/src/index.js +1 -0
  15. package/build/src/tools/build-universal-bundles.js +52 -25
  16. package/build/src/tools/validate-source-tree.js +5 -2
  17. package/context/scripts/hooks/lib/continuation-turn-authority.js +386 -0
  18. package/context/scripts/hooks/stop-goal-fit.js +244 -73
  19. package/context/scripts/hooks/workflow-steering.js +24 -15
  20. package/context/scripts/telemetry/lib/pricing.sh +18 -6
  21. package/context/scripts/telemetry/lib/transport.sh +139 -37
  22. package/context/scripts/telemetry/lib/usage.sh +282 -32
  23. package/context/scripts/telemetry/lib/usage_model_guard.sh +42 -0
  24. package/context/scripts/telemetry/telemetry.sh +76 -1
  25. package/docs/coordination-guide.md +21 -5
  26. package/docs/fixture-ownership.md +2 -1
  27. package/docs/public-workflow-cli.md +98 -0
  28. package/docs/spec/runtime-hook-surface.md +8 -4
  29. package/docs/workflow-usage-guide.md +7 -0
  30. package/evals/acceptance/prove-capture-teeth-declared.sh +13 -0
  31. package/evals/acceptance/prove-capture-teeth.sh +13 -0
  32. package/evals/ci/run-baseline.sh +2 -0
  33. package/evals/fixtures/codex-legacy-agents/5273878130bdafc8a024a650bb5b66c9b003f1f859b5dc6e5b588cbf4ab23228.md +25 -0
  34. package/evals/fixtures/telemetry/usage-transcript-adversarial.jsonl +2 -0
  35. package/evals/fixtures/telemetry/usage-transcript-malformed-sandwich.jsonl +3 -0
  36. package/evals/fixtures/telemetry/usage-transcript-negative-tokens.jsonl +1 -0
  37. package/evals/fixtures/telemetry/usage-transcript-oversized-prefix.jsonl +201 -0
  38. package/evals/fixtures/telemetry/usage-transcript-oversized-tokens.jsonl +1 -0
  39. package/evals/fixtures/telemetry/usage-transcript-prefixed-secret-lower.jsonl +1 -0
  40. package/evals/fixtures/telemetry/usage-transcript-prefixed-secret-upper.jsonl +1 -0
  41. package/evals/fixtures/telemetry/usage-transcript-secret-model-jwt.jsonl +1 -0
  42. package/evals/fixtures/telemetry/usage-transcript-secret-model.jsonl +1 -0
  43. package/evals/fixtures/telemetry/usage-transcript-torn-trailing.jsonl +2 -0
  44. package/evals/integration/test_actor_identity.sh +63 -0
  45. package/evals/integration/test_assignment_provider_local_file.sh +34 -0
  46. package/evals/integration/test_builder_entry_enforcement.sh +5 -1
  47. package/evals/integration/test_bundle_install.sh +106 -17
  48. package/evals/integration/test_bundle_lifecycle.sh +17 -3
  49. package/evals/integration/test_captured_fail_reconciliation.sh +11 -0
  50. package/evals/integration/test_ci_actor_identity.sh +2 -2
  51. package/evals/integration/test_command_log_concurrency.sh +11 -0
  52. package/evals/integration/test_command_log_fork_classification.sh +11 -0
  53. package/evals/integration/test_command_log_integrity.sh +11 -0
  54. package/evals/integration/test_current_json_per_actor.sh +416 -21
  55. package/evals/integration/test_enforcer_expects_driven.sh +11 -0
  56. package/evals/integration/test_ensure_session_ownership_guard.sh +39 -0
  57. package/evals/integration/test_evidence_capture_hook.sh +25 -6
  58. package/evals/integration/test_fixture_retirement_audit.sh +2 -2
  59. package/evals/integration/test_flowdef_union_floor_regression.sh +11 -0
  60. package/evals/integration/test_gate_bypass_chain.sh +11 -0
  61. package/evals/integration/test_gate_lockdown.sh +10 -0
  62. package/evals/integration/test_goal_fit_ghost_session.sh +11 -0
  63. package/evals/integration/test_goal_fit_hook.sh +257 -9
  64. package/evals/integration/test_goal_fit_rederive.sh +11 -0
  65. package/evals/integration/test_install_merge.sh +141 -3
  66. package/evals/integration/test_liveness_conflict_injection.sh +39 -3
  67. package/evals/integration/test_liveness_heartbeat.sh +46 -9
  68. package/evals/integration/test_phase_map_and_gate_claim.sh +10 -1
  69. package/evals/integration/test_public_workflow_cli.sh +6 -6
  70. package/evals/integration/test_session_resume_roundtrip.sh +30 -0
  71. package/evals/integration/test_stop_hook_release.sh +111 -11
  72. package/evals/integration/test_telemetry_tool_usage.sh +394 -0
  73. package/evals/integration/test_verify_hold.sh +64 -1
  74. package/evals/integration/test_workflow_sidecar_writer.sh +10 -1
  75. package/evals/integration/test_workflow_steering_hook.sh +20 -4
  76. package/evals/run.sh +2 -0
  77. package/evals/static/test_universal_bundles.sh +123 -0
  78. package/package.json +4 -4
  79. package/packaging/README.md +21 -0
  80. package/packaging/codex-legacy-agents-fingerprints.json +33 -0
  81. package/scripts/README.md +2 -1
  82. package/scripts/audit-codex-legacy-agents.js +43 -0
  83. package/scripts/classify-codex-legacy-agents.js +71 -0
  84. package/scripts/hooks/evidence-capture.js +18 -6
  85. package/scripts/hooks/lib/actor-identity.js +62 -29
  86. package/scripts/hooks/lib/continuation-turn-authority.js +386 -0
  87. package/scripts/hooks/lib/current-pointer.js +114 -12
  88. package/scripts/hooks/lib/liveness-heartbeat.js +15 -12
  89. package/scripts/hooks/stop-goal-fit.js +244 -73
  90. package/scripts/hooks/workflow-steering.js +24 -15
  91. package/scripts/install-codex-home.sh +8 -7
  92. package/scripts/telemetry/lib/pricing.sh +18 -6
  93. package/scripts/telemetry/lib/transport.sh +71 -0
  94. package/scripts/telemetry/lib/usage.sh +282 -32
  95. package/scripts/telemetry/lib/usage_model_guard.sh +42 -0
  96. package/scripts/telemetry/telemetry.sh +76 -1
  97. package/src/builder-flow-runtime.ts +9 -3
  98. package/src/cli/assignment-provider.ts +5 -26
  99. package/src/cli/builder-flow-runtime.test.mjs +215 -2
  100. package/src/cli/continuation-adapter.ts +265 -0
  101. package/src/cli/continuation-driver.test.mjs +1303 -0
  102. package/src/cli/public-api.test.mjs +1 -0
  103. package/src/cli/validate-workflow-artifacts.ts +19 -1
  104. package/src/cli/workflow-sidecar.ts +18 -23
  105. package/src/cli/workflow.ts +113 -6
  106. package/src/continuation-driver.ts +638 -0
  107. package/src/index.ts +24 -0
  108. package/src/tools/build-universal-bundles.ts +61 -25
  109. package/src/tools/validate-source-tree.ts +5 -2
@@ -0,0 +1 @@
1
+ {"type": "assistant", "message": {"model": "claude-opus-4-8", "usage": {"input_tokens": 1e+308, "output_tokens": 20, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}}}
@@ -0,0 +1 @@
1
+ {"type":"assistant","message":{"model":"claude-abcdefghijklmnopqrstuvwxyz0","usage":{"input_tokens":13,"output_tokens":26,"cache_creation_input_tokens":0,"cache_read_input_tokens":0}}}
@@ -0,0 +1 @@
1
+ {"type":"assistant","message":{"model":"claude-UPPERCASE-REJECTED","usage":{"input_tokens":11,"output_tokens":22,"cache_creation_input_tokens":0,"cache_read_input_tokens":0}}}
@@ -0,0 +1 @@
1
+ {"type": "assistant", "message": {"model": "not-a-real-jwt.payload-user-at-example-com.sig", "usage": {"input_tokens": 30, "output_tokens": 9, "cache_creation_input_tokens": 1, "cache_read_input_tokens": 2}}}
@@ -0,0 +1 @@
1
+ {"type": "assistant", "message": {"model": "sk-ant-api03-abcDEF123456789_secretlookingkey-XYZ", "usage": {"input_tokens": 42, "output_tokens": 17, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}}}
@@ -0,0 +1,2 @@
1
+ {"type": "assistant", "message": {"model": "claude-torn-trailing-safe", "usage": {"input_tokens": 15, "output_tokens": 25, "cache_creation_input_tokens": 1, "cache_read_input_tokens": 2}}}
2
+ {"type":"assistant","message":{"model":"claude-torn-victim","usage":{"input_tokens":999,"output_tok
@@ -245,6 +245,69 @@ else
245
245
  _fail "resolveActor did not honor a deliberate literal FLOW_AGENTS_ACTOR=unknown override"
246
246
  fi
247
247
 
248
+ # 11 (#554). CODEX_THREAD_ID is the preferred native Codex signal. Its raw value must never
249
+ # become durable actor text: it is transformed into a stable, domain-separated 96-bit token.
250
+ if node - "$MODULE" <<'NODE'
251
+ const path = process.argv[2];
252
+ const { detectRuntime, runtimeSessionId, resolveActor, resolveActorIdentity, serializeActor } = require(path);
253
+ const rawA = "thread:private/value\nwith-control";
254
+ const rawB = "thread:other-private-value";
255
+ const envA = { CODEX_THREAD_ID: rawA, CODEX_SESSION_ID: "legacy-must-lose" };
256
+ if (detectRuntime(envA) !== "codex") throw new Error("CODEX_THREAD_ID did not detect codex");
257
+ if (runtimeSessionId(envA) !== rawA) throw new Error("CODEX_THREAD_ID did not win ordered priority");
258
+ if (runtimeSessionId({ CODEX_SESSION_ID: "legacy-ok" }) !== "legacy-ok") throw new Error("legacy CODEX_SESSION_ID stopped working");
259
+ const first = resolveActor(envA);
260
+ const repeat = resolveActor(envA);
261
+ const distinct = resolveActor({ CODEX_THREAD_ID: rawB });
262
+ const identity = resolveActorIdentity(envA);
263
+ if (JSON.stringify(first) !== JSON.stringify(repeat)) throw new Error("same thread was unstable");
264
+ if (first.actor === distinct.actor) throw new Error("distinct threads collided");
265
+ if (first.source !== "runtime-session-id:codex") throw new Error(`wrong source: ${JSON.stringify(first)}`);
266
+ if (!identity.actorStruct || identity.actor !== serializeActor(identity.actorStruct)) throw new Error("canonical actor struct/key diverged");
267
+ if (identity.actorStruct.session_id.includes("private")) throw new Error("raw id leaked through canonical actor struct");
268
+ if (!/^codex:thread-[a-f0-9]{24}:[A-Za-z0-9_.-]+$/.test(first.actor)) throw new Error(`unsafe actor shape: ${JSON.stringify(first)}`);
269
+ if (first.actor.includes(rawA) || first.actor.includes("private") || first.actor.includes("legacy-must-lose")) throw new Error("raw native id leaked");
270
+ NODE
271
+ then
272
+ _pass "CODEX_THREAD_ID yields a stable, distinct, privacy-safe Codex actor and wins over the legacy signal"
273
+ else
274
+ _fail "CODEX_THREAD_ID native identity contract failed"
275
+ fi
276
+
277
+ # 12 (#554). Ancestry is explicitly classified in its display string but remains unstable by source.
278
+ if node - "$MODULE" <<'NODE'
279
+ const path = process.argv[2];
280
+ const { resolveActorIdentity, serializeActor } = require(path);
281
+ const out = resolveActorIdentity({});
282
+ if (!out || out.source !== "process-ancestry") throw new Error(`wrong fallback source: ${JSON.stringify(out)}`);
283
+ if (!out.actor.startsWith("process-ancestry:anc-")) throw new Error(`fallback remains misleading: ${JSON.stringify(out)}`);
284
+ if (out.actor.startsWith("unknown:anc-")) throw new Error(`legacy fallback still emitted: ${JSON.stringify(out)}`);
285
+ if (!out.actorStruct || out.actorStruct.runtime !== "process-ancestry" || out.actor !== serializeActor(out.actorStruct)) throw new Error(`fallback struct/key diverged: ${JSON.stringify(out)}`);
286
+ NODE
287
+ then
288
+ _pass "ancestry fallback is explicitly labeled process-ancestry without changing its source classification"
289
+ else
290
+ _fail "ancestry fallback label/source contract failed"
291
+ fi
292
+
293
+ # 13 (#554). Historical actor values remain opaque and readable; no alias/schema rewrite is needed.
294
+ if node - "$ROOT/scripts/hooks/lib/liveness-read.js" <<'NODE'
295
+ const { freshHolders } = require(process.argv[2]);
296
+ const legacy = "unknown:anc-eabf4cb9ef38:old-host";
297
+ const at = "2026-07-11T12:00:00.000Z";
298
+ const events = [{ type: "claim", subjectId: "legacy-work", actor: legacy, at, ttlSeconds: 1800 }];
299
+ const foreign = freshHolders(events, "legacy-work", "new-self", Date.parse(at) + 1000);
300
+ const self = freshHolders(events, "legacy-work", legacy, Date.parse(at) + 1000);
301
+ if (foreign.length !== 1 || foreign[0].actor !== legacy) throw new Error(`legacy record unreadable: ${JSON.stringify(foreign)}`);
302
+ if (self.length !== 0) throw new Error(`legacy self-filter failed: ${JSON.stringify(self)}`);
303
+ if (events[0].actor !== legacy || Object.prototype.hasOwnProperty.call(events[0], "actor_key")) throw new Error("legacy record was mutated");
304
+ NODE
305
+ then
306
+ _pass "legacy unknown:anc-* records remain readable, self-filterable, and unmodified"
307
+ else
308
+ _fail "legacy unknown:anc-* compatibility failed"
309
+ fi
310
+
248
311
  if [[ "$errors" -eq 0 ]]; then
249
312
  echo "Actor identity resolver integration passed."
250
313
  exit 0
@@ -268,6 +268,40 @@ fi
268
268
  [[ "$(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)" || fail "on-disk record holder matches the process that actually exited 0 (no silent overwrite by the loser)"
269
269
 
270
270
  echo ""
271
+
272
+ # 8. #554: auto-derived Codex identity persists only the canonical privacy-safe struct/key.
273
+ CODEX_ROOT="$TMPDIR_EVAL/codex-artifact-root"
274
+ CODEX_SUBJECT="codex-private-identity"
275
+ CODEX_RAW='PRIVATE-SENTINEL:thread/value-with-punctuation'
276
+ CODEX_THREAD_ID="$CODEX_RAW" CODEX_SESSION_ID= node "$CLI" assignment-provider claim \
277
+ --provider local-file --artifact-root "$CODEX_ROOT" --subject-id "$CODEX_SUBJECT" \
278
+ --branch agent/codex-private --artifact-dir .kontourai/flow-agents/codex-private \
279
+ >"$TMPDIR_EVAL/codex-claim.json" 2>"$TMPDIR_EVAL/codex-claim.err"
280
+ CODEX_RECORD="$CODEX_ROOT/assignment/$CODEX_SUBJECT.json"
281
+ if node - "$CODEX_RECORD" "$ROOT/scripts/hooks/lib/actor-identity.js" <<'NODE'
282
+ const fs = require('fs');
283
+ const record = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
284
+ const { serializeActor } = require(process.argv[3]);
285
+ if (record.actor.runtime !== 'codex') throw new Error('wrong runtime');
286
+ if (!/^thread-[a-f0-9]{24}$/.test(record.actor.session_id)) throw new Error('unsafe session token');
287
+ if (record.actor_key !== serializeActor(record.actor)) throw new Error('actor_key differs from stored actor');
288
+ NODE
289
+ then pass "Codex assignment record stores one canonical privacy-safe struct/key"
290
+ else fail "Codex assignment record did not store the canonical privacy-safe struct/key"
291
+ fi
292
+ if rg -qF 'PRIVATE-SENTINEL' "$CODEX_ROOT" "$TMPDIR_EVAL/codex-claim.json" "$TMPDIR_EVAL/codex-claim.err"; then
293
+ fail "raw CODEX_THREAD_ID sentinel leaked into assignment artifacts or command output"
294
+ else
295
+ pass "raw CODEX_THREAD_ID sentinel is absent from the complete assignment artifact/output set"
296
+ fi
297
+ CODEX_KEY="$(json_query "$CODEX_RECORD" "actor_key")"
298
+ CODEX_THREAD_ID="$CODEX_RAW" CODEX_SESSION_ID= node "$CLI" assignment-provider status \
299
+ --provider local-file --artifact-root "$CODEX_ROOT" --subject-id "$CODEX_SUBJECT" \
300
+ --liveness-events-json <(echo '[]') --self-actor "$CODEX_KEY" >"$TMPDIR_EVAL/codex-status.json"
301
+ [[ "$(json_query "$TMPDIR_EVAL/codex-status.json" "effective.reason")" == "self_is_holder" ]] \
302
+ && pass "subsequent assignment status recognizes the canonical Codex actor as self" \
303
+ || fail "subsequent assignment status did not recognize the canonical Codex actor as self"
304
+
271
305
  if [[ "$errors" -eq 0 ]]; then
272
306
  echo "test_assignment_provider_local_file: all checks passed."
273
307
  else
@@ -478,7 +478,11 @@ else
478
478
  fail "preexisting assignment was treated as current selection evidence: $(cat "$TMP/preexisting.out")"
479
479
  fi
480
480
 
481
- if FLOW_AGENTS_GOAL_FIT_MODE=block FLOW_AGENTS_GOAL_FIT_MAX_BLOCKS=100000 \
481
+ # #440 FIXTURE-GAP: $TMP/local's session was established under FLOW_AGENTS_ACTOR=builder-entry-local
482
+ # (see the "workflow start" call above) -- the Stop hook must run as that SAME actor so
483
+ # stop-goal-fit.js's ownership-scoped analyze() finds builder-entry-local's own per-actor pointer
484
+ # (established by workflow start's dual-write), not nothing for an unrelated ambient actor.
485
+ if FLOW_AGENTS_ACTOR=builder-entry-local FLOW_AGENTS_GOAL_FIT_MODE=block FLOW_AGENTS_GOAL_FIT_MAX_BLOCKS=100000 \
482
486
  node "$ROOT/scripts/hooks/stop-goal-fit.js" >"$TMP/stop.out" 2>"$TMP/stop.err" <<JSON
483
487
  {"hook_event_name":"Stop","cwd":"$TMP/local"}
484
488
  JSON
@@ -44,35 +44,94 @@ OPENCODE_DEST="$TMPDIR_EVAL/opencode-workspace"
44
44
  OPENCODE_CONSOLE_DEST="$TMPDIR_EVAL/opencode-console-workspace"
45
45
  OPENCODE_FULL_DEST="$TMPDIR_EVAL/opencode-full-workspace"
46
46
  PI_DEST="$TMPDIR_EVAL/pi-workspace"
47
+ INSTRUCTION_FIXTURES="$TMPDIR_EVAL/instruction-fixtures"
47
48
  CONSOLE_TOKEN_FILE="$TMPDIR_EVAL/console-token"
48
49
  printf 'test-token\n' > "$CONSOLE_TOKEN_FILE"
49
50
  chmod 600 "$CONSOLE_TOKEN_FILE" 2>/dev/null || true
50
51
 
52
+ instruction_fixture_key() {
53
+ printf '%s' "$1" | shasum -a 256 | awk '{print $1}'
54
+ }
55
+
56
+ instruction_file_identity() {
57
+ local file="$1"
58
+ printf '%s %s\n' "$(wc -c < "$file" | tr -d ' ')" "$(shasum -a 256 "$file" | awk '{print $1}')"
59
+ }
60
+
61
+ seed_instruction_fixture() {
62
+ local dest="$1"
63
+ local label="$2"
64
+ local key
65
+ key="$(instruction_fixture_key "$dest")"
66
+ mkdir -p "$dest" "$INSTRUCTION_FIXTURES"
67
+ printf '# Repository AGENTS instructions\nfixture=%s\nkind=agents\nliteral=__KIRO_PACKAGE_ROOT__\n' "$label" > "$dest/AGENTS.md"
68
+ printf '# Repository CLAUDE instructions\nfixture=%s\nkind=claude\nliteral=__KIRO_PACKAGE_ROOT__\n' "$label" > "$dest/CLAUDE.md"
69
+ instruction_file_identity "$dest/AGENTS.md" > "$INSTRUCTION_FIXTURES/$key.AGENTS.md.identity"
70
+ instruction_file_identity "$dest/CLAUDE.md" > "$INSTRUCTION_FIXTURES/$key.CLAUDE.md.identity"
71
+ }
72
+
73
+ assert_instruction_fixture_preserved() {
74
+ local dest="$1"
75
+ local label="$2"
76
+ local key filename expected actual
77
+ key="$(instruction_fixture_key "$dest")"
78
+ for filename in AGENTS.md CLAUDE.md; do
79
+ expected="$(cat "$INSTRUCTION_FIXTURES/$key.$filename.identity")"
80
+ if [[ -f "$dest/$filename" ]]; then
81
+ actual="$(instruction_file_identity "$dest/$filename")"
82
+ else
83
+ actual="missing"
84
+ fi
85
+ if [[ "$actual" == "$expected" ]]; then
86
+ _pass "$label preserves existing $filename byte-for-byte"
87
+ else
88
+ _fail "$label changed or deleted existing $filename (expected identity: $expected; actual: $actual)"
89
+ fi
90
+ done
91
+ }
92
+
93
+ # Every install path starts with distinct repository-owned instructions. Kiro's
94
+ # fixture is intentionally at the rsync --delete destination root so deletion,
95
+ # not only replacement, is covered by this regression test.
96
+ seed_instruction_fixture "$KIRO_DEST" "direct-kiro-delete-sync"
97
+ seed_instruction_fixture "$BASE_DEST" "direct-base"
98
+ seed_instruction_fixture "$CLAUDE_DEST" "direct-claude-code"
99
+ seed_instruction_fixture "$CODEX_DEST" "direct-codex"
100
+ seed_instruction_fixture "$OPENCODE_DEST" "direct-opencode"
101
+ seed_instruction_fixture "$PI_DEST" "direct-pi"
102
+ seed_instruction_fixture "$BASE_INIT_DEST" "cli-base"
103
+ seed_instruction_fixture "$CODEX_INIT_DEST" "cli-codex"
104
+ seed_instruction_fixture "$OPENCODE_FULL_DEST" "cli-opencode"
105
+
51
106
  echo ""
52
107
  echo "--- Install ---"
53
- if (cd "$ROOT_DIR/dist/kiro" && bash install.sh "$KIRO_DEST" >/dev/null); then
108
+ if (cd "$ROOT_DIR/dist/kiro" && bash install.sh "$KIRO_DEST/" >/dev/null); then
54
109
  _pass "Kiro install succeeded"
55
110
  else
56
111
  _fail "Kiro install failed"
57
112
  fi
113
+ assert_instruction_fixture_preserved "$KIRO_DEST" "Kiro direct install with trailing-slash destination and rsync --delete"
58
114
 
59
115
  if (cd "$ROOT_DIR/dist/base" && bash install.sh "$BASE_DEST" >/dev/null); then
60
116
  _pass "Base install succeeded"
61
117
  else
62
118
  _fail "Base install failed"
63
119
  fi
120
+ assert_instruction_fixture_preserved "$BASE_DEST" "Base direct install"
64
121
 
65
122
  if (cd "$ROOT_DIR/dist/claude-code" && bash install.sh "$CLAUDE_DEST" >/dev/null); then
66
123
  _pass "Claude Code install succeeded"
67
124
  else
68
125
  _fail "Claude Code install failed"
69
126
  fi
127
+ assert_instruction_fixture_preserved "$CLAUDE_DEST" "Claude Code direct install"
70
128
 
71
129
  if (cd "$ROOT_DIR/dist/codex" && bash install.sh "$CODEX_DEST" >/dev/null); then
72
130
  _pass "Codex install succeeded"
73
131
  else
74
132
  _fail "Codex install failed"
75
133
  fi
134
+ assert_instruction_fixture_preserved "$CODEX_DEST" "Codex direct install"
76
135
 
77
136
  if (cd "$ROOT_DIR/dist/codex" && bash install.sh "$CODEX_CONSOLE_DEST" --telemetry-sink local-kontour-console --console-token-file "$CONSOLE_TOKEN_FILE" --console-tenant tenant-a >/dev/null); then
78
137
  _pass "Codex install with Console telemetry config succeeded"
@@ -118,17 +177,19 @@ else
118
177
  _pass "Codex install rejects unsafe hosted Console http URL"
119
178
  fi
120
179
 
121
- if node "$ROOT_DIR/build/src/cli.js" init --dest "$BASE_INIT_DEST" --telemetry-sink local-kontour-console --yes >/dev/null; then
180
+ if node "$ROOT_DIR/build/src/cli.js" init --runtime base --dest "$BASE_INIT_DEST" --telemetry-sink local-kontour-console --yes >/dev/null; then
122
181
  _pass "flow-agents init headless base install succeeded"
123
182
  else
124
183
  _fail "flow-agents init headless base install failed"
125
184
  fi
185
+ assert_instruction_fixture_preserved "$BASE_INIT_DEST" "flow-agents init Base install"
126
186
 
127
187
  if node "$ROOT_DIR/build/src/cli.js" init --runtime codex --dest "$CODEX_INIT_DEST" --telemetry-sink local-kontour-console --console-tenant tenant-a --activate-kits --yes >/dev/null; then
128
188
  _pass "flow-agents init headless Codex install succeeded"
129
189
  else
130
190
  _fail "flow-agents init headless Codex install failed"
131
191
  fi
192
+ assert_instruction_fixture_preserved "$CODEX_INIT_DEST" "flow-agents init Codex install"
132
193
 
133
194
  echo ""
134
195
  echo "--- Guided Console-Connect Wizard (G2/G3): headless regression + summary/verify ---"
@@ -266,6 +327,7 @@ if (cd "$ROOT_DIR/dist/opencode" && bash install.sh "$OPENCODE_DEST" >/dev/null)
266
327
  else
267
328
  _fail "opencode install failed"
268
329
  fi
330
+ assert_instruction_fixture_preserved "$OPENCODE_DEST" "OpenCode direct install"
269
331
 
270
332
  if (cd "$ROOT_DIR/dist/opencode" && bash install.sh "$OPENCODE_CONSOLE_DEST" --telemetry-sink local-kontour-console --console-token-file "$CONSOLE_TOKEN_FILE" --console-tenant tenant-oc >/dev/null); then
271
333
  _pass "opencode install with Console telemetry config succeeded"
@@ -278,12 +340,14 @@ if node "$ROOT_DIR/build/src/cli.js" init --runtime opencode --dest "$OPENCODE_F
278
340
  else
279
341
  _fail "flow-agents init headless opencode install failed"
280
342
  fi
343
+ assert_instruction_fixture_preserved "$OPENCODE_FULL_DEST" "flow-agents init OpenCode install"
281
344
 
282
345
  if (cd "$ROOT_DIR/dist/pi" && bash install.sh "$PI_DEST" >/dev/null); then
283
346
  _pass "pi install succeeded"
284
347
  else
285
348
  _fail "pi install failed"
286
349
  fi
350
+ assert_instruction_fixture_preserved "$PI_DEST" "pi direct install"
287
351
 
288
352
  USER_SKILLS_DIR="$CODEX_FULL_DEST/.agents/sk""ills/user-skill"
289
353
  mkdir -p "$CODEX_FULL_DEST/.codex/ag""ents" "$USER_SKILLS_DIR"
@@ -323,7 +387,7 @@ done
323
387
 
324
388
  echo ""
325
389
  echo "--- Placeholder Rewriting ---"
326
- if rg -n '__KIRO_PACKAGE_ROOT__' "$KIRO_DEST" >/tmp/kiro-placeholder-leaks.txt 2>/dev/null; then
390
+ if rg -n --glob '!AGENTS.md' --glob '!CLAUDE.md' '__KIRO_PACKAGE_ROOT__' "$KIRO_DEST" >/tmp/kiro-placeholder-leaks.txt 2>/dev/null; then
327
391
  _fail "Kiro install left placeholder tokens behind (see /tmp/kiro-placeholder-leaks.txt)"
328
392
  else
329
393
  _pass "Kiro install rewrote package root placeholders"
@@ -373,13 +437,13 @@ else
373
437
  _fail "flow-agents init did not persist Console config or activate Codex kits"
374
438
  fi
375
439
 
376
- if [[ -f "$BASE_INIT_DEST/AGENTS.md" ]] \
440
+ if [[ -f "$BASE_INIT_DEST/README.md" ]] \
377
441
  && [[ -d "$BASE_INIT_DEST/.flow-agents" ]] \
378
442
  && [[ -d "$BASE_INIT_DEST/.kontourai/flow-agents" ]] \
379
443
  && rg -F -q "console_telemetry_url=$LOCAL_KONTOUR_CONSOLE_URL" "$BASE_INIT_DEST/scripts/telemetry/telemetry.conf"; then
380
- _pass "flow-agents init default installs base AGENTS.md workspace contract"
444
+ _pass "flow-agents init default installs base documented workspace contract"
381
445
  else
382
- _fail "flow-agents init default did not install base AGENTS.md workspace contract"
446
+ _fail "flow-agents init default did not install base documented workspace contract"
383
447
  fi
384
448
 
385
449
  if rg -F -q "console_telemetry_url=$LOCAL_KONTOUR_CONSOLE_URL" "$OPENCODE_CONSOLE_DEST/scripts/telemetry/telemetry.conf" \
@@ -636,11 +700,18 @@ fi
636
700
 
637
701
  KIRO_WORKSPACE="$TMPDIR_EVAL/kiro-workspace"
638
702
  mkdir -p "$KIRO_WORKSPACE"
639
- if node - "$CLAUDE_DEST" "$CODEX_DEST" "$KIRO_DEST" "$KIRO_WORKSPACE" <<'NODE'
703
+ if node - "$CLAUDE_DEST" "$CODEX_DEST" "$KIRO_DEST" "$KIRO_WORKSPACE" "$ROOT_DIR/scripts/hooks/lib/current-pointer.js" <<'NODE'
640
704
  const fs = require("node:fs");
641
705
  const path = require("node:path");
642
706
  const { spawnSync } = require("node:child_process");
643
- const [claudeDest, codexDest, kiroDest, kiroWorkspace] = process.argv.slice(2);
707
+ const [claudeDest, codexDest, kiroDest, kiroWorkspace, currentPointerHelperPath] = process.argv.slice(2);
708
+ const { writePerActorCurrent } = require(currentPointerHelperPath);
709
+ // #440 FIXTURE-GAP: this fixture was written before #440's per-actor ownership scoping and never
710
+ // established a per-actor current pointer for the invoking actor -- under a RESOLVED ambient
711
+ // actor, workflow-steering.js's actorScopedWorkflowState now scopes to that actor's own
712
+ // (nonexistent) pointer and never surfaces the WORKFLOW STATE ATTENTION banner. Give each
713
+ // runCommand invocation a stable, explicit actor below (see FLOW_AGENTS_ACTOR in env).
714
+ const installedHookActor = "eval-actor-bundle-install-installed-hook";
644
715
  const state = {
645
716
  schema_version: "1.0",
646
717
  task_slug: "installed-hook-demo",
@@ -661,6 +732,10 @@ function writeFixture(root) {
661
732
  fs.writeFileSync(path.join(taskDir, "trust.bundle"), JSON.stringify(trustBundle), "utf8");
662
733
  fs.mkdirSync(path.join(root, "docs"), { recursive: true });
663
734
  fs.writeFileSync(path.join(root, "docs/context-map.md"), "# Context Map\n", "utf8");
735
+ const flowAgentsDir = path.join(root, ".kontourai/flow-agents");
736
+ const currentPayload = { schema_version: "1.0", active_slug: "installed-hook-demo", artifact_dir: "installed-hook-demo" };
737
+ fs.writeFileSync(path.join(flowAgentsDir, "current.json"), JSON.stringify(currentPayload, null, 2) + "\n");
738
+ writePerActorCurrent(flowAgentsDir, installedHookActor, currentPayload);
664
739
  }
665
740
  function eventGroups(file, ...names) {
666
741
  const hooks = JSON.parse(fs.readFileSync(file, "utf8")).hooks || {};
@@ -683,7 +758,7 @@ function workflowCommand(file, ...eventNames) {
683
758
  }
684
759
  function runCommand(label, command, cwd, runtimeJson) {
685
760
  const payload = JSON.stringify({ hook_event_name: "UserPromptSubmit", cwd, prompt: "continue" });
686
- const env = { ...process.env, SA_HOOK_PROFILE: "standard", CLAUDE_PROJECT_DIR: cwd };
761
+ const env = { ...process.env, SA_HOOK_PROFILE: "standard", CLAUDE_PROJECT_DIR: cwd, FLOW_AGENTS_ACTOR: installedHookActor };
687
762
  if (label === "Codex") env.CODEX_HOME = cwd;
688
763
  const result = spawnSync(command, { input: payload, cwd, env, shell: true, encoding: "utf8", timeout: 30000 });
689
764
  if (result.status !== 0) throw new Error(`${label} installed hook failed: rc=${result.status} stdout=${result.stdout} stderr=${result.stderr}`);
@@ -709,11 +784,14 @@ fi
709
784
  # Execute the opencode plugin's workflow-steering command path directly
710
785
  OPENCODE_WORKSPACE="$TMPDIR_EVAL/opencode-exec-workspace"
711
786
  mkdir -p "$OPENCODE_WORKSPACE"
712
- if node - "$OPENCODE_DEST" "$OPENCODE_WORKSPACE" <<'NODE'
787
+ if node - "$OPENCODE_DEST" "$OPENCODE_WORKSPACE" "$ROOT_DIR/scripts/hooks/lib/current-pointer.js" <<'NODE'
713
788
  const fs = require("node:fs");
714
789
  const path = require("node:path");
715
790
  const { spawnSync } = require("node:child_process");
716
- const [opencodeDest, opencodeWorkspace] = process.argv.slice(2);
791
+ const [opencodeDest, opencodeWorkspace, currentPointerHelperPath] = process.argv.slice(2);
792
+ const { writePerActorCurrent } = require(currentPointerHelperPath);
793
+ // #440 FIXTURE-GAP: see the installed-prompt-submit block above for the full rationale.
794
+ const opencodeHookActor = "eval-actor-bundle-install-opencode-hook";
717
795
  const state = {
718
796
  schema_version: "1.0",
719
797
  task_slug: "opencode-hook-demo",
@@ -730,6 +808,10 @@ function writeFixture(root) {
730
808
  fs.writeFileSync(path.join(taskDir, "trust.bundle"), JSON.stringify(trustBundle), "utf8");
731
809
  fs.mkdirSync(path.join(root, "docs"), { recursive: true });
732
810
  fs.writeFileSync(path.join(root, "docs/context-map.md"), "# Context Map\n", "utf8");
811
+ const flowAgentsDir = path.join(root, ".kontourai/flow-agents");
812
+ const currentPayload = { schema_version: "1.0", active_slug: "opencode-hook-demo", artifact_dir: "opencode-hook-demo" };
813
+ fs.writeFileSync(path.join(flowAgentsDir, "current.json"), JSON.stringify(currentPayload, null, 2) + "\n");
814
+ writePerActorCurrent(flowAgentsDir, opencodeHookActor, currentPayload);
733
815
  }
734
816
  function runOpencodeAdapter(bundleDest, cwd) {
735
817
  const adapterPath = path.join(bundleDest, "scripts", "hooks", "opencode-hook-adapter.js");
@@ -737,7 +819,7 @@ function runOpencodeAdapter(bundleDest, cwd) {
737
819
  const result = spawnSync(process.execPath, [adapterPath, "UserPromptSubmit", "workflow-steering", "workflow-steering.js", "default"], {
738
820
  input: payload,
739
821
  cwd,
740
- env: { ...process.env, SA_HOOK_PROFILE: "standard", FLOW_AGENTS_HOOK_RUNTIME: "opencode" },
822
+ env: { ...process.env, SA_HOOK_PROFILE: "standard", FLOW_AGENTS_HOOK_RUNTIME: "opencode", FLOW_AGENTS_ACTOR: opencodeHookActor },
741
823
  encoding: "utf8",
742
824
  timeout: 30000,
743
825
  });
@@ -761,11 +843,14 @@ fi
761
843
  # Execute the pi extension's hook adapter command path directly
762
844
  PI_WORKSPACE="$TMPDIR_EVAL/pi-exec-workspace"
763
845
  mkdir -p "$PI_WORKSPACE"
764
- if node - "$PI_DEST" "$PI_WORKSPACE" <<'NODE'
846
+ if node - "$PI_DEST" "$PI_WORKSPACE" "$ROOT_DIR/scripts/hooks/lib/current-pointer.js" <<'NODE'
765
847
  const fs = require("node:fs");
766
848
  const path = require("node:path");
767
849
  const { spawnSync } = require("node:child_process");
768
- const [piDest, piWorkspace] = process.argv.slice(2);
850
+ const [piDest, piWorkspace, currentPointerHelperPath] = process.argv.slice(2);
851
+ const { writePerActorCurrent } = require(currentPointerHelperPath);
852
+ // #440 FIXTURE-GAP: see the installed-prompt-submit block above for the full rationale.
853
+ const piHookActor = "eval-actor-bundle-install-pi-hook";
769
854
  const state = {
770
855
  schema_version: "1.0",
771
856
  task_slug: "pi-hook-demo",
@@ -782,6 +867,10 @@ function writeFixture(root) {
782
867
  fs.writeFileSync(path.join(taskDir, "trust.bundle"), JSON.stringify(trustBundle), "utf8");
783
868
  fs.mkdirSync(path.join(root, "docs"), { recursive: true });
784
869
  fs.writeFileSync(path.join(root, "docs/context-map.md"), "# Context Map\n", "utf8");
870
+ const flowAgentsDir = path.join(root, ".kontourai/flow-agents");
871
+ const currentPayload = { schema_version: "1.0", active_slug: "pi-hook-demo", artifact_dir: "pi-hook-demo" };
872
+ fs.writeFileSync(path.join(flowAgentsDir, "current.json"), JSON.stringify(currentPayload, null, 2) + "\n");
873
+ writePerActorCurrent(flowAgentsDir, piHookActor, currentPayload);
785
874
  }
786
875
  function runPiAdapter(bundleDest, cwd) {
787
876
  const adapterPath = path.join(bundleDest, "scripts", "hooks", "pi-hook-adapter.js");
@@ -789,7 +878,7 @@ function runPiAdapter(bundleDest, cwd) {
789
878
  const result = spawnSync(process.execPath, [adapterPath, "UserPromptSubmit", "workflow-steering", "workflow-steering.js", "default"], {
790
879
  input: payload,
791
880
  cwd,
792
- env: { ...process.env, SA_HOOK_PROFILE: "standard", FLOW_AGENTS_HOOK_RUNTIME: "pi" },
881
+ env: { ...process.env, SA_HOOK_PROFILE: "standard", FLOW_AGENTS_HOOK_RUNTIME: "pi", FLOW_AGENTS_ACTOR: piHookActor },
793
882
  encoding: "utf8",
794
883
  timeout: 30000,
795
884
  });
@@ -890,11 +979,11 @@ NODE
890
979
  )"
891
980
 
892
981
  package_flow() {
893
- (cd "$PACKAGE_PROJECT" && CODEX_SESSION_ID=packed-package-consumer node "$PACKAGE_CLI" workflow "$@")
982
+ (cd "$PACKAGE_PROJECT" && env -u CODEX_THREAD_ID CODEX_SESSION_ID=packed-package-consumer node "$PACKAGE_CLI" workflow "$@")
894
983
  }
895
984
 
896
985
  package_review() {
897
- (cd "$PACKAGE_PROJECT" && CODEX_SESSION_ID=packed-package-reviewer node "$PACKAGE_CLI" workflow "$@")
986
+ (cd "$PACKAGE_PROJECT" && env -u CODEX_THREAD_ID CODEX_SESSION_ID=packed-package-reviewer node "$PACKAGE_CLI" workflow "$@")
898
987
  }
899
988
 
900
989
  if (cd "$ROOT_DIR" && npm pack --silent --pack-destination "$TMPDIR_EVAL" >"$PACKAGE_PACK_LOG") \
@@ -528,11 +528,11 @@ fi
528
528
  DOGFOOD_WORKSPACE="$CLAUDE_IDEM" # reuse the installed workspace from the idempotent section
529
529
  mkdir -p "$DOGFOOD_WORKSPACE/.kontourai/flow-agents"
530
530
 
531
- if node - "$DOGFOOD_DEST2/.claude/settings.json" "$DOGFOOD_WORKSPACE" << 'NODE'
531
+ if node - "$DOGFOOD_DEST2/.claude/settings.json" "$DOGFOOD_WORKSPACE" "$ROOT_DIR/scripts/hooks/lib/current-pointer.js" << 'NODE'
532
532
  const fs = require("node:fs");
533
533
  const path = require("node:path");
534
534
  const { spawnSync } = require("node:child_process");
535
- const [settingsPath, workspace] = process.argv.slice(2);
535
+ const [settingsPath, workspace, currentPointerHelperPath] = process.argv.slice(2);
536
536
 
537
537
  // Write minimal fixtures for workflow-steering into the workspace
538
538
  const taskDir = path.join(workspace, ".kontourai", "flow-agents", "dogfood-hook-demo");
@@ -583,10 +583,24 @@ for (const group of groups) {
583
583
  }
584
584
  if (!wsCommand) throw new Error("workflow-steering hook command not found");
585
585
 
586
+ // #440 FIXTURE-GAP: this fixture was written before #440's per-actor ownership scoping and
587
+ // never established a per-actor current pointer for the invoking actor -- under a RESOLVED
588
+ // ambient actor (ancestry-derived locally, GITHUB_RUN_ID-derived CI-runtime in CI),
589
+ // workflow-steering.js's actorScopedWorkflowState now scopes to that actor's own (nonexistent)
590
+ // pointer and never surfaces the WORKFLOW STATE ATTENTION banner. Give the hook a stable,
591
+ // explicit actor and seed that actor's own per-actor pointer for dogfood-hook-demo, mirroring
592
+ // workflow-sidecar.ts's real writeCurrent() dual-write via current-pointer.js's own
593
+ // writePerActorCurrent.
594
+ const dogfoodActor = "eval-actor-bundle-lifecycle-dogfood";
595
+ const flowAgentsDir = path.join(workspace, ".kontourai", "flow-agents");
596
+ const currentPayload = { schema_version: "1.0", active_slug: "dogfood-hook-demo", artifact_dir: "dogfood-hook-demo" };
597
+ fs.writeFileSync(path.join(flowAgentsDir, "current.json"), JSON.stringify(currentPayload, null, 2) + "\n");
598
+ require(currentPointerHelperPath).writePerActorCurrent(flowAgentsDir, dogfoodActor, currentPayload);
599
+
586
600
  // Execute the hook. CLAUDE_PROJECT_DIR must point to the workspace that has scripts/hooks/.
587
601
  // In the real dogfood use case this is the repo root; here we use the installed test workspace.
588
602
  const payload = JSON.stringify({ hook_event_name: "UserPromptSubmit", cwd: workspace, prompt: "continue" });
589
- const env = { ...process.env, SA_HOOK_PROFILE: "standard", CLAUDE_PROJECT_DIR: workspace };
603
+ const env = { ...process.env, SA_HOOK_PROFILE: "standard", CLAUDE_PROJECT_DIR: workspace, FLOW_AGENTS_ACTOR: dogfoodActor };
590
604
  const result = spawnSync(wsCommand, {
591
605
  input: payload,
592
606
  cwd: workspace,
@@ -29,6 +29,17 @@ set -uo pipefail
29
29
  ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
30
30
  GATE="$ROOT/scripts/hooks/stop-goal-fit.js"
31
31
 
32
+ # #440 FIXTURE-GAP: this suite's fixtures were written before #440's per-actor ownership scoping
33
+ # and never establish a per-actor current pointer for the invoking actor -- under a RESOLVED
34
+ # ambient actor (ancestry-derived locally, GITHUB_RUN_ID-derived CI-runtime in CI), stop-goal-fit.js's
35
+ # analyze() now scopes to that actor's own (nonexistent) pointer and never reaches the
36
+ # fixture-under-test at all. This suite is about anti-gaming/gate mechanics, not #440's ownership
37
+ # scoping, so forcing the documented test-only unresolved-actor escape hatch restores EXACTLY this
38
+ # suite's pre-#440 behavior (D3 compat: an unresolved actor keeps the unchanged legacy-fallback/
39
+ # global-scan discovery every assertion below was written against).
40
+ export FLOW_AGENTS_ACTOR_TEST_FORCE_UNRESOLVED=1
41
+ export NODE_ENV=test
42
+
32
43
  export FLOW_AGENTS_GOAL_FIT_MAX_BLOCKS=100000
33
44
 
34
45
  TMP="$(mktemp -d)"
@@ -60,7 +60,7 @@ process.stdout.write(process.argv[2] === "source" ? r.source : r.actor);
60
60
  verify_hold_under_ci() {
61
61
  local dir="$1"; shift
62
62
  (
63
- unset FLOW_AGENTS_ACTOR CLAUDE_CODE_SESSION_ID CODEX_SESSION_ID OPENCODE_SESSION_ID PI_SESSION_ID CLAUDECODE
63
+ unset FLOW_AGENTS_ACTOR CLAUDE_CODE_SESSION_ID CODEX_THREAD_ID CODEX_SESSION_ID OPENCODE_SESSION_ID PI_SESSION_ID CLAUDECODE
64
64
  while [[ $# -gt 0 ]]; do export "${1?}"; shift; done
65
65
  flow_agents_node "$WRITER" verify-hold "$dir"
66
66
  )
@@ -150,7 +150,7 @@ CLI="$ROOT/build/src/cli.js"
150
150
  RT_ROOT="$TMPDIR_EVAL/rt-artifact-root"
151
151
  RT_SLUG="ci-roundtrip"
152
152
  (
153
- unset FLOW_AGENTS_ACTOR CLAUDE_CODE_SESSION_ID CODEX_SESSION_ID OPENCODE_SESSION_ID PI_SESSION_ID CLAUDECODE
153
+ unset FLOW_AGENTS_ACTOR CLAUDE_CODE_SESSION_ID CODEX_THREAD_ID CODEX_SESSION_ID OPENCODE_SESSION_ID PI_SESSION_ID CLAUDECODE
154
154
  export "${GHA[@]}"
155
155
  node "$CLI" assignment-provider claim --provider local-file --artifact-root "$RT_ROOT" \
156
156
  --subject-id "$RT_SLUG" --branch main --artifact-dir "$RT_SLUG"
@@ -18,6 +18,17 @@
18
18
  set -uo pipefail
19
19
 
20
20
  ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
21
+
22
+ # #440 FIXTURE-GAP: this suite's fixtures were written before #440's per-actor ownership scoping
23
+ # and never establish a per-actor current pointer for the invoking actor -- under a RESOLVED
24
+ # ambient actor (ancestry-derived locally, GITHUB_RUN_ID-derived CI-runtime in CI), stop-goal-fit.js's
25
+ # analyze() now scopes to that actor's own (nonexistent) pointer and never reaches the
26
+ # fixture-under-test at all. This suite is about anti-gaming/gate mechanics, not #440's ownership
27
+ # scoping, so forcing the documented test-only unresolved-actor escape hatch restores EXACTLY this
28
+ # suite's pre-#440 behavior (D3 compat: an unresolved actor keeps the unchanged legacy-fallback/
29
+ # global-scan discovery every assertion below was written against).
30
+ export FLOW_AGENTS_ACTOR_TEST_FORCE_UNRESOLVED=1
31
+ export NODE_ENV=test
21
32
  CAPTURE="$ROOT/scripts/hooks/evidence-capture.js"
22
33
  GATE="$ROOT/scripts/hooks/stop-goal-fit.js"
23
34
 
@@ -15,6 +15,17 @@ set -uo pipefail
15
15
 
16
16
  ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
17
17
  export GATE="$ROOT/scripts/hooks/stop-goal-fit.js"
18
+
19
+ # #440 FIXTURE-GAP: this suite's fixtures were written before #440's per-actor ownership scoping
20
+ # and never establish a per-actor current pointer for the invoking actor -- under a RESOLVED
21
+ # ambient actor (ancestry-derived locally, GITHUB_RUN_ID-derived CI-runtime in CI), stop-goal-fit.js's
22
+ # analyze() now scopes to that actor's own (nonexistent) pointer and never reaches the
23
+ # fixture-under-test at all. This suite is about anti-gaming/gate mechanics, not #440's ownership
24
+ # scoping, so forcing the documented test-only unresolved-actor escape hatch restores EXACTLY this
25
+ # suite's pre-#440 behavior (D3 compat: an unresolved actor keeps the unchanged legacy-fallback/
26
+ # global-scan discovery every assertion below was written against).
27
+ export FLOW_AGENTS_ACTOR_TEST_FORCE_UNRESOLVED=1
28
+ export NODE_ENV=test
18
29
  REPAIR="$ROOT/scripts/repair-command-log.js"
19
30
 
20
31
  TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
@@ -16,6 +16,17 @@
16
16
  set -uo pipefail
17
17
 
18
18
  ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
19
+
20
+ # #440 FIXTURE-GAP: this suite's fixtures were written before #440's per-actor ownership scoping
21
+ # and never establish a per-actor current pointer for the invoking actor -- under a RESOLVED
22
+ # ambient actor (ancestry-derived locally, GITHUB_RUN_ID-derived CI-runtime in CI), stop-goal-fit.js's
23
+ # analyze() now scopes to that actor's own (nonexistent) pointer and never reaches the
24
+ # fixture-under-test at all. This suite is about anti-gaming/gate mechanics, not #440's ownership
25
+ # scoping, so forcing the documented test-only unresolved-actor escape hatch restores EXACTLY this
26
+ # suite's pre-#440 behavior (D3 compat: an unresolved actor keeps the unchanged legacy-fallback/
27
+ # global-scan discovery every assertion below was written against).
28
+ export FLOW_AGENTS_ACTOR_TEST_FORCE_UNRESOLVED=1
29
+ export NODE_ENV=test
19
30
  CAPTURE="$ROOT/scripts/hooks/evidence-capture.js"
20
31
  GATE="$ROOT/scripts/hooks/stop-goal-fit.js"
21
32