- H-229: 사용자 별해(retractor가 inst를 인자로 받아 업밸류 캡처 제거) 검토 —
retractor의 Observer 캡처 → observer.fn의 inst 캡처 경로가 남아 불충분.
끝까지 끊으려면 클로저 상태를 Relate/weak 셀로 밀어내야 하는데 이는 확정
설계(kSlotMap 삭제 — upvalue 캡처)의 역전이고 불변식이 전 핸들러 작성자에
분산됨. gchold 앵커((a))는 불변식을 Dispatch 한 줄에 중앙화 — 사용자의
안전 직감("후자가 안전해보여")과 같은 결론
- H-231(② 신설, 사용자 설계): error 유틸 — setLevel(fn, tag) 맵 +
debug.info 스택 워커. H-219 drive 도착지 한계·H-212 스텁 level 미정·
프레임 손 세기 전부 자연 해소. 사용자 유의점("클로저 수령분 등록 필요,
안 그럼 중간에 짤림 / 아니면 최상단부터") 반영 — 권고: (i) 최상단 하강
(경계 직전 blame, 등록 1회성) + 독립 패키지(가칭 quad-error) + 단위 2 전
일괄 이관 + 선행 스파이크(인라이닝/코루틴/프레임 보정)
- H-230: level Enum화 반론(리터럴 전제)은 워커 설계로 소멸 — H-231에 흡수,
H-230엔 우선순위 상수 배치만 남김
Co-authored-by: qwreey <me@qwreey.moe>
Claude-Session: https://claude.ai/code/session_01LF78pXeFGD1ZSVD3ifteYG
38 KiB
M3 구현 12라운드 — 발견 원문 + 배치 문항지
이 파일이 무엇인가: [2026-08-31 신설] M3 자율 구현 구간 (
m3-implementation-round12-brief.md가 규약, 같은 날 §0·§6 확정)에서 나온 발견 전부. 실제 코드를 옮기고 돌리다 나온 것이다. 번호는H-212부터 (11라운드가H-211까지 썼다).갈래 표기(규약 §2): ① 자율로 고침(같은 커밋에서
base/+코드) / ② §4 표에 쌓아 배치 회신 대기 / ③ 즉시 중단·보고. M2 하자는 규약 §0 Q3 (a) — 경미하면 여기 ①, 설계 결정 규모면 그때m2-implementation-round13을 새로 연다.상태의 소스는 이 파일 자신 — 요약 표의 상태 열이 최신.
요약 표
| 번호 | 갈래 | 단위 | 심각도 | 한 줄 | 상태 |
|---|---|---|---|---|---|
H-212 |
① | 1 | 🟢 | dispatch-core-plan.md "Dispatch 체인" 의사코드의 error 세 자리가 한국어·level 없음 — 그 절(2026-08-13)보다 늦게 확정된 architecture.md error 계약(영어, level 이분, 2026-08-25)이 미반영 |
✅ 반영(dispatch-core-plan.md 의사코드 영어+level, H-212 문단). [감사 3라운드 확장] 같은 문서의 Length/Offset 절 두 자리(getOffsetAt 한국어 혼용 / recompute 한국어·level 없음)와 주입 op 스텁·가로채기 예시 메시지도 같은 부류라 같이 정정 — 스텁의 level 숫자만은 구현 시점(M5/M10) 몫으로 남김(도착지까지의 프레임 수를 문서 시점에 못 셈) |
H-213 |
① | 1 | 🟢 | HANDLER_PRIORITY_* 상수의 실제 숫자값을 어느 문서도 안 정했다 — 문서가 정한 건 이름·순서(HIGH > NORMAL > LOW > FALLBACK)·열린 공간(± 오프셋)뿐 |
✅ 구현이 채움: 1000 / 0 / -1000 / -1000000 (밴드 간 ± 오프셋 여유, Dispatch/init.luau 주석) |
H-214 |
② | 1 | 🟡 | listHandlers가 "이름/priority를 반환"이고 동률 경고·quad-debug 체인 덤프도 핸들러 이름을 원하는데, Handler 계약(3종)엔 name 필드가 없다 — 새 필드라 자율 반영 불가 |
✅ (a) 사용자 확정(2026-08-31 §4 회신) — 계약에 선택 필드 name: string?(진단 전용). quad-types·Dispatch/init.luau(동률 경고에 이름, TODO 마커 제거)·dispatch-core-plan.md "핸들러 계약" 절·spec.dispatch 11번 반영 |
H-215 |
② | 1 | 🟢 | 스파이크 04(Dispatch 체인 retractFrom) 처분 — spec.dispatch.luau가 검증 대상 대부분을 실측했는데 재귀 재발행 경로는 로컬 wrapping 핸들러 근사라 실제 StoreBind(M4) 몫이 남는다. 폐기는 01처럼 사용자 승인 사안(감사 2라운드 발견) |
✅ (a) 사용자 확정(2026-08-31 §4 회신) — 폐기·done/ 이동, 잔여 몫(실제 StoreBind 경유 재발행)은 ROADMAP M4 mock 테스트 항목에 명시. STATUS.md·README.md·ROADMAP 재검증 대기 절 [x] |
H-216 |
① | 1 | 🟢 | slot-plan.md의 옛 error 예시 11곳(감사 5라운드가 계수 정정 — 처음 12로 잘못 셌다)이 한국어·level 없음(대표: Slot:Add 범위 검증, 요소 타입 3종, dispose 둘) — H-212와 같은 부류로, 그 문서 자신이 별도 절에서 "level 2, 영어" 규칙을 알고 있으면서 예시가 안 따라온 자리. M3 단위 2~4·M6이 옮겨 적기 전에 정리(감사 4라운드 발견) |
✅ 반영(slot-plan.md — 사용자 입력 검증은 level 2, releaseOwner 소유권 추적 파손만 내부 불변식 level 1) |
H-217 |
① | 1 | 🟢 | 같은 부류 마지막 4곳 — attribute-plan.md(그룹 이중 배치)/debounce-throttle-plan.md(Leading·Trailing 둘 다 false)/ref-plan.md(PreRef 재사용 — 파이프라인 의사코드의 "PreRef instance reused"와 다른 문구로 갈라져 있던 것도 통일)/source-state-plan.md(bindLifetime 이중 바인드 — mock 실구현이 던지는 두 분기 메시지의 공통 접두를 딴 근사, 실분기는 그 절의 게이트 스케치 몫)가 한국어·level 없음(감사 5라운드 전수 스윕) |
✅ 반영 — 전부 영어+level 2. base/ 코드 리터럴의 한국어 error는 이제 0(잔여는 산문·옛 모델 인용·주석뿐) |
H-218 |
② | 1 | 🟡 | (/code-review high) chains 리스트의 retractor 클로저가 inst를 캡처해 weak 키를 버킷 값이 되참조 — H-71이 실측한 "100% 새는" 패턴이라, dispatch-core-plan.md의 "자식을 버리면 결국 GC되지만" 주장과 그 위에 선 ui-shorthand-plan.md UI-11의 GC 근거가 거짓. 반응형 숏핸드 자식을 파괴/재생성하는 사이클마다 구독·gchold가 누적. 구현은 확정 의사코드에 충실 — 처방(위임 자식 철거 시 retractFrom 의무화 여부)이 계약 변경 |
✅ (a) 사용자 확정(2026-08-31 회신 2, "a 로 가면 될것 같아") — 위임 핸들러는 자식을 버릴 때 무조건 retractFrom(child, prop, 1)(UI-11 부분 역전). dispatch-core-plan.md·ui-shorthand-plan.md(스케치 포함) 반영. 사용자 되물음(Destroy 경로도 같은 문제 아닌가)은 검증 결과 맞았다 — H-229로 분리 |
H-219 |
② | 1 | 🟡 | (/code-review high) 매치 실패 error(…, 2)의 도착지가 drive 경로(가장 흔한 사용자 경로)에선 사용자 코드가 아니라 quad 내부 프레임 — "프레임 수가 아니라 도착지가 계약" 위반. 단 process는 핸들러 재귀도 받는 공개 진입점이라 level 하나로 두 경로를 다 못 맞춤 — 증상 확정, 처방(재상승 등)은 새 메커니즘 |
✅ (a) 사용자 확정(2026-08-31 회신 2) — 현행 유지 + 한계 명시(dispatch-core-plan.md "우선순위 동률/매치 실패 처리" 절), M5 파이프라인 완성 시 재평가 |
H-220 |
① | 1 | 🟡 | (/code-review high) BRAND_PROBES가 상위 술어 isRef를 isPreRef/isPostRef보다 앞에 둬 둘이 도달 불능 — brand-plan.md의 술어 합성(isRef = PreRef∪PostRef∪RefBrand)이 소스라 specific-first가 답 |
✅ 반영 — 순서 재배열 + "손 복사 목록이라 새 브랜드 마일스톤마다 확장" 주석 |
H-221 |
① | 1 | 🟢 | (/code-review high) ROADMAP 우선순위 체크박스 주석이 H-214를 여전히 "§4 대기·임시 반환"으로 서술 — 같은 커밋에서 종결됐는데 배너가 부정하는 문장을 안 고친 자리 |
✅ 반영 — "(a) 확정으로 닫힘"으로 갱신 |
H-222 |
② | 1 | 🟢 | (/code-review high) H-212 문단이 *"제공자 계약 위반 → 2"*라는 계약 표에 없는 제3 분류를 확정 서술처럼 base/에 넣었다 — 표는 "사용자 입력 검증(2)/내부 불변식(1)" 두 행뿐 |
✅ (a) 사용자 확정(2026-08-31 회신 2) — architecture.md 표에 제3 행("제공자 계약 위반 = 2, 가장 가까운 호출 구조") 신설, H-212 문단의 잠정 표시 해제 |
H-223 |
① | 1 | 🟢 | (/code-review high) retractor 생략 error가 위반 핸들러를 특정할 정보(방금 확정된 name·priority·k·index)를 하나도 안 실음 — h.process 프레임은 이미 반환돼 어떤 level로도 도달 불가라 메시지가 유일한 단서 |
✅ 반영 — noRetractorMessage(h, k, index)(코드+의사코드 동기), 동률 경고도 같은 describeHandler 사용 |
H-224 |
① | 1 | 🟢 | (/code-review high) H-215가 M4로 넘긴 잔여 몫 문구가 순서 단언만 요구해, 스파이크 04의 존재 이유였던 효과 수준 검증(retractor가 실제로 구독을 끊는다)을 떨어뜨림 |
✅ 반영 — ROADMAP M4 mock 항목에 효과 단언(옛 구독 0, stale Set 불전파) 보강 |
H-225 |
① | 1 | 🟢 | (/code-review high) 세션 파일이 감사 루프를 5라운드에서 멈춘 것처럼 서술(6라운드 수렴·리뷰 반영 미기재) |
✅ 반영 — 세션 파일·summary에 6라운드 수렴과 리뷰 결과까지 기록 |
H-226 |
기각 | 1 | 🟢 | (/code-review high) process (A)/(B) 꼬리 병합·(B) 이중 할당 제거·retractFrom 재조회 제거 리팩터 제안 |
❌ 반영 안 함 — 확정 의사코드와의 1:1 유지가 우선이고 실측 병목 아님("실제로 관측된 문제에만 구조"). 메시지 drift 우려는 H-223의 공용 noRetractorMessage로 소멸 |
H-227 |
① | 1 | 🟡 | (/code-review high) local Dispatch = {} :: any가 생산자 표면을 quad-types 선언과 대조 불능으로 만듦 — H-25가 막으려던 드리프트가 생산자 쪽에서 무검사 |
✅ 반영 — 로컬 함수 정의 후 local Dispatch: Dispatch = { … } 타입 주석 조립(analyze가 표면 검사) |
H-228 |
① | 1 | 🟢 | (탐사자) describeHandler가 name 부재 시 "?"를 이름 자리에 찍는다 — dispatch-core-plan.md "핸들러 계약" 절의 H-214 블록은 "없으면 priority만 보인다". 실측: handler priority tie between "?" (priority 5) and "?" (priority 5) |
✅ 반영 — 코드를 문서에 맞춤: 이름 부재 시 (priority N)만 |
H-229 |
② | 1 | 🔴 | (사용자 되물음 → 검증 확인) H-218의 누수는 위임 경로만의 문제가 아니다 — 일반 Destroy 경로 전체가 같은 패턴으로 샌다. Destroy 시 retract를 안 부르는 건 계약("오직 같은 key에 새 값" 시나리오만)인데, gchold 섬은 Destroy로 무너져도 chains의 강한 버킷 값이 retractor→inst를 별도 루트로 붙잡아 weak 항목이 영영 안 비워진다 — 반응형 바인딩이 하나라도 있던 모든 파괴 인스턴스가 소량 부기+inst userdata를 영구 잔존시키고, 죽은 Observer가 상류 State의 전파 순회에 계속 남는다(회당 canExecute 스킵 비용) |
⏳ §4 대기 — 분석 상세는 아래 H-229 절 |
H-230 |
② | 1 | 🟢 | (사용자 제안) 공유 상수의 배치 — HANDLER_PRIORITY_*를 quad-base 밖(백엔드)에서도 의미 있게 쓰려면 quad-const 신설 또는 quad-types 승격이 필요하다는 제안. level 넘버 Enum화H-231에 흡수(리터럴 전제의 "프레임 수라 Enum 불가" 반론은 워커가 소멸시킴) |
⏳ §4 대기 — 권고: 우선순위 상수는 quad-types 승격 (a) |
H-231 |
② | 1 | 🟡 | (사용자 설계, 회신 3) error 유틸 모듈 — setLevel(fn, tag) 등록 맵 + debug.info(i, "f") 스택 워커로 "원하는 계층까지 올려서" error를 내는 유틸(type-version-check처럼 독립 패키지). 프레임 수 손 세기(2·3 갈림, H-219의 drive 도착지 한계, H-212 스텁 level 미정)가 전부 자연 해소되고 level이 진짜 의미 Enum이 된다. 에러는 cold path라 debug.info 비용 무시 가능(사용자 논거, 동의) |
⏳ §4 대기 — 세부 문항·권고는 아래 H-231 절 |
H-212 — base 의사코드 error가 error 계약 이전 표기로 남아 있었다 (①)
process의 retractor 생략 error 두 자리와 retractFrom의 배열 구멍 error가
한국어 메시지에 level 인자 없음 — base/architecture.md의 "error 계약 —
level 이분과 메시지 언어" 절("base/의 예시 메시지도 영어로 쓴다")이 그
의사코드보다 늦게 확정되며 반영이 안 된 자리다. 문서가 이미 답(영어 + level
이분)을 갖고 있어 ①: retractor 생략은 핸들러(제공자) 계약 위반이라 호출부를
가리키는 2, 배열 구멍은 내부 부기 파손이라 그 자리를 가리키는 1.
base/와 코드를 같은 커밋에서 맞췄다.
H-213 — 우선순위 밴드 상수의 리터럴 값 (①)
"우선순위 동률/매치 실패 처리" 절은 상수 이름 넷과 순서, "열린 숫자 공간
위의 편의 상수"(HANDLER_PRIORITY_HIGH + 1식 미세 조정)만 정하고 값은 안
정했다. 구현 선택: HIGH = 1000 / NORMAL = 0 / LOW = -1000 /
FALLBACK = -1000000. 근거 — 밴드 사이 간격이 커서 ± 오프셋이 이웃 밴드를
침범하기 어렵고, FALLBACK + 1(base Fallback을 가로채는 관례 자리)이 LOW
보다 한참 아래에 남는다. 계약 의미(순서·밴드)는 값과 무관해 사용자 결정
대상이 아니라고 판단 — 다른 값을 원하면 §4 회신에 얹으면 된다.
H-214 — listHandlers/동률 경고가 원하는 핸들러 "이름"이 계약에 없다 (②)
dispatch-core-plan.md "우선순위 동률/매치 실패 처리" 절: "Dispatch.listHandlers()는
현재 등록된 전체 핸들러(이름/priority)를 반환". [감사 3라운드 정정]
"체인 슬롯을 이름으로 바로 덤프" 서술도 같은 문서의 "부수 효과 —
quad-debug에 유리" 항목에 있다(처음엔 research/debug-tooling-plan.md로 잘못
적었었다 — 그 파일엔 그 구절이 없고, 대신 핸들러가 선택적으로 구현하는
describe 훅(가칭, 미정)이 이름 관련 전례로 있다). 그런데 핸들러
계약은 isHandlable/priority/process 3종으로 못 박혀 있고(같은 문서
"핸들러 계약" 절: "다음 3개를 제공하는"), name을 붙이는 건 새 필드라
규약 §2의 ② 갈래다. 기각 이력 grep: Handler에 이름 필드를 검토·기각한 기록
없음(등록 엔티티 이름 논의(TagFallbackHandler 등)는 변수명 이야기지
계약 필드가 아님). 임시 구현: 등록된 핸들러 객체 배열(우선순위순 사본)을
그대로 반환 — 정의된 정보(priority, 함수들)는 다 담기고 새 개념이 없다.
동률 경고 print는 priority 값만 찍는다. 코드 마커 TODO(H-214) 1곳
(Dispatch/init.luau의 listHandlers).
[2026-08-31 종결 — (a) 사용자 확정] 계약에 선택 필드 name: string?
(진단 전용 — 동률 경고·listHandlers·나중의 quad-debug 덤프, 스캔·매치
무영향). 반영: quad-types Handler 타입 / Dispatch/init.luau(동률 경고에
양쪽 이름, 마커 제거) / dispatch-core-plan.md "핸들러 계약" 절 신설 항목 /
spec.dispatch 11번(이름 왕복·부재 시 nil). 코드 마커 0.
H-228 — 진단 라벨의 이름 부재 표기가 문서와 다르다 (①, 탐사자)
Dispatch/init.luau의 describeHandler(h)는 `"{h.name or "?"}" (priority {h.priority})`
— name이 없으면 "?"라는 자리표시자를 이름처럼 찍는다. 그런데
dispatch-core-plan.md "핸들러 계약" 절의 H-214 확정 블록은 "없으면
priority만 보인다" — 문서가 이미 답을 가진 자리라 ①이다(동률 경고와
H-223의 retractor 생략 메시지 양쪽이 이 라벨을 쓴다). 실측(무이름 동률
경고, 탐사자 프로브):
quad.Dispatch: handler priority tie between "?" (priority 5) and "?" (priority 5) — ties have no defined order; offset from a HANDLER_PRIORITY_* band
수정은 메인 세션 몫(탐사자는 round12.md만 편집) — 코드가 문서를 따라
이름 부분을 생략하든((priority 5)만), 문서에 자리표시자 표기를 명시하든
어느 쪽이든 한 줄이다. 순수 진단 문자열이라 계약·부기 영향 없음.
H-229 — Destroy 경로 검증: 사용자 되물음이 맞았다 (②, 🔴)
사용자 되물음(2026-08-31): "핸들러가 자식을 버릴 때 Destroy가 호출되어 bind들이 자동으로 끊어지고 gchold가 사라질 수 있었다는 건데 … 그냥 Destroy 호출되는 것도 retract가 안 먹어서 문제가 생긴다는 부분 아냐?"
검증 결과: 맞다. 경로를 끝까지 따라가면:
inst:Destroy()— 엔진이 커넥션을 끊는다. gcconn 클로저↔gchold 상호 참조 섬은 이 절단으로 무너지고(lifecycle-pattern.md(0)의 "Destroy가 유일한 절단면"), gchold가 강하게 쥐던 바인딩 값들이 풀린다. 여기까지는 설계대로.- 그런데
chains는 두 번째 강한 루트다 —buckets[inst(weak)] = bucket(강)이고 bucket → 리스트 → retractor 클로저 →inst(캡처). Luau에 ephemeron이 없으므로 "값이 자기 weak 키를 되참조"하는 이 항목은 영영 안 비워진다(H-71실측 패턴 그대로). Destroy가 끊는 것은 엔진 쪽 참조뿐이고 이 루트는 안 건드린다. - Destroy 시 retract를 안 부르는 건 계약이라("오직 같은 key에 새 값이
들어와서 이전 처리를 갈아치우는 시나리오에만") 아무도
list[i] = nil을 해주지 않는다 — 즉 반응형 바인딩이 하나라도 있던(= retractor나 그 Observer가inst를 캡처한) 모든 파괴 인스턴스의 버킷·retractor· Observer·instuserdata가 모듈chains에 영구 잔존한다. - 부수 비용: 잔존 Observer는 상류 State의 weak 구독 집합에 살아 있어(강한
경로가 chains에 있으므로 weak라도 안 걷힘) 상류 emit마다 전파 루프가
그것들을 방문해
canExecutefalse로 스킵한다 — 메모리에 더해 전파 비용이 파괴 누적에 비례해 자란다.
H-218 (a)의 의무화가 닫는 것은 위임 경로뿐이다(retractFrom 호출 후엔
리스트가 비어 버킷 값이 inst를 안 잡으므로 weak 항목이 정상 회수된다).
최상위 인스턴스·Slot 요소처럼 retract 없이 Destroy로만 죽는 일반 경로가
이 항목이고, 처방 선택지는 §4 표. 정적 값만 있는 인스턴스는 retractor가
전부 Void(캡처 없음)라 새지 않는다 — 새는 것은 반응형 바인딩이 있던
것들이다.
[2026-08-31 회신 3 — 사용자 별해 검토] 사용자 제안: "retract 클로저가
inst를 받으면 될 것 같다는 느낌. retractFrom이나 process에서 이미 inst를
알고 … 온디맨드로 넘겨줄 수 있어. 그러면 retract 자체가 업밸류를 캐치하지
않게 돼" + 스스로 단 캐비엇: "어떤 방식으로든 실수가 나게 된다면 Strong
이라서 gc가 안 되는 사고가 날 수 있게 되어 후자(gchold 앵커)를 제공하는 게
안전해보이기는 해". 검토 결과 — 인자 전달만으로는 경로가 안 끊긴다:
retractor가 inst를 직접 캡처하지 않아도, StoreBind류 retractor는 자기
Observer를 캡처해야 하고(구독 해제 대상) 그 Observer의 콜백이 재디스패치를
위해 inst를 캡처한다 — chains → retractor → observer → fn → inst
경로가 그대로 남는다. 이걸 끝까지 끊으려면 핸들러 상태를 클로저 밖(weak
셀/Relate 조회)으로 밀어내야 하는데, 그건 확정 설계("단발성 handoff는
upvalue 캡처로 충분" — 옛 kSlotMap 삭제, Handler 작성 체크리스트 4번)의
역전이고, 불변식이 모든 핸들러 작성자(서드파티 포함)에게 분산되며
위반의 증상이 조용한 영구 누수다. gchold 앵커((a))는 캡처 인체공학을
그대로 두고 불변식을 Dispatch 안의 한 줄에 중앙화한다 — 사용자의 안전
직감과 같은 결론.
H-231 — error 유틸 모듈: 스택 워커로 level 손 세기를 없앤다 (②, 사용자 설계)
사용자 설계(회신 3): "error 자체를 모듈로써 제공 … debug.info(2, "f")를
하면 각 프레임에 대한 함수를 구할 수 있어. 간단하게 함수를 태그해주는 맵만
있다면 에러를 원하는 레벨까지 낮춰서 내준다를 지금처럼 어렵게 세어 가며 처리
안 해도 되고, 여러 래퍼가 들어가도 사용자 지점까지 확실히 올려낼 수 있어 …
type-version-check와 유사하게 외부에 유틸을 파. setLevel(fn, num) … 등록 안
된 함수는 level 0 … 그러면 num을 Enum으로 둘 수 있고, 우리가 한계라고 했던
부분이 해소돼" + 유의점(추가 회신): "클로저로 입력 받은 것도 입력 받은
측에서 level을 설정해야 할 수도 있어 … (안 그럼 중간에 짤릴 수도 있으니까)
아니면 최상단부터 구한 다음, 거기서 진입하는 게 옳을 수도 있어".
평가 — 채택 권고. 이 유틸이 있으면: H-219의 drive 도착지 한계(현행
유지로 닫았던 것)가 근본 해소되고, H-212가 "구현 시점 몫"으로 미룬 주입 op
스텁 level도 답이 생기며, H-205류 "헬퍼 프레임 하나에 2→3" 손 세기가
사라진다. 에러는 cold path라 비용 논거도 맞다. 사용자가 짚은 "중간에 짤림"이
정확히 급소다 — 세부 선택지:
- 워커 방향 — (i) 최상단 하강(권고, 사용자 후자 제안): 스택 최상단
(사용자 루트)에서 내려오다 처음 만나는 등록(quad) 프레임의 직전에서
낸다. 등록이 필요한 건 quad의 정적 표면 +
addHandler/주입 시점에 받는 제공자 클로저뿐(1회성) — 런타임에 생기는 익명 클로저(retractor, observer 콜백)는 경계 아래라 애초에 검사 안 되므로 "중간에 짤림"이 구조적으로 없다. blame은 항상 "quad로 들어온 최상위 진입점의 호출부"(drive면New호출부, 전파면:Set호출부) — 일관되고 안전. / (ii) 에러 지점 상승(첫 미등록 프레임에 blame): 샌드위치된 사용자 콜백까지 정밀 blame되지만, quad가 만들거나 받는 모든 클로저(retractor 포함 — 매process마다 생성되는 핫패스!)를 등록해야 하고 누락 하나가 내부 클로저를 사용자로 오인시킨다. 정밀도 이득 대비 규율 표면이 너무 넓다. - 배치 — type-version-check처럼 독립 워크스페이스 패키지(가칭
quad-error): 백엔드(EngineOps 스텁·핸들러)도 써야 하는데 그쪽은 quad-base를 의존하지 않는다. 이름은 사용자 확정 대상. - Enum 구성 —
H-222의 표와 1:1로: 예)Level.User(사용자 호출부까지) /Level.Provider(제공자 계약 위반 — 같은 워커, 등록 집합만 다르게 볼지) /Level.Here(내부 불변식 = 그 자리, 워커 불필요). 멤버 구성은 확정 대상. - 선행 스파이크(luau-test) — 추론으로 확정하지 않는다: (a)
debug.info가 인라이닝(-O2)·네이티브 코드젠에서 프레임을 어떻게 보이는지(등록 함수가 인라인되면 걷기가 짧아질 뿐인지), (b) 코루틴 경계(스택 루트가 코루틴 진입점일 때의 degrade), (c) 걷은 깊이 →error(msg, n)프레임 계산이 유틸 자신의 프레임을 정확히 보정하는지. - 이관 시점 — 기존 error 자리(M2 커밋분 + 단위 1)의 리터럴 level을 유틸 호출로 바꾸는 걸 단위 2 착수 전 일괄로 할지, 마일스톤 진행하며 점진으로 할지.
architecture.md의 error 계약 절(방금 신설한 제3 행 포함)은 채택 시 이
유틸 기준으로 재서술된다 — "도착지가 계약"이라는 원칙 자체는 그대로이고,
도착지를 세는 방법이 손에서 워커로 바뀌는 것.
§4 배치 문항지 (사용자가 읽을 유일한 자리)
⭐ [2026-08-31 회신 1] 사용자: "배치 문항은 중간확인 완료했어. 전부
권고안에 동의해. 나중에 천천히 반영해줘" — H-214·H-215 둘 다 권고 (a)
채택, 같은 날 반영 완료(각 행 상태 참고).
⭐ [2026-08-31 회신 2] /code-review high가 올린 셋도 확정 —
H-218 (a)("확인함. a 로 가면 될것 같아") / H-219 동의 / H-222 동의,
전부 반영 완료. 같은 회신이 둘을 새로 열었다: H-218에 대한 되물음
("Destroy 호출되는 것도 retract가 안 먹어서 문제가 생긴다는 부분 아냐?
한번만 다시 봐줘" → 검증 결과 맞았다, H-229)과 상수 배치 제안
("quad-const 등을 만드는 게 좋아보임 … 혹은 그러한 Enum 값도 타입으로 보고
quad-types에 할당하는 건 어떤지?" → H-230).
⭐ [2026-08-31 회신 3] H-229에 사용자 별해(retractor 인자 전달) 검토
요청 — 검토 결과 Observer 캡처 경로가 남아 불충분, gchold 앵커 쪽이 맞다는
분석을 H-229 절에 추가. 그리고 error 유틸 모듈 설계(setLevel 맵 +
debug.info 스택 워커, "중간에 짤림" 유의점과 최상단 하강 대안까지 사용자
제안 → H-231). 아래 세 행이 회신 대기.
| 번호 | 무엇 | 선택지 | 권고 | 권고 근거 |
|---|---|---|---|---|
H-214 |
listHandlers·동률 경고·(나중의) quad-debug 덤프가 쓸 핸들러 이름 — Handler 계약(3종)엔 name이 없다 |
(a) 계약에 선택 필드 name: string? 추가 — 있으면 경고·덤프·listHandlers가 쓰고 없으면 priority만 / (b) 이름 없이 감 — listHandlers는 핸들러 객체 배열만 반환(지금 임시 구현), "이름/priority" 서술을 문서에서 걷어냄 / (c) 다른 방식(별도 등록 인자 addHandler(h, name) 등) |
(a) | dispatch-core-plan.md 두 자리("우선순위 동률/매치 실패 처리"의 listHandlers 이름/priority + "부수 효과 — quad-debug에 유리"의 체인 슬롯 이름 덤프)가 이미 "이름"을 전제하고([감사 3라운드 정정] 후자를 처음엔 research/debug-tooling-plan.md로 잘못 인용 — 거긴 대신 선택적 describe 훅(가칭)이 전례), 선택 필드면 기존 3종 계약을 안 깬다. (b)를 고르면 두 자리 다 걷어야 한다. (c)는 이름이 핸들러 자신이 아니라 레지스트리에 살게 돼 체인 슬롯 덤프(슬롯엔 handler 객체만 저장)가 역조회를 또 요구함 |
H-218 |
위임 자식 철거 시 retractFrom(child, prop, 1)을 계약으로 의무화할지 — chains의 retractor 클로저가 inst를 캡처해 자식을 버리는 것만으론 회수 안 됨(H-71 패턴, 거짓 GC 주장 둘은 이미 정정). 반응형 숏핸드(UICorner = state) 자식 파괴/재생성 사이클마다 구독·gchold 누적 |
(a) 항상 의무화 — 위임 핸들러는 자식을 버릴 때 무조건 retractFrom(child, prop, 1)(정적 값이면 no-op retractor라 비용 ~0; UI-11의 "필요하지 않다" 결론 일부 역전 — 옛 결정 역전 표시) / (b) 반응형 값이 걸린 자식만 의무(정적은 UI-11 유지 — 단 폐기 주체가 값의 반응형 여부를 추적해야) / (c) 다른 방식(chains 구조 변경 등 — 단 리스트 SetWeak 전환은 살아있는 체인을 잃는 오답) |
(a) | 계약이 조건 없이 한 줄이라 어기기 어렵고, 정적 경로 비용이 사실상 0이라 UI-11의 실익 논거("불러봐야 하는 일이 없다")와 실충돌이 없다 — 그 논거는 "호출 금지"가 아니라 "요구 안 함"이었고, 새로 드러난 누수가 요구할 이유를 만들었다. (b)는 폐기 주체마다 반응형 추적 부기가 하나 더 생긴다 |
H-219 |
매치 실패 error의 도착지 — drive 경로(리터럴 props의 미지원 값, 가장 흔한 사용자 실수)에서 level 2가 사용자 코드가 아니라 quad 내부 프레임을 가리킨다. process는 핸들러 재귀도 받는 공개 진입점이라 level 하나로 두 경로를 다 못 맞춤 |
(a) 지금 유지 + 한계 명시 — 메시지가 key·typeof·브랜드·provider 안내로 자기설명적이라 위치 없이도 진단 가능. M5에서 New 파이프라인이 완성돼 drive 경유 프레임 수가 고정되면 재평가 / (b) drive가 매치 실패를 잡아 사용자 호출부 level로 재상승 — 단 pcall 금지 계약(예외 안전성)과 긴장, 새 메커니즘 / (c) 다른 방식 |
(a) | (b)는 pcall을 안 쓰기로 한 전 자리 계약과 정면 충돌하고, 지금 잃는 건 위치 접두뿐 메시지 자체는 원인을 다 싣는다. 재평가 시점(M5)이 자연스럽게 온다 |
H-222 |
제공자(핸들러 작성자) 계약 위반 — retractor 생략·매치 실패류 — 의 level 분류가 architecture.md 계약 표(사용자 입력 2 / 내부 불변식 1)에 없다. 지금 코드는 잠정 2 |
(a) 표에 세 번째 행 신설 — "제공자 계약 위반 = 2(그 계약을 어긴 호출 구조에 가장 가까운 프레임)" / (b) 표는 안 늘리고 잠정 2 유지(주석만) / (c) 내부 불변식으로 보고 1 | (a) | M3 단위 2~4·M5·M10의 provider-facing error 전부가 같은 분류를 반복해서 물을 자리라, 표에 한 줄 넣는 게 자리마다 잠정 표시를 다는 것보다 싸다. (c)는 "quad 자신의 버그"가 아니라 제공자의 버그라 표의 1행 정의와 안 맞다 |
H-229 |
일반 Destroy 경로의 chains 잔존 — Destroy는 retract를 안 부르는 게 계약이고 gchold 섬은 Destroy로 무너지지만, chains의 강한 버킷 값(retractor→inst 캡처)이 별도 루트로 남아 weak 항목이 영영 안 비워진다. 반응형 바인딩이 있던 모든 파괴 인스턴스가 부기+userdata 영구 잔존, 죽은 Observer가 상류 전파 순회에 잔류(상세는 H-229 절) |
(a) 체인 리스트의 GC 앵커를 gchold로 통일 — chains는 리스트를 SetWeak으로만 잡고, 버킷 첫 생성 시 bindLifetime(inst, list)로 리스트를 gchold에 앵커(Destroy → gchold 붕괴 → 리스트·retractor·inst 전부 회수; retractor는 호출 안 함 — 계약 유지, 메모리만 해제). 선례: slot-plan.md 13차 세션이 두-Relate 상호 순환을 정확히 이 약(전부 weak + 앵커는 bindLifetime 하나)으로 고침. ⚠️ 결합 캐비엇: Dispatch가 생명주기 주입을 요구하게 돼 순수 디스패치 테스트도 mock 인스턴스가 필요해짐(단위 1 spec의 평범한 테이블 inst 수정) / (b) 버킷 첫 생성 시 onDestroying(inst, …) 훅으로 버킷 드롭 — 같은 결합 + Connection 부기 추가 / (c) 수용·문서화(파괴 인스턴스당 소량 영구 잔존 + 상류 emit 순회 비용) |
(a) | 같은 병(값이 weak 키를 되참조하는 상호 순환)을 같은 약으로 고친 확정 선례가 있고, 새 개념 없이 기존 프리미티브(bindLifetime/gchold) 재사용이다. (b)는 앵커 대신 훅이라 Connection 관리가 하나 더 생기고, (c)는 장수명 게임의 동적 UI에서 무한 누적이라 라이브러리 목표(정확성 우선)와 안 맞다 |
H-230 |
공유 상수의 배치 — ① HANDLER_PRIORITY_*: 백엔드(M5 quad-roblox)가 핸들러 등록 시 필요. 지금도 InitRoblox(module)이 받는 인스턴스의 module.Dispatch.*로 닿긴 하지만, 사용자 제안대로 의미가 드러나는 단일 소스가 낫다. ② error level 넘버(1/2/3)의 Enum화 |
(a) 우선순위 상수만 quad-types로 승격 — 이미 모든 패키지가 의존하는 의존성-0 계약 패키지고 런타임 테이블을 반환하므로 값 넷을 싣는 비용이 0에 가깝다(Dispatch는 require해 재노출, 단일 소스; 머리말의 "런타임 값은 없다시피" 서술 갱신). level은 Enum화하지 않는다 — level은 의미 enum이 아니라 프레임 수라 같은 의미("사용자 호출부")가 자리에 따라 2·3으로 갈리고(architecture.md "프레임 수가 아니라 도착지가 계약", H-205의 level 3이 실사례), ERROR_LEVEL_USER = 2 상수는 헬퍼 프레임이 끼는 자리에서 거짓말을 하게 된다 / (b) quad-const 패키지 신설(상수 전부 이관) / (c) 현행 유지(모듈 인스턴스 경유) |
(a) | (b)는 숫자 넷에 워크스페이스 멤버·pesde shim·relink 비용이 과하고(H-165류 함정도 하나 더 생김), 지금 상수가 이것뿐이라 "상수 패키지"가 설 자리가 아직 없다 — 상수가 실제로 불어나면 그때 분리해도 늦지 않다("실제로 관측된 문제에만 구조"). level 비권고 근거는 왼쪽 칸 |
H-231 |
error 유틸 모듈(사용자 설계) — setLevel(fn, tag) + debug.info 스택 워커로 level 손 세기 제거. 채택 자체는 사용자 발안이라 사실상 정해졌고, 세부 넷이 문항: 워커 방향 / 패키지 이름 / Enum 멤버 구성 / 기존 error 자리 이관 시점(상세는 H-231 절) |
방향: (i) 최상단 하강(경계 직전 blame — 등록은 정적 표면+수령 클로저 1회성, 익명 내부 클로저에 강건) / (ii) 에러 지점 상승(정밀하나 retractor까지 핫패스 등록 + 누락 = 오인). 배치: 독립 패키지(가칭 quad-error) vs quad-base 잎. 이관: 단위 2 전 일괄 vs 점진 |
(i) + 독립 패키지 + 단위 2 전 일괄 이관 | (i)는 사용자의 "중간에 짤림" 유의점을 구조적으로 없애고 등록 규율이 1회성 표면에 갇힌다. 독립 패키지는 backends(EngineOps 스텁)가 quad-base 없이 쓰기 위함(type-version-check 선례). 일괄 이관은 자리가 아직 적을 때(M2+단위 1) 끝내야 마일스톤마다 두 표기가 섞이지 않아서 |
H-215 |
스파이크 04(Dispatch 체인 retractFrom, rewrite-required/) 처분 — spec.dispatch.luau가 체인 깊이·레벨별 힌트·3-인자 retractFrom·SetStrong 음성 대조군을 이미 실측했으나, 재귀 재발행은 spec-로컬 wrapping 핸들러 근사다(실제 StoreBind는 M4) |
(a) 지금 폐기(01처럼 done/ 이동) — 잔여 몫(실제 StoreBind 경유 재발행 경로)은 M4 StoreBind spec이 진다고 그 단위 계획에 명시 / (b) M4까지 rewrite-required/에 유지(현 ROADMAP 문구 "M3 착수 시 같이 처리"를 "M4에서"로 정정) / (c) 지금 재작성 |
(a) | 체인 메커니즘 자체는 spec.dispatch.luau 12절이 실제 구현에 대고 고정했고(스파이크는 격리 재현이라 오히려 약함), 05/15/01 폐기와 같은 근거 구조다. 잔여 몫을 M4 spec 항목으로 옮겨 적으면 잊히지 않는다 |
§5 이상 없음 확인 (탐사자·구현이 확인만 하고 문제 없었던 자리)
- [2026-08-31, 단위 1 구현] "Dispatch 체인" 절 의사코드를 한 줄씩 옮기며
대조 — (A)/(B) 분기, (A)의 소비 직후
NOOP교체, (B)의 점유 마커 선행,chains:SetStrong이h.process앞, retractor 생략 즉시 error 양쪽,retractFrom꼬리 역순·항상 소비·구멍 error,H-103주석(pcall 안 감쌈) 전부 그대로 — 전사 차이는H-212(error 표기)뿐.spec.dispatch.luau1~12가 각 계약을 실측(같은 핸들러 두 슬롯 =State<State<T>>유사 구조, 깊은 체인 (A) 연쇄에서 각 레벨이 자기 힌트를 받는 것 포함). - [2026-08-31]
F-4-1의 언어 동작(일반화for가 배열 파트 전체를 해시보다 먼저, 배열 안은 index 순서) —spec.drive.luau1번이 실측 통과. 스파이크01재작성은 이 spec이 상시 회귀로 대체(§6 계획,STATUS.md에 기록). - [2026-08-31] 매치 실패 메시지의 "브랜드 출력"은 기각된 Brand 역조회
(
archive/brand-shared-registry-reversed.md)를 재도입하지 않고 모듈 공개 술어(is*) 프로브로 구현 — 실패 경로에서만 돌고(지연 생성 규칙), 새 표면 없음. - [2026-08-31]
H-165(quad-types에export type추가 시 pesde shim 재생성 필요)를 예고대로 밟았고pesde install재실행으로 해소 —Handler/Dispatch가 shim에 올라옴. 새 발견 아님(문서 그대로). - [2026-08-31, 단위 1 탐사자]
./scripts/test.sh전체 exit 0 (luau-analyze무출력 클린 포함),grep -rn "TODO(H-"0건, 작업 트리 클린.Dispatch/init.luau를 "Dispatch 체인" 절 의사코드와 재차 한 줄 대조 — (A)/(B) 분기·NOOP교체/점유 마커·SetStrong선행·retractor 생략 error 양쪽·retractFrom꼬리 역순/항상 소비/구멍 error(level 1) 전부 일치, 전사 차이는H-228(진단 라벨 표기) 하나뿐. - [2026-08-31, 단위 1 탐사자]
getHandler가 매치 실패에 error가 아니라nil을 돌려주는 건 발견이 아니다 — brief §6 행은 error를getHandler괄호 안에 적었지만, 정본인dispatch-core-plan.md의 "Dispatch.process/Handler.process이름 겹침" 항목이getHandler(inst,k,v): Handler?= 순수 스캔/부작용 없음, error는 오케스트레이터(process)의 일로 이미 갈라뒀다. 코드·quad-types·spec 1번이 전부 정본 쪽이다. - [2026-08-31, 단위 1 탐사자]
Handler타입의 정의 위치(quad-types 소유,Dispatch/Handler.luau는 재수출)는 §6 표기("quad-types …Handler타입 재수출")와 방향이 반대로 읽히지만 구현 방향이 유일하게 가능한 쪽이다 — quad-types는 quad-base를 require할 수 없고(의존이 base → types 단방향)Dispatch타입이Handler를 참조하므로 정의는 types 쪽에만 살 수 있다. 잎 유지 계약("Dispatch를 되참조하지 않는다")은 그대로 성립. - [2026-08-31, 단위 1 탐사자] spec이 안 태우던 경로 셋을 프로브로 실측,
전부 계약대로 (스크립트는 스크래치, 남기지 않음): (1)
H-223— retractor 생략 메시지가 name·priority·k·index를 실제로 싣는다 (handler "MyLeaf" (priority 7) returned no retractor at key SomeKey, index 1). (2)H-103—h.process가 던지면NOOP마커가 남고, 이후retractFrom은 조용히 소비(에러 없음, 정리 0회)하며 그 뒤 재설치·철거는 정상(문서가 말한 "부기 무결성 비보장 + 크래시는 아님" 그대로). (3) 매치 실패 후 같은(inst,k)에 핸들러를 등록하면 정상 동작 — 실패 경로가 chains에 빈 리스트 하나를 남기지만 의사코드도 같은 순서(list 확보가 getHandler보다 앞)라 전사 차이 아님. - [2026-08-31, 단위 1 탐사자]
addHandler의table.sort는 불안정 정렬이라 동률 핸들러끼리의 스캔 순서가 등록이 추가될 때마다 뒤섞일 수 있는데, 이는 문서가 이미 확정한 "동률 tiebreak 규칙은 강제하지 않는다"(우선순위 동률/매치 실패 처리 절) 범위 안이다 — 가드·안정화 제안 안 함(관측된 문제 없음 원칙).
§6 남은 의심 (발견은 아니지만 다음 라운드가 파볼 자리)
(아직 없음)