@ccoalm/ccl-skills 0.1.0 → 0.2.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 (38) hide show
  1. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_init_policy_matrix.sh +93 -16
  2. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_parse_probe_result.sh +10 -0
  3. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate.sh +249 -5
  4. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate_abort_leak.sh +394 -0
  5. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/architecture-playbook.md +2 -0
  6. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/cross-cutting-concerns.md +6 -0
  7. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/SKILL.md +3 -1
  8. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-capability-composition.md +128 -0
  9. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-command-sandbox.md +35 -0
  10. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-session-persistence.md +54 -2
  11. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-tool-dispatch.md +11 -0
  12. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/model-prompt-evaluation.md +7 -0
  13. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/canary-and-rollout-strategy.md +6 -0
  14. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/external-ui-ux-quality-benchmarks.md +50 -1
  15. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/ui-ux-audit.md +1 -0
  16. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/architecture-playbook.md +1 -0
  17. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/packaging-runtime-readiness.md +6 -0
  18. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/SKILL.md +1 -1
  19. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/external-practice-controls.md +59 -1
  20. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/harness-patterns-and-eval.md +1 -0
  21. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/rule-consolidation.md +3 -1
  22. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-register.md +43 -0
  23. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-to-skill-extraction.md +16 -4
  24. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/impact-chain-gate.rb +391 -14
  25. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/register-firing-path-resolution.rb +74 -0
  26. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_regressions.sh +74 -33
  27. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_skill_catalog.sh +11 -4
  28. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_gate_verdict_differential.sh +421 -0
  29. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_round_attribution.sh +576 -0
  30. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_source_refuted.sh +176 -0
  31. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_regression_runner_lanes.sh +101 -0
  32. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_validate_skill_root_depth.sh +6 -2
  33. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/SKILL.md +2 -2
  34. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/ci-fixtures-and-flake-control.md +19 -2
  35. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/e2e-real-flow-testing.md +1 -0
  36. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/test-code-authoring-patterns.md +3 -3
  37. package/dist/assets/release.json +63 -33
  38. package/package.json +1 -1
