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>
110 KiB
110 KiB
.claude/ — quad-v2 계획/설계 문서 색인
이 레포 전체가 quad-v2(재작성) 프로젝트이므로, webmanager류 서브프로젝트 구분 없이
.claude/ 바로 아래에 전부 있음. 루트 CLAUDE.md가 진입점 — 먼저 그걸 보고,
특정 결정의 자세한 근거/논의가 필요할 때만 아래 개별 문서를 열어볼 것.
[2026-08-16 재구조화] CLAUDE.md가 1537줄까지 불어나 사람이 검토할 수
없고 지침 준수도도 떨어져서, 주제별로 쪼개고 @import로 다시 합침. 지금
CLAUDE.md는 짧은 진입점일 뿐이고 실제 내용은 아래 파일들에 있음(앞 셋은
매 세션 자동 로드, session-summary.md/question.md는 온디맨드):
| 파일 | 무엇이 들어있나 |
|---|---|
conventions.md |
언어/모델 관례 + 설계 원칙 + 작업 방식(핸드오버 체크리스트, doc-check.py, quad-doc-auditor, SAFETY 준수 등 에이전트가 따라야 할 절차 전부) |
project-context.md |
이 프로젝트가 뭔지 + 계획 문서 구조(폴더별 성격 요약 — 상세 색인은 이 README가 소스) |
todos.md |
지금 할 일(우선순위순). 가장 자주 바뀜 |
session-summary.md |
세션별 2~4줄 요약 색인. @import 안 됨(의도적) — 이만한 분량을 매 세션 컨텍스트에 올릴 이유가 없어 온디맨드로 둠, 선행 맥락이 필요할 때 grep해서 열 것. 자동생성 전환 예정(research/doc-include-plan.md) |
question.md |
사용자가 답해야 할 열린 질문(우선순위순) |
폴더 기준
| 폴더 | 기준 |
|---|---|
base/ |
결정 완료 + 프로젝트 전체에 걸치는 컨텍스트 — plan/done 개념 없음, 계속 참조되는 배경지식. 항상 읽어야 하는 배경지식만 여기 둠(다른 문서를 이해하는 데 전제되는 것) |
reference/ |
[2026-08-07 신설] 결정 자체가 아니라 다른 문서가 근거로 인용하는 온디맨드 참고 자료(v1 스냅샷, 프레임워크 비교 리서치) — "완료" 개념 없는 건 base/와 같지만, 항상 읽을 필요는 없고 해당 문서가 인용될 때만 열어보면 됨. quadnomicon 소재 후보가 많음. [2026-08-21 확장] 확정된 결정의 "왜 그렇게 정했나" 근거 기록도 여기 둔다 — research/(아직 상의 필요)도 archive/(뒤집혔거나 기각됨)도 아니고, base/가 근거로 인용하는 온디맨드 자료라는 이 폴더의 기준에 정확히 맞기 때문(slot-attach-decomposition.md/epoch-brand-composition.md가 그렇게 들어옴) |
research/ |
아직 착수 전, 사용자와 스코프/설계를 더 상의해야 함 |
qa-request/ |
원래 용도는 "구현 완료(코드/에이전트 검증까지 끝남) + 사용자 본인의 실기기(Roblox Studio) QA만 남음". [2026-08-18 확장] 구현 전에도 사용자 심사 라운드의 산출물을 여기 둠 — pre-implementation-qa-round1.md(1라운드: base/ 확정 문서 전체를 문항으로 재확인받아 "아니오"가 나온 항목만 모은 결함 목록 + 신규 요구사항(N-n) + 부수 오탈자. 같은 날 전부 base/에 반영 완료라 지금은 "무엇이 왜 틀렸었나"의 근거 기록이고, 지금 유효한 설계는 항상 base/가 소스. 아직 안 닫힌 것은 question.md 최우선 절과 .claude/todos.md 00번이 소스), pre-implementation-qa-round2.md(2라운드: 확정 의사코드를 실제로 손으로 실행해보는 트레이싱 — 완료, 발견된 크래시 RC-1(recompute 트리거 모델)도 같은 날 후속 세션에서 Blocker 게이팅 설계로 해결·반영까지 끝남, archive/question-resolved.md에 논의 요지 정리), pre-implementation-qa-round3.md(3라운드: RC-1 해법(Blocker 게이팅)이 실제로 attachSlot/recompute에 반영된 걸 손으로 트레이싱 — 완료, RC-3/RC-4(activateList가 자기 Slot의 Blocker보다 먼저 실행되는 순서 문제)와 bk.N 수명주기 미정을 발견했다가 같은 세션에 사용자가 최초 분석 오류를 직접 정정하며 전부 해결·base/ 반영까지 끝남, archive/question-resolved.md에 논의 요지 정리. ROADMAP.md M3가 M2의 Blocker.luau에 의존하게 된 마일스톤 순서 불일치는 각주로 반영 — [2026-08-24] 그때 열어뒀던 마일스톤 재편은 M2/M3 순서 교체로 닫혔다). pre-implementation-qa-round4.md(4라운드: 사용자 요청으로 base/ 확정 전체를 "예가 나와야 정상인 문항"으로 다시 뽑은 전수 문항지 — [2026-08-21] 완료·종결. 1-followup.md에 쌓였으므로 "아니오만 남기는 재편"은 안 하기로 함(같은 정보가 두 곳에 갈라지는 걸 피함). 문항 수/문서별 분포는 그 파일 자신이 소스). pre-implementation-qa-round4-response.md(사용자 회신 원문 — 이 라운드는 회신을 별도 파일로 받았다, 1pre-implementation-qa-round4-followup.md([2026-08-20 시작, 2026-08-21 종결] 그 회신 처리 결과 — ADetach 보존 주체, KeyGone, Owned, attachSlot 분해). 열린 질문 없음. [2026-08-21 정정] 여기 적혀 있던 "5라운드 문항지는 만들지 않는다"는 뒤집혔다 — 같은 날 사용자 요청으로 5라운드를 만들었다), pre-implementation-qa-round5.md([2026-08-21 신설·처리 완료] 5라운드: 4라운드에서 "예"로 넘어간 자리는 건너뛰고 (1) 4라운드에 문항이 아예 없던 영역(project-setup-plan.md/quad-types-plan.md, 그리고 문서가 아니라 실제 커밋된 M1 코드), (2) 4라운드 회신 이후 새로 확정된 것(Detach/_detached/KeyGone/Owned/attachSlot 분해 등), (3) 큰 문서의 심화(예: debounce-throttle-plan.md)만 묻는다. 문항 수는 그 문서 자신이 소스), pre-implementation-qa-round5-response.md(사용자 회신 원문 — 4라운드와 같이 별도 파일), pre-implementation-qa-round5-followup.md([2026-08-21] 그 회신 처리 결과 — 즉시 반영분 / 재질문 / 사용자 판단 필요 / 새로 만든 문서 둘(gate-plan.md·state-epoch-plan.md — 같은 날 확정되며 base/로 승격)까지. 처리 결과의 소스는 이 파일). pre-implementation-handtrace-round6.md([2026-08-22 신설] 6라운드: 문항지가 아니라 손 트레이싱이다(2·3라운드와 같은 성격) — 사용자가 지목한 최근 확정 5개 영역(Effect(fn, ...deps)/Gate·Blocker/State 전파(rawInvalid·emit 지연)/Slot의 native*·offset·length·mount/Brand·Epoch·EpochMap)을 실제 값으로 돌려본 결과. 발견 번호는 H-n. [2026-08-23] 2차 패스 — 1차가 안 본 영역(디스패치 코어 전체/라이프타임 유틸/Ref·Tag·Attribute·UI 숏핸드 핸들러/Slot의 raw* 계층). [2026-08-23] 3차 패스 — 1·2차가 한 번도 안 연 문서 전체(Store/State/Source 코어, Modifier·컴포넌트 합성, 이벤트·라이프사이클·에러 격리, Tween·시간 게이트, 타입 계약과 실제 커밋된 M1 코드) + 통합 시나리오. [2026-08-24] 4차 패스 — 문서 단위가 아니라 축을 바꿔서(핸들러 레지스트리 전수/두 대형 핸들러 문서 심층/luau-test 스파이크 실제 재실행/프리미티브 조합 매트릭스/reference·archive·로드맵 M2luau/luau-analyze와 공식 문서로 직접 재현·교차검증했고, 그 부수로 기존 H-2의 크래시 주장이 틀렸음도 드러났다(3차 패스 머리의 정정 절). 발견 번호는 패스를 가로질러 이어서 매긴다. 발견 수/심각도 분포는 그 문서 자신이 소스. 각 패스의 "확인만 하고 문제 없었던 것" 절은 다시 트레이싱할 필요 없는 자리를 적어둔 것. ⭐ [2026-08-24] 전량 처리·반영 완료 — 그 문서는 이제 발견 당시의 기록이라 각 항목의 "갈래"는 선택 전 목록이니 그대로 믿지 말 것), pre-implementation-handtrace-round6-followup.md([2026-08-24 신설] 6라운드의 결정과 근거가 여기 소스다 — H-1~H-54를 사용자와 대화형으로 하나씩 결정한 기록이고, 반영 후 /code-review high가 잡은 7건(그중 셋이 이번 반영이 만든 회귀)도 D절에 있다. 진행 경위와 사용자 발언 원문은 session/2026-08-24-01-handtrace-round6-resolution.md). pre-implementation-handtrace-round7.md([2026-08-25 신설, 회신 대기] 7라운드: 6라운드와 같은 손 트레이싱이되 범위가 M2(반응형 코어)와 M2→M3 경계다. 패스 5개가 각기 다른 각도를 쓴다 — 1차는 프리미티브 사이의 호출 순서를 시간축으로 겹쳐 보기, 2차는 문서가 "확인했다"고 적은 런타임/타입 주장을 실제로 luau/luau-analyze에 걸어보기, 3차는 커밋된 M1 코드·툴체인을 이 저장소에서 그대로 돌려보기 + 1·2차가 뺐던 "M2를 소비하는 문서", 4차는 M2 코어를 문서 그대로 옮긴 참조 구현을 돌려보기 + 아무 문서도 안 정한 예외 경로, 5차는 확정된 M2 표면을 실제 Luau 타입으로 선언해 luau-analyze에 걸어보기(확정 시그니처가 확정 관용구를 통과시키는가). 발견 번호는 6라운드에서 이어서 H-55부터, 패스를 가로질러 연속으로 매긴다. 발견 수/심각도 분포는 그 문서 자신이 소스. 각 패스의 "문제가 없던 것" 부록은 다시 파지 않아도 되는 자리를 적어둔 것. 아직 아무것도 base/에 반영하지 않았다 — 판정은 사용자가 하고, 결정이 나면 6라운드처럼 -followup.md를 새로 만든다). 다음 라운드가 필요해지면 라운드마다 파일을 새로 만들고 이름에 라운드 번호를 넣을 것 |
archive/ |
완료 + 사용자가 실사용/실기기로 직접 검증까지 마침 (구현 대상). [2026-08-06 확장] 완전히 뒤집힌 설계 결정을 원문+역전 이유+diff와 함께 보존하는 용도로도 사용(제목 [역전됨] — 한 번 확정했다가 뒤집힌 것) — 더 이상 능동적으로 참고 안 해도 되지만(토큰 낭비 방지 위해 base//research/에서 뺌) quadnomicon 소재로는 나중에 쓸 수 있음. [2026-08-07 확장] 후보였다가 채택 안 된 것(확정한 적 없이 검토 후 기각)도 같은 방식으로 보존, 제목은 구분을 위해 [기각됨] — [역전됨]과 의미가 다르므로 혼동하지 말 것. [2026-08-07 세 번째 확장] 설계 반전/기각과 별개로, 에이전트가 문서 작성 중 스스로 낸 개념 혼동을 정정한 이력은 [에이전트 실수] 태그로 agent-mistake.md 하나에 모음(.claude/session-summary.md/session/ 로그와의 중복 방지) |
feedback/ |
실사용 피드백을 정리한 긴 로그 — [2026-08-19 기준] 폴더 자체가 아직 없음(M0/M1 스캐폴딩만으론 안 생기고 실제로 렌더링해보고 쓰는 단계부터, 첫 피드백이 생길 때 만들면 됨). qa-request/는 [2026-08-18] 더 이상 비어 있지 않음(구현 전 QA 1라운드 산출물이 들어감) — 여긴 아직 폴더도 없음 |
luau-test/ |
[2026-08-09 신설] base/ 확정 사항 중 "추론만으로 확정하고 실제 Luau로 부딪혀본 적 없는 것"(M0 스파이크 대상)을 luau/luau-analyze/luau-lsp/Roblox Studio로 사용자가 직접 돌려볼 독립 실행 스크립트 모음. [2026-08-13 여섯 번째 세션, 첫 실측] luau/luau-analyze 바이너리가 생겨 처음으로 실제 실행 — 런타임 12개 전원 통과, 타입 쪽에서 :Compute(fn) lazy 핸들 계약이 Luau 추론과 충돌하는 게 드러남(당시 question.md 0-Y). [2026-08-13 열세 번째 세션] 그 0-Y가 해소되며 review-required/가 비었음 — 계약은 유지 확정, 남은 건 Luau 자체 한계라 base/typing-limits.md가 담당. STATUS.md가 상태의 소스(pass / 사람 결정 필요 / 스파이크 깨짐 / 미실행 분류 — 사람이 먼저 볼 것만 위에), luau-test/README.md는 각 파일의 검증 의도·배경, 실행 결과 상세는 audit/luau-test-first-run-2026-08-13.md |
audit/ |
[2026-08-13 신설] luau-test/ 등 스파이크를 실제로 돌려본 뒤 "무엇이 확인됐고 무엇이 아직 안 됐는지"를 기록하는 곳 — 스크립트/계획 자체가 아니라 실측 결과만 다룸. base/luau-test와 달리 부분 확인(일부만 통과)도 있는 그대로 기록, 완전히 해소되면 관련 base//luau-test/README.md 캐비엇을 지우고 이 문서는 근거로 남김. 개수는 폴더가 소스(여기서 세지 않음): luau-test-first-run-2026-08-13.md(첫 실측 라운드 전체 — 런타임 12개 통과, 구 question.md 0-Y의 1차 근거. 단 이 문서의 "콜백이 raw 값을 받으면 완전 클린" 판정은 아래 type-recursion-issue/가 뒤집었음), gcconn-trick-verification.md(사용자가 Studio에서 직접 돌린 gcconn 트릭 부분 확인 — 10의 A 섹션 앞부분만. [2026-08-14 다섯 번째 세션, 열한 번째 세션에 canBound 재도입 반영해 재갱신] 실측된 사실 자체는 그대로 유효하고 value 단독 1-인자 재정정으로 오히려 더 중요해졌음 — 이중 바인딩 게이트(canBound)/emit 게이팅(canExecute)/재바인딩 허용/value 쪽 복사 gcconn 판정/Instance userdata 동일성/B/C가 미확인), type-recursion-issue/([2026-08-13 열세 번째 세션 신설] 0-Y 재실측 전체 — REPORT.md + spikes/(개수는 폴더가 소스). 다른 audit 기록과 달리 스크립트를 같이 둠: 이 건의 근거가 "여러 formulation을 서로 대조한 것"이라 개별 파일을 직접 돌려야 판정이 재현되기 때문. 결론은 base/typing-limits.md로 승격됨), fallback-xpcall-verification.md([2026-08-14 신설] base/fallback-plan.md의 Traceback 메커니즘 전부 확인 — 클로저 업밸류 배선/중첩 스택 캡처/err: any/error(msg) 위치 접두 10개 검증 전부 통과. 스크립트 1개뿐이라 재현용으로 같이 둠: fallback-xpcall-spike.luau), type-recursive-issue-with-typeof/([2026-08-15 신설] 사용자가 발견한 typeof(named fn) 간접참조가 0-Y(재귀 제네릭 반환 leak)를 실제로 우회하는지 실측 — REPORT.md + spikes/. 결론: 인라인 대신 이름 붙은 함수 + typeof로 선언하면 LHS 명시 없이도 다운스트림이 안전해짐(체이닝 50단·타입 변경·중첩 self 호출까지 확인), typing-limits.md §1 ③으로 승격. 부수적으로 setmetatable 확장 시도에서 quad와 무관한 Luau 0.733 솔버 버그(모순 진단 두 개 동시 발생) 발견, 채택 안 함. luau-test/16(type function으로 Store<T> 레코드 필드 합성) 복구도 이 조사 중 완료 — API 버전 드리프트였을 뿐 설계 문제 아니었음, typing-limits.md §5 승격), type-recursive-issue-try-callback/([2026-08-15 신설] 콜백 파라미터 무주석 추론을 뚫을 방법이 정말 없는지 type function/메타테이블/오버로드/제네릭 디폴트 등 전방위로 재시도 — REPORT.md + spikes/(개수는 폴더가 소스 — 최초 라운드 + /code-review high가 이중 꺾쇠 명시적 제네릭 인스턴스화를 안 시도했음을 지적해 추가된 후속 조사 라운드로 구성). 결론: quad의 state:Compute(fn) 단일 호출 모양을 유지한 채로는 여전히 안 됨. 발견 셋 — (1) 근본 원인이 재귀 자기참조가 아니라 "제네릭 콜백 인자 전반에 컨텍스트 타입 전파가 안 됨"이라는 게 더 정확함(재귀 없는 최소 사례로도 재현), (2) T를 명시 중간 변수로 먼저 고정하거나 재사용 가능한 monomorphize 헬퍼를 거치면 실제로 추론이 살아나지만 둘 다 단일 콜론 호출을 2단계 체인으로 바꿔야만 해서 §0 대전제로 채택 안 함, (3) 이중 꺾쇠 명시 인스턴스화(Compute<<T,U>>(fn))는 leaf 호출에선 sound하게 성립하지만(spurious 진단 원인도 규명 — read-only/read-write 가변성 불일치) 매 호출 T/U 전부 명시 필요 + 중첩 self 호출 여전히 실패라 순손해로 채택 안 함) |
tools/ |
[2026-08-13 신설, session/2026-08-13-09-structure-and-guardrails.md] 코퍼스 기계 점검 — doc-check.py가 깨진 파일/절 참조, README 색인 누락, 날짜 없는 시한부 주장("아직 안 돌려봄" 등), 미반영 ⚠️ 배너를 한 번에 훑음. [2026-08-16] 절 참조는 WARN이 아니라 ERROR — 판정 규칙은 conventions.md의 "절 인용 규약"이 소스. 중대 변경 후 커밋 전에 돌릴 것(python3 .claude/tools/doc-check.py) — 수동 감사에서 나온 발견의 대부분이 이 종류였고, 실제로 문서를 쪼개다 잘못 옮긴 참조를 이게 잡아냄. ERROR는 고치고 WARN은 판단 대상 |
agents/ |
[2026-08-16 신설] 프로젝트 서브에이전트 정의(.claude/agents/*.md, Claude Code 표준 위치). 현재 quad-doc-auditor.md 하나 — doc-check.py가 못 잡는 의미론적 stale/모순(본문 문장이 뒤집힌 결정을 여전히 서술, 개수/목록 이중 소스 드리프트 등)을 신선한 맥락에서 찾는 읽기 전용 감사자. 중대 변경 커밋 전에 위임하는 게 기본 — [2026-08-18 재설계] 한 턴에 하나씩만 돌리고(병렬 금지) 발견이 0건인 라운드가 나올 때까지 턴을 늘리는 루프, 수정은 메인 세션이 일괄로 함(라운드 수·범위 좁히기 규칙은 여기 안 적음 — 소스는 conventions.md)이며 절차는 .claude/conventions.md "작업 방식" 절이 소스([2026-08-16] 이 루프를 담던 workflows/quad-handover-audit.js는 토큰 과다·픽스 에이전트발 부정확 서술·사용자 질의 불가 때문에 폐기됨). tools/의 기계 점검과 짝을 이루는 의미론적 점검 계층 |
agent-memory/ |
[2026-08-16 신설] 서브에이전트가 라운드를 넘겨 유지하는 영속 메모리(agent-memory/<에이전트 이름>/MEMORY.md가 색인). 지금은 quad-doc-auditor/ 하나 — 코퍼스 구조, 반복되는 실패 패턴 등을 기억해 감사 라운드마다 처음부터 파악하지 않게 함. 사람이 손으로 채우는 문서가 아니라 에이전트가 스스로 쓰는 것이지만, .gitignore 대상이 아니라 커밋하면 코퍼스 일부가 되고 doc-check.py 검사 대상에도 들어감(감사 대상이기도 하다는 뜻 — 여기 적힌 주장도 stale해질 수 있음). [2026-08-16 확정] 커밋해서 추적함(사용자 결정). 사용자 논거: "실 기록이고 디펜던시도 아니고, 어차피 SAFETY.md에 따라 구현 시점에는 컨테이너에서 개발되며 다른 프라이빗 git에 올라가고 검토 후 머징되는거라, 문제되는 메모리 있으면(환경 노출 등) 사람이 감사처리 마지막으로 함. 결국 프로젝트 사이드 기록이고 같이 올려지는게 맞는게, 개발 환경이 다수라서 필요해보임" — 즉 개발 환경이 여러 개라 메모리가 따라다녀야 하고, 노출 위험은 머지 전 사람 검토가 최종 방어선. 커밋하는 쪽이 정해졌으니 여기 내용도 감사 대상이다(에이전트가 자기 메모리에 stale한 결론을 남기는 일이 실제로 있었음 — 2026-08-16에 폐기된 워크플로를 살아있는 것처럼 서술한 2건이 감사로 잡힘) |
session/ |
[2026-08-11 신설] 세션별 상세 로그 원문(시행착오·정정 전 서술 포함, quadnomicon 개발로그 소재용) — 루트 CLAUDE.md가 3196줄까지 불어나 성능 저하를 유발해서 분리함. 파일명 YYYY-MM-DD-NN-slug.md, .claude/session-summary.md의 각 항목이 여기로 링크. 항상 읽을 필요 없음 — 결정의 논의 과정이 궁금할 때만 |
initreq/ |
프로젝트 착수 시 클론해둔 참고 레포(quad v1, fusion, vide, rbvm, tbox, code-docker) + PA님 실 코드(artworks/, 4차 라운드 교차검증 근거) + 원본 요청(req.md, raw-userinput.md) + quad2-try(이전에 시도했다 폐기한 v2 재작성 시도 — 리서치 완료, 결론은 base/bind-system-plan.md) — 읽기 전용 리서치 소스, 여기 내용을 옮기지 말고 항상 원본 그대로 유지 |
research/의 문서가 설계 확정되면 base/로 승격(또는 구현 착수 시
qa-request/행). 지금은 구현 라운드 전(설계 단계)이라 전부 base//research/에만
있음.
base/ — 결정된 것, 프로젝트 전체 컨텍스트
| 문서 | 내용 |
|---|---|
architecture.md |
quad-v2 전체 아키텍처 확정 사항 요약(제일 먼저 볼 문서). [2026-08-12 세션 신설, 같은 날 후속 세션에서 강화] "코드 스타일 — Luau 문법 관례" 절 신설 — if-then-else가 공식 Luau 문법임을 명문화(환각/오타로 오인해 and/or로 되돌리는 회귀 방지), A and B or C 삼항 관용구는 항상-truthy 예외도 없이 전면 금지로 강화(bind-system-plan.md의 retractUnder falsy-값 버그가 실사례). const 바인딩은 공식 문법이나 툴링 미성숙으로 지금은 채택 보류. [2026-08-19 정정] 패키징 매니저를 wally에서 pesde로 전환 — 세부는 project-setup-plan.md |
project-setup-plan.md |
[2026-08-19 신설, 같은 날 두 차례 후속 갱신] M0/M1 스캐폴딩을 실제로 pesde/luau/Rojo/selene CLI로 굴려보고 검증한 결과 — pesde 워크스페이스 구조(workspace_members, 패키지 이름은 하이픈 금지), mise.toml 툴체인 핀(rokit.toml에서 전환, 실제 설치·attestation 검증까지 확인), init.luau에서 @self가 필수인 이유(Luau RFC abstract-module-paths-and-init-dot-luau), 워크스페이스 의존성이 심볼릭 링크로 연결되고 luau CLI의 require-by-string은 이를 못 따라가지만 Rojo/Studio 배포 경로는 무관함을 실측 확인, selene의 CWD 상대 config 탐색 함정, .luaurc alias 런타임 미지원 재확인, pesde.lock 커밋 권고(잠정). "확인 완료/아직 확인 안 된 것" 절이 다음에 뭘 검증해야 하는지의 소스 |
typing-limits.md |
[2026-08-13 열세 번째 세션 신설] Luau 타입 시스템이 quad 설계에 대해 못 해주는 것을 한 군데 모은 확정 문서 — 여러 base/ 문서에 캐비엇으로 흩어져 있던 걸 통합. 대전제는 "Luau의 한계를 우회하려고 타입/API를 비틀지 않는다"(비틀면 나중에 Luau가 고쳐줘도 자동 수혜를 못 받고 되돌리는 마이그레이션이 생김). 1번 항목이 가장 큼 — 재귀 제네릭이 다른 타입 인자로 자기를 반환하면(Compute<U>(self: State<T>,...) -> State<U>) 타입 안전성이 에러 없이 조용히 사라짐(구 question.md 0-Y, 스파이크 다수로 확정 — 근거·개수는 audit/type-recursion-issue/). 대응은 두 개: (a) 타입 선언을 "데이터부/메소드부"로 쪼개 콜백 파라미터 추론을 살리고, (b) 파생 State를 만드는 자리마다 결과 타입을 명시 주석으로 바인딩(그 한 줄만 검증 안 되고 다운스트림 전체는 정상 체크됨). Luau RFC relax-recursive-type-restriction이 Promise<T>.andThen으로 예시 든 바로 그 패턴이라 지금 선언 그대로 두면 Luau 쪽 수정만으로 코드 변경 없이 풀림(추적: luau-lang/luau#2380). [2026-08-15 추가] ③ 인라인 대신 이름 붙은 함수 + typeof로 선언하면 콜백 파라미터 주석은 여전히 필요하지만 LHS 명시 없이도 다운스트림이 안전해짐(①을 대체하지 않음, 보강). 그 외 Modifier Overridden 서브타입/Attribute 제네릭 키 narrowing/nilable default 오버로드도 여기 통합, store.key type function 한계는 검증 완료로 승격(§5), §8에 새 타입·API 설계 시 체크리스트. [2026-08-19 신설] §6 — type function을 거친 값(패스스루라도)은 이후 제네릭 self 메소드 체이닝(AddPlugin<Self,P>류)이 조용히 깨짐, quad-types-plan.md의 CheckedQuad<T, Pattern> 배선 중 실측 발견·회피(원본은 type function을 절대 안 거치게 하고 검사 결과는 별도 필드로 격리). 실측 근거는 audit/type-recursion-issue/ + audit/type-recursive-issue-with-typeof/ + luau-test/23 [2026-08-24 6라운드 H-24 — 실측] 영향 범위 표에 tween:Mapped 추가 — tween:Mapped(fn: (T) -> U): Tween<U>가 1번이 지목한 모양(Foo<T> 안에서 -> Foo<U>)과 글자 그대로 같은데 이 문서도 tween-plan.md도 그걸 모르고 있었다. 인라인 제네릭 메소드로 선언하면 luau-analyze가 진단 없이 조용히 통과시키고, ③(typeof(named function))로 바꾸면 정상적으로 잡힌다 — 기존 완화책이 그대로 통하는데 아무도 적용을 지시하지 않고 있었다 |
lifecycle-pattern.md |
rbvm의 Connected+GC 관용구를 quad-v2가 채택하는 방식. [2026-08-14 다섯 번째 세션, 시그니처 정정] bindLifetime(inst,value)/unbindLifetime(value)/canExecute(value) — 뒤의 둘은 inst를 안 받음(bindLifetime이 바인딩 시점에 gcconn 참조를 value 쪽 Relate로 복사해두므로 value 하나로 생존을 물을 수 있고, 실제 호출부인 State 전파 루프엔 애초에 inst가 없음). .Subscribed는 전역 :Subscribe() 전용 필드로 분리(bindLifetime은 읽지도 쓰지도 않음), gcconn/gchold는 lazy가 아니라 Instance 생성 시점에 만들고 클로저가 gchold와 inst를 둘 다 캡처(userdata 포인터 동일성 = inst-키 Relate 전체의 전제). 옛 2-인자 모델은 archive/canexecute-inst-arg-reversed.md. [2026-08-14 열한 번째 세션] 별도 canBound가 다시 도입됨 — bindLifetime/Observer:Subscribe()의 이중 바인딩 가드는 canBound, State emit 전파 게이팅만 canExecute(판정 로직은 비공개 헬퍼 isBoundAlive 하나를 공유). [2026-08-18 구현 전 QA 반영] 두 predicate는 값이 같은 게 아니라 서로의 부정(canBound 참 = "지금 묶어도 됨")이라 게이트가 전부 if not canBound(v) then error(...)로 정정됨 — 옛 서술대로 짰으면 정상 첫 바인드가 전부 에러났음. gcconn/gchold 저장도 SetStrong→SetWeak 정정 [2026-08-24 6라운드 H-11] bindLifetime/unbindLifetime이 isEffect를 보고 Destroying을 걸고/끊는다 — LP-2가 *"Effect가 그 훅을 쓰는 유일한 소비자"*라 확정해뒀는데 실제로 거는 코드가 어디에도 없었다. unbind는 cleanup을 부르지 않는다(대칭이라 포탈이 자연히 성립) |
store-plan.md |
[2026-08-14 신설 — bind-system-plan.md 3단계 분할 + 구 store-semantics.md 흡수] Store = 이름 붙은 Source 모음, 그 이상 아님 — Store 부작용 허용이 기본 디자인(국소적 vs 경계를 넘는 부작용), defaults는 선택적 초기값 템플릿(원본을 나중에 mutate해도 UB 아님)이고 eager 생성과 lazy 생성이 둘 다 필요(Luau 타입은 런타임에 강제 안 되므로), table.clone 기반 eager 생성 스케치, store.key(dot-access)가 1급 경로([2026-08-18] store "key" 문자열 커링은 기각 — 동적 키는 store:GetDynamic<<T>>(name)), 레코드 필드 타이핑은 Luau type function으로 해결 확인, store.key = value 폐기 → store.key:Set(value)(타입 대칭성+lazy 정직성), "Store가 Store를 저장 가능한가"는 그런 경우를 안 만듦으로 확정(State<State<T>>와는 다른 축) |
source-state-plan.md |
[2026-08-14 신설 — bind-system-plan.md 3단계 분할 + 구 store-semantics.md 흡수] 반응형 코어: Source⊇State 구조적 서브타입(RefSource 폐기, 단방향 의존으로 Luau 솔버 회피 — 스파이크 08 통과), push-invalidate/pull-recompute 전파 모델과 "관측해야 실체화된다" 전역 원칙, State 체인 플래튼 기각(캐싱이 State의 존재 이유), :With도 매번 새 노드(clone 계열인 Tag/Modifier와 혼동 주의), :Compute의 lazy 핸들 계약(:Get() 누락이 반복되는 실수)·trailing args sugar·fn(self, previous?, ...deps) 순서·previous, :Apply, :Emit()(Source 원천 전용 하드 경계)과 Store<T>/Source<T>의 T가 Modifier일 수 없는 따름정리, state:Observer(fn), :Subscribe()/:Unsubscribe(), 이중 바인딩 금지 게이트(canBound, State emit 전파 게이팅은 canExecute — base/lifecycle-pattern.md의 "canBound vs canExecute" 절이 소스), PA님 코드 교차검증. [2026-08-14 열두 번째 세션] 새 절 "Observer/Effect Leaf dedup" — RefLeafHandler와 같은 old ~= v dedup(성능 최적화, correctness엔 불필요). [2026-08-18 구현 전 QA 반영] canBound 방향 정정, :Compute 콜백 표기 정정(fn(self, previous?, ...deps)), FALLBACK 가드 에러에 k 타입 싣기, 그리고 ⚠️ 미해결로 신설된 "중간 State가 살아남는가"(상류 strong/하류 weak 불변식 — M2 착수 전 결론 필요) [2026-08-24 6라운드] 전파 루프가 구독자 집합을 스냅샷으로 복사한 뒤 돈다 — 순회 중 새 구독자 추가가 정상 경로인데 Lua에서 미정의라, 실측에서 실행마다 결과가 달라지고 한 Observer가 통째로 누락됐다(H-23, Epoch dedup으로는 이중 발화만 접히고 누락은 안 접힌다). Effect(fn, ...deps) 역전이 이 문서에 미반영이던 것도 정정 — Observer만 기각 유지이고 그 근거를 새로 씀("Observer는 리시버 State 하나에 붙는 구독, 여럿을 엮는 건 Effect가 대신한다", H-13) 그리고 ObserverEffectLeafHandler도 자기 배열 자리의 setOffsetSource/setLength를 안 등록하던 것을 정정(H-39 — 말단 핸들러 넷이 같은 결함이었고 이게 그중 하나다, Frame { someObserver, Frame{} }가 첫 recompute에서 죽었다) |
dispatch-core-plan.md |
[2026-08-13 열네 번째 세션 신설 — bind-system-plan.md 2단계 분할 + 0-A/0-Z 반영] 디스패치 코어: 핸들러 계약(isHandlable/priority/process가 retract 클로저를 반환) / 하강 diff 재디스패치(래핑 핸들러의 retractFrom 선행 호출 폐기, Dispatch.process가 슬롯의 handler를 먼저 비교해 — 같으면 그 자리 클로저에 새 값을 넘기고 재process, 다르면 그 자리부터 전량 철거) / chains 인덱스 체인과 3-인자 Dispatch.retractFrom(inst,k,index)(힌트 인자 소멸 — 값 전달 경로가 (A) 분기 하나로 통일) / None 센티널 / Handler 작성 체크리스트 8개 / Length·Offset 형제 순서 보장 / "store 바인드는 래핑" 결론. 새 결정 둘: HANDLER_PRIORITY_FALLBACK(base 제공 핸들러의 기본 밴드 — 백엔드가 평범한 우선순위로 덮어쓰면 언제나 이김), "base가 소유하는 핸들러와 주입되는 엔진 op"(부기가 엔진 지식을 요구하지 않으면 알고리즘은 base, 마지막 한 줄만 주입 — addTag/removeTag/setAttribute, [2026-08-14 열 번째 세션] 같은 패턴을 Dispatch 밖의 dispose(value)/nativeDispose에도 재사용 — [2026-08-22] 그 op 이름은 native* 계층으로 확정, 옛 가칭 disposeInst). 옛 힌트 모델은 archive/dispatch-hintvalue-model-reversed.md. [2026-08-14 열두 번째 세션] Observer/Effect Leaf도 Ref와 같은 identical-value dedup 채택(성능 최적화). [2026-08-18 구현 전 QA 반영] Dispatch.drive의 None 스킵 분기 폐기(반응형 값이 내놓는 None은 어차피 process에 도착) → NoneHandler는 재귀 전담, NilHandler 신설(k=number and v==nil 말단, setLength(0)/setOffsetSource(None) 등록 담당). Length/Offset 등록 책임도 "처음 매치한 Handler"→말단 Handler로 정정. base 소유 Fallback Handler 등록 주체는 백엔드 팩토리→quad-base 자신으로 재역전. "방어 가드는 죽은 코드" 서술에 한정 추가(한 핸들러가 여러 값 모양을 받으면 판별은 그 핸들러 몫), PreRef가 "배열 먼저" 보장 위에 성립한다는 근거 정정(별도 pre-pass라 독립), Quad.debug 게이팅. [2026-08-18 구현 전 QA 2라운드 후속] "Length/Offset" 절에 크래시하던 recompute 트리거 모델(RC-1)을 owner별 Blocker 게이팅으로 고친 "배치 등록을 안전하게 만드는 Blocker 게이팅" 절 신설 — setLength/setOffsetSource 재작성, Dispatch.drive도 자기 Blocker로 배열 파트 순회를 감쌈. [2026-08-18 구현 전 QA 3라운드] "저장 위치" 절에 bk.N(recompute 순회 상한) 수명주기 신설(그때그때 실제 개수, inst/Slot 두 owner 타입 동일 규칙 — setLength가 갱신, setOffsetSource는 안 건드림) — 부수로 RC-1의 원래 크래시 서술도 정정("N이 배치 전에 고정"이라는 옛 전제의 부산물이었을 뿐, 지금 Blocker 게이팅이 필요한 이유는 크래시 방지가 아니라 비용) [2026-08-24 6라운드] (1) 말단 핸들러 4종(Tag/AttributeGroup/RefLeaf/ObserverEffectLeaf)이 setOffsetSource/setLength를 아예 등록 안 하던 것이 발견돼 계약대로 등록하게 됨 — 안 그러면 Frame { Tag("x"), Child{} } 같은 흔한 배치가 첫 recompute에서 명시적 error로 죽는다(H-39). (2) 접두합 캐시 무효화 3규칙이 산문으로만 있고 코드 경로가 없던 것을 setLength/spliceArrays*/_baseObserver에 실제 배치(H-3), bk 스펙에 offsetCache/invalidAfter 추가(H-4). (3) recompute 호출은 setLength의 단독 책임(H-19). (4) Blocker 범위를 drive 전체로 정정하고 PostRef 콜백이 게이트 안에서 돈다는 걸 계약화(H-17). (5) *"잔여 부기는 인스턴스 GC로 정리된다"*는 틀린 안전망 주장 삭제(gcconn 불멸성과 양립 불가, H-26) |
bind-system-plan.md |
[2026-08-14, 3단계 분할로 203줄까지 축소 — 지금은 "인스턴스 생성/이벤트 네이밍 인체공학 + 분할 색인" 문서] 반응형 코어는 source-state-plan.md, Store는 store-plan.md, 디스패치 코어는 dispatch-core-plan.md로 나갔음. 아래 이력은 분할 전 이 파일이 담고 있던 결정들의 기록(현행 소스는 각 분할 문서). pluggable key/value 핸들러 레지스트리 — process/retract 디스패치 모델, Ref, Store/State/Source 온톨로지 + 인체공학 질문 전부 확정. 디스패치 엔진은 quad-base가 인터페이스로 소유(2026-08-04 5차 라운드). [2026-08-11 세션, 여섯 번째] Dispatch.setLength/setOffsetSource의 owner 키가 물리 Instance로 한정될 필요 없음을 명시(Slot-in-Slot 재귀의 근거) — 같은 절 recompute의 off-by-one 버그 발견·수정(offset이 자기 자신을 포함해 누적되던 것), 재진입 방지 가드는 검토 후 기각(Source⊇State 단방향 원칙과 같은 카테고리의 UB로 명명, 각 Slot이 독립 bk를 가져 nesting만으로는 재진입 경로 자체가 없음을 확인). [2026-08-12 열한 번째 세션, 전면 정정] "핸들러 타입이 안 바뀌면 retract 없이 process가 diff"는 틀렸음 — retract는 store 재발행마다(핸들러 타입 무관) 항상 불림, v는 대체 값 자체일 수 있어 nil로 가정 금지. Tag/Ref/Slot/Attribute 전부 이 오류로 설계돼 있었음이 드러나 한 세션에 전부 정정(archive/retract-always-fires-reversed.md). [2026-08-12 세션 후속] retractUnder의 A and B or C 삼항 관용구 버그(v가 false일 때 nil로 새던 것)를 if-then-else로 수정한 게 계기가 되어 and/or 삼항 전면 금지 규칙으로 발전(architecture.md "코드 스타일" 절). [2026-08-12 열일곱 번째 세션] 우선순위 동률/매치 실패 처리(HANDLER_PRIORITY_* 상수+디버그 동률 감지, 매치실패는 즉시 error) 확정, store.key 레코드 필드 타이핑이 Luau type function으로 가능함을 스케치로 확인(pre-implementation-audit.md 1-3/1-4/1-10 해소). [2026-08-12 스무 번째 세션] Ref 사용 관례 명문화 — React useRef급 스코프 감각(만든 컴포넌트 자신이 쓰거나 자식에게 넘기는 용도, 경계 밖 반출·전역 장기 보관은 비권장). [2026-08-12 스물한 번째 세션] :With가 Tag/Modifier의 : clone 체이닝과 겉보기엔 같은 문법이지만 실제로는 정반대(clone 아니라 매번 새 State 노드)라는 혼동 경고 추가, Compute가 -ed(Computed)가 아닌 이유 절 신설(quad 자기 관례상 Tag.Added/Modifier.Overridden이 이미 "-ed = clone 후 즉시 확정된 값"을 선점해 lazy한 State에 재사용하면 충돌). [2026-08-13 세션, 두 번째] State<State<T>>(store가 emit하는 값 자체가 또 State/Source)가 같은 (inst,k)에 같은 핸들러를 중복 push시켜 retractUnder의 첫-매치 cutoff가 안쪽 자신을 잘못 retract하는 실제 체인 파손 버그로 확인됨(손 트레이싱, luau-test/04가 no-op retract 스텁 때문에 이 증상을 못 잡던 사각지대였음도 같이 발견) — Dispatch.process에 중복 핸들러 즉시 error 가드 추가, "동일한 재귀적 디스패치로 처리 가능"이라던 낙관적 서술과 "Store가 Store를 저장 가능한가" 절도 정정. [2026-08-13 세션, 네 번째] 사각지대 손 트레이싱 라운드에서 isHandlable 필드를 선택적으로 허용(생략하면 스캔에 안 걸림)하고, 그런 "체크포인트" 핸들러를 명시적으로 체인에 꽂는 Dispatch.processAs/Dispatch.retractSelfAndUnder(target 자신 포함 철거) 신설 — attribute-plan.md의 그룹/직접쓰기 이름 소유권 충돌을 별도 레지스트리 없이 기존 재진입 가드로 흡수하는 데 씀. [2026-08-13 세션, 다섯 번째, 전면 재설계 — 위 processAs/retractSelfAndUnder 대체] chains를 핸들러 객체 identity가 아니라 재귀 깊이 인덱스로 추적하도록 재설계 — Dispatch.process(inst,k,v,index)가 핸들러 호출 전에 그 인덱스 점유 여부를 체크(핸들러 부작용 낭비 없음), process는 이제 retract 필드 대신 자기 retract 클로저((hintValue)->())를 반환. 같은 키 재귀는 index+1, 다른 키 위임은 항상 1부터 — 이걸로 State<State<T>>가 UB에서 정상 지원 대상으로 재정정됨(각 재귀 단계가 다른 슬롯을 쓰니 identity 충돌 자체가 없어짐), retractUnder/retractSelfAndUnder도 Dispatch.retractFrom(inst,k,index,v) 하나로 통합(자기 포함/미만은 호출자가 넘기는 인덱스로 표현)되며 체크포인트 패턴 자체가 불필요해짐(archive/checkpoint-handler-pattern-reversed.md). 계기: AttributeGroupHandler 소유권 버그를 체크포인트로 고치다, 그 근본 원인(identity 기반 추적)을 되짚은 사용자 지적. [2026-08-13 감사] 위 재설계 의사코드에서 실제 버그 셋 발견·수정 — (1) chains:SetStrong이 handler.process 뒤에 있어 최초 마운트에서 하위 위임 retractor가 통째로 유실되던 것(재귀가 자기 테이블을 만들었다 바깥이 덮어씀), (2) Ref retractor가 spurious 재발행에서도 relate를 지워 dedup이 무력화되던 것, (3) Dispatch.drive의 진입 인덱스(1) 미명시. 덧붙여 retractor 안에서는 같은 키에 대한 retractFrom도 process와 똑같이 금지(진행 중인 루프가 #list를 이미 캡처)임을 명문화 **[2026-08-13 열네 번째 세션] 2단계 분할 + 모델 교체 — 디스패치 코어 전체가 dispatch-core-plan.md로 나갔고(이 문서엔 반응형 코어와 인체공학만 남음), 나가면서 하강 diff로 재작성됨. 따라서 위 5차 세션 서술 중 "Dispatch.process가 인덱스 점유 여부를 먼저 체크"와 "retractFrom(inst,k,index,v) 4-인자"는 더 이상 현행이 아님(점유 체크 폐지 → 핸들러 비교, 힌트 인자 소멸 → 3-인자) — 현행은 dispatch-core-plan.md. [2026-08-18 구현 전 QA 반영] 남아 있던 인체공학 절이 크게 갱신됨 — 네임스페이스 DI→D(Declarative) 확정(코퍼스 전수 반영, "특수 DI 키"라는 설명 표현은 "특수 키"로 단순화), New는 커링(New "Frame" {...})이고 D는 전량 코드 생성된 순수 별칭 테이블(생성 범위는 "GUI에 쓰이는 모든 인스턴스", 밖은 any), 그리고 "이벤트 콜백 시그니처는 Luau가 검증 못 한다"는 옛 전제가 거짓임이 사용자 반례로 확인돼 "생성기가 이벤트 필드의 콜백 타입까지 만든다"로 바뀜 |
module-lifecycle-plan.md |
프로바이더 패턴, bind/store 구현 책임 분리 — 확정. [2026-08-18 구현 전 QA 반영] 모듈 표면에 Quad.debug(기본 false) 신설(지금은 핸들러 우선순위 동률 경고를 게이팅), base 소유 Fallback Handler 등록 주체가 quad-base 자신이라는 명시적 예외 반영. [2026-08-19 신설, 같은 날 후속 정정] "New()의 내부 구성" 절 — InitXxx(module) 팩토리 체이닝 + module:RunInit(initFn)(함수 자체를 릴레이션 키로 쓰는 공유 멱등 가드, 파일마다 따로 두던 센티널 폐기). 실제로 quad-base/src/init.luau에 구현·검증됨. RunInit은 backend 설치엔 재사용 안 함 — _initializedBy 문자열 마커(같은 팩토리=no-op, 다른 팩토리=에러)를 별도로 유지하는 걸로 확정(2026-08-19 해소) |
quad-types-plan.md |
[2026-08-19 신설, 같은 날 후속으로 확장] 워크스페이스 세 번째 멤버 quad-types — 구현 없는 Quad 타입 계약 + AddPlugin<Self,P>(실측 검증된 제네릭 self 플러그인 체이닝) + CheckedQuad<T, Pattern>(런타임 주입 때문에 pesde semver 보호가 안 걸리는 자리를 메꾸는 컴파일 타임 버전 체크, 글롭/캐럿 패턴 지원). 버전 패턴 매칭 자체는 quad에 종속되지 않은 범용 패키지 type-version-check(워크스페이스 네 번째 멤버, 사용자가 나중에 독립 저장소로 분리 예정 — HUMAN_TODO.md 9번)로 분리됐고 CheckedQuad가 그 위에 얹힘. type function이 T를 패스스루만 해도 이후 제네릭 self 메소드 체이닝이 조용히 깨진다는 새 Luau 함정을 발견·회피(별도 가상 필드로 격리), export type function/이중 꺾쇠 제네릭 인스턴스화 등 cross-package 사용 함정도 정리. quad-roblox가 quad-base 대신 이 가벼운 패키지만 의존 — dev-dependency로 두면 게시 후 소비자 환경에서 타입-전용 require가 런타임 크래시하는 문제를 원천 회피 [2026-08-24 6라운드 H-25 — 실측] Quad가 5필드 닫힌 레코드라 RunInit으로 붙인 서브시스템이 타입에 안 보인다(quad.Dispatch가 luau-analyze에서 타입에러 — architecture.md 결정 13번이 그 접근을 표준 사용법으로 확정해둔 것과 정면 충돌). 확정: quad-types의 Quad를 마일스톤마다 갱신한다 — 서브시스템을 붙이는 모든 마일스톤(M2/M3/M6/M7/M8/M10)에 체크박스가 뿌려졌다(규칙이 쓰인 계기는 Dispatch(M3)이지만 순서상 첫 적용은 M2다). 타입만 재수출하므로 "가벼운 타입 계약"이라는 존재 이유와 안 부딪힌다 |
slot-plan.md |
뮤터블 자식 배열, 엄격한 단일 마운트 소유권, 재마운트 시 throw, base/roblox 패키지 경계까지 확정. [2026-08-09 세 번째 세션] Add/Remove/Extract/Clear/Move/Swap CRUD(복잡도 표기 포함), isMounted 이중 추적 분리, 요소 타입 제약(nil/None/핸들러 계층 값 금지, Slot<T>() 제네릭), 키 기반 동적 컬렉션 재조정(Slot:List(data, updateFn, keyFn?))까지 전부 확정 통합, base/roblox 경계에 reposition 훅 추가. [2026-08-09 열한 번째 세션, 중간검토] CRUD 식별 기준을 element 레퍼런스에서 인덱스 기준으로 재정정(Remove(index)/Extract(index, newElement?)/Move(oldIndex, newIndex)), ExtractAll/Get/IndexOf 신설. [2026-08-11 세션] `updateFn(item, index: number, offset: Source, prev: T?, userdata: UD?): (T |
modifier-plan.md |
Modifier는 런타임 plug 아닌 정적 merge, immutable+clone 기반 체이닝 — 메커니즘 확정. [2026-08-07 다섯 번째 세션 추가] :Apply(factory) 팩토리 체이닝, Overridden(구 Merge→Override, 2026-08-08 세션에서 이름까지 확정) 값 결합+성능 기준, :Peek/isState 필드 읽기까지 전부 확정(Peek/isState는 이름만 용어 정리 라운드까지 잠정). [2026-08-12 열일곱 번째 세션] table.clone이 메타테이블을 참조로 공유한다는 핵심 전제(M7 "클래스별 코드 없이 제네릭 __index 하나로 충분" 설계의 근거)가 실제 Luau 동작으로 확인됨(pre-implementation-audit.md 1-11 해소). Property에 Attribute식 이름 소유권 레지스트리를 적용하는 안은 검토 후 기각(엔진이 정한 유한 프로퍼티 이름 집합은 전용 키를 못 만들어 소유권 판정 자체가 성립 안 함 — Property가 override 우선순위를 쓰는 이유). [2026-08-18 구현 전 QA 반영] 고정 메소드(=Modifier 필드 이름 예약)는 Apply 하나가 아니라 Apply/Peek/Overridden 셋(M7 타입 생성 스크립트 제외 목록에 반영 필요), Overridden은 닷/콜론 둘 다 가능 [2026-08-24 6라운드 H-35] flatten이 만드는 ProcessedModifier 센티널을 받는 ProcessedModifierHandler의 의사코드가 없고 색인 두 곳에서도 빠져 있던 것을 보강 — Modifier가 하나라도 든 리터럴은 전부 이 핸들러를 거치므로, 이 문서를 안 읽고 색인만 보고 구현하면 존재 자체를 놓친다. 소속 파일은 quad-base/Dispatch/Modifier.luau(architecture.md 소스 트리와 ROADMAP.md M7에도 등재) |
purity-and-effects-plan.md |
컴포넌트 "순수성"이 아니라 "이식성" 문제로 재정의 — 문서 경고 수준으로 확정 |
component-composition-plan.md |
컴포넌트=플레인 함수, State/Source 읽기·쓰기 경계, Source가 State를 구조적으로 만족 — modifier/Ref 컴포넌트 경계 통과까지 전부 확정, 남은 건 API 이름뿐. [2026-08-07 정리] 폐기된 StoreSource 프록시 설계로의 역전 이력은 본문에서 빼고 archive/store-source-proxy-reversed.md 포인터로 압축 |
blocker-plan.md |
[2026-08-07 신설] Blocker — 여러 Source를 한꺼번에 바꿔도 파생값 재계산이 한 번만 되게. [2026-08-24 재확정] 구현은 State 코어와 같은 M2이고(2026-08-22에 디스패치 쪽으로 앞당겼다가 마일스톤 순서 교체로 되돌아옴), 바닥부터 짜는 게 아니라 공용 GateNode(gate-plan.md) 위의 정책이다. 메커니즘+이름 확정. [2026-08-18 구현 전 QA 2라운드 후속] IsOn()/OffWithoutEmit() 신설(RC-1 해결 과정에서 나옴) — state:Block() 없이 Blocker를 직접 쓰는 두 번째 용례(base 내부 Length/Offset 배치 게이팅)도 추가. [2026-08-18 구현 전 QA 3라운드] 이 용례의 존재 이유 정정 — RC-1의 원래 크래시는 사라졌고(bk.N 수명주기 재정의로), 지금 필요한 이유는 배치 등록 비용(O(N²)→O(N)) [2026-08-24 6라운드 H-33/H-49] blocker:Policy(emit) -> onUpstreamEmit 신설 — 자기 게이트 정책을 값으로 내주고 state:Block(b)가 그 위의 얇은 래퍼가 된다. Debounce/Throttle이 이걸로 자기 Blocker를 조종한다 |
debounce-throttle-plan.md |
[2026-08-14 신설, 2026-08-19 전부 해소돼 research/에서 승격] 시간 기반 전파 게이트 Debounce/Throttle — 사용자 요청("Blocker와 유사하게")으로 신설. 요지: (1) Blocker가 이미 쓰는 게이트 노드의 릴리스 트리거만 타이머로 바꾼 것이라 새 전파 메커니즘이 아님, (2) 무효화 채널만 만지므로 laziness 안 깨짐, (3) Debounce/Throttle의 차이는 "신호가 창 타이머를 리셋하는가" 한 비트뿐 — 공개 생성자는 둘, 구현은 하나, (4) 알고리즘은 quad-base + 주입 op 2개 setTimeout(func, delay) -> Timeout/clearTimeout(Roblox task.delay/task.cancel로 배선 — 인자 순서 반대라 주의), Timeout은 { __type_timeout: true, _native: any }. [2026-08-19 마지막 라운드] 의미론은 (A) emit-gate(Blocker와 동일, :Get()은 항상 최신값)로 확정 — 검토했던 값-지연 안은 laziness와 상충해 철회. 제어 핸들은 개별은 Ref 아웃파라미터·전체는 팩토리 자체의 :Flush()/:Cancel()(weak 레지스트리)로 확정, Time/MaxTime은 number | State<number>(스케줄 시점에만 폴링) 허용. ⚠️ [2026-08-24 6라운드 H-32/H-33] 7절 의사코드에 무효화 배너가 붙었다 — 그 골격은 확정된 state:Gate(setup) API로는 성립하지 않아 재작성 대상이다. 새 모델은 Debounce/Throttle이 emit을 아예 안 쥐고 자기 Blocker를 사적으로 하나 갖고(적용 핸들당 하나) On()/Off() 시점만 정하는 것 — pending도 Blocker의 HasBlockedEmit으로 흡수되어 Trailing=false+MaxTime에서 Flush가 영구 no-op이던 결함(H-32)이 구조적으로 사라진다. 창/타이머 정책 자체(openWindow/onWindowEnd/MaxTime 분기)는 그대로 유효. 이름은 Debounce/Throttle 유지 + Roblox 관용 "debounce"와 다르다는 문서 경고. 결과적으로 quad-base에 새 코어 메커니즘을 안 더하는 순수 슈가로 귀결(Blocker의 gated state + Ref + 주입 op 2개 위에 전부 얹힘) — 우선순위는 Operator.*와 같은 급으로 재평가됨. 부수 성과: 이 설계 중 source-state-plan.md의 무효화 dedup 서술이 Observer 계약과 모순되는 게 발견돼 base 전면 정정(archive/invalidate-dedup-propagation-reversed.md) |
effect-plan.md |
[2026-08-07 신설, 여섯 번째 세션에 확정] Effect(fn, ...deps) — deps 없으면 설치 1회+leaf 사망 시 확정 정리, 있으면 각 dep에 구독을 따로 걸어(State/Source면 Observer, Ref면 :Callback) 재실행+cleanup 체이닝(React useEffect 동형). [2026-08-24 표기 정정] 여기 원래 *"내부적으로 state:Observer(...)를 조합"*이라 적혀 있었는데 그건 단수 시절 모델이고, 같은 행 뒤쪽의 C-6 서술과 어긋났다. Observer와의 관계 해소 완료. [2026-08-18 구현 전 QA 반영] :Unsubscribe()는 :Subscribe()의 짝으로 축소 — leaf 바인딩 경로에서 cleanup을 앞당기면 dedup 때문에 재바인딩이 안 일어나 Effect가 조용히 죽음. [2026-08-20 E-10 → 2026-08-21 EF-3에서 반영] 그 dedup 경로의 process/retract 대칭은 성립함이 확인됨(핸들러가 old를 Relate로 직접 들고 양쪽이 같은 비교식을 씀 — 남은 건 구현 시 회귀 확인뿐, 설계상 열린 항목 아님). [2026-08-21 5라운드 C-6] 시그니처가 Effect(fn, ...deps)로 확장돼 의존성을 여러 개 직접 받고(Ref도 가능) 각각에 구독을 건다. [2026-08-21] 다중 의존성이 공통 상류를 공유할 때 한 파동에 fn이 여러 번 돌던 미해결 갭은 EffectHandle이 자기 EpochMap을 들어 닫힘(base/state-epoch-plan.md). [2026-08-24 6라운드] fn 시그니처가 fn(self: EffectHandle) -> (() -> ())?로 확정(...deps는 의존성 선언일 뿐 fn에 안 넘어간다, H-14), _observer(단수)→_observers(배열)(H-8), 그리고 leaf 사망 cleanup을 실제로 발화시키는 배선이 없던 것이 bindLifetime/unbindLifetime이 isEffect를 보고 Destroying을 걸고/끊는 것으로 닫힘(H-11 — 이게 없어서 slot._detachCleanup과 OnDestroyed가 통째로 무동작이었다). Ref dep 콜백은 해제 경로(:Uncallback)와 발화 시 canExecute 확인을 둘 다 갖는다(H-7) |
ui-shorthand-plan.md |
[2026-08-07 research/에서 승격] UICorner/UIPadding/UIScale 인라인 편의 키 — 이름(v1 Corner/PaddingAll/Scale에서 Modifier 필드명과 안 겹치게 UI 프리픽스로 확정)·메커니즘(Handler)·패키지 배치(quad-roblox 코어)·store-bind 가능성까지 전부 확정. 이미지 라운드 트릭(RoundSize)은 드롭 — archive/ui-shorthand-roundsize-dropped.md 참고. v=nil이면 process 자신이 만든 자식 제거(retract 아님). [2026-08-14 세션] Tween 지원 추가 — 자식 프로퍼티를 직접 대입하지 않고 Dispatch.process(child, prop, ..., 1)로 위임하는 것으로 확정(프로세스 중 inst를 바꾸는 건 키를 바꾸는 것과 같은 층위라 UB 아님, dispatch-core-plan.md에 일반 규칙으로 명문화) — Tween 해석 코드가 PropertyHandler 하나에만 남는다는 불변식이 유지되고, 이 문서가 새로 정할 건 스칼라→프로퍼티 wrap을 Tween<T>.Value에만 적용되도록 들어올리는 헬퍼 하나뿐. 옛 "트윈까지 지원할 필요 없음" 서술은 역전됨(그때는 Tween이 독립 Dispatch 핸들러였음). ROADMAP M10에 빠져 있던 체크리스트 항목도 이 세션에 보강. [2026-08-18 구현 전 QA 반영] 만든 자식을 다시 찾을 때 FindFirstChild 대신 Relate 저장(이름은 표시·판정용, 릴레이션은 조회용), 자식 프로퍼티 세팅도 Dispatch.process로 위임해 Tween이 공짜로 따라오게 |
tag-plan.md |
[2026-08-08 세 번째 세션 재설계, 2026-08-12 열한 번째 세션 메커니즘 정정] Tag(...) — array-part 값 객체, Modifier와 같은 immutable clone 체이닝(:Added/:Removed/:Contains/:Apply/Merged), CollectionService 글루만 quad-roblox. retract가 이전 Tag가 걸었던 이름을 이름별 참조 카운트 맵에서 빼고(다른 위치가 겹쳐 쓰면 실제 RemoveTag는 skip), process가 새 Tag의 이름을 등록 — 여러 위치가 같은 이름을 겹쳐 가져도(웹 className류 합집합) 안전. 구 해시 파트 boolean 모델은 archive/tag-hash-key-model-reversed.md, 구 assert(v==nil) 메커니즘은 archive/retract-always-fires-reversed.md. [2026-08-12 열다섯 번째 세션] Added/Removed가 vararg가 아니라 `string |
attribute-plan.md |
[2026-08-07 여덟 번째 세션 신설] 단일 키 [AttributeKey<T> "Name"](구 Attribute<T>) — SetAttribute(name, nil)이 네이티브 지우기라 None 센티널과 가장 깔끔하게 맞아떨어짐. [2026-08-11 아홉 번째 세션] 여러 Store를 한 번에 attribute로 묶는 그룹 Attribute(...) 프리미티브 신설(Tag와 동형 array-part 값 객체, Merged로 헤테로지니어스 Store 합성), 이름 충돌 방지로 단일 키를 AttributeKey로 리네임(잠정). [같은 세션 후속] AttributeKey(name)이 이름별 weak 캐시로 동등성 보장하도록 확정되며, 그룹 Handler는 자기 완결형 재구현 대신 메모이즈된 키로 기존 단일 키 경로에 재귀 위임하는 걸로 개정(중복 구현 제거). [2026-08-12 열 번째 세션] 그룹/직접 쓰기가 같은 이름을 동시에 관리하는 충돌을 막기 위해 그룹은 공개 캐시 대신 rawNew(name) 전용 키+소유권 Relate로 전환. [열한 번째 세션] retract가 store 재발행마다 항상 불린다는 정정에 맞춰 AttributeKeyHandler.retract를 손봄(이 시점엔 v==nil 가드 버전 — 아래 열여섯 번째 세션에서 최종 재정정됨), 그룹의 "남아있는 이름" 위임도 매번 retractUnder를 먼저 부르도록 정정(체인 누수 방지). [2026-08-12 열여섯 번째 세션, 최종 재정정] retract는 완전 no-op으로 굳어짐(SetAttribute는 오직 process(inst,k,nil)에서만) — Attribute는 명시적 None/nil로만 지워지고, 그룹 diff나 컴포넌트 언마운트로 이름이 조용히 사라져도 값은 자동으로 안 지워짐(Ref의 "Destroy 무관, 정리는 명시적으로" 철학과 통일), 단 사라진 이름의 구독은 끊어 자원 누수는 막음 — 위 "v==nil 가드" 버전은 이걸로 폐기. [2026-08-13 세션, 전면 재정정] rawNew+owners 수동 레지스트리 방식이 "그룹이 이름을 놓았다 다시 포함하면 자기 자신과 충돌"하는 실제 버그로 확인됨 — AttributeGroupKeyHandler라는 isHandlable 없는 순수 체크포인트 핸들러를 Dispatch.processAs로 명시 push하고 Dispatch.retractSelfAndUnder로 통째 철거하는 방식으로 전면 재설계, 소유권 충돌 감지도 별도 레지스트리 없이 기존 재진입 가드가 대신 잡아줌(bind-system-plan.md 참고). AttributeKeyHandler는 다시 완전 무상태로 단순화됨. [2026-08-13 세션, 다섯 번째, 전면 재설계 — 체크포인트조차 불필요해짐] Dispatch가 인덱스 기반으로 재설계되며 AttributeGroupKeyHandler/processAs/retractSelfAndUnder를 전부 걷어냄 — 그룹이 그냥 공개 AttributeKey(name)으로 항상 인덱스 1부터 Dispatch.process/retractFrom을 직접 부르면 끝(점유 체크 자체가 소유권 충돌 감지), groupState Relate도 필요 없어짐(반환 클로저가 이름 집합을 직접 캡처) — 중간 버전은 archive/checkpoint-handler-pattern-reversed.md. [2026-08-13 감사, 정정] 그런데 그 의사코드가 process 안에서 이름마다 retractFrom(...,1,...)을 먼저 부르고 있어 인덱스 1이 무조건 비워지는 바람에 점유 체크가 전혀 작동하지 않았음(그룹↔그룹 사이에서 조용한 last-write-wins가 그대로 남아 있었음) — process는 Dispatch.process만 부르고 철거는 반환 클로저가 자기가 등록한 이름 전부에 대해 하도록 정정. 그룹 Handler 시그니처가 계약과 안 맞던 것(process(inst,index,v) 3-인자)도 같이 수정 [2026-08-13 열네 번째 세션, 0-Z 확정] 그룹이 자기 전용 키(비공개 GetKey)로 위임하고 이름 소유권은 AttributeKeyHandler의 이름 claim(nameClaims Relate, 충돌 시 즉시 error)이 판정 — 하강 diff에선 두 그룹이 똑같이 StoreBind로 보여 점유 체크가 성립하지 않기 때문. 후보 (a)(그룹 안 claimant Relate)는 그룹↔직접 쓰기를 못 잡아 기각. 같은 세션에 패키지 재배치(값·알고리즘·단일 키 전부 quad-base, 백엔드는 setAttribute(inst,name,v)만 주입, 엔진 고유 타입 패밀리만 백엔드). [2026-08-18 구현 전 QA 반영] Attribute.Merged(겹치면 error) / Attribute.Overridden(뒤가 이김)을 둘 다 제공으로 열린 항목 해소, 그리고 ⚠️ 같은 그룹 객체를 두 위치에 놓는 경우를 잡을 위치별 claim이 필요하다는 미해결 항목 신설(Ref처럼 bindLifetime 재사용은 불가) [2026-08-24 6라운드] AttributeGroupHandler에 (1) 배열 자리 부기 등록(H-39, Tag와 같은 결함 — 층위 예외를 두지 않고 다른 말단 핸들러와 똑같이 등록한다), (2) type(k) == "number" 가드(H-52), (3) groupClaimKeys 위치 claim 배선(H-41 — 5라운드 AT-1에서 키를 확정해놓고 의사코드에 안 들어가 있었다, nameClaims보다 먼저 해야 절반만 기록되는 중간 상태가 안 생긴다). 그리고 attribute 이름을 서로 다른 두 자리 사이에서 옮기는 것은 UB로 확정(H-18/H-45 — 두 체인이 별개라 emit 순서에 따라 성공하거나 크래시하는데, 사용자 판단: 막으려면 process/retract 계약 전체에 예외가 생겨 오버엔지니어링) |
onchange-plan.md |
[2026-08-10 세션 신설] OnChange(name) — GetPropertyChangedSignal 바인딩 전용 특수 키, Attribute와 달리 제네릭 타입 파라미터 없음(콜백 타입은 인라인 명시, 이벤트 바인딩과 같은 급 트레이드오프). 전부 quad-roblox(Handlers/OnChange.luau), State<function>은 기존 이벤트 store-bind 메커니즘 재사용. [2026-08-11 아홉 번째 세션 후속] AttributeKey와 동일한 이름별 weak 캐시로 OnChange(a) == OnChange(a) 동등성 보장 [2026-08-24 6라운드 H-27] process에 v == nil 얼리리턴을 추가 — 이 문서가 스스로 *"event-plan.md와 같은 결"*이라 결론냈는데 그 "같은 결"의 핵심(v=nil이면 해제만 하고 새로 Connect하지 않는다)이 의사코드에 없었다. 없으면 State를 None으로 꺼서 콜백을 끄는 게 실제로는 나중에 터질 Connection을 새로 심는 동작이 된다 |
relate-plan.md |
[2026-08-08 신설] Relate — inst를 weak 키로 하는 범용 릴레이션 프리미티브(SetWeak/GetWeak/SetStrong/GetStrong, 비싱글톤 생성자). 구 base.perInstanceState(inst) placeholder를 대체·정식 승격, lifecycle-pattern.md의 bindLifetime/canExecute가 그 위에 얹힘. [2026-08-12 열세/열네 번째 세션] 서로 다른 두 Relate가 서로의 키를 상대방 값으로 강하게 붙잡는 상호 순환 패턴 경고 신설 — Luau에 ephemeron 테이블이 없어(공식 확인, luau.org/compatibility) 이런 순환은 실제로 GC가 안 됨, Slot의 kSlotMap/slotOwner가 실제 사례이자 수정 사례 |
ref-plan.md |
[2026-08-13 아홉 번째 세션, bind-system-plan.md에서 분리] Ref/PreRef/PostRef — 지연 없는 확정 값 박스. 용도 재정의(leaf 노드를 담는 용도로도, leaf 노드에 바인딩하는 용도로도), .Value+:Set/:Callback/:Wait(전부 self 반환), Ref의 retract가 TagHandler와 같은 Relate diff 패턴이라는 것, 이중 바인딩 금지(canBound), PreRef 호이스팅 pre-pass와 1회용 _fired 가드. 분리는 순수 이동 — 결정은 하나도 안 바뀜 [2026-08-13 열네 번째 세션] 하강 diff 반영(0-Z 배너 해소) — "이전 클로저가 언바인딩, 다음 process가 바인딩" 두 단계는 그대로이고 그걸 일으키는 주체만 Dispatch.process의 핸들러 선비교로 바뀜 [2026-08-14 아홉 번째 세션] PostRef 확정·편입 — PreRef의 거울상(같은 pre-pass가 수집만 하고 두 패스가 전부 끝난 뒤 fire, ProcessedPostRef 센티널+전담 Handler까지 완전 대칭). 보장 범위는 "자기 서브트리 완성"이고 자기가 부모에 붙는 것보다는 여전히 먼저임에 주의. 계열 안 fire 순서는 배열 index 순서 보장 유지(같은 세션에 미보장으로 뒤집었다 철회 — archive/preref-order-unguaranteed-withdrawn.md). [2026-08-18 구현 전 QA 반영] 내부 구조가 별도 .Callbacks 테이블 + 평범한 .Value 필드로 단순화(__index 우회 폐기), RefLeafHandler.isHandlable에 빠져 있던 type(k)=="number" 추가(leaf 바인딩은 배열 전용), "배열 파트의 None은 process를 안 탄다"는 옛 명확화 전면 정정. [2026-08-24 6라운드] .Callbacks가 배열에서 **`{[callback |
event-plan.md |
[2026-08-13 아홉 번째 세션, bind-system-plan.md에서 분리 — 사용자가 직접 지목] 이벤트 바인딩 — 핸들러가 self(Instance)를 안 받는다는 확정(Ref가 이미 커버, 이중 쓰기 경로 방지), 이벤트도 store-bind 가능하며 [2026-08-18 정정] None/nil을 넣으면 disconnect(옛 false 센티널은 None 도입 전의 선택이라 폐기 — EventHandler.isHandlable이 v == nil에도 매치돼야 함). 이벤트 네이밍 관례는 인스턴스 생성과 한 절에 섞여 있어 bind-system-plan.md에 남음, GetPropertyChangedSignal은 onchange-plan.md. 분리는 순수 이동 |
brand-plan.md |
[2026-08-13 아홉 번째 세션, bind-system-plan.md에서 분리] Brand — 런타임 nominal 타입 판별 통합 메커니즘, isState를 branded 타입 전부로 일반화(isPostRef 포함, 2026-08-14 아홉 번째 세션). [2026-08-21 전면 재작성] 공유 레지스트리 + Brand.get(x) -> tag(객체당 태그 하나)에서 인스턴스 브랜드(Brand() + :register/:is, 다중 태깅 허용)로 바뀜 — 발단은 Source가 SourceBrand이면서 동시에 EpochBrand여야 하는데 옛 모양으로는 표현이 안 되던 것. 역조회는 없어졌고(멤버십 질문만 씀), weak-key·테이블 아이덴티티·duck-typing 기각 근거·포함 관계 predicate 합성은 전부 유지. 역전 원문은 archive/brand-shared-registry-reversed.md, 근거 기록은 reference/epoch-brand-composition.md. 동작/구현은 확정, 이름 Brand 자체만 용어 정리 대기(question.md 1번). [2026-08-18 구현 전 QA 반영] Brand는 아무 의존성도 갖지 않는다 — None 특수 분기 안은 기각(isNone은 그냥 v == None) |
tween-plan.md |
[2026-08-12 세션, research/에서 승격] 값-레벨 Tween<T> 래퍼(PropertyHandler가 소비, 구 특수 bind key 모델은 archive/tween-special-bind-key-reversed.md). 3-상태 릴레이션 슬롯({Tween,Value}|true|nil), T'=T|Tween<T> 타입 치환. 옵션 값 모양은 Info: TweenInfo? 우선+편의 필드 폴백, override는 Tween.Cancel(기본)/Tween.Finish 2값. Animate(info)는 Tween opts를 T|State<T>로 받아 :Apply로 꽂는 sugar. 자연완료 시 per-instance 북키핑은 정리 안 해도 됨으로 확정(목표값 도달 상태라 부작용 없음, Completed 이벤트 구독 장치는 오버엔지니어링으로 판단). initValue는 사용자가 직접 처리(에이전트 범위 제외) [2026-08-24 6라운드 H-24] :Mapped 절에 타입 단서 추가 — 그 시그니처는 재귀 제네릭 누수 패턴이라 인라인이 아니라 typeof(named function)으로 선언해야 체커가 지켜준다(base/typing-limits.md). "타입이 안전하게 성립한다"는 의미론상 맞지만 체커가 지켜준다는 뜻이 아니었다 |
fallback-plan.md |
[2026-08-14 세션, research/에서 승격] Fallback/Traceback — 컴포넌트 함수를 감싸 에러 시 플레이스홀더를 그려주는 순수 슈가(additional-primitives-plan.md의 "Error Boundary" 절이 내린 "빈 자리 아님" 결론 위에 얹힘). Fallback은 pcall 기반(trace 없음), Traceback은 xpcall+debug.traceback 기반(trace 항상 있음) — 플래그 대신 별도 함수로 분리(Ref/PreRef와 같은 패턴). err: any(Lua error()가 임의 값을 던질 수 있음, error(msg) 기본 호출의 위치 접두 캐비엇 포함) 확정. 패키지는 quad-base, 이름 확정. 메커니즘 실측은 audit/fallback-xpcall-verification.md. 구현 우선순위는 형제 백로그(quad-mock/quad-debug/Operator)와 동급, 맨 뒤 ⚠️ [2026-08-24 6라운드 H-26] 미해결 항목이 하나 신설됐다 — 실패 이전에 생성된 부분 트리는 회수되지 않는다. quad Instance는 gcconn 때문에 Destroy로만 회수되는데, 컴포넌트가 리터럴을 만들다 던지면 그때까지 완성된 형제/자손이 트리에 붙지도 파괴되지도 않은 채 사라지고 Fallback은 그 존재를 알 방법이 없다. Fallback/Traceback이 그 경로를 계속 살려두는 걸 존재 이유로 삼는 대표 사용처라 층위가 다르다 — 백로그(그 둘이 슈가라 구현 시점에 같이 다룬다, 그 문서의 ⚠️ 절이 소스) |
lifecycle-hooks-plan.md |
[2026-08-14 아홉 번째 세션, research/에서 승격] 생명주기 훅 슈가 OnCreated/OnRendered/OnDestroyed — 각각 PreRef():Callback(fn)/PostRef():Callback(fn)/Effect(function() return fn end)를 반환하는 순수 팩토리 함수라 새 타입/Dispatch 개념이 전혀 안 생김(호출 즉시 평가돼 기존 인스턴스로 사라짐), 여러 개 나란히 등록도 자연 지원(단 같은 계열끼리의 순서는 미보장). 마지막 열린 항목이던 OnRendered는 사용자가 채택 확정 — 메커니즘은 PostRef(base/ref-plan.md), 원래 열어뒀던 (a)/(b)/(c) 중 (a). 캐비엇: OnRendered는 서브트리 완성은 보장하지만 이 인스턴스가 부모에 붙기 전에 불림(React componentDidMount와 다름) — 문서화 필수. 패키지 quad-base 확정. [2026-08-14 열 번째 세션] dispose() 범위(0-B)가 Slot+Instance로 좁혀지고 Observer/Effect는 제외되는 쪽으로 확정되며 OnDestroyed 이름 재검토 조건이 발동 없이 종결 — OnDestroyed가 최종 이름, 용어 대기열에서도 제외 |
gate-plan.md |
[2026-08-21 신설, 같은 날 표면 확정] state:Gate(setup) — 상류 emit을 가로채 내려보낼지 정책이 정하는 GateNode(ComputeNode와 같은 층위)를 만드는 State 메소드. 탑레벨 Gate(...) 프리미티브는 안 만든다(처음 방향에서 뒤집힘) — Blocker가 state:Block(blocker) 안에서 이 배선을 쓰고, Debounce/Throttle은 state:Apply(...) 팩토리가 내부에서 :Gate를 부른다. Get()엔 영향 없음(통지만 막음)까지 확정. [2026-08-24 6라운드 H-33/H-49] 열린 항목이 전부 닫혔다 — 재진입은 2026-08-21에 이미 닫혀 있었고, 마지막 남은 생명주기(=Gate에 Flush/Cancel 표면을 둘지)는 안 두는 것으로 확정: blocker:Policy(emit)을 노출하고 Debounce/Throttle이 자기 Blocker를 조종하는 정책이 된다(정책 합성은 손으로 중첩). ([2026-08-22] 미결이던 "마일스톤 범위 — Gate만 vs Blocker까지"는 둘 다 같은 마일스톤으로 해소.) 구현은 M2 |
state-epoch-plan.md |
[2026-08-21 신설, 같은 날 채택 확정·Epoch 일반화까지 반영] State의 재계산/전파 판정을 invalid 플래그가 아니라 Epoch 리비전 비교로 한다 — DFS 전파 도중 Get()이 섞인 값을 캐시하던 glitch(실재)를 없애는 정확성 결정. type Epoch = { Revision: number }(그 자체로 키가 되는 unique 테이블, Source가 구조적으로 만족), 부기는 재사용 가능한 EpochMap(`:Update(Epoch |
reference/ — 온디맨드 참고 자료 (2026-08-07 신설)
| 문서 | 내용 |
|---|---|
quad-v1-architecture.md |
v1(initreq/quad) 내부 동작 스냅샷 — "이 문제를 안 반복하려면"의 기준선. [2026-08-07 base/→reference/ 이동] v2의 결정 자체가 아니라 다른 문서가 인용하는 온디맨드 자료라 항상 읽을 필요는 없음 |
comparison-fusion-vide.md |
Fusion/Vide 아키텍처 비교 리서치 — 설계 결정 근거 자료(전파 모델 등 일부 서술은 이후 라운드에서 뒤집혔으니 bind-system-plan.md 쪽을 최신으로 볼 것). [2026-08-07 base/→reference/ 이동], quadnomicon 소재 후보 |
comparison-charm.md |
[2026-08-09 신설] littensy/charm(Roblox Zustand류) 비교 — batch()/atom()/수동 dispose Effect 3가지는 quad가 이미 기각한 패턴이라 반면교사, None 센티널은 독립 재확인, charm-sync의 diff/patch는 quad 미착수 네트워크 복제 영역의 첫 참고자료, 이미 확정된 :Compute의 previous 인자(base/source-state-plan.md)엔 정황 증거(생성 시 필수 equals, computed의 previous-in-getter) 제공 |
epoch-brand-composition.md |
[2026-08-21 신설, 같은 날 확정·승격 완료 → research/에서 이동] Epoch 인터페이스 + EpochMap 컴포지션 + Brand 인스턴스화의 결정 근거 기록. 발단은 다중 의존성 Effect에서 한 파동에 fn이 두 번 도는 갭 — Effect가 자기 EpochMap을 들면 닫힌다. 에이전트가 짚었던 대가 둘(Brand.get 역조회 상실 / 포함관계가 흩어짐)은 둘 다 해소(전자는 필요 없고, 후자는 착오 — predicate 합성으로 그대로 쓰면 됨). 에이전트가 한때 ":Sync가 필수"라 적은 것도 Update를 좁게 본 착오로 철회. 정본은 base/state-epoch-plan.md/base/brand-plan.md/base/source-state-plan.md/base/effect-plan.md |
slot-attach-decomposition.md |
[2026-08-21 신설, 같은 날 확정 → research/에서 이동] attachSlot의 책임 분해 결정 근거 기록 — 결론은 후보 (B) 분해 채택이고 base/slot-plan.md에 반영 완료(materializeSlotTree+mountSlotTree+두 줄짜리 attachSlot 래퍼). QA 4라운드 F-4-3에서 Dispatch.setLength를 flush 앞/뒤 어디에 둘지가 갈렸는데, 사용자가 그건 자리 선택 문제가 아니라 "attachSlot 의 기능이 너무 다양해진게 문제" 라고 짚어 확장 논의로 넘어간 것. attachSlot이 지던 책임 일곱과 순서 제약 일곱(각각 RC-1/RC-3/RC-4 등 실제로 밟은 버그 출처까지)을 모아두고, C6(길이 최종값은 flush 뒤에야 정해짐)와 C7(부기가 물리보다 먼저)이 단일 함수로는 동시 만족 불가능함을 보인 뒤 분해 후보 넷을 대조. [2026-08-21 후속 캐비엇] 그 C7은 같은 날 native* 계층 확정으로 일반 계약으로서는 폐기됐다(archive/bookkeeping-before-physical-reversed.md) — 결론은 그대로 유효하지만(배치 경로가 부기를 먼저 끝내는 건 C6 요구사항이라 별개) 그 표만 떼어 읽지 말 것, 문서 상단에 배너를 달아뒀다. 사용자 확정 논거는 "지금 의사코드를 건들이는 비용이, 추후 실수가 누적되는 비용보다 싸다고 생각함". 정본은 base/slot-plan.md |
research/ — 아직 착수 전, 상의 필요
| 문서 | 내용 | 우선순위 |
|---|---|---|
debug-tooling-plan.md |
실물 Instance→코드 위치 역추적 Studio 플러그인(quad-debug) — 채널 실현 가능성(BindableEvent/Function 크로스 컨텍스트)까지 실측 검증 완료, 세부 API 이름·구현만 남음 |
하 — 사용자가 "quad 개발 완료 전엔 착수 못 함"으로 직접 후순위 지정, base 설계 시 훅 확장 지점만 고려 |
documentation-plan.md |
문서 사이트 구조(초심자/api/심화/quadnomicon 4축, 백엔드별 트랙 분리) + UI 네이밍 컨벤션·Store 부작용 패턴·권장 이벤트 핸들링 3개 세부 문서 뼈대 |
하 — 착수 시점 미정, 구조/스코프만 합의된 상태 |
documentation-content-map.md |
위 4축에 실제로 뭘 채울지 base/ 전체를 초심자/api/심화/skip으로 서베이한 콘텐츠 맵 — 초심자 core loop 목차 초안 포함 |
하 — 문서화 착수 시점의 목차/우선순위표로 쓸 것 |
framework-comparison-findings.md |
quad vs Fusion/Vide/react-lua 정직한 비교(실 소스 근거) — quad 강점, 못 고치는 트레이드오프(의도된 설계) 정리. [2026-08-12 열여덟 번째 세션] "고칠 만한 것" 절에 남아있던 마지막 두 항목(use-after-destroy 검증 안전망 부재, :With 동적 의존성 미지원)도 사용자가 "고칠 필요 없음"으로 최종 판단해 3번 절(못 고치는 트레이드오프)로 이전 — 2번 절은 이제 해소된 항목만 남음 |
하 — 배경 자료, 더 이상 사용자 판단 대기 상태 아님 |
additional-primitives-plan.md |
[2026-08-09 세 번째 세션, 전부 해소] 마지막으로 남아있던 키 기반 동적 컬렉션 재조정도 Slot:List(...)로 확정되어 base/slot-plan.md로 승격 — 이 문서엔 새로 열린 설계 질문 없음, "빈 자리 아닌 것"/"문서화 백로그"/조사 소스 목록만 배경 자료로 유지 |
하 — 배경 리서치 기록용, 열린 결정 없음 |
pre-implementation-audit.md |
M0 착수 직전 크리티컬 감사(2026-08-06 신설) — base/ 전체를 모호성/지연결정리스크/단순화후보 세 렌즈로 재검토, 11개 우선순위1 + 11개 우선순위2 + 2개 단순화후보. [2026-08-12 열일곱 번째 세션] 우선순위1 11개 전원 해소 — 남은 건 .claude/luau-test/ 스파이크 실측 확인뿐 |
상 — 설계는 전부 해소, .claude/luau-test/ 스파이크 실측만 남음 |
operator-sugar-plan.md |
[2026-08-12 신설] Sum/Product/Not/비트연산 등 :Compute/:Apply용 연산자 콤비네이터 슈가 — 메커니즘은 이미 확정된 계약(Animate와 동형 패턴) 재사용이라 확정, 네임스페이스 이름만 미정. [2026-08-12 열아홉 번째 세션] 서브 에이전트 외부 리서치로 다른 리액티브 라이브러리 선례와 대조 — Operator가 가장 강한 선례(Python operator 모듈), Clamp/Min/Max가 추가 후보로 부상, 비트연산·비교연산자·Sub/Div는 선례 전무로 드랍 후보, Debounce/Throttle은 Blocker와 다른 시간 기반 메커니즘이라 별도 질문으로 분리([2026-08-19] 그 질문은 base/debounce-throttle-plan.md로 전부 해소·승격 완료), Filtered의 Slot 안/밖 구분 판단이 ReactiveUI/SolidJS 선례로 뒷받침됨 — 최종 이름 결정은 여전히 사용자 몫. [2026-08-13 세션, 두 번째] Haskell 비교 리서치 중 Alternative(nil 대체값, coalesce류) 후보 신설 — 카탈로그 확정 규칙에 그대로 맞음, 이전엔 없던 게 확인됨 |
하 — 구현은 맨 마지막(순수 슈가, 없어도 무방, 함수 간 의존 없음), 사용자가 직접 후순위 지정 [2026-08-13 여섯 번째 세션] `State<State |
quad-recursive-acronym.md |
[2026-08-14 신설] GNU/WINE류로 Quad를 재귀 약어화하는 카피 브레인스토밍 — 설계 결정도 착수 게이팅도 아니고 나중에 README.md 헤딩 등에 쓸 캐치프레이즈 후보 모음. 자학 개그 방향(기각)과 지연평가/재귀·커링/펑터/클로저를 자랑하는 방향(채택 후보, 미확정) 정리 |
하 — 카피 소재, 설계 상의 필요 없음. 사용자가 최종 문구 고르면 반영 |
v1-compat-plan.md |
v1 하위호환(compat) 레이어 — quad-roblox-v1-compat 패키지, v2→v1 단방향 브리지(state:Observer()+v1 프로퍼티 재대입), v2-in-v1/v1-in-v2 두 임베딩 방향의 기술 규칙까지 확정. quad2-try의 quad-compat은 빈 폴더로 실제 시도된 적 없었음을 확인 |
하 — Slot이 foreign Instance를 어떻게 다루는지만 Slot 코어 구현 시점까지 미결 |
doc-include-plan.md |
[2026-08-14 신설] 문서 stale 감소용 include 도구 doc-include.py(가칭) — 원본 파일에 <!--#summary--> 류 마커로 요약 구간을 표시해두면 인용하는 문서가 그 구간을 기계적으로 추출해 붙여넣게 하는 도구. doc-check.py(사후 탐지)와 짝을 이루는 사전 차단 장치. AsciiDoc tagged include/markdown-magic이 선례, build vs buy 검토 후 Python 표준 라이브러리로 직접 제작(~100줄) 채택. 파일럿은 .claude/session-summary.md ← .claude/session/*.md 요약 마커부터(CLAUDE.md 분할로 목적지가 "통째로 생성되는 파일"이 돼 단방향 생성으로 단순화됨) |
하 — M0/설계 게이트와 무관한 메타 도구. [2026-08-16 기준] 플랜 초안 단계, 열린 질문 미해소(소스: 이 문서의 "열린 질문" 절) |
fastscroll-plan.md |
[2026-08-18 신설] 사용자 아이디어 메모 — 완전 외부 패키지 quad-roblox-fastscroll(리스트/그리드 내 상대 위치 계산으로 움직일 요소만 갱신, 배경의 빈 공간만 스크롤). 가상 레이아웃 유틸이 선행 요구사항으로 보임. 설계 논의 전, 아직 아이디어 단계 |
최하 — 사용자가 "quad가 잘 작동하게 될 때" 직접 검토하겠다고 후순위 지정. 선행 확인 필요 사항(Visible=false일 때 AbsoluteSize/AbsolutePosition 갱신 여부)은 Roblox Studio 실측 필요 |
spring-plan.md |
[2026-08-18 신설] 사용자 아이디어 메모 — 스프링 물리 기반 지속 업데이트 프리미티브(quad-spring), 이전 상태와 비교해 스프링 연산을 수행하는 중간 핸들러. 참고 구현 qwreey/spring.lua 사용 가능 여부 확인 필요. 확정 Tween<T> 모델과는 별개 트랙 — quad-base의 onStep류 후킹 인터페이스로 얹을지, 엔진별 quad-roblox-spring으로 각자 구현할지, Source<number> 확장 primitive로 둘지 미정 |
최하 — "모든게 완성된 후, 별도 모듈로 분화"라고 사용자가 직접 명시 |
archive/ — 완료됐거나 완전히 뒤집힌 것, 능동 참고 불필요
| 문서 | 내용 |
|---|---|
always-propagate-no-dedup-superseded.md |
[역전됨, 2026-08-21 신설] source-state-plan.md가 확정해뒀던 "emit은 자기 invalid와 무관하게 항상 전파된다 / quad가 접지 않는 것은 중복 통지뿐이다" — base/state-epoch-plan.md 채택으로 "같은 Epoch의 같은 리비전이 두 번째로 도착하면 접는다"로 바뀜. ⚠️ 2026-08-14의 invalid 기반 dedup 역전을 되돌린 게 아님(그 금지는 유효) |
brand-shared-registry-reversed.md |
[역전됨, 2026-08-21 신설] Brand의 옛 표면 — 공유 weak 레지스트리 하나 + Brand.set/Brand.get(x) -> tag(객체당 태그 하나). Source가 SourceBrand이면서 동시에 EpochBrand여야 하는 요구(base/state-epoch-plan.md의 Epoch 인터페이스)를 표현할 수 없어 인스턴스 브랜드로 재작성됨(base/brand-plan.md). 같이 버려진 역조회 Brand.get은 코퍼스 전수 조사에서 쓰는 자리가 하나도 없어 대가가 아니었고, "포함 관계가 코드에 드러난다"는 우려는 에이전트 착오로 철회됨 |
store-source-proxy-reversed.md |
[역전됨] 2026-08-04에 확정했던 StoreSource 프록시 설계(Store가 Source를 감춘 별도 프록시로 감쌈) — 2026-08-06 세 번째 세션에서 "Source가 State를 구조적으로 만족" 재구성으로 완전히 대체됨. 원문·역전 이유·신구 비교표 보존, quadnomicon 소재 후보 |
ref-phase-option-reversed.md |
[역전됨] CreatedRef의 phase 옵션 — 위치 기반 순서 + PreRef 신설로 대체됨 |
preref-order-unguaranteed-withdrawn.md |
[철회됨, 2026-08-14 아홉 번째 세션 신설] 복수 PreRef/PostRef 간 fire 순서를 "배열 index 순서 보장"에서 미보장으로 바꾸려던 안 — 같은 세션에 제안·철회, 현재 계약은 보장(2026-08-07 결정 그대로). 반례는 FastQuery(...) -> PreRef류 조합(앞자리 항목이 뒤 항목의 전제를 만들어주는 정당한 합성), 보장 비용 0 + 배열 파트 index 순서 계약의 자동 귀결이라 새로 내주는 자유도 없음. 양쪽 논거 보존 |
ui-shorthand-roundsize-dropped.md |
[기각됨, 2026-08-07 신설] v1 RoundSize(이미지 9-slice 라운드 트릭) — 네이티브 UICorner로 대체되어 포팅 불필요. 이 판단이 한 차례 "Corner/PaddingAll/Scale 숏핸드 전체가 불필요하다"로 과잉일반화됐다가 정정된 이력 포함 |
batch-rejected.md |
[기각됨, 2026-08-07 신설] lexical Batch(fn) — 코루틴 yield 위에서 구조적으로 위험해 기각, 값 기반 Blocker(base/blocker-plan.md)로 대체 |
context-rejected.md |
[기각됨, 2026-08-07 신설] Context(트리 하위 암묵 전파) + 대안이던 레이어드 Store 둘 다 기각 — 명시적 타입 강제 Store 전달로 충분하다는 판단 |
modifier-apply-mutable-rejected.md |
[기각됨, 2026-08-08 신설] Modifier.Apply/setter를 mutable로 바꾸는 방안(및 "Apply 경계에서만 clone" 절충안) — 둘 다 형제 서브트리 오염 방지가 clone 비용 절감보다 우선이라 기각 |
tag-hash-key-model-reversed.md |
[역전됨] 구 Tag 모델(해시 파트 boolean 키, 태그 개수만큼 키 갱신) — 2026-08-08 세 번째 세션에서 array-part 값 객체(Tag(...), :Added/:Removed/:Contains/:Apply/Merged) 모델로 완전히 대체됨 |
agent-mistake.md |
[에이전트 실수, 2026-08-07 신설] 설계 반전이 아니라 에이전트가 문서 작성 중 개념을 혼동했다가 같은 세션 안에서 스스로 정정한 사례 모음(canExecute/isHandlable 혼동, isSource 불필요 오판) — CLAUDE.md 세션 로그의 중복 서술을 여기로 옮기고 포인터만 남김 |
quad2-try-research-findings-rejected.md |
[기각됨, 2026-08-09 코퍼스 정리 신설] quad2-try 이전 시도 리서치 전문(OOP 상속/커스텀 파서/Slot 스텁/Pipe copy-on-write 4가지 죽은 접근 + :With 이름 방증) — base/bind-system-plan.md에 남아있던 인라인 전체 서술을 이전, 결론 한 줄 포인터만 본문에 남김 |
observer-cleanup-contract-rejected.md |
[기각됨, 2026-08-09 코퍼스 정리 신설] Observer 자체에 React useEffect식 cleanup 반환 계약을 추가하는 안 — 클로저로 이미 충분해 기각, Effect가 opt-in 상위 계층으로 이 패턴을 제공 |
keyed-collection-state-method-rejected.md |
[기각됨, 2026-08-09 코퍼스 정리 신설] 키 기반 동적 컬렉션 재조정 프리미티브를 state:Keyed(...) State 메소드로 두려던 초안 — Source 미사용 컴포넌트가 접근 못 한다는 반례로 철회, 현재는 자유 함수로 확정 |
debug-channel-replicatedstorage-rejected.md |
[기각됨, 2026-08-09 코퍼스 정리 신설] quad-debug 채널을 ReplicatedStorage에 자동 생성하던 초안 — 게임 트리 오염 부작용으로 기각, quad 모듈 자신의 트리+CollectionService 태그로 대체 |
tween-special-bind-key-reversed.md |
[역전됨, 2026-08-10 신설] 구 Tween 모델([Tween(key,tweenData...)] = storeValue 특수 bind key, 우선순위 최상위 Dispatch 핸들러) — 값-레벨 Tween<T> 래퍼 모델로 완전히 대체됨(base/tween-plan.md) |
onchange-per-property-codegen-rejected.md |
[기각됨, 2026-08-10 신설] OnChange.PropertyName 프로퍼티별 정적 코드 생성 — Attribute의 정적 지름길과 달리 (클래스 수 × 프로퍼티 수) 규모로 폭발해 기각, OnChange(name) 단일 팩토리로 대체 |
invalidate-dedup-propagation-reversed.md |
[역전됨, 2026-08-14 신설] "신호를 받은 State는 이미 invalid였다면 그 아래로 더 전파하지 않는다"(다이아몬드 중복 워크 방지) — 실제로는 emit이 자기 invalid 상태와 무관하게 항상 전파되고, 중복 재계산은 pull-recompute+캐시가 막음. 옛 서술은 확정된 Observer 계약(fn이 :Get()을 안 불러도 됨)과 정면 충돌해 :Get() 안 하는 Observer가 한 번 울고 영구 침묵하게 만들었고, architecture.md와도 어긋나 있었음. Debounce 설계 중 사용자 지적으로 발견 — 그 위에 쌓였던 debounce-throttle-plan.md 3절 발견도 같이 철회됨 |
retract-always-fires-reversed.md |
[역전됨, 2026-08-12 열한 번째 세션 신설] "핸들러 타입이 안 바뀌면 retract 없이 process가 diff" — 실제로는 retract가 store 재발행마다 항상 불림(핸들러 타입 무관). Tag/Ref/Slot/Attribute 전부 이 오류 위에서 설계돼 있었음이 드러나 한 세션에 전부 정정 |
slot-discard-no-portal-reversed.md |
[역전됨, 2026-08-13 일곱 번째 세션 신설] Slot의 "retract = 폐기, 옮기지 않음"(2026-08-04 확정) + "portal은 오버엔지니어링이라 안 함" — 여섯 번째 세션에 State<Slot> 교체가 파괴에서 언마운트로 뒤집히며 portal이 별도 기능이 아니라 그 귀결이 됨(state<Frame>와 동일한 시맨틱). base/slot-plan.md에 히스토리로 남아 있던 세 덩어리(확정 문단 + State<Slot?> 왕복 분석 + 포탈 검토와 숙제 셋)를 원문 그대로 이전, 숙제 셋이 각각 어떻게 결말났는지도 정리 |
existing-instance-bind-rejected.md |
[기각됨, 2026-08-14 세션 — research/에서 이전] 이미 생성된 Instance에 나중에 {k=v} 프롭 테이블을 바인드하는 기능 — 오래 "열린 가능성"으로 남겨뒀으나 사용자 확정으로 기각. 사유: 허용하면 Dispatch.setOffsetSource/setLength 같은 "quad가 만든 트리" 전제의 부기를 바깥에서 밀고 당기는 부가 작용이 전부 가능해져 버그 표면이 치명적으로 넓어짐. pre-implementation-audit.md 2-4(Slot 단일 마운트 소유권과의 충돌)도 이걸로 해소 |
question-resolved.md |
[해소 아카이브, 2026-08-13 아홉 번째 세션 신설] question.md에서 걷어낸 결정 완료 항목 전부(당시 32개 [해소됨] 마커) — 추가 프리미티브 필요성 라운드, 구현 착수 직전 감사 요약, 확정된 용어들(State/Relate/List/canBound(2026-08-14 다섯 번째 세션에 폐기됐다가 열한 번째 세션에 별도 진입점으로 재도입 — canExecute와 판정 로직만 공유)/Ref/PreRef/Peek/isState/None/Handler), 포탈·State<Slot?> 왕복 해소 등. 분리 직전 전문을 그대로 보존. question.md는 이제 사용자가 답해야 할 것만 담음 — 항목이 해소되면 여기로 옮길 것 |
dispatch-hintvalue-model-reversed.md |
[2026-08-13 열네 번째 세션 신설 — 옛 이름은 research/ 아래의 dispatch-redispatch-diff-plan] 뒤집힌 "철거 후 재구축 + hintValue 힌트" 재디스패치 모델 원문 + 역전을 이끈 분석 전문(None/State 래퍼가 힌트로 새는 재현 사례, 깊은 인덱스 힌트 유실, 옛 점유 체크가 Attribute 소유권을 대신하던 구조). 지금 유효한 모델은 base/dispatch-core-plan.md |
checkpoint-handler-pattern-reversed.md |
[역전됨, 2026-08-13 다섯 번째 세션 신설] AttributeGroupHandler의 이름 소유권 충돌을 고치려고 만든 Dispatch.processAs/Dispatch.retractSelfAndUnder 체크포인트 핸들러 패턴(같은 날 네 번째 세션 신설) — chains를 핸들러 identity가 아니라 재귀 깊이 인덱스로 추적하는 더 근본적인 재설계로 대체되며 같은 날 바로 불필요해짐. State<State<T>>도 이 재설계로 UB에서 정상 지원 대상으로 바뀜 |
bookkeeping-before-physical-reversed.md |
[역전됨, 2026-08-21 구현 전 QA 5라운드] "부기가 물리 트리 조작보다 항상 먼저 끝난다"(4라운드 C-7 일반 계약) — native* 주입 op 계층이 확정되며 폐기. base엔 물리적으로 자리를 비워둘 수단이 없고(밀어내는 주체는 백엔드의 삽입 연산 자신), 그래서 "빼기는 물리 먼저/넣기는 부기 먼저" 두 얼굴 대신 "자기 자리를 정하는 것 먼저(setOffsetSource) / 뒤를 미는 것 나중(setLength→recompute)" 하나로 줄었다. 배치 경로의 부기-전량-먼저는 C6가 요구하는 별개 사안이라 그대로. 현행은 base/dispatch-core-plan.md의 "일반 계약 — 물리와 부기의 순서" 절 |
bindlifetime-slot-owner-reversed.md |
[역전됨, 2026-08-21 구현 전 QA 5라운드 C-4] "bindLifetime의 첫 인자가 Slot일 수 있으니 백엔드가 그 경우를 핸들링하고 isBoundAlive에 세 번째 분기를 둬라"(4라운드 D-56) — Dispatch.setLength가 부기 키와 생명주기 앵커를 따로 받도록 바뀌면서 요구사항 자체가 사라짐(앵커는 언제나 물리 target, 모든 호출부가 이미 그 값을 안다). 부수로 형태 미정이던 "세 번째 분기" 항목도 같이 닫힘. 현행은 base/dispatch-core-plan.md의 "setLength 구현" 절 뒤 문단 |
canexecute-inst-arg-reversed.md |
[역전됨, 2026-08-14 다섯 번째 세션 신설] canExecute(inst,value)/unbindLifetime(inst,value) 2-인자 시그니처와, 그 뿌리였던 bindLifetime이 .Subscribed를 세팅한다는 오염(2026-08-08 다섯 번째 세션에 "재정정"으로 들어와 2026-08-09의 canBound까지 그 위에 세워짐) — .Subscribed는 전역 :Subscribe() 전용 필드라 leaf 경로와 무관했고, bindLifetime이 gcconn 참조를 value 쪽으로 복사해두면 value 하나로 생존을 물을 수 있음. 같이 폐기된 것은 canBound(handle)(2026-08-14 열한 번째 세션에 별도 진입점으로 재도입 — 이 문서 하단에 addendum)와 gcconn/gchold의 lazy 생성. 오류가 여섯 세션을 살아남은 이유(canExecute의 실제 호출부가 어느 문서에도 코드로 없었음)와 그 일반 교훈("계약을 정할 때 호출부를 최소 하나는 의사코드로 같이 적을 것")도 정리. 현행은 base/lifecycle-pattern.md |
tag-attribute-load-time-registration-reversed.md |
[역전됨, 2026-08-14 열두 번째 세션 신설, 2026-08-18 재역전으로 원래 결론 쪽이 다시 현행] "TagHandler/AttributeKeyHandler/AttributeGroupHandler가 quad-base 모듈 로드 시점에 스스로 등록한다"(열한 번째 세션 "네 번째, 최종 정정") — 2026-08-14 열두 번째 세션엔 base/lifecycle-pattern.md가 거부한 InitNamespace류 top-level 부작용 패턴과 같은 클래스라며 틀렸다고 보고, 등록 주체를 백엔드 팩토리로 정정했었음. 그런데 2026-08-18 구현 전 QA에서 다시 뒤집힘(D-7) — 백엔드 팩토리가 등록 주체면 quad-roblox를 아예 안 붙인 상태에서 "provider가 초기화됐는지" 안내 경로 자체가 안 돌기 때문. 지금 현행은 이 문서가 역전이라 부르던 원래 결론(quad-base 자신이 로드 시점에 등록)과 같은 방향 — 상세는 base/dispatch-core-plan.md의 "base가 소유하는 핸들러와 주입되는 엔진 op" 절, 재역전 논의 원문은 qa-request/pre-implementation-qa-round1.md의 "D-7" 절 |
참고
- 저장소 소유자가 답해야 할 질문 전체 취합:
.claude/question.md - 사람만 할 수 있는 일(로컬 조작/결정): 루트
HUMAN_TODO.md - 원본 브레인스토밍(raw chain-of-thought):
.claude/initreq/raw-userinput.md,.claude/initreq/req.md— 위 문서들로 나누기 전의 원본, 참고용 백업이니 그대로 둘 것