@chrono-meta/fh-gate 1.4.96 → 1.4.98

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 (49) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CATALOG.md +19 -0
  3. package/CHEATSHEET.md +9 -1
  4. package/CLAUDE.md +39 -6
  5. package/README.ja.md +10 -0
  6. package/README.ko.md +10 -0
  7. package/README.md +30 -0
  8. package/README.zh.md +10 -0
  9. package/docs/pillars.svg +3 -7
  10. package/knowledge/shared/harness-core/fh_ecosystem_positioning.md +2 -0
  11. package/knowledge/shared/harness-core/fh_global_positioning_and_distribution_roadmap.md +136 -0
  12. package/knowledge/shared/harness-core/fh_three_layer_canon.md +20 -0
  13. package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +215 -2
  14. package/knowledge/shared/harness-core/ship_readiness_gate.md +112 -0
  15. package/knowledge/shared/learnings/subagent_invocations_log.yaml +125 -0
  16. package/package.json +9 -2
  17. package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
  18. package/plugins/fh-commons/skills/ko-tech-writer/SKILL.md +63 -12
  19. package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
  20. package/plugins/fh-meta/CHANGELOG.md +65 -0
  21. package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +30 -0
  22. package/scripts/compaction_probe.sh +153 -10
  23. package/scripts/consent_registry_check.sh +63 -0
  24. package/scripts/degrade_direction_scan.sh +10 -1
  25. package/scripts/digest_landing_check.sh +20 -4
  26. package/scripts/fh_node_check.sh +60 -1
  27. package/scripts/fh_session_load.sh +22 -2
  28. package/scripts/frontier_digest_autopilot.sh +229 -0
  29. package/scripts/lane_runner_check.sh +637 -0
  30. package/scripts/package_coverage_check.sh +199 -35
  31. package/scripts/postinstall_notice.js +34 -0
  32. package/scripts/psa_scan_lib.sh +164 -0
  33. package/scripts/public_surface_scan_files.sh +30 -0
  34. package/scripts/selfcheck.sh +442 -33
  35. package/scripts/test_consent_registry.sh +54 -0
  36. package/scripts/test_degrade_scan_shell_probes.sh +75 -0
  37. package/scripts/test_field_canon_lanes.sh +29 -5
  38. package/scripts/test_lane_runner_lanes.sh +221 -0
  39. package/scripts/test_marker_crossfamily_lanes.sh +10 -1
  40. package/scripts/test_marker_floor_lanes.sh +25 -1
  41. package/scripts/test_psa_singlefile_lanes.sh +288 -0
  42. package/scripts/test_reviewer_capability_conformance.sh +10 -1
  43. package/scripts/test_selfcheck_state_lanes.sh +173 -0
  44. package/scripts/test_stale_clone_guard_lanes.sh +21 -7
  45. package/scripts/test_version_lockstep_lanes.sh +62 -0
  46. package/scripts/version_lockstep_check.sh +143 -1
  47. package/templates/.git-hooks/pre-commit +22 -1
  48. package/templates/degrade_direction_scan.sh +10 -1
  49. package/templates/goal-quench-settings-merged.json +30 -0
@@ -0,0 +1,637 @@
1
+ #!/usr/bin/env bash
2
+ # lane_runner_check.sh — a lane suite that nothing executes is prose, not an anchor.
3
+ #
4
+ # WHY (measured 2026-08-12, reship axis, card §🔱⑮ A):
5
+ # The card recorded three repairs as "지워도 레인 초록" — delete the repair, the lanes stay green,
6
+ # i.e. the anchor is decorative. Investigating those three found the class is much wider: of 43 lane
7
+ # and test suites under scripts/, **12 had no runner anywhere** — not selfcheck.sh, not the git
8
+ # hooks, not the CI workflows (all three surfaces enumerated, not assumed). 9 of the 12 ship to npm
9
+ # consumers (`npm pack --dry-run --json`, 263 files — a tarball measurement, not a files[] reading).
10
+ # ⚠️ This header said **11 / 8** until 2026-08-13 while the DEBT block below said 12, and the
11
+ # discrepancy is not a typo: 11 was the HAND-ROLLED probe's count, 12 is what this check found on
12
+ # its first run (the hand probe had read a pre-commit COMMENT as wiring). The corrected number went
13
+ # into the array and the prose above it was left alone, so the file carried both. Caught by
14
+ # cross-family review, not by re-reading. ★ A file that states its own measurement twice will
15
+ # eventually state it two different ways — and the stale copy is the one a reader meets first.
16
+ # The sharpest case was scripts/test_marker_floor_lanes.sh: pre-commit's
17
+ # validate_marker_floor() is live and blocks real commits, while its own known-pair calibration has
18
+ # never executed. A shipped gate whose calibration is dead is exactly the defect this repo spent the
19
+ # 2026-08-11/12 campaign closing, one file at a time.
20
+ #
21
+ # WHY A DERIVED CHECK AND NOT 12 MORE ANCHORS (the actual design decision):
22
+ # "Wire each one" closes today's 12 and is blind to the 13th — and the 13th is not hypothetical: the
23
+ # first run of THIS check found one (test_marker_crossfamily_lanes.sh) that the hand-rolled probe
24
+ # behind the original count had misread, because the hook names it only in a comment.
25
+ #
26
+ # ⚠️ THE JUSTIFICATION FOR BUILDING THIS IS THE MEASUREMENT, NOT A RECURRENCE COUNT — and the first
27
+ # draft of this header got that wrong. It claimed N=3 for "hardcoded list where a derived one is
28
+ # available" (citing selfcheck's `for _subj in compaction_probe judgment_circuit_lint
29
+ # novelty_claim_check`, test_selfcheck_state_lanes' four-name list, and the same-day ACCEPTED_ABSENT
30
+ # fix) and leaned on [[feedback_mechanize_at_repetition_prose_before]]'s N≥3 threshold. An
31
+ # independent scan refuted the count using this repo's own discriminator — "첫 발생 직후 고쳤다면
32
+ # 나머지가 막혔겠는가": the ACCEPTED_ABSENT fix is what CREATED the helper that exposed the second
33
+ # site, so those two are one discovery, not two recurrences; and the compaction_probe fix runs the
34
+ # OPPOSITE direction (no declaration existed there, so the correct fix was to start consulting the
35
+ # environment and assert both arms). Honest count for that class: **N=1–2, below threshold.**
36
+ #
37
+ # What actually justifies this file is a direct measurement, which needs no recurrence argument:
38
+ # **12 of 43 lane suites execute nowhere**, 8 of them shipped, including the calibrations for two
39
+ # gates that block real commits. That is a present hole, not a predicted one. Where the derived form
40
+ # is genuinely doing work is narrower and worth stating plainly: it is what makes the DEBT list
41
+ # shrink-only and what fails on the 13th — not a claim that hardcoded lists are a recurring disease
42
+ # here.
43
+ #
44
+ # THREE-VALUED, and the middle value is the whole point:
45
+ # WIRED — some runner invokes it (selfcheck · git hook · CI workflow · another script's dispatch)
46
+ # EXEMPT — declared below with a reason it must NOT be auto-run (cost, live CLI, network)
47
+ # DEBT — known-unwired, listed below, counted loudly on every run, does NOT block
48
+ # An undeclared unwired suite is none of these and FAILS. That is the regrowth this file exists to
49
+ # stop: today's 11 are debt, tomorrow's 12th is a failure.
50
+ #
51
+ # WHY DEBT DOESN'T BLOCK: 11 suites cannot be wired in one sitting, and a check that fails for weeks
52
+ # is a check that trains `--no-verify` — this repo has logged that trade explicitly
53
+ # ([[feedback_overblock_traded_for_failopen]]). The DEBT list is a decision surface, not a silencer:
54
+ # every entry is printed on every run, and the count is a number someone has to look at.
55
+ #
56
+ # Usage: bash scripts/lane_runner_check.sh [--list-debt]
57
+ # Exit: 0 = every suite is WIRED, EXEMPT, or declared DEBT · 1 = an undeclared suite has no runner,
58
+ # or the instrument itself broke (zero suites scanned / dead control).
59
+ set -uo pipefail
60
+
61
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
62
+ cd "$REPO_ROOT" || exit 1
63
+
64
+ # ── EXEMPT — declared reasons a suite must NOT be auto-run ────────────────────────────────────
65
+ # Same rule as package_coverage_check.sh's ACCEPTED_ABSENT: if you cannot write the sentence, the
66
+ # suite probably belongs in a runner instead of here.
67
+ #
68
+ # test_sessionstart_multihook_lanes.sh — spends live `claude` CLI calls (auth + tokens) on whoever
69
+ # runs it. selfcheck DOES reference it, via the SessionStart anchor-pair loop, and routes its
70
+ # exit 2 to NOT EXERCISED; it is listed here only so a future reader does not "fix" that loop
71
+ # into an unconditional run.
72
+ EXEMPT=(
73
+ "test_sessionstart_multihook_lanes.sh"
74
+ )
75
+
76
+ # ── DEBT — measured unwired on 2026-08-12, each awaiting a runner ─────────────────────────────
77
+ # This list is the todo, and it must only ever shrink. Adding a line here is a decision that needs a
78
+ # reason in the commit message; the check does not care, but the reviewer should.
79
+ # 🟥 WHAT `DEBT: 0` DOES NOT MEAN — read before quoting the number anywhere.
80
+ # The scope of this check is a NAME CONVENTION: `suites` is globbed as `test_*.sh` / `*_lanes.sh`
81
+ # (see the glob above). A lane suite that lives INSIDE its subject as a `--self-test` dispatcher has
82
+ # no such filename and is structurally invisible here. Measured 2026-08-13, after DEBT hit zero:
83
+ # scripts/chamber_witness.sh --self-test 16 lanes --self-test callers: 0
84
+ # scripts/capability_registry_check.sh --self-test 7 lanes --self-test callers: 0
85
+ # scripts/digest_landing_check.sh --self-test 10 lanes --self-test callers: 0
86
+ # scripts/directional_diff_gate.sh --self-test --self-test callers: 0
87
+ # 🟥 READ THAT COLUMN EXACTLY: it counts dispatches of `--self-test`, NOT callers of the script.
88
+ # The two come apart, and collapsing them does real damage in both directions. chamber_witness.sh
89
+ # has FOUR live production callers (chamber_run.sh:118·137·158·182) and zero self-test dispatches:
90
+ # writing that as "zero callers" would falsify the evidence sentence that ship_readiness_gate.md's
91
+ # identity-② promotion rests on ("wired into chamber_run.sh steps 2–5"), and ② would be overturned
92
+ # for a reason that is not true. The reverse shorthand is just as wrong: having production callers
93
+ # does NOT discharge the self-test debt — a witness generator whose known-pair never runs can break
94
+ # silently while every run that uses it still reports success, which is the fail-open the promotion
95
+ # criterion is supposed to exclude. (Both halves named by a peer session on 2026-08-13, after my
96
+ # own summary of this block used the collapsing shorthand.)
97
+ # (callers = grep over selfcheck.sh + .github/workflows/*.yml + templates/.git-hooks/*, the same
98
+ # three surfaces this check enumerates. All four SHIP.) selfcheck.sh wires exactly three embedded
99
+ # self-tests by name — compaction_probe · judgment_circuit_lint · novelty_claim_check — and these
100
+ # four are not among them. So the debt number below is true of the filename-convention population
101
+ # and false of "every lane suite in this repo executes". A second lens over `--self-test` dispatchers is
102
+ # the honest next step and is NOT built here; it is carried to the card as a measured residual so
103
+ # the number in this file cannot be read as a claim it does not make.
104
+ # (Raised by adversarial review against this delta, which called C1 "partially refuted" for exactly
105
+ # this reason. It was right: the denominator was mine to state and I had stated it as if it were the
106
+ # population.)
107
+ #
108
+ # ── 12 → 2 as of 2026-08-13 (PR: reship — lane-debt wiring) ──────────────────────────────────
109
+ # TEN of the twelve are discharged by the pair-loop in scripts/selfcheck.sh. TWO are back here,
110
+ # and the point of this block is that THE REASON CHANGED. Before today the entry meant "nothing
111
+ # runs this, and we do not know what happens if it does". Now it means "we ran it, and it fails
112
+ # outside this operator's machine" — which is a different, smaller, and actionable debt.
113
+ #
114
+ # What the twelve were: capability_entrypoint_shipping · chamber_run · destructive_pre_gate ·
115
+ # env_purity · field_canon · frontier_digest_retry · knowledge_seam · marker_crossfamily ·
116
+ # marker_floor · residency_closure · reviewer_capability_conformance · stale_clone_guard.
117
+ # Nine were in the published tarball, so consumers carried nine suites nothing called. The two
118
+ # that mattered most were the marker pair: test_marker_crossfamily_lanes.sh calibrates the
119
+ # `crossfamily:` enum that has hard-blocked commits since 2026-08-08 (the hook names it only in a
120
+ # COMMENT — templates/.git-hooks/pre-commit:1068 — which is what made a hand-rolled probe report
121
+ # 11 not 12), and test_marker_floor_lanes.sh calibrates pre-commit's live validate_marker_floor().
122
+ # Both are wired now.
123
+ #
124
+ # 🟢 THE TWO THAT WERE HERE ARE RE-WIRED (2026-08-14) — DEBT 2 → 0. Both entries below are
125
+ # HISTORICAL: they record why the suites were pulled out and what the fix had to be, kept because
126
+ # the reasoning (and the exact CI-reproduction numbers) are load-bearing evidence for the next
127
+ # person who touches either suite, not because either is still debt.
128
+ #
129
+ # test_field_canon_lanes.sh — was: PASS 19 · FAIL 0 locally; PASS 12 · FAIL 7 in CI. Reproduced
130
+ # locally by pointing HOME at an empty directory — 12/7, the same split — so the dependency was
131
+ # exact and not a guess: field_canon_preload.sh resolves `${HOME}/projects` and seven lanes assumed
132
+ # the operator's mapped-project layout was there. Nothing in the suite declared that precondition,
133
+ # so on a machine without it the suite reported a REGRESSION rather than "not exercised here". Fix
134
+ # (built the second option this entry named, not the first): the suite now carries its own fixture
135
+ # hub+project-root (mirroring the shape lane ⑭ already used) instead of depending on the operator's
136
+ # real filesystem — `run()`/`runD()` inject `CLAUDE_PROJECT_DIR`/`FIELD_CANON_PROJECT_ROOT` at a
137
+ # fixed fixture path. Verified both directions: PASS 19/19 under an empty HOME (the exact CI
138
+ # reproduction) AND a known-negative check — breaking the `-dev`-suffix repo-resolution fallback in
139
+ # field_canon_preload.sh reproduces the identical PASS 12 / FAIL 7 split, so the fixture measures
140
+ # real behavior, not a vacuously-green rewrite.
141
+ #
142
+ # test_stale_clone_guard_lanes.sh — was: 17/17 locally; 2/17 in CI. Predicted by an adversarial
143
+ # review of the delta that introduced `elapsed < 10` as a wall-clock assertion on a mandatory path;
144
+ # CI then produced exactly it. Fix (the first option this entry named): the wedge sleep widened
145
+ # 30s→60s and the assertion changed from a fixed `elapsed < 10` to a relative `elapsed <
146
+ # (wedge/2)=30s` — proving the guard's internal 0.5s budget bounded it (60x headroom) rather than an
147
+ # absolute constant tuned to one machine's speed. Known-negative check: neutering the budget's kill
148
+ # condition in stale_clone_guard.sh (so it waits out the full wedge instead of self-bounding)
149
+ # correctly fails exactly this one lane, confirming the widened threshold still discriminates.
150
+ #
151
+ # Re-wired into scripts/selfcheck.sh's pair-loop alongside the other ten (found→extend, same shape:
152
+ # "scripts/field_canon_preload.sh|scripts/test_field_canon_lanes.sh" and
153
+ # "scripts/stale_clone_guard.sh|scripts/test_stale_clone_guard_lanes.sh").
154
+ DEBT=()
155
+
156
+ if [ "${1:-}" = "--list-debt" ]; then
157
+ # Same bash-3.2 empty-array guard as the scan call below. This site was MISSED when that one was
158
+ # fixed in the same commit — the fix stopped at the two sites the failing run happened to touch,
159
+ # and `--list-debt` (a documented interface, see Usage above) still died with `unbound variable`.
160
+ # Caught by adversarial review, not by me. [[feedback_half_fix_propagation_boundary]]: the
161
+ # question a repair must ask is "where else does this fact have to reach", and "the sites my
162
+ # repro exercised" is not an answer to it.
163
+ printf '%s\n' "${DEBT[@]+"${DEBT[@]}"}"
164
+ exit 0
165
+ fi
166
+
167
+ # ── SELF-REFERENCE: this checker is not a lane suite, so it does not appear in its own scanned set
168
+ # (the name pattern below matches `test_*.sh` / `*_lanes.sh`, and this is neither). That gap is the
169
+ # exact defect this file exists to detect, one level up: unwire it and nothing notices, because the
170
+ # only thing that would notice is itself. Measured by a revert probe on the day it was written —
171
+ # removing its call from selfcheck.sh left every check green, this one included.
172
+ # Guarded on selfcheck.sh's presence so a tree that legitimately lacks it does not fail here.
173
+ if [ -f scripts/selfcheck.sh ] && ! grep -qE '^[[:space:]]*(if !? ?)?bash scripts/lane_runner_check\.sh' scripts/selfcheck.sh; then
174
+ echo "FAIL lane-runner: scripts/selfcheck.sh no longer invokes this check — the checker that"
175
+ echo " detects unrun suites is itself unrun. Restore the call in selfcheck.sh."
176
+ exit 1
177
+ fi
178
+
179
+ # ── ORG SEAM — a downstream org declares ITS OWN suites in ITS OWN file ───────────────────────
180
+ # Why this exists (requested 2026-08-13 by a downstream org fork of this harness, with evidence):
181
+ # EXEMPT and DEBT above are hardcoded arrays in a SHARED-LAYER file. A downstream fork carries
182
+ # suites this repo has never heard of (`test_rest_push_lanes.sh`, `test_company_delta_lanes.sh` …),
183
+ # and all three remedies this check offers land in files that fork does not own:
184
+ # wire into selfcheck.sh → shared layer · EXEMPT array → this file, shared layer
185
+ # DEBT array → this file, shared layer
186
+ # So the fork's only choices were "violate the sync boundary" or "live at rc=1 forever". Measured:
187
+ # 5 such suites on their first run of this check. That is not a preference — a permanently red
188
+ # check stops being read, and one of those five suites had just caught a real regression when it
189
+ # was run by hand.
190
+ #
191
+ # Shape: OPTIONAL file, absent by default. This repo ships none, so the no-op arm is the arm that
192
+ # runs here — verified by the known-pair in scripts/test_lane_runner_lanes.sh.
193
+ #
194
+ # 🟥 A MALFORMED FILE IS FAIL-CLOSED, a stale entry is advisory. Those are different failures:
195
+ # · cannot parse → the instrument did not look. Reporting "no declarations" would render
196
+ # UNMEASURED as ZERO, which is the exact family this repo keeps closing.
197
+ # · names a suite that no longer exists → hygiene, same as a stale DEBT entry above, so it warns
198
+ # with the same voice. Making the downstream rule STRICTER than the upstream
199
+ # one it mirrors would just train people to delete the file.
200
+ ORG_DECL="company/lane_declarations.yaml"
201
+ ORG_EXEMPT=(); ORG_DEBT=()
202
+ if [ -f "$ORG_DECL" ]; then
203
+ _org_out=$(python3 - "$ORG_DECL" <<'ORGPY'
204
+ import re, sys
205
+ path = sys.argv[1]
206
+ try:
207
+ txt = open(path, encoding='utf-8').read()
208
+ except OSError as e:
209
+ print(f"ERR\tcannot read {path}: {e}"); sys.exit(0)
210
+ sect = None; seen = {}
211
+ for i, ln in enumerate(txt.splitlines(), 1):
212
+ s = ln.split('#', 1)[0].rstrip()
213
+ if not s.strip():
214
+ continue
215
+ m = re.match(r'^([A-Za-z_]+):\s*$', s)
216
+ if m:
217
+ sect = m.group(1)
218
+ if sect not in ('exempt', 'debt'):
219
+ print(f"ERR\t{path}:{i}: unknown section '{sect}' (expected 'exempt' or 'debt')")
220
+ sys.exit(0)
221
+ continue
222
+ m = re.match(r'^\s+-\s+(\S+)\s*$', s)
223
+ if m:
224
+ if sect is None:
225
+ print(f"ERR\t{path}:{i}: entry before any section header"); sys.exit(0)
226
+ name = m.group(1)
227
+ if name in seen and seen[name] != sect:
228
+ print(f"ERR\t{path}:{i}: '{name}' declared both exempt and debt — ambiguous")
229
+ sys.exit(0)
230
+ seen[name] = sect
231
+ print(f"{sect}\t{name}")
232
+ continue
233
+ print(f"ERR\t{path}:{i}: unparseable line: {s.strip()[:60]}")
234
+ sys.exit(0)
235
+ ORGPY
236
+ )
237
+ # `grep -q '^ERR'` — NOT `'^ERR\t'`. In a POSIX BRE, GNU grep reads `\t` as a literal `t`
238
+ # (so the pattern becomes `^ERRt` and matches nothing) while BSD grep on macOS takes it as a tab.
239
+ # Measured 2026-08-13: this exact line passed all 11 lanes locally and failed L6/L7/L8 in CI —
240
+ # the fail-closed arm was silently dead on Linux, which is the platform that gates merges.
241
+ # Same family as the `"$DDSCAN" "` anchor divergence a sibling harness measured the same day:
242
+ # a regex that means different things on the two greps is an instrument, and an instrument that
243
+ # answers differently per platform has not measured anything.
244
+ if printf '%s' "$_org_out" | grep -q '^ERR'; then
245
+ echo "FAIL lane-runner: $ORG_DECL exists but could not be read as declarations —"
246
+ printf '%s' "$_org_out" | sed 's/^ERR[[:space:]]*/ /'
247
+ echo " An unreadable declaration file is UNMEASURED, not empty. Fix the file or delete it."
248
+ exit 2
249
+ fi
250
+ while IFS=$'\t' read -r _k _v; do
251
+ [ -z "${_v:-}" ] && continue
252
+ case "$_k" in
253
+ exempt) ORG_EXEMPT+=("$_v") ;;
254
+ debt) ORG_DEBT+=("$_v") ;;
255
+ esac
256
+ done <<< "$_org_out"
257
+ # Stale-entry warning, same voice as the DEBT hygiene warnings below.
258
+ for _n in "${ORG_EXEMPT[@]+"${ORG_EXEMPT[@]}"}" "${ORG_DEBT[@]+"${ORG_DEBT[@]}"}"; do
259
+ [ -f "scripts/$_n" ] || echo "⚠️ lane-runner: $ORG_DECL declares '$_n' but scripts/$_n does not exist (renamed/deleted?)"
260
+ done
261
+ # Visibility: an org-declared suite must never be indistinguishable from an upstream one. A
262
+ # reader who sees "0 debt" has to be able to tell whether that is this repo's zero or a fork's
263
+ # declaration absorbing its own list.
264
+ echo " lane-runner: $ORG_DECL — ${#ORG_EXEMPT[@]} exempt · ${#ORG_DEBT[@]} debt (org-declared)"
265
+ EXEMPT+=("${ORG_EXEMPT[@]+"${ORG_EXEMPT[@]}"}")
266
+ DEBT+=("${ORG_DEBT[@]+"${ORG_DEBT[@]}"}")
267
+ fi
268
+
269
+ # `"${ARR[@]+"${ARR[@]}"}"` and not the plain `"${ARR[@]}"`: on bash 3.2 (stock macOS) `set -u`
270
+ # treats an EMPTY array's expansion as an unbound variable and aborts. DEBT is now empty by design,
271
+ # which is exactly the state the plain form cannot survive — measured here 2026-08-13, and it
272
+ # aborted so early that the script still printed PASS (see the rc routing further down).
273
+ out=$(python3 - "${#EXEMPT[@]}" "${EXEMPT[@]+"${EXEMPT[@]}"}" "${DEBT[@]+"${DEBT[@]}"}" <<'PY'
274
+ import os, re, sys, glob, json
275
+
276
+ n_exempt = int(sys.argv[1])
277
+ exempt = set(sys.argv[2:2 + n_exempt])
278
+ debt = set(sys.argv[2 + n_exempt:])
279
+
280
+ # Suites: deduped by NAME. An earlier draft globbed `scripts/*_lanes.sh` and `scripts/test_*.sh`
281
+ # separately and summed them — every `test_*_lanes.sh` matched both, inflating 43 to 75. The count
282
+ # was never published; it was caught because a hand-check of one case disagreed with it
283
+ # (CLAUDE.md §Instrument-Calibration, hand-verify-one-sample). Keep this a set.
284
+ suites = sorted({os.path.basename(f) for f in glob.glob('scripts/*.sh')
285
+ if re.match(r'^(test_.*|.*_lanes)\.sh$', os.path.basename(f))})
286
+
287
+ # Runner surfaces, enumerated rather than assumed. All three were checked by hand when this file was
288
+ # written: CI runs `bash scripts/selfcheck.sh` + count_check + the plugin validators and nothing else.
289
+ SELF = os.path.basename(__file__) if '__file__' in dir() else 'lane_runner_check.sh'
290
+ runners = []
291
+ for pat in ('scripts/*.sh', 'templates/.git-hooks/*', '.github/workflows/*.yml'):
292
+ runners += [p for p in glob.glob(pat) if os.path.isfile(p)]
293
+ # This file names every DEBT and EXEMPT suite in its own arrays. Scanning itself would read each of
294
+ # those declarations as evidence the suite is run — the check would certify its own todo list as
295
+ # done. Caught by the known-negative control below on the first execution.
296
+ runners = [r for r in runners if os.path.basename(r) != 'lane_runner_check.sh']
297
+
298
+ # An invocation is `bash <path-ending-in-suite>`, not a mention of the name. The bounded `.{0,60}?`
299
+ # spans wrappers like `exec bash "$(dirname "$0")/x.sh"` (which contains a space inside the command
300
+ # substitution) without running to the end of an arbitrary line.
301
+ INVOKE = re.compile(r'\b(?:exec\s+)?(?:bash|sh)\s+.{0,60}?')
302
+
303
+ def has_runner(suite):
304
+ """A mention is not an invocation. Comments, package.json files[] entries and prose references in
305
+ tracks/ all name suites without running them — that conflation is what made the original count
306
+ wrong (75 vs the real 43).
307
+
308
+ Two measured failures shaped this predicate, both caught by the controls rather than by review:
309
+ · `\\b(bash|sh)\\b` matched the `sh` inside the `.sh` extension itself, so ANY line naming a
310
+ suite counted as running it — every suite read as WIRED, including the known-negative.
311
+ · An earlier quote-anchored regex missed `exec bash "$(dirname "$0")/x.sh"` and reported NONE
312
+ for a suite a hand-check had already confirmed was dispatched.
313
+ Both are the same class: the predicate was re-spelled instead of being pinned to a known pair."""
314
+ for r in runners:
315
+ if os.path.basename(r) == suite:
316
+ continue
317
+ try:
318
+ txt = open(r, encoding='utf-8', errors='replace').read()
319
+ except OSError:
320
+ continue
321
+ if runner_dispatches(suite, txt):
322
+ return True
323
+ return False
324
+
325
+ def runner_dispatches(suite, txt):
326
+ """The whole predicate, over ONE runner's text. Split out from has_runner so the controls below
327
+ can drive it with synthetic fixtures instead of with whatever the repo happens to look like
328
+ today — see the CONTROL block for why that mattered."""
329
+ direct = re.compile(r'\b(?:exec\s+)?(?:bash|sh)\s+.{0,60}?' + re.escape(suite))
330
+ lines = txt.split('\n')
331
+ # Does this runner invoke ANYTHING through a variable? If so, a suite named in one of its
332
+ # list constructs is plausibly dispatched by that loop.
333
+ indirect_dispatch = any(
334
+ re.search(r'\b(?:exec\s+)?(?:bash|sh)\s+"?\$', ln)
335
+ for ln in lines if not ln.strip().startswith('#'))
336
+ for line in lines:
337
+ if suite not in line or line.strip().startswith('#'):
338
+ continue
339
+ if direct.search(line):
340
+ return True
341
+ # for-list idiom, used twice in selfcheck.sh:
342
+ # for _anchor in scripts/a.sh scripts/b.sh; do ... bash "$_anchor" ... done
343
+ # for _pair in "subject|scripts/x.sh|mode" ...; do ... bash "$_anc" ... done
344
+ # The literal name is in the list; the invocation is through the variable, so the direct
345
+ # pattern above structurally cannot see it. Hand-verified 2026-08-12 for both
346
+ # test_session_close_lanes.sh and test_wizard_snippet_merge_lanes.sh — the strict
347
+ # detector called both UNWIRED and both are genuinely run.
348
+ # Gated on the file actually containing a variable dispatch, so a prose mention in a
349
+ # script that never runs anything indirectly does not get a free pass.
350
+ if indirect_dispatch and re.match(r'\s*(for\s+\w+\s+in\b|["\']?\S*\|)', line):
351
+ return True
352
+ return False
353
+
354
+ wired = {s for s in suites if has_runner(s)}
355
+
356
+ # ── Embedded --self-test dispatchers — a class the name-pattern `suites` glob cannot see ──────
357
+ # Measured 2026-08-13, the header of this file, §WHAT DEBT:0 DOES NOT MEAN: a lane suite that
358
+ # lives INSIDE its subject as a `--self-test` flag — not a separate `test_*.sh`/`*_lanes.sh` file
359
+ # — is structurally invisible to the glob above. 4 scripts carry one — chamber_witness.sh ·
360
+ # capability_registry_check.sh · digest_landing_check.sh · directional_diff_gate.sh — and none
361
+ # showed up as WIRED or UNWIRED anywhere in this report; the wiring line for the 3 that ARE wired,
362
+ # the `for _subj in ...` loop at scripts/selfcheck.sh:478, could be deleted and nothing here would
363
+ # go red. found→extend, not a new file: same idiom as `suites`/`has_runner` above — discover
364
+ # subjects, detect dispatch, report undeclared — new predicates for the shape this pattern uses.
365
+ #
366
+ # 🟥 A PARENTHESIS TRAP LIVES IN THIS SPECIFIC HEREDOC, READ BEFORE ADDING A LINE HERE.
367
+ # Measured 2026-08-14: a single heredoc-body line whose own paren count was unbalanced — one more
368
+ # close-paren than open-paren, from a regex needing a literal close-paren character — broke bash's
369
+ # parse of every line after it, on THIS file only. The reason is that the heredoc below sits inside
370
+ # a command-substitution wrapper, and that wrapper's own close-paren-matching scan turned out not
371
+ # to be fully heredoc-blind in the bash build this repo has tested. `bash -n` then failed dozens of
372
+ # lines later with an unrelated-looking error, because by then the parser believed the heredoc had
373
+ # already closed. Reproduced in isolation: a bare regex assignment needing a literal close-paren,
374
+ # spliced into this file at this exact position, alone, with nothing else added. The fix is
375
+ # structural, not "be careful" — every line added inside this heredoc must carry a matched
376
+ # open-paren and close-paren count on that same line, and a matched pair spanning two lines is
377
+ # exactly the shape that tripped this the first time. Collapse it back to one line, or spell the
378
+ # literal paren out as an escape sequence instead of a bare character, rather than splitting it
379
+ # across lines.
380
+ # A bare substring match on --self-test would also catch prose that only DISCUSSES the flag
381
+ # (measured: scripts/selfcheck.sh:482 has a comment naming it as an example of what NOT to grep
382
+ # for, which is exactly the false positive this narrower check exists to avoid). Require one of
383
+ # the two real dispatcher shapes instead: `"--self-test"` in a quoted comparison, or `--self-test)`
384
+ # as a bare case-pattern. The close-paren is built via chr — see the paren-trap note above; a
385
+ # literal `)` character on this line, however it is spelled, throws this file's parser off.
386
+ _CP = chr(41)
387
+ SELFTEST_DISPATCH_FORMS = ('"--self-test"', '--self-test' + _CP)
388
+
389
+ def _read(path):
390
+ try:
391
+ return open(path, encoding='utf-8', errors='replace').read()
392
+ except OSError:
393
+ return ''
394
+
395
+ # One line on purpose — see the paren-trap note above the SELFTEST_PAT definition.
396
+ _st_candidates = [f for f in glob.glob('scripts/*.sh') if os.path.basename(f) not in suites and os.path.basename(f) != 'lane_runner_check.sh']
397
+ _st_names = [os.path.basename(f)[:-3] for f in _st_candidates if any(_form in _read(f) for _form in SELFTEST_DISPATCH_FORMS)]
398
+ selftest_subjects = sorted(set(_st_names))
399
+
400
+ def selftest_dispatched(bare_name, txt):
401
+ """Two shapes, both real in this repo. Cross-family review (2026-08-14) caught the first draft
402
+ shipping only the second — it read scripts/selfcheck.sh:478's `_subj` for-loop but missed
403
+ :898/:933's direct `bash scripts/probe_scope_check.sh --self-test` / `bash scripts/
404
+ utterance_landing_check.sh --self-test`, so those two subjects were reported UNDECLARED while
405
+ selfcheck.sh runs them every time. This is the exact failure the header above names by cite —
406
+ a reader trusting the count over the source would have been told a false thing with confidence.
407
+
408
+ Shape 1 (direct): `bash scripts/<name>.sh ... --self-test` on one line — mirrors
409
+ runner_dispatches' direct branch, structurally simpler than the indirect case below.
410
+ Shape 2 (indirect): bare_name sits in a `for VAR in ... bare_name ...; do` loop whose body
411
+ dispatches $VAR with --self-test — mirrors the indirect-branch reasoning of runner_dispatches:
412
+ the literal name is in a list construct, the invocation runs through the loop variable, so a
413
+ direct-dispatch grep alone structurally cannot see it (scripts/selfcheck.sh:478)."""
414
+ if re.search(rf'\bbash\s+scripts/{re.escape(bare_name)}\.sh\b[^\n]*--self-test', txt):
415
+ return True
416
+ in_loop = False; loop_var = None; has_name = False
417
+ for ln in txt.split('\n'):
418
+ s = ln.strip()
419
+ m = re.match(r'for\s+(\w+)\s+in\b(.*)', s)
420
+ if m:
421
+ loop_var = m.group(1)
422
+ has_name = bool(re.search(rf'\b{re.escape(bare_name)}\b', m.group(2)))
423
+ in_loop = True
424
+ continue
425
+ if in_loop:
426
+ if has_name and '--self-test' in ln and re.search(rf'\${{?{re.escape(loop_var)}\b', ln):
427
+ return True
428
+ if s == 'done':
429
+ in_loop = False; loop_var = None; has_name = False
430
+ return False
431
+
432
+ def has_selftest_runner(bare_name):
433
+ return any(selftest_dispatched(bare_name, _read(r)) for r in runners)
434
+
435
+ selftest_wired = {s for s in selftest_subjects if has_selftest_runner(s)}
436
+ selftest_undeclared = sorted(s for s in selftest_subjects if s not in selftest_wired)
437
+
438
+ # Minimal known-pair — proportionate to the size of this addition, not the full CTL apparatus
439
+ # below, but a dead predicate must still be caught rather than trusted on read-through alone.
440
+ _ST_POS_FIXTURE = 'for _subj in alpha beta; do\n bash "scripts/$_subj.sh" --self-test\ndone\n'
441
+ _ST_NEG_FIXTURE = 'for _subj in alpha beta; do\n bash "scripts/$_subj.sh" --normal-run\ndone\n'
442
+ if not selftest_dispatched('alpha', _ST_POS_FIXTURE):
443
+ print("CONTROL_FAILED\tself-test known-positive fixture read as undispatched — detector is blind")
444
+ raise SystemExit(2)
445
+ if selftest_dispatched('alpha', _ST_NEG_FIXTURE):
446
+ print("CONTROL_FAILED\tself-test known-negative fixture with no --self-test flag read as dispatched")
447
+ raise SystemExit(2)
448
+
449
+ # ── CONTROL: the instrument must be able to see a suite known to be wired, and must NOT see one
450
+ # known to be dead. Without both arms a broken detector reports "all clean" or "all broken" and
451
+ # either reads as a verdict. [[feedback_absence_measurement_needs_control]]
452
+ CTL_POS = 'test_selfcheck_state_lanes.sh' # selfcheck.sh invokes this DIRECTLY
453
+ CTL_POS2 = 'test_session_close_lanes.sh' # selfcheck.sh:613 for-list + `bash "$_anchor"` — the
454
+ # INDIRECT arm. Pins the second detection branch: the
455
+ # strict detector called this UNWIRED and a hand-check
456
+ # showed it runs. Without this control that branch
457
+ # could silently rot back to blind.
458
+ # ── the known-NEGATIVE arm is now synthetic, and that is a repair, not a weakening ────────────
459
+ # It used to name a real suite (test_residency_closure_lanes.sh) that had zero callers. On
460
+ # 2026-08-13 the DEBT list was discharged, that suite gained a runner, and the control fired:
461
+ # "known-negative read as WIRED — the detector over-matches". The detector was fine. The CONTROL
462
+ # had been anchored to WORK NOT YET DONE, so doing the work broke the instrument. A negative
463
+ # control that lives on the todo list dies the moment the todo list is emptied — and it dies
464
+ # LOUDLY, as a false accusation against the thing it was guarding, which is the worst possible
465
+ # time to be debugging your own instrument.
466
+ # So the negative arm is driven with fixture text instead. The three fixtures are the three ways a
467
+ # name appears WITHOUT being run, all of them observed in this repo:
468
+ # · in a comment (every DEBT/EXEMPT declaration, and the pre-commit:1068 mention that fooled a
469
+ # hand-rolled probe into reporting 11 instead of 12)
470
+ # · in a manifest-style list with no dispatch anywhere in the file (package.json files[])
471
+ # · next to a `bash -n "$f"` syntax-check loop over a glob — flagged 2026-08-13 by a peer session
472
+ # as the way a DEBT count could be silently understated, since that loop touches every script
473
+ # in the tree. Pinned here so the answer stays measured rather than argued: syntax-checking a
474
+ # file is not running its lanes, and `bash -n` must never satisfy this predicate.
475
+ # 🟥 NAMED RESIDUAL — the indirect branch is a KNOWN BYPASS, and the claim is narrowed accordingly.
476
+ # Both an in-family adversarial review and an independent cross-family audit (codex/gpt-5.5) landed
477
+ # on this same hole on 2026-08-13, and the cross-family one reproduced it against the predicate
478
+ # itself: a file that contains `bash "$ANY_VAR"` ANYWHERE, plus a line of the shape
479
+ # `"x|scripts/test_future_lanes.sh|y"`, reads as WIRED even when nothing dispatches that list.
480
+ # So "an undeclared unwired suite cannot regrow" is true of a suite nobody MENTIONS and false of one
481
+ # added decoratively to an existing pair table. It is documented rather than closed because
482
+ # narrowing the branch to same-loop-body variable matching would put both real known-positives
483
+ # (selfcheck.sh:723 and :630) at risk, and this file sits exactly where "the repair is the main
484
+ # source of the next defect" has already been measured. The fixture below pins the CURRENT
485
+ # permissive behaviour so that a future narrowing is a visible, deliberate change rather than a
486
+ # silent one — it asserts what IS, not what should be.
487
+ CTL_BYPASS_FIXTURE = (
488
+ '#!/usr/bin/env bash\nbash "$UNRELATED"\n'
489
+ ' "subject|scripts/test_control_never_wired_lanes.sh|mode"\n')
490
+ CTL_NEG_NAME = 'test_control_never_wired_lanes.sh'
491
+ CTL_NEG_FIXTURES = [
492
+ ("comment-only mention",
493
+ '#!/usr/bin/env bash\nbash "$SOME_OTHER"\n# see test_control_never_wired_lanes.sh for detail\n'),
494
+ ("manifest-style list, no dispatch in file",
495
+ '{\n "files": [\n "scripts/test_control_never_wired_lanes.sh"\n ]\n}\n'),
496
+ ("bash -n glob loop + prose mention",
497
+ '#!/usr/bin/env bash\nfor f in scripts/*.sh; do bash -n "$f" || fail=1; done\n'
498
+ 'echo "coverage includes scripts/test_control_never_wired_lanes.sh"\n'),
499
+ ]
500
+ ctl = []
501
+ if CTL_POS in suites and CTL_POS not in wired:
502
+ ctl.append(f"known-positive {CTL_POS} read as UNWIRED — the direct detector is blind")
503
+ if CTL_POS2 in suites and CTL_POS2 not in wired:
504
+ ctl.append(f"known-positive {CTL_POS2} read as UNWIRED — the for-list/indirect branch is blind")
505
+ for _why, _txt in CTL_NEG_FIXTURES:
506
+ if runner_dispatches(CTL_NEG_NAME, _txt):
507
+ ctl.append(f"known-negative ({_why}) read as WIRED — the detector over-matches")
508
+ # The fixtures above drive `runner_dispatches` DIRECTLY, which leaves the layer between it and
509
+ # `has_runner` uncontrolled — runner enumeration, and in particular the self-exclusion filter that
510
+ # keeps this file out of its own scanned set. That filter is load-bearing: without it, every name
511
+ # declared in the arrays here reads as evidence that the suite runs, and the check certifies its own
512
+ # todo list as done. The old real-suite known-negative used to cover that path incidentally; the
513
+ # fixtures do not, so the loss is pinned explicitly rather than left as an unstated regression
514
+ # (raised by adversarial review, which was right that C4 was a strengthening AND a narrowing).
515
+ if any(os.path.basename(r) == 'lane_runner_check.sh' for r in runners):
516
+ ctl.append("self-exclusion filter is gone — this file is in its own runner set, so every name "
517
+ "in DEBT/EXEMPT would read as WIRED")
518
+ if not runners:
519
+ ctl.append("runner enumeration collapsed to zero — every suite would read as UNWIRED")
520
+ # And the positive arm on fixture text too, so the negative arm cannot pass by the predicate simply
521
+ # having gone blind — a detector that answers False to everything satisfies three negatives.
522
+ if not runner_dispatches(CTL_NEG_NAME, 'bash scripts/test_control_never_wired_lanes.sh\n'):
523
+ ctl.append("fixture known-positive read as UNWIRED — the predicate answers False to everything")
524
+ # Pins the documented bypass above. If this ever stops holding, someone narrowed the indirect branch
525
+ # — which may well be the right move, but it must be a decision, and both real known-positives above
526
+ # have to be re-confirmed at the same time. Update this line and the residual note together.
527
+ if not runner_dispatches(CTL_NEG_NAME, CTL_BYPASS_FIXTURE):
528
+ ctl.append("the documented indirect-branch bypass no longer reproduces — the predicate was "
529
+ "narrowed. That is not a failure, but the residual note above is now stale and the "
530
+ "two real for-list known-positives must be re-verified before removing this check.")
531
+ if ctl:
532
+ print("CONTROL_FAILED\t" + " · ".join(ctl))
533
+ raise SystemExit(2)
534
+
535
+ if not suites:
536
+ print("EXTRACTOR_BROKE\tzero lane suites found under scripts/")
537
+ raise SystemExit(2)
538
+
539
+ undeclared = sorted(s for s in suites if s not in wired and s not in exempt and s not in debt)
540
+ # A DEBT entry that has since gained a runner is resolved — say so, so the list shrinks by evidence
541
+ # rather than by someone remembering. Same shape as package_coverage_check.sh's STALE report.
542
+ resolved = sorted(s for s in debt if s in wired)
543
+ # A DEBT entry that no longer exists at all (renamed/deleted) is also stale.
544
+ gone = sorted(s for s in debt if s not in suites)
545
+
546
+ print(f"COUNTS\t{len(suites)}\t{len(wired)}\t{len(exempt)}\t{len(debt)}")
547
+ for s in undeclared:
548
+ print(f"UNDECLARED\t{s}")
549
+ for s in resolved:
550
+ print(f"RESOLVED\t{s}")
551
+ for s in gone:
552
+ print(f"GONE\t{s}")
553
+ print(f"SELFTEST_COUNTS\t{len(selftest_subjects)}\t{len(selftest_wired)}")
554
+ for s in selftest_undeclared:
555
+ print(f"SELFTEST_UNDECLARED\t{s}")
556
+ PY
557
+ )
558
+ rc=$?
559
+
560
+ if [ "$rc" -eq 2 ]; then
561
+ echo "FAIL lane-runner: the instrument broke, it did not pass"
562
+ printf '%s\n' "$out" | sed 's/^/ /'
563
+ exit 1
564
+ fi
565
+
566
+ # ANY other non-zero is also the instrument failing, and it used to fall straight through to the
567
+ # PASS line below. Measured 2026-08-13, on this file, by the change that emptied DEBT: under
568
+ # `set -u` on bash 3.2 (stock macOS) an EMPTY array expanded with "${DEBT[@]}" is an UNBOUND
569
+ # VARIABLE, so the heredoc never ran, `out` was empty, every count parsed as 0 via the `${1:-0}`
570
+ # defaults — and the script printed `PASS lane-runner: 0 suites`. A dead instrument reported a
571
+ # clean tree. Only rc==2 was handled because only rc==2 had ever been produced deliberately;
572
+ # everything else was assumed impossible rather than routed. That assumption is the defect class
573
+ # this whole file exists to catch, reproduced inside the catcher.
574
+ if [ "$rc" -ne 0 ]; then
575
+ echo "FAIL lane-runner: the scan exited $rc — the instrument did not complete, so this run"
576
+ echo " measured nothing. It is not a pass. (An empty result set is NOT an empty todo list.)"
577
+ printf '%s\n' "$out" | head -5 | sed 's/^/ /'
578
+ exit 1
579
+ fi
580
+
581
+ COUNTS=$(printf '%s\n' "$out" | awk -F'\t' '$1=="COUNTS"{print $2" "$3" "$4" "$5}')
582
+ set -- $COUNTS
583
+ TOTAL="${1:-0}"; WIRED="${2:-0}"; N_EXEMPT="${3:-0}"; N_DEBT="${4:-0}"
584
+
585
+ UNDECLARED=$(printf '%s\n' "$out" | awk -F'\t' '$1=="UNDECLARED"{print $2}')
586
+ RESOLVED=$(printf '%s\n' "$out" | awk -F'\t' '$1=="RESOLVED"{print $2}')
587
+ GONE=$(printf '%s\n' "$out" | awk -F'\t' '$1=="GONE"{print $2}')
588
+
589
+ if [ -n "$UNDECLARED" ]; then
590
+ echo "FAIL lane-runner: lane suite(s) with no runner and no declaration:"
591
+ printf '%s\n' "$UNDECLARED" | sed 's/^/ /'
592
+ echo " A suite nothing executes is prose. Fix by ONE of:"
593
+ echo " · wire it into scripts/selfcheck.sh (the usual answer)"
594
+ echo " · add it to EXEMPT here WITH the reason it must not be auto-run"
595
+ echo " · add it to DEBT here if it is known-unwired work you are deferring"
596
+ exit 1
597
+ fi
598
+
599
+ # Advisory, never blocking — a stale DEBT entry is hygiene, and converting a clean run into a red
600
+ # gate over bookkeeping is how the check stops being read.
601
+ if [ -n "$RESOLVED" ]; then
602
+ echo "⚠️ lane-runner: DEBT entry(ies) now have a runner — remove them from DEBT:"
603
+ printf '%s\n' "$RESOLVED" | sed 's/^/ /'
604
+ fi
605
+ if [ -n "$GONE" ]; then
606
+ echo "⚠️ lane-runner: DEBT entry(ies) no longer exist (renamed/deleted?) — remove them:"
607
+ printf '%s\n' "$GONE" | sed 's/^/ /'
608
+ fi
609
+
610
+ if [ "$N_DEBT" -gt 0 ]; then
611
+ echo "⚠️ lane-runner: ${N_DEBT} suite(s) still have NO runner (declared debt — see DEBT for the"
612
+ echo " per-entry reason and the date each was measured; they are not all from one measurement)."
613
+ echo " They are shipped or present but never execute — see DEBT in this file. This number must"
614
+ echo " only go down; a new one fails the check rather than joining the list silently."
615
+ fi
616
+
617
+ SELFTEST_COUNTS=$(printf '%s\n' "$out" | awk -F'\t' '$1=="SELFTEST_COUNTS"{print $2" "$3}')
618
+ set -- $SELFTEST_COUNTS
619
+ ST_TOTAL="${1:-0}"; ST_WIRED="${2:-0}"
620
+ SELFTEST_UNDECLARED=$(printf '%s\n' "$out" | awk -F'\t' '$1=="SELFTEST_UNDECLARED"{print $2}')
621
+ ST_UNDECLARED_N=0
622
+ if [ -n "$SELFTEST_UNDECLARED" ]; then
623
+ ST_UNDECLARED_N=$(printf '%s\n' "$SELFTEST_UNDECLARED" | wc -l | tr -d ' ')
624
+ # Advisory, not blocking — these are pre-existing (measured 2026-08-13, before this check saw
625
+ # them at all), not something introduced by whatever change is running this check right now.
626
+ # Same DEBT philosophy as above: loudly counted, never silent, must only go down from here.
627
+ echo "⚠️ lane-runner: ${ST_UNDECLARED_N} embedded --self-test subject(s) with no --self-test"
628
+ echo " dispatcher anywhere (self-test code exists, nothing calls it — see this file's own"
629
+ echo " §Embedded --self-test comment for why the suites glob above cannot see this class):"
630
+ printf '%s\n' "$SELFTEST_UNDECLARED" | sed 's/^/ /'
631
+ echo " Fix by wiring \`bash scripts/<name>.sh --self-test\` into scripts/selfcheck.sh's"
632
+ echo " _subj for-loop (scripts/selfcheck.sh:478), same shape as the 3 already there."
633
+ fi
634
+
635
+ echo "PASS lane-runner: ${TOTAL} suites — ${WIRED} wired · ${N_EXEMPT} exempt · ${N_DEBT} declared debt" \
636
+ "· self-test: ${ST_WIRED}/${ST_TOTAL} wired"
637
+ exit 0