@chrono-meta/fh-gate 1.4.89 → 1.4.90

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 (35) hide show
  1. package/.claude/judgment_circuits.txt +14 -0
  2. package/.claude/rules/fh_4axis_gate.md +7 -0
  3. package/.claude-plugin/marketplace.json +2 -2
  4. package/AGENTS.md +25 -0
  5. package/CLAUDE.md +179 -12
  6. package/knowledge/shared/harness-core/dispatch_conditional_prohibition.md +105 -0
  7. package/knowledge/shared/harness-core/fh_three_layer_canon.md +165 -0
  8. package/knowledge/shared/harness-core/harness_incubator_doctrine.md +100 -0
  9. package/knowledge/shared/harness-core/onboarding_acceleration_autopilot.md +3 -1
  10. package/knowledge/shared/harness-core/ship_readiness_gate.md +181 -13
  11. package/knowledge/shared/learnings/subagent_invocations_log.yaml +528 -0
  12. package/package.json +16 -2
  13. package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
  14. package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
  15. package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +56 -8
  16. package/plugins/fh-meta/skills/install-wizard/SKILL.md +33 -0
  17. package/plugins/fh-meta/skills/install-wizard/SKILL_detail.md +104 -15
  18. package/scripts/chamber_run.sh +64 -2
  19. package/scripts/chamber_witness.sh +439 -0
  20. package/scripts/compaction_probe.sh +456 -0
  21. package/scripts/digest_landing_check.sh +385 -0
  22. package/scripts/directional_diff_gate.sh +459 -0
  23. package/scripts/judgment_circuit_lint.sh +239 -0
  24. package/scripts/novelty_claim_check.sh +193 -0
  25. package/scripts/relay_channel.sh +645 -0
  26. package/scripts/reviewer_capability_corpus.tsv +124 -0
  27. package/scripts/selfcheck.sh +66 -0
  28. package/scripts/test_marker_crossfamily_lanes.sh +132 -0
  29. package/scripts/test_marker_floor_lanes.sh +9 -8
  30. package/scripts/test_relay_channel_lanes.sh +583 -0
  31. package/scripts/test_reviewer_capability_conformance.sh +173 -0
  32. package/scripts/test_wizard_snippet_merge_lanes.sh +104 -11
  33. package/scripts/utterance_landing_check.sh +209 -0
  34. package/templates/.git-hooks/pre-commit +253 -13
  35. package/templates/settings.Compaction.snippet.json +56 -0
