From 590b0fe6a19f208079f9b078fa123ba0327ef272 Mon Sep 17 00:00:00 2001 From: qwreey-agent-selene Date: Mon, 31 Aug 2026 15:13:08 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20M3=20=EB=8B=A8=EC=9C=84=201=20=E2=80=94?= =?UTF-8?q?=20Handler=20=EA=B3=84=EC=95=BD=20=ED=83=80=EC=9E=85(quad-types?= =?UTF-8?q?=20=EC=86=8C=EC=9C=A0)=20+=20Quad.Dispatch=20=ED=95=84=EB=93=9C?= =?UTF-8?q?(H-25)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 Claude-Session: https://claude.ai/code/session_01LF78pXeFGD1ZSVD3ifteYG --- .../01-two-pass-array-hash-order.luau | 0 quad-base/src/Dispatch/Handler.luau | 30 ++++++++++++++++ quad-types/src/init.luau | 34 +++++++++++++++++++ 3 files changed, 64 insertions(+) rename .claude/luau-test/{rewrite-required => done}/01-two-pass-array-hash-order.luau (100%) create mode 100644 quad-base/src/Dispatch/Handler.luau diff --git a/.claude/luau-test/rewrite-required/01-two-pass-array-hash-order.luau b/.claude/luau-test/done/01-two-pass-array-hash-order.luau similarity index 100% rename from .claude/luau-test/rewrite-required/01-two-pass-array-hash-order.luau rename to .claude/luau-test/done/01-two-pass-array-hash-order.luau diff --git a/quad-base/src/Dispatch/Handler.luau b/quad-base/src/Dispatch/Handler.luau new file mode 100644 index 0000000..9e3f1b2 --- /dev/null +++ b/quad-base/src/Dispatch/Handler.luau @@ -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 {} diff --git a/quad-types/src/init.luau b/quad-types/src/init.luau index 0b0ddda..49d0ad5 100644 --- a/quad-types/src/init.luau +++ b/quad-types/src/init.luau @@ -126,6 +126,35 @@ export type Source = State & { Emit: (self: Source) -> Source, } +-- ── 디스패치 엔진(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`(키 싱글톤 유니온)만 받는다(`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) -> (),