From 1aa01c60fbd7cee3cc887282d6603f6539125641 Mon Sep 17 00:00:00 2001 From: qwreey Date: Thu, 13 Aug 2026 17:08:49 +0900 Subject: [PATCH] =?UTF-8?q?docs(audit):=20=EC=BD=94=ED=8D=BC=EC=8A=A4=203?= =?UTF-8?q?=EC=B0=A8=20=EA=B0=90=EC=82=AC=20=E2=80=94=20=EC=9E=90=EA=B8=B0?= =?UTF-8?q?=EB=AA=A8=EC=88=9C/stale=20=EC=8B=A4=EC=B8=A1=EA=B2=B0=EA=B3=BC?= =?UTF-8?q?=20=EB=AF=B8=EB=B0=98=EC=98=81=207=EA=B1=B4=20=EC=A0=95?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 병렬 에이전트 5개로 직전 커밋(9f9e83b)의 수정 검증 + 재감사: - store-semantics.md: "전부 확정" 헤더 바로 아래 그걸 반박하는 0-Y 배너를 붙여 생긴 자기모순 정리, "검증 필요(M0 스파이크 대상)" 서술이 실제로는 luau-test/08로 이미 통과 검증됐는데 안 갱신돼 있던 것 정정 (좁은 잔여 케이스만 0-Y로 계속 추적) - ROADMAP.md: M6(Slot)도 M2/M4/M7/M10과 같은 재디스패치 모델 교체 경고 배너가 빠져 있던 것 추가 - tag-plan.md: 최상단 배너가 "0-Z 해소 전엔 옛 모델"이라 경고하는데 바로 아래 "열린 질문 — 없음, 전부 확정" 절이 정면으로 모순되던 것 정정 - slot-plan.md: reconcile이 이제 비파괴 rawUnmount를 쓰도록 바뀐 걸 한 문장 전에 정정해놓고, 바로 다음 문장이 "제거는 항상 파괴 확정" 이라는 뒤집힌 전제로 rawExtract 미사용을 정당화하던 자기모순 정정 - documentation-content-map.md: 같은 파일 안에서 폐기된 Tween 특수 bind-key 모델(취소선 처리)이 caveat 없이 3곳 더 남아있던 것 정정 - comparison-fusion-vide.md(reference/): quad 설계 근거 서술이 2026-08-10 폐기된 구 Tween 모델을 그대로 인용하던 것 정정(반면교사 논리 자체는 유효, 인용한 결론 쪽 이름만 stale했음) - pre-implementation-audit.md 2-1: "검증 필요"였던 항목이 이미 luau-test/08로 해소됐는데 [해소됨] 표시가 안 붙어있던 것 정정 - luau-test/15 헤더: 이 스파이크가 실제론 파싱 실패로 아무 섹션도 검증 못 한 상태인데, 헤더는 "이미 정정되어 확정됨"이라고 서술하던 것을 STATUS.md 실측 결과에 맞게 정정 Co-Authored-By: Claude Sonnet 5 --- .claude/base/slot-plan.md | 16 +++++++-- .claude/base/store-semantics.md | 36 ++++++++++++------- .claude/base/tag-plan.md | 11 ++++-- ...5-type-compute-trailing-deps-typepack.luau | 11 ++++++ .claude/reference/comparison-fusion-vide.md | 14 +++++--- .claude/research/documentation-content-map.md | 6 ++-- .claude/research/pre-implementation-audit.md | 11 ++++++ ROADMAP.md | 5 +++ 8 files changed, 84 insertions(+), 26 deletions(-) diff --git a/.claude/base/slot-plan.md b/.claude/base/slot-plan.md index 9e285f7..c4081f3 100644 --- a/.claude/base/slot-plan.md +++ b/.claude/base/slot-plan.md @@ -1165,9 +1165,19 @@ raw `i`를 그대로 위치 인자로 썼는데, 앞쪽 item이 filter로 마운 언마운트만 되며, 아무도 안 들고 있으면 GC) — `rawExtract`/`rawSwap`/`rawClear`도 (위 "모든 공개 CRUD는 가드+위임" 구조상) 당연히 존재하지만, `:List`의 reconcile 알고리즘 자체가 그 - 셋을 쓸 일이 없을 뿐(제거는 항상 파괴 확정이라 `Extract` 아닌 `Remove` - 경로, 리오더는 항상 절대 위치 이동이라 `Swap` 아닌 `Move` 경로, - `Clear`는 reconcile 단위가 아니라 Slot 전체 단위 연산이라 무관). + 셋을 직접 호출할 일이 없을 뿐 — **[정정, 2026-08-13 감사] "제거는 + 항상 파괴 확정이라 Extract 아닌 Remove 경로"라는 예전 근거는 언마운트 + 전환으로 이제 틀림**(바로 위에서 정정했듯 reconcile의 제거는 이제 + 비파괴 `rawUnmount`이고, 이 함수 자체가 `rawRemove`의 비파괴 + 짝으로서 `Extract` 계열과 공유하는 저수준 프리미티브 — 위 코드 + 블록의 "rawRemove의 비파괴 짝 — `:List`의 reconcile과 `Extract` + 계열이 씀" 주석 참고). reconcile이 공개 `Slot:Extract` 대신 + `rawUnmount`를 직접 부르는 진짜 이유는 파괴 여부가 아니라, reconcile이 + 이미 자기 `mounted` 맵으로 element를 추적 중이라 `Extract`의 "제거한 + element를 호출자에게 반환" 계약이 불필요하고, 공개 CRUD의 가드/에러 + 체크도 reconcile 내부 상태 일관성상 중복이기 때문. 리오더는 항상 + 절대 위치 이동이라 `Swap` 아닌 `Move` 경로, `Clear`는 reconcile 단위가 + 아니라 Slot 전체 단위 연산이라 무관 — 이 둘의 근거는 여전히 유효. - **리오더는 `Move`(의 가드 없는 버전)** — Parent를 안 건드리는 진짜 저비용 경로. 최소-이동 알고리즘(LIS 기반 등) 자체는 구현 시점 최적화로 미룸, 여기선 계약(파괴 없이 위치만 바뀜)만 확정. diff --git a/.claude/base/store-semantics.md b/.claude/base/store-semantics.md index 1bb4a2f..5de6a2c 100644 --- a/.claude/base/store-semantics.md +++ b/.claude/base/store-semantics.md @@ -1,15 +1,16 @@ # Store 의미론 — 부작용 허용, State는 Source 위의 조합 가능한 캐시 레이어 -**상태**: base — 전부 확정. State/Source 온톨로지는 2026-08-04 검증 -라운드에서 새로 열려 같은 세션 2~4차 라운드에 걸쳐 확정까지 마침 — 최신 -상세는 `base/bind-system-plan.md` 참고. 원본: `.claude/initreq/raw-userinput.md` -"store는 부작용을 허용함" / "state는 어떻게 구현하는가" 절. +**상태**: base — Store가 부작용을 허용한다는 핵심 결정과 State/Source +온톨로지 구조 자체는 확정(2026-08-04 검증 라운드에서 새로 열려 같은 세션 +2~4차 라운드에 걸쳐 확정까지 마침 — 최신 상세는 `base/bind-system-plan.md` +참고). 원본: `.claude/initreq/raw-userinput.md` "store는 부작용을 허용함" / +"state는 어떻게 구현하는가" 절. -> **⚠️ [2026-08-13 첫 실측에서 발견, `question.md` 0-Y]** 아래에서 -> "확정"으로 서술하는 self/deps lazy `State` 핸들 계약이 Luau 양방향 -> 추론과 충돌함이 실측으로 확인됨 — 상세는 -> `base/bind-system-plan.md`의 동일 배너, M0 착수 전 사용자가 확정해야 -> 할 미해결 사안. +> **⚠️ [2026-08-13 첫 실측에서 발견, `question.md` 0-Y] 단, 온톨로지 중 +> 하나 — self/deps를 lazy `State` 핸들로 넘기는 `:Compute`/`:With` 콜백 +> 계약 — 는 "확정"이 아니라 미해결.** Luau 양방향 추론과 충돌함이 +> 실측으로 확인됨 — 상세는 `base/bind-system-plan.md`의 동일 배너, +> M0 착수 전 사용자가 확정해야 할 사안. ## Store는 부작용을 허용하는 게 기본 디자인 @@ -186,10 +187,19 @@ State를 만족하도록 만들고, RefSource라는 별도 타입은 폐기**하 - **동적 키 폴백(`store "key"`)은 이제 `State`가 아니라 `Source`를 반환**하는 것으로 자연히 갱신됨(위 "타입 추론 문제" 절과 연동). -**검증 필요(확정 아님, M0 스파이크 대상)**: `Source`의 `:Compute` -시그니처가 자기 자신(`Source`)과 `State`를 동시에 참조하는 제네릭 -메소드라, Luau 솔버가 재귀 타입 조합에서 막히지 않는지 실제로 검증 -필요(사용자 우려: "솔버가 종종 죽는다"). 구분해서 볼 것: +**[해소됨, 2026-08-13 첫 실측 라운드]** 핵심 질문(Source가 State를 구조적으로 +만족하는 제네릭 메소드 체이닝)은 `luau-test/08-type-source-satisfies-state.luau`로 +실측 통과 확인됨 — 아래 우려대로 "두 제네릭 타입 별칭이 서로를 참조하는 +상호 재귀"는 실제로 위험했지만, 그 아래 제안한 단방향 의존(`State`가 +`Source`를 참조 안 함) 회피책이 그대로 맞아떨어짐. **다만 좁은 잔여 +케이스 하나는 남음**: `State`가 **자기 자신**을 다른 타입 인자로 +재귀 참조하면(`Recursive type being used with different parameters`) +막힘 — 이건 아래 논의 대상이던 "두 타입 간 상호 재귀"와는 다른 문제라 +별도로 `question.md` **0-Y** 하단에 추적 중(사용자 방향: 구울 때 +인라이닝). 아래는 그 판단에 이른 원래 추론 과정(구분 기준 등)이라 계속 +유효한 배경 — `Source`의 `:Compute` 시그니처가 자기 자신(`Source`)과 +`State`를 동시에 참조하는 제네릭 메소드라, Luau 솔버가 재귀 타입 +조합에서 막히지 않는지가 원래 질문이었음. 구분해서 볼 것: - **자기 자신을 가리키는 self 타이핑**(`{ Compute: (self: Source, ...) -> State }` 같은 패턴)은 Luau에서 극히 흔하고 대체로 안전 — 모든 메소드 테이블 클래스가 쓰는 패턴이라 이것 자체가 위험 신호는 아님. diff --git a/.claude/base/tag-plan.md b/.claude/base/tag-plan.md index 5efd46a..02dcb5d 100644 --- a/.claude/base/tag-plan.md +++ b/.claude/base/tag-plan.md @@ -237,6 +237,11 @@ end ## 열린 질문 -없음 — 값 모양/메커니즘/retract/패키지 배치 전부 확정. 이름 자체 -(`Tag`/`Added`/`Removed`/`Merged`)는 다른 가칭들과 같이 용어 정리 대상 -(`.claude/question.md`). +값 모양/이름(`Tag`/`Added`/`Removed`/`Merged`, 용어 정리 대상, +`.claude/question.md`) 자체는 없음. **단, 이 문서 최상단 배너가 이미 +경고하듯 `hintValue`/`retractFrom` 선행 호출 메커니즘은 `question.md` +**0-Z**(Attribute 이름 소유권) 해소 대기 중인 "하강 diff" 재디스패치 +모델로 교체 예정** — `research/dispatch-redispatch-diff-plan.md` 6절이 +이 파일을 반영 대상으로 명시 지목(`isTag(hintValue)` 가드 제거 등). +이 절이 예전엔 "없음"으로만 적혀 있었던 건 그 배너가 붙기 전 작성된 +서술이 안 갱신된 stale — 재발 방지용으로 여기 명시. diff --git a/.claude/luau-test/15-type-compute-trailing-deps-typepack.luau b/.claude/luau-test/15-type-compute-trailing-deps-typepack.luau index 157e486..fbdfbf0 100644 --- a/.claude/luau-test/15-type-compute-trailing-deps-typepack.luau +++ b/.claude/luau-test/15-type-compute-trailing-deps-typepack.luau @@ -20,6 +20,17 @@ 실행: luau-analyze 15-type-compute-trailing-deps-typepack.luau (또는 luau-lsp) + + [정정, 2026-08-13 첫 실측 라운드] 위 실행 결과는 **파싱 실패 + (SyntaxError)** — 아래 (C) 음성 대조군의 옛-순서 타입 표기가 + `TypeError`가 아니라 `SyntaxError`로 걸려서 파일 전체가 파싱 단계에서 + 막힘. 그 결과 (A)/(B)/(D)를 포함해 이 파일의 어떤 섹션도 개별적으로 + 검증되지 못한 상태 — 위 "이미 정정되어 확정됨"/"남은 진짜 불확실성은 + (B)뿐"이라는 서술은 재작성 전까지는 검증되지 않은 기대치로 읽을 것. + 재작성 시 (C) 대조군을 별도 파일/블록으로 격리해야 함. 상세는 + `luau-test/STATUS.md` 🟠 항목. 다만 `:Compute(fn)` lazy 핸들 계약 + 충돌 자체(`question.md` 0-Y)는 이 파일과 별개로 다른 최소 재현으로 + 이미 확인됨 — 이 파일의 파싱 실패가 0-Y 판단을 바꾸지 않음. ]] export type State = { diff --git a/.claude/reference/comparison-fusion-vide.md b/.claude/reference/comparison-fusion-vide.md index 63248ee..4d45c73 100644 --- a/.claude/reference/comparison-fusion-vide.md +++ b/.claude/reference/comparison-fusion-vide.md @@ -24,10 +24,16 @@ Store/Slot/Tween/bind-dispatch 설계 결정에 근거로 인용될 때만 열 priority 레지스트리보다 약함. - **Tween/Spring이 State그래프 안의 1급 노드** — 매 프레임 틱하는 외부 Stopwatch/ExternalTime 소스에 의존, 애니메이션-입력 간 별도 lifetime 체크 - 기계장치 필요. **quad가 트윈을 Store 밖(특수 bind key)으로 빼려는 이유의 - 반면교사**: Fusion처럼 그래프 안에 넣으면 Computed의 입력으로 자유롭게 합성 - 가능해지지만, 그 대가로 프레임 클럭 통합 + eager 노드 + 교차 lifetime 체크 - 3중 복잡도를 떠안음. + 기계장치 필요. **quad가 Tween을 반응 그래프의 1급 노드로 만들지 않은 + 이유의 반면교사**: Fusion처럼 그래프 안에 넣으면 Computed의 입력으로 + 자유롭게 합성 가능해지지만, 그 대가로 프레임 클럭 통합 + eager 노드 + + 교차 lifetime 체크 3중 복잡도를 떠안음. **[2026-08-13 정정] "Store 밖 + 특수 bind key" 표현은 2026-08-10 폐기된 quad 구모델( + `archive/tween-special-bind-key-reversed.md`) 서술 — 현재 quad의 실제 + 결론은 Tween을 별도 bind key가 아니라 `Tween(opts) -> Tween` + 값-레벨 래퍼로 만들어 Property 타입 자리(`T|Tween`)에 꽂는 것. + 이 문단의 Fusion 반면교사 논리(그래프 1급 노드화의 3중 복잡도) 자체는 + 두 모델 다에 여전히 유효, 인용하는 결론 쪽 이름만 stale했던 것. ## Vide (`.claude/initreq/vide/`) diff --git a/.claude/research/documentation-content-map.md b/.claude/research/documentation-content-map.md index fa48e4d..f691ced 100644 --- a/.claude/research/documentation-content-map.md +++ b/.claude/research/documentation-content-map.md @@ -36,7 +36,7 @@ 10. **생명주기** — GC 위임(수동 정리 불필요), Destroy 이후 대상 재사용 금지 (`lifecycle-pattern.md`) 11. **Ref 기초** — 외부 관리 Instance 참조/마이그레이션용, `Ref(default):Callback(fn)`을 children 배열 숫자 슬롯에 직접 놓기 + 배열 위치로 자식 전/후 표현, "프로퍼티보다도 먼저" 필요할 때만 `PreRef`(2026-08-07 세 번째 세션, `phase` 옵션 폐기) (`architecture.md`, `bind-system-plan.md`) 12. **파생값 최소 예시** — `:With(...)` + `:Compute(fn)` 기본형 (`bind-system-plan.md`, `store-semantics.md`) -13. **Tween 기초** — `[Tween(key, ...)] = storeValue`, 취소 시 현재 보간값에서 자연스럽게 이어짐 (`base/tween-plan.md`) +13. **Tween 기초** — ~~`[Tween(key, ...)] = storeValue`~~ **[2026-08-13 정정] 구모델(폐기, `archive/tween-special-bind-key-reversed.md`) — 실제로는 `Tween(opts) -> Tween` 값-레벨 래퍼**, 취소 시 현재 보간값에서 자연스럽게 이어짐 (`base/tween-plan.md`) 14. **UI 숏핸드(quad-roblox 한정)** — `UICorner`/`UIPadding`/`UIPaddingOffset`/`UIScale` 인라인 키 (`base/ui-shorthand-plan.md`) --- @@ -108,8 +108,8 @@ v1 폐기 API/버그/구조 결함 전부 v2 설계를 정당화하는 내부 ### store-semantics.md / tween-plan.md / ui-shorthand-plan.md - 초심자: Store 생성+`myStore.key:Set(value)` 문법 / `store.key`로 State 얻기 개념 / Tween 기본 바인드 키+취소 기본 동작 / UI 숏핸드 인라인 키 기본 예시(`Frame { UIPaddingOffset = 50 }`) -- api: `:With`+`:Compute` 시그니처(→심화) / `source:Emit()` 존재+"Get() 결과 캐시 금지" 캐비엇(버그 유발 포인트라 api에도 명시 가치 있음, →심화; 2026-08-06 후속 세션에서 `Store:Emit(key)`→`source:Emit()`로 호출부 변경, `store-semantics.md` 참고) / Tween 핸들러가 Instance 직접 받음(Ref 불필요) / retract는 Destroy 시 호출 안 됨(→심화) / UI 숏핸드 키 목록 레퍼런스 표 / Modifier와 순수 인라인 키 동등성 -- 심화: Source·Store·State·Observer 온톨로지(독립 프리미티브 vs 파생 데이터 원칙, 생성자 모양 근거) / `Emit`이 Source 전용인 이유(디버깅 그래프 무결성) / `Store`의 T가 Modifier 불가인 이유 / Tween을 반응 그래프 밖 특수 bind key로 둔 이유(Fusion 반면교사) / RoundSize 포팅 불필요 vs UICorner/UIPadding/UIScale 필요 이유 / "작고 opt-in 아닌 편의 기능은 코어 포함" 원칙 +- api: `:With`+`:Compute` 시그니처(→심화) / `source:Emit()` 존재+"Get() 결과 캐시 금지" 캐비엇(버그 유발 포인트라 api에도 명시 가치 있음, →심화; 2026-08-06 후속 세션에서 `Store:Emit(key)`→`source:Emit()`로 호출부 변경, `store-semantics.md` 참고) / ~~Tween 핸들러가 Instance 직접 받음(Ref 불필요)~~ **[2026-08-13 정정] 구모델(폐기) — 실제로는 `Tween(opts)` 값-레벨 래퍼가 Property 자리에 놓이고 `PropertyHandler`가 `isTween`으로 분기** / retract는 Destroy 시 호출 안 됨(→심화) / UI 숏핸드 키 목록 레퍼런스 표 / Modifier와 순수 인라인 키 동등성 +- 심화: Source·Store·State·Observer 온톨로지(독립 프리미티브 vs 파생 데이터 원칙, 생성자 모양 근거) / `Emit`이 Source 전용인 이유(디버깅 그래프 무결성) / `Store`의 T가 Modifier 불가인 이유 / ~~Tween을 반응 그래프 밖 특수 bind key로 둔 이유(Fusion 반면교사)~~ **[2026-08-13 정정] 이 근거 자체가 폐기된 구모델 서술 — 현재는 Tween이 반응 그래프 "밖"이 아니라 Property 값 타입 치환(`T|Tween`)으로 자연스럽게 들어와 있음, `base/tween-plan.md` 참고** / RoundSize 포팅 불필요 vs UICorner/UIPadding/UIScale 필요 이유 / "작고 opt-in 아닌 편의 기능은 코어 포함" 원칙 - 열린 질문(문서화 보류): tween-plan.md의 오버라이드/삭제후재시작/끝점이동 옵션 키 이름 미정 / ui-shorthand의 RoundSize 완전 드롭 여부 - skip: 세션 정정 이력, v1 소스 조사 경위 diff --git a/.claude/research/pre-implementation-audit.md b/.claude/research/pre-implementation-audit.md index a4bcedf..d62b8a8 100644 --- a/.claude/research/pre-implementation-audit.md +++ b/.claude/research/pre-implementation-audit.md @@ -356,6 +356,17 @@ M7의 전제가 Luau 공식 동작대로 성립함. 별도로, 프로퍼티에 A ### 2-1. Source가 State를 만족하는 제네릭 검증이 실패했을 때의 대안(Plan B)이 전혀 없음 +**[해소됨, 2026-08-13 첫 실측 라운드]** 검증 자체는 +`luau-test/08-type-source-satisfies-state.luau`로 실행돼 핵심 질문(Source가 +State를 구조적으로 만족)이 통과했음 — 아래 "검증이 실패했을 때"라는 +전제 자체가 (핵심 케이스에 한해) 더 이상 미래형이 아님. 다만 통과와 +별개로 좁은 잔여 케이스(`State`가 자기 자신을 다른 타입 인자로 +재귀 참조하는 경우, `Recursive type being used with different +parameters`)가 하나 발견돼 `question.md` **0-Y** 하단에서 추적 중 — +이건 "검증 실패 시 Plan B 없음"과는 다른 종류의 문제(전면 실패가 +아니라 narrow edge case)라 아래 원래 제안(M0 스파이크에 폴백 한 줄 +박아두기)은 더 이상 적용 대상 없음. 원래 서술은 배경 기록으로 남김: + **위치**: `base/store-semantics.md` "Source가 State를 만족함" 절 — "검증 필요(확정 아님, M0 스파이크 대상)... 다만 이것도 추론이라 실제 Luau로 확인 전엔 확정 아님." diff --git a/ROADMAP.md b/ROADMAP.md index 6d0f677..88a9ce5 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -268,6 +268,11 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검 ## M6 — Slot +> **⚠️ M2의 ⚠️ 배너와 같은 주의** — 아래 "`SlotHandler.process`는 claim +> 실패 시에도 파괴적 클로저를 반환해야 함(`retractFrom`은... 항상 소비)" +> 항목은 현행(교체 예정) 재-dispatch 모델을 전제로 쓰여 있음. +> `question.md` 0-Z 먼저 해소할 것. + - [ ] **[2026-08-13 여섯 번째 세션 — 이 세션의 Slot 결정 전부, 구현 전 필독]** - **`State` 교체 = 파괴가 아니라 언마운트**(`state`와 동일). 비파괴 경로 `unmountSlotTree`를 `destroySlotTree`와 별도로 구현 —