@@ -0,0 +1,124 @@
1
+ # reviewer_capability_corpus.tsv — SHARED known-pair corpus for "can this model be an
2
+
3
+ # adversarial reviewer?". Language-neutral DATA, deliberately not code.
4
+ #
5
+ # WHY DATA AND NOT A SHARED LIBRARY. The same judgment lives in at least three places, in
6
+ # three languages, across a public/private repo boundary: a public gate hook (bash), a
7
+ # field probe (shell), and a review backend (python). They cannot import each other —
8
+ # different repos, and the private side's vocabulary contains identifiers that must not
9
+ # reach a public repo. So the shareable artifact is the CORPUS, not the classifier: each
10
+ # side implements in its own language and proves agreement by running these rows.
11
+ #
12
+ # Measured drift 2026-08-08, before this file existed: 19 probe ids, **10 disagreements**
13
+ # between two of the implementations — and asymmetrically, 9 of them were the private side
14
+ # reading INCAPABLE models as CAPABLE. A permissive drift on the side that faces a real
15
+ # model catalog is the dangerous direction: it inflates panel size and family-diversity
16
+ # counts simultaneously, so the defense reports green while the panel cannot review.
17
+ #
18
+ # RESIDENCY: this file is PUBLIC. Generic capability classes and publicly-documented model
19
+ # families only. A corp/internal model id NEVER goes here — carry those in a local overlay
20
+ # corpus alongside (gitignored), same two-layer shape as the public-surface pattern file.
21
+ #
22
+ # NAMESPACES — declare what you own, skip what you do not (OPA-bundle `roots` shape).
23
+ # Two different things get classified under the same question and they are NOT interchangeable:
24
+ # ns:cli sidecar / CLI names a panel is composed of — `codex`, `gemini`, `agy`
25
+ # ns:model-id concrete served model identifiers — `voyage-3`, `glm-ocr`
26
+ # A consumer that classifies model ids has no opinion on `codex`; a consumer that composes
27
+ # panels has no opinion on `bge-m3`. Mixing them silently is a defect: measured 2026-08-08,
28
+ # the first draft of this corpus put both in one list and a model-id classifier failed the
29
+ # `codex` row — reported as implementation drift when it was corpus scope error. An adapter
30
+ # MUST declare its namespaces and SKIP rows outside them EXPLICITLY (a skipped row is
31
+ # reported, never silently counted as passing — unmeasured is not clean).
32
+ #
33
+ # FORMAT (tab-separated): id <TAB> expected <TAB> ns:<namespace>/<class> <TAB> why
34
+ # expected ∈ CAPABLE | INCAPABLE | UNDECIDABLE
35
+ # CAPABLE a generative model of a family known to return prose findings
36
+ # INCAPABLE emits vectors / scores / labels / transcripts — cannot return a finding
37
+ # UNDECIDABLE unrecognised. NOT "capable by default" — default-deny, because a
38
+ # denylist cannot know that `voyage-3` is an embedding model. An
39
+ # implementation that answers CAPABLE here is failing open.
40
+ #
41
+ # THE CORPUS STATES GROUND TRUTH, NOT WHAT ANY IMPLEMENTATION CAN SEE. `voyage-3` IS an
42
+ # embedding model, so the row says INCAPABLE — even though a pattern classifier cannot
43
+ # tell from the string. Grading is therefore on OUTCOME SAFETY, not label identity:
44
+ #
45
+ # hard FAIL want ∈ {INCAPABLE, UNDECIDABLE} and got = CAPABLE (permissive — ships a
46
+ # broken panel; the only direction that can actually hurt)
47
+ # hard FAIL want = CAPABLE and got ≠ CAPABLE (over-block — an
48
+ # always-red gate teaches --no-verify)
49
+ # DEVIATION INCAPABLE ↔ UNDECIDABLE in either direction (reported, not failed:
50
+ # both BLOCK under default-deny, so the outcome is identical)
51
+ #
52
+ # This rule exists because the first draft graded on label identity and was silently fitted
53
+ # to one implementation's regex — `armorm` was written UNDECIDABLE and `voyage-3` INCAPABLE
54
+ # purely because that regex caught one and not the other. Two ids with the same property,
55
+ # two different standards, and the corpus was grading itself. Worse, it made "delete the
56
+ # row" the rational move for whoever maintained the weaker classifier.
57
+ #
58
+ # ORDERING INVARIANT the rows enforce: ineligibility must be tested BEFORE eligibility.
59
+ # Rows marked `overlap` match a valid family AND an incapable class simultaneously; an
60
+ # eligibility-first implementation admits them silently. They are the anchor for the ORDER,
61
+ # not for the list.
62
+
63
+ # ── CONTRACT PIN ───────────────────────────────────────────────────────────────
64
+ #CORPUS-VERSION 2
65
+ #CORPUS-SHA256 e0d8d83b9ae042e4
66
+ # Hash covers the DATA rows only (comments/format may be reworded without a version bump).
67
+ # Recompute EXACTLY this way — two ways of computing it is itself a drift source, and the
68
+ # first draft of this pin failed on a trailing-newline difference between two implementations:
69
+ # grep -E '^[^#[:space:]]' reviewer_capability_corpus.tsv | shasum -a 256 | cut -c1-16
70
+ # Every consumer recomputes it and compares. A mismatch is HARNESS_ERROR — not PASS, not FAIL:
71
+ # it means the consumer is reading a DIFFERENT corpus than the one this contract names, and a
72
+ # drifted local copy is the failure mode that makes every downstream instrument report healthy
73
+ # while nothing is actually shared. (Independent precedent: OPA bundle `roots`, test262/CNCF
74
+ # conformance-suite-as-contract; failure taxonomy: local-first resolution · silent skipping ·
75
+ # drifted duplicates.)
76
+
77
+ # ── capable: generative families ────────────────────────────────────────────────
78
+ codex CAPABLE ns:cli/generative GPT-family agent CLI
79
+ gpt-5.5 CAPABLE ns:model-id/generative GPT family
80
+ gemini CAPABLE ns:cli/generative Gemini family
81
+ qwen3-32b CAPABLE ns:model-id/generative Qwen family, generative variant
82
+ glm-4.6 CAPABLE ns:model-id/generative GLM family, generative variant
83
+ deepseek-v3 CAPABLE ns:model-id/generative DeepSeek family
84
+ mistral-large CAPABLE ns:model-id/generative Mistral family
85
+ llama-4-70b CAPABLE ns:model-id/generative Llama family
86
+
87
+ # ── incapable: named by class token ─────────────────────────────────────────────
88
+ text-embedding-3-large INCAPABLE ns:model-id/embedding emits vectors
89
+ some-reranker-v1 INCAPABLE ns:model-id/reranker emits scores
90
+ generic-ocr-2b INCAPABLE ns:model-id/ocr emits transcribed text, not judgment
91
+ acme-safeguard-8b INCAPABLE ns:model-id/safeguard classifier — emits labels
92
+ text-moderation-latest INCAPABLE ns:model-id/moderation classifier — emits labels
93
+ whisper-large-v3 INCAPABLE ns:model-id/speech speech-to-text
94
+
95
+ # ── incapable WITHOUT a class token in the name (the denylist's blind spot) ─────
96
+ # These are why a denylist alone is insufficient and default-deny is required: nothing in
97
+ # the string says "embedding". Every one is a real, publicly-documented model.
98
+ voyage-3 INCAPABLE ns:model-id/embedding embedding model; name carries no class token
99
+ bge-m3 INCAPABLE ns:model-id/embedding embedding model; name carries no class token
100
+ gte-large INCAPABLE ns:model-id/embedding embedding model; name carries no class token
101
+ all-minilm-l6-v2 INCAPABLE ns:model-id/embedding embedding model; name carries no class token
102
+ armorm INCAPABLE ns:model-id/reward reward model — emits a scalar; opaque name, no class token
103
+ starling-rm INCAPABLE ns:model-id/reward reward model — emits a scalar
104
+ cohere-rank-v3 INCAPABLE ns:model-id/reranker reranker named `rank`, not `rerank`
105
+
106
+ # ── overlap: matches a VALID family AND an incapable class (ordering anchor) ────
107
+ glm-ocr INCAPABLE ns:model-id/overlap matches `glm` (capable) and `ocr` (incapable) — order decides
108
+ qwen3-embedding-8b INCAPABLE ns:model-id/overlap matches `qwen` (capable) and `embed` (incapable)
109
+ snowflake-arctic-embed-l-v2.0 INCAPABLE ns:model-id/overlap family prefix unrecognised; `embed` decides
110
+
111
+ # ── undecidable: unrecognised → default-deny, never assumed capable ─────────────
112
+ some-new-model-x UNDECIDABLE ns:model-id/unknown no known family; answering CAPABLE here is fail-open
113
+ internal-model-7 UNDECIDABLE ns:model-id/unknown no known family
114
+ codeguardian UNDECIDABLE ns:cli/unknown NOT auto-incapable — a bare `guard` substring rule blocks this legitimately-named review tool; unrecognised, so default-deny applies, but as UNDECIDABLE not INCAPABLE
115
+
116
+ # ── same-family exclusion (decorrelation, not capability) ──────────────────────
117
+ # Capable models that must still be REJECTED as panel members when the governor is of that
118
+ # family. Capability and decorrelation are different axes; an implementation that only
119
+ # checks capability passes a same-family panel. Marked separately so an adapter that does
120
+ # not own the decorrelation axis can skip this block explicitly rather than silently.
121
+ #SAMEFAMILY claude
122
+ #SAMEFAMILY opus
123
+ #SAMEFAMILY sonnet
124
+ #SAMEFAMILY haiku
@@ -211,6 +211,45 @@ else
211
211
  fail=1
