@kontourai/flow-agents 3.5.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 (111) hide show
  1. package/.github/workflows/ci.yml +4 -0
  2. package/CHANGELOG.md +15 -0
  3. package/build/src/builder-flow-run-adapter.d.ts +32 -3
  4. package/build/src/builder-flow-run-adapter.js +113 -20
  5. package/build/src/builder-flow-runtime.d.ts +26 -3
  6. package/build/src/builder-flow-runtime.js +502 -49
  7. package/build/src/builder-lifecycle-authority.d.ts +35 -0
  8. package/build/src/builder-lifecycle-authority.js +219 -0
  9. package/build/src/cli/assignment-provider.d.ts +14 -7
  10. package/build/src/cli/assignment-provider.js +128 -65
  11. package/build/src/cli/builder-run.js +46 -9
  12. package/build/src/cli/workflow-artifact-cleanup-audit.js +3 -0
  13. package/build/src/cli/workflow-sidecar.d.ts +30 -3
  14. package/build/src/cli/workflow-sidecar.js +825 -99
  15. package/build/src/cli/workflow.d.ts +2 -0
  16. package/build/src/cli/workflow.js +769 -0
  17. package/build/src/cli.js +2 -0
  18. package/build/src/flow-kit/validate.d.ts +17 -0
  19. package/build/src/flow-kit/validate.js +340 -2
  20. package/build/src/index.d.ts +4 -0
  21. package/build/src/index.js +7 -5
  22. package/build/src/lib/observed-command.d.ts +7 -0
  23. package/build/src/lib/observed-command.js +100 -0
  24. package/build/src/lib/package-version.d.ts +2 -0
  25. package/build/src/lib/package-version.js +13 -0
  26. package/build/src/lib/pinned-cli-command.d.ts +6 -0
  27. package/build/src/lib/pinned-cli-command.js +21 -0
  28. package/build/src/tools/generate-context-map.js +5 -3
  29. package/context/contracts/artifact-contract.md +1 -1
  30. package/context/scripts/hooks/config-protection.js +8 -1
  31. package/context/scripts/hooks/lib/config-protection-remedies.js +3 -0
  32. package/context/scripts/hooks/lib/kit-catalog.js +1 -1
  33. package/context/scripts/hooks/stop-goal-fit.js +79 -10
  34. package/docs/context-map.md +24 -20
  35. package/docs/developer-architecture.md +1 -1
  36. package/docs/public-workflow-cli.md +132 -0
  37. package/docs/skills-map.md +51 -27
  38. package/docs/spec/builder-flow-runtime.md +78 -40
  39. package/docs/workflow-usage-guide.md +110 -38
  40. package/evals/ci/run-baseline.sh +2 -0
  41. package/evals/fixtures/hook-influence/cases.json +2 -2
  42. package/evals/integration/test_builder_entry_enforcement.sh +57 -45
  43. package/evals/integration/test_builder_step_producers.sh +297 -6
  44. package/evals/integration/test_bundle_install.sh +258 -55
  45. package/evals/integration/test_critique_supersession_roundtrip.sh +21 -8
  46. package/evals/integration/test_current_json_per_actor.sh +12 -0
  47. package/evals/integration/test_dual_emit_flow_step.sh +62 -43
  48. package/evals/integration/test_flowdef_session_activation.sh +145 -25
  49. package/evals/integration/test_flowdef_session_history_preservation.sh +23 -21
  50. package/evals/integration/test_gate_lockdown.sh +3 -5
  51. package/evals/integration/test_goal_fit_hook.sh +60 -2
  52. package/evals/integration/test_liveness_verdict.sh +14 -17
  53. package/evals/integration/test_phase_map_and_gate_claim.sh +63 -38
  54. package/evals/integration/test_public_workflow_cli.sh +573 -0
  55. package/evals/integration/test_pull_work_liveness_preflight.sh +22 -66
  56. package/evals/integration/test_sidecar_field_preservation.sh +36 -11
  57. package/evals/integration/test_workflow_sidecar_writer.sh +277 -44
  58. package/evals/integration/test_workflow_steering_hook.sh +15 -38
  59. package/evals/run.sh +2 -0
  60. package/evals/static/test_builder_skill_coherence.sh +247 -0
  61. package/evals/static/test_library_exports.sh +5 -2
  62. package/evals/static/test_workflow_skills.sh +13 -325
  63. package/kits/builder/flows/build.flow.json +22 -0
  64. package/kits/builder/flows/shape.flow.json +9 -9
  65. package/kits/builder/kit.json +70 -16
  66. package/kits/builder/skills/builder-shape/SKILL.md +75 -75
  67. package/kits/builder/skills/continue-work/SKILL.md +45 -106
  68. package/kits/builder/skills/deliver/SKILL.md +96 -442
  69. package/kits/builder/skills/design-probe/SKILL.md +40 -139
  70. package/kits/builder/skills/evidence-gate/SKILL.md +59 -201
  71. package/kits/builder/skills/execute-plan/SKILL.md +54 -125
  72. package/kits/builder/skills/fix-bug/SKILL.md +42 -132
  73. package/kits/builder/skills/gate-review/SKILL.md +60 -211
  74. package/kits/builder/skills/idea-to-backlog/SKILL.md +63 -244
  75. package/kits/builder/skills/learning-review/SKILL.md +63 -170
  76. package/kits/builder/skills/pickup-probe/SKILL.md +54 -111
  77. package/kits/builder/skills/plan-work/SKILL.md +51 -185
  78. package/kits/builder/skills/pull-work/SKILL.md +136 -485
  79. package/kits/builder/skills/release-readiness/SKILL.md +66 -107
  80. package/kits/builder/skills/review-work/SKILL.md +89 -176
  81. package/kits/builder/skills/tdd-workflow/SKILL.md +53 -147
  82. package/kits/builder/skills/verify-work/SKILL.md +101 -113
  83. package/package.json +2 -2
  84. package/schemas/builder-lifecycle-authorization.schema.json +57 -0
  85. package/schemas/lifecycle-authority-keys.schema.json +25 -0
  86. package/schemas/workflow-state.schema.json +1 -1
  87. package/scripts/hooks/config-protection.js +8 -1
  88. package/scripts/hooks/lib/config-protection-remedies.js +3 -0
  89. package/scripts/hooks/lib/kit-catalog.js +1 -1
  90. package/scripts/hooks/stop-goal-fit.js +79 -10
  91. package/src/builder-flow-run-adapter.ts +156 -23
  92. package/src/builder-flow-runtime.ts +535 -53
  93. package/src/builder-lifecycle-authority.ts +218 -0
  94. package/src/cli/assignment-provider-lock.test.mjs +83 -0
  95. package/src/cli/assignment-provider.ts +91 -22
  96. package/src/cli/builder-flow-run-adapter.test.mjs +4 -2
  97. package/src/cli/builder-flow-runtime.test.mjs +597 -8
  98. package/src/cli/builder-run.ts +54 -9
  99. package/src/cli/kit-metadata-security.test.mjs +232 -6
  100. package/src/cli/public-api.test.mjs +15 -0
  101. package/src/cli/workflow-artifact-cleanup-audit.ts +3 -0
  102. package/src/cli/workflow-sidecar-execution-proof.test.mjs +90 -0
  103. package/src/cli/workflow-sidecar.ts +748 -99
  104. package/src/cli/workflow.ts +708 -0
  105. package/src/cli.ts +2 -0
  106. package/src/flow-kit/validate.ts +320 -2
  107. package/src/index.ts +20 -5
  108. package/src/lib/observed-command.ts +96 -0
  109. package/src/lib/package-version.ts +15 -0
  110. package/src/lib/pinned-cli-command.ts +23 -0
  111. package/src/tools/generate-context-map.ts +5 -3
