quad/.claude/session/2026-08-13-04-blind-spot-audit-checkpoint-handlers.md
qwreey c33ae041b0
design(dispatch): 사각지대 손 트레이싱 라운드 + Dispatch 인덱스 기반 전면 재설계
네 번째 세션: State<State<T>> 발견 방식을 서브에이전트로 코퍼스 전체에
반복해 Tag 참조 카운트(객체 identity 키잉)/Attribute 그룹 자기충돌 버그
발견·수정. Attribute 소유권 충돌 방지를 위해 처음엔 Dispatch.processAs/
retractSelfAndUnder 체크포인트 핸들러 패턴을 신설.

다섯 번째 세션: 체크포인트 패턴이 임시방편이라는 지적에서 출발해, chains의
핸들러 객체 identity 기반 추적 자체가 State<State<T>>를 UB로 만든 근본
원인이었음을 재확인 — 재귀 깊이 인덱스로 재설계(같은 키 재귀는 index+1,
다른 키 위임은 항상 1부터), Handler 계약을 process/retract 2-메소드에서
process가 자기 retract 클로저를 반환하는 1-메소드로 축소. 이걸로
State<State<T>>가 UB에서 정상 지원 대상으로 바뀌고, 전날 만든 체크포인트
패턴 전체가 불필요해져 archive로 이전. 여러 핸들러(StoreBind/Ref/Tag/Slot/
Attribute)의 private Relate 상태 저장소도 대거 정리됨.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0132hkscPQWa34iyHrHdoRiF
2026-08-13 14:24:48 +09:00

6.3 KiB

2026-08-13 네 번째 세션 — 사각지대 손 트레이싱 라운드, Dispatch.processAs/retractSelfAndUnder 체크포인트 핸들러 신설

배경

직전 세션(02)에서 State<State<T>>가 UB인데 "가능하다"로 문서에 낙관적으로 적혀 있던 문제를 발견·수정한 것을 계기로, 사용자가 같은 방식(합성 시나리오를 pseudocode에 손으로 대입)으로 다른 사각지대가 더 있는지 찾아달라고 요청. 서브에이전트 4개(Tag/Attribute/Slot/Ref+교차훑기)를 병렬로 띄워 .claude/base/ 전체를 훑음.

1부 — 서브에이전트 발견 (실제 버그 3건)

  1. Tag 참조 카운트 붕괴: tagNameMap의 holders가 Tag 객체 identity로 키잉돼 있어서, 같은 Tag 객체(immutable이라 재사용이 흔한 관례, local SELECTED = Tag("selected")류)를 두 위치에 걸면 한 위치만 retract돼도 다른 위치가 쓰는 태그가 지워지는 버그.
  2. Attribute 그룹 자기충돌: 그룹이 이름을 놓았다(retract, 로컬 캐시 초기화) 나중에 같은 그룹이 같은 이름을 다시 포함하면(rawNew로 새 키 생성), 전역 owners 레지스트리엔 옛 키가 안 지워진 채 남아있어 "이미 다른 AttributeKey가 관리 중" 에러 — 그룹이 자기 자신과 충돌.
  3. Slot rawAdd의 이중 State 언랩 실패: Slot:Add(State<State<T>>)류 이중 래핑에서 reconcile이 부르는 rawAddisState 재검사를 안 해서 State 객체가 그대로 _elements에 박힘 — [정정, 2부에서 사용자 확인] 실제로는 버그가 아니라 이미 확정된 State<State<T>> UB 범위의 당연한 사례, 최초 보고가 과다 보고였음.

문제 없음으로 확인된 것: State<State<Ref>>/State<State<Tag>>(StoreBind 재진입 가드가 먼저 걸림), PreRef 재사용 가드, Slot의 owner-키 Relate ↔ chains 네임스페이스 분리, elementOwner의 다단 Slot-in-Slot GC 안전성.

2부 — 사용자의 직접 기술 리뷰, 세 번째 설계로 수렴

