Commit graph

7 commits

Author SHA1 Message Date
56f8269236
design: M2/M3 마일스톤 순서 교체 — 반응형 코어를 먼저, 디스패치를 그 위에
`question.md`에 열려 있던 마지막 항목(마일스톤 경계)을 사용자가
**(a) 순서 교체**로 닫았다 — 이제 **M2 = 반응형 코어(Source/State/Store),
M3 = 디스패치 엔진**이다. 결정과 기각된 선택지는
`archive/question-resolved.md`의 "마일스톤 경계" 절, 경위와 시행착오는
`session/2026-08-24-02-milestone-order-swap.md`.

## 왜 (a)인가

의존이 양방향처럼 보였지만 실제로는 한 방향이었다 — 디스패치→반응형은
*본체* 의존(`setLength`가 `State<number>`를, `setOffsetSource`가
`Source<number>`를 받고 `recompute`가 `offset:Set()`을 부름)이라 우회
불가고, 반대는 *등록 표면* 의존(핸들러 셋)이라 미루면 그만이다. 옛
순서로는 디스패치 마일스톤의 `mock 대상 테스트` 체크박스조차 State 없이
불가능했고, 반응형 코어는 순수 Lua라 `luau`만으로 단독 테스트가 된다.
(b) 분할은 기존 `M2` 참조 전부를 "M2a인가 M2b인가"로 만들어 오히려 비싸고,
(c) 유지는 "순서의 소스는 `ROADMAP.md`"라는 원칙을 스스로 무효화한다.

## 그냥 맞바꾸기로는 안 끝났다

- **`Brand`/`Relate`/`LifetimeHandle`(인터페이스)이 M2 앞머리
  `### 공통 기반` 절로** — 반응형이 이 셋을 먼저 요구한다(`Source`가
  `SourceBrand`+`EpochBrand`에 등록되고, State 전파 루프가 매 발화마다
  `canExecute`를 부르며, 그 판정이 `Relate` 위에 얹힌다). 셋 다 State-free
  이자 dispatch-free라 어느 쪽에도 안 걸린다.
- **2026-08-22에 디스패치로 앞당겼던 `EpochMap`/`GateNode`/`Blocker`가
  M2로 복귀** — 앞당길 이유 자체가 순서 교체로 사라졌다. "게이팅 먼저"는
  그대로 지켜진다(게이팅이 디스패치보다 먼저 지어진다).
- **Observer/Effect 동적 경로 가드 등록과 `ObserverEffectLeafHandler`는
  M3로** — 둘 다 "핸들러를 등록한다"뿐이라 본체와 잘라내기 쉽다. 이것이
  M2가 M3에 개념상 지던 유일한 의존이고, 미뤘으므로 빌드 순서엔 역방향
  간선이 없다.
- **`EpochMap`이 State 본체보다 앞으로**(감사 5라운드 발견) — State가
  `valueEpochMap`/`emitEpochMap` 둘을 컴포지션하므로 옛 배치로는
  `State.luau`를 못 짠다. 문서 자신이 *"아래 `Source.luau`/`State.luau`가
  이걸 전제로"*라 쓰면서 그 둘이 위에 있는 형태로 증거를 남기고 있었다.

## 대가 하나 — 게이트 둘이 "바로 다음"으로 올라왔다

`question.md` 낮은 우선순위에 있던 **중간 State GC 미검증**과
**`store:GetDynamic` 위치**가 원래 반응형(옛 M3)의 게이트였는데, 반응형이
M2가 되면서 착수 직전 항목이 됐다 — 최우선 절로 승격했다. 순수 *설계*
결정 대기는 여전히 0건이다(하나는 실측 미완, 하나는 표면 위치 선택).

## 번호 재부여의 경계