@@ -0,0 +1,576 @@
1
+ #!/usr/bin/env bash
2
+ # RED-baseline probes for the impact-chain gate's ROW-OWNERSHIP ATTRIBUTION.
3
+ #
4
+ # Three forms were recorded separately across the 034 batches and registered as
5
+ # one deferred hardening item. Reproduction against the current baseline split
6
+ # them into two independent roots, and only one is fixed here.
7
+ #
8
+ # Leg 0 CONTROL one owner change, one well-formed row: must be green.
9
+ # Leg A form 1 an owner restored to base bytes leaves the changed set,
10
+ # and its surviving row kept vouching for a change the
11
+ # delivered diff no longer contains.
12
+ # Leg B form 2 a row citing a path inside the owner package but never
13
+ # its SKILL.md resolved to nothing, so the declaration it
14
+ # makes was never evaluated.
15
+ # Leg H REGRESSION the register carries other five-column tables; widening
16
+ # resolution must not drag them into this gate.
17
+ #
18
+ # FORM 3 IS NOT FIXED HERE, BY DECISION. A stacked branch that integrates its
19
+ # upstream branch by merge is still refused a row it cannot supply; the standing
20
+ # workaround is to integrate by rebase, which is already the documented practice.
21
+ # The suppression that would have fixed it was implemented and then removed: it
22
+ # necessarily proxies "this round authored nothing of its own" with a textual
23
+ # comparison, and three adversarial review rounds found eight ways through that
24
+ # proxy (deletion, relocation, duplication, partial import, dropped import,
25
+ # rejected removal, duplicate-line cancellation, and binary/mode invisibility).
26
+ # Same class three rounds running is the repo's own signal to question the
27
+ # capability rather than patch it again, and the risk is asymmetric: form 3 costs
28
+ # an author one rebase, while a leaky suppression silently accepts undeclared
29
+ # owner changes — the very failure the two forms above are about. The risk owner
30
+ # chose deletion. Reinstating it needs an invariant, not another proxy.
31
+ #
32
+ # Each leg asserts the DESIRED behavior, and every leg expecting a refusal also
33
+ # asserts WHICH refusal. rc alone is a weak oracle: a leg red for a fixture
34
+ # defect looks identical to the form reproducing, and both directions of that
35
+ # mistake were observed while writing this file. Do not "fix" a leg by relaxing
36
+ # its assertion.
37
+ set -euo pipefail
38
+
39
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
40
+ # REVERSE DIFFERENTIAL. Each leg below states which gate behavior it pins, and a
41
+ # leg only means something if the gate it replaces fails it. Point this at the
42
+ # older gate to check that:
43
+ #
44
+ # git show <ref>:skills/skill-extraction-workflow/scripts/impact-chain-gate.rb > /tmp/old.rb
45
+ # IMPACT_CHAIN_GATE=/tmp/old.rb bash "$0"
46
+ #
47
+ # The legs marked as differential must go RED there. Without the override this
48
+ # runs the working-tree gate, which is the ordinary regression direction; the
49
+ # override is what makes the reverse direction reproducible instead of a claim
50
+ # about a run nobody else can repeat.
51
+ GATE="${IMPACT_CHAIN_GATE:-$SCRIPT_DIR/impact-chain-gate.rb}"
52
+ [ -f "$GATE" ] || { echo "FAIL: gate not found: $GATE" >&2; exit 1; }
53
+
54
+ TMP="$(mktemp -d "${TMPDIR:-/tmp}/icattr.XXXXXX")"
55
+ trap 'rc=$?; rm -rf "$TMP"; exit $rc' EXIT
56
+
57
+ fail() { echo "FAIL: $*" >&2; exit 1; }
58
+ note() { echo " $*"; }
59
+
60
+ LEDGER_REL="skills/skill-extraction-workflow/references/source-register.md"
61
+
62
+ # One curated upstream owner package plus the ledger header. The owner body
63
+ # carries a numbered normative rule line so a firing-path anchor can resolve to
64
+ # a genuinely changed rule rather than to prose.
65
+ seed_repo() { # <repo-dir>
66
+ local repo="$1"
67
+ mkdir -p "$repo"
68
+ git -C "$repo" init -q -b trunk
69
+ git -C "$repo" config user.email test@example.invalid
70
+ git -C "$repo" config user.name "Test User"
71
+ git -C "$repo" config commit.gpgsign false
72
+ mkdir -p "$repo/skills/product-rd-workflow" \
73
+ "$repo/skills/testing-strategy" \
74
+ "$repo/skills/skill-extraction-workflow/references"
75
+ cat > "$repo/skills/product-rd-workflow/SKILL.md" <<'MD'
76
+ ---
77
+ name: product-rd-workflow
78
+ description: Fixture routing description for the attribution differential.
79
+ ---
80
+
81
+ # Fixture Owner
82
+
83
+ ## Rules
84
+
85
+ - Baseline rule line that the case commits mutate.
86
+ MD
87
+ cat > "$repo/skills/testing-strategy/SKILL.md" <<'MD'
88
+ ---
89
+ name: testing-strategy
90
+ description: Second fixture owner, used by the upstream-branch round.
91
+ ---
92
+
93
+ # Second Fixture Owner
94
+
95
+ ## Rules
96
+
97
+ - Baseline rule line owned by the upstream integration branch.
98
+ MD
99
+ printf '| Lesson | Downstream owner | Behavior | Status | Evidence |\n| --- | --- | --- | --- | --- |\n' \
100
+ > "$repo/$LEDGER_REL"
101
+ git -C "$repo" add -A
102
+ git -C "$repo" commit -qm "seed fixture base"
103
+ }
104
+
105
+ # The owner mutation and the row's firing path are a matched pair. The gate
106
+ # resolves a file locator by finding the anchor as a substring of an ADDED line
107
+ # in the row's own round diff, requiring that line to be a list item carrying
108
+ # normative-action vocabulary. So the rule line must be a real `- ... must ...`
109
+ # rule and the anchor must be a token unique to it.
110
+ rule_anchor() { printf 'ATTRIBUTION-FIXTURE-%s' "$1"; }
111
+
112
+ mutate_owner_rule() { # <repo-dir> <owner-slug> <marker>
113
+ local repo="$1" owner="$2" marker="$3"
114
+ printf -- '- Rule %s must be recorded before the round lands.\n' "$(rule_anchor "$marker")" \
115
+ >> "$repo/skills/$owner/SKILL.md"
116
+ }
117
+
118
+ # A complete, well-formed row: valid status, full behavioral-evidence
119
+ # declaration, owner-scoped firing path that resolves to the rule line the
120
+ # matching mutate_owner_rule added, and the owner key in the evidence cell.
121
+ append_valid_row() { # <repo-dir> <owner-slug> <marker> <lesson>
122
+ local repo="$1" owner="$2" marker="$3" lesson="$4"
123
+ printf '| %s | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; firing-path: file:skills/%s/SKILL.md#%s | `updated` | `%s/SKILL.md` fixture change |\n' \
124
+ "$lesson" "$owner" "$(rule_anchor "$marker")" "$owner" >> "$repo/$LEDGER_REL"
125
+ }
126
+
127
+ run_gate() { # <repo-dir> <base-ref> -> sets rc, out
128
+ set +e
129
+ out="$(env -u ALIAS_AUDIT_CMD CCL_SKILL_BASE_REF="$2" ruby "$GATE" "$1" 2>&1)"
130
+ rc=$?
131
+ set -e
132
+ }
133
+
134
+ legs_failed=0
135
+ # <leg-name> <expected-rc> <form-summary> [expected-token]
136
+ # The token is the attribution guard. rc alone is a weak oracle here: a refusal
137
+ # for an unrelated reason (a fixture defect, a broken anchor) is still rc=1 and
138
+ # would read as the form being closed. When a leg expects a refusal it must
139
+ # also name WHICH refusal, so a green leg means the intended predicate fired.
140
+ report_leg() {
141
+ if [ "$rc" != "$2" ]; then
142
+ echo " $1: RED (rc=$rc, want $2) — $3 REPRODUCES"
143
+ note "gate output:"
144
+ printf '%s\n' "$out" | sed 's/^/ | /'
145
+ legs_failed=$((legs_failed + 1))
146
+ return
147
+ fi
148
+ if [ -n "${4:-}" ]; then
149
+ case "$out" in
150
+ *"$4"*) : ;;
151
+ *)
152
+ echo " $1: RED (rc=$rc as wanted, but the wrong refusal) — expected $4"
153
+ note "gate output:"
154
+ printf '%s\n' "$out" | sed 's/^/ | /'
155
+ legs_failed=$((legs_failed + 1))
156
+ return
157
+ ;;
158
+ esac
159
+ fi
160
+ echo " $1: PASS (rc=$rc) — $3 does not reproduce on this baseline"
161
+ }
162
+
163
+ echo "test_impact_chain_round_attribution: row-ownership attribution"
164
+
165
+ # ---------------------------------------------------------------------------
166
+ # Leg 0 — CONTROL. The plain shape every leg below is a variation of: one owner
167
+ # change, one well-formed row declaring it. This must be GREEN. Without this
168
+ # control a fixture defect (an anchor that does not resolve, a malformed row)
169
+ # reads as the form reproducing, and a leg that is red for the wrong reason
170
+ # reads as the form being closed. Both directions were observed while writing
171
+ # this file, so the control is load-bearing, not ceremony.
172
+ # ---------------------------------------------------------------------------
173
+ Z="$TMP/leg-0"
174
+ seed_repo "$Z"
175
+ git -C "$Z" branch fixture-base HEAD
176
+ git -C "$Z" switch -q -c case-control
177
+ mutate_owner_rule "$Z" product-rd-workflow Z
178
+ append_valid_row "$Z" product-rd-workflow Z "Fixture control row"
179
+ git -C "$Z" add -A
180
+ git -C "$Z" commit -qm "round 1: change the owner and declare it"
181
+ run_gate "$Z" fixture-base
182
+ if [ "$rc" != 0 ]; then
183
+ echo " leg 0 (control): BROKEN FIXTURE (rc=$rc, want 0)"
184
+ printf '%s\n' "$out" | sed 's/^/ | /'
185
+ fail "the control shape is not green, so no other leg's verdict means anything"
186
+ fi
187
+ echo " leg 0 (control): PASS (rc=0) — the fixture shape itself is green"
188
+
189
+
190
+ # ---------------------------------------------------------------------------
191
+ # Leg A — form 1: owner restored to base, row stays green (FALSE-GREEN)
192
+ # ---------------------------------------------------------------------------
193
+ A="$TMP/leg-a"
194
+ seed_repo "$A"
195
+ git -C "$A" branch fixture-base HEAD
196
+ git -C "$A" switch -q -c case-restore
197
+ mutate_owner_rule "$A" product-rd-workflow "A"
198
+ append_valid_row "$A" product-rd-workflow A "Fixture restored-owner row"
199
+ git -C "$A" add -A
200
+ git -C "$A" commit -qm "round 1: change the owner and declare it"
201
+ # The restore: exactly what a rebase/conflict resolution that takes the base
202
+ # side does. The ledger row is untouched and still reads as a live declaration.
203
+ git -C "$A" checkout -q fixture-base -- skills/product-rd-workflow/SKILL.md
204
+ git -C "$A" commit -qm "owner restored to base bytes (rebase/conflict restore)" -q
205
+ # Guard: the delivered diff really contains no owner change any more.
206
+ if git -C "$A" diff --name-only fixture-base HEAD | grep -q '^skills/product-rd-workflow/'; then
207
+ fail "leg A fixture is a no-op: the owner is still changed at HEAD"
208
+ fi
209
+ git -C "$A" diff --name-only fixture-base HEAD | grep -qx "$LEDGER_REL" \
210
+ || fail "leg A fixture is a no-op: the ledger row did not survive"
211
+ run_gate "$A" fixture-base
212
+ report_leg "leg A (owner restored to base)" 1 "form 1" "impact_chain_row_vouches_for_unchanged_owner"
213
+
214
+ # ---------------------------------------------------------------------------
215
+ # Leg B — form 2: row without a recognized owner key is never evaluated
216
+ # ---------------------------------------------------------------------------
217
+ B="$TMP/leg-b"
218
+ seed_repo "$B"
219
+ git -C "$B" branch fixture-base HEAD
220
+ git -C "$B" switch -q -c case-unkeyed
221
+ mutate_owner_rule "$B" product-rd-workflow "B"
222
+ # Row 1 binds the owner and satisfies the per-round demand.
223
+ append_valid_row "$B" product-rd-workflow B "Fixture binding row"
224
+ # Row 2 is deliberately INVALID where the gate checks bound rows: the
225
+ # behavioral-evidence declaration is truncated (no observed-failure, no
226
+ # firing-path) and the evidence cell names only a references/ path, never the
227
+ # owning SKILL.md key. If the gate evaluates every added row, this must be
228
+ # rejected. If it only ever evaluates rows it managed to bind, this row is
229
+ # silently inert.
230
+ printf '| Fixture unkeyed row | `downstream-executor` | behavioral-evidence: RED-baseline | `updated` | skills/product-rd-workflow/references/nonexistent-note.md supporting note only |\n' \
231
+ >> "$B/$LEDGER_REL"
232
+ git -C "$B" add -A
233
+ git -C "$B" commit -qm "round 1: one binding row plus one unkeyed incomplete row"
234
+ run_gate "$B" fixture-base
235
+ report_leg "leg B (unkeyed row never evaluated)" 1 "form 2" "impact_chain_firing_path_missing"
236
+
237
+ # ---------------------------------------------------------------------------
238
+ # Leg H — UNRELATED TABLE regression. The register carries other five-column
239
+ # tables whose rows have a status word and can cite a path inside an owner
240
+ # package. Before prefix resolution they never matched an exact
241
+ # `<owner>/SKILL.md` and were skipped; that prior behavior must survive, or the
242
+ # widening silently drags unrelated bookkeeping into this gate. The row here
243
+ # cites a curated owner's reference path and carries NO behavioral-evidence
244
+ # declaration, which is the discriminator that keeps it out.
245
+ # ---------------------------------------------------------------------------
246
+ H="$TMP/leg-h"
247
+ seed_repo "$H"
248
+ git -C "$H" branch fixture-base HEAD
249
+ git -C "$H" switch -q -c case-unrelated
250
+ printf '| Note | Owner | Rationale | Status | Reference |\n| --- | --- | --- | --- | --- |\n| Fixture unrelated bookkeeping row | `some-reviewer` | Recorded for traceability, no impact-chain claim | `routed` | see skills/product-rd-workflow/references/relocated-note.md for the rationale |\n' \
251
+ >> "$H/$LEDGER_REL"
252
+ git -C "$H" add -A
253
+ git -C "$H" commit -qm "round 1: append a row belonging to a different register table"
254
+ # Guard: the row really would resolve to a selected owner on a path-only rule.
255
+ git -C "$H" show "HEAD:$LEDGER_REL" | grep -q 'skills/product-rd-workflow/references/relocated-note.md' \
256
+ || fail "leg H fixture is a no-op: the unrelated row lost its owner-package path"
257
+ run_gate "$H" fixture-base
258
+ report_leg "leg H (unrelated five-column table row)" 0 "the unrelated-table regression"
259
+
260
+ # ---------------------------------------------------------------------------
261
+ # Leg L — MULTI-OWNER blocking. Resolving to more than one selected owner used
262
+ # to warn and drop the row, which is the worst pairing: the author sees a
263
+ # warning, the row is never evaluated, and the exit code says the gate passed.
264
+ # Without a leg here a regression back to warn-and-drop leaves every other leg
265
+ # green while an explicit acceptance requirement fails.
266
+ # ---------------------------------------------------------------------------
267
+ L="$TMP/leg-l"
268
+ seed_repo "$L"
269
+ git -C "$L" branch fixture-base HEAD
270
+ git -C "$L" switch -q -c case-ambiguous
271
+ mutate_owner_rule "$L" product-rd-workflow L
272
+ mutate_owner_rule "$L" testing-strategy L2
273
+ # Both owners are separately and validly declared, so the ONLY thing left for
274
+ # the gate to object to is the ambiguous row. Without these two the round would
275
+ # fail for a missing declaration instead, and the leg would pass against a gate
276
+ # that still merely warns — which is exactly what it must detect.
277
+ append_valid_row "$L" product-rd-workflow L "Fixture first owner row"
278
+ append_valid_row "$L" testing-strategy L2 "Fixture second owner row"
279
+ printf '| Fixture ambiguous row | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; firing-path: file:skills/product-rd-workflow/SKILL.md#%s | `updated` | `product-rd-workflow/SKILL.md` and `testing-strategy/SKILL.md` both changed and are cited in one row |\n' \
280
+ "$(rule_anchor L)" >> "$L/$LEDGER_REL"
281
+ git -C "$L" add -A
282
+ git -C "$L" commit -qm "round 1: one row citing two changed selected owners"
283
+ run_gate "$L" fixture-base
284
+ report_leg "leg L (row resolving to two selected owners)" 1 "the multi-owner warn-and-drop" "impact_chain_row_ambiguous"
285
+
286
+ # ---------------------------------------------------------------------------
287
+ # Leg M — NON-CURATED owner. A row about a skill outside the curated upstream
288
+ # list is outside this gate's jurisdiction and must stay skipped, declaration or
289
+ # not. This is the acceptance requirement that the widening must not start
290
+ # selecting owners the curated list never picked.
291
+ # ---------------------------------------------------------------------------
292
+ M="$TMP/leg-m"
293
+ seed_repo "$M"
294
+ mkdir -p "$M/skills/worktree-isolation"
295
+ cat > "$M/skills/worktree-isolation/SKILL.md" <<'MD'
296
+ ---
297
+ name: worktree-isolation
298
+ description: A fixture skill deliberately outside the curated upstream-owner list.
299
+ ---
300
+
301
+ # Non-curated Fixture Owner
302
+
303
+ ## Rules
304
+
305
+ - Baseline rule line owned by a skill this gate does not govern.
306
+ MD
307
+ git -C "$M" add -A
308
+ git -C "$M" commit -qm "seed a non-curated owner package"
309
+ git -C "$M" branch fixture-base HEAD
310
+ git -C "$M" switch -q -c case-noncurated
311
+ printf -- '- Rule %s must be recorded before the round lands.\n' "$(rule_anchor M)" \
312
+ >> "$M/skills/worktree-isolation/SKILL.md"
313
+ printf '| Fixture non-curated row | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; firing-path: file:skills/worktree-isolation/SKILL.md#%s | `updated` | `worktree-isolation/SKILL.md` changed; this skill is not a curated upstream owner |\n' \
314
+ "$(rule_anchor M)" >> "$M/$LEDGER_REL"
315
+ git -C "$M" add -A
316
+ git -C "$M" commit -qm "round 1: change a non-curated owner and record it"
317
+ run_gate "$M" fixture-base
318
+ report_leg "leg M (non-curated owner stays out of scope)" 0 "the curated-list bypass"
319
+
320
+ # ---------------------------------------------------------------------------
321
+ # Leg N — MULTI-OWNER, NO DECLARATION. Leg L pins that a declaring row citing two
322
+ # selected owners blocks. This pins the other side: an unrelated register row
323
+ # that happens to mention two curated owners in prose asserts nothing this gate
324
+ # owns, so promoting its old warning to a merge block would be the same
325
+ # unrelated-table regression leg H guards. The two legs together are what make
326
+ # the blocking change scoped rather than blanket.
327
+ # ---------------------------------------------------------------------------
328
+ N="$TMP/leg-n"
329
+ seed_repo "$N"
330
+ git -C "$N" branch fixture-base HEAD
331
+ git -C "$N" switch -q -c case-ambiguous-undeclared
332
+ printf '| Note | Owner | Rationale | Status | Reference |\n| --- | --- | --- | --- | --- |\n| Fixture cross-owner bookkeeping note | `some-reviewer` | Recorded for traceability; this row makes no impact-chain claim | `routed` | compares how product-rd-workflow/SKILL.md and testing-strategy/SKILL.md phrase the same gate |\n' \
333
+ >> "$N/$LEDGER_REL"
334
+ git -C "$N" add -A
335
+ git -C "$N" commit -qm "round 1: an unrelated row naming two curated owners, declaring nothing"
336
+ # Guard: both owner keys must sit in the EVIDENCE cell, which is what ownership
337
+ # resolution reads. A fixture that put them in another column would resolve to
338
+ # nothing and pass against any gate.
339
+ git -C "$N" show "HEAD:$LEDGER_REL" | tail -1 | awk -F'|' '{print $6}' \
340
+ | grep -q 'product-rd-workflow/SKILL.md' \
341
+ || fail "leg N fixture is a no-op: first owner key is not in the evidence cell"
342
+ git -C "$N" show "HEAD:$LEDGER_REL" | tail -1 | awk -F'|' '{print $6}' \
343
+ | grep -q 'testing-strategy/SKILL.md' \
344
+ || fail "leg N fixture is a no-op: second owner key is not in the evidence cell"
345
+ run_gate "$N" fixture-base
346
+ report_leg "leg N (undeclared row naming two owners)" 0 "the blanket multi-owner block"
347
+
348
+ # ---------------------------------------------------------------------------
349
+ # Leg O — the same evasion as leg A, written with a path prefix an author uses
350
+ # without meaning anything unusual: `./skills/<owner>/...`. The locator's
351
+ # lookbehind refuses a match preceded by a path character, so before the prefix
352
+ # allowance this citation resolved to nothing and the row was silently skipped —
353
+ # putting a restored owner right back where leg A found it, one keystroke away.
354
+ # ---------------------------------------------------------------------------
355
+ O="$TMP/leg-o"
356
+ seed_repo "$O"
357
+ git -C "$O" branch fixture-base HEAD
358
+ git -C "$O" switch -q -c case-dotslash
359
+ mutate_owner_rule "$O" product-rd-workflow O
360
+ printf '| Fixture dot-slash row | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; firing-path: file:skills/product-rd-workflow/SKILL.md#%s | `updated` | see ./skills/product-rd-workflow/references/design-note.md for the rationale |\n' \
361
+ "$(rule_anchor O)" >> "$O/$LEDGER_REL"
362
+ git -C "$O" add -A
363
+ git -C "$O" commit -qm "round 1: change the owner, cite it with a ./ prefixed path"
364
+ git -C "$O" checkout -q fixture-base -- skills/product-rd-workflow/SKILL.md
365
+ git -C "$O" commit -qm "owner restored to base bytes" -q
366
+ git -C "$O" diff --name-only fixture-base HEAD | grep -q '^skills/product-rd-workflow/' \
367
+ && fail "leg O fixture is a no-op: the owner is still changed at HEAD"
368
+ run_gate "$O" fixture-base
369
+ report_leg "leg O (restored owner, ./ prefixed citation)" 1 "the path-prefix evasion" "impact_chain_row_vouches_for_unchanged_owner"
370
+
371
+ # Leg O2 — the rooted counterpart. The adversarial challenge found this one: the
372
+ # allowance covered `./skills/` but not `/skills/`, so a rooted citation resolved
373
+ # to nothing, and a row that resolves to nothing is skipped before any refusal
374
+ # can apply. One character of prefix was the whole evasion.
375
+ O2="$TMP/leg-o2"
376
+ seed_repo "$O2"
377
+ git -C "$O2" branch fixture-base HEAD
378
+ git -C "$O2" switch -q -c case-rooted
379
+ mutate_owner_rule "$O2" product-rd-workflow O2
380
+ printf '| Fixture rooted-path row | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; firing-path: file:skills/product-rd-workflow/SKILL.md#%s | `updated` | see /skills/product-rd-workflow/references/design-note.md for the rationale |\n' \
381
+ "$(rule_anchor O2)" >> "$O2/$LEDGER_REL"
382
+ git -C "$O2" add -A
383
+ git -C "$O2" commit -qm "round 1: change the owner, cite it with a rooted path"
384
+ git -C "$O2" checkout -q fixture-base -- skills/product-rd-workflow/SKILL.md
385
+ git -C "$O2" commit -qm "owner restored to base bytes" -q
386
+ git -C "$O2" diff --name-only fixture-base HEAD | grep -q '^skills/product-rd-workflow/' \
387
+ && fail "leg O2 fixture is a no-op: the owner is still changed at HEAD"
388
+ run_gate "$O2" fixture-base
389
+ report_leg "leg O2 (restored owner, rooted citation)" 1 "the rooted-path evasion" "impact_chain_row_vouches_for_unchanged_owner"
390
+
391
+ # Leg O3 — the remainder class. Enumerating the characters a filename may hold
392
+ # gets it wrong: a citation whose remainder starts `@`, or carries a non-ASCII
393
+ # character, failed to match, the optional `skills/` prefix backtracked onto the
394
+ # literal `skills`, and the row resolved to nothing — skipped before any refusal
395
+ # could apply. Both remainders are exercised in one row because a single
396
+ # unresolved citation is enough to lose the whole row.
397
+ O3="$TMP/leg-o3"
398
+ seed_repo "$O3"
399
+ git -C "$O3" branch fixture-base HEAD
400
+ git -C "$O3" switch -q -c case-remainder
401
+ mutate_owner_rule "$O3" product-rd-workflow O3
402
+ printf '| Fixture remainder row | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; firing-path: file:skills/product-rd-workflow/SKILL.md#%s | `updated` | rationale in skills/product-rd-workflow/@design.md and skills/product-rd-workflow/设计说明.md |\n' \
403
+ "$(rule_anchor O3)" >> "$O3/$LEDGER_REL"
404
+ git -C "$O3" add -A
405
+ git -C "$O3" commit -qm "round 1: cite the owner with unusual but valid path remainders"
406
+ git -C "$O3" checkout -q fixture-base -- skills/product-rd-workflow/SKILL.md
407
+ git -C "$O3" commit -qm "owner restored to base bytes" -q
408
+ git -C "$O3" diff --name-only fixture-base HEAD | grep -q '^skills/product-rd-workflow/' \
409
+ && fail "leg O3 fixture is a no-op: the owner is still changed at HEAD"
410
+ run_gate "$O3" fixture-base
411
+ report_leg "leg O3 (restored owner, unusual path remainder)" 1 "the remainder-class evasion" "impact_chain_row_vouches_for_unchanged_owner"
412
+
413
+ # Leg O4 — the Markdown-relative form, the fourth spelling a path parse missed
414
+ # and the one that ended the patching: an ordinary `[note](../../<owner>/…)` link
415
+ # from the ledger to a file inside the owner package. Legs O through O4 are kept
416
+ # together deliberately: each was a separate round of the same class, and the
417
+ # resolution is now a membership test over the owner vocabulary, which admits all
418
+ # four by construction rather than by four accepted spellings.
419
+ O4="$TMP/leg-o4"
420
+ seed_repo "$O4"
421
+ git -C "$O4" branch fixture-base HEAD
422
+ git -C "$O4" switch -q -c case-relative
423
+ mutate_owner_rule "$O4" product-rd-workflow O4
424
+ printf '| Fixture relative-link row | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; firing-path: file:skills/product-rd-workflow/SKILL.md#%s | `updated` | rationale in [the design note](../../product-rd-workflow/references/design-note.md) |\n' \
425
+ "$(rule_anchor O4)" >> "$O4/$LEDGER_REL"
426
+ git -C "$O4" add -A
427
+ git -C "$O4" commit -qm "round 1: cite the owner through a Markdown-relative link"
428
+ git -C "$O4" checkout -q fixture-base -- skills/product-rd-workflow/SKILL.md
429
+ git -C "$O4" commit -qm "owner restored to base bytes" -q
430
+ git -C "$O4" diff --name-only fixture-base HEAD | grep -q '^skills/product-rd-workflow/' \
431
+ && fail "leg O4 fixture is a no-op: the owner is still changed at HEAD"
432
+ run_gate "$O4" fixture-base
433
+ report_leg "leg O4 (restored owner, Markdown-relative link)" 1 "the relative-link evasion" "impact_chain_row_vouches_for_unchanged_owner"
434
+
435
+ # ---------------------------------------------------------------------------
436
+ # Leg P — the refusal an undeclared row still owes. It cites two curated owners
437
+ # but only one of them changed, so the OLD predicate filtered the citation down
438
+ # to that one owner, bound the row, and rejected it for its missing declaration.
439
+ # Sharing the new name-level count with undeclared rows turned that into a
440
+ # two-owner ambiguity that merely warns and drops — a refusal quietly lost while
441
+ # the change was billed as a tightening. A second, valid row declares the changed
442
+ # owner, so the demand is satisfied and this row's own rejection is the only
443
+ # thing the verdict can be measuring.
444
+ # ---------------------------------------------------------------------------
445
+ P1="$TMP/leg-p"
446
+ seed_repo "$P1"
447
+ git -C "$P1" branch fixture-base HEAD
448
+ git -C "$P1" switch -q -c case-undeclared-partial
449
+ mutate_owner_rule "$P1" product-rd-workflow P
450
+ append_valid_row "$P1" product-rd-workflow P "Fixture declaring row"
451
+ printf '| Fixture undeclared row | `downstream-executor` | no declaration here, only prose | `updated` | contrasts `product-rd-workflow/SKILL.md` with `testing-strategy/SKILL.md` |\n' \
452
+ >> "$P1/$LEDGER_REL"
453
+ git -C "$P1" add -A
454
+ git -C "$P1" commit -qm "round 1: a declaring row plus an undeclared row citing two owners"
455
+ git -C "$P1" diff --name-only fixture-base HEAD | grep -q '^skills/testing-strategy/' \
456
+ && fail "leg P fixture is a no-op: the second cited owner must NOT be changed"
457
+ run_gate "$P1" fixture-base
458
+ report_leg "leg P (undeclared row, one cited owner changed)" 1 "the lost undeclared-row refusal" "impact_chain_behavior_evidence_missing"
459
+
460
+ # ---------------------------------------------------------------------------
461
+ # Leg S — the MALFORMED-ROW escape. One unescaped pipe puts a row outside the
462
+ # five-column parse, and a row outside the parse is invisible to every later
463
+ # check. That was survivable while it could only cost the row its own
464
+ # completeness; with a refusal now riding on surviving rows it becomes a way to
465
+ # buy silence: declare, name a byte-identical owner, add a stray pipe, and the
466
+ # range reports nothing at all. Blocking is scoped to that pair — declaration
467
+ # plus selected owner — so the register's other malformed prose keeps its
468
+ # advisory, which leg S2 pins.
469
+ # ---------------------------------------------------------------------------
470
+ S1="$TMP/leg-s"
471
+ seed_repo "$S1"
472
+ git -C "$S1" branch fixture-base HEAD
473
+ git -C "$S1" switch -q -c case-malformed
474
+ printf '| Fixture smuggled row | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; firing-path: file:skills/product-rd-workflow/SKILL.md#nope | `updated` | rationale in skills/product-rd-workflow/references/note.md, see a|b for the table |\n' \
475
+ >> "$S1/$LEDGER_REL"
476
+ git -C "$S1" add -A
477
+ git -C "$S1" commit -qm "round 1: a declaring row for a selected owner with an unescaped pipe"
478
+ git -C "$S1" diff --name-only fixture-base HEAD | grep -q '^skills/product-rd-workflow/' \
479
+ && fail "leg S fixture is a no-op: the owner must be byte-identical to base"
480
+ run_gate "$S1" fixture-base
481
+ report_leg "leg S (malformed row buys silence)" 1 "the malformed-row escape" "impact_chain_row_blocking_malformed"
482
+
483
+ # Leg S2 — the other side: a malformed row that makes no declaration and names no
484
+ # selected owner keeps its long-standing advisory. Without this the block is a
485
+ # blanket one and the register's other tables start failing merges on prose.
486
+ S2="$TMP/leg-s2"
487
+ seed_repo "$S2"
488
+ git -C "$S2" branch fixture-base HEAD
489
+ git -C "$S2" switch -q -c case-malformed-unrelated
490
+ printf '| Fixture unrelated malformed note | `some-reviewer` | recorded for traceability | `routed` | see the a|b column of the upstream table |\n' \
491
+ >> "$S2/$LEDGER_REL"
492
+ git -C "$S2" add -A
493
+ git -C "$S2" commit -qm "round 1: an unrelated malformed row"
494
+ run_gate "$S2" fixture-base
495
+ report_leg "leg S2 (unrelated malformed row stays advisory)" 0 "the blanket malformed block"
496
+
497
+ # ---------------------------------------------------------------------------
498
+ # Leg S3 — the same escape through a different structural rejection. A stray pipe
499
+ # is only one way out of the parse; an empty cell is another, and the row is just
500
+ # as invisible afterwards. Blocking the pipe alone would have moved the escape
501
+ # rather than closed it.
502
+ # ---------------------------------------------------------------------------
503
+ S3="$TMP/leg-s3"
504
+ seed_repo "$S3"
505
+ git -C "$S3" branch fixture-base HEAD
506
+ git -C "$S3" switch -q -c case-emptycell
507
+ printf '| Fixture empty-cell row | | behavioral-evidence: RED-baseline; observed-failure: yes; firing-path: file:skills/product-rd-workflow/SKILL.md#nope | `updated` | rationale in skills/product-rd-workflow/references/note.md |\n' \
508
+ >> "$S3/$LEDGER_REL"
509
+ git -C "$S3" add -A
510
+ git -C "$S3" commit -qm "round 1: a declaring row for a selected owner with an empty cell"
511
+ run_gate "$S3" fixture-base
512
+ report_leg "leg S3 (empty cell buys the same silence)" 1 "the empty-cell escape" "impact_chain_row_blocking_malformed"
513
+
514
+ # ---------------------------------------------------------------------------
515
+ # Leg S4 — the repair must be accepted. A malformed declaring row corrected in a
516
+ # later round must stop blocking, or the gate refuses the very fix it demanded:
517
+ # the survival budget applies to malformed lines exactly as it does to parsed
518
+ # ones. Round 1 carries a sound row so its own demand is met; round 2 escapes the
519
+ # pipe AND lands the rule the corrected row anchors on, because a row is judged
520
+ # against the round it lands in — a correction that resolves only against an
521
+ # earlier round's diff is not yet a valid row, which is a different rule and not
522
+ # what this leg pins.
523
+ # ---------------------------------------------------------------------------
524
+ S4="$TMP/leg-s4"
525
+ seed_repo "$S4"
526
+ git -C "$S4" branch fixture-base HEAD
527
+ git -C "$S4" switch -q -c case-corrected
528
+ mutate_owner_rule "$S4" product-rd-workflow S4
529
+ append_valid_row "$S4" product-rd-workflow S4 "Fixture sound row"
530
+ printf '| Fixture to-be-corrected row | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; firing-path: file:skills/product-rd-workflow/SKILL.md#%s | `updated` | rationale in skills/product-rd-workflow/references/note.md, see a|b |\n' \
531
+ "$(rule_anchor S4b)" >> "$S4/$LEDGER_REL"
532
+ git -C "$S4" add -A
533
+ git -C "$S4" commit -qm "round 1: a sound row plus a malformed declaring row"
534
+ perl -0pi -e 's/, see a\|b \|/, see a\\|b |/' "$S4/$LEDGER_REL"
535
+ grep -q 'see a\\|b' "$S4/$LEDGER_REL" || fail "leg S4 fixture is a no-op: the pipe was not escaped"
536
+ mutate_owner_rule "$S4" product-rd-workflow S4b
537
+ git -C "$S4" add -A
538
+ git -C "$S4" commit -qm "round 2: escape the pipe and land the rule the row anchors on"
539
+ run_gate "$S4" fixture-base
540
+ report_leg "leg S4 (a corrected malformed row stops blocking)" 0 "the un-repairable block"
541
+ # ---------------------------------------------------------------------------
542
+ # Leg S5 / S6 — the outer-pipe spellings. Markdown accepts a table row without a
543
+ # leading or trailing pipe, so dropping one is a fourth way out of the parse and
544
+ # was found separately from the stray pipe, the empty cell and the bad status
545
+ # word. The check is now defined by "this line did not become a row" rather than
546
+ # by which rejection turned it away, so these two close with the others rather
547
+ # than needing their own branch.
548
+ # ---------------------------------------------------------------------------
549
+ S5="$TMP/leg-s5"
550
+ seed_repo "$S5"
551
+ git -C "$S5" branch fixture-base HEAD
552
+ git -C "$S5" switch -q -c case-noleading
553
+ printf 'Fixture no-leading-pipe row | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; firing-path: file:skills/product-rd-workflow/SKILL.md#nope | `updated` | rationale in skills/product-rd-workflow/references/note.md |\n' \
554
+ >> "$S5/$LEDGER_REL"
555
+ git -C "$S5" add -A
556
+ git -C "$S5" commit -qm "round 1: a declaring row without its leading pipe"
557
+ run_gate "$S5" fixture-base
558
+ report_leg "leg S5 (no leading pipe)" 1 "the missing-leading-pipe escape" "impact_chain_row_blocking_malformed"
559
+
560
+ S6="$TMP/leg-s6"
561
+ seed_repo "$S6"
562
+ git -C "$S6" branch fixture-base HEAD
563
+ git -C "$S6" switch -q -c case-notrailing
564
+ printf '| Fixture no-trailing-pipe row | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; firing-path: file:skills/product-rd-workflow/SKILL.md#nope | `updated` | rationale in skills/product-rd-workflow/references/note.md\n' \
565
+ >> "$S6/$LEDGER_REL"
566
+ git -C "$S6" add -A
567
+ git -C "$S6" commit -qm "round 1: a declaring row without its trailing pipe"
568
+ run_gate "$S6" fixture-base
569
+ report_leg "leg S6 (no trailing pipe)" 1 "the missing-trailing-pipe escape" "impact_chain_row_blocking_malformed"
570
+
571
+ echo
572
+ if [ "$legs_failed" -gt 0 ]; then
573
+ echo "test_impact_chain_round_attribution: $legs_failed/18 legs RED (forms confirmed reproducing)"
574
+ exit 1
575
+ fi
576
+ echo "test_impact_chain_round_attribution: ok (all forms closed)"