docs(audit): 프로젝트 전체 재감사 — 분할 후 잔여 stale 참조 14곳 정정

69466ab/32e9db0 이후 5개 영역 병렬 에이전트 감사 + 직접 트레이싱으로
프로젝트 전체를 다시 훑음. 핵심 설계(하강 diff 재디스패치, Tag
참조카운트, nameClaims, Slot-in-Slot 해체)는 재트레이싱해도 버그
없음 — 전부 같은 클래스의 문서 참조 잔여 오류였음: `bind-system-plan.md`
2단계 분할(14차 세션) 이후에도 그 파일을 계속 가리키는 느슨한 산문
인용(따옴표 절 제목이 아니라 "~가 말하는"/"~ 참고" 식이라 doc-check.py
정규식이 못 잡는 형태).

- `ref-plan.md`/`ui-shorthand-plan.md`(2곳)/`tween-plan.md`/
  `slot-plan.md`(3곳) — `dispatch-core-plan.md`로 정정.
- `module-lifecycle-plan.md:21` — 32e9db0가 같은 파일 114/128행은
  고쳤지만 21행만 놓쳤던 것.
- `ROADMAP.md`(2곳)/`luau-test/README.md` — `None`/`recompute` 절
  참조 정정.
- `luau-test/done/02`/`03` 스파이크 주석 — 02는 `ref-plan.md`(9차
  세션에 이미 옮겨간 절이었음, bind-system-plan.md였던 적 없음),
  03은 `dispatch-core-plan.md`로 정정.
- `audit/luau-test-first-run-2026-08-13.md` — "no-op 점유 마커"를
  여전히 유효한 수정 근거처럼 서술하던 부분에 정정 각주 추가(점유
  체크 자체는 하강 diff 재설계로 폐지됨, `chains:SetStrong` 순서
  버그만 지금도 유효).

doc-check.py ERROR 0 유지, WARN 101 그대로(전부 판단 필요한 기존
느슨한 인용, 이번 라운드가 새로 만든 건 없음). 5개 에이전트 전원
"설계 결함 0건" 보고로 수렴 판단.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
qwreey 2026-08-14 00:45:03 +09:00
parent 32e9db0773
commit 10cd31be2c
Signed by: qwreey
GPG key ID: D28DB79297A214BD
10 changed files with 28 additions and 14 deletions

View file

@ -62,6 +62,20 @@
나중에 UI를 덮어쓰는** 증상까지 간다는 게 실측으로 확인됨. 수정
(`SetStrong` hoist + no-op 점유 마커)이 옳았다는 결정적 근거.
> **[2026-08-14 리뷰에서 정정] "no-op 점유 마커" 부분은 이후 폐기된
> 옛 모델 서술입니다.** `chains:SetStrong``process` 호출 *전에* 끝내야
> 한다는 순서 버그(위 표, `retractor` 유실/`STALE` 재현)는 하강 diff
> 재설계(2026-08-13 열네 번째 세션, `base/dispatch-core-plan.md`)에서도
> 그대로 유효 — 지금 `Dispatch.process`도 여전히 `chains:GetStrong`/
> `SetStrong`으로 list를 먼저 확보한 뒤에 `h.process`를 부름. 다만 "점유
> 마커"(같은 인덱스가 이미 점유돼 있으면 즉시 error)는 그 뒤 **Dispatch의
> 점유 체크 자체가 폐지**되며(`dispatch-core-plan.md` "Dispatch 체인" 절)
> 없어진 옛 개념 — 지금은 `list[index] = { handler = h, retractor = NOOP }`
> placeholder만 미리 박아두는 것으로 대체됨(같은 "호출 전 자리 확보"
> 목적, 에러를 내는 메커니즘은 아님). 이 스파이크는 `rewrite-required/`
> 이동돼 있고(`luau-test/STATUS.md`), 재작성 시 이 절의 "정상(수정본)"
> 시나리오도 새 모델(placeholder, 점유 에러 없음)로 갱신할 것.
## `07` — 스파이크를 보강해야 실제 검증이 됐음
원래 3번 섹션이 "강하게 붙잡아둔 10개가 살아있는가"라는 **sanity check만**

View file

@ -18,7 +18,7 @@ Slot과 맞물려서 잘 생각해서 구현해야 하는 부분. **기울어진
"Handler로 base를 받는" 게 아니라) — 이유: 여긴 가상돔이 없어서, base
쪽이 "누가 실제로 그려주는지" 모르는 채로 있다가 Roblox Handler를 주입받는
모양이 더 자연스러워 보임. (이름 자체는 이후 "Handler"로 확정 —
`base/bind-system-plan.md`의 핸들러 계약 절 참고, 이 문서는 여전히
`base/dispatch-core-plan.md`의 핸들러 계약 절 참고, 이 문서는 여전히
초안 당시 표현인 "프로바이더"로 쓰여 있던 걸 정정.)
## pluggable 플러그 초기화는 누구 몫인가

