@kontourai/flow-agents 3.6.0 → 3.8.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 (96) hide show
  1. package/CHANGELOG.md +19 -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 +407 -51
  6. package/build/src/cli/assignment-provider.d.ts +4 -7
  7. package/build/src/cli/assignment-provider.js +80 -14
  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 +825 -103
  11. package/build/src/cli/workflow.js +301 -43
  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/build-universal-bundles.js +53 -3
  18. package/build/src/tools/generate-context-map.js +5 -3
  19. package/context/scripts/hooks/lib/kit-catalog.js +1 -1
  20. package/context/scripts/hooks/stop-goal-fit.js +78 -9
  21. package/docs/context-map.md +22 -20
  22. package/docs/developer-architecture.md +1 -1
  23. package/docs/getting-started.md +9 -1
  24. package/docs/public-workflow-cli.md +76 -7
  25. package/docs/skills-map.md +51 -27
  26. package/docs/spec/builder-flow-runtime.md +75 -40
  27. package/docs/workflow-usage-guide.md +109 -42
  28. package/evals/fixtures/hook-influence/cases.json +2 -2
  29. package/evals/integration/test_builder_entry_enforcement.sh +52 -41
  30. package/evals/integration/test_builder_step_producers.sh +330 -6
  31. package/evals/integration/test_bundle_install.sh +318 -65
  32. package/evals/integration/test_bundle_lifecycle.sh +4 -6
  33. package/evals/integration/test_critique_supersession_roundtrip.sh +21 -8
  34. package/evals/integration/test_current_json_per_actor.sh +12 -0
  35. package/evals/integration/test_dual_emit_flow_step.sh +62 -43
  36. package/evals/integration/test_flowdef_session_activation.sh +145 -25
  37. package/evals/integration/test_flowdef_session_history_preservation.sh +23 -21
  38. package/evals/integration/test_gate_lockdown.sh +3 -5
  39. package/evals/integration/test_goal_fit_hook.sh +60 -2
  40. package/evals/integration/test_install_merge.sh +18 -8
  41. package/evals/integration/test_liveness_verdict.sh +14 -17
  42. package/evals/integration/test_phase_map_and_gate_claim.sh +63 -38
  43. package/evals/integration/test_public_workflow_cli.sh +334 -14
  44. package/evals/integration/test_pull_work_liveness_preflight.sh +22 -66
  45. package/evals/integration/test_sidecar_field_preservation.sh +36 -11
  46. package/evals/integration/test_workflow_sidecar_writer.sh +277 -44
  47. package/evals/integration/test_workflow_steering_hook.sh +15 -38
  48. package/evals/run.sh +2 -0
  49. package/evals/static/test_builder_skill_coherence.sh +247 -0
  50. package/evals/static/test_library_exports.sh +5 -2
  51. package/evals/static/test_universal_bundles.sh +44 -1
  52. package/evals/static/test_workflow_skills.sh +13 -325
  53. package/kits/builder/flows/build.flow.json +22 -0
  54. package/kits/builder/flows/shape.flow.json +9 -9
  55. package/kits/builder/kit.json +70 -16
  56. package/kits/builder/skills/builder-shape/SKILL.md +75 -75
  57. package/kits/builder/skills/continue-work/SKILL.md +45 -106
  58. package/kits/builder/skills/deliver/SKILL.md +96 -442
  59. package/kits/builder/skills/design-probe/SKILL.md +40 -139
  60. package/kits/builder/skills/evidence-gate/SKILL.md +59 -201
  61. package/kits/builder/skills/execute-plan/SKILL.md +54 -125
  62. package/kits/builder/skills/fix-bug/SKILL.md +42 -132
  63. package/kits/builder/skills/gate-review/SKILL.md +60 -211
  64. package/kits/builder/skills/idea-to-backlog/SKILL.md +63 -244
  65. package/kits/builder/skills/learning-review/SKILL.md +63 -170
  66. package/kits/builder/skills/pickup-probe/SKILL.md +54 -111
  67. package/kits/builder/skills/plan-work/SKILL.md +51 -185
  68. package/kits/builder/skills/pull-work/SKILL.md +136 -485
  69. package/kits/builder/skills/release-readiness/SKILL.md +66 -107
  70. package/kits/builder/skills/review-work/SKILL.md +89 -176
  71. package/kits/builder/skills/tdd-workflow/SKILL.md +53 -147
  72. package/kits/builder/skills/verify-work/SKILL.md +101 -113
  73. package/package.json +2 -2
  74. package/packaging/README.md +1 -1
  75. package/scripts/README.md +1 -1
  76. package/scripts/hooks/lib/kit-catalog.js +1 -1
  77. package/scripts/hooks/stop-goal-fit.js +78 -9
  78. package/scripts/install-codex-home.sh +63 -23
  79. package/scripts/install-owned-files.js +18 -2
  80. package/src/builder-flow-run-adapter.ts +118 -32
  81. package/src/builder-flow-runtime.ts +426 -64
  82. package/src/cli/assignment-provider-lock.test.mjs +83 -0
  83. package/src/cli/assignment-provider.ts +75 -14
  84. package/src/cli/builder-flow-run-adapter.test.mjs +4 -2
  85. package/src/cli/builder-flow-runtime.test.mjs +436 -18
  86. package/src/cli/builder-run.ts +3 -9
  87. package/src/cli/kit-metadata-security.test.mjs +232 -6
  88. package/src/cli/public-api.test.mjs +15 -0
  89. package/src/cli/workflow-sidecar-execution-proof.test.mjs +90 -0
  90. package/src/cli/workflow-sidecar.ts +746 -103
  91. package/src/cli/workflow.ts +288 -41
  92. package/src/flow-kit/validate.ts +320 -2
  93. package/src/index.ts +6 -5
  94. package/src/lib/observed-command.ts +96 -0
  95. package/src/tools/build-universal-bundles.ts +51 -3
  96. package/src/tools/generate-context-map.ts +5 -3
