quad/.claude/session/2026-08-12-16-corpus-audit-attribute-retract-slot-owner.md
qwreey 2df591851f
fix(attribute,slot): retract 전면 재설계, 소유권 레지스트리 통합, 코퍼스 감사
- Attribute: AttributeKeyHandler.retract를 완전 no-op으로 재정정 —
  지우는 건 오직 명시적 None/nil(process)로만, 그룹이 사라진 이름을
  자동으로 SetAttribute(nil) 안 해줌(Ref의 "Destroy 무관" 철학과 통일).
  단 사라진 이름의 StoreBind 구독은 retractUnder로 끊어 리소스 누수 방지.
- Slot: slotOwner를 elementOwner로 일반화해 top-level Dispatch/nested
  Add 경로가 같은 소유권 레지스트리를 쓰도록 통합(이중 마운트 gap 폐쇄).
  bindLifetime을 top-level 전용으로 축소(nested는 _elements 강참조로
  transitively 생존).
- 일반 규칙 신설(bind-system-plan.md): retract의 v는 타입 미보장이라
  내용을 보려면 isX(v) 가드 필수, retract 안에서 process 호출은
  retractUnder 체인 추적을 꼬는 UB.
- and/or 삼항 관용구 전면 금지(기존 "항상-truthy면 예외" 조항 폐기),
  코퍼스 전체 실제 코드 6곳을 if-then-else로 교체.
- 7-에이전트 코퍼스 감사로 stale 서술 다수 정정: retract-always-fires
  정정 전파 누락(bind-system-plan.md Tag 예시), Tween research→base
  승격 반영 누락(architecture.md/ui-shorthand-plan.md), Relate API
  인자 개수 버그(slot-plan.md), pre-implementation-audit.md 열린 항목
  개수 오류(question.md/CLAUDE.md).
- 백로그: Attribute 자동 unset용 :Apply 유틸 아이디어를
  research/operator-sugar-plan.md에 추가(착수 안 함).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVG74qV2nQVykhvMRQW2UC
2026-08-12 18:39:45 +09:00

122 lines
8.1 KiB
Markdown

