# 구현 착수 직전 감사 — 모호성 / 지연결정 리스크 / 단순화 후보

**상태**: research — 사용자 상의 필요. 2026-08-06 세션에서 신설. `.claude/base/`
전체가 "확정"으로 표시돼 있지만, 실제 `ROADMAP.md` M0 착수를 앞두고 구현자
시점에서 다시 크리티컬하게 훑어본 결과. 방법론: `base/` + 근접
`research/`(tween-plan, ui-shorthand-plan, existing-instance-bind-plan —
앞의 둘은 이후 `base/`로 승격, 마지막은 2026-08-14에 기각되어 `archive/`)를
4개 클러스터로 나눠 서브에이전트로 병렬 정독시키고, 각각 세 가지 렌즈로
리뷰하게 했음 —

1. **모호성**: 실 구현 중 "이 경우엔 정확히 어떻게 동작하지?"라고 멈춰서
   다시 물어봐야 할 만한, 구체적 동작이 명시 안 된 지점.
2. **지연 결정 리스크**: 지금 "나중에 정해도 된다"고 취급되지만 사실 코어
   타입 구조/패키지 경계/데이터 모델에 깊이 얽혀 있어 나중에 바꾸면 연쇄
   파급이 클 것 같은 항목.
3. **오버엔지니어링/단순화 후보**: 확정됐다고 적혀 있지만 목적에 비해 과한
   추상화로 보이거나 더 간단한 대안이 있어 보이는 지점. (단, "이미 여러
   라운드에 걸쳐 검증됨"이라고 문서가 스스로 못박은 결정 자체를 재론하는
   건 배제 — `.claude/project-context.md`의 반복 조사 금지 원칙과 같은 이유.)

이미 `.claude/question.md`에 취합된 항목(용어 재검토, M0 스파이크 항목
자체, Slot 형제 순서 보장 등)은 여기서 제외했다 — 아래는 **전부 새로 발견된
것**. 부수적으로 이 감사 과정에서 `architecture.md`의 stale한 부분 두 곳
(온톨로지 요약 절, 소스트리 `Store.luau`/`Ref.luau` 주석)을 발견해 같은
세션에서 바로 고쳤다 — 판단이 필요 없는 순수 문서 동기화라 여기 남기지
않고 해당 문서에서 직접 정정함.

## 어떻게 쓸 것

우선순위 1은 M0~M4 구현 도중 실제로 부딪힐 가능성이 높은 것 — **가능하면
M0 착수 전에 확인**. 우선순위 2는 지금 결정해두면 싼데 안 해두면 나중에
비쌀 것들 — 해당 마일스톤 착수 직전에만 확인해도 됨. 우선순위 3은 사용자
판단에 달린 단순화 제안. 문서 모순 절은 이미 고친 것과 아직 안 고친 것을
구분해뒀다.

---

## 우선순위 1 — M0~M4 착수 전 확인 권장

### 1-1. Tween이 "범용 store-bind 캐치올 핸들러"의 유일한 예시로 쓰여, 일반 반응형 프로퍼티 바인딩과 혼동될 위험 — [해소됨, 2026-08-10 세션]

**해소**: 아래 "제안"과 같은 방향(별개 핸들러로 분리)이 아니라, 그보다
더 근본적인 재설계로 해소됨 — Tween 자체가 더 이상 독립 Dispatch
핸들러가 아니라 PropertyHandler가 소비하는 값-레벨 래퍼(`Tween<T>`)로
바뀌어, "일반 store-bind와 Tween-store-bind가 같은 핸들러인지"라는
질문 자체가 성립하지 않게 됨 — 범용 State/Source 언랩은 `Dispatch/
StoreBind.luau` 하나뿐이고 Tween은 그 아래에서 나온 `realv`를
PropertyHandler가 직접 판별. 상세는 `base/tween-plan.md`(전면
재작성), 구 모델은 `archive/tween-special-bind-key-reversed.md`. 아래는
원래 발견 당시 기록.

**위치**: `base/dispatch-core-plan.md` "확정된 디스패치 모델" 절 67-79행 —
"Tween의 store-bind 핸들러는 **`k`는 무엇이든 받고 `v`가 Store인 경우를
잡아내는, 우선순위가 매우 높은 핸들러**"; `architecture.md` 소스트리엔 이
역할을 하는 quad-roblox 파일이 `Handlers/Tween.luau` 하나뿐(별도 범용
StoreBind 핸들러 파일 없음); `ROADMAP.md` M11도 Tween을 "높은 우선순위
store-bind 핸들러"로 서술.

