quad/.claude/archive/tag-attribute-load-time-registration-reversed.md
qwreey-agent-selene 8b57cfbb3c
qa: 구현 전 QA 1라운드 결과를 base/에 전량 반영
`.claude/pre-implementation-qa.md`(사용자가 base/ 확정 문서를 문항으로
재심사한 결과)를 실제 문서에 반영하고, 그 문서를 qa-request/로 옮기며
1라운드임을 파일명·제목에 명시(2라운드는 새 파일).

그대로 구현하면 반대로 돌던 것 2건:
- canBound의 판정 방향이 이름과 반대였음 → canBound(v) == not
  isBoundAlive(v), 게이트는 전부 `if not canBound(v) then error(...)`.
  canExecute와는 값이 같은 게 아니라 서로의 부정이고, 그게 오히려 이름
  분리의 명분이 됨(옛 근거 "값이 항상 같다"는 폐기).
- gcconn/gchold 보관이 SetStrong으로 적혀 있었음 → SetWeak. 근거 문장까지
  틀렸던 것이라 같이 교체(그대로 짰으면 두-Relate 상호 강참조 누수).

설계가 바뀐 것:
- Dispatch.drive의 None 스킵 분기 폐기 → NoneHandler는 재귀 전담,
  NilHandler 신설(k=number and v==nil 말단이 setLength/setOffsetSource
  등록). 깨진 전제는 "배열 파트의 None은 process를 안 탄다".
- Length/Offset 등록 책임이 "처음 매치한 Handler" → 말단 Handler.
- 이벤트 disconnect 센티널 false → None/nil.
- Ref 내부 구조를 .Callbacks 분리 + 평범한 .Value 필드로 단순화,
  RefLeafHandler에 빠져 있던 type(k)=="number" 추가(leaf는 배열 전용).
- :List reconcile의 nil 리턴은 다시 파괴가 기본, 값 교체와 PopOnly(가칭)만
  비파괴.
- base 소유 Fallback Handler 등록 주체를 백엔드 팩토리 → quad-base 자신으로
  재역전(백엔드 미로드 시 안내 에러 경로가 안 돌았음).
- "이벤트 콜백 시그니처는 Luau가 검증 못 한다"가 거짓임이 사용자 반례로
  확인 → onchange-plan.md의 파생 근거까지 교체(결론은 유지).

이름/표면: DI → D(Declarative) 확정 및 전수 반영, New 커링 + D는 전량
코드 생성, Attribute.Merged/Overridden 둘 다 제공, Quad.debug 신설,
store "key" 문자열 커링 기각(→ store:GetDynamic).

판단이 갈리던 4건(PopOnly 채택 / D-7 재역전 / NoneHandler·NilHandler 역할
분담 / 동적 키 경로)은 사용자에게 물어 확정.

커밋 전 검증: quad-doc-auditor 1패스가 1건, 사용자가 돌린
`/code-review high`가 10건을 더 잡아 전부 반영(ROADMAP이 SL-3 역전을 안
따라오던 것, 설계 갭 2건은 새 열린 질문으로 등록). doc-check.py ERROR 0.

Co-authored-by: qwreey <me@qwreey.moe>
2026-08-18 19:39:03 +09:00

5.2 KiB

[역전됨 → 절반 재역전됨] Tag/Attribute Handler는 "quad-base 모듈 로드 시점에 스스로 등록" — 등록 주체와 이름이 둘 다 틀렸음

상태: archive — 2026-08-14 열두 번째 세션(이 대화)에서 역전. 정본은 base/dispatch-core-plan.md의 "base가 소유하는 핸들러와 주입되는 엔진 op" 절.

⚠️ [재역전, 2026-08-18 구현 전 QA] 이 문서가 폐기했던 두 결론 중 "등록 주체" 쪽은 다시 뒤집혀 현행이 됐다. 사용자 확정 — Fallback Handler는 quad-base 자신이 등록한다. 이유는 백엔드를 로드하지 않았을 때 "provider가 초기화됐는지 확인하라"는 안내 경로가 아예 안 도는 것("quad-roblox 를 로드하지 않았을 때 로드했는지 물어보는 요소가 처리가 안 된다"), 그리고 InitNamespace 거부 원칙은 남의 상태를 건드리는 top-level 부작용사용자 수동 init을 금지한 것이지 모듈이 자기 레지스트리를 채우는 걸 금지한 게 아니라는 것. 근거 전문은 base/dispatch-core-plan.md의 "base가 소유하는 핸들러와 주입되는 엔진 op" 절이 소스. 이 문서의 "이름" 쪽 결론(등록되는 건 TagHandler가 아니라 TagFallbackHandler)은 그대로 유효하다 — 아래 2번 항목. 즉 이 문서는 이제 "폐기된 설계의 보존"이 아니라 한 번 뒤집혔다가 절반이 되돌아온 경위 기록이다.

