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>
161 lines
11 KiB
Plaintext
161 lines
11 KiB
Plaintext
Sub-scope 03 (mongo) mapping + nativecap + geo — manifest and negative-space probes
|
|
revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5
|
|
generatedAt=2026-08-29T23:49:32+00:00
|
|
cwd=/shared/codebase/clean-architecture-backend-template/src
|
|
|
|
=== OWNED FILES ===
|
|
72953a64190f0f694f89591c80a764b27db54069 main/java/dev/caskeleton/adapter/outbound/mongo/geo/MongoGeoDistance.java 54
|
|
944c7b86656ab33af7b42f82e15c8d735db13395 main/java/dev/caskeleton/adapter/outbound/mongo/geo/MongoGeoPoint.java 39
|
|
214513c7132868faf96e60d6bd0b02a4c221ab2a main/java/dev/caskeleton/adapter/outbound/mongo/geo/MongoGeoQuery.java 43
|
|
c437f2e6527dde79d91ad8a964a22a554fa8b1f3 main/java/dev/caskeleton/adapter/outbound/mongo/geo/MongoGeospatialOperations.java 21
|
|
bc2dbbfa7e681c4bbb4a59bce60c4a9ffa8bc191 main/java/dev/caskeleton/adapter/outbound/mongo/geo/SpringMongoGeospatialOperations.java 120
|
|
767ddc4caec1751d879a90ed4525dc54521d413a main/java/dev/caskeleton/adapter/outbound/mongo/mapping/BigDecimalToDecimal128Converter.java 56
|
|
a6670a37afd4c8e0f55063f99e7607b8182b01e4 main/java/dev/caskeleton/adapter/outbound/mongo/mapping/BigIntegerRepresentationConverters.java 96
|
|
7c1f34986f59537eb14277537c5e2c56c6ad3cc2 main/java/dev/caskeleton/adapter/outbound/mongo/mapping/Decimal128ToBigDecimalConverter.java 29
|
|
5bf3289eef7b7d51fc03f38b7f183af2def725b9 main/java/dev/caskeleton/adapter/outbound/mongo/mapping/DomainIdReadConverter.java 21
|
|
65ac609e91d4044dab7a2e599c8271756c96b240 main/java/dev/caskeleton/adapter/outbound/mongo/mapping/DomainIdWriteConverter.java 20
|
|
1d11c7523c07489299da5c6e169d3cce9a82daa9 main/java/dev/caskeleton/adapter/outbound/mongo/mapping/LocalDateTimeMappingGuard.java 61
|
|
3316ca832683b235c8e9b313afcfb68d271b86cc main/java/dev/caskeleton/adapter/outbound/mongo/mapping/MongoCustomConversionsFactory.java 87
|
|
9ff0549795955266fd4ece2368a27e90b2804301 main/java/dev/caskeleton/adapter/outbound/mongo/mapping/MongoMappingConfiguration.java 110
|
|
ab6677dcad01cc2e978967061bd71178c0a83838 main/java/dev/caskeleton/adapter/outbound/mongo/mapping/MongoTypeMetadataConfigurer.java 38
|
|
d1ed747d5bd36c632d0e5d98806ac53b8dd1513e main/java/dev/caskeleton/adapter/outbound/mongo/mapping/type/LongLivedMongoDocument.java 31
|
|
29ddc389b6530a6a6203d78a722a817d0de61f4b main/java/dev/caskeleton/adapter/outbound/mongo/mapping/type/MongoTypeMetadataDescriptor.java 46
|
|
1e9b1bb1072a5b0aea37a486cd169fc28040bcbb main/java/dev/caskeleton/adapter/outbound/mongo/mapping/type/MongoTypeMetadataRegistry.java 122
|
|
0fa892842fe5233aaf3b381fc3900dc28f2af2e9 main/java/dev/caskeleton/adapter/outbound/mongo/mapping/type/PolicyAwareMongoTypeMapper.java 205
|
|
8ddb11c59c6e405aaa119038f35279443891a784 main/java/dev/caskeleton/adapter/outbound/mongo/nativecap/ApprovedMongoNativeOperation.java 67
|
|
d39bcca075a57bc14dbe429f6918ae847af36d34 main/java/dev/caskeleton/adapter/outbound/mongo/nativecap/MongoNativeCapabilityGateway.java 14
|
|
b83fc163ef8b2bbc96c06869405290edac0b22ac main/java/dev/caskeleton/adapter/outbound/mongo/nativecap/MongoNativeCommandCategory.java 28
|
|
5f759fa4c14ccec81b5a421a728552c8f227fd48 main/java/dev/caskeleton/adapter/outbound/mongo/nativecap/MongoNativeOperationPolicy.java 148
|
|
a4b61e84e77ad188f521b1d754d673ecfd05851e main/java/dev/caskeleton/adapter/outbound/mongo/nativecap/PolicyAwareMongoNativeGateway.java 46
|
|
57693cee88da8503991f42d52cbfef9b9c796aab test/java/dev/caskeleton/adapter/outbound/mongo/geo/MongoGeoPointTest.java 58
|
|
e9327b84b30a1262b65d07a9e749826ae60efb90 test/java/dev/caskeleton/adapter/outbound/mongo/mapping/MongoMappingConfigurationTest.java 177
|
|
5b93db65a7ab80e91648322ebec0387f9072c834 test/java/dev/caskeleton/adapter/outbound/mongo/mapping/type/PolicyAwareMongoTypeMapperTest.java 118
|
|
f1ccfd0e6c231e5c3b77e8c4fc97d86972ab6ec5 test/java/dev/caskeleton/adapter/outbound/mongo/nativecap/PolicyAwareMongoNativeGatewayTest.java 148
|
|
|
|
production: 23
|
|
test: 4
|
|
|
|
=== 8.1 reachability: which of these types a production configuration actually builds ===
|
|
$ git grep -n 'MongoMappingConfiguration\|MongoTypeMetadataConfigurer\|MongoTypeMetadataRegistry\|LocalDateTimeMappingGuard\|MongoCustomConversionsFactory' -- 'adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo' | grep -v '/mapping/'
|
|
adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/autoconfigure/MongoPlatformAutoConfiguration.java:13:import dev.caskeleton.adapter.outbound.mongo.mapping.MongoMappingConfiguration;
|
|
adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/autoconfigure/MongoPlatformAutoConfiguration.java:48:@Import(MongoMappingConfiguration.class)
|
|
exit=0
|
|
|
|
$ git grep -n 'SpringMongoGeospatialOperations\|MongoGeospatialOperations' -- 'adapter/outbound/persistence-mongo/src/main' | grep -v '/geo/'
|
|
exit=1
|
|
|
|
$ git grep -n 'MongoNativeCapabilityGateway\|PolicyAwareMongoNativeGateway' -- 'adapter/outbound/persistence-mongo/src/main' | grep -v '/nativecap/'
|
|
exit=1
|
|
|
|
=== 8.2 sibling comparison: how the same 'unregistered type' question is answered ===
|
|
$ grep -n 'orElseThrow' -A 8 adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/mapping/type/PolicyAwareMongoTypeMapper.java | sed -n '1,14p'
|
|
75: .orElseThrow(
|
|
76- () ->
|
|
77- new IllegalStateException(
|
|
78- "no type metadata policy is registered for "
|
|
79- + rawType.getName()
|
|
80- + "; a stored document's type metadata outlives the class, so the"
|
|
81- + " policy is a decision to record rather than to default"));
|
|
82- switch (policy) {
|
|
83- case NO_TYPE_METADATA -> {
|
|
--
|
|
182: .orElseThrow(
|
|
183- () ->
|
|
184- new MongoSchemaValidationException(
|
|
185- MongoFailureContext.schemaMismatch(READ_OPERATION)));
|
|
exit=0
|
|
|
|
$ grep -n 'orElse(MongoTypeMetadataPolicy.CLASS_METADATA_ALLOWED)' -B 4 adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/mapping/type/PolicyAwareMongoTypeMapper.java
|
|
130- Optional<MongoTypeMetadataDescriptor> descriptor = registry.find(restrictedType);
|
|
131- MongoTypeMetadataPolicy policy =
|
|
132- descriptor
|
|
133- .map(MongoTypeMetadataDescriptor::policy)
|
|
134: .orElse(MongoTypeMetadataPolicy.CLASS_METADATA_ALLOWED);
|
|
exit=0
|
|
|
|
$ grep -n 'policyFor' -B 2 -A 5 adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/mapping/type/MongoTypeMetadataRegistry.java
|
|
72-
|
|
73- /** The effective policy for a type; unregistered types keep Spring Data's default. */
|
|
74: public MongoTypeMetadataPolicy policyFor(Class<?> documentType) {
|
|
75- return find(documentType)
|
|
76- .map(MongoTypeMetadataDescriptor::policy)
|
|
77- .orElse(MongoTypeMetadataPolicy.CLASS_METADATA_ALLOWED);
|
|
78- }
|
|
79-
|
|
exit=0
|
|
|
|
=== 8.2b the D3 gateway's documented sequence versus the checks that exist ===
|
|
$ grep -n 'Runs the design.s stated sequence' -A 4 adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/nativecap/PolicyAwareMongoNativeGateway.java
|
|
11: * <p>Runs the design's stated sequence and stops at the first refusal: registration, capability,
|
|
12- * database profile, collection profile, timeout, category, then execution. The audit record carries
|
|
13- * the operation id and the outcome and never the BSON arguments — the arguments are the part that
|
|
14- * contains data, and an audit trail that leaks data is a liability rather than a control.
|
|
15- */
|
|
exit=0
|
|
|
|
$ grep -n 'D3는 raw client escape가 아니다' -A 3 adapter/outbound/persistence-mongo/README.md
|
|
65:D3는 raw client escape가 아니다. `PolicyAwareMongoNativeGateway`가 capability → database profile →
|
|
66-collection allowlist → operation name → timeout → consistency → result limit → trace → redaction →
|
|
67-command category → D4 차단 순서를 고정한다.
|
|
68-
|
|
exit=0
|
|
|
|
$ grep -n 'throw MongoOperationRejectedException' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/nativecap/MongoNativeOperationPolicy.java
|
|
56: throw MongoOperationRejectedException.of(
|
|
64: throw MongoOperationRejectedException.of(
|
|
75: throw MongoOperationRejectedException.of(
|
|
80: throw MongoOperationRejectedException.of(
|
|
85: throw MongoOperationRejectedException.of(
|
|
92: throw MongoOperationRejectedException.of(
|
|
exit=0
|
|
|
|
$ git grep -n 'operation.timeout()\|\.hasBody()' -- 'adapter/outbound/persistence-mongo/src/main'
|
|
exit=1
|
|
|
|
$ git grep -n 'operation.maxResults()' -- 'adapter/outbound/persistence-mongo/src/main'
|
|
exit=1
|
|
|
|
=== 8.3 duplicate mechanism: two result-limit vocabularies ===
|
|
$ git grep -ln 'maxResults()' -- 'adapter/outbound/persistence-mongo/src/main'
|
|
adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/aggregation/PolicyAwareMongoAggregationExecutor.java
|
|
adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/query/PolicyAwareMongoQueryBuilder.java
|
|
adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/query/budget/MongoBudgetEnforcer.java
|
|
adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/reactive/cursor/MongoReactiveCursorPublisher.java
|
|
adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/reactive/cursor/MongoResultBudgetTracker.java
|
|
exit=0
|
|
|
|
=== 8.4 geo: the index precondition and the unit type ===
|
|
$ grep -n 'requireSphereIndex' -A 6 adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/geo/SpringMongoGeospatialOperations.java | head -20
|
|
49: requireSphereIndex(access.collection(), query.field());
|
|
50- // $nearSphere over a GeoJSON point takes metres and already returns nearest-first, so
|
|
51- // no sort stage is needed — and adding one would discard the index-ordered scan.
|
|
52- Query near =
|
|
53- new Query(
|
|
54- Criteria.where(query.field())
|
|
55- .nearSphere(query.center().toGeoJsonPoint())
|
|
--
|
|
76: requireSphereIndex(access.collection(), query.field());
|
|
77- Circle circle =
|
|
78- new Circle(toPoint(query.center()), query.maxDistance().toSpringDistance());
|
|
79- Query within =
|
|
80- new Query(Criteria.where(query.field()).withinSphere(circle))
|
|
81- .limit(query.resultLimit());
|
|
82- return access.rawOperations().find(within, documentType, access.collection());
|
|
--
|
|
92: private void requireSphereIndex(String collection, String field) {
|
|
93- boolean supported =
|
|
94- manifests
|
|
95- .find(collection)
|
|
exit=0
|
|
|
|
$ grep -n 'toSpringDistance' -A 6 adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/geo/MongoGeoDistance.java
|
|
39: public Distance toSpringDistance() {
|
|
40- return unit == MongoDistanceUnit.KILOMETERS
|
|
41- ? new Distance(value, Metrics.KILOMETERS)
|
|
42- : new Distance(value / 1000, Metrics.KILOMETERS);
|
|
43- }
|
|
44-
|
|
45- /** The unit a geo distance is expressed in. */
|
|
exit=0
|
|
|