- 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
122 lines
8.1 KiB
Markdown
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 소유권 메커니즘 둘 다 최종 확정.
|