Commit graph

7 commits

Author SHA1 Message Date
c6fdf1b348
qa: 구현 전 QA 5라운드 — 문항지·회신·전량 반영 + 감사 2라운드
4라운드 종결 때 "안 만든다"고 했던 5라운드를 사용자 요청으로 신설(205문항).
범위를 셋으로 좁힘 — (1) 4라운드에 문항이 아예 없던 영역(project-setup /
quad-types, 그리고 문서가 아니라 실제 커밋된 M1 코드), (2) 그 이후 확정된 것
(Detach/KeyGone/Owned/attachSlot 분해), (3) 큰 문서의 심화. 회신을 4차에 걸쳐
받아 전량 반영했고, 커밋 전 감사를 각도를 바꿔 2라운드 돌렸다.

주요 확정/역전:
- slot._detached lazy화, KeyGone엔 새 값 반환도 error,
  Owned=false에서 Detach는 _detached에 안 들어감(rawUnmount)
- Slot:Replace 신설 + rawReplace/rawAdd 의사코드 신설(문서에 정의가 없었음)
- raw* 인자를 index로 전부 통일 — 오래 열려 있던 캐비엇 종결.
  래핑은 raw* 바깥에서만(공개 표면 + settle), raw*는 물리 요소만 다룸
- 물리 조작을 주입 op로(mountInst/unmountInst/disposeInst, 이름 가칭) —
  base는 Parent를 모른다는 지적. mountInst는 0-based 절대 offset을 받음
- Dispatch.setLength에 anchor(생략 시 ownerKey) — 부기 키와 생명주기 앵커
  분리, 4라운드 D-56 역전(archive로)
- Dispatch.getOffsetAt 신설(pull) + 접두합 캐시(offsetDirtyFrom),
  setOffsetSource(None)은 얼리 리턴, None의 뜻을 "발행 채널 없음"으로 정정
- recompute가 owner 베이스에서 시작(중첩 offset이 부모 베이스를 못 받던
  결함), _baseObserver로 깊은 전파, Offset Source identity 재사용(포탈),
  bk.N or 0(빈 Slot 크래시)
- Effect(fn, ...deps) 확정 — Ref도 의존성(옛 "trailing args sugar 안 만듦"
  역전), Tween:Mapped, groupClaimKeys 키 = (inst, groupValue) → k
- 게이팅 먼저(M2로 앞당김) — 다만 대상이 Blocker가 아니라 공용 Gate 노드로
  바뀌었고, 설계는 사용자 지시로 다음 세션(M2 착수를 막는 유일한 항목)

새 research 둘: gate-primitive.md(다음 세션이 이어받을 재료),
state-epoch-validation.md(전파 중 Get이 섞인 값을 캐시하는 glitch — 정확성
결정이라 M3 전 결론 필요).

감사가 잡은 것 중 큰 것: 확정한 Owned가 Slot:List 시그니처에 배선이 안 돼
코드에 도달 못 하던 것, effect-plan.md의 역전 배너 없는 자기모순,
그리고 손대지 않은 문서(ROADMAP 백로그·debounce-throttle-plan)가 "Gate는
M3에서"로 남아 있던 사각지대.

doc-check.py ERROR 0. 상세는 qa-request/pre-implementation-qa-round5-followup.md
(A~K절, 마지막이 최신).

Co-authored-by: qwreey <me@qwreey.moe>
2026-08-21 18:19:58 +09:00
d5e5e1a1b9
fix: /code-review high 발견 2건 — plain-table :List 재마운트 크래시 등
quad-doc-auditor 6라운드 수렴 후 /code-review high로 diff 자체를 재검토한
결과. 감사자는 코퍼스 전체 정합성만 보고 diff 결함은 못 잡는다는 게
conventions.md가 이미 명시한 한계인데, 실제로 이번 diff 안의 결함이
나왔다.

- activateList의 재마운트 분기가 bindLifetime(physicalTarget, nil)로
  크래시할 수 있었음. _listObserver는 data가 reactive(State/Source)일 때만
  세팅되고 plain table data(문서가 지원하는 형태)면 영원히 nil인데,
  가드 없이 불렀음. 짝인 unmountSlotTree 쪽은 이미 방어돼 있었던 것과
  비대칭 — 가드 추가.
- "구독 시점" 절에 activateList의 옛 파라미터 이름 inst가 리네이밍 후에도
  남아 있어, 그 절만 읽는 구현자가 physicalTarget 리네이밍의 취지(owner
  키가 Slot일 수도 있는 문맥과의 혼동 방지)를 놓칠 위험 — 정정.

qa-request/pre-implementation-qa-round4-followup.md에 I-8로 기록.
doc-check.py ERROR 0.

Co-authored-by: qwreey <me@qwreey.moe>
2026-08-21 13:30:33 +09:00
4622fbeec8
qa: 반영 후 감사 6라운드 — 실제 크래시 3건 포함 18건 수정
커밋 9b7f847(Detach/KeyGone/Owned/attachSlot 분해) 반영 후 각도를 바꿔가며
quad-doc-auditor를 6라운드 돌린 결과. 라운드별 확실 발견 4/6/2/3/3/0으로
6라운드에서 새 발견 0건 — 수렴 확인 후 종료. 경위 전량은
qa-request/pre-implementation-qa-round4-followup.md의 I절이 소스.