212
212
  fi
213
213
 
214
+ # embedded --self-test suites (compaction_probe · judgment_circuit_lint · novelty_claim_check).
215
+ # These three carry their lanes INSIDE the script (`--self-test`) rather than in a sibling
216
+ # test_*_lanes.sh, so the name-list wiring above skipped them silently: 48 lanes existed and ran
217
+ # only when a human typed the command. That is built-but-not-wired applied to the anchors themselves
218
+ # — a later edit that breaks a lane stays green everywhere the project actually checks
219
+ # (high re-review 2026-08-08). Same shape as the block above: subject absent → SKIP, subject present
220
+ # but self-test missing → FAIL, never a silent pass.
221
+ for _subj in compaction_probe judgment_circuit_lint novelty_claim_check; do
222
+ if [ ! -f "scripts/$_subj.sh" ]; then
223
+ echo "SKIP $_subj --self-test (subject scripts/$_subj.sh absent)"
224
+ else
225
+ # ⚠️ **문자열 존재로 판정하지 마라.** 초판은 `grep -q -- '--self-test'` 였는데, 그 문자열은
226
+ # 헤더 주석과 usage echo 에도 있어서 **디스패처 한 줄만 지워도 여전히 매치**한다. 그리고
227
+ # 인식 못 한 모드에서 스크립트가 usage 를 찍고 exit 0 을 내므로, selfcheck 는 rc=0 을 보고
228
+ # 조용히 통과했다 — 25개 레인이 통째로 사라져도 `npm test` 는 PASS (high 3차 리뷰 실측).
229
+ # 존재검사가 진위를 못 본다는 그 클래스의 재발이다. **실행이 일어났다는 증거**를 요구한다.
230
+ # `< /dev/null` 필수: 인식 못 한 모드로 떨어지면 스크립트가 stdin 을 기다려 **무한 대기**한다
231
+ # (실측 — 디스패처 제거 known-negative 가 2분 타임아웃). CI 를 멈추는 건 조용한 통과보다 나쁘다.
232
+ # `timeout` 은 GNU coreutils 이고 **stock macOS 에 없다**. 가용성 확인 없이 부르면 rc=127 +
233
+ # 빈 출력 → 아래 `*)` 가 발동해 "dispatcher missing?" 이라는 **틀린 원인**으로 거짓 FAIL 이
234
+ # 난다(실측: homebrew 없는 PATH 에서 3개 subject 전부). 소비자 머신에서 `npm test` 와
235
+ # `prepublishOnly` 를 깨뜨리는 경로다. 정답 폼은 이미 레포에 있다(sync-from-be.sh:134).
236
+ # 없으면 무한대기 방지를 잃는 대신 도는 쪽을 택한다 — `< /dev/null` 이 그 방어의 본체다.
237
+ local _to=""; command -v timeout >/dev/null 2>&1 && _to="timeout 120"
238
+ _st_out="$($_to bash "scripts/$_subj.sh" --self-test < /dev/null 2>&1)"; _st_rc=$?
239
+ case "$_st_out" in
240
+ *캘리브레이션*) : ;;
241
+ *) echo "FAIL $_subj: --self-test produced no calibration verdict (dispatcher missing?)"
242
+ printf '%s\n' "$_st_out" | head -3 | sed 's/^/ /'
243
+ fail=1; _st_rc=0 ;; # 이미 FAIL 로 셌으니 아래서 중복 계상 안 한다
244
+ esac
245
+ if [ "$_st_rc" -ne 0 ]; then
246
+ echo "FAIL $_subj --self-test (exit $_st_rc)"
247
+ printf '%s\n' "$_st_out" | tail -6 | sed 's/^/ /'
248
+ fail=1
249
+ fi
250
+ fi
251
+ done
252
+
214
253
  # memory-link-check — the memory store is a GRAPH (memory_intent_recall.md: nodes=files,
