@chrono-meta/fh-gate 2.6.0 → 2.8.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 (55) hide show
  1. package/.claude/rules/fh_4axis_gate.md +26 -3
  2. package/.claude-plugin/marketplace.json +2 -2
  3. package/AGENTS.md +28 -3
  4. package/CLAUDE.md +146 -168
  5. package/README.ja.md +144 -24
  6. package/README.ko.md +135 -22
  7. package/README.md +204 -27
  8. package/README.zh.md +126 -21
  9. package/docs/ETHOS.md +10 -3
  10. package/knowledge/shared/dialogue/ai_dialogue_playbook.md +131 -0
  11. package/knowledge/shared/harness-core/claude_md_gate_details.md +90 -0
  12. package/knowledge/shared/harness-core/dispatch_conditional_prohibition.md +75 -0
  13. package/knowledge/shared/harness-core/fh_three_layer_canon.md +77 -3
  14. package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +92 -5
  15. package/knowledge/shared/harness-core/harness_incubator_doctrine.md +12 -2
  16. package/knowledge/shared/harness-core/multi_model_sidecar_strategy.md +32 -0
  17. package/knowledge/shared/harness-core/ship_readiness_gate.md +77 -8
  18. package/knowledge/shared/learnings/subagent_invocations_log.yaml +196 -0
  19. package/knowledge/shared/rules/knowledge_layer_seam.md +1 -1
  20. package/knowledge/shared/rules/multi_session_close_protocol.md +7 -3
  21. package/package.json +15 -2
  22. package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
  23. package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
  24. package/plugins/fh-meta/CHANGELOG.md +157 -0
  25. package/plugins/fh-meta/agents/persona-innovator.md +170 -0
  26. package/plugins/fh-meta/skills/cross-ecosystem-synergy-detection/SKILL.md +37 -0
  27. package/plugins/fh-meta/skills/public-surface-audit/SKILL_detail.md +16 -1
  28. package/plugins/fh-meta/skills/steel-quench/SKILL.md +97 -0
  29. package/scripts/adapters/peer_resolve.sh +58 -6
  30. package/scripts/branch_claim.sh +3 -1
  31. package/scripts/cluster_capability_scan.sh +42 -13
  32. package/scripts/digest_landing_check.sh +142 -2
  33. package/scripts/fh_hub_identity.sh +83 -0
  34. package/scripts/fh_session_load.sh +53 -5
  35. package/scripts/fh_track_resolve.sh +114 -0
  36. package/scripts/field_canon_preload.sh +50 -5
  37. package/scripts/listing_watch.sh +187 -0
  38. package/scripts/package_coverage_check.sh +8 -0
  39. package/scripts/prior_art_prompt.sh +168 -0
  40. package/scripts/psa_scan_lib.sh +201 -15
  41. package/scripts/residency_admission_check.sh +204 -0
  42. package/scripts/selfcheck.sh +103 -0
  43. package/scripts/test_adapter_lanes.sh +67 -2
  44. package/scripts/test_heavy_classifier_lanes.sh +144 -0
  45. package/scripts/test_listing_watch_lanes.sh +162 -0
  46. package/scripts/test_marker_defense_lanes.sh +152 -0
  47. package/scripts/test_marker_soul_check_lanes.sh +211 -0
  48. package/scripts/test_prior_art_prompt_lanes.sh +128 -0
  49. package/scripts/test_psa_singlefile_lanes.sh +351 -1
  50. package/scripts/test_residency_admission_lanes.sh +60 -0
  51. package/scripts/test_track_resolve_lanes.sh +158 -0
  52. package/scripts/test_wizard_snippet_merge_lanes.sh +40 -0
  53. package/templates/.git-hooks/pre-commit +400 -4
  54. package/templates/.git-hooks/pre-push +17 -2
  55. package/templates/settings.PriorArt.snippet.json +44 -0
