# 매니페스트가 선언하는 것
  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);
