aborist/arborist
modified: .gitlab-ci.yml modified: bench/qa_questions.txt modified: bench/qa_sweep.py modified: bench/run.sh modified: docs/TICKETS.md modified: docs/_source/README.md modified: docs/_source/_ext/makefile_targets.py modified: docs/_source/api/cli.rst modified: docs/_source/api/distill.rst modified: docs/_source/api/mesh.rst modified: docs/_source/api/qa.rst modified: docs/_source/api/retrieval.rst modified: docs/_source/api/storage.rst modified: docs/_source/api/substrate.rst modified: docs/_source/concepts.rst modified: docs/_source/conf.py modified: docs/_source/cookbook.rst modified: docs/_source/index.rst modified: docs/_source/license.rst modified: docs/_source/quickstart.rst modified: docs/bench-maxing.md modified: docs/benchmarks.md modified: docs/cti-architecture.md modified: docs/diagrams/aborist-modules.dot modified: docs/diagrams/aborist-modules.svg modified: docs/diagrams/mesh-data-flow.dot modified: docs/diagrams/mesh-epoch-lifecycle.dot modified: docs/diagrams/mesh-epoch-lifecycle.svg modified: docs/diagrams/mesh-group-decisions.dot modified: docs/diagrams/mesh-group-decisions.svg modified: docs/diagrams/mesh-identity-stack.dot modified: docs/diagrams/mesh-secret-envelope.dot modified: docs/mesh.md modified: docs/qa-modes-bench.md modified: docs/seven-point-program.md modified: docs/tickets/ticket-000001-retrieval-keywords-audit-gap.md modified: docs/tickets/ticket-000002-reference-frame-polarity-contract.md modified: docs/tickets/ticket-000003-anchor-class-warrant.md modified: docs/tickets/ticket-000005-label-ladder-migration.md modified: docs/tickets/ticket-000006-bench-emergent-findings.md modified: docs/tickets/ticket-000007-query-layer-hyphen-fold.md modified: docs/tickets/ticket-000008-broad-quantifier-preflight-guard.md modified: docs/tickets/ticket-000009-quantifier-preflight-dag-binding.md modified: docs/tickets/ticket-000010-metacognition-preflight-guard.md modified: docs/tickets/ticket-000011-soft-preflight-hint-sidecar.md modified: scripts/backfill_concepts.py modified: scripts/bench_emergent.py modified: tests/crawler/test_async_web_fetcher.py modified: tests/crawler/test_bridge.py modified: tests/crawler/test_web_fetch.py modified: tests/test_bench_qa_sweep.py modified: tests/test_burn.py modified: tests/test_burn_doc.py modified: tests/test_claim_lattice.py modified: tests/test_cli_render.py modified: tests/test_compress.py modified: tests/test_concepts.py modified: tests/test_dag.py modified: tests/test_directives.py modified: tests/test_distill.py modified: tests/test_distill_recursive.py modified: tests/test_evict.py modified: tests/test_frame.py modified: tests/test_grok_source.py modified: tests/test_html_source.py modified: tests/test_ingest.py modified: tests/test_inspect.py modified: tests/test_journal.py modified: tests/test_keys.py modified: tests/test_llm_context_base.py modified: tests/test_merkle.py modified: tests/test_mesh.py modified: tests/test_mesh_aead.py modified: tests/test_mesh_chain.py modified: tests/test_mesh_cli.py modified: tests/test_mesh_cli_pull.py modified: tests/test_mesh_wire.py modified: tests/test_mesh_wire_e2e.py modified: tests/test_metacognition.py modified: tests/test_migration_audit_mode.py modified: tests/test_providence_source.py modified: tests/test_qa.py modified: tests/test_qa_quality_live.py modified: tests/test_quantifier_caps.py modified: tests/test_quantifier_classifier.py modified: tests/test_quantifier_phase4.py modified: tests/test_quantifier_reminder.py modified: tests/test_query.py modified: tests/test_reclassify.py modified: tests/test_repair.py modified: tests/test_resume.py modified: tests/test_snapshot.py modified: tests/test_soft_preflight.py modified: tests/test_tfidf.py modified: tests/test_vcs_source.py modified: tests/test_verify.py modified: tests/test_verify_json.py modified: tests/test_versioned_ingest.py modified: tests/test_warrant.py modified: tests/test_wikipedia_old.py modified: tests/test_wikipedia_xml.py modified: tests/test_wikitext.py
This commit is contained in:
parent
2c9d00ac95
commit
8d6961fcc1
102 changed files with 716 additions and 716 deletions
|
|
@ -1,4 +1,4 @@
|
|||
# GitLab CI for aborist.
|
||||
# GitLab CI for arborist.
|
||||
#
|
||||
# Runs the unit test suite on every push. Lives at the same shape as
|
||||
# the sibling repos (`unsandbox.com`, `unfirehose-nextjs-logger`):
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
#
|
||||
# What's NOT in CI:
|
||||
# - `make test-live` — needs the live Hermes endpoint + populated
|
||||
# shards under ~/.aborist/shards. Run by hand via `make test-live`
|
||||
# shards under ~/.arborist/shards. Run by hand via `make test-live`
|
||||
# when iterating on QA quality.
|
||||
# - `make test-crawler` — needs `[crawler]` extras + network access
|
||||
# to real HTML sites. Opt-in via `make test-crawler` locally.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# aborist QA-quality benchmark question set.
|
||||
# arborist QA-quality benchmark question set.
|
||||
#
|
||||
# One question per line. `#`-prefixed lines and blank lines ignored.
|
||||
# Designed for the 2003-05-16 Wikipedia cur snapshot — questions must
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"""QA-quality benchmark sweep.
|
||||
|
||||
Runs a fixed question set through ``aborist.qa.query.query`` under each
|
||||
Runs a fixed question set through ``arborist.qa.query.query`` under each
|
||||
answer mode and tabulates STRICT / HYBRID / UNGROUNDED counts, the
|
||||
``n_verified / n_quotes`` ratio, latency, and lazy-anchor signals.
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
|
|||
from pathlib import Path
|
||||
from threading import Lock
|
||||
|
||||
# Defer aborist imports until argparse runs so `--help` works without
|
||||
# Defer arborist imports until argparse runs so `--help` works without
|
||||
# the package installed.
|
||||
|
||||
ANSWER_MODES = ("quote", "claim_lattice_pointer", "claim_lattice")
|
||||
|
|
@ -134,19 +134,19 @@ def _run_one(
|
|||
can flip individual fields for an A/B cycle without touching
|
||||
the policy defaults.
|
||||
"""
|
||||
from aborist.qa.client import OpenAICompatibleClient
|
||||
from aborist.qa.query import DEFAULT_QUERY_POLICY, query
|
||||
from arborist.qa.client import OpenAICompatibleClient
|
||||
from arborist.qa.query import DEFAULT_QUERY_POLICY, query
|
||||
|
||||
policy = dict(DEFAULT_QUERY_POLICY)
|
||||
policy["answer_mode"] = answer_mode
|
||||
if policy_overrides:
|
||||
policy.update(policy_overrides)
|
||||
|
||||
api_key = os.environ.get("ABORIST_LLM_API_KEY")
|
||||
api_key = os.environ.get("ARBORIST_LLM_API_KEY")
|
||||
client = OpenAICompatibleClient(base_url=endpoint, api_key=api_key)
|
||||
|
||||
if qa_db is None:
|
||||
qa_db = (shards_dir / "qa.db") if shards_dir else (Path.home() / ".aborist" / "qa.db")
|
||||
qa_db = (shards_dir / "qa.db") if shards_dir else (Path.home() / ".arborist" / "qa.db")
|
||||
|
||||
t0 = time.monotonic()
|
||||
err: str | None = None
|
||||
|
|
@ -171,7 +171,7 @@ def _run_one(
|
|||
# never mentions the question's subject) at bench-aggregate
|
||||
# scale so a creeping "model deflects rather than refuses"
|
||||
# regression is legible across runs.
|
||||
from aborist.qa.inspect import diagnose_deflection, diagnose_metaphor_deflection
|
||||
from arborist.qa.inspect import diagnose_deflection, diagnose_metaphor_deflection
|
||||
deflection = diagnose_deflection(question, result.get("answer_text") or "")
|
||||
metaphor = diagnose_metaphor_deflection(
|
||||
question, result.get("answer_text") or ""
|
||||
|
|
@ -493,7 +493,7 @@ def _render_markdown(
|
|||
) -> str:
|
||||
lines: list[str] = []
|
||||
n_runs = len(rows)
|
||||
lines.append(f"# aborist QA-quality benchmark — {started_utc}")
|
||||
lines.append(f"# arborist QA-quality benchmark — {started_utc}")
|
||||
lines.append("")
|
||||
lines.append(
|
||||
f"questions: {len(questions)} · modes: {len(modes)} · samples per cell: "
|
||||
|
|
@ -730,7 +730,7 @@ def _render_markdown(
|
|||
def main(argv: list[str] | None = None) -> int:
|
||||
ap = argparse.ArgumentParser(description=__doc__)
|
||||
ap.add_argument("--questions", type=Path, default=Path("bench/qa_questions.txt"))
|
||||
ap.add_argument("--shards-dir", type=Path, default=Path.home() / ".aborist" / "shards")
|
||||
ap.add_argument("--shards-dir", type=Path, default=Path.home() / ".arborist" / "shards")
|
||||
ap.add_argument("--qa-db", type=Path, default=None)
|
||||
ap.add_argument("--out-dir", type=Path, default=Path("bench/qa_results"))
|
||||
ap.add_argument("--top-k", type=int, default=8)
|
||||
|
|
@ -765,9 +765,9 @@ def main(argv: list[str] | None = None) -> int:
|
|||
"re-rendered from the union of pre-existing + "
|
||||
"new rows. Stop/start-able bench.")
|
||||
ap.add_argument("--endpoint", default=os.environ.get(
|
||||
"ABORIST_LLM_ENDPOINT", "https://hermes.ai.unturf.com/v1"))
|
||||
"ARBORIST_LLM_ENDPOINT", "https://hermes.ai.unturf.com/v1"))
|
||||
ap.add_argument("--model", default=os.environ.get(
|
||||
"ABORIST_LLM_MODEL", "adamo1139/Hermes-3-Llama-3.1-8B-FP8-Dynamic"))
|
||||
"ARBORIST_LLM_MODEL", "adamo1139/Hermes-3-Llama-3.1-8B-FP8-Dynamic"))
|
||||
ap.add_argument(
|
||||
"--policy", action="append", default=None, metavar="KEY=VALUE",
|
||||
help=(
|
||||
|
|
|
|||
14
bench/run.sh
14
bench/run.sh
|
|
@ -12,10 +12,10 @@
|
|||
set -uo pipefail
|
||||
|
||||
BENCH_DOCS=${1:-5000}
|
||||
BENCH_DIR=${BENCH_DIR:-/tmp/aborist-bench}
|
||||
BENCH_DIR=${BENCH_DIR:-/tmp/arborist-bench}
|
||||
SHARDS=${SHARDS:-4}
|
||||
DUMP=${DUMP:-data/20030516_cur_tablesql.bz2}
|
||||
ABORIST=${ABORIST:-.venv/bin/aborist}
|
||||
ARBORIST=${ARBORIST:-.venv/bin/arborist}
|
||||
|
||||
mkdir -p "$BENCH_DIR"
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ bench_serial() {
|
|||
local db=$BENCH_DIR/serial.db
|
||||
rm -f "$db" "$db-"*
|
||||
local wall
|
||||
wall=$(run_silent "$ABORIST" --db "$db" ingest --quiet \
|
||||
wall=$(run_silent "$ARBORIST" --db "$db" ingest --quiet \
|
||||
--source wikipedia_cur --path "$DUMP" --limit "$BENCH_DOCS")
|
||||
local docs
|
||||
docs=$(sqlite3 "$db" "SELECT COUNT(*) FROM documents")
|
||||
|
|
@ -54,7 +54,7 @@ bench_parallel_shared() {
|
|||
local start end wall
|
||||
start=$(date +%s.%N)
|
||||
for i in $(seq 0 $((SHARDS - 1))); do
|
||||
"$ABORIST" --db "$db" ingest --quiet \
|
||||
"$ARBORIST" --db "$db" ingest --quiet \
|
||||
--source wikipedia_cur --path "$DUMP" \
|
||||
--shard "$i/$SHARDS" --limit "$per" >/dev/null 2>&1 &
|
||||
done
|
||||
|
|
@ -74,7 +74,7 @@ bench_attached() {
|
|||
local start end wall
|
||||
start=$(date +%s.%N)
|
||||
for i in $(seq 0 $((SHARDS - 1))); do
|
||||
"$ABORIST" ingest --quiet \
|
||||
"$ARBORIST" ingest --quiet \
|
||||
--source wikipedia_cur --path "$DUMP" \
|
||||
--shards-dir "$dir" --shard "$i/$SHARDS" --limit "$per" >/dev/null 2>&1 &
|
||||
done
|
||||
|
|
@ -82,12 +82,12 @@ bench_attached() {
|
|||
end=$(date +%s.%N)
|
||||
wall=$(echo "$end - $start" | bc -l)
|
||||
local docs
|
||||
docs=$("$ABORIST" --shards-dir "$dir" stats 2>/dev/null \
|
||||
docs=$("$ARBORIST" --shards-dir "$dir" stats 2>/dev/null \
|
||||
| python3 -c "import json,sys; print(json.load(sys.stdin)['documents_total'])")
|
||||
printf "attached,%s,%s\n" "$wall" "$docs"
|
||||
}
|
||||
|
||||
echo "=== aborist ETL benchmark — $BENCH_DOCS docs target, $SHARDS shards ==="
|
||||
echo "=== arborist ETL benchmark — $BENCH_DOCS docs target, $SHARDS shards ==="
|
||||
echo
|
||||
|
||||
results+=("$(bench_serial)")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Aborist tickets — index
|
||||
# Arborist tickets — index
|
||||
|
||||
Aborist uses a flat `ticket-NNNNNN-<slug>.md` convention for actionable
|
||||
Arborist uses a flat `ticket-NNNNNN-<slug>.md` convention for actionable
|
||||
proposals: defects with a known root cause, design proposals awaiting
|
||||
go/no-go, or scoped enhancements queued for future implementation.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Aborist API Reference (Sphinx)
|
||||
# Arborist API Reference (Sphinx)
|
||||
|
||||
This directory contains Sphinx configuration to generate API documentation from docstrings.
|
||||
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ def generate_rst(all_targets: dict[str, str], output_path: Path) -> None:
|
|||
"Makefile reference",
|
||||
"==================",
|
||||
"",
|
||||
"Every aborist workflow lives behind a ``make`` target. This page is",
|
||||
"Every arborist workflow lives behind a ``make`` target. This page is",
|
||||
"auto-generated from the project ``Makefile``'s ``## description``",
|
||||
"annotations at Sphinx build time, so it stays in sync with the source.",
|
||||
"",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
CLI: Command-line interface
|
||||
============================
|
||||
|
||||
Entry point for all aborist operations.
|
||||
Entry point for all arborist operations.
|
||||
|
||||
.. automodule:: aborist.cli
|
||||
.. automodule:: arborist.cli
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ Distillation: surface → core compression
|
|||
|
||||
Surface-to-core extraction and recursive distillation.
|
||||
|
||||
.. automodule:: aborist.distill
|
||||
.. automodule:: arborist.distill
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
Federation: multiplayer aborist
|
||||
Federation: multiplayer arborist
|
||||
================================
|
||||
|
||||
Gossip-based mesh for cross-peer data sharing.
|
||||
|
||||
.. automodule:: aborist.mesh
|
||||
.. automodule:: arborist.mesh
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
|
|
|||
|
|
@ -6,62 +6,62 @@ Question answering, caching, verification, evidence mapping.
|
|||
keys
|
||||
----
|
||||
|
||||
.. automodule:: aborist.qa.keys
|
||||
.. automodule:: arborist.qa.keys
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
runner
|
||||
------
|
||||
|
||||
.. automodule:: aborist.qa.runner
|
||||
.. automodule:: arborist.qa.runner
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
query
|
||||
-----
|
||||
|
||||
.. automodule:: aborist.qa.query
|
||||
.. automodule:: arborist.qa.query
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
verify
|
||||
------
|
||||
|
||||
.. automodule:: aborist.qa.verify
|
||||
.. automodule:: arborist.qa.verify
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
evidence
|
||||
--------
|
||||
|
||||
.. automodule:: aborist.qa.evidence
|
||||
.. automodule:: arborist.qa.evidence
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
quantifier
|
||||
----------
|
||||
|
||||
.. automodule:: aborist.qa.quantifier
|
||||
.. automodule:: arborist.qa.quantifier
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
metacognition
|
||||
-------------
|
||||
|
||||
.. automodule:: aborist.qa.metacognition
|
||||
.. automodule:: arborist.qa.metacognition
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
dag
|
||||
---
|
||||
|
||||
.. automodule:: aborist.qa.dag
|
||||
.. automodule:: arborist.qa.dag
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
client
|
||||
------
|
||||
|
||||
.. automodule:: aborist.qa.client
|
||||
.. automodule:: arborist.qa.client
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ Full-text search, concept relations (synonym/rivalry overlay).
|
|||
search
|
||||
------
|
||||
|
||||
.. automodule:: aborist.search
|
||||
.. automodule:: arborist.search
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
|
@ -14,22 +14,22 @@ search
|
|||
sources
|
||||
-------
|
||||
|
||||
.. automodule:: aborist.source
|
||||
.. automodule:: arborist.source
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
.. automodule:: aborist.sources.wikipedia
|
||||
.. automodule:: arborist.sources.wikipedia
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
.. automodule:: aborist.sources.html_page
|
||||
.. automodule:: arborist.sources.html_page
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
concepts
|
||||
--------
|
||||
|
||||
.. automodule:: aborist.concepts
|
||||
.. automodule:: arborist.concepts
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ v9.8 SQLite schema, audit chain, and cross-shard views.
|
|||
store
|
||||
-----
|
||||
|
||||
.. automodule:: aborist.store
|
||||
.. automodule:: arborist.store
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
|
@ -14,7 +14,7 @@ store
|
|||
ingest
|
||||
------
|
||||
|
||||
.. automodule:: aborist.ingest
|
||||
.. automodule:: arborist.ingest
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
|
@ -22,6 +22,6 @@ ingest
|
|||
evict
|
||||
-----
|
||||
|
||||
.. automodule:: aborist.evict
|
||||
.. automodule:: arborist.evict
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ Pure Merkle tree and document primitives.
|
|||
merkle
|
||||
------
|
||||
|
||||
.. automodule:: aborist.merkle
|
||||
.. automodule:: arborist.merkle
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
|
@ -14,7 +14,7 @@ merkle
|
|||
document
|
||||
--------
|
||||
|
||||
.. automodule:: aborist.document
|
||||
.. automodule:: arborist.document
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
|
@ -22,6 +22,6 @@ document
|
|||
wikitext
|
||||
--------
|
||||
|
||||
.. automodule:: aborist.wikitext
|
||||
.. automodule:: arborist.wikitext
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
Concepts
|
||||
========
|
||||
|
||||
Aborist is a content-addressed document store that gives every cached
|
||||
Arborist is a content-addressed document store that gives every cached
|
||||
LLM answer a verifiable Merkle proof tying it back to its source
|
||||
documents. This page is the orientation: what the system is, the
|
||||
core abstractions you'll see in code and docs, and how they compose.
|
||||
|
||||
What aborist is
|
||||
What arborist is
|
||||
---------------
|
||||
|
||||
A reference implementation of two papers stacked:
|
||||
|
|
@ -26,8 +26,8 @@ Three layers stacked on one SQLite file (per shard):
|
|||
- **Providence cache** — Q&A records keyed on the v9.8 8-dimension
|
||||
invariant; every record carries an audit_mode and a Merkle proof.
|
||||
|
||||
.. figure:: diagrams/aborist-modules.svg
|
||||
:alt: Aborist module graph
|
||||
.. figure:: diagrams/arborist-modules.svg
|
||||
:alt: Arborist module graph
|
||||
:width: 100%
|
||||
|
||||
Top-level module graph. Substrate (merkle, document) at the bottom;
|
||||
|
|
@ -41,7 +41,7 @@ canonicalization compute **bit-identical** ``document_root`` hashes.
|
|||
That is the v9.8 admissibility property: identity by content, not by
|
||||
location.
|
||||
|
||||
Aborist uses fox's existing Go Merkle conventions verbatim
|
||||
Arborist uses fox's existing Go Merkle conventions verbatim
|
||||
(``proxy.unturf.com/pkg/verified/merkle.go``):
|
||||
|
||||
- **Leaf hash:** ``sha256(0x00 || canonical_chunk_bytes)``.
|
||||
|
|
@ -78,7 +78,7 @@ with ``event_hash = sha256(prev_event_hash || canonical(body))``.
|
|||
Linear chain per shard, verified by ``make chain-check-shards`` (any
|
||||
break is the loudest possible signal).
|
||||
|
||||
Always write via ``aborist.store.append_audit`` — never insert into
|
||||
Always write via ``arborist.store.append_audit`` — never insert into
|
||||
``audit_events`` directly.
|
||||
|
||||
The trichotomy and the four-rung ladder
|
||||
|
|
@ -168,7 +168,7 @@ framing, and **never write to providence_cache or extend the audit
|
|||
chain**. That invariant is what keeps ``audit_mode`` a binary
|
||||
classification rather than a soft score.
|
||||
|
||||
See :doc:`api/qa` (``aborist.qa.inspect``).
|
||||
See :doc:`api/qa` (``arborist.qa.inspect``).
|
||||
|
||||
Where to go next
|
||||
----------------
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
"""Sphinx configuration for Aborist API reference."""
|
||||
"""Sphinx configuration for Arborist API reference."""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Add aborist package + local _ext (Sphinx extensions) to path
|
||||
# Add arborist package + local _ext (Sphinx extensions) to path
|
||||
project_root = Path(__file__).parent.parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
sys.path.insert(0, str(Path(__file__).parent / "_ext"))
|
||||
|
||||
# Project info
|
||||
project = "Aborist"
|
||||
project = "Arborist"
|
||||
copyright = "2026, agent blackops"
|
||||
author = "fox/timehexon"
|
||||
version = "9.8"
|
||||
|
|
@ -37,7 +37,7 @@ napoleon_attr_annotations = True
|
|||
# HTML output
|
||||
html_theme = "sphinx_book_theme"
|
||||
html_static_path = ["_static"]
|
||||
html_title = "Aborist API Reference"
|
||||
html_title = "Arborist API Reference"
|
||||
|
||||
# Standard sphinx-book-theme layout: full project TOC on the left,
|
||||
# 'On this page' (current page sections) on the right. show_toc_level=3
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ Cookbook
|
|||
========
|
||||
|
||||
Recipes for common workflows beyond the quickstart. Each starts from
|
||||
a working aborist install (``make bootstrap`` already run) and a
|
||||
populated shards directory under ``~/.aborist/shards/``.
|
||||
a working arborist install (``make bootstrap`` already run) and a
|
||||
populated shards directory under ``~/.arborist/shards/``.
|
||||
|
||||
Re-crawl a website to detect changes
|
||||
-------------------------------------
|
||||
|
|
@ -56,7 +56,7 @@ time to fail. See :doc:`api/qa` for the ``ProvidenceSource`` impl.
|
|||
Query across mixed corpora
|
||||
--------------------------
|
||||
|
||||
Every shard under ``~/.aborist/shards/`` is queried automatically.
|
||||
Every shard under ``~/.arborist/shards/`` is queried automatically.
|
||||
Mix Wikipedia, your Grok export, a crawled site, and your own git
|
||||
repos in one query — retrieval ranks across all of them.
|
||||
|
||||
|
|
@ -81,9 +81,9 @@ no auth). Point at any OpenAI-compatible endpoint via env:
|
|||
|
||||
.. code-block:: sh
|
||||
|
||||
export ABORIST_LLM_ENDPOINT="https://your-vllm.example.com/v1"
|
||||
export ABORIST_LLM_MODEL="meta-llama/Llama-3.1-70B-Instruct"
|
||||
export ABORIST_LLM_API_KEY="..." # optional; many vLLM deploys are open
|
||||
export ARBORIST_LLM_ENDPOINT="https://your-vllm.example.com/v1"
|
||||
export ARBORIST_LLM_MODEL="meta-llama/Llama-3.1-70B-Instruct"
|
||||
export ARBORIST_LLM_API_KEY="..." # optional; many vLLM deploys are open
|
||||
make query Q="..."
|
||||
|
||||
The model id folds into ``model_profile_hash`` (one of the 8 cache
|
||||
|
|
@ -140,4 +140,4 @@ keywords without changing what the LLM sees as the question:
|
|||
make query Q="What did Orwell mean by always at war?" K="1984 Oceania Eastasia"
|
||||
|
||||
Provenance gap on this is tracked in
|
||||
:doc:`api/qa` (``aborist.qa.query``).
|
||||
:doc:`api/qa` (``arborist.qa.query``).
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
Aborist API Reference
|
||||
Arborist API Reference
|
||||
=====================
|
||||
|
||||
Generated from docstrings. Replaces the static modules.md.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
License
|
||||
=======
|
||||
|
||||
Aborist ships under the **GNU Affero General Public License v3.0 (only)**
|
||||
Arborist ships under the **GNU Affero General Public License v3.0 (only)**
|
||||
with a Permacomputer Preamble. Full text below, sourced from the
|
||||
``LICENSE`` file at the repository root (single source of truth — this
|
||||
page just renders it).
|
||||
|
|
|
|||
|
|
@ -7,18 +7,18 @@ the same query, verify, falsify, and inspect surfaces.
|
|||
Install
|
||||
-------
|
||||
|
||||
Aborist needs Python 3.10+, GNU make, ``curl``, and ``bzip2``. SQLite
|
||||
Arborist needs Python 3.10+, GNU make, ``curl``, and ``bzip2``. SQLite
|
||||
3.35+ ships with CPython.
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
git clone https://git.unturf.com/engineering/unturf/aborist.git
|
||||
cd aborist
|
||||
git clone https://git.unturf.com/engineering/unturf/arborist.git
|
||||
cd arborist
|
||||
make bootstrap # one-time: venv + dev extras
|
||||
|
||||
``make bootstrap`` creates ``.venv/``, installs the package in editable
|
||||
mode with ``[dev,html]`` extras, and exposes ``aborist`` at
|
||||
``.venv/bin/aborist``. No system-wide install. Re-running is a no-op
|
||||
mode with ``[dev,html]`` extras, and exposes ``arborist`` at
|
||||
``.venv/bin/arborist``. No system-wide install. Re-running is a no-op
|
||||
if the venv is up to date.
|
||||
|
||||
After bootstrap, every workflow lives behind a ``make`` target. Run
|
||||
|
|
@ -54,7 +54,7 @@ Path B — Crawl any live website and query it
|
|||
make query Q="who is Russell Ballestrini?" # cross-shard; picks up new shard automatically
|
||||
|
||||
The crawl shard is named after the seed hostname
|
||||
(``crawl_russell_ballestrini_net.db``) under ``~/.aborist/shards/``.
|
||||
(``crawl_russell_ballestrini_net.db``) under ``~/.arborist/shards/``.
|
||||
``FAST=1`` enables aggressive crawling for your own sites; ``MAX=N``
|
||||
caps discovery; ``DEPTH=N`` bounds BFS. Robots ``Disallow`` is always
|
||||
honored. After ingest, ``make recrawl-check DOMAIN=...`` does a
|
||||
|
|
@ -98,15 +98,15 @@ Llama-3.1-8B, 82K context, no auth). Override:
|
|||
|
||||
.. code-block:: sh
|
||||
|
||||
export ABORIST_LLM_ENDPOINT="https://your-vllm.example/v1"
|
||||
export ABORIST_LLM_MODEL="meta-llama/Llama-3.1-70B-Instruct"
|
||||
export ABORIST_LLM_API_KEY="..."
|
||||
export ARBORIST_LLM_ENDPOINT="https://your-vllm.example/v1"
|
||||
export ARBORIST_LLM_MODEL="meta-llama/Llama-3.1-70B-Instruct"
|
||||
export ARBORIST_LLM_API_KEY="..."
|
||||
|
||||
Where to next
|
||||
-------------
|
||||
|
||||
* :doc:`api/makefile` — every ``make`` target with a one-line description
|
||||
* :doc:`api/cli` — direct ``aborist`` CLI reference
|
||||
* :doc:`api/cli` — direct ``arborist`` CLI reference
|
||||
* :doc:`api/qa` — Q&A pipeline internals (verifier, evidence, DAG)
|
||||
* :doc:`api/substrate` — Merkle tree + document primitives
|
||||
* :doc:`license` — full AGPL + Permacomputer Preamble
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ during the JSON-mode hardening journey:
|
|||
whatever partial content the model produced and lands in the
|
||||
unverified bucket honestly. Retrying spends another inference
|
||||
round and may produce identical garbage. See
|
||||
`_repair_truncated_json` in `aborist/qa/verify.py` for the
|
||||
`_repair_truncated_json` in `arborist/qa/verify.py` for the
|
||||
pattern: walk once tracking string state + bracket stack, close /
|
||||
drop / balance at end-of-input. Conservative repairs only — never
|
||||
insert content, never fabricate keys.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Benchmarks — orientation, harnesses, fixtures, discipline
|
||||
|
||||
Aborist ships two bench harnesses, four question fixtures, and a
|
||||
Arborist ships two bench harnesses, four question fixtures, and a
|
||||
signal-floor discipline pinned across the substrate. This doc is
|
||||
the canonical entry point. Three companion docs cover specifics:
|
||||
|
||||
|
|
@ -41,7 +41,7 @@ output (`quantifier_intensity`, `scope_bound_hint`,
|
|||
|
||||
Picks three random words from `/usr/share/dict/words`, asks
|
||||
Hermes (at temp 0.8) to weave them into a creative question
|
||||
paragraph, then sends that paragraph to aborist. Logs every cycle
|
||||
paragraph, then sends that paragraph to arborist. Logs every cycle
|
||||
to `bench/emergent_log.jsonl` as one JSONL line. Designed for
|
||||
**blue-moon cadence**, not every-commit benching — the
|
||||
combinatoric word space surfaces failure shapes the curated bench
|
||||
|
|
@ -109,13 +109,13 @@ make bench-qa
|
|||
# Same but with limit / different file
|
||||
.venv/bin/python bench/qa_sweep.py \
|
||||
--questions bench/qa_questions_quantifier_subset.txt \
|
||||
--shards-dir ~/.aborist/shards --n 3 --concurrency 4 \
|
||||
--shards-dir ~/.arborist/shards --n 3 --concurrency 4 \
|
||||
--out-dir bench/qa_results
|
||||
|
||||
# A/B cycle: flip a policy knob for one bench cell
|
||||
.venv/bin/python bench/qa_sweep.py \
|
||||
--questions bench/qa_questions_quantifier_subset.txt \
|
||||
--shards-dir ~/.aborist/shards --n 3 --concurrency 4 \
|
||||
--shards-dir ~/.arborist/shards --n 3 --concurrency 4 \
|
||||
--policy quantifier_guard_apply_caps=true \
|
||||
--policy quantifier_reminder_enabled=true
|
||||
|
||||
|
|
@ -219,7 +219,7 @@ random-word stress cycles. Each entry has:
|
|||
"ts": <unix>, "iso_ts": "2026-05-04T...",
|
||||
"words": ["dismally", "heats", "Jamaicans"],
|
||||
"question": "<model-generated question paragraph>",
|
||||
"answer": "<aborist's answer>",
|
||||
"answer": "<arborist's answer>",
|
||||
"audit_mode": "STRICT|HYBRID|UNGROUNDED",
|
||||
"verifier_method": "claim_lattice",
|
||||
"n_quotes": 3, "n_verified": 0,
|
||||
|
|
@ -292,7 +292,7 @@ seven-point-program directives (D2/D3/D4/D6/D7). Substrate-level
|
|||
hygiene check; should be ~100% on lattice modes.
|
||||
|
||||
**Audit-line tails** (rendered by
|
||||
`aborist/cli.py:_render_warrant_tail`):
|
||||
`arborist/cli.py:_render_warrant_tail`):
|
||||
- Hard tails: `· warrant missing`, `· title mismatch`,
|
||||
`· format collapsed`, `· broad cap N`, `· broad rejected`,
|
||||
`· broad unbounded`, `· broad runaway`, `· false premise`,
|
||||
|
|
@ -311,20 +311,20 @@ warnings worth the operator's attention.
|
|||
|
||||
```bash
|
||||
# Inspect preflight stage payload from any cache row
|
||||
aborist providence --show-preflight CACHE_KEY_PREFIX
|
||||
arborist providence --show-preflight CACHE_KEY_PREFIX
|
||||
|
||||
# Single query with all preflight knobs flipped
|
||||
aborist query "winners of all major sports?" \
|
||||
arborist query "winners of all major sports?" \
|
||||
--apply-quantifier-caps --reject-broad --soft-preflight
|
||||
|
||||
# Dry-run — assemble context but skip LLM
|
||||
aborist query "..." --dry-run
|
||||
arborist query "..." --dry-run
|
||||
|
||||
# Force fresh inference (delete prior cache row first)
|
||||
aborist query "..." --burn
|
||||
arborist query "..." --burn
|
||||
|
||||
# Show raw JSON record instead of human render
|
||||
aborist query "..." --json
|
||||
arborist query "..." --json
|
||||
```
|
||||
|
||||
The `Makefile` exposes most of these via shorthand:
|
||||
|
|
|
|||
|
|
@ -63,10 +63,10 @@ The animating principle: **the model proposes, the runtime structures, the verif
|
|||
- which falsification triggers stale a record
|
||||
|
||||
**Today's codebase:**
|
||||
- `aborist/qa/runner.py:ask` — orchestrates the full lookup → infer → verify → admissibility flow for the per-document path
|
||||
- `aborist/qa/query.py:query` — same orchestration for the multi-source RAG path
|
||||
- `arborist/qa/runner.py:ask` — orchestrates the full lookup → infer → verify → admissibility flow for the per-document path
|
||||
- `arborist/qa/query.py:query` — same orchestration for the multi-source RAG path
|
||||
- `DEFAULT_POLICY` (runner) and `DEFAULT_QUERY_POLICY` (query) — the policy dict
|
||||
- `aborist.qa.keys.governance_policy_hash` — folds policy into the cache_key
|
||||
- `arborist.qa.keys.governance_policy_hash` — folds policy into the cache_key
|
||||
|
||||
**Status:** Implicit. The layer exists as code but is not named. The de-novo doc proposes naming it; the codebase doesn't need a `prometheus.py` module today — the dispatch logic in `runner.py` and `query.py` IS PROMETHEUS-Σ.
|
||||
|
||||
|
|
@ -79,15 +79,15 @@ The animating principle: **the model proposes, the runtime structures, the verif
|
|||
- the boundary between hard checks (lexical, deterministic) and soft checks (heuristic, demote-only)
|
||||
|
||||
**Today's codebase:**
|
||||
- `aborist/qa/parse_claims.py:parse_pointer_claims` — the model's pointer-line output → `ParsedClaim` records (the CTI compile step)
|
||||
- `aborist/qa/evidence.py:EvidenceObject` — typed evidence nodes with `evidence_id` (content-addressed) + `pointer_id` (prompt-facing)
|
||||
- `aborist/qa/evidence.py:render_claim_lattice` — the renderer that interpolates source spans by offset
|
||||
- `aborist/qa/verify.py:verify_claim_lattice` — the deterministic verifier (six hard checks + sidecar signals)
|
||||
- `aborist/qa/verify.py:verify_claim_lattice_json` — same for the JSON variant
|
||||
- `aborist/qa/quantifier.py:classify_question_quantifier` — pure 10-rung intensity classifier (#000008); feeds the preflight stage's `classifier` clause
|
||||
- `aborist/qa/model_profiles.py:cap_for_intensity` — per-model claim-cap profiles (#000008); feeds the `answer_contract` clause's `claim_cap_resolved`
|
||||
- `aborist/qa/quantifier_reminder.py:broad_quantifier_reminder` — broad-question reminder text generator (#000008); feeds the `prompt_contract` clause
|
||||
- `aborist/qa/metacognition.py:preflight_question` — meta-cognition QuestionState detector suite (#000010): temporal, contradiction, false-premise-lite, out-of-corpus. Feeds the preflight stage's `question_state` clause.
|
||||
- `arborist/qa/parse_claims.py:parse_pointer_claims` — the model's pointer-line output → `ParsedClaim` records (the CTI compile step)
|
||||
- `arborist/qa/evidence.py:EvidenceObject` — typed evidence nodes with `evidence_id` (content-addressed) + `pointer_id` (prompt-facing)
|
||||
- `arborist/qa/evidence.py:render_claim_lattice` — the renderer that interpolates source spans by offset
|
||||
- `arborist/qa/verify.py:verify_claim_lattice` — the deterministic verifier (six hard checks + sidecar signals)
|
||||
- `arborist/qa/verify.py:verify_claim_lattice_json` — same for the JSON variant
|
||||
- `arborist/qa/quantifier.py:classify_question_quantifier` — pure 10-rung intensity classifier (#000008); feeds the preflight stage's `classifier` clause
|
||||
- `arborist/qa/model_profiles.py:cap_for_intensity` — per-model claim-cap profiles (#000008); feeds the `answer_contract` clause's `claim_cap_resolved`
|
||||
- `arborist/qa/quantifier_reminder.py:broad_quantifier_reminder` — broad-question reminder text generator (#000008); feeds the `prompt_contract` clause
|
||||
- `arborist/qa/metacognition.py:preflight_question` — meta-cognition QuestionState detector suite (#000010): temporal, contradiction, false-premise-lite, out-of-corpus. Feeds the preflight stage's `question_state` clause.
|
||||
|
||||
**Status:** Real. CTI is what `claim_lattice` already is. The architectural insight from fox's synthesis — that this is an INTERNAL bytecode for reasoning, not a model output format — is the right reading: the model emits weak pointer lines; the runtime parses them into the CTI lattice; the lattice is what the verifier and renderer operate on. The pre-answer preflight contract (#000008 quantifier classifier + #000010 metacognition QuestionState) extends CTI upstream of retrieval — the runtime now classifies the *question* before generating an answer, so policy can refuse, cap, or reject without burning an LLM call.
|
||||
|
||||
|
|
@ -119,12 +119,12 @@ The animating principle: **the model proposes, the runtime structures, the verif
|
|||
**Audit-replay payoff:** two cache rows that share the same question + same model output + same verifier verdict but different preflight policy state (cap on/off, reminder on/off, reject path taken/not, metacog gates) now produce **different `run_dag_root`** values.
|
||||
|
||||
**Today's codebase:**
|
||||
- `aborist/qa/dag.py:build_run_dag` — emits the 7/8/9/10-stage DAG; returns `{root, nodes, blob}`. `preflight_hash` parameter optional for backward-compat with legacy roots.
|
||||
- `aborist/qa/dag.py:build_reject_run_dag` — emits the 3-stage reject-path DAG.
|
||||
- `aborist/qa/dag.py:preflight_node_hash` + `build_preflight_node_payload` — canonical hash of the preflight clause set.
|
||||
- `aborist/merkle.py` — the non-commutative HashCombine + odd-self-duplicate Merkle conventions (Python port of `proxy.unturf.com/pkg/verified/merkle.go`)
|
||||
- `aborist.store.append_audit` — the only legal entry point for `audit_events`; computes `event_hash = sha256(prev_event_hash || canonical(body))`
|
||||
- `aborist/qa/keys.py:cache_key` — the 8-dim composite hash that gates record reuse
|
||||
- `arborist/qa/dag.py:build_run_dag` — emits the 7/8/9/10-stage DAG; returns `{root, nodes, blob}`. `preflight_hash` parameter optional for backward-compat with legacy roots.
|
||||
- `arborist/qa/dag.py:build_reject_run_dag` — emits the 3-stage reject-path DAG.
|
||||
- `arborist/qa/dag.py:preflight_node_hash` + `build_preflight_node_payload` — canonical hash of the preflight clause set.
|
||||
- `arborist/merkle.py` — the non-commutative HashCombine + odd-self-duplicate Merkle conventions (Python port of `proxy.unturf.com/pkg/verified/merkle.go`)
|
||||
- `arborist.store.append_audit` — the only legal entry point for `audit_events`; computes `event_hash = sha256(prev_event_hash || canonical(body))`
|
||||
- `arborist/qa/keys.py:cache_key` — the 8-dim composite hash that gates record reuse
|
||||
|
||||
**Status:** Real. The base 7/9-stage DAG was the F-track work earlier in the session; preflight binding landed 2026-05-03 (#000009 base, `c36e85c`) and was restructured to nested-clause shape 2026-05-04 (`111dda6`).
|
||||
|
||||
|
|
@ -136,9 +136,9 @@ The animating principle: **the model proposes, the runtime structures, the verif
|
|||
- the renderer pulls source text by `(chunk_root, offset_start, offset_end)` — the model NEVER owns the quote text; the runtime interpolates it
|
||||
|
||||
**Today's codebase:**
|
||||
- `aborist/qa/evidence.py` — `EvidenceObject(source_root, document_uri, chunk_root, offset_start, offset_end, source_role, text_hash, span, evidence_id, pointer_id)`. The `pointer_id` (E1, E2, …) is what the model sees in the prompt; the `evidence_id` (content-addressed) is the cache/run-DAG handle. The runtime maps pointer_id → object → content-addressed evidence_id internally.
|
||||
- `aborist/qa/verify.py:verify_claim_lattice` step 6 (citation-overlap check) — the `claim → cited_span` lexical-coverage gate
|
||||
- `aborist/qa/evidence.py:render_claim_lattice` — interpolates source spans by offset, not Hermes text. Synthetic-elision is impossible by construction in pointer mode because the model never produces the quote string.
|
||||
- `arborist/qa/evidence.py` — `EvidenceObject(source_root, document_uri, chunk_root, offset_start, offset_end, source_role, text_hash, span, evidence_id, pointer_id)`. The `pointer_id` (E1, E2, …) is what the model sees in the prompt; the `evidence_id` (content-addressed) is the cache/run-DAG handle. The runtime maps pointer_id → object → content-addressed evidence_id internally.
|
||||
- `arborist/qa/verify.py:verify_claim_lattice` step 6 (citation-overlap check) — the `claim → cited_span` lexical-coverage gate
|
||||
- `arborist/qa/evidence.py:render_claim_lattice` — interpolates source spans by offset, not Hermes text. Synthetic-elision is impossible by construction in pointer mode because the model never produces the quote string.
|
||||
|
||||
**Status:** Real. Pointer mode makes the reverse-RAG direction explicit; the model produces the LEFT side of the chain (claim) and points to the RIGHT side (evidence_id) — the runtime walks the rest.
|
||||
|
||||
|
|
@ -151,9 +151,9 @@ The animating principle: **the model proposes, the runtime structures, the verif
|
|||
- the deliberate honesty boundary: NOT semantic truth, NOT hallucination elimination, NOT formal proof — just admissible provenance
|
||||
|
||||
**Today's codebase:**
|
||||
- `aborist/qa/keys.py` — all eight dimensions hashed into `cache_key`
|
||||
- `aborist/store.py` — schema, audit chain, transaction helpers
|
||||
- `aborist/qa/runner.py` + `aborist/qa/query.py` — the cache-lookup-then-infer-then-write flow
|
||||
- `arborist/qa/keys.py` — all eight dimensions hashed into `cache_key`
|
||||
- `arborist/store.py` — schema, audit chain, transaction helpers
|
||||
- `arborist/qa/runner.py` + `arborist/qa/query.py` — the cache-lookup-then-infer-then-write flow
|
||||
|
||||
**Status:** Real and honest. CLAUDE.md "Schema invariants (do not break)" enforces this boundary.
|
||||
|
||||
|
|
@ -165,8 +165,8 @@ The animating principle: **the model proposes, the runtime structures, the verif
|
|||
- nothing else — every other artifact (the lattice, the verdict, the rendered prose, the run DAG) comes from the runtime
|
||||
|
||||
**Today's codebase:**
|
||||
- `aborist/qa/client.py:OpenAICompatibleClient` — HTTP client with retry on 502/503/504
|
||||
- `aborist/qa/client.py:StubClient` — offline test stub
|
||||
- `arborist/qa/client.py:OpenAICompatibleClient` — HTTP client with retry on 502/503/504
|
||||
- `arborist/qa/client.py:StubClient` — offline test stub
|
||||
- `claim_lattice_system_prompt` + `claim_lattice_grounding_reminder` — the prompt fields that frame Hermes' role
|
||||
- `claim_lattice_json_system_prompt` + `claim_lattice_json_grounding_reminder` — JSON variant
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
// Aborist module graph — top-level packages & their dependencies.
|
||||
// Arborist module graph — top-level packages & their dependencies.
|
||||
//
|
||||
// Arrows point in the import direction (A → B means A imports from B).
|
||||
// Cluster boxes group modules with shared concerns (storage, retrieval,
|
||||
// federation, etc.).
|
||||
//
|
||||
// Render: dot -Tsvg aborist-modules.dot -o aborist-modules.svg
|
||||
// Render: dot -Tsvg arborist-modules.dot -o arborist-modules.svg
|
||||
|
||||
digraph aborist_modules {
|
||||
digraph arborist_modules {
|
||||
rankdir=LR
|
||||
node [shape=box, style="rounded,filled", fontname="Helvetica", fontsize=10]
|
||||
edge [fontname="Helvetica", fontsize=9]
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@
|
|||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 2.43.0 (0)
|
||||
-->
|
||||
<!-- Title: aborist_modules Pages: 1 -->
|
||||
<!-- Title: arborist_modules Pages: 1 -->
|
||||
<svg width="1663pt" height="1147pt"
|
||||
viewBox="0.00 0.00 1663.00 1146.81" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1142.81)">
|
||||
<title>aborist_modules</title>
|
||||
<title>arborist_modules</title>
|
||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-1142.81 1659,-1142.81 1659,4 -4,4"/>
|
||||
<g id="clust1" class="cluster">
|
||||
<title>cluster_substrate</title>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
|
|
@ -1,6 +1,6 @@
|
|||
// aborist/mesh — what flows between peers.
|
||||
// arborist/mesh — what flows between peers.
|
||||
//
|
||||
// Aborist is content-addressed: two peers ingesting the same dump
|
||||
// Arborist is content-addressed: two peers ingesting the same dump
|
||||
// compute bit-identical document_roots. Gossip exchanges identifiers,
|
||||
// derivations, falsifications, and witness signatures. Bulk content
|
||||
// only flows on cache miss, by Merkle proof of inclusion.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// aborist/mesh — epoch state machine.
|
||||
// arborist/mesh — epoch state machine.
|
||||
//
|
||||
// Every roster mutation bumps a counter. Old epochs persist forever
|
||||
// (signatures stay verifiable). New gossip is encrypted under the
|
||||
|
|
@ -18,7 +18,7 @@ digraph mesh_epoch_lifecycle {
|
|||
eN [label="epoch N+1\nfresh secret\nfresh envelope", fillcolor="#d6ffd6"];
|
||||
historic [label="every prior epoch\nstays on disk\n(roster + signatures verifiable)", fillcolor="#ffffff", shape=note];
|
||||
|
||||
none -> e0 [label="aborist mesh init --group <name>"];
|
||||
none -> e0 [label="arborist mesh init --group <name>"];
|
||||
e0 -> e1 [label="add | kick | rotate"];
|
||||
e1 -> eN [label="add | kick | rotate"];
|
||||
eN -> eN [label="rotate (no roster change)"];
|
||||
|
|
@ -31,8 +31,8 @@ digraph mesh_epoch_lifecycle {
|
|||
style="rounded,dashed";
|
||||
color="#666666";
|
||||
node [fillcolor="#e8f0ff", shape=note];
|
||||
op_add [label="aborist mesh add\n--member-id bob ...\nadmin only"];
|
||||
op_kick [label="aborist mesh kick\n--member-id bob ...\nadmin only"];
|
||||
op_rotate [label="aborist mesh rotate\nany current member"];
|
||||
op_add [label="arborist mesh add\n--member-id bob ...\nadmin only"];
|
||||
op_kick [label="arborist mesh kick\n--member-id bob ...\nadmin only"];
|
||||
op_rotate [label="arborist mesh rotate\nany current member"];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
<title>none->e0</title>
|
||||
<path fill="none" stroke="black" d="M195.03,-30.5C254.65,-30.5 330.75,-30.5 389.75,-30.5"/>
|
||||
<polygon fill="black" stroke="black" points="389.81,-34 399.81,-30.5 389.81,-27 389.81,-34"/>
|
||||
<text text-anchor="middle" x="297.5" y="-33.5" font-family="Helvetica,sans-Serif" font-size="10.00">aborist mesh init --group <name></text>
|
||||
<text text-anchor="middle" x="297.5" y="-33.5" font-family="Helvetica,sans-Serif" font-size="10.00">arborist mesh init --group <name></text>
|
||||
</g>
|
||||
<!-- e1 -->
|
||||
<g id="node3" class="node">
|
||||
|
|
@ -99,7 +99,7 @@
|
|||
<polygon fill="#e8f0ff" stroke="black" points="167,-248 22,-248 22,-195 173,-195 173,-242 167,-248"/>
|
||||
<polyline fill="none" stroke="black" points="167,-248 167,-242 "/>
|
||||
<polyline fill="none" stroke="black" points="173,-242 167,-242 "/>
|
||||
<text text-anchor="middle" x="97.5" y="-232.8" font-family="Helvetica,sans-Serif" font-size="14.00">aborist mesh add</text>
|
||||
<text text-anchor="middle" x="97.5" y="-232.8" font-family="Helvetica,sans-Serif" font-size="14.00">arborist mesh add</text>
|
||||
<text text-anchor="middle" x="97.5" y="-217.8" font-family="Helvetica,sans-Serif" font-size="14.00">--member-id bob ...</text>
|
||||
<text text-anchor="middle" x="97.5" y="-202.8" font-family="Helvetica,sans-Serif" font-size="14.00">admin only</text>
|
||||
</g>
|
||||
|
|
@ -109,7 +109,7 @@
|
|||
<polygon fill="#e8f0ff" stroke="black" points="167,-177 22,-177 22,-124 173,-124 173,-171 167,-177"/>
|
||||
<polyline fill="none" stroke="black" points="167,-177 167,-171 "/>
|
||||
<polyline fill="none" stroke="black" points="173,-171 167,-171 "/>
|
||||
<text text-anchor="middle" x="97.5" y="-161.8" font-family="Helvetica,sans-Serif" font-size="14.00">aborist mesh kick</text>
|
||||
<text text-anchor="middle" x="97.5" y="-161.8" font-family="Helvetica,sans-Serif" font-size="14.00">arborist mesh kick</text>
|
||||
<text text-anchor="middle" x="97.5" y="-146.8" font-family="Helvetica,sans-Serif" font-size="14.00">--member-id bob ...</text>
|
||||
<text text-anchor="middle" x="97.5" y="-131.8" font-family="Helvetica,sans-Serif" font-size="14.00">admin only</text>
|
||||
</g>
|
||||
|
|
@ -119,7 +119,7 @@
|
|||
<polygon fill="#e8f0ff" stroke="black" points="171,-105.5 18,-105.5 18,-67.5 177,-67.5 177,-99.5 171,-105.5"/>
|
||||
<polyline fill="none" stroke="black" points="171,-105.5 171,-99.5 "/>
|
||||
<polyline fill="none" stroke="black" points="177,-99.5 171,-99.5 "/>
|
||||
<text text-anchor="middle" x="97.5" y="-90.3" font-family="Helvetica,sans-Serif" font-size="14.00">aborist mesh rotate</text>
|
||||
<text text-anchor="middle" x="97.5" y="-90.3" font-family="Helvetica,sans-Serif" font-size="14.00">arborist mesh rotate</text>
|
||||
<text text-anchor="middle" x="97.5" y="-75.3" font-family="Helvetica,sans-Serif" font-size="14.00">any current member</text>
|
||||
</g>
|
||||
</g>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
|
|
@ -1,4 +1,4 @@
|
|||
// aborist/mesh — group operator decision tree.
|
||||
// arborist/mesh — group operator decision tree.
|
||||
//
|
||||
// When a group steward (admin) faces a roster question, this diagram
|
||||
// shows which CLI verb solves it and what guarantee comes out the
|
||||
|
|
@ -19,10 +19,10 @@ digraph mesh_group_decisions {
|
|||
q_who [label="add, remove,\nor refresh?", shape=diamond, style="filled", fillcolor="#fff7d6"];
|
||||
q_admin [label="are you\nadmin in\ncurrent epoch?", shape=diamond, style="filled", fillcolor="#fff7d6"];
|
||||
|
||||
a_init [label="aborist mesh init --group <name>\nthen mesh enable\n(creates epoch 0; founder = sole admin)", shape=box, style="rounded,filled", fillcolor="#d6ffd6"];
|
||||
a_add [label="aborist mesh add\n--member-id <id>\n--sign-pub <hex>\n--dh-pub <hex>\n[--role admin]", shape=box, style="rounded,filled", fillcolor="#d6ffd6"];
|
||||
a_kick [label="aborist mesh kick\n--member-id <id>\n--reason '...'", shape=box, style="rounded,filled", fillcolor="#d6ffd6"];
|
||||
a_rotate [label="aborist mesh rotate\n--reason '...'\n(any current member)", shape=box, style="rounded,filled", fillcolor="#d6ffd6"];
|
||||
a_init [label="arborist mesh init --group <name>\nthen mesh enable\n(creates epoch 0; founder = sole admin)", shape=box, style="rounded,filled", fillcolor="#d6ffd6"];
|
||||
a_add [label="arborist mesh add\n--member-id <id>\n--sign-pub <hex>\n--dh-pub <hex>\n[--role admin]", shape=box, style="rounded,filled", fillcolor="#d6ffd6"];
|
||||
a_kick [label="arborist mesh kick\n--member-id <id>\n--reason '...'", shape=box, style="rounded,filled", fillcolor="#d6ffd6"];
|
||||
a_rotate [label="arborist mesh rotate\n--reason '...'\n(any current member)", shape=box, style="rounded,filled", fillcolor="#d6ffd6"];
|
||||
|
||||
g_admin [label="permission denied\n(only admins of the\ncurrent epoch may add or kick)", shape=box, style="rounded,filled", fillcolor="#ffe0e0"];
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@
|
|||
<g id="node6" class="node">
|
||||
<title>a_init</title>
|
||||
<path fill="#d6ffd6" stroke="black" d="M1104,-575.5C1104,-575.5 830,-575.5 830,-575.5 824,-575.5 818,-569.5 818,-563.5 818,-563.5 818,-534.5 818,-534.5 818,-528.5 824,-522.5 830,-522.5 830,-522.5 1104,-522.5 1104,-522.5 1110,-522.5 1116,-528.5 1116,-534.5 1116,-534.5 1116,-563.5 1116,-563.5 1116,-569.5 1110,-575.5 1104,-575.5"/>
|
||||
<text text-anchor="middle" x="967" y="-560.3" font-family="Helvetica,sans-Serif" font-size="14.00">aborist mesh init --group <name></text>
|
||||
<text text-anchor="middle" x="967" y="-560.3" font-family="Helvetica,sans-Serif" font-size="14.00">arborist mesh init --group <name></text>
|
||||
<text text-anchor="middle" x="967" y="-545.3" font-family="Helvetica,sans-Serif" font-size="14.00">then mesh enable</text>
|
||||
<text text-anchor="middle" x="967" y="-530.3" font-family="Helvetica,sans-Serif" font-size="14.00">(creates epoch 0; founder = sole admin)</text>
|
||||
</g>
|
||||
|
|
@ -77,7 +77,7 @@
|
|||
<g id="node9" class="node">
|
||||
<title>a_rotate</title>
|
||||
<path fill="#d6ffd6" stroke="black" d="M826,-299.5C826,-299.5 680,-299.5 680,-299.5 674,-299.5 668,-293.5 668,-287.5 668,-287.5 668,-258.5 668,-258.5 668,-252.5 674,-246.5 680,-246.5 680,-246.5 826,-246.5 826,-246.5 832,-246.5 838,-252.5 838,-258.5 838,-258.5 838,-287.5 838,-287.5 838,-293.5 832,-299.5 826,-299.5"/>
|
||||
<text text-anchor="middle" x="753" y="-284.3" font-family="Helvetica,sans-Serif" font-size="14.00">aborist mesh rotate</text>
|
||||
<text text-anchor="middle" x="753" y="-284.3" font-family="Helvetica,sans-Serif" font-size="14.00">arborist mesh rotate</text>
|
||||
<text text-anchor="middle" x="753" y="-269.3" font-family="Helvetica,sans-Serif" font-size="14.00">--reason '...'</text>
|
||||
<text text-anchor="middle" x="753" y="-254.3" font-family="Helvetica,sans-Serif" font-size="14.00">(any current member)</text>
|
||||
</g>
|
||||
|
|
@ -114,7 +114,7 @@
|
|||
<g id="node7" class="node">
|
||||
<title>a_add</title>
|
||||
<path fill="#d6ffd6" stroke="black" d="M180.5,-173C180.5,-173 61.5,-173 61.5,-173 55.5,-173 49.5,-167 49.5,-161 49.5,-161 49.5,-102 49.5,-102 49.5,-96 55.5,-90 61.5,-90 61.5,-90 180.5,-90 180.5,-90 186.5,-90 192.5,-96 192.5,-102 192.5,-102 192.5,-161 192.5,-161 192.5,-167 186.5,-173 180.5,-173"/>
|
||||
<text text-anchor="middle" x="121" y="-157.8" font-family="Helvetica,sans-Serif" font-size="14.00">aborist mesh add</text>
|
||||
<text text-anchor="middle" x="121" y="-157.8" font-family="Helvetica,sans-Serif" font-size="14.00">arborist mesh add</text>
|
||||
<text text-anchor="middle" x="121" y="-142.8" font-family="Helvetica,sans-Serif" font-size="14.00">--member-id <id></text>
|
||||
<text text-anchor="middle" x="121" y="-127.8" font-family="Helvetica,sans-Serif" font-size="14.00">--sign-pub <hex></text>
|
||||
<text text-anchor="middle" x="121" y="-112.8" font-family="Helvetica,sans-Serif" font-size="14.00">--dh-pub <hex></text>
|
||||
|
|
@ -131,7 +131,7 @@
|
|||
<g id="node8" class="node">
|
||||
<title>a_kick</title>
|
||||
<path fill="#d6ffd6" stroke="black" d="M384.5,-158C384.5,-158 265.5,-158 265.5,-158 259.5,-158 253.5,-152 253.5,-146 253.5,-146 253.5,-117 253.5,-117 253.5,-111 259.5,-105 265.5,-105 265.5,-105 384.5,-105 384.5,-105 390.5,-105 396.5,-111 396.5,-117 396.5,-117 396.5,-146 396.5,-146 396.5,-152 390.5,-158 384.5,-158"/>
|
||||
<text text-anchor="middle" x="325" y="-142.8" font-family="Helvetica,sans-Serif" font-size="14.00">aborist mesh kick</text>
|
||||
<text text-anchor="middle" x="325" y="-142.8" font-family="Helvetica,sans-Serif" font-size="14.00">arborist mesh kick</text>
|
||||
<text text-anchor="middle" x="325" y="-127.8" font-family="Helvetica,sans-Serif" font-size="14.00">--member-id <id></text>
|
||||
<text text-anchor="middle" x="325" y="-112.8" font-family="Helvetica,sans-Serif" font-size="14.00">--reason '...'</text>
|
||||
</g>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
|
@ -1,4 +1,4 @@
|
|||
// aborist/mesh — cryptographic identity stack per peer.
|
||||
// arborist/mesh — cryptographic identity stack per peer.
|
||||
//
|
||||
// Each peer carries two keypairs and one shared epoch secret.
|
||||
// Keys are raw 32-byte forms; everything lives in the standard SQLite db.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// aborist/mesh — per-member secret envelope.
|
||||
// arborist/mesh — per-member secret envelope.
|
||||
//
|
||||
// Every epoch carries one fresh 32-byte symmetric secret. That single
|
||||
// secret is wrapped N times, once per member, using ECDH between the
|
||||
|
|
|
|||
40
docs/mesh.md
40
docs/mesh.md
|
|
@ -1,7 +1,7 @@
|
|||
# Mesh — multiplayer aborist
|
||||
# Mesh — multiplayer arborist
|
||||
|
||||
Aborist on a single laptop is a content-addressed forest of documents.
|
||||
Aborist across many laptops is a *gossip-able* forest. Two peers that
|
||||
Arborist on a single laptop is a content-addressed forest of documents.
|
||||
Arborist across many laptops is a *gossip-able* forest. Two peers that
|
||||
ingest the same Wikipedia dump compute bit-identical `document_root`s,
|
||||
so any peer can verify another peer's claim by re-deriving the hash
|
||||
locally. Mesh is wire-and-trust scaffolding wrapped around that fact.
|
||||
|
|
@ -20,10 +20,10 @@ Lookups, ingests, distillations, and Q&A run identically with or without
|
|||
mesh. Initialization is two commands and is reversible.
|
||||
|
||||
```
|
||||
aborist mesh init --group myteam # mint Ed25519 + X25519 keys; create epoch 0
|
||||
aborist mesh enable # flip gating flag on
|
||||
aborist mesh status # always-safe inspection
|
||||
aborist mesh disable # flag back off; keys + history stay on disk
|
||||
arborist mesh init --group myteam # mint Ed25519 + X25519 keys; create epoch 0
|
||||
arborist mesh enable # flip gating flag on
|
||||
arborist mesh status # always-safe inspection
|
||||
arborist mesh disable # flag back off; keys + history stay on disk
|
||||
```
|
||||
|
||||
## Identity stack
|
||||
|
|
@ -71,14 +71,14 @@ between their own X25519 priv and a rotator's X25519 pub, then unwraps
|
|||
their own slot. An evicted peer simply has no slot, so unwrap raises
|
||||
`ValueError` — they are opaque to gossip from epoch+1 onward by design.
|
||||
|
||||
Code: `aborist/mesh/state.py::_wrap_secret_for_members` and
|
||||
Code: `arborist/mesh/state.py::_wrap_secret_for_members` and
|
||||
`unwrap_secret_for_self`.
|
||||
|
||||
## What flows between peers
|
||||
|
||||

|
||||
|
||||
Aborist is content-addressed, so identifiers are short and bodies are
|
||||
Arborist is content-addressed, so identifiers are short and bodies are
|
||||
optional. A typical sync round looks like:
|
||||
|
||||
1. **alice** signs and sends `ANNOUNCE_ROOT(document_root, source_uri,
|
||||
|
|
@ -120,10 +120,10 @@ is rejected; an admin who wants out runs `mesh disable` instead.
|
|||
|
||||
```
|
||||
# bob mints his keys and shares pubs (out-of-band: signal, in person, signed file).
|
||||
aborist mesh init --group myteam --member-id bob # bob's machine
|
||||
arborist mesh init --group myteam --member-id bob # bob's machine
|
||||
|
||||
# alice (admin) enrolls bob at her machine:
|
||||
aborist mesh add --member-id bob \
|
||||
arborist mesh add --member-id bob \
|
||||
--sign-pub <bob_sign_pub_hex> \
|
||||
--dh-pub <bob_dh_pub_hex>
|
||||
# epoch bumps. fresh secret wrapped to alice + bob.
|
||||
|
|
@ -135,7 +135,7 @@ design, joining doesn't grant retroactive access.
|
|||
### Kick a member (admin only)
|
||||
|
||||
```
|
||||
aborist mesh kick --member-id dave --reason "left team 2026-04-28"
|
||||
arborist mesh kick --member-id dave --reason "left team 2026-04-28"
|
||||
```
|
||||
|
||||
Epoch bumps. New secret wrapped to everyone *except* dave. Dave's prior
|
||||
|
|
@ -148,7 +148,7 @@ Cannot leave a roster with no admins. Promote first, then kick.
|
|||
### Rotate the secret (any member)
|
||||
|
||||
```
|
||||
aborist mesh rotate --reason "scheduled monthly hygiene"
|
||||
arborist mesh rotate --reason "scheduled monthly hygiene"
|
||||
```
|
||||
|
||||
Same roster, fresh secret. Use on suspected secret leak when no
|
||||
|
|
@ -203,19 +203,19 @@ Mesh does not defend against:
|
|||
|
||||
## Related code
|
||||
|
||||
- `aborist/mesh/__init__.py` — module entrypoint, public API
|
||||
- `aborist/mesh/crypto.py` — Ed25519 + X25519 + ChaCha20-Poly1305 wrappers
|
||||
- `aborist/mesh/state.py` — DB writes + audit chain hooks; epoch rotation
|
||||
- `aborist/mesh/members.py` — `add_member`, `kick_member`, `scheduled_rotate`
|
||||
- `aborist/cli.py` — `_cmd_mesh_*` argparse handlers
|
||||
- `aborist/store.py` — `mesh_identity`, `mesh_roster`, `mesh_epochs` schema
|
||||
- `arborist/mesh/__init__.py` — module entrypoint, public API
|
||||
- `arborist/mesh/crypto.py` — Ed25519 + X25519 + ChaCha20-Poly1305 wrappers
|
||||
- `arborist/mesh/state.py` — DB writes + audit chain hooks; epoch rotation
|
||||
- `arborist/mesh/members.py` — `add_member`, `kick_member`, `scheduled_rotate`
|
||||
- `arborist/cli.py` — `_cmd_mesh_*` argparse handlers
|
||||
- `arborist/store.py` — `mesh_identity`, `mesh_roster`, `mesh_epochs` schema
|
||||
|
||||
## Related docs
|
||||
|
||||
- `README.md` — top-level overview, ingest flow, single-peer Q&A
|
||||
- `CLAUDE.md` — schema invariants, conventions, audit chain rules
|
||||
- `~/git/proxy.unturf.com/pkg/verified/merkle.go` — fox's Go merkle
|
||||
reference. Aborist's Python port mirrors the conventions.
|
||||
reference. Arborist's Python port mirrors the conventions.
|
||||
|
||||
## Rendering the diagrams
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Date:** 2026-05-02
|
||||
**Endpoint:** `https://hermes.ai.unturf.com/v1` (Hermes-3-Llama-3.1-8B-FP8-Dynamic, vLLM, 82K ctx)
|
||||
**Corpus:** Wikipedia 2003-05-16 cur snapshot, sharded under `~/.aborist/shards`
|
||||
**Corpus:** Wikipedia 2003-05-16 cur snapshot, sharded under `~/.arborist/shards`
|
||||
|
||||
Two sweeps landed today, each 71 questions × 3 modes:
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ Substrate-side (between `2026-05-02T11:31Z` and `2026-05-02T15:07Z`):
|
|||
|
||||
- **Sprint 1b** — per-mode `max_context_chars`. Bench's recommended-context-budget table flows back into `DEFAULT_QUERY_POLICY["max_context_chars_by_mode"]`: quote 24 KB, pointer 24 KB, JSON 48 KB. Folds into `governance_policy_hash`.
|
||||
- **Sprint 2** — pointer Rule 9: chunk-specificity instruction added to the lattice-pointer system prompt.
|
||||
- **DRY collapse** — the four lattice prompts (system + grounding × pointer + JSON) lifted to `aborist/qa/prompts.py` as a single source of truth, imported by both `runner.DEFAULT_POLICY` and `query.DEFAULT_QUERY_POLICY`.
|
||||
- **DRY collapse** — the four lattice prompts (system + grounding × pointer + JSON) lifted to `arborist/qa/prompts.py` as a single source of truth, imported by both `runner.DEFAULT_POLICY` and `query.DEFAULT_QUERY_POLICY`.
|
||||
- **httpx persistent client** — the chat-completion path used to construct a fresh `httpx.Client` per call, paying a TLS handshake every request. Move to `__init__`; HTTP/1.1 keep-alive across calls. Save 1-2 min on a 426-call bench.
|
||||
- **Sample-level shuffled bench scheduling** — every `(question, mode, sample_idx)` is a task, shuffled with `--seed`, dispatched concurrently. Per-cell `Lock` dict serializes burn-then-write on the shared cache_key. True i.i.d. n=3 variance; vLLM batcher fed a diverse request stream.
|
||||
- **Bench `--resume`** — read existing JSONL, skip done tasks, append fresh rows. Stop/start-able.
|
||||
|
|
@ -152,7 +152,7 @@ vLLM saturates at c=3-4 on this endpoint. More concurrent requests fill the batc
|
|||
|
||||
Across both benches: **2 errors at 11:31Z, 6 errors at 15:07Z** — all on the same question (`tell me about the roman empire`). Lone UTF-16 surrogates in Wikipedia chunk content, two distinct paths:
|
||||
|
||||
- v1 (`3b91223`) — hardened SHA-256 hashers on the OUTPUT side (`aborist/qa/dag.py`, `keys.py`, `evidence.py`, `store.py` audit chain) with `errors='surrogatepass'` so the run-DAG roots survive surrogate-bearing model output.
|
||||
- v1 (`3b91223`) — hardened SHA-256 hashers on the OUTPUT side (`arborist/qa/dag.py`, `keys.py`, `evidence.py`, `store.py` audit chain) with `errors='surrogatepass'` so the run-DAG roots survive surrogate-bearing model output.
|
||||
- v2 (`41d1d9b`) — scrubs message content INSIDE `OpenAICompatibleClient.chat_completion` before httpx's outbound JSON encode. The corpus chunk text was the path; httpx's `.encode('utf-8')` on the request body raised before the call left the client.
|
||||
|
||||
Verified: `tell me about the roman empire` under `claim_lattice` now classifies HYBRID 5/7 instead of erroring. Next bench will land 0 errors.
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ not a model self-grade.
|
|||
|
||||
- **Status**: ✓ enforced.
|
||||
- **Code anchors**:
|
||||
`aborist/qa/verify.py:verify_quotes`,
|
||||
`arborist/qa/verify.py:verify_quotes`,
|
||||
`verify_claim_lattice`, `verify_claim_lattice_json` — pure functions
|
||||
with no chat-client parameter.
|
||||
- **Pinning tests**: `tests/test_verify*.py`,
|
||||
|
|
@ -43,9 +43,9 @@ never types the quote string itself.
|
|||
|
||||
- **Status**: ✓ enforced for lattice modes.
|
||||
- **Code anchors**:
|
||||
`aborist/qa/parse_claims.py:parse_pointer_claims`,
|
||||
`aborist/qa/verify.py:CLAIM_LATTICE_JSON_SCHEMA`,
|
||||
`aborist/qa/runner.py` (system prompts).
|
||||
`arborist/qa/parse_claims.py:parse_pointer_claims`,
|
||||
`arborist/qa/verify.py:CLAIM_LATTICE_JSON_SCHEMA`,
|
||||
`arborist/qa/runner.py` (system prompts).
|
||||
- **Pinning tests**: `tests/test_claim_lattice.py`,
|
||||
`tests/test_verify_json.py`.
|
||||
- **Bench signal**: rows with `answer_mode ∈
|
||||
|
|
@ -66,9 +66,9 @@ frame and discarding the others.
|
|||
quantifier classifier + #000010 metacognition QuestionState)
|
||||
landed 2026-05-03. Answer-side multi-frame compilation still
|
||||
pending.
|
||||
- **Code anchors**: `aborist/qa/quantifier.py:classify_question_quantifier`
|
||||
(#000008), `aborist/qa/metacognition.py:preflight_question`
|
||||
(#000010), `aborist/qa/dag.py:preflight_node_hash` /
|
||||
- **Code anchors**: `arborist/qa/quantifier.py:classify_question_quantifier`
|
||||
(#000008), `arborist/qa/metacognition.py:preflight_question`
|
||||
(#000010), `arborist/qa/dag.py:preflight_node_hash` /
|
||||
`build_preflight_node_payload` (#000009 — five nested CTI
|
||||
clauses bind into the run-DAG `preflight` stage). Future:
|
||||
runtime-side polarity contract emission for multi-frame answers.
|
||||
|
|
@ -96,17 +96,17 @@ the **preflight contract** governing the run (#000009 — guard /
|
|||
cap / reminder / reject decisions hash-bound).
|
||||
|
||||
- **Status**: ¾ — `evidence_map_root` lives in the run-DAG
|
||||
(`aborist/qa/dag.py`); `retrieval_plan_hash` landed via #000001.
|
||||
(`arborist/qa/dag.py`); `retrieval_plan_hash` landed via #000001.
|
||||
Preflight stage (#000009) added the upstream control commitment:
|
||||
classifier output + answer/prompt/evidence contracts +
|
||||
`governance_policy_hash` / `model_profile_hash` references all
|
||||
fold into a single `preflight` Merkle leaf. Quote mode now 7→8
|
||||
stages; pointer-mode CTI 9→10 stages; reject-broad path emits a
|
||||
3-stage minimal DAG (`question → preflight → final_label`).
|
||||
- **Code anchors**: `aborist/qa/dag.py:build_run_dag` (legacy +
|
||||
- **Code anchors**: `arborist/qa/dag.py:build_run_dag` (legacy +
|
||||
preflight-extended shapes), `build_reject_run_dag` (3-stage),
|
||||
`preflight_node_hash` / `build_preflight_node_payload` (5-clause
|
||||
payload), `aborist/qa/evidence.py:evidence_map_root`.
|
||||
payload), `arborist/qa/evidence.py:evidence_map_root`.
|
||||
- **Pinning tests**: `tests/test_dag.py` (24 tests including
|
||||
preflight stage + reject path), `tests/test_evidence.py`.
|
||||
- **Open ticket**: [#000001 Retrieval-keywords audit gap](ticket-000001-retrieval-keywords-audit-gap.md)
|
||||
|
|
@ -125,14 +125,14 @@ citation coverage, pointer-count cap, lexical anchor warrant).
|
|||
No NLI, no embeddings, no model self-grading.
|
||||
|
||||
- **Status**: ✓ enforced.
|
||||
- **Code anchors**: `aborist/qa/verify.py:verify_claim_lattice` —
|
||||
- **Code anchors**: `arborist/qa/verify.py:verify_claim_lattice` —
|
||||
seven hard checks documented inline.
|
||||
- **Pinning tests**: `tests/test_claim_lattice.py` (per-rule
|
||||
coverage), `tests/test_verify_json.py`,
|
||||
`tests/test_verify.py`.
|
||||
- **Bench signal**: every row carries `verifier_method ∈ {quote,
|
||||
span, entity, paraphrase, claim_lattice, none}` (CHECK constraint
|
||||
in `aborist/store.py`).
|
||||
in `arborist/store.py`).
|
||||
|
||||
### D6 — General anchor-class warrant before semantic NLI
|
||||
|
||||
|
|
@ -147,8 +147,8 @@ exists in the proof path.
|
|||
- **Status**: ½ — relation-shape only. Entity-list / why-cause /
|
||||
when-date shapes pass through unchecked.
|
||||
- **Code anchors**:
|
||||
`aborist/qa/verify.py:_extract_anchor_candidates`,
|
||||
`aborist/qa/verify.py:_question_is_relation_shape`.
|
||||
`arborist/qa/verify.py:_extract_anchor_candidates`,
|
||||
`arborist/qa/verify.py:_question_is_relation_shape`.
|
||||
- **Pinning tests**: `tests/test_claim_lattice.py` covers
|
||||
relation-shape; entity-list / cause / date shapes have no warrant
|
||||
test today.
|
||||
|
|
@ -175,7 +175,7 @@ stays `STRICT`/`HYBRID`/`UNGROUNDED` so v9.8 cache_key invariants
|
|||
hold.
|
||||
|
||||
- **Status**: ✓ enforced for lattice modes.
|
||||
- **Code anchors**: `aborist/cli.py:_render_audit_label`.
|
||||
- **Code anchors**: `arborist/cli.py:_render_audit_label`.
|
||||
- **Pinning tests**: `tests/test_cli_render.py` —
|
||||
`test_render_label_strict_in_claim_lattice_becomes_evidence_linked`
|
||||
and siblings.
|
||||
|
|
@ -253,7 +253,7 @@ passed at the original snapshot.
|
|||
Three additional tickets landed extending D1, D3, and D4:
|
||||
|
||||
- **#000008** (closed `4f2b5a6`, 2026-05-03) — Broad-quantifier
|
||||
preflight guard. New modules `aborist/qa/quantifier.py`,
|
||||
preflight guard. New modules `arborist/qa/quantifier.py`,
|
||||
`model_profiles.py`, `quantifier_reminder.py`. Seven new
|
||||
policy fields fold into `governance_policy_hash`; six-level
|
||||
disable hierarchy. Defaults flipped (after n=5 verification):
|
||||
|
|
@ -261,7 +261,7 @@ Three additional tickets landed extending D1, D3, and D4:
|
|||
opt-in with JSON-only allowlist.
|
||||
|
||||
- **#000010** (closed, 2026-05-03) — Meta-Cognition Preflight
|
||||
Guard. New module `aborist/qa/metacognition.py` with
|
||||
Guard. New module `arborist/qa/metacognition.py` with
|
||||
`QuestionState` dataclass + four pure detectors (temporal,
|
||||
contradiction, false-premise-lite, out-of-corpus). Six new
|
||||
policy fields, all default-on except
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ aren't even queryable to know which records to re-run.
|
|||
|
||||
### 1.2 What gets logged today
|
||||
|
||||
The `run_dag` retrieval stage at `aborist/qa/dag.py:155` hashes only the
|
||||
The `run_dag` retrieval stage at `arborist/qa/dag.py:155` hashes only the
|
||||
`sources_summary` (output):
|
||||
|
||||
```python
|
||||
|
|
@ -164,27 +164,27 @@ question." The 8-dim `cache_key` invariant stays intact.
|
|||
|
||||
## 3. Implementation sketch (when scheduled)
|
||||
|
||||
1. **`aborist/qa/dag.py`** — `build_run_dag` gains `retrieval_inputs`
|
||||
1. **`arborist/qa/dag.py`** — `build_run_dag` gains `retrieval_inputs`
|
||||
parameter; embeds it into the retrieval-stage hash. Backward-compat:
|
||||
when `retrieval_inputs` is None, fall back to the current
|
||||
sources-summary-only hash.
|
||||
|
||||
2. **`aborist/qa/query.py`** — at the existing run_dag construction
|
||||
2. **`arborist/qa/query.py`** — at the existing run_dag construction
|
||||
site, pass `{"question": ..., "retrieval_keywords": ...,
|
||||
"top_k": ..., "over_fetch": ..., "max_context_chars": ...}` as
|
||||
`retrieval_inputs`.
|
||||
|
||||
3. **`aborist/store.py`** — schema migration: add
|
||||
3. **`arborist/store.py`** — schema migration: add
|
||||
`retrieval_keywords TEXT` (nullable) to `providence_cache`. Mirror
|
||||
the existing `_rebuild_providence_cache_*` pattern only if a CHECK
|
||||
constraint or column-default constraint requires it (otherwise a
|
||||
plain `ALTER TABLE ... ADD COLUMN` suffices).
|
||||
|
||||
4. **`aborist/qa/query.py`** persist site — include `retrieval_keywords`
|
||||
4. **`arborist/qa/query.py`** persist site — include `retrieval_keywords`
|
||||
in the INSERT.
|
||||
|
||||
5. **`aborist/qa/verify.py`** + relevant CLI render — surface keywords
|
||||
in `aborist providence` output and `aborist inspect` so an operator
|
||||
5. **`arborist/qa/verify.py`** + relevant CLI render — surface keywords
|
||||
in `arborist providence` output and `arborist inspect` so an operator
|
||||
can see at a glance whether a record was retrieval-augmented.
|
||||
|
||||
6. **Tests:**
|
||||
|
|
@ -233,16 +233,16 @@ question." The 8-dim `cache_key` invariant stays intact.
|
|||
|
||||
**Closed 2026-05-02.** Run-DAG binding scope landed:
|
||||
|
||||
- New module `aborist/qa/retrieval_plan.py` — `RetrievalPlan`
|
||||
- New module `arborist/qa/retrieval_plan.py` — `RetrievalPlan`
|
||||
dataclass + `retrieval_plan_hash` function. Captures
|
||||
`retrieval_keywords`, `top_k`, `over_fetch`, `max_context_chars`,
|
||||
`shard_ids`. SHA-256 over canonical-JSON; deterministic per call.
|
||||
- `aborist/qa/dag.py:build_run_dag` accepts `retrieval_plan_hash`
|
||||
- `arborist/qa/dag.py:build_run_dag` accepts `retrieval_plan_hash`
|
||||
parameter. When provided, the retrieval stage hash binds BOTH
|
||||
the plan (input) and `sources_summary` (output). When omitted,
|
||||
falls back to the historical sources-summary-only hash so
|
||||
pre-#000001 records keep their `run_dag_root` values stable.
|
||||
- `aborist/qa/query.py` constructs the plan per call from the
|
||||
- `arborist/qa/query.py` constructs the plan per call from the
|
||||
query-time inputs (keywords from `--retrieval-keywords`,
|
||||
`top_k` / `over_fetch` / `max_context_chars` from policy +
|
||||
args) plus the resolved shard set, computes the hash, and
|
||||
|
|
@ -791,7 +791,7 @@ J — Retrieval Plan Provenance Binding
|
|||
|
||||
### A.10 Concrete diff
|
||||
|
||||
#### `aborist/qa/retrieval.py`
|
||||
#### `arborist/qa/retrieval.py`
|
||||
|
||||
Return a structured object:
|
||||
|
||||
|
|
@ -822,14 +822,14 @@ class RetrievalPlan:
|
|||
}
|
||||
```
|
||||
|
||||
#### `aborist/qa/hash.py`
|
||||
#### `arborist/qa/hash.py`
|
||||
|
||||
```python
|
||||
def retrieval_plan_hash(plan: RetrievalPlan) -> str:
|
||||
return hash_json("aborist-retrieval-plan-v1", plan.canonical())
|
||||
return hash_json("arborist-retrieval-plan-v1", plan.canonical())
|
||||
```
|
||||
|
||||
#### `aborist/qa/dag.py`
|
||||
#### `arborist/qa/dag.py`
|
||||
|
||||
Change retrieval node from:
|
||||
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ The additive insight:
|
|||
|
||||
Three new pieces, each scoped tight:
|
||||
|
||||
### 4.1 Frame detector (`aborist/qa/frame.py`)
|
||||
### 4.1 Frame detector (`arborist/qa/frame.py`)
|
||||
|
||||
Given a question + retrieved sources, detect the reference frame:
|
||||
|
||||
|
|
@ -242,7 +242,7 @@ To add when this ticket lands:
|
|||
|
||||
**Closed 2026-05-02.** Landed via:
|
||||
|
||||
- New module `aborist/qa/frame.py` — `FrameDetection` dataclass
|
||||
- New module `arborist/qa/frame.py` — `FrameDetection` dataclass
|
||||
+ `detect_frame(question, sources, phrase_match_roots)`. Detects
|
||||
reference-frame queries via the conjunction of (phrase route
|
||||
fired) AND (phrase-matched source is a reference work — title
|
||||
|
|
@ -251,7 +251,7 @@ To add when this ticket lands:
|
|||
`protagonist` / `plot`). Distinct-marker count (not total) keeps
|
||||
the heuristic robust against single-marker repetition (e.g. a
|
||||
history article saying "novel approach" twice doesn't trip).
|
||||
- `aborist/qa/query.py` calls `detect_frame` for lattice modes,
|
||||
- `arborist/qa/query.py` calls `detect_frame` for lattice modes,
|
||||
using the article LEAD (chunk_idx=0, post-wikitext-strip) as the
|
||||
body sample so fiction markers cluster where they appear on
|
||||
Wikipedia (lead paragraph).
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ covers that path separately).
|
|||
|
||||
## 1. Problem statement
|
||||
|
||||
Today's warrant-lite (`aborist/qa/warrant.py:warrant_check`) covers
|
||||
Today's warrant-lite (`arborist/qa/warrant.py:warrant_check`) covers
|
||||
two anchor classes:
|
||||
|
||||
- **Proper-noun anchors** (relation-shape questions): for
|
||||
|
|
@ -128,7 +128,7 @@ shape to constrain it.
|
|||
|
||||
### 3.1 Question-shape detector extension
|
||||
|
||||
`aborist/qa/warrant.py` already has `_question_is_relation_shape`.
|
||||
`arborist/qa/warrant.py` already has `_question_is_relation_shape`.
|
||||
Add:
|
||||
|
||||
```python
|
||||
|
|
@ -251,7 +251,7 @@ To add when this ticket lands:
|
|||
## 6. Status
|
||||
|
||||
**Closed 2026-05-02.** Landed via per-shape detectors + extractors
|
||||
in `aborist/qa/warrant.py`:
|
||||
in `arborist/qa/warrant.py`:
|
||||
|
||||
- `_question_is_entity_list_shape` / `_question_is_count_shape` /
|
||||
`_question_is_why_shape` regex detectors.
|
||||
|
|
|
|||
|
|
@ -110,13 +110,13 @@ ladder migration render under the new ladder identically.
|
|||
|
||||
## 5. Implementation sketch
|
||||
|
||||
1. **Verifier enhancement** (`aborist/qa/verify.py`): both
|
||||
1. **Verifier enhancement** (`arborist/qa/verify.py`): both
|
||||
`verify_claim_lattice` and `verify_claim_lattice_json` add a
|
||||
`verifier_steps_ran: list[str]` field on the verdict dict
|
||||
listing which verifier paths fired (`pointer_resolve`,
|
||||
`source_role`, `coverage`, `pointer_cap`, `warrant`,
|
||||
`lazy_anchor_demote`).
|
||||
2. **Renderer mapping** (`aborist/cli.py:_render_audit_label`):
|
||||
2. **Renderer mapping** (`arborist/cli.py:_render_audit_label`):
|
||||
add the four-rung mapping function gated on
|
||||
`verifier_method.startswith("claim_lattice")`.
|
||||
3. **Bench update** (`bench/qa_sweep.py`): per-row
|
||||
|
|
@ -159,7 +159,7 @@ ladder migration render under the new ladder identically.
|
|||
**Closed 2026-05-02.** Landed via:
|
||||
|
||||
- `_render_audit_label(audit_mode, verifier_method, violations)`
|
||||
in `aborist/cli.py` — three-arg signature; `violations` defaults
|
||||
in `arborist/cli.py` — three-arg signature; `violations` defaults
|
||||
to `None` for backward-compat with callers that don't have the
|
||||
list yet.
|
||||
- `_ladder_rung_for_lattice` helper computes the rung from
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ discipline (POINTER-LINKED → ANCHOR-WARRANTED → EVIDENCE-WARRANTED
|
|||
|
||||
1. Three random words from `/usr/share/dict/words` (`^[a-z]{5,12}$`).
|
||||
2. Hermes @ temp=0.8 wove a creative question paragraph.
|
||||
3. Aborist answered via `claim_lattice` mode (full pipeline).
|
||||
3. Arborist answered via `claim_lattice` mode (full pipeline).
|
||||
4. Append journey to `bench/emergent_log.jsonl`.
|
||||
|
||||
Aggregate distribution:
|
||||
|
|
@ -297,7 +297,7 @@ language-discourse vocabulary. The model wove a definition-style
|
|||
answer using overlapping vocabulary that the verifier's per-claim
|
||||
citation-coverage check passed.
|
||||
|
||||
`aborist inspect --cache-key 08dbd2c1...` confirms STRICT 1/1
|
||||
`arborist inspect --cache-key 08dbd2c1...` confirms STRICT 1/1
|
||||
verified, no unverified spans, no violations. The verifier
|
||||
genuinely accepted the claim.
|
||||
|
||||
|
|
@ -347,7 +347,7 @@ that the lexical Rule-5 check cannot catch alone.
|
|||
|
||||
1. **Rule 9 — SUBJECT_TOKENS_ABSENT detector — landed inline (no
|
||||
separate ticket).** New per-claim check in
|
||||
`aborist/qa/verify.py`. For each resolving claim, compute the
|
||||
`arborist/qa/verify.py`. For each resolving claim, compute the
|
||||
set of content tokens shared by question AND claim
|
||||
(`_parroted_subject_tokens_absent`). For each, check substring
|
||||
presence in the union of cited evidence spans (lower-cased,
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ breadth metric for non-hyphen queries.
|
|||
- Zero blast radius for non-hyphen queries.
|
||||
|
||||
**Cons:**
|
||||
- Body-FTS still uses `aborist/search/fts5.py:_query_tokens` (a
|
||||
- Body-FTS still uses `arborist/search/fts5.py:_query_tokens` (a
|
||||
separate function) and stays on AND-mode `bi AND polar AND rare`.
|
||||
Body-FTS won't pull in `Bipolar disorder` directly. Title-route +
|
||||
accept-path 5 + rerank carries the recall load. Acceptable: title
|
||||
|
|
@ -140,7 +140,7 @@ bench shows it's worth the version-bump cost.
|
|||
|
||||
## 3. Implementation sketch
|
||||
|
||||
1. **`aborist/qa/query.py`** — new module-level constant
|
||||
1. **`arborist/qa/query.py`** — new module-level constant
|
||||
`_HYPHEN_RUN_RE = re.compile(r"[A-Za-z][A-Za-z0-9]*(?:-[A-Za-z][A-Za-z0-9]*)+")`
|
||||
plus pure helper:
|
||||
|
||||
|
|
@ -234,7 +234,7 @@ bench shows it's worth the version-bump cost.
|
|||
|
||||
Closed 2026-05-02. Landed Option A as designed:
|
||||
|
||||
- `aborist/qa/query.py` — `_HYPHEN_RUN_RE` constant +
|
||||
- `arborist/qa/query.py` — `_HYPHEN_RUN_RE` constant +
|
||||
`_hyphen_fold_variants(s)` helper. `_title_query_tokens(s)`
|
||||
additively merges the variants. `_filter_by_title_relevance`
|
||||
gains optional `hyphen_fold_anchors` kwarg + accept-path 5
|
||||
|
|
@ -242,8 +242,8 @@ Closed 2026-05-02. Landed Option A as designed:
|
|||
when title-breadth fails). `_rerank` threads the kwarg.
|
||||
`_search_corpus` caller computes
|
||||
`_hyphen_fold_variants(retrieval_query)` and passes through.
|
||||
- `aborist/qa/query.py:DEFAULT_QUERY_POLICY` and
|
||||
`aborist/qa/runner.py:DEFAULT_POLICY` — `hyphen_fold_v1: True`
|
||||
- `arborist/qa/query.py:DEFAULT_QUERY_POLICY` and
|
||||
`arborist/qa/runner.py:DEFAULT_POLICY` — `hyphen_fold_v1: True`
|
||||
marker. Folds into `governance_policy_hash` so records
|
||||
produced under the new rule cache-split cleanly.
|
||||
- `tests/test_query.py` — 4 new tests:
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ intensity (Section 2). Fox sketched it as:
|
|||
|
||||
The categories below derive from formal-semantics quantifier theory
|
||||
(Mostowski generalized quantifiers; Barwise-Cooper; Partee D- vs A-
|
||||
quantifiers) intersected with the operational axis aborist actually
|
||||
quantifiers) intersected with the operational axis arborist actually
|
||||
needs: **expected number of claims in the answer**. Categories that
|
||||
don't change the expected answer length are dropped from this table
|
||||
and surfaced as adjacent dimensions in Section 2.1.
|
||||
|
|
@ -114,7 +114,7 @@ ladder, and may warrant their own ticket later.
|
|||
modify a numeric bound (`about ten`) — fold into the
|
||||
SMALL_NUM_EXPLICIT or COMPARATIVE_BOUND rung with the bound.
|
||||
|
||||
### 2.2 Why these specific categories matter for aborist
|
||||
### 2.2 Why these specific categories matter for arborist
|
||||
|
||||
Each new rung names a *distinct expected-answer-length distribution*:
|
||||
|
||||
|
|
@ -226,7 +226,7 @@ Original (superseded):
|
|||
|
||||
Land Option D. Concrete plan:
|
||||
|
||||
1. New module `aborist/qa/quantifier.py` with
|
||||
1. New module `arborist/qa/quantifier.py` with
|
||||
`classify_question_quantifier(question: str) -> dict` returning
|
||||
`{"intensity": "ALL"|"MANY"|"FEW"|"SMALL_NUM"|"SINGULAR"|"ABSENT",
|
||||
"matched_token": str, "explicit_count": int | None}`. Pure
|
||||
|
|
@ -240,9 +240,9 @@ Land Option D. Concrete plan:
|
|||
across the board, so opting in costs nothing).
|
||||
3. Per-model profile registry. `model_profile_hash` (already in the
|
||||
8-dim cache key) gets a quantifier-cap profile attached. The
|
||||
profile lives in `aborist/qa/model_profiles.py` (new file).
|
||||
4. Preflight wiring in `aborist/qa/runner.py:ask` and
|
||||
`aborist/qa/query.py:query`: classify the question, look up the
|
||||
profile lives in `arborist/qa/model_profiles.py` (new file).
|
||||
4. Preflight wiring in `arborist/qa/runner.py:ask` and
|
||||
`arborist/qa/query.py:query`: classify the question, look up the
|
||||
intensity-keyed cap from the model profile, override the
|
||||
`claim_lattice_max_claims_per_answer` for this call.
|
||||
5. Optional reminder injection in the prompt builder (gated on
|
||||
|
|
@ -366,7 +366,7 @@ Originally tracked here as a gap — closed in this same session.
|
|||
|
||||
Landed:
|
||||
|
||||
- `aborist/qa/query.py:2580-2592` — `format_collapsed` and
|
||||
- `arborist/qa/query.py:2580-2592` — `format_collapsed` and
|
||||
`raw_answer` surfaced on the `query()` result dict.
|
||||
- `bench/qa_sweep.py:_run_one` — three new fields per row:
|
||||
`format_collapsed`, `violation_kinds` (sorted unique kind
|
||||
|
|
@ -428,7 +428,7 @@ enhancement, not blocking.
|
|||
MANY? The "tell me all there is to know" precedent (york-england
|
||||
case, ticket #000006) suggests yes — operationally it produces
|
||||
the same enumeration pressure.
|
||||
- Does the per-model profile belong in `aborist/qa/model_profiles.py`
|
||||
- Does the per-model profile belong in `arborist/qa/model_profiles.py`
|
||||
or extend the existing `model_profile_hash` derivation in
|
||||
`keys.py`? Initial answer: new file, hash-derived from the profile
|
||||
dict so changing a cap invalidates prior records.
|
||||
|
|
@ -441,7 +441,7 @@ enhancement, not blocking.
|
|||
surfaces through the existing TOO_MANY_CLAIMS violation path on
|
||||
the audit-line tail.
|
||||
- This ticket does NOT touch retrieval. Quantifier guard runs at
|
||||
policy layer only; retrieval pipeline (`aborist/qa/query.py`
|
||||
policy layer only; retrieval pipeline (`arborist/qa/query.py`
|
||||
Sections 1–9) stays untouched.
|
||||
|
||||
## 8. Status
|
||||
|
|
@ -476,9 +476,9 @@ Implementation commit chain (all on `origin/main`):
|
|||
|-----------|-------|----------------------------------------------------------|-------|
|
||||
| `2ffed00` | 0 | bench-harness extension (FC rate, violation kinds) | +5 |
|
||||
| `5e8d662` | 0.x | bench-row telemetry (pointer/bracket/profile fields) | +5 |
|
||||
| `926b05e` | 1 | pure quantifier classifier `aborist/qa/quantifier.py` | +61 |
|
||||
| `84d5b5c` | 2 | model-profile caps `aborist/qa/model_profiles.py` | +19 |
|
||||
| `6f90f21` | 3 | reminder injection `aborist/qa/quantifier_reminder.py` | +19 |
|
||||
| `926b05e` | 1 | pure quantifier classifier `arborist/qa/quantifier.py` | +61 |
|
||||
| `84d5b5c` | 2 | model-profile caps `arborist/qa/model_profiles.py` | +19 |
|
||||
| `6f90f21` | 3 | reminder injection `arborist/qa/quantifier_reminder.py` | +19 |
|
||||
| `5a60e85` | 4 | CLI flags + 4 violation kinds + reject-broad early-return | +16 |
|
||||
|
||||
Total: **906 tests passing (120 new); 36 skipped.**
|
||||
|
|
@ -503,7 +503,7 @@ quantifier_guard_modes ["claim_lattice_pointer",
|
|||
reject_broad, caps_by_intensity, guard_modes, plus Phase 0
|
||||
format_collapse_check_enabled)
|
||||
4. Per-mode: `quantifier_guard_modes` (quote opts out by default)
|
||||
5. Per-model: `aborist/qa/model_profiles.py` PROFILES dict
|
||||
5. Per-model: `arborist/qa/model_profiles.py` PROFILES dict
|
||||
6. Master via governance: all 7 quantifier policy fields fold into
|
||||
`_VERIFIER_POLICY_FIELDS` so flipping any of them invalidates
|
||||
prior cache records on lookup
|
||||
|
|
@ -511,7 +511,7 @@ quantifier_guard_modes ["claim_lattice_pointer",
|
|||
Live end-to-end verification (post-`5a60e85`):
|
||||
|
||||
```
|
||||
$ aborist query --reject-broad "Winners of all major sports?"
|
||||
$ arborist query --reject-broad "Winners of all major sports?"
|
||||
Winners of all major sports?
|
||||
UNGROUNDED · via BROAD_QUANTIFIER_REJECTED · ALL ("all")
|
||||
· cap was 8 0/0 0.0s (preflight)
|
||||
|
|
@ -521,7 +521,7 @@ Winners of all major sports?
|
|||
universe. Narrow ... or run with --allow-broad for exploratory
|
||||
enumeration.
|
||||
|
||||
$ aborist query --reject-broad "name all members of the Beatles"
|
||||
$ arborist query --reject-broad "name all members of the Beatles"
|
||||
name all members of the Beatles
|
||||
UNGROUNDED · via claim_lattice · title mismatch 4/4 20.9s
|
||||
[Beatles enumerated; scope_bound_hint=bounded → not rejected]
|
||||
|
|
@ -747,7 +747,7 @@ _RE`, `_POINTER_ID_RE`) avoid per-row recompilation.
|
|||
|
||||
#### Phase 1 — Pure quantifier classifier (LANDED `926b05e`)
|
||||
|
||||
`aborist/qa/quantifier.py` with `classify_question_quantifier()`,
|
||||
`arborist/qa/quantifier.py` with `classify_question_quantifier()`,
|
||||
pure function (no I/O, no model call, no retrieval call). Returns:
|
||||
|
||||
```json
|
||||
|
|
@ -780,7 +780,7 @@ refinement.
|
|||
|
||||
#### Phase 2 — Model-profile claim-budget contract (LANDED `84d5b5c`)
|
||||
|
||||
`aborist/qa/model_profiles.py` ships two profiles:
|
||||
`arborist/qa/model_profiles.py` ships two profiles:
|
||||
|
||||
- `adamo1139/Hermes-3-Llama-3.1-8B-FP8-Dynamic` — tight caps for
|
||||
broad intensities (ALL=8, COMPREHENSIVE=5, OPEN_REQUEST=5).
|
||||
|
|
@ -821,7 +821,7 @@ the default with the looked-up cap.
|
|||
|
||||
#### Phase 3 — Prompt reminder (LANDED `6f90f21`, default OFF)
|
||||
|
||||
`aborist/qa/quantifier_reminder.py` ships
|
||||
`arborist/qa/quantifier_reminder.py` ships
|
||||
`broad_quantifier_reminder()` with two templates:
|
||||
|
||||
- **Bounded universe** (`all members of the Beatles`):
|
||||
|
|
@ -854,7 +854,7 @@ beyond the §10.8 5pp floor.
|
|||
|
||||
#### Phase 4 — CLI flags + violation tails (LANDED `5a60e85`)
|
||||
|
||||
CLI flags on `aborist query`:
|
||||
CLI flags on `arborist query`:
|
||||
|
||||
```
|
||||
--no-quantifier-guard Level 2 disable: kills guard for one call
|
||||
|
|
@ -1204,7 +1204,7 @@ BROAD_QUANTIFIER_SCOPE_UNBOUND scope-bound hint fired in 10.1;
|
|||
```
|
||||
|
||||
Folds into `governance_policy_hash` via the §1 hard constraint and
|
||||
into `_SOFT_DEMOTE_VIOLATION_KINDS` in `aborist/cli.py`.
|
||||
into `_SOFT_DEMOTE_VIOLATION_KINDS` in `arborist/cli.py`.
|
||||
|
||||
This keeps the schema and the four-rung ladder unchanged. Display
|
||||
labels carry the broad-quantifier signal via the existing tail
|
||||
|
|
@ -1304,7 +1304,7 @@ answer_pointer_count: int count of [E\d+] tags in raw_answer
|
|||
answer_chars_with_brackets: int char count of bracket-tagged regions
|
||||
model_profile_id: str e.g. "hermes-3-llama-3.1-8b-fp8"
|
||||
(with the FP8 suffix; the
|
||||
ABORIST_LLM_MODEL default uses the
|
||||
ARBORIST_LLM_MODEL default uses the
|
||||
FP8-Dynamic variant)
|
||||
scope_bound_hint: str classifier output (10.1):
|
||||
bounded / unbounded / unknown
|
||||
|
|
@ -1457,7 +1457,7 @@ From most granular to most global:
|
|||
Level 1 — Per-test override
|
||||
Tests pass policy={"quantifier_guard_enabled": False}.
|
||||
Same mechanism claim_lattice_warrant_check_enabled already uses
|
||||
(aborist/qa/runner.py:191). One line. No environment changes.
|
||||
(arborist/qa/runner.py:191). One line. No environment changes.
|
||||
|
||||
Level 2 — Per-call CLI flag
|
||||
--no-quantifier-guard kills Phase 1+2 for one call
|
||||
|
|
@ -1483,7 +1483,7 @@ Level 4 — Per-mode gating
|
|||
on baseline; its failure shape is different.
|
||||
|
||||
Level 5 — Per-model profile
|
||||
aborist/qa/model_profiles.py keys by model_profile_id.
|
||||
arborist/qa/model_profiles.py keys by model_profile_id.
|
||||
Models without an entry fall back to the "default" profile,
|
||||
which has the conservative caps from §10.6 (no Hermes-tight
|
||||
tightening).
|
||||
|
|
@ -1612,18 +1612,18 @@ Single-source-of-truth for **what was actually built** in the
|
|||
|
||||
| Path | Purpose | Phase |
|
||||
|-------------------------------------|------------------------------------------------------------------------|-------|
|
||||
| `aborist/qa/quantifier.py` | Pure 10-rung classifier + scope-bound heuristic | 1 |
|
||||
| `aborist/qa/model_profiles.py` | PROFILES dict + `cap_for_intensity()` + `EXPLICIT_COUNT` sentinel | 2 |
|
||||
| `aborist/qa/quantifier_reminder.py` | `broad_quantifier_reminder()` with bounded vs unbounded templates | 3 |
|
||||
| `arborist/qa/quantifier.py` | Pure 10-rung classifier + scope-bound heuristic | 1 |
|
||||
| `arborist/qa/model_profiles.py` | PROFILES dict + `cap_for_intensity()` + `EXPLICIT_COUNT` sentinel | 2 |
|
||||
| `arborist/qa/quantifier_reminder.py` | `broad_quantifier_reminder()` with bounded vs unbounded templates | 3 |
|
||||
|
||||
### 11.2 Modified modules (new behavior)
|
||||
|
||||
| Path | Change | Phase |
|
||||
|-----------------------|---------------------------------------------------------------------------------------------|-------|
|
||||
| `aborist/qa/query.py` | classifier wired post-policy-resolution; `effective_max_claims` lookup; reject-broad early-return; reminder injection | 1, 2, 3, 4 |
|
||||
| `aborist/qa/runner.py` | same wiring as query() for `aborist ask` parity; new policy fields in DEFAULT_POLICY | 1, 2, 3, 4 |
|
||||
| `aborist/qa/keys.py` | 7 new fields in `_VERIFIER_POLICY_FIELDS` for governance-hash binding | 2, 3, 4 |
|
||||
| `aborist/cli.py` | 4 new CLI flags; 4 new violation kinds in `_SOFT_DEMOTE_VIOLATION_KINDS`; `_render_warrant_tail` extended; `_render_query_human` branch for `broad_quantifier_rejected` status | 4 |
|
||||
| `arborist/qa/query.py` | classifier wired post-policy-resolution; `effective_max_claims` lookup; reject-broad early-return; reminder injection | 1, 2, 3, 4 |
|
||||
| `arborist/qa/runner.py` | same wiring as query() for `arborist ask` parity; new policy fields in DEFAULT_POLICY | 1, 2, 3, 4 |
|
||||
| `arborist/qa/keys.py` | 7 new fields in `_VERIFIER_POLICY_FIELDS` for governance-hash binding | 2, 3, 4 |
|
||||
| `arborist/cli.py` | 4 new CLI flags; 4 new violation kinds in `_SOFT_DEMOTE_VIOLATION_KINDS`; `_render_warrant_tail` extended; `_render_query_human` branch for `broad_quantifier_rejected` status | 4 |
|
||||
| `bench/qa_sweep.py` | `_bracket_diagnostics()`; module regexes; 8 new fields in `_run_one`; aggregation in `_summarize`; new markdown section | 0, 0.x |
|
||||
|
||||
### 11.3 New test files
|
||||
|
|
@ -1640,7 +1640,7 @@ Total new tests: **120**; full suite **906 passing, 36 skipped.**
|
|||
|
||||
### 11.4 New policy fields (governance-hash bound)
|
||||
|
||||
All seven fold into `_VERIFIER_POLICY_FIELDS` (`aborist/qa/keys.py`)
|
||||
All seven fold into `_VERIFIER_POLICY_FIELDS` (`arborist/qa/keys.py`)
|
||||
so flipping any of them invalidates prior cache records on lookup.
|
||||
|
||||
```
|
||||
|
|
@ -1653,7 +1653,7 @@ quantifier_reject_broad False Phase 4 reject default off
|
|||
claim_lattice_format_collapse_check_enabled True (Phase 0 prereq)
|
||||
```
|
||||
|
||||
### 11.5 New CLI flags (`aborist query`)
|
||||
### 11.5 New CLI flags (`arborist query`)
|
||||
|
||||
```
|
||||
--no-quantifier-guard Level 2 disable (kills guard for one call)
|
||||
|
|
@ -1666,7 +1666,7 @@ claim_lattice_format_collapse_check_enabled True (Phase 0 prereq)
|
|||
|
||||
Three soft demotes (cap ladder at ANCHOR-WARRANTED) + one hard demote
|
||||
(early-return UNGROUNDED). All four render audit-line tails via
|
||||
`_render_warrant_tail` in `aborist/cli.py`:
|
||||
`_render_warrant_tail` in `arborist/cli.py`:
|
||||
|
||||
```
|
||||
BROAD_QUANTIFIER_RUNAWAY soft → "broad runaway"
|
||||
|
|
@ -1711,33 +1711,33 @@ Choices made during implementation that didn't pre-exist in §9 / §10
|
|||
but are now binding via tests + commits:
|
||||
|
||||
1. **`RUNG_PRIORITY` puts COMPREHENSIVE above OPEN_REQUEST**
|
||||
(`aborist/qa/quantifier.py:_RUNG_PRIORITY`). Caught during Phase 1
|
||||
(`arborist/qa/quantifier.py:_RUNG_PRIORITY`). Caught during Phase 1
|
||||
tests when `tell me everything about all wars` matched both
|
||||
templates. COMPREHENSIVE wins because §2.2 calls it "strictly
|
||||
stronger than ALL"; OPEN_REQUEST is a softer shape-detector.
|
||||
2. **Bounded-domain anchor regex list**
|
||||
(`aborist/qa/quantifier.py:_BOUNDED_DOMAIN_ANCHORS`). Hand-curated
|
||||
(`arborist/qa/quantifier.py:_BOUNDED_DOMAIN_ANCHORS`). Hand-curated
|
||||
list covering Beatles, US states/presidents, planets, founding
|
||||
fathers, continents, oceans, plus a year-anchor `(?:19|20)\d{2}`
|
||||
pattern and a season/year/championship/tournament prefix. Future
|
||||
refinement (§10.1 noted): corpus-arity check.
|
||||
3. **Reminder templates** (`aborist/qa/quantifier_reminder.py`).
|
||||
3. **Reminder templates** (`arborist/qa/quantifier_reminder.py`).
|
||||
Two-template split (bounded vs unbounded); the bounded template
|
||||
omits the "do not enumerate from training prior" clause because
|
||||
the corpus has the answer set. Unknown scope falls back to the
|
||||
stricter unbounded template (over-warn rather than under-warn).
|
||||
4. **Reject-broad answer_text format** (`aborist/qa/query.py`).
|
||||
4. **Reject-broad answer_text format** (`arborist/qa/query.py`).
|
||||
`BROAD-QUANTIFIER PREFLIGHT REJECTED · scope unbounded` header +
|
||||
actionable narrowing hints (year, league, country, category) +
|
||||
`--allow-broad` escape-hatch mention. Format chosen so an
|
||||
operator reading raw output sees both the WHY and the HOW-TO-FIX.
|
||||
5. **`_render_query_human` branch for `broad_quantifier_rejected`
|
||||
status** (`aborist/cli.py`). Dedicated render path so the new
|
||||
status** (`arborist/cli.py`). Dedicated render path so the new
|
||||
status doesn't fall through to the generic "unknown error"
|
||||
handler. Renders intensity, matched token, cap, and the rejection
|
||||
text on a single audit-line + body.
|
||||
6. **EXPLICIT_COUNT defensive fallback to MANY cap**
|
||||
(`aborist/qa/model_profiles.py:cap_for_intensity`). When the
|
||||
(`arborist/qa/model_profiles.py:cap_for_intensity`). When the
|
||||
classifier fired SMALL_NUM_EXPLICIT or COMPARATIVE_BOUND but
|
||||
didn't extract a count (shouldn't happen but covers regressions),
|
||||
fall back to the same profile's MANY cap rather than crash.
|
||||
|
|
@ -1750,10 +1750,10 @@ but are now binding via tests + commits:
|
|||
|
||||
### 11.10 Live verification artifacts
|
||||
|
||||
- `aborist query --reject-broad "Winners of all major sports?"` →
|
||||
- `arborist query --reject-broad "Winners of all major sports?"` →
|
||||
preflight rejection, 0.0s, no LLM call. Rendered output recorded
|
||||
in §8 Status.
|
||||
- `aborist query --reject-broad "name all members of the Beatles"`
|
||||
- `arborist query --reject-broad "name all members of the Beatles"`
|
||||
→ ran the LLM (scope_bound_hint=bounded correctly suppressed
|
||||
rejection), enumerated 4 verified Beatles members. Rendered output
|
||||
recorded in §8 Status.
|
||||
|
|
@ -2239,10 +2239,10 @@ quote:
|
|||
|
||||
Code changes (commit pending):
|
||||
|
||||
- `aborist/qa/runner.py`: `quantifier_reminder_enabled=True`,
|
||||
- `arborist/qa/runner.py`: `quantifier_reminder_enabled=True`,
|
||||
new field `quantifier_apply_caps_modes=["claim_lattice"]`.
|
||||
- `aborist/qa/query.py`: same defaults.
|
||||
- `aborist/qa/keys.py`: `quantifier_apply_caps_modes` added to
|
||||
- `arborist/qa/query.py`: same defaults.
|
||||
- `arborist/qa/keys.py`: `quantifier_apply_caps_modes` added to
|
||||
`_VERIFIER_POLICY_FIELDS` so flipping it bumps governance hash.
|
||||
- Cap-application gate now reads:
|
||||
`if apply_caps AND mode in apply_caps_modes AND cap is not None:`
|
||||
|
|
@ -2257,20 +2257,20 @@ Code changes (commit pending):
|
|||
```
|
||||
# Default behavior (no flag): reminder ON for lattice modes; cap
|
||||
# OFF (operator opt-in only).
|
||||
$ aborist query "winners of all major sports?"
|
||||
$ arborist query "winners of all major sports?"
|
||||
|
||||
# Flip cap on for one call (JSON-only by default):
|
||||
$ aborist query --apply-quantifier-caps "..."
|
||||
$ arborist query --apply-quantifier-caps "..."
|
||||
|
||||
# Override the allowlist to include pointer mode too (Option D):
|
||||
$ aborist query --apply-quantifier-caps \
|
||||
$ arborist query --apply-quantifier-caps \
|
||||
--policy quantifier_apply_caps_modes='["claim_lattice","claim_lattice_pointer"]' "..."
|
||||
|
||||
# Disable the reminder for one call:
|
||||
$ aborist query --policy quantifier_reminder_enabled=false "..."
|
||||
$ arborist query --policy quantifier_reminder_enabled=false "..."
|
||||
|
||||
# Master kill:
|
||||
$ aborist query --no-quantifier-guard "..."
|
||||
$ arborist query --no-quantifier-guard "..."
|
||||
```
|
||||
|
||||
**Six-level disable hierarchy still intact** — no flag forced upon
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ split if a future clause needs independent hashing.**
|
|||
|
||||
### 3.3 Where the node lives in code
|
||||
|
||||
- New helper in `aborist/qa/dag.py` (alongside existing per-stage
|
||||
- New helper in `arborist/qa/dag.py` (alongside existing per-stage
|
||||
helpers): `_quantifier_preflight_node(quantifier, policy,
|
||||
effective_max_claims, reminder_injected)`.
|
||||
- Wire into `query()` at the same point quantifier classification
|
||||
|
|
@ -176,7 +176,7 @@ preflight contract").
|
|||
## 4. Implementation sketch
|
||||
|
||||
```
|
||||
1. New function in aborist/qa/dag.py:
|
||||
1. New function in arborist/qa/dag.py:
|
||||
def quantifier_preflight_node(quantifier, policy, ...)
|
||||
2. Wire it into query() between policy resolution and t_start.
|
||||
3. Wire it into runner.ask() symmetrically.
|
||||
|
|
@ -219,17 +219,17 @@ This ticket closes the loop.
|
|||
**Closed · landed 2026-05-03 (zero-shot).** Mechanism shipped end-
|
||||
to-end:
|
||||
|
||||
- `aborist/qa/dag.py:preflight_node_hash()` — combines
|
||||
- `arborist/qa/dag.py:preflight_node_hash()` — combines
|
||||
QuestionState + quantifier classifier output + behavioral
|
||||
policy_state into one canonical SHA-256 hex.
|
||||
- `aborist/qa/dag.py:build_run_dag()` — new optional
|
||||
- `arborist/qa/dag.py:build_run_dag()` — new optional
|
||||
`preflight_hash` parameter inserts a `preflight` stage at
|
||||
position 1 (between `question` and `retrieval`). Quote-mode
|
||||
shape becomes 8 stages; pointer-mode CTI shape becomes 10
|
||||
stages. Backward-compatible: when `preflight_hash` is None,
|
||||
shapes stay 7/9 exactly so legacy `run_dag_root` values
|
||||
re-validate.
|
||||
- `aborist/qa/query.py` + `aborist/qa/runner.py` — both build
|
||||
- `arborist/qa/query.py` + `arborist/qa/runner.py` — both build
|
||||
the preflight payload from `question_state`, the quantifier
|
||||
dict, and a 10-field `policy_state` capturing the *behavioral*
|
||||
decisions taken on this run (guard_enabled, apply_caps_active,
|
||||
|
|
@ -276,7 +276,7 @@ preflight contract").
|
|||
### 7.2 What's NOT in this ticket
|
||||
|
||||
- **CLI flag for inspecting preflight node**: a future
|
||||
`aborist providence --show-preflight <cache_key>` would render
|
||||
`arborist providence --show-preflight <cache_key>` would render
|
||||
the preflight payload from `run_dag_blob`. Out of scope here.
|
||||
- **Bench harness preflight-hash field**: bench rows could
|
||||
surface `preflight_hash` (12-char prefix like `cache_key`)
|
||||
|
|
@ -369,7 +369,7 @@ Confirmed. Corrections stay strictly in the audit-binding lane.
|
|||
|
||||
**A — reject-path DAG** (the critical gap):
|
||||
|
||||
`aborist/qa/dag.py:build_reject_run_dag()` ships. Builds a 3-stage
|
||||
`arborist/qa/dag.py:build_reject_run_dag()` ships. Builds a 3-stage
|
||||
`question → preflight → final_label` DAG for the reject-broad
|
||||
early-return path. `query()` now wires it in and returns
|
||||
`run_dag_root` + `run_dag_blob` on the rejection result dict.
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ follow-up; first pass surfaces it on the result dict only).
|
|||
|
||||
### 6.1 Quantifier (REUSE from #000008)
|
||||
|
||||
Already shipped as `aborist/qa/quantifier.py`. Returns intensity +
|
||||
Already shipped as `arborist/qa/quantifier.py`. Returns intensity +
|
||||
`scope_bound_hint`. Meta-cognition wraps it; doesn't duplicate.
|
||||
|
||||
### 6.2 Temporal sensitivity
|
||||
|
|
@ -204,7 +204,7 @@ record `false_premise_suspected` so the audit-line can surface it.
|
|||
### 6.5 Reference-frame detector
|
||||
|
||||
Reuse the existing frame-detection logic from ticket #000002
|
||||
(`aborist/qa/query.py:_detect_frame`). Already classifies
|
||||
(`arborist/qa/query.py:_detect_frame`). Already classifies
|
||||
`reference` vs `literal` vs `ambiguous`. Meta-cognition wraps the
|
||||
output as `reference_frames: list[str]`.
|
||||
|
||||
|
|
@ -216,11 +216,11 @@ has no matching upload, sets `corpus_status: "out_of_corpus"`.
|
|||
Conservative — defaults to `"likely_in_corpus"` for typical
|
||||
encyclopedic questions.
|
||||
|
||||
## 7. Implementation in Aborist
|
||||
## 7. Implementation in Arborist
|
||||
|
||||
### 7.1 New module
|
||||
|
||||
`aborist/qa/metacognition.py` with:
|
||||
`arborist/qa/metacognition.py` with:
|
||||
|
||||
```python
|
||||
def preflight_question(
|
||||
|
|
@ -325,13 +325,13 @@ to fox.
|
|||
|
||||
### 12.1 Phase 1 — module + detectors (LANDED `55efb04`)
|
||||
|
||||
`aborist/qa/metacognition.py` shipped with:
|
||||
`arborist/qa/metacognition.py` shipped with:
|
||||
|
||||
- `QuestionState` dataclass (frozen, JSON-serializable via `to_dict`)
|
||||
- `preflight_question()` pure function
|
||||
- 4 new deterministic detectors (temporal, contradiction, false-
|
||||
premise-lite, out-of-corpus)
|
||||
- Reuses `aborist.qa.quantifier.classify_question_quantifier`
|
||||
- Reuses `arborist.qa.quantifier.classify_question_quantifier`
|
||||
- 8 LogicalStatus values + 3 PreflightResult values
|
||||
- 6 per-detector enable switches in policy
|
||||
- `preflight_policy_hash` for governance binding (Phase 3)
|
||||
|
|
@ -372,7 +372,7 @@ metacognition_block_on_contradiction False
|
|||
All folded into `_VERIFIER_POLICY_FIELDS` so flipping any of them
|
||||
invalidates prior cache records.
|
||||
|
||||
CLI flags on `aborist query`:
|
||||
CLI flags on `arborist query`:
|
||||
|
||||
```
|
||||
--no-preflight disable the guard for one call
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ class SoftPreflightHint:
|
|||
The hint always carries the `SOFT_` prefix on its `classifier_label`
|
||||
so it can never be mistaken for a deterministic verdict by:
|
||||
|
||||
- The audit-line tail renderer in `aborist/cli.py:_render_warrant_tail`
|
||||
- The audit-line tail renderer in `arborist/cli.py:_render_warrant_tail`
|
||||
- The verifier (which doesn't read sidecar fields anyway)
|
||||
- Bench aggregations
|
||||
- Operators inspecting bench JSONL or run-DAG blobs
|
||||
|
|
@ -69,11 +69,11 @@ so it can never be mistaken for a deterministic verdict by:
|
|||
## 3. Where it lives
|
||||
|
||||
```
|
||||
aborist/qa/soft_preflight.py — sidecar implementation
|
||||
arborist/qa/soft_preflight.py — sidecar implementation
|
||||
(new module)
|
||||
```
|
||||
|
||||
Mirrors `aborist/qa/inspect.py` (the existing read-only sidecar
|
||||
Mirrors `arborist/qa/inspect.py` (the existing read-only sidecar
|
||||
diagnostic for span classification) — same architectural pattern:
|
||||
soft signal, never enters proof path, never written to providence
|
||||
cache, never bumps governance hash on use.
|
||||
|
|
@ -99,7 +99,7 @@ remains driven by the hard policy.
|
|||
## 5. Implementation sketch
|
||||
|
||||
```python
|
||||
# aborist/qa/soft_preflight.py
|
||||
# arborist/qa/soft_preflight.py
|
||||
|
||||
def soft_preflight_question(
|
||||
question: str,
|
||||
|
|
@ -149,17 +149,17 @@ def soft_preflight_question(
|
|||
|
||||
## 6. Integration points
|
||||
|
||||
- `aborist/qa/query.py` — call `soft_preflight_question()` after
|
||||
- `arborist/qa/query.py` — call `soft_preflight_question()` after
|
||||
`preflight_question()` (the hard call) but before the main LLM
|
||||
call. Soft hint surfaces on result dict as
|
||||
`soft_preflight_hint`; merges into the run-DAG `preflight`
|
||||
stage payload as a sub-field of `question_state`.
|
||||
- `aborist/qa/runner.py` — same wiring.
|
||||
- `aborist/cli.py` — new `--soft-preflight` flag (per-call
|
||||
- `arborist/qa/runner.py` — same wiring.
|
||||
- `arborist/cli.py` — new `--soft-preflight` flag (per-call
|
||||
override; default off matches policy default).
|
||||
- `bench/qa_sweep.py` — bench rows pick up `soft_preflight_label`
|
||||
+ `soft_preflight_confidence` as bounded-projection fields.
|
||||
- `aborist/cli.py:_render_warrant_tail` — soft hints render as
|
||||
- `arborist/cli.py:_render_warrant_tail` — soft hints render as
|
||||
`· soft: <label>` on the audit-line (clearly differentiated from
|
||||
the hard `· false premise` etc. tails). Operator can tell at a
|
||||
glance whether a tail is hard or soft.
|
||||
|
|
@ -225,7 +225,7 @@ hash never enters the proof path" rule (cf. whitepaper
|
|||
|
||||
## 11. What landed
|
||||
|
||||
- **`aborist/qa/soft_preflight.py`** — `SoftPreflightHint`
|
||||
- **`arborist/qa/soft_preflight.py`** — `SoftPreflightHint`
|
||||
dataclass + `soft_preflight_question()` pure function.
|
||||
9 canonical labels (`SOFT_WELL_FORMED`,
|
||||
`SOFT_FALSE_PREMISE_SUSPECTED`, `SOFT_CONTRADICTION_SUSPECTED`,
|
||||
|
|
@ -238,18 +238,18 @@ hash never enters the proof path" rule (cf. whitepaper
|
|||
(raises → `SOFT_PARSE_FAIL`, drift → `SOFT_PARSE_FAIL`).
|
||||
`SOFT_PREFLIGHT_VERSION = "soft-preflight-v0.1"`.
|
||||
|
||||
- **`aborist/qa/query.py`** — wired into the post-classifier /
|
||||
- **`arborist/qa/query.py`** — wired into the post-classifier /
|
||||
pre-retrieval segment. One short LLM call (~200ms median),
|
||||
result surfaces as `soft_preflight_hint` on the result dict
|
||||
+ `soft_preflight_ms` in the timings dict.
|
||||
|
||||
- **`aborist/qa/runner.py` + `aborist/qa/query.py`** —
|
||||
- **`arborist/qa/runner.py` + `arborist/qa/query.py`** —
|
||||
`soft_preflight_enabled: False` policy default; cache row
|
||||
identity unchanged (NOT folded into `_VERIFIER_POLICY_FIELDS`
|
||||
per §4 — soft hints don't gate cache identity).
|
||||
|
||||
- **`aborist/cli.py`** — `--soft-preflight` flag on
|
||||
`aborist query`. Audit-line tail renders soft hints as
|
||||
- **`arborist/cli.py`** — `--soft-preflight` flag on
|
||||
`arborist query`. Audit-line tail renders soft hints as
|
||||
`· soft: <label>` (e.g. `· soft: time sensitive`) — distinct
|
||||
from hard tails so operators see the signal separation at a
|
||||
glance. SOFT_DISABLED / SOFT_PARSE_FAIL / SOFT_WELL_FORMED
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ contend.
|
|||
|
||||
Wired into `make backfill-concepts`. Usage:
|
||||
|
||||
python scripts/backfill_concepts.py --shards-dir ~/.aborist/shards
|
||||
python scripts/backfill_concepts.py --shards-dir ~/.aborist/shards \
|
||||
python scripts/backfill_concepts.py --shards-dir ~/.arborist/shards
|
||||
python scripts/backfill_concepts.py --shards-dir ~/.arborist/shards \
|
||||
--extractors link_reciprocity,token_idf,documents_fts
|
||||
python scripts/backfill_concepts.py --shards-dir ~/.aborist/shards \
|
||||
python scripts/backfill_concepts.py --shards-dir ~/.arborist/shards \
|
||||
--workers 8
|
||||
|
||||
Skips shards that aren't numeric-stem (`000.db`, `001.db`, …) by
|
||||
|
|
@ -28,8 +28,8 @@ import sys
|
|||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from aborist.concepts.extract import EXTRACTORS
|
||||
from aborist.store import connect, discover_shards
|
||||
from arborist.concepts.extract import EXTRACTORS
|
||||
from arborist.store import connect, discover_shards
|
||||
|
||||
|
||||
# Default extractor order. link_reciprocity must run BEFORE token_idf
|
||||
|
|
@ -66,7 +66,7 @@ def main(argv: list[str] | None = None) -> int:
|
|||
parser.add_argument(
|
||||
"--shards-dir",
|
||||
type=Path,
|
||||
default=Path.home() / ".aborist" / "shards",
|
||||
default=Path.home() / ".arborist" / "shards",
|
||||
help="Directory containing *.db shards.",
|
||||
)
|
||||
parser.add_argument(
|
||||
|
|
@ -74,7 +74,7 @@ def main(argv: list[str] | None = None) -> int:
|
|||
default=",".join(DEFAULT_EXTRACTORS),
|
||||
help=(
|
||||
"Comma-separated extractor names (registered in "
|
||||
"aborist.concepts.extract.EXTRACTORS). Default: "
|
||||
"arborist.concepts.extract.EXTRACTORS). Default: "
|
||||
f"{','.join(DEFAULT_EXTRACTORS)}"
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Pick three random words from /usr/share/dict/words, ask Hermes at
|
||||
temp 0.8 to weave them into a creative question paragraph, send that
|
||||
paragraph to aborist, log the whole journey (words → question →
|
||||
paragraph to arborist, log the whole journey (words → question →
|
||||
answer → audit_mode → sources → timings) as a single JSONL line.
|
||||
|
||||
Designed for **blue moon cadence**, NOT every-commit benching. The
|
||||
|
|
@ -128,7 +128,7 @@ def run_one_cycle(
|
|||
top_k: int,
|
||||
) -> dict:
|
||||
"""One emergent cycle: words → question → answer → log entry dict."""
|
||||
from aborist.qa.query import DEFAULT_QUERY_POLICY, query
|
||||
from arborist.qa.query import DEFAULT_QUERY_POLICY, query
|
||||
|
||||
t_pick = time.time()
|
||||
question = generate_question(client, model_id, words)
|
||||
|
|
@ -159,7 +159,7 @@ def run_one_cycle(
|
|||
# ignored. Surfaced 2026-05-02 by the swallowtail/upbraided/rockiest
|
||||
# emergent log entry. Off-the-binary-chain — gives the teacher
|
||||
# reviewer a flag for poetic-question-vs-literal-answer mismatches.
|
||||
from aborist.qa.inspect import diagnose_metaphor_deflection
|
||||
from arborist.qa.inspect import diagnose_metaphor_deflection
|
||||
metaphor = diagnose_metaphor_deflection(
|
||||
question or "", result.get("answer_text") or ""
|
||||
)
|
||||
|
|
@ -206,14 +206,14 @@ def append_log(entry: dict, log_path: Path) -> None:
|
|||
def emit_unfirehose_session(entry: dict, model_id: str) -> None:
|
||||
"""Write one unfirehose/1.0 session for this bench cycle.
|
||||
|
||||
Same auto-discovery path as `aborist.cli._emit_query_journal`:
|
||||
~/.aborist/unfirehose/{slug}/{session_uuid}.jsonl. Each cycle gets
|
||||
Same auto-discovery path as `arborist.cli._emit_query_journal`:
|
||||
~/.arborist/unfirehose/{slug}/{session_uuid}.jsonl. Each cycle gets
|
||||
its own session file (one prompt → one answer = one session).
|
||||
Failures must NEVER break the bench loop — wrapped at the call
|
||||
site.
|
||||
"""
|
||||
from aborist.journal import SessionWriter
|
||||
aborist_meta = {
|
||||
from arborist.journal import SessionWriter
|
||||
arborist_meta = {
|
||||
"audit_mode": entry.get("audit_mode"),
|
||||
"verifier_method": entry.get("verifier_method"),
|
||||
"n_quotes": entry.get("n_quotes"),
|
||||
|
|
@ -236,7 +236,7 @@ def emit_unfirehose_session(entry: dict, model_id: str) -> None:
|
|||
s.system_message(
|
||||
"bench-emergent cycle: 3-word triangulation",
|
||||
subtype="init",
|
||||
aborist_meta={"words": entry.get("words"), "harness_role": "generator"},
|
||||
arborist_meta={"words": entry.get("words"), "harness_role": "generator"},
|
||||
)
|
||||
s.user_message(entry.get("question") or "")
|
||||
s.assistant_message(
|
||||
|
|
@ -244,7 +244,7 @@ def emit_unfirehose_session(entry: dict, model_id: str) -> None:
|
|||
model=model_id,
|
||||
provider="hermes",
|
||||
duration_ms=int((entry.get("answer_seconds") or 0) * 1000) or None,
|
||||
aborist_meta=aborist_meta,
|
||||
arborist_meta=arborist_meta,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -284,23 +284,23 @@ def main(argv: list[str] | None = None) -> int:
|
|||
parser.add_argument(
|
||||
"--shards-dir",
|
||||
type=Path,
|
||||
default=Path.home() / ".aborist" / "shards",
|
||||
default=Path.home() / ".arborist" / "shards",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--qa-db",
|
||||
type=Path,
|
||||
default=Path.home() / ".aborist" / "shards" / "qa.db",
|
||||
default=Path.home() / ".arborist" / "shards" / "qa.db",
|
||||
)
|
||||
parser.add_argument("--answer-mode", default="claim_lattice")
|
||||
parser.add_argument("--top-k", type=int, default=8)
|
||||
parser.add_argument(
|
||||
"--endpoint",
|
||||
default=os.environ.get("ABORIST_LLM_ENDPOINT", "https://hermes.ai.unturf.com/v1"),
|
||||
default=os.environ.get("ARBORIST_LLM_ENDPOINT", "https://hermes.ai.unturf.com/v1"),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--model",
|
||||
default=os.environ.get(
|
||||
"ABORIST_LLM_MODEL",
|
||||
"ARBORIST_LLM_MODEL",
|
||||
"adamo1139/Hermes-3-Llama-3.1-8B-FP8-Dynamic",
|
||||
),
|
||||
)
|
||||
|
|
@ -320,9 +320,9 @@ def main(argv: list[str] | None = None) -> int:
|
|||
parser.error(f"shards dir not found: {ns.shards_dir}")
|
||||
|
||||
rng = random.Random(ns.seed)
|
||||
from aborist.qa.client import OpenAICompatibleClient
|
||||
from arborist.qa.client import OpenAICompatibleClient
|
||||
|
||||
api_key = os.environ.get("ABORIST_LLM_API_KEY")
|
||||
api_key = os.environ.get("ARBORIST_LLM_API_KEY")
|
||||
client = OpenAICompatibleClient(base_url=ns.endpoint, api_key=api_key)
|
||||
|
||||
print(f">> emergent bench: {ns.n} cycles · seed={ns.seed} · log={ns.log_path}", flush=True)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from unittest.mock import Mock, AsyncMock, patch, MagicMock
|
|||
import asyncio
|
||||
import aiohttp
|
||||
|
||||
from aborist.sources.crawler.async_web_fetcher import (
|
||||
from arborist.sources.crawler.async_web_fetcher import (
|
||||
CrawlMode,
|
||||
MediaItem,
|
||||
strip_uri_fragment,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
These tests stub the network. The verbatim crawler tests in this same
|
||||
directory exercise the underlying AsyncWebFetcher; here we exercise
|
||||
the aborist bridge logic on top of it (BFS bounds, same-domain filter,
|
||||
the arborist bridge logic on top of it (BFS bounds, same-domain filter,
|
||||
ETag capture, conditional HEAD classification).
|
||||
"""
|
||||
|
||||
|
|
@ -16,14 +16,14 @@ from unittest.mock import patch
|
|||
import httpx
|
||||
import pytest
|
||||
|
||||
from aborist.sources.crawler.bridge import (
|
||||
from arborist.sources.crawler.bridge import (
|
||||
_normalize,
|
||||
_same_domain,
|
||||
crawl_seed,
|
||||
ingest_crawled,
|
||||
recrawl_check,
|
||||
)
|
||||
from aborist.store import connect
|
||||
from arborist.store import connect
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -71,7 +71,7 @@ class _StubFetcher:
|
|||
def _run_crawl(page_links, seed, depth, max_pages):
|
||||
"""Helper: drive _crawl_seed_async with a stub fetcher."""
|
||||
import asyncio
|
||||
from aborist.sources.crawler import bridge
|
||||
from arborist.sources.crawler import bridge
|
||||
|
||||
return asyncio.run(
|
||||
bridge._crawl_seed_async(
|
||||
|
|
@ -162,7 +162,7 @@ def test_crawl_seed_fast_flag_creates_fast_mode_fetcher(monkeypatch):
|
|||
async def fetch_webpage(self, url, extract_links=False, **kw):
|
||||
return None, []
|
||||
|
||||
from aborist.sources.crawler import bridge
|
||||
from arborist.sources.crawler import bridge
|
||||
|
||||
monkeypatch.setattr(bridge, "AsyncWebFetcher", _CapturingFetcher)
|
||||
bridge.crawl_seed("https://x.com/", max_depth=1, max_pages=1, fast=True)
|
||||
|
|
@ -180,7 +180,7 @@ def test_crawl_seed_default_creates_polite_fetcher(monkeypatch):
|
|||
async def fetch_webpage(self, url, extract_links=False, **kw):
|
||||
return None, []
|
||||
|
||||
from aborist.sources.crawler import bridge
|
||||
from arborist.sources.crawler import bridge
|
||||
|
||||
monkeypatch.setattr(bridge, "AsyncWebFetcher", _CapturingFetcher)
|
||||
bridge.crawl_seed("https://x.com/", max_depth=1, max_pages=1)
|
||||
|
|
@ -202,7 +202,7 @@ def test_crawl_seed_passes_extract_links_true():
|
|||
return "<html></html>", []
|
||||
|
||||
import asyncio
|
||||
from aborist.sources.crawler import bridge
|
||||
from arborist.sources.crawler import bridge
|
||||
|
||||
fetcher = _RecordingFetcher()
|
||||
asyncio.run(
|
||||
|
|
@ -289,7 +289,7 @@ def test_ingest_crawled_writes_http_meta(http_meta_db):
|
|||
}
|
||||
}
|
||||
|
||||
with patch("aborist.sources.crawler.bridge.httpx.Client", _patched_httpx_get(responses)):
|
||||
with patch("arborist.sources.crawler.bridge.httpx.Client", _patched_httpx_get(responses)):
|
||||
conn = connect(http_meta_db)
|
||||
try:
|
||||
result = ingest_crawled(conn, [url])
|
||||
|
|
@ -324,7 +324,7 @@ def test_ingest_crawled_skips_non_html(http_meta_db):
|
|||
"body": "%PDF-1.4 binary",
|
||||
}
|
||||
}
|
||||
with patch("aborist.sources.crawler.bridge.httpx.Client", _patched_httpx_get(responses)):
|
||||
with patch("arborist.sources.crawler.bridge.httpx.Client", _patched_httpx_get(responses)):
|
||||
conn = connect(http_meta_db)
|
||||
try:
|
||||
result = ingest_crawled(conn, [url])
|
||||
|
|
@ -354,7 +354,7 @@ def test_ingest_crawled_skips_feed_urls_by_path(http_meta_db, feed_url):
|
|||
# Response stub never gets used because the path filter is pre-fetch,
|
||||
# but we provide one in case the filter regresses.
|
||||
responses = {feed_url: {"status": 200, "headers": {"content-type": "text/html"}}}
|
||||
with patch("aborist.sources.crawler.bridge.httpx.Client", _patched_httpx_get(responses)):
|
||||
with patch("arborist.sources.crawler.bridge.httpx.Client", _patched_httpx_get(responses)):
|
||||
conn = connect(http_meta_db)
|
||||
try:
|
||||
result = ingest_crawled(conn, [feed_url])
|
||||
|
|
@ -386,7 +386,7 @@ def test_ingest_crawled_skips_feed_content_types(http_meta_db, ct):
|
|||
"body": "<?xml version='1.0'?><feed></feed>",
|
||||
}
|
||||
}
|
||||
with patch("aborist.sources.crawler.bridge.httpx.Client", _patched_httpx_get(responses)):
|
||||
with patch("arborist.sources.crawler.bridge.httpx.Client", _patched_httpx_get(responses)):
|
||||
conn = connect(http_meta_db)
|
||||
try:
|
||||
result = ingest_crawled(conn, [url])
|
||||
|
|
@ -406,7 +406,7 @@ def test_ingest_crawled_keeps_xhtml(http_meta_db):
|
|||
"body": "<html><body><h1>T</h1>Real content here.</body></html>",
|
||||
}
|
||||
}
|
||||
with patch("aborist.sources.crawler.bridge.httpx.Client", _patched_httpx_get(responses)):
|
||||
with patch("arborist.sources.crawler.bridge.httpx.Client", _patched_httpx_get(responses)):
|
||||
conn = connect(http_meta_db)
|
||||
try:
|
||||
result = ingest_crawled(conn, [url])
|
||||
|
|
@ -423,7 +423,7 @@ def test_ingest_crawled_keeps_xhtml(http_meta_db):
|
|||
|
||||
def _seed_meta(conn, *, document_root: str, document_uri: str, etag: str | None = None):
|
||||
"""Insert a documents row + http_meta row for recheck tests."""
|
||||
from aborist.store import transaction
|
||||
from arborist.store import transaction
|
||||
|
||||
with transaction(conn):
|
||||
conn.execute(
|
||||
|
|
@ -451,7 +451,7 @@ def test_recrawl_check_classifies_304_as_fresh(tmp_path):
|
|||
conn.close()
|
||||
|
||||
responses = {"https://x.com/p": {"head_status": 304}}
|
||||
with patch("aborist.sources.crawler.bridge.httpx.Client", _patched_httpx_get(responses)):
|
||||
with patch("arborist.sources.crawler.bridge.httpx.Client", _patched_httpx_get(responses)):
|
||||
conn = connect(db)
|
||||
try:
|
||||
result = recrawl_check(conn, limit=10)
|
||||
|
|
@ -471,7 +471,7 @@ def test_recrawl_check_classifies_200_as_stale(tmp_path):
|
|||
conn.close()
|
||||
|
||||
responses = {"https://x.com/q": {"head_status": 200}}
|
||||
with patch("aborist.sources.crawler.bridge.httpx.Client", _patched_httpx_get(responses)):
|
||||
with patch("arborist.sources.crawler.bridge.httpx.Client", _patched_httpx_get(responses)):
|
||||
conn = connect(db)
|
||||
try:
|
||||
result = recrawl_check(conn, limit=10)
|
||||
|
|
@ -490,7 +490,7 @@ def test_recrawl_check_classifies_404_as_gone(tmp_path):
|
|||
conn.close()
|
||||
|
||||
responses = {"https://x.com/r": {"head_status": 404}}
|
||||
with patch("aborist.sources.crawler.bridge.httpx.Client", _patched_httpx_get(responses)):
|
||||
with patch("arborist.sources.crawler.bridge.httpx.Client", _patched_httpx_get(responses)):
|
||||
conn = connect(db)
|
||||
try:
|
||||
result = recrawl_check(conn, limit=10)
|
||||
|
|
@ -512,7 +512,7 @@ def test_recrawl_check_domain_filter(tmp_path):
|
|||
"https://keep.com/a": {"head_status": 304},
|
||||
# drop.com would error if the filter is broken; we expect it to be skipped.
|
||||
}
|
||||
with patch("aborist.sources.crawler.bridge.httpx.Client", _patched_httpx_get(responses)):
|
||||
with patch("arborist.sources.crawler.bridge.httpx.Client", _patched_httpx_get(responses)):
|
||||
conn = connect(db)
|
||||
try:
|
||||
result = recrawl_check(conn, domain="keep.com", limit=10)
|
||||
|
|
@ -531,7 +531,7 @@ def test_recrawl_check_updates_last_checked_at(tmp_path):
|
|||
conn.close()
|
||||
|
||||
responses = {"https://x.com/s": {"head_status": 304}}
|
||||
with patch("aborist.sources.crawler.bridge.httpx.Client", _patched_httpx_get(responses)):
|
||||
with patch("arborist.sources.crawler.bridge.httpx.Client", _patched_httpx_get(responses)):
|
||||
conn = connect(db)
|
||||
try:
|
||||
recrawl_check(conn, limit=10)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ Tests URL extraction, sources footer building, and web fetch helpers.
|
|||
|
||||
import pytest
|
||||
from unittest.mock import Mock, AsyncMock, patch
|
||||
from aborist.sources.crawler.web_fetch import (
|
||||
from arborist.sources.crawler.web_fetch import (
|
||||
extract_url_from_content,
|
||||
is_web_fetch_request,
|
||||
build_sources_footer,
|
||||
|
|
@ -85,8 +85,8 @@ class TestURLExtraction:
|
|||
|
||||
@pytest.mark.skip(
|
||||
reason="chat-bot fetch triggers (has_web_fetch_trigger) were "
|
||||
"dropped during the aborist lift — aborist has no chat surface; "
|
||||
"see aborist/sources/crawler/__init__.py for rationale."
|
||||
"dropped during the arborist lift — arborist has no chat surface; "
|
||||
"see arborist/sources/crawler/__init__.py for rationale."
|
||||
)
|
||||
class TestWebFetchDetection:
|
||||
"""Test web fetch request detection."""
|
||||
|
|
@ -106,8 +106,8 @@ class TestWebFetchDetection:
|
|||
|
||||
@pytest.mark.skip(
|
||||
reason="chat-bot fresh-fetch triggers (has_fresh_fetch_trigger) were "
|
||||
"dropped during the aborist lift — aborist has no chat surface; "
|
||||
"see aborist/sources/crawler/__init__.py for rationale."
|
||||
"dropped during the arborist lift — arborist has no chat surface; "
|
||||
"see arborist/sources/crawler/__init__.py for rationale."
|
||||
)
|
||||
class TestFreshFetchDetection:
|
||||
"""Test fresh fetch intent detection."""
|
||||
|
|
@ -300,7 +300,7 @@ class TestWebFetchIntegration:
|
|||
"""Test successful fetch and cache operation"""
|
||||
# This would require mocking web_cache_manager
|
||||
# For now, just verify the function exists and has correct signature
|
||||
from aborist.sources.crawler.web_fetch import fetch_and_cache
|
||||
from arborist.sources.crawler.web_fetch import fetch_and_cache
|
||||
import inspect
|
||||
sig = inspect.signature(fetch_and_cache)
|
||||
assert 'url' in sig.parameters
|
||||
|
|
@ -312,7 +312,7 @@ class TestWebFetchIntegration:
|
|||
@pytest.mark.asyncio
|
||||
async def test_fetch_and_cache_with_progress_callback(self):
|
||||
"""Test fetch with progress callback"""
|
||||
from aborist.sources.crawler.web_fetch import fetch_and_cache
|
||||
from arborist.sources.crawler.web_fetch import fetch_and_cache
|
||||
|
||||
# Mock web cache manager
|
||||
mock_manager = Mock()
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import pytest
|
|||
@pytest.fixture(scope="module")
|
||||
def qa_sweep():
|
||||
"""Import bench/qa_sweep.py as a module despite living outside the
|
||||
aborist package. Avoids polluting the package or requiring a
|
||||
arborist package. Avoids polluting the package or requiring a
|
||||
pip-install of the bench harness."""
|
||||
bench_path = Path(__file__).parent.parent / "bench" / "qa_sweep.py"
|
||||
spec = importlib.util.spec_from_file_location("qa_sweep_under_test", bench_path)
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ import time
|
|||
|
||||
import pytest
|
||||
|
||||
from aborist.cli import _burn_cache_key
|
||||
from aborist.store import append_audit, connect, transaction
|
||||
from arborist.cli import _burn_cache_key
|
||||
from arborist.store import append_audit, connect, transaction
|
||||
|
||||
|
||||
def _seed_providence_record(db_path, *, cache_key: str, question: str = "q?", audit_mode: str = "HYBRID") -> None:
|
||||
|
|
@ -250,8 +250,8 @@ def test_burn_preserves_audit_chain(tmp_path):
|
|||
|
||||
|
||||
def test_burn_cli_invocation(tmp_path, capsys):
|
||||
"""`aborist burn` end-to-end through build_parser, including JSON output."""
|
||||
from aborist.cli import build_parser
|
||||
"""`arborist burn` end-to-end through build_parser, including JSON output."""
|
||||
from arborist.cli import build_parser
|
||||
|
||||
db = tmp_path / "qa.db"
|
||||
KEY = "fa" * 32
|
||||
|
|
@ -279,8 +279,8 @@ def test_burn_kindergarten_burns_only_recent_rows(tmp_path, capsys):
|
|||
test seeds two rows, backdates one to 2h ago, and asserts the
|
||||
fresh one burns and the old one stays."""
|
||||
import time as _time
|
||||
from aborist.cli import build_parser
|
||||
from aborist.store import transaction
|
||||
from arborist.cli import build_parser
|
||||
from arborist.store import transaction
|
||||
|
||||
db = tmp_path / "qa.db"
|
||||
OLD = "ab" * 32
|
||||
|
|
@ -326,7 +326,7 @@ def test_burn_kindergarten_burns_only_recent_rows(tmp_path, capsys):
|
|||
|
||||
def test_burn_kindergarten_dry_run_writes_nothing(tmp_path, capsys):
|
||||
"""--dry-run reports what would burn but doesn't write or audit."""
|
||||
from aborist.cli import build_parser
|
||||
from arborist.cli import build_parser
|
||||
|
||||
db = tmp_path / "qa.db"
|
||||
KEY = "ef" * 32
|
||||
|
|
@ -369,7 +369,7 @@ def test_burn_kindergarten_dry_run_writes_nothing(tmp_path, capsys):
|
|||
|
||||
def test_burn_kindergarten_zero_seconds_burns_everything(tmp_path, capsys):
|
||||
"""0-second window = burn every live providence_cache row."""
|
||||
from aborist.cli import build_parser
|
||||
from arborist.cli import build_parser
|
||||
|
||||
db = tmp_path / "qa.db"
|
||||
K1 = "11" * 32
|
||||
|
|
@ -399,7 +399,7 @@ def test_burn_kindergarten_zero_seconds_burns_everything(tmp_path, capsys):
|
|||
def test_burn_kindergarten_respects_children_gate_without_force(tmp_path, capsys):
|
||||
"""A row with falsifications is REFUSED unless --force. Audit chain
|
||||
still grows on burned rows; refused rows leave no audit entry."""
|
||||
from aborist.cli import build_parser
|
||||
from arborist.cli import build_parser
|
||||
|
||||
db = tmp_path / "qa.db"
|
||||
BURNABLE = "33" * 32
|
||||
|
|
@ -423,7 +423,7 @@ def test_burn_kindergarten_respects_children_gate_without_force(tmp_path, capsys
|
|||
|
||||
def test_burn_cli_returns_non_zero_on_refused(tmp_path, capsys):
|
||||
"""Non-zero exit when refused so scripts can detect and react."""
|
||||
from aborist.cli import build_parser
|
||||
from arborist.cli import build_parser
|
||||
|
||||
db = tmp_path / "qa.db"
|
||||
KEY = "fb" * 32
|
||||
|
|
|
|||
|
|
@ -18,13 +18,13 @@ import json
|
|||
import time
|
||||
from typing import Iterator
|
||||
|
||||
from aborist.cli import _burn_core_root, _burn_document_root
|
||||
from aborist.distill import FirstSentenceDistiller
|
||||
from aborist.distill.runner import distill_existing
|
||||
from aborist.document import Document
|
||||
from aborist.ingest import ingest_source
|
||||
from aborist.source import Source
|
||||
from aborist.store import append_audit, connect, transaction
|
||||
from arborist.cli import _burn_core_root, _burn_document_root
|
||||
from arborist.distill import FirstSentenceDistiller
|
||||
from arborist.distill.runner import distill_existing
|
||||
from arborist.document import Document
|
||||
from arborist.ingest import ingest_source
|
||||
from arborist.source import Source
|
||||
from arborist.store import append_audit, connect, transaction
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -56,7 +56,7 @@ def _doc(uri: str, content: str, *, edges=None) -> Document:
|
|||
|
||||
# Long enough that the default tok-512-v1 chunker emits multiple chunks.
|
||||
_LONG = (
|
||||
"Aborist tends trees and forests of cross-linked information. " * 40
|
||||
"Arborist tends trees and forests of cross-linked information. " * 40
|
||||
+ "\n\n"
|
||||
+ "Burn is the kindergarten leaf removal — children gate enforced. " * 40
|
||||
)
|
||||
|
|
@ -460,7 +460,7 @@ def test_burn_document_preserves_audit_chain(tmp_path):
|
|||
|
||||
|
||||
def test_burn_cli_document_invocation(tmp_path, capsys):
|
||||
from aborist.cli import build_parser
|
||||
from arborist.cli import build_parser
|
||||
|
||||
db = tmp_path / "burn.db"
|
||||
root = _ingest_one(db, "html://cli-doc")
|
||||
|
|
@ -484,7 +484,7 @@ def test_burn_cli_document_invocation(tmp_path, capsys):
|
|||
|
||||
|
||||
def test_burn_cli_core_invocation(tmp_path, capsys):
|
||||
from aborist.cli import build_parser
|
||||
from arborist.cli import build_parser
|
||||
|
||||
db = tmp_path / "burn.db"
|
||||
_, core_root = _ingest_and_distill(db, "html://cli-core")
|
||||
|
|
@ -507,7 +507,7 @@ def test_burn_cli_core_invocation(tmp_path, capsys):
|
|||
|
||||
def test_burn_cli_default_kind_is_providence(tmp_path, capsys):
|
||||
"""Backwards compat: bare `burn --cache-key X` still works (no --kind)."""
|
||||
from aborist.cli import build_parser
|
||||
from arborist.cli import build_parser
|
||||
|
||||
db = tmp_path / "burn.db"
|
||||
# Seed a providence record reusing the existing test_burn helper logic.
|
||||
|
|
@ -552,7 +552,7 @@ def test_burn_cli_default_kind_is_providence(tmp_path, capsys):
|
|||
|
||||
|
||||
def test_burn_cli_document_returns_non_zero_on_refused(tmp_path, capsys):
|
||||
from aborist.cli import build_parser
|
||||
from arborist.cli import build_parser
|
||||
|
||||
db = tmp_path / "burn.db"
|
||||
root = _ingest_one(db, "html://cli-refuse")
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ from pathlib import Path
|
|||
|
||||
import pytest
|
||||
|
||||
from aborist.qa.dag import build_run_dag, verify_run_dag
|
||||
from aborist.qa.evidence import (
|
||||
from arborist.qa.dag import build_run_dag, verify_run_dag
|
||||
from arborist.qa.evidence import (
|
||||
build_evidence_map,
|
||||
evidence_map_by_evidence_id,
|
||||
evidence_map_by_pointer_id,
|
||||
|
|
@ -22,11 +22,11 @@ from aborist.qa.evidence import (
|
|||
render_claim_lattice,
|
||||
render_evidence_map,
|
||||
)
|
||||
from aborist.qa.keys import governance_policy_hash
|
||||
from aborist.qa.parse_claims import parse_pointer_claims
|
||||
from aborist.qa.runner import DEFAULT_POLICY
|
||||
from aborist.qa.query import DEFAULT_QUERY_POLICY
|
||||
from aborist.qa.verify import (
|
||||
from arborist.qa.keys import governance_policy_hash
|
||||
from arborist.qa.parse_claims import parse_pointer_claims
|
||||
from arborist.qa.runner import DEFAULT_POLICY
|
||||
from arborist.qa.query import DEFAULT_QUERY_POLICY
|
||||
from arborist.qa.verify import (
|
||||
ANSWER_MODES,
|
||||
DEFAULT_ANSWER_MODE,
|
||||
verify_claim_lattice,
|
||||
|
|
@ -470,7 +470,7 @@ def test_run_dag_root_changes_when_parsed_lattice_changes():
|
|||
|
||||
def test_verifier_method_check_accepts_claim_lattice(tmp_path: Path):
|
||||
"""Fresh shards include 'claim_lattice' in the verifier_method CHECK."""
|
||||
from aborist.store import connect
|
||||
from arborist.store import connect
|
||||
|
||||
db = tmp_path / "fresh.db"
|
||||
conn = connect(db)
|
||||
|
|
@ -486,7 +486,7 @@ def test_verifier_method_check_accepts_claim_lattice(tmp_path: Path):
|
|||
def test_legacy_shard_migrates_in_place(tmp_path: Path):
|
||||
"""An old shard with the pre-claim_lattice CHECK gets rebuilt on open
|
||||
AND keeps run_dag_root / run_dag_blob populated."""
|
||||
from aborist.store import connect
|
||||
from arborist.store import connect
|
||||
|
||||
db = tmp_path / "legacy.db"
|
||||
c = sqlite3.connect(db)
|
||||
|
|
@ -803,12 +803,12 @@ def test_per_chunk_evidence_map_query_path(tmp_path):
|
|||
"""A single source with three chunks should produce three evidence
|
||||
objects in the prompt-facing map — not one whole-doc entry."""
|
||||
from typing import Iterator
|
||||
from aborist.document import Document
|
||||
from aborist.ingest import ingest_source
|
||||
from aborist.qa.client import StubClient
|
||||
from aborist.qa.query import DEFAULT_QUERY_POLICY, query
|
||||
from aborist.source import Source
|
||||
from aborist.store import connect
|
||||
from arborist.document import Document
|
||||
from arborist.ingest import ingest_source
|
||||
from arborist.qa.client import StubClient
|
||||
from arborist.qa.query import DEFAULT_QUERY_POLICY, query
|
||||
from arborist.source import Source
|
||||
from arborist.store import connect
|
||||
|
||||
class FakeSource(Source):
|
||||
source_type = "test"
|
||||
|
|
@ -872,7 +872,7 @@ def test_per_chunk_evidence_map_query_path(tmp_path):
|
|||
|
||||
|
||||
def test_chunk_query_relevance_scores_by_overlap():
|
||||
from aborist.qa.query import _chunk_query_relevance
|
||||
from arborist.qa.query import _chunk_query_relevance
|
||||
|
||||
qstem = {"brachiosaurus", "film"}
|
||||
# Many distinct + many mentions wins.
|
||||
|
|
@ -895,7 +895,7 @@ def test_chunk_query_relevance_scores_by_overlap():
|
|||
|
||||
|
||||
def test_chunk_query_relevance_stem_aware():
|
||||
from aborist.qa.query import _chunk_query_relevance
|
||||
from arborist.qa.query import _chunk_query_relevance
|
||||
|
||||
# Query has the plural "dinosaurs"; chunk has the singular "dinosaur".
|
||||
# The stem strip in _body_count_with_stem should still match.
|
||||
|
|
@ -911,16 +911,16 @@ def test_query_orders_chunks_by_relevance_g03(tmp_path):
|
|||
of the document — G0.3 must promote it to E1 so a model that
|
||||
lazy-anchors on early pointers still cites the relevant chunk."""
|
||||
from typing import Iterator
|
||||
from aborist.document import Document
|
||||
from aborist.ingest import ingest_source
|
||||
from aborist.qa.client import StubClient
|
||||
from aborist.qa.evidence import build_evidence_map
|
||||
from aborist.qa.query import (
|
||||
from arborist.document import Document
|
||||
from arborist.ingest import ingest_source
|
||||
from arborist.qa.client import StubClient
|
||||
from arborist.qa.evidence import build_evidence_map
|
||||
from arborist.qa.query import (
|
||||
DEFAULT_QUERY_POLICY, _load_doc_chunks, _chunk_query_relevance,
|
||||
)
|
||||
from aborist.qa.query import _stem_token_for_match, _title_query_tokens
|
||||
from aborist.source import Source
|
||||
from aborist.store import connect
|
||||
from arborist.qa.query import _stem_token_for_match, _title_query_tokens
|
||||
from arborist.source import Source
|
||||
from arborist.store import connect
|
||||
|
||||
class FakeSource(Source):
|
||||
source_type = "test"
|
||||
|
|
@ -1066,7 +1066,7 @@ def test_lazy_anchor_signals_not_in_run_dag_payload():
|
|||
signals recoverable from claim_statuses, and threading them in
|
||||
would make run_dag_root depend on the model's anchoring habits."""
|
||||
import inspect as _inspect
|
||||
from aborist.qa.dag import build_run_dag
|
||||
from arborist.qa.dag import build_run_dag
|
||||
sig = _inspect.signature(build_run_dag)
|
||||
params = set(sig.parameters)
|
||||
assert "pointer_id_distribution" not in params
|
||||
|
|
@ -1078,11 +1078,11 @@ def test_per_chunk_evidence_map_uses_distinct_chunk_roots(tmp_path):
|
|||
different chunks of the same source produce different evidence_ids
|
||||
even though they share source_root."""
|
||||
from typing import Iterator
|
||||
from aborist.document import Document
|
||||
from aborist.ingest import ingest_source
|
||||
from aborist.qa.query import _load_doc_chunks
|
||||
from aborist.source import Source
|
||||
from aborist.store import connect
|
||||
from arborist.document import Document
|
||||
from arborist.ingest import ingest_source
|
||||
from arborist.qa.query import _load_doc_chunks
|
||||
from arborist.source import Source
|
||||
from arborist.store import connect
|
||||
|
||||
class FakeSource(Source):
|
||||
source_type = "test"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""Pretty-print + ensure_ascii=False on `aborist query` output.
|
||||
"""Pretty-print + ensure_ascii=False on `arborist query` output.
|
||||
|
||||
The CLI default emits a human-readable render of the query result;
|
||||
``--json`` switches to the raw record. Both paths must:
|
||||
|
|
@ -12,7 +12,7 @@ import json
|
|||
|
||||
import pytest
|
||||
|
||||
from aborist.cli import _render_query_human
|
||||
from arborist.cli import _render_query_human
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -139,7 +139,7 @@ def test_render_includes_short_cache_key_with_pointer_to_json():
|
|||
def test_unicode_round_trips_through_print_path(capsys):
|
||||
"""Pin: a result with a Pokémon-style answer prints é, not \\u00e9.
|
||||
Uses argparse via build_parser to exercise the real CLI dispatch."""
|
||||
from aborist.cli import build_parser
|
||||
from arborist.cli import build_parser
|
||||
|
||||
parser = build_parser()
|
||||
# Build a fake result and call _cmd_query's render path indirectly.
|
||||
|
|
|
|||
|
|
@ -4,16 +4,16 @@ from __future__ import annotations
|
|||
|
||||
import sqlite3
|
||||
|
||||
from aborist.compress import (
|
||||
from arborist.compress import (
|
||||
_MIN_COMPRESS_BYTES,
|
||||
_ZSTD_MAGIC,
|
||||
is_compressed,
|
||||
pack_chunk,
|
||||
unpack_chunk,
|
||||
)
|
||||
from aborist.ingest import ingest_source
|
||||
from aborist.sources.wikipedia_xml import WikipediaXmlDump
|
||||
from aborist.store import connect
|
||||
from arborist.ingest import ingest_source
|
||||
from arborist.sources.wikipedia_xml import WikipediaXmlDump
|
||||
from arborist.store import connect
|
||||
|
||||
|
||||
def test_pack_passes_short_text_through_uncompressed():
|
||||
|
|
@ -107,7 +107,7 @@ def test_ingest_writes_compressed_for_large_chunks(tmp_path):
|
|||
fixture = tmp_path / "wp.xml"
|
||||
fixture.write_text(xml, encoding="utf-8")
|
||||
|
||||
db_path = tmp_path / "aborist.db"
|
||||
db_path = tmp_path / "arborist.db"
|
||||
conn = connect(db_path)
|
||||
try:
|
||||
ingest_source(conn, WikipediaXmlDump(fixture))
|
||||
|
|
@ -131,7 +131,7 @@ def test_ingest_then_search_round_trip_finds_compressed_doc(tmp_path):
|
|||
"""FTS5 still indexes plaintext; chunks.content stays compressed; reads
|
||||
that go through unpack_chunk reconstruct the original. The whole
|
||||
pipeline keeps working."""
|
||||
from aborist.search import FTS5Backend
|
||||
from arborist.search import FTS5Backend
|
||||
|
||||
big_body = "merkle providence wikipedia anarchism " * 200 # ~7.4 KB
|
||||
xml = f"""<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
|
@ -150,7 +150,7 @@ def test_ingest_then_search_round_trip_finds_compressed_doc(tmp_path):
|
|||
fixture = tmp_path / "wp.xml"
|
||||
fixture.write_text(xml, encoding="utf-8")
|
||||
|
||||
db_path = tmp_path / "aborist.db"
|
||||
db_path = tmp_path / "arborist.db"
|
||||
conn = connect(db_path)
|
||||
try:
|
||||
ingest_source(conn, WikipediaXmlDump(fixture))
|
||||
|
|
|
|||
|
|
@ -13,15 +13,15 @@ from pathlib import Path
|
|||
|
||||
import pytest
|
||||
|
||||
from aborist.concepts import invalidate_cache as _invalidate_cache
|
||||
from aborist.concepts.query import invalidate_cache
|
||||
from aborist.concepts.seed import seed_legacy_concepts
|
||||
from aborist.qa.concepts import (
|
||||
from arborist.concepts import invalidate_cache as _invalidate_cache
|
||||
from arborist.concepts.query import invalidate_cache
|
||||
from arborist.concepts.seed import seed_legacy_concepts
|
||||
from arborist.qa.concepts import (
|
||||
has_compare_phrasing,
|
||||
rivalry_excluded,
|
||||
synonym_expand,
|
||||
)
|
||||
from aborist.store import connect
|
||||
from arborist.store import connect
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -171,6 +171,6 @@ def test_invalidate_cache_does_not_break_lookup(seeded_shards):
|
|||
|
||||
def test_export_invalidate_cache_alias(seeded_shards):
|
||||
"""The package-level ``invalidate_cache`` is the same callable as
|
||||
``aborist.concepts.query.invalidate_cache`` — exposed at top level
|
||||
``arborist.concepts.query.invalidate_cache`` — exposed at top level
|
||||
for callers that don't want to import the implementation module."""
|
||||
assert _invalidate_cache is invalidate_cache
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ exactly as recorded.
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from aborist.qa.dag import (
|
||||
from arborist.qa.dag import (
|
||||
build_run_dag,
|
||||
localize_failure,
|
||||
preflight_node_hash,
|
||||
|
|
@ -262,7 +262,7 @@ def test_preflight_node_hash_changes_with_policy_refs():
|
|||
def test_preflight_node_hash_includes_node_version():
|
||||
"""node_version field is part of the hashed payload so a future
|
||||
schema bump (preflight-node-v2 etc.) invalidates legacy nodes."""
|
||||
from aborist.qa.dag import (
|
||||
from arborist.qa.dag import (
|
||||
PREFLIGHT_NODE_VERSION,
|
||||
build_preflight_node_payload,
|
||||
)
|
||||
|
|
@ -373,7 +373,7 @@ def test_reject_run_dag_three_stage_shape():
|
|||
question → preflight → final_label. Audit replay can read the
|
||||
stage list and tell instantly that this row is a preflight
|
||||
rejection (3 stages = reject path)."""
|
||||
from aborist.qa.dag import build_reject_run_dag, preflight_node_hash
|
||||
from arborist.qa.dag import build_reject_run_dag, preflight_node_hash
|
||||
pre = preflight_node_hash(
|
||||
question_state={"preflight_result": "PREFLIGHT_BLOCKED"},
|
||||
quantifier={"intensity": "ALL", "is_broad": True,
|
||||
|
|
@ -396,7 +396,7 @@ def test_reject_run_dag_root_changes_with_preflight_hash():
|
|||
"""Two reject runs that differ only in the preflight payload
|
||||
(e.g. different policy state at rejection time) must produce
|
||||
different run_dag_root values."""
|
||||
from aborist.qa.dag import build_reject_run_dag, preflight_node_hash
|
||||
from arborist.qa.dag import build_reject_run_dag, preflight_node_hash
|
||||
pre_a = preflight_node_hash(
|
||||
question_state={"preflight_result": "PREFLIGHT_BLOCKED"},
|
||||
quantifier={"intensity": "ALL"},
|
||||
|
|
@ -422,7 +422,7 @@ def test_reject_run_dag_round_trips_through_verify():
|
|||
"""3-stage reject DAG must verify the same way as the standard
|
||||
7/9/8/10-stage shapes."""
|
||||
import json
|
||||
from aborist.qa.dag import build_reject_run_dag, preflight_node_hash
|
||||
from arborist.qa.dag import build_reject_run_dag, preflight_node_hash
|
||||
pre = preflight_node_hash(
|
||||
question_state={"preflight_result": "PREFLIGHT_BLOCKED"},
|
||||
quantifier={"intensity": "ALL"},
|
||||
|
|
@ -442,8 +442,8 @@ def test_reject_run_dag_round_trips_through_verify():
|
|||
def test_extract_preflight_hash_from_blob_with_preflight():
|
||||
"""Pull preflight stage hash out of a persisted run_dag_blob."""
|
||||
import json
|
||||
from aborist.qa.dag import build_run_dag, preflight_node_hash
|
||||
from aborist.qa.query import _extract_preflight_hash_from_blob
|
||||
from arborist.qa.dag import build_run_dag, preflight_node_hash
|
||||
from arborist.qa.query import _extract_preflight_hash_from_blob
|
||||
pre = preflight_node_hash(
|
||||
question_state={"preflight_result": "PREFLIGHT_OK"},
|
||||
quantifier={"intensity": "SINGULAR"},
|
||||
|
|
@ -458,14 +458,14 @@ def test_extract_preflight_hash_returns_none_for_legacy_blob():
|
|||
"""Legacy blobs (no preflight stage) return None — this is the
|
||||
fall-through path for cache rows written before #000009."""
|
||||
import json
|
||||
from aborist.qa.query import _extract_preflight_hash_from_blob
|
||||
from arborist.qa.query import _extract_preflight_hash_from_blob
|
||||
out = build_run_dag(**_kw()) # no preflight_hash
|
||||
blob = json.dumps(out, separators=(",", ":"))
|
||||
assert _extract_preflight_hash_from_blob(blob) is None
|
||||
|
||||
|
||||
def test_extract_preflight_hash_returns_none_for_empty_or_invalid():
|
||||
from aborist.qa.query import _extract_preflight_hash_from_blob
|
||||
from arborist.qa.query import _extract_preflight_hash_from_blob
|
||||
assert _extract_preflight_hash_from_blob(None) is None
|
||||
assert _extract_preflight_hash_from_blob("") is None
|
||||
assert _extract_preflight_hash_from_blob("not json {{{") is None
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ def test_d1_verify_quotes_signature_has_no_chat_client():
|
|||
"""The hard verifier never accepts a chat-client / LLM judge.
|
||||
A future PR that adds a chat_client parameter to verify_quotes
|
||||
would silently introduce a soft-signal-as-hard-check leak."""
|
||||
from aborist.qa.verify import verify_quotes
|
||||
from arborist.qa.verify import verify_quotes
|
||||
|
||||
sig = inspect.signature(verify_quotes)
|
||||
forbidden = {"chat_client", "client", "llm", "judge", "model"}
|
||||
|
|
@ -37,7 +37,7 @@ def test_d1_verify_quotes_signature_has_no_chat_client():
|
|||
|
||||
|
||||
def test_d1_verify_claim_lattice_signature_has_no_chat_client():
|
||||
from aborist.qa.verify import verify_claim_lattice
|
||||
from arborist.qa.verify import verify_claim_lattice
|
||||
|
||||
sig = inspect.signature(verify_claim_lattice)
|
||||
forbidden = {"chat_client", "client", "llm", "judge", "model"}
|
||||
|
|
@ -49,7 +49,7 @@ def test_d1_verify_claim_lattice_signature_has_no_chat_client():
|
|||
|
||||
|
||||
def test_d1_verify_claim_lattice_json_signature_has_no_chat_client():
|
||||
from aborist.qa.verify import verify_claim_lattice_json
|
||||
from arborist.qa.verify import verify_claim_lattice_json
|
||||
|
||||
sig = inspect.signature(verify_claim_lattice_json)
|
||||
forbidden = {"chat_client", "client", "llm", "judge", "model"}
|
||||
|
|
@ -65,7 +65,7 @@ def test_d1_verifier_method_enum_excludes_llm_judges():
|
|||
include any LLM-as-judge token. If a future migration adds
|
||||
`llm`, `model`, `judge`, or `nli` to the enum, the verifier
|
||||
started accepting soft signals into the hard chain."""
|
||||
from aborist.store import SCHEMA_SQL
|
||||
from arborist.store import SCHEMA_SQL
|
||||
|
||||
# Find the verifier_method CHECK constraint in SCHEMA_SQL.
|
||||
match = re.search(
|
||||
|
|
@ -97,14 +97,14 @@ def test_d1_verifier_method_enum_excludes_llm_judges():
|
|||
def test_d2_answer_modes_include_lattice_variants():
|
||||
"""ANSWER_MODES must contain both lattice variants so an agent
|
||||
can pick a pointer-clause-emitting mode."""
|
||||
from aborist.qa.verify import ANSWER_MODES
|
||||
from arborist.qa.verify import ANSWER_MODES
|
||||
|
||||
assert "claim_lattice_pointer" in ANSWER_MODES
|
||||
assert "claim_lattice" in ANSWER_MODES
|
||||
|
||||
|
||||
def test_d2_default_answer_mode_is_a_known_mode():
|
||||
from aborist.qa.verify import ANSWER_MODES, DEFAULT_ANSWER_MODE
|
||||
from arborist.qa.verify import ANSWER_MODES, DEFAULT_ANSWER_MODE
|
||||
|
||||
assert DEFAULT_ANSWER_MODE in ANSWER_MODES
|
||||
|
||||
|
|
@ -113,7 +113,7 @@ def test_d2_pointer_parser_exists_and_returns_claim_nodes():
|
|||
"""The pointer-line parser is the surface that turns model
|
||||
output into internal claim nodes. Its absence would mean
|
||||
Hermes has nowhere to emit pointer clauses to."""
|
||||
from aborist.qa.parse_claims import parse_pointer_claims
|
||||
from arborist.qa.parse_claims import parse_pointer_claims
|
||||
|
||||
sample = "Steve Jobs co-founded Apple. [E1]\n"
|
||||
out = parse_pointer_claims(sample)
|
||||
|
|
@ -136,7 +136,7 @@ def test_d3_runtime_owns_claim_lattice_construction():
|
|||
pointer mode — it produces prose that the runtime structures.
|
||||
A regression that asks the model for structured JSON in pointer
|
||||
mode would violate this directive."""
|
||||
from aborist.qa.parse_claims import parse_pointer_claims
|
||||
from arborist.qa.parse_claims import parse_pointer_claims
|
||||
|
||||
multi_line = (
|
||||
"Steve Jobs co-founded Apple. [E1]\n"
|
||||
|
|
@ -158,7 +158,7 @@ def test_d3_evidence_map_built_from_runtime_chunks_not_model_input():
|
|||
chunks, not threaded in from the model's prompt-side
|
||||
invention. Two chunks → two evidence objects with deterministic
|
||||
pointer ids."""
|
||||
from aborist.qa.evidence import build_evidence_map
|
||||
from arborist.qa.evidence import build_evidence_map
|
||||
|
||||
chunks = [
|
||||
{
|
||||
|
|
@ -198,7 +198,7 @@ def test_d4_run_dag_carries_evidence_map_root():
|
|||
"""The 9-stage CTI run-DAG must commit the evidence_map_root.
|
||||
Without this, two runs whose retrieval surfaced different chunks
|
||||
would produce indistinguishable run_dag_roots."""
|
||||
from aborist.qa.dag import build_run_dag
|
||||
from arborist.qa.dag import build_run_dag
|
||||
|
||||
sig = inspect.signature(build_run_dag)
|
||||
assert "evidence_map_root" in sig.parameters, (
|
||||
|
|
@ -216,7 +216,7 @@ def test_d4_retrieval_plan_binding_landed():
|
|||
Two runs with identical sources but different retrieval keywords
|
||||
produce different `run_dag_root` values — provenance closes the
|
||||
'how did retrieval choose these sources' gap."""
|
||||
from aborist.qa.dag import build_run_dag
|
||||
from arborist.qa.dag import build_run_dag
|
||||
|
||||
sig = inspect.signature(build_run_dag)
|
||||
assert "retrieval_plan_hash" in sig.parameters, (
|
||||
|
|
@ -251,8 +251,8 @@ def test_d4_retrieval_plan_binding_landed():
|
|||
|
||||
def test_d4_retrieval_plan_hash_module_exists():
|
||||
"""RetrievalPlan dataclass + retrieval_plan_hash function landed
|
||||
in `aborist.qa.retrieval_plan` per ticket #000001."""
|
||||
from aborist.qa.retrieval_plan import RetrievalPlan, retrieval_plan_hash
|
||||
in `arborist.qa.retrieval_plan` per ticket #000001."""
|
||||
from arborist.qa.retrieval_plan import RetrievalPlan, retrieval_plan_hash
|
||||
|
||||
plan = RetrievalPlan(
|
||||
retrieval_keywords="orwell 1984",
|
||||
|
|
@ -285,8 +285,8 @@ def test_d5_verify_claim_lattice_is_deterministic():
|
|||
"""Same inputs → same verdict, byte-for-byte. No randomness, no
|
||||
time-dependent state, no hidden global. Regression here means
|
||||
the verifier started consulting some non-deterministic source."""
|
||||
from aborist.qa.evidence import build_evidence_map
|
||||
from aborist.qa.verify import verify_claim_lattice
|
||||
from arborist.qa.evidence import build_evidence_map
|
||||
from arborist.qa.verify import verify_claim_lattice
|
||||
|
||||
chunks = [{
|
||||
"source_root": "f" * 64,
|
||||
|
|
@ -310,7 +310,7 @@ def test_d5_seven_hard_checks_are_pure_functions():
|
|||
"""Spot check: the per-check helper for citation overlap
|
||||
(`_claim_textually_overlaps_evidence`) is a pure function — no
|
||||
network, no chat client, no global state."""
|
||||
from aborist.qa.verify import _claim_textually_overlaps_evidence
|
||||
from arborist.qa.verify import _claim_textually_overlaps_evidence
|
||||
|
||||
sig = inspect.signature(_claim_textually_overlaps_evidence)
|
||||
forbidden = {"chat_client", "client", "llm", "judge", "model"}
|
||||
|
|
@ -326,7 +326,7 @@ def test_d6_warrant_check_exists_and_is_lexical():
|
|||
"""warrant_check must exist as a deterministic lexical check.
|
||||
A future replacement that imports an NLI model into the hard
|
||||
path would violate D6's 'before NLI' constraint."""
|
||||
from aborist.qa.warrant import warrant_check
|
||||
from arborist.qa.warrant import warrant_check
|
||||
|
||||
sig = inspect.signature(warrant_check)
|
||||
forbidden = {"chat_client", "client", "llm", "judge", "model", "nli"}
|
||||
|
|
@ -342,7 +342,7 @@ def test_d6_warrant_fires_on_date_anchor_mismatch():
|
|||
class. This case is the deterministic anchor (year strings
|
||||
are case-trivial; ALL-match semantics). Regression here means
|
||||
the date-anchor extractor or check silently broke."""
|
||||
from aborist.qa.warrant import warrant_check
|
||||
from arborist.qa.warrant import warrant_check
|
||||
|
||||
# Claim asserts "1985"; cited span has no 1985 anywhere.
|
||||
ok, missing = warrant_check(
|
||||
|
|
@ -365,7 +365,7 @@ def test_d6_warrant_relation_shape_fires_when_all_anchors_missing():
|
|||
extracted anchor must appear in some cited span. This test
|
||||
exercises a true failure where the claim's only proper-noun
|
||||
anchor is missing from every cited span."""
|
||||
from aborist.qa.warrant import warrant_check
|
||||
from arborist.qa.warrant import warrant_check
|
||||
|
||||
ok, missing = warrant_check(
|
||||
claim_text="Mr. Burns is the boss.",
|
||||
|
|
@ -386,7 +386,7 @@ def test_d6_warrant_generalization_landed():
|
|||
"""Per-shape warrant detectors landed via ticket #000003.
|
||||
Entity-list / count / why-cause shapes now dispatch through
|
||||
warrant_check alongside the original relation + date classes."""
|
||||
import aborist.qa.warrant as warrant_mod
|
||||
import arborist.qa.warrant as warrant_mod
|
||||
|
||||
expected = (
|
||||
"_question_is_entity_list_shape",
|
||||
|
|
@ -416,7 +416,7 @@ def test_d7_renderer_relabels_strict_for_claim_lattice():
|
|||
(POINTER-LINKED / ANCHOR-WARRANTED / EVIDENCE-WARRANTED) replaces
|
||||
the previous two-rung EVIDENCE-LINKED/EVIDENCE-LINKED-PARTIAL
|
||||
surface as of #000005."""
|
||||
from aborist.cli import _render_audit_label
|
||||
from arborist.cli import _render_audit_label
|
||||
|
||||
# No violations + STRICT → top rung.
|
||||
assert "EVIDENCE-WARRANTED" in _render_audit_label("STRICT", "claim_lattice", [])
|
||||
|
|
@ -431,7 +431,7 @@ def test_d7_renderer_keeps_strict_for_pinned_span_methods():
|
|||
"""Quote / span / entity / paraphrase verify against pinned
|
||||
spans, not synthesis. STRICT is honest there; renderer must
|
||||
NOT apply the ladder relabel."""
|
||||
from aborist.cli import _render_audit_label
|
||||
from arborist.cli import _render_audit_label
|
||||
|
||||
for method in ("quote", "span", "entity", "paraphrase"):
|
||||
label = _render_audit_label("STRICT", method, [])
|
||||
|
|
@ -447,7 +447,7 @@ def test_d7_audit_mode_enum_canonical_set():
|
|||
"""Schema column must keep the canonical 3-value enum so v9.8
|
||||
cache_key invariants hold. Renderer-level relabel (above)
|
||||
doesn't touch this."""
|
||||
from aborist.store import SCHEMA_SQL
|
||||
from arborist.store import SCHEMA_SQL
|
||||
|
||||
match = re.search(
|
||||
r"audit_mode\s+TEXT[^,]+CHECK\s*\(\s*audit_mode\s+IN\s*\(([^)]+)\)\)",
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@ from __future__ import annotations
|
|||
import json
|
||||
from typing import Iterator
|
||||
|
||||
from aborist.distill import FirstSentenceDistiller
|
||||
from aborist.distill.runner import distill_existing
|
||||
from aborist.document import Document
|
||||
from aborist.ingest import ingest_source
|
||||
from aborist.merkle import proof_from_dict, verify_proof
|
||||
from aborist.source import Source
|
||||
from aborist.store import connect
|
||||
from arborist.distill import FirstSentenceDistiller
|
||||
from arborist.distill.runner import distill_existing
|
||||
from arborist.document import Document
|
||||
from arborist.ingest import ingest_source
|
||||
from arborist.merkle import proof_from_dict, verify_proof
|
||||
from arborist.source import Source
|
||||
from arborist.store import connect
|
||||
|
||||
|
||||
class FakeSource(Source):
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@ from __future__ import annotations
|
|||
import json
|
||||
from typing import Iterator
|
||||
|
||||
from aborist.distill import FirstSentenceDistiller
|
||||
from aborist.distill.runner import distill_existing
|
||||
from aborist.document import Document
|
||||
from aborist.ingest import ingest_source
|
||||
from aborist.merkle import proof_from_dict, verify_proof
|
||||
from aborist.source import Source
|
||||
from aborist.store import connect
|
||||
from arborist.distill import FirstSentenceDistiller
|
||||
from arborist.distill.runner import distill_existing
|
||||
from arborist.document import Document
|
||||
from arborist.ingest import ingest_source
|
||||
from arborist.merkle import proof_from_dict, verify_proof
|
||||
from arborist.source import Source
|
||||
from arborist.store import connect
|
||||
|
||||
|
||||
class FakeSource(Source):
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@ from __future__ import annotations
|
|||
import json
|
||||
from typing import Iterator
|
||||
|
||||
from aborist.distill import FirstSentenceDistiller
|
||||
from aborist.distill.runner import distill_existing
|
||||
from aborist.document import Document
|
||||
from aborist.evict import evict_to_cold, rehydrate
|
||||
from aborist.ingest import ingest_source
|
||||
from aborist.source import Source
|
||||
from aborist.store import connect
|
||||
from arborist.distill import FirstSentenceDistiller
|
||||
from arborist.distill.runner import distill_existing
|
||||
from arborist.document import Document
|
||||
from arborist.evict import evict_to_cold, rehydrate
|
||||
from arborist.ingest import ingest_source
|
||||
from arborist.source import Source
|
||||
from arborist.store import connect
|
||||
|
||||
|
||||
class FakeSource(Source):
|
||||
|
|
@ -226,7 +226,7 @@ def test_rehydrate_nothing_to_do_when_all_hot(tmp_path):
|
|||
|
||||
def test_rehydrate_non_rehydratable_source(tmp_path):
|
||||
"""A source_type without a registered fetcher (and no override) is honest about it."""
|
||||
from aborist.source import Source
|
||||
from arborist.source import Source
|
||||
|
||||
class WikiSource(Source):
|
||||
source_type = "wikipedia_cur"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ indicators.
|
|||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from aborist.qa.frame import (
|
||||
from arborist.qa.frame import (
|
||||
FrameDetection,
|
||||
_body_indicates_reference_work,
|
||||
_source_is_reference_work,
|
||||
|
|
|
|||
|
|
@ -13,15 +13,15 @@ from pathlib import Path
|
|||
|
||||
import pytest
|
||||
|
||||
from aborist.ingest import ingest_source
|
||||
from aborist.sources.grok import (
|
||||
from arborist.ingest import ingest_source
|
||||
from arborist.sources.grok import (
|
||||
GrokExportSource,
|
||||
GrokMediaPostsSource,
|
||||
_extract_urls,
|
||||
_normalize_ts,
|
||||
_scrub,
|
||||
)
|
||||
from aborist.store import connect
|
||||
from arborist.store import connect
|
||||
|
||||
|
||||
def _make_export(tmp_path: Path) -> Path:
|
||||
|
|
@ -224,7 +224,7 @@ def test_resolve_missing_path_raises(tmp_path):
|
|||
def test_grok_ingest_round_trip(tmp_path):
|
||||
"""End-to-end: ingest a tiny export into a fresh DB, verify counts."""
|
||||
root = _make_export(tmp_path)
|
||||
db_path = tmp_path / "aborist.db"
|
||||
db_path = tmp_path / "arborist.db"
|
||||
conn = connect(db_path)
|
||||
try:
|
||||
conv_stats = ingest_source(conn, GrokExportSource(path=root))
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import pytest
|
|||
# Skip if optional extras are not installed.
|
||||
selectolax = pytest.importorskip("selectolax")
|
||||
|
||||
from aborist.sources.html_page import parse_html
|
||||
from arborist.sources.html_page import parse_html
|
||||
|
||||
|
||||
SAMPLE_HTML = """<!doctype html>
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ from __future__ import annotations
|
|||
|
||||
from typing import Iterator
|
||||
|
||||
from aborist.document import Document, Edge
|
||||
from aborist.ingest import ingest_source, verify_random_sample
|
||||
from aborist.search import FTS5Backend
|
||||
from aborist.search.base import AuditMode
|
||||
from aborist.source import Source
|
||||
from aborist.store import connect, stats
|
||||
from arborist.document import Document, Edge
|
||||
from arborist.ingest import ingest_source, verify_random_sample
|
||||
from arborist.search import FTS5Backend
|
||||
from arborist.search.base import AuditMode
|
||||
from arborist.source import Source
|
||||
from arborist.store import connect, stats
|
||||
|
||||
|
||||
class FakeSource(Source):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"""Sidecar diagnostic for unverified spans.
|
||||
|
||||
`aborist inspect --cache-key X` reads a providence_cache record, pulls
|
||||
`arborist inspect --cache-key X` reads a providence_cache record, pulls
|
||||
the same source chunks the verifier saw, and classifies each
|
||||
unverified_quote into one of: verbatim_in_base, verbatim_in_raw_only,
|
||||
trailing_artifact, paraphrase, partial_paraphrase, no_overlap.
|
||||
|
|
@ -18,7 +18,7 @@ from pathlib import Path
|
|||
|
||||
import pytest
|
||||
|
||||
from aborist.qa.inspect import (
|
||||
from arborist.qa.inspect import (
|
||||
_classify_span,
|
||||
_normalize,
|
||||
diagnose_deflection,
|
||||
|
|
@ -26,7 +26,7 @@ from aborist.qa.inspect import (
|
|||
diagnose_title_relevance,
|
||||
inspect_cache_key,
|
||||
)
|
||||
from aborist.store import append_audit, connect, transaction
|
||||
from arborist.store import append_audit, connect, transaction
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -303,7 +303,7 @@ def _seed_doc(shard: Path, *, document_root: str, document_uri: str,
|
|||
"""Insert a documents row + one hot chunk with the given text."""
|
||||
conn = connect(shard)
|
||||
try:
|
||||
from aborist.compress import pack_chunk
|
||||
from arborist.compress import pack_chunk
|
||||
|
||||
with transaction(conn):
|
||||
conn.execute(
|
||||
|
|
@ -666,7 +666,7 @@ def test_metaphor_deflection_filters_common_ly_nouns():
|
|||
"""Naive .endswith('ly') would pick up 'butterfly', 'family',
|
||||
'italy', 'july' etc. as adverbs. The block-list filters them so
|
||||
they don't inflate cue count."""
|
||||
from aborist.qa.inspect import _extract_metaphor_cues
|
||||
from arborist.qa.inspect import _extract_metaphor_cues
|
||||
cues = _extract_metaphor_cues(
|
||||
"the butterfly flew over italy in july with the family"
|
||||
)
|
||||
|
|
@ -694,7 +694,7 @@ def test_metaphor_deflection_under_threshold_returns_no_signal():
|
|||
def test_register_metaphor_dictionary_unions_into_wordlist(tmp_path):
|
||||
"""Custom wordlist registers, unions into the default, and the
|
||||
suffix tests pick up domain-specific stems."""
|
||||
import aborist.qa.inspect as m
|
||||
import arborist.qa.inspect as m
|
||||
|
||||
# Reset cache so the test sees a clean slate.
|
||||
saved_cache = m._english_wordlist_cache
|
||||
|
|
@ -722,10 +722,10 @@ def test_register_metaphor_dictionary_unions_into_wordlist(tmp_path):
|
|||
m._extra_dict_paths = saved_extra
|
||||
|
||||
|
||||
def test_aborist_metaphor_dicts_env_var_supplements(monkeypatch, tmp_path):
|
||||
"""Setting ABORIST_METAPHOR_DICTS=path1:path2 unions both into
|
||||
def test_arborist_metaphor_dicts_env_var_supplements(monkeypatch, tmp_path):
|
||||
"""Setting ARBORIST_METAPHOR_DICTS=path1:path2 unions both into
|
||||
the default wordlist on first lookup."""
|
||||
import aborist.qa.inspect as m
|
||||
import arborist.qa.inspect as m
|
||||
|
||||
saved_cache = m._english_wordlist_cache
|
||||
m._english_wordlist_cache = None
|
||||
|
|
@ -735,7 +735,7 @@ def test_aborist_metaphor_dicts_env_var_supplements(monkeypatch, tmp_path):
|
|||
d1.write_text("widgetspeak\n")
|
||||
d2 = tmp_path / "d2.txt"
|
||||
d2.write_text("frizzlebop\n")
|
||||
monkeypatch.setenv("ABORIST_METAPHOR_DICTS", f"{d1}:{d2}")
|
||||
monkeypatch.setenv("ARBORIST_METAPHOR_DICTS", f"{d1}:{d2}")
|
||||
|
||||
wl = m._english_wordlist()
|
||||
assert "widgetspeak" in wl
|
||||
|
|
@ -747,7 +747,7 @@ def test_aborist_metaphor_dicts_env_var_supplements(monkeypatch, tmp_path):
|
|||
def test_register_metaphor_dictionary_idempotent(tmp_path):
|
||||
"""Registering the same path twice is a no-op (cache invalidates
|
||||
once, second call is a no-op since path already in list)."""
|
||||
import aborist.qa.inspect as m
|
||||
import arborist.qa.inspect as m
|
||||
|
||||
saved_extra = list(m._extra_dict_paths)
|
||||
saved_cache = m._english_wordlist_cache
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ The contract being tested is the unfirehose/1.0 schema:
|
|||
- one message per subsequent line
|
||||
- session_end system message on close
|
||||
- ``$schema: "unfirehose/1.0"`` on every record
|
||||
- aborist-specific extras under namespaced ``aborist_meta``
|
||||
- arborist-specific extras under namespaced ``arborist_meta``
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -13,7 +13,7 @@ from __future__ import annotations
|
|||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from aborist.journal import (
|
||||
from arborist.journal import (
|
||||
HARNESS_NAME,
|
||||
UNFIREHOSE_SCHEMA,
|
||||
SessionWriter,
|
||||
|
|
@ -27,7 +27,7 @@ def _read_lines(path: Path) -> list[dict]:
|
|||
|
||||
|
||||
def test_slugify_cwd_basic():
|
||||
assert slugify_cwd("/home/fox/git/aborist") == "-home-fox-git-aborist"
|
||||
assert slugify_cwd("/home/fox/git/arborist") == "-home-fox-git-arborist"
|
||||
|
||||
|
||||
def test_slugify_cwd_dots_become_hyphens():
|
||||
|
|
@ -84,15 +84,15 @@ def test_close_writes_session_end(tmp_path):
|
|||
assert last["durationMs"] >= 0
|
||||
|
||||
|
||||
def test_aborist_meta_passes_through(tmp_path):
|
||||
def test_arborist_meta_passes_through(tmp_path):
|
||||
meta = {"audit_mode": "STRICT", "n_verified": 2, "cache_key": "abc123"}
|
||||
with SessionWriter(first_prompt="q", root=tmp_path) as s:
|
||||
s.assistant_message("answer", aborist_meta=meta)
|
||||
s.assistant_message("answer", arborist_meta=meta)
|
||||
rows = _read_lines(list(tmp_path.glob("**/*.jsonl"))[0])
|
||||
asst = next(r for r in rows if r["type"] == "message" and r["role"] == "assistant")
|
||||
assert asst["aborist_meta"]["audit_mode"] == "STRICT"
|
||||
assert asst["aborist_meta"]["n_verified"] == 2
|
||||
assert asst["aborist_meta"]["cache_key"] == "abc123"
|
||||
assert asst["arborist_meta"]["audit_mode"] == "STRICT"
|
||||
assert asst["arborist_meta"]["n_verified"] == 2
|
||||
assert asst["arborist_meta"]["cache_key"] == "abc123"
|
||||
|
||||
|
||||
def test_session_id_used_as_filename(tmp_path):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"""Cache-key dimension hashes — pure functions, deterministic.
|
||||
|
||||
The 8-dim cache_key invariants are spread across `aborist/qa/keys.py`.
|
||||
The 8-dim cache_key invariants are spread across `arborist/qa/keys.py`.
|
||||
Most behavior is covered indirectly by `test_qa.py` (cache hits, etc.);
|
||||
this file pins the hash-input canonicalization rules so a future tweak
|
||||
to question normalization can't silently change the equivalence class
|
||||
|
|
@ -9,7 +9,7 @@ of "what counts as the same question."
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from aborist.qa.keys import (
|
||||
from arborist.qa.keys import (
|
||||
cache_key,
|
||||
conversation_hash,
|
||||
governance_policy_hash,
|
||||
|
|
@ -51,7 +51,7 @@ def test_question_hash_lowercases():
|
|||
def test_question_hash_collapses_internal_spaces():
|
||||
"""canonicalize() collapses runs of spaces to a single space. Tabs/
|
||||
newlines map to newline (a separate equivalence class — see
|
||||
aborist.document.canonicalize), so tab vs space is intentionally
|
||||
arborist.document.canonicalize), so tab vs space is intentionally
|
||||
NOT in the same bucket."""
|
||||
assert question_hash("who is X") == question_hash("who is X")
|
||||
|
||||
|
|
@ -248,7 +248,7 @@ def test_verifier_policy_hash_only_hashes_verifier_subset():
|
|||
governance_policy_hash is to surface verifier-rule changes
|
||||
independently from prompt / sampling-knob changes.
|
||||
"""
|
||||
from aborist.qa.keys import verifier_policy_hash
|
||||
from arborist.qa.keys import verifier_policy_hash
|
||||
|
||||
base = {
|
||||
"answer_mode": "claim_lattice_pointer",
|
||||
|
|
@ -273,7 +273,7 @@ def test_verifier_policy_hash_changes_when_verifier_field_changes():
|
|||
of these means the verifier rules differ and a new cache record
|
||||
is required.
|
||||
"""
|
||||
from aborist.qa.keys import verifier_policy_hash
|
||||
from arborist.qa.keys import verifier_policy_hash
|
||||
|
||||
base = {
|
||||
"answer_mode": "claim_lattice_pointer",
|
||||
|
|
@ -296,7 +296,7 @@ def test_verifier_policy_hash_changes_when_verifier_field_changes():
|
|||
|
||||
def test_verifier_policy_hash_empty_policy_is_stable():
|
||||
"""Empty policy → constant hash. Stable across runs."""
|
||||
from aborist.qa.keys import verifier_policy_hash
|
||||
from arborist.qa.keys import verifier_policy_hash
|
||||
assert verifier_policy_hash({}) == verifier_policy_hash({})
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -19,12 +19,12 @@ from pathlib import Path
|
|||
|
||||
import pytest
|
||||
|
||||
from aborist.document import Document
|
||||
from aborist.ingest import ingest_source
|
||||
from aborist.qa.client import StubClient
|
||||
from aborist.qa.runner import DEFAULT_POLICY, ask
|
||||
from aborist.store import connect
|
||||
from aborist.wikitext import BASE_VERSION
|
||||
from arborist.document import Document
|
||||
from arborist.ingest import ingest_source
|
||||
from arborist.qa.client import StubClient
|
||||
from arborist.qa.runner import DEFAULT_POLICY, ask
|
||||
from arborist.store import connect
|
||||
from arborist.wikitext import BASE_VERSION
|
||||
|
||||
|
||||
WIKITEXT_DOC = (
|
||||
|
|
@ -156,7 +156,7 @@ def test_base_version_changes_governance_hash(tmp_path):
|
|||
answers (under raw-wikitext policy) from satisfying lookups under
|
||||
the new (stripped) policy on the same question/source — they remain
|
||||
distinct cache_keys."""
|
||||
from aborist.qa.keys import governance_policy_hash
|
||||
from arborist.qa.keys import governance_policy_hash
|
||||
|
||||
p_with = dict(DEFAULT_POLICY)
|
||||
p_with["base_version"] = "wikitext-base-v1"
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from __future__ import annotations
|
|||
|
||||
import pytest
|
||||
|
||||
from aborist.merkle import (
|
||||
from arborist.merkle import (
|
||||
HASH_LEN,
|
||||
MerkleTree,
|
||||
ZERO_HASH,
|
||||
|
|
@ -117,7 +117,7 @@ def test_tampered_sibling_fails_verification():
|
|||
leaves = [_leaf(f"chunk-{i}") for i in range(4)]
|
||||
tree = MerkleTree.build(leaves)
|
||||
proof = tree.proof(0)
|
||||
from aborist.merkle import ProofNode
|
||||
from arborist.merkle import ProofNode
|
||||
|
||||
bad_siblings = list(proof.siblings)
|
||||
s0 = bad_siblings[0]
|
||||
|
|
@ -136,7 +136,7 @@ def test_swapped_is_left_flag_fails():
|
|||
leaves = [_leaf(f"chunk-{i}") for i in range(4)]
|
||||
tree = MerkleTree.build(leaves)
|
||||
proof = tree.proof(1)
|
||||
from aborist.merkle import MerkleProof, ProofNode
|
||||
from arborist.merkle import MerkleProof, ProofNode
|
||||
|
||||
flipped = MerkleProof(
|
||||
leaf=proof.leaf,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"""Tests for the mesh layer: crypto primitives, state machine, eviction.
|
||||
|
||||
Mesh state lives in the standard aborist DB. These tests build a fresh
|
||||
Mesh state lives in the standard arborist DB. These tests build a fresh
|
||||
DB per test via tmp_path so cross-test contamination is impossible.
|
||||
"""
|
||||
|
||||
|
|
@ -10,7 +10,7 @@ import os
|
|||
|
||||
import pytest
|
||||
|
||||
from aborist.mesh import (
|
||||
from arborist.mesh import (
|
||||
aead_decrypt,
|
||||
aead_encrypt,
|
||||
current_epoch,
|
||||
|
|
@ -24,13 +24,13 @@ from aborist.mesh import (
|
|||
sign,
|
||||
verify,
|
||||
)
|
||||
from aborist.mesh.members import add_member, kick_member, scheduled_rotate
|
||||
from aborist.mesh.state import (
|
||||
from arborist.mesh.members import add_member, kick_member, scheduled_rotate
|
||||
from arborist.mesh.state import (
|
||||
MeshRosterEntry,
|
||||
roster_at,
|
||||
unwrap_secret_for_self,
|
||||
)
|
||||
from aborist.store import connect
|
||||
from arborist.store import connect
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@ from pathlib import Path
|
|||
|
||||
import pytest
|
||||
|
||||
from aborist.mesh import init_identity
|
||||
from aborist.mesh.members import add_member
|
||||
from aborist.mesh.state import recover_epoch_secret
|
||||
from aborist.mesh.wire import (
|
||||
from arborist.mesh import init_identity
|
||||
from arborist.mesh.members import add_member
|
||||
from arborist.mesh.state import recover_epoch_secret
|
||||
from arborist.mesh.wire import (
|
||||
MeshWireServer,
|
||||
TYPE_ANNOUNCE_FALSIFICATION,
|
||||
TYPE_ANNOUNCE_ROOT,
|
||||
|
|
@ -32,7 +32,7 @@ from aborist.mesh.wire import (
|
|||
decrypt_envelope_body,
|
||||
encrypt_body_for_envelope,
|
||||
)
|
||||
from aborist.store import connect
|
||||
from arborist.store import connect
|
||||
|
||||
|
||||
def _bootstrap_two_peer_db(tmp_path: Path) -> tuple[Path, Path, dict, dict]:
|
||||
|
|
@ -55,7 +55,7 @@ def _bootstrap_two_peer_db(tmp_path: Path) -> tuple[Path, Path, dict, dict]:
|
|||
import json as _json
|
||||
import sqlite3 as _sqlite3
|
||||
|
||||
from aborist.store import append_audit, latest_event_hash
|
||||
from arborist.store import append_audit, latest_event_hash
|
||||
|
||||
alice_db = tmp_path / "alice.db"
|
||||
bob_db = tmp_path / "bob.db"
|
||||
|
|
@ -409,7 +409,7 @@ def test_mixed_cleartext_and_encrypted_round_trip(tmp_path):
|
|||
"""Alice sends one cleartext ANNOUNCE_ROOT, then one encrypted
|
||||
ANNOUNCE_FALSIFICATION. Bob accepts both; audit chain logs the
|
||||
`encrypted` flag accurately on each."""
|
||||
from aborist.mesh.wire import MeshWireClient
|
||||
from arborist.mesh.wire import MeshWireClient
|
||||
|
||||
alice_db, bob_db, _alice_id, _bob_id = _bootstrap_two_peer_db(tmp_path)
|
||||
bob_srv = MeshWireServer(bob_db, host="127.0.0.1", port=0)
|
||||
|
|
@ -463,7 +463,7 @@ def test_cleartext_default_envelope_unchanged(tmp_path):
|
|||
identically to the v1 wire format — no `encrypted_body` field on
|
||||
the canonical bytes, no signature change, no audit-body change.
|
||||
"""
|
||||
from aborist.mesh.wire import MeshWireClient
|
||||
from arborist.mesh.wire import MeshWireClient
|
||||
|
||||
alice_db, bob_db, _alice_id, _bob_id = _bootstrap_two_peer_db(tmp_path)
|
||||
bob_srv = MeshWireServer(bob_db, host="127.0.0.1", port=0)
|
||||
|
|
|
|||
|
|
@ -21,15 +21,15 @@ from pathlib import Path
|
|||
|
||||
import pytest
|
||||
|
||||
from aborist.mesh import init_identity
|
||||
from aborist.mesh.crypto import sign as _sign
|
||||
from aborist.mesh.members import add_member
|
||||
from aborist.mesh.wire import (
|
||||
from arborist.mesh import init_identity
|
||||
from arborist.mesh.crypto import sign as _sign
|
||||
from arborist.mesh.members import add_member
|
||||
from arborist.mesh.wire import (
|
||||
MeshWireServer,
|
||||
TYPE_ANNOUNCE_ROOT,
|
||||
WireEnvelope,
|
||||
)
|
||||
from aborist.store import connect
|
||||
from arborist.store import connect
|
||||
|
||||
|
||||
def _bootstrap_three_peer_db(tmp_path: Path):
|
||||
|
|
@ -40,7 +40,7 @@ def _bootstrap_three_peer_db(tmp_path: Path):
|
|||
roster at ``current_epoch`` contains bob and carol so signature
|
||||
verification finds their sign_pubs when receiving from either.
|
||||
"""
|
||||
from aborist.mesh.state import current_epoch as _current_epoch
|
||||
from arborist.mesh.state import current_epoch as _current_epoch
|
||||
|
||||
alice_db = tmp_path / "alice.db"
|
||||
bob_db = tmp_path / "bob.db"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""CLI integration: `aborist mesh serve` and `aborist mesh sync`.
|
||||
"""CLI integration: `arborist mesh serve` and `arborist mesh sync`.
|
||||
|
||||
Wire-layer protocol behavior is covered in test_mesh_wire.py and
|
||||
test_mesh_wire_e2e.py — these tests just exercise the argparse plumbing,
|
||||
|
|
@ -11,13 +11,13 @@ from __future__ import annotations
|
|||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from aborist.cli import build_parser
|
||||
from aborist.document import Document
|
||||
from aborist.ingest import ingest_source
|
||||
from aborist.mesh import init_identity, set_enabled
|
||||
from aborist.mesh.members import add_member
|
||||
from aborist.mesh.wire import MeshWireServer
|
||||
from aborist.store import connect
|
||||
from arborist.cli import build_parser
|
||||
from arborist.document import Document
|
||||
from arborist.ingest import ingest_source
|
||||
from arborist.mesh import init_identity, set_enabled
|
||||
from arborist.mesh.members import add_member
|
||||
from arborist.mesh.wire import MeshWireServer
|
||||
from arborist.store import connect
|
||||
|
||||
|
||||
class _OneDocSource:
|
||||
|
|
@ -166,7 +166,7 @@ def test_sync_announces_falsifications(tmp_path, capsys):
|
|||
the falsification. Alice's audit chain gains one mesh_received event
|
||||
whose body carries an ANNOUNCE_FALSIFICATION envelope."""
|
||||
import time as _time
|
||||
from aborist.store import append_audit, transaction
|
||||
from arborist.store import append_audit, transaction
|
||||
|
||||
alice_db, bob_db, _alice, _bob = _bootstrap_two_peers(tmp_path)
|
||||
|
||||
|
|
@ -232,7 +232,7 @@ def test_sync_no_falsifications_flag_skips_them(tmp_path, capsys):
|
|||
"""--no-falsifications skips the falsification broadcast. Bob's
|
||||
falsification is NOT announced. Alice gets only the root broadcasts."""
|
||||
import time as _time
|
||||
from aborist.store import append_audit, transaction
|
||||
from arborist.store import append_audit, transaction
|
||||
|
||||
alice_db, bob_db, _alice, _bob = _bootstrap_two_peers(tmp_path)
|
||||
b_conn = connect(bob_db)
|
||||
|
|
@ -322,7 +322,7 @@ def test_sync_kindergarten_holds_fresh_records(tmp_path, capsys):
|
|||
A document ingested 30 seconds ago must NOT broadcast under the
|
||||
default 1-hour kindergarten window."""
|
||||
import time as _time
|
||||
from aborist.store import transaction
|
||||
from arborist.store import transaction
|
||||
|
||||
alice_db, bob_db, _alice, _bob = _bootstrap_two_peers(tmp_path)
|
||||
_ingest(bob_db, uri="https://example.com/old", content="Old prose.", title="Old")
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""CLI integration: `aborist mesh pull`.
|
||||
"""CLI integration: `arborist mesh pull`.
|
||||
|
||||
Closes the request half of the gossip loop. Wire-layer Merkle / signature
|
||||
verification is covered in test_mesh_wire_e2e.py — these tests cover the
|
||||
|
|
@ -11,13 +11,13 @@ from __future__ import annotations
|
|||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from aborist.cli import build_parser
|
||||
from aborist.document import Document
|
||||
from aborist.ingest import ingest_source
|
||||
from aborist.mesh import init_identity, set_enabled
|
||||
from aborist.mesh.members import add_member
|
||||
from aborist.mesh.wire import MeshWireServer
|
||||
from aborist.store import connect
|
||||
from arborist.cli import build_parser
|
||||
from arborist.document import Document
|
||||
from arborist.ingest import ingest_source
|
||||
from arborist.mesh import init_identity, set_enabled
|
||||
from arborist.mesh.members import add_member
|
||||
from arborist.mesh.wire import MeshWireServer
|
||||
from arborist.store import connect
|
||||
|
||||
|
||||
class _OneDocSource:
|
||||
|
|
|
|||
|
|
@ -11,13 +11,13 @@ from pathlib import Path
|
|||
|
||||
import pytest
|
||||
|
||||
from aborist.mesh import init_identity
|
||||
from aborist.mesh.members import add_member
|
||||
from aborist.mesh.crypto import (
|
||||
from arborist.mesh import init_identity
|
||||
from arborist.mesh.members import add_member
|
||||
from arborist.mesh.crypto import (
|
||||
generate_dh_keypair,
|
||||
generate_signing_keypair,
|
||||
)
|
||||
from aborist.mesh.wire import (
|
||||
from arborist.mesh.wire import (
|
||||
ALL_TYPES,
|
||||
PATH_ANNOUNCE,
|
||||
PATH_INFO,
|
||||
|
|
@ -30,7 +30,7 @@ from aborist.mesh.wire import (
|
|||
WireEnvelope,
|
||||
verify_envelope_sig,
|
||||
)
|
||||
from aborist.store import connect
|
||||
from arborist.store import connect
|
||||
|
||||
|
||||
def _bootstrap_two_peer_db(tmp_path: Path) -> tuple[Path, Path, dict, dict]:
|
||||
|
|
@ -205,7 +205,7 @@ def test_verify_envelope_sig_accepts_known_member(tmp_path):
|
|||
body={"document_root": "11" * 32, "source_uri": "https://x"},
|
||||
)
|
||||
sig_payload = env.canonical_bytes()
|
||||
from aborist.mesh.crypto import sign as _sign
|
||||
from arborist.mesh.crypto import sign as _sign
|
||||
sig = _sign(alice_id.sign_priv, sig_payload)
|
||||
|
||||
a_conn = connect(alice_db)
|
||||
|
|
@ -225,7 +225,7 @@ def test_verify_envelope_sig_rejects_tampered_body(tmp_path):
|
|||
ts=100,
|
||||
body={"document_root": "aa" * 32, "source_uri": "https://x"},
|
||||
)
|
||||
from aborist.mesh.crypto import sign as _sign
|
||||
from arborist.mesh.crypto import sign as _sign
|
||||
sig = _sign(alice_id.sign_priv, env_orig.canonical_bytes())
|
||||
|
||||
env_tampered = WireEnvelope(
|
||||
|
|
@ -252,7 +252,7 @@ def test_verify_envelope_sig_rejects_unknown_sender(tmp_path):
|
|||
ts=0,
|
||||
body={"document_root": "00" * 32, "source_uri": "https://x"},
|
||||
)
|
||||
from aborist.mesh.crypto import sign as _sign
|
||||
from arborist.mesh.crypto import sign as _sign
|
||||
sig = _sign(sign_priv, env.canonical_bytes())
|
||||
a_conn = connect(alice_db)
|
||||
try:
|
||||
|
|
@ -272,7 +272,7 @@ def test_verify_envelope_sig_rejects_wrong_key_for_known_member(tmp_path):
|
|||
ts=0,
|
||||
body={"document_root": "00" * 32, "source_uri": "https://x"},
|
||||
)
|
||||
from aborist.mesh.crypto import sign as _sign
|
||||
from arborist.mesh.crypto import sign as _sign
|
||||
sig = _sign(rogue_priv, env.canonical_bytes())
|
||||
a_conn = connect(alice_db)
|
||||
try:
|
||||
|
|
@ -288,7 +288,7 @@ def test_verify_envelope_sig_rejects_wrong_key_for_known_member(tmp_path):
|
|||
|
||||
def test_handle_announce_writes_audit_event(tmp_path):
|
||||
"""Bob sends ANNOUNCE_ROOT to Alice's server; Alice's chain gains an event."""
|
||||
from aborist.mesh.wire import MeshWireServer
|
||||
from arborist.mesh.wire import MeshWireServer
|
||||
|
||||
alice_db, _bob_db, _alice_id, bob_id = _bootstrap_two_peer_db(tmp_path)
|
||||
|
||||
|
|
@ -340,7 +340,7 @@ def test_handle_announce_writes_audit_event(tmp_path):
|
|||
|
||||
def test_handle_announce_rejects_bad_sig(tmp_path):
|
||||
"""Sig from wrong key → 401, no audit event."""
|
||||
from aborist.mesh.wire import MeshWireServer
|
||||
from arborist.mesh.wire import MeshWireServer
|
||||
|
||||
alice_db, _bob_db, _alice_id, _bob_id = _bootstrap_two_peer_db(tmp_path)
|
||||
rogue_priv, _ = generate_signing_keypair()
|
||||
|
|
@ -352,7 +352,7 @@ def test_handle_announce_rejects_bad_sig(tmp_path):
|
|||
ts=0,
|
||||
body={"document_root": "00" * 32, "source_uri": "x"},
|
||||
)
|
||||
from aborist.mesh.crypto import sign as _sign
|
||||
from arborist.mesh.crypto import sign as _sign
|
||||
sig = _sign(rogue_priv, env.canonical_bytes())
|
||||
import base64 as _b64
|
||||
signed = {"envelope": env.__dict__, "sig_b64": _b64.b64encode(sig).decode()}
|
||||
|
|
@ -381,7 +381,7 @@ def test_handle_announce_rejects_bad_sig(tmp_path):
|
|||
|
||||
def test_handle_announce_rejects_request_body_type(tmp_path):
|
||||
"""The /announce endpoint refuses REQUEST_BODY / DELIVER_BODY."""
|
||||
from aborist.mesh.wire import MeshWireServer
|
||||
from arborist.mesh.wire import MeshWireServer
|
||||
|
||||
alice_db, _bob_db, _alice_id, bob_id = _bootstrap_two_peer_db(tmp_path)
|
||||
env = WireEnvelope(
|
||||
|
|
@ -404,7 +404,7 @@ def test_handle_announce_rejects_request_body_type(tmp_path):
|
|||
|
||||
def test_handle_request_404_on_unknown_root(tmp_path):
|
||||
"""Asking for a doc the responder doesn't have returns 404 cleanly."""
|
||||
from aborist.mesh.wire import MeshWireServer
|
||||
from arborist.mesh.wire import MeshWireServer
|
||||
|
||||
alice_db, _bob_db, _alice_id, bob_id = _bootstrap_two_peer_db(tmp_path)
|
||||
env = WireEnvelope(
|
||||
|
|
@ -427,7 +427,7 @@ def test_handle_request_404_on_unknown_root(tmp_path):
|
|||
|
||||
def test_announce_falsification_round_trips(tmp_path):
|
||||
"""Falsifications propagate as ANNOUNCE_FALSIFICATION."""
|
||||
from aborist.mesh.wire import MeshWireServer
|
||||
from arborist.mesh.wire import MeshWireServer
|
||||
|
||||
alice_db, _bob_db, _alice_id, bob_id = _bootstrap_two_peer_db(tmp_path)
|
||||
env = WireEnvelope(
|
||||
|
|
|
|||
|
|
@ -17,16 +17,16 @@ from pathlib import Path
|
|||
|
||||
import pytest
|
||||
|
||||
from aborist.document import Document
|
||||
from aborist.ingest import ingest_source
|
||||
from aborist.mesh import init_identity
|
||||
from aborist.mesh.members import add_member
|
||||
from aborist.mesh.wire import (
|
||||
from arborist.document import Document
|
||||
from arborist.ingest import ingest_source
|
||||
from arborist.mesh import init_identity
|
||||
from arborist.mesh.members import add_member
|
||||
from arborist.mesh.wire import (
|
||||
MeshWireClient,
|
||||
MeshWireServer,
|
||||
TYPE_ANNOUNCE_ROOT,
|
||||
)
|
||||
from aborist.store import connect
|
||||
from arborist.store import connect
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
@ -180,7 +180,7 @@ def test_request_body_rejects_tampered_response(two_peers, monkeypatch):
|
|||
title="X",
|
||||
)
|
||||
with MeshWireClient(two_peers["bob"]["db"], two_peers["alice"]["srv"].url) as c:
|
||||
from aborist.mesh import wire as _wire
|
||||
from arborist.mesh import wire as _wire
|
||||
monkeypatch.setattr(_wire, "_merkle_root_matches", lambda *a, **k: False)
|
||||
with pytest.raises(ValueError, match="Merkle-derive"):
|
||||
c.request_body(root=document_root)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from __future__ import annotations
|
|||
|
||||
import pytest
|
||||
|
||||
from aborist.qa.metacognition import (
|
||||
from arborist.qa.metacognition import (
|
||||
PREFLIGHT_VERSION,
|
||||
QuestionState,
|
||||
detect_contradiction,
|
||||
|
|
@ -305,12 +305,12 @@ def test_question_state_is_serializable():
|
|||
def test_metacognition_field_is_in_verifier_policy_fields(field):
|
||||
"""All six policy fields must be in _VERIFIER_POLICY_FIELDS so
|
||||
flipping any of them invalidates prior cache records on lookup."""
|
||||
from aborist.qa.keys import _VERIFIER_POLICY_FIELDS
|
||||
from arborist.qa.keys import _VERIFIER_POLICY_FIELDS
|
||||
assert field in _VERIFIER_POLICY_FIELDS
|
||||
|
||||
|
||||
def test_governance_hash_changes_when_metacognition_enabled_flips():
|
||||
from aborist.qa.keys import _VERIFIER_POLICY_FIELDS, verifier_policy_hash
|
||||
from arborist.qa.keys import _VERIFIER_POLICY_FIELDS, verifier_policy_hash
|
||||
base = dict.fromkeys(_VERIFIER_POLICY_FIELDS, "default")
|
||||
base["metacognition_enabled"] = True
|
||||
h_on = verifier_policy_hash(base)
|
||||
|
|
@ -320,7 +320,7 @@ def test_governance_hash_changes_when_metacognition_enabled_flips():
|
|||
|
||||
|
||||
def test_governance_hash_changes_when_block_on_contradiction_flips():
|
||||
from aborist.qa.keys import _VERIFIER_POLICY_FIELDS, verifier_policy_hash
|
||||
from arborist.qa.keys import _VERIFIER_POLICY_FIELDS, verifier_policy_hash
|
||||
base = dict.fromkeys(_VERIFIER_POLICY_FIELDS, "default")
|
||||
base["metacognition_block_on_contradiction"] = False
|
||||
h_off = verifier_policy_hash(base)
|
||||
|
|
@ -332,8 +332,8 @@ def test_governance_hash_changes_when_block_on_contradiction_flips():
|
|||
def test_default_policy_has_metacognition_enabled():
|
||||
"""Master switch default-on per ticket #000010 §7.3 — detectors
|
||||
are pure-on-question so the cost is negligible."""
|
||||
from aborist.qa.runner import DEFAULT_POLICY as RUNNER_POLICY
|
||||
from aborist.qa.query import DEFAULT_QUERY_POLICY
|
||||
from arborist.qa.runner import DEFAULT_POLICY as RUNNER_POLICY
|
||||
from arborist.qa.query import DEFAULT_QUERY_POLICY
|
||||
assert RUNNER_POLICY["metacognition_enabled"] is True
|
||||
assert DEFAULT_QUERY_POLICY["metacognition_enabled"] is True
|
||||
|
||||
|
|
@ -341,8 +341,8 @@ def test_default_policy_has_metacognition_enabled():
|
|||
def test_default_policy_block_on_contradiction_off():
|
||||
"""Default to label-only on contradictions. False-positive risk
|
||||
not yet bench-validated; opt-in via --block-on-contradiction."""
|
||||
from aborist.qa.runner import DEFAULT_POLICY as RUNNER_POLICY
|
||||
from aborist.qa.query import DEFAULT_QUERY_POLICY
|
||||
from arborist.qa.runner import DEFAULT_POLICY as RUNNER_POLICY
|
||||
from arborist.qa.query import DEFAULT_QUERY_POLICY
|
||||
assert RUNNER_POLICY["metacognition_block_on_contradiction"] is False
|
||||
assert DEFAULT_QUERY_POLICY["metacognition_block_on_contradiction"] is False
|
||||
|
||||
|
|
@ -360,7 +360,7 @@ def _result_with_question_state(state: QuestionState) -> dict:
|
|||
|
||||
|
||||
def test_tail_renders_false_premise():
|
||||
from aborist.cli import _render_warrant_tail
|
||||
from arborist.cli import _render_warrant_tail
|
||||
state = preflight_question(
|
||||
"When did Mr. Burns become Homer's biological father?"
|
||||
)
|
||||
|
|
@ -369,28 +369,28 @@ def test_tail_renders_false_premise():
|
|||
|
||||
|
||||
def test_tail_renders_contradictory():
|
||||
from aborist.cli import _render_warrant_tail
|
||||
from arborist.cli import _render_warrant_tail
|
||||
state = preflight_question("Which unmarried spouse is Homer married to?")
|
||||
tail = _render_warrant_tail(_result_with_question_state(state))
|
||||
assert "contradictory" in tail
|
||||
|
||||
|
||||
def test_tail_renders_stale_risk():
|
||||
from aborist.cli import _render_warrant_tail
|
||||
from arborist.cli import _render_warrant_tail
|
||||
state = preflight_question("Who is the current CEO of OpenAI?")
|
||||
tail = _render_warrant_tail(_result_with_question_state(state))
|
||||
assert "stale risk" in tail
|
||||
|
||||
|
||||
def test_tail_renders_out_of_corpus():
|
||||
from aborist.cli import _render_warrant_tail
|
||||
from arborist.cli import _render_warrant_tail
|
||||
state = preflight_question("What does my uploaded contract say?")
|
||||
tail = _render_warrant_tail(_result_with_question_state(state))
|
||||
assert "out of corpus" in tail
|
||||
|
||||
|
||||
def test_tail_omits_metacog_when_well_formed():
|
||||
from aborist.cli import _render_warrant_tail
|
||||
from arborist.cli import _render_warrant_tail
|
||||
state = preflight_question("what is the capital of france?")
|
||||
tail = _render_warrant_tail(_result_with_question_state(state))
|
||||
# well_formed should NOT produce any metacog tail tokens.
|
||||
|
|
@ -403,7 +403,7 @@ def test_tail_omits_metacog_when_well_formed():
|
|||
def test_tail_combines_metacog_with_existing_kinds():
|
||||
"""Multiple tails compose: a TITLE_MISMATCH from the verifier
|
||||
plus a stale_risk from the preflight should both surface."""
|
||||
from aborist.cli import _render_warrant_tail
|
||||
from arborist.cli import _render_warrant_tail
|
||||
state = preflight_question("Who is the current CEO of OpenAI?")
|
||||
result = _result_with_question_state(state)
|
||||
result["violations"] = [{"kind": "TITLE_MISMATCH"}]
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ from __future__ import annotations
|
|||
|
||||
import sqlite3
|
||||
|
||||
from aborist.store import connect
|
||||
from arborist.store import connect
|
||||
|
||||
|
||||
# Pre-v9.8-audit-mode providence_cache (snapshot of the schema before the
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ docs/self-reference-design.md:
|
|||
4. anti-recursion: records whose answer cites a self-reference URI
|
||||
are excluded (first-generation only)
|
||||
|
||||
Plus the classifier-side: documents with `aborist://providence/`
|
||||
Plus the classifier-side: documents with `arborist://providence/`
|
||||
URIs classify as `self_reference_source`.
|
||||
"""
|
||||
|
||||
|
|
@ -20,12 +20,12 @@ import time
|
|||
|
||||
import pytest
|
||||
|
||||
from aborist.qa.query import _classify_source_role
|
||||
from aborist.sources.providence import (
|
||||
from arborist.qa.query import _classify_source_role
|
||||
from arborist.sources.providence import (
|
||||
PROVIDENCE_URI_PREFIX,
|
||||
ProvidenceSource,
|
||||
)
|
||||
from aborist.store import connect
|
||||
from arborist.store import connect
|
||||
|
||||
|
||||
def _seed(conn: sqlite3.Connection, **fields) -> str:
|
||||
|
|
@ -160,7 +160,7 @@ def test_kindergarten_zero_admits_all_strict_live(tmp_path):
|
|||
|
||||
def test_anti_recursion_excludes_self_referencing_records(tmp_path):
|
||||
"""A STRICT record whose own answer text contains
|
||||
`aborist://providence/...` is excluded — first-generation only.
|
||||
`arborist://providence/...` is excluded — first-generation only.
|
||||
Prevents echo-chamber chains where a wrong-but-STRICT record
|
||||
keeps getting recompiled into deeper claims."""
|
||||
db = tmp_path / "p.db"
|
||||
|
|
|
|||
|
|
@ -9,20 +9,20 @@ from __future__ import annotations
|
|||
import hashlib
|
||||
from typing import Iterator
|
||||
|
||||
from aborist.document import Document
|
||||
from aborist.ingest import ingest_source
|
||||
from aborist.merkle import proof_from_dict, verify_proof
|
||||
from aborist.qa import ask
|
||||
from aborist.qa.client import StubClient
|
||||
from aborist.qa.keys import (
|
||||
from arborist.document import Document
|
||||
from arborist.ingest import ingest_source
|
||||
from arborist.merkle import proof_from_dict, verify_proof
|
||||
from arborist.qa import ask
|
||||
from arborist.qa.client import StubClient
|
||||
from arborist.qa.keys import (
|
||||
cache_key,
|
||||
conversation_hash,
|
||||
governance_policy_hash,
|
||||
model_profile_hash,
|
||||
question_hash,
|
||||
)
|
||||
from aborist.source import Source
|
||||
from aborist.store import connect
|
||||
from arborist.source import Source
|
||||
from arborist.store import connect
|
||||
|
||||
|
||||
class FakeSource(Source):
|
||||
|
|
@ -195,7 +195,7 @@ def test_unknown_document(tmp_path):
|
|||
|
||||
def test_cold_source_refuses(tmp_path):
|
||||
"""Source must be hot — answer derived from evicted content can't be proved."""
|
||||
from aborist.evict import evict_to_cold
|
||||
from arborist.evict import evict_to_cold
|
||||
|
||||
db = tmp_path / "cold.db"
|
||||
conn = connect(db)
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
These tests are deliberately gated — they hit
|
||||
``hermes.ai.unturf.com/v1`` and require a populated shard set under
|
||||
``~/.aborist/shards`` (default) or the path passed via
|
||||
``ABORIST_LIVE_SHARDS_DIR``. Default `make test` does NOT run these;
|
||||
opt in via ``ABORIST_LIVE_TESTS=1`` (the ``make test-live`` target
|
||||
``~/.arborist/shards`` (default) or the path passed via
|
||||
``ARBORIST_LIVE_SHARDS_DIR``. Default `make test` does NOT run these;
|
||||
opt in via ``ARBORIST_LIVE_TESTS=1`` (the ``make test-live`` target
|
||||
sets it).
|
||||
|
||||
Why this exists:
|
||||
|
|
@ -44,24 +44,24 @@ from pathlib import Path
|
|||
import pytest
|
||||
|
||||
|
||||
_LIVE_OPT_IN = os.environ.get("ABORIST_LIVE_TESTS") == "1"
|
||||
_LIVE_OPT_IN = os.environ.get("ARBORIST_LIVE_TESTS") == "1"
|
||||
_SHARDS_DIR = Path(
|
||||
os.environ.get("ABORIST_LIVE_SHARDS_DIR")
|
||||
or (Path.home() / ".aborist" / "shards")
|
||||
os.environ.get("ARBORIST_LIVE_SHARDS_DIR")
|
||||
or (Path.home() / ".arborist" / "shards")
|
||||
)
|
||||
_QA_DB = _SHARDS_DIR / "qa.db"
|
||||
_ENDPOINT = os.environ.get(
|
||||
"ABORIST_LLM_ENDPOINT", "https://hermes.ai.unturf.com/v1"
|
||||
"ARBORIST_LLM_ENDPOINT", "https://hermes.ai.unturf.com/v1"
|
||||
)
|
||||
_MODEL = os.environ.get(
|
||||
"ABORIST_LLM_MODEL",
|
||||
"ARBORIST_LLM_MODEL",
|
||||
"adamo1139/Hermes-3-Llama-3.1-8B-FP8-Dynamic",
|
||||
)
|
||||
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
not _LIVE_OPT_IN,
|
||||
reason="live QA tests gated by ABORIST_LIVE_TESTS=1; "
|
||||
reason="live QA tests gated by ARBORIST_LIVE_TESTS=1; "
|
||||
"run via `make test-live` or set the env var",
|
||||
)
|
||||
|
||||
|
|
@ -75,10 +75,10 @@ def _ask(question: str, *, mode: str = "claim_lattice", top_k: int = 8) -> dict:
|
|||
|
||||
# Lazy import so collecting the file doesn't pull qa modules in
|
||||
# default-skip mode.
|
||||
from aborist.qa.client import OpenAICompatibleClient
|
||||
from aborist.qa.query import DEFAULT_QUERY_POLICY, query
|
||||
from arborist.qa.client import OpenAICompatibleClient
|
||||
from arborist.qa.query import DEFAULT_QUERY_POLICY, query
|
||||
|
||||
api_key = os.environ.get("ABORIST_LLM_API_KEY")
|
||||
api_key = os.environ.get("ARBORIST_LLM_API_KEY")
|
||||
client = OpenAICompatibleClient(base_url=_ENDPOINT, api_key=api_key)
|
||||
policy = dict(DEFAULT_QUERY_POLICY)
|
||||
policy["answer_mode"] = mode
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"""Quantifier cap table + governance hash + dry-run discipline.
|
||||
|
||||
Ticket #000008 Phase 2: per-model cap profiles
|
||||
(``aborist/qa/model_profiles.py``) and the four policy fields that
|
||||
(``arborist/qa/model_profiles.py``) and the four policy fields that
|
||||
gate cap behavior:
|
||||
|
||||
- ``quantifier_guard_enabled`` — master kill
|
||||
|
|
@ -23,8 +23,8 @@ from __future__ import annotations
|
|||
|
||||
import pytest
|
||||
|
||||
from aborist.qa.keys import _VERIFIER_POLICY_FIELDS, verifier_policy_hash
|
||||
from aborist.qa.model_profiles import (
|
||||
from arborist.qa.keys import _VERIFIER_POLICY_FIELDS, verifier_policy_hash
|
||||
from arborist.qa.model_profiles import (
|
||||
EXPLICIT_COUNT,
|
||||
PROFILES,
|
||||
cap_for_intensity,
|
||||
|
|
@ -230,8 +230,8 @@ def test_default_reminder_enabled_for_lattice_modes():
|
|||
eliminates FORMAT_COLLAPSED, reduces NO_EVIDENCE_POINTER 33%,
|
||||
boosts mean ratio +17pp pointer / +21pp JSON, rescues JSON
|
||||
UNGROUNDED 7→1."""
|
||||
from aborist.qa.runner import DEFAULT_POLICY as RUNNER_POLICY
|
||||
from aborist.qa.query import DEFAULT_QUERY_POLICY
|
||||
from arborist.qa.runner import DEFAULT_POLICY as RUNNER_POLICY
|
||||
from arborist.qa.query import DEFAULT_QUERY_POLICY
|
||||
assert RUNNER_POLICY["quantifier_reminder_enabled"] is True
|
||||
assert DEFAULT_QUERY_POLICY["quantifier_reminder_enabled"] is True
|
||||
|
||||
|
|
@ -242,8 +242,8 @@ def test_default_apply_caps_modes_is_json_only():
|
|||
TOO_MANY_CLAIMS 20× without moving the 0/45 STRICT floor.
|
||||
Default the apply-caps allowlist to JSON-only so flipping
|
||||
apply_caps=True doesn't add wasted cap-noise on pointer mode."""
|
||||
from aborist.qa.runner import DEFAULT_POLICY as RUNNER_POLICY
|
||||
from aborist.qa.query import DEFAULT_QUERY_POLICY
|
||||
from arborist.qa.runner import DEFAULT_POLICY as RUNNER_POLICY
|
||||
from arborist.qa.query import DEFAULT_QUERY_POLICY
|
||||
assert RUNNER_POLICY["quantifier_apply_caps_modes"] == ["claim_lattice"]
|
||||
assert DEFAULT_QUERY_POLICY["quantifier_apply_caps_modes"] == ["claim_lattice"]
|
||||
|
||||
|
|
@ -271,7 +271,7 @@ def test_apply_caps_default_off_preserves_dry_run_discipline():
|
|||
even with reminder default-on. Dry-run discipline (§10.11.3)
|
||||
survives the §12 bench cycle — operators flip apply_caps via
|
||||
--apply-quantifier-caps after their own bench review."""
|
||||
from aborist.qa.runner import DEFAULT_POLICY as RUNNER_POLICY
|
||||
from aborist.qa.query import DEFAULT_QUERY_POLICY
|
||||
from arborist.qa.runner import DEFAULT_POLICY as RUNNER_POLICY
|
||||
from arborist.qa.query import DEFAULT_QUERY_POLICY
|
||||
assert RUNNER_POLICY["quantifier_guard_apply_caps"] is False
|
||||
assert DEFAULT_QUERY_POLICY["quantifier_guard_apply_caps"] is False
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ from __future__ import annotations
|
|||
|
||||
import pytest
|
||||
|
||||
from aborist.qa.quantifier import (
|
||||
from arborist.qa.quantifier import (
|
||||
CLASSIFIER_VERSION,
|
||||
classify_question_quantifier,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -17,13 +17,13 @@ from __future__ import annotations
|
|||
|
||||
import pytest
|
||||
|
||||
from aborist.cli import (
|
||||
from arborist.cli import (
|
||||
_SOFT_DEMOTE_VIOLATION_KINDS,
|
||||
_ladder_rung_for_lattice,
|
||||
_render_query_human,
|
||||
_render_warrant_tail,
|
||||
)
|
||||
from aborist.qa.keys import _VERIFIER_POLICY_FIELDS, verifier_policy_hash
|
||||
from arborist.qa.keys import _VERIFIER_POLICY_FIELDS, verifier_policy_hash
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- soft-demote registration
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ from __future__ import annotations
|
|||
|
||||
import pytest
|
||||
|
||||
from aborist.qa.keys import _VERIFIER_POLICY_FIELDS, verifier_policy_hash
|
||||
from aborist.qa.quantifier_reminder import broad_quantifier_reminder
|
||||
from arborist.qa.keys import _VERIFIER_POLICY_FIELDS, verifier_policy_hash
|
||||
from arborist.qa.quantifier_reminder import broad_quantifier_reminder
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- gating
|
||||
|
|
|
|||
|
|
@ -12,12 +12,12 @@ from __future__ import annotations
|
|||
import json
|
||||
from typing import Iterator
|
||||
|
||||
from aborist.document import Document
|
||||
from aborist.ingest import ingest_source
|
||||
from aborist.qa import query
|
||||
from aborist.qa.client import StubClient
|
||||
from aborist.source import Source
|
||||
from aborist.store import connect
|
||||
from arborist.document import Document
|
||||
from arborist.ingest import ingest_source
|
||||
from arborist.qa import query
|
||||
from arborist.qa.client import StubClient
|
||||
from arborist.source import Source
|
||||
from arborist.store import connect
|
||||
|
||||
|
||||
class FakeSource(Source):
|
||||
|
|
@ -339,7 +339,7 @@ def test_classify_source_role_separates_primary_from_noisy():
|
|||
be secondary; The Lost World should be sequel; off-topic background.
|
||||
Catches the case where peripheral pages with strong title overlap
|
||||
used to share the primary slot."""
|
||||
from aborist.qa.query import _classify_source_role
|
||||
from arborist.qa.query import _classify_source_role
|
||||
|
||||
qstems = {"dinosaur", "jurassic", "park", "film"}
|
||||
assert _classify_source_role("Jurassic Park (film)", qstems) == "primary_answer_source"
|
||||
|
|
@ -378,7 +378,7 @@ def test_query_persists_run_dag_root_on_record_and_result(tmp_path):
|
|||
"""Per-run Merkle-DAG fingerprint surfaces on both the result dict
|
||||
& the persisted providence_cache row. Recomputing the root from
|
||||
the persisted blob matches what was stored."""
|
||||
from aborist.qa.dag import verify_run_dag
|
||||
from arborist.qa.dag import verify_run_dag
|
||||
|
||||
main_db = tmp_path / "corpus.db"
|
||||
qa_db = tmp_path / "qa.db"
|
||||
|
|
@ -561,7 +561,7 @@ def test_query_burn_existing_forces_fresh_inference(tmp_path):
|
|||
def test_query_burn_existing_writes_audit_event(tmp_path):
|
||||
"""Each --burn writes a providence_burn audit event so the chain
|
||||
records the bust. Verifies one event lands per burn."""
|
||||
from aborist.store import connect as _connect
|
||||
from arborist.store import connect as _connect
|
||||
|
||||
main_db = tmp_path / "corpus.db"
|
||||
qa_db = tmp_path / "qa.db"
|
||||
|
|
@ -947,7 +947,7 @@ def test_question_phrases_returns_sliding_n_grams_no_stopword_strip():
|
|||
"""`_question_phrases` extracts verbatim n-token windows. Function
|
||||
words are kept — diagnostic value of an allusion is the EXACT
|
||||
sequence ('always been at war' >> 'always war')."""
|
||||
from aborist.qa.query import _question_phrases
|
||||
from arborist.qa.query import _question_phrases
|
||||
out = _question_phrases("has oceania always been at war with east asia", n=4)
|
||||
# 9-token query, 4-gram window → 6 phrases, all preserved verbatim
|
||||
# (lowercase) and deduped.
|
||||
|
|
@ -961,7 +961,7 @@ def test_question_phrases_returns_sliding_n_grams_no_stopword_strip():
|
|||
def test_question_phrases_n_5_yields_five_token_phrases():
|
||||
"""5-grams trade recall for precision; 'oceania always been at war'
|
||||
is a much stronger Orwell signal than 'always been at war' alone."""
|
||||
from aborist.qa.query import _question_phrases
|
||||
from arborist.qa.query import _question_phrases
|
||||
out = _question_phrases("has oceania always been at war with east asia", n=5)
|
||||
assert "oceania always been at war" in out
|
||||
assert "always been at war with" in out
|
||||
|
|
@ -972,7 +972,7 @@ def test_question_phrases_n_5_yields_five_token_phrases():
|
|||
def test_question_phrases_skips_when_question_shorter_than_n():
|
||||
"""`who is X?` is too short to yield 4-grams. Empty output is the
|
||||
expected behavior (the body BM25 + title routes still cover it)."""
|
||||
from aborist.qa.query import _question_phrases
|
||||
from arborist.qa.query import _question_phrases
|
||||
assert _question_phrases("who is X?", n=4) == []
|
||||
assert _question_phrases("", n=4) == []
|
||||
|
||||
|
|
@ -981,7 +981,7 @@ def test_question_phrases_drops_all_short_token_phrases():
|
|||
"""A window of all 1-3 char tokens is boilerplate ('to be or not')
|
||||
— drops to avoid over-matching. The skip rule fires only when ALL
|
||||
tokens in the window are <4 chars."""
|
||||
from aborist.qa.query import _question_phrases
|
||||
from arborist.qa.query import _question_phrases
|
||||
# All ≤3-char tokens — drop.
|
||||
assert _question_phrases("to be or not", n=4) == []
|
||||
# Mixed: at least one ≥4-char token → keep.
|
||||
|
|
@ -992,7 +992,7 @@ def test_question_phrases_drops_all_short_token_phrases():
|
|||
def test_question_phrases_lowercases_and_dedupes():
|
||||
"""Output is lowercase, deduped on string equality. Same
|
||||
sequence in different cases collapses to one phrase."""
|
||||
from aborist.qa.query import _question_phrases
|
||||
from arborist.qa.query import _question_phrases
|
||||
out = _question_phrases("Always been at war Always been at war", n=4)
|
||||
# Repeated sequence appears only once in the output.
|
||||
assert out.count("always been at war") == 1
|
||||
|
|
@ -1000,7 +1000,7 @@ def test_question_phrases_lowercases_and_dedupes():
|
|||
|
||||
def test_search_phrases_returns_empty_on_no_phrases(tmp_path):
|
||||
"""Defensive: empty phrase list yields no rows, no exceptions."""
|
||||
from aborist.qa.query import _search_phrases
|
||||
from arborist.qa.query import _search_phrases
|
||||
main_db = tmp_path / "corpus.db"
|
||||
conn = connect(main_db)
|
||||
try:
|
||||
|
|
@ -1014,7 +1014,7 @@ def test_search_phrases_returns_empty_on_no_phrases(tmp_path):
|
|||
def test_search_phrases_skips_phrases_with_double_quotes(tmp_path):
|
||||
"""Adversarial input safety: phrases containing `"` would break
|
||||
the FTS5 quoted-phrase syntax. The function silently drops them."""
|
||||
from aborist.qa.query import _search_phrases
|
||||
from arborist.qa.query import _search_phrases
|
||||
main_db = tmp_path / "corpus.db"
|
||||
conn = connect(main_db)
|
||||
try:
|
||||
|
|
@ -1075,7 +1075,7 @@ def test_phrase_route_skipped_when_question_shorter_than_min_n():
|
|||
tokens to trigger the n=5/n=6 phrase route. Short conventional
|
||||
queries route through body-BM25 + title-LIKE only — phrase routing
|
||||
is structurally biased toward longer allusion-shape questions."""
|
||||
from aborist.qa.query import _question_phrases
|
||||
from arborist.qa.query import _question_phrases
|
||||
# 4 tokens after extraction → empty 5-gram and 6-gram outputs.
|
||||
assert _question_phrases("oceania east asia geography", n=5) == []
|
||||
assert _question_phrases("oceania east asia geography", n=6) == []
|
||||
|
|
@ -1136,7 +1136,7 @@ def test_filter_keeps_phrase_match_root_with_no_title_overlap():
|
|||
"""Direct unit test for accept-path 4: a hit whose title shares
|
||||
zero content tokens with the question, but whose document_root is
|
||||
in `phrase_match_roots`, must pass the filter."""
|
||||
from aborist.qa.query import _Hit, _filter_by_title_relevance
|
||||
from arborist.qa.query import _Hit, _filter_by_title_relevance
|
||||
hits = [
|
||||
# Title shares NO content tokens with the question. Without
|
||||
# accept-path 4 (phrase_match_roots), it would be dropped.
|
||||
|
|
@ -1211,7 +1211,7 @@ def test_unit_search_titles_handles_long_question_without_crash(tmp_path):
|
|||
OR-chain + ORDER BY LENGTH(title) — handles arbitrary token
|
||||
counts. Cap at MAX_TITLE_LIKE_TOKENS bounds the OR-chain
|
||||
growth defensively."""
|
||||
from aborist.qa.query import _search_titles
|
||||
from arborist.qa.query import _search_titles
|
||||
|
||||
main_db, _qa_db = _build_titled_corpus(tmp_path)
|
||||
conn = connect(main_db)
|
||||
|
|
@ -1239,7 +1239,7 @@ def test_unit_search_titles_handles_long_question_without_crash(tmp_path):
|
|||
|
||||
def test_unit_search_titles_handles_zero_tokens():
|
||||
"""Defensive: zero tokens → empty list, no SQL executed."""
|
||||
from aborist.qa.query import _search_titles
|
||||
from arborist.qa.query import _search_titles
|
||||
|
||||
rows = _search_titles(None, [], 32)
|
||||
assert rows == []
|
||||
|
|
@ -1251,7 +1251,7 @@ def test_unit_search_titles_caps_or_chain_at_max_tokens(tmp_path):
|
|||
expression-tree growth. Beyond the cap, additional tokens
|
||||
are dropped at the SQL layer; the post-filter in the caller
|
||||
sees them all and does the actual filtering."""
|
||||
from aborist.qa.query import _search_titles
|
||||
from arborist.qa.query import _search_titles
|
||||
|
||||
main_db, _qa_db = _build_titled_corpus(tmp_path)
|
||||
conn = connect(main_db)
|
||||
|
|
@ -1334,7 +1334,7 @@ def test_functional_long_question_returns_sources(tmp_path):
|
|||
def test_unit_hyphen_fold_variants_emits_joined_form():
|
||||
"""`bi-polar` → {"bipolar"}; multiple hyphen runs emit one
|
||||
joined variant each; non-hyphenated input emits nothing."""
|
||||
from aborist.qa.query import _hyphen_fold_variants
|
||||
from arborist.qa.query import _hyphen_fold_variants
|
||||
|
||||
assert _hyphen_fold_variants("bi-polar is rare?") == {"bipolar"}
|
||||
assert _hyphen_fold_variants("high-school co-op") == {
|
||||
|
|
@ -1349,7 +1349,7 @@ def test_unit_hyphen_fold_variants_emits_joined_form():
|
|||
def test_unit_title_query_tokens_includes_hyphen_fold_additively():
|
||||
"""Hyphen-fold is additive — `bi-polar is rare?` produces both
|
||||
the split forms (bi, polar) AND the joined form (bipolar)."""
|
||||
from aborist.qa.query import _title_query_tokens
|
||||
from arborist.qa.query import _title_query_tokens
|
||||
|
||||
toks = _title_query_tokens("bi-polar is rare?")
|
||||
assert "bi" in toks
|
||||
|
|
@ -1363,7 +1363,7 @@ def test_unit_title_query_tokens_includes_hyphen_fold_additively():
|
|||
def test_unit_title_query_tokens_no_hyphen_unchanged():
|
||||
"""Non-hyphen input behaves exactly as before — pin that the
|
||||
fold doesn't add spurious tokens for plain queries."""
|
||||
from aborist.qa.query import _title_query_tokens
|
||||
from arborist.qa.query import _title_query_tokens
|
||||
|
||||
assert _title_query_tokens("anarchism political philosophy") == {
|
||||
"anarchism",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""`aborist reclassify` — re-runs the verifier against live providence records.
|
||||
"""`arborist reclassify` — re-runs the verifier against live providence records.
|
||||
|
||||
Covers:
|
||||
- Stale UNGROUNDED records become STRICT/HYBRID/etc when the new verifier
|
||||
|
|
@ -16,13 +16,13 @@ import argparse
|
|||
import json
|
||||
from typing import Iterator
|
||||
|
||||
from aborist.cli import _cmd_reclassify
|
||||
from aborist.document import Document
|
||||
from aborist.ingest import ingest_source
|
||||
from aborist.qa import query
|
||||
from aborist.qa.client import StubClient
|
||||
from aborist.source import Source
|
||||
from aborist.store import connect
|
||||
from arborist.cli import _cmd_reclassify
|
||||
from arborist.document import Document
|
||||
from arborist.ingest import ingest_source
|
||||
from arborist.qa import query
|
||||
from arborist.qa.client import StubClient
|
||||
from arborist.source import Source
|
||||
from arborist.store import connect
|
||||
|
||||
|
||||
class _FakeSource(Source):
|
||||
|
|
|
|||
|
|
@ -19,13 +19,13 @@ from __future__ import annotations
|
|||
|
||||
from typing import Iterator
|
||||
|
||||
from aborist.document import Document
|
||||
from aborist.ingest import ingest_source
|
||||
from aborist.qa import query
|
||||
from aborist.qa.client import StubClient
|
||||
from aborist.qa.repair import mechanical_repair
|
||||
from aborist.source import Source
|
||||
from aborist.store import connect
|
||||
from arborist.document import Document
|
||||
from arborist.ingest import ingest_source
|
||||
from arborist.qa import query
|
||||
from arborist.qa.client import StubClient
|
||||
from arborist.qa.repair import mechanical_repair
|
||||
from arborist.source import Source
|
||||
from arborist.store import connect
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- mechanical_repair
|
||||
|
|
@ -200,7 +200,7 @@ def test_query_reprompt_rewrites_on_paraphrase_failure(tmp_path):
|
|||
|
||||
client = _SeqClient()
|
||||
|
||||
from aborist.qa.query import DEFAULT_QUERY_POLICY
|
||||
from arborist.qa.query import DEFAULT_QUERY_POLICY
|
||||
policy = dict(DEFAULT_QUERY_POLICY)
|
||||
policy["repair_enabled"] = True
|
||||
policy["repair_max_reprompts"] = 1
|
||||
|
|
@ -245,7 +245,7 @@ def test_query_repair_enabled_promotes_synthetic_elision_to_strict(tmp_path):
|
|||
)
|
||||
|
||||
# Build a policy variant with repair_enabled=True.
|
||||
from aborist.qa.query import DEFAULT_QUERY_POLICY
|
||||
from arborist.qa.query import DEFAULT_QUERY_POLICY
|
||||
policy = dict(DEFAULT_QUERY_POLICY)
|
||||
policy["repair_enabled"] = True
|
||||
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ from __future__ import annotations
|
|||
|
||||
from typing import Iterator
|
||||
|
||||
from aborist.document import Document
|
||||
from aborist.ingest import ingest_source
|
||||
from aborist.source import Source
|
||||
from aborist.store import connect, get_meta, set_meta
|
||||
from arborist.document import Document
|
||||
from arborist.ingest import ingest_source
|
||||
from arborist.source import Source
|
||||
from arborist.store import connect, get_meta, set_meta
|
||||
|
||||
|
||||
class IndexedSource(Source):
|
||||
|
|
@ -54,7 +54,7 @@ def test_resume_skips_processed(tmp_path):
|
|||
conn = connect(db)
|
||||
try:
|
||||
# Pre-seed a high-water as if a prior run got through doc 4.
|
||||
with __import__("aborist").store.transaction(conn):
|
||||
with __import__("arborist").store.transaction(conn):
|
||||
set_meta(conn, "source_high_water:indexed_test", "4")
|
||||
src = IndexedSource(n_docs=10)
|
||||
result = ingest_source(conn, src, batch_size=5, resume=True)
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@ import time
|
|||
|
||||
import pytest
|
||||
|
||||
from aborist.document import Document
|
||||
from aborist.ingest import ingest_source
|
||||
from aborist.snapshot import (
|
||||
from arborist.document import Document
|
||||
from arborist.ingest import ingest_source
|
||||
from arborist.snapshot import (
|
||||
EMPTY_SNAPSHOT_ROOT,
|
||||
compute_snapshot_root,
|
||||
create_snapshot,
|
||||
|
|
@ -20,8 +20,8 @@ from aborist.snapshot import (
|
|||
list_snapshots,
|
||||
verify_snapshot,
|
||||
)
|
||||
from aborist.source import Source
|
||||
from aborist.store import connect
|
||||
from arborist.source import Source
|
||||
from arborist.store import connect
|
||||
|
||||
|
||||
class _FakeSource(Source):
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ from __future__ import annotations
|
|||
|
||||
import pytest
|
||||
|
||||
from aborist.qa.soft_preflight import (
|
||||
from arborist.qa.soft_preflight import (
|
||||
SOFT_PREFLIGHT_VERSION,
|
||||
SoftPreflightHint,
|
||||
_normalize_label,
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@ from __future__ import annotations
|
|||
import json
|
||||
from typing import Iterator
|
||||
|
||||
from aborist.distill import TfidfKeywordDistiller
|
||||
from aborist.distill.runner import distill_existing
|
||||
from aborist.document import Document
|
||||
from aborist.ingest import ingest_source
|
||||
from aborist.merkle import proof_from_dict, verify_proof
|
||||
from aborist.source import Source
|
||||
from aborist.store import connect
|
||||
from arborist.distill import TfidfKeywordDistiller
|
||||
from arborist.distill.runner import distill_existing
|
||||
from arborist.document import Document
|
||||
from arborist.ingest import ingest_source
|
||||
from arborist.merkle import proof_from_dict, verify_proof
|
||||
from arborist.source import Source
|
||||
from arborist.store import connect
|
||||
|
||||
|
||||
class FakeSource(Source):
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ from pathlib import Path
|
|||
|
||||
import pytest
|
||||
|
||||
from aborist.ingest import ingest_source
|
||||
from aborist.sources.vcs import GitRepoSource, MercurialRepoSource
|
||||
from aborist.store import connect
|
||||
from arborist.ingest import ingest_source
|
||||
from arborist.sources.vcs import GitRepoSource, MercurialRepoSource
|
||||
from arborist.store import connect
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -120,7 +120,7 @@ def test_git_source_yields_text_files_and_skips_binary(tmp_path):
|
|||
def test_git_source_round_trip_and_supersedes_chain(tmp_path):
|
||||
repo = tmp_path / "demo-repo"
|
||||
_mk_git_repo(repo)
|
||||
db = tmp_path / "aborist.db"
|
||||
db = tmp_path / "arborist.db"
|
||||
|
||||
# First ingest: 2 docs (README.md, src.py).
|
||||
conn = connect(db)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ Tests cover the full trichotomy under each path plus regressions:
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from aborist.qa.verify import (
|
||||
from arborist.qa.verify import (
|
||||
extract_claim_spans,
|
||||
extract_proper_nouns,
|
||||
extract_quotes,
|
||||
|
|
@ -360,7 +360,7 @@ def test_entity_path_hybrid_when_some_proper_nouns_unverified():
|
|||
def test_wikitext_context_strips_markup_before_substring_test():
|
||||
"""Without the strip the verifier sees [[Cloud Strife]] in source and a
|
||||
bare 'Cloud Strife' in the answer's quoted span; substring fails. With
|
||||
aborist.wikitext.to_base wired into verify_quotes, the context becomes
|
||||
arborist.wikitext.to_base wired into verify_quotes, the context becomes
|
||||
plain prose and the model's quote verifies. This is the case from the
|
||||
real FF7 query that prompted wiring wikitext-base-v1 into the verifier."""
|
||||
raw_wikitext_context = (
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ import json
|
|||
|
||||
import pytest
|
||||
|
||||
from aborist.qa.evidence import EvidenceObject
|
||||
from aborist.qa.verify import _lenient_json_parse, verify_claim_lattice_json
|
||||
from arborist.qa.evidence import EvidenceObject
|
||||
from arborist.qa.verify import _lenient_json_parse, verify_claim_lattice_json
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- lenient parser
|
||||
|
|
@ -207,7 +207,7 @@ def test_verify_json_blocks_disallowed_source_role():
|
|||
|
||||
def test_claim_title_overlap_passes_when_title_shares_token():
|
||||
"""Rule 8 helper: title shares ≥1 stemmed content token with claim."""
|
||||
from aborist.qa.verify import _claim_title_overlap
|
||||
from arborist.qa.verify import _claim_title_overlap
|
||||
|
||||
assert _claim_title_overlap(
|
||||
"Homer Simpson's boss is Mr. Burns.",
|
||||
|
|
@ -227,7 +227,7 @@ def test_claim_title_overlap_passes_when_title_shares_token():
|
|||
def test_claim_title_overlap_fails_on_qcd_for_spin_glass():
|
||||
"""Rule 8 helper: spin-glass case from 2026-05-02. Claim about
|
||||
spin glass cited to Quantum chromodynamics → no token overlap."""
|
||||
from aborist.qa.verify import _claim_title_overlap
|
||||
from arborist.qa.verify import _claim_title_overlap
|
||||
|
||||
assert not _claim_title_overlap(
|
||||
"Spin glass modeling involves the use of mathematical tensors.",
|
||||
|
|
@ -237,7 +237,7 @@ def test_claim_title_overlap_fails_on_qcd_for_spin_glass():
|
|||
|
||||
def test_claim_title_overlap_vacuous_pass_on_empty():
|
||||
"""Defensive: empty title or empty claim → vacuous pass."""
|
||||
from aborist.qa.verify import _claim_title_overlap
|
||||
from arborist.qa.verify import _claim_title_overlap
|
||||
|
||||
assert _claim_title_overlap("Some claim here.", None)
|
||||
assert _claim_title_overlap("Some claim here.", "")
|
||||
|
|
@ -347,13 +347,13 @@ def test_runner_ask_json_mode_passes_guided_json_extra_body(tmp_path):
|
|||
through the chat client. Stub captures the kwargs so we can assert."""
|
||||
from typing import Iterator
|
||||
|
||||
from aborist.document import Document
|
||||
from aborist.ingest import ingest_source
|
||||
from aborist.qa import ask
|
||||
from aborist.qa.client import StubClient
|
||||
from aborist.qa.runner import DEFAULT_POLICY
|
||||
from aborist.source import Source
|
||||
from aborist.store import connect
|
||||
from arborist.document import Document
|
||||
from arborist.ingest import ingest_source
|
||||
from arborist.qa import ask
|
||||
from arborist.qa.client import StubClient
|
||||
from arborist.qa.runner import DEFAULT_POLICY
|
||||
from arborist.source import Source
|
||||
from arborist.store import connect
|
||||
|
||||
class _FakeSource(Source):
|
||||
source_type = "test"
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ from __future__ import annotations
|
|||
import time
|
||||
from typing import Iterator
|
||||
|
||||
from aborist.document import Document
|
||||
from aborist.ingest import ingest_source
|
||||
from aborist.source import Source
|
||||
from aborist.store import connect
|
||||
from arborist.document import Document
|
||||
from arborist.ingest import ingest_source
|
||||
from arborist.source import Source
|
||||
from arborist.store import connect
|
||||
|
||||
|
||||
class FakeSource(Source):
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ Covers:
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from aborist.qa.warrant import (
|
||||
from arborist.qa.warrant import (
|
||||
_question_is_count_shape,
|
||||
_question_is_entity_list_shape,
|
||||
_question_is_why_shape,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from pathlib import Path
|
|||
|
||||
import pytest
|
||||
|
||||
from aborist.sources import WikipediaOldDump, WikipediaSqlDump
|
||||
from arborist.sources import WikipediaOldDump, WikipediaSqlDump
|
||||
|
||||
|
||||
# Minimal fabricated 'old' table dump matching the 2003-05-16 schema.
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue