quad/.claude/qa-request/pre-implementation-handtrace-round10-followup.md
qwreey ae34cfa316
qa: 10라운드 결정 반영 (H-147~H-158) — fn/cleanup 자기 구독 금지, Refresh 캐치업 폐기, 루트는 Claim으로
- H-147 (A): fn/cleanup은 자기 구독을 못 바꾼다 — rawRerun(force)/Rerun 분리, 진입 canExecute 게이트,
  네 진입점+_bindDestroying에 _running/_cleanupRunning 가드, H-143(원샷) 소멸, 자기 leaf 파괴 UB
- H-148: 루트는 밖에서 .Parent=가 아니라 quad가 Claim으로 소유 → research/existing-mount-plan.md 신설,
  H-146 예외·전용 문구 폐기, archive 부활 배너
- H-149 Observer 진입점 인라인 / H-150 Effect._blocker 제거 / H-151 _epochs는 emit 때만(게이트는 emit 경로만
  미룬다 계약) / H-152 GateNode StateBrand:register / H-153 Store 예약 이름 런타임 가드 + 그림자=store 자신 /
  H-154 InstanceChildHandler dedup / H-155~H-157 stale
- 감사 3→5→2→3→1→0, /code-review high 10건 중 7 반영, 셋(H-159~H-161)은 -round10.md §4 문항으로

Co-authored-by: qwreey <me@qwreey.moe>
Claude-Session: https://claude.ai/code/session_01546hjsYNLSMZdHdPyTZaGb
2026-08-28 13:00:49 +09:00