View file

@ -6,7 +6,7 @@
> 했고 결정은 하나도 안 바뀜.**
**상태**: base — 확정. `Dispatch`/`Brand`와의 관계는
`base/bind-system-plan.md`(디스패치 코어)와 `base/brand-plan.md` 참고.
`base/dispatch-core-plan.md`(디스패치 코어)와 `base/brand-plan.md` 참고.
## Ref — 도입 확정, 단 용도는 재정의됨

View file

@ -490,7 +490,7 @@ nested `Slot`을 파괴 후 재사용하는 경로가 정확히 이 케이스).
체크로 이걸 막고, `process``old == slotValue` 체크로 대칭적으로 막음
— 둘 다 스킵돼야 완전한 no-op.
- Slot 핸들러 자신이 감시 중인 값(배열/스토어)이 바뀔 때 child를 갱신하는
추적(구독)도 `base/bind-system-plan.md`가 말하는 "process 함수가 다른 값
추적(구독)도 `base/dispatch-core-plan.md`가 말하는 "process 함수가 다른 값
변경을 추적해도 됨" 범위에 속하고, `retract` 시점엔 그 추적만 풀면 됨 —
Destroy 시점엔 `retract`가 호출되지 않는다는 원칙(`base/lifecycle-pattern.md`)도
동일하게 적용.
@ -1440,7 +1440,7 @@ end
```
`recompute`가 owner가 Slot이면 그 `.Length`에도 합계를 반영하도록
확장됐으므로(`base/bind-system-plan.md` 참고) — `Slot.Length`는 더
확장됐으므로(`base/dispatch-core-plan.md` 참고) — `Slot.Length`는 더
이상 raw 개수가 아니라 **"요소별 기여도의 합"**(plain=1, nested
Slot=그 `.Length`)이 됨. plain 요소만 있는 흔한 경우엔 항상 합==개수라
체감 차이 없음.
@ -1558,7 +1558,7 @@ GC가 전부 한 번에 정리하니 손 안 대도 됨 — 이 구분은 새
죽는 경우)를 만나서 드러난 것뿐.
- **Length 변경은 정확히 offset 변경으로만 전파됨, 별도 채널 없음**
수정된 `recompute`(`base/bind-system-plan.md` 참고)를 보면 `:Set()`
수정된 `recompute`(`base/dispatch-core-plan.md` 참고)를 보면 `:Set()`
호출되는 대상은 (a) 뒤 형제들의 offset, (b) owner가 Slot이면 그
`.Length` 딱 둘뿐. Length 값 자체는 읽히기만 함(`:Get()`/Observer
트리거) — (b)로 올라간 `.Length`도 한 단계 위에서는 그냥 또 다른

View file

@ -154,7 +154,7 @@ StoreBind가 State/Source 레이어를 전부 풀어낸 뒤의 값(그리고 이
`inst`가 죽으면 이 슬롯(엔진 Tween 객체+`Value` 포함)도 별도 정리 로직
없이 같이 GC됨. **[정정, 2026-08-12 열한 번째 세션, 2026-08-13 다섯 번째
세션에 클로저 반환 계약으로 서술 갱신]** `process`가 반환한 클로저는 store
재발행마다 항상 불리지만(`bind-system-plan.md` 일반 retract 계약 절
재발행마다 항상 불리지만(`dispatch-core-plan.md` 일반 retract 계약 절
정정분 — "거의 안 불림"이었던 원 서술은 틀렸음), PropertyHandler의
그 클로저는 몸체가 no-op이라 실질적으로 하는 일이 없음 — 아래 절 참고.

View file

@ -83,7 +83,7 @@ Modifier 타입의 메소드 목록에 끼워 넣도록 챙기면 됨, 새로
### `v``nil`인 경우 — `process`가 직접 자식 제거, 반환 클로저는 관여 안 함 (2026-08-07 여덟 번째 세션)
`modifier-plan.md``None` 센티널(`base/bind-system-plan.md`의
`modifier-plan.md``None` 센티널(`base/dispatch-core-plan.md`의
`NoneHandler` 재귀 재디스패치 절 참고)이 최종적으로 이 Handler의
`process(inst, k, nil)`을 호출하는 구체 사례 — 이 Handler에서 "`v`가
`nil`"은 만들어둔 `_quad_corner`류 자식이 있으면 그냥 지우는 것으로 확정.
@ -93,7 +93,7 @@ Modifier 타입의 메소드 목록에 끼워 넣도록 챙기면 됨, 새로
- **이건 반환 클로저가 아니라 `process` 자신의 로직****[정정, 2026-08-12
열한 번째 세션, 2026-08-13 다섯 번째 세션에 클로저 반환 계약으로 서술
갱신]** 그 클로저는 store 재발행마다 항상 불리지만(핸들러 타입이 안
바뀌어도, `bind-system-plan.md` 일반 retract 계약 절 정정분 참고), 이
바뀌어도, `dispatch-core-plan.md` 일반 retract 계약 절 정정분 참고), 이
Handler는 `process(inst,k,v,index)` 자체가 `v``nil`이든 숫자든
전부 완결적으로 처리하므로(있으면 지우거나 만들거나) 반환 클로저가 할 일이
없어 `function() end`이면 충분 — 일반 프로퍼티 핸들러가 no-op 클로저를

View file

@ -86,7 +86,7 @@ ROADMAP 항목 근거인지, 어떻게 실행하는지, 실행 후 뭘 확인해
| `17-modifier-index-tableclone-chaining.luau` | Modifier의 제네릭 `__index`+`table.clone` 체이닝 — 임의 필드 이름에 대해 즉석 setter가 만들어지는지, `table.clone`이 메타테이블을 참조로 공유해 여러 단계 clone에서도 체이닝이 안 끊기는지, 원본이 mutate 안 되는지, 형제 분기끼리 오염 안 되는지 | `modifier-plan.md` "런타임은 클래스별 코드 없이 base에 딱 하나만 있으면 됨" 절 + "`table.clone`의 정확한 동작 — 확인됨" 절(2026-08-12 열일곱 번째 세션), `pre-implementation-audit.md` 1-11 |
| `18-relate-mutual-cycle-gc.luau` | **[2026-08-13 신규]** 서로 다른 두 `Relate`가 서로의 키를 상대방의 강한 값으로 제공하는 상호 순환은 Luau에 ephemeron이 없어 GC가 못 푼다는 주장(지금까지 공식 문서 인용으로만 뒷받침됨) — 음성 대조군(순환 재현)과 양성 대조군(한쪽을 weak-value로 낮추면 풀리는지) 둘 다 실측 | `relate-plan.md` "위험한 패턴" 절(2026-08-12 열세/열네 번째 세션), `slot-plan.md``kSlotMap`/`slotOwner`/`elementOwner` 실사례 |
| `19-ownership-refcount-relate-patterns.luau` | **[2026-08-13 신규, 같은 날 B/C 전면 재작성 — 지금은 현행 설계 기준]** 세 소유권/참조카운트 알고리즘 검증. **A**: Tag `tagNameMap` 참조 카운트(여러 위치가 같은 이름을 겹쳐 가져도 마지막 홀더가 빠질 때만 실제 `RemoveTag`. 옛 `kTagMap`은 클로저 캡처로 대체돼 삭제됨). **B**: Attribute 이름 소유권 — 공개 `AttributeKey(name)` 캐시 + `Dispatch.process`의 인덱스 1 **점유 체크**가 충돌을 잡는지(옛 `rawNew`+`owners` 수동 레지스트리는 폐기). **C**: Slot 소유권 — nested 엄격 `claimOwner`(같은 owner 재클레임도 error) vs top-level `claimOwnerAt(inst,k)`(정확히 같은 자리 재발행만 no-op). **셋 다 음성 대조군 포함** — 옛 로직이 `Slot{a,a}`/`Frame{slot,slot}`을 조용히 통과시키는 걸 재현. **[2026-08-13 열네 번째 세션] 0-Z가 확정되며 B 섹션이 낡음 → `rewrite-required/`** — 이제 "그룹 전용 키 + `AttributeKeyHandler`의 이름 claim"을 검증해야 함(A/C는 그대로 유효) | `tag-plan.md` "메커니즘", `attribute-plan.md` "이름 소유권", `slot-plan.md` "요소 소유권" |
| `20-slot-splice-index-arithmetic.luau` | **[2026-08-13 신규]** `Slot:Splice(index, removeCount, ...newElements)`의 shift+recompute 1회 계산이, `Extract`/`Add` 반복으로 재현한 참조 구현과 항상 같은 결과를 내는지 — 제거/삽입 길이가 다를 때(delta 양수/음수) 뒤 요소가 밀리는 방향과 양을 헷갈리는 off-by-one 위험(이 프로젝트가 `Dispatch.recompute`에서 실제로 냈던 것과 같은 클래스의 버그)을 경계값 케이스로 검증 | `slot-plan.md` "확정" CRUD 표 + "`Splice` 신설" 절(2026-08-12 열다섯 번째 세션), `bind-system-plan.md`의 `recompute` off-by-one 수정 사례(2026-08-11 여섯 번째 세션) |
| `20-slot-splice-index-arithmetic.luau` | **[2026-08-13 신규]** `Slot:Splice(index, removeCount, ...newElements)`의 shift+recompute 1회 계산이, `Extract`/`Add` 반복으로 재현한 참조 구현과 항상 같은 결과를 내는지 — 제거/삽입 길이가 다를 때(delta 양수/음수) 뒤 요소가 밀리는 방향과 양을 헷갈리는 off-by-one 위험(이 프로젝트가 `Dispatch.recompute`에서 실제로 냈던 것과 같은 클래스의 버그)을 경계값 케이스로 검증 | `slot-plan.md` "확정" CRUD 표 + "`Splice` 신설" 절(2026-08-12 열다섯 번째 세션), `dispatch-core-plan.md`의 `recompute` off-by-one 수정 사례(2026-08-11 여섯 번째 세션) |
## 공통 유틸리티

View file

@ -8,7 +8,7 @@
사용자가 직접 찾아낸 버그: None으로 소진하면 그 슬롯이 영원히
non-nil로 남아있어서, `:Wait()`/`:Callback()`가 반복 호출될 때마다
배열이 끝없이 길어지는(예전 소진 슬롯을 재사용 못 하는) 진짜 버그였음.
.claude/base/bind-system-plan.md "왜 None이 아니라 nil인가" 절(2026-08-09
.claude/base/ref-plan.md "왜 `None`이 아니라 `nil`인가" 절(2026-08-09
열한 번째 세션, 최종 정정)이 최신 소스 — 결론은 두 패턴이 서로 다른
문제를 풀고 있었다는 것:
@ -164,7 +164,7 @@ end
작은 값 유지)?
3. Part B-2 — None+table.insert 방식은 실제로 1000까지 자라는가 —
이게 바로 사용자가 찾아낸 "무한 성장" 버그의 정량적 재현. 이 결과가
기대와 다르면(예: 실제로는 안 자란다면) bind-system-plan.md의 정정
기대와 다르면(예: 실제로는 안 자란다면) ref-plan.md의 정정
근거 자체를 재검토해야 하니 반드시 알려줄 것.
4. Part B-3 — 새 등록이 소진된 빈 슬롯(index=idx2)을 실제로 재사용하는가
— 이게 "table.insert 대신 선형 탐색 재사용 등록 함수"가 실제로

View file

@ -1,6 +1,6 @@
--[[
검증 대상: process(inst,k,v)/retract(inst,k,v) 기반 재귀 재-dispatch
모델(.claude/base/bind-system-plan.md "확정된 디스패치 모델" 절)이 실제
모델(.claude/base/dispatch-core-plan.md "확정된 디스패치 모델" 절)이 실제
Luau 함수 재귀로 자연스럽게 짜이는지, 우선순위 스캔(isHandlable)이
기대대로 동작하는지에 대한 최소 스파이크.

View file

@ -95,7 +95,7 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검
(아래 항목) / `Dispatch.addHandler(handler)`(레지스트리 등록,
quad-roblox가 팩토리 뮤테이션 시점에 호출) / `Dispatch.drive(inst,
flattened)`(배열→해시 두 패스 순회하며 각 `(k,v)`
`Dispatch.process(inst,k,v,1)` 호출 — `bind-system-plan.md`의 `None`
`Dispatch.process(inst,k,v,1)` 호출 — `dispatch-core-plan.md`의 `None`
센티널 절, 2026-08-07 여덟 번째 세션에 네이밍 확정).
**[2026-08-13 다섯 번째 세션 전면 재설계]** "이전 담당자와 다르면
`retract`"라는 옛 diff 모델은 폐기 — 정리 책임은 전적으로
@ -509,7 +509,7 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검
- [ ] 인라인 키/setter로 modifier 필드를 명시적으로 지우는 `None` 센티널
(이름 확정, `modifier-plan.md` 2-1번, `Peek` 반환 타입에 `None` 추가) +
이를 `nil`로 재디스패치하는 base 내장 `NoneHandler`
(`bind-system-plan.md`의 `None` 센티널 절, M2 dispatch 엔진의
(`dispatch-core-plan.md`의 `None` 센티널 절, M2 dispatch 엔진의
"이전 매치 핸들러 추적" 항목과 함께 구현 — `StoreBind` 핸들러와
동일한 재귀 재디스패치 패턴이라 새 메커니즘 아님) — `None` 센티널
자체는 확정 완료. **[2026-08-13 열네 번째 세션 갱신]** `NoneHandler`