215
254
  # edges=[[links]], recall walks one hop). Measured 2026-07-28: 50 of 872 edges pointed at a note
216
255
  # that existed under a different separator and 22 at nothing — a dead edge returns nothing and is
@@ -520,6 +559,33 @@ else
520
559
  fail=1
521
560
  fi
522
561
 
562
+ # utterance landing check — the close chain's CONTENT anchor, and it has to be RUN, not parsed.
563
+ # Measured 2026-08-08 on the branch that introduced it: the only thing in this file that touched
564
+ # `scripts/utterance_landing_check.sh` was the `bash -n` syntax sweep at the top. Syntax passing is
565
+ # not the instrument working, so the script shipped via files[] with zero behavioural callers —
566
+ # [[feedback_built_but_not_wired]] in its purest form, where the sole caller is prose in CLAUDE.md.
567
+ #
568
+ # What the self-test defends is specifically the DEGRADE DIRECTION. This checker's whole reason for
569
+ # existing is that a dead grep prints zero hits and zero hits read as "nothing landed" — a fail-open
570
+ # that manufactures a clean verdict out of a broken instrument. Its known-pair set pins that apart:
571
+ # a genuine miss must exit 1 while a dead control must exit 10, and 10 must never collapse into 1.
572
+ # Left unrun, the file rots exactly where it is load-bearing and nothing here would notice.
573
+ # Absence is a FAIL, not a SKIP — and the distinction is mechanical, not stylistic. The SKIP arm
574
+ # above for `probe_scope_check.sh` is correct because that file genuinely never ships; this one is
575
+ # listed in package.json files[], so in BOTH a source tree and an installed package it must be here.
576
+ # A SKIP would convert `rm scripts/utterance_landing_check.sh` into a green run — deletion as a
577
+ # clean bill of health, which is the same fail-open shape the checker itself exists to refuse.
578
+ if [ ! -f scripts/utterance_landing_check.sh ]; then
579
+ echo "FAIL utterance_landing_check.sh: missing — it ships via package.json files[], so absence is deletion, not package mode"
580
+ fail=1
581
+ elif _out=$(bash scripts/utterance_landing_check.sh --self-test 2>&1); then
582
+ echo "PASS utterance_landing_check.sh (known-pair self-test: control-death → 10, target-miss → 1)"
583
+ else
584
+ echo "FAIL utterance_landing_check.sh: self-test failed — the close-chain content anchor cannot be trusted"
585
+ _show_failure "$_out"
586
+ fail=1
587
+ fi
588
+
523
589
  # tag/version consistency guard. Wired with the guard it anchors: the guard exists because a wrong
