@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
@@ -13,6 +13,12 @@ trap 'rm -rf "$TMP"' EXIT
13
13
  pass() { printf ' PASS %s\n' "$1"; }
14
14
  fail() { printf ' FAIL %s\n' "$1"; errors=$((errors + 1)); }
15
15
 
16
+ seed_pull_work_report() {
17
+ local artifact_root="$1" slug="$2" work_item_ref="$3"
18
+ mkdir -p "$artifact_root/$slug"
19
+ printf '# Pull Work\n\nSelected Work Item: %s\n' "$work_item_ref" > "$artifact_root/$slug/$slug--pull-work.md"
20
+ }
21
+
16
22
  WRITER="workflow-sidecar"
17
23
 
18
24
  echo "=== Builder workflow entry enforcement ==="
@@ -185,14 +191,20 @@ else
185
191
  fi
186
192
 
187
193
  LOCAL_ROOT="$TMP/local/.kontourai/flow-agents"
188
- if flow_agents_node "$WRITER" ensure-session \
194
+ seed_pull_work_report "$LOCAL_ROOT" "local-request" "local:local-request"
195
+ cat >"$LOCAL_ROOT/local-request/work-item.json" <<'JSON'
196
+ {"id":"local-request","title":"Local request","source_provider":{"kind":"local","path":"work-item.json"}}
197
+ JSON
198
+ cat >"$LOCAL_ROOT/local-request/state.json" <<'JSON'
199
+ {"schema_version":"1.0","task_slug":"local-request","status":"planned","phase":"pickup","work_item_refs":["local:local-request"],"next_action":{"status":"continue","summary":"Start Builder."}}
200
+ JSON
201
+ if FLOW_AGENTS_ACTOR=builder-entry-local node "$ROOT/build/src/cli.js" workflow start \
189
202
  --artifact-root "$LOCAL_ROOT" \
203
+ --flow builder.build \
204
+ --work-item local:local-request \
190
205
  --task-slug local-request \
191
- --actor builder-entry-local \
192
206
  --title "Local request" \
193
- --summary "Providerless work still needs an anchor." \
194
- --flow-id builder.build \
195
- --timestamp "2026-07-10T00:00:00Z" >"$TMP/local.out" 2>&1; then
207
+ --summary "Providerless work still needs an anchor." >"$TMP/local.out" 2>&1; then
196
208
  if node - "$LOCAL_ROOT" <<'NODE'
197
209
  const fs = require('node:fs');
198
210
  const path = require('node:path');
@@ -228,14 +240,13 @@ fi
228
240
 
229
241
  LOCAL_SESSION="$LOCAL_ROOT/local-request"
230
242
  FLOW_DIGEST_BEFORE="$(find "$TMP/local/.kontourai/flow/runs/local-request" -type f -print0 | sort -z | xargs -0 shasum -a 256)"
231
- if flow_agents_node "$WRITER" ensure-session \
243
+ if FLOW_AGENTS_ACTOR=builder-entry-local node "$ROOT/build/src/cli.js" workflow start \
232
244
  --artifact-root "$LOCAL_ROOT" \
245
+ --flow builder.build \
246
+ --work-item local:local-request \
233
247
  --task-slug local-request \
234
- --actor builder-entry-local \
235
248
  --title "Local request" \
236
- --summary "Providerless work still needs an anchor." \
237
- --flow-id builder.build \
238
- --timestamp "2026-07-10T00:00:00Z" >"$TMP/builder-ensure-again.out" 2>&1 \
249
+ --summary "Providerless work still needs an anchor." >"$TMP/builder-ensure-again.out" 2>&1 \
239
250
  && [[ "$FLOW_DIGEST_BEFORE" == "$(find "$TMP/local/.kontourai/flow/runs/local-request" -type f -print0 | sort -z | xargs -0 shasum -a 256)" ]] \
240
251
  && node - "$TMP/local" "$LOCAL_SESSION" <<'NODE'
241
252
  const fs = require('node:fs');
@@ -258,6 +269,7 @@ fi
258
269
  BROKEN_PROJECT="$TMP/broken-start"
259
270
  BROKEN_ROOT="$BROKEN_PROJECT/.kontourai/flow-agents"
260
271
  mkdir -p "$BROKEN_PROJECT/.kontourai"
272
+ seed_pull_work_report "$BROKEN_ROOT" "broken-start" "local:broken-start"
261
273
  printf 'not a run-store directory\n' > "$BROKEN_PROJECT/.kontourai/flow"
262
274
  if flow_agents_node "$WRITER" ensure-session \
263
275
  --artifact-root "$BROKEN_ROOT" \
@@ -265,12 +277,9 @@ if flow_agents_node "$WRITER" ensure-session \
265
277
  --actor builder-entry-broken \
266
278
  --title "Broken start" \
267
279
  --summary "Flow startup must fail visibly." \
268
- --flow-id builder.build >"$TMP/broken-start.out" 2>&1; then
269
- fail "invalid canonical Flow startup should fail ensure-session"
270
- elif [[ -f "$BROKEN_ROOT/broken-start/state.json" ]] \
280
+ --flow-id builder.build >"$TMP/broken-start.out" 2>&1 \
281
+ && [[ -f "$BROKEN_ROOT/broken-start/state.json" ]] \
271
282
  && [[ ! -e "$BROKEN_PROJECT/.kontourai/flow/runs/broken-start" ]] \
272
- && grep -q 'canonical Builder Flow entry failed' "$TMP/broken-start.out" \
273
- && grep -q 'Re-run the same ensure-session command' "$TMP/broken-start.out" \
274
283
  && node - "$BROKEN_ROOT/broken-start/state.json" <<'NODE'
275
284
  const fs = require('node:fs');
276
285
  const state = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
@@ -278,19 +287,19 @@ if (state.flow_run) process.exit(1);
278
287
  if (!state.next_action?.command?.includes("'workflow' 'start'")) process.exit(1);
