@chrono-meta/fh-gate 1.4.97 β 1.4.99
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.
- package/.claude-plugin/marketplace.json +2 -2
- package/CATALOG.md +19 -0
- package/CHEATSHEET.md +9 -1
- package/CLAUDE.md +28 -2
- package/README.ja.md +229 -42
- package/README.ko.md +241 -45
- package/README.md +168 -31
- package/README.zh.md +219 -40
- package/docs/OUTPUT_EVIDENCE.md +21 -12
- package/docs/pillars.svg +3 -7
- package/knowledge/shared/harness-core/fh_ecosystem_positioning.md +2 -0
- package/knowledge/shared/harness-core/fh_global_positioning_and_distribution_roadmap.md +136 -0
- package/knowledge/shared/harness-core/fh_three_layer_canon.md +20 -0
- package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +215 -2
- package/knowledge/shared/harness-core/ship_readiness_gate.md +112 -0
- package/knowledge/shared/learnings/subagent_invocations_log.yaml +65 -0
- package/package.json +5 -1
- package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-commons/skills/ko-tech-writer/SKILL.md +63 -12
- package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/CHANGELOG.md +166 -0
- package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +30 -0
- package/scripts/consent_registry_check.sh +124 -1
- package/scripts/degrade_direction_scan.sh +10 -1
- package/scripts/digest_landing_check.sh +20 -4
- package/scripts/fh_node_check.sh +128 -1
- package/scripts/fh_session_load.sh +22 -2
- package/scripts/frontier_digest_autopilot.sh +229 -0
- package/scripts/lane_runner_check.sh +294 -26
- package/scripts/package_coverage_check.sh +17 -0
- package/scripts/postinstall_notice.js +34 -0
- package/scripts/selfcheck.sh +183 -5
- package/scripts/test_consent_registry.sh +99 -0
- package/scripts/test_degrade_scan_shell_probes.sh +75 -0
- package/scripts/test_field_canon_lanes.sh +29 -5
- package/scripts/test_lane_runner_lanes.sh +295 -0
- package/scripts/test_node_check_lanes.sh +217 -0
- package/scripts/test_selfcheck_state_lanes.sh +61 -0
- package/scripts/test_stale_clone_guard_lanes.sh +21 -7
- package/scripts/test_version_lockstep_lanes.sh +62 -0
- package/scripts/version_lockstep_check.sh +143 -1
- package/templates/.git-hooks/pre-commit +22 -1
- package/templates/consent_classes.yaml.example +30 -0
- package/templates/degrade_direction_scan.sh +10 -1
|
@@ -121,30 +121,37 @@ EXEMPT=(
|
|
|
121
121
|
# 11 not 12), and test_marker_floor_lanes.sh calibrates pre-commit's live validate_marker_floor().
|
|
122
122
|
# Both are wired now.
|
|
123
123
|
#
|
|
124
|
-
#
|
|
125
|
-
#
|
|
126
|
-
#
|
|
127
|
-
#
|
|
128
|
-
#
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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=()
|
|
148
155
|
|
|
149
156
|
if [ "${1:-}" = "--list-debt" ]; then
|
|
150
157
|
# Same bash-3.2 empty-array guard as the scan call below. This site was MISSED when that one was
|
|
@@ -169,6 +176,96 @@ if [ -f scripts/selfcheck.sh ] && ! grep -qE '^[[:space:]]*(if !? ?)?bash script
|
|
|
169
176
|
exit 1
|
|
170
177
|
fi
|
|
171
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
|
+
|
|
172
269
|
# `"${ARR[@]+"${ARR[@]}"}"` and not the plain `"${ARR[@]}"`: on bash 3.2 (stock macOS) `set -u`
|
|
173
270
|
# treats an EMPTY array's expansion as an unbound variable and aborts. DEBT is now empty by design,
|
|
174
271
|
# which is exactly the state the plain form cannot survive β measured here 2026-08-13, and it
|
|
@@ -256,11 +353,150 @@ def runner_dispatches(suite, txt):
|
|
|
256
353
|
|
|
257
354
|
wired = {s for s in suites if has_runner(s)}
|
|
258
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. When this block was written, 4 such scripts were
|
|
360
|
+
# known and none showed up as WIRED or UNWIRED anywhere in this report.
|
|
361
|
+
# β οΈ Do not read that 4 as a current figure, and do not re-list the names here. The set is
|
|
362
|
+
# DISCOVERED at run time by `_st_candidates` below and the live figure is printed in this file's
|
|
363
|
+
# own SELFTEST_COUNTS line, so a hand-maintained example list beside a self-discovering scan is a
|
|
364
|
+
# second source of truth that goes stale in silence. That is not hypothetical: the previous
|
|
365
|
+
# version of this comment named a 4th example the code no longer agreed with, and the session that
|
|
366
|
+
# noticed had to trace the discrepancy to ground by hand.
|
|
367
|
+
# The wiring line for the subjects that ARE wired,
|
|
368
|
+
# the `for _subj in ...` loop in scripts/selfcheck.sh, could be deleted and nothing here would
|
|
369
|
+
# go red. foundβextend, not a new file: same idiom as `suites`/`has_runner` above β discover
|
|
370
|
+
# subjects, detect dispatch, report undeclared β new predicates for the shape this pattern uses.
|
|
371
|
+
#
|
|
372
|
+
# π₯ A PARENTHESIS TRAP LIVES IN THIS SPECIFIC HEREDOC, READ BEFORE ADDING A LINE HERE.
|
|
373
|
+
# Measured 2026-08-14: a single heredoc-body line whose own paren count was unbalanced β one more
|
|
374
|
+
# close-paren than open-paren, from a regex needing a literal close-paren character β broke bash's
|
|
375
|
+
# parse of every line after it, on THIS file only. The reason is that the heredoc below sits inside
|
|
376
|
+
# a command-substitution wrapper, and that wrapper's own close-paren-matching scan turned out not
|
|
377
|
+
# to be fully heredoc-blind in the bash build this repo has tested. `bash -n` then failed dozens of
|
|
378
|
+
# lines later with an unrelated-looking error, because by then the parser believed the heredoc had
|
|
379
|
+
# already closed. Reproduced in isolation: a bare regex assignment needing a literal close-paren,
|
|
380
|
+
# spliced into this file at this exact position, alone, with nothing else added. The fix is
|
|
381
|
+
# structural, not "be careful" β every line added inside this heredoc must carry a matched
|
|
382
|
+
# open-paren and close-paren count on that same line, and a matched pair spanning two lines is
|
|
383
|
+
# exactly the shape that tripped this the first time. Collapse it back to one line, or spell the
|
|
384
|
+
# literal paren out as an escape sequence instead of a bare character, rather than splitting it
|
|
385
|
+
# across lines.
|
|
386
|
+
# A bare substring match on --self-test would also catch prose that only DISCUSSES the flag
|
|
387
|
+
# (measured: scripts/selfcheck.sh's `_subj` loop carries a comment naming it as what NOT to grep
|
|
388
|
+
# for, which is exactly the false positive this narrower check exists to avoid). Require one of
|
|
389
|
+
# the two real dispatcher shapes instead: `"--self-test"` in a quoted comparison, or `--self-test)`
|
|
390
|
+
# as a bare case-pattern. The close-paren is built via chr β see the paren-trap note above; a
|
|
391
|
+
# literal `)` character on this line, however it is spelled, throws this file's parser off.
|
|
392
|
+
_CP = chr(41)
|
|
393
|
+
SELFTEST_DISPATCH_FORMS = ('"--self-test"', '--self-test' + _CP)
|
|
394
|
+
|
|
395
|
+
def _read(path):
|
|
396
|
+
try:
|
|
397
|
+
return open(path, encoding='utf-8', errors='replace').read()
|
|
398
|
+
except OSError:
|
|
399
|
+
return ''
|
|
400
|
+
|
|
401
|
+
# π₯ NAMED RESIDUALS in the self-test branch β two asymmetries the 2026-08-15 repair did NOT close,
|
|
402
|
+
# written here because this file names its other residuals and silence would read as coverage.
|
|
403
|
+
# 1. Comment-stripping is LINE-PREFIX only. A dispatch line living inside a heredoc body or
|
|
404
|
+
# inside an echoed string is not a comment and still counts as a live caller. Symmetric with
|
|
405
|
+
# runner_dispatches, which has the same limit β so "symmetry restored" is true, and the shared
|
|
406
|
+
# blind spot survives in both. In-tree example of the shape: the heredoc fixtures in
|
|
407
|
+
# scripts/test_lane_runner_lanes.sh. Harmless today only because those name a fixture subject.
|
|
408
|
+
# 2. Subject DISCOVERY below still reads comments. `_st_names` matches the raw dispatcher forms
|
|
409
|
+
# anywhere in the file, so a comment that merely mentions the flag can enrol a script as a
|
|
410
|
+
# self-test subject. Real case: chamber_witness.sh carries the flag inside a usage comment.
|
|
411
|
+
# Harmless today because that file also has a real dispatcher β but it means "a mention is not
|
|
412
|
+
# a declaration" is enforced on the RUNNER side and not on the SUBJECT side.
|
|
413
|
+
# Neither is mechanized: closing them means parsing shell rather than scanning lines, which is the
|
|
414
|
+
# Grep-Collision Treadmill this repo has already logged. Fix on the first case that actually bites.
|
|
415
|
+
#
|
|
416
|
+
# One line on purpose β see the paren-trap note above the SELFTEST_PAT definition.
|
|
417
|
+
_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']
|
|
418
|
+
_st_names = [os.path.basename(f)[:-3] for f in _st_candidates if any(_form in _read(f) for _form in SELFTEST_DISPATCH_FORMS)]
|
|
419
|
+
selftest_subjects = sorted(set(_st_names))
|
|
420
|
+
|
|
421
|
+
def selftest_dispatched(bare_name, txt):
|
|
422
|
+
"""Two shapes, both real in this repo. Cross-family review (2026-08-14) caught the first draft
|
|
423
|
+
shipping only the second β it read scripts/selfcheck.sh's `_subj` for-loop but missed that
|
|
424
|
+
file's direct `bash scripts/probe_scope_check.sh --self-test` / `bash scripts/
|
|
425
|
+
utterance_landing_check.sh --self-test`, so those two subjects were reported UNDECLARED while
|
|
426
|
+
selfcheck.sh runs them every time. This is the exact failure the header above names by cite β
|
|
427
|
+
a reader trusting the count over the source would have been told a false thing with confidence.
|
|
428
|
+
|
|
429
|
+
Shape 1 (direct): `bash scripts/<name>.sh ... --self-test` on one line β mirrors
|
|
430
|
+
runner_dispatches' direct branch, structurally simpler than the indirect case below.
|
|
431
|
+
Shape 2 (indirect): bare_name sits in a `for VAR in ... bare_name ...; do` loop whose body
|
|
432
|
+
dispatches $VAR with --self-test β mirrors the indirect-branch reasoning of runner_dispatches:
|
|
433
|
+
the literal name is in a list construct, the invocation runs through the loop variable, so a
|
|
434
|
+
direct-dispatch grep alone structurally cannot see it (selfcheck.sh's `for _subj in ...` loop).
|
|
435
|
+
|
|
436
|
+
Comment lines are dropped first, exactly as runner_dispatches does for ordinary suites: "a
|
|
437
|
+
mention is not an invocation" applies identically here, and until 2026-08-15 it was enforced in
|
|
438
|
+
only one of the two predicates. Measured: a `# bash scripts/x.sh --self-test` usage line
|
|
439
|
+
matched the direct regex and certified a subject nothing ran."""
|
|
440
|
+
_body = '\n'.join(_l for _l in txt.split('\n') if not _l.strip().startswith('#'))
|
|
441
|
+
if re.search(rf'\bbash\s+scripts/{re.escape(bare_name)}\.sh\b[^\n]*--self-test', _body):
|
|
442
|
+
return True
|
|
443
|
+
in_loop = False; loop_var = None; has_name = False
|
|
444
|
+
for ln in _body.split('\n'):
|
|
445
|
+
s = ln.strip()
|
|
446
|
+
m = re.match(r'for\s+(\w+)\s+in\b(.*)', s)
|
|
447
|
+
if m:
|
|
448
|
+
loop_var = m.group(1)
|
|
449
|
+
has_name = bool(re.search(rf'\b{re.escape(bare_name)}\b', m.group(2)))
|
|
450
|
+
in_loop = True
|
|
451
|
+
continue
|
|
452
|
+
if in_loop:
|
|
453
|
+
if has_name and '--self-test' in ln and re.search(rf'\${{?{re.escape(loop_var)}\b', ln):
|
|
454
|
+
return True
|
|
455
|
+
if s == 'done':
|
|
456
|
+
in_loop = False; loop_var = None; has_name = False
|
|
457
|
+
return False
|
|
458
|
+
|
|
459
|
+
def has_selftest_runner(bare_name):
|
|
460
|
+
# A subject does not wire itself. has_runner() has skipped a suite's own file since this file
|
|
461
|
+
# was written; this branch did not, and the asymmetry was invisible because nothing exercised
|
|
462
|
+
# it β the only in-file control drove selftest_dispatched with synthetic text, never the
|
|
463
|
+
# runner set.
|
|
464
|
+
# β οΈ Attribution, kept precise because a cross-family round caught it being loose: the 2026-08-15
|
|
465
|
+
# incident β directional_diff_gate.sh dispatched by nothing while the report said 10/10 β was
|
|
466
|
+
# caused by the COMMENT half, and comment-stripping alone closes it. Every self-reference in
|
|
467
|
+
# this tree at that date was a `#` line; a non-comment one existed nowhere. So this guard is
|
|
468
|
+
# SYMMETRY, not a repair of an observed case: it covers the shape where a subject names its own
|
|
469
|
+
# dispatch in live code, such as a usage helper. That shape is pinned by lane L16, which was
|
|
470
|
+
# added at the same time and for the same reason β without it this line is a repair no control
|
|
471
|
+
# drives, which is the failure mode this file exists to name.
|
|
472
|
+
_own = bare_name + '.sh'
|
|
473
|
+
return any(selftest_dispatched(bare_name, _read(r)) for r in runners if os.path.basename(r) != _own)
|
|
474
|
+
|
|
475
|
+
selftest_wired = {s for s in selftest_subjects if has_selftest_runner(s)}
|
|
476
|
+
selftest_undeclared = sorted(s for s in selftest_subjects if s not in selftest_wired)
|
|
477
|
+
|
|
478
|
+
# Minimal known-pair β proportionate to the size of this addition, not the full CTL apparatus
|
|
479
|
+
# below, but a dead predicate must still be caught rather than trusted on read-through alone.
|
|
480
|
+
_ST_POS_FIXTURE = 'for _subj in alpha beta; do\n bash "scripts/$_subj.sh" --self-test\ndone\n'
|
|
481
|
+
_ST_NEG_FIXTURE = 'for _subj in alpha beta; do\n bash "scripts/$_subj.sh" --normal-run\ndone\n'
|
|
482
|
+
if not selftest_dispatched('alpha', _ST_POS_FIXTURE):
|
|
483
|
+
print("CONTROL_FAILED\tself-test known-positive fixture read as undispatched β detector is blind")
|
|
484
|
+
raise SystemExit(2)
|
|
485
|
+
if selftest_dispatched('alpha', _ST_NEG_FIXTURE):
|
|
486
|
+
print("CONTROL_FAILED\tself-test known-negative fixture with no --self-test flag read as dispatched")
|
|
487
|
+
raise SystemExit(2)
|
|
488
|
+
# Second known-negative, added 2026-08-15 with the comment-skip repair. Without it the repair is a
|
|
489
|
+
# line of code no control drives β the failure mode this whole file exists to name.
|
|
490
|
+
_ST_CMT_FIXTURE = '# bash scripts/alpha.sh --self-test # usage example, not a caller\n'
|
|
491
|
+
if selftest_dispatched('alpha', _ST_CMT_FIXTURE):
|
|
492
|
+
print("CONTROL_FAILED\tself-test commented usage line read as a live dispatcher")
|
|
493
|
+
raise SystemExit(2)
|
|
494
|
+
|
|
259
495
|
# ββ CONTROL: the instrument must be able to see a suite known to be wired, and must NOT see one
|
|
260
496
|
# known to be dead. Without both arms a broken detector reports "all clean" or "all broken" and
|
|
261
497
|
# either reads as a verdict. [[feedback_absence_measurement_needs_control]]
|
|
262
498
|
CTL_POS = 'test_selfcheck_state_lanes.sh' # selfcheck.sh invokes this DIRECTLY
|
|
263
|
-
CTL_POS2 = 'test_session_close_lanes.sh' # selfcheck.sh
|
|
499
|
+
CTL_POS2 = 'test_session_close_lanes.sh' # selfcheck.sh's `_anchor` for-list + `bash "$_anchor"` β the
|
|
264
500
|
# INDIRECT arm. Pins the second detection branch: the
|
|
265
501
|
# strict detector called this UNWIRED and a hand-check
|
|
266
502
|
# showed it runs. Without this control that branch
|
|
@@ -360,6 +596,9 @@ for s in resolved:
|
|
|
360
596
|
print(f"RESOLVED\t{s}")
|
|
361
597
|
for s in gone:
|
|
362
598
|
print(f"GONE\t{s}")
|
|
599
|
+
print(f"SELFTEST_COUNTS\t{len(selftest_subjects)}\t{len(selftest_wired)}")
|
|
600
|
+
for s in selftest_undeclared:
|
|
601
|
+
print(f"SELFTEST_UNDECLARED\t{s}")
|
|
363
602
|
PY
|
|
364
603
|
)
|
|
365
604
|
rc=$?
|
|
@@ -421,5 +660,34 @@ if [ "$N_DEBT" -gt 0 ]; then
|
|
|
421
660
|
echo " only go down; a new one fails the check rather than joining the list silently."
|
|
422
661
|
fi
|
|
423
662
|
|
|
424
|
-
|
|
663
|
+
SELFTEST_COUNTS=$(printf '%s\n' "$out" | awk -F'\t' '$1=="SELFTEST_COUNTS"{print $2" "$3}')
|
|
664
|
+
set -- $SELFTEST_COUNTS
|
|
665
|
+
ST_TOTAL="${1:-0}"; ST_WIRED="${2:-0}"
|
|
666
|
+
SELFTEST_UNDECLARED=$(printf '%s\n' "$out" | awk -F'\t' '$1=="SELFTEST_UNDECLARED"{print $2}')
|
|
667
|
+
ST_UNDECLARED_N=0
|
|
668
|
+
if [ -n "$SELFTEST_UNDECLARED" ]; then
|
|
669
|
+
ST_UNDECLARED_N=$(printf '%s\n' "$SELFTEST_UNDECLARED" | wc -l | tr -d ' ')
|
|
670
|
+
# Advisory, not blocking β these are pre-existing (measured 2026-08-13, before this check saw
|
|
671
|
+
# them at all), not something introduced by whatever change is running this check right now.
|
|
672
|
+
# Same DEBT philosophy as above: loudly counted, never silent, must only go down from here.
|
|
673
|
+
echo "β οΈ lane-runner: ${ST_UNDECLARED_N} embedded --self-test subject(s) with no --self-test"
|
|
674
|
+
echo " dispatcher anywhere (self-test code exists, nothing calls it β see this file's own"
|
|
675
|
+
echo " Β§Embedded --self-test comment for why the suites glob above cannot see this class):"
|
|
676
|
+
printf '%s\n' "$SELFTEST_UNDECLARED" | sed 's/^/ /'
|
|
677
|
+
echo " Fix by wiring \`bash scripts/<name>.sh --self-test\` into scripts/selfcheck.sh, by"
|
|
678
|
+
echo " ONE of its two shapes β and the choice is decided by the subject's own output, not"
|
|
679
|
+
echo " by which is shorter. The \`for _subj in ...\` loop gates on the substring μΊλ¦¬λΈλ μ΄μ
;"
|
|
680
|
+
echo " a subject whose terminal verdict is worded any other way goes red there for a reason"
|
|
681
|
+
echo " that has nothing to do with its lanes β capability_registry_check was added to that"
|
|
682
|
+
echo " loop and reverted for exactly this. Such a subject takes the second shape: its own"
|
|
683
|
+
echo " direct-dispatch block, as capability_registry_check.sh and relay_channel.sh do there."
|
|
684
|
+
echo " π₯ In that second shape do NOT gate on the exit code alone. Measured 2026-08-15 on"
|
|
685
|
+
echo " directional_diff_gate: a suite whose lanes were all deleted still printed a PASS"
|
|
686
|
+
echo " verdict and exited 0, and a usage banner carrying the same words satisfied a"
|
|
687
|
+
echo " substring match. Require the terminal verdict AS A WHOLE LINE, with a non-zero count"
|
|
688
|
+
echo " in it, or exit 0 will certify a suite that ran nothing."
|
|
689
|
+
fi
|
|
690
|
+
|
|
691
|
+
echo "PASS lane-runner: ${TOTAL} suites β ${WIRED} wired Β· ${N_EXEMPT} exempt Β· ${N_DEBT} declared debt" \
|
|
692
|
+
"Β· self-test: ${ST_WIRED}/${ST_TOTAL} wired"
|
|
425
693
|
exit 0
|
|
@@ -36,6 +36,8 @@ cd "$REPO_ROOT" || exit 1
|
|
|
36
36
|
# a file of that name to exercise the LOW allowlist β not a pointer to this file at all.)
|
|
37
37
|
# scripts/sync_guard_check.sh β anchor for that same operator-private mirror sync;
|
|
38
38
|
# no shipped hook invokes it.
|
|
39
|
+
# scripts/sync_to_be_lanes.sh β forward-path lane suite for sync-to-be.sh, itself
|
|
40
|
+
# ACCEPTED_ABSENT above; added 2026-08-14, pmh-dev#69.
|
|
39
41
|
ACCEPTED_ABSENT=(
|
|
40
42
|
".claude/registry/LOCAL_SKILL_REGISTRY.md"
|
|
41
43
|
# An INSTALL DESTINATION the user creates (`cp templates/local_fh_context.md
|
|
@@ -57,6 +59,21 @@ ACCEPTED_ABSENT=(
|
|
|
57
59
|
# anchor but guards on the subject's presence, so package mode SKIPs rather than falling through.
|
|
58
60
|
"scripts/sync-from-be.sh"
|
|
59
61
|
"scripts/sync_from_be_lanes.sh"
|
|
62
|
+
# Forward path's own lane suite (added 2026-08-14, pmh-dev#69). Same reason as its return-path
|
|
63
|
+
# sibling directly above: it exercises scripts/sync-to-be.sh, itself ACCEPTED_ABSENT β a lane
|
|
64
|
+
# suite for a script that never ships has nothing to verify on a consumer's machine either.
|
|
65
|
+
"scripts/sync_to_be_lanes.sh"
|
|
66
|
+
# Shared identity resolver for the two transports directly above (added to files[] by #368,
|
|
67
|
+
# REMOVED from it 2026-08-15). It was the odd one out: its own callers β sync-to-be.sh and
|
|
68
|
+
# sync-from-be.sh β are ACCEPTED_ABSENT here for a reason that applies to it verbatim (the
|
|
69
|
+
# namespacing it computes only means anything on a machine that HAS the operator's companion
|
|
70
|
+
# store), yet it alone was listed for shipping. The pre-publish confidentiality scan is what
|
|
71
|
+
# surfaced the misclassification: it carries the companion store's own directory vocabulary,
|
|
72
|
+
# which would have reached the registry for the first time in 1.4.98. The one shipped caller,
|
|
73
|
+
# fh_session_load.sh, sources it behind `[ -f ]` and documents its own degrade ("an unresolved
|
|
74
|
+
# identity degrades to the historical unsuffixed tracks-meta rather than erroring β that is the
|
|
75
|
+
# pre-fix behavior, not a new failure mode"), so a consumer loses nothing that consumer ever had.
|
|
76
|
+
"scripts/fh_hub_identity.sh"
|
|
60
77
|
# ββ The three lane suites selfcheck.sh's DEBT-12 pair-loop names but does not ship ββββββββββββ
|
|
61
78
|
# Added 2026-08-13, and the way they got here is the point: this check CAUGHT them. Before that
|
|
62
79
|
# loop existed, these names lived in lane_runner_check.sh's DEBT array as bare basenames
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// postinstall_notice.js β one-line, stderr-only, CI-silent install notice.
|
|
4
|
+
//
|
|
5
|
+
// WHY: 16k+ npm downloads (measured live, 2026-08-15) vs 7 GitHub stars on the same day β
|
|
6
|
+
// people are using fh-gate and have no reason to ever see the GitHub repo, since `npx
|
|
7
|
+
// @chrono-meta/fh-gate` never surfaces it. This closes that gap at the one moment every
|
|
8
|
+
// installer passes through, without touching fh-gate.js's own stdout (that IS the CI
|
|
9
|
+
// contract per its own header comment β this notice must never risk polluting it).
|
|
10
|
+
//
|
|
11
|
+
// WHY stderr, not stdout: npm's own install-time output already goes to stderr by
|
|
12
|
+
// convention (npm's progress/warnings), and more importantly this keeps it structurally
|
|
13
|
+
// impossible for the notice to ever be mistaken for gate output β a script piping
|
|
14
|
+
// `npx @chrono-meta/fh-gate`'s stdout for FH_GATE_VERDICT: parsing never sees this line,
|
|
15
|
+
// even if this file were somehow invoked in the same process (it isn't β separate
|
|
16
|
+
// lifecycle script, separate process).
|
|
17
|
+
//
|
|
18
|
+
// WHY CI-silent: postinstall banners are a known source of npm-ecosystem noise complaints,
|
|
19
|
+
// and this package explicitly markets itself for CI use (CHEATSHEET.md Β§9.5). A line that
|
|
20
|
+
// reprints on every CI run install is exactly the annoyance that gets a package reported,
|
|
21
|
+
// not starred. Skip whenever a CI environment is plausible OR the operator opts out.
|
|
22
|
+
if (process.env.CI || process.env.FH_NO_BANNER || process.env.CONTINUOUS_INTEGRATION) {
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
process.stderr.write(
|
|
27
|
+
'\nβ forge-harness (fh-gate) β if this is useful, a star helps others find it:\n' +
|
|
28
|
+
' https://github.com/chrono-meta/forge-harness\n' +
|
|
29
|
+
' (set FH_NO_BANNER=1 to silence this message)\n\n'
|
|
30
|
+
);
|
|
31
|
+
} catch (_) {
|
|
32
|
+
// Never fail an install over a courtesy message.
|
|
33
|
+
}
|
|
34
|
+
process.exit(0);
|