@kontourai/flow-agents 3.6.0 → 3.7.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 (86) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/build/src/builder-flow-run-adapter.d.ts +22 -2
  3. package/build/src/builder-flow-run-adapter.js +93 -28
  4. package/build/src/builder-flow-runtime.d.ts +8 -3
  5. package/build/src/builder-flow-runtime.js +308 -47
  6. package/build/src/cli/assignment-provider.d.ts +4 -7
  7. package/build/src/cli/assignment-provider.js +67 -13
  8. package/build/src/cli/builder-run.js +14 -18
  9. package/build/src/cli/workflow-sidecar.d.ts +28 -4
  10. package/build/src/cli/workflow-sidecar.js +801 -97
  11. package/build/src/cli/workflow.js +290 -42
  12. package/build/src/flow-kit/validate.d.ts +17 -0
  13. package/build/src/flow-kit/validate.js +340 -2
  14. package/build/src/index.js +5 -5
  15. package/build/src/lib/observed-command.d.ts +7 -0
  16. package/build/src/lib/observed-command.js +100 -0
  17. package/build/src/tools/generate-context-map.js +5 -3
  18. package/context/scripts/hooks/lib/kit-catalog.js +1 -1
  19. package/context/scripts/hooks/stop-goal-fit.js +78 -9
  20. package/docs/context-map.md +22 -20
  21. package/docs/developer-architecture.md +1 -1
  22. package/docs/public-workflow-cli.md +76 -7
  23. package/docs/skills-map.md +51 -27
  24. package/docs/spec/builder-flow-runtime.md +41 -40
  25. package/docs/workflow-usage-guide.md +109 -42
  26. package/evals/fixtures/hook-influence/cases.json +2 -2
  27. package/evals/integration/test_builder_entry_enforcement.sh +52 -41
  28. package/evals/integration/test_builder_step_producers.sh +297 -6
  29. package/evals/integration/test_bundle_install.sh +212 -63
  30. package/evals/integration/test_critique_supersession_roundtrip.sh +21 -8
  31. package/evals/integration/test_current_json_per_actor.sh +12 -0
  32. package/evals/integration/test_dual_emit_flow_step.sh +62 -43
  33. package/evals/integration/test_flowdef_session_activation.sh +145 -25
  34. package/evals/integration/test_flowdef_session_history_preservation.sh +23 -21
  35. package/evals/integration/test_gate_lockdown.sh +3 -5
  36. package/evals/integration/test_goal_fit_hook.sh +60 -2
  37. package/evals/integration/test_liveness_verdict.sh +14 -17
  38. package/evals/integration/test_phase_map_and_gate_claim.sh +63 -38
  39. package/evals/integration/test_public_workflow_cli.sh +325 -11
  40. package/evals/integration/test_pull_work_liveness_preflight.sh +22 -66
  41. package/evals/integration/test_sidecar_field_preservation.sh +36 -11
  42. package/evals/integration/test_workflow_sidecar_writer.sh +277 -44
  43. package/evals/integration/test_workflow_steering_hook.sh +15 -38
  44. package/evals/run.sh +2 -0
  45. package/evals/static/test_builder_skill_coherence.sh +247 -0
  46. package/evals/static/test_library_exports.sh +5 -2
  47. package/evals/static/test_workflow_skills.sh +13 -325
  48. package/kits/builder/flows/build.flow.json +22 -0
  49. package/kits/builder/flows/shape.flow.json +9 -9
  50. package/kits/builder/kit.json +70 -16
  51. package/kits/builder/skills/builder-shape/SKILL.md +75 -75
  52. package/kits/builder/skills/continue-work/SKILL.md +45 -106
  53. package/kits/builder/skills/deliver/SKILL.md +96 -442
  54. package/kits/builder/skills/design-probe/SKILL.md +40 -139
  55. package/kits/builder/skills/evidence-gate/SKILL.md +59 -201
  56. package/kits/builder/skills/execute-plan/SKILL.md +54 -125
  57. package/kits/builder/skills/fix-bug/SKILL.md +42 -132
  58. package/kits/builder/skills/gate-review/SKILL.md +60 -211
  59. package/kits/builder/skills/idea-to-backlog/SKILL.md +63 -244
  60. package/kits/builder/skills/learning-review/SKILL.md +63 -170
  61. package/kits/builder/skills/pickup-probe/SKILL.md +54 -111
  62. package/kits/builder/skills/plan-work/SKILL.md +51 -185
  63. package/kits/builder/skills/pull-work/SKILL.md +136 -485
  64. package/kits/builder/skills/release-readiness/SKILL.md +66 -107
  65. package/kits/builder/skills/review-work/SKILL.md +89 -176
  66. package/kits/builder/skills/tdd-workflow/SKILL.md +53 -147
  67. package/kits/builder/skills/verify-work/SKILL.md +101 -113
  68. package/package.json +2 -2
  69. package/scripts/hooks/lib/kit-catalog.js +1 -1
  70. package/scripts/hooks/stop-goal-fit.js +78 -9
  71. package/src/builder-flow-run-adapter.ts +118 -32
  72. package/src/builder-flow-runtime.ts +331 -61
  73. package/src/cli/assignment-provider-lock.test.mjs +83 -0
  74. package/src/cli/assignment-provider.ts +62 -13
  75. package/src/cli/builder-flow-run-adapter.test.mjs +4 -2
  76. package/src/cli/builder-flow-runtime.test.mjs +194 -8
  77. package/src/cli/builder-run.ts +3 -9
  78. package/src/cli/kit-metadata-security.test.mjs +232 -6
  79. package/src/cli/public-api.test.mjs +15 -0
  80. package/src/cli/workflow-sidecar-execution-proof.test.mjs +90 -0
  81. package/src/cli/workflow-sidecar.ts +724 -97
  82. package/src/cli/workflow.ts +277 -40
  83. package/src/flow-kit/validate.ts +320 -2
  84. package/src/index.ts +6 -5
  85. package/src/lib/observed-command.ts +96 -0
  86. package/src/tools/generate-context-map.ts +5 -3
@@ -36,6 +36,15 @@ WRITER="workflow-sidecar"
36
36
  VALIDATOR="validate-workflow-artifacts"
37
37
  ARTIFACT_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/auto-sidecars"
38
38
  mkdir -p "$ARTIFACT_DIR"
39
+ # Every direct critique write must identify a review lane. Individual focused cases below
40
+ # additionally prove local artifact hashing and stale-target rejection.
41
+ CRITIQUE_LANE='{"id":"code-review","status":"pass","summary":"Fixture code review completed.","evidence_refs":[{"kind":"external","url":"https://example.invalid/review-fixture","summary":"Fixture review evidence."}]}'
42
+
43
+ canonical_review_target() {
44
+ local session_dir="$1"
45
+ local suffix="${session_dir#*/.kontourai/flow-agents/}"
46
+ printf '.kontourai/flow-agents/%s/%s--deliver.md' "$suffix" "$(basename "$session_dir")"
47
+ }
39
48
 
40
49
  DEFAULT_ROOT_REPO="$TMPDIR_EVAL/default-root-repo"
41
50
  mkdir -p "$DEFAULT_ROOT_REPO"
@@ -951,12 +960,12 @@ else
951
960
  _fail "sidecar writer evidence failed: $(cat "$TMPDIR_EVAL/evidence.out" "$TMPDIR_EVAL/evidence.err")"
952
961
  fi
953
962
 
