feat: add object storage production capability

This commit is contained in:
donghyeon-ka
2026-07-31 23:50:03 +09:00
parent b3add0162d
commit f0a6d1c8c8
427 changed files with 39930 additions and 699 deletions
@@ -0,0 +1,81 @@
# Object Storage Batch A Checkpoint
- Date: 2026-07-28
- Branch: `codex/objectstorage-production-capability`
- Worktree:
`/home/donghyeon/workspace/clean-architecture-backend-template-objectstorage`
- Claimed level: R0 application contract only
- Provider readiness advanced: no
## Implemented scope
- Characterized the legacy caller-key overwrite, whole-object materialization, locator exposure,
eager filesystem directory creation, optional S3 bucket provisioning, and Poster transaction/API
coupling without changing those behaviors.
- Added provider-neutral identities, opaque checked references/handles, bounded streaming
callbacks, content identity, digest/range values, requests, receipts, outcomes, and narrow ports
under `dev.caskeleton.application.objectstorage`.
- Required an `ObjectOperationKey` on mutation requests and separated normal publication,
scan-maintenance, purge-maintenance, direct, and staged privilege surfaces.
- Added recursive contract-purity tests and an ArchUnit freeze for the one existing sample legacy
import.
- Marked the legacy `ObjectStoragePort` and `StoredObject` as removal boundaries without adapting
new semantic calls back to raw keys.
No provider-neutral kernel, canonical namespace/control codec, local R1 provider, S3/MinIO
qualification, sample migration, or R2 readiness claim is included.
## TDD evidence
The planned RED checks failed only for the intentionally missing types or removal annotations:
- `ObjectStorageIdentityContractTest`: missing identity types before Task 2 implementation.
- `ObjectContentContractTest` and `ObjectStorageValueContractTest`: missing content/value types
before Task 3 implementation.
- `ObjectStoragePortContractTest`: missing request/receipt/port family before Task 4 implementation.
- `ObjectStorageArchitectureContractTest`: missing legacy removal annotations before Task 5
implementation.
An initial ArchUnit DSL compilation error was a test-authoring error, not accepted as a RED result;
the rule was corrected and rerun.
## GREEN verification
All commands ran from `src/` and completed with `BUILD SUCCESSFUL`:
```bash
./gradlew :application-core:resolveAndLockAll --write-locks
./gradlew :application-core:verifyDependencyLocks --console=plain
./gradlew :application-core:test --tests '*ObjectStorageIdentityContractTest' --console=plain
./gradlew :application-core:test \
--tests '*ObjectContentContractTest' \
--tests '*ObjectStorageValueContractTest' --console=plain
./gradlew :application-core:test --tests '*ObjectStoragePortContractTest' --console=plain
./gradlew :application-core:test \
--tests '*ObjectStorageArchitectureContractTest' --console=plain
./gradlew :app-bootstrap:test --tests '*CleanArchitectureTest' --console=plain
./gradlew :application-core:check --console=plain
./gradlew verifyCleanArchitectureDependencies --console=plain
./gradlew :adapter:outbound:objectstorage:test :sample-portfolio:test --console=plain
```
The final combined legacy focused suites completed in 27 seconds. Deprecation-for-removal warnings
are expected evidence that legacy consumers remain visible; they are not suppressed.
## LLM Wiki capture
The canonical vault required by repository policy,
`/home/donghyeon/workspace/ai-tool/llm-wiki-private/`, and its parent
`/home/donghyeon/workspace/ai-tool/` do not exist in this environment. Therefore the required
`raw/branch-notes/codex-objectstorage-production-capability.md` could not be created or updated.
No similarly named non-canonical clone was used. This exact access block is recorded in both the
plan and design headers and here at the Batch A boundary.
## Remaining gates and risks
- External broker and REST consumers and deployed legacy data were not inspected; Gate A remains
blocked for legacy removal or public API versioning.
- The new contracts have no provider implementation yet.
- The current legacy adapter retains whole-object and raw-locator behavior by design until the
later migration batch.
- No readiness registry row is promoted by this checkpoint.
@@ -0,0 +1,109 @@
# Object Storage Batch B Checkpoint
- Date: 2026-07-28
- Branch: `codex/objectstorage-production-capability`
- Worktree:
`/home/donghyeon/workspace/clean-architecture-backend-template-objectstorage`
- Evidence grade: repository-local non-skipping unit/contract/application-context tests
- Advanced cards: local managed single upload R1, local managed download R1
- R2 or production-provider readiness advanced: no
## Implemented scope
- Added deterministic data/control namespaces, opaque reference/handle codecs, canonical request
fingerprints, frozen binding/policy revisions, and bounded operation epochs.
- Added six strict canonical JSON control-record families with fixed field order, outer SHA-256
envelopes, schema/size checks, corruption rejection, and checked-in golden digests.
- Added provider-neutral publication, scan, reference, direct-session, multipart, and pending-effect
state transitions with same-operation replay and conflicting-intent rejection.
- Added a provider contract and `filesystem-local-dev` implementation with bounded streaming,
immutable exclusive create, SHA-256 verification, exact inspect/version, full/range transfer,
conditional retirement, create resolution, restrictive permissions, and path/symlink
confinement.
- Added single-process exact-version control CAS and restart/corruption/fault characterization.
Logical control keys use `.record` physical leaves locally so object-store-valid prefix/leaf key
pairs cannot collide as filesystem file/directory paths.
- Added constructor-bound `app.object-storage` settings and compile-before-construction
provider/destination/route/policy binding. The capability is disabled by default and
`filesystem-local-dev` is rejected for `prod`/`production`.
- Added disabled, unselected, invalid, selected-success, selected-construction-failure, close,
legacy-only, and namespace-separated dual-run composition tests.
- Added semantic routing evidence for publish, replay without producer invocation, inspect,
full transfer, absent reference, and exact retained route lookup.
- Added the exact nine-card readiness registry. Only local managed single upload/download are R1;
direct, multipart, quarantine, retention, and production reconciliation remain R0.
## TDD and defect evidence
Planned RED checks failed for the intentionally absent codec/kernel/provider/settings/readiness
types before each implementation. Additional tests found and drove these corrections:
- Local control keys may legally have both a leaf and a child in object storage, while a filesystem
cannot have both `reference` and `reference/lifecycle`; local physical `.record` mapping fixed the
collision without changing logical keys.
- `ObjectInspectionPort.inspect` initially threw for an absent known-route reference; it now
returns `Optional.empty()` while incomplete/corrupt evidence still fails closed.
- The application purity test initially scanned its own test output after a full `check`; it now
derives the production class root from a production contract type.
- The general B7 ArchUnit rule initially classified objectstorage provider-internal SPI/control
return values as public adapter responses. The existing negative fixture remains active, while a
dedicated non-empty rule now checks the actual objectstorage `*Adapter` semantic boundaries.
No skipped Docker or external-service test is used as Batch B readiness evidence.
## GREEN verification
All commands ran from `src/` unless noted and completed with `BUILD SUCCESSFUL` after the documented
RED/fix cycles:
```bash
./gradlew :adapter:outbound:objectstorage:verifyDependencyLocks --console=plain
./gradlew :adapter:outbound:objectstorage:test \
--tests '*ObjectNamespaceCodecTest' \
--tests '*ObjectRequestFingerprintCodecTest' \
--tests '*ObjectOperationEpochTest' --console=plain
./gradlew :adapter:outbound:objectstorage:test \
--tests '*ObjectControlRecordCodecTest' \
--tests '*ObjectOperationStateMachineTest' \
--tests '*ObjectOperationKernelTest' --console=plain
./gradlew :adapter:outbound:objectstorage:test \
--tests '*ObjectStorageProviderContract' \
--tests '*LocalDevObjectStorageProviderTest' \
--tests '*LocalDevObjectStorageRecoveryTest' --console=plain
./gradlew :adapter:outbound:objectstorage:test \
--tests '*ObjectStorageBindingCompilerTest' \
--tests '*ObjectStorageCapabilityConfigTest' \
--tests '*RoutingObjectStorageAdapterTest' --console=plain
./gradlew :adapter:outbound:objectstorage:test \
--tests '*ObjectStorageReadinessRegistryTest' --console=plain
./gradlew :sample-portfolio:test --console=plain
./gradlew :application-core:check \
:adapter:outbound:objectstorage:check --console=plain
./gradlew :app-bootstrap:test --tests '*CleanArchitectureTest' --console=plain
./gradlew verifyCleanArchitectureDependencies --console=plain
```
The final combined application/objectstorage checkpoint completed in 23 seconds. The focused
Clean Architecture suite and dependency verification also passed.
## LLM Wiki capture
The canonical vault required by repository policy,
`/home/donghyeon/workspace/ai-tool/llm-wiki-private/`, and its parent
`/home/donghyeon/workspace/ai-tool/` do not exist in this environment. Therefore the required
`raw/branch-notes/codex-objectstorage-production-capability.md` and any derived raw documents could
not be created or updated. No similarly named non-canonical clone was used. This exact access block
is recorded in the plan/design status and at this Batch B boundary.
## Remaining gates and risks
- `filesystem-local-dev` has no multi-node linearizability or power-loss durability evidence and is
forbidden in production profiles.
- The canonical S3/MinIO provider contribution, async bounded transport, provider qualification,
response-loss fault tests, and protected AWS evidence are not implemented.
- Direct grants, multipart, quarantine/scan, retention/legal hold, privileged purge, reapers, and
production reconciliation remain R0.
- The sample Poster workflow still uses the deprecated whole-`byte[]` port and transaction-coupled
legacy choreography. It is explicitly activated only in sample local/test configuration.
- External API/broker consumers and deployed legacy data remain uninspected, so Gate A still blocks
destructive migration or legacy removal.
@@ -0,0 +1,84 @@
# Object Storage Batch C Checkpoint
- Date: 2026-07-28
- Branch: `codex/objectstorage-production-capability`
- Worktree:
`/home/donghyeon/workspace/clean-architecture-backend-template-objectstorage`
- Evidence grade: repository-local tests plus digest-pinned single-node MinIO/Toxiproxy tests
- AWS execution: not authorized; source set compiled only
- Production-provider readiness advanced: no
## Implemented scope
- Added exact AWS S3 and MinIO provider bindings, bounded evidence descriptors, qualifier/error
mapping, secret references, endpoint/owner/addressing validation, and selected-only lifecycle
construction.
- Added bounded async request/response bridges and the managed S3 put, inspect, full/range download,
checksum, exact-version, cancellation, and content-length paths.
- Added canonical conditional S3 control storage and operation response-loss resolution. Provider
ETags remain adapter-private and are never exposed as logical versions.
- Added low-level managed multipart planning, sharded immutable part ledgers, initiate-before-I/O
state, explicit create/upload/list/complete/abort calls, and exact completion verification.
- Added non-skipping MinIO contract/fault lanes, an AWS compile-only qualification lane, a protected
workflow, and gate-matrix coverage.
The exact MinIO image is
`minio/minio@sha256:4c4a4876193f030c81f57aabb22bcb9a73462010eb61fcab66908e03e5484af8`.
The exact Toxiproxy image is
`ghcr.io/shopify/toxiproxy@sha256:9378ed52a28bc50edc1350f936f518f31fa95f0d15917d6eb40b8e376d1a214e`.
## Exact MinIO finding
Real-provider tests proved an asymmetric conditional profile:
- `PutObject If-None-Match: *` was accepted but overwrote an existing object.
- stale `PutObject If-Match` was rejected with HTTP 412.
- `CompleteMultipartUpload If-None-Match: *` was accepted and overwrote an existing object.
- checksum, HEAD, and range behavior passed the exercised contract.
Because immutable create and create-if-absent control CAS cannot be proven, the exact MinIO managed
and direct mutation profiles remain `UNSUPPORTED`. The implementation does not emulate missing
atomicity with HEAD followed by an unconditional write and does not promote a readiness card.
## TDD and verification
The task-focused RED runs first failed on the planned absent binding, bridge, conditional store,
multipart, and qualification types. Provider qualification then found the real MinIO conditional
behavior above; the descriptor and negative contract were changed instead of weakening the
contract.
Commands completed with `BUILD SUCCESSFUL`:
```bash
cd src
./gradlew :adapter:outbound:objectstorage:test \
--tests '*S3ProviderBindingTest' \
--tests '*S3ProviderQualifierTest' \
--tests '*S3ProviderCompositionTest' --console=plain
./gradlew :adapter:outbound:objectstorage:objectStorageMinioContractTest --console=plain
./gradlew :adapter:outbound:objectstorage:objectStorageMinioFaultTest --console=plain
./gradlew :adapter:outbound:objectstorage:objectStorageAwsQualificationTestClasses --console=plain
./gradlew :adapter:outbound:objectstorage:check --console=plain
./gradlew :adapter:outbound:objectstorage:verifyDependencyLocks \
verifyCleanArchitectureDependencies --console=plain
bash ../.github/scripts/verify-gate-matrix.sh
```
The gate matrix reports 22 gates: 21 verified and the protected AWS qualification gate explicitly
`delegated-pending`.
## LLM Wiki capture
The canonical vault `/home/donghyeon/workspace/ai-tool/llm-wiki-private/` and its parent
`/home/donghyeon/workspace/ai-tool/` do not exist in this environment. The required
`raw/branch-notes/codex-objectstorage-production-capability.md` and derived raw documents could not
be created or updated. No similarly named non-canonical clone was used.
## Remaining risks
- No AWS request was executed, so there is no observed AWS provider claim.
- The pinned MinIO topology is a local single-node container and is not production TLS,
multi-node, durability, or linearizability evidence.
- The detailed managed multipart fault matrix is not exhaustive enough for R2.
- No sample migration, public API, scan/publication choreography, retention, purge, or reaper is
included in this checkpoint.
@@ -0,0 +1,104 @@
# Object Storage Batch D Checkpoint
- Date: 2026-07-28
- Branch: `codex/objectstorage-production-capability`
- Worktree:
`/home/donghyeon/workspace/clean-architecture-backend-template-objectstorage`
- Scope: direct-transfer provider/application primitives only
- Public endpoint: none
- Readiness advanced: no; all direct cards remain R0
## Implemented scope
- Added direct single-upload session policy, durable prepared/issued transitions, bearer
redaction, exact completion verification, published-version download resolution, and an
S3-presigner lifecycle owned by the selected provider.
- Added direct multipart durable session and part-grant families, opaque acknowledgement tokens,
sharded part records, admission-close/expiry fencing, exact ledger validation, completion/abort
states, response-loss resolution, and persisted terminal exact-version replay.
- Added direct S3 initiate/discovery, exact-part presign, `ListParts` acknowledgement, conditional
complete followed by exact HEAD verification, and abort resolution.
- Registered direct single and multipart delegates only when their exact compiled capability is
selected. One presigner is constructed and closed exactly once.
- Added golden canonical envelopes for the direct session, direct multipart session, and direct
multipart grant families.
- Fixed `MultipartCompleteRequest` null validation so valid immutable `List.of(...)` input no longer
throws from `contains(null)`.
## Qualification truth
The exact MinIO release cannot prove create-only PUT or create-only multipart completion, so both
direct profiles are explicitly `UNSUPPORTED`. The direct MinIO contract/fault lanes are negative
admission tests: they prove no bearer or multipart mutation enters an unsupported profile. No test
skip is used as positive evidence.
The AWS managed/direct source sets compile, but no AWS call was made and no AWS evidence row was
published. No inbound controller, authorization surface, CORS runtime configuration, or public
direct API exists.
## Verification
Commands completed with `BUILD SUCCESSFUL`:
```bash
cd src
./gradlew :adapter:outbound:objectstorage:test \
--tests '*DirectTransferCoordinatorTest' \
--tests '*PresignedGrantRedactionTest' \
--tests '*S3DirectTransferProviderTest' \
--tests '*ObjectControlRecordCodecTest' \
--tests '*S3ProviderCompositionTest' --console=plain
./gradlew :adapter:outbound:objectstorage:test \
--tests '*DirectMultipartCoordinatorTest' \
--tests '*DirectMultipartRaceTest' \
--tests '*S3DirectMultipartProviderTest' \
--tests '*ObjectControlRecordCodecTest' \
--tests '*S3ProviderCompositionTest' --console=plain
./gradlew \
:adapter:outbound:objectstorage:objectStorageMinioContractTest \
:adapter:outbound:objectstorage:objectStorageMinioFaultTest \
--tests '*DirectTransfer*' --console=plain
./gradlew :adapter:outbound:objectstorage:test \
--tests '*DirectTransferCorsContractTest' --console=plain
./gradlew :adapter:outbound:objectstorage:objectStorageAwsQualificationTestClasses --console=plain
./gradlew :adapter:outbound:objectstorage:check --console=plain
./gradlew :adapter:outbound:objectstorage:verifyDependencyLocks \
verifyCleanArchitectureDependencies --console=plain
./gradlew \
:adapter:outbound:objectstorage:objectStorageMinioContractTest \
:adapter:outbound:objectstorage:objectStorageMinioFaultTest --console=plain
bash ../.github/scripts/verify-gate-matrix.sh
./gradlew test --console=plain
./gradlew check --console=plain
```
The module `check` includes unit tests, Checkstyle, Spotless, SpotBugs, architecture, configuration
processor, environment-key, and repository-wide policy checks. Existing test-only compiler
warnings remain non-failing. The final repository-wide test run completed 79 tasks and the final
repository-wide check completed 214 tasks.
## Deliberate limitations
- Issued bearer material is process-local. A restart fails closed instead of reconstructing or
reissuing an already-issued bearer.
- The signing clock/window is stored and bounded, but AWS SDK presigner query timing is not driven
by the injected application clock.
- The direct multipart recovery/race matrix covers its principal fences and completion response
loss but is not exhaustive enough for an R2 claim.
- Retention/Object Lock grant headers and a provider-enforced direct-single hard size ceiling are
not qualified.
- No public endpoint exists, so CORS evidence is a pure contract and no direct card may exceed R0
in the current registry.
## Approval Gate A
Tasks 2024 remain blocked until the user explicitly approves scanner ownership, the sample's
first publication profile, the additive asynchronous API/status contract, and digest transport.
No scan/publication/sample endpoint implementation was started.
## LLM Wiki capture
The canonical vault `/home/donghyeon/workspace/ai-tool/llm-wiki-private/` and its parent
`/home/donghyeon/workspace/ai-tool/` do not exist in this environment. The required
`raw/branch-notes/codex-objectstorage-production-capability.md` and derived raw documents could not
be created or updated. No similarly named non-canonical clone was used.
@@ -0,0 +1,92 @@
# Object Storage Batch E Pause Checkpoint
- Recorded: 2026-07-29 (Asia/Seoul)
- Branch: `codex/objectstorage-production-capability`
- Worktree:
`/home/donghyeon/workspace/clean-architecture-backend-template-objectstorage`
- Status: implementation in progress; intentionally paused at the user's request
- Evidence grade: local unit/integration/architecture evidence only; no AWS R2 evidence
## Implemented at this checkpoint
- Staged integrity verification, fake-scanner routing, publication handoff fencing, and stable
replay receipts.
- Additive Poster V7 dual-read schema, upload/retirement intents, HMAC-sanitized idempotency scope,
PostgreSQL atomic claim SPI, and forward-only migration qualification lane.
- Short-transaction Poster image publication flow and additive locator-free `202` API under the
AIP-122-compatible `/posters/{id}/imagePublications` collection.
- Exact-reference/version logical retirement enqueue, lease/fence takeover, response-loss retry,
Poster deletion survival, and disabled-by-default worker composition.
- Isolated legacy migration contracts, report-only inspection, two-distinct-approver Ed25519
approval verification, nonce replay boundary, and explicit maintenance-only composition.
## Verification completed
The following focused command passed after the final architecture fixes:
```bash
cd src
./gradlew \
:sample-portfolio:spotlessApply \
:sample-portfolio:test --tests '*PosterImagePublicationControllerWireTest' \
:app-bootstrap:test --tests '*CleanArchitectureTest' \
--console=plain
```
The following focused suites also passed during this checkpoint:
```bash
./gradlew :adapter:outbound:objectstorage:test \
--tests '*LegacyObjectAdoptionServiceTest' \
--tests '*LegacyAdoptionApprovalVerifierTest' \
--tests '*ObjectStorageLegacyMigrationConfigTest' --console=plain
./gradlew :sample-portfolio:test \
--tests '*DeletePosterImageRetirementTest' \
--tests '*PosterImageRetirementCrashMatrixTest' \
--tests '*PosterImageRetirementConfigTest' \
--tests '*LegacyPosterImageUploadCharacterizationTest' --console=plain
./gradlew :sample-portfolio:test \
--tests '*SampleApplicationContextTest' \
:sample-portfolio:posterImageMigrationTest --console=plain
```
The migration lane included
`PosterImageRetirementQualificationTest`, which proved that an exact retirement row survives
deletion of its Poster row.
## Failures found and resolved
- `spotlessJavaCheck` initially found formatting drift in newly changed application-core and
persistence files. The owner-module Spotless apply tasks fixed it.
- `SampleApplicationContextTest` initially failed because Spring's persistence exception advisor
could not CGLIB-proxy the final `PosterImageAttachmentCasRepository`. Removing `final` fixed the
context; the focused context suite then passed.
- `CleanArchitectureTest` initially rejected an application-core return type from the sample domain
and the kebab-case `image-publications` path. Conversion moved back to the application use case,
and the endpoint changed to the repository's AIP-122-compatible `imagePublications` segment. The
complete focused architecture suite then passed.
## Not yet re-run / not complete
- The combined Batch E checkpoint command stopped on the two architecture failures above before all
requested root tasks could complete. The focused failing suites passed after the fixes, but
`:sample-portfolio:check`, `verifyPublicPathSnapshot`, and the full combined Batch E command have
not been re-run after those final fixes.
- The complete repository `./gradlew test` and `./gradlew check` have not been re-run after the
Batch E additions.
- The legacy adoption runner/configuration is not yet wired to a production legacy inspector,
permission-checked trust-key loader, or durable control-record replay-store implementation.
- Tasks 2530 (Batch F) have not started in this continuation.
- Actual AWS qualification is blocked by Approval Gate B: no approved account, bucket/namespaces,
workload roles, signed deployment attestation, or mutation/test authority was supplied.
- No readiness card was promoted. Local/MinIO ceilings and unsupported conditional behavior remain
unchanged.
## Wiki capture
The canonical vault
`/home/donghyeon/workspace/ai-tool/llm-wiki-private/` is absent in this environment. Therefore the
required branch note and derived raw documents could not be written. No non-canonical substitute
was used.
@@ -0,0 +1,73 @@
# Object Storage Phase 0 Inventory
- Captured: 2026-07-28
- Branch: `codex/objectstorage-production-capability`
- Scope: repository-local source, tests, configuration, migrations, and documentation
- Evidence grade: repository-local only; deployed data, broker subscribers, and external REST
consumers were not inspected
## Commands
```bash
rg -n 'application\.storage|ObjectStoragePort|StoredObject|ca-skeleton\.objectstorage|file://|s3://' \
src docs
rg -n 'image_key|posters/.*/image' src/sample-portfolio
rg -n 'poster\.image-attached|StoredObjectResponse|PosterResponse|imageKey' \
src/sample-portfolio docs
```
The commands completed successfully in the isolated worktree. Results are classified below.
Documentation hits in the Object Storage design/plan describe the migration and are not runtime
consumers. The `s3://bucket/key-1` fixture in
`IdempotencyStoreAdapterTest` belongs to the generic idempotency response-reference test and is not
an Object Storage legacy-port consumer.
## Repository-local runtime inventory
| Contract/data | Producer | Repository-local consumers | Classification |
| --- | --- | --- | --- |
| `ObjectStoragePort` / `StoredObject` | `application-core/application/storage` | filesystem and S3 adapters, `UploadPosterImageUseCase`, `PosterController`/`PosterWebMapper` | legacy runtime contract |
| `ca-skeleton.objectstorage.*` | `ObjectStorageSettings` / `ObjectStorageConfig` | sample runtime through its objectstorage runtime dependency | legacy runtime configuration |
| `file://` receipt | `FilesystemObjectStorageAdapter` | `StoredObjectResponse.location` through `PosterWebMapper` | public legacy locator |
| `s3://bucket/key` receipt | `S3ObjectStorageAdapter` | `StoredObjectResponse.location` through `PosterWebMapper` | public legacy locator |
| `/posters/{id}/image` | `PosterController` | repository tests and the generated/public HTTP contract | legacy inbound API |
| `StoredObjectResponse` | `PosterController` / `PosterWebMapper` | HTTP caller, with `key`, `size`, `contentType`, and `location` | legacy response DTO |
| `PosterResponse.imageKey` | `PosterWebMapper` | list/get/create/update/publish/archive HTTP responses | legacy general response field |
| `poster.image-attached` | `PosterEventPublisher` | no subscriber found in this repository | versionless broker event; external consumers unknown |
| `poster.image-attached.imageKey` | `PosterImageAttached` and publisher JSON | no subscriber found in this repository | raw locator-shaped event field |
| `poster.image_key` | Flyway V6, `PosterEntity`, persistence mapper | `Poster` aggregate and repository adapter | stored-data schema |
| `posters/{id}/image` key | `UploadPosterImageUseCase` | aggregate `imageKey`, event payload, DB row, HTTP response | deterministic overwriteable legacy key |
## Executable characterization
The following tests pin the current behavior without approving it as the target design:
- `LegacyObjectStorageBehaviorTest`
- caller-selected keys overwrite;
- `get` returns `Optional<byte[]>` and materializes the whole object;
- receipts expose `file://` and `s3://` locators.
- `LegacyObjectStorageConfigTest`
- missing backend configuration selects filesystem;
- context creation creates the filesystem directory before the first write;
- `autoCreateBucket=true` probes and creates a missing bucket during S3 bean construction.
- `LegacyPosterImageUploadCharacterizationTest`
- remote storage is called while `TransactionPort.inWrite` is active;
- the controller calls `MultipartFile.getBytes`;
- the response exposes raw key and provider location;
- Poster deletion leaves the legacy object untouched.
## External inventory gap and Gate A
Repository search does not prove that the following have no deployed consumers:
- `POST /posters/{id}/image`;
- `StoredObjectResponse.key` and `.location`;
- `PosterResponse.imageKey`;
- broker event type `poster.image-attached` and its `imageKey` payload;
- rows already stored in `poster.image_key`;
- filesystem/S3 objects already written under `posters/{id}/image`.
No deployed database, object namespace, access log, API client catalog, broker consumer group, schema
registry, or owning team approval was inspected. Therefore removal, in-place field rename, event
payload replacement, or legacy-object deletion remains blocked. Approval Gate A must obtain owner
and consumer evidence and choose an additive/versioned migration contract.
@@ -0,0 +1,114 @@
schema_version: 1
claims:
- card_id: object-storage-managed-upload-single
provider_type: filesystem-local-dev
provider_version: jdk-21
destination_profile: local-managed-integrity
claimed_level: R1
evidence_revision: batch-b-local-r1
evidence_expires_on: ""
required_tasks:
- ":adapter:outbound:objectstorage:check"
limitations:
- single-process control CAS only; no multi-node linearizability
- fsync and atomic move tests do not prove power-loss durability
- local development provider is forbidden in production profiles
- card_id: object-storage-managed-upload-multipart
provider_type: filesystem-local-dev
provider_version: jdk-21
destination_profile: local-unimplemented
claimed_level: R0
evidence_revision: batch-b-contract-r0
evidence_expires_on: ""
required_tasks:
- ":application-core:check"
limitations:
- multipart publication protocol is not implemented
- card_id: object-storage-managed-download
provider_type: filesystem-local-dev
provider_version: jdk-21
destination_profile: local-managed-integrity
claimed_level: R1
evidence_revision: batch-b-local-r1
evidence_expires_on: ""
required_tasks:
- ":adapter:outbound:objectstorage:check"
limitations:
- local functional full and range reads are not production-provider qualification
- no multi-node or power-loss durability claim
- card_id: object-storage-direct-upload-single
provider_type: filesystem-local-dev
provider_version: jdk-21
destination_profile: local-unimplemented
claimed_level: R0
evidence_revision: batch-b-contract-r0
evidence_expires_on: ""
required_tasks:
- ":application-core:check"
limitations:
- direct grant provider and public inbound endpoint are not implemented
- card_id: object-storage-direct-upload-multipart
provider_type: filesystem-local-dev
provider_version: jdk-21
destination_profile: local-unimplemented
claimed_level: R0
evidence_revision: batch-b-contract-r0
evidence_expires_on: ""
required_tasks:
- ":application-core:check"
limitations:
- direct multipart session and public inbound endpoint are not implemented
- card_id: object-storage-direct-download
provider_type: filesystem-local-dev
provider_version: jdk-21
destination_profile: local-unimplemented
claimed_level: R0
evidence_revision: batch-b-contract-r0
evidence_expires_on: ""
required_tasks:
- ":application-core:check"
limitations:
- direct download grant and public inbound endpoint are not implemented
- card_id: object-storage-quarantine-publication
provider_type: filesystem-local-dev
provider_version: jdk-21
destination_profile: local-unimplemented
claimed_level: R0
evidence_revision: batch-b-contract-r0
evidence_expires_on: ""
required_tasks:
- ":application-core:check"
limitations:
- scanner handoff and verdict fencing are not implemented
- card_id: object-storage-retention
provider_type: filesystem-local-dev
provider_version: jdk-21
destination_profile: local-unimplemented
claimed_level: R0
evidence_revision: batch-b-contract-r0
evidence_expires_on: ""
required_tasks:
- ":application-core:check"
limitations:
- retention and legal-hold provider enforcement are not implemented
- privileged purge composition remains intentionally empty
- card_id: object-storage-reconciliation
provider_type: filesystem-local-dev
provider_version: jdk-21
destination_profile: local-unimplemented
claimed_level: R0
evidence_revision: batch-b-contract-r0
evidence_expires_on: ""
required_tasks:
- ":application-core:check"
limitations:
- local create resolution is single-process functional evidence only
- production response-loss and multi-node reconciliation are not implemented
File diff suppressed because it is too large Load Diff
@@ -949,6 +949,13 @@ and durable interfaces are explicit.
### 13.3 Object storage
The authoritative implementation-level design for this capability is
[Object Storage Production Capability Deep Design](2026-07-28-objectstorage-production-capability-design.md).
Its ordered REDGREEN execution batches and promotion gates are in the
[Object Storage Production Capability Implementation Plan](../plans/2026-07-28-objectstorage-production-capability.md).
This subsection is only the cross-capability baseline; the dedicated design governs when details
differ.
Replace whole-object `byte[]` as the only path with:
- streaming upload/download and range reads;
File diff suppressed because it is too large Load Diff