@kontourai/flow-agents 3.6.0 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/build/src/builder-flow-run-adapter.d.ts +22 -2
  3. package/build/src/builder-flow-run-adapter.js +93 -28
  4. package/build/src/builder-flow-runtime.d.ts +8 -3
  5. package/build/src/builder-flow-runtime.js +308 -47
  6. package/build/src/cli/assignment-provider.d.ts +4 -7
  7. package/build/src/cli/assignment-provider.js +67 -13
  8. package/build/src/cli/builder-run.js +14 -18
  9. package/build/src/cli/workflow-sidecar.d.ts +28 -4
  10. package/build/src/cli/workflow-sidecar.js +801 -97
  11. package/build/src/cli/workflow.js +290 -42
  12. package/build/src/flow-kit/validate.d.ts +17 -0
  13. package/build/src/flow-kit/validate.js +340 -2
  14. package/build/src/index.js +5 -5
  15. package/build/src/lib/observed-command.d.ts +7 -0
  16. package/build/src/lib/observed-command.js +100 -0
  17. package/build/src/tools/generate-context-map.js +5 -3
  18. package/context/scripts/hooks/lib/kit-catalog.js +1 -1
  19. package/context/scripts/hooks/stop-goal-fit.js +78 -9
  20. package/docs/context-map.md +22 -20
  21. package/docs/developer-architecture.md +1 -1
  22. package/docs/public-workflow-cli.md +76 -7
  23. package/docs/skills-map.md +51 -27
  24. package/docs/spec/builder-flow-runtime.md +41 -40
  25. package/docs/workflow-usage-guide.md +109 -42
  26. package/evals/fixtures/hook-influence/cases.json +2 -2
  27. package/evals/integration/test_builder_entry_enforcement.sh +52 -41
  28. package/evals/integration/test_builder_step_producers.sh +297 -6
  29. package/evals/integration/test_bundle_install.sh +212 -63
  30. package/evals/integration/test_critique_supersession_roundtrip.sh +21 -8
  31. package/evals/integration/test_current_json_per_actor.sh +12 -0
  32. package/evals/integration/test_dual_emit_flow_step.sh +62 -43
  33. package/evals/integration/test_flowdef_session_activation.sh +145 -25
  34. package/evals/integration/test_flowdef_session_history_preservation.sh +23 -21
  35. package/evals/integration/test_gate_lockdown.sh +3 -5
  36. package/evals/integration/test_goal_fit_hook.sh +60 -2
  37. package/evals/integration/test_liveness_verdict.sh +14 -17
  38. package/evals/integration/test_phase_map_and_gate_claim.sh +63 -38
  39. package/evals/integration/test_public_workflow_cli.sh +325 -11
  40. package/evals/integration/test_pull_work_liveness_preflight.sh +22 -66
  41. package/evals/integration/test_sidecar_field_preservation.sh +36 -11
  42. package/evals/integration/test_workflow_sidecar_writer.sh +277 -44
  43. package/evals/integration/test_workflow_steering_hook.sh +15 -38
  44. package/evals/run.sh +2 -0
  45. package/evals/static/test_builder_skill_coherence.sh +247 -0
  46. package/evals/static/test_library_exports.sh +5 -2
  47. package/evals/static/test_workflow_skills.sh +13 -325
  48. package/kits/builder/flows/build.flow.json +22 -0
  49. package/kits/builder/flows/shape.flow.json +9 -9
  50. package/kits/builder/kit.json +70 -16
  51. package/kits/builder/skills/builder-shape/SKILL.md +75 -75
  52. package/kits/builder/skills/continue-work/SKILL.md +45 -106
  53. package/kits/builder/skills/deliver/SKILL.md +96 -442
  54. package/kits/builder/skills/design-probe/SKILL.md +40 -139
  55. package/kits/builder/skills/evidence-gate/SKILL.md +59 -201
  56. package/kits/builder/skills/execute-plan/SKILL.md +54 -125
  57. package/kits/builder/skills/fix-bug/SKILL.md +42 -132
  58. package/kits/builder/skills/gate-review/SKILL.md +60 -211
  59. package/kits/builder/skills/idea-to-backlog/SKILL.md +63 -244
  60. package/kits/builder/skills/learning-review/SKILL.md +63 -170
  61. package/kits/builder/skills/pickup-probe/SKILL.md +54 -111
  62. package/kits/builder/skills/plan-work/SKILL.md +51 -185
  63. package/kits/builder/skills/pull-work/SKILL.md +136 -485
  64. package/kits/builder/skills/release-readiness/SKILL.md +66 -107
  65. package/kits/builder/skills/review-work/SKILL.md +89 -176
  66. package/kits/builder/skills/tdd-workflow/SKILL.md +53 -147
  67. package/kits/builder/skills/verify-work/SKILL.md +101 -113
  68. package/package.json +2 -2
  69. package/scripts/hooks/lib/kit-catalog.js +1 -1
  70. package/scripts/hooks/stop-goal-fit.js +78 -9
  71. package/src/builder-flow-run-adapter.ts +118 -32
  72. package/src/builder-flow-runtime.ts +331 -61
  73. package/src/cli/assignment-provider-lock.test.mjs +83 -0
  74. package/src/cli/assignment-provider.ts +62 -13
  75. package/src/cli/builder-flow-run-adapter.test.mjs +4 -2
  76. package/src/cli/builder-flow-runtime.test.mjs +194 -8
  77. package/src/cli/builder-run.ts +3 -9
  78. package/src/cli/kit-metadata-security.test.mjs +232 -6
  79. package/src/cli/public-api.test.mjs +15 -0
  80. package/src/cli/workflow-sidecar-execution-proof.test.mjs +90 -0
  81. package/src/cli/workflow-sidecar.ts +724 -97
  82. package/src/cli/workflow.ts +277 -40
  83. package/src/flow-kit/validate.ts +320 -2
  84. package/src/index.ts +6 -5
  85. package/src/lib/observed-command.ts +96 -0
  86. package/src/tools/generate-context-map.ts +5 -3
@@ -251,43 +251,71 @@ fi
251
251
  echo ""
252
252
  echo "── P-d declared-only: session WITH active_flow_id=builder.build / active_step_id=verify ──"
253
253
 
254
- # Create a session at the declared first step, then use the transition surface to
255
- # establish the verify-state fixture exercised by this producer test.
254
+ # Each fixture starts at the declared first step with selected-work evidence,
255
+ # then advances by recording the durable artifacts that the declared producers
256
+ # require. current.json is only projected by the writer during those transitions.
256
257
  mkdir -p "$SESSION_ROOT"
257
- if flow_agents_node "$WRITER" ensure-session \
258
- --artifact-root "$SESSION_ROOT" \
259
- --task-slug dual-emit-test \
260
- --flow-id builder.build \
261
- --skip-ownership-guard \
262
- --title "Declared-Only Test" \
263
- --summary "Test declared-only emit for ADR 0016 P-d." \
264
- --criterion "Tests pass" \
265
- --timestamp "2026-06-26T00:00:00Z" >"$TMP/ensure.out" 2>"$TMP/ensure.err"; then
266
- _pass "ensure-session with --flow-id succeeds at the declared first step"
267
- else
268
- _fail "ensure-session with --flow-id failed: $(cat "$TMP/ensure.out" "$TMP/ensure.err")"
269
- fi
270
-
271
- DUAL_DIR="$SESSION_ROOT/dual-emit-test"
272
258
 
273
259
  advance_builder_to_verify() {
274
- local session_dir="$1"
275
- flow_agents_node "$WRITER" record-gate-claim "$session_dir" --status pass --expectation selected-work \
276
- --summary "Selected the fixture Work Item." --timestamp "2026-06-26T00:00:10Z" >/dev/null 2>&1 \
277
- && flow_agents_node "$WRITER" record-gate-claim "$session_dir" --status pass --expectation pickup-probe-readiness \
278
- --summary "Pickup readiness is established." --timestamp "2026-06-26T00:00:20Z" >/dev/null 2>&1 \
279
- && flow_agents_node "$WRITER" record-gate-claim "$session_dir" --status pass --expectation probe-decisions-or-accepted-gaps \
280
- --summary "Probe decisions are recorded." --timestamp "2026-06-26T00:00:30Z" >/dev/null 2>&1 \
281
- && flow_agents_node "$WRITER" record-gate-claim "$session_dir" --status pass --expectation implementation-plan \
282
- --summary "Implementation plan is recorded." --timestamp "2026-06-26T00:00:40Z" >/dev/null 2>&1 \
283
- && flow_agents_node "$WRITER" record-gate-claim "$session_dir" --status pass --expectation implementation-scope \
284
- --summary "Implementation scope is recorded." --timestamp "2026-06-26T00:00:50Z" >/dev/null 2>&1
260
+ local slug="$1"
261
+ local title="$2"
262
+ local summary="$3"
263
+ local session_dir="$SESSION_ROOT/$slug"
264
+ local pull_work="$session_dir/$slug--pull-work.md"
265
+ local plan="$session_dir/$slug--plan-work.md"
266
+ local deliver="$session_dir/$slug--deliver.md"
267
+ local work_item="${slug/-/:}"
268
+
269
+ mkdir -p "$session_dir"
270
+ printf 'Selected Work Item: %s\n' "$work_item" > "$pull_work"
271
+ FLOW_AGENTS_ACTOR="$slug-fixture-actor" node "$ROOT/build/src/cli.js" workflow start \
272
+ --artifact-root "$SESSION_ROOT" \
273
+ --flow builder.build \
274
+ --work-item "$work_item" \
275
+ --assignment-provider local-file \
276
+ --title "$title" \
277
+ --summary "$summary" \
278
+ --criterion "Fixture producer artifacts reach verify." >/dev/null 2>&1 || return 1
279
+ flow_agents_node "$WRITER" init-plan "$deliver" \
280
+ --source-request "Test declared-only producer artifacts." \
281
+ --summary "Fixture plan for declared-only producer artifacts." \
282
+ --timestamp "2026-06-26T00:00:05Z" >/dev/null 2>&1 || return 1
283
+ node - "$session_dir" "$slug" <<'NODE'
284
+ const fs = require('node:fs');
285
+ const path = require('node:path');
286
+ const [dir, slug] = process.argv.slice(2);
287
+ fs.writeFileSync(path.join(dir, `${slug}--plan-work.md`), '# Plan\n\n- AC-1: Reach verify through declared producer artifacts.\n', 'utf8');
288
+ fs.writeFileSync(path.join(dir, 'acceptance.json'), JSON.stringify({
289
+ schema_version: '1.0',
290
+ task_slug: slug,
291
+ criteria: [{
292
+ id: 'AC-1',
293
+ description: 'Reach verify through declared producer artifacts.',
294
+ status: 'pending',
295
+ evidence_refs: [],
296
+ }],
297
+ goal_fit: { status: 'pending', summary: 'Fixture has not completed Goal Fit review.' },
298
+ }, null, 2), 'utf8');
299
+ NODE
300
+ FLOW_AGENTS_ACTOR="$slug-fixture-actor" node "$ROOT/build/src/cli.js" workflow evidence --session-dir "$session_dir" --status pass --expectation pickup-probe-readiness \
301
+ --summary "Pickup readiness is recorded in the selected-work artifact." \
302
+ --evidence-ref-json "{\"kind\":\"artifact\",\"file\":\"$pull_work\",\"summary\":\"Declared durable producer artifact for pickup-probe-readiness.\"}" >/dev/null 2>&1 || return 1
303
+ FLOW_AGENTS_ACTOR="$slug-fixture-actor" node "$ROOT/build/src/cli.js" workflow evidence --session-dir "$session_dir" --status pass --expectation probe-decisions-or-accepted-gaps \
304
+ --summary "Probe decisions are recorded in the selected-work artifact." \
305
+ --evidence-ref-json "{\"kind\":\"artifact\",\"file\":\"$pull_work\",\"summary\":\"Declared durable producer artifact for probe decisions.\"}" >/dev/null 2>&1 || return 1
306
+ FLOW_AGENTS_ACTOR="$slug-fixture-actor" node "$ROOT/build/src/cli.js" workflow evidence --session-dir "$session_dir" --status pass --expectation implementation-plan \
307
+ --summary "Implementation plan is recorded in the plan artifact." \
308
+ --evidence-ref-json "{\"kind\":\"artifact\",\"file\":\"$plan\",\"summary\":\"Declared durable producer artifact for implementation-plan.\"}" >/dev/null 2>&1 || return 1
309
+ FLOW_AGENTS_ACTOR="$slug-fixture-actor" node "$ROOT/build/src/cli.js" workflow evidence --session-dir "$session_dir" --status pass --expectation implementation-scope \
310
+ --summary "Implementation scope is recorded in the delivery artifact." \
311
+ --evidence-ref-json "{\"kind\":\"artifact\",\"file\":\"$deliver\",\"summary\":\"Declared durable producer artifact for implementation-scope.\"}" >/dev/null 2>&1 || return 1
285
312
  }
