docs: consolidate docs/modules/* → docs/modules.md (single file)
12 per-module files + index = 13 files of 50-185 lines each =
1,064 lines of API reference scattered across a directory.
Each per-module file had real meat (API examples, ASCII tree,
conventions) but the cognitive cost of 'which file is this in?'
outweighed the navigation benefit.
Built via concatenation + patch-fix:
- cat index.md + per-module files in topological order
- rewrite ../diagrams/ -> diagrams/ (relative to docs/modules.md)
- rewrite ../../aborist/ -> ../aborist/
- rewrite ../TICKETS.md -> TICKETS.md, ../mesh.md -> mesh.md, etc.
- inter-module links (./<name>.md, <name>.md) -> #<name>-py anchors
- demote per-module H1 -> H2, H2 -> H3, etc., so the wrapper H1
is the only top-level heading
- de-dup the index.md's (now-H2) 'Aborist module reference'
header against the wrapper, replace with 'Diagrams index'
- inject explicit <a id="<name>-py"></a> anchors after each
module's H2 so the TOC links resolve regardless of GitHub's
auto-slug rules
- polish TOC link text: '[merkle.md](#merkle-py)' -> '[↓](#...)'
(the '.md' suffix made no sense once it's an in-doc anchor)
References updated:
- README.md (×2)
Net: 1,124 single-file lines vs 1,064 across 13 files. Slightly
larger because of the patch-fix scaffolding (anchors + section
markers), but one Cmd-F covers everything.
751/34 tests still pass.
This commit is contained in:
parent
7444f106fc
commit
ea82e4288c
14 changed files with 1126 additions and 1066 deletions
|
|
@ -414,7 +414,7 @@ make activity ACTIVITY_LIMIT=20
|
|||
|
||||

|
||||
|
||||
For per-module reference docs see [`docs/modules/`](docs/modules/index.md).
|
||||
For per-module reference docs see [`docs/modules.md`](docs/modules.md).
|
||||
Full pipeline diagrams live in [`docs/diagrams/`](docs/diagrams/) — query,
|
||||
ingest, verifier-ladder, plus the mesh sub-diagrams. Render any change
|
||||
with `make docs`.
|
||||
|
|
@ -478,7 +478,7 @@ aborist/
|
|||
└── cli.py argparse entrypoint (the make targets call into here).
|
||||
```
|
||||
|
||||
For diagrams of how these modules wire together, see `docs/diagrams/` (rendered SVGs of the module graph, query pipeline, ingest pipeline, mesh wire, & verifier ladder). For per-module reference docs see `docs/modules/` — every top-level package has its own page.
|
||||
For diagrams of how these modules wire together, see `docs/diagrams/` (rendered SVGs of the module graph, query pipeline, ingest pipeline, mesh wire, & verifier ladder). For per-module reference docs see `docs/modules.md` — every top-level package has its own page.
|
||||
|
||||
Source papers (read first if confused):
|
||||
|
||||
|
|
|
|||
1124
docs/modules.md
Normal file
1124
docs/modules.md
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,179 +0,0 @@
|
|||
# `aborist.concepts`
|
||||
|
||||
Per-shard `concept_relations` SQLite table — the corpus-derived
|
||||
synonym, rivalry, antonym & category overlay that replaces the
|
||||
hand-curated frozensets that lived in `aborist/qa/concepts.py`
|
||||
through April 2026.
|
||||
|
||||
**Full architecture rationale lives in
|
||||
[`../concept-relations-design.md`](../concept-relations-design.md)**
|
||||
including the 1.6% storage-tax measurement and the three-compactions-
|
||||
considered-and-rejected analysis. This page is the API reference.
|
||||
|
||||
## Sub-modules
|
||||
|
||||
### `concepts.store` — append-only CRUD
|
||||
|
||||
```python
|
||||
from aborist.concepts.store import (
|
||||
add_concept_relation, # idempotent INSERT OR IGNORE
|
||||
concept_relations_for_token, # read all relations for a token
|
||||
purge_by_evidence_kind, # the only DELETE path
|
||||
list_evidence_kinds, # diagnostic
|
||||
RELATION_KINDS, # ('synonym','antonym','rivalry','category')
|
||||
)
|
||||
```
|
||||
|
||||
UNIQUE on `(source_root, relation_kind, token, target, evidence_kind)`
|
||||
makes re-derivation idempotent. `purge_by_evidence_kind` lets an
|
||||
operator revoke a single extractor's output without touching manual
|
||||
or other-extractor rows.
|
||||
|
||||
[`aborist/concepts/store.py`](../../aborist/concepts/store.py)
|
||||
|
||||
### `concepts.query` — cross-shard lookup
|
||||
|
||||
```python
|
||||
from aborist.concepts import (
|
||||
synonym_expand, # query tokens → expanded set
|
||||
rivalry_excluded, # query tokens → tokens to drop from results
|
||||
has_compare_phrasing, # bool — does the query say "vs", "compare", etc.
|
||||
invalidate_cache, # drop the per-process LRU
|
||||
)
|
||||
|
||||
expanded = synonym_expand({"thoughts"}, shards_dir=shards_dir)
|
||||
# → {"thoughts", "telepathy", "neurotechnology", "mind", "cognition", ...}
|
||||
```
|
||||
|
||||
Two synonym indices are loaded:
|
||||
|
||||
- **`manual_index`** — `manual_legacy` + `manual` rows. Curated;
|
||||
always expanded regardless of per-token degree. Captures the
|
||||
brain-tech / AMD-family / Mac / Linux / etc. seed groups.
|
||||
- **`derived_index`** — `link_reciprocity` & other corpus-derived
|
||||
edges. Subject to **`MAX_NEIGHBORS_PER_TOKEN=8`** cap because the
|
||||
Wikipedia link graph carries topic-adjacency noise on generic
|
||||
tokens (person, thoughts, language).
|
||||
|
||||
Overall **`MAX_TOTAL_TOKENS=50`** cap on expanded set bounds the
|
||||
SQL clause count downstream so retrieval stays sub-second.
|
||||
|
||||
A per-process LRU keyed on shard mtime avoids re-loading the index
|
||||
on every query (290k rows across 4 shards loads in ~1.8s cold).
|
||||
|
||||
[`aborist/concepts/query.py`](../../aborist/concepts/query.py)
|
||||
|
||||
### `concepts.extract` — pluggable extractor framework
|
||||
|
||||
```python
|
||||
from aborist.concepts.extract import (
|
||||
EXTRACTORS, # registry: evidence_kind → callable
|
||||
link_reciprocity_synonym, # built-in extractor
|
||||
)
|
||||
|
||||
# Run an extractor against a shard:
|
||||
result = link_reciprocity_synonym(conn, derived_from="backfill@2026-05-01")
|
||||
# → {"reciprocal_pairs": N, "synonyms_inserted": M, "synonyms_skipped": K}
|
||||
```
|
||||
|
||||
Each extractor walks the shard's existing rows (`documents`,
|
||||
`chunks`, `edges`, `derivations`) — **no new crawler needed** — &
|
||||
emits concept relations under a stable `evidence_kind` string that
|
||||
supports targeted purge.
|
||||
|
||||
The built-in `link_reciprocity_synonym` reads the existing `edges`
|
||||
table for reciprocal A↔B link pairs and emits a synonym edge between
|
||||
every (title-token-of-A, title-token-of-B) pair. Works for
|
||||
Wikipedia (See-also bidirectional), HTML site internal links
|
||||
(russell.ballestrini.net pattern), or any document graph with
|
||||
bidirectional links. Title-tokens are filtered to ≥4 chars +
|
||||
stopword-stripped.
|
||||
|
||||
To add a new extractor:
|
||||
|
||||
1. Implement `(conn, *, derived_from) -> dict[str, int]` that calls
|
||||
`add_concept_relation` for each finding.
|
||||
2. Pick a stable `evidence_kind` string.
|
||||
3. Register in `EXTRACTORS`.
|
||||
|
||||
[`aborist/concepts/extract.py`](../../aborist/concepts/extract.py)
|
||||
|
||||
### `concepts.seed` — legacy frozenset migration
|
||||
|
||||
One-shot migration of the 8 hand-curated frozenset groups
|
||||
(AMD-family, Intel-family, HTTP, FTP, Mac, Windows, Linux,
|
||||
brain-tech) to `evidence_kind='manual_legacy'` rows.
|
||||
|
||||
Writes **clique edges** within each group — every `(a, b)` pair —
|
||||
so any member retrieves every other member (preserves the legacy
|
||||
frozenset semantic where lookups didn't depend on which token in
|
||||
the group was the anchor).
|
||||
|
||||
```python
|
||||
from aborist.concepts.seed import seed_legacy_concepts
|
||||
result = seed_legacy_concepts(conn)
|
||||
# → {"synonyms_inserted": N, "rivalries_inserted": M, "skipped": K}
|
||||
```
|
||||
|
||||
Idempotent — re-running adds nothing if all the rows already exist.
|
||||
|
||||
[`aborist/concepts/seed.py`](../../aborist/concepts/seed.py)
|
||||
|
||||
## Data model
|
||||
|
||||
```sql
|
||||
CREATE TABLE concept_relations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
source_root TEXT NOT NULL,
|
||||
relation_kind TEXT NOT NULL CHECK (relation_kind IN
|
||||
('synonym','antonym','rivalry','category')),
|
||||
token TEXT NOT NULL,
|
||||
target TEXT NOT NULL,
|
||||
evidence_kind TEXT NOT NULL,
|
||||
confidence REAL NOT NULL DEFAULT 1.0,
|
||||
derived_at INTEGER NOT NULL,
|
||||
derived_from TEXT,
|
||||
UNIQUE (source_root, relation_kind, token, target, evidence_kind)
|
||||
);
|
||||
```
|
||||
|
||||
**Append-only by construction.** Re-running an extractor adds
|
||||
nothing if every relation already exists. No `UPDATE` path; only
|
||||
`add_concept_relation` (insert) and `purge_by_evidence_kind`
|
||||
(targeted delete).
|
||||
|
||||
**Per-shard storage.** Concept relations live in the shard whose
|
||||
document derived them. Mesh sync moves shards between peers;
|
||||
relations come along.
|
||||
|
||||
**Orthogonal to Merkle.** Writes to `concept_relations` NEVER
|
||||
affect `document_root`, `chunk_root`, or `cache_key`. Backfilling
|
||||
relations is safe across the entire corpus without invalidating
|
||||
any cached answer or breaking any audit chain.
|
||||
|
||||
## Storage cost — measured
|
||||
|
||||
Backfill on 4 wiki shards (3.47M docs, 10.75M resolved edges):
|
||||
|
||||
| Shard | Reciprocal pairs | Synonyms | Storage |
|
||||
|---|---:|---:|---:|
|
||||
| 000.db | 13,562 | 71,288 | 23.52 MB |
|
||||
| 001.db | 14,078 | 73,351 | 24.20 MB |
|
||||
| 002.db | 13,708 | 72,576 | 23.92 MB |
|
||||
| 003.db | 13,800 | 72,633 | 23.94 MB |
|
||||
| **total** | **55,148** | **289,848** | **95.58 MB** |
|
||||
|
||||
**1.6% storage tax** on the 6 GB corpus. Backfill takes ~4 min
|
||||
wall-clock total. Cost is paid once at backfill; every retrieval-
|
||||
time lookup benefits.
|
||||
|
||||
## Diagrams
|
||||
|
||||

|
||||

|
||||
|
||||
## Source
|
||||
|
||||
- [`aborist/concepts/`](../../aborist/concepts/) — package
|
||||
- [`docs/concept-relations-design.md`](../concept-relations-design.md) — full design doc
|
||||
- Whitepaper §13.4.11 — public-facing summary
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
# `aborist.distill`
|
||||
|
||||
Surface → core distillation. Takes a set of surface documents
|
||||
(the original ingest layer) and produces "core" documents — shorter,
|
||||
more focused, Merkle-bound back to their contributing surface
|
||||
chunks via per-chunk inclusion proofs.
|
||||
|
||||
The "trees and forests of cross-linked information" tagline aborist
|
||||
takes its name from comes from this layer: planet-toward-center
|
||||
compression where each layer of cores derives from the previous,
|
||||
recursively.
|
||||
|
||||
## Layered design
|
||||
|
||||
```
|
||||
distill/
|
||||
├── base.py Distiller ABC + DistillationResult dataclass
|
||||
├── first_sentence.py no-ML stub: take the first sentence of each doc
|
||||
├── tfidf.py pure-Python TF-IDF top-keyword extraction
|
||||
└── runner.py batched distillation + per-contrib-chunk proofs
|
||||
```
|
||||
|
||||
## `Distiller` ABC
|
||||
|
||||
```python
|
||||
from aborist.distill import Distiller, DistillationResult
|
||||
|
||||
class Distiller(ABC):
|
||||
@abstractmethod
|
||||
def distill(self, docs: list[Document]) -> DistillationResult: ...
|
||||
```
|
||||
|
||||
Each `DistillationResult` carries the new core's content +
|
||||
references to every contributing surface chunk by `(document_root,
|
||||
chunk_root)`. The runner writes one `derivations` row per core,
|
||||
with `proof_blob = json.dumps(per_chunk_inclusion_proofs)`.
|
||||
|
||||
## Built-in distillers
|
||||
|
||||
### `FirstSentenceDistiller` (no-ML stub)
|
||||
|
||||
Take the first sentence of each input doc, concatenate. Used as a
|
||||
sanity-check for the pipeline + a baseline for measuring the
|
||||
benefit of richer distillers.
|
||||
|
||||
### `TfidfKeywordDistiller`
|
||||
|
||||
Pure-Python TF-IDF. Computes term frequencies across the input
|
||||
doc set + inverse document frequencies; emits the top-K terms per
|
||||
doc as the core's content. The "permacomputer" neologism case
|
||||
(every Grok conversation has the word, no Wikipedia article does)
|
||||
is the canonical TF-IDF win — surfaces the topic that title-search
|
||||
can't catch.
|
||||
|
||||
## Why distill
|
||||
|
||||
Three use cases:
|
||||
|
||||
1. **Retrieval signal.** Cores feed the third accept path in
|
||||
`_filter_by_title_relevance` — `core_match_roots` (TF-IDF top-
|
||||
keywords contain a query token). Closes the gap for neologisms
|
||||
that never make Wikipedia titles but ARE distinctive.
|
||||
|
||||
2. **Hot/cold tier discipline.** `evict_to_cold` only touches
|
||||
`kind='surface'` — cores never evict. Distilling surface to
|
||||
cores then evicting surfaces gives a "long tail keeps small
|
||||
cache" pattern with full provenance preserved.
|
||||
|
||||
3. **Recursive abstraction.** Cores can themselves be distilled
|
||||
into shorter cores. Each generation Merkle-binds back to the
|
||||
previous via `derivations.proof_blob` — the audit chain stays
|
||||
intact across an arbitrary distillation depth.
|
||||
|
||||
## Source
|
||||
|
||||
[`aborist/distill/`](../../aborist/distill/)
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
# `aborist.document`
|
||||
|
||||
The data structures every source produces and every storage layer
|
||||
consumes. Three core types: `Document`, `Edge`, `Chunker`.
|
||||
|
||||
## `Document`
|
||||
|
||||
A single ingest unit: a Wikipedia article, an HTML page, a Grok
|
||||
conversation, a git commit message, etc. Carries both the raw
|
||||
content AND the version tags that determine its identity:
|
||||
|
||||
```python
|
||||
@dataclass(frozen=True)
|
||||
class Document:
|
||||
document_uri: str # canonical URI (or stable surrogate for non-URI sources)
|
||||
raw_content: str # source-of-truth bytes pre-canonicalization
|
||||
kind: str # 'surface' / 'core' / 'visual' / etc.
|
||||
chunking_version: str # e.g. 'tok-512-v1' — pinned by the Chunker
|
||||
canonicalization_version: str # e.g. 'norm-v1' — pinned by canonicalize()
|
||||
schema_version: str # e.g. 'v9.8.0' — store schema generation
|
||||
title: str | None = None
|
||||
edges: list[Edge] = () # outbound link graph
|
||||
metadata: dict = ... # source-specific opaque payload
|
||||
```
|
||||
|
||||
`document_root` is computed at ingest time as the Merkle root over
|
||||
the canonicalized chunks. Two peers ingesting the same source +
|
||||
running the same `chunking_version` + `canonicalization_version`
|
||||
get bit-identical `document_root`s — the v9.8 admissibility property.
|
||||
|
||||
## `Edge`
|
||||
|
||||
One outbound link. `aborist/sources/wikipedia.py` emits one Edge
|
||||
per `[[wikilink]]`; `aborist/sources/html_page.py` emits one per
|
||||
`<a href>`. The link graph IS the corpus topology — `concepts/extract.py`
|
||||
later reads `edges` rows to derive synonym relations from
|
||||
reciprocal links (no separate crawler needed).
|
||||
|
||||
```python
|
||||
@dataclass(frozen=True)
|
||||
class Edge:
|
||||
src_root: str # source document_root
|
||||
dst_uri: str # always present
|
||||
dst_root: str # '' (unresolved) until the dst doc is also ingested
|
||||
edge_type: str # 'wikilink' / 'href' / 'citation' / 'derived_from' / ...
|
||||
anchor: str # chunk index or fragment, '' if N/A
|
||||
```
|
||||
|
||||
## `Chunker`
|
||||
|
||||
ABC with one method `chunk(text: str) -> list[str]`. Default impl
|
||||
is `TokenChunker` (`name='tok-512-v1'`) — splits on token-rough
|
||||
windows so the resulting chunks are predictable for downstream FTS5
|
||||
indexing & for the LLM context budget.
|
||||
|
||||
**Changing the chunker bumps `chunking_version` AND stales every
|
||||
prior cache record** (chunking is one of the 8 cache_key dimensions).
|
||||
Don't redefine `tok-512-v1`; add a new chunker as a new `name`
|
||||
instead.
|
||||
|
||||
## Diagrams
|
||||
|
||||

|
||||

|
||||
|
||||
## Source
|
||||
|
||||
[`aborist/document.py`](../../aborist/document.py)
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
# `aborist.evict`
|
||||
|
||||
Hot ↔ cold tier transitions. The corpus is large (3.47M Wikipedia
|
||||
docs); not every chunk fits in working memory. `evict.py` is the
|
||||
mechanism that moves rarely-touched chunks to a cold tier (still
|
||||
indexed, just stored separately) and rehydrates them on demand
|
||||
from the original source.
|
||||
|
||||
## API surface
|
||||
|
||||
```python
|
||||
from aborist.evict import evict_to_cold, rehydrate
|
||||
|
||||
# Move chunks unused for >threshold days to cold tier
|
||||
evict_to_cold(conn, max_age_days=90, max_evictions=10000)
|
||||
|
||||
# Pull a cold chunk back to hot from its original source
|
||||
rehydrate(conn, document_root="abc123...")
|
||||
```
|
||||
|
||||
## Invariant: cores never evict
|
||||
|
||||
`evict_to_cold` filters `WHERE kind='surface'`. Cores are always
|
||||
hot — they're the long-tail-friendly compression layer that justifies
|
||||
evicting their underlying surfaces. Evicting cores would defeat the
|
||||
purpose.
|
||||
|
||||
## v9.8 falsification on drift
|
||||
|
||||
When `rehydrate()` re-fetches a document and the recomputed
|
||||
`document_root` differs from the stored one, the source has
|
||||
changed since ingest (Wikipedia article was edited, HTML page was
|
||||
republished, etc.). The cache record's `falsification_state` flips
|
||||
from `live` to `stale` — every providence record keyed on that
|
||||
`source_root` is no longer admissible to lookups.
|
||||
|
||||
This is the **drift-detection-as-falsification** discipline: cache
|
||||
hits don't blindly trust historical answers; they trust answers
|
||||
that the SAME source still grounds.
|
||||
|
||||
## Tier values
|
||||
|
||||
`chunks.tier ∈ {'hot', 'cold'}`. Hot chunks live in `chunks.content`;
|
||||
cold chunks live with NULL `content` and a `cold_uri` pointing at
|
||||
the source. The QA pipeline's chunk-fetch path checks `tier`; on
|
||||
'cold', it triggers `rehydrate` before continuing.
|
||||
|
||||
## Source
|
||||
|
||||
[`aborist/evict.py`](../../aborist/evict.py)
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
# Aborist module reference
|
||||
|
||||
One page per top-level package + one diagram per major data flow.
|
||||
This index is the entry point — every module page links back here
|
||||
and to the relevant diagrams.
|
||||
|
||||
## Diagrams
|
||||
|
||||
| Diagram | What it shows | File |
|
||||
|---|---|---|
|
||||
| **Module graph** | Top-level packages & how they import each other | [`aborist-modules.svg`](../diagrams/aborist-modules.svg) ([dot](../diagrams/aborist-modules.dot)) |
|
||||
| **Query pipeline** | Question → cache → retrieval → LLM → verify → render | [`query-pipeline.svg`](../diagrams/query-pipeline.svg) ([dot](../diagrams/query-pipeline.dot)) |
|
||||
| **Ingest pipeline** | Source document → Merkle-committed shard | [`ingest-pipeline.svg`](../diagrams/ingest-pipeline.svg) ([dot](../diagrams/ingest-pipeline.dot)) |
|
||||
| **Verifier ladder** | (audit_mode, violations) → display rung | [`verifier-ladder.svg`](../diagrams/verifier-ladder.svg) ([dot](../diagrams/verifier-ladder.dot)) |
|
||||
| **Mesh data flow** | Federation: roster, gossip, AEAD envelope | [`mesh-data-flow.svg`](../diagrams/mesh-data-flow.svg) |
|
||||
| **Mesh epoch lifecycle** | Epoch advance via add/kick/rotate | [`mesh-epoch-lifecycle.svg`](../diagrams/mesh-epoch-lifecycle.svg) |
|
||||
| **Mesh identity stack** | Ed25519 sign + X25519 DH key derivation | [`mesh-identity-stack.svg`](../diagrams/mesh-identity-stack.svg) |
|
||||
| **Mesh secret envelope** | AEAD-wrapped epoch secret per peer | [`mesh-secret-envelope.svg`](../diagrams/mesh-secret-envelope.svg) |
|
||||
| **Mesh group decisions** | Membership change voting & quorum | [`mesh-group-decisions.svg`](../diagrams/mesh-group-decisions.svg) |
|
||||
|
||||
Render diagrams locally:
|
||||
|
||||
```
|
||||
make docs # runs `dot -Tsvg` and `-Tpng` on every docs/diagrams/*.dot
|
||||
```
|
||||
|
||||
## Substrate (no SQL, pure data structures)
|
||||
|
||||
| Module | One-line role | Doc |
|
||||
|---|---|---|
|
||||
| [`merkle.py`](../../aborist/merkle.py) | Merkle tree + proof — Python port of `proxy.unturf.com/pkg/verified/merkle.go` | [merkle.md](./merkle.md) |
|
||||
| [`document.py`](../../aborist/document.py) | `Document`, `Edge`, `Chunker` (default `tok-512-v1`) | [document.md](./document.md) |
|
||||
| [`wikitext.py`](../../aborist/wikitext.py) | `to_base()` — wikitext → plain prose, BASE_VERSION-pinned | [wikitext.md](./wikitext.md) |
|
||||
|
||||
## Storage
|
||||
|
||||
| Module | One-line role | Doc |
|
||||
|---|---|---|
|
||||
| [`store.py`](../../aborist/store.py) | v9.8 SQLite schema + audit chain helpers | [store.md](./store.md) |
|
||||
| [`ingest.py`](../../aborist/ingest.py) | normalize → chunk → merkle → upsert (bulk-batched) | [ingest.md](./ingest.md) |
|
||||
| [`evict.py`](../../aborist/evict.py) | hot ↔ cold tier transitions; rehydrate via source | [evict.md](./evict.md) |
|
||||
|
||||
## Sources (corpus producers)
|
||||
|
||||
| Module | One-line role | Doc |
|
||||
|---|---|---|
|
||||
| [`sources/wikipedia.py`](../../aborist/sources/wikipedia.py) | Wikipedia 2003 cur + old SQL dumps (bz2-streamed) | [sources.md](./sources.md) |
|
||||
| [`sources/wikipedia_xml.py`](../../aborist/sources/wikipedia_xml.py) | Phase IV XML dumps (iterparse, page + history) | [sources.md](./sources.md) |
|
||||
| [`sources/html_page.py`](../../aborist/sources/html_page.py) | URL list + selectolax + httpx (robots-aware) | [sources.md](./sources.md) |
|
||||
| [`sources/crawler/`](../../aborist/sources/crawler/) | verbatim AsyncWebFetcher lift + ingest bridge | [sources.md](./sources.md) |
|
||||
| [`sources/grok.py`](../../aborist/sources/grok.py) | xAI data export (conversations + media prompts) | [sources.md](./sources.md) |
|
||||
| [`sources/vcs.py`](../../aborist/sources/vcs.py) | git + Mercurial repos (HEAD walk, supersedes chain) | [sources.md](./sources.md) |
|
||||
|
||||
## Search & retrieval
|
||||
|
||||
| Module | One-line role | Doc |
|
||||
|---|---|---|
|
||||
| [`search/`](../../aborist/search/) | FTS5 backend + `SearchBackend` ABC + `AuditMode` enum | [search.md](./search.md) |
|
||||
| [`concepts/`](../../aborist/concepts/) | Per-shard `concept_relations` synonym/rivalry overlay | [concepts.md](./concepts.md) |
|
||||
|
||||
## Q&A pipeline
|
||||
|
||||
| Module | One-line role | Doc |
|
||||
|---|---|---|
|
||||
| [`qa/keys.py`](../../aborist/qa/keys.py) | 8-dim cache_key + `question_hash` | [qa.md](./qa.md) |
|
||||
| [`qa/client.py`](../../aborist/qa/client.py) | `ChatClient` + `StubClient` + `OpenAICompatibleClient` | [qa.md](./qa.md) |
|
||||
| [`qa/runner.py`](../../aborist/qa/runner.py) | `ask()`: single-doc Q&A + cache + verify | [qa.md](./qa.md) |
|
||||
| [`qa/query.py`](../../aborist/qa/query.py) | `query()`: multi-source RAG + concept overlay | [qa.md](./qa.md) |
|
||||
| [`qa/verify.py`](../../aborist/qa/verify.py) | quote/span/entity/paraphrase + claim_lattice (7 hard checks) | [qa.md](./qa.md) |
|
||||
| [`qa/warrant.py`](../../aborist/qa/warrant.py) | 5 anchor classes (proper-noun · date · count · entity-list · cause) | [qa.md](./qa.md) |
|
||||
| [`qa/evidence.py`](../../aborist/qa/evidence.py) | `EvidenceObject` + spotlight excerpt (density rank) | [qa.md](./qa.md) |
|
||||
| [`qa/parse_claims.py`](../../aborist/qa/parse_claims.py) | pointer-line parser (`claim. [E1,E2]`) | [qa.md](./qa.md) |
|
||||
| [`qa/dag.py`](../../aborist/qa/dag.py) | per-run Merkle-DAG (7-stage quote / 9-stage CTI) | [qa.md](./qa.md) |
|
||||
| [`qa/inspect.py`](../../aborist/qa/inspect.py) | sidecar diagnostic (read-only span classifier) | [qa.md](./qa.md) |
|
||||
|
||||
## Distillation
|
||||
|
||||
| Module | One-line role | Doc |
|
||||
|---|---|---|
|
||||
| [`distill/`](../../aborist/distill/) | `Distiller` ABC + `first_sentence` + `tfidf` + runner | [distill.md](./distill.md) |
|
||||
|
||||
## Federation (off by default)
|
||||
|
||||
| Module | One-line role | Doc |
|
||||
|---|---|---|
|
||||
| [`mesh/`](../../aborist/mesh/) | identity (Ed25519/X25519), per-epoch roster, AEAD envelope, gossip wire | [../mesh.md](../mesh.md), [../mesh-deploy.md](../mesh-deploy.md) |
|
||||
|
||||
## Entry point
|
||||
|
||||
| Module | One-line role | Doc |
|
||||
|---|---|---|
|
||||
| [`cli.py`](../../aborist/cli.py) | `argparse` entrypoint — every `make` target dispatches here | run `aborist --help` or any `make help` target |
|
||||
|
||||
## Tickets, design docs, journals
|
||||
|
||||
See [`../TICKETS.md`](../TICKETS.md) for the ticket index and the
|
||||
list of design-reference docs that aren't tickets.
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
# `aborist.ingest`
|
||||
|
||||
The bulk-batched pipeline that turns documents from a source into
|
||||
Merkle-committed shard storage. Source-agnostic: anything that
|
||||
implements `Source.iter_documents()` flows through here.
|
||||
|
||||

|
||||
|
||||
## Public API
|
||||
|
||||
```python
|
||||
from aborist.ingest import ingest_source
|
||||
from aborist.sources.wikipedia import WikipediaSqlDump
|
||||
|
||||
source = WikipediaSqlDump("/path/to/cur.sql.bz2")
|
||||
ingest_source(
|
||||
source,
|
||||
db_path=Path("~/.aborist/shards/000.db"),
|
||||
batch_size=200, # docs per transaction
|
||||
progress_every=1000,
|
||||
)
|
||||
```
|
||||
|
||||
## What happens per document
|
||||
|
||||
1. **Canonicalize** — `canonicalize(text)`: NFC + ws-collapse +
|
||||
strip ends. Pinned by `canonicalization_version='norm-v1'`.
|
||||
2. **Chunk** — `Chunker.chunk(canonical_text)` → list of token-
|
||||
bounded substrings. Default `tok-512-v1` chunker.
|
||||
3. **Hash leaves** — `sha256(0x00 || canonical_chunk_bytes)` per
|
||||
chunk.
|
||||
4. **Merkle tree** — `MerkleTree.build(leaves).root` →
|
||||
`document_root`. Two peers running the same chunker on the same
|
||||
canonicalized content compute bit-identical roots.
|
||||
5. **Upsert** — `documents` row keyed on `document_root` (idempotent
|
||||
re-ingest), `chunks` rows with leaf hashes, `merkle_nodes` for
|
||||
proof reconstruction, `edges` per outbound link.
|
||||
6. **FTS5** — `chunks_fts` insert with rowid = `chunks.chunk_id`
|
||||
so the search-time JOIN lines up.
|
||||
7. **Audit event** — one row per ingest batch in `audit_events`,
|
||||
chained on `prev_event_hash`.
|
||||
|
||||
## Batching discipline
|
||||
|
||||
Default `batch_size=200`: balances Python GIL overhead vs SQLite
|
||||
transaction commit cost. Lower it (e.g. 50) only to bound peak
|
||||
memory on a low-RAM host. Higher (e.g. 1000) for ETL throughput on
|
||||
SSD storage when memory isn't tight.
|
||||
|
||||
`progress_every` prints a stderr line every N docs so long ingests
|
||||
are observable. Use `PYTHONUNBUFFERED=1` for tail-able output.
|
||||
|
||||
## Resumability
|
||||
|
||||
Idempotent re-ingest: same content + same chunker + same canonicalize
|
||||
= same `document_root` = no-op insert. So a crashed ingest can be
|
||||
restarted from the source's beginning without duplicating rows.
|
||||
|
||||
Different content at the same URI gets a new `document_root` AND a
|
||||
`supersedes` edge linking new → old (lossless history).
|
||||
|
||||
## Source
|
||||
|
||||
[`aborist/ingest.py`](../../aborist/ingest.py)
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
# `aborist.merkle`
|
||||
|
||||
Pure Merkle tree + proof primitives. Python port of
|
||||
`proxy.unturf.com/pkg/verified/merkle.go` — convention-identical.
|
||||
Used everywhere a content-addressable handle is needed: per-chunk
|
||||
leaves, document_root, evidence_map_root, run_dag_root, snapshots.
|
||||
|
||||
## Conventions (do not silently change)
|
||||
|
||||
These match the Go reference & are load-bearing for cross-language
|
||||
verification (Go peer ↔ Python peer compute bit-identical roots):
|
||||
|
||||
- **Leaf hash:** `sha256(0x00 || canonical_chunk_bytes)`. The `0x00`
|
||||
prefix domain-separates leaves from internal nodes.
|
||||
- **Internal hash:** `sha256(0x03 || left || right)`. The `0x03`
|
||||
prefix is the **non-commutative** combine — `H(L,R) ≠ H(R,L)`.
|
||||
Order matters.
|
||||
- **Odd-element rule:** when a level has an odd count, the last
|
||||
leaf is **self-duplicated** before pairing. NOT zero-padded.
|
||||
- **Proof path:** each step carries an explicit `is_left: bool`
|
||||
alongside the sibling hash so a verifier knows which side to put
|
||||
the sibling on. Never sort siblings lexically — the order tells
|
||||
the verifier the tree topology.
|
||||
|
||||
## API surface
|
||||
|
||||
```python
|
||||
from aborist.merkle import MerkleTree, MerkleProof
|
||||
|
||||
tree = MerkleTree.build([b"chunk_0_bytes", b"chunk_1_bytes", ...])
|
||||
tree.root # bytes(32) — sha256 of the whole tree
|
||||
tree.leaves # list[bytes(32)] — leaf hashes in input order
|
||||
|
||||
proof = tree.proof_for(leaf_index=2)
|
||||
proof.siblings # list[(sibling_hash, is_left)]
|
||||
proof.verify(leaf_hash=tree.leaves[2], root=tree.root) # bool
|
||||
```
|
||||
|
||||
## When to read the source
|
||||
|
||||
- Adding a new content-addressable artifact (cores, evidence maps,
|
||||
snapshots, run-DAGs all touch this).
|
||||
- Cross-language verification debugging (Go peer says one root,
|
||||
Python peer says another — the difference is always in canonical
|
||||
encoding, ordering, or one of the three prefix bytes above).
|
||||
- Performance work — the Python build is ~3× slower than the Go
|
||||
reference; if it ever shows up in profiling, that's the file.
|
||||
|
||||
## Diagrams
|
||||
|
||||
The module graph shows what depends on `merkle.py` (a lot — it's
|
||||
substrate):
|
||||
|
||||

|
||||
|
||||
The ingest pipeline shows where leaf & root hashes get computed:
|
||||
|
||||

|
||||
|
||||
## Source
|
||||
|
||||
[`aborist/merkle.py`](../../aborist/merkle.py) ·
|
||||
Reference: [`proxy.unturf.com/pkg/verified/merkle.go`](https://git.unturf.com/engineering/unturf/proxy.unturf.com/-/blob/main/pkg/verified/merkle.go)
|
||||
|
|
@ -1,185 +0,0 @@
|
|||
# `aborist.qa`
|
||||
|
||||
The Q&A pipeline. Question → cache → retrieval → LLM → verify →
|
||||
render → cache write. Lives in 9 sub-modules; this page is the
|
||||
map.
|
||||
|
||||

|
||||
|
||||
## Sub-modules
|
||||
|
||||
### `qa.client` — LLM transport
|
||||
|
||||
`ChatClient` ABC with three concrete implementations:
|
||||
|
||||
- `StubClient` — deterministic test fixture; returns canned
|
||||
responses keyed on the input. Used in unit tests to avoid network.
|
||||
- `OpenAICompatibleClient` — talks to any OpenAI-shape `/v1/chat/completions`
|
||||
endpoint (Hermes-3 on vLLM by default). Includes HTTP retry layer
|
||||
(3× exponential backoff on 5xx).
|
||||
- (Future) `AnthropicClient` — Claude API direct.
|
||||
|
||||
[`aborist/qa/client.py`](../../aborist/qa/client.py)
|
||||
|
||||
### `qa.keys` — the 8-dim cache_key
|
||||
|
||||
```
|
||||
cache_key = sha256(
|
||||
source_root | question_hash | model_profile_hash |
|
||||
conversation_hash | governance_policy_hash |
|
||||
schema_version | canonicalization_version | chunking_version
|
||||
)
|
||||
```
|
||||
|
||||
Two question-hash modes (`strict` vs `equivalence_class`) live here.
|
||||
Bumping any of these eight dimensions invalidates prior records on
|
||||
lookup. The `verifier_policy_hash` (v9.9 9th dim) is also
|
||||
implemented here.
|
||||
|
||||
[`aborist/qa/keys.py`](../../aborist/qa/keys.py)
|
||||
|
||||
### `qa.runner` — `ask()` for single-doc Q&A
|
||||
|
||||
The simplest entry point. Take one document, ask one question, get
|
||||
back an answer + audit_mode + cache record. Used by the CLI for
|
||||
focused queries against one URI.
|
||||
|
||||
[`aborist/qa/runner.py`](../../aborist/qa/runner.py)
|
||||
|
||||
### `qa.query` — `query()` for multi-source RAG
|
||||
|
||||
The main retrieval entry point. Walks shards, runs FTS5 BM25 with
|
||||
AND→OR fallback (with synonym-pool injection in OR mode), filters
|
||||
by title relevance with 4 accept paths, reranks by body coverage +
|
||||
title boost + source role + title purity, assembles a 60 KB context
|
||||
budget, calls the LLM, runs the verifier, persists to
|
||||
`providence_cache`.
|
||||
|
||||
[`aborist/qa/query.py`](../../aborist/qa/query.py)
|
||||
|
||||
### `qa.verify` — the layered verifier
|
||||
|
||||
Five strategies run in sequence; first to find evidence classifies:
|
||||
1. `quote` — `"..."`-wrapped claims tested verbatim
|
||||
2. `span` — bullet/sentence units substring-tested
|
||||
3. `entity` — multi-word proper nouns with proximity gating
|
||||
4. `paraphrase` — token coverage on prose-shaped spans (≥85%)
|
||||
5. `claim_lattice` — pointer-line `[E1,E2]` or JSON; runs **seven
|
||||
deterministic hard checks**:
|
||||
1. parser succeeded
|
||||
2. evidence_id resolves
|
||||
3. source_role allowed
|
||||
4. claim text non-empty
|
||||
5. citation coverage threshold
|
||||
6. pointer count cap (trim-and-verify)
|
||||
7. anchor-class warrant (see `qa.warrant`)
|
||||
|
||||
The classifier output rolls up into the v9.8 trichotomy
|
||||
`audit_mode ∈ {STRICT, HYBRID, UNGROUNDED}`. Display layer (in
|
||||
`cli.py`) maps `(audit_mode, violations) → four-rung ladder`.
|
||||
|
||||

|
||||
|
||||
[`aborist/qa/verify.py`](../../aborist/qa/verify.py)
|
||||
|
||||
### `qa.warrant` — anchor-class warrant
|
||||
|
||||
Five lexical anchor classes the verifier composes:
|
||||
|
||||
- **Proper-noun** — relation-question shape; at least one
|
||||
Title-Case anchor must appear in some cited span
|
||||
- **Date** — claim has a 4-digit year + month name; ALL components
|
||||
required in some cited span
|
||||
- **Entity-list** — entity-list-shape question; ≥1 named entity
|
||||
must anchor (demote-don't-reject)
|
||||
- **Count** — count-shape question; count token must appear in
|
||||
word OR digit form (digit↔word equivalence)
|
||||
- **Cause** — why-shape question; ≥1 cause anchor (proper noun OR
|
||||
≥5-char common noun outside stopword pool)
|
||||
|
||||
The warrant layer earns proof-path entry by staying **lexical** —
|
||||
no NLI, no embeddings. Substring tests over already-canonicalized
|
||||
spans. See `docs/concept-relations-design.md` (sibling section)
|
||||
for the relationship to retrieval-time synonym expansion.
|
||||
|
||||
[`aborist/qa/warrant.py`](../../aborist/qa/warrant.py)
|
||||
|
||||
### `qa.evidence` — EvidenceObject + spotlight
|
||||
|
||||
Builds the runtime evidence map for claim-lattice modes. Each
|
||||
chunk becomes one `EvidenceObject` carrying TWO ids:
|
||||
|
||||
- `pointer_id` — short prompt-facing tag (`E1`, `E2`, …)
|
||||
- `evidence_id` — content-addressed `E########` (sha256-derived)
|
||||
|
||||
The model sees only `pointer_id`s in the prompt; the runtime maps
|
||||
to `evidence_id` for the cache & run-DAG (run-stable identity).
|
||||
|
||||
The spotlight excerpt picks the load-bearing slice via **density
|
||||
rank** — find ALL match positions for ALL claim content tokens,
|
||||
pick the position with maximum distinct-token cluster within
|
||||
±half-window. Replaces the older first-match-of-longest-token
|
||||
approach which lost the load-bearing slice on noisy chunks.
|
||||
|
||||
[`aborist/qa/evidence.py`](../../aborist/qa/evidence.py)
|
||||
|
||||
### `qa.parse_claims` — pointer-line parser
|
||||
|
||||
Walks lines of the model output, pulls every `[E\d+]` and
|
||||
`[E\d+,E\d+,…]` bracket payload, returns
|
||||
`(claim_text, pointer_ids[])` per line. Lines without a tag get
|
||||
`parse_status='NO_EVIDENCE_POINTER'` & count toward the denominator
|
||||
so unsourced prose can't smuggle past the verifier.
|
||||
|
||||
[`aborist/qa/parse_claims.py`](../../aborist/qa/parse_claims.py)
|
||||
|
||||
### `qa.dag` — per-run Merkle DAG
|
||||
|
||||
Commits each provenance step independently as a stage hash. Two
|
||||
shapes:
|
||||
|
||||
- **7-stage (quote mode):** question / retrieval / context /
|
||||
prompt / answer / verify / final_label
|
||||
- **9-stage (claim-lattice / CTI):** question / retrieval /
|
||||
evidence_map / prompt / raw_answer / parsed_claim_lattice /
|
||||
verify / render / final_label
|
||||
|
||||
The `run_dag_root` is persisted alongside every providence record;
|
||||
`run_dag_blob` carries the full `{root, nodes}` JSON so an auditor
|
||||
can recompute & verify any step.
|
||||
|
||||
[`aborist/qa/dag.py`](../../aborist/qa/dag.py)
|
||||
|
||||
### `qa.inspect` — read-only sidecar
|
||||
|
||||
Pulls source chunks for a given cache_key & classifies each
|
||||
unverified span: `verbatim_in_base` / `verbatim_in_raw_only` /
|
||||
`trailing_artifact` / `paraphrase` / `partial_paraphrase` /
|
||||
`no_overlap`. Also includes the deflection-detection sidecar
|
||||
(subject-anchor heuristic for adversarial-premise topic shift).
|
||||
|
||||
**Sidecars never write to `providence_cache` or `audit_events`** —
|
||||
they're diagnostic only. That invariant is what keeps `audit_mode`
|
||||
a binary classification rather than a soft score.
|
||||
|
||||
[`aborist/qa/inspect.py`](../../aborist/qa/inspect.py)
|
||||
|
||||
### `qa.concepts` — backwards-compat shim
|
||||
|
||||
Delegates to `aborist.concepts` (the corpus-derived synonym/rivalry
|
||||
layer). Pre-2026-05-01 the data lived as hand-curated frozensets in
|
||||
this file; now it's a per-shard SQLite table. The shim preserves
|
||||
the legacy public API (`synonym_expand`, `rivalry_excluded`,
|
||||
`has_compare_phrasing`) so call sites in `qa/query.py` didn't have
|
||||
to change.
|
||||
|
||||
[`aborist/qa/concepts.py`](../../aborist/qa/concepts.py) →
|
||||
[`aborist/concepts/`](../../aborist/concepts/)
|
||||
|
||||
## Source papers
|
||||
|
||||
- Whitepaper §13.8 covers the layered verifier in depth
|
||||
- Whitepaper §13.9 covers claim-lattice / CTI mode
|
||||
- `docs/cti-architecture.md` is the architecture reference
|
||||
- `docs/seven-point-program.md` enumerates the seven hard checks
|
||||
- `docs/concept-relations-design.md` covers the synonym layer
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
# `aborist.search`
|
||||
|
||||
The retrieval primitive. Today's only backend is FTS5 over the
|
||||
chunks table; the `SearchBackend` ABC is in place so additional
|
||||
backends (BM25 over titles, embedding-based vector search) can be
|
||||
added without touching the rest of the QA pipeline.
|
||||
|
||||
## `SearchBackend` ABC
|
||||
|
||||
```python
|
||||
from aborist.search import SearchBackend, AuditMode, Hit
|
||||
|
||||
class SearchBackend(ABC):
|
||||
@abstractmethod
|
||||
def search(self, query: str, limit: int = 20) -> list[Hit]: ...
|
||||
```
|
||||
|
||||
Each `Hit` carries `(document_root, document_uri, chunk_idx,
|
||||
snippet, score, audit_mode, title)`. `audit_mode` is the sticky
|
||||
provenance label that tracks how the chunk made it into the index;
|
||||
FTS5 backend always sets `UNGROUNDED` (search itself doesn't verify
|
||||
anything — that's the QA pipeline's job).
|
||||
|
||||
## `FTS5Backend`
|
||||
|
||||
Wraps the contentless `chunks_fts` virtual table. Two-mode query:
|
||||
|
||||
- **AND-mode (strict, primary):** every content token must appear
|
||||
in the doc. Keeps unrelated docs out of the context window.
|
||||
- **OR-mode (fallback):** when AND returns 0 hits, fall back to OR
|
||||
but **capped to top-5 longest tokens** (proxy for rarity). Long
|
||||
topical synonyms fed via `extra_or_tokens` join the pool —
|
||||
`neurotechnology` (15 chars) outranks `thoughts` (8) by length
|
||||
and surfaces brain-tech titles for vocabulary-mismatch queries.
|
||||
|
||||
```python
|
||||
from aborist.search import FTS5Backend
|
||||
backend = FTS5Backend(conn)
|
||||
|
||||
# Plain search
|
||||
hits = backend.search("permacomputer", limit=32)
|
||||
|
||||
# Search with synonym pool injection (used by qa.query._search_corpus)
|
||||
hits = backend.search(
|
||||
long_query,
|
||||
limit=32,
|
||||
extra_or_tokens=synonym_expand(qtokens, shards_dir=shards_dir),
|
||||
)
|
||||
```
|
||||
|
||||
## Stopword & stopword-cap discipline
|
||||
|
||||
`_FTS5_STOPWORDS` filters question words (`what`, `tell`, `please`)
|
||||
+ generic connectors (`one`, `some`, `another`, `without`, `soon`,
|
||||
`currently`) before AND/OR construction. Two principles:
|
||||
|
||||
- **Stay in sync with `_TITLE_STOPWORDS`** in `qa/query.py`. A token
|
||||
filtered at retrieval time but kept at title-relevance check (or
|
||||
vice versa) creates ranking incoherence.
|
||||
- **`_OR_FALLBACK_MAX_TOKENS=5`** caps the OR-mode pool. Without
|
||||
this, a 19-token OR clause matches millions of docs and forces
|
||||
BM25 to rank them all — 13s/shard observed pre-cap. Now 0.25s/shard.
|
||||
|
||||
## Snippet building
|
||||
|
||||
FTS5 contentless mode means SQLite's built-in `snippet()` and
|
||||
`highlight()` return empty. Aborist builds snippets in Python by
|
||||
joining `chunks_fts.rowid = chunks.chunk_id`, decompressing the
|
||||
chunk content, and locating query tokens locally
|
||||
(`_build_snippet`).
|
||||
|
||||
## Source
|
||||
|
||||
[`aborist/search/fts5.py`](../../aborist/search/fts5.py) ·
|
||||
[`aborist/search/__init__.py`](../../aborist/search/__init__.py)
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
# `aborist.sources`
|
||||
|
||||
Corpus producers. Each is a `Source` ABC implementation that yields
|
||||
`Document` instances; the standard `ingest.ingest_source(source, db)`
|
||||
pipeline takes them from there.
|
||||
|
||||
The `Source` ABC lives in [`aborist/source.py`](../../aborist/source.py):
|
||||
|
||||
```python
|
||||
class Source(ABC):
|
||||
@abstractmethod
|
||||
def iter_documents(self) -> Iterator[Document]: ...
|
||||
```
|
||||
|
||||
## Built-in sources
|
||||
|
||||
### `wikipedia.py` — Phase III SQL dumps (the canonical bootstrap)
|
||||
|
||||
Streams the Wikipedia 2003 `cur` (current revisions) and `old`
|
||||
(revision history) SQL dumps. Hand-rolled escape-aware parser
|
||||
(no `sqlite3` import — the dump is MySQL syntax). 4× speedup vs
|
||||
char-by-char loops via `str.find` + slicing. cProfile any change.
|
||||
|
||||
Default Wikipedia 2003-05-16 dump source:
|
||||
`https://dumps.wikimedia.org/archive/2003/2003-05-16/en/`.
|
||||
robots.txt returned 404 → no rules.
|
||||
|
||||
[`aborist/sources/wikipedia.py`](../../aborist/sources/wikipedia.py)
|
||||
|
||||
### `wikipedia_xml.py` — Phase IV XML dumps
|
||||
|
||||
Modern Wikipedia dump format (`enwiki-YYYYMMDD-pages-articles.xml.bz2`,
|
||||
`enwiki-YYYYMMDD-pages-meta-history*.xml.bz2`). Uses `xml.etree.ElementTree.iterparse`
|
||||
to stream-parse without loading the whole tree.
|
||||
|
||||
[`aborist/sources/wikipedia_xml.py`](../../aborist/sources/wikipedia_xml.py)
|
||||
|
||||
### `html_page.py` — single-URL or URL-list HTML ingest
|
||||
|
||||
Robots-aware (`urllib.robotparser`). Uses `selectolax` for fast
|
||||
HTML parsing (CSS-selector based; ~10× faster than `lxml`). Pulls
|
||||
the main body text + every `<a href>` as an `Edge` row.
|
||||
|
||||
The `edges` rows are what the corpus-derived synonym extractor
|
||||
later reads — no separate crawler needed for site-internal link
|
||||
graphs.
|
||||
|
||||
Optional dep: `pip install '.[html]'` for `selectolax` + `httpx`.
|
||||
|
||||
[`aborist/sources/html_page.py`](../../aborist/sources/html_page.py)
|
||||
|
||||
### `crawler/` — async BFS web crawl
|
||||
|
||||
Verbatim lift of an `AsyncWebFetcher` implementation + an `ingest`
|
||||
bridge. BFS-discovers same-domain URLs from a seed, respecting
|
||||
`robots.txt` + crawl delays. Captures ETag + Last-Modified per URL
|
||||
into `document_http_meta` so a future recrawl can send conditional
|
||||
HEAD requests.
|
||||
|
||||
[`aborist/sources/crawler/bridge.py`](../../aborist/sources/crawler/bridge.py)
|
||||
|
||||
### `grok.py` — xAI Grok export
|
||||
|
||||
Reads the `xAI-conversations.json` data export shape. Each
|
||||
conversation becomes one `Document`; media prompts are kept
|
||||
inline.
|
||||
|
||||
[`aborist/sources/grok.py`](../../aborist/sources/grok.py)
|
||||
|
||||
### `vcs.py` — git + Mercurial repositories
|
||||
|
||||
HEAD walk. Each commit becomes a `Document` (commit message + diff
|
||||
stat). The supersedes chain captures commit ancestry as edges.
|
||||
|
||||
[`aborist/sources/vcs.py`](../../aborist/sources/vcs.py)
|
||||
|
||||
## Source
|
||||
|
||||
[`aborist/sources/`](../../aborist/sources/) ·
|
||||
[`aborist/source.py`](../../aborist/source.py) (ABC)
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
# `aborist.store`
|
||||
|
||||
The v9.8 SQLite schema, the audit chain, and the cross-shard
|
||||
read-only view. Every table that holds runtime state lives here.
|
||||
|
||||
## Schema overview (per shard)
|
||||
|
||||
```
|
||||
documents – one row per source document, keyed on document_root
|
||||
chunks – per-document chunk content + tier (hot/cold)
|
||||
chunks_fts – FTS5 contentless index, rowid = chunks.chunk_id
|
||||
merkle_nodes – internal-node hashes for proof reconstruction
|
||||
edges – src_root → dst_root link graph (wikilink, href, …)
|
||||
derivations – core_root ← src_root with proof_blob (Merkle)
|
||||
providence_cache – Q&A records keyed on the v9.8 8-dim cache_key
|
||||
audit_events – linear chain; event_hash = sha256(prev || canonical(body))
|
||||
falsifications – record_id → state transition + reason + actor
|
||||
snapshots – named corpus roots (one hash names a forest)
|
||||
document_http_meta – ETag + Last-Modified for crawler conditional fetches
|
||||
concept_relations – per-shard synonym/rivalry/category/antonym overlay
|
||||
mesh_* – federation tables (off by default)
|
||||
```
|
||||
|
||||
## v9.8 invariants (do not break)
|
||||
|
||||
- **Aborist is a v9.8 store.** Every providence record carries the
|
||||
full **8-dim cache_key**: `source_root | question_hash |
|
||||
model_profile_hash | conversation_hash | governance_policy_hash |
|
||||
schema_version | canonicalization_version | chunking_version`.
|
||||
Bumping any one invalidates prior records on lookup.
|
||||
- **`falsification_state ∈ {live, failed, stale, quarantined}`.**
|
||||
Cache lookups must filter on `state='live'`. Drift detection
|
||||
flips to `stale`.
|
||||
- **Audit chain.** Every state-changing op writes one row in
|
||||
`audit_events` with `event_hash = sha256(prev_event_hash ||
|
||||
canonical(body))`. Chain integrity is verified in
|
||||
`make analyze-shards`. **Never insert into `audit_events`
|
||||
directly — use `aborist.store.append_audit`.**
|
||||
- **Cores never evict.** `evict_to_cold` only touches `kind='surface'`.
|
||||
- **Idempotent re-ingest.** Same content → same `document_root` →
|
||||
no-op insert. Same URI + different content → new doc + `supersedes`
|
||||
edge linking new → old (lossless history).
|
||||
|
||||
## API surface
|
||||
|
||||
```python
|
||||
from aborist.store import (
|
||||
connect, # writable connection to a single shard
|
||||
connect_query, # read-only UNION view across all shards
|
||||
discover_shards, # list *.db files in a shards_dir
|
||||
transaction, # BEGIN IMMEDIATE / COMMIT / ROLLBACK context manager
|
||||
get_meta, set_meta,
|
||||
append_audit, # the ONLY way to write audit_events
|
||||
)
|
||||
```
|
||||
|
||||
Cross-shard reads use `connect_query(shards_dir=...)` which ATTACHes
|
||||
every `*.db` and creates UNION views over the shardable tables
|
||||
(`documents`, `chunks`, `merkle_nodes`, `edges`, `derivations`,
|
||||
`providence_cache`, `audit_events`, `falsifications`,
|
||||
`concept_relations`).
|
||||
|
||||
## Performance pragmas
|
||||
|
||||
`connect()` applies these per-connection:
|
||||
- `journal_mode=WAL` (set in SCHEMA_SQL once at first ingest)
|
||||
- `synchronous=NORMAL` (skip per-commit fsync; safe under WAL)
|
||||
- `cache_size=-65536` (64 MB page cache)
|
||||
- `temp_store=MEMORY` (no /tmp churn for temp tables)
|
||||
|
||||
Don't downgrade to `synchronous=FULL` without a measured reason —
|
||||
costs ~5× throughput.
|
||||
|
||||
## Diagrams
|
||||
|
||||

|
||||

|
||||
|
||||
## Source
|
||||
|
||||
[`aborist/store.py`](../../aborist/store.py)
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
# `aborist.wikitext`
|
||||
|
||||
A single function: `to_base(raw)`. Converts MediaWiki wikitext to
|
||||
plain prose deterministically.
|
||||
|
||||
```python
|
||||
from aborist.wikitext import to_base, BASE_VERSION
|
||||
|
||||
prose = to_base("[[The Beatles]] are an [[English rock band]] from [[Liverpool]].")
|
||||
# → "The Beatles are an English rock band from Liverpool."
|
||||
```
|
||||
|
||||
## Why it exists
|
||||
|
||||
The corpus stores raw wikitext (so the link graph is recoverable
|
||||
on demand) but the LLM and verifier both want plain prose. Reasons:
|
||||
|
||||
1. **Token efficiency.** Wikipedia chunks ship to Hermes with ~43%
|
||||
fewer tokens after wikitext-strip — bigger context window for
|
||||
the same chars budget.
|
||||
2. **Verbatim citation.** The model can quote source paragraphs
|
||||
verbatim instead of escaping `[[wikilinks]]`. The verifier's
|
||||
substring test then matches cleanly.
|
||||
3. **Pinned identity.** `BASE_VERSION='wikitext-base-v1'` lives in
|
||||
`policy["base_version"]`, which folds into
|
||||
`governance_policy_hash`. Bumping `BASE_VERSION` invalidates
|
||||
every prior cache record on next lookup — same discipline as
|
||||
`chunking_version` and `canonicalization_version`.
|
||||
|
||||
## Hot-path discipline
|
||||
|
||||
`to_base()` runs on the assembled context **before** the LLM call
|
||||
in `aborist/qa/runner.py` and `aborist/qa/query.py`, **and again
|
||||
inside `verify_quotes`** so the verifier compares like-against-like.
|
||||
Both sides see prose.
|
||||
|
||||
## Optional dependency
|
||||
|
||||
Backed by `mwparserfromhell`. Install via `pip install '.[wikitext]'`
|
||||
to enable. Without the dep, `_wikitext_to_base = None` and
|
||||
`policy["base_version"] = None` — graceful fallback leaves raw
|
||||
wikitext in both context and verifier (works, just less efficient).
|
||||
|
||||
## Source
|
||||
|
||||
[`aborist/wikitext.py`](../../aborist/wikitext.py)
|
||||
Loading…
Add table
Add a link
Reference in a new issue