@chrono-meta/fh-gate 2.0.1 → 2.1.0

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 (32) hide show
  1. package/.claude/rules/fh_4axis_gate.md +77 -1
  2. package/.claude-plugin/marketplace.json +3 -3
  3. package/AGENTS.md +19 -3
  4. package/CLAUDE.md +240 -4
  5. package/knowledge/shared/harness-core/capability_composition_contract.md +68 -0
  6. package/knowledge/shared/harness-core/fh_three_layer_canon.md +38 -12
  7. package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +115 -3
  8. package/knowledge/shared/harness-core/harness_incubator_doctrine.md +82 -0
  9. package/knowledge/shared/harness-core/harness_terminal_correlation_and_recommendations.md +49 -8
  10. package/knowledge/shared/harness-core/ship_readiness_gate.md +205 -1
  11. package/knowledge/shared/learnings/subagent_invocations_log.yaml +66 -0
  12. package/knowledge/shared/rules/sister_asset_protocol.md +12 -0
  13. package/package.json +3 -2
  14. package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
  15. package/plugins/fh-commons/agents/quench-challenger.md +6 -1
  16. package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
  17. package/plugins/fh-meta/CHANGELOG.md +43 -0
  18. package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +14 -2
  19. package/plugins/fh-meta/skills/steel-quench/SKILL_detail.md +13 -0
  20. package/plugins/fh-meta/skills/verify-bidirectional/SKILL.md +34 -1
  21. package/scripts/capability_registry_check.sh +75 -12
  22. package/scripts/chamber_run.sh +24 -3
  23. package/scripts/chamber_witness.sh +25 -0
  24. package/scripts/fh-gate.sh +16 -1
  25. package/scripts/package_coverage_check.sh +10 -0
  26. package/scripts/prepublish_scope_note.sh +139 -0
  27. package/scripts/selfcheck.sh +30 -5
  28. package/scripts/test_fh_gate_regressions.sh +21 -9
  29. package/scripts/test_marker_axes_run_lanes.sh +87 -3
  30. package/scripts/test_marker_crossfamily_lanes.sh +31 -1
  31. package/templates/.git-hooks/pre-commit +225 -10
  32. package/templates/subagent-tally-hook.json +2 -2
@@ -67,7 +67,11 @@ No user request is needed — this is a mandatory autonomous step, not a proposa
67
67
  FH asset modified → Axis 1 (templates/regression_guard.sh --pr {BRANCH})
68
68
  → Axis 2 (/steel-quench) → Axis 3 (/phantom-quench)
69
69
  → marker: tracks/_meta/.axes_23_passed_{branch}_{date}.marker
