4204 lines
164 KiB
Markdown
4204 lines
164 KiB
Markdown
# Object Storage Production Capability Deep Design
|
||
|
||
- 작성일: 2026-07-28
|
||
- 상태: Approval Gate A 승인 반영, Batch A–D 및 Batch E pause checkpoint를 main에 통합, R2 미구현
|
||
- 독립 재리뷰: 완료 — 아키텍처 및 문서 실행성 blocker 0, high 0
|
||
- LLM Wiki capture: public vault의
|
||
`raw/branch-notes/chore-main-worktree-capability-integration.md`에 main 통합 기록 완료
|
||
- 기준: Java 21, Spring Boot 4.0.0, Gradle 멀티모듈 Clean Architecture
|
||
- 대상 leaf: `adapter-outbound-objectstorage`
|
||
- 현재 구현 수준: provider-neutral kernel + `filesystem-local-dev` managed single R1,
|
||
S3 managed/direct primitives, exact MinIO mutation profiles `UNSUPPORTED`,
|
||
legacy whole-object filesystem/S3-MinIO 격리
|
||
- 상위 문서:
|
||
[Production Capability Platform Design](2026-07-26-production-capability-platform-design.md)
|
||
- 구현 계획:
|
||
[Object Storage Production Capability Implementation Plan](../plans/2026-07-28-objectstorage-production-capability.md)
|
||
- 참고 설계:
|
||
[Redis Production Capability Deep Design](2026-07-26-redis-production-capability-design.md),
|
||
[Fileserver Production Capability Deep Design](2026-07-26-fileserver-production-capability-design.md),
|
||
[HTTP Client Production Capability Deep Design](2026-07-27-httpclient-production-capability-design.md)
|
||
|
||
## 0. 구현 상태
|
||
|
||
### Approval Gate A 결정 — 2026-07-29
|
||
|
||
사용자의 구현 계속 승인에 따라 Batch E의 public/sample 계약을 다음과 같이 고정한다.
|
||
|
||
- production scanner는 주장하지 않는다. 스캔 경계는 test fake만 제공하고 첫 sample
|
||
destination profile은 `integrity-verified-reference`로 한다.
|
||
- canonical surface는 additive
|
||
`POST /posters/{id}/imagePublications`와 별도 authorized GET status resource다. 저장소의
|
||
AIP-122 경로 규칙에 따라 collection segment에는 kebab-case를 사용하지 않는다.
|
||
POST는 항상 동일한 bounded `202` reservation을 반환하며 READY 결과를 같은 idempotency
|
||
response에 섞지 않는다.
|
||
- 정확한 full-file SHA-256은 canonical Base64 형식의 bounded multipart field로 받고 TX1 전에
|
||
semantic fingerprint에 포함한 뒤 staging 중 다시 검증한다.
|
||
- `Idempotency-Key`는 필수다. 첫 committed UploadIntent가 stable operation ID를 할당하며
|
||
generic idempotency reservation과 intent는 같은 TX1에서 원자적으로 commit한다.
|
||
- sample authorization은 기존 `poster:write` permission을 유지한다. 존재하지 않는 Poster
|
||
owner 속성을 발명하지 않는다.
|
||
- legacy `/posters/{id}/image`는 명시적 compatibility profile에서만 유지한다. canonical과
|
||
legacy controller는 동시에 활성화하지 않는다.
|
||
- 기존 `poster.image-attached`/`imageKey` 이벤트는 변경하거나 제거하지 않는다. canonical
|
||
흐름은 새 versioned V2 event를 사용하며 외부 zero-consumer를 주장하지 않는다.
|
||
- OpenAPI 승인은 새 publication/status surface에만 한정한다. 기존 DTO의 locator-shaped
|
||
필드는 compatibility window 동안 그대로 둔다.
|
||
- 통합 타임라인의 V8은 additive, forward-only다. V7 Poster schema의 `image_key`와 legacy
|
||
reader를 유지하고 canonical writer는
|
||
READY opaque reference만 기록한다. rollback은 구 binary가 additive columns를 무시하는
|
||
binary-only window이며 down migration이나 자동 object mutation은 없다.
|
||
|
||
2026-07-28 Batch D 체크포인트 기준 다음 범위가 구현되었다.
|
||
|
||
- `application-core`의 framework/provider-neutral object-storage identity, opaque reference,
|
||
bounded content callback, digest/range, request/receipt/outcome, capability requirement;
|
||
- managed, staged, direct single/multipart, scan-maintenance, purge-maintenance를 분리한 narrow port;
|
||
- 모든 mutation의 `ObjectOperationKey`, strict reference grammar/checksum, transient grant redaction;
|
||
- application contract의 framework/provider/transport/persistent-locator purity 검사;
|
||
- legacy port/receipt의 removal-boundary deprecation과 sample legacy import freeze;
|
||
- deterministic `data/v1`/`control/v1` namespace, reference/handle/fingerprint/policy codec,
|
||
operation epoch와 immutable route/policy snapshot;
|
||
- strict canonical JSON control envelope와 operation/manifest/reference/multipart record,
|
||
reserve/replay/conflict/pending-effect state kernel;
|
||
- bounded immutable local create, exact inspect/version, full/range transfer, conditional retire,
|
||
create resolution, symlink/path confinement, restrictive permissions와 single-process CAS;
|
||
- constructor-bound `app.object-storage` settings, compile-before-construction provider
|
||
contribution/assembler, destination/retained-route routers, disabled/unselected/invalid/close
|
||
side-effect matrix;
|
||
- explicit canonical legacy subgroup와 old alias isolation, sample local/test explicit activation;
|
||
- exact nine-card readiness registry. `filesystem-local-dev` managed single upload/download만 R1,
|
||
나머지는 R0;
|
||
- pinned AWS SDK `2.30.0` conditional/checksum/expected-owner API characterization과 explicit
|
||
`NettyNioAsyncHttpClient` policy/factory/lifecycle;
|
||
- exact AWS/MinIO binding, bounded async transport, managed S3 single/multipart/control primitives,
|
||
response-loss resolution, selected-only provider composition;
|
||
- durable direct single/multipart session and grant records, S3 presigner lifecycle, exact part
|
||
acknowledgement, completion/abort fences, terminal exact-version replay;
|
||
- digest-pinned MinIO contract/fault lanes and protected AWS compile-only lane. Exact MinIO는
|
||
create-only PUT/complete 조건을 무시하므로 managed/direct mutation profile을
|
||
`UNSUPPORTED`로 유지하며 readiness를 올리지 않는다.
|
||
|
||
증거는 [Batch A checkpoint](../../evidence/object-storage/batch-a-checkpoint.md),
|
||
[Batch B checkpoint](../../evidence/object-storage/batch-b-checkpoint.md),
|
||
[Batch C checkpoint](../../evidence/object-storage/batch-c-progress.md),
|
||
[Batch D checkpoint](../../evidence/object-storage/batch-d-checkpoint.md),
|
||
[Batch E pause checkpoint](../../evidence/object-storage/batch-e-pause-checkpoint.md)에 기록했다.
|
||
|
||
격리 브랜치에서는 private vault 경로를 잘못 선택해 capture가 차단됐지만, main 통합 작업은
|
||
사용자가 지정한 public vault의
|
||
`raw/branch-notes/chore-main-worktree-capability-integration.md`에 캡처했다.
|
||
|
||
legacy runtime 범위는 별도로 유지된다.
|
||
|
||
- `application-core`의 범용 `ObjectStoragePort`;
|
||
- caller가 지정한 raw key에 `byte[]`를 put/get/delete/exists하는 계약;
|
||
- local filesystem adapter;
|
||
- synchronous AWS SDK v2 `S3Client`를 사용하는 S3/MinIO adapter;
|
||
- 명시적 legacy-only filesystem 또는 S3 Spring composition;
|
||
- filesystem unit test, mocked S3 unit test, Testcontainers MinIO integration test;
|
||
- module registry가 허용한 `application-core`, `shared-contract` 의존성.
|
||
|
||
아직 구현되지 않은 runtime/provider 범위:
|
||
|
||
- staged upload, quarantine, scan, publication;
|
||
- public presigned upload/download endpoint와 inbound authorization;
|
||
- R2 수준의 exhaustive multipart/direct response-loss·race·restart qualification;
|
||
- lifecycle, versioning, retention, legal hold 검증;
|
||
- multi-node linearizable control CAS와 production unknown-outcome reconciliation;
|
||
- production credential, encryption, TLS, retention/Object Lock qualification;
|
||
- orphan/abandoned multipart/retired object reaper;
|
||
- database와 object storage 사이의 crash-safe workflow;
|
||
- 운영 metric, trace, audit, runbook;
|
||
- AWS sandbox fault/security evidence.
|
||
|
||
따라서 현재 MinIO round-trip test가 통과하더라도 S3 production readiness를 의미하지 않는다.
|
||
Batch B의 R1은 exact local functional topology 증거다. Multi-node CAS, power-loss durability,
|
||
S3/MinIO qualification, R2 또는 운영 준비 완료를 뜻하지 않는다.
|
||
|
||
## 1. 설계 판정
|
||
|
||
현재 구현은 개발 편의를 위한 blob CRUD 예제다.
|
||
|
||
```text
|
||
MultipartFile.getBytes()
|
||
-> UploadPosterImageCommand(byte[])
|
||
-> DB write transaction 안에서 ObjectStoragePort.put(raw key, byte[])
|
||
-> provider final object overwrite
|
||
-> aggregate에 raw key 저장
|
||
-> file:// 또는 s3:// 내부 locator를 HTTP 응답으로 반환
|
||
```
|
||
|
||
목표는 bucket/key CRUD wrapper가 아니다. 목표는 다음 capability다.
|
||
|
||
> 제한된 크기의 content를 bounded streaming으로 저장·검증하고, private immutable
|
||
> reference를 통해 공개 상태와 version을 추적하며, direct transfer와 multipart의
|
||
> 불확정 결과까지 재조정할 수 있는 production object publication capability
|
||
|
||
선택한 핵심 구조는 다음과 같다.
|
||
|
||
1. Application은 bucket, path, endpoint가 아닌 `ObjectDestinationId`를 선택한다.
|
||
2. 모든 mutation은 안정적인 `ObjectOperationId`와 canonical request fingerprint를 가진다.
|
||
3. caller는 raw provider key를 만들지 않는다. Adapter가 managed namespace 아래 immutable key를
|
||
생성한다.
|
||
4. Application과 domain은 provider locator 대신 opaque `ObjectReference`를 저장한다.
|
||
5. upload/download는 동기식 bounded chunk callback을 baseline으로 하며 adapter가 resource
|
||
lifecycle을 소유한다.
|
||
6. 업로드 완료, integrity 검증, malware scan, business attachment, public visibility를 서로 다른
|
||
상태로 모델링한다.
|
||
7. publication은 public ACL 변경이 아니라 private object에 대한 durable reference/manifest의
|
||
상태 전이로 구현한다.
|
||
8. provider 응답 유실은 임의 retry가 아니라 `INDETERMINATE`로 분류하고 먼저 reconcile한다.
|
||
9. ETag를 whole-object checksum 또는 portable version으로 간주하지 않는다.
|
||
10. database와 object storage 사이의 원자적 transaction을 주장하지 않는다.
|
||
11. provider 지원 수준은 설정 이름이 아니라 startup qualification과 CI evidence로 판정한다.
|
||
12. 사용하지 않는 provider는 client, thread, scheduler, directory 또는 network side effect를
|
||
만들지 않는다.
|
||
|
||
## 2. 상위 설계 및 기존 capability 설계와의 관계
|
||
|
||
상위 설계의 §13.3은 다음 cross-capability baseline을 이미 정했다.
|
||
|
||
- streaming, head, range;
|
||
- checksum과 conditional mutation;
|
||
- bounded presigned transfer와 multipart cleanup;
|
||
- encryption, TLS, endpoint, region, credentials;
|
||
- lifecycle, versioning, retention;
|
||
- quarantine와 scan 전 publication 금지;
|
||
- payload/metadata limit;
|
||
- explicit staged lifecycle;
|
||
- database rollback이 object write를 되돌린다고 가정하지 않기.
|
||
|
||
이번 문서는 그 방향을 구현 계획으로 변환할 수 있도록 다음 결정을 추가한다.
|
||
|
||
- 정확한 port 분리와 framework-free callback signature;
|
||
- logical destination, operation, object, reference, version의 identity;
|
||
- immutable data key와 mutable control record의 경계;
|
||
- state machine, request fingerprint, result taxonomy;
|
||
- managed upload, direct upload, multipart, download의 protocol;
|
||
- provider-neutral 보장과 AWS/MinIO/filesystem별 차이;
|
||
- provider capability descriptor와 startup qualification;
|
||
- presigned URL의 bearer-secret 취급 및 검증 후 publish;
|
||
- database attachment workflow의 commit 순서와 crash gap;
|
||
- version-aware delete, retention hold, orphan cleanup;
|
||
- exact readiness card와 R0/R1/R2/R3 증거;
|
||
- 기존 `ObjectStoragePort`와 sample-portfolio migration;
|
||
- idempotency response offload 계약과 sibling adapter boundary.
|
||
|
||
기존 심화 설계에서 재사용하는 방식은 다음과 같다.
|
||
|
||
| 기존 설계 | 재사용하는 결정 | Object Storage에서 달라지는 점 |
|
||
| --- | --- | --- |
|
||
| Redis | exact activation, provider/card readiness, fail-closed binding | data plane이 대용량 stream이며 object mutation의 unknown outcome을 별도로 다룬다. |
|
||
| Fileserver | stable operation ID, staged publication, immutable naming, reconciliation | rename 대신 object+manifest/reference 전이를 사용하고 multipart/presign이 추가된다. |
|
||
| HTTP Client | body lifecycle, timeout budget, retry amplification, secret-safe observability | provider SDK retry와 multipart part 단위 resource budget을 함께 제한한다. |
|
||
|
||
이 문서와 상위 문서가 충돌하면 Object Storage 구현 세부에는 이 문서를 적용한다. 모듈
|
||
dependency edge에는 언제나 `src/config/architecture/modules.json`이 우선한다.
|
||
|
||
## 3. 현재 코드의 증거 기반 진단
|
||
|
||
### 3.1 Application contract
|
||
|
||
| 영역 | 현재 구현 | 운영상 의미 |
|
||
| --- | --- | --- |
|
||
| Identity | caller supplied `String key` | tenant, destination, ownership, generation 경계가 없다. |
|
||
| Upload | `put(String, byte[], String)` | content 전체를 heap에 적재하고 overwrite한다. |
|
||
| Download | `Optional<byte[]> get(String)` | large object와 range/backpressure를 지원하지 않는다. |
|
||
| Existence | `boolean exists(String)` | not-found와 forbidden/provider failure를 충분히 구분하지 못한다. |
|
||
| Delete | unconditional `void delete(String)` | version, precondition, retention hold, unknown outcome이 없다. |
|
||
| Receipt | key, size, content type, provider `URI` | `file://`와 `s3://bucket/key`가 application/HTTP로 유출된다. |
|
||
| Error | primitive/SDK exception 혼합 | retry와 reconciliation 결정을 application이 할 수 없다. |
|
||
|
||
### 3.2 Filesystem adapter
|
||
|
||
현재 `FilesystemObjectStorageAdapter`는 다음 문제를 가진다.
|
||
|
||
- constructor에서 base directory를 즉시 생성한다;
|
||
- `Files.write(final, bytes)`로 기존 파일을 truncate/overwrite한다;
|
||
- temp, exclusive create, file force, directory force가 없다;
|
||
- `readAllBytes`로 전체 content를 heap에 적재한다;
|
||
- content type과 checksum을 영속화하지 않는다;
|
||
- `normalize().startsWith(baseDir)` lexical check만 사용한다;
|
||
- nested symlink와 TOCTOU 탈출을 막지 못한다;
|
||
- `"."`, `"a/.."`처럼 root alias로 normalize되는 key가 가능하다;
|
||
- root alias delete가 base directory 자체를 대상으로 삼을 수 있다;
|
||
- file permission, quota, inode, retention, orphan cleanup이 없다;
|
||
- absolute `file://` URI를 receipt에 넣는다.
|
||
|
||
따라서 이 provider는 현재 local developer fixture이지 persistent production store가 아니다.
|
||
|
||
### 3.3 S3/MinIO adapter
|
||
|
||
현재 `S3ObjectStorageAdapter`는 다음 문제를 가진다.
|
||
|
||
- synchronous `S3Client`와 `RequestBody.fromBytes`/`getObjectAsBytes`만 사용한다;
|
||
- put이 unconditional overwrite다;
|
||
- checksum, size precondition, metadata schema가 없다;
|
||
- exact version/ETag을 receipt에 보존하지 않는다;
|
||
- conditional create/delete와 reconciliation이 없다;
|
||
- presign과 multipart가 없다;
|
||
- SSE/KMS, expected bucket owner, ownership/BPA qualification이 없다;
|
||
- finite API call/attempt/acquire/read/write timeout을 명시하지 않는다;
|
||
- provider SDK exception이 application boundary를 통과할 수 있다;
|
||
- missing object mapping이 operation마다 일관되지 않다;
|
||
- `s3://bucket/key` URI 생성이 성공한 write 뒤에 별도로 실패할 수 있다;
|
||
- startup `HEAD bucket` 실패가 404이면 runtime identity로 bucket을 생성할 수 있다.
|
||
|
||
같은 adapter가 endpoint override와 path-style 설정만으로 AWS S3와 MinIO를 “동일한 backend”로
|
||
취급한다. 이는 protocol happy path 재사용에는 유용하지만 consistency, checksum, conditional
|
||
request, versioning, retention, encryption, error 의미론의 동등성을 증명하지 않는다.
|
||
|
||
### 3.4 Settings와 activation
|
||
|
||
현재 설정은 다음 production-unsafe default를 가진다.
|
||
|
||
```yaml
|
||
backend: filesystem
|
||
base-path: ./.data/objectstorage
|
||
bucket: ca-skeleton
|
||
endpoint: http://localhost:9000
|
||
region: us-east-1
|
||
path-style-access: true
|
||
auto-create-bucket: true
|
||
```
|
||
|
||
- filesystem bean이 `matchIfMissing=true`로 활성화된다;
|
||
- S3 설정이 없으면 local plaintext MinIO를 가리킨다;
|
||
- access key 하나만 있으면 secret key pair validation 없이 static provider를 만든다;
|
||
- production bucket을 runtime startup에서 생성할 수 있다;
|
||
- `app-bootstrap`은 registry상 objectstorage leaf에 의존하지 않지만 sample application의 넓은
|
||
component scan에서는 configuration이 발견된다;
|
||
- Docker runtime의 read-only root와 `./.data/objectstorage` default가 맞지 않는다;
|
||
- canonical env/settings/secrets registries에 object storage key가 등록되지 않았다.
|
||
|
||
### 3.5 Sample workflow
|
||
|
||
`sample-portfolio`의 image upload는 다음 failure gap을 가진다.
|
||
|
||
```text
|
||
begin DB transaction
|
||
load Poster
|
||
overwrite deterministic final object key
|
||
attach raw key
|
||
save Poster
|
||
enqueue outbox
|
||
commit DB transaction
|
||
```
|
||
|
||
- DB rollback이 이미 완료된 object overwrite를 복원하지 못한다;
|
||
- 기존 image를 overwrite했으므로 compensation으로 delete해도 이전 version을 복구하지 못한다;
|
||
- upload 시간 동안 DB transaction과 connection을 점유한다;
|
||
- concurrent transaction loser도 final object를 바꿀 수 있다;
|
||
- `MultipartFile.getBytes()`가 inbound와 application 양쪽에서 전체 heap materialization을 만든다;
|
||
- client가 보낸 content type을 검증 없이 신뢰한다;
|
||
- delete use case는 Poster만 삭제하고 object lifecycle을 처리하지 않는다;
|
||
- aggregate와 public DTO에 raw key가 저장/노출된다;
|
||
- scan, checksum, quarantine, ready 상태가 없다.
|
||
|
||
### 3.6 별도 persistence idempotency blob seam
|
||
|
||
`adapter:outbound:persistence-jpa`에는 idempotency response body를 외부 object store로 offload할
|
||
수 있다는 별도 interface가 있다. Object Storage leaf가 그 sibling adapter interface를
|
||
구현하면 registry edge와 Clean Architecture 방향을 위반한다.
|
||
|
||
이번 baseline에서 이 용도는 제외한다. 향후 필요하면:
|
||
|
||
1. application 의미인 response-blob 계약을 `application-core`에 둔다;
|
||
2. persistence adapter와 objectstorage adapter가 각각 application port만 의존한다;
|
||
3. orchestration은 application service 또는 composition root가 담당한다;
|
||
4. objectstorage가 persistence leaf의 type을 import하지 않는다.
|
||
|
||
## 4. 범위와 명시적 비범위
|
||
|
||
### 4.1 전체 설계 범위
|
||
|
||
- bounded streaming managed upload/download;
|
||
- single contiguous range read;
|
||
- metadata/head와 exact version;
|
||
- SHA-256 content digest와 provider transport checksum;
|
||
- immutable create와 conditional mutation;
|
||
- private staged object와 durable publish reference;
|
||
- server-mediated transfer;
|
||
- short-lived presigned PUT/GET;
|
||
- adapter-owned managed multipart와 optional direct multipart;
|
||
- quarantine/scan integration seam;
|
||
- version-aware retirement/delete;
|
||
- orphan, abandoned multipart, indeterminate operation reconciliation;
|
||
- filesystem-local-dev, AWS S3 general-purpose, version-pinned MinIO provider cards;
|
||
- TLS, credentials, ownership, encryption, endpoint, lifecycle startup validation;
|
||
- resource budgets, timeout, retry, graceful shutdown;
|
||
- readiness, metrics, trace, audit, CI evidence;
|
||
- sample image attachment migration protocol.
|
||
|
||
`filesystem-local-dev`는 이 전체 설계의 R0/R1 개발 provider일 뿐 R2 대상이 아니다. R2 claim은
|
||
exact AWS S3 또는 qualified MinIO/deployment provider와 §32의 개별 card 조합에만 부여한다.
|
||
|
||
### 4.2 Optional capability
|
||
|
||
다음은 destination이 요구하고 provider card가 증명할 때만 활성화한다.
|
||
|
||
- browser POST policy;
|
||
- SSE-KMS 또는 DSSE-KMS;
|
||
- Object Lock retention/legal hold;
|
||
- provider-side copy;
|
||
- provider checksum algorithm 추가;
|
||
- filesystem-local-persistent;
|
||
- direct multipart upload;
|
||
- customer-managed public download domain;
|
||
- provider notification을 이용한 reconciliation hint;
|
||
- cross-region replication을 고려한 secondary verification.
|
||
|
||
Optional capability 부재를 silent emulation하지 않는다. 요청 시
|
||
`UNSUPPORTED_CAPABILITY`로 실패한다.
|
||
|
||
### 4.3 비범위
|
||
|
||
- generic bucket CRUD/list console;
|
||
- runtime bucket 생성, lifecycle/IAM/KMS/IaC provisioning;
|
||
- public-read ACL 또는 website hosting;
|
||
- CDN/cache invalidation;
|
||
- generic file server 또는 mounted drop-zone;
|
||
- inbound object-created event consumer;
|
||
- scanner/AV engine 자체 구현;
|
||
- media transcoding, thumbnail 생성, EXIF business policy;
|
||
- arbitrary provider endpoint를 request마다 선택하는 기능;
|
||
- database와 object store의 distributed transaction 또는 exactly-once claim;
|
||
- backup/restore 시스템 자체;
|
||
- Glacier restore workflow;
|
||
- S3 Express One Zone/directory bucket;
|
||
- Multi-Region Access Point와 cross-region failover;
|
||
- provider replication을 application consistency로 일반화;
|
||
- unlimited multi-range response;
|
||
- S3 Select/object SQL;
|
||
- unbounded user-specified metadata/tag;
|
||
- raw object key를 public API로 제공하는 기능.
|
||
|
||
## 5. HARD invariants
|
||
|
||
다음 항목은 구현 선택이 아니라 위반 시 중단하는 불변식이다.
|
||
|
||
1. `domain-core`에는 Spring, AWS SDK, `Path`, `URI`, stream, transport DTO를 넣지 않는다.
|
||
2. `application-core` port에는 AWS SDK request/response, `S3Client`, provider exception을 노출하지
|
||
않는다.
|
||
3. inbound `MultipartFile`, servlet stream, WebFlux publisher를 application command로 전달하지
|
||
않는다.
|
||
4. controller가 bucket/key를 만들거나 SDK/repository/object persistence entity를 호출하지 않는다.
|
||
5. caller가 provider-relative raw key, bucket, base path, endpoint를 선택하지 않는다.
|
||
6. Destination ID는 bounded registered value이며 untrusted inbound/tenant input이 임의 destination을
|
||
선택하지 않는다.
|
||
7. object reference는 locator도 authorization token도 아니다.
|
||
8. presigned URL은 bearer secret으로 취급하고 log, trace, metric, audit payload에 기록하지 않는다.
|
||
9. 모든 production mutation은 stable operation ID와 canonical fingerprint를 가진다.
|
||
10. R2 write fingerprint는 exact length + full content digest 또는 검증 가능한 immutable source
|
||
revision을 포함한다.
|
||
11. 같은 operation ID에 다른 fingerprint가 오면 conflict이며 기존 결과를 반환하지 않는다.
|
||
12. 기본 upload는 immutable create-only다. unconditional overwrite는 baseline에 없다.
|
||
13. successful SDK response와 business publication을 같은 상태로 간주하지 않는다.
|
||
14. ETag를 portable checksum 또는 multipart 전체 MD5로 간주하지 않는다.
|
||
15. partial/truncated upload·download 또는 consumer failure를 success receipt로 만들지 않는다.
|
||
16. content size, chunk size, part size/count/concurrency, metadata 수와 길이에 finite limit가 있다.
|
||
17. timeout/retry 후 mutation 결과가 확정되지 않으면 phase-specific `INDETERMINATE`이며 blind
|
||
retry하지 않는다.
|
||
18. database transaction 안에서 remote object transfer를 실행하고 rollback 원자성을 주장하지
|
||
않는다.
|
||
19. unpublished/quarantined object에 public/direct download grant를 발급하지 않는다.
|
||
20. scan verdict가 필요한 destination은 `SCAN_CLEAN` 전 publish하지 않는다.
|
||
21. object lifecycle/retention/hold를 application business 삭제로 우회하지 않는다.
|
||
22. versioned bucket의 delete marker 생성을 physical purge라고 보고하지 않는다.
|
||
23. cleanup은 owned namespace와 known schema만 처리하고 newer/unknown record를 삭제하지 않는다.
|
||
24. LIST 결과만으로 direct lookup, ownership, completion truth를 판정하지 않는다.
|
||
25. production startup은 bucket, policy, lifecycle, public access setting을 자동 수정하지 않는다.
|
||
26. disabled capability는 bean/client/thread/scheduler/network/directory side effect가 0이다.
|
||
27. filesystem과 S3-compatible provider가 AWS S3와 동등하다고 일반화하지 않는다.
|
||
28. module dependency는 registry의 `allowed_dependencies`만 따른다.
|
||
29. provider/card별 증거 없이 module 전체를 R2라고 부르지 않는다.
|
||
30. readiness는 exact required destination/provider/capability mismatch에서 fail closed한다.
|
||
31. metric/tag/log에 object key, bucket, tenant/user ID, original filename, content, URL을 넣지 않는다.
|
||
32. verification과 Wiki capture 또는 명시적 capture 차단 사유 없이 완료를 주장하지 않는다.
|
||
|
||
## 6. 대안 검토
|
||
|
||
### A. 현재 CRUD port에 InputStream만 추가
|
||
|
||
기각한다.
|
||
|
||
- caller와 adapter 사이 close ownership이 모호하다;
|
||
- repeatability와 retry 가능성을 표현하지 못한다;
|
||
- raw key/overwrite/locator leak이 남는다;
|
||
- direct upload, multipart, staged publish를 같은 의미로 섞게 된다.
|
||
|
||
### B. 범용 `ObjectStoragePort` 하나에 모든 operation 추가
|
||
|
||
기각한다.
|
||
|
||
- read-only use case도 delete/presign/multipart 권한을 가진 interface를 주입받는다;
|
||
- provider optional capability가 nullable method와 runtime branch로 퍼진다;
|
||
- 테스트와 readiness가 “module on/off” 한 단계로 뭉개진다.
|
||
|
||
Operation family별 작은 port를 사용하고 destination capability compile 단계에서 조합한다.
|
||
|
||
### C. Application에 AWS presigner와 multipart upload ID 노출
|
||
|
||
기각한다.
|
||
|
||
- provider 교체와 테스트가 불가능해진다;
|
||
- provider upload ID와 part ETag가 domain state로 퍼진다;
|
||
- security/expiry/header 조건을 adapter 밖에서 조립하게 된다.
|
||
|
||
Application은 opaque `DirectTransferSessionId`, `PartReceiptToken`, typed grant만 본다.
|
||
|
||
### D. 업로드 후 final key로 copy하여 publish
|
||
|
||
기본 전략으로 기각한다.
|
||
|
||
- 대용량 object copy 비용과 latency가 추가된다;
|
||
- copy response loss의 unknown outcome이 하나 더 생긴다;
|
||
- KMS/metadata/version semantics가 복잡해진다.
|
||
|
||
기본은 immutable private data object를 그대로 두고 durable reference/manifest만 publish한다.
|
||
보안 등급이나 destination이 physical namespace 분리를 요구할 때만 optional copy capability를
|
||
사용한다.
|
||
|
||
### E. object tag 또는 ACL 변경을 publication truth로 사용
|
||
|
||
기각한다.
|
||
|
||
- tag/ACL policy와 cache/authorization coupling이 커진다;
|
||
- S3-compatible provider 의미가 일치하지 않는다;
|
||
- public access 실수의 blast radius가 크다.
|
||
|
||
모든 bucket은 private이고, application authorization 후 server stream 또는 short-lived grant로
|
||
전달한다.
|
||
|
||
### F. DB transaction 안에서 upload하고 실패 시 delete
|
||
|
||
기각한다.
|
||
|
||
- delete도 실패하거나 indeterminate일 수 있다;
|
||
- overwrite된 기존 object는 복구되지 않는다;
|
||
- DB lock/connection 보유 시간이 content 크기에 비례한다.
|
||
|
||
외부 transfer와 짧은 DB transaction을 단계별로 분리하고 durable recovery state를 둔다.
|
||
|
||
### G. provider별 Gradle leaf 즉시 분리
|
||
|
||
초기에는 보류한다.
|
||
|
||
현재 registry의 19개 leaf와 dependency direction을 유지하며 한 physical leaf 안에서 package,
|
||
settings, test suite, readiness card를 분리한다. 다음 조건 중 둘 이상이 생기면 별도 설계로
|
||
leaf split을 검토한다.
|
||
|
||
- provider별 독립 release cadence;
|
||
- SDK/security patch 주기가 현저히 다름;
|
||
- runtime image에서 선택하지 않은 SDK를 제거해야 함;
|
||
- credential/IAM 또는 deployment owner가 분리됨;
|
||
- provider별 test/CI 비용이 기본 pipeline을 과도하게 지연함;
|
||
- registry edge가 실제로 달라짐.
|
||
|
||
### H. filesystem을 S3 emulator로 승격
|
||
|
||
기각한다.
|
||
|
||
Filesystem은 local development와 provider-neutral contract 일부를 빠르게 검증하는 수단이다.
|
||
ETag, multipart, presign, versioning, retention, KMS를 흉내 내어 R2 증거로 사용하지 않는다.
|
||
|
||
## 7. 목표 아키텍처
|
||
|
||
```text
|
||
adapter:inbound:web
|
||
- multipart parsing / request validation
|
||
- bounded ingress bridge
|
||
- authorization mapping
|
||
|
|
||
v
|
||
sample/application orchestration
|
||
- business consent and ownership
|
||
- attachment state
|
||
- DB transaction boundaries
|
||
|
|
||
v
|
||
application-core object storage ports
|
||
- publication / inspection / transfer / deletion / direct transfer
|
||
- framework-free IDs, request, receipt, error
|
||
|
|
||
v
|
||
adapter:outbound:objectstorage
|
||
+-- binding compiler / capability registry
|
||
+-- provider-neutral operation kernel
|
||
+-- durable control-plane codec/store
|
||
+-- reconciliation / cleanup engine
|
||
+-- filesystem-local-dev provider
|
||
+-- AWS S3 general-purpose provider
|
||
`-- version-pinned MinIO provider
|
||
|
|
||
v
|
||
private provider data plane + private control namespace
|
||
```
|
||
|
||
Inbound stream과 outbound storage stream을 직접 결합하지 않는다. Application callback 경계가
|
||
다음 책임을 분리한다.
|
||
|
||
- inbound adapter: transport framing, multipart limit, client disconnect;
|
||
- application: business authorization, destination 선택, operation identity;
|
||
- objectstorage adapter: chunk budget, hashing, SDK lifecycle, retry, stage/publish;
|
||
- provider: durable byte/object primitive.
|
||
|
||
## 8. 모듈과 계층 소유권
|
||
|
||
### 8.1 `domain-core`
|
||
|
||
- provider 독립적인 business entity와 invariant만 소유한다;
|
||
- object storage concept가 business에 필요하면 opaque string wrapper 수준의 domain reference만
|
||
둘 수 있다;
|
||
- checksum, bucket, key, presign, multipart, scan vendor result를 소유하지 않는다.
|
||
|
||
### 8.2 `application-core`
|
||
|
||
- outbound port와 framework-free value object;
|
||
- logical destination, operation ID, object reference, version token;
|
||
- content producer/consumer callback;
|
||
- common request/result/error taxonomy;
|
||
- application-level deadline와 cancellation signal;
|
||
- capability requirement.
|
||
|
||
Business별 media type 허용, 사용자 소유권, 공개 승인, attachment 교체 정책은 sample 또는 실제
|
||
application layer가 소유한다. 공통 adapter limit는 business policy의 대체물이 아니다.
|
||
|
||
### 8.3 `adapter:outbound:objectstorage`
|
||
|
||
- settings binding과 exact validation;
|
||
- provider selection과 effective capability compilation;
|
||
- internal key/reference/manifest codec;
|
||
- SDK/client/presigner lifecycle;
|
||
- streaming, hashing, conditional operation;
|
||
- operation journal과 reconciliation;
|
||
- cleanup/retention execution;
|
||
- provider error normalization;
|
||
- health/metric/trace/audit instrumentation.
|
||
|
||
Malware clean/malicious 판정 정책을 adapter configuration이나 mapper에 넣지 않는다.
|
||
|
||
### 8.4 `adapter:inbound:*`
|
||
|
||
- HTTP body/multipart limit;
|
||
- upload command 생성;
|
||
- request content type/name을 untrusted input으로 다룸;
|
||
- callback을 통해 bounded chunk를 application에 전달;
|
||
- direct transfer grant를 안전한 response DTO로 매핑;
|
||
- raw reference 또는 URL logging 방지.
|
||
|
||
### 8.5 `sample-portfolio`
|
||
|
||
- image attachment business lifecycle;
|
||
- Poster ownership과 permission;
|
||
- 허용 media type/size/image decode 정책;
|
||
- pending/ready/retired attachment 상태;
|
||
- DB transaction/outbox orchestration;
|
||
- sample consumer test.
|
||
|
||
Production leaf가 sample type에 의존하지 않는다.
|
||
|
||
### 8.6 `app-bootstrap`
|
||
|
||
- 실제 runtime에서 objectstorage leaf를 classpath에 넣을지 결정;
|
||
- required destination/card와 environment profile을 composition;
|
||
- startup readiness exposure;
|
||
- business use case를 구현하지 않는다.
|
||
|
||
## 9. Application 계약
|
||
|
||
### 9.1 Port 분리
|
||
|
||
초기 target interface family는 다음 의미를 가진다. 이름은 구현 계획에서 Java naming 규칙에
|
||
맞춰 확정하되 의미를 합치지 않는다.
|
||
|
||
```java
|
||
public interface ManagedObjectPublicationPort {
|
||
ObjectPublishReceipt publish(
|
||
ObjectPublishRequest request,
|
||
ObjectContentProducer producer);
|
||
}
|
||
|
||
public interface ObjectInspectionPort {
|
||
Optional<ObjectDescriptor> inspect(ObjectReference reference);
|
||
}
|
||
|
||
public interface ObjectTransferPort {
|
||
ObjectReadReceipt transfer(
|
||
ObjectReadRequest request,
|
||
ObjectContentConsumer consumer);
|
||
}
|
||
|
||
public interface ObjectRetirementPort {
|
||
ObjectMutationReceipt retire(ObjectRetireRequest request);
|
||
}
|
||
|
||
public interface ObjectPurgeMaintenancePort {
|
||
ObjectMutationReceipt purge(ObjectPurgeRequest request);
|
||
}
|
||
|
||
public interface ObjectOperationResolutionPort {
|
||
ObjectOperationResolution resolve(ObjectOperationKey operationKey);
|
||
}
|
||
|
||
public interface ObjectPublicationHandoffPort {
|
||
ObjectHandoffReceipt claimForPublication(ObjectHandoffClaimRequest request);
|
||
ObjectHandoffReceipt renewClaim(ObjectHandoffRenewRequest request);
|
||
ObjectMutationReceipt releaseClaim(ObjectHandoffReleaseRequest request);
|
||
ObjectMutationReceipt authorizeAbort(ObjectAbortAuthorization request);
|
||
}
|
||
|
||
public interface DirectObjectUploadPort {
|
||
DirectUploadGrant createUploadGrant(DirectUploadGrantRequest request);
|
||
DirectUploadCompletionReceipt completeUpload(DirectUploadCompletionRequest request);
|
||
}
|
||
|
||
public interface DirectObjectDownloadGrantPort {
|
||
DirectDownloadGrant createDownloadGrant(DirectDownloadGrantRequest request);
|
||
}
|
||
|
||
public interface DirectMultipartUploadPort {
|
||
MultipartSession startMultipart(MultipartStartRequest request);
|
||
PartUploadGrant createPartGrant(PartUploadGrantRequest request);
|
||
PartReceiptToken acknowledgePart(MultipartPartAcknowledgement request);
|
||
MultipartReceipt completeMultipart(MultipartCompleteRequest request);
|
||
ObjectMutationReceipt abortMultipart(MultipartAbortRequest request);
|
||
}
|
||
```
|
||
|
||
Quarantine가 필요한 workflow에는 single-call `publish`만으로 부족하다. 다음 staged family를
|
||
분리한다.
|
||
|
||
```java
|
||
public interface StagedObjectPublicationPort {
|
||
ObjectStageReceipt stage(ObjectStageRequest request, ObjectContentProducer producer);
|
||
ObjectVerificationReceipt verifyIntegrity(ObjectVerifyRequest request);
|
||
ObjectPublishReceipt finalizePublication(ObjectFinalizeRequest request);
|
||
ObjectMutationReceipt abort(ObjectAbortRequest request);
|
||
}
|
||
|
||
public interface ObjectScanMaintenancePort {
|
||
ObjectReadReceipt transferForScan(
|
||
ObjectScanReadRequest request,
|
||
ObjectContentConsumer consumer);
|
||
ObjectMutationReceipt recordScanVerdict(ObjectScanVerdictRequest request);
|
||
}
|
||
```
|
||
|
||
`ManagedObjectPublicationPort.publish`는 compiled destination이 scan-free publication을
|
||
명시한 경우에만 사용한다. Scan-gated destination을 이 편의 port로 호출하면
|
||
`UNSUPPORTED_CAPABILITY`가 아니라 configuration/programming error로 fail closed하며 반드시
|
||
staged family를 사용한다.
|
||
|
||
모든 mutation request와 resolution은 공통 `ObjectOperationKey`를 포함한다. Operation record의
|
||
physical route를 current provider default로 추론하지 않는다. §10.2의 destination route token,
|
||
epoch registry, retained binding revision을 통해 exact control namespace를 찾는다.
|
||
|
||
Direct operation의 의미:
|
||
|
||
- `completeUpload`: client completion claim을 신뢰하지 않고 exact HEAD/checksum/size/encryption을
|
||
검증한 뒤 staged receipt를 만든다;
|
||
- `createDownloadGrant`: application authorization 뒤 published exact version에 대한 bounded GET
|
||
grant를 만든다;
|
||
- `acknowledgePart`: client가 반환한 bounded provider completion claim을 검증하고 server-side
|
||
opaque part token/ledger로 바꾼다;
|
||
- `completeMultipart`: server ledger의 part token만 받아 provider complete와 reconciliation을
|
||
수행한다.
|
||
|
||
Scan operation은 normal staged publication과 다른 maintenance 권한 port를 사용한다.
|
||
`ObjectScanMaintenancePort`의 의미:
|
||
|
||
- `transferForScan`은 unpublished exact version만 scanner workflow에 bounded read한다;
|
||
- `recordScanVerdict`는 object version, scan operation, scanner policy/version precondition이
|
||
일치할 때만 `CLEAN`, `MALICIOUS`, `INDETERMINATE` verdict를 기록한다;
|
||
- stale/duplicate verdict는 current object에 적용하지 않는다.
|
||
|
||
`ObjectScanMaintenancePort`와 `ObjectPurgeMaintenancePort`는 일반 business use case에 주입하지
|
||
않는다. Scanner workflow용 maintenance composition과 physical purge용 privileged composition도
|
||
서로 다른 concrete router/client 권한으로 유지한다. Maintenance
|
||
composition 또는 명시적으로 승인된 privileged administration workflow만 사용한다.
|
||
`ObjectOperationResolutionPort`는 read-only recovery service가 공유한다.
|
||
`ObjectPublicationHandoffPort`는 application DB/outbox와 adapter reaper 사이의 destructive
|
||
cleanup fence다. Objectstorage adapter가 persistence repository를 직접 조회하지 않는다.
|
||
|
||
구현 시 interface를 과도하게 세분화하지 않되 다음 권한 경계는 유지한다.
|
||
|
||
- inspect/read;
|
||
- managed write/stage/finalize;
|
||
- scanner unpublished-read/verdict maintenance;
|
||
- direct upload, direct download grant, multipart;
|
||
- business retirement;
|
||
- privileged physical purge;
|
||
- maintenance/reconciliation.
|
||
|
||
Maintenance port는 application business service에 주입하지 않고 운영 job/composition에만
|
||
노출한다.
|
||
|
||
### 9.2 Identity type
|
||
|
||
필수 identity:
|
||
|
||
| Type | 의미 | 생성 주체 |
|
||
| --- | --- | --- |
|
||
| `ObjectDestinationId` | logical storage/security/retention 목적지 | application/config |
|
||
| `ObjectOperationEpoch` | operation namespace rotation/rejection epoch | composition/application ID factory |
|
||
| `ObjectOperationId` | mutation 한 건의 stable idempotency identity | application |
|
||
| `ObjectOperationKey` | destination, epoch, operation을 묶은 모든 mutation의 exact key | application |
|
||
| `ObjectId` | immutable data object identity | adapter |
|
||
| `ObjectStageHandle` | unpublished exact object를 stage/scan/finalize에만 쓰는 opaque handle | adapter |
|
||
| `ObjectReference` | application이 저장하는 opaque published reference | adapter |
|
||
| `ObjectVersionToken` | exact immutable generation/version precondition | adapter |
|
||
| `DirectTransferSessionId` | direct upload workflow identity | adapter |
|
||
| `MultipartPartNumber` | bounded 1-based logical part number | application/adapter validation |
|
||
| `PartReceiptToken` | provider part result를 숨긴 opaque token | adapter |
|
||
|
||
모든 ID는:
|
||
|
||
- null/blank를 허용하지 않는다;
|
||
- canonical text form을 가진다;
|
||
- log용 hash/token을 별도로 제공한다;
|
||
- provider locator를 encode하지 않는다;
|
||
- parsing이 bounded이고 exception message에 secret/raw content를 넣지 않는다.
|
||
|
||
`ObjectOperationId`는 HTTP request ID와 다르다. 동일 business mutation retry에서 유지되고, 새
|
||
사용자 의도에는 새 ID를 사용한다.
|
||
|
||
`ObjectStageHandle`은 published reference가 아니며 public DTO/download port에 사용할 수 없다.
|
||
`ObjectStageReceipt`가 이를 반환하고 finalize success가 처음으로 `ObjectReference`를 만든다.
|
||
|
||
Destination ID의 provider/namespace binding은 한 번 published operation이 생기면 in-place로
|
||
재지정하지 않는다. Provider migration은 새 destination route token/binding revision을 만들고
|
||
기존 binding을 read/reconcile/drain 상태로 보존한다. 그렇지 않으면
|
||
`destinationId + operationId` retry가 과거 control record를 찾지 못한다.
|
||
|
||
### 9.3 Content callback
|
||
|
||
Java standard `InputStream`을 port에 그대로 넘기지 않는다. Baseline은 synchronous callback이다.
|
||
|
||
```java
|
||
@FunctionalInterface
|
||
public interface ObjectContentProducer {
|
||
void produce(
|
||
ObjectContentProductionContext context,
|
||
ObjectChunkSink sink)
|
||
throws ObjectContentProductionException;
|
||
}
|
||
|
||
public interface ObjectChunkSink {
|
||
void write(byte[] bytes, int offset, int length)
|
||
throws ObjectChunkWriteException;
|
||
}
|
||
|
||
@FunctionalInterface
|
||
public interface ObjectContentConsumer {
|
||
void consume(
|
||
ObjectContentReadContext context,
|
||
ObjectChunkSource source)
|
||
throws ObjectContentConsumptionException;
|
||
}
|
||
|
||
public interface ObjectChunkSource {
|
||
int read(byte[] destination, int offset, int length)
|
||
throws ObjectChunkReadException;
|
||
}
|
||
```
|
||
|
||
계약:
|
||
|
||
- context는 read-only `CancellationView`, remaining deadline/budget checkpoint를 제공한다;
|
||
- read context는 body 전에 validated descriptor, exact version, delivered range/length를 제공한다;
|
||
- callback이 반환되면 producer/consumer가 provider resource를 보관할 수 없다;
|
||
- adapter가 sink/source를 닫고 invalidation한다;
|
||
- source는 EOF에 `-1`, positive request에서 progress가 없으면 bounded zero-read 후 protocol
|
||
failure를 반환한다;
|
||
- offset/length/array bounds를 호출 전에 검증한다;
|
||
- producer의 input array는 `write` 반환까지만 유효하며 adapter가 반환 뒤 reference를 보관하지
|
||
않는다;
|
||
- consumer destination array는 `read`를 호출한 consumer가 소유하며 adapter가 보관하지 않는다;
|
||
- configured max chunk보다 큰 write를 쪼개거나 거부한다;
|
||
- producer가 던진 application 오류와 storage 오류를 분리한다;
|
||
- producer는 한 operation attempt에서 기본적으로 한 번만 호출한다;
|
||
- 재호출이 필요하면 request가 repeatable임을 명시하고 별도 factory를 사용한다;
|
||
- consumer failure, cancellation, disconnect는 read success가 아니다;
|
||
- adapter는 application callback을 SDK event-loop thread에서 실행하지 않는다;
|
||
- callback이 blocking임을 contract에 명시한다.
|
||
|
||
향후 reactive port가 필요하면 별도 capability로 설계한다. `Flow.Publisher`, Reactor type 또는
|
||
AWS `AsyncRequestBody`를 이 baseline port에 노출하지 않는다.
|
||
|
||
### 9.4 Publish request
|
||
|
||
`ObjectPublishRequest`의 최소 필드:
|
||
|
||
- `ObjectOperationKey operationKey`;
|
||
- `ObjectMediaType declaredMediaType`;
|
||
- `ObjectContentIdentity contentIdentity`;
|
||
- `ObjectPublicationRequirement publicationRequirement`;
|
||
- `ObjectRetentionRequirement retentionRequirement`;
|
||
- `ObjectEncryptionRequirement encryptionRequirement`;
|
||
- `OperationDeadline deadline`;
|
||
- bounded correlation/audit context.
|
||
|
||
넣지 않는 필드:
|
||
|
||
- bucket;
|
||
- raw key/path;
|
||
- region/endpoint;
|
||
- AWS storage class enum;
|
||
- KMS raw key ARN;
|
||
- public ACL;
|
||
- arbitrary metadata map;
|
||
- original filename;
|
||
- inbound DTO.
|
||
|
||
Destination binding이 provider와 namespace, maximum size, checksum, encryption, retention,
|
||
direct-transfer 허용 여부를 결정한다.
|
||
|
||
R2 `ObjectContentIdentity`는 다음 중 하나다.
|
||
|
||
- exact byte length + expected full SHA-256;
|
||
- adapter가 검증 가능한 immutable source reference/revision + expected length/digest.
|
||
|
||
Maximum size만 있고 payload digest가 없는 one-shot upload는 다른 payload로 operation ID를
|
||
오용했을 때 conflict를 검출할 수 없다. 이 경로는 R0/R1 compatibility로만 허용하고 R2
|
||
publication card에 포함하지 않는다.
|
||
|
||
### 9.5 Read request
|
||
|
||
`ObjectReadRequest`:
|
||
|
||
- exact `ObjectReference`;
|
||
- optional expected `ObjectVersionToken`;
|
||
- `ObjectReadRange`;
|
||
- digest verification mode;
|
||
- maximum delivered bytes;
|
||
- deadline/cancellation.
|
||
|
||
Public `ObjectTransferPort`는 caller가 publication/scan requirement를 낮추는 field를 받지 않는다.
|
||
항상 compiled destination minimum인 `PUBLISHED`와 required `SCAN_CLEAN`을 강제한다. Unpublished
|
||
exact-version read는 별도 narrow scan/maintenance port와 bean만 제공한다.
|
||
|
||
Baseline range는 하나의 contiguous `(offset, length)`다.
|
||
|
||
- offset은 0 이상;
|
||
- length는 1 이상이며 destination maximum 이하;
|
||
- object end를 넘는 range의 exact 결과를 정의한다;
|
||
- suffix/multi-range HTTP 문법은 inbound에서 canonical form으로 변환한다;
|
||
- provider range response의 실제 offset/length/content-range를 검증한다.
|
||
|
||
### 9.6 Descriptor와 receipt
|
||
|
||
`ObjectDescriptor`:
|
||
|
||
- opaque reference;
|
||
- exact version token;
|
||
- logical size;
|
||
- declared/detected media type;
|
||
- content digest algorithm/value;
|
||
- publication state;
|
||
- scan state;
|
||
- encryption profile ID;
|
||
- retention state;
|
||
- created/published timestamp;
|
||
- schema version.
|
||
|
||
`ObjectPublishReceipt`:
|
||
|
||
- operation ID;
|
||
- request fingerprint;
|
||
- opaque reference;
|
||
- exact version;
|
||
- size;
|
||
- digest;
|
||
- media type;
|
||
- terminal outcome;
|
||
- applied timestamp;
|
||
- effective capability descriptor revision.
|
||
|
||
Receipt에는 bucket, provider key, filesystem path, endpoint, raw ETag, upload ID를 넣지 않는다.
|
||
Provider-specific evidence는 private operation record에만 저장한다.
|
||
|
||
### 9.7 Capability requirement
|
||
|
||
Request는 필요한 보장을 typed enum/set으로 표현한다.
|
||
|
||
- immutable create;
|
||
- exact version read;
|
||
- conditional retirement;
|
||
- SHA-256 verification;
|
||
- scan-gated publication;
|
||
- direct upload;
|
||
- direct multipart;
|
||
- retention hold;
|
||
- server-side encryption profile;
|
||
- response-loss reconciliation.
|
||
|
||
Binding compiler는 destination의 required capability와 provider의 effective capability를
|
||
startup에서 대조한다. Runtime request가 compiled binding보다 강한 보장을 요구하면 호출 전에
|
||
`UNSUPPORTED_CAPABILITY`로 거부한다.
|
||
|
||
Runtime request의 scan, encryption, retention, checksum, publication requirement는 destination
|
||
minimum을 강화할 수만 있고 낮출 수 없다. Weaker request는 stronger destination policy로
|
||
승격하거나 ambiguous하면 fail closed한다.
|
||
|
||
## 10. Logical key, object reference, namespace
|
||
|
||
### 10.1 Internal data key
|
||
|
||
Provider key는 adapter 내부에서 생성한다. 예시 grammar:
|
||
|
||
```text
|
||
data/v1/<destination-token>/<shard>/<object-id>/<generation>
|
||
```
|
||
|
||
요구사항:
|
||
|
||
- ASCII lower-case의 제한된 alphabet;
|
||
- segment 길이와 전체 길이 제한;
|
||
- `.`/`..`, empty segment, slash alias 금지;
|
||
- percent/Unicode normalization ambiguity 금지;
|
||
- tenant/user/original filename/email 같은 PII 금지;
|
||
- object ID에서 deterministic shard 계산;
|
||
- destination별 private prefix 고정;
|
||
- generation은 immutable create마다 새 값;
|
||
- canonical encoder와 parser에 property test;
|
||
- provider별 key normalization 차이를 adapter kernel에서 제거.
|
||
|
||
### 10.2 Control namespace
|
||
|
||
```text
|
||
control/v1/operations/<destination-token>/<operation-epoch>/<shard>/<operation-id>
|
||
control/v1/references/<destination-token>/<shard>/<reference-id>
|
||
control/v1/manifests/<destination-token>/<shard>/<object-id>/<revision>
|
||
control/v1/multipart/<destination-token>/<shard>/<session-id>
|
||
control/v1/reaper-cursors/<destination-token>/<job-id>
|
||
control/v1/operation-epochs/<destination-token>/<operation-epoch>
|
||
```
|
||
|
||
Data와 control prefix는 IAM/policy와 lifecycle에서 분리한다. Runtime identity가 broad bucket
|
||
list/delete 권한을 갖지 않도록 operation별 최소 prefix 권한을 설계한다.
|
||
|
||
`destination-token`은 stable, non-secret route identity다. 동일 token의 provider/namespace를
|
||
in-place로 바꾸지 않는다. Migration은 새 token을 만들며 old token의 binding revision을
|
||
read/reconcile/retire 기간 동안 보존한다.
|
||
|
||
### 10.3 Public opaque reference
|
||
|
||
예시 외형:
|
||
|
||
```text
|
||
osr1.<route-token>.<random-object-token>.<check-digits>
|
||
```
|
||
|
||
Reference는:
|
||
|
||
- provider/bucket/key를 복호화할 수 없는 opaque value;
|
||
- 오타 탐지용 check digits;
|
||
- schema/version prefix;
|
||
- destination route token;
|
||
- 충분한 entropy;
|
||
- application DB에 저장 가능한 bounded string;
|
||
- secret이 아니지만 log에서는 hash/token 처리;
|
||
- authorization을 대체하지 않음;
|
||
- public download URL이 아님.
|
||
|
||
Reference를 받은 사용자는 application authorization을 통과해야만 stream 또는 presigned grant를
|
||
받는다.
|
||
|
||
Route registry는:
|
||
|
||
- token -> destination/binding revision history를 durable하게 보존;
|
||
- destination display-name rename과 route identity를 분리;
|
||
- live reference/operation/tombstone가 남아 있으면 token 삭제·재사용 금지;
|
||
- provider migration 시 old route를 read/reconcile/retire 상태로 유지;
|
||
- unknown/removed route를 current default provider로 보내지 않음;
|
||
- route alias/tombstone retention을 reference maximum lifetime보다 길게 유지
|
||
|
||
한다. Startup validation은 token collision/reuse와 required old binding 부재를 hard fail한다.
|
||
|
||
### 10.4 Original filename와 user metadata
|
||
|
||
Original filename은 object key로 사용하지 않는다. Business상 필요하면:
|
||
|
||
- inbound에서 control character/path separator를 제거한다;
|
||
- length를 제한한다;
|
||
- public response용 display metadata로 application DB에 저장한다;
|
||
- storage adapter control record에는 allowlisted, encoded metadata만 둔다;
|
||
- log/metric tag에 넣지 않는다.
|
||
|
||
Arbitrary `Map<String, String>` metadata는 baseline port에 없다. 필요한 metadata는 typed,
|
||
versioned field로 추가한다.
|
||
|
||
## 11. Operation identity와 request fingerprint
|
||
|
||
### 11.1 Canonical fingerprint
|
||
|
||
Fingerprint는 content bytes 자체가 아니라 immutable request intent를 canonical encode한 뒤
|
||
SHA-256으로 계산한다.
|
||
|
||
포함:
|
||
|
||
- schema version;
|
||
- destination ID;
|
||
- operation kind;
|
||
- declared media type canonical form;
|
||
- exact/maximum size expectation;
|
||
- exact R2 content identity 또는 explicit R1 compatibility marker;
|
||
- publication/scan requirement;
|
||
- encryption/retention profile;
|
||
- direct/multipart parameter;
|
||
- prior reference/version precondition.
|
||
|
||
제외:
|
||
|
||
- request ID, trace ID;
|
||
- current time;
|
||
- provider-generated key/upload ID;
|
||
- presigned URL;
|
||
- credential;
|
||
- transient retry count.
|
||
|
||
Canonical encoding은:
|
||
|
||
- field 순서 고정;
|
||
- enum canonical name 고정;
|
||
- number decimal encoding 고정;
|
||
- absence와 empty를 구분;
|
||
- Unicode normalization 정책 고정;
|
||
- schema version 포함;
|
||
- golden vector test 보유.
|
||
|
||
### 11.2 Same-operation decision
|
||
|
||
| 기존 operation | 새 요청 | 결과 |
|
||
| --- | --- | --- |
|
||
| 없음 | valid fingerprint | reserve 후 실행 |
|
||
| non-terminal, same fingerprint | retry | current state/continuation 반환 |
|
||
| terminal success, same fingerprint | retry | 저장된 receipt 반환, producer 재호출 금지 |
|
||
| terminal failure, same fingerprint | retry | 정책에 따라 same failure 또는 explicit new operation 요구 |
|
||
| 어떤 상태든 different fingerprint | retry | `OPERATION_CONFLICT` |
|
||
| unknown/newer schema | retry | fail closed, manual/upgrade reconciliation |
|
||
|
||
Operation ID uniqueness만 보고 dedupe하지 않는다. Fingerprint 비교가 필수다.
|
||
|
||
### 11.3 Content digest와 operation fingerprint 분리
|
||
|
||
두 digest를 혼동하지 않는다.
|
||
|
||
- request fingerprint: 같은 사용자 의도인지 판정;
|
||
- content digest: 업로드된 byte가 기대한 content인지 판정.
|
||
|
||
R2 expected content digest는 fingerprint에 포함하고 adapter가 streaming 중 계산한 값과 비교한다.
|
||
R1 compatibility 경로는 adapter가 discovered digest를 기록하지만, terminal retry에서 다른
|
||
payload 오용을 검출하지 못한다는 보장 한계를 descriptor/receipt에 표시한다. Direct upload는
|
||
server가 bytes를 직접 보지 않을 수 있으므로 provider checksum/head 또는 별도 verification
|
||
read가 필요하다.
|
||
|
||
### 11.4 Replay horizon과 tombstone
|
||
|
||
Destination은 다음보다 긴 `minimumOperationReplayHorizon`을 가진다.
|
||
|
||
- public API idempotency retry horizon;
|
||
- outbox/redelivery/dead-letter recovery horizon;
|
||
- maximum worker outage;
|
||
- provider indeterminate reconciliation horizon.
|
||
|
||
Operation identity는 `destination route + operation epoch + operation ID`다.
|
||
|
||
- active epoch에서는 새 operation을 받을 수 있다;
|
||
- active epoch의 per-operation tombstone은 개별 horizon 경과만으로 삭제하지 않는다;
|
||
- compaction하려면 epoch를 먼저 `SEALED`로 바꿔 신규 operation을 거부한다;
|
||
- seal 뒤 maximum replay horizon과 indeterminate/retention recovery가 모두 끝날 때까지 individual
|
||
receipt/tombstone을 유지한다;
|
||
- 그 뒤 immutable epoch rejection record를 남기고 per-operation tombstone을 compact할 수 있다;
|
||
- sealed/compacted epoch로 온 어떤 operation ID도 exact lookup 결과와 무관하게
|
||
`OPERATION_EXPIRED`를 반환한다;
|
||
- epoch token은 destination lifetime 동안 재사용하지 않는다.
|
||
|
||
따라서 tombstone 삭제 뒤 “record 없음”을 새 operation으로 오인하지 않는다. Current active
|
||
epoch의 tombstone을 지우고 같은 epoch를 계속 쓰는 구현은 금지한다. Application operation-ID
|
||
factory와 outbox는 epoch를 operation과 함께 durable하게 보존한다.
|
||
|
||
### 11.5 Bounded epoch rotation
|
||
|
||
```text
|
||
WARM -> ACTIVE -> DRAINING -> SEALED -> COMPACTED
|
||
```
|
||
|
||
Finite destination settings:
|
||
|
||
- `epochMaxAge`;
|
||
- `epochMaxOperations`;
|
||
- `epochMaxControlBytes`;
|
||
- maximum concurrently readable old epochs;
|
||
- drain/replay/compaction deadline.
|
||
|
||
Rotation:
|
||
|
||
1. 새 epoch를 `WARM`으로 만들고 codec/policy/binding 및 모든 reader가 old/new lookup을 지원하는지
|
||
qualification한다.
|
||
2. Durable current-write-epoch pointer를 CAS로 새 epoch에 전환한다.
|
||
3. Old epoch를 `DRAINING`으로 바꾸고 stale writer admission을 거부한다.
|
||
4. maximum in-flight request/outbox dispatch horizon 동안 old operation continuation/replay만
|
||
허용한다.
|
||
5. unresolved indeterminate operation이 없고 drain fence가 확인되면 `SEALED`한다.
|
||
6. replay/retention horizon 뒤 immutable rejection record를 남기고 `COMPACTED`한다.
|
||
|
||
Pointer unavailable, stale writer, seal race는 fail closed한다. Rollback은 새 epoch에 operation이
|
||
없을 때만 pointer를 되돌리거나 또 다른 epoch를 사용한다. Multi-pod cutover, delayed outbox,
|
||
partitioned writer, restore를 rolling/fault test한다.
|
||
|
||
## 12. 분리된 상태 머신
|
||
|
||
Publication operation, published reference lifecycle, retirement/purge mutation, direct grant
|
||
session, multipart session을 한 enum에 합치지 않는다. 각각 별도 record와 stable operation
|
||
identity를 가진다.
|
||
|
||
### 12.1 Publication operation
|
||
|
||
```text
|
||
RESERVED
|
||
-> DATA_UPLOAD_IN_PROGRESS
|
||
-> DATA_UPLOADED
|
||
-> INTEGRITY_VERIFIED
|
||
-> SCAN_PENDING
|
||
-> SCAN_CLEAN
|
||
-> REFERENCE_PUBLISH_IN_PROGRESS
|
||
-> PUBLISHED
|
||
```
|
||
|
||
Terminal:
|
||
|
||
```text
|
||
PUBLISHED | ABORTED | QUARANTINED | EXPIRED | FAILED | CORRUPT
|
||
```
|
||
|
||
Scan이 필요 없는 destination은 `INTEGRITY_VERIFIED -> REFERENCE_PUBLISH_IN_PROGRESS`로
|
||
전이한다. `PUBLISHED`는 이 publication operation의 immutable terminal이다. 이후 retirement가
|
||
같은 record를 변경하지 않는다.
|
||
|
||
### 12.2 Scan sub-state
|
||
|
||
```text
|
||
NOT_REQUIRED
|
||
PENDING
|
||
CLEAN
|
||
MALICIOUS
|
||
INDETERMINATE
|
||
```
|
||
|
||
- destination minimum이 scan을 요구하면 caller가 `NOT_REQUIRED`로 낮출 수 없다;
|
||
- verdict는 exact object version, scan operation, scanner policy revision, fence를 묶는다;
|
||
- `MALICIOUS`는 publication operation을 `QUARANTINED` terminal로 보낸다;
|
||
- `INDETERMINATE`는 clean이 아니며 scan retry/운영 판정 전 publish를 막는다.
|
||
|
||
### 12.3 Published reference lifecycle
|
||
|
||
```text
|
||
PUBLISHED
|
||
-> RETIREMENT_PENDING
|
||
-> RETIRED
|
||
-> PURGE_ELIGIBLE
|
||
-> PURGED
|
||
```
|
||
|
||
Reference lifecycle record는 publication receipt와 별도다.
|
||
|
||
- retirement mutation마다 새 `ObjectOperationId`와 fingerprint를 사용한다;
|
||
- physical purge도 retirement와 다른 privileged operation ID를 사용한다;
|
||
- `RETIRED`는 business visibility 제거이며 physical purge와 다르다;
|
||
- retention/legal hold는 purge operation outcome을 `HELD`로 만들며 reference record를 임의로
|
||
`PURGED`로 승격하지 않는다;
|
||
- reference가 가리키는 immutable generation은 lifecycle 전체에서 덮어쓰지 않는다.
|
||
|
||
### 12.4 Direct grant session
|
||
|
||
```text
|
||
SESSION_RESERVED
|
||
-> GRANT_PREPARED
|
||
-> GRANT_ISSUED
|
||
-> UPLOAD_VERIFICATION_IN_PROGRESS
|
||
-> DATA_UPLOADED
|
||
```
|
||
|
||
Terminal/branch:
|
||
|
||
```text
|
||
EXPIRED | ABORTED | FAILED
|
||
```
|
||
|
||
Grant generation, constraints digest, signing revision, expiry, outstanding-grant exposure는 session
|
||
record가 소유한다. Download grant도 별도 grant-operation record와 published reference revision
|
||
precondition을 가진다. Grant response uncertainty는 stable grant state와 pending-effect certainty로
|
||
표현한다. 상세 발급/reissue linearization은 §18.6을 따른다.
|
||
|
||
### 12.5 Multipart session
|
||
|
||
```text
|
||
SESSION_RESERVED
|
||
-> INITIATE_IN_PROGRESS
|
||
-> ACCEPTING_PARTS
|
||
-> COMPLETE_IN_PROGRESS
|
||
-> COMPLETED
|
||
```
|
||
|
||
Terminal/branch:
|
||
|
||
```text
|
||
ABORTED | EXPIRED | FAILED | CORRUPT
|
||
```
|
||
|
||
Session record는 provider upload ID, part ledger, grant fence를 private하게 보존한다. `COMPLETED`가
|
||
되면 publication operation의 `DATA_UPLOADED` evidence로 연결되지만 두 record를 같은 state로
|
||
간주하지 않는다.
|
||
|
||
### 12.6 Pending effect와 certainty
|
||
|
||
단일 `INDETERMINATE` state로 phase를 지우지 않는다. 모든 external mutation 직전에 control
|
||
record를 CAS하여 다음을 기록한다.
|
||
|
||
```text
|
||
stablePhase
|
||
pendingEffect {
|
||
kind
|
||
attemptId
|
||
exactTargetAndVersion
|
||
desiredRevisionOrState
|
||
precondition
|
||
requestEvidenceDigest
|
||
}
|
||
certainty = NOT_SENT | SENT | CONFIRMED | INDETERMINATE
|
||
```
|
||
|
||
`kind` 예:
|
||
|
||
- `DATA_PUT`;
|
||
- `DIRECT_GRANT_ISSUE`;
|
||
- `DIRECT_UPLOAD_VERIFY`;
|
||
- `DIRECT_DOWNLOAD_GRANT_ISSUE`;
|
||
- `MULTIPART_INITIATE`;
|
||
- `MULTIPART_PART`;
|
||
- `MULTIPART_COMPLETE`;
|
||
- `MULTIPART_ABORT`;
|
||
- `REFERENCE_CAS`;
|
||
- `REFERENCE_RETIRE`;
|
||
- `OBJECT_PURGE`;
|
||
- `CONTROL_RECORD_CAS`.
|
||
|
||
따라서 `DATA_PUT_INDETERMINATE`, `REFERENCE_CAS_INDETERMINATE`,
|
||
`MULTIPART_COMPLETE_INDETERMINATE`, `OBJECT_PURGE_INDETERMINATE`를 서로 다른 reconciler
|
||
branch로 보낸다.
|
||
|
||
### 12.7 전이 규칙
|
||
|
||
- terminal operation record는 immutable하게 보존한다;
|
||
- state version/CAS와 pending-effect precondition이 일치할 때만 전이한다;
|
||
- state마다 allowed predecessor를 고정한다;
|
||
- 같은 terminal transition retry는 stored receipt를 반환한다;
|
||
- out-of-order callback은 무시하지 말고 conflict/audit한다;
|
||
- object data 존재만으로 publication state를 승격하지 않는다;
|
||
- state와 provider evidence가 충돌하면 `CORRUPT` 또는 phase-specific indeterminate로 격리한다;
|
||
- quarantined/unpublished object는 public transfer/grant 대상이 아니다.
|
||
|
||
### 12.8 Mutation response loss
|
||
|
||
Response loss가 발생하면:
|
||
|
||
1. pending effect와 certainty를 `INDETERMINATE`로 유지한다;
|
||
2. 같은 mutation을 blind retry하지 않는다;
|
||
3. effect kind에 맞는 exact key/version/upload session/control revision을 조회한다;
|
||
4. size, digest, metadata, version, state evidence를 비교한다;
|
||
5. `APPLIED`, `NOT_APPLIED`, `PRECONDITION_FAILED`, 계속 `INDETERMINATE` 중 하나로 resolve한다;
|
||
6. resolve 결과에 따라 continuation 또는 compensation을 수행한다.
|
||
|
||
## 13. Durable control plane
|
||
|
||
### 13.1 필요성
|
||
|
||
Provider object data만으로 다음을 복구할 수 없다.
|
||
|
||
- operation ID와 request fingerprint;
|
||
- scan/publication 상태;
|
||
- provider response loss 전후 의도;
|
||
- old/new attachment reference;
|
||
- cleanup ownership/age;
|
||
- multipart part ledger;
|
||
- schema/policy/encryption revision;
|
||
- terminal receipt.
|
||
|
||
따라서 R2에는 durable control plane이 필요하다.
|
||
|
||
### 13.2 Source of truth
|
||
|
||
초기 선택은 같은 object storage의 private control namespace에 versioned canonical record를 두는
|
||
것이다.
|
||
|
||
- data plane과 failure domain이 같아 disaster recovery가 단순하다;
|
||
- conditional create/CAS를 provider capability로 검증할 수 있다;
|
||
- application DB schema와 capability internals를 분리한다.
|
||
|
||
단, control record와 data object는 여전히 단일 atomic transaction이 아니다. protocol과
|
||
reconciliation이 crash gap을 닫는다.
|
||
|
||
Control plane을 별도 database에 두는 선택도 가능하지만 이 문서의 baseline이 아니다. 바꾸려면:
|
||
|
||
- ownership과 dependency edge;
|
||
- transaction/capacity/failover;
|
||
- dual-store recovery;
|
||
- backup/restore ordering
|
||
|
||
을 별도 설계로 승인한다.
|
||
|
||
### 13.3 Record schema
|
||
|
||
Operation record v1 최소 필드:
|
||
|
||
- schema version;
|
||
- operation ID;
|
||
- canonical request fingerprint;
|
||
- operation kind;
|
||
- destination route token;
|
||
- provider/binding/policy revision;
|
||
- canonical non-secret effective policy snapshot와 digest;
|
||
- key grammar/codec/checksum/encryption/retention plan revision;
|
||
- logical credential/key reference revision;
|
||
- internal object ID/key hash;
|
||
- opaque reference;
|
||
- operation family, stable phase, family-specific state/state version;
|
||
- pending effect, attempt ID, precondition, request evidence digest, certainty;
|
||
- expected/observed size;
|
||
- expected/observed content digest;
|
||
- provider checksum algorithm/value;
|
||
- provider version/ETag private evidence;
|
||
- multipart opaque session and completed-part ledger;
|
||
- encryption/retention/scan profile revision;
|
||
- timestamps and bounded lease/fence;
|
||
- last normalized outcome/error;
|
||
- terminal public receipt;
|
||
- minimum replay-until/terminal tombstone epoch;
|
||
- cleanup ownership/eligibility;
|
||
- application handoff/cleanup authorization fence;
|
||
- audit correlation token.
|
||
|
||
Record는 canonical format으로 encode하고:
|
||
|
||
- checksum/MAC 또는 authenticated encryption 정책;
|
||
- maximum record size;
|
||
- unknown field policy;
|
||
- schema upgrade/downgrade behavior;
|
||
- corruption handling;
|
||
- golden fixtures
|
||
|
||
를 테스트한다. Secret, presigned URL, raw credential은 기록하지 않는다.
|
||
|
||
Reserve 시 effective execution policy를 freeze한다. Same operation retry/reconcile은:
|
||
|
||
- 저장된 policy snapshot과 digest;
|
||
- 저장된 provider/binding revision;
|
||
- 저장된 key/codec/checksum/encryption/retention plan;
|
||
- 당시의 logical credential/key revision
|
||
|
||
만 사용한다. Current configuration으로 다시 resolve하거나 silent fallback하지 않는다. 필요한 old
|
||
revision을 복원할 수 없으면 `POLICY_REVISION_UNAVAILABLE`로 fail closed하고 manual
|
||
reconciliation 대상으로 보낸다.
|
||
|
||
Old revision은 maximum operation/replay/retention horizon보다 길게 보존한다. 새 policy는 새
|
||
operation에만 적용한다. Rolling deployment test는 old revision read/continue와 new revision
|
||
write를 함께 검증한다.
|
||
|
||
### 13.4 Conditional update
|
||
|
||
R2 provider는 control record에 다음 중 하나를 증명해야 한다.
|
||
|
||
- create-if-absent와 exact version compare-and-swap;
|
||
- immutable revision append + conditional current pointer;
|
||
- 동등한 linearizable primitive.
|
||
|
||
지원하지 않는 provider는 multi-node deterministic recovery를 claim하지 않는다.
|
||
|
||
LIST는 reaper candidate discovery에만 사용한다. Operation/reference direct lookup의 source of
|
||
truth는 deterministic exact key GET/HEAD다.
|
||
|
||
### 13.5 Schema evolution
|
||
|
||
- reader는 자신보다 낮은 supported version을 migrate in memory할 수 있다;
|
||
- writer는 deployment의 selected write version만 쓴다;
|
||
- newer version은 삭제/overwrite하지 않고 `UNSUPPORTED_CONTROL_SCHEMA`로 격리한다;
|
||
- rolling deployment 동안 old/new reader compatibility matrix를 CI에서 검증한다;
|
||
- downgrade 전에 write-version gate를 낮추는 별도 단계가 필요하다;
|
||
- cleanup job은 unknown schema를 보고만 하고 삭제하지 않는다.
|
||
|
||
## 14. Managed upload protocol
|
||
|
||
### 14.1 Plan
|
||
|
||
Adapter는 content producer를 호출하기 전에:
|
||
|
||
1. operation key의 destination route token, epoch, operation ID로 existing record 또는 sealed
|
||
epoch rejection record를 exact lookup한다;
|
||
2. existing record가 있으면 frozen binding/policy revision을 복원하고 fingerprint를 비교한다;
|
||
3. record가 없을 때만 current destination binding과 capability requirement를 resolve한다;
|
||
4. operation ID/fingerprint와 R2 content identity를 검증한다;
|
||
5. immutable object ID/key와 opaque reference를 생성한다;
|
||
6. size/digest/encryption/retention limit와 effective policy snapshot/digest를 freeze한다;
|
||
7. `RESERVED` control record를 create-if-absent한다;
|
||
8. winning record를 다시 읽어 same-operation decision을 수행한다;
|
||
9. operation-scoped resource budget을 예약한다.
|
||
|
||
이 단계에는 data write가 없다.
|
||
|
||
### 14.2 Upload
|
||
|
||
1. control state를 CAS로 `DATA_UPLOAD_IN_PROGRESS`로 전이하고 `DATA_PUT` pending effect를
|
||
`NOT_SENT`로 기록한다.
|
||
2. provider immutable-create request를 열기 직전에 pending effect certainty를 `SENT`로
|
||
전이한다.
|
||
3. producer callback의 chunk를 bounded buffer로 전달한다.
|
||
4. 동시에 logical byte count와 SHA-256을 계산한다.
|
||
5. provider transport checksum을 지원하면 별도로 계산/전송한다.
|
||
6. maximum bytes를 넘기기 전에 sink를 중단한다.
|
||
7. producer, adapter, SDK 오류를 서로 다른 normalized cause로 기록한다.
|
||
8. successful provider response에서 exact version/checksum/encryption evidence를 보존한다.
|
||
9. response가 확정되면 pending effect를 confirm/clear하고 `DATA_UPLOADED`로 전이한다.
|
||
불확정이면 stable phase는 `DATA_UPLOAD_IN_PROGRESS`, pending effect kind는 `DATA_PUT`,
|
||
certainty는 `INDETERMINATE`로 유지한다.
|
||
|
||
Producer exception 뒤에는 partial provider upload를 abort/cleanup한다. Cleanup 실패가 원래
|
||
producer 오류를 덮지 않으며 operation record에 별도 evidence로 남는다.
|
||
|
||
### 14.3 Integrity verification
|
||
|
||
검증 우선순위:
|
||
|
||
1. caller expected SHA-256과 adapter-calculated SHA-256 비교;
|
||
2. sent provider checksum과 provider response/head checksum 비교;
|
||
3. exact object size 비교;
|
||
4. exact immutable version 확인;
|
||
5. encryption/retention response attestation 확인;
|
||
6. provider 특성상 response evidence가 불충분하면 bounded verification read.
|
||
|
||
불일치 시 object를 publish하지 않고 `QUARANTINED` 또는 `CORRUPT`로 보낸다. 단순 retry로
|
||
정상화하지 않는다.
|
||
|
||
### 14.4 Scan
|
||
|
||
Scan이 필요한 destination:
|
||
|
||
```text
|
||
DATA_UPLOADED
|
||
-> INTEGRITY_VERIFIED
|
||
-> scanState=PENDING
|
||
-> scanner application workflow
|
||
-> scanState=CLEAN | MALICIOUS | INDETERMINATE
|
||
```
|
||
|
||
`scanState=CLEAN`만 publication state를 `SCAN_CLEAN`으로 진행시킨다. `MALICIOUS`는
|
||
`QUARANTINED` terminal, `INDETERMINATE`는 `SCAN_PENDING` publication state에 머문다.
|
||
|
||
Object Storage adapter는 scanner SDK나 business verdict를 소유하지 않는다. 다음 seam만 제공한다.
|
||
|
||
- unpublished exact version에 대한 authorized bounded read;
|
||
- scan operation correlation;
|
||
- clean/malicious/indeterminate verdict를 conditional state transition으로 기록;
|
||
- stale verdict가 새 version에 적용되지 않도록 version precondition;
|
||
- scanner unavailable 시 fail-closed publication.
|
||
|
||
Scanner capability는 별도 outbound adapter가 application port 뒤에서 구현한다. Objectstorage
|
||
leaf가 sibling adapter를 직접 의존하지 않는다.
|
||
|
||
### 14.5 Publish
|
||
|
||
기본 publication:
|
||
|
||
1. required integrity/scan state를 재검증한다;
|
||
2. immutable manifest revision을 기록한다;
|
||
3. reference current pointer를 conditional create/CAS한다;
|
||
4. `PUBLISHED` terminal receipt를 operation record에 보존한다;
|
||
5. caller에게 opaque receipt를 반환한다.
|
||
|
||
Data object key는 이동/복사/ACL 변경하지 않는다. Reference lookup이 private immutable object의
|
||
exact version을 가리킨다.
|
||
|
||
Manifest/pointer write response가 유실되면 reference exact GET과 revision/digest 비교로
|
||
reconcile한다.
|
||
|
||
### 14.6 Abort
|
||
|
||
Abort는:
|
||
|
||
- terminal published object를 지우지 않는다;
|
||
- non-terminal object와 multipart session만 대상으로 한다;
|
||
- operation ID/fingerprint를 확인한다;
|
||
- active publication handoff claim이 있으면 destructive abort를 거부한다;
|
||
- business-owned verified stage는 DB upload-intent의 terminal abort fence에 묶인
|
||
`ObjectAbortAuthorization`을 검증한다;
|
||
- conditional state transition으로 single owner를 확보한다;
|
||
- provider delete/abort 결과를 reconcile한다;
|
||
- retention/hold가 있으면 `HELD`를 반환한다;
|
||
- cleanup failure를 숨기지 않는다.
|
||
|
||
### 14.7 Empty object
|
||
|
||
Empty object 지원 여부를 destination별로 명시한다.
|
||
|
||
- generic binary destination은 size 0을 허용할 수 있다;
|
||
- poster image 같은 business destination은 application policy로 거부한다;
|
||
- checksum은 empty SHA-256의 정상 값으로 계산한다;
|
||
- multipart는 empty object에 사용하지 않는다.
|
||
|
||
## 15. Database attachment workflow
|
||
|
||
### 15.1 금지 shape
|
||
|
||
```text
|
||
DB transaction {
|
||
remote upload
|
||
aggregate save
|
||
}
|
||
```
|
||
|
||
이 shape는 사용하지 않는다.
|
||
|
||
### 15.2 권장 workflow
|
||
|
||
Poster image 교체 예시:
|
||
|
||
```text
|
||
1. authorize + validate request
|
||
2. stable attachment operation 생성
|
||
3. short DB transaction:
|
||
Poster exists/version 확인
|
||
versioned HMAC key epoch을 lock하고 raw tenant/principal/Idempotency-Key를
|
||
domain-separated alias/digest로 변환
|
||
tx.inWrite(() -> IdempotencyExecutor.execute(
|
||
sanitizedContext,
|
||
create-or-read UploadIntent(operation, expected poster version, content identity, RESERVED),
|
||
bounded reservation codec))
|
||
generic idempotency COMPLETED reservation과 UploadIntent를 함께 commit
|
||
4. object stage/upload outside DB transaction
|
||
5. integrity verify + required scan
|
||
6. object publication handoff claim을 획득
|
||
7. short DB transaction:
|
||
UploadIntent가 RESERVED이고 operation/fence가 같은지 CAS
|
||
pending attachment(stage handle, operation, expected object version, handoff fence) 저장
|
||
UploadIntent를 PENDING으로 전이
|
||
outbox AttachmentPrepared(handoff fence) 기록
|
||
8. committed UploadIntent worker가 PENDING row를 claim하고 handoff claim을 갱신한 뒤
|
||
object publication finalize
|
||
9. short DB transaction:
|
||
same pending stage handle인지 CAS 확인
|
||
finalize receipt의 published reference로 교체해 READY 승격
|
||
UploadIntent를 READY로 전이
|
||
old reference를 retirement queue/outbox에 추가
|
||
outbox AttachmentReady 기록
|
||
10. handoff claim release
|
||
11. async old-reference retirement
|
||
```
|
||
|
||
UploadIntent는 remote transfer 전에 commit되므로 “DB row가 아직 commit될 수 있는 중인데 absence를
|
||
읽고 abort”하는 race를 제거한다. Handoff fence는 secret authorization이 아니라
|
||
operation/version/claim generation에 묶인 opaque value다. DB commit 여부를 objectstorage
|
||
adapter가 추측하지 않는다.
|
||
|
||
TX1은 generic idempotency claim, UploadIntent create/read, bounded reservation COMPLETE가 같은
|
||
PostgreSQL transaction에 참여한다. Commit 전 crash는 둘 다 남기지 않고, commit 후에는
|
||
COMPLETED generic row와 matching intent가 함께 남는다. Scope claim은 transaction을
|
||
unique-violation으로 poison하지 않는 PostgreSQL `ON CONFLICT` claim primitive를 사용한다.
|
||
Versioned key epoch과 retained HMAC key lookup은 rolling rotation 중 old/new digest가 서로 다른
|
||
intent를 만들지 못하게 하며, generic row와 intent에는 raw header/principal/tenant를 저장하지
|
||
않는다.
|
||
|
||
Multipart HTTP 요청의 `RequestFingerprint`는 raw body나 multipart boundary의 hash가 아니다.
|
||
`poster-image-publication-fingerprint-v1` canonical codec이 schema/domain separator, Poster ID, expected
|
||
aggregate version, destination/profile, normalized media type, declared content length, 그리고
|
||
요청 전에 제출된 full-file SHA-256을 length-prefixed bytes로 직렬화한 semantic digest다.
|
||
Filename, multipart boundary, part/header 순서와 transport-only header는 제외한다. 같은 의미의
|
||
재시도는 같은 fingerprint가 되고, content identity나 business precondition이 바뀌면 반드시
|
||
달라지는 golden/property test를 둔다.
|
||
|
||
HTTP multipart producer는 request lifetime에 묶이므로 4–7은 successful `202` 반환 전에 같은
|
||
request invocation에서 끝난다. Worker는 request body가 없는 `RESERVED` intent를 임의로 stage하지
|
||
않는다. TX1 뒤 stage 전 crash는 same-idempotency-key/same-fingerprint retry가 같은 operation에 새
|
||
producer를 공급하고, retry가 없으면 bounded expiry/abort/report 대상으로 남긴다. Worker가
|
||
비동기로 소유하는 구간은 durable `PENDING` 이후 finalize/READY다.
|
||
|
||
`AttachmentPrepared`/`AttachmentReady` outbox row는 versioned integration notification/audit다.
|
||
Object publication의 canonical work queue와 recovery source of truth는
|
||
`poster_image_upload_intent`이며, outbox/broker delivery만을 유일한 wake-up 또는 object operation
|
||
journal로 사용하지 않는다.
|
||
|
||
### 15.3 Crash gap
|
||
|
||
| Crash 위치 | 남는 상태 | 복구 |
|
||
| --- | --- | --- |
|
||
| TX1 commit 후 stage 전 | COMPLETED reservation + DB RESERVED intent, no object | same-key/same-fingerprint HTTP retry가 새 request producer로 same operation stage; retry가 없으면 expiry/abort report |
|
||
| upload 중 | DB RESERVED intent + pending/partial provider effect | exact provider evidence가 있으면 resolver가 reconcile; bytes 재현이 필요하면 HTTP retry producer만 continuation; worker가 request bytes를 invent/replay하지 않음 |
|
||
| verified 후 handoff 전 | DB RESERVED intent + exact verified stage evidence | worker가 exact stage/operation claim; staged evidence가 없으면 worker가 stage하지 않음 |
|
||
| handoff 후 pending DB commit 전 | active claim + DB RESERVED intent | worker가 pending CAS; claim expiry 후에만 abort CAS |
|
||
| pending DB commit 후 finalize 전 | DB PENDING + active/frozen claim | UploadIntent worker가 same operation finalize/claim renew |
|
||
| finalize 후 ready DB commit 전 | published reference + pending DB | worker가 reference inspect 후 ready CAS |
|
||
| ready DB 후 old retire 전 | new ready + old published | retirement outbox 재처리 |
|
||
| retire response loss | old state indeterminate | exact reference/version reconcile |
|
||
|
||
Verified/staged object는 age만으로 reaper가 삭제하지 않는다. Application maintenance가
|
||
operation ID로 business DB/UploadIntent와 관련 outbox evidence를 조회한 뒤:
|
||
|
||
- pending/ready면 claim을 획득·갱신하거나 finalize를 재개;
|
||
- `UploadIntent=RESERVED`이고 active claim이 없으면 DB CAS로 `ABORT_AUTHORIZED` terminal과
|
||
fence를 먼저 기록한 뒤 exact object/version `ObjectAbortAuthorization`을 발급;
|
||
- worker의 late `RESERVED -> PENDING` CAS는 `ABORT_AUTHORIZED` 뒤 실패하며 새 operation으로
|
||
restage/manual recovery;
|
||
- object는 있는데 matching UploadIntent가 없으면 auto-delete하지 않고 corruption quarantine;
|
||
- DB/outbox 확인이 불가능하면 keep/quarantine하고 alert
|
||
|
||
한다. DB absence read만으로 abort authorization을 만들지 않는다. 이 protocol은 orphan leak을
|
||
data loss보다 우선하며 cleanup-vs-late-commit/claim-expiry race를 fault test한다.
|
||
|
||
### 15.4 Truth priority
|
||
|
||
- business visibility의 source of truth는 DB attachment `READY` 상태다;
|
||
- object availability의 source of truth는 published reference manifest다;
|
||
- 둘 중 하나만 ready이면 workflow가 non-terminal이다;
|
||
- HTTP download는 DB authorization/READY 확인 뒤 object reference를 resolve한다;
|
||
- DB row 삭제만으로 object가 물리 삭제되었다고 간주하지 않는다.
|
||
|
||
### 15.5 Concurrency
|
||
|
||
동일 Poster에 concurrent image upload가 오면:
|
||
|
||
- 각 upload는 새 immutable reference를 만든다;
|
||
- DB aggregate version/pending attachment CAS로 winner를 결정한다;
|
||
- loser object는 publish하지 않거나, 이미 publish되었으면 TX3에서 intent를
|
||
`SUPERSEDED` terminal로 전이하면서 exact losing reference/version retirement row를 같은
|
||
transaction에 기록한다. CAS 예외로 TX 전체를 rollback해 cleanup evidence를 잃지 않는다;
|
||
- deterministic final key overwrite를 하지 않는다;
|
||
- retry는 same operation ID를 유지한다;
|
||
- user가 새 image를 선택한 새 의도에는 새 operation ID를 사용한다.
|
||
|
||
### 15.6 Delete
|
||
|
||
Poster 삭제:
|
||
|
||
1. short DB transaction에서 business 삭제와 reference retirement intent/outbox를 기록한다;
|
||
2. object retirement worker가 visibility를 제거한다;
|
||
3. retention 정책 뒤 physical purge eligibility를 계산한다;
|
||
4. exact version conditional purge를 시도한다;
|
||
5. hold/retention은 정상 `HELD` 결과로 보존한다.
|
||
|
||
Object delete 실패 때문에 이미 승인된 business delete transaction을 장시간 붙잡지 않는다.
|
||
|
||
### 15.7 Legacy object adoption
|
||
|
||
기존 raw locator를 opaque reference로 채택하는 기능은 일반 upload/publish port에 섞지 않는다.
|
||
Deprecated administrative migration seam과 명시적인 maintenance profile에서만
|
||
`REPORT_ONLY` 또는 `APPLY`로 실행한다. `REPORT_ONLY`가 먼저 exact legacy namespace inventory와
|
||
HEAD/read SHA-256 evidence를 bounded manifest로 만들며 mutation은 하지 않는다.
|
||
|
||
`APPLY`는 그 immutable manifest와 별도의 canonical Ed25519 이중 승인 문서가 정확히 일치해야
|
||
한다. 승인 문서는 schema version, adoption operation ID, manifest SHA-256, legacy namespace
|
||
digest, target destination/namespace, literal mode `APPLY`, 유효 시간, nonce, 서로 다른 trusted
|
||
approver 두 명을 묶는다. Permission-checked configured key file 외의 key는 신뢰하지 않는다.
|
||
Verifier는 두 서명과 모든 binding을 확인하고 nonce replay record를 exact operation/digest에
|
||
CAS한 뒤, manifest bytes를 다시 hash한 다음에만 per-row inspect/digest/adoption CAS를 허용한다.
|
||
동일 terminal operation replay만 idempotent하며 다른 binding의 nonce 재사용은 거부한다.
|
||
|
||
Scheduled maintenance runner는 web `SecurityContext`에 의존하지 않고 격리된 execution
|
||
identity와 administrative capability를 명시적으로 전달한다. Normal context에는 migration
|
||
port, verifier, replay store, runner bean이 하나도 없다. 적용 중 DB CAS loser나
|
||
missing/corrupt/unknown-version object는 durable evidence와 reconciliation 대상으로 남기며,
|
||
자동 overwrite/copy/delete를 하지 않는다. Legacy object retirement는 별도 exact-reference
|
||
retirement authorization 뒤에 수행한다.
|
||
|
||
## 16. Read, HEAD, range
|
||
|
||
### 16.1 Inspect
|
||
|
||
`inspect(reference)`는:
|
||
|
||
1. reference syntax/check digits를 검증한다;
|
||
2. deterministic reference control key를 exact GET한다;
|
||
3. supported schema와 `PUBLISHED` 상태를 확인한다;
|
||
4. exact immutable data key/version을 얻는다;
|
||
5. 필요 시 provider HEAD로 size/checksum/version/encryption을 확인한다;
|
||
6. normalized descriptor만 반환한다.
|
||
|
||
`Optional.empty`는 reference가 존재하지 않을 때만 사용한다. 다음은 typed error다.
|
||
|
||
- malformed reference;
|
||
- forbidden destination;
|
||
- quarantined/retired;
|
||
- provider unavailable;
|
||
- corrupt manifest;
|
||
- unsupported schema.
|
||
|
||
### 16.2 Server-mediated download
|
||
|
||
- application authorization이 먼저다;
|
||
- adapter가 exact version/range precondition으로 provider read를 연다;
|
||
- configured buffer와 delivered-byte limit를 적용한다;
|
||
- consumer callback이 반환/실패하면 provider response body를 확실히 close/cancel한다;
|
||
- full read digest verification은 전체 object를 모두 읽었을 때만 success다;
|
||
- range read는 whole-object digest를 “검증 완료”로 표시하지 않는다;
|
||
- optional chunk/range digest가 manifest에 있을 때만 range integrity를 별도 증명한다;
|
||
- short read, excess read, wrong content-range는 provider protocol failure다;
|
||
- client disconnect는 partial delivery outcome이며 storage success와 구분한다.
|
||
|
||
### 16.3 Direct download
|
||
|
||
Presigned GET은:
|
||
|
||
- DB authorization과 published/scan-clean 확인 후 발급한다;
|
||
- exact immutable key/version과 response header profile에 묶는다;
|
||
- short TTL과 maximum download size policy를 적용한다;
|
||
- URL을 log/audit payload에 저장하지 않는다;
|
||
- revocation이 필요한 resource에는 사용하지 않거나 매우 짧은 TTL을 사용한다;
|
||
- reference retire 후에도 이미 발급한 URL이 만료 전 유효할 수 있음을 계약에 명시한다;
|
||
- AWS는 request 시작 시 expiry를 평가하므로 expiry 직전 시작한 transfer가 이후 계속될 수 있고
|
||
connection이 끊겨 재시도하면 실패할 수 있음을 client contract에 명시한다;
|
||
- AWS card는 bucket policy의 bounded `s3:signatureAge` upper bound를 qualification 후보로 둔다.
|
||
|
||
One-time URL이라고 부르지 않는다.
|
||
|
||
### 16.4 HTTP Range mapping
|
||
|
||
Inbound HTTP adapter가 `Range`를 지원할 때:
|
||
|
||
- single range만 baseline으로 허용한다;
|
||
- unsatisfiable range는 application typed outcome을 HTTP 416으로 매핑한다;
|
||
- `If-Range`, ETag 같은 HTTP transport semantics를 application port의 raw header로 넘기지 않는다;
|
||
- public ETag을 provider ETag 그대로 쓰지 않고 application version token으로 생성한다;
|
||
- exact `Content-Length`와 `Content-Range`를 descriptor/receipt로 검증한다.
|
||
|
||
## 17. Checksum, media type, metadata
|
||
|
||
### 17.1 Digest model
|
||
|
||
```text
|
||
ObjectDigest {
|
||
algorithm: SHA_256
|
||
encoding: LOWERCASE_HEX
|
||
value: exactly 64 lowercase hexadecimal characters
|
||
scope: FULL_CONTENT
|
||
}
|
||
```
|
||
|
||
R2 baseline business digest는 SHA-256이다. Provider transport checksum은 별도 value object다.
|
||
|
||
```text
|
||
ProviderChecksum {
|
||
algorithm: CRC32 | CRC32C | CRC64NVME | SHA1 | SHA256 | qualified extension
|
||
checksumType: FULL_OBJECT | COMPOSITE
|
||
scope: OBJECT | PART(partNumber)
|
||
encoding: BASE64
|
||
value
|
||
}
|
||
```
|
||
|
||
Algorithm만 같은 composite multipart checksum을 full-content SHA-256과 비교하지 않는다.
|
||
|
||
- content digest: application-level immutable content identity/integrity;
|
||
- transport checksum: provider request/response corruption detection;
|
||
- ETag: provider-specific entity/version evidence;
|
||
- operation fingerprint: user intent identity.
|
||
|
||
서로 대체하지 않는다.
|
||
|
||
### 17.2 ETag 규칙
|
||
|
||
- single-part/plain object에서도 ETag을 portable MD5 계약으로 노출하지 않는다;
|
||
- multipart ETag은 whole-object MD5가 아니다;
|
||
- encryption/provider 구현에 따라 의미가 달라질 수 있다;
|
||
- ETag은 private conditional evidence로 보존할 수 있다;
|
||
- public API version은 opaque `ObjectVersionToken`이다.
|
||
|
||
### 17.3 Direct upload checksum
|
||
|
||
Grant에는:
|
||
|
||
- required checksum algorithm;
|
||
- checksum header/field;
|
||
- declared exact/maximum size;
|
||
- content type;
|
||
- key/version precondition
|
||
|
||
을 묶는다. Upload 뒤에는:
|
||
|
||
1. client completion claim을 신뢰하지 않는다;
|
||
2. exact HEAD로 size/checksum/version을 확인한다;
|
||
3. provider가 full SHA-256을 증명하지 못하면 bounded verification read 또는 scan pipeline을
|
||
수행한다;
|
||
4. 검증 전에는 publish하지 않는다.
|
||
|
||
### 17.4 Media type
|
||
|
||
- inbound `Content-Type`은 declared value일 뿐이다;
|
||
- application이 destination별 allowlist를 적용한다;
|
||
- scan/content-sniff 단계에서 detected media type을 별도로 기록할 수 있다;
|
||
- declared/detected mismatch 정책은 business/application 소유다;
|
||
- browser-executable type은 download response의 disposition/CSP/nosniff 정책과 함께 다룬다;
|
||
- metadata value에 CR/LF/control character를 허용하지 않는다.
|
||
|
||
### 17.5 Metadata budget
|
||
|
||
Typed metadata마다:
|
||
|
||
- field count;
|
||
- key/value byte length;
|
||
- character set;
|
||
- canonical encoding;
|
||
- redaction;
|
||
- persistence location;
|
||
- public exposure 여부
|
||
|
||
를 고정한다. Provider user-metadata 최대치에 기대어 application input을 무제한 허용하지 않는다.
|
||
|
||
## 18. Direct upload grant
|
||
|
||
### 18.1 Threat model
|
||
|
||
Presigned request는 URL을 가진 주체가 제한된 provider operation을 실행할 수 있는 bearer
|
||
credential이다.
|
||
|
||
따라서:
|
||
|
||
- authenticated/authorized business intent 뒤에만 생성한다;
|
||
- TLS 외 endpoint를 production에서 허용하지 않는다;
|
||
- query string을 access log/APM/error message에서 redact한다;
|
||
- browser history/referrer 노출을 줄이는 client contract를 제공한다;
|
||
- TTL은 destination마다 짧은 upper bound를 둔다;
|
||
- underlying temporary credential 만료보다 길게 발급하지 않는다;
|
||
- provider/client clock skew budget을 빼고 grant expiry가 session expiry보다 먼저 오도록 compile한다;
|
||
- production signer/provider의 NTP/clock health가 허용 skew를 넘으면 신규 grant admission을
|
||
fail closed한다;
|
||
- signing identity 권한보다 강한 grant를 만들 수 없음을 qualification한다;
|
||
- CORS는 exact origin/method/header allowlist로 pre-provision한다.
|
||
|
||
### 18.2 Presigned PUT
|
||
|
||
Grant에 고정할 항목:
|
||
|
||
- method;
|
||
- exact immutable internal key;
|
||
- expiry;
|
||
- content type;
|
||
- checksum header;
|
||
- encryption headers;
|
||
- expected owner/provider profile;
|
||
- atomic create-only precondition;
|
||
- exact provider-enforced content length 또는 더 엄격한 hard byte ceiling;
|
||
- named profile이 요구하는 Object Lock mode/retain-until/legal-hold header;
|
||
- allowed signed headers.
|
||
|
||
R2 `direct-put` card에서 atomic create-only는 필수다. 같은 URL이 expiry 전 재사용되거나 concurrent
|
||
사용되어도 immutable key를 덮어쓸 수 없어야 한다. AWS profile은 signed
|
||
`If-None-Match: *`와 bucket-policy enforcement를 qualification한다. Provider가 이를 지원하지
|
||
않으면 `direct-put=UNSUPPORTED`이며 trusted ingress 또는 server-mediated upload로 보낸다.
|
||
|
||
Presigned PUT만으로 exact body size를 모든 client/provider 조합에서 강제했다고 주장하지 않는다.
|
||
R2 direct admission은 다음 중 하나가 실제 fault/security test로 hard ceiling을 증명해야 한다.
|
||
|
||
- signed exact `Content-Length`가 provider/client/proxy 조합에서 강제됨;
|
||
- browser POST policy의 `content-length-range`;
|
||
- controlled ingress proxy가 body를 provider write 전에 제한함.
|
||
|
||
그 증거가 없으면 server-mediated upload를 사용한다. Upload 후 HEAD/quarantine/delete는
|
||
publication integrity와 cleanup 수단일 뿐 storage/transfer cost DoS에 대한 admission bound가
|
||
아니다. 사후 검증만 가능한 profile은 별도 R1 soft-limit profile로 낮추고 maximum provider
|
||
exposure를 명시한다.
|
||
|
||
AWS direct upload와 per-object Object Lock을 조합하는 profile은:
|
||
|
||
- retention/legal-hold header를 exact signed condition으로 묶고;
|
||
- Object Lock upload에 요구되는 `Content-MD5` 또는 qualified SDK checksum algorithm을 강제하고;
|
||
- exact version의 retention/legal-hold를 scoped `GetObjectRetention`/`GetObjectLegalHold` evidence로
|
||
검증한다.
|
||
|
||
이 조합을 구현하지 않으면 bucket-default retention만 사용하거나 named direct+retention profile을
|
||
`UNSUPPORTED`로 둔다. Generic 403을 `HELD`로 매핑하지 않는다.
|
||
|
||
### 18.3 POST policy
|
||
|
||
Browser POST는 optional capability다.
|
||
|
||
Pinned AWS SDK 2.30.0 `S3Presigner`는 이 문서가 요구하는 browser POST-policy signer를 제공한다고
|
||
가정하지 않는다. 별도 audited SigV4 POST policy signer와 golden/security test가 없으면 해당
|
||
provider profile은 `directPost=UNSUPPORTED`다.
|
||
|
||
- exact bucket/key;
|
||
- content-length-range;
|
||
- content type prefix가 아닌 exact/좁은 allowlist;
|
||
- checksum;
|
||
- encryption;
|
||
- success status;
|
||
- expiration
|
||
|
||
을 policy condition으로 고정한다. Policy/fields도 bearer secret으로 redact한다.
|
||
|
||
### 18.4 Completion
|
||
|
||
Client가 direct upload 완료 API를 호출하면 application은:
|
||
|
||
1. session ID와 operation ID/fingerprint를 확인한다;
|
||
2. expected object exact HEAD를 수행한다;
|
||
3. size/checksum/encryption/version을 검증한다;
|
||
4. required scan을 수행한다;
|
||
5. 그 뒤에만 publish 또는 DB pending attachment로 진행한다.
|
||
|
||
Completion endpoint를 여러 번 호출해도 same terminal receipt를 반환한다.
|
||
|
||
### 18.5 Public signing endpoint
|
||
|
||
S3-compatible deployment는 application이 접근하는 internal endpoint와 browser가 접근할 public
|
||
signing endpoint가 다를 수 있다.
|
||
|
||
Binding은 둘을 분리한다.
|
||
|
||
- control endpoint: adapter SDK network target;
|
||
- presign endpoint: client가 실제 도달할 authority/scheme;
|
||
- approved host/scheme allowlist;
|
||
- path-style/virtual-host signing mode;
|
||
- proxy forwarded-host를 무조건 신뢰하지 않음.
|
||
|
||
Request header나 arbitrary URL로 presign authority를 선택하지 않는다.
|
||
|
||
### 18.6 Grant issue, response loss, reissue
|
||
|
||
Grant response도 bearer credential delivery이므로 stable session/request와 durable generation을
|
||
가진다.
|
||
|
||
```text
|
||
SESSION_RESERVED
|
||
-> GRANT_PREPARED(generation, constraintsDigest, signingTime, expiry, credentialRevision)
|
||
-> GRANT_ISSUED
|
||
-> UPLOAD_VERIFICATION_IN_PROGRESS
|
||
-> DATA_UPLOADED | EXPIRED | ABORTED
|
||
```
|
||
|
||
Rules:
|
||
|
||
- grant 발급 자체가 stable grant-operation ID와 fingerprint를 가진다;
|
||
- URL을 만들기 전에 constraints와 generation을 CAS로 `GRANT_PREPARED`한다;
|
||
- signing 뒤 반환 전에 `GRANT_ISSUED` evidence를 기록하되 URL 자체는 저장/log하지 않는다;
|
||
- response loss retry는 same session/fingerprint/generation을 lookup한다;
|
||
- frozen signing input과 credential revision으로 byte-identical grant를 안전하게 재생성할 수 있을
|
||
때만 같은 grant를 반환한다;
|
||
- 재생성이 불가능하면 CAS로 새 generation을 발급하며 old URL이 revoke됐다고 가정하지 않는다;
|
||
- frozen revision이 없거나 active-generation/exposure limit 때문에 안전한 replay/reissue가
|
||
불가능하면 `GRANT_REPLAY_UNAVAILABLE`로 fail closed한다;
|
||
- old/new generation은 같은 immutable key, exact content identity, hard size, create-only
|
||
constraint만 가진다;
|
||
- active generation 수와 worst-case expiry/in-flight exposure를 제한한다;
|
||
- grant response의 operation ID/fingerprint가 다르면 conflict;
|
||
- completion은 어느 generation을 사용했는지 client claim만 믿지 않고 exact object를 검증한다.
|
||
|
||
Single direct upload abort/reaper는 모든 issued generation의 expiry + clock skew + qualified
|
||
provider in-flight horizon 전에는 terminal delete/absence를 주장하지 않는다. Finite horizon이
|
||
없으면 controlled ingress를 사용하거나 indeterminate로 유지한다.
|
||
|
||
Direct download grant는 expected reference lifecycle revision에 대해 `GRANT_ISSUED` record를
|
||
conditional CAS한 시점을 linearization point로 삼는다.
|
||
|
||
- retirement가 먼저 linearize되면 CAS가 실패하고 생성한 URL은 폐기하며 응답하지 않는다;
|
||
- grant issue가 먼저 linearize되면 이후 retirement가 일어나도 이미 issued URL의 잔여 유효성을
|
||
인정한다;
|
||
- URL response가 network에서 늦게 도착해도 ordering은 control CAS로 판정한다.
|
||
|
||
Direct download reissue도 old URL의 expiry 전 유효성을 인정하고 active grant/audit count에
|
||
포함한다. Secret delivery ACK loss를 “발급되지 않음”으로 취급하지 않는다.
|
||
|
||
## 19. Multipart protocol
|
||
|
||
### 19.1 사용 기준
|
||
|
||
Multipart는 다음 조건에서만 사용한다.
|
||
|
||
- object size/profile이 configured threshold 이상;
|
||
- provider card가 exact capability를 지원;
|
||
- part count/size/concurrency budget이 계산 가능;
|
||
- abort/reconciliation/lifecycle backstop이 준비됨.
|
||
|
||
Small object를 무조건 multipart로 보내지 않는다.
|
||
|
||
### 19.2 Session
|
||
|
||
`startMultipart`:
|
||
|
||
1. operation/fingerprint reserve;
|
||
2. immutable internal key 생성;
|
||
3. provider multipart create;
|
||
4. provider upload ID를 private encrypted/control record에 저장;
|
||
5. opaque session ID와 bounded parameters 반환.
|
||
|
||
반환 필드:
|
||
|
||
- opaque session ID;
|
||
- minimum/maximum part size;
|
||
- maximum part count;
|
||
- maximum concurrent grants/uploads;
|
||
- session expiry;
|
||
- required checksum;
|
||
- final expected size/digest requirement.
|
||
|
||
Provider upload ID를 public API에 노출하지 않는다.
|
||
|
||
### 19.3 Part upload
|
||
|
||
- part number는 1부터 시작하는 bounded integer;
|
||
- final part를 제외한 minimum size 규칙을 provider card가 제공;
|
||
- client는 arbitrary key/upload ID를 지정하지 않는다;
|
||
- part grant는 exact session/key/part/checksum/exact content length/expiry에 묶인다;
|
||
- grant expiry는 session expiry보다 짧거나 같다;
|
||
- out-of-order part upload는 허용하되 complete는 consecutive `1..N`을 오름차순으로 고정한다;
|
||
- final part만 configured minimum보다 작을 수 있다;
|
||
- part별 current grant generation은 하나이며 acknowledge 뒤 신규 grant를 발급하지 않는다;
|
||
- same part 재발급은 exact length와 full part digest가 같을 때만 새 generation으로 허용한다;
|
||
- prior grant는 revoke됐다고 보지 않으며 expiry + skew + qualified in-flight margin 전에는 새
|
||
generation acknowledge/complete를 허용하지 않거나 새 multipart session으로 교체한다;
|
||
- provider가 signed part checksum을 강제하지 못하면 direct multipart R2를 비활성화하거나
|
||
controlled ingress를 사용한다;
|
||
- direct mode에서 grant issuance count, declared bytes, active expiry window, worst-case replay
|
||
exposure를 tenant/destination/global budget으로 제한한다;
|
||
- 하나의 presigned part URL이 expiry 전 반복/concurrent 사용될 수 있으므로 provider-side 실제
|
||
request concurrency/transfer bytes가 bounded됐다고 주장하지 않는다;
|
||
- strict transport admission은 revocable controlled ingress 또는 provider-enforced primitive가
|
||
필요하다.
|
||
|
||
Browser direct multipart acknowledgement:
|
||
|
||
1. CORS가 exact origin/method/request headers와 필요한 `ETag`/checksum response
|
||
`Expose-Headers`만 허용한다.
|
||
2. Client는 grant와 함께 받은 acknowledgement nonce, part number, bounded provider ETag/checksum
|
||
claim을 server에 보낸다.
|
||
3. `acknowledgePart`는 session/grant nonce/expiry/fence/exact part length/checksum을 검증한다.
|
||
4. Provider `ListParts` 또는 exact provider evidence로 part 존재와 ETag/checksum을 검증한다.
|
||
5. 검증된 provider evidence를 private ledger에 CAS로 기록한다.
|
||
6. Server는 provider value를 숨긴 `PartReceiptToken`을 반환한다.
|
||
|
||
Forged, stale, wrong-session, superseded-attempt claim은 conflict/security audit이며 ledger에 넣지
|
||
않는다. Complete request는 public provider ETag 목록이 아니라 server가 발급한 opaque part
|
||
token만 받는다.
|
||
|
||
`COMPLETE_IN_PROGRESS` CAS 전에:
|
||
|
||
1. 신규 grant/acknowledge admission을 닫는다.
|
||
2. 모든 relevant grant generation의 expiry/in-flight horizon을 만족하거나 controlled ingress
|
||
drain evidence를 얻는다.
|
||
3. paginated `ListParts`를 다시 읽는다.
|
||
4. current ledger revision, ETag, algorithm/type/scope checksum, exact length와 비교한다.
|
||
5. 불일치하면 complete하지 않고 re-acknowledge/reconcile한다.
|
||
|
||
Complete CAS 뒤 신규 grant/acknowledge는 거부한다. Final object full SHA-256이 expected content
|
||
identity와 다르면 publish하지 않고 quarantine한다.
|
||
|
||
### 19.4 Complete
|
||
|
||
Complete 전:
|
||
|
||
- required part numbers의 연속성;
|
||
- duplicate/missing part;
|
||
- each opaque token/session binding;
|
||
- expected total size;
|
||
- aggregate checksum policy;
|
||
- session expiry/state
|
||
|
||
를 검증한다.
|
||
|
||
Complete response loss는 “실패했으므로 다시 complete”가 아니다.
|
||
|
||
Canonical transition:
|
||
|
||
```text
|
||
ACCEPTING_PARTS
|
||
-> COMPLETE_IN_PROGRESS
|
||
pendingEffect.kind = MULTIPART_COMPLETE
|
||
certainty = NOT_SENT | SENT | INDETERMINATE
|
||
-> COMPLETED
|
||
```
|
||
|
||
Indeterminate complete는 `COMPLETE_IN_PROGRESS`와 pending-effect certainty를 유지한 채 exact key
|
||
HEAD, multipart state, size/checksum/version을 비교해
|
||
`APPLIED`/`NOT_APPLIED`/계속 `INDETERMINATE`로 resolve한다.
|
||
|
||
AWS conditional complete가 concurrent delete/write와 경합해 `409 Conflict`를 반환하면 기존
|
||
upload ID에 complete만 재시도하지 않는다. Official provider semantics가 요구하는 경우 새
|
||
`CreateMultipartUpload`부터 전체 session을 재시작하며, original operation record에는 old session
|
||
abort/cleanup과 replacement session link를 보존한다. Conditional complete에서 `404`, `409`,
|
||
`412`를 각각 provider/card evidence에 따라 분리한다.
|
||
|
||
### 19.5 Abort와 orphan
|
||
|
||
- abort admission 전에 새 part grant 발급을 막고 application-known attempt를 fence한다;
|
||
- 이미 발급한 presigned part URL과 provider가 수락한 in-flight request는 application fence로
|
||
revoke됐다고 주장하지 않는다;
|
||
- application abort는 logical하게 idempotent;
|
||
- already completed session을 abort success로 오인하지 않는다;
|
||
- abort response loss를 reconcile한다;
|
||
- abort record는 `pendingEffect.kind=MULTIPART_ABORT`와 certainty를 유지한다;
|
||
- provider가 in-flight part의 late success를 허용하면 `Abort -> paginated ListParts -> 필요 시
|
||
Abort 반복`으로 part가 없음을 확인한다;
|
||
- earliest terminal check는 `latest issued grant expiry + qualified clock skew + qualified maximum
|
||
provider in-flight/request duration` 뒤다;
|
||
- presigned expiry는 request-start admission이며 transfer cutoff가 아니므로 expiry만으로
|
||
in-flight 종료를 증명하지 않는다;
|
||
- provider가 maximum in-flight horizon을 증명하지 못하면 controlled/revocable ingress를
|
||
사용하거나 abort certainty를 `INDETERMINATE`로 유지하고 repeated reaper + lifecycle
|
||
backstop만 claim한다;
|
||
- `NoSuchUpload`는 exact final object/session/control state와 함께 해석해 completed session을
|
||
aborted로 오인하지 않는다;
|
||
- maximum abort attempt/deadline 뒤 empty evidence가 없으면 `MULTIPART_ABORT_INDETERMINATE`로
|
||
유지한다;
|
||
- session TTL 뒤 reaper가 exact control state를 claim한다;
|
||
- provider lifecycle의 incomplete-multipart abort rule을 backstop으로 설정한다;
|
||
- lifecycle rule만을 유일한 cleanup으로 사용하지 않는다;
|
||
- reaper와 lifecycle 사이 race를 terminal state/evidence로 처리한다.
|
||
|
||
Finite deterministic abort를 R2 profile이 요구하면 위 maximum horizon 또는 revocable controlled
|
||
ingress evidence가 필수다. Incomplete-multipart lifecycle age도 이 horizon과 reconciliation
|
||
margin보다 길어야 한다.
|
||
|
||
### 19.6 Provider limits
|
||
|
||
AWS가 제공하는 maximum object/part limits를 portable application default로 그대로 사용하지 않는다.
|
||
Destination은 훨씬 낮은 안전한 limit를 고정하고, provider card가 이를 만족하는지만 판정한다.
|
||
|
||
## 20. Conditional mutation과 outcome/error
|
||
|
||
### 20.1 Mutation outcome
|
||
|
||
모든 mutation은 다음 outcome을 사용한다.
|
||
|
||
- `APPLIED`;
|
||
- `ALREADY_APPLIED`;
|
||
- `NOT_APPLIED`;
|
||
- `PRECONDITION_FAILED`;
|
||
- `INDETERMINATE`;
|
||
- `HELD`;
|
||
- `UNSUPPORTED`.
|
||
|
||
`void`, boolean 한 개, generic success/failure로 축약하지 않는다.
|
||
|
||
### 20.2 Preconditions
|
||
|
||
지원하는 logical precondition:
|
||
|
||
- create if absent;
|
||
- operation record state/version equals;
|
||
- reference absent;
|
||
- reference points to expected object/version;
|
||
- object exact version equals;
|
||
- retire only published;
|
||
- purge only retired and retention elapsed;
|
||
- multipart session state equals;
|
||
- request fingerprint equals.
|
||
|
||
Provider primitive가 이 precondition을 안전하게 구현하지 못하면 해당 card를 R2로 활성화하지
|
||
않는다. check-then-act를 atomic conditional mutation처럼 보고하지 않는다.
|
||
|
||
### 20.3 Error taxonomy
|
||
|
||
Application-visible typed category:
|
||
|
||
- `INVALID_REQUEST`;
|
||
- `OBJECT_NOT_FOUND`;
|
||
- `OBJECT_NOT_PUBLISHED`;
|
||
- `OBJECT_QUARANTINED`;
|
||
- `OPERATION_CONFLICT`;
|
||
- `OPERATION_EXPIRED`;
|
||
- `PRECONDITION_FAILED`;
|
||
- `RANGE_NOT_SATISFIABLE`;
|
||
- `OBJECT_TOO_LARGE`;
|
||
- `METADATA_TOO_LARGE`;
|
||
- `MEDIA_TYPE_NOT_ALLOWED`;
|
||
- `CHECKSUM_MISMATCH`;
|
||
- `RETENTION_HELD`;
|
||
- `UNSUPPORTED_CAPABILITY`;
|
||
- `POLICY_REVISION_UNAVAILABLE`;
|
||
- `BINDING_SECURITY_MISMATCH`;
|
||
- `DEPENDENCY_ACCESS_DENIED`;
|
||
- `CAPACITY_EXHAUSTED`;
|
||
- `THROTTLED`;
|
||
- `DEPENDENCY_UNAVAILABLE`;
|
||
- `TIMEOUT`;
|
||
- `CANCELLED`;
|
||
- `CONTENT_PRODUCTION_FAILED`;
|
||
- `CONTENT_CONSUMPTION_FAILED`;
|
||
- `DIRECT_SESSION_EXPIRED`;
|
||
- `GRANT_REPLAY_UNAVAILABLE`;
|
||
- `SCAN_INDETERMINATE`;
|
||
- `PROVIDER_PROTOCOL_VIOLATION`;
|
||
- `PUBLISH_INDETERMINATE`;
|
||
- `CORRUPT_CONTROL_RECORD`;
|
||
- `UNSUPPORTED_CONTROL_SCHEMA`;
|
||
- `INTERNAL_ERROR`.
|
||
|
||
각 category는:
|
||
|
||
- safe public code/message;
|
||
- retryability;
|
||
- reconciliation requirement;
|
||
- health impact;
|
||
- metric outcome;
|
||
- audit severity
|
||
|
||
를 table-driven mapping으로 가진다.
|
||
|
||
### 20.4 Provider error mapping
|
||
|
||
AWS/MinIO/filesystem raw exception은 adapter 안에서:
|
||
|
||
- operation;
|
||
- provider error/status/code;
|
||
- bytes sent/received 여부;
|
||
- response presence;
|
||
- request id의 safe hash;
|
||
- exact precondition;
|
||
- retry attempt;
|
||
- mutation certainty
|
||
|
||
를 고려해 normalize한다.
|
||
|
||
HTTP 404만 보고 모두 not-found로 매핑하지 않는다. Wrong owner/bucket/permission/endpoint가
|
||
404처럼 보일 수 있는 경우 startup binding과 operation context를 함께 사용한다.
|
||
|
||
### 20.5 Retry
|
||
|
||
- validation, checksum mismatch, conflict, held는 retry하지 않는다;
|
||
- throttling/unavailable은 bounded retry 대상일 수 있다;
|
||
- body producer가 non-repeatable이면 upload transport retry를 제한한다;
|
||
- immutable create의 response loss는 reconcile 먼저;
|
||
- multipart part는 exact part identity/checksum으로 retry 가능 여부를 판정한다;
|
||
- complete/delete/reference CAS는 unknown outcome 규칙을 따른다;
|
||
- SDK internal retry도 total amplification budget에 포함한다.
|
||
|
||
Provider-wide `max-attempts`만으로 mutation 안전을 결정하지 않는다. Put, complete, reference CAS,
|
||
delete, abort별 retry policy와 physical attempt telemetry를 고정한다. Mutation에서:
|
||
|
||
- SDK가 2회 이상 physical attempt를 수행했거나;
|
||
- response body/ack가 유실되었거나;
|
||
- AWS `CompleteMultipartUpload`처럼 initial HTTP 200 뒤 embedded error가 가능한 operation이면
|
||
|
||
final SDK exception/success 하나만 보고 certainty를 확정하지 않고 operation-specific evidence와
|
||
reconciliation 규칙을 적용한다.
|
||
|
||
## 21. Provider model
|
||
|
||
### 21.1 Provider identity
|
||
|
||
다음 exact provider type을 사용한다.
|
||
|
||
- `filesystem-local-dev`;
|
||
- `filesystem-local-persistent` optional;
|
||
- `aws-s3-general-purpose`;
|
||
- `s3-compatible-minio-<distribution>-<qualified-release-token>`.
|
||
|
||
`s3`, `s3-compatible`, `filesystem` 같은 넓은 이름 하나로 production 보장을 선언하지 않는다.
|
||
|
||
### 21.2 Capability descriptor
|
||
|
||
Provider startup qualification 결과:
|
||
|
||
```text
|
||
ObjectStorageCapabilityDescriptor
|
||
providerType
|
||
providerVersion
|
||
bindingRevision
|
||
qualificationTimestamp
|
||
evidenceRevision
|
||
evidenceExpiresAt
|
||
operations {
|
||
managedUpload: CapabilityEvidence
|
||
managedDownload: CapabilityEvidence
|
||
rangeRead: CapabilityEvidence
|
||
controlPlaneCas: CapabilityEvidence
|
||
directPut: CapabilityEvidence
|
||
directPost: CapabilityEvidence
|
||
directGet: CapabilityEvidence
|
||
managedMultipart: CapabilityEvidence
|
||
directMultipart: CapabilityEvidence
|
||
retirement: CapabilityEvidence
|
||
exactPurge: CapabilityEvidence
|
||
}
|
||
guaranteeAxes {
|
||
visibility
|
||
crashDurability
|
||
consistency
|
||
mutationOutcomeCertainty
|
||
versionIdentity
|
||
authoritativeAbsence
|
||
}
|
||
namedOperationProfiles {
|
||
transferMode
|
||
checksumAlgorithm/type/scope/encoding
|
||
encryptionProfile
|
||
retentionProfile
|
||
immutableCreate
|
||
exactSizeEnforcement
|
||
responseLossReconciliation
|
||
}
|
||
limits
|
||
qualificationEvidenceRevision
|
||
```
|
||
|
||
`CapabilityEvidence`:
|
||
|
||
```text
|
||
status = SUPPORTED | UNSUPPORTED | UNVERIFIABLE
|
||
source = STATIC_ATTESTATION | STARTUP_PROBE | CI_QUALIFICATION
|
||
evidenceDigest
|
||
observedAt
|
||
validUntil
|
||
providerAndDeploymentIdentity
|
||
limitations
|
||
```
|
||
|
||
Flat boolean의 AND로 조합 capability를 승인하지 않는다. 예를 들어 각각의 checksum, direct
|
||
multipart, SSE-KMS, retention이 지원되어도 그 조합이 지원된다는 뜻이 아니다. Destination은
|
||
exact named operation profile을 요구하고 binding compiler는 그 profile 전체에 대한
|
||
`SUPPORTED` + unexpired evidence만 수용한다.
|
||
|
||
Direct upload profile이 per-object retention을 요구하면 Object Lock/retention header와
|
||
permission도 signed grant condition에 포함되어야 한다. 그렇지 않은 direct+retention 조합은
|
||
별개 기능이 각각 supported여도 `UNSUPPORTED`다.
|
||
|
||
Descriptor 값은 code default가 아니라:
|
||
|
||
- provider/version allowlist;
|
||
- static deployment attestation;
|
||
- safe startup probe;
|
||
- integration/qualification evidence
|
||
|
||
를 합성한 결과다.
|
||
|
||
`UNVERIFIABLE`, expired evidence, guarantee axis가 requirement보다 낮은 상태는
|
||
`SUPPORTED`로 취급하지 않는다. Atomic visibility와 crash durability, consistency와 outcome
|
||
certainty를 한 “durable/strong” boolean으로 합치지 않는다.
|
||
|
||
### 21.3 Common semantic subset
|
||
|
||
Common R2 baseline 후보:
|
||
|
||
- private immutable create;
|
||
- exact GET/HEAD;
|
||
- bounded single range;
|
||
- SHA-256 logical digest;
|
||
- stable opaque reference;
|
||
- control record conditional mutation;
|
||
- response-loss reconciliation;
|
||
- version-aware retire/purge;
|
||
- encryption-at-rest evidence;
|
||
- finite timeout/resource budget.
|
||
|
||
Provider가 하나라도 증명하지 못하면 destination requirement를 낮춰야 하는 것이 아니라 해당
|
||
provider/destination binding이 startup에서 실패한다.
|
||
|
||
### 21.4 Provider-specific capability
|
||
|
||
다음은 common subset이 아니다.
|
||
|
||
- AWS Object Lock;
|
||
- AWS DSSE-KMS;
|
||
- provider native checksum 조합;
|
||
- MinIO의 specific retention/lifecycle behavior;
|
||
- filesystem atomic/durability primitive;
|
||
- provider copy;
|
||
- provider notification;
|
||
- provider replication/region behavior.
|
||
|
||
Application이 optional capability를 요구할 때 exact provider card를 통해서만 접근한다.
|
||
|
||
## 22. Filesystem providers
|
||
|
||
### 22.1 `filesystem-local-dev`
|
||
|
||
목적:
|
||
|
||
- local sample와 unit/contract test;
|
||
- network 없는 개발;
|
||
- object/reference/control codec 빠른 검증.
|
||
|
||
제한:
|
||
|
||
- R0/R1까지만;
|
||
- single process/node;
|
||
- production profile 금지;
|
||
- presign/multipart/KMS/versioning/Object Lock 미지원;
|
||
- local disk 소실을 durable storage로 간주하지 않음;
|
||
- container image layer 또는 read-only root default 사용 금지.
|
||
|
||
활성화하려면 explicit dev/test profile과 absolute configured root가 필요하다.
|
||
|
||
### 22.2 `filesystem-local-persistent`
|
||
|
||
운영에서 local persistent disk가 정말 요구될 때 별도 card로 qualification한다.
|
||
|
||
필수 조건:
|
||
|
||
- dedicated mounted volume identity 검증;
|
||
- mount missing 시 local directory fallback 금지;
|
||
- restrictive root owner/permission;
|
||
- no-follow directory traversal;
|
||
- exclusive create;
|
||
- temp/control record atomic replace;
|
||
- file `force`와 directory durability strategy;
|
||
- disk/inode/free-space alert;
|
||
- quota와 reaper;
|
||
- single-node 또는 external fencing 범위 명시;
|
||
- backup/restore와 fsck/corruption runbook.
|
||
|
||
NFS/shared mount는 이 card에 포함하지 않는다. 필요하면 Fileserver 설계와 별도 provider 설계를
|
||
한다.
|
||
|
||
### 22.3 Path safety
|
||
|
||
Filesystem key resolution은 string normalize만으로 끝내지 않는다.
|
||
|
||
- application raw key 입력 자체를 제거;
|
||
- canonical internal segments만 사용;
|
||
- root를 startup에서 real path로 pin;
|
||
- intermediate symlink/reparse point 거부;
|
||
- supported platform에서 `SecureDirectoryStream` 또는 directory-handle-relative operation 사용;
|
||
- temp/data/control root 분리;
|
||
- root alias와 empty final segment 거부;
|
||
- create/delete 시 exact file type과 link count 정책;
|
||
- recursive delete 금지;
|
||
- cleanup traversal 중 mount/device boundary 정책;
|
||
- TOCTOU race fault test.
|
||
|
||
지원 플랫폼에서 필요한 safe primitive를 제공하지 않으면 provider card를 낮춘다.
|
||
|
||
### 22.4 Write/durability
|
||
|
||
Managed write:
|
||
|
||
1. target directory 아래 private temp/exclusive file 생성;
|
||
2. restrictive permission 적용;
|
||
3. bounded stream과 hash;
|
||
4. file flush/force;
|
||
5. immutable final name으로 no-replace publish;
|
||
6. 필요한 directory force;
|
||
7. manifest/control CAS;
|
||
8. temp cleanup.
|
||
|
||
Atomic visibility와 crash durability는 별도 descriptor field다. `ATOMIC_MOVE` 하나로 둘을 모두
|
||
증명하지 않는다.
|
||
|
||
### 22.5 Filesystem metadata
|
||
|
||
Content type, digest, exact size, generation, state를 filename/xattr에만 의존하지 않는다. Versioned
|
||
private manifest를 사용한다. Xattr은 optional evidence일 뿐 portable truth가 아니다.
|
||
|
||
## 23. AWS S3 general-purpose provider
|
||
|
||
### 23.1 범위
|
||
|
||
초기 AWS card는 regional general-purpose bucket에 한정한다.
|
||
|
||
- directory bucket/S3 Express 제외;
|
||
- access point/MRAP 제외;
|
||
- Requester Pays와 MFA Delete는 initial card에서 제외하고 필요 시 별도 permission/cost/operator
|
||
workflow card로 qualification;
|
||
- private bucket;
|
||
- versioning required 여부는 destination profile에 명시;
|
||
- active published object는 online-readable storage-class profile에 고정;
|
||
- restore workflow가 없는 initial card는 current data를 archive retrieval이 필요한 tier로 전환하는
|
||
lifecycle을 거부;
|
||
- lifecycle, ownership, BPA, encryption은 pre-provisioned;
|
||
- runtime은 object/control prefix operation만 수행.
|
||
|
||
### 23.2 Consistency
|
||
|
||
AWS S3가 현재 제공하는 strong read-after-write/list consistency는 이 exact provider card에만
|
||
적용한다.
|
||
|
||
- PUT/DELETE/HEAD/GET 후 object data lookup;
|
||
- control record conditional protocol;
|
||
- LIST maintenance discovery.
|
||
|
||
Bucket configuration, IAM propagation, DNS/network, replication 의미를 같은 consistency로
|
||
확장하지 않는다. MinIO 또는 다른 compatible store로 일반화하지 않는다.
|
||
|
||
### 23.3 Client 선택
|
||
|
||
초기 managed streaming transport는 Java-based `S3AsyncClient`를 우선 검토한다. 그러나 R2
|
||
multipart orchestration을 SDK의 opaque automatic multipart에 맡기지 않는다.
|
||
|
||
선정 이유:
|
||
|
||
- unknown content length streaming 지원;
|
||
- standard SDK HTTP/timeouts/retry/metrics와 통합;
|
||
- provider client lifecycle을 한 곳에서 소유.
|
||
|
||
R2 managed multipart는 adapter가 low-level:
|
||
|
||
- `CreateMultipartUpload`;
|
||
- `UploadPart`;
|
||
- `ListParts`;
|
||
- `CompleteMultipartUpload`;
|
||
- `AbortMultipartUpload`
|
||
|
||
를 직접 호출하고 provider upload ID, part evidence, pending effect, complete/abort certainty를 durable
|
||
control record에 보존한다. SDK-managed automatic multipart가 이 evidence/hook를 public하게
|
||
노출하지 않으면 R2 card에 사용할 수 없다.
|
||
|
||
Pinned 2.30.0의 automatic multipart는 upload ID/part ledger를 application adapter에 노출하지
|
||
않고 실패 cleanup을 deterministic reconciliation protocol로 제공하지 않는다. 따라서 별도
|
||
qualification 전에는 R1 convenience/transport evidence로만 취급한다. Maximum size와 integer
|
||
overflow를 포함한 boundary test 없이 large-object fallback으로 사용하지 않는다.
|
||
|
||
CRT-based client는 high-throughput optional card다. 다음을 별도로 qualification한 뒤에만 사용한다.
|
||
|
||
- SDK/HTTP configuration 차이;
|
||
- retry/timeout 의미;
|
||
- metric visibility;
|
||
- native library packaging;
|
||
- memory/direct-buffer footprint;
|
||
- shutdown and cancellation;
|
||
- checksum/multipart behavior.
|
||
|
||
“더 빠르다”는 이유만으로 baseline을 교체하지 않는다.
|
||
|
||
### 23.4 SDK version
|
||
|
||
현재 build는 AWS SDK BOM `2.30.0`을 pin한다. 구현 계획은:
|
||
|
||
- 승인된 exact version의 API/bug/security evidence를 다시 확인;
|
||
- 2.30.0부터 적용되는 default upload checksum calculation behavior를 characterization;
|
||
- `requestChecksumCalculation`, `responseChecksumValidation`, explicit algorithm을 provider
|
||
profile에 고정;
|
||
- AWS/MinIO/presign별 default CRC32와 explicit SHA-256 compatibility를 test;
|
||
- pinned 2.30.0 `CompleteMultipartUploadRequest.Builder.mpuObjectSize(Integer)` 경계를 반영해
|
||
FULL_OBJECT multipart checksum profile이 `Integer.MAX_VALUE`를 넘으면 startup에서 거부하거나,
|
||
audited raw-header path/승인된 SDK upgrade 뒤에만 허용;
|
||
- 2 GiB 경계와 overflow test;
|
||
- dependency lock 갱신;
|
||
- provider qualification version 기록;
|
||
- version upgrade compatibility/fault test;
|
||
- deprecated/changed conditional header support 확인
|
||
|
||
을 포함한다. 이 문서가 미래 SDK의 API 존재를 보장하지 않는다.
|
||
|
||
### 23.5 Conditional request
|
||
|
||
Immutable data/control create에 `If-None-Match: *` 또는 해당 SDK의 exact conditional primitive를
|
||
사용한다. CAS/delete에는 expected ETag/version/precondition을 사용한다.
|
||
|
||
- request builder가 header를 제공하는지 exact pinned SDK에서 검증;
|
||
- proxy/gateway가 header를 보존하는지 integration test;
|
||
- conditional complete의 `404`/`409`/`412`와 transient/permission 오류를 분리;
|
||
- AWS가 `409` 뒤 multipart 전체 재시작을 요구하는 operation은 기존 upload ID complete retry
|
||
금지;
|
||
- unsupported provider는 check-then-put으로 downgrade하지 않음;
|
||
- bucket policy로 conditional write를 강제할 수 있으면 deployment control에 포함.
|
||
|
||
AWS bucket policy가 conditional create를 강제할 때 `PutObject`/`CompleteMultipartUpload`에는
|
||
조건을 요구하되, conditional header를 받지 않는 `CreateMultipartUpload`/`UploadPart` 같은
|
||
`s3:ObjectCreationOperation` 단계는 official policy shape에 맞게 exempt한다. 그렇지 않으면
|
||
multipart를 403으로 막을 수 있다. ETag `If-Match` write/delete에 필요한 scoped
|
||
`s3:GetObject` permission도 qualification한다.
|
||
|
||
### 23.6 Expected bucket owner
|
||
|
||
모든 supported request에 expected bucket owner를 설정한다. Startup binding의 account/bucket
|
||
attestation과 함께 confused-deputy/misrouting을 줄인다.
|
||
|
||
Expected-owner mismatch는 not-found가 아니라 hard configuration/security failure다.
|
||
|
||
### 23.7 HEAD, checksum, authoritative absence
|
||
|
||
- checksum을 HEAD/GET response로 요구할 때 `ChecksumMode.ENABLED`를 명시한다;
|
||
- checksum evidence는 algorithm/type/scope를 함께 읽고 business full digest와 무조건 비교하지
|
||
않는다;
|
||
- SSE-KMS checksum 조회에 필요한 `kms:Decrypt`와 provider 문서가 요구하는 KMS permission을
|
||
qualification한다;
|
||
- HEAD 요청에 PUT용 encryption header를 보내지 않는다;
|
||
- 403/404만으로 absent, forbidden, wrong owner를 단정하지 않는다.
|
||
|
||
AWS `HeadObject`는 missing key에서 caller의 `s3:ListBucket` 권한에 따라 404 또는 403을 반환할 수
|
||
있다. Descriptor에 `authoritativeAbsence` evidence를 둔다.
|
||
|
||
- destination prefix로 제한한 `s3:ListBucket`와 version workflow에 필요한
|
||
`s3:ListBucketVersions` 권한으로 negative lookup을 증명하거나;
|
||
- absence를 끝까지 `INDETERMINATE`로 유지한다.
|
||
|
||
Startup probe는 existing sentinel뿐 아니라 missing-key negative lookup과 forbidden-key
|
||
differentiation을 검증한다. MinIO도 exact permission/error behavior를 별도 card로 test한다.
|
||
|
||
### 23.8 Encryption
|
||
|
||
Destination이 다음 named encryption profile 중 하나를 요구한다.
|
||
|
||
- `sse-s3`;
|
||
- `sse-kms:<logical-key-profile>`;
|
||
- `dsse-kms:<logical-key-profile>` optional.
|
||
|
||
Application request에 raw KMS key ARN을 넣지 않는다. Binding compiler가 logical profile을
|
||
pre-approved key와 encryption context로 resolve한다.
|
||
|
||
검증:
|
||
|
||
- single PUT에는 selected SSE request header;
|
||
- multipart에는 `CreateMultipartUpload`에 selected SSE-KMS/DSSE 설정;
|
||
- `UploadPart`와 `CompleteMultipartUpload`는 create 설정을 상속하며 SSE-C처럼 동일 KMS request
|
||
header를 반복 전송하지 않음;
|
||
- part/complete/final response와 HEAD encryption attestation;
|
||
- response/head encryption mode;
|
||
- KMS key identity/version policy;
|
||
- `GenerateDataKey`, `Decrypt` 등 exact operation에 필요한 KMS permission;
|
||
- copy 시 source/destination encryption;
|
||
- KMS throttling/error mapping;
|
||
- presign에 필요한 signed header.
|
||
|
||
SSE-C는 baseline에서 제외한다. Key material을 application memory/header/log에 전달하지 않는다.
|
||
|
||
### 23.9 Ownership와 public access
|
||
|
||
Production requirement:
|
||
|
||
- Block Public Access;
|
||
- bucket-owner-enforced object ownership;
|
||
- ACL disabled;
|
||
- public bucket policy 없음;
|
||
- access logging/CloudTrail data event 정책은 risk profile에 따라 활성화;
|
||
- runtime principal은 exact bucket/prefix/action 최소 권한;
|
||
- maintenance principal은 runtime principal과 분리 가능;
|
||
- public delivery는 application authorization + stream/presign만 사용.
|
||
|
||
### 23.10 Versioning
|
||
|
||
Versioning이 required인 destination:
|
||
|
||
- startup attestation/probe로 enabled 확인;
|
||
- exact version ID를 private evidence에 보존;
|
||
- read/retire/purge가 version-aware;
|
||
- versioned read/purge role에 scoped `s3:GetObjectVersion`/`s3:DeleteObjectVersion` permission;
|
||
- delete marker와 object version을 구분;
|
||
- lifecycle noncurrent-version retention을 검증;
|
||
- suspended 상태를 enabled로 간주하지 않음.
|
||
|
||
AWS bucket에 versioning을 처음 enable한 직후의 propagation window는 `enabled` 조회 한 번으로
|
||
readiness를 승인하지 않는다. IaC attestation에 activation timestamp를 넣고 provider가 권고한
|
||
soak 기간 뒤 sentinel create/read/delete/version test를 통과해야 신규 admission을 연다.
|
||
|
||
### 23.11 Retention/Object Lock
|
||
|
||
Object Lock card는:
|
||
|
||
- exact provider/version의 enablement constraint와 irreversible setting;
|
||
- governance/compliance mode;
|
||
- default retention;
|
||
- legal hold permission;
|
||
- versioning;
|
||
- bypass-governance 권한 부재 또는 엄격한 별도 break-glass;
|
||
- clock/reference time;
|
||
- audit
|
||
|
||
을 qualification한다.
|
||
|
||
AWS general-purpose bucket은 current provider semantics에 따라 existing bucket enablement를 별도
|
||
qualification한다. Older MinIO/other distribution의 creation-time-only 제약을 AWS에
|
||
일반화하거나, 반대로 current AWS 동작을 old MinIO에 일반화하지 않는다.
|
||
|
||
Application delete가 hold를 만나면 `HELD`이며 success purge로 보고하지 않는다.
|
||
|
||
### 23.12 Lifecycle
|
||
|
||
Pre-provisioned lifecycle은 다음을 backstop한다.
|
||
|
||
- incomplete multipart expiration;
|
||
- noncurrent version retention;
|
||
- expired delete marker;
|
||
- separately copied immutable terminal-audit archive가 replay/retention horizon을 지난 뒤 만료되는
|
||
narrow prefix.
|
||
|
||
Lifecycle만으로 application attachment 상태를 판정하지 않는다. Rule ID와 expected digest를
|
||
deployment attestation에 고정한다.
|
||
|
||
Incomplete-multipart lifecycle age는 maximum active session + grant expiry + clock-skew +
|
||
reconciliation margin보다 길어야 한다. 정상 장기 upload를 lifecycle이 먼저 abort하지 않는지
|
||
fault test한다.
|
||
|
||
Baseline의 published/staged/quarantined data가 같은 immutable `data/v1` prefix를 사용하므로,
|
||
provider lifecycle은 control state를 보고 staged object만 안전하게 골라낼 수 없다. 따라서
|
||
staged/quarantined data cleanup은 fenced reconciler만 수행한다. Lifecycle을 여기에 적용하려면
|
||
state-safe immutable prefix 또는 immutable lifecycle marker가 publication과 race 없이 유지된다는
|
||
별도 protocol/fault evidence가 먼저 필요하다. Bucket policy도 lifecycle engine의 잘못된
|
||
deletion을 application state로 막는 대체 수단이 아니다.
|
||
|
||
Live/current/non-terminal `control/v1` record에는 provider age-based lifecycle을 적용하지 않는다.
|
||
Lifecycle은 pending effect, replay-until, handoff fence, schema, live reference를 이해하지 못한다.
|
||
Control cleanup은 fenced reconciler가 exact state와 horizon을 검증해 수행한다.
|
||
|
||
## 24. MinIO provider
|
||
|
||
### 24.1 Exact qualification
|
||
|
||
MinIO는 “S3-compatible”이라는 이유로 AWS card를 상속하지 않는다.
|
||
|
||
Card는 최소 다음을 pin한다.
|
||
|
||
- exact product/distribution;
|
||
- tested server version/range;
|
||
- deployment topology;
|
||
- Java SDK version;
|
||
- versioning;
|
||
- retention/Object Lock;
|
||
- lifecycle;
|
||
- checksum behavior;
|
||
- conditional request behavior;
|
||
- presign/path-style/virtual-host behavior;
|
||
- multipart complete/abort semantics;
|
||
- error code mapping;
|
||
- consistency/failure assumptions.
|
||
|
||
### 24.2 Current Testcontainers evidence
|
||
|
||
현재 MinIO integration test는:
|
||
|
||
- OSS image `minio/minio:RELEASE.2024-01-16T16-07-38Z`;
|
||
- bucket create;
|
||
- byte[] put/get;
|
||
- exists/delete round trip
|
||
|
||
정도의 functional topology 증거다. 이는 R1이며 다음을 증명하지 않는다.
|
||
|
||
- streaming heap bound;
|
||
- conditional race;
|
||
- node/process/network failure;
|
||
- response-loss reconciliation;
|
||
- multipart orphan;
|
||
- versioning/retention;
|
||
- TLS/credentials/ownership;
|
||
- presigned public endpoint;
|
||
- rolling upgrade;
|
||
- backup/restore.
|
||
|
||
§41의 current MinIO AIStor 문서는 미래 AIStor card를 설계하기 위한 primary reference이며 이
|
||
OSS 2024 image의 동작 증거가 아니다. Current OSS card는 exact image digest, release/source
|
||
provenance, AWS-SDK-based contract/fault test로 별도 qualification한다. 현재 adapter는 MinIO
|
||
Java SDK가 아니라 AWS SDK를 사용하므로 MinIO Java SDK 문서도 current implementation evidence로
|
||
사용하지 않는다.
|
||
|
||
### 24.3 Endpoint
|
||
|
||
- production은 HTTPS;
|
||
- certificate/hostname verification을 끄지 않는다;
|
||
- internal/public presign endpoint를 분리;
|
||
- path-style 설정은 exact deployment와 DNS에 맞춤;
|
||
- arbitrary endpoint override 금지;
|
||
- loopback/RFC1918 endpoint 허용은 explicit environment policy;
|
||
- region/signature expectation을 startup에서 검증;
|
||
- redirect를 provider equivalence로 따라가지 않는다.
|
||
|
||
### 24.4 Feature downgrade 금지
|
||
|
||
MinIO가 특정 checksum/conditional/retention behavior를 지원하지 않으면:
|
||
|
||
- 해당 capability evidence를 `UNSUPPORTED` 또는 증거가 불충분하면 `UNVERIFIABLE`로 둔다;
|
||
- 필요한 destination binding은 startup 실패;
|
||
- AWS semantics를 client-side check-then-act로 흉내 내지 않는다;
|
||
- 별도 protocol이 안전하다면 provider-specific 설계와 fault evidence 후 추가한다.
|
||
|
||
## 25. Configuration schema
|
||
|
||
### 25.1 Canonical prefix
|
||
|
||
새 canonical prefix:
|
||
|
||
```text
|
||
app.object-storage
|
||
```
|
||
|
||
기존 `ca-skeleton.objectstorage.*`는 migration 기간 legacy alias로만 탐지한다.
|
||
|
||
- canonical과 legacy가 동시에 존재하면 startup 실패;
|
||
- legacy를 silent precedence로 덮지 않는다;
|
||
- migration warning에 secret/value를 출력하지 않는다;
|
||
- removal release를 문서화한다.
|
||
|
||
### 25.2 Top-level
|
||
|
||
예시:
|
||
|
||
```yaml
|
||
app:
|
||
object-storage:
|
||
enabled: false
|
||
required-destinations: []
|
||
providers: {}
|
||
destinations: {}
|
||
maintenance:
|
||
enabled: false
|
||
```
|
||
|
||
`enabled` default는 `false`다. Provider type과 destination은 default가 없다.
|
||
|
||
### 25.3 Provider binding
|
||
|
||
개념 예시:
|
||
|
||
```yaml
|
||
app:
|
||
object-storage:
|
||
enabled: true
|
||
providers:
|
||
poster-s3:
|
||
type: aws-s3-general-purpose
|
||
bucket: ${OBJECT_STORAGE_POSTER_BUCKET}
|
||
region: ${AWS_REGION}
|
||
expected-owner: ${OBJECT_STORAGE_EXPECTED_OWNER}
|
||
credentials:
|
||
mode: default-chain
|
||
endpoint:
|
||
control: null
|
||
public-presign: null
|
||
addressing: virtual-hosted
|
||
encryption-profiles:
|
||
poster-default:
|
||
type: sse-kms
|
||
key-ref: poster-object-key
|
||
timeouts:
|
||
api-call: 20s
|
||
api-attempt: 8s
|
||
connect: 2s
|
||
tls-negotiation: 3s
|
||
acquire: 1s
|
||
read: 10s
|
||
write: 10s
|
||
pool:
|
||
max-concurrency: 64
|
||
max-pending-acquires: 128
|
||
retry:
|
||
strategy: standard
|
||
max-attempts: 3
|
||
```
|
||
|
||
숫자는 예시이며 performance/fault test 없이 production default로 복사하지 않는다.
|
||
|
||
### 25.4 Destination binding
|
||
|
||
```yaml
|
||
app:
|
||
object-storage:
|
||
destinations:
|
||
poster-image:
|
||
provider-ref: poster-s3
|
||
namespace: poster-image-v1
|
||
allowed-operations:
|
||
- managed-upload
|
||
- server-download
|
||
- direct-download
|
||
maximum-object-bytes: 10485760
|
||
required-content-digest: sha-256
|
||
publication:
|
||
mode: scan-gated-reference
|
||
encryption-profile: poster-default
|
||
retention-profile: poster-standard
|
||
direct-upload-profile: disabled
|
||
```
|
||
|
||
실제 business media allowlist와 image decode 정책은 sample application typed settings/policy가
|
||
소유할 수 있다. Adapter destination의 maximum bytes와 checksum/encryption은 infrastructure
|
||
safety ceiling이다. 둘 다 존재하면 더 엄격한 값을 적용한다.
|
||
|
||
### 25.5 Settings type
|
||
|
||
- immutable nested records 또는 constructor-bound settings;
|
||
- Bean Validation;
|
||
- `Duration`, `DataSize`, typed enum;
|
||
- provider별 sealed/validated variant;
|
||
- unknown property fail 정책;
|
||
- duplicate normalized destination/provider ID 거부;
|
||
- secret은 `String` field로 직접 바인딩하지 않고 secret reference/credential provider 사용;
|
||
- `toString`, validation message, actuator configprops에서 secret redaction;
|
||
- endpoint URI의 scheme/userinfo/query/fragment 검증.
|
||
|
||
### 25.6 Invalid configuration
|
||
|
||
다음은 startup hard failure다.
|
||
|
||
- enabled인데 provider/destination 없음;
|
||
- required destination 누락;
|
||
- unknown provider type/version;
|
||
- destination의 provider ref 누락;
|
||
- plaintext production endpoint;
|
||
- endpoint에 userinfo/query/fragment;
|
||
- static credential pair 일부만 존재;
|
||
- production static literal credential;
|
||
- AWS provider의 expected owner 누락 또는 MinIO/filesystem provider의 required deployment
|
||
identity/mount attestation 누락;
|
||
- bucket/base root invalid;
|
||
- auto-create production option;
|
||
- unsupported capability requirement;
|
||
- size/part/retry/timeout가 0, 음수, overflow 또는 전체 budget과 모순;
|
||
- same namespace collision;
|
||
- public presign host가 allowlist 밖;
|
||
- scan-required인데 scan seam 없음;
|
||
- retention required인데 provider card 불충족;
|
||
- legacy/canonical key 동시 사용.
|
||
|
||
## 26. Activation, lifecycle, bootstrap
|
||
|
||
### 26.1 Exact activation
|
||
|
||
Activation 순서:
|
||
|
||
```text
|
||
settings bind/validate
|
||
-> enabled?
|
||
-> provider definitions compile
|
||
-> destination references compile
|
||
-> required capabilities compare
|
||
-> safe qualification/attestation verify
|
||
-> clients/presigners create
|
||
-> maintenance jobs register
|
||
-> readiness card publish
|
||
```
|
||
|
||
Disabled일 때:
|
||
|
||
- filesystem root 생성 없음;
|
||
- AWS credential resolution 없음;
|
||
- client/event-loop/thread 없음;
|
||
- DNS/network 없음;
|
||
- scheduler 없음;
|
||
- health indicator 없음;
|
||
- warning spam 없음.
|
||
|
||
### 26.2 No default provider
|
||
|
||
`matchIfMissing=true`를 제거한다. Local development도 명시적으로:
|
||
|
||
```yaml
|
||
app.object-storage.enabled: true
|
||
app.object-storage.providers.local.type: filesystem-local-dev
|
||
```
|
||
|
||
를 선택한다.
|
||
|
||
### 26.3 Provisioning boundary
|
||
|
||
Runtime startup은 다음을 만들거나 바꾸지 않는다.
|
||
|
||
- bucket;
|
||
- KMS key;
|
||
- IAM policy;
|
||
- lifecycle;
|
||
- versioning;
|
||
- Object Lock;
|
||
- Block Public Access;
|
||
- ownership controls;
|
||
- CORS.
|
||
|
||
IaC/deployment pipeline이 미리 provision한다. Runtime은 safe read/probe 또는 signed attestation으로
|
||
검증한다.
|
||
|
||
### 26.4 Startup qualification
|
||
|
||
모든 runtime principal에 broad configuration read 권한을 주지 않는다. 두 mode를 지원한다.
|
||
|
||
1. safe probe mode:
|
||
- expected owner가 있는 exact bucket/head;
|
||
- reserved sentinel prefix에 create/head/get/delete;
|
||
- conditional create/CAS;
|
||
- checksum/encryption response;
|
||
- optional multipart probe;
|
||
2. deployment attestation mode:
|
||
- IaC가 생성한 canonical capability document;
|
||
- bucket/account/region/policy/lifecycle/encryption/versioning digest;
|
||
- signer identity와 expiry;
|
||
- runtime은 signature/digest와 minimal data-plane probe만 검증.
|
||
|
||
Probe object는 dedicated namespace, short TTL, bounded size, audit tag를 사용한다. Production user
|
||
namespace를 오염시키지 않는다.
|
||
|
||
Attestation은 background에서 expiry 전에 refresh한다.
|
||
|
||
- bounded last-known-good grace는 signed policy에 명시된 경우에만 사용;
|
||
- grace 중 readiness는 degraded이며 신규 write/direct grant admission을 막을 수 있다;
|
||
- expiry 뒤 required destination은 신규 mutation/grant를 fail closed;
|
||
- 이미 published exact-version read는 별도 read-only continuity policy와 live safe probe가
|
||
허용할 때만 유지;
|
||
- refresh failure가 current binding으로 silent recompile을 일으키지 않음;
|
||
- expiry/refresh/last-known-good 사용을 metric/audit한다.
|
||
|
||
### 26.5 Bootstrap dependency
|
||
|
||
현재 `app-bootstrap` registry는 objectstorage leaf에 production dependency를 허용하지 않는다.
|
||
실제 production runtime에 이 capability를 포함하려면:
|
||
|
||
1. use case/runtime owner를 확정한다;
|
||
2. registry의 `allowed_dependencies`를 설계 승인 후 갱신한다;
|
||
3. Gradle dependency를 추가한다;
|
||
4. architecture verification을 통과한다;
|
||
5. required destination startup test를 추가한다.
|
||
|
||
Classpath scan에 우연히 발견되는 configuration을 composition 근거로 사용하지 않는다.
|
||
|
||
### 26.6 Lifecycle
|
||
|
||
- client/presigner/event-loop executor ownership 명시;
|
||
- Spring context stop 시 새 operation admission 중지;
|
||
- grace period 동안 in-flight managed upload/download 완료;
|
||
- direct session은 durable state라 process shutdown과 분리;
|
||
- grace 초과 operation을 cancellation/indeterminate로 기록;
|
||
- SDK client/executor close;
|
||
- maintenance lease release;
|
||
- shutdown hook 하나에만 의존하지 않음.
|
||
|
||
## 27. Security design
|
||
|
||
### 27.1 Credential
|
||
|
||
Production 우선순위:
|
||
|
||
1. workload identity/instance/container role;
|
||
2. short-lived assumed role;
|
||
3. approved external credential process;
|
||
4. static credential은 local/test 전용.
|
||
|
||
- default credential chain의 exact allowed source를 environment별로 검토;
|
||
- developer credential source가 production에서 우연히 선택되지 않게 한다;
|
||
- credential expiration/refresh failure를 metric/readiness에 반영;
|
||
- access key ID조차 일반 log에 출력하지 않는다;
|
||
- credential provider 객체는 provider configuration이 소유한다.
|
||
|
||
### 27.2 Least privilege
|
||
|
||
역할 분리 후보:
|
||
|
||
- runtime managed transfer;
|
||
- presign issuer;
|
||
- reconciliation/cleanup;
|
||
- scanner read/quarantine;
|
||
- deployment qualification;
|
||
- break-glass retention administration.
|
||
|
||
각 역할은 bucket-wide wildcard 대신 destination prefix와 action을 제한한다. Presign issuer 권한은
|
||
grant 가능한 최대 권한의 상한이다.
|
||
|
||
### 27.3 Network
|
||
|
||
- TLS 1.2 이상;
|
||
- hostname/certificate verification;
|
||
- outbound DNS/host/port allowlist;
|
||
- VPC endpoint/private network 사용 시 policy와 DNS qualification;
|
||
- proxy 사용 시 CONNECT/authority/credential leak 검증;
|
||
- endpoint override는 static approved binding만 허용;
|
||
- SSRF-style request-controlled host/key/presign authority 금지.
|
||
|
||
### 27.4 Content safety
|
||
|
||
- extension과 client media type을 신뢰하지 않는다;
|
||
- maximum decompressed/archive expansion 같은 business risk는 scanner/application이 제한;
|
||
- dangerous format은 quarantine;
|
||
- scan engine failure/timeout은 clean이 아니다;
|
||
- malicious object는 isolation/retention/audit 정책에 따라 처리;
|
||
- public download에 `Content-Disposition`, `X-Content-Type-Options` 등 inbound response 정책 적용;
|
||
- active content를 same-origin inline으로 제공하지 않는다.
|
||
|
||
### 27.5 Confidentiality
|
||
|
||
- 모든 provider object/control record private;
|
||
- encryption at rest profile required;
|
||
- control record에 secret/presigned URL/PII 최소화;
|
||
- sensitive metadata는 application DB 또는 encrypted manifest;
|
||
- object/reference/key를 metric tag로 금지;
|
||
- debug body logging 금지;
|
||
- heap dump/core dump risk와 buffer zeroing 필요성을 data classification별로 검토.
|
||
|
||
### 27.6 Audit
|
||
|
||
감사 event:
|
||
|
||
- operation reserved/terminal;
|
||
- direct grant issued/expired;
|
||
- scan verdict;
|
||
- publication/retirement/purge;
|
||
- retention held/break-glass;
|
||
- qualification mismatch;
|
||
- cleanup decision;
|
||
- corruption/indeterminate manual resolution.
|
||
|
||
Audit에는 safe hashed operation/reference token, destination, action, actor/correlation, outcome,
|
||
policy revision을 기록한다. URL, raw key, filename, content는 기록하지 않는다.
|
||
|
||
### 27.7 Threat-control-evidence matrix
|
||
|
||
| Threat | Required control | Required evidence |
|
||
| --- | --- | --- |
|
||
| raw key/path traversal or symlink escape | generated canonical key, handle-relative no-follow filesystem access | property/race/security test |
|
||
| overwrite/reused direct URL | immutable key, atomic create-only, bucket policy | concurrent/replay provider test |
|
||
| oversized upload cost DoS | provider-enforced hard length or bounded ingress | excess-body security/cost-bound test |
|
||
| URL/credential leakage | redaction, short TTL, secret-safe telemetry | captured log/trace/audit negative test |
|
||
| malicious active content | quarantine, exact-version scan fence, fail-closed publish | clean/malicious/stale/timeout workflow test |
|
||
| confused bucket/account/endpoint | expected owner/deployment identity, endpoint allowlist, TLS | wrong-owner/host/cert startup test |
|
||
| checksum substitution | typed full/composite/part checksum, expected SHA-256 | algorithm/scope mismatch test |
|
||
| mutation ACK loss | pending effect, phase-specific indeterminate reconciliation | dropped-response fault test |
|
||
| DB/object split-brain | pending DB state, outbox handoff fence, explicit abort authorization | crash-at-every-gap test |
|
||
| forged multipart part claim | nonce/session/fence, ListParts verification, opaque token | forged/stale/cross-session test |
|
||
| cleanup data loss | supported schema, exact version, handoff/retention fence, report-only | cleanup-vs-late-commit fault test |
|
||
| privilege/hold bypass | retire/purge port split, least privilege, no default governance bypass | IAM/retention negative test |
|
||
| stale policy/route | frozen policy revision, retained route registry, no current fallback | rolling migration/revision removal test |
|
||
| body/resource exhaustion | aggregate buffers/concurrency/timeouts/cancellation | heap/direct-memory/FD/slow-peer test |
|
||
|
||
## 28. Resource budget, timeout, retry, cancellation
|
||
|
||
### 28.1 Budget dimension
|
||
|
||
Provider/global/destination/tenant별로 제한한다.
|
||
|
||
- maximum object bytes;
|
||
- maximum delivered range bytes;
|
||
- chunk/buffer bytes;
|
||
- concurrent managed uploads/downloads;
|
||
- concurrent multipart sessions;
|
||
- parts per session;
|
||
- concurrent parts per session;
|
||
- total in-flight part bytes;
|
||
- HTTP connection concurrency;
|
||
- pending connection acquire;
|
||
- control record size;
|
||
- pending operation count;
|
||
- cleanup batch size;
|
||
- scan backlog;
|
||
- presign issue rate.
|
||
|
||
`byte[]` 전체 materialization을 없애도 concurrent buffer 곱이 heap/direct-memory를 초과할 수
|
||
있으므로 aggregate budget test가 필요하다.
|
||
|
||
Managed/server-mediated path는 actual in-flight resource를 admission control한다. Direct presigned
|
||
path는 grant issuance와 worst-case replay exposure만 제어하며 provider-side actual request
|
||
concurrency/bytes를 strict bound했다고 주장하지 않는다.
|
||
|
||
### 28.2 Deadline decomposition
|
||
|
||
하나의 “timeout” 필드로 합치지 않는다.
|
||
|
||
- application operation deadline;
|
||
- connection acquire;
|
||
- DNS/connect;
|
||
- TLS negotiation;
|
||
- SDK API call;
|
||
- SDK API attempt;
|
||
- socket read/write idle;
|
||
- producer/consumer stall;
|
||
- scan;
|
||
- reconciliation;
|
||
- graceful shutdown;
|
||
- presign/session expiry.
|
||
|
||
Child timeout의 합과 retry backoff가 parent deadline을 넘지 않게 compile한다.
|
||
|
||
### 28.3 Retry amplification
|
||
|
||
최악의 physical attempt:
|
||
|
||
```text
|
||
application retry
|
||
x operation-kernel retry
|
||
x SDK retry
|
||
x multipart part count
|
||
x concurrent workers
|
||
```
|
||
|
||
Binding compiler가 최대 증폭을 계산하고 upper bound를 넘으면 startup 실패시킨다. Metric은
|
||
logical operation과 physical SDK attempt를 분리한다.
|
||
|
||
### 28.4 Backpressure
|
||
|
||
- bounded executor/queue;
|
||
- semaphore admission;
|
||
- connection acquire queue 상한;
|
||
- multipart concurrency 상한;
|
||
- producer가 sink보다 빠르게 무한 buffer하지 않음;
|
||
- consumer가 느리면 provider read를 bounded 방식으로 늦춤;
|
||
- overload는 `CAPACITY_EXHAUSTED`/429·503 mapping;
|
||
- admission 거부가 thread starvation보다 먼저 발생.
|
||
|
||
### 28.5 Cancellation
|
||
|
||
Cancellation point:
|
||
|
||
- before producer start;
|
||
- between chunks;
|
||
- while SDK future/HTTP body active;
|
||
- between multipart parts;
|
||
- scan wait;
|
||
- reconciliation wait.
|
||
|
||
Cancellation 뒤:
|
||
|
||
- provider request cancel/response body close;
|
||
- partial single upload/multipart abort;
|
||
- operation state persist;
|
||
- resources/semaphore release;
|
||
- caller에게 `CANCELLED` 또는 mutation certainty에 따른 `INDETERMINATE`;
|
||
- cancellation을 success로 기록하지 않음.
|
||
|
||
### 28.6 Retryable producer
|
||
|
||
Managed upload의 producer를 SDK가 임의로 재호출하게 하지 않는다.
|
||
|
||
- single-pass producer는 one logical data production;
|
||
- transport retry가 body replay를 요구하면 adapter-owned bounded spool 또는 explicit repeatable
|
||
producer factory가 있어야 한다;
|
||
- spool은 private filesystem/object, quota, encryption, cleanup을 갖춘 별도 optional 전략;
|
||
- repeatability가 없으면 mutation을 reconcile하거나 new operation을 요구한다;
|
||
- input servlet stream을 재사용 가능하다고 가정하지 않는다.
|
||
|
||
## 29. Reconciliation, cleanup, LIST
|
||
|
||
### 29.1 Reconciler
|
||
|
||
Reconciler는:
|
||
|
||
- exact operation ID/reference/session으로 lookup;
|
||
- state/version CAS lease;
|
||
- bounded batch/deadline;
|
||
- provider exact HEAD/GET;
|
||
- normalized evidence comparison;
|
||
- deterministic continuation/compensation;
|
||
- terminal receipt restoration;
|
||
- audit/metric
|
||
|
||
을 수행한다.
|
||
|
||
Application retry path의 inline resolve와 background worker가 같은 kernel을 공유한다.
|
||
|
||
### 29.2 Candidate discovery
|
||
|
||
Discovery source:
|
||
|
||
- durable queue/outbox;
|
||
- deterministic age-partitioned control prefix;
|
||
- bounded LIST with continuation cursor;
|
||
- retry/dead-letter registry.
|
||
|
||
LIST의 문제:
|
||
|
||
- large namespace 비용;
|
||
- pagination;
|
||
- concurrent add/delete;
|
||
- provider별 ordering/consistency;
|
||
- permission 제한.
|
||
|
||
따라서 LIST는 후보를 놓치지 않도록 반복하는 maintenance 수단이며 단일 object truth가 아니다.
|
||
|
||
### 29.3 Cleanup eligibility
|
||
|
||
삭제 전 모두 만족:
|
||
|
||
- owned namespace;
|
||
- supported schema;
|
||
- operation terminal/expired 상태;
|
||
- minimum age;
|
||
- active lease/fence 없음;
|
||
- published reference가 가리키지 않음;
|
||
- active application handoff claim 없음;
|
||
- verified/staged business object이면 application이 DB/outbox를 조회한 뒤 발급한 exact
|
||
`ObjectAbortAuthorization`;
|
||
- provider retention/legal hold 없음;
|
||
- exact version precondition;
|
||
- report-only 결과와 delete 계획 audit.
|
||
|
||
Age, LIST 부재, expired worker lease만으로 `ObjectAbortAuthorization`을 대체하지 않는다.
|
||
Application 확인이 불가능하면 destructive cleanup을 보류한다.
|
||
|
||
### 29.4 Cleanup mode
|
||
|
||
- `disabled`;
|
||
- `report-only`;
|
||
- `delete`.
|
||
|
||
Production 첫 활성화는 report-only 기간과 샘플 검토 뒤 delete로 전환한다. Mode 변경은 audit하고
|
||
blast-radius limit를 둔다.
|
||
|
||
### 29.5 Unknown/newer object
|
||
|
||
Unknown prefix, malformed record, newer schema, missing ownership evidence는:
|
||
|
||
- 삭제하지 않는다;
|
||
- metric/audit/alert;
|
||
- quarantine candidate report;
|
||
- manual or upgraded reconciler 대상.
|
||
|
||
### 29.6 Notifications
|
||
|
||
Provider object event는 reconciliation을 빠르게 하는 hint일 수 있다.
|
||
|
||
- event 중복/순서 뒤바뀜/유실을 허용;
|
||
- exact state lookup 후 처리;
|
||
- event payload를 truth로 사용하지 않음;
|
||
- inbound event verification은 inbound/messaging adapter 소유;
|
||
- objectstorage outbound leaf가 messaging sibling adapter를 의존하지 않음.
|
||
|
||
## 30. Versioning, retention, retirement, purge
|
||
|
||
### 30.1 Lifecycle 용어
|
||
|
||
- detach: business entity가 reference를 더 이상 사용하지 않음;
|
||
- retire: reference를 download/publication 대상에서 제거;
|
||
- delete marker: provider current view 변경;
|
||
- purge: exact physical object version 제거;
|
||
- expire: policy age 도달;
|
||
- held: retention/legal hold로 purge 금지.
|
||
|
||
용어를 섞지 않는다.
|
||
|
||
### 30.2 Immutable replacement
|
||
|
||
Replace는:
|
||
|
||
1. 새 immutable object/reference publish;
|
||
2. DB CAS로 새 reference 선택;
|
||
3. old reference retire;
|
||
4. retention 후 old exact version purge.
|
||
|
||
같은 key overwrite가 아니다.
|
||
|
||
### 30.3 Retention source
|
||
|
||
Retention은:
|
||
|
||
- business minimum;
|
||
- security/quarantine;
|
||
- audit/control record;
|
||
- provider lifecycle;
|
||
- legal hold
|
||
|
||
의 합성이다. 가장 긴/강한 requirement를 적용한다. Application이 짧은 TTL을 보내 provider
|
||
compliance retention을 줄일 수 없다.
|
||
|
||
### 30.4 Physical purge
|
||
|
||
- exact object version/token;
|
||
- current reference graph 확인;
|
||
- retention/hold 조회;
|
||
- conditional delete;
|
||
- delete response loss reconcile;
|
||
- versioned provider에서 delete marker와 version delete 구분;
|
||
- manifest/control record tombstone 보존;
|
||
- audit/replay/retention horizon 뒤에도 §11.4의 epoch seal/rejection record protocol을 거쳐
|
||
control record를 정리.
|
||
|
||
AWS versioned bucket에서 noncurrent version의 exact purge identity는 `versionId`다. `If-Match`가
|
||
current version에 대해 평가되는 의미를 noncurrent-version CAS로 일반화하지 않는다.
|
||
|
||
- unversioned/current object: provider가 증명한 ETag `If-Match`와 exact key;
|
||
- noncurrent version: exact `versionId`, reference graph/retention fence, provider-specific delete
|
||
evidence;
|
||
- delete marker: marker version ID를 별도 type으로 구분.
|
||
|
||
Provider가 concurrent safety를 증명하지 못하면 purge card를 `UNVERIFIABLE`로 둔다.
|
||
|
||
### 30.5 Quarantine retention
|
||
|
||
Malicious object는 즉시 public visibility에서 격리하지만 physical delete 시점은 security/audit
|
||
policy가 결정한다. Scanner verdict evidence와 content 접근 권한을 최소화한다.
|
||
|
||
## 31. Observability
|
||
|
||
### 31.1 Metrics
|
||
|
||
예시:
|
||
|
||
- `object_storage_operation_total{destination,provider,operation,outcome}`;
|
||
- `object_storage_operation_duration_seconds{destination,provider,operation}`;
|
||
- `object_storage_bytes_total{destination,provider,direction,outcome}`;
|
||
- `object_storage_inflight{destination,provider,operation}`;
|
||
- `object_storage_sdk_attempt_total{provider,operation,outcome}`;
|
||
- `object_storage_reconciliation_total{provider,resolution}`;
|
||
- `object_storage_indeterminate_current{destination,provider,operation}`;
|
||
- `object_storage_orphan_candidate_current{destination,provider,type}`;
|
||
- `object_storage_multipart_session_current{destination,provider,state}`;
|
||
- `object_storage_cleanup_total{provider,mode,outcome}`;
|
||
- `object_storage_credential_refresh_total{provider,outcome}`;
|
||
- `object_storage_capability_mismatch_total{provider,capability}`;
|
||
- `object_storage_buffer_bytes{provider,direction}`;
|
||
- `object_storage_pool_pending_acquire{provider}`.
|
||
|
||
허용 tag:
|
||
|
||
- logical destination;
|
||
- exact provider type;
|
||
- operation family;
|
||
- normalized outcome/error;
|
||
- capability/card revision의 bounded token.
|
||
|
||
금지 tag:
|
||
|
||
- bucket/key/path;
|
||
- object/reference/operation raw ID;
|
||
- tenant/user;
|
||
- filename/media metadata;
|
||
- endpoint;
|
||
- presigned URL;
|
||
- provider request ID.
|
||
|
||
### 31.2 Trace
|
||
|
||
한 logical operation span 아래:
|
||
|
||
- binding;
|
||
- admission;
|
||
- producer/consumer;
|
||
- provider attempt;
|
||
- integrity verify;
|
||
- control transition;
|
||
- scan wait;
|
||
- reconciliation
|
||
|
||
span을 둔다. Content와 URL을 attribute로 넣지 않는다. Provider request ID가 필요하면 bounded hash와
|
||
restricted debug log로만 연결한다.
|
||
|
||
### 31.3 Logging
|
||
|
||
- terminal transition과 operator action은 structured info/audit;
|
||
- transient attempt는 rate-limited debug;
|
||
- payload/body, URL, credential, raw key 금지;
|
||
- exception message sanitize;
|
||
- same operation retry log storm 억제;
|
||
- cleanup report는 bounded sample + aggregate count.
|
||
|
||
### 31.4 Health
|
||
|
||
- liveness는 object storage를 호출하지 않는다;
|
||
- readiness는 required destination/card만 평가한다;
|
||
- optional destination 장애는 degraded로 노출하되 전체 readiness 정책은 composition에서 결정;
|
||
- health request마다 bucket/list/write를 하지 않는다;
|
||
- cached qualification + low-rate sentinel probe;
|
||
- dependency outage, credential refresh, control corruption, capacity saturation을 구분.
|
||
|
||
### 31.5 SLO
|
||
|
||
Provider/card별:
|
||
|
||
- managed upload success/latency;
|
||
- published download success/latency;
|
||
- direct completion verification latency;
|
||
- indeterminate resolution age;
|
||
- orphan backlog age;
|
||
- multipart abandon age;
|
||
- scan pending age;
|
||
- credential expiry horizon
|
||
|
||
를 정의한다. Provider SDK success rate만으로 business publication SLO를 계산하지 않는다.
|
||
|
||
## 32. Readiness card
|
||
|
||
### 32.1 Card ID
|
||
|
||
한 module card가 아니라 capability별 card:
|
||
|
||
- `object-storage-managed-upload-single`;
|
||
- `object-storage-managed-upload-multipart`;
|
||
- `object-storage-managed-download`;
|
||
- `object-storage-direct-upload-single`;
|
||
- `object-storage-direct-upload-multipart`;
|
||
- `object-storage-direct-download`;
|
||
- `object-storage-quarantine-publication`;
|
||
- `object-storage-retention`;
|
||
- `object-storage-reconciliation`.
|
||
|
||
Registry entry는 exact provider type/version과 destination profile을 dimension으로 가진다.
|
||
|
||
### 32.2 R0
|
||
|
||
- application port/value object compile;
|
||
- adapter/provider seam compile;
|
||
- settings disabled by default;
|
||
- no framework/provider type leak;
|
||
- unit tests for validation/fingerprint/state.
|
||
|
||
### 32.3 R1
|
||
|
||
- local-dev and version-pinned MinIO functional topology;
|
||
- bounded managed stream;
|
||
- exact head/range;
|
||
- checksum happy/mismatch;
|
||
- immutable create/conflict;
|
||
- control record codec/CAS basic;
|
||
- lifecycle/close tests;
|
||
- sample consumer contract.
|
||
|
||
R1은 production security/failure recovery를 뜻하지 않는다.
|
||
|
||
### 32.4 R2
|
||
|
||
모든 R2 card의 공통 evidence:
|
||
|
||
- production-like TLS/credential/ownership/encryption;
|
||
- startup qualification;
|
||
- connection/DNS/TLS/read/write timeout;
|
||
- throttling and retry amplification;
|
||
- request/response loss;
|
||
- process kill/crash recovery;
|
||
- rolling schema compatibility;
|
||
- observability/redaction;
|
||
- no silent test skip.
|
||
|
||
Card별 추가 evidence:
|
||
|
||
| Card | Required evidence |
|
||
| --- | --- |
|
||
| managed-upload-single | bounded producer/heap, immutable create, checksum, hard size, single-put response-loss reconciliation |
|
||
| managed-upload-multipart | adapter-owned session/upload ID/part ledger, bounded buffers/parts, complete/abort/409/response-loss, orphan cleanup, full digest |
|
||
| managed-download | exact published version, full/range read, bounded consumer/heap, truncated/slow/failed consumer, response-body close |
|
||
| direct-upload-single | atomic create-only, provider-enforced hard size ceiling, signed checksum/header, expiry/CORS/public endpoint, completion HEAD/verification |
|
||
| direct-upload-multipart | session/part ledger, CORS exposed evidence, acknowledge handshake, part budget, complete/abort/409/response-loss, orphan cleanup |
|
||
| direct-download | authorization-before-grant, published exact version, expiry/signature-age, response headers, URL redaction/revocation limitation |
|
||
| quarantine-publication | unpublished narrow read, scanner fence/policy, clean/malicious/indeterminate, handoff/DB crash gaps, cleanup authorization race |
|
||
| retention | versioning, exact version purge, delete marker, lifecycle, retention/legal hold, privileged purge separation |
|
||
| reconciliation | pending-effect phase, authoritative absence, conditional race/CAS, process kill, replay tombstone, backup/restore reconciliation |
|
||
|
||
Card가 요구하지 않는 multipart, presign, retention evidence를 억지로 요구하지 않는다. 반대로 다른
|
||
card의 evidence를 가져와 해당 card가 준비됐다고 주장하지 않는다. Exact named operation profile
|
||
조합이 matrix의 모든 relevant evidence를 만족해야 한다.
|
||
|
||
### 32.5 R3
|
||
|
||
- multi-node failover/fencing;
|
||
- rolling provider/application upgrade;
|
||
- regional/cluster disaster recovery;
|
||
- restore ordering and integrity audit;
|
||
- sustained scale/soak;
|
||
- quota/capacity exhaustion;
|
||
- credential/KMS rotation under load;
|
||
- provider version upgrade/rollback;
|
||
- operational game day and runbook evidence.
|
||
|
||
### 32.6 Card claim rule
|
||
|
||
Readiness claim은 다음 형태다.
|
||
|
||
```text
|
||
card + provider exact type/version + destination profile + evidence revision
|
||
```
|
||
|
||
예:
|
||
|
||
```text
|
||
object-storage-managed-upload-single
|
||
/ aws-s3-general-purpose
|
||
/ poster-image-v1
|
||
/ R2
|
||
/ evidence-2026-08-...
|
||
```
|
||
|
||
“Objectstorage R2”처럼 범위를 생략한 표현은 금지한다.
|
||
|
||
## 33. Test strategy
|
||
|
||
### 33.1 Application-core unit/property
|
||
|
||
- ID/reference syntax and check digits;
|
||
- operation key/epoch seal/rejection/rotation;
|
||
- canonical request fingerprint golden vector;
|
||
- content digest representation;
|
||
- state transition table;
|
||
- same operation/same fingerprint;
|
||
- same operation/different fingerprint;
|
||
- range arithmetic/overflow;
|
||
- deadline/budget validation;
|
||
- typed error/outcome exhaustiveness;
|
||
- framework/AWS type absence.
|
||
|
||
### 33.2 Provider-neutral contract
|
||
|
||
모든 qualifying provider에 같은 semantic suite:
|
||
|
||
- empty/one-byte/chunk-boundary/maximum-size upload;
|
||
- immutable create and conflict;
|
||
- exact descriptor/version;
|
||
- full and range read;
|
||
- short/slow/failing producer;
|
||
- slow/failing consumer;
|
||
- checksum match/mismatch;
|
||
- cancel/resource close;
|
||
- same operation terminal replay;
|
||
- different fingerprint conflict;
|
||
- conditional retire/delete;
|
||
- indeterminate resolve;
|
||
- control schema compatibility.
|
||
|
||
Provider unsupported capability test는 silent skip 대신 descriptor와 expected `UNSUPPORTED`를
|
||
검증한다.
|
||
|
||
### 33.3 Filesystem
|
||
|
||
- traversal/root alias/absolute path/Unicode ambiguity;
|
||
- symlink swap and nested symlink;
|
||
- exclusive create race;
|
||
- process kill before/after force/publish/control CAS;
|
||
- disk full, inode exhaustion, permission denied, read-only mount;
|
||
- mount identity mismatch;
|
||
- cleanup unknown/newer schema;
|
||
- file descriptor leak;
|
||
- restrictive permission.
|
||
|
||
### 33.4 MinIO
|
||
|
||
- version-pinned Testcontainers;
|
||
- actual streaming;
|
||
- conditional create/control CAS;
|
||
- multipart part/complete/abort;
|
||
- presigned PUT/GET and signed headers;
|
||
- grant response-loss/reissue/retirement linearization;
|
||
- checksum/head;
|
||
- versioning/lifecycle/retention where card claims;
|
||
- network cut via Toxiproxy;
|
||
- process restart;
|
||
- concurrent operations;
|
||
- endpoint/path-style/public presign;
|
||
- no Docker이면 selected readiness task가 성공으로 끝나지 않음.
|
||
|
||
Developer fast test는 Docker 없이 skip할 수 있지만 readiness task는 required environment 부재를
|
||
failure로 처리한다.
|
||
|
||
### 33.5 AWS sandbox
|
||
|
||
AWS-only R2 evidence:
|
||
|
||
- actual account/region/bucket owner;
|
||
- TLS/VPC endpoint if used;
|
||
- workload role/temporary credential refresh;
|
||
- Block Public Access/object ownership;
|
||
- SSE-KMS/DSSE profile;
|
||
- conditional write/delete;
|
||
- strong consistency assumption test boundary;
|
||
- versioning/delete marker/noncurrent purge;
|
||
- lifecycle/incomplete multipart;
|
||
- Object Lock/retention optional card;
|
||
- KMS and S3 throttling;
|
||
- credential/permission revocation;
|
||
- presigned URL expiration and signed checksum;
|
||
- request IDs captured safely.
|
||
|
||
Sandbox resource provisioning/cleanup은 IaC와 unique namespace를 사용한다.
|
||
|
||
### 33.6 Fault/concurrency
|
||
|
||
- two writers same operation/same fingerprint;
|
||
- same operation/different fingerprint;
|
||
- two new operations replacing same attachment;
|
||
- DB UploadIntent reserve/pending/abort fence race;
|
||
- direct grant response loss, multiple outstanding generations, expiry/in-flight horizon;
|
||
- same multipart part reissue, stale late request, acknowledge/complete race;
|
||
- operation epoch multi-node cutover/seal/rollback;
|
||
- response dropped after put/complete/delete/reference CAS;
|
||
- DB crash at every §15 gap;
|
||
- worker lease expiry and takeover;
|
||
- cleanup versus late finalize;
|
||
- retention activated during purge;
|
||
- scanner delayed/duplicate/stale verdict;
|
||
- rolling old/new control schema.
|
||
|
||
### 33.7 Performance/resource
|
||
|
||
- object size가 커져도 heap이 size와 선형 증가하지 않음;
|
||
- configured chunk/pool/multipart aggregate bound;
|
||
- direct memory/FD/thread stability;
|
||
- slow producer/consumer;
|
||
- connection pool saturation;
|
||
- retry storm;
|
||
- large concurrent range download;
|
||
- graceful shutdown with in-flight operation;
|
||
- long soak with cleanup/reconciliation.
|
||
|
||
### 33.8 Security/config
|
||
|
||
- disabled zero-side-effect context;
|
||
- canonical/legacy conflict;
|
||
- secret redaction;
|
||
- invalid endpoint/owner/credential pair;
|
||
- no plaintext production endpoint;
|
||
- no auto-create;
|
||
- unsupported required capability startup failure;
|
||
- presigned URL absent from logs/traces;
|
||
- raw key/path/URI absent from public receipt;
|
||
- ACL/public access configuration mismatch;
|
||
- malicious filename/metadata/control character.
|
||
|
||
## 34. Gradle, CI, supply chain
|
||
|
||
### 34.1 Focused task
|
||
|
||
현재 owner leaf:
|
||
|
||
```bash
|
||
cd src
|
||
./gradlew :adapter:outbound:objectstorage:test --console=plain
|
||
```
|
||
|
||
향후 task 후보:
|
||
|
||
```text
|
||
:adapter:outbound:objectstorage:objectStorageUnitTest
|
||
:adapter:outbound:objectstorage:objectStorageFilesystemContractTest
|
||
:adapter:outbound:objectstorage:objectStorageMinioContractTest
|
||
:adapter:outbound:objectstorage:objectStorageMinioFaultTest
|
||
:adapter:outbound:objectstorage:objectStorageAwsQualificationTest
|
||
:adapter:outbound:objectstorage:objectStorageSecurityTest
|
||
:adapter:outbound:objectstorage:objectStorageResourceTest
|
||
```
|
||
|
||
Task 이름과 exact selected provider/card를 machine-readable readiness registry에 연결한다.
|
||
|
||
### 34.2 CI lanes
|
||
|
||
- PR fast: unit, architecture, filesystem, mocked mapping;
|
||
- PR container: MinIO common contract;
|
||
- scheduled fault: MinIO/Toxiproxy/process restart;
|
||
- protected AWS: sandbox qualification/security;
|
||
- release gate: required production cards, schema compatibility, supply-chain scan;
|
||
- soak/game day: R3 evidence.
|
||
|
||
### 34.3 Architecture verification
|
||
|
||
필수:
|
||
|
||
```bash
|
||
./gradlew verifyCleanArchitectureDependencies
|
||
./gradlew test
|
||
./gradlew check
|
||
```
|
||
|
||
추가 ArchUnit/compile checks:
|
||
|
||
- `application-core`에 `software.amazon.awssdk`, Spring, `Path`, inbound type 없음;
|
||
- objectstorage leaf가 sibling adapter/sample/bootstrap을 의존하지 않음;
|
||
- controller가 repository/SDK/persistence entity를 사용하지 않음;
|
||
- raw inbound DTO가 application/domain으로 유출되지 않음.
|
||
|
||
### 34.4 Dependency
|
||
|
||
- AWS SDK BOM/lock exact pin;
|
||
- async HTTP implementation 선택과 transitive dependency review;
|
||
- CRT 사용 시 native binary provenance/SBOM;
|
||
- CVE/license scan;
|
||
- checksum/crypto provider 정책;
|
||
- Testcontainers/MinIO image digest pin;
|
||
- dependency update 후 provider qualification 재실행;
|
||
- unused provider dependency를 runtime image에서 제거할 필요가 생기면 leaf split 검토.
|
||
|
||
## 35. Migration plan
|
||
|
||
### Phase 0 — Truth and characterization
|
||
|
||
변경:
|
||
|
||
- 현재 CRUD/overwrite/default activation/sample transaction을 characterization test;
|
||
- existing settings/env/runtime usage inventory;
|
||
- public `file://`/`s3://` response 소비자 확인;
|
||
- current MinIO test를 R1로 명명.
|
||
|
||
Acceptance:
|
||
|
||
- 현재 동작과 위험이 evidence로 고정;
|
||
- production readiness claim 없음.
|
||
|
||
Rollback:
|
||
|
||
- code behavior change 없음.
|
||
|
||
### Phase 1 — Framework-free semantic contract
|
||
|
||
변경:
|
||
|
||
- ID/reference/version/digest/range/error/outcome;
|
||
- streaming callback;
|
||
- managed publication/inspection/transfer port;
|
||
- existing port는 deprecated compatibility seam;
|
||
- default disabled settings skeleton.
|
||
|
||
Acceptance:
|
||
|
||
- application-core pure unit/architecture test;
|
||
- byte[] path를 새 business flow가 사용하지 않음;
|
||
- provider type leak 0.
|
||
|
||
Rollback:
|
||
|
||
- old port consumer 유지, 새 binding disabled.
|
||
|
||
### Phase 2 — Provider-neutral kernel and local R1
|
||
|
||
변경:
|
||
|
||
- key/reference/control codec;
|
||
- operation fingerprint/state machine;
|
||
- local-dev provider;
|
||
- bounded stream/checksum/immutable create;
|
||
- single-node reconciliation;
|
||
- disabled zero-side-effect composition.
|
||
|
||
Acceptance:
|
||
|
||
- provider-neutral contract;
|
||
- filesystem security/crash tests;
|
||
- R1 card만 게시.
|
||
|
||
Rollback:
|
||
|
||
- new capability disabled, old local example 유지.
|
||
|
||
### Phase 3 — S3 managed transfer common subset
|
||
|
||
변경:
|
||
|
||
- async S3 client;
|
||
- finite timeout/pool/retry;
|
||
- streaming put/get/head/range;
|
||
- conditional data/control operation;
|
||
- checksum/encryption/version evidence;
|
||
- exact AWS/MinIO provider package 분리.
|
||
|
||
Acceptance:
|
||
|
||
- MinIO contract/fault R1;
|
||
- AWS sandbox common-subset partial target evidence, Phase 6 전 R2 card claim 금지;
|
||
- heap/resource bound.
|
||
|
||
Rollback:
|
||
|
||
- destination provider binding을 qualified previous provider로 전환;
|
||
- operation/reference schema backward-readable.
|
||
|
||
### Phase 4 — Direct transfer and multipart
|
||
|
||
변경:
|
||
|
||
- presigner;
|
||
- opaque session/part token;
|
||
- multipart ledger/complete/abort/reconcile;
|
||
- public signing endpoint;
|
||
- browser POST optional.
|
||
|
||
Acceptance:
|
||
|
||
- URL/header/expiry/redaction;
|
||
- complete response-loss;
|
||
- orphan cleanup/lifecycle backstop;
|
||
- provider/card exact evidence.
|
||
|
||
Rollback:
|
||
|
||
- direct card disabled;
|
||
- managed server upload/download 유지;
|
||
- existing sessions drain/expire/reconcile.
|
||
|
||
### Phase 5 — Staged scan/publication and sample migration
|
||
|
||
변경:
|
||
|
||
- staged port;
|
||
- scan verdict seam;
|
||
- durable reference publish;
|
||
- Poster pending/ready/retired state;
|
||
- short DB transaction/outbox worker;
|
||
- public DTO opaque reference.
|
||
|
||
Acceptance:
|
||
|
||
- every crash gap test;
|
||
- unscanned object inaccessible;
|
||
- concurrent replacement deterministic;
|
||
- old object retirement;
|
||
- no raw locator exposure.
|
||
|
||
Rollback:
|
||
|
||
- new upload admission 중지;
|
||
- pending operation drain/reconcile;
|
||
- existing published reference reader 유지;
|
||
- old API 제거 전 dual-read compatibility.
|
||
|
||
### Phase 6 — Production security and maintenance R2
|
||
|
||
변경:
|
||
|
||
- exact settings/qualification;
|
||
- workload credential/expected owner/TLS;
|
||
- ownership/BPA/encryption/versioning/lifecycle;
|
||
- reaper/report-only/delete;
|
||
- exact reconciliation card를 위한 disposable namespace bounded backup/restore reconciliation;
|
||
- readiness/metrics/audit/runbooks.
|
||
|
||
Acceptance:
|
||
|
||
- exact required card R2;
|
||
- protected fault/security tests;
|
||
- cleanup report review;
|
||
- no false global claim.
|
||
|
||
Rollback:
|
||
|
||
- maintenance delete -> report-only;
|
||
- provider/card admission off;
|
||
- published reads 유지;
|
||
- manual reconciliation queue 보존.
|
||
|
||
### Phase 7 — R3 and split review
|
||
|
||
변경:
|
||
|
||
- multi-node fencing;
|
||
- rolling upgrade, regional/cluster disaster-recovery game day, restore under failover, scale;
|
||
- provider leaf split decision;
|
||
- optional retention/direct/high-throughput cards.
|
||
|
||
Acceptance:
|
||
|
||
- R3 evidence and game day;
|
||
- split/no-split ADR;
|
||
- restore integrity audit.
|
||
|
||
Rollback:
|
||
|
||
- optional card disable;
|
||
- last qualified provider/schema write version.
|
||
|
||
## 36. Existing API compatibility and removal
|
||
|
||
### 36.1 Legacy port
|
||
|
||
기존 `ObjectStoragePort`는 migration 동안:
|
||
|
||
- `@Deprecated`와 explicit legacy name;
|
||
- production profile disabled;
|
||
- separate legacy root/bucket prefix;
|
||
- public URI 반환을 신규 API가 재사용하지 않음;
|
||
- usage metric;
|
||
- removal deadline
|
||
|
||
을 가진다.
|
||
|
||
Legacy와 new namespace가 겹치면 startup 실패한다.
|
||
|
||
### 36.2 Existing data
|
||
|
||
Raw key를 저장한 Poster data migration:
|
||
|
||
1. raw key inventory;
|
||
2. object exact HEAD/digest/media/size;
|
||
3. immutable new reference manifest 생성;
|
||
4. DB row를 opaque reference로 CAS migration;
|
||
5. dual-read 기간;
|
||
6. public response에서 locator 제거;
|
||
7. legacy object retirement;
|
||
8. unknown/missing/corrupt data report.
|
||
|
||
Migration이 existing key를 무조건 rename/copy/delete하지 않는다. Provider/version/retention에 따라
|
||
별도 plan을 생성한다.
|
||
|
||
### 36.3 API contract
|
||
|
||
기존 upload response의 `key`/`location` 제거는 breaking change다.
|
||
|
||
새 response 후보:
|
||
|
||
```json
|
||
{
|
||
"reference": "osr1....",
|
||
"size": 12345,
|
||
"mediaType": "image/png",
|
||
"digest": {
|
||
"algorithm": "SHA-256",
|
||
"value": "..."
|
||
},
|
||
"state": "READY"
|
||
}
|
||
```
|
||
|
||
실제 public field/version은 inbound API 설계와 snapshot test로 승인한다. Presigned URL은 별도
|
||
authorization endpoint의 ephemeral response이며 stored object DTO에 영구 포함하지 않는다.
|
||
|
||
## 37. 구현 계획 작성 전 확정 항목
|
||
|
||
정본 구현 순서와 아래 결정의 현재 freeze/approval gate는
|
||
[Object Storage Production Capability Implementation Plan](../plans/2026-07-28-objectstorage-production-capability.md)에
|
||
기록한다. Tasks 1–24의 구현은 Batch E pause checkpoint까지 진행됐지만 Batch E 전체
|
||
combined/root gate는 아직 통과하지 않았다. Tasks 25–30은 미착수다. 이 구현 진행이나
|
||
계획 승인이 public API, scanner provider 또는 AWS/IaC 외부 변경 권한을 자동으로 부여하지
|
||
않는다.
|
||
|
||
구현 계획은 다음 결정을 task 단위로 명시해야 한다.
|
||
|
||
- exact Java type/package 이름;
|
||
- old/new port coexistence 기간;
|
||
- control record serialization format;
|
||
- conditional CAS primitive;
|
||
- reference text format과 check digit;
|
||
- async S3 HTTP implementation;
|
||
- approved AWS SDK version;
|
||
- local-dev root;
|
||
- exact AWS/MinIO test versions;
|
||
- application deadline/cancellation representation;
|
||
- scanner capability owner와 port;
|
||
- sample Poster schema/outbox migration;
|
||
- bootstrap registry edge 필요 여부;
|
||
- canonical env/settings/secrets registry 변경;
|
||
- machine-readable readiness registry schema;
|
||
- cleanup lease/fence;
|
||
- API breaking-change versioning.
|
||
|
||
이 중 architecture 또는 public contract를 바꾸는 선택은 brainstorming/설계 승인 없이 구현
|
||
task에서 임의 결정하지 않는다.
|
||
|
||
## 38. Completion criteria
|
||
|
||
### 38.1 Design complete
|
||
|
||
- 현재 코드와 sample workflow의 증거가 기록됨;
|
||
- alternatives와 selected architecture가 기록됨;
|
||
- port/identity/state/protocol/provider/config/security/test/migration 결정이 연결됨;
|
||
- official primary references로 변동 가능한 provider 의미가 뒷받침됨;
|
||
- 상위 설계에서 dedicated design으로 링크됨;
|
||
- 독립 리뷰에서 blocker/high가 해소됨;
|
||
- 문서 검증과 LLM Wiki capture 또는 차단 사유가 기록됨.
|
||
|
||
### 38.2 Implementation complete
|
||
|
||
다음이 모두 있어야 하며 이 문서 작성만으로 충족되지 않는다.
|
||
|
||
- approved implementation plan;
|
||
- test-first code;
|
||
- focused/common/full architecture verification;
|
||
- exact provider/card readiness evidence;
|
||
- sample workflow migration;
|
||
- settings/env/secrets/runtime docs;
|
||
- runbook and observability;
|
||
- Wiki branch-note;
|
||
- human code review.
|
||
|
||
### 38.3 R2 complete
|
||
|
||
Provider/card별 §32.4 evidence와 production-like qualification이 있어야 한다. MinIO byte[] happy
|
||
path, mocked AWS test, filesystem unit test만으로는 R2가 아니다.
|
||
|
||
## 39. 금지하는 완료 표현
|
||
|
||
다음 표현은 해당 exact evidence 없이 사용하지 않는다.
|
||
|
||
- “ETag은 object MD5다.”
|
||
- “presigned URL은 한 번만 쓸 수 있다.”
|
||
- “S3-compatible이므로 AWS S3와 동일하다.”
|
||
- “PUT이 성공했으므로 DB와 object가 원자적으로 commit됐다.”
|
||
- “DB rollback이 object upload도 취소했다.”
|
||
- “versioning이 켜져 있으므로 삭제됐다.”
|
||
- “delete가 204라 physical version이 사라졌다.”
|
||
- “HEAD bucket이 성공했으므로 모든 capability가 준비됐다.”
|
||
- “TLS와 SSE를 켰으므로 secure하다.”
|
||
- “auto-create가 편리하므로 production에서도 안전하다.”
|
||
- “normalize/startsWith로 symlink 공격을 막았다.”
|
||
- “MinIO test가 통과했으므로 AWS production ready다.”
|
||
- “multipart complete timeout이므로 적용되지 않았다.”
|
||
- “retry하면 정확히 한 번 upload된다.”
|
||
- “LIST에 없으므로 object가 없다.”
|
||
- “scan timeout이므로 clean으로 간주한다.”
|
||
- “direct upload 완료 callback을 받았으므로 검증됐다.”
|
||
- “objectstorage module이 R2다.”
|
||
|
||
## 40. 운영 runbook 요구
|
||
|
||
- required destination startup qualification mismatch;
|
||
- wrong bucket/account/region/endpoint;
|
||
- credential expiry/refresh/rotation;
|
||
- KMS deny/throttle/key disabled;
|
||
- TLS/certificate/DNS/VPC endpoint failure;
|
||
- connection pool/acquire saturation;
|
||
- managed upload/download timeout;
|
||
- checksum mismatch/corrupt object;
|
||
- scan backlog/scanner outage/malicious verdict;
|
||
- publication indeterminate;
|
||
- control record corruption/newer schema;
|
||
- multipart complete indeterminate;
|
||
- abandoned multipart growth;
|
||
- orphan/staged/quarantined backlog;
|
||
- cleanup report-only에서 delete 전환;
|
||
- cleanup wrong-scope kill switch;
|
||
- versioning suspended/delete marker growth;
|
||
- retention/legal hold;
|
||
- presigned URL leak;
|
||
- grant response loss/reissue exposure;
|
||
- public endpoint/CORS drift;
|
||
- MinIO node/restart/upgrade;
|
||
- AWS regional/provider outage;
|
||
- disk/inode/mount loss for filesystem provider;
|
||
- graceful shutdown with in-flight operations;
|
||
- database pending attachment backlog;
|
||
- UploadIntent/handoff fence stuck or abort race;
|
||
- operation epoch rotation/seal/compaction stuck;
|
||
- backup/restore 후 reference/data/control reconciliation;
|
||
- operation/reference lookup hot partition;
|
||
- provider SDK upgrade rollback;
|
||
- readiness card downgrade.
|
||
|
||
각 runbook은:
|
||
|
||
- detection signal;
|
||
- safe first action;
|
||
- admission/maintenance kill switch;
|
||
- evidence collection;
|
||
- reconciliation command;
|
||
- destructive step의 dry-run/report-only;
|
||
- rollback;
|
||
- incident/audit link
|
||
|
||
를 포함한다.
|
||
|
||
## 41. Primary references
|
||
|
||
### AWS S3 semantics
|
||
|
||
- [Amazon S3 User Guide](https://docs.aws.amazon.com/AmazonS3/latest/userguide/)
|
||
- [S3 conditional writes](https://docs.aws.amazon.com/AmazonS3/latest/userguide/conditional-writes.html)
|
||
- [S3 conditional deletes](https://docs.aws.amazon.com/AmazonS3/latest/userguide/conditional-deletes.html)
|
||
- [DeleteObject API and conditional headers](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObject.html)
|
||
- [HeadObject API](https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html)
|
||
- [CompleteMultipartUpload API](https://docs.aws.amazon.com/AmazonS3/latest/API/API_CompleteMultipartUpload.html)
|
||
- [AbortMultipartUpload API](https://docs.aws.amazon.com/AmazonS3/latest/API/API_AbortMultipartUpload.html)
|
||
- [Checking object integrity](https://docs.aws.amazon.com/AmazonS3/latest/userguide/checking-object-integrity-upload.html)
|
||
- [Multipart upload overview](https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpuoverview.html)
|
||
- [Multipart upload limits](https://docs.aws.amazon.com/AmazonS3/latest/userguide/qfacts.html)
|
||
- [Abort a multipart upload](https://docs.aws.amazon.com/AmazonS3/latest/userguide/abort-mpu.html)
|
||
- [Abort incomplete multipart uploads with lifecycle](https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpu-abort-incomplete-mpu-lifecycle-config.html)
|
||
- [Presigned URL capabilities and limitations](https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html)
|
||
- [Signature V4 POST policy](https://docs.aws.amazon.com/AmazonS3/latest/developerguide/sigv4-HTTPPOSTConstructPolicy.html)
|
||
- [S3 versioning](https://docs.aws.amazon.com/AmazonS3/latest/userguide/Versioning.html)
|
||
- [S3 versioning enablement examples and propagation note](https://docs.aws.amazon.com/AmazonS3/latest/userguide/manage-versioning-examples.html)
|
||
- [S3 delete markers](https://docs.aws.amazon.com/AmazonS3/latest/userguide/DeleteMarker.html)
|
||
- [S3 Object Lock](https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lock.html)
|
||
- [S3 server-side encryption](https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingEncryption.html)
|
||
- [S3 SSE-KMS](https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingKMSEncryption.html)
|
||
- [S3 Object Ownership](https://docs.aws.amazon.com/AmazonS3/latest/userguide/about-object-ownership.html)
|
||
- [S3 security best practices](https://docs.aws.amazon.com/AmazonS3/latest/userguide/security-best-practices.html)
|
||
- [Expected bucket owner](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-owner-condition.html)
|
||
- [S3 network isolation and TLS](https://docs.aws.amazon.com/AmazonS3/latest/userguide/network-isolation.html)
|
||
|
||
### AWS SDK for Java 2.x
|
||
|
||
- [S3 asynchronous multipart client](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/s3-async-client-multipart.html)
|
||
- [S3 client examples and client comparison](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/examples-s3.html)
|
||
- [CRT-based S3 client](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/crt-based-s3-client.html)
|
||
- [S3 checksums](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/s3-checksums.html)
|
||
- [API timeout configuration](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/timeouts.html)
|
||
- [Retry strategy](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/retry-strategy.html)
|
||
- [HTTP client configuration](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/http-configuration.html)
|
||
- [SDK metrics](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/metrics.html)
|
||
- [SDK troubleshooting](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/troubleshooting.html)
|
||
- [SDK best practices](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/best-practices.html)
|
||
- [Default credentials provider chain](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/credentials-chain.html)
|
||
|
||
### MinIO AIStor future-card context and upload security
|
||
|
||
다음 MinIO 문서는 current AIStor product용이며 현재 OSS 2024 Testcontainers image의 readiness
|
||
evidence로 사용하지 않는다.
|
||
|
||
- [MinIO versioning](https://docs.min.io/aistor/administration/objects-and-versioning/versioning/)
|
||
- [MinIO object locking and immutability](https://docs.min.io/aistor/administration/object-locking-and-immutability/)
|
||
- [MinIO lifecycle rule patterns](https://docs.min.io/aistor/administration/object-lifecycle-management/lifecycle-rule-patterns/)
|
||
- [MinIO Java SDK API](https://docs.min.io/aistor/developers/sdk/java/api/)
|
||
- [OWASP File Upload Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html)
|