라이브 문서의 `M2`/`M3` 참조 248건은 전량 새 번호로 맞췄다(코퍼스의 참조가
거의 전부 "그 내용이 사는 마일스톤"을 가리켜 기계적 맞교환으로 의미가
보존됨). **`session/`·`archive/`·`qa-request/`는 히스토리라 소급 수정하지
않았다** — 그 문서들의 `M2`/`M3`는 옛 의미(M2=디스패치, M3=반응형)이고,
이 경고를 인덱스 다섯 곳에 박아뒀다.

일괄 치환에 `\bM([23])\b`를 썼다가 Python `\w`가 유니코드라 한글이 붙은
93건(`M2는`/`M2로`)이 안 바뀌는 실수를 냈고, `git show HEAD:<경로>`로
되돌린 뒤 ASCII 경계 lookaround로 재실행했다. 그 부수로 `Relate`/
`LifetimeHandle` 참조들은 구 M2 → 신 M2로 두 번 옮겨져 **번호가 우연히
보존**됐다는 것도 드러났다(치환하면 안 되는 자리 — 감사가 잡아 되돌렸다).

## 검증

`quad-doc-auditor` 루프가 라운드마다 각도를 바꿔 **7라운드에서 새 발견
0건으로 수렴**(10→8→1→1→2→2→0). 가장 값이 큰 건 5라운드(구현 순서
시뮬레이션)로, 위 `EpochMap` 배치 오류를 잡았고 **순서 교체의 전제 자체도
검증**했다(새 M2 전체를 디스패치 심볼로 훑어 "가드 등록 둘 말고는 없음").

**수렴 뒤 사용자가 돌린 `/code-review high`가 5건을 더 잡았고 전부 유효** —
넷이 *"라벨은 치환됐는데 그 라벨을 설명하던 산문이 안 고쳐진"* 종류였고,
그중 하나는 `research/` 안의 **히스토리 블록**이 치환을 맞아 원래 논거가
문장 그대로 거짓이 된 것이었다(소급 수정 제외 대상을 세 폴더로만 잡은 게
샜다). `conventions.md`의 *"`/code-review`는 감사자를 대체하지 않는다"*가
또 재확인됐다. `doc-check.py` ERROR 0.

Co-authored-by: qwreey <me@qwreey.moe>
Claude-Session: https://claude.ai/code/session_01Jjrec9xAS7TZstMx5gi3cm
2026-08-25 00:29:15 +09:00
19cd046275
design: 손 트레이싱 6라운드 전량 처리·반영 — H-1~H-54 확정, M2/M3 설계 게이트 해소
사용자 요청("같이 하나하나 처리해나가보자. 질문 모드로 계속 물어보며")으로
발견 보고 `H-1`~`H-54`를 문항지로 만들지 않고 **갈래 선택이 필요한 것만 급한
순서로** 물어 전량 결정하고 `base/` 24개 문서에 반영했다. 결정과 근거는
`qa-request/pre-implementation-handtrace-round6-followup.md`가 소스이고,
진행 경위와 사용자 발언 원문은
`session/2026-08-24-01-handtrace-round6-resolution.md`.

**M2/M3를 막던 것이 전부 닫혔다** — 말단 핸들러 4종의 `setLength`/
`setOffsetSource` 미등록(`H-39`), `New(): Quad`가 닫힌 타입이라 `quad.Dispatch`가
타입에러인 것(`H-25`), `Effect`의 leaf 사망 cleanup 배선 부재(`H-11`),
`:List`의 좌표계 결함 둘(`H-1`/`H-2`).

## 구조가 바뀐 것 넷

- **`slot._elemIndex`**(물리 요소→인덱스 역방향 맵) 신설로 `indexOfRaw`가 O(1)
  기본 경로가 되고, `:List`의 `keyIndex`는 **단순 키 집합(`prevKeys`)**으로
  강등. 사용자 역제안 — *"raw* 가 층위를 알아야할 이유를 모르겠는 상태 …
  realElem->index 해시맵을 만들어주고, index 밀고 당기는 동작에서 이걸 같이
  업데이트해주는 편이"*. 맵을 `_elements`와 같은 층에 두니 층 분리가 오히려
  깨끗해졌다
- **`_mounted`가 "물리 인스턴스 유무"만 뜻하게 좁혀지고 `slot._physicalTarget`
  신설** — 상태가 셋이 됐다(미실체화/실체화/마운트). `raw*`는 부기를 실체화
  시점부터 항상 하고 `native*`만 가른다. 그래야 최초 population 중
  `getOffsetAt`이 성립해 `updateFn`의 `index`를 거기서 뽑을 수 있다
- **`Ref.Callbacks`가 해시맵 셋 + `:Uncallback`**(사용자 발견) — 해제가 O(1)이
  되고 `ref-plan.md`의 `#t` border 실측 항목이 폐기됐다
- **`blocker:Policy(emit)` 노출** — `Debounce`/`Throttle`이 emit을 안 쥐고 자기
  Blocker를 On/Off만 하는 정책이 된다. `Gate`엔 `Flush`/`Cancel`을 안 둔다

요소 타입 검증은 블랙리스트에서 **주입 술어 `isInst` 기반 화이트리스트**로
뒤집혔고(`H-40`), 주입 op이 둘 늘었다(`isInst`/`onDestroying` — 조합 폴백이
불가능해 미주입이면 에러).

## 사용자가 에이전트 갈래를 뒤집은 자리가 여럿

`H-1`(세 갈래가 전부 차선), `H-2`(*"부기 확정에서 length 를 확정해도 되는거
아님?"* — 부기와 물리 마운트를 분리하라는 되물음), `H-40`(브랜드 판정이
2026-08-21 인스턴스 브랜드 재작성 이후 성립 불가임을 지적), `H-33`(제 중첩
합성안이 unblock 시 디바운스 창을 새로 시작시켜 창이 안 끝난다는 지적).
`Ref.Callbacks` 해시맵화와 `Ref` 콜백의 `canExecute` 확인은 사용자가 먼저 발견.

## 반영 후 재검토 — `/code-review high` 7건 + 감사 9라운드 34건

**`/code-review high` 7건 중 셋이 이번 반영이 만든 회귀**였다 — 상태가 셋이
됐다고 산문에 쓰고 코드엔 경계 하나만 남겨 `Slot { frameA }` 생성자가
크래시하던 것, `native*`를 `_mounted`로 가리면서 그게 곧 파괴였다는 걸 놓쳐
영구 누수를 만든 것, "`:List`와 CRUD는 상호배타"라며 승인받은 분기가
**재마운트 경로를 안 봐서** 포탈을 깬 것. 셋 다 코퍼스 정합성 각도로는
구조적으로 안 보이는 종류라 *"`/code-review`는 감사자를 대체하지 않는다"*가
실측으로 재확인됐다. 같은 리뷰가 `H-11`의 두 결정이 서로 모순임을 잡아
재결정했다 — *"`EffectHandle`이 자기 `bindLifetime` 직후에 건다"*는 **그 호출부가
실재하지 않았고**, 사용자 판단으로 `bindLifetime`/`unbindLifetime`이 `isEffect`를
보고 직접 처리하는 것으로 바뀌었다.

**`quad-doc-auditor` 감사 루프는 9라운드에서 새 발견 0건으로 수렴**(라운드별
5→7→2→2→3→9→2→4→0, 각도와 목록은 followup의 E절). 한 턴에 하나씩 돌리고
라운드마다 각도를 바꿨다. 가장 많이 잡은 6라운드(9건)는 *"이 체크박스로 코드를
짜면 무엇이 나오는가"*를 물은 라운드였고, 그때 `ROADMAP.md`의 미완료 항목이
대거 stale인 게 드러났다(폐기된 `pos` 공식이 "확정"으로, 접두합 캐시 무효화
계약이 통째로 부재, `bindLifetime`이 "둘만 한다"고 적혀 `H-11`과 직접 모순).

