1~3차가 안 쓴 각도 둘로 같은 라운드를 이어서 돌렸다.
(1) M2 코어(state-epoch §2~§5 / 전파 모델 / gate 4·8번 / Blocker)를
방어 코드 없이 문서 그대로 옮긴 참조 구현을 Luau로 짜서 시나리오를
돌렸다. 손 트레이싱이 못 보는 "여러 규칙이 동시에 걸리는 자리"가
여기서 나온다. 참조 구현의 대조군은 §1이 예고한 다이아몬드 glitch가
실제로 사라지는지 확인한 것.
(2) 예외 경로 — 반응형 문서 셋에 "error"가 0건이다. 사용자 콜백이
던졌을 때 반쯤 갱신된 부기가 무엇을 남기는지 정한 문장이 없다.
🔴 H-85 재계산 끝의 `rawInvalid = false`가 재계산 도중 도착한 무효화를
지운다 → 캐시가 다음 Set까지 영구 stale (재현 + 한 줄 수정 대조까지)
🔴 H-86 정책이 "보류분이 있는가"를 읽을 수 없어 Throttle의 창이 idle로
못 돌아온다 → leading 영구 소실 + 타이머 체인이 안 끝나 8절의
"유계 GC" 주장도 깨짐 (H-55와 별개 — 그쪽은 버리기, 이쪽은 읽기)
🔴 H-87 배치 게이팅 도중 error → 그 owner의 Blocker가 영구 On,
recompute가 조용히 영영 안 돎 (문서는 yield만 UB로 못박음)
🟡 H-88 전파 중 콜백이 던지면 나머지 구독자는 영구 침묵(값만 자가치유)
🟡 H-89 flush 중 error → 떼어낸 배치 소멸, :Sync와 전파의 순서 미정
🟡 H-90 Effect의 dedup이 루트 에포크 기준이라 dep 하나만 게이팅하면
같은 루트를 공유하는 다른 dep 때문에 게이팅이 무력화
🟢 H-91 §8의 "항상 Get이 최신"이 과한 서술 (Animate가 반례를 설계로 씀)
🟢 H-92 구독자 스냅샷이 emit마다·노드마다 배열 하나 — §2의 GC 근거와 충돌
🟢 H-93 EpochMap 키가 weak라 중간 State GC 미해결이 "최신이라고 오판"
으로도 나타남 (실측 스파이크 요구가 하나 늘어남)
부록에 "돌려봤는데 문제가 없던 것" 6건(다이아몬드 glitch 해소, 유한
재진입 안전, Blocker 정책 배선, 빈 배치 무통지, Store lazy __index의
identity 안정성, GateNode 순환 require 없음)도 같이 남겼다.
부수로 .claude/README.md의 qa-request 행에 7라운드가 아예 빠져 있던 걸
채웠다(1~3차 패스 때부터 비어 있었음).
아무것도 base/에 반영하지 않았다 — 회신 대기.
Co-authored-by: qwreey <me@qwreey.moe>
2차 패스와 같은 방식으로 7라운드 문서에 **3차 패스**로 이어붙였다(발견
번호도 계속 이어서 매김). 여전히 아무것도 반영하지 않았고 README 색인도
안 넣음(결정 후 followup과 함께). doc-check ERROR 0.
## 이번 패스의 각도
2차는 문서가 주장하는 Luau 동작을 **새로 짠 최소 재현**으로 걸었다.
3차는 각도를 둘로 바꿨다 — (1) **저장소에 이미 있는 것을 그대로 돌린다**
(`quad-base/test/smoke.*`, `luau-test/done/` 전량, `doc-check.py`, 커밋된
`quad-base/src/*.luau`의 `luau-analyze`). 즉 "설계가 맞나"가 아니라
"지금 이 저장소가 문서가 말하는 상태인가"를 본다. (2) 1차가 범위에서
명시적으로 뺐던 **M2 *소비자* 문서**(`modifier`/`attribute`/`tween`/
`onchange`/`quad-types`/`architecture`/`project-setup`/`module-lifecycle`)
— M2를 만드는 문서가 아니라 M2가 만든 걸 쓰는 문서라, "소비자가
요구하는데 M2 설계엔 없는 것"이 여기서만 보인다.
검증 중 `pesde` 심볼릭 링크를 실제 디렉토리로 치환한 구간이 있으나 전부
원상복구했다(링크 20개, 전부 `.gitignore` 대상이라 저장소 파일 무변경).
## 발견
- `H-77` 🔴 **`Relate`의 *내부 키*가 `inst`를 되참조하면 `SetStrong`/
`SetWeak` 둘 다 샌다.** `H-71`은 *값*의 되참조였고 결론이 "`SetWeak`은
안전"이었는데, `WeakMap`이 `__mode = "v"`라 **키는 언제나 강함**이다 —
즉 `H-71`의 해법 (b)가 여기선 전혀 안 듣는다. `relate-plan.md`의 규칙은
위험을 전부 "값" 기준으로만 서술해 이 슬롯이 아예 없다. 커밋된
`Relate.luau`로 50/50, 그리고 **커밋된 `init.luau`의 `RunInit`이 실제로
물린다** — `initFn`이 module을 캡처하면 30/30이 안 죽고, 그 파일 주석
("module이 GC되면 이 기록도 같이 사라짐")이 지금 거짓이다.
- `H-78` 🔴 **커밋된 M1 스모크 2개와 `done/`의 타입 스파이크 `23`이 지금
저장소 상태에서 안 돈다.** 원인은 pesde 워크스페이스 심볼릭 링크(이
`luau` CLI가 require 경로의 심볼릭 링크를 디렉토리든 파일이든 해소
안 함 — 격리 확인). 더 나쁜 쪽은 타입으로, `luau-analyze`가 `Quad`/
`CheckVersion`을 아예 못 봐 스파이크 `23`의 음성 대조군이 한 건도 안
뜬다 = "진단 0건"으로 오독된다. 워크어라운드를 적용하면 전부 정상이라
설계가 아니라 **절차가 없는 것**이고, 그 치환은 `project-setup-plan.md`가
스스로 "정식화 안 함"이라 밝힌 상태다. `ROADMAP.md` M1의 "전부 PASS"는
날짜도 전제조건도 없다.
- `H-79` 🟡 **`Store`에 열거 표면이 없는데** 그룹 `Attribute(...)`/
`:NameMap()`이 그걸 요구한다 — lazy `__index` 때문에 키 집합이 접근
이력에 좌우되고(실측 0개/1개/2개), `defaults` 없이 만든 Store에선 빈
맵이 된다. `:NameMap()`이 스냅샷이냐 라이브냐도 미정.
- `H-80` 🟡 M2가 `quad-types`의 `Quad`에 추가할 목록이 `Source`/`State`/
`Store`뿐 — **`State`는 런타임 값이 아예 없고**(타입 재수출만 가능),
M2가 실제로 얹는 나머지 탑레벨 값(`Effect`·`is*` 전량·`bindLifetime`
4종·`Relate`·`Blocker`)이 전부 빠져 `H-25`가 만든 그 벽에 똑같이
부딪힌다. 부수로 옛 표기 `state(state)`가 두 문서에 남아 있다.
- `H-81` 🟡 `isModifier` 런타임 가드의 적용 지점이 **전부 M2 코드**
(`Source:Set`/Store 생성/`:Compute` 캐싱)인데 체크박스는 M7에만 있고
M2 체크리스트엔 한 줄도 없다(그 사이 M4가 가드 없이 돈다). 게다가
적용 지점 목록이 두 문서에서 갈린다 — `source-state-plan.md`는 독립
`Source(someModifier)` 생성자도 포함하는데 `modifier-plan.md` 7번엔
그 자리가 없다.
- `H-82` 🟢 `:With`를 실노드로 확정한 **근거 2번이 pass-through 노드엔
성립하지 않는다** — 계산 함수가 없으니 공유될 계산도 없다. 2026-08-14
재작성이 "근거가 더 강해졌다"면서 실제론 정확한 서술(순회 비용)에서
부정확한 서술(중복 계산)로 내려갔다. 결론은 안 바뀜(근거 1·3 유효).
- `H-83` 🟢 확정된 Store 구현 스케치를 그대로 쓰면 무인자 `Store()`가
`table.clone(nil)`로 크래시한다 — 같은 절이 `defaults`는 선택이라고
확정해뒀다.
- `H-84` 🟢 `:With(...)`/`state:Block(b)`/`Source:Emit()`이 M2 체크리스트에
개별 항목으로 없다 — `H-55`/`H-68`/"중간 State GC"의 결론이 각각 어디로
가야 하는지가 로드맵에 자리가 없다는 뜻.
## 부록 — 돌려봤는데 문제가 없던 것
다음 라운드가 같은 곳을 다시 파지 않도록 남겼다 — `bit32.bnot(-rev)`
실측 표 정확, `doc-check.py` ERROR 0, `done/` 런타임 스파이크 8개 전원
재통과, `STATUS.md` 개수 표가 실제 폴더와 일치, 워크어라운드 적용 시
커밋된 `quad-base/src/*.luau` 진단 0건, `Relate.luau` 구현이
`relate-plan.md`의 "실제 구조" 절과 일치(문제는 코드가 아니라 규칙 공백).
Co-authored-by: qwreey <me@qwreey.moe>
Claude-Session: https://claude.ai/code/session_012oLwATeQdq9TCFdENPutFG
별도 라운드로 쪼개지 않고 7라운드 문서에 **2차 패스**로 이어붙였다
(6라운드가 패스를 이어 붙인 것과 같은 방식, 발견 번호도 패스를 가로질러
이어서 매김). 여전히 아무것도 반영하지 않았고 README 색인도 안 넣음
(결정 후 followup과 함께). doc-check ERROR 0.
## 이번 패스의 각도
1차는 문서 대 문서 손 트레이싱이었고, 2차는 **문서가 "확인했다"고 적어둔
런타임/타입 주장을 실제로 `luau`/`luau-analyze`에 걸어봤다** — 6건 중
5건이 추측이 아니라 실행 결과이고 재현 코드를 항목마다 넣었다. 부수로
드러난 패턴: **"검증 완료"로 인용되는 스파이크들이 정작 위험한 모양을
안 테스트했다**(`07`은 되참조 없는 payload만, `16`은 대입을 아예 안 함).
## 발견
- `H-71` 🔴 **단일 `Relate` 안의 자기참조(값→키)는 GC 안전하지 않다.**
`relate-plan.md`의 "위험한 패턴" 절이 안전하다고 단언한 바로 그 모양이
커밋된 `Relate.luau`로 50/50 누수(SetWeak은 0). 그 절은 "Luau엔
ephemeron이 없다"를 근거로 두-`Relate` 순환을 금지하면서 단일 `Relate`
자기참조엔 ephemeron 의미론을 가정한다. `RefLeafHandler`가 정확히 그
모양이고(`SetStrong(inst,k,ref)` + `ref.Value = inst`), 정상 `Destroy`
경로엔 그 기록을 지우는 코드가 없다.
- `H-72` 🟡 `GateNode`가 `state-epoch-plan.md` §4 규칙 1~3을 돌려면
`emitEpochMap`을 갱신 없이 비교해야 하는데 `EpochMap` 표면
(`Update`/`Refresh`/`Sync`/`TrackFrom`)에 그 연산이 없다.
- `H-73` 🟡 `store:GetDynamic<<T>>(name): Source<T>`는 콜론이든 탑레벨이든
`T`를 바인딩할 수 없다 — Luau엔 호출부 명시 타입 인자가 없고 기대 타입
추론도 안 돼 `T`가 `unknown`으로 떨어진다. 콜론 쪽은 합성 타입에 키
자체가 없어 `Key 'GetDynamic' not found`. `question.md` 최우선 항목의
실측 근거.
- `H-74` 🟡 eager `defaults` 경로가 `__index`를 통째로 우회해 예약 키
방어가 성립 안 함 — `Store({GetDynamic = 3}):GetDynamic("x")`가
`attempt to call a table value`. Modifier와 "정확히 같은 구조"가 아니다.
- `H-75` 🔴 `WrapStore`가 스파이크 `16`의 평평한 모양이면
`store.key:Compute(무주석 콜백)`이 깨진다 — `typing-limits.md` ②쪼개기를
`type function` 안에서도 해야 한다(그 모양이면 통과함까지 확인).
- `H-76` 🔴 `type function`은 바깥 타입 별칭을 참조 못 하므로 `Source<T>`
전 표면을 손으로 중복 유지해야 하고, 메소드 self 파라미터가 불변이라
`Revision` 하나만 빠뜨려도 `store.key`가 `State<T>` 파라미터 자리에 안
들어간다. 스파이크 `16`은 그 대입을 한 번도 안 해봤다. 충실한 재현 +
정합성 단언 한 줄이면 닫힌다(실측 확인).
## 부록 — 열려 있던 실측 항목 하나는 성립
스파이크 `15`의 (B)(이형 다중 deps를 제네릭 팩으로 좁혀 받기)를 같이
재봤고 **된다** — 확정된 `previous?`-앞-팩 순서 그대로, 음성 대조군도
정확히 잡힌다.
Co-authored-by: qwreey <me@qwreey.moe>
Claude-Session: https://claude.ai/code/session_012oLwATeQdq9TCFdENPutFG
6라운드가 안 쓴 각도(시간축 겹치기 / 정책 분리 후 권한 경계 / 두 진입
경로 중 한쪽만 배선 / 두 문서가 다른 말로 부르는 자리)로 반응형 코어
문서를 다시 돌렸다. 아무것도 반영하지 않았고 README 색인도 아직 안 넣음
(결정 후 followup과 함께). doc-check ERROR 0.
Co-authored-by: qwreey <me@qwreey.moe>
Claude-Session: https://claude.ai/code/session_01Sv6h6YbuzYwPaEoRDQBQsc