954
- # Phase 4c: acceptance.json criteria status no longer updated at verification time (bundle-only).
955
- # State is verified; bundle claims carry the criteria status.
956
- if rg -q '"status": "verified"' "$ARTIFACT_DIR/state.json" && [[ -f "$ARTIFACT_DIR/trust.bundle" ]] && node -e 'const fs=require("fs"); const b=JSON.parse(fs.readFileSync(process.argv[1],"utf8")); const ac=b.claims.filter(c=>c.claimType==="workflow.acceptance.criterion"); if(ac.length===0) throw new Error("no acceptance criterion claims in bundle"); if(ac.some(c=>c.value!=="pass")) throw new Error("some acceptance criterion not pass in bundle: "+JSON.stringify(ac.map(c=>c.value)));' "$ARTIFACT_DIR/trust.bundle" 2>/dev/null; then
957
- _pass "sidecar writer updates state and records acceptance in bundle from evidence"
963
+ # A generic evidence write cannot fabricate acceptance success. Criteria remain pending until a
964
+ # passing tests-evidence gate claim supplies one structured proof per declared criterion.
965
+ if rg -q '"status": "verified"' "$ARTIFACT_DIR/state.json" && [[ -f "$ARTIFACT_DIR/trust.bundle" ]] && node -e 'const fs=require("fs"); const b=JSON.parse(fs.readFileSync(process.argv[1],"utf8")); const ac=b.claims.filter(c=>c.claimType==="workflow.acceptance.criterion"); if(ac.length===0) throw new Error("no acceptance criterion claims in bundle"); if(ac.some(c=>c.value==="pass")) throw new Error("generic evidence fabricated acceptance pass: "+JSON.stringify(ac.map(c=>c.value)));' "$ARTIFACT_DIR/trust.bundle" 2>/dev/null; then
966
+ _pass "sidecar writer preserves unverified acceptance criteria during generic evidence writes"
958
967
  else
959
- _fail "sidecar writer did not update state or bundle from evidence"
968
+ _fail "sidecar writer did not preserve state and bundle acceptance evidence correctly"
960
969
  fi
961
970
 
962
971
  # ─── WS8 (ADR 0020) AC1: producer evidence classification ─────────────────────
@@ -1461,7 +1470,8 @@ if flow_agents_node "$WRITER" record-critique "$ARTIFACT_DIR" \
1461
1470
  --reviewer tool-code-reviewer \
1462
1471
  --verdict pass \
1463
1472
  --summary "No blocking findings." \
1464
- --artifact-ref auto-sidecars--deliver.md \
1473
+ --artifact-ref "$(canonical_review_target "$ARTIFACT_DIR")" \
1474
+ --lane-json "$CRITIQUE_LANE" \
1465
1475
  --timestamp "2026-05-09T00:02:00Z" >"$TMPDIR_EVAL/critique.out" 2>"$TMPDIR_EVAL/critique.err"; then
1466
1476
  _pass "sidecar writer records passing critique"
1467
1477
  else
@@ -1486,6 +1496,8 @@ FLOW_AGENTS_WORKFLOW_SIDECAR_LOCK_DELAY=0.2 flow_agents_node "$WRITER" record-cr
1486
1496
  --reviewer tool-code-reviewer \
1487
1497
  --verdict pass \
1488
1498
  --summary "Concurrent review A passed." \
1499
+ --artifact-ref "$(canonical_review_target "$CONCURRENT_DIR")" \
1500
+ --lane-json "$CRITIQUE_LANE" \
1489
1501
  --timestamp "2026-05-09T00:02:00Z" >"$TMPDIR_EVAL/concurrent-a.out" 2>"$TMPDIR_EVAL/concurrent-a.err" &
1490
1502
  pid_a=$!
1491
1503
  FLOW_AGENTS_WORKFLOW_SIDECAR_LOCK_DELAY=0.2 flow_agents_node "$WRITER" record-critique "$CONCURRENT_DIR" \
@@ -1493,6 +1505,8 @@ FLOW_AGENTS_WORKFLOW_SIDECAR_LOCK_DELAY=0.2 flow_agents_node "$WRITER" record-cr
1493
1505
  --reviewer tool-code-reviewer \
1494
1506
  --verdict pass \
1495
1507
  --summary "Concurrent review B passed." \
1508
+ --artifact-ref "$(canonical_review_target "$CONCURRENT_DIR")" \
1509
+ --lane-json "$CRITIQUE_LANE" \
1496
1510
  --timestamp "2026-05-09T00:02:01Z" >"$TMPDIR_EVAL/concurrent-b.out" 2>"$TMPDIR_EVAL/concurrent-b.err" &
1497
1511
  pid_b=$!
1498
1512
  wait "$pid_a"
@@ -2167,14 +2181,13 @@ if flow_agents_node "$WRITER" dogfood-pass \
2167
2181
  --require-critique \
2168
2182
  --critique-id dogfood-clean-review \
2169
2183
  --critique-verdict pass \
2170
- --critique-summary "New critique is clean but prior critique is still open." \
2171
- --summary "Should fail before evidence." >"$TMPDIR_EVAL/dogfood-existing-dirty-critique.out" 2>&1; then
2172
- _fail "dogfood-pass should reject existing dirty critique before evidence writes"
2173
- elif rg -q 'requires clean critique before recording pass evidence' "$TMPDIR_EVAL/dogfood-existing-dirty-critique.out" \
2174
- && [[ ! -f "$DIRTY_CRITIQUE_DOGFOOD_DIR/evidence.json" ]]; then
2175
- _pass "dogfood-pass rejects existing dirty critique before partial evidence writes"
2184
+ --critique-summary "New bundle critique is clean; legacy residue is ignored." \
2185
+ --artifact-ref "$(canonical_review_target "$DIRTY_CRITIQUE_DOGFOOD_DIR")" \
2186
+ --lane-json "$CRITIQUE_LANE" \
2187
+ --summary "Legacy critique.json cannot block bundle-only evidence." >"$TMPDIR_EVAL/dogfood-existing-dirty-critique.out" 2>&1; then
2188
+ _pass "dogfood-pass ignores a dirty legacy critique.json and records a clean bundle critique"
2176
2189
  else
2177
- _fail "dogfood-pass existing dirty critique was not fail-closed"
2190
+ _fail "dogfood-pass let legacy critique.json wedge bundle-only completion: $(cat "$TMPDIR_EVAL/dogfood-existing-dirty-critique.out")"
2178
2191
  fi
2179
2192
 
2180
2193
  FAILED_DOGFOOD_DIR="$TMPDIR_EVAL/repo/.kontourai/flow-agents/dogfood-failed-pass"
@@ -2194,6 +2207,8 @@ if flow_agents_node "$WRITER" dogfood-pass \
2194
2207
  --critique-id dogfood-failed-review \
2195
2208
  --critique-verdict fail \
2196
2209
  --critique-summary "Failed critique should be recorded for routing." \
2210
+ --artifact-ref "$(canonical_review_target "$FAILED_DOGFOOD_DIR")" \
2211
+ --lane-json "$CRITIQUE_LANE" \
2197
2212
  --finding-json '{"id":"failed-dogfood-finding","severity":"high","status":"open","description":"Failed dogfood finding remains open."}' \
2198
2213
  --summary "Dogfood pass failed and should route back to execution." \
2199
2214
  --timestamp "2026-05-09T00:04:30Z" >"$TMPDIR_EVAL/dogfood-failed-pass.out" 2>"$TMPDIR_EVAL/dogfood-failed-pass.err"; then
@@ -2232,6 +2247,8 @@ if flow_agents_node "$WRITER" dogfood-pass \
2232
2247
  --reviewer tool-code-reviewer \
2233
2248
  --critique-verdict pass \
2234
2249
  --critique-summary "Dogfood critique passed." \
2250
+ --artifact-ref "$(canonical_review_target "$DOGFOOD_DIR")" \
2251
+ --lane-json "$CRITIQUE_LANE" \
2235
2252
  --learning-record-json '{"id":"dogfood-learning","source_refs":["evidence.json","critique.json"],"outcome":"success","facts":["Dogfood pass command recorded evidence and critique."],"interpretation":"Dogfood pass can close a clean local loop.","routing":[{"target":"none","action":"No follow-up required.","status":"completed"}],"correction":{"needed":false,"evidence":"Evidence, critique, and learning matched intended dogfood behavior."}}' \
2236
2253
  --learning-summary "Dogfood command learning recorded." \
2237
2254
  --summary "Dogfood pass completed." \
@@ -2291,6 +2308,8 @@ if flow_agents_node "$WRITER" dogfood-pass \
2291
2308
  --reviewer tool-code-reviewer \
2292
2309
  --critique-verdict pass \
2293
2310
  --critique-summary "Dogfood release critique passed." \
2311
+ --artifact-ref "$(canonical_review_target "$DOGFOOD_DIR")" \
2312
+ --lane-json "$CRITIQUE_LANE" \
2294
2313
  --release-decision merge \
2295
2314
  --release-scope "Dogfood pass release readiness." \
