- CLAUDE.md 아홉 번째 세션 항목 제목이 "미보장으로 역전"으로 남아 본문
(철회·보장 유지)과 정면 모순 → 정정
- CLAUDE.md 여섯 번째 세션 항목이 "OnRendered는 지금 의도적으로 구현 안 함"을
현재형 확정으로 서술 중 → 아홉 번째 세션 역전 포인터 추가
- research/documentation-content-map.md:
(1) "PreRef는 retract 체인에 아예 안 올라감" — 2026-08-14 두 번째 세션의
ProcessedPreRef 정정이 미반영된 옛 근거였음(이번 변경과 무관한 기존
stale) → 정정, PostRef도 동일하다고 명시
(2) hook/pre-hook 프레이밍에 PostRef(post-hook) 3항 확장 + 순서 보장
왕복을 에세이 소재로 메모
(3) 서베이가 2026-08-06 파일 구성 기준이라 bind-system-plan.md 포인터가
전부 분할 전 것 → 파일 상단에 읽는 법 배너
- luau-test/README.md: 13번 재작성 시 PostRef까지 커버할 것
- base/lifecycle-hooks-plan.md: 시그니처의 `Instance`는 가독성용 표기이고
실제로는 백엔드 타입을 모르는 자리라는 주석(quad-base 소속과의 정합)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
직전 커밋(bcd02f1)에서 미보장으로 뒤집었던 것을 같은 세션에 철회.
반례는 `FastQuery(...) -> PreRef`류 조합 — 앞자리 항목이 뒤 항목의
전제를 만들어주는 정당한 합성이고, children 마운트 선후가 배열 위치로
정해지는 것과 같은 성격. 보장 비용 0(pre-pass가 어차피 index 순서)이고
배열 파트 index 순서는 이미 백엔드 이식성 때문에 명시적 계약이라 새로
내주는 자유도 없음. 보장 + 스타일 권고를 같이 기재.
- archive/preref-order-guaranteed-reversed.md → preref-order-unguaranteed-withdrawn.md
(제안·철회 양쪽 논거 보존)
- OnRendered의 "부모에 붙기 전에 불림" 캐비엇은 사용자 지시대로 이름 유지 +
명시만(ref-plan "보장 범위" 항목, lifecycle-hooks-plan ⚠️ 항목)
- ref-plan/lifecycle-hooks-plan/ROADMAP/README/CLAUDE.md/세션 로그 동기화
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- base/ref-plan.md: "PostRef" 절 신설(PreRef의 거울상 — pre-pass 공동 수집,
ProcessedPostRef 센티널+전담 Handler, 동적 경로 가드, _fired, 타입 차단).
보장 범위를 명시: 자기 서브트리 완성은 보장하되 이 인스턴스가 부모에
붙기 전에 불림(React componentDidMount와 다름).
- 복수 PreRef/PostRef의 계열 안 fire 순서를 "배열 index 순서 보장"에서
미보장으로 역전 — 구현이 아니라 계약만 좁힘
(archive/preref-order-guaranteed-reversed.md 신설).
- research/lifecycle-hooks-plan.md → base/ 승격, OnRendered 채택 반영
(마지막 열린 항목이던 채택 여부/메커니즘/스코프/패키지 전부 확정).
- dispatch-core-plan/brand/architecture/slot/modifier/typing-limits/README/
ROADMAP/question.md 전파. doc-check.py ERROR 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
워크트리(worktree-debounce-throttle-plan)에서 네 라운드로 다듬은 결과를
메인의 3단계 분할 구조에 맞춰 필요한 변경만 이식.
## 신설: research/debounce-throttle-plan.md
- Blocker가 이미 쓰는 게이티드 노드의 릴리스 트리거만 타이머로 바꾼 것.
공개 Blocker API엔 "상류 신호 도착" 통지가 없어 그 위엔 못 얹음 →
M3에서 게이트를 공용 Gate로 뺄 것.
- 두 도구의 차이는 "신호가 창 타이머를 리셋하는가" 한 비트뿐.
공개 생성자 2개 + 내부 구현 1개(초안이 옮겨온 lodash식 maxWait 공식엔
trailing 통과 직후 이중 발화 버그가 있었음).
- quad-base + 주입 op 2개: setTimeout(func, delay) -> Timeout /
clearTimeout. Roblox는 task.delay/task.cancel로 배선(인자 순서 반대).
os.clock()은 Luau 표준 라이브러리라 주입 대상 아님(diff 전용).
Timeout = { __type_timeout: true, _native: any }.
## 역전: emit은 자기 invalid 상태와 무관하게 항상 전파된다
source-state-plan.md의 "이미 invalid였다면 그 아래로 더 전파하지 않는다"가
확정된 Observer 계약(fn이 :Get()을 안 불러도 됨)과 정면 충돌 — 액면대로면
:Get() 안 하는 Observer는 한 번 울고 영구 침묵. architecture.md가 같은
다이아몬드 문제를 pull-recompute로 설명하는 것과도 어긋나 있었음.
정정 모델: invalid는 캐시 낡음 표시일 뿐, 중복 재계산은 pull-recompute+
캐시가 막고 중복 통지는 안 접음(접으려면 Blocker 같은 명시적 게이트).
- source-state-plan.md: 전파 규칙 재작성, "다이아몬드 의존성은 무엇이
푸는가" 절 신설, Observer 절 상호 참조. 플래튼 기각/:With 빌더 기각
근거를 캐시 공유로 재작성(두 결론 유지, 근거 강도는 상승)
- architecture.md, blocker-plan.md(전파를 지연시키는 유일한 요소로 위치
명문화), comparison-fusion-vide.md, framework-comparison-findings.md
- ROADMAP M0 체크리스트: 확인할 것이 정반대가 됨
- luau-test 05 → rewrite-required/(옛 모델을 통과 상태로 검증 중이었음),
STATUS.md 개수 동기화(rewrite 6→7, done 14→13)
- audit: 05 행 정정 + "12개 전원 통과"를 액면대로 읽지 말라는 경고
- archive/invalidate-dedup-propagation-reversed.md 신설
doc-check: ERROR 0 / WARN 84(작업 전 85).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
세 건을 한 커밋에 처리:
1. ui-shorthand-plan.md — Tween 지원 확정. 숏핸드가 자식 프로퍼티를 직접
대입하지 않고 Dispatch.process(child, prop, ..., 1)로 위임하면 Tween이
공짜로 따라옴(해석 코드는 PropertyHandler 하나에만 남음). "process 중
inst를 바꾸는 건 키를 바꾸는 것과 같은 층위라 UB 아님"을
dispatch-core-plan.md에 일반 규칙으로 명문화. wrap을 Tween<T>.Value에만
적용되도록 들어올리는 헬퍼가 새로 필요한 유일한 부품. ROADMAP M10에
통째로 빠져 있던 UI 숏핸드 항목도 보강.
2. existing-instance-bind — 기각, research/ → archive/. 사유: Length/Offset
등 quad가 만든 트리를 전제한 부기를 바깥에서 밀고 당기는 버그 표면이
치명적으로 넓어짐. "열려 있음"을 전제로 쓰인 본문 7곳도 같이 정정
(architecture.md의 "아직 미정" 절은 유일 항목이었어서 절 자체를 갱신).
3. bind-system-plan.md 3단계 분할 + store-semantics.md 흡수(순수 이동):
- base/store-plan.md 신설 — Store = 이름 붙은 Source 모음
- base/source-state-plan.md 신설 — 반응형 코어(Source⊇State, 전파 모델,
:With/:Compute/:Apply/previous, Observer, 구독·생명주기 게이트)
- bind-system-plan.md 1238→203줄(인스턴스 생성·이벤트 네이밍 + 색인)
- store-semantics.md 삭제
참조 40여 곳 스윕. doc-check.py ERROR 0, WARN 101→84.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
pcall 기반 Fallback과 xpcall+debug.traceback 기반 Traceback으로 분리,
정확한 제네릭 시그니처(OkComp/ErrComp/Args... 독립 제네릭)와 err: any를
확정. 패키지(quad-base)·이름(Fallback/Traceback) 확정으로 남은 열린
질문이 없어져 base/로 승격. 스파이크를 audit/fallback-xpcall-spike.luau로
옮기고 base/fallback-plan.md와 이름을 맞춰 내부 함수를 Traceback으로
정정, audit/fallback-xpcall-verification.md에 실측 결과 기록. README/
question.md/archive/question-resolved.md/lifecycle-hooks-plan.md의 상호
참조 동기화.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
session/2026-08-14-06-lifecycle-hooks-plan.md로 OnCreated/OnDestroyed
백로그 세션 전체 기록(워크트리 조사, 사용자 refinement 두 차례, 메인
병합, 동시 편집 충돌 대응까지) 신설. 다른 세션이 04/05를 이미 써서
CLAUDE.md 세션 히스토리에서 이 항목을 원래 "세 번째" 자리에서 맨 끝
"여섯 번째"로 옮기고 파일명(03→06)/역참조를 재조정 — 실제 작업 순서와
문서 번호가 어긋나는 이유를 양쪽에 메모로 남김.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
canExecute(inst,value) 2-인자 시그니처를 폐기하고 canExecute(value)로 정정.
2-인자는 증상이었고 원인은 2026-08-08 세션이 .Subscribed에 "leaf 바인딩
생존"이라는 두 번째 의미를 겹쳐 얹은 것 — .Subscribed는 전역 :Subscribe()
전용 필드이고 bindLifetime과 무관함. bindLifetime이 바인딩 시점에 inst의
gcconn 참조를 value 쪽 Relate로 복사해두면 생존을 value 하나로 물을 수 있음.
- canBound(handle) 폐기 → canExecute(value)로 통합(이중 바인딩 게이트 겸함)
- unbindLifetime도 1-인자로 축소 — 호출부가 _mountedInst를 되짚을 필요 없어져
"홀더가 갈아치워지면 해제가 빗나가는" 잠재 버그 클래스 소멸(slot-plan 5곳)
- gcconn/gchold를 lazy 생성에서 Instance 생성 시점으로 전환, 클로저가 inst까지
캡처 — Instance userdata 포인터 동일성은 inst-키 Relate 전체의 전제였음
(relate-plan에 "전제" 절 + "안전히 유지되면 항상 SetWeak" 일반 규칙 신설)
- canExecute의 실제 호출부를 State 전파 루프로 명시(구독자 weak + 발화마다
게이팅) — 이게 코드로 한 번도 안 적힌 게 오류가 여섯 세션 살아남은 이유
역전 원문/오염 경로/교훈은 archive/canexecute-inst-arg-reversed.md.
luau-test/10은 폐기된 모델을 검증 중이라 rewrite-required/로 이동.
부수: 3~4차 세션이 남겨둔 CLAUDE.md 세션 히스토리 항목과 4차 세션 로그
파일도 미커밋 상태여서 같이 실림. doc-check ERROR 1건(CLAUDE.md:1156 →
session/2026-08-14-03-lifecycle-hooks-plan.md)은 그 파일이 디스크 어디에도
없어서 남음 — 3차 세션 쪽에서 채워야 함.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
component-fallback-plan.md가 열어뒀던 "xpcall 에러 핸들러 배선 실측
필요" 질문을 luau 스파이크(component-fallback-xpcall-spike.luau)로
확인 — 클로저 업밸류 배선, 3단 중첩 debug.traceback 캡처 등 10개 검증
전부 통과. 부수적으로 error(msg) 기본 호출이 위치 접두("파일:줄: ")를
자동으로 붙인다는 캐비엇을 새로 확인해 "프로덕션에서의 동작" 절에 반영.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
컴포넌트 함수를 감싸 에러 시 플레이스홀더를 그려주는 pcall/xpcall 래퍼
아이디어를 백로그로 문서화(research/component-fallback-plan.md), README/
question.md/CLAUDE.md 인덱스 반영. 후속 code-review로 발견된 결함(코드
스팬이 줄바꿈에 걸쳐 깨진 곳 4군데, 워크트리가 계획 문서 없이 시작되는
원인을 "git 미추적"으로 오진단했던 세션 로그 서술)도 같이 정정.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
다른 에이전트 감사와 사용자 트레이싱으로 69466ab에서 나온 지적 반영.
핵심 알고리즘(하강 diff/인덱스 체인)엔 버그 없음이 확인됐고, 의사코드와
문서 정합성에서 나온 것들:
1. `nameClaims`가 Relate의 3-인자 계약 위반(`GetStrong(inst)` /
`SetStrong(inst, claims)`) → `(inst, name[, key])`로 정정. 같은 커밋의
`tagNameMap`은 이미 3-인자였어서 대조로 드러남.
2. TagHandler가 생존 이름의 홀더를 비웠다가 곧이은 process에서 `addTag`를
헛되이 재호출 — 문서 서술("addTag 자체가 안 불림")과 정반대였음.
생존 이름은 홀더를 유지하도록 정정하고, `addTag`도 `removeTag`처럼
배치 호출로 통일(`{string}` 시그니처 도입 근거와 맞춤).
3. 그룹 process의 부분 실패 경로(순회 중 충돌 error) 문서화 — 피해가
인스턴스 수명으로 한정되고 재현이 시끄럽게 반복됨을 트레이싱으로
확인, 롤백 장치는 안 넣고 question.md 3번에 열어둠.
doc-check.py: REF 정규식이 줄 단위라 파일명과 절 제목이 줄바꿈에 걸친
인용을 통째로 놓치고 있었음(이번 2단계 분할뿐 아니라 9차 세션 1단계
분할 stale까지 숨어 있던 원인). 파일 전체 스캔 + 개행 허용으로 고치고,
새로 드러난 stale 참조 30여 곳을 dispatch-core/event-plan/ref-plan/
typing-limits로 정정. 오탐 없음(참조–인용 스팬 3줄 미만 전수 확인).
부수: 세션 로그에 리뷰 라운드 절 추가(커밋 메시지 줄수 오기 2291→1213
정정 포함), CLAUDE.md의 스파이크 이동 서술에 `19` 누락 보강,
luau-test/README 상단 요약표를 STATUS.md와 동기화.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KckSawrsJSJmDBcSojJPxZ
`question.md` 최우선 두 건을 한 패스로 닫고, 배너를 달고 있던 7개 문서 +
인덱스 레이어 전체를 갱신했다.
0-Z (Attribute 이름 소유권):
- 그룹은 비공개 `GetKey`로 이름마다 자기 전용 키를 써서 위임 → 교차 오염
구조적 제거. 이름 소유권은 `AttributeKeyHandler`의 이름 claim이 판정
(`nameClaims` Relate, 다른 키가 같은 이름을 노리면 즉시 error).
- 권고안 (a)(그룹 안 claimant Relate)는 그룹↔직접 쓰기를 못 잡아 기각 —
두 경로가 만나는 말단 핸들러에서 공개 키는 같은 객체라 소유자 구분 불가.
0-A (재디스패치 = 하강 diff):
- 래핑 핸들러의 선행 `retractFrom` 폐기, `Dispatch.process`가 슬롯의
`handler`를 먼저 비교(같으면 클로저에 새 값 전달 후 재process, 다르면
그 자리부터 전량 철거).
- 귀결: `retractFrom`이 3-인자로 축소(힌트를 외부에서 만들어 넣을 자리
소멸), `isX(hintValue)` 가드 규칙 폐지, 깊은 체인 힌트 유실 캐비엇 삭제,
Dispatch의 점유 체크 폐지.
- 9차 세션이 미뤄둔 2단계 분할을 같이 수행 — 디스패치 코어를
`base/dispatch-core-plan.md`로 분리하며 재작성(bind-system-plan은
2263→1219줄). 옛 모델은 archive/dispatch-hintvalue-model-reversed.md.
패키지 재배치 (사용자 제기):
- Tag/Attribute의 부기 알고리즘 전체를 quad-base로, 백엔드는
`addTag`/`removeTag(inst,{string})`/`setAttribute(inst,name,v)` 3개 op만
주입(웹 className/data-* 대응). 엔진 고유 타입 패밀리만 백엔드.
- `HANDLER_PRIORITY_FALLBACK` 신설 — base 제공 핸들러의 밴드, 백엔드가
평범한 우선순위로 덮어쓰면 언제나 이김.
부수: 스파이크 04/19가 옛 모델을 검증 중이라 rewrite-required로 이동,
question.md 최우선 칸 비움, 새 소소 항목 2건 등록(Merged 이름 중복,
`hintValue` 이름 재검토). doc-check ERROR 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KckSawrsJSJmDBcSojJPxZ
44개 스파이크로 question.md 0-Y를 재실측한 결과, 여섯 번째 세션의
"콜백이 raw 값을 받으면 완전 클린" 판정이 틀렸음이 드러남 — 그건 진단
0건만 확인한 것이었고, luau-analyze --annotate로 열어보니 반환 타입이
Unifiable<Error>로 조용히 새고 있었음(틀린 대입도 안 잡힘).
진짜 원인은 콜백 계약이 아니라 Compute가 State<U>(자기 이름을 다른 타입
인자로 감싼 타입)를 반환한다는 것 자체 — RFC relax-recursive-type-restriction이
Promise<T>.andThen으로 예시 든 바로 그 패턴. 사용자 확정: quad가 타입을
비틀 일이 아니라 상위 Luau의 현 한계이고, RFC/이슈 수혜를 받을 때 해결될
일이라 당장 할 수 있는 바 없음.
- base/typing-limits.md 신설 — 흩어져 있던 타입 한계 5건 통합, 대전제
"Luau 한계를 우회하려 타입/API를 비틀지 않는다", 새 API 설계 체크리스트
- audit/type-recursion-issue/ 신설 — REPORT.md + spikes 44개(audit 폴더에
스크립트를 같이 둔 첫 예외, 판정 재현에 개별 실행이 필요해서)
- 0-Y 해소 전파: question.md(최우선 2건→1건) / archive / base 5개 /
research 2개 / 인덱스 4개 / luau-test(08을 done/으로, review-required 비움)
- audit/luau-test-first-run-2026-08-13.md: 판정이 뒤집힌 당사자라 배너뿐
아니라 본문 표·문단·결론까지 전수 수정
- HUMAN_TODO 6번 신설: luau-lsp 기본이 옛 솔버라 CLI와 진단이 다름
교훈: luau-analyze 진단 0건은 타입 해소를 뜻하지 않음 — 타입 스파이크는
--annotate로 실제 추론 타입을 확인하고 음성 대조군을 같이 둘 것.
doc-check.py ERROR 0 유지(WARN 59건, 변경 전과 동일).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KckSawrsJSJmDBcSojJPxZ
서브 에이전트 없이 직접 코퍼스 전체를 순회한 감사(base/research/reference/
luau-test/audit/archive 전부 확인, doc-check.py ERROR 0 유지). 발견한
부정확성은 전부 9~10차 세션의 문서 재편 이후 반영이 누락된 stale 카운트/
포인터:
- question.md 0-Z/0-A: 반영 대상 문서 개수가 "6개"로 남아 ref-plan.md가
누락돼 있던 것을 "7개"로 정정(dispatch-redispatch-diff-plan.md 6절과
일치).
- HUMAN_TODO.md 4번: 동일한 6→7 stale 카운트 정정.
- ROADMAP.md 백로그: 2026-08-13 세 번째 세션에 이미 불필요로 해소된
objectListClass.__newIndex 재현 테스트가 여전히 할 일로 남아있던 것 정정.
세션 로그(session/2026-08-13-11-...)와 CLAUDE.md 세션 히스토리 요약도 추가.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6hzeUi5QdLPEk69B6cXFa
9차 세션의 구조 변경(luau-test 재편/bind-system-plan 분할/question.md
트림)이 남긴 반영 누락을 6개 병렬 에이전트로 찾아 즉시 수정:
- luau-test 재편 후 깨진 flat 경로 참조 9곳을 파일명+실제 폴더로 정정
- bind-system-plan.md 분할 후 자기참조/외부참조 깨짐 8곳 정정
- ref-plan.md에 0-Z 배너가 안 옮겨와 옛 재디스패치 모델을 무배너로
서술 중이던 것 발견 — 배너 추가, 반영 대상 6개→7개로 갱신
- "8차 세션"으로 잘못 표기된 9차 세션 작업 17곳(git 커밋 타임스탬프로
교차검증) 정정
- question.md 트림 중 빠진 열린 질문(State<State<T>> 평탄화) 복원,
트림 후 깨진 참조 2곳 정정
- ROADMAP.md M0 섹션에 0-Y/0-Z 게이트 표시 누락 보강
doc-check.py ERROR 0 유지. 새로 연 설계 질문 없음 — 전부 기존 서술
정합성 문제.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6hzeUi5QdLPEk69B6cXFa
사용자 질문: "이런 깊은 감사를 재발 방지하는 방법이 있나. 중대 변경에 대해
그 사항을 아는 세션이 stale 없게 핸드오버해야 하는데 그런 게 없어서 쌓임."
진단: 7·8차 감사에서 찾은 55건이 거의 전부 **변경한 세션이 그 자리에서
안 한 일**이었고, 그중 상당수는 grep으로 잡히는 기계적 종류였음. 그래서
규율 문서만 쓰지 않고 **실제 검사기**를 만듦.
.claude/tools/doc-check.py — 라이브 문서 61개 대상:
[ERROR] 깨진 파일 참조(우리 문서 명명 관례에 맞는 것만; 아직 없는 소스
트리 .luau와 외부 문서명은 오탐이라 WARN으로 분리)
[WARN] 절 참조 불일치 — `foo.md` "절 제목"이 그 파일에 없음
[ERROR] README 색인 누락
[WARN] 날짜 없는 시한부 주장("아직 안 돌려봄"/"열린 질문 없음" 등)
[WARN] 미반영 ⚠️ 배너 단 파일 vs 반영 목록 일치
현재 ERROR 0 / WARN 58(대부분 절 제목 의역 인용, 판단 대상).
**검사기가 즉시 실효를 증명함** — 같은 세션에 bind-system-plan.md를 쪼개며
"이중 바인딩 금지" 절 참조 4곳을 ref-plan.md로 잘못 옮긴 걸(실제로는
Observer/Effect 절이라 이동 대상이 아니었음) 이 스크립트가 잡아내 되돌림.
CLAUDE.md "작업 방식"에 중대 변경 핸드오버 체크리스트 6단계 명문화:
doc-check 실행 → 부정당한 *본문 문장* 전수 grep(배너만 달고 본문 방치가
최다 실패) → 뒤집힌 원문은 archive로 → 개수/상태는 소스 하나만 → 시한부
주장엔 날짜 → 인덱스 레이어 3개 동시 갱신.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6hzeUi5QdLPEk69B6cXFa
사용자 지적: "question.md 또한, 사람이 봐야하는 문서인데 해결된 것이 많아서
필터해서 필요한 부분만 읽어보기 힘듦."
- archive/question-resolved.md 신설 — 분리 직전 전문을 그대로 보존
(결정 내용은 하나도 안 바뀜, 읽는 자리만 이동)
- question.md는 **사용자가 답해야 할 것만** 남김:
⭐최우선 2건(0-Y/0-Z, M0를 막고 있고 사용자가 직접 판단하겠다고 이관)
→ 결정 대기 2건(0-A 컨텍스트/0-B) → 용어 미확정 6건 → 낮은 우선순위 5건
- 통째로 해소된 옛 "0번(추가 프리미티브)"/"2번(구현 착수 직전 감사)"과
맨 아래 확정 요약표(README base 표와 중복)는 archive로
- 재발 방지: "항목을 해소하면 여기서 지우고 archive로 옮길 것"을 문서 상단에
명시
인바운드 참조 보호: 절 번호 1/3은 헤딩에 유지(참조 ~10곳), 없어진 0번/2번을
가리키던 CLAUDE.md·effect-plan.md 2곳은 실제 원본(pre-implementation-audit.md,
archive)으로 재지정. 없어진 번호에 대한 안내를 question.md 하단에 남김.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6hzeUi5QdLPEk69B6cXFa
CLAUDE.md 관례대로 세션 로그 신설 + 압축 요약 추가.
6라운드 재검증 결과 16건 추가 발견(9→2→3→2→0→0)으로 수렴 종료.
직전 세션이 "수렴"으로 끝낸 지점에서 나온 것이고, 그중 1건은 문서
정합성이 아니라 실제 의사코드 결함(retractFrom의 nil 가드).
감사 사각지대 둘을 다음 라운드용 일반 교훈으로 기록:
1. 정정 배너를 달면 그 배너가 부정하는 본문 문장까지 같은 커밋에서
고쳤는지 확인 — 직전 감사가 CLAUDE.md 헤더에만 배너를 달고 본문
bullet은 그대로 둔 게 이번 최대 발견의 원인
2. 영역 분할 병렬 감사는 "아무 영역에도 안 속한 파일"을 통째로 빠뜨림
— HUMAN_TODO.md가 6라운드 동안 한 번도 안 열렸고 하필 사람에게 가장
중요한 문서였음. 레포 루트 파일 목록으로 커버리지부터 체크할 것
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6hzeUi5QdLPEk69B6cXFa
실제 의사코드 결함 1건(문서 정합성이 아니라 로직):
- `Dispatch.retractFrom`이 `if retractor then ... end`로 감싸고 있어서,
핸들러가 계약을 어기고 `nil`을 반환해도 조용히 넘어감. 그런데 문서
두 곳(핸들러 계약 절, Handler 작성 체크리스트 7번)은 "생략하면
`attempt to call a nil value`로 크래시"라고 서술 — 그 가드 때문에
크래시조차 안 나는 게 실제였음.
더 나쁜 건 실제 결과: `list[index] = nil`로 **배열에 구멍**이 나서
(a) `#list`가 Lua 명세상 미정의가 되어 retractFrom의 순회 시작점이
어긋나고, (b) 그 자리가 비어 보여 `Dispatch.process`의 점유 체크를
통과 → **소유권 충돌 감지가 조용히 꺼짐**(0-Z가 다루는 바로 그 안전망).
가드에 걸리는 유일한 경우가 계약 위반뿐이므로(정상 경로는 process가
NOOP 마커를 먼저 박음) 프로젝트의 "즉시 error" 원칙대로 error로 전환,
서술 두 곳도 실제 실패 모드로 정정.
CLAUDE.md "계획 문서 구조"에 `luau-test/`와 `audit/`가 통째로 누락돼
있던 것 추가 — 항상 로드되는 진입점인데, 지금 스파이크 상태의 소스
(`luau-test/STATUS.md`)와 0-Y의 근거(`audit/`)로 가는 길이 이 지도에
없었음. README의 `audit/` 행에도 파일 2개 명시(gcconn-trick-verification.md는
README 어디에도 등재돼 있지 않았음).
색인 완전성 전수 확인: base 17 / research 10 / archive 17 / reference 3
전부 README 등재됨(누락은 위 audit 1건뿐이었고 이번에 해소).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6hzeUi5QdLPEk69B6cXFa
1. `research/dispatch-redispatch-diff-plan.md` 6절(0-Z 확정 시 파일별 반영
목록)이 base 4개만 나열 — 그런데 `architecture.md`와 `ROADMAP.md`도
같은 ⚠️ 배너를 달고 있음(전자는 소스트리의 chains/retractFrom 서술,
후자는 M2/M4/M6/M10 체크리스트). 목록대로만 옮기면 이 둘이 옛 모델로
남는 실제 핸드오버 갭이라 6절에 추가하고, "배너 단 파일 = 반영 대상,
반영 후 배너 제거" 규칙으로 명문화. CLAUDE.md 0번/question.md 0-Z·0-A의
"4개" 표기도 6개로 동기화.
※ architecture.md는 모든 세션이 "먼저 읽으라"고 지목하는 진입점이라
누락 시 파급이 가장 큼.
2. CLAUDE.md 세션 히스토리(2026-08-09 열두 번째)의 "**아직 결과 미확인** —
M0 착수 전 최우선 확인 대상"이 현재형으로 읽히던 것 정정.
전수 검증해 문제 없던 것:
- 문서 간 파일 참조 152개 링크 체크 — 라이브 문서(base/research/reference/
README/ROADMAP/question/CLAUDE) 깨진 링크 0건. 깨진 참조는 전부
session/(원문 보존 대상), initreq/(읽기 전용 클론), archive/ 히스토리 안.
- luau-test README/STATUS 파일명 ↔ 실제 파일 일치(04 개명, gc-trigger 헬퍼 포함)
- Handler 계약 정의부(process가 retractor 반환, 반환 생략 불가) 일관
- Dispatch.process/retractFrom 의사코드 손 트레이싱 — 점유 마커 선행,
#list hole 없음, hint가 target 인덱스에만, index>#list일 때 no-op 정상
- Relate API arity(Set 3인자/Get 2인자) 문서 간 일치
- 폐기된 이름(retractUnder/processAs/retractSelfAndUnder) 잔존 8건은 전부
"옛 이름은 X였음" 형태의 의도된 히스토리 표기
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6hzeUi5QdLPEk69B6cXFa
직전 6라운드 감사(에이전트 병렬)가 "수렴"으로 종료했으나, 손으로 재검증하니
가장 영향 큰 층위에서 stale이 남아 있었음:
1. CLAUDE.md "지금 할 일" 1번 본문 — 항상 로드되는 진입점인데 "스파이크
20개 중 19개를 여전히 안 돌려봄"이라고 서술(사실은 여섯 번째 세션에
런타임 12개 전원 통과), "19는 재작성 대기"(사실은 재작성 완료·통과),
"설계는 더 이상 안 막힘"(실측이 0-Y를 새로 염). 4라운드에서 헤더에
정정 배너만 붙이고 본문 bullet은 안 고친 게 원인.
2. question.md 2번 — 같은 "아직 사용자가 안 돌려봄"이 잔존, 자기 문서
최상단 0-Y/0-Z와도 모순.
3. pre-implementation-audit.md — 16/17 "결과 미확인" 잔존(17 통과, 16 실패).
Slot 파괴→언마운트 전환(여섯 번째 세션)의 미반영도 추가로 6곳:
- slot-plan.md 최상단 "상태" 줄이 여전히 "retract=폐기"(앞에서부터 읽는
구현자가 구 모델로 짤 위험 — 4라운드가 같은 사유로 다른 곳을 고쳤던 것)
- reconcile이 rawRemove(파괴)를 부른다는 서술 3곳(391/450/1708 부근).
rawUnmount도 releaseOwner를 부르므로 소유권 논증 자체는 성립 — 함수명만
stale이라 논증 유지 근거를 함께 명시
- question.md 확정 요약표 Slot 행, README.md slot-plan 행(여섯 번째 세션
전환 전체가 색인에 누락 — 언마운트/rawUnmount/portal/dispose 전부)
역전 서사 archive 이전(사용자 지시):
- archive/slot-discard-no-portal-reversed.md 신설 — base/slot-plan.md에
히스토리로만 남아 있던 세 덩어리(2026-08-04 "폐기, 옮기지 않음" 확정,
State<Slot?> 왕복 분석, 포탈 검토+숙제 셋)를 원문 보존해 이전하고 본문엔
요약 포인터만 남김. slot-plan.md 1982 → 1919줄
검증했으나 정확했던 것: 4개 base 문서의 0-Z 배너, architecture.md 0-Z
포인터, ROADMAP 게이트 서술, luau-test/STATUS.md 분류, luau-test/README.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6hzeUi5QdLPEk69B6cXFa
검증 라운드(커밋 없이 직전 5라운드 수정 검증 + D./DI. 전수 재확인 +
6라운드 동안 한 번도 안 건드린 파일 3개 전체 정독)에서 유효한 새 문제
0건 확인, 사소한 표현 하나만 정리(modifier-plan.md 9-2의 "당장의
fallback" 문단이 이미 확정된 결론인데 여전히 조건문 톤이던 것).
발견 건수 추이(8→7→11→9→4→0)로 수렴 판단, 반복 종료. CLAUDE.md 관례대로
세션 기록 신설 + 압축 요약 추가(session/2026-08-13-07-corpus-audit-six-rounds.md).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
5차 감사(파괴/0-Y/0-Z 두 패턴 최종 검증 + 다른 카테고리 전용 탐색)에서
발견한 것 정리:
- modifier-plan.md 9-2/ROADMAP.md M7: Modifier.Overridden 서브타입
이슈가 이미 luau-test/09로 실측 통과됐는데(fallback any 정상 작동
확인) 3곳(ROADMAP/modifier-plan.md/luau-test/README.md 중 실제
"미검증" 서술이 있던 2곳)이 여전히 "미검증"으로 남아있던 것 정정
- ROADMAP.md M6/slot-plan.md: `DI`→`D` 리네임이 question.md 1번에서
아직 미확정인데 `D.InstSlot`으로 앞서서 확정된 것처럼 쓰던 것을
현재 우세 표기 `DI.InstSlot`(+ 미확정이라는 캐비엇)로 정정
- ROADMAP.md M6: 확정·실측 통과된 `Slot:Splice`가 CRUD 체크리스트
항목 제목에서 아예 누락돼 있던 것 추가
- CLAUDE.md: "지금 할 일" 0번이 "0-Y/0-Z 두 결정"이라 명시하는데
question.md엔 0-B(dispose 시그니처)도 열려 있어 개수가 안 맞던 것 —
0-B는 M0가 아니라 M6만 막는다는 캐비엇 추가
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
4차 감사(반복되던 두 패턴을 grep으로 전수 스윕)에서 발견한 것 정리:
- architecture.md: 모든 세션이 "먼저 읽으라"는 진입점 파일인데 소스
트리의 chains/retractFrom 서술에 0-Z 포인터가 전혀 없었음 — 최상단
배너 추가(가장 중요한 gap, question.md 0-A가 경고하던 바로 그 함정)
- tween-plan.md/operator-sugar-plan.md/effect-plan.md: self-lazy-핸들
계약(0-Y)에 의존하는 실행 예시/메커니즘 서술에 포인터가 전혀 없었음
— 3곳 추가
- CLAUDE.md: "지금 할 일" 0번(0-Y/0-Z가 최우선 게이트)과 1번("M0 착수
전 남은 유일한 게이트는 luau-test뿐")이 정면 모순 — 0-Y/0-Z 발견
이전에 쓰인 문장이 안 갱신된 stale, 정정
- slot-plan.md 3곳/additional-primitives-plan.md/
dispatch-redispatch-diff-plan.md: reconcile/Slot:List/재귀 재emit
가드 설명이 여전히 "파괴"를 현재형으로 서술 — 2026-08-13 여섯 번째
세션의 언마운트 전환 반영해 정정(지난 세 라운드가 놓친 인스턴스들,
전수 grep으로 확인)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
## luau-test/STATUS.md 신설 (사용자 요청)
"사람이 보기엔 뭐를 바로 봐야하는지 눈에 안 띄어서" — 파일별 상태를
🔴 사람 결정 필요 / 🟠 스파이크 깨짐 / ⚪ 미실행 / ✅ 통과로 분류하고
봐야 할 것을 위에 배치. 역할 분담도 명시: STATUS.md가 상태의 소스,
README.md는 각 파일의 검증 의도·배경, audit/는 실행 결과 상세.
## 코퍼스 전반 감사 (에이전트 둘, 보고받은 것 전부 직접 사실 확인 후 수정)
가장 위험: ROADMAP.md가 최우선 게이트(question.md 0-Z, 하강 diff 재설계)를
전혀 안 짚고 있었음. base 4개 문서엔 ⚠️ 배너를 달아뒀는데 정작 구현 순서의
소스인 ROADMAP엔 없어 M2/M4/M10 담당자가 옛 모델로 구현할 위험이 실재.
→ 세 마일스톤 전부에 배너/포인터 추가.
두 번째: slot-plan.md 앞부분이 뒤집힌 결정을 여전히 "확정"으로 자칭.
"폐기, 옮기지 않음, portal은 오버엔지니어링"과 "열린 질문" 절에 역전 표시가
없어 앞에서부터 읽는 구현자가 구 모델로 짤 위험 → 🔄 역전 배너.
더 심각하게 reconcile 의사코드가 여전히 rawRemove(파괴)를 부르고 있었음
(같은 문서가 "[반영 완료]"라 태그해둔 것과 정면 모순) → rawUnmount 신설.
filter/toggle 근거 절도 "제거=파괴" 전제라 캐비엇 추가(결론은 유효).
그 외:
- ROADMAP M11 Tween이 이미 확정된 넷을 미결로 둠(override 정책/옵션 값
모양/Animate 시그니처/initValue) + research/tween-plan.md 죽은 링크 4곳
- bind-system-plan.md:328, architecture.md:213의 "4종 계약" 잔여
(isHandlable(k,v) 구식 시그니처 포함)
- store-semantics.md의 "State는 가칭" stale — 2026-08-12에 최종 확정됨
- luau-test/README.md의 04/19 판정 기준이 재작성된 파일을 못 따라감.
특히 19 C섹션 기준이 정상 동작(nested error)을 실패로 오판하게 돼 있었음
- documentation-content-map.md의 4종 계약 / Attribute<T> 옛 이름
- modifier-plan.md의 "정정이 원문에 소급 안 됨" 패턴
- attribute-plan.md "이름 소유권"이 0-Z 미결인데 "최종"이라 적힌 것 캐비엇
- relate-plan.md의 kSlotMap 역할 서술 부정확, 상호참조 방향 오류 2곳
판단해서 안 고친 것: effect-plan.md의 "cleanup" 14곳 — Handler retract
(내부 배관)와 달리 Effect(fn)에서 사용자가 작성한 fn이 반환하는 React식
콜백이라 다른 층위. 매 감사마다 재지적되므로 lifecycle-pattern.md에
"확인 완료, 의도된 별개 개념"으로 못박음.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
다른 에이전트 리뷰가 짚은 5건을 전부 사실 확인 후 수정.
1. (가장 심각) slot-plan.md의 SlotHandler.process 반환 클로저가 여전히
destroySlotTree를 불러, 같은 커밋에서 확정한 "State<Slot> 교체는 파괴가
아니라 언마운트" 결정과 정면 모순 — 그 결정이 적용돼야 할 최상위 dispatch
경로가 바로 이 클로저인데 "구현상 바뀌어야 하는 것" 절이 reconcile만
언급하고 빠뜨렸음. 그대로 뒀으면 언마운트 결정 자체가 무의미해질 뻔함.
→ 비파괴 경로 unmountSlotTree 신설(destroySlotTree와의 차이는 딱 둘:
실제 Destroy 안 함, 자식 releaseOwner 안 함 — 자식이 계속 그 slot
소유라 통째로 재마운트 가능 = 포탈). 클로저를 unmountSlotTree +
setOffsetSource(None) -> setLength(0) + unbindLifetime + releaseOwner로
교체. "자동 경로는 언마운트, 명시적 Remove/Clear/dispose만 파괴"로 정리.
2. bind-system-plan.md "확정된 것" 절이 폐기된 4-메소드 계약을 여전히 확정
사실로 서술 — 같은 파일 상단은 이미 3-메소드로 갱신돼 자기 문서 내 모순.
3. question.md의 Slot 해제 순서가 거꾸로 — setLength(0)을 먼저 부르라고 돼
있었는데, 같은 커밋이 slot-plan.md에 추가한 경고가 정확히 그 순서가
죽어가는 서브트리 Source에 헛된 :Set()을 날린다고 명시.
4. ROADMAP.md M6에 이번 세션 Slot 결정이 전혀 미반영(M0/M2/M4/M10엔 반영됨)
— 언마운트 전환/dispose/claimOwner vs claimOwnerAt 분리/버그 4건 추가.
5. CLAUDE.md 여섯 번째 세션 항목이 자체 규칙(2~4줄+링크) 위반, 63줄까지
불어남 — 과거 3000줄 비대화를 유발한 패턴 재발. 15줄로 압축.
6번(StoreBind가 최초 발화에서도 retractFrom을 부르는 것)은 사용자 판단으로
그대로 둠 — 빈 슬롯 retractFrom은 무시 가능하고 일관성이 낫다는 결정.
추가: question.md 0-Y에 사용자 방향 기록 — 순환 타입을 만들기보다 State 타입
자체를 구울 때 인라이닝하는 쪽(Modifier flat 타입 생성과 같은 결). 사람이
직접 확인할 부분이라 0-Z(Attribute)와 함께 사용자 판단 목록으로 이관.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luau/luau-analyze 바이너리가 사용 가능해져, 2026-08-09에 스파이크를 만들기
시작한 이래 처음으로 실제로 돌림(CLAUDE.md가 "M0 착수 전 남은 유일한
게이트"로 꼽아온 항목). 전문: .claude/audit/luau-test-first-run-2026-08-13.md
## 설계 검증 결과 — 런타임은 전부 성립
- 04가 직전 커밋의 감사에서 찾은 버그를 음성 대조군으로 재현: chains:SetStrong을
process 뒤에 두면 체인 깊이가 3 대신 1로 무너지고, 죽은 store가 나중에 UI를
덮어씀(STALE). 감사→수정 사이클이 실측으로 닫힘.
- 07은 보강해야 실제 검증이 됐음. 3번 섹션이 sanity check만 하고 있었고 헤더가
내세운 연쇄 GC 주장은 미검증이었음. 파일이 스스로 적어둔 "weak table 엔트리를
셀 표준 API가 없다"는 전제도 틀렸음(outer가 __mode="k"라 GC 후 pairs에서
사라짐) — _countEntries + weak-value canary로 4번 섹션 신설, GC-native
아키텍처의 핵심 전제(연쇄 GC)가 실측 확정됨.
- 18이 relate-plan.md의 상호 순환 경고를 실증 — 추측이 아니라 실제로 GC 안 됨.
- 01/02/03/05/06/20도 전부 통과.
## 타입 — 진짜 설계 이슈 1건 (question.md 0-Y 신설)
:Compute(fn)의 lazy 핸들 계약이 Luau 양방향 추론과 충돌.
`state:Compute(function(s) return s:Get() * 2 end)`가 타입 에러를 냄.
최소 재현으로 원인 확정: read/self 표기 조정으로는 안 풀리고, 콜백이 raw 값을
받으면 완전 클린. Effect/Observer/Animate/Operator 등 같은 계약을 공유하는
API 전부에 걸림 — M0 착수 전 결정 필요.
## 문서 결함 발견 — modifier-plan.md
"데이터를 테이블에 직접 두고"가 "self 최상위 리터럴 키"로 읽힐 여지가 있었는데,
그렇게 하면 __index가 rawget 성공 시 안 불려 같은 필드 재호출이 죽음. 그 재호출
패턴이 문서 3·4절의 대표 용례라 실사용에서 즉시 터지는 경로였음 — 경고 문단 추가.
## 스파이크 수정
- 17: self 최상위 키 저장 → 내부 저장소 구조로 재작성(크래시 해소)
- 11: 브랜드 판별 크래시로 "다른 Modifier" 케이스가 엉뚱하게 통과하던 것 수정
- 19: B/C를 폐기 설계(rawNew+owners, 3분기 claimOwner)에서 현행 설계로 재작성,
음성 대조군으로 옛 로직이 Slot{a,a}/Frame{slot,slot}을 통과시킴을 재현
- 07: 위 참고
남은 것은 13/15/16의 스파이크 격리·API 재확인(설계 문제 아님).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
c33ae04(인덱스 기반 Dispatch 재설계) 전체를 직접 정독. 방향은 옳으나 새로 쓴
의사코드가 손 트레이싱을 안 거친 채 커밋됐음이 드러나 실제 버그 4건 발견·수정:
1. Dispatch.process가 chains:SetStrong을 h.process 뒤에 둬서 최초 마운트에서
하위 위임 retractor가 통째로 유실(재귀가 자기 테이블을 만들었다 바깥이
덮어씀 — Slot이면 서브트리 중복 마운트로 직결). SetStrong hoist + 재귀 중
hole 방지용 no-op 점유 마커 추가.
2. Attribute 그룹이 process에서 retractFrom을 선행 호출해 인덱스 1이 무조건
비워지는 바람에 점유 체크(=소유권 충돌 감지)가 전혀 작동 안 함. 철거를
반환 클로저로 이동.
3. SlotHandler가 claim 실패에도 파괴적 클로저를 반환 — nested는 엄격
claimOwner, top-level은 claimOwnerAt(inst,k)로 분리. rawRemove의
releaseOwner 누락, destroySlotTree의 GC 타이밍 의존 오류도 수정.
4. Ref retractor가 spurious 재발행에서도 relate를 지워 dedup 무력화.
사용자 설계 결정:
- State<Slot> 교체 = 파괴가 아니라 언마운트(state<Frame>와 동일). 포탈이
별도 기능이 아니라 이 결정의 귀결이 됨. 해제는 setOffsetSource(None) →
setLength(0) 순서 고정(반대면 죽는 중인 서브트리 Source에 헛된 Set이
날아감). dispose(value) 신설 — 트리가 아직 요구하면 파괴 거부하고 error.
- 재디스패치를 "하강 diff"로 재설계 — 래핑 핸들러의 retractFrom 선행 호출을
폐기하고 Dispatch.process가 핸들러를 먼저 비교. 힌트가 None/래퍼로 오염돼
깜빡임 방지가 조용히 꺼지는 결함이 계기. 아직 research/에만 있고 base
미반영(Attribute 이름 소유권 하나가 남음) — base 4개 문서에 경고 배너.
전파 누락 정리: ROADMAP.md가 2026-08-08 이전 모델로 남아있던 것, base 내
"3종 vs 4종 계약" 모순 6개 문서, luau-test/04가 없어진 가드를 검증하던 것
(04는 인덱스 모델 + 버그 1 재현 음성 대조군으로 전면 재작성).
재발 방지: bind-system-plan.md "Handler 작성 체크리스트"(7항목),
relate-plan.md "언제 Relate를 쓰고 언제 쓰면 안 되는가" 신설.
다음 세션 최우선: question.md 0-Z(Attribute 이름 소유권) — 사용자가 직접
스케치하며 심층 분석 예정.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
네 번째 세션: State<State<T>> 발견 방식을 서브에이전트로 코퍼스 전체에
반복해 Tag 참조 카운트(객체 identity 키잉)/Attribute 그룹 자기충돌 버그
발견·수정. Attribute 소유권 충돌 방지를 위해 처음엔 Dispatch.processAs/
retractSelfAndUnder 체크포인트 핸들러 패턴을 신설.
다섯 번째 세션: 체크포인트 패턴이 임시방편이라는 지적에서 출발해, chains의
핸들러 객체 identity 기반 추적 자체가 State<State<T>>를 UB로 만든 근본
원인이었음을 재확인 — 재귀 깊이 인덱스로 재설계(같은 키 재귀는 index+1,
다른 키 위임은 항상 1부터), Handler 계약을 process/retract 2-메소드에서
process가 자기 retract 클로저를 반환하는 1-메소드로 축소. 이걸로
State<State<T>>가 UB에서 정상 지원 대상으로 바뀌고, 전날 만든 체크포인트
패턴 전체가 불필요해져 archive로 이전. 여러 핸들러(StoreBind/Ref/Tag/Slot/
Attribute)의 private Relate 상태 저장소도 대거 정리됨.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0132hkscPQWa34iyHrHdoRiF
v1 재현 테스트가 필요하다고 남아있던 question.md 항목을 사용자가 정리 —
당시 실수였던 건 맞지만 v2는 오브젝트 id 주입/조회 개념 자체가 없어져
재현 여부와 무관하게 v2 마이그레이션 가이드에서 다룰 대상이 아님.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Haskell Monad/Applicative 비교 리서치 중 사용자가 retractUnder의 꼬리부터
cutoff 로직을 재검토하다 제기한 의심을 pseudocode 손 트레이싱으로 확인 —
store가 emit하는 값 자체가 또 State/Source면 같은 (inst,k)에 같은 핸들러가
중복 push되어 안쪽 구독이 등록 직후 스스로 끊기는 실제 체인 파손 버그였음.
Dispatch.process에 중복 핸들러 즉시 error 가드를 추가하고, 같은 시나리오를
이미 다루고 있었지만 no-op retract 스텁 때문에 증상을 못 잡던 luau-test/04의
사각지대도 재작성. operator-sugar-plan.md에 Alternative(nil 대체값) 후보 신설.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
User confirmed the two Roblox-engine-dependent assumptions behind the
gcconn trick (ClassName signal never fires, Connection.Connected flips
synchronously on Destroy) via a Studio script; record what's verified vs
still open in a new audit/ folder and document the GC-trigger technique
used. While re-auditing the corpus, found relate-plan.md's ephemeron/
mutual-cycle claim was never actually run in Luau, and CLAUDE.md's
open-questions list still listed State as unresolved after it was
finalized in session 20 — add spike 18 and fix the stale entry. A
background sweep synced .claude/README.md's summary rows against
sessions 8-21 and added spikes 19/20 for newer ownership/refcount
mechanisms lacking coverage.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PGWwZ8khc3Zq7DAnd4Uw6a
- use-after-destroy 검증 안전망: rbvm 영역/quad-debug 스코프 밖으로 최종
기각, Ref 사용 관례(useRef급 스코프) 명문화
- :With 동적 의존성: State immutable 가정과 모순되어 의도적 비지원 확정
- Operator 콤비네이터 카탈로그: 서브 에이전트 외부 리서치로 포함 범위/
네임스페이스 이름 근거 보강(Clamp/Min/Max 추가 후보, 비트/비교/Sub/Div
드랍 후보, Debounce/Throttle 별도 질문으로 분리)
- State 용어 정리 최종 확정(현재 이름 유지), Pipe 기각 근거·Compute vs
Computed 네이밍 근거 문서화, :With와 Tag/Modifier clone 체이닝 혼동
방지 경고 추가
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVG74qV2nQVykhvMRQW2UC
사용자가 우선순위 동률/매치실패(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
- Attribute: AttributeKeyHandler.retract를 완전 no-op으로 재정정 —
지우는 건 오직 명시적 None/nil(process)로만, 그룹이 사라진 이름을
자동으로 SetAttribute(nil) 안 해줌(Ref의 "Destroy 무관" 철학과 통일).
단 사라진 이름의 StoreBind 구독은 retractUnder로 끊어 리소스 누수 방지.
- Slot: slotOwner를 elementOwner로 일반화해 top-level Dispatch/nested
Add 경로가 같은 소유권 레지스트리를 쓰도록 통합(이중 마운트 gap 폐쇄).
bindLifetime을 top-level 전용으로 축소(nested는 _elements 강참조로
transitively 생존).
- 일반 규칙 신설(bind-system-plan.md): retract의 v는 타입 미보장이라
내용을 보려면 isX(v) 가드 필수, retract 안에서 process 호출은
retractUnder 체인 추적을 꼬는 UB.
- and/or 삼항 관용구 전면 금지(기존 "항상-truthy면 예외" 조항 폐기),
코퍼스 전체 실제 코드 6곳을 if-then-else로 교체.
- 7-에이전트 코퍼스 감사로 stale 서술 다수 정정: retract-always-fires
정정 전파 누락(bind-system-plan.md Tag 예시), Tween research→base
승격 반영 누락(architecture.md/ui-shorthand-plan.md), Relate API
인자 개수 버그(slot-plan.md), pre-implementation-audit.md 열린 항목
개수 오류(question.md/CLAUDE.md).
- 백로그: Attribute 자동 unset용 :Apply 유틸 아이디어를
research/operator-sugar-plan.md에 추가(착수 안 함).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVG74qV2nQVykhvMRQW2UC
직전 커밋이 "Slot의 T(Instance|Slot)가 우연히 테이블이라 T|{T}가
모호하다"고 적었는데, 이는 quad-roblox라는 특정 백엔드의 구체적 T에
기댄 특수 사례일 뿐 근본 원인이 아님 — 정확한 이유는 Slot<T>가 base
레벨에선 T가 뭔지 전혀 모르는 제네릭(다른 백엔드면 테이블/userdata/
그 밖에 뭐든 가능)이라, 바깥 {}가 단일 T인지 {T} 배열인지 원천적으로
판별 불가능하다는 것.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Tag:Added가 string|{string}로 간 진짜 이유를 Lua table.unpack의 tail
위치 제약(뒤에 다른 인자/unpack이 오면 첫 값 하나로 잘림)으로 정확히
보강. 같은 논거가 Slot:Splice의 newElements에도 적용되는지 검토했으나
기각 — 사용 패턴이 소수 고정이고 동적이면 Slot-in-Slot으로 흡수 가능,
게다가 Slot의 T 자체가 테이블이라 T|{T}가 오히려 모호해짐 — vararg 유지로 확정.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
동적으로 조립한 이름 목록을 vararg에 넘기려면 table.unpack이 필요해
번거롭다는 지적 — string 단일 이름 또는 {string} 배열을 받아 내부에서
flatten하는 쪽으로 같은 세션 내 재수렴. Tag(...) 생성자는 정적 호출
자리라 vararg 유지.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Tag:Added/:Removed가 문서상 단일 name만 받던 불일치를 vararg로 정정
(self-return 최적화는 멤버십을 매번 먼저 읽어야 해서 기각). Slot:Splice
CRUD 신설 — 구간 제거+삽입을 shift/recompute 1회로 묶는 순수 최적화.
Slot-in-Slot relate 범위/Animate 반환타입/Slot retract 파괴는 기존
문서와 일치 확인만.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
User cited https://luau.org/compatibility/ (Lua 5.2 section, "Ephemeron
tables") -- Luau explicitly did not adopt this due to complexity. Upgrades
the previous session's defensive "unverified, avoid just in case" framing
to a confirmed fact: two separate Relates strongly cross-referencing each
other's keys is a real, unrecoverable leak in Luau, not just a theoretical
risk. Adds a general rule to relate-plan.md so future Relate designs don't
have to rediscover this from scratch.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two separate Relates strongly holding each other's key as their value
(inst->slot in one, slot->inst in the other) is a mutual-reachability
cycle that ordinary weak-key GC can't resolve on its own -- the case
Lua 5.2 ephemerons exist for. Weaken both to pure lookups and make
bindLifetime/unbindLifetime (already used for Slot's own observers)
the single GC anchor for the slot object itself, which attachSlot/
destroySlotTree were missing.
Audited every other Relate() in base/ for the same shape (an object
other than inst used as the outer key) -- slotOwner was the only one;
everything else either has no back-reference to inst or is the safe
single-table self-reference shape bindLifetime already relies on.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Position-keyed comparison only catches "did this exact slot spot change,"
not "is this same Slot object already mounted somewhere else" -- which is
exactly the double-mount invariant Slot already promises for its elements.
A Relate<slot, inst> enforces it directly: same owner -> spurious re-emit,
ignore; different owner -> error; no owner -> bind.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Corrects a foundational error: "handler type unchanged -> retract skipped,
process diffs" was never actually true. StoreBind unconditionally calls
Dispatch.retractUnder before every re-dispatch (already stated in the
original dispatch model doc) -- Tag's old assert(v==nil) was taken at
face value and the general rule was wrongly back-derived from it.
Tag, Ref, Slot, and Attribute (the last three designed earlier this same
session) all inherited the flawed premise. Fixes across all four:
- Tag: rewritten around kTagMap/tagNameMap reference counting so
AddTag is entirely process's job, RemoveTag entirely retract's,
using a Contains hint to skip engine calls that would be immediately
redone. Also fixes the case where two independent Tag(...) values at
different positions share a name (union semantics, like className).
- Ref/Slot: drop the incorrect assert(v==nil), split into "retract
unbinds/destroys the old, process binds/mounts the new" with an
identity check on both sides to avoid spurious re-fire.
- Attribute: AttributeKeyHandler.retract gated on v==nil so ordinary
value updates don't flicker the attribute to nil; the group's
"unchanged name" delegation now also retracts before reprocessing,
since skipping it was silently stacking chain entries every cycle.
Reversal preserved in archive/retract-always-fires-reversed.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Group Attribute(...) delegating through the public memoized AttributeKey(name)
cache meant a direct [AttributeKey "name"]=v write and a group field (or two
independent groups) targeting the same name silently converged on the same
dispatch slot -- last write wins, no error. Fix: groups use rawNew(name) to
mint a private per-name key cached on their own (inst,index) relation instead
of the shared cache, so AttributeKeyHandler can detect a conflicting claimant
by simple object-identity comparison -- no separate ownership registry needed.
Persisting names reuse the same cached key across group value swaps, so the
existing diff (nil only what left, leave the rest alone) keeps working.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same gap as Ref: slotA -> slotB via State<Slot> matches the same
SlotHandler both times, so Dispatch skips retract and process must
diff. Slot's confirmed no-portal/discard-only policy makes an identity
short-circuit a correctness requirement, not just an optimization --
without it, any spurious same-value re-emit would tear down and
rebuild the entire mounted subtree.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
State<Ref> rebinds (refA -> refB) go through process's own diff (same
handler type matches both), not retract -- exactly the TagHandler
precedent. retract only fires when the slot stops being a Ref at all.
Both paths converge on old:Set(nil). Non-nilable Ref<T> remains a valid
"read a settled value" use case; callers opt into Ref<T?> explicitly for
Store/Modifier slots. Unbinding is independent of Instance Destroy --
that's Effect's job, not Ref's.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
PreRef never enters the normal retract dispatch chain (consumed via None
in the pre-pass), so "cancel" was never structurally possible. The real
hazard is reuse across constructions (stale .Value silently firing
callbacks) — guard it with an explicit error on re-fire instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>