Files
document-haness/docs/clean-architecture-backend-template/final/evidence/raw/150-objectstorage-config-activation-probes.txt
T
DongHyeonkaandClaude Opus 5 b2963105a8 docs(keycloak-session-store): import the session-storage lab as a new project
The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.

Follows the import procedure in README.md.

  source/     the originating repository verbatim — 78 documents, 28 SVGs,
              8 manifests, plus .source-revision recording the commit
  final/      the SSOT
    document.md   729 lines written from the 29 experiment documents, not
                  concatenated: what was predicted, what was measured, and
                  where the measurement itself was wrong
    evidence/raw    125 outputs, flattened to <experiment>__<file> because
                    the originals collided (01-baseline.txt appeared three
                    times) and the audit only globs the top level
    evidence/meta   one per raw file; command and exitCode are null and the
                    README says why rather than inventing them
    evidence/browser  22 captures
    assets/       three diagrams through techviz
    .techviz/     their VizSpecs

A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.

Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.

verify-pipeline.py passes. audit-records.py reports no issues.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 22:51:59 +09:00

307 lines
18 KiB
Plaintext

Sub-scope 01 (objectstorage) governance + config/** - manifest and probes
revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5
generatedAt=2026-08-30T02:38:00+00:00
=== OWNED FILES ===
848a96fae0288a4f45f277e59519162098de42a6 CLAUDE.md 52
b009f7e1d21c68ae4cade2311d7aeb4a4daf3118 README.md 72
ad99e57d20a4be7df7ad4514dec20de86c595acc build.gradle 84
493644f4567e5fae94bc4d4dd45294957b2e66f1 gradle.lockfile 217
70535fba48e01ac6814e2afffc0498ddb5c009d6 config/CompiledObjectStorageDestination.java 21
9ae8d2fe0aa878b95d527455e91e90e663c533c7 config/CompiledObjectStorageProvider.java 53
69f0037741960803ae9fdd8e02de4e45e38796d0 config/LegacyObjectAdoptionSettings.java 41
1705a48cc64501e17e38c84a2f9ab51840bade70 config/LegacyObjectStorageActivationGuard.java 60
9bb2f57920e4ba27bbd357310c7b3467bc3dfbce config/ObjectStorageBindingCompiler.java 276
d8648f156810a06d9ed4158f9503d1a5312dc721 config/ObjectStorageCapabilityAssembler.java 132
cd1d0f00b8cb2e1206060fa483910e53fb1270c9 config/ObjectStorageCapabilityConfig.java 164
346fd88cf5030a6a1efe5a4fc66880ecfc547096 config/ObjectStorageCapabilitySettings.java 49
eb8e5a2d35b58d1bbda8c9a52e5d97251a8c8658 config/ObjectStorageDestinationSettings.java 46
8ff739407bc795f04c9ba04ac2c425392a450ddf config/ObjectStorageLegacyMigrationConfig.java 37
784343e77bf981a8c76f72b27ebec0272d5c17b2 config/ObjectStorageMaintenanceCapabilityConfig.java 10
da0692f4cc012c5ba9f161d5c8cb23c27f730a8e config/ObjectStorageProviderContribution.java 28
58ea89e50d3918f14faeae29a5cfa38ee479dacb config/ObjectStorageProviderSettings.java 13
41a53fae2eaec8f6fc965447f3428c947e4afde2 config/ObjectStorageScanMaintenanceConfig.java 20
2c0559b1d5e051771648f91c5885625d41ab1340 config/RoutingObjectDirectGrantAdapter.java 10
57723eb8d8f3fd8e81f90fee380540c089b9277c config/RoutingObjectMutationAdapter.java 523
24ed0f52e84a38abbfc797e42f05a00625eea0de config/RoutingObjectReadAdapter.java 147
85aaa9a830aeea0452c32e96c08efbda553f9fb9 config/RoutingObjectScanMaintenanceAdapter.java 147
192666945aceeef3e34cd16fb911f9cd2a5bf54b config/SelectedObjectStorageProviderFactory.java 98
8474c92e55cb29df477bd0698526be48ec51f163 test/config/ObjectStorageBindingCompilerTest.java 167
11f1d10fac90b601d6e6cf3858305701787fc610 test/config/ObjectStorageCapabilityConfigTest.java 480
4effeb8c4acb65b6a19e9f0328634db76d35eb63 test/config/ObjectStorageLegacyMigrationConfigTest.java 66
b455e7675c64ab00bf1a7c40db14b0a9c06b2d06 test/config/ObjectStorageScanMaintenanceConfigTest.java 56
5db09bc83df4af94c03719828e29ba7b7be0557e test/config/RoutingObjectStorageAdapterTest.java 154
=== 8.1 reachability: how the configs load and what they gate ===
$ git ls-files 'adapter/outbound/objectstorage' | grep -i 'META-INF\|imports\|spring.factories'
exit=1
$ git grep -rn 'ObjectStorageCapabilityConfig\|ObjectStorageScanMaintenanceConfig\|ObjectStorageMaintenanceCapabilityConfig\|ObjectStorageLegacyMigrationConfig' -- . | grep -v "^adapter/outbound/objectstorage/" | head
exit=0
$ grep -rn 'objectstorage' app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java sample-portfolio/src/main/java/dev/caskeleton/sample/portfolio/*.java 2>/dev/null | head
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java:76: "dev.caskeleton.adapter.outbound.objectstorage",
exit=0
$ git grep -rn 'app.object-storage' -- sample-portfolio/src/main/resources app-bootstrap/src/main/resources 2>/dev/null | head -20
exit=0
=== 8.2 the four claims README makes about composition ===
--- (a) no direct-grant port is registered
$ grep -n 'class RoutingObjectDirectGrantAdapter' -A 4 adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/config/RoutingObjectDirectGrantAdapter.java
7:public final class RoutingObjectDirectGrantAdapter {
8-
9- private RoutingObjectDirectGrantAdapter() {}
10-}
exit=0
$ git grep -rn 'RoutingObjectDirectGrantAdapter' -- 'adapter/outbound/objectstorage/src' | grep -v 'RoutingObjectDirectGrantAdapter.java'
exit=1
--- (b) scanner and privileged purge remain separate and empty
$ cat adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/config/ObjectStorageMaintenanceCapabilityConfig.java
package dev.caskeleton.adapter.outbound.objectstorage.config;
import org.springframework.context.annotation.Configuration;
/**
* Separate privileged composition boundary. Batch B intentionally contributes no purge or scanner
* maintenance bean.
*/
@Configuration(proxyBeanMethods = false)
public class ObjectStorageMaintenanceCapabilityConfig {}
exit=0
$ grep -n '@Bean' -A 6 adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/config/ObjectStorageScanMaintenanceConfig.java
11: @Bean
12- @ConditionalOnProperty(
13- prefix = "app.object-storage.scan-maintenance",
14- name = "enabled",
15- havingValue = "true")
16- public RoutingObjectScanMaintenanceAdapter routingObjectScanMaintenanceAdapter(
17- ObjectStorageCapabilityAssembler.AssembledCapability capability) {
exit=0
--- (c) filesystem-local-dev rejected under prod/production
$ grep -n 'filesystem-local-dev is rejected\|productionProfileActive' -B 3 -A 6 adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/config/ObjectStorageBindingCompiler.java
122- if (compiled.put(providerId, provider) != null) {
123- throw invalid();
124- }
125: if ("filesystem-local-dev".equals(providerType) && productionProfileActive()) {
126: throw new IllegalArgumentException("filesystem-local-dev is rejected in production");
127- }
128- }
129- return Map.copyOf(compiled);
130- }
131-
132- private Map<String, CompiledObjectStorageDestination> compileDestinations(
--
234- }
235- }
236-
237: private boolean productionProfileActive() {
238- return activeProfiles.stream()
239- .map(profile -> profile.toLowerCase(Locale.ROOT))
240- .anyMatch(profile -> profile.equals("prod") || profile.equals("production"));
241- }
242-
243- private static String canonicalId(String label, String value) {
exit=0
--- (d) mixing old alias with canonical settings fails startup without echoing values
$ grep -n 'cannot be combined' -B 8 adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/config/LegacyObjectStorageActivationGuard.java
14- private static final String CANONICAL_PREFIX = "app.object-storage.";
15-
16- @Override
17- public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
18- boolean legacy = hasPrefix(context, LEGACY_PREFIX);
19- boolean canonical = hasPrefix(context, CANONICAL_PREFIX);
20- if (legacy && canonical) {
21- throw new IllegalStateException(
22: "legacy and canonical object-storage configuration cannot be combined");
exit=0
=== 8.2b compile-before-construct: is that ordering real? ===
$ grep -n 'compiler.compile(settings)' -A 6 adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/config/ObjectStorageCapabilityAssembler.java
25: ObjectStorageBindingCompiler.CompiledBindings bindings = compiler.compile(settings);
26- if (bindings.destinations().isEmpty()) {
27- throw new IllegalArgumentException("disabled capability cannot be assembled");
28- }
29- Map<String, ObjectStorageProviderContribution> byType = new LinkedHashMap<>();
30- for (ObjectStorageProviderContribution contribution : contributions) {
31- for (String providerType : contribution.providerTypes()) {
exit=0
$ grep -n 'contribution.create(provider)' -B 6 -A 8 adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/config/ObjectStorageCapabilityAssembler.java
42- CompiledObjectStorageProvider provider =
43- Objects.requireNonNull(bindings.providers().get(destination.providerId()));
44- ObjectStorageProviderContribution contribution = byType.get(provider.providerType());
45- if (contribution == null) {
46- throw new IllegalArgumentException("selected provider contribution is unavailable");
47- }
48: SelectedObjectStorageProviderFactory factory = contribution.create(provider);
49- constructed.add(factory);
50- selected.put(provider.providerId(), factory);
51- }
52- return new AssembledCapability(bindings, selected);
53- } catch (RuntimeException exception) {
54- for (int index = constructed.size() - 1; index >= 0; index--) {
55- constructed.get(index).close();
56- }
exit=0
$ grep -n 'must not resolve credentials' -B 3 -A 3 adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/config/ObjectStorageProviderContribution.java
6-/**
7- * Side-effect-free descriptor plus an explicitly selected provider construction seam.
8- *
9: * <p>{@link #describe} must not resolve credentials, create files, clients, threads, or schedulers.
10- * {@link #create} owns cleanup of every partial allocation before it throws; after a successful
11- * return the assembler owns the returned lifecycle exactly once.
12- */
exit=0
=== 8.3 duplicate mechanism: two legacy activation paths ===
$ grep -rn 'LEGACY_PREFIX\|app.object-storage.legacy' adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/config/*.java | head -12
adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/config/LegacyObjectAdoptionSettings.java:9:@ConfigurationProperties(prefix = "app.object-storage.legacy-adoption")
adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/config/LegacyObjectStorageActivationGuard.java:13: private static final String LEGACY_PREFIX = "ca-skeleton.objectstorage.";
adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/config/LegacyObjectStorageActivationGuard.java:18: boolean legacy = hasPrefix(context, LEGACY_PREFIX);
adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/config/LegacyObjectStorageActivationGuard.java:41: if (LEGACY_PREFIX.equals(prefix)) {
adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/config/ObjectStorageCapabilityConfig.java:90: prefix = "app.object-storage.legacy",
adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/config/ObjectStorageCapabilityConfig.java:115: prefix = "app.object-storage.legacy",
adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/config/ObjectStorageLegacyMigrationConfig.java:22: prefix = "app.object-storage.legacy-adoption",
exit=0
$ git grep -rn 'LegacyObjectStorageActivationGuard' -- 'adapter/outbound/objectstorage/src' | grep -v 'LegacyObjectStorageActivationGuard.java'
adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/ObjectStorageConfig.java:30: dev.caskeleton.adapter.outbound.objectstorage.config.LegacyObjectStorageActivationGuard.class)
exit=0
=== 8.4 the readiness registry the build declares as a test input ===
$ grep -n 'objectStorageReadinessRegistry' -A 6 adapter/outbound/objectstorage/build.gradle
45:def objectStorageReadinessRegistry = rootProject.projectDir.parentFile.toPath()
46- .resolve('docs/registries/object-storage-readiness.yaml').toFile()
47-tasks.named('test') {
48: inputs.file(objectStorageReadinessRegistry)
49- .withPathSensitivity(PathSensitivity.RELATIVE)
50: systemProperty 'objectstorage.readiness.registry', objectStorageReadinessRegistry.absolutePath
51-}
52-
53-def objectStorageMinioContractQualification = registerStrictQualificationTest(
54- name: 'objectStorageMinioContractTest',
55- sourceSet: sourceSets.objectStorageMinioContractTest,
56- requiredClasses: [
exit=0
$ ls -l ../docs/registries/object-storage-readiness.yaml 2>&1
-rw-rw-r-- 1 ubuntu ubuntu 4319 Aug 1 23:21 ../docs/registries/object-storage-readiness.yaml
exit=0
$ git grep -rln 'object-storage-readiness' -- 'adapter/outbound/objectstorage/src' ../docs | head
../docs/superpowers/plans/2026-07-28-objectstorage-production-capability.md
exit=0
=== 8.4b the unguarded split in the read router ===
$ grep -n 'canonicalText().split' -B 3 -A 3 adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/config/RoutingObjectReadAdapter.java
100- }
101-
102- private Optional<LoadedReference> load(ObjectReference reference) {
103: String routeToken = reference.canonicalText().split("\\.", -1)[1];
104- ObjectStorageCapabilityAssembler.DestinationRuntime runtime = capability.route(routeToken);
105- ObjectControlStore control = runtime.providerFactory().controlStore();
106- ObjectReferencePointerRecord pointer =
exit=0
$ git grep -rn 'canonicalText' -- application-core/src/main/java/dev/caskeleton/application/objectstorage/model/ObjectReference.java | head
exit=0
=== 8.2c the production-profile deny check: which names does it recognise? ===
$ grep -rn 'prod\|production' adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/config/ObjectStorageBindingCompiler.java | grep -i profile
125: if ("filesystem-local-dev".equals(providerType) && productionProfileActive()) {
237: private boolean productionProfileActive() {
240: .anyMatch(profile -> profile.equals("prod") || profile.equals("production"));
exit=0
--- how other leaves in this repo detect a production profile
$ git grep -rn 'equals("prod")\|equalsIgnoreCase("prod")\|"production"' -- '*/src/main/java' | grep -iE 'profile' | head -12
exit=0
--- profile names the repo actually ships
$ ls -1 app-bootstrap/src/main/resources/ sample-portfolio/src/main/resources/ 2>/dev/null | grep -i 'application-' | head -20
application-dev.yml
application-local.yml
application-prod.yml
exit=0
$ git grep -rn 'spring.profiles.active' -- . 2>/dev/null | grep -v Binary | head -10
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/logging/ResolvedProfileLoggingContextListener.java:15: * because Logback initialises before {@code spring.profiles.active} resolves and binding to that
app-bootstrap/src/main/resources/logback-spring.xml:9: Reads the environment variable, not spring.profiles.active.
app-bootstrap/src/main/resources/logback-spring.xml:12: the environment stamps log lines with the stale one. Binding to spring.profiles.active was
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverRuntimeAssemblyTest.java:148: .withPropertyValues("spring.profiles.active=prod")
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/UnsafeStartupConfigurationTest.java:46: "spring.profiles.active=prod", "app.httpclient.clients[0].tls.trust-all=true")
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/UnsafeStartupConfigurationTest.java:58: "spring.profiles.active=prod",
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/UnsafeStartupConfigurationTest.java:79: "spring.profiles.active=prod", "app.httpclient.clients[0].transport=SIMPLE")
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/StructuredLogFieldContractTest.java:79: .doesNotContain("source=\"spring.profiles.active\"")
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/logging/LogProfileDriftCharacterizationTest.java:20: * {@code spring.profiles.active} yields an empty string: Logback initialises before that property
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/runtime/OperationalContractRuntimeTest.java:73: "spring.profiles.active=test",
exit=0
=== 8.4c the readiness registry: rows and the test that reads it ===
$ ls -l ../docs/registries/object-storage-readiness.yaml
-rw-rw-r-- 1 ubuntu ubuntu 4319 Aug 1 23:21 ../docs/registries/object-storage-readiness.yaml
exit=0
$ head -30 ../docs/registries/object-storage-readiness.yaml
# Repository owner test: dev.caskeleton.bootstrap.contract.ContractRegistrySchemaGovernanceTest
# Owner Gradle path: :app-bootstrap:test
# Semantic owner test: dev.caskeleton.adapter.outbound.objectstorage.readiness.ObjectStorageReadinessRegistryTest
# Semantic owner Gradle path: :adapter:outbound:objectstorage:test
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:
exit=0
$ git grep -rln 'object-storage-readiness' -- 'adapter/outbound/objectstorage/src'
exit=1
=== 8.4d ObjectReference canonical text shape (the unguarded split) ===
$ find . -name 'ObjectReference.java' -path '*objectstorage*' | head
./application-core/src/main/java/dev/caskeleton/application/objectstorage/identity/ObjectReference.java
exit=0
$ grep -n 'canonicalText\|record ObjectReference\|matches\|split' $(find . -name 'ObjectReference.java' -path '*objectstorage*' | head -1) | head -20
8: private final String canonicalText;
10: private ObjectReference(String canonicalText) {
11: this.canonicalText = ObjectIdentitySupport.requireRouted(canonicalText, "osr1");
14: public static ObjectReference parse(String canonicalText) {
15: return new ObjectReference(canonicalText);
18: public String canonicalText() {
19: return canonicalText;
23: return ObjectIdentitySupport.redactedLogToken(canonicalText);
33: return other instanceof ObjectReference that && canonicalText.equals(that.canonicalText);
38: return Objects.hash(canonicalText);
exit=0