@chrono-meta/fh-gate 3.1.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,19 +11,19 @@
11
11
  "plugins": [
12
12
  {
13
13
  "name": "fh-meta",
14
- "version": "3.1.0",
14
+ "version": "3.1.1",
15
15
  "description": "New in 2.2.0: BREAKING (gate): chamber step 6 now reads ACTUAL.md, not BUDGET.md — an in-flight chamber run whose actual cost sits in BUDGET.md blocks until the ACTUAL: line moves to tracks/_chamber/<slug>/ACTUAL.md (the runner prints the path). Why: BUDGET.md's pre-verdict hash IS the ordering witness, and step 6 hard-blocked until that same file changed, so every run that reached COMPLETE necessarily mutated a witnessed artifact and verify returned TAMPERED — the chamber's promotion condition was unsatisfiable by construction, not by strictness. Two roles (immutable witness / post-verdict calibration sink) had collided in one file; each was correct alone, so neither side's code showed the conflict. Also: ko-tech-writer Step 2/4-b scans are now calibration-backed (known-pair fixtures + reproducible command, shipped) — discrimination is proven, 'zero residue' is explicitly NOT; chamber lane suite 12 -> 33 including the runner x witness seam no test covered; chamber_run.sh now teaches the two-commit discipline (gate hashes and verdict hash must land in separate commits/PRs — it previously advised the opposite). New in 2.1.0: BREAKING (gate): `crossfamily: declined` in an Axes 2-3 marker now requires grounds naming a record path that RESOLVES on disk — bare `declined`, and `declined` justified by author judgment, are blocked at commit. Remedy: cite where the operator decision lives (e.g. `.. — operator declined sidecars, per knowledge/shared/rules/operational_adaptation.md`), or use `DEGRADED_PANEL_UNUSED` if a panel was reachable and you chose not to recruit it — which is what author judgment actually is. `declined` was the only enum value with no grounds requirement; a cross-family review then broke the first (vocabulary-grep) fix three ways — self-validating on the value's own token, vacuous keyword passes, and over-blocking real declinations in natural prose — so the check asserts a resolvable record instead of words. Also: standpoint axis gains `tier1b` (a STATIC read of a target repo, executed nothing) plus a decide-in-order procedure, after blind floor-tier sims graded pure cold-reads as `tier2` three rounds running; steel-quench Wave 1's sixth angle (gate-locality) gains the output-template row it never had, so a mandatory angle stops being structurally unreportable; verify-bidirectional gains category 5 (prescriptive doctrine statement); Sister Asset Protocol gains an active-adoption trigger; new resident doctrine — Mechanization Boundary, Local Execution First, Skeleton-not-Muscle, Expedition track, and this package's versioning policy. Hub meta-operations toolkit — 35 skills + 7 agents. New in 2.0.1: harness-doctor cadence hook, portability lint wired into pre-commit, branch_claim.sh claim-count-vs-tree-count warning, louder confidentiality-scan fail-open notice, fh-gate.sh missing-package.json survival, identity ① reclassified 🟢 (cross-harness adapters + relay argument channel). New in 1.4.53: `fh-codex-doctor` (npm bin) — Codex adapter drift scanner; reads the documented M1/M2/M3 skill tier map + skill/agent source and reports codex-native/adapter-required/claude-native/unclassified per unit, wired into `npm test`/`prepublishOnly` (fail-closed on unclassified Claude-native primitives). New in 1.4.49: steel-quench gains Step 0.6 Verdict-Invariance Probe (groundedness axis — a load-bearing judged gate's verdict must track behavior, not rubric phrasing; measured flip-count over cross-family paraphrases; arXiv:2605.06161 Policy Invariance anchor); multi_model_sidecar_strategy §Vendor-native harness (a model is strongest in its own vendor CLI — Claude/CC, GPT/codex, Gemini/Antigravity; a universal router degrades all of them, so it stays an autocomplete/QA sidecar, never orchestration); predelete_check.sh fail-closed rewrite; memory-hygiene A-TMA anchor. New in 1.4.48: phantom-quench + steel-quench gain external frontier anchors (arXiv:2607.02052 package-hallucination; arXiv:2607.02057 prompt-coverage-adequacy); README model-flat claim reframed from a per-release point-curve to structural invariants (operation flattens across tiers; depth tier-order fixed within a generation). New in 1.4.47: onboarding step ① surfaces the Mode D companion-store session-start load in the auto-read salience anchor (previously only in the local binding + rules, so a greeting could skip the load). New in 1.4.46: context-doctor command-output axis (route to rtk/proxy for verbose CLI stdout, complementing .claudeignore; risk-gated to token-scarce envs). New in 1.4.41: context-doctor 2026 trigger vocab (context engineering/rot/collapse) + phantom-citation hardening; hub measurement-integrity-checklist (cross-model measurement pre-flight: display-name pin/reps≥3/discriminating probe). New in 1.4.40: install-wizard queryable-wiki scaffold (INDEX + session-start read + R/W/C ingest). New in 1.4.39: auto-decorrelation (cross-family verifier sidecar recruitment) + video-ingest (capability-routed video ingestion). New in 1.4.x: verify-axis check-class taxonomy (mandatory-pass/measured/judged), no-reinvention Tier-0 inventory, 7-class failure taxonomy, Destructive-Op Gate, Wave-T (Temper), tier-floor governance, Mode D Model Notice, FC consent lane, default-Sonnet guidance. New in 1.3.0: public-surface-audit, field-harvest Mode B auto-trigger, 4-axis gate scope ext. Validated cross-CLI: Claude Code, Codex, Gemini.",
16
16
  "source": "./plugins/fh-meta"
17
17
  },
18
18
  {
19
19
  "name": "fh-commons",
20
- "version": "3.1.0",
20
+ "version": "3.1.1",
21
21
  "description": "Project-agnostic utility skills — 5 skills (convergence-loop · deliberation · mcp-circuit-breaker · token-budget-gate · ko-tech-writer) + 1 agent (quench-challenger). Domain-independent utilities transplantable into any project.",
22
22
  "source": "./plugins/fh-commons"
23
23
  },
24
24
  {
25
25
  "name": "fh-qp",
26
- "version": "3.1.0",
26
+ "version": "3.1.1",
27
27
  "description": "QP (Quality Platform) — the generic edition of a field QA harness's Prepare→Automation→Regression loop as an FH plugin: 4 skills (qp router · qp-plan · qp-run · qp-regress) + qp_tools.sh (target-class · adapter-probe · mask · surface-reach · mtm-check · run-verbs, typed exit codes) + a zero-domain-constant profile slot + 29 known-pair lanes. Drives web targets through the session's Playwright MCP and desktop targets through computer-use MCP (mobile deferred); calls a registered qasp typed capability when one exists (strictest-wins) — none is registered today, so the MCP fallback is the first edition. Verdict contract: a MACHINE closure requires a recorded assertion; a failed first step is attributed BLOCKED, not FAIL; surface_reach counts every TC in the denominator. Born as chamber run #18 (EMIT, 2026-09-05).",
28
28
  "source": "./plugins/fh-qp"
29
29
  }
package/README.ja.md CHANGED
@@ -58,7 +58,7 @@ brew tap chrono-meta/forge-harness && brew install forge-harness # あるい
58
58
  **GitHub Actions では** — 同じゲートを1つのステップとして、判定は型のあるまま:
59
59
 
60
60
  ```yaml
61
- - uses: chrono-meta/forge-harness@v3.1.0
61
+ - uses: chrono-meta/forge-harness@v3.1.1
62
62
  with:
63
63
  files: ${{ steps.changed.outputs.files }}
64
64
  env:
package/README.ko.md CHANGED
@@ -45,7 +45,7 @@ brew tap chrono-meta/forge-harness && brew install forge-harness # 또는 이
45
45
  **GitHub Actions 에서** — 같은 게이트를 스텝 하나로, 판정은 타입을 유지한 채:
46
46
 
47
47
  ```yaml
48
- - uses: chrono-meta/forge-harness@v3.1.0
48
+ - uses: chrono-meta/forge-harness@v3.1.1
49
49
  with:
50
50
  files: ${{ steps.changed.outputs.files }}
51
51
  env:
package/README.md CHANGED
@@ -47,7 +47,7 @@ brew tap chrono-meta/forge-harness && brew install forge-harness # or this
47
47
  **In GitHub Actions** — the same gate as a step, with the verdict kept typed:
48
48
 
49
49
  ```yaml
50
- - uses: chrono-meta/forge-harness@v3.1.0
50
+ - uses: chrono-meta/forge-harness@v3.1.1
51
51
  with:
52
52
  files: ${{ steps.changed.outputs.files }}
53
53
  env:
package/README.zh.md CHANGED
@@ -57,7 +57,7 @@ brew tap chrono-meta/forge-harness && brew install forge-harness # 或者用
57
57
  **在 GitHub Actions 里** —— 同一道门禁作为一个 step,判定依然是带类型的:
58
58
 
59
59
  ```yaml
60
- - uses: chrono-meta/forge-harness@v3.1.0
60
+ - uses: chrono-meta/forge-harness@v3.1.1
61
61
  with:
62
62
  files: ${{ steps.changed.outputs.files }}
63
63
  env:
@@ -3517,3 +3517,13 @@
3517
3517
  evidence: "PR #264·265·266·268 전부 머지(main=01dafcd) · CI 6/6 × 4. cross-family 1차 A급 4건(3 수리 · 1 **반증** — needs_clarification 은 disposition 게이트가 이미 막는다) · 2차 S1+A4(전부 수리). 거버너 독립 되돌림 재검 4건 전부 적색 확인. 전체 스위트 1 failed/3996 passed(시간의존 픽스처 1, 기존)"
3518
3518
  tokens_subagent: 986241
3519
3519
  notes: "🟥 계기 확정 하나가 부수 수확: 「기존 실패 13건」은 코드가 아니라 venv 아티팩트였다(uv 3.11 위에 3.13 접목, pip 이 코드가 안 읽는 곳으로). 깨끗한 클론·라이브 트리 양팔 모두 1 failed. 세션마다 13/12/14/1 로 갈리던 원인. 🟥 그리고 codex 가 못 본 형제 경로(not-mapped 분기)를 거버너가 찾았다 — 사이드카는 지목자이지 판정자가 아니라는 계보 그대로"
3520
+
3521
+ - date: 2026-09-06
3522
+ agent: general-purpose ×8 (live-eval 진단 · archify U1 재현+PR · qasp 웹QA 레디 점검 · README ko/zh/ja · arXiv 반려 분석[자체 서브에이전트 1 재디스패치] · 논문 문체 감사 · 논문 인용 수정본 · qasp M①M③ 수리) + fh-meta:beginner ×1 (qasp 막별 산출물 콜드리드) + general-purpose ×1 (논문 B 초안) + codex sidecar ×2 (릴리스 델타 cross-family — 1차 stdin 대기로 타임아웃, 2차 성공)
3523
+ invoked_by: FH governor session (Opus 5, 운영자 «오늘 야간 내가 요청한대로 자율주행 이어가줘 … 위임할게 5대정체성 4개 엔진 초록인만큼 잘 써가면서 일해줘»)
3524
+ task: "3.1.0 릴리스 집행 · live-eval 보정 · qasp 웹QA 레디(다음 주 실사용) · arXiv 반려 원인 규명과 논문 재구성 · 랜딩 개편 · archify upstream 기여"
3525
+ context_card: yes (건별 절대경로 · 정본 선독 · 커밋/푸시 경계 · 컨트롤 동반 요구 · 외부 PR 은 금지어휘 목록)
3526
+ outcome: accepted
3527
+ evidence: "릴리스 v3.1.0 npm 라이브(latest=3.1.0) + GitHub 릴리스 + PR #664 머지 · PR #665(verify 재시도) · gh-pages 랜딩 개편 발행(지도 91%→5.6%) · tt-a1i/archify#328 upstream 제출(스위트 1058/1030/0, zip 결정적 재빌드) · qasp PR #269 머지 · 논문 v1.0.1 수정본(인용 21/21 재검증, 원본 해시 불변). cross-family(codex) A급 1건 적발 — CHANGELOG L12 과대주장, 소스 재확인 후 수리, **자력 적발 0**"
3528
+ tokens_subagent: 2100000
3529
+ notes: "🟥 이 날의 최대 발견은 코드가 아니라 **논문**이다 — 반려된 v1.0 의 참고문헌 17건 중 11건이 어긋나 있었다(ID 는 실재, 제목·저자가 다른 논문 것, 한 건은 공간생물학). 그 논문 자신이 «존재 검사 → 출처 검사» 2단계를 방법론으로 제시하고 「허위율 0%」라 보고했다. **방법은 있었고 자기에게 안 돌렸다.** 대조군이 이례적으로 좋다: 같은 심사 창에서 제품명 0회인 자매 논문은 보류 해제. 🟥 부수 교훈 3건이 전부 계기 결함이다 — ① 내 인용 추출기가 References 대신 Changelog 를 긁었다 ② 레인 rc=1 인데 MV 줄만 필터해 보고 실패 3건을 놓쳤다 ③ archify 테스트를 잘못된 cwd 에서 돌려 rc=254 를 «실패»로 읽을 뻔했다. 셋 다 «출력을 좁혀 본 것»이 원인이다"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrono-meta/fh-gate",
3
- "version": "3.1.0",
3
+ "version": "3.1.1",
4
4
  "description": "FH runtime adapters — run FH governance, skills, and agents via Claude or Codex with machine-parseable gates.",
5
5
  "homepage": "https://github.com/chrono-meta/forge-harness#readme",
6
6
  "bugs": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fh-commons",
3
- "version": "3.1.0",
3
+ "version": "3.1.1",
4
4
  "engines": {
5
5
  "claudeCode": ">=1.0.0"
6
6
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fh-meta",
3
- "version": "3.1.0",
3
+ "version": "3.1.1",
4
4
  "engines": {
5
5
  "claudeCode": ">=1.0.0"
6
6
  },
@@ -1,5 +1,38 @@
1
1
  # forge-harness (fh-meta) Changelog
2
2
 
3
+ ### [3.1.1] — 2026-09-07 — fh-gate Action 의 기본 `level` 이 게이트가 안 받는 값이었다
4
+
5
+ ### 🟥 무엇이 깨져 있었나
6
+ `action.yml` 의 `level` 기본값이 **`'standard'`** 였는데, `scripts/fh-gate.sh:275` 는 `quick` 과
7
+ `full` 만 받고 나머지는 **ARG_ERROR(11)** 로 거절한다. 그리고 `fail-on` 기본 집합에 `ARG_ERROR`
8
+ 가 들어 있다. ⇒ **`uses: chrono-meta/forge-harness@v3.1.0` 을 `level:` 없이 쓰면 스텝이 실패했고,
9
+ 에러 메시지는 사용자가 설정한 적 없는 인자를 지목했다.**
10
+
11
+ 발행된 3.1.0 타르볼로 실측(소스를 읽은 게 아니다):
12
+ ```
13
+ level=standard → rc=11 ERROR: gate level must be 'quick' or 'full' (got: standard)
14
+ level=quick → rc=12 (DRY_RUN, 정상)
15
+ level=full → rc=12 (DRY_RUN, 정상)
16
+ ```
17
+
18
+ ### 어떻게 잡혔나
19
+ 릴리스 마커에 `standpoint: DEGRADED_NOT_RUN — 소비자 install 팔을 안 돌렸다` 라고 적어 두고,
20
+ 발행 직후 그 팔을 실제로 돌렸다. **읽어서는 안 나오고 돌려야 나오는 자리**였다.
21
+ 레인 28개가 이미 있었지만 전부 **종료코드 매핑**만 봤고 「입력 기본값이 CLI 가 받는 값인가」는
22
+ 아무도 안 봤다.
23
+
24
+ ### 고친 것
25
+ - `action.yml` `level` 기본값 `'standard'` → **`'quick'`**, description 도 «다른 값은 안 받는다»로
26
+ - **레인 D0~D2 신설** — `action.yml` 의 기본값과 `fh-gate.sh` 의 허용 집합을 **둘 다 실물에서 추출**해
27
+ 대조한다(어느 쪽도 하드코딩 안 한다 — 그러면 같은 방식으로 또 드리프트한다).
28
+ D0 = 허용 집합 추출 성공(계기 생사) · D1 = 기본값이 그 안에 있나 · D2 = **컨트롤**(같은 검사가
29
+ `'standard'` 를 거절하는지 — 아니면 D1 이 아무것도 증명 못 한다). fail-before 실증: 기본값을
30
+ `standard` 로 되돌리면 **정확히 D1 만** 적색(30 passed · 1 failed), 복원하면 31/0.
31
+ - README 4판의 사용례를 `@v3.1.1` 로
32
+
33
+ ⚠️ **`@v3.1.0` 태그는 그대로 둔다**(태그는 안 움직인다). 그 태그를 쓰는 워크플로는 `level: quick`
34
+ 을 명시하면 정상 동작한다.
35
+
3
36
  ### [3.1.0] — 2026-09-06 — QP 플러그인 · `oracle:` 마커 채널 · preprep 도해 굽기 · FH 전체 지도 발행 · fh-gate GitHub Action
4
37
 
5
38
  ### 왜 minor 인가
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fh-qp",
3
- "version": "3.1.0",
3
+ "version": "3.1.1",
4
4
  "engines": {
5
5
  "claudeCode": ">=1.0.0"
6
6
  },
@@ -14,16 +14,16 @@
14
14
  <string>/bin/bash</string>
15
15
  <string>/path/to/forge-harness/scripts/probe_live_eval.sh</string>
16
16
  <!-- 🟥 COST + API-KEY DECISION IS THE OPERATOR'S — this template ships with NO default
17
- --subset/--ids limiter, i.e. installing it as-is runs the FULL selected set (12
17
+ the subset / ids limiter flags limiter, i.e. installing it as-is runs the FULL selected set (12
18
18
  probes x 2 calls = 24 live `claude -p` invocations) every night. Read
19
19
  scripts/probe_live_eval.sh's own header (§COST) before installing, and add a
20
- --subset N or --ids P1,P2 argument here if a nightly full-set run is not what you
20
+ a subset or ids argument argument here if a nightly full-set run is not what you
21
21
  want. This plist is a template, not a recommendation of scope. -->
22
22
  <!-- 🟥 reps=3 (2026-09-06). 이 인자가 없으면 reps=1 이고, 그때의 pass_rate 로는 문턱을
23
23
  정할 수 없다 — 실측: 유효 런 3 개 재채점에서 12 프로브 중 5 개가 flaky, 같은 코퍼스
24
24
  15 분 간격 두 런에서 4 개가 뒤집혔고 관측 pass_rate 는 0.50/0.67/0.67 이었다(단일 rep
25
25
  노이즈 폭 > 문턱까지의 거리). 판정은 과반, 분산은 리포트 `reps(pass/ran)` 칸에 남는다.
26
- ⚠️ 비용이 3 배다: 24 → **72** 회 `claude -p`. 줄이려면 --subset/--ids 를 같이 줘라. -->
26
+ ⚠️ 비용이 3 배다: 24 → **72** 회 `claude -p`. 줄이려면 the subset / ids limiter flags 를 같이 줘라. -->
27
27
  <string>--reps</string>
28
28
  <string>3</string>
29
29
  </array>
@@ -28,6 +28,69 @@ chk(){ if [ "$1" = "0" ]; then ok "$2"; else no "$2"; fi; }
28
28
 
29
29
  # ── extract the real case block from the shipped file ────────────────────────────────────────
30
30
  MAP="$(awk '/^ *case "\$rc" in/{f=1} f{print} /^ *esac/{if(f){exit}}' "$A" | sed 's/^ *//')"
31
+
32
+ # ── D. input defaults must be values the CLI accepts (2026-09-06) ────────────────────────────
33
+ # WHY THIS EXISTS. The lanes above test the exit-code MAPPING and nothing else — the header says
34
+ # so. Nobody was checking the other direction: that the values this action sends the CLI are
35
+ # values the CLI takes. v3.1.0 shipped `level` defaulting to 'standard', which `fh-gate.sh` has
36
+ # never accepted; it rejects with ARG_ERROR (11), and ARG_ERROR is in fail-on's own default set.
37
+ # So `uses: chrono-meta/forge-harness@v3.1.0` with no `level:` failed the step, and the error
38
+ # named an argument the user never set. Found by running the PUBLISHED tarball as a consumer
39
+ # would, not by reading either file — which is why the standpoint arm is not optional.
40
+ #
41
+ # Both sides are extracted from the REAL files. A hard-coded expected value here would drift the
42
+ # same way the default did, and this lane would stay green while the shipped file rotted.
43
+ _ACTION_LEVEL_DEFAULT="$(awk '
44
+ /^ level:/ {inlvl=1; next}
45
+ inlvl && /^ [a-z-]+:/ {inlvl=0}
46
+ inlvl && /^ *default:/ {gsub(/^ *default: *|""|\x27/,""); print; exit}
47
+ ' "$A")"
48
+ _GATE_SH="$ROOT/scripts/fh-gate.sh"
49
+ if [ ! -f "$_GATE_SH" ]; then
50
+ no "D0 HARNESS — scripts/fh-gate.sh absent; cannot learn which levels the CLI accepts"
51
+ else
52
+ # the validation line is the single source of truth for the accepted set
53
+ _ACCEPTED="$(grep -oE '"\$GATE_LEVEL" != "[a-z]+"' "$_GATE_SH" | grep -oE '"[a-z]+"$' | tr -d '"' | sort -u | tr '\n' ' ')"
54
+ [ -n "$_ACCEPTED" ] && ok "D0 extracted the CLI's accepted level set from fh-gate.sh: [$_ACCEPTED]" \
55
+ || no "D0 could not extract an accepted-level set from fh-gate.sh (instrument dead — the check below would pass vacuously)"
56
+ case " $_ACCEPTED " in
57
+ *" $_ACTION_LEVEL_DEFAULT "*) ok "D1 action.yml level default ('$_ACTION_LEVEL_DEFAULT') is a value the CLI accepts" ;;
58
+ *) no "D1 action.yml level default ('$_ACTION_LEVEL_DEFAULT') is NOT in the CLI's accepted set [$_ACCEPTED] — every step that does not override level: will exit ARG_ERROR" ;;
59
+ esac
60
+ # control: the check must reject a value the CLI does not take. If this passes, D1 proves nothing.
61
+ case " $_ACCEPTED " in
62
+ *" standard "*) no "D2 control dead — 'standard' appears accepted, so D1 cannot discriminate" ;;
63
+ *) ok "D2 control — the same test rejects 'standard' (the value v3.1.0 shipped), so D1 discriminates" ;;
64
+ esac
65
+
66
+ # D3/D4 — the SAME check for `backend`, because closing the instance and leaving the class open
67
+ # is how this defect comes back wearing a different input's name. `backend` is the other
68
+ # enum-shaped input the action forwards; `model` (free-form, empty = backend default) and
69
+ # `timeout` (numeric, matches the gate's own 120) have no closed set to drift against, so they
70
+ # are deliberately not lanes — an assertion with nothing to assert is decoration.
71
+ _ACTION_BACKEND_DEFAULT="$(awk '
72
+ /^ backend:/ {inb=1; next}
73
+ inb && /^ [a-z-]+:/ {inb=0}
74
+ inb && /^ *default:/ {gsub(/^ *default: *|""|\x27/,""); print; exit}
75
+ ' "$A")"
76
+ _ACCEPTED_BE="$(grep -oE "must be '[a-z]+', '[a-z]+', '[a-z]+', or '[a-z]+'" "$_GATE_SH" \
77
+ | grep -oE "'[a-z]+'" | tr -d "'" | sort -u | tr '\n' ' ')"
78
+ if [ -z "$_ACCEPTED_BE" ]; then
79
+ no "D3 could not extract the accepted backend set from fh-gate.sh (instrument dead — D4 would pass vacuously)"
80
+ else
81
+ ok "D3 extracted the CLI's accepted backend set: [$_ACCEPTED_BE]"
82
+ case " $_ACCEPTED_BE " in
83
+ *" $_ACTION_BACKEND_DEFAULT "*) ok "D4 action.yml backend default ('$_ACTION_BACKEND_DEFAULT') is a value the CLI accepts" ;;
84
+ *) no "D4 action.yml backend default ('$_ACTION_BACKEND_DEFAULT') is NOT in [$_ACCEPTED_BE]" ;;
85
+ esac
86
+ case " $_ACCEPTED_BE " in
87
+ *" anthropic "*) no "D5 control dead — a non-existent backend appears accepted" ;;
88
+ *) ok "D5 control — the same test rejects 'anthropic' (a plausible-but-wrong value), so D4 discriminates" ;;
89
+ esac
90
+ fi
91
+ fi
92
+
93
+
31
94
  [ -n "$MAP" ] || { echo "❌ HARNESS: could not extract the case block from action.yml"; exit 10; }
32
95
  printf '%s' "$MAP" | grep -q 'esac' || { echo "❌ HARNESS: extracted block has no esac (truncated)"; exit 10; }
33
96