286
313
 
287
- if advance_builder_to_verify "$DUAL_DIR"; then
288
- _pass "canonical Builder Flow advances through required gates to verify"
314
+ DUAL_DIR="$SESSION_ROOT/dual-emit-test"
315
+ if advance_builder_to_verify "dual-emit-test" "Declared-Only Test" "Test declared-only emit for ADR 0016 P-d."; then
316
+ _pass "canonical Builder Flow advances through declared producer artifacts to verify"
289
317
  else
290
- _fail "canonical Builder Flow did not reach verify"
318
+ _fail "canonical Builder Flow did not reach verify through declared producer artifacts"
291
319
  fi
292
320
 
293
321
  # Verify current.json carries the flow keys
@@ -345,19 +373,10 @@ echo "── P-d declared-only: policy-kind check maps to builder.verify.policy-
345
373
 
346
374
  # Use a separate canonical run because the failing tests evidence above legitimately
347
375
  # routes its run back to execute before this independent producer assertion.
348
- if flow_agents_node "$WRITER" ensure-session \
349
- --artifact-root "$SESSION_ROOT" \
350
- --task-slug dual-emit-policy \
351
- --flow-id builder.build \
352
- --skip-ownership-guard \
353
- --title "Declared Policy Test" \
354
- --summary "Test declared-only policy emit for ADR 0016 P-d." \
355
- --criterion "Policy passes" \
356
- --timestamp "2026-06-26T00:01:30Z" >/dev/null 2>&1 \
357
- && advance_builder_to_verify "$SESSION_ROOT/dual-emit-policy"; then
358
- _pass "policy fixture canonical Builder Flow reaches verify"
376
+ if advance_builder_to_verify "dual-emit-policy" "Declared Policy Test" "Test declared-only policy emit for ADR 0016 P-d."; then
377
+ _pass "policy fixture canonical Builder Flow reaches verify through declared producer artifacts"
359
378
  else
360
- _fail "policy fixture canonical Builder Flow did not reach verify"
379
+ _fail "policy fixture canonical Builder Flow did not reach verify through declared producer artifacts"
361
380
  fi
362
381
 
363
382
  POLICY_DIR="$SESSION_ROOT/dual-emit-policy"
@@ -6,11 +6,11 @@
6
6
  # correctly sets active_step_id via the phase_map at each phase.
7
7
  #
8
8
  # Tests:
9
- # 1. ensure-session --flow-id builder.build writes active_flow_id + default
10
- # active_step_id (pull-work) to current.json.
9
+ # 1. Pre-seeded concrete pull-work evidence lets ensure-session --flow-id
10
+ # builder.build bind the local Work Item and project the canonical Flow.
11
11
  # 2. advance-state through phases (planning→execution→verification) sets correct
12
12
  # active_step_id via phase_map at each transition.
13
- # 3. At the verify step, record-gate-claim for tests-evidence produces
13
+ # 3. At the verify step, public workflow evidence for tests-evidence produces
14
14
  # builder.verify.tests (status=verified) in the bundle — producer fires.
15
15
  # 4. A TAMPERED builder.verify.tests bundle at the verify step BLOCKS (exit 2)
16
16
  # with the tamper warning naming the declared claimType.
@@ -46,17 +46,17 @@ echo "=== 1. ensure-session --flow-id builder.build activates FlowDefinition-dri
46
46
  MAIN_AROOT="$TMP/main-project/.kontourai/flow-agents"
