decide(dispatch,modifier,store): pre-implementation-audit 우선순위1 마지막 넷 해소, 누락 스파이크 보강

사용자가 우선순위 동률/매치실패(1-3), provider 미주입(1-4), store.key
type function 타이핑(1-10), Modifier __index+table.clone 트릭(1-11)에
대해 구체적 결정을 제시 - 전부 base/ 문서에 반영해 우선순위1 11개 전원
해소. 핸드오버 점검 중 1-10/1-11이 설계 레벨로만 확인됐고 실제 Luau
스파이크 파일이 없었던 갭을 발견해 16/17 신규 추가, ROADMAP.md M2/M3/M7
체크리스트 누락분도 보강.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVG74qV2nQVykhvMRQW2UC
This commit is contained in:
qwreey 2026-08-12 20:24:04 +09:00
parent 2df591851f
commit 4c5ee1b66a
Signed by: qwreey
GPG key ID: D28DB79297A214BD
11 changed files with 493 additions and 37 deletions

View file

@ -63,6 +63,41 @@ v1의 `ProcessQuadProperty`(`.claude/initreq/quad/src/class.lua:134-214`)는
src/schema/union.luau:48-68`) — 에러 메시지는 즉시 문자열로 만들지 말고 매치
실패 시에만 클로저 호출.
**우선순위 동률/매치 실패 처리 — 확정(2026-08-12 열일곱 번째 세션,
`pre-implementation-audit.md` 1-3/1-4 해소).**
- **동률(같은 `priority` 값)에 대한 tiebreak 규칙은 강제하지 않는다.**
"등록 순서가 이긴다" 같은 규칙을 강제하면 `NoneHandler`/`StoreBind`처럼
이미 서로 `isHandlable`이 안 겹치는 내장 핸들러에까지 전부 그 규칙을
지켜가며 순서를 신경 써야 하고, 나중에 서드파티 핸들러가 늘어나면 더
골치아파짐(사용자 판단). 대신 **목적별로 이름 붙은 우선순위 상수**
(`HANDLER_PRIORITY_HIGH`/`HANDLER_PRIORITY_NORMAL`/`HANDLER_PRIORITY_LOW`
등, 여전히 열린 숫자 공간 위의 편의 상수라 `HANDLER_PRIORITY_HIGH + 1`처럼
세밀 조정도 가능)를 제공해 애초에 동률이 잘 안 나오게 유도 — "우선순위
밴드 + 오프셋"은 여러 업계에서 이미 흔한 패턴. 실제로 동률이 나면 그건
대개 핸들러 설계 실수라, 강제 규칙보다 아래 디버그 가시성으로 대응하는
쪽이 맞음.
- **매치 실패(`isHandlable`을 만족하는 핸들러가 하나도 없음)는 조용한
무시 없이 즉시 `error`.** 에러 메시지엔 값의 `Brand`(있으면)와
`typeof(v)`를 함께 출력하고, "quad-roblox 등 필요한 provider가
초기화됐는지 확인하라"는 안내만 덧붙임 — 그 이상의 특수 분기는 두지
않음(다른 라이브러리에서도 흔한 "매치 실패=에러" 패턴 그대로).
**이걸로 `module-lifecycle-plan.md`의 "provider가 아직 주입 안 된
상태에서 dispatch가 호출되면?" 케이스(`pre-implementation-audit.md`
1-4)도 별도 분기 없이 자동으로 해소됨** — provider 미주입 상태는
결국 그 클래스를 다루는 핸들러가 레지스트리에 하나도 없는 상태이므로
"매치 실패"와 정확히 같은 경로로 수렴함. 오타 키/미지원 조합/provider
미주입을 서로 다른 에러 종류로 구분할 필요가 없음.
- **디버그 모드 — 핸들러 등록/정렬 시점에 동률 감지 시 print 경고 +
전체 핸들러 목록 조회 함수.** 우선순위는 핸들러 등록 시점에 정적으로
sort되므로 동률 감지 자체는 그 시점에 공짜로 가능 — `priority`가 같은
두 핸들러가 등록되면 콘솔에 경고를 찍고, `Dispatch.listHandlers()`
함수로 현재 등록된 전체 핸들러(이름/priority)를 덤프할 수 있게 함.
구현 비용이 거의 없고 실제 개발 중 디버깅에 바로 도움되는 항목이라
M2(Dispatch 엔진) 착수 시 기본 기능으로 같이 넣음 — 런타임 플러그인인
`quad-debug`(후순위, `research/debug-tooling-plan.md`)와는 다른 층위의,
라이브러리 자체에 내장된 개발자 편의 기능.
## 확정된 디스패치 모델: `process(inst, k, v)` / `retract(inst, k, v)`
**사용자가 직접 준 구체적인 모델 — 이 문서의 이전 초안보다 우선함.** 아래가
@ -2258,6 +2293,45 @@ Modifier처럼 플래튼하지 않는가"는 설계 근거를 알고 싶은 사
가드를 넣어 재설계하는 대신, State 자체가 파이핑 결합체이고
`state(state)`로 분기하는 위 모델로 완전히 대체됨.
**`store.key` 레코드 필드 타이핑 — Luau 타입함수로 해결 확인
(2026-08-12 열일곱 번째 세션, `pre-implementation-audit.md` 1-10 해소).**
위 "타입 추론 문제" 절이 "`store.key`를 평범한 레코드 필드 타이핑으로 자동
해결"이라 서술했지만, `Store<T>`가 입력 `T`(예: `{ty: string}`)를 받아
`{ty: Source<string>}`류 결과 타입을 실제로 어떻게 합성하는지는 미검증으로
남아있었음. **Luau의 `type function`**(컴파일타임에 타입을 인자로 받아 새
타입을 조립하는 기능, https://luau.org/types/type-functions/ ,
https://luau.org/types-library/ — tbox에서도 이미 쓰이는 검증된 패턴)으로
정확히 풀림:
```luau
type function WrapStore(ty: type): type
-- Source<T> 형태를 그대로 조립(:Get/:Set/:Compute/:With 등)
local result = types.newtable()
result:setproperty(types.singleton("Get"), types.newfunction(...))
return result
end
type function ProcessStoreType(ty: type): type
local props = ty:properties() :: { [type]: { read: type?, write: type? } }
local result = types.newtable()
for i, v in props do
-- i는 프로퍼티 이름을 담은 singleton 타입, i:value()로 실제 문자열
result:setproperty(i, WrapStore(v))
end
return result
end
```
`ProcessStoreType<{ty: string}>``{ty: Source<string>}`가 나옴 — 결과는
선언 시점에 이름 붙은 `Source<string>` 그 자체가 아니라 구조를 그대로 풀어낸
(flatten) 익명 타입이지만, **Luau는 이름이 아니라 "만족하는가"로 구조적
일치를 검사**하므로 문제없이 `Source<string>` 자리에 대입 가능 — 오히려 이
방식과 정확히 맞는 조합. 이걸로 `store.key`가 실제로 타입 명시 가능함이
확인돼 M0/M3 어느 시점에 검증해도 기술적으로 막힐 위험은 없음 —
`ROADMAP.md`의 M0/M3 배치를 강제로 바꿀 필요는 없어짐, 검증 난이도
문제였던 것만 해소.
**PA님 코드와의 교차검증(2026-08-04 4차 라운드) — 둘 다 기존 확정 유지**
`.claude/initreq/artworks/EventDrivenProgramming/`(Connection/Event/

View file

@ -45,6 +45,21 @@ cascade 문제가 그대로 오는데, 이건 이미 확정된 "Store 바인드
참고 — 여기서 다루는 건 컴포지션 타임의 modifier 값 자체 flatten이라 층위가
다름.
**참고 — Property(일반 프로퍼티)에 Attribute식 "이름 소유권 레지스트리"를
적용하는 안은 검토 후 기각(2026-08-12 열일곱 번째 세션).** `Attribute`
(`base/attribute-plan.md`)의 그룹 위임은 이름별 전용 키 객체가 "지금 이
이름을 누가 쓰고 있는가"를 스스로 추적해 충돌을 `error`로 잡아주는데, 이
패턴을 일반 Instance 프로퍼티(`BackgroundColor3` 등)에도 그대로 적용할 수
있을지 검토했으나 기각됨 — **Attribute 이름은 호출자가 자유롭게 짓는
네임스페이스라 자기 전용 키 객체를 새로 만들 수 있지만, Instance 프로퍼티
이름은 엔진이 이미 정해둔 유한 집합이라 호출자가 자기만의 전용 키를 새로
못 만든다.** 그래서 "이 프로퍼티를 지금 누가 소유하고 있는가"라는 질문
자체가 원천적으로 성립하지 않음 — 여러 modifier/컴포넌트가 같은 프로퍼티를
건드리는 게 오히려 흔한 정상 시나리오(테마 오버라이드 등)이기도 함. 이게
Property가 소유권 추적 대신 **덮어쓰기 우선순위(위 "2. Merge 우선순위"
절)**로 처리되는 이유 — 새로 결정한 게 아니라 이미 확정된 설계가 왜
그 모양인지를 명문화한 것.
### 2. Merge 우선순위: 배열 순서와 인라인은 독립된 두 규칙
`Frame { modifier1, modifier2, Name = ... }` 평탄화 시:
@ -167,6 +182,19 @@ Compute를 합치고 싶다"는 동기였는데, 이미 두 가지로 다 커버
메타테이블까지 그대로 복사해주는 Luau 동작 덕분에 클론해도 체이닝이 안
끊김. flatten도 그 테이블 필드를 직접 읽으면 됨.
**`table.clone`의 정확한 동작 — 확인됨(2026-08-12 열일곱 번째 세션,
`pre-implementation-audit.md` 1-11 해소).** 새 빈 테이블을 만들고 원본의
키를 네이티브 슬롯 단위로 복사(얕은 복사, 값 자체는 안 파고듦)한 뒤,
원본의 `getmetatable` 결과를 그대로 그 새 테이블에 `setmetatable` — **이
메타테이블은 복사되는 게 아니라 같은 참조를 공유**함. 그래서 위 제네릭
`__index` 함수(메타테이블에 딱 하나 있는 그 함수 자체)는 원본과 clone
사이에서 물리적으로 동일 객체이고, `mod:FontSize(14)``__index`가 리턴한
클로저가 `table.clone(self)`로 새 테이블을 또 만들 때도 그 새 테이블 역시
같은 메타테이블을 공유하며 체이닝이 끊기지 않음 — M7 전체 설계("클래스별
런타임 코드 없이 base에 제네릭 `__index` 하나면 충분")가 의존하던 두 Luau
동작(제네릭 `__index`가 임의 key를 잡아 클로저를 즉석 생성하는 것,
`table.clone`이 메타테이블 참조를 보존하는 것) 모두 공식 동작대로 확인됨.
**런타임은 클래스별 코드 없이 base에 딱 하나만 있으면 됨(2026-08-06 후속
세션, 핵심 통찰).** `mod:FontSize(14)``mod.FontSize(mod, 14)`로 풀리는
문법 설탕이고, `mod.FontSize``FontSize`가 리터럴 키로 안 박혀있으니

View file

@ -119,6 +119,12 @@ init하려 하면 오류, 없는데 뭔가 생성해서 bind하려 해도 오류
처리한다"는 의미가 빠져 있음 — `Handler`가 계약 4종
(`isHandlable`/`priority`/`process`/`retract`) 전체를 가장 정확히
담는다는 결론.
- **[해소됨, 2026-08-12 열일곱 번째 세션]** provider(팩토리)가 아직 한
번도 실행 안 된 상태에서 dispatch가 호출되면 어떻게 되는지
(`pre-implementation-audit.md` 1-4) — 별도 케이스로 처리하지 않음.
provider 미주입 상태는 결국 그 클래스의 핸들러가 레지스트리에 하나도
없는 상태이므로, `base/bind-system-plan.md` "우선순위 동률/매치 실패
처리" 절의 일반 "매치 실패 시 즉시 error" 규칙 하나로 자연히 커버됨.
- base 유틸(per-instance 상태 저장소, 생명 바인드 유틸)이 인터페이스만 두고
실제 구현은 백엔드 팩토리(`RobloxFactory(BaseModule)`류)가 뮤테이션으로
주입한다는 패턴이 확정됨 — 상세는 `base/bind-system-plan.md`의 "base

View file

@ -0,0 +1,67 @@
--!strict
--[[
검증 대상: `Store<T>`가 레코드 타입 T의 각 필드를 Source로 감싼 타입을
Luau `type function`으로 실제로 합성할 수 있는지 — 실제 실행이 아니라
타입 체크(luau-analyze/luau-lsp) 대상.
배경: .claude/base/bind-system-plan.md "`store.key` 레코드 필드 타이핑"
절(2026-08-12 열일곱 번째 세션) — 사용자가 제시한 WrapStore/
ProcessStoreType 스케치를 그대로 옮김. `research/pre-implementation-audit.md`
1-10 해소 근거.
핵심 우려: (a) `type function` 문법 자체(`types.newtable`/`:setproperty`/
`types.singleton`/`ty:properties()`/`types.newfunction`)가 실제 설치된
Luau 버전에서 정확히 이 API 모양인지 — type function은 비교적 최근/
계속 진화 중인 기능이라 버전에 따라 세부 이름이 다를 수 있음
(https://luau.org/types/type-functions/ , https://luau.org/types-library/
최신 문서 기준으로 맞춰볼 것 — 아래는 사용자가 손으로 쓴 스케치를 최대한
그대로 옮긴 것이라 API 이름이 실제와 다를 수 있음, 그 자체가 이
스파이크의 목적). (b) 결과 타입 `ProcessStoreType<{ty: string}>`이
실제로 `{ty: Source<string>}`을 "만족"해서(이름이 아니라 구조로)
`Source<string>` 필드가 필요한 자리에 그대로 대입 가능한지.
실행: `luau-analyze 16-type-store-key-typefunction.luau` 또는
`luau-lsp analyze` — 문법/API 에러가 나면 정확한 에러 메시지(어느 줄,
어떤 API 호출)를 그대로 알려줄 것 — 그게 다음 시도에서 API 이름을
고치는 데 바로 쓰임. 이 파일 자체가 한 번에 통과할 걸 기대하기보다,
"type function으로 이 방향 자체가 막히는지 아닌지"를 확인하는 게 목적.
기대 결과: 맨 아래 `checkTy`/`checkCount` 대입이 타입 에러 없이 통과
(= `Processed.ty`가 구조적으로 `Source<string>`을 만족).
]]
export type Source<T> = {
Get: (self: Source<T>) -> T,
Set: (self: Source<T>, value: T) -> (),
}
type function WrapStore(ty: type): type
-- Source<T> 형태를 그대로 조립 — 실사용에선 :Set/:Compute/:With 등도
-- 같은 방식으로 추가해야 하지만, 이 스파이크는 "필드가 Source로 감싸
-- 지는가"라는 구조적 만족 여부만 보면 되므로 :Get만 붙임
local result = types.newtable()
result:setproperty(types.singleton("Get"), types.newfunction({}, { ty }))
return result
end
type function ProcessStoreType(ty: type): type
local props = ty:properties() :: { [type]: { read: type?, write: type? } }
local result = types.newtable()
for i, v in props do
-- i는 프로퍼티 이름을 담은 singleton 타입, i:value()로 실제 문자열
local fieldType = v.read or v.write
if fieldType then
result:setproperty(i, WrapStore(fieldType))
end
end
return result
end
type Input = { ty: string, count: number }
type Processed = ProcessStoreType<Input>
local processed: Processed = nil :: any
-- 검증 포인트: Processed의 각 필드가 실제로 Source<T> 구조를 만족하는지
local checkTy: string = processed.ty:Get()
local checkCount: number = processed.count:Get()

View file

@ -0,0 +1,70 @@
--[[
검증 대상: Modifier의 "제네릭 __index + table.clone" 트릭 — 클래스별
런타임 코드 없이 하나의 제네릭 __index만으로 임의 필드 setter 체이닝이
실제로 동작하는지, 그리고 table.clone이 매 단계 메타테이블을 복사가
아니라 참조로 유지해 체이닝이 안 끊기는지.
배경: .claude/base/modifier-plan.md "런타임은 클래스별 코드 없이 base에
딱 하나만 있으면 됨" 절 + "`table.clone`의 정확한 동작 — 확인됨"
절(2026-08-12 열일곱 번째 세션) — 사용자가 설명한 메커니즘(원본 키를
네이티브 슬롯 단위로 얕은 복사 + 메타테이블은 복사가 아니라
getmetatable/setmetatable로 참조 공유)을 실제 코드로 재현.
`research/pre-implementation-audit.md` 1-11 해소 근거.
실행: `luau 17-modifier-index-tableclone-chaining.luau`
기대 결과: 아래 모든 assert가 통과 — 특히 (B) 메타테이블이 매 clone마다
원본과 물리적으로 동일 객체인지(참조 공유 확인), (C) 원본이 각 체이닝
단계에서 전혀 mutate 안 됐는지(immutable 보장), (D) 서로 다른 체이닝
경로(형제 분기)가 서로 오염 안 시키는지가 핵심.
]]
local function genericIndex(_, key)
return function(selfArg, arg)
local clone = table.clone(selfArg)
if type(arg) == "function" then
clone[key] = arg(selfArg[key])
else
clone[key] = arg
end
return clone
end
end
local ModifierMT = { __index = genericIndex }
local function Modifier()
return setmetatable({}, ModifierMT)
end
-- (A) 필드가 하나도 미리 등록 안 돼 있어도 임의 메소드 이름으로 체이닝되는지
local mod0 = Modifier()
local mod1 = mod0:FontSize(14)
local mod2 = mod1:Round(8)
local mod3 = mod2:FontSize(function(old)
return old * 1.2
end)
assert(mod1.FontSize == 14, "mod1.FontSize expected 14")
assert(mod2.FontSize == 14 and mod2.Round == 8, "mod2 expected FontSize=14, Round=8")
assert(mod3.FontSize == 14 * 1.2, "mod3 expected FontSize=16.8 (14*1.2)")
-- (B) getmetatable이 매 clone마다 원본과 physically 동일 객체인지
-- (table.clone이 메타테이블을 복사가 아니라 참조로 공유한다는 주장의 핵심 검증)
assert(getmetatable(mod0) == ModifierMT, "mod0 metatable should be ModifierMT")
assert(getmetatable(mod1) == getmetatable(mod0), "mod1 metatable should be same reference as mod0's")
assert(getmetatable(mod2) == getmetatable(mod0), "mod2 metatable should be same reference as mod0's")
assert(getmetatable(mod3) == getmetatable(mod0), "mod3 metatable should be same reference as mod0's")
-- (C) 원본은 절대 mutate 안 됨(immutable 체이닝 보장)
assert(mod0.FontSize == nil, "mod0 must stay untouched (FontSize)")
assert(mod0.Round == nil, "mod0 must stay untouched (Round)")
assert(mod1.Round == nil, "mod1 must stay untouched (Round) -- clone은 앞 단계까지만 반영")
-- (D) 서로 다른 체이닝 경로(형제 분기)가 서로 오염 안 시키는지
local branchA = mod1:Round(4)
local branchB = mod1:Round(100)
assert(branchA.Round == 4 and branchB.Round == 100, "sibling branches must not cross-contaminate")
assert(mod1.Round == nil, "mod1 itself must stay untouched by either branch")
print("17-modifier-index-tableclone-chaining: all checks passed")

View file

@ -23,10 +23,15 @@ ROADMAP 항목 근거인지, 어떻게 실행하는지, 실행 후 뭘 확인해
| 환경 | 필요한 것 | 해당 파일 |
|---|---|---|
| **순수 Luau CLI** (`luau`) | [luau-lang/luau 릴리즈](https://github.com/luau-lang/luau/releases)의 `luau` 인터프리터, 또는 `lune` | 01, 02, 03, 04, 05, 06(런타임 부분), 07, 11, 13(런타임 부분) |
| **Luau 타입체커** (`luau-analyze` 또는 `luau-lsp`) | 같은 릴리즈에 포함된 `luau-analyze`, 또는 `luau-lsp analyze`/에디터 인라인 진단 | 06(타입 부분), 08, 09, 12, 13(타입 부분), 14, 15 |
| **순수 Luau CLI** (`luau`) | [luau-lang/luau 릴리즈](https://github.com/luau-lang/luau/releases)의 `luau` 인터프리터, 또는 `lune` | 01, 02, 03, 04, 05, 06(런타임 부분), 07, 11, 13(런타임 부분), 17 |
| **Luau 타입체커** (`luau-analyze` 또는 `luau-lsp`) | 같은 릴리즈에 포함된 `luau-analyze`, 또는 `luau-lsp analyze`/에디터 인라인 진단 | 06(타입 부분), 08, 09, 12, 13(타입 부분), 14, 15, 16 |
| **Roblox Studio** | 별도 계정으로 로그인(`HUMAN_TODO.md` 1번, `SAFETY.md` 준수) | 10 |
**16은 특히 `type function`이라는 비교적 최근/계속 진화 중인 Luau 기능을
쓰므로, luau-analyze 버전이 오래되면 아예 문법 자체를 못 알아볼 수 있음**
— 그 경우는 실패가 아니라 "이 Luau 버전에서 type function 자체가 아직
지원 안 됨"이라는 별개의 신호이니 버전 정보와 함께 알려줄 것.
**12/13/14는 특히 `luau-lsp`로 확인해달라고 요청받은 것들** — `luau-analyze`
같은 타입 솔버를 쓰므로 원리적으로는 같은 결과가 나와야 하지만, `luau-lsp`
에디터에서 인라인으로 에러 위치/메시지를 보여줘서 "정확히 어느 표현식이
@ -56,6 +61,8 @@ ROADMAP 항목 근거인지, 어떻게 실행하는지, 실행 후 뭘 확인해
| `13-type-ref-preref-subtype.luau` | (A, 타입) `PreRef<T>``Ref<T>`를 구조적으로 만족하는지, (B, 런타임) `isRef`/`isPreRef` 합성이 재정정대로 동작하는지(`isRef(preRefInstance)`가 이제 `true`) + Leaf 핸들러가 `isRef(v) and not isPreRef(v)`로 명시적으로 좁혀야 하는 이유 | `bind-system-plan.md``Brand` 절(2026-08-09 열한 번째 세션 재정정) |
| `14-type-nilable-default-overload.luau` (타입체크 전용) | `Source(default)`/`Ref(default)`의 `default` 생략이 `T`가 nilable일 때만 안전하다는 캐비엇을, 함수 오버로드(교차 타입)로 실제로 타입 레벨에서 막을 수 있는지 | `bind-system-plan.md` "[보강, 2026-08-09 열한 번째 세션]" 절 |
| `15-type-compute-trailing-deps-typepack.luau` (타입체크 전용) | `:Compute(fn, ...)`의 trailing deps를 `fn`에 위치 인자(lazy State 핸들)로도 노출하는 확장, 최종 시그니처 `fn(self, previous?, ...deps)` — 이형(heterogeneous) 다중 deps를 제네릭 타입 팩(`U...`)으로 표현 가능한지, `previous?`가 팩 앞(정정된 순서)에서만 통과하고 팩 뒤(옛 순서)에서는 막히는지 | `bind-system-plan.md` "trailing deps를 fn에 lazy positional 인자로도 노출" 절(2026-08-11 후속 세션, 순서는 같은 날 세 번째 세션에 정정) |
| `16-type-store-key-typefunction.luau` (타입체크 전용) | `Store<T>``T`의 각 필드를 `Source`로 감싼 타입을 Luau `type function`(`types.newtable`/`:setproperty`/`ty:properties()`)으로 실제 합성 가능한지, 결과가 구조적으로 `Source<T>` 필드를 만족하는지 | `bind-system-plan.md` "`store.key` 레코드 필드 타이핑" 절(2026-08-12 열일곱 번째 세션), `pre-implementation-audit.md` 1-10 |
| `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 |
## 갱신 이력
@ -101,6 +108,13 @@ deps의 제네릭 타입 팩 표현 가능 여부와 `previous?`를 팩 뒤에
검증하는 (D) 양성 대조군을 신규 추가 — 이제 진짜 불확실성은 (B)
이형 다중 deps의 제네릭 팩 표현 가능 여부 하나뿐.
**6차 (2026-08-12, 열일곱 번째 세션, `16`/`17` 신규)**: `pre-implementation-audit.md`
우선순위1의 마지막 두 항목(1-10 `store.key` 타이핑, 1-11 Modifier
`__index`+`table.clone` 트릭)이 이 세션에 설계 레벨로는 해소됐지만, 실제
Luau로 부딪혀본 적은 없다는 걸 핸드오버 점검 중 발견 — `16`(type function
스케치, 타입체크 전용)/`17`(제네릭 __index 체이닝, 런타임)로 신규 추가.
둘 다 이전까지 이 폴더 어디에도 커버 대상이 없던 완전히 새 항목.
## 결과 확인 후 할 일
각 파일 결과를 알려주면, 실제로 걸리는 부분이 있는지 보고 필요하면
@ -130,3 +144,12 @@ deps의 제네릭 타입 팩 표현 가능 여부와 `previous?`를 팩 뒤에
- `13`은 A(타입)/B(런타임) 둘 다 확인해줄 것 — B의 assert가 실패하면
`Dispatch/Leaf.luau` 설계(`isRef(v) and not isPreRef(v)`) 자체가
잘못 짜인 것이니 우선순위 높게 알려줄 것.
- `16`**어느 쪽으로 나와도 유용** — 통과하면 `store.key` 타이핑을 이
type function 방식으로 그대로 채택, API 이름이 틀려서 막히면(`type
function`이 비교적 새 기능이라 가능성 높음) 정확한 에러 메시지를 그대로
알려줄 것 — 다음 시도의 API 이름을 고치는 데 바로 쓰임. 아예 이 Luau
버전이 `type function` 자체를 지원 안 하면 그것도 알려줄 것(다른 대안
필요).
- `17`은 전부 PASS가 기대값 — 특히 (B) 메타테이블 참조 동일성 assert가
실패하면 M7 전체 설계("클래스별 런타임 코드 불필요")의 핵심 전제가
무너지는 것이니 최우선으로 알려줄 것.

View file

@ -195,14 +195,16 @@ context-rejected.md`. 아래는 그중 **아직 실제로 열려있는 것만**
있던 것을 이번에 `pre-implementation-audit.md` 2-11에도 해소 표시로
동기화. **[해소됨, 2026-08-09 세 번째 세션]** Slot CRUD 의미론
(`add`/`remove`/`clear`) 미정의(1-7)/`isMounted` 이중 추적 혼용(1-8) —
`base/slot-plan.md` 참고. **[2026-08-12 감사 세션 정정] 아직 실제로
열려있는 건 하나가 아니라 넷** — `pre-implementation-audit.md` 원문에
`[해소됨]` 마커가 없는 항목이 1-3 외에도 셋 더 있음: 우선순위 스캔
동률/매치실패 처리(1-3), provider 미주입 상태 dispatch 처리(1-4),
`store.key`의 레코드 필드 타이핑을 M0로 앞당길지(1-10), Modifier
`__index`+`table.clone` 트릭 검증(1-11) — 이전 요약이 이 셋을 누락한
채로 "1-3 하나뿐"이라 잘못 축약해뒀던 것, `pre-implementation-audit.md`
본문 및 "다음 액션 제안" 절 참고.
`base/slot-plan.md` 참고. **[해소됨, 2026-08-12 열일곱 번째 세션]**
나머지 넷도 전부 확정 — 우선순위 스캔 동률/매치실패 처리(1-3, tiebreak
강제 없이 `HANDLER_PRIORITY_*` 상수+디버그 print/목록 함수, 매치실패는
즉시 error), provider 미주입 상태 dispatch 처리(1-4, 매치실패 규칙에
자연 흡수), `store.key`의 레코드 필드 타이핑(1-10, Luau `type function`으로
가능함 확인), Modifier `__index`+`table.clone` 트릭 검증(1-11, 메타테이블
참조 공유 방식 확인) — 상세는 `pre-implementation-audit.md` 해당 항목,
`base/bind-system-plan.md`/`base/modifier-plan.md` 참고. **우선순위1
11개 전부 해소됨** — M0 착수 전 남은 건 `.claude/luau-test/` 스파이크
실측 결과 확인뿐(아직 사용자가 안 돌려봄).
- **[해소됨, 2026-08-08 두 번째 세션]** `Frame { ref }`/`Frame { observer }`처럼
children 배열 숫자 슬롯에 직접 놓는 leaf 값을 매칭·바인드하는 Handler
(`(i:number, v=Ref/Observer/PreRef)`)의 패키지 배치 — 원래 제안대로

View file

@ -106,7 +106,15 @@ bind-system-plan.md` "Dispatch 체인" 절, `ROADMAP.md` M2/M4. 아래는
`retract`를 호출하는 식으로 지금 결정해두는 게 좋아 보임 — M2/M4에서 바로
부딪힐 지점.
### 1-3. 우선순위 스캔의 동률 처리, 매치 실패 시 동작이 정의 안 됨
### 1-3. 우선순위 스캔의 동률 처리, 매치 실패 시 동작이 정의 안 됨 — [해소됨, 2026-08-12 열일곱 번째 세션]
**해소**: 동률은 tiebreak 규칙을 강제하지 않고 대신 목적별 우선순위 상수
(`HANDLER_PRIORITY_HIGH` 등, "밴드+오프셋" 패턴)로 애초에 안 나게 유도,
매치 실패는 조용한 무시 없이 즉시 `error`(브랜드+`typeof` 출력, provider
초기화 확인 안내)로 확정. 핸들러 등록 시점 동률 감지 print 경고와
`Dispatch.listHandlers()`류 전체 목록 조회 함수도 M2 기본 기능으로
확정 — 상세는 `base/bind-system-plan.md` "우선순위 동률/매치 실패 처리"
절. 아래는 원래 발견 당시 기록.
**위치**: `base/bind-system-plan.md` "핸들러 계약" 절 — "디스패치는 등록된
핸들러를 우선순위 순으로 스캔하며 `isHandlable`을 호출, 첫 매치가 처리."
@ -121,7 +129,13 @@ bind-system-plan.md` "Dispatch 체인" 절, `ROADMAP.md` M2/M4. 아래는
결정해두면 구현 중 인터럽트를 막을 수 있음. 동률은 "등록 순서가 tiebreak"
정도로 명시만 해둬도 충분. M2(Dispatch 엔진) 착수 직전 확인.
### 1-4. provider(팩토리) 미주입 상태에서 dispatch가 호출되면 어떻게 되는지 세 번째 케이스가 빠짐
### 1-4. provider(팩토리) 미주입 상태에서 dispatch가 호출되면 어떻게 되는지 세 번째 케이스가 빠짐 — [해소됨, 2026-08-12 열일곱 번째 세션]
**해소**: 별도 케이스로 처리하지 않음 — provider 미주입 상태는 결국 그
클래스의 핸들러가 레지스트리에 하나도 없는 상태이므로, 위 1-3에서 확정된
일반 "매치 실패 시 즉시 error" 규칙 하나로 자연히 커버됨. 상세는
`base/module-lifecycle-plan.md` 해당 항목, `base/bind-system-plan.md`
"우선순위 동률/매치 실패 처리" 절. 아래는 원래 발견 당시 기록.
**위치**: `base/module-lifecycle-plan.md` "Bind는 누가, 어떻게 구현하는가"
절 — 재호출 가드는 "같은 팩토리=무시, 다른 팩토리=에러" 두 케이스로
@ -274,7 +288,18 @@ base 인터페이스가 그보다 늦은 M8에서 만들어지는 순서 역전.
`Connected` 기반)"만 담당하도록 분리. M1 mock에도 이 인터페이스의 트리비얼
스텁(항상 true)을 붙여두면 M4/M6 테스트가 자연스러워짐.
### 1-10. `store.key`의 레코드 필드 타이핑 검증이 M0가 아니라 M3로 밀려 있음
### 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/bind-system-plan.md` "`store.key` 레코드 필드
타이핑" 절, 실제 문법 실측은
`.claude/luau-test/16-type-store-key-typefunction.luau`(신규). 아래는
원래 발견 당시 기록.
**위치**: `ROADMAP.md` M0 vs M3 `"store.key dot-access 타입 추론 확인"`.
@ -292,7 +317,20 @@ M1(스캐폴딩)·M2(디스패치 엔진) 투자가 먼저 이뤄진 뒤에야
안전하게 추론되는지"도 같이 넣을 것 — 어차피 같은 스파이크 파일에서 몇 줄
추가로 검증 가능.
### 1-11. Modifier의 "제네릭 `__index` + `table.clone` 메타테이블 보존" 트릭이 검증 안 된 채 M7 전체 설계의 전제가 됨
### 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식 소유권 레지스트리 기각" 절, 실제 실행 확인은
`.claude/luau-test/17-modifier-index-tableclone-chaining.luau`(신규).
아래는 원래 발견 당시 기록.
**위치**: `base/modifier-plan.md` "런타임은 클래스별 코드 없이 base에 딱
하나만 있으면 됨" 절, `ROADMAP.md` M7.
@ -652,16 +690,22 @@ Handler"라고만 서술해, 사실상 3개의 거의 동일한 형태(리터럴
## 다음 액션 제안
- **M0 착수 전**: 1-5(props.Modifier/Ref nil-hole)는 `ROADMAP.md` M0에
반영 완료. 1-10(`store.key` 레코드 필드 타이핑)을 M0로 앞당기는 것 검토,
1-11(Modifier `__index` 트릭)도 비용이 낮으니 M0 후보로 포함 검토는 계속
열려있음.
- **M2(Dispatch) 착수 전**: 1-3, 1-4를 한 번에 확정(전부 base dispatch
엔진의 에러/상태관리 규칙이라 같이 결정하는 게 효율적). 1-2는 2026-08-08
세 번째 세션에 Dispatch 체인+`retractUnder`로 이미 해소됨(위 1-2번 항목
참고) — 남은 건 M2 스파이크에서 다단 체인 케이스가 실제로 맞게
동작하는지 실측하는 것뿐.
**[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 코드로
돌려보는 것 자체는 여전히 스파이크 대상 — `.claude/luau-test/16-type-
store-key-typefunction.luau`/`17-modifier-index-tableclone-chaining.luau`로
신규 추가됨(2026-08-12 열일곱 번째 세션 핸드오버 점검 중 발견된 갭,
이전까진 이 두 항목을 커버하는 스파이크 파일 자체가 없었음). 아직
사용자가 안 돌려봄, 결과 미확인.
- **나머지**: 해당 마일스톤 착수 시점에 이 문서를 다시 열어 관련 항목만
확인하면 됨 — 지금 전부 결정할 필요는 없음.

View file

@ -0,0 +1,111 @@
# 2026-08-12 열일곱 번째 세션 — `pre-implementation-audit.md` 우선순위1 마지막 넷 전부 해소
**배경**: 이전 세션(열여섯 번째)까지 `research/pre-implementation-audit.md`
우선순위1 11개 중 7개는 해소됐지만 4개(1-3/1-4/1-10/1-11)가 열려있었음.
사용자가 이번 세션에서 4개 전부에 대해 구체적인 결정을 한 번에 제시.
## 1-3. 우선순위 스캔 동률/매치 실패 처리
사용자 판단: 동률(같은 `priority`)을 엄격한 tiebreak 규칙(예: 등록 순서)으로
잡으려 하면 `None`/`Store`처럼 이미 `isHandlable`이 안 겹치는 내장 핸들러까지
전부 그 규칙을 신경 써야 하고, 나중에 서드파티 핸들러가 늘어나면 계속
골치아파짐. 대신 **목적별로 이름 붙은 우선순위 상수**(`HANDLER_PRIORITY_HIGH`
등, 열린 숫자 공간 위의 편의 상수라 `+1`류 세밀 조정도 가능 — "우선순위
밴드+오프셋"은 여러 업계에서 흔한 패턴)로 애초에 동률이 잘 안 나오게 유도.
실제로 동률이 나면 대개 설계 실수이므로, 강제 규칙보다 **디버그 모드**(핸들러
등록/정렬 시점에 동률 감지 시 print 경고, `Dispatch.listHandlers()`류 전체
핸들러 목록 조회 함수)로 대응 — 구현 비용이 거의 없고 실제 개발에 바로
도움이 되는 항목이라 M2 기본 기능으로 확정.
## 1-4. provider(팩토리) 미주입 상태에서 dispatch 호출
사용자 판단: 별도 처리 불필요. 핸들러가 없으면(`isHandlable`을 만족하는
핸들러가 하나도 없으면) `Brand`/`typeof(v)`를 출력하고 그냥 에러로 죽으면
됨 — "quad-roblox 등 provider가 초기화됐는지 확인하라"는 안내 문구 정도만
추가. 이게 1-3의 "매치 실패=즉시 error" 규칙과 정확히 같은 경로라는 걸
이번 세션에 확인 — provider 미주입 상태는 결국 "그 클래스의 핸들러가
레지스트리에 없는 상태"이므로 매치 실패와 별개 케이스가 아님. 오타 키/
미지원 조합/provider 미주입을 서로 다른 에러로 구분할 필요 없음(다른
라이브러리에서도 흔한 패턴).
## 1-10. `store.key` 레코드 필드 타이핑
사용자가 Luau `type function`(https://luau.org/types/type-functions/,
https://luau.org/types-library/) 기반 구체적 구현 방법을 제시 — `T`
테이블 타입이면 `ty:properties()`로 각 필드를 순회하며 `Source`로 감싼
새 타입을 조립하는 `WrapStore`/`ProcessStoreType` 타입함수 스케치.
`ProcessStoreType<{ty: string}>``{ty: Source<string>}`가 나오는데, 이건
선언 시점에 이름 붙은 `Source<string>` 자체가 아니라 구조를 풀어낸 익명
타입이지만 Luau가 이름이 아니라 "만족하는가"로 구조적 일치를 검사하므로
문제없이 대입 가능 — 오히려 이 방식과 정확히 맞는 조합이라는 걸 확인.
`type function`은 TBox에서도 이미 쓰이는 검증된 패턴. 이걸로 "검증 난이도"
자체가 사라져 M0/M3 어느 시점에 실제로 부딪혀봐도 막힐 위험 없음 —
`ROADMAP.md`의 M0/M3 배치를 억지로 옮길 필요는 없어짐.
## 1-11. `table.clone` + `__index` 메타테이블 트릭
사용자가 `table.clone`의 정확한 동작을 직접 확인해줌: 새 빈 테이블을 만들고
원본 키를 네이티브 슬롯 단위로 얕은 복사(깊은 복사 아님), 그 다음 원본의
`getmetatable` 결과를 그대로 새 테이블에 `setmetatable` — **메타테이블
자체는 복사되는 게 아니라 같은 참조를 공유**. 이걸로 M7 전체 설계가
의존하던 두 Luau 동작(제네릭 `__index`가 임의 key를 잡아 즉석 클로저를
만드는 것, `table.clone`이 메타테이블 참조를 보존하는 것) 모두 공식
동작대로 확인됨.
**추가 논의 — Property에 Attribute식 소유권 레지스트리를 적용하는 안,
검토 후 기각.** 사용자가 "프로퍼티도 Attribute처럼 처리해도 될까" 생각해봤다가
스스로 기각한 근거를 공유: Attribute 이름은 호출자가 자유롭게 짓는
네임스페이스라 자기 전용 키 객체를 만들 수 있지만, Instance 프로퍼티
이름은 엔진이 이미 정해둔 유한 집합이라 호출자가 전용 키를 새로 못 만듦.
그래서 "이 프로퍼티를 지금 누가 소유하고 있는가"라는 질문 자체가 원천적으로
성립 안 함(여러 modifier가 같은 프로퍼티를 건드리는 게 오히려 정상 시나리오,
테마 오버라이드 등) — 이게 Property가 소유권 추적 대신 Modifier의 override
우선순위 패턴을 쓰는 이유라는 걸 명문화.
## 결과
- `base/bind-system-plan.md`: "우선순위 동률/매치 실패 처리" 절 신설(핸들러
계약 절), "`store.key` 레코드 필드 타이핑" 절 신설(타입 추론 문제 절
바로 뒤).
- `base/module-lifecycle-plan.md`: provider 미주입 케이스 해소 표시 추가.
- `base/modifier-plan.md`: `table.clone` 정확한 동작 절 추가, Property
소유권 레지스트리 기각 절 추가.
- `research/pre-implementation-audit.md`: 1-3/1-4/1-10/1-11 전부
`[해소됨]` 표시, "다음 액션 제안" 절 갱신 — **우선순위1 11개 전부 해소**.
- `.claude/question.md` 2번 절 동기화.
M0 착수 전 남은 유일한 게이트는 `.claude/luau-test/` 스파이크 실측
결과 확인(아직 사용자가 안 돌려봄) — 설계 자체는 더 이상 막힌 게 없음.
## 핸드오버 점검(같은 세션, 후속) — 누락된 스파이크 파일 발견
사용자가 "세션 중 알게된 지식 누락/stale 점검" 요청 — 위 문서 반영을 다시
훑던 중, 1-10/1-11은 `pre-implementation-audit.md`/`base/`에 "확인됨"으로
표시했지만 실제로는 **사용자의 설명/지식을 바탕으로 한 설계 레벨 확인이지,
실제 Luau 코드로 돌려본 적은 없다**는 걸 재확인. 그런데 `.claude/luau-test/`
15개 파일 중 이 둘(Luau `type function`으로 `store.key` 타이핑, 제네릭
`__index`+`table.clone` 체이닝)을 커버하는 스파이크가 하나도 없었음 —
둘 다 이번 세션에 처음 나온 구체 메커니즘이라 이전 스파이크 라운드
(1~5차)에 있을 수 없었던 게 당연하지만, 그렇다고 "실측 대상"이라고만
말해두고 실제 파일을 안 만들어두면 다음에 또 놓치기 쉬움.
**조치**:
- `.claude/luau-test/16-type-store-key-typefunction.luau` 신규(타입체크
전용) — 사용자가 준 `WrapStore`/`ProcessStoreType` 스케치를 그대로
옮김, `luau-analyze`/`luau-lsp`로 실측.
- `.claude/luau-test/17-modifier-index-tableclone-chaining.luau` 신규
(런타임) — 제네릭 `__index` 체이닝(A), 메타테이블 참조 동일성(B),
원본 불변(C), 형제 분기 비오염(D) 네 갈래로 assert.
- `.claude/luau-test/README.md` 파일 목록 표/실행환경 표/"결과 확인 후
할 일"/갱신 이력(6차) 갱신.
- `ROADMAP.md`도 같이 감사 — M2에 우선순위 동률/매치실패 처리(디버그
모드 `Dispatch.listHandlers()` 포함) 체크리스트 항목이 통째로 빠져있던
것, M3/M7의 `store.key`/`table.clone` 항목에 새 스파이크 파일 링크가
없던 것을 같이 보강.
- `pre-implementation-audit.md`의 1-10/1-11 해소 블럭과 "다음 액션 제안"에
새 스파이크 파일 이름을 명시적으로 링크.
**교훈**: "설계는 확정됐다"와 "실제로 실행해서 확인했다"를 같은 `[해소됨]`
마커로 뭉뚱그리면, 정작 실측이 필요한 지점이 조용히 새는 패턴이 생김 —
앞으로 `[해소됨]` 표시를 붙일 때 그 해소가 (a) 순수 설계 결정인지 (b) 실측
검증까지 끝난 것인지 구분해서 적을 것.

View file

@ -111,21 +111,16 @@ modifier/Ref의 컴포넌트 경계 통과 방식) 논의도 2026-08-04 세션
## 지금 할 일 (우선순위순)
1. **구현 시작 — 루트 `ROADMAP.md`의 M0부터.** 설계 단계는 2026-08-04 로드맵
인수인계 라운드로 종료. M0 착수 직전 확인할 것 둘:
- **`.claude/luau-test/`(2026-08-09 신설) 스파이크 결과** — 아직 사용자가
인수인계 라운드로 종료. `research/pre-implementation-audit.md` 우선순위1은
2026-08-12 열일곱 번째 세션에 마지막 넷(1-3/1-4/1-10/1-11)까지 전부
해소되어 **11개 전원 완료** — M0 착수 전 남은 유일한 게이트는:
- **`.claude/luau-test/`(2026-08-09 신설, 2026-08-12 열일곱 번째 세션에
16/17 추가로 총 17개) 스파이크 결과** — 아직 사용자가
`luau`/`luau-analyze`/`luau-lsp`/Roblox Studio로 안 돌려봄, 결과가
나오면 그것부터 반영할 것(`luau-test/README.md`가 파일별로 뭘 우선
확인해야 하는지 이미 적어둠). 걸리는 게 있으면 `base/` 문서부터 고치고,
없으면 그대로 M0 실제 코드 작성에 재사용.
- **`research/pre-implementation-audit.md` 우선순위1** — 신설 당시
11개였으나 대부분 이후 세션에서 해소됨(Tween/Tag 재설계, Slot CRUD,
`canExecute` 시그니처, `LifetimeHandle` 로드맵 순서 등). **[2026-08-12
감사 세션 정정]** 실제로 아직 열려있는 건 넷(원문에 `[해소됨]` 마커가
없는 항목 기준) — 우선순위 스캔 동률/매치실패 처리(1-3), provider
미주입 상태 dispatch 처리(1-4), `store.key` 레코드 필드 타이핑을 M0로
앞당길지(1-10), Modifier `__index`+`table.clone` 트릭 검증(1-11).
이전엔 "1-3 하나뿐"이라 잘못 축약돼 있었음(1-4/1-10/1-11 누락) —
`.claude/question.md` 2번이 최신 상태.
없으면 그대로 M0 실제 코드 작성에 재사용. 설계 자체는 더 이상 막힌
게 없음 — `.claude/question.md` 2번이 최신 상태.
2. **용어 정리 — 1차 제안 이후 대부분 확정, 소수만 남음.** 최신 소스는
`.claude/question.md` 1번(개수 반복 안 함, 항목 추가/해소될 때마다 여기가
stale해지는 패턴이 반복됐어서). 아직 열려있는 것만 짚으면: `State`(1순위,
@ -668,3 +663,22 @@ transitively 생존). `and`/`or` 삼항 관용구 전면 금지(기존 "항상-t
예외" 조항 폐기), 코퍼스 전체 실제 코드 6곳을 `if-then-else`로 교체.
Attribute 자동 unset이 필요해지면 쓸 `:Apply` opt-in 유틸을
`research/operator-sugar-plan.md`에 백로그로 추가(착수 안 함).
**2026-08-12 열일곱 번째 세션 — 우선순위1 마지막 넷 전부 해소**
(`session/2026-08-12-17-priority1-audit-resolved.md`)
`pre-implementation-audit.md` 우선순위1 중 열려있던 마지막 넷을 사용자가
한 번에 확정: 우선순위 동률/매치실패(1-3, tiebreak 강제 대신
`HANDLER_PRIORITY_*` 상수+디버그 모드 동률 감지/핸들러 목록 함수, 매치실패는
즉시 error), provider 미주입 dispatch(1-4, 매치실패 규칙에 자연 흡수),
`store.key` 레코드 필드 타이핑(1-10, Luau `type function`으로 `Store<T>`
`{[K]: Source<V>}` 합성 가능함을 구체 스케치로 확인), Modifier
`__index`+`table.clone` 트릭(1-11, 메타테이블이 복사 아닌 참조 공유임을
확인). 부수적으로 Property에 Attribute식 소유권 레지스트리를 적용하는 안을
검토 후 기각(엔진이 정한 유한 프로퍼티 이름 집합은 호출자가 전용 키를 못
만들어 소유권 판정 불가 — Property가 override 우선순위를 쓰는 이유).
**우선순위1 11개 전원 해소** — M0 착수 전 남은 건 `.claude/luau-test/`
스파이크 실측뿐. **핸드오버 점검 중 발견**: 1-10/1-11은 설계는 확정됐지만
이를 실제로 실측할 스파이크 파일 자체가 없었던 갭 — `16-type-store-key-
typefunction.luau`/`17-modifier-index-tableclone-chaining.luau` 신규
추가(총 17개), `ROADMAP.md` M2/M3/M7 체크리스트에도 누락됐던 항목(디버그
모드 동률 감지+`listHandlers`, `store.key`/`table.clone` 실측 링크) 보강.

View file

@ -137,6 +137,14 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검
걸러내기(no-op이라도 필드 자체는 항상 정의 — `Dispatch.process`가 핸들러
교체 시 nil 체크 없이 호출, `base/bind-system-plan.md` "핸들러 계약"
절, 2026-08-08 세션)
- [ ] 우선순위 동률/매치 실패 처리(2026-08-12 열일곱 번째 세션 확정,
`base/bind-system-plan.md` "우선순위 동률/매치 실패 처리" 절) —
`HANDLER_PRIORITY_HIGH`/`_NORMAL`/`_LOW` 등 목적별 우선순위 상수,
매치 실패(`isHandlable`을 만족하는 핸들러 없음)는 `Brand`+`typeof(v)`
출력 후 즉시 error(provider 초기화 확인 안내 포함 — provider
미주입 상태도 이 경로로 자동 커버, `pre-implementation-audit.md`
1-3/1-4), 핸들러 등록/정렬 시점 동률 감지 print 경고 +
`Dispatch.listHandlers()` 디버그 유틸
- [ ] `Dispatch/Leaf.luau``(i:number, v=Ref/Observer/PreRef)` children-array
leaf 매칭 Handler, `StoreBind.luau`와 같은 층위(범용/엔진무관) —
quad-base 소속으로 확정(2026-08-08 두 번째 세션, `base/
@ -153,7 +161,12 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검
## M3 — Store/State/Source
- [ ] `Source.luau`/`State.luau`/`Store.luau`
- [ ] `store.key` dot-access 타입 추론 확인
- [ ] `store.key` dot-access 타입 추론 확인 — Luau `type function`
(`WrapStore`/`ProcessStoreType`)으로 `Store<T>``T`의 각 필드를
`Source`로 감싼 레코드 타입을 합성 가능함을 확인(2026-08-12 열일곱
번째 세션, `base/bind-system-plan.md` "`store.key` 레코드 필드
타이핑" 절) — 실제 문법이 통과하는지는
`.claude/luau-test/16-type-store-key-typefunction.luau`로 실측 필요
- [ ] `:Compute(fn, ...)` — trailing args로 추가 의존성 직접 받는 sugar
(2026-08-11 세션, `base/bind-system-plan.md` "`:Compute(fn, ...)`"
절) — `:With(...):Compute(fn)` 체인과 달리 노드 1개(Compute 노드
@ -369,7 +382,11 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검
`Type(args)` 팩토리 관습, `modifier-plan.md` 3번)
- [ ] flatten-before-dispatch(`isModifier(v)`로 배열 항목 중 Modifier만
판별해 필드 merge, 나머지는 안 건드리고 통과 — 2026-08-07 열 번째
세션 명시, `modifier-plan.md` 1번), immutable `table.clone` 체이닝
세션 명시, `modifier-plan.md` 1번), immutable `table.clone` 체이닝 —
`table.clone`이 메타테이블을 복사 아닌 참조로 공유해 제네릭 `__index`
기반 체이닝이 안 끊긴다는 메커니즘은 확인됨(2026-08-12 열일곱 번째
세션, `modifier-plan.md` "`table.clone`의 정확한 동작" 절) — 실제
Luau 실행 확인은 `.claude/luau-test/17-modifier-index-tableclone-chaining.luau`
- [ ] `Modifier.Overridden(mod1, mod2, ...)`(이름 확정, 구 `Merge`→`Override`,
2026-08-08 세션) — 필드별 raw 덮어쓰기, 특별한 State/함수 분기
불필요(`modifier-plan.md` 9번)