# VD-16: Browser transfer composition과 Image delivery - 상태: Accepted — design complete, implementation pending - 결정일: 2026-07-28 - 이 ADR이 선택한 common delta의 current status: `DESIGNED_NOT_IMPLEMENTED` - common delta의 목표 reference status: `AVAILABLE_NOT_COMPOSED` - 관련 결정: VD-10, VD-11, VD-12, VD-13, VD-14, VD-15 - current status ledger: `docs/architecture/browser-data-capability-completion-ledger.md` - 재검토: 첫 product upload/download/Image CDN capability를 조합하기 전 ## 배경 현재 저장소에는 File runtime, presigned capability provider/vault/executor, multipart/resumable upload, one-shot streaming download와 Image CDN verification runtime의 개별 factory가 있다. 이 구현들은 production bootstrap에서 제거돼 있고 각각의 local `close()` 또는 `dispose()`만 제공한다. 제품에서 이들을 직접 조합하면 다음 문제가 생긴다. - account/session이 바뀌어도 이전 capability, checkpoint, refresh 또는 async completion이 살아남을 수 있다. - 서로 다른 config가 같은 byte/resource/preset을 다르게 해석할 수 있다. - 일부 provider만 생성된 partial runtime이 요청을 받기 시작할 수 있다. - upload, download와 image에 retry, kill switch, deadline과 observation owner가 중복될 수 있다. - Image engine은 이미 decode된 server descriptor를 받으므로 BFF transport, expiry refresh와 presentation handoff의 책임이 비어 있다. - 실제 provider가 frontend mock과 같은 계약을 지키는지 재사용 가능한 conformance harness가 없다. 이 결정은 도메인별 upload 화면이나 cloud vendor를 공통 플랫폼에 넣지 않는다. 선택된 capability를 안전하게 조립·폐기하는 composition protocol과 Image descriptor acquisition 경계를 정한다. ## 현재 상태 | 항목 | 상태 | 설명 | | --- | --- | --- | | 개별 presigned/upload/image runtime | `AVAILABLE_NOT_COMPOSED` | factory와 deterministic test가 있으나 제품 graph에는 없음 | | one-shot streaming download | `AVAILABLE_NOT_COMPOSED` | Range resume가 아닌 전체 객체 스트림 | | top-level transfer runtime | `DESIGNED_NOT_IMPLEMENTED` | export index만 있고 atomic factory/readiness/lifecycle 없음 | | Image descriptor HTTP provider | `DESIGNED_NOT_IMPLEMENTED` | caller가 `BackendIssuedImageAsset`을 직접 전달 | | safe image DOM projection | `DESIGNED_NOT_IMPLEMENTED` | presentation descriptor는 있으나 renderer boundary 없음 | | app-managed background download | `NOT_SELECTED` | VD-14의 별도 optional capability | | cross-browser background-download guarantee | `PLATFORM_LIMITED` | browser-managed handoff가 기본 fallback | | app-managed background upload | `NOT_SELECTED` | durable source staging/worker auth protocol이 별도로 필요 | | cross-browser background-upload guarantee | `PLATFORM_LIMITED` | worker lifetime/local source permission을 공통 보장할 수 없음 | 이 ADR을 추가해도 위 상태는 자동으로 바뀌지 않는다. port, runtime, test와 removal evidence가 구현된 뒤에만 reference 상태를 올린다. ## 결정 ### 1. 하나의 account-scoped composition owner 선택된 file/transfer/image capability는 `BrowserTransferRuntimeComposition` 역할의 단일 owner가 다음 순서로 생성한다. ```text parse immutable config -> validate implementation ceilings -> obtain immutable session/account scope -> create policy registries -> create provider transports -> create capability vaults -> create checkpoint/lock/channel owners -> create file/upload/download/image runtimes -> run required compatibility probes -> publish READY facade atomically ``` factory가 중간에 실패하면 생성된 owner를 역순으로 닫고 facade를 반환하지 않는다. partial runtime, degraded provider 또는 mutable config를 application에 노출하지 않는다. composition은 다음 두 종류를 반환하는 union이어야 한다. ```text READY { generation, capabilities, application facades, readiness, close() } UNAVAILABLE { safe reason, retryability, fallback capability, disposePartial() } ``` `UNAVAILABLE`에 provider URL, raw browser exception, account/tenant ID 또는 credential을 넣지 않는다. ### 2. Runtime config는 closed schema다 config는 composition root만 읽고 깊은 snapshot/freeze한다. 최소한 다음 registry-owned reference를 갖는다. - config schema version과 runtime compatibility version - opaque session/account scope와 generation - application origin과 fixed BFF endpoint IDs - exact `PRESIGNED_TRANSFER_V1`, `PRESIGNED_MULTIPART_V1`, `RANGE_RESUMABLE_DOWNLOAD_V1`, `IMAGE_CDN_DESCRIPTOR_V1` 중 선택한 protocol registry와 fixed endpoint map - upload purpose/profile, part/concurrency/retry/deadline hard ceiling - download profile, size/integrity/strategy와 Range capability selection - checkpoint namespace, retention, inventory와 maintenance budget - lock/cancel transport selection과 unsupported outcome - Image issuer/origin/preset/key/probe/decode policy - descriptor refresh lead time, request/decode deadline와 concurrency - capability별 traffic admission과 kill switch - observation sink와 redaction policy - active-operation drain deadline caller는 raw endpoint, URL, header, object key, transform, retry count, byte ceiling, cache policy 또는 account partition을 request마다 override할 수 없다. config는 구현 절대 상한을 높일 수 없다. 구현 상한보다 큰 값, 중복 registry ID, same-origin private Image CDN, 모순되는 fallback 또는 provider 누락은 startup에서 fail-closed한다. ### 3. Lifecycle state machine top-level runtime은 다음 상태만 가진다. ```text CREATING -> PROBING -> READY -> DRAINING -> CLOSED CREATING | PROBING -> FAILED -> CLOSED ``` - `READY`만 새 operation을 받는다. - `DRAINING`은 새 operation을 거절하고 진행 중 operation에 bounded deadline을 제공한다. - deadline 뒤 남은 operation은 runtime lifetime signal로 abort한다. - `close()`는 terminal/idempotent이며 `DRAINING/CLOSED`에서 반복 호출해도 새 side effect를 만들지 않는다. - 닫힌 runtime은 reopen하지 않는다. 새 config/scope에는 새 generation을 만든다. - operation은 시작할 때 runtime generation과 account scope snapshot을 얻고 모든 async boundary와 terminal commit 전에 다시 확인한다. - 늦게 끝난 fetch, hash, IndexedDB transaction, image verification 또는 decode가 old generation이면 결과를 폐기하고 native resource를 닫는다. ### 4. Logout과 account/tenant switch session owner notification이 authority다. BroadcastChannel, storage event, capability expiry 또는 page unload를 logout authority로 사용하지 않는다. ```text session owner announces local revoke -> traffic admission CLOSED -> runtime generation FENCED -> reject new operations -> signal active reads/fetch/backoff/probes -> bounded drain -> close capability and image vaults -> close cancel channels and release locks/connections -> apply checkpoint retention/purge policy with exact old scope -> dispose observations -> CLOSED -> construct new scope/runtime independently ``` - old-scope purge와 new-scope open을 같은 transaction이나 facade에 섞지 않는다. - checkpoint가 crash 때문에 남아도 exact scope/policy binding이 다르면 새 runtime이 읽지 못해야 한다. - presigned URL, signed headers와 Image private URL은 어떤 teardown record에도 저장하지 않는다. - old account의 descriptor refresh, part completion과 download destination commit은 generation fence 뒤 성공으로 보고하지 않는다. - logout이 backend capability의 즉시 revoke를 보장하지 않는다. 강한 회수가 필요하면 BFF가 revoke authority 또는 proxy/relay를 제공해야 한다. ### 5. Capability별 facade application에는 top-level runtime 자체나 native adapter를 반환하지 않는다. composition은 설치된 feature에 필요한 좁은 facade만 주입한다. ```text FeatureUploadFacade -> select local file profile -> create/resume/pause/abort approved purpose FeatureDownloadFacade -> request approved resource -> receive policy-selected handoff/save outcome FeatureImageFacade -> request opaque asset/preset -> receive safe presentation descriptor ``` feature는 `File`, `Blob`, `Response`, `ReadableStream`, `FileSystemHandle`, presigned URL, Image signature DTO, checkpoint store 또는 QueryClient를 받지 않는다. progress UI를 위한 observation도 bounded aggregate snapshot이며 transfer authority가 아니다. ## Upload lifecycle integration ### 6. Pause와 abort는 다르다 top-level upload facade가 향후 제공할 상태는 다음과 같다. ```text ACTIVE -> PAUSE_REQUESTED -> PAUSED -> RESUMING -> ACTIVE ACTIVE | PAUSED -> ABORT_REQUESTED -> ABORT_PENDING -> ABORTED ACTIVE -> COMPLETING -> QUARANTINED ``` - pause는 새 part와 retry를 중지하고 현재 bounded native operation을 abort한 뒤 non-authorizing checkpoint를 유지한다. - cross-context pause wire literal은 `RESUMABLE_UPLOAD_PAUSE_V1`이며 `uploadKey`, exact scope/generation과 bounded message metadata만 운반한다. - durable `PAUSED`를 추가하는 checkpoint는 `schemaVersion: 2`다. v1 `ACTIVE | ABORT_PENDING` reader/writer와 섞지 않고 old-writer drain, historical migration과 N-1 fail-closed를 증명한다. - abort는 server session authority와 reconcile한 뒤 terminal checkpoint를 제거한다. - pause signal은 authority가 아니라 best-effort same-scope hint다. 수신자는 exact upload key/scope/generation을 검증한다. - checkpoint inventory는 presigned URL이나 raw file path 없이 opaque upload key, safe state, age/byte/part bucket과 expiry만 반환한다. - inventory/list와 retention sweep은 count, cursor, deadline을 갖는다. - abandoned/expired checkpoint는 server status 또는 expiry policy와 CAS를 확인한 뒤 bounded batch로 제거한다. - file 재선택 뒤 source fingerprint/size/media/part layout이 exact하게 맞지 않으면 resume하지 않는다. app-managed background upload는 이 state machine을 재사용할 수 있지만 page runtime의 pause/resume를 background 보장으로 표현하지 않는다. ## Download integration ### 7. Strategy selector download strategy는 application caller가 지정하지 않고 immutable profile, resource delivery class, expected bytes, browser capability와 user activation을 입력으로 하는 headless selector가 결정한다. | 조건 | 결과 | | --- | --- | | save picker 지원, user activation 있음, large stream | `WHOLE_OBJECT_PICKER_STREAM` | | server-managed resource, picker 없음 또는 handoff가 제품 정책 | `BROWSER_MANAGED_HANDOFF` | | generated artifact가 approved Blob cap 이하 | `BOUNDED_OBJECT_URL` | | large generated artifact, picker 없음 | `SERVER_GENERATION_REQUIRED` 또는 `UNSUPPORTED` | | Range profile + seekable destination + provider contract | `RANGE_RESUMABLE_FOREGROUND` | | background download가 선택되고 지원되는 browser + owned staging | `APP_MANAGED_BACKGROUND_DOWNLOAD` | selector는 capability probe와 actual invocation failure를 구분한다. `WHOLE_OBJECT_PICKER_STREAM`인데 picker가 없으면 request validation 오류가 아니라 `UNSUPPORTED` 또는 승인 fallback이어야 한다. Safari/WebView/private mode의 fallback도 동일 표에서 결정하며 user agent 문자열만으로 기능을 가정하지 않는다. selector의 위 값은 application-level `DownloadExecutionPlan`이다. 현재 file-delivery primitive와의 mapping은 다음처럼 닫는다. | execution plan | 현재 adapter mapping | | --- | --- | | `WHOLE_OBJECT_PICKER_STREAM` | `DownloadStrategy=PROMPT_AND_STREAM` | | `BROWSER_MANAGED_HANDOFF` | source kind `BROWSER_MANAGED_RESOURCE` + `DownloadStrategy=BROWSER_MANAGED` | | `BOUNDED_OBJECT_URL` | `DownloadStrategy=BOUNDED_OBJECT_URL` | | `RANGE_RESUMABLE_FOREGROUND` | VD-14의 별도 future port; 현재 adapter에 mapping 금지 | | `APP_MANAGED_BACKGROUND_DOWNLOAD` | 별도 optional worker/staging port | | `SERVER_GENERATION_REQUIRED` / `UNSUPPORTED` | browser delivery를 시작하지 않는 closed outcome | Range resume의 checkpoint, validator와 seek/truncate 결정은 VD-14를 따른다. app-managed background download는 기본 selector 결과가 아니다. ## Image descriptor acquisition과 delivery ### 8. Wire protocol private Image descriptor BFF 계약은 다음 literal을 사용한다. ```text IMAGE_CDN_DESCRIPTOR_V1 ``` request는 composition-owned fixed HTTPS endpoint를 호출하며 최소한 다음 application-safe 입력만 허용한다. - protocol - opaque asset reference - named preset reference 또는 preset family - intended presentation class - current runtime generation에 묶인 CSRF/session transport caller는 CDN URL, source URL, origin, object key, width, height, DPR, quality, fit, format, cache header, signing key ID나 expiry를 제출하지 않는다. response decoder는 content type, status, header/body byte cap, total deadline와 closed JSON shape를 검증한다. response에는 최소한 다음이 binding된다. - exact protocol과 issuer - opaque asset ID와 immutable revision - origin/preset binding IDs - static raster media와 intrinsic dimensions - allowed preset binding ID set - issued/expiry time - signature algorithm, key ID, canonical binding digest와 signature unknown field 정책은 protocol version에서 고정한다. credential, backend stack, raw provider key 또는 arbitrary transform은 descriptor에 포함하지 않는다. HTTP `200`만 descriptor success다. `401/403/404`의 외부 mapping은 existence hiding 정책에 따라 closed failure로 정규화하며 raw backend message를 버린다. redirect, opaque response, wrong content type, oversize, timeout와 malformed descriptor는 capability를 생성하지 않는다. ### 9. Provider와 verifier 경계 - BFF는 authorization, asset existence, quarantine/promotion state와 descriptor 발급 authority를 소유한다. - verifier registry는 composition이 승인한 bounded old/new public key set만 가진다. - client signature 검증은 BFF authorization의 대체가 아니라 response tamper와 registry mismatch를 fail-closed하는 보조 경계다. - CDN은 asset revision과 preset binding ID로 exact transform candidate를 재계산한다. signed query나 client 계산 width가 authority가 아니다. - private asset의 emergency revocation은 backend/CDN/BFF가 소유한다. client는 runtime close와 short expiry로 exposure를 줄인다. ### 10. Refresh state machine descriptor provider는 asset/preset/scope/generation별 bounded single-flight만 허용한다. ```text ABSENT -> FETCHING -> VERIFIED -> FRESH -> REFRESH_DUE -> REFRESHING -> FRESH FETCHING | REFRESHING -> TERMINAL_POLICY_FAILURE -> PLACEHOLDER FETCHING | REFRESHING -> RETRYABLE_FAILURE -> EXISTING_FRESH_UNTIL_EXPIRY | PLACEHOLDER any state + scope/runtime revoke -> REVOKED ``` - refresh lead time은 config가 정하되 expiry hard ceiling을 넘지 않는다. - private descriptor를 generic Query cache, Web Storage 또는 IndexedDB에 persistence하지 않는다. - concurrent callers는 같은 verified result를 받을 수 있지만 URL/string을 application state에 장기 복사하지 않는다. - 기존 descriptor가 아직 fresh하고 refresh가 일시 실패하면 expiry까지만 사용할 수 있다. expiry 뒤 stale-while-error를 금지한다. - `lazy` load로 실제 fetch가 expiry 뒤 시작될 가능성이 있으면 eager/priority로 바꾸거나 load 직전에 새 descriptor를 발급한다. - logout, key registry replacement와 runtime generation 변경은 in-flight transport, verification과 probe를 abort하고 늦은 결과를 폐기한다. ### 11. Safe presentation projection 공통 presentation primitive는 검증된 `ImagePresentationDescriptor`를 다음 정적 속성으로만 투영한다. - fallback `src` - ordered `` - registry-owned `sizes` - intrinsic `width`와 `height` - `loading`, `decoding`, `fetchpriority` - `referrerpolicy` - `crossorigin="anonymous"` primitive는 URL을 parse·조립·append하거나 transform query를 생성하지 않는다. descriptor가 가진 string을 React property로 전달하기 전에 closed allowed protocol/origin과 runtime generation을 다시 확인한다. raw HTML 주입과 CSS URL 조립을 금지한다. 다음은 제품 presentation owner가 결정한다. - 의미 있는 `alt` - placeholder와 오류 copy - skeleton/aspect-ratio UX - above-the-fold preload/priority - route/SSR preload hint - click/open/download behavior descriptor refresh 실패를 native broken-image UI에만 맡기지 않고 제품이 승인한 placeholder outcome으로 매핑한다. ## Provider contract harness ### 12. 재사용 가능한 suite frontend는 transport 구현과 분리된 provider contract harness를 제공한다. 같은 case set을 deterministic fake, local emulator와 실제 BFF/object storage/CDN에 실행한다. Presigned/download case: - exact protocol/status/content type/body cap - method/origin/path/query/header binding - redirect와 credential omission - expiry/revocation/replay - truncation/overrun/content encoding - CORS exposed receipt/checksum Multipart case: - create/status/part/complete/abort idempotency - part layout/checksum/receipt reconciliation - 404/410/expiry와 orphan cleanup - quarantine/promotion - retry-after와 ambiguous completion Image case: - protocol/issuer/key/preset exact match - old/new signing key overlap과 removal - immutable revision/cache key - private no-store/CORS/CSP - pixel/decode/encoded byte ceiling - malformed/animated/active content - expiry refresh, revocation과 placeholder actual provider test는 bearer URL, signature, account/asset/session ID를 artifact에 기록하지 않는다. fixture는 synthetic opaque values와 disposable storage를 쓴다. ## Failure, readiness와 fallback ### 13. Readiness report readiness는 application-safe capability별 결과다. | 상태 | 의미 | | --- | --- | | `READY` | 필수 provider/config/browser probe가 모두 유효 | | `DEGRADED` | 승인된 좁은 fallback만 가능 | | `UNAVAILABLE` | 기능을 노출하지 않음 | | `DRAINING` | 기존 작업만 정리 중 | | `CLOSED` | terminal | `DEGRADED`는 byte/pixel/security ceiling을 낮출 수는 있지만 높이지 않는다. 예를 들어 enhanced picker off → native input, multipart concurrency off → sequential, private Image CDN off → approved placeholder는 가능하다. integrity off, arbitrary URL 허용, private response cache 또는 unbounded Blob은 fallback이 아니다. ### 14. Kill switch 최소한 다음 switch를 독립적으로 둔다. - new presigned issuance - direct object-storage data plane - new upload session - upload resume - upload complete - Range resume - picker streaming save - private image descriptor issuance - advanced image format - responsive candidates switch 변경은 active operation의 의미를 소급 변경하지 않는다. 신규 진입을 닫은 뒤 reconcile/drain한다. remote runtime config를 사용한다면 config의 authenticity, release compatibility와 last-known-safe 정책을 별도 hosting 계약으로 검증한다. ## 관측성과 개인정보 허용: - operation kind와 safe outcome - runtime/readiness state - byte/part/candidate/retry/age/deadline bucket - policy rejection, abort, reconcile와 drain bucket - aggregate active count, checkpoint count와 orphan age 금지: - URL, query, signed/request/response header - capability, bearer token, signature와 key material - resource/session/asset/upload/account/tenant ID - file name, local path, object key와 storage physical key - digest, ETag, receipt와 checkpoint payload - raw backend/browser exception message와 stack runtime generation과 registry ID도 외부 telemetry에 그대로 보내지 않고 bounded compatibility bucket으로 변환한다. ## Rollout과 rollback ### 15. Rollout 1. ledger와 관련 ADR을 accepted로 고정한다. 2. closed config/port와 provider contract harness를 먼저 구현한다. 3. fake와 negative fixture에서 partial composition/late result를 거절한다. 4. top-level runtime을 `AVAILABLE_NOT_COMPOSED`로 유지하고 removal gate를 만든다. 5. product owner, exact scope와 provider config를 선택하고 bootstrap에 kill switch `DISABLED` 상태로 조합한다. 이 시점 primary status는 `COMPOSED`다. 6. 실제 BFF/storage/CDN conformance와 readiness probe를 통과한다. 7. Chromium/Firefox/WebKit과 실제 device에서 account switch, expiry와 crash를 검증한다. 8. runbook/rollback drill 뒤 internal cohort의 read-only/image public 또는 upload shadow flow부터 연다. 9. private/image upload/download capability를 독립 canary와 kill switch로 확대한다. ### 16. Rollback 1. 신규 issuance/session/descriptor를 중지한다. 2. runtime을 `DRAINING`으로 바꾸고 bounded operation을 마무리한다. 3. ambiguous upload는 server reconcile하고 Range partial은 checkpoint 정책대로 보존 또는 삭제한다. 4. private capability를 backend에서 revoke하고 client runtime을 close한다. 5. old compatible composition을 새 generation으로 다시 생성하거나 기능을 unavailable로 유지한다. 6. optional source, config와 facade를 제거하고 production module inventory와 removal gate를 재검증한다. schema version을 내리거나 checkpoint를 무조건 삭제해 rollback하지 않는다. ## 검증과 완료 기준 ### 17. Deterministic - partial factory failure의 reverse-order cleanup - close idempotency와 closed-runtime rejection - account switch 중 late fetch/hash/transaction/decode drop - config duplicate/ceiling/missing provider fail-closed - selector의 picker/size/resource matrix - upload pause/abort/reconcile race와 bounded inventory - descriptor refresh single-flight, expiry와 generation fence - picture projection의 arbitrary URL/query 생성 0건 - diagnostics forbidden-value negative fixtures ### 18. Native browser - Chromium/Firefox/WebKit의 native input/save picker fallback - multi-tab upload pause/cancel과 unsupported Web Locks path - page reload/account switch 중 active transfer drain - public/private Image fetch, actual CORS/no-store와 bitmap decode - offline/timeout/abort/late completion cleanup - Safari/WebView/private mode의 approved selector result ### 19. Provider와 operations - fake/emulator/실제 BFF·object storage·CDN 동일 contract suite - signing key rotation과 emergency revoke drill - checkpoint retention/orphan cleanup drill - capability별 kill switch와 N-1 rollback - runtime removal 후 source-module inventory 0건 다음 조건 전에는 목표 상태를 `AVAILABLE_NOT_COMPOSED`로 올리지 않는다. - [ ] top-level closed config와 atomic factory가 구현됐다. - [ ] generation-bound lifecycle과 account teardown이 구현됐다. - [ ] `RESUMABLE_UPLOAD_PAUSE_V1`, checkpoint schema v2 old-writer migration과 bounded upload inventory/retention owner가 구현됐다. - [ ] `PRESIGNED_TRANSFER_V1`과 선택 protocol의 strict codec, unknown-version rejection 및 reusable provider contract harness가 구현됐다. - [ ] strategy selector가 browser fallback을 fail-closed한다. - [ ] Image descriptor provider/refresh와 safe projection이 구현됐다. - [ ] deterministic fault, boundary, removal test가 통과한다. 다음 조건 전에는 제품 상태를 `COMPOSED`로 올리지 않는다. - [ ] product owner와 opaque account scope가 정해졌다. - [ ] strict config를 사용하는 top-level runtime이 production bootstrap에서 생성되고 실제 feature facade consumer까지 연결됐다. - [ ] traffic 기본값이 `DISABLED`이며 local teardown/close 경로가 연결됐다. 다음 조건 전에는 product-local production traffic을 승인하지 않는다. - [ ] actual BFF/storage/CDN contract harness가 통과한다. - [ ] browser/device evidence와 runbook drill이 보존됐다. - [ ] kill switch와 rollback owner가 운영 승인됐다. ## 관련 문서 - [Browser data capability completion ledger](../browser-data-capability-completion-ledger.md) - [Presigned transfer and Image CDN](../presigned-transfer-and-image-cdn.md) - [VD-14 Resumable download와 background download](./VD-14-resumable-download-and-background-transfer.md) - [Server file capability infrastructure](../server-file-capability-infrastructure.md) - [Browser transfer recovery](../../operations/browser-transfer-recovery.md) ## 선택하지 않은 대안 - feature가 개별 transfer adapter factory를 직접 조합 - singleton runtime을 여러 account/tenant가 공유 - raw presigned/Image URL을 Query cache나 persistence에 저장 - Image descriptor endpoint/transform을 request마다 caller가 지정 - page abort를 upload pause 또는 server abort 완료로 간주 - browser-managed handoff를 저장 완료로 간주 - user-agent 문자열만으로 Safari fallback 결정 - Service Worker를 설치하면 background upload/download가 보장된다고 가정 - capability 일부만 준비된 partial runtime을 degraded success로 반환 ## 결과 장점: - account 전환과 teardown의 한 owner가 생긴다. - 개별 runtime의 안전한 메커니즘을 제품별 facade로 좁혀 조합할 수 있다. - provider mock과 실제 인프라 사이의 계약 차이를 같은 suite로 찾을 수 있다. - Image URL과 transform이 application/presentation에서 재조립되지 않는다. - capability별 rollout, kill switch와 제거가 독립적이다. 비용: - config, lifecycle, provider harness와 browser evidence가 늘어난다. - 제품이 선택하지 않은 capability는 여전히 조합할 수 없으며 이것이 의도된 결과다. - actual provider와 운영 증거 없이는 reference runtime 구현만으로 production 완료를 주장할 수 없다.