47
47
  SLUG="activation-test"
48
48
  SESSION_DIR="$MAIN_AROOT/$SLUG"
49
- mkdir -p "$MAIN_AROOT"
49
+ mkdir -p "$SESSION_DIR"
50
+ printf 'Selected Work Item: activation:test\n' > "$SESSION_DIR/$SLUG--pull-work.md"
50
51
 
51
- if flow_agents_node "$WRITER" ensure-session \
52
+ if FLOW_AGENTS_ACTOR=activation-test-actor node "$ROOT/build/src/cli.js" workflow start \
52
53
  --artifact-root "$MAIN_AROOT" \
53
- --task-slug "$SLUG" \
54
- --actor activation-test-actor \
54
+ --flow builder.build \
55
+ --work-item activation:test \
56
+ --assignment-provider local-file \
55
57
  --title "Step 1 activation test" \
56
58
  --summary "Test that --flow-id builder.build activates the FlowDefinition-driven path." \
57
- --criterion "All gates produce declared claims" \
58
- --flow-id builder.build \
59
- --timestamp "2026-06-01T00:00:00Z" >/dev/null 2>&1; then
59
+ --criterion "All gates produce declared claims" >/dev/null 2>&1; then
60
60
  _pass "ensure-session starts the canonical Builder Flow from a canonical artifact root"
61
61
  else
62
62
  _fail "ensure-session failed to start the canonical Builder Flow"
@@ -76,17 +76,54 @@ console.log('current.json: active_flow_id=' + c.active_flow_id + ' active_step_i
76
76
  && _pass "ensure-session records trusted selection and projects the canonical Flow run at design-probe" \
77
77
  || _fail "ensure-session did not create and project the canonical Flow run"
78
78
 
79
+ if node - "$ROOT/kits/builder/kit.json" "$ROOT" <<'NODE'
80
+ const fs = require('node:fs');
81
+ const kit = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
82
+ const { workflowTriggersFor } = require(`${process.argv[3]}/scripts/hooks/lib/kit-catalog.js`);
83
+ const roles = new Map(kit.skill_roles.map((entry) => [entry.skill_id.replace(/^builder\./, ''), entry.role]));
84
+ const trigger = kit.workflow_triggers.find((entry) => entry.id === 'builder-build-work');
85
+ if (roles.get(trigger.default_skill) !== 'entrypoint') process.exit(1);
86
+ if (!trigger.conditional_skills.every((entry) => roles.get(entry.skill) === 'profile')) process.exit(2);
87
+ const [rendered] = workflowTriggersFor(process.argv[3], 'implementation-work-detected');
88
+ if (!rendered?.steering.includes('activate `tdd-workflow`; otherwise activate `deliver`')) process.exit(5);
89
+ if (!rendered.steering.includes('public `flow-agents workflow` interface') || rendered.steering.includes('workflow:sidecar')) process.exit(6);
90
+ for (const action of kit.flow_step_actions) {
91
+ if (!action.skills.every((skill) => roles.get(skill) === 'step')) process.exit(3);
92
+ }
93
+ for (const skill of ['design-probe', 'continue-work', 'gate-review', 'fix-bug']) {
94
+ if (kit.flow_step_actions.some((action) => action.skills.includes(skill))) process.exit(4);
95
+ if (rendered.steering.includes(`activate \`${skill}\``)) process.exit(7);
96
+ }
97
+ NODE
98
+ then
99
+ _pass "runtime steering activates the entrypoint/profile and keeps primitives/extensions outside automatic step actions"
100
+ else
101
+ _fail "activation metadata blurred entrypoint, profile, step, primitive, or extension roles"
102
+ fi
103
+
79
104
  # ─── TEST 2: advance-state sets active_step_id via phase_map ─────────────────
80
105
  echo ""
81
106
  echo "=== 2. advance-state through phases sets active_step_id via phase_map ==="
82
107
 
83
- flow_agents_node "$WRITER" init-plan "$SESSION_DIR/$SLUG--deliver.md" \
108
+ PHASE_AROOT="$TMP/phase-project/.kontourai/flow-agents"
109
+ PHASE_SLUG="phase-map-test"
110
+ PHASE_DIR="$PHASE_AROOT/$PHASE_SLUG"
111
+ mkdir -p "$PHASE_AROOT"
112
+
113
+ flow_agents_node "$WRITER" ensure-session \
114
+ --artifact-root "$PHASE_AROOT" \
115
+ --task-slug "$PHASE_SLUG" \
116
+ --title "Phase map test" \
117
+ --summary "Test FlowDefinition phase-map projection without an active Flow run." \
118
+ --timestamp "2026-06-01T00:00:30Z" >/dev/null 2>&1
119
+
120
+ flow_agents_node "$WRITER" init-plan "$PHASE_DIR/$PHASE_SLUG--deliver.md" \
84
121
  --source-request "Test" --summary "Testing" \
85
122
  --timestamp "2026-06-01T00:00:30Z" >/dev/null 2>&1
86
123
 
87
124
  test_phase_step() {
88
125
  local phase="$1" expected_step="$2"
89
- flow_agents_node "$WRITER" advance-state "$SESSION_DIR" \
126
+ flow_agents_node "$WRITER" advance-state "$PHASE_DIR" \
90
127
  --status in_progress --phase "$phase" \
91
128
  --summary "Testing phase $phase." \
92
129
  --next-action "Continue." \
@@ -95,7 +132,7 @@ test_phase_step() {
95
132
  local actual
96
133
  actual=$(node -e "
97
134
  const fs = require('fs');
98
- const c = JSON.parse(fs.readFileSync('$MAIN_AROOT/current.json', 'utf8'));
135
+ const c = JSON.parse(fs.readFileSync('$PHASE_AROOT/current.json', 'utf8'));
99
136
  console.log(c.active_step_id || '');
100
137
  " 2>/dev/null)
101
138
  if [ "$actual" = "$expected_step" ]; then
@@ -109,18 +146,95 @@ test_phase_step "planning" "plan"
109
146
  test_phase_step "execution" "execute"
110
147
  test_phase_step "verification" "verify"
111
148
 
112
- # ─── TEST 3: at verify step, record-gate-claim produces builder.verify.tests ──
149
+ node - "$SESSION_DIR" <<'NODE'
150
+ const fs = require('node:fs');
151
+ const path = require('node:path');
152
+ const dir = process.argv[2];
153
+ const slug = path.basename(dir);
154
+ fs.writeFileSync(path.join(dir, `${slug}--plan-work.md`), '# Plan\n\n- AC-1: Verify FlowDefinition producer evidence.\n', 'utf8');
155
+ fs.writeFileSync(path.join(dir, 'acceptance.json'), JSON.stringify({ schema_version: '1.0', task_slug: slug, criteria: [{ id: 'AC-1', description: 'Verify FlowDefinition producer evidence.', status: 'pending', evidence_refs: [] }], goal_fit: { status: 'pending', summary: 'Fixture has not completed Goal Fit review.' } }, null, 2), 'utf8');
156
+ NODE
157
+ mkdir -p "$TMP/main-project/checks"
158
+ cat > "$TMP/main-project/checks/check-flow-step.test.mjs" <<'JS'
159
+ import test from 'node:test';
160
+ import assert from 'node:assert/strict';
161
+ import fs from 'node:fs';
162
+
163
+ test('the canonical Builder run reached verify', () => {
164
+ const state = JSON.parse(fs.readFileSync('.kontourai/flow-agents/activation-test/state.json', 'utf8'));
165
+ assert.equal(state.flow_run?.current_step, 'verify');
166
+ });
167
+ JS
168
+
169
+ record_passing_producer_evidence() {
170
+ local expectation="$1" artifact="$2"
171
+ if FLOW_AGENTS_ACTOR=activation-test-actor node "$ROOT/build/src/cli.js" workflow evidence \
172
+ --session-dir "$SESSION_DIR" \
173
+ --status pass \
174
+ --expectation "$expectation" \
175
+ --summary "Fixture records passing $expectation evidence from its declared durable artifact." \
176
+ --evidence-ref-json "{\"kind\":\"artifact\",\"file\":\"$artifact\",\"summary\":\"Declared durable producer artifact for $expectation.\"}" >/dev/null 2>&1; then
177
+ _pass "public workflow evidence records declared durable producer evidence for $expectation"
178
+ else
179
+ _fail "public workflow evidence did not record declared durable producer evidence for $expectation"
180
+ fi
181
+ }
182
+
183
+ # Satisfy the canonical Flow itself. advance-state above tests phase-map projection;
184
+ # these producer claims advance the persisted Flow run to verify.
185
+ record_passing_producer_evidence "pickup-probe-readiness" "$SESSION_DIR/$SLUG--pull-work.md"
186
+ record_passing_producer_evidence "probe-decisions-or-accepted-gaps" "$SESSION_DIR/$SLUG--pull-work.md"
187
+ record_passing_producer_evidence "implementation-plan" "$SESSION_DIR/$SLUG--plan-work.md"
188
+ record_passing_producer_evidence "implementation-scope" "$SESSION_DIR/$SLUG--deliver.md"
189
+
190
+ # ─── TEST 3: clean critique + complete tests evidence at verify ───────────────
113
191
  echo ""
114
- echo "=== 3. verify step: producer fires record-gate-claim produces builder.verify.tests ==="
192
+ echo "=== 3. verify step: clean critique and criterion-backed producer evidence ==="
193
+
194
+ if FLOW_AGENTS_ACTOR=activation-reviewer node "$ROOT/build/src/cli.js" workflow critique \
195
+ --session-dir "$SESSION_DIR" \
196
+ --id "activation-review" \
197
+ --verdict pass \
198
+ --summary "Fixture review found no open findings before verification." \
199
+ --artifact-ref "$SESSION_DIR/$SLUG--deliver.md" \
200
+ --lane-json "{\"id\":\"code-review\",\"status\":\"pass\",\"summary\":\"Reviewed the delivered activation fixture.\",\"evidence_refs\":[{\"kind\":\"artifact\",\"file\":\"$SESSION_DIR/$SLUG--deliver.md\",\"summary\":\"Reviewed delivery artifact.\"}]}" >/dev/null 2>&1; then
201
+ _pass "public workflow critique records a clean review before passing tests-evidence"
202
+ else
203
+ _fail "public workflow critique did not record a clean review before tests-evidence"
204
+ fi
115
205
 
116
- if flow_agents_node "$WRITER" record-gate-claim "$SESSION_DIR" \
206
+ TEST_COMMAND="node --test checks/check-flow-step.test.mjs"
207
+ CRITERION_JSON="$(node - "$TEST_COMMAND" <<'NODE'
208
+ const command = process.argv[2];
209
+ process.stdout.write(JSON.stringify({
210
+ id: 'AC-1',
211
+ status: 'pass',
212
+ evidence_refs: [{
213
+ kind: 'command',
214
+ excerpt: command,
215
+ summary: 'Asserts the exact session is at the verify Flow step.',
216
+ }],
217
+ }));
218
+ NODE
219
+ )"
220
+ COMMAND_REF="$(node - "$TEST_COMMAND" <<'NODE'
221
+ const command = process.argv[2];
222
+ process.stdout.write(JSON.stringify({ kind: 'command', excerpt: command, summary: 'Runs the project-local verify-step assertion.' }));
223
+ NODE
224
+ )"
225
+
226
+ TEST_EVIDENCE_OUTPUT="$(FLOW_AGENTS_ACTOR=activation-test-actor node "$ROOT/build/src/cli.js" workflow evidence \
227
+ --session-dir "$SESSION_DIR" \
117
228
  --status pass \
118
- --summary "All tests pass." \
229
+ --summary "The substantive producer fixture command passed for every declared criterion." \
119
230
  --expectation "tests-evidence" \
120
- --timestamp "2026-06-01T00:02:00Z" >/dev/null 2>&1; then
121
- _pass "record-gate-claim at verify step succeeds (expectation=tests-evidence)"
231
+ --command "$TEST_COMMAND" \
232
+ --criterion-json "$CRITERION_JSON" \
233
+ --evidence-ref-json "$COMMAND_REF" 2>&1)"
234
+ if [ "$?" -eq 0 ]; then
235
+ _pass "public workflow evidence records passing criterion-backed tests-evidence at verify"
122
236
  else
123
- _fail "record-gate-claim at verify step FAILED"
237
+ _fail "public workflow evidence did not record passing criterion-backed tests-evidence: $TEST_EVIDENCE_OUTPUT"
124
238
  fi
125
239
 
126
240
  node -e "
@@ -128,13 +242,17 @@ const fs = require('fs');
128
242
  const bundlePath = '$SESSION_DIR/trust.bundle';
129
243
  if (!fs.existsSync(bundlePath)) throw new Error('trust.bundle not found');
130
244
  const bundle = JSON.parse(fs.readFileSync(bundlePath, 'utf8'));
131
- const declared = (bundle.claims || []).find(c => c.claimType === 'builder.verify.tests');
245
+ const declared = (bundle.claims || []).find(c => c.metadata?.gate_claim?.expectation_id === 'tests-evidence');
132
246
  if (!declared) throw new Error('MISSING builder.verify.tests; claims: ' + (bundle.claims||[]).map(c=>c.claimType).join(', '));
133
- if (declared.status !== 'verified') throw new Error('expected status=verified, got ' + declared.status);
134
- console.log('builder.verify.tests: subjectType=' + declared.subjectType + ' status=' + declared.status + ' value=' + declared.value);
247
+ if (declared.claimType !== 'builder.verify.tests' || declared.status !== 'verified') throw new Error('expected verified builder.verify.tests, got ' + JSON.stringify({ claimType: declared.claimType, status: declared.status }));
248
+ const critique = (bundle.claims || []).find(c => c.metadata?.origin === 'critique' && c.value === 'pass' && !c.metadata?.superseded_by);
249
+ if (!critique) throw new Error('MISSING clean live critique before tests-evidence');
250
+ const criterion = (bundle.claims || []).find(c => c.metadata?.origin === 'acceptance' && c.metadata?.criterion?.id === 'AC-1');
251
+ if (!criterion || criterion.value !== 'pass' || criterion.status !== 'verified') throw new Error('MISSING verified completed AC-1 criterion');
252
+ console.log('builder.verify.tests: subjectType=' + declared.subjectType + ' status=' + declared.status + ' value=' + declared.value + '; clean critique and AC-1 verified');
135
253
  " 2>&1 \
136
- && _pass "bundle contains builder.verify.tests (subjectType=flow-step, status=verified, value=pass)" \
137
- || _fail "bundle missing or incorrect builder.verify.tests claim"
254
+ && _pass "bundle contains declared tests evidence with a clean critique and completed AC-1" \
255
+ || _fail "bundle lacks declared tests evidence, clean critique, or completed AC-1"
138
256
 
139
257
  # ─── TEST 4: tampered bundle at verify step BLOCKS ────────────────────────────
140
258
  echo ""
@@ -145,10 +263,12 @@ TAMPER_SLUG="tamper-verify-test"
145
263
  mkdir -p "$TAMPER_DIR"
146
264
  printf '# Test repo\n' > "$TAMPER_DIR/AGENTS.md"
147
265
  mkdir -p "$TAMPER_DIR/.kontourai/flow-agents/$TAMPER_SLUG"
266
+ printf 'Selected Work Item: local:%s\n' "$TAMPER_SLUG" > "$TAMPER_DIR/.kontourai/flow-agents/$TAMPER_SLUG/$TAMPER_SLUG--pull-work.md"
148
267
 
149
268
  flow_agents_node "$WRITER" ensure-session \
150
269
  --artifact-root "$TAMPER_DIR/.kontourai/flow-agents" \
151
270
  --task-slug "$TAMPER_SLUG" \
271
+ --actor tamper-verify-actor \
152
272
  --title "Tamper verify test" \
153
273
  --summary "Testing tamper detection at verify step." \
154
274
  --flow-id builder.build \
@@ -2,7 +2,7 @@
2
2
  # test_flowdef_session_history_preservation.sh — Integration eval for ADR 0016 Step 0.
3
3
  #
4
4
  # Proves:
5
- # 1. A FlowDefinition-driven session (ensure-session --flow-id builder.build, step=verify)
5
+ # 1. A noncanonical FlowDefinition-projected session (advance-state, step=verify)
6
6
  # records a check via the declared builder.verify.tests path, then record-critique and
7
7
  # record-learning PRESERVE the prior declared check + critique claims in the rebuilt
8
8
  # bundle (no history loss).
@@ -34,18 +34,18 @@ WRITER="workflow-sidecar"
34
34
  echo ""
35
35
  echo "=== 1. FlowDefinition-driven session: record-critique/record-learning preserve declared claims ==="
36
36
 
37
- FLOW_AROOT="$TMP/flow-aroot"
37
+ FLOW_AROOT="$TMP/flow-project/.kontourai/flow-agents"
38
38
  SLUG="history-flow-test"
39
39
  SESSION_DIR="$FLOW_AROOT/$SLUG"
40
40
  mkdir -p "$FLOW_AROOT"
41
41
 
42
- # Start at the declared first step, then establish the verify-state fixture.
42
+ # Create a plain sidecar session, then project it onto the verify step. This test covers the
43
+ # generic ADR 0016 sidecar path; canonical Builder runs are covered by the public workflow tests.
43
44
  flow_agents_node "$WRITER" ensure-session \
44
45
  --artifact-root "$FLOW_AROOT" \
45
46
  --task-slug "$SLUG" \
46
47
  --title "History preservation test" \
47
48
  --summary "Test that declared builder.* claims survive round-trips." \
48
- --flow-id builder.build \
49
49
  --timestamp "2026-06-01T00:00:00Z" >/dev/null 2>&1
50
50
 
51
51
  flow_agents_node "$WRITER" init-plan "$SESSION_DIR/$SLUG--deliver.md" \
@@ -80,6 +80,8 @@ flow_agents_node "$WRITER" record-critique "$SESSION_DIR" \
80
80
  --id "code-review" \
81
81
  --verdict pass \
82
82
  --summary "Code review passed." \
83
+ --lane-json "{\"id\":\"code\",\"status\":\"pass\",\"summary\":\"Code review passed.\",\"evidence_refs\":[{\"kind\":\"artifact\",\"file\":\"$SESSION_DIR/$SLUG--deliver.md\",\"summary\":\"Reviewed the delivery artifact.\"}]}" \
84
+ --artifact-ref "$SESSION_DIR/$SLUG--deliver.md" \
83
85
  --timestamp "2026-06-01T00:02:00Z" >/dev/null 2>&1
84
86
 
85
87
  # Assert builder.verify.tests survived the record-critique round-trip
@@ -93,24 +95,21 @@ console.log('after record-critique: builder.verify.tests status=' + declared.sta
93
95
  && _pass "builder.verify.tests declared claim preserved after record-critique (no history loss)" \
94
96
  || _fail "builder.verify.tests declared claim LOST after record-critique (history loss)"
95
97
 
96
- # Also verify the critique claim itself is present.
97
- # In a flow-driven session (verify step), critique maps to the declared builder.verify.policy-compliance
98
- # (the critique heuristic matches: subjectType=artifact + claimType contains "compliance").
99
- # workflow.critique.review is emitted in no-flow sessions only (P-d: shadow retired).
98
+ # Also verify the critique claim itself is present and remains distinct from optional policy
99
+ # evidence. The Builder runtime consumes metadata.origin=critique directly; a code review must
100
+ # not be relabeled as builder.verify.policy-compliance merely because that expectation is open.
100
101
  node -e "
101
102
  const fs = require('fs');
102
103
  const bundle = JSON.parse(fs.readFileSync('$SESSION_DIR/trust.bundle', 'utf8'));
103
104
  const claims = bundle.claims || [];
104
- // Declared critique claim for verify-step: builder.verify.policy-compliance
105
- const crit = claims.find(c => c.claimType === 'builder.verify.policy-compliance');
106
- if (!crit) throw new Error('MISSING builder.verify.policy-compliance critique claim after record-critique; claims: ' + claims.map(c=>c.claimType).join(', '));
107
- // Must NOT have workflow.critique.review in a flow-driven session (no shadow, P-d)
108
- const legacy = claims.find(c => c.claimType === 'workflow.critique.review');
109
- if (legacy) throw new Error('UNEXPECTED workflow.critique.review in flow-driven session (P-d retired shadow); id=' + legacy.id);
110
- console.log('declared critique claim: claimType=' + crit.claimType + ' value=' + crit.value);
105
+ const crit = claims.find(c => c.claimType === 'workflow.critique.review' && c.metadata?.origin === 'critique');
106
+ if (!crit) throw new Error('MISSING first-class critique claim after record-critique; claims: ' + claims.map(c=>c.claimType).join(', '));
107
+ const mislabeled = claims.find(c => c.claimType === 'builder.verify.policy-compliance' && c.metadata?.origin === 'critique');
108
+ if (mislabeled) throw new Error('critique was mislabeled as optional policy-compliance evidence: ' + mislabeled.id);
109
+ console.log('critique claim remains distinct: claimType=' + crit.claimType + ' value=' + crit.value);
111
110
  " 2>&1 \
112
- && _pass "builder.verify.policy-compliance declared critique claim present (no workflow.critique.review shadow, P-d)" \
113
- || _fail "declared critique claim MISSING or unexpected workflow.critique.review found after record-critique"
111
+ && _pass "first-class critique preserved without policy-compliance mislabeling" \
112
+ || _fail "critique claim missing or mislabeled after record-critique"
114
113
 
115
114
  # Now do record-learning (second round-trip)
116
115
  flow_agents_node "$WRITER" record-learning "$SESSION_DIR" \
@@ -140,7 +139,7 @@ console.log('after record-learning: builder.verify.tests status=' + declared.sta
140
139
  echo ""
141
140
  echo "=== 2. workflow.* session (no --flow-id): round-trip unchanged ==="
142
141
 
143
- NOFLOW_AROOT="$TMP/noflow-aroot"
142
+ NOFLOW_AROOT="$TMP/noflow-project/.kontourai/flow-agents"
144
143
  NOFLOW_SLUG="history-noflow-test"
145
144
  NOFLOW_DIR="$NOFLOW_AROOT/$NOFLOW_SLUG"
146
145
  mkdir -p "$NOFLOW_AROOT"
@@ -167,6 +166,8 @@ flow_agents_node "$WRITER" record-critique "$NOFLOW_DIR" \
167
166
  --id "noflow-review" \
168
167
  --verdict pass \
169
168
  --summary "Review passed." \
169
+ --lane-json "{\"id\":\"code\",\"status\":\"pass\",\"summary\":\"Review passed.\",\"evidence_refs\":[{\"kind\":\"artifact\",\"file\":\"$NOFLOW_DIR/$NOFLOW_SLUG--deliver.md\",\"summary\":\"Reviewed the delivery artifact.\"}]}" \
170
+ --artifact-ref "$NOFLOW_DIR/$NOFLOW_SLUG--deliver.md" \
170
171
  --timestamp "2026-06-01T10:02:00Z" >/dev/null 2>&1
171
172
 
172
173
  # Assert only workflow.* claims survived (no builder.* contamination)
@@ -189,8 +190,8 @@ console.log('after record-critique: workflow.check.test + workflow.critique.revi
189
190
  echo ""
190
191
  echo "=== 3. evidenceClean/critiqueClean correct for builder.* bundle ==="
191
192
 
192
- # Create a fresh builder.build session at verify step for dogfood-pass test
193
- DOGFOOD_AROOT="$TMP/dogfood-aroot"
193
+ # Create a fresh noncanonical FlowDefinition-projected session at verify for dogfood-pass.
194
+ DOGFOOD_AROOT="$TMP/dogfood-project/.kontourai/flow-agents"
194
195
  DOGFOOD_SLUG="dogfood-clean-test"
195
196
  DOGFOOD_DIR="$DOGFOOD_AROOT/$DOGFOOD_SLUG"
196
197
  mkdir -p "$DOGFOOD_AROOT"
@@ -200,7 +201,6 @@ flow_agents_node "$WRITER" ensure-session \
200
201
  --task-slug "$DOGFOOD_SLUG" \
201
202
  --title "Dogfood clean test" \
202
203
  --summary "Test evidenceClean/critiqueClean on builder.build session." \
203
- --flow-id builder.build \
204
204
  --timestamp "2026-06-01T20:00:00Z" >/dev/null 2>&1
205
205
 
206
206
  flow_agents_node "$WRITER" init-plan "$DOGFOOD_DIR/$DOGFOOD_SLUG--deliver.md" \
@@ -224,6 +224,8 @@ flow_agents_node "$WRITER" record-critique "$DOGFOOD_DIR" \
224
224
  --id "ev-critique" \
225
225
  --verdict pass \
226
226
  --summary "Critique passed." \
227
+ --lane-json "{\"id\":\"code\",\"status\":\"pass\",\"summary\":\"Critique passed.\",\"evidence_refs\":[{\"kind\":\"artifact\",\"file\":\"$DOGFOOD_DIR/$DOGFOOD_SLUG--deliver.md\",\"summary\":\"Reviewed the delivery artifact.\"}]}" \
228
+ --artifact-ref "$DOGFOOD_DIR/$DOGFOOD_SLUG--deliver.md" \
227
229
  --timestamp "2026-06-01T20:02:00Z" >/dev/null 2>&1
228
230
 
229
231
  # dogfood-pass --verdict pass should succeed: evidenceClean=true (builder.verify.tests passes)
@@ -1156,16 +1156,14 @@ echo "=== Diff scope check ==="
1156
1156
  # Verify that ONLY the allowed files were modified.
1157
1157
  # Round 2 (fix/gate-lockdown) scope: config-protection.js, stop-goal-fit.js, evidence-capture.js.
1158
1158
  # The security-hardening files (config-protection.js, stop-goal-fit.js, workflow-sidecar.ts,
1159
- # flow-resolver.ts, evidence-capture.js) are legitimately modified across the rounds, so the
1160
- # only true invariants this scope-check enforces are the hard collision boundaries.
1159
+ # flow-resolver.ts, evidence-capture.js) are legitimately modified across the rounds. This
1160
+ # check protects the cross-kit collision boundary; it must not reject legitimate changes to
1161
+ # Builder skills merely because another issue originally treated them as out of scope.
1161
1162
  # Use grep patterns to avoid triggering the source path validator.
1162
1163
  FORBIDDEN_MODIFIED=""
1163
1164
  FORBIDDEN_PATTERNS=(
1164
1165
  "kits/knowledge/"
1165
1166
  )
1166
- # continue-work: the collision boundary skill file (not in scripts/hooks/).
1167
- # Use a conservative basename check to avoid a false src-path reference.
1168
- FORBIDDEN_PATTERNS+=("continue-work")
1169
1167
  for pat in "${FORBIDDEN_PATTERNS[@]}"; do
1170
1168
  if git -C "$ROOT" diff --name-only HEAD 2>/dev/null | grep -q "$pat"; then
1171
1169
  FORBIDDEN_MODIFIED="$FORBIDDEN_MODIFIED $pat"
@@ -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."