@@ -2242,12 +2242,12 @@ node - "$ROOT/scripts/hooks/stop-goal-fit.js" <<'NODEEOF' 2>"$TMPDIR_EVAL/hardbl
2242
2242
  const fs = require('fs');
2243
2243
  const file = process.argv[2];
2244
2244
  let src = fs.readFileSync(file, 'utf8');
2245
- const needle = "const HARD_BLOCK = /contradicts evidence\\.json|caught false-completion|evidence verdict:|evidence check .+ status:|critique status|critique open|required sidecar is missing|command-log integrity check FAILED|gate misconfiguration:|exit-code-laundered|NOT_VERIFIED \\(ambiguous\\)/;";
2245
+ const needle = "const HARD_BLOCK = /contradicts evidence\\.json|caught false-completion|evidence verdict:|evidence check .+ status:|critique status|critique open|required sidecar is missing|command-log integrity check FAILED|gate misconfiguration:|exit-code-laundered|NOT_VERIFIED \\(ambiguous\\)|canonical Flow (?:run remains active|state is unsafe or malformed)/;";
2246
2246
  if (!src.includes(needle)) {
2247
2247
  process.stderr.write('mutation: HARD_BLOCK NOT_VERIFIED (ambiguous) pattern not found — source pattern drifted, cannot mutation-test\n');
2248
2248
  process.exit(1);
2249
2249
  }
2250
- const mutated = "const HARD_BLOCK = /contradicts evidence\\.json|caught false-completion|evidence verdict:|evidence check .+ status:|critique status|critique open|required sidecar is missing|command-log integrity check FAILED|gate misconfiguration:|exit-code-laundered/;";
2250
+ const mutated = "const HARD_BLOCK = /contradicts evidence\\.json|caught false-completion|evidence verdict:|evidence check .+ status:|critique status|critique open|required sidecar is missing|command-log integrity check FAILED|gate misconfiguration:|exit-code-laundered|canonical Flow (?:run remains active|state is unsafe or malformed)/;";
2251
2251
  src = src.split(needle).join(mutated);
2252
2252
  fs.writeFileSync(file, src);
2253
2253
  NODEEOF
@@ -2712,6 +2712,64 @@ else
2712
2712
  _fail "mutation-test cleanup re-check REGRESSION: restored stop-goal-fit.js no longer confirms a clean end-to-end pass for the dialect fixture: $(cat "$TMPDIR_EVAL/dialect-restore-e2e.out" "$TMPDIR_EVAL/dialect-restore-e2e.err")"
2713
2713
  fi
2714
2714
 
