# 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했다.

## 순서

1. **업스트림 병합 먼저** — `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줄만 수정했기 때문).
2. **회신 읽고 분류** — 회신은 문항 번호별로 자유서술이라, (a) 바로 반영,
   (b) 설명 보강 후 재질문, (c) 사용자 판단 필요, (d) 조사해서 답이 나옴으로
   갈랐다.
3. **(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`가
  참으로 잘못 나옴**)까지 같이 짚었다.

## 사용자가 새로 찾아낸 갭 — 문서에 아예 없던 것 둘

1. **`Attribute(a, b, ...)` 생성자의 이름 겹침 정책이 어디에도 없었다.**
   `Merged`(error)/`Overridden`(뒤가 이김)만 적혀 있었고, 생성자가 인자들을
   하나의 이름 맵으로 평탄화하는데 겹치면 어떻게 되는지가 공백. 배열 순서
   규칙과 결을 맞춰 **뒤가 이김**으로 명시했다.
2. **flatten이 소진한 Modifier 자리를 어떻게 하는지가 없었다**(`M-2`).
   "`isModifier(v)`인 항목만 필드를 뽑아 merge"만 적혀 있고 그 자리를 지우는지
   센티널로 채우는지가 공백 — 그냥 지우면 `PreRef` pre-pass가 `ProcessedPreRef`로
   소진하는 바로 그 이유(sparse 테이블)에 걸린다. 사용자는 `ProcessedModifier`
   센티널을 제안했고, 나는 **flatten이 어차피 새 테이블을 만드는 순수 변환이라
   압축(compact)이 더 싸다**고 봐서 두 안을 followup C-3에 나란히 올렸다.

## followup으로 넘긴 것 중 가장 큰 둘

- **`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로 들어온 것은 사용자 소유라 죽이면 안 된다. 지금 설계엔 후자를 표현할
  방법이 없어서(‌`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절에 적어두고 **사용자 지시 대기**.
