8라운드가 써둔 지시서(-round9-brief.md)대로 커밋 9dd8213의 델타를 재트레이싱해 발견 18건(H-124~H-141)을 냈고(qa-request/pre-implementation-handtrace-round9.md), §4 문항 중 Q1~Q3를 사용자와 확정해 base/·ROADMAP에 반영했다. 결정의 소스는 -round9-followup.md(진행 표가 상태의 소스). 🔴 둘 다 luau 실측 재현. Q1 H-124 — recompute가 lengthList[i]를 되감기 판정보다 먼저 읽어, offset:Set 안의 사용자 코드가 커서 뒤 자리 수를 줄이면 sum += nil로 죽고 recomputeBlocker가 영구 On. 되감기 판정을 앞으로(continue), 읽기·누적은 되감지 않을 때만. Q2 H-125 — 재마운트 시 setOffsetSource가 slot.Offset을 바꾸는 순간 _baseObserver가 unbind 상태라 두 필드가 0으로 안 내려가 옛 베이스의 offsetCache[1]을 씀. 사용자 확정: Offset·_baseObserver를 Slot 생성자로(첫 마운트/재마운트 분기 소멸), materializeSlotTree는 blocker:On → bindLifetime → setOffsetSource, 파괴는 _destroyed 플래그 하나(핸들은 unbind만, mutate CRUD·:List·마운트 진입 error, Owned=false는 안 섬, 이중 dispose no-op). Q3 H-126/H-137/H-141 — element→index 맵이 Slot 층(slot._elemIndex)과 Dispatch 층(bk.tokens/indexOfToken)에 두 벌 있었고, 후자의 token은 사용자가 정한 적 없는 것(2026-08-25 /code-review가 발명해 사용자 인용문 옆에 앉아 있던 것). bk.indexOfElement 하나로 통일, setLength 5번째 인자 element, splice가 비운 자리는 세 배열 전부 처리. H-137 소멸. 부수: H-140(ROADMAP의 폐기된 "해제 시 slot.Offset = nil" 잔존) 정정, H-125 피해 범위를 "중첩 Slot의 Offset"으로 축소(유저 체인은 요소 인스턴스에 바인드돼 전파됨 — 실측), G각도로 "for d in seen do는 유효한 Luau가 아니다"가 거짓임을 확인, keyof<{}> 빈 Store 실측 클린. conventions.md 신설: "/code-review(그리고 메인 세션)가 내놓는 새 필드·인자· 이름·메커니즘은 발견이지 결정이 아니다" — 이 세션에서 메인 세션도 같은 실수를 세 번 했다(subject 인자 / Observer 위치 필드 = 기각된 Effect userdata 재개방 / 조회 클로저). 검증: quad-doc-auditor 6라운드(확실 1→1→3→1→1→0, base/ 본문 결함은 1라운드 이후 0건 — 나머지는 인덱스 레이어·인용처), doc-check ERROR 0. /code-review high는 Q4~Q10 반영 뒤 한 번에(같은 파일을 또 건드려 diff가 섞이므로 지금 체크포인트). Claude-Session: https://claude.ai/code/session_01F9zgJ4c4kDitAoQMm9qxKn Co-authored-by: qwreey <me@qwreey.moe>
235 lines
13 KiB
Markdown
235 lines
13 KiB
Markdown
# 구현 전 손 트레이싱 **9라운드** 감사 지시서
|
||
|
||
> **이 파일이 무엇인가**: 9라운드 감사자에게 그대로 주는 지시서다. 발견
|
||
> 보고가 아니라 그 **앞단**이고, 산출물은 별도 파일이다(§6).
|
||
> **[2026-08-26] 지시서를 저장소에 남기는 건 이번이 처음이다** — 7·8라운드
|
||
> 지시서는 대화에만 있었고 저장되지 않았다(8라운드 본문이 인용하는
|
||
> "감사 지시서 §2"가 코퍼스 어디에도 없는 이유). 앞으로 라운드마다
|
||
> `-roundN-brief.md`를 같이 남긴다.
|
||
|
||
당신은 Roblox 엔진용 DOMless UI 렌더러 **quad**의 설계 코퍼스를 감사한다.
|
||
저장소 루트는 이 세션의 작업 디렉토리다. **구현(M2 = 반응형 코어) 착수
|
||
직전**이고, 여기서 놓친 설계 결함은 구현 한참 뒤에 터져 M2/M3를 다시 짜는
|
||
비용이 된다.
|
||
|
||
---
|
||
|
||
## §0 먼저 읽을 것 (이 순서로)
|
||
|
||
1. `CLAUDE.md` → `.claude/conventions.md` / `.claude/project-context.md` /
|
||
`.claude/todos.md` — 프로젝트 관례와 현재 상태.
|
||
2. `.claude/qa-request/pre-implementation-handtrace-round8.md` — 8라운드
|
||
발견 원문(`H-107`~`H-123`). 특히 **§5(이상 없다고 확인한 것)** 와
|
||
**§6(남은 의심 / 못 본 것)**.
|
||
3. `.claude/qa-request/pre-implementation-handtrace-round8-followup.md` —
|
||
**이 라운드의 출발점.** 8라운드 결정 Q1~Q10과, 그 반영 뒤 돌린
|
||
`quad-doc-auditor` 11라운드 + `/code-review high` **7패스**의 기록.
|
||
`/code-review high`의 차수별 절(1차부터 7차까지)을 반드시 정독할 것 —
|
||
거기 적힌 **반복 실패 모드**가 당신의 사냥 목록이다(§3).
|
||
4. `ROADMAP.md`(M2/M3 체크리스트) / `.claude/question.md` /
|
||
`.claude/luau-test/STATUS.md`.
|
||
5. `base/`는 레인별 필요 범위만(§2). 전량 완독은 요구하지 않는다 — 8라운드
|
||
3차 패스가 이미 전량 완독했고 🔴가 0건이었다.
|
||
|
||
**이전 라운드**(1~7)는 필요할 때만 인용 자리를 부분 확인하라. 재심사
|
||
대상이 아니다.
|
||
|
||
---
|
||
|
||
## §1 이 라운드의 전제
|
||
|
||
8라운드 결정 반영과 그 뒤의 code-review 7패스 수정이 **전부 단일 커밋
|
||
`9dd8213`에 들어 있고, 그 결과물을 아무도 처음부터 트레이싱한 적이 없다.**
|
||
|
||
```
|
||
git show 9dd8213 --stat
|
||
git show 9dd8213 -- .claude/base/ ROADMAP.md
|
||
```
|
||
|
||
이게 이 라운드가 볼 델타의 **완전한 정의**다. `base/` 주요 증분:
|
||
`dispatch-core-plan +250` / `source-state-plan +160` / `slot-plan +139` /
|
||
`lifecycle-pattern +124` / `store-plan +117` / `effect-plan +103`.
|
||
|
||
**왜 이 델타가 특히 위험한가** — 같은 프로젝트가 낸 실측이 셋 있다.
|
||
|
||
- 8라운드의 🔴 다섯(`H-107`/`108`/`109`/`112`/`119`)은 **전부** "직전
|
||
라운드(7라운드)의 반영분이 서로 겹치는 자리"에서 나왔다. 개별 함수의
|
||
버그가 아니라, 하루 차로 확정된 결정들이 서로를 못 본 자리다.
|
||
- code-review 7패스의 HIGH 추이는 `1 → 1 → 0 → 1(설계 역전) → 0 → 3 → 0`
|
||
이다. **5차가 HIGH 0인데 6차에서 HIGH 3이 나왔고 전부 5차 수정이 만든
|
||
것**이었다. "직전 패스가 조용했다"는 수렴의 증거가 아니다. 7차 수정분은
|
||
**아무도 안 봤다.**
|
||
- 4차에서 설계 역전이 있었다 — `H-101`의 "새 필드를 안 만든다"가 뒤집혀
|
||
`bk.invalidAfter`가 `bk.offsetCacheValidUpTo` / `bk.offsetSetUpTo` **둘로
|
||
분리**됐고 40곳 넘게 치환됐다. 감사 사이클 **끝머리**에 들어온 구조
|
||
변경이라 가장 덜 검증됐다.
|
||
|
||
---
|
||
|
||
## §2 레인 — 우선순위 A > C > B
|
||
|
||
리포트를 레인별로 독립적으로 쓸 것. 예산이 모자라 B를 못 해도 A/C 결과가
|
||
그대로 쓸 수 있어야 한다.
|
||
|
||
### 레인 A (최우선) — `9dd8213` 델타의 상호 간섭
|
||
|
||
이번에 바뀐 계약들을 **서로 겹쳐서** 읽는다. 개별 문서가 자기 안에서
|
||
일관된지가 아니라, **두 결정이 만나는 자리에서 성립하는지**를 본다.
|
||
|
||
이번 라운드가 바꾼 계약(전량은 followup이 소스, 여기 나열은 진입점):
|
||
|
||
- `Ref` 콜백이 **`fn(value, ref)`** — 2번째 인자가 곧 출처 `Epoch`.
|
||
훅 슈가의 `guard(fn)`도 2-인자가 됐다.
|
||
- Observer `fn`이 **세 자리** `fn(targetState, self, emitFrom)` +
|
||
`observer._state` **강참조**.
|
||
- `WeakSubscribe`도 `.Subscribed = true`를 세운다.
|
||
- `Subscribe`/`WeakUnsubscribe`/`Unsubscribe`가 **전부 fail-fast**
|
||
("해제는 건 경로로 푼다"). *idempotent* 서술은 세 번에 걸쳐 지워졌다 —
|
||
**네 번째 사본이 남아 있는지 전수하라.**
|
||
- 예약 키 진단 타입 함수가 `CheckReservedKeys<keyof<T>>`.
|
||
- **부기 필드 2분할** — `offsetCacheValidUpTo`(올리는 쪽: `getOffsetAt`,
|
||
어디서 불리든) / `offsetSetUpTo`(올리는 쪽: `recompute`만). 무효화는 둘 다
|
||
내린다. 옛 이름 `invalidAfter`는 **의도적으로 전멸**시켰다(단, **인용문·
|
||
절 제목·정정 배너 안에서는 옛 이름이 정본**이다).
|
||
- 무효화 인덱스가 **세 가지 → 네 가지**(splice 계열은 `j - 1`,
|
||
`rawMove`/`rawSwap` 계열 행 신설, `rawExtract`는 **조건부**).
|
||
- `recompute` 재진입 게이트가 **명시 호출부 전부**로 확대(`raw*` 3형제,
|
||
`_baseObserver`, `:List` 활성화 꼬리, `mountSlotTree` 꼬리, `Dispatch.drive`
|
||
꼬리, 일반 배치 계약).
|
||
- Store 생성자의 `defaults` `isSource` 화이트리스트 검증(`error` level 2),
|
||
`isModifier` 가드가 `Source` 생성자로 이동.
|
||
|
||
**특히 겹쳐 볼 것**(과거에 실제로 여기서 🔴가 나왔다):
|
||
|
||
- `H-113`(splice `-1`) × `H-119`(`_baseObserver`가 마커를 0으로) —
|
||
이 둘이 겹쳐 `math.max(…, 1)` 클램프가 필요해졌다. **같은 종류의
|
||
0-하한/끝-초과 경로가 다른 조합에 또 있는가.**
|
||
- 부기 필드 분할 × 무효화 4규칙 × `bk.N` 예외(`rawSplice`/`rawClear`/
|
||
조건부 `rawExtract`) — 세 개를 동시에 만족하는 CRUD 시퀀스를 **실제 값으로**
|
||
돌려라. 특히 **한 콜백에서 CRUD를 두 번**(예: `Remove` 뒤 `Add`) 하는
|
||
2-연산 경로. 4차 HIGH가 정확히 그 형태였다.
|
||
- `Ref` 2-인자 × 훅 슈가 × `Effect`의 dep 종류별 클로저 둘.
|
||
- fail-fast 3종 × `Effect` cleanup × leaf 사망 경로.
|
||
|
||
### 레인 C — 참조 구현을 갱신해 **실제로 돌리기**
|
||
|
||
`.claude/audit/handtrace-round7-reference-impl/`에 7라운드가 만든 M2 코어 +
|
||
M2→M3 경계 참조 구현과 스파이크(`spikes/`)가 있다. **그 계약은 8라운드
|
||
이후로 낡았다.**
|
||
|
||
1. 그 README의 원문 대조표를 따라 **지금의 `base/` 서술로 갱신**하라
|
||
(계약 4변경 + 부기 필드 분할 + 무효화 4규칙 + 재진입 게이트 확대).
|
||
2. `luau` / `luau-analyze`로 돌려라.
|
||
3. **⚠️ 참조 구현은 확정 의사코드의 *전사물*이라 그 자체가 틀렸을 수
|
||
있다.** 재실행은 검증이 아니다 — 발견을 판정할 땐 `base/` 원문과 줄
|
||
단위로 대조하라. 반대로 **참조 구현이 안 돌아가면 그건 거의 항상 문서의
|
||
결함**이다(7차 code-review가 `for d in seen do`가 유효한 Luau가 아니라는
|
||
걸 이렇게 잡았다).
|
||
|
||
실행 환경:
|
||
- 저장소 테스트는 **반드시 `./scripts/test.sh`**. `luau` CLI가 심볼릭 링크를
|
||
못 타는데 pesde 워크스페이스 링크가 전부 심볼릭이라, 그냥 `luau`로 돌리면
|
||
스모크가 죽고 `luau-analyze`는 **조용히 통과**한다(거짓 클린).
|
||
- 독립 스파이크는 스크래치패드에 만들어 `luau <파일>` /
|
||
`luau-analyze <파일>`로 돌려라.
|
||
|
||
### 레인 B — M5+ 값 단위 트레이싱 (첫 시도)
|
||
|
||
8라운드 §6이 남긴 유일한 실질 공백이다. **문서 정독은 됐지만 의사코드를
|
||
실제 값으로 돌려본 적이 한 번도 없다**:
|
||
|
||
- 그룹 `Attribute` 위임 체인(`attribute-plan.md`)
|
||
- `D` 생성자 (`ui-shorthand-plan.md`)
|
||
- 숏핸드 → `PropertyHandler` 위임 (`ui-shorthand-plan.md` ×
|
||
`dispatch-core-plan.md`)
|
||
- `:List` reconcile의 **실제 값 대입** (`slot-plan.md`)
|
||
|
||
구체적인 인스턴스 트리와 구체적인 값으로 한 사이클을 손으로 돌려라
|
||
("계약이 이어진다"는 수준이 아니라 변수마다 값을 적으면서).
|
||
|
||
---
|
||
|
||
## §3 사냥 목록 — 이 코퍼스에서 **반복 관측된** 실패 모드
|
||
|
||
followup의 code-review 절이 명시적으로 남긴 것들이다. 델타를 읽을 때 이
|
||
패턴을 **능동적으로** 찾아라.
|
||
|
||
1. **⭐ 토큰만 바꾸고 그 토큰이 든 문장을 안 읽음** — 이 세션에서만 **네 번**
|
||
반복됐다(`_observers`→`_deps`, `CheckReserved`→`CheckReservedKeys`,
|
||
`invalidAfter`→`offsetSetUpTo`, 그리고 역사 인용문 오염). 이름이 바뀌었는데
|
||
**그 이름이 서술하던 동작·근거·불변식이 옛것 그대로**인 자리를 찾아라.
|
||
2. **전역 치환이 인용문·절 제목·정정 배너를 오염** — 그 셋은 *과거에 무엇이라
|
||
적혀 있었는가*를 보존하는 게 목적이다. `H-101` 인용문이 새 필드 이름으로
|
||
바뀌어 "새 필드를 안 만든다"와 자기모순이 된 사례가 실제로 있었다.
|
||
**반대 방향도 보라** — 되돌리다 살아 있는 서술까지 옛 이름으로 돌린 자리.
|
||
3. **표에 행만 넣고 헤딩/산문/개수는 그대로** — "세 가지"라고 쓰인 채 표엔
|
||
네 행. 두 번 반복됐고(2차/3차), `ROADMAP` 체크리스트에도 번졌다.
|
||
4. **폐기 블록을 만지면 오히려 해로움** — 죽은 문단에 이름만 고치거나 날짜
|
||
마커를 찍으면 **갓 정비된 것처럼** 보여 구현자가 더 믿는다. 폐기 배너
|
||
아래 본문이 최근에 수정된 흔적이 있는지 보라.
|
||
5. **한 곳에서 고친 거짓 전제가 새로 쓰는 글에서 되살아남** —
|
||
*"Store는 `Source`를 만들지 않는다"*가 3차에 세 곳에서 고쳐졌는데 5차에
|
||
새 블록에 다시 들어갔다. **이미 거짓으로 판정된 문장의 새 사본**을 전수하라.
|
||
6. **같은 계약의 N번째 사본** — *idempotent* 서술이 1차·6차·7차에 걸쳐
|
||
세 번 지워졌다. 하나를 고쳤으면 `grep`으로 전수하라.
|
||
7. **`ROADMAP.md` 체크박스가 `base/`보다 낡음** — 7차의 2번이 그랬다.
|
||
**구현자가 실제로 보는 자리**라 심각도가 높다.
|
||
|
||
---
|
||
|
||
## §4 각도 (8라운드와 같은 문자를 유지 — 상호참조용)
|
||
|
||
- **A. 반영분 재트레이싱** (최우선, 레인 A)
|
||
- **B. 엔드투엔드 체인** — Store→State→Observer/Effect→Gate/Blocker→
|
||
`setLength`/`recompute`, 그리고 **M5+**(레인 B)
|
||
- **C. 확정 의사코드/타입 실측** (레인 C)
|
||
- **D. 구현 순서 시뮬레이션** — `ROADMAP.md` M2 체크박스를 위에서부터
|
||
실제로 짜는 시뮬레이션. **체크박스만 보고 짰을 때 성립하는가**(7차 2번이
|
||
여기서 나왔다)
|
||
- **E. 공개 API 오용 진단** — 사용자가 틀리게 썼을 때 조용히 통과하는가
|
||
- **F. 코퍼스 미다룸 영역**
|
||
- **G. Luau 사실 주장 재확인** — 문서가 "확인했다"고 적은 언어 동작을 실제로
|
||
걸어보기
|
||
- **H. 비용 서술 점검**
|
||
|
||
---
|
||
|
||
## §5 규칙 (엄수)
|
||
|
||
1. **저장소를 수정하지 마라.** 산출물은 리포트 파일 **하나**뿐이다.
|
||
스파이크·참조 구현 갱신본은 스크래치패드에 만들고, 발견의 근거는 리포트
|
||
항목 안에 **인라인으로 전사**하라(파일이 유실돼도 재현 가능하게).
|
||
2. **`git stash`를 절대 쓰지 마라.** 과거 실동에서 메인 세션의 스테이지가
|
||
반복적으로 풀렸다. 과거 상태가 필요하면 `git show <ref>:<경로>` /
|
||
`git diff <ref> -- <경로>`.
|
||
3. **아무것도 반영하지 마라.** 판정은 사용자가 이 리포트를 보고 한다.
|
||
4. **8라운드까지의 결정은 뒤집지 않는 것을 기본으로 하라.** 뒤집어야 한다고
|
||
보면, **그 결정의 어느 추론이 틀렸는지**를 항목 안에서 지목하라
|
||
(근거 없이 "다시 생각해보니"는 안 된다).
|
||
5. **발견 번호는 `H-124`부터** 이어서 매긴다(라운드를 가로질러 연속).
|
||
심각도는 🔴(M2 착수 전 결정 필요) / 🟡(계약 결정) / 🟢(문서 정합·문서화).
|
||
6. `.claude/audit/`의 참조 구현은 **참고용**이다 — 재실행이 곧 검증이 아니다
|
||
(§2 레인 C의 ⚠️).
|
||
7. **확신이 안 서면 발견으로 올리지 말고 §6 "남은 의심"에 적어라.**
|
||
반대로, 확인해서 **이상 없었던 것**은 §5에 적어라 — 다음 라운드가 같은
|
||
자리를 다시 파지 않도록.
|
||
8. **못 본 범위를 §6에 정직하게 선언하라.** 리포트가 "감사 통과"로
|
||
읽히면 안 된다.
|
||
|
||
---
|
||
|
||
## §6 산출물
|
||
|
||
`.claude/qa-request/pre-implementation-handtrace-round9.md` 하나. 구성:
|
||
|
||
- **머리말** — 무엇인가 / 쓴 각도 / 실제로 본 범위 / 읽는 순서
|
||
- **요약 표** — `| 번호 | 심각도 | 한 줄 | 주 대상 | 성격 | 실측 |`
|
||
- **상세** — 항목마다: 무엇이 문제인가 / **어떻게 재현·트레이싱했는가**
|
||
(값 단위로) / 어느 문서 어느 줄이 근거인가 / 그대로 구현하면 무슨 일이
|
||
일어나는가 / (있으면) 실측 출력 전사
|
||
- **§4 사용자 결정이 필요한 것** — 배치 회신용. 문항마다 선택지와
|
||
**당신의 권고**를 달아라. 사용자는 이걸 위에서부터 답한다
|
||
- **§5 이상 없다고 확인한 것**
|
||
- **§6 남은 의심 / 못 본 것**
|
||
|
||
문서는 **한국어**로 쓴다(사용자가 읽는 문서). 코드·식별자는 원문 그대로.
|