279
288
  NODE
280
289
  then
281
- pass "failed canonical startup is visible and leaves only retryable sidecar guidance"
290
+ pass "private writer leaves public retry guidance without touching an invalid Flow store"
282
291
  else
283
- fail "failed canonical startup forged state or lost recovery guidance: $(cat "$TMP/broken-start.out")"
292
+ fail "private writer touched canonical Flow or lost public retry guidance: $(cat "$TMP/broken-start.out")"
284
293
  fi
285
294
 
286
295
  rm -f "$BROKEN_PROJECT/.kontourai/flow"
287
- if flow_agents_node "$WRITER" ensure-session \
296
+ if FLOW_AGENTS_ACTOR=builder-entry-broken node "$ROOT/build/src/cli.js" workflow start \
288
297
  --artifact-root "$BROKEN_ROOT" \
298
+ --flow builder.build \
299
+ --work-item local:broken-start \
289
300
  --task-slug broken-start \
290
- --actor builder-entry-broken \
291
301
  --title "Broken start" \
292
- --summary "Flow startup must recover from persisted acquisition provenance." \
293
- --flow-id builder.build >"$TMP/broken-start-retry.out" 2>&1 \
302
+ --summary "Flow startup must recover from persisted acquisition provenance." >"$TMP/broken-start-retry.out" 2>&1 \
294
303
  && node - "$BROKEN_PROJECT" <<'NODE'
295
304
  const fs = require('node:fs');
296
305
  const path = require('node:path');
@@ -301,7 +310,7 @@ if (flowState.current_step !== 'design-probe') process.exit(1);
301
310
  if (!(bundle.claims || []).some((claim) => claim.claimType === 'builder.pull-work.selected' && claim.status === 'verified')) process.exit(1);
302
311
  NODE
303
312
  then
304
- pass "interrupted Flow startup retries from exact persisted acquisition provenance"
313
+ pass "public startup consumes exact persisted acquisition provenance and advances canonical Flow"
305
314
  else
306
315
  fail "interrupted acquisition could not recover: $(cat "$TMP/broken-start-retry.out")"
307
316
  fi
@@ -337,15 +346,14 @@ const fs = require('node:fs');
337
346
  const path = require('node:path');
338
347
  const project = process.argv[2];
339
348
  const session = path.join(project, '.kontourai', 'flow-agents', 'skipped-ownership');
340
- const flowState = JSON.parse(fs.readFileSync(path.join(project, '.kontourai', 'flow', 'runs', 'skipped-ownership', 'state.json'), 'utf8'));
341
349
  const sidecar = JSON.parse(fs.readFileSync(path.join(session, 'state.json'), 'utf8'));
342
- if (flowState.current_step !== 'pull-work' || sidecar.flow_run?.current_step !== 'pull-work') process.exit(1);
343
- if (JSON.stringify(sidecar.next_action?.skills) !== JSON.stringify(['pull-work'])) process.exit(1);
350
+ if (sidecar.flow_run) process.exit(1);
351
+ if (fs.existsSync(path.join(project, '.kontourai', 'flow', 'runs', 'skipped-ownership'))) process.exit(1);
344
352
  if (fs.existsSync(path.join(session, 'trust.bundle'))) process.exit(1);
345
353
  if (fs.existsSync(path.join(project, '.kontourai', 'flow-agents', 'assignment', 'skipped-ownership.json'))) process.exit(1);
346
354
  NODE
347
355
  then
348
- pass "skipped ownership remains at pull-work without selected-work evidence"
356
+ pass "private skipped-ownership seed creates no assignment, evidence, or canonical Flow"
349
357
  else
350
358
  fail "unproven ownership advanced the canonical run: $(cat "$TMP/skipped.out")"
351
359
  fi
@@ -364,15 +372,14 @@ const fs = require('node:fs');
364
372
  const path = require('node:path');
365
373
  const project = process.argv[2];
366
374
  const session = path.join(project, '.kontourai', 'flow-agents', 'arbitrary-session-name');
367
- const flowState = JSON.parse(fs.readFileSync(path.join(project, '.kontourai', 'flow', 'runs', 'arbitrary-session-name', 'state.json'), 'utf8'));
368
375
  const sidecar = JSON.parse(fs.readFileSync(path.join(session, 'state.json'), 'utf8'));
369
- if (flowState.subject !== 'kontourai/flow-agents#541' || flowState.current_step !== 'pull-work') process.exit(1);
370
- if (sidecar.flow_run?.current_step !== 'pull-work') process.exit(1);
376
+ if (JSON.stringify(sidecar.work_item_refs) !== JSON.stringify(['kontourai/flow-agents#541'])) process.exit(1);
377
+ if (sidecar.flow_run || fs.existsSync(path.join(project, '.kontourai', 'flow', 'runs', 'arbitrary-session-name'))) process.exit(1);
371
378
  if (fs.existsSync(path.join(session, 'trust.bundle'))) process.exit(1);
372
379
  if (!fs.existsSync(path.join(project, '.kontourai', 'flow-agents', 'assignment', 'arbitrary-session-name.json'))) process.exit(1);
373
380
  NODE
374
381
  then
375
- pass "assignment subject must canonically match the exact Work Item before pull-work can pass"
382
+ pass "private mismatched-subject seed records scope but cannot create or advance canonical Flow"
376
383
  else
377
384
  fail "mismatched assignment subject was treated as exact Work Item evidence: $(cat "$TMP/mismatched-subject.out")"
378
385
  fi
@@ -462,8 +469,7 @@ const fs = require('node:fs');
462
469
  const path = require('node:path');
463
470
  const project = process.argv[2];
464
471
  const session = path.join(project, '.kontourai', 'flow-agents', 'preexisting-selection');
