# 2026-08-18 — 구현 전 QA 결과를 `base/`에 일괄 반영 **요청**: "pre-implementation-qa 의 적용을 수행하자." `.claude/qa-request/pre-implementation-qa-round1.md`(같은 날 앞선 세션이 만든, `base/` 확정 문서를 사용자에게 문항으로 재심사한 결과)의 항목을 실제 문서에 반영한 세션. 그 문서는 "여기서 정정하지 않는다, 사용자 정정 회신이 오면 반영한다"고 적혀 있었지만 **각 항목에 이미 사용자 답변 원문과 논거가 붙어 있었고**, 사용자가 적용을 지시했으므로 그 답변을 정정 근거로 삼아 반영했다. ## 먼저 사용자에게 물은 네 가지 QA 문서가 "결론 없음"으로 남겨둔 항목 중, 임의로 정하면 안 되는 것만 `AskUserQuestion`으로 물었다(나머지는 답변 원문이 이미 방향을 확정함): 1. **SL-3 — `:List` reconcile의 `nil` 리턴** → *"PopOnly 확정. 다만 이름은 변경될 수 있음. 이름에 대해서는 더 생각해보아야함"* → 파괴가 기본으로 되돌리고 `PopOnly`(가칭)를 이번 설계에 넣음. 2. **D-7 — base Fallback Handler 등록 주체** → **quad-base 로드 시 등록으로 재역전**(2026-08-14의 역전을 다시 뒤집음). 3. **N-4/RF-4 — `None`/`nil` 배열 슬롯 처리 책임** → *"NoneHandler는 재귀만, NilHandler가 실질 담당"*. 4. **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 }`이면 탄다는 것. - **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**: `:List` reconcile의 `nil`/키 소멸은 다시 파괴, 값 교체와 `PopOnly`만 비파괴. `State` 교체가 언마운트인 것은 그대로 유지되게 세 경로를 표로 갈랐다(`:Single` sugar가 교체 경로를 타므로 자동으로 안전). - **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` 스윕에서 누락. ## 절차 변경 — 감사 루프를 병렬에서 순차로 (사용자 지침) 이번 라운드의 토큰 소비를 보고 사용자가 감사 절차 자체를 바꿨다: > "시간이 걸려도 상관 없으니, 차라리 병렬 에이전트를 덜 써줘. 그냥 한 턴에 > 하나씩만 사용하고, 0 이 나올때 까지 턴 수를 늘리는게 나아보임. 토큰을 > 너무 많이 소비해서 다른 작업을 못 하고, 세션 한도에 닿더라고." `conventions.md`의 감사 루프 절을 그에 맞게 재작성했다 — (1) **병렬 금지, 한 턴에 하나**(2026-08-16의 "최소 2개 병렬" 지침을 대체), (2) **프롬프트로 범위를 diff로 좁힐 것**(전 코퍼스 스윕은 오래 안 돌렸을 때만), 라운드마다 각도를 바꿔 병렬로 얻던 폭을 턴으로 얻는다, (3) 종료 조건은 **무발견 1회** (옛 "무발견 2연속"은 병렬 다패스 전제라 같이 완화), (4) 비용 때문에 중간에 멈출 때도 "몇 라운드에서 왜 멈췄는지" 반드시 보고. 같이 명문화한 것: **`/code-review`는 감사자를 대체하지 않는다** — 이번에 감사자 1패스(1건) 뒤 `/code-review high`가 10건을 더 잡았고 전부 유효했다. 보는 축이 달라서다(감사자=코퍼스 전체 정합성, code-review=diff 자체의 결함). `/code-review`는 사용자만 호출할 수 있으므로, 큰 변경 커밋 전엔 돌릴지 물어보는 것을 관례로 넣었다. ## 도구/절차 메모 - `doc-check.py`: 처음 돌렸을 때 ERROR 6건 — 전부 **내가 절 제목을 바꾸는 바람에 다른 문서의 인용이 깨진 것**과, ROADMAP blockquote 안에서 인용을 줄바꿈에 걸친 것(`conventions.md`가 이미 경고한 실패 모드를 그대로 밟음). 고쳐서 **ERROR 0**, WARN 8은 전부 이 세션 이전부터 있던 것. - 그 QA 문서는 지우지 않고 **근거 기록으로 격하**(상단 배너 교체) — 사용자 답변 원문이 그대로 남아 있어야 나중에 되짚을 수 있음.