feat: M3 단위 1 — Handler 계약 타입(quad-types 소유) + Quad.Dispatch 필드(H-25)

- quad-types: `Handler`(isHandlable/priority/process→retractor 3종) /
  `Dispatch`(getHandler/process/retractFrom/addHandler/listHandlers/drive +
  HANDLER_PRIORITY_* 밴드) export type 신설, `Quad`에 `Dispatch` 필드
  (H-25 — 닫힌 레코드라 없으면 quad.Dispatch 접근이 analyze 타입에러)
- quad-base/src/Dispatch/Handler.luau: 타입 전용 잎(§6 계획) — quad-types
  재export만, Dispatch 되참조 없음(단방향 의존)
- H-165 예고대로 pesde shim 재생성 필요했음 — `pesde install` 재실행으로 해소

Co-authored-by: qwreey <me@qwreey.moe>
Claude-Session: https://claude.ai/code/session_01LF78pXeFGD1ZSVD3ifteYG
This commit is contained in:
qwreey-agent-selene 2026-08-31 15:13:08 +09:00
parent f14ba09cd6
commit 590b0fe6a1
No known key found for this signature in database
3 changed files with 64 additions and 0 deletions

View file

@ -0,0 +1,30 @@
--[[
Handler — the handler contract, types only. `.claude/base/dispatch-core-plan.md`
"핸들러 계약" section, as-is:
isHandlable(inst, key, value) -> boolean -- pure, fast predicate (no side
-- effects; validation happens
-- *after* selection, inside process)
priority: number -- open numeric space; use the
-- HANDLER_PRIORITY_* band constants
-- (`Dispatch/init.luau`) ± offsets
process(inst, key, value, index) -> retractor
-- does the work and returns the 1-arg closure that undoes exactly what
-- this call did. Returning nil is a contract violation (Dispatch errors
-- immediately); return `Void` when there is nothing to undo (`H-162`).
-- The retractor's argument is either nil (plain retract) or a new value
-- this same handler is about to process — nothing else can arrive
-- (descending diff, "Dispatch 체인" section).
This file is a leaf on purpose: concrete handlers and `Dispatch/init.luau`
both depend on it, never the other way around ("Dispatch는 프리미티브가
아니다" — the dependency is one-way). The type itself is owned by
`quad-types` (so `Quad.Dispatch.addHandler` and backends share it) and only
re-exported here.
]]
local QuadTypes = require("../../roblox_packages/quad_types")
export type Handler = QuadTypes.Handler
return {}

View file

@ -126,6 +126,35 @@ export type Source<T> = State<T> & {
Emit: (self: Source<T>) -> Source<T>,
}
-- ── 디스패치 엔진(M3 단위 1) ──────────────────────────────────────────
-- `Handler` — 핸들러 계약 **3종**(`.claude/base/dispatch-core-plan.md` "핸들러
-- 계약"): `isHandlable`은 부작용 없는 순수 판별(inst도 받음), `process`는 자기
-- 자신을 무르는 1-인자 retractor를 반환(생략 불가 — 정리할 게 없어도 `Void`).
-- retractor 인자는 `nil`(단순 철거)이거나 같은 핸들러가 처리할 새 값, 둘뿐.
export type Handler = {
isHandlable: (inst: any, key: any, value: any) -> boolean,
priority: number,
process: (inst: any, key: any, value: any, index: number) -> (nextValue: any?) -> (),
}
-- `Dispatch` — 스캔/실행 엔진(`dispatch-core-plan.md` "Dispatch 체인" /
-- "우선순위 동률/매치 실패 처리"). 프리미티브가 아니라 모듈 인스턴스별
-- 네임스페이스(`InitDispatch(module)`가 설치, 멤버는 lowercase 네임스페이스
-- 규칙 — `architecture.md` "코드 스타일"). `HANDLER_PRIORITY_*`는 열린 숫자
-- 공간 위의 밴드 상수(`+ 1` 미세 조정 가능).
export type Dispatch = {
getHandler: (inst: any, key: any, value: any) -> Handler?,
process: (inst: any, key: any, value: any, index: number) -> (),
retractFrom: (inst: any, key: any, index: number) -> (),
addHandler: (handler: Handler) -> (),
listHandlers: () -> { Handler },
drive: (inst: any, flattened: { [any]: any }) -> (),
HANDLER_PRIORITY_HIGH: number,
HANDLER_PRIORITY_NORMAL: number,
HANDLER_PRIORITY_LOW: number,
HANDLER_PRIORITY_FALLBACK: number,
}
-- 예약 키 진단 — `keyof<T>`(키 싱글톤 유니온)만 받는다(`H-112`). `error()`는 못 쓴다
-- (타입 함수 자체가 실패로 판정) — `print` + `types.never`. 목록은 quad-base
-- `Store.luau`의 `RESERVED` 테이블의 사본이라 이름을 바꿀 땐 둘을 같이.
@ -171,6 +200,11 @@ export type Quad = {
-- 단위 4.
Blocker: () -> Blocker,
-- 디스패치 엔진(M3 단위 1, `H-25` — 마일스톤마다 서브시스템 필드를 여기
-- 같이 갱신하는 규칙의 M3 몫). 닫힌 레코드라 이 필드가 없으면
-- `quad.Dispatch` 접근이 런타임엔 되는데 `luau-analyze`에선 타입에러다.
Dispatch: Dispatch,
-- 생명주기 4종 — quad-base는 인터페이스(에러 스텁)만, 백엔드가 주입
-- (`.claude/base/lifecycle-pattern.md`). `canBound(v) == not canExecute(v)`.
bindLifetime: (inst: any, value: any) -> (),