@windyroad/risk-scorer 0.17.4 → 0.18.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 (55) hide show
  1. package/.agents/plugins/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/README.md +13 -2
  5. package/bin/install.mjs +42 -8
  6. package/hooks/codex-agent-completion.mjs +82 -0
  7. package/hooks/hooks.json +5 -2
  8. package/hooks/risk-scorer-dispatch.sh +3 -0
  9. package/hooks/risk-scorer-scaffold-nudge.sh +15 -4
  10. package/package.json +5 -2
  11. package/scripts/codex-agents.mjs +208 -0
  12. package/scripts/sync-codex-skills.mjs +8 -3
  13. package/skills/assess-external-comms/SKILL.md +5 -4
  14. package/skills/assess-inbound-report/SKILL.md +5 -4
  15. package/skills/assess-release/SKILL.md +5 -4
  16. package/skills/assess-wip/SKILL.md +5 -4
  17. package/skills/bootstrap-catalog/SKILL.md +5 -4
  18. package/skills/create-risk/SKILL.md +5 -4
  19. package/skills/external-comms/SKILL.md +5 -4
  20. package/skills/pipeline/SKILL.md +5 -4
  21. package/skills/update-policy/SKILL.md +6 -5
  22. package/skills/wip/SKILL.md +5 -4
  23. package/agents/test/inbound-report-contract.bats +0 -225
  24. package/agents/test/risk-scorer-above-appetite-stop.bats +0 -80
  25. package/agents/test/risk-scorer-catalog-consumption.bats +0 -138
  26. package/agents/test/risk-scorer-commit-verdict.bats +0 -66
  27. package/agents/test/risk-scorer-monitoring-not-a-control.bats +0 -76
  28. package/agents/test/risk-scorer-reducing-bypass-criteria.bats +0 -62
  29. package/agents/test/risk-scorer-register-hint.bats +0 -111
  30. package/agents/test/risk-scorer-structured-remediations.bats +0 -123
  31. package/agents/test/risk-scorer-user-stated-preconditions.bats +0 -89
  32. package/hooks/test/ci-status-gate.bats +0 -234
  33. package/hooks/test/external-comms-gate.bats +0 -528
  34. package/hooks/test/external-comms-key-substance.bats +0 -74
  35. package/hooks/test/git-push-gate.bats +0 -82
  36. package/hooks/test/pipeline-state-changeset-partition.bats +0 -181
  37. package/hooks/test/pipeline-state-hash.bats +0 -195
  38. package/hooks/test/plan-risk-guidance-once-per-session.bats +0 -95
  39. package/hooks/test/reducing-marker-persistence.bats +0 -236
  40. package/hooks/test/risk-gate.bats +0 -330
  41. package/hooks/test/risk-policy-project-root.bats +0 -20
  42. package/hooks/test/risk-score-commit-gate-adr-031-bypass.bats +0 -98
  43. package/hooks/test/risk-score-commit-gate-cadence-staleness.bats +0 -105
  44. package/hooks/test/risk-score-mark-external-comms-prompt-parse.bats +0 -94
  45. package/hooks/test/risk-score-mark-register-queue.bats +0 -253
  46. package/hooks/test/risk-score-mark.bats +0 -112
  47. package/hooks/test/risk-scorer-hook-dispatch.bats +0 -77
  48. package/hooks/test/risk-scorer-no-stop-hook.bats +0 -19
  49. package/hooks/test/risk-scorer-scaffold-nudge.bats +0 -132
  50. package/hooks/test/slide-marker-on-subprocess-return.bats +0 -118
  51. package/hooks/test/wip-risk-mark.bats +0 -64
  52. package/scripts/test/drain-register-queue.bats +0 -359
  53. package/scripts/test/evaluate-graduation.bats +0 -533
  54. package/scripts/test/extract-risks-from-reports.bats +0 -366
  55. package/scripts/test/restage-commit.bats +0 -137