524
590
  # tag reached the remote and a publish from the wrong tree was stopped only by npm's own collision
525
591
  # check, so an unrun anchor here would be the same luck-as-floor arrangement one layer up.
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env bash
2
+ # test_marker_crossfamily_lanes.sh — regression fixtures for pre-commit's
3
+ # validate_crossfamily_leg (typed cross-family verdict, 2026-08-08).
4
+ #
5
+ # WHY: the `crossfamily:` marker line has been REQUIRED on load-bearing changes since
6
+ # 2026-06 (commit c1fa459) — but only its PRESENCE was checked, so the value was free
7
+ # prose. That is how a false one shipped: a sibling harness recorded
8
+ # `crossfamily: none this round — 도달 불가` (unreachable), the claim was later found
9
+ # FALSE, and a later session cited it as grounds. Presence-checking catches silence; it
10
+ # cannot catch a confident wrong answer. This lane types the value.
11
+ #
12
+ # Fixtures assert BOTH directions (known-pair): every intended shape is admitted, and
13
+ # every hole the lane closes still blocks. A test that only asserts BLOCK cannot tell
14
+ # "blocked" from "blocked for the wrong reason"; one that only asserts PASS cannot see a
15
+ # lane that admits everything.
16
+ #
17
+ # Usage: bash scripts/test_marker_crossfamily_lanes.sh Exit: 0 = all behave; 1 = regression.
18
+
19
+ set -uo pipefail
20
+ REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
21
+ HOOK="$REPO_ROOT/templates/.git-hooks/pre-commit"
22
+ T=$(mktemp -d); trap 'rm -rf "$T"' EXIT
23
+
24
+ sed -n '/^validate_crossfamily_leg()/,/^}/p' "$HOOK" > "$T/fn.sh"
25
+
26
+ # Instrument calibration: an empty extraction would let every fixture "pass" against
27
+ # nothing. Assert the function body actually arrived before measuring anything.
28
+ if ! grep -q 'DEGRADED_PANEL_UNUSED' "$T/fn.sh"; then
29
+ echo "❌ HARNESS-ERROR — validate_crossfamily_leg did not extract from $HOOK."
30
+ echo " Fixtures below would measure an empty function. Aborting rather than reporting green."
31
+ exit 1
32
+ fi
33
+
34
+ mk() { printf "$1" > "$T/$2"; } # $1 = marker body, $2 = fixture name
35
+ run() { bash -c "source '$T/fn.sh'; validate_crossfamily_leg '$1'" >/dev/null 2>&1; }
36
+
37
+ FAIL=0; N=0
38
+ check() { # $1=fixture $2=expected(PASS|BLOCK) $3=label
39
+ N=$((N+1))
40
+ if run "$T/$1"; then got=PASS; else got=BLOCK; fi
41
+ if [ "$got" = "$2" ]; then echo "✅ $3 → $got"; else echo "❌ $3 → $got (expected $2)"; FAIL=1; fi
42
+ }
43
+
44
+ echo "── admitted shapes (must PASS) ──"
45
+ mk 'crossfamily: panel(codex,gemini) — R1..R3, 9 findings, 8 fixed 1 refuted, CONVERGED\n' k1
46
+ check k1 PASS "panel() with family list + verdict"
47
+ mk 'crossfamily: panel(codex, gemini) — spaced list, the S-grade over-block\n' k1b
48
+ check k1b PASS "panel(codex, gemini) — SPACE after comma (was truncated to 'panel(codex,')"
49
+ mk 'crossfamily: declined\n' k3
50
+ check k3 PASS "declined (operator chose this floor — not a degrade)"
51
+ mk 'crossfamily: DEGRADED_SINGLE_FAMILY — probed codex/agy/4090, 0 capable reachable\n' k4
52
+ check k4 PASS "DEGRADED_SINGLE_FAMILY + substantive reason"
53
+ mk 'crossfamily: UNKNOWN — consent unset, panel not probed this round\n' k5
54
+ check k5 PASS "UNKNOWN + reason naming why it was not probed"
55
+ mk 'crossfamily: DEGRADED_PANEL_UNUSED — codex/agy/gemini reachable, not recruited (scope)\n' k6
56
+ check k6 PASS "DEGRADED_PANEL_UNUSED + reason naming the reachable families"
57
+ mk 'crossfamily: panel(qwen,gpt-oss,glm) — clean, 0 findings\n' k7
58
+ check k7 PASS "capable families only — guard does not over-block a real panel"
59
+
60
+ echo "── closed holes (must BLOCK) ──"
61
+ mk 'axis2-evidence: PASS no-S\n' b1
62
+ check b1 BLOCK "no crossfamily line at all (silence — the original guard, still intact)"
63
+ mk 'crossfamily: none this round — 인라인 same-family\n' b2
64
+ check b2 BLOCK "'none' free prose (the shape found on disk)"
65
+ mk 'crossfamily: none this round — 도달 불가\n' b3
66
+ check b3 BLOCK "'none' asserting unreachable (the FALSE one that shipped)"
67
+ mk 'crossfamily: none\n' b4
68
+ check b4 BLOCK "bare 'none' (merges could-not / did-not / did-not-look)"
69
+ mk 'crossfamily: DEGRADED_SINGLE_FAMILY\n' b5
70
+ check b5 BLOCK "degrade value with no reason (silent degrade — the whole point)"
71
+ mk 'crossfamily: UNKNOWN\n' b6
72
+ check b6 BLOCK "UNKNOWN with no reason (unprobed rendered as zero)"
73
+ mk 'crossfamily: DEGRADED_PANEL_UNUSED\n' b7
74
+ check b7 BLOCK "PANEL_UNUSED with no reason ('did not' passing as 'could not')"
75
+ mk 'crossfamily: DEGRADED_SINGLE_FAMILY — degraded\n' b8
76
+ check b8 BLOCK "reason too vacuous (names no probe and no grounds)"
77
+ mk 'crossfamily: panel\n' b9
78
+ check b9 BLOCK "bare panel claim naming no family"
79
+ mk 'crossfamily: panel()\n' b10
80
+ check b10 BLOCK "panel() with empty family list"
81
+ mk 'crossfamily: DEGRADED\n' b11
82
+ check b11 BLOCK "near-miss token (not an enum value)"
83
+ mk 'crossfamily: codex/gpt-5.5 — R1..R4, 16 findings, CONVERGED\n' b12
84
+ check b12 BLOCK "legacy free-form engine string (pre-typed convention) now rejected"
85
+
86
+ echo "── cross-family review findings (agy/Gemini 3.1 Pro, 2026-08-08) ──"
87
+ mk 'crossfamily: panel(claude) — reviewed by another claude session\n' x1
88
+ check x1 BLOCK "panel names the AUTHOR'S own family (decorrelation gate not seeing decorrelation)"
89
+ mk 'crossfamily: panel(opus,sonnet) — two claude tiers\n' x2
90
+ check x2 BLOCK "two same-family tiers dressed as a 2-family panel"
91
+ mk 'crossfamily: panel(none)\n' x3
92
+ check x3 BLOCK "panel(none) — a non-run laundered through the parens"
93
+ mk 'crossfamily: panel(unprobed)\n' x4
94
+ check x4 BLOCK "panel(unprobed) — same laundering, other token"
95
+ mk 'crossfamily: single-family\n' x5
96
+ check x5 BLOCK "single-family on a LOAD-BEARING change (free no-ack bypass of the lane)"
97
+ mk 'crossfamily: UNKNOWN — 0\n' x6
98
+ check x6 BLOCK "grounds '0' (porous non-vacuity: bare digit passed)"
99
+ mk 'crossfamily: UNKNOWN — problem\n' x7
100
+ check x7 BLOCK "grounds 'problem' (matched on substring 'prob')"
101
+ mk 'crossfamily: UNKNOWN — client error\n' x8
102
+ check x8 BLOCK "grounds 'client error' (matched on substring 'cli')"
103
+ mk 'crossfamily: panel(voyage-3,bge-m3) — 2 families\n' x9
104
+ check x9 BLOCK "embedding models WITHOUT 'embed' in the name (denylist false negative)"
105
+ mk 'crossfamily: panel(armorm,starling-rm) — 2 families\n' x10
106
+ check x10 BLOCK "reward models emitting scalars, not findings"
107
+ mk 'crossfamily: panel(cohere-rank-v3) — reranker named 'rank' not 'rerank'\n' x11
108
+ check x11 BLOCK "reranker named 'rank' (denylist false negative)"
109
+ mk 'crossfamily: DEGRADED_SINGLE_FAMILY — agy sidecar daemon crashed on startup, none reachable\n' x12
110
+ check x12 PASS "grounds naming agy (was rejected — keyword list omitted it)"
111
+
112
+ mk 'crossfamily: single-family\ncrossfamily: DEGRADED_SINGLE_FAMILY — probed codex/agy, 0 reachable\n' x13
113
+ check x13 BLOCK "two crossfamily lines — appended correction shadowed by stale first (codex net-new)"
114
+
115
+ echo "── review-capability guard (pmh-dev #41 field measurement) ──"
116
+ mk 'crossfamily: panel(qwen,embed,embed) — 3 families\n' c1
117
+ check c1 BLOCK "embeddings counted as panel members (the measured false panel)"
118
+ mk 'crossfamily: panel(embed,rerank,ocr) — 3 families\n' c2
119
+ check c2 BLOCK "every member incapable — '3 families', 0 reviewers"
120
+ mk 'crossfamily: panel(gpt-oss,safeguard) — 2 families\n' c3
121
+ check c3 BLOCK "safeguard classifier padding one real family"
122
+ # Ordering invariant: ineligibility is tested FIRST. Both tokens ALSO match a valid
123
+ # family (glm-ocr → glm, qwen-embedding → qwen), so an eligibility-first implementation
124
+ # admits them silently. This pair anchors the ORDER, not the list.
125
+ mk 'crossfamily: panel(glm-ocr) — glm family\n' c4
126
+ check c4 BLOCK "glm-ocr — matches a valid family AND an incapable class"
127
+ mk 'crossfamily: panel(qwen-embedding-8b) — qwen family\n' c5
128
+ check c5 BLOCK "qwen-embedding — same overlap, other direction"
129
+
130
+ echo
131
+ if [ "$FAIL" -eq 0 ]; then echo "✅ all $N fixtures behave"; else echo "❌ regression ($N fixtures run)"; fi
132
+ exit "$FAIL"
@@ -23,14 +23,15 @@ check() { # $1=fixture $2=expected(PASS|BLOCK) $3=label
23
23
  if [ "$got" = "$2" ]; then echo "✅ $3 → $got"; else echo "❌ $3 → $got (expected $2)"; FAIL=1; fi
24
24
  }