**문제**: 이 문서 전체에서 "v가 store인 값을 구독해 realv로 재귀 process
하는" 범용 메커니즘의 **유일한 구체 예시가 항상 "Tween"으로만 등장**한다.
그런데 Tween(실제 애니메이션, override/cancel 정책)은 `research/
tween-plan.md`라는 별개 리서치 문서와 별도 로드맵 마일스톤(M11)을 가진,
명백히 더 좁고 아직 미확정인 기능이다. `Frame { BackgroundColor3 =
store.color }`처럼 애니메이션 없이 그냥 반응형으로 값만 바뀌길 원하는
가장 흔한 케이스가 (a) 결국 이름은 "Tween"인 파일을 거쳐가며 "애니메이션
없음"으로 처리되는 건지, (b) Property/Tag/Attribute 등 각 핸들러가 각자
`Dispatch/StoreBind.luau`(quad-base, 범용) 유틸을 직접 써서 독립적으로
구현해야 하는 건지 문서가 정하지 않았다. `ROADMAP.md` M4("첫 end-to-end
반응형 업데이트")는 Tween 없이(M11보다 훨씬 전에) `Dispatch/StoreBind.luau`
만으로 "store 값 바꾸면 process가 다시 호출된다"를 검증하게 돼 있어 (a)는
아닌 것 같지만, 그럼 M11에서 Tween.luau가 실제로 추가될 때 그게 기존
경로와 **레이어링(우선순위로 얹힘)되는지 대체되는지**가 불명확하다.

**제안**: "일반 store-bind(애니메이션 없음)"와 "Tween 전용 store-bind
(애니메이션 있음)"가 같은 핸들러인지 별개 핸들러인지부터 확정. 별개라면
소스트리에 `Handlers/StoreBind.luau`(또는 유사) 항목을 명시적으로 추가하고,
`Tween.luau`는 그 위에 얹히는 "값에 tween 설정이 붙어있으면 가로채는" 더
높은 우선순위의 특수 케이스로 재정리하는 게 자연스러워 보임.

### 1-2. retract 시 "이전에 실제로 매치됐던 핸들러"를 누가 추적하는지 불명 — [해소됨, 2026-08-08 세 번째 세션]

**해소**: `Dispatch`가 `(inst,k)`별 핸들러 체인(순서 있는 배열, `chains`)을
직접 소유하고, `Dispatch.retractFrom(inst,k,index)`가 꼬리부터 `index`
까지 정리해주는 걸로 확정(2026-08-08 확정 당시 이름은 `retractUnder`이고
체인이 핸들러 배열이었음 — 2026-08-13 다섯 번째 세션에 인덱스 기반으로
재설계되며 개명, 같은 날 열네 번째 세션에 힌트 인자가 빠져 3-인자가 됨,
결론 자체는 유지) — 아래 원래 제안(`Dispatch/StoreBind.luau`가
"마지막 선택된 핸들러"를 직접 들고 있는 방식)은 재귀/래핑 핸들러가
여러 단계(A→B→C)로 겹칠 때 자기 자신의 상태와 위임한 핸들러의 상태가
슬롯 하나를 두고 충돌하는 문제가 있어 기각되고, 대신 Dispatch 자신이
전체 체인을 배열로 들고 있는 쪽으로 정리됨. 상세는 `base/
dispatch-core-plan.md` "Dispatch 체인" 절, `ROADMAP.md` M2/M4. 아래는
원래 발견 당시 기록.

**위치**: `base/dispatch-core-plan.md` "확정된 디스패치 모델" 절 90-91행 —
"store bind가 새 값으로 넘어갈 때 이전 핸들러의 `retract(inst, k, v)`를
한 번 호출해주면 됨."

**문제**: store-bind 재실행 모델에서 `realv`의 실질 타입은 매 갱신마다
바뀔 수 있다(예: 처음엔 숫자값이라 Property 핸들러가 매치, 다음번엔 다른
타입값이라 다른 핸들러가 매치). 이 경우 "이전 핸들러"가 정확히 어느
핸들러였는지는 `(inst, k)`별로 어딘가 기록돼 있어야 정확한 `retract`
대상을 찾을 수 있다. "핸들러 내부 상태 저장" 절은 "각 핸들러가 자기가
만든 것"(예: 실행 중인 Tween 객체)을 저장하는 패턴만 다루지, "이 키를
마지막으로 어느 핸들러가 담당했는가"라는 상위 레벨 라우팅 상태를 누가
(base 엔진 vs `Dispatch/StoreBind.luau` 래퍼) 관리하는지는 명시가 없다.

**제안**: `Dispatch/StoreBind.luau`가 "마지막으로 선택된 핸들러" 자체를
`(inst, k)`별 상태로 들고 있다가, 새 `realv` 처리 전에 그 핸들러의
`retract`를 호출하는 식으로 지금 결정해두는 게 좋아 보임 — M2/M4에서 바로
부딪힐 지점.

### 1-3. 우선순위 스캔의 동률 처리, 매치 실패 시 동작이 정의 안 됨 — [해소됨, 2026-08-12 열일곱 번째 세션]

**해소**: 동률은 tiebreak 규칙을 강제하지 않고 대신 목적별 우선순위 상수
(`HANDLER_PRIORITY_HIGH` 등, "밴드+오프셋" 패턴)로 애초에 안 나게 유도,
매치 실패는 조용한 무시 없이 즉시 `error`(브랜드+`typeof` 출력, provider
초기화 확인 안내)로 확정. 핸들러 등록 시점 동률 감지 print 경고와
`Dispatch.listHandlers()`류 전체 목록 조회 함수도 M2 기본 기능으로
확정 — 상세는 `base/dispatch-core-plan.md` "우선순위 동률/매치 실패 처리"
절. 아래는 원래 발견 당시 기록.

**위치**: `base/dispatch-core-plan.md` "핸들러 계약" 절 — "디스패치는 등록된
핸들러를 우선순위 순으로 스캔하며 `isHandlable`을 호출, 첫 매치가 처리."

**문제**: (a) 두 핸들러가 같은 `priority` 값을 가질 때 어느 쪽이 우선인지
(등록 순서? 정의 안 됨) 규칙이 없음. (b) 어떤 핸들러도 `isHandlable(k, v)`를
만족하지 않는 `(k, v)` 쌍이 들어왔을 때 — 조용히 무시? 에러? — 도 정의가
없음. 후자는 특히 사용자가 오타 키를 쓰거나 지원 안 되는 조합을 넣었을 때
디버깅 경험에 직결.

**제안**: 최소한 "매치 실패는 에러(silent 무시 금지)"만이라도 지금
결정해두면 구현 중 인터럽트를 막을 수 있음. 동률은 "등록 순서가 tiebreak"
정도로 명시만 해둬도 충분. M2(Dispatch 엔진) 착수 직전 확인.

### 1-4. provider(팩토리) 미주입 상태에서 dispatch가 호출되면 어떻게 되는지 세 번째 케이스가 빠짐 — [해소됨, 2026-08-12 열일곱 번째 세션]

**해소**: 별도 케이스로 처리하지 않음 — provider 미주입 상태는 결국 그
클래스의 핸들러가 레지스트리에 하나도 없는 상태이므로, 위 1-3에서 확정된
일반 "매치 실패 시 즉시 error" 규칙 하나로 자연히 커버됨. 상세는
`base/module-lifecycle-plan.md` 해당 항목, `base/dispatch-core-plan.md`
"우선순위 동률/매치 실패 처리" 절. 아래는 원래 발견 당시 기록.

**위치**: `base/module-lifecycle-plan.md` "Bind는 누가, 어떻게 구현하는가"
절 — 재호출 가드는 "같은 팩토리=무시, 다른 팩토리=에러" 두 케이스로
확정됐지만, 이건 전부 "팩토리가 이미 한 번 실행된 이후" 얘기다.

**문제**: 원문이 처음 언급했던 세 번째 케이스 — **아직 아무 팩토리도 실행
안 된 상태에서 dispatch(`process`/`retract`)가 호출되는 경우**(예:
`InitRoblox` 호출 전에 컴포넌트를 마운트 시도)는 이후 어느 문서에서도 다시
다뤄지지 않았다. 이때 정확히 뭐가 일어나는지(명시적 에러 메시지 vs
nil-index 크래시 vs 조용한 no-op)가 안 정해져 있음.

**제안**: base dispatch 엔진이 "아직 provider 미주입" 상태를 감지해 명확한
에러를 던지도록 지금 결정해두면, 구현 중 흔한 초기화 순서 실수를 훨씬 덜
헷갈리게 만들 수 있음. 1-2번과 같은 타이밍(M2)에 같이 확정.

### 1-5. `props.Modifier`/`props.Ref` forwarding 관례가 Lua 배열 리터럴의 nil-hole 함정에 그대로 노출됨

**[2026-08-07 열 번째 세션 갱신 — 반영 완료.]** 아래 제안 (a)/(b) 대신
더 단순한 (c)류 해법으로 확정: `props.Modifier or None`/`props.Ref or
None` 관용구를 필수로 강제 — `None`이 항상 non-nil이라 리터럴 구멍 자체가
안 생기고, 이미 있는 array-part `None`-스킵 메커니즘(PreRef 논의 중
같은 세션에서 확정)을 그대로 재사용해 새 코드가 안 늘어남.
`base/component-composition-plan.md` "필수 관용구" 절, `ROADMAP.md` M0에
반영 완료 — 더 이상 열린 항목 아님, 아래는 원래 발견 당시 기록.

**위치(당시)**: `base/component-composition-plan.md` "최종 결론" 1번 —
`return Frame { props.Modifier, props.Ref, ... }` 패턴.

**문제**: caller가 `props.Modifier`나 `props.Ref`를 안 넘기면 그 값은
`nil`이다. Lua 테이블 생성자에서 `{nil, refValue, child}`는 `t[1]`이
사실상 키 없는 상태가 되지만 `t[2]`, `t[3]`은 정상적으로 채워진다. 이때
디스패치 루프가 v1처럼 `ipairs`로 배열을 순회한다면 **`t[1]`이 nil이라는
이유만으로 `t[2]`(Ref)와 `t[3]`(자식)까지 통째로 무시**될 수 있다 — Ref
콜백이 조용히 안 불리고 자식도 안 그려지는, 원인 추적이 매우 어려운 버그
클래스. 이 문서가 정식으로 권장하는 forwarding 패턴 자체가 이 함정을
유발하는 전형적 모양인데 nil 처리 규칙이 전혀 언급되지 않는다.

**제안**: (a) 디스패치 루프를 `ipairs` 대신 `#t` 기반 명시적 인덱스 루프나
`table.pack`/센티널로 nil-safe하게 만들거나, (b) forwarding 관례 자체를
`Frame { Modifier = props.Modifier, Ref = props.Ref, [1] = child }`처럼
명시적 키로 넘기게 하거나, (c) 최소한 "props.Modifier/Ref가 nil일 수
있으니 배열 위치에 직접 넣지 말라"는 경고를 문서에 남길 것. **M0
스파이크가 이 패턴을 이미 검증 대상으로 잡고 있으니(`props.Modifier`/
`props.Ref` named-parameter 컴포넌트 작성), 그 스파이크 코드에 caller가
Modifier/Ref를 아예 안 넘기는 케이스를 반드시 포함시킬 것.**

### 1-6. `canExecute`/`Connected`의 실제 구현 방식이 미확정인 채로 코어 전역에 이미 재사용 확정됨

**[해소됨, 2026-08-08 세션. 시그니처는 2026-08-14 다섯 번째 세션에 정정,
`canBound`는 열한 번째 세션에 별도 진입점으로 재도입]**
`bindLifetime(inst,value)`/`unbindLifetime(value)`/`canBound(value)`/
`canExecute(value)` 탑레벨 함수로 확정, `Relate` 프리미티브(`base/relate-plan.md`) 위에 gcconn/
gchold를 얹는 구체 구현까지 나옴 — `base/lifecycle-pattern.md`의
"`bindLifetime`/`canBound`/`canExecute`/`unbindLifetime` — 확정" 절이 최신
(뒤의 둘이 `inst`를 안 받게 된 경위는
`archive/canexecute-inst-arg-reversed.md`). 아래는 이 결정이 나오기
전까지의 문제 서술(정확했던 문제 인식이라 그대로 둠, 남은 실측 항목은
`lifecycle-pattern.md` 쪽 "M0/M2 실측 필요" 캐비엇으로 이동).

**위치**: `base/lifecycle-pattern.md` "2026-08-04 검증 라운드에서 보강된
내용" 절, 특히 "`Destroying` 훅은 생각보다 덜 중요할 수 있음" 부분.

**문제**: base는 "이 바인드가 아직 유효한가"를 묻는 인터페이스만 정의하고
quad-roblox가 구현을 채워넣는다고 되어 있는데, 후보 구현 방식들이 서로
다른 타이밍/정확도 보장을 갖는다 — `Instance.Parent == nil` 체크(단순하지만
"일시적으로 부모 없이 옮기는 중"일 때 false positive 위험), 저장해둔
`RBXScriptConnection.Connected`(정확하지만 "무엇에 Connect한 Connection을
기준 삼을지" 별도 결정 필요), `Destroying:Connect`로 세운 플래그(문서가
스스로 "덜 중요할 수 있다"고 약화시킴). 그런데 이 predicate는 이미 Slot
생존 확인·Observer 게이팅·store-bind retract 등 코어 전역에 "canExecute
하나로 통일" 원칙으로 재사용 확정돼 있다 — 즉 여러 하위 시스템이 의존하는
핵심 predicate의 실제 정확도 보장이 아직 안 정해진 채로 그 위에 여러 기능이
이미 "확정"되어 쌓인 상태.

부가적으로, `framework-comparison-findings.md`는 "Vide는 GC와
`Instance.Destroying` 발화 순서가 비결정적이라는 알려진 함정 때문에 의도적
eager cleanup을 택했다"는 구체적 위험을 지적하며 "quad는 rbvm 실물 검증
근거로 이 리스크가 완화됐다"고 적었지만, `lifecycle-pattern.md` 본문
어디에도 **"Destroying 발화 순서 비결정성"이라는 그 구체적 함정에 대한
분석이 없다** — rbvm 검증은 "GC-native 정리가 프로덕션에서 잘 돌아간다"는
것만 보여줄 뿐, "발화 순서가 신뢰 가능한가"라는 별개 질문엔 답하지 않는다.
리스크가 "완화됐다"는 문장이 실제로는 근거 문서 안에서 뒷받침되지 않음.

**제안**: M0 스파이크(또는 M0 직후, M2/M3 착수 전)에서 실제로 어떤 구현이
오탐 없이 동작하는지(특히 Reparent-but-not-Destroy 케이스, 여러 자식이
동시에 Destroy될 때 부모/자식 `Destroying` 발화 순서) 먼저 실측하고, 그
결과로 `lifecycle-pattern.md`의 애매한 서술을 확정 문장으로 교체할 것.

### 1-7. Slot의 `add`/`remove`/`clear` CRUD 의미론이 정의돼 있지 않음

**[해소됨, 2026-08-09 세 번째 세션]** `base/slot-plan.md`의 "CRUD API
확정" 절에 `Add`/`Remove`/`Extract`/`Clear` 시그니처·에러 조건·재진입성까지
전부 확정 반영됨(`get`/`set`은 드롭). 아래는 당시 지적 원문, 참고용으로만 남김.

**위치**: `base/slot-plan.md` "개념" 절 — "`add`/`remove`/`clear`/`get`/
`set` 등 뮤터블 연산을 지원하는 메타 배열"이라고만 서술.

**문제**: 실제 시그니처/의미론이 전혀 없다. 예: `remove`는 인덱스를
받는지 값(참조)을 받는지, 존재하지 않는 값을 remove하면 no-op인지
에러인지, `set(i, v)`가 기존 위치의 element를 retract하고 교체하는지,
`clear()` 중간에 개별 element의 retract가 실패(에러)하면 나머지는 계속
처리되는지 등. Observer 콜백이나 store-bind 재실행 안에서 `add`/`clear`가
재진입적으로 호출될 가능성도 있는데, 그 경우의 동작도 무정의.

**제안**: M6(Slot) 착수 시점에 CRUD 각 연산의 인자/반환값/에러 조건을
최소한 표로 확정해둘 것 — 이미 알려진 "여러 Slot 순서 보장" 논의와 같은
타이밍에 같이 정리하면 됨.

### 1-8. Slot "재마운트 시 throw"가 두 가지 다른 추적 대상을 혼용해서 서술됨

**[해소됨, 2026-08-09 세 번째 세션]** `base/slot-plan.md`의 "`isMounted`
이중 추적 분리" 절에 Slot 컨테이너(`self._mounted`, dispatch-process 시점
트리거)와 개별 element(전역 weak-set)를 명시적으로 분리 반영됨. 아래는
당시 지적 원문, 참고용으로만 남김.

