Commit graph

840 commits

Author SHA1 Message Date
trvon
9ca69d42a1
fix(downloader): emit the Resolving and Verifying progress stages
ProgressStage declares Resolving and Verifying, and the init, model and
download commands render both, but DownloadManager never emitted either:
the server probe and the integrity finalize / checksum check ran with no
progress event, so those CLI branches were unreachable and a long hash of
a large file looked like a stall.

The manager now reports Resolving before the probe and Verifying before
the digest is finalized. A new test records every stage the manager
emits for a checksummed download and requires Resolving < Connecting <
Verifying < Finalizing; it fails before this change.

Found by pipe's experimental C++ front end: both stages were matched in
CLI switches but never produced anywhere in src/.
2026-09-24 18:20:21 -06:00
trvon
322c908d3a
fix(downloader): report a user cancel as OperationCancelled
The curl adapter returned PolicyViolation when shouldCancel() stopped a
transfer. DownloadService maps only OperationCancelled to a cancellation,
so a user's cancel reached callers as a NetworkError, and the service's
cancellation branch could never run.

The existing manager-level cancellation test used a fake adapter that
returns OperationCancelled itself, so it never exercised this path. The
new test drives the real curl adapter over a file:// URL, which runs its
write callback without a network, and fails before this change.

Found by pipe's experimental C++ front end: downloader::ErrorCode::
OperationCancelled was compared (download_service.cpp) but never produced.
2026-09-24 18:20:08 -06:00
Trevon
bb0be0d587
Merge pull request #192 from trvon/test/fuzz-parsers
fix: harden search, UTF-8, extraction and compression parsers; libFuzzer lane on Linux
2026-09-23 20:25:20 -06:00
trvon
15f3e3db73
refactor(compression): remove the never-built LZMA compressor
lzma_compressor.cpp targets the 7-Zip LZMA SDK (LzmaEnc.h/Alloc.h), but
the build only finds xz/liblzma, which does not ship those headers, so
every build (release logs: 'Has header "LzmaEnc.h" ... NO') compiled it
out. No build could read or write LZMA blocks, and its decoder never
bounded output by the expected size.

The policy still chose LZMA for cold/archival and large inactive data;
compression then failed ('Compressor not available') and batch stores
kept those blocks uncompressed. Select Zstandard, which already uses the
archive level for old data.

Remove the compressor, its test, the enable-lzma option and the build
flags that passed it. The LZMA algorithm id stays in the header format,
and compression.lzma_level remains accepted in config (now unused).
2026-09-23 19:31:37 -06:00
trvon
d4b4842559
fix(repair): refuse embedding repair without a resolved data directory
repairMissingEmbeddings built its database path as dataPath/'vectors.db'
with create_if_missing, so an unresolved (empty) data directory created a
stray vectors.db in the process working directory. The dispatcher passes
an empty path when the service manager has not resolved its data dir.
Return an error instead.

Tests that exercised repair or placed plugins under the unresolved data
directory now give the service manager a temp dir; the dispatcher suite
no longer leaves vectors.db or dispatcher_external_* in the working
directory.
2026-09-23 19:26:31 -06:00
trvon
466927f502
fix(search): strip matched XML-style tag pairs in linear time
stripXmlTags removed '<name>...</name>' pairs with a lazy backreference
std::regex; every unmatched opener rescanned the rest of the query, so a
32 KB query of '<a>x' took 4.4 s (libstdc++, -O2) and time grew 4x per
doubling. Search text comes from MCP clients and the CLI.

Index closing tags once and find each opener's first closer by binary
search, preserving the regex's left-to-right shortest-match semantics.
The suspected stack overflow did not reproduce (libc++ or libstdc++, down
to 256 KB thread stacks under ASan); the new test guards the time bound.
2026-09-23 19:26:25 -06:00
trvon
ccc310432b
fix(search): make grouped FTS5 operator queries explicit (found by fuzzing)
fuzz_fts5_query found two inputs the operator-query normalizer still
rejected at MATCH time:
- FTS5 applies implicit AND only between phrases, so '(foo OR bar) baz',
  'bar (foo)' and 'NEAR(a b) c' are syntax errors; insert an explicit AND
  next to a parenthesized or NEAR group.
- 'NEAR/<x>(...)' was treated as a NEAR group; FTS5 accepts only 'NEAR('.
2026-09-23 18:25:30 -06:00
trvon
f5e29366bf
fix(compression): check stored zstd sizes against the frame before allocating
decompress() trusted a caller-provided expected size (from the stored
block header, which has no checksum of its own) and allocated that many
bytes, up to 16 GB, before decoding. A corrupt size field could therefore
drive a huge allocation.

When the frame records its content size, the expected size must match
it. When it does not (streaming compressor output), decode incrementally
so the buffer grows with the real output and fail once it passes the
expected size.
2026-09-23 18:05:05 -06:00
trvon
5899d6fb44
fix(extraction): keep HTML script/style and tag scans linear
removeScriptAndStyle re-searched <script, <style and <!-- from the cursor
on every iteration and, for an unclosed opener, scanned to the end for
its closing tag once per occurrence; convertBlockTagsToNewlines rescanned
to the end for '>' at every '<'. An ingested ~1 MB file of unclosed
'<script' openers, empty comments or bare '<' took over a minute.

Cache each next-occurrence position until the cursor passes it and
remember when a closing token is absent from the rest of the document.
The same three inputs now finish in well under a second.
2026-09-23 17:59:50 -06:00
trvon
2d4fca1d04
fix(extraction): tighten UTF-8 detection and keep units after bad surrogates
EncodingDetector::detectEncoding accepted C0/C1 and F5-F7 lead bytes,
surrogates and overlong sequences as UTF-8, so such files skipped
conversion and reached the index as invalid UTF-8. It now uses the shared
well-formedness check.

convertToUtf8 consumed the unit after an unpaired high surrogate before
checking it, so UTF-16 'D800 0041' lost the 'A'. Only consume it when it
is a low surrogate.
2026-09-23 17:56:59 -06:00
trvon
90dca11690
fix(common): make the lenient UTF-8 sanitizers emit well-formed UTF-8
sanitizeUtf8 and ensureValidUtf8 only checked continuation-byte bits, so
overlong encodings (E0 80 80), UTF-16 surrogates (ED A0 80) and values
above U+10FFFF (F4 90 80 80) passed through. They exist to satisfy
proto3 string fields (proto_serializer.cpp calls them for every string),
which reject exactly those sequences, so a document containing them could
make a peer fail to parse the message. Share one well-formedness check
with sanitizeUtf8Strict; the lenient variant still replaces bytes with
'?'.
2026-09-23 17:56:52 -06:00
trvon
56fd0535ed
fix(search): keep operator queries valid FTS5 instead of passing them through
Queries containing FTS5 operator syntax (a quote, uppercase AND/OR/NOT,
parentheses, column filters) were passed to MATCH almost verbatim, so
'foo "bar', '(foo OR bar', 'foo AND', 'NOT foo', 'IL-6 OR TNF' or
'ratio:2' failed with an fts5 syntax or no-such-column error instead of
returning results; MCP clients and the CLI reach this path.

Rebuild operator queries token by token: keep phrases, parentheses,
AND/OR/NOT, well-formed NEAR(...) groups and filters on real FTS columns,
quote any other non-bareword term, and fall back to the literal word
query when the structure is invalid. The existing assertion that
'hello NEAR/2 world' passes through encoded a query FTS5 rejects.
2026-09-23 17:43:12 -06:00
trvon
af058f1ad2
fix(graph): make isolated-node queries usable after the symbol graph removal
Isolated mode lost its function/calls default in v0.20 and instead
required a node type, but MCP only sets the node type through list_type,
which also switched the request to list-by-type (checked first), so
isolated:true either errored or silently listed nodes.

