design: 빈 배치 emit은 통지하지 않음 — 에이전트 권고 기각, Blocker 계약의 일반화

에이전트 권고("빈 배치 = 무조건 통지")를 사용자가 기각. 빈 배치는 "이미 다
던져서 더 던질 게 없다"는 뜻이고, 같은 카운트가 두 번 흘러든 emit을 전파하지
않는 것과 같은 자리다. 그걸 흘리는 건 표면적으로 State 중간에 Source:Emit을
추가하는 격이라 Gate의 성격과 안 맞는다.

확정: next(withheld) == nil이면 통지 자체를 안 한다. 새 규칙이 아니라 기존
계약의 일반화임을 확인 — blocker-plan.md가 이미 "HasBlockedEmit이 false면
emit 값과 무관하게 아무 것도 안 함(idempotent)"으로 확정해뒀고
HasBlockedEmit은 next(withheld) ~= nil의 특수형이다. Debounce/Throttle도
if pending일 때만 passThrough()를 부른다.

따름정리 — Effect(fn, ...deps)의 설치 구간 억제가 Gate 소비자에서 빠졌다.
설치 구간엔 어떤 Set도 안 일어나 게이트에 쌓이는 소스가 없으므로 게이트가
내보낼 것 자체가 없고, Effect 내부 플래그면 충분하다. effect-plan.md에서
"⚠️ 억제 장치의 모양은 Gate 설계에 딸려 있다"와 우선순위 문단의 "Gate보다
뒤다"라는 순서 제약이 같이 빠졌다.

이로써 Gate에 사용자 판단이 필요한 항목은 없다 — 남은 건 생명주기 계약과
M2 범위뿐이고 둘 다 구현 시 결정. 처리 전량은 V절. doc-check.py ERROR 0.

Co-authored-by: qwreey <me@qwreey.moe>
Claude-Session: https://claude.ai/code/session_01TiW21rnti9SbLgF6twtn6D
This commit is contained in:
qwreey 2026-08-21 22:35:58 +09:00
parent 46287ee393
commit a55e726808
Signed by: qwreey
GPG key ID: D28DB79297A214BD
7 changed files with 122 additions and 31 deletions

View file

@ -259,17 +259,22 @@ Effect의 의존성이 될 방법이 아예 없다.** 사용자 제기: *"Effect
가능하므로 그때마다). 채워지지 않은 상태는 발화가 아니다. 가능하므로 그때마다). 채워지지 않은 상태는 발화가 아니다.
- **최초 1회를 한 번만 돌리는 장치**: 의존성마다 구독을 걸면 각 구독의 "등록 - **최초 1회를 한 번만 돌리는 장치**: 의존성마다 구독을 걸면 각 구독의 "등록
즉시 1회 실행"이 N번 발화하므로, 설치 구간 동안 발화를 눌러뒀다가 마지막에 즉시 1회 실행"이 N번 발화하므로, 설치 구간 동안 발화를 눌러뒀다가 마지막에
한 번만 실행한다 — **`Blocker`의 "`state:Block()` 없이 직접 쓰는" 용례**를 한 번만 실행한다. **[2026-08-21 확정] 이건 `Effect` 내부 플래그로 한다 —
그대로 재사용(`base/blocker-plan.md`). **⚠️ 이 억제 장치의 정확한 모양은 게이트도 `Blocker`도 안 쓴다.** 한때 *"`Blocker`의 "`state:Block()` 없이
`Gate`(공용 게이트 노드) 설계에 딸려 있다** — `base/gate-plan.md` 직접 쓰는" 용례를 그대로 재사용"*이라 적고 정확한 모양을 `Gate` 설계에
닫힌 뒤에 확정할 것. 걸어뒀는데, `Gate`가 **빈 배치일 땐 통지를 안 하는 것**으로 확정되면서
성립하지 않는 게 확인됐다(`base/gate-plan.md`의 8번) — 설치 구간엔 어떤
`Set`도 안 일어나 게이트에 쌓이는 소스가 없으므로 게이트가 내보낼 것 자체가
없다. 설치 중 발화를 누르는 플래그 하나면 되고 새 메커니즘이 필요 없다.
- **leaf dedup/cascade가 전부를 덮어야 한다** — 의존성이 N개면 내부 Observer도 - **leaf dedup/cascade가 전부를 덮어야 한다** — 의존성이 N개면 내부 Observer도
N개라, `EffectHandle`의 bind/unbind cascade와 dedup 분기가 **그 전부**를 N개라, `EffectHandle`의 bind/unbind cascade와 dedup 분기가 **그 전부**를
같이 처리해야 한다(위 `E-10`/`EF-5`와 같은 함정). 사용자 확인: *"어차피 같이 처리해야 한다(위 `E-10`/`EF-5`와 같은 함정). 사용자 확인: *"어차피
모든 옵져버들이 내부에 들어가 있을것이므로 가능하다."* 모든 옵져버들이 내부에 들어가 있을것이므로 가능하다."*
**우선순위**: 새 코어 메커니즘이 아니라 `Effect` 표면 확장이므로 M3의 **우선순위**: 새 코어 메커니즘이 아니라 `Effect` 표면 확장이므로 M3의
`Effect` 구현과 같이 간다 — 다만 위 ⚠️(억제 장치) 때문에 `Gate`보다 뒤다. `Effect` 구현과 같이 간다. **[2026-08-21]** 여기 있던 "억제 장치 때문에
`Gate`보다 뒤"라는 순서 제약은 **없어졌다** — 억제가 `Effect` 내부 플래그로
확정돼 `Gate`에 안 걸린다.
## 해결됨 — Effect/Observer 관계 (2026-08-07 여섯 번째 세션, 이전 미해결 절 대체) ## 해결됨 — Effect/Observer 관계 (2026-08-07 여섯 번째 세션, 이전 미해결 절 대체)