257 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 구현 전 손 트레이싱 **10라운드** — 결정과 반영 (2026-08-28)
> **이 파일이 무엇인가**: `-round10.md` §4 문항 7건(+ 갈래 없는 넷)에 대한
> 사용자 결정과 근거, 그리고 `base/` 반영 결과. **결정의 소스는 이 파일**이고
> 발견 원문은 `-round10.md`. 사용자가 *"하나하나 같이 보자"*라고 해 이번 라운드는
> 대화형으로 처리한다(배치 회신 대신) — 반영은 전부 정한 뒤 한 번에.
## 진행 표 (상태의 소스)
| 문항 | 무엇 | 상태 |
|---|---|---|
| `H-147` | 죽은 핸들에서 `Rerun()` | ✅ **확정 — 문항의 전제를 뒤집음**: `fn`/cleanup은 자기 생명주기를 못 바꾼다(A). `H-143`도 함께 소멸 |
| `H-148` | `Parent` 거부 문구 | ✅ **전제 정정** — 문구가 아니라 루트 마운트 표면의 부재. `research/existing-mount-plan.md` 신설(`Claim` + `D.Mapper`), `H-146` 루트 예외 폐기, 전용 문구 철회 |
| `H-149` | Observer `Subscribe` 위임과 `level 2` | ✅ **확정 (a)**`Subscribe`/`Unsubscribe`도 게이트·등록을 인라인, 위임 없음 |
| `H-150` | `Effect._blocker` 죽은 부품 | ✅ **확정 (a)** — 제거, 억제는 Effect 핸들의 `canExecute` |
| `H-151` | 게이트 우회 계약 | ✅ **확정 — (a) 문서화 + `Refresh` 캐치업 폐기**: Effect의 `_epochs`는 emit 수신 때만 갱신, 재바인드/재구독은 초기 설치와 같다 |
| `H-158` | `state:Block(blocker)` 슈가 잔존 (이 대화에서 나옴) | ⏳ 권고: 폐기 → `state:Apply(blocker)` |
| `H-159`~`H-161` | 반영 뒤 `/code-review high`가 낸 새 메커니즘 셋 (`-round10.md` §4 하단) | ⏳ 판단 대기 — 바인드 전 emit 캐치업 / `Destroying` 경로 cleanup `Rerun` / M5 루트 부착·다중 스크립트 `Claim` |
| `H-153` | Store 예약 이름 런타임 가드 | ✅ **확정 (a)** — 생성자·`Of(name)`에 예약 이름 검사(level 2), 그림자 = store 자신 (I) |
| `H-154` | `InstanceChildHandler` dedup | ✅ **확정 (a)** — retractor 첫 줄 `if nextValue == v then return end` |
| `H-152`/`H-155`~`H-157` | 갈래 없음 | ✅ 반영(`gate-plan.md` 조립 첫 줄 `StateBrand:register` / `ROADMAP.md` M6×3·M11 / `debounce-throttle-plan.md` 7절 `H-32` 문단 / `store-plan.md` 빈 Store 실측 완료) |
## `H-147` — 전제 정정: `fn`/cleanup은 자기 구독을 바꿀 수 없다 (A) — `H-143` 소멸
문항은 "(a) UB / (b) `_everAlive` / (c) `wasAlive` 위치"였는데, 대화 중에 **문제의
뿌리가 `H-143`의 허용 자체**라는 것이 드러났다.
**경위**:
1. 사용자가 *"canExecute 자체가 유저함수인 cleanup 아래 있으면"*을 제안 → 그러면
생성자 최초 실행이 죽는 문제(감사 2라운드와 같은 함정)를 짚었고, `wasAlive`
cleanup 앞에서 잡는 절충을 냈다.
2. 사용자: *"처음부터 rerun 이 're'-run 인데도 초기 실행까지 담당하고 있잖아 …
rerun 자체에 인자로써 force: boolean? 처럼 주거나, rawRerun(force: boolean) 을
만들어 생성 시점과 실행 시점에서 이를 명시하는게 맞지 않아?"* → `wasAlive`
호출자가 아는 사실("초기 설치냐")을 상태로 추론하던 편법이었음을 인정,
`rawRerun(self, force)` 분리 + `not force and not canExecute` 게이트 제안.
3. 사용자가 그것도 기각: *"not force 가지고 확인하면 안 될 부분같음. 초기
설치에서도 본인을 직접 죽이거나 바운딩을 걸거나 할 수 있잖아. … 유저 함수가
본인을 죽이고 살린다는점 자체가 모순이였다는 문제가 나와. 처음 실행해 unsub
했는데, 아래에서 sub 해버릴 수도 있지. 이건 의도 동작일까? 게다가 unbind/bind
는 본인이 못 해. 같은 계층으로 sub/unsub 가 본인이 할 수 있어야할 이유 제공
자체가 큰 그림에서 무언가 잘못된거 아닐까?"*
4. 갈래 (A) `fn`/cleanup의 자기 구독 변경 금지(leaf와 대칭) / (B) 자기 해제만 허용
(비대칭 감수)을 올렸고 **사용자 확정 (A)**: *"그런것 같아. 나는 지원 안 할 이유가
안 보였었는데, 지금 보면 엄청난 모순이네. 나는 너의 권고처럼 A가 맞아보여."*
**확정된 것**:
- **Effect의 생애는 묶은 쪽이 소유한다** — leaf면 Instance, `:Subscribe()`면 그
호출자. `fn`은 dep을 읽고 부작용을 내고 cleanup을 돌려주는 것까지. leaf가
`fn` 안에서 unbind/bind를 못 하는 것과 **대칭**.
- **`Subscribe`/`Unsubscribe`/`WeakSubscribe`/`WeakUnsubscribe`는 `self._running`
또는 `self._cleanupRunning`이면 error(level 2, "cannot change subscription from
inside fn or cleanup")**. **[반영 뒤 감사 2라운드 정정]** 처음엔 `_running`
하나로 적었는데 cleanup은 `rawRerun` 밖(`Unsubscribe()`·leaf `Destroying`)에서도
돌아 그 안의 `self:Subscribe()`가 가드를 지났다(재`Unsubscribe`만 레지스트리
가드에 걸렸다). 갈래 (a) `_consumeCleanup``_running`을 세움 / (b) UB 문서화 /
(c) 별도 플래그 → **사용자 확정 (c)** `_cleanupRunning`: *"_running 으로 묶어
보는건 여전히 별로 괜찮은 이유가 없음. _cleanupRunning 같은걸 넣지 말아야할
이유가 없는것"* — 한 플래그에 두 뜻을 얹지 않는다.
- **`fn` 안에서 자기 leaf `inst`를 파괴하는 것은 UB**(같은 감사 2라운드 미서술
항목): `SignalBehavior = Immediate``Destroying` 콜백이 `fn` 도중 동기 발화해
cleanup이 영구 미소진. 사용자 확정: *"bind/unbind 에 간접 영향을 주는건데, UB
인게 맞다는 생각."* — `effect-plan.md` `_bindDestroying` 아래 주석.
- **`H-143` 소멸** — `fn` 안 자기 해제 지원 철회. `Rerun` 본체는 원래 모양
(`_consumeCleanup → fn → 저장`), `wasAlive`도 사후 판정도 없다. 종료 신호는
다시 **둘**(강한 `Unsubscribe` / leaf 사망).
- **`rawRerun(self, force)` / 공개 `Rerun()` 분리** — "re"-run과 초기 설치는 다른
일. 공개 `Rerun`은 진입에서 `canExecute` 게이트(죽은 핸들·안 묶인 핸들은
**정의된 no-op**, `fire`와 같은 규칙), 생성자는 `rawRerun(self, true)`로 게이트만
건너뛴다. (A)에서는 `fn`이 전이를 못 일으키므로 `force`가 건너뛰는 것은 정확히
"아직 안 묶임" 하나. 사용자 확인: *"unsub 뒤에 오는 rerun 은 그냥 실행 안
되는게 원래 정상적 형태"* — 맞다.
- **원샷("딱 한 번 처리하고 끝")은 지원 목록에서 빠진다** — 소유자가 밖에서
`Unsubscribe`하거나, 나중에 `Once`류 슈가로 별도 결정. 코어에 넣지 않는다.
**반영 대상**: `base/effect-plan.md`(`Rerun` 의사코드 → `rawRerun`/`Rerun`, `H-143`
관련 서술 전부 — 꼬리 분기·"종료 신호 셋"·"`fn` 안 허용 호출" bullet·`self`를 주는
덕에 bullet, 네 진입점에 `_running` 가드, 실측 bullet), `base/lifecycle-pattern.md`
(포인터), `ROADMAP.md` M2 `Effect` 체크박스, `conventions.md`(원칙 사례로 추가 여부는
반영 시 판단), `-round9-followup.md` `H-143` 절에 소멸 배너, `-round9.md` 요약 표
`H-143` 행.
## `H-148` — 전제 정정: 문구 문제가 아니라 루트 마운트 표면의 부재 → `Claim` + `D.Mapper` (research 신설)
권고 (a)(전용 문구 철회)를 올렸더니 사용자가 더 큰 공백을 짚었다: *"slot 은
물리 장치에 mount 할 방법이 거의 존재하지 않음. … PlayerGui 가 상위에 있고
거기에 GUI 를 여럿 바운딩 해야해서 `Slot { Shop{} … }` 하는게 안 될것 같은
느낌이 듦. 이건 Parent 이상의 문제인것 같아."* → 이미 있는 트리를 quad가
소유하는 `Claim(inst, D.Mapper.<Class> "Name" {…})` 제안(원문·확정·갈래 전량은
`research/existing-mount-plan.md`).
**확정된 것**: 방향 자체 / 디스크립터는 `D.Mapper`에(`D.Frame`에 직접 얹지 않음)
/ `Claim` DFS(내려가며 해석 → 자식부터 `drive`)는 derive 위의 한 겹 / 부기 대상
자식은 전부 매핑, 숏핸드(`UI*`)는 부기 밖 — quad가 직접 쓰거나 실제 객체로
매핑하거나 / 이름 중복·부재는 UB + debug 모드 `seen` 검사 / `nativeFindChild`
프로바이더 op, 순회는 quad-base / 다중 quad 한 트리 UB / M5 이후.
**따름**: 전용 문구 **철회**(일반 매치 실패 그대로), `H-146` (a)의 "루트는 밖에서
`.Parent =`" **폐기**(루트가 quad 소유). `H-142` 키 금지는 그대로.
**미결 6개**는 그 research 문서 §5 — 다음 배치 문항.
## `H-149` — Observer `Subscribe`/`Unsubscribe`도 인라인 (a)
**사용자 확정**: *"a 로 가는게 맞는듯. weak 나 아닌거나 줄 차이가 그리 안 커서,
분리할 큰 이유가 없음."* — `Observer:Subscribe``self:WeakSubscribe()`
위임하지 않고 게이트(`canBound`, `error(…, 2)`)·플래그·약한 등록·강한 킵을 자기
안에 펼친다. `Unsubscribe`도 같은 이유로 `self:WeakUnsubscribe()` 위임을 풀어
양쪽 레지스트리 삭제를 직접 한다(콜론 위임 자체를 남기지 않는다 — `H-144` (b)의
교훈). `lifecycle-pattern.md`의 *"게이트는 한 번만 돈다 — `Subscribe`
`WeakSubscribe`에 위임하므로"* 문장은 "각 진입점이 자기 게이트를 한 번 돈다"로.
`EffectHandle`의 넷과 모양이 같아진다(거기에 `H-147` (A)의 `_running` 가드가
하나 더 붙는 것만 다름 — Observer엔 `_running`이 없다).
**반영 대상**: `base/lifecycle-pattern.md` Observer 네 진입점 의사코드 + "게이트는
한 번만 돈다" bullet, `base/effect-plan.md``EffectHandle` 블록 머리 주석(Observer
위임 서술 참조 부분).
## `H-150` — `Effect._blocker` 제거 (a)
사용자가 확인한 전제: *"canExecute 와 별개로 처음 observer 생성에는 callback 이
실행되어야하는게 맞잖아. … 그러니까, Effect 의 canExecute 를 보겠다는거지? 그럼
그건 맞는것 같아."* — 두 층이 다르다: 내부 Observer/`Ref` 콜백의 **설치 발화는
그대로 일어나고**(그 계약 불변), 그 콜백이 부르는 `fire`의 첫 줄
`canExecute(self)``self`가 **Effect 핸들**이라 생성자 안(아직 안 묶임)에선
거기서 흡수된다. `_blocker`는 같은 흡수를 한 번 더 하려던 장치라 어떤 경로에서도
판정에 닿지 않는다(실측 `t18`). `H-147` (A)로 `fn`이 생성자 안에서 자기를 묶을
수도 없어졌으니 "생성자 구간 = 안 묶임 = `canExecute` 거짓"은 불변식.
**확정**: `_blocker` 필드·`On()`/`OffWithoutEmit()` 제거. 생성자 주석은 *"등록
즉시 1회는 Effect의 `canExecute`가 막는다"*로. 7라운드 `H-58`의 지시(*"모든
옵저버와 callback 등록에 있어서 이를 수행해야할 것임"*)는 그 전제("등록 즉시
1회가 `Rerun`에 닿는다")가 성립하지 않았던 것으로 정정 배너.
**반영 대상**: `base/effect-plan.md` 생성자 의사코드·"확정 구조" 절·`H-58` 문단·
필드 목록, `base/gate-plan.md` 7번(`_installing` 잔재), `ROADMAP.md` M2 `Effect`
체크박스의 "선행: `Blocker` 기본 메커니즘" 문구(Effect 자체는 이제 Blocker 불필요
`Blocker.luau` 선행 요구는 `GateNode`/Slot 쪽만).
## `H-151` — 게이트는 유보만 한다; Effect는 emit 받을 때만 `_epochs`를 갱신 (`Refresh` 캐치업 폐기)
**사용자 확정**: *"해당 우회는 더 크게 보면, 처음부터 Effect 가 Rerun 되는
경로라서 그건 맞아. 그리고 Observer 에서 받은 emit 의 epoch|{epoch:boolean} 를
받는 시점은 emit 되어야하는 시점이 맞기도 하지. 우린 애초에 Refersh 를 할 필요가
없는거야. 재진입은 초기 설정해주는 요소이고, 그건 처음 생성할때랑 같은거야. 사실
처음 생성할 때에도 Block 되어있던게 나중에 다시 들어오는 경로가 있어. 그 경우도
그냥 재실행 해주지. 또 observer 도 마찬가지야. block 은 단지 유보만 해줄뿐이라서.
- Effect 도 observer 랑 똑같게, 중간 state 랑 똑같게, emit 받을때에만 epoch 맵을
업데이트 하면 돼. 계약 추가로 끝나는 일로 보여"*
**확정된 것**:
- **계약(문항의 (a))**: 게이트는 emit 경로만 미룬다. 재바인드/재구독 캐치업과
게이트 없는 형제 dep의 emit은 게이트를 거치지 않고, 그때 `:Get()`은 최신값.
유보됐던 emit이 나중에 풀려 들어오면 그냥 재실행 — 생성 직후 유보분이 들어오는
것과 같은 경로.
- **`_epochs`는 `fire``Update(from)`에서만 갱신** — Observer·중간 State와
같다. `_bindDestroying`·`resubscribeTail`의 `_epochs:Refresh()``depsChanged`
**폐기**. 캐치업은 `if not self._installed then self:Rerun() end` 하나(초기 설치와
같은 뜻 — 소진돼 있으면 다시 설치). `H-144`의 "`Refresh` 먼저" 하위 결정과
그 단축평가 캐비엇(`ROADMAP.md`·`lifecycle-pattern.md`·`ref-plan.md`에 오늘 넣은
두 줄 형태)은 전부 이 결정으로 **소멸**.
- 따름: 죽어 있는 동안 떨어뜨린 emit은 다음 emit 때 리비전 차이로 잡힌다 —
Observer와 같은 정도의 "캐치업 없음"이고 계약으로 적는다. 재구독 뒤 게이트
flush가 같은 값으로 한 번 더 도는 것(`H-144` 재트레이싱의 케이스)은 **허용**
(유보가 풀리는 정상 재실행).
- `EpochMap:Refresh`는 State의 `rawInvalid == false` 경로(`state-epoch-plan.md`
§4)에만 남는다 — Effect 소비자 삭제.
**반영 대상**: `base/effect-plan.md`(`_bindDestroying`·`resubscribeTail`·`H-65`
캐치업 서술·`H-144` 블록 주석), `base/gate-plan.md`(계약 문장),
`base/debounce-throttle-plan.md` 11절, `base/lifecycle-pattern.md` 357행 주석,
`base/ref-plan.md` 244·284·443행(`Refresh` 전제 서술 — 284행의 "다음 `Refresh()`
때에야" 추론은 재검토), `ROADMAP.md` M2·M6 캐치업 문구, `-round9-followup.md`
`H-144` 절에 소멸 배너.
## `H-158` — `state:Block(blocker)` 슈가 (이 대화에서 나옴, 미결)
사용자: *"`:Block` 은 이제 없는거 아냐? Apply(Blocker) 이긴 할꺼야 (표면은
:Gate 만 남아 Blocker 는 Apply 슈거와 Policy 를 주는 프리미티브)"* — 확인 결과
`base/blocker-plan.md``state:Block(blocker)``state:Gate(function(emit)
return b:Policy(emit) end)` 위의 슈가로 **아직 있다**(60·94·163·178·207행).
갈래: (a) `:Block` 폐기, Blocker가 `state:Apply(factory)` 프로토콜을 만족해
`state:Apply(blocker)`로 / (b) `:Block` 유지. **권고 (a)** — 동사 하나가 줄고
"Blocker = Policy를 주는 프리미티브 + Apply 슈가"로 뜻이 하나. 반영 시
`blocker-plan.md`·`gate-plan.md`·`debounce-throttle-plan.md`의 `:Block` 예시 전부.
## `H-153` — Store 예약 이름 런타임 가드 (a)
**사용자 확정**: *"나도 a 동의."* — 생성자의 `isSource` 순회에 `if RESERVED[k]
then error(…, 2) end`, `store:Of(name)`에 같은 검사. `H-122` 화이트리스트와 같은
자리·같은 논거(조용히 받고 엉뚱한 자리에서 죽는 것을 fail-fast로). 부수로
스케치의 "그림자 테이블"은 **store 자신**(I)으로 못박는다 — `store.key`가 평범한
레코드 필드라는 계약과 맞고, 메소드는 `__index`에 있어 `Names()`가 안 센다.
`RESERVED`는 메소드 이름 집합(`Of`/`Names`/… — 구현 시 `__index` 테이블의 키에서
자동 도출하면 두 곳에 안 적어도 된다, 권고).
**반영 대상**: `base/store-plan.md` 구현 스케치·`Of` 절·`__reservedCheck`
주석(동적 키는 런타임 가드가 맡는다고 명시), `ROADMAP.md` M2 Store 체크박스.
## `H-154` — `InstanceChildHandler` retractor에 같은 값 dedup (a)
**사용자 확정**: *"a 동의. … 간단한 dedup 이고 말단 핸들러가 v 를 정확히 알아서
retract 가 정확히 해소되는 부분이 맞네."* — retractor 첫 줄에 `if nextValue == v
then return end`(`SlotHandler` 동형). 같은 값 재발행에 `Parent = nil → inst`
`recompute` 2회가 사라진다. **반영 대상**: `base/dispatch-core-plan.md` `H-134`
문단, `ROADMAP.md` M5 `InstanceChild.luau` 체크박스.
## 반영 기록 (2026-08-28)
`base/`: `effect-plan.md`(`rawRerun`/`Rerun` 분리, `_blocker` 제거, `Refresh` 캐치업
폐기, 네 진입점 `_running` 가드, `H-143` 관련 서술 전부) / `lifecycle-pattern.md`
(Observer `Subscribe`/`Unsubscribe` 인라인, 캐치업 주석) / `gate-plan.md`(7번 정정,
조립 첫 줄 브랜드, "계약 — 게이트는 emit 경로만 미룬다" 절 신설) /
`debounce-throttle-plan.md`(7절 `H-32` 문단, 11절 `Effect`) / `ref-plan.md`(`Refresh`
전제 셋) / `store-plan.md`(그림자 = store 자신, 예약 이름 가드 둘, 빈 Store 실측) /
`dispatch-core-plan.md`(`InstanceChildHandler` dedup) / `bind-system-plan.md`(전용 문구
철회, 루트 예외 폐기 배너) / `slot-plan.md`(각주 둘). `archive/existing-instance-bind-rejected.md`
부활 배너. `ROADMAP.md` 배너·M2 `Effect`/`GateNode`/Store·M5·M6×3·M8·M11·백로그.
`-round9-followup.md`/`-round9.md`의 `H-143`/`H-144`/`H-146` 소멸·정정 배너.
**남은 미결**: `H-158`(`state:Block` 슈가 폐기 → `state:Apply(blocker)`, 권고만) —
`question.md`에; `research/existing-mount-plan.md` §5 갈래 6개 — 다음 배치.
## 감사 루프 (2026-08-28, 10라운드 반영분)
`quad-doc-auditor` 한 턴에 하나, diff 범위, 각도 교체. 새 발견 3→5→2→3→1→**0**
(6라운드에서 수렴). 라운드별: 1 문구 잔존(ROADMAP 필드 목록 `_blocker` / 콜론 위임
현재형 둘 / `rawRerun` 선언 순서) · 2 의미론(README `effect-plan.md` 행 /
`StateBrand:add`→`register` / 예약 이름 도출 권고와 팬텀 필드 모순 /
**`_running` 가드가 `Unsubscribe`·`Destroying`의 cleanup을 안 덮음 → 사용자 결정
`_cleanupRunning`** / `fn` 안 자기 leaf 파괴 미서술 → UB) · 3 수정분 재검토(`Store:Of`
스니펫의 `shadow` 업밸류 / UB 괄호) · 4 전체 diff(`gate-plan.md` 새 절의 "4절"
인용에 문서명 / 인용문 한 구절 / README archive 행 부활 포인터) · 5 수렴
확인(`CLAUDE.md` 볼드 짝) · 6 형식·라벨 정합 **0건**.
## `/code-review high` (2026-08-28, 10라운드 반영분 + 감사 6라운드 뒤)
10건. **일곱 반영**, **셋은 새 메커니즘·기존 결정 변경이라 문항**(`-round10.md`
`H-159`~`H-161`, `question.md`).
반영한 일곱:
1. `bindLifetime` 경로에 (A)의 강제가 없었다 — `fn``New "Frame" { self }`
자기를 leaf에 묶을 수 있었다. `_bindDestroying` 첫 줄에 `isRunning` 가드.
2. `guardNotRunning` 헬퍼 안의 `error(…, 2)`가 헬퍼 호출 줄(quad 내부)을 가리킴
(`H-104`, `H-149`와 같은 이유) — 술어 `isRunning`만 헬퍼로, `error`는 다섯 본문에
인라인(`level 3` 선례를 만들지 않음). `lifecycle-pattern.md`*헬퍼로 빼도 된다* 문장에
단서.
3. `RESERVED`가 어디에도 정의되지 않았고 예약 이름 셋이 네 곳에 리터럴 — `Of` 절에
`local RESERVED = {…}` 단일 소스, 나머지는 가리키기만.
4. `H-154` 서술 정확화 — dedup이 없애는 건 물리 detach/attach와 `recompute` **1회**
(2→1); process 쪽 skip은 옛 값을 몰라 안 둔다(`SlotHandler`의 `claimOwnerAt`
다른 점 명시).
5. 산문 ↔ 의사코드 모순 — *기존 플래그 재사용, 새 상태 없음* / *재`Unsubscribe`는
레지스트리 가드* / *`fn` 안 직접 호출은 게이트하지 않는다* / `lifecycle-pattern.md`
*`_running` 가드*만 — 전부 `_cleanupRunning`·진입 게이트 반영.
6. `-round9-followup.md` 진행 표 행·`H-146` 절에 2026-08-28 소멸 표시, `todos.md`
*`Refresh` 먼저*·*뒤집힌 것 둘*→셋, *갈래 6개* 다섯 곳 → 개수는 §5가 소스.
7. stale 문장 — `effect-plan.md`*게이트가 아니라 `Blocker`를 쓰는 이유* / 포탈
근거를 *`H-64` 캐치업*으로 적은 것(재마운트는 `_installed` 참이라 캐치업이 아예
없다) / `source-state-plan.md`*구현이 한 벌*(위임 아님으로 정정).