Default isolated mode to document nodes (with the existing
semantic_neighbor relation default: documents no other document lists as
an embedding neighbour), keep MCP list_type from overriding isolated mode,
and update the tool schema examples that still named calls edges.
2026-09-23 17:34:42 -06:00
trvon
6e1a5262b5
fix(topology): keep rebuilding when the latest snapshot is undecodable
storeBatch loads the previous snapshot only to find documents that left
the topology, but returned early when it could not decode it, so one
corrupt payload blocked every future topology write. On a decode error,
log and store the new snapshot without clearing dropped documents' keys;
other load errors still propagate.
2026-09-23 17:30:05 -06:00
trvon
b2ee196c8a
fix(search): keep topology cluster edges out of relational graph richness
Topology rebuilds now materialize member_of/overlaps/medoid_of/
subcluster_of edges in the KG. They derive from embeddings, but counted
toward kgRelationalEdgeCount, so building topology alone could push
hasRichGraphTopology() over its threshold and reopen graph-weight gates
that stay closed for corpora without real relations. Exclude them.
2026-09-23 17:28:19 -06:00
trvon
5b88c11e8f
fix(metadata): scope migration 40 deletes to symbol extractor output
Migration 40 deleted every kg_edges row whose relation was calls,
implements, includes, ... and every node of a symbol type such as class or
interface. Graph ingest through MCP and IPC accepts any type and relation,
so an upgrade from v0.19 irreversibly removed user-modelled data like a
'class' node or an 'implements' edge.

Select the extractor's nodes by provenance and key shape instead
(<kind>:<qualified>@<file>[@snap:<hash>], symbol_ref:<name>, or nodes its
kg_doc_entities rows reference) and delete edges that are tagged with the
extractor or touch those nodes. The new test fails before the change.
2026-09-23 17:26:09 -06:00
trvon
76a0fd7e65
test(daemon): remove host-dependent assumptions from two Linux failures
- config_resolver: several [tuning] defaults derive from core count (e.g.
  onnx_max_concurrent is 7 on a 48-thread host), so a reload is now checked
  against the values observed before the override instead of != sentinel.
- service_manager: write the P2P trust files with explicit permissions so
  a umask of 002 (Debian user-private groups) no longer trips the
  group-writable check before the symbolic-link check under test.
2026-09-22 18:56:36 -06:00
trvon
b2a5d6131b
fix(integrity): publish verifier pool shutdown under the queue mutex
~VerificationThreadPool set stop without holding queueMutex, so a worker
that had evaluated the wait predicate but not yet blocked could miss
notify_all() and join() hung forever (integrity_catch2 timed out under
parallel load; gdb showed join() waiting on workers parked in wait()).
Set the flag under the mutex. The new shutdown churn test hung in 35/40
parallel runs before the fix and passes 200/200 full-suite runs after.
2026-09-22 18:56:24 -06:00
trvon
4ff7279c90
fix(search): report the scoring mode of the wired cross reranker
cross_rerank_simeon_outer_maxsim read SearchEngineConfig, but the daemon
resolves the Simeon scoring mode from reranker policy (ConfigResolver), so
the stat said 0 for every outer-MaxSim run and the scifact ablation could
not tell which scorer ran. setCrossReranker() now takes the mode label the
wiring resolved; the engine reports it as cross_rerank_scoring_mode and
derives the outer-MaxSim stat from it.
2026-09-22 17:22:30 -06:00
trvon
e3f7e42492
feat(search): optional graph-rerank community signal from topology
Graph rerank recomputes connected components over mutual
semantic_neighbor edges among the candidates on every query, reading
each candidate's edges from the KG. The resident topology snapshot
already holds corpus-wide clusters built from those edges.

Add search.topology.graph_community_source (reciprocal_edges |
topology_snapshot). topology_snapshot scores candidates that share a
snapshot cluster with the same normalization and falls back to the edge
walk when no snapshot is loaded; graph_community_source in debug stats
reports which ran. The default stays reciprocal_edges until a benchmark
gate shows the snapshot source is at least as good. The retrieval bench
can set it for ablations.
2026-09-22 15:50:37 -06:00
trvon
14057afbf8
feat(topology): materialize clusters in the knowledge graph
Topology wrote back only an opaque compressed snapshot node, so yams
graph, explore and graph expansion could not traverse clusters, and
every rebuild added another full snapshot node that was never removed.

Each stored snapshot now replaces the cluster graph in one KG write
batch before the latest pointer flips: topology:cluster:<id> nodes with
size, level, persistence, cohesion and density; document member_of
edges weighted by persistence, medoid_of edges, overlaps edges for
boundary-spill memberships, and subcluster_of hierarchy edges. After the
pointer flips, snapshot nodes beyond the newest three are removed; the
one the pointer names is always kept.
2026-09-22 15:38:33 -06:00
trvon
1a74d4c73e
fix(graph): make semantic_neighbor reciprocity mean mutual kNN
Every kNN edge was also written in reverse, so each semantic_neighbor
edge looked reciprocal and topology's reciprocalOnly filtering, SGC
smoothing over reciprocal edges, and graph reciprocity metrics filtered
nothing. Write only the edge from a document to each of its k nearest
neighbours; a reverse edge now exists exactly when the pair is mutual.