**위치**: `base/slot-plan.md` "핵심 제약: 소유권 귀속과 단일 마운트" +
"마운트된 Slot의 재마운트는 즉시 throw" 절.

**문제**: `isMounted`라는 용어가 두 가지 다른 대상에 쓰이는 것처럼 읽힌다
— (a) "한 인스턴스에 대한 다중 마운팅이 절대 일어나지 않도록 강제"는
**Slot에 담기는 개별 child element**가 두 곳에 동시 마운트되는 걸 막는
얘기, (b) "이미 사용된 slot을 재마운트하려 하면 즉시 error()"는 **Slot
컨테이너 자체**가 두 번째 바인드 지점에 쓰이는 걸 막는 얘기다. 둘은 서로
다른 추적 대상(개별 element vs Slot 객체)인데 문서는 하나의 "isMounted
관리"로 뭉뚱그린다. (b)의 트리거 시점도 미정 — `process(inst,k,slotValue)`
가 실제로 호출된 시점(핸들러 매칭)인지, Instance `Parent` 대입까지 끝난
시점인지에 따라 "컴포넌트가 Slot을 prop으로 받아 저장만 하고 실제로는
렌더하지 않는 경로"에서 오탐 throw가 날 수도, 반대로 진짜 이중 마운트를
놓칠 수도 있음.

