Files
clean-architecture-frontend…/docs/architecture/decisions/VD-15-origin-storage-lifecycle-and-migration.md
T

39 KiB

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와 별도로 다음 축을 기록한다.

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으로 고정한다.

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를 제공한다.

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 순서는 모든 기술에서 다음 우선순위를 유지한다.

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만으로 다음 두 상황을 완전하게 구분할 수 없다.

이 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다.

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은 다음 흐름이다.

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를 만들지 않는다.

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으로 바꾼다.

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한다.

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로 승격한다.

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 제품 승격

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라고 부르지 않는다.