docs(audit): 코퍼스 3차 감사 — 자기모순/stale 실측결과 미반영 7건 정정

병렬 에이전트 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 <noreply@anthropic.com>
This commit is contained in:
qwreey 2026-08-13 17:08:49 +09:00
parent 9f9e83bc5b
commit 1aa01c60fb
Signed by: qwreey
GPG key ID: D28DB79297A214BD
8 changed files with 84 additions and 26 deletions

View file

@ -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 기반 등) 자체는 구현 시점 최적화로
미룸, 여기선 계약(파괴 없이 위치만 바뀜)만 확정.

View file

@ -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<any>`가 아니라 `Source<any>`
반환**하는 것으로 자연히 갱신됨(위 "타입 추론 문제" 절과 연동).
**검증 필요(확정 아님, M0 스파이크 대상)**: `Source<T>``:Compute`
시그니처가 자기 자신(`Source<T>`)과 `State<U>`를 동시에 참조하는 제네릭
메소드라, Luau 솔버가 재귀 타입 조합에서 막히지 않는지 실제로 검증
필요(사용자 우려: "솔버가 종종 죽는다"). 구분해서 볼 것:
**[해소됨, 2026-08-13 첫 실측 라운드]** 핵심 질문(Source가 State를 구조적으로
만족하는 제네릭 메소드 체이닝)은 `luau-test/08-type-source-satisfies-state.luau`
실측 통과 확인됨 — 아래 우려대로 "두 제네릭 타입 별칭이 서로를 참조하는
상호 재귀"는 실제로 위험했지만, 그 아래 제안한 단방향 의존(`State`가
`Source`를 참조 안 함) 회피책이 그대로 맞아떨어짐. **다만 좁은 잔여
케이스 하나는 남음**: `State<T>`가 **자기 자신**을 다른 타입 인자로
재귀 참조하면(`Recursive type being used with different parameters`)
막힘 — 이건 아래 논의 대상이던 "두 타입 간 상호 재귀"와는 다른 문제라
별도로 `question.md` **0-Y** 하단에 추적 중(사용자 방향: 구울 때
인라이닝). 아래는 그 판단에 이른 원래 추론 과정(구분 기준 등)이라 계속
유효한 배경 — `Source<T>``:Compute` 시그니처가 자기 자신(`Source<T>`)과
`State<U>`를 동시에 참조하는 제네릭 메소드라, Luau 솔버가 재귀 타입
조합에서 막히지 않는지가 원래 질문이었음. 구분해서 볼 것:
- **자기 자신을 가리키는 self 타이핑**(`{ Compute: <U>(self: Source<T>, ...) -> State<U> }`
같은 패턴)은 Luau에서 극히 흔하고 대체로 안전 — 모든 메소드 테이블
클래스가 쓰는 패턴이라 이것 자체가 위험 신호는 아님.

View file

@ -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 — 재발 방지용으로 여기 명시.

View file

@ -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<T> = {

View file

@ -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<T>`
값-레벨 래퍼로 만들어 Property 타입 자리(`T|Tween<T>`)에 꽂는 것.
이 문단의 Fusion 반면교사 논리(그래프 1급 노드화의 3중 복잡도) 자체는
두 모델 다에 여전히 유효, 인용하는 결론 쪽 이름만 stale했던 것.
## Vide (`.claude/initreq/vide/`)

View file

@ -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<T>` 값-레벨 래퍼**, 취소 시 현재 보간값에서 자연스럽게 이어짐 (`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>`의 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>`의 T가 Modifier 불가인 이유 / ~~Tween을 반응 그래프 밖 특수 bind key로 둔 이유(Fusion 반면교사)~~ **[2026-08-13 정정] 이 근거 자체가 폐기된 구모델 서술 — 현재는 Tween이 반응 그래프 "밖"이 아니라 Property 값 타입 치환(`T|Tween<T>`)으로 자연스럽게 들어와 있음, `base/tween-plan.md` 참고** / RoundSize 포팅 불필요 vs UICorner/UIPadding/UIScale 필요 이유 / "작고 opt-in 아닌 편의 기능은 코어 포함" 원칙
- 열린 질문(문서화 보류): tween-plan.md의 오버라이드/삭제후재시작/끝점이동 옵션 키 이름 미정 / ui-shorthand의 RoundSize 완전 드롭 여부
- skip: 세션 정정 이력, v1 소스 조사 경위

View file

@ -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<T>`가 자기 자신을 다른 타입 인자로
재귀 참조하는 경우, `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로 확인 전엔 확정 아님."

View file

@ -268,6 +268,11 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검
## M6 — Slot
> **⚠️ M2의 ⚠️ 배너와 같은 주의** — 아래 "`SlotHandler.process`는 claim
> 실패 시에도 파괴적 클로저를 반환해야 함(`retractFrom`은... 항상 소비)"
> 항목은 현행(교체 예정) 재-dispatch 모델을 전제로 쓰여 있음.
> `question.md` 0-Z 먼저 해소할 것.
- [ ] **[2026-08-13 여섯 번째 세션 — 이 세션의 Slot 결정 전부, 구현 전 필독]**
- **`State<Slot>` 교체 = 파괴가 아니라 언마운트**(`state<Frame>`와 동일).
비파괴 경로 `unmountSlotTree``destroySlotTree`와 별도로 구현 —