트레이싱 라운드(4~5)가 잡은 실제 크래시 — 셋 다 Detach가 신설한 경로가
기존 불변식과 부딪히는데 그쪽이 안 고쳐진 것:

- I-1 (치명): rawDetach가 소유권을 유지하는데 재마운트는 rawAdd →
  claimOwner를 거치고, claimOwner는 같은 owner의 재클레임도 무조건 error다
  (2026-08-13 감사가 Slot{a,a}를 막으려고 넣은 것). 문서가 권장하는
  "prev를 그대로 반환하면 재마운트" 패턴이 그대로 죽었음. fromDetached
  플래그로 그 경로만 좁게 예외 처리.
- I-2: 재마운트된 자식 Slot이 activateList를 두 번 실행해 구독이 이중으로
  생기고 mounted/keyIndex 클로저 상태가 통째로 새로 만들어짐 → 멱등 가드.
  가드만으로는 :List 구독이 옛 physicalTarget에 앵커된 채 남아 포탈
  재마운트 후 조용히 멈추므로, _listObserver 핸들 보관 + 재앵커까지 처리.
- I-3: _detachCleanup이 releaseOwner를 안 불러 Owned=false 요소가 죽은
  Slot을 owner로 달고 남음 → 두 분기 공통으로 호출.
- I-6: 위 수정의 회귀 트레이싱 — destroySlotTree에 _listObserver 해제 누락,
  claimOwner의 옛 논증 두 문단이 fromDetached와 정면 모순, 소유권 예시
  코드가 C-4와 모순.
- I-7: 사용자가 별도 상의해 가져온 두 건 — _detachCleanup 설치를
  mountSlotTree → activateList로 이관(:List 없는 Slot마다 no-op Effect를
  트리 크기만큼 심고 있었음), activateList의 inst → physicalTarget 리네이밍.
  이관 근거가 멱등 가드 이전 동작을 전제하고 있어 가드 분기의 재앵커까지
  같이 반영. 이로써 _listObserver/_detachCleanup이 같은 범주로 통일됨.

I-4(materializeSlotTree 중 예외 시 Blocker 잔류)는 사용자 판단으로 pcall
없이 문서화만 — 아직 밟은 적 없는 경로이고 옛 단일 attachSlot에도 있었을
구조적 갭.

문서 정합성 라운드(1~3)에서 나온 것: slot-plan의 "값 교체는 비파괴" 잔존,
분해 완료 후에도 남아 있던 "논의 대기 중" 배너, attachSlot의 flush 루프를
가리키던 문장 5곳, README 색인 행이 2026-08-19에서 멈춰 있던 것,
qa-round4 문항지/followup의 "회신 대기" 상태줄, todos의 용어 목록 이중 소스,
dispatch-core의 raw* 일반 계약에 rawDetach 누락.

luau-test: 스파이크 01이 "재작성 필요" 마커를 단 채 done/에 남아 있어
STATUS.md 자신의 "폴더가 곧 상태" 규칙을 어기고 있었음 → rewrite-required/로
이동하고 개수 정정. "만들어야 할 스파이크" 절 신설(아직 파일조차 없는 실측
항목이 어느 폴더로도 표현되지 않아 구조적으로 잊히던 자리).

doc-check.py ERROR 0.

Co-authored-by: qwreey <me@qwreey.moe>
2026-08-21 13:20:42 +09:00
9b7f847014
design: Detach 보존 주체/KeyGone/Owned 확정 + attachSlot 분해
QA 4라운드 followup의 마지막 열린 항목(F-3)이 사용자 회신으로 전량
닫히면서 :List 요소 소유권 모델과 attachSlot 책임 분해를 base/에 반영.

- Detach 보존 주체를 userdata → slot._detached 필드로 전면 정정.
  근거는 gcconn 트릭 — detach된 quad-제작 Instance는 GC 폴백이 없어
  명시적 정리 경로가 필수인데 userdata는 :List에게 opaque라 처분 불가.
  재-Detach는 nop, prev 반환은 재마운트. raw 3형제(rawRemove/rawUnmount/
  rawDetach)로 "파괴하는가"와 "소유권을 놓는가"를 분리.
- KeyGone 센티널 신설 — 키가 사라진 자리도 조용히 처분하지 않고
  updateFn(KeyGone, 0, offset, prev, ud)로 한 번 더 묻는다. owner 사망
  시 최종 정리는 mountSlotTree가 거는 Effect가 담당.
- Owned 설치 플래그 신설 — Detach(사이클 단위)와 직교하는 축.
  state<Frame> 의미론 충돌(C-2)이 이걸로 닫힘.