View file

@ -5,8 +5,8 @@
없이 `state:Gate( (emit) -> ()->() )` 처럼 선언되고 마치 Compute 처럼 없이 `state:Gate( (emit) -> ()->() )` 처럼 선언되고 마치 Compute 처럼
GateNode(ComputeNode 처럼) 생성된다 그리고 Blocker 는 해당 내부 배선을 따른다 GateNode(ComputeNode 처럼) 생성된다 그리고 Blocker 는 해당 내부 배선을 따른다
← 동의합니다 해당 방법대로 확정하면 됩니다."* 구현은 **M2**("게이팅 먼저" ← 동의합니다 해당 방법대로 확정하면 됩니다."* 구현은 **M2**("게이팅 먼저"
결정, `ROADMAP.md`). **남은 것은 아래 "아직 안 정한 것"의 생명주기·빈 배치 emit·M2 결정, `ROADMAP.md`). **남은 것은 아래 "아직 안 정한 것"의 생명주기와 M2 범위뿐이고, 둘 다
범위뿐** — `/code-review high`가 잡았던 4번(유보된 emit이 싣는 출처)은 같은 구현 시 정하면 되는 것들이다 — 사용자 판단이 필요한 항목은 없다** — `/code-review high`가 잡았던 4번(유보된 emit이 싣는 출처)은 같은
`emit(self)` + 흡수 집합으로 닫혔고, **`setup` 시그니처는 안 바뀌었다.** `emit(self)` + 흡수 집합으로 닫혔고, **`setup` 시그니처는 안 바뀌었다.**
**⚠️ 처음 방향이 한 번 바뀌었다.** 신설 당시엔 *"공용 `Gate` 프리미티브를 꺼내고 **⚠️ 처음 방향이 한 번 바뀌었다.** 신설 당시엔 *"공용 `Gate` 프리미티브를 꺼내고
@ -224,25 +224,44 @@ end)
**flush 진입 시 스왑**이다. **flush 진입 시 스왑**이다.
- **같은 게이트/`Blocker` 인스턴스를 중첩해 쓰지 않는다**`Blocker` - **같은 게이트/`Blocker` 인스턴스를 중첩해 쓰지 않는다**`Blocker`
기존 규칙이 그대로 적용된다(겹치는 배치는 새 인스턴스). 기존 규칙이 그대로 적용된다(겹치는 배치는 새 인스턴스).
7. **⭐ 소비자가 하나 더 있다 — `Effect(fn, ...deps)`의 최초 1회 억제.** 7. **[2026-08-21 해소] `Effect(fn, ...deps)`의 최초 1회 억제는 `Gate`
소비자가 **아니다**.** 한때 이 용례까지 게이트가 커버해야 한다고 적어뒀으나,
위 8번(빈 배치는 통지 안 함)으로 **성립하지 않는 게 확인됐다** — 설치 구간엔
어떤 `Set`도 안 일어나 쌓이는 소스가 없으므로 게이트가 내보낼 것 자체가 없다.
`Effect`**자기 내부 플래그로** 설치 중 발화를 누르고 마지막에 한 번
직접 실행하면 되고, 새 메커니즘이 필요 없다. `base/effect-plan.md`의 그
항목에 달려 있던 "⚠️ `Gate` 설계에 딸려 있다"도 같이 해소됐다. 아래는
원 서술:
2026-08-21 5라운드 `C-6`에서 확정된 다중 의존성 `Effect`는, 의존성마다 구독을 2026-08-21 5라운드 `C-6`에서 확정된 다중 의존성 `Effect`는, 의존성마다 구독을
걸면 각 구독의 "등록 즉시 1회 실행"이 N번 발화하므로 **설치 구간 동안 발화를 걸면 각 구독의 "등록 즉시 1회 실행"이 N번 발화하므로 **설치 구간 동안 발화를
눌러뒀다가 마지막에 한 번만 실행**해야 한다(`base/effect-plan.md`의 그 절). 눌러뒀다가 마지막에 한 번만 실행**해야 한다(`base/effect-plan.md`의 그 절).
`Gate`(또는 `Blocker`의 직접 사용)가 **"설치 구간을 감싸 최초 발화를 한 `Gate`(또는 `Blocker`의 직접 사용)가 **"설치 구간을 감싸 최초 발화를 한
번으로 접는" 용례까지 커버해야** 한다 — 설계할 때 이 소비자를 같이 볼 것. 번으로 접는" 용례까지 커버해야** 한다 — 설계할 때 이 소비자를 같이 볼 것.
8. **⭐ [2026-08-21 신설, `/code-review high`] 소스 없는 emit — 빈 배치를 8. **[2026-08-21 해소] 소스 없는 emit(빈 배치) — 아무것도 안 한다.**
어떻게 다루나.** `state:Gate`는 공개 표면이므로 유저랜드 정책이 상류 신호와 `/code-review high`가 "정책이 상류 신호와 무관하게 flush를 부르면 빈 배치가
무관하게 `emit()`을 부를 수 있고, 위 3번 항목도 *"이 `emit`은 setup 밖으로 나가 하류가 조용히 삼킨다"를 문제로 제기했고, 에이전트는 "빈 배치 = 무조건
캡처해 **언제든** 부를 수 있다"*고 문서화한다. 그런데 지금 규칙대로면 그때 통지"를 권고했다. **사용자 기각**:
나가는 배치가 **비어 있어** 하류가 "전부 3번" 판정으로 **조용히 삼킨다.**
- 이건 아래 7번이 예고한 소비자에 직접 걸린다 — `Effect(fn, ...deps)` > *"빈 배치면 이미 하류로 한번 다 던져서 더 던질게 없다는 의미입니다. 마치
설치 구간 억제는 "어떤 Source 에포크와도 무관한 발화를 마지막에 한 번 > 두번 흘러들어온 같은 카운트의 emit 과 유사한데요. 그건 전파 안 합니다. …
내보내기"라, 지금 규칙으로는 성립하지 않는다. > 애초에 Gate 는 중간에 emit 을 할 수 있는 핸들을 노출하는, `Source:Emit`
- **에이전트 권고: 빈 배치 = 무조건 통지**(dedup 대상에서 제외). 명시적으로 > 같은걸 주는 요소도 아니고, 쌓아두다 뒤로 넘기는건데, 쌓아둔것 자체가
부른 명령형 통지라 접을 근거가 없고, 규칙에 한 줄만 추가하면 된다. > 없는데 뒤로 넘긴다는건 이상합니다."*
대안은 `Effect` 쪽을 게이트 없이 자체 플래그로 해결하고 게이트에는 이
용례를 아예 안 주는 것 — 그러면 3번 항목의 "언제든 부를 수 있다"를 **확정: `next(withheld) == nil`이면 통지 자체를 안 한다.** 그래야 다른
"상류 신호가 하나라도 쌓여 있을 때만 의미가 있다"로 좁혀 적어야 한다. State와 동작이 같아진다 — 빈 배치를 흘리는 건 표면적으로 **State 중간에
`Source:Emit`을 추가하는 격**이고, `Gate`는 그런 요소가 아니다.
- **새 규칙이 아니라 기존 계약의 일반화다.** `base/blocker-plan.md`는 이미
*"이미 `HasBlockedEmit`이 false면 `emit` 값과 무관하게 아무 것도 안 함
(idempotent)"*이라고 확정해뒀다 — 즉 `HasBlockedEmit`
`next(withheld) ~= nil`의 특수형이다. `Debounce`/`Throttle`도
`if pending`일 때만 `passThrough()`를 부른다
(`base/debounce-throttle-plan.md`의 `onWindowEnd`).
- **3번 항목의 "`emit`은 언제든 부를 수 있다"는 그대로 유효하다** — 언제
불러도 되지만, 쌓인 게 없으면 그 호출은 no-op이라는 뜻으로 읽는다.
- **따름정리: `Effect(fn, ...deps)`의 설치 구간 억제는 `Gate` 소비자가
아니다** — 아래 7번 참고.
9. **M2 범위.** M2에 `Gate`만 넣고 `Blocker`는 M3에 그대로 둘지, 아니면 9. **M2 범위.** M2에 `Gate`만 넣고 `Blocker`는 M3에 그대로 둘지, 아니면
`Blocker`까지 같이 앞당길지. `Dispatch.drive`의 배치 등록이 실제로 쓰는 건 `Blocker`까지 같이 앞당길지. `Dispatch.drive`의 배치 등록이 실제로 쓰는 건
`blocker:On()`/`OffWithoutEmit()`/`IsOn()`이므로(배치 게이팅 절), **최소한 `blocker:On()`/`OffWithoutEmit()`/`IsOn()`이므로(배치 게이팅 절), **최소한

