quad/.claude/luau-test/STATUS.md
qwreey 9dd82136bd
qa: 8라운드 손 트레이싱 처리 — 결정 Q1~Q10 반영, M2 착수 게이트 0
발견 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>
2026-08-26 17:18:30 +09:00

26 KiB

스파이크 상태판 — 폴더가 곧 상태

마지막 갱신: 2026-08-21Brand인스턴스 브랜드로 전면 재작성되면서(base/brand-plan.md) 옛 Brand.set/Brand.get을 직접 구현해 쓰던 22done/rewrite-required/ 이동(검증 대상인 isRef/isPreRef 포함 관계 자체는 그대로). 같은 날 Epoch/EpochMap 승격도 있었으나 그건 이미 rewrite-required/에 있는 05의 지침에 반영돼 있다. 직전 갱신도 같은 날 — QA 4라운드 F-4-1Dispatch.drive의 props 순회가 단일 일반화 for로 정정되면서, 두 루프 버전을 검증하던 01이 낡아 done/rewrite-required/ 이동(검증 대상인 순서 계약 자체는 그대로). 같이 만들어야 할 스파이크 절 신설 — 아직 파일이 없는 실측 항목(중간 State GC — [2026-08-24] 같이 나열돼 있던 R-11table.insert 구멍 재사용은 6라운드 H-7전제가 사라져 폐기됐다)을 여기 모은다. 직전 갱신은 2026-08-19 — 신규 quad-types 패키지(CheckedQuad<T, Pattern> 버전 패턴 체크 + AddPlugin<Self,P> 체이닝) 검증용 23 신규 추가 → done/ 직행, 같은 날 후속으로 type-version-check 분리에 맞춰 재작성. 그 과정에서 type function을 거친 값은 패스스루라도 이후 제네릭 self 체이닝이 깨진다는 새 Luau 함정을 발견(typing-limits.md §6로 승격). 직전 갱신은 같은 날 — 13을 타입 전용/런타임 두 파일로 분리 (A의 더미 스텁이 B 실행을 막던 문제 해결) + PostRef까지 확장, 런타임 절반은 신규 22로 분가 → 둘 다 done/. 직전 갱신은 같은 날 — M0/M1 스캐폴딩을 처음 실제로 짜보는 과정에서 05를 현행 모델("emit은 항상 전파, 재계산만 캐시로 dedup")로 재작성해 통과 → rewrite-required/done/ 이동, todos.md 00번이 요구하던 "Store 미선언 키 타입 에러" 확인도 신규 스파이크 21로 완료 → done/ 직행. 직전 갱신은 2026-08-15 — 16types.newfunction API 버전 드리프트 (배열이 아니라 {head=..., tail=...} 레코드를 받음) 수정으로 통과, rewrite-required/done/ 이동. 근거: audit/type-recursive-issue-with-typeof/REPORT.md 6-1절. 직전 갱신은 2026-08-14("emit은 항상 전파" 정정으로 05가 옛 모델을 검증 중이라 rewrite-required/로 이동 — archive/invalidate-dedup-propagation-reversed.md). 직전 갱신은 같은 날 다섯 번째 세션(bindLifetime/canExecute/ unbindLifetime 재정정으로 10이 옛 모델을 검증하고 있어 rewrite-required/로 이동 — 이제 not-run/에는 스파이크가 없고 헬퍼만 남음). 직전 갱신은 2026-08-13 열네 번째 세션(하강 diff 재디스패치 확정으로 04/19 이동). 첫 실측은 여섯 번째 세션 — 상세 결과는 .claude/audit/luau-test-first-run-2026-08-13.md. 실행법: luau <파일> (런타임) / luau-analyze <파일> (타입 전용).

[2026-08-13 열세 번째 세션] review-required/가 비었습니다 — 마지막 한 건이던 08이 해소돼 done/으로 갔습니다. [2026-08-14 다섯 번째 세션] not-run/의 유일한 스파이크였던 10rewrite-required/로 갔습니다 — 지금 남은 건 전부 에이전트가 먼저 재작성해야 할 일(rewrite-required/)이고, not-run/엔 스파이크가 아닌 헬퍼 하나만 있습니다.