**반복된 실패 패턴은 하나 — "고쳐야 할 자리가 N개인데 일부만 고쳤다"**:
배너를 달고 그 배너가 부정하는 문장을 안 고침(1라운드), `base/` 19개를 바꾸고
`.claude/README.md`를 한 줄도 안 고침(2라운드), 그 README를 고칠 때 11행 중
6행만(7·8라운드). 셋 다 핸드오버 체크리스트가 명시적으로 경고하는 항목이라,
규율이 없어서가 아니라 지켰는지 스스로 확인하지 않아서 생긴 실패다.

## 백로그 하나

`Fallback`/`Traceback` 중 생성된 부분 트리의 회수(`H-26`) — 그 둘이 슈가라
구현 시점에 같이 다룬다. 같이 확인된 것: `dispatch-core-plan.md`가 잔여 부기를
인스턴스 GC가 정리한다고 적은 문장은 **gcconn 불멸성과 양립하지 않는 틀린
안전망 주장**이라 삭제했다.

**M2 착수를 막는 설계 항목은 이제 없다** — 남은 건 `question.md` 2번(M2↔M3
양방향 의존, 마일스톤 순서)뿐이다. `doc-check.py` ERROR 0.

Co-authored-by: qwreey <me@qwreey.moe>
Claude-Session: https://claude.ai/code/session_01Jjrec9xAS7TZstMx5gi3cm
2026-08-24 18:54:14 +09:00
9622ccd3e9
design: ROADMAP 마일스톤 경계 재편 + 전반 stale 정리
`ROADMAP.md` 전문을 `base/`의 현재 확정과 대조한 전반 점검. 사용자 판단으로
마일스톤을 각주가 아니라 체크박스째 재편했다.

## 마일스톤 이동 (M3/M7 → M2)

`EpochMap.luau` / `state:Gate`+`GateNode` / `Blocker.luau` / `None`+
`Dispatch/None.luau`. 넷 다 M2가 실제로 호출하는데 각주로만 예고돼 있었고,
`GateNode`는 체크박스 자체가 없어 M2를 훑는 구현자에게 항목으로 보이지
않았다. `LifetimeHandle` 인터페이스를 M8→M2로 옮겼던 전례와 같은 처리.
`Blocker`는 `GateNode` 위의 정책으로 얹는다(노드를 다시 만들지 말 것).

##  새 미결 — M2와 M3의 의존이 양방향

이동하다 드러났다. `Dispatch.setLength`가 `State<number>`, `setOffsetSource`가
`Source<number>`를 받고 `recompute`가 `offset:Set()`을 부르며, `Dispatch.drive`
자신도 배치 등록을 Blocker로 게이팅한다 — 즉 M2는 `Source.luau`/`State.luau`
없이 구현이 안 된다. 설계가 아니라 마일스톤 순서 문제라, 선택지 셋((a) 순서
교체 / (b) M2 분할 — 경계선은 `drive` / (c) 유지)을 `question.md` 2번으로
신설하고 유일한 소스로 지정했다. `HUMAN_TODO.md` 11번이 사용자 진입점.

## 모순 정정

- `Dispatch.drive` 순회 구현은 두 패스가 아니라 단일 일반화 `for`(`F-4-1`).
  "배열→해시 먼저"는 그 루프가 지키는 계약. 코퍼스의 "두 패스"는 본체 루프의
  옛 이름으로 정리(시점 표기는 유지, 용어 각주 신설). 따름정리로 "base는
  언어 동작에 안 기댄다"는 서술이 거짓이 됐고, 재작성될 스파이크 `01`이
  검증할 것도 언어 동작 자체로 바뀐다
- 물리 조작 주입 op 이름은 `native*` 확정(옛 가칭 `mountInst`/`unmountInst`/
  `disposeInst` 폐기). 단건 경로 순서는 `setOffsetSource` → `nativeInsert` →
  `setLength` → `recompute` — 역전 배너를 스스로 단 절 안에 옛 "부기 먼저"
  주장이 두 문단 살아 있었다