Semantic maintenance pruned one-way edges on the assumption that edges
were symmetric; with directed kNN edges that would erase every
non-mutual neighbour, so it now removes only self-loops (and no longer
reports a fabricated community size for the scoped pass).

Edge reads truncated by insertion order: getEdgesBidirectional sorted by
id and getEdgesTo had no order, so a LIMIT kept arbitrary or oldest
edges. Both now return strongest first, and the topology extractor reads
a document's own neighbours separately from its incoming edges so a
hub's many incoming edges cannot evict them.

Existing databases keep their mirrored edges until the semantic
neighbour graph is rebuilt.
2026-09-22 15:32:59 -06:00
trvon
e31eda4b55
fix(search): measure graph richness from relational edges only
kgEdgeDensity counted every kg_edges row, including the ~16
semantic_neighbor edges per document that 8-NN embedding neighbours
write in both directions, plus file-lineage and entity-attachment edges.
Any embedded corpus therefore looked like a rich knowledge graph, so the
SCIENTIFIC-profile gate that should zero the KG leg on flat prose never
fired and graph richness scaled from embedding edges.

Add kgRelationalEdgeCount/Density, which exclude embedding kNN, file
lineage and layout, text segments, and entity-to-document attachments,
and use it for hasRichGraphTopology() and the tuner's richness scaling.
kgEdgeCount keeps its meaning as the reconciled-graph signal. The live
overlay carries the reconciled relational count forward.
2026-09-22 15:23:45 -06:00
trvon
c766a17bd1
Merge branch 'refactor/remove-symbol-abi' into release/v0.20-integration
# Conflicts:
#	tests/scripts/portability_allowlist.txt
2026-09-22 15:14:55 -06:00
trvon
66689cd7a3
refactor(daemon): remove the symbol_concurrent tuning knob and stage metrics
The retired symbol stage never activates, so tuning.post_ingest.
symbol_concurrent and YAMS_POST_SYMBOL_CONCURRENT had no effect, and
status reported symbol_* queue metrics and post_symbol_limit that were
always zero. Remove the config key and environment override, the status
keys and CLI rows, the metrics snapshot fields, and the event-bus symbol
counters. The internal stage slot stays so budget and limiter indices
are unchanged; benchmark JSON keeps its symbol fields at zero.
2026-09-22 14:41:05 -06:00
trvon
ac070d8d2a
refactor(search): remove symbol-based result ranking
SymbolEnricher boosted results by looking up code-symbol nodes that no
longer exist since v0.20, so it ran KG queries for the top results and
never boosted anything. Remove it, the YAMS_SYMBOL_WEIGHT variable, the
symbol_rank_* search stats, and the symbolRank option on the service,
retrieval, MCP and CLI request types. The IPC field stays in the wire
format but is ignored, and --symbol-rank/--no-symbol-rank remain
accepted as hidden no-ops so existing scripts keep working.
2026-09-22 14:40:52 -06:00
trvon
c6e960eed7
Merge branch 'test/p2p-fuzz-lane' into release/v0.20-integration 2026-09-22 13:55:37 -06:00
trvon
e6de1978c2
Merge branch 'refactor/remove-symbol-abi' into release/v0.20-integration 2026-09-22 13:55:37 -06:00
trvon
b702cb47e1
fix(p2p): validate connection-string hosts
Found by fuzz_p2p_connstr. Hosts containing brackets, spaces, control
or non-ASCII bytes were accepted and could not be rendered back through
endpoint(). Accept only hostname/IPv4 characters for plain hosts and
hex, ':', '.' plus an optional %zone inside brackets.
2026-09-22 13:53:10 -06:00
trvon
de9e3245f8
fix(p2p): admit only non-negative integers in control frames
Found by fuzz_p2p_handshake. A pre-authentication hello whose integer
field exceeded uint64 was stored by the JSON parser as a double and then
read with get<std::uint32_t>(), an undefined float-to-integer
conversion. A value just above 2^32 was also silently truncated, so
protocol 2^32+4 passed the protocol-4 version check, and negative values
wrapped to huge sizes.

Reject any frame containing a float or a negative number at the frame
boundary (the wire carries none), which also keeps the memory-sync
record deserializers defined, and read p2p numeric fields through a
range-checked unsignedField<T>.
2026-09-22 13:53:04 -06:00
trvon
ec5b78980e
refactor(graph): resolve explore through graph nodes and report removed impact
Graph explore looked up entry points in symbol_metadata, which no longer
exists. Resolve them from node labels, aliases, and ingest path nodes
instead, gather a wider candidate pool, and rank it by query-term
coverage per file before applying the symbol budget so broad early
terms cannot crowd out the file that answers the query.

Impact and affected-test analysis walked code-symbol edges that are no
longer produced; they now return a warning saying the symbol graph was
removed in v0.20 instead of silently empty results.
2026-09-22 13:30:46 -06:00
trvon
c39ed84bf7
refactor(plugins)!: remove the symbol_extractor_v1 plugin ABI
Migration 40 dropped the symbol tables and graph edges, but the daemon
could still adopt an external symbol_extractor_v1 plugin (for example a
0.19 plugin left in a user plugin directory) and rebuild them.

Remove the interface header, the ABI adapter, plugin adoption, the
EntityGraphService extract-and-write path, the post-ingest symbol
channel, the write-coordinator symbol ops, and the KG-store symbol
metadata and extraction-state API. The ABI loader and the external host
now refuse a plugin that declares the interface (or the legacy
symbol_extraction capability) with a one-time warning. Status no longer
reports symbol extractors, generated configs no longer write
[plugins.symbol_extraction] (an existing section still parses), and
the benchmark suite drops symbol_extraction_bench.

Tests that exercised the dropped tables or the v39 schema template are
removed; the rest run against the current schema.

BREAKING CHANGE: code-symbol extraction is gone. Removed: the
symbol_extractor_v1 plugin ABI, `yams config grammar` (list, download,
path, auto-enable, auto-disable), the grammar steps in `yams init`, the
meson option `plugin-symbols`, the conan option
`enable_symbol_extraction`, and the Windows installer SymbolPlugin
component. Databases migrate to schema 41; a v0.19 binary refuses to
open them, and symbol data dropped by migration 40 cannot be recovered.
2026-09-22 13:30:35 -06:00
trvon
7eb64fa8a2
test(search): include the BQ index header in the snapshot cache test
The BQ prefix assertions dereference centroidBqIndex, which the routing
headers only forward-declare.
2026-09-22 12:37:38 -06:00
trvon
715a0428df
fix(rerank): sample MaxSim fragments across the whole document
Outer MaxSim scored only the first eight sentences of each document, so
a relevant passage later in a long document was never seen, which
defeats the point of fragment scoring. Sample sentences evenly across
the document (first and last kept), always reserve a document-head
fragment, and bound per-call fragment embeddings so large rerank
windows shrink the per-document budget instead of growing unbounded.
2026-09-22 12:31:45 -06:00
trvon
c990b4878d
fix(topology): make SGC smoothing deterministic and skip ragged inputs
Edges were emitted in unordered_map iteration order, so float
accumulation, and the smoothed embeddings feeding snapshot fingerprints,
varied with input order. Order each neighbor list by document hash.

Documents whose embedding is missing or of a different dimension were
treated as zero vectors yet still added degree, damping their
neighbors. Exclude them from the propagation graph. Hop 0 now reads the
inputs directly, dropping one n*dim buffer for single-hop smoothing.

Add closed-form checks against D^-1/2 (A+I) D^-1/2 for one and two
hops.
2026-09-22 12:28:34 -06:00
trvon
b9ec471c4c
feat(topology): wire SGC smoothing and BQ routing through typed config
sgcHops, sgcNormalize, bqCandidateLimit and bqPrefixDimension existed
only as struct fields, so no production path could enable them. Resolve
topology.sgc_hops / topology.sgc_normalize into the TopologyManager
build config (hops capped at 4 to bound over-smoothing) and
search.topology.bq_candidate_limit / bq_prefix_dim into the routing
options, request, and fingerprint. The snapshot cache rebuilds only the
route index when the BQ prefix changes. All default to off.

