diff --git a/.claude/base/source-state-plan.md b/.claude/base/source-state-plan.md index 1dcd463..08d7a0e 100644 --- a/.claude/base/source-state-plan.md +++ b/.claude/base/source-state-plan.md @@ -501,10 +501,11 @@ gc되긴 하지만.)"* 확신한다"*는 경로가 생기지 않고, `:Subscribe()`의 공개 계약 두 문장 (*"참조를 아무 데도 안 담아도 정상"* / *"GC되지 않고 영원히 계속 실행됨"*)이 서로 모순 없이 성립한다. -- **남은 것은 실측 스파이크 하나** — `luau-test`에 "상류 strong / 하류 weak" - 불변식을 음성 대조군까지 확인하는 파일을 추가한다 - (`07-relate-weak-table-gc.luau`가 연쇄 GC를 이미 다루므로 그 옆에). - **M2 착수 게이트는 아니다**(`question.md` 최우선 절에서 내려갔다). +- **[2026-08-28 닫힘] 실측은 `quad-base/test/spec.state.luau` 11번이 한다** — 양성(말단을 + 들고 있으면 루트·중간 노드가 GC를 넘긴다)과 음성(말단을 놓으면 하류가 수거되고 상류의 + 구독자 집합에서 사라진다) 둘 다. 한때 여기 *"남은 것은 실측 스파이크 하나 — + `luau-test`에 … 파일을 추가한다"*로 열려 있었고 **M2 착수 게이트는 아니었다** + (`luau-test/STATUS.md`의 그 행도 같은 날 닫혔다). **결론**: 노드별 캐시 유지(현재 모델) 유지, 플래튼 기각. Modifier가 플래튼+클론을 쓰는 건 애초에 캐싱이 필요 없는 정적 데이터라 성립하는 diff --git a/.claude/qa-request/pre-implementation-handtrace-round11.md b/.claude/qa-request/pre-implementation-handtrace-round11.md index 7f29fda..9227333 100644 --- a/.claude/qa-request/pre-implementation-handtrace-round11.md +++ b/.claude/qa-request/pre-implementation-handtrace-round11.md @@ -230,10 +230,10 @@ lazy 하게 읽으면 되는거 아냐? Set 재진입 같은 경우는, 반복 - `Store`: `H-122`/`H-153`/`H-83` 전부 `spec.store`. `Names()`는 `pairs(self)` — 메소드가 `__index`라 안 센다는 `H-153` 전제가 실측으로 성립. - **조립 세부**(`H-174`의 구현, 새 표면 아님): `State.luau`는 `Init(module)`이 인스턴스별 임플을 - 만들고 `implFor(module)`로 `Source`/`Store` Init에 건넨다(각 Init이 `module:RunInit(dep)`로 + 만들고 `implFor(module)`로 `Source` Init에 건넨다(`Store` Init은 이 임플을 직접 안 받고 `module.Source`를 통해서만 State에 닿는다; 각 Init이 `module:RunInit(dep)`로 의존성을 직접 당겨오므로 `New()` 순서 무관 — `H-177`; `implFor`의 level 1 error는 그래도 남겨둔 불변식 검사). `Source`의 `__index` 체인이 그 임플을 가리켜 `With`/`Compute`/ - `Apply`가 위임된다. `Ref`/`Void`/`Relate`/`Brand`만 인스턴스 간 공유 잎. + `Apply`가 위임된다. `Ref`/`Void`/`Relate`/`Brand`/`EpochMap`만 인스턴스 간 공유 잎. - **입력 검증 하나 추가**(`architecture.md` error 계약의 예 *"dep #3 is not a State/Source/Ref"* 그대로): `newNode`가 dep이 `isState`가 아니면 `error(…, 3)` — 없으면 `dep._subs` nil 인덱스로 quad 내부 줄에서 죽는다. 새 메커니즘이 아니라 계약이 이미 요구하는 자리라 ①로 둔다. diff --git a/quad-base/src/State.luau b/quad-base/src/State.luau index dad9be6..582b061 100644 --- a/quad-base/src/State.luau +++ b/quad-base/src/State.luau @@ -7,7 +7,9 @@ closure, so later units (`Observer`, `Gate`) can read lifetime gates as `module.canExecute(...)` AT FIRE TIME (never captured at init — the backend overwrites those fields after `New()`). `implFor(module)` hands - the same table to `Source.luau`/`Store.luau` inits. + the same table to `Source.luau`'s init (its only caller — `Store.luau` + never touches it; it reaches State only through the `Source` values it + creates). There is no runtime `State` constructor — nodes exist only as results of `:Compute`/`:With` (and `:Gate`, unit 4). `State` is NOT an `Epoch` @@ -191,7 +193,7 @@ end local function implFor(module: any) local impl = implByModule[module] if impl == nil then - error("State: InitState(module) has not run for this quad instance — RunInit order is broken", 1) + error("State: InitState(module) has not run for this quad instance", 1) -- invariant check; every caller pulls it in first (`H-177`) end return impl end