2296
2315
  --release-summary "Dogfood pass can record release readiness after clean evidence and critique." \
@@ -2585,7 +2604,9 @@ if flow_agents_node "$WRITER" record-critique "$BAD_DIR" \
2585
2604
  --reviewer tool-code-reviewer \
2586
2605
  --verdict pass \
2587
2606
  --summary "Open finding should fail." \
2607
+ --artifact-ref "$(canonical_review_target "$BAD_DIR")" \
2588
2608
  --finding-json '{"id":"open-medium","severity":"medium","status":"open","description":"Open finding."}' \
2609
+ --lane-json "$CRITIQUE_LANE" \
2589
2610
  --timestamp "2026-05-09T00:02:00Z" >"$TMPDIR_EVAL/bad-critique.out" 2>&1; then
2590
2611
  _fail "sidecar writer should reject open critique findings"
2591
2612
  elif rg -q 'required critique must pass' "$TMPDIR_EVAL/bad-critique.out"; then
@@ -2734,6 +2755,8 @@ flow_agents_node "$WRITER" record-critique "$TB_CRITIQUE_DIR" \
2734
2755
  --reviewer tool-code-reviewer \
2735
2756
  --verdict fail \
2736
2757
  --summary "Critique failed — blocking finding." \
2758
+ --artifact-ref "$(canonical_review_target "$TB_CRITIQUE_DIR")" \
2759
+ --lane-json "$CRITIQUE_LANE" \
2737
2760
  --timestamp "2026-05-09T00:02:00Z" >"$TMPDIR_EVAL/tb-critique-fail.out" 2>"$TMPDIR_EVAL/tb-critique-fail.err" || true
2738
2761
 
2739
2762
  # Record a passing critique (verdict pass, no open findings → claim status verified)
@@ -2742,6 +2765,8 @@ if flow_agents_node "$WRITER" record-critique "$TB_CRITIQUE_DIR" \
2742
2765
  --reviewer tool-code-reviewer \
2743
2766
  --verdict pass \
2744
2767
  --summary "Critique passed — no blocking findings." \
2768
+ --artifact-ref "$(canonical_review_target "$TB_CRITIQUE_DIR")" \
2769
+ --lane-json "$CRITIQUE_LANE" \
2745
2770
  --timestamp "2026-05-09T00:02:30Z" >"$TMPDIR_EVAL/tb-critique-pass.out" 2>"$TMPDIR_EVAL/tb-critique-pass.err" \
2746
2771
  && [[ -f "$TB_CRITIQUE_DIR/trust.bundle" ]]; then
2747
2772
  if node --input-type=module <<NODEOF 2>"$TMPDIR_EVAL/tb-critique-assert.err"
@@ -2887,6 +2912,8 @@ if flow_agents_node "$WRITER" record-critique "$TB_UNSTAMPED_DIR" \
2887
2912
  --reviewer tool-code-reviewer \
2888
2913
  --verdict pass \
2889
2914
  --summary "Should never be recorded -- the bundle is unstamped." \
2915
+ --artifact-ref "$(canonical_review_target "$TB_UNSTAMPED_DIR")" \
2916
+ --lane-json "$CRITIQUE_LANE" \
2890
2917
  --timestamp "2026-05-09T00:02:00Z" >"$TMPDIR_EVAL/tb-unstamped-critique.out" 2>"$TMPDIR_EVAL/tb-unstamped-critique.err"; then
2891
2918
  _fail "record-critique should refuse to read an unstamped (pre-#344) trust.bundle, not silently reclassify it by claimType"
2892
2919
  elif rg -q 'pre-supersession trust\.bundle' "$TMPDIR_EVAL/tb-unstamped-critique.out" "$TMPDIR_EVAL/tb-unstamped-critique.err" \
@@ -3204,7 +3231,7 @@ cp "$ARTIFACT_DIR/auto-sidecars--deliver.md" "$TB_FO_DIR/failopen--deliver.md"
3204
3231
  flow_agents_node "$WRITER" init-plan "$TB_FO_DIR/failopen--deliver.md" --task-slug failopen --source-request x --summary y --next-action z --timestamp "2026-05-09T00:00:00Z" >/dev/null 2>&1
3205
3232
  flow_agents_node "$WRITER" record-evidence "$TB_FO_DIR" --verdict pass --check-json '{"id":"c1","kind":"test","status":"pass","summary":"s"}' --timestamp "2026-05-09T00:01:00Z" >/dev/null 2>&1
3206
3233
  # With Surface forced-unavailable, record-critique MUST fail (non-zero), not silently drop the critique.
3207
- if FLOW_AGENTS_SURFACE_UNAVAILABLE=1 flow_agents_node "$WRITER" record-critique "$TB_FO_DIR" --id rev-fo --reviewer r --verdict pass --summary fo --timestamp "2026-05-09T00:02:00Z" >"$TMPDIR_EVAL/failopen.out" 2>&1; then
3234
+ if FLOW_AGENTS_SURFACE_UNAVAILABLE=1 flow_agents_node "$WRITER" record-critique "$TB_FO_DIR" --id rev-fo --reviewer r --verdict pass --summary fo --artifact-ref "$(canonical_review_target "$TB_FO_DIR")" --lane-json "$CRITIQUE_LANE" --timestamp "2026-05-09T00:02:00Z" >"$TMPDIR_EVAL/failopen.out" 2>&1; then
3208
3235
  _fail "record-critique fail-opened (exit 0) when Surface unavailable — SILENT DATA LOSS: $(cat "$TMPDIR_EVAL/failopen.out")"
3209
3236
  elif grep -qiE "was NOT written|not persisted" "$TMPDIR_EVAL/failopen.out"; then
3210
3237
  _pass "bundle-writers fail loudly (no silent data loss) when Surface unavailable (#156)"
@@ -3402,7 +3429,7 @@ if flow_agents_node "$WRITER" ensure-session \
3402
3429
  --summary "Should fail." \
3403
3430
  --timestamp "2026-06-25T00:00:06Z" >"$TMPDIR_EVAL/wi-bad-slash.out" 2>&1; then
3404
3431
  _fail "ensure-session should reject work-item ref without # separator"
3405
- elif grep -q 'owner/repo#id format' "$TMPDIR_EVAL/wi-bad-slash.out"; then
3432
+ elif grep -q 'provider-neutral provider:id ref or owner/repo#numeric-id' "$TMPDIR_EVAL/wi-bad-slash.out"; then
3406
3433
  _pass "ensure-session rejects work-item ref without # separator"
3407
3434
  else
3408
3435
  _fail "malformed ref rejection message was unexpected: $(cat "$TMPDIR_EVAL/wi-bad-slash.out")"
@@ -3532,11 +3559,13 @@ else
3532
3559
  fi
3533
3560
 
3534
3561
  # ─── AC1/AC10: record-gate-claim (pull-work-gate/selected-work) with --command ───────────────
3562
+ printf '%s\n' "# Pull Work Report" "" "Selected Work Item: local:${COMPOSE_SLUG}" > "$COMPOSE_DIR/${COMPOSE_SLUG}--pull-work.md"
3535
3563
  if flow_agents_node "$WRITER" record-gate-claim "$COMPOSE_DIR" \
3536
3564
  --actor compose-actor \
3537
3565
  --status pass \
3538
3566
  --summary "Work item selected for compose session" \
3539
- --command "npm run validate:source --" \
3567
+ --command "test -f .kontourai/flow-agents/compose-270/compose-270--pull-work.md" \
3568
+ --evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/compose-270/compose-270--pull-work.md","summary":"Concrete pull-work selection report naming local:compose-270."}' \
3540
3569
  --timestamp "2026-07-05T09:07:00Z" >"$TMPDIR_EVAL/compose-gate1.out" 2>"$TMPDIR_EVAL/compose-gate1.err"; then
3541
3570
  _pass "compose: record-gate-claim (selected-work) with --command succeeds"
3542
3571
  else
