@ccoalm/ccl-skills 0.15.0 → 0.15.1

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 (32) hide show
  1. package/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/ccl-skills.ts +80 -4
  2. package/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/commands/ccl-install-skills.md +16 -4
  3. package/dist/assets/marketplace/plugins/ccl-skills/scripts/owner-dispatch/owner-dispatch.sh +13 -2
  4. package/dist/assets/marketplace/plugins/ccl-skills/scripts/owner-dispatch/test.sh +53 -0
  5. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/review_gate.py +7 -1
  6. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate.sh +65 -0
  7. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/SKILL.md +4 -4
  8. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/alerting-and-on-call.md +8 -0
  9. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/SKILL.md +14 -14
  10. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/dual-sidecar-and-traffic-config-center.md +1 -1
  11. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/grpc-authority-workaround.md +40 -83
  12. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/mesh-architecture.md +2 -2
  13. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/retry-timeout-circuit-breaker.md +44 -37
  14. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/service-discovery-recipe.md +1 -1
  15. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/delivery-lifecycle.md +1 -1
  16. package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-scope/SKILL.md +8 -5
  17. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/external-practice-controls.md +3 -3
  18. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-register.md +13 -0
  19. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/eval-golden-trace.rb +31 -7
  20. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/impact-chain-gate.rb +74 -3
  21. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/skill-behavior-eval.py +103 -21
  22. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_impact_chain_refscripts.sh +74 -1
  23. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_eval_runtime.py +428 -0
  24. package/dist/assets/release.json +45 -30
  25. package/dist/claude-adapter.js +14 -7
  26. package/dist/codex-host.d.ts +1 -3
  27. package/dist/codex-host.js +6 -9
  28. package/dist/host-probe.d.ts +11 -0
  29. package/dist/host-probe.js +27 -0
  30. package/dist/opencode-adapter.js +24 -19
  31. package/dist/unified.js +11 -9
  32. package/package.json +1 -1
@@ -5,7 +5,7 @@
5
5
  # upstream-owner skill must declare a behavioral-evidence status and an
6
6
  # observed-failure state, and (for non-wording changes) name an owner-scoped
7
7
  # FIRING PATH that resolves to this diff — an anchor on a changed normative
8
- # rule line, or a changed owner executable. The statuses are required author
8
+ # rule or table data line, or a changed owner executable. The statuses are required author
9
9
  # declarations; the firing path and the wording-only classification are the
10
10
  # machine-verified core. Extracted from the former inline `ruby -e` block in
11
11
  # check-ccl-skills.sh so the program gets normal Ruby tooling and no