- M2 첫 체크박스가 하강 diff와 폐기된 옛 모델을 한 불릿에서 둘 다 서술

## 상태 표시

- `[x]`는 "짜야 할 코드"만. 설계 확정은 `### 확정된 것` 절 또는 전용
  불릿으로 분리(M3/M6/M11) — 이제 `[x]`는 M0/M1에만 남는다
- M0에 `### 재검증 대기` 절 신설 — 설계 변경으로 무효화된 스파이크들이
  어느 체크박스에도 없어 잊히기 쉬웠다(현황의 소스는 `luau-test/STATUS.md`)
- 주입 op 개수 하드코딩을 네 문서에서 걷고 `architecture.md`의
  `EngineOps.luau` 줄 하나로 단일화(그 줄에 빠져 있던 시간 op도 채움)

## 검증

`quad-doc-auditor` 10라운드(각도를 매번 바꿔 34건) + 사용자가 돌린
`/code-review high` 9건. `doc-check.py` ERROR 0. 경위와 실측된 실패 패턴은
`.claude/session/2026-08-22-01-roadmap-milestone-review.md`.

Claude-Session: https://claude.ai/code/session_01TiW21rnti9SbLgF6twtn6D

Co-authored-by: qwreey <me@qwreey.moe>
2026-08-22 03:19:13 +09:00
0498816c10
design: Epoch/EpochMap/Brand 전면 승격 + 해소 기록 flatten
앞 세션이 컨텍스트 피로로 미뤄둔 승격(todos 000번)을 수행하고, 코퍼스에
쌓여 있던 [해소]/[정정] 층을 걷어냈다. 감사 3라운드 + /code-review high로
15건을 잡아 전부 반영했다.

승격 — base/ 넷 + 파급 넷
- state-epoch-plan.md 재작성: Epoch 인터페이스({Revision:number}, 그 자체로
  키가 되는 unique 테이블, Source가 구조적으로 만족)와 EpochMap(Update/
  Refresh/Sync/TrackFrom) 신설. State는 EpochMap을 둘 컴포지션 —
  sourceCountMap/sourceEmitMap -> valueEpochMap/emitEpochMap. §1~§8로 재편.
- brand-plan.md 전면 재작성: 공유 레지스트리 + Brand.get(객체당 태그 하나)
  -> 인스턴스 브랜드 Brand() + :register/:is, 다중 태깅 허용. 발단은 Source가
  SourceBrand이면서 동시에 EpochBrand여야 하는데 옛 모양으로 표현 불가.
  역조회는 제거(전수 조사에서 쓰는 자리 0). weak-key/테이블 아이덴티티/
  duck-typing 기각 근거/predicate 합성은 전부 유지.
- source-state-plan.md: Source가 Epoch도 구조적으로 만족(Revision은 공개여야
  타입 레벨에서 성립), Observer 클로저가 fn(self, from: (Epoch|EpochSet)?).
-  effect-plan.md: 다중 의존성 중복 발화 미해결 항목이 닫힘 —
  EffectHandle이 자기 EpochMap을 들어 첫 번째만 통과시킨다.
- gate-plan.md/architecture.md/bind-system-plan.md/ROADMAP.md 어휘 통일,
  EpochMap.luau가 M2에도 필요하다는 것 반영(GateNode가 씀).

리비전 갱신은 bit32.bnot(-rev) — 사용자 확정
- a>0이면 a-1, 0이면 4294967295인 랩어라운드 감소. 갱신과 랩이 FASTCALL
  하나로 끝난다(luau 실측). 근거: 2^53 포화는 도달 불가능한데 그걸 피하려고
  값을 double 영역까지 키울 이유가 없다, 매번 도는 hot path다.
- 에이전트가 이걸 band(rev+1, mask)로 잘못 옮기고 "그러니 bit32가 더 싼 건
  아니다"라는 틀린 단서까지 달았다가 사용자 정정("제가 말한건 bit32.bnot(-a)
  입니다"). 세 문서에 정정 경위를 남겼다.