@@ -3554,7 +3583,7 @@ if (!buildClaim || !manualClaim) { process.stderr.write('record-gate-claim clobb
3554
3583
  if (!gateClaim) { process.stderr.write('gate claim missing metadata.gate_claim stamp\n'); process.exit(1); }
3555
3584
  if (gateClaim.claimType !== 'builder.pull-work.selected' || gateClaim.subjectType !== 'work-item') { process.stderr.write('gate claim typed incorrectly: ' + gateClaim.claimType + '/' + gateClaim.subjectType + '\n'); process.exit(1); }
3556
3585
  const ev = bundle.evidence.find((e) => e.claimId === gateClaim.id);
3557
- if (!ev || !ev.execution || ev.execution.label !== 'npm run validate:source --') { process.stderr.write('AC10: --command did not stamp execution.label on the gate claim evidence\n'); process.exit(1); }
3586
+ if (!ev || !ev.execution || ev.execution.label !== 'test -f .kontourai/flow-agents/compose-270/compose-270--pull-work.md') { process.stderr.write('AC10: --command did not stamp execution.label on the gate claim evidence\n'); process.exit(1); }
3558
3587
  if (claims.length !== 4) { process.stderr.write('expected exactly 4 claims after record-gate-claim (2 checks + 1 acceptance + 1 gate claim), got ' + claims.length + '\n'); process.exit(1); }
3559
3588
  NODEOF
3560
3589
  then
@@ -3581,11 +3610,15 @@ fi
3581
3610
 
3582
3611
  # ─── AC1: a SECOND gate claim against a DIFFERENT expectation (plan-gate/implementation-plan) ──
3583
3612
  # is additive, not a replacement, of the FIRST gate claim (pull-work-gate/selected-work) ───────
3613
+ printf '%s\n' "# Implementation Plan" "" "Files: evals/integration/test_workflow_sidecar_writer.sh" "Evidence: acceptance.json, handoff.json" > "$COMPOSE_DIR/${COMPOSE_SLUG}--plan-work.md"
3584
3614
  if flow_agents_node "$WRITER" record-gate-claim "$COMPOSE_DIR" \
3585
3615
  --actor compose-actor \
3586
3616
  --status pass \
3587
3617
  --summary "Implementation plan recorded for compose session" \
3588
3618
  --expectation implementation-plan \
3619
+ --evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/compose-270/compose-270--plan-work.md","summary":"Concrete plan-work report for the implementation plan."}' \
3620
+ --evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/compose-270/acceptance.json","summary":"Declared acceptance criteria for the implementation plan."}' \
3621
+ --evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/compose-270/handoff.json","summary":"Plan handoff for the implementation plan."}' \
3589
3622
  --timestamp "2026-07-05T09:07:30Z" >"$TMPDIR_EVAL/compose-gate2.out" 2>"$TMPDIR_EVAL/compose-gate2.err"; then
3590
3623
  _pass "compose: second record-gate-claim (different expectation, implementation-plan) succeeds"
3591
3624
  else
@@ -3631,6 +3664,8 @@ if flow_agents_node "$WRITER" record-critique "$COMPOSE_DIR" \
3631
3664
  --reviewer compose-reviewer \
3632
3665
  --verdict pass \
3633
3666
  --summary "Compose review looks good" \
3667
+ --artifact-ref "$(canonical_review_target "$COMPOSE_DIR")" \
3668
+ --lane-json "$CRITIQUE_LANE" \
3634
3669
  --timestamp "2026-07-05T09:09:00Z" >"$TMPDIR_EVAL/compose-critique.out" 2>"$TMPDIR_EVAL/compose-critique.err"; then
3635
3670
  _pass "compose: record-critique rebuilds the bundle at the new active step (merge-ready)"
3636
3671
  else
@@ -3665,6 +3700,180 @@ else
3665
3700
  _fail "AC2: gate-claim-typing-survives-rebuild assertion failed: $(cat "$TMPDIR_EVAL/compose-ac2-assert.err")"
3666
3701
  fi
3667
3702
 
3703
+ # Legacy critique.json is unsupported historical residue. It must neither alter the
3704
+ # authoritative bundle nor create a live blocker during a subsequent critique write.
3705
+ LEGACY_CRITIQUE_PROJECT="$TMPDIR_EVAL/legacy-critique-project"
3706
+ LEGACY_CRITIQUE_ROOT="$LEGACY_CRITIQUE_PROJECT/.kontourai/flow-agents"
3707
+ mkdir -p "$LEGACY_CRITIQUE_PROJECT/.kontourai"
3708
+ cp -r "$COMPOSE_ROOT" "$LEGACY_CRITIQUE_ROOT"
3709
+ LEGACY_CRITIQUE_DIR="$LEGACY_CRITIQUE_ROOT/$COMPOSE_SLUG"
3710
+ cat > "$LEGACY_CRITIQUE_DIR/critique.json" <<'JSON'
3711
+ {
3712
+ "critiques": [
3713
+ {"id":"compose-review","reviewer":"legacy-reviewer","reviewed_at":"2026-07-05T08:00:00Z","verdict":"fail","summary":"Stale legacy duplicate must not replace the bundle record.","findings":[]},
3714
+ {"id":"legacy-only-review","reviewer":"legacy-reviewer","reviewed_at":"2026-07-05T08:01:00Z","verdict":"not_verified","summary":"Legacy-only migration record.","findings":[]}
3715
+ ]
3716
+ }
3717
+ JSON
3718
+ if flow_agents_node "$WRITER" record-critique "$LEGACY_CRITIQUE_DIR" \
3719
+ --id bundle-only-review \
3720
+ --reviewer compose-reviewer \
3721
+ --verdict pass \
3722
+ --summary "Bundle-only review looks good" \
3723
+ --artifact-ref "$(canonical_review_target "$LEGACY_CRITIQUE_DIR")" \
3724
+ --lane-json "$CRITIQUE_LANE" \
3725
+ --timestamp "2026-07-05T09:09:30Z" >"$TMPDIR_EVAL/legacy-critique.out" 2>"$TMPDIR_EVAL/legacy-critique.err"; then
3726
+ if node --input-type=module <<NODEOF 2>"$TMPDIR_EVAL/legacy-critique-assert.err"
3727
+ import { readFileSync } from 'node:fs';
3728
+ const bundle = JSON.parse(readFileSync('${LEGACY_CRITIQUE_DIR}/trust.bundle', 'utf8'));
3729
+ const critiques = bundle.claims.filter((claim) => claim.metadata?.origin === 'critique');
3730
+ const byId = new Map(critiques.map((claim) => [String(claim.subjectId).split('/').pop(), claim]));
3731
+ if (critiques.length !== 2 || byId.size !== 2) process.exit(1);
3732
+ if (byId.get('compose-review')?.value !== 'pass') process.exit(2);
3733
+ if (byId.has('legacy-only-review')) process.exit(3);
3734
+ if (byId.get('bundle-only-review')?.value !== 'pass') process.exit(4);
3735
+ NODEOF
3736
+ then
3737
+ _pass "legacy critique.json cannot alter or wedge authoritative bundle critiques"
3738
+ else
3739
+ _fail "legacy critique isolation assertion failed: $(cat "$TMPDIR_EVAL/legacy-critique-assert.err")"
3740
+ fi
3741
+ else
3742
+ _fail "legacy critique isolation write failed: $(cat "$TMPDIR_EVAL/legacy-critique.out" "$TMPDIR_EVAL/legacy-critique.err")"
3743
+ fi
3744
+
3745
+ # ─── Tests-evidence writer: immutable criteria, multiple observed commands, non-vacuous scripts ──
3746
+ MULTI_PROJECT="$TMPDIR_EVAL/multi-observed-project"
3747
+ MULTI_ROOT="$MULTI_PROJECT/.kontourai/flow-agents"
3748
+ MULTI_SLUG="multi-observed"
3749
+ MULTI_DIR="$MULTI_ROOT/$MULTI_SLUG"
3750
+ mkdir -p "$MULTI_ROOT" "$MULTI_PROJECT/checks" "$MULTI_PROJECT/test"
3751
+ printf '#!/usr/bin/env bash\nset -eu\ntest -f "$1"\nprintf "1..1\\nok 1 - fixture exists\\n"\n' > "$MULTI_PROJECT/checks/test-one.sh"
3752
+ printf '#!/usr/bin/env bash\nset -eu\ntest -f "$1"\nprintf "1..1\\nok 1 - fixture exists\\n"\n' > "$MULTI_PROJECT/checks/test-two.sh"
3753
+ chmod +x "$MULTI_PROJECT/checks/test-one.sh" "$MULTI_PROJECT/checks/test-two.sh"
3754
+ printf 'import test from "node:test";\nimport assert from "node:assert/strict";\ntest("fixture", () => assert.equal(1, 1));\n' > "$MULTI_PROJECT/test/sample.test.mjs"
3755
+ cat > "$MULTI_PROJECT/package.json" <<'JSON'
3756
+ {"scripts":{"test":"true","check":"echo check"}}
3757
+ JSON
3758
+ flow_agents_node "$WRITER" ensure-session \
3759
+ --artifact-root "$MULTI_ROOT" --task-slug "$MULTI_SLUG" --actor multi-observed-actor \
3760
+ --title "Multiple observed commands" --summary "Verify immutable acceptance criteria." \
3761
+ --criterion "First immutable criterion" --criterion "Second immutable criterion" \
3762
+ --timestamp "2026-07-11T11:00:00Z" >"$TMPDIR_EVAL/multi-seed.out" 2>"$TMPDIR_EVAL/multi-seed.err"
3763
+ flow_agents_node "$WRITER" advance-state "$MULTI_DIR" \
3764
+ --actor multi-observed-actor --status in_progress --phase verification \
3765
+ --summary "Move fixture to tests evidence." --next-action "Verify commands." \
3766
+ --flow-definition builder.build --timestamp "2026-07-11T11:00:10Z" >"$TMPDIR_EVAL/multi-advance.out" 2>"$TMPDIR_EVAL/multi-advance.err"
3767
+ printf '# Plan evidence\n' > "$MULTI_DIR/$MULTI_SLUG--plan-work.md"
3768
+ if flow_agents_node "$WRITER" record-critique "$MULTI_DIR" \
3769
+ --id multi-review --reviewer multi-reviewer --verdict pass --summary "Review completed." \
3770
+ --artifact-ref "$(canonical_review_target "$MULTI_DIR")" --lane-json "$CRITIQUE_LANE" \
3771
+ --timestamp "2026-07-11T11:00:20Z" >"$TMPDIR_EVAL/multi-critique.out" 2>"$TMPDIR_EVAL/multi-critique.err"; then
3772
+ _pass "tests-evidence fixture records a clean critique with a record-time snapshot"
3773
+ else
3774
+ _fail "tests-evidence fixture critique failed: $(cat "$TMPDIR_EVAL/multi-critique.out" "$TMPDIR_EVAL/multi-critique.err")"
3775
+ fi
3776
+
3777
+ MULTI_ONE="bash checks/test-one.sh test/sample.test.mjs"
3778
+ MULTI_TWO="bash checks/test-two.sh test/sample.test.mjs"
3779
+ MULTI_DIGEST="e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
3780
+ MULTI_REF_ONE="{\"kind\":\"command\",\"excerpt\":\"$MULTI_ONE\",\"summary\":\"First exact command.\"}"
3781
+ MULTI_REF_TWO="{\"kind\":\"command\",\"excerpt\":\"$MULTI_TWO\",\"summary\":\"Second exact command.\"}"
3782
+ MULTI_OBS_ONE="{\"command\":\"$MULTI_ONE\",\"exit_code\":0,\"test_count\":1,\"output_sha256\":\"$MULTI_DIGEST\"}"
3783
+ MULTI_OBS_TWO="{\"command\":\"$MULTI_TWO\",\"exit_code\":0,\"test_count\":1,\"output_sha256\":\"$MULTI_DIGEST\"}"
3784
+
3785
+ MULTI_VACUOUS_REJECTED=yes
3786
+ for multi_script_command in "npm test" "pnpm test" "yarn test" "bun run test"; do
3787
+ if flow_agents_node "$WRITER" record-gate-claim "$MULTI_DIR" \
3788
+ --actor multi-observed-actor --expectation tests-evidence --status pass --summary "Vacuous package script." \
3789
+ --command "$multi_script_command" --evidence-ref-json "$MULTI_REF_ONE" \
3790
+ --criterion-json "{\"id\":\"first-immutable-criterion\",\"status\":\"pass\",\"evidence_refs\":[$MULTI_REF_ONE]}" \
3791
+ --criterion-json "{\"id\":\"second-immutable-criterion\",\"status\":\"pass\",\"evidence_refs\":[$MULTI_REF_ONE]}" \
3792
+ >"$TMPDIR_EVAL/multi-vacuous.out" 2>"$TMPDIR_EVAL/multi-vacuous.err"; then
3793
+ MULTI_VACUOUS_REJECTED=no
3794
+ fi
3795
+ done
3796
+ if [ "$MULTI_VACUOUS_REJECTED" = yes ]; then
3797
+ _pass "tests-evidence resolves npm/pnpm/yarn/bun scripts and rejects vacuous package bodies"
3798
+ else
3799
+ _fail "tests-evidence accepted a vacuous npm/pnpm/yarn/bun package script"
3800
+ fi
3801
+
3802
+ cat > "$MULTI_PROJECT/package.json" <<'JSON'
3803
+ {"scripts":{"build":"tsc --noEmit","test":"node --test test/*.test.mjs","test:unit":"npm run build && node --test test/*.test.mjs"}}
3804
+ JSON
3805
+ if node --input-type=module - "$ROOT/build/src/cli/workflow-sidecar.js" "$MULTI_PROJECT" <<'NODE'
3806
+ const [modulePath, projectRoot] = process.argv.slice(2);
3807
+ const { isMeaningfulTestCommand } = await import(modulePath);
3808
+ if (!isMeaningfulTestCommand('npm test', projectRoot)) process.exit(1);
3809
+ if (!isMeaningfulTestCommand('npm run test:unit', projectRoot)) process.exit(2);
3810
+ NODE
3811
+ then
3812
+ _pass "tests-evidence accepts substantive direct and composed Node package test scripts"
3813
+ else
3814
+ _fail "tests-evidence rejected a substantive package test script"
3815
+ fi
3816
+ if flow_agents_node "$WRITER" record-critique "$MULTI_DIR" \
3817
+ --id multi-review --reviewer multi-reviewer --verdict pass --summary "Review refreshed after the package-script fixture changed." \
3818
+ --artifact-ref "$(canonical_review_target "$MULTI_DIR")" --lane-json "$CRITIQUE_LANE" \
3819
+ --timestamp "2026-07-11T11:00:25Z" >"$TMPDIR_EVAL/multi-critique-refresh.out" 2>"$TMPDIR_EVAL/multi-critique-refresh.err"; then
3820
+ _pass "tests-evidence fixture refreshes review after an intentional source change"
3821
+ else
3822
+ _fail "tests-evidence fixture failed to refresh stale review: $(cat "$TMPDIR_EVAL/multi-critique-refresh.out" "$TMPDIR_EVAL/multi-critique-refresh.err")"
3823
+ fi
3824
+
3825
+ if flow_agents_node "$WRITER" record-gate-claim "$MULTI_DIR" \
3826
+ --actor multi-observed-actor --expectation tests-evidence --status pass --summary "Rewritten criterion." \
3827
+ --command "$MULTI_ONE" --observed-command-json "$MULTI_OBS_ONE" --evidence-ref-json "$MULTI_REF_ONE" \
3828
+ --criterion-json "{\"id\":\"first-immutable-criterion\",\"description\":\"Caller rewrite.\",\"status\":\"pass\",\"evidence_refs\":[$MULTI_REF_ONE]}" \
3829
+ --criterion-json "{\"id\":\"second-immutable-criterion\",\"status\":\"pass\",\"evidence_refs\":[$MULTI_REF_ONE]}" \
3830
+ >"$TMPDIR_EVAL/multi-rewrite.out" 2>"$TMPDIR_EVAL/multi-rewrite.err"; then
3831
+ _fail "tests-evidence accepted caller rewriting an immutable criterion description"
3832
+ else
3833
+ _pass "tests-evidence rejects criterion description rewriting"
3834
+ fi
3835
+
3836
+ if flow_agents_node "$WRITER" record-gate-claim "$MULTI_DIR" \
3837
+ --actor multi-observed-actor --expectation tests-evidence --status pass --summary "Unknown criterion field." \
3838
+ --command "$MULTI_ONE" --observed-command-json "$MULTI_OBS_ONE" --evidence-ref-json "$MULTI_REF_ONE" \
3839
+ --criterion-json "{\"id\":\"first-immutable-criterion\",\"status\":\"pass\",\"unexpected\":true,\"evidence_refs\":[$MULTI_REF_ONE]}" \
3840
+ --criterion-json "{\"id\":\"second-immutable-criterion\",\"status\":\"pass\",\"evidence_refs\":[$MULTI_REF_ONE]}" \
3841
+ >"$TMPDIR_EVAL/multi-unknown.out" 2>"$TMPDIR_EVAL/multi-unknown.err"; then
3842
+ _fail "tests-evidence accepted an unknown criterion field"
3843
+ else
3844
+ _pass "tests-evidence rejects unknown criterion fields"
3845
+ fi
3846
+
3847
+ if flow_agents_node "$WRITER" record-gate-claim "$MULTI_DIR" \
3848
+ --actor multi-observed-actor --expectation tests-evidence --status pass --summary "Two exact observed commands." \
3849
+ --command "$MULTI_ONE" --command "$MULTI_TWO" \
3850
+ --observed-command-json "$MULTI_OBS_ONE" --observed-command-json "$MULTI_OBS_TWO" \
3851
+ --evidence-ref-json "{\"kind\":\"artifact\",\"file\":\".kontourai/flow-agents/$MULTI_SLUG/$MULTI_SLUG--plan-work.md\",\"summary\":\"Durable verification plan.\"}" \
3852
+ --evidence-ref-json "$MULTI_REF_ONE" --evidence-ref-json "$MULTI_REF_TWO" \
3853
+ --criterion-json "{\"id\":\"first-immutable-criterion\",\"status\":\"pass\",\"evidence_refs\":[$MULTI_REF_ONE]}" \
3854
+ --criterion-json "{\"id\":\"second-immutable-criterion\",\"status\":\"pass\",\"evidence_refs\":[$MULTI_REF_TWO]}" \
3855
+ --timestamp "2026-07-11T11:00:30Z" >"$TMPDIR_EVAL/multi-pass.out" 2>"$TMPDIR_EVAL/multi-pass.err"; then
3856
+ if node - "$MULTI_DIR/acceptance.json" "$MULTI_DIR/trust.bundle" <<'NODE'
3857
+ const fs = require('node:fs');
3858
+ const [acceptanceFile, bundleFile] = process.argv.slice(2);
3859
+ const acceptance = JSON.parse(fs.readFileSync(acceptanceFile, 'utf8'));
3860
+ if (acceptance.criteria[0].description !== 'First immutable criterion' || acceptance.criteria[1].description !== 'Second immutable criterion') process.exit(1);
3861
+ const bundle = JSON.parse(fs.readFileSync(bundleFile, 'utf8'));
3862
+ const gate = bundle.claims.find((claim) => claim.metadata?.gate_claim?.expectation_id === 'tests-evidence');
3863
+ const review = bundle.claims.find((claim) => claim.metadata?.origin === 'critique');
3864
+ if (!gate || gate.metadata?.observed_commands?.length !== 2) process.exit(2);
3865
+ const snapshot = review?.metadata?.review_target?.workspace_snapshot;
3866
+ if (!snapshot || snapshot.version !== 1 || snapshot.algorithm !== 'sha256' || typeof snapshot.digest !== 'string') process.exit(3);
3867
+ NODE
3868
+ then
3869
+ _pass "tests-evidence preserves immutable acceptance semantics and maps each criterion to its successful observed command"
3870
+ else
3871
+ _fail "tests-evidence multi-command or review snapshot assertion failed"
3872
+ fi
3873
+ else
3874
+ _fail "tests-evidence multi-command write failed: $(cat "$TMPDIR_EVAL/multi-pass.out" "$TMPDIR_EVAL/multi-pass.err")"
3875
+ fi
3876
+
3668
3877
  # ─── AC4: compose-three-writer (five writer calls total: evidence x2, gate-claim x2, critique, ──
3669
3878
  # then record-learning) — all claim families present and correctly typed in the final bundle ──
3670
3879
  if flow_agents_node "$WRITER" record-learning "$COMPOSE_DIR" \
@@ -4053,7 +4262,9 @@ fi
4053
4262
  # buildTrustBundle) needs them to resolve the flow definition for stamp validation. Copying only
4054
4263
  # the session dir would silently make sessionFlowId resolve to null, which changes what's being
4055
4264
  # tested (the "no flow definition resolvable" edge case) rather than the forged-stamp case.
4056
- FORGED_ROOT="$TMPDIR_EVAL/forged-stamp-root"
4265
+ FORGED_PROJECT="$TMPDIR_EVAL/forged-stamp-project"
4266
+ FORGED_ROOT="$FORGED_PROJECT/.kontourai/flow-agents"
4267
+ mkdir -p "$FORGED_PROJECT/.kontourai"
4057
4268
  cp -r "$COMPOSE_ROOT" "$FORGED_ROOT"
4058
4269
  FORGED_DIR="$FORGED_ROOT/$COMPOSE_SLUG"
4059
4270
 
@@ -4076,6 +4287,8 @@ if flow_agents_node "$WRITER" record-critique "$FORGED_DIR" \
4076
4287
  --reviewer forged-stamp-tester \
4077
4288
  --verdict pass \
4078
4289
  --summary "Rebuild attempt against a forged gate_claim stamp" \
4290
+ --artifact-ref "$(canonical_review_target "$FORGED_DIR")" \
4291
+ --lane-json "$CRITIQUE_LANE" \
4079
4292
  --timestamp "2026-07-05T09:12:00Z" >"$TMPDIR_EVAL/forged-critique.out" 2>"$TMPDIR_EVAL/forged-critique.err"; then
4080
4293
  _fail "FORGED-STAMP REGRESSION: record-critique succeeded against a bundle with a forged metadata.gate_claim.expectation_id (silent re-typing fall-through, the #270 CRITICAL/HIGH defect)"
4081
4294
  elif grep -qi "forged or corrupt" "$TMPDIR_EVAL/forged-critique.out" "$TMPDIR_EVAL/forged-critique.err" \
@@ -4089,7 +4302,9 @@ fi
4089
4302
  # claim (origin:"check", check_kind:"external", kit-typed claimType) — mimicking the real
4090
4303
  # kontourai-flow-agents-303 bundle shape (a claim recorded before the gate_claim stamp existed).
4091
4304
  # Any rebuild must die with the re-record remedy, never silently re-type via matchExpectsEntry.
4092
- PRECLUSTER_ROOT="$TMPDIR_EVAL/precluster-missing-stamp-root"
4305
+ PRECLUSTER_PROJECT="$TMPDIR_EVAL/precluster-missing-stamp-project"
4306
+ PRECLUSTER_ROOT="$PRECLUSTER_PROJECT/.kontourai/flow-agents"
4307
+ mkdir -p "$PRECLUSTER_PROJECT/.kontourai"
4093
4308
  cp -r "$COMPOSE_ROOT" "$PRECLUSTER_ROOT"
4094
4309
  PRECLUSTER_DIR="$PRECLUSTER_ROOT/$COMPOSE_SLUG"
4095
4310
 
@@ -4112,6 +4327,8 @@ if flow_agents_node "$WRITER" record-critique "$PRECLUSTER_DIR" \
4112
4327
  --reviewer precluster-tester \
4113
4328
  --verdict pass \
4114
4329
  --summary "Rebuild attempt against a pre-cluster-270 unstamped gate claim" \
4330
+ --artifact-ref "$(canonical_review_target "$PRECLUSTER_DIR")" \
4331
+ --lane-json "$CRITIQUE_LANE" \
4115
4332
  --timestamp "2026-07-05T09:13:00Z" >"$TMPDIR_EVAL/precluster-critique.out" 2>"$TMPDIR_EVAL/precluster-critique.err"; then
4116
4333
  _fail "PRE-CLUSTER-270 REGRESSION: record-critique succeeded against a bundle with an unstamped gate-claim-shaped claim (silent re-typing fall-through)"
4117
4334
  elif grep -qi "pre-cluster-270 gate claim" "$TMPDIR_EVAL/precluster-critique.out" "$TMPDIR_EVAL/precluster-critique.err" \
@@ -4207,6 +4424,8 @@ if flow_agents_node "$WRITER" record-critique "$COLLISION_DIR" \
4207
4424
  --reviewer collision-tester \
4208
4425
  --verdict pass \
4209
4426
  --summary "Rebuild after the rejected collision write — must succeed, no landmine" \
4427
+ --artifact-ref "$(canonical_review_target "$COLLISION_DIR")" \
4428
+ --lane-json "$CRITIQUE_LANE" \
4210
4429
  --timestamp "2026-07-05T09:16:30Z" >"$TMPDIR_EVAL/collision-rebuild.out" 2>"$TMPDIR_EVAL/collision-rebuild.err"; then
4211
4430
  _pass "#270 HIGH: a rebuild (record-critique) after the rejected collision write succeeds cleanly — no rebuild-time landmine"
4212
4431
  else
@@ -4222,31 +4441,32 @@ UNRESOLVABLE_ROOT="$TMPDIR_EVAL/unresolvable-project/.kontourai/flow-agents"
4222
4441
  UNRESOLVABLE_SLUG="unresolvable-270"
4223
4442
  UNRESOLVABLE_DIR="$UNRESOLVABLE_ROOT/$UNRESOLVABLE_SLUG"
4224
4443
  mkdir -p "$UNRESOLVABLE_ROOT"
4444
+ mkdir -p "$UNRESOLVABLE_DIR"
4445
+ printf '%s\n' "# Pull Work Report" "" "Selected Work Item: unresolvable:270" > "$UNRESOLVABLE_DIR/${UNRESOLVABLE_SLUG}--pull-work.md"
4225
4446
 
4226
- flow_agents_node "$WRITER" ensure-session \
4447
+ FLOW_AGENTS_ACTOR=unresolvable-actor node "$ROOT/build/src/cli.js" workflow start \
4227
4448
  --artifact-root "$UNRESOLVABLE_ROOT" \
4228
- --task-slug "$UNRESOLVABLE_SLUG" \
4229
- --actor unresolvable-actor \
4230
- --flow-id builder.build \
4231
- --skip-ownership-guard \
4449
+ --flow builder.build \
4450
+ --work-item unresolvable:270 \
4451
+ --assignment-provider local-file \
4232
4452
  --title "Unresolvable FlowDefinition regression" \
4233
4453
  --source-request "Regression: an unresolvable FlowDefinition must die with the dedicated cannot-be-loaded message." \
4234
4454
  --summary "Seed session with a REAL flow so a real stamped gate claim can be recorded." \
4235
- --criterion "Unresolvable FlowDefinition dies with the dedicated message" \
4236
- --timestamp "2026-07-05T09:17:00Z" >"$TMPDIR_EVAL/unresolvable-seed.out" 2>"$TMPDIR_EVAL/unresolvable-seed.err"
4455
+ --criterion "Unresolvable FlowDefinition dies with the dedicated message" >"$TMPDIR_EVAL/unresolvable-seed.out" 2>"$TMPDIR_EVAL/unresolvable-seed.err"
4237
4456
 
4238
- if flow_agents_node "$WRITER" record-gate-claim "$UNRESOLVABLE_DIR" \
4239
- --actor unresolvable-actor \
4240
- --status pass \
4241
- --summary "Work item selected for unresolvable-flow-definition regression" \
4242
- --timestamp "2026-07-05T09:17:15Z" >"$TMPDIR_EVAL/unresolvable-gate-claim.out" 2>"$TMPDIR_EVAL/unresolvable-gate-claim.err"; then
4457
+ if node - "$UNRESOLVABLE_DIR/trust.bundle" <<'NODEOF'
4458
+ const fs = require('node:fs');
4459
+ const bundle = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
4460
+ if (!bundle.claims.some((claim) => claim.metadata?.gate_claim?.expectation_id === 'selected-work')) process.exit(1);
4461
+ NODEOF
4462
+ then
4243
4463
  _pass "#270 MEDIUM setup: real stamped gate claim recorded before breaking FlowDefinition resolution"
4244
4464
  else
4245
- _fail "#270 MEDIUM setup: record-gate-claim (real flow) failed: $(cat "$TMPDIR_EVAL/unresolvable-gate-claim.out" "$TMPDIR_EVAL/unresolvable-gate-claim.err")"
4465
+ _fail "#270 MEDIUM setup: public workflow did not record a real selected-work gate claim: $(cat "$TMPDIR_EVAL/unresolvable-seed.out" "$TMPDIR_EVAL/unresolvable-seed.err")"
4246
4466
  fi
4247
4467
 
4248
- # Break resolution: point BOTH the legacy and per-actor current-pointer files at a flow id with
4249
- # no corresponding kits/<kit>/flows/<name>.flow.json file.
4468
+ # Poison BOTH navigation pointers. Exact-session writers must continue to use the persisted
4469
+ # canonical flow_run from the session instead of treating ambient pointers as authority.
4250
4470
  node --input-type=module <<NODEOF 2>"$TMPDIR_EVAL/unresolvable-mutate.err"
4251
4471
  import { readFileSync, writeFileSync } from 'node:fs';
4252
4472
  for (const f of ['${UNRESOLVABLE_ROOT}/current.json', '${UNRESOLVABLE_ROOT}/current/unresolvable-actor.json']) {
@@ -4265,14 +4485,12 @@ if flow_agents_node "$WRITER" record-critique "$UNRESOLVABLE_DIR" \
4265
4485
  --reviewer unresolvable-tester \
4266
4486
  --verdict pass \
4267
4487
  --summary "Rebuild attempt against an unresolvable FlowDefinition" \
4488
+ --artifact-ref "$(canonical_review_target "$UNRESOLVABLE_DIR")" \
4489
+ --lane-json "$CRITIQUE_LANE" \
4268
4490
  --timestamp "2026-07-05T09:17:30Z" >"$TMPDIR_EVAL/unresolvable-critique.out" 2>"$TMPDIR_EVAL/unresolvable-critique.err"; then
4269
- _fail "#270 MEDIUM REGRESSION: record-critique succeeded against a stamped gate claim whose FlowDefinition cannot be loaded"
4270
- elif grep -qi "cannot be loaded" "$TMPDIR_EVAL/unresolvable-critique.out" "$TMPDIR_EVAL/unresolvable-critique.err" \
4271
- && grep -qi "cannot validate the gate-claim stamp" "$TMPDIR_EVAL/unresolvable-critique.out" "$TMPDIR_EVAL/unresolvable-critique.err" \
4272
- && ! grep -qi "forged or corrupt" "$TMPDIR_EVAL/unresolvable-critique.out" "$TMPDIR_EVAL/unresolvable-critique.err"; then
4273
- _pass "#270 MEDIUM: an unresolvable FlowDefinition dies with the dedicated 'cannot be loaded' message, naming the flow id — NEVER the 'forged or corrupt' message"
4491
+ _pass "#270 MEDIUM: record-critique ignores poisoned navigation pointers and validates stamped claims against the exact session FlowDefinition"
4274
4492
  else
4275
- _fail "#270 MEDIUM: unresolvable-FlowDefinition rejection message was unexpected: $(cat "$TMPDIR_EVAL/unresolvable-critique.out" "$TMPDIR_EVAL/unresolvable-critique.err")"
4493
+ _fail "#270 MEDIUM REGRESSION: poisoned navigation pointers blocked or redirected an exact-session critique rebuild: $(cat "$TMPDIR_EVAL/unresolvable-critique.out" "$TMPDIR_EVAL/unresolvable-critique.err")"
4276
4494
  fi
4277
4495
 
4278
4496
  # ─── Mutation test (#270 HIGH reserved-prefix guard): temporarily disable normalizeCheck's ──────
@@ -4364,10 +4582,12 @@ flow_agents_node "$WRITER" ensure-session \
4364
4582
  # not write trust.bundle yet) — a real, properly-stamped gate claim at pull-work-gate/selected-work,
4365
4583
  # exactly the compose-layer pattern above, so this session has a normal, valid bundle to seed the
4366
4584
  # mis-recorded claim alongside (never the sole content of the bundle).
4585
+ printf '%s\n' "# Pull Work Report" "" "Selected Work Item: local:${CORRECTION_SLUG}" > "$CORRECTION_DIR/${CORRECTION_SLUG}--pull-work.md"
4367
4586
  flow_agents_node "$WRITER" record-gate-claim "$CORRECTION_DIR" \
4368
4587
  --actor correction-actor \
4369
4588
  --status pass \
4370
4589
  --summary "Work item selected for correction-path session" \
4590
+ --evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/correction-270/correction-270--pull-work.md","summary":"Concrete pull-work selection report naming local:correction-270."}' \
4371
4591
  --timestamp "2026-07-05T09:10:50Z" >"$TMPDIR_EVAL/correction-gate-seed.out" 2>"$TMPDIR_EVAL/correction-gate-seed.err"
4372
4592
 
4373
4593
  if [[ ! -f "$CORRECTION_DIR/trust.bundle" ]]; then
@@ -4500,7 +4720,8 @@ flow_agents_node "$WRITER" ensure-session --artifact-root "$STAMPED_ROOT" --
4500
4720
 
4501
4721
  # Real record-gate-claim call — a genuine, properly-stamped gate claim (metadata.gate_claim is
4502
4722
  # stamped by buildTrustBundle itself, never hand-constructed).
4503
- flow_agents_node "$WRITER" record-gate-claim "$STAMPED_DIR" --actor stamped-claim-actor --status pass --summary "Work item selected for stamped-claim supersession session" --timestamp "2026-07-05T09:12:35Z" >"$TMPDIR_EVAL/stamped-claim-gate.out" 2>"$TMPDIR_EVAL/stamped-claim-gate.err"
4723
+ printf '%s\n' "# Pull Work Report" "" "Selected Work Item: local:${STAMPED_SLUG}" > "$STAMPED_DIR/${STAMPED_SLUG}--pull-work.md"
4724
+ flow_agents_node "$WRITER" record-gate-claim "$STAMPED_DIR" --actor stamped-claim-actor --status pass --summary "Work item selected for stamped-claim supersession session" --evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/stamped-claim-270/stamped-claim-270--pull-work.md","summary":"Concrete pull-work selection report naming local:stamped-claim-270."}' --timestamp "2026-07-05T09:12:35Z" >"$TMPDIR_EVAL/stamped-claim-gate.out" 2>"$TMPDIR_EVAL/stamped-claim-gate.err"
4504
4725
 
4505
4726
  if [[ ! -f "$STAMPED_DIR/trust.bundle" ]]; then
4506
4727
  _fail "eval setup: record-gate-claim did not create trust.bundle for the stamped-claim session: $(cat "$TMPDIR_EVAL/stamped-claim-gate.out" "$TMPDIR_EVAL/stamped-claim-gate.err")"
@@ -4747,16 +4968,22 @@ NODEOF
4747
4968
  cp "$MUTATION_SCRATCH/workflow-sidecar.mutated.js" "$DIST_SIDECAR"
4748
4969
  # Copy the WHOLE root again (see the FORGED_ROOT/PRECLUSTER_ROOT comment above) — same
4749
4970
  # current.json/current/<actor>.json resolution requirement applies here.
4750
- MUTATION_FORGED_ROOT="$TMPDIR_EVAL/mutation-forged-root"
4971
+ MUTATION_FORGED_PROJECT="$TMPDIR_EVAL/mutation-forged-project"
4972
+ MUTATION_FORGED_ROOT="$MUTATION_FORGED_PROJECT/.kontourai/flow-agents"
4973
+ mkdir -p "$MUTATION_FORGED_PROJECT/.kontourai"
4751
4974
  cp -r "$FORGED_ROOT" "$MUTATION_FORGED_ROOT"
4752
4975
  MUTATION_FORGED_DIR="$MUTATION_FORGED_ROOT/$COMPOSE_SLUG"
4753
- MUTATION_PRECLUSTER_ROOT="$TMPDIR_EVAL/mutation-precluster-root"
4976
+ MUTATION_PRECLUSTER_PROJECT="$TMPDIR_EVAL/mutation-precluster-project"
4977
+ MUTATION_PRECLUSTER_ROOT="$MUTATION_PRECLUSTER_PROJECT/.kontourai/flow-agents"
4978
+ mkdir -p "$MUTATION_PRECLUSTER_PROJECT/.kontourai"
4754
4979
  cp -r "$PRECLUSTER_ROOT" "$MUTATION_PRECLUSTER_ROOT"
4755
4980
  MUTATION_PRECLUSTER_DIR="$MUTATION_PRECLUSTER_ROOT/$COMPOSE_SLUG"
4756
4981
 
4757
4982
  if flow_agents_node "$WRITER" record-critique "$MUTATION_FORGED_DIR" \
4758
4983
  --id mutation-forged-review --reviewer mutation-tester --verdict pass \
4759
4984
  --summary "Mutation-test: forged-stamp guard disabled, must go red" \
4985
+ --artifact-ref "$(canonical_review_target "$MUTATION_FORGED_DIR")" \
4986
+ --lane-json "$CRITIQUE_LANE" \
4760
4987
  --timestamp "2026-07-05T09:14:00Z" >"$TMPDIR_EVAL/mutation-forged.out" 2>"$TMPDIR_EVAL/mutation-forged.err"; then
4761
4988
  _pass "mutation-test: with the stamp-mismatch guard neutered, the forged-stamp fixture now SUCCEEDS (eval correctly goes red without the guard, proving the guard is what the eval exercises)"
4762
4989
  else
@@ -4766,6 +4993,8 @@ NODEOF
4766
4993
  if flow_agents_node "$WRITER" record-critique "$MUTATION_PRECLUSTER_DIR" \
4767
4994
  --id mutation-precluster-review --reviewer mutation-tester --verdict pass \
4768
4995
  --summary "Mutation-test: pre-cluster-270 guard disabled, must go red" \
4996
+ --artifact-ref "$(canonical_review_target "$MUTATION_PRECLUSTER_DIR")" \
4997
+ --lane-json "$CRITIQUE_LANE" \
4769
4998
  --timestamp "2026-07-05T09:14:30Z" >"$TMPDIR_EVAL/mutation-precluster.out" 2>"$TMPDIR_EVAL/mutation-precluster.err"; then
4770
4999
  _pass "mutation-test: with the pre-cluster-270 unstamped guard neutered, the missing-stamp fixture now SUCCEEDS (eval correctly goes red without the guard)"
4771
5000
  else
@@ -4778,12 +5007,16 @@ NODEOF
4778
5007
  # Restore the real compiled guard immediately — never leave the mutated binary in place, and
4779
5008
  # re-run BOTH negatives to confirm the restored binary rejects them again (guard is back).
4780
5009
  cp "$MUTATION_SCRATCH/workflow-sidecar.orig.js" "$DIST_SIDECAR"
4781
- RESTORE_FORGED_ROOT="$TMPDIR_EVAL/restore-check-forged-root"
5010
+ RESTORE_FORGED_PROJECT="$TMPDIR_EVAL/restore-check-forged-project"
5011
+ RESTORE_FORGED_ROOT="$RESTORE_FORGED_PROJECT/.kontourai/flow-agents"
5012
+ mkdir -p "$RESTORE_FORGED_PROJECT/.kontourai"
4782
5013
  cp -r "$FORGED_ROOT" "$RESTORE_FORGED_ROOT"
4783
5014
  RESTORE_FORGED_DIR="$RESTORE_FORGED_ROOT/$COMPOSE_SLUG"
4784
5015
  if flow_agents_node "$WRITER" record-critique "$RESTORE_FORGED_DIR" \
4785
5016
  --id restore-check-review --reviewer restore-check-tester --verdict pass \
4786
5017
  --summary "Restore check: forged-stamp guard must be back after mutation-test cleanup" \
5018
+ --artifact-ref "$(canonical_review_target "$RESTORE_FORGED_DIR")" \
5019
+ --lane-json "$CRITIQUE_LANE" \
4787
5020
  --timestamp "2026-07-05T09:15:00Z" >"$TMPDIR_EVAL/mutation-restore.out" 2>"$TMPDIR_EVAL/mutation-restore.err"; then
4788
5021
  _fail "mutation-test cleanup REGRESSION: the forged-stamp guard did not come back after restoring the original compiled file"
4789
5022
  else