diff --git a/.claude/base/bind-system-plan.md b/.claude/base/bind-system-plan.md index 35e4a5e..caa2276 100644 --- a/.claude/base/bind-system-plan.md +++ b/.claude/base/bind-system-plan.md @@ -2669,9 +2669,15 @@ State/스트림)를 다뤘던 이전 시도가 있어 조사함 — **확인된 ## 확정된 것 (더 이상 열린 질문 아님) -- **핸들러 계약**: `isHandlable(inst,k,v)` + `priority` + `process`(구 - `bind`) + `retract`(구 `cleanup`) 4종 조합으로 확정 — tbox식 6-hook 세분화는 지금은 +- **핸들러 계약**: `isHandlable(inst,k,v)` + `priority` + + `process(inst,k,v,index)` **3종**으로 확정 — tbox식 6-hook 세분화는 지금은 안 함. 실제 구현하며 부족한 지점이 보이면 그때 hook 추가(점진적 확장). + **[정정, 2026-08-13 다섯 번째 세션 — 이 항목이 갱신에서 누락돼 문서 상단 + "핸들러 계약" 절과 모순돼 있던 걸 같은 날 리뷰에서 발견]** 예전엔 + `process`(구 `bind`) + `retract`(구 `cleanup`) 4종이었으나, `retract`가 + 별도 필드에서 **`process`의 반환값(retractor 클로저)** 으로 합쳐짐 — + 이름과 개념은 그대로 유효하고 자리만 옮겨온 것(위 "핸들러 계약" 절이 + 정본). - **Signal 클래스**: 안 만듦, 콜백 + `Connected` 계산 속성만(`base/ lifecycle-pattern.md`). - **Ref**: 도입 확정(위 절 참고), 용도는 "id 기반 조회 대체"가 아니라 "외부 diff --git a/.claude/base/slot-plan.md b/.claude/base/slot-plan.md index b487fea..013158c 100644 --- a/.claude/base/slot-plan.md +++ b/.claude/base/slot-plan.md @@ -340,13 +340,27 @@ function SlotHandler.process(inst, k, slotValue, index) -- no-op을 심어두면 그 다음 진짜 교체 때 이전 서브트리를 정리할 주체가 사라짐. return function(hintValue) if slotValue == hintValue then return end -- 같은 Slot 재발행 → no-op - destroySlotTree(slotValue) -- 재귀 파괴(자식 정리), 폐기·옮기지 않음 — slot 자신의 GC 앵커는 안 건드림(top-level 전용) - unbindLifetime(inst, slotValue) -- top-level 자신의 GC 앵커 해제 — attachSlot의 top-level bindLifetime과 짝 - releaseOwner(slotValue, inst) + -- [정정, 2026-08-13 감사 후속] 파괴가 아니라 **언마운트** — + -- 아래 "`State` 교체는 파괴가 아니라 언마운트" 절이 확정한 + -- 결정이 적용돼야 하는 자리가 바로 여기(최상위 dispatch 경로). + -- 예전엔 destroySlotTree를 불러 그 결정과 정면으로 모순됐음. + unmountSlotTree(slotValue) + -- 이 위치가 더 이상 기여하지 않음을 owner에게 알림 — **순서 고정** + -- (setLength가 끝에서 recompute를 돌리므로 offsetSource를 먼저 비워야 + -- 죽는 중인 Source에 헛된 :Set()이 안 감, 아래 ⚠️ 절 참고) + Dispatch.setOffsetSource(inst, k, None) + Dispatch.setLength(inst, k, 0) + unbindLifetime(inst, slotValue) -- top-level 자신의 GC 앵커 해제 — SlotHandler.process의 bindLifetime과 짝 + releaseOwner(slotValue, inst) -- 이제 이 Slot은 아무에게도 안 묶임(다른 곳에 다시 넣을 수 있음) end end ``` +**언마운트된 Slot은 그대로 재사용 가능** — `_elements`와 그 안의 소유권이 +전부 보존되므로(아래 `unmountSlotTree` 참고), `slot`을 들고 있던 코드가 +다른 곳에 다시 넣으면 `attachSlot`이 새 물리 부모로 다시 flush함. 아무도 +안 들고 있으면 그냥 GC. 지금 확실히 죽이려면 `dispose`(아래 절). + **[전면 정정, 2026-08-13 감사] `claimOwner`를 nested/top-level 두 함수로 쪼개고, top-level은 `(inst, k)`까지 본다.** 원래는 하나의 `claimOwner`가 "같은 ownerKey면 `false` 반환(no-op)"을 양쪽에 공유했는데, 그 분기가 @@ -1390,6 +1404,32 @@ Slot=그 `.Length`)이 됨. plain 요소만 있는 흔한 경우엔 항상 합== 대해서만 한 번 돎:** ```lua +-- [신설, 2026-08-13 감사 후속] 비파괴 언마운트 — `State` 교체/`Extract` +-- 계열이 쓰는 경로. `destroySlotTree`와 **딱 하나만 다름: 실제로 안 죽인다.** +-- 물리 트리에서만 떼어내고 `_elements`/자식 소유권은 통째로 보존하므로, +-- 같은 Slot을 나중에 다른 곳에 다시 마운트할 수 있음(= 포탈). +local function unmountSlotTree(slot) + for i, element in ipairs(slot._elements) do + if isSlot(element) then + unmountSlotTree(element) -- 재귀 — 중첩 Slot도 똑같이 비파괴 + else + element.Parent = nil -- Destroy 아님(quad-roblox 글루가 수행) + end + -- releaseOwner를 **안 부름** — 자식들은 여전히 이 slot의 소유. 이게 + -- destroySlotTree와의 핵심 차이(파괴는 소유권까지 반납, 언마운트는 유지). + end + local bk = getBookkeeping(slot) + if bk then + for i, observer in pairs(bk.observers) do + unbindLifetime(slot._mountedInst, observer) -- 물리 target에 걸린 배관만 해제 + end + end + slot._mounted, slot._mountedInst = false, nil + slot.Offset = nil -- 마운트 전 상태로 복원(위 "`Slot.Offset`은 마운트 전엔 nil") + -- slot 자신의 unbindLifetime / releaseOwner / owner쪽 setLength·setOffsetSource는 + -- 호출부 몫 — destroySlotTree와 동일한 층위 분리. +end + local function destroySlotTree(slot) for i, element in ipairs(slot._elements) do -- [정정, 2026-08-13 감사] 소유권 반납을 먼저 — 아래 "소유권 반납은 @@ -1696,11 +1736,28 @@ Slot이 마운트될 때 **자기 하위 요소들까지 `bindLifetime`으로 #### 구현상 바뀌어야 하는 것 -`reconcile`이 교체/소멸 시 `rawRemove`(파괴) 대신 **비파괴 경로**를 타야 -하고, 그 경로는 `destroySlotTree`가 지금 하는 일 중 **파괴만 빼고 나머지는 -그대로 해야 함**(자식 observer `unbindLifetime`, 옛 owner에 등록해둔 -`Dispatch.setLength`/`setOffsetSource` 해제, `_mounted`/`_mountedInst` -복원, `releaseOwner`). +**[반영 완료, 2026-08-13 감사 후속]** 비파괴 경로를 `unmountSlotTree`로 +신설하고(아래 "파괴" 절), 이걸 쓰는 자리를 둘로 못박음: + +1. **`SlotHandler.process`가 반환하는 클로저**(최상위 dispatch 경로) — + **이 결정이 적용돼야 하는 바로 그 자리인데 처음엔 빠뜨려서 + `destroySlotTree`를 계속 부르고 있었음**(다른 에이전트 리뷰가 지적, + 그대로 뒀으면 언마운트 결정 자체가 무의미해질 뻔함). 지금은 위 절의 + 코드가 `unmountSlotTree` + `setOffsetSource(None)`/`setLength(0)` + + `unbindLifetime` + `releaseOwner`를 부름. +2. **`:List`의 `reconcile`** — 교체/소멸 시 `rawRemove`(파괴) 대신 같은 + 비파괴 경로. 데이터에서 빠진 아이템도 파괴되지 않고 언마운트만 되며, + 아무도 안 들고 있으면 GC(quad 전역의 GC-native 원칙 그대로). + +**여전히 파괴인 것**: 명시적 CRUD `Slot:Remove(index)`/`Slot:Clear()` +(CRUD 표가 "제거 **+ 파괴**"로 이미 정의)와 `dispose`. 즉 **"자동 경로는 +언마운트, 명시적으로 지우라고 한 것만 파괴"**로 갈림 — `Ref`/`Attribute`의 +"지울 거면 명시적으로" 철학과 정확히 같은 결. + +`unmountSlotTree`는 `destroySlotTree`가 하는 일 중 **실제 파괴와 자식 +소유권 반납만 빼고 나머지는 그대로 함**(자식 observer `unbindLifetime`, +`_mounted`/`_mountedInst` 복원, `slot.Offset = nil`). 옛 owner에 등록해둔 +`Dispatch.setLength`/`setOffsetSource` 해제는 호출부 몫(아래). **[정정, 사용자 지적] "해제 짝"이라는 새 API는 필요 없음** — 옛 owner에 대해 그냥 **`Dispatch.setOffsetSource(ownerKey, position, None)` + diff --git a/.claude/question.md b/.claude/question.md index fbd1b23..ddee280 100644 --- a/.claude/question.md +++ b/.claude/question.md @@ -45,6 +45,20 @@ API 전부에 걸리며, 2026-08-07 일곱 번째 세션(커링 스타일 확정 **M0 착수 전에 정하는 게 맞음** — 2번을 고르면 base 문서 다수가 영향받음. +**[사용자 방향, 2026-08-13]** 순환 타입을 만드는 것보다 **`State` 타입 +자체를 구울 때(코드 생성 시점) 인라이닝**해주는 쪽이 맞아 보인다는 의견 — +즉 `State`가 자기 자신을 재귀 참조하는 선언을 피하고, 타입 생성기가 +각 `T`에 대해 평탄한 타입을 뽑아주는 방향(`Modifier`의 클래스별 flat 타입을 +생성기로 뽑는 이미 확정된 패턴과 같은 결). 이러면 `08`에서 걸린 +`Recursive type being used with different parameters` 제약도 같이 비켜감. +**다만 이건 사람이 직접 확인해야 할 부분으로 사용자가 보류** — 0-Z(Attribute)와 +함께 사용자가 직접 스케치하며 판단할 목록. + +**관련 실측 근거**: `08`(재귀 타입 제약), `15`(read-only/read-write `Get` +불일치 — 파싱 실패로 검증불가 상태라 재작성 필요), `14`(Ref 생성자 오버로드가 +정상 nilable 사용례까지 막음), `16`(`type function` API 불일치). 전부 +`audit/luau-test-first-run-2026-08-13.md`. + ### 0-Z. ⭐ **최우선 — Attribute 이름 소유권을 무엇으로 판정할 것인가** (2026-08-13 여섯 번째 세션, 사용자가 다음 세션 심층 분석으로 이관) **이게 지금 유일하게 `base/` 반영을 막고 있는 결정.** 아래 0-A의 @@ -157,9 +171,13 @@ Slot이 됨. 막는 게 소유권 규칙이 아니라 "제거 = 파괴"라는 re 판단이라, 포탈은 별도 기능이 아니라 그 결정의 귀결. 안 지운 Slot은 아무도 안 들고 있으면 GC되고, 지금 죽이려면 `dispose`(위 0-B). **[해소, 사용자 지적] "해제 짝"이라는 새 API는 필요 없음** — 옛 owner에 -대해 그냥 `setLength(ownerKey, position, 0)` + `setOffsetSource(ownerKey, -position, None)`을 다시 부르면 됨(이미 확정된 "마운트 안 하는 위치는 -`0`/`None` 등록" 관용구 그대로). 즉 **해제 = 0/`None`으로 재등록**. +대해 그냥 `setOffsetSource(ownerKey, position, None)` **다음에** +`setLength(ownerKey, position, 0)`을 부르면 됨(이미 확정된 "마운트 안 하는 +위치는 `0`/`None` 등록" 관용구 그대로). 즉 **해제 = `None`/0으로 재등록**. +**순서가 중요** — `setLength`가 끝에서 `recompute`를 돌리므로 먼저 부르면 +아직 남아있는 옛 `Source`(죽는 중인 서브트리의 것)에 헛된 `:Set()`이 날아감 +(`base/slot-plan.md`의 ⚠️ 절). 이 줄이 한때 순서를 거꾸로 적어놔서 같은 날 +리뷰에서 지적됨. `state>`류로 offset이 밀리는 문제는 `state>`와 같은 범주로 **"그냥 확인된 것"으로 수용**(평탄화 도구가 처리, 케이스 드묾). 상세는 `base/slot-plan.md` "`State` 교체는 파괴가 아니라 언마운트" 절. diff --git a/CLAUDE.md b/CLAUDE.md index 3700164..7f9d1c4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -121,6 +121,9 @@ modifier/Ref의 컴포넌트 경계 통과 방식) 논의도 2026-08-04 세션 `Effect`/`Observer`/`Animate`/`Operator` 등 같은 계약을 공유하는 API 전부에 걸림 — 선택지는 (1) 계약 유지 + 파라미터 주석 필수, (2) raw 값 전환(**구조 변경 규모 큼**), (3) 혼합. **M0 착수 전에 정할 것.** + 사용자 방향: 순환 타입을 만들기보다 **`State` 타입을 구울 때 인라이닝** + 하는 쪽(생성기가 `T`별 평탄 타입을 뽑는, `Modifier` flat 타입과 같은 결) + — 다만 사람이 직접 확인할 부분이라 0-Z와 함께 사용자 판단 대기. **0-Z: Attribute 이름 소유권 결정.** 2026-08-13 여섯 번째 세션에 `Dispatch` 재디스패치 모델이 "하강 diff"로 @@ -885,66 +888,18 @@ handoff용 저장이 불필요해짐 — `Relate`는 여러 위치/사이클을 `modifier-plan.md` 전부 반영, `archive/checkpoint-handler-pattern-reversed.md` 신설. -**2026-08-13 여섯 번째 세션 — c33ae04 커밋 전체 감사(버그 4건), Slot -언마운트 전환, 재디스패치 모델 재설계** -(`session/2026-08-13-06-commit-audit-dispatch-redesign-bugs.md`) -사용자 지시로 직전 커밋을 메인 컨텍스트에서 직접 정독 — 인덱스 재설계 -**방향은 옳지만 새로 쓴 의사코드가 손 트레이싱을 안 거친 채 커밋**됐음이 -드러나 실제 버그 4건 발견·수정: (1) `Dispatch.process`의 `chains:SetStrong`이 -`h.process` 뒤에 있어 최초 마운트에서 하위 retractor가 통째로 유실, -(2) Attribute 그룹이 `process`에서 `retractFrom`을 선행 호출해 점유 -체크(=소유권 충돌 감지)가 전혀 작동 안 함, (3) `SlotHandler`가 claim -실패에도 파괴적 클로저를 반환해 `Frame{slot,slot}`에서 이중 파괴, -(4) `Ref` retractor가 spurious 재발행에서도 `relate`를 지워 dedup 무력화. -Slot 소유권은 nested=엄격 `claimOwner`/top-level=`claimOwnerAt(inst,k)`로 -분리하고 `rawRemove`의 `releaseOwner` 누락·`destroySlotTree`의 GC 타이밍 -의존 오류도 수정. `ROADMAP.md`가 2026-08-08 이전 모델로 남아있던 것, base -내 "3종 vs 4종 계약" 모순, `luau-test/04`가 없어진 가드를 검증하던 것도 -정리(`04`는 인덱스 모델 + 버그 (1) 재현 음성 대조군으로 재작성). -재발 방지로 `bind-system-plan.md`에 "Handler 작성 체크리스트"(7항목), -`relate-plan.md`에 "언제 `Relate`를 쓰고 언제 쓰면 안 되는가" 신설. - -**이어진 사용자 설계 결정(같은 세션, 최종 상태만)**: -- **`State` 교체 = 파괴가 아니라 언마운트**(`state`와 동일, - 비파괴 추출은 `Splice`로 이미 지원) — **포탈이 별도 기능이 아니라 이 - 결정의 귀결이 됨**. 해제는 `setOffsetSource(None)` → `setLength(0)` - **순서 고정**(반대면 죽는 중인 서브트리의 offset `Source`에 헛된 `:Set()`이 - 날아감) + `recompute`의 `nil` 관대 처리/`slot.Offset = nil` 방어. - 별도 unregister API는 불필요. `base/slot-plan.md`에 **확정 반영 완료**. -- **`dispose(value)` 신설** — "트리가 아직 살아있길 요구하면 파괴를 거부하고 - error"(엔진은 조용히 넘어가지만 quad 자료구조가 깨지므로). 시그니처/범위는 - `question.md` 0-B. -- **재디스패치를 "하강 diff"로 재설계** — 래핑 핸들러의 `retractFrom` 선행 - 호출을 폐기하고 `Dispatch.process`가 **핸들러를 먼저 비교**(같으면 그 자리 - 클로저에 새 값 넘기고 자기 process 재호출, 다르면 아래 전량 철거). 계기는 - 힌트가 `None`/래퍼로 오염돼 깜빡임 방지가 조용히 꺼지는 결함(사용자 제기). - 이 모델이면 **힌트 타입이 구조적으로 보장되고 깊은 체인 힌트 유실까지 - 사라짐**. 내가 낸 반론("StoreBind 구독 갈아타기")과 보완안(`oldValue` 전달)은 - 둘 다 사용자 지적으로 철회 — 클로저가 이미 old를 캡처하고 있음. - **아직 `research/dispatch-redispatch-diff-plan.md`에만 있음, base 미반영.** -- **평탄화**(`state:Flatten()`)는 백로그 상세화만 — 반환 노드의 **동적 - 의존성**이 최대 쟁점(`research/operator-sugar-plan.md`). -- **남은 결정 하나: Attribute 이름 소유권** — 새 모델에선 양쪽 다 - `StoreBind`라 "같은 핸들러"로 판정돼 조용히 갈아탐. 사용자가 "이전 - 결정(claimant `Relate`)을 다시 가져오는 게 맞아 보이나 다음 세션에 직접 - 스케치하며 심층 분석"으로 이관 — `question.md` **0-Z(최우선)**. - -**이어진 첫 실측 라운드(같은 세션)** — `luau`/`luau-analyze` 바이너리가 -사용 가능해져 **스파이크를 만들기 시작한 2026-08-09 이래 처음으로 실제로 -돌림**(CLAUDE.md가 "M0 착수 전 남은 유일한 게이트"로 꼽아온 항목). 결과 -전문은 `audit/luau-test-first-run-2026-08-13.md`. -- **런타임 12개 전원 통과**(01~07/11/17~20). **`04`가 이번 세션 감사의 - 버그를 음성 대조군으로 재현** — 체인 깊이가 3 대신 1로 무너지고 죽은 - store가 나중에 UI를 덮어쓰는 것까지 실측돼 감사→수정 사이클이 닫힘. - **`07`은 보강해야 실제 검증이 됐음**(연쇄 GC 미검증 상태였고, 파일이 - 스스로 적어둔 "weak 엔트리를 셀 방법 없음" 전제가 틀렸음) — GC-native - 아키텍처의 핵심 전제가 이걸로 실측 확정. **`18`이 `Relate` 상호 순환 - 경고를 실증**(추측이 아니라 실제로 GC 안 됨). -- **타입 쪽에서 진짜 이슈 하나 발견 → `question.md` 0-Y 신설**(위 참고). -- `modifier-plan.md`의 "데이터를 테이블에 직접 두고"가 두 갈래로 읽히는 - **문서 결함**이 드러남 — self 최상위 리터럴 키에 두면 `__index`가 - `rawget` 성공 시 안 불려 같은 필드 재호출이 죽음(문서 3·4절 대표 용례가 - 바로 그 패턴). 경고 문단 추가 + `17` 재작성으로 해소. -- 스파이크 결함 수정: `17`(크래시), `11`(엉뚱한 이유로 통과), `19` B/C - (폐기 설계 검증 → 현행 설계로 재작성, 음성 대조군 포함), `07`(보강). - 남은 것은 `13`/`15`/`16`의 스파이크 격리·API 재확인(설계 문제 아님). +**2026-08-13 여섯 번째 세션 — c33ae04 감사(버그 4건), Slot 언마운트 전환, +재디스패치 재설계안, 첫 실측 라운드** +(`session/2026-08-13-06-commit-audit-dispatch-redesign-bugs.md`, +실측 결과는 `audit/luau-test-first-run-2026-08-13.md`) +직전 커밋을 직접 정독해 인덱스 재설계 의사코드의 실제 버그 4건 발견·수정 +(`chains:SetStrong` 순서로 하위 retractor 유실, Attribute 그룹의 점유 체크 +무력화, `SlotHandler`의 claim 실패 시 이중 파괴, `Ref` dedup 무력화). +이어 사용자 결정으로 **`State` 교체를 파괴→언마운트로 전환**(포탈이 +그 귀결이 됨), **`dispose(value)`**(트리가 요구하면 파괴 거부·error) 신설, +**재디스패치를 "하강 diff"로 재설계**(`research/dispatch-redispatch-diff-plan.md`, +**base 미반영 — `question.md` 0-Z 하나 남음**). `ROADMAP.md`/base 계약 개수 +모순/`luau-test` stale도 정리. 마지막으로 `luau` 바이너리가 생겨 **첫 실측** +— 런타임 12개 전원 통과, `04`가 위 버그를 음성 대조군으로 재현, `07` 보강으로 +GC-native 전제 확정, `18`이 `Relate` 순환 경고 실증. 타입에선 `:Compute(fn)` +lazy 핸들 계약이 Luau 추론과 충돌하는 게 드러나 `question.md` **0-Y** 신설. diff --git a/ROADMAP.md b/ROADMAP.md index 324d254..03d50f8 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -252,6 +252,38 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검 ## M6 — Slot +- [ ] **[2026-08-13 여섯 번째 세션 — 이 세션의 Slot 결정 전부, 구현 전 필독]** + - **`State` 교체 = 파괴가 아니라 언마운트**(`state`와 동일). + 비파괴 경로 `unmountSlotTree`를 `destroySlotTree`와 별도로 구현 — + 차이는 딱 둘: 실제 `Destroy()`를 안 하고, 자식 `releaseOwner`도 안 함 + (자식은 계속 그 slot 소유라 통째로 재마운트 가능 = 포탈). + **쓰는 자리 둘**: `SlotHandler.process`가 반환하는 클로저, `:List`의 + `reconcile`. **여전히 파괴인 것**: 명시적 `Remove`/`Clear`/`dispose`. + - **해제 시 owner 등록 되돌리는 순서 고정** — + `setOffsetSource(inst,k,None)` **먼저**, `setLength(inst,k,0)` **나중**. + 반대로 하면 `setLength` 안의 `recompute`가 죽는 중인 서브트리의 offset + `Source`에 헛된 `:Set()`을 날림. `recompute`는 `sourceList[i]`가 `nil` + 이어도 `None`처럼 skip(방어), 해제 시 `slot.Offset = nil`. + - **소유권 판정을 둘로 분리** — nested(`rawAdd`)는 엄격 `claimOwner` + (같은 owner 재클레임도 error, `Slot{a,a}` 차단, 반환값 없음), + top-level은 `claimOwnerAt(element, inst, k)`(정확히 같은 `(inst,k)`의 + spurious 재발행만 `false`, `Frame{slot,slot}`은 error). + `releaseOwner`는 불일치 시 즉시 error. + - **`rawRemove`가 `releaseOwner`를 부를 것**(옛 의사코드에서 누락돼 있었음), + **`destroySlotTree`가 자식 소유권 반납 + `_mounted`/`_mountedInst` 복원** + (GC에 맡기면 재사용이 GC 타이밍 의존으로 비결정적 실패). + - **`SlotHandler.process`는 claim 실패 시에도 파괴적 클로저를 반환해야 함** + — no-op을 반환하면 다음 진짜 교체 때 정리 주체가 사라짐(`retractFrom`은 + 클로저가 early-return해도 체인에서 항상 소비하므로). + - 전부 `base/slot-plan.md`에 반영돼 있고, `luau-test/19` C 섹션이 + 소유권 분기를 음성 대조군까지 포함해 실측 검증함. +- [ ] **`dispose(value)`** — 대상이 아직 어느 트리에 의해 살아있길 요구되면 + **파괴를 거부하고 즉시 error**(떼어내주지 않음 — 떼는 건 `Set`=언마운트의 + 몫). 엔진은 `Destroy`/`Clear`에 에러를 안 내지만 quad 자료구조가 깨지므로, + quad가 관리 중인 값을 안전하게 지우는 유일한 경로. 마운트 위치는 + `elementOwner`가 이미 알고 있어 새 부기 불필요. 시그니처/대상 범위는 + `.claude/question.md` 0-B에서 미확정 + - [x] **"여러 Slot이 형제로 섞일 때 순서 보장" 해소**(2026-08-09 여섯 번째 세션) — `Dispatch.setLength`/`setOffsetSource` 메커니즘, `base/ bind-system-plan.md` "Length/Offset" 절. `Slot.Length: State`도