@@ -1,533 +0,0 @@
1
- #!/usr/bin/env bats
2
- # Behavioural-fixture coverage for packages/risk-scorer/scripts/evaluate-graduation.sh
3
- # per ADR-052 (behavioural tests default) and ADR-061 (dogfood graduation criteria).
4
- #
5
- # Phase 2a coverage — orthogonal-gate class (Class 3a). Maps to
6
- # ADR-061 Confirmation criterion 2 items a-f.
7
- #
8
- # Phase 2b coverage — atomic-cohort class (Class 3b — Rule 3b cohort enumeration).
9
- # Maps to ADR-061 Confirmation criterion 2 item g (full-cohort evaluation,
10
- # max(Priority) across cohort tickets, atomic VP-blocked + halt propagation).
11
- # Cohort detection reads docs/changesets-holding/README.md "Currently held"
12
- # section and groups entries by shared reinstate-trigger prose (parenthetical
13
- # elaborations stripped before grouping). Single-member "cohorts" fall back
14
- # to class=3a (no Phase 2a regression).
15
-
16
- setup() {
17
- REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
18
- SCRIPT="$REPO_ROOT/packages/risk-scorer/scripts/evaluate-graduation.sh"
19
- SHIM="$REPO_ROOT/packages/risk-scorer/bin/wr-risk-scorer-evaluate-graduation"
20
- WORK_DIR="$(mktemp -d)"
21
- cd "$WORK_DIR"
22
- # Minimal git setup — fixture follows drain-register-queue.bats precedent
23
- # so dual-layout problem-ticket lookup (flat + per-state subdir) exercises
24
- # the same shape the script handles in canonical adopter trees.
25
- git init --quiet
26
- git config user.email "graduation-test@example.com"
27
- git config user.name "Graduation Test"
28
- git commit --quiet --allow-empty -m "init"
29
- mkdir -p docs/changesets-holding docs/problems/open docs/problems/known-error \
30
- docs/problems/verifying docs/problems/closed docs/problems/parked
31
- }
32
-
33
- teardown() {
34
- cd /
35
- rm -rf "$WORK_DIR"
36
- }
37
-
38
- # ----- Helpers -----
39
-
40
- seed_problem() {
41
- # seed_problem <id-padded> <state> <priority> [<extra-body>]
42
- local id="$1" state="$2" priority="$3" extra="${4:-}"
43
- local path
44
- # Use per-state subdir layout (RFC-002 canonical post-migration shape)
45
- path="docs/problems/${state}/${id}-fixture-ticket.md"
46
- cat > "$path" <<EOF
47
- # Problem ${id}: Fixture ticket
48
-
49
- **Status**: ${state}
50
- **Reported**: 2026-05-01
51
- **Priority**: ${priority} (label) — Impact: 3 x Likelihood: $(( priority / 3 ))
52
-
53
- ## Description
54
-
55
- Fixture body for graduation-evaluator tests.
56
-
57
- ${extra}
58
- EOF
59
- }
60
-
61
- seed_problem_flat() {
62
- # seed_problem_flat <id-padded> <state> <priority>
63
- # Exercises the flat-layout half of the RFC-002 dual-tolerant glob.
64
- local id="$1" state="$2" priority="$3"
65
- cat > "docs/problems/${id}-fixture-flat.${state}.md" <<EOF
66
- # Problem ${id}: Fixture flat ticket
67
-
68
- **Status**: ${state}
69
- **Reported**: 2026-05-01
70
- **Priority**: ${priority} (label) — Impact: 3 x Likelihood: $(( priority / 3 ))
71
-
72
- ## Description
73
-
74
- Flat-layout fixture body for graduation-evaluator tests.
75
- EOF
76
- }
77
-
78
- seed_changeset() {
79
- # seed_changeset <filename> [<body>]
80
- local filename="$1" body="${2:-Default fixture changeset body.}"
81
- cat > "docs/changesets-holding/${filename}" <<EOF
82
- ---
83
- '@windyroad/itil': minor
84
- ---
85
-
86
- ${body}
87
- EOF
88
- }
89
-
90
- # ----- Smoke tests -----
91
-
92
- @test "shim wrapper exists and is executable" {
93
- [ -x "$SHIM" ]
94
- }
95
-
96
- @test "shim resolves canonical script (not exit 127)" {
97
- # Empty holding-area → exit 1 (no-op caller signal); but not exit 127.
98
- run "$SHIM" "$WORK_DIR"
99
- [ "$status" -ne 127 ]
100
- }
101
-
102
- @test "missing docs/ → exit 2 (invalid project root)" {
103
- cd "$BATS_TEST_TMPDIR"
104
- empty_dir=$(mktemp -d)
105
- run bash "$SCRIPT" "$empty_dir"
106
- [ "$status" -eq 2 ]
107
- }
108
-
109
- @test "missing holding dir → exit 1 (no-op signal)" {
110
- rm -rf docs/changesets-holding
111
- run bash "$SCRIPT" "$WORK_DIR"
112
- [ "$status" -eq 1 ]
113
- echo "$output" | grep -q 'GRADUATION_SUMMARY: total=0'
114
- }
115
-
116
- @test "empty holding dir → exit 1 (no-op signal)" {
117
- run bash "$SCRIPT" "$WORK_DIR"
118
- [ "$status" -eq 1 ]
119
- echo "$output" | grep -q 'GRADUATION_SUMMARY: total=0'
120
- }
121
-
122
- @test "README.md in holding-area is ignored" {
123
- echo "# Holding Area" > docs/changesets-holding/README.md
124
- run bash "$SCRIPT" "$WORK_DIR"
125
- # No other entries — exit 1 (empty after exclusion)
126
- [ "$status" -eq 1 ]
127
- echo "$output" | grep -q 'GRADUATION_SUMMARY: total=0'
128
- }
129
-
130
- # ----- ADR-061 Confirmation criterion 2 cases -----
131
-
132
- # Case (a) — filename-convention join resolves correctly
133
- @test "case (a): filename-convention join — wr-itil-p085-...md → P085 → Priority" {
134
- seed_problem "085" "open" "9"
135
- seed_changeset "wr-itil-p085-assistant-output-gate.md"
136
- run bash "$SCRIPT" "$WORK_DIR"
137
- [ "$status" -eq 0 ]
138
- echo "$output" | grep -q 'changeset=wr-itil-p085-assistant-output-gate.md'
139
- echo "$output" | grep -q 'ticket=P085'
140
- echo "$output" | grep -q 'priority=9'
141
- echo "$output" | grep -q 'status=resolved'
142
- echo "$output" | grep -q 'class=3a'
143
- echo "$output" | grep -q 'GRADUATION_SUMMARY: total=1 resolved=1 vp_blocked=0 halts=0'
144
- }
145
-
146
- # Case (b) — body-grep fallback resolves when filename lacks convention
147
- @test "case (b): body-grep fallback — non-conventional filename resolves via P<NNN> in body" {
148
- seed_problem "100" "known-error" "12"
149
- seed_changeset "feature-rollout-cohort.md" "Fixes P100. Related to feature work."
150
- run bash "$SCRIPT" "$WORK_DIR"
151
- [ "$status" -eq 0 ]
152
- echo "$output" | grep -q 'changeset=feature-rollout-cohort.md'
153
- echo "$output" | grep -q 'ticket=P100'
154
- echo "$output" | grep -q 'priority=12'
155
- echo "$output" | grep -q 'status=resolved'
156
- }
157
-
158
- # Case (c) — multi-ticket changeset uses max(Priority)
159
- @test "case (c): multi-ticket changeset uses max(Priority) across referenced set" {
160
- seed_problem "200" "open" "6"
161
- seed_problem "201" "open" "15"
162
- seed_problem "202" "open" "9"
163
- seed_changeset "multi-ticket-cohort.md" "References P200, P201, and P202 in body for max-priority test."
164
- run bash "$SCRIPT" "$WORK_DIR"
165
- [ "$status" -eq 0 ]
166
- # Max priority across P200(6) P201(15) P202(9) is 15 → ticket=P201
167
- echo "$output" | grep -q 'changeset=multi-ticket-cohort.md'
168
- echo "$output" | grep -q 'ticket=P201'
169
- echo "$output" | grep -q 'priority=15'
170
- echo "$output" | grep -q 'status=resolved'
171
- }
172
-
173
- # Case (d) — halt-and-prompt when no resolution path succeeds
174
- @test "case (d.1): halt-no-resolution when filename lacks convention AND body has no P<NNN>" {
175
- seed_changeset "no-ticket-reference.md" "Body has no problem-ticket references at all."
176
- run bash "$SCRIPT" "$WORK_DIR"
177
- [ "$status" -eq 0 ]
178
- echo "$output" | grep -q 'changeset=no-ticket-reference.md'
179
- echo "$output" | grep -q 'status=halt-no-resolution'
180
- echo "$output" | grep -q 'GRADUATION_SUMMARY: total=1 resolved=0 vp_blocked=0 halts=1'
181
- }
182
-
183
- @test "case (d.2): halt-no-resolution when filename references missing ticket" {
184
- # Filename references P999 but ticket file does not exist in fixture
185
- seed_changeset "wr-itil-p999-orphan.md"
186
- run bash "$SCRIPT" "$WORK_DIR"
187
- [ "$status" -eq 0 ]
188
- echo "$output" | grep -q 'status=halt-no-resolution'
189
- echo "$output" | grep -q 'halts=1'
190
- }
191
-
192
- # Case (e) — VP-blocked ticket emitted with vp-blocked marker
193
- @test "case (e): VP-blocked ticket emits status=vp-blocked (Rule 2 carve-out)" {
194
- seed_problem "300" "verifying" "10"
195
- seed_changeset "wr-itil-p300-mid-verification.md"
196
- run bash "$SCRIPT" "$WORK_DIR"
197
- [ "$status" -eq 0 ]
198
- echo "$output" | grep -q 'changeset=wr-itil-p300-mid-verification.md'
199
- echo "$output" | grep -q 'ticket=P300'
200
- echo "$output" | grep -q 'priority=10'
201
- echo "$output" | grep -q 'status=vp-blocked'
202
- echo "$output" | grep -q 'GRADUATION_SUMMARY: total=1 resolved=0 vp_blocked=1 halts=0'
203
- }
204
-
205
- # ----- P398: Rule 2 narrowed — shipped verifying graduates, unshipped holds -----
206
-
207
- # Case (h.1) — verifying ticket WITH populated `## Fix Released` graduates (code shipped)
208
- @test "case (h.1): shipped verifying (populated Fix Released) → resolved (P398)" {
209
- seed_problem "310" "verifying" "9" "## Fix Released
210
-
211
- Deployed in v0.42.0. Awaiting user verification."
212
- seed_changeset "wr-itil-p310-shipped.md"
213
- run bash "$SCRIPT" "$WORK_DIR"
214
- [ "$status" -eq 0 ]
215
- echo "$output" | grep -q 'changeset=wr-itil-p310-shipped.md'
216
- echo "$output" | grep -q 'ticket=P310'
217
- echo "$output" | grep -q 'status=resolved'
218
- echo "$output" | grep -q 'GRADUATION_SUMMARY: total=1 resolved=1 vp_blocked=0 halts=0'
219
- }
220
-
221
- # Case (h.2) — verifying ticket with placeholder-only `## Fix Released` stays held
222
- @test "case (h.2): unshipped verifying (placeholder Fix Released) → vp-blocked (P398)" {
223
- seed_problem "311" "verifying" "9" "## Fix Released
224
-
225
- (pending)"
226
- seed_changeset "wr-itil-p311-pending.md"
227
- run bash "$SCRIPT" "$WORK_DIR"
228
- [ "$status" -eq 0 ]
229
- echo "$output" | grep -q 'ticket=P311'
230
- echo "$output" | grep -q 'status=vp-blocked'
231
- }
232
-
233
- # Case (h.3) — verifying ticket with NO `## Fix Released` section stays held (case (e) invariant)
234
- @test "case (h.3): verifying with no Fix Released section → vp-blocked (P398 unshipped branch)" {
235
- seed_problem "312" "verifying" "9"
236
- seed_changeset "wr-itil-p312-no-section.md"
237
- run bash "$SCRIPT" "$WORK_DIR"
238
- [ "$status" -eq 0 ]
239
- echo "$output" | grep -q 'ticket=P312'
240
- echo "$output" | grep -q 'status=vp-blocked'
241
- }
242
-
243
- # Case (h.4) — an all-shipped-verifying cohort rolls up to resolved and graduates atomically
244
- @test "case (h.4): cohort of all-shipped-verifying members rolls up to resolved (P398)" {
245
- seed_problem "320" "verifying" "9" "## Fix Released
246
-
247
- Shipped in v0.50.0."
248
- seed_problem "321" "verifying" "12" "## Fix Released
249
-
250
- Shipped in v0.50.0."
251
- seed_changeset "wr-itil-p320-slice-a.md"
252
- seed_changeset "wr-itil-p321-slice-b.md"
253
- seed_holding_readme \
254
- "- \`wr-itil-p320-slice-a.md\` — patch. **Reinstate trigger**: shipped cohort graduates." \
255
- "- \`wr-itil-p321-slice-b.md\` — patch. **Reinstate trigger**: shipped cohort graduates."
256
- run bash "$SCRIPT" "$WORK_DIR"
257
- [ "$status" -eq 0 ]
258
- # Both members class=3b, both resolved (shipped), cohort priority = max(9,12) = 12
259
- echo "$output" | grep 'changeset=wr-itil-p320-slice-a.md' | grep -q 'class=3b'
260
- echo "$output" | grep 'changeset=wr-itil-p320-slice-a.md' | grep -q 'status=resolved'
261
- echo "$output" | grep 'changeset=wr-itil-p321-slice-b.md' | grep -q 'status=resolved'
262
- echo "$output" | grep 'changeset=wr-itil-p320-slice-a.md' | grep -q 'priority=12'
263
- }
264
-
265
- # Case (f) — status-agnostic Priority lookup (open, known-error, closed all resolve)
266
- @test "case (f.1): open ticket resolves" {
267
- seed_problem "400" "open" "8"
268
- seed_changeset "wr-itil-p400-open.md"
269
- run bash "$SCRIPT" "$WORK_DIR"
270
- [ "$status" -eq 0 ]
271
- echo "$output" | grep -q 'ticket=P400'
272
- echo "$output" | grep -q 'status=resolved'
273
- }
274
-
275
- @test "case (f.2): known-error ticket resolves" {
276
- seed_problem "401" "known-error" "8"
277
- seed_changeset "wr-itil-p401-ke.md"
278
- run bash "$SCRIPT" "$WORK_DIR"
279
- [ "$status" -eq 0 ]
280
- echo "$output" | grep -q 'ticket=P401'
281
- echo "$output" | grep -q 'status=resolved'
282
- }
283
-
284
- @test "case (f.3): closed ticket resolves (Priority still readable)" {
285
- seed_problem "402" "closed" "8"
286
- seed_changeset "wr-itil-p402-closed.md"
287
- run bash "$SCRIPT" "$WORK_DIR"
288
- [ "$status" -eq 0 ]
289
- echo "$output" | grep -q 'ticket=P402'
290
- echo "$output" | grep -q 'status=resolved'
291
- }
292
-
293
- @test "case (f.4): parked ticket resolves" {
294
- seed_problem "403" "parked" "8"
295
- seed_changeset "wr-itil-p403-parked.md"
296
- run bash "$SCRIPT" "$WORK_DIR"
297
- [ "$status" -eq 0 ]
298
- echo "$output" | grep -q 'ticket=P403'
299
- echo "$output" | grep -q 'status=resolved'
300
- }
301
-
302
- # Dual-tolerant layout coverage (ADR-031 / RFC-002 migration window)
303
- @test "flat-layout ticket resolves (RFC-002 pre-migration shape)" {
304
- seed_problem_flat "500" "open" "12"
305
- seed_changeset "wr-itil-p500-flat-layout.md"
306
- run bash "$SCRIPT" "$WORK_DIR"
307
- [ "$status" -eq 0 ]
308
- echo "$output" | grep -q 'ticket=P500'
309
- echo "$output" | grep -q 'priority=12'
310
- echo "$output" | grep -q 'status=resolved'
311
- }
312
-
313
- # Mixed-state holding-area — multiple changesets in one run
314
- @test "mixed-state holding-area — resolved + vp-blocked + halt in single run" {
315
- seed_problem "600" "open" "9"
316
- seed_problem "601" "verifying" "12"
317
- seed_problem "602" "open" "6"
318
- seed_changeset "wr-itil-p600-resolved.md"
319
- seed_changeset "wr-itil-p601-vp.md"
320
- seed_changeset "no-resolution-ref.md" "Generic body, no P references."
321
- run bash "$SCRIPT" "$WORK_DIR"
322
- [ "$status" -eq 0 ]
323
- # Counts: 3 total, 1 resolved (P600), 1 vp-blocked (P601), 1 halt
324
- echo "$output" | grep -q 'GRADUATION_SUMMARY: total=3 resolved=1 vp_blocked=1 halts=1'
325
- }
326
-
327
- # Filename-convention takes precedence over body-grep when both present
328
- @test "filename takes precedence over body — wr-itil-p700-fix.md with P800 in body resolves to P700" {
329
- seed_problem "700" "open" "5"
330
- seed_problem "800" "open" "20"
331
- seed_changeset "wr-itil-p700-fix.md" "Also references P800 in body (should be ignored — filename wins)."
332
- run bash "$SCRIPT" "$WORK_DIR"
333
- [ "$status" -eq 0 ]
334
- echo "$output" | grep -q 'ticket=P700'
335
- echo "$output" | grep -q 'priority=5'
336
- # Confirm body-referenced P800 was NOT picked up
337
- ! echo "$output" | grep -q 'ticket=P800'
338
- }
339
-
340
- # ----- Phase 2b: ADR-061 Confirmation criterion 2 item (g) — atomic-cohort -----
341
-
342
- # Helper: seed a Currently held entry into docs/changesets-holding/README.md.
343
- # Cohort detection reads this file and groups entries by shared reinstate-trigger
344
- # prose (parenthetical elaborations stripped) — see evaluate-graduation.sh.
345
- seed_holding_readme() {
346
- # seed_holding_readme <bullet-line> [<bullet-line>...]
347
- local readme="docs/changesets-holding/README.md"
348
- if [ ! -f "$readme" ]; then
349
- cat > "$readme" <<'EOF'
350
- # Changesets Holding Area
351
-
352
- ## Currently held
353
-
354
- EOF
355
- fi
356
- for bullet in "$@"; do
357
- printf '%s\n' "$bullet" >> "$readme"
358
- done
359
- }
360
-
361
- # Case (g.1) — two members sharing identical reinstate-trigger prose form a cohort
362
- @test "case (g.1): two members sharing reinstate-trigger form Class 3b cohort" {
363
- seed_problem "170" "open" "9"
364
- seed_problem "171" "open" "12"
365
- seed_changeset "wr-itil-p170-phase4.md"
366
- seed_changeset "wr-itil-p171-phase3.md"
367
- seed_holding_readme \
368
- "- \`wr-itil-p170-phase4.md\` — patch. **Reinstate trigger**: Phase 3 + Phase 4 end-of-chain user verification fires." \
369
- "- \`wr-itil-p171-phase3.md\` — minor. **Reinstate trigger**: Phase 3 + Phase 4 end-of-chain user verification fires."
370
- run bash "$SCRIPT" "$WORK_DIR"
371
- [ "$status" -eq 0 ]
372
- # Both members emit class=3b
373
- echo "$output" | grep 'changeset=wr-itil-p170-phase4.md' | grep -q 'class=3b'
374
- echo "$output" | grep 'changeset=wr-itil-p171-phase3.md' | grep -q 'class=3b'
375
- # Both members share the same cohort= column
376
- cohort_p170=$(echo "$output" | grep 'changeset=wr-itil-p170-phase4.md' | sed -n 's/.*cohort=\([^ |]*\).*/\1/p')
377
- cohort_p171=$(echo "$output" | grep 'changeset=wr-itil-p171-phase3.md' | sed -n 's/.*cohort=\([^ |]*\).*/\1/p')
378
- [ -n "$cohort_p170" ]
379
- [ "$cohort_p170" = "$cohort_p171" ]
380
- }
381
-
382
- # Case (g.2) — cohort uses max(Priority) across all member tickets per ADR-061 Rule 3b
383
- @test "case (g.2): cohort priority is max across member tickets" {
384
- seed_problem "172" "open" "6"
385
- seed_problem "173" "open" "15"
386
- seed_problem "174" "open" "9"
387
- seed_changeset "wr-itil-p172-slice-a.md"
388
- seed_changeset "wr-itil-p173-slice-b.md"
389
- seed_changeset "wr-itil-p174-slice-c.md"
390
- seed_holding_readme \
391
- "- \`wr-itil-p172-slice-a.md\` — patch. **Reinstate trigger**: RFC-009 end-of-chain verification." \
392
- "- \`wr-itil-p173-slice-b.md\` — patch. **Reinstate trigger**: RFC-009 end-of-chain verification." \
393
- "- \`wr-itil-p174-slice-c.md\` — patch. **Reinstate trigger**: RFC-009 end-of-chain verification."
394
- run bash "$SCRIPT" "$WORK_DIR"
395
- [ "$status" -eq 0 ]
396
- # Every cohort member carries priority=15 (max across P172/P173/P174)
397
- echo "$output" | grep 'changeset=wr-itil-p172-slice-a.md' | grep -q 'priority=15'
398
- echo "$output" | grep 'changeset=wr-itil-p173-slice-b.md' | grep -q 'priority=15'
399
- echo "$output" | grep 'changeset=wr-itil-p174-slice-c.md' | grep -q 'priority=15'
400
- }
401
-
402
- # Case (g.3) — one VP-blocked cohort member marks the entire cohort vp-blocked
403
- @test "case (g.3): VP-blocked member blocks entire cohort (Rule 2 carve-out symmetric)" {
404
- seed_problem "175" "open" "9"
405
- seed_problem "176" "verifying" "12"
406
- seed_changeset "wr-itil-p175-slice-a.md"
407
- seed_changeset "wr-itil-p176-slice-b.md"
408
- seed_holding_readme \
409
- "- \`wr-itil-p175-slice-a.md\` — minor. **Reinstate trigger**: cohort verification fires." \
410
- "- \`wr-itil-p176-slice-b.md\` — minor. **Reinstate trigger**: cohort verification fires."
411
- run bash "$SCRIPT" "$WORK_DIR"
412
- [ "$status" -eq 0 ]
413
- # Both members report status=vp-blocked even though only P176 is in verifying state
414
- echo "$output" | grep 'changeset=wr-itil-p175-slice-a.md' | grep -q 'status=vp-blocked'
415
- echo "$output" | grep 'changeset=wr-itil-p176-slice-b.md' | grep -q 'status=vp-blocked'
416
- }
417
-
418
- # Case (g.4) — one halt-no-resolution member propagates to entire cohort (architect C1)
419
- @test "case (g.4): halt-no-resolution member propagates to entire cohort" {
420
- seed_problem "177" "open" "9"
421
- # P178 deliberately NOT seeded → halt-no-resolution
422
- seed_changeset "wr-itil-p177-slice-a.md"
423
- seed_changeset "wr-itil-p178-slice-b.md"
424
- seed_holding_readme \
425
- "- \`wr-itil-p177-slice-a.md\` — patch. **Reinstate trigger**: shared cohort fires." \
426
- "- \`wr-itil-p178-slice-b.md\` — patch. **Reinstate trigger**: shared cohort fires."
427
- run bash "$SCRIPT" "$WORK_DIR"
428
- [ "$status" -eq 0 ]
429
- # Both members report status=halt-no-resolution — cohort cannot graduate partially
430
- echo "$output" | grep 'changeset=wr-itil-p177-slice-a.md' | grep -q 'status=halt-no-resolution'
431
- echo "$output" | grep 'changeset=wr-itil-p178-slice-b.md' | grep -q 'status=halt-no-resolution'
432
- }
433
-
434
- # Case (g.5) — single-member "cohort" falls back to Class 3a (no Phase 2a regression)
435
- @test "case (g.5): single-member 'cohort' falls back to class=3a" {
436
- seed_problem "179" "open" "9"
437
- seed_changeset "wr-itil-p179-solo.md"
438
- seed_holding_readme \
439
- "- \`wr-itil-p179-solo.md\` — patch. **Reinstate trigger**: nobody else shares this trigger."
440
- run bash "$SCRIPT" "$WORK_DIR"
441
- [ "$status" -eq 0 ]
442
- echo "$output" | grep 'changeset=wr-itil-p179-solo.md' | grep -q 'class=3a'
443
- ! echo "$output" | grep 'changeset=wr-itil-p179-solo.md' | grep -q 'cohort='
444
- }
445
-
446
- # Case (g.6) — parenthetical elaborations stripped before grouping
447
- @test "case (g.6): parenthetical elaborations stripped before cohort grouping" {
448
- seed_problem "180" "open" "9"
449
- seed_problem "181" "open" "9"
450
- seed_changeset "wr-itil-p180-a.md"
451
- seed_changeset "wr-itil-p181-b.md"
452
- # P180 trigger has no parens; P181 trigger has parenthetical elaboration —
453
- # cohort detection must strip the paren content before comparison.
454
- seed_holding_readme \
455
- "- \`wr-itil-p180-a.md\` — patch. **Reinstate trigger**: end-of-chain fires." \
456
- "- \`wr-itil-p181-b.md\` — patch. **Reinstate trigger**: end-of-chain fires (only the slice 3 dependency remains, can defer per Reassessment criterion k)."
457
- run bash "$SCRIPT" "$WORK_DIR"
458
- [ "$status" -eq 0 ]
459
- # Despite different surface prose, the normalised trigger matches → both class=3b
460
- echo "$output" | grep 'changeset=wr-itil-p180-a.md' | grep -q 'class=3b'
461
- echo "$output" | grep 'changeset=wr-itil-p181-b.md' | grep -q 'class=3b'
462
- }
463
-
464
- # Case (g.7) — README without "Currently held" section → all entries fall back to class=3a
465
- @test "case (g.7): README without 'Currently held' section falls back to class=3a (defensive)" {
466
- seed_problem "182" "open" "9"
467
- seed_problem "183" "open" "12"
468
- seed_changeset "wr-itil-p182-a.md"
469
- seed_changeset "wr-itil-p183-b.md"
470
- # README exists but has no "Currently held" section — cohort detection finds nothing.
471
- cat > "docs/changesets-holding/README.md" <<'EOF'
472
- # Holding Area
473
- Some unrelated content.
474
- EOF
475
- run bash "$SCRIPT" "$WORK_DIR"
476
- [ "$status" -eq 0 ]
477
- echo "$output" | grep 'changeset=wr-itil-p182-a.md' | grep -q 'class=3a'
478
- echo "$output" | grep 'changeset=wr-itil-p183-b.md' | grep -q 'class=3a'
479
- }
480
-
481
- # Case (g.8) — README absent entirely → all entries fall back to class=3a (defensive)
482
- @test "case (g.8): missing README falls back to class=3a" {
483
- seed_problem "184" "open" "9"
484
- seed_changeset "wr-itil-p184-a.md"
485
- # Do NOT create README at all
486
- run bash "$SCRIPT" "$WORK_DIR"
487
- [ "$status" -eq 0 ]
488
- echo "$output" | grep 'changeset=wr-itil-p184-a.md' | grep -q 'class=3a'
489
- }
490
-
491
- # Case (g.9) — multiple distinct cohorts in the same holding-area resolve independently
492
- @test "case (g.9): multiple distinct cohorts coexist with distinct cohort= ids" {
493
- seed_problem "185" "open" "9"
494
- seed_problem "186" "open" "10"
495
- seed_problem "187" "open" "12"
496
- seed_problem "188" "open" "8"
497
- seed_changeset "wr-itil-p185-cohort-a.md"
498
- seed_changeset "wr-itil-p186-cohort-a.md"
499
- seed_changeset "wr-itil-p187-cohort-b.md"
500
- seed_changeset "wr-itil-p188-cohort-b.md"
501
- seed_holding_readme \
502
- "- \`wr-itil-p185-cohort-a.md\` — minor. **Reinstate trigger**: cohort alpha fires." \
503
- "- \`wr-itil-p186-cohort-a.md\` — minor. **Reinstate trigger**: cohort alpha fires." \
504
- "- \`wr-itil-p187-cohort-b.md\` — minor. **Reinstate trigger**: cohort beta fires." \
505
- "- \`wr-itil-p188-cohort-b.md\` — minor. **Reinstate trigger**: cohort beta fires."
506
- run bash "$SCRIPT" "$WORK_DIR"
507
- [ "$status" -eq 0 ]
508
- cohort_a1=$(echo "$output" | grep 'changeset=wr-itil-p185-cohort-a.md' | sed -n 's/.*cohort=\([^ |]*\).*/\1/p')
509
- cohort_a2=$(echo "$output" | grep 'changeset=wr-itil-p186-cohort-a.md' | sed -n 's/.*cohort=\([^ |]*\).*/\1/p')
510
- cohort_b1=$(echo "$output" | grep 'changeset=wr-itil-p187-cohort-b.md' | sed -n 's/.*cohort=\([^ |]*\).*/\1/p')
511
- cohort_b2=$(echo "$output" | grep 'changeset=wr-itil-p188-cohort-b.md' | sed -n 's/.*cohort=\([^ |]*\).*/\1/p')
512
- [ -n "$cohort_a1" ] && [ "$cohort_a1" = "$cohort_a2" ]
513
- [ -n "$cohort_b1" ] && [ "$cohort_b1" = "$cohort_b2" ]
514
- [ "$cohort_a1" != "$cohort_b1" ]
515
- # Cohort A priority is max(9,10) = 10; Cohort B priority is max(12,8) = 12
516
- echo "$output" | grep 'changeset=wr-itil-p185-cohort-a.md' | grep -q 'priority=10'
517
- echo "$output" | grep 'changeset=wr-itil-p187-cohort-b.md' | grep -q 'priority=12'
518
- }
519
-
520
- # Case (g.10) — cohort detection does NOT regress Phase 2a summary counts
521
- @test "case (g.10): cohort members still count individually in GRADUATION_SUMMARY" {
522
- seed_problem "190" "open" "9"
523
- seed_problem "191" "open" "9"
524
- seed_changeset "wr-itil-p190-cohort.md"
525
- seed_changeset "wr-itil-p191-cohort.md"
526
- seed_holding_readme \
527
- "- \`wr-itil-p190-cohort.md\` — patch. **Reinstate trigger**: shared cohort." \
528
- "- \`wr-itil-p191-cohort.md\` — patch. **Reinstate trigger**: shared cohort."
529
- run bash "$SCRIPT" "$WORK_DIR"
530
- [ "$status" -eq 0 ]
531
- # Phase 2a parsers see total=2 resolved=2 — backwards compatible
532
- echo "$output" | grep -q 'GRADUATION_SUMMARY: total=2 resolved=2 vp_blocked=0 halts=0'
533
- }