- attachSlot을 materializeSlotTree(부기) + mountSlotTree(물리)로 분해.
  "부모에게 미는 길이는 최종값"(C6)과 "부기가 물리보다 먼저"(C7)가 한
  함수 안에선 동시 만족 불가라는 진단이 근거. 공개 표면은 두 줄짜리
  래퍼로 유지해 호출부 무변경. research/slot-attach-decomposition.md 확정.
- ROADMAP M6의 옛 Detach 서술 2건과 미결 마커 정정, question.md/todos.md
  해소 반영, session/2026-08-21-01 원문 + session-summary 색인 공백 4건 보강.

doc-check.py ERROR 0.

Co-authored-by: qwreey <me@qwreey.moe>
2026-08-21 11:55:11 +09:00
1e6cb0111d
design: QA 4라운드 3차 회신 반영 + attachSlot 분해 논의 자료 준비
확인받은 것 반영:
- Owned 설치 플래그 확정 — "누가 요소를 만들었는가"는 사이클마다 달라지는 게
  아니라 설치 시점 속성이라는 사용자 판단. Detach(사이클 판단)와 직교하는
  축이므로 반환값 계열에 unowned 센티널을 더 만들지 않는다. destroySlotTree/
  dispose도 이 플래그를 봐야 해서 클로저가 아닌 Slot 필드여야 함.
- props 순회를 일반화 for 한 번으로 정정 — flattened는 항상 평범한 Luau
  테이블(__pairs/__ipairs를 갈아끼운 ud가 들어올 경로가 없음)이라 옛 근거
  "다른 백엔드가 Lua 테이블이 아닌 자료구조로"는 inst엔 해당해도 flattened엔
  해당하지 않았다. 계약(배열 먼저)은 그대로, 구현만 1회 순회로. 스파이크 01은
  두 루프 버전이라 재작성 필요로 STATUS에 표시.

attachSlot 분해는 research/slot-attach-decomposition.md로 준비:
setLength를 flush 앞/뒤 어디에 둘지가 안 풀린 이유가 자리 선택이 아니라
"한 함수가 책임 일곱을 지고 있어서"라는 사용자 진단에 따라, 책임 목록과
순서 제약(전부 RC-1/RC-3/RC-4 등 실제로 밟은 버그가 출처)을 모으고 C6(길이
최종값은 flush 뒤에야 정해짐)와 C7(부기가 물리보다 먼저)이 단일 함수로는
동시 만족 불가능함을 보인 뒤 분해 후보 넷을 대조. 확정은 아무것도 안 함.

Co-authored-by: qwreey <me@qwreey.moe>
2026-08-21 11:10:42 +09:00
f36bdcbe5d
design: QA 4라운드 2차 회신 반영 — B절 확인분 + C절 결정 7건
B절(설명 보강 재질문) 전부 확인됨. 확인 과정에서 나온 보강:
- B-1: (A) 분기는 "교체"라 stack-down이 아니고 retractFrom만 스택을 역순으로
  푼다는 구분을 명시. "자기 아래는 이미 정리된 뒤" 보장도 retractFrom 한정이고,
  (A)에서 아래가 살아 있는 게 깜빡임 없는 갈아끼우기의 근거.
- B-3: 고아 체인이 실제로 어떻게 생기는지 가상 위반 예시(MaybeWrapHandler) 추가.
- B-4: "Brand는 데이터 타입에 부작용 없이 런타임 명시 타이핑을 하기 위한 것"을
  존재 이유로 명시하고, duck-typing 기각 근거를 정확성/안전성 둘로 분리.

C절 결정 반영:
- C-3: flatten의 정확한 형태 확정 — in-place 뮤테이션(클론 안 함),
  ProcessedModifier로 소진, 인라인 우선이 `~= nil` 하나로 성립. 단 주신 코드의
  반복 방향은 역순이어야 "나중 modifier가 우선"이 성립해서 그것만 정정(F-4-2).
- C-4: destroySlotTree의 명시적 releaseOwner 제거. 파괴된 걸 재사용하는 코드는
  그 자체로 버그이므로 "비결정적으로 실패"보다 "항상 실패"가 낫다.
- C-6: recompute의 sourceList[i] == nil을 skip에서 즉시 error로 승격. 재추적
  결과 도달 경로가 없으므로 관측되면 부기가 깨진 것.
- C-7: "부기가 물리 트리 조작보다 항상 먼저"를 일반 계약으로 승격. 빼기는 물리
  먼저/넣기는 부기 먼저가 같은 원칙(좁은 쪽이 먼저)의 두 얼굴이라는 것과,
  yield 금지 덕에 프레임 경계가 안 끼므로 진짜 근거는 "백엔드가 전제할 수
  있게 하나로 고정"이라는 것까지.

followup F절에 남은 것: KeyGone 홀드 + Owned 설치 플래그 설계 제안(F-3),
단일 일반화 for 전환 여부/flatten 반복 방향/setLength 위치(F-4).

Co-authored-by: qwreey <me@qwreey.moe>
2026-08-21 10:47:45 +09:00
9642cf8f18
qa: QA 4라운드 followup — 회신 처리 결과 + 재질문 정리
사용자 회신을 (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>
2026-08-20 00:57:19 +09:00