@@ -1141,9 +1141,80 @@ if upstream.any? || routing_entrypoint_changed || changed_paths.include?(LEDGER_
1141
1141
  blob[:content].scan(Regexp.new(Regexp.escape(anchor))).length == 1
1142
1142
  end
1143
1143
  end
1144
+ # Definition/decision tables are firing surfaces too. Recognize explicit
1145
+ # Markdown tables with a header and delimiter; comments and code examples do
1146
+ # not count. This proves location/shape, not the truth of the definition.
1147
+ table_data_anchor_valid = lambda do |scope, parts|
1148
+ prior = blob_at.call(scope.base, parts[:path])
1149
+ next false if prior && prior[:content].include?(parts[:anchor])
1150
+ previous_cells = nil
1151
+ columns = nil
1152
+ fence = nil
1153
+ comment = false
1154
+ raw_html = nil
1155
+ html_block = false
1156
+ head_blob.call(scope, parts[:path])[:content].each_line do |raw_line|
1157
+ line = raw_line.chomp
1158
+ if fence
1159
+ fence = nil if line.match?(/\A {0,3}#{Regexp.escape(fence[0])}{#{fence.length},}\s*\z/)
1160
+ next
1161
+ end
1162
+ if raw_html
1163
+ raw_html = nil if line.match?(raw_html)
1164
+ next
1165
+ end
1166
+ if html_block
1167
+ html_block = false if line.strip.empty?
1168
+ next
1169
+ end
1170
+ hidden = comment || line.include?("<!--") || line.include?("-->")
1171
+ line.scan(/<!--|-->/).each { |marker| comment = marker == "<!--" }
1172
+ if hidden
1173
+ previous_cells = columns = nil
1174
+ next
1175
+ end
1176
+ if (opening = line.match(/\A {0,3}(`{3,}|~{3,})/))
1177
+ fence = opening[1]
1178
+ previous_cells = columns = nil
1179
+ next
1180
+ end
1181
+ terminator = case line
1182
+ when /\A {0,3}<\?/ then /\?>/
1183
+ when /\A {0,3}<!\[CDATA\[/ then /\]\]>/
1184
+ when /\A {0,3}<![A-Z]/ then />/
1185
+ end
1186
+ if terminator
1187
+ raw_html = terminator unless line.match?(terminator)
1188
+ previous_cells = columns = nil
1189
+ next
1190
+ end
1191
+ if line.match?(%r{\A {0,3}</?[A-Za-z][\w-]*(?:\s|>|/|\z)})
1192
+ tag = line[/\A {0,3}<(script|pre|style|textarea)(?:\s|>|\z)/i, 1]
1193
+ raw_html = %r{</#{tag}\s*>}i if tag && !line.match?(%r{</#{tag}\s*>}i)
1194
+ html_block = !tag
1195
+ previous_cells = columns = nil
1196
+ next
1197
+ end
1198
+ unless line.match?(/\A {0,3}\|.*\|\s*\z/)
1199
+ previous_cells = columns = nil
1200
+ next
1201
+ end
1202
+ cells = line.strip[1...-1].split(/(?<!\\)\|/, -1).map(&:strip)
1203
+ delimiter = cells.length >= 2 && cells.all? { |cell| cell.match?(/\A:?-{3,}:?\z/) }
1204
+ if delimiter
1205
+ columns = previous_cells && previous_cells.length == cells.length ? cells.length : nil
1206
+ elsif columns && columns == cells.length
1207
+ break true if cells.any? { |cell| cell.include?(parts[:anchor]) }
1208
+ else
1209
+ columns = nil
1210
+ end
1211
+ previous_cells = cells
1212
+ end == true
1213
+ end
1144
1214
  enforcing_file_locator_valid = lambda do |scope, parts|
1145
1215
  next false unless parts && parts[:kind] == "file"
1146
1216
  next false unless parts[:path].end_with?(".md")
1217
+ next false if parts[:path] == LEDGER_PATH # Evidence cannot certify itself.
1147
1218
  next false unless locator_valid.call(scope, "file:#{parts[:path]}##{parts[:anchor]}")
1148
1219
  line = added_lines_for.call(scope, parts[:path]).find { |added| added.include?(parts[:anchor]) }
1149
1220
  next false unless line
@@ -1161,7 +1232,7 @@ if upstream.any? || routing_entrypoint_changed || changed_paths.include?(LEDGER_
1161
1232
  # "the adapter does not support"), and single characters with broad
1162
1233
  # compounds (应/只/别 — 应用/只是/区别).
1163
1234
  normative = line.match?(/(?:\b(?:must|shall|never|do\s+not|don'?t|required?|requires?|block(?:s|ed)?|reject(?:s|ed)?|deny|denied|invalidates?|forbid(?:s|den)?|cannot|enforcement)\b|必须|不得|禁止|拒绝|作废|仅限|只能|应当|应该|务必|不能|不允许|不可)/i)
1164
- list_rule && normative
1235
+ (list_rule && normative) || table_data_anchor_valid.call(scope, parts)
1165
1236
  end
1166
1237
  # A routing-surface-only owner has no changed rule line to anchor on: its whole
1167
1238
  # change is one YAML scalar. The answer is NOT to exempt it — a description edit
@@ -1360,7 +1431,7 @@ if upstream.any? || routing_entrypoint_changed || changed_paths.include?(LEDGER_
1360
1431
  firing_parts = locator_parts.call(firing_path)
1361
1432
  firing_path_valid = firing_locator_valid.call(row_scope, firing_parts, owner)
1362
1433
  # The machine-checked core is the FIRING PATH (an owner-scoped anchor on a
1363
- # changed normative rule, or a changed owner executable) plus the
1434
+ # changed normative rule/table data, or a changed owner executable) plus the
1364
1435
  # deterministic wording-only classification. The behavioral-evidence
1365
1436
  # status and observed-failure fields are required author declarations —
1366
1437
  # honest labels, not digest-verified artifacts: a digest-bound evidence
@@ -52,7 +52,7 @@ Usage:
52
52
  Run in a SCRATCH checkout: the current arm executes the installed hooks/plugins (not just the
53
53
  read-only model tools), so treat it as potentially side-effecting, not inert.
54
54
  """
55
- import argparse, hashlib, json, os, re, subprocess, sys, threading, time
55
+ import argparse, hashlib, json, os, re, signal, subprocess, sys, time
56
56
 
57
57
  HERE = os.path.dirname(os.path.abspath(__file__))
58
58
  DEFAULT_FIXTURES = os.path.normpath(os.path.join(HERE, "..", "..", "..", "eval", "behavior-fixtures.jsonl"))
@@ -100,23 +100,62 @@ def _headless_claude(cmd, prompt, timeout_s):
100
100
  # stderr → DEVNULL: we never read it, and a full stderr pipe would deadlock the child
101
101
  # on a verbose run and time out an otherwise-valid answer.
102
102
  p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
103
- stderr=subprocess.DEVNULL, text=True)
103
+ stderr=subprocess.DEVNULL, text=True,
104
+ start_new_session=(os.name == "posix"))
104
105
  except FileNotFoundError:
105
106
  return None, "claude_not_found", None, []
106
- out = {"s": ""}
107
- t = threading.Thread(target=lambda: out.__setitem__("s", p.stdout.read()))
108
- t.start()
109
107
  try:
110
- p.stdin.write(prompt); p.stdin.close()
111
- except (BrokenPipeError, OSError):
112
- pass
113
- t.join(timeout_s)
114
- if t.is_alive():
115
- p.kill(); t.join(2)
116
- return None, f"timeout_{timeout_s}s", None, []
117
- rc = p.wait()
108
+ # One deadline covers stdin backpressure, stdout collection and process
109
+ # completion. A reader-only timer leaves writes and p.wait() unbounded.
110
+ out, _ = p.communicate(input=prompt, timeout=timeout_s)
111
+ except BaseException as stopped:
112
+ # The group can outlive its leader while a descendant holds stdout open.
113
+ # Kill the recorded group even when the direct child has already exited.
114
+ cleanup_errors = []
115
+ try:
116
+ if os.name == "posix":
117
+ os.killpg(p.pid, signal.SIGKILL)
118
+ else:
119
+ cleanup_errors.append("descendant_cleanup_unsupported")
120
+ p.kill()
121
+ except ProcessLookupError:
122
+ pass
123
+ except PermissionError:
124
+ target = "group" if os.name == "posix" else "process"
125
+ cleanup_errors.append(f"{target}_kill_permission_denied")
126
+ try:
127
+ p.communicate(timeout=1)
128
+ except (subprocess.TimeoutExpired, OSError) as cleanup_error:
129
+ # An escaped descendant may still own a pipe; do not wait for EOF.
130
+ cleanup_errors.append("stdio_timeout" if isinstance(cleanup_error, subprocess.TimeoutExpired) else "stdio_error")
131
+ try:
132
+ p.kill()
133
+ except ProcessLookupError:
134
+ pass
135
+ except PermissionError:
136
+ cleanup_errors.append("process_kill_permission_denied")
137
+ try:
138
+ p.wait(timeout=1)
139
+ except (subprocess.TimeoutExpired, OSError) as cleanup_error:
140
+ cleanup_errors.append("wait_timeout" if isinstance(cleanup_error, subprocess.TimeoutExpired) else "wait_error")
141
+ # A denied kill or incomplete drain/reap is not evidence of cleanup.
142
+ # Return it so callers can save partial results and stop new processes.
143
+ interrupted = not isinstance(stopped, subprocess.TimeoutExpired)
144
+ error = "interrupted" if interrupted else f"timeout_{timeout_s}s"
145
+ if cleanup_errors:
146
+ error += ";cleanup_unconfirmed:" + ",".join(cleanup_errors)
147
+ if interrupted:
148
+ if cleanup_errors:
149
+ print(f"{error}; confirm process termination before resuming", file=sys.stderr)
150
+ raise
151
+ return None, error, None, []
152
+ finally:
153
+ p.stdin.close()
154
+ p.stdout.close()
155
+ rc = p.returncode
118
156
  parts, result_text, result_subtype, util, invoked = [], None, None, None, []
119
- for ln in out["s"].splitlines():
157
+ terminal_results = []
158
+ for ln in out.splitlines():
120
159
  # The stream is external/untrusted: a line may be invalid JSON, deeply nested (RecursionError),
121
160
  # or a shape-drifted value. Wrap the WHOLE per-line parse+extract so any bad line skips itself
122
161
  # and never crashes the eval run (fail-closed-skip). We read only known fields of known event
@@ -127,6 +166,7 @@ def _headless_claude(cmd, prompt, timeout_s):
127
166
  if not isinstance(ev, dict):
128
167
  continue
129
168
  if ev.get("type") == "result":
169
+ terminal_results.append(ev)
130
170
  result_subtype = ev.get("subtype")
131
171
  if result_subtype == "success":
132
172
  result_text = ev.get("result")
@@ -159,8 +199,19 @@ def _headless_claude(cmd, prompt, timeout_s):
159
199
  # teardown failure — the answer is complete, accept. Without that event we do NOT bank the
160
200
  # text, even if some assistant chunks streamed and rc==0: a truncation or format drift before
161
201
  # the terminal event would otherwise be recorded as a valid sample.
162
- if result_subtype == "success":
163
- return ("\n\n".join(parts) if parts else result_text), None, util, invoked
202
+ if len(terminal_results) > 1:
203
+ return None, "invalid_terminal_result", util, invoked
204
+ if result_subtype == "success" and terminal_results:
205
+ terminal = terminal_results[0]
206
+ if (terminal.get("is_error") not in (None, False)
207
+ or terminal.get("permission_denials")
208
+ or terminal.get("api_error_status") not in (None, 0, "0")
209
+ or terminal.get("terminal_reason") not in (None, "completed")):
210
+ return None, "invalid_terminal_result", util, invoked
211
+ text = "\n\n".join(parts) if parts else result_text
212
+ if not isinstance(text, str) or not text.strip():
213
+ return None, "invalid_terminal_result", util, invoked
214
+ return text, None, util, invoked
164
215
  if rc != 0:
165
216
  return None, f"claude_exit_{rc}", util, invoked
166
217
  if result_subtype:
@@ -312,8 +363,12 @@ def build_report(rows, out_dir, do_judge, timeout_s, last_util, stop_util,
312
363
  in the summary (never silently dropped, or the report reads clean when it isn't). Raw responses
313
364
  stay on disk for audit; judgment-ASSIST, never a score. do_judge=False → fill-in scaffold."""
314
365
  verdicts = []
366
+ cleanup_error = None
315
367
  for fx in rows:
316
368
  base = {"id": fx["id"], "axis": fx.get("axis", "")}
369
+ if cleanup_error:
370
+ verdicts.append({**base, "status": "judge-skipped", "note": cleanup_error})
371
+ continue
317
372
  cur = read_saved_response(os.path.join(out_dir, f"{fx['id']}.current.s1.txt"))
318
373
  cand = read_saved_response(os.path.join(out_dir, f"{fx['id']}.candidate.s1.txt"))
319
374
  if not cur or not cand:
@@ -342,6 +397,8 @@ def build_report(rows, out_dir, do_judge, timeout_s, last_util, stop_util,
342
397
  last_util = util
343
398
  if err:
344
399
  verdicts.append({**base, "status": f"judge-error:{err}"})
400
+ if ";cleanup_unconfirmed:" in err:
401
+ cleanup_error = err
345
402
  continue
346
403
  v.update(base); v["status"] = "judged"
347
404
  if fixture_needs_human(fx):
@@ -467,8 +524,10 @@ def main():
467
524
  if not a.no_judge:
468
525
  print(f"--report-only will make up to {len(rows)} LLM-judge claude call(s) "
469
526
  f"(one per fixture with both arms saved). Use --no-judge for a fill-in scaffold.")
470
- build_report(rows, a.out, not a.no_judge, a.timeout, None, a.stop_util, ctext, a.current_tag)
527
+ verdicts = build_report(rows, a.out, not a.no_judge, a.timeout, None, a.stop_util, ctext, a.current_tag)
471
528
  print(f"\nReport: {os.path.join(a.out, 'capability-delta-report.md')}")
529
+ if any(";cleanup_unconfirmed:" in v["status"] for v in verdicts):
530
+ sys.exit(1)
472
531
  return
473
532
  # Fail fast: don't burn every current-arm run and only then discover the candidate
474
533
  # arm has no contract to inject.
@@ -501,6 +560,12 @@ def main():
501
560
  f"{a.stop_util:.0%} — stopping before [{fx['id']}/{arm}/s{s}]. "
502
561
  f"Partial results in {a.out}; rerun to continue, then --report-only.")
503
562
  logf.close(); return
563
+ # A failed replacement must not leave its old response usable.
564
+ # Invalidate only after quota allows this sample to start.
565
+ try:
566
+ os.unlink(rpath)
567
+ except FileNotFoundError:
568
+ pass
504
569
  t0 = time.time()
505
570
  text, err, util, invoked = run_agent(fx["prompt"], a.timeout, arm, a.contract)
506
571
  dt = time.time() - t0
@@ -509,7 +574,13 @@ def main():
509
574
  if err:
510
575
  print(f"[{fx['id']}/{arm}/s{s}] ERROR {err} ({dt:.0f}s)")
511
576
  logf.write(json.dumps({"id": fx["id"], "arm": arm, "sample": s, "error": err}) + "\n")
512
- logf.flush(); continue
577
+ logf.flush()
578
+ if ";cleanup_unconfirmed:" in err:
579
+ print(f"*** ABORT: process cleanup is unconfirmed; no further samples or judges started. "
580
+ f"Partial results in {a.out}. Confirm process termination before resuming.")
581
+ logf.close()
582
+ sys.exit(1)
583
+ continue
513
584
  with open(rpath, "w", encoding="utf-8") as rf:
514
585
  rf.write(f"# sig: {sig}\n")
515
586
  rf.write(f"# {fx['id']} / arm={arm} / sample={s} / {dt:.0f}s / util={last_util}\n")
@@ -528,13 +599,24 @@ def main():
528
599
  if "current" in arms and "candidate" in arms:
529
600
  print("Building capability-delta report (candidate vs current)"
530
601
  + ("" if a.no_judge else " via LLM-judge — this makes more claude calls") + " ...")
531
- build_report(rows, a.out, not a.no_judge, a.timeout, last_util, a.stop_util,
532
- contract_text, a.current_tag)
602
+ verdicts = build_report(rows, a.out, not a.no_judge, a.timeout, last_util, a.stop_util,
603
+ contract_text, a.current_tag)
533
604
  print(f"Report: {os.path.join(a.out, 'capability-delta-report.md')} "
534
605
  f"(confirm every 🔴 HUMAN row by eye; it is judgment-assist, not a score).")
606
+ if any(";cleanup_unconfirmed:" in v["status"] for v in verdicts):
607
+ sys.exit(1)
535
608
  else:
536
609
  print("Single arm — no delta to report. Run --both-arms for the capability-delta report.")
537
610
 
538
611
 
539
612
  if __name__ == "__main__":
540
- main()
613
+ previous_sigterm = signal.getsignal(signal.SIGTERM)
614
+ if previous_sigterm == signal.SIG_DFL:
615
+ def terminate(signum, _frame):
616
+ raise SystemExit(128 + signum)
617
+ signal.signal(signal.SIGTERM, terminate)
618
+ try:
619
+ main()
620
+ finally:
621
+ if previous_sigterm == signal.SIG_DFL:
622
+ signal.signal(signal.SIGTERM, previous_sigterm)
@@ -48,6 +48,10 @@ printf '# fixture slug-named reference\n\nNeutral placeholder for the inner-rena
48
48
  # and isolate the prose-only guard from the reproduction check.
49
49
  printf '# fixture slug mention: platform-observability\n' >> "$REPO/skills/product-rd-workflow/scripts/check-agent-contract-coverage.sh"
50
50
  printf '\nFixture eligible sibling mention: platform-observability.\n' >> "$REPO/skills/product-rd-workflow/references/adr-convention.md"
51
+ # A real table baseline distinguishes a changed definition from an old anchor
52
+ # surviving an unrelated source-link edit on the same line.
53
+ TABLE_REL="skills/product-rd-workflow/references/zz-fixture-definition.md"
54
+ printf '# Definitions\n\n| Metric | Definition | Source |\n| --- | --- | --- |\n| Incident deployment ratio | Deployments needing later work | old-source |\n' > "$REPO/$TABLE_REL"
51
55
  git -C "$REPO" add -A
52
56
  git -C "$REPO" commit -qm "seed throwaway upstream reference"
53
57
  git -C "$REPO" branch fixture-base HEAD
@@ -154,6 +158,75 @@ run_gate
154
158
  assert_not_contains "impact_chain_firing_path_missing" "$out" "a never-phrased normative rule should satisfy the firing-path gate"
155
159
  assert_rc "$rc" 0 "a never-phrased normative list rule must be accepted"
156
160
 
161
+ # Definitions are executable guidance even when their Markdown surface is a
162
+ # table instead of a normative list. Negative controls keep anchors bound to
163
+ # new, rendered data cells in the correct owner and round.
164
+ definition_table_case() {
165
+ local label="$1" expected="$2" anchor='Unplanned deployments caused by production incidents'
166
+ local firing_path="$TABLE_REL"
167
+ new_case "case-ref-definition-table-$label"
168
+ case "$label" in
169
+ surviving-anchor)
170
+ anchor='Deployments needing later work'
171
+ perl -pi -e 's/old-source/new-source/' "$REPO/$TABLE_REL" ;;
172
+ unchanged-table)
173
+ anchor='Deployments needing later work'
174
+ printf '\nChanged neighboring prose.\n' >> "$REPO/$TABLE_REL" ;;
175
+ *)
176
+ perl -pi -e 's/Deployments needing later work/Unplanned deployments caused by production incidents/' "$REPO/$TABLE_REL" ;;
177
+ esac
178
+ case "$label" in
179
+ foreign-owner)
180
+ firing_path='skills/terminal-cli-dev/references/zz-fixture-definition.md'
181
+ cp "$REPO/$TABLE_REL" "$REPO/$firing_path" ;;
182
+ duplicate) printf '\n%s\n' "$anchor" >> "$REPO/$TABLE_REL" ;;
183
+ inline-comment) perl -pi -e 's/Unplanned deployments caused by production incidents/<!-- Unplanned deployments caused by production incidents -->/' "$REPO/$TABLE_REL" ;;
184
+ multiline-comment) perl -0777 -pi -e 's/\n\| Metric/\n<!--\n| Metric/; $_ .= "-->\n"' "$REPO/$TABLE_REL" ;;
185
+ backtick-fence) perl -0777 -pi -e 's/\n\| Metric/\n```markdown\n| Metric/; $_ .= "```\n"' "$REPO/$TABLE_REL" ;;
186
+ tilde-fence) perl -0777 -pi -e 's/\n\| Metric/\n~~~markdown\n| Metric/; $_ .= "~~~\n"' "$REPO/$TABLE_REL" ;;
187
+ raw-html) perl -0777 -pi -e 's/\n\| Metric/\n<script>\n\n| Metric/; $_ .= "<\/script>\n"' "$REPO/$TABLE_REL" ;;
188
+ raw-html-open-line) perl -0777 -pi -e 's/\n\| Metric/\n<script\n| Metric/; $_ .= "<\/script>\n"' "$REPO/$TABLE_REL" ;;
189
+ processing-instruction) perl -0777 -pi -e 's/\n\| Metric/\n<?xml\n| Metric/; $_ .= "?>\n"' "$REPO/$TABLE_REL" ;;
190
+ cdata) perl -0777 -pi -e 's/\n\| Metric/\n<![CDATA[\n| Metric/; $_ .= "]]>\n"' "$REPO/$TABLE_REL" ;;
191
+ declaration) perl -0777 -pi -e 's/\n\| Metric/\n<!DOCTYPE\n| Metric/; $_ .= ">\n"' "$REPO/$TABLE_REL" ;;
192
+ closed-cdata) perl -0777 -pi -e 's/\n\| Metric/\n<![CDATA[\nclosed example\n]]>\n\n| Metric/' "$REPO/$TABLE_REL" ;;
193
+ html-block) perl -0777 -pi -e 's/\n\| Metric/\n<div>\n| Metric/; $_ .= "<\/div>\n"' "$REPO/$TABLE_REL" ;;
194
+ indented-code) perl -pi -e 's/^\|/ |/' "$REPO/$TABLE_REL" ;;
195
+ no-header) perl -ni -e 'print unless /^\| (Metric|---)/' "$REPO/$TABLE_REL" ;;
196
+ header-anchor) perl -0777 -pi -e 's/\| Metric \| Definition \| Source \|/| Unplanned deployments caused by production incidents | Definition | Source |/; s/\n\| Incident deployment ratio[^\n]*\n/\n/' "$REPO/$TABLE_REL" ;;
197
+ no-red) : ;;
198
+ esac
199
+ local status='RED-baseline' observed='yes'
200
+ if [ "$label" = no-red ]; then status='semantic-control'; observed='no'; fi
201
+ printf '| Fixture definition table %s | `downstream-executor` | behavioral-evidence: %s; observed-failure: %s; result-class: failure; firing-path: file:%s#%s | `updated` | `product-rd-workflow/SKILL.md` definition correction |\n' "$label" "$status" "$observed" "$firing_path" "$anchor" >> "$REGISTER"
202
+ commit_case "definition table $label"
203
+ run_gate
204
+ assert_rc "$rc" "$expected" "definition table $label"
205
+ if [ "$expected" = 1 ]; then
206
+ if [ "$label" = no-red ]; then
207
+ assert_contains 'impact_chain_behavior_evidence_missing' "$out" "definition table $label: $out"
208
+ else
209
+ assert_contains 'impact_chain_firing_path_missing' "$out" "definition table $label: $out"
210
+ fi
211
+ fi
212
+ }
213
+ definition_table_case accepted 0
214
+ definition_table_case closed-cdata 0
215
+ for definition_control in surviving-anchor unchanged-table foreign-owner duplicate inline-comment multiline-comment backtick-fence tilde-fence raw-html raw-html-open-line processing-instruction cdata declaration html-block indented-code no-header header-anchor no-red; do
216
+ definition_table_case "$definition_control" 1
217
+ done
218
+
219
+ # Evidence is not its own firing surface. Keep the anchor only in the locator
220
+ # itself so uniqueness cannot accidentally reject the self-certifying row.
221
+ new_case case-ref-definition-table-self-register
222
+ printf '\nFixture bookkeeping note, with no new enforcing rule.\n' >> "$REPO/skills/skill-extraction-workflow/references/validation-and-landing.md"
223
+ printf '\n| Rule | Owner | Behavior | Status | Evidence |\n| --- | --- | --- | --- | --- |\n' >> "$REGISTER"
224
+ printf '| Fixture self-citing table | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; result-class: failure; firing-path: file:skills/skill-extraction-workflow/references/source-register.md#SELF-REGISTER-DEFINITION-ANCHOR | updated | `skill-extraction-workflow/SKILL.md` bookkeeping edit |\n' >> "$REGISTER"
225
+ commit_case "source register cannot certify its own firing path"
226
+ run_gate
227
+ assert_rc "$rc" 1 "source register must not be its own firing surface"
228
+ assert_contains 'impact_chain_firing_path_missing' "$out" "self-register refusal must name the firing-path boundary"
229
+
157
230
  # A purely DESCRIPTIVE list line ("always exposes" — no imperative/prohibitive
158
231
  # verb) is not an enforcing rule; the widened verb list must not admit it.
159
232
  new_case case-ref-descriptive-always-firing-path
@@ -1583,6 +1656,6 @@ assert_rc "$rc" 1 "a directory masquerading as SKILL.md must not read as a prese
1583
1656
  assert_contains "platform-observability/SKILL.md" "$out" "the masqueraded owner must be named"
1584
1657
 
1585
1658
  assert_rc "$full_check_runs" 1 "fixture suite must retain exactly one full checker wiring case"
1586
- assert_rc "$gate_runs" 97 "all remaining impact-chain fixtures must run the standalone gate"
1659
+ assert_rc "$gate_runs" 118 "all remaining impact-chain fixtures must run the standalone gate"
1587
1660
 
1588
1661
  echo "test_check_ccl_impact_chain_refscripts: ok"