465
- const flowState = JSON.parse(fs.readFileSync(path.join(project, '.kontourai', 'flow', 'runs', 'preexisting-selection', 'state.json'), 'utf8'));
466
- if (flowState.current_step !== 'pull-work') process.exit(1);
472
+ if (fs.existsSync(path.join(project, '.kontourai', 'flow', 'runs', 'preexisting-selection'))) process.exit(1);
467
473
  if (fs.existsSync(path.join(session, 'trust.bundle'))) process.exit(1);
468
474
  NODE
469
475
  then
@@ -497,31 +503,35 @@ NODE
497
503
  fi
498
504
  fi
499
505
 
500
- if flow_agents_node "$WRITER" record-gate-claim "$LOCAL_SESSION" \
506
+ printf '# Plan Work\n\nRoute-back fixture plan evidence.\n' > "$LOCAL_SESSION/local-request--plan-work.md"
507
+ printf '# Delivery\n\nRoute-back fixture execution evidence.\n' > "$LOCAL_SESSION/local-request--deliver.md"
508
+
509
+ if FLOW_AGENTS_ACTOR=builder-entry-local node "$ROOT/build/src/cli.js" workflow evidence --session-dir "$LOCAL_SESSION" \
501
510
  --expectation pickup-probe-readiness \
502
511
  --status pass \
503
512
  --summary "Pickup Probe confirmed scope and planning readiness." \
504
- --evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/local-request/work-item.json","summary":"Probe fixture evidence."}' >/dev/null 2>&1 \
505
- && flow_agents_node "$WRITER" record-gate-claim "$LOCAL_SESSION" \
513
+ --evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/local-request/local-request--pull-work.md","summary":"Declared pull-work fixture evidence."}' >/dev/null 2>&1 \
514
+ && FLOW_AGENTS_ACTOR=builder-entry-local node "$ROOT/build/src/cli.js" workflow evidence --session-dir "$LOCAL_SESSION" \
506
515
  --expectation probe-decisions-or-accepted-gaps \
507
516
  --status pass \
508
517
  --summary "Probe decisions and accepted gaps are recorded." \
509
- --evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/local-request/work-item.json","summary":"Probe decision fixture evidence."}' >/dev/null 2>&1 \
510
- && flow_agents_node "$WRITER" record-gate-claim "$LOCAL_SESSION" \
518
+ --evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/local-request/local-request--pull-work.md","summary":"Declared pull-work decision fixture evidence."}' >/dev/null 2>&1 \
519
+ && FLOW_AGENTS_ACTOR=builder-entry-local node "$ROOT/build/src/cli.js" workflow evidence --session-dir "$LOCAL_SESSION" \
511
520
  --expectation implementation-plan \
512
521
  --status pass \
513
522
  --summary "Implementation plan records files, sequence, and evidence." \
514
- --evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/local-request/work-item.json","summary":"Plan fixture evidence."}' >/dev/null 2>&1 \
515
- && flow_agents_node "$WRITER" record-gate-claim "$LOCAL_SESSION" \
523
+ --evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/local-request/local-request--plan-work.md","summary":"Declared plan fixture evidence."}' >/dev/null 2>&1 \
524
+ && FLOW_AGENTS_ACTOR=builder-entry-local node "$ROOT/build/src/cli.js" workflow evidence --session-dir "$LOCAL_SESSION" \
516
525
  --expectation implementation-scope \
517
526
  --status pass \
518
527
  --summary "Implementation scope and changed files are recorded." \
519
- --evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/local-request/work-item.json","summary":"Execution fixture evidence."}' >/dev/null 2>&1 \
520
- && flow_agents_node "$WRITER" record-gate-claim "$LOCAL_SESSION" \
528
+ --evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/local-request/local-request--deliver.md","summary":"Declared execution fixture evidence."}' >/dev/null 2>&1 \
529
+ && FLOW_AGENTS_ACTOR=builder-entry-local node "$ROOT/build/src/cli.js" workflow evidence --session-dir "$LOCAL_SESSION" \
521
530
  --expectation tests-evidence \
522
531
  --status fail \
523
532
  --route-reason implementation_defect \
524
533
  --summary "Verification found an implementation defect." \
534
+ --command false \
525
535
  --evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/local-request/work-item.json","summary":"Failed verification fixture evidence."}' >"$TMP/route-back.out" 2>&1 \
526
536
  && node - "$TMP/local" "$LOCAL_SESSION" <<'NODE'
527
537
  const fs = require('node:fs');
@@ -542,6 +552,7 @@ else
542
552
  fi
543
553
 
544
554
  PROVIDER_ROOT="$TMP/provider/.kontourai/flow-agents"
555
+ seed_pull_work_report "$PROVIDER_ROOT" "kontourai-flow-agents-438" "kontourai/flow-agents#438"
545
556
  if flow_agents_node "$WRITER" ensure-session \
546
557
  --artifact-root "$PROVIDER_ROOT" \
547
558
  --work-item "kontourai/flow-agents#438" \
@@ -59,8 +59,9 @@ if (fs.existsSync(actorRoot)) {
59
59
  files.push(...fs.readdirSync(actorRoot).filter((name) => name.endsWith('.json')).map((name) => path.join(actorRoot, name)));
60
60
  }
61
61
  for (const file of files) {
62
- const current = JSON.parse(fs.readFileSync(file, 'utf8'));
63
- current.active_step_id = 'design-probe';
62
+ const current = JSON.parse(fs.readFileSync(file, 'utf8'));
63
+ current.active_flow_id = 'builder.build';
64
+ current.active_step_id = 'design-probe';
64
65
  fs.writeFileSync(file, `${JSON.stringify(current, null, 2)}\n`);
65
66
  }
66
67
  NODE
@@ -92,14 +93,16 @@ NODE
92
93
  # ─── Helper: bootstrap a session for produce tests ───────────────────────────
