merge: integrate object storage production capability
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
# Object Storage Production Capability Implementation Plan
|
||||
|
||||
- 작성일: 2026-07-28
|
||||
- 상태: 구현 계획 작성 완료, 모든 task 미착수, R2 미구현
|
||||
- 상태: Batch A–D 및 Batch E pause checkpoint를 main에 통합, Batch E 전체 gate와 R2는 미완료
|
||||
- 범위: 상세 설계 Phase 0–6; Phase 7/R3는 별도 승인 계획
|
||||
- LLM Wiki capture: 정본 vault
|
||||
`/home/donghyeon/workspace/ai-tool/llm-wiki-private/` 부재로 차단; 비정본 clone 대체 사용 안 함
|
||||
- LLM Wiki capture: public vault의
|
||||
`raw/branch-notes/chore-main-worktree-capability-integration.md`에 main 통합 기록 완료
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILLS: use
|
||||
> `superpowers:subagent-driven-development` to execute independent tasks,
|
||||
@@ -43,6 +43,27 @@ provider-leaf split, a production malware-scanner implementation, and browser AP
|
||||
outside the approved contracts require follow-up plans. Writing this plan changes no runtime
|
||||
behavior and advances no readiness card.
|
||||
|
||||
**Implementation checkpoint (2026-07-28):** The Batch A–B application/kernel/local-provider
|
||||
baseline and the Batch C–D S3/MinIO managed/direct primitive slices are implemented and verified in
|
||||
`codex/objectstorage-production-capability`. Exact MinIO qualification proved that create-only PUT
|
||||
and multipart completion conditions are ignored, so its managed/direct mutation profiles remain
|
||||
`UNSUPPORTED`. AWS lanes compile only and have no observed evidence. Only the exact local managed
|
||||
single upload/download cards remain R1; every direct, multipart, quarantine, retention, and
|
||||
production reconciliation card remains R0. The exhaustive R2 fault/race matrices named by
|
||||
unchecked steps remain follow-up evidence, not implied completion. The legacy provider and Poster
|
||||
consumer remain isolated behind explicit sample-local/test activation. Evidence:
|
||||
[Batch A](../../evidence/object-storage/batch-a-checkpoint.md),
|
||||
[Batch B](../../evidence/object-storage/batch-b-checkpoint.md), and
|
||||
[Batch C](../../evidence/object-storage/batch-c-progress.md),
|
||||
[Batch D](../../evidence/object-storage/batch-d-checkpoint.md).
|
||||
|
||||
Batch E의 개별 focused fix 증거는
|
||||
[Batch E pause checkpoint](../../evidence/object-storage/batch-e-pause-checkpoint.md)에 기록됐다.
|
||||
당시 Batch E combined/root gate는 재실행되지 않았으므로 Batch E 전체 완료나 R2를 주장하지
|
||||
않는다. 격리 브랜치에서 잘못 선택했던 private vault 경로 대신, main 통합 작업은 사용자가
|
||||
지정한 public vault의 `raw/branch-notes/chore-main-worktree-capability-integration.md`에
|
||||
캡처했다.
|
||||
|
||||
---
|
||||
|
||||
## Execution rules
|
||||
@@ -154,7 +175,7 @@ Task 30; a batch is not closed if any output is missing.
|
||||
- Create:
|
||||
`docs/evidence/object-storage/phase-0-inventory.md`
|
||||
|
||||
- [ ] **Step 1: Record current executable behavior**
|
||||
- [x] **Step 1: Record current executable behavior**
|
||||
|
||||
Prove the current contract exactly as-is:
|
||||
|
||||
@@ -169,7 +190,7 @@ Prove the current contract exactly as-is:
|
||||
- the response exposes raw key/location;
|
||||
- Poster deletion does not retire the object.
|
||||
|
||||
- [ ] **Step 2: Verify the characterization baseline**
|
||||
- [x] **Step 2: Verify the characterization baseline**
|
||||
|
||||
Run:
|
||||
|
||||
@@ -185,7 +206,7 @@ cd src
|
||||
|
||||
Expected: PASS against the current implementation. This is a baseline, not R1/R2 evidence.
|
||||
|
||||
- [ ] **Step 3: Inventory runtime and data dependencies**
|
||||
- [x] **Step 3: Inventory runtime and data dependencies**
|
||||
|
||||
In the evidence document record command output, not an unsupported repository-wide conclusion:
|
||||
|
||||
@@ -204,7 +225,7 @@ Classify every hit as legacy runtime, test, documentation, stored-data schema, o
|
||||
Record whether real deployed consumers/data were inspected; if they were not, state that external
|
||||
inventory is still required.
|
||||
|
||||
- [ ] **Step 4: Run unchanged focused suites**
|
||||
- [x] **Step 4: Run unchanged focused suites**
|
||||
|
||||
Run:
|
||||
|
||||
@@ -232,7 +253,7 @@ Expected: PASS. No source behavior changes belong to this task.
|
||||
- Test:
|
||||
`src/application-core/src/test/java/dev/caskeleton/application/objectstorage/ObjectStorageIdentityContractTest.java`
|
||||
|
||||
- [ ] **Step 1: Add the approved test-only property engine**
|
||||
- [x] **Step 1: Add the approved test-only property engine**
|
||||
|
||||
Add `testImplementation 'net.jqwik:jqwik:1.9.1'`, matching the existing sample test version, and
|
||||
update only the application-core lockfile:
|
||||
@@ -245,7 +266,7 @@ cd src
|
||||
|
||||
Expected: PASS with test-only jqwik entries and no production dependency.
|
||||
|
||||
- [ ] **Step 2: Write the failing identity contract**
|
||||
- [x] **Step 2: Write the failing identity contract**
|
||||
|
||||
Test null/blank/control-character/oversize rejection, canonical round trips, operation-key
|
||||
composition, part range `1..10_000`, prefix separation, route-token grammar, reference check-digit
|
||||
@@ -253,7 +274,7 @@ tampering, and provider-locator non-disclosure. Include jqwik properties for arb
|
||||
input and a fixed golden vector for each prefix. Route existence/retirement is adapter binding
|
||||
state and is deliberately not tested or imported in `application-core`.
|
||||
|
||||
- [ ] **Step 3: Verify RED**
|
||||
- [x] **Step 3: Verify RED**
|
||||
|
||||
Run:
|
||||
|
||||
@@ -265,7 +286,7 @@ cd src
|
||||
|
||||
Expected: compilation failure because the new identity types do not exist.
|
||||
|
||||
- [ ] **Step 4: Implement the minimum values**
|
||||
- [x] **Step 4: Implement the minimum values**
|
||||
|
||||
Use immutable final classes or records with constructor validation. `ObjectOperationKey` contains
|
||||
exactly destination, epoch, and operation ID. Public reference/handle types expose canonical text
|
||||
@@ -274,7 +295,7 @@ validation in framework-free Java (`MessageDigest`). The adapter-owned codec in
|
||||
minting path from a retained route token plus generated `ObjectId`; application values do not
|
||||
consult a route registry.
|
||||
|
||||
- [ ] **Step 5: Verify GREEN**
|
||||
- [x] **Step 5: Verify GREEN**
|
||||
|
||||
Run the command from Step 3 and
|
||||
`./gradlew :application-core:verifyDependencyLocks --console=plain`. Expected: PASS.
|
||||
@@ -299,7 +320,7 @@ Run the command from Step 3 and
|
||||
- Test:
|
||||
`src/application-core/src/test/java/dev/caskeleton/application/objectstorage/ObjectStorageValueContractTest.java`
|
||||
|
||||
- [ ] **Step 1: Write failing callback and value tests**
|
||||
- [x] **Step 1: Write failing callback and value tests**
|
||||
|
||||
Prove:
|
||||
|
||||
@@ -313,7 +334,7 @@ Prove:
|
||||
- range offset/length arithmetic rejects zero, negative, overflow, and over-budget delivery;
|
||||
- media types are canonical, bounded, and contain no control characters.
|
||||
|
||||
- [ ] **Step 2: Verify RED**
|
||||
- [x] **Step 2: Verify RED**
|
||||
|
||||
Run:
|
||||
|
||||
@@ -326,14 +347,14 @@ cd src
|
||||
|
||||
Expected: compilation failure because the callback/value types do not exist.
|
||||
|
||||
- [ ] **Step 3: Implement the minimum contract**
|
||||
- [x] **Step 3: Implement the minimum contract**
|
||||
|
||||
Reuse `CallBudget`; do not create serializable wall-clock deadlines. Callback types may declare only
|
||||
application exceptions and Java primitive/array types. Do not expose `InputStream`, `OutputStream`,
|
||||
`ByteBuffer`, Reactor, Flow, servlet, Spring, or AWS types. Document that callbacks are synchronous
|
||||
and blocking and that adapters must not invoke application callbacks on SDK event-loop threads.
|
||||
|
||||
- [ ] **Step 4: Verify GREEN**
|
||||
- [x] **Step 4: Verify GREEN**
|
||||
|
||||
Run the command from Step 2. Expected: PASS.
|
||||
|
||||
@@ -376,7 +397,7 @@ Run the command from Step 2. Expected: PASS.
|
||||
- Test:
|
||||
`src/application-core/src/test/java/dev/caskeleton/application/objectstorage/ObjectStoragePortContractTest.java`
|
||||
|
||||
- [ ] **Step 1: Write the failing port-shape contract**
|
||||
- [x] **Step 1: Write the failing port-shape contract**
|
||||
|
||||
Use reflection and construction tests to prove:
|
||||
|
||||
@@ -393,7 +414,7 @@ Use reflection and construction tests to prove:
|
||||
- receipts expose no locator, ETag, upload ID, URL, credential, or provider enum;
|
||||
- requirements can strengthen but never lower destination policy.
|
||||
|
||||
- [ ] **Step 2: Verify RED**
|
||||
- [x] **Step 2: Verify RED**
|
||||
|
||||
Run:
|
||||
|
||||
@@ -405,7 +426,7 @@ cd src
|
||||
|
||||
Expected: compilation failure because the semantic port family does not exist.
|
||||
|
||||
- [ ] **Step 3: Implement the minimum framework-free API**
|
||||
- [x] **Step 3: Implement the minimum framework-free API**
|
||||
|
||||
Follow design §9 exactly. Keep one public top-level type per file. Use immutable collections and
|
||||
defensive copies where required. Grants contain a bounded URI, signed header names/values,
|
||||
@@ -414,7 +435,7 @@ expiration, and opaque session identity, but their `toString` must redact the UR
|
||||
Document the separate scanner-workflow composition boundary on `ObjectScanMaintenancePort`; the
|
||||
normal staged port owns only stage, integrity verification, finalize, and abort.
|
||||
|
||||
- [ ] **Step 4: Verify GREEN**
|
||||
- [x] **Step 4: Verify GREEN**
|
||||
|
||||
Run the command from Step 2. Expected: PASS.
|
||||
|
||||
@@ -431,7 +452,7 @@ Run the command from Step 2. Expected: PASS.
|
||||
- Modify:
|
||||
`src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java`
|
||||
|
||||
- [ ] **Step 1: Write failing purity tests**
|
||||
- [x] **Step 1: Write failing purity tests**
|
||||
|
||||
Assert recursively through fields, methods, constructors, record components, generic arguments, and
|
||||
annotations that `dev.caskeleton.application.objectstorage..` has no Spring, AWS SDK, servlet,
|
||||
@@ -442,7 +463,7 @@ boundary. Add an ArchUnit rule that new sample business code may not import
|
||||
into an explicitly named legacy-only package, and permit the later exact
|
||||
`application.storage.migration` adoption use case as an admin-only compatibility exception.
|
||||
|
||||
- [ ] **Step 2: Verify RED**
|
||||
- [x] **Step 2: Verify RED**
|
||||
|
||||
Run:
|
||||
|
||||
@@ -458,7 +479,7 @@ Expected: the new sample-import rule initially identifies the current legacy con
|
||||
fixture explicitly records it as the single frozen violation. No unrelated architecture violation
|
||||
may be accepted.
|
||||
|
||||
- [ ] **Step 3: Deprecate without adapting new calls back to raw keys**
|
||||
- [x] **Step 3: Deprecate without adapting new calls back to raw keys**
|
||||
|
||||
Mark both legacy types `@Deprecated(forRemoval = true)` and document:
|
||||
|
||||
@@ -473,13 +494,13 @@ Task 23 must move the remaining compatibility surface into an allowlisted `..pos
|
||||
slice; no non-legacy sample package may import the old port after that cutover. Do not create a
|
||||
semantic-to-legacy adapter that throws away operation identity or guarantees.
|
||||
|
||||
- [ ] **Step 4: Verify GREEN**
|
||||
- [x] **Step 4: Verify GREEN**
|
||||
|
||||
Run the command from Step 2. Expected: PASS with exactly the named frozen legacy violation and zero
|
||||
provider/framework leaks. The later migration exception must be bounded/redacted and visible only
|
||||
to the named administrative use case.
|
||||
|
||||
- [ ] **Step 5: Run Batch A checkpoint**
|
||||
- [x] **Step 5: Run Batch A checkpoint**
|
||||
|
||||
Run:
|
||||
|
||||
@@ -523,7 +544,7 @@ object-storage provider.
|
||||
- Test:
|
||||
`src/adapter/outbound/objectstorage/src/test/java/dev/caskeleton/adapter/outbound/objectstorage/kernel/ObjectOperationEpochTest.java`
|
||||
|
||||
- [ ] **Step 1: Add the approved test-only property engine**
|
||||
- [x] **Step 1: Add the approved test-only property engine**
|
||||
|
||||
Add `testImplementation 'net.jqwik:jqwik:1.9.1'` and update only this leaf's lockfile:
|
||||
|
||||
@@ -535,7 +556,7 @@ cd src
|
||||
|
||||
Expected: PASS with no production jqwik dependency.
|
||||
|
||||
- [ ] **Step 2: Write failing codec and epoch tests**
|
||||
- [x] **Step 2: Write failing codec and epoch tests**
|
||||
|
||||
Use golden vectors and property tests for:
|
||||
|
||||
@@ -555,7 +576,7 @@ Use golden vectors and property tests for:
|
||||
- epoch `WARM -> ACTIVE -> DRAINING -> SEALED -> COMPACTED` transitions, no token reuse, and
|
||||
`OPERATION_EXPIRED` after seal/compaction.
|
||||
|
||||
- [ ] **Step 3: Verify RED**
|
||||
- [x] **Step 3: Verify RED**
|
||||
|
||||
Run:
|
||||
|
||||
@@ -569,14 +590,14 @@ cd src
|
||||
|
||||
Expected: compilation failure because the codec/kernel types do not exist.
|
||||
|
||||
- [ ] **Step 4: Implement deterministic codecs**
|
||||
- [x] **Step 4: Implement deterministic codecs**
|
||||
|
||||
Keep all physical key construction in these codecs. Route lookup uses retained binding revision,
|
||||
never a current-provider default. The fingerprint includes exact content identity for R2 and an
|
||||
explicit `R1_UNVERIFIED_CONTENT` marker for compatibility; it never hashes content by materializing
|
||||
the object. Operation epoch records include finite replay/retention/compaction bounds.
|
||||
|
||||
- [ ] **Step 5: Verify GREEN**
|
||||
- [x] **Step 5: Verify GREEN**
|
||||
|
||||
Run the command from Step 3 and
|
||||
`./gradlew :adapter:outbound:objectstorage:verifyDependencyLocks --console=plain`. Expected: PASS.
|
||||
@@ -610,7 +631,7 @@ Run the command from Step 3 and
|
||||
- Test:
|
||||
`src/adapter/outbound/objectstorage/src/test/java/dev/caskeleton/adapter/outbound/objectstorage/kernel/ObjectOperationKernelTest.java`
|
||||
|
||||
- [ ] **Step 1: Write failing strict-codec tests**
|
||||
- [x] **Step 1: Write failing strict-codec tests**
|
||||
|
||||
For every record family introduced through Task 7 prove:
|
||||
|
||||
@@ -629,7 +650,7 @@ size/digest/media type, encryption/retention evidence, and immutable data versio
|
||||
small reference pointer CASes only the current manifest revision. No mutable pointer is treated as
|
||||
the manifest itself.
|
||||
|
||||
- [ ] **Step 2: Write failing transition-table tests**
|
||||
- [x] **Step 2: Write failing transition-table tests**
|
||||
|
||||
Cover every allowed and forbidden transition from design §12, including:
|
||||
|
||||
@@ -641,7 +662,7 @@ Cover every allowed and forbidden transition from design §12, including:
|
||||
- stale fence/revision rejection;
|
||||
- unknown/newer state never auto-deleted or downgraded.
|
||||
|
||||
- [ ] **Step 3: Verify RED**
|
||||
- [x] **Step 3: Verify RED**
|
||||
|
||||
Run:
|
||||
|
||||
@@ -655,7 +676,7 @@ cd src
|
||||
|
||||
Expected: compilation failure because the control-plane and state-machine types do not exist.
|
||||
|
||||
- [ ] **Step 4: Implement the minimum kernel**
|
||||
- [x] **Step 4: Implement the minimum kernel**
|
||||
|
||||
The kernel accepts a compiled policy snapshot and an `ObjectControlStore`; it does not import a
|
||||
provider SDK. Reserve writes the frozen snapshot before provider mutation. Every mutation writes a
|
||||
@@ -665,7 +686,7 @@ discriminator; no reflective or `Map<String,Object>` binding and no undecided JS
|
||||
allowed. A later task that adds a durable family must modify this codec, add checked-in golden
|
||||
bytes, and prove old/new reader compatibility before the selected write version changes.
|
||||
|
||||
- [ ] **Step 5: Verify GREEN**
|
||||
- [x] **Step 5: Verify GREEN**
|
||||
|
||||
Run the command from Step 3. Expected: PASS.
|
||||
|
||||
@@ -689,7 +710,7 @@ Run the command from Step 3. Expected: PASS.
|
||||
- Test:
|
||||
`src/adapter/outbound/objectstorage/src/test/java/dev/caskeleton/adapter/outbound/objectstorage/filesystem/LocalDevObjectStorageRecoveryTest.java`
|
||||
|
||||
- [ ] **Step 1: Write the reusable provider contract**
|
||||
- [x] **Step 1: Write the reusable provider contract**
|
||||
|
||||
The abstract suite must be executable for each provider and cover:
|
||||
|
||||
@@ -707,7 +728,7 @@ The abstract suite must be executable for each provider and cover:
|
||||
|
||||
Unsupported optional capabilities must assert descriptor `UNSUPPORTED`, not skip.
|
||||
|
||||
- [ ] **Step 2: Write failing local security/recovery tests**
|
||||
- [x] **Step 2: Write failing local security/recovery tests**
|
||||
|
||||
Use `@TempDir` and injected filesystem/fault collaborators to test:
|
||||
|
||||
@@ -720,7 +741,7 @@ Use `@TempDir` and injected filesystem/fault collaborators to test:
|
||||
- corrupt/newer records quarantined, never absent/deleted;
|
||||
- file descriptor and temporary-file cleanup.
|
||||
|
||||
- [ ] **Step 3: Verify RED**
|
||||
- [x] **Step 3: Verify RED**
|
||||
|
||||
Run:
|
||||
|
||||
@@ -733,7 +754,7 @@ cd src
|
||||
|
||||
Expected: compilation failure because the local provider/kernel integration does not exist.
|
||||
|
||||
- [ ] **Step 4: Implement bounded local R1**
|
||||
- [x] **Step 4: Implement bounded local R1**
|
||||
|
||||
Use adapter-generated immutable data names, `CREATE_NEW`, bounded chunks, streaming SHA-256, staged
|
||||
temporary files, force/atomic move only where the host proves it, and strict relative path checks.
|
||||
@@ -741,7 +762,7 @@ The local control store serializes per operation in one process. When portabilit
|
||||
durability cannot be proven, return the truthful R1 descriptor; never claim multi-node CAS or
|
||||
power-loss durability. This provider is rejected in production profiles.
|
||||
|
||||
- [ ] **Step 5: Verify GREEN**
|
||||
- [x] **Step 5: Verify GREEN**
|
||||
|
||||
Run the command from Step 3. Expected: PASS.
|
||||
|
||||
@@ -770,7 +791,7 @@ Run the command from Step 3. Expected: PASS.
|
||||
- Modify:
|
||||
`src/sample-portfolio/src/main/resources/application.yml`
|
||||
|
||||
- [ ] **Step 1: Write failing binding tests**
|
||||
- [x] **Step 1: Write failing binding tests**
|
||||
|
||||
Bind `app.object-storage` and reject:
|
||||
|
||||
@@ -788,7 +809,7 @@ Bind `app.object-storage` and reject:
|
||||
|
||||
Prove a valid local profile compiles one exact route and immutable policy snapshot.
|
||||
|
||||
- [ ] **Step 2: Write failing composition tests**
|
||||
- [x] **Step 2: Write failing composition tests**
|
||||
|
||||
Use `ApplicationContextRunner` to prove:
|
||||
|
||||
@@ -805,7 +826,7 @@ Use `ApplicationContextRunner` to prove:
|
||||
- a namespace-separated canonical `legacy` subgroup plus the new capability may run together only
|
||||
in the explicit migration/sample-local profile.
|
||||
|
||||
- [ ] **Step 3: Verify RED**
|
||||
- [x] **Step 3: Verify RED**
|
||||
|
||||
Run:
|
||||
|
||||
@@ -819,7 +840,7 @@ cd src
|
||||
Expected: compilation/test failure because canonical settings/composition do not exist and current
|
||||
legacy configuration activates filesystem by default.
|
||||
|
||||
- [ ] **Step 4: Implement exact binding and activation**
|
||||
- [x] **Step 4: Implement exact binding and activation**
|
||||
|
||||
Use immutable constructor-bound settings and typed `Duration`/`DataSize`/enums. Compile settings
|
||||
before constructing any provider. Contributions are side-effect-free descriptors; the assembler
|
||||
@@ -838,11 +859,11 @@ Until Task 22 migrates the consumer, the sample-local YAML explicitly enables th
|
||||
filesystem seam and may also select `filesystem-local-dev` at `./.data/object-storage-v1` for the
|
||||
new capability. No production YAML receives a local fallback.
|
||||
|
||||
- [ ] **Step 5: Verify GREEN**
|
||||
- [x] **Step 5: Verify GREEN**
|
||||
|
||||
Run the command from Step 3. Expected: PASS.
|
||||
|
||||
- [ ] **Step 6: Prove the stable contribution seam**
|
||||
- [x] **Step 6: Prove the stable contribution seam**
|
||||
|
||||
Add an `ApplicationContextRunner` matrix with a counting fake contribution: disabled, unselected,
|
||||
invalid binding, selected success, selected construction failure, and close. Prove construction
|
||||
@@ -874,7 +895,7 @@ unverified post-GREEN addition.
|
||||
- Modify:
|
||||
`docs/superpowers/specs/2026-07-28-objectstorage-production-capability-design.md`
|
||||
|
||||
- [ ] **Step 1: Write the failing registry/schema test**
|
||||
- [x] **Step 1: Write the failing registry/schema test**
|
||||
|
||||
Validate the frozen schema and exact nine card IDs. Reject:
|
||||
|
||||
@@ -886,7 +907,7 @@ Validate the frozen schema and exact nine card IDs. Reject:
|
||||
- local-dev above R1;
|
||||
- a limitation-free row when a provider descriptor reports limitations.
|
||||
|
||||
- [ ] **Step 2: Verify RED**
|
||||
- [x] **Step 2: Verify RED**
|
||||
|
||||
Run:
|
||||
|
||||
@@ -898,13 +919,13 @@ cd src
|
||||
|
||||
Expected: failure because the registry and runtime card types do not exist.
|
||||
|
||||
- [ ] **Step 3: Add only evidenced claims**
|
||||
- [x] **Step 3: Add only evidenced claims**
|
||||
|
||||
Initial entries may claim R0 for contract-only cards and R1 for local managed single upload/download
|
||||
only after Tasks 6–9 pass. Direct, multipart, quarantine, retention, and production reconciliation
|
||||
remain R0/unimplemented. Document that local process recovery is not multi-node CAS or R2.
|
||||
|
||||
- [ ] **Step 4: Run Batch B checkpoint**
|
||||
- [x] **Step 4: Run Batch B checkpoint**
|
||||
|
||||
Run:
|
||||
|
||||
@@ -919,7 +940,7 @@ cd src
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 5: Verify rollback**
|
||||
- [x] **Step 5: Verify rollback**
|
||||
|
||||
Start a context with the canonical capability disabled and legacy disabled. Assert there are no
|
||||
storage beans or side effects. Start the explicit legacy profile and prove old data remains
|
||||
@@ -945,7 +966,7 @@ readable. No migration or deletion occurs at startup.
|
||||
- Test:
|
||||
`src/adapter/outbound/objectstorage/src/test/java/dev/caskeleton/adapter/outbound/objectstorage/s3/S3AsyncClientFactoryTest.java`
|
||||
|
||||
- [ ] **Step 1: Write the failing pinned-API characterization**
|
||||
- [x] **Step 1: Write the failing pinned-API characterization**
|
||||
|
||||
Compile and assert the exact `2.30.0` API used by later tasks:
|
||||
|
||||
@@ -960,7 +981,7 @@ Add boundary tests at `Integer.MAX_VALUE`, `Integer.MAX_VALUE + 1L`, part count
|
||||
minimum S3 non-final part size. Record the full-object multipart checksum profile as unsupported
|
||||
above the SDK integer boundary unless an independently approved path exists.
|
||||
|
||||
- [ ] **Step 2: Write failing client-policy tests**
|
||||
- [x] **Step 2: Write failing client-policy tests**
|
||||
|
||||
Reject missing/non-positive/contradictory:
|
||||
|
||||
@@ -974,7 +995,7 @@ Reject missing/non-positive/contradictory:
|
||||
Assert the factory uses `S3AsyncClient` plus explicit `NettyNioAsyncHttpClient`, not CRT or the sync
|
||||
client, and owns close order.
|
||||
|
||||
- [ ] **Step 3: Verify RED**
|
||||
- [x] **Step 3: Verify RED**
|
||||
|
||||
Run:
|
||||
|
||||
@@ -988,14 +1009,14 @@ cd src
|
||||
Expected: compilation failure because the client factory and compile-scoped Netty async client do
|
||||
not exist.
|
||||
|
||||
- [ ] **Step 4: Add only the required dependency and implementation**
|
||||
- [x] **Step 4: Add only the required dependency and implementation**
|
||||
|
||||
Add `software.amazon.awssdk:netty-nio-client` under the existing AWS BOM. Keep
|
||||
`software.amazon.awssdk:s3` and the BOM at `2.30.0`; do not add CRT or Transfer Manager. Configure
|
||||
finite client and HTTP timeouts/pools/retry from the compiled policy. Default-chain credentials are
|
||||
resolved only after the provider is selected.
|
||||
|
||||
- [ ] **Step 5: Regenerate and verify locks**
|
||||
- [x] **Step 5: Regenerate and verify locks**
|
||||
|
||||
Run:
|
||||
|
||||
@@ -1752,6 +1773,13 @@ Approval Gate A.
|
||||
|
||||
## Approval Gate A — Scanner ownership and sample public contract
|
||||
|
||||
**Approved 2026-07-29:** The user authorized continued implementation using the recommended
|
||||
defaults. The deep design now records the exact decisions: test-fake scanner only,
|
||||
`integrity-verified-reference`, additive POST/GET with stable `202`, Base64 SHA-256 multipart
|
||||
field, required keyed idempotency with atomic TX1 reservation/intent, existing `poster:write`,
|
||||
legacy compatibility profile, new V2 event without v1 removal, intentionally scoped OpenAPI
|
||||
snapshot, and additive forward-only V8 dual read.
|
||||
|
||||
Do not execute Tasks 20–24 until the approved design records all of the following:
|
||||
|
||||
- staged scan fencing may be implemented with a test fake, but no production scanner/provider is
|
||||
@@ -1759,7 +1787,7 @@ Do not execute Tasks 20–24 until the approved design records all of the follow
|
||||
- the sample's first migrated profile is either `integrity-verified-reference` or an explicitly
|
||||
named scan-gated profile with a real scanner owner;
|
||||
- the additive endpoint proposal
|
||||
`POST /posters/{id}/image-publications`, status, authorization, request size/media policy, and
|
||||
`POST /posters/{id}/imagePublications`, status, authorization, request size/media policy, and
|
||||
locator-free response fields;
|
||||
- the asynchronous response contract: POST always returns the same bounded `202` reservation
|
||||
(opaque publication operation plus status link) replayed by `IdempotencyExecutor`; a separately
|
||||
@@ -1783,8 +1811,8 @@ Do not execute Tasks 20–24 until the approved design records all of the follow
|
||||
window, consumer migration evidence, rollback, and zero-consumer proof before v1 removal. Never
|
||||
rename `imageKey` to `reference` under the same unversioned event contract;
|
||||
- intentional OpenAPI snapshot approval;
|
||||
- the additive V7 schema and forward-only rollback window.
|
||||
- the compatibility model during V7: existing legacy attachments remain readable/publishable,
|
||||
- the additive V8 schema and forward-only rollback window.
|
||||
- the compatibility model during V8: existing legacy attachments remain readable/publishable,
|
||||
legacy writes are restricted to the compatibility controller/profile, and the canonical profile
|
||||
writes only READY opaque references.
|
||||
|
||||
@@ -1902,7 +1930,7 @@ scanner, `object-storage-quarantine-publication` remains below R2.
|
||||
- Modify:
|
||||
`src/sample-portfolio/gradle.lockfile`
|
||||
- Create:
|
||||
`src/sample-portfolio/src/main/resources/db/sample-migration/V7__poster_image_publication.sql`
|
||||
`src/sample-portfolio/src/main/resources/db/sample-migration/V8__poster_image_publication.sql`
|
||||
- Create under
|
||||
`src/sample-portfolio/src/main/java/dev/caskeleton/sample/portfolio/application/posterimage/`:
|
||||
`PosterImageUploadIntent.java`, `PosterImageUploadIntentState.java`,
|
||||
@@ -1954,7 +1982,7 @@ scanner, `object-storage-quarantine-publication` remains below R2.
|
||||
- Test:
|
||||
`src/sample-portfolio/src/test/java/dev/caskeleton/sample/portfolio/bootstrap/objectstorage/PosterImageIdempotencyConfigTest.java`
|
||||
- Create:
|
||||
`src/sample-portfolio/src/posterImageMigrationTest/java/dev/caskeleton/sample/portfolio/qualification/PosterImageV7MigrationQualificationTest.java`
|
||||
`src/sample-portfolio/src/posterImageMigrationTest/java/dev/caskeleton/sample/portfolio/qualification/PosterImageV8MigrationQualificationTest.java`
|
||||
- Create:
|
||||
`src/sample-portfolio/src/posterImageMigrationTest/java/dev/caskeleton/sample/portfolio/qualification/PosterImageIdempotencyRotationQualificationTest.java`
|
||||
- Modify:
|
||||
@@ -1986,7 +2014,7 @@ Expected: the task/configuration exists and locks are valid; no migration behavi
|
||||
|
||||
Run real PostgreSQL/Flyway and prove:
|
||||
|
||||
- fresh V1–V7 and V6→V7 upgrade;
|
||||
- fresh V1–V8 and V7→V8 upgrade;
|
||||
- V6 is unchanged;
|
||||
- existing `poster.image_key` rows remain readable through the internal dual-read mapper;
|
||||
- new READY attachments store only bounded opaque `image_reference`;
|
||||
@@ -2004,7 +2032,7 @@ Run real PostgreSQL/Flyway and prove:
|
||||
- replacement/delete work uses a separate durable `poster_image_retirement_intent` row and cannot
|
||||
be inferred from a deleted Poster or overloaded upload intent.
|
||||
|
||||
The V7 intent table must include stable operation identity/epoch/destination, request fingerprint,
|
||||
The V8 intent table must include stable operation identity/epoch/destination, request fingerprint,
|
||||
bounded idempotency-scope digest, expected poster version, exact content identity/media type, state,
|
||||
stage handle, handoff claim/fence/expiry, published reference/version, replaced reference,
|
||||
retry/error timestamps, and optimistic revision. Index the unique scope, operation, worker claims,
|
||||
@@ -2047,7 +2075,7 @@ cd src
|
||||
./gradlew :sample-portfolio:posterImageMigrationTest --console=plain
|
||||
```
|
||||
|
||||
Expected: compilation/migration failures because V7, the intent model, key-ring binding, and
|
||||
Expected: compilation/migration failures because V8, the intent model, key-ring binding, and
|
||||
non-skipping qualification lane do not exist. Docker/PostgreSQL absence is an actionable failure,
|
||||
not a skip or passing default test.
|
||||
|
||||
@@ -2084,7 +2112,7 @@ Expected: PASS, and the `posterImageMigrationTest` result XML reports zero skipp
|
||||
|
||||
- [ ] **Step 6: Verify rollback window**
|
||||
|
||||
The non-skipping `PosterImageV7MigrationQualificationTest` must run a V6-compatible SQL/JPA
|
||||
The non-skipping `PosterImageV8MigrationQualificationTest` must run a V7-compatible SQL/JPA
|
||||
projection against the expanded schema, including old INSERT/UPDATE/read behavior. Document that
|
||||
rollback is binary-only while old code ignores additive columns; there is no down migration,
|
||||
column drop, intent deletion, or object mutation.
|
||||
@@ -2313,7 +2341,7 @@ idempotency package. Preserve the existing application port contract and prove w
|
||||
expired-find→reclaim→complete with new fingerprint/expiry/COMPLETED state, and unrelated managed
|
||||
entity preservation in non-skipping real PostgreSQL. Do not emulate with a process lock. Update the
|
||||
owner README/CLAUDE vendor SPI table. The same qualification queries both `idempotency_record` and
|
||||
V7 intent tables and proves neither contains the raw Idempotency-Key, tenant, or principal; stored
|
||||
V8 intent tables and proves neither contains the raw Idempotency-Key, tenant, or principal; stored
|
||||
aliases/digests retain tenant separation.
|
||||
|
||||
Reconciliation settings are constructor-bound, disabled by default, and bound batch size,
|
||||
@@ -2671,7 +2699,7 @@ migration-port/verifier/replay-store/runner beans and zero side effects. The clo
|
||||
registers approval replay records with golden/new-reader/old-reader compatibility fixtures.
|
||||
|
||||
Retirement settings are constructor-bound and disabled by default, with bounded claim/renew,
|
||||
batch/page, delay, retry/backoff, concurrency, and shutdown grace. The job claims the dedicated V7
|
||||
batch/page, delay, retry/backoff, concurrency, and shutdown grace. The job claims the dedicated V8
|
||||
retirement table and calls only logical exact-reference retirement outside DB transactions. A
|
||||
disabled context creates no scheduler/thread; stale fence/takeover is rejected and response loss
|
||||
remains operation-keyed exact-reference/version reconciliation, not blind retry. TX3 replacement and
|
||||
@@ -3419,7 +3447,7 @@ gates.
|
||||
Before the implementation completion response, read the canonical vault instructions and update:
|
||||
|
||||
```text
|
||||
/home/donghyeon/workspace/ai-tool/llm-wiki-private/raw/branch-notes/<branch-name>.md
|
||||
/home/donghyeon/workspace/ai-tools/llm-wiki/raw/branch-notes/<branch-name>.md
|
||||
```
|
||||
|
||||
Record files, decisions, test commands/results, failures/blocks, evidence grade, and derivative
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
# Object Storage Production Capability Deep Design
|
||||
|
||||
- 작성일: 2026-07-28
|
||||
- 상태: 상세 설계 및 구현 계획 완료, 구현 미착수, R2 미구현
|
||||
- 상태: Approval Gate A 승인 반영, Batch A–D 및 Batch E pause checkpoint를 main에 통합, R2 미구현
|
||||
- 독립 재리뷰: 완료 — 아키텍처 및 문서 실행성 blocker 0, high 0
|
||||
- LLM Wiki capture: 정본 vault
|
||||
`/home/donghyeon/workspace/ai-tool/llm-wiki-private/` 부재로 차단; 비정본 clone 대체 사용 안 함
|
||||
- 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`
|
||||
- 현재 구현 수준: whole-object `byte[]` 기반 local filesystem/S3-MinIO 예제, R0~R1 일부
|
||||
- 현재 구현 수준: 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)
|
||||
- 구현 계획:
|
||||
@@ -19,39 +21,100 @@
|
||||
|
||||
## 0. 구현 상태
|
||||
|
||||
2026-07-28 기준 구현된 범위는 다음뿐이다.
|
||||
### 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;
|
||||
- 설정 한 개로 filesystem 또는 S3 backend를 선택하는 Spring composition;
|
||||
- 명시적 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 범위:
|
||||
|
||||
- streaming upload/download와 range read;
|
||||
- immutable object reference와 version token;
|
||||
- stable operation ID, request fingerprint, durable operation record;
|
||||
- conditional create/update/delete와 unknown-outcome reconciliation;
|
||||
- checksum 생성·전송·검증 계약;
|
||||
- metadata/head 계약;
|
||||
- staged upload, quarantine, scan, publication;
|
||||
- presigned upload/download;
|
||||
- multipart start/part/complete/abort/recovery;
|
||||
- public presigned upload/download endpoint와 inbound authorization;
|
||||
- R2 수준의 exhaustive multipart/direct response-loss·race·restart qualification;
|
||||
- lifecycle, versioning, retention, legal hold 검증;
|
||||
- production credential, encryption, TLS, expected-owner 정책;
|
||||
- bounded timeout, connection pool, retry amplification, graceful shutdown;
|
||||
- provider capability qualification과 exact readiness card;
|
||||
- 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를 의미하지 않는다.
|
||||
이 문서의 상태가 “상세 설계 완료”인 것은 구현, R2 qualification 또는 운영 준비 완료를
|
||||
뜻하지 않는다.
|
||||
Batch B의 R1은 exact local functional topology 증거다. Multi-node CAS, power-loss durability,
|
||||
S3/MinIO qualification, R2 또는 운영 준비 완료를 뜻하지 않는다.
|
||||
|
||||
## 1. 설계 판정
|
||||
|
||||
@@ -3955,8 +4018,10 @@ authorization endpoint의 ephemeral response이며 stored object DTO에 영구
|
||||
|
||||
정본 구현 순서와 아래 결정의 현재 freeze/approval gate는
|
||||
[Object Storage Production Capability Implementation Plan](../plans/2026-07-28-objectstorage-production-capability.md)에
|
||||
기록한다. 계획 작성은 완료됐지만 모든 구현 task는 아직 미착수이며, 계획 승인이 public API,
|
||||
scanner provider 또는 AWS/IaC 외부 변경 권한을 자동으로 부여하지 않는다.
|
||||
기록한다. Tasks 1–24의 구현은 Batch E pause checkpoint까지 진행됐지만 Batch E 전체
|
||||
combined/root gate는 아직 통과하지 않았다. Tasks 25–30은 미착수다. 이 구현 진행이나
|
||||
계획 승인이 public API, scanner provider 또는 AWS/IaC 외부 변경 권한을 자동으로 부여하지
|
||||
않는다.
|
||||
|
||||
구현 계획은 다음 결정을 task 단위로 명시해야 한다.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user