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
228 lines
8.4 KiB
Python
228 lines
8.4 KiB
Python
"""Tests for ProvidenceSource — self-reference thought chains.
|
|
|
|
Covers the four iteration-time gates documented in
|
|
docs/self-reference-design.md:
|
|
|
|
1. audit_mode == 'STRICT' (HYBRID/UNGROUNDED excluded)
|
|
2. falsification_state == 'live' (failed/stale/quarantined excluded)
|
|
3. now - created_at >= kindergarten_seconds (fresh records cool first)
|
|
4. anti-recursion: records whose answer cites a self-reference URI
|
|
are excluded (first-generation only)
|
|
|
|
Plus the classifier-side: documents with `arborist://providence/`
|
|
URIs classify as `self_reference_source`.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sqlite3
|
|
import time
|
|
|
|
import pytest
|
|
|
|
from arborist.qa.query import _classify_source_role
|
|
from arborist.sources.providence import (
|
|
PROVIDENCE_URI_PREFIX,
|
|
ProvidenceSource,
|
|
)
|
|
from arborist.store import connect
|
|
|
|
|
|
def _seed(conn: sqlite3.Connection, **fields) -> str:
|
|
"""Insert one minimal providence_cache row. Returns the cache_key."""
|
|
defaults = {
|
|
"cache_key": fields.get("cache_key", "ck_" + str(int(time.time() * 1e6))),
|
|
"source_root": "00" * 32,
|
|
"document_uri": "test://doc",
|
|
"question_hash": "11" * 32,
|
|
"question_text": "stub question",
|
|
"answer_text": "stub answer",
|
|
"merkle_proof": '{"proofs": []}',
|
|
"model_profile_hash": "22" * 32,
|
|
"conversation_hash": "33" * 32,
|
|
"governance_policy_hash": "44" * 32,
|
|
"schema_version": "v9.8.0",
|
|
"canonicalization_version": "norm-v1",
|
|
"chunking_version": "tok-512-v1",
|
|
"falsification_state": "live",
|
|
"chain": "private",
|
|
"audit_event_hash": "55" * 32,
|
|
"created_at": time.time() - 7200, # 2h old by default — past kindergarten
|
|
"last_hit_at": None,
|
|
"hit_count": 0,
|
|
"audit_mode": "STRICT",
|
|
"n_quotes": 1,
|
|
"n_verified": 1,
|
|
"unverified_quotes": None,
|
|
"verifier_method": "claim_lattice",
|
|
"run_dag_root": "66" * 32,
|
|
"run_dag_blob": "{}",
|
|
}
|
|
defaults.update(fields)
|
|
cols = ", ".join(defaults.keys())
|
|
placeholders = ", ".join("?" * len(defaults))
|
|
conn.execute(
|
|
f"INSERT INTO providence_cache ({cols}) VALUES ({placeholders})",
|
|
tuple(defaults.values()),
|
|
)
|
|
conn.commit()
|
|
return defaults["cache_key"]
|
|
|
|
|
|
def test_yields_strict_live_record_past_kindergarten(tmp_path):
|
|
db = tmp_path / "p.db"
|
|
conn = connect(db)
|
|
try:
|
|
ck = _seed(conn, cache_key="strict-cooled", question_text="who is X?",
|
|
answer_text="X is Y.")
|
|
# Source created with kindergarten=3600. Record is 2h old → eligible.
|
|
src = ProvidenceSource(conn, kindergarten_seconds=3600)
|
|
docs = list(src.iter_documents())
|
|
assert len(docs) == 1
|
|
d = docs[0]
|
|
assert d.uri == f"{PROVIDENCE_URI_PREFIX}{ck}"
|
|
assert d.source_type == "providence"
|
|
assert d.title == "who is X?"
|
|
assert "Q: who is X?" in d.content
|
|
assert "A: X is Y." in d.content
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def test_excludes_hybrid_and_ungrounded(tmp_path):
|
|
db = tmp_path / "p.db"
|
|
conn = connect(db)
|
|
try:
|
|
_seed(conn, cache_key="strict-1", audit_mode="STRICT")
|
|
_seed(conn, cache_key="hybrid-1", audit_mode="HYBRID")
|
|
_seed(conn, cache_key="ungrounded-1", audit_mode="UNGROUNDED")
|
|
docs = list(ProvidenceSource(conn).iter_documents())
|
|
uris = {d.uri for d in docs}
|
|
assert f"{PROVIDENCE_URI_PREFIX}strict-1" in uris
|
|
assert f"{PROVIDENCE_URI_PREFIX}hybrid-1" not in uris
|
|
assert f"{PROVIDENCE_URI_PREFIX}ungrounded-1" not in uris
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def test_excludes_falsified_records(tmp_path):
|
|
db = tmp_path / "p.db"
|
|
conn = connect(db)
|
|
try:
|
|
_seed(conn, cache_key="live-strict", audit_mode="STRICT", falsification_state="live")
|
|
_seed(conn, cache_key="failed-strict", audit_mode="STRICT", falsification_state="failed")
|
|
_seed(conn, cache_key="stale-strict", audit_mode="STRICT", falsification_state="stale")
|
|
_seed(conn, cache_key="quarantined-strict", audit_mode="STRICT",
|
|
falsification_state="quarantined")
|
|
docs = list(ProvidenceSource(conn).iter_documents())
|
|
uris = {d.uri for d in docs}
|
|
assert f"{PROVIDENCE_URI_PREFIX}live-strict" in uris
|
|
# Falsified rows excluded — verifier-falsification mechanism
|
|
# works exactly because state=live is the gate.
|
|
assert f"{PROVIDENCE_URI_PREFIX}failed-strict" not in uris
|
|
assert f"{PROVIDENCE_URI_PREFIX}stale-strict" not in uris
|
|
assert f"{PROVIDENCE_URI_PREFIX}quarantined-strict" not in uris
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def test_kindergarten_window_excludes_fresh_records(tmp_path):
|
|
db = tmp_path / "p.db"
|
|
conn = connect(db)
|
|
try:
|
|
now = time.time()
|
|
# 30s old — fresh, inside the 1h window.
|
|
_seed(conn, cache_key="fresh", created_at=now - 30)
|
|
# 2h old — past the window.
|
|
_seed(conn, cache_key="cooled", created_at=now - 7200)
|
|
src = ProvidenceSource(conn, kindergarten_seconds=3600, now_seconds=now)
|
|
docs = list(src.iter_documents())
|
|
uris = {d.uri for d in docs}
|
|
assert f"{PROVIDENCE_URI_PREFIX}cooled" in uris
|
|
assert f"{PROVIDENCE_URI_PREFIX}fresh" not in uris
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def test_kindergarten_zero_admits_all_strict_live(tmp_path):
|
|
db = tmp_path / "p.db"
|
|
conn = connect(db)
|
|
try:
|
|
now = time.time()
|
|
_seed(conn, cache_key="just-now", created_at=now - 1)
|
|
_seed(conn, cache_key="cooled", created_at=now - 86400)
|
|
src = ProvidenceSource(conn, kindergarten_seconds=0, now_seconds=now)
|
|
docs = list(src.iter_documents())
|
|
assert len(docs) == 2
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def test_anti_recursion_excludes_self_referencing_records(tmp_path):
|
|
"""A STRICT record whose own answer text contains
|
|
`arborist://providence/...` is excluded — first-generation only.
|
|
Prevents echo-chamber chains where a wrong-but-STRICT record
|
|
keeps getting recompiled into deeper claims."""
|
|
db = tmp_path / "p.db"
|
|
conn = connect(db)
|
|
try:
|
|
_seed(conn, cache_key="first-gen",
|
|
answer_text="X is Y per primary source.")
|
|
_seed(conn, cache_key="second-gen",
|
|
answer_text=f"X is Y per {PROVIDENCE_URI_PREFIX}other-key cited record.")
|
|
docs = list(ProvidenceSource(conn).iter_documents())
|
|
uris = {d.uri for d in docs}
|
|
assert f"{PROVIDENCE_URI_PREFIX}first-gen" in uris
|
|
assert f"{PROVIDENCE_URI_PREFIX}second-gen" not in uris
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def test_skips_records_with_empty_question_or_answer(tmp_path):
|
|
db = tmp_path / "p.db"
|
|
conn = connect(db)
|
|
try:
|
|
_seed(conn, cache_key="no-q", question_text="", answer_text="A")
|
|
_seed(conn, cache_key="no-a", question_text="Q", answer_text="")
|
|
_seed(conn, cache_key="both", question_text="Q", answer_text="A")
|
|
docs = list(ProvidenceSource(conn).iter_documents())
|
|
uris = {d.uri for d in docs}
|
|
assert f"{PROVIDENCE_URI_PREFIX}both" in uris
|
|
assert f"{PROVIDENCE_URI_PREFIX}no-q" not in uris
|
|
assert f"{PROVIDENCE_URI_PREFIX}no-a" not in uris
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def test_classify_source_role_recognizes_providence_uri():
|
|
"""`_classify_source_role` short-circuits on the URI scheme
|
|
regardless of title shape. Trust model is URI-based, not
|
|
title-heuristic-based."""
|
|
qstem = {"foo", "bar"}
|
|
role = _classify_source_role(
|
|
"Some Title (film)",
|
|
qstem,
|
|
document_uri=f"{PROVIDENCE_URI_PREFIX}abc123",
|
|
)
|
|
assert role == "self_reference_source"
|
|
|
|
|
|
def test_classify_source_role_falls_through_for_non_providence_uri():
|
|
"""External URIs get the existing title-based classification."""
|
|
qstem = {"jurassic", "park", "film"}
|
|
role = _classify_source_role(
|
|
"Jurassic Park (film)",
|
|
qstem,
|
|
document_uri="https://en.wikipedia.org/wiki/Jurassic_Park_(film)",
|
|
)
|
|
# Title has 3 stems matching the 3-stem query → primary.
|
|
assert role == "primary_answer_source"
|
|
|
|
|
|
def test_classify_source_role_handles_missing_uri():
|
|
"""document_uri is optional; without it the function falls back
|
|
to the existing title-based classification (backward compat)."""
|
|
qstem = {"jurassic", "park"}
|
|
role = _classify_source_role("Jurassic Park (film)", qstem)
|
|
# Stems match, primary classification.
|
|
assert role in ("primary_answer_source", "background_source")
|