25
25
 
26
- printf 'axis2-engine: inline\naxis2-model: sonnet\nfloor-status: sonnet-floor\naxis2-anchor: regression test 5/5 pass\naxis2-evidence: PASS no-S, 2B applied\n' > "$T/m1"
27
- printf 'axis2-engine: inline\naxis2-model: sonnet\nfloor-status: sonnet-floor\naxis2-evidence: PASS no-S\n' > "$T/m2"
28
- printf 'axis2-engine: inline\naxis2-model: haiku\nfloor-status: sonnet-floor\naxis2-anchor: probe 3/3\naxis2-evidence: PASS no-S\n' > "$T/m3"
29
- printf 'axis2-engine: inline\naxis2-model: sonnet\nfloor-status: at-floor\naxis2-evidence: PASS no-S\n' > "$T/m4"
30
- printf 'axis2-engine: inline\naxis2-model: haiku\nfloor-status: below-floor\naxis2-evidence: PASS no-S\n' > "$T/m5"
31
- printf 'axis2-engine: quench-challenger\naxis2-model: opus\nfloor-status: at-floor\naxis2-evidence: 1S/4A fixed\n' > "$T/m6"
32
- printf 'axis2-engine: inline\naxis2-model: haiku\nfloor-status: below-floor\nbelow-floor-ack: "approved, proceed" — canary-only change\naxis2-evidence: PASS no-S\n' > "$T/m7"
33
- printf 'axis2-engine: inline\naxis2-model: opus\nfloor-status: bogus-status\naxis2-evidence: PASS\n' > "$T/m8"
26
+ CF=''
27
+ printf "axis2-engine: inline\naxis2-model: sonnet\nfloor-status: sonnet-floor\naxis2-anchor: regression test 5/5 pass\n${CF}axis2-evidence: PASS no-S, 2B applied\n" > "$T/m1"
28
+ printf "axis2-engine: inline\naxis2-model: sonnet\nfloor-status: sonnet-floor\n${CF}axis2-evidence: PASS no-S\n" > "$T/m2"
29
+ printf "axis2-engine: inline\naxis2-model: haiku\nfloor-status: sonnet-floor\naxis2-anchor: probe 3/3\n${CF}axis2-evidence: PASS no-S\n" > "$T/m3"
30
+ printf "axis2-engine: inline\naxis2-model: sonnet\nfloor-status: at-floor\n${CF}axis2-evidence: PASS no-S\n" > "$T/m4"
31
+ printf "axis2-engine: inline\naxis2-model: haiku\nfloor-status: below-floor\n${CF}axis2-evidence: PASS no-S\n" > "$T/m5"
32
+ printf "axis2-engine: quench-challenger\naxis2-model: opus\nfloor-status: at-floor\n${CF}axis2-evidence: 1S/4A fixed\n" > "$T/m6"
33
+ printf "axis2-engine: inline\naxis2-model: haiku\nfloor-status: below-floor\nbelow-floor-ack: \"approved, proceed\" — canary-only change\n${CF}axis2-evidence: PASS no-S\n" > "$T/m7"
34
+ printf "axis2-engine: inline\naxis2-model: opus\nfloor-status: bogus-status\n${CF}axis2-evidence: PASS\n" > "$T/m8"
34
35
 
35
36
  check "$T/m1" PASS "sonnet-floor + anchor + sonnet model (new lane, intended shape)"
36
37
  check "$T/m2" BLOCK "sonnet-floor WITHOUT anchor (anchor is the compensating requirement)"