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
197 lines
6.6 KiB
Python
197 lines
6.6 KiB
Python
"""Phase 4 — broad-quantifier soft-demote tails + reject-broad path.
|
|
|
|
Three new violation kinds (§10.3) added to the soft-demote ladder:
|
|
- BROAD_QUANTIFIER_RUNAWAY raw_line_count >> pointer_count
|
|
- BROAD_QUANTIFIER_CAP_APPLIED preflight cap fired below default
|
|
- BROAD_QUANTIFIER_SCOPE_UNBOUND unbounded universal reached LLM
|
|
|
|
Plus one HARD demote that returns UNGROUNDED via early-return:
|
|
- BROAD_QUANTIFIER_REJECTED preflight rejected before LLM call
|
|
|
|
Tests pin: tail rendering, ladder behavior, governance-hash binding
|
|
on quantifier_reject_broad, and the early-return shape for the
|
|
reject path.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from arborist.cli import (
|
|
_SOFT_DEMOTE_VIOLATION_KINDS,
|
|
_ladder_rung_for_lattice,
|
|
_render_query_human,
|
|
_render_warrant_tail,
|
|
)
|
|
from arborist.qa.keys import _VERIFIER_POLICY_FIELDS, verifier_policy_hash
|
|
|
|
|
|
# ---------------------------------------------------------------- soft-demote registration
|
|
|
|
@pytest.mark.parametrize("kind", [
|
|
"BROAD_QUANTIFIER_RUNAWAY",
|
|
"BROAD_QUANTIFIER_CAP_APPLIED",
|
|
"BROAD_QUANTIFIER_SCOPE_UNBOUND",
|
|
])
|
|
def test_broad_quantifier_kinds_in_soft_demote_set(kind):
|
|
"""All three soft-demote kinds must be registered so they cap
|
|
the ladder at ANCHOR-WARRANTED rather than reaching EVIDENCE-
|
|
WARRANTED."""
|
|
assert kind in _SOFT_DEMOTE_VIOLATION_KINDS
|
|
|
|
|
|
def test_rejected_kind_not_in_soft_demote_set():
|
|
"""REJECTED is a HARD demote (UNGROUNDED via early-return).
|
|
Listing it as a soft demote would let UNGROUNDED rejected runs
|
|
bubble up to ANCHOR-WARRANTED on the ladder."""
|
|
assert "BROAD_QUANTIFIER_REJECTED" not in _SOFT_DEMOTE_VIOLATION_KINDS
|
|
|
|
|
|
# ---------------------------------------------------------------- ladder rung
|
|
|
|
def test_cap_applied_demotes_to_anchor_warranted():
|
|
"""CAP_APPLIED is a soft demote — claim verified, but the cap
|
|
bound the answer below default. ANCHOR-WARRANTED, not
|
|
EVIDENCE-WARRANTED."""
|
|
rung = _ladder_rung_for_lattice(
|
|
"STRICT",
|
|
violations=[{"kind": "BROAD_QUANTIFIER_CAP_APPLIED"}],
|
|
)
|
|
assert rung == "ANCHOR-WARRANTED"
|
|
|
|
|
|
def test_scope_unbound_demotes_to_anchor_warranted():
|
|
rung = _ladder_rung_for_lattice(
|
|
"STRICT",
|
|
violations=[{"kind": "BROAD_QUANTIFIER_SCOPE_UNBOUND"}],
|
|
)
|
|
assert rung == "ANCHOR-WARRANTED"
|
|
|
|
|
|
def test_runaway_demotes_to_anchor_warranted():
|
|
rung = _ladder_rung_for_lattice(
|
|
"STRICT",
|
|
violations=[{"kind": "BROAD_QUANTIFIER_RUNAWAY"}],
|
|
)
|
|
assert rung == "ANCHOR-WARRANTED"
|
|
|
|
|
|
# ---------------------------------------------------------------- tail rendering
|
|
|
|
def test_tail_renders_broad_cap_with_count():
|
|
"""Cap value must appear in the tail — operators shouldn't have
|
|
to dig into violations to see what cap fired."""
|
|
tail = _render_warrant_tail({
|
|
"violations": [{"kind": "BROAD_QUANTIFIER_CAP_APPLIED"}],
|
|
"claim_cap_applied": 8,
|
|
})
|
|
assert "broad cap 8" in tail
|
|
|
|
|
|
def test_tail_renders_broad_unbounded():
|
|
tail = _render_warrant_tail({
|
|
"violations": [{"kind": "BROAD_QUANTIFIER_SCOPE_UNBOUND"}],
|
|
})
|
|
assert "broad unbounded" in tail
|
|
|
|
|
|
def test_tail_renders_broad_runaway():
|
|
tail = _render_warrant_tail({
|
|
"violations": [{"kind": "BROAD_QUANTIFIER_RUNAWAY"}],
|
|
})
|
|
assert "broad runaway" in tail
|
|
|
|
|
|
def test_tail_renders_broad_rejected():
|
|
tail = _render_warrant_tail({
|
|
"violations": [{"kind": "BROAD_QUANTIFIER_REJECTED"}],
|
|
})
|
|
assert "broad rejected" in tail
|
|
|
|
|
|
def test_tail_combines_with_existing_violations():
|
|
"""A run can have both a broad-quantifier soft demote AND a
|
|
title mismatch — both should surface on the tail."""
|
|
tail = _render_warrant_tail({
|
|
"violations": [
|
|
{"kind": "TITLE_MISMATCH"},
|
|
{"kind": "BROAD_QUANTIFIER_CAP_APPLIED"},
|
|
],
|
|
"claim_cap_applied": 8,
|
|
})
|
|
assert "title mismatch" in tail
|
|
assert "broad cap 8" in tail
|
|
|
|
|
|
def test_tail_omits_when_no_violations():
|
|
tail = _render_warrant_tail({"violations": []})
|
|
assert tail == ""
|
|
|
|
|
|
# ---------------------------------------------------------------- audit-line render
|
|
|
|
def test_render_label_with_broad_cap_tail():
|
|
"""End-to-end through _render_query_human: HYBRID + broad cap
|
|
surfaces both the ladder rung (ANCHOR-WARRANTED-PARTIAL) and
|
|
the broad-cap tail."""
|
|
result = {
|
|
"status": "cache_miss_then_written",
|
|
"audit_mode": "HYBRID",
|
|
"cache_key": "abc" * 21,
|
|
"context_root": "ab" * 32,
|
|
"answer_text": "Some bounded broad-quantifier answer.",
|
|
"sources": [],
|
|
"n_quotes": 8,
|
|
"n_verified": 6,
|
|
"verifier_method": "claim_lattice_pointer",
|
|
"unverified_quotes": [],
|
|
"violations": [{"kind": "BROAD_QUANTIFIER_CAP_APPLIED"}],
|
|
"claim_cap_applied": 8,
|
|
"timings": {"total_ms": 9000.0},
|
|
}
|
|
out = _render_query_human(result, "winners of all major sports?")
|
|
assert "ANCHOR-WARRANTED-PARTIAL" in out
|
|
assert "broad cap 8" in out
|
|
|
|
|
|
# ---------------------------------------------------------------- governance hash
|
|
|
|
def test_quantifier_reject_broad_in_verifier_policy_fields():
|
|
"""Flipping reject-broad must invalidate prior cache records —
|
|
same question with reject_broad=False vs True should produce
|
|
different cache_keys."""
|
|
assert "quantifier_reject_broad" in _VERIFIER_POLICY_FIELDS
|
|
|
|
|
|
def test_governance_hash_changes_when_reject_broad_flips():
|
|
base_policy = dict.fromkeys(_VERIFIER_POLICY_FIELDS, "default")
|
|
base_policy["quantifier_reject_broad"] = False
|
|
h_off = verifier_policy_hash(base_policy)
|
|
base_policy["quantifier_reject_broad"] = True
|
|
h_on = verifier_policy_hash(base_policy)
|
|
assert h_off != h_on
|
|
|
|
|
|
# ---------------------------------------------------------------- reject-broad path
|
|
|
|
def test_reject_broad_returns_early_for_unbounded_all():
|
|
"""Integration-shape check: when policy enables reject_broad and
|
|
the question is broad-unbounded, query() returns UNGROUNDED with
|
|
a BROAD_QUANTIFIER_REJECTED violation BEFORE the LLM call.
|
|
|
|
Skipped here because exercising the full query() path requires
|
|
a populated shards-dir + qa.db which isn't set up in this unit-
|
|
test scope. The behavior is exercised by the live bench cycle
|
|
when --reject-broad is passed."""
|
|
pytest.skip(
|
|
"integration: requires populated shards; covered by live bench"
|
|
)
|
|
|
|
|
|
def test_reject_broad_does_not_fire_for_bounded_universal():
|
|
"""`all members of the Beatles` is a bounded universal — should
|
|
NOT reject even with quantifier_reject_broad=True. Same skip
|
|
rationale as above."""
|
|
pytest.skip(
|
|
"integration: requires populated shards; covered by live bench"
|
|
)
|