- 따름정리: 리비전은 증가가 아니라 감소한다. ==/~= 만 쓰는 지금 규칙에서만
  무해하다는 경고를 §2에 명시.

archive / flatten
- archive/brand-shared-registry-reversed.md 신설(옛 Brand 표면 원문).
- question.md 421->208줄: 해소 항목 18건을 archive/question-resolved.md로
  이관. 그 문서가 스스로 정한 규칙("해소하면 여기서 지우고 archive로")을
  다시 어기고 있었다. 이관분의 옛 필드명은 소급 수정하지 않고 머리에 경고만.
- todos.md 000번 삭제, "M3 착수 전 필요" 목록에서 해소 항목 일곱 제거
  (실제로 열린 건 중간 State GC와 store:GetDynamic 둘뿐).
- research/ -> reference/ 이동 둘(epoch-brand-composition,
  slot-attach-decomposition). 확정된 결정의 근거 기록은 research(상의 필요)도
  archive(뒤집힘)도 아니므로, reference의 폴더 기준에 그 용도를 명문화했다.

감사 3라운드(각도: base 정합성 / 인덱스+luau-test / archive+qa-request)
- §8에 "기각된 대안 — 게이트를 에포크 경계로" 논거 신설(재작성 때 떨어뜨렸고,
  두 문서가 서로 다른 없는 §번호를 대고 있었다).
- luau-test 스파이크 22를 done/ -> rewrite-required/(옛 Brand.set/get을 직접
  구현). STATUS.md 개수와 절 제목의 하드코딩 개수 정리.
- state-epoch/source-state가 "Revision을 증가시킨다"고 적어놓고 20줄 뒤에
  "감소한다"로 반박하던 자기모순 정정.

커밋 전 /code-review high — 9건, 전부 유효(감사자가 못 보는 축)
-  {Epoch}는 Luau에서 배열인데 실제 게이트 배치는 {[Epoch]:true} 집합.
  그대로 ipairs로 구현하면 유보됐다 풀린 emit이 전부 삼켜진다(gate-plan 4번이
  애초에 고치려던 그 버그) -> EpochSet으로 확정.
-  새 노드 시딩이 확정된 EpochMap 표면으로 표현 불가능했다(:With의 상류는
  State이지 Epoch가 아니고, 키 열거/병합 연산이 없었음) -> :TrackFrom 신설.
- GateNode 예외(emitEpochMap을 전파 시점에 갱신)가 §4에 미기록.
- 설치 발화엔 from이 없다 -> 옵셔널로, Effect의 억제 플래그가 Update보다
  먼저여야 함을 명시.
- 2^32 랩을 "똑같이 도달 불가능"이라 한 근거가 틀렸다(같은 척도로 285년 vs
  72분). 실제 안전 근거는 충돌 조건이 한 점이라는 것으로 정정.
- 그 외 :Sync 용도 충돌, isEpoch 누락, TweenTag 3곳, Effect(fn,state?) 4곳,
  § 참조 3곳.

에이전트가 이름 붙인 연산 둘은 사용자 검토로 확정
- :Refresh 유지 — "Update는 받은 것을 처리, Refresh는 내가 받았던 걸 처리라
  표면적 의미 자체가 다르다"(오버로드로 합치지 않음).
- :Absorb -> :TrackFrom 개명 — absorb는 상위에서 제거할 것처럼 읽히고,
  gate-plan이 이미 "흡수 집합"을 다른 뜻으로 쓴다. TrackFrom은 이 맵의 존재
  이유("내가 뭘 추적하고 있나")를 그대로 쓰고 From이 비파괴를 못박는다.

Epoch/EpochMap/Brand에 열린 설계 항목 없음. doc-check.py ERROR 0.

Co-authored-by: qwreey <me@qwreey.moe>
Claude-Session: https://claude.ai/code/session_01TiW21rnti9SbLgF6twtn6D
2026-08-22 01:27:18 +09:00
c58c97a877
design: Gate 표면 확정(state:Gate + GateNode) + State 에포크 채택, 두 문서 base/ 승격
사용자 확정 둘로 M2 착수를 막던 설계 항목이 전부 사라졌다.

1) Gate — 탑레벨 프리미티브를 만들지 않고 state:Gate(setup) 메소드로 확정.
   ComputeNode와 같은 층위의 GateNode를 만든다. Blocker는 그 위의 별개
   프리미티브로, 이미 확정돼 있던 state:Block(blocker)가 내부에서
   self:Gate(policy)를 부른다. Debounce/Throttle의 state:Apply(...) 관용구는
   그대로 — 팩토리가 내부에서 :Gate를 부르면 되기 때문. 이름 문제(Gater?)도
   메소드 자리로 가면서 소멸. Get()엔 영향 없음(통지만 막음)까지 확정.
   research/gate-primitive.md -> base/gate-plan.md.

2) State 에포크 — 채택 확정. 구현은 M3.
   research/state-epoch-validation.md -> base/state-epoch-plan.md.

에포크 채택으로 source-state-plan.md의 두 확정 서술("emit은 항상 전파" /
"quad가 접지 않는 것은 중복 통지뿐")이 역전됐다. 원문은
archive/always-propagate-no-dedup-superseded.md. 지금 계약은 "invalid로는
절대 안 접고, 같은 소스의 같은 에포크가 두 번째로 도착했을 때만 접는다" —
2026-08-14의 invalid 기반 dedup 금지를 되돌린 게 아니라는 점을 역전 문서와
source-state-plan.md, README 세 곳에 못박음(흐려지면 "영구 침묵" 버그로
되돌아감).

같이 갱신: architecture.md 전파 모델 요약, blocker-plan.md(:Gate 배선 +
Get 계약이 에포크 안의 전제라는 것), debounce-throttle-plan.md(공용 게이트
권고가 실현됨 / 파동 단위 최적화 서술 정정), reference/comparison-fusion-vide.md,
source-state-plan.md의 Observer 계약 각주(이제 "새 에포크는 항상 통과"에
의존), ROADMAP M0 각주·M2 각주·M3 체크박스, README/question/todos 인덱스.

스파이크 05-store-state-diamond-propagation은 done/ -> rewrite-required/ 로
되돌렸다 — 다이아몬드 Observer가 이제 변경당 1회만 울어야 해서 핵심 assert가
정반대가 됐다(살릴 것/새로 넣을 것은 STATUS.md에 기재).

처리 전량의 소스는 qa-request/pre-implementation-qa-round5-followup.md의 O절.
doc-check.py ERROR 0.

Co-authored-by: qwreey <me@qwreey.moe>
Claude-Session: https://claude.ai/code/session_01TiW21rnti9SbLgF6twtn6D
2026-08-21 21:04:27 +09:00
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
14f733dde1
design: Debounce/Throttle 마지막 판단 대기 4개 닫고 base/로 승격
의미론은 (A) emit-gate로 확정(value-hold 안은 laziness와 상충해 철회),
제어 핸들은 개별 Ref 아웃파라미터 + 전체 팩토리 브로드캐스트(weak
레지스트리)로 수렴, Time/MaxTime은 number|State<number> 허용(스케줄
시점에만 폴링), 이름은 Debounce/Throttle 유지로 확정. 전부 닫히면서
quad-base에 새 코어 메커니즘을 안 더하는 순수 슈가로 재평가됨(Blocker의
gated state + Ref + 주입 op 2개 위에 전부 얹힘) — 우선순위 서술도 갱신.

Co-authored-by: qwreey <me@qwreey.moe>
2026-08-19 02:11:11 +09:00
Renamed from .claude/research/debounce-throttle-plan.md (Browse further)