Drop the snapshot interner, which was allocated but never used.
2026-09-22 12:25:47 -06:00
trvon
da3e398b5c
fix(topology): keep exhaustive routing when BQ cannot score a query
The BQ branch cleared every route candidate before searching, so a
query the index could not quantize (shorter than the indexed dimension)
left only sparse-seeded clusters. The BQ fallback runs whenever the
dense ANN index is absent, which made this reachable in production.
Only narrow candidates when BQ returns a shortlist, matching the dense
ANN branch, and document that the SimHash similarity estimate is an
ordering proxy for raw sign bits.
2026-09-22 12:20:21 -06:00
trvon
c401e045d0
fix(metadata): guard snapshot pruning and topology count triggers
Migration 40 pruned every topology snapshot when the latest pointer was
missing or named a snapshot that did not exist, and aborted on malformed
pointer JSON. Prune only when the pointer is valid JSON naming an
existing snapshot node.

Migration 41 matched topology keys with LIKE, which is case-insensitive
in SQLite and also stopped counting user keys such as Topology.*. Use
GLOB, and rebuild the topology counts on rollback so down leaves
metadata_value_counts consistent. Neither migration has shipped in a
release or nightly.
2026-09-22 12:15:41 -06:00
trvon
fff24242fd
fix(topology): make the latest pointer the storeBatch commit point
storeBatch wrote per-document keys before the snapshot node, so a
failure in between left documents pointing at a snapshot that was never
persisted. Write the snapshot node first and the latest pointer last so
a partial store leaves the previous snapshot authoritative.

Remove only the two per-document keys still written, in one transaction
via a new removeMetadataBatch, and stop the membership slow path from
reading structural keys that migration 41 prunes.
2026-09-22 12:12:43 -06:00
trvon
522c33a9ca
fix(topology): share one resident snapshot behind a lock
The metadata store cached the latest batch in an unguarded mutable
optional that search threads read and wrote, and handed out a full copy
on every loadLatest, so search kept the snapshot resident twice.

Publish the latest batch and its membership index as one immutable
shared snapshot under a mutex, add loadLatestShared to hand it out
without copying, and feed the routing snapshot cache from it. Historical
loads no longer displace the resident latest snapshot, and a reader that
raced a write never installs the older batch.
2026-09-22 12:05:53 -06:00
trvon
5267b32775
fix(topology): bound snapshot decode against corrupt payloads
Validate every element count against the remaining payload before
reserving, reject trailing bytes, type-check envelope fields, bound the
untrusted uncompressed_bytes hint, read the magic little-endian, and
convert legacy JSON type errors into Result errors instead of throwing.
2026-09-22 11:59:44 -06:00
trvon
8a72da96ff
test(metadata): align legacy symbol test cases with schema v39
- Use v39 metadata DB template for legacy symbol metadata test cases in
  metadata_schema_catch2_test.cpp and kg_store_catch2_test.cpp to avoid
  running against schema v40 where symbol tables are dropped.
- Use yams::test::make_temp_sqlite_path instead of raw getenv in
  metadata_schema_catch2_test.cpp and prune the stale portability allowlist entry.
- Add site directory to SKIP_PARTS in check_repository_metadata.py to prevent
  local docs builds from triggering retired marker warnings.

Signed-off-by: trvon <me@trevon.dev>
2026-09-22 09:37:00 -06:00
trvon
c3b667ee40
feat(retrieval): SGC topology smoothing, Matryoshka BQ prefix, and fragment outer-MaxSim
Implement three research-backed retrieval optimizations connecting
paperbridge literature findings with YAMS subsystems:

1. SGC Feature Smoothing (Proposal A):
   - Add sgcHops and sgcNormalize to TopologyBuildConfig.
   - Diffuse document embeddings across reciprocal nearest-neighbor graph
     edges prior to clustering, formalizing Lean SGC.lean.
   - Re-normalize diffused embeddings to preserve unit hypersphere metric geometry.
   - Wire into ConnectedComponent, Louvain, and KMeans topology engines.

2. Matryoshka Prefix Binary Quantization (Proposal C):
   - Add maxDimensions parameter to BinaryQuantizer::quantize and
     maxPrefixDimension to BinaryQuantizedIndex::build.
   - Support fast 64-bit word-aligned prefix Hamming shortlisting in
     SparseGuidedClusterRouter via bqPrefixDimension.

