* chore(repo): land the retrieval contract and drop root cruft
Lands work that had been sitting uncommitted in the working tree:
- docs/architecture/retrieval_contracts.md plus its executable form,
tests/benchmarks/retrieval_contract.py and test_retrieval_contract.py,
now registered in meson as retrieval_contract_unit (suite: policy).
.gitignore keeps them tracked beneath the tests/benchmarks/*.py ignore.
- AGENTS.md: the Development Memory section, and the product default
recorded as hybrid_assist + shadow with topology-off as the control,
which is what search_topology_stage already does.
docs/benchmarks/index.md gains the matching search_shadow_cost plan.
- .gitleaks.toml, and a Secret scan workflow that actually runs it on
push and pull requests (nothing in .github invoked gitleaks before).
Deletes three root files with zero references anywhere in the tree:
bench_profile.json (ad-hoc profiling leftover), CppProperties.json
(Visual Studio config; the repo standardises on Meson), and spdlog.pc
(a hand-copied spdlog 1.12 pkg-config with prefix=/usr/local that would
mislead any PKG_CONFIG_PATH lookup; spdlog comes from Conan).
docs/plans/ (two p2p designs already shipped in 5a53cdfa and d012d95b)
was ingested into YAMS with task=desloppification source=plan, hydrated
via yams cat and byte-compared before removal, per the new AGENTS.md
rule; tests/scripts/analyze_simeon_adaptivity.py no longer points at a
plans directory.
Also reflows two stale line anchors in raw_environment_allowlist.txt
for tools/yams-cli/main.cpp (shifted by 11c39157) so the policy suite
passes again; no new environment writes.
Not included: the external/yams-repo submodule pointer in the working
tree was a stale local clone two commits behind, not an intended bump.
Signed-off-by: trvon <me@trevon.dev>
* test(policy): make the configuration-authority ratchet bidirectional
check_configuration_authority.py only reported keys and TOML readers
present in code but missing from the allowlists, so the reviewed
YAMS_* surface could grow but never shrink: production_environment_keys.txt
even said removals "may remain grandfathered". An allowlist that only
accretes stops describing the code.
The policy now also fails on allowlist entries with no remaining
occurrence in src/, include/, or tools/, naming the lines to delete.
Key removals in later commits therefore have to update the allowlist in
the same change, and the count in the success banner becomes a real
measure of the environment surface (432 today).
stale_entries() exposes the two stale sets for tooling; three unit
tests cover stale key, stale reader, and the returned names.
Signed-off-by: trvon <me@trevon.dev>
* tools(policy): report where every configuration key is referenced
Adds tests/scripts/report_config_key_usage.py: for each YAMS_* key in
production_environment_keys.txt and each dotted TOML key literally
resolved in ConfigResolver.cpp, count whole-token references per tree
bucket (src, test, bench, docs, plugin; build output and the allowlists
themselves excluded) and assign a verdict: KEEP when anything outside
product code exercises or documents the key, CANDIDATE when only
product code mentions it, NOT_IN_SRC when the allowlist is stale.
The report is evidence, not a decision. From here on a key deletion
must paste its CANDIDATE rows into the change description; the first
audit pass hand-listed keys that turned out to have bench and test
consumers, which is exactly the mistake a mechanical inventory avoids.
Today: 432 env keys (215 CANDIDATE, 217 KEEP), 117 TOML keys
(103 CANDIDATE, 14 KEEP). Registered as config_key_usage_report_unit.
The report counts only files git tracks (git ls-files) when the root is a
work tree, so an ignored local bench harness cannot turn a CANDIDATE into a
KEEP on one machine and not another; outside a work tree it falls back to
scanning the directory, which is what the synthetic-tree test exercises.
Signed-off-by: trvon <me@trevon.dev>
---------
Signed-off-by: trvon <me@trevon.dev>
* feat(embedding): complete embeddings by derivation token
cbc4959c added derivation tokens (begin/complete with a per-attempt
generation, invalidated by document_content triggers) but nothing
produced them: beginDocumentEmbeddingDerivation had no callers and the
completion op's derivations field was never written, while every ingest
still paid for the triggers and the per-hash NOT EXISTS guard.
processEmbedJob now mints one token per document it will embed, before
any input snapshot is read, and completes by token instead of by hash.
A document whose content changes while it is being embedded keeps
has_embedding = 0 and is re-derived later, instead of publishing vectors
for text the corpus no longer holds. Repair jobs travel the same
channel, so they get the same guarantee. The recipe binds the
preparation policy, embedding space, provider version, and dimension
through the embeddingDerivationRecipe helper that cbc4959c also left
unused.
Repository: batchBeginDocumentEmbeddingDerivations and
batchCompleteDocumentEmbeddingDerivations do a whole job in one
transaction each (the single-token completion loop in WriteCoordinator
opened one transaction per document). WriteCoordinator uses the batch
form and takes its stats lock once.
A small EmbeddingDerivationLedger maps job hashes to their tokens so
completion is issued for exactly the documents that produced vectors.
Behavior note: a re-embed (skipExisting = false) of an already-embedded
document resets its readiness at mint time; if that job then fails, the
document reads as not embedded until the next attempt. That is the
attempt semantics the token table was built for.
Not included: the legacy hash completion path is now unproduced and is
removed in a follow-up so the coalescer change is reviewed on its own.
tests/scripts/portability_allowlist.txt: 13 EmbeddingService.cpp
line anchors reflowed; no new environment reads.
The startup reconcile (ServiceManager, "Reconciling document_embeddings_status
from vector rows") used to mark every document with vector rows as embedded,
which would have undone the stale-token guarantee on the next daemon start: a
document whose content changed under an in-flight embed has vector rows for the
old text. reconcileDocumentEmbeddingStatusByHashes now leaves a document with an
open derivation (document_embedding_derivations.completed = 0) at has_embedding =
0, so it is re-derived rather than published; the reconciled-count query applies
the same predicate. Test: an open derivation stays unembedded across a reconcile
and flips once its token completes.
Signed-off-by: trvon <me@trevon.dev>
* fix(embedding): require current recipe to skip
Re-derive legacy or mismatched recipes rather than trusting readiness
alone. Treat derivation lookup errors as failed gathering.
Cover service-level recipe changes and legacy readiness, and characterize
atomic batch completion rollback and retry.
Signed-off-by: trvon <me@trevon.dev>
---------
Signed-off-by: trvon <me@trevon.dev>
* refactor(search): extract auxiliary vector merge
Signed-off-by: trvon <me@trevon.dev>
* fix(search): preserve auxiliary tie behavior
Keep the extracted merge comparator identical to the original score-only sort
and avoid prescribing equal-score order in characterization tests. Qualify the
search detail namespace so the topology test compiles unambiguously.
Signed-off-by: trvon <me@trevon.dev>
* test(search): avoid auxiliary score tie
Signed-off-by: trvon <git@trevon.dev>
---------
Signed-off-by: trvon <me@trevon.dev>
Signed-off-by: trvon <git@trevon.dev>
* chore: remove dead post-ingest helper
Remove shouldPrepareSequentially, the sole Fold unused-function finding
confirmed dead by clean ASan and TSan builds. All other 45 findings have
live callers or uses that Fold's bounded model missed and remain intact.
Fold provenance: build/fold/release-prep-79.sarif (2026-09-01 run).
Signed-off-by: trvon <me@trevon.dev>
* chore(plugins): document Fold ownership
Document 6 proved memory-leak findings as intentional ownership:
process-lifetime Ort::Env singletons and host-freed C-ABI result data.
Refresh portability allowlist anchors shifted by the comments.
Fold provenance: build/fold/release-prep-79.sarif (2026-09-01 run).
Signed-off-by: trvon <me@trevon.dev>
* fix(storage): treat reserve boundary as emergency
Classify available bytes equal to the configured emergency reserve as Emergency, matching the hard-minimum contract and operator message that the reserve has been reached. Add a regression assertion at the exact boundary.
Signed-off-by: trvon <git@trevon.dev>
---------
Signed-off-by: trvon <me@trevon.dev>
Signed-off-by: trvon <git@trevon.dev>
* feat(storage): expose typed disk pressure policy
Signed-off-by: trvon <git@trevon.dev>
* fix(mcp): preserve embedded stdio buffers
Move process-global stream setup to the MCP executable so embedded transports retain caller-owned redirection. Add a regression for the failing construction order.
* refactor(daemon): scope shared P2P protected-file accessors for cross-TU use
The protected-file accessors (readProtectedP2pPrivateKey,
readProtectedP2pTrustFile, writeExclusiveP2pPrivateKey) are shared by the
P2P identity-key paths and the memory-sync bootstrap. Keep their
platform-specific definitions in p2p_integration.cpp unchanged and scope
them into yams::daemon::service_manager_detail, declared by the private,
non-installed header p2p_protected_file.h. Qualify the internal
callsites. No behavior change.
Prepares moving ServiceManager::initializeMemorySync into the
memory-sync translation unit (Brick audit cohesion note).
Signed-off-by: trvon <me@trevon.dev>
* refactor(daemon): place initializeMemorySync with memory-sync TU
Brick structural audit of PR #79 flagged that
ServiceManager::initializeMemorySync lives in p2p_integration.cpp while
the rest of the memory-sync surface lives in memory_sync.cpp. Move it —
together with its exclusive helper ensureAuthenticatedDirectStore — into
memory_sync.cpp.
The shared protected-file accessors remain defined in
p2p_integration.cpp (see the preceding service_manager_detail commit);
moved function bodies are unchanged and ServiceManager.h is untouched.
Brick evidence (experimental pre-79 af609a1e vs post-79 4d942793):
ServiceManager.cpp functions 84 -> 59 with 25 moved and 0 vanished;
ServiceManager.cpp outgoing call/import edges 1292 -> 954, distinct
targets 59 -> 41.
Validation: full ninja build green; meson test yams:service_manager,
yams:service_manager_shutdown, yams:service_manager_degraded — 3/3 OK.
Signed-off-by: trvon <me@trevon.dev>
* refactor(vector): break simeon_embedding_backend.h include cycle with embedding_generator.h
simeon_embedding_backend.h only declares functions taking EmbeddingConfig
by reference, yet included the full embedding_generator.h definition,
forcing every consumer of the simeon backend header to transitively pull
the generator header (and feeding the embedding_generator <->
simeon_embedding_backend 2-cycle reported by the Brick structural survey).
Replace the include with forward declarations for IEmbeddingBackend and
EmbeddingConfig. Consumers that use the full definitions
(simeon_embedding_backend.cpp, search_engine.cpp,
ingestion_throughput_bench.cpp) now include embedding_generator.h
directly.
Brick caveat: the tool's lexical lane cannot see the header-include edge
removal (0.65-confidence interface-dispatch misattribution keeps a
file-level cycle in its graph); the include-cycle break is verified at
source level. Brick survey pre/post artifacts kept in
worktree build/brick/architecture-iter{,-after}.json.
Validation: full ninja build green; meson test yams:vector_submodule,
yams:simeon_model_provider, yams:embedding_lifecycle,
yams:embedding_recovery, yams:service_manager — 5/5 OK.
Signed-off-by: trvon <me@trevon.dev>
* refactor(vector): extract computeCosineSimilarity from VectorDatabase into vector_utils
The pure cosine-similarity helper was stranded as a static member of the
VectorDatabase storage class, forcing sqlite_vec_backend.cpp to include
vector_database.h for a pure math function (Brick deferred lead #2 from
the PR #79 audit loop).
Move the implementation to vector_utils as a free function
(yams::vector::vector_utils::computeCosineSimilarity, semantics copied
verbatim: 0.0 on size mismatch or zero norm), keep
VectorDatabase::computeCosineSimilarity as a forwarding static for the
external caller in doctor dedupe, and switch the four backend callsites
to the free function. sqlite_vec_backend.cpp no longer includes
vector_database.h.
Validation: full ninja build green; meson test vector_submodule (incl.
simeon_backend_catch2_test) and yams:service_manager — green.
Signed-off-by: trvon <me@trevon.dev>
* refactor(daemon): extract client transport types to break sandbox probe header cycle
ClientTransportMode and ClientConfig moved verbatim from daemon_client.h
into a new shared header client_config.h (Brick deferred lead #3 from the
PR #79 audit loop: daemon_client <-> sandbox_detection type-level cycle).
sandbox_detection.h now depends only on client_config.h; its
implementation adds the explicit daemon_client.h include it needs for
the DaemonClient discovery statics. All existing consumers are
unaffected: daemon_client.h includes client_config.h so ClientConfig
remains visible transitively.
Remaining runtime dependency (sandbox_detection.cpp calling
DaemonClient::resolveSocketPathConfigFirst / isDaemonRunning statics,
which forward to socket_utils and a file-local ping helper) is recorded
as the follow-up for full decoupling; the header-level cycle is broken.
Validation: full ninja build green; meson test yams:service_manager,
yams:multiplex_client, yams:jsonrpc_client, vector_submodule — all green.
Signed-off-by: trvon <me@trevon.dev>
* test: refresh portability allowlist line anchors after include shifts
The Brick-audit include additions (search_engine.cpp,
ingestion_throughput_bench.cpp) and removal (sqlite_vec_backend.cpp)
shifted line numbers, making seven portability allowlist entries stale
(same reviewed raw getenv/path usages, new line numbers). Refresh the
anchors; no code or policy change.
Signed-off-by: trvon <me@trevon.dev>
* fix(daemon): include <fstream> for all POSIX /proc probe paths
The /proc readers compile in the non-Windows/non-Apple branch (BSD and
other POSIX targets), not just under __linux__; guard the <fstream>
include with the same condition so those targets compile.
Copilot review finding on PR #80 (src/daemon/components/DaemonMetrics.cpp:7).
Signed-off-by: trvon <me@trevon.dev>
* refactor(daemon): apply storage.disk_pressure outside applyMemorySync
ConfigResolver::applyMemorySync also applied [storage.disk_pressure],
coupling unrelated config parsing to the memory_sync apply path and
producing a misleading refusal message for invalid disk-pressure
policies. Move the call to its natural site in daemon_main before the
memory_sync apply, with its own accurate refusal message. Same startup
semantics (exit 1 on invalid policy), correct attribution.
Copilot review finding on PR #80 (src/daemon/components/ConfigResolver.cpp:1658).
Signed-off-by: trvon <me@trevon.dev>
* test(daemon): update applyMemorySync no-op expectation after disk-pressure decoupling
applyMemorySync no longer applies [storage.disk_pressure] (moved to its
own call site in daemon_main), so a section set without memory_sync
entries is now a no-op rather than a disk-pressure rejection. The
invalid-disk-pressure rejection is still asserted directly against
applyStorageDiskPressure in the preceding checks.
Caught by the pre-push CI gate (asan+tsan: daemon_component_submodule).
Signed-off-by: trvon <me@trevon.dev>
---------
Signed-off-by: trvon <git@trevon.dev>
Signed-off-by: trvon <me@trevon.dev>
Keep `p2p status` text output machine-parseable by removing ANSI severity
colorization, and stop connect/disconnect/forget from throwing on malformed
daemon responses (safe .value defaults, uint64 counters). Bound handshake and
delta timeouts to kP2pMaxOperationTimeout up front, and ceiling direct
sync_interval_ms to the 5-minute reconnect bound so invalid config fails early
instead of aborting daemon startup. Degrade memory-sync status to peerCount=0
on a transient peer-registry read instead of failing the whole status IPC. Add
an explicit `p2p forget` operation (proto FORGET=9 through dispatcher, manager,
and registry) so operators can rotate a peer's pinned key while preserving
fail-closed enrollment.
Signed-off-by: trvon <git@trevon.dev>
* fix(memory-sync): keep replication responsive
Skip poisoned blobs without wedging backfill and publish immutable status snapshots so daemon IPC does not block on an active reconciliation cycle.
Reconstructed from ff62d3f8, 8e232721, and e60f513d.
Signed-off-by: trvon <git@trevon.dev>
* feat(p2p): add authenticated direct transport
Require TLS 1.3 mutual identity and bind the authenticated certificate, node, corpus, and epoch before replication state is exchanged.
Reconstructed from 96f9db88 and c46de77b.
Signed-off-by: trvon <git@trevon.dev>
* feat(p2p): exchange causal deltas
Persist peer pins and causal resume state before visibility so reconnects continue bounded authenticated replication without becoming remote query proxies.
Reconstructed from 1b8be146 and 41883338.
Signed-off-by: trvon <git@trevon.dev>
* feat(p2p): integrate direct sync services
Wire listener, reconnect, IPC, CLI, peer persistence, and daemon lifecycle around the authenticated direct transport.
Reconstructed from a19aaa44.
Signed-off-by: trvon <git@trevon.dev>
* fix(p2p): apply replicated document state
Index authenticated remote winners, propagate document tombstones, and keep service/test wiring complete across daemon paths.
Reconstructed from 4ca660c6, 443253f1, 3e9db24d, and 4b2ddcc5.
Signed-off-by: trvon <git@trevon.dev>
* test(p2p): register direct sync gates
Replace the superseded internal contract with operator-facing direct-sync guidance and register daemon integration coverage in the standard test layout.
Reconstructed from 360b9115 and 89416ccb.
Signed-off-by: trvon <git@trevon.dev>
* fix(p2p): require bounded peer enrollment
Bound aggregate sessions and worker lifetimes, create owner-only identity keys atomically, and reject unknown inbound peers until operators pin their mTLS identity.
Reconstructed from f91be528, 79de8e48, 4fb1e4b8, dc30a6c0, and b329edd3.
Signed-off-by: trvon <git@trevon.dev>
* fix(p2p): persist quarantine and deletes
Persist full-history writer commitments before invalidating forked state, and order local deletion through a durable authenticated outbox so tombstones survive crashes and reconnects.
Reconstructed from 74ce01e0 and 19888604.
Signed-off-by: trvon <git@trevon.dev>
* feat(p2p): add authenticated cold bootstrap
Install bounded zero-state snapshots only from previously operator-pinned peers, bind them to exact frontiers and full-history commitments, and recover staged journals before replication can resume.
Reconstructed from 8a9f5acf.
Signed-off-by: trvon <git@trevon.dev>
---------
Signed-off-by: trvon <git@trevon.dev>
The child forked by 'Daemon does not terminate unverified data-dir lock
holder' built an fs::path and called json::dump(), both of which allocate;
a fork in a multithreaded process can leave the malloc lock held, so the
child could fail before signaling readiness. Pre-compute the lock path in
the parent, format the payload with snprintf into a stack buffer, signal a
failure byte on open/flock error, and retry the parent's read on EINTR.
Portability allowlist line numbers updated for the shift.
Factor the post-setup 'what next / how to configure' guidance into pure
formatLaterCommand/formatInitSummary builders so each decision branch's
help is testable without stdout, and add a focused test asserting the
later-command and ready-summary text. Output is byte-identical.
Portability allowlist line numbers updated for the shift.
Factor the alreadyInitialized && !force_ branch out of the monolithic
execute() into a private handleAlreadyInitialized() helper. Behavior
preserved; setup decision/try nesting reduced. Portability allowlist
line numbers updated for the shift.
Catch Windows/Linux/macOS-only defect classes before push: raw
std::jthread, sqlite3_open* via bare path.c_str(), and raw getenv
reads outside the config boundary. Wired into pre-commit and the
unit/policy suite; 575 reviewed sites allowlisted.
* feat: search work and moving to experimental
* WIP
* feat(search): diagnostic logging pipeline — graph build, concepts, reranker gate
- New [concepts] per-50-query log showing extracted query concepts, type
distribution, and used_gliner flag.
- New [PIQ-entity] per-1000-dispatch + per-document completion log with
entity type counts (e.g., 'protein=3, gene=1, chemical=2').
- New [graph_rerank-gate] one-shot log showing why enableGraphRerank
is zeroed: shows shortQueryBudgeted, corpusWarming, bypassWarmingGate,
lexicalReady, and post-ingest inflight.
- New [graph_rerank] per-50-query log with window size, matched
candidates, applied count, concept count, and elapsed time.
- retrieval_quality_bench.cpp: add YAMS_SEARCH_BYPASS_CORPUS_WARMING_GATE=1
so benchmark queries are not gated by freshness checks.
- resolve merge conflicts from experimental branch.
* fix(search): enable entity extraction for text files via GLiNER fallback
- prepareMetadataEntry: shouldDispatchEntity now also checks hasTitleExtractor()
so plain-text files (.txt, .md) get entity extraction even when no binary
entity provider supports their extension.
- processEntityExtractionStage: when no ExternalEntityProvider matches,
fall back to processTitleExtractionStage which runs GLiNER on text.
- refreshStageAvailability: entity stage is now considered capable when
hasTitleExtractor() is true, so the entity poller gets concurrency budget
even if entityProviders_ is empty.
- kCombinedEntityTypes: add biomedical entity types (protein, gene, cell,
disease, chemical, drug, pathway, biological_process, biomarker, anatomy,
organism) so GLiNER extracts high-value biomedical entities instead of
only generic NL types (person, organization, concept).
* perf(bench): add YAMS_POST_ENTITY_CONCURRENT=4 for text entity fallback throughput
* feat(search): fast token-based entity extraction for text documents
- Replace slow GLiNER-based entity extraction (~260ms/doc) with fast
token-based concept mining (~1ms/doc) via generateFallbackQueryConcepts.
Uses sub-phrase mining + IDF-weighted token salience — the same function
already used for query-time concept fallback.
- Store entities with full KG scaffolding: KGNode with entity type/label,
KGAlias for query-time resolution, DeferredEdge from entity→document,
and DeferredDocEntity linking entity to document for getDocEntitiesForDocument.
- Add biomedical entity types to kCombinedEntityTypes in title extraction
(protein, gene, cell, disease, chemical, drug, pathway, etc.) so GLiNER
can output high-value biomedical entities when used.
- Add include for query_expansion.h to access generateFallbackQueryConcepts.
- bench: add YAMS_POST_ENTITY_CONCURRENT=4 for entity poller throughput.
Benchmark: 22K+ entities extracted across 5183 docs, ~41% high-value
biomedical. MRR 0.4281 (beats 0.4273 baseline by +0.2%).
* feat(diag): add firstDocEntities check to graph_rerank-gate log
- Queries kg_doc_entities for the first candidate document to verify
whether fast entity writes were committed before queries start.
- Confirmed: firstDocEntities=0 — entities stuck in WriteCoordinator
async queue when benchmark queries begin.
* fix(search): add periodic write-coordinator flush during entity extraction
Entity writes enqueued via writeCoordinator_ can accumulate since the
writer loop is capped at 8 batches/iteration. For 5K+ documents this
creates a backlog that outlasts the post-ingest drain wait — entities
are not committed to kg_doc_entities before benchmark queries start.
Add a periodic flush (every 128 batches, 500ms timeout) in the entity
extraction batch processor to drain the WC queue incrementally.
Diagnostic confirms firstDocEntities=0 persists — a synchronous
committed flush or batched WC enqueue is needed.
* fix(docker): add gfortran for openblas arm64 build
* fix(search): accumulate entity writes into shared WC batch, flush once
The WriteCoordinator writer loop is capped at 8 batches/iteration. With
5K+ per-doc entity writes, entities outlast the benchmark's post-ingest
drain and firstDocEntities=0 when the graph scorer runs.
- processEntityExtractionStage: add optional DeferredKGBatch* parameter.
Text-fallback appends entity nodes/aliases/edges/doc-entities to the
shared batch instead of creating per-doc WC batches.
- processEntityExtractionBatch: creates one DeferredKGBatch, passes it to
all stage calls in the batch, enqueues to WC once at end.
- Single-shot WC flush: after all stages go idle (totalInFlight==0),
calls writeCoordinator_->flush(15s) to drain remaining entity writes
before benchmark queries start.
- DeferredKGBatch forward-declared in PostIngestQueue header.
* ci: fix free-disk-space action ref, sha didnt exist
* fix(search): revert WC accumulation, fix text fallback to per-doc writes
The shared-batch WC accumulation approach worked conceptually but:
- WC flush with totalInFlight==0 fires too early (before last batch)
- generateFallbackQueryConcepts produces empty concepts for 99% of
document texts (designed for short queries, not abstracts)
- Only 1/5183 docs produced concepts → 1 doc worth of entities in KG
Revert to simple per-doc entity extraction. The enable=/disable=
graph reranker gate is still controlled by shortQueryBudgeted=1
which zeroes the graph reranker on the first query (intent=Code).
firstDocEntities=0 persists — entities need a different creation
strategy for document text (phrase mining too conservative).
* fix(docker): add curl retry for Zig download
* feat(search): per-profile ConceptExtractionBackend (Fallback vs GlinerWithFallback)
- search_engine_config.h: new ConceptExtractionBackend enum with Fallback
(sub-phrase mining + IDF tokens, <1ms) and GlinerWithFallback (GLiNER
first, fall back on empty/timeout). Default: GlinerWithFallback.
- search_tuner.h: SCIENTIFIC profile sets Fallback (GLiNER returns empty
for 99% of short biomedical claims). Other profiles keep default.
Wired through TunedParams.applyTo().
- search_engine.cpp: gate GLiNER concept extraction on the config backend.
When Fallback, conceptFuture is never created and concepts come directly
from generateFallbackQueryConcepts. Added [concept-backend] diagnostic.
- search_engine_builder.cpp: builder log now includes concept_backend
in the override-state startup message.
- search_tuner_catch2_test.cpp: verify SCIENTIFIC profile uses Fallback.
Note: builder correctly sets Fallback but query-time workingConfig still
shows GlinerWithFallback (int=1). This is a config-sync issue between
builder-time profile application and runtime config snapshot — the
tuner or a post-override config copy may reset the field. To be fixed.
* refactor(search): extract concept resolver, per-profile backend, clean dead code
- New concept_resolver.h/cpp: extract enrichWithFallbackConcepts from the
search engine monolith into a testable unit. Generates IDF-weighted
token concepts via generateFallbackQueryConcepts, deduplicates, and
appends to existing concepts.
- search_engine_config.h: new ConceptExtractionBackend enum (Fallback vs
GlinerWithFallback). SCIENTIFIC profile sets Fallback since GLiNER
extracts 0 concepts for 99% of short biomedical claims.
- search_tuner.h: wire conceptExtractionBackend through TunedParams,
applyTo, seedTunedParamsFromConfig (the one-line fix that resolved
the config-sync bug where profile Fallback was overridden at query
policy resolution time).
- search_engine.cpp: materializeConcepts lambda restructured to always
reach the fallback even when GLiNER future is invalid. Removed
~80 lines of diagnostic bloat ([concept-backend], [workingConfig-*],
per-50 [concepts]). GLiNER gate checks config backend.
- PostIngestQueue.cpp: removed dead fast-token entity extraction
(~150 lines of concept generation + KG scaffolding that produced
0 entities for 99% of documents). Reverted entity-stage gating
(entityCapable || hasTitleExtractor) and shouldDispatchEntity to
extension-only checks. Removed DeferredKGBatch forward decl.
- gliner_query_extractor: removed unused createGlinerExtractionFunc-
FromProvider fallback factory (~150 lines).
- ServiceManager.cpp: removed fallback call site for removed factory.
- builder log: removed concept_backend diagnostic line noise.
- search_tuner_catch2_test.cpp: verify SCIENTIFIC uses Fallback.
Net: ~400 lines removed, ~120 lines added (concept_resolver unit +
config wiring). GLiNER concept extraction savings: ~30-80ms/query
× 300 queries = 9-24s total for SCIENTIFIC profiles.
* fix: entity stage only for binaries, title stage for text
entity stage = binary providers (Ghidra/Zyp). title stage = GLiNER for all
text. No overlap — title already stores NL entities to KG.
* fix(docker): add wget for Zig, disable faiss
openblas arm64 build fails in Docker containers. YAMS_DISABLE_FAISS=1
env var skips faiss (and its openblas transitive dep). Zig download
now uses wget -c for resume on flaky ziglang.org connections.
* ci: add experimental branch to tests trigger
* ci: pin free-disk-space to v1.3.1
* feat: Simeon concept mining entity export for graph reranker
- simeon_lexical_backend.h: EntityCallback in Config. Called with
(docId, entityText, confidence) per concept match per doc.
- simeon_lexical_backend.cpp: after mine_concepts, iterate docs
via collect_doc_concepts + entity_callback.
- SearchEngineManager.cpp: enable concept_mining_enabled, wire
entity_callback batch-inserting DocEntity via KG store. Fix
kgStore capture-before-move race.
Note: simeon submodule has collect_doc_concepts in concept_mining.hpp
+ concept_mining.cpp (must commit to submodule separately).
* ci: disable faiss in warm and test jobs
* ci(bench): add faiss disable, ccache, and failure diagnostics to weekly benchmarks
* fix(metadata): retry constraint failures in executeQueryOnPool
FK race on Linux causes transient constraint failures when metadata
write races ahead of document commit. Previously treated as terminal.
Now retried with backoff like lock errors.
* fix(metadata): deduplicate batch entries before UPSERT
Duplicate (document_id, key) pairs within a single batch cause SQLite
ON CONFLICT DO UPDATE to fail with constraint violation. Dedup keeps
the last write per key.
* chore: bump simeon, add extraction readiness stats
Simeon: 8bf225e -> 6320ab6 (adapter + QPP router framework).
CorpusStats: add contentExtracted*, ftsIndexed*, titleCoverage,
languageCoverage, and KG topology richness fields.
* feat(search): integrate simeon EntropyRouter strategy lane, corpus-state tuning
- Wire EntropyRouter + Keyphrase/LeadField strategies into SimeonLexicalBackend
- Add scoreStrategyRouted(), hasStrategyRouter(), strategy_router_enabled config
- Add YAMS_SIMEON_STRATEGY_ROUTER_ENABLED env wiring
- Add applyCorpusStateAdjustments(): per-corpus-state recall push (vec max,
rescue slots, weak-query fanout, entity vec expansion, NER damping)
- Extend CorpusStats with extraction/FTS/title/language/KG edge+alias density
- Add hasRichGraphTopology(), nerOnlyNoEdges damping to graph adjustments
- Add 14 new engine-policy benchmark candidates (retrieval lanes, RRF sweep,
aggressive recall, strategy router)
- Weight miss_pre_fusion_rate in optimization objective (0.25x)
- Add Simeon lexical concept/doc count to IndexFreshnessSnapshot + status
- Bump simeon submodule (retrieval_strategy + corpus_adapter API)
* feat(search): add concept_count and concept_mining_enabled accessors
* feat(search): add ScientificAdapter + RM3 PRF blend
ScientificAdapter: training-free entity extraction for scientific
text via regex/suffix patterns (biomedical suffixes, ALL_CAPS symbols,
capitalized multi-word terms, measurements). Replaces TextAdapter
in strategy router build, providing entity lists for KeyphraseStrategy.
RM3: add score_with_prf blend path to scoreStrategyRouted at 0.30
weight when rm3_enabled=true. New benchmark candidate
scientific_strategy_rm3.
Strategy router + ScientificAdapter: mrr=0.4514 ndcg=0.4789 recall=0.5849