docs: 단위 2 감사 2라운드 반영 — ROADMAP 418 깨진 볼드, source-state-plan의 '구현 시 정한다' 잔여 둘(Source 이름·__apply 시그니처), 스파이크 15를 done/으로(폴더가 곧 상태)
Co-authored-by: qwreey <me@qwreey.moe>
This commit is contained in:
parent
500ae298aa
commit
cd65520921
4 changed files with 10 additions and 6 deletions
|
|
@ -956,8 +956,10 @@ someSource:Compute(computeFn)
|
||||||
프리미티브로 노출** — Store는 다수의 source를 등록/관리하는 무거운
|
프리미티브로 노출** — Store는 다수의 source를 등록/관리하는 무거운
|
||||||
구조라, 값 하나만 반응형으로 다루고 싶을 때 Store를 통째로 만드는 건
|
구조라, 값 하나만 반응형으로 다루고 싶을 때 Store를 통째로 만드는 건
|
||||||
비효율이라는 게 사용자 판단("store가 source 수십 개 만드는건 비효율이니
|
비효율이라는 게 사용자 판단("store가 source 수십 개 만드는건 비효율이니
|
||||||
둘이 다른 구현이라 봐도 될듯"). `Source(initial)` 류의 독립 생성자
|
둘이 다른 구현이라 봐도 될듯"). `Source(default)` 독립 생성자가
|
||||||
(정확한 이름은 구현 단계에서 확정)가 Store와 나란히 존재.
|
Store와 나란히 존재(**[2026-08-28]** 이름은 아래 항목대로 `Source(default)`로 확정됐고
|
||||||
|
M2 단위 2 `quad-base/src/Source.luau`가 그 이름이다 — 한때 여기 "구현 단계에서
|
||||||
|
확정"이라 열려 있었다).
|
||||||
- **생성자 스타일 확정(2026-08-06 후속 세션): Kotlin Compose식 "타입
|
- **생성자 스타일 확정(2026-08-06 후속 세션): Kotlin Compose식 "타입
|
||||||
이름 자체를 팩토리 함수로" — `Source(default)`, `Ref(default)`,
|
이름 자체를 팩토리 함수로" — `Source(default)`, `Ref(default)`,
|
||||||
`Store({defaults})`.** Ref도 예외 없이 이 스타일을 따름 — Ref가
|
`Store({defaults})`.** Ref도 예외 없이 이 스타일을 따름 — Ref가
|
||||||
|
|
@ -1118,7 +1120,9 @@ then return factory(self) else return factory:__apply(self) end end` — 즉
|
||||||
- **함수와 콜러블의 유니온으로 여는 안은 기각** — 필드로 받으면
|
- **함수와 콜러블의 유니온으로 여는 안은 기각** — 필드로 받으면
|
||||||
유니온도 캐스트도 필요 없다. 필드 이름은 **`__apply`**(**[2026-08-28 10라운드
|
유니온도 캐스트도 필요 없다. 필드 이름은 **`__apply`**(**[2026-08-28 10라운드
|
||||||
`H-158` 사용자 확정]** *"키는 __apply 로 하기로 했던거로 기억중임"* —
|
`H-158` 사용자 확정]** *"키는 __apply 로 하기로 했던거로 기억중임"* —
|
||||||
`base/blocker-plan.md` 배너), 정확한 시그니처는 구현 시 정한다.
|
`base/blocker-plan.md` 배너), 시그니처는 **[2026-08-28 M2 단위 2 확정]** 메소드형
|
||||||
|
`__apply: (self: any, state: State<T>) -> U` — `quad-types/src/init.luau`의 `State<T>.Apply`
|
||||||
|
파라미터 타입과 `State.luau`의 `(factory :: any):__apply(self)` 호출이 소스.
|
||||||
- **구현 비용 거의 0**: Modifier와 달리 State/Source는 제네릭 `__index`로
|
- **구현 비용 거의 0**: Modifier와 달리 State/Source는 제네릭 `__index`로
|
||||||
필드 setter를 즉석 합성하는 메커니즘이 없어서(고정된 메소드 표면만
|
필드 setter를 즉석 합성하는 메커니즘이 없어서(고정된 메소드 표면만
|
||||||
존재), Modifier의 `Apply`처럼 "필드 이름으로 예약해야 하는" 충돌
|
존재), Modifier의 `Apply`처럼 "필드 이름으로 예약해야 하는" 충돌
|
||||||
|
|
@ -1128,7 +1132,7 @@ then return factory(self) else return factory:__apply(self) end end` — 즉
|
||||||
**함수만** 받으므로 위 `H-94` 항목이 확정한 "지정된 필드를 가진 객체"
|
**함수만** 받으므로 위 `H-94` 항목이 확정한 "지정된 필드를 가진 객체"
|
||||||
형태를 **거부한다** — `state:Apply(Debounce{...})`가 그대로 타입에러다.
|
형태를 **거부한다** — `state:Apply(Debounce{...})`가 그대로 타입에러다.
|
||||||
파라미터는 **함수 또는 그 필드를 가진 객체**를 받고 반환 `U`만 열어둔다
|
파라미터는 **함수 또는 그 필드를 가진 객체**를 받고 반환 `U`만 열어둔다
|
||||||
(필드 이름은 `__apply` — **[2026-08-28 `H-158`]**; 시그니처는 구현 시 정한다). 아래 논거는 **반환 쪽**에
|
(필드 이름은 `__apply` — **[2026-08-28 `H-158`]**; 시그니처는 위 항목대로 M2 단위 2에서 확정). 아래 논거는 **반환 쪽**에
|
||||||
대한 것이라 그대로 유효하다 — Modifier의
|
대한 것이라 그대로 유효하다 — Modifier의
|
||||||
`Apply`는 `factory: (M) -> M`으로 같은 타입을 유지해야 체이닝이
|
`Apply`는 `factory: (M) -> M`으로 같은 타입을 유지해야 체이닝이
|
||||||
이어지지만, State의 `:Apply`는 팩토리가 State가 아닌 값(예: 최종
|
이어지지만, State의 `:Apply`는 팩토리가 State가 아닌 값(예: 최종
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,6 @@
|
||||||
| `05-store-state-diamond-propagation.luau` | 2026-08-19 재작성분은 그 시점 모델 기준 ✅ 통과였음 | **[2026-08-21] 모델이 또 바뀌었다** — 소스 에포크 비교 채택(`base/state-epoch-plan.md`)으로 다이아몬드에서 **두 번째 통지가 접힌다**. 그래서 이 스파이크의 핵심 assert("`:Get()`을 안 부르는 Observer가 변경당 경로 수(2)만큼 운다")가 **정반대**가 됐다 — 이제 **변경당 1회**여야 한다. **살릴 것**: `invalid` 기반 dedup이면 두 번째 변경부터 침묵하는 것을 잡는 음성 대조군(그 금지는 지금도 유효). **새로 넣을 것**: DFS 도중 `Get()`이 섞인 값을 캐시하던 glitch가 에포크로 사라지는지(그 문서 §1의 시나리오) |
|
| `05-store-state-diamond-propagation.luau` | 2026-08-19 재작성분은 그 시점 모델 기준 ✅ 통과였음 | **[2026-08-21] 모델이 또 바뀌었다** — 소스 에포크 비교 채택(`base/state-epoch-plan.md`)으로 다이아몬드에서 **두 번째 통지가 접힌다**. 그래서 이 스파이크의 핵심 assert("`:Get()`을 안 부르는 Observer가 변경당 경로 수(2)만큼 운다")가 **정반대**가 됐다 — 이제 **변경당 1회**여야 한다. **살릴 것**: `invalid` 기반 dedup이면 두 번째 변경부터 침묵하는 것을 잡는 음성 대조군(그 금지는 지금도 유효). **새로 넣을 것**: DFS 도중 `Get()`이 섞인 값을 캐시하던 glitch가 에포크로 사라지는지(그 문서 §1의 시나리오) |
|
||||||
| `04-dispatch-chain-retractFrom.luau` | 옛 모델 기준으로는 ✅ 통과였음 | (1) `chains` 슬롯이 `{handler, retractor}`가 되고 `Dispatch.process`가 핸들러를 먼저 비교하는 **하강 diff**로 재작성, (2) `retractFrom`은 **3-인자**(힌트 인자 없음), (3) "힌트가 target 인덱스에만 간다"를 검증하던 부분은 **정반대**로 뒤집힘 — 이제 각 레벨이 자기 값을 받는지를 검증해야 함. **살릴 것**: `chains:SetStrong` 순서 음성 대조군(그 버그는 새 모델에서도 그대로 유효) |
|
| `04-dispatch-chain-retractFrom.luau` | 옛 모델 기준으로는 ✅ 통과였음 | (1) `chains` 슬롯이 `{handler, retractor}`가 되고 `Dispatch.process`가 핸들러를 먼저 비교하는 **하강 diff**로 재작성, (2) `retractFrom`은 **3-인자**(힌트 인자 없음), (3) "힌트가 target 인덱스에만 간다"를 검증하던 부분은 **정반대**로 뒤집힘 — 이제 각 레벨이 자기 값을 받는지를 검증해야 함. **살릴 것**: `chains:SetStrong` 순서 음성 대조군(그 버그는 새 모델에서도 그대로 유효) |
|
||||||
| `19-ownership-refcount-relate-patterns.luau` | A/C ✅ 유효, **B 섹션이 낡음** | B가 검증하던 "공개 `AttributeKey(name)` + 인덱스 1 점유 체크"가 폐기됨 — **그룹 전용 키 + `AttributeKeyHandler`의 이름 claim**으로 재작성하고, 음성 대조군도 "두 그룹이 같은 이름 → 즉시 error", "그룹↔직접 쓰기 → 즉시 error"로 바꿀 것(0-Z 확정 내용). A/C는 손댈 것 없음 |
|
| `19-ownership-refcount-relate-patterns.luau` | A/C ✅ 유효, **B 섹션이 낡음** | B가 검증하던 "공개 `AttributeKey(name)` + 인덱스 1 점유 체크"가 폐기됨 — **그룹 전용 키 + `AttributeKeyHandler`의 이름 claim**으로 재작성하고, 음성 대조군도 "두 그룹이 같은 이름 → 즉시 error", "그룹↔직접 쓰기 → 즉시 error"로 바꿀 것(0-Z 확정 내용). A/C는 손댈 것 없음 |
|
||||||
| `15-type-compute-trailing-deps-typepack.luau` | **[2026-08-28 폐기 — 재작성 안 함]** M2 단위 2가 실제 `quad-types` 선언에서 타입팩 형태를 실측해 기각했다(`round11.md` `H-176`: strict에서 콜백 dep 추론이 깨져 정상 호출까지 막힘 → deps 자리 `...any`). 이 스파이크가 물으려던 (B)의 답이 나왔으므로 파일은 역사로만 남긴다. 아래는 폐기 전 상태: **파싱 실패**(SyntaxError) | 음성 대조군의 타입 표기가 `TypeError`가 아니라 `SyntaxError`로 걸려 **파일 전체가 아무것도 검증 못 함** — 대조군을 별도 파일/블록으로 격리 |
|
|
||||||
| `22-runtime-ref-preref-postref-brand.luau` | 옛 `Brand` API 기준으로는 ✅ 통과였음 | **[2026-08-21] `Brand`가 인스턴스 브랜드로 재작성됨** — 파일 안의 `Brand.set(x, tag)`/`Brand.get(x)`/`XxxTag` 변수를 `Brand()` + `SomeBrand:register(x)`/`SomeBrand:is(x)`로 바꿔 쓸 것(`base/brand-plan.md`). **검증 대상(`isPreRef`/`isPostRef` 배타 + 둘 다 `isRef`엔 `true`, Leaf 핸들러 흉내)은 그대로**라 assert는 손댈 게 없다. **새로 넣을 것**: 다중 태깅이 실제로 되는지 — 한 값을 두 브랜드에 등록하고 양쪽 `:is`가 다 `true`인지(`Source`가 `SourceBrand`+`EpochBrand`인 자리, `base/state-epoch-plan.md` §2) |
|
| `22-runtime-ref-preref-postref-brand.luau` | 옛 `Brand` API 기준으로는 ✅ 통과였음 | **[2026-08-21] `Brand`가 인스턴스 브랜드로 재작성됨** — 파일 안의 `Brand.set(x, tag)`/`Brand.get(x)`/`XxxTag` 변수를 `Brand()` + `SomeBrand:register(x)`/`SomeBrand:is(x)`로 바꿔 쓸 것(`base/brand-plan.md`). **검증 대상(`isPreRef`/`isPostRef` 배타 + 둘 다 `isRef`엔 `true`, Leaf 핸들러 흉내)은 그대로**라 assert는 손댈 게 없다. **새로 넣을 것**: 다중 태깅이 실제로 되는지 — 한 값을 두 브랜드에 등록하고 양쪽 `:is`가 다 `true`인지(`Source`가 `SourceBrand`+`EpochBrand`인 자리, `base/state-epoch-plan.md` §2) |
|
||||||
| `16-type-store-key-typefunction.luau` | 옛 접근 기준으로는 ✅ 통과였음 | **[2026-08-25] 검증 대상이 폐기됨** — `WrapStore`/`ProcessStoreType` 합성 자체가 사라졌다. **재작성 지침**: 타입 함수 없는 평범한 레코드 모양(`base/store-plan.md`의 "`store.key` 레코드 필드 타이핑" 절)을 검증하고, 음성 대조군에 **예약 키 충돌**(`CheckReservedKeys<keyof<T>>`가 `types.never`로 무너뜨리는지 — **[2026-08-26 `H-112`]** 인자가 `T`가 아니라 `keyof<T>`다)과 **없는 키 접근**을 포함할 것 |
|
| `16-type-store-key-typefunction.luau` | 옛 접근 기준으로는 ✅ 통과였음 | **[2026-08-25] 검증 대상이 폐기됨** — `WrapStore`/`ProcessStoreType` 합성 자체가 사라졌다. **재작성 지침**: 타입 함수 없는 평범한 레코드 모양(`base/store-plan.md`의 "`store.key` 레코드 필드 타이핑" 절)을 검증하고, 음성 대조군에 **예약 키 충돌**(`CheckReservedKeys<keyof<T>>`가 `types.never`로 무너뜨리는지 — **[2026-08-26 `H-112`]** 인자가 `T`가 아니라 `keyof<T>`다)과 **없는 키 접근**을 포함할 것 |
|
||||||
| `11-modifier-illegal-value-error.luau` | 옛 형태 기준으로는 ✅ 통과였음(16개 케이스 전원) | **[2026-08-26, 8라운드 `H-122`/`H-123`] 검증 코드가 폐기된 모델을 박제하고 있다** — 그 파일의 Store 생성자가 **eager `Source(v)`** 모델이다. 명시적 초기화 확정(2026-08-25) 이후 **`defaults` 경로에선** Store가 `Source`를 만들지 않는다(동적 키 창구 `store:Of(name)`은 여전히 만든다 — 그래서 가드가 `Source` 생성자로 갔다). **재작성 지침**: `isModifier` 가드의 새 자리는 **`Source` 생성자**(+`Source:Set`, `:Compute` 결과 캐싱)이고, Store 생성자가 하는 건 `defaults`의 **`isSource` 화이트리스트 검증**(error level 2)이다 — 둘을 각각 양성/음성으로 볼 것. **검증 대상(핸들러 계층 값 즉시 error)은 그대로**라 결론이 바뀌는 건 아님 |
|
| `11-modifier-illegal-value-error.luau` | 옛 형태 기준으로는 ✅ 통과였음(16개 케이스 전원) | **[2026-08-26, 8라운드 `H-122`/`H-123`] 검증 코드가 폐기된 모델을 박제하고 있다** — 그 파일의 Store 생성자가 **eager `Source(v)`** 모델이다. 명시적 초기화 확정(2026-08-25) 이후 **`defaults` 경로에선** Store가 `Source`를 만들지 않는다(동적 키 창구 `store:Of(name)`은 여전히 만든다 — 그래서 가드가 `Source` 생성자로 갔다). **재작성 지침**: `isModifier` 가드의 새 자리는 **`Source` 생성자**(+`Source:Set`, `:Compute` 결과 캐싱)이고, Store 생성자가 하는 건 `defaults`의 **`isSource` 화이트리스트 검증**(error level 2)이다 — 둘을 각각 양성/음성으로 볼 것. **검증 대상(핸들러 계층 값 즉시 error)은 그대로**라 결론이 바뀌는 건 아님 |
|
||||||
|
|
@ -183,6 +182,7 @@
|
||||||
|
|
||||||
| 파일 | 확인된 것 |
|
| 파일 | 확인된 것 |
|
||||||
|---|---|
|
|---|---|
|
||||||
|
| `15-type-compute-trailing-deps-typepack.luau` | **[2026-08-28 폐기 → `done/`로 이동, 재작성 안 함]** M2 단위 2가 실제 `quad-types` 선언에서 타입팩 형태를 실측해 기각했다(`round11.md` `H-176`: strict에서 콜백 dep 추론이 깨져 정상 호출까지 막힘 → deps 자리 `...any`). 이 스파이크가 물으려던 (B)의 답이 나왔으므로 파일은 역사로만 `done/`에 남긴다. 아래는 폐기 전 상태: **파싱 실패**(SyntaxError) | 음성 대조군의 타입 표기가 `TypeError`가 아니라 `SyntaxError`로 걸려 **파일 전체가 아무것도 검증 못 함** — 대조군을 별도 파일/블록으로 격리 |
|
||||||
| `02-none-sentinel-vs-nil-holes` | `nil` 소진 시 `#t` 50→49로 무너짐 / `None`은 항상 50. 반대로 **당시의** Ref 콜백 배열은 `None` 쓰면 죽은 슬롯 1000개 잔존 — **두 배열의 규칙이 서로 반대여야 함**이 정량 확인. **[2026-08-24]** 그 대비의 한쪽(Ref 콜백)은 6라운드 `H-7`로 **해시맵 셋**이 되어 사라졌지만, 이 스파이크가 실제로 확인한 것(**일반 Lua 테이블에서 `nil` 구멍과 `None` 채움의 거동 차이**)은 그대로 유효하다 — `sourceList`/`flattened`처럼 순서가 중요한 배열이 여전히 그 결론 위에 선다 |
|
| `02-none-sentinel-vs-nil-holes` | `nil` 소진 시 `#t` 50→49로 무너짐 / `None`은 항상 50. 반대로 **당시의** Ref 콜백 배열은 `None` 쓰면 죽은 슬롯 1000개 잔존 — **두 배열의 규칙이 서로 반대여야 함**이 정량 확인. **[2026-08-24]** 그 대비의 한쪽(Ref 콜백)은 6라운드 `H-7`로 **해시맵 셋**이 되어 사라졌지만, 이 스파이크가 실제로 확인한 것(**일반 Lua 테이블에서 `nil` 구멍과 `None` 채움의 거동 차이**)은 그대로 유효하다 — `sourceList`/`flattened`처럼 순서가 중요한 배열이 여전히 그 결론 위에 선다 |
|
||||||
| `03-recursive-store-bind-dispatch` | StoreBind 재귀 재-dispatch, `None`→`nil` 흐름, 무한재귀 없이 종료 |
|
| `03-recursive-store-bind-dispatch` | StoreBind 재귀 재-dispatch, `None`→`nil` 흐름, 무한재귀 없이 종료 |
|
||||||
| `06-component-boundary-nil-hole-props` | `or None` 없으면 앞쪽 nil-hole로 슬롯 소실, 관용구 쓰면 항상 보존 |
|
| `06-component-boundary-nil-hole-props` | `or None` 없으면 앞쪽 nil-hole로 슬롯 소실, 관용구 쓰면 항상 보존 |
|
||||||
|
|
|
||||||
|
|
@ -415,7 +415,7 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검
|
||||||
- [x] **[2026-08-28 완료 — 단위 2]** **[2026-08-25 신설]** `store:Names(): { string }` — 선언된 키 집합
|
- [x] **[2026-08-28 완료 — 단위 2]** **[2026-08-25 신설]** `store:Names(): { string }` — 선언된 키 집합
|
||||||
열거(그림자 테이블의 키). 그룹 `Attribute(...)`/`attr:NameMap()`이
|
열거(그림자 테이블의 키). 그룹 `Attribute(...)`/`attr:NameMap()`이
|
||||||
요구한다(`base/attribute-plan.md`)
|
요구한다(`base/attribute-plan.md`)
|
||||||
- [x] **[2026-08-28 완료 — 단위 2]** `quad-types`에 `export type function`으로]** **[2026-08-25 신설, 2026-08-26 배선 정정 `H-112`]** `CheckReservedKeys`
|
- [x] **[2026-08-28 완료 — 단위 2, `quad-types`에 `export type function`으로 구현]** **[2026-08-25 신설, 2026-08-26 배선 정정 `H-112`]** `CheckReservedKeys`
|
||||||
타입 함수 — **`T`가 아니라 `keyof<T>`**(키 싱글톤 유니온)를 받아
|
타입 함수 — **`T`가 아니라 `keyof<T>`**(키 싱글톤 유니온)를 받아
|
||||||
예약 키를 검증만 하고, 팬텀 필드 `__reservedCheck`로 격리한다.
|
예약 키를 검증만 하고, 팬텀 필드 `__reservedCheck`로 격리한다.
|
||||||
`error()`가 아니라 `print(...)` + `return types.never`를 써야 한다.
|
`error()`가 아니라 `print(...)` + `return types.never`를 써야 한다.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue