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>
193 lines
12 KiB
Plaintext
193 lines
12 KiB
Plaintext
# 매니페스트가 선언하는 것
|
|
MongoIndexManifest.java:11 /**
|
|
MongoIndexManifest.java:12 * One declared index, with everything a diff or a review needs (design §18).
|
|
MongoIndexManifest.java:13 *
|
|
MongoIndexManifest.java:14 * <p>Two fields carry most of the weight. {@code expectedUsage} names the operations the index
|
|
MongoIndexManifest.java:15 * exists for, which is what makes "is this index still needed" answerable without guessing from
|
|
MongoIndexManifest.java:16 * production statistics. {@code metadataOwnership} says who created it, which is what keeps drift
|
|
MongoIndexManifest.java:17 * cleanup from proposing to drop an encryption or search index it did not declare.
|
|
MongoIndexManifest.java:18 *
|
|
MongoIndexManifest.java:19 * <p>Key order is preserved because it is part of the index's identity, not a presentation detail.
|
|
MongoIndexManifest.java:20 */
|
|
MongoIndexManifest.java:21 public record MongoIndexManifest(
|
|
MongoIndexManifest.java:22 String name,
|
|
MongoIndexManifest.java:23 List<MongoIndexKey> keys,
|
|
MongoIndexManifest.java:24 boolean unique,
|
|
MongoIndexManifest.java:25 boolean sparse,
|
|
MongoIndexManifest.java:26 boolean hidden,
|
|
MongoIndexManifest.java:27 boolean deprecated,
|
|
MongoIndexManifest.java:28 String partialFilterExpression,
|
|
MongoIndexManifest.java:29 String collationProfile,
|
|
MongoIndexManifest.java:30 Duration expireAfter,
|
|
MongoIndexManifest.java:31 String wildcardProjection,
|
|
MongoIndexManifest.java:32 boolean shardKeySupport,
|
|
MongoIndexManifest.java:33 Set<String> expectedUsage,
|
|
MongoIndexManifest.java:34 String owner,
|
|
MongoIndexManifest.java:35 MongoMetadataOwnership metadataOwnership) {
|
|
MongoIndexManifest.java:36
|
|
MongoIndexManifest.java:37 public MongoIndexManifest {
|
|
MongoIndexManifest.java:38 Objects.requireNonNull(name, "name");
|
|
MongoIndexManifest.java:39 Objects.requireNonNull(keys, "keys");
|
|
MongoIndexManifest.java:40 Objects.requireNonNull(partialFilterExpression, "partialFilterExpression");
|
|
MongoIndexManifest.java:41 Objects.requireNonNull(collationProfile, "collationProfile");
|
|
MongoIndexManifest.java:42 Objects.requireNonNull(wildcardProjection, "wildcardProjection");
|
|
MongoIndexManifest.java:43 Objects.requireNonNull(expectedUsage, "expectedUsage");
|
|
MongoIndexManifest.java:44 Objects.requireNonNull(owner, "owner");
|
|
MongoIndexManifest.java:45 Objects.requireNonNull(metadataOwnership, "metadataOwnership");
|
|
|
|
# 비교용 뷰가 나르는 것
|
|
MongoIndexDescriptorView.java:6 /**
|
|
MongoIndexDescriptorView.java:7 * An index as it actually exists on the server (design §18).
|
|
MongoIndexDescriptorView.java:8 *
|
|
MongoIndexDescriptorView.java:9 * <p>Read through the D4 admin client and reduced to the fields a diff can compare. Ownership is
|
|
MongoIndexDescriptorView.java:10 * inferred here rather than trusted from the manifest, because the whole purpose of the diff is to
|
|
MongoIndexDescriptorView.java:11 * find indexes the manifest does not know about — and some of those belong to encryption or search
|
|
MongoIndexDescriptorView.java:12 * and must never be proposed for deletion.
|
|
MongoIndexDescriptorView.java:13 */
|
|
MongoIndexDescriptorView.java:14 public record MongoIndexDescriptorView(
|
|
MongoIndexDescriptorView.java:15 String collection,
|
|
MongoIndexDescriptorView.java:16 String name,
|
|
MongoIndexDescriptorView.java:17 String keySignature,
|
|
MongoIndexDescriptorView.java:18 boolean unique,
|
|
MongoIndexDescriptorView.java:19 boolean hidden,
|
|
MongoIndexDescriptorView.java:20 MongoMetadataOwnership metadataOwnership) {
|
|
|
|
# 비교 엔진이 실제로 견주는 것
|
|
MongoIndexDiffEngine.java:25 /** Compares one collection's declared indexes with the observed ones. */
|
|
MongoIndexDiffEngine.java:26 public MongoIndexDiff compare(
|
|
MongoIndexDiffEngine.java:27 MongoCollectionManifest manifest, Collection<MongoIndexDescriptorView> observed) {
|
|
MongoIndexDiffEngine.java:28 Objects.requireNonNull(manifest, "manifest");
|
|
MongoIndexDiffEngine.java:29 Objects.requireNonNull(observed, "observed");
|
|
MongoIndexDiffEngine.java:30
|
|
MongoIndexDiffEngine.java:31 Map<String, MongoIndexDescriptorView> observedByName = new LinkedHashMap<>();
|
|
MongoIndexDiffEngine.java:32 for (MongoIndexDescriptorView view : observed) {
|
|
MongoIndexDiffEngine.java:33 observedByName.put(view.name(), view);
|
|
MongoIndexDiffEngine.java:34 }
|
|
MongoIndexDiffEngine.java:35
|
|
MongoIndexDiffEngine.java:36 List<String> create = new ArrayList<>();
|
|
MongoIndexDiffEngine.java:37 List<String> change = new ArrayList<>();
|
|
MongoIndexDiffEngine.java:38 List<String> hide = new ArrayList<>();
|
|
MongoIndexDiffEngine.java:39 List<String> dropCandidates = new ArrayList<>();
|
|
MongoIndexDiffEngine.java:40
|
|
MongoIndexDiffEngine.java:41 for (MongoIndexManifest declared : manifest.indexes()) {
|
|
MongoIndexDiffEngine.java:42 MongoIndexDescriptorView actual = observedByName.get(declared.name());
|
|
MongoIndexDiffEngine.java:43 if (actual == null) {
|
|
MongoIndexDiffEngine.java:44 if (!declared.deprecated()) {
|
|
MongoIndexDiffEngine.java:45 create.add(qualify(manifest, declared.name()));
|
|
MongoIndexDiffEngine.java:46 }
|
|
MongoIndexDiffEngine.java:47 continue;
|
|
MongoIndexDiffEngine.java:48 }
|
|
MongoIndexDiffEngine.java:49 if (!actual.keySignature().equals(declared.keySignature())
|
|
MongoIndexDiffEngine.java:50 || actual.unique() != declared.unique()) {
|
|
MongoIndexDiffEngine.java:51 // Same name, different definition: MongoDB will not silently re-create it, so a rebuild has
|
|
MongoIndexDiffEngine.java:52 // to be planned rather than discovered when a query starts scanning.
|
|
MongoIndexDiffEngine.java:53 change.add(qualify(manifest, declared.name()));
|
|
MongoIndexDiffEngine.java:54 }
|
|
MongoIndexDiffEngine.java:55 if (declared.hidden() && !actual.hidden()) {
|
|
MongoIndexDiffEngine.java:56 hide.add(qualify(manifest, declared.name()));
|
|
MongoIndexDiffEngine.java:57 }
|
|
MongoIndexDiffEngine.java:58 }
|
|
MongoIndexDiffEngine.java:59
|
|
MongoIndexDiffEngine.java:60 for (MongoIndexDescriptorView actual : observedByName.values()) {
|
|
MongoIndexDiffEngine.java:61 if (!actual.metadataOwnership().droppableByApplicationDrift()) {
|
|
MongoIndexDiffEngine.java:62 continue;
|
|
MongoIndexDiffEngine.java:63 }
|
|
MongoIndexDiffEngine.java:64 if (manifest.index(actual.name()).isEmpty()) {
|
|
MongoIndexDiffEngine.java:65 dropCandidates.add(qualify(manifest, actual.name()));
|
|
MongoIndexDiffEngine.java:66 }
|
|
MongoIndexDiffEngine.java:67 }
|
|
MongoIndexDiffEngine.java:68
|
|
MongoIndexDiffEngine.java:69 create.sort(String::compareTo);
|
|
MongoIndexDiffEngine.java:70 change.sort(String::compareTo);
|
|
한 줄에서 declared 값과 actual 값을 견주는 줄 : 3 개
|
|
MongoIndexDiffEngine.java:49 if (!actual.keySignature().equals(declared.keySignature())
|
|
MongoIndexDiffEngine.java:50 || actual.unique() != declared.unique()) {
|
|
MongoIndexDiffEngine.java:55 if (declared.hidden() && !actual.hidden()) {
|
|
[참고] 그 반복문에서 declared 나 actual 이 나오는 줄 전부 :
|
|
MongoIndexDiffEngine.java:42 MongoIndexDescriptorView actual = observedByName.get(declared.name());
|
|
MongoIndexDiffEngine.java:44 if (!declared.deprecated()) {
|
|
MongoIndexDiffEngine.java:45 create.add(qualify(manifest, declared.name()));
|
|
MongoIndexDiffEngine.java:49 if (!actual.keySignature().equals(declared.keySignature())
|
|
MongoIndexDiffEngine.java:50 || actual.unique() != declared.unique()) {
|
|
MongoIndexDiffEngine.java:53 change.add(qualify(manifest, declared.name()));
|
|
MongoIndexDiffEngine.java:55 if (declared.hidden() && !actual.hidden()) {
|
|
MongoIndexDiffEngine.java:56 hide.add(qualify(manifest, declared.name()));
|
|
|
|
# 뷰에 없어서 견줄 수 없는 요소
|
|
매니페스트 요소 : 14 개
|
|
뷰 요소 : 6 개
|
|
각 요소가 뷰에 있는가 :
|
|
name 뷰에 있음
|
|
keys 뷰에 없음
|
|
unique 뷰에 있음
|
|
sparse 뷰에 없음
|
|
hidden 뷰에 있음
|
|
deprecated 뷰에 없음
|
|
partialFilterExpression 뷰에 없음
|
|
collationProfile 뷰에 없음
|
|
expireAfter 뷰에 없음
|
|
wildcardProjection 뷰에 없음
|
|
shardKeySupport 뷰에 없음
|
|
expectedUsage 뷰에 없음
|
|
owner 뷰에 없음
|
|
metadataOwnership 뷰에 있음
|
|
[자기검증] 매니페스트 요소가 14 개가 아니면 실패 : 14 확인
|
|
|
|
# 빈 보고서가 무엇으로 읽히는가
|
|
MongoIndexDiff.java:1 package dev.caskeleton.adapter.outbound.mongo.schema.index;
|
|
MongoIndexDiff.java:2
|
|
MongoIndexDiff.java:3 import java.util.List;
|
|
MongoIndexDiff.java:4 import java.util.Objects;
|
|
MongoIndexDiff.java:5
|
|
MongoIndexDiff.java:6 /**
|
|
MongoIndexDiff.java:7 * The difference between declared and observed indexes (design §18).
|
|
MongoIndexDiff.java:8 *
|
|
MongoIndexDiff.java:9 * <p>Every list is sorted so the diff is byte-identical for the same inputs. A CI artifact that
|
|
MongoIndexDiff.java:10 * reorders between runs cannot be diffed against the previous run, which is most of what an index
|
|
MongoIndexDiff.java:11 * drift report is for.
|
|
MongoIndexDiff.java:12 *
|
|
MongoIndexDiff.java:13 * <p>{@code dropCandidates} is named for what it is: a proposal that still has to pass deprecation,
|
|
MongoIndexDiff.java:14 * hiding, observation and approval before anything is dropped.
|
|
MongoIndexDiff.java:15 */
|
|
MongoIndexDiff.java:16 public record MongoIndexDiff(
|
|
MongoIndexDiff.java:17 List<String> create, List<String> change, List<String> hide, List<String> dropCandidates) {
|
|
MongoIndexDiff.java:18
|
|
MongoIndexDiff.java:19 public MongoIndexDiff {
|
|
MongoIndexDiff.java:20 Objects.requireNonNull(create, "create");
|
|
MongoIndexDiff.java:21 Objects.requireNonNull(change, "change");
|
|
MongoIndexDiff.java:22 Objects.requireNonNull(hide, "hide");
|
|
MongoIndexDiff.java:23 Objects.requireNonNull(dropCandidates, "dropCandidates");
|
|
MongoIndexDiff.java:24 create = List.copyOf(create);
|
|
MongoIndexDiff.java:25 change = List.copyOf(change);
|
|
MongoIndexDiff.java:26 hide = List.copyOf(hide);
|
|
MongoIndexDiff.java:27 dropCandidates = List.copyOf(dropCandidates);
|
|
MongoIndexDiff.java:28 }
|
|
MongoIndexDiff.java:29
|
|
MongoIndexDiff.java:30 /** A diff with no differences. */
|
|
MongoIndexDiff.java:31 public static MongoIndexDiff empty() {
|
|
MongoIndexDiff.java:32 return new MongoIndexDiff(List.of(), List.of(), List.of(), List.of());
|
|
MongoIndexDiff.java:33 }
|
|
MongoIndexDiff.java:34
|
|
MongoIndexDiff.java:35 /** True when declared and observed state already agree. */
|
|
MongoIndexDiff.java:36 public boolean isClean() {
|
|
MongoIndexDiff.java:37 return create.isEmpty() && change.isEmpty() && hide.isEmpty() && dropCandidates.isEmpty();
|
|
MongoIndexDiff.java:38 }
|
|
MongoIndexDiff.java:39
|
|
MongoIndexDiff.java:40 /** A stable, line-oriented rendering suitable for a CI artifact. */
|
|
MongoIndexDiff.java:41 public String render() {
|
|
MongoIndexDiff.java:42 StringBuilder report = new StringBuilder();
|
|
MongoIndexDiff.java:43 appendSection(report, "create", create);
|
|
MongoIndexDiff.java:44 appendSection(report, "change", change);
|
|
MongoIndexDiff.java:45 appendSection(report, "hide", hide);
|
|
MongoIndexDiff.java:46 appendSection(report, "drop-candidate", dropCandidates);
|
|
MongoIndexDiff.java:47 return report.toString();
|
|
MongoIndexDiff.java:48 }
|
|
MongoIndexDiff.java:49
|
|
MongoIndexDiffEngine 이 main 에 나오는 줄 : 1 개
|
|
MongoIndexDiffEngine.java:23 public final class MongoIndexDiffEngine {
|
|
[대조] 시험에서 그 엔진의 compare 를 부르는 줄 : 4 개
|
|
MongoIndexDiffEngineTest.java:30 MongoIndexDiff diff = engine.compare(manifest, observed);
|
|
MongoIndexDiffEngineTest.java:42 MongoIndexDiff diff = engine.compare(applicationManifest(), observed);
|
|
MongoIndexDiffEngineTest.java:49 MongoIndexDiff diff = engine.compare(applicationManifest(), List.of());
|
|
MongoIndexDiffEngineTest.java:61 MongoIndexDiff diff = engine.compare(applicationManifest(), observed);
|