@@ -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,285 @@ 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" = "pass" ]; then
460
+ local test_command criterion_one criterion_two command_ref
461
+ test_command="bash checks/check-public-session.sh .kontourai/flow-agents/$slug/state.json"
462
+ criterion_one="$(node - "$test_command" <<'NODE'
463
+ const command = process.argv[2];
464
+ process.stdout.write(JSON.stringify({ id: 'AC-1', status: 'pass', evidence_refs: [{ kind: 'command', excerpt: command, summary: 'Substantive fixture assertion for AC-1.' }] }));
465
+ NODE
466
+ )"
467
+ criterion_two="$(node - "$test_command" <<'NODE'
468
+ const command = process.argv[2];
469
+ process.stdout.write(JSON.stringify({ id: 'AC-2', status: 'pass', evidence_refs: [{ kind: 'command', excerpt: command, summary: 'Substantive fixture assertion for AC-2.' }] }));
470
+ NODE
471
+ )"
472
+ command_ref="$(node - "$test_command" <<'NODE'
473
+ const command = process.argv[2];
474
+ process.stdout.write(JSON.stringify({ kind: 'command', excerpt: command, summary: 'Exact project-local verification command.' }));
475
+ NODE
476
+ )"
477
+ args+=(--command "$test_command" --evidence-ref-json "$command_ref" --criterion-json "$criterion_one" --criterion-json "$criterion_two")
478
+ fi
479
+ local output
480
+ if ! output="$(public_flow "${args[@]}" 2>&1)"; then
481
+ _fail "public evidence failed for $expectation: $output"
482
+ fi
483
+ }
484
+
485
+ prepare_public_artifacts() {
486
+ node - "$PUBLIC_SESSION" <<'NODE'
487
+ const fs = require('node:fs');
488
+ const path = require('node:path');
489
+ const session = process.argv[2];
490
+ const slug = path.basename(session);
491
+ const write = (name, body) => fs.writeFileSync(path.join(session, name), body, 'utf8');
492
+ const projectRoot = path.dirname(path.dirname(path.dirname(session)));
493
+ const checksDir = path.join(projectRoot, 'checks');
494
+ fs.mkdirSync(checksDir, { recursive: true });
495
+ const checkScript = path.join(checksDir, 'check-public-session.sh');
496
+ fs.writeFileSync(checkScript, '#!/usr/bin/env bash\nset -eu\ntest -f "$1"\nprintf "1..1\\nok 1 - session exists\\n"\n', 'utf8');
497
+ fs.chmodSync(checkScript, 0o755);
498
+ write(`${slug}--idea-to-backlog.md`, '# Idea To Backlog Report\n\nShaped problem, slices, and filed work are reviewable here.\n');
499
+ write(`${slug}--pull-work.md`, '# Pull and Probe Report\n\nSelected work, scope, decisions, and accepted gaps are reviewable here.\n');
500
+ 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');
501
+ write(`${slug}--evidence-gate.md`, '# Evidence Gate\n\nAcceptance coverage and scope-integrity decision.\n');
502
+ 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));
503
+ 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));
504
+ 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));
505
+ 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));
506
+ NODE
507
+ }
508
+
509
+ mkdir -p "$PUBLIC_ROOT"
510
+ mkdir -p "$PUBLIC_SESSION"
511
+ printf 'Selected Work Item: acme/builder#901\n' > "$PUBLIC_SESSION/acme-builder-901--pull-work.md"
512
+ public_flow start --artifact-root "$PUBLIC_ROOT" --flow builder.build \
513
+ --work-item acme/builder#901 --assignment-provider local-file --summary "Public producer path" >/dev/null 2>&1 \
514
+ || _fail "public workflow start failed"
515
+ prepare_public_artifacts
516
+ assert_public_step "design-probe" "pickup-probe" ""
517
+ record_public_expectation "pickup-probe-readiness"
518
+ assert_public_step "design-probe" "pickup-probe" "" "active-or-blocked"
519
+ record_public_expectation "probe-decisions-or-accepted-gaps"
520
+ assert_public_step "plan" "plan-work" ""
521
+ record_public_expectation "implementation-plan"
522
+ assert_public_step "execute" "execute-plan" ""
523
+ record_public_expectation "implementation-scope"
524
+ assert_public_step "verify" "review-work,verify-work" ""
525
+ 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
526
+ _fail "public critique accepted a passing review without lanes"
527
+ else
528
+ _pass "public critique rejects a passing review without lanes"
529
+ fi
530
+ PUBLIC_CRITIQUE_OUTPUT="$(public_review critique --session-dir "$PUBLIC_SESSION" \
531
+ --verdict pass \
532
+ --summary "Authenticated review found no blocking fixture findings." \
533
+ --artifact-ref "$PUBLIC_SESSION/$(basename "$PUBLIC_SESSION")--deliver.md" \
534
+ --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)" \
535
+ || _fail "public authenticated critique failed before tests-evidence: $PUBLIC_CRITIQUE_OUTPUT"
536
+ PUBLIC_TEST_COMMAND="bash checks/check-public-session.sh .kontourai/flow-agents/$(basename "$PUBLIC_SESSION")/state.json"
537
+ 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." \
538
+ --criterion-json '{"id":"AC-1","status":"pass","evidence_refs":[{"kind":"command","excerpt":"bash -c true","summary":"Wrapped no-op."}]}' \
539
+ --criterion-json '{"id":"AC-2","status":"pass","evidence_refs":[{"kind":"command","excerpt":"bash -c true","summary":"Wrapped no-op."}]}' >/dev/null 2>&1; then
540
+ _fail "public tests-evidence accepted a wrapped no-op command"
541
+ else
542
+ _pass "public tests-evidence rejects a wrapped no-op command"
543
+ fi
544
+ 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." \
545
+ --criterion-json '{"id":"AC-1","status":"pass","evidence_refs":[{"kind":"external","url":"https://example.invalid/ac-1","summary":"External attestation only."}]}' \
546
+ --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
547
+ _fail "public tests-evidence accepted external-only passing criterion evidence"
548
+ else
549
+ _pass "public tests-evidence rejects external-only passing criterion evidence"
550
+ fi
551
+ 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." \
552
+ --criterion-json "{\"id\":\"AC-1\",\"status\":\"pass\",\"evidence_refs\":[{\"kind\":\"command\",\"excerpt\":\"$PUBLIC_TEST_COMMAND\",\"summary\":\"Exact command for AC-1.\"}]}" \
553
+ --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
554
+ _fail "public tests-evidence accepted a criterion without the exact command"
555
+ else
556
+ _pass "public tests-evidence requires the exact command for every criterion"
557
+ fi
558
+ record_public_expectation "tests-evidence"
559
+ if node - "$PUBLIC_SESSION/trust.bundle" <<'NODE'
560
+ const fs = require('node:fs');
561
+ const bundle = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
562
+ const verified = (claimType, subjectType) => bundle.claims.some((claim) => (
563
+ claim.claimType === claimType
564
+ && claim.subjectType === subjectType
565
+ && claim.status === 'verified'
566
+ ));
567
+ if (!verified('workflow.critique.review', 'workflow-critique')) process.exit(1);
568
+ if (!verified('workflow.acceptance.criterion', 'flow-step')) process.exit(2);
569
+ if (!verified('builder.verify.tests', 'flow-step')) process.exit(3);
570
+ NODE
571
+ then
572
+ _pass "public verify gate is backed by current critique, criterion, and test claims"
573
+ else
574
+ _fail "public verify gate is missing one or more declared trust-bundle claims"
575
+ fi
576
+ assert_public_step "merge-ready" "evidence-gate" ""
577
+ record_public_expectation "merge-readiness"
578
+ assert_public_step "pr-open" "" "publish-change"
579
+ record_public_expectation "pull-request-opened"
580
+ assert_public_step "merge-ready-ci" "release-readiness" ""
581
+ record_public_expectation "ci-merge-readiness"
582
+ assert_public_step "learn" "learning-review" ""
583
+ record_public_expectation "decision-evidence"
584
+ assert_public_step "learn" "learning-review" "" "active-or-blocked"
585
+ record_public_expectation "learning-evidence"
586
+ if node - "$PUBLIC_SESSION/trust.bundle" <<'NODE'
587
+ const fs = require('node:fs');
588
+ const bundle = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
589
+ if (!bundle.claims.some((claim) => claim.claimType === 'builder.learn.evidence' && claim.subjectType === 'release' && claim.status === 'verified')) process.exit(1);
590
+ NODE
591
+ then
592
+ _pass "learning-review records verified learning.json evidence"
593
+ else
594
+ _fail "learning-review did not record durable learning.json evidence in trust.bundle"
595
+ fi
596
+ assert_public_step "learn" "" "" "completed"
597
+
598
+ echo ""
599
+ echo "=== PUBLIC CLI: failed verify route-back remains canonical ==="
600
+ ROUTE_SESSION="$PUBLIC_ROOT/acme-builder-902"
601
+ PUBLIC_SESSION="$ROUTE_SESSION"
602
+ mkdir -p "$ROUTE_SESSION"
603
+ printf 'Selected Work Item: acme/builder#902\n' > "$ROUTE_SESSION/acme-builder-902--pull-work.md"
604
+ public_flow start --artifact-root "$PUBLIC_ROOT" --flow builder.build \
605
+ --work-item acme/builder#902 --assignment-provider local-file --summary "Public route-back path" >/dev/null 2>&1 \
606
+ || _fail "route-back workflow start failed"
607
+ prepare_public_artifacts
608
+ for expectation in pickup-probe-readiness probe-decisions-or-accepted-gaps implementation-plan implementation-scope; do
609
+ record_public_expectation "$expectation"
610
+ done
611
+ ROUTE_CRITIQUE_OUTPUT="$(public_review critique --session-dir "$PUBLIC_SESSION" \
612
+ --verdict pass \
613
+ --summary "Authenticated review completed before failed verification evidence." \
614
+ --artifact-ref "$PUBLIC_SESSION/$(basename "$PUBLIC_SESSION")--deliver.md" \
615
+ --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)" \
616
+ || _fail "public authenticated critique failed before failed tests-evidence: $ROUTE_CRITIQUE_OUTPUT"
617
+ record_public_expectation "tests-evidence" "fail"
618
+ ROUTE_REPORT="$(public_flow status --session-dir "$PUBLIC_SESSION" --json 2>/dev/null)"
619
+ if node - "$ROUTE_REPORT" <<'NODE'
620
+ const report = JSON.parse(process.argv[2]);
621
+ if (report.current_step !== 'verify') process.exit(1);
622
+ if ((report.next_action?.skills || []).join(',') !== 'review-work,verify-work') process.exit(2);
623
+ if (!/attempt 1\/3 returned to `verify`/.test(report.next_action?.summary || '')) process.exit(3);
624
+ NODE
625
+ then
626
+ _pass "failed verify evidence routes back through Flow with attempt 1/3 and canonical producers"
627
+ else
628
+ _fail "public failed-verify route-back was not projected correctly: $ROUTE_REPORT"
629
+ fi
630
+
631
+ echo ""
632
+ echo "=== PUBLIC CLI: complete builder.shape path when supported ==="
633
+ SHAPE_SESSION="$PUBLIC_ROOT/builder-shape-903"
634
+ PUBLIC_SESSION="$SHAPE_SESSION"
635
+ SHAPE_START_OUTPUT="$(public_flow start --artifact-root "$PUBLIC_ROOT" --flow builder.shape \
636
+ --task-slug builder-shape-903 --summary "Shape public producer path" 2>&1)"
637
+ if [ "$?" -ne 0 ]; then
638
+ if printf '%s' "$SHAPE_START_OUTPUT" | rg -q 'supports only|unsupported.*builder\.shape|Unknown workflow'; then
639
+ _pass "public builder.shape path is deferred until the installed runtime exposes it"
640
+ else
641
+ _fail "public builder.shape start failed unexpectedly: $SHAPE_START_OUTPUT"
642
+ fi
643
+ else
644
+ assert_public_shape_step() {
645
+ local step="$1" skills="$2" status="${3:-active}"
646
+ local report
647
+ report="$(public_flow status --session-dir "$SHAPE_SESSION" --json 2>/dev/null)" || {
648
+ _fail "public shape status failed at $step"
649
+ return
650
+ }
651
+ if node - "$report" "$step" "$skills" "$status" <<'NODE'
652
+ const [reportText, step, skills, status] = process.argv.slice(2);
653
+ const report = JSON.parse(reportText);
654
+ const statusMatches = status === 'active-or-completed'
655
+ ? ['active', 'completed'].includes(report.status)
656
+ : status === 'active-or-blocked'
657
+ ? ['active', 'blocked'].includes(report.status)
658
+ : report.status === status;
659
+ if (report.definition_id !== 'builder.shape' || report.current_step !== step || !statusMatches) process.exit(1);
660
+ if ((report.next_action?.skills || []).join(',') !== skills) process.exit(2);
661
+ NODE
662
+ then
663
+ _pass "public shape run projects $step to ${skills:-no skill}"
664
+ else
665
+ _fail "public shape projection mismatch at $step: $report"
666
+ fi
667
+ }
668
+
669
+ prepare_public_artifacts
670
+ assert_public_shape_step "shape" "idea-to-backlog" "active-or-blocked"
671
+ for expectation in shaped-problem shaped-outcome shaped-constraints shaped-non-goals shaped-success shaped-risk; do
672
+ record_public_expectation "$expectation"
673
+ done
674
+ assert_public_shape_step "breakdown" "idea-to-backlog"
675
+ record_public_expectation "slices-defined"
676
+ assert_public_shape_step "file-issues" "idea-to-backlog"
677
+ record_public_expectation "work-items-filed"
678
+ assert_public_shape_step "shape-done" "" "active-or-completed"
679
+ fi
680
+
390
681
  # ─── Summary ──────────────────────────────────────────────────────────────────
391
682
  echo ""
392
683
  if [ "$errors" -eq 0 ]; then
393
- echo "Builder step producer tests passed (6 claims: produce + tamper-block each)."
684
+ echo "Builder step producer tests passed (legacy tamper checks plus full public happy path and route-back)."
394
685
  exit 0
395
686
  fi
396
687
  echo "Builder step producer tests FAILED: $errors issue(s)."