859 lines
39 KiB
Markdown
859 lines
39 KiB
Markdown
# VD-15: Origin storage lifecycle, migration, and optional file capabilities
|
|
|
|
- 상태: Accepted design — implementation pending
|
|
- 결정일: 2026-07-28
|
|
- 현재 구현 상태: capability별로 아래 표에 명시
|
|
- 이 ADR이 선택한 common delta의 목표 reference 상태:
|
|
`AVAILABLE_NOT_COMPOSED`
|
|
- 관련 결정: VD-10, VD-11, VD-12, VD-14
|
|
- current status ledger:
|
|
`docs/architecture/browser-data-capability-completion-ledger.md`
|
|
- 적용 범위: File/Blob preview, file/directory selection, IndexedDB, OPFS,
|
|
Cache Storage, StorageManager, optional Service Worker lifecycle
|
|
|
|
이 결정은 VD-11의 reference runtime을 실제 제품에 조립하기 전에 남아 있는
|
|
origin-wide lifecycle과 migration 경계를 고정한다. 문서가 추가됐다는 사실은
|
|
runtime 구현, bootstrap composition 또는 production traffic 승격을 의미하지
|
|
않는다.
|
|
|
|
현재 구현돼 있는 것은 transient file vault, file picker, bounded download,
|
|
IndexedDB repository/migration mechanism, OPFS object/journal runtime, public static
|
|
Cache Storage release runtime과 StorageManager inspection primitive다. 다음은 아직
|
|
구현되지 않았다.
|
|
|
|
- IndexedDB, OPFS, Cache Storage를 함께 조정하는 pressure/write-admission/GC
|
|
coordinator
|
|
- origin 전체 eviction을 완전하게 판별하는 mechanism
|
|
- OPFS physical layout/journal과 Cache control schema의 forward migration runtime
|
|
- 실제 OPFS write/read/delete readiness probe
|
|
- cursor/deadline이 있는 bounded Cache Storage inspection/cleanup
|
|
- Service Worker update/client-drain controller
|
|
- local preview의 pixel/decode/frame safety probe
|
|
- directory/persistent handle/drag-and-drop capability
|
|
- Range/206 download 또는 private/range response cache
|
|
|
|
## 1. 상태 모델과 현재/목표
|
|
|
|
### 1.1 다섯 primary current-status literal
|
|
|
|
capability의 source 구현, 제품 선택, composition, traffic과 evidence를 하나의
|
|
`enabled` boolean으로 합치지 않는다. 이 결정에서 capability의 **primary current
|
|
status**로 허용하는 literal은 정확히 다음 다섯 가지다.
|
|
|
|
| primary status | 의미 |
|
|
| --- | --- |
|
|
| `COMPOSED` | 실제 owner/policy/provider가 production composition root에 연결돼 있다. traffic이 disabled/canary/enabled인지는 이 상태가 아니라 별도 admission 축이다. |
|
|
| `AVAILABLE_NOT_COMPOSED` | 실행 가능한 runtime과 test가 source에 있지만 production bootstrap과 제품 dataset에는 연결하지 않았다. |
|
|
| `DESIGNED_NOT_IMPLEMENTED` | port, invariant, failure/recovery와 promotion 기준은 결정됐지만 실행 가능한 runtime이 없다. |
|
|
| `NOT_SELECTED` | 제품 요구와 owner가 capability를 선택하지 않았다. source 설계나 일부 primitive가 있더라도 runtime, DB, worker, listener를 만들지 않는다. |
|
|
| `PLATFORM_LIMITED` | 요구한 의미를 대상 browser 전체에서 application-controlled capability로 보장할 수 없다. 지원 engine의 progressive enhancement와 명시적 fallback만 허용한다. |
|
|
|
|
이 다섯 값은 선형 maturity 단계가 아니다. 예를 들어 구현이 존재해도 제품이
|
|
선택하지 않은 별도 capability의 primary status는 `NOT_SELECTED`일 수 있고,
|
|
cross-browser 보장이 불가능하면 구현량과 무관하게 `PLATFORM_LIMITED`다.
|
|
`COMPOSED`도 traffic enablement나 runtime health를 암묵적으로 뜻하지 않는다.
|
|
|
|
primary status와 별도로 다음 축을 기록한다.
|
|
|
|
```text
|
|
Selection
|
|
NOT_SELECTED | SELECTED | REMOVING
|
|
|
|
TrafficAdmission
|
|
DISABLED | SHADOW | CANARY | ENABLED
|
|
|
|
RuntimeHealth
|
|
UNKNOWN | AVAILABLE | DEGRADED | UNAVAILABLE | INCOMPATIBLE
|
|
|
|
PromotionEvidence
|
|
MISSING | PARTIAL | COMPLETE | EXPIRED
|
|
```
|
|
|
|
이 네 이름과 literal은 completion ledger의 canonical readiness 축이다.
|
|
`PromotionEvidence`는 별도 임의 enum이 아니라 ledger의 contract, provider,
|
|
browser, operations component gate를 합성한 값이다. required artifact가 없으면
|
|
`MISSING`, 일부만 terminal이면 `PARTIAL`, 모든 required gate와 freshness가
|
|
충족될 때만 `COMPLETE`, 한 번 유효했던 required artifact가 만료되면
|
|
`EXPIRED`로 기록한다.
|
|
|
|
예를 들어 실제 dependency를 조립한 첫 배포는
|
|
`primaryStatus=COMPOSED`, `TrafficAdmission=DISABLED`,
|
|
`RuntimeHealth=UNKNOWN`, `PromotionEvidence=PARTIAL`일 수 있다. probe와 canary
|
|
승격은 primary status를 새 literal로 바꾸지 않고 별도 축만 변경한다.
|
|
|
|
rollback은 `TrafficAdmission`을 먼저 `DISABLED`로 내린다. schema version을
|
|
내리거나, user-authored data를 자동 삭제하거나, unavailable runtime을 in-memory
|
|
fake로 교체하지 않는다.
|
|
|
|
### 1.2 current vs target
|
|
|
|
| capability | 현재 | 이 결정의 목표 | 비고 |
|
|
| --- | --- | --- | --- |
|
|
| transient File/Blob vault와 picker | `AVAILABLE_NOT_COMPOSED` | 유지 | 제품 policy가 없으므로 조립하지 않음 |
|
|
| foreground streaming/save와 browser handoff | `AVAILABLE_NOT_COMPOSED` | 유지 | Range resume는 포함하지 않음 |
|
|
| IndexedDB repository/codec migration | `AVAILABLE_NOT_COMPOSED` | 유지, coordinator hook 추가 대상 | 제품 dataset/schema는 없음 |
|
|
| OPFS object/journal v1 | `AVAILABLE_NOT_COMPOSED` | 유지 | 현재 byte runtime과 v1 reconciliation 범위 |
|
|
| OPFS real readiness preflight | `DESIGNED_NOT_IMPLEMENTED` | `AVAILABLE_NOT_COMPOSED` | property probe/native test를 composition readiness로 오인하지 않음 |
|
|
| public static Cache release v1 | `AVAILABLE_NOT_COMPOSED` | 유지 | 현재 stage/activate/previous retain은 구현 |
|
|
| bounded Cache inspect/cleanup | `DESIGNED_NOT_IMPLEMENTED` | `AVAILABLE_NOT_COMPOSED` | 현재 ownership은 검증하지만 cache-count scan은 unbounded |
|
|
| StorageManager estimate/persist primitive | `AVAILABLE_NOT_COMPOSED` | 유지 | origin coordinator는 없음 |
|
|
| origin storage lifecycle coordinator | `DESIGNED_NOT_IMPLEMENTED` | `AVAILABLE_NOT_COMPOSED` | 이 ADR이 계약을 확정 |
|
|
| OPFS physical/journal forward migration | `DESIGNED_NOT_IMPLEMENTED` | `AVAILABLE_NOT_COMPOSED` | 기존 v1 layout/journal을 migrator 구현으로 오인하지 않음 |
|
|
| Cache control/prefix forward migration | `DESIGNED_NOT_IMPLEMENTED` | `AVAILABLE_NOT_COMPOSED` | 기존 v1 parser/release primitive를 migrator 구현으로 오인하지 않음 |
|
|
| local preview safety probe | `DESIGNED_NOT_IMPLEMENTED` | `AVAILABLE_NOT_COMPOSED` | 현재 byte/signature check만 있음 |
|
|
| Service Worker update lifecycle | `NOT_SELECTED` | 제품이 PWA를 선택할 때 별도 승격 | Cache Storage 사용만으로 자동 선택하지 않음 |
|
|
| directory selection/persistent handles/drop | `NOT_SELECTED` | 제품 workspace 요구가 있을 때 별도 승격 | consent/retention 결정 필요 |
|
|
| Range resumable download | `DESIGNED_NOT_IMPLEMENTED` | VD-14의 별도 capability | backend validator/range 계약과 별도 runtime 필요 |
|
|
| sparse Range response cache | `NOT_SELECTED` | Range download와도 분리된 별도 capability | segment merge/cache threat model 필요 |
|
|
| private response cache | `NOT_SELECTED` | 별도 security review 전 금지 | public cache를 확장해 암묵 설치하지 않음 |
|
|
| app-managed background download | `NOT_SELECTED` | 지원 browser용 별도 optional capability | 별도 owner/staging/worker protocol 필요 |
|
|
| cross-browser app-managed background download guarantee | `PLATFORM_LIMITED` | browser-managed handoff를 기본 fallback으로 유지 | Service Worker가 장시간 transfer 지속을 보장하지 않음 |
|
|
|
|
## 2. 변경할 수 없는 불변조건
|
|
|
|
1. `navigator.storage.estimate()`는 rough origin signal이지 free-space reservation,
|
|
per-store usage 또는 eviction guarantee가 아니다.
|
|
2. 실제 `QuotaExceededError`가 write failure의 authority다. estimate가 정상이어도
|
|
write는 실패할 수 있다.
|
|
3. IndexedDB, OPFS와 Cache Storage 사이에는 atomic transaction이 없다. coordinator는
|
|
saga와 idempotency를 제공할 뿐 cross-API ACID를 주장하지 않는다.
|
|
4. user-authored/unsynced data는 pressure 또는 migration convenience를 이유로 자동
|
|
삭제하지 않는다.
|
|
5. credential, session token, raw authorization header, presigned URL과 signing key는
|
|
어느 origin store에도 저장하지 않는다.
|
|
6. migration은 expand/migrate/contract 순서다. committed OPFS file을 in-place로
|
|
변환하지 않고, Cache Storage의 검증되지 않은 candidate를 active로 만들지 않는다.
|
|
7. future schema를 이전 bundle이 발견하면 destructive open/delete 대신 read-only,
|
|
online-only 또는 export-required로 전환한다.
|
|
8. capability probe failure를 fake success로 바꾸지 않는다.
|
|
9. Service Worker 등록·활성화와 Cache Storage ownership은 서로 다른 capability다.
|
|
10. local image preview는 encoded byte cap만으로 decode safety를 주장하지 않는다.
|
|
11. directory handle과 persistent file handle은 transient file selection의 자연스러운
|
|
연장이 아니라 별도 consent/persistence capability다.
|
|
12. public Cache Storage는 private/auth/range representation의 repository가 아니다.
|
|
|
|
## 3. composition과 owner/policy injection
|
|
|
|
### 3.1 composition root
|
|
|
|
제품이 선택하면 한 composition root가 다음 dependency를 immutable snapshot으로
|
|
고정한다.
|
|
|
|
```text
|
|
OriginStorageLifecycleComposition
|
|
originScope
|
|
releaseId
|
|
datasetPolicyRegistry
|
|
storageDurability
|
|
indexedDbMaintenance[]
|
|
opfsMaintenance[]
|
|
publicCacheMaintenance[]
|
|
mutationLock
|
|
clock
|
|
scheduler
|
|
lifecycleAuthority
|
|
safeObserver
|
|
killSwitches
|
|
```
|
|
|
|
page, hook 또는 domain use case는 native manager와 maintenance adapter를 직접
|
|
조합하지 않는다. coordinator에 등록되는 각 dataset profile은 다음을 필수로
|
|
소유한다.
|
|
|
|
| 필드 | owner가 결정할 내용 |
|
|
| --- | --- |
|
|
| `datasetRegistryId` | readable user/account 값이 아닌 고정된 registry ID |
|
|
| `owner` | product owner와 operational owner |
|
|
| `technology` | IndexedDB, OPFS, public Cache 중 정확한 storage |
|
|
| `authority` | server, local-first, reconstructable |
|
|
| `classification` | public, internal, personal, confidential |
|
|
| `accountScope` | origin-shared 또는 opaque partition |
|
|
| `retention` | session, TTL, until-synced, explicit delete |
|
|
| `soft/hardBudget` | logical dataset budget; native free-space claim이 아님 |
|
|
| `pressurePriority` | expired/reconstructable, synced-copy, user-authored 순서 |
|
|
| `writeCriticality` | essential user write, sync receipt, reconstructable cache |
|
|
| `fallback` | read-only, online-only, export-required |
|
|
| `migrationOwner` | schema/codec/layout migration과 rollback owner |
|
|
| `recoveryOwner` | rehydrate, export, backend sync와 incident owner |
|
|
|
|
registry는 composition 시 deep snapshot/freeze하고 같은 문자열을 가진 caller-created
|
|
profile을 identity로 인정하지 않는다. presentation은 priority, retention 또는
|
|
eviction eligibility를 요청별로 고를 수 없다.
|
|
|
|
### 3.2 authority가 필요한 동작
|
|
|
|
logout, account deletion, until-synced deletion과 user-authored export/purge는 제품
|
|
authority를 요구한다. 기존 OPFS maintenance proof와 동일하게 provider가 exact
|
|
reason/scope/policy에 묶인 짧은 proof를 발급하고 consumer가 원자적으로
|
|
consume한다.
|
|
|
|
pressure에 따른 expired/reconstructable GC는 product proof가 없어도 실행할 수
|
|
있지만 등록 policy와 bounded budget을 벗어나면 안 된다. `SYNCED_COPY` 삭제는
|
|
authoritative server revision 또는 별도 sync receipt가 확인된 항목에만 허용한다.
|
|
|
|
## 4. origin-wide pressure, write admission, and GC
|
|
|
|
### 4.1 coordinator port
|
|
|
|
목표 runtime은 native type을 노출하지 않는 다음 의미의 port를 제공한다.
|
|
|
|
```ts
|
|
type PressureState =
|
|
| "UNKNOWN"
|
|
| "NORMAL"
|
|
| "PRESSURE"
|
|
| "CRITICAL"
|
|
| "QUOTA_FAILURE";
|
|
|
|
type WriteAdmission =
|
|
| { kind: "ADMITTED"; admissionId: string; attempt: 1 | 2 }
|
|
| { kind: "DEFERRED"; recovery: "RETRY" | "ONLINE_ONLY" }
|
|
| { kind: "DENIED"; recovery: "READ_ONLY" | "EXPORT_REQUIRED" };
|
|
```
|
|
|
|
실제 API는 repository write를 대신하지 않는다. 각 adapter가 write 직전 admission을
|
|
얻고, commit/rollback 이후 exact admission을 완료하도록 좁은 hook을 받는다.
|
|
admission ID는 diagnostic 또는 persistence에 남기지 않는 runtime-local fencing
|
|
token이다.
|
|
|
|
### 4.2 pressure state와 hysteresis
|
|
|
|
기본 threshold 시작점은 VD-11과 일치한다.
|
|
|
|
| 진입 조건 | 상태 |
|
|
| --- | --- |
|
|
| usage/quota를 모름 | `UNKNOWN` |
|
|
| `< 70%` | `NORMAL` |
|
|
| `>= 70%` | `PRESSURE` |
|
|
| `>= 85%` | `CRITICAL` |
|
|
| 실제 write의 quota exception | `QUOTA_FAILURE` |
|
|
|
|
flapping을 막기 위해 하향 전이는 더 낮은 threshold를 사용한다.
|
|
|
|
- `CRITICAL -> PRESSURE`: 두 번 연속 inspection에서 `< 80%`
|
|
- `PRESSURE -> NORMAL`: 두 번 연속 inspection에서 `< 65%`
|
|
- inspection 간격은 composition policy가 정하되 boot polling loop를 만들지 않는다.
|
|
- tab마다 독립 GC하지 않는다. 고정된 origin Web Lock 아래 leader 하나만 maintenance를
|
|
수행하고, lock이 없으면 destructive maintenance를 하지 않는다.
|
|
|
|
두 번 연속 규칙은 in-memory observation일 뿐 영구 storage truth가 아니다. 새
|
|
runtime은 persisted pressure state를 맹신하지 않고 `UNKNOWN`에서 시작한다.
|
|
|
|
### 4.3 admission matrix
|
|
|
|
| pressure | essential user-authored | sync/export receipt | reconstructable/cache |
|
|
| --- | --- | --- | --- |
|
|
| `UNKNOWN` | policy hard budget 안에서 허용, failure 대비 | 허용 | 보수적으로 defer 가능 |
|
|
| `NORMAL` | 허용 | 허용 | 허용 |
|
|
| `PRESSURE` | 허용 | 허용 | 먼저 bounded GC, 신규 speculative write 제한 |
|
|
| `CRITICAL` | hard budget과 recovery path가 있을 때만 허용 | 허용 우선 | 거절/online-only |
|
|
| `QUOTA_FAILURE` | rollback 후 export/sync UX | rollback 후 retry 조건 평가 | rollback, GC, 최대 1회 retry |
|
|
|
|
estimate만으로 “N bytes를 예약했다”고 기록하지 않는다. IndexedDB/OPFS의 logical
|
|
budget reservation은 동시 writer 간 policy ceiling을 강제하기 위한 값이며 origin
|
|
free space가 아니다.
|
|
|
|
### 4.4 GC 순서와 bounded execution
|
|
|
|
GC 순서는 모든 기술에서 다음 우선순위를 유지한다.
|
|
|
|
```text
|
|
incomplete candidate / stale staging
|
|
-> expired reconstructable
|
|
-> unreferenced immutable chunk with grace
|
|
-> inactive public cache release
|
|
-> confirmed synced copy
|
|
-> stop
|
|
```
|
|
|
|
user-authored/unsynced는 자동 GC 목록에 들어가지 않는다. 각 invocation은 다음
|
|
두 예산을 모두 가진다.
|
|
|
|
- 기본 최대 100 items 또는 5초
|
|
- 구현 절대 상한 500 items 또는 30초
|
|
|
|
각 native operation 사이에 deadline과 AbortSignal을 다시 확인한다. 결과는
|
|
`inspected`, `removed`, `releasedLogicalBytes`, `moreAvailable`,
|
|
`deadlineReached`와 opaque cursor를 반환한다. cursor는 dataset/policy/release
|
|
epoch에 묶고 다른 owner에서 replay하면 `STALE_RESULT`다.
|
|
|
|
### 4.5 quota failure 뒤 단 한 번의 retry
|
|
|
|
자동 retry는 다음 조건을 전부 만족할 때만 허용한다.
|
|
|
|
1. 첫 attempt가 실제 `QuotaExceededError`로 rollback됐다.
|
|
2. operation이 같은 idempotency key, revision fence와 payload digest를 가진다.
|
|
3. 외부 side effect 또는 cross-store publish가 commit되지 않았다.
|
|
4. bounded GC가 실제로 candidate를 제거했거나 pressure가 하향됐다.
|
|
5. retry가 같은 operation lifecycle에서 정확히 한 번뿐이다.
|
|
6. 새 admission token을 발급하고 현재 revision/generation을 다시 읽는다.
|
|
|
|
두 번째 quota failure, partial external commit, user-authored destructive overwrite,
|
|
unknown idempotency는 retry하지 않는다. recovery는 policy에 따라 `READ_ONLY`,
|
|
`ONLINE_ONLY` 또는 `EXPORT_REQUIRED`다.
|
|
|
|
## 5. eviction detection의 범위와 한계
|
|
|
|
### 5.1 감지할 수 있는 것
|
|
|
|
각 조립된 dataset은 opaque scope에 다음 binding을 둔다.
|
|
|
|
- IndexedDB governance row와 dataset epoch
|
|
- OPFS journal logical object와 physical manifest/digest
|
|
- public Cache active pointer와 verified release marker
|
|
- 선택적으로 backend가 알고 있는 opaque dataset installation epoch
|
|
|
|
다음 partial mismatch는 `STORAGE_EVICTED` 또는 `CORRUPT_DATA`로 구분할 수 있다.
|
|
|
|
- logical OPFS object는 있는데 physical manifest/chunk가 없음
|
|
- Cache active pointer는 있는데 candidate cache/marker가 없음
|
|
- migration checkpoint는 있는데 target generation이 없음
|
|
- expected dataset epoch와 local governance binding이 다름
|
|
|
|
reconstructable data는 rehydrate하고, local-first/user-authored data는 자동 empty
|
|
state로 초기화하지 않고 read-only/export-required incident로 올린다.
|
|
|
|
### 5.2 감지할 수 없는 것
|
|
|
|
browser가 origin의 IndexedDB, OPFS와 Cache Storage를 모두 함께 지우면 local
|
|
sentinel도 함께 사라진다. local state만으로 다음 두 상황을 완전하게 구분할 수
|
|
없다.
|
|
|
|
```text
|
|
이 browser의 첫 설치
|
|
origin storage 전체 eviction/user clear
|
|
```
|
|
|
|
따라서 “sentinel이 없으므로 첫 설치”라고 단정하지 않는다. 제품이 구분을 요구하면
|
|
현재 인증 session의 backend에 opaque installation/dataset epoch를 보관하고
|
|
authorization 후 비교해야 한다. backend marker도 browser byte backup이 아니며,
|
|
local-only data 복구를 보장하지 않는다.
|
|
|
|
backend epoch가 없으면 UI는 empty/new와 storage-reset-possible 상태를 제품 정책에
|
|
맞게 합쳐 표현해야 한다. raw account ID, filename, object ID 또는 digest를
|
|
sentinel/log에 넣지 않는다.
|
|
|
|
## 6. schema, codec, physical migration
|
|
|
|
### 6.1 독립 version 축
|
|
|
|
다음 version을 하나의 숫자로 합치지 않는다.
|
|
|
|
| 축 | 의미 | 현재 |
|
|
| --- | --- | --- |
|
|
| IndexedDB DDL | store/index/governance shape | reference runtime에 additive planner 있음 |
|
|
| IndexedDB record codec | payload decode/encode | resumable maintenance mechanism 있음 |
|
|
| OPFS journal DDL | logical object/journal/budget/refcount schema | v1 고정 |
|
|
| OPFS physical layout | root/path/manifest/chunk-tree algorithm | v1 고정 |
|
|
| Cache control schema | marker/active pointer JSON | v1 고정 |
|
|
| Cache release manifest | URL/header/type/length/digest binding | current static release contract |
|
|
| lifecycle registry schema | owner/policy/admission binding | 이 결정에서 설계, 구현 없음 |
|
|
|
|
IndexedDB mechanism이 존재한다고 해서 제품 codec/migration이 자동으로 존재하는
|
|
것은 아니다. OPFS와 Cache v1 parser가 있다는 사실도 forward migration 구현을
|
|
뜻하지 않는다.
|
|
|
|
### 6.2 공통 expand/migrate/contract
|
|
|
|
1. **expand:** 새 reader가 N과 N-1을 읽고 새 metadata/checkpoint를 additive하게
|
|
추가한다.
|
|
2. **drain:** old writer가 더는 N-1 shape를 쓰지 않는다는 release/lease evidence를
|
|
확인한다.
|
|
3. **migrate:** bounded batch와 keyset/opaque cursor로 copy/verify한다.
|
|
4. **publish:** row, checkpoint, logical budget과 generation fence를 가능한 한 같은
|
|
native transaction에서 commit한다.
|
|
5. **observe:** canary와 rollback window 동안 N-1 reader compatibility를 확인한다.
|
|
6. **contract:** 모든 active/rollback release가 지난 별도 release에서만 old shape를
|
|
정리한다.
|
|
|
|
schema downgrade, blanket database/cache/root deletion과 read-time unbounded rewrite는
|
|
금지한다.
|
|
|
|
### 6.3 OPFS migration
|
|
|
|
OPFS physical migration은 copy-on-write다.
|
|
|
|
```text
|
|
v1 committed object
|
|
-> v2 staging transaction
|
|
-> bounded chunk copy/read
|
|
-> v2 manifest + tree digest verify
|
|
-> IDB journal generation/fencing CAS
|
|
-> v2 logical publish
|
|
-> rollback window 동안 v1 retain
|
|
-> authority 확인 후 v1 bounded cleanup
|
|
```
|
|
|
|
- committed v1 file/chunk를 in-place로 수정하지 않는다.
|
|
- checkpoint는 last logical object key와 source/target generation을 저장한다.
|
|
- source digest, target digest, bytes와 policy binding이 맞지 않으면 quarantine하고
|
|
다음 object로 성공 처리하지 않는다.
|
|
- crash가 v2 publish 전이면 v1이 authority다.
|
|
- publish 후 cleanup crash는 v2가 authority이고 cleanup을 재개한다.
|
|
- N-1 bundle은 v2를 쓰지 않고 read-only/online-only로 degrade한다.
|
|
- local-first bytes를 contract하려면 export/sync 또는 승인된 rollback-window
|
|
evidence가 필요하다.
|
|
|
|
### 6.4 Cache migration과 rollback
|
|
|
|
public Cache data는 reconstructable이므로 byte-by-byte schema rewrite보다 새
|
|
release를 다시 stage/verify/activate한다. Service Worker를 선택하지 않은 static
|
|
Cache-only 조합의 migration은 다음 흐름이다.
|
|
|
|
```text
|
|
old active verified release
|
|
-> new prefix/control schema candidate
|
|
-> exact network fetch + integrity verify
|
|
-> explicit activation
|
|
-> old + previous retain
|
|
-> composition-owned rollback/grace window 확인
|
|
-> bounded owned-prefix cleanup
|
|
```
|
|
|
|
이 흐름에는 waiting worker, `controllerchange` 또는 controlled-client drain을
|
|
성공 조건으로 넣지 않는다. Service Worker를 별도 선택한 조합만 section 9.2의
|
|
waiting/activation protocol을 실행하고, old controlled client가 drain된 뒤 해당
|
|
release를 cleanup eligible로 만든다.
|
|
|
|
rollback은 검증된 previous release의 ID와 manifest digest로 같은 activation
|
|
protocol을 다시 실행한다. caller가 raw cache name이나 retain list를 전달하지
|
|
않는다. new control schema가 unreadable하면 old pointer를 덮어쓰지 않고
|
|
network-only로 degrade한다.
|
|
|
|
Cache control/release cleanup도 section 4의 cursor/deadline 예산을 적용한다.
|
|
unregister 또는 새 Service Worker install만으로 cache migration이 완료됐다고
|
|
보지 않는다.
|
|
|
|
### 6.5 N-1 rollback contract
|
|
|
|
모든 durable migration은 최소 다음 fixture를 보유한다.
|
|
|
|
- N-1 fresh -> N open
|
|
- N-1 populated -> N partial migration crash -> N resume
|
|
- N migration 완료 -> N-1 open: destructive write 없이 read-only/online-only
|
|
- N canary rollback -> N-1 server path로 정상 동작
|
|
- N rollback window 종료 뒤 별도 contract release
|
|
|
|
rollback bundle은 schema number를 낮추지 않는다. 새로운 writer를 끄고 compatible
|
|
reader/fallback을 사용한다.
|
|
|
|
## 7. OPFS real readiness preflight
|
|
|
|
현재 `inspectBrowserOpfsSupport()`는 API property를 확인한다. 목표 preflight는
|
|
실제 작은 operation을 검증한다.
|
|
|
|
### 7.1 probe protocol
|
|
|
|
probe는 `primaryStatus=COMPOSED`, `Selection=SELECTED`이고 readiness 확인이
|
|
필요할 때 실행한다. 최초 composition에서는 `TrafficAdmission=DISABLED` 또는
|
|
`SHADOW`로 probe하며, 선택하지 않은 skeleton boot에서 OPFS root/DB/worker를
|
|
만들지 않는다.
|
|
|
|
```text
|
|
secure context/API check
|
|
-> DedicatedWorker boot + protocol handshake
|
|
-> origin Web Lock acquire
|
|
-> owned opaque probe scope의 IDB journal transaction
|
|
-> random staging file create
|
|
-> bounded bytes write + flush/close
|
|
-> read + length/digest verify
|
|
-> file/journal cleanup
|
|
-> lock/worker/connection close
|
|
```
|
|
|
|
규칙:
|
|
|
|
- main thread에서 SyncAccessHandle을 만들지 않는다.
|
|
- synchronous path와 configured async writable fallback을 각각 capability로
|
|
보고한다.
|
|
- probe object ID/path는 secure random opaque value이고 log에 기록하지 않는다.
|
|
- 기본 deadline 5초, 절대 상한 30초다.
|
|
- timeout/crash 뒤 stale probe는 reconciliation owner가 grace 후 bounded cleanup한다.
|
|
- 결과는 runtime memory에 짧게 cache할 수 있지만 browser update, visibility가 긴
|
|
sleep에서 복귀, quota/permission failure 뒤 다시 `UNKNOWN`으로 돌린다.
|
|
- probe 성공은 future write 또는 persistence guarantee가 아니다.
|
|
|
|
### 7.2 readiness mapping
|
|
|
|
| 결과 | runtime health | admission |
|
|
| --- | --- | --- |
|
|
| full worker/lock/journal/write/read/delete 성공 | `AVAILABLE` | policy에 따라 가능 |
|
|
| sync handle 없음, 승인된 async fallback 성공 | `DEGRADED` | size/concurrency ceiling 하향 |
|
|
| API 없음/secure context 아님 | `UNAVAILABLE` | online-only |
|
|
| protocol/schema mismatch | `INCOMPATIBLE` | read/write 금지 |
|
|
| timeout/quota/permission | `DEGRADED` 또는 `UNAVAILABLE` | 신규 write 금지, recovery 실행 |
|
|
|
|
## 8. bounded Cache Storage maintenance
|
|
|
|
현재 static public cache는 release stage/verify/activate, previous retain과
|
|
owned-prefix cleanup을 구현한다. 현재 `cleanupOwned()`와 `inspect()`에는
|
|
max-count/deadline/cursor가 없다. 목표 contract는 이를 bounded operation으로
|
|
바꾼다.
|
|
|
|
```ts
|
|
type CacheMaintenancePage = Readonly<{
|
|
inspectedCaches: number;
|
|
deletedCaches: number;
|
|
retainedCaches: number;
|
|
unreadableCaches: number;
|
|
nextCursor: string | null;
|
|
moreAvailable: boolean;
|
|
deadlineReached: boolean;
|
|
}>;
|
|
```
|
|
|
|
- default 100 caches/5초, absolute 500 caches/30초
|
|
- cursor는 owned prefix, active pointer epoch와 policy fingerprint에 binding
|
|
- caller는 raw cache name, retain list 또는 prefix를 제출할 수 없음
|
|
- mutation Web Lock 아래 active pointer를 다시 읽은 뒤 한 cache씩 처리
|
|
- abort/deadline 뒤 이미 완료한 delete truth는 되돌리지 않고 cursor부터 재개
|
|
- corrupt active pointer면 destructive cleanup을 중지하고 network-only
|
|
- unreadable inactive candidate는 grace와 current/previous binding 확인 뒤 삭제
|
|
- `QuotaExceededError`를 이유로 다른 origin cache나 user data를 삭제하지 않음
|
|
|
|
inspection도 동일한 page contract를 써서 cache 수에 비례한 unbounded boot work를
|
|
금지한다.
|
|
|
|
## 9. static Cache release와 optional Service Worker lifecycle
|
|
|
|
### 9.1 현재 static release capability
|
|
|
|
현재 adapter가 소유하는 범위:
|
|
|
|
- same-origin anonymous public GET
|
|
- exact query/request header/Vary
|
|
- type, declared/actual length와 SHA-256
|
|
- candidate 전체 성공 뒤 explicit activation
|
|
- failed candidate 삭제와 기존 active 유지
|
|
- active + verified previous release retain
|
|
- private/no-store/auth/opaque/redirect/206 거부
|
|
|
|
Window 또는 Worker에서 Cache Storage를 쓸 수 있으므로 이 기능은 Service Worker
|
|
설치를 의미하지 않는다.
|
|
|
|
### 9.2 Service Worker를 선택할 때의 별도 protocol
|
|
|
|
PWA/offline interception을 제품이 선택하면 별도 owner가 다음 lifecycle을
|
|
composition한다.
|
|
|
|
```text
|
|
installing worker
|
|
-> candidate static release stage/verify
|
|
-> waiting
|
|
-> page update controller:
|
|
dirty form / active transfer / compatibility 확인
|
|
-> explicit ACTIVATE(version, manifest)
|
|
-> pointer flip
|
|
-> skipWaiting opt-in
|
|
-> controllerchange acknowledgement
|
|
-> old clients drain
|
|
-> clients.claim opt-in
|
|
-> previous release grace retain
|
|
-> bounded cleanup
|
|
```
|
|
|
|
`skipWaiting()`과 `clients.claim()`을 install handler에서 자동 호출하지 않는다.
|
|
message는 protocol version, release ID, nonce와 exact target worker에 binding하고
|
|
unknown message를 drop한다.
|
|
|
|
fetch 전략은 route registry에 고정한다.
|
|
|
|
| route class | 허용 전략 |
|
|
| --- | --- |
|
|
| content-hashed static asset | exact active cache-first |
|
|
| navigation | network-first + 별도 검증된 static offline page |
|
|
| runtime config/release manifest/auth/API | network-only |
|
|
| approved public runtime media | 별도 TTL metadata owner가 있을 때만 bounded SWR |
|
|
|
|
runtime TTL/SWR은 static release adapter의 묵시적 기능이 아니다. 별도 entry/count/
|
|
byte/TTL budget, revalidation owner와 prune cursor가 있어야 한다.
|
|
|
|
Service Worker는 application-controlled long-running background download를
|
|
cross-browser로 보장하지 않는다. download lifecycle은 VD-14의 별도 capability다.
|
|
|
|
## 10. local preview decode safety
|
|
|
|
### 10.1 현재와 목표
|
|
|
|
현재 preview path는 selection byte cap, signature receipt, media allowlist,
|
|
active-content denylist와 object URL lease를 제공한다. static raster의 intrinsic
|
|
dimensions, decoded surface와 animation frame 수를 검사하지 않는다.
|
|
|
|
목표 runtime은 object URL을 발급하기 전에 exact registered preview policy에
|
|
묶인 `PreviewSafetyProbePort`를 호출한다.
|
|
|
|
### 10.2 policy와 검사 순서
|
|
|
|
owner가 최소 다음을 결정한다.
|
|
|
|
- 허용 static format과 signature parser version
|
|
- max encoded bytes
|
|
- max width/height
|
|
- max total pixels
|
|
- max decoded bytes
|
|
- animation 허용 여부와 max frames/total pixels
|
|
- decode concurrency와 deadline
|
|
- malformed/unsupported metadata 동작
|
|
|
|
기본은 JPEG, PNG, WebP, AVIF 중 검토된 static parser만 허용하고 animation,
|
|
SVG, HTML, XML, PDF는 preview에서 거절한다. animation이 제품 요구면 별도
|
|
frame/time/memory capability로 승격한다.
|
|
|
|
```text
|
|
bounded header read
|
|
-> container/signature parse
|
|
-> width/height/frame/static 여부
|
|
-> overflow-safe pixel/decoded-byte 계산
|
|
-> optional real bitmap decode
|
|
-> decoded dimensions exact match
|
|
-> bitmap close
|
|
-> object URL lease 발급
|
|
```
|
|
|
|
`width * height * 4` 계산은 safe integer overflow를 검사한다. parser header만
|
|
신뢰하지 않고 지원 browser에서는 `createImageBitmap` 등 실제 decode를 bounded
|
|
concurrency/deadline 아래 확인하고 즉시 `close()`한다. decode failure 뒤 object
|
|
URL을 발급하지 않는다.
|
|
|
|
runtime absolute ceiling은 product policy보다 크거나 같고 caller는 낮출 수만 있다.
|
|
원본 filename, digest와 dimensions를 telemetry에 기록하지 않고 bucket만 남긴다.
|
|
|
|
## 11. directory, persistent handles, and drag-and-drop
|
|
|
|
세 기능은 현재 transient picker port에 추가하지 않는다.
|
|
|
|
### 11.1 directory selection
|
|
|
|
제품이 folder import/workspace를 선택하면 별도 `DirectorySelectionPort`를 만든다.
|
|
|
|
- `showDirectoryPicker`는 progressive enhancement
|
|
- `<input webkitdirectory>`는 검증된 baseline으로만 사용
|
|
- depth, entry count, per-file/total bytes, traversal time의 hard cap
|
|
- relative path segment NFC 정규화, `.`/`..`, separator, control/bidi 거부
|
|
- 파일이 아닌 entry, traversal 중 permission loss와 mutation을 closed failure로
|
|
처리
|
|
- traversal 결과는 opaque file refs와 sanitized relative metadata만 반환
|
|
- directory name/path를 domain ID 또는 log로 사용하지 않음
|
|
|
|
directory upload가 필요하면 backend도 archive/path/symlink/traversal과 total
|
|
expanded budget을 다시 검증한다.
|
|
|
|
### 11.2 persistent handles and permission
|
|
|
|
persistent handle은 별도 registry와 consent가 필요하다.
|
|
|
|
- IDB structured-clone support를 실제 probe
|
|
- handle 자체를 application/domain/query cache에 노출하지 않음
|
|
- opaque handle ref, account partition, purpose, retention과 last-used bucket만 보관
|
|
- boot/background에서 `requestPermission()` 금지
|
|
- explicit user action에서 `queryPermission()` 후 필요한 경우에만 request
|
|
- denied/revoked/stale handle은 `RESELECT`, silent empty file로 처리하지 않음
|
|
- logout/account deletion과 handle registry purge는 authority를 요구
|
|
- browser가 OS 권한 철회를 지원하지 않을 수 있음을 UX에 명시
|
|
|
|
handle persistence는 local bytes backup이 아니며 파일이 외부에서 바뀔 수 있다.
|
|
매 open마다 size/lastModified와 제품이 요구하는 content identity를 다시 검사한다.
|
|
|
|
### 11.3 drag-and-drop
|
|
|
|
현재 `DROP` source enum은 full adapter를 의미하지 않는다. 선택 시 별도 inbound
|
|
adapter가 `DataTransfer`를 event 안에서 snapshot하고, file-only drop과 directory
|
|
traversal을 구분한다. pasted/dropped HTML, URL과 string item을 file capability로
|
|
승격하지 않는다. same count/byte/type/path policy를 picker와 공유하되 UI event
|
|
type을 permission으로 사용하지 않는다.
|
|
|
|
## 12. Range와 private cache는 별도 capability
|
|
|
|
### 12.1 Range/206
|
|
|
|
public static cache는 `Range` request와 206 response를 계속 거절한다. resumable
|
|
download에는 별도 계약이 필요하다.
|
|
|
|
- immutable object version 또는 strong validator
|
|
- `Range`/`If-Range`
|
|
- exact `206 Content-Range`
|
|
- `200`, `206`, `412`, `416` state transition
|
|
- destination offset/seek/truncate와 partial checkpoint
|
|
- overlap/gap 방지
|
|
- capability 재발급 시 같은 representation binding
|
|
- 전체 완료 뒤 whole-object integrity
|
|
|
|
sparse range를 Cache Storage entry로 합치는 것은 현재 public release port의 역할이
|
|
아니다. 필요하면 OPFS staging 또는 별도 range store를 선택하고 backend
|
|
File/Object Server와 validator/range 계약을 맞춘다.
|
|
|
|
### 12.2 private response cache
|
|
|
|
private/auth/account representation은 public Cache Storage adapter에서 계속
|
|
fail-closed한다. offline private data가 제품 요구면 별도 설계가 최소 다음을
|
|
소유해야 한다.
|
|
|
|
- current authorization과 server source-of-truth
|
|
- opaque account partition
|
|
- logout/account-deletion purge authority
|
|
- TTL/revalidation/revocation
|
|
- offline disclosure threat model
|
|
- export/recovery
|
|
- XSS가 same-origin key를 사용할 수 있다는 한계
|
|
|
|
client-side encryption만으로 authorization boundary를 만들었다고 주장하지 않는다.
|
|
security/privacy 승인이 없으면 network-only다.
|
|
|
|
## 13. fault and recovery matrix
|
|
|
|
| fault | fail-closed 결과 | recovery |
|
|
| --- | --- | --- |
|
|
| estimate unavailable | `UNKNOWN` | essential만 policy budget 내 허용, speculative cache defer |
|
|
| pressure/critical | admission 제한 | bounded GC, sync/export 안내 |
|
|
| first quota failure | transaction/candidate rollback | eligible GC 후 exact operation 최대 1회 retry |
|
|
| second quota failure | 신규 write 중지 | read-only/online-only/export-required |
|
|
| partial sentinel mismatch | `STORAGE_EVICTED`/`CORRUPT_DATA` | reconstructable rehydrate, local-first quarantine |
|
|
| 모든 local marker 소실 | first install과 구분 불가 | backend epoch가 있으면 비교, 없으면 정직한 degraded UX |
|
|
| migration crash | old committed generation 유지 | checkpoint부터 resume/reconcile |
|
|
| future schema | `INCOMPATIBLE` | N-1 destructive write 금지, online-only/read-only |
|
|
| OPFS real probe fail | `DEGRADED/UNAVAILABLE` | async fallback probe 또는 online-only |
|
|
| Cache cleanup deadline | partial success + cursor | 다음 bounded invocation |
|
|
| corrupt active pointer | cleanup/interception 중지 | network-only, verified recovery tool |
|
|
| preview pixel/decode limit | `LIMIT_EXCEEDED/POLICY_REJECTED` | attachment-only 또는 reselect |
|
|
| persistent permission revoked | `PERMISSION_DENIED` | 명시적 reselect/re-authorize |
|
|
| SW old/new incompatibility | activation 중지 | old active 유지 또는 verified previous 재활성화 |
|
|
|
|
## 14. observability
|
|
|
|
허용:
|
|
|
|
- capability/lifecycle/runtime-health 상태
|
|
- operation과 closed failure code
|
|
- pressure/byte/count/duration bucket
|
|
- migration version ID와 processed/remaining bucket
|
|
- GC deadline/more-available 여부
|
|
- probe phase와 capability boolean
|
|
- release registry ID처럼 registry-owned non-user identifier
|
|
|
|
금지:
|
|
|
|
- filename, directory path, object ID, account/tenant ID
|
|
- URL/query/request/response body
|
|
- exact digest, ETag, raw cache/DB/path name
|
|
- handle, capability receipt, authority proof
|
|
- native exception message/stack
|
|
- exact usage/quota로 사용자의 device storage를 fingerprint하는 event
|
|
|
|
## 15. rollout and rollback
|
|
|
|
### 15.1 구현 순서
|
|
|
|
1. lifecycle policy/registry와 deterministic state machine
|
|
2. bounded maintenance page/cursor 계약
|
|
3. cross-store admission + injected fault adapters
|
|
4. OPFS real preflight
|
|
5. OPFS/Cache historical migration fixtures와 runtime
|
|
6. preview safety probe
|
|
7. optional capability는 제품 선택 후 별도 branch에서 구현
|
|
|
|
새 runtime은 구현과 evidence가 끝나도 skeleton에서는
|
|
`AVAILABLE_NOT_COMPOSED`로 종료한다.
|
|
|
|
### 15.2 제품 승격
|
|
|
|
```text
|
|
owner/policy와 필요한 경우 backend authority/re-sync 결정
|
|
-> registry 및 immutable composition
|
|
-> primaryStatus=COMPOSED, TrafficAdmission=DISABLED
|
|
-> real browser readiness/shadow inspection
|
|
-> RuntimeHealth=AVAILABLE 또는 승인된 DEGRADED
|
|
-> PromotionEvidence=COMPLETE
|
|
-> TrafficAdmission=CANARY (reconstructable dataset)
|
|
-> TrafficAdmission=CANARY (user-authored write)
|
|
-> migration/rollback drill
|
|
-> TrafficAdmission=ENABLED
|
|
```
|
|
|
|
user-authored/local-first를 reconstructable cache보다 먼저 canary하지 않는다.
|
|
|
|
### 15.3 rollback
|
|
|
|
1. 신규 write, migration, cache activation과 SW update를 disable한다.
|
|
2. in-flight operation을 abort/drain하고 native truth를 reconcile한다.
|
|
3. current schema를 읽을 수 있는 bundle은 read-only로 유지한다.
|
|
4. N-1이 future schema면 online-only/export-required로 전환한다.
|
|
5. previous verified static cache가 있으면 explicit activation으로 rollback한다.
|
|
6. user-authored data는 sync/export 확인 없이 purge하지 않는다.
|
|
7. old physical/cache generation은 rollback window와 client drain 뒤 bounded
|
|
maintenance로 정리한다.
|
|
|
|
## 16. test and promotion evidence
|
|
|
|
### 16.1 deterministic tests
|
|
|
|
- threshold/hysteresis와 concurrent admission
|
|
- pressure leader lock loss, abort, timeout
|
|
- first quota failure -> GC -> exact one retry
|
|
- retry가 non-idempotent/partial commit/second failure에서 차단됨
|
|
- GC ordering과 user-authored non-eviction
|
|
- sentinel partial mismatch와 all-marker-loss ambiguity
|
|
- migration batch crash/resume/replay/fencing
|
|
- OPFS v1->v2 copy/verify/publish/cleanup fault
|
|
- Cache candidate failure, pointer corruption, rollback과 cursor expiry
|
|
- cleanup/inspect count/deadline absolute ceiling
|
|
- preview hostile dimensions, integer overflow, animation, truncated container와 decode
|
|
- permission denied/revoked/stale persistent handle
|
|
- redaction과 dependency snapshot mutation
|
|
|
|
### 16.2 real browser tests
|
|
|
|
Chromium, Firefox와 WebKit에서 지원 범위를 명시하고 skip을 success로 세지 않는다.
|
|
|
|
- StorageManager estimate/persist denial
|
|
- native IndexedDB/OPFS/Cache quota exception mapping
|
|
- DedicatedWorker + Web Lock + OPFS write/read/delete probe
|
|
- two-tab migration/maintenance serialization
|
|
- versionchange/future schema and N-1 read-only
|
|
- actual Cache stage/activate/previous rollback/controlled client drain
|
|
- storage clear 뒤 explicit degraded behavior
|
|
- file preview real static decode/cleanup
|
|
- directory/handle은 지원 engine + OS manual evidence
|
|
|
|
quota를 실제로 완전히 채우는 flaky test는 유일한 gate로 쓰지 않는다. deterministic
|
|
fault injection과 실제 small-operation smoke를 함께 보존한다.
|
|
|
|
### 16.3 promotion artifact
|
|
|
|
artifact는 다음을 포함한다.
|
|
|
|
- release/commit, browser/OS/image
|
|
- policy/registry/migration suite version과 hash
|
|
- runtime lifecycle/health/admission
|
|
- deterministic + native pass/fail/skip
|
|
- historical fixture N-1/N/N+1 결과
|
|
- rollback drill과 recovery runbook link
|
|
- evidence expiry와 waiver
|
|
|
|
필수 engine skip, expired evidence, migration fixture 누락, quota retry invariant 위반,
|
|
preview decode safety 누락 또는 user-authored auto-delete가 있으면 promotion을
|
|
차단한다.
|
|
|
|
## 17. 완료 기준
|
|
|
|
이 결정의 공통 runtime 구현은 다음을 모두 만족해야
|
|
`AVAILABLE_NOT_COMPOSED`로 완료된다.
|
|
|
|
- origin coordinator가 immutable registry, 다섯 primary status literal과 독립된
|
|
selection/admission/health/evidence 축을 강제
|
|
- pressure hysteresis, bounded GC와 exact one-retry가 executable test로 검증
|
|
- all-marker-loss ambiguity를 API/result/문서에서 숨기지 않음
|
|
- OPFS real preflight가 worker/lock/journal/write/read/delete/cleanup을 검증
|
|
- OPFS/Cache forward migration과 N-1 rollback historical fixture 통과
|
|
- Cache inspect/cleanup이 cursor/count/deadline 상한을 강제
|
|
- static Cache와 optional Service Worker composition이 import/bundle 경계로 분리
|
|
- preview가 pixel/decoded-byte/animation/decode limit을 object URL 전에 강제
|
|
- directory/persistent/drop이 transient picker에 암묵적으로 추가되지 않음
|
|
- Range download와 private/sparse Range cache가 서로도 별도 capability로
|
|
남고 public cache가 둘을 계속 거부
|
|
- Chromium/Firefox/WebKit의 required evidence와 recovery/rollback drill 완성
|
|
- default production build에는 선택되지 않은 runtime, worker, DB open, listener,
|
|
timer가 없음
|
|
|
|
이 기준 전에는 기존 `AVAILABLE_NOT_COMPOSED` runtime 일부가 존재하더라도 origin
|
|
storage lifecycle 전체를 production-ready 또는 `COMPOSED`라고 부르지 않는다.
|