2715
+ # Canonical Flow is authoritative for an active scoped session. Parent-directory
2716
+ # symlinks and malformed run state must therefore fail closed instead of being
2717
+ # silently treated as a missing optional run.
2718
+ CANONICAL_UNSAFE_REPO="$TMPDIR_EVAL/canonical-unsafe-repo"
2719
+ CANONICAL_UNSAFE_SESSION="$CANONICAL_UNSAFE_REPO/.kontourai/flow-agents/canonical-unsafe"
2720
+ mkdir -p "$CANONICAL_UNSAFE_SESSION"
2721
+ printf '# Test Repo\n' > "$CANONICAL_UNSAFE_REPO/AGENTS.md"
2722
+ cat > "$CANONICAL_UNSAFE_SESSION/canonical-unsafe--deliver.md" <<'MARKDOWN'
2723
+ # Canonical unsafe fixture
2724
+
2725
+ status: executing
2726
+ type: deliver
2727
+ MARKDOWN
2728
+ cat > "$CANONICAL_UNSAFE_SESSION/state.json" <<'JSON'
2729
+ {
2730
+ "task_slug": "canonical-unsafe",
2731
+ "status": "executing",
2732
+ "phase": "execution",
2733
+ "flow_run": { "status": "active", "current_step": "verify" }
2734
+ }
2735
+ JSON
2736
+ cat > "$CANONICAL_UNSAFE_REPO/.kontourai/flow-agents/current.json" <<'JSON'
2737
+ { "active_slug": "canonical-unsafe" }
2738
+ JSON
2739
+ CANONICAL_OUTSIDE="$TMPDIR_EVAL/canonical-outside"
2740
+ mkdir -p "$CANONICAL_OUTSIDE"
2741
+ ln -s "$CANONICAL_OUTSIDE" "$CANONICAL_UNSAFE_REPO/.kontourai/flow"
2742
+ if FLOW_AGENTS_GOAL_FIT_STRICT=true node "$ROOT/scripts/hooks/stop-goal-fit.js" >"$TMPDIR_EVAL/canonical-symlink.out" 2>"$TMPDIR_EVAL/canonical-symlink.err" <<JSON
2743
+ {"hook_event_name":"Stop","cwd":"$CANONICAL_UNSAFE_REPO"}
2744
+ JSON
2745
+ then
2746
+ _fail "strict goal-fit hook allowed an active scoped session with a symlinked canonical Flow parent"
2747
+ elif [[ "$?" -eq 2 ]] && grep -q 'canonical Flow state is unsafe or malformed' "$TMPDIR_EVAL/canonical-symlink.err"; then
2748
+ _pass "strict goal-fit hook rejects symlinked canonical Flow parent components for an active scoped session"
2749
+ else
2750
+ _fail "strict goal-fit hook did not report the unsafe canonical Flow parent: $(cat "$TMPDIR_EVAL/canonical-symlink.err")"
2751
+ fi
2752
+
2753
+ rm "$CANONICAL_UNSAFE_REPO/.kontourai/flow"
2754
+ mkdir -p "$CANONICAL_UNSAFE_REPO/.kontourai/flow/runs/canonical-unsafe"
2755
+ printf 'not-json\n' > "$CANONICAL_UNSAFE_REPO/.kontourai/flow/runs/canonical-unsafe/state.json"
2756
+ if FLOW_AGENTS_GOAL_FIT_STRICT=true node "$ROOT/scripts/hooks/stop-goal-fit.js" >"$TMPDIR_EVAL/canonical-malformed.out" 2>"$TMPDIR_EVAL/canonical-malformed.err" <<JSON
2757
+ {"hook_event_name":"Stop","cwd":"$CANONICAL_UNSAFE_REPO"}
2758
+ JSON
2759
+ then
2760
+ _fail "strict goal-fit hook allowed an active scoped session with malformed canonical Flow state"
2761
+ elif [[ "$?" -eq 2 ]] && grep -q 'canonical Flow state is unsafe or malformed' "$TMPDIR_EVAL/canonical-malformed.err"; then
2762
+ _pass "strict goal-fit hook fails closed for malformed canonical Flow state of an active scoped session"
2763
+ else
2764
+ _fail "strict goal-fit hook did not report malformed canonical Flow state: $(cat "$TMPDIR_EVAL/canonical-malformed.err")"
2765
+ fi
2766
+
2767
+ if cmp -s "$ROOT/scripts/hooks/stop-goal-fit.js" "$ROOT/context/scripts/hooks/stop-goal-fit.js"; then
2768
+ _pass "canonical Stop hook source and shipped context mirror remain byte-identical"
2769
+ else
2770
+ _fail "canonical Stop hook source and shipped context mirror diverged"
2771
+ fi
2772
+
2715
2773
 
2716
2774
  if [[ "$errors" -eq 0 ]]; then
2717
2775
  echo "Goal Fit hook integration passed."
@@ -22,6 +22,10 @@ set -euo pipefail
22
22
 
23
23
  ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
24
24
  TMPDIR_EVAL="$(mktemp -d /tmp/install-merge.XXXXXX)"
25
+ export HOME="$TMPDIR_EVAL/hermetic-home"
26
+ export FLOW_AGENTS_SKILLS_DIR="$TMPDIR_EVAL/hermetic-universal/skills"
27
+ mkdir -p "$HOME/.agents" "$FLOW_AGENTS_SKILLS_DIR"
28
+ printf 'installer tests must not replace this file\n' > "$HOME/.agents/ambient-sentinel"
25
29
  pass=0
26
30
  fail=0
27
31
 
