Commit graph

1,002 commits

Author SHA1 Message Date
trvon
95f3c3ee92
test(policy): drop environment keys removed with the symbol pipeline
YAMS_POST_SYMBOL_CONCURRENT and YAMS_SYMBOL_WEIGHT no longer have
readers, so configuration_authority_policy flagged them as stale.
2026-09-22 17:39:17 -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
e7bf9a05be
fix(bench): drop removed symbolRank option and tolerate duplicate-content corpora
nfcorpus contains byte-identical documents that collapse to one content
hash; skip repeats when building topology inputs instead of aborting.
2026-09-22 16:29:46 -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
8912d70e5c
perf(bench): expose BQ routing work and new topology levers to benchmarks
Report topology_route_bq_used / topology_route_bq_candidates in search
debug stats so benchmarks can tell BQ-shortlisted routing apart from
ANN and exhaustive routing. Let retrieval_quality_bench write
bq_candidate_limit, bq_prefix_dim, sgc_hops and
simeon_rerank_outer_maxsim into its per-run config from YAMS_BENCH_*
harness settings, read through getenv_optional.
2026-09-22 12:51:12 -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
438f8a8a7b
ci(release): refuse stable releases from non-version refs
A stable workflow_dispatch took the release tag from GITHUB_REF_NAME
without checking it, so dispatching from main published a release
tagged main. Require refs/tags/v* or refs/tags/yams-v*.

Fetch main and experimental with explicit refspecs in the candidate
preflight so a tag named main cannot shadow the branch, and drop the
workflow_run trust path from draft reconciliation now that the workflow
is dispatch-only.
2026-09-22 12:18:20 -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
2bfc5334eb
ci(release): decouple draft promotion and enforce non-squash merge policy
- Decouple draft-promotion.yml from automated Tests workflow_run triggers,
  making promotion draft creation manual via workflow_dispatch to prevent
  GitHub from auto-chaining active experimental feature PRs into a stack
  targeting main.
- Enforce and document the mandatory Promotion Merge Policy across
  reconcile_promotion_pr.py, validate_release_candidate.py, and draft-promotion.yml
  step summary: promotions must use a merge commit (--no-ff) or fast-forward,
  never squash-merge into main, preserving conventional commit history and
  unbroken git ancestry.
- Unhide refactor commits in release-please-config.json so architectural
  refactorings are tracked under Changed in CHANGELOG.md.
- Update unit tests in test_reconcile_promotion_pr.py and
  test_validate_release_candidate.py for the new policy invariants.

Signed-off-by: trvon <me@trevon.dev>
2026-09-22 07:58:31 -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
trvon
b6f5faba06
test(bus): make channel capacity reuse mismatches observable
InternalEventBus::get_or_create_channel returns an existing channel for a name and silently
discards the requested capacity, so the first requester sizes a channel for the whole process.
Several call sites depend on that silence, and five of them survived an earlier fix precisely
because nothing reported it.

Record each distinct (name, actual, requested) disagreement and warn once. The warning is emitted
outside the lock so a sink cannot re-enter the bus while mu_ is held. remove_channel now drops a
name's records too, so the list describes live disagreements and an assertion cannot become
order-dependent. The spdlog include is guarded with __has_include, matching
pressure_limited_poller.h, since this is an installed public header.

The recorded capacity is stored beside the type-erased channel pointer rather than read back through
static_pointer_cast<SpscQueue<T>>. A name is NOT guaranteed to map to one T: "model.events" is
registered as SpscQueue<ModelReadyEvent> at plugins/onnx/model_provider.cpp:1076 and as
SpscQueue<ModelLoadFailedEvent> at :1089/:1266/:1503, so reading capacity() through the cast would be
a wrong-type member access on a reachable production path. The cast itself is still unvalidated, but
that risk is unchanged from what callers already accepted for a doubly-registered name.

On the test side the drain helpers no longer pin a capacity: they drain and remove, so each case's
own declared capacity applies. The two embed_jobs sites that want a small ring drain first, so they
actually receive 2048 instead of silently inheriting a larger registered ring, and a
ScopedChannelRemoval guard drops that ring at scope exit. The throughput case asserts that no
post_ingest disagreement was recorded, guarding the drain-then-remove behaviour its ring depends on.

Evidence: with the pre-fix pinning helper restored, the diagnostic reports "channel 'post_ingest'
already exists with capacity 32 but 64 was requested" and the new assertion fails (10 assertions, 1
failed); with the fix the case passes 10/10 and the binary is 307/307. The 4-batch outcome itself is
load-dependent and did not reproduce on this machine, so the guard's value is that it detects the
precondition deterministically rather than waiting for a loaded runner.