사용자가 서브에이전트 보고에 대해 직접 pseudocode 레벨로 반박/재설계를 제시(이 세션의 핵심):

  • Tag: 위치(k) 기준 재키잉에 동의. 추가로 "Tag는 immutable이니 oldv==newv면 retract 스킵 가능" 최적화 제안 — 채택.
  • Attribute: AttributeGroupHandler.processretractUnderself를 안 넣는 이유를 질문하다, retractUnderprocess 안에 있는 것 자체가 구조적으로 틀렸다고 재지적(process가 다시 안 불리는 상황이면 retractUnder도 안 일어남) — 결론적으로 owners/rawNew 레지스트리를 통째로 버리고, isHandlable이 없는(스캔에 안 걸리는) 순수 체크포인트 핸들러 AttributeGroupKeyHandler를 새로 만들어 Dispatch.processAs로 명시 push하고, 나중에 Dispatch.retractSelfAndUnder(신설 — 기존 retractUnderkeep 미만만 지우는 것과 달리 target 이하까지 지움)로 체크포인트+그 아래 전부를 한 번에 철거하는 설계 제안. 손 트레이싱으로 검증: 이 설계는 소유권 충돌 감지를 별도 레지스트리 없이 기존 "같은 (inst,k)에 같은 핸들러 재사용 시 error" 가드(State<State<T>> 가드와 동일 메커니즘)로 공짜로 얻음 — 채택, AttributeKeyHandler는 완전 무상태로 단순화됨.
  • Slot: releaseOwner의 소유권 불일치를 조용히 무시하던 걸 즉시 error로 바꿔야 한다는 지적(그 상황 자체가 이미 버그라는 판단) — 채택. bindLifetimeattachSlot의 조건 분기에 묻혀있는 게 unbindLifetime이 Handler 층위(SlotHandler.retract)에 있는 것과 비대칭이라는 지적 — bindLifetimeSlotHandler.process로 이동, attachSlot은 순수 구조적 mount 로직으로 단순화. 채택.
  • Brand: isXX 판별 함수들이 nil을 안전하게 처리하는지 서브에이전트로 확인 요청 — 결과 안전함(weak registry lookup 방식이라 nil 키 조회도 항상 안전하게 false로 귀결, 에러 나는 경로 없음). 문서 수정 불필요, M0 스파이크 목록에 isXX(nil) 명시적 실측 케이스 추가는 선택 사항으로 남김(안 함).

반영 완료

  • base/bind-system-plan.md: 핸들러 계약에 isHandlable 선택적 필드로 확장(생략 시 스캔 불가), "Dispatch 체인" 절에 Dispatch.processAs/ Dispatch.retractSelfAndUnder 신설 + 체크포인트 패턴 설명, handler.process 직접 호출 UB 경고에 processAs도 정식 진입점으로 포함.
  • base/tag-plan.md: holders를 Tag 객체 identity에서 위치(k) 기준으로 재키잉, TagHandler.retractoldv==newv 조기 반환 추가.
  • base/attribute-plan.md: "이름 소유권"/"메커니즘" 두 절 전면 재작성 — rawNew/owners 제거, AttributeGroupKeyHandler 체크포인트+ processAs/retractSelfAndUnder 페어로 교체, AttributeKeyHandler 무상태화, groupState를 "이름→키 객체 맵"에서 "이름 집합"으로 단순화.
  • base/slot-plan.md: releaseOwner 불일치 시 error로 강화, bindLifetime 호출을 attachSlot에서 SlotHandler.process로 이동.
  • .claude/README.md: 4개 파일 행에 이 세션 요약 append.

서브에이전트 조사(문서 수정 없음, 결과만)

  • Relate GetStrong/SetStrong 키 누락 + const-키 비효율 패턴 코퍼스 전수조사 — attribute-plan.mdowners:GetStrong(inst) 1건(이미 이 세션에서 재설계로 해소됨) 외 추가 발견 없음, 나머지 전부 정상 사용.
  • Brand/isXX nil 처리 전수조사 — 안전함 확인(위 2부 참고).

남는 것

  • Slot:ExtractAll()/:Splice(...)의 반환값을 Slot(...) 생성자에 바로 넣을 수 있다는 조합 예시를 문서에 추가하면 좋겠다는 사용자 코멘트 — 버그 아님, 착수 안 함(원하면 다음 세션에).
  • luau-test/ 스파이크(20개, 이 세션에서 다룬 새 메커니즘은 아직 스파이크 파일 없음 — Dispatch.processAs/retractSelfAndUnder/AttributeGroupKeyHandler 재설계를 검증할 스파이크 추가 여지가 있음, 필요시 다음 세션) — 여전히 사용자가 luau로 안 돌려봄, M0 착수 전 최우선 게이트 그대로.