@@ -91,6 +91,109 @@ API 키 대화창 기록 방지 — Write 툴로 `.env` 직접 생성 (대화창
91
91
 
92
92
  **Forbidden response**: "I can't do that — I'm not in that project's cwd." Always check if Agent dispatch covers it first.
93
93
 
94
+ 🟥 **A live peer session is a different thing from a subagent — and the default reading is wrong.**
95
+ Operator, 2026-08-21, correcting exactly that: *"이작업을 분할하라는게 아니라 논의를 해보라는
96
+ 뜻이었어 작업은 니가하고"* → *"주위피어에는 **두뇌를빌려서 네 수행에 대한 판단**을 도움받아보라는
97
+ 거였으니"*.
98
+
99
+ | | you dispatch it | you ask a peer |
100
+ |---|---|---|
101
+ | what moves | **the work** | **the judgment** |
102
+ | what comes back | a completed unit | a verdict on *your* execution |
103
+ | the peer's own work | — | keeps running; you did not take it |
104
+
105
+ The reflex is to read "talk to the peers" as "split the task across the peers", because a peer
106
+ looks like a bigger subagent. It is not. The operator's own framing, same day:
107
+
108
+ > *"예열되어 다른갈래로 뻗어있는 자신의 다른 면모들은 하나의 **별개하네스(동일맥락을 공유하는)**
109
+ > 로도 동작도가능하므로 인사이트를 얻을수있다"*
110
+
111
+ A peer is **pre-warmed on its own branch of the same context** — a separate harness that shares
112
+ your origin but not your recent path. The operator's sharper form, same day:
113
+
114
+ > *"동일 하네스에서 분화한 세션이라도 **그 세션으로 뜨거워진 지점에서는 다른 양상과 관점을
115
+ > 지니게 된다. 그 세션만큼은 두 얼굴이 생기는거다.**"*
116
+ >
117
+ > *"**자신에 대해서는 뜨겁게, 남에 대해서는 그만큼 차갑게** (동일 하네스라도)"*
118
+
119
+ The divergence is not in how context is *delivered* — the peer genuinely **holds a different
120
+ aspect** at the point it got hot. The second line names the mechanism, and it is the half that
121
+ is easy to miss: the same heat that makes a session sharp on **its own** thread makes it
122
+ correspondingly **cold on yours**. Both halves are load-bearing and they buy different things:
123
+
124
+ | | what it buys |
125
+ |---|---|
126
+ | **hot on its axis** | it sees what you structurally cannot — it did that work |
127
+ | **cold on yours** | it does not carry your optimism, your sunk cost, or your reading of your own claim |
128
+
129
+ The second is why "read it again, more carefully" never substitutes: **you cannot make yourself
130
+ cold about your own output.** Dividing the work destroys both properties at once — a peer given
131
+ part of your task becomes hot on it, and is then judging its own output.
132
+
133
+ 🟥 **Two operating consequences, which follow from the mechanism rather than from etiquette:**
134
+ - **Ask the peer about the axis it got hot on.** Off that axis a peer wears *your* face — asking
135
+ buys nothing and costs a round trip.
136
+ - **A subagent or your own re-read cannot substitute.** The second face comes from that session
137
+ having actually done the work; a prompt cannot manufacture heat, and nothing manufactures
138
+ coldness toward your own output.
139
+
140
+ **Measured, and it is not one good day.** Scanned this hub's own records for peer-attributed
141
+ catches: **119 mentions across 35 files spanning 13 distinct dates (2026-08-08 → 08-21)**;
142
+ cross-family sidecar catches count separately at **113** — comparable in size, a *different*
143
+ axis. Eight sampled hits were hand-verified as genuine peer attribution, and at least two run
144
+ the **other way** (a peer's misdiagnosis reversed by measurement here) — so the shape is mutual
145
+ contention, not one-way review.
146
+ ⚠️ Named residuals: these are *mentions*, not deduplicated incidents (one event can be described
147
+ in several files); attribution is self-reported in this hub's own records; the window is the
148
+ ~2-week parallel-session era only.
149
+
150
+ 🟥 **Whether this is a verification axis in its own right is an OPEN OPERATOR DECISION, not
151
+ settled here.** The operator also said *"이것도 6축검증법에 속한다"*, which places peer discussion
152
+ inside the canonical six axes — but which axis, or whether it is a seventh, is unresolved: it is
153
+ closest to ⓒ isolated-grounding (someone re-verifies *your record*), yet a peer is precisely
154
+ **not** isolated. Do not cite this paragraph as if the axis question were closed.
155
+ See `tracks/_meta/fh_signal_2026-08-21_peer-axis-canon-claim.md`.
156
+
157
+ **So the ask to a peer is a question, not a work item.** The operator's own modelling of the
158
+ boundary, same day, on a branch belonging to another session: *"그 브랜치는 08d6fe75 갈래
159
+ 것이고 **여기서 손댈 게 아니다. 판단만 낸다.**"* — when the artifact belongs to another
160
+ session, produce a verdict, never an edit. This is about **scope of authority** and is separate
161
+ from the shared-checkout rules (which are about not clobbering); both apply at once.
162
+
163
+ ### The effect that needs 2+ concurrent — and why it is not a preference
164
+
165
+ Operator, 2026-08-21, arriving at it against their own habit:
166
+
167
+ > *"내가 두개이상을 동시에 잘 쓰려고는 안하는데 아이러니하게도 **두개이상을 동시에 써야 얻을수
168
+ > 있는 효과**도있어보이네"* → *"**2명~3명이 하네스를 굴려서 경합시키는 모양새**가 나오니까"*
169
+
170
+ The shape is not "parallel work". It is **contention** — two or three sessions *driving the same
171
+ harness* and colliding. What that produces cannot be reached serially, because both halves of the
172
+ hot/cold table must exist **at the same time**: one session hot on a thread while another is cold
173
+ on it. Run them in sequence and you get one face twice.
174
+
175
+ Same day, in both directions: the wiring-hot session found a defect in this session's delta; this
176
+ session found a drift defect inside *that* session's delta — and the check that caught it fires on
177
+ *"did main change today"*, not *"did I change it"*, which is a contention-shaped trigger by
178
+ construction. Neither finding was self-caught by its author.
179
+
180
+ ⚠️ **An observed effect, not a promotion to default.** Concurrency costs coordination, and it is
181
+ where the same day's two shared-checkout accidents came from. The claim is narrow: *some* effects
182
+ are unobtainable without it.
183
+
184
+ **So how to hold it** (operator, 2026-08-21): *"디폴트 승격은 안 해도 되. 다만 병렬세션 띄워서
185
+ 운영자가 작업할 때 얻게 되는 **반사이익**이라고 보면 되지. 아니면 **네가 권유해도 괜찮음**.
186
+ 「병렬세션으로 나눠서 작업해도 좋을 것 같습니다」 이런 식으로"*.
187
+
188
+ Two things follow, and the second is a permission most sessions will not assume they have:
189
+ - **It is a by-product, not a reason.** The operator opens parallel sessions for their own reasons;
190
+ contention is what they *also* get. Do not argue for concurrency on the strength of this effect
191
+ alone.
192
+ - 🟥 **You may propose it.** When work has separable threads and a second face would help — a
193
+ decision you would otherwise ratify alone, a claim only its author has read — say so plainly:
194
+ *"병렬세션으로 나눠서 작업해도 좋을 것 같습니다."* Proposing is invited; deciding is not yours,
195
+ and the standing preference remains delegation-in-conversation over standing parallelism.
196
+
94
197
  **Context Card** (required for non-trivial dispatch):
95
198
  ```
96
199
  [Session Context Card]
@@ -120,6 +223,34 @@ Note: {constraints the agent must know}
120
223
 
121
224
  ---
122
225
 
226
+ ## Approval Must Be Evaluable — never hand over a blind stamp
227
+
228
+ Operator, 2026-08-21: *"내가 판단해야하는건 내가 알아볼수있게 쉽게 브리핑해줘 **승인하더라도
229
+ 블라인드 도장은 찍고싶지않아**."*
230
+
231
+ An approval request is not done when it is *complete*; it is done when the approver can **reach
232
+ the verdict themselves**. A dump of everything measured technically discloses more and decides
233
+ less — the approver ends up ratifying your conclusion rather than forming one.
234
+
235
+ **What a decision-ready item carries:**
236
+ ```
237
+ the decision stated as a choice, not as a status report
238
+ what it costs what becomes irreversible, what stays open
239
+ the evidence the number AND how it was measured (an uncalibrated number is not evidence)
240
+ your recommendation named as yours, so it can be rejected without re-deriving everything
241
+ what you did NOT check the residual, by name — this is the half that makes the rest trustworthy
242
+ ```
243
+
244
+ 🟥 **The failure mode is not withholding — it is volume.** Burying the one load-bearing fact in a
245
+ complete record is the same defect as omitting it: either way the approver cannot separate what
246
+ matters. If it takes the approver a re-derivation to decide, the briefing has handed them a blind
247
+ stamp with extra steps.
248
+
249
+ **Applies to every approval surface**, not only long briefs: a one-line "merge this?" still owes
250
+ the choice, the cost, and the residual.
251
+
252
+ ---
253
+
123
254
  ## Counter-Argument Protocol
124
255
 
125
256
  When the user pushes back on an AI recommendation ("is that right?", "something seems off"):
@@ -370,3 +370,93 @@ sessions (no FH asset modification) never see this notice — the Sonnet default
370
370
  > `knowledge/shared/harness-core/capability_escalation_consent.md` — the negotiated-consent protocol
371
371
  > (UAP `sidecar_consent`/`floorup_consent`) that decides ask-once vs. no-surprise floor-up/sidecar use.
372
372
  > This notice is the passive advisory; that doc is the active escalation gate.
373
+
374
+ ---
375
+
376
+ ## §Version-Digit-Policy — 어느 자릿수를 올리나 (CLAUDE.md §Session-Close ④-b 상세)
377
+
378
+ > **왜 여기 있나**: 이 판단은 **버전을 올릴 때만** 필요하다 — 매 세션 마감이 아니다.
379
+ > CLAUDE.md 는 규칙(3값 + 판별자 + `BREAKING (gate):` 의무)만 상주로 갖고, 아래 근거·판례를
380
+ > 여기서 읽는다. salience-split 2026-08-21.
381
+
382
+ 🟥 **WHICH DIGIT — operator decision 2026-08-17, and it is deliberately NOT strict semver.**
383
+ There was no policy before this line, which is why one session proposed three different bumps
384
+ for the same delta on three different (and each individually defensible) grounds. Decide by
385
+ **what the number tells a reader**, not by whether anything technically broke:
386
+
387
+ | Bump | Reserved for (operator's own wording, 2026-08-17) |
388
+ |---|---|
389
+ | **major** `+1.0.0` | **any one of three**: ⓐ **완전히 새로 지음** — rebuilt from scratch, not extended · ⓑ **정체성이 확립됨** — 🟥 **다섯이 «전부» 🟢** 인 순간이지 하나가 🟢 로 올라선 순간이 아니다(운영자 결정 2026-08-21). 초판은 *"an identity of the five … actually standing 🟢"* 였고 **「하나만 초록이어도 major」로 읽혔다** — 실제로 그날 ②가 🟢 로 판정되면서 3.0.0 후보로 올라왔고, 그 애매함이 그때 닫혔다. 🟥 그리고 **정체성 등급은 npm 이 나르는 신호가 아니다** — 그건 `identity-v*` 계보의 사건이고, npm 이 또 나르면 같은 날 고친 「두 계보 한 이름」 결함을 번호에서 재생산한다. ⇒ major-ⓑ 는 **`identity-v1.0.0` 과 같은 사건**을 가리킨다 · ⓒ **기능이 혁신적으로 변경되거나 늘어남** — a capability *class* appears or is replaced, not a capability instance. 🟥 **Never** for tightening a gate that already existed |
390
+ | **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 |
391
+ | **patch** `+0.0.1` | 트리비아급 — fixes, wiring, docs that change no behavior |
392
+
393
+ **The discriminator between major-ⓒ and minor**: *class* vs *instance*. A sixth Wave-1 attack
394
+ angle is an instance → minor. An attack-angle **registry** where none existed is a class → major.
395
+ Today's delta is instances and tightenings throughout, which is why it is 2.1.0 and not 3.0.0
396
+ even though it breaks a gate acceptance.
397
+
398
+ **Why gate-tightenings are minor here, stated so it is not mistaken for hiding a break**: what
399
+ breaks is the **record format of a gitignored local marker**, not an API or the consumer's code;
400
+ the hook prints exactly what to write instead; and the blast radius needs the consumer to have
401
+ installed the hook AND be making a load-bearing change AND have used the specific old form.
402
+ Against that, strict semver would burn a major on every gate we tighten — this repo took 2.0.0
403
+ for a publish-freshness gate one day and would have taken 3.0.0 for a commit gate the next.
404
+ **A major number that arrives monthly stops meaning anything**, and the milestone it should be
405
+ reserved for would have no word left.
406
+
407
+ ⚠️ **The condition that makes this honest, and it is not optional**: a minor that breaks gate
408
+ acceptance MUST carry `BREAKING (gate): <what now blocks> — <the one-line remedy>` in the
409
+ release description AND the CHANGELOG. Without it this policy is just burying breaks in minors.
410
+ **Applies from 2026-08-17 forward, not retroactively** (2.0.0 was the same class and is left
411
+ as-is rather than rewritten).
412
+
413
+ ---
414
+
415
+ ## §Branch-Protection-Two-Layers — 서버측 보호의 2층 구조와 세 번의 오판
416
+
417
+ > CLAUDE.md 는 결론만 상주로 갖는다: **통합 브랜치는 PR 전용 · 하드 플로어는 서버측 ·
418
+ > 브랜치 표면을 판정하려면 두 API 를 **모두** 읽어라.** 아래는 그 근거와, 한쪽만 읽어서
419
+ > 세 번 오판한 기록이다. salience-split 2026-08-21.
420
+
421
+ > **Two layers, and which one is the floor**: the **hard floor is server-side** — this repo now runs
422
+ > `enforce_admins: true` with `required_approving_review_count: 0` (set 2026-07-20; the count must be
423
+ > `0`, because enabling `enforce_admins` while it is `1` locks a solo operator out of merging their
424
+ > own PRs — self-approval is impossible). The hook is the **shift-left layer**: it fails at push time
425
+ > and prints the actual remedy, and it keeps holding if the server setting is ever relaxed. It is
426
+ > deliberately not the floor — a client-side hook is bypassable with `--no-verify`.
427
+ > *Origin*: before that change the server had `enforce_admins: false`, so an admin push *satisfied*
428
+ > the rule and merely printed `Bypassed rule violations` — a notice, not a block. A rule that
429
+ > announces its own bypass is not a floor.
430
+ > ✅ **Retraction — the server-side force-push surface is CLOSED, and the way it was misread is the
431
+ > durable part.** An earlier version of this block said `allow_force_pushes` on `main` was "still
432
+ > `true`", that two API writes "did not persist", and that the **server-side** history-rewrite
433
+ > surface therefore "remains open". The field reading was correct; the conclusion was not.
434
+ > **Branch protection is two independent layers — legacy protection and rulesets coexist, and the
435
+ > strictest wins** — so a field on the protection object is never the effective answer by itself.
436
+ > Measured on this repo 2026-08-09: `GET /repos/{owner}/{repo}/rules/branches/main` returns
437
+ > `non_fast_forward` from ruleset `main-no-force-push` — `enforcement: active`,
438
+ > `current_user_can_bypass: never`, `bypass_actors: []`, live since 2026-07-25 — while the legacy
439
+ > object still reports `allow_force_pushes: true`. The two API writes that "did not persist" were
440
+ > writing to the layer that does not govern *this* outcome while the stricter ruleset is active — not
441
+ > a layer that is inert in general: disable or retarget the ruleset and the legacy toggle governs
442
+ > again. **Read BOTH layers before declaring any branch surface open or closed** — `/rules/branches/
443
+ > {branch}` shows only what the *rulesets* impose, and `/branches/{branch}/protection` only what
444
+ > *legacy protection* imposes; neither is the effective view alone. A protection-object field read by
445
+ > itself misjudged this three times ([[reference_github_protection_two_layers]]).
446
+ > **Scope of the retraction, stated narrowly on purpose**: it covers *force-push / non-fast-forward*,
447
+ > which is what `non_fast_forward` blocks. Branch **deletion** is a separate rule and is closed on the
448
+ > other layer (`allow_deletions: false`, same GET). PR-routing is likewise a different field —
449
+ > `required_pull_request_reviews` present with `enforce_admins: true` — not something
450
+ > `required_status_checks` says anything about.
451
+ > ⚠️ A *different* residual on `main` is still real and must not be folded into the one just
452
+ > retracted — but the residual's own description was itself stale and needed correction on
453
+ > 2026-08-12 (live re-check, `[[reference_github_protection_two_layers]]`): legacy
454
+ > `required_status_checks.contexts` is **`["validate"]`**, not `[]` — a green `validate` check IS
455
+ > required before a PR can merge, and `GET /rules/branches/main` carries no competing
456
+ > `required_status_checks` rule, so the legacy field is the effective one here. `validate`
457
+ > (`.github/workflows/validate.yml`) is a **separate job from Axis 1** (`regression-guard.yml`) —
458
+ > Axis 1 is still not required, see the 4-axis section below. The gap on `validate` is
459
+ > `strict: false`: that check re-runs on every push to the PR branch, but nothing re-forces it
460
+ > against a **moving** main after it last ran — so a check that passed can still land behind
461
+ > concurrent merges it never saw.
462
+
@@ -103,3 +103,78 @@ dd if="$V" bs=1 skip=$off count=420 2>/dev/null | tr -d '\0'
103
103
  - Whether any **operator-config** key gates the constant is unmeasured; only text-absence was checked.
104
104
  - What ① (client-data) actually carries in this environment was not inspected — only that it takes
105
105
  precedence.
106
+
107
+ ---
108
+
109
+ ## §Hook-Floor-Unverified — 「어느 방향도 훅으로 안 막힌다」의 실측 근거
110
+
111
+ > CLAUDE.md §Agent Dispatch 는 결론 한 줄만 상주로 갖는다. 아래가 그 근거·미검증 항목이다.
112
+ > salience-split 2026-08-21.
113
+
114
+ ⚠️ **Neither direction has a confirmed hook-level floor. Say that plainly rather than implying one.**
115
+ ```
116
+ opening salience only for the POSTURE. The prohibition met in the field is CONDITIONAL,
117
+ which changes what "override" even means — see the measured block below.
118
+ blocking ALSO not hook-enforced. `SubagentStart` fires on spawn but is **context-only** —
119
+ it cannot block, exit 2 only surfaces stderr, and it has no decision field
120
+ (official hooks reference, read 2026-08-08). It can INJECT context at the moment
121
+ of dispatch, which is better-placed salience than this file, but still salience.
122
+ UNVERIFIED whether a `permissions` deny entry or a `PreToolUse` matcher can target subagent
123
+ spawning at all — the reference does not name a tool for it, and this repo has no
124
+ precedent. **Do not cite a blocking mechanism until someone runs the known pair**
125
+ (configure the deny, attempt a dispatch, observe). Until then: unverified, not absent.
126
+ ```
127
+
128
+ ## §Retraction-SubagentStart — 이 블록이 두 번 틀렸던 기록
129
+
130
+ An earlier draft of this very block asserted "a `SubagentStart` hook can deny, and a denial there is a
131
+ real floor." That was false, taken on trust from an adjacent session and written here before the
132
+ reference was read. A second draft, on 2026-08-09, then wrote that the constant's call site "needs
133
+ binary inspection, which was blocked" — **also false**: three plain `grep` calls resolved it, and an
134
+ adversarial reviewer demonstrated that by doing it. Declaring something unmeasurable before trying the
135
+ cheap tool is [[feedback_impossible_verdict_may_be_unread_half]]; the honest label is *"not yet
136
+ measured,"* never *"blocked."* A blind target-tier sim then read it back correctly — which shows a sim measures
137
+ whether text is *followable*, never whether it is *true*. Both checks are needed; neither substitutes.
138
+
139
+
140
+ ## §Worktree-Gate-Integrity — `core.hooksPath` 양팔 실측 (2026-08-05)
141
+
142
+ > 🟥 **운영 규칙은 CLAUDE.md 에 있다**: *워크트리에서 FH 자산을 커밋하지 않는다.*
143
+ > 아래는 그 규칙이 왜 조건부 사실 위에 서 있는지의 근거이고, **규칙을 실행하는 데는 필요 없다** —
144
+ > 「내 설치가 어느 팔인가」를 따져야 할 때만 읽어라. salience-split 2026-08-21.
145
+
146
+ **Fourth reason — gate-integrity in a worktree, and the answer is CONDITIONAL on how `core.hooksPath`
147
+ was set (measured 2026-08-05, both arms).** Do not carry a single verdict here; the two installs
148
+ behave differently:
149
+
150
+ | `core.hooksPath` | Which hook actually runs in a worktree | Consequence |
151
+ |---|---|---|
152
+ | **relative** — `templates/.git-hooks`, the form every FH doc installs (`CHEATSHEET.md`, `.claude/rules/fh_4axis_gate.md`, `install-wizard`, `self_evolution_routine.md`) | the **worktree's own copy** | Editing that copy *inside the worktree* disables the gate for that worktree — measured: neutralized hook → FH-asset commit with no marker succeeded (`rc=0`). The verifier becomes the verified, and the edit is invisible to `git status` in the main tree. |
153
+ | **absolute** — a hand-set full path (this operator's machine; **not** what any doc tells you to run) | the **main tree's copy** | A worktree-local edit has no effect; a known-positive is blocked there exactly as in the main tree (`rc=1`). |
154
+
155
+ An earlier draft of this section reported only the absolute-path arm and declared the
156
+ "worktree bypasses the gates" hypothesis *refuted* — from **n=1 on a non-canonical setting**, with a
157
+ do-not-revisit label attached. The relative-path arm, which is what everyone else runs, reproduces
158
+ the bypass. Freezing a conclusion is a defect when the measurement did not cover the shipped
159
+ configuration.
160
+
161
+ 🟥 **RETRACTED 2026-08-22.** This said the marker and manifest are *structurally absent* in a
162
+ worktree. They are not: `templates/.git-hooks/pre-commit` resolves evidence through
163
+ `git rev-parse --git-common-dir`, which returns the **main tree's** `.git` from inside a worktree.
164
+ Measured in a clean worktree with the known-negative established first (its own `tracks/` = skeleton
165
+ only; `$REPO_ROOT/tracks/_meta/edit_manifest.yaml` NOT FOUND, `$EVIDENCE_ROOT/...` FOUND — the two
166
+ roots disagree, so the probe discriminates).
167
+ **The operating rule is UNCHANGED: do not commit FH assets from a worktree.** Its remaining **open risk areas** — not
168
+ established grounds — are marker provenance, concurrent manifest append, and a copy of `tracks/` that
169
+ appears in a worktree from an unattributed source. They are **deliberately not enumerated as reasons
170
+ here**; an earlier draft enumerated
171
+ them and adversarial review found a defect in nearly every added claim. ⚠️ **Reachable is not automatic**: the hook *reads* `$EVIDENCE_ROOT`, but a
172
+ worktree session writing by relative path lands in the worktree's own `tracks/`, not the main tree's.
173
+ The gate is satisfiable — its failure message prints the absolute `MARKER_DIR` to write to — but the
174
+ routing is the author's job, not the tool's.
175
+ ⚠️ The `core.hooksPath` two-arm table above is a separate axis (hook substitution) and is untouched.
176
+ One observation from the retracted paragraph survives on its own: note the
177
+ hook itself prints `mkdir -p …/tracks/_meta` on that failure, i.e. the actor's own error message
178
+ teaches the marker-creation path — so "just don't fabricate it" is prose sitting under a machine
179
+ instruction pointing the other way.
180
+
@@ -50,8 +50,12 @@ tags: [canon, three-layer, engines, identity, verification-axes]
50
50
  ```
51
51
  ① 초기 영혼 심지(판단 회로)를 먼저 심는다. 무엇이 성공 · 어디로 기움 ·
52
52
  범위 밖 · 절대 안 함
53
- ② 중간 탈상관 가속화 축을 골라 동시에 친다. **곱하지 말고 골라라** — 병렬화 자체엔
54
- 방향이 없다(축은 판단 회로가 고른다)
53
+ ② 중간 **병렬 탈상관** 가속화
54
+ 다이얼을 **각각** 돌린다 — 곱하지 말고 골라라.
55
+ **탈상관** ← 사각 위험에 반응. **모델 탈상관**(ⓐ 계열) +
56
+ **입장 탈상관**(ⓑ 대상 하네스의 정본·레포)
57
+ **병렬** ← 표면 크기에 반응(한 컨텍스트에 담기나). fan-out
58
+ 병렬화 자체엔 방향이 없다 — 축은 판단 회로(①)가 고른다
55
59
  ③ 마무리 6축 태우기 §1-a-2 의 여섯 축으로 태운다. **적대검증 하나가 아니다**
56
60
  ```
57
61
 
@@ -176,6 +180,21 @@ measurement_needs_control]])에 있다. 표본이 지지하는 건 "바깥 하
176
180
  `fh_signal_2026-08-07_shift-left-governance` §④ 의 *"뒷단 부하는 앞단 부재의 함수다"* 와
177
181
  같은 명제이고, 이 괄호가 그것을 ②의 정의 안으로 넣는다.
178
182
 
183
+ 🟥 **이름이 손실 압축돼 있었다 — 2026-08-21 에 운영자가 잡아 복원했다.** 바로 위 원발화는
184
+ **「병렬 탈상관 가속화」**인데, 요약줄(§0)과 `CLAUDE.md` 는 **「병렬」을 떨어뜨린 채** 8월 내내
185
+ 「탈상관 가속화」로 실려 있었다. 그러자 이름이 다이얼 **하나만** 부르게 되고, 읽는 쪽에서
186
+ *"이거 그냥 서브에이전트 병렬 아니냐"* 가 나온다 — 실제로 운영자가 그 질문을 던져서 발견됐다.
187
+ **자력 적발 0.**
188
+
189
+ 🟥 **그리고 「다양성」으로 부르지 않는다 — 검토했고 기각했다(운영자 결정 2026-08-21).**
190
+ 다양성은 **리뷰어 집합**의 성질이고 탈상관은 **입력**의 성질이다. 같은 diff 를 세 계열에 주면
191
+ *다양하되 상관돼 있고* 사각은 그대로 남는다 — §1-a-2 의 판별 명제(«무엇을 받았는가»)가 정확히
192
+ 그것을 막는 문장이다. 「다양성」으로 부르면 *"모델 세 개 썼으니 됐다"* 가 통과한다. 실측:
193
+ **이 문단을 쓰기 전** 기준으로 이 파일 · `field_verdict_crossfamily_gate.md` · `CLAUDE.md` 에서
194
+ 「다양성」 **0건**이었고, 그건 우연이 아니라 이 구분을 지키느라 안 쓴 것으로 보인다.
195
+ 🟥 **스코프를 명시하는 이유**: 지금은 이 문단 자신이 그 단어를 쓰므로 같은 grep 이 히트한다.
196
+ 스코프 없는 부재 주장은 자기 자신이 반례가 된다(cross-family 지목, 자력 적발 0).
197
+
179
198
  ⚠️ **가속화 ≠ shift-left.** 앞당기는 것과 탈상관하는 것은 다른 축이다. 오늘은 둘이 같이
180
199
  갔을 뿐이고, 한 문장으로 묶으면 *"병렬로 돌렸으니 앞당긴 것"* 이라는 오독이 나온다.
181
200
 
@@ -251,11 +270,27 @@ measurement_needs_control]])에 있다. 표본이 지지하는 건 "바깥 하
251
270
  |---|---|---|
252
271
  | ⓐ 다른 계열 | diff + 저자의 프레이밍 | 구현 |
253
272
  | **ⓑ 입장** (신규) | diff + **대상 하네스의 정본** | **인용한 규약이 정말 그런가** |
254
- | ⓒ 격리 그라운딩 | 저자가 쓴 문장 + 지금의 트리 | 주장 |
273
+ | ⓒ 격리 그라운딩 | 저자가 쓴 문장(**사후 주장 및 사전 선언**) + 지금의 트리 | 주장 · **델타가 선언한 것과 안 맞음** |
255
274
  | **ⓓ 3자 대면** (신규) | 문제 + **남의 코드베이스** | **이미 풀린 문제 아닌가** · 내 변경이 남의 레포를 어디서 만지나 |
256
275
  | ⓔ 첫 실사용 | 실물 대상 한 건 | 재는 방식(계기의 계기) |
257
276
  | ⓕ 되돌림 실측 | 배선을 지운 트리 | 앵커 |
258
277
 
278
+ 🟥 **ⓒ 행은 2026-08-21 에 넓어졌고, 이 정본은 그날 안 따라왔다.**
279
+ ⚠️ **초판은 여기 「8일간」이라고 적었는데 그건 지어낸 숫자다** — `db8e72a` 는 **같은 날
280
+ 15:45:09 +0900** 이라 실제 지연은 몇 시간이고, 자력 적발 0(cross-family 가 커밋 날짜를 대조해
281
+ 반증했다). 짧다고 덜 나쁜 것은 아니다 — **길이가 아니라 「기계가 갱신될 때 정본이 같은 PR 에
282
+ 안 들어갔다」가 결함**이고, 그 성질은 지연이 1시간이든 8일이든 같다. 숫자를 크게 잡아 심각성을
283
+ 빌려온 형태라 그대로 남긴다. `templates/.git-hooks/pre-commit:1186` 이 이미 *"⇒ ⓒ's 「받는 것」 widens to «저자가 쓴
284
+ 문장(사후 주장 및 **사전 선언**)»"* 라고 선언하고 `soul-check:` 레인까지 돌고 있는데(PR #481,
285
+ `db8e72a`), 그 PR 이 바꾼 파일 셋(`selfcheck.sh` · `test_marker_soul_check_lanes.sh` ·
286
+ `pre-commit`)에 **이 파일이 없었다.** 그래서 이 절 전체에서 `사전 선언` grep 이 **0건**이었다 — 🟥 **이 문단을 쓰기 전 기준이다.**
287
+ 지금은 이 문단 자신이 그 문자열을 여러 번 쓰므로 같은 grep 이 히트한다. 스코프를 안 적으면
288
+ 자기무효 측정이 된다(cross-family 지목).
289
+ `[[feedback_half_fix_propagation_boundary]]` 이고, 방향이 뒤집힌
290
+ `[[feedback_rule_misdescribes_its_own_machine]]` 이다 — 보통은 규칙이 없는 기계를 주장하는데
291
+ 여기서는 **규칙이 있는 기계를 안 적었다.** 발견자는 병렬 피어 세션(하네스닥터)이고 **자력 적발
292
+ 0** 이다 — 위 §피어 절이 말한 ⓑ∪ⓓ 가 실제로 낸 첫 수확이다.
293
+
259
294
  **저자 자평을 걷어낸 자리**: 발견 16건을 **출처를 지운 채** 봉인된 정답키와 함께 **레포 밖 cwd·
260
295
  헤드리스**로 다른 계열 분류자 **둘**에게 독립 판정시켰다. 채점 11/15 · 12/15, 상호 일치 13/15(87%),
261
296
  사전 등록한 반증 조건(일치율 50% 미만) 미발동.
@@ -271,6 +306,45 @@ measurement_needs_control]])에 있다. 표본이 지지하는 건 "바깥 하
271
306
  다른 축으로 넘어갔다). 반대로 「남의 레포가 과거에 폐기한 규칙」은 **도구로도 못 가져온다** —
272
307
  그 프로젝트의 리뷰 이력에 접근할 이유가 없기 때문이다. 거기가 ⓓ가 남는 자리다.
273
308
 
309
+ #### 피어 세션은 새 축이 아니라 ⓑ∪ⓓ 의 변형이다 (2026-08-21, 운영자 결정)
310
+
311
+ > **운영자**: *"피어논의는 입장리뷰+3자대면의 변형이지않을까"*
312
+
313
+ 같은 하네스에서 갈라져 나온 **병렬 세션**의 판정을 어디에 적을지가 열려 있었다(신호:
314
+ `tracks/_meta/fh_signal_2026-08-21_peer-axis-canon-claim.md`). 결정은 **7번째 축 아님** —
315
+ ⇒ **마커 `axes-run` 배열 변경 없음**(기존 마커 전량 무효화라는 가장 비싼 결과를 피했다).
316
+
317
+ 위 표의 판별자(«무엇을 받았는가»)로 대조하면 둘 다 맞는다:
318
+ - **ⓑ 입장** — 피어가 낸 S-1 이 정확히 *"인용한 규약이 정말 그런가"* 였다(rc 계약을 인용해
319
+ 놓고 주 진입점이 그 계약을 안 썼다).
320
+ - **ⓓ 3자 대면** — 같은 날 사고 둘이 문자 그대로 *"내 변경이 남의 레포를 어디서 만지나"* 였다.
321
+
322
+ 🟥 **«변형»은 전달 방식이 아니라 «관점 자체»다 — 이 절의 초판이 그걸 틀렸고 운영자가 정정했다.**
323
+
324
+ > **운영자**: *"동일 하네스에서 분화한 세션이라도 **그 세션으로 뜨거워진 지점에서는 다른 양상과
325
+ > 관점을 지니게 된다. 그 세션만큼은 두 얼굴이 생기는거다.**"*
326
+
327
+ 초판은 *"받는 «것»은 같고 받는 «방식»이 다르다"* 라고 적었는데 그건 열화다. **뜨거워진 지점에서
328
+ 그 세션은 진짜로 다른 입장이고 진짜로 남의 코드베이스 자리다.** 그래서 ⓑ∪ⓓ 로 갈리는 것이지
329
+ «내용은 같은데 배달이 다른» 것이 아니다.
330
+
331
+ **그리고 이 정정이 운용 규칙을 바꾼다** — 초판대로면 안 나왔을 규칙이다:
332
+ ```
333
+ ✅ 피어에게 물을 때는 그 피어가 «뜨거워진 축»을 물어라 — 거기서만 두 번째 얼굴이 있다
334
+ 🟥 뜨겁지 않은 축에서 피어는 나와 같은 얼굴이다 — 물어도 탈상관이 안 된다(같은 사각)
335
+ 🟥 서브에이전트·내 재독으로 대체 불가다. 두 번째 얼굴은 «그 세션이 실제로 그 일을 한 것»
336
+ 에서만 나오지, 프롬프트로 만들어지지 않는다
337
+ ```
338
+ `[[feedback_decorrelation_axis_matches_failure_mode]]` 의 피어 판본이다 — 축은 계열만이 아니라
339
+ **«그 세션이 무엇으로 뜨거워졌나»**로도 갈린다.
340
+
341
+ **기록 규약**: 피어 판정을 근거로 쓸 때 `axes-run` 은 `ⓑ=` 와 `ⓓ=` 에 적는다. 새 기호는 안
342
+ 만든다. 단 **`ⓑ=→standpoint`** 규약이 이미 있으므로(값은 `standpoint:` 필드가 가진다),
343
+ 피어-유래 ⓑ 는 그 필드에 «peer 세션이 자기 갈래에서 판정» 임을 명시한다.
344
+
345
+ ⚠️ **표본 한계**: 근거는 하루·두 세션이고 **대조군이 없다**(같은 일을 피어 없이 했을 때와
346
+ 비교하지 않았다). 결정은 운영자 판단이고, 판별력 실측은 미실시다.
347
+
274
348
  ✅ **기계층이 6축으로 확장됐다 (2026-08-17, 운영자 승인).** 아래는 그 상태와 **남은 잔여 하나**다.
275
349
  ```
276
350
  axes-run: ⓐ~ⓕ 마커 날짜 ≥ 2026-08-17 이면 pre-commit 이 **기호 여섯 글자를 요구**한다
@@ -469,11 +469,25 @@ own. `tier2b` is the honest reachable rung for that pairing; do not inflate a `t
469
469
  `tier3`, and do not undersell it to `tier2` either — it is a distinct, real, if operator-correlated,
470
470
  data point.
471
471
 
472
- **Mechanization status — `standpoint:` is prose-only today, and this must not be read as more than
473
- that.** `crossfamily:`'s degrade triad is hard-blocked at commit (`templates/.git-hooks/pre-commit`,
474
- `scripts/test_marker_crossfamily_lanes.sh` grep-verified: ~20 crossfamily references in the hook,
475
- validated fixtures). `standpoint:` has **no value-enum validation and no fixture suite** — nothing stops an author from
476
- writing `not-applicable` with a thin justification, and no marker-shape check catches a wrong value.
472
+ **Mechanization status — 🟥 CORRECTED 2026-08-20.** This paragraph used to say `standpoint:` was
473
+ "prose-only today" with "**no value-enum validation and no fixture suite**". **Both halves are false**
474
+ and had been for a while: `validate_standpoint_leg()` lives in `templates/.git-hooks/pre-commit` and
475
+ `scripts/test_marker_standpoint_lanes.sh` is wired through `scripts/selfcheck.sh`. Measured by varying
476
+ one variable at a time `banana(qasp)` **blocked (enum)** · `tier2` without parens → **blocked
477
+ (enum)** · `tier2(qasp)` with no execution grounds → **passes with a warning** · with grounds →
478
+ **passes**.
479
+
480
+ **What is actually true, stated at the right width**: the enum IS closed and enforced; the `tier2`+
481
+ execution grounds are **advisory** (a thin `tier2` records and warns, it does not block); and nothing
482
+ checks whether the recorded value is *true*. The old sentence collapsed all three into "no validation",
483
+ which suppresses use of a control that exists — the quietest kind of drift, because it reads as
484
+ honest modesty.
485
+
486
+ 🟥 **This same false claim stood in FIVE places** (`CLAUDE.md` ×2, `.claude/rules/fh_4axis_gate.md`,
487
+ `AGENTS.md`, here). The first repair fixed one; the second fixed three and still missed this file and
488
+ over-stated the fix. Both misses were surfaced by a **different-family reviewer**, not by a lane and
489
+ not by the author — there is still no check that compares a rule's self-description against the
490
+ machinery it describes.
477
491
 
478
492
  🟥 **Two sentences that stood here were STALE and are corrected (2026-08-17, re-measured — a
479
493
  cross-family reviewer flagged the second, the first fell out of checking it).** They read
@@ -757,3 +771,76 @@ standpoint.
757
771
  third-party axis's *exclusive* yield at **2 of 15** findings. Both were boundary-crossing
758
772
  (a rule the other project had already abandoned; another repo importing the changed file) —
759
773
  which is why it earns its place on large-and-irreversible surfaces and nowhere else.
774
+
775
+ ---
776
+
777
+ ## §Q0-Evidence — 「shipped 여부는 판별자가 아니다」의 실측과 인용 경고
778
+
779
+ > CLAUDE.md 는 Q0 의 **규칙(ⓐⓑⓒ + pre-push 결박)**만 상주로 갖는다. 아래는 그 근거·경고다.
780
+ > salience-split 2026-08-21.
781
+
782
+ 🟥 **Settle the TARGET CLASS(es) before the tier — §7's `Q0`, added 2026-08-17 (operator decision).**
783
+ A consumer install **is** another harness; what the enum scopes is not who *receives* the change but
784
+ where it has to be **executed**. Q0 is **not first-match — it can return more than one target, and
785
+ each owes its own tier**: ⓐ a **named peer** whose local repo carries the changed surface, or which
786
+ the cluster registry / a `scripts/adapters/` entry names (decide by that test, not from a name list)
787
+ → the enum as written · ⓑ the delta changes **consumer-visible behavior** (what a consumer's gate
788
+ blocks or passes, what their session is told to do, what an install receives) → target = a *clean
789
+ install of the packed artifact*, and it binds **now, pre-push — never deferred to the eventual
790
+ release** · ⓒ neither → `not-applicable`. 🟥 Do **not** read «no cross-repo consumer contract» as
791
+ «this file is not shipped» — measured, **194/200 recent non-merge commits touch a shipped path (97%,
792
+ all 6 exceptions hand-verified)**, so shipped-ness cannot be the discriminator; the behavior clause
793
+ is. Pricing the axis at that rate is the **over-triggering** §7's own Trigger paragraph rejects.
794
+ ⚠️ 97% measures **the reach of the discarded shipped-path argument**, not the applicability rate —
795
+ how often ⓑ actually fires is **unmeasured in both tails** (it could also land near-universal, which
796
+ would be a rubber stamp — watch the next 20 markers). 🟥 Do **not** cite
797
+ `[[feedback_unreachable_done_when_trains_evasion]]` here: that memory concerns an unreachable *pass
798
+ condition*, and `not-applicable` is an *exemption* — the direction inverts. Three marker-audit legs all failed at that reading;
799
+ they were **not** decorrelated (same family · same prompt · same canon) so that is one observation,
800
+ not three. ⚠️ The consumer-install arm's *presence* half is mechanized at ship time; its
801
+ **execution** half (extract the tarball, run the gate, record command + output) has **no lane** —
802
+ do it by hand, and do not cite that arm as mechanized.
803
+
804
+ ## §Standpoint-Execution-Evidence — 실행 하중 · 철회 · 명명 충돌 · 2026-08-20 정정
805
+
806
+ > CLAUDE.md 는 판별 규칙(**명령과 출력을 댈 수 있나 → tier2, 못 대면 tier1b**)만 상주로 갖는다.
807
+ > 아래는 그 근거와 이 문단이 두 번 틀렸던 기록이다. salience-split 2026-08-21.
808
+
809
+ 🟥 **The execution is the load-bearing half** (operator decision 2026-08-16): a static standpoint
810
+ read competes with cross-family review for the same defect classes and mostly loses — *running the
811
+ target harness locally, to completion*, is the part with no substitute. Measured on one delta the
812
+ same day: static read found 1, running the target's own suite found 2 more, one of which printed
813
+ neither `FAIL` nor `❌` and was unreachable by any read. So **`tier2`+ asserts something was RUN** —
814
+ if the review only read, it is `tier1b`, and `tier1b` is deliberately the weak rung so that
815
+ recording it honestly surfaces that the execution arm is still owed. (Broken on the day it was
816
+ written — a static read was recorded as `tier2` because `tier1b` did not yet exist; a missing rung
817
+ gets filled by the next one up rather than staying empty.) Naming note: this collides in
818
+ English with FH's own persona/viewpoint sense of "standpoint" (`fh-meta:beginner`/`main-player`/
819
+ `expert`) — a different axis (which persona reviews, not whose repo is ground truth); kept as-is,
820
+ not renamed, but do not conflate the two. 🟥 **CORRECTED 2026-08-20 — this paragraph used to say
821
+ `standpoint:` was "Prose-only today — no pre-commit hook or fixture suite validates this field yet".
822
+ That is FALSE and was false in this same file**: `validate_standpoint_leg()` lives in
823
+ `templates/.git-hooks/pre-commit` and is called from the marker block, and its fixture suite
824
+ `scripts/test_marker_standpoint_lanes.sh` is wired through `scripts/selfcheck.sh`. (🟥 **Grep the
825
+ names, do not trust line numbers** — the first version of this correction cited `:798`/`:1575` and a
826
+ commit landed the same hour that moved them to `:878`/`:1665`. A hardcoded anchor in prose is a
827
+ phantom waiting for the next edit.) §자기 대조
828
+ above already said so (PR #429), so **one file carried both claims at once** and a reader landed on
829
+ whichever they reached first. Found by the residency-ledger pass, not by a lane — no check compares
830
+ a rule's self-description against the machinery it describes, which is why a stale "we have not
831
+ built this yet" is the quietest form of drift: it reads as honest modesty and it suppresses use of a
832
+ control that already exists. **What is validated is the ENUM** — measured by varying ONE variable at a
833
+ time, because the first version of this correction varied two and mis-attributed the result:
834
+ `banana(qasp)` → blocked (enum) · `tier2` without parens → blocked (enum) · `tier2(qasp)` with **no**
835
+ execution grounds → **passes with a warning** · with grounds → passes. 🟥 So the first fix's claim
836
+ that "grounds are non-empty" are checked **over-shot, and a different-family reviewer caught it**:
837
+ the `tier2`+ execution grounds are **advisory**. Two residuals remain and both are real — grounds are
838
+ not enforced, and whether `tier2` is *true* is still self-attested. What was wrong was only the claim
839
+ that nothing validated the field at all. Three artifacts, one carrying two
840
+ independent trials (forge-harness PR #368, a sibling field harness's PR #8 reps=3 and its
841
+ known-answer trial, qasp-dev PR #161 as adjacent corroboration) crossed this repo's own evidence
842
+ bar the same day this was formalized — including one caught by this session's own qasp PR #161
843
+ review, not fed in externally, and a second live demonstration the same day when a cross-harness
844
+ standpoint review of this very section caught real residency and citation defects in the first
845
+ draft (fixed in the same commit that added this line).
846
+
@@ -331,8 +331,18 @@ by design. ~~**Therefore a meta-harness candidate is not a chamber candidate.**~
331
331
  day-one walk 의 정의는 *"on the first run, with the user adding nothing, the thing produces
332
332
  something useful"* 이고 반대말이 `built-but-not-wired`, 그리고 *"**wiring is part of the birth**"*
333
333
  다. **md 규율은 구조적으로 호출부가 없다**(살리언스). 실제 반례:
334
- `.claude/rules/fh_4axis_gate.md` 의 `standpoint:` 필드 — *"Still validated by nothing — zero hook
335
- lines, no fixture suite"* 였고, `tier1b` 등급이 **없어서 정적 리뷰가 tier2 로 기록**됐다.
334
+ `.claude/rules/fh_4axis_gate.md` 의 `standpoint:` 필드 — 그 규율이 *"Still validated by nothing —
335
+ zero hook lines, no fixture suite"* 라고 **자기 자신에 대해** 적고 있었다.
336
+ 🟥 **2026-08-20 정정 — 이 예시는 인용한 문장이 틀렸다는 것이 밝혀져 방향이 바뀐다.** 실측: 그 시점에
337
+ `validate_standpoint_leg()` 는 훅에 **있었고** 픽스처도 `selfcheck.sh` 로 배선돼 있었다
338
+ (`banana(qasp)` → 차단 · `tier2` 괄호 없음 → 차단). 즉 이건 «태어났는데 안 걸은 규율»이 아니라
339
+ **«걷고 있는데 자기가 못 걷는다고 적어둔 규율»**이다 — 그리고 그게 더 조용한 결함이다: 정직한
340
+ 겸손처럼 읽히면서 **이미 있는 컨트롤의 사용을 억제**하고, 실제로 그 문장은 **여섯 개 문서로
341
+ 전파돼** 있었다(`CLAUDE.md` ×2 · 그 규칙 파일 · `AGENTS.md` · `field_verdict_crossfamily_gate.md` ·
342
+ 여기). ⇒ md 규율의 실패 모드는 **하나가 아니라 둘**이다:
343
+ ① 태어나고 안 걷는다(호출부 부재) ② **걷는데 자기 상태를 틀리게 적는다**(자기서술 드리프트).
344
+ ②는 ①의 판정자(«블라인드 sim 이 발화하는가»)로는 안 잡힌다 — 규율은 정상 발화하고, 틀린 것은
345
+ 그 규율이 자기에 대해 하는 **진술**이기 때문이다. 그걸 잡는 검사는 아직 없다.
336
346
  `axes-run` 기호 키도 53건 중 2건이 옛 의미로 쓰였다. **태어났고 안 걸은 md 규율들**이다.
337
347
 
338
348
  ⇒ **유닛 클래스별 day-one walk 판정자를 따로 건다**:
@@ -149,6 +149,38 @@ human-review floor is structural, not a transitional patch. (Its Faros AI number
149
149
  the author's own caveat travels with any citation.)
150
150
  "Aggressive" Codex/Gemini use is bounded by the fit task-class above, never a blanket main-seat swap.
151
151
 
152
+ ### Write authority — a sidecar audits, it does not edit (measured 2026-08-21)
153
+
154
+ The doctrine above governs **verdicts**: a sidecar's finding is an evidence candidate until the governor
155
+ source-closes it. That is the *judgment* axis. It says nothing about the **write** axis, and the gap is
156
+ not academic — a sidecar with tool access will start editing the target tree unless told not to.
157
+
158
+ > **A sidecar returns findings and, at most, a proposed patch as TEXT. It does not write to the target
159
+ > tree.** The governor applies, or does not apply, each change. Same shape as the verdict rule: the
160
+ > sidecar supplies material, the governor is the only writer.
161
+
162
+ 🟥 **The reason is not overreach. It is that the fixer and the checker become the same party.**
163
+ Measured this day: `codex/gpt-5.5` was dispatched as an adversarial auditor over a hook delta and began
164
+ editing the working tree directly (caught by `mtime`, not by any gate). Its *finding* was real and sharp
165
+ — two detectors in the same file had divergent leniency. Its *fix* widened one detector's pattern, and
166
+ the widened pattern then matched **the claim line's own text** as its own evidence — a self-referential
167
+ false positive that flipped a BLOCK lane to PASS. **41 lanes stayed green.** A human reading the diff
168
+ sees "leniency aligned", which is correct as far as it goes. What caught it was the first real use in an
169
+ isolated clone. This is [[feedback_blind_separation_is_a_defect_generator]] arriving through a door the
170
+ verdict rule left open: the sidecar was, for those minutes, both author and reviewer of the same lines.
171
+
172
+ **Operationally**: dispatch sidecars with the target tree read-only where the runtime supports it; when it
173
+ does not, state the constraint in the prompt AND verify afterwards (`git status` / `mtime` on the files you
174
+ handed over). A sidecar edit discovered after the fact is treated as a **finding to re-derive**, never as a
175
+ patch to keep — re-decide it in the governor's seat, because a patch you did not author is a patch you did
176
+ not review.
177
+
178
+ ⚠️ **Process safety, same surface**: terminating a runaway sidecar needs a discriminator for *your own*
179
+ process — pin the model, capture the PID, or `pgrep` first. A bare `pkill -f codex` on a shared machine
180
+ reaches other sessions' sidecars; measured the same day (nothing was actually killed — exit codes
181
+ separated the cases afterwards — but the command could not have known that in advance). **Confirm what
182
+ died after killing**, not before.
183
+
152
184
  ### Decorrelation axes — model family AND authoring context (measured 2026-08-01)
153
185
 
154
186
  Decorrelation has been treated as one axis (model family). A codex-outage session measured a second,