diff --git a/.claude/base/blocker-plan.md b/.claude/base/blocker-plan.md index 26587dd..6e7b44a 100644 --- a/.claude/base/blocker-plan.md +++ b/.claude/base/blocker-plan.md @@ -67,6 +67,13 @@ gated state의 동작: `OffWithoutEmit()`은 "밀린 전파를 버리며 끈다" — 어느 쪽이든 대기 상태(`HasBlockedEmit`)는 항상 깨끗하게 리셋됨. +**⭐ [2026-08-21] `HasBlockedEmit`은 게이트 흡수 집합의 특수형이다.** +`GateNode`가 드는 `withheld`(이번에 유보한 소스들)에 대해 +`HasBlockedEmit == (next(withheld) ~= nil)`이고, 아래 "밀린 전파가 없으면 +아무 것도 안 함(idempotent)"이 게이트 층위에서는 **"빈 배치면 통지 자체를 +안 한다"**로 일반화된다(`base/gate-plan.md`의 8번). 구현 시 두 개를 따로 +들지 말 것. + **⭐ [2026-08-21 신설] `state:Block(blocker)`는 `state:Gate(setup)` 위에 얹힌다.** 위 "gated state의 동작"은 `Blocker`만의 특수 노드가 아니라 `base/gate-plan.md`가 확정한 **`GateNode`**(`ComputeNode`와 같은 층위)의 diff --git a/.claude/base/brand-plan.md b/.claude/base/brand-plan.md index 42db944..39da189 100644 --- a/.claude/base/brand-plan.md +++ b/.claude/base/brand-plan.md @@ -7,6 +7,15 @@ **상태**: base — 동작/구현 방식은 확정, **이름 `Brand` 자체만 용어 정리 대기**(`question.md` 1번). +**⚠️ [2026-08-21] 이 문서를 전면 재작성하는 제안이 `research/`에 대기 중이다** — +단일 공유 레지스트리 + `Brand.get(x) -> tag`(객체당 태그 하나)를 **인스턴스 +브랜드**(`Brand()` + `:register`/`:is`, **다중 태깅 허용**)로 바꾸는 안 +(`research/epoch-brand-composition.md`). 발단은 `Source`가 `SourceBrand`이면서 +동시에 `EpochBrand`여야 하는데 지금 구조로는 표현이 안 되는 것. **역조회 +`Brand.get`은 불필요로 확정**됐고, 아래 "포함 관계가 코드 모양에 드러난다"는 +성질은 **그대로 유지된다**(predicate 합성을 계속 쓰면 됨). **커밋된 M1 코드는 +아직 `Brand`를 안 쓰므로 전환 비용은 문서뿐.** 승격 전엔 이 문서가 정본. + ## `Brand` — 런타임 nominal 타입 판별 통합 메커니즘, `isState`를 일반화 (2026-08-07 여덟 번째 세션) **배경**: `isState`(2026-08-07 다섯 번째 세션 확정, `:Peek<>(key): diff --git a/.claude/base/effect-plan.md b/.claude/base/effect-plan.md index 7f11d4f..6668a05 100644 --- a/.claude/base/effect-plan.md +++ b/.claude/base/effect-plan.md @@ -266,6 +266,21 @@ Effect의 의존성이 될 방법이 아예 없다.** 사용자 제기: *"Effect 성립하지 않는 게 확인됐다(`base/gate-plan.md`의 8번) — 설치 구간엔 어떤 `Set`도 안 일어나 게이트에 쌓이는 소스가 없으므로 게이트가 내보낼 것 자체가 없다. 설치 중 발화를 누르는 플래그 하나면 되고 새 메커니즘이 필요 없다. +- **⚠️ [2026-08-21 신설 — 미해결] 의존성들이 공통 상류를 공유하면 한 파동에 + `fn`이 여러 번 돈다.** `A → b`, `A → c`, `Effect(fn, b, c)`에서 `A:Set()` + 한 번에 `b`가 자기 observer를, `c`가 자기 observer를 **각각 정당하게** 깨워 + `fn`이 두 번 돈다. **소스 에포크 dedup으로는 안 접힌다** — `b`와 `c`는 서로 + 다른 노드라 접어줄 **공통 하류가 없고**, 둘 다 규칙 1에 정당하게 걸린다 + (`base/state-epoch-plan.md`). 위의 "설치 구간 억제"도 이건 안 덮는다(그건 + 등록 시점만). + - **해법 후보**: `Effect`가 **자기 `EpochMap`을 하나 들고** 각 내부 observer가 + 그걸 `Update`해서 첫 번째만 통과시키는 것 — + `research/epoch-brand-composition.md`가 정확히 이걸 노린 제안이고 + 승격 대기 중이다. (검토했다 접은 대안: deps를 하나의 파생 노드로 수렴시켜 + 다이아몬드 dedup에 태우기 — 노드가 늘고 "N deps → N observers" 구조를 + 바꿔야 해서 위 안보다 못하다.) + - **그 제안을 안 쓴다면** `useEffect`처럼 "N번 돌아도 무방"으로 계약을 + 명시하는 선택지도 있다. **어느 쪽이든 M3 구현 전에 정할 것.** - **leaf dedup/cascade가 전부를 덮어야 한다** — 의존성이 N개면 내부 Observer도 N개라, `EffectHandle`의 bind/unbind cascade와 dedup 분기가 **그 전부**를 같이 처리해야 한다(위 `E-10`/`EF-5`와 같은 함정). 사용자 확인: *"어차피 diff --git a/.claude/base/gate-plan.md b/.claude/base/gate-plan.md index 64adf6a..50ab254 100644 --- a/.claude/base/gate-plan.md +++ b/.claude/base/gate-plan.md @@ -16,6 +16,12 @@ GateNode(ComputeNode 처럼) 생성된다 그리고 Blocker 는 해당 내부 표면을 주기도 하구요."* 아래 본문 중 "공개 프리미티브로 꺼낸다"류 서술은 그 이전 시점 표현이니 이 배너 기준으로 읽을 것. +**⚠️ [2026-08-21] emit 페이로드 표현을 바꾸는 제안이 `research/`에 대기 중** +(`research/epoch-brand-composition.md`) — 하류가 게이트 identity를 한 번도 안 +쓰므로 페이로드를 `Epoch|{Epoch}`로 일반화하는 안. 아래 4번의 **기제(흡수 집합, +flush 시 스왑, 게이트-게이트 unfold, 빈 배치 무통지)는 그대로 유효**하고 넘기는 +값의 타입만 바뀐다. 승격 전엔 이 문서가 정본. + **한 줄**: `Blocker`가 쓰던 "게이티드 State 노드"를 한 겹 일반화해서, **상류 emit을 가로채 내려보낼지 말지를 정책이 정하는** 노드를 **`state:Gate(setup)` 공개 메소드**로 낸다(탑레벨 생성자가 아니라 — 위 배너 참고). diff --git a/.claude/base/source-state-plan.md b/.claude/base/source-state-plan.md index ac167b5..beb696c 100644 --- a/.claude/base/source-state-plan.md +++ b/.claude/base/source-state-plan.md @@ -180,6 +180,14 @@ Handler가 애초에 다른 층위. 관련해서 Handler를 담는 엔진(`Dispa 왜 프리미티브가 아니라 탑레벨 싱글톤인지는 `base/dispatch-core-plan.md`의 "Dispatch는 프리미티브가 아니다" 절 참고. +> **⚠️ [2026-08-21] 이 문서의 두 자리를 바꾸는 제안이 `research/`에 대기 중** +> (`research/epoch-brand-composition.md`, 승격만 남음) — (1) `Source`가 +> **`Epoch` 인터페이스**(`{Revision: number}`)를 구조적으로 만족한다는 서술 +> 추가(`State`를 만족하는 기존 패턴과 같은 모양, 리비전 필드는 **공개**여야 +> 타입 레벨에서 성립), (2) 아래 "`state:Observer(fn)`" 절의 클로저가 +> `:Compute`와 같은 모양인 **`fn(self, from: Epoch|{Epoch})`**로 인자를 받음. +> 값은 여전히 안 실어주므로 그 계약은 안 깨진다. + ## 전파 모델 확정: push-invalidate(신호만) / pull-recompute(`Get()` 시점에만) **Fusion식 eager 노드·생성순 정렬은 안 만듦.** diff --git a/.claude/base/state-epoch-plan.md b/.claude/base/state-epoch-plan.md index b119ad2..4647a5c 100644 --- a/.claude/base/state-epoch-plan.md +++ b/.claude/base/state-epoch-plan.md @@ -9,6 +9,12 @@ 얹는다.** 그 절이 원래 갖고 있던 두 서술은 이 채택으로 뒤집혔고(아래 §3), 역전 원문은 `archive/always-propagate-no-dedup-superseded.md`에 있다. +**⚠️ [2026-08-21] 이 문서의 구조를 바꾸는 제안이 `research/`에 대기 중이다** — +두 맵을 `EpochMap`으로 컴포지션하고 `Source` 대신 **`Epoch` 인터페이스**로 +일반화하는 안(`research/epoch-brand-composition.md`). **사실상 전량 확정됐고 +승격만 남았다**(같은 날 세션이 길어져 다음 세션으로 미룸). 여기 적힌 규칙 자체는 +그대로 성립하고 표현만 바뀐다 — 승격 전엔 이 문서가 정본. + **읽는 순서**: 규칙만 필요하면 **§2**만 보면 된다. §1은 왜 이걸 하는가(실재하는 glitch), §3은 무엇이 고쳐지는가, §5는 세부 계약, §7은 구현 시 확인할 것. diff --git a/.claude/question.md b/.claude/question.md index 43898fa..cfbd300 100644 --- a/.claude/question.md +++ b/.claude/question.md @@ -68,6 +68,12 @@ 만들고 `state:Gate(setup)` 메소드로 가면서 `Gater` 문제 자체가 사라짐 — 메소드 자리에서는 `:With`/`:Compute`와 나란히 자연스럽다). 노드 타입 이름은 `GateNode`. `base/gate-plan.md`의 1번이 소스. +- **`Epoch`의 리비전 필드 증가 방식(3순위, 2026-08-21 신설)**: `bit32` 랩 + (uint32 랩어라운드라 double 포화가 없음, 사용자가 염두에 둔 쪽) vs 평이한 + `+1`(할당·연산 더 쌈, 포화가 `2^53`이라 `bit32`의 `2^32` 랩보다 충돌 거리가 + 오히려 넓음). **둘 다 도달 불가능이라 실질 위험은 없고 취향 문제다.** + 필드 이름은 `Revision` 권고(코퍼스 공개 필드 PascalCase 관례). + `research/epoch-brand-composition.md` §4의 4번. - **`Slot`(2순위)**: Vue의 "slot"(콘텐츠 주입 지점)과 이름은 같지만 의미가 다름(quad의 Slot은 자식 배열 재조정 프리미티브) — Vue 배경 있는 사람이 헷갈릴 수 있음. diff --git a/.claude/session-summary.md b/.claude/session-summary.md index 89620ec..83be192 100644 --- a/.claude/session-summary.md +++ b/.claude/session-summary.md @@ -1654,7 +1654,7 @@ quad-제작 Instance는 **GC 폴백이 없다**는 걸 근거로 보존 주체 안에선 동시 만족 불가라는 진단이 근거였고, 사용자가 "지금 의사코드를 건들이는 비용이 추후 실수가 누적되는 비용보다 싸다"로 확정. -## 2026-08-21-02 — QA 5라운드(문항지·회신·1차 처리) + `Gate`/에포크 리서치 신설 +## 2026-08-21-02 — QA 5라운드 전량 처리 + `Gate` 표면·State 에포크 확정, `Epoch`/`Brand` 제안 원문: `session/2026-08-21-02-qa-round5-and-gate-epoch-research.md` @@ -1688,3 +1688,41 @@ lazy화, `KeyGone`엔 새 값 반환도 error, **`Owned=false`에서 `Detach`는 물리보다 먼저")이 역전**됐다 — base엔 자리를 비워둘 수단이 없고 미는 주체는 백엔드 삽입 연산 자신이라, 규칙이 "자기 자리 먼저 / 뒤를 미는 것 나중" 하나로 줄었다. **`Gate`만 사용자 지시로 다음 세션 — M2를 막는 유일한 항목.** + +**같은 세션 후반 — 리서치로 신설했던 둘이 그 자리에서 확정됐다.** +`Gate`는 **탑레벨 프리미티브 없이 `state:Gate(setup)` 메소드 + `GateNode`**로, +State의 재계산/전파 판정은 **소스 에포크 비교 채택**으로 닫히며 두 문서가 +`research/` → `base/`로 승격(`base/gate-plan.md`, `base/state-epoch-plan.md`). +그 여파로 `source-state-plan.md`의 확정 서술 둘("emit은 **항상** 전파 / quad가 +접지 않는 것은 중복 *통지*뿐")이 역전돼 +`archive/always-propagate-no-dedup-superseded.md`로 갔다 — **2026-08-14의 +`invalid` 기반 dedup 금지를 되돌린 게 아니라는 것**을 세 곳에 못박았고, +스파이크 `05`는 핵심 assert가 정반대가 되어 `rewrite-required/`로 돌아갔다. + +**에포크 기제는 네 라운드에 걸쳐 사용자 정정으로 다듬어졌다** — 순회 조건이 +`rawInvalid == false`로 뒤집히고, emit은 count 없이 출처만 싣고, 순회가 값만 +앞당기고 통지는 상류 emit을 기다리게 하려고 맵이 +`sourceCountMap`/`sourceEmitMap` 둘로 갈렸다(에이전트가 한 번 철회했던 분리가 +**다른 근거로** 되살아난 것). 새 노드의 두 맵은 **비대칭 초기화** +(emit 맵은 비우고, count 맵은 실제 값으로 채운 뒤 `rawInvalid = true`). +게이트는 흡수 집합을 들고 **flush 시 스왑**해 배치를 떼어 넘기며, 게이트가 +게이트 emit을 받으면 **풀어서 자기 집합에 합치고**, **빈 배치면 통지 자체를 +안 한다**(기존 `HasBlockedEmit` 계약의 일반화). 그 따름정리로 +`Effect`의 설치 구간 억제가 `Gate` 소비자에서 빠졌다. + +**`/code-review high`를 두 번 돌려 19건이 나왔고 전부 유효했다** — 그중 +재진입 시 빈 배치가 새던 것과 `OffWithoutEmit`이 흡수 집합을 안 비우던 것은 +실제 유실 경로였다. 반대로 리뷰가 제기한 것 중 **에이전트 서술 자체가 +틀렸던 것도 셋** — "Gate 재진입 계약"은 `Blocker`의 인스턴스 중첩 규칙을 +잘못 옮긴 것이었고, "다중 소스 배치의 중복 계산"은 동기 전파를 놓친 것이었고, +"빈 배치 = 무조건 통지" 권고는 State 층에 `Source:Emit`을 추가하는 격이라 +기각됐다. + +**마지막으로 사용자가 `Epoch`/`EpochMap`/`Brand` 재구성을 제안**했다 — 에포크 +부기를 State에서 떼어 컴포지션하고, emit 페이로드를 `Epoch` 인터페이스 +(`{Revision: number}`, 그 자체로 키)로 일반화하고, 그러려면 `Brand`를 +**인스턴스 브랜드**(다중 태깅)로 바꾸는 안. 발단은 **다중 의존성 `Effect`에서 +한 파동에 `fn`이 두 번 도는 갭**(공통 하류가 없어 에포크 dedup이 못 접는다)이고, +`Effect`가 자기 `EpochMap`을 들면 그게 공통 하류가 되어 닫힌다. **사실상 전량 +확정됐으나 세션 길이 때문에 `base/` 승격은 다음 세션으로 미뤄졌다** — +`research/epoch-brand-composition.md`가 소스, `todos.md` 000번이 진입점. diff --git a/.claude/session/2026-08-21-02-qa-round5-and-gate-epoch-research.md b/.claude/session/2026-08-21-02-qa-round5-and-gate-epoch-research.md index a2061c3..a644e83 100644 --- a/.claude/session/2026-08-21-02-qa-round5-and-gate-epoch-research.md +++ b/.claude/session/2026-08-21-02-qa-round5-and-gate-epoch-research.md @@ -377,3 +377,27 @@ emit(빈 배치)"은 사용자 판단 항목으로 승격 — 후자는 `Effect( 없고, `Effect` 내부 플래그면 충분하다. `effect-plan.md`의 "`Gate`보다 뒤" 순서 제약도 같이 사라졌다. 이로써 `Gate`에 사용자 판단 항목은 남지 않았다. 전량은 V절. + +## 19. `Epoch`/`EpochMap`/`Brand` 제안, 그리고 핸드오버 (세션 종료) + +에이전트가 "다중 의존성 `Effect`에서 한 파동에 `fn`이 두 번 돈다"는 갭을 +제기하자(`A → b`, `A → c`일 때 접어줄 공통 하류가 없어 에포크 dedup이 +못 접는다), 사용자가 그걸 계기로 더 큰 재구성을 제안했다 — 에포크 부기를 +State에서 떼어 `EpochMap`으로 컴포지션하고, emit 페이로드를 `Epoch` +인터페이스로 일반화하고, 그러려면 `Brand`를 인스턴스 브랜드(다중 태깅)로 +바꾸는 것. `Effect`가 자기 `EpochMap`을 들면 **그 지점이 공통 하류가 되어** +갭이 닫힌다. + +두 차례 회신으로 사실상 전량 확정됐다. 그 과정에서 에이전트 주장 셋이 +반증됐다 — (a) "포함 관계가 생성자로 흩어진다"는 자기 등록이 다중 등록을 +강제하지 않으므로 틀렸고, (b) "재계산 후 sync 연산이 따로 필요하다"는 +`Update`가 `{Epoch}`를 받는다는 걸 놓친 것이었으며, (c) "오버플로해도 +다르다"는 사용자 근거는 double이 랩이 아니라 **포화**한다는 점에서 +정정됐다(도달 불가능이라 결론은 유지). + +**승격은 사용자 지시로 다음 세션** — *"전면 승격을 하고 싶지만, 이 세션은 +너무 길어요."* 감사도 승격 이후에 돌리기로 했다. 핸드오버로 `base/` 네 +문서(`state-epoch-plan`/`source-state-plan`/`effect-plan`/`brand-plan`) +상단에 이 제안을 가리키는 ⚠️ 배너를 달았고, `todos.md`에 **000번**을 신설해 +다음 세션의 첫 작업으로 지정했다. 다중 dep `Effect` 갭 자체도 +`effect-plan.md`에 미해결로 기록했다 — 제안이 기각되더라도 갭은 남으므로. diff --git a/.claude/todos.md b/.claude/todos.md index 9bc8ffe..79be7fb 100644 --- a/.claude/todos.md +++ b/.claude/todos.md @@ -5,6 +5,24 @@ (`.claude/question.md`, `luau-test/STATUS.md` 등). +000. **⭐⭐⭐ [2026-08-21 신설] 다음 세션의 첫 작업 — `Epoch`/`EpochMap`/`Brand` + 제안을 `base/`로 승격할 것.** 설계는 **사실상 전량 확정**됐고 + (`research/epoch-brand-composition.md`가 소스, §4에 결정 상태 전부), + 같은 날 세션이 너무 길어져 **사용자 지시로 승격만 다음 세션에 미뤘다** + (*"전면 승격을 하고 싶지만, 이 세션은 너무 길어요. 컨텍스트 피로 상 + 핸드오버 이후 승격조치를 해야할것 같습니다"*). + - **고쳐야 할 문서 넷**: `base/state-epoch-plan.md`(두 맵 → `EpochMap` 둘, + "Source의 에포크" → `Epoch`), `base/source-state-plan.md`(`Source`가 + `Epoch`를 구조적으로 만족 + `Observer` 클로저가 `fn(self, from)`), + `base/effect-plan.md`(`Effect`가 자기 `EpochMap`을 들어 다중 dep 중복 + 발화를 접음 — 그 문서의 ⚠️ 미해결 항목이 이걸로 닫힌다), + `base/brand-plan.md`(인스턴스 브랜드로 전면 재작성). + 넷 다 상단에 이 제안을 가리키는 ⚠️ 배너가 이미 달려 있다. + - **남은 미정은 하나** — 리비전 증가를 `bit32` 랩으로 할지 평이한 `+1`로 + 할지(둘 다 실질 위험 없음, `question.md` 1번). + - **감사는 승격 이후에 돌린다**(사용자 지시) — 지금 돌리면 곧 바뀔 서술을 + 감사하게 된다. + 00. **⭐⭐ [2026-08-18 신설] 구현 전 QA — [2026-08-21] 1~5라운드 전부 `base/` 반영 완료. ⭐ 같은 날 마지막에 `Gate`와 State 에포크까지 확정되면서 **M2 착수를 막는 설계 항목은 더 이상 없다.**** `Gate`는