70
- (required fields: axis2-engine / axis2-model / floor-status / axis2-evidence;
70
+ (required fields: axis2-engine / axis2-model / floor-status / axis2-evidence /
71
+ **`axes-run:`** + **`controls:`** (see §Marker axis fields below — these were enforced by the
72
+ hook since 2026-08-10 and listed in NO rule file until 2026-08-17; the spec lived only in the
73
+ hook and in two CLAUDE.md/AGENTS.md one-liners, so an author reading the rules could not learn
74
+ the format that blocks their commit);
71
75
  + **`crossfamily:`** — required on LOAD-BEARING changes only, and TYPED since 2026-08-08:
72
76
  `panel(<families>)` | `declined` | `DEGRADED_SINGLE_FAMILY` | `DEGRADED_PANEL_UNUSED` |
73
77
  `UNKNOWN`, the three degrade values requiring substantive grounds on the same line.
@@ -126,6 +130,78 @@ no runnable path exists (run-first, ask-last — sonnet_floor_doctrine.md §Auto
126
130
 
127
131
  Record sim results in the Axes 2–3 marker + sub-agent invocation log.
128
132
 
133
+ ### Marker axis fields — `axes-run:` · `controls:` · `standpoint:`
134
+
135
+ These are enforced (the first two) or expected (the third) on the Axes 2–3 marker. Until 2026-08-17
136
+ **none of the three appeared in any rule file** — the format that hard-blocks the commit was legible
137
+ only from `templates/.git-hooks/pre-commit` itself. That is a gate-locality defect on the *spec*
138
+ side: the rule the author reads did not describe the check the author must pass.
139
+
140
+ **`axes-run:` — one line, all keys present, `none` for axes not run.** Silence is not zero: an axis
141
+ you skipped is written `none`, never omitted. Omitting it makes the reader parse an absence as a
142
+ decision.
143
+
144
+ ```
145
+ # markers dated BEFORE 2026-08-17 — old 4-axis array, ASCII keys
146
+ axes-run: a=<다른 계열> b=<첫 실사용> c=<기록 그라운딩> d=<되돌림 실측>
147
+
148
+ # markers dated 2026-08-17 OR LATER — 6-axis array, circled keys
149
+ axes-run: ⓐ=<다른 계열> ⓑ=→standpoint ⓒ=<격리 그라운딩> ⓓ=<3자 대면> ⓔ=<첫 실사용> ⓕ=<되돌림 실측>
150
+ ```
151
+
152
+ 🟥 **The two arrays are not the same letters shifted — `b` and `d` mean different axes in each.**
153
+ Old `b`=첫 실사용 is now **ⓔ**; old `d`=되돌림 is now **ⓕ**. Copying an old line forward silently
154
+ swaps two axes, and no error fires.
155
+
156
+ **Which array a marker used is decided by the date in its filename** (`< 2026-08-17` = old 4-axis).
157
+ ⚠️ **The notation is NOT the discriminator** — the first version of this section said it was
158
+ ("circled keys = 6-axis, so an auditor can grep"), and a hand-count of the corpus refuted it: of 53
159
+ markers carrying `axes-run`, 4 use circled keys and 1 mixes, and **2 of those 4 are dated 2026-08-10
160
+ while meaning the OLD axes** (`ⓑ 첫실사용` · `ⓓ 되돌림` — ⓔ and ⓕ in the current array). The hook
161
+ never reads those markers so no commit is affected; the party that gets a wrong answer is the
162
+ **auditor**. Aligning the notation is still worth it going forward — it stops the next author from
163
+ copying an old line — but it does not work backwards.
164
+
165
+ ⚠️ **The grace-date comparison is an unreachable branch in production, and saying so is the point.**
166
+ The call site builds the marker path from `${TODAY}`, so `mdate` is always today; no real commit
167
+ after 2026-08-17 takes the `six=0` path, and the only live consumer is the fixture suite. **What
168
+ protected the existing markers was the path construction, not the constant** — the same reason
169
+ `crossfamily:` could go from free prose to a closed enum eight days earlier with no cutoff at all.
170
+
171
+ - **ⓑ입장 carries a pointer, not a value** — `standpoint:` is its canonical field, so writing the
172
+ value in both places would be a double record. `ⓑ=→standpoint` requires a non-empty `standpoint:`
173
+ line to exist (a pointer at nothing is not a record). Enforced on any ⓑ value *referencing*
174
+ standpoint, arrow or not — requiring the arrow was fail-open and was measured as such.
175
+ - **Multiple `axes-run:` lines block.** Only the first is read, so a second line is not *rejected*,
176
+ it is **invisible** — anything written there bypasses the check entirely.
177
+ - **Grace dates are deliberate**: `AXES_RUN_GRACE_DATE=2026-08-10`, `SIX_AXES_GRACE_DATE=2026-08-17`,
178
+ compared against the date in the marker's **filename**, boundary `<` (the grace day itself is
179
+ required). Retroactive enforcement would block every in-flight branch, and that over-block trains
180
+ `--no-verify`, which disarms the Destructive-Op gate living in the same hook.
181
+
182
+ **`controls:` — the liveness of the controls, not their existence.** An axis is «run» only with an
183
+ execution output in which a control was alive; "붙였다" without a life/death token is vacuous and
184
+ blocks. Note the asymmetry this field exists for: *having* a control is not *having discrimination*
185
+ — a known-negative only tells you whether false positives exist at all.
186
+
187
+ ```
188
+ controls: alive — known-positive 'X' 3 hits · known-negative 0
189
+ controls: n/a — no measurement in this delta (<reason>)
190
+ ```
191
+
192
+ **`standpoint:`** — closed enum, canonical spec in
193
+ `knowledge/shared/harness-core/field_verdict_crossfamily_gate.md §7`. **Still validated by nothing**
194
+ — zero hook lines, no fixture suite. Recorded here so the gap is visible from the rules side rather
195
+ than only from the hook's absence.
196
+
197
+ **Scope, unchanged**: form + non-vacuity + auditability, **never provenance**. A marker claiming
198
+ `ⓐ=codex` when codex never ran passes — deliberately. Catching that is cross-family review *reading*
199
+ the marker, which is not this hook's job.
200
+
201
+ > Fixtures: `scripts/test_marker_axes_run_lanes.sh` (25 lanes, incl. wiring anchor + over-block
202
+ > controls). Extending the format means adding lanes there, and running the **revert probe** — a
203
+ > lane that stays green when you disable the branch it claims to anchor is decoration.
204
+
129
205
  ### Reviewer-visible evidence — the marker is not readable from the other side
130
206
 
131
207
  **Rule**: a PR touching FH assets carries a **sanitized evidence capsule** in its body — what was
@@ -11,13 +11,13 @@
11
11
  "plugins": [
12
12
  {
13
13
  "name": "fh-meta",
14
- "version": "2.0.1",
15
- "description": "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.",
14
+ "version": "2.1.0",
15
+ "description": "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": "2.0.1",
20
+ "version": "2.1.0",
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
  }
package/AGENTS.md CHANGED
@@ -116,9 +116,25 @@ Because non-Claude runtimes do not auto-load Claude path rules, apply these rule
116
116
  (2026-08-09) and §1-a-2 expanded them to six (2026-08-16)**: ⓐ different family · ⓑ standpoint ·
117
117
  ⓒ isolated grounding · ⓓ third-party encounter · ⓔ first real use · ⓕ revert-and-observe.
118
118
  Axes are separated by **what they received**, not by how adversarial they are — same input,
119
- same blind spot, however many reviewers you add. ⚠️ The machine layer is still four: `axes-run`
120
- requires four letters, has its own marker field, and **ⓓ has no field at all** — record it in
121
- prose and do not read a silent as a run one. Read it before recording; §1-c
119
+ same blind spot, however many reviewers you add. **The machine layer now carries all six**
120
+ (2026-08-17): a marker dated on/after that day must write `axes-run:` with the **circled keys**
121
+ `ⓐ=… ⓑ=→standpoint ⓒ=… ⓓ=… ⓔ=… ⓕ=…`; markers dated earlier keep the old **ASCII four**
122
+ (`a b c d`) and are not retroactively blocked. 🟥 **The two arrays are not the same letters —
123
+ old `b` (first real use) is now `ⓔ`, old `d` (revert probe) is now `ⓕ`.** Copying an old line
124
+ forward silently swaps two axes and raises no error, which is why the *notation itself* declares
125
+ which array a marker used. `standpoint:` remains the canonical field for ⓑ (the `axes-run` entry
126
+ is only a pointer to it, and a pointer at an empty field is blocked); **its value enum is still
127
+ validated by nothing** — that is the one remaining gap, and it is not the same thing as the axis
128
+ being unmechanized. Format spec: `.claude/rules/fh_4axis_gate.md §Marker axis fields`.
129
+ (Two drift corrections landed here on 2026-08-17: first this sentence said "four" while its own
130
+ next clause described the +1 — caught by the session-close ④-b CC↔Codex parity check — and then
131
+ the machine layer moved to six the same day.)
132
+ **ⓓ has no field at all** — record it in prose and do not read a silent ⓓ as a run one.
133
+ 🟥 ⓑ **standpoint is itself split** (2026-08-17): a STATIC read of the target's own files is
134
+ `tier1b` and **executes nothing**; `tier2`+ asserts that something was RUN — the discriminator is
135
+ mechanical, *name the command you ran and the output you saw*. Measured on one delta: the static
136
+ arm found 1, running the target's own suite found 2 more, one of which printed neither `FAIL` nor
137
+ `❌`. Execution is the half with no substitute; see `field_verdict_crossfamily_gate.md §7`. Read it before recording; §1-c
122
138
  holds the sample limits — read it before citing. This record is self-attested and has no hook
123
139
  behind it; it is closed by a different-family reader, not by writing it more carefully.
124
140
  8. **Branch-surface claims:** GitHub branch protection is two independent layers — legacy
package/CLAUDE.md CHANGED
@@ -50,7 +50,7 @@ core invariants never melt). The nursery also **verifies what it births**: harne
50
50
  > 4-Axis Auto-Gate 와 **같은 트리거**이므로 새 트리거도 새 파일도 만들지 않는다 — 기록 자리는
51
51
  > **4축 마커의 기존 필드**(`axis2-*` · `axis3-*` · `residual`)다.
52
52
  > **마커에 반드시 남는 3줄** ① **①영혼** — 설계 *전에* 쓴 «성공 정의 / 절대 안 함»(없으면 `없음`)
53
- > · ② **돌린 축과 안 돌린 축을 각각 이름으로.** 마커 `axes-run` 기계적으로 요구하는 것은 **네 글자**(a=계열 · b=첫실사용 · c=격리 그라운딩 · d=되돌림)이고, ⓑ**입장**은 마커의 **`standpoint:` 자기 필드**로, ⓓ**3자대면**은 **아직 기록할 필드가 없다**(명시된 잔여). 즉 산문 정본은 6축인데 기계는 4+1축이다 어긋남을 알고 쓴다
53
+ > · ② **돌린 축과 안 돌린 축을 각각 이름으로.** 마커 `axes-run` **2026-08-17 부로 여섯 글자**를 요구한다 — **기호 키**(ⓐ계열 · ⓑ입장 · ⓒ격리 그라운딩 · ⓓ3자대면 · ⓔ첫실사용 · ⓕ되돌림). 그 전 날짜의 마커는 옛 **ASCII 네 글자**(a·b·c·d) 그대로다. 🟥 **두 배열은 같은 글자가 다른 축을 가리킨다** — 옛 `b`=첫실사용은 지금 **ⓔ**, 옛 `d`=되돌림은 지금 **ⓕ** 라서, 옛 줄을 그대로 옮기면 축 둘이 조용히 뒤바뀌고 아무 오류도 안 난다. **어느 배열인지는 마커 파일명의 날짜로 판별한다**(`< 2026-08-17` = 옛 4축). ⚠️ **표기법은 판별자가 아니다** — 초판이 «기호 키를 보면 6축인 줄 안다» 고 적었는데 **코퍼스 실측이 반증했다**: axes-run 보유 53건 중 기호 키가 4건인데 그중 **2건이 2026-08-10 자이면서 옛 4축 의미로 기호를 쓴다**(`ⓑ 첫실사용` · `ⓓ 되돌림` — 현 배열에선 각각 ⓔ·ⓕ), 혼용도 1건 있다. 훅은 그 셋을 안 읽으므로 커밋은 안 막지만 **감사자의 grep 은 거기서 틀린 답을 낸다**. ⓑ입장은 값을 여기 적지 않고 **`standpoint:` 자기 필드**를 가리킨다(`ⓑ=→standpoint`, 줄이 비면 죽은 포인터라 차단). 즉 산문 정본과 기계가 **축 개수로는 맞았고**, 남은 어긋남은 `standpoint:` 값을 **검증하는 코드가 아직 0줄**이라는 것 하나다(명시된 잔여). 형식 정본 = `.claude/rules/fh_4axis_gate.md §Marker axis fields`
54
54
  > · ③ **각 축의 컨트롤과 그 생사**. 축을 «돌렸다»의 **최소 증거 = 컨트롤이 살아 있는 실행 출력**
55
55
  > 이다 — 안 고른 이유만 적은 것은 준수가 아니다.
56
56
  > **비용 경계**: 넷을 매번 다 돌리지 않는다. 실패 모드에 맞춰 **고른다**.
@@ -122,6 +122,142 @@ onto their single hardest sub-mechanism, and a failure from a **non-harness** ru
122
122
  metric — each read a live-but-incomplete thing as zero, each caught by the operator, not self-caught.
123
123
  Detail: `[[feedback_reinvention_reflex_normalization_counterweight]]`.)
124
124
 
125
+ ## Mechanization Boundary — machinery at irreversible edges and channels, judgment left to evolution
126
+
127
+ **Operator thesis (2026-08-16, verbatim)**: *"기계는 비가역 경계와 채널에만 두고, 판단은 진화에
128
+ 맡긴다. 「한 모델로도 도달하지만 진화에 기대어 100%를 뽑는다」는 그 형태에서만 성립한다 — 판단을
129
+ 내가 코드로 굳혀두면 그게 바로 진화를 막는 천장이 되니까."*
130
+
131
+ This is the standing answer to *"should this become a check?"*, and it is **not** "mechanize less":
132
+
133
+ | Build machinery | Leave to judgment |
134
+ |---|---|
135
+ | **Irreversible boundaries** — publish · delete · history-rewrite · anything a stranger can observe | Whether a given review was deep enough |
136
+ | **Channels** — that a typed field carries a value, that a verdict is typed not grepped, that grounds are attributable | What the right value *is* |
137
+
138
+ The discriminator: does the check assert a **property of the record** (present · typed · attributable ·
139
+ non-vacuous), or does it assert a **conclusion**? The first is a channel and ages well. The second
140
+ freezes today's judgment into tomorrow's ceiling — and this repo's own thesis is that the model layer
141
+ converges upward while the harness persists, so a frozen conclusion is a harness that gets *worse*
142
+ relative to what it wraps.
143
+
144
+ **Corollary — tier-visible behavior is not automatically a defect.** Some FH capability only becomes
145
+ reachable at a higher tier. `sonnet_floor_doctrine.md` is unchanged and remains a floor: **base ops
146
+ must run 100% at Sonnet, and a tier-gated *base op* is still a defect.** What this corollary adds is
147
+ the other side — where the gap is in *judgment quality* rather than in whether the capability fires,
148
+ the answer is not always to encode the judgment. Discipline and channel-typing are how Sonnet reaches
149
+ it; a frozen rule is how nobody ever exceeds it.
150
+
151
+ ⚠️ **Applied honestly to this file's own machinery, same day**: the `declined`-grounds lane added to
152
+ `templates/.git-hooks/pre-commit` is a **channel** check (a claim must name attributable grounds) —
153
+ it does not judge whether decorrelation was warranted. But its grounds test is a *vocabulary grep*,
154
+ and a vocabulary list is a small frozen judgment: a legitimately-phrased `declined` in unforeseen
155
+ wording over-blocks. Accepted because the failure is **loud and cheap** (author rephrases) rather
156
+ than silent, and because it mirrors the existing degrade-branch form — named here rather than
157
+ claimed pure.
158
+
159
+ ## Local Execution First — CI is a backstop, never the discovery mechanism
160
+
161
+ **Operator, 2026-08-16**: *"이 실패가 CI 확인 단계에서야 발견되는 건 매우 늦다 … 로컬에서 그
162
+ [대상 레포]를 통해서 실제로 구동시켜 봤다면 안 발생했을까"* and *"CI 확인도 중요하지만 사실 이는
163
+ **깃헙의 기능에 기대는 것**이라고 봐야 하려나."*
164
+
165
+ Both halves are load-bearing. **Late**: a red CI check is discovery at the slowest, most expensive
166
+ point in the loop, after push, after the PR, in front of an audience. **Borrowed**: CI is a
167
+ *platform* feature, so a harness that only finds its own defects there has not built a gate — it has
168
+ outsourced one, and it silently inherits that platform's coverage boundaries as its own.
169
+
170
+ **The order**: run the target's own suite locally, **to completion**, before pushing. Then let CI
171
+ confirm. A green CI on a change whose suite was never run locally is not a second opinion — it is the
172
+ *first* one.
173
+
174
+ **Why "to completion" is the operative phrase** (measured 2026-08-16, pmh-dev): that repo's
175
+ `validate.yml` was wired the same day, so CI's first run was the suite's first real execution ever —
176
+ there was no "previously known-good" for it to confirm. A partial local run would have missed it too:
177
+ the suite printed `SELFCHECK: FAIL` while **neither `FAIL` nor `❌` appeared anywhere in its output**
178
+ (the failing lane used its own vocabulary, `INSTRUMENT ERROR`), so locating it needed `bash -x` to
179
+ the actual failing line. Reading the tail, grepping for the expected token, or trusting an exit code
180
+ you did not trace are all forms of not-running-it.
181
+
182
+ **Relationship to the standpoint axis**: this is that axis's execution half, applied to your own
183
+ change rather than to a peer harness — see `field_verdict_crossfamily_gate.md §7`
184
+ «execution is the load-bearing half». Same principle, two surfaces.
185
+
186
+ ## Skeleton, Not Muscle — a wiring change is DONE when the floor tier executes it
187
+
188
+ **Operator, 2026-08-16, verbatim**: *"배선에 대한 건 소넷이 실제로 돌아갈 수 있는지 봐야 잘 된
189
+ 거니까. **근육이 아니라 뼈대 기준으로 돌아야 하는 거야.**"* — and, on having had to ask for it:
190
+ *"초기라서 내가 계속 이렇게 해봐라고 메뉴얼로 요청하고 있는데 **알아서 해야 할 거야.**"*
191
+
192
+ **근육(muscle)** = a strong model's raw capability carrying a rule that is not actually wired.
193
+ **뼈대(skeleton)** = the harness itself — the structure that makes the rule fire regardless of who
194
+ is running. A rule that only works because the session was smart enough is not wired; it is being
195
+ *carried*. It fails silently the moment a Sonnet session, a fresh install, or a compacted context
196
+ picks it up — which is every install that is not the author's.
197
+
198
+ **So the definition of done changes.** For any salience-dependent change (a rule, a trigger, an
199
+ enum value, an onboarding path, a doctrine line), "done" is **not** «the text is correct and a
200
+ reviewer agrees». It is: **a blind session at the floor tier, given a realistic situation and not
201
+ told which rule is being tested, actually fires it.** This upgrades `fh_4axis_gate.md`'s target-tier
202
+ sim gate from a near-mandatory step into the completion criterion itself.
203
+
204
+ **And it is self-dispatched.** Do not wait to be asked to run it. The operator asking *"소넷이 실제로
205
+ 돌아갈지 확인은 하겠지?"* is the failure — the sim is part of authoring the change, like the
206
+ known-pair is part of authoring an instrument.
207
+
208
+ **Why «reads correctly» is not evidence.** A `tier1b` rung was added to the `standpoint:` enum
209
+ precisely so a static review would stop being recorded as `tier2`. The text was correct; a reader
210
+ would agree — and a reader agreeing is not a measurement, which is this paragraph's whole point.
211
+
212
+ 🟥 **RETRACTED (2026-08-17) — the numbers this paragraph used to cite are withdrawn, in BOTH
213
+ directions.** It read: *"Two independent blind Sonnet sims then graded a pure cold-read as `tier2`,
214
+ **0/2** … A static read of my own fix would have scored it PASS. Only running it found the hole."*
215
+ That sim set was **8 runs at `tool_uses: 0`** — the agents never opened a file, so the instrument
216
+ was dead and the grades measure nothing (`tracks/_meta/fh_completed_2026-08-16.md:690`, retracted
217
+ the same day the doctrine was written and **before** this paragraph's own commit). The re-run with a
218
+ live instrument then landed the **opposite** result — the rung was graded correctly — at **reps=1**,
219
+ below this repo's own `reps>=3` bar. **So neither «it failed» nor «it worked» is established.** Do
220
+ not restore either number, and do not read the retraction as proof of the inverse.
221
+
222
+ **The claim that survives is narrower and does not need those numbers**: a static read cannot
223
+ establish that a rule *fires*, because the thing being tested is whether a reader who is not the
224
+ author lands on the right rung — and the author reading their own text is the one reader guaranteed
225
+ to. That is an argument about what a read can measure, not a measurement. The general principle
226
+ (`field_verdict_crossfamily_gate.md §7`'s execution-over-static asymmetry) rests on its own separate
227
+ field evidence; **this paragraph is no longer one of its data points.**
228
+
229
+ **Corollary — what a sim failure means.** It is a defect in the *wiring*, not in the floor model.
230
+ The response is to make the rule fire (disambiguate, give it a mechanical discriminator, move it to
231
+ where the actor reads it) — never to conclude the tier is too weak and move on. That conclusion is
232
+ how a harness quietly becomes tier-gated, which `sonnet_floor_doctrine.md` calls a defect of the
233
+ same severity class as a phantom reference.
234
+
235
+ ### Scope, and the target state it exists for (operator, 2026-08-16)
236
+
237
+ **Scope — not FH-only**: *"FH뿐만이 아니라 **기계화 뼈대를 통해서 돌아가는 것들은 다** 이러한 과정을
238
+ 거쳐야 제대로 돌아가는지 아닌지 파악할 수 있을 거니까."* Anything whose behavior depends on a
239
+ mechanized skeleton is in scope: field harnesses, propagated `templates/`, a mapped project's own
240
+ gates, **and code contributed upstream to someone else's repo**. The question «does this actually
241
+ fire for a reader who is not me, at the floor tier?» does not become optional because the artifact
242
+ lives outside this repo.
243
+
244
+ **Target state**: *"나머지는 정말 **저자(인간 저자)의 취향만** PR에서 첨삭할 수 있도록 하는 게
245
+ 목표야."* A PR should arrive with every **mechanical** question already settled — does it fire ·
246
+ does it degrade in the safe direction · does the floor tier execute it · is the claim reproducible —
247
+ so that the only thing left for the human reviewer is **taste**: naming, framing, whether this is
248
+ the change they want. Review time spent re-deriving whether the thing works is review time
249
+ *taken from* the judgment only a human can supply.
250
+
251
+ **Existence proof, ours, this session**: *"우리가 최근에 클로드온데스크에 기여한 것처럼."*
252
+ `rullerzhou-afk/clawd-on-desk` PR #888 was merged **exactly as submitted, with no changes
253
+ requested** — the owner's words: *"focused, technically sound, and well-tested … we merged it
254
+ exactly as submitted, with no changes needed."* That is the shape: the mechanical case was closed
255
+ before submission (a fixture whose potency was reasoned about in-comment, a lane that re-executes
256
+ the real consumer path rather than asserting a flag), so nothing was left to negotiate but whether
257
+ they wanted it. **This is the bar to hold ourselves to on every outbound PR**, and it is why the
258
+ survivor-lane technique from that same PR is worth absorbing rather than admiring
259
+ (`tracks/_meta/fh_signal_2026-08-16_clawd-survivor-lane-air.md`).
260
+
125
261
  ## Instrument Calibration — before you trust a number, prove the instrument works *here*
126
262
 
127
263
  An instrument (a scan, a grep, a checker, a diagnostic row, a metric) is a claim about the world only
@@ -387,12 +523,27 @@ raises resolution *within* one standpoint (the author's own repo, the author's o
387
523
  target's rules); it does not decorrelate the review's ground-truth source. For a **shared-body /
388
524
  cross-harness-boundary** change — scoped by *effect* (alters another harness's behavior, gate
389
525
  outcome, or interaction contract), not merely by touching a synced file path — the marker
390
- additionally carries `standpoint:` — a closed enum (`tier1` content-only · `tier2(<harness>)`
391
- peer-simulated, ran the target's own repo · `tier2b(<harness>)` same operator, target's real
526
+ additionally carries `standpoint:` — a closed enum (`tier1` content-only · **`tier1b(<harness>)`
527
+ STATIC read of the target's own files, executed nothing** · `tier2(<harness>)`
528
+ peer-simulated, **EXECUTED CODE in** the target's own repo — 🟥 the discriminator is mechanical:
529
+ *name the command you ran and the output you saw*; cannot name one → `tier1b`, always. Reading the
530
+ target's real files, however cold, is `tier1b` (🟥 the "two blind Sonnet sims graded a cold-read
531
+ `tier2`" citation that stood here is **RETRACTED** — dead instrument, `tool_uses: 0`; the live re-run
532
+ inverted it at reps=1, below bar. The **rule** stands on its own wording, not on that sim) ·
533
+ `tier2b(<harness>)` same operator, target's real
392
534
  runtime (local wiring visible, not independent) · `tier3(<harness>)` a *different* operator of the
393
535
  target harness ran it · `not-applicable` · degrade triad `DEGRADED_NO_TARGET_ACCESS` could-not /
394
536
  `DEGRADED_NOT_RUN` did-not / `UNKNOWN` did-not-look — same shape as `crossfamily:`'s triad,
395
- **distinct literal values**, do not reuse crossfamily's tokens). Naming note: this collides in
537
+ **distinct literal values**, do not reuse crossfamily's tokens).
538
+ 🟥 **The execution is the load-bearing half** (operator decision 2026-08-16): a static standpoint
539
+ read competes with cross-family review for the same defect classes and mostly loses — *running the
540
+ target harness locally, to completion*, is the part with no substitute. Measured on one delta the
541
+ same day: static read found 1, running the target's own suite found 2 more, one of which printed
542
+ neither `FAIL` nor `❌` and was unreachable by any read. So **`tier2`+ asserts something was RUN** —
543
+ if the review only read, it is `tier1b`, and `tier1b` is deliberately the weak rung so that
544
+ recording it honestly surfaces that the execution arm is still owed. (Broken on the day it was
545
+ written — a static read was recorded as `tier2` because `tier1b` did not yet exist; a missing rung
546
+ gets filled by the next one up rather than staying empty.) Naming note: this collides in
396
547
  English with FH's own persona/viewpoint sense of "standpoint" (`fh-meta:beginner`/`main-player`/
397
548
  `expert`) — a different axis (which persona reviews, not whose repo is ground truth); kept as-is,
398
549
  not renamed, but do not conflate the two. **Prose-only today** — unlike `crossfamily:`, no
@@ -615,6 +766,7 @@ Proposal format: `"I see [X]. Want me to run /[skill] to [one-line description]?
615
766
  | **"이 절 잘라도 되나", "상주에서 빼자", "cut this section", "is this section load-bearing", "ablate this"** — a proposal to REMOVE resident text (the decision `/context-doctor` and `/salience-splitter` reach, not the routing to them) | **Ablation procedure — do not decide by eye.** Canon = `scripts/probe_scope_check.sh` header (arms · isolation · `reps>=3` · pre-registration · the two leak channels); runner precondition = `bash scripts/ablation_calibrate.sh` exits 0; verdicts land in `.claude/regression/ablation_verdicts.md`. **A section is CUT only on a pre-registered question set an isolated arm B answers correctly** — "I read it and it looks redundant" is not a measurement, and arm B answering *confidently wrong* is a KEEP, not a pass |
616
767
  | "wrap up this week", "review", "audit", "weekly", "retrospective" | `/harvest-loop` |
617
768
  | "pull this into FH", "reverse-harvest", "worth keeping", "harvest pattern", "field pattern" | `/field-harvest` |
769
+ | **you installed or invoked an EXTERNAL asset (a tool, framework, or repo not ours) and ran it against something this hub owns** — `pip install`/`npm i` of an outside framework, cloning a peer repo to run it, adopting an upstream utility. Fires on the ACT, not on a keyword: the trigger is *"I reached outside because ours did not cover this"* | **Sister Asset Protocol** (`knowledge/shared/rules/sister_asset_protocol.md` §Active adoption) — record the resolution difference, list **items to import** AND **items the hub can propagate** (bidirectionality is a prohibition, not a nicety), and where there is no write access write a `tracks/_audit/proposal_*.md` so the operator can decide whether to contribute it upstream. 🟥 Missed 2026-08-16 on exactly this shape: an external red-team framework was installed, run against a field harness, found a real bypass — and was filed as a `type: reference` **tool pointer** with no sister audit at all |
618
770
  | "용광로모드", "crucible mode", "absorb this whole corpus", "throw everything in", "re-forge FH identity", "melt this down" (total-immersion absorption, not cherry-pick — esp. a whole corpus on a core FH axis, or a frontier showcase risking FOMO) | `knowledge/shared/harness-core/crucible_mode.md` (read it, run the chain: total-ingest → steel-quench/phantom-quench melt → governor identity-bonding → sim/persona reforge → field-harvest rebirth; the core invariants stay unmeltable) |
619
771
  | "review this PR", "check diff", "code review" | code diff → built-in `/code-review`·`/review` · FH-asset coherence → `/hub-cc-pr-reviewer` (role split) |
620
772
  | "keep watching X", "poll this", "check every N minutes", recurring WATCH item | built-in `/loop` (interval runner) — pair with the WATCH list, don't hand-poll |
@@ -660,6 +812,40 @@ At session start, determine the last run time from history files and auto-propos
660
812
 
661
813
  > A cadence reminder the user has repeatedly declined is **muted** per the UAP (see the loop below) — don't re-nag.
662
814
 
815
+ #### Expedition (원정) — measured first, cadence only if it earns one
816
+
817
+ **Operator, agreed and recorded 2026-08-17** (it had been agreed verbally before and was **not in any
818
+ file** — grepped, zero hits; that gap is why this paragraph exists): *"원정이 가치 있고 성공적이었다면
819
+ **주기적으로 제안하는 것**으로 가기로 했었지."*
820
+
821
+ An **expedition** is a deliberate, extended run that uses the harness cluster at full stretch against
822
+ targets outside this hub — contributing to an external repo, declaring a peer's assets as cluster
823
+ nodes, driving a foreign codebase through FH's own gates. Its operating conditions are unusual and
824
+ must not be judged by another track's: **token cost is expected and pre-approved** (the `/goal-quench`
825
+ budget gate still applies — approval removes the prompt, never the gate), **one session will not
826
+ finish it**, and the success definition is not "expedition completed" but ⓐ **finding the pieces that
827
+ move identities to 🟢 faster than internal work would** and ⓑ **hardening existing weak points by
828
+ stressing them somewhere real**. An expedition that builds nothing and produces those two has succeeded.
829
+
830
+ **Promotion path — and the interval is set AFTER the first one, not before** (operator, 2026-08-17:
831
+ *"그 주기를 얼마나에 한 번씩 잡아야 할지도 그때 세우도록 할게 — 원정 한 차례 다 마치고 답습한 후에"*).
832
+ Expeditions are **not** on a cadence today; they are proposed case-by-case.
833
+
834
+ Sequence, in order, and do not skip to the end:
835
+ 1. **Run one, all the way through.** Not a slice — a complete expedition, however many sessions it
836
+ takes (the thread-continuation block on the session card is the carrier).
837
+ 2. **Absorb it** (답습) — what came back, what it cost, what it hardened, what it found that internal
838
+ work would not have.
839
+ 3. **Then set the interval**, informed by (2). An expedition's cadence has to be derived from what one
840
+ actually costs and yields — a number picked before the first run is a guess wearing a schedule.
841
+
842
+ ⚠️ **This deliberately does NOT use the `operations.md` `accepted ≥ 60%` promotion gate.** That gate
843
+ measures how often a proposal class is *accepted*, which is the wrong quantity here: an expedition
844
+ could be accepted every time and still not warrant a schedule, or be proposed once and clearly warrant
845
+ one. The evidence that sets the interval is the **completed run itself**, not an acceptance rate.
846
+ **No new gate, no new cadence table, no new registry** either — when the interval is set, it graduates
847
+ into the existing §Cadence-Rules table like any other row.
848
+
663
849
  #### Event-bound proposals (context-entry, not time)
664
850
 
665
851
  Some proposals are not *time*-overdue — they fire **once when a specific work context is entered**. `persona-innovator` (ideation/naming + external-frontier absorption) is most valuable in exactly two contexts and friction-noise everywhere else, so it is proposed on context-entry rather than every session or every N days:
@@ -988,6 +1174,37 @@ Closing phrase detected ("wrap up", "done", "good work", "end session", etc.)
988
1174
  across `package.json` + every `.claude-plugin/plugin.json` + `.claude-plugin/marketplace.json` (single-source =
989
1175
  `package.json`) → Pre-Publish gate → `npm publish` → `git tag vX.Y.Z` at publish. **Propose, don't
990
1176
  auto-publish.** (Why lockstep — Codex caches on plugin.json version — + drift-check + tag-drift caveat → §detail below.)
1177
+
1178
+ 🟥 **WHICH DIGIT — operator decision 2026-08-17, and it is deliberately NOT strict semver.**
1179
+ There was no policy before this line, which is why one session proposed three different bumps
1180
+ for the same delta on three different (and each individually defensible) grounds. Decide by
1181
+ **what the number tells a reader**, not by whether anything technically broke:
1182
+
1183
+ | Bump | Reserved for (operator's own wording, 2026-08-17) |
1184
+ |---|---|
1185
+ | **major** `+1.0.0` | **any one of three**: ⓐ **완전히 새로 지음** — rebuilt from scratch, not extended · ⓑ **정체성이 확립됨** — an identity of the five (+Ⓑ) actually standing 🟢, not progressing toward it · ⓒ **기능이 혁신적으로 변경되거나 늘어남** — a capability *class* appears or is replaced, not a capability instance. 🟥 **Never** for tightening a gate that already existed |
1186
+ | **minor** `+0.1.0` | 미들급 — new assets, new gate lanes, doctrine that changes behavior; **including changes that break a consumer's gate acceptance**, which then carry a mandatory `BREAKING (gate):` line |
1187
+ | **patch** `+0.0.1` | 트리비아급 — fixes, wiring, docs that change no behavior |
1188
+
1189
+ **The discriminator between major-ⓒ and minor**: *class* vs *instance*. A sixth Wave-1 attack
1190
+ angle is an instance → minor. An attack-angle **registry** where none existed is a class → major.
1191
+ Today's delta is instances and tightenings throughout, which is why it is 2.1.0 and not 3.0.0
1192
+ even though it breaks a gate acceptance.
1193
+
1194
+ **Why gate-tightenings are minor here, stated so it is not mistaken for hiding a break**: what
1195
+ breaks is the **record format of a gitignored local marker**, not an API or the consumer's code;
1196
+ the hook prints exactly what to write instead; and the blast radius needs the consumer to have
1197
+ installed the hook AND be making a load-bearing change AND have used the specific old form.
1198
+ Against that, strict semver would burn a major on every gate we tighten — this repo took 2.0.0
1199
+ for a publish-freshness gate one day and would have taken 3.0.0 for a commit gate the next.
1200
+ **A major number that arrives monthly stops meaning anything**, and the milestone it should be
1201
+ reserved for would have no word left.
1202
+
1203
+ ⚠️ **The condition that makes this honest, and it is not optional**: a minor that breaks gate
1204
+ acceptance MUST carry `BREAKING (gate): <what now blocks> — <the one-line remedy>` in the
1205
+ release description AND the CHANGELOG. Without it this policy is just burying breaks in minors.
1206
+ **Applies from 2026-08-17 forward, not retroactively** (2.0.0 was the same class and is left
1207
+ as-is rather than rewritten).
991
1208
  → ④-c Handoff lifecycle (cross-machine continuity) — when a durable **result artifact lands** this
992
1209
  session (mechanical hint: a new `*result*`/`*signal*`/`*_run_*` file in your companion store or
993
1210
  `tracks/`), do two things: **(a) ④-c stamps** any `"run this/start here"` run-handoff whose
@@ -1050,6 +1267,25 @@ Card update is NOT a sub-step of harvest-loop — even if harvest-loop is skippe
1050
1267
  ① **Agent View pre-read** (see above) → ② Step 0-b cross-check generates removal list → ③ Remove completed items → ④ Add new priorities → ⑤ Fix stale paths/versions → ⑥ Overwrite → ⑦ Output "BEFORE N items → AFTER M items" diff.
1051
1268
  "Delta update" not "snapshot" — completed items remaining in next session card is a bug.
1052
1269
 
1270
+ **Thread-continuation block (operator, 2026-08-16)**: *"특정 주제에 집중해서 진행한 세션이라면
1271
+ 앞으로도 마감할 때 그 갈래로 이어갈 수 있게 알아서 정리해줘."* When a session ran predominantly on
1272
+ **one thread** (an incubation, one field harness, one doctrine arc) — as opposed to scattered
1273
+ maintenance — the card additionally carries a **named continuation block for that thread**, without
1274
+ being asked:
1275
+
1276
+ ```
1277
+ 🧵 <thread name> — 이어가려면
1278
+ 지금 어디 : <state, with the artifact that proves it — a verdict line, a test count, a merged PR>
1279
+ 다음 한 걸음: <the single next action, concrete enough to start without re-deriving>
1280
+ 안 닫힌 것 : <what is open, stated as open — not omitted>
1281
+ 읽을 것 : <the 1-3 files that reconstitute context, in read order>
1282
+ ```
1283
+
1284
+ Not a second card and not a summary of the session — it is the **entry point for the next session
1285
+ that picks up this thread**, and it is written so that session does not have to re-derive where the
1286
+ thread stood. A scattered-maintenance session correctly writes none. Two or more threads → one block
1287
+ each, in priority order.
1288
+
1053
1289
  ## Session Sync / Knowledge Push Protocol
1054
1290
 
1055
1291
  > Detailed procedure: `knowledge/shared/rules/sync_push_protocols.md`
@@ -345,6 +345,74 @@ the governor / mechanical-anchor doctrine, applied at registration instead of at
345
345
 
346
346
  ---
347
347
 
348
+ ## ⓒ Node Identity Declaration (operator-approved 2026-08-16)
349
+
350
+ ### ⓒ.1 The gap this closes
351
+
352
+ The eight axes of §ⓐ.2 are properties **of a harness**, not of any one function — `residency`,
353
+ `tier_floor`, `degrade`, `approval` describe what a node *is* and what it will not do. Yet today
354
+ they are declarable only **attached to a callable**. A node with a real identity and no
355
+ bar-clearing entry point therefore has no way to state what it is, and `cluster_capability_scan.sh`
356
+ prints it as `NONE 0` — **byte-identical to an empty directory**. That is `not found ≠ 0` at the
357
+ cluster layer: "has an identity, exposes no function" and "has nothing" are rendered as one value.
358
+
359
+ **This does not reopen §ⓑ.** That section deliberately refuses to register *prose* as a
360
+ capability, for a reason that still holds: prose does not fire without attention. An identity
361
+ declaration is not prose. It is the **same enum'd 8-axis vocabulary**, machine-readable, and it
362
+ merges under the §ⓐ operators exactly as a capability's `constraints:` block does. It fires by
363
+ being merged, not by being read.
364
+
365
+ ### ⓒ.2 Refusal is the point, not a fallback
366
+
367
+ The most valuable thing many nodes have to declare is what they **will not** do. A field harness
368
+ whose canon says *"operates standalone — no external harness dependency"* is making a real claim,
369
+ and today that claim is a sentence nothing enforces: any composition attempt sails past it.
370
+ Declared as `approval: forbidden` / `residency: company`, the same claim **blocks the attempt
371
+ mechanically**.
372
+
373
+ So identity declaration is not a device for making every node joinable. It is a device for making
374
+ a node's boundaries enforceable instead of merely written down. A node that declares itself
375
+ uncomposable has declared successfully.
376
+
377
+ ### ⓒ.3 Form
378
+
379
+ A node-level declaration carries `id`, `summary`, and the eight axes — and **no** `entry`,
380
+ `verdict_enum`, or calibration pair, because it exposes no call. It is not registrable as a
381
+ callable and must never be counted as one; the scan reports identity-declared and
382
+ capability-declared **separately**, or it recreates the collapse this section exists to fix.
383
+
384
+ `UNKNOWN` is a **first-class value and the required answer for any axis the declarer cannot
385
+ ground in its own files.** A profile with five `UNKNOWN`s and three cited axes is worth more than
386
+ eight plausible defaults, because the defaults are indistinguishable from measurements once
387
+ written. The §ⓑ.5 worst-case substitution applies unchanged at composition time: unknown is not
388
+ safe, and the runtime assumes the node can do the most and is allowed the least.
389
+
390
+ ### ⓒ.4 Evidence (2026-08-16 self-declaration campaign, n=4 nodes)
391
+
392
+ Four peer harnesses were assessed from **their own** repositories, each asked to reach its own
393
+ verdict and to prove it by running FH's real registration bar. Their four outcomes are four
394
+ *different* failures, and the campaign's main result is that they are different:
395
+
396
+ | Node | callable | somewhere to put it | outcome |
397
+ |---|---|---|---|
398
+ | forge-wiki | ✅ clears the bar, known-pair separates | ✅ | `SHIP-READY` |
399
+ | the-bible | ✅ separates 4 ways live | ✅ | **bar cannot express it** — its entry takes stdin JSON; the schema drives calibration arms by argv only, so both arms ran empty and returned the same code |
400
+ | qasp-dev | ✅ clears the bar | ❌ — declaration path is outside the mirror-protected set, so it would travel to an organization mirror | `BLOCKED(mirror boundary)` |
401
+ | dashboard | — | — | identity resident; primary consumer is a different operator, so its exposure decision is not this operator's to make |
402
+
403
+ Three FH-side defects fell out of running the bar for real rather than reasoning about it:
404
+ the M3 model-independence test matched `*claude*` as a **substring**, so an entry whose *path*
405
+ contained that word was silently rejected as "calls a model CLI" (fixed — basename tokens);
406
+ the effect probe validates inside a `git clone --local`, so a declaration **cannot be validated
407
+ until it is committed**; and the schema has **no stdin channel**, which excludes an entire class
408
+ of callables from ever being declarable. The second and third are open.
409
+
410
+ Note what did *not* happen: no arm wrote a shim into a peer repo to make a failing candidate pass.
411
+ The stdin case was reported as an FH schema gap rather than patched at the field node — which is
412
+ the correct direction, and the one that would have been easiest to get wrong.
413
+
414
+ ---
415
+
348
416
  ## 1. The two live constraints — answered, and what stays open
349
417
 
350
418
  ### Salience — does a reader meet these rules when they apply?
@@ -71,8 +71,11 @@ tags: [canon, three-layer, engines, identity, verification-axes]
71
71
  > | ⓔ | — | 첫 실사용 |
72
72
  > | ⓕ | — | 되돌림 실측 |
73
73
  >
74
- > ⚠️ **마커 `axes-run` 여전히 배열을 요구한다**(a=계열 · b=첫실사용 · c=격리그라운딩 ·
75
- > d=되돌림). **산문 정본은 6축인데 기계는 4축이고, 문자가 어긋난다.** 배열로 마커를 쓰면
74
+ > **2026-08-17 부로 마커도 배열이다** 날짜 2026-08-17 마커는 `axes-run` 이
75
+ > **기호 여섯 글자(ⓐ~ⓕ)**를 요구하고, 이전 마커는 ASCII 넷을 그대로 쓴다(소급 강제 없음).
76
+ > **표기법이 곧 배열 선언**이라 감사자가 grep 한 번으로 판별한다. 남은 어긋남은 **하나뿐** —
77
+ > `standpoint:` 값의 enum 을 **검증하는 코드가 아직 0줄**이다. 아래 「옛 배열」 서술은 유예일
78
+ > 이전 마커를 읽을 때만 유효한 이력이다. 옛 배열로 쓴 줄을
76
79
  > 기계가 **조용히 다른 축으로 읽으며 오류를 내지 않는다.** 확장은 기존 마커 전부를 무효화하므로
77
80
  > **운영자 결정 사항으로 열려 있다** — 그때까지 마커를 쓸 때는 **옛 배열**을 쓰고, 산문으로
78
81
  > 논할 때는 §1-a-2 의 6축을 쓴다. 이 어긋남을 **알고** 쓰는 것이 현 규율이다.
@@ -197,8 +200,9 @@ measurement_needs_control]])에 있다. 표본이 지지하는 건 "바깥 하
197
200
  > → 현 **ⓔ**, 옛 ⓓ(되돌림) → 현 **ⓕ**. 표는 §1 요약줄 아래에 있다. 이 절의 문자를 그대로
198
201
  > 6축 문맥에 옮기면 **조용히 다른 축을 지목하게 된다.**
199
202
  >
200
- > ⚠️ **마커 `axes-run` 여전히 절의 배열을 요구한다** 마커를 때는 여기가 맞고,
201
- > 산문으로 논할 때는 §1-a-2 가 맞다. 이 어긋남은 미해결이며 운영자 결정 대기다.
203
+ > ⚠️ **이 절의 배열은 2026-08-17 이전 날짜의 마커에만 유효하다** 날짜부터 마커도 §1-a-2
204
+ > 6축(기호 키)을 요구한다. 옛 마커를 **읽을** 때는 여기가 맞고, 새로 **쓸** 때는 §1-a-2 가 맞다.
205
+ > 산문/기계 어긋남은 해소됐고, 남은 잔여는 `standpoint:` 값 검증이 없다는 것 하나다.
202
206
 
203
207
  3단계를 오래 «적대검증» 이라고 불렀으나, 그건 **넷 중 하나**다. 각 축은 **다른 것이 틀린
204
208
  경우**를 잡고 **서로를 대체하지 못한다**:
@@ -255,16 +259,38 @@ measurement_needs_control]])에 있다. 표본이 지지하는 건 "바깥 하
255
259
  다른 축으로 넘어갔다). 반대로 「남의 레포가 과거에 폐기한 규칙」은 **도구로도 못 가져온다** —
256
260
  그 프로젝트의 리뷰 이력에 접근할 이유가 없기 때문이다. 거기가 ⓓ가 남는 자리다.
257
261
 
258
- 🟥 **기계층은 아직 6축이 아니다 사실을 여기 적어 둔다(숨기지 않는다).**
262
+ **기계층이 6축으로 확장됐다 (2026-08-17, 운영자 승인).** 아래는 상태와 **남은 잔여 하나**다.
259
263
  ```
260
- axes-run: a/b/c/d pre-commit **네 글자를 요구**한다 — 2026-08-09 그대로
261
- standpoint: **자기 필드**로 따로 기계화돼 있다(2026-08-14 신설, 닫힌 enum)
262
- ⚠️ 다만 훅/픽스처 검증은 아직 없다(산문 규율)
263
- 3자 대면 **필드가 없다.** 기록할 자리 자체가 없다
264
+ axes-run: ⓐ~ⓕ 마커 날짜 2026-08-17 이면 pre-commit **기호 여섯 글자를 요구**한다
265
+ axes-run: a/b/c/d 그 이전 날짜의 마커는 네 글자 그대로 (소급 강제 없음)
266
+ standpoint: ⓑ **자기 필드**가 정본이고, `axes-run` 은 `ⓑ=→standpoint` 포인터만 든다
267
+ (이중 기록 회피). 포인터가 있는데 줄이 비면 **죽은 포인터로 차단**
268
+ ⚠️ 🟥 **값 자체의 enum 검증은 여전히 0줄** — 이게 남은 잔여다
269
+ ⓓ 3자 대면 ✅ 기록할 자리가 생겼다 (`ⓓ=`)
264
270
  ```
265
- `axes-run` 여섯 글자로 넓히는 것은 **기존 마커 전부를 무효화하는 변경**이라 운영자 결정 사항이고,
266
- **이 절은 결정을 대신하지 않는다.** 지금은 산문 정본이 6축이고 기계가 4+1축이라는 상태이며,
267
- 어긋남을 **명시된 잔여로 남긴다**.
271
+ 🟥 **판별자는 마커 파일명의 날짜다**(`< 2026-08-17` = 4축). 표기를 가른 이유는 미관이 아니라
272
+ 실측이다: 배열은 **같은 글자가 다른 축을 가리킨다**(옛 `b`=첫실사용 지금 **ⓔ**, 옛 `d`=되돌림
273
+ 지금 **ⓕ**). 같은 글자를 쓰면서 의미만 바꾸면 **조용히 다른 축으로 읽히고 오류가 안 난다** —
274
+ `not found ≠ 0` 의 형제다.
275
+
276
+ ⚠️ **다만 「표기법이 배열을 선언한다」는 명제는 반증됐다** — 이 절의 초판이 그렇게 적었고, 같은 날
277
+ ⓓ3자대면 축이 코퍼스를 손으로 세어 반례를 냈다: `axes-run` 보유 **53건 중 기호 키 4 · 혼용 1**,
278
+ 그리고 **기호 4건 중 2건이 2026-08-10 자이면서 옛 4축 의미**(`ⓑ 첫실사용` · `ⓓ 되돌림`)다. 훅은
279
+ 그 셋을 안 읽으니 커밋에는 영향이 없고, 틀린 답을 받는 쪽은 **감사자**다. 표기 정렬은 «앞으로 쓸
280
+ 때 옛 줄을 복붙하지 않게» 하는 값이지 **소급 판별자가 아니다**.
281
+
282
+ ⚠️ 그리고 **날짜 비교 자체가 프로덕션에서 도달 불가 분기**다 — 훅 호출부가 마커 경로를 `${TODAY}`
283
+ 로 구성하므로 `mdate` 는 항상 오늘이다. **기존 마커를 지킨 것은 그 상수가 아니라 경로 구성**이고,
284
+ 8일 전 `crossfamily:` 확장이 컷오프 없이 성립한 이유도 같다. 숨기지 않고 적는다.
285
+
286
+ **승인의 전제였던 「기존 마커 전부 무효화」는 반증됐다(2026-08-17 실측).** 훅은
287
+ `.axes_23_passed_{브랜치}_{오늘}.marker` **한 개만** 검증하고 과거 마커 재검증 경로가 없다:
288
+ 디스크 마커 **190개** 중 커밋이 막히는 건수는 **0**, 실제 비용은 유예일 이후 **51건이 「어느
289
+ 배열인지 읽을 수 없다」**는 것 — 파손이 아니라 **미측정**이다. 그래서 처방이 「고친다」가 아니라
290
+ **「어느 배열인지 말하게 만든다」**(표기 정렬)가 됐다. 이식성은 bash 3.2 + BSD grep, 4개 locale
291
+ × 6키 전부 HIT · known-negative 0 으로 선행 실측했다(Linux/GNU grep 은 CI 가 첫 측정).
292
+ 형식 정본 = `.claude/rules/fh_4axis_gate.md §Marker axis fields` · 픽스처 25레인 =
293
+ `scripts/test_marker_axes_run_lanes.sh`.
268
294
 
269
295
  > **한계 — 인용 전에 읽어라**: **n=1**(한 산출물 · 한 세션 · 한 저자). 축의 «비중복»이 구조적인지
270
296
  > 그날 우연인지는 **미측정**이다. 축은 **사전 등록되지 않았다**(ⓓ는 세션 도중에 생겼고 그 뒤에