- H-218 (a): 위임 자식 철거 시 retractFrom(child, prop, 1) 의무화 —
dispatch-core-plan H-218 블록 확정, ui-shorthand UI-11 부분 역전(스케치에
retractFrom 선행 추가)
- H-219 (a): 매치 실패 error level 2 현행 유지 + drive 경로 도착지 한계
명시(재상승은 pcall 금지 계약과 충돌로 기각), M5 재평가 예약
- H-222 (a): architecture error 계약 표에 제3 행(제공자 계약 위반 = 2,
가장 가까운 호출 구조 + 메시지가 핸들러 특정) 신설, H-212 잠정 표시 해제
- H-229(② 🔴, 사용자 되물음 검증): Destroy 경로 전체가 같은 누수 —
chains 강한 버킷 값이 retractor→inst 캡처로 두 번째 루트가 되어 gchold
섬이 무너져도 weak 항목이 안 비워짐. 반응형 바인딩 있던 모든 파괴
인스턴스가 부기·userdata 영구 잔존 + 상류 전파 순회 비용 누적.
선택지 3(권고 (a): 체인 리스트 GC 앵커를 bindLifetime/gchold로 통일 —
slot-plan 13차 선례) §4 등재
- H-230(②, 사용자 제안): HANDLER_PRIORITY_* 배치 — 권고 (a) quad-types
승격(quad-const 신설은 숫자 넷에 과함), error level Enum화는 비권고
(level은 의미가 아니라 프레임 수 — 자리마다 2·3으로 갈림)
Co-authored-by: qwreey <me@qwreey.moe>
Claude-Session: https://claude.ai/code/session_01LF78pXeFGD1ZSVD3ifteYG
- 탐사자(신선한 컨텍스트, round12 brief §5): 코드-문서 한 줄 대조 1:1 확인,
spec 미태움 경로 셋(H-223 메시지 내용/H-103 NOOP 잔존/매치 실패 후 재등록)
프로브 실측 전부 계약대로, TODO 마커 0 — 발견은 H-228(①, 🟢) 하나
- H-228: describeHandler가 name 부재 시 "?"를 찍던 것을 문서("없으면
priority만 보인다")에 맞춰 (priority N)만 — 순수 진단 문자열
- round12.md §5 이상 없음 확인 3건은 탐사자가 직접 기록(getHandler nil 반환
정본 정합 / Handler 타입 배치 방향 / table.sort 불안정성은 확정 범위 안)
Co-authored-by: qwreey <me@qwreey.moe>
Claude-Session: https://claude.ai/code/session_01LF78pXeFGD1ZSVD3ifteYG
① 반영:
- H-220: BRAND_PROBES specific-first(isPreRef/isPostRef가 isRef 앞) + 손 복사
목록 확장 주의 주석 — 상위 술어가 앞이라 둘이 도달 불능이었음
- H-223: retractor 생략 error에 위반 핸들러 특정 정보(name/priority/k/index)
— h.process 프레임은 반환돼 도달 불가라 메시지가 유일한 단서. 동률 경고도
같은 describeHandler. base 의사코드 동기
- H-227: local Dispatch = {} :: any → 로컬 함수 + 타입 주석 조립로 생산자
표면을 quad-types 선언과 analyze 대조 가능하게
- H-221: ROADMAP 우선순위 체크박스의 stale H-214 주석("§4 대기") 종결로 갱신
- H-224: M4 잔여 몫 문구에 효과 수준 단언(옛 구독 0, stale Set 불전파) 복원
- H-225: 세션 파일에 감사 6라운드 수렴·리뷰 결과 기록
② §4 합류(증상 확정, 처방 대기 — 거짓 사실 주장만 선반영):
- H-218: chains retractor 클로저의 inst 캡처 → H-71 "버킷 값이 weak 키
되참조" 누수 패턴. dispatch-core-plan·ui-shorthand-plan의 "버리면 결국
GC" 주장 정정(retractFrom 의무화 여부는 §4)
- H-219: drive 경로 매치 실패 error의 도착지가 내부 프레임
- H-222: "제공자 계약 위반" level 분류가 계약 표에 없음 — H-212 문단을
잠정 표시로 완화
기각: H-226(process 꼬리 병합 리팩터 — 확정 의사코드 1:1 유지 우선, 실측
병목 아님. 메시지 drift 우려는 H-223 공용 함수로 소멸)
감사 6라운드는 da67a8c diff 전수 점검으로 확실 0건 수렴(의심 1건 표현
정밀도만 정정). 테스트·analyze·doc-check ERROR 0 클린.
Co-authored-by: qwreey <me@qwreey.moe>
Claude-Session: https://claude.ai/code/session_01LF78pXeFGD1ZSVD3ifteYG
- pre-implementation-handtrace-round11(.md/-brief.md) → m2-implementation-round11*
(사용자: 구현 중 문서라 pre-implementation이 안 맞음), 인용처 22곳 일괄 치환
- 명명 규약(사용자 확정, README qa-request/ 행): mN-implementation-roundNN —
라운드 번호는 마일스톤을 가로질러 단순 증가("m3 진행 중 m2 하자로 돌아가는
경우도 있을 것"), 마일스톤 접두가 소속을 담음. round1~10 옛 이름은 실제로
구현 전 라운드라 유지
- CLAUDE.md/project-context.md 머리말: M0~M2 완료, 다음은 M3(디스패치 엔진);
todos 00 종결 표기 — 다음 액션은 M3 착수 규약 문항(사용자와 정할 것)
Co-authored-by: qwreey <me@qwreey.moe>
Claude-Session: https://claude.ai/code/session_01LF78pXeFGD1ZSVD3ifteYG
- H-198(사용자 안): 상류 스탬프(dep:_track)를 fn 직전으로 + Get을 수렴까지의
재시작 루프로 — 계약 강화: 모든 Get이 fn 도중 변경(재진입·게이트 유보)을
같은 호출 안에서 수렴시켜 항상 최신 반환. 매 패스 자기 dep Set은 UB.
state-epoch-plan §4 세 절 + H-85 bullet 정정, spec.state 6 새 계약,
spec.gate 10(리뷰의 영구 stale 재현 → 같은 Get에서 99 + flush 통지만)
- H-186(b): 교차 인스턴스 값 혼용 UB 문서화 — architecture 13번 + content-map
§4 22번(H-116 이웃), 코드 주석. M5 재검토
- H-205(a): Modifier 가드 level 2→3(직접 Get이면 유저 호출부), spec.state 12 단언
- H-208: Ref:Set 스냅샷을 table.clone + 집합 병합으로(사용자 — 더 싸고 dedup 공짜)
- H-209: src 전 파일 pairs/ipairs → generalized iteration(사용자 — 최적화로 더
빠름; 메타테이블 있는 테이블의 raw 순회 실측 확인). 문서 의사코드 표기는
H-178과 같은 급으로 무변경
- H-211: Relate:SetWeak의 캐스트 없는 setmetatable 대입이 IDE(strict)에서
TypeError(플레인 luau-analyze는 솔버 차로 조용) → 로컬 주석 + :: any 경유
- §4 열린 문항 0, 코드 마커 0. 감사 2라운드 수렴(확실 1·의심 1 → 0)
Co-authored-by: qwreey <me@qwreey.moe>
Claude-Session: https://claude.ai/code/session_01LF78pXeFGD1ZSVD3ifteYG
- 신선한 탐사자(fable) 단일 컨텍스트, 지시서 -round10-brief.md §2 레인 A·C 완료, B 부분, D ALL PASS
- audit/handtrace-round10-reference-impl/: round7 참조 구현을 현재 계약으로 갱신·재실행
(부수: round7/ref9 _recompute 첫 인자 오류 발견·정정)
- base/·인덱스 레이어는 미변경 — 결정은 사용자 배치 회신 뒤 -round10-followup.md로
Co-authored-by: qwreey <me@qwreey.moe>
Claude-Session: https://claude.ai/code/session_01546hjsYNLSMZdHdPyTZaGb
- H-143 Rerun 꼬리: 실행 중 사망(wasAlive and not canExecute)이면 cleanup 즉시 소진
(처음 쓴 not canExecute 판정은 생성자 최초 설치를 죽여 감사 2라운드가 정정)
- H-144 재구독 꼬리(Refresh 먼저) + 진입점은 EffectHandle 자기 것 (b)
(Observer 함수 배정은 콜론 위임으로 꼬리 2회 — 감사 4라운드, luau 재현)
→ conventions.md 설계 원칙 신설: 하나의 무언가가 두 일을 하지 않는가
- H-145 bk.indexOfElement weak-key / H-146 루트 .Parent는 사용자 몫, Mount 없음
- 감사 1→1→1→1→1→1→1→0, /code-review high 10건 중 7 반영
- H-147~H-149는 qa-request/pre-implementation-handtrace-round10.md §4로 (배치 회신)
- round10 지시서(-brief.md) 신설, 광범위 탐사 예정
Co-authored-by: qwreey <me@qwreey.moe>
Claude-Session: https://claude.ai/code/session_01546hjsYNLSMZdHdPyTZaGb
8라운드가 써둔 지시서(-round9-brief.md)대로 커밋 9dd8213의 델타를 재트레이싱해
발견 18건(H-124~H-141)을 냈고(qa-request/pre-implementation-handtrace-round9.md),
§4 문항 중 Q1~Q3를 사용자와 확정해 base/·ROADMAP에 반영했다. 결정의 소스는
-round9-followup.md(진행 표가 상태의 소스). 🔴 둘 다 luau 실측 재현.
Q1 H-124 — recompute가 lengthList[i]를 되감기 판정보다 먼저 읽어, offset:Set 안의
사용자 코드가 커서 뒤 자리 수를 줄이면 sum += nil로 죽고 recomputeBlocker가
영구 On. 되감기 판정을 앞으로(continue), 읽기·누적은 되감지 않을 때만.
Q2 H-125 — 재마운트 시 setOffsetSource가 slot.Offset을 바꾸는 순간 _baseObserver가
unbind 상태라 두 필드가 0으로 안 내려가 옛 베이스의 offsetCache[1]을 씀.
사용자 확정: Offset·_baseObserver를 Slot 생성자로(첫 마운트/재마운트 분기
소멸), materializeSlotTree는 blocker:On → bindLifetime → setOffsetSource,
파괴는 _destroyed 플래그 하나(핸들은 unbind만, mutate CRUD·:List·마운트 진입
error, Owned=false는 안 섬, 이중 dispose no-op).
Q3 H-126/H-137/H-141 — element→index 맵이 Slot 층(slot._elemIndex)과 Dispatch
층(bk.tokens/indexOfToken)에 두 벌 있었고, 후자의 token은 사용자가 정한 적
없는 것(2026-08-25 /code-review가 발명해 사용자 인용문 옆에 앉아 있던 것).
bk.indexOfElement 하나로 통일, setLength 5번째 인자 element, splice가 비운
자리는 세 배열 전부 처리. H-137 소멸.
부수: H-140(ROADMAP의 폐기된 "해제 시 slot.Offset = nil" 잔존) 정정,
H-125 피해 범위를 "중첩 Slot의 Offset"으로 축소(유저 체인은 요소 인스턴스에
바인드돼 전파됨 — 실측), G각도로 "for d in seen do는 유효한 Luau가 아니다"가
거짓임을 확인, keyof<{}> 빈 Store 실측 클린.
conventions.md 신설: "/code-review(그리고 메인 세션)가 내놓는 새 필드·인자·
이름·메커니즘은 발견이지 결정이 아니다" — 이 세션에서 메인 세션도 같은 실수를
세 번 했다(subject 인자 / Observer 위치 필드 = 기각된 Effect userdata 재개방 /
조회 클로저).
검증: quad-doc-auditor 6라운드(확실 1→1→3→1→1→0, base/ 본문 결함은 1라운드
이후 0건 — 나머지는 인덱스 레이어·인용처), doc-check ERROR 0. /code-review high는
Q4~Q10 반영 뒤 한 번에(같은 파일을 또 건드려 diff가 섞이므로 지금 체크포인트).
Claude-Session: https://claude.ai/code/session_01F9zgJ4c4kDitAoQMm9qxKn
Co-authored-by: qwreey <me@qwreey.moe>
발견 17건(H-107~H-123)의 사용자 결정을 base/ 전체에 반영. 7라운드 확정 중
뒤집힌 건 없고, 고친 건 전부 7라운드가 base/에 내려앉을 때 생긴 누락·충돌
(하루 차로 확정된 결정들이 서로를 못 본 자리)이다. 결정의 소스는
qa-request/pre-implementation-handtrace-round8-followup.md.
계약 변경 넷:
- Ref 콜백이 fn(value, ref) — 2번째가 곧 출처 Epoch. Effect가 Update(from)에
넘길 유일한 통로였다(k(value)뿐이면 Update(nil) 크래시, 실측 재현).
- Observer fn이 세 자리 fn(targetState, self, emitFrom) + observer._state
강참조. 옛 2-인자는 "self는 리시버" 계약과 정면 충돌해 무인자
state:Observer()의 내부 콜백이 즉사했다.
- WeakSubscribe도 .Subscribed를 세운다. 안 그러면 Effect의 State dep 전량이
조용히 침묵. 해제는 "건 경로로 푼다"(양방향 fail-fast).
- 예약 키 진단이 CheckReservedKeys<keyof<T>> — T를 통째로 넘기는 배선은
실사용 T에서 아예 안 돈다(Source<T>가 *error-type*을 품어 유효한 Store
전부에 스퓨리어스 에러).
사용자가 문항의 전제를 두 번 정정: Ref 콜백과 Observer 콜백은 이질적이라
애초에 통합 대상이 아니었고(Observer엔 자기 epoch가 없다), H-118은 소유권
문제가 아니라 gate-plan 5번의 문장이 틀린 것이었다(🟡→🟢).
커밋 전 검증 — 감사 11라운드(44건, 0건으로 수렴) + /code-review high
7라운드(42건) = 86건. 감사가 0으로 수렴한 직후 code-review가 42건을 냈고,
그중 하나가 H-101의 "새 필드를 안 만든다"를 역전시켰다: getOffsetAt의
부수효과가 splice의 되감기 신호를 지우는 경로가 실재해, 부기 필드를
offsetCacheValidUpTo(캐시)와 offsetSetUpTo(:Set 완료) 둘로 분리했다.
"Set을 해줬느냐"와 "캐시가 유효하냐"를 한 값이 쥔 게 원인이었다.
그 외: splice 무효화 i-1, 명시 recompute 호출부 전부 재진입 게이트,
recompute 되감기 클램프, Store defaults isSource 검증, isModifier 가드를
Source 생성자로, 훅 슈가 nil 가드, pesde.lock 커밋 확정, :Single 3-인자.
2026-08-25 session/ 원문 공백은 2026-08-19 선례대로 재구성 없이 기록만.
doc-check ERROR 0 / WARN 기준선 유지.
Claude-Session: https://claude.ai/code/session_01F9zgJ4c4kDitAoQMm9qxKn
Co-authored-by: qwreey <me@qwreey.moe>
3패스에 걸쳐 base/ 전 문서 완독. 개별 함수가 아니라 "7라운드에 반영된
결정들이 서로 겹칠 때 성립하는가"를 트레이싱했고, 스파이크 7개로 실측
(H-107 런타임 크래시 재현, H-112 keyof 배선 해법 실증 포함). 사용자 결정
문항 Q1~Q10 배치 정리 — 그중 🔴 다섯(H-107~H-109/H-112/H-119)은 M2 착수
전 회신 필요. 아직 base/ 반영 없음, 결정이 나면 followup을 만든다.
커밋 전 /code-review가 보고서 자체의 결함 7건을 잡아 전부 검증 후 반영:
문서 내 사실 정정 6건([code-review 정정/추가] 표시)과 인덱스 레이어
미갱신 1건 — question.md 최우선 절/todos.md 00/README.md/CLAUDE.md/
project-context.md의 "M2 게이트 0" 서술을 "8라운드 회신 대기"로 정정.
doc-check ERROR 0 / WARN 기준선 유지.
Claude-Session: https://claude.ai/code/session_01Cokkxc38ozgmcxPZraV8b7
Co-authored-by: qwreey <me@qwreey.moe>
`qa-request/pre-implementation-handtrace-round7.md`의 `H-55`~`H-106`을
사용자와 대화형으로 처리하고 `base/`에 전량 반영했다. 결정의 소스는
`-followup.md`(개수·개별 항목은 여기서 세지 않는다).
## 처분
확정 39 / 무효·소멸 4(`H-73`~`H-76`) / 기각 1(`H-77`) / 범위 축소 2 /
다른 항목으로 흡수 6.
**부수로 `question.md` 최우선 절이 비었다** — 중간 State GC는 `_hold`
불변식(하류 → 상류 강함)으로, 동적 키 표면 위치는 `store:Of<<T>>(name)`
하나로 닫혔다. **M2 착수를 막는 항목이 없다.**
## 구조가 바뀐 것
- `Ref`가 `Epoch`를 만족(`.Revision` + `EpochBrand`) — 포탈 캐치업 비대칭과
같은 `Ref` 중복 dep이 같이 닫힘
- `Weak*` 등록 표면 신설(`Ref:WeakCallback` / `Observer:WeakSubscribe`) —
Weak 쪽이 프리미티브고 강한 쪽이 "GC 킵"을 얹은 것
- `Effect`: dep 등록이 생성자 한 곳으로, 강한 주인은 `_deps` 하나,
억제는 사적 `Blocker`, `bindLifetime`은 핸들 하나에만 적용.
`:Rerun()` 정의 신설(재진입은 지연 재실행), `_installed` 신설
- 전파 루프 의사코드 확정 — 구독자 집합의 원소는 Observer **값**이고
**자식 State 노드는 `canExecute`를 안 탄다**(그대로 짜면 파생 State
아래가 전부 침묵했다)
- `rawInvalid` → `cacheTargetCount`/`cacheCurrCount` 카운터 쌍
- `recompute` 재진입 차단 + `invalidAfter` 되감기, `gatedRecompute`가
인덱스를 캡처하지 않고 토큰으로 조회
- `emit(commit) -> boolean`, `EpochMap:Peek` — 정책이 흡수 집합을
버리지도 읽지도 못하던 것을 닫음
- error 계약(`level` 이분, 메시지는 영어)과 예외 계약(`pcall`로 안 감쌈)을
`architecture.md`에 신설
## Store — 같은 날 재설계하고 철회했다
`H-75`/`H-76`으로 `WrapStore`/`ProcessStoreType`이 폐기되자 그 자리를
"`store.key`는 값, `store:Of(k)`가 프리미티브"로 채웠다가 **같은 날
철회**했다(`archive/store-value-field-redesign-withdrawn.md`). 살아남은 건
**명시적 초기화** 하나다. 최종형은 타입 인자에 `Source<T>`를 직접 쓰고
`store.key`는 평범한 레코드 필드이며 **타입 함수를 안 쓴다**.
철회 이유 중 하나가 원칙으로 승격됐다 — **"타입 함수는 타입이 못 잡는
문제를 에러로 띄우는 정도 이상으로 가지 않는다"**(`typing-limits.md` §0).
`index<>`/`keyof<>`도 Luau가 predefine한 타입 함수라 같은 함정을 갖는다.
## 툴체인
**`luau` CLI가 심볼릭 링크를 못 탄다**(디렉토리·파일 둘 다)는 것이 최소
재현으로 밝혀졌다 — pesde 워크스페이스 링크가 전부 심볼릭이라 스모크 2개가
안 돌았고 `luau-analyze`는 **조용히 통과**했다("거짓 클린"). `scripts/relink.sh`
+ `scripts/test.sh` 신설, 이제 스모크 셋 전부 PASS.
## 검증
`/code-review high` 2회(12건 + 14건)와 감사 8패스(12/6/6/4/13/3/6/0건)를
각도를 바꿔가며 돌렸고 전부 반영했다 — 마지막 패스가 무발견으로 수렴.
각 패스의 각도와 대표 발견은 `-followup.md`의 검증 절이 소스.
`doc-check.py` ERROR 0.
Co-authored-by: qwreey <me@qwreey.moe>
Claude-Session: https://claude.ai/code/session_012oLwATeQdq9TCFdENPutFG
발견을 더 찾는 패스가 아니라 이미 나온 것이 유효한지만 판정한다. 52건
전부를 한 세션이 한 컨텍스트에서 썼으므로 쓴 세션 자신은 자기가 뭘 잘못
봤는지 모른다(conventions.md가 반복 확인한 그 패턴). 판정 뒤 사용자가
batch로 결정하므로 무효 항목이 섞이면 그 비용을 그대로 문다.
**재실행이 아니라 대조로 판정했다.** audit/handtrace-round7-reference-impl/
README가 못박은 대로, 4·5·6차 패스의 발견은 스파이크를 다시 돌려서가 아니라
전사물(core/dispatch/chain.luau)과 base/ 확정 의사코드를 줄 단위로 맞춰
판정했다. Luau 언어 동작·저장소 상태를 주장하는 것(H-71/H-77/H-73/H-75/
H-76/H-78/H-83, 타입 스파이크 전량)만 직접 재실행해 교차 확인했다.
## 전사 대조 결과 — 전사 오류로 생긴 발견은 없다
State:_receive / _recompute / Get, GateNode _flush/_receive, Source:Set의
bit32.bnot(-rev), emitDown 스냅샷, getOffsetAt/recompute(에러 메시지까지)/
setLength/setOffsetSource, chain의 process/retractFrom — 전부 원문과 일치.
README가 신고한 의도적 차이 3개 외에 **미신고 차이 3개**를 찾았으나 셋 다
발견을 만들지 않는다: (1) Blocker:Policy가 2인자(죽은 인자, 아무도 안 넘김),
(2) Blocker.handles가 강한 배열(문서는 weak — H-63(2)를 오히려 가림),
(3) M.Observer가 즉시 1회 실행을 안 하고 self.owner 강참조를 둠(앞은
dispatch.luau가 명시 호출로 등가 보정, 뒤는 GC 문제를 가리는 방향).
## 판정
- 유효 49 / 범위 축소 2 / 무효 0 / 중복 0.
- **H-87 범위 축소** — "코퍼스가 error 경로를 어느 쪽도 안 다룬다"가 거짓.
slot-plan.md가 같은 실패 모드를 명시하고 사용자가 2026-08-21에 "pcall로
감싸지 않는다"로 이미 판단했다. 갈래 (a)가 든 두 자리 중 하나가 닫힌
결정이라, 그대로 올리면 사용자가 자기 판단을 모른 채 재결정하게 된다.
남는 진짜 질문은 그 판단을 Dispatch.drive(자가치유 없음)까지 확장할지.
- **H-105 범위 축소** — 개수를 직접 셌다. 영어 6은 맞지만 한국어는 17이
아니라 약 23이고, 17+6이 애초에 "42곳"과 안 맞는다. 현상과 결정
필요성은 유효.
- **H-88 정정** — 셋 문서에 error 0건은 참이나 "코퍼스에 한 줄도 없다"는
과하다. 선례가 하나 있고 그건 갈래 (b) 쪽을 가리킨다.
- **H-104 정정** — "level 인자가 하나도 없다"는 전수 확인으로 참,
"42곳"만 약 29곳으로 정정.
## batch용 요약
번호순 52건이 아니라 결정 단위 12묶음(A~L)으로 재편성했다. 같은 묶음은
결론이 서로를 규정하므로 따로 답하면 갈라진다. 몇 가지 눈에 띄는 것:
- H-55(버리기)와 H-86(읽기)은 emit(commit) -> boolean 하나로 같이 닫힌다.
- H-56/H-62/H-61은 "전파 루프 의사코드" 한 블록을 쓰면 같이 닫힌다.
- H-73을 탑레벨 함수로 닫으면 H-74가 통째로 소멸한다.
- H-58과 H-59는 Ref 콜백 등록을 공유 헬퍼로 빼는 같은 수정으로 닫히고,
그 전에 H-60(Rerun 정의)이 있어야 나머지를 쓸 문장이 생긴다.
- H-71의 해법 (b)가 H-77엔 안 듣는다(실측) — Relate 슬롯 셋을 나눠 쓸 것.
- H-78이 제일 급하다: 지금 이 저장소에서 smoke 2개가 안 돌고
luau-analyze가 거짓 클린을 준다(검증 세션에서 재현).
- H-66/H-67/H-82/H-91은 결정 불필요(근거·표기만 정정).
.claude/README.md의 qa-request 행에도 등재. doc-check.py ERROR 0.
Co-authored-by: qwreey <me@qwreey.moe>
지금까지 세션 스크래치패드에만 있던 것을
.claude/audit/handtrace-round7-reference-impl/로 옮긴다. 다음 세션이
발견 52건을 판정하려면 이게 있어야 한다.
구성:
- spikes/core.luau — 반응형 코어(state-epoch §2~§5, 전파 모델,
gate 4·8번, Blocker, H-23 스냅샷)
- spikes/dispatch.luau — Length/Offset 부기(getOffsetAt 접두합 캐시,
recompute, setLength/setOffsetSource, 배치 게이팅)
- spikes/chain.luau — Dispatch 체인(하강 diff, retractFrom)
- spikes/*.luau — 시나리오 29개(런타임 18 + 타입 11)
- RUN-runtime.txt / RUN-typecheck.txt — 2026-08-25 실행 결과 스냅샷
- README.md — 원문 대조표 + 스파이크→발견 대조표
⚠️ README가 가장 중요하다: 이 참조 구현은 base/의 확정 의사코드를 손으로
옮긴 **전사물**이라 그 자체가 틀렸을 수 있다. 그래서 "재실행은 검증이
아니다"를 못박고, (a) 어느 파일이 어느 절을 옮긴 것인지 대조표와
(b) 문서와 **의도적으로** 다른 곳 3개(H-72 때문에 임시로 둔 PeekDiffers,
H-102의 A/B 대조를 위한 위치 박스, 생명주기 게이팅 부재=H-97)를 같이
싣는다. 그 셋 외의 모든 차이는 전사 오류 후보다.
부수로 round7 문서 머리에 이 폴더 포인터와 같은 경고를 달았고,
.claude/README.md의 audit/ 행에도 등재했다.
Co-authored-by: qwreey <me@qwreey.moe>
1~5차가 안 쓴 각도 둘:
(1) 4차의 참조 구현을 M2→M3 경계까지 이어 붙였다 — Length/Offset 부기
(접두합 캐시 getOffsetAt / recompute / setLength / 배치 게이팅)와
Dispatch 체인(하강 diff / retractFrom)을 의사코드 그대로 돌렸다.
이 부기는 2026-08-21 접두합 캐시 도입과 6라운드 H-3/H-5/H-19 수정을
거친 뒤 한 번도 실행된 적이 없다.
(2) quad 자신이 던지기로 확정한 error 42곳의 계약 감사 — 4차가 "사용자
콜백이 던졌을 때"를 봤다면 이번엔 반대 방향이다.
🔴 H-101 recompute가 순회 도중 재진입되면 바깥 호출의 꼬리가
ownerKey.Length를 낡은 합계로 덮어쓴다(합계 3인데 2가 남음).
offset은 다 맞고 Length만 틀려서, 증상은 한 단계 위 형제부터 난다.
H-85("긴 연산의 마지막 줄이 도중에 바뀐 값을 덮어쓴다")와 같은 계열
🔴 H-102 spliceArraysDown이 bk.observers를 옮기지만 setLength가 만든
gatedRecompute 클로저에 박힌 position 인덱스는 안 고쳐진다 → 이후
그 자리의 길이 변경이 캐시를 엉뚱한 범위로 무효화해 뒤 형제 offset이
낡은 채로 남는다(A/B 대조: 1 vs 기대 5). 규칙 셋(2026-08-18 observers
이동, 2026-08-21 접두합, 2026-08-24 H-3)이 각각 옳은데 겹치는 자리가
아무 목록에도 없었다
🟡 H-103 process가 던지면 체인 슬롯에 NOOP retractor가 영구히 남아 그
자리가 잡은 자원의 정리가 사라진다. 미주입 op 스텁(확정된 정상 동작)
때문에 흔하다 — attribute 이름이 영구 잠기고, 다음 사용자에겐 진짜
원인이 아니라 "이미 다른 소유자가 잡고 있다"는 엉뚱한 에러가 보인다
🟡 H-104 error 42곳 전부에 level 인자가 없다 → 전부 quad 내부 줄을
가리킨다. 사용자 입력 검증과 내부 불변식 위반을 갈라야 한다
🟡 H-105 error 메시지 언어가 이미 갈려 있다(한국어 17 / 영어 6).
conventions.md의 "사용자=본인"과 "라이브러리 사용자"가 다르므로
에이전트가 임의로 정할 수 없는 항목
🟢 H-106 C-6이 승격한 "부기가 깨졌음" 진단이 getOffsetAt 경로에선 우회돼
익명 산술 에러로 먼저 터진다
부록에 "문제가 없던 것" 5건 — 접두합 캐시 자체의 정확성, 배치 게이팅이
실제로 O(N)으로 접히는 것, 등록 시점 길이가 최종이 아니어도 자가교정되는
것, 빈 Slot 방어, Dispatch 체인 정상 경로.
아무것도 base/에 반영하지 않았다 — 회신 대기.
Co-authored-by: qwreey <me@qwreey.moe>
1~4차가 안 쓴 각도: **확정된 M2 표면을 문서에 적힌 시그니처 그대로 Luau
타입으로 선언하고 `luau-analyze`에 걸었다.** 2차 패스도 타입을 봤지만
대상이 Store의 타입 합성 하나였다(H-73~H-76). 이번엔 호출부 관점 —
"확정된 타입이 확정된 관용구를 통과시키는가"다. 루트 .luaurc가
languageMode: strict라 아래 진단은 프로젝트에 그대로 적용된다.
🔴 H-94 `__call` 테이블은 `(State<T>) -> U` 자리에 안 들어간다 →
`state:Apply(Debounce{...})` 확정 관용구가 타입에러. gate-plan이
"확인할 필요도 없어졌다"며 접은 불확실성이 Debounce/Throttle 쪽에
그대로 남아 있었다(팩토리에 Flush/Cancel을 붙이는 이상 콜러블
테이블일 수밖에 없다). 런타임은 멀쩡하고 타입만 막힌다
🔴 H-95 콜백이 "선언보다 적게 반환"하면 strict에서 에러 — Effect의
`fn -> (() -> ())?`는 cleanup 없는 흔한 모양이, :List의
`updateFn -> (T?, UD?)`는 userdata를 안 쓰는 모든 모양이 안 통과한다.
통과하는 두 형태(가변 반환 팩 / 함수 타입 유니온)를 대조군으로 확인
🟡 H-96 trailing deps가 붙는 순간 콜백 파라미터 무주석 추론이 깨진다 —
typing-limits ②쪼개기가 푸는 범위 밖인데 경계가 어디에도 없다.
2차 패스 부록의 "무주석 콜백도 살아 있다"도 이 구분을 안 했다
🟡 H-97 M2의 `mock 대상 테스트`가 전파 루프를 한 번도 못 돈다 — 루프가
매 발화마다 부르는 canExecute가 M8에서만 구현되고, 미주입 슬롯의
확정 기본값은 에러 스텁이다. 커밋된 mock엔 재료(Signal/Connection)가
이미 있는데 쓰는 쪽이 없다
🟡 H-98 `:Subscribe()`의 공개 계약("참조를 안 담아도 계속 돎")이 중간
State GC 미해결에 종속 — 잘못 닫히면 "GC도 안 되고 발화도 안 하는"
조합이 된다(실제 collectgarbage로 재현)
🟢 H-99 Observer가 파일 자리를 못 받았고 :Subscribe() 전역 강참조
레지스트리의 주인이 어디에도 없다 (Effect.luau는 있는데)
🟢 H-100 `{[Source<T>]: true}`가 `{[Epoch]: true}` 자리에 안 들어간다
(인덱서 키 불변). 단일 Epoch 전달과 §2의 구조적 만족은 성립 확인
부록에 "걸어봤는데 문제가 없던 것" 6건 — state:Gate + blocker:Policy가
타입으로 성립하고 gate-plan의 2026-08-24 정정(오답 b.Policy)은 타입이
잡아준다, source:Apply에 State용 팩토리 통과, Effect의 이형 deps는 타입으로
표현 가능, 게이트 2겹 unfold가 어느 순서로 풀려도 안 샌다 등.
아무것도 base/에 반영하지 않았다 — 회신 대기.
Co-authored-by: qwreey <me@qwreey.moe>
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