3. Fragment Outer-MaxSim Reranker (Proposal B):
   - Implement SimeonScoringMode { SingleVectorCosine, FragmentOuterMaxSim }
     in SimeonModelProvider.
   - Add extractDocumentFragments and late-interaction outer-MaxSim scoring
     inspired by ColBERTv2/PLAID/SPLATE to prevent relevance dilution in long docs.
   - Expose simeonRerankOuterMaxSim on SearchEngineConfig and ConfigResolver.
   - Add Catch2 test coverage and verify BEIR scifact benchmark metrics.

Signed-off-by: trvon <me@trevon.dev>
2026-09-22 07:37:35 -06:00
trvon
052031a484
feat(vector): binary quantization (BQ) pre-filter for cluster routing
- Add BinaryQuantizer and BinaryQuantizedIndex in include/yams/vector/binary_quantization.h
  and src/vector/binary_quantization.cpp implementing 1-bit sign quantization,
  packed 64-bit word storage, hardware popcount Hamming distance, and cosine
  similarity estimation.
- Integrate centroidBqIndex into SparseRouteIndex, constructing compact BQ
  centroids alongside dense ANN indices during SparseGuidedClusterRouter::buildRouteIndex.
- Add bqCandidateLimit to TopologyRouteRequest and tracking metrics (bqUsed,
  bqCandidates, bqDistanceEvaluations) to SparseRouteWork.
- Enable fast candidate pre-filtering in SparseGuidedClusterRouter::route with
  1-bit Hamming distance scans, providing sub-millisecond coarse candidate
  shortlisting and automatic fallback when the dense ANN index is omitted.
- Add Catch2 unit test suites in tests/unit/vector/binary_quantization_catch2_test.cpp
  and tests/unit/topology/topology_baseline_catch2_test.cpp.

Signed-off-by: trvon <me@trevon.dev>
2026-09-22 07:37:35 -06:00
trvon
1550dad3be
perf(search): flyweight string interning in TopologyRoutingSnapshotCache
- Add StringInterner in include/yams/search/string_interner.h for
  reusable, thread-compatible flyweight string deduplication with
  heterogeneous string_view lookups.
- Update TopologyRoutingSnapshot in include/yams/search/topology_routing_session.h
  to store zero-copy string_view keys for clustersById and membershipsByDocumentHash,
  pointing directly into immutable TopologyArtifactBatch strings.
- Add shared_ptr<StringInterner> to TopologyRoutingSnapshot to intern repeated
  lookup and dynamically generated strings.
- Eliminate ~75,000 duplicate heap-allocated std::string objects per cached
  snapshot, reducing cache footprint by ~70%.
- Add Catch2 tests in tests/unit/search/string_interner_catch2_test.cpp covering
  interning, deduplication, lifetime guarantees, and zero-copy pointer preservation
  across snapshot upgrades.

Signed-off-by: trvon <me@trevon.dev>
2026-09-22 07:37:35 -06:00
trvon
dd8105f25d
perf(topology): bypass metadata table triggers during snapshot sync and prune legacy metadata
- Add Migration 41 to drop and recreate metadata_value_counts triggers with
  'WHEN ... AND key NOT LIKE topology.%' condition, eliminating ~650,000 trigger
  executions per topology rebuild in a 72k document corpus.
- Purge legacy continuous float/structural keys from metadata table
  (persistence_score, cohesion_score, bridge_score, role, cluster_level,
  parent_cluster_id, overlap_cluster_ids_json) in Migration 41.
- In MetadataKgTopologyArtifactStore::storeBatch, replace N individual
  getDocumentByHash calls with single batchGetDocumentsByHash and write
  only essential snapshot_id and cluster_id to metadata table.
- In MetadataKgTopologyArtifactStore::loadMemberships, add in-memory fast
  path serving memberships directly from cached latest snapshot batch.
- Update serializeTopologyBatchCompressed to handle uncompressed tiny batches.
- Add Catch2 unit tests in tests/unit/topology/topology_metadata_store_catch2_test.cpp
  validating trigger bypass, metadata pruning, and fast-path membership loading.