**제안**: 두 추적을 명시적으로 분리(예: `Slot._mounted: boolean` vs
element별 weak-set) — throw 조건을 "Slot 핸들러의 `process`가 같은 Slot
객체에 대해 두 번째로 불렸을 때"로 명문화. M6 착수 시.

### 1-9. `LifetimeHandle` 인터페이스가 M8에 배치돼 있지만 M4/M6이 이미 그걸 필요로 함(로드맵 순서 역전)

**[2026-08-07 세 번째 세션 갱신 — 반영 완료.]** 아래 제안대로
`LifetimeHandle.luau`/`PerInstanceState.luau` 인터페이스가 `ROADMAP.md`
M2로 이동됐고, M8은 quad-roblox 실제 구현만 담당하도록 분리됨 — 더 이상
열린 항목 아님, 아래는 원래 발견 당시 기록.

**위치(당시)**: `ROADMAP.md` M8 "Ref" — `"LifetimeHandle 인터페이스 + quad-roblox
실제 구현(Instance 생존 확인)"`.

**문제**: `base/lifecycle-pattern.md`("생명 바인드 유틸"을 State-invalidate
리스너 클로저 등록에도 재사용)와 `base/slot-plan.md`(Slot의 `retract`가
같은 canExecute 패턴을 그대로 씀)는 둘 다 이 유틸을 State/Store 구독
(M3/M4 영역)과 Slot(M6)에서 이미 쓴다고 명시하는데, `LifetimeHandle`
인터페이스 자체는 M8에서야 정의된다. 즉 M4/M6이 개념적으로 필요로 하는
base 인터페이스가 그보다 늦은 M8에서 만들어지는 순서 역전.

**제안**: `LifetimeHandle.luau`(quad-base, 인터페이스만)를 M2(Dispatch
엔진) 또는 M3(Store/State)로 옮기고, M8은 "quad-roblox 실제 구현(Instance
`Connected` 기반)"만 담당하도록 분리. M1 mock에도 이 인터페이스의 트리비얼
스텁(항상 true)을 붙여두면 M4/M6 테스트가 자연스러워짐.

### 1-10. `store.key`의 레코드 필드 타이핑 검증이 M0가 아니라 M3로 밀려 있음 — [해소됨, 2026-08-12 열일곱 번째 세션]

