- H-143 Rerun 꼬리: 실행 중 사망(wasAlive and not canExecute)이면 cleanup 즉시 소진 (처음 쓴 not canExecute 판정은 생성자 최초 설치를 죽여 감사 2라운드가 정정) - H-144 재구독 꼬리(Refresh 먼저) + 진입점은 EffectHandle 자기 것 (b) (Observer 함수 배정은 콜론 위임으로 꼬리 2회 — 감사 4라운드, luau 재현) → conventions.md 설계 원칙 신설: 하나의 무언가가 두 일을 하지 않는가 - H-145 bk.indexOfElement weak-key / H-146 루트 .Parent는 사용자 몫, Mount 없음 - 감사 1→1→1→1→1→1→1→0, /code-review high 10건 중 7 반영 - H-147~H-149는 qa-request/pre-implementation-handtrace-round10.md §4로 (배치 회신) - round10 지시서(-brief.md) 신설, 광범위 탐사 예정 Co-authored-by: qwreey <me@qwreey.moe> Claude-Session: https://claude.ai/code/session_01546hjsYNLSMZdHdPyTZaGb
99 lines
7.7 KiB
Markdown
99 lines
7.7 KiB
Markdown
# 구현 전 손 트레이싱 **10라운드** — 발견 보고 + 배치 결정 문항지
|
|
|
|
> **이 파일이 무엇인가**: **[2026-08-28 신설]** 9라운드 후속(`H-143`~`H-146`)
|
|
> 반영 뒤 `/code-review high`가 낸 판단 대기 셋(`H-147`~`H-149`)을 씨앗으로,
|
|
> 신선한 탐사자가 **M2~M8 전 표면을 광범위하게** 손 트레이싱·실측한 결과를
|
|
> 여기 이어 쓴다(`H-150`~). 지시서는 `-round10-brief.md`. **사용자는 §4 표를
|
|
> 위에서 아래로 읽고 갈래만 회신한다**(배치). 결정이 나면 `-round10-followup.md`를
|
|
> 새로 만들고 `base/`에 반영한다 — **이 파일은 발견 당시의 기록**이라 각 항목의
|
|
> "갈래"는 선택 전 목록이니 반영 뒤엔 그대로 믿지 말 것.
|
|
>
|
|
> 상태: **[2026-08-28 기준] 탐사 진행 중 / 회신 대기.** `H-147`~`H-149`는
|
|
> `H-143`~`H-146` 반영분에 `/code-review high`가 낸 10건 중 새 메커니즘·기존
|
|
> 결정 변경이라 문항으로 올린 셋(나머지 일곱은 반영 — `-round9-followup.md`의
|
|
> 마지막 code-review 절).
|
|
|
|
## 요약 표
|
|
|
|
| 번호 | 심각도 | 한 줄 | 주 대상 | 성격 | 실측 |
|
|
|---|---|---|---|---|---|
|
|
| `H-147` | 🟡 | `Rerun` 꼬리 `wasAlive`가 루프 머리 `_consumeCleanup()` **뒤**에 잡히고 공개 `Rerun()`에 게이트가 없어 **진입 시점에 이미 죽은 핸들**(직전 cleanup이 `self:Unsubscribe()` / 해제 뒤 타이머의 `self:Rerun()`)은 else 분기 → 고아 cleanup + `_installed = true` | `effect-plan.md` `Rerun` | `/code-review` 발견, 처방은 규칙 또는 새 플래그 | — |
|
|
| `H-148` | 🟡 | `H-146`의 "`Parent` 거부는 **전용 문구**"가 새 메커니즘 — `isHandlable` 거부는 `Dispatch.process`의 **일반** 매치 실패 문구로 떨어지고 그 자리에 특수 분기는 두지 않기로 확정돼 있다. 사용자 회신에 문구 언급 없음(에이전트 선택이었음) | `bind-system-plan.md` `H-142` 항목, `ROADMAP.md` M5 | `/code-review` 발견, 처방은 새 핸들러 또는 철회 | — |
|
|
| `H-149` | 🟡 | `Observer:Subscribe`가 `self:WeakSubscribe()`로 위임하므로 거기 붙인 `error(…, 2)`가 `o:Subscribe()` 경로에선 사용자 호출부가 아니라 `Observer:Subscribe` 본문을 가리킴(`H-104` level 계약 위반). `EffectHandle`은 (b)로 인라인해 문제 없음 | `lifecycle-pattern.md` Observer 네 진입점 | `/code-review` 발견, 처방은 기존 결정("위임하므로 게이트 한 번") 변경 | — |
|
|
|
|
## 상세
|
|
|
|
### `H-147` 🟡 — 이미 죽은 핸들에서 `Rerun()`이 불리면 (`H-143` 잔여)
|
|
|
|
`effect-plan.md` `Rerun`: 루프 머리 `_consumeCleanup()` → `local wasAlive =
|
|
canExecute(self)` → `fn` → `if wasAlive and not canExecute(self)`. "실행 중에
|
|
죽은" 건 잡지만 **"진입 시점에 이미 죽은"** 건 못 잡는다:
|
|
1. 직전 cleanup이 `self:Unsubscribe()`를 부름 → 루프 머리 소진 안에서 죽음 →
|
|
`wasAlive` 거짓 → `fn` 실행 → else → 저장 + `_installed = true`. 고아 cleanup,
|
|
이후 재`Subscribe`의 `resubscribeTail`이 `_installed` 참을 보고 재설치 생략.
|
|
2. `fn`이 `task.delay(1, function() self:Rerun() end)`를 걸고 사용자가 그 전에
|
|
`Unsubscribe()`.
|
|
생성자 최초 `Rerun`("한 번도 산 적 없음")과 구분할 상태가 없다.
|
|
**갈래**: (a) "죽은 핸들에서의 `Rerun()`은 사용자 책임(UB)"으로 문서화 — 새
|
|
상태 없음; (1)은 "cleanup 안에서 자기 해제"를 지원 목록에서 빼는 것, (2)는
|
|
`Unsubscribe`가 도는 cleanup이 그 예약을 취소해야 하는 것(그게 cleanup의 일).
|
|
기존 "error 시 UB / 수렴 책임은 `fn`" 계약과 같은 결 / (b) `_everAlive` 플래그
|
|
하나(첫 `canExecute` 참 시점에 세움) → `Rerun` 진입에서 `everAlive and not
|
|
canExecute → return`. 필드 하나, 생성자 케이스도 가림 / (c) `wasAlive`를
|
|
`_consumeCleanup()` **앞**에서 잡기 — (1)만 닫히고 (2)는 그대로.
|
|
**권고 (a)** — 두 시나리오 다 "죽은 뒤에도 자기를 부르는 코드"고, `Unsubscribe`가
|
|
cleanup을 도는 이유가 바로 그런 예약을 거두라는 것.
|
|
|
|
### `H-148` 🟡 — `H-146`의 "전용 에러 문구"는 새 메커니즘이었다
|
|
|
|
`PropertyHandler.isHandlable`이 `"Parent"`를 거부하면 나오는 건 `Dispatch.process`의
|
|
**일반** 매치 실패 문구(*"no handler … check provider"*)이고, 그 자리에 특수
|
|
분기는 두지 않기로 확정돼 있다(`dispatch-core-plan.md`의 매치 실패 절). 전용
|
|
문구를 내려면 새 자리가 필요하다. 사용자 `H-146` 회신엔 문구 언급이 없었고
|
|
에이전트가 "배선 세부"라며 붙였다 — `conventions.md` 2026-08-27 규칙 위반.
|
|
**갈래**: (a) 전용 문구 **철회** — 일반 매치 실패 error 그대로, 오해는 사용자
|
|
문서("`Parent`는 props에 못 쓴다, 루트는 밖에서")로 흡수. 새 것 0 / (b) **가드
|
|
핸들러** — `k == "Parent"`에 매치해 `process`에서 전용 문구로 error. Observer/
|
|
Effect의 "동적 경로 가드"(`effect-plan.md`)와 같은 모양이라 새 *종류*는 아니나
|
|
핸들러 하나 + 숏핸드보다 위 우선순위 필요 / (c) `Dispatch.process` 매치 실패
|
|
문구에 키 이름을 싣기(일반 개선이지만 별도 결정).
|
|
**권고 (a)** — 원래 회신의 취지(새 API 없음, 최종 사용자 몫)와 같은 결. M5에서
|
|
실제로 밟아 헷갈리면 (b)를 그때 얹는 게 싸다.
|
|
|
|
### `H-149` 🟡 — `Observer:Subscribe`의 위임 때문에 `level 2`가 quad 내부 줄을 가리킨다
|
|
|
|
2026-08-27에 `Observer:WeakSubscribe`의 `error`에 `, 2`를 붙였는데(`H-104`
|
|
계약), `Subscribe`가 `self:WeakSubscribe()`로 위임하므로 `o:Subscribe()` 경로의
|
|
level 2는 **`Observer:Subscribe` 본문**을 가리킨다. 가장 흔한 오용(leaf 바인딩된
|
|
Observer에 `o:Subscribe()`)이 quad 내부 줄을 블레임한다. `EffectHandle` 쪽은 (b)로
|
|
게이트를 인라인해 문제 없음.
|
|
**갈래**: (a) Observer도 `Subscribe`에 게이트·등록 세 줄을 **인라인** — Effect와
|
|
같은 모양, level 2 정확. "게이트는 한 번만 돈다 — `Subscribe`가 `WeakSubscribe`에
|
|
위임"이라는 기존 문장이 "각자 한 번"으로 바뀜. 중복 세 줄은 같은 타입 안이라
|
|
dot 호출 로컬 헬퍼로 빼도 됨(가상 디스패치 없음) / (b) 내부 헬퍼
|
|
`weakSubscribe(self, level)`로 위임하고 `Subscribe`는 `level 3` — 코퍼스에
|
|
`level 3` 선례 없음 / (c) 그대로 두고 `Subscribe` 경로만 블레임이 한 단계 안쪽임을
|
|
문서화.
|
|
**권고 (a)** — "본문을 섞지 않는다"(2026-08-27 원칙)와 결이 같다.
|
|
|
|
## §4 ⭐ 사용자 결정이 필요한 것 (배치 회신용)
|
|
|
|
| 문항 | 무엇 | 선택지 | 권고 |
|
|
|---|---|---|---|
|
|
| **`H-147`** | 죽은 핸들에서 `Rerun()` | (a) UB로 문서화(cleanup 안 자기 해제는 지원 목록 밖, 타이머 취소는 cleanup의 일) / (b) `_everAlive` 플래그 + 진입 게이트 / (c) `wasAlive`를 소진 앞에서 | **(a)** — 새 상태 없이 기존 UB 계약과 같은 결 |
|
|
| **`H-148`** | `Parent` 거부 문구 | (a) 전용 문구 철회, 일반 매치 실패 그대로 / (b) `Parent` 가드 핸들러(동적 경로 가드형) / (c) 일반 문구에 키 이름 | **(a)** — 회신 취지(새 API 없음) 그대로, 필요하면 M5에서 (b) |
|
|
| **`H-149`** | Observer `Subscribe` 위임과 `level 2` | (a) `Subscribe`에 게이트·등록 인라인(Effect와 동형) / (b) `level 3` 위임 / (c) 문서화만 | **(a)** — 본문 안 섞기 원칙과 동형 |
|
|
|
|
(탐사자의 문항은 아래에 이어 붙는다.)
|
|
|
|
## §5 이상 없다고 확인한 것
|
|
|
|
(탐사자가 채운다.)
|
|
|
|
## §6 남은 의심 / 못 본 것
|
|
|
|
(탐사자가 채운다.)
|
|
|
|
## §7 레인 C 실행 기록
|
|
|
|
(탐사자가 채운다 — `audit/handtrace-round10-reference-impl/`.)
|