@@ -1138,7 +1142,8 @@ echo ""
1138
1142
  echo "--- CH4: codex-home preserves user config, profiles, hooks, local kit state, and user skills ---"
1139
1143
 
1140
1144
  CH4_DEST="$TMPDIR_EVAL/codex-home-ch4"
1141
- mkdir -p "$CH4_DEST/kits/local/repositories/user-kit" "$CH4_DEST/sk""ills/user-owned-skill" "$CH4_DEST/ag""ents"
1145
+ CH4_SKILLS="$TMPDIR_EVAL/codex-home-ch4-universal-skills"
1146
+ mkdir -p "$CH4_DEST/kits/local/repositories/user-kit" "$CH4_SKILLS/user-owned-skill" "$CH4_DEST/ag""ents"
1142
1147
 
1143
1148
  CH4_GENERIC_DIRS=(agent-cards build context docs evals integrations packaging powers prompts schemas scripts kits)
1144
1149
  for CH4_DIR in "${CH4_GENERIC_DIRS[@]}"; do
@@ -1197,14 +1202,14 @@ JSON
1197
1202
  cat > "$CH4_DEST/kits/local/repositories/user-kit/kit.json" << 'JSON'
1198
1203
  {"id":"user-kit","name":"User Kit"}
1199
1204
  JSON
1200
- cat > "$CH4_DEST/sk""ills/user-owned-skill/SKILL.md" << 'EOF_SKILL'
1205
+ cat > "$CH4_SKILLS/user-owned-skill/SKILL.md" << 'EOF_SKILL'
1201
1206
  # User Owned Skill
1202
1207
  EOF_SKILL
1203
1208
  cat > "$CH4_DEST/ag""ents/user-owned-agent.toml" << 'EOF_AGENT'
1204
1209
  name = "user-owned-agent"
1205
1210
  EOF_AGENT
1206
1211
 
1207
- CODEX_REAL_HOME="$TMPDIR_EVAL/fake-real-codex" bash "$ROOT_DIR/scripts/install-codex-home.sh" "$CH4_DEST" >/dev/null 2>&1
1212
+ CODEX_REAL_HOME="$TMPDIR_EVAL/fake-real-codex" FLOW_AGENTS_SKILLS_DIR="$CH4_SKILLS" bash "$ROOT_DIR/scripts/install-codex-home.sh" "$CH4_DEST" >/dev/null 2>&1
1208
1213
 
1209
1214
  if grep -q 'user-model' "$CH4_DEST/config.toml" \
1210
1215
  && grep -q 'user-profile-model' "$CH4_DEST/custom.config.toml" \
@@ -1243,10 +1248,10 @@ else
1243
1248
  _fail "CH4: kits/local registry or repository was removed"
1244
1249
  fi
1245
1250
 
1246
- if [[ -f "$CH4_DEST/sk""ills/search-first/SKILL.md" && -f "$CH4_DEST/sk""ills/plan-work/SKILL.md" && -f "$CH4_DEST/sk""ills/user-owned-skill/SKILL.md" ]]; then
1247
- _pass "CH4: root, Builder Kit workflow, and user-owned skills coexist in flattened Codex skills"
1251
+ if [[ -f "$CH4_SKILLS/search-first/SKILL.md" && -f "$CH4_SKILLS/plan-work/SKILL.md" && -f "$CH4_SKILLS/user-owned-skill/SKILL.md" ]]; then
1252
+ _pass "CH4: root, Builder Kit workflow, and user-owned skills coexist in universal Codex skills"
1248
1253
  else
1249
- _fail "CH4: root skill, Builder Kit workflow skill, or user-owned skill missing from flattened Codex skills"
1254
+ _fail "CH4: root skill, Builder Kit workflow skill, or user-owned skill missing from universal Codex skills"
1250
1255
  fi
1251
1256
 
1252
1257
  if [[ -f "$CH4_DEST/scripts/install-merge.js" && -f "$CH4_DEST/build/src/cli/kit.js" && -f "$CH4_DEST/ag""ents/tool-worker.toml" && -f "$CH4_DEST/ag""ents/user-owned-agent.toml" && -f "$CH4_DEST/hooks.json" ]] \
@@ -1272,7 +1277,7 @@ else
1272
1277
  _fail "CH4: installed scripts/kit.js could not run with installed build bundle"
1273
1278
  fi
1274
1279
 
1275
- CODEX_REAL_HOME="$TMPDIR_EVAL/fake-real-codex" bash "$ROOT_DIR/scripts/install-codex-home.sh" "$CH4_DEST" >/dev/null 2>&1
1280
+ CODEX_REAL_HOME="$TMPDIR_EVAL/fake-real-codex" FLOW_AGENTS_SKILLS_DIR="$CH4_SKILLS" bash "$ROOT_DIR/scripts/install-codex-home.sh" "$CH4_DEST" >/dev/null 2>&1
1276
1281
  CH4_GENERIC_PRESERVED=1
