@chrono-meta/fh-gate 2.3.0 → 2.5.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 (39) hide show
  1. package/.claude/rules/fh_4axis_gate.md +25 -0
  2. package/.claude-plugin/marketplace.json +2 -2
  3. package/CLAUDE.md +107 -7
  4. package/README.ja.md +14 -2
  5. package/README.ko.md +14 -1
  6. package/README.md +16 -2
  7. package/README.zh.md +12 -1
  8. package/knowledge/shared/harness-core/fh_three_layer_canon.md +18 -6
  9. package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +262 -7
  10. package/knowledge/shared/harness-core/harness_incubator_doctrine.md +215 -1
  11. package/knowledge/shared/harness-core/ship_readiness_gate.md +168 -1
  12. package/knowledge/shared/harness-core/sonnet_floor_doctrine.md +16 -0
  13. package/knowledge/shared/learnings/subagent_invocations_log.yaml +120 -0
  14. package/knowledge/shared/rules/multi_session_close_protocol.md +124 -2
  15. package/package.json +8 -1
  16. package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
  17. package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
  18. package/plugins/fh-meta/CHANGELOG.md +105 -0
  19. package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +20 -0
  20. package/plugins/fh-meta/skills/steel-quench/SKILL.md +25 -2
  21. package/scripts/adapters/mate_agent_boundary.sh +27 -7
  22. package/scripts/capability_effect_probe.sh +173 -5
  23. package/scripts/capability_registry_check.sh +69 -3
  24. package/scripts/digest_landing_check.sh +168 -13
  25. package/scripts/fh_session_load.sh +71 -0
  26. package/scripts/relay_channel.sh +151 -6
  27. package/scripts/selfcheck.sh +47 -0
  28. package/scripts/target_freeze.sh +239 -0
  29. package/scripts/test_adapter_lanes.sh +17 -0
  30. package/scripts/test_evidence_root_psa_lanes.sh +78 -0
  31. package/scripts/test_marker_standpoint_lanes.sh +83 -0
  32. package/scripts/test_marker_thirdparty_lanes.sh +73 -0
  33. package/scripts/test_regression_guard_ci_lanes.sh +170 -0
  34. package/scripts/test_relay_channel_lanes.sh +146 -17
  35. package/scripts/test_satellite_publish_gate_lanes.sh +339 -0
  36. package/scripts/test_target_freeze_lanes.sh +203 -0
  37. package/templates/.git-hooks/pre-commit +306 -6
  38. package/templates/PRE-PUBLISH-CHECKLIST.md +22 -0
  39. package/templates/regression_guard.sh +32 -4
@@ -159,6 +159,37 @@ changes which ground truth the review is checked against — they are orthogonal
159
159
  that maxes out the first while leaving the second at zero has not raised its coverage of
160
160
  standpoint-dependent defects at all.*
161
161
 
162
+ **Relationship to the isolation axis — standpoint is isolation whose scope moved up to the
163
+ harness (operator, 2026-08-18).** Operator wording: *"요는 이것도 '격리' 프레이밍이 하네스 단위로
164
+ 확장되는 거지 … 그 하네스 자체의 입장을 돌리는 거니까 (하네스라는 껍질에 모델이라는 알맹이를
165
+ 넣어서 자신이 직접 돌아가게 하니까)."*
166
+
167
+ The ⓒ isolation axis is normally scoped to a **session**: run the reviewer where the author's
168
+ context cannot reach it — outside the repo, so repo-level instructions are not inherited. `tier2`+
169
+ standpoint makes the same move one scope up: rather than isolating a session from the author's
170
+ context, **boot the target harness itself** — its shell, a model as the filling — and let it run.
171
+ What is isolated is no longer a session but an entire harness's operating context.
172
+
173
+ This is also the answer to the question the axis reliably draws — *"how is that different from just
174
+ running it in the target repo myself, which I already do before opening a PR?"* Running it yourself
175
+ carries your own assumptions into the run; the harness running itself does not. That is the same
176
+ failure the session-scoped isolation axis already records (a reviewer spawned inside the repo
177
+ inherits that repo's instructions regardless of the prompt), one scope up.
178
+
179
+ ⚠️ **A framing, not an identity — do not collapse the two axes.** Axis membership is decided by
180
+ *what the reviewer receives* (`fh_three_layer_canon.md §1-a`): ⓒ receives the author's sentences
181
+ plus the current tree, ⓑ receives the target's own canon. Raising isolation scope to the harness
182
+ **also swaps the input** — you cannot boot the target harness without it reading its own canon — so
183
+ ⓑ is *isolation-scope expansion accompanied by input replacement*. That accompaniment is why it
184
+ stays a separate axis instead of becoming a tier of ⓒ.
185
+
186
+ 🚫 **Do not describe this as role-play or persona assignment.** *"Act as if you were harness X"* is
187
+ exactly the prompt-level disguise that isolation failures already refuted; booting the shell is its
188
+ opposite. The tier ladder encodes the distinction — `tier2b` (same operator, target's real runtime,
189
+ local wiring visible) sits below `tier3` (a different operator of the target harness runs it)
190
+ precisely because the remaining gap is *whose context is still in the room*, not what the reviewer
191
+ was told to pretend.
192
+
162
193
  **Why the same author cannot close this by working inside the target repo and opening the PR
163
194
  there — corrected framing (2026-08-14, after a cross-harness standpoint review of this section
164
195
  itself).** The claim is *not* "the author is structurally blind to their own work" — a controlled
@@ -224,6 +255,37 @@ pass):
224
255
  **🟥 DECIDE IN THIS ORDER — first match wins. Do not pick by matching a description.**
225
256
 
