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
|
# Runs the unit test suite on every push. Lives at the same shape as
|
||||||
# the sibling repos (`unsandbox.com`, `unfirehose-nextjs-logger`):
|
# the sibling repos (`unsandbox.com`, `unfirehose-nextjs-logger`):
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
#
|
#
|
||||||
# What's NOT in CI:
|
# What's NOT in CI:
|
||||||
# - `make test-live` — needs the live Hermes endpoint + populated
|
# - `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.
|
# when iterating on QA quality.
|
||||||
# - `make test-crawler` — needs `[crawler]` extras + network access
|
# - `make test-crawler` — needs `[crawler]` extras + network access
|
||||||
# to real HTML sites. Opt-in via `make test-crawler` locally.
|
# 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.
|
# One question per line. `#`-prefixed lines and blank lines ignored.
|
||||||
# Designed for the 2003-05-16 Wikipedia cur snapshot — questions must
|
# Designed for the 2003-05-16 Wikipedia cur snapshot — questions must
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"""QA-quality benchmark sweep.
|
"""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
|
answer mode and tabulates STRICT / HYBRID / UNGROUNDED counts, the
|
||||||
``n_verified / n_quotes`` ratio, latency, and lazy-anchor signals.
|
``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 pathlib import Path
|
||||||
from threading import Lock
|
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.
|
# the package installed.
|
||||||
|
|
||||||
ANSWER_MODES = ("quote", "claim_lattice_pointer", "claim_lattice")
|
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
|
can flip individual fields for an A/B cycle without touching
|
||||||
the policy defaults.
|
the policy defaults.
|
||||||
"""
|
"""
|
||||||
from aborist.qa.client import OpenAICompatibleClient
|
from arborist.qa.client import OpenAICompatibleClient
|
||||||
from aborist.qa.query import DEFAULT_QUERY_POLICY, query
|
from arborist.qa.query import DEFAULT_QUERY_POLICY, query
|
||||||
|
|
||||||
policy = dict(DEFAULT_QUERY_POLICY)
|
policy = dict(DEFAULT_QUERY_POLICY)
|
||||||
policy["answer_mode"] = answer_mode
|
policy["answer_mode"] = answer_mode
|
||||||
if policy_overrides:
|
if policy_overrides:
|
||||||
policy.update(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)
|
client = OpenAICompatibleClient(base_url=endpoint, api_key=api_key)
|
||||||
|
|
||||||
if qa_db is None:
|
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()
|
t0 = time.monotonic()
|
||||||
err: str | None = None
|
err: str | None = None
|
||||||
|
|
@ -171,7 +171,7 @@ def _run_one(
|
||||||
# never mentions the question's subject) at bench-aggregate
|
# never mentions the question's subject) at bench-aggregate
|
||||||
# scale so a creeping "model deflects rather than refuses"
|
# scale so a creeping "model deflects rather than refuses"
|
||||||
# regression is legible across runs.
|
# 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 "")
|
deflection = diagnose_deflection(question, result.get("answer_text") or "")
|
||||||
metaphor = diagnose_metaphor_deflection(
|
metaphor = diagnose_metaphor_deflection(
|
||||||
question, result.get("answer_text") or ""
|
question, result.get("answer_text") or ""
|
||||||
|
|
@ -493,7 +493,7 @@ def _render_markdown(
|
||||||
) -> str:
|
) -> str:
|
||||||
lines: list[str] = []
|
lines: list[str] = []
|
||||||
n_runs = len(rows)
|
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("")
|
||||||
lines.append(
|
lines.append(
|
||||||
f"questions: {len(questions)} · modes: {len(modes)} · samples per cell: "
|
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:
|
def main(argv: list[str] | None = None) -> int:
|
||||||
ap = argparse.ArgumentParser(description=__doc__)
|
ap = argparse.ArgumentParser(description=__doc__)
|
||||||
ap.add_argument("--questions", type=Path, default=Path("bench/qa_questions.txt"))
|
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("--qa-db", type=Path, default=None)
|
||||||
ap.add_argument("--out-dir", type=Path, default=Path("bench/qa_results"))
|
ap.add_argument("--out-dir", type=Path, default=Path("bench/qa_results"))
|
||||||
ap.add_argument("--top-k", type=int, default=8)
|
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 + "
|
"re-rendered from the union of pre-existing + "
|
||||||
"new rows. Stop/start-able bench.")
|
"new rows. Stop/start-able bench.")
|
||||||
ap.add_argument("--endpoint", default=os.environ.get(
|
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(
|
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(
|
ap.add_argument(
|
||||||
"--policy", action="append", default=None, metavar="KEY=VALUE",
|
"--policy", action="append", default=None, metavar="KEY=VALUE",
|
||||||
help=(
|
help=(
|
||||||
|
|
|
||||||
14
bench/run.sh
14
bench/run.sh
|
|
@ -12,10 +12,10 @@
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
BENCH_DOCS=${1:-5000}
|
BENCH_DOCS=${1:-5000}
|
||||||
BENCH_DIR=${BENCH_DIR:-/tmp/aborist-bench}
|
BENCH_DIR=${BENCH_DIR:-/tmp/arborist-bench}
|
||||||
SHARDS=${SHARDS:-4}
|
SHARDS=${SHARDS:-4}
|
||||||
DUMP=${DUMP:-data/20030516_cur_tablesql.bz2}
|
DUMP=${DUMP:-data/20030516_cur_tablesql.bz2}
|
||||||
ABORIST=${ABORIST:-.venv/bin/aborist}
|
ARBORIST=${ARBORIST:-.venv/bin/arborist}
|
||||||
|
|
||||||
mkdir -p "$BENCH_DIR"
|
mkdir -p "$BENCH_DIR"
|
||||||
|
|
||||||
|
|
@ -40,7 +40,7 @@ bench_serial() {
|
||||||
local db=$BENCH_DIR/serial.db
|
local db=$BENCH_DIR/serial.db
|
||||||
rm -f "$db" "$db-"*
|
rm -f "$db" "$db-"*
|
||||||
local wall
|
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")
|
--source wikipedia_cur --path "$DUMP" --limit "$BENCH_DOCS")
|
||||||
local docs
|
local docs
|
||||||
docs=$(sqlite3 "$db" "SELECT COUNT(*) FROM documents")
|
docs=$(sqlite3 "$db" "SELECT COUNT(*) FROM documents")
|
||||||
|
|
@ -54,7 +54,7 @@ bench_parallel_shared() {
|
||||||
local start end wall
|
local start end wall
|
||||||
start=$(date +%s.%N)
|
start=$(date +%s.%N)
|
||||||
for i in $(seq 0 $((SHARDS - 1))); do
|
for i in $(seq 0 $((SHARDS - 1))); do
|
||||||
"$ABORIST" --db "$db" ingest --quiet \
|
"$ARBORIST" --db "$db" ingest --quiet \
|
||||||
--source wikipedia_cur --path "$DUMP" \
|
--source wikipedia_cur --path "$DUMP" \
|
||||||
--shard "$i/$SHARDS" --limit "$per" >/dev/null 2>&1 &
|
--shard "$i/$SHARDS" --limit "$per" >/dev/null 2>&1 &
|
||||||
done
|
done
|
||||||
|
|
@ -74,7 +74,7 @@ bench_attached() {
|
||||||
local start end wall
|
local start end wall
|
||||||
start=$(date +%s.%N)
|
start=$(date +%s.%N)
|
||||||
for i in $(seq 0 $((SHARDS - 1))); do
|
for i in $(seq 0 $((SHARDS - 1))); do
|
||||||
"$ABORIST" ingest --quiet \
|
"$ARBORIST" ingest --quiet \
|
||||||
--source wikipedia_cur --path "$DUMP" \
|
--source wikipedia_cur --path "$DUMP" \
|
||||||
--shards-dir "$dir" --shard "$i/$SHARDS" --limit "$per" >/dev/null 2>&1 &
|
--shards-dir "$dir" --shard "$i/$SHARDS" --limit "$per" >/dev/null 2>&1 &
|
||||||
done
|
done
|
||||||
|
|
@ -82,12 +82,12 @@ bench_attached() {
|
||||||
end=$(date +%s.%N)
|
end=$(date +%s.%N)
|
||||||
wall=$(echo "$end - $start" | bc -l)
|
wall=$(echo "$end - $start" | bc -l)
|
||||||
local docs
|
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'])")
|
| python3 -c "import json,sys; print(json.load(sys.stdin)['documents_total'])")
|
||||||
printf "attached,%s,%s\n" "$wall" "$docs"
|
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
|
echo
|
||||||
|
|
||||||
results+=("$(bench_serial)")
|
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
|
proposals: defects with a known root cause, design proposals awaiting
|
||||||
go/no-go, or scoped enhancements queued for future implementation.
|
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.
|
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",
|
"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``",
|
"auto-generated from the project ``Makefile``'s ``## description``",
|
||||||
"annotations at Sphinx build time, so it stays in sync with the source.",
|
"annotations at Sphinx build time, so it stays in sync with the source.",
|
||||||
"",
|
"",
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
CLI: Command-line interface
|
CLI: Command-line interface
|
||||||
============================
|
============================
|
||||||
|
|
||||||
Entry point for all aborist operations.
|
Entry point for all arborist operations.
|
||||||
|
|
||||||
.. automodule:: aborist.cli
|
.. automodule:: arborist.cli
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ Distillation: surface → core compression
|
||||||
|
|
||||||
Surface-to-core extraction and recursive distillation.
|
Surface-to-core extraction and recursive distillation.
|
||||||
|
|
||||||
.. automodule:: aborist.distill
|
.. automodule:: arborist.distill
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
Federation: multiplayer aborist
|
Federation: multiplayer arborist
|
||||||
================================
|
================================
|
||||||
|
|
||||||
Gossip-based mesh for cross-peer data sharing.
|
Gossip-based mesh for cross-peer data sharing.
|
||||||
|
|
||||||
.. automodule:: aborist.mesh
|
.. automodule:: arborist.mesh
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
|
||||||
|
|
@ -6,62 +6,62 @@ Question answering, caching, verification, evidence mapping.
|
||||||
keys
|
keys
|
||||||
----
|
----
|
||||||
|
|
||||||
.. automodule:: aborist.qa.keys
|
.. automodule:: arborist.qa.keys
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
|
|
||||||
runner
|
runner
|
||||||
------
|
------
|
||||||
|
|
||||||
.. automodule:: aborist.qa.runner
|
.. automodule:: arborist.qa.runner
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
|
|
||||||
query
|
query
|
||||||
-----
|
-----
|
||||||
|
|
||||||
.. automodule:: aborist.qa.query
|
.. automodule:: arborist.qa.query
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
|
|
||||||
verify
|
verify
|
||||||
------
|
------
|
||||||
|
|
||||||
.. automodule:: aborist.qa.verify
|
.. automodule:: arborist.qa.verify
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
|
|
||||||
evidence
|
evidence
|
||||||
--------
|
--------
|
||||||
|
|
||||||
.. automodule:: aborist.qa.evidence
|
.. automodule:: arborist.qa.evidence
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
|
|
||||||
quantifier
|
quantifier
|
||||||
----------
|
----------
|
||||||
|
|
||||||
.. automodule:: aborist.qa.quantifier
|
.. automodule:: arborist.qa.quantifier
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
|
|
||||||
metacognition
|
metacognition
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
.. automodule:: aborist.qa.metacognition
|
.. automodule:: arborist.qa.metacognition
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
|
|
||||||
dag
|
dag
|
||||||
---
|
---
|
||||||
|
|
||||||
.. automodule:: aborist.qa.dag
|
.. automodule:: arborist.qa.dag
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
|
|
||||||
client
|
client
|
||||||
------
|
------
|
||||||
|
|
||||||
.. automodule:: aborist.qa.client
|
.. automodule:: arborist.qa.client
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ Full-text search, concept relations (synonym/rivalry overlay).
|
||||||
search
|
search
|
||||||
------
|
------
|
||||||
|
|
||||||
.. automodule:: aborist.search
|
.. automodule:: arborist.search
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
@ -14,22 +14,22 @@ search
|
||||||
sources
|
sources
|
||||||
-------
|
-------
|
||||||
|
|
||||||
.. automodule:: aborist.source
|
.. automodule:: arborist.source
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
|
|
||||||
.. automodule:: aborist.sources.wikipedia
|
.. automodule:: arborist.sources.wikipedia
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
|
|
||||||
.. automodule:: aborist.sources.html_page
|
.. automodule:: arborist.sources.html_page
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
|
|
||||||
concepts
|
concepts
|
||||||
--------
|
--------
|
||||||
|
|
||||||
.. automodule:: aborist.concepts
|
.. automodule:: arborist.concepts
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ v9.8 SQLite schema, audit chain, and cross-shard views.
|
||||||
store
|
store
|
||||||
-----
|
-----
|
||||||
|
|
||||||
.. automodule:: aborist.store
|
.. automodule:: arborist.store
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
@ -14,7 +14,7 @@ store
|
||||||
ingest
|
ingest
|
||||||
------
|
------
|
||||||
|
|
||||||
.. automodule:: aborist.ingest
|
.. automodule:: arborist.ingest
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
@ -22,6 +22,6 @@ ingest
|
||||||
evict
|
evict
|
||||||
-----
|
-----
|
||||||
|
|
||||||
.. automodule:: aborist.evict
|
.. automodule:: arborist.evict
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ Pure Merkle tree and document primitives.
|
||||||
merkle
|
merkle
|
||||||
------
|
------
|
||||||
|
|
||||||
.. automodule:: aborist.merkle
|
.. automodule:: arborist.merkle
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
@ -14,7 +14,7 @@ merkle
|
||||||
document
|
document
|
||||||
--------
|
--------
|
||||||
|
|
||||||
.. automodule:: aborist.document
|
.. automodule:: arborist.document
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
@ -22,6 +22,6 @@ document
|
||||||
wikitext
|
wikitext
|
||||||
--------
|
--------
|
||||||
|
|
||||||
.. automodule:: aborist.wikitext
|
.. automodule:: arborist.wikitext
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
Concepts
|
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
|
LLM answer a verifiable Merkle proof tying it back to its source
|
||||||
documents. This page is the orientation: what the system is, the
|
documents. This page is the orientation: what the system is, the
|
||||||
core abstractions you'll see in code and docs, and how they compose.
|
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:
|
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
|
- **Providence cache** — Q&A records keyed on the v9.8 8-dimension
|
||||||
invariant; every record carries an audit_mode and a Merkle proof.
|
invariant; every record carries an audit_mode and a Merkle proof.
|
||||||
|
|
||||||
.. figure:: diagrams/aborist-modules.svg
|
.. figure:: diagrams/arborist-modules.svg
|
||||||
:alt: Aborist module graph
|
:alt: Arborist module graph
|
||||||
:width: 100%
|
:width: 100%
|
||||||
|
|
||||||
Top-level module graph. Substrate (merkle, document) at the bottom;
|
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
|
That is the v9.8 admissibility property: identity by content, not by
|
||||||
location.
|
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``):
|
(``proxy.unturf.com/pkg/verified/merkle.go``):
|
||||||
|
|
||||||
- **Leaf hash:** ``sha256(0x00 || canonical_chunk_bytes)``.
|
- **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
|
Linear chain per shard, verified by ``make chain-check-shards`` (any
|
||||||
break is the loudest possible signal).
|
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.
|
``audit_events`` directly.
|
||||||
|
|
||||||
The trichotomy and the four-rung ladder
|
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
|
chain**. That invariant is what keeps ``audit_mode`` a binary
|
||||||
classification rather than a soft score.
|
classification rather than a soft score.
|
||||||
|
|
||||||
See :doc:`api/qa` (``aborist.qa.inspect``).
|
See :doc:`api/qa` (``arborist.qa.inspect``).
|
||||||
|
|
||||||
Where to go next
|
Where to go next
|
||||||
----------------
|
----------------
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
"""Sphinx configuration for Aborist API reference."""
|
"""Sphinx configuration for Arborist API reference."""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
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
|
project_root = Path(__file__).parent.parent.parent
|
||||||
sys.path.insert(0, str(project_root))
|
sys.path.insert(0, str(project_root))
|
||||||
sys.path.insert(0, str(Path(__file__).parent / "_ext"))
|
sys.path.insert(0, str(Path(__file__).parent / "_ext"))
|
||||||
|
|
||||||
# Project info
|
# Project info
|
||||||
project = "Aborist"
|
project = "Arborist"
|
||||||
copyright = "2026, agent blackops"
|
copyright = "2026, agent blackops"
|
||||||
author = "fox/timehexon"
|
author = "fox/timehexon"
|
||||||
version = "9.8"
|
version = "9.8"
|
||||||
|
|
@ -37,7 +37,7 @@ napoleon_attr_annotations = True
|
||||||
# HTML output
|
# HTML output
|
||||||
html_theme = "sphinx_book_theme"
|
html_theme = "sphinx_book_theme"
|
||||||
html_static_path = ["_static"]
|
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,
|
# Standard sphinx-book-theme layout: full project TOC on the left,
|
||||||
# 'On this page' (current page sections) on the right. show_toc_level=3
|
# '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
|
Recipes for common workflows beyond the quickstart. Each starts from
|
||||||
a working aborist install (``make bootstrap`` already run) and a
|
a working arborist install (``make bootstrap`` already run) and a
|
||||||
populated shards directory under ``~/.aborist/shards/``.
|
populated shards directory under ``~/.arborist/shards/``.
|
||||||
|
|
||||||
Re-crawl a website to detect changes
|
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
|
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
|
Mix Wikipedia, your Grok export, a crawled site, and your own git
|
||||||
repos in one query — retrieval ranks across all of them.
|
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
|
.. code-block:: sh
|
||||||
|
|
||||||
export ABORIST_LLM_ENDPOINT="https://your-vllm.example.com/v1"
|
export ARBORIST_LLM_ENDPOINT="https://your-vllm.example.com/v1"
|
||||||
export ABORIST_LLM_MODEL="meta-llama/Llama-3.1-70B-Instruct"
|
export ARBORIST_LLM_MODEL="meta-llama/Llama-3.1-70B-Instruct"
|
||||||
export ABORIST_LLM_API_KEY="..." # optional; many vLLM deploys are open
|
export ARBORIST_LLM_API_KEY="..." # optional; many vLLM deploys are open
|
||||||
make query Q="..."
|
make query Q="..."
|
||||||
|
|
||||||
The model id folds into ``model_profile_hash`` (one of the 8 cache
|
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"
|
make query Q="What did Orwell mean by always at war?" K="1984 Oceania Eastasia"
|
||||||
|
|
||||||
Provenance gap on this is tracked in
|
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.
|
Generated from docstrings. Replaces the static modules.md.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
License
|
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
|
with a Permacomputer Preamble. Full text below, sourced from the
|
||||||
``LICENSE`` file at the repository root (single source of truth — this
|
``LICENSE`` file at the repository root (single source of truth — this
|
||||||
page just renders it).
|
page just renders it).
|
||||||
|
|
|
||||||
|
|
@ -7,18 +7,18 @@ the same query, verify, falsify, and inspect surfaces.
|
||||||
Install
|
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.
|
3.35+ ships with CPython.
|
||||||
|
|
||||||
.. code-block:: sh
|
.. code-block:: sh
|
||||||
|
|
||||||
git clone https://git.unturf.com/engineering/unturf/aborist.git
|
git clone https://git.unturf.com/engineering/unturf/arborist.git
|
||||||
cd aborist
|
cd arborist
|
||||||
make bootstrap # one-time: venv + dev extras
|
make bootstrap # one-time: venv + dev extras
|
||||||
|
|
||||||
``make bootstrap`` creates ``.venv/``, installs the package in editable
|
``make bootstrap`` creates ``.venv/``, installs the package in editable
|
||||||
mode with ``[dev,html]`` extras, and exposes ``aborist`` at
|
mode with ``[dev,html]`` extras, and exposes ``arborist`` at
|
||||||
``.venv/bin/aborist``. No system-wide install. Re-running is a no-op
|
``.venv/bin/arborist``. No system-wide install. Re-running is a no-op
|
||||||
if the venv is up to date.
|
if the venv is up to date.
|
||||||
|
|
||||||
After bootstrap, every workflow lives behind a ``make`` target. Run
|
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
|
make query Q="who is Russell Ballestrini?" # cross-shard; picks up new shard automatically
|
||||||
|
|
||||||
The crawl shard is named after the seed hostname
|
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``
|
``FAST=1`` enables aggressive crawling for your own sites; ``MAX=N``
|
||||||
caps discovery; ``DEPTH=N`` bounds BFS. Robots ``Disallow`` is always
|
caps discovery; ``DEPTH=N`` bounds BFS. Robots ``Disallow`` is always
|
||||||
honored. After ingest, ``make recrawl-check DOMAIN=...`` does a
|
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
|
.. code-block:: sh
|
||||||
|
|
||||||
export ABORIST_LLM_ENDPOINT="https://your-vllm.example/v1"
|
export ARBORIST_LLM_ENDPOINT="https://your-vllm.example/v1"
|
||||||
export ABORIST_LLM_MODEL="meta-llama/Llama-3.1-70B-Instruct"
|
export ARBORIST_LLM_MODEL="meta-llama/Llama-3.1-70B-Instruct"
|
||||||
export ABORIST_LLM_API_KEY="..."
|
export ARBORIST_LLM_API_KEY="..."
|
||||||
|
|
||||||
Where to next
|
Where to next
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
* :doc:`api/makefile` — every ``make`` target with a one-line description
|
* :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/qa` — Q&A pipeline internals (verifier, evidence, DAG)
|
||||||
* :doc:`api/substrate` — Merkle tree + document primitives
|
* :doc:`api/substrate` — Merkle tree + document primitives
|
||||||
* :doc:`license` — full AGPL + Permacomputer Preamble
|
* :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
|
whatever partial content the model produced and lands in the
|
||||||
unverified bucket honestly. Retrying spends another inference
|
unverified bucket honestly. Retrying spends another inference
|
||||||
round and may produce identical garbage. See
|
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 /
|
pattern: walk once tracking string state + bracket stack, close /
|
||||||
drop / balance at end-of-input. Conservative repairs only — never
|
drop / balance at end-of-input. Conservative repairs only — never
|
||||||
insert content, never fabricate keys.
|
insert content, never fabricate keys.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Benchmarks — orientation, harnesses, fixtures, discipline
|
# 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
|
signal-floor discipline pinned across the substrate. This doc is
|
||||||
the canonical entry point. Three companion docs cover specifics:
|
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
|
Picks three random words from `/usr/share/dict/words`, asks
|
||||||
Hermes (at temp 0.8) to weave them into a creative question
|
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
|
to `bench/emergent_log.jsonl` as one JSONL line. Designed for
|
||||||
**blue-moon cadence**, not every-commit benching — the
|
**blue-moon cadence**, not every-commit benching — the
|
||||||
combinatoric word space surfaces failure shapes the curated bench
|
combinatoric word space surfaces failure shapes the curated bench
|
||||||
|
|
@ -109,13 +109,13 @@ make bench-qa
|
||||||
# Same but with limit / different file
|
# Same but with limit / different file
|
||||||
.venv/bin/python bench/qa_sweep.py \
|
.venv/bin/python bench/qa_sweep.py \
|
||||||
--questions bench/qa_questions_quantifier_subset.txt \
|
--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
|
--out-dir bench/qa_results
|
||||||
|
|
||||||
# A/B cycle: flip a policy knob for one bench cell
|
# A/B cycle: flip a policy knob for one bench cell
|
||||||
.venv/bin/python bench/qa_sweep.py \
|
.venv/bin/python bench/qa_sweep.py \
|
||||||
--questions bench/qa_questions_quantifier_subset.txt \
|
--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_guard_apply_caps=true \
|
||||||
--policy quantifier_reminder_enabled=true
|
--policy quantifier_reminder_enabled=true
|
||||||
|
|
||||||
|
|
@ -219,7 +219,7 @@ random-word stress cycles. Each entry has:
|
||||||
"ts": <unix>, "iso_ts": "2026-05-04T...",
|
"ts": <unix>, "iso_ts": "2026-05-04T...",
|
||||||
"words": ["dismally", "heats", "Jamaicans"],
|
"words": ["dismally", "heats", "Jamaicans"],
|
||||||
"question": "<model-generated question paragraph>",
|
"question": "<model-generated question paragraph>",
|
||||||
"answer": "<aborist's answer>",
|
"answer": "<arborist's answer>",
|
||||||
"audit_mode": "STRICT|HYBRID|UNGROUNDED",
|
"audit_mode": "STRICT|HYBRID|UNGROUNDED",
|
||||||
"verifier_method": "claim_lattice",
|
"verifier_method": "claim_lattice",
|
||||||
"n_quotes": 3, "n_verified": 0,
|
"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.
|
hygiene check; should be ~100% on lattice modes.
|
||||||
|
|
||||||
**Audit-line tails** (rendered by
|
**Audit-line tails** (rendered by
|
||||||
`aborist/cli.py:_render_warrant_tail`):
|
`arborist/cli.py:_render_warrant_tail`):
|
||||||
- Hard tails: `· warrant missing`, `· title mismatch`,
|
- Hard tails: `· warrant missing`, `· title mismatch`,
|
||||||
`· format collapsed`, `· broad cap N`, `· broad rejected`,
|
`· format collapsed`, `· broad cap N`, `· broad rejected`,
|
||||||
`· broad unbounded`, `· broad runaway`, `· false premise`,
|
`· broad unbounded`, `· broad runaway`, `· false premise`,
|
||||||
|
|
@ -311,20 +311,20 @@ warnings worth the operator's attention.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Inspect preflight stage payload from any cache row
|
# 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
|
# 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
|
--apply-quantifier-caps --reject-broad --soft-preflight
|
||||||
|
|
||||||
# Dry-run — assemble context but skip LLM
|
# Dry-run — assemble context but skip LLM
|
||||||
aborist query "..." --dry-run
|
arborist query "..." --dry-run
|
||||||
|
|
||||||
# Force fresh inference (delete prior cache row first)
|
# Force fresh inference (delete prior cache row first)
|
||||||
aborist query "..." --burn
|
arborist query "..." --burn
|
||||||
|
|
||||||
# Show raw JSON record instead of human render
|
# Show raw JSON record instead of human render
|
||||||
aborist query "..." --json
|
arborist query "..." --json
|
||||||
```
|
```
|
||||||
|
|
||||||
The `Makefile` exposes most of these via shorthand:
|
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
|
- which falsification triggers stale a record
|
||||||
|
|
||||||
**Today's codebase:**
|
**Today's codebase:**
|
||||||
- `aborist/qa/runner.py:ask` — orchestrates the full lookup → infer → verify → admissibility flow for the per-document path
|
- `arborist/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/query.py:query` — same orchestration for the multi-source RAG path
|
||||||
- `DEFAULT_POLICY` (runner) and `DEFAULT_QUERY_POLICY` (query) — the policy dict
|
- `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-Σ.
|
**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)
|
- the boundary between hard checks (lexical, deterministic) and soft checks (heuristic, demote-only)
|
||||||
|
|
||||||
**Today's codebase:**
|
**Today's codebase:**
|
||||||
- `aborist/qa/parse_claims.py:parse_pointer_claims` — the model's pointer-line output → `ParsedClaim` records (the CTI compile step)
|
- `arborist/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)
|
- `arborist/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
|
- `arborist/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)
|
- `arborist/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
|
- `arborist/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
|
- `arborist/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`
|
- `arborist/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
|
- `arborist/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/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.
|
**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.
|
**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:**
|
**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.
|
- `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.
|
||||||
- `aborist/qa/dag.py:build_reject_run_dag` — emits the 3-stage reject-path DAG.
|
- `arborist/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.
|
- `arborist/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`)
|
- `arborist/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))`
|
- `arborist.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/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`).
|
**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
|
- 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:**
|
**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.
|
- `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.
|
||||||
- `aborist/qa/verify.py:verify_claim_lattice` step 6 (citation-overlap check) — the `claim → cited_span` lexical-coverage gate
|
- `arborist/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: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.
|
**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
|
- the deliberate honesty boundary: NOT semantic truth, NOT hallucination elimination, NOT formal proof — just admissible provenance
|
||||||
|
|
||||||
**Today's codebase:**
|
**Today's codebase:**
|
||||||
- `aborist/qa/keys.py` — all eight dimensions hashed into `cache_key`
|
- `arborist/qa/keys.py` — all eight dimensions hashed into `cache_key`
|
||||||
- `aborist/store.py` — schema, audit chain, transaction helpers
|
- `arborist/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/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.
|
**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
|
- nothing else — every other artifact (the lattice, the verdict, the rendered prose, the run DAG) comes from the runtime
|
||||||
|
|
||||||
**Today's codebase:**
|
**Today's codebase:**
|
||||||
- `aborist/qa/client.py:OpenAICompatibleClient` — HTTP client with retry on 502/503/504
|
- `arborist/qa/client.py:OpenAICompatibleClient` — HTTP client with retry on 502/503/504
|
||||||
- `aborist/qa/client.py:StubClient` — offline test stub
|
- `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_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
|
- `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).
|
// Arrows point in the import direction (A → B means A imports from B).
|
||||||
// Cluster boxes group modules with shared concerns (storage, retrieval,
|
// Cluster boxes group modules with shared concerns (storage, retrieval,
|
||||||
// federation, etc.).
|
// 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
|
rankdir=LR
|
||||||
node [shape=box, style="rounded,filled", fontname="Helvetica", fontsize=10]
|
node [shape=box, style="rounded,filled", fontname="Helvetica", fontsize=10]
|
||||||
edge [fontname="Helvetica", fontsize=9]
|
edge [fontname="Helvetica", fontsize=9]
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@
|
||||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
<!-- Generated by graphviz version 2.43.0 (0)
|
<!-- Generated by graphviz version 2.43.0 (0)
|
||||||
-->
|
-->
|
||||||
<!-- Title: aborist_modules Pages: 1 -->
|
<!-- Title: arborist_modules Pages: 1 -->
|
||||||
<svg width="1663pt" height="1147pt"
|
<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">
|
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)">
|
<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"/>
|
<polygon fill="white" stroke="transparent" points="-4,4 -4,-1142.81 1659,-1142.81 1659,4 -4,4"/>
|
||||||
<g id="clust1" class="cluster">
|
<g id="clust1" class="cluster">
|
||||||
<title>cluster_substrate</title>
|
<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,
|
// compute bit-identical document_roots. Gossip exchanges identifiers,
|
||||||
// derivations, falsifications, and witness signatures. Bulk content
|
// derivations, falsifications, and witness signatures. Bulk content
|
||||||
// only flows on cache miss, by Merkle proof of inclusion.
|
// 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
|
// Every roster mutation bumps a counter. Old epochs persist forever
|
||||||
// (signatures stay verifiable). New gossip is encrypted under the
|
// (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"];
|
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];
|
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"];
|
e0 -> e1 [label="add | kick | rotate"];
|
||||||
e1 -> eN [label="add | kick | rotate"];
|
e1 -> eN [label="add | kick | rotate"];
|
||||||
eN -> eN [label="rotate (no roster change)"];
|
eN -> eN [label="rotate (no roster change)"];
|
||||||
|
|
@ -31,8 +31,8 @@ digraph mesh_epoch_lifecycle {
|
||||||
style="rounded,dashed";
|
style="rounded,dashed";
|
||||||
color="#666666";
|
color="#666666";
|
||||||
node [fillcolor="#e8f0ff", shape=note];
|
node [fillcolor="#e8f0ff", shape=note];
|
||||||
op_add [label="aborist mesh add\n--member-id bob ...\nadmin only"];
|
op_add [label="arborist mesh add\n--member-id bob ...\nadmin only"];
|
||||||
op_kick [label="aborist mesh kick\n--member-id bob ...\nadmin only"];
|
op_kick [label="arborist mesh kick\n--member-id bob ...\nadmin only"];
|
||||||
op_rotate [label="aborist mesh rotate\nany current member"];
|
op_rotate [label="arborist mesh rotate\nany current member"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@
|
||||||
<title>none->e0</title>
|
<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"/>
|
<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"/>
|
<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>
|
</g>
|
||||||
<!-- e1 -->
|
<!-- e1 -->
|
||||||
<g id="node3" class="node">
|
<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"/>
|
<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="167,-248 167,-242 "/>
|
||||||
<polyline fill="none" stroke="black" points="173,-242 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="-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>
|
<text text-anchor="middle" x="97.5" y="-202.8" font-family="Helvetica,sans-Serif" font-size="14.00">admin only</text>
|
||||||
</g>
|
</g>
|
||||||
|
|
@ -109,7 +109,7 @@
|
||||||
<polygon fill="#e8f0ff" stroke="black" points="167,-177 22,-177 22,-124 173,-124 173,-171 167,-177"/>
|
<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="167,-177 167,-171 "/>
|
||||||
<polyline fill="none" stroke="black" points="173,-171 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="-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>
|
<text text-anchor="middle" x="97.5" y="-131.8" font-family="Helvetica,sans-Serif" font-size="14.00">admin only</text>
|
||||||
</g>
|
</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"/>
|
<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="171,-105.5 171,-99.5 "/>
|
||||||
<polyline fill="none" stroke="black" points="177,-99.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>
|
<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>
|
||||||
</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
|
// When a group steward (admin) faces a roster question, this diagram
|
||||||
// shows which CLI verb solves it and what guarantee comes out the
|
// 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_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"];
|
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_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="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_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="aborist mesh kick\n--member-id <id>\n--reason '...'", 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="aborist mesh rotate\n--reason '...'\n(any current member)", 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"];
|
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">
|
<g id="node6" class="node">
|
||||||
<title>a_init</title>
|
<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"/>
|
<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="-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>
|
<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>
|
</g>
|
||||||
|
|
@ -77,7 +77,7 @@
|
||||||
<g id="node9" class="node">
|
<g id="node9" class="node">
|
||||||
<title>a_rotate</title>
|
<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"/>
|
<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="-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>
|
<text text-anchor="middle" x="753" y="-254.3" font-family="Helvetica,sans-Serif" font-size="14.00">(any current member)</text>
|
||||||
</g>
|
</g>
|
||||||
|
|
@ -114,7 +114,7 @@
|
||||||
<g id="node7" class="node">
|
<g id="node7" class="node">
|
||||||
<title>a_add</title>
|
<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"/>
|
<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="-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="-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>
|
<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">
|
<g id="node8" class="node">
|
||||||
<title>a_kick</title>
|
<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"/>
|
<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="-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>
|
<text text-anchor="middle" x="325" y="-112.8" font-family="Helvetica,sans-Serif" font-size="14.00">--reason '...'</text>
|
||||||
</g>
|
</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.
|
// Each peer carries two keypairs and one shared epoch secret.
|
||||||
// Keys are raw 32-byte forms; everything lives in the standard SQLite db.
|
// 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
|
// Every epoch carries one fresh 32-byte symmetric secret. That single
|
||||||
// secret is wrapped N times, once per member, using ECDH between the
|
// 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.
|
Arborist on a single laptop is a content-addressed forest of documents.
|
||||||
Aborist across many laptops is a *gossip-able* forest. Two peers that
|
Arborist across many laptops is a *gossip-able* forest. Two peers that
|
||||||
ingest the same Wikipedia dump compute bit-identical `document_root`s,
|
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
|
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.
|
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.
|
mesh. Initialization is two commands and is reversible.
|
||||||
|
|
||||||
```
|
```
|
||||||
aborist mesh init --group myteam # mint Ed25519 + X25519 keys; create epoch 0
|
arborist mesh init --group myteam # mint Ed25519 + X25519 keys; create epoch 0
|
||||||
aborist mesh enable # flip gating flag on
|
arborist mesh enable # flip gating flag on
|
||||||
aborist mesh status # always-safe inspection
|
arborist mesh status # always-safe inspection
|
||||||
aborist mesh disable # flag back off; keys + history stay on disk
|
arborist mesh disable # flag back off; keys + history stay on disk
|
||||||
```
|
```
|
||||||
|
|
||||||
## Identity stack
|
## 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
|
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.
|
`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`.
|
`unwrap_secret_for_self`.
|
||||||
|
|
||||||
## What flows between peers
|
## 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:
|
optional. A typical sync round looks like:
|
||||||
|
|
||||||
1. **alice** signs and sends `ANNOUNCE_ROOT(document_root, source_uri,
|
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).
|
# 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:
|
# 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> \
|
--sign-pub <bob_sign_pub_hex> \
|
||||||
--dh-pub <bob_dh_pub_hex>
|
--dh-pub <bob_dh_pub_hex>
|
||||||
# epoch bumps. fresh secret wrapped to alice + bob.
|
# epoch bumps. fresh secret wrapped to alice + bob.
|
||||||
|
|
@ -135,7 +135,7 @@ design, joining doesn't grant retroactive access.
|
||||||
### Kick a member (admin only)
|
### 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
|
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)
|
### 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
|
Same roster, fresh secret. Use on suspected secret leak when no
|
||||||
|
|
@ -203,19 +203,19 @@ Mesh does not defend against:
|
||||||
|
|
||||||
## Related code
|
## Related code
|
||||||
|
|
||||||
- `aborist/mesh/__init__.py` — module entrypoint, public API
|
- `arborist/mesh/__init__.py` — module entrypoint, public API
|
||||||
- `aborist/mesh/crypto.py` — Ed25519 + X25519 + ChaCha20-Poly1305 wrappers
|
- `arborist/mesh/crypto.py` — Ed25519 + X25519 + ChaCha20-Poly1305 wrappers
|
||||||
- `aborist/mesh/state.py` — DB writes + audit chain hooks; epoch rotation
|
- `arborist/mesh/state.py` — DB writes + audit chain hooks; epoch rotation
|
||||||
- `aborist/mesh/members.py` — `add_member`, `kick_member`, `scheduled_rotate`
|
- `arborist/mesh/members.py` — `add_member`, `kick_member`, `scheduled_rotate`
|
||||||
- `aborist/cli.py` — `_cmd_mesh_*` argparse handlers
|
- `arborist/cli.py` — `_cmd_mesh_*` argparse handlers
|
||||||
- `aborist/store.py` — `mesh_identity`, `mesh_roster`, `mesh_epochs` schema
|
- `arborist/store.py` — `mesh_identity`, `mesh_roster`, `mesh_epochs` schema
|
||||||
|
|
||||||
## Related docs
|
## Related docs
|
||||||
|
|
||||||
- `README.md` — top-level overview, ingest flow, single-peer Q&A
|
- `README.md` — top-level overview, ingest flow, single-peer Q&A
|
||||||
- `CLAUDE.md` — schema invariants, conventions, audit chain rules
|
- `CLAUDE.md` — schema invariants, conventions, audit chain rules
|
||||||
- `~/git/proxy.unturf.com/pkg/verified/merkle.go` — fox's Go merkle
|
- `~/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
|
## Rendering the diagrams
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
**Date:** 2026-05-02
|
**Date:** 2026-05-02
|
||||||
**Endpoint:** `https://hermes.ai.unturf.com/v1` (Hermes-3-Llama-3.1-8B-FP8-Dynamic, vLLM, 82K ctx)
|
**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:
|
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 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.
|
- **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.
|
- **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.
|
- **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.
|
- **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:
|
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.
|
- 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.
|
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.
|
- **Status**: ✓ enforced.
|
||||||
- **Code anchors**:
|
- **Code anchors**:
|
||||||
`aborist/qa/verify.py:verify_quotes`,
|
`arborist/qa/verify.py:verify_quotes`,
|
||||||
`verify_claim_lattice`, `verify_claim_lattice_json` — pure functions
|
`verify_claim_lattice`, `verify_claim_lattice_json` — pure functions
|
||||||
with no chat-client parameter.
|
with no chat-client parameter.
|
||||||
- **Pinning tests**: `tests/test_verify*.py`,
|
- **Pinning tests**: `tests/test_verify*.py`,
|
||||||
|
|
@ -43,9 +43,9 @@ never types the quote string itself.
|
||||||
|
|
||||||
- **Status**: ✓ enforced for lattice modes.
|
- **Status**: ✓ enforced for lattice modes.
|
||||||
- **Code anchors**:
|
- **Code anchors**:
|
||||||
`aborist/qa/parse_claims.py:parse_pointer_claims`,
|
`arborist/qa/parse_claims.py:parse_pointer_claims`,
|
||||||
`aborist/qa/verify.py:CLAIM_LATTICE_JSON_SCHEMA`,
|
`arborist/qa/verify.py:CLAIM_LATTICE_JSON_SCHEMA`,
|
||||||
`aborist/qa/runner.py` (system prompts).
|
`arborist/qa/runner.py` (system prompts).
|
||||||
- **Pinning tests**: `tests/test_claim_lattice.py`,
|
- **Pinning tests**: `tests/test_claim_lattice.py`,
|
||||||
`tests/test_verify_json.py`.
|
`tests/test_verify_json.py`.
|
||||||
- **Bench signal**: rows with `answer_mode ∈
|
- **Bench signal**: rows with `answer_mode ∈
|
||||||
|
|
@ -66,9 +66,9 @@ frame and discarding the others.
|
||||||
quantifier classifier + #000010 metacognition QuestionState)
|
quantifier classifier + #000010 metacognition QuestionState)
|
||||||
landed 2026-05-03. Answer-side multi-frame compilation still
|
landed 2026-05-03. Answer-side multi-frame compilation still
|
||||||
pending.
|
pending.
|
||||||
- **Code anchors**: `aborist/qa/quantifier.py:classify_question_quantifier`
|
- **Code anchors**: `arborist/qa/quantifier.py:classify_question_quantifier`
|
||||||
(#000008), `aborist/qa/metacognition.py:preflight_question`
|
(#000008), `arborist/qa/metacognition.py:preflight_question`
|
||||||
(#000010), `aborist/qa/dag.py:preflight_node_hash` /
|
(#000010), `arborist/qa/dag.py:preflight_node_hash` /
|
||||||
`build_preflight_node_payload` (#000009 — five nested CTI
|
`build_preflight_node_payload` (#000009 — five nested CTI
|
||||||
clauses bind into the run-DAG `preflight` stage). Future:
|
clauses bind into the run-DAG `preflight` stage). Future:
|
||||||
runtime-side polarity contract emission for multi-frame answers.
|
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).
|
cap / reminder / reject decisions hash-bound).
|
||||||
|
|
||||||
- **Status**: ¾ — `evidence_map_root` lives in the run-DAG
|
- **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:
|
Preflight stage (#000009) added the upstream control commitment:
|
||||||
classifier output + answer/prompt/evidence contracts +
|
classifier output + answer/prompt/evidence contracts +
|
||||||
`governance_policy_hash` / `model_profile_hash` references all
|
`governance_policy_hash` / `model_profile_hash` references all
|
||||||
fold into a single `preflight` Merkle leaf. Quote mode now 7→8
|
fold into a single `preflight` Merkle leaf. Quote mode now 7→8
|
||||||
stages; pointer-mode CTI 9→10 stages; reject-broad path emits a
|
stages; pointer-mode CTI 9→10 stages; reject-broad path emits a
|
||||||
3-stage minimal DAG (`question → preflight → final_label`).
|
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-extended shapes), `build_reject_run_dag` (3-stage),
|
||||||
`preflight_node_hash` / `build_preflight_node_payload` (5-clause
|
`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
|
- **Pinning tests**: `tests/test_dag.py` (24 tests including
|
||||||
preflight stage + reject path), `tests/test_evidence.py`.
|
preflight stage + reject path), `tests/test_evidence.py`.
|
||||||
- **Open ticket**: [#000001 Retrieval-keywords audit gap](ticket-000001-retrieval-keywords-audit-gap.md)
|
- **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.
|
No NLI, no embeddings, no model self-grading.
|
||||||
|
|
||||||
- **Status**: ✓ enforced.
|
- **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.
|
seven hard checks documented inline.
|
||||||
- **Pinning tests**: `tests/test_claim_lattice.py` (per-rule
|
- **Pinning tests**: `tests/test_claim_lattice.py` (per-rule
|
||||||
coverage), `tests/test_verify_json.py`,
|
coverage), `tests/test_verify_json.py`,
|
||||||
`tests/test_verify.py`.
|
`tests/test_verify.py`.
|
||||||
- **Bench signal**: every row carries `verifier_method ∈ {quote,
|
- **Bench signal**: every row carries `verifier_method ∈ {quote,
|
||||||
span, entity, paraphrase, claim_lattice, none}` (CHECK constraint
|
span, entity, paraphrase, claim_lattice, none}` (CHECK constraint
|
||||||
in `aborist/store.py`).
|
in `arborist/store.py`).
|
||||||
|
|
||||||
### D6 — General anchor-class warrant before semantic NLI
|
### 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 /
|
- **Status**: ½ — relation-shape only. Entity-list / why-cause /
|
||||||
when-date shapes pass through unchecked.
|
when-date shapes pass through unchecked.
|
||||||
- **Code anchors**:
|
- **Code anchors**:
|
||||||
`aborist/qa/verify.py:_extract_anchor_candidates`,
|
`arborist/qa/verify.py:_extract_anchor_candidates`,
|
||||||
`aborist/qa/verify.py:_question_is_relation_shape`.
|
`arborist/qa/verify.py:_question_is_relation_shape`.
|
||||||
- **Pinning tests**: `tests/test_claim_lattice.py` covers
|
- **Pinning tests**: `tests/test_claim_lattice.py` covers
|
||||||
relation-shape; entity-list / cause / date shapes have no warrant
|
relation-shape; entity-list / cause / date shapes have no warrant
|
||||||
test today.
|
test today.
|
||||||
|
|
@ -175,7 +175,7 @@ stays `STRICT`/`HYBRID`/`UNGROUNDED` so v9.8 cache_key invariants
|
||||||
hold.
|
hold.
|
||||||
|
|
||||||
- **Status**: ✓ enforced for lattice modes.
|
- **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` —
|
- **Pinning tests**: `tests/test_cli_render.py` —
|
||||||
`test_render_label_strict_in_claim_lattice_becomes_evidence_linked`
|
`test_render_label_strict_in_claim_lattice_becomes_evidence_linked`
|
||||||
and siblings.
|
and siblings.
|
||||||
|
|
@ -253,7 +253,7 @@ passed at the original snapshot.
|
||||||
Three additional tickets landed extending D1, D3, and D4:
|
Three additional tickets landed extending D1, D3, and D4:
|
||||||
|
|
||||||
- **#000008** (closed `4f2b5a6`, 2026-05-03) — Broad-quantifier
|
- **#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
|
`model_profiles.py`, `quantifier_reminder.py`. Seven new
|
||||||
policy fields fold into `governance_policy_hash`; six-level
|
policy fields fold into `governance_policy_hash`; six-level
|
||||||
disable hierarchy. Defaults flipped (after n=5 verification):
|
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.
|
opt-in with JSON-only allowlist.
|
||||||
|
|
||||||
- **#000010** (closed, 2026-05-03) — Meta-Cognition Preflight
|
- **#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,
|
`QuestionState` dataclass + four pure detectors (temporal,
|
||||||
contradiction, false-premise-lite, out-of-corpus). Six new
|
contradiction, false-premise-lite, out-of-corpus). Six new
|
||||||
policy fields, all default-on except
|
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
|
### 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):
|
`sources_summary` (output):
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
|
@ -164,27 +164,27 @@ question." The 8-dim `cache_key` invariant stays intact.
|
||||||
|
|
||||||
## 3. Implementation sketch (when scheduled)
|
## 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:
|
parameter; embeds it into the retrieval-stage hash. Backward-compat:
|
||||||
when `retrieval_inputs` is None, fall back to the current
|
when `retrieval_inputs` is None, fall back to the current
|
||||||
sources-summary-only hash.
|
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": ...,
|
site, pass `{"question": ..., "retrieval_keywords": ...,
|
||||||
"top_k": ..., "over_fetch": ..., "max_context_chars": ...}` as
|
"top_k": ..., "over_fetch": ..., "max_context_chars": ...}` as
|
||||||
`retrieval_inputs`.
|
`retrieval_inputs`.
|
||||||
|
|
||||||
3. **`aborist/store.py`** — schema migration: add
|
3. **`arborist/store.py`** — schema migration: add
|
||||||
`retrieval_keywords TEXT` (nullable) to `providence_cache`. Mirror
|
`retrieval_keywords TEXT` (nullable) to `providence_cache`. Mirror
|
||||||
the existing `_rebuild_providence_cache_*` pattern only if a CHECK
|
the existing `_rebuild_providence_cache_*` pattern only if a CHECK
|
||||||
constraint or column-default constraint requires it (otherwise a
|
constraint or column-default constraint requires it (otherwise a
|
||||||
plain `ALTER TABLE ... ADD COLUMN` suffices).
|
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.
|
in the INSERT.
|
||||||
|
|
||||||
5. **`aborist/qa/verify.py`** + relevant CLI render — surface keywords
|
5. **`arborist/qa/verify.py`** + relevant CLI render — surface keywords
|
||||||
in `aborist providence` output and `aborist inspect` so an operator
|
in `arborist providence` output and `arborist inspect` so an operator
|
||||||
can see at a glance whether a record was retrieval-augmented.
|
can see at a glance whether a record was retrieval-augmented.
|
||||||
|
|
||||||
6. **Tests:**
|
6. **Tests:**
|
||||||
|
|
@ -233,16 +233,16 @@ question." The 8-dim `cache_key` invariant stays intact.
|
||||||
|
|
||||||
**Closed 2026-05-02.** Run-DAG binding scope landed:
|
**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
|
dataclass + `retrieval_plan_hash` function. Captures
|
||||||
`retrieval_keywords`, `top_k`, `over_fetch`, `max_context_chars`,
|
`retrieval_keywords`, `top_k`, `over_fetch`, `max_context_chars`,
|
||||||
`shard_ids`. SHA-256 over canonical-JSON; deterministic per call.
|
`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
|
parameter. When provided, the retrieval stage hash binds BOTH
|
||||||
the plan (input) and `sources_summary` (output). When omitted,
|
the plan (input) and `sources_summary` (output). When omitted,
|
||||||
falls back to the historical sources-summary-only hash so
|
falls back to the historical sources-summary-only hash so
|
||||||
pre-#000001 records keep their `run_dag_root` values stable.
|
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`,
|
query-time inputs (keywords from `--retrieval-keywords`,
|
||||||
`top_k` / `over_fetch` / `max_context_chars` from policy +
|
`top_k` / `over_fetch` / `max_context_chars` from policy +
|
||||||
args) plus the resolved shard set, computes the hash, and
|
args) plus the resolved shard set, computes the hash, and
|
||||||
|
|
@ -791,7 +791,7 @@ J — Retrieval Plan Provenance Binding
|
||||||
|
|
||||||
### A.10 Concrete diff
|
### A.10 Concrete diff
|
||||||
|
|
||||||
#### `aborist/qa/retrieval.py`
|
#### `arborist/qa/retrieval.py`
|
||||||
|
|
||||||
Return a structured object:
|
Return a structured object:
|
||||||
|
|
||||||
|
|
@ -822,14 +822,14 @@ class RetrievalPlan:
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `aborist/qa/hash.py`
|
#### `arborist/qa/hash.py`
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def retrieval_plan_hash(plan: RetrievalPlan) -> str:
|
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:
|
Change retrieval node from:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ The additive insight:
|
||||||
|
|
||||||
Three new pieces, each scoped tight:
|
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:
|
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:
|
**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
|
+ `detect_frame(question, sources, phrase_match_roots)`. Detects
|
||||||
reference-frame queries via the conjunction of (phrase route
|
reference-frame queries via the conjunction of (phrase route
|
||||||
fired) AND (phrase-matched source is a reference work — title
|
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
|
`protagonist` / `plot`). Distinct-marker count (not total) keeps
|
||||||
the heuristic robust against single-marker repetition (e.g. a
|
the heuristic robust against single-marker repetition (e.g. a
|
||||||
history article saying "novel approach" twice doesn't trip).
|
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
|
using the article LEAD (chunk_idx=0, post-wikitext-strip) as the
|
||||||
body sample so fiction markers cluster where they appear on
|
body sample so fiction markers cluster where they appear on
|
||||||
Wikipedia (lead paragraph).
|
Wikipedia (lead paragraph).
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ covers that path separately).
|
||||||
|
|
||||||
## 1. Problem statement
|
## 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:
|
two anchor classes:
|
||||||
|
|
||||||
- **Proper-noun anchors** (relation-shape questions): for
|
- **Proper-noun anchors** (relation-shape questions): for
|
||||||
|
|
@ -128,7 +128,7 @@ shape to constrain it.
|
||||||
|
|
||||||
### 3.1 Question-shape detector extension
|
### 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:
|
Add:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
|
@ -251,7 +251,7 @@ To add when this ticket lands:
|
||||||
## 6. Status
|
## 6. Status
|
||||||
|
|
||||||
**Closed 2026-05-02.** Landed via per-shape detectors + extractors
|
**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_entity_list_shape` / `_question_is_count_shape` /
|
||||||
`_question_is_why_shape` regex detectors.
|
`_question_is_why_shape` regex detectors.
|
||||||
|
|
|
||||||
|
|
@ -110,13 +110,13 @@ ladder migration render under the new ladder identically.
|
||||||
|
|
||||||
## 5. Implementation sketch
|
## 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
|
`verify_claim_lattice` and `verify_claim_lattice_json` add a
|
||||||
`verifier_steps_ran: list[str]` field on the verdict dict
|
`verifier_steps_ran: list[str]` field on the verdict dict
|
||||||
listing which verifier paths fired (`pointer_resolve`,
|
listing which verifier paths fired (`pointer_resolve`,
|
||||||
`source_role`, `coverage`, `pointer_cap`, `warrant`,
|
`source_role`, `coverage`, `pointer_cap`, `warrant`,
|
||||||
`lazy_anchor_demote`).
|
`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
|
add the four-rung mapping function gated on
|
||||||
`verifier_method.startswith("claim_lattice")`.
|
`verifier_method.startswith("claim_lattice")`.
|
||||||
3. **Bench update** (`bench/qa_sweep.py`): per-row
|
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:
|
**Closed 2026-05-02.** Landed via:
|
||||||
|
|
||||||
- `_render_audit_label(audit_mode, verifier_method, violations)`
|
- `_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
|
to `None` for backward-compat with callers that don't have the
|
||||||
list yet.
|
list yet.
|
||||||
- `_ladder_rung_for_lattice` helper computes the rung from
|
- `_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}$`).
|
1. Three random words from `/usr/share/dict/words` (`^[a-z]{5,12}$`).
|
||||||
2. Hermes @ temp=0.8 wove a creative question paragraph.
|
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`.
|
4. Append journey to `bench/emergent_log.jsonl`.
|
||||||
|
|
||||||
Aggregate distribution:
|
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
|
answer using overlapping vocabulary that the verifier's per-claim
|
||||||
citation-coverage check passed.
|
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
|
verified, no unverified spans, no violations. The verifier
|
||||||
genuinely accepted the claim.
|
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
|
1. **Rule 9 — SUBJECT_TOKENS_ABSENT detector — landed inline (no
|
||||||
separate ticket).** New per-claim check in
|
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
|
set of content tokens shared by question AND claim
|
||||||
(`_parroted_subject_tokens_absent`). For each, check substring
|
(`_parroted_subject_tokens_absent`). For each, check substring
|
||||||
presence in the union of cited evidence spans (lower-cased,
|
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.
|
- Zero blast radius for non-hyphen queries.
|
||||||
|
|
||||||
**Cons:**
|
**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`.
|
separate function) and stays on AND-mode `bi AND polar AND rare`.
|
||||||
Body-FTS won't pull in `Bipolar disorder` directly. Title-route +
|
Body-FTS won't pull in `Bipolar disorder` directly. Title-route +
|
||||||
accept-path 5 + rerank carries the recall load. Acceptable: title
|
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
|
## 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]*)+")`
|
`_HYPHEN_RUN_RE = re.compile(r"[A-Za-z][A-Za-z0-9]*(?:-[A-Za-z][A-Za-z0-9]*)+")`
|
||||||
plus pure helper:
|
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:
|
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)`
|
`_hyphen_fold_variants(s)` helper. `_title_query_tokens(s)`
|
||||||
additively merges the variants. `_filter_by_title_relevance`
|
additively merges the variants. `_filter_by_title_relevance`
|
||||||
gains optional `hyphen_fold_anchors` kwarg + accept-path 5
|
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.
|
when title-breadth fails). `_rerank` threads the kwarg.
|
||||||
`_search_corpus` caller computes
|
`_search_corpus` caller computes
|
||||||
`_hyphen_fold_variants(retrieval_query)` and passes through.
|
`_hyphen_fold_variants(retrieval_query)` and passes through.
|
||||||
- `aborist/qa/query.py:DEFAULT_QUERY_POLICY` and
|
- `arborist/qa/query.py:DEFAULT_QUERY_POLICY` and
|
||||||
`aborist/qa/runner.py:DEFAULT_POLICY` — `hyphen_fold_v1: True`
|
`arborist/qa/runner.py:DEFAULT_POLICY` — `hyphen_fold_v1: True`
|
||||||
marker. Folds into `governance_policy_hash` so records
|
marker. Folds into `governance_policy_hash` so records
|
||||||
produced under the new rule cache-split cleanly.
|
produced under the new rule cache-split cleanly.
|
||||||
- `tests/test_query.py` — 4 new tests:
|
- `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
|
The categories below derive from formal-semantics quantifier theory
|
||||||
(Mostowski generalized quantifiers; Barwise-Cooper; Partee D- vs A-
|
(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
|
needs: **expected number of claims in the answer**. Categories that
|
||||||
don't change the expected answer length are dropped from this table
|
don't change the expected answer length are dropped from this table
|
||||||
and surfaced as adjacent dimensions in Section 2.1.
|
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
|
modify a numeric bound (`about ten`) — fold into the
|
||||||
SMALL_NUM_EXPLICIT or COMPARATIVE_BOUND rung with the bound.
|
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*:
|
Each new rung names a *distinct expected-answer-length distribution*:
|
||||||
|
|
||||||
|
|
@ -226,7 +226,7 @@ Original (superseded):
|
||||||
|
|
||||||
Land Option D. Concrete plan:
|
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
|
`classify_question_quantifier(question: str) -> dict` returning
|
||||||
`{"intensity": "ALL"|"MANY"|"FEW"|"SMALL_NUM"|"SINGULAR"|"ABSENT",
|
`{"intensity": "ALL"|"MANY"|"FEW"|"SMALL_NUM"|"SINGULAR"|"ABSENT",
|
||||||
"matched_token": str, "explicit_count": int | None}`. Pure
|
"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).
|
across the board, so opting in costs nothing).
|
||||||
3. Per-model profile registry. `model_profile_hash` (already in the
|
3. Per-model profile registry. `model_profile_hash` (already in the
|
||||||
8-dim cache key) gets a quantifier-cap profile attached. The
|
8-dim cache key) gets a quantifier-cap profile attached. The
|
||||||
profile lives in `aborist/qa/model_profiles.py` (new file).
|
profile lives in `arborist/qa/model_profiles.py` (new file).
|
||||||
4. Preflight wiring in `aborist/qa/runner.py:ask` and
|
4. Preflight wiring in `arborist/qa/runner.py:ask` and
|
||||||
`aborist/qa/query.py:query`: classify the question, look up the
|
`arborist/qa/query.py:query`: classify the question, look up the
|
||||||
intensity-keyed cap from the model profile, override the
|
intensity-keyed cap from the model profile, override the
|
||||||
`claim_lattice_max_claims_per_answer` for this call.
|
`claim_lattice_max_claims_per_answer` for this call.
|
||||||
5. Optional reminder injection in the prompt builder (gated on
|
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:
|
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.
|
`raw_answer` surfaced on the `query()` result dict.
|
||||||
- `bench/qa_sweep.py:_run_one` — three new fields per row:
|
- `bench/qa_sweep.py:_run_one` — three new fields per row:
|
||||||
`format_collapsed`, `violation_kinds` (sorted unique kind
|
`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
|
MANY? The "tell me all there is to know" precedent (york-england
|
||||||
case, ticket #000006) suggests yes — operationally it produces
|
case, ticket #000006) suggests yes — operationally it produces
|
||||||
the same enumeration pressure.
|
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
|
or extend the existing `model_profile_hash` derivation in
|
||||||
`keys.py`? Initial answer: new file, hash-derived from the profile
|
`keys.py`? Initial answer: new file, hash-derived from the profile
|
||||||
dict so changing a cap invalidates prior records.
|
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
|
surfaces through the existing TOO_MANY_CLAIMS violation path on
|
||||||
the audit-line tail.
|
the audit-line tail.
|
||||||
- This ticket does NOT touch retrieval. Quantifier guard runs at
|
- 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.
|
Sections 1–9) stays untouched.
|
||||||
|
|
||||||
## 8. Status
|
## 8. Status
|
||||||
|
|
@ -476,9 +476,9 @@ Implementation commit chain (all on `origin/main`):
|
||||||
|-----------|-------|----------------------------------------------------------|-------|
|
|-----------|-------|----------------------------------------------------------|-------|
|
||||||
| `2ffed00` | 0 | bench-harness extension (FC rate, violation kinds) | +5 |
|
| `2ffed00` | 0 | bench-harness extension (FC rate, violation kinds) | +5 |
|
||||||
| `5e8d662` | 0.x | bench-row telemetry (pointer/bracket/profile fields) | +5 |
|
| `5e8d662` | 0.x | bench-row telemetry (pointer/bracket/profile fields) | +5 |
|
||||||
| `926b05e` | 1 | pure quantifier classifier `aborist/qa/quantifier.py` | +61 |
|
| `926b05e` | 1 | pure quantifier classifier `arborist/qa/quantifier.py` | +61 |
|
||||||
| `84d5b5c` | 2 | model-profile caps `aborist/qa/model_profiles.py` | +19 |
|
| `84d5b5c` | 2 | model-profile caps `arborist/qa/model_profiles.py` | +19 |
|
||||||
| `6f90f21` | 3 | reminder injection `aborist/qa/quantifier_reminder.py` | +19 |
|
| `6f90f21` | 3 | reminder injection `arborist/qa/quantifier_reminder.py` | +19 |
|
||||||
| `5a60e85` | 4 | CLI flags + 4 violation kinds + reject-broad early-return | +16 |
|
| `5a60e85` | 4 | CLI flags + 4 violation kinds + reject-broad early-return | +16 |
|
||||||
|
|
||||||
Total: **906 tests passing (120 new); 36 skipped.**
|
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
|
reject_broad, caps_by_intensity, guard_modes, plus Phase 0
|
||||||
format_collapse_check_enabled)
|
format_collapse_check_enabled)
|
||||||
4. Per-mode: `quantifier_guard_modes` (quote opts out by default)
|
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
|
6. Master via governance: all 7 quantifier policy fields fold into
|
||||||
`_VERIFIER_POLICY_FIELDS` so flipping any of them invalidates
|
`_VERIFIER_POLICY_FIELDS` so flipping any of them invalidates
|
||||||
prior cache records on lookup
|
prior cache records on lookup
|
||||||
|
|
@ -511,7 +511,7 @@ quantifier_guard_modes ["claim_lattice_pointer",
|
||||||
Live end-to-end verification (post-`5a60e85`):
|
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?
|
Winners of all major sports?
|
||||||
UNGROUNDED · via BROAD_QUANTIFIER_REJECTED · ALL ("all")
|
UNGROUNDED · via BROAD_QUANTIFIER_REJECTED · ALL ("all")
|
||||||
· cap was 8 0/0 0.0s (preflight)
|
· 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
|
universe. Narrow ... or run with --allow-broad for exploratory
|
||||||
enumeration.
|
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
|
name all members of the Beatles
|
||||||
UNGROUNDED · via claim_lattice · title mismatch 4/4 20.9s
|
UNGROUNDED · via claim_lattice · title mismatch 4/4 20.9s
|
||||||
[Beatles enumerated; scope_bound_hint=bounded → not rejected]
|
[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`)
|
#### 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:
|
pure function (no I/O, no model call, no retrieval call). Returns:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|
@ -780,7 +780,7 @@ refinement.
|
||||||
|
|
||||||
#### Phase 2 — Model-profile claim-budget contract (LANDED `84d5b5c`)
|
#### 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
|
- `adamo1139/Hermes-3-Llama-3.1-8B-FP8-Dynamic` — tight caps for
|
||||||
broad intensities (ALL=8, COMPREHENSIVE=5, OPEN_REQUEST=5).
|
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)
|
#### 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:
|
`broad_quantifier_reminder()` with two templates:
|
||||||
|
|
||||||
- **Bounded universe** (`all members of the Beatles`):
|
- **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`)
|
#### 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
|
--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
|
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
|
This keeps the schema and the four-rung ladder unchanged. Display
|
||||||
labels carry the broad-quantifier signal via the existing tail
|
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
|
answer_chars_with_brackets: int char count of bracket-tagged regions
|
||||||
model_profile_id: str e.g. "hermes-3-llama-3.1-8b-fp8"
|
model_profile_id: str e.g. "hermes-3-llama-3.1-8b-fp8"
|
||||||
(with the FP8 suffix; the
|
(with the FP8 suffix; the
|
||||||
ABORIST_LLM_MODEL default uses the
|
ARBORIST_LLM_MODEL default uses the
|
||||||
FP8-Dynamic variant)
|
FP8-Dynamic variant)
|
||||||
scope_bound_hint: str classifier output (10.1):
|
scope_bound_hint: str classifier output (10.1):
|
||||||
bounded / unbounded / unknown
|
bounded / unbounded / unknown
|
||||||
|
|
@ -1457,7 +1457,7 @@ From most granular to most global:
|
||||||
Level 1 — Per-test override
|
Level 1 — Per-test override
|
||||||
Tests pass policy={"quantifier_guard_enabled": False}.
|
Tests pass policy={"quantifier_guard_enabled": False}.
|
||||||
Same mechanism claim_lattice_warrant_check_enabled already uses
|
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
|
Level 2 — Per-call CLI flag
|
||||||
--no-quantifier-guard kills Phase 1+2 for one call
|
--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.
|
on baseline; its failure shape is different.
|
||||||
|
|
||||||
Level 5 — Per-model profile
|
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,
|
Models without an entry fall back to the "default" profile,
|
||||||
which has the conservative caps from §10.6 (no Hermes-tight
|
which has the conservative caps from §10.6 (no Hermes-tight
|
||||||
tightening).
|
tightening).
|
||||||
|
|
@ -1612,18 +1612,18 @@ Single-source-of-truth for **what was actually built** in the
|
||||||
|
|
||||||
| Path | Purpose | Phase |
|
| Path | Purpose | Phase |
|
||||||
|-------------------------------------|------------------------------------------------------------------------|-------|
|
|-------------------------------------|------------------------------------------------------------------------|-------|
|
||||||
| `aborist/qa/quantifier.py` | Pure 10-rung classifier + scope-bound heuristic | 1 |
|
| `arborist/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 |
|
| `arborist/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_reminder.py` | `broad_quantifier_reminder()` with bounded vs unbounded templates | 3 |
|
||||||
|
|
||||||
### 11.2 Modified modules (new behavior)
|
### 11.2 Modified modules (new behavior)
|
||||||
|
|
||||||
| Path | Change | Phase |
|
| 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 |
|
| `arborist/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 |
|
| `arborist/qa/runner.py` | same wiring as query() for `arborist 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 |
|
| `arborist/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/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 |
|
| `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
|
### 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)
|
### 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.
|
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)
|
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)
|
--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
|
Three soft demotes (cap ladder at ANCHOR-WARRANTED) + one hard demote
|
||||||
(early-return UNGROUNDED). All four render audit-line tails via
|
(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"
|
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:
|
but are now binding via tests + commits:
|
||||||
|
|
||||||
1. **`RUNG_PRIORITY` puts COMPREHENSIVE above OPEN_REQUEST**
|
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
|
tests when `tell me everything about all wars` matched both
|
||||||
templates. COMPREHENSIVE wins because §2.2 calls it "strictly
|
templates. COMPREHENSIVE wins because §2.2 calls it "strictly
|
||||||
stronger than ALL"; OPEN_REQUEST is a softer shape-detector.
|
stronger than ALL"; OPEN_REQUEST is a softer shape-detector.
|
||||||
2. **Bounded-domain anchor regex list**
|
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
|
list covering Beatles, US states/presidents, planets, founding
|
||||||
fathers, continents, oceans, plus a year-anchor `(?:19|20)\d{2}`
|
fathers, continents, oceans, plus a year-anchor `(?:19|20)\d{2}`
|
||||||
pattern and a season/year/championship/tournament prefix. Future
|
pattern and a season/year/championship/tournament prefix. Future
|
||||||
refinement (§10.1 noted): corpus-arity check.
|
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
|
Two-template split (bounded vs unbounded); the bounded template
|
||||||
omits the "do not enumerate from training prior" clause because
|
omits the "do not enumerate from training prior" clause because
|
||||||
the corpus has the answer set. Unknown scope falls back to the
|
the corpus has the answer set. Unknown scope falls back to the
|
||||||
stricter unbounded template (over-warn rather than under-warn).
|
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 +
|
`BROAD-QUANTIFIER PREFLIGHT REJECTED · scope unbounded` header +
|
||||||
actionable narrowing hints (year, league, country, category) +
|
actionable narrowing hints (year, league, country, category) +
|
||||||
`--allow-broad` escape-hatch mention. Format chosen so an
|
`--allow-broad` escape-hatch mention. Format chosen so an
|
||||||
operator reading raw output sees both the WHY and the HOW-TO-FIX.
|
operator reading raw output sees both the WHY and the HOW-TO-FIX.
|
||||||
5. **`_render_query_human` branch for `broad_quantifier_rejected`
|
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"
|
status doesn't fall through to the generic "unknown error"
|
||||||
handler. Renders intensity, matched token, cap, and the rejection
|
handler. Renders intensity, matched token, cap, and the rejection
|
||||||
text on a single audit-line + body.
|
text on a single audit-line + body.
|
||||||
6. **EXPLICIT_COUNT defensive fallback to MANY cap**
|
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
|
classifier fired SMALL_NUM_EXPLICIT or COMPARATIVE_BOUND but
|
||||||
didn't extract a count (shouldn't happen but covers regressions),
|
didn't extract a count (shouldn't happen but covers regressions),
|
||||||
fall back to the same profile's MANY cap rather than crash.
|
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
|
### 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
|
preflight rejection, 0.0s, no LLM call. Rendered output recorded
|
||||||
in §8 Status.
|
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
|
→ ran the LLM (scope_bound_hint=bounded correctly suppressed
|
||||||
rejection), enumerated 4 verified Beatles members. Rendered output
|
rejection), enumerated 4 verified Beatles members. Rendered output
|
||||||
recorded in §8 Status.
|
recorded in §8 Status.
|
||||||
|
|
@ -2239,10 +2239,10 @@ quote:
|
||||||
|
|
||||||
Code changes (commit pending):
|
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"]`.
|
new field `quantifier_apply_caps_modes=["claim_lattice"]`.
|
||||||
- `aborist/qa/query.py`: same defaults.
|
- `arborist/qa/query.py`: same defaults.
|
||||||
- `aborist/qa/keys.py`: `quantifier_apply_caps_modes` added to
|
- `arborist/qa/keys.py`: `quantifier_apply_caps_modes` added to
|
||||||
`_VERIFIER_POLICY_FIELDS` so flipping it bumps governance hash.
|
`_VERIFIER_POLICY_FIELDS` so flipping it bumps governance hash.
|
||||||
- Cap-application gate now reads:
|
- Cap-application gate now reads:
|
||||||
`if apply_caps AND mode in apply_caps_modes AND cap is not None:`
|
`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
|
# Default behavior (no flag): reminder ON for lattice modes; cap
|
||||||
# OFF (operator opt-in only).
|
# 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):
|
# 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):
|
# 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"]' "..."
|
--policy quantifier_apply_caps_modes='["claim_lattice","claim_lattice_pointer"]' "..."
|
||||||
|
|
||||||
# Disable the reminder for one call:
|
# Disable the reminder for one call:
|
||||||
$ aborist query --policy quantifier_reminder_enabled=false "..."
|
$ arborist query --policy quantifier_reminder_enabled=false "..."
|
||||||
|
|
||||||
# Master kill:
|
# Master kill:
|
||||||
$ aborist query --no-quantifier-guard "..."
|
$ arborist query --no-quantifier-guard "..."
|
||||||
```
|
```
|
||||||
|
|
||||||
**Six-level disable hierarchy still intact** — no flag forced upon
|
**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
|
### 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,
|
helpers): `_quantifier_preflight_node(quantifier, policy,
|
||||||
effective_max_claims, reminder_injected)`.
|
effective_max_claims, reminder_injected)`.
|
||||||
- Wire into `query()` at the same point quantifier classification
|
- Wire into `query()` at the same point quantifier classification
|
||||||
|
|
@ -176,7 +176,7 @@ preflight contract").
|
||||||
## 4. Implementation sketch
|
## 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, ...)
|
def quantifier_preflight_node(quantifier, policy, ...)
|
||||||
2. Wire it into query() between policy resolution and t_start.
|
2. Wire it into query() between policy resolution and t_start.
|
||||||
3. Wire it into runner.ask() symmetrically.
|
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-
|
**Closed · landed 2026-05-03 (zero-shot).** Mechanism shipped end-
|
||||||
to-end:
|
to-end:
|
||||||
|
|
||||||
- `aborist/qa/dag.py:preflight_node_hash()` — combines
|
- `arborist/qa/dag.py:preflight_node_hash()` — combines
|
||||||
QuestionState + quantifier classifier output + behavioral
|
QuestionState + quantifier classifier output + behavioral
|
||||||
policy_state into one canonical SHA-256 hex.
|
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
|
`preflight_hash` parameter inserts a `preflight` stage at
|
||||||
position 1 (between `question` and `retrieval`). Quote-mode
|
position 1 (between `question` and `retrieval`). Quote-mode
|
||||||
shape becomes 8 stages; pointer-mode CTI shape becomes 10
|
shape becomes 8 stages; pointer-mode CTI shape becomes 10
|
||||||
stages. Backward-compatible: when `preflight_hash` is None,
|
stages. Backward-compatible: when `preflight_hash` is None,
|
||||||
shapes stay 7/9 exactly so legacy `run_dag_root` values
|
shapes stay 7/9 exactly so legacy `run_dag_root` values
|
||||||
re-validate.
|
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
|
the preflight payload from `question_state`, the quantifier
|
||||||
dict, and a 10-field `policy_state` capturing the *behavioral*
|
dict, and a 10-field `policy_state` capturing the *behavioral*
|
||||||
decisions taken on this run (guard_enabled, apply_caps_active,
|
decisions taken on this run (guard_enabled, apply_caps_active,
|
||||||
|
|
@ -276,7 +276,7 @@ preflight contract").
|
||||||
### 7.2 What's NOT in this ticket
|
### 7.2 What's NOT in this ticket
|
||||||
|
|
||||||
- **CLI flag for inspecting preflight node**: a future
|
- **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.
|
the preflight payload from `run_dag_blob`. Out of scope here.
|
||||||
- **Bench harness preflight-hash field**: bench rows could
|
- **Bench harness preflight-hash field**: bench rows could
|
||||||
surface `preflight_hash` (12-char prefix like `cache_key`)
|
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):
|
**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
|
`question → preflight → final_label` DAG for the reject-broad
|
||||||
early-return path. `query()` now wires it in and returns
|
early-return path. `query()` now wires it in and returns
|
||||||
`run_dag_root` + `run_dag_blob` on the rejection result dict.
|
`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)
|
### 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.
|
`scope_bound_hint`. Meta-cognition wraps it; doesn't duplicate.
|
||||||
|
|
||||||
### 6.2 Temporal sensitivity
|
### 6.2 Temporal sensitivity
|
||||||
|
|
@ -204,7 +204,7 @@ record `false_premise_suspected` so the audit-line can surface it.
|
||||||
### 6.5 Reference-frame detector
|
### 6.5 Reference-frame detector
|
||||||
|
|
||||||
Reuse the existing frame-detection logic from ticket #000002
|
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
|
`reference` vs `literal` vs `ambiguous`. Meta-cognition wraps the
|
||||||
output as `reference_frames: list[str]`.
|
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
|
Conservative — defaults to `"likely_in_corpus"` for typical
|
||||||
encyclopedic questions.
|
encyclopedic questions.
|
||||||
|
|
||||||
## 7. Implementation in Aborist
|
## 7. Implementation in Arborist
|
||||||
|
|
||||||
### 7.1 New module
|
### 7.1 New module
|
||||||
|
|
||||||
`aborist/qa/metacognition.py` with:
|
`arborist/qa/metacognition.py` with:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def preflight_question(
|
def preflight_question(
|
||||||
|
|
@ -325,13 +325,13 @@ to fox.
|
||||||
|
|
||||||
### 12.1 Phase 1 — module + detectors (LANDED `55efb04`)
|
### 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`)
|
- `QuestionState` dataclass (frozen, JSON-serializable via `to_dict`)
|
||||||
- `preflight_question()` pure function
|
- `preflight_question()` pure function
|
||||||
- 4 new deterministic detectors (temporal, contradiction, false-
|
- 4 new deterministic detectors (temporal, contradiction, false-
|
||||||
premise-lite, out-of-corpus)
|
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
|
- 8 LogicalStatus values + 3 PreflightResult values
|
||||||
- 6 per-detector enable switches in policy
|
- 6 per-detector enable switches in policy
|
||||||
- `preflight_policy_hash` for governance binding (Phase 3)
|
- `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
|
All folded into `_VERIFIER_POLICY_FIELDS` so flipping any of them
|
||||||
invalidates prior cache records.
|
invalidates prior cache records.
|
||||||
|
|
||||||
CLI flags on `aborist query`:
|
CLI flags on `arborist query`:
|
||||||
|
|
||||||
```
|
```
|
||||||
--no-preflight disable the guard for one call
|
--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`
|
The hint always carries the `SOFT_` prefix on its `classifier_label`
|
||||||
so it can never be mistaken for a deterministic verdict by:
|
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)
|
- The verifier (which doesn't read sidecar fields anyway)
|
||||||
- Bench aggregations
|
- Bench aggregations
|
||||||
- Operators inspecting bench JSONL or run-DAG blobs
|
- 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
|
## 3. Where it lives
|
||||||
|
|
||||||
```
|
```
|
||||||
aborist/qa/soft_preflight.py — sidecar implementation
|
arborist/qa/soft_preflight.py — sidecar implementation
|
||||||
(new module)
|
(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:
|
diagnostic for span classification) — same architectural pattern:
|
||||||
soft signal, never enters proof path, never written to providence
|
soft signal, never enters proof path, never written to providence
|
||||||
cache, never bumps governance hash on use.
|
cache, never bumps governance hash on use.
|
||||||
|
|
@ -99,7 +99,7 @@ remains driven by the hard policy.
|
||||||
## 5. Implementation sketch
|
## 5. Implementation sketch
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# aborist/qa/soft_preflight.py
|
# arborist/qa/soft_preflight.py
|
||||||
|
|
||||||
def soft_preflight_question(
|
def soft_preflight_question(
|
||||||
question: str,
|
question: str,
|
||||||
|
|
@ -149,17 +149,17 @@ def soft_preflight_question(
|
||||||
|
|
||||||
## 6. Integration points
|
## 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
|
`preflight_question()` (the hard call) but before the main LLM
|
||||||
call. Soft hint surfaces on result dict as
|
call. Soft hint surfaces on result dict as
|
||||||
`soft_preflight_hint`; merges into the run-DAG `preflight`
|
`soft_preflight_hint`; merges into the run-DAG `preflight`
|
||||||
stage payload as a sub-field of `question_state`.
|
stage payload as a sub-field of `question_state`.
|
||||||
- `aborist/qa/runner.py` — same wiring.
|
- `arborist/qa/runner.py` — same wiring.
|
||||||
- `aborist/cli.py` — new `--soft-preflight` flag (per-call
|
- `arborist/cli.py` — new `--soft-preflight` flag (per-call
|
||||||
override; default off matches policy default).
|
override; default off matches policy default).
|
||||||
- `bench/qa_sweep.py` — bench rows pick up `soft_preflight_label`
|
- `bench/qa_sweep.py` — bench rows pick up `soft_preflight_label`
|
||||||
+ `soft_preflight_confidence` as bounded-projection fields.
|
+ `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
|
`· soft: <label>` on the audit-line (clearly differentiated from
|
||||||
the hard `· false premise` etc. tails). Operator can tell at a
|
the hard `· false premise` etc. tails). Operator can tell at a
|
||||||
glance whether a tail is hard or soft.
|
glance whether a tail is hard or soft.
|
||||||
|
|
@ -225,7 +225,7 @@ hash never enters the proof path" rule (cf. whitepaper
|
||||||
|
|
||||||
## 11. What landed
|
## 11. What landed
|
||||||
|
|
||||||
- **`aborist/qa/soft_preflight.py`** — `SoftPreflightHint`
|
- **`arborist/qa/soft_preflight.py`** — `SoftPreflightHint`
|
||||||
dataclass + `soft_preflight_question()` pure function.
|
dataclass + `soft_preflight_question()` pure function.
|
||||||
9 canonical labels (`SOFT_WELL_FORMED`,
|
9 canonical labels (`SOFT_WELL_FORMED`,
|
||||||
`SOFT_FALSE_PREMISE_SUSPECTED`, `SOFT_CONTRADICTION_SUSPECTED`,
|
`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`).
|
(raises → `SOFT_PARSE_FAIL`, drift → `SOFT_PARSE_FAIL`).
|
||||||
`SOFT_PREFLIGHT_VERSION = "soft-preflight-v0.1"`.
|
`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),
|
pre-retrieval segment. One short LLM call (~200ms median),
|
||||||
result surfaces as `soft_preflight_hint` on the result dict
|
result surfaces as `soft_preflight_hint` on the result dict
|
||||||
+ `soft_preflight_ms` in the timings 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
|
`soft_preflight_enabled: False` policy default; cache row
|
||||||
identity unchanged (NOT folded into `_VERIFIER_POLICY_FIELDS`
|
identity unchanged (NOT folded into `_VERIFIER_POLICY_FIELDS`
|
||||||
per §4 — soft hints don't gate cache identity).
|
per §4 — soft hints don't gate cache identity).
|
||||||
|
|
||||||
- **`aborist/cli.py`** — `--soft-preflight` flag on
|
- **`arborist/cli.py`** — `--soft-preflight` flag on
|
||||||
`aborist query`. Audit-line tail renders soft hints as
|
`arborist query`. Audit-line tail renders soft hints as
|
||||||
`· soft: <label>` (e.g. `· soft: time sensitive`) — distinct
|
`· soft: <label>` (e.g. `· soft: time sensitive`) — distinct
|
||||||
from hard tails so operators see the signal separation at a
|
from hard tails so operators see the signal separation at a
|
||||||
glance. SOFT_DISABLED / SOFT_PARSE_FAIL / SOFT_WELL_FORMED
|
glance. SOFT_DISABLED / SOFT_PARSE_FAIL / SOFT_WELL_FORMED
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@ contend.
|
||||||
|
|
||||||
Wired into `make backfill-concepts`. Usage:
|
Wired into `make backfill-concepts`. Usage:
|
||||||
|
|
||||||
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 ~/.aborist/shards \
|
python scripts/backfill_concepts.py --shards-dir ~/.arborist/shards \
|
||||||
--extractors link_reciprocity,token_idf,documents_fts
|
--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
|
--workers 8
|
||||||
|
|
||||||
Skips shards that aren't numeric-stem (`000.db`, `001.db`, …) by
|
Skips shards that aren't numeric-stem (`000.db`, `001.db`, …) by
|
||||||
|
|
@ -28,8 +28,8 @@ import sys
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from aborist.concepts.extract import EXTRACTORS
|
from arborist.concepts.extract import EXTRACTORS
|
||||||
from aborist.store import connect, discover_shards
|
from arborist.store import connect, discover_shards
|
||||||
|
|
||||||
|
|
||||||
# Default extractor order. link_reciprocity must run BEFORE token_idf
|
# 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(
|
parser.add_argument(
|
||||||
"--shards-dir",
|
"--shards-dir",
|
||||||
type=Path,
|
type=Path,
|
||||||
default=Path.home() / ".aborist" / "shards",
|
default=Path.home() / ".arborist" / "shards",
|
||||||
help="Directory containing *.db shards.",
|
help="Directory containing *.db shards.",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
|
|
@ -74,7 +74,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||||
default=",".join(DEFAULT_EXTRACTORS),
|
default=",".join(DEFAULT_EXTRACTORS),
|
||||||
help=(
|
help=(
|
||||||
"Comma-separated extractor names (registered in "
|
"Comma-separated extractor names (registered in "
|
||||||
"aborist.concepts.extract.EXTRACTORS). Default: "
|
"arborist.concepts.extract.EXTRACTORS). Default: "
|
||||||
f"{','.join(DEFAULT_EXTRACTORS)}"
|
f"{','.join(DEFAULT_EXTRACTORS)}"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Pick three random words from /usr/share/dict/words, ask Hermes at
|
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
|
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.
|
answer → audit_mode → sources → timings) as a single JSONL line.
|
||||||
|
|
||||||
Designed for **blue moon cadence**, NOT every-commit benching. The
|
Designed for **blue moon cadence**, NOT every-commit benching. The
|
||||||
|
|
@ -128,7 +128,7 @@ def run_one_cycle(
|
||||||
top_k: int,
|
top_k: int,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""One emergent cycle: words → question → answer → log entry 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()
|
t_pick = time.time()
|
||||||
question = generate_question(client, model_id, words)
|
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
|
# ignored. Surfaced 2026-05-02 by the swallowtail/upbraided/rockiest
|
||||||
# emergent log entry. Off-the-binary-chain — gives the teacher
|
# emergent log entry. Off-the-binary-chain — gives the teacher
|
||||||
# reviewer a flag for poetic-question-vs-literal-answer mismatches.
|
# 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(
|
metaphor = diagnose_metaphor_deflection(
|
||||||
question or "", result.get("answer_text") or ""
|
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:
|
def emit_unfirehose_session(entry: dict, model_id: str) -> None:
|
||||||
"""Write one unfirehose/1.0 session for this bench cycle.
|
"""Write one unfirehose/1.0 session for this bench cycle.
|
||||||
|
|
||||||
Same auto-discovery path as `aborist.cli._emit_query_journal`:
|
Same auto-discovery path as `arborist.cli._emit_query_journal`:
|
||||||
~/.aborist/unfirehose/{slug}/{session_uuid}.jsonl. Each cycle gets
|
~/.arborist/unfirehose/{slug}/{session_uuid}.jsonl. Each cycle gets
|
||||||
its own session file (one prompt → one answer = one session).
|
its own session file (one prompt → one answer = one session).
|
||||||
Failures must NEVER break the bench loop — wrapped at the call
|
Failures must NEVER break the bench loop — wrapped at the call
|
||||||
site.
|
site.
|
||||||
"""
|
"""
|
||||||
from aborist.journal import SessionWriter
|
from arborist.journal import SessionWriter
|
||||||
aborist_meta = {
|
arborist_meta = {
|
||||||
"audit_mode": entry.get("audit_mode"),
|
"audit_mode": entry.get("audit_mode"),
|
||||||
"verifier_method": entry.get("verifier_method"),
|
"verifier_method": entry.get("verifier_method"),
|
||||||
"n_quotes": entry.get("n_quotes"),
|
"n_quotes": entry.get("n_quotes"),
|
||||||
|
|
@ -236,7 +236,7 @@ def emit_unfirehose_session(entry: dict, model_id: str) -> None:
|
||||||
s.system_message(
|
s.system_message(
|
||||||
"bench-emergent cycle: 3-word triangulation",
|
"bench-emergent cycle: 3-word triangulation",
|
||||||
subtype="init",
|
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.user_message(entry.get("question") or "")
|
||||||
s.assistant_message(
|
s.assistant_message(
|
||||||
|
|
@ -244,7 +244,7 @@ def emit_unfirehose_session(entry: dict, model_id: str) -> None:
|
||||||
model=model_id,
|
model=model_id,
|
||||||
provider="hermes",
|
provider="hermes",
|
||||||
duration_ms=int((entry.get("answer_seconds") or 0) * 1000) or None,
|
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(
|
parser.add_argument(
|
||||||
"--shards-dir",
|
"--shards-dir",
|
||||||
type=Path,
|
type=Path,
|
||||||
default=Path.home() / ".aborist" / "shards",
|
default=Path.home() / ".arborist" / "shards",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--qa-db",
|
"--qa-db",
|
||||||
type=Path,
|
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("--answer-mode", default="claim_lattice")
|
||||||
parser.add_argument("--top-k", type=int, default=8)
|
parser.add_argument("--top-k", type=int, default=8)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--endpoint",
|
"--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(
|
parser.add_argument(
|
||||||
"--model",
|
"--model",
|
||||||
default=os.environ.get(
|
default=os.environ.get(
|
||||||
"ABORIST_LLM_MODEL",
|
"ARBORIST_LLM_MODEL",
|
||||||
"adamo1139/Hermes-3-Llama-3.1-8B-FP8-Dynamic",
|
"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}")
|
parser.error(f"shards dir not found: {ns.shards_dir}")
|
||||||
|
|
||||||
rng = random.Random(ns.seed)
|
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)
|
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)
|
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 asyncio
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
from aborist.sources.crawler.async_web_fetcher import (
|
from arborist.sources.crawler.async_web_fetcher import (
|
||||||
CrawlMode,
|
CrawlMode,
|
||||||
MediaItem,
|
MediaItem,
|
||||||
strip_uri_fragment,
|
strip_uri_fragment,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
These tests stub the network. The verbatim crawler tests in this same
|
These tests stub the network. The verbatim crawler tests in this same
|
||||||
directory exercise the underlying AsyncWebFetcher; here we exercise
|
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).
|
ETag capture, conditional HEAD classification).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -16,14 +16,14 @@ from unittest.mock import patch
|
||||||
import httpx
|
import httpx
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aborist.sources.crawler.bridge import (
|
from arborist.sources.crawler.bridge import (
|
||||||
_normalize,
|
_normalize,
|
||||||
_same_domain,
|
_same_domain,
|
||||||
crawl_seed,
|
crawl_seed,
|
||||||
ingest_crawled,
|
ingest_crawled,
|
||||||
recrawl_check,
|
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):
|
def _run_crawl(page_links, seed, depth, max_pages):
|
||||||
"""Helper: drive _crawl_seed_async with a stub fetcher."""
|
"""Helper: drive _crawl_seed_async with a stub fetcher."""
|
||||||
import asyncio
|
import asyncio
|
||||||
from aborist.sources.crawler import bridge
|
from arborist.sources.crawler import bridge
|
||||||
|
|
||||||
return asyncio.run(
|
return asyncio.run(
|
||||||
bridge._crawl_seed_async(
|
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):
|
async def fetch_webpage(self, url, extract_links=False, **kw):
|
||||||
return None, []
|
return None, []
|
||||||
|
|
||||||
from aborist.sources.crawler import bridge
|
from arborist.sources.crawler import bridge
|
||||||
|
|
||||||
monkeypatch.setattr(bridge, "AsyncWebFetcher", _CapturingFetcher)
|
monkeypatch.setattr(bridge, "AsyncWebFetcher", _CapturingFetcher)
|
||||||
bridge.crawl_seed("https://x.com/", max_depth=1, max_pages=1, fast=True)
|
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):
|
async def fetch_webpage(self, url, extract_links=False, **kw):
|
||||||
return None, []
|
return None, []
|
||||||
|
|
||||||
from aborist.sources.crawler import bridge
|
from arborist.sources.crawler import bridge
|
||||||
|
|
||||||
monkeypatch.setattr(bridge, "AsyncWebFetcher", _CapturingFetcher)
|
monkeypatch.setattr(bridge, "AsyncWebFetcher", _CapturingFetcher)
|
||||||
bridge.crawl_seed("https://x.com/", max_depth=1, max_pages=1)
|
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>", []
|
return "<html></html>", []
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from aborist.sources.crawler import bridge
|
from arborist.sources.crawler import bridge
|
||||||
|
|
||||||
fetcher = _RecordingFetcher()
|
fetcher = _RecordingFetcher()
|
||||||
asyncio.run(
|
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)
|
conn = connect(http_meta_db)
|
||||||
try:
|
try:
|
||||||
result = ingest_crawled(conn, [url])
|
result = ingest_crawled(conn, [url])
|
||||||
|
|
@ -324,7 +324,7 @@ def test_ingest_crawled_skips_non_html(http_meta_db):
|
||||||
"body": "%PDF-1.4 binary",
|
"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)
|
conn = connect(http_meta_db)
|
||||||
try:
|
try:
|
||||||
result = ingest_crawled(conn, [url])
|
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,
|
# Response stub never gets used because the path filter is pre-fetch,
|
||||||
# but we provide one in case the filter regresses.
|
# but we provide one in case the filter regresses.
|
||||||
responses = {feed_url: {"status": 200, "headers": {"content-type": "text/html"}}}
|
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)
|
conn = connect(http_meta_db)
|
||||||
try:
|
try:
|
||||||
result = ingest_crawled(conn, [feed_url])
|
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>",
|
"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)
|
conn = connect(http_meta_db)
|
||||||
try:
|
try:
|
||||||
result = ingest_crawled(conn, [url])
|
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>",
|
"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)
|
conn = connect(http_meta_db)
|
||||||
try:
|
try:
|
||||||
result = ingest_crawled(conn, [url])
|
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):
|
def _seed_meta(conn, *, document_root: str, document_uri: str, etag: str | None = None):
|
||||||
"""Insert a documents row + http_meta row for recheck tests."""
|
"""Insert a documents row + http_meta row for recheck tests."""
|
||||||
from aborist.store import transaction
|
from arborist.store import transaction
|
||||||
|
|
||||||
with transaction(conn):
|
with transaction(conn):
|
||||||
conn.execute(
|
conn.execute(
|
||||||
|
|
@ -451,7 +451,7 @@ def test_recrawl_check_classifies_304_as_fresh(tmp_path):
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
responses = {"https://x.com/p": {"head_status": 304}}
|
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)
|
conn = connect(db)
|
||||||
try:
|
try:
|
||||||
result = recrawl_check(conn, limit=10)
|
result = recrawl_check(conn, limit=10)
|
||||||
|
|
@ -471,7 +471,7 @@ def test_recrawl_check_classifies_200_as_stale(tmp_path):
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
responses = {"https://x.com/q": {"head_status": 200}}
|
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)
|
conn = connect(db)
|
||||||
try:
|
try:
|
||||||
result = recrawl_check(conn, limit=10)
|
result = recrawl_check(conn, limit=10)
|
||||||
|
|
@ -490,7 +490,7 @@ def test_recrawl_check_classifies_404_as_gone(tmp_path):
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
responses = {"https://x.com/r": {"head_status": 404}}
|
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)
|
conn = connect(db)
|
||||||
try:
|
try:
|
||||||
result = recrawl_check(conn, limit=10)
|
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},
|
"https://keep.com/a": {"head_status": 304},
|
||||||
# drop.com would error if the filter is broken; we expect it to be skipped.
|
# 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)
|
conn = connect(db)
|
||||||
try:
|
try:
|
||||||
result = recrawl_check(conn, domain="keep.com", limit=10)
|
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()
|
conn.close()
|
||||||
|
|
||||||
responses = {"https://x.com/s": {"head_status": 304}}
|
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)
|
conn = connect(db)
|
||||||
try:
|
try:
|
||||||
recrawl_check(conn, limit=10)
|
recrawl_check(conn, limit=10)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ Tests URL extraction, sources footer building, and web fetch helpers.
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from unittest.mock import Mock, AsyncMock, patch
|
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,
|
extract_url_from_content,
|
||||||
is_web_fetch_request,
|
is_web_fetch_request,
|
||||||
build_sources_footer,
|
build_sources_footer,
|
||||||
|
|
@ -85,8 +85,8 @@ class TestURLExtraction:
|
||||||
|
|
||||||
@pytest.mark.skip(
|
@pytest.mark.skip(
|
||||||
reason="chat-bot fetch triggers (has_web_fetch_trigger) were "
|
reason="chat-bot fetch triggers (has_web_fetch_trigger) were "
|
||||||
"dropped during the aborist lift — aborist has no chat surface; "
|
"dropped during the arborist lift — arborist has no chat surface; "
|
||||||
"see aborist/sources/crawler/__init__.py for rationale."
|
"see arborist/sources/crawler/__init__.py for rationale."
|
||||||
)
|
)
|
||||||
class TestWebFetchDetection:
|
class TestWebFetchDetection:
|
||||||
"""Test web fetch request detection."""
|
"""Test web fetch request detection."""
|
||||||
|
|
@ -106,8 +106,8 @@ class TestWebFetchDetection:
|
||||||
|
|
||||||
@pytest.mark.skip(
|
@pytest.mark.skip(
|
||||||
reason="chat-bot fresh-fetch triggers (has_fresh_fetch_trigger) were "
|
reason="chat-bot fresh-fetch triggers (has_fresh_fetch_trigger) were "
|
||||||
"dropped during the aborist lift — aborist has no chat surface; "
|
"dropped during the arborist lift — arborist has no chat surface; "
|
||||||
"see aborist/sources/crawler/__init__.py for rationale."
|
"see arborist/sources/crawler/__init__.py for rationale."
|
||||||
)
|
)
|
||||||
class TestFreshFetchDetection:
|
class TestFreshFetchDetection:
|
||||||
"""Test fresh fetch intent detection."""
|
"""Test fresh fetch intent detection."""
|
||||||
|
|
@ -300,7 +300,7 @@ class TestWebFetchIntegration:
|
||||||
"""Test successful fetch and cache operation"""
|
"""Test successful fetch and cache operation"""
|
||||||
# This would require mocking web_cache_manager
|
# This would require mocking web_cache_manager
|
||||||
# For now, just verify the function exists and has correct signature
|
# 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
|
import inspect
|
||||||
sig = inspect.signature(fetch_and_cache)
|
sig = inspect.signature(fetch_and_cache)
|
||||||
assert 'url' in sig.parameters
|
assert 'url' in sig.parameters
|
||||||
|
|
@ -312,7 +312,7 @@ class TestWebFetchIntegration:
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_fetch_and_cache_with_progress_callback(self):
|
async def test_fetch_and_cache_with_progress_callback(self):
|
||||||
"""Test fetch with progress callback"""
|
"""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 web cache manager
|
||||||
mock_manager = Mock()
|
mock_manager = Mock()
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import pytest
|
||||||
@pytest.fixture(scope="module")
|
@pytest.fixture(scope="module")
|
||||||
def qa_sweep():
|
def qa_sweep():
|
||||||
"""Import bench/qa_sweep.py as a module despite living outside the
|
"""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."""
|
pip-install of the bench harness."""
|
||||||
bench_path = Path(__file__).parent.parent / "bench" / "qa_sweep.py"
|
bench_path = Path(__file__).parent.parent / "bench" / "qa_sweep.py"
|
||||||
spec = importlib.util.spec_from_file_location("qa_sweep_under_test", bench_path)
|
spec = importlib.util.spec_from_file_location("qa_sweep_under_test", bench_path)
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ import time
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aborist.cli import _burn_cache_key
|
from arborist.cli import _burn_cache_key
|
||||||
from aborist.store import append_audit, connect, transaction
|
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:
|
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):
|
def test_burn_cli_invocation(tmp_path, capsys):
|
||||||
"""`aborist burn` end-to-end through build_parser, including JSON output."""
|
"""`arborist burn` end-to-end through build_parser, including JSON output."""
|
||||||
from aborist.cli import build_parser
|
from arborist.cli import build_parser
|
||||||
|
|
||||||
db = tmp_path / "qa.db"
|
db = tmp_path / "qa.db"
|
||||||
KEY = "fa" * 32
|
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
|
test seeds two rows, backdates one to 2h ago, and asserts the
|
||||||
fresh one burns and the old one stays."""
|
fresh one burns and the old one stays."""
|
||||||
import time as _time
|
import time as _time
|
||||||
from aborist.cli import build_parser
|
from arborist.cli import build_parser
|
||||||
from aborist.store import transaction
|
from arborist.store import transaction
|
||||||
|
|
||||||
db = tmp_path / "qa.db"
|
db = tmp_path / "qa.db"
|
||||||
OLD = "ab" * 32
|
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):
|
def test_burn_kindergarten_dry_run_writes_nothing(tmp_path, capsys):
|
||||||
"""--dry-run reports what would burn but doesn't write or audit."""
|
"""--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"
|
db = tmp_path / "qa.db"
|
||||||
KEY = "ef" * 32
|
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):
|
def test_burn_kindergarten_zero_seconds_burns_everything(tmp_path, capsys):
|
||||||
"""0-second window = burn every live providence_cache row."""
|
"""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"
|
db = tmp_path / "qa.db"
|
||||||
K1 = "11" * 32
|
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):
|
def test_burn_kindergarten_respects_children_gate_without_force(tmp_path, capsys):
|
||||||
"""A row with falsifications is REFUSED unless --force. Audit chain
|
"""A row with falsifications is REFUSED unless --force. Audit chain
|
||||||
still grows on burned rows; refused rows leave no audit entry."""
|
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"
|
db = tmp_path / "qa.db"
|
||||||
BURNABLE = "33" * 32
|
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):
|
def test_burn_cli_returns_non_zero_on_refused(tmp_path, capsys):
|
||||||
"""Non-zero exit when refused so scripts can detect and react."""
|
"""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"
|
db = tmp_path / "qa.db"
|
||||||
KEY = "fb" * 32
|
KEY = "fb" * 32
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,13 @@ import json
|
||||||
import time
|
import time
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
|
|
||||||
from aborist.cli import _burn_core_root, _burn_document_root
|
from arborist.cli import _burn_core_root, _burn_document_root
|
||||||
from aborist.distill import FirstSentenceDistiller
|
from arborist.distill import FirstSentenceDistiller
|
||||||
from aborist.distill.runner import distill_existing
|
from arborist.distill.runner import distill_existing
|
||||||
from aborist.document import Document
|
from arborist.document import Document
|
||||||
from aborist.ingest import ingest_source
|
from arborist.ingest import ingest_source
|
||||||
from aborist.source import Source
|
from arborist.source import Source
|
||||||
from aborist.store import append_audit, connect, transaction
|
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 enough that the default tok-512-v1 chunker emits multiple chunks.
|
||||||
_LONG = (
|
_LONG = (
|
||||||
"Aborist tends trees and forests of cross-linked information. " * 40
|
"Arborist tends trees and forests of cross-linked information. " * 40
|
||||||
+ "\n\n"
|
+ "\n\n"
|
||||||
+ "Burn is the kindergarten leaf removal — children gate enforced. " * 40
|
+ "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):
|
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"
|
db = tmp_path / "burn.db"
|
||||||
root = _ingest_one(db, "html://cli-doc")
|
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):
|
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"
|
db = tmp_path / "burn.db"
|
||||||
_, core_root = _ingest_and_distill(db, "html://cli-core")
|
_, 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):
|
def test_burn_cli_default_kind_is_providence(tmp_path, capsys):
|
||||||
"""Backwards compat: bare `burn --cache-key X` still works (no --kind)."""
|
"""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"
|
db = tmp_path / "burn.db"
|
||||||
# Seed a providence record reusing the existing test_burn helper logic.
|
# 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):
|
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"
|
db = tmp_path / "burn.db"
|
||||||
root = _ingest_one(db, "html://cli-refuse")
|
root = _ingest_one(db, "html://cli-refuse")
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aborist.qa.dag import build_run_dag, verify_run_dag
|
from arborist.qa.dag import build_run_dag, verify_run_dag
|
||||||
from aborist.qa.evidence import (
|
from arborist.qa.evidence import (
|
||||||
build_evidence_map,
|
build_evidence_map,
|
||||||
evidence_map_by_evidence_id,
|
evidence_map_by_evidence_id,
|
||||||
evidence_map_by_pointer_id,
|
evidence_map_by_pointer_id,
|
||||||
|
|
@ -22,11 +22,11 @@ from aborist.qa.evidence import (
|
||||||
render_claim_lattice,
|
render_claim_lattice,
|
||||||
render_evidence_map,
|
render_evidence_map,
|
||||||
)
|
)
|
||||||
from aborist.qa.keys import governance_policy_hash
|
from arborist.qa.keys import governance_policy_hash
|
||||||
from aborist.qa.parse_claims import parse_pointer_claims
|
from arborist.qa.parse_claims import parse_pointer_claims
|
||||||
from aborist.qa.runner import DEFAULT_POLICY
|
from arborist.qa.runner import DEFAULT_POLICY
|
||||||
from aborist.qa.query import DEFAULT_QUERY_POLICY
|
from arborist.qa.query import DEFAULT_QUERY_POLICY
|
||||||
from aborist.qa.verify import (
|
from arborist.qa.verify import (
|
||||||
ANSWER_MODES,
|
ANSWER_MODES,
|
||||||
DEFAULT_ANSWER_MODE,
|
DEFAULT_ANSWER_MODE,
|
||||||
verify_claim_lattice,
|
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):
|
def test_verifier_method_check_accepts_claim_lattice(tmp_path: Path):
|
||||||
"""Fresh shards include 'claim_lattice' in the verifier_method CHECK."""
|
"""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"
|
db = tmp_path / "fresh.db"
|
||||||
conn = connect(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):
|
def test_legacy_shard_migrates_in_place(tmp_path: Path):
|
||||||
"""An old shard with the pre-claim_lattice CHECK gets rebuilt on open
|
"""An old shard with the pre-claim_lattice CHECK gets rebuilt on open
|
||||||
AND keeps run_dag_root / run_dag_blob populated."""
|
AND keeps run_dag_root / run_dag_blob populated."""
|
||||||
from aborist.store import connect
|
from arborist.store import connect
|
||||||
|
|
||||||
db = tmp_path / "legacy.db"
|
db = tmp_path / "legacy.db"
|
||||||
c = sqlite3.connect(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
|
"""A single source with three chunks should produce three evidence
|
||||||
objects in the prompt-facing map — not one whole-doc entry."""
|
objects in the prompt-facing map — not one whole-doc entry."""
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
from aborist.document import Document
|
from arborist.document import Document
|
||||||
from aborist.ingest import ingest_source
|
from arborist.ingest import ingest_source
|
||||||
from aborist.qa.client import StubClient
|
from arborist.qa.client import StubClient
|
||||||
from aborist.qa.query import DEFAULT_QUERY_POLICY, query
|
from arborist.qa.query import DEFAULT_QUERY_POLICY, query
|
||||||
from aborist.source import Source
|
from arborist.source import Source
|
||||||
from aborist.store import connect
|
from arborist.store import connect
|
||||||
|
|
||||||
class FakeSource(Source):
|
class FakeSource(Source):
|
||||||
source_type = "test"
|
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():
|
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"}
|
qstem = {"brachiosaurus", "film"}
|
||||||
# Many distinct + many mentions wins.
|
# Many distinct + many mentions wins.
|
||||||
|
|
@ -895,7 +895,7 @@ def test_chunk_query_relevance_scores_by_overlap():
|
||||||
|
|
||||||
|
|
||||||
def test_chunk_query_relevance_stem_aware():
|
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".
|
# Query has the plural "dinosaurs"; chunk has the singular "dinosaur".
|
||||||
# The stem strip in _body_count_with_stem should still match.
|
# 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
|
of the document — G0.3 must promote it to E1 so a model that
|
||||||
lazy-anchors on early pointers still cites the relevant chunk."""
|
lazy-anchors on early pointers still cites the relevant chunk."""
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
from aborist.document import Document
|
from arborist.document import Document
|
||||||
from aborist.ingest import ingest_source
|
from arborist.ingest import ingest_source
|
||||||
from aborist.qa.client import StubClient
|
from arborist.qa.client import StubClient
|
||||||
from aborist.qa.evidence import build_evidence_map
|
from arborist.qa.evidence import build_evidence_map
|
||||||
from aborist.qa.query import (
|
from arborist.qa.query import (
|
||||||
DEFAULT_QUERY_POLICY, _load_doc_chunks, _chunk_query_relevance,
|
DEFAULT_QUERY_POLICY, _load_doc_chunks, _chunk_query_relevance,
|
||||||
)
|
)
|
||||||
from aborist.qa.query import _stem_token_for_match, _title_query_tokens
|
from arborist.qa.query import _stem_token_for_match, _title_query_tokens
|
||||||
from aborist.source import Source
|
from arborist.source import Source
|
||||||
from aborist.store import connect
|
from arborist.store import connect
|
||||||
|
|
||||||
class FakeSource(Source):
|
class FakeSource(Source):
|
||||||
source_type = "test"
|
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
|
signals recoverable from claim_statuses, and threading them in
|
||||||
would make run_dag_root depend on the model's anchoring habits."""
|
would make run_dag_root depend on the model's anchoring habits."""
|
||||||
import inspect as _inspect
|
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)
|
sig = _inspect.signature(build_run_dag)
|
||||||
params = set(sig.parameters)
|
params = set(sig.parameters)
|
||||||
assert "pointer_id_distribution" not in params
|
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
|
different chunks of the same source produce different evidence_ids
|
||||||
even though they share source_root."""
|
even though they share source_root."""
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
from aborist.document import Document
|
from arborist.document import Document
|
||||||
from aborist.ingest import ingest_source
|
from arborist.ingest import ingest_source
|
||||||
from aborist.qa.query import _load_doc_chunks
|
from arborist.qa.query import _load_doc_chunks
|
||||||
from aborist.source import Source
|
from arborist.source import Source
|
||||||
from aborist.store import connect
|
from arborist.store import connect
|
||||||
|
|
||||||
class FakeSource(Source):
|
class FakeSource(Source):
|
||||||
source_type = "test"
|
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;
|
The CLI default emits a human-readable render of the query result;
|
||||||
``--json`` switches to the raw record. Both paths must:
|
``--json`` switches to the raw record. Both paths must:
|
||||||
|
|
@ -12,7 +12,7 @@ import json
|
||||||
|
|
||||||
import pytest
|
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):
|
def test_unicode_round_trips_through_print_path(capsys):
|
||||||
"""Pin: a result with a Pokémon-style answer prints é, not \\u00e9.
|
"""Pin: a result with a Pokémon-style answer prints é, not \\u00e9.
|
||||||
Uses argparse via build_parser to exercise the real CLI dispatch."""
|
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()
|
parser = build_parser()
|
||||||
# Build a fake result and call _cmd_query's render path indirectly.
|
# Build a fake result and call _cmd_query's render path indirectly.
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,16 @@ from __future__ import annotations
|
||||||
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
from aborist.compress import (
|
from arborist.compress import (
|
||||||
_MIN_COMPRESS_BYTES,
|
_MIN_COMPRESS_BYTES,
|
||||||
_ZSTD_MAGIC,
|
_ZSTD_MAGIC,
|
||||||
is_compressed,
|
is_compressed,
|
||||||
pack_chunk,
|
pack_chunk,
|
||||||
unpack_chunk,
|
unpack_chunk,
|
||||||
)
|
)
|
||||||
from aborist.ingest import ingest_source
|
from arborist.ingest import ingest_source
|
||||||
from aborist.sources.wikipedia_xml import WikipediaXmlDump
|
from arborist.sources.wikipedia_xml import WikipediaXmlDump
|
||||||
from aborist.store import connect
|
from arborist.store import connect
|
||||||
|
|
||||||
|
|
||||||
def test_pack_passes_short_text_through_uncompressed():
|
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 = tmp_path / "wp.xml"
|
||||||
fixture.write_text(xml, encoding="utf-8")
|
fixture.write_text(xml, encoding="utf-8")
|
||||||
|
|
||||||
db_path = tmp_path / "aborist.db"
|
db_path = tmp_path / "arborist.db"
|
||||||
conn = connect(db_path)
|
conn = connect(db_path)
|
||||||
try:
|
try:
|
||||||
ingest_source(conn, WikipediaXmlDump(fixture))
|
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
|
"""FTS5 still indexes plaintext; chunks.content stays compressed; reads
|
||||||
that go through unpack_chunk reconstruct the original. The whole
|
that go through unpack_chunk reconstruct the original. The whole
|
||||||
pipeline keeps working."""
|
pipeline keeps working."""
|
||||||
from aborist.search import FTS5Backend
|
from arborist.search import FTS5Backend
|
||||||
|
|
||||||
big_body = "merkle providence wikipedia anarchism " * 200 # ~7.4 KB
|
big_body = "merkle providence wikipedia anarchism " * 200 # ~7.4 KB
|
||||||
xml = f"""<?xml version="1.0" encoding="UTF-8"?>
|
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 = tmp_path / "wp.xml"
|
||||||
fixture.write_text(xml, encoding="utf-8")
|
fixture.write_text(xml, encoding="utf-8")
|
||||||
|
|
||||||
db_path = tmp_path / "aborist.db"
|
db_path = tmp_path / "arborist.db"
|
||||||
conn = connect(db_path)
|
conn = connect(db_path)
|
||||||
try:
|
try:
|
||||||
ingest_source(conn, WikipediaXmlDump(fixture))
|
ingest_source(conn, WikipediaXmlDump(fixture))
|
||||||
|
|
|
||||||
|
|
@ -13,15 +13,15 @@ from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aborist.concepts import invalidate_cache as _invalidate_cache
|
from arborist.concepts import invalidate_cache as _invalidate_cache
|
||||||
from aborist.concepts.query import invalidate_cache
|
from arborist.concepts.query import invalidate_cache
|
||||||
from aborist.concepts.seed import seed_legacy_concepts
|
from arborist.concepts.seed import seed_legacy_concepts
|
||||||
from aborist.qa.concepts import (
|
from arborist.qa.concepts import (
|
||||||
has_compare_phrasing,
|
has_compare_phrasing,
|
||||||
rivalry_excluded,
|
rivalry_excluded,
|
||||||
synonym_expand,
|
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):
|
def test_export_invalidate_cache_alias(seeded_shards):
|
||||||
"""The package-level ``invalidate_cache`` is the same callable as
|
"""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."""
|
for callers that don't want to import the implementation module."""
|
||||||
assert _invalidate_cache is invalidate_cache
|
assert _invalidate_cache is invalidate_cache
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ exactly as recorded.
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from aborist.qa.dag import (
|
from arborist.qa.dag import (
|
||||||
build_run_dag,
|
build_run_dag,
|
||||||
localize_failure,
|
localize_failure,
|
||||||
preflight_node_hash,
|
preflight_node_hash,
|
||||||
|
|
@ -262,7 +262,7 @@ def test_preflight_node_hash_changes_with_policy_refs():
|
||||||
def test_preflight_node_hash_includes_node_version():
|
def test_preflight_node_hash_includes_node_version():
|
||||||
"""node_version field is part of the hashed payload so a future
|
"""node_version field is part of the hashed payload so a future
|
||||||
schema bump (preflight-node-v2 etc.) invalidates legacy nodes."""
|
schema bump (preflight-node-v2 etc.) invalidates legacy nodes."""
|
||||||
from aborist.qa.dag import (
|
from arborist.qa.dag import (
|
||||||
PREFLIGHT_NODE_VERSION,
|
PREFLIGHT_NODE_VERSION,
|
||||||
build_preflight_node_payload,
|
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
|
question → preflight → final_label. Audit replay can read the
|
||||||
stage list and tell instantly that this row is a preflight
|
stage list and tell instantly that this row is a preflight
|
||||||
rejection (3 stages = reject path)."""
|
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(
|
pre = preflight_node_hash(
|
||||||
question_state={"preflight_result": "PREFLIGHT_BLOCKED"},
|
question_state={"preflight_result": "PREFLIGHT_BLOCKED"},
|
||||||
quantifier={"intensity": "ALL", "is_broad": True,
|
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
|
"""Two reject runs that differ only in the preflight payload
|
||||||
(e.g. different policy state at rejection time) must produce
|
(e.g. different policy state at rejection time) must produce
|
||||||
different run_dag_root values."""
|
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(
|
pre_a = preflight_node_hash(
|
||||||
question_state={"preflight_result": "PREFLIGHT_BLOCKED"},
|
question_state={"preflight_result": "PREFLIGHT_BLOCKED"},
|
||||||
quantifier={"intensity": "ALL"},
|
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
|
"""3-stage reject DAG must verify the same way as the standard
|
||||||
7/9/8/10-stage shapes."""
|
7/9/8/10-stage shapes."""
|
||||||
import json
|
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(
|
pre = preflight_node_hash(
|
||||||
question_state={"preflight_result": "PREFLIGHT_BLOCKED"},
|
question_state={"preflight_result": "PREFLIGHT_BLOCKED"},
|
||||||
quantifier={"intensity": "ALL"},
|
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():
|
def test_extract_preflight_hash_from_blob_with_preflight():
|
||||||
"""Pull preflight stage hash out of a persisted run_dag_blob."""
|
"""Pull preflight stage hash out of a persisted run_dag_blob."""
|
||||||
import json
|
import json
|
||||||
from aborist.qa.dag import build_run_dag, preflight_node_hash
|
from arborist.qa.dag import build_run_dag, preflight_node_hash
|
||||||
from aborist.qa.query import _extract_preflight_hash_from_blob
|
from arborist.qa.query import _extract_preflight_hash_from_blob
|
||||||
pre = preflight_node_hash(
|
pre = preflight_node_hash(
|
||||||
question_state={"preflight_result": "PREFLIGHT_OK"},
|
question_state={"preflight_result": "PREFLIGHT_OK"},
|
||||||
quantifier={"intensity": "SINGULAR"},
|
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
|
"""Legacy blobs (no preflight stage) return None — this is the
|
||||||
fall-through path for cache rows written before #000009."""
|
fall-through path for cache rows written before #000009."""
|
||||||
import json
|
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
|
out = build_run_dag(**_kw()) # no preflight_hash
|
||||||
blob = json.dumps(out, separators=(",", ":"))
|
blob = json.dumps(out, separators=(",", ":"))
|
||||||
assert _extract_preflight_hash_from_blob(blob) is None
|
assert _extract_preflight_hash_from_blob(blob) is None
|
||||||
|
|
||||||
|
|
||||||
def test_extract_preflight_hash_returns_none_for_empty_or_invalid():
|
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(None) is None
|
||||||
assert _extract_preflight_hash_from_blob("") is None
|
assert _extract_preflight_hash_from_blob("") is None
|
||||||
assert _extract_preflight_hash_from_blob("not json {{{") 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.
|
"""The hard verifier never accepts a chat-client / LLM judge.
|
||||||
A future PR that adds a chat_client parameter to verify_quotes
|
A future PR that adds a chat_client parameter to verify_quotes
|
||||||
would silently introduce a soft-signal-as-hard-check leak."""
|
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)
|
sig = inspect.signature(verify_quotes)
|
||||||
forbidden = {"chat_client", "client", "llm", "judge", "model"}
|
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():
|
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)
|
sig = inspect.signature(verify_claim_lattice)
|
||||||
forbidden = {"chat_client", "client", "llm", "judge", "model"}
|
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():
|
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)
|
sig = inspect.signature(verify_claim_lattice_json)
|
||||||
forbidden = {"chat_client", "client", "llm", "judge", "model"}
|
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
|
include any LLM-as-judge token. If a future migration adds
|
||||||
`llm`, `model`, `judge`, or `nli` to the enum, the verifier
|
`llm`, `model`, `judge`, or `nli` to the enum, the verifier
|
||||||
started accepting soft signals into the hard chain."""
|
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.
|
# Find the verifier_method CHECK constraint in SCHEMA_SQL.
|
||||||
match = re.search(
|
match = re.search(
|
||||||
|
|
@ -97,14 +97,14 @@ def test_d1_verifier_method_enum_excludes_llm_judges():
|
||||||
def test_d2_answer_modes_include_lattice_variants():
|
def test_d2_answer_modes_include_lattice_variants():
|
||||||
"""ANSWER_MODES must contain both lattice variants so an agent
|
"""ANSWER_MODES must contain both lattice variants so an agent
|
||||||
can pick a pointer-clause-emitting mode."""
|
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_pointer" in ANSWER_MODES
|
||||||
assert "claim_lattice" in ANSWER_MODES
|
assert "claim_lattice" in ANSWER_MODES
|
||||||
|
|
||||||
|
|
||||||
def test_d2_default_answer_mode_is_a_known_mode():
|
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
|
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
|
"""The pointer-line parser is the surface that turns model
|
||||||
output into internal claim nodes. Its absence would mean
|
output into internal claim nodes. Its absence would mean
|
||||||
Hermes has nowhere to emit pointer clauses to."""
|
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"
|
sample = "Steve Jobs co-founded Apple. [E1]\n"
|
||||||
out = parse_pointer_claims(sample)
|
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.
|
pointer mode — it produces prose that the runtime structures.
|
||||||
A regression that asks the model for structured JSON in pointer
|
A regression that asks the model for structured JSON in pointer
|
||||||
mode would violate this directive."""
|
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 = (
|
multi_line = (
|
||||||
"Steve Jobs co-founded Apple. [E1]\n"
|
"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
|
chunks, not threaded in from the model's prompt-side
|
||||||
invention. Two chunks → two evidence objects with deterministic
|
invention. Two chunks → two evidence objects with deterministic
|
||||||
pointer ids."""
|
pointer ids."""
|
||||||
from aborist.qa.evidence import build_evidence_map
|
from arborist.qa.evidence import build_evidence_map
|
||||||
|
|
||||||
chunks = [
|
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.
|
"""The 9-stage CTI run-DAG must commit the evidence_map_root.
|
||||||
Without this, two runs whose retrieval surfaced different chunks
|
Without this, two runs whose retrieval surfaced different chunks
|
||||||
would produce indistinguishable run_dag_roots."""
|
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)
|
sig = inspect.signature(build_run_dag)
|
||||||
assert "evidence_map_root" in sig.parameters, (
|
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
|
Two runs with identical sources but different retrieval keywords
|
||||||
produce different `run_dag_root` values — provenance closes the
|
produce different `run_dag_root` values — provenance closes the
|
||||||
'how did retrieval choose these sources' gap."""
|
'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)
|
sig = inspect.signature(build_run_dag)
|
||||||
assert "retrieval_plan_hash" in sig.parameters, (
|
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():
|
def test_d4_retrieval_plan_hash_module_exists():
|
||||||
"""RetrievalPlan dataclass + retrieval_plan_hash function landed
|
"""RetrievalPlan dataclass + retrieval_plan_hash function landed
|
||||||
in `aborist.qa.retrieval_plan` per ticket #000001."""
|
in `arborist.qa.retrieval_plan` per ticket #000001."""
|
||||||
from aborist.qa.retrieval_plan import RetrievalPlan, retrieval_plan_hash
|
from arborist.qa.retrieval_plan import RetrievalPlan, retrieval_plan_hash
|
||||||
|
|
||||||
plan = RetrievalPlan(
|
plan = RetrievalPlan(
|
||||||
retrieval_keywords="orwell 1984",
|
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
|
"""Same inputs → same verdict, byte-for-byte. No randomness, no
|
||||||
time-dependent state, no hidden global. Regression here means
|
time-dependent state, no hidden global. Regression here means
|
||||||
the verifier started consulting some non-deterministic source."""
|
the verifier started consulting some non-deterministic source."""
|
||||||
from aborist.qa.evidence import build_evidence_map
|
from arborist.qa.evidence import build_evidence_map
|
||||||
from aborist.qa.verify import verify_claim_lattice
|
from arborist.qa.verify import verify_claim_lattice
|
||||||
|
|
||||||
chunks = [{
|
chunks = [{
|
||||||
"source_root": "f" * 64,
|
"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
|
"""Spot check: the per-check helper for citation overlap
|
||||||
(`_claim_textually_overlaps_evidence`) is a pure function — no
|
(`_claim_textually_overlaps_evidence`) is a pure function — no
|
||||||
network, no chat client, no global state."""
|
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)
|
sig = inspect.signature(_claim_textually_overlaps_evidence)
|
||||||
forbidden = {"chat_client", "client", "llm", "judge", "model"}
|
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.
|
"""warrant_check must exist as a deterministic lexical check.
|
||||||
A future replacement that imports an NLI model into the hard
|
A future replacement that imports an NLI model into the hard
|
||||||
path would violate D6's 'before NLI' constraint."""
|
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)
|
sig = inspect.signature(warrant_check)
|
||||||
forbidden = {"chat_client", "client", "llm", "judge", "model", "nli"}
|
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
|
class. This case is the deterministic anchor (year strings
|
||||||
are case-trivial; ALL-match semantics). Regression here means
|
are case-trivial; ALL-match semantics). Regression here means
|
||||||
the date-anchor extractor or check silently broke."""
|
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.
|
# Claim asserts "1985"; cited span has no 1985 anywhere.
|
||||||
ok, missing = warrant_check(
|
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
|
extracted anchor must appear in some cited span. This test
|
||||||
exercises a true failure where the claim's only proper-noun
|
exercises a true failure where the claim's only proper-noun
|
||||||
anchor is missing from every cited span."""
|
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(
|
ok, missing = warrant_check(
|
||||||
claim_text="Mr. Burns is the boss.",
|
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.
|
"""Per-shape warrant detectors landed via ticket #000003.
|
||||||
Entity-list / count / why-cause shapes now dispatch through
|
Entity-list / count / why-cause shapes now dispatch through
|
||||||
warrant_check alongside the original relation + date classes."""
|
warrant_check alongside the original relation + date classes."""
|
||||||
import aborist.qa.warrant as warrant_mod
|
import arborist.qa.warrant as warrant_mod
|
||||||
|
|
||||||
expected = (
|
expected = (
|
||||||
"_question_is_entity_list_shape",
|
"_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
|
(POINTER-LINKED / ANCHOR-WARRANTED / EVIDENCE-WARRANTED) replaces
|
||||||
the previous two-rung EVIDENCE-LINKED/EVIDENCE-LINKED-PARTIAL
|
the previous two-rung EVIDENCE-LINKED/EVIDENCE-LINKED-PARTIAL
|
||||||
surface as of #000005."""
|
surface as of #000005."""
|
||||||
from aborist.cli import _render_audit_label
|
from arborist.cli import _render_audit_label
|
||||||
|
|
||||||
# No violations + STRICT → top rung.
|
# No violations + STRICT → top rung.
|
||||||
assert "EVIDENCE-WARRANTED" in _render_audit_label("STRICT", "claim_lattice", [])
|
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
|
"""Quote / span / entity / paraphrase verify against pinned
|
||||||
spans, not synthesis. STRICT is honest there; renderer must
|
spans, not synthesis. STRICT is honest there; renderer must
|
||||||
NOT apply the ladder relabel."""
|
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"):
|
for method in ("quote", "span", "entity", "paraphrase"):
|
||||||
label = _render_audit_label("STRICT", method, [])
|
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
|
"""Schema column must keep the canonical 3-value enum so v9.8
|
||||||
cache_key invariants hold. Renderer-level relabel (above)
|
cache_key invariants hold. Renderer-level relabel (above)
|
||||||
doesn't touch this."""
|
doesn't touch this."""
|
||||||
from aborist.store import SCHEMA_SQL
|
from arborist.store import SCHEMA_SQL
|
||||||
|
|
||||||
match = re.search(
|
match = re.search(
|
||||||
r"audit_mode\s+TEXT[^,]+CHECK\s*\(\s*audit_mode\s+IN\s*\(([^)]+)\)\)",
|
r"audit_mode\s+TEXT[^,]+CHECK\s*\(\s*audit_mode\s+IN\s*\(([^)]+)\)\)",
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,13 @@ from __future__ import annotations
|
||||||
import json
|
import json
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
|
|
||||||
from aborist.distill import FirstSentenceDistiller
|
from arborist.distill import FirstSentenceDistiller
|
||||||
from aborist.distill.runner import distill_existing
|
from arborist.distill.runner import distill_existing
|
||||||
from aborist.document import Document
|
from arborist.document import Document
|
||||||
from aborist.ingest import ingest_source
|
from arborist.ingest import ingest_source
|
||||||
from aborist.merkle import proof_from_dict, verify_proof
|
from arborist.merkle import proof_from_dict, verify_proof
|
||||||
from aborist.source import Source
|
from arborist.source import Source
|
||||||
from aborist.store import connect
|
from arborist.store import connect
|
||||||
|
|
||||||
|
|
||||||
class FakeSource(Source):
|
class FakeSource(Source):
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,13 @@ from __future__ import annotations
|
||||||
import json
|
import json
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
|
|
||||||
from aborist.distill import FirstSentenceDistiller
|
from arborist.distill import FirstSentenceDistiller
|
||||||
from aborist.distill.runner import distill_existing
|
from arborist.distill.runner import distill_existing
|
||||||
from aborist.document import Document
|
from arborist.document import Document
|
||||||
from aborist.ingest import ingest_source
|
from arborist.ingest import ingest_source
|
||||||
from aborist.merkle import proof_from_dict, verify_proof
|
from arborist.merkle import proof_from_dict, verify_proof
|
||||||
from aborist.source import Source
|
from arborist.source import Source
|
||||||
from aborist.store import connect
|
from arborist.store import connect
|
||||||
|
|
||||||
|
|
||||||
class FakeSource(Source):
|
class FakeSource(Source):
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,13 @@ from __future__ import annotations
|
||||||
import json
|
import json
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
|
|
||||||
from aborist.distill import FirstSentenceDistiller
|
from arborist.distill import FirstSentenceDistiller
|
||||||
from aborist.distill.runner import distill_existing
|
from arborist.distill.runner import distill_existing
|
||||||
from aborist.document import Document
|
from arborist.document import Document
|
||||||
from aborist.evict import evict_to_cold, rehydrate
|
from arborist.evict import evict_to_cold, rehydrate
|
||||||
from aborist.ingest import ingest_source
|
from arborist.ingest import ingest_source
|
||||||
from aborist.source import Source
|
from arborist.source import Source
|
||||||
from aborist.store import connect
|
from arborist.store import connect
|
||||||
|
|
||||||
|
|
||||||
class FakeSource(Source):
|
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):
|
def test_rehydrate_non_rehydratable_source(tmp_path):
|
||||||
"""A source_type without a registered fetcher (and no override) is honest about it."""
|
"""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):
|
class WikiSource(Source):
|
||||||
source_type = "wikipedia_cur"
|
source_type = "wikipedia_cur"
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ indicators.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from aborist.qa.frame import (
|
from arborist.qa.frame import (
|
||||||
FrameDetection,
|
FrameDetection,
|
||||||
_body_indicates_reference_work,
|
_body_indicates_reference_work,
|
||||||
_source_is_reference_work,
|
_source_is_reference_work,
|
||||||
|
|
|
||||||
|
|
@ -13,15 +13,15 @@ from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aborist.ingest import ingest_source
|
from arborist.ingest import ingest_source
|
||||||
from aborist.sources.grok import (
|
from arborist.sources.grok import (
|
||||||
GrokExportSource,
|
GrokExportSource,
|
||||||
GrokMediaPostsSource,
|
GrokMediaPostsSource,
|
||||||
_extract_urls,
|
_extract_urls,
|
||||||
_normalize_ts,
|
_normalize_ts,
|
||||||
_scrub,
|
_scrub,
|
||||||
)
|
)
|
||||||
from aborist.store import connect
|
from arborist.store import connect
|
||||||
|
|
||||||
|
|
||||||
def _make_export(tmp_path: Path) -> Path:
|
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):
|
def test_grok_ingest_round_trip(tmp_path):
|
||||||
"""End-to-end: ingest a tiny export into a fresh DB, verify counts."""
|
"""End-to-end: ingest a tiny export into a fresh DB, verify counts."""
|
||||||
root = _make_export(tmp_path)
|
root = _make_export(tmp_path)
|
||||||
db_path = tmp_path / "aborist.db"
|
db_path = tmp_path / "arborist.db"
|
||||||
conn = connect(db_path)
|
conn = connect(db_path)
|
||||||
try:
|
try:
|
||||||
conv_stats = ingest_source(conn, GrokExportSource(path=root))
|
conv_stats = ingest_source(conn, GrokExportSource(path=root))
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import pytest
|
||||||
# Skip if optional extras are not installed.
|
# Skip if optional extras are not installed.
|
||||||
selectolax = pytest.importorskip("selectolax")
|
selectolax = pytest.importorskip("selectolax")
|
||||||
|
|
||||||
from aborist.sources.html_page import parse_html
|
from arborist.sources.html_page import parse_html
|
||||||
|
|
||||||
|
|
||||||
SAMPLE_HTML = """<!doctype html>
|
SAMPLE_HTML = """<!doctype html>
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,12 @@ from __future__ import annotations
|
||||||
|
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
|
|
||||||
from aborist.document import Document, Edge
|
from arborist.document import Document, Edge
|
||||||
from aborist.ingest import ingest_source, verify_random_sample
|
from arborist.ingest import ingest_source, verify_random_sample
|
||||||
from aborist.search import FTS5Backend
|
from arborist.search import FTS5Backend
|
||||||
from aborist.search.base import AuditMode
|
from arborist.search.base import AuditMode
|
||||||
from aborist.source import Source
|
from arborist.source import Source
|
||||||
from aborist.store import connect, stats
|
from arborist.store import connect, stats
|
||||||
|
|
||||||
|
|
||||||
class FakeSource(Source):
|
class FakeSource(Source):
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"""Sidecar diagnostic for unverified spans.
|
"""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
|
the same source chunks the verifier saw, and classifies each
|
||||||
unverified_quote into one of: verbatim_in_base, verbatim_in_raw_only,
|
unverified_quote into one of: verbatim_in_base, verbatim_in_raw_only,
|
||||||
trailing_artifact, paraphrase, partial_paraphrase, no_overlap.
|
trailing_artifact, paraphrase, partial_paraphrase, no_overlap.
|
||||||
|
|
@ -18,7 +18,7 @@ from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aborist.qa.inspect import (
|
from arborist.qa.inspect import (
|
||||||
_classify_span,
|
_classify_span,
|
||||||
_normalize,
|
_normalize,
|
||||||
diagnose_deflection,
|
diagnose_deflection,
|
||||||
|
|
@ -26,7 +26,7 @@ from aborist.qa.inspect import (
|
||||||
diagnose_title_relevance,
|
diagnose_title_relevance,
|
||||||
inspect_cache_key,
|
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."""
|
"""Insert a documents row + one hot chunk with the given text."""
|
||||||
conn = connect(shard)
|
conn = connect(shard)
|
||||||
try:
|
try:
|
||||||
from aborist.compress import pack_chunk
|
from arborist.compress import pack_chunk
|
||||||
|
|
||||||
with transaction(conn):
|
with transaction(conn):
|
||||||
conn.execute(
|
conn.execute(
|
||||||
|
|
@ -666,7 +666,7 @@ def test_metaphor_deflection_filters_common_ly_nouns():
|
||||||
"""Naive .endswith('ly') would pick up 'butterfly', 'family',
|
"""Naive .endswith('ly') would pick up 'butterfly', 'family',
|
||||||
'italy', 'july' etc. as adverbs. The block-list filters them so
|
'italy', 'july' etc. as adverbs. The block-list filters them so
|
||||||
they don't inflate cue count."""
|
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(
|
cues = _extract_metaphor_cues(
|
||||||
"the butterfly flew over italy in july with the family"
|
"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):
|
def test_register_metaphor_dictionary_unions_into_wordlist(tmp_path):
|
||||||
"""Custom wordlist registers, unions into the default, and the
|
"""Custom wordlist registers, unions into the default, and the
|
||||||
suffix tests pick up domain-specific stems."""
|
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.
|
# Reset cache so the test sees a clean slate.
|
||||||
saved_cache = m._english_wordlist_cache
|
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
|
m._extra_dict_paths = saved_extra
|
||||||
|
|
||||||
|
|
||||||
def test_aborist_metaphor_dicts_env_var_supplements(monkeypatch, tmp_path):
|
def test_arborist_metaphor_dicts_env_var_supplements(monkeypatch, tmp_path):
|
||||||
"""Setting ABORIST_METAPHOR_DICTS=path1:path2 unions both into
|
"""Setting ARBORIST_METAPHOR_DICTS=path1:path2 unions both into
|
||||||
the default wordlist on first lookup."""
|
the default wordlist on first lookup."""
|
||||||
import aborist.qa.inspect as m
|
import arborist.qa.inspect as m
|
||||||
|
|
||||||
saved_cache = m._english_wordlist_cache
|
saved_cache = m._english_wordlist_cache
|
||||||
m._english_wordlist_cache = None
|
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")
|
d1.write_text("widgetspeak\n")
|
||||||
d2 = tmp_path / "d2.txt"
|
d2 = tmp_path / "d2.txt"
|
||||||
d2.write_text("frizzlebop\n")
|
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()
|
wl = m._english_wordlist()
|
||||||
assert "widgetspeak" in wl
|
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):
|
def test_register_metaphor_dictionary_idempotent(tmp_path):
|
||||||
"""Registering the same path twice is a no-op (cache invalidates
|
"""Registering the same path twice is a no-op (cache invalidates
|
||||||
once, second call is a no-op since path already in list)."""
|
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_extra = list(m._extra_dict_paths)
|
||||||
saved_cache = m._english_wordlist_cache
|
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
|
- one message per subsequent line
|
||||||
- session_end system message on close
|
- session_end system message on close
|
||||||
- ``$schema: "unfirehose/1.0"`` on every record
|
- ``$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
|
from __future__ import annotations
|
||||||
|
|
@ -13,7 +13,7 @@ from __future__ import annotations
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from aborist.journal import (
|
from arborist.journal import (
|
||||||
HARNESS_NAME,
|
HARNESS_NAME,
|
||||||
UNFIREHOSE_SCHEMA,
|
UNFIREHOSE_SCHEMA,
|
||||||
SessionWriter,
|
SessionWriter,
|
||||||
|
|
@ -27,7 +27,7 @@ def _read_lines(path: Path) -> list[dict]:
|
||||||
|
|
||||||
|
|
||||||
def test_slugify_cwd_basic():
|
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():
|
def test_slugify_cwd_dots_become_hyphens():
|
||||||
|
|
@ -84,15 +84,15 @@ def test_close_writes_session_end(tmp_path):
|
||||||
assert last["durationMs"] >= 0
|
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"}
|
meta = {"audit_mode": "STRICT", "n_verified": 2, "cache_key": "abc123"}
|
||||||
with SessionWriter(first_prompt="q", root=tmp_path) as s:
|
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])
|
rows = _read_lines(list(tmp_path.glob("**/*.jsonl"))[0])
|
||||||
asst = next(r for r in rows if r["type"] == "message" and r["role"] == "assistant")
|
asst = next(r for r in rows if r["type"] == "message" and r["role"] == "assistant")
|
||||||
assert asst["aborist_meta"]["audit_mode"] == "STRICT"
|
assert asst["arborist_meta"]["audit_mode"] == "STRICT"
|
||||||
assert asst["aborist_meta"]["n_verified"] == 2
|
assert asst["arborist_meta"]["n_verified"] == 2
|
||||||
assert asst["aborist_meta"]["cache_key"] == "abc123"
|
assert asst["arborist_meta"]["cache_key"] == "abc123"
|
||||||
|
|
||||||
|
|
||||||
def test_session_id_used_as_filename(tmp_path):
|
def test_session_id_used_as_filename(tmp_path):
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"""Cache-key dimension hashes — pure functions, deterministic.
|
"""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.);
|
Most behavior is covered indirectly by `test_qa.py` (cache hits, etc.);
|
||||||
this file pins the hash-input canonicalization rules so a future tweak
|
this file pins the hash-input canonicalization rules so a future tweak
|
||||||
to question normalization can't silently change the equivalence class
|
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 __future__ import annotations
|
||||||
|
|
||||||
from aborist.qa.keys import (
|
from arborist.qa.keys import (
|
||||||
cache_key,
|
cache_key,
|
||||||
conversation_hash,
|
conversation_hash,
|
||||||
governance_policy_hash,
|
governance_policy_hash,
|
||||||
|
|
@ -51,7 +51,7 @@ def test_question_hash_lowercases():
|
||||||
def test_question_hash_collapses_internal_spaces():
|
def test_question_hash_collapses_internal_spaces():
|
||||||
"""canonicalize() collapses runs of spaces to a single space. Tabs/
|
"""canonicalize() collapses runs of spaces to a single space. Tabs/
|
||||||
newlines map to newline (a separate equivalence class — see
|
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."""
|
NOT in the same bucket."""
|
||||||
assert question_hash("who is X") == question_hash("who is X")
|
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
|
governance_policy_hash is to surface verifier-rule changes
|
||||||
independently from prompt / sampling-knob changes.
|
independently from prompt / sampling-knob changes.
|
||||||
"""
|
"""
|
||||||
from aborist.qa.keys import verifier_policy_hash
|
from arborist.qa.keys import verifier_policy_hash
|
||||||
|
|
||||||
base = {
|
base = {
|
||||||
"answer_mode": "claim_lattice_pointer",
|
"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
|
of these means the verifier rules differ and a new cache record
|
||||||
is required.
|
is required.
|
||||||
"""
|
"""
|
||||||
from aborist.qa.keys import verifier_policy_hash
|
from arborist.qa.keys import verifier_policy_hash
|
||||||
|
|
||||||
base = {
|
base = {
|
||||||
"answer_mode": "claim_lattice_pointer",
|
"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():
|
def test_verifier_policy_hash_empty_policy_is_stable():
|
||||||
"""Empty policy → constant hash. Stable across runs."""
|
"""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({})
|
assert verifier_policy_hash({}) == verifier_policy_hash({})
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,12 @@ from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aborist.document import Document
|
from arborist.document import Document
|
||||||
from aborist.ingest import ingest_source
|
from arborist.ingest import ingest_source
|
||||||
from aborist.qa.client import StubClient
|
from arborist.qa.client import StubClient
|
||||||
from aborist.qa.runner import DEFAULT_POLICY, ask
|
from arborist.qa.runner import DEFAULT_POLICY, ask
|
||||||
from aborist.store import connect
|
from arborist.store import connect
|
||||||
from aborist.wikitext import BASE_VERSION
|
from arborist.wikitext import BASE_VERSION
|
||||||
|
|
||||||
|
|
||||||
WIKITEXT_DOC = (
|
WIKITEXT_DOC = (
|
||||||
|
|
@ -156,7 +156,7 @@ def test_base_version_changes_governance_hash(tmp_path):
|
||||||
answers (under raw-wikitext policy) from satisfying lookups under
|
answers (under raw-wikitext policy) from satisfying lookups under
|
||||||
the new (stripped) policy on the same question/source — they remain
|
the new (stripped) policy on the same question/source — they remain
|
||||||
distinct cache_keys."""
|
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 = dict(DEFAULT_POLICY)
|
||||||
p_with["base_version"] = "wikitext-base-v1"
|
p_with["base_version"] = "wikitext-base-v1"
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aborist.merkle import (
|
from arborist.merkle import (
|
||||||
HASH_LEN,
|
HASH_LEN,
|
||||||
MerkleTree,
|
MerkleTree,
|
||||||
ZERO_HASH,
|
ZERO_HASH,
|
||||||
|
|
@ -117,7 +117,7 @@ def test_tampered_sibling_fails_verification():
|
||||||
leaves = [_leaf(f"chunk-{i}") for i in range(4)]
|
leaves = [_leaf(f"chunk-{i}") for i in range(4)]
|
||||||
tree = MerkleTree.build(leaves)
|
tree = MerkleTree.build(leaves)
|
||||||
proof = tree.proof(0)
|
proof = tree.proof(0)
|
||||||
from aborist.merkle import ProofNode
|
from arborist.merkle import ProofNode
|
||||||
|
|
||||||
bad_siblings = list(proof.siblings)
|
bad_siblings = list(proof.siblings)
|
||||||
s0 = bad_siblings[0]
|
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)]
|
leaves = [_leaf(f"chunk-{i}") for i in range(4)]
|
||||||
tree = MerkleTree.build(leaves)
|
tree = MerkleTree.build(leaves)
|
||||||
proof = tree.proof(1)
|
proof = tree.proof(1)
|
||||||
from aborist.merkle import MerkleProof, ProofNode
|
from arborist.merkle import MerkleProof, ProofNode
|
||||||
|
|
||||||
flipped = MerkleProof(
|
flipped = MerkleProof(
|
||||||
leaf=proof.leaf,
|
leaf=proof.leaf,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"""Tests for the mesh layer: crypto primitives, state machine, eviction.
|
"""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.
|
DB per test via tmp_path so cross-test contamination is impossible.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -10,7 +10,7 @@ import os
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aborist.mesh import (
|
from arborist.mesh import (
|
||||||
aead_decrypt,
|
aead_decrypt,
|
||||||
aead_encrypt,
|
aead_encrypt,
|
||||||
current_epoch,
|
current_epoch,
|
||||||
|
|
@ -24,13 +24,13 @@ from aborist.mesh import (
|
||||||
sign,
|
sign,
|
||||||
verify,
|
verify,
|
||||||
)
|
)
|
||||||
from aborist.mesh.members import add_member, kick_member, scheduled_rotate
|
from arborist.mesh.members import add_member, kick_member, scheduled_rotate
|
||||||
from aborist.mesh.state import (
|
from arborist.mesh.state import (
|
||||||
MeshRosterEntry,
|
MeshRosterEntry,
|
||||||
roster_at,
|
roster_at,
|
||||||
unwrap_secret_for_self,
|
unwrap_secret_for_self,
|
||||||
)
|
)
|
||||||
from aborist.store import connect
|
from arborist.store import connect
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,10 @@ from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aborist.mesh import init_identity
|
from arborist.mesh import init_identity
|
||||||
from aborist.mesh.members import add_member
|
from arborist.mesh.members import add_member
|
||||||
from aborist.mesh.state import recover_epoch_secret
|
from arborist.mesh.state import recover_epoch_secret
|
||||||
from aborist.mesh.wire import (
|
from arborist.mesh.wire import (
|
||||||
MeshWireServer,
|
MeshWireServer,
|
||||||
TYPE_ANNOUNCE_FALSIFICATION,
|
TYPE_ANNOUNCE_FALSIFICATION,
|
||||||
TYPE_ANNOUNCE_ROOT,
|
TYPE_ANNOUNCE_ROOT,
|
||||||
|
|
@ -32,7 +32,7 @@ from aborist.mesh.wire import (
|
||||||
decrypt_envelope_body,
|
decrypt_envelope_body,
|
||||||
encrypt_body_for_envelope,
|
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]:
|
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 json as _json
|
||||||
import sqlite3 as _sqlite3
|
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"
|
alice_db = tmp_path / "alice.db"
|
||||||
bob_db = tmp_path / "bob.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
|
"""Alice sends one cleartext ANNOUNCE_ROOT, then one encrypted
|
||||||
ANNOUNCE_FALSIFICATION. Bob accepts both; audit chain logs the
|
ANNOUNCE_FALSIFICATION. Bob accepts both; audit chain logs the
|
||||||
`encrypted` flag accurately on each."""
|
`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)
|
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)
|
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
|
identically to the v1 wire format — no `encrypted_body` field on
|
||||||
the canonical bytes, no signature change, no audit-body change.
|
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)
|
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)
|
bob_srv = MeshWireServer(bob_db, host="127.0.0.1", port=0)
|
||||||
|
|
|
||||||
|
|
@ -21,15 +21,15 @@ from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aborist.mesh import init_identity
|
from arborist.mesh import init_identity
|
||||||
from aborist.mesh.crypto import sign as _sign
|
from arborist.mesh.crypto import sign as _sign
|
||||||
from aborist.mesh.members import add_member
|
from arborist.mesh.members import add_member
|
||||||
from aborist.mesh.wire import (
|
from arborist.mesh.wire import (
|
||||||
MeshWireServer,
|
MeshWireServer,
|
||||||
TYPE_ANNOUNCE_ROOT,
|
TYPE_ANNOUNCE_ROOT,
|
||||||
WireEnvelope,
|
WireEnvelope,
|
||||||
)
|
)
|
||||||
from aborist.store import connect
|
from arborist.store import connect
|
||||||
|
|
||||||
|
|
||||||
def _bootstrap_three_peer_db(tmp_path: Path):
|
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
|
roster at ``current_epoch`` contains bob and carol so signature
|
||||||
verification finds their sign_pubs when receiving from either.
|
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"
|
alice_db = tmp_path / "alice.db"
|
||||||
bob_db = tmp_path / "bob.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
|
Wire-layer protocol behavior is covered in test_mesh_wire.py and
|
||||||
test_mesh_wire_e2e.py — these tests just exercise the argparse plumbing,
|
test_mesh_wire_e2e.py — these tests just exercise the argparse plumbing,
|
||||||
|
|
@ -11,13 +11,13 @@ from __future__ import annotations
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from aborist.cli import build_parser
|
from arborist.cli import build_parser
|
||||||
from aborist.document import Document
|
from arborist.document import Document
|
||||||
from aborist.ingest import ingest_source
|
from arborist.ingest import ingest_source
|
||||||
from aborist.mesh import init_identity, set_enabled
|
from arborist.mesh import init_identity, set_enabled
|
||||||
from aborist.mesh.members import add_member
|
from arborist.mesh.members import add_member
|
||||||
from aborist.mesh.wire import MeshWireServer
|
from arborist.mesh.wire import MeshWireServer
|
||||||
from aborist.store import connect
|
from arborist.store import connect
|
||||||
|
|
||||||
|
|
||||||
class _OneDocSource:
|
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
|
the falsification. Alice's audit chain gains one mesh_received event
|
||||||
whose body carries an ANNOUNCE_FALSIFICATION envelope."""
|
whose body carries an ANNOUNCE_FALSIFICATION envelope."""
|
||||||
import time as _time
|
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)
|
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
|
"""--no-falsifications skips the falsification broadcast. Bob's
|
||||||
falsification is NOT announced. Alice gets only the root broadcasts."""
|
falsification is NOT announced. Alice gets only the root broadcasts."""
|
||||||
import time as _time
|
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)
|
alice_db, bob_db, _alice, _bob = _bootstrap_two_peers(tmp_path)
|
||||||
b_conn = connect(bob_db)
|
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
|
A document ingested 30 seconds ago must NOT broadcast under the
|
||||||
default 1-hour kindergarten window."""
|
default 1-hour kindergarten window."""
|
||||||
import time as _time
|
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)
|
alice_db, bob_db, _alice, _bob = _bootstrap_two_peers(tmp_path)
|
||||||
_ingest(bob_db, uri="https://example.com/old", content="Old prose.", title="Old")
|
_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
|
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
|
verification is covered in test_mesh_wire_e2e.py — these tests cover the
|
||||||
|
|
@ -11,13 +11,13 @@ from __future__ import annotations
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from aborist.cli import build_parser
|
from arborist.cli import build_parser
|
||||||
from aborist.document import Document
|
from arborist.document import Document
|
||||||
from aborist.ingest import ingest_source
|
from arborist.ingest import ingest_source
|
||||||
from aborist.mesh import init_identity, set_enabled
|
from arborist.mesh import init_identity, set_enabled
|
||||||
from aborist.mesh.members import add_member
|
from arborist.mesh.members import add_member
|
||||||
from aborist.mesh.wire import MeshWireServer
|
from arborist.mesh.wire import MeshWireServer
|
||||||
from aborist.store import connect
|
from arborist.store import connect
|
||||||
|
|
||||||
|
|
||||||
class _OneDocSource:
|
class _OneDocSource:
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,13 @@ from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aborist.mesh import init_identity
|
from arborist.mesh import init_identity
|
||||||
from aborist.mesh.members import add_member
|
from arborist.mesh.members import add_member
|
||||||
from aborist.mesh.crypto import (
|
from arborist.mesh.crypto import (
|
||||||
generate_dh_keypair,
|
generate_dh_keypair,
|
||||||
generate_signing_keypair,
|
generate_signing_keypair,
|
||||||
)
|
)
|
||||||
from aborist.mesh.wire import (
|
from arborist.mesh.wire import (
|
||||||
ALL_TYPES,
|
ALL_TYPES,
|
||||||
PATH_ANNOUNCE,
|
PATH_ANNOUNCE,
|
||||||
PATH_INFO,
|
PATH_INFO,
|
||||||
|
|
@ -30,7 +30,7 @@ from aborist.mesh.wire import (
|
||||||
WireEnvelope,
|
WireEnvelope,
|
||||||
verify_envelope_sig,
|
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]:
|
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"},
|
body={"document_root": "11" * 32, "source_uri": "https://x"},
|
||||||
)
|
)
|
||||||
sig_payload = env.canonical_bytes()
|
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)
|
sig = _sign(alice_id.sign_priv, sig_payload)
|
||||||
|
|
||||||
a_conn = connect(alice_db)
|
a_conn = connect(alice_db)
|
||||||
|
|
@ -225,7 +225,7 @@ def test_verify_envelope_sig_rejects_tampered_body(tmp_path):
|
||||||
ts=100,
|
ts=100,
|
||||||
body={"document_root": "aa" * 32, "source_uri": "https://x"},
|
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())
|
sig = _sign(alice_id.sign_priv, env_orig.canonical_bytes())
|
||||||
|
|
||||||
env_tampered = WireEnvelope(
|
env_tampered = WireEnvelope(
|
||||||
|
|
@ -252,7 +252,7 @@ def test_verify_envelope_sig_rejects_unknown_sender(tmp_path):
|
||||||
ts=0,
|
ts=0,
|
||||||
body={"document_root": "00" * 32, "source_uri": "https://x"},
|
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())
|
sig = _sign(sign_priv, env.canonical_bytes())
|
||||||
a_conn = connect(alice_db)
|
a_conn = connect(alice_db)
|
||||||
try:
|
try:
|
||||||
|
|
@ -272,7 +272,7 @@ def test_verify_envelope_sig_rejects_wrong_key_for_known_member(tmp_path):
|
||||||
ts=0,
|
ts=0,
|
||||||
body={"document_root": "00" * 32, "source_uri": "https://x"},
|
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())
|
sig = _sign(rogue_priv, env.canonical_bytes())
|
||||||
a_conn = connect(alice_db)
|
a_conn = connect(alice_db)
|
||||||
try:
|
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):
|
def test_handle_announce_writes_audit_event(tmp_path):
|
||||||
"""Bob sends ANNOUNCE_ROOT to Alice's server; Alice's chain gains an event."""
|
"""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)
|
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):
|
def test_handle_announce_rejects_bad_sig(tmp_path):
|
||||||
"""Sig from wrong key → 401, no audit event."""
|
"""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)
|
alice_db, _bob_db, _alice_id, _bob_id = _bootstrap_two_peer_db(tmp_path)
|
||||||
rogue_priv, _ = generate_signing_keypair()
|
rogue_priv, _ = generate_signing_keypair()
|
||||||
|
|
@ -352,7 +352,7 @@ def test_handle_announce_rejects_bad_sig(tmp_path):
|
||||||
ts=0,
|
ts=0,
|
||||||
body={"document_root": "00" * 32, "source_uri": "x"},
|
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())
|
sig = _sign(rogue_priv, env.canonical_bytes())
|
||||||
import base64 as _b64
|
import base64 as _b64
|
||||||
signed = {"envelope": env.__dict__, "sig_b64": _b64.b64encode(sig).decode()}
|
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):
|
def test_handle_announce_rejects_request_body_type(tmp_path):
|
||||||
"""The /announce endpoint refuses REQUEST_BODY / DELIVER_BODY."""
|
"""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)
|
alice_db, _bob_db, _alice_id, bob_id = _bootstrap_two_peer_db(tmp_path)
|
||||||
env = WireEnvelope(
|
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):
|
def test_handle_request_404_on_unknown_root(tmp_path):
|
||||||
"""Asking for a doc the responder doesn't have returns 404 cleanly."""
|
"""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)
|
alice_db, _bob_db, _alice_id, bob_id = _bootstrap_two_peer_db(tmp_path)
|
||||||
env = WireEnvelope(
|
env = WireEnvelope(
|
||||||
|
|
@ -427,7 +427,7 @@ def test_handle_request_404_on_unknown_root(tmp_path):
|
||||||
|
|
||||||
def test_announce_falsification_round_trips(tmp_path):
|
def test_announce_falsification_round_trips(tmp_path):
|
||||||
"""Falsifications propagate as ANNOUNCE_FALSIFICATION."""
|
"""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)
|
alice_db, _bob_db, _alice_id, bob_id = _bootstrap_two_peer_db(tmp_path)
|
||||||
env = WireEnvelope(
|
env = WireEnvelope(
|
||||||
|
|
|
||||||
|
|
@ -17,16 +17,16 @@ from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aborist.document import Document
|
from arborist.document import Document
|
||||||
from aborist.ingest import ingest_source
|
from arborist.ingest import ingest_source
|
||||||
from aborist.mesh import init_identity
|
from arborist.mesh import init_identity
|
||||||
from aborist.mesh.members import add_member
|
from arborist.mesh.members import add_member
|
||||||
from aborist.mesh.wire import (
|
from arborist.mesh.wire import (
|
||||||
MeshWireClient,
|
MeshWireClient,
|
||||||
MeshWireServer,
|
MeshWireServer,
|
||||||
TYPE_ANNOUNCE_ROOT,
|
TYPE_ANNOUNCE_ROOT,
|
||||||
)
|
)
|
||||||
from aborist.store import connect
|
from arborist.store import connect
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|
@ -180,7 +180,7 @@ def test_request_body_rejects_tampered_response(two_peers, monkeypatch):
|
||||||
title="X",
|
title="X",
|
||||||
)
|
)
|
||||||
with MeshWireClient(two_peers["bob"]["db"], two_peers["alice"]["srv"].url) as c:
|
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)
|
monkeypatch.setattr(_wire, "_merkle_root_matches", lambda *a, **k: False)
|
||||||
with pytest.raises(ValueError, match="Merkle-derive"):
|
with pytest.raises(ValueError, match="Merkle-derive"):
|
||||||
c.request_body(root=document_root)
|
c.request_body(root=document_root)
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aborist.qa.metacognition import (
|
from arborist.qa.metacognition import (
|
||||||
PREFLIGHT_VERSION,
|
PREFLIGHT_VERSION,
|
||||||
QuestionState,
|
QuestionState,
|
||||||
detect_contradiction,
|
detect_contradiction,
|
||||||
|
|
@ -305,12 +305,12 @@ def test_question_state_is_serializable():
|
||||||
def test_metacognition_field_is_in_verifier_policy_fields(field):
|
def test_metacognition_field_is_in_verifier_policy_fields(field):
|
||||||
"""All six policy fields must be in _VERIFIER_POLICY_FIELDS so
|
"""All six policy fields must be in _VERIFIER_POLICY_FIELDS so
|
||||||
flipping any of them invalidates prior cache records on lookup."""
|
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
|
assert field in _VERIFIER_POLICY_FIELDS
|
||||||
|
|
||||||
|
|
||||||
def test_governance_hash_changes_when_metacognition_enabled_flips():
|
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 = dict.fromkeys(_VERIFIER_POLICY_FIELDS, "default")
|
||||||
base["metacognition_enabled"] = True
|
base["metacognition_enabled"] = True
|
||||||
h_on = verifier_policy_hash(base)
|
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():
|
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 = dict.fromkeys(_VERIFIER_POLICY_FIELDS, "default")
|
||||||
base["metacognition_block_on_contradiction"] = False
|
base["metacognition_block_on_contradiction"] = False
|
||||||
h_off = verifier_policy_hash(base)
|
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():
|
def test_default_policy_has_metacognition_enabled():
|
||||||
"""Master switch default-on per ticket #000010 §7.3 — detectors
|
"""Master switch default-on per ticket #000010 §7.3 — detectors
|
||||||
are pure-on-question so the cost is negligible."""
|
are pure-on-question so the cost is negligible."""
|
||||||
from aborist.qa.runner import DEFAULT_POLICY as RUNNER_POLICY
|
from arborist.qa.runner import DEFAULT_POLICY as RUNNER_POLICY
|
||||||
from aborist.qa.query import DEFAULT_QUERY_POLICY
|
from arborist.qa.query import DEFAULT_QUERY_POLICY
|
||||||
assert RUNNER_POLICY["metacognition_enabled"] is True
|
assert RUNNER_POLICY["metacognition_enabled"] is True
|
||||||
assert DEFAULT_QUERY_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():
|
def test_default_policy_block_on_contradiction_off():
|
||||||
"""Default to label-only on contradictions. False-positive risk
|
"""Default to label-only on contradictions. False-positive risk
|
||||||
not yet bench-validated; opt-in via --block-on-contradiction."""
|
not yet bench-validated; opt-in via --block-on-contradiction."""
|
||||||
from aborist.qa.runner import DEFAULT_POLICY as RUNNER_POLICY
|
from arborist.qa.runner import DEFAULT_POLICY as RUNNER_POLICY
|
||||||
from aborist.qa.query import DEFAULT_QUERY_POLICY
|
from arborist.qa.query import DEFAULT_QUERY_POLICY
|
||||||
assert RUNNER_POLICY["metacognition_block_on_contradiction"] is False
|
assert RUNNER_POLICY["metacognition_block_on_contradiction"] is False
|
||||||
assert DEFAULT_QUERY_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():
|
def test_tail_renders_false_premise():
|
||||||
from aborist.cli import _render_warrant_tail
|
from arborist.cli import _render_warrant_tail
|
||||||
state = preflight_question(
|
state = preflight_question(
|
||||||
"When did Mr. Burns become Homer's biological father?"
|
"When did Mr. Burns become Homer's biological father?"
|
||||||
)
|
)
|
||||||
|
|
@ -369,28 +369,28 @@ def test_tail_renders_false_premise():
|
||||||
|
|
||||||
|
|
||||||
def test_tail_renders_contradictory():
|
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?")
|
state = preflight_question("Which unmarried spouse is Homer married to?")
|
||||||
tail = _render_warrant_tail(_result_with_question_state(state))
|
tail = _render_warrant_tail(_result_with_question_state(state))
|
||||||
assert "contradictory" in tail
|
assert "contradictory" in tail
|
||||||
|
|
||||||
|
|
||||||
def test_tail_renders_stale_risk():
|
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?")
|
state = preflight_question("Who is the current CEO of OpenAI?")
|
||||||
tail = _render_warrant_tail(_result_with_question_state(state))
|
tail = _render_warrant_tail(_result_with_question_state(state))
|
||||||
assert "stale risk" in tail
|
assert "stale risk" in tail
|
||||||
|
|
||||||
|
|
||||||
def test_tail_renders_out_of_corpus():
|
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?")
|
state = preflight_question("What does my uploaded contract say?")
|
||||||
tail = _render_warrant_tail(_result_with_question_state(state))
|
tail = _render_warrant_tail(_result_with_question_state(state))
|
||||||
assert "out of corpus" in tail
|
assert "out of corpus" in tail
|
||||||
|
|
||||||
|
|
||||||
def test_tail_omits_metacog_when_well_formed():
|
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?")
|
state = preflight_question("what is the capital of france?")
|
||||||
tail = _render_warrant_tail(_result_with_question_state(state))
|
tail = _render_warrant_tail(_result_with_question_state(state))
|
||||||
# well_formed should NOT produce any metacog tail tokens.
|
# 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():
|
def test_tail_combines_metacog_with_existing_kinds():
|
||||||
"""Multiple tails compose: a TITLE_MISMATCH from the verifier
|
"""Multiple tails compose: a TITLE_MISMATCH from the verifier
|
||||||
plus a stale_risk from the preflight should both surface."""
|
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?")
|
state = preflight_question("Who is the current CEO of OpenAI?")
|
||||||
result = _result_with_question_state(state)
|
result = _result_with_question_state(state)
|
||||||
result["violations"] = [{"kind": "TITLE_MISMATCH"}]
|
result["violations"] = [{"kind": "TITLE_MISMATCH"}]
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import sqlite3
|
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
|
# 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
|
4. anti-recursion: records whose answer cites a self-reference URI
|
||||||
are excluded (first-generation only)
|
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`.
|
URIs classify as `self_reference_source`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -20,12 +20,12 @@ import time
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aborist.qa.query import _classify_source_role
|
from arborist.qa.query import _classify_source_role
|
||||||
from aborist.sources.providence import (
|
from arborist.sources.providence import (
|
||||||
PROVIDENCE_URI_PREFIX,
|
PROVIDENCE_URI_PREFIX,
|
||||||
ProvidenceSource,
|
ProvidenceSource,
|
||||||
)
|
)
|
||||||
from aborist.store import connect
|
from arborist.store import connect
|
||||||
|
|
||||||
|
|
||||||
def _seed(conn: sqlite3.Connection, **fields) -> str:
|
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):
|
def test_anti_recursion_excludes_self_referencing_records(tmp_path):
|
||||||
"""A STRICT record whose own answer text contains
|
"""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
|
Prevents echo-chamber chains where a wrong-but-STRICT record
|
||||||
keeps getting recompiled into deeper claims."""
|
keeps getting recompiled into deeper claims."""
|
||||||
db = tmp_path / "p.db"
|
db = tmp_path / "p.db"
|
||||||
|
|
|
||||||
|
|
@ -9,20 +9,20 @@ from __future__ import annotations
|
||||||
import hashlib
|
import hashlib
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
|
|
||||||
from aborist.document import Document
|
from arborist.document import Document
|
||||||
from aborist.ingest import ingest_source
|
from arborist.ingest import ingest_source
|
||||||
from aborist.merkle import proof_from_dict, verify_proof
|
from arborist.merkle import proof_from_dict, verify_proof
|
||||||
from aborist.qa import ask
|
from arborist.qa import ask
|
||||||
from aborist.qa.client import StubClient
|
from arborist.qa.client import StubClient
|
||||||
from aborist.qa.keys import (
|
from arborist.qa.keys import (
|
||||||
cache_key,
|
cache_key,
|
||||||
conversation_hash,
|
conversation_hash,
|
||||||
governance_policy_hash,
|
governance_policy_hash,
|
||||||
model_profile_hash,
|
model_profile_hash,
|
||||||
question_hash,
|
question_hash,
|
||||||
)
|
)
|
||||||
from aborist.source import Source
|
from arborist.source import Source
|
||||||
from aborist.store import connect
|
from arborist.store import connect
|
||||||
|
|
||||||
|
|
||||||
class FakeSource(Source):
|
class FakeSource(Source):
|
||||||
|
|
@ -195,7 +195,7 @@ def test_unknown_document(tmp_path):
|
||||||
|
|
||||||
def test_cold_source_refuses(tmp_path):
|
def test_cold_source_refuses(tmp_path):
|
||||||
"""Source must be hot — answer derived from evicted content can't be proved."""
|
"""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"
|
db = tmp_path / "cold.db"
|
||||||
conn = connect(db)
|
conn = connect(db)
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
These tests are deliberately gated — they hit
|
These tests are deliberately gated — they hit
|
||||||
``hermes.ai.unturf.com/v1`` and require a populated shard set under
|
``hermes.ai.unturf.com/v1`` and require a populated shard set under
|
||||||
``~/.aborist/shards`` (default) or the path passed via
|
``~/.arborist/shards`` (default) or the path passed via
|
||||||
``ABORIST_LIVE_SHARDS_DIR``. Default `make test` does NOT run these;
|
``ARBORIST_LIVE_SHARDS_DIR``. Default `make test` does NOT run these;
|
||||||
opt in via ``ABORIST_LIVE_TESTS=1`` (the ``make test-live`` target
|
opt in via ``ARBORIST_LIVE_TESTS=1`` (the ``make test-live`` target
|
||||||
sets it).
|
sets it).
|
||||||
|
|
||||||
Why this exists:
|
Why this exists:
|
||||||
|
|
@ -44,24 +44,24 @@ from pathlib import Path
|
||||||
import pytest
|
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(
|
_SHARDS_DIR = Path(
|
||||||
os.environ.get("ABORIST_LIVE_SHARDS_DIR")
|
os.environ.get("ARBORIST_LIVE_SHARDS_DIR")
|
||||||
or (Path.home() / ".aborist" / "shards")
|
or (Path.home() / ".arborist" / "shards")
|
||||||
)
|
)
|
||||||
_QA_DB = _SHARDS_DIR / "qa.db"
|
_QA_DB = _SHARDS_DIR / "qa.db"
|
||||||
_ENDPOINT = os.environ.get(
|
_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(
|
_MODEL = os.environ.get(
|
||||||
"ABORIST_LLM_MODEL",
|
"ARBORIST_LLM_MODEL",
|
||||||
"adamo1139/Hermes-3-Llama-3.1-8B-FP8-Dynamic",
|
"adamo1139/Hermes-3-Llama-3.1-8B-FP8-Dynamic",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.skipif(
|
pytestmark = pytest.mark.skipif(
|
||||||
not _LIVE_OPT_IN,
|
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",
|
"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
|
# Lazy import so collecting the file doesn't pull qa modules in
|
||||||
# default-skip mode.
|
# default-skip mode.
|
||||||
from aborist.qa.client import OpenAICompatibleClient
|
from arborist.qa.client import OpenAICompatibleClient
|
||||||
from aborist.qa.query import DEFAULT_QUERY_POLICY, query
|
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)
|
client = OpenAICompatibleClient(base_url=_ENDPOINT, api_key=api_key)
|
||||||
policy = dict(DEFAULT_QUERY_POLICY)
|
policy = dict(DEFAULT_QUERY_POLICY)
|
||||||
policy["answer_mode"] = mode
|
policy["answer_mode"] = mode
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
"""Quantifier cap table + governance hash + dry-run discipline.
|
"""Quantifier cap table + governance hash + dry-run discipline.
|
||||||
|
|
||||||
Ticket #000008 Phase 2: per-model cap profiles
|
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:
|
gate cap behavior:
|
||||||
|
|
||||||
- ``quantifier_guard_enabled`` — master kill
|
- ``quantifier_guard_enabled`` — master kill
|
||||||
|
|
@ -23,8 +23,8 @@ from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aborist.qa.keys import _VERIFIER_POLICY_FIELDS, verifier_policy_hash
|
from arborist.qa.keys import _VERIFIER_POLICY_FIELDS, verifier_policy_hash
|
||||||
from aborist.qa.model_profiles import (
|
from arborist.qa.model_profiles import (
|
||||||
EXPLICIT_COUNT,
|
EXPLICIT_COUNT,
|
||||||
PROFILES,
|
PROFILES,
|
||||||
cap_for_intensity,
|
cap_for_intensity,
|
||||||
|
|
@ -230,8 +230,8 @@ def test_default_reminder_enabled_for_lattice_modes():
|
||||||
eliminates FORMAT_COLLAPSED, reduces NO_EVIDENCE_POINTER 33%,
|
eliminates FORMAT_COLLAPSED, reduces NO_EVIDENCE_POINTER 33%,
|
||||||
boosts mean ratio +17pp pointer / +21pp JSON, rescues JSON
|
boosts mean ratio +17pp pointer / +21pp JSON, rescues JSON
|
||||||
UNGROUNDED 7→1."""
|
UNGROUNDED 7→1."""
|
||||||
from aborist.qa.runner import DEFAULT_POLICY as RUNNER_POLICY
|
from arborist.qa.runner import DEFAULT_POLICY as RUNNER_POLICY
|
||||||
from aborist.qa.query import DEFAULT_QUERY_POLICY
|
from arborist.qa.query import DEFAULT_QUERY_POLICY
|
||||||
assert RUNNER_POLICY["quantifier_reminder_enabled"] is True
|
assert RUNNER_POLICY["quantifier_reminder_enabled"] is True
|
||||||
assert DEFAULT_QUERY_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.
|
TOO_MANY_CLAIMS 20× without moving the 0/45 STRICT floor.
|
||||||
Default the apply-caps allowlist to JSON-only so flipping
|
Default the apply-caps allowlist to JSON-only so flipping
|
||||||
apply_caps=True doesn't add wasted cap-noise on pointer mode."""
|
apply_caps=True doesn't add wasted cap-noise on pointer mode."""
|
||||||
from aborist.qa.runner import DEFAULT_POLICY as RUNNER_POLICY
|
from arborist.qa.runner import DEFAULT_POLICY as RUNNER_POLICY
|
||||||
from aborist.qa.query import DEFAULT_QUERY_POLICY
|
from arborist.qa.query import DEFAULT_QUERY_POLICY
|
||||||
assert RUNNER_POLICY["quantifier_apply_caps_modes"] == ["claim_lattice"]
|
assert RUNNER_POLICY["quantifier_apply_caps_modes"] == ["claim_lattice"]
|
||||||
assert DEFAULT_QUERY_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)
|
even with reminder default-on. Dry-run discipline (§10.11.3)
|
||||||
survives the §12 bench cycle — operators flip apply_caps via
|
survives the §12 bench cycle — operators flip apply_caps via
|
||||||
--apply-quantifier-caps after their own bench review."""
|
--apply-quantifier-caps after their own bench review."""
|
||||||
from aborist.qa.runner import DEFAULT_POLICY as RUNNER_POLICY
|
from arborist.qa.runner import DEFAULT_POLICY as RUNNER_POLICY
|
||||||
from aborist.qa.query import DEFAULT_QUERY_POLICY
|
from arborist.qa.query import DEFAULT_QUERY_POLICY
|
||||||
assert RUNNER_POLICY["quantifier_guard_apply_caps"] is False
|
assert RUNNER_POLICY["quantifier_guard_apply_caps"] is False
|
||||||
assert DEFAULT_QUERY_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
|
import pytest
|
||||||
|
|
||||||
from aborist.qa.quantifier import (
|
from arborist.qa.quantifier import (
|
||||||
CLASSIFIER_VERSION,
|
CLASSIFIER_VERSION,
|
||||||
classify_question_quantifier,
|
classify_question_quantifier,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,13 @@ from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aborist.cli import (
|
from arborist.cli import (
|
||||||
_SOFT_DEMOTE_VIOLATION_KINDS,
|
_SOFT_DEMOTE_VIOLATION_KINDS,
|
||||||
_ladder_rung_for_lattice,
|
_ladder_rung_for_lattice,
|
||||||
_render_query_human,
|
_render_query_human,
|
||||||
_render_warrant_tail,
|
_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
|
# ---------------------------------------------------------------- soft-demote registration
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@ from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aborist.qa.keys import _VERIFIER_POLICY_FIELDS, verifier_policy_hash
|
from arborist.qa.keys import _VERIFIER_POLICY_FIELDS, verifier_policy_hash
|
||||||
from aborist.qa.quantifier_reminder import broad_quantifier_reminder
|
from arborist.qa.quantifier_reminder import broad_quantifier_reminder
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------- gating
|
# ---------------------------------------------------------------- gating
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,12 @@ from __future__ import annotations
|
||||||
import json
|
import json
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
|
|
||||||
from aborist.document import Document
|
from arborist.document import Document
|
||||||
from aborist.ingest import ingest_source
|
from arborist.ingest import ingest_source
|
||||||
from aborist.qa import query
|
from arborist.qa import query
|
||||||
from aborist.qa.client import StubClient
|
from arborist.qa.client import StubClient
|
||||||
from aborist.source import Source
|
from arborist.source import Source
|
||||||
from aborist.store import connect
|
from arborist.store import connect
|
||||||
|
|
||||||
|
|
||||||
class FakeSource(Source):
|
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.
|
be secondary; The Lost World should be sequel; off-topic background.
|
||||||
Catches the case where peripheral pages with strong title overlap
|
Catches the case where peripheral pages with strong title overlap
|
||||||
used to share the primary slot."""
|
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"}
|
qstems = {"dinosaur", "jurassic", "park", "film"}
|
||||||
assert _classify_source_role("Jurassic Park (film)", qstems) == "primary_answer_source"
|
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
|
"""Per-run Merkle-DAG fingerprint surfaces on both the result dict
|
||||||
& the persisted providence_cache row. Recomputing the root from
|
& the persisted providence_cache row. Recomputing the root from
|
||||||
the persisted blob matches what was stored."""
|
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"
|
main_db = tmp_path / "corpus.db"
|
||||||
qa_db = tmp_path / "qa.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):
|
def test_query_burn_existing_writes_audit_event(tmp_path):
|
||||||
"""Each --burn writes a providence_burn audit event so the chain
|
"""Each --burn writes a providence_burn audit event so the chain
|
||||||
records the bust. Verifies one event lands per burn."""
|
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"
|
main_db = tmp_path / "corpus.db"
|
||||||
qa_db = tmp_path / "qa.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
|
"""`_question_phrases` extracts verbatim n-token windows. Function
|
||||||
words are kept — diagnostic value of an allusion is the EXACT
|
words are kept — diagnostic value of an allusion is the EXACT
|
||||||
sequence ('always been at war' >> 'always war')."""
|
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)
|
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
|
# 9-token query, 4-gram window → 6 phrases, all preserved verbatim
|
||||||
# (lowercase) and deduped.
|
# (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():
|
def test_question_phrases_n_5_yields_five_token_phrases():
|
||||||
"""5-grams trade recall for precision; 'oceania always been at war'
|
"""5-grams trade recall for precision; 'oceania always been at war'
|
||||||
is a much stronger Orwell signal than 'always been at war' alone."""
|
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)
|
out = _question_phrases("has oceania always been at war with east asia", n=5)
|
||||||
assert "oceania always been at war" in out
|
assert "oceania always been at war" in out
|
||||||
assert "always been at war with" 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():
|
def test_question_phrases_skips_when_question_shorter_than_n():
|
||||||
"""`who is X?` is too short to yield 4-grams. Empty output is the
|
"""`who is X?` is too short to yield 4-grams. Empty output is the
|
||||||
expected behavior (the body BM25 + title routes still cover it)."""
|
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("who is X?", n=4) == []
|
||||||
assert _question_phrases("", 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')
|
"""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
|
— drops to avoid over-matching. The skip rule fires only when ALL
|
||||||
tokens in the window are <4 chars."""
|
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.
|
# All ≤3-char tokens — drop.
|
||||||
assert _question_phrases("to be or not", n=4) == []
|
assert _question_phrases("to be or not", n=4) == []
|
||||||
# Mixed: at least one ≥4-char token → keep.
|
# 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():
|
def test_question_phrases_lowercases_and_dedupes():
|
||||||
"""Output is lowercase, deduped on string equality. Same
|
"""Output is lowercase, deduped on string equality. Same
|
||||||
sequence in different cases collapses to one phrase."""
|
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)
|
out = _question_phrases("Always been at war Always been at war", n=4)
|
||||||
# Repeated sequence appears only once in the output.
|
# Repeated sequence appears only once in the output.
|
||||||
assert out.count("always been at war") == 1
|
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):
|
def test_search_phrases_returns_empty_on_no_phrases(tmp_path):
|
||||||
"""Defensive: empty phrase list yields no rows, no exceptions."""
|
"""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"
|
main_db = tmp_path / "corpus.db"
|
||||||
conn = connect(main_db)
|
conn = connect(main_db)
|
||||||
try:
|
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):
|
def test_search_phrases_skips_phrases_with_double_quotes(tmp_path):
|
||||||
"""Adversarial input safety: phrases containing `"` would break
|
"""Adversarial input safety: phrases containing `"` would break
|
||||||
the FTS5 quoted-phrase syntax. The function silently drops them."""
|
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"
|
main_db = tmp_path / "corpus.db"
|
||||||
conn = connect(main_db)
|
conn = connect(main_db)
|
||||||
try:
|
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
|
tokens to trigger the n=5/n=6 phrase route. Short conventional
|
||||||
queries route through body-BM25 + title-LIKE only — phrase routing
|
queries route through body-BM25 + title-LIKE only — phrase routing
|
||||||
is structurally biased toward longer allusion-shape questions."""
|
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.
|
# 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=5) == []
|
||||||
assert _question_phrases("oceania east asia geography", n=6) == []
|
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
|
"""Direct unit test for accept-path 4: a hit whose title shares
|
||||||
zero content tokens with the question, but whose document_root is
|
zero content tokens with the question, but whose document_root is
|
||||||
in `phrase_match_roots`, must pass the filter."""
|
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 = [
|
hits = [
|
||||||
# Title shares NO content tokens with the question. Without
|
# Title shares NO content tokens with the question. Without
|
||||||
# accept-path 4 (phrase_match_roots), it would be dropped.
|
# 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
|
OR-chain + ORDER BY LENGTH(title) — handles arbitrary token
|
||||||
counts. Cap at MAX_TITLE_LIKE_TOKENS bounds the OR-chain
|
counts. Cap at MAX_TITLE_LIKE_TOKENS bounds the OR-chain
|
||||||
growth defensively."""
|
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)
|
main_db, _qa_db = _build_titled_corpus(tmp_path)
|
||||||
conn = connect(main_db)
|
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():
|
def test_unit_search_titles_handles_zero_tokens():
|
||||||
"""Defensive: zero tokens → empty list, no SQL executed."""
|
"""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)
|
rows = _search_titles(None, [], 32)
|
||||||
assert rows == []
|
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
|
expression-tree growth. Beyond the cap, additional tokens
|
||||||
are dropped at the SQL layer; the post-filter in the caller
|
are dropped at the SQL layer; the post-filter in the caller
|
||||||
sees them all and does the actual filtering."""
|
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)
|
main_db, _qa_db = _build_titled_corpus(tmp_path)
|
||||||
conn = connect(main_db)
|
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():
|
def test_unit_hyphen_fold_variants_emits_joined_form():
|
||||||
"""`bi-polar` → {"bipolar"}; multiple hyphen runs emit one
|
"""`bi-polar` → {"bipolar"}; multiple hyphen runs emit one
|
||||||
joined variant each; non-hyphenated input emits nothing."""
|
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("bi-polar is rare?") == {"bipolar"}
|
||||||
assert _hyphen_fold_variants("high-school co-op") == {
|
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():
|
def test_unit_title_query_tokens_includes_hyphen_fold_additively():
|
||||||
"""Hyphen-fold is additive — `bi-polar is rare?` produces both
|
"""Hyphen-fold is additive — `bi-polar is rare?` produces both
|
||||||
the split forms (bi, polar) AND the joined form (bipolar)."""
|
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?")
|
toks = _title_query_tokens("bi-polar is rare?")
|
||||||
assert "bi" in toks
|
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():
|
def test_unit_title_query_tokens_no_hyphen_unchanged():
|
||||||
"""Non-hyphen input behaves exactly as before — pin that the
|
"""Non-hyphen input behaves exactly as before — pin that the
|
||||||
fold doesn't add spurious tokens for plain queries."""
|
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") == {
|
assert _title_query_tokens("anarchism political philosophy") == {
|
||||||
"anarchism",
|
"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:
|
Covers:
|
||||||
- Stale UNGROUNDED records become STRICT/HYBRID/etc when the new verifier
|
- Stale UNGROUNDED records become STRICT/HYBRID/etc when the new verifier
|
||||||
|
|
@ -16,13 +16,13 @@ import argparse
|
||||||
import json
|
import json
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
|
|
||||||
from aborist.cli import _cmd_reclassify
|
from arborist.cli import _cmd_reclassify
|
||||||
from aborist.document import Document
|
from arborist.document import Document
|
||||||
from aborist.ingest import ingest_source
|
from arborist.ingest import ingest_source
|
||||||
from aborist.qa import query
|
from arborist.qa import query
|
||||||
from aborist.qa.client import StubClient
|
from arborist.qa.client import StubClient
|
||||||
from aborist.source import Source
|
from arborist.source import Source
|
||||||
from aborist.store import connect
|
from arborist.store import connect
|
||||||
|
|
||||||
|
|
||||||
class _FakeSource(Source):
|
class _FakeSource(Source):
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,13 @@ from __future__ import annotations
|
||||||
|
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
|
|
||||||
from aborist.document import Document
|
from arborist.document import Document
|
||||||
from aborist.ingest import ingest_source
|
from arborist.ingest import ingest_source
|
||||||
from aborist.qa import query
|
from arborist.qa import query
|
||||||
from aborist.qa.client import StubClient
|
from arborist.qa.client import StubClient
|
||||||
from aborist.qa.repair import mechanical_repair
|
from arborist.qa.repair import mechanical_repair
|
||||||
from aborist.source import Source
|
from arborist.source import Source
|
||||||
from aborist.store import connect
|
from arborist.store import connect
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------- mechanical_repair
|
# ---------------------------------------------------------------- mechanical_repair
|
||||||
|
|
@ -200,7 +200,7 @@ def test_query_reprompt_rewrites_on_paraphrase_failure(tmp_path):
|
||||||
|
|
||||||
client = _SeqClient()
|
client = _SeqClient()
|
||||||
|
|
||||||
from aborist.qa.query import DEFAULT_QUERY_POLICY
|
from arborist.qa.query import DEFAULT_QUERY_POLICY
|
||||||
policy = dict(DEFAULT_QUERY_POLICY)
|
policy = dict(DEFAULT_QUERY_POLICY)
|
||||||
policy["repair_enabled"] = True
|
policy["repair_enabled"] = True
|
||||||
policy["repair_max_reprompts"] = 1
|
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.
|
# 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 = dict(DEFAULT_QUERY_POLICY)
|
||||||
policy["repair_enabled"] = True
|
policy["repair_enabled"] = True
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ from __future__ import annotations
|
||||||
|
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
|
|
||||||
from aborist.document import Document
|
from arborist.document import Document
|
||||||
from aborist.ingest import ingest_source
|
from arborist.ingest import ingest_source
|
||||||
from aborist.source import Source
|
from arborist.source import Source
|
||||||
from aborist.store import connect, get_meta, set_meta
|
from arborist.store import connect, get_meta, set_meta
|
||||||
|
|
||||||
|
|
||||||
class IndexedSource(Source):
|
class IndexedSource(Source):
|
||||||
|
|
@ -54,7 +54,7 @@ def test_resume_skips_processed(tmp_path):
|
||||||
conn = connect(db)
|
conn = connect(db)
|
||||||
try:
|
try:
|
||||||
# Pre-seed a high-water as if a prior run got through doc 4.
|
# 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")
|
set_meta(conn, "source_high_water:indexed_test", "4")
|
||||||
src = IndexedSource(n_docs=10)
|
src = IndexedSource(n_docs=10)
|
||||||
result = ingest_source(conn, src, batch_size=5, resume=True)
|
result = ingest_source(conn, src, batch_size=5, resume=True)
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,9 @@ import time
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aborist.document import Document
|
from arborist.document import Document
|
||||||
from aborist.ingest import ingest_source
|
from arborist.ingest import ingest_source
|
||||||
from aborist.snapshot import (
|
from arborist.snapshot import (
|
||||||
EMPTY_SNAPSHOT_ROOT,
|
EMPTY_SNAPSHOT_ROOT,
|
||||||
compute_snapshot_root,
|
compute_snapshot_root,
|
||||||
create_snapshot,
|
create_snapshot,
|
||||||
|
|
@ -20,8 +20,8 @@ from aborist.snapshot import (
|
||||||
list_snapshots,
|
list_snapshots,
|
||||||
verify_snapshot,
|
verify_snapshot,
|
||||||
)
|
)
|
||||||
from aborist.source import Source
|
from arborist.source import Source
|
||||||
from aborist.store import connect
|
from arborist.store import connect
|
||||||
|
|
||||||
|
|
||||||
class _FakeSource(Source):
|
class _FakeSource(Source):
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aborist.qa.soft_preflight import (
|
from arborist.qa.soft_preflight import (
|
||||||
SOFT_PREFLIGHT_VERSION,
|
SOFT_PREFLIGHT_VERSION,
|
||||||
SoftPreflightHint,
|
SoftPreflightHint,
|
||||||
_normalize_label,
|
_normalize_label,
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,13 @@ from __future__ import annotations
|
||||||
import json
|
import json
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
|
|
||||||
from aborist.distill import TfidfKeywordDistiller
|
from arborist.distill import TfidfKeywordDistiller
|
||||||
from aborist.distill.runner import distill_existing
|
from arborist.distill.runner import distill_existing
|
||||||
from aborist.document import Document
|
from arborist.document import Document
|
||||||
from aborist.ingest import ingest_source
|
from arborist.ingest import ingest_source
|
||||||
from aborist.merkle import proof_from_dict, verify_proof
|
from arborist.merkle import proof_from_dict, verify_proof
|
||||||
from aborist.source import Source
|
from arborist.source import Source
|
||||||
from aborist.store import connect
|
from arborist.store import connect
|
||||||
|
|
||||||
|
|
||||||
class FakeSource(Source):
|
class FakeSource(Source):
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,9 @@ from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aborist.ingest import ingest_source
|
from arborist.ingest import ingest_source
|
||||||
from aborist.sources.vcs import GitRepoSource, MercurialRepoSource
|
from arborist.sources.vcs import GitRepoSource, MercurialRepoSource
|
||||||
from aborist.store import connect
|
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):
|
def test_git_source_round_trip_and_supersedes_chain(tmp_path):
|
||||||
repo = tmp_path / "demo-repo"
|
repo = tmp_path / "demo-repo"
|
||||||
_mk_git_repo(repo)
|
_mk_git_repo(repo)
|
||||||
db = tmp_path / "aborist.db"
|
db = tmp_path / "arborist.db"
|
||||||
|
|
||||||
# First ingest: 2 docs (README.md, src.py).
|
# First ingest: 2 docs (README.md, src.py).
|
||||||
conn = connect(db)
|
conn = connect(db)
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ Tests cover the full trichotomy under each path plus regressions:
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from aborist.qa.verify import (
|
from arborist.qa.verify import (
|
||||||
extract_claim_spans,
|
extract_claim_spans,
|
||||||
extract_proper_nouns,
|
extract_proper_nouns,
|
||||||
extract_quotes,
|
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():
|
def test_wikitext_context_strips_markup_before_substring_test():
|
||||||
"""Without the strip the verifier sees [[Cloud Strife]] in source and a
|
"""Without the strip the verifier sees [[Cloud Strife]] in source and a
|
||||||
bare 'Cloud Strife' in the answer's quoted span; substring fails. With
|
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
|
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."""
|
real FF7 query that prompted wiring wikitext-base-v1 into the verifier."""
|
||||||
raw_wikitext_context = (
|
raw_wikitext_context = (
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ import json
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aborist.qa.evidence import EvidenceObject
|
from arborist.qa.evidence import EvidenceObject
|
||||||
from aborist.qa.verify import _lenient_json_parse, verify_claim_lattice_json
|
from arborist.qa.verify import _lenient_json_parse, verify_claim_lattice_json
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------- lenient parser
|
# ---------------------------------------------------------------- lenient parser
|
||||||
|
|
@ -207,7 +207,7 @@ def test_verify_json_blocks_disallowed_source_role():
|
||||||
|
|
||||||
def test_claim_title_overlap_passes_when_title_shares_token():
|
def test_claim_title_overlap_passes_when_title_shares_token():
|
||||||
"""Rule 8 helper: title shares ≥1 stemmed content token with claim."""
|
"""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(
|
assert _claim_title_overlap(
|
||||||
"Homer Simpson's boss is Mr. Burns.",
|
"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():
|
def test_claim_title_overlap_fails_on_qcd_for_spin_glass():
|
||||||
"""Rule 8 helper: spin-glass case from 2026-05-02. Claim about
|
"""Rule 8 helper: spin-glass case from 2026-05-02. Claim about
|
||||||
spin glass cited to Quantum chromodynamics → no token overlap."""
|
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(
|
assert not _claim_title_overlap(
|
||||||
"Spin glass modeling involves the use of mathematical tensors.",
|
"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():
|
def test_claim_title_overlap_vacuous_pass_on_empty():
|
||||||
"""Defensive: empty title or empty claim → vacuous pass."""
|
"""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.", None)
|
||||||
assert _claim_title_overlap("Some claim here.", "")
|
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."""
|
through the chat client. Stub captures the kwargs so we can assert."""
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
|
|
||||||
from aborist.document import Document
|
from arborist.document import Document
|
||||||
from aborist.ingest import ingest_source
|
from arborist.ingest import ingest_source
|
||||||
from aborist.qa import ask
|
from arborist.qa import ask
|
||||||
from aborist.qa.client import StubClient
|
from arborist.qa.client import StubClient
|
||||||
from aborist.qa.runner import DEFAULT_POLICY
|
from arborist.qa.runner import DEFAULT_POLICY
|
||||||
from aborist.source import Source
|
from arborist.source import Source
|
||||||
from aborist.store import connect
|
from arborist.store import connect
|
||||||
|
|
||||||
class _FakeSource(Source):
|
class _FakeSource(Source):
|
||||||
source_type = "test"
|
source_type = "test"
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@ from __future__ import annotations
|
||||||
import time
|
import time
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
|
|
||||||
from aborist.document import Document
|
from arborist.document import Document
|
||||||
from aborist.ingest import ingest_source
|
from arborist.ingest import ingest_source
|
||||||
from aborist.source import Source
|
from arborist.source import Source
|
||||||
from aborist.store import connect
|
from arborist.store import connect
|
||||||
|
|
||||||
|
|
||||||
class FakeSource(Source):
|
class FakeSource(Source):
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ Covers:
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from aborist.qa.warrant import (
|
from arborist.qa.warrant import (
|
||||||
_question_is_count_shape,
|
_question_is_count_shape,
|
||||||
_question_is_entity_list_shape,
|
_question_is_entity_list_shape,
|
||||||
_question_is_why_shape,
|
_question_is_why_shape,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
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.
|
# 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