Files
document-haness/docs/clean-architecture-backend-template/final/evidence/raw/analysis-finding-a06-f022.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

206 lines
15 KiB
Plaintext

# 규약이 강제라고 적는 문장
MongoObservationConvention.java:1 package dev.caskeleton.adapter.outbound.mongo.observation;
MongoObservationConvention.java:2
MongoObservationConvention.java:3 import java.util.Objects;
MongoObservationConvention.java:4 import java.util.Set;
MongoObservationConvention.java:5
MongoObservationConvention.java:6 /**
MongoObservationConvention.java:7 * The only tags MongoDB telemetry may carry (design §27).
MongoObservationConvention.java:8 *
MongoObservationConvention.java:9 * <p>Two problems, one allowlist. High-cardinality tags — a document id, a tenant id, a query
MongoObservationConvention.java:10 * parameter — multiply time series until the metrics backend either drops data or bills for it. And
MongoObservationConvention.java:11 * the same values are the ones that turn a metrics store into an unmanaged copy of production data,
MongoObservationConvention.java:12 * outside whatever retention and access rules the database is under.
MongoObservationConvention.java:13 *
MongoObservationConvention.java:14 * <p>Both are prevented the same way: a tag not on this list cannot be attached, so the mistake has
MongoObservationConvention.java:15 * to be made in this file rather than at a call site.
MongoObservationConvention.java:16 */
MongoObservationConvention.java:17 public final class MongoObservationConvention {
MongoObservationConvention.java:18
MongoObservationConvention.java:19 private static final Set<String> ALLOWED_TAGS =
MongoObservationConvention.java:20 Set.of(
MongoObservationConvention.java:21 "mongoProfile",
MongoObservationConvention.java:22 "databaseProfile",
MongoObservationConvention.java:23 "collectionProfile",
MongoObservationConvention.java:24 "operationName",
MongoObservationConvention.java:25 "operationType",
MongoObservationConvention.java:26 "result",
MongoObservationConvention.java:27 "failureCategory",
MongoObservationConvention.java:28 "consistencyProfile");
MongoObservationConvention.java:29
MongoObservationConvention.java:30 private static final Set<String> FORBIDDEN_TAGS =
MongoObservationConvention.java:31 Set.of(
MongoObservationConvention.java:32 "documentId",
MongoObservationConvention.java:33 "rawTenantId",
MongoObservationConvention.java:34 "tenantId",
MongoObservationConvention.java:35 "dynamicCollectionName",
MongoObservationConvention.java:36 "queryParameter",
MongoObservationConvention.java:37 "query",
MongoObservationConvention.java:38 "fullBson",
MongoObservationConvention.java:39 "resumeToken",
MongoObservationConvention.java:40 "shardKeyValue",
MongoObservationConvention.java:41 "plaintextPII",
MongoObservationConvention.java:42 "credential");
MongoObservationConvention.java:43
MongoObservationConvention.java:44 private MongoObservationConvention() {}
MongoObservationConvention.java:45
MongoObservationConvention.java:46 /** The platform's standard convention. */
MongoObservationConvention.java:47 public static MongoObservationConvention standard() {
MongoObservationConvention.java:48 return new MongoObservationConvention();
MongoObservationConvention.java:49 }
MongoObservationConvention.java:50
MongoObservationConvention.java:51 /** The tag names telemetry may use. */
MongoObservationConvention.java:52 public Set<String> allowedTagNames() {
MongoObservationConvention.java:53 return ALLOWED_TAGS;
MongoObservationConvention.java:54 }
MongoObservationConvention.java:55
MongoObservationConvention.java:56 /** The tag names the design explicitly forbids, kept for assertions and review. */
MongoObservationConvention.java:57 public Set<String> forbiddenTagNames() {
# 그 강제를 수행하는 메서드
MongoObservationConvention.java:58 return FORBIDDEN_TAGS;
MongoObservationConvention.java:59 }
MongoObservationConvention.java:60
MongoObservationConvention.java:61 /**
MongoObservationConvention.java:62 * Rejects a tag that is not on the allowlist.
MongoObservationConvention.java:63 *
MongoObservationConvention.java:64 * @throws IllegalArgumentException naming the rejected tag
MongoObservationConvention.java:65 */
MongoObservationConvention.java:66 public void requireAllowed(String tagName) {
MongoObservationConvention.java:67 Objects.requireNonNull(tagName, "tagName");
MongoObservationConvention.java:68 if (!ALLOWED_TAGS.contains(tagName)) {
MongoObservationConvention.java:69 throw new IllegalArgumentException(
MongoObservationConvention.java:70 "tag '"
MongoObservationConvention.java:71 + tagName
MongoObservationConvention.java:72 + "' is not on the MongoDB observation allowlist; the allowed tags are "
MongoObservationConvention.java:73 + ALLOWED_TAGS);
MongoObservationConvention.java:74 }
MongoObservationConvention.java:75 }
MongoObservationConvention.java:76 }
# 그것을 부르는 자리
MongoObservationConvention 을 언급하는 main 파일 : 2 개
MongoDriverObservabilityConfiguration.java
MongoObservationConvention.java
이 리프 main 에서 requireAllowed 를 부르는 줄 : 0 개
requireAllowed 가 이 리프에 나오는 자리 전부 :
main · MongoSearchQuery.java:48 public void requireAllowedPaths(Set<String> allowedPaths) {
main · ShardAwareQueryValidator.java:59 public MongoRoutingClassification requireAllowedRead(
main · MongoAggregationProfile.java:86 public void requireAllowed(MongoAggregationStageDescriptor descriptor) {
main · PolicyAwareMongoAggregationExecutor.java:49 plan.stages().forEach(profile::requireAllowed);
main · MongoObservationConvention.java:66 public void requireAllowed(String tagName) {
test · MongoSearchReadinessGateTest.java:52 assertThatCode(() -> query.requireAllowedPaths(Set.of("customerName")))
test · MongoSearchReadinessGateTest.java:54 assertThatThrownBy(() -> query.requireAllowedPaths(Set.of("orderNumber")))
test · ShardAwareQueryValidatorTest.java:57 assertThatThrownBy(() -> validator.requireAllowedRead(key, Set.of("status"), false))
test · ShardAwareQueryValidatorTest.java:60 assertThatCode(() -> validator.requireAllowedRead(key, Set.of("status"), true))
test · PolicyAwareMongoAggregationExecutorTest.java:20 assertThatThrownBy(() -> profile.requireAllowed(MongoAggregationStageDescriptor.of("$merge")))
test · PolicyAwareMongoAggregationExecutorTest.java:29 .requireAllowed(MongoAggregationStageDescriptor.of("$out")))
test · PolicyAwareMongoAggregationExecutorTest.java:37 assertThatCode(() -> profile.requireAllowed(MongoAggregationStageDescriptor.of("$match")))
test · PolicyAwareMongoAggregationExecutorTest.java:39 assertThatCode(() -> profile.requireAllowed(MongoAggregationStageDescriptor.of("$project")))
test · PolicyAwareMongoAggregationExecutorTest.java:48 .requireAllowed(MongoAggregationStageDescriptor.of("$group")))
test · PolicyAwareMongoAggregationExecutorTest.java:55 .requireAllowed(MongoAggregationStageDescriptor.of("$group")))
test · PolicyAwareMongoAggregationExecutorTest.java:64 assertThatCode(() -> reviewed.requireAllowed(MongoAggregationStageDescriptor.of("$facet")))
test · PolicyAwareMongoAggregationExecutorTest.java:67 () -> reviewed.requireAllowed(MongoAggregationStageDescriptor.of("$graphLookup")))
test · MongoObservationConventionTest.java:38 assertThatCode(() -> convention.requireAllowed("operationName")).doesNotThrowAnyException();
test · MongoObservationConventionTest.java:39 assertThatThrownBy(() -> convention.requireAllowed("shardKeyValue"))
test · MongoSecurityIntegrationLaneTest.java:163 assertThatThrownBy(() -> convention.requireAllowed("documentId"))
test · MongoSecurityIntegrationLaneTest.java:165 assertThatThrownBy(() -> convention.requireAllowed("tenantId"))
test · MongoSecurityIntegrationLaneTest.java:167 assertThatCode(() -> convention.requireAllowed("operationName")).doesNotThrowAnyException();
[대조] 저장소 전체 main 에서 그 이름을 부르는 줄 : 3 개
GraphQlRSocketAdmission.java:47 routePolicy.requireAllowed(route);
BlockingRedirectCoordinator.java:93 targetGuard.requireAllowed(follow.target(), metadata);
PublishPosterImageUseCase.java:106 policy.requireAllowed(command.contentIdentity(), command.mediaType());
[대조] 같은 pathspec 으로 Tags.of 를 세면 : 9 개
# 그 규약을 언급하는 다른 한 파일이 하는 일
MongoDriverObservabilityConfiguration.java:20 public MongoDriverObservabilityConfiguration(MeterRegistry registry, String mongoProfile) {
MongoDriverObservabilityConfiguration.java:21 this.registry = Objects.requireNonNull(registry, "registry");
MongoDriverObservabilityConfiguration.java:22 this.mongoProfile = Objects.requireNonNull(mongoProfile, "mongoProfile");
MongoDriverObservabilityConfiguration.java:23 }
MongoDriverObservabilityConfiguration.java:24
MongoDriverObservabilityConfiguration.java:25 /** Adds the command, SDAM and pool listeners to a client settings builder. */
MongoDriverObservabilityConfiguration.java:26 public MongoClientSettings.Builder apply(MongoClientSettings.Builder builder) {
MongoDriverObservabilityConfiguration.java:27 Objects.requireNonNull(builder, "builder");
MongoDriverObservabilityConfiguration.java:28 builder.addCommandListener(new MongoCommandObservationListener(registry, mongoProfile));
MongoDriverObservabilityConfiguration.java:29 builder.applyToClusterSettings(
MongoDriverObservabilityConfiguration.java:30 cluster ->
MongoDriverObservabilityConfiguration.java:31 cluster.addClusterListener(new MongoSdamObservationListener(registry, mongoProfile)));
MongoDriverObservabilityConfiguration.java:32 builder.applyToConnectionPoolSettings(
MongoDriverObservabilityConfiguration.java:33 pool ->
MongoDriverObservabilityConfiguration.java:34 pool.addConnectionPoolListener(
MongoDriverObservabilityConfiguration.java:35 new MongoPoolObservationListener(registry, mongoProfile)));
MongoDriverObservabilityConfiguration.java:36 return builder;
MongoDriverObservabilityConfiguration.java:37 }
MongoDriverObservabilityConfiguration.java:38
MongoDriverObservabilityConfiguration.java:39 /** The tag convention these listeners obey. */
MongoDriverObservabilityConfiguration.java:40 public MongoObservationConvention convention() {
MongoDriverObservabilityConfiguration.java:41 return MongoObservationConvention.standard();
MongoDriverObservabilityConfiguration.java:42 }
MongoDriverObservabilityConfiguration.java:43 }
standard() 를 부르는 main 줄 : 1 개
convention() 을 부르는 main 줄 : 0 개
# 관측 클래스들이 실제로 태그를 붙이는 방법
이 패키지에서 Tags.of 를 부르는 줄 : 9 개
MicrometerMongoOperationObserver.java:46 Tags.of(
MicrometerMongoOperationObserver.java:72 this.outcomeTags = Tags.of("result", "unknown", "failureCategory", "none");
MicrometerMongoOperationObserver.java:77 outcomeTags = Tags.of("result", outcome.name(), "failureCategory", "none");
MicrometerMongoOperationObserver.java:83 Tags.of(
MongoCommandObservationListener.java:63 Tags.of(
MongoPoolObservationListener.java:40 Tags tags = Tags.of("mongoProfile", mongoProfile);
MongoPoolObservationListener.java:60 .tags(Tags.of("mongoProfile", mongoProfile, "failureCategory", event.getReason().name()))
MongoSdamObservationListener.java:46 .tags(Tags.of("mongoProfile", mongoProfile))
MongoSdamObservationListener.java:61 .tags(Tags.of("mongoProfile", mongoProfile))
[대조] 저장소 전체에서 Tags.of 를 부르는 줄 : 30 개
# 편집기의 세 갈래 가운데 쓰이는 것
MongoObservationRedactor.java:18 /** Commands whose arguments are structural rather than data-bearing. */
MongoObservationRedactor.java:19 private static final Set<String> DESCRIBABLE_COMMANDS =
MongoObservationRedactor.java:20 Set.of("ping", "hello", "buildInfo", "listCollections", "listIndexes", "collStats");
MongoObservationRedactor.java:21
MongoObservationRedactor.java:22 /** Commands whose arguments always carry credentials or key material. */
MongoObservationRedactor.java:23 private static final Set<String> ALWAYS_REDACTED =
MongoObservationRedactor.java:24 Set.of(
MongoObservationRedactor.java:25 "authenticate",
MongoObservationRedactor.java:26 "saslStart",
MongoObservationRedactor.java:27 "saslContinue",
MongoObservationRedactor.java:28 "getnonce",
MongoObservationRedactor.java:29 "createUser",
MongoObservationRedactor.java:30 "updateUser",
MongoObservationRedactor.java:31 "copydbgetnonce",
MongoObservationRedactor.java:32 "copydbsaslstart",
MongoObservationRedactor.java:33 "copydb");
MongoObservationRedactor.java:34
MongoObservationRedactor.java:35 /** The redacted rendering of a command. */
MongoObservationRedactor.java:36 public String describe(String commandName) {
MongoObservationRedactor.java:37 Objects.requireNonNull(commandName, "commandName");
MongoObservationRedactor.java:38 if (ALWAYS_REDACTED.contains(commandName)) {
MongoObservationRedactor.java:39 return "<redacted>";
MongoObservationRedactor.java:40 }
MongoObservationRedactor.java:41 return DESCRIBABLE_COMMANDS.contains(commandName) ? commandName : commandName + "(...)";
MongoObservationRedactor.java:42 }
MongoObservationRedactor.java:43
MongoObservationRedactor.java:44 /** True when a command's arguments must never be described. */
MongoObservationRedactor.java:45 public boolean isAlwaysRedacted(String commandName) {
MongoObservationRedactor.java:46 return ALWAYS_REDACTED.contains(Objects.requireNonNull(commandName, "commandName"));
MongoObservationRedactor.java:47 }
MongoObservationRedactor.java:48 }
redactor.describe 를 부르는 main 줄 : 0 개
redactor.isAlwaysRedacted 를 부르는 main 줄 : 1 개
[대조] 시험에서 redactor.describe 는 : 6 개
리스너가 그 둘 중 무엇을 쓰는가 :
MongoCommandObservationListener.java:59 private void record(String commandName, String result, long elapsedNanos) {
MongoCommandObservationListener.java:60 registry
MongoCommandObservationListener.java:61 .timer(
MongoCommandObservationListener.java:62 METRIC_NAME,
MongoCommandObservationListener.java:63 Tags.of(
MongoCommandObservationListener.java:64 "mongoProfile",
MongoCommandObservationListener.java:65 mongoProfile,
MongoCommandObservationListener.java:66 "operationType",
MongoCommandObservationListener.java:67 redactor.isAlwaysRedacted(commandName) ? "<redacted>" : commandName,
MongoCommandObservationListener.java:68 "result",
MongoCommandObservationListener.java:69 result))
MongoCommandObservationListener.java:70 .record(elapsedNanos, TimeUnit.NANOSECONDS);
MongoCommandObservationListener.java:71 }
MongoCommandObservationListener.java:72 }