quad/.claude/qa-request/pre-implementation-handtrace-round10.md
qwreey 0ec22fbe73
qa: 9라운드 후속 H-143~H-146 확정·반영 + 감사 8라운드·code-review — 잔여 셋은 10라운드 문항지로
- 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
2026-08-28 00:28:52 +09:00

7.7 KiB

구현 전 손 트레이싱 10라운드 — 발견 보고 + 배치 결정 문항지

이 파일이 무엇인가: [2026-08-28 신설] 9라운드 후속(H-143H-146) 반영 뒤 /code-review high가 낸 판단 대기 셋(H-147H-149)을 씨앗으로, 신선한 탐사자가 M2~M8 전 표면을 광범위하게 손 트레이싱·실측한 결과를 여기 이어 쓴다(H-150~). 지시서는 -round10-brief.md. 사용자는 §4 표를 위에서 아래로 읽고 갈래만 회신한다(배치). 결정이 나면 -round10-followup.md를 새로 만들고 base/에 반영한다 — 이 파일은 발견 당시의 기록이라 각 항목의 "갈래"는 선택 전 목록이니 반영 뒤엔 그대로 믿지 말 것.

상태: [2026-08-28 기준] 탐사 진행 중 / 회신 대기. H-147H-149H-143H-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:Subscribeself: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)fnif wasAlive and not canExecute(self). "실행 중에 죽은" 건 잡지만 "진입 시점에 이미 죽은" 건 못 잡는다:

  1. 직전 cleanup이 self:Unsubscribe()를 부름 → 루프 머리 소진 안에서 죽음 → wasAlive 거짓 → fn 실행 → else → 저장 + _installed = true. 고아 cleanup, 이후 재SubscriberesubscribeTail_installed 참을 보고 재설치 생략.
  2. fntask.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:WeakSubscribeerror, 2를 붙였는데(H-104 계약), Subscribeself:WeakSubscribe()로 위임하므로 o:Subscribe() 경로의 level 2는 Observer:Subscribe 본문을 가리킨다. 가장 흔한 오용(leaf 바인딩된 Observer에 o:Subscribe())이 quad 내부 줄을 블레임한다. EffectHandle 쪽은 (b)로 게이트를 인라인해 문제 없음. 갈래: (a) Observer도 Subscribe에 게이트·등록 세 줄을 인라인 — Effect와 같은 모양, level 2 정확. "게이트는 한 번만 돈다 — SubscribeWeakSubscribe에 위임"이라는 기존 문장이 "각자 한 번"으로 바뀜. 중복 세 줄은 같은 타입 안이라 dot 호출 로컬 헬퍼로 빼도 됨(가상 디스패치 없음) / (b) 내부 헬퍼 weakSubscribe(self, level)로 위임하고 Subscribelevel 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/.)