knowledge-rag 4.8.3__tar.gz → 4.8.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {knowledge_rag-4.8.3 → knowledge_rag-4.8.4}/PKG-INFO +91 -24
- {knowledge_rag-4.8.3 → knowledge_rag-4.8.4}/README.md +89 -22
- {knowledge_rag-4.8.3 → knowledge_rag-4.8.4}/mcp_server/__init__.py +1 -1
- {knowledge_rag-4.8.3 → knowledge_rag-4.8.4}/mcp_server/ingestion.py +24 -3
- {knowledge_rag-4.8.3 → knowledge_rag-4.8.4}/mcp_server/server.py +11 -1
- {knowledge_rag-4.8.3 → knowledge_rag-4.8.4}/pyproject.toml +1 -1
- {knowledge_rag-4.8.3 → knowledge_rag-4.8.4}/.gitignore +0 -0
- {knowledge_rag-4.8.3 → knowledge_rag-4.8.4}/LICENSE +0 -0
- {knowledge_rag-4.8.3 → knowledge_rag-4.8.4}/config.example.yaml +0 -0
- {knowledge_rag-4.8.3 → knowledge_rag-4.8.4}/mcp_server/config.py +0 -0
- {knowledge_rag-4.8.3 → knowledge_rag-4.8.4}/mcp_server/fts5_index.py +0 -0
- {knowledge_rag-4.8.3 → knowledge_rag-4.8.4}/mcp_server/guarded.py +0 -0
- {knowledge_rag-4.8.3 → knowledge_rag-4.8.4}/mcp_server/instance_lock.py +0 -0
- {knowledge_rag-4.8.3 → knowledge_rag-4.8.4}/mcp_server/metrics.py +0 -0
- {knowledge_rag-4.8.3 → knowledge_rag-4.8.4}/mcp_server/preflight.py +0 -0
- {knowledge_rag-4.8.3 → knowledge_rag-4.8.4}/mcp_server/query_router.py +0 -0
- {knowledge_rag-4.8.3 → knowledge_rag-4.8.4}/mcp_server/ratelimit.py +0 -0
- {knowledge_rag-4.8.3 → knowledge_rag-4.8.4}/mcp_server/security.py +0 -0
- {knowledge_rag-4.8.3 → knowledge_rag-4.8.4}/npm/README.md +0 -0
- {knowledge_rag-4.8.3 → knowledge_rag-4.8.4}/presets/cybersecurity.yaml +0 -0
- {knowledge_rag-4.8.3 → knowledge_rag-4.8.4}/presets/developer.yaml +0 -0
- {knowledge_rag-4.8.3 → knowledge_rag-4.8.4}/presets/general.yaml +0 -0
- {knowledge_rag-4.8.3 → knowledge_rag-4.8.4}/presets/multilingual.yaml +0 -0
- {knowledge_rag-4.8.3 → knowledge_rag-4.8.4}/presets/research.yaml +0 -0
- {knowledge_rag-4.8.3 → knowledge_rag-4.8.4}/requirements.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: knowledge-rag
|
|
3
|
-
Version: 4.8.
|
|
3
|
+
Version: 4.8.4
|
|
4
4
|
Summary: Local RAG System for Claude Code — Hybrid search + Cross-encoder Reranking + 13 MCP Tools + 20 Format Parsers. Zero external servers.
|
|
5
5
|
Project-URL: Homepage, https://github.com/lyonzin/knowledge-rag
|
|
6
6
|
Project-URL: Repository, https://github.com/lyonzin/knowledge-rag
|
|
@@ -278,19 +278,32 @@ See [Changelog](#changelog) for full history.
|
|
|
278
278
|
flowchart TB
|
|
279
279
|
subgraph MCP["MCP SERVER (FastMCP)"]
|
|
280
280
|
direction TB
|
|
281
|
-
TOOLS["13 MCP Tools<br/>
|
|
281
|
+
TOOLS["13 MCP Tools (frozen, LEI 1)<br/>search_knowledge | get_document | search_similar<br/>add_document | add_from_url | update_document | remove_document<br/>reindex_documents | get_reindex_status<br/>list_categories | list_documents | get_index_stats | evaluate_retrieval"]
|
|
282
282
|
end
|
|
283
283
|
|
|
284
|
-
subgraph SEARCH["
|
|
284
|
+
subgraph SEARCH["SEARCH DISPATCH (v4.8.2+)"]
|
|
285
|
+
direction TB
|
|
286
|
+
SM["search_method dispatch<br/>auto | hybrid | fts5"]
|
|
287
|
+
QROUTER["QueryRouter (regex)<br/>lexical vs semantic<br/>(ADR-002)"]
|
|
288
|
+
FTS5PATH["FTS5 Fast-Path<br/>(exact identifiers <10ms)"]
|
|
289
|
+
HYBRIDPATH["Hybrid Pipeline<br/>(conceptual queries)"]
|
|
290
|
+
|
|
291
|
+
SM --> QROUTER
|
|
292
|
+
QROUTER -->|lexical| FTS5PATH
|
|
293
|
+
QROUTER -->|semantic| HYBRIDPATH
|
|
294
|
+
FTS5PATH -.->|not_ready / low_hits<br/>fallback| HYBRIDPATH
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
subgraph HYBRID["HYBRID PIPELINE"]
|
|
285
298
|
direction LR
|
|
286
|
-
|
|
299
|
+
KWROUTER["Keyword Router<br/>(word boundaries → category filter)"]
|
|
287
300
|
SEMANTIC["Semantic Search<br/>(ChromaDB)"]
|
|
288
301
|
BM25["BM25 Keyword<br/>(inverted-index + expansion)"]
|
|
289
302
|
RRF["Reciprocal Rank<br/>Fusion (RRF)"]
|
|
290
303
|
RERANK["Cross-Encoder<br/>Reranker"]
|
|
291
304
|
|
|
292
|
-
|
|
293
|
-
|
|
305
|
+
KWROUTER --> SEMANTIC
|
|
306
|
+
KWROUTER --> BM25
|
|
294
307
|
SEMANTIC --> RRF
|
|
295
308
|
BM25 --> RRF
|
|
296
309
|
RRF --> RERANK
|
|
@@ -298,9 +311,11 @@ flowchart TB
|
|
|
298
311
|
|
|
299
312
|
subgraph STORAGE["STORAGE LAYER"]
|
|
300
313
|
direction LR
|
|
301
|
-
CHROMA[("ChromaDB<br/>
|
|
302
|
-
|
|
314
|
+
CHROMA[("ChromaDB<br/>data/chroma_db/<br/>(vector store)")]
|
|
315
|
+
FTS5DB[("SQLite FTS5<br/>data/fts5_index.db<br/>(lexical index, ADR-001)")]
|
|
316
|
+
COLLECTIONS["8 Categories<br/>redteam | blueteam | ctf<br/>security | logscale<br/>development | aar | general"]
|
|
303
317
|
CHROMA --- COLLECTIONS
|
|
318
|
+
FTS5DB --- COLLECTIONS
|
|
304
319
|
end
|
|
305
320
|
|
|
306
321
|
subgraph EMBED["EMBEDDINGS (In-Process)"]
|
|
@@ -312,36 +327,55 @@ flowchart TB
|
|
|
312
327
|
subgraph INGEST["DOCUMENT INGESTION"]
|
|
313
328
|
PARSERS["20 Parsers<br/>MD | PDF | TXT | PY | C | H | CPP | JS | JSX | TS | TSX | JSON | XML | CSV<br/>DOCX | XLSX | PPTX | IPYNB | MQH | MQ4"]
|
|
314
329
|
CHUNKER["Chunking<br/>MD: section-aware<br/>Other: 1000 chars + 200 overlap"]
|
|
315
|
-
|
|
330
|
+
FTS5SYNC["FTS5 CRUD sync<br/>(v4.8.2 task 05)"]
|
|
331
|
+
PARSERS --> CHUNKER --> FTS5SYNC
|
|
316
332
|
end
|
|
317
333
|
|
|
318
334
|
CLAUDE["Claude Code"] --> MCP
|
|
319
335
|
MCP --> SEARCH
|
|
320
|
-
|
|
336
|
+
HYBRIDPATH --> HYBRID
|
|
337
|
+
FTS5PATH --> FTS5DB
|
|
338
|
+
HYBRID --> STORAGE
|
|
321
339
|
STORAGE --> EMBED
|
|
322
340
|
INGEST --> EMBED
|
|
323
341
|
EMBED --> STORAGE
|
|
342
|
+
FTS5SYNC --> FTS5DB
|
|
324
343
|
```
|
|
325
344
|
|
|
326
345
|
### Query Processing Flow
|
|
327
346
|
|
|
328
347
|
```mermaid
|
|
329
348
|
flowchart TB
|
|
330
|
-
QUERY["User Query<br/>'mimikatz credential dump'"] -->
|
|
349
|
+
QUERY["User Query<br/>'mimikatz credential dump' | 'CVE-2021-4034'"] --> METHOD
|
|
350
|
+
|
|
351
|
+
subgraph DISPATCH["Dispatch (v4.8.2+, ADR-006)"]
|
|
352
|
+
METHOD{"search_method<br/>auto | hybrid | fts5"}
|
|
353
|
+
AUTOROUTE["QueryRouter.classify()<br/>regex-based lexical detection"]
|
|
354
|
+
FASTPATH["FTS5 Fast-Path<br/>SQLite MATCH<br/>(<10ms cold, <2ms hot)"]
|
|
355
|
+
NOTREADY{"FTS5 ready<br/>AND hits >= min_hits?"}
|
|
356
|
+
|
|
357
|
+
METHOD -->|auto| AUTOROUTE
|
|
358
|
+
METHOD -->|fts5| FASTPATH
|
|
359
|
+
METHOD -->|hybrid| EXPAND
|
|
360
|
+
AUTOROUTE -->|lexical| FASTPATH
|
|
361
|
+
AUTOROUTE -->|semantic| EXPAND
|
|
362
|
+
FASTPATH --> NOTREADY
|
|
363
|
+
NOTREADY -->|no, fallback| EXPAND
|
|
364
|
+
end
|
|
331
365
|
|
|
332
|
-
subgraph EXPANSION["Query Expansion"]
|
|
366
|
+
subgraph EXPANSION["Query Expansion (hybrid path)"]
|
|
333
367
|
EXPAND["Synonym Expansion<br/>mimikatz -> mimikatz, sekurlsa, logonpasswords"]
|
|
334
368
|
end
|
|
335
369
|
|
|
336
|
-
EXPAND -->
|
|
370
|
+
EXPAND --> KWROUTER
|
|
337
371
|
|
|
338
|
-
subgraph ROUTING["Keyword Routing"]
|
|
339
|
-
|
|
372
|
+
subgraph ROUTING["Keyword Routing (category filter)"]
|
|
373
|
+
KWROUTER["Keyword Router<br/>(word boundaries)"]
|
|
340
374
|
MATCH{"Word Boundary<br/>Match?"}
|
|
341
375
|
CATEGORY["Filter: redteam"]
|
|
342
376
|
NOFILTER["No Filter"]
|
|
343
377
|
|
|
344
|
-
|
|
378
|
+
KWROUTER --> MATCH
|
|
345
379
|
MATCH -->|Yes| CATEGORY
|
|
346
380
|
MATCH -->|No| NOFILTER
|
|
347
381
|
end
|
|
@@ -374,7 +408,8 @@ flowchart TB
|
|
|
374
408
|
BM25 --> RRF
|
|
375
409
|
|
|
376
410
|
ADJ --> MINSCORE
|
|
377
|
-
|
|
411
|
+
NOTREADY -->|yes, hit| MINSCORE
|
|
412
|
+
SNIPPET --> RESULTS["Results<br/>search_method: fts5 | hybrid | semantic | keyword<br/>routed_by: fts5_router | none<br/>score + filtered_by_score + content_length"]
|
|
378
413
|
```
|
|
379
414
|
|
|
380
415
|
### Document Ingestion Flow
|
|
@@ -382,7 +417,7 @@ flowchart TB
|
|
|
382
417
|
```mermaid
|
|
383
418
|
flowchart LR
|
|
384
419
|
subgraph INPUT["Input"]
|
|
385
|
-
FILES["documents/<br/>├── security/<br/>├──
|
|
420
|
+
FILES["documents/<br/>├── security/<br/>│ ├── redteam/<br/>│ ├── blueteam/<br/>│ └── ctf/<br/>├── aar/<br/>├── logscale/<br/>├── development/<br/>└── general/"]
|
|
386
421
|
end
|
|
387
422
|
|
|
388
423
|
subgraph PARSE["Parse (20 formats)"]
|
|
@@ -402,9 +437,18 @@ flowchart LR
|
|
|
402
437
|
FASTEMBED["FastEmbed ONNX<br/>bge-small-en-v1.5<br/>(384D, CPU or GPU)"]
|
|
403
438
|
end
|
|
404
439
|
|
|
440
|
+
subgraph DISPATCH["Write Dispatch (v4.8.3, #161)"]
|
|
441
|
+
WCOL{"_write_collection<br/>routes writes"}
|
|
442
|
+
STAGING["Staging Collection<br/>(nuclear_rebuild only,<br/>keeps prod serving queries)"]
|
|
443
|
+
PROD["Production Collection<br/>(default)"]
|
|
444
|
+
WCOL -->|_staging_target set| STAGING
|
|
445
|
+
WCOL -->|default| PROD
|
|
446
|
+
end
|
|
447
|
+
|
|
405
448
|
subgraph STORE["Store"]
|
|
406
|
-
CHROMADB[("ChromaDB")]
|
|
407
|
-
BM25IDX["BM25 Index"]
|
|
449
|
+
CHROMADB[("ChromaDB<br/>data/chroma_db/")]
|
|
450
|
+
BM25IDX["BM25 Index<br/>(in-memory,<br/>rebuilt on write)"]
|
|
451
|
+
FTS5DB[("SQLite FTS5<br/>data/fts5_index.db<br/>(CRUD-synced, ADR-008)")]
|
|
408
452
|
end
|
|
409
453
|
|
|
410
454
|
FILES --> MD & PDF & OFFICE & CODE
|
|
@@ -413,14 +457,20 @@ flowchart LR
|
|
|
413
457
|
MDSPLIT --> DEDUP
|
|
414
458
|
TXTSPLIT --> DEDUP
|
|
415
459
|
DEDUP --> EMBED
|
|
416
|
-
EMBED -->
|
|
460
|
+
EMBED --> DISPATCH
|
|
461
|
+
STAGING --> CHROMADB
|
|
462
|
+
PROD --> CHROMADB
|
|
463
|
+
PROD --> BM25IDX
|
|
464
|
+
PROD --> FTS5DB
|
|
417
465
|
```
|
|
418
466
|
|
|
419
467
|
### hybrid_alpha Parameter Effect
|
|
420
468
|
|
|
469
|
+
`hybrid_alpha` weights RRF fusion between semantic and BM25 rankings on the **hybrid pipeline only**. When `search_method="auto"` and the QueryRouter classifies the query as lexical (e.g. `CVE-2021-4034`, `MDR-AD002`, `T1078.001`, file hashes), the FTS5 fast-path fires and `hybrid_alpha` is not consulted — FTS5 uses SQLite's native `bm25()` scoring exclusively. Pass `search_method="hybrid"` to force RRF fusion + rerank on every query if you want deterministic hybrid semantics regardless of the query shape.
|
|
470
|
+
|
|
421
471
|
```mermaid
|
|
422
472
|
flowchart LR
|
|
423
|
-
subgraph ALPHA["hybrid_alpha values"]
|
|
473
|
+
subgraph ALPHA["hybrid_alpha values (hybrid path only)"]
|
|
424
474
|
A0["0.0<br/>Pure BM25<br/>Instant"]
|
|
425
475
|
A3["0.3 (default)<br/>Keyword-heavy<br/>Fast"]
|
|
426
476
|
A5["0.5<br/>Balanced"]
|
|
@@ -429,7 +479,7 @@ flowchart LR
|
|
|
429
479
|
end
|
|
430
480
|
|
|
431
481
|
subgraph USE["Best For"]
|
|
432
|
-
U0["CVEs, tool names<br/>exact matches"]
|
|
482
|
+
U0["CVEs, tool names<br/>exact matches<br/>(consider FTS5 fast-path)"]
|
|
433
483
|
U3["Technical queries<br/>specific terms"]
|
|
434
484
|
U5["General queries"]
|
|
435
485
|
U7["Conceptual queries<br/>related topics"]
|
|
@@ -1483,6 +1533,23 @@ Common issues:
|
|
|
1483
1533
|
|
|
1484
1534
|
### Unreleased
|
|
1485
1535
|
|
|
1536
|
+
### v4.8.4 (2026-08-13) — Patch: security + durability + defensive fixes
|
|
1537
|
+
|
|
1538
|
+
**Recommended upgrade for anyone running HTTP/SSE transport with bearer auth on a non-loopback host, or relying on `reindex_documents(force=True)`.** Four independent fixes accumulated since v4.8.3: one previously blocked remote-with-auth deployments (#174), one silently corrupted durable index metadata after a failed rebuild (#173), and two defensive fixes hardened `.ipynb` ingestion (#175) and the nightly chaos suite (#164).
|
|
1539
|
+
|
|
1540
|
+
**Fixed:**
|
|
1541
|
+
|
|
1542
|
+
- **fix(security)** — create the HTTP MCP app with the configured `server_host`. On the bearer-auth path the app factory was called without a host, so it defaulted to `127.0.0.1` and MCP 2.0.0's localhost-only DNS-rebinding allowlist rejected a configured non-loopback host with `421 Invalid Host header` before auth ran — even though uvicorn was bound to that host. Remote/LAN deployments with auth are now reachable (#174).
|
|
1543
|
+
- **fix(reindex)** — restore durable metadata on a failed zero-downtime rebuild. The staging populate path persisted the staging map to `index_metadata.json` before validation ran; when validation failed, rollback restored only the in-memory `_indexed_docs`, leaving the file describing staging while memory held production. After a restart `_load_metadata` trusted the stale file, corrupting skip/update and embedding-dimension decisions. Rollback now re-persists the restored production metadata (#173).
|
|
1544
|
+
- **fix(ingestion)** — `_parse_ipynb` handled invalid JSON gracefully but then assumed the parsed notebook was a well-formed dict. A valid-JSON notebook with a null `metadata` or `cells` field, a non-dict cell, a bare list, or a dict cell whose `source` is not `str | list[str]` (a number, a dict, or a list with non-string items) crashed with `AttributeError`/`TypeError`. Guarded the notebook, `metadata`, `cells`, cell, and cell `source` lookups so a parseable-but-malformed notebook is handled the same as any other odd input (#175, thanks @eeshsaxena).
|
|
1545
|
+
- **fix(ci)** — the nightly chaos job (`pytest -m chaos`) aborted collection with `ModuleNotFoundError: No module named 'hypothesis'` because `tests/test_query_router.py` imported hypothesis at module top-level while the chaos job installs only `pytest psutil numpy`. Guarded the import with `pytest.importorskip("hypothesis")` so the module is skipped cleanly instead of failing the whole nightly run (#164).
|
|
1546
|
+
|
|
1547
|
+
**Docs:**
|
|
1548
|
+
|
|
1549
|
+
- **docs(architecture)** — sync the 4 Mermaid diagrams in `## Architecture` (System Overview, Query Processing Flow, Document Ingestion Flow, hybrid_alpha Parameter Effect) with the v4.8.2 FTS5 fast-path (ADR-002/003/006), the v4.8.3 `_write_collection` staging dispatcher (#161), and the current 8-category layout. The diagrams previously described only the pre-v4.8 hybrid pipeline and 4-category storage; they now also show FTS5 dispatch, fallback semantics, FTS5 CRUD sync (ADR-008), and the zero-downtime staging write path. `hybrid_alpha` now clarifies that the fast-path bypasses RRF entirely so the weighting only matters on the hybrid path.
|
|
1550
|
+
|
|
1551
|
+
**No breaking changes.** Backwards compat verified by `check_api_surface.py` (zero diff vs baseline). All 13 MCP tool parameter names frozen (verified by `tests/test_backwards_compat.py`).
|
|
1552
|
+
|
|
1486
1553
|
### v4.8.3 (2026-08-10) — Critical hotfix: nuclear-rebuild + smart-reindex hardening
|
|
1487
1554
|
|
|
1488
1555
|
**Recommended upgrade for any corpus with >1000 chunks or any user relying on `reindex_documents(force=True)` after a config change.** The v4.8.0 nuclear rebuild + v4.8.2 FTS5 fast-path have several latent state-management bugs that only trigger under a real-world combination (large corpus + rebuild + concurrent queries + FTS5 opt-in). Enterprise users with hacktricks-scale corpora (~50k+ chunks) are affected.
|
|
@@ -230,19 +230,32 @@ See [Changelog](#changelog) for full history.
|
|
|
230
230
|
flowchart TB
|
|
231
231
|
subgraph MCP["MCP SERVER (FastMCP)"]
|
|
232
232
|
direction TB
|
|
233
|
-
TOOLS["13 MCP Tools<br/>
|
|
233
|
+
TOOLS["13 MCP Tools (frozen, LEI 1)<br/>search_knowledge | get_document | search_similar<br/>add_document | add_from_url | update_document | remove_document<br/>reindex_documents | get_reindex_status<br/>list_categories | list_documents | get_index_stats | evaluate_retrieval"]
|
|
234
234
|
end
|
|
235
235
|
|
|
236
|
-
subgraph SEARCH["
|
|
236
|
+
subgraph SEARCH["SEARCH DISPATCH (v4.8.2+)"]
|
|
237
|
+
direction TB
|
|
238
|
+
SM["search_method dispatch<br/>auto | hybrid | fts5"]
|
|
239
|
+
QROUTER["QueryRouter (regex)<br/>lexical vs semantic<br/>(ADR-002)"]
|
|
240
|
+
FTS5PATH["FTS5 Fast-Path<br/>(exact identifiers <10ms)"]
|
|
241
|
+
HYBRIDPATH["Hybrid Pipeline<br/>(conceptual queries)"]
|
|
242
|
+
|
|
243
|
+
SM --> QROUTER
|
|
244
|
+
QROUTER -->|lexical| FTS5PATH
|
|
245
|
+
QROUTER -->|semantic| HYBRIDPATH
|
|
246
|
+
FTS5PATH -.->|not_ready / low_hits<br/>fallback| HYBRIDPATH
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
subgraph HYBRID["HYBRID PIPELINE"]
|
|
237
250
|
direction LR
|
|
238
|
-
|
|
251
|
+
KWROUTER["Keyword Router<br/>(word boundaries → category filter)"]
|
|
239
252
|
SEMANTIC["Semantic Search<br/>(ChromaDB)"]
|
|
240
253
|
BM25["BM25 Keyword<br/>(inverted-index + expansion)"]
|
|
241
254
|
RRF["Reciprocal Rank<br/>Fusion (RRF)"]
|
|
242
255
|
RERANK["Cross-Encoder<br/>Reranker"]
|
|
243
256
|
|
|
244
|
-
|
|
245
|
-
|
|
257
|
+
KWROUTER --> SEMANTIC
|
|
258
|
+
KWROUTER --> BM25
|
|
246
259
|
SEMANTIC --> RRF
|
|
247
260
|
BM25 --> RRF
|
|
248
261
|
RRF --> RERANK
|
|
@@ -250,9 +263,11 @@ flowchart TB
|
|
|
250
263
|
|
|
251
264
|
subgraph STORAGE["STORAGE LAYER"]
|
|
252
265
|
direction LR
|
|
253
|
-
CHROMA[("ChromaDB<br/>
|
|
254
|
-
|
|
266
|
+
CHROMA[("ChromaDB<br/>data/chroma_db/<br/>(vector store)")]
|
|
267
|
+
FTS5DB[("SQLite FTS5<br/>data/fts5_index.db<br/>(lexical index, ADR-001)")]
|
|
268
|
+
COLLECTIONS["8 Categories<br/>redteam | blueteam | ctf<br/>security | logscale<br/>development | aar | general"]
|
|
255
269
|
CHROMA --- COLLECTIONS
|
|
270
|
+
FTS5DB --- COLLECTIONS
|
|
256
271
|
end
|
|
257
272
|
|
|
258
273
|
subgraph EMBED["EMBEDDINGS (In-Process)"]
|
|
@@ -264,36 +279,55 @@ flowchart TB
|
|
|
264
279
|
subgraph INGEST["DOCUMENT INGESTION"]
|
|
265
280
|
PARSERS["20 Parsers<br/>MD | PDF | TXT | PY | C | H | CPP | JS | JSX | TS | TSX | JSON | XML | CSV<br/>DOCX | XLSX | PPTX | IPYNB | MQH | MQ4"]
|
|
266
281
|
CHUNKER["Chunking<br/>MD: section-aware<br/>Other: 1000 chars + 200 overlap"]
|
|
267
|
-
|
|
282
|
+
FTS5SYNC["FTS5 CRUD sync<br/>(v4.8.2 task 05)"]
|
|
283
|
+
PARSERS --> CHUNKER --> FTS5SYNC
|
|
268
284
|
end
|
|
269
285
|
|
|
270
286
|
CLAUDE["Claude Code"] --> MCP
|
|
271
287
|
MCP --> SEARCH
|
|
272
|
-
|
|
288
|
+
HYBRIDPATH --> HYBRID
|
|
289
|
+
FTS5PATH --> FTS5DB
|
|
290
|
+
HYBRID --> STORAGE
|
|
273
291
|
STORAGE --> EMBED
|
|
274
292
|
INGEST --> EMBED
|
|
275
293
|
EMBED --> STORAGE
|
|
294
|
+
FTS5SYNC --> FTS5DB
|
|
276
295
|
```
|
|
277
296
|
|
|
278
297
|
### Query Processing Flow
|
|
279
298
|
|
|
280
299
|
```mermaid
|
|
281
300
|
flowchart TB
|
|
282
|
-
QUERY["User Query<br/>'mimikatz credential dump'"] -->
|
|
301
|
+
QUERY["User Query<br/>'mimikatz credential dump' | 'CVE-2021-4034'"] --> METHOD
|
|
302
|
+
|
|
303
|
+
subgraph DISPATCH["Dispatch (v4.8.2+, ADR-006)"]
|
|
304
|
+
METHOD{"search_method<br/>auto | hybrid | fts5"}
|
|
305
|
+
AUTOROUTE["QueryRouter.classify()<br/>regex-based lexical detection"]
|
|
306
|
+
FASTPATH["FTS5 Fast-Path<br/>SQLite MATCH<br/>(<10ms cold, <2ms hot)"]
|
|
307
|
+
NOTREADY{"FTS5 ready<br/>AND hits >= min_hits?"}
|
|
308
|
+
|
|
309
|
+
METHOD -->|auto| AUTOROUTE
|
|
310
|
+
METHOD -->|fts5| FASTPATH
|
|
311
|
+
METHOD -->|hybrid| EXPAND
|
|
312
|
+
AUTOROUTE -->|lexical| FASTPATH
|
|
313
|
+
AUTOROUTE -->|semantic| EXPAND
|
|
314
|
+
FASTPATH --> NOTREADY
|
|
315
|
+
NOTREADY -->|no, fallback| EXPAND
|
|
316
|
+
end
|
|
283
317
|
|
|
284
|
-
subgraph EXPANSION["Query Expansion"]
|
|
318
|
+
subgraph EXPANSION["Query Expansion (hybrid path)"]
|
|
285
319
|
EXPAND["Synonym Expansion<br/>mimikatz -> mimikatz, sekurlsa, logonpasswords"]
|
|
286
320
|
end
|
|
287
321
|
|
|
288
|
-
EXPAND -->
|
|
322
|
+
EXPAND --> KWROUTER
|
|
289
323
|
|
|
290
|
-
subgraph ROUTING["Keyword Routing"]
|
|
291
|
-
|
|
324
|
+
subgraph ROUTING["Keyword Routing (category filter)"]
|
|
325
|
+
KWROUTER["Keyword Router<br/>(word boundaries)"]
|
|
292
326
|
MATCH{"Word Boundary<br/>Match?"}
|
|
293
327
|
CATEGORY["Filter: redteam"]
|
|
294
328
|
NOFILTER["No Filter"]
|
|
295
329
|
|
|
296
|
-
|
|
330
|
+
KWROUTER --> MATCH
|
|
297
331
|
MATCH -->|Yes| CATEGORY
|
|
298
332
|
MATCH -->|No| NOFILTER
|
|
299
333
|
end
|
|
@@ -326,7 +360,8 @@ flowchart TB
|
|
|
326
360
|
BM25 --> RRF
|
|
327
361
|
|
|
328
362
|
ADJ --> MINSCORE
|
|
329
|
-
|
|
363
|
+
NOTREADY -->|yes, hit| MINSCORE
|
|
364
|
+
SNIPPET --> RESULTS["Results<br/>search_method: fts5 | hybrid | semantic | keyword<br/>routed_by: fts5_router | none<br/>score + filtered_by_score + content_length"]
|
|
330
365
|
```
|
|
331
366
|
|
|
332
367
|
### Document Ingestion Flow
|
|
@@ -334,7 +369,7 @@ flowchart TB
|
|
|
334
369
|
```mermaid
|
|
335
370
|
flowchart LR
|
|
336
371
|
subgraph INPUT["Input"]
|
|
337
|
-
FILES["documents/<br/>├── security/<br/>├──
|
|
372
|
+
FILES["documents/<br/>├── security/<br/>│ ├── redteam/<br/>│ ├── blueteam/<br/>│ └── ctf/<br/>├── aar/<br/>├── logscale/<br/>├── development/<br/>└── general/"]
|
|
338
373
|
end
|
|
339
374
|
|
|
340
375
|
subgraph PARSE["Parse (20 formats)"]
|
|
@@ -354,9 +389,18 @@ flowchart LR
|
|
|
354
389
|
FASTEMBED["FastEmbed ONNX<br/>bge-small-en-v1.5<br/>(384D, CPU or GPU)"]
|
|
355
390
|
end
|
|
356
391
|
|
|
392
|
+
subgraph DISPATCH["Write Dispatch (v4.8.3, #161)"]
|
|
393
|
+
WCOL{"_write_collection<br/>routes writes"}
|
|
394
|
+
STAGING["Staging Collection<br/>(nuclear_rebuild only,<br/>keeps prod serving queries)"]
|
|
395
|
+
PROD["Production Collection<br/>(default)"]
|
|
396
|
+
WCOL -->|_staging_target set| STAGING
|
|
397
|
+
WCOL -->|default| PROD
|
|
398
|
+
end
|
|
399
|
+
|
|
357
400
|
subgraph STORE["Store"]
|
|
358
|
-
CHROMADB[("ChromaDB")]
|
|
359
|
-
BM25IDX["BM25 Index"]
|
|
401
|
+
CHROMADB[("ChromaDB<br/>data/chroma_db/")]
|
|
402
|
+
BM25IDX["BM25 Index<br/>(in-memory,<br/>rebuilt on write)"]
|
|
403
|
+
FTS5DB[("SQLite FTS5<br/>data/fts5_index.db<br/>(CRUD-synced, ADR-008)")]
|
|
360
404
|
end
|
|
361
405
|
|
|
362
406
|
FILES --> MD & PDF & OFFICE & CODE
|
|
@@ -365,14 +409,20 @@ flowchart LR
|
|
|
365
409
|
MDSPLIT --> DEDUP
|
|
366
410
|
TXTSPLIT --> DEDUP
|
|
367
411
|
DEDUP --> EMBED
|
|
368
|
-
EMBED -->
|
|
412
|
+
EMBED --> DISPATCH
|
|
413
|
+
STAGING --> CHROMADB
|
|
414
|
+
PROD --> CHROMADB
|
|
415
|
+
PROD --> BM25IDX
|
|
416
|
+
PROD --> FTS5DB
|
|
369
417
|
```
|
|
370
418
|
|
|
371
419
|
### hybrid_alpha Parameter Effect
|
|
372
420
|
|
|
421
|
+
`hybrid_alpha` weights RRF fusion between semantic and BM25 rankings on the **hybrid pipeline only**. When `search_method="auto"` and the QueryRouter classifies the query as lexical (e.g. `CVE-2021-4034`, `MDR-AD002`, `T1078.001`, file hashes), the FTS5 fast-path fires and `hybrid_alpha` is not consulted — FTS5 uses SQLite's native `bm25()` scoring exclusively. Pass `search_method="hybrid"` to force RRF fusion + rerank on every query if you want deterministic hybrid semantics regardless of the query shape.
|
|
422
|
+
|
|
373
423
|
```mermaid
|
|
374
424
|
flowchart LR
|
|
375
|
-
subgraph ALPHA["hybrid_alpha values"]
|
|
425
|
+
subgraph ALPHA["hybrid_alpha values (hybrid path only)"]
|
|
376
426
|
A0["0.0<br/>Pure BM25<br/>Instant"]
|
|
377
427
|
A3["0.3 (default)<br/>Keyword-heavy<br/>Fast"]
|
|
378
428
|
A5["0.5<br/>Balanced"]
|
|
@@ -381,7 +431,7 @@ flowchart LR
|
|
|
381
431
|
end
|
|
382
432
|
|
|
383
433
|
subgraph USE["Best For"]
|
|
384
|
-
U0["CVEs, tool names<br/>exact matches"]
|
|
434
|
+
U0["CVEs, tool names<br/>exact matches<br/>(consider FTS5 fast-path)"]
|
|
385
435
|
U3["Technical queries<br/>specific terms"]
|
|
386
436
|
U5["General queries"]
|
|
387
437
|
U7["Conceptual queries<br/>related topics"]
|
|
@@ -1435,6 +1485,23 @@ Common issues:
|
|
|
1435
1485
|
|
|
1436
1486
|
### Unreleased
|
|
1437
1487
|
|
|
1488
|
+
### v4.8.4 (2026-08-13) — Patch: security + durability + defensive fixes
|
|
1489
|
+
|
|
1490
|
+
**Recommended upgrade for anyone running HTTP/SSE transport with bearer auth on a non-loopback host, or relying on `reindex_documents(force=True)`.** Four independent fixes accumulated since v4.8.3: one previously blocked remote-with-auth deployments (#174), one silently corrupted durable index metadata after a failed rebuild (#173), and two defensive fixes hardened `.ipynb` ingestion (#175) and the nightly chaos suite (#164).
|
|
1491
|
+
|
|
1492
|
+
**Fixed:**
|
|
1493
|
+
|
|
1494
|
+
- **fix(security)** — create the HTTP MCP app with the configured `server_host`. On the bearer-auth path the app factory was called without a host, so it defaulted to `127.0.0.1` and MCP 2.0.0's localhost-only DNS-rebinding allowlist rejected a configured non-loopback host with `421 Invalid Host header` before auth ran — even though uvicorn was bound to that host. Remote/LAN deployments with auth are now reachable (#174).
|
|
1495
|
+
- **fix(reindex)** — restore durable metadata on a failed zero-downtime rebuild. The staging populate path persisted the staging map to `index_metadata.json` before validation ran; when validation failed, rollback restored only the in-memory `_indexed_docs`, leaving the file describing staging while memory held production. After a restart `_load_metadata` trusted the stale file, corrupting skip/update and embedding-dimension decisions. Rollback now re-persists the restored production metadata (#173).
|
|
1496
|
+
- **fix(ingestion)** — `_parse_ipynb` handled invalid JSON gracefully but then assumed the parsed notebook was a well-formed dict. A valid-JSON notebook with a null `metadata` or `cells` field, a non-dict cell, a bare list, or a dict cell whose `source` is not `str | list[str]` (a number, a dict, or a list with non-string items) crashed with `AttributeError`/`TypeError`. Guarded the notebook, `metadata`, `cells`, cell, and cell `source` lookups so a parseable-but-malformed notebook is handled the same as any other odd input (#175, thanks @eeshsaxena).
|
|
1497
|
+
- **fix(ci)** — the nightly chaos job (`pytest -m chaos`) aborted collection with `ModuleNotFoundError: No module named 'hypothesis'` because `tests/test_query_router.py` imported hypothesis at module top-level while the chaos job installs only `pytest psutil numpy`. Guarded the import with `pytest.importorskip("hypothesis")` so the module is skipped cleanly instead of failing the whole nightly run (#164).
|
|
1498
|
+
|
|
1499
|
+
**Docs:**
|
|
1500
|
+
|
|
1501
|
+
- **docs(architecture)** — sync the 4 Mermaid diagrams in `## Architecture` (System Overview, Query Processing Flow, Document Ingestion Flow, hybrid_alpha Parameter Effect) with the v4.8.2 FTS5 fast-path (ADR-002/003/006), the v4.8.3 `_write_collection` staging dispatcher (#161), and the current 8-category layout. The diagrams previously described only the pre-v4.8 hybrid pipeline and 4-category storage; they now also show FTS5 dispatch, fallback semantics, FTS5 CRUD sync (ADR-008), and the zero-downtime staging write path. `hybrid_alpha` now clarifies that the fast-path bypasses RRF entirely so the weighting only matters on the hybrid path.
|
|
1502
|
+
|
|
1503
|
+
**No breaking changes.** Backwards compat verified by `check_api_surface.py` (zero diff vs baseline). All 13 MCP tool parameter names frozen (verified by `tests/test_backwards_compat.py`).
|
|
1504
|
+
|
|
1438
1505
|
### v4.8.3 (2026-08-10) — Critical hotfix: nuclear-rebuild + smart-reindex hardening
|
|
1439
1506
|
|
|
1440
1507
|
**Recommended upgrade for any corpus with >1000 chunks or any user relying on `reindex_documents(force=True)` after a config change.** The v4.8.0 nuclear rebuild + v4.8.2 FTS5 fast-path have several latent state-management bugs that only trigger under a real-world combination (large corpus + rebuild + concurrent queries + FTS5 opt-in). Enterprise users with hacktricks-scale corpora (~50k+ chunks) are affected.
|
|
@@ -613,23 +613,44 @@ class DocumentParser:
|
|
|
613
613
|
metadata["is_valid_json"] = False
|
|
614
614
|
return raw, metadata
|
|
615
615
|
|
|
616
|
+
# Valid JSON, but not necessarily a well-formed notebook: it may be a
|
|
617
|
+
# bare list/scalar, or have null / wrong-typed "metadata"/"cells" fields.
|
|
618
|
+
# Guard the lookups so a malformed-but-parseable .ipynb is handled
|
|
619
|
+
# gracefully instead of crashing with AttributeError/TypeError.
|
|
620
|
+
if not isinstance(nb, dict):
|
|
621
|
+
metadata["is_valid_json"] = False
|
|
622
|
+
return raw, metadata
|
|
623
|
+
|
|
616
624
|
metadata["is_valid_json"] = True
|
|
617
625
|
metadata["nbformat"] = nb.get("nbformat", 0)
|
|
618
|
-
|
|
626
|
+
nb_metadata = nb.get("metadata")
|
|
627
|
+
kernel = nb_metadata.get("kernelspec") if isinstance(nb_metadata, dict) else None
|
|
628
|
+
if not isinstance(kernel, dict):
|
|
629
|
+
kernel = {}
|
|
619
630
|
metadata["kernel"] = kernel.get("display_name", kernel.get("name", "unknown"))
|
|
620
631
|
|
|
621
|
-
cells = nb.get("cells"
|
|
632
|
+
cells = nb.get("cells")
|
|
633
|
+
if not isinstance(cells, list):
|
|
634
|
+
cells = []
|
|
622
635
|
metadata["cells"] = len(cells)
|
|
623
636
|
code_cells = 0
|
|
624
637
|
markdown_cells = 0
|
|
625
638
|
|
|
626
639
|
parts = []
|
|
627
640
|
for cell in cells:
|
|
641
|
+
if not isinstance(cell, dict):
|
|
642
|
+
continue
|
|
628
643
|
cell_type = cell.get("cell_type", "")
|
|
629
644
|
source = cell.get("source", "")
|
|
630
645
|
|
|
646
|
+
# Per the nbformat spec `source` is str | list[str]. Anything else
|
|
647
|
+
# (a number, a dict, a list with non-string items) is the same
|
|
648
|
+
# malformed-but-parseable case handled above, so tolerate it rather
|
|
649
|
+
# than letting it reach "".join()/.strip() and raise.
|
|
631
650
|
if isinstance(source, list):
|
|
632
|
-
source = "".join(source)
|
|
651
|
+
source = "".join(s for s in source if isinstance(s, str))
|
|
652
|
+
elif not isinstance(source, str):
|
|
653
|
+
continue
|
|
633
654
|
|
|
634
655
|
if not source or not source.strip():
|
|
635
656
|
continue
|
|
@@ -2420,6 +2420,12 @@ class KnowledgeOrchestrator:
|
|
|
2420
2420
|
# collection we're about to delete.
|
|
2421
2421
|
self._staging_target = None
|
|
2422
2422
|
self._rollback_staging_state(saved)
|
|
2423
|
+
# GH #173: populate persisted the staging map to index_metadata.json
|
|
2424
|
+
# (index_all -> _finalize_reindex -> _save_metadata) before validation.
|
|
2425
|
+
# Restoring only in-memory fields would leave the file describing
|
|
2426
|
+
# staging while memory holds production; re-persist so durable metadata
|
|
2427
|
+
# matches the restored state and a restart won't load stale staging.
|
|
2428
|
+
self._save_metadata()
|
|
2423
2429
|
try:
|
|
2424
2430
|
self.chroma_client.delete_collection(f"{prod_name}__staging_{ts}")
|
|
2425
2431
|
except Exception:
|
|
@@ -4550,7 +4556,11 @@ def _run_transport(transport: str) -> None:
|
|
|
4550
4556
|
import uvicorn
|
|
4551
4557
|
|
|
4552
4558
|
app_factory = _http_app_factory(transport)
|
|
4553
|
-
app
|
|
4559
|
+
# Build the app with the configured host. MCP 2.0.0's factories default to
|
|
4560
|
+
# host="127.0.0.1", which turns on a localhost-only DNS-rebinding allowlist;
|
|
4561
|
+
# without this, a non-loopback server_host is served by uvicorn but the app
|
|
4562
|
+
# rejects the configured host with 421 before auth runs (GH #174).
|
|
4563
|
+
app = app_factory(host=config.server_host)
|
|
4554
4564
|
guarded = BearerAuthMiddleware(app, token)
|
|
4555
4565
|
print(
|
|
4556
4566
|
f"[SECURITY] Bearer auth enforced on {transport} transport ({config.server_host}:{config.server_port})",
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "knowledge-rag"
|
|
7
|
-
version = "4.8.
|
|
7
|
+
version = "4.8.4"
|
|
8
8
|
description = "Local RAG System for Claude Code — Hybrid search + Cross-encoder Reranking + 13 MCP Tools + 20 Format Parsers. Zero external servers."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|