**해소**: 걱정했던 "검증 난이도" 자체가 사라짐 — Luau `type function`
(`https://luau.org/types/type-functions/`)으로 `Store<T>`가 `T`의 각
필드를 `Source`로 감싼 레코드 타입을 실제로 합성 가능함을 확인(tbox에서도
이미 쓰이는 패턴). 결과가 구조적으로 `Source<T>`를 만족하기만 하면 Luau가
이름이 아니라 구조로 일치를 검사하므로 문제없음. 기술적으로 막힐 위험이
없어졌으니 M0/M3 어느 시점에 검증해도 무방 — `ROADMAP.md` 배치를 억지로
안 옮겨도 됨. 상세는 `base/typing-limits.md` "`store.key` 레코드 필드
타이핑" 절, 실제 문법 실측은 `luau-test/done/16-type-store-key-
typefunction.luau`(**[2026-08-15] 통과** — 원래 스파이크가 API 버전
드리프트로 깨져있던 걸 고침, `audit/type-recursive-issue-with-typeof/
REPORT.md` 6-1절). 아래는 원래 발견 당시 기록.

**위치**: `ROADMAP.md` M0 vs M3 `"store.key dot-access 타입 추론 확인"`.

**문제**: M0의 정의 자체가 "추론만으로 확정하고 실제 Luau로 부딪혀본 적
없는 것"을 검증하는 단계다. `base/source-state-plan.md`가 요청한 M0 항목(
"Source가 State를 만족하는 제네릭 메소드 체이닝"의 솔버 안정성)은 이미
반영됐지만, 이건 `:Compute` 같은 제네릭 메소드 체이닝만 다루고 `{key:
Source<number>}` 같은 **레코드 필드로서의 dot-access 타이핑**(읽기/쓰기
대칭성 논거의 핵심 전제)은 별개로 M3에 남아있다. 같은 리스크 카테고리인데
M1(스캐폴딩)·M2(디스패치 엔진) 투자가 먼저 이뤄진 뒤에야 검증되는 셈이라,
여기서 걸리면 이미 만든 스캐폴딩/디스패치 타입 시그니처를 다시 손봐야 할
수 있음.

**제안**: M0 항목에 "`store.key`가 실제로 `Source<T>` 레코드 필드로
안전하게 추론되는지"도 같이 넣을 것 — 어차피 같은 스파이크 파일에서 몇 줄
추가로 검증 가능.

### 1-11. Modifier의 "제네릭 `__index` + `table.clone` 메타테이블 보존" 트릭이 검증 안 된 채 M7 전체 설계의 전제가 됨 — [해소됨, 2026-08-12 열일곱 번째 세션]

**해소**: `table.clone`의 정확한 동작(새 테이블에 원본 키를 네이티브 슬롯
단위로 얕은 복사, 메타테이블은 복사가 아니라 `getmetatable`/`setmetatable`로
같은 참조를 공유)을 사용자가 직접 확인 — 제네릭 `__index` 함수가 원본과
clone 사이에서 물리적으로 동일 객체로 유지되므로 체이닝이 끊기지 않는다는
M7의 전제가 Luau 공식 동작대로 성립함. 별도로, 프로퍼티에 Attribute식
소유권 레지스트리를 적용하는 대안도 검토했으나 기각(엔진이 정한 유한
프로퍼티 이름 집합은 호출자가 전용 키를 못 만들어 소유권 판정이 원천적으로
불가능 — Property가 소유권 추적 대신 Modifier의 override 우선순위를 쓰는
이유). 상세는 `base/modifier-plan.md` "`table.clone`의 정확한 동작" 절과
"Property에 Attribute식 소유권 레지스트리 기각" 절, 실제 실행 확인은
`luau-test`의 `17-modifier-index-tableclone-chaining.luau`(신규).
아래는 원래 발견 당시 기록.

**위치**: `base/modifier-plan.md` "런타임은 클래스별 코드 없이 base에 딱
하나만 있으면 됨" 절, `ROADMAP.md` M7.

**문제**: M7의 핵심 주장("base에 제네릭 `__index` 하나면 충분, FrameModifier
류는 순전히 정적 타입 체크용")은 `mod:FontSize(14)` → `__index(self,
"FontSize")`가 즉석 클로저를 리턴하고, `table.clone`이 메타테이블을 그대로
복사해줘서 체이닝이 안 끊긴다는 두 가지 Luau 동작에 전적으로 의존한다.
문서 자체가 "핵심 통찰"이라 부르지만 실제 Luau 코드로 확인된 적은 없다.
이게 틀리면 M7에서 "클래스별 런타임 코드 불필요"라는 설계가 무너지고
필드별 정적 등록 방식으로 되돌아가야 하는데, M7은 M0~M6 다 끝난 뒤라
되돌릴 때 비용이 큼.

**제안**: 검증 비용이 낮음(Modifier 없이도 순수 메타테이블 실험 몇 줄로
가능) — M0 스파이크 후보에 추가하거나, 최소한 "M7 착수 시점에 제일 먼저
확인"이라고 `ROADMAP.md`에 명시.

---

## 우선순위 2 — 지금 결정해두면 싼 것 (지연 결정 리스크)

### 2-1. Source가 State를 만족하는 제네릭 검증이 실패했을 때의 대안(Plan B)이 전혀 없음

**[해소됨, 2026-08-13 첫 실측 라운드]** 검증 자체는
`08-type-source-satisfies-state.luau`(`luau-test/STATUS.md` 기준 `done/`)로 실행돼 핵심 질문(Source가
State를 구조적으로 만족)이 통과했음 — 아래 "검증이 실패했을 때"라는
전제 자체가 (핵심 케이스에 한해) 더 이상 미래형이 아님. 다만 통과와
별개로 좁은 잔여 케이스(`State<T>`가 자기 자신을 다른 타입 인자로
재귀 참조하는 경우, `Recursive type being used with different
parameters`)가 하나 발견됐고, **[2026-08-13 열세 번째 세션] 그건
Luau의 현 한계로 확정되어 `base/typing-limits.md` 1번이 담당**함
(구 `question.md` 0-Y는 해소 — quad가 설계로 풀 대상이 아님).
이건 "검증 실패 시 Plan B 없음"과는 다른 종류의 문제(전면 실패가
아니라 narrow edge case)라 아래 원래 제안(M0 스파이크에 폴백 한 줄
박아두기)은 더 이상 적용 대상 없음. 원래 서술은 배경 기록으로 남김:

**위치**: `base/source-state-plan.md` "Source가 State를 만족함" 절 —
"검증 필요(확정 아님, M0 스파이크 대상)... 다만 이것도 추론이라 실제
Luau로 확인 전엔 확정 아님."

**문제**: 검증 필요성 자체는 이미 M0 항목이라 새 지적 아니지만, **검증이
실패했을 때 뭘 하는지가 문서 어디에도 없다.** 이 타입 구조(Source⊂State
서브타입)는 `store.key`의 반환 타입, `:Set()` 문법, `:Emit()`의 위치,
dot-access 타입추론, `RefSource` 폐기 결정까지 전부 이 위에 얹혀 있어서,
Luau 솔버가 막히면 되돌릴 범위가 `source-state-plan.md`의 절반 이상에 걸침.

**제안**: M0 스파이크 계획에 "실패 시 폴백은 RefSource 부활 vs 다른 대안"
한 줄이라도 미리 박아두면, 실패했을 때 다시 사용자 자문을 구하느라 멈추는
걸 막을 수 있음.

### 2-2. `State<Modifier>` 타입 차단이 Luau에서 실제로 가능한지 검증 계획이 없음

**[대부분 해소, 2026-08-09 세션]** "가능하면 타입 차단, 안 되면 UB로
후퇴"라는 원래 걱정 자체가 무의미해짐 — `State<Modifier>`를 이제
`isModifier` predicate 기반 명시적 `error`로 막기로 확정
(`base/modifier-plan.md` 7번 정정, `base/source-state-plan.md` "따름정리"
절)했으므로, 타입 차단은 성공하든 실패하든 런타임 에러라는 안전망이
항상 있음 — 아래 "제안"이 우려했던 "조용히 UB로 후퇴" 시나리오 자체가
발생하지 않음. 타입 차단이 Luau에서 실제로 가능한지는 여전히 미검증이지만,
이제 "되면 좋은 보너스"로 우선순위가 낮아짐 — M0/M7 필수 검증 항목에서
제외해도 됨. 아래는 원래 발견 당시 기록.

**위치**: `base/modifier-plan.md` 7번.

**문제**: "가능하면 타입 시스템으로 아예 못 넣게 막을 것"이라 확정했지만,
Luau 제네릭은 "T가 특정 타입이면 거부"하는 부정 제약을 기본 지원하지
않는다. `source-state-plan.md`는 이보다 단순한 `Source<T> satisfies
State<T>` 조합조차 M0 스파이크 대상(솔버가 죽을 수 있음)으로 잡아뒀는데,
이보다 어려운 문제(제네릭 타입 파라미터 배제 제약)인 `State<Modifier>`
차단은 어디에도 검증 대상으로 언급되지 않는다. 실제로 안 되면 "UB,
가능하면 타입 차단"이 조용히 "그냥 UB, 런타임 가드 없음"으로 후퇴하는데
그 fallback도 안 적혀있음.

**제안**: `ROADMAP.md` M0(또는 M7 착수 시점)에 이 케이스를 포함하거나,
최소한 `modifier-plan.md`에 "타입 차단이 Luau에서 불가능하면 순수 UB로
폴백"이라는 명시적 fallback 문장을 추가할 것.

### 2-3. Component 래퍼 필요 여부가 "이름만 남음"으로 후순위 처리됐지만 구조적 결정일 가능성

**위치**: `base/component-composition-plan.md` "남은 열린 질문" —
"`Component`(플레인 함수 규약이라 별도 래퍼가 필요한지 자체도 불확실 —
아마 불필요)".

**문제**: 다른 순수 네이밍 항목들과 동급의 후순위로 묶여 있지만, 이건 이름
문제가 아니라 **구조 문제**일 수 있다 — 래퍼가 있고 없고에 따라 (a) 정적
타입 체크 지점(`props.Modifier`/`props.Ref` 필수 필드 검증을 어디서
강제할지), (b) `quad-debug`가 요구하는 컴파일타임 소스 위치 주입(darklua)
훅 지점이 "함수 정의부"가 되는지 "매 호출부"가 되는지가 갈린다. 나중에
"역시 얇은 래퍼가 필요하다"로 뒤집히면, 이미 "그냥 함수" 규약으로 짜인
기존 컴포넌트 전체를 마이그레이션해야 하는 연쇄가 발생.

**제안**: 루트 `ROADMAP.md`의 `quad-debug` 백로그 항목("M2/M3/M5 구현 시
훅 확장 지점만 고려해두면 이 항목 자체는 지금 착수 불필요")과
`question.md`의 같은 서술이 이미 그 시점을 지목해뒀으니, 그때 이 질문도
같이 열어 "래퍼 없음"이 구조적으로도 최종 확정인지 한 번 더 확인할 것.
M1 스캐폴딩 전에. (**[2026-08-16 인용 정정]** 원래 이 근거를 `CLAUDE.md`가
명시해뒀다고 적혀 있었으나 CLAUDE.md엔 그런 서술이 없었음 — 실제 소스는
위 두 곳. 다만 `research/debug-tooling-plan.md` 쪽 서술은 더 약한
"훅 확장 지점 존재 가능성만 인지해두는 정도"라는 점에 유의.)

### 2-4. existing-instance-bind가 Slot의 "엄격한 단일 마운트 소유권" 불변식과 근본적으로 긴장 — **[해소됨, 2026-08-14 세션: 기능 자체가 기각]**

> **[해소]** 이 긴장은 "재바인드를 어떻게 안전하게 할 것인가"를 풀어서가
> 아니라 **그 기능이 통째로 기각**되어 없어졌음
> (`archive/existing-instance-bind-rejected.md`) — 아래 분석은 그 기각
> 판단을 뒷받침한 근거 중 하나로 보존. Slot 소유권 모델에 "흡수(adopt)"
> 개념을 추가하는 방향도 같이 폐기.

**위치**: `archive/existing-instance-bind-rejected.md` 전체 vs `base/
slot-plan.md` "핵심 제약: 소유권 귀속과 단일 마운트".

**문제**: 문서가 스스로 언급한 긴장(Modifier flatten의 clone 비용)과는
별개로 더 근본적인 긴장이 있다. `retract`는 "이전에 자신이 process한 것을
무른다"는 전제(quad가 자신이 만든 Instance를 생명주기 끝까지 들고 있는
소유자)로 설계됐는데, existing-instance-bind는 정의상 **quad가 한 번도
process한 적 없는 인스턴스**에 처음 바인드하는 시나리오다. 특히 Slot을 이
인스턴스의 children 제어에 쓰려는 경우 — 기존에 손으로 만들어둔 자식들이
Slot의 "own"한 대상인지 아닌지가 완전히 미정. 문서는 "핸들러 레지스트리가
이미 우선순위 스캔 후 bind 구조라 재바인드도 같은 경로를 타면 됨"이라고
낙관하지만, 이건 "새 값을 process하는 법"만 있으면 된다는 얘기고 "이
인스턴스에 대해 quad가 이전에 뭘 소유했었는지 모르는 상태에서 안전하게
재바인드하는 법"은 다른 문제다. 나중에 "기존 children을 흡수(adopt)하는
API"를 얹어야 할 때 Slot의 "own한 것만 CRUD 대상" 불변식 자체를 건드려야
할 수 있음.

**당시 제안(이제 불필요)**: "이 기능이 실제로 필요해지면 Slot의
소유권 모델에 '흡수(adopt)' 개념을 추가해야 할 수도 있다"는 캐비엇을 그
문서에 병기해둘 것 — **2026-08-14 기각으로 대체됨**(캐비엇 대신 기각
사유가 `archive/existing-instance-bind-rejected.md` 상단 배너에 있음).

### 2-5. `Modifier.Overridden`(구 `Merge`) 시 서로 다른 클래스의 Modifier가 섞이는 게 허용되는지 — **런타임은 해소, 타입 레벨은 여전히 미정**

**위치**: `base/modifier-plan.md` "2. Merge 우선순위" + 9번(`Overridden`) +
`base/component-composition-plan.md` 3번.

**[2026-08-07 다섯 번째 세션 갱신] 런타임 동작은 이제 명확함**: `modifier-plan.md`
9번에서 `Overridden`가 "필드별 raw 덮어쓰기"로 확정됐고, "Modifier는 핸들러
계층을 모름 — 순수 데이터 merge 레이어"(1번 절) 원칙도 이미 있었으므로,
**런타임 레벨에서는 대상 클래스가 다른 Modifier끼리 `Overridden`해도 막을
이유가 없음**(필드명만 보고 그대로 덮어쓸 뿐 — Luau 타입은 런타임에
강제되지 않는다는 점은 `store-plan.md`에도 이미 명시된 전제).

**여전히 미정인 것 — 타입 레벨**: Modifier가 target 클래스별 제네릭
타입(`Modifier<Frame>` 등)이라면, `Modifier.Overridden<T>(mod1: Modifier<T>,
mod2: Modifier<T>): Modifier<T>`처럼 같은 `T`만 받도록 타입으로 강제할지,
아니면 공통 base 타입(여러 GuiObject 클래스에 걸친 공통 필드)과 클래스별
확장 사이의 계층 구조를 별도로 두고 `Overridden`가 그 계층을 넘나들 수
있게 할지는 아직 결정된 바 없음 — "공통 테마 Modifier + 클래스별 override
Modifier를 합친다"는 시나리오가 `Overridden`의 가장 그럴듯한 실사용
예시라 이 타입 설계가 실제로 막히면 바로 걸릴 문제.

**제안**: Modifier의 클래스별 typed 생성자 계층(2-8번과 같은 지점) 설계
시 `Overridden`의 제네릭 시그니처도 같이 확정할 것. M7 착수 시.

### 2-6. Modifier 필드에 State/Source를 인자로 넘기는 케이스가 세터 표에서 빠짐

**위치**: `base/modifier-plan.md` "4-1. 필드가 State일 수도 있음" 표.

**문제**: 표는 {필드: plain/State} × {인자: 리터럴/함수} 4칸만 다루는데,
"인자 자체가 State/Source"인 경우(예: `mod:FontSize(theme.fontSize)`처럼
이미 반응형인 값을 modifier 필드에 바인딩)가 없다. 상위에서 내려온 테마
색상을 Modifier 필드에 물리는 매우 흔한 패턴일 가능성이 높음. "리터럴"의
정의에 State 핸들도 포함되는지(포함된다면 필드가 State로 교체돼 반응형이
되는지) 불명.

**제안**: 표에 "인자=State" 행 2개(필드 plain/State 각각)를 추가해 명시할
것 — 아마 "clone 후 필드를 그 State로 교체(반응형 획득/전환)"가 자연스러운
답이지만, 함수 인자 케이스(`field:Compute(fn)`)와 어떻게 다른지 분명히 할
것.

### 2-7. 여러 Ref를 하나의 named parameter로 넘길 때 nested-array flatten 여부 불명

**위치**: `base/component-composition-plan.md` 3번(`Modifier.Overridden`,
구 `Merge`) vs "Ref는... 별도 결합 유틸 불필요" 문장.

**문제**: Modifier는 여러 개를 합치려면 `Modifier.Overridden`가 명시적으로
필요한데, 바로 다음 문장은 Ref는 "여러 Ref를 받으면 그냥 전부 실행하면
됨 — 별도 결합 유틸 불필요"라고 한다. `props.Ref = {ref1, ref2}`처럼
배열을 넘기면 리프 디스패처가 그 중첩 배열을 재귀적으로 펼쳐서 각 Ref를
인식한다는 뜻인지, 아니면 다중 Ref를 한 named parameter에 담아 넘기는
구체적 방법 자체가 여전히 안 정해진 것인지 불명확. 리프 레벨 디스패처가
배열 위치의 항목을 태그로 판별한다면, 항목 자체가 "배열"일 때 태그가
없어 인식 실패할 가능성이 있음.

**제안**: 리프 디스패처가 중첩 배열을 flatten하는지 명시적으로 확정하고,
다중 Ref를 넘기는 구체적 문법을 한 줄로 못박을 것. M8/M9 착수 시.

### 2-8. Modifier 클래스별 typed 생성자(`FrameModifier` 등)가 M5/M7 로드맵 어디에도 없음

**위치**: `base/modifier-plan.md` "5. 타입 출처는 이미 확정된 dot-access
관습 재사용" 절, `.claude/question.md` 1번.

**문제**: Modifier의 런타임 체이닝 엔진은 quad-base 소유가 맞지만, 클래스별
정적 타입 안전성(`mod:UICorner(8)`가 `FrameModifier` 타입으로 추론되는
것)은 "`D` 쪽 '제네릭 생성자 함수 하나 + 정적 별칭 필드' 패턴
재사용"이라 문서 스스로 밝히듯 quad-roblox의 `D` 타입 생성 계층(M5)에 강하게
결합돼 있다. 그런데 `ROADMAP.md` M7 체크리스트(flatten-before-dispatch,
`Modifier.Overridden`, `State<Modifier>` 차단)엔 이 클래스별 타입 생성 작업이
전혀 없고, M5 `D` 체크리스트에도 Modifier 언급이 없음.

**제안**: M5 또는 M7 체크리스트에 "quad-roblox 클래스별 typed Modifier
생성자(FrameModifier 등)" 항목을 명시적으로 추가해 누락을 막을 것.

### 2-9. 컴포넌트가 `props.Modifier`를 받아놓고 forward 안 하면 조용히 드롭됨 — 원칙 명문화 필요

**위치**: `base/component-composition-plan.md` "최종 결론" 1번, 3번.

**문제**: 저작자가 `props.Modifier`를 실수로(또는 의도적으로) 내부
`Frame{...}` 호출에 안 꽂아 넣으면 caller가 넘긴 modifier/ref는 조용히
사라진다. 타입 시그니처에 `props.Modifier: Modifier`가 선언돼 있어도
**실제로 그걸 쓰는지는 런타임/타입 어느 쪽도 강제 안 함** — "받았는데
안 쓰는" 실패 모드가 별도로 존재.

**제안**: 최소한 "무시되면 조용히 드롭된다(UB, 방어 로직 없음)"는 원칙을
명시적으로 못박을 것. 방어할 가치가 있다고 판단되면 컴파일타임 린트
(darklua) 후보로 `quad-debug`/문서화 백로그에 메모.

### 2-10. Tween 자연완료(Completed) 시 per-instance 북키핑 정리 여부가 명세 안 됨

**[해소됨, 2026-08-12 세션]** 결론은 "정리 안 해도 됨" — 자연완료 상태는
유저가 원한 목표값에 도달한 상태라 남은 북키핑이 부작용을 안 일으키고
(`Value`가 항상 lerp 가능한 프리미티브), Completed 이벤트 구독 같은 별도
정리 장치는 오버엔지니어링으로 판단해 안 만들기로 확정. 상세 근거는
`base/tween-plan.md`(승격됨) "자연 완료(Completed) 시 per-instance
북키핑 — 정리 안 해도 됨" 절. 아래는 원래 발견 당시 기록.

**위치**: `base/tween-plan.md` "override 정책" 절.

**문제**: "새 값이 들어와 갈아치울 때"의 `retract` 동작(4가지 옵션, 기본값
Cancel)은 상세히 정의했지만, **Tween이 사용자 개입 없이 스스로 끝까지
재생되어 자연 완료된 경우** per-instance weak-keyed 저장소에 남아있는
"이전 Tween 객체" 참조를 어떻게 다루는지는 언급이 없다. 남은 세 오버라이드
옵션(override-without-delete/delete-then-restart/move-to-end) 각각이
"이전 Tween이 아직 재생 중인가, 이미 끝났는가"에 따라 동작이 갈릴 수
있는데(예: "끝점으로 옮기고 새 트윈 시작"은 이미 완료된 Tween엔 의미가
이상해짐), 이 구분 로직 자체가 설계에 없음.

**제안**: Tween 핸들러가 생성한 Tween의 `Completed` 이벤트를 구독해
per-instance 저장소를 정리(또는 상태 플래그 갱신)하는지 여부를 명시.
M11 착수 시.

### 2-11. UI shorthand — 기존 UICorner 자식과의 매칭 기준(이름 vs 타입)이 불명, 사용자 실수 유발 위험 높음

**[해소됨 — 이 감사와 같은 세션(2026-08-06)에 이미 확정, 여기 표시만
누락돼 있었음, 2026-08-09 세션에 발견해 동기화.]** `base/ui-shorthand-plan.md`
"기존 자식과의 매칭 기준" 절이 정확히 아래 "제안"대로 확정해둠 —
재사용 대상은 quad가 이전에 만든 고정 이름(`_quad_corner`류) 자식으로
한정, 타입만 보고(`UICorner`이기만 하면 아무거나) 재사용하지 않음.
아래는 원래 발견 당시 기록.

**위치**: `base/ui-shorthand-plan.md` "v1 실제 메커니즘" 절.

**문제**: v1 메커니즘은 "기존 `UICorner` 자식이 있으면 재사용, 없으면
`Instance.new("UICorner", item)`(`Name = "_quad_round"`)"라고 서술되는데,
**이름으로 매칭**(quad가 이전에 만든 `_quad_round`만 재사용)인지 **타입으로
매칭**(자식 중 아무 `UICorner`나 있으면 재사용)인지 불명확. quad-v2 문서가
이 구분을 명시하지 않은 채 그대로 포팅 대상으로 재확정했다. 후자면
사용자가 직접 넣은(quad가 모르는) `UICorner`를 quad가 멋대로 바꿔버리는
부작용 경로가 생기고, 전자인데 사용자가 별도 이름으로 `UICorner`를 하나
더 넣으면 같은 GuiObject에 UICorner가 2개 존재하는 상태(Roblox에서 어느
쪽이 실제로 적용되는지 불명확)가 됨. 다른 항목들과 달리 이건 UB로 방치하기
엔 사용자가 실수하기 매우 쉬운 흔한 시나리오(디자이너가 UICorner를 수동
으로 넣어본 적 있는 프로젝트).

**제안**: "재사용 대상은 quad가 이전에 만든 고정 이름(`_quad_round`류)
자식으로 한정하고, 사용자가 별도로 만든 UICorner와는 아예 상호작용하지
않는다"는 규칙을 명시적으로 확정할 것. M10 전후 착수 시.

---

## 우선순위 3 — 단순화 후보 (사용자 판단 필요)

### 3-1. `:Compute(fn)`의 `previous` 인자 — 클로저 업밸류로 이미 되는 걸 별도 API로 만든 것일 수 있음

**[해소됨, 2026-08-09 세션]** 오버엔지니어링 아님으로 확정, 현재
`fn(self, previous)` 설계 그대로 유지 — 클로저 업밸류 대안은 IIFE로
감싸야 하는 준비 비용이 오히려 더 크다는 게 사용자 반박 논거.
`previous`는 `self`(입력)가 아니라 이 `:Compute` 호출 하나가 만든
결과 State 노드 자신에 귀속되므로 팬아웃 시에도 충돌 없음 — 상세는
`base/source-state-plan.md`의 "previous" 절 참고. 아래는 원래 발견
당시 기록.

**위치**: `base/source-state-plan.md` "`:Compute(fn)`의 선택적 두 번째
인자 — `previous`" 절.

**문제**: quad는 "렌더 함수가 계속 재실행되지 않고, `Compute`에 전달한
함수 자체가 한 번 등록되어 재계산마다 그 동일 클로저가 재호출된다"는
모델(문서 자체가 명시)다. 그렇다면 사용자가 `fn` 바깥에 `local prev`
업밸류를 두고 `fn` 안에서 그걸 읽고 갱신하면, 별도 `previous` 파라미터
없이도 정확히 같은 "직전 반환값 재사용" 효과를 순수 Lua 문법만으로 얻을
수 있어 보인다. 그런데 이 문서는 `previous`를 **별도 API 표면**(두 번째
인자)으로 만들었고, "능동적으로 계속 관측되지 않으면 조용히 영구
정지한다"는 상당히 위험한 캐비엇까지 별도로 문서화해야 할 만큼 무거운
기능이다. 왜 클로저 업밸류로 충분하지 않은지 근거가 안 보임.

**제안**: `previous` 인자를 유지할 근거(예: 업밸류 방식보다 타입 추론이
쉬워진다든가)가 있다면 한 줄 추가하고, 없다면 "그냥 클로저 업밸류를
쓰라"는 문서화 패턴으로 대체해 API 표면 자체를 줄이는 걸 검토.

### 3-2. UICorner/UIPadding/UIScale 개별 Handler 대신 데이터 테이블 구동 단일 제네릭 Handler

**위치**: `base/ui-shorthand-plan.md` "메커니즘 — 새 아키텍처 개념
불필요" 절.

**문제**: 문서는 "UICorner/UIPadding/UIScale 같은 특수 키를 인식하는
Handler"라고만 서술해, 사실상 3개의 거의 동일한 형태(리터럴 값 하나 →
고정 이름 자식 찾기/생성 → 프로퍼티 세팅)의 Handler를 각각 만드는
그림이다. 문서 자체가 "앞으로 비슷한 제안이 오면 이 선례를 따르라"고
일반화하고 있어, 향후 비슷한 shorthand가 추가될 때마다 Handler 파일이
선형으로 늘어나는 구조.

**제안**: `{key -> {ChildClassName, ChildDefaultName, Property, wrap=fn}}`
형태의 룩업 테이블 하나로 구동되는 단일 `Handlers/InstanceShorthand.luau`로
통합하는 안을 검토. 새 shorthand 키 추가가 "테이블에 항목 하나 추가"로
끝나 M10 이후 유지보수 비용이 줄어듦. 강제 사항 아님, 구현 시점에 결정할
정도의 사소한 개선 후보.

---

## 문서 모순 — 발견 현황

### 이미 고침

아래 첫 두 건은 이 감사 세션 자신이 정정했고, 세 번째는 나중 세션이
해소한 뒤 `session/2026-08-16-03-doc-check-section-convention.md`에서
"아직 안 고침"에 잘못 남아 있던 걸 발견해 여기로 옮긴 것이다.

- `architecture.md`의 "Store/State/Source 온톨로지 — 확정됨 (요약)" 절이
  `source-state-plan.md`의 최신 재구성(Source가 State를 만족, `store.key`가
  Source를 직접 반환, `store.key:Set()`)을 못 따라가고 있던 것 — 이 감사
  세션 도중 발견해 직접 정정(커밋 `4b839b0`에서 별도로 이미 반영됨을 뒤늦게
  확인 — 같은 문제를 두 세션이 독립적으로 발견한 셈).
- `architecture.md` 소스트리의 `Store.luau`/`Ref.luau` 주석이 각각 옛
  `__newindex` 모델, 옛 "Ref=CreatedRef 자체" 정의를 그대로 담고 있던 것 —
  이번 세션에서 직접 정정.

- **`State<Modifier>` 타입 차단(엔지니어링 비용 감수) vs Ref/Slot이
  Modifier 필드에 들어가는 건 UB로 방치 — 같은 문서 안에서 정반대 원칙이
  나란히 적용됨.** `base/modifier-plan.md`의 옛 `Modifier는 핸들러 계층을
  모름` 절은 "권장 사용법은 아니지만 막을 이유도 없음 — 방어 로직 없는
  UB로 남겨둠"이라 명시적으로 방어를 포기했는데, 바로 옆 7번 절은
  `State<Modifier>` 조합을 "UB로 확정, **가능하면 타입 시스템으로 아예 못
  넣게 막을 것**"이라며 정반대로 엔지니어링 비용을 들여 방어하기로 했다.
  두 결정 다 나름의 근거(후자는 "State에 담기면 재-flatten이 필요해져서
  정적 merge 전제와 정면 충돌"이라 더 위험하다는 논리로 보임)가 있어
  보이지만, 문서 어디에도 "왜 이 경우엔 원칙에서 예외로 처리하는가"를
  명시적으로 인정/정당화하지 않고 그냥 나란히 적혀 있다. 위 2-2 항목
  (Luau에서 실제 차단 가능한지)과 묶어서 같이 정리할 문제.

  **[완전 해소, 2026-08-09 세션]** 양쪽 다 이제 같은 메커니즘 —
  Ref/Slot(+Observer/Effect/Modifier 자기 자신)이 Modifier *필드*로
  들어오는 것도, `State<Modifier>`처럼 Modifier가 State/Source *값*으로
  담기는 것도 전부 `Brand` 기반 `isX` predicate로 런타임에 즉시
  `error`(`base/modifier-plan.md` 4번/7번 절 정정, `base/
  source-state-plan.md` "따름정리" 절). 더 이상 "한쪽만 방어" 비대칭이
  아님 — 남은 차이는 `State<Modifier>` 쪽에 "되면 좋은 보너스"로
  타입 차단을 추가 시도해볼 여지가 있다는 것뿐(위 2-2번, 미검증이지만
  더 이상 필수 방어선이 아니라 우선순위 낮음).

### 아직 안 고침 (판단 필요해서 여기 남김)

- **Destroying 훅 신뢰도에 대한 서술이 `lifecycle-pattern.md` 내부에서도,
  `framework-comparison-findings.md`와의 사이에서도 어긋남** — 위 1-6
  항목에 상세, 여기서는 "아직 아무도 하나의 확정 문장으로 정리 안 함"이라는
  사실만 문서모순 항목으로 남겨둠.

---

## 참고 — 감사했지만 문제없다고 확인된 것

- `component-composition-plan.md`가 예전 `StoreSource`/`RefSource`
  개념을 참조하는 채로 남아있진 않은지 확인 — 이미 "Source가 State를
  만족함" 최신 모델로 정정돼 있어 문제없음.
- `ROADMAP.md`에 `store.key = value`(구 `__newindex`) 모델을 암시하는
  잔여 표현은 없음 — M3/M4 서술 모두 문법을 명시하지 않아 최신 `:Set()`
  모델과 직접 충돌하는 곳은 없음.
- M9(컴포넌트 합성)이 M7(Modifier)·M8(Ref) 뒤에 오는 순서 — M9는 "M0
  스파이크(named-parameter 전달)를 정식 Modifier/Ref로 검증"하는 단계라고
  명시돼 있어 뒤늦은 검증이 아니라 의도된 정식화.
- `Relate`(구 `PerInstanceState`) 실제 구현 시점(M8) — 이걸 필요로 하는
  핸들러(Tag/Attribute/Tween)가 전부 M10/M11이라 순서상 문제없음.
- Slot의 store-bind 의존(M6→M4) 순서.

---

## 다음 액션 제안

**[2026-08-12 열일곱 번째 세션 갱신] 우선순위1 11개 전부 해소됨** — 1-3/
1-4/1-10/1-11이 이 세션에서 마저 확정되어(위 각 항목 참고), 더 이상 M0
착수 전에 열려있는 우선순위1 항목이 없음. 아래는 남은 실측/검증 항목만
정리(전부 "설계는 확정, 실제 Luau/구현으로 부딪혀볼 것만 남음" 상태):

- **M2(Dispatch) 착수 시**: 1-2/1-3/1-4는 설계 확정 완료 — M2 스파이크에서
  다단 체인 케이스, 동률 감지 디버그 print, 매치 실패 에러 경로가 실제로
  맞게 동작하는지 실측.
- **M2/M3 착수 전**: 1-6(canExecute 실제 구현) 실측(1-9는 반영 완료, 위
  항목 참고).
- **M0 스파이크**: 1-10(`store.key` type function)과 1-11(`table.clone`
  메타테이블 공유)은 메커니즘 자체는 이제 확실하지만, 실제 Luau 코드로
  돌려보는 것 자체는 여전히 스파이크 대상 — `luau-test`의
  `16-type-store-key-typefunction.luau`/`17-modifier-index-tableclone-chaining.luau`로
  신규 추가됨(2026-08-12 열일곱 번째 세션 핸드오버 점검 중 발견된 갭,
  이전까진 이 두 항목을 커버하는 스파이크 파일 자체가 없었음).
  **[2026-08-13 첫 실측 라운드 결과]** `17`은 **통과**(제네릭 `__index` +
  `table.clone` 체이닝, 메타테이블 참조 공유, 형제 분기 무오염) — 다만
  최초 실행은 크래시했고 그 원인이 실은 **문서 결함**이었음(`modifier-plan.md`의
  `내부 구현` 절 본문에 있던
  "데이터를 테이블에 직접 두고"가 self 최상위 리터럴 키로 읽히면 `__index`가
  `rawget` 성공 시 안 불려 두 번째 호출에서 죽음) — 문서 수정 후 통과.
  `16`은 **실패** — `types.newfunction` 시그니처가 설치된 Luau 버전의 실제
  API와 안 맞음(문서가 스스로 예비해둔 케이스), 실제 API 재확인 후 재시도
  필요. 상세는 `audit/luau-test-first-run-2026-08-13.md`, 상태판은
  `luau-test/STATUS.md`.
- **나머지**: 해당 마일스톤 착수 시점에 이 문서를 다시 열어 관련 항목만
  확인하면 됨 — 지금 전부 결정할 필요는 없음.
