앞 세션이 컨텍스트 피로로 미뤄둔 승격(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
336 lines
22 KiB
Markdown
336 lines
22 KiB
Markdown
# 2026-08-21-03 — `Epoch`/`EpochMap`/`Brand` 전면 승격 + 해소 기록 flatten
|
|
|
|
**요지**: 앞 세션(`2026-08-21-02-qa-round5-and-gate-epoch-research.md`)이
|
|
컨텍스트 피로로 미뤄둔 승격을 실제로 수행하고, 그 과정에서 코퍼스 곳곳에 쌓여
|
|
있던 `[해소]`/`[정정]` 층을 걷어냈다. 지금 유효한 설계는 전부 `base/`가 소스 —
|
|
이 파일은 "무엇을 어디로 옮겼나"의 기록이다.
|
|
|
|
## 1. 사용자 지시
|
|
|
|
두 갈래였다.
|
|
|
|
1. **질문**: *"slot-attach-decomposition.md 은 이제 확정 이야기이기에, 리서치
|
|
대상이 아니지 않나요? 또, 이것이 base에 적용되어 있습니까?"*
|
|
2. **작업 지시**: `state-epoch-plan.md`/`source-state-plan.md`/`effect-plan.md`/
|
|
`brand-plan.md` 넷을 전면 승격하고, *"이 승격을 하며 더이상 필요 없어진
|
|
요소들은 archive 하세요. 특히 재정정 기록이 쌓인 부분이 있는데, 풀어가며
|
|
archive 해서 flatten 해야할 필요가 보입니다."*
|
|
|
|
## 2. 1번 질문에 대한 답 — 둘 다 예
|
|
|
|
- **`base/slot-plan.md`에 이미 반영돼 있다** — `materializeSlotTree` /
|
|
`mountSlotTree` / 두 줄짜리 `attachSlot` 래퍼가 그 문서의 "재귀 메커니즘"
|
|
절에 의사코드로 들어가 있고, 그 문서가 근거로 이 파일을 네 곳에서 인용한다.
|
|
- **`research/`에 있을 이유는 없다** — `.claude/README.md`의 폴더 기준상
|
|
`research/`는 "아직 착수 전, 상의 필요"다.
|
|
|
|
**어디로 옮길지**가 실제 판단이었다. `archive/`는 기준이 "뒤집혔거나 기각됨"
|
|
이라 채택된 결정의 근거 기록엔 안 맞고, `reference/`의 기준("결정 자체가
|
|
아니라 다른 문서가 근거로 인용하는 온디맨드 자료")엔 정확히 맞는다. 그래서
|
|
**`reference/`로 옮기고 폴더 기준 자체에 그 용도를 명문화**했다
|
|
(`README.md` 폴더 기준표 + `project-context.md`). 선례도 있다 —
|
|
`quad-v1-architecture.md`/`comparison-fusion-vide.md`가 `base/`에서 같은
|
|
이유로 이동해온 것.
|
|
|
|
같은 처리를 `epoch-brand-composition.md`에도 적용했다(승격이 끝나면 성격이
|
|
똑같아지므로).
|
|
|
|
## 3. 승격 — 무엇이 어떻게 바뀌었나
|
|
|
|
### `base/state-epoch-plan.md` (재작성)
|
|
|
|
- **`Epoch` 인터페이스 신설** — `type Epoch = { Revision: number }`, 그 자체로
|
|
키가 되는 unique 테이블. `Source`가 구조적으로 만족하고 `EpochBrand`에
|
|
등록된다. "루트 `Source`의 에포크"라는 좁은 서술이 전부 여기로 일반화됨.
|
|
- **`EpochMap` 신설** — `:Update(Epoch|{Epoch}) -> boolean`(반환값의 뜻은
|
|
"뒤로 전파가 필요한가"), `:Refresh()`, `:Sync()`. 부기가 State에서 떨어져
|
|
나와 재사용 가능한 객체가 됐다.
|
|
- **`:Refresh`는 이 세션이 이름만 붙인 것**이다 — "순회"라는 연산 자체는
|
|
이미 확정돼 있었고(`rawInvalid == false`일 때 자기 맵을 훑는다), 맵이
|
|
키를 소유하므로 인자 없는 형태가 자연스러워서 그렇게 적었다. 새 설계가
|
|
아니다.
|
|
- **State의 두 맵이 `EpochMap` 둘이 됐다** — `sourceCountMap` →
|
|
`valueEpochMap`, `sourceEmitMap` → `emitEpochMap`. 이름을 바꾼 이유는
|
|
"source"가 더 이상 정확하지 않아서다.
|
|
- **문서 구조를 §1~§8로 다시 짰다.** 옛 §3(에이전트 분석)과 §5(열린 질문)에
|
|
결론과 정정 경위가 섞여 있었는데, 결론은 규칙 본문(§2~§5)으로 올리고
|
|
경위는 걷어냈다(원문은 `qa-request/pre-implementation-qa-round5-followup.md`
|
|
M·N절과 `reference/epoch-brand-composition.md`에 있다).
|
|
|
|
### `base/brand-plan.md` (전면 재작성)
|
|
|
|
- **인스턴스 브랜드** — `Brand()`가 브랜드마다 weak 집합 하나를 들고,
|
|
`SomeBrand:register(x)` / `SomeBrand:is(x)`. **다중 태깅**이 이 재작성의
|
|
존재 이유다(`Source`가 `SourceBrand`이면서 동시에 `EpochBrand`).
|
|
- **역조회 `Brand.get`은 없어졌다.**
|
|
- **유지된 것**: weak-key 레지스트리, 테이블 아이덴티티, duck-typing 기각
|
|
근거 둘, 포함 관계 predicate 합성, `Brand`가 `None`에 의존하지 않는다는
|
|
결정, Luau narrowing 주의.
|
|
- **새로 명시한 것 하나** — 다중 태깅이 가능해져도 **포함 관계는 계속
|
|
predicate 합성으로 쓴다**(`Source`를 `StateBrand`에도 등록하지 않는다).
|
|
등록 지점이 흩어지면 조용한 버그가 되고 포함 관계가 코드에서 사라지기
|
|
때문. 정당한 다중 등록은 `Epoch`처럼 **다른 축의 계약**뿐이다.
|
|
- 옛 표면은 `archive/brand-shared-registry-reversed.md`.
|
|
|
|
### `base/source-state-plan.md`
|
|
|
|
- "Source가 State를 만족함" 절에 **`Epoch`도 같은 방식으로 만족**한다는 항목
|
|
추가(`Revision`이 공개여야 하는 이유 포함).
|
|
- `state:Observer(fn)` 절에 **`fn(self, from: Epoch | {Epoch})`** 시그니처
|
|
확정 추가. "값을 안 실어주는 구독" 계약이 안 깨지는 이유도 같이.
|
|
- 승격 대기 ⚠️ 배너 제거, 그 절들의 "에포크" 표현을 "리비전"으로 통일.
|
|
|
|
### `base/effect-plan.md`
|
|
|
|
- **⚠️ 미해결이던 다중 의존성 중복 발화가 닫혔다** — `EffectHandle`이
|
|
`EpochMap`을 하나 들고 각 내부 Observer가 받은 `from`으로 `Update`,
|
|
`true`일 때만 `fn`을 돌린다. `Effect`가 곧 deps의 공통 하류가 된다.
|
|
- **`Ref` 의존성은 이 맵에 안 낀다**(이 세션이 명시) — `Ref`는 `Epoch`가
|
|
아니고 `:Callback`으로 발화해 `from`이 없다. 공통 상류 문제 자체가 없다.
|
|
|
|
### `base/gate-plan.md`
|
|
|
|
승격 대기 배너를 반영 완료로 바꾸고, 페이로드/`withheld`/`emitEpochMap`
|
|
표현을 `Epoch` 어휘로 통일. **기제는 하나도 안 바뀌었다.**
|
|
|
|
## 4. flatten — 걷어낸 것들
|
|
|
|
사용자가 지목한 "재정정 기록이 쌓인 부분"은 실제로 세 군데였다.
|
|
|
|
1. **`question.md`가 다시 해소 항목으로 절반이 찼다.** 그 문서 스스로
|
|
*"항목을 해소하면 여기서 지우고 `archive/question-resolved.md`에 근거와
|
|
함께 옮길 것 — 다시 쌓이면 같은 문제가 반복됨"*이라 규정해뒀는데,
|
|
2026-08-21 하루에 여러 건이 닫히면서 예고대로 재발했다. **16건을 일괄
|
|
이관**하고(`Gate` 이름 항목 포함), 파일이 421줄 → 208줄이 됐다.
|
|
- 이관분 중 `Epoch` 관련 서술은 **이관 시점 표현 그대로 뒀고**, 대신
|
|
이관 섹션 머리에 "필드 이름이 그 뒤 바뀌었다"는 경고를 붙였다. 히스토리
|
|
문서의 원문을 소급해 고치지 않는다는 관례를 지키면서 오독만 막는 처리.
|
|
2. **`todos.md`의 "M3 착수 전에 결론이 필요한 항목 목록"이 절반 넘게 `[해소]`
|
|
였다.** 실제로 열려 있는 건 둘뿐(중간 State GC 미검증, `store:GetDynamic`)
|
|
인데 해소 항목 일곱이 섞여 있어 "지금 할 일"로 안 읽혔다. 걷어냄.
|
|
3. **`todos.md` 000번**(다음 세션 첫 작업 = 이 승격)은 완료됐으므로 삭제하고,
|
|
00번 안에 승격 완료 사실만 남겼다.
|
|
|
|
## 5. 마지막 미정도 같은 세션에 닫힘 — 리비전 증가는 `bit32` 랩
|
|
|
|
승격 보고 직후 사용자가 그 자리에서 정했다: *"리비전 증가는 bit32 로
|
|
두고싶어요. 그건 luau 에서 native call 이라 아주 빨라요. 반면 double 의
|
|
연산이 느린편인데, 희소 수준이 아니라, 사실상 만나는걸 수년간 보기 어려운
|
|
라운드되어 동일해 무시되는 경우를 막기 위해 double 까지 올려야할 이유를
|
|
모르겠어요. 매번 도는 코드인지라, 값 싸게 native call + num 연산으로 가볍게
|
|
가고 싶어요."*
|
|
|
|
논거의 핵심은 **"`2^53` 포화는 어차피 도달 불가능한 시나리오인데, 그걸
|
|
피하겠다고 값을 double 영역까지 키울 이유가 없다"**이고, 매 `Set`마다 도는
|
|
hot path라는 것.
|
|
|
|
**⚠️ 에이전트가 형태를 잘못 옮겼고, 사용자가 그 자리에서 정정했다.**
|
|
에이전트는 이걸 `bit32.band(rev + 1, 0xFFFFFFFF)`로 적고, 거기에 "그러니
|
|
`bit32`라서 증가가 더 싼 건 아니다 — `n + 1`은 어느 쪽이든 double 덧셈이고
|
|
`bit32`는 fastcall을 하나 더 얹는다"는 단서까지 달았다. 사용자 정정:
|
|
|
|
> *"잠시만요. 제가 말한건, bit32.bnot(-a) 입니다"* — 그리고 REPL 출력 셋을
|
|
> 그대로 제시했다(`bit32.bnot(-1)` → `0`, `bit32.bnot(-0)` → `4294967295`,
|
|
> `bit32.bnot(-4294967295)` → `4294967294`).
|
|
|
|
**즉 그 세 줄은 예시가 아니라 연산 자체였다.** `luau`로 재확인한 결과:
|
|
|
|
```
|
|
bump(a) = bit32.bnot(-a)
|
|
0 → 4294967295 | 1 → 0 | 2 → 1 | 4294967295 → 4294967294
|
|
```
|
|
|
|
`a > 0`이면 `a - 1`, `0`이면 `4294967295`인 **랩어라운드 감소**이고, **갱신과
|
|
랩이 FASTCALL 하나로** 끝난다. 그래서:
|
|
|
|
- **에이전트가 붙였던 단서는 틀렸다** — `band(rev + 1, mask)`라는 **다른
|
|
형태**를 놓고 한 비교였다. `bnot(-a)`는 덧셈 위에 얹히는 게 아니라 갱신
|
|
자체를 대체하므로, *"native call 이라 아주 빨라요"*라는 사용자 서술이 맞다.
|
|
- **따름정리는 방향만 바뀌어 유효하다** — 단조 증가가 아닌 게 아니라 아예
|
|
**감소**한다. 지금 규칙이 `==`/`~=`만 쓰기 때문에 무해하지만, 순서 비교를
|
|
넣고 싶어지면 이 결정부터 되짚어야 한다. `Revision`이라는 이름이 순서를
|
|
뜻하지 않는다는 것도 같이 적어뒀다.
|
|
|
|
**교훈**: 사용자가 근거로 든 REPL 출력을 "그 함수가 랩한다는 예시"로만 읽고
|
|
연산 형태를 에이전트가 임의로 재구성했다. `conventions.md`의 "사용자 발언을
|
|
근거로 인용할 때는 결론만 적지 말고 논거까지 남길 것" 항목이 겨냥하는 실패에
|
|
가깝다 — 이 경우엔 논거를 남기긴 했는데 **형태를 바꿔 남겼다.**
|
|
|
|
**그래서 `Epoch`/`EpochMap`/`Brand`에 열린 설계 항목은 하나도 남지 않았다.**
|
|
|
|
`doc-check.py` ERROR 0 유지(WARN 30건은 전부 이 세션 이전부터 있던 것 —
|
|
축약 파일명 인용과 날짜 없는 시한부 주장).
|
|
|
|
|
|
## 6. 감사 루프 기록
|
|
|
|
`conventions.md`의 "핸드오버 준비하고 커밋해" 절차대로 **한 턴에 하나씩**,
|
|
라운드마다 각도를 바꿔 돌렸다.
|
|
|
|
### 1라운드 — `base/` 정합성 + diff 범위
|
|
|
|
발견 넷 중 **하나는 stale**(감사자가 `bit32` 편집 이전 스냅샷을 읽어
|
|
"리비전 증가 방식이 아직 미정"이라 보고했으나 다섯 곳 전부 이미 갱신돼
|
|
있었음 — 감사자가 도는 도중 메인 세션이 같은 파일을 고치면 생기는 일이라,
|
|
**감사 리포트를 받으면 항상 현재 파일로 재확인할 것**). 나머지 셋은 유효:
|
|
|
|
1. **`state-epoch-plan.md`가 "§8에서 기각됨"으로 실재하지 않는 내용을
|
|
가리켰다** — 재작성하면서 "게이트를 에포크 경계로" 기각 논거를 떨어뜨렸고,
|
|
`gate-plan.md`와 `reference/`는 또 **다른** 번호(§5-3)를 대고 있었다.
|
|
→ §8에 실제 논거를 쓰고 세 포인터를 절 제목으로 통일. **`doc-check.py`는
|
|
산문 속 `§번호`를 검증하지 못한다** — 문서를 재작성할 때 §번호 포인터는
|
|
손으로 훑어야 한다.
|
|
2. `ROADMAP.md` M7이 `isState`/`isSource`를 아직 "공유 레지스트리 기반"으로
|
|
서술 → 멤버십 기반으로.
|
|
3. **스파이크 `22`가 역전된 `Brand.set`/`Brand.get`을 직접 구현한 채
|
|
`done/`에 있었다** → `rewrite-required/`로 이동. 검증 대상(포함 관계)은
|
|
유효하므로 결론이 틀려서가 아니라 **구현자가 그 파일의 `Brand` 구현을
|
|
참고 모델로 오독**하는 걸 막기 위함. `05`와 같은 처리.
|
|
|
|
### 2라운드 — 인덱스 레이어 + `luau-test` + 1라운드 이후 diff
|
|
|
|
여섯 건, 전부 유효:
|
|
|
|
1. **`state-epoch-plan.md`가 자기 문서 안에서 모순**했다 — §2가 "`Revision`을
|
|
**증가**시킨다"라고 적어놓고 20줄 뒤에 "확정된 방식은 증가가 아니라
|
|
**감소**한다"라고 반박. `bit32.bnot(-a)` 정정을 반영하면서 앞쪽 문장을
|
|
안 고친 것. → 방향을 안 담는 "갱신한다"로.
|
|
2. `source-state-plan.md`에 **같은 문장이 복붙돼 있었다** — 같이 정정.
|
|
3. `README.md`의 `effect-plan.md` 행이 dedup 대칭을 아직 "미확인"으로 서술
|
|
(본문은 `EF-3`에서 이미 "성립함"으로 확정). 세션 전부터 있던 stale.
|
|
4. `STATUS.md`의 `✅ done/` 절이 **같은 파일 위쪽과 어긋났다** — 배너와 표는
|
|
`22` 이동을 반영했는데 절 제목("17건")과 본문 나열("런타임 9건 …/`22`")은
|
|
옛 상태. → 하드코딩 개수를 빼고 폴더/표를 소스로.
|
|
5. `luau-test/README.md`의 "실행 환경 세 갈래" 표가 `21`/`22`/`23`을
|
|
빠뜨리고 `13`을 아직 "런타임/타입 부분"으로 쪼개 적고 있었다(런타임
|
|
절반은 2026-08-19에 `22`로 분리돼 나갔다). → 실제와 맞추고, 파일 헤더가
|
|
소스임을 명시.
|
|
6. `reference/slot-attach-decomposition.md`의 제약 `C7`이 그 뒤 `native*`
|
|
계층 확정으로 **일반 계약으로서는 폐기**됐는데 문서에 아무 표시가
|
|
없었다. 결론 자체는 유효하지만(배치 경로가 부기를 먼저 끝내는 건 `C6`
|
|
요구사항이라 별개) 표만 떼어 읽으면 오독하므로 상단에 캐비엇 배너.
|
|
`reference/comparison-fusion-vide.md`가 이미 같은 형태의 캐비엇을 달고
|
|
있어 선례를 따랐다.
|
|
|
|
**교훈 하나** — 4·5번은 둘 다 **"같은 문서 안에서 위쪽만 갱신되고 아래쪽이
|
|
남은"** 형태다. 개수를 제목에 박아두면 정확히 이렇게 갈라지므로, 이번에
|
|
`STATUS.md`의 절 제목 두 곳에서 하드코딩 개수를 뺐다.
|
|
|
|
### 3라운드 — `archive/` 배너 정합성 + `qa-request/`의 "반영 완료" 주장
|
|
|
|
**확실한 모순 발견 0건 — 수렴.** 감사자가 대조한 것: `archive/`의 배너
|
|
포인터 넷(`brand-shared-registry-reversed` / `question-resolved`의 이관 섹션 /
|
|
`always-propagate-no-dedup-superseded` / `invalidate-dedup-propagation-reversed` /
|
|
`bookkeeping-before-physical-reversed`), 2라운드가 고친 여섯 자리, 그리고
|
|
`round4/5-followup.md`가 소스라고 선언한 항목들.
|
|
|
|
특히 **2라운드에서 내가 새로 쓴 `C7` 캐비엇**(*"배치 경로가 부기를 먼저
|
|
끝내는 건 `C7`이 아니라 `C6`가 요구하는 별개 사안"*)이
|
|
`base/dispatch-core-plan.md`의 "일반 계약 — 물리와" 절 4번과 정확히 대응함이
|
|
확인됐다 — 새로 쓴 서술이 기존 확정과 어긋나지 않는지가 이 라운드의 핵심
|
|
질문이었다.
|
|
|
|
유일한 지적은 문체였다 — §8에 "`Revision`만 **올리면**"이라는 방향 어감이
|
|
남아 있던 것(§2가 이미 "방향은 계약이 아니다"로 커버하고 있어 모순은 아님).
|
|
같이 정리했다.
|
|
|
|
**수렴 판정**: 3라운드에서 새 발견 0건이므로 `conventions.md`의 루프 종료
|
|
조건을 만족한다. 총 소비는 서브에이전트 3패스(각도: `base/` 정합성 →
|
|
인덱스 레이어+`luau-test` → `archive/`+`qa-request/`).
|
|
|
|
## 7. 커밋 전 `/code-review high` — 감사 3라운드가 못 본 축에서 9건
|
|
|
|
`conventions.md`가 명문화한 그대로였다 — **감사자와 code-review는 보는 축이
|
|
다르다.** 감사자 3라운드가 수렴(0건)한 뒤 사용자가 `/code-review high`를
|
|
돌리자 **9건이 더 나왔고 전부 유효**했다. 감사자는 코퍼스 전체의 **의미론적
|
|
정합성**(A 문서 결정 ↔ B 문서 서술)을 보고, code-review는 **diff 자체의
|
|
결함**(이번에 새로 쓴 서술 안의 모순, 새 표면이 기존 계약과 충돌하는가)을
|
|
본다.
|
|
|
|
### 구현을 실제로 막았을 것들
|
|
|
|
1. **⭐ `{Epoch}` 표기가 실제 배치 모양과 달랐다.** Luau에서 `{Epoch}`는
|
|
**배열**(`{[number]: Epoch}`)인데, 실제로 넘어오는 게이트 배치는
|
|
`gate-plan.md` 4번이 확정한 `withheld : { [epoch] : true }` — **집합**이다.
|
|
이 표기를 믿고 `ipairs`로 구현하면 배치 순회에서 **원소가 0개**가 되어
|
|
유보됐다 풀린 emit이 전부 조용히 삼켜진다. `gate-plan.md` 4번이 애초에
|
|
고치려던 바로 그 버그였다. → `type EpochSet = { [Epoch]: true }`로 확정.
|
|
**집합이어야 하는 이유는 게이트 쪽 요구**다(흡수·unfold가 저절로 접혀야
|
|
함). 이건 사용자 원 제안의 `Epoch|{Epoch}` 표기를 에이전트가 그대로
|
|
옮기면서 **Luau 타입으로서 뭘 뜻하는지 확인 안 한** 결과다.
|
|
2. **⭐ 새 노드 시딩이 확정된 `EpochMap` 표면으로 표현 불가능했다.**
|
|
"상류의 `Epoch`를 전부 끌어와 채운다"인데 `:With(a, b)`의 상류는
|
|
**State이지 `Epoch`가 아니다.** 상류가 추적 중인 루트 집합은 그 State의
|
|
`valueEpochMap` 안에만 있는데 §3 표면엔 **키 열거도 병합도 없었다.**
|
|
→ `:TrackFrom(other)` 신설(가칭 `Absorb`, 같은 날 개명 — §8 참고).
|
|
`:Refresh`와 같은 성격으로 **이미 확정된 동작에 이름을 붙인 것**이지
|
|
새 설계가 아니다(사용자 확정 문구가 이미 *"전부 가져와서, 실제 count 로
|
|
둡니다"*였다). dep이 `Epoch`면 `:Sync`, State면 `:TrackFrom`으로 갈리는
|
|
것도 같이 적었다.
|
|
3. **`GateNode` 예외가 §4에 기록돼 있지 않았다.** §4 의사코드는
|
|
`emitEpochMap:Update(from)`을 **수신 시점에 무조건** 부르는데,
|
|
`gate-plan.md` 4번은 게이트가 **전파할 때** `:Sync(batch)`로 갱신하는
|
|
것으로 확정돼 있다. §4대로 구현하면 그 계약이 조용히 깨진다.
|
|
4. **설치 시 즉시 1회 발화에 `from`이 없다.** `fn(self, from)`을
|
|
non-optional로 선언해뒀는데 등록 시점 발화엔 출처가 없다 —
|
|
`Effect`의 확정 클로저가 `Update(nil)`을 부르게 된다. → 옵셔널로 바꾸고,
|
|
`Effect`의 억제 플래그가 `Update`보다 **먼저** 와야 함을 명시(순서를
|
|
뒤집으면 설치 발화가 맵을 건드려 그 파동의 첫 진짜 emit이 접힌다).
|
|
|
|
### 근거·표기 정확성
|
|
|
|
5. **`2^32` 랩이 "똑같이 도달 불가능"이라는 근거가 틀렸다.** 같은 척도(초당
|
|
100만 `Set`)로 `2^53`은 285년인데 **`2^32`는 약 72분**이다(20만 배 차이).
|
|
실제로 안전한 이유는 **도달 시간이 아니라 충돌 조건이 한 점**이라는 것
|
|
("정확히 `2^32`만큼 뒤처진 항목"이라야 하고, 한 바퀴 중 한 번이라도
|
|
건드려지면 갱신됨). 이 문서가 바로 위 항목에서 *"근거를 정확히 적을 것"*
|
|
이라 스스로 규정해놓고 20줄 뒤에 어긴 셈이라 그대로 정정했다.
|
|
6. `:Sync`를 "초기화에만 쓴다"고 적었으나 `gate-plan.md`가 flush 경로에서도
|
|
쓴다 → "반환값이 필요 없다고 이미 아는 곳 둘"로 정정.
|
|
7. `ROADMAP.md`의 `Brand.luau` predicate 목록에 `isEpoch` 누락.
|
|
8. **역전된 옛 이름 잔재** — `TweenTag`(3곳)와 `Effect(fn, state?)`(4곳).
|
|
후자는 code-review가 짚은 것보다 실제로 더 많았다
|
|
(`architecture.md`/`lifecycle-hooks-plan.md`/`ROADMAP.md`/`README.md`).
|
|
9. **절 재편에 안 따라온 `§` 참조 3곳** — 1라운드가 같은 유형을 잡았는데도
|
|
남아 있었다. `doc-check.py`가 `§번호`를 검증 못 하는 사각지대다.
|
|
|
|
**교훈**: 1·2번은 둘 다 **"사용자 제안의 표기를 그대로 옮겼는데 그게 실제
|
|
메커니즘과 안 맞는" 유형**이다. 승격은 문장을 옮기는 작업이 아니라 **표기가
|
|
가리키는 것이 실제로 성립하는지 확인하는 작업**이라는 게 이번의 교훈.
|
|
|
|
## 8. `:Refresh`/`:TrackFrom` — 에이전트가 이름 붙인 둘, 사용자 검토로 확정
|
|
|
|
`/code-review`까지 처리한 뒤, **에이전트가 임의로 이름 붙인 연산 둘을
|
|
사용자에게 명시적으로 올렸다.** 동작 자체는 회신에 이미 확정돼 있었지만 표면
|
|
이름은 에이전트 판단이었기 때문 — `conventions.md`의 "애매하면 임의로 정하지
|
|
말고 그 자리에서 사용자에게 보고할 것"에 해당한다.
|
|
|
|
**`:Refresh` — 그대로 확정.** 사용자가 `:Update`와 합치지 않는 이유를 직접
|
|
정리했다: *"Update 에 인자 없는건 좀 아니야. 뭔가 받아서 받은것들에 대해서
|
|
처리하겠다는건데, 리프레시는 아무래도 내가 받았던걸 처리하겠다는거라
|
|
표면적 의미 자체가 다르지."* — **인자를 받아 그것을 처리하는 연산**과
|
|
**자기가 이미 들고 있는 것을 처리하는 연산**은 표면적 의미가 달라서 오버로드로
|
|
합치면 안 된다는 것. 표면이 하나 줄어드는 것보다 이 구분이 값이 크다.
|
|
|
|
**`:Absorb` → `:TrackFrom`으로 개명.** 사용자 지적: *"absorb 는 조금 상위
|
|
요소꺼를 흡수해서 상위 요소에서 제거할것만 같은 이름이긴 하네."* 맞다 — 이
|
|
연산은 `other`를 **전혀 안 건드린다.** 덧붙여 `base/gate-plan.md`가 이미
|
|
"흡수 집합"을 **다른 뜻**(emit을 붙들고 있음)으로 쓰고 있어, 한 코퍼스 안에
|
|
같은 단어가 두 의미로 놓이는 문제도 있었다.
|
|
|
|
후보를 넷 올렸고(`TrackFrom`/`SeedFrom`/`Include`/`Adopt`) 권장안이 채택됐다.
|
|
`TrackFrom`을 고른 근거 셋:
|
|
|
|
1. **이 맵의 존재 이유를 사용자가 표현한 말이 "추적"이었다** —
|
|
*"'내가 뭘 추적하고 있나' 가 필요하죠"*(§4의 시딩 규칙 근거). 코퍼스가 이
|
|
맵을 설명하는 말과 메소드 이름이 일치한다.
|
|
2. **`From`이 방향을 못박아 비파괴가 드러난다** — `Absorb`가 실패한 지점.
|
|
3. **`SeedFrom`보다 오래 간다** — 동적 의존성으로 "생성 이후에 키를 더하는"
|
|
자리가 생겨도 이름이 그대로 맞다. `Seed`는 그때 거짓말이 된다.
|
|
|
|
배제한 것도 근거가 있다 — `Extend`/`Inherit`은 `quad2-try`의 `Base:Extends`
|
|
OOP 상속이 **확인된 죽은 접근**이라 그 어휘를 되살리면 오독을 부르고,
|
|
`Extract`는 코퍼스에서 이미 "소유권을 통째로 넘긴다"는 뜻이다.
|
|
|
|
**두 이름 다 `question.md`에 안 올린다** — 같은 자리에서 확정됐으므로 열린
|
|
항목이 아니다.
|