1277
1282
  for CH4_DIR in "${CH4_GENERIC_DIRS[@]}"; do
1278
1283
  [[ "$(cat "$CH4_DEST/$CH4_DIR/user-owned.txt" 2>/dev/null)" == "user-owned-$CH4_DIR" ]] || CH4_GENERIC_PRESERVED=0
@@ -1288,7 +1293,7 @@ echo ""
1288
1293
  # ─── codex-home: CH5: destination symlink containment ───────────────────────
1289
1294
  echo "--- CH5: codex-home refuses managed destination symlinks ---"
1290
1295
 
1291
- for CH5_REL in scripts kits sk""ills hooks.json; do
1296
+ for CH5_REL in scripts kits hooks.json; do
1292
1297
  CH5_DEST="$TMPDIR_EVAL/codex-home-ch5-${CH5_REL//\//-}"
1293
1298
  CH5_OUTSIDE="$TMPDIR_EVAL/codex-home-ch5-outside-${CH5_REL//\//-}"
1294
1299
  mkdir -p "$CH5_DEST" "$CH5_OUTSIDE"
@@ -1680,6 +1685,11 @@ echo ""
1680
1685
 
1681
1686
  echo ""
1682
1687
  echo "==========================="
1688
+ if [[ "$(cat "$HOME/.agents/ambient-sentinel" 2>/dev/null)" == "installer tests must not replace this file" ]]; then
1689
+ _pass "dedicated Codex installer calls remain inside hermetic universal roots"
1690
+ else
1691
+ _fail "dedicated Codex installer calls mutated the isolated default-home sentinel"
1692
+ fi
1683
1693
  total=$((pass + fail))
1684
1694
  echo "Results: ${pass}/${total} passed, ${fail} failed"
1685
1695
  [[ "$fail" -gt 0 ]] && exit 1
@@ -292,23 +292,20 @@ else
292
292
  fi
293
293
 
294
294
  # ─── G. Static skill-text assertions (AC2) ──────────────────────────────────
295
- echo "--- G. Static pull-work tiebreaker/loser-release skill-text assertions ---"
296
-
297
- require_text "$PULL" '### Post-Claim Conflict Re-check' "pull-work documents the Post-Claim Conflict Re-check subsection (AC2)"
298
- require_text "$PULL" 'When a double-hold is detected, immediately run the deterministic tiebreaker' "pull-work instructs running the deterministic tiebreaker immediately on a detected double-hold (AC2)"
299
- require_text "$PULL" 'liveness verdict <subjectId> --json' "pull-work references liveness verdict <subjectId> --json by exact command name (AC1, AC2)"
300
- require_text "$PULL" 'the same pinned .self_actor. and the same .subjectId. already in scope' "pull-work reuses the same pinned self_actor/subjectId already in scope for the verdict call, never re-derived (AC2)"
301
- require_text "$PULL" 'an exact-timestamp tie breaks by ascending actor-id string comparison \(.reason: \"tie-actor-lexicographic\".\)' "pull-work documents the exact-timestamp tiebreak and its reason value verbatim (AC1, AC2)"
302
- require_text "$PULL" 'the SAME verdict for the SAME stream state regardless of which actor invokes it' "pull-work states the verdict is deterministic regardless of which actor invokes it (AC1, AC2)"
303
- require_text "$PULL" 'If .winner\.actor !== self_actor., this session is the loser' "pull-work's loser branch is keyed on winner.actor !== self_actor (AC2)"
304
- require_text "$PULL" 'immediately run .npm run workflow:sidecar -- liveness release <subjectId> --actor <self_actor>.' "pull-work's loser branch runs liveness release <subjectId> --actor <self_actor> immediately (AC2)"
305
- require_text "$PULL" 'extend .post_claim_conflict. with .\{verdict_reason, winner_actor, conceded: true\}.' "pull-work's loser branch extends post_claim_conflict with {verdict_reason, winner_actor, conceded: true} (AC2)"
306
- require_text "$PULL" 'return to .### 3\. Select Work. to reselect within the same .pull-work. pass' "pull-work's loser branch returns to Select Work to reselect within the same pass (AC2)"
307
- require_text "$PULL" 'excluding the just-released subject' "pull-work's reselect excludes the just-released subject (AC2)"
308
- require_text "$PULL" 'If .winner\.actor === self_actor., this session wins' "pull-work's winner branch is keyed on winner.actor === self_actor (AC2)"
309
- require_text "$PULL" 'record the verdict for transparency \(.\{verdict_reason, winner_actor: self_actor, conceded: false\}.\) in .post_claim_conflict. and proceed normally; do not release' "pull-work's winner branch records the verdict for transparency and proceeds without releasing (AC2)"
310
- require_text "$PULL" 'closes the .detected but advisory-only. gap ADR 0012 §4 names for THIS session.s own double-hold' "pull-work's honesty note states the verdict+release loop closes the detected-but-advisory-only gap for this session's own double-hold (AC2)"
311
- require_text "$PULL" 'still does not provide true mutual exclusion across the read-then-write race window itself' "pull-work's honesty note distinguishes the new convergence guarantee from the unchanged read-then-write race residual (AC2)"
295
+ echo "--- G. Provider-neutral pull-work conflict contract assertions ---"
296
+
297
+ require_text "$PULL" 'deterministic conflict arbitration' "pull-work uses provider conflict arbitration"
298
+ require_text "$PULL" 'same pinned caller identity, subject, and observation' "pull-work reuses one conflict snapshot"
299
+ require_text "$PULL" 'Prefer the earlier claim' "pull-work defines deterministic winner precedence"
300
+ require_text "$PULL" 'exact timestamp tie by ascending' "pull-work defines deterministic tie handling"
301
+ require_text "$PULL" 'same winner from the same state' "pull-work requires actor-independent verdicts"
302
+ require_text "$PULL" 'If the caller loses' "pull-work defines loser behavior"
303
+ require_text "$PULL" 'release or concede its claim immediately' "pull-work makes the loser concede"
304
+ require_text "$PULL" 'conceded: true' "pull-work records loser concession"
305
+ require_text "$PULL" 'exclude the contested subject' "pull-work excludes the lost subject before reselection"
306
+ require_text "$PULL" 'If the caller wins' "pull-work defines winner behavior"
307
+ require_text "$PULL" 'conceded: false' "pull-work records winner transparency"
308
+ require_text "$PULL" 'underlying read-then-write race' "pull-work distinguishes convergence from mutual exclusion"
312
309
 
