From c10a709f2cdea9d58d2f4816b48b4c41d0b5268b Mon Sep 17 00:00:00 2001 From: DongHyeonka Date: Wed, 16 Sep 2026 20:18:01 +0900 Subject: [PATCH] docs: record how the IndexedDB kernel promotion actually executed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 사양 추정이 전부 빗나갔다. 4벌 감소는 -693이 아니라 -322이고 커널은 470이 아니라 1132줄이라, 소스 순증감이 -223이 아니라 +810이다. 원인은 사양이 translate/실패 헬퍼 비용을 안 셌고 이행된 파일의 주석이 크게 늘었기 때문이다. 작업의 근거는 처음부터 줄 수가 아니었지만, 줄 수가 준다는 기대가 틀렸다는 것은 기록해 둔다. 사양이 틀린 것으로 판명된 항목 3건과, 보존하지 못한 동작 1건(RT-2)을 복원하지 않기로 한 근거, 테스트로 덮지 못한 경로 1건을 남긴다. Co-Authored-By: Claude Opus 5 (1M context) --- .../2026-09-16-indexeddb-kernel-promotion.md | 94 +++++++++++++++---- 1 file changed, 77 insertions(+), 17 deletions(-) diff --git a/docs/superpowers/plans/2026-09-16-indexeddb-kernel-promotion.md b/docs/superpowers/plans/2026-09-16-indexeddb-kernel-promotion.md index 8307fa4..dbdd736 100644 --- a/docs/superpowers/plans/2026-09-16-indexeddb-kernel-promotion.md +++ b/docs/superpowers/plans/2026-09-16-indexeddb-kernel-promotion.md @@ -165,7 +165,7 @@ EOF - Consumes: Task 1의 `openIndexedDbDatabase`, `runIndexedDbTransaction`, `deleteIndexedDbDatabase` - Produces: 없음 (공개 포트 형태 불변) -- [ ] **Step 1: 동작 변화 지점을 테스트로 먼저 고정한다** +- [x] **Step 1: 동작 변화 지점을 테스트로 먼저 고정한다** 구현 전에 아래 5개가 현재 동작대로 통과하는지 확인한다. 이행 후에도 같아야 한다. @@ -179,7 +179,7 @@ EOF 기존 단언 위치: `:142` CONFLICT/RECONCILE, `:190` UNAVAILABLE/RESUME, `:194` blocked PENDING, `:238` false-abort 금지. -- [ ] **Step 2: 삭제 대상 블록을 커널 호출로 바꾼다** +- [x] **Step 2: 삭제 대상 블록을 커널 호출로 바꾼다** | 블록 | 줄 | 대체 | |---|---|---| @@ -191,7 +191,7 @@ EOF **남길 것:** `PENDING_DELETIONS` 레지스트리(L31-41) + 생성 시 검사(L116-122), `uploadCheckpointDatabaseName`(L73-83), `sameScopeBinding`(L656-672), `snapshotScope`(L674-691), `snapshotCheckpoint`(L693-711). -- [ ] **Step 3: 게이트 + 표적 테스트** +- [x] **Step 3: 게이트 + 표적 테스트** ```bash corepack pnpm check:types:app && corepack pnpm lint && corepack pnpm check:architecture @@ -199,7 +199,7 @@ npx vitest run tests/unit/resumable-upload-checkpoint.test.ts --reporter=default ``` Expected: 전부 PASS. CP-1~CP-5가 이행 전과 같은 답을 내야 한다. -- [ ] **Step 4: 커밋** +- [x] **Step 4: 커밋** ```bash git add src/adapters/browser-transfer tests/unit/resumable-upload-checkpoint.test.ts @@ -221,13 +221,13 @@ Co-Authored-By: Claude Opus 5 (1M context) " **Interfaces:** - Consumes: Task 1의 `openIndexedDbDatabase`, `openIndexedDbTransaction`, `runIndexedDbTransaction`, `walkIndexedDbCursor` -- [ ] **Step 1: 두 개의 함정을 먼저 이해한다** +- [x] **Step 1: 두 개의 함정을 먼저 이해한다** **MT-1 (가장 위험).** `blockedTimeoutMs`를 **넘기지 마라.** 안 넘겨야 오늘 동작(blocked 이벤트 즉시 `BLOCKED`, L485-492)이 유지된다. 넘기면 배치가 최대 그 시간만큼 매달린다. `tests/unit/indexeddb-maintenance.test.ts:289-290`이 `BLOCKED/retryable:true/RELOAD_OTHER_CONTEXTS`를 **즉시** 받길 기대하므로 값을 넣으면 타임아웃으로 실패한다. **MT-2.** `upgrade` 콜백을 **생략하라.** 생략해야 오늘의 "upgrade는 곧 실패"(L477-484)가 유지된다. 커널은 생략을 `UPGRADE_REJECTED`로 해석한다. 실수로 `upgrade: () => ({kind:"APPLIED"})`를 넣으면 **잘못된 스키마로 열린다.** -- [ ] **Step 2: 삭제 대상 블록을 커널 호출로 바꾼다** +- [x] **Step 2: 삭제 대상 블록을 커널 호출로 바꾼다** | 블록 | 줄 | 대체 | |---|---|---| @@ -241,7 +241,7 @@ Co-Authored-By: Claude Opus 5 (1M context) " **남길 것:** 체크포인트 상태기계(`readCheckpoint` L707-756, `commitPrepared` L969-1251), `prepareRecords`(L863-967), `clock`/`epochClock`(L401-421), `countBucket`(L239-245), 저장 술어(L118-223). -- [ ] **Step 3: 게이트 + 표적 테스트** +- [x] **Step 3: 게이트 + 표적 테스트** ```bash corepack pnpm check:types:app && corepack pnpm lint && corepack pnpm check:architecture @@ -249,7 +249,7 @@ npx vitest run tests/unit/indexeddb-maintenance.test.ts --reporter=default ``` Expected: 전부 PASS. 특히 `:289-290`이 **즉시** BLOCKED를 받아야 한다(타임아웃이 아니라). -- [ ] **Step 4: 커밋** +- [x] **Step 4: 커밋** ```bash git add src/adapters/storage tests/unit/indexeddb-maintenance.test.ts @@ -272,7 +272,7 @@ Co-Authored-By: Claude Opus 5 (1M context) " **Interfaces:** - Consumes: Task 1의 `openIndexedDbDatabase`, `createIndexedDbConnection`, `runIndexedDbTransaction`, `openIndexedDbTransaction`, `walkIndexedDbCursor` -- [ ] **Step 1: OP-1을 테스트로 먼저 드러낸다 — 이 태스크의 핵심** +- [x] **Step 1: OP-1을 테스트로 먼저 드러낸다 — 이 태스크의 핵심** 오늘 OP는 트랜잭션 안의 개별 요청에 `.onerror`를 **하나도** 안 단다(파일 전체에서 request `.onerror`는 L1042의 open 요청 하나뿐). 요청 실패는 `transaction.onabort`로만 흘러 `mapIndexedDbException(transaction.error)`(L1158)가 된다. @@ -280,13 +280,13 @@ Co-Authored-By: Claude Opus 5 (1M context) " 이행 **전에** 중복 put 테스트를 `tests/unit/indexeddb-opfs-journal.test.ts`에 추가해 현재 답을 기록하고, 이행 후 달라진 답을 의도된 변경으로 승인한다. 가짜 IDB가 이 경로를 표현하지 못하면 `tests/helpers/memory-indexeddb.ts`를 먼저 보강한다. -- [ ] **Step 2: OP-2를 확인한다** +- [x] **Step 2: OP-2를 확인한다** 오늘 OP의 `runTransaction`은 `succeed` 이후 `fail`이 와도 `explicitFailure`가 이기지만(L1133-1141, `hasValue`는 true 유지) `oncomplete`는 값을 반환한다(L1143-1153). 커널의 "첫 결과가 이긴다" 규칙을 따르면 **`succeed` 후의 `fail`이 무시된다.** 현재 OP 코드에 그 순서가 실제로 발생하는 경로가 있는지 **확인하라**(spec은 미확인으로 남겼다). 없으면 변화 없음으로 기록하고 넘어간다. -- [ ] **Step 3: 삭제 대상 블록을 커널 호출로 바꾼다** +- [x] **Step 3: 삭제 대상 블록을 커널 호출로 바꾼다** | 블록 | 줄 | 대체 | |---|---|---| @@ -299,7 +299,7 @@ Co-Authored-By: Claude Opus 5 (1M context) " **OP-4.** `signal`은 **넣지 않는다.** 오늘 없는 취소를 새로 만들지 않는다. -- [ ] **Step 4: 게이트 + 표적 테스트** +- [x] **Step 4: 게이트 + 표적 테스트** ```bash corepack pnpm check:types:app && corepack pnpm lint && corepack pnpm check:architecture @@ -307,7 +307,7 @@ npx vitest run tests/unit/indexeddb-opfs-journal.test.ts tests/unit/opfs-byte-st ``` Expected: PASS. OP-1로 인한 답 변화는 Step 1에서 승인한 것만 있어야 한다. -- [ ] **Step 5: 커밋** +- [x] **Step 5: 커밋** ```bash git add src/adapters/storage tests @@ -329,13 +329,13 @@ Co-Authored-By: Claude Opus 5 (1M context) " - Modify: `src/adapters/storage/indexeddb/indexeddb-runtime.ts` - Modify: `tests/unit/indexeddb-runtime.test.ts` -- [ ] **Step 1: RT-1을 번역기에 명시한다** +- [x] **Step 1: RT-1을 번역기에 명시한다** `close()`가 진행 중 open을 끝내는 원인이 `unavailable(operation)`(L743)에서 `translate({kind:"CLOSED"})`로 바뀐다. 번역기가 **`CLOSED → unavailable`을 명시 매핑**해야 오늘 동작이 유지된다. 빠뜨리면 `close()` 중 open이 ABORTED로 보고된다. 기존 실패 코드 단언 위치: `tests/unit/indexeddb-runtime.test.ts:339-340, :371-372, :454, :534, :549-550, :734-735`. -- [ ] **Step 2: 삭제 대상 블록을 커널 호출로 바꾼다** +- [x] **Step 2: 삭제 대상 블록을 커널 호출로 바꾼다** | 블록 | 줄 | 대체 | |---|---|---| @@ -353,7 +353,7 @@ Co-Authored-By: Claude Opus 5 (1M context) " **남길 것:** 코덱/영수증/보존/예산(bytes)/governance/migration 목록/상태 브로드캐스트/`monotonicClock`/`countBucket`/저장 레코드 술어/`purgePartitionRecords`의 스토어 순서 로직 — 전부 정책이다. -- [ ] **Step 3: 게이트 + 전체 테스트** +- [x] **Step 3: 게이트 + 전체 테스트** ```bash corepack pnpm check:types && corepack pnpm lint && corepack pnpm check:architecture \ @@ -363,7 +363,7 @@ corepack pnpm test:integration ``` Expected: `ci-artifact-contract.test.ts` 외 실패 0. -- [ ] **Step 4: 커밋** +- [x] **Step 4: 커밋** ```bash git add src/adapters/storage tests/unit/indexeddb-runtime.test.ts @@ -402,3 +402,63 @@ grep -rn "createObjectStore\|onupgradeneeded" src/adapters --include='*.ts' \ - **실패 매핑 4벌 통일** — `mapIndexedDbException`과 `mapBrowserDataException`이 같은 에러에 다른 답을 내는 것은 별건이다. 이 계획은 그 차이를 `translate` 주입으로 **보존**한다. - **`deleteDatabase`를 3벌에 추가** — CP에만 있는 것이 의도다(spec §2.3). - 대형 파일 분할 — 커널 이행으로 RT가 2902 → 2665가 되지만 여전히 크다. 분할은 별도 계획이다. + + +--- + +## 실행 기록 (2026-09-16 — 5개 태스크 전부 완료) + +커밋 5개: `cb62bfb`(커널) · `bb6080b`(CP) · `3366a81`(MT) · `217c1dd`(OP) · `a91e78f`(RT). + +### 실측 LOC — 사양 추정은 전부 빗나갔다 + +| 사본 | 사양 추정 | 실제(전체 줄) | 실제(코드 줄) | +|---|---:|---:|---:| +| CP `indexeddb-checkpoint-store.ts` | −142 | 712 → 612 = **−100** | | +| MT `indexeddb-maintenance.ts` | −188 | 1558 → 1430 = **−128** | | +| OP `indexeddb-opfs-journal.ts` | −127 | 1817 → 1881 = **+64** | 1744 → 1724 = −20 | +| RT `indexeddb-runtime.ts` | −235 | 2902 → 2744 = **−158** | 2807 → 2563 = −244 | +| 4벌 합 | **−693** | **−322** | | +| 커널 2파일 | +470 | **+1,132** | +751 | +| **소스 순증감** | **−223** | **+810** | | +| 커널 테스트 2파일 | (미기재) | +1,602 | | + +**공통 원인:** 사양이 `translate`/실패 헬퍼 어댑터 함수 비용을 세지 않았다. CP는 4개(~37줄), MT는 6개가 필요했다. 그리고 이행된 파일들의 주석이 크게 늘었다(OP 6→90줄, RT 12→111줄) — 이 레포 관례상 부풀림이 아니라 개선이지만, 줄 수 예측은 무너뜨린다. + +**결론은 바뀌지 않는다.** 이 작업의 근거는 처음부터 줄 수가 아니라 상태기계 통합이었고, 그건 달성됐다. 다만 **줄 수가 준다는 기대는 완전히 틀렸다**는 것을 기록해 둔다. + +### 부수 성과 + +- **abort 래칫이 24 → 21로 세 칸 조여졌다.** CP·MT·RT가 각각 자기 abort 리스너를 지웠다. 래칫이 설계대로 작동했다. +- **가짜 IndexedDB가 unique 인덱스를 전혀 강제하지 않는 것을 찾아 고쳤다**(`tests/helpers/memory-indexeddb.ts`). 보강 전에는 중복 `begin`이 `ok:true`로 성공했다 — 브라우저가 거부할 상태를 테스트가 조용히 허용하고 있었다. 이 발견이 커널 이행 자체보다 가치가 클 수 있다. +- 사양이 빠뜨린 함정 하나를 CP에서 막았다: `bindScope`는 `succeed()`에 해당하는 것이 없어 그대로 옮기면 정상 바인딩이 `CORRUPT_DATA`로 보고된다. 이후 MT·OP·RT는 성공 출구를 전수 대조했다. + +### 사양이 틀린 것으로 판명된 항목 3건 + +1. **OP-1** — 사양은 "이 리팩토링에서 가장 큰 동작 변화"로 지목하며 중복 put의 답이 달라진다고 봤다. **틀렸다.** 이행 전후 모두 `CONFLICT / retryable:false / recovery:NONE`이다. 요청 오류를 아무도 처리하지 않으면 스토어가 바로 그 에러로 abort해서 `transaction.error === request.error`이기 때문이다. 바뀐 것은 답이 아니라 출처다. +2. **RT-2** — 사양은 "의미 동일"이라고 썼다. **틀렸다.** 아래 참조. +3. **§3.1의 파일 분할 논거** — "470 LOC라 1파일은 폴더 관례를 깬다"고 썼으나 실제 구현은 1,132줄이다. 2분할 자체는 유지할 값이 있지만(테스트 셋업이 갈린다) 그 논거는 사실이 아니었다. + +### 보존하지 못한 동작 1건 — RT-2 (의도적으로 남김) + +blocked 데드라인 이후 재시도가 새 `factory.open()`을 띄운다. 이행 전에는 안 띄웠다. 커널에 "settle 이후에도 살아 있는 요청"을 알려줄 훅이 없기 때문이다. + +**복원하지 않기로 했다.** 근거: +- 이행 전후 모두 10초 blocked 데드라인이 있고, 차이는 두 번째 네이티브 open을 띄우는지뿐이다. +- 늦게 도착한 연결은 커널이 `closeQuietly`로 닫으므로 **연결 누수도 데이터 위험도 없다.** +- 재시도가 10초 데드라인에 게이트되므로 쌓이는 속도가 제한적이다. +- 반면 지금 커널을 고치면 **이미 검증이 끝난 4개 사본을 전부 재검증**해야 한다. 이익 대비 위험이 맞지 않는다. + +복원하려면 `openIndexedDbDatabase`에 `onSettled`를 추가하면 된다(`deleteIndexedDbDatabase`에는 이미 있다). 다만 그러면 "blocked 데드라인 실패가 버려진 요청이 끝날 때까지 다음 acquire를 막는가"라는 설계 질문이 따라온다 — 막는다면 다른 탭이 영영 안 닫힐 때 재시도가 영구 차단되어 **지금보다 나쁘다.** 착수 전 그 답부터 정해야 한다. + +### 테스트로 덮지 못한 경로 1건 + +RT의 `POLICY_REJECTED` upgrade 거절 경로. `queueIndexedDbUpgradeBinding`의 `onRejected`가 비동기라 `.then` 후처리로 보존했으나, **가짜 IDB의 upgrade 트랜잭션이 동기라 단위 테스트로 검증할 수 없다.** 레포 전체에 이 경로를 덮는 테스트가 없다. **실브라우저 확인이 필요하다.** + +### 검증 결과 + +**PASS:** `check:types` `lint` `check:architecture` `check:adapter-inventory`(21/21 래칫) `check:bundle`(181114/204800, 이행 전과 동일) `test:integration`(81) `test:component`(130) + +**`test:unit`:** 실패 파일은 `tests/unit/ci-artifact-contract.test.ts` 하나뿐(16~18건, 실행마다 흔들림 — `bwrap: loopback: Failed RTM_NEWADDR`). `develop` `5434760` 기준선에서도 동일함을 stash 후 실행해 확인했다. **그 파일 밖 실패 0.** + +**실브라우저 미검증:** `tests/browser-capabilities/indexeddb-runtime.spec.ts`(1033) · `opfs-runtime.spec.ts`(221) · `resumable-upload.spec.ts`(526). 전부 가짜 IDB 위에서만 검증됐다. blocked/versionchange 실동작과 위 upgrade 경로는 여기서만 잡힌다. **CI 또는 로컬 브라우저 실행이 남은 과제다.**