폴더 개수 누가 처리
review-required/ 설계가 걸림 — 사람 결정 필요 0 사용자
rewrite-required/ 스파이크가 낡음(코드가 깨졌거나, 설계가 바뀌어 옛 모델을 검증 중) 9 에이전트
not-run/ 이 환경에서 못 돌림(Studio 전용) 0(+헬퍼 1) 사용자 or MCP 연결 후 에이전트
done/ 통과 or 판정 끝, 더 할 일 없음 14

⚠️ [2026-08-25 신설] 타입 스파이크는 ./scripts/test.sh가 하는 리링크를 먼저 거쳐야 한다. luau CLI가 심볼릭 링크를 못 타는데(디렉토리·파일 둘 다) pesde의 워크스페이스 링크가 전부 심볼릭이라, 실제 패키지를 require하는 스파이크(23 등)는 리링크 없이는 의도한 진단 대신 링크 실패 진단이 뜬다 — 게다가 luau-analyze는 그걸 조용히 통과시키기도 한다(모듈을 any로 떨어뜨림). scripts/relink.sh를 돌린 뒤 확인할 것. 경위는 7라운드 H-78.

폴더를 옮기는 게 곧 상태 갱신 — 스파이크를 고치거나 돌렸으면 파일을 해당 폴더로 git mv하고 아래 표의 줄도 같이 옮길 것. 파일별 "무엇을 왜 검증하는가"는 README.md가 담당(이 파일은 상태만).


review-required/ — 사람 결정 필요 (0건, 비어 있음)

[2026-08-13 열세 번째 세션] 마지막 한 건이 해소됐습니다. 08-type-source-satisfies-state.luau가 남겨뒀던 잔여 케이스(State<T>가 자기 자신을 다른 타입 인자로 재귀 참조하면 막힘)는 Luau의 현 한계로 확정되어 quad가 설계로 풀 대상이 아님이 정해졌고(구 question.md 0-Y 해소), 스파이크는 done/으로 이동했습니다. 당시 검토됐던 "구울 때 인라이닝" 방향은 채택 안 함.

  • 지금 유효한 규약: base/typing-limits.md
  • 실측 근거 전문(스파이크 다수 포함 — 개수는 spikes/ 폴더가 소스): audit/type-recursion-issue/

15도 같은 계약을 다루지만 스파이크 자체가 파싱 실패rewrite-required/에 그대로 둠 — 재작성 대상이지 사람 결정 대상이 아님(계약 자체는 위에서 이미 확정됨).

🟠 rewrite-required/ — 스파이크가 낡음

(개수는 위 표와 폴더가 소스 — 여기서 다시 세지 않는다. 예전엔 이 제목이 개수를 들고 있다가 실제와 어긋난 적이 있다.)

[2026-08-13 열네 번째 세션] 앞의 두 건은 "코드가 깨진" 게 아니라 "설계가 바뀐" 경우question.md 0-A/0-Z 확정으로 재디스패치가 하강 diff가 되면서, 이 둘이 검증하던 전제(선행 retractFrom + 4-인자 힌트 + 인덱스 점유 체크)가 더 이상 설계가 아님. 통과 상태로 done/에 두면 옛 모델을 "검증됨"으로 오독하게 되므로 옮김. 새 정본은 base/dispatch-core-plan.md/base/attribute-plan.md.

[2026-08-14 다섯 번째 세션] 10도 같은 이유로 합류bindLifetime/ canExecute/unbindLifetime 재정정으로 A 섹션이 폐기된 모델(canBound, bindLifetime.Subscribed 세팅, 2-인자 canExecute)을 검증 중. 10Studio 전용이라 재작성해도 이 환경에서는 못 돌린다 — 재작성 후 다시 not-run/으로 내려가 사용자/MCP를 기다리는 자리다.

[2026-08-21] 0105가 합류 — 둘 다 같은 "설계가 바뀐" 유형이다. 통과 상태로 done/에 두면 01은 구현이 안 하는 두 루프 순회를, 05이제 접히는 중복 통지가 안 접힌다는 것을 "검증됨"으로 오독하게 된다.

[2026-08-26, 8라운드 H-122/H-123] 11-modifier-illegal-value-error가 합류했다 — 그 파일의 Store 생성자가 eager Source(v) 모델을 코드로 박제한 채 done/에서 "통과" 상태로 앉아 있었다. 명시적 초기화 확정 (2026-08-25) 이후 Store는 Source를 만들지 않으므로, 그대로 두면 옛 모델을 "검증됨"으로 오독하게 된다. 재작성 지침은 위 표의 그 행.

[2026-08-26, 8라운드 H-115/H-112] 16/21 재작성 때 넣을 것base/store-plan.md의 최종형 실측 표가 audit/type-store-index-keyof/를 출처로 인용하는데 그 폴더에 최종형 결합을 도는 파일이 없다(대부분 철회된 재설계 대상). 재작성이 최종형(Store<T> = T & {Of, Names, __reservedCheck}, 필드가 Source<T>)과 CheckReservedKeys<keyof<T>> 배선을 같이 담으면 그 출처 문제가 닫힌다. 그 폴더의 02/06은 최종형에서도 유효한 측정이니 버리지 말 것. 그리고 Store<<{}>>()(무인자, keyof<{}>)를 대조군으로 반드시 넣을 것/code-review high(2026-08-26)가 지적한 미실측 자리다. H-83이 무인자 생성을 유효하다고 확정했는데 H-112의 실측은 키가 있는 T로만 돌았고, keyof<{}>가 빈 유니온인지 에러인지에 따라 무인자 Store 전체가 스퓨리어스 타입 에러를 받을 수 있다.

[2026-08-25] 1621이 합류 — Store 재설계로 검증 대상 자체가 폐기됐다. WrapStore/ProcessStoreType로 결과 타입을 합성하는 접근이 사라졌다 — 지금은 타입 함수를 안 쓰고 타입 인자에 Source<T>를 직접 써서 평범한 레코드로 짓는다(base/store-plan.md). 같은 날 index<>/keyof<> + 팬텀 필드 안을 넣었다가 typing-limits.md §0 원칙에 따라 철회한 경위는 archive/store-value-field-redesign-withdrawn.md. 발단은 7라운드 H-75/H-76 실측이다 — 평평한 합성이면 store.key:Compute(무주석 콜백)이 깨지고, type function이 바깥 타입 별칭을 참조하지 못해 구조를 통째로 중복 작성해야 하며 메소드 self 파라미터가 불변이라 필드 하나만 어긋나도 대입이 실패한다. 21의 검증 대상(미선언 키는 타입 에러)은 그대로 유효하고 새 모양에서도 성립함이 확인됐다.

[2026-08-21 후속] 22도 같은 이유로 합류Brand인스턴스 브랜드로 전면 재작성되면서(base/brand-plan.md) 이 스파이크가 직접 구현해 쓰는 Brand.set/Brand.get/XxxTag역전된 옛 API가 됐다 (archive/brand-shared-registry-reversed.md). 검증 대상 자체는 그대로 유효하다isPreRef/isPostRef가 서로 배타적 형제이고 둘 다 isReftrue라는 포함 관계는 재작성 후에도 안 바뀌었다. 옮기는 이유는 결론이 틀려서가 아니라, 통과 상태로 done/에 두면 구현자가 그 파일의 Brand 구현을 참고 모델로 오독하기 때문이다.