313
310
  # ─── H. Hostile-actor injection: winner.actor sanitized in --json and text (F5) ─────
314
311
  echo "--- H. Hostile-actor verdict sanitization (F5) ---"
@@ -8,7 +8,7 @@
8
8
  # 4. record-gate-claim at pull-work step produces builder.pull-work.selected claim (status=verified).
9
9
  # 5. A TAMPERED bundle (stored verified, evidence fail) at pull-work step BLOCKS (exit 2)
10
10
  # with the tamper warning naming the declared claimType.
11
- # 6. A CLEAN record-gate-claim bundle is not reported as false completion;
11
+ # 6. A CLEAN public Builder bundle is not reported as false completion;
12
12
  # its still-active canonical Flow run remains blocked from stopping.
13
13
  #
14
14
  # Deterministic, no model spend, self-cleaning.
@@ -162,10 +162,14 @@ flow_agents_node "workflow-sidecar" init-plan "$CLAIM_ROOT/gate-claim/gate-claim
162
162
  --source-request "Test" --summary "Testing" \
163
163
  --timestamp "2026-06-26T00:00:00Z" >/dev/null 2>&1
164
164
 
165
+ CLAIM_ARTIFACT="$CLAIM_ROOT/gate-claim/gate-claim--pull-work.md"
166
+ printf '# Pull Work\n\nSelected issue #177 for implementation.\n' > "$CLAIM_ARTIFACT"
167
+
165
168
  if flow_agents_node "workflow-sidecar" record-gate-claim "$CLAIM_ROOT/gate-claim" \
166
169
  --status pass \
167
170
  --summary "Selected issue #177 for implementation." \
168
171
  --expectation selected-work \
172
+ --evidence-ref-json "{\"kind\":\"artifact\",\"file\":\"$CLAIM_ARTIFACT\",\"summary\":\"Declared durable pull-work artifact for selected-work.\"}" \
169
173
  --timestamp "2026-06-26T00:01:00Z" >/dev/null 2>&1; then
170
174
  _pass "record-gate-claim exits 0 at pull-work step"
171
175
  else
@@ -188,9 +192,18 @@ node -e "
188
192
  console.error('expected status=verified, got', target.status);
189
193
  process.exit(1);
190
194
  }