Signed-off-by: trvon <me@trevon.dev>
2026-09-22 07:37:34 -06:00
trvon
8fdb4df27e
feat(topology): binary snapshot serialization and zstd compression
Replace 960+ MB raw JSON snapshot serialization in MetadataKgTopologyArtifactStore with a compact, string-interned binary format compressed with Zstandard.

- Add yams::topology::serializeTopologyBatchBinary and deserializeTopologyBatchBinary with integer length packing and string deduplication.
- Wrap binary payload in a backward-compatible JSON envelope with format: zstd_binary_v1 and base64 encoded payload, maintaining SQLite json_valid(properties) == 1.
- Fall back gracefully to legacy raw JSON deserializer when reading pre-existing uncompressed snapshots.
- Add Catch2 unit test suite verifying binary roundtrip fidelity, compression ratio, backward-compatibility with legacy JSON snapshots, and corrupted payload resilience.

Signed-off-by: trvon <me@trevon.dev>
2026-09-22 07:37:34 -06:00
trvon
32e57d758d
refactor(symbols): remove tree-sitter from conan and fix legacy symbol tests
- Remove enable_symbol_extraction option and tree-sitter requirement from conanfile.py.
- Remove YAMS_DISABLE_SYMBOL_EXTRACTION toggles from setup.ps1, setup.sh, and ubuntu.Dockerfile.
- Add v39_metadata_db_template helper to MetadataDbTemplate for legacy symbol tests.
- Update GraphComponent, DeleteCascade, GraphContextService, and KGStore test
  fixtures to run symbol-specific test cases against schema v39.

Signed-off-by: trvon <me@trevon.dev>
2026-09-22 07:37:30 -06:00
trvon
ee51abcb1a
refactor(symbols): remove in-tree tree-sitter symbol extractor plugin and tests
Remove legacy Tree-sitter symbol extractor plugin (plugins/symbol_extractor_treesitter),
the unused plugin-symbols build option, and associated tests and benchmark targets:
- Drop plugins/symbol_extractor_treesitter/ (grammar_loader, symbol_extractor, plugin_api).
- Remove plugin-symbols Meson option and packaging references.
- Remove dedicated symbol plugin tests and benchmark targets (symbol_extraction_bench,
  symbol_extractor_all_langs_catch2_test, symbol_extractor_edge_cases_catch2_test,
  symbol_extractor_solidity_catch2_test, symbol_extractor_query_test,
  symbol_extractor_validation_test).
- Clean up dead symbol extraction plugin helper and test from hnsw_entity_search_bench.
- Prune obsolete portability allowlist entries.

Signed-off-by: trvon <me@trevon.dev>
2026-09-22 07:37:30 -06:00
trvon
9c7f780d4c
refactor(symbols): remove symbol extraction subsystem and add migration 40
Drop legacy Tree-sitter symbol extraction subsystem, associated database
tables, triggers, and AST edge types:
- Migration 40 drops symbol_metadata, document_symbol_extraction_state,
  symbol_metadata_fts, associated triggers, and prunes historical bloated
  topology snapshots while preserving active snapshot topology,
  semantic_neighbor edges, and non-symbol observed_as provenance edges.
  Exclude nl_entity:% nodes from kg_nodes / kg_edges pruning to preserve
  canonicalized method-type technology entities.
  Provides complete downSQL restoring symbol tables, indexes, FTS virtual table,
  and sync triggers for clean v39 rollbacks.
- In EntityGraphService, ensure locally resolved documentDbId propagates to
  WriteBatch for knowledgeGraphDocumentId to ensure accurate enrichment
  token completion, and only populate batch->documentIdToDelete for snapshot
  documents with a resolved document database ID.
- In SymbolEnricher and SearchService, restore one-directional symbol-contains-query
  matching with non-empty checks to prevent short substrings matching broad queries.
- In tests/unit/metadata/symbol_metadata_schema_test.cpp, migrate test DB up to
  version 39 so tests pass against their target version prior to v40 table drops.
- Remove Tree-sitter grammar download and compile workflows from CLI init
- Default plugin-symbols to false.
- Fix macOS daemon client and CLI executable resolution using
  _NSGetExecutablePath with candidate fallbacks.
- Add migration validation tests and symbol enrichment unit tests.

Signed-off-by: trvon <me@trevon.dev>
2026-09-22 07:37:30 -06:00