파일 상태 무엇을 고쳐야 하나
01-two-pass-array-hash-order.luau 옛 형태 기준으로는 통과였음 숫자 for + 일반화 for 두 루프로 짜여 있는데, 구현은 **단일 일반화 for**로 정정됨(base/dispatch-core-plan.md의 "props 순회 순서" 절, QA 4라운드 F-4-1) — Luau의 일반화 for가 배열 파트를 먼저 다 돌고 해시 파트로 넘어간다는 것 자체를 한 루프로 검증하도록 다시 쓸 것. 검증 대상(순서 계약)은 그대로라 결론이 바뀌는 건 아님
05-store-state-diamond-propagation.luau 2026-08-19 재작성분은 그 시점 모델 기준 통과였음 [2026-08-21] 모델이 또 바뀌었다 — 소스 에포크 비교 채택(base/state-epoch-plan.md)으로 다이아몬드에서 두 번째 통지가 접힌다. 그래서 이 스파이크의 핵심 assert(":Get()을 안 부르는 Observer가 변경당 경로 수(2)만큼 운다")가 정반대가 됐다 — 이제 변경당 1회여야 한다. 살릴 것: invalid 기반 dedup이면 두 번째 변경부터 침묵하는 것을 잡는 음성 대조군(그 금지는 지금도 유효). 새로 넣을 것: DFS 도중 Get()이 섞인 값을 캐시하던 glitch가 에포크로 사라지는지(그 문서 §1의 시나리오)
04-dispatch-chain-retractFrom.luau 옛 모델 기준으로는 통과였음 (1) chains 슬롯이 {handler, retractor}가 되고 Dispatch.process가 핸들러를 먼저 비교하는 하강 diff로 재작성, (2) retractFrom3-인자(힌트 인자 없음), (3) "힌트가 target 인덱스에만 간다"를 검증하던 부분은 정반대로 뒤집힘 — 이제 각 레벨이 자기 값을 받는지를 검증해야 함. 살릴 것: chains:SetStrong 순서 음성 대조군(그 버그는 새 모델에서도 그대로 유효)
19-ownership-refcount-relate-patterns.luau A/C 유효, B 섹션이 낡음 B가 검증하던 "공개 AttributeKey(name) + 인덱스 1 점유 체크"가 폐기됨 — 그룹 전용 키 + AttributeKeyHandler의 이름 claim으로 재작성하고, 음성 대조군도 "두 그룹이 같은 이름 → 즉시 error", "그룹↔직접 쓰기 → 즉시 error"로 바꿀 것(0-Z 확정 내용). A/C는 손댈 것 없음
15-type-compute-trailing-deps-typepack.luau 파싱 실패(SyntaxError) 음성 대조군의 타입 표기가 TypeError가 아니라 SyntaxError로 걸려 파일 전체가 아무것도 검증 못 함 — 대조군을 별도 파일/블록으로 격리
22-runtime-ref-preref-postref-brand.luau Brand API 기준으로는 통과였음 [2026-08-21] Brand가 인스턴스 브랜드로 재작성됨 — 파일 안의 Brand.set(x, tag)/Brand.get(x)/XxxTag 변수를 Brand() + SomeBrand:register(x)/SomeBrand:is(x)로 바꿔 쓸 것(base/brand-plan.md). 검증 대상(isPreRef/isPostRef 배타 + 둘 다 isReftrue, Leaf 핸들러 흉내)은 그대로라 assert는 손댈 게 없다. 새로 넣을 것: 다중 태깅이 실제로 되는지 — 한 값을 두 브랜드에 등록하고 양쪽 :is가 다 true인지(SourceSourceBrand+EpochBrand인 자리, base/state-epoch-plan.md §2)
16-type-store-key-typefunction.luau 옛 접근 기준으로는 통과였음 [2026-08-25] 검증 대상이 폐기됨WrapStore/ProcessStoreType 합성 자체가 사라졌다. 재작성 지침: 타입 함수 없는 평범한 레코드 모양(base/store-plan.md의 "store.key 레코드 필드 타이핑" 절)을 검증하고, 음성 대조군에 예약 키 충돌(CheckReservedKeys<keyof<T>>types.never로 무너뜨리는지 — [2026-08-26 H-112] 인자가 T가 아니라 keyof<T>다)과 없는 키 접근을 포함할 것
11-modifier-illegal-value-error.luau 옛 형태 기준으로는 통과였음(16개 케이스 전원) [2026-08-26, 8라운드 H-122/H-123] 검증 코드가 폐기된 모델을 박제하고 있다 — 그 파일의 Store 생성자가 eager Source(v) 모델이다. 명시적 초기화 확정(2026-08-25) 이후 defaults 경로에선 Store가 Source를 만들지 않는다(동적 키 창구 store:Of(name)은 여전히 만든다 — 그래서 가드가 Source 생성자로 갔다). 재작성 지침: isModifier 가드의 새 자리는 Source 생성자(+Source:Set, :Compute 결과 캐싱)이고, Store 생성자가 하는 건 defaultsisSource 화이트리스트 검증(error level 2)이다 — 둘을 각각 양성/음성으로 볼 것. 검증 대상(핸들러 계층 값 즉시 error)은 그대로라 결론이 바뀌는 건 아님
21-type-store-undeclared-key-rejected.luau 옛 접근 기준으로는 통과였음 16ProcessStoreType을 재사용하므로 같이 낡음. 검증 대상(미선언 키가 타입 에러)은 그대로 유효하다 — 새 Store<T> 선언으로 바꿔 쓰기만 하면 된다(store:Of("nope")이 거부되는 것도 같이 넣을 것)
10-roblox-studio-checks.server.luau (Studio 전용) 미실행 + A 섹션이 옛 모델 A가 옛 2-인자 canExecute(inst,value)bindLifetime.Subscribed 세팅을 검증 중 — bindLifetime이 gcconn을 value 쪽 릴레이션에 복사하는 모델로 재작성할 것(base/lifecycle-pattern.md). [2026-08-14 열한 번째 세션 재정정, 2026-08-18 방향 정정] 이중 바인딩 게이트는 canBound(value)(if not canBound(v) then error(...) endcanBound 참 = "지금 묶어도 됨") — canExecute는 State emit 전파 게이팅 전용으로 분리됨, 둘 다 비공개 헬퍼 isBoundAlive를 공유하는 1-인자 진입점이지만 서로의 부정(base/lifecycle-pattern.md의 "canBound vs canExecute" 절). 살릴 것: "ClassName 신호 미발화 / Destroy 시 Connected 즉시 전환" 검증(새 모델에서 더 중요해짐), gcconn/gchold를 Instance 생성 시점에 만드는 것으로 바꿀 것(옛 lazy 생성 폐기). B/C 섹션은 손댈 것 없음