195
+ const expectedArtifact = '.kontourai/flow-agents/gate-claim/gate-claim--pull-work.md';
196
+ if (!fs.existsSync('$CLAIM_ARTIFACT')) {
197
+ console.error('missing declared durable selected-work artifact:', '$CLAIM_ARTIFACT');
198
+ process.exit(1);
199
+ }
200
+ if (!target.metadata?.artifact_refs?.some(ref => ref.kind === 'artifact' && ref.file === expectedArtifact)) {
201
+ console.error('selected-work claim does not cite declared artifact:', JSON.stringify(target.metadata?.artifact_refs));
202
+ process.exit(1);
203
+ }
191
204
  " 2>/dev/null \
192
- && _pass "bundle contains builder.pull-work.selected with subjectType=work-item, status=verified" \
193
- || _fail "bundle missing or incorrect builder.pull-work.selected claim"
205
+ && _pass "bundle contains builder.pull-work.selected with declared durable artifact evidence" \
206
+ || _fail "bundle missing selected-work claim or declared durable artifact evidence"
194
207
 
195
208
  echo ""
196
209
  echo "=== 4b. composed publish-learn gate claim emits builder.pr-open.pull-request ==="
@@ -215,10 +228,14 @@ flow_agents_node "workflow-sidecar" advance-state "$COMPOSED_ROOT/composed-gate-
215
228
  --flow-definition builder.build \
216
229
  --timestamp "2026-06-26T00:00:30Z" >/dev/null 2>&1
217
230
 
231
+ COMPOSED_ARTIFACT="$COMPOSED_ROOT/composed-gate-claim/release.json"
232
+ printf '{"schema_version":"1.0","task_slug":"composed-gate-claim","status":"pr_open"}\n' > "$COMPOSED_ARTIFACT"
233
+
218
234
  if flow_agents_node "workflow-sidecar" record-gate-claim "$COMPOSED_ROOT/composed-gate-claim" \
219
235
  --status pass \
220
236
  --summary "PR opened with verification evidence." \
221
237
  --expectation pull-request-opened \
238
+ --evidence-ref-json "{\"kind\":\"artifact\",\"file\":\"$COMPOSED_ARTIFACT\",\"summary\":\"Declared durable release artifact for pull-request-opened.\"}" \
222
239
  --timestamp "2026-06-26T00:01:00Z" >/dev/null 2>&1; then
223
240
  _pass "record-gate-claim exits 0 at composed pr-open step"
224
241
  else
@@ -244,9 +261,18 @@ node -e "
244
261
  console.error('expected status=verified, got', target.status);
245
262
  process.exit(1);
246
263
  }
264
+ const expectedArtifact = '.kontourai/flow-agents/composed-gate-claim/release.json';
265
+ if (!fs.existsSync('$COMPOSED_ARTIFACT')) {
266
+ console.error('missing declared durable pr-open artifact:', '$COMPOSED_ARTIFACT');
267
+ process.exit(1);
268
+ }
269
+ if (!target.metadata?.artifact_refs?.some(ref => ref.kind === 'artifact' && ref.file === expectedArtifact)) {
270
+ console.error('pr-open claim does not cite declared artifact:', JSON.stringify(target.metadata?.artifact_refs));
271
+ process.exit(1);
272
+ }
247
273
  " 2>/dev/null \
248
- && _pass "composed bundle contains builder.pr-open.pull-request with active parent flow" \
249
- || _fail "composed bundle missing or incorrect builder.pr-open.pull-request claim"
274
+ && _pass "composed bundle contains builder.pr-open.pull-request with declared durable artifact evidence" \
275
+ || _fail "composed bundle missing pr-open claim or declared durable artifact evidence"
250
276
 
251
277
  # ─── Tamper-blocks: stored verified + evidence fail → BLOCK (exit 2) ─────────
252
278
  echo ""
@@ -355,59 +381,58 @@ fi
355
381
 
356
382
  # ─── Clean gate-claim: no false completion, active Flow still blocks ─────────
357
383
  echo ""
358
- echo "=== 6. CLEAN record-gate-claim remains governed by active canonical Flow ==="
384
+ echo "=== 6. CLEAN public Builder evidence remains governed by active canonical Flow ==="
359
385
 
360
386
  C_DIR="$TMP/clean-test"
387
+ CLEAN_SLUG="provider-clean"
361
388
  mkdir -p "$C_DIR"
362
389
  printf '# Repo\n' > "$C_DIR/AGENTS.md"
390
+ mkdir -p "$C_DIR/.kontourai/flow-agents/$CLEAN_SLUG"
391
+ CLEAN_ARTIFACT="$C_DIR/.kontourai/flow-agents/$CLEAN_SLUG/$CLEAN_SLUG--pull-work.md"
392
+ printf 'Selected Work Item: provider:clean\n' > "$CLEAN_ARTIFACT"
363
393
 
364
- flow_agents_node "workflow-sidecar" ensure-session \
394
+ if ! FLOW_AGENTS_ACTOR=clean-fixture-actor node "$ROOT/build/src/cli.js" workflow start \
365
395
  --artifact-root "$C_DIR/.kontourai/flow-agents" \