View file

@ -102,6 +102,10 @@ count가 이미 최신이면 **캐시는 유효한 채로 남는다**(아래 2
갱신하고 **뒤로 emit** 한다 — `rawInvalid`는 안 건드린다. 갱신하고 **뒤로 emit** 한다 — `rawInvalid`는 안 건드린다.
3. 둘 다 같으면 → **삼킨다.** 3. 둘 다 같으면 → **삼킨다.**
- **다른 소스 항목은 건드리지 않는다** — 그 소스들은 자기가 직접 emit 하므로. - **다른 소스 항목은 건드리지 않는다** — 그 소스들은 자기가 직접 emit 하므로.
- **게이트는 배치가 비어 있으면 애초에 통지하지 않는다**(`base/gate-plan.md`의
8번) — 빈 배치를 흘리는 건 "쌓인 게 없는데 뒤로 넘기는" 꼴이라 State 층에
`Source:Emit`을 추가하는 것과 같아진다. 그래서 아래 규칙이 빈 집합을 받는
경우는 없다.
- **출처가 게이트 배치면** 그 집합을 순회하며 **각 소스에 위 1~3을 - **출처가 게이트 배치면** 그 집합을 순회하며 **각 소스에 위 1~3을
그대로 적용**하고, 하나라도 1번이나 2번에 걸렸으면 **받은 출처(그 게이트)를 그대로 적용**하고, 하나라도 1번이나 2번에 걸렸으면 **받은 출처(그 게이트)를
그대로** 뒤로 넘긴다. 전부 3번이면 삼킨다 — 다이아몬드에서 같은 해제 통지가 그대로** 뒤로 넘긴다. 전부 3번이면 삼킨다 — 다이아몬드에서 같은 해제 통지가

View file

@ -1231,3 +1231,43 @@ T절이 "재진입 위험"이라 부른 것은 **에이전트가 적은 "전파
**빈 배치 emit**(소스 없는 flush)을 무조건 통지로 볼 것인가 — **빈 배치 emit**(소스 없는 flush)을 무조건 통지로 볼 것인가 —
`gate-plan.md` 8번, `question.md` 3번. 나머지(생명주기, M2 범위)는 구현 시 `gate-plan.md` 8번, `question.md` 3번. 나머지(생명주기, M2 범위)는 구현 시
정하면 된다. 정하면 된다.
---
# V절 — 빈 배치 emit: 에이전트 권고 기각, "아무것도 안 함"으로 확정 (2026-08-21)
U절이 남긴 마지막 사용자 판단 항목이 같은 날 닫혔다. 에이전트 권고는
"빈 배치 = 무조건 통지"였고 **기각**됐다:
> *"빈 배치면 이미 하류로 한번 다 던져서 더 던질게 없다는 의미입니다. 마치
> 두번 흘러들어온 같은 카운트의 emit 과 유사한데요. 그건 전파 안 합니다. …
> 애초에 Gate 는 중간에 emit 을 할 수 있는 핸들을 노출하는, `Source:Emit`
> 같은걸 주는 요소도 아니고, 쌓아두다 뒤로 넘기는건데, 쌓아둔것 자체가 없는데
> 뒤로 넘긴다는건 이상합니다. … 비어있는걸 뒤로 넘겨 Effect 가 받게 만드는건
> … 표면적으로 보면 State 중간에 Emit 을 추가하는 격이라서요."*
**확정**: `next(withheld) == nil`이면 **통지 자체를 안 한다.**
- **새 규칙이 아니라 기존 계약의 일반화다**`base/blocker-plan.md`가 이미
*"이미 `HasBlockedEmit`이 false면 `emit` 값과 무관하게 아무 것도 안 함
(idempotent)"*으로 확정해뒀고, `HasBlockedEmit``next(withheld) ~= nil`
특수형이다. `Debounce`/`Throttle`도 `if pending`일 때만 `passThrough()`
부른다.
- **3번 항목의 "`emit`은 언제든 부를 수 있다"는 유지** — 언제 불러도 되지만
쌓인 게 없으면 no-op이라는 뜻.
## V-1. 따름정리 — `Effect`의 설치 구간 억제가 `Gate` 소비자에서 빠졌다
`gate-plan.md` 7번이 예고하던 소비자가 **성립하지 않는 게 확인됐다**: 설치
구간엔 어떤 `Set`도 안 일어나 게이트에 쌓이는 소스가 없으므로 게이트가
내보낼 것 자체가 없다. `Effect`**자기 내부 플래그로** 설치 중 발화를 누르고
마지막에 한 번 직접 실행하면 되고 새 메커니즘이 필요 없다.
그래서 `base/effect-plan.md`에서 둘이 같이 빠졌다 — 그 항목의 "⚠️ 이 억제
장치의 정확한 모양은 `Gate` 설계에 딸려 있다"와, 우선순위 문단의 "`Gate`보다
뒤다"라는 **순서 제약**.
## V-2. 현재 상태
`Gate`**사용자 판단이 필요한 항목은 없다.** 남은 건 생명주기 계약과
M2 범위(`Gate`만 vs `Blocker`까지)이고 둘 다 구현 시 정하면 된다.

View file

@ -220,13 +220,15 @@
(비워두면 순회가 훑을 목록 자체가 없어 "유효하다"로 오판한다 — *"'내가 뭘 (비워두면 순회가 훑을 목록 자체가 없어 "유효하다"로 오판한다 — *"'내가 뭘
추적하고 있나' 가 필요하죠"*). 그래서 `:With` 병합 규칙은 **필요 없어졌다.** 추적하고 있나' 가 필요하죠"*). 그래서 `:With` 병합 규칙은 **필요 없어졌다.**
재계산 시 갱신 범위(전부 갱신)도 확정. `base/state-epoch-plan.md`의 §2·§5 7번. 재계산 시 갱신 범위(전부 갱신)도 확정. `base/state-epoch-plan.md`의 §2·§5 7번.
- **⭐ [신설, 2026-08-21 두 번째 `/code-review high`] `Gate` — 소스 없는 - **[해소, 2026-08-21 같은 날] `Gate` — 소스 없는 emit(빈 배치)은 **아무것도
emit(빈 배치)을 어떻게 다루나. M2 착수 전.** 정책이 상류 신호와 무관하게 안 한다**.** 에이전트 권고("빈 배치 = 무조건 통지")는 **사용자 기각**
`emit()`(=flush)을 부르면 **빈 배치**가 나가 하류가 조용히 삼킨다. *"쌓아둔것 자체가 없는데 뒤로 넘긴다는건 이상합니다 … 표면적으로 보면 State
`Effect(fn, ...deps)`의 설치 구간 억제가 정확히 이 모양(어떤 소스 에포크와도 중간에 Emit 을 추가하는 격"*. 새 규칙도 아니다: `blocker-plan.md`가 이미
무관한 발화를 마지막에 한 번)이라 그 용례가 직접 걸린다. 권고는 **"빈 배치 = "`HasBlockedEmit`이 false면 아무 것도 안 함(idempotent)"으로 확정해뒀고,
무조건 통지"**(명령형 통지라 접을 근거가 없음). 상세는 `base/gate-plan.md` `withheld`는 그 플래그의 일반화다. **따름정리로 `Effect(fn, ...deps)`의 설치
8번. 구간 억제는 `Gate` 소비자가 아니게 됐다** — `Effect` 내부 플래그로 처리하고,
`effect-plan.md`에 있던 "`Gate`보다 뒤" 순서 제약도 사라졌다.
`base/gate-plan.md`의 7·8번.
- **같이 제기됐던 "재진입 계약"은 열린 항목이 아니었다**(사용자 지적으로 - **같이 제기됐던 "재진입 계약"은 열린 항목이 아니었다**(사용자 지적으로
2026-08-21 정리) — `blocker-plan.md`의 재진입은 **같은 인스턴스 중첩**을 2026-08-21 정리) — `blocker-plan.md`의 재진입은 **같은 인스턴스 중첩**을
말하는 것이지 정책의 `emit()` 호출과 무관하고, 끝나지 않는 되먹임은 말하는 것이지 정책의 `emit()` 호출과 무관하고, 끝나지 않는 되먹임은
@ -238,8 +240,9 @@
프리미티브 없이 state:Gate( (emit) -> ()->() ) 처럼 선언되고 마치 Compute 프리미티브 없이 state:Gate( (emit) -> ()->() ) 처럼 선언되고 마치 Compute
처럼 GateNode(ComputeNode 처럼) 생성된다"*). `Get()`엔 영향 없음(통지만 처럼 GateNode(ComputeNode 처럼) 생성된다"*). `Get()`엔 영향 없음(통지만
막음)까지 확정. **[2026-08-21 정정]** 여기 "남은 것은 사용자 판단이 아니라 막음)까지 확정. **[2026-08-21 정정]** 여기 "남은 것은 사용자 판단이 아니라
구현 시 정할 것들"이라 적었으나, 두 번째 `/code-review high`가 **빈 배치 구현 시 정할 것들"이라 적었으나, 두 번째 `/code-review high`가 빈 배치
emit을 사용자 판단 항목으로 되돌렸다**(바로 위 항목). 구현 시 emit을 잠시 사용자 판단 항목으로 되돌렸다 — **같은 날 해소돼(바로 위 항목)
원래 서술로 돌아왔다.** 구현 시
정하면 되는 건 생명주기와 M2 범위뿐 — `base/gate-plan.md` 정하면 되는 건 생명주기와 M2 범위뿐 — `base/gate-plan.md`
소스. 아래는 열려 있던 시점의 서술: 위 항목의 결정("게이팅 먼저")에 따라 base에 만들 것이 소스. 아래는 열려 있던 시점의 서술: 위 항목의 결정("게이팅 먼저")에 따라 base에 만들 것이
`Blocker`가 아니라 **상류 emit을 가로채 정책이 통과 여부를 정하는 공용 게이트 `Blocker`가 아니라 **상류 emit을 가로채 정책이 통과 여부를 정하는 공용 게이트

View file

@ -363,3 +363,17 @@ emit(빈 배치)"은 사용자 판단 항목으로 승격 — 후자는 `Effect(
그대로 유효하고 서술만 고쳤다. 그대로 유효하고 서술만 고쳤다.
그래서 `Gate`에 남은 **사용자 판단은 빈 배치 emit 하나뿐**이다. 전량은 U절. 그래서 `Gate`에 남은 **사용자 판단은 빈 배치 emit 하나뿐**이다. 전량은 U절.
## 18. 빈 배치 emit — 에이전트 권고 기각, 기존 Blocker 계약의 일반화로 확정
마지막 열린 항목. 에이전트는 "빈 배치 = 무조건 통지"를 권고했으나 사용자가
기각했다 — 빈 배치는 "더 던질 게 없다"는 뜻이고 그걸 흘리는 건 State 층에
`Source:Emit`을 추가하는 격이다. 확정은 **통지 자체를 안 함**이고, 확인해보니
새 규칙도 아니었다: `blocker-plan.md`의 "`HasBlockedEmit`이 false면 아무 것도
안 함(idempotent)"이 이미 같은 계약이고 `withheld`가 그 플래그의 일반화다.
따름정리로 `Effect(fn, ...deps)`의 설치 구간 억제가 **`Gate` 소비자에서
빠졌다** — 설치 구간엔 `Set`이 없어 쌓일 게 없으므로 게이트가 내보낼 것이
없고, `Effect` 내부 플래그면 충분하다. `effect-plan.md`의 "`Gate`보다 뒤"
순서 제약도 같이 사라졌다. 이로써 `Gate`에 사용자 판단 항목은 남지 않았다.
전량은 V절.

View file

@ -18,7 +18,13 @@
제기됐던 에포크 쪽 세 자리도 **전량 확정**됐다(재계산 시 count 전부 갱신 / 제기됐던 에포크 쪽 세 자리도 **전량 확정**됐다(재계산 시 count 전부 갱신 /
새 노드는 `sourceEmitMap`은 비우고 `sourceCountMap`은 실제 count로 채운 뒤 새 노드는 `sourceEmitMap`은 비우고 `sourceCountMap`은 실제 count로 채운 뒤
`rawInvalid = true` / 그래서 `:With` 병합 규칙은 불필요) — `rawInvalid = true` / 그래서 `:With` 병합 규칙은 불필요) —
`state-epoch-plan.md` §5 7번. `state-epoch-plan.md` §5 7번. **[같은 날 두 번째 `/code-review high`]**
7건이 더 나왔고 전부 유효했는데(재진입 시 빈 배치가 새어 변경이 증발하던
것, `OffWithoutEmit`이 흡수 집합을 안 비우던 것 등), 그중 사용자 판단으로
올라갔던 둘도 같은 날 닫혔다 — "재진입 계약"은 애초에 **잘못 옮긴
서술**이었고, "빈 배치 emit"은 **아무것도 안 하는 것**으로 확정(그 따름정리로
`Effect`의 설치 구간 억제가 `Gate` 소비자에서 빠지며 `effect-plan.md`
순서 제약도 사라짐). **다시, M2 착수를 막는 설계 항목은 없다.**
그 외 남은 것은 판단이 아니라 구현 시 정할 것들 — `Gate` 그 외 남은 것은 판단이 아니라 구현 시 정할 것들 — `Gate`
생명주기·재진입 계약, M2에 `Blocker`까지 넣을지, 스파이크 `05` 재작성 생명주기·재진입 계약, M2에 `Blocker`까지 넣을지, 스파이크 `05` 재작성
(`luau-test/STATUS.md`). (`luau-test/STATUS.md`).