not-run/ — 이 환경에서 못 돌림

[2026-08-14 다섯 번째 세션] 스파이크는 0건 — 유일했던 10rewrite-required/로 갔음(위 표). 남은 건 헬퍼 하나뿐.

파일 이유
gc-trigger-helper.server.luau 스파이크가 아니라 헬퍼 — Studio에 collectgarbage()가 없어서 GC를 강제 트리거하는 기법. 10을 돌릴 때 같이 씀

done/ — 통과 or 판정 끝

(개수는 위 표와 폴더가 소스 — 여기서 다시 세지 않는다.)

지금 done/에 있는 런타임 스파이크는 02/03/06/07/11/17/18/20, 전원 통과(crash 0 / FAIL 0). 나머지는 타입 스파이크다. [2026-08-21] 22는 여기서 빠졌다Brand 인스턴스 브랜드 재작성으로 파일이 쓰는 Brand.set/Brand.get이 옛 API가 되어 rewrite-required/로 이동(검증 대상 자체는 유효, 위 표의 재작성 지침 참고). [2026-08-21 정정] 여기 "런타임 12개"라고 적혀 있었는데, 그 산술이 이미 rewrite-required/로 나간 04/10/19까지 포함한 옛 총계에서 이어져 온 것이라 실제와 안 맞았다(두 번째 /code-review high 발견). — [열네 번째 세션] 04/19는 검증 대상 설계가 바뀌어 rewrite-required/로 이동했고, [2026-08-19] 05는 현행 모델로 재작성해 잠시 돌아왔다가 [2026-08-21] 소스 에포크 채택으로 다시 나갔으며, 신규 22(구 13 런타임 절반, PostRef까지 확장)가 합류:

파일 확인된 것
02-none-sentinel-vs-nil-holes nil 소진 시 #t 50→49로 무너짐 / None은 항상 50. 반대로 당시의 Ref 콜백 배열은 None 쓰면 죽은 슬롯 1000개 잔존 — 두 배열의 규칙이 서로 반대여야 함이 정량 확인. [2026-08-24] 그 대비의 한쪽(Ref 콜백)은 6라운드 H-7해시맵 셋이 되어 사라졌지만, 이 스파이크가 실제로 확인한 것(일반 Lua 테이블에서 nil 구멍과 None 채움의 거동 차이)은 그대로 유효하다 — sourceList/flattened처럼 순서가 중요한 배열이 여전히 그 결론 위에 선다
03-recursive-store-bind-dispatch StoreBind 재귀 재-dispatch, Nonenil 흐름, 무한재귀 없이 종료
06-component-boundary-nil-hole-props or None 없으면 앞쪽 nil-hole로 슬롯 소실, 관용구 쓰면 항상 보존
07-relate-weak-table-gc 연쇄 GC 확정(아래 별도 절) — GC-native 아키텍처의 핵심 전제
17-modifier-index-tableclone-chaining 제네릭 __index + table.clone 체이닝, 메타테이블 참조 공유, 형제 분기 무오염
18-relate-mutual-cycle-gc Relate 상호 순환은 실제로 GC 안 됨(아래 별도 절)
20-slot-splice-index-arithmetic Splice 산술 11개 경계 케이스 전부 참조 구현과 일치

타입 스파이크 중 판정이 끝나 더 할 일 없는 것:

파일 판정
08-type-source-satisfies-state 핵심 질문(Source⊇State 구조적 서브타이핑) 통과. 잔여 케이스(자기 이름을 다른 인자로 재귀 참조)는 [2026-08-13 13차 세션] Luau 현 한계로 확정 — quad가 풀 대상 아님, base/typing-limits.md 1번
09-type-modifier-overridden-subtype 통과 — 문서가 우려한 FrameModifierGuiObjectModifier 서브타입 깨짐이 그대로 재현, fallback(any)은 정상
12-type-attribute-generic-key-narrowing 지만 설계 영향 없음 — 제네릭 키 narrowing이 안 되는 건 attribute-plan.md가 이미 fallback으로 예비해둔 결과(타입 패밀리가 유일하게 믿을 경로). [2026-08-24 H-54] 단 스파이크 자신의 주석이 실제 결과와 어긋난다 — *"이건 당연히 통과해야 함"*이라 적어둔 동질 대조군(line 41-43)이 실제로는 에러를 낸다(AttributeKey<T>(name)이 문맥에서 T를 못 추론해 unknown으로 남음). 오히려 "왜 진짜 테스트 대상이 조용히 통과하는지(= narrowing이 아예 안 일어남)"를 설명해주는 정합적 결과라 이 총론은 그대로 유효하고, 근거 라인만 다르다 — 재작성 시 참고
13-type-ref-preref-subtype [2026-08-19 재작성] 통과 — PreRef<T>/PostRef<T> 둘 다 Ref<T>를 구조적으로 만족(음성 대조군도 정확히 에러). 런타임 B섹션은 22로 분리(A의 더미 스텁이 B 실행을 막던 문제 해결)
14-type-nilable-default-overload ⚠️ 부분 — 의도한 오용은 막지만 정상 nilable 사용례까지 막아 현 스케치로는 채택 불가. 설계 결정은 아직 필요 없음(대안이 이미 UB 경고로 존재)이라 review-required가 아님
23-type-quadtypes-checkversion-addplugin [2026-08-19 신규, 같은 날 후속으로 재작성] 통과 — 실제 quad-types/quad-base/type-version-checkCheckedQuad<T, Pattern>+AddPlugin<Self,P> 통합 검증. 재작성 과정에서 type function을 거친 값은 패스스루라도 이후 제네릭 self 체이닝이 조용히 깨진다는 새 Luau 함정 발견(typing-limits.md §6으로 승격), export type function/이중 꺾쇠 제네릭 인스턴스화 요구도 같이 실측 — 최종 설계(별도 가상 필드로 격리)는 양성/음성 경로 모두 클린

특별히 중요한 통과 3건

04 — 직전 감사가 찾은 버그가 음성 대조군으로 재현됨(파일은 지금 rewrite-required/에 있음 — 아래 관측 자체는 새 모델에서도 유효)

관측 지점 정상(수정본) 대조군(버그)
최초 마운트 후 체인 깊이 3 1
재발행 후 옛 store 구독 0 (끊김) 1 (안 끊김)
죽은 store를 건드리면 값 유지 STALE로 덮어써짐

chains:SetStronghandler.process 뒤에 두면 하위 retractor가 통째로 유실되고, 결국 버려진 store가 나중에 UI를 덮어쓰는 데까지 감.

07 — GC-native 아키텍처의 핵심 전제

