@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.
- package/.claude/judgment_circuits.txt +14 -0
- package/.claude/rules/fh_4axis_gate.md +7 -0
- package/.claude-plugin/marketplace.json +2 -2
- package/AGENTS.md +25 -0
- package/CLAUDE.md +179 -12
- package/knowledge/shared/harness-core/dispatch_conditional_prohibition.md +105 -0
- package/knowledge/shared/harness-core/fh_three_layer_canon.md +165 -0
- package/knowledge/shared/harness-core/harness_incubator_doctrine.md +100 -0
- package/knowledge/shared/harness-core/onboarding_acceleration_autopilot.md +3 -1
- package/knowledge/shared/harness-core/ship_readiness_gate.md +181 -13
- package/knowledge/shared/learnings/subagent_invocations_log.yaml +528 -0
- package/package.json +16 -2
- package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +56 -8
- package/plugins/fh-meta/skills/install-wizard/SKILL.md +33 -0
- package/plugins/fh-meta/skills/install-wizard/SKILL_detail.md +104 -15
- package/scripts/chamber_run.sh +64 -2
- package/scripts/chamber_witness.sh +439 -0
- package/scripts/compaction_probe.sh +456 -0
- package/scripts/digest_landing_check.sh +385 -0
- package/scripts/directional_diff_gate.sh +459 -0
- package/scripts/judgment_circuit_lint.sh +239 -0
- package/scripts/novelty_claim_check.sh +193 -0
- package/scripts/relay_channel.sh +645 -0
- package/scripts/reviewer_capability_corpus.tsv +124 -0
- package/scripts/selfcheck.sh +66 -0
- package/scripts/test_marker_crossfamily_lanes.sh +132 -0
- package/scripts/test_marker_floor_lanes.sh +9 -8
- package/scripts/test_relay_channel_lanes.sh +583 -0
- package/scripts/test_reviewer_capability_conformance.sh +173 -0
- package/scripts/test_wizard_snippet_merge_lanes.sh +104 -11
- package/scripts/utterance_landing_check.sh +209 -0
- package/templates/.git-hooks/pre-commit +253 -13
- 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
|
package/scripts/selfcheck.sh
CHANGED
|
@@ -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
|
-
|
|
27
|
-
printf
|
|
28
|
-
printf
|
|
29
|
-
printf
|
|
30
|
-
printf
|
|
31
|
-
printf
|
|
32
|
-
printf
|
|
33
|
-
printf
|
|
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)"
|