`.claude/pre-implementation-qa.md`(사용자가 base/ 확정 문서를 문항으로 재심사한 결과)를 실제 문서에 반영하고, 그 문서를 qa-request/로 옮기며 1라운드임을 파일명·제목에 명시(2라운드는 새 파일). 그대로 구현하면 반대로 돌던 것 2건: - canBound의 판정 방향이 이름과 반대였음 → canBound(v) == not isBoundAlive(v), 게이트는 전부 `if not canBound(v) then error(...)`. canExecute와는 값이 같은 게 아니라 서로의 부정이고, 그게 오히려 이름 분리의 명분이 됨(옛 근거 "값이 항상 같다"는 폐기). - gcconn/gchold 보관이 SetStrong으로 적혀 있었음 → SetWeak. 근거 문장까지 틀렸던 것이라 같이 교체(그대로 짰으면 두-Relate 상호 강참조 누수). 설계가 바뀐 것: - Dispatch.drive의 None 스킵 분기 폐기 → NoneHandler는 재귀 전담, NilHandler 신설(k=number and v==nil 말단이 setLength/setOffsetSource 등록). 깨진 전제는 "배열 파트의 None은 process를 안 탄다". - Length/Offset 등록 책임이 "처음 매치한 Handler" → 말단 Handler. - 이벤트 disconnect 센티널 false → None/nil. - Ref 내부 구조를 .Callbacks 분리 + 평범한 .Value 필드로 단순화, RefLeafHandler에 빠져 있던 type(k)=="number" 추가(leaf는 배열 전용). - :List reconcile의 nil 리턴은 다시 파괴가 기본, 값 교체와 PopOnly(가칭)만 비파괴. - base 소유 Fallback Handler 등록 주체를 백엔드 팩토리 → quad-base 자신으로 재역전(백엔드 미로드 시 안내 에러 경로가 안 돌았음). - "이벤트 콜백 시그니처는 Luau가 검증 못 한다"가 거짓임이 사용자 반례로 확인 → onchange-plan.md의 파생 근거까지 교체(결론은 유지). 이름/표면: DI → D(Declarative) 확정 및 전수 반영, New 커링 + D는 전량 코드 생성, Attribute.Merged/Overridden 둘 다 제공, Quad.debug 신설, store "key" 문자열 커링 기각(→ store:GetDynamic). 판단이 갈리던 4건(PopOnly 채택 / D-7 재역전 / NoneHandler·NilHandler 역할 분담 / 동적 키 경로)은 사용자에게 물어 확정. 커밋 전 검증: quad-doc-auditor 1패스가 1건, 사용자가 돌린 `/code-review high`가 10건을 더 잡아 전부 반영(ROADMAP이 SL-3 역전을 안 따라오던 것, 설계 갭 2건은 새 열린 질문으로 등록). doc-check.py ERROR 0. Co-authored-by: qwreey <me@qwreey.moe>
11 KiB
2026-08-18 — 구현 전 QA 결과를 base/에 일괄 반영
요청: "pre-implementation-qa 의 적용을 수행하자."
.claude/qa-request/pre-implementation-qa-round1.md(같은 날 앞선 세션이
만든, base/ 확정
문서를 사용자에게 문항으로 재심사한 결과)의 항목을 실제 문서에 반영한 세션.
그 문서는 "여기서 정정하지 않는다, 사용자 정정 회신이 오면 반영한다"고
적혀 있었지만 각 항목에 이미 사용자 답변 원문과 논거가 붙어 있었고,
사용자가 적용을 지시했으므로 그 답변을 정정 근거로 삼아 반영했다.
먼저 사용자에게 물은 네 가지
QA 문서가 "결론 없음"으로 남겨둔 항목 중, 임의로 정하면 안 되는 것만
AskUserQuestion으로 물었다(나머지는 답변 원문이 이미 방향을 확정함):
- SL-3 —
:Listreconcile의nil리턴 → "PopOnly 확정. 다만 이름은 변경될 수 있음. 이름에 대해서는 더 생각해보아야함" → 파괴가 기본으로 되돌리고PopOnly(가칭)를 이번 설계에 넣음. - D-7 — base Fallback Handler 등록 주체 → quad-base 로드 시 등록으로 재역전(2026-08-14의 역전을 다시 뒤집음).
- N-4/RF-4 —
None/nil배열 슬롯 처리 책임 → "NoneHandler는 재귀만, NilHandler가 실질 담당". - ST-2 파급 — 동적 키 경로 → "동적히는 여전히 그냥 Store.Name 하면 얻어는 짐. 타입 애러가 난다는 점인데, 이는 GetDynamic(name): Source 로 제공하는게 최선으로 보임."
반영한 것 — 성격별
(1) 그대로 구현하면 반대로 도는 것
- S-1
canBound방향 반전:canBound(v) == not isBoundAlive(v), 게이트는 전부if not canBound(v) then error(...). 진원지lifecycle-pattern.md의 (1)(2)(3) 절을 다시 쓰고,source-state-plan.md/ROADMAP.md/luau-test(README·STATUS)/audit/gcconn-trick-verification.md까지 같은 방향으로 정정. 부수 발견: 열한 번째 세션이 이름 분리의 근거로 적은 "판정 로직도 같고 값도 항상 같다"가 무너짐 — 실제로는 서로의 부정이고, 그게 오히려 이름 분리의 명분을 강화한다는 쪽으로 절을 다시 씀. - RE-1
SetStrong→SetWeak:relate-plan.md의 "대체하는 것" 절과architecture.md소스 트리 주석. 근거 문장("둘 다 존재 이유가 '안 죽는 것'이므로 strong")까지 통째로 틀렸던 것이라 근거도 교체 — 그대로 짰으면 같은 문서가 경고하는 두-Relate상호 강참조 누수에 정확히 걸렸다.
(2) 설계가 바뀐 것
- RF-4+N-4:
Dispatch.drive의None스킵 분기 폐기 →NoneHandler는 재귀 전담,NilHandler신설(k=number and v==nil말단,setLength(0)/setOffsetSource(None)등록). 깨진 전제는 "배열 파트의None은process를 절대 안 탄다"였는데Frame{ State<Slot|None> }이면 탄다는 것. - D-6 파생: Length/Offset 등록 책임이 "그 위치를 처음 매치한
Handler"에서 말단 Handler로 정정(중간 노드는
inst에 부작용을 안 가한다는 D-3 계약과 충돌했음). 같이 검토 대상이던 "모든 핸들러가k=number일 때 처리" 안은NilHandler가 갭을 닫아 채택 안 함 — 이건 사용자 답변에서 바로 나온 결론이 아니라 두 답변을 합친 추론이라 세션 보고에서 따로 짚었다. - EV-1: 이벤트 disconnect 센티널
false→None/nil.EventHandler가v == nil에도 매치돼야 한다는 계약이 새로 생김. - D-7: Fallback Handler 등록 주체 재역전.
InitNamespace거부 원칙과의 양립 근거를 새로 씀 — 그 원칙이 금지한 건 사용자 수동 init과 남의 상태를 건드리는 top-level 부작용이지, 모듈이 자기 레지스트리를 채우는 게 아니다.archive/tag-attribute-load-time-registration-reversed.md엔 "절반 재역전" 배너를 달았다(이름 쪽 결론은 그대로 유효). - R-1:
Ref내부 구조가.Callbacks별도 테이블 + 평범한.Value필드로 단순화 →__index우회 기법의 존재 이유 자체가 사라짐. - SL-3:
:Listreconcile의nil/키 소멸은 다시 파괴, 값 교체와PopOnly만 비파괴.State<Slot>교체가 언마운트인 것은 그대로 유지되게 세 경로를 표로 갈랐다(:Singlesugar가 교체 경로를 타므로 자동으로 안전). - BS-2+N-9: "이벤트 콜백 시그니처는 Luau가 검증 못 한다"가 거짓 —
사용자가 반례 코드를 직접 작성해 보여줌.
onchange-plan.md가 이 전제를 근거로 쓰던 자리도 근거만 교체(결론은 유지:OnChange는 필드가 아니라 팩토리라 타입을 미리 찍어둘 자리가 없다). 겸해서New커링 계약과 "D는 전량 코드 생성된 순수 별칭 테이블"을 명문화.
(3) 이름/표면
- N-8
DI→D(Declarative) 확정 — 코퍼스 전수 반영(네임스페이스는D, "특수 DI 키"라는 설명 표현은 "특수 키"로 단순화). 2026-08-08부터 개명을 미뤄온 유일한 사유(한 글자 식별자의 검색성)는 "문서에서 처음 나올 때 항상D(Declarative)로 풀어쓴다" 표기 규약으로 보완하고architecture.md의 네이밍 케이싱 절에 4번 항목으로 넣었다.question.md의 1순위 항목은archive/question-resolved.md로 이전. - N-5
Attribute.Merged(겹치면 error) /Attribute.Overridden(뒤가 이김) 둘 다 제공 — 열려 있던 "error냐 override냐"가 제3안으로 해소. 덤으로Merged/Overridden이라는 이름 쌍의 의미가 코퍼스에서 재정렬됨 (연산의 종류 → 충돌 시 정책).
(4) 나머지 — A-3(New() 자동 스코핑이 아니라 Quad() + 코드 수정
필요), D-1(방어 가드 "죽은 코드"에 한정 추가), D-5(PreRef는 배열 우선
보장 위가 아니라 별도 pre-pass), M-3(예약 필드가 Apply 하나가 아니라
셋 + Overridden은 콜론도 가능), B-1(Brand는 무의존 — None 특수 분기
기각), R-3(ProcessedPreRef 센티널), SL-1(RefLeafHandler에 k 체크
추가 + 배열 전용 근거 명문화), E-2(:Unsubscribe()는 :Subscribe()의
짝으로 축소), N-1(FALLBACK 에러에 k 타입), N-2(타입이 방어선), N-3
(Quad.debug), N-6(SetAndDispose 후보), N-7(UI 숏핸드는 Relate로
조회), 부수 오탈자 2건(.value 케이싱, fn(value, previous) 표기).
열어둔 것 (착수 금지 게이트)
question.md 3번과 todos.md 00번이 소스 — 중간 State GC 미검증(M3),
그룹 Attribute 위치별 claim 키 설계(M10), SetAndDispose 방향(M3 전),
dedup 경로의 process/retract 대칭 확인(M3 전), PopOnly 이름,
Store 미선언 키의 타입 에러 실측(M0).
커밋 전 검증 — 감사자 1패스 + /code-review high
quad-doc-auditor 1패스(base 코퍼스 각도): 확실 발견 1건 —
ref-plan.md가 "원래부터 빈 자리인 None은 여전히 두 패스 루프가 직접
건너뜀"이라고 남겨둔 문장이 같은 파일의 2026-08-18 배너와 정면 모순
(정확히 "배너는 고쳤는데 그 배너가 부정하는 본문 bullet은 안 고친" 실패
패턴). 수정 완료.
감사 비용 이슈로 나머지 각도는 중단 — 한 패스가 서브에이전트 토큰
21만/툴 호출 82회였다. 감사자 정의가 "코퍼스 전체를 신선한 맥락에서
다시 읽는다"인 데다(라이브 문서 91개, base/만 ~12,000줄) 이번 프롬프트가
바뀐 결정 16개를 교차 검증하라고 시켜서, 계획대로 4개를 돌렸으면 80만
토큰대였을 것. 다음에 큰 변경을 감사할 때는 전 코퍼스가 아니라 diff가
건드린 파일 + 그걸 인용하는 곳으로 범위를 좁혀 프롬프트할 것.
(부수: /model이 opus로 보여 감사자가 opus로 도는지 의심됐는데, 정의
frontmatter는 model: sonnet이고 오버라이드도 안 넘겼다. 다만 이번
실행이 실제 sonnet이었는지는 확인 못 함 — 이 세션 트랜스크립트에
sidechain 레코드가 안 남았다. todos.md 7번의 "정의가 언제/얼마나
반영되는지 모른다"가 여전히 유효.)
사용자가 /code-review high를 직접 돌림 — 10건 전부 유효했고 전부
반영했다. 감사자가 못 잡은 것들이라 두 도구가 서로를 대체하지 않는다는
게 실측으로 드러난 라운드(감사자는 코퍼스 전체 정합성, code-review는
diff 자체의 결함):
- [high]
ROADMAP.md가 SL-3 역전을 안 따라옴 —unmountSlotTree를 ":List의 reconcile"이 쓴다고 그대로 적혀 있었음. M8 체크리스트를 보고 구현하면 정확히 이번에 되돌린 결함을 다시 만든다. - [medium]
modifier-plan.md§5 /attribute-plan.md근거 문단이 기각된 "문자열 폴백"과 "자주 쓰는 ~25개"를 근거로 계속 인용. - [medium]
GetDynamic콜론 메소드가 Store의 lazy__index와 충돌 — 아무 장치 없이 부르면"GetDynamic"이라는 이름의 Source를 만들어 함수로 호출하게 됨. 예약 키가 되거나 탑레벨 함수여야 함 → 새 열린 질문. - [medium] ROADMAP에 이번 라운드의 새 표면이 통째로 누락
(
Attribute.Overridden/Quad.debug/GetDynamic/PopOnly) → 전부 추가. - [medium]
PopOnly계약과 의사코드 불일치 — 키가 사라지면 홀드 중이던 요소가 파괴도 반환도 안 되고 참조만 끊김 → 새 열린 질문. - [low]
NilHandler의setLength/setOffsetSource호출 순서가 같은 문서의 해제 순서 계약과 반대 → 뒤집음.ProcessedPreRef/PostRef핸들러도 같은 순서 오류가 이번 세션 이전부터 있어서 같이 고침. - [low]
architecture.md정정 배너가 원문을 "콜론"이 아니라 "콜백" 메서드로 오인용(정정하려는 문장의 뜻이 뒤집힘), ROADMAP 433행에 리네임 전 "대기 중/잠정 표기" 잔여,documentation-content-map.md가D스윕에서 누락.
도구/절차 메모
doc-check.py: 처음 돌렸을 때 ERROR 6건 — 전부 내가 절 제목을 바꾸는 바람에 다른 문서의 인용이 깨진 것과, ROADMAP blockquote 안에서 인용을 줄바꿈에 걸친 것(conventions.md가 이미 경고한 실패 모드를 그대로 밟음). 고쳐서 ERROR 0, WARN 8은 전부 이 세션 이전부터 있던 것.- 그 QA 문서는 지우지 않고 근거 기록으로 격하(상단 배너 교체) — 사용자 답변 원문이 그대로 남아 있어야 나중에 되짚을 수 있음.