# 2026-08-12 열여섯 번째 세션 — 코퍼스 전체 감사, Attribute retract 전면 재설계, Slot 소유권 일반화
## 배경
사용자가 "문서 전체에서 가정이 틀렸는데 다른 문서에서는 이미 정정된
사실이 있음에도 잘못된 추론이 그대로 남아있는 곳이 있는지" 감사를 요청.
이전 세션들(특히 2026-08-12 열한~열다섯 번째, retract-always-fires
정정과 그 파생 GC 이슈 시리즈)에서 큰 변경이 여러 차례 있었기 때문 —
"정정됨" 표시만 붙고 원 문장이 그대로 방치되는 패턴이 반복된다는 CLAUDE.md
자기 경고가 이번에도 실제로 맞아떨어짐.
## 1부 — 코퍼스 전체 감사 (7개 에이전트 병렬)
`base/` 14개 파일 + `research/`/`question.md`/`README.md`를 7개
서브에이전트로 나눠 정독·대조. 발견해서 그 자리에서 고친 것:
- **`bind-system-plan.md`**: "Tag는 retract가 무조건 전체 삭제"라던
옛 서술 2곳이 세션11 정정(참조 카운트 기반) 이후에도 안 고쳐진 채
남아있었음 — 수정.
- **`attribute-plan.md`**: "retract 불필요" 헤더가 바로 아래 정정된 코드
주석과 모순 — 수정(이후 2부에서 이 절 전체가 다시 크게 재작성됨).
- **`slot-plan.md`**: `slotOwner:GetWeak/SetWeak` 호출이 `Relate` 공식
API(항상 3-인자)와 인자 개수가 안 맞는 실제 버그 — sentinel key 추가로
수정. "재마운트는 즉시 throw" 절이 나중에 확정된 "같은 위치 재-emit은
no-op" 예외를 반영 안 함 — 교차참조 추가. `rawRemove`가 element
기준/index 기준 두 가지로 같은 파일에 리터럴로 존재 — M6 미확정임을
명시하는 주석 추가(결정 자체는 안 내림).
- **Tween 승격(research→base) 반영 누락**: `tween-plan.md` 상태 필드가
여전히 "research", `architecture.md` 2곳("Tween은 여전히 research/에
있음"), `ui-shorthand-plan.md`의 폐기된 "Tween 핸들러" 용어 — 전부 수정.
- **`question.md`/`CLAUDE.md`의 "지금 할 일" 1번**: `pre-implementation-audit.md`
우선순위1 중 열려있는 게 "1개(1-3)뿐"이라던 요약이 실제로는 4개(1-3/
1-4/1-10/1-11, 원문에 `[해소됨]` 마커 없음)였음 — 개수/목록만 정정,
실제 해소는 사용자가 "나중으로 연기" 선택.
## 2부 — Attribute retract 전면 재설계 (사용자 주도 다단계 정정)
diff를 직접 보던 사용자가 "AttributeHandler가 retract 없는 게 말이 되나"
질문에서 시작해 여러 라운드에 걸쳐 최종 설계에 도달:
1. **1차**: 그룹 자신의 `(inst,index)` 슬롯도 process/retract 코드가
없다는 gap 발견 — `AttributeGroupHandler.process`/`.retract` 스케치.
2. **2차 (사용자 catch)**: `retract``v` 타입을 안 보고 `v:NameMap()`
호출하는 게 버그 — `TagHandler.retract``isTag(newv)` 가드 선례와
대조해 확인. `Ref`/`Slot`/`AttributeKey`의 기존 retract는 전부
identity/nil 비교뿐이라 애초에 이 문제가 없었음(내용을 봐야 하는 건
Tag/Attribute뿐).
3. **3차 (사용자 catch)**: `AttributeKeyHandler.retract``v==nil`이면
`SetAttribute(name,nil)`을 직접 부르는 게 깜빡임(`a→nil→b`) 위험 —
"일반 프로퍼티와 동일하게 취급"이라는 기존 원칙대로 retract를 완전
no-op으로, 지우는 건 오직 `process(inst,k,nil)`(None/nil 명시)로만
통일.
4. **4차 (사용자 결정, 가장 큰 정정)**: 그룹이 "사라진 이름"을 자동으로
`SetAttribute(nil)` 해주는 것 자체를 기각 — **Attribute는 오직
명시적 `None`/`nil`로만 지워진다.** 그룹 바인딩이 통째로 사라져도
(컴포넌트 언마운트 등) 자동 청소 없음. 이유: diff로 조용히 빠지는
이름과 통째 소멸을 다르게 취급하면 오히려 모호해지고, Attribute는
이미 "겹치면 error"로 소유 코드가 명확히 갈리는 설계라 프레임워크가
대신 판단할 근거가 불투명함. `Ref`의 "Destroy 무관, 정리는 Effect로"
철학과 통일. 이 결정으로 기존 "Tag와 동일하게 확실히 청소" 초안(line
308-313)이 뒤집힘.
5. **5차**: (4차와 별개 문제) 값을 안 지워도 사라진 이름의 *구독*은
끊어야 함 — 안 그러면 그룹이 더 이상 안 쓰는 Source를 향한 StoreBind
구독이 인스턴스가 살아있는 동안 계속 살아남아 리소스 누수. `retract`
`Dispatch.retractUnder`만 부르고(`process`는 안 부름 — retract 안에서
process 호출은 UB, 아래 참고) 구독만 끊는 걸로 확정 — `SetAttribute`
여기서도 절대 안 일어남.
**부수적으로 확정된 일반 규칙 2개**(`bind-system-plan.md`에 추가):
- retract의 `v`는 타입 보장 안 됨 — 내용을 보려면 `isX(v)` 가드 필수.
- retract 안에서 `Dispatch.process` 호출은 UB(`retractUnder`의 체인
추적이 꼬임) — retract는 구조적 팝/자원 해제만, 새 등록 트리거 금지.
`Dispatch.retractUnder`를 (다른 키에 대해) retract 안에서 부르는 건
문제없음.
**백로그**: 그래도 자동 unset이 필요해지면 `Animate`와 같은 모양의
`:Apply` opt-in 유틸(이전 이름 집합과 비교해 사라진 이름을 `None`으로
채워주는 콤비네이터)을 나중에 추가 가능 — `research/operator-sugar-plan.md`
"Attribute 그룹 명시적 unset 유틸" 절로 신설, 착수 안 함.
## 3부 — Slot 소유권 일반화, bindLifetime 스코프 정정
같은 흐름에서 Slot도 감사:
- **소유권 레지스트리 이중화 gap(사용자 발견)**: top-level Dispatch
마운트(`slotOwner`)와 nested `Add`(문서에만 있고 코드 없던 "전역
weak-set")가 서로 다른 레지스트리라, 한쪽으로 먼저 마운트한 걸 다른
쪽이 못 보고 이중 마운트를 허용하는 실제 gap이 있었음. `slotOwner`
`elementOwner`(Slot이든 plain Instance든 공용)로 승격, `claimOwner`/
`releaseOwner`를 top-level(`SlotHandler`)과 nested(`rawAdd`/`rawRemove`/
`rawExtract`)가 동일하게 호출하도록 통합.
- **bindLifetime 스코프 정정(사용자 판단)**: `attachSlot`이 재귀 모든
레벨에서 `bindLifetime`을 부르던 것을 **top-level에서만**으로 축소 —
nested Slot은 담는 outer의 `_elements`(plain strong array)로 이미
transitively 살아있어서 별도 anchor가 불필요, State의 노드 연결처럼
말단만 실제 엔진 생명주기에 건다는 원칙과 일치. `destroySlotTree`
자기-unbind도 대칭으로 제거, top-level 파괴 지점(`SlotHandler.retract`)
에서만 짝 맞춤.
- **GC 안전성 확인**: `slot._mountedInst = physicalTarget`
`relate-plan.md`가 이미 안전하다고 확정한 "값이 자기 키를 다시
참조하는" 단일-Relate 자기참조 패턴(`Ref.Value=inst`와 동일 부류)이라
안전 — `relate-plan.md`에 확인 사례로 추가.
## 4부 — `and`/`or` 삼항 관용구 전면 금지
사용자가 성능(진짜 short-circuit이라 매 단계 truthiness 테스트, `if-then-else`
단일 분기)과 안전성(가운데 값 falsy 시 새는 구조적 결함) 둘 다 근거로
"쓸 수 있는 곳은 전부 `if-then-else`"를 요청 — 기존에 "가운데 값이
항상-truthy면 예외적으로 and/or 허용"이라던 예외 조항을 폐기하고, 코드로
실존하던 6곳(`bind-system-plan.md`/`slot-plan.md`/`tag-plan.md`)을 전부
`if-then-else`로 교체. `architecture.md`/`bind-system-plan.md`의 규칙
설명도 같이 강화. 단순 2항 `A or B` fallback(`props.Modifier or None`류)은
애초에 이 문제가 없어 그대로 유지.
## 반영 완료 확인
`base/attribute-plan.md`(221줄 변경), `base/slot-plan.md`(147줄),
`base/bind-system-plan.md`(67줄), `base/architecture.md`(22줄),
`base/tag-plan.md`(24줄), `base/relate-plan.md`, `base/tween-plan.md`,
`base/ui-shorthand-plan.md`, `research/operator-sugar-plan.md`,
`.claude/question.md`, 루트 `CLAUDE.md` 전부 이 세션 안에서 갱신 완료.
열린 설계 질문 없음 — Attribute/Slot 소유권 메커니즘 둘 다 최종 확정.