Two defects in an earlier revision of this commit were found by the entry-review workflow (Gemini
3.8 Flash + Claude Opus, adjudicated) before the push, and are fixed here: one embed_jobs site never
established the 2048 its comment claimed, so the comment and this message were false; and the
capacity read went through the cast while claiming a one-T-per-name invariant that is already false
in production.

This does not reduce the mismatch count in this binary (9 -> 10 -> 9 distinct after these fixes, with
the false direction gone). The
remaining records are deliberate test-local rings plus production disagreements that need separate
fixes: embed_jobs (8192 vs 1000) and post_ingest (1000 vs 4096 in RepairService). The diagnostic also
surfaced a family that was not previously known: PostIngestQueue derives symbol_extraction,
entity_extraction, title_extraction and kg_jobs from its per-instance capacity_ via
boundedStageChannelCapacity, while those channels are process-wide singletons, so the
first-constructed queue sizes them for every later queue (32 vs 512 and 32 vs 64 both observed).

Signed-off-by: trvon <me@trevon.dev>
2026-09-22 07:37:30 -06:00
Trevon
27f133f595
test(daemon): stop pinning the shared post_ingest channel capacity (#170)
Some checks failed
Secret scan / gitleaks (push) Failing after 7s
Mirror source to Forgejo / mirror (push) Has been skipped
Release Candidate / Validate promotion and preview release (push) Has been cancelled
Tests / Static Analysis (push) Has been cancelled
Tests / Warm Conan & ccache (linux-arm64) (push) Has been cancelled
Tests / Warm Conan & ccache (macos-arm64) (push) Has been cancelled
Tests / Warm Conan & ccache (linux-x86_64) (push) Has been cancelled
Tests / Warm Conan & ccache (macos-x86_64) (push) Has been cancelled
Tests / Warm Conan & ccache (windows-x86_64) (push) Has been cancelled
Tests / warm (push) Has been cancelled
Tests / Tests (linux-arm64) (push) Has been cancelled
Tests / Tests (macos-arm64) (push) Has been cancelled
Tests / Tests (linux-x86_64-asan) (push) Has been cancelled
Tests / Tests (linux-x86_64-ubsan) (push) Has been cancelled
Tests / Tests (linux-x86_64) (push) Has been cancelled
Tests / Tests (windows-x86_64) (push) Has been cancelled
The case "keeps multi-doc batches when extraction concurrency is low"
asserts an exact batch count (extractionBatches == 3) and produced 4 on a
loaded macOS runner (16+16+15+1).

The binding constraint was not the coalescing window: the drain helper
created the shared post_ingest channel with capacity 32 before any queue
was constructed, and get_or_create_channel ignores the requested capacity
on a name hit, so the queue declared capacity 64 but got a 31-slot ring.
With 48 tasks the producer had to block on the channel until the poller
drained, which made batch boundaries a function of runner load.

Drain and then drop the channel so each case capacity applies, and add the
bus remove_channel() that makes that possible. The exact-count assertions
are kept, not relaxed.

Signed-off-by: trvon <me@trevon.dev>
2026-09-20 16:56:22 -06:00
Trevon
1cfdf13e50
Merge pull request #164 from trvon/fix/copilot-promotion-findings
Some checks failed
Secret scan / gitleaks (push) Failing after 9s
Mirror source to Forgejo / mirror (push) Has been skipped
Release Candidate / Validate promotion and preview release (push) Has been cancelled
Tests / Static Analysis (push) Has been cancelled
Tests / Warm Conan & ccache (linux-arm64) (push) Has been cancelled
Tests / Warm Conan & ccache (macos-arm64) (push) Has been cancelled
Tests / Warm Conan & ccache (linux-x86_64) (push) Has been cancelled
Tests / Warm Conan & ccache (macos-x86_64) (push) Has been cancelled
Tests / Warm Conan & ccache (windows-x86_64) (push) Has been cancelled
Tests / warm (push) Has been cancelled
Tests / Tests (linux-arm64) (push) Has been cancelled
Tests / Tests (macos-arm64) (push) Has been cancelled
Tests / Tests (linux-x86_64-asan) (push) Has been cancelled
Tests / Tests (linux-x86_64-ubsan) (push) Has been cancelled
Tests / Tests (linux-x86_64) (push) Has been cancelled
Tests / Tests (windows-x86_64) (push) Has been cancelled
fix: address the review findings from the experimental->main promotion
2026-09-19 15:50:15 -06:00
trvon
41e86a6564
fix(ipc): validate memory sync operations against a named wire limit
The template deserializer bounded operations by Enroll, so appending an
enum member would reject the new value. Use one named constant tied to
the highest member.

This is a latent path: it has no callers. The shipping protobuf path is
unaffected because proto_serializer.cpp asserts the proto range is >=
the highest C++ value rather than equal to it, and the round-trip test
added here covers protobuf rather than the bound.

Signed-off-by: trvon <me@trevon.dev>
2026-09-19 13:46:08 -06:00
trvon
c37b20170f
fix(storage): harden reference-counter schema migration errors
Stop discarding the failure text, and stop swallowing non-duplicate
errors in the ALTER handler. Lock contention during migrations stays
classifiable and retryable, and the schema can no longer be treated as
migrated when the ALTER failed for an unrelated reason.

No observed abort is reproduced by this change: the pre-existing
classifier already retried the held-lock case, because the failure
surfaces with a SQLITE_BUSY-style message. This is hardening; the added
contention test is a regression guard, not a red/green proof.

Signed-off-by: trvon <me@trevon.dev>
2026-09-19 13:45:58 -06:00
trvon
be95561c1e
fix(wal): keep WAL metrics noexcept and race-free
getStats() is noexcept but calls a WALManager query that iterates the
WAL directory, so an inaccessible directory terminated the daemon
instead of degrading to zeroed metrics. Catch and report zeros.

WALManager::getStats() also read lastSync/lastRotation after releasing
stateMutex while the sync and rotation paths write them under it. Read
them inside the same critical section.

Signed-off-by: trvon <me@trevon.dev>
2026-09-19 12:45:34 -06:00
Trevon
649f98ca32
Merge branch 'experimental' into fix/win-extended-paths-scoped 2026-09-19 08:18:27 -06:00
trvon
815d85cac9
test(daemon): keep repair work pending while awaiting degrade
The lifecycle Degraded transition requires a non-empty repair queue at
the moment the busy hold elapses (pending && busyHeld). A finite batch
that drains faster than the hold returns the daemon to Healthy, so
enqueue-once-then-wait is racy against machine speed and fails
independently of load. Resubmit bounded work while waiting.

Observed on hosted linux-arm64 and linux-x86_64 in runs containing no
Windows-path change, so it is not caused by the storage work.

Signed-off-by: trvon <me@trevon.dev>
2026-09-18 16:23:20 -06:00
trvon
283148e32d
fix(storage): enumerate Windows long paths in the extended namespace
Directory enumeration builds descendant paths internally, so a short
root can still produce child paths past MAX_PATH and fail partway
through the walk. Run list() and getStats() iteration in the extended
namespace unconditionally on Windows while keeping key derivation on
de-prefixed paths.

Found by the hosted Windows lane: the long-key listing case failed at
REQUIRE(listed) with the backend returning a filesystem error.

Signed-off-by: trvon <me@trevon.dev>
2026-09-18 12:42:53 -06:00
trvon
329a6022af
fix(storage): scope Windows long paths to paths that need them
Add the extended Win32 prefix per filesystem operation and only when
the path reaches MAX_PATH, instead of rewriting the whole storage root.
Short paths keep their ordinary spelling, so listing, canonicalisation
and iteration semantics are unchanged.

Derive list() keys lexically from de-prefixed entries, and canonicalise
containment in the extended namespace but compare it in the ordinary one,
so prefixed and plain spellings are never mixed.

Keep the existing publication regression and cover drive/UNC spelling
plus long-key CRUD.

Signed-off-by: trvon <me@trevon.dev>
2026-09-17 07:16:00 -06:00
trvon
93db4850cd
test(daemon): synchronize on committed content state
PostIngestQueue::processed() counts prepared tasks before their content is
committed, so waiting on it and then reading repository state is racy.
Wait on the committed observable in the three affected cases and
characterize the counter ordering with a gated-insert regression.

Signed-off-by: trvon <me@trevon.dev>
2026-09-16 17:15:49 -06:00
trvon
9ef69b7656
fix(metadata): reserve pool creation capacity
Reserve slots before unlocking for connection creation. Route replacement
and maintenance through the same capacity accounting and wake waiters
when reservations settle.

Reproduce concurrent over-admission with bounded creation barriers.
Reanchor existing portability exceptions without adding exemptions.

Signed-off-by: trvon <me@trevon.dev>
2026-09-14 08:20:42 -06:00
trvon
26a1716400
fix: preserve cancellation across worker restart
Reassert backend cancellation under the lifecycle lock. Protect fake
backend condition-variable predicates against lost wakeups.

Fix Windows fixture encoding, shell selection, and rooted-path checks.
Keep the unresolved rename failure diagnostic-only.

Signed-off-by: trvon <me@trevon.dev>
2026-09-14 00:58:19 -06:00