93
94
  setup_session_for_produce() {
94
95
  local aroot="$1" slug="$2" step="$3"
95
- mkdir -p "$aroot"
96
+ mkdir -p "$aroot/$slug"
97
+ printf '# Pull Work\n\nSelected Work Item: local:%s\n\nFixture probe decisions are reviewable here.\n' "$slug" > "$aroot/$slug/$slug--pull-work.md"
98
+ printf '{"schema_version":"1.0","task_slug":"%s","decision":"hold"}\n' "$slug" > "$aroot/$slug/release.json"
99
+ printf '{"schema_version":"1.0","task_slug":"%s","status":"learned","records":[]}\n' "$slug" > "$aroot/$slug/learning.json"
96
100
 
97
101
  flow_agents_node "workflow-sidecar" ensure-session \
98
102
  --artifact-root "$aroot" \
99
103
  --task-slug "$slug" \
100
104
  --title "Producer test: $step" \
101
105
  --summary "Test gate-claim producer at $step." \
102
- --flow-id builder.build \
103
106
  --timestamp "2026-06-26T00:00:00Z" >/dev/null 2>&1
104
107
 
105
108
  flow_agents_node "workflow-sidecar" init-plan "$aroot/$slug/$slug--deliver.md" \
@@ -115,13 +118,13 @@ setup_tamper_session() {
115
118
  mkdir -p "$t_dir"
116
119
  printf '# Repo\n' > "$t_dir/AGENTS.md"
117
120
  mkdir -p "$t_dir/.kontourai/flow-agents/$slug"
121
+ printf '# Pull Work\n\nSelected Work Item: local:%s\n' "$slug" > "$t_dir/.kontourai/flow-agents/$slug/$slug--pull-work.md"
118
122
 
119
123
  flow_agents_node "workflow-sidecar" ensure-session \
120
124
  --artifact-root "$t_dir/.kontourai/flow-agents" \
121
125
  --task-slug "$slug" \
122
126
  --title "Tamper test: $step" \
123
127
  --summary "Testing tamper detection." \
124
- --flow-id builder.build \
125
128
  --timestamp "2026-06-26T00:00:00Z" >/dev/null 2>&1
126
129
 
127
130
  flow_agents_node "workflow-sidecar" init-plan "$t_dir/.kontourai/flow-agents/$slug/$slug--deliver.md" \
@@ -142,10 +145,19 @@ test_produce_claim() {
142
145
  local aroot="$TMP/$slug/.kontourai/flow-agents"
143
146
  setup_session_for_produce "$aroot" "$slug" "$step"
144
147
 
148
+ local artifact
149
+ case "$expectation" in
150
+ selected-work|pickup-probe-readiness|probe-decisions-or-accepted-gaps) artifact="$aroot/$slug/$slug--pull-work.md" ;;
151
+ pull-request-opened) artifact="$aroot/$slug/release.json" ;;
152
+ decision-evidence|learning-evidence) artifact="$aroot/$slug/learning.json" ;;
153
+ *) _fail "$label: fixture has no declared producer artifact"; return ;;
154
+ esac
155
+
145
156
  if flow_agents_node "workflow-sidecar" record-gate-claim "$aroot/$slug" \
146
157
  --status pass \
147
158
  --summary "Test claim: $label" \
148
159
  --expectation "$expectation" \
160
+ --evidence-ref-json "{\"kind\":\"artifact\",\"file\":\"$artifact\",\"summary\":\"Durable producer fixture artifact.\"}" \
149
161
  --timestamp "2026-06-26T00:02:00Z" >/dev/null 2>&1; then
150
162
  _pass "$label: record-gate-claim exits 0 at $step step"
151
163
  else
@@ -169,6 +181,10 @@ test_produce_claim() {
169
181
  console.error('expected status=verified, got', target.status);
170
182
  process.exit(1);
171
183
  }
184
+ if (typeof target.metadata?.expected_producer !== 'string' || typeof target.metadata?.recorded_by !== 'string') {
185
+ console.error('gate claim must distinguish its expected producer from the actor that recorded it');
186
+ process.exit(1);
187
+ }
172
188
  " 2>/dev/null \
173
189
  && _pass "$label: bundle contains $expected_claim_type with subjectType=$expected_subject_type, status=verified" \
174
190
  || _fail "$label: bundle missing or incorrect $expected_claim_type claim"
@@ -387,10 +403,318 @@ test_tamper_blocks \
387
403
  "builder.learn.evidence" \
388
404
  "learn" "builder.learn.evidence" "release"
389
405
 
406
+ # ─── Public CLI happy path + route-back ─────────────────────────────────────
407
+ echo ""
408
+ echo "=== PUBLIC CLI: complete canonical skill/operation path ==="
409
+ flow_agents_build_ts || _fail "public CLI fixture build failed"
410
+ PUBLIC_ROOT="$TMP/public/.kontourai/flow-agents"
411
+ PUBLIC_SESSION="$PUBLIC_ROOT/acme-builder-901"
412
+
413
+ public_flow() {
414
+ CODEX_SESSION_ID=builder-public-producers node "$ROOT/build/src/cli.js" workflow "$@"
415
+ }
416
+
417
+ public_review() {
418
+ CODEX_SESSION_ID=builder-public-reviewer node "$ROOT/build/src/cli.js" workflow "$@"
419
+ }
420
+
421
+ assert_public_step() {
422
+ local step="$1" skills="$2" operations="$3" status="${4:-active}"
423
+ local report
424
+ report="$(public_flow status --session-dir "$PUBLIC_SESSION" --json 2>/dev/null)" || {
425
+ _fail "public status failed at $step"
426
+ return
427
+ }
428
+ if node - "$report" "$step" "$skills" "$operations" "$status" <<'NODE'
429
+ const [reportText, step, skills, operations, status] = process.argv.slice(2);
430
+ const report = JSON.parse(reportText);
431
+ const statusMatches = status === 'active-or-blocked' ? ['active', 'blocked'].includes(report.status) : report.status === status;
432
+ if (report.definition_id !== 'builder.build' || report.current_step !== step || !statusMatches) process.exit(1);
433
+ if ((report.next_action?.skills || []).join(',') !== skills) process.exit(2);
434
+ if ((report.next_action?.operations || []).join(',') !== operations) process.exit(3);
435
+ NODE
436
+ then
437
+ _pass "public run projects $step to ${skills:-no skill}${operations:+ / $operations}"
438
+ else
439
+ _fail "public run projection mismatch at $step: $report"
440
+ fi
441
+ }
442
+
443
+ record_public_expectation() {
444
+ local expectation="$1" status="${2:-pass}"
445
+ local artifact slug
446
+ slug="$(basename "$PUBLIC_SESSION")"
447
+ case "$expectation" in
448
+ shaped-problem|shaped-outcome|shaped-constraints|shaped-non-goals|shaped-success|shaped-risk|open-decisions|slices-defined|work-items-filed) artifact="$PUBLIC_SESSION/$slug--idea-to-backlog.md" ;;
449
+ pickup-probe-readiness|probe-decisions-or-accepted-gaps) artifact="$PUBLIC_SESSION/$slug--pull-work.md" ;;
450
+ implementation-plan) artifact="$PUBLIC_SESSION/$slug--plan-work.md" ;;
451
+ implementation-scope) artifact="$PUBLIC_SESSION/$slug--deliver.md" ;;
452
+ tests-evidence) artifact="$PUBLIC_SESSION/$slug--plan-work.md" ;;
453
+ merge-readiness) artifact="$PUBLIC_SESSION/$slug--evidence-gate.md" ;;
454
+ pull-request-opened|ci-merge-readiness) artifact="$PUBLIC_SESSION/release.json" ;;
455
+ decision-evidence|learning-evidence) artifact="$PUBLIC_SESSION/learning.json" ;;
456
+ *) _fail "public producer fixture has no durable artifact for $expectation"; return ;;
457
+ esac
458
+ local -a args=(evidence --session-dir "$PUBLIC_SESSION" --expectation "$expectation" --status "$status" --summary "public producer fixture records a reviewable durable artifact" --evidence-ref-json "{\"kind\":\"artifact\",\"file\":\"$artifact\",\"summary\":\"Fixture artifact for $expectation.\"}")
459
+ if [ "$expectation" = "tests-evidence" ] && [ "$status" = "fail" ]; then
460
+ args+=(--route-reason implementation_defect)
461
+ fi
462
+ if [ "$expectation" = "tests-evidence" ] && [ "$status" = "pass" ]; then
463
+ local test_command criterion_one criterion_two command_ref
464
+ test_command="bash checks/check-public-session.sh .kontourai/flow-agents/$slug/state.json"
465
+ criterion_one="$(node - "$test_command" <<'NODE'
466
+ const command = process.argv[2];
467
+ process.stdout.write(JSON.stringify({ id: 'AC-1', status: 'pass', evidence_refs: [{ kind: 'command', excerpt: command, summary: 'Substantive fixture assertion for AC-1.' }] }));
468
+ NODE
469
+ )"
470
+ criterion_two="$(node - "$test_command" <<'NODE'
471
+ const command = process.argv[2];
472
+ process.stdout.write(JSON.stringify({ id: 'AC-2', status: 'pass', evidence_refs: [{ kind: 'command', excerpt: command, summary: 'Substantive fixture assertion for AC-2.' }] }));
473
+ NODE
474
+ )"
475
+ command_ref="$(node - "$test_command" <<'NODE'
476
+ const command = process.argv[2];
477
+ process.stdout.write(JSON.stringify({ kind: 'command', excerpt: command, summary: 'Exact project-local verification command.' }));
478
+ NODE
479
+ )"
480
+ args+=(--command "$test_command" --evidence-ref-json "$command_ref" --criterion-json "$criterion_one" --criterion-json "$criterion_two")
481
+ fi
482
+ local output
483
+ if ! output="$(public_flow "${args[@]}" 2>&1)"; then
484
+ _fail "public evidence failed for $expectation: $output"
485
+ fi
486
+ }
487
+
488
+ prepare_public_artifacts() {
489
+ node - "$PUBLIC_SESSION" <<'NODE'
490
+ const fs = require('node:fs');
491
+ const path = require('node:path');
492
+ const session = process.argv[2];
493
+ const slug = path.basename(session);
494
+ const write = (name, body) => fs.writeFileSync(path.join(session, name), body, 'utf8');
495
+ const projectRoot = path.dirname(path.dirname(path.dirname(session)));
496
+ const checksDir = path.join(projectRoot, 'checks');
497
+ fs.mkdirSync(checksDir, { recursive: true });
498
+ const checkScript = path.join(checksDir, 'check-public-session.sh');
499
+ fs.writeFileSync(checkScript, '#!/usr/bin/env bash\nset -eu\ntest -f "$1"\nprintf "1..1\\nok 1 - session exists\\n"\n', 'utf8');
500
+ fs.chmodSync(checkScript, 0o755);
501
+ write(`${slug}--idea-to-backlog.md`, '# Idea To Backlog Report\n\nShaped problem, slices, and filed work are reviewable here.\n');
502
+ write(`${slug}--pull-work.md`, '# Pull and Probe Report\n\nSelected work, scope, decisions, and accepted gaps are reviewable here.\n');
503
+ write(`${slug}--plan-work.md`, '# Plan\n\n## Definition Of Done\n\n- AC-1: The producer fixture records criterion-backed evidence.\n- AC-2: Every accepted criterion is backed by the exact test command.\n');
504
+ write(`${slug}--evidence-gate.md`, '# Evidence Gate\n\nAcceptance coverage and scope-integrity decision.\n');
505
+ write('acceptance.json', JSON.stringify({ schema_version: '1.0', task_slug: slug, criteria: [{ id: 'AC-1', description: 'The producer fixture records criterion-backed evidence.', status: 'pending', evidence_refs: [] }, { id: 'AC-2', description: 'Every accepted criterion is backed by the exact test command.', status: 'pending', evidence_refs: [] }], goal_fit: { status: 'pending', summary: 'Fixture has not completed Goal Fit review.' } }, null, 2));
506
+ write('handoff.json', JSON.stringify({ schema_version: '1.0', task_slug: slug, summary: 'Fixture execution handoff.', next_steps: ['Execute the reviewed plan.'], blockers: [] }, null, 2));
507
+ write('release.json', JSON.stringify({ schema_version: '1.0', task_slug: slug, decision: 'hold', updated_at: '2026-06-26T00:00:00Z', scope: 'producer fixture', evidence_ref: `${slug}--evidence-gate.md`, gates: [{ name: 'merge', status: 'hold', summary: 'Fixture is not authorized to merge.' }], rollback_plan: { status: 'not_required', summary: 'No release.', owner: 'fixture' }, observability_plan: { status: 'not_required', summary: 'No release.' }, post_deploy_checks: [], docs: { status: 'not_needed', summary: 'Fixture.' } }, null, 2));
508
+ write('learning.json', JSON.stringify({ schema_version: '1.0', task_slug: slug, status: 'learned', updated_at: '2026-06-26T00:00:00Z', records: [{ id: 'fixture-learning', recorded_at: '2026-06-26T00:00:00Z', source_refs: [`${slug}--evidence-gate.md`], outcome: 'success', facts: ['Fixture created reviewable durable artifacts.'], interpretation: 'No workflow correction is needed.', routing: [{ target: 'none', action: 'No follow-up required.', status: 'completed' }], correction: { needed: false, evidence: 'Fixture contract passes.' } }] }, null, 2));
509
+ NODE
510
+ }
511
+
512
+ mkdir -p "$PUBLIC_ROOT"
513
+ mkdir -p "$PUBLIC_SESSION"
514
+ printf 'Selected Work Item: acme/builder#901\n' > "$PUBLIC_SESSION/acme-builder-901--pull-work.md"
515
+ public_flow start --artifact-root "$PUBLIC_ROOT" --flow builder.build \
516
+ --work-item acme/builder#901 --assignment-provider local-file --summary "Public producer path" >/dev/null 2>&1 \
517
+ || _fail "public workflow start failed"
518
+ prepare_public_artifacts
519
+ assert_public_step "design-probe" "pickup-probe" ""
520
+ record_public_expectation "pickup-probe-readiness"
521
+ assert_public_step "design-probe" "pickup-probe" "" "active-or-blocked"
522
+ record_public_expectation "probe-decisions-or-accepted-gaps"
523
+ assert_public_step "plan" "plan-work" ""
524
+ record_public_expectation "implementation-plan"
525
+ assert_public_step "execute" "execute-plan" ""
526
+ record_public_expectation "implementation-scope"
527
+ assert_public_step "verify" "review-work,verify-work" ""
528
+ if public_review critique --session-dir "$PUBLIC_SESSION" --verdict pass --summary "Missing lanes must fail." --artifact-ref "$PUBLIC_SESSION/$(basename "$PUBLIC_SESSION")--deliver.md" >/dev/null 2>&1; then
529
+ _fail "public critique accepted a passing review without lanes"
530
+ else
531
+ _pass "public critique rejects a passing review without lanes"
532
+ fi
533
+ PUBLIC_CRITIQUE_OUTPUT="$(public_review critique --session-dir "$PUBLIC_SESSION" \
534
+ --verdict pass \
535
+ --summary "Authenticated review found no blocking fixture findings." \
536
+ --artifact-ref "$PUBLIC_SESSION/$(basename "$PUBLIC_SESSION")--deliver.md" \
537
+ --lane-json "{\"id\":\"code-review\",\"status\":\"pass\",\"summary\":\"Public fixture code review completed.\",\"evidence_refs\":[{\"kind\":\"artifact\",\"file\":\"$PUBLIC_SESSION/$(basename "$PUBLIC_SESSION")--deliver.md\",\"summary\":\"Reviewed public fixture delivery artifact.\"}]}" 2>&1)" \
538
+ || _fail "public authenticated critique failed before tests-evidence: $PUBLIC_CRITIQUE_OUTPUT"
539
+ PUBLIC_TEST_COMMAND="bash checks/check-public-session.sh .kontourai/flow-agents/$(basename "$PUBLIC_SESSION")/state.json"
540
+ if public_flow evidence --session-dir "$PUBLIC_SESSION" --expectation tests-evidence --status pass --command "bash -c true" --summary "Wrapped no-op must not count as tests evidence." \
541
+ --criterion-json '{"id":"AC-1","status":"pass","evidence_refs":[{"kind":"command","excerpt":"bash -c true","summary":"Wrapped no-op."}]}' \
542
+ --criterion-json '{"id":"AC-2","status":"pass","evidence_refs":[{"kind":"command","excerpt":"bash -c true","summary":"Wrapped no-op."}]}' >/dev/null 2>&1; then
543
+ _fail "public tests-evidence accepted a wrapped no-op command"
544
+ else
545
+ _pass "public tests-evidence rejects a wrapped no-op command"
546
+ fi
547
+ if public_flow evidence --session-dir "$PUBLIC_SESSION" --expectation tests-evidence --status pass --command "$PUBLIC_TEST_COMMAND" --summary "External-only criterion evidence must not pass." \
548
+ --criterion-json '{"id":"AC-1","status":"pass","evidence_refs":[{"kind":"external","url":"https://example.invalid/ac-1","summary":"External attestation only."}]}' \
549
+ --criterion-json '{"id":"AC-2","status":"pass","evidence_refs":[{"kind":"external","url":"https://example.invalid/ac-2","summary":"External attestation only."}]}' >/dev/null 2>&1; then
550
+ _fail "public tests-evidence accepted external-only passing criterion evidence"
551
+ else
552
+ _pass "public tests-evidence rejects external-only passing criterion evidence"
553
+ fi
554
+ if public_flow evidence --session-dir "$PUBLIC_SESSION" --expectation tests-evidence --status pass --command "$PUBLIC_TEST_COMMAND" --summary "Every criterion must cite the exact command." \
555
+ --criterion-json "{\"id\":\"AC-1\",\"status\":\"pass\",\"evidence_refs\":[{\"kind\":\"command\",\"excerpt\":\"$PUBLIC_TEST_COMMAND\",\"summary\":\"Exact command for AC-1.\"}]}" \
556
+ --criterion-json '{"id":"AC-2","status":"pass","evidence_refs":[{"kind":"command","excerpt":"bash checks/check-public-session.sh .kontourai/flow-agents/missing/state.json","summary":"Different command for AC-2."}]}' >/dev/null 2>&1; then
557
+ _fail "public tests-evidence accepted a criterion without the exact command"
558
+ else
559
+ _pass "public tests-evidence requires the exact command for every criterion"
560
+ fi
561
+ record_public_expectation "tests-evidence"
562
+ if node - "$PUBLIC_SESSION/trust.bundle" <<'NODE'
563
+ const fs = require('node:fs');
564
+ const bundle = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
565
+ const verified = (claimType, subjectType) => bundle.claims.some((claim) => (
566
+ claim.claimType === claimType
567
+ && claim.subjectType === subjectType
568
+ && claim.status === 'verified'
569
+ ));
570
+ if (!verified('workflow.critique.review', 'workflow-critique')) process.exit(1);
571
+ if (!verified('workflow.acceptance.criterion', 'flow-step')) process.exit(2);
572
+ if (!verified('builder.verify.tests', 'flow-step')) process.exit(3);
573
+ NODE
574
+ then
575
+ _pass "public verify gate is backed by current critique, criterion, and test claims"
576
+ else
577
+ _fail "public verify gate is missing one or more declared trust-bundle claims"
578
+ fi
579
+ assert_public_step "merge-ready" "evidence-gate" ""
580
+ record_public_expectation "merge-readiness"
581
+ assert_public_step "pr-open" "" "publish-change"
582
+ record_public_expectation "pull-request-opened"
583
+ assert_public_step "merge-ready-ci" "release-readiness" ""
584
+ record_public_expectation "ci-merge-readiness"
585
+ assert_public_step "learn" "learning-review" ""
586
+ record_public_expectation "decision-evidence"
587
+ assert_public_step "learn" "learning-review" "" "active-or-blocked"
588
+ record_public_expectation "learning-evidence"
589
+ if node - "$PUBLIC_SESSION/trust.bundle" <<'NODE'
590
+ const fs = require('node:fs');
591
+ const bundle = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
592
+ if (!bundle.claims.some((claim) => claim.claimType === 'builder.learn.evidence' && claim.subjectType === 'release' && claim.status === 'verified')) process.exit(1);
593
+ NODE
594
+ then
595
+ _pass "learning-review records verified learning.json evidence"
596
+ else
597
+ _fail "learning-review did not record durable learning.json evidence in trust.bundle"
598
+ fi
599
+ assert_public_step "learn" "" "" "completed"
600
+
601
+ echo ""
602
+ echo "=== PUBLIC CLI: failed verify route-back remains canonical ==="
603
+ ROUTE_SESSION="$PUBLIC_ROOT/acme-builder-902"
604
+ PUBLIC_SESSION="$ROUTE_SESSION"
605
+ mkdir -p "$ROUTE_SESSION"
606
+ printf 'Selected Work Item: acme/builder#902\n' > "$ROUTE_SESSION/acme-builder-902--pull-work.md"
607
+ public_flow start --artifact-root "$PUBLIC_ROOT" --flow builder.build \
608
+ --work-item acme/builder#902 --assignment-provider local-file --summary "Public route-back path" >/dev/null 2>&1 \
609
+ || _fail "route-back workflow start failed"
610
+ prepare_public_artifacts
611
+ for expectation in pickup-probe-readiness probe-decisions-or-accepted-gaps implementation-plan implementation-scope; do
612
+ record_public_expectation "$expectation"
613
+ done
614
+ ROUTE_CRITIQUE_OUTPUT="$(public_review critique --session-dir "$PUBLIC_SESSION" \
615
+ --verdict pass \
616
+ --summary "Authenticated review completed before failed verification evidence." \
617
+ --artifact-ref "$PUBLIC_SESSION/$(basename "$PUBLIC_SESSION")--deliver.md" \
618
+ --lane-json "{\"id\":\"code-review\",\"status\":\"pass\",\"summary\":\"Public fixture code review completed.\",\"evidence_refs\":[{\"kind\":\"artifact\",\"file\":\"$PUBLIC_SESSION/$(basename "$PUBLIC_SESSION")--deliver.md\",\"summary\":\"Reviewed public fixture delivery artifact.\"}]}" 2>&1)" \
619
+ || _fail "public authenticated critique failed before failed tests-evidence: $ROUTE_CRITIQUE_OUTPUT"
620
+ # Seed current verified acceptance prerequisites through the private producer
621
+ # without synchronizing Flow. The subsequent public failed tests claim replaces
622
+ # the provisional passing tests check, preserves these criteria + the clean
623
+ # critique, and is the only attachment/evaluation for this gate visit.
624
+ ROUTE_TEST_COMMAND="bash checks/check-public-session.sh .kontourai/flow-agents/$(basename "$PUBLIC_SESSION")/state.json"
625
+ ROUTE_COMMAND_REF="$(node - "$ROUTE_TEST_COMMAND" <<'NODE'
626
+ const command = process.argv[2];
627
+ process.stdout.write(JSON.stringify({ kind: 'command', excerpt: command, summary: 'Current route-back prerequisite command.' }));
628
+ NODE
629
+ )"
630
+ ROUTE_CRITERION_ONE="$(node - "$ROUTE_TEST_COMMAND" <<'NODE'
631
+ const command = process.argv[2];
632
+ process.stdout.write(JSON.stringify({ id: 'AC-1', status: 'pass', evidence_refs: [{ kind: 'command', excerpt: command, summary: 'Current AC-1 prerequisite.' }] }));
633
+ NODE
634
+ )"
635
+ ROUTE_CRITERION_TWO="$(node - "$ROUTE_TEST_COMMAND" <<'NODE'
636
+ const command = process.argv[2];
637
+ process.stdout.write(JSON.stringify({ id: 'AC-2', status: 'pass', evidence_refs: [{ kind: 'command', excerpt: command, summary: 'Current AC-2 prerequisite.' }] }));
638
+ NODE
639
+ )"
640
+ ROUTE_OBSERVED="$(node - "$ROUTE_TEST_COMMAND" <<'NODE'
641
+ const command = process.argv[2];
642
+ process.stdout.write(JSON.stringify({ command, exit_code: 0, test_count: 1, output_sha256: '0'.repeat(64) }));
643
+ NODE
644
+ )"
645
+ CODEX_SESSION_ID=builder-public-producers flow_agents_node "workflow-sidecar" record-gate-claim "$PUBLIC_SESSION" \
646
+ --expectation tests-evidence --status pass --summary "Seed complete current acceptance prerequisites before routed failure." \
647
+ --command "$ROUTE_TEST_COMMAND" --observed-command-json "$ROUTE_OBSERVED" \
648
+ --evidence-ref-json "$ROUTE_COMMAND_REF" --criterion-json "$ROUTE_CRITERION_ONE" --criterion-json "$ROUTE_CRITERION_TWO" >/dev/null 2>&1 \
649
+ || _fail "failed to seed current acceptance prerequisites for routed failure"
650
+ record_public_expectation "tests-evidence" "fail"
651
+ ROUTE_REPORT="$(public_flow status --session-dir "$PUBLIC_SESSION" --json 2>/dev/null)"
652
+ if node - "$ROUTE_REPORT" <<'NODE'
653
+ const report = JSON.parse(process.argv[2]);
654
+ if (report.current_step !== 'execute') process.exit(1);
655
+ if ((report.next_action?.skills || []).join(',') !== 'execute-plan') process.exit(2);
656
+ if (!/attempt 1\/3 returned to `execute` for `implementation_defect`/.test(report.next_action?.summary || '')) process.exit(3);
657
+ NODE
658
+ then
659
+ _pass "complete failed verify evidence routes back through Flow once to execute for implementation_defect"
660
+ else
661
+ _fail "public failed-verify route-back was not projected correctly: $ROUTE_REPORT"
662
+ fi
663
+
664
+ echo ""
665
+ echo "=== PUBLIC CLI: complete builder.shape path when supported ==="
666
+ SHAPE_SESSION="$PUBLIC_ROOT/builder-shape-903"
667
+ PUBLIC_SESSION="$SHAPE_SESSION"
668
+ SHAPE_START_OUTPUT="$(public_flow start --artifact-root "$PUBLIC_ROOT" --flow builder.shape \
669
+ --task-slug builder-shape-903 --summary "Shape public producer path" 2>&1)"
670
+ if [ "$?" -ne 0 ]; then
671
+ if printf '%s' "$SHAPE_START_OUTPUT" | rg -q 'supports only|unsupported.*builder\.shape|Unknown workflow'; then
672
+ _pass "public builder.shape path is deferred until the installed runtime exposes it"
673
+ else
674
+ _fail "public builder.shape start failed unexpectedly: $SHAPE_START_OUTPUT"
675
+ fi
676
+ else
677
+ assert_public_shape_step() {
678
+ local step="$1" skills="$2" status="${3:-active}"
679
+ local report
680
+ report="$(public_flow status --session-dir "$SHAPE_SESSION" --json 2>/dev/null)" || {
681
+ _fail "public shape status failed at $step"
682
+ return
683
+ }
684
+ if node - "$report" "$step" "$skills" "$status" <<'NODE'
685
+ const [reportText, step, skills, status] = process.argv.slice(2);
686
+ const report = JSON.parse(reportText);
687
+ const statusMatches = status === 'active-or-completed'
688
+ ? ['active', 'completed'].includes(report.status)
689
+ : status === 'active-or-blocked'
690
+ ? ['active', 'blocked'].includes(report.status)
691
+ : report.status === status;
692
+ if (report.definition_id !== 'builder.shape' || report.current_step !== step || !statusMatches) process.exit(1);
693
+ if ((report.next_action?.skills || []).join(',') !== skills) process.exit(2);
694
+ NODE
695
+ then
696
+ _pass "public shape run projects $step to ${skills:-no skill}"
697
+ else
698
+ _fail "public shape projection mismatch at $step: $report"
699
+ fi
700
+ }
701
+
702
+ prepare_public_artifacts
703
+ assert_public_shape_step "shape" "idea-to-backlog" "active-or-blocked"
704
+ for expectation in shaped-problem shaped-outcome shaped-constraints shaped-non-goals shaped-success shaped-risk; do
705
+ record_public_expectation "$expectation"
706
+ done
707
+ assert_public_shape_step "breakdown" "idea-to-backlog"
708
+ record_public_expectation "slices-defined"
709
+ assert_public_shape_step "file-issues" "idea-to-backlog"
710
+ record_public_expectation "work-items-filed"
711
+ assert_public_shape_step "shape-done" "" "active-or-completed"
712
+ fi
713
+
390
714
  # ─── Summary ──────────────────────────────────────────────────────────────────
391
715
  echo ""
392
716
  if [ "$errors" -eq 0 ]; then
393
- echo "Builder step producer tests passed (6 claims: produce + tamper-block each)."
717
+ echo "Builder step producer tests passed (legacy tamper checks plus full public happy path and route-back)."
394
718
  exit 0
395
719
  fi
396
720
  echo "Builder step producer tests FAILED: $errors issue(s)."