사용자 회신을 (a) 바로 반영 (b) 설명 부족으로 재질문 (c) 사용자 판단 필요 (d) 조사로 닫힘으로 갈라 followup 문서로 정리했다. A절(반영 완료)은 앞 커밋에서 이미 base/에 들어갔고, 이 커밋은 그 기록과 남은 항목이다. 가장 큰 두 미결: - C-1 KeyGone 센티널 — 키 소멸 시 updateFn을 한 번 더 불러 처분을 묻는 사용자 제안. SL-45의 "파괴도 반환도 안 되고 참조만 끊기는" 상태를 정확히 메우지만, 반환값 의미/userdata 수명/소멸 루프 순회 대상/index 인자 넷이 안 정해지면 구현이 못 나감. - C-2 "밀려난 prev는 dispose" vs state<Frame> 의미론 충돌 — 회신의 SL-43과 SL-51이 서로 반대를 가리켰는데, 따져보니 둘 다 맞고 갈리는 축이 "누가 그 요소를 만들었는가"였다. :List의 updateFn이 만든 건 reconcile이 지워야 하고 (만든 쪽이 자기 손으로 못 지움), Slot:Add(state) sugar로 온 건 사용자 소유라 죽이면 안 됨. 지금 설계엔 후자를 표현할 방법이 없어 선택지 셋을 올리고 per-installation 소유권 옵션을 추천. 부수로 사용자가 문서에 아예 없던 갭 둘을 찾아냄 — Attribute 생성자 자신의 이름 겹침 정책(앞 커밋에서 "뒤가 이김"으로 명시)과, flatten이 소진한 Modifier 자리 처리(C-3, ProcessedModifier 센티널 vs flatten 압축). Co-authored-by: qwreey <me@qwreey.moe>
7.7 KiB
7.7 KiB
2026-08-20-01 — QA 4라운드 회신 1차 처리 + 업스트림 스캐폴딩 병합
한 줄: 사용자가 남긴 pre-implementation-qa-round4-response.md를 받아
판단이 명확한 것 20여 건을 base/에 반영하고, 나머지를
pre-implementation-qa-round4-followup.md로 정리했다. 같은 세션에 업스트림
12커밋(M0 검증 + M1 스캐폴딩)을 pull해 문항지 커밋을 그 위로 rebase했다.
순서
- 업스트림 병합 먼저 —
git fetch결과origin/main이 12커밋 앞서 있었음(pesde 전환, mise/selene, RunInit 재설계, quad-types/type-version-check 신설, M0 스파이크 21~23 추가, ROADMAP/CLAUDE.md stale 정정). 전 세션 산출물(QA 4라운드 문항지)을 먼저 커밋한 뒤git rebase origin/main— 충돌 없이 통과(문항지가 신규 파일이고, 인덱스 2줄만 수정했기 때문). - 회신 읽고 분류 — 회신은 문항 번호별로 자유서술이라, (a) 바로 반영, (b) 설명 보강 후 재질문, (c) 사용자 판단 필요, (d) 조사해서 답이 나옴으로 갈랐다.
- (a)만 문서에 반영 — (b)/(c)는 손대지 않고 followup에만 적었다.
반영한 것 중 설계가 실제로 바뀐 것
목록 전체는 followup A절이 소스(여기서 반복 안 함). 그 중 "표현 정정"이 아니라 동작이 달라지는 것만:
SL-75/D-60— 언마운트 시slot.Offset = nil금지. 옛 서술은 "마운트 전 상태로 복원"이었는데, 사용자가 포탈이 깨진다고 지적. 이유가 깔끔했다 —updateFn이layoutOrder:With(offset):Compute(...)처럼 그 Source를 파생 그래프에 엮어둔 상태에서 필드를 갈아치우면, 그 구독은 옛 Source를 계속 보는데 Slot은 새 Source를 등록하게 되어 영영 갈라진다. 재마운트가 "값이 다시 채워지는 일"이 아니라 "연결이 끊긴 채 조용히 멈추는 일"이 되는 것. 부수로Slot.Offset의 마운트 전 기본값도nil→0.E-11— leaf 바인딩된 Effect엔:Unsubscribe()가 아예 안 먹음. 옛 서술의 "(3) 이후 leaf가 실제로 죽어도 중복 호출되지 않는다"가 사용자 눈에 이상해 보였는데, 따져보니 성립할 수 없는 문장이었다 — 이중 바인딩 게이트가:Subscribe()(전역)와bindLifetime(leaf)을 상호 배타로 막아두므로:Subscribe()된 핸들은 애초에 leaf에 붙어 있을 수가 없고, "그 leaf가 죽는" 사건 자체가 없다. Observer와 같은 규칙으로 통일하는 것으로 정리.AT-20— 생존 이름 최적화는 "안 하는" 게 아니라 "못 하는" 것. 옛 서술은 "이론상 가능하지만 부품이 늘어나서 기본은 균일 철거"였는데, 사용자 지적대로 이름이 같아도 값이 바뀌었을 수 있어 이름 목록 비교만으로는 아무것도 못 건너뛴다. 값을 비교하려면:Get()이 필요하고 그건 State 계약 위반 — 즉 설계 원칙상 애초에 막힌 길이다.Tag가 되는 이유(자원이 이름 집합이라 겹쳐도 합집합)와의 대조까지 같이 적었다.D-56—bindLifetime의 첫 인자가 Instance가 아닐 수 있다.Dispatch.setLength(ownerKey, ...)의ownerKey가 Slot-in-Slot에선 Slot 자신인데, 구현 스케치는InstData에서 gcconn을 찾는 걸 전제하고 있었다. gcconn 트릭은 엔진 객체에만 걸 수 있으므로 백엔드가 별도 경로를 제공해야 한다는 요구사항으로 신설.isBoundAlive에 세 번째 분기가 필요하다는 것 (지금은 gcconn 없으면.Subscribed폴백으로 떨어져 살아있는데canBound가 참으로 잘못 나옴)까지 같이 짚었다.
사용자가 새로 찾아낸 갭 — 문서에 아예 없던 것 둘
Attribute(a, b, ...)생성자의 이름 겹침 정책이 어디에도 없었다.Merged(error)/Overridden(뒤가 이김)만 적혀 있었고, 생성자가 인자들을 하나의 이름 맵으로 평탄화하는데 겹치면 어떻게 되는지가 공백. 배열 순서 규칙과 결을 맞춰 뒤가 이김으로 명시했다.- flatten이 소진한 Modifier 자리를 어떻게 하는지가 없었다(
M-2). "isModifier(v)인 항목만 필드를 뽑아 merge"만 적혀 있고 그 자리를 지우는지 센티널로 채우는지가 공백 — 그냥 지우면PreRefpre-pass가ProcessedPreRef로 소진하는 바로 그 이유(sparse 테이블)에 걸린다. 사용자는ProcessedModifier센티널을 제안했고, 나는 flatten이 어차피 새 테이블을 만드는 순수 변환이라 압축(compact)이 더 싸다고 봐서 두 안을 followup C-3에 나란히 올렸다.
followup으로 넘긴 것 중 가장 큰 둘
C-1KeyGone센티널 — 사용자 제안(키 소멸 시updateFn을 한 번 더 불러 처분을 묻기)이SL-45의 어정쩡한 상태를 정확히 메운다. 다만 반환값 의미/userdata수명/소멸 루프 순회 대상/index인자 넷이 안 정해지면 구현이 못 나가서 그대로 물었다.C-2"밀려난prev는 dispose" vsstate<Frame>의미론 — 회신의 두 답변(SL-43,SL-51)이 서로 반대를 가리켰다. 따져보니 **둘 다 맞고 갈리는 축이 "누가 그 요소를 만들었는가"**였다 —:List의updateFn이 만든 것은 reconcile이 지워줘야 하고(만든 쪽이 자기 손으로 못 지움),Slot:Add(state)sugar로 들어온 것은 사용자 소유라 죽이면 안 된다. 지금 설계엔 후자를 표현할 방법이 없어서(Detach는 교체와 같이 못 씀) 선택지 셋을 제시했고, per-installation 소유권 옵션을 추천했다 — 갈리는 기준이 per-cycle이 아니라 설치 단위 속성이기 때문.
조사만으로 닫은 것
E-10(dedup 대칭) — 의사코드를 다시 따라가니bindLifetime(내부 Observer cascade 포함)이old ~= v분기 안에,unbindLifetime이nextValue ~= v분기 안에 있어 이미 대칭이었다. 사용자가 말한 "relate로 old를 들고 있다가 process에서 조회해 같으면 dedup"이 정확히 지금 코드다. 설계 갭 없음, 구현 시 회귀 확인만.D-10— 사용자 지적("Luau 일반화 for가 그냥 지원함")이 맞고, 문항이 "M0에서 순회 동작을 검증"이라 적어 언어 동작을 의심하는 것처럼 읽힌 게 문제였다. 스파이크01이 검증한 건 언어가 아니라 우리가 짠 두 패스 드라이버이고, 두 패스를 명시하는 진짜 이유는 이식성이다. 업스트림에서 이미 통과했으므로 base의 "M0에서 검증할 것"도 닫았다.SL-76— 사용자 의문("해제에서 nil이 관측될 일이 없다")대로 지금 설계에서 도달 경로를 못 찾았다.bk.N정의 확정 + Blocker 게이팅 +spliceArraysDown의 압축이 각각 그 경로를 막는다. 그래서 "관대한 skip"을 유지/삭제/error 승격 중 고르는 문제로 바꿔 올렸고 error 승격을 추천했다.
안 한 것
- 감사 루프 안 돌림 — 이번 변경이 크긴 하지만 아직 회신 처리가 절반
(B/C절이 남음)이라, 감사는 그 라운드까지 끝난 뒤 한 번에 도는 게 맞다고 봄.
doc-check.py는 매 편집 후 돌려 ERROR 0 유지. - 5라운드 문항지 — 업스트림이 들여온 신규 문서 둘(
project-setup-plan.md,quad-types-plan.md)과 이번에 내용이 늘어난architecture.md/typing-limits.md는 문항이 아예 없다. followup D절에 적어두고 사용자 지시 대기.