inst 5개만 살린 상태 → 살아남은 payload 5 / 엔트리 5   (기대치 일치)
모든 참조를 놓은 뒤   → 살아남은 payload 0 / 엔트리 0   (기대치 일치)

bindLifetime으로 매달아둔 자원이 inst와 함께 연쇄 소멸함이 확인됨. (이 스파이크는 원래 sanity check만 하고 있어서 이번에 보강한 것 — 파일이 스스로 적어둔 "weak table 엔트리를 셀 방법 없음"이라는 전제가 틀렸음.)

18relate-plan.md의 상호 순환 경고

상호 강참조 순환:        inst=true,  value=true   (GC 못 풂)
한쪽을 weak-value로 낮춤: inst=false, value=false  (풀림)

추측이 아니라 실제로 GC가 안 됨Slot의 두-Relate 수정이 필수 조치였음이 입증.

🔵 만들어야 할 스파이크 — 아직 파일이 없음 (2026-08-21 신설)

폴더가 곧 상태인 이 문서에서 **"아직 파일조차 없는 실측 항목"**은 어느 폴더로도 표현되지 않아 그냥 잊혔다. 실제로 QA 4라운드 followup(H-7)이 "실측으로 남은 것"의 소스로 이 문서를 지목했는데 여기 항목이 없었다. 앞으로 이 절이 그 소스다 — 파일을 만들면 not-run/ 또는 실행 결과에 따라 해당 폴더로 옮기고 여기서 지운다.

검증할 것 출처
table.insert가 배열 중간의 구멍을 재사용하는가 [2026-08-24 폐기] 전제 자체가 없어졌다 — 6라운드 H-7Ref.Callbacks가 배열에서 {[callback|thread] = true} 해시맵 셋으로 바뀌었고, 해시맵엔 border 개념도 구멍도 없다(base/ref-plan.md). 이 스파이크는 만들지 말 것 QA 4라운드 R-11(폐기), 6라운드 H-7
중간 State가 _hold(하류 → 상류 강함) 불변식으로 실제로 살아남는가 [2026-08-25] 설계는 확정됐다 — 각 파생 노드가 자기 상류를 _hold로 강하게 든다(사용자 확정). 남은 건 실측뿐이고 M2 착수 게이트는 아니다(question.md 최우선 절에서 내려감). 음성 대조군으로 "_hold 없이 짜면 중간 노드가 수거돼 전파가 끊긴다"까지 볼 것 base/source-state-plan.md의 "해소됨 — 중간 State는 _hold로 살아남는다" 절
Relate 값/내부 키가 바깥 키를 되참조하면 새는가 [2026-08-25 신설, 7라운드 H-71/H-77] done/07안전한 모양만 봤는데 여러 문서가 그걸 "GC-native 아키텍처의 핵심 전제 검증"으로 인용한다. 실제로는 (a) SetStrong이 되참조하면 100% 새고, (b) 내부 키가 되참조하면 SetStrong/SetWeak 둘 다 샌다. 07에 음성 대조군으로 추가할 것 base/relate-plan.md의 "위험한 패턴" 절 슬롯 표
[2026-08-26 신설, 8라운드 H-112] CheckedQuad<T, Pattern>이 M2 표면 추가 후에도 사는가 8라운드에 한 번 실측해 통과했지만(배선이 격리형이라 QuadSource 필드를 더해도 클린), 실제 M2 반영 뒤 Quad 타입의 선언 스타일이 정해지면 다시 봐야 한다. 23(done/에서 통과 상태)이 CheckedQuad를 쓰므로 그걸 다시 돌리는 배치. [2026-08-26 정정, /code-review high] 여기 한때 "rewrite-required/23의 재작성과 같은 배치"라고 적었는데 rewrite-required/23은 없다 — 일어나지 않을 재작성에 실측을 매달아 고아가 될 뻔했다 ROADMAP.md M2의 H-80 체크박스, 8라운드 H-112
Visible = false인 GuiObject의 AbsoluteSize/AbsolutePosition이 갱신되는가 quad-roblox-fastscroll 설계의 선행 실측. Studio 필요 — 만들면 not-run/ research/fastscroll-plan.md