yams/formal/topology/Yams/Topology/TopologyInfluence.lean
Trevon a3d065dd8d
feat(search): make topology-assisted retrieval the default (#57)
* fix(release): repair package repo publish

Arch repo metadata and R2 uploads need to be reproducible from CI, with retry hardening for transient Cloudflare failures.

* fix(daemon): stop shutdown log storm

Init waiter must treat ShuttingDown as terminal so daemon shutdown tests do not flood Meson logs while the daemon is stopping.

* refactor(cli): delegate serve to MCP binary

Keep yams serve UX while removing MCP server linkage from yams-cli. The standalone MCP binary owns stdio serving and exits on stdin EOF.

* refactor(cli): move daemon path helpers

Keep CLI path resolution out of YamsDaemon so later link cuts can separate CLI and daemon internals. Full yams_daemon removal still needs a socket-only daemon-client split.

* refactor(daemon): split embedded client

* perf(search): add lean-guided benchmark gates

Add Lean topology/search models, ingestion ablation docs, and focused regression tests for topology and compression threshold behavior.

Extract search observability, keep benchmark-only ablation controls, and make retrieval quality benchmarks default to Simeon while preserving explicit ONNX opt-in.

* refactor(config): move daemon policies

* refactor(daemon): extract plugin runtime

* refactor(daemon): untangle service coordinators

Untangle CLI/MCP from daemon service coordinators and the daemon static lib.

- MetadataRepository uses a daemon-neutral tree-diff callback. The
  GraphComponent wrapper moves to src/daemon/components/metadata_graph_bridge.cpp,
  owned by the daemon library.
- WriteCoordinator moves to a dedicated build-only yams_write_coordinator
  static lib. Drop ResourceGovernor coupling inside the writer loop; batch
  sizing still respects config.maxBatchSize.
- AppContext gains enqueuePostIngest callback. ServiceManager wires it to
  its own enqueuePostIngest; app services no longer call ServiceManager
  directly.
- Embedding repair uses BulkIngestLease/BeginBulkIngestCallback instead of
  concrete VectorIndexCoordinator::BulkScope. Daemon dispatchers provide a
  lease factory backed by the real coordinator.
- CLI daemon_command uses config helpers instead of YamsDaemon::getXDGRuntimeDir.
- Remove explicit dependency('yams_daemon') from src/cli and tools/yams-mcp.
- Declare missing yams_extraction/yams_ingest deps on yams_app_services so
  app services link cleanly without daemon.

Validated:
  meson compile -C build/release -j4 yams-daemon yams-cli yams-mcp-server
  ninja -C build/release -t commands tools/yams-cli/yams-cli tools/yams-mcp/yams-mcp-server | grep -c libyams_daemon.a -> 0
  yams-cli --version, yams-mcp-server init, yams serve --help smoke tests

Size drop on macOS release: yams-cli 41M -> 32M, yams-mcp-server 37M -> 22M.

* fix(cli): keep stdin add local fallback

No embedded daemon now. When yams add - buffers stdin and daemon IPC is missing, store via local app services instead of failing socket_missing.

Validated:
  meson compile -C build/debug -j4 yams-cli catch2_cli_commands
  MESON_BUILD_ROOT=/Users/trevon/work/tools/yams/build/debug build/debug/tests/catch2_cli_commands 'AddCommand - reads from piped stdin and stores content'
  MESON_BUILD_ROOT=/Users/trevon/work/tools/yams/build/debug build/debug/tests/catch2_cli_commands
  meson compile -C build/release -j4 yams-cli yams-mcp-server
  meson compile -C build/asan-api -j2 yams-cli catch2_cli_commands
  MESON_BUILD_ROOT=/Users/trevon/work/tools/yams/build/asan-api ASAN_OPTIONS=halt_on_error=1:abort_on_error=1:print_summary=1 build/asan-api/tests/catch2_cli_commands 'AddCommand - reads from piped stdin and stores content'
  ninja -C build/release -t commands tools/yams-cli/yams-cli tools/yams-mcp/yams-mcp-server | grep libyams_daemon.a -> no output

* feat(bench): add xplan multi-arm experiment harness

Unified plan runner, ablation axes, REPORT/ablation docs, and
KPI snapshot so optimization loops stop living in shell matrices.

* feat(formal): expand topology/search Lean contracts

Boundary models for search provenance, construction gates,
and cluster/route objectives so Lean guides correctness while
xplan ranks performance levers.

* feat(search,topology): construction purity + graph expansion path

Raise topology build defaults (min edge 0.25, max component 64) and
split oversized CC pieces so hybrid assist is quality-positive on
scifact. Add GraphNeighbors expansion with medoid-anchor fallback,
structured helpers instead of nested session logic, and BEIR-default
xplan plans. Search engine extraction (fanout/classic pipeline) rides
along as the integration surface for routing.

* feat(topology): add KMeans engine and SGC identity contracts

Register kmeans in the factory with deterministic spherical k-means
over embeddings, and enforce SGC document-identity invariants in
debug builds with matching unit coverage.

* feat(daemon): topology readiness gates and selectable reranker

Drive topologyReady from published epoch/freshness/rebuild/dirty
ingest state (not just KG drain), surface those fields in search
stats, and wire onnx|simeon|auto|none cross-reranker selection.

* chore(search): drop meta_path tuner knobs

Meta-path routing is gone; remove dead TunedParams fields and the
ConfigResolver test for [search.meta_path].

* feat(bench): add topology_purity_validate plan

Repeats=3 validation matrix for construction-purity winners vs
topo_off so MRR deltas can clear run-to-run noise.

* chore: format add_command and config include order

Mechanical clang-format / include reorder only; no behavior change.

* feat(bench): decision-grade xplan loop for search engine

Document search-engine measurement in AGENTS; harden xplan with
repeats=3 plans, quality×latency primaries, mechanism metrics from
debug.jsonl, multi-rep log promotion, and archive superseded topology
plans.

* feat(graph): denser product semantic_neighbor topK

Raise default YAMS_GRAPH_SEMANTIC_TOPK fallback from 4 to 8 so product
graphs leave fewer degree-0 docs for GraphNeighbors expansion.

* feat(search): extract topology assist and cross-rerank seams

Pull topology assist into search_topology_stage (seed merge, skip
reasons, optional vector seed probe default 0). Extract cross_rerank
compute from search_engine. Wire probe via typed config/ConfigResolver.

* feat(bench): product-path search engine ablation plans

Add topology-off component, vector-weight, and tiered-execution xplan
plans (repeats=3) and wire them into the AGENTS measurement loop table.

* fix(ci): repair pre-push fallbacks

* feat(bench): lock-free mid-run builds + multi-corpus gates

xplan skips meson rebuild when the quality binary exists (flock +
YAMS_BENCH_FORCE_BUILD override) to stop multi-arm link races. Add
graph_vector weight and nfcorpus transfer-gate plans; document in AGENTS.

* fix(daemon): keep SearchEngine alive via shared_ptr cache

getCachedEngine previously published a raw pointer that metrics polling
used through rebuild, racing destructor of SearchEngine::Impl (TSAN).
Store and return a shared_ptr snapshot so diagnostic readers pin the
engine for the duration of getConfig/health checks.

Also adds the cautious vectorWeight 0.30→0.20 multi-corpus xplan plan.

* feat(search): route vectors through cached topology

Build and reuse validated topology snapshots, rank routed members by query similarity, and narrow the vector leg to the accepted document set with explicit work diagnostics.

Remove the retired HDBSCAN engine and vendor dependency, preserve typed topology policy through search tuning, and cover the runtime/configuration contracts.

* feat(bench): consolidate search decision plans

Enforce deterministic experiment identity and declared baselines, expose routed/global vector work metrics, and register the focused benchmark contracts.

Promote the compact repeats=3 routing-budget gate, archive superseded topology mechanism screens, and add multi-corpus product calibration plans.

* feat(search): refine topology vector routing

* fix(search): propagate daemon readiness

* feat(bench): measure generalized memory health

* fix(cli): honor in-process fallbacks

* ci: harden smolvm debug builds

* perf(search): route topology through filtered ANN

* feat(bench): measure read-write pressure

* perf(metadata): index symbol lookups on read pool

* fix(daemon): isolate startup reads and maintenance

* feat(bench): replay isolated generalized corpora

* feat(search): add evidence routing pipeline

Replace the blocking smolvm pre-push path with host ASan/TSan validation, harden sanitizer-sensitive smoke tests, and add recovery cleanup for malformed SQLite WAL sidecars.

* refactor(search): unify topology evidence path

Remove classic and sidecar fusion lanes so topology augments the recall-preserving ANN and lexical candidate pool through one product path.

* refactor(bench): prune superseded search lanes

Measure the topology-plus-ANN product path with stable experiment identity and remove obsolete sidecar and tiered controls.

* docs(search): retire stale experiment notes

Keep the benchmark handoff focused on the topology-plus-ANN product lane and its active decision plans.

* refactor(search): prune dead search surfaces

Remove unused fusion, reranker, policy, and router abstractions.
Bound topology materialization and keep document trace payloads opt-in.

* fix(cli): clear build warnings

* fix(xplan): reject stale run identity

* refactor(search)!: remove legacy query stack

Replace the unused AST/ranker/post-processor surface with focused facet aggregation used by product search.

BREAKING CHANGE: removes query parser, result ranker, search filter, and SearchResults container APIs from yams_search.

* refactor(search): type value stage traces

Remove synthetic ComponentResult markers for embedding and concept stages. Preserve failure and timeout states instead of overwriting them with fake successful results.

* refactor(search): isolate benchmark env overlays

Move test-only search environment parsing behind an injectable internal adapter. Keep all existing overlay keys and tuner pin semantics while restoring SearchEngineBuilder to composition.

* refactor(search): unify topology routing options

Snapshot topology policy once at the assist boundary and pass it through routing sessions and diagnostics. Remove repeated scalar copying without changing routing behavior or metric keys.

* fix(search): unify tuner config ownership

Use one complete config-to-params overlay for runtime seeding and no-tuner policy resolution. Reseed attached tuners on setConfig so reported and executed configuration cannot diverge.

* fix(search): snapshot tuner state per query

Read tuned config, params, and state under one lock before routing. Add a TSAN correlation test so adaptive observation cannot produce a torn logical query snapshot.

* fix(search): enforce topology routing contract

Generated configs must preserve hybrid-assist shadow routing, while daemon-backed search must abstain when published topology artifacts are stale. Keep standalone validation and global ANN recall intact.

* fix(daemon): let SQLite reclaim its file

Directly resizing a live database from sampled page counts can truncate pages allocated by concurrent writers. VACUUM must remain the only file-reclamation mechanism.

* fix(metadata): tolerate missing symbol FTS

Readiness lookups can race an unavailable optional FTS migration. Retain the indexed fast path, but retry against the base table when prepare or cached-statement execution proves the capability absent.

* build(fuzz): drop removed query parser target

The optional AFL build still referenced the deleted legacy query parser API and could no longer compile.

* fix(bench): include writes in drain oracle

A zero post-ingest queue is not durable drain while coordinated writes remain queued or in flight.

* fix(daemon): vacuum padded WAL databases safely
2026-07-13 20:26:43 -04:00

498 lines
22 KiB
Lean4

import Yams.Core
import Yams.Topology.Artifacts
import Yams.Topology.SearchEngine
namespace Yams.SearchEngine
/-- Product-level topology routing policy. The C++ benchmark can map typed config
or benchmark-only overrides onto these modes, while Lean states what each mode is
allowed to change. -/
inductive TopologyRoutingMode where
| disabled
| weakQueryOnly
| hybridAssist
| rerankOnly
deriving Repr, BEq, DecidableEq
/-- Bounded topology influence configuration. -/
structure TopologyInfluenceConfig where
mode : TopologyRoutingMode := .weakQueryOnly
maxClusters : Nat := 2
maxDocs : Nat := 64
deriving Repr, BEq
/-- Minimal persisted topology route: a cluster and its member document IDs.
Uses Yams.Core types for document IDs. -/
structure TopologyCluster where
id : Yams.Core.ClusterId
docs : List Yams.Core.DocumentId := []
deriving Repr, BEq
/-- Unbounded union of the documents represented by a collection of topology
fragments. Fragmentation is useful when each component is a small local memory
neighborhood; retrieval may select and union several such neighborhoods. -/
def fragmentUnionDocs (fragments : List TopologyCluster) : List Yams.Core.DocumentId :=
fragments.flatMap (fun fragment => fragment.docs)
/-- `fragments` refine a coarse semantic region when they neither invent
documents outside it nor lose any document from it. No lower bound on fragment
size is required: singleton fragments are valid refinements. -/
def IsFragmentationOf (coarse : TopologyCluster) (fragments : List TopologyCluster) : Prop :=
(∀ fragment ∈ fragments, ∀ doc ∈ fragment.docs, doc ∈ coarse.docs) ∧
(∀ doc ∈ coarse.docs, ∃ fragment ∈ fragments, doc ∈ fragment.docs)
/-- Fragmentation does not lose recoverability: every document represented by
the coarse region remains present in the union of its fragments. -/
theorem fragmentation_preservesDocumentCoverage
{coarse : TopologyCluster}
{fragments : List TopologyCluster}
{doc : Yams.Core.DocumentId}
(hFragmentation : IsFragmentationOf coarse fragments)
(hDoc : doc ∈ coarse.docs) :
doc ∈ fragmentUnionDocs fragments := by
rcases hFragmentation.2 doc hDoc with ⟨fragment, hFragment, hMember⟩
simp [fragmentUnionDocs]
exact ⟨fragment, hFragment, hMember⟩
/-- Refinement cannot invent a document outside the coarse region. -/
theorem fragmentation_noArbitraryDocuments
{coarse : TopologyCluster}
{fragments : List TopologyCluster}
{doc : Yams.Core.DocumentId}
(hFragmentation : IsFragmentationOf coarse fragments)
(hDoc : doc ∈ fragmentUnionDocs fragments) :
doc ∈ coarse.docs := by
simp [fragmentUnionDocs] at hDoc
rcases hDoc with ⟨fragment, hFragment, hMember⟩
exact hFragmentation.1 fragment hFragment doc hMember
/-- Selecting another fragment is monotone before budget truncation: it can add
coverage but cannot remove a document already exposed by earlier fragments. -/
theorem fragmentUnionDocs_append_monotone
(selected additional : List TopologyCluster)
{doc : Yams.Core.DocumentId}
(hDoc : doc ∈ fragmentUnionDocs selected) :
doc ∈ fragmentUnionDocs (selected ++ additional) := by
simp [fragmentUnionDocs] at hDoc ⊢
exact Or.inl hDoc
/-- Search-path observability for topology routing. This mirrors the benchmark
counters used to decide whether a measured A/B run actually exercised topology. -/
structure TopologyRouteObservation where
enabled : Bool := false
loadAttempted : Bool := false
loadSucceeded : Bool := false
applied : Bool := false
addedCandidates : Nat := 0
routedClusters : Nat := 0
routedDocs : Nat := 0
deriving Repr, BEq
/-- Modes that may expand the candidate set. Rerank-only is intentionally false:
it may use topology to alter scores, but not to add documents. -/
def topologyMayExpand (mode : TopologyRoutingMode) (weakTier1Query : Bool) : Bool :=
match mode with
| .disabled => false
| .weakQueryOnly => weakTier1Query
| .hybridAssist => true
| .rerankOnly => false
/-- Modes that may load topology artifacts for either expansion or reranking. -/
def topologyMayLoad (mode : TopologyRoutingMode) (weakTier1Query : Bool) : Bool :=
match mode with
| .disabled => false
| .weakQueryOnly => weakTier1Query
| .hybridAssist => true
| .rerankOnly => true
/-- Candidate-expansion cluster window. -/
def selectedTopologyClusters
(cfg : TopologyInfluenceConfig) (clusters : List TopologyCluster) : List TopologyCluster :=
clusters.take cfg.maxClusters
/-- Flatten selected cluster members before the document cap. -/
def selectedTopologyDocsUnbounded
(cfg : TopologyInfluenceConfig) (clusters : List TopologyCluster) : List Yams.Core.DocumentId :=
(selectedTopologyClusters cfg clusters).flatMap (fun c => c.docs)
/-- Bounded candidate-expansion document IDs. -/
def selectedTopologyDocs
(cfg : TopologyInfluenceConfig) (clusters : List TopologyCluster) : List Yams.Core.DocumentId :=
(selectedTopologyDocsUnbounded cfg clusters).take cfg.maxDocs
/-- A bounded fragment route is safe for a protected set when the selected
document budget explicitly covers every protected document. The empirical router
must establish this premise; fragmentation itself does not make it false. -/
def FragmentBudgetCovers
(cfg : TopologyInfluenceConfig)
(clusters : List TopologyCluster)
(protectedDocs : List Yams.Core.DocumentId) : Prop :=
∀ doc ∈ protectedDocs, doc ∈ selectedTopologyDocs cfg clusters
/-- Additive retrieval needs a weaker but useful certificate: at least one
protected/relevant document is exposed by the selected fragment budget. This is
not sufficient for replacing global ANN, but it can augment vector recall. -/
def FragmentBudgetHits
(cfg : TopologyInfluenceConfig)
(clusters : List TopologyCluster)
(protectedDocs : List Yams.Core.DocumentId) : Prop :=
∃ doc ∈ protectedDocs, doc ∈ selectedTopologyDocs cfg clusters
/-- Every protected document survives a fragment budget that certifies coverage. -/
theorem selectedTopologyDocs_preservesProtected
{cfg : TopologyInfluenceConfig}
{clusters : List TopologyCluster}
{protectedDocs : List Yams.Core.DocumentId}
(hCoverage : FragmentBudgetCovers cfg clusters protectedDocs) :
∀ doc ∈ protectedDocs, doc ∈ selectedTopologyDocs cfg clusters := by
exact hCoverage
/-- A fragment hit certificate yields an explicit protected document in the
bounded selected set. -/
theorem selectedTopologyDocs_hitsProtected
{cfg : TopologyInfluenceConfig}
{clusters : List TopologyCluster}
{protectedDocs : List Yams.Core.DocumentId}
(hHit : FragmentBudgetHits cfg clusters protectedDocs) :
∃ doc ∈ protectedDocs, doc ∈ selectedTopologyDocs cfg clusters := by
exact hHit
/-- Confidence-bearing decision for the live hard-narrowing path. An unconfident
decision is an explicit abstention and must preserve the baseline candidate set. -/
structure TopologyNarrowingDecision where
confident : Bool := false
allowedDocs : List Yams.Core.DocumentId := []
deriving Repr, BEq
/-- Restrict one candidate leg to topology-selected documents only when the
route is confident. This models the live vector-leg narrowing boundary. -/
def narrowCandidates
(decision : TopologyNarrowingDecision)
(candidates : List ComponentCandidate) : List ComponentCandidate :=
if decision.confident then
candidates.filter (fun candidate => decision.allowedDocs.contains candidate.doc)
else
candidates
/-- Abstention is candidate-set identity. This is the formal fallback contract
that an empirical confidence gate must preserve. -/
theorem narrowCandidates_abstain_identity
(decision : TopologyNarrowingDecision)
(candidates : List ComponentCandidate)
(hAbstain : decision.confident = false) :
narrowCandidates decision candidates = candidates := by
simp [narrowCandidates, hAbstain]
/-- Hard narrowing cannot invent candidates. -/
theorem narrowCandidates_subset
{decision : TopologyNarrowingDecision}
{candidates : List ComponentCandidate}
{candidate : ComponentCandidate}
(h : candidate ∈ narrowCandidates decision candidates) :
candidate ∈ candidates := by
by_cases hConfident : decision.confident
· have hFiltered : candidate ∈ candidates ∧ candidate.doc ∈ decision.allowedDocs := by
simpa [narrowCandidates, hConfident] using h
exact hFiltered.1
· simpa [narrowCandidates, hConfident] using h
/-- Every candidate surviving a confident narrowing decision is explicitly in
the allowed-document set. -/
theorem narrowCandidates_confident_fromAllowed
{decision : TopologyNarrowingDecision}
{candidates : List ComponentCandidate}
{candidate : ComponentCandidate}
(hConfident : decision.confident = true)
(h : candidate ∈ narrowCandidates decision candidates) :
candidate.doc ∈ decision.allowedDocs := by
simp [narrowCandidates, hConfident] at h
exact h.2
/-- A protected candidate is retained when it was present before narrowing and
its document is covered by the confidence certificate's allowed set. Benchmarks
must establish which observable candidates deserve this protection. -/
theorem narrowCandidates_preservesProtected
{decision : TopologyNarrowingDecision}
{candidates protectedCandidates : List ComponentCandidate}
(hConfident : decision.confident = true)
(hPresent : ∀ candidate ∈ protectedCandidates, candidate ∈ candidates)
(hCovered : ∀ candidate ∈ protectedCandidates, candidate.doc ∈ decision.allowedDocs) :
∀ candidate ∈ protectedCandidates, candidate ∈ narrowCandidates decision candidates := by
intro candidate hProtected
simp [narrowCandidates, hConfident, hPresent candidate hProtected,
hCovered candidate hProtected]
/-- Convert bounded topology documents into search candidates. The abstract model
uses the KG source for topology-expanded candidates; the provenance theorem below
is the important guard: every added doc must come from a selected cluster member. -/
def topologyCandidates
(cfg : TopologyInfluenceConfig)
(weakTier1Query : Bool)
(clusters : List TopologyCluster) : List ComponentCandidate :=
if topologyMayExpand cfg.mode weakTier1Query then
(selectedTopologyDocs cfg clusters).map
(fun doc => { doc := doc, source := .knowledgeGraph, score := 0 })
else
[]
/-- Topology vector-augmentation candidates. Routed hashes are scored by the query
embedding and unioned with global ANN under ordinary vector provenance. -/
def topologyVectorAugmentationCandidates
(cfg : TopologyInfluenceConfig)
(weakTier1Query : Bool)
(clusters : List TopologyCluster) : List ComponentCandidate :=
if topologyMayExpand cfg.mode weakTier1Query then
(selectedTopologyDocs cfg clusters).map
(fun doc => { doc := doc, source := .vector, score := 0 })
else
[]
/-- Unbounded candidate view of explicitly selected fragments. This separates
the semantic value of fragmentation from a later candidate-budget policy. -/
def fragmentCandidates (fragments : List TopologyCluster) : List ComponentCandidate :=
(fragmentUnionDocs fragments).map
(fun doc => { doc := doc, source := .vector, score := 0 })
/-- Additive fragment augmentation keeps the global ANN/component candidates and
appends query-scored local topology neighborhoods to the vector candidate stream. -/
def augmentWithFragments
(baseCandidates : List ComponentCandidate)
(fragments : List TopologyCluster) : List ComponentCandidate :=
baseCandidates ++ fragmentCandidates fragments
/-- Fragment augmentation cannot remove a global ANN/component candidate. -/
theorem augmentWithFragments_preservesBase
(baseCandidates : List ComponentCandidate)
(fragments : List TopologyCluster)
{candidate : ComponentCandidate}
(hCandidate : candidate ∈ baseCandidates) :
candidate ∈ augmentWithFragments baseCandidates fragments := by
exact List.mem_append_left (fragmentCandidates fragments) hCandidate
/-- Adding more fragments is candidate-set monotone before a budget is applied. -/
theorem fragmentCandidates_append_monotone
(selected additional : List TopologyCluster)
{candidate : ComponentCandidate}
(hCandidate : candidate ∈ fragmentCandidates selected) :
candidate ∈ fragmentCandidates (selected ++ additional) := by
rcases List.mem_map.mp hCandidate with ⟨doc, hDoc, rfl⟩
apply List.mem_map.mpr
exact ⟨doc, fragmentUnionDocs_append_monotone selected additional hDoc, rfl⟩
/-- If selected fragments refine a coarse region, additive augmentation exposes
every coarse-region document as a vector candidate, regardless of how many
or how small the fragments are. -/
theorem fragmentedAugmentation_recoversDocument
(baseCandidates : List ComponentCandidate)
{coarse : TopologyCluster}
{fragments : List TopologyCluster}
{doc : Yams.Core.DocumentId}
(hFragmentation : IsFragmentationOf coarse fragments)
(hDoc : doc ∈ coarse.docs) :
∃ candidate ∈ augmentWithFragments baseCandidates fragments, candidate.doc = doc := by
let candidate : ComponentCandidate := { doc := doc, source := .vector, score := 0 }
refine ⟨candidate, ?_, rfl⟩
apply List.mem_append_right
apply List.mem_map.mpr
exact ⟨doc, fragmentation_preservesDocumentCoverage hFragmentation hDoc, rfl⟩
/-- Search pipeline with topology candidate expansion. Rerank-only intentionally
uses the baseline pipeline here because this model separates candidate-set safety
from score-ordering safety. -/
def runSearchWithTopology
(searchCfg : SearchConfig)
(topologyCfg : TopologyInfluenceConfig)
(weakTier1Query : Bool)
(baseCandidates : List ComponentCandidate)
(clusters : List TopologyCluster)
(failed timedOut : List SearchSource := []) : SearchResponse :=
runSearch searchCfg (baseCandidates ++ topologyCandidates topologyCfg weakTier1Query clusters)
failed timedOut
/-- Additive topology pipeline: ordinary candidates remain present while routed,
query-scored members are unioned into the normal vector candidate stream. -/
def runSearchWithTopologyAugmentation
(searchCfg : SearchConfig)
(topologyCfg : TopologyInfluenceConfig)
(weakTier1Query : Bool)
(baseCandidates : List ComponentCandidate)
(clusters : List TopologyCluster)
(failed timedOut : List SearchSource := []) : SearchResponse :=
runSearch searchCfg
(baseCandidates ++ topologyVectorAugmentationCandidates topologyCfg weakTier1Query clusters)
failed timedOut
/-- The augmentation form cannot narrow away any pre-existing component candidate
before fusion: every baseline candidate is still present in the additive input. -/
theorem topologyAugmentation_preservesBaseCandidateMembership
(cfg : TopologyInfluenceConfig)
(weakTier1Query : Bool)
(baseCandidates : List ComponentCandidate)
(clusters : List TopologyCluster)
{candidate : ComponentCandidate}
(h : candidate ∈ baseCandidates) :
candidate ∈ baseCandidates ++
topologyVectorAugmentationCandidates cfg weakTier1Query clusters := by
exact List.mem_append_left
(topologyVectorAugmentationCandidates cfg weakTier1Query clusters) h
/-- Benchmark observability model: applied requires a successful load and at
least one added candidate. -/
def observeTopologyRoute
(cfg : TopologyInfluenceConfig)
(weakTier1Query : Bool)
(loadSucceeded : Bool)
(clusters : List TopologyCluster) : TopologyRouteObservation :=
let mayLoad := topologyMayLoad cfg.mode weakTier1Query
let added := topologyCandidates cfg weakTier1Query clusters
{ enabled := cfg.mode != .disabled,
loadAttempted := mayLoad,
loadSucceeded := mayLoad && loadSucceeded,
applied := mayLoad && loadSucceeded && !added.isEmpty,
addedCandidates := added.length,
routedClusters := if mayLoad && loadSucceeded then (selectedTopologyClusters cfg clusters).length else 0,
routedDocs := if mayLoad && loadSucceeded then (selectedTopologyDocs cfg clusters).length else 0 }
/-- Candidate expansion respects the configured document cap. -/
theorem topologyCandidates_respectsDocCap
(cfg : TopologyInfluenceConfig)
(weakTier1Query : Bool)
(clusters : List TopologyCluster) :
(topologyCandidates cfg weakTier1Query clusters).length ≤ cfg.maxDocs := by
unfold topologyCandidates selectedTopologyDocs
by_cases h : topologyMayExpand cfg.mode weakTier1Query
· simp [h]
exact Nat.min_le_left cfg.maxDocs (selectedTopologyDocsUnbounded cfg clusters).length
· simp [h]
/-- Selected route set respects the configured cluster cap. -/
theorem selectedTopologyClusters_respectsClusterCap
(cfg : TopologyInfluenceConfig)
(clusters : List TopologyCluster) :
(selectedTopologyClusters cfg clusters).length ≤ cfg.maxClusters := by
unfold selectedTopologyClusters
rw [List.length_take]
exact Nat.min_le_left cfg.maxClusters clusters.length
/-- Disabled topology cannot add candidates. -/
theorem topologyCandidates_disabled_empty
(cfg : TopologyInfluenceConfig)
(weakTier1Query : Bool)
(clusters : List TopologyCluster)
(hMode : cfg.mode = .disabled) :
topologyCandidates cfg weakTier1Query clusters = [] := by
unfold topologyCandidates topologyMayExpand
simp [hMode]
/-- Weak-query-only topology cannot add candidates for a strong tier-1 query. -/
theorem topologyCandidates_weakOnly_strong_empty
(cfg : TopologyInfluenceConfig)
(clusters : List TopologyCluster)
(hMode : cfg.mode = .weakQueryOnly) :
topologyCandidates cfg false clusters = [] := by
unfold topologyCandidates topologyMayExpand
simp [hMode]
/-- Rerank-only topology cannot add candidates. -/
theorem topologyCandidates_rerankOnly_empty
(cfg : TopologyInfluenceConfig)
(weakTier1Query : Bool)
(clusters : List TopologyCluster)
(hMode : cfg.mode = .rerankOnly) :
topologyCandidates cfg weakTier1Query clusters = [] := by
unfold topologyCandidates topologyMayExpand
simp [hMode]
/-- Every topology-added candidate comes from the bounded selected document list. -/
theorem topologyCandidates_fromSelectedDocs
{cfg : TopologyInfluenceConfig}
{weakTier1Query : Bool}
{clusters : List TopologyCluster}
{candidate : ComponentCandidate}
(h : candidate ∈ topologyCandidates cfg weakTier1Query clusters) :
candidate.doc ∈ selectedTopologyDocs cfg clusters ∧ candidate.source = .knowledgeGraph := by
unfold topologyCandidates at h
by_cases hExpand : topologyMayExpand cfg.mode weakTier1Query
· simp [hExpand] at h
rcases h with ⟨doc, hDoc, hCandidate⟩
subst hCandidate
exact ⟨hDoc, rfl⟩
· simp [hExpand] at h
/-- Every topology-added candidate came from a selected cluster member before the
document cap. This forbids arbitrary document injection by topology routing. -/
theorem topologyCandidates_noArbitraryDocs
{cfg : TopologyInfluenceConfig}
{weakTier1Query : Bool}
{clusters : List TopologyCluster}
{candidate : ComponentCandidate}
(h : candidate ∈ topologyCandidates cfg weakTier1Query clusters) :
∃ cluster, cluster ∈ selectedTopologyClusters cfg clusters ∧ candidate.doc ∈ cluster.docs := by
have hSelected := (topologyCandidates_fromSelectedDocs h).1
unfold selectedTopologyDocs selectedTopologyDocsUnbounded at hSelected
have hUnbounded : candidate.doc ∈ selectedTopologyDocsUnbounded cfg clusters :=
List.mem_of_mem_take hSelected
simp [selectedTopologyDocsUnbounded] at hUnbounded
exact hUnbounded
/-- Disabled topology is result-equivalent to the baseline search path. -/
theorem runSearchWithTopology_disabled_baseline
(searchCfg : SearchConfig)
(topologyCfg : TopologyInfluenceConfig)
(weakTier1Query : Bool)
(baseCandidates : List ComponentCandidate)
(clusters : List TopologyCluster)
(failed timedOut : List SearchSource)
(hMode : topologyCfg.mode = .disabled) :
runSearchWithTopology searchCfg topologyCfg weakTier1Query baseCandidates clusters failed timedOut =
runSearch searchCfg baseCandidates failed timedOut := by
unfold runSearchWithTopology
rw [topologyCandidates_disabled_empty topologyCfg weakTier1Query clusters hMode]
simp
/-- Rerank-only topology is candidate-set equivalent to the baseline search path
in this expansion model. Score/order effects should be modeled separately by a
rerank contract that cannot introduce document IDs. -/
theorem runSearchWithTopology_rerankOnly_baseline
(searchCfg : SearchConfig)
(topologyCfg : TopologyInfluenceConfig)
(weakTier1Query : Bool)
(baseCandidates : List ComponentCandidate)
(clusters : List TopologyCluster)
(failed timedOut : List SearchSource)
(hMode : topologyCfg.mode = .rerankOnly) :
runSearchWithTopology searchCfg topologyCfg weakTier1Query baseCandidates clusters failed timedOut =
runSearch searchCfg baseCandidates failed timedOut := by
unfold runSearchWithTopology
rw [topologyCandidates_rerankOnly_empty topologyCfg weakTier1Query clusters hMode]
simp
/-- Full topology-assisted pipeline still respects the user-visible top-k limit. -/
theorem runSearchWithTopology_respectsLimit
(searchCfg : SearchConfig)
(topologyCfg : TopologyInfluenceConfig)
(weakTier1Query : Bool)
(baseCandidates : List ComponentCandidate)
(clusters : List TopologyCluster)
(failed timedOut : List SearchSource) :
(runSearchWithTopology searchCfg topologyCfg weakTier1Query baseCandidates clusters failed timedOut).results.length ≤
searchCfg.limit := by
unfold runSearchWithTopology
exact runSearch_respectsLimit searchCfg
(baseCandidates ++ topologyCandidates topologyCfg weakTier1Query clusters) failed timedOut
/-- Applied topology implies observability recorded a load attempt and success. -/
theorem topologyObservation_applied_implies_loaded
{cfg : TopologyInfluenceConfig}
{weakTier1Query loadSucceeded : Bool}
{clusters : List TopologyCluster}
(hApplied : (observeTopologyRoute cfg weakTier1Query loadSucceeded clusters).applied = true) :
(observeTopologyRoute cfg weakTier1Query loadSucceeded clusters).loadAttempted = true ∧
(observeTopologyRoute cfg weakTier1Query loadSucceeded clusters).loadSucceeded = true := by
unfold observeTopologyRoute at hApplied ⊢
simp only
cases hMay : topologyMayLoad cfg.mode weakTier1Query <;>
cases hLoad : loadSucceeded <;>
simp [hMay, hLoad] at hApplied ⊢
end Yams.SearchEngine