366
- --task-slug clean \
396
+ --flow builder.build \
397
+ --work-item provider:clean \
398
+ --assignment-provider local-file \
367
399
  --title "Clean Test" \
368
- --summary "Testing clean gate claim." \
369
- --flow-id builder.build \
370
- --skip-ownership-guard \
371
- --timestamp "2026-06-26T00:00:00Z" >/dev/null 2>&1
400
+ --summary "Testing clean gate claim." >"$TMP/clean-start.out" 2>&1; then
401
+ _fail "public clean fixture failed to start: $(cat "$TMP/clean-start.out")"
402
+ fi
372
403
 
373
- flow_agents_node "workflow-sidecar" init-plan "$C_DIR/.kontourai/flow-agents/clean/clean--deliver.md" \
404
+ flow_agents_node "workflow-sidecar" init-plan "$C_DIR/.kontourai/flow-agents/$CLEAN_SLUG/$CLEAN_SLUG--deliver.md" \
374
405
  --source-request "Test" --summary "Testing" \
375
406
  --timestamp "2026-06-26T00:00:00Z" >/dev/null 2>&1
376
407
 
377
- flow_agents_node "workflow-sidecar" advance-state "$C_DIR/.kontourai/flow-agents/clean" \
378
- --status in_progress \
379
- --phase pickup \
380
- --summary "In progress." \
381
- --next-action "done" \
382
- --flow-definition builder.build \
383
- --timestamp "2026-06-26T00:00:30Z" >/dev/null 2>&1
384
-
385
408
  # Fix next_action so it reads as "done" for the gate
386
409
  node -e "
387
410
  const fs = require('fs');
388
- const f = '$C_DIR/.kontourai/flow-agents/clean/state.json';
411
+ const f = '$C_DIR/.kontourai/flow-agents/$CLEAN_SLUG/state.json';
389
412
  const s = JSON.parse(fs.readFileSync(f, 'utf8'));
390
413
  s.next_action = { status: 'done', summary: 'Work complete.' };
391
414
  s.status = 'verified';
415
+ delete s.flow_run;
392
416
  fs.writeFileSync(f, JSON.stringify(s, null, 2) + '\n');
393
417
  " 2>/dev/null
394
-
395
- flow_agents_node "workflow-sidecar" record-gate-claim "$C_DIR/.kontourai/flow-agents/clean" \
396
- --status pass \
397
- --summary "Selected issue #177 for implementation." \
398
- --expectation selected-work \
399
- --timestamp "2026-06-26T00:01:00Z" >/dev/null 2>&1
400
-
401
- set +e
402
- clean_out="$(FLOW_AGENTS_GOAL_FIT_MODE=block FLOW_AGENTS_GOAL_FIT_BACKSTOP=skip \
403
- node "$GATE" 2>&1 <<< "{\"hook_event_name\":\"Stop\",\"cwd\":\"$C_DIR\"}")"
404
- clean_exit="$?"
405
- set -e
406
-
407
- if [ "$clean_exit" -eq 2 ] && echo "$clean_out" | grep -q 'release skipped for active Flow run "clean"'; then
408
- _pass "clean claim advances to the next Flow step and active run blocks premature Stop"
418
+ rm -f "$C_DIR/.kontourai/flow-agents/$CLEAN_SLUG/$CLEAN_SLUG--deliver.md"
419
+
420
+ clean_out=""
421
+ clean_exit=0
422
+ for attempt in 1 2; do
423
+ set +e
424
+ attempt_out="$(FLOW_AGENTS_GOAL_FIT_MODE=block FLOW_AGENTS_GOAL_FIT_MAX_BLOCKS=2 FLOW_AGENTS_GOAL_FIT_BACKSTOP=skip \
425
+ node "$GATE" 2>&1 <<< "{\"hook_event_name\":\"Stop\",\"cwd\":\"$C_DIR\"}")"
426
+ attempt_exit="$?"
427
+ set -e
428
+ clean_out="$clean_out$attempt_out"
429
+ if [ "$attempt_exit" -ne 2 ]; then clean_exit="$attempt_exit"; fi
430
+ done
431
+
432
+ if [ "$clean_exit" -eq 0 ] && echo "$clean_out" | grep -q 'canonical Flow run remains active at step design-probe'; then
433
+ _pass "canonical Flow blocks repeated Stop after the sidecar projection and Markdown are removed"
409
434
  else
410
- _fail "clean active Flow run returned the wrong Stop decision (exit $clean_exit): $clean_out"
435
+ _fail "canonical Flow could be hidden or auto-released after sidecar rewrite (exit $clean_exit): $clean_out"
411
436
  fi
412
437
 
413
438
  if echo "$clean_out" | grep -q "caught false-completion"; then