yams/tests/unit/search/search_tracing_catch2_test.cpp
Trevon 3f37e8f564
Sync experimental into main: Search retrieval improvements (#60)
* fix(graph): handle qualified C++ symbols

Signed-off-by: trvon <me@trevon.dev>

* refactor(vector)!: remove dead transition APIs

Split Simeon vector and retrieval linkage so consumers inherit only the runtime surface they use.

BREAKING CHANGE: remove unused async/model/config helpers and the undefined vector-backend factory from public headers.

Signed-off-by: trvon <me@trevon.dev>

* fix(vector): harden PQ snapshot lifecycle

Validate and atomically install persisted generations, revalidate after races, and fall back to exact search when a snapshot is stale. Add the exact-scan reference lane and distinguish observed work counters from unavailable backend metrics.

Signed-off-by: trvon <me@trevon.dev>

* feat(topology): expose route coordinates

Persist chart distortion and local-dimension evidence, retain pre-scalarization route axes, and emit detailed trace rows without treating unavailable vector work as zero.

Signed-off-by: trvon <me@trevon.dev>

* feat(xplan): add vector decision gates

Track binary and corpus identity, preserve missing-work semantics, and add repeated PQ routing, crossover, and rerank plans with quality and cost summaries.

Signed-off-by: trvon <me@trevon.dev>

* feat(topology): formalize coordinate routing

Separate chart geometry from route-risk admission and prove candidate-stage recall and strict-focus boundaries. Record the measured atlas limitations and literature basis for the next construction experiment.

Signed-off-by: trvon <me@trevon.dev>

* fix(search): serialize SymSpell readers

SQLite-backed lookups reuse mutable prepared statements, so shared-reader locking is unsafe. Advance the owned SymSpell revision for batched delete insertion and cover concurrent root lookups under TSAN.

Signed-off-by: trvon <me@trevon.dev>

* fix(vector): make mutations atomic

Preserve stored vectors on replacement and commit failures, reject cross-thread writes during explicit transactions, and restore exact reranking for quantized-primary PQ rows.

* fix(search)!: surface SymSpell write errors

Propagate persistent dictionary failures through metadata ingestion, roll back failed batches, and make clear remove stored terms.

BREAKING CHANGE: SymSpellSearch mutations now return Result values.

* feat(topology): formalize candidate expansion

* feat(vector): add coordinate-aware embeddings

* feat(topology): preserve protected relation cover

* feat(search): add graph candidate rescue

* feat(daemon): expose retrieval hydration telemetry

* feat(xplan): add rescue and hydration gates

* refactor(content): dedupe media handlers

Reuse shared graph presenter helpers and centralize repeated audio/video/archive buffer processing, validation, lifecycle, and stats helper logic.

* feat(lifecycle): prune stale corpus data

Keep cleanup preview-first while retention stays opt-in and long prune work no longer holds IPC responses open.

* feat(search): add topology candidate rescue

Preserve high-value routed candidates across vector backends while keeping exact rerank diagnostics explicit.

* feat(search): expose evidence paths

* docs: define mobile host contract

* formal: model bounded candidate rescue

* fix(prune): inspect extensionless signatures

* fix(prune): tolerate stale extensions

---------

Signed-off-by: trvon <me@trevon.dev>
2026-07-24 19:00:22 -04:00

434 lines
22 KiB
C++

#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
#include <yams/search/search_engine.h>
#include <yams/search/search_metric_keys.h>
#include <yams/search/search_tracing.h>
using yams::search::ComponentResult;
using yams::search::SearchEngineConfig;
using yams::search::SearchTraceCollector;
namespace {
ComponentResult makeComponent(std::string path, float score, ComponentResult::Source source,
size_t rank) {
ComponentResult c;
c.filePath = std::move(path);
c.documentHash = c.filePath;
c.score = score;
c.source = source;
c.rank = rank;
return c;
}
} // namespace
TEST_CASE("buildComponentHitSummaryJson preserves ranked top hit details",
"[search][tracing][catch2]") {
std::vector<ComponentResult> componentResults;
componentResults.push_back(
makeComponent("/tmp/31715818.txt", 0.91f, ComponentResult::Source::Vector, 150));
componentResults.push_back(
makeComponent("/tmp/31715818.txt", 0.89f, ComponentResult::Source::Vector, 151));
componentResults.push_back(
makeComponent("/tmp/11111111.txt", 0.95f, ComponentResult::Source::Vector, 5));
componentResults.push_back(
makeComponent("/tmp/22222222.txt", 0.72f, ComponentResult::Source::Text, 12));
const auto summary = yams::search::buildComponentHitSummaryJson(componentResults, 3);
REQUIRE(summary.contains("vector"));
const auto& vectorSummary = summary.at("vector");
REQUIRE(vectorSummary.contains("unique_top_doc_ids"));
REQUIRE(vectorSummary.contains("top_hits"));
const auto& vectorIds = vectorSummary.at("unique_top_doc_ids");
REQUIRE(vectorIds.is_array());
REQUIRE(vectorIds.size() == 2);
CHECK(vectorIds.at(0).get<std::string>() == "11111111");
CHECK(vectorIds.at(1).get<std::string>() == "31715818");
const auto& topHits = vectorSummary.at("top_hits");
REQUIRE(topHits.is_array());
REQUIRE(topHits.size() == 2);
CHECK(topHits.at(0).at("doc_id").get<std::string>() == "11111111");
CHECK(topHits.at(0).at("rank").get<size_t>() == 6);
CHECK(topHits.at(1).at("doc_id").get<std::string>() == "31715818");
CHECK(topHits.at(1).at("rank").get<size_t>() == 151);
CHECK(topHits.at(1).at("score").get<double>() == Catch::Approx(0.91));
REQUIRE(summary.contains("text"));
const auto& textHits = summary.at("text").at("top_hits");
REQUIRE(textHits.is_array());
REQUIRE(textHits.size() == 1);
CHECK(textHits.at(0).at("doc_id").get<std::string>() == "22222222");
CHECK(textHits.at(0).at("rank").get<size_t>() == 13);
}
TEST_CASE("SearchTraceCollector records per-surface counters under stage[\"counters\"]",
"[search][tracing][catch2]") {
SearchEngineConfig config;
SearchTraceCollector collector(config);
collector.markStageConfigured("vector", true);
collector.markStageAttempted("vector");
collector.recordStageCounter("vector", "backend_raw_return", 300);
collector.recordStageCounter("vector", "post_threshold", 86);
collector.recordStageCounter("vector", "should_narrow_applied", 1);
collector.recordStageCounter("vector", "post_threshold", 92);
const auto summary = collector.buildStageSummaryJson();
REQUIRE(summary.contains("vector"));
const auto& vectorStage = summary.at("vector");
REQUIRE(vectorStage.contains("counters"));
const auto& counters = vectorStage.at("counters");
REQUIRE(counters.is_object());
CHECK(counters.at("backend_raw_return").get<std::int64_t>() == 300);
CHECK(counters.at("post_threshold").get<std::int64_t>() == 92);
CHECK(counters.at("should_narrow_applied").get<std::int64_t>() == 1);
REQUIRE(summary.contains("vector"));
CHECK(summary.at("vector").at("attempted").get<bool>() == true);
}
TEST_CASE("SearchTraceCollector counters are per-stage isolated", "[search][tracing][catch2]") {
SearchEngineConfig config;
SearchTraceCollector collector(config);
collector.recordStageCounter("vector", "shouldSkipSemantic", 0);
collector.recordStageCounter("text", "shouldSkipSemantic", 1);
const auto summary = collector.buildStageSummaryJson();
REQUIRE(summary.at("vector").at("counters").at("shouldSkipSemantic").get<std::int64_t>() == 0);
REQUIRE(summary.at("text").at("counters").at("shouldSkipSemantic").get<std::int64_t>() == 1);
}
TEST_CASE("SearchTraceCollector emits empty counters object when none recorded",
"[search][tracing][catch2]") {
SearchEngineConfig config;
SearchTraceCollector collector(config);
collector.markStageConfigured("embedding", true);
collector.markStageAttempted("embedding");
const auto summary = collector.buildStageSummaryJson();
REQUIRE(summary.at("embedding").contains("counters"));
CHECK(summary.at("embedding").at("counters").is_object());
CHECK(summary.at("embedding").at("counters").empty());
}
TEST_CASE("SearchTraceCollector only retains document identifiers for explicit traces",
"[search][tracing][catch2]") {
SearchEngineConfig config;
const std::vector<ComponentResult> results{
makeComponent("/tmp/b.txt", 0.8F, ComponentResult::Source::Vector, 0),
makeComponent("/tmp/a.txt", 0.7F, ComponentResult::Source::Vector, 1),
};
SearchTraceCollector compactCollector(config, false);
compactCollector.markStageResult("vector", results, 10, true);
const auto compact = compactCollector.buildStageSummaryJson().at("vector");
CHECK(compact.at("unique_doc_count").get<std::size_t>() == 2);
CHECK(compact.at("unique_doc_ids").empty());
SearchTraceCollector tracedCollector(config, true);
tracedCollector.markStageResult("vector", results, 10, true);
const auto traced = tracedCollector.buildStageSummaryJson().at("vector");
CHECK(traced.at("unique_doc_count").get<std::size_t>() == 2);
CHECK(traced.at("unique_doc_ids") == nlohmann::json::array({"a", "b"}));
}
TEST_CASE("SearchTraceCollector records non-document stage outcomes without fake hits",
"[search][tracing][catch2]") {
SearchEngineConfig config;
SearchTraceCollector collector(config);
collector.markValueStageResult("embedding", true, 2500);
collector.markValueStageResult("concepts", false, 1000);
const auto summary = collector.buildStageSummaryJson();
const auto& embedding = summary.at("embedding");
CHECK(embedding.at("attempted").get<bool>());
CHECK(embedding.at("contributed").get<bool>());
CHECK(embedding.at("raw_hit_count").get<std::size_t>() == 1);
CHECK(embedding.at("unique_doc_count").get<std::size_t>() == 0);
CHECK(embedding.at("score_stats_valid").get<bool>());
CHECK(embedding.at("min_score").get<double>() == Catch::Approx(1.0));
CHECK(embedding.at("max_score").get<double>() == Catch::Approx(1.0));
CHECK(embedding.at("duration_ms").get<double>() == Catch::Approx(2.5));
const auto& concepts = summary.at("concepts");
CHECK(concepts.at("attempted").get<bool>());
CHECK_FALSE(concepts.at("contributed").get<bool>());
CHECK(concepts.at("raw_hit_count").get<std::size_t>() == 0);
CHECK_FALSE(concepts.at("score_stats_valid").get<bool>());
}
TEST_CASE("recordTopologyRoutingDebug emits the legacy topology key set",
"[search][tracing][topology][catch2]") {
SearchEngineConfig config;
config.topologyRouteScoringMode = SearchEngineConfig::TopologyRouteScoringMode::SizeWeighted;
config.topologySparseDenseAlpha = 0.25f;
config.topologyMinRouteScore = 0.5f;
yams::search::TopologyRoutingSessionResult session;
session.loadAttempted = true;
session.loadSucceeded = true;
session.artifactAdmitted = true;
session.applied = true;
session.artifactsFresh = true;
session.topologyEpoch = 7;
session.certificate.constructionFingerprint = "0123456789abcdef";
session.certificate.allowedDocumentHashes = {"hash-a", "hash-c"};
session.certificate.selectedCoverIds = {"cluster-a"};
session.certificate.selectedProtectedRelationFiberIds = {"fiber-a", "fiber-b"};
session.routedClusters = 2;
session.availableRoutes = 3;
session.routedDocs = 5;
session.routesRejected = 1;
session.addedCandidates = 4;
session.duplicateCandidates = 3;
session.staleCandidates = 1;
session.routeBoundaryScoreMargin = 0.17F;
session.confidenceAbstained = true;
session.routeRepresentativeDistanceEvaluations = 7;
session.routeRepresentativeCountMax = 4;
session.routeAnnUsed = true;
session.routeAnnCandidates = 6;
session.routeAnnDistanceEvaluations = 4;
session.routeExactRepresentativeDistanceEvaluations = 3;
session.certificate.admission.coverMaterialization =
yams::search::TopologyProofObligationStatus::Violated;
session.certificate.admission.protectedFibersRepresented =
yams::search::TopologyProofObligationStatus::Satisfied;
session.certificate.admission.certificateSaturatesProtectedFibers =
yams::search::TopologyProofObligationStatus::Violated;
session.certificate.admission.selectedCoverDocuments = 5;
session.certificate.admission.materializedCoverDocuments = 4;
session.routeEvidence = {
yams::search::TopologyRouteEvidence{
.clusterId = "cluster-a",
.semanticCost = 0.1F,
.sparseCost = 0.2F,
.persistencePenalty = 0.3F,
.cohesionPenalty = 0.4F,
.sizePenalty = 0.5F,
.routeScore = 0.9F,
.scoreEligible = true,
.inSelectedPrefix = true,
},
yams::search::TopologyRouteEvidence{
.clusterId = "cluster-b",
.sparseCost = 0.8F,
.persistencePenalty = 0.7F,
.cohesionPenalty = 0.6F,
.sizePenalty = 0.5F,
.routeScore = 0.3F,
.scoreEligible = false,
.inSelectedPrefix = false,
},
};
session.addedCandidateHashes = {"hash-a", "hash-b"};
session.routedCandidateDocIds = {"doc-c", "doc-a", "doc-a"};
session.routedCandidateHashes = {"hash-c", "hash-a"};
session.graphNeighborTrace.collected = true;
session.graphNeighborTrace.edgeFetchLimit = 128;
session.graphNeighborTrace.fetchTruncatedSeedCount = 1;
session.graphNeighborTrace.seedDocumentIds = {"seed-a", "seed-b"};
session.graphNeighborTrace.seedUnresolvedCount = 1;
session.graphNeighborTrace.relationCandidateCount = 4;
session.graphNeighborTrace.relationCandidateDocumentIds = {"raw-b", "raw-a"};
session.graphNeighborTrace.relationUnresolvedCount = 2;
session.graphNeighborTrace.fetchedCandidateCount = 3;
session.graphNeighborTrace.fetchedCandidateDocumentIds = {"fetched-b", "fetched-a"};
session.graphNeighborTrace.fetchedUnresolvedCount = 1;
session.graphNeighborTrace.eligibleCandidateCount = 2;
session.graphNeighborTrace.eligibleCandidateDocumentIds = {"eligible-b", "eligible-a"};
session.graphNeighborTrace.eligibleUnresolvedCount = 0;
session.candidateStructureEvidence.emplace("hash-a",
yams::search::TopologyCandidateStructureEvidence{
.scaleAgreement = 0.8F,
.overlapSupport = 0.4F,
.persistenceSupport = 0.6F,
.cohesionSupport = 0.9F,
.bridgeSupport = 0.3F,
.densitySupport = 0.7F,
});
session.candidateStructureEvidence.emplace("hash-c",
yams::search::TopologyCandidateStructureEvidence{
.scaleAgreement = 0.2F,
.overlapSupport = 0.2F,
.persistenceSupport = 0.8F,
.cohesionSupport = 0.3F,
.bridgeSupport = 0.2F,
.densitySupport = 0.6F,
});
session.timings.totalMicros = 100;
session.timings.loadMicros = 10;
session.timings.validateMicros = 20;
session.timings.requestPrepMicros = 30;
session.timings.routeMicros = 40;
session.timings.clusterLookupMicros = 50;
session.timings.docLookupMicros = 60;
session.timings.candidateInsertMicros = 70;
yams::search::SearchResponse response;
yams::search::recordTopologyRoutingDebug(
response, config, SearchEngineConfig::TopologyRoutingMode::HybridAssist, session,
"skip-reason", 42, yams::search::TopologyRoutingDebugOptions{.includeDetailedTrace = true});
const auto& debug = response.debugStats;
CHECK(debug.at("topology_routing_mode") == "hybrid_assist");
CHECK(debug.at("topology_route_scoring_mode") == "size_weighted");
CHECK(debug.at("topology_sparse_dense_alpha") == std::to_string(0.25f));
CHECK(debug.at("topology_min_route_score") == std::to_string(0.5f));
CHECK(debug.at("topology_weak_query_enabled") == "1");
CHECK(debug.at("topology_weak_query_load_attempted") == "1");
CHECK(debug.at("topology_weak_query_load_succeeded") == "1");
CHECK(debug.at("topology_artifact_admitted") == "1");
CHECK(debug.at("topology_weak_query_applied") == "1");
CHECK(debug.at("topology_weak_query_narrow_applied") == "0");
CHECK(debug.at("topology_weak_query_skip_reason") == "skip-reason");
CHECK(debug.at("topology_weak_query_routes_rejected") == "1");
CHECK(debug.at("topology_weak_query_routed_clusters") == "2");
CHECK(debug.at("topology_route_available_count") == "3");
CHECK(debug.at("topology_route_boundary_score_margin") == std::to_string(0.17F));
CHECK(debug.at("topology_route_confidence_abstained") == "1");
CHECK(debug.at("topology_route_representative_distance_evaluations") == "7");
CHECK(debug.at("topology_route_representative_count_max") == "4");
CHECK(debug.at("topology_route_ann_used") == "1");
CHECK(debug.at("topology_route_ann_candidates") == "6");
CHECK(debug.at("topology_route_ann_distance_evaluations") == "4");
CHECK(debug.at("topology_route_exact_representative_distance_evaluations") == "3");
CHECK(debug.at("topology_route_evidence_count") == "2");
CHECK(debug.at("topology_route_narrow_proposed") == "1");
CHECK(debug.at("topology_route_action") == "augment");
CHECK(debug.at("topology_route_selected_cover_count") == "1");
CHECK(debug.at("topology_route_selected_protected_relation_fiber_count") == "2");
CHECK(debug.at("topology_route_admission_eligible") == "0");
CHECK(debug.at("topology_route_admission_denial_reason") == "cover_materialization_incomplete");
CHECK(debug.at("topology_route_protected_relation_coverage_status") == "unavailable");
CHECK(debug.at("topology_route_risk_status") == "unavailable");
CHECK(debug.at("topology_route_work_status") == "unavailable");
CHECK(debug.at("topology_route_cover_materialization_status") == "violated");
CHECK(debug.at("topology_route_protected_fibers_represented_status") == "satisfied");
CHECK(debug.at("topology_route_certificate_saturates_protected_fibers_status") == "violated");
CHECK(debug.at("topology_route_selected_cover_documents") == "5");
CHECK(debug.at("topology_route_materialized_cover_documents") == "4");
const auto evidenceRows = nlohmann::json::parse(debug.at("topology_route_evidence_rows"));
REQUIRE(evidenceRows.size() == 2);
CHECK(evidenceRows.at(0).at("cluster_id") == "cluster-a");
CHECK(evidenceRows.at(0).at("semantic_cost").get<float>() == Catch::Approx(0.1F));
CHECK(evidenceRows.at(0).at("in_selected_prefix") == true);
CHECK(evidenceRows.at(0).at("graph_cost").is_null());
CHECK(evidenceRows.at(1).at("semantic_cost").is_null());
CHECK(evidenceRows.at(1).at("score_eligible") == false);
CHECK(evidenceRows.at(1).at("in_selected_prefix") == false);
CHECK(debug.at("topology_weak_query_routed_docs") == "5");
CHECK(debug.at("topology_weak_query_added_candidates") == "4");
CHECK(debug.at("topology_weak_query_duplicate_candidates") == "3");
CHECK(debug.at("topology_weak_query_stale_candidates") == "1");
CHECK(debug.at("topology_weak_query_added_candidate_hashes") == "hash-a\thash-b");
CHECK(debug.at("topology_weak_query_allowed_candidate_hashes") == "hash-a\thash-c");
CHECK(debug.at("topology_weak_query_allowed_candidate_doc_ids") == "doc-a\tdoc-c");
CHECK(debug.at("topology_graph_trace_collected") == "1");
CHECK(debug.at("topology_graph_edge_fetch_limit") == "128");
CHECK(debug.at("topology_graph_fetch_truncated_seed_count") == "1");
CHECK(debug.at("topology_graph_seed_doc_ids") == "seed-a\tseed-b");
CHECK(debug.at("topology_graph_seed_unresolved_count") == "1");
CHECK(debug.at("topology_graph_relation_candidate_count") == "4");
CHECK(debug.at("topology_graph_relation_candidate_doc_ids") == "raw-b\traw-a");
CHECK(debug.at("topology_graph_relation_unresolved_count") == "2");
CHECK(debug.at("topology_graph_fetched_candidate_count") == "3");
CHECK(debug.at("topology_graph_fetched_candidate_doc_ids") == "fetched-b\tfetched-a");
CHECK(debug.at("topology_graph_fetched_unresolved_count") == "1");
CHECK(debug.at("topology_graph_eligible_candidate_count") == "2");
CHECK(debug.at("topology_graph_eligible_candidate_doc_ids") == "eligible-b\teligible-a");
CHECK(debug.at("topology_graph_eligible_unresolved_count") == "0");
CHECK(debug.at("topology_weak_query_total_candidates") == "42");
CHECK(debug.at("topology_structure_candidate_count") == "2");
CHECK(std::stof(debug.at("topology_structure_scale_agreement_mean")) == Catch::Approx(0.5F));
CHECK(std::stof(debug.at("topology_structure_overlap_support_mean")) == Catch::Approx(0.3F));
CHECK(std::stof(debug.at("topology_structure_persistence_support_mean")) ==
Catch::Approx(0.7F));
CHECK(std::stof(debug.at("topology_structure_cohesion_support_mean")) == Catch::Approx(0.6F));
CHECK(std::stof(debug.at("topology_structure_bridge_support_mean")) == Catch::Approx(0.25F));
CHECK(std::stof(debug.at("topology_structure_density_support_mean")) == Catch::Approx(0.65F));
CHECK(debug.at("topology_ready") == "1");
CHECK(debug.at("topology_artifacts_fresh") == "1");
CHECK(debug.at("topology_epoch") == "7");
CHECK(debug.at("topology_construction_fingerprint") == "0123456789abcdef");
const auto& timing = response.componentTimingMicros;
CHECK(timing.at("topology_weak_query") == 100);
CHECK(timing.at("topology_load") == 10);
CHECK(timing.at("topology_validate") == 20);
CHECK(timing.at("topology_request_prep") == 30);
CHECK(timing.at("topology_route") == 40);
CHECK(timing.at("topology_cluster_lookup") == 50);
CHECK(timing.at("topology_doc_lookup") == 60);
CHECK(timing.at("topology_candidate_insert") == 70);
}
TEST_CASE("recordTopologyRoutingDebug omits readiness and timing when load not attempted",
"[search][tracing][topology][catch2]") {
SearchEngineConfig config;
yams::search::TopologyRoutingSessionResult session;
yams::search::SearchResponse response;
yams::search::recordTopologyRoutingDebug(response, config,
SearchEngineConfig::TopologyRoutingMode::Disabled,
session, "disabled", 0);
const auto& debug = response.debugStats;
CHECK(debug.at("topology_weak_query_enabled") == "0");
CHECK(debug.at("topology_weak_query_load_attempted") == "0");
CHECK(debug.at("topology_artifact_admitted") == "0");
CHECK(debug.at("topology_weak_query_skip_reason") == "disabled");
CHECK_FALSE(debug.contains("topology_ready"));
CHECK_FALSE(debug.contains("topology_epoch"));
CHECK_FALSE(debug.contains("topology_weak_query_added_candidate_hashes"));
CHECK_FALSE(debug.contains("topology_weak_query_allowed_candidate_hashes"));
CHECK_FALSE(debug.contains("topology_weak_query_allowed_candidate_doc_ids"));
CHECK(response.componentTimingMicros.empty());
}
TEST_CASE("recordIndexReadinessDebug emits readiness flags for both pipelines",
"[search][tracing][catch2]") {
yams::search::IndexFreshnessSnapshot freshness;
freshness.lexicalReady = true;
freshness.vectorReady = false;
freshness.kgReady = true;
freshness.topologyReady = false;
freshness.topologyArtifactsFresh = true;
freshness.topologyEpoch = 12;
std::unordered_map<std::string, std::string> debug;
yams::search::recordIndexReadinessDebug(debug, freshness);
CHECK(debug.at("search_engine_ready") == "1");
CHECK(debug.at("vector_ready") == "0");
CHECK(debug.at("kg_ready") == "1");
CHECK(debug.at("topology_ready") == "0");
CHECK(debug.at("topology_artifacts_fresh") == "1");
CHECK(debug.at("topology_epoch") == "12");
CHECK(debug.size() == 6);
}
TEST_CASE("metric key constants are stable strings", "[search][tracing][catch2]") {
namespace metrics = yams::search::metrics;
CHECK(metrics::kSearchPipelineName == "search_pipeline_name");
CHECK(metrics::kTopologyArtifactAdmitted == "topology_artifact_admitted");
CHECK(metrics::kTopologyWeakQueryApplied == "topology_weak_query_applied");
CHECK(metrics::kTopologyWeakQueryAddedCandidates == "topology_weak_query_added_candidates");
CHECK(metrics::kTopologyWeakQueryDuplicateCandidates ==
"topology_weak_query_duplicate_candidates");
CHECK(metrics::kTopologyRouteBestScore == "topology_route_best_score");
CHECK(metrics::kTopologyRouteMeanAcceptedScore == "topology_route_mean_accepted_score");
CHECK(metrics::kTopologyRouteAcceptedCount == "topology_route_accepted_count");
CHECK(metrics::kTopologyRouteEvidenceCount == "topology_route_evidence_count");
CHECK(metrics::kTopologyRouteEvidenceRows == "topology_route_evidence_rows");
CHECK(metrics::kTopologySeedCount == "topology_seed_count");
CHECK(metrics::kTopologySeedCoverageCount == "topology_seed_coverage_count");
}