226
257
  ```
258
+ Q0. WHICH TARGETS? — settle the target CLASS(es) before reaching for a tier.
259
+ 🟥 NOT first-match. Q0 can return MORE THAN ONE target, and each one owes its own tier.
260
+ A release that also alters a named peer's contract owes both arms; recording only the
261
+ first shadows the second (caught by cross-family review, 2026-08-17).
262
+
263
+ ⓐ Does a NAMED PEER HARNESS carry this surface?
264
+ Mechanical test, in this order — do not decide from memory or from a name list:
265
+ · a local repo of a cluster peer carries the file this delta changes
266
+ (`for d in ~/projects/*; do test -f "$d/<changed-path>"; done`), OR
267
+ · the peer is declared in `.claude/capabilities` / the cluster registry, OR
268
+ the change edits an adapter under `scripts/adapters/` naming it
269
+ YES → target = that repo (one per peer) → run Q1 for each
270
+ ⓑ Does this delta change CONSUMER-VISIBLE BEHAVIOR — what a consumer's gate blocks or
271
+ passes, what a consumer's session is instructed to do, what an install receives?
272
+ YES → target = a CLEAN INSTALL of the packed artifact → run Q1
273
+ 🟥 This binds NOW, at the change, pre-push (§Sequencing) — NOT deferred to
274
+ the eventual release. Deferral was the first draft's hole: "not a release
275
+ yet" would have let a behavior change ship unexamined and left the releaser
276
+ holding a delta they did not write.
277
+ ⓒ Neither ⓐ nor ⓑ → `not-applicable` STOP.
278
+
279
+ 🟥 A consumer install IS another harness — settled, not open. What Q0 scopes is not WHO
280
+ RECEIVES the change but WHERE IT HAS TO BE EXECUTED, and 🟥 **not «is this file shipped»**:
281
+ almost everything here is shipped, so shipped-ness cannot be the discriminator (see
282
+ «Target class» below). The discriminator is ⓑ's *behavior* clause — the same effect-based
283
+ trigger this section already uses, applied to the consumer as a target rather than as an
284
+ audience.
285
+ Reading Q1/Q2 when the target is an install tree rather than a repo: Q1's "executed in the
286
+ target's repo" = "executed inside the extracted/installed tree"; Q2's local-wiring question
287
+ is answered NO for a bare extraction (`tier2`), YES only if a real consumer node's own
288
+ settings/state were in play (`tier2b`).
227
289
  Q1. Did anything EXECUTE in the target's repo — a command, a script, a suite?
228
290
  NO, I only read files → tier1b(<harness>) STOP.
229
291
  NO, I did not touch its repo → tier1 STOP.
@@ -269,6 +331,24 @@ tier1b(<target-harness>) STATIC standpoint read — the reviewer read the TA
269
331
  nearer value existed. A missing rung does not stay empty — it gets
270
332
  filled by the next one up. Real but weak: see «execution is the
271
333
  load-bearing half» below before crediting it.
334
+ 🟥 «FROM THE TARGET'S REPO» IS LOAD-BEARING, and a field case the
335
+ day this rung shipped shows why. A session changed FH code that would
336
+ make two peer harnesses' capability declarations start being REJECTED
337
+ — genuinely applicable — and reached for `tier1b` because it had
338
+ *read files*. The files it read were FH-local records ABOUT those
339
+ peers (`tracks/_meta/relay/*.cap`), not the peers' own files. Reading
340
+ your own repo's description of a peer is NOT a standpoint read; the
341
+ whole point of the axis is whose ground truth the review was checked
342
+ against, and that was still your own. The honest value there is
343
+ `DEGRADED_NOT_RUN(<peers>)` — applicable, target reachable, nothing
344
+ done at the target. Note the direction of the risk has INVERTED since
345
+ this rung was added: tier1b was created because a real static read had
346
+ no home and got recorded one rung too high; the new failure mode is
347
+ local reading being recorded as tier1b because "I read something"
348
+ feels the same. 🟥 Two different questions get merged here and must
349
+ not be: *does a target exist and is it affected* is **Q0**; *what did
350
+ you actually do at that target* is the **tier**. "The impact is real"
351
+ answers the first and says nothing about the second.
272
352
  tier2(<target-harness>) peer-simulated — the reviewer EXECUTED CODE in the TARGET's own repo
273
353
  (a local clone, real content) and observed the result.
274
354
  🟥 DISCRIMINATOR — «reading the target's real files is NOT this rung».
@@ -305,8 +385,18 @@ tier2b(<target-harness>) same operator, target's real runtime — the SAME h
305
385
  tier3(<target-harness>) actual peer — a DIFFERENT human operator of the target harness ran
306
386
  the change in their real runtime. The only tier with both local
307
387
  wiring AND reviewer independence from the author.
308
- not-applicable the change has no target-harness standpoint to borrow (no
309
- shared-body surface, no cross-repo consumer contract) distinct
388
+ not-applicable the change has no target-harness standpoint to borrow — Q0 found
389
+ neither a named peer harness NOR a release/publish surface, so there
390
+ is no repo and no install to run this from. 🟥 Read the second half
391
+ literally: «no cross-repo consumer contract» does NOT mean «this file
392
+ is never shipped». Almost everything here is shipped (measured
393
+ 2026-08-17: 192/200 recent commits touch an npm-shipped path, and that
394
+ 96% is a LOWER bound — `package.json` is packed without appearing in
395
+ `files[]`). Reading shipped-ness as the trigger makes this value
396
+ reachable in under 4% of commits, i.e. effectively unreachable, and an
397
+ unreachable value teaches authors to delete what they are counting
398
+ (`[[feedback_unreachable_done_when_trains_evasion]]`). The trigger is
399
+ the RELEASE, not the path — distinct
310
400
  from a degrade value; this is a scoping fact, not a miss. Carries the
311
401
  same substantive-grounds-on-the-same-line discipline as a degrade
312
402
  value below — asserting non-applicability without naming what was
@@ -382,11 +472,22 @@ data point.
382
472
  **Mechanization status — `standpoint:` is prose-only today, and this must not be read as more than
383
473
  that.** `crossfamily:`'s degrade triad is hard-blocked at commit (`templates/.git-hooks/pre-commit`,
384
474
  `scripts/test_marker_crossfamily_lanes.sh` — grep-verified: ~20 crossfamily references in the hook,
385
- validated fixtures). `standpoint:` has **zero** matches in that hook and no fixture suite — nothing
386
- stops an author from writing `not-applicable` with a thin justification, or omitting the field
387
- entirely, and no marker-shape check catches it. §Marker required fields in
388
- `.claude/rules/fh_4axis_gate.md` does not yet list `standpoint:` either (add it there when this
389
- mechanizes). This is the honest current state, not a placeholder apology: the field exists so a
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.
477
+
478
+ 🟥 **Two sentences that stood here were STALE and are corrected (2026-08-17, re-measured a
479
+ cross-family reviewer flagged the second, the first fell out of checking it).** They read
480
+ *"`standpoint:` has **zero** matches in that hook"* and *"§Marker required fields in
481
+ `.claude/rules/fh_4axis_gate.md` does not yet list `standpoint:` either"*. Both are false as of this
482
+ date: `grep -c standpoint templates/.git-hooks/pre-commit` → **15**, and the hook *does* enforce one
483
+ property (when `axes-run` carries `ⓑ=→standpoint`, the `standpoint:` line must exist and be non-empty
484
+ — `pre-commit:780-782`); `.claude/rules/fh_4axis_gate.md` lists the field at **§133 and §192**.
485
+ **What is still true is the narrower claim**: the *value* is unvalidated — «the line exists» is
486
+ enforced, «the value is right» is deliberately reserved (§Mechanization Boundary). Do not read the
487
+ corrected sentence as "now mechanized"; read it as "the channel is checked, the judgment is not".
488
+ The distinction this stale text destroyed is exactly the one that matters here, and it destroyed it
489
+ in the *pessimistic* direction — under-claiming coverage is not a safe error either, because it
490
+ invites someone to rebuild a lane that already exists. This is the honest current state, not a placeholder apology: the field exists so a
390
491
  human reader can ask for it and so the *next* occurrence of a false `not-applicable` has something
391
492
  concrete to point at — mechanize on that first recorded false value
392
493
  (`[[feedback_mechanize_at_repetition_prose_before]]`), not before. **Ownership**: this field lives
@@ -449,6 +550,160 @@ proposal (pmh-dev issue #68, verified verbatim in that thread: *"대상 후보
449
550
  behavioral reading after that same review found the file-class reading false for at least one real
450
551
  pair.
451
552
 
553
+ **Target class — «누가 받나» is not the question; «어디서 돌려야 하나» is (operator decision,
554
+ 2026-08-17).** The trigger above says *effect, not file-class*, and that was still not enough: three
555
+ independent marker-audit legs, run the same day against three different markers, all failed at the
556
+ same place — each reasoned *"it ships, therefore there are consumers, therefore a cross-repo consumer
557
+ contract exists, therefore applicable"*. That reading is not obviously wrong; the enum's own words
558
+ (*"no cross-repo consumer contract"*) invite it. What kills it is the size: **192 of the last 200
559
+ commits touch an npm-shipped path (96%, a lower bound — `package.json` ships without being listed in
560
+ `files[]`, so at least one of the 8 remaining is a false negative too)**. A value reachable in under
561
+ 4% of commits is not a scoping value — it prices the axis at a rate this section's own **Trigger**
562
+ paragraph already rejects as over-triggering (*"almost every commit would qualify, which is the
563
+ over-pricing this trigger is trying to avoid, not invoke"*). That is the argument, and it was
564
+ already in this document.
565
+
566
+ ⚠️ **A citation is withdrawn here.** The first draft justified this with
567
+ `[[feedback_unreachable_done_when_trains_evasion]]` — *"an unreachable condition teaches people to
568
+ delete what they are counting"*. An adversarial round showed the direction is **inverted**: that
569
+ memory is about an unreachable **pass condition** a person must reach, whereas `not-applicable` is an
570
+ **exemption**. An unreachable exemption does not make anyone delete anything; it makes the obligation
571
+ fire almost always, and the failure mode is over-pricing and formality, not evasion. Same discomfort,
572
+ different mechanism — do not re-import the citation.
573
+
574
+ 🟥 **State precisely what that 96% does and does not establish — it is narrower than it looks, and a
575
+ cross-family reviewer caught the first draft overreaching on exactly this.** It is a *file-class*
576
+ count, which this very section rejects as a trigger. It is admissible for **one** claim: the legs'
577
+ argument *was* the file-class argument (*"it ships, therefore consumers, therefore applicable"*), so
578
+ the number measures **that argument's reach**, and shows that adopting it collapses `not-applicable`
579
+ to under 4%. It does **NOT** establish how often the *behavioral* trigger (Q0-ⓑ) actually fires —
580
+ that number is **unmeasured**, and the step from "touched a shipped path" to "a consumer standpoint
581
+ obligation arose" is precisely the step Q0-ⓑ exists to make someone take by hand. Anyone citing 96%
582
+ as the applicability rate is citing it wrong.
583
+
584
+ **Corrected figure, and the instrument defect behind it — surfaced only because an adversarial round
585
+ demanded the hand-check.** The first pass reported **192/200 (96%)** with 8 non-touching commits. Two
586
+ faults, in opposite directions:
587
+ - **Instrument**: the scan ran `--first-parent` *including merge commits*, and `--name-only` emits no
588
+ file list for a merge — so merges were silently scored *non-touching*. Re-run with `--no-merges`:
589
+ the ratio held at 192/200, but the non-touching set collapsed to **6 real cases** (2 commits
590
+ legitimately carry no files).
591
+ - **Hand-check of all 6** (this repo's own publish-a-number rule; N=6 made it trivially cheap):
592
+ **2 are `package.json`-only**, which npm packs regardless of `files[]` — they *are* shipped. The
593
+ other 4 are genuinely unshipped (`.gitignore` · `knowledge/shared/learnings/…` · two `scripts/`
594
+ paths absent from `files[]`).
595
+
596
+ ⇒ **Measured: 194/200 = 97%, every exception hand-verified.** ⚠️ One direction stays unexamined: the
597
+ scan applies **today's** `files[]` to **past** commits, and that manifest has only grown, so older
598
+ commits are over-counted as shipped. That FP direction is **unmeasured** — read 97% as *"under the
599
+ current manifest"*, never as a historical claim.
600
+
601
+ 🟥 **And read the reachability argument in BOTH directions, which the first draft did not.** It
602
+ measured only what the *rejected* reading does to `not-applicable` (collapses it to <4%). It never
603
+ measured what the *adopted* reading does — Q0-ⓑ is a judged behavioral test, so no scan settles it,
604
+ and the honest statement is that **the new rate is unknown in both tails**: the value could stay rare
605
+ (if most shipped-path commits do change consumer-visible behavior) or become near-universal (if most
606
+ do not), and a near-universal exemption is a rubber stamp, which is its own failure — not the one
607
+ this edit was fixing. Watch the next 20 markers rather than assuming this landed in the middle.
608
+ ⚠️ **The «unreachable ⇒ trap» premise is also weaker here than the first draft implied**: `tier1` is
609
+ explicitly *"NOT itself a failure"*, so even under the rejected reading an author had a cheap honest
610
+ value to write and was not cornered into deleting anything.
611
+
612
+ ⚠️ **Two different denominators, both of which happen to be 200 — do not merge them.** The 192/200 is
613
+ over the **last 200 commits**. The 177/200 below is over the **200 markers in
614
+ `tracks/_meta/.axes_23_passed_*.marker`**. Commits and markers are different populations (a marker
615
+ covers a delta, not a commit; unmarked commits exist), and their coincident size is an accident of
616
+ this corpus. No ratio may be carried from one to the other.
617
+
618
+ So the split is by **execution site**:
619
+
620
+ | Q0 target class | What discharges the standpoint arm | Binds at |
621
+ |---|---|---|
622
+ | named peer harness (qasp · pmh · mate · gstack · sibling hub) | the enum as written — `tier1b`/`tier2`/`tier2b`/`tier3` against that repo | the change, pre-push |
623
+ | generic consumer install | run the **packed artifact in a clean install** — see the split below; the *presence* half already runs at ship time, the *execution* half does not exist yet | the **release/publish** delta |
624
+ | neither | `not-applicable` | — |
625
+
626
+ **🟥 What that arm is actually covered by today — corrected in the same session that wrote it, by
627
+ reading the lanes instead of naming them.** The first draft of this table said the consumer-install
628
+ arm was *"already mechanized"* by `publish_freshness_check.sh` · `package_coverage_check.sh
629
+ --vs-tarball` · `test_capability_entrypoint_shipping.sh`. Reading those three shows they answer a
630
+ narrower question than the arm asks:
631
+
632
+ ```
633
+ covered, AT PUBLISH `prepublishOnly` = prepublish_scope_note · publish_freshness_check ·
634
+ version_lockstep_check · package_coverage_check --vs-tarball ·
635
+ public_surface_scan_files ← read from package.json, not recalled
636
+ covered, BUT IN CI test_capability_entrypoint_shipping.sh is NOT in that chain — it runs under
637
+ the selfcheck anchor loop (`npm test`/CI). Naming it as a ship-time lane was
638
+ wrong; a green CI is not a publish gate (§Local Execution First).
639
+ NOT covered (exec) extract the tarball into a clean directory, run the gate as a consumer would,
640
+ observe it behaves as intended. Measured 2026-08-17: of the lanes that invoke
641
+ `npm pack`, ZERO extract or execute the result — `--vs-tarball` compares a
642
+ FILE LIST (`npm pack --dry-run --json`), it never unpacks.
643
+ ```
644
+
645
+ This matters because it is the same asymmetry this section already argues for: *"execution is the
646
+ load-bearing half"*. An arm discharged by presence checks alone is a `tier1b`-shaped arm wearing a
647
+ `tier2` label — the exact substitution the `tier1b` rung was added to stop. **So: the presence half
648
+ is mechanized and free; the execution half is a named residual, discharged by hand
649
+ (`npm pack` → extract to a clean dir → run the gate → record the command and the output, per the
650
+ `tier2` discriminator) until a lane exists.** Do not cite this arm as fully mechanized. The
651
+ decision's «no new machinery» framing was correct about the *presence* half and overstated about
652
+ the whole — recorded here rather than quietly narrowed, because a reader reaching for this table
653
+ mid-release is exactly the reader who would otherwise skip the half that has no lane.
654
+
655
+ **What this decision costs, stated rather than hidden.** Of the three legs, **leg B was right and
656
+ the other two were wrong to generalize it**: the `release_2.3.0` marker's `not-applicable` IS a
657
+ defect under this closure (a release delta whose own grounds line concede *"소비자 install 의 게이트
658
+ 수용은 바뀐다 (BREAKING 2건)"* — that is the trigger being met, written out in the field that denies
659
+ it), while an ordinary commit touching a shipped script correctly stays `not-applicable`. Row 1
660
+ (PR #368) needs no reclassification: its target was a sibling hub with its own repo, a named peer.
661
+ ⚠️ Those three legs were **not decorrelated** — same family, same prompt shape, same canon — so their
662
+ 3/3 agreement is closer to one observation than three; it is cited here as *the pattern that exposed
663
+ the definitional hole*, never as three confirmations
664
+ (`[[feedback_decorrelation_axis_is_what_you_send]]`).
665
+
666
+ **Measured after this edit shipped, and it REFUTES the residual this paragraph first carried.** The
667
+ original text read: *"177 of the 200 corpus markers carry no `standpoint:` line at all … that pool is
668
+ larger than the one measured and remains unexamined."* It has now been examined, and the pool is not
669
+ larger — it barely exists:
670
+
671
+ ```
672
+ 201 markers · 24 carry `standpoint:` · 177 do not
673
+ 172 predate the field itself (born 2026-08-14, PR #370 landed 14:13) — structural, not a miss
674
+ 2 written the same day but BEFORE 14:13 (12:29 · 12:39) — also structural
675
+ 3 written after the field existed (16:41 · 19:10 · 21:06) ← the entire real pool
676
+ 0 absences dated 2026-08-15 or later — adoption is 100% from day two onward
677
+ ```
678
+
679
+ 🟥 **The first step was decomposition, not adjudication.** Counting "no field" as "not recorded"
680
+ folds *the field did not exist yet* into *the author skipped it* — the same `not-found ≠ 0` collapse
681
+ this session hit three separate times (`[[feedback_not_found_is_not_zero_family]]`). Splitting by the
682
+ field's own birth timestamp is what turned 177 into 3.
683
+
684
+ **Hand-check of all 3, judged from the real diffs** (their markers use the pre-2026-08-17 four-letter
685
+ `axes-run` notation, where `b` means first-real-use and NOT standpoint — reading the marker's own
686
+ self-description instead of the diff would have inverted two axes):
687
+
688
+ | commit | delta | verdict under Q0 |
689
+ |---|---|---|
690
+ | #375 `cedd8ac` | `version_lockstep_check.sh` +11 lines, **all comment** | `not-applicable` would have been correct — a **missing line**, not a wrong judgment |
691
+ | #374 `0690ba7` | 7 files, +409 — shipped gate scripts plus `templates/degrade_direction_scan.sh` | 🟥 **genuinely under-recorded** (Q0-ⓑ, and `templates/` propagates to field harnesses) |
692
+ | #373 `549a4bc` | `ko-tech-writer/SKILL.md` +63/−12 (shipped plugin) | 🟥 **genuinely under-recorded** (Q0-ⓑ) |
693
+
694
+ **The direction matches §6's earlier finding: over-claiming 0, under-recording only.** A gate that
695
+ tightens *"were you really tier2?"* cannot catch this direction by construction.
696
+
697
+ ⚠️ **Retroactive-application caveat**: Q0 was written 2026-08-17 and these three are 2026-08-14, so
698
+ their authors could not have applied it. What survives the caveat is narrower and still real — the
699
+ **field existed** by then, so the absent line is a gap independent of Q0.
700
+
701
+ **Consequence for sequencing**: the argument for postponing mechanization was *"absence is the
702
+ dominant reality, so validating values would only tighten a recording minority."* That premise is
703
+ **dead** — recording is 100% from 2026-08-15 onward. ⚠️ The refutation does **not** travel to peers:
704
+ pmh-dev's 42 markers carry the field 0 times, and whether that is late arrival or non-adoption is
705
+ **unmeasured** there. `thirdparty:` likewise stands at 2 corpus instances — unmeasured, not clean.
706
+
452
707
  **Relationship to `harness_verification_core_extended.md`'s core/extended axis**: tier2 and tier3
453
708
  are both "extended" in that document's sense (they require a cluster member's engine or repo to
454
709
  discharge) — this field does not compete with that doctrine, it subdivides one corner of it.
@@ -110,6 +110,11 @@ this chamber's field emit terminus); an **FH-internal utility** (a skill/script/
110
110
  field harness) instead routes through the **New-Skill Pre-Commit gate + `asset-placement-gate`** (the
111
111
  same gate every FH asset passes). KILL emits nothing — the workspace stays as the evidence record.
112
112
 
113
+ > 🟥 **2026-08-17 이후 이 문단은 단독으로 읽으면 틀린다.** 아래 «clears **all four**» 중
114
+ > **(1) net-new 는 더 이상 KILL 사유가 아니다**(→ `CURATED` 라우팅) — §3-SCREEN-2026-08-17 로
115
+ > 대체됐고, (2) 는 `NOT-APPLICABLE` 라우팅이다. **(3)·(4) 는 KILL 로 그대로 유효하다.**
116
+ > 아래 본문은 그 정정 이전의 원문이며 이력으로 보존한다.
117
+
113
118
  **EMIT-worthiness — the measured screening criterion (runs #5–#6, 2026-07-14)**: six chamber runs, EMIT
114
119
  0/6, all KILL. A candidate is emit-worthy only if it clears **all four** of — (1) **net-new** (not a
115
120
  reinvention of an existing FH/official asset, nor a cosmetic re-wrap of code that already ships — runs
@@ -132,6 +137,90 @@ standalone graduations — not yet *birthing*. **Graduation order** (run #6's po
132
137
  hub-state-dependent capability graduates hub-internal → proven in use → THEN extracted portable, never
133
138
  speculated standalone-first — the only path every successfully-portable FH asset actually took.
134
139
 
140
+ ---
141
+
142
+ ### 🟢 §3-SCREEN-2026-08-17 — **(1) net-new 는 더 이상 KILL 사유가 아니다** (운영자 결정)
143
+
144
+ > *"사람들이 자신이 구상한 하네스를 인큐베이팅으로 해서 출하하려는데, 그럴 때마다 항상
145
+ > **«이미 있는 기능이야»라고 리젝시키면 쓰고 싶은 생각이 들까?** 그것보다는 «이미 이러한
146
+ > 레퍼런스가 있는데 **너만의 방법으로 커스터마이징하고 싶다면 여기서 출발해보자**»가 되어야 할
147
+ > 것 같아. **인큐베이팅이 필요없다면 큐레이팅으로 가면 되는 거고.**"*
148
+ > · *"**그 사람이 만들려는 걸 인큐베이터가 막을 필요가 있을까. 그냥 만들게 두면 되지.**"*
149
+ > · *"인큐베이터는 **훈수를 놓게 하기 위한 장치가 아니라**, 원하는 프로젝트나 하네스를
150
+ > **출하 전부터 미리 굴려보고 사용해보게 하는 에뮬레이션**과 그로 인해 자신만의 것을
151
+ > **성숙하게 출하시키기 위한 장치**"*
152
+
153
+ **바뀌는 것 — 기준은 남고, 그 기준의 «판정 결과»가 바뀐다:**
154
+ ```
155
+ 전 net-new 미달 → KILL (후보가 회수된다. 사용자는 빈손으로 돌아간다)
156
+ 후 net-new 미달 → **CURATED** (선행 목록 + 가장 가까운 것 + 그것이 안 덮는 델타를 준다)
157
+ → 사용자가 «그거 쓸게» 면 거기서 끝(큐레이팅 종료)
158
+ → 사용자가 «내 걸 만들래» 면 **인큐베이터로 들어간다**
159
+ ```
160
+ 🟥 **전환점은 판정이 아니라 의사다.** 인큐베이터는 「이게 새로운가」를 묻지 않고 「너 만들 거냐」를
161
+ 묻는다. 그리고 선행 목록은 **회수 통보가 아니라 재료 목록**이다 — 아래 §3-SCREEN-b.
162
+
163
+ 🟥 **초판이 여기서 게이트를 무력화했고, 적대검증이 커밋 전에 잡았다. 그 정정을 남긴다.**
164
+ 초판은 «진짜 KILL» 을 셋으로 줄이며 **(3) 을 「precision 을 만들 수 없음(원리적 불가)」으로
165
+ 바꾸고 (4) 를 「형태 라우팅」으로 격하**했다. 둘 다 틀렸다:
166
+ - **(3) 의 원문은 «원리적 불가» 가 아니라 «실측 미달» 이다** — *"run #5's rule scored **5/5
167
+ false-positive** on 111 real files; run #6's heuristic scored **14/22 false-fire**"*.
168
+ 초판 문장대로면 **런 #5·#6 이 KILL 이 아니게 된다**(둘 다 precision 을 *만들 수는* 있었다).
169
+ - **합산이 진짜 문제였다**: 이 절이 챔버의 실증된 값어치를 셋으로 적는데
170
+ (*"preventing **reinventions** · **low-precision births** · **premature standalone
171
+ graduations**"*), 초판이 재발명→CURATE · (3)→원리적불가 · (4)→형태라우팅 으로
172
+ **셋 다 뺐다.** 남는 entry 스크린이 「사용자가 만들겠다고 하나」 하나뿐이 됐다.
173
+
174
+ **그래서 이렇게 정리한다 — entry 와 exit 를 섞지 않는다** (§3-c 가 *"two different questions,
175
+ **do not merge them**"* 이라고 명시한 그 분리):
176
+
177
+ ```
178
+ ── ENTRY (들어갈 때, 이 절) ──────────────────────────────────────────────
179
+ (1) net-new 미달 → **CURATED** 라우팅. KILL 아님 (§3-SCREEN-2026-08-17)
180
+ (2) artifact-shaped 미달 → **NOT-APPLICABLE**. 판단-방법은 이 인큐베이터의 산출 형태가 아니다
181
+ (3) precision **실측 미달** → **KILL 유지** 🟥 «못 만든다» 가 아니라 «돌려봤더니 나빴다» 다
182
+ (4) hub-state 의존 → **KILL 유지** 🟥 premature standalone graduation 방지가 이 축의 일이고,
183
+ §3 말미의 Graduation order(hub-internal → proven → extract)가 그 처방이다
184
+ ★ 사용자가 «안 만들래» → **종료**(큐레이팅으로 끝. KILL 원장에 세지 않는다)
185
+
186
+ ── EXIT (나올 때, §3-c) ──────────────────────────────────────────────────
187
+ «설 수 없다»(inability-to-run) → **KILL**. 약함은 KILL 아님(§3-c ④)
188
+ ```
189
+ 🟥 **원리적으로 precision 을 만들 수 없는 대상**(판별 쌍을 구성할 수 없는 것)은 KILL 이 아니라
190
+ **`NOT-APPLICABLE`** 이다 — 후보의 실패가 아니라 **이 인큐베이터의 바가 안 맞는 것**이다.
191
+
192
+ ⚠️ **§3-c 의 한 문장이 이 정정으로 갱신된다**: *"clear this one and fail the first
193
+ (**alive, but a reinvention**)"* — 그 분기는 이제 KILL 이 아니라 **CURATED 로 라우팅**된다.
194
+
195
+ **⚠️ 경계가 사라지는 것은 아니다.** 재발명이라는 *사실*은 여전히 측정하고 기록한다. 바뀐 것은
196
+ 그 사실을 **누구에게 무엇으로 주느냐**다: 게이트의 판정 근거 → 제작자의 출발점.
197
+
198
+ ### §3-SCREEN-b — **선행은 재료다** (인큐베이팅 가속화, 운영자)
199
+
200
+ > *"앞서 찾았거나 정말 새로운 거라면 검색한 레포 중에서 **배워올 만한 것들을 일부만 빌려와서
201
+ > 개발해 나가는 것**이 곧 인큐베이팅 **가속화**의 방법이 되기도 하겠지. 제로부터 자신만의
202
+ > 아이디어만으로 하는 게 아니라 **프런티어에서 배워옴으로서 시작점부터 프런티어급 가능성을
203
+ > 품은 채 태어나게 하는 것.** 개발자들이 코드를 다 새로 짜는 게 아니라 **구글링하는 전통**이
204
+ > 있던 것처럼."*
205
+
206
+ 🟥 **가장 날카로운 형태**: **재발명 위험이 높다는 것은, 그만큼 빌려올 선행이 많다는 뜻이다.**
207
+ 옛 taxonomy 는 이것을 **거꾸로** 읽었다 — 재료가 풍부할수록 죽였다.
208
+ ```
209
+ 전 레퍼런스 발견 → KILL 근거 → 배출 확률 0 · 사용자는 빈손
210
+ 후 레퍼런스 발견 → 빌드 재료 → 시작점부터 프런티어급 · 사용자는 목록을 들고 간다
211
+ ```
212
+ **같은 정보, 소비처만 바꾸면 정반대로 작동한다.** 그리고 이건 §3-c 의 EMIT 바(«설 수 있나»)를
213
+ **더 쉽게 넘게 만든다** — 즉 큐레이팅은 인큐베이팅의 **관문이 아니라 성공률을 올리는 장치**다.
214
+
215
+ **실물 대조(챔버 런 #13 `n-eff-probe`, 2026-08-17)**: K1(재발명)으로 KILL 됐고, 그 근거였던
216
+ 선행 5건(capture-recapture · double-fault measure · Snyk VulnBench · BenchGuard · SAST overlap)은
217
+ 새 taxonomy 에서 **부품 목록**이다 — 통계량은 기성품을 쓰고 새로 만들 것이 「크로스-노드 배선 +
218
+ 픽스처」 하나로 줄어든다. **판정을 소급해 무르지는 않는다**(사전등록 원칙). 바뀐 것은
219
+ **다음 후보가 같은 자리에서 어떻게 처리되는가**다.
220
+
221
+ > **정본**: `tracks/_meta/fh_signal_2026-08-17_incubator-is-not-a-gate.md`
222
+ > — 흐름 전체 · 큐레이팅의 자리(⑤ 증폭자) · 진입로 B(축적 기반 제안) · 원장 과소계상 실측.
223
+
135
224
  **Chamber scope — what belongs in the chamber at all (run #7, 2026-07-14)**: run #7 tested a hub-internal
136
225
  reactivation of the cluster-wizard signal and KILLed it — decisively on its own merits (its "narrow
137
226
  net-new" claim collapsed against the real shipped registry and an already-existing synergy skill), but
@@ -218,7 +307,89 @@ meta harness easy to birth (a declaration starts one) │ needs endless tendi
218
307
 
219
308
  A meta-harness cannot clear a bar that reads *"walks on day one"* — not because it is worse, but
220
309
  because unbounded growth is its point. FH itself is the standing evidence: it is tended continuously,
221
- by design. **Therefore a meta-harness candidate is not a chamber candidate.**
310
+ by design. ~~**Therefore a meta-harness candidate is not a chamber candidate.**~~
311
+
312
+ ### 🟥 §3-a-1-2026-08-17 — 위 결론 문장은 **폐기한다. 범위가 과했다** (운영자 결정)
313
+
314
+ **폐기 사유 ① — 아래쪽 범위는 이미 넓혀져 있었고, 이 파일이 그 확장을 반영하지 않았다.**
315
+ `ship_readiness_gate.md §Ⓑ-layering`(2026-08-16, 운영자 발화 기반)이 ② 의 범위를 이렇게 적는다:
316
+ > *"② 프로젝트 인큐베이터 | **유닛을 낳는다 — 하네스만이 아니다.** … 범위가 **하네스 · 스킬 ·
317
+ > 에이전트** · 하네스 형태가 아닌 일반 레포까지다"*
318
+
319
+ 🟥 **초판은 이걸 «8일 전에 뒤집혔는데 안 고쳐졌다 = half-fix 전파 결함» 이라고 적었다. 틀렸고,
320
+ 적대검증이 잡았다** — 그 줄이 넓힌 것은 사다리의 **아래쪽**(스킬·에이전트·일반 레포)이고,
321
+ 폐기 대상 문장은 **위쪽**(meta-**harness**)에 대한 진술이라 **둘은 논리적으로 양립한다**
322
+ (하위를 포함한다고 상위가 포함되지 않는다). 게다가 그 줄에 **「md 규율」은 없다** — 그건
323
+ 2026-08-17 운영자 발화에서만 나온다. **결함 귀속을 철회하고 사유를 약화한다**: 뒤집힌 게
324
+ 아니라 **범위 확장이 이 파일에 반영되지 않은 것**이다. (그리고 폐기 자체는 사유 ③ 으로 선다.)
325
+
326
+ **폐기 사유 ② — 논거가 실제로 덮는 범위는 «하네스 급» 뿐이다.**
327
+ 위 논거는 *"unbounded growth 가 본질이라 day-one walk 를 못 넘는다"* 인데, 그건 **meta-harness**
328
+ 에 대한 진술이다. 스킬 · 에이전트 · md 규율은 **day-one walk 가 가능하다 — 🟥 단 미검증이다.**
329
+
330
+ ⚠️ **초판은 «실제로 넘고 있다» 고 적었는데 근거가 없었고, 반례가 같은 레포에 있다.**
331
+ day-one walk 의 정의는 *"on the first run, with the user adding nothing, the thing produces
332
+ something useful"* 이고 반대말이 `built-but-not-wired`, 그리고 *"**wiring is part of the birth**"*
333
+ 다. **md 규율은 구조적으로 호출부가 없다**(살리언스). 실제 반례:
334
+ `.claude/rules/fh_4axis_gate.md` 의 `standpoint:` 필드 — *"Still validated by nothing — zero hook
335
+ lines, no fixture suite"* 였고, `tier1b` 등급이 **없어서 정적 리뷰가 tier2 로 기록**됐다.
336
+ `axes-run` 기호 키도 53건 중 2건이 옛 의미로 쓰였다. **태어났고 안 걸은 md 규율들**이다.
337
+
338
+ ⇒ **유닛 클래스별 day-one walk 판정자를 따로 건다**:
339
+ ```
340
+ 스킬 · 에이전트 첫 런에서 사용자가 아무것도 안 더하고 쓸 만한 산출이 나오는가
341
+ md 규율 🟥 **블라인드 floor-tier sim 이 실제로 그 규율을 발화하는가**
342
+ (CLAUDE.md §Skeleton, Not Muscle — "done 은 블라인드 세션이 실제로 발화하는 것")
343
+ ```
344
+
345
+ **폐기 사유 ③ — 그 셋이 이 인큐베이터의 최대 산출이다** (운영자, 2026-08-17):
346
+ > *"**FH 의 스킬 상당수는 후자식(축적 기반)으로 만들어졌고** 전자도 거기에 해당하지.
347
+ > FH 스킬은 **내 아이디어로 발명**한 거니까."* ·
348
+ > *"인큐베이터의 **최대 레버는 하네스**이지만 그 **하위의 것들은 다 포함**된다.
349
+ > 스킬 · 에이전트 · **md 규율** 등…"* ·
350
+ > *"인큐베이션 대상은 마감시점 정리 시 **FH 자체가 될 수도 있다.**"*
351
+
352
+ ### 🟥 그러면 경계가 필요하다 — 판별 기준 (운영자)
353
+
354
+ 경계 없이 폐기만 하면 인큐베이터 정의가 **«FH 가 한 모든 것»**이 되어 계상이 무한대가 되고,
355
+ 그건 1건만 세는 것과 똑같이 못 쓴다. 기준은 이것이다:
356
+
357
+ > *"**스킬 없이도 FH 는 동작하지만, 그걸 단축시킬 유닛을 낳은 것까지는 인큐베이팅으로 봐야
358
+ > 한다.**"*
359
+
360
+ ```
361
+ ❌ 「FH 가 그 일을 했나」 — 이러면 전부가 인큐베이팅이라 측정 불능
362
+ ⭕ 조건 1 「그 일을 **단축시키는 유닛을 낳았나**」
363
+ ⭕ 조건 2 「그 유닛이 **자기 소비처를 새로 갖는가**」 ← 🟥 **크기 축. 둘 다 필요하다**
364
+ (별도 레포 · 플러그인 엔트리 · 독립 호출면 — «호출부가 새로 생겼나»)
365
+
366
+ 범위 하네스 ← 최대 레버
367
+ ⊃ 스킬 · 에이전트 · **md 규율**
368
+ ⊃ 하네스 형태가 아닌 일반 레포
369
+ ```
370
+ 🟥 **조건 1 만으로는 계상이 무한대가 된다 — 적대검증이 반례를 냈다.**
371
+ > `templates/.git-hooks/pre-commit` 에 필드 검사 한 줄 추가 → ⓐ md 규율이고 ⓑ 그 일을 단축시키고
372
+ > ⓒ 「유닛」으로 셀 수 있다 = **세 조건 충족.** 그런데 그건 평범한 Mode D 자기개발이고
373
+ > **4축 게이트가 이미 관할한다.** 여기에 «대상이 FH 자체일 수 있다» 가 붙으면
374
+ > **모든 FH 세션의 대부분 커밋이 인큐베이팅**이 된다.
375
+
376
+ **조건 2 가 그걸 자른다**: 기존 훅에 줄을 더하는 것은 **소비처가 이미 있던 것**이라 탈락하고,
377
+ `dashboard-dev`·`ko-tech-writer`(플러그인 엔트리 신설)·`the-bible`(별도 레포)은 통과한다.
378
+ **「일을 했다」와 「유닛이 태어났다」를 가르는 것이 조건 1, 「유닛」과 「기존 것의 확장」을
379
+ 가르는 것이 조건 2** 다.
380
+
381
+ ⚠️ **그래도 남는 잔여**: 조건 2 는 「소비처가 새로 생겼나」라는 **사후 관측**이라, 만드는 중에는
382
+ 판정이 유보된다. 사전 판정이 필요하면 «이것이 태어나면 어디서 불리는가» 를 INTENT 에 적게 하는
383
+ 것이 대응이지만 **미구축**이다.
384
+
385
+ ### 위 ⚠️ 회고 관찰은 어떻게 되나 — **재분류하되 폐기하지 않는다**
386
+
387
+ 아래 회고 관찰(«KILL 된 것 중 chamber-internal metering · hub-internal orchestration ·
388
+ cluster wizardry · org relay 는 meta-shaped 였다»)은 **여전히 유효한 관찰**이다. 다만 그
389
+ 처분이 바뀐다: **「스코프 밖이라 KILL」이 아니라 「meta-*harness* 급이라 day-one walk 바를
390
+ 다르게 받아야 한다」**이다. `NOT-APPLICABLE` 분리(아래 문단)는 그대로 필요하고, 오히려
391
+ 이 정정으로 **더 필요해진다** — 이제 통이 셋이다: `KILL`(못 선다) · `CURATED`(선행이 있다,
392
+ §3-SCREEN-2026-08-17) · `NOT-APPLICABLE`(meta-harness 급이라 이 바가 안 맞는다).
222
393
 
223
394
  ⚠️ **Retrospective observation, not a finding.** Re-reading the run ledger along this axis: of the
224
395
  KILLed candidates, those aimed at chamber-internal metering, hub-internal orchestration, cluster
@@ -387,6 +558,49 @@ one harness sees runtime behaviour and not source structure, another sees source
387
558
  runtime. **The meta-harness's job is not to dig a deeper well; it is to make outputs flow across the
388
559
  boundary between wells.**
389
560
 
561
+ ### 4-c. The return leg, and why the cluster exists at all (operator, 2026-08-17)
562
+
563
+ §4-b says outputs **flow across** the boundary. It does not say what happens to what flows *in*,
564
+ nor why FH pays for the crossing. Both were load-bearing and neither was written down. Operator's
565
+ formulation, in three parts — and the third is the one that makes the first two a strategy rather
566
+ than a nice habit:
567
+
568
+ ```
569
+ 1 BORROW-EXECUTE (standing) FH does not endlessly BUILD the capability it lacks.
570
+ It reaches into a cluster harness and RUNS theirs.
571
+ 2 INTERNALIZE (selective) Of the insight that comes back, only what is genuinely worth
572
+ adopting is PROPOSED and absorbed. Never automatic.
573
+ 3 THE POINT IS TIME Not capability breadth. What FH buys by not-building is time to
574
+ sharpen its OWN edge — and that is what lets it keep pace with a
575
+ fast-moving model layer instead of being outrun by it.
576
+ ```
577
+
578
+ 🟥 **Without (3), (1) inverts on reading.** "We borrow instead of building" reads as *"FH need not
579
+ have capabilities"* — which turns the cluster into dependency. The operator's claim is the opposite:
580
+ **borrowing is how FH protects its own sharpness.** State (3) whenever (1) is cited.
581
+
582
+ **The asymmetry in (2) is the whole filter.** Borrowing is constant; absorbing is rare and gated by
583
+ a proposal. Automatic absorption produces a collage of other harnesses' capabilities wearing FH's
584
+ name — the exact opposite of (3), because a collage has no edge to sharpen.
585
+
586
+ **First recorded run of the full loop (2026-08-17, gstack → qasp, FH as governor).** FH did not
587
+ build a DX/product lens; it stood up the gstack persona (`/plan-devex-review`, `/qa-only`) against a
588
+ qasp change and observed. The review returned three findings FH's and qasp's lenses structurally do
589
+ not ask (guide-promises-vs-delivered-bundle · the shipped document's readability · release-doc
590
+ drift). Then the filter ran, and **it rejected more than it accepted**:
591
+
592
+ | finding | disposition |
593
+ |---|---|
594
+ | anchor should be *discovery-based*, not a hand-kept list | **rejected** — measured against FH and the transfer did not hold; a control showed the suites believed unwired do run by another path |
595
+ | guide promises ≠ delivered bundle | **rejected** — `package_coverage_check.sh` already covers it |
596
+ | release docs did not follow | **rejected** — §④-b entry-point drift covers half; not enough delta |
597
+ | **a shipped document is never read as a reader before it ships** | **accepted** — and NOT because gstack said so: FH reached the same place independently the same day from a different axis, recording that *"readability is measured only by rendering it or by a person; a static scan catches what is ABSENT, never what is UNREADABLE"* with 12 findings and **0** caught by static scanners. **Two independent arrivals is the internalization signal.** One source alone leaves it a candidate. |
598
+
599
+ ⚠️ **Honest scope**: one run, one target, one third harness. This is an existence proof that the
600
+ loop closes, not a rate. And the filter rejecting 3 of 4 is the expected shape — if it accepted
601
+ everything it would not be a filter, it would be a funnel.
602
+
603
+
390
604
  Three rules, in falling order of how easily they are broken:
391
605
 
392
606
  1. **Crossing must not overwrite the receiving harness's identity.** If harness B is deliberately