뒤집힌 주장 (2026-08-14 열한 번째 세션, "네 번째, 최종 정정")

TagHandler/AttributeKeyHandler/AttributeGroupHandlerquad-base 자기 모듈 로드 시점(top-level, require 시)에 HANDLER_PRIORITY_FALLBACK 우선순위로 스스로 Dispatch.addHandler를 호출해 등록한다고 확정했었음 — dispatch-core-plan.md/tag-plan.md/attribute-plan.md/ module-lifecycle-plan.md/architecture.md 5개 파일에 반영됐었음.

왜 틀렸나 (사용자 정정, 2026-08-14 열두 번째 세션)

두 가지가 섞여 있었음:

  1. 등록 주체가 틀림 — "모듈 로드 시점"은 이 프로젝트가 이미 명시적으로 거부한 패턴. base/lifecycle-pattern.md "rbvm에서 그대로 가져오면 안 되는 것" 절이 "InitNamespace/Registered-가드/NewLib 3종 세트로 라이브러리마다 하나하나 수동 init 하는 방식은 정확히 사용자가 피하고 싶다고 한 패턴"이라고 이미 못박아뒀는데, "quad-base가 자기 모듈 로드 시점에 스스로 등록"은 이름만 다를 뿐 같은 클래스의 top-level 부작용임. base/module-lifecycle-plan.md가 이미 확정해둔 일반 원칙("base 유틸은 인터페이스만, 실제 등록/구현은 백엔드 팩토리가 BaseModule을 뮤테이션하는 시점에 주입")과도 정면으로 어긋남 — 같은 문서 124-154줄이 "이 결론이 Dispatch의 handler 레지스트리에도 그대로 적용된다"고 이미 일반화해뒀던 걸 이 Tag/Attribute 결론만 예외로 뒀던 셈.
  2. 이름이 틀림 — "TagHandler 자신이 등록되는 주체"와 "TagHandler라는 공유 알고리즘 구현"을 하나로 뭉갬. TagHandler/AttributeKeyHandler/ AttributeGroupHandler는 참조 카운트/이름 claim 알고리즘 그 자체를 가리키는 이름이 맞고, 이건 엔진 지식을 요구하지 않는 공유 코드라서 base에 위치하는 것뿐 — 이름 자체가 "자동으로 설치되는 안전망"이라는 의미까지 담고 있지 않음.

정정된 모델

  • TagHandler/AttributeKeyHandler/AttributeGroupHandler = 참조 카운트/이름 claim 알고리즘 구현(그대로 base 소유, 이름도 그대로).
  • 실제로 HANDLER_PRIORITY_FALLBACK에 꽂히는 건 별도로 "Fallback"이 이름에 들어간 엔티티TagFallbackHandler/ AttributeKeyFallbackHandler/AttributeGroupFallbackHandler. 위 알고리즘을 그대로 감싸 쓰되, "이게 기본 안전망으로 자동 설치되는 대상"임을 이름으로 구분.
  • 등록 주체는 quad-base 모듈 자체가 아니라 필요한 엔진(백엔드 팩토리) — quad-roblox 같은 백엔드의 팩토리 함수가 BaseModule을 구성할 때, 자기 전용 Handler(Property/Event/OnChange/UICorner)들과 같이 이 base 소유 Fallback Handler들도 등록해준다. 엔진 저자 입장에선 "자동/공짜"(직접 코드를 안 짜도 됨)이지만, 메커니즘은 팩토리 뮤테이션 시점의 정상 경로 — module-lifecycle-plan.md가 이미 확정해둔 패턴을 그대로 따르는 것뿐, 새 예외가 아님.

영향 범위(정정 완료)

base/dispatch-core-plan.md(핸들러 계약 절, "base가 소유하는 핸들러와 주입되는 엔진 op" 절), base/tag-plan.md, base/attribute-plan.md, base/module-lifecycle-plan.md, base/architecture.md(Tag.luau 파일 설명) — 전부 이 세션에 재반영. CLAUDE.md의 11번째 세션 서술(네 라운드 연속 정정 서사)은 역사적 기록으로 그대로 둠(그 세션 시점엔 이게 최선의 결론이었음) — 12번째 세션 요약이 이 재역전을 링크로 가리킴.