앞 세션이 컨텍스트 피로로 미뤄둔 승격(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
22 KiB
2026-08-21-03 — Epoch/EpochMap/Brand 전면 승격 + 해소 기록 flatten
요지: 앞 세션(2026-08-21-02-qa-round5-and-gate-epoch-research.md)이
컨텍스트 피로로 미뤄둔 승격을 실제로 수행하고, 그 과정에서 코퍼스 곳곳에 쌓여
있던 [해소]/[정정] 층을 걷어냈다. 지금 유효한 설계는 전부 base/가 소스 —
이 파일은 "무엇을 어디로 옮겼나"의 기록이다.
1. 사용자 지시
두 갈래였다.
- 질문: "slot-attach-decomposition.md 은 이제 확정 이야기이기에, 리서치 대상이 아니지 않나요? 또, 이것이 base에 적용되어 있습니까?"
- 작업 지시:
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.mdM·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 — 걷어낸 것들
사용자가 지목한 "재정정 기록이 쌓인 부분"은 실제로 세 군데였다.
question.md가 다시 해소 항목으로 절반이 찼다. 그 문서 스스로 *"항목을 해소하면 여기서 지우고archive/question-resolved.md에 근거와 함께 옮길 것 — 다시 쌓이면 같은 문제가 반복됨"*이라 규정해뒀는데, 2026-08-21 하루에 여러 건이 닫히면서 예고대로 재발했다. 16건을 일괄 이관하고(Gate이름 항목 포함), 파일이 421줄 → 208줄이 됐다.- 이관분 중
Epoch관련 서술은 이관 시점 표현 그대로 뒀고, 대신 이관 섹션 머리에 "필드 이름이 그 뒤 바뀌었다"는 경고를 붙였다. 히스토리 문서의 원문을 소급해 고치지 않는다는 관례를 지키면서 오독만 막는 처리.
- 이관분 중
todos.md의 "M3 착수 전에 결론이 필요한 항목 목록"이 절반 넘게[해소]였다. 실제로 열려 있는 건 둘뿐(중간 State GC 미검증,store:GetDynamic) 인데 해소 항목 일곱이 섞여 있어 "지금 할 일"로 안 읽혔다. 걷어냄.todos.md000번(다음 세션 첫 작업 = 이 승격)은 완료됐으므로 삭제하고, 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 편집 이전 스냅샷을 읽어
"리비전 증가 방식이 아직 미정"이라 보고했으나 다섯 곳 전부 이미 갱신돼
있었음 — 감사자가 도는 도중 메인 세션이 같은 파일을 고치면 생기는 일이라,
감사 리포트를 받으면 항상 현재 파일로 재확인할 것). 나머지 셋은 유효:
state-epoch-plan.md가 "§8에서 기각됨"으로 실재하지 않는 내용을 가리켰다 — 재작성하면서 "게이트를 에포크 경계로" 기각 논거를 떨어뜨렸고,gate-plan.md와reference/는 또 다른 번호(§5-3)를 대고 있었다. → §8에 실제 논거를 쓰고 세 포인터를 절 제목으로 통일.doc-check.py는 산문 속§번호를 검증하지 못한다 — 문서를 재작성할 때 §번호 포인터는 손으로 훑어야 한다.ROADMAP.mdM7이isState/isSource를 아직 "공유 레지스트리 기반"으로 서술 → 멤버십 기반으로.- 스파이크
22가 역전된Brand.set/Brand.get을 직접 구현한 채done/에 있었다 →rewrite-required/로 이동. 검증 대상(포함 관계)은 유효하므로 결론이 틀려서가 아니라 구현자가 그 파일의Brand구현을 참고 모델로 오독하는 걸 막기 위함.05와 같은 처리.
2라운드 — 인덱스 레이어 + luau-test + 1라운드 이후 diff
여섯 건, 전부 유효:
state-epoch-plan.md가 자기 문서 안에서 모순했다 — §2가 "Revision을 증가시킨다"라고 적어놓고 20줄 뒤에 "확정된 방식은 증가가 아니라 감소한다"라고 반박.bit32.bnot(-a)정정을 반영하면서 앞쪽 문장을 안 고친 것. → 방향을 안 담는 "갱신한다"로.source-state-plan.md에 같은 문장이 복붙돼 있었다 — 같이 정정.README.md의effect-plan.md행이 dedup 대칭을 아직 "미확인"으로 서술 (본문은EF-3에서 이미 "성립함"으로 확정). 세션 전부터 있던 stale.STATUS.md의✅ done/절이 같은 파일 위쪽과 어긋났다 — 배너와 표는22이동을 반영했는데 절 제목("17건")과 본문 나열("런타임 9건 …/22")은 옛 상태. → 하드코딩 개수를 빼고 폴더/표를 소스로.luau-test/README.md의 "실행 환경 세 갈래" 표가21/22/23을 빠뜨리고13을 아직 "런타임/타입 부분"으로 쪼개 적고 있었다(런타임 절반은 2026-08-19에22로 분리돼 나갔다). → 실제와 맞추고, 파일 헤더가 소스임을 명시.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 자체의
결함(이번에 새로 쓴 서술 안의 모순, 새 표면이 기존 계약과 충돌하는가)을
본다.
구현을 실제로 막았을 것들
- ⭐
{Epoch}표기가 실제 배치 모양과 달랐다. Luau에서{Epoch}는 배열({[number]: Epoch})인데, 실제로 넘어오는 게이트 배치는gate-plan.md4번이 확정한withheld : { [epoch] : true }— 집합이다. 이 표기를 믿고ipairs로 구현하면 배치 순회에서 원소가 0개가 되어 유보됐다 풀린 emit이 전부 조용히 삼켜진다.gate-plan.md4번이 애초에 고치려던 바로 그 버그였다. →type EpochSet = { [Epoch]: true }로 확정. 집합이어야 하는 이유는 게이트 쪽 요구다(흡수·unfold가 저절로 접혀야 함). 이건 사용자 원 제안의Epoch|{Epoch}표기를 에이전트가 그대로 옮기면서 Luau 타입으로서 뭘 뜻하는지 확인 안 한 결과다. - ⭐ 새 노드 시딩이 확정된
EpochMap표면으로 표현 불가능했다. "상류의Epoch를 전부 끌어와 채운다"인데:With(a, b)의 상류는 State이지Epoch가 아니다. 상류가 추적 중인 루트 집합은 그 State의valueEpochMap안에만 있는데 §3 표면엔 키 열거도 병합도 없었다. →:TrackFrom(other)신설(가칭Absorb, 같은 날 개명 — §8 참고).:Refresh와 같은 성격으로 이미 확정된 동작에 이름을 붙인 것이지 새 설계가 아니다(사용자 확정 문구가 이미 *"전부 가져와서, 실제 count 로 둡니다"*였다). dep이Epoch면:Sync, State면:TrackFrom으로 갈리는 것도 같이 적었다. GateNode예외가 §4에 기록돼 있지 않았다. §4 의사코드는emitEpochMap:Update(from)을 수신 시점에 무조건 부르는데,gate-plan.md4번은 게이트가 전파할 때:Sync(batch)로 갱신하는 것으로 확정돼 있다. §4대로 구현하면 그 계약이 조용히 깨진다.- 설치 시 즉시 1회 발화에
from이 없다.fn(self, from)을 non-optional로 선언해뒀는데 등록 시점 발화엔 출처가 없다 —Effect의 확정 클로저가Update(nil)을 부르게 된다. → 옵셔널로 바꾸고,Effect의 억제 플래그가Update보다 먼저 와야 함을 명시(순서를 뒤집으면 설치 발화가 맵을 건드려 그 파동의 첫 진짜 emit이 접힌다).
근거·표기 정확성
2^32랩이 "똑같이 도달 불가능"이라는 근거가 틀렸다. 같은 척도(초당 100만Set)로2^53은 285년인데2^32는 약 72분이다(20만 배 차이). 실제로 안전한 이유는 도달 시간이 아니라 충돌 조건이 한 점이라는 것 ("정확히2^32만큼 뒤처진 항목"이라야 하고, 한 바퀴 중 한 번이라도 건드려지면 갱신됨). 이 문서가 바로 위 항목에서 "근거를 정확히 적을 것" 이라 스스로 규정해놓고 20줄 뒤에 어긴 셈이라 그대로 정정했다.:Sync를 "초기화에만 쓴다"고 적었으나gate-plan.md가 flush 경로에서도 쓴다 → "반환값이 필요 없다고 이미 아는 곳 둘"로 정정.ROADMAP.md의Brand.luaupredicate 목록에isEpoch누락.- 역전된 옛 이름 잔재 —
TweenTag(3곳)와Effect(fn, state?)(4곳). 후자는 code-review가 짚은 것보다 실제로 더 많았다 (architecture.md/lifecycle-hooks-plan.md/ROADMAP.md/README.md). - 절 재편에 안 따라온
§참조 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을 고른 근거 셋:
- 이 맵의 존재 이유를 사용자가 표현한 말이 "추적"이었다 — "'내가 뭘 추적하고 있나' 가 필요하죠"(§4의 시딩 규칙 근거). 코퍼스가 이 맵을 설명하는 말과 메소드 이름이 일치한다.
From이 방향을 못박아 비파괴가 드러난다 —Absorb가 실패한 지점.SeedFrom보다 오래 간다 — 동적 의존성으로 "생성 이후에 키를 더하는" 자리가 생겨도 이름이 그대로 맞다.Seed는 그때 거짓말이 된다.
배제한 것도 근거가 있다 — Extend/Inherit은 quad2-try의 Base:Extends
OOP 상속이 확인된 죽은 접근이라 그 어휘를 되살리면 오독을 부르고,
Extract는 코퍼스에서 이미 "소유권을 통째로 넘긴다"는 뜻이다.
두 이름 다 question.md에 안 올린다 — 같은 자리에서 확정됐으므로 열린
항목이 아니다.