arborist/tests/test_cli_render.py
russell@unturf.com 8d6961fcc1
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
2026-05-07 09:31:49 -04:00

385 lines
14 KiB
Python

"""Pretty-print + ensure_ascii=False on `arborist query` output.
The CLI default emits a human-readable render of the query result;
``--json`` switches to the raw record. Both paths must:
- render unicode literals (no \\uXXXX escape sequences)
- present the answer prominently; metadata is supporting info
"""
from __future__ import annotations
import json
import pytest
from arborist.cli import _render_query_human
# ---------------------------------------------------------------------------
# _render_query_human — direct unit tests
# ---------------------------------------------------------------------------
def _result(**overrides) -> dict:
base = {
"status": "cache_miss_then_written",
"audit_mode": "HYBRID",
"cache_key": "35ab7d3355c723b759145d6d446cb1d9010abe0dce9992ca0e8b87837b596610",
"context_root": "ab" * 32,
"answer_text": "Pikachu is a species of Pokémon creatures.",
"sources": [
{
"document_root": "cd" * 32,
"document_uri": "https://en.wikipedia.org/wiki/Pikachu",
"title": "Pikachu",
"score": 36.6,
"chunk_idx": 6,
"shard": "002.db",
}
],
"n_quotes": 2,
"n_verified": 1,
"verifier_method": "quote",
"unverified_quotes": ["something the model said but didn't ground"],
"timings": {"total_ms": 9234.1},
}
base.update(overrides)
return base
def test_render_includes_question_at_top():
out = _render_query_human(_result(), "who is pikachu?")
assert out.splitlines()[0] == "who is pikachu?"
def test_render_summary_line_has_audit_n_verified_method_elapsed():
"""Summary line carries audit_label · n_verified/n_quotes · elapsed.
The label format updated 2026-05-01 to honesty-spell verifier
method (e.g. "EVIDENCE-LINKED · via claim_lattice" instead of
"STRICT") so user-facing output doesn't overclaim semantic truth.
Quote / span / entity / paraphrase modes keep the original
audit_mode token plus a "via {method}" tail."""
out = _render_query_human(_result(), "q")
second = out.splitlines()[1]
# Quote-mode keeps the audit_mode token (HYBRID) — it's the
# claim_lattice modes that get the EVIDENCE-LINKED relabel.
assert "HYBRID" in second
# Counts and method still in the line; "verified" word dropped
# to give the slimmer "1/2" form room for the longer label tail.
assert "1/2" in second
assert "via quote" in second
assert "9.2s" in second
def test_render_marks_cache_hit_distinctly():
out_hit = _render_query_human(_result(status="cache_hit"), "q")
out_fresh = _render_query_human(_result(status="cache_miss_then_written"), "q")
assert "(cached)" in out_hit
assert "(fresh)" in out_fresh
def test_render_emits_unicode_literals_not_escapes():
"""Pokémon, not Pok\\u00e9mon — the user's terminal should see real é."""
out = _render_query_human(_result(), "who is pikachu?")
assert "Pokémon" in out
assert "\\u00e9" not in out
def test_render_lists_sources_with_clean_host_path():
out = _render_query_human(_result(), "q")
# "[1] Pikachu — en.wikipedia.org/wiki/Pikachu (002.db)"
assert "[1] Pikachu" in out
assert "en.wikipedia.org/wiki/Pikachu" in out
# scheme stripped
assert "https://" not in out.split("sources (")[1] if "sources (" in out else True
assert "(002.db)" in out
def test_render_truncates_long_unverified_quotes():
long = "x" * 200
out = _render_query_human(_result(unverified_quotes=[long]), "q")
# Truncated form ends in ellipsis
assert "..." in out
# Original doesn't fully appear
assert long not in out
def test_render_omits_unverified_section_when_empty():
out = _render_query_human(_result(unverified_quotes=[]), "q")
assert "unverified" not in out.lower()
def test_render_omits_sources_section_when_empty():
out = _render_query_human(_result(sources=[]), "q")
assert "sources (" not in out
def test_render_falls_back_for_error_status():
"""no_sources / unknown_document / etc. produce a one-line status."""
out = _render_query_human(
{"status": "no_sources", "msg": "FTS5 returned no hits"},
"q",
)
assert "no_sources" in out
assert "FTS5" in out
def test_render_includes_short_cache_key_with_pointer_to_json():
out = _render_query_human(_result(), "q")
# Short prefix only — full key requires --json
assert "cache_key: 35ab7d33" in out
assert "--json" in out
# ---------------------------------------------------------------------------
# json.dumps everywhere uses ensure_ascii=False (regression on the global pass)
# ---------------------------------------------------------------------------
def test_unicode_round_trips_through_print_path(capsys):
"""Pin: a result with a Pokémon-style answer prints é, not \\u00e9.
Uses argparse via build_parser to exercise the real CLI dispatch."""
from arborist.cli import build_parser
parser = build_parser()
# Build a fake result and call _cmd_query's render path indirectly.
out = _render_query_human(_result(), "who is pikachu?")
print(out)
captured = capsys.readouterr().out
assert "Pokémon" in captured
assert "\\u00e9" not in captured
def test_json_mode_also_uses_unicode():
"""The --json path should also emit unicode literals, not escapes."""
payload = {"answer": "Pokémon"}
s = json.dumps(payload, ensure_ascii=False)
assert "Pokémon" in s
assert "\\u00e9" not in s
# ---------------------------------------------------------------------------
# capacity metrics — human render surfaces prompt_chars breakdown
# ---------------------------------------------------------------------------
def test_render_shows_capacity_line_when_prompt_chars_present():
"""When the result carries `prompt_chars`, the human render
emits a one-liner summarizing prompt size + answer size so an
operator can tell at a glance whether STRICT came from a small
prompt or a context-stuffed one."""
r = _result(
prompt_chars={
"system_prompt": 1292,
"grounding_reminder": 342,
"user_question": 30,
"evidence_or_context": 20304,
"messages_total": 21996,
},
answer_chars=247,
)
out = _render_query_human(r, "what is the capital of france?")
assert "capacity:" in out
assert "21,996 chars" in out # messages_total
assert "sys 1,292" in out
assert "evidence 20,304" in out
assert "answer 247 chars" in out
def test_render_omits_capacity_line_on_legacy_results_without_prompt_chars():
"""Backwards-compat: results that pre-date capacity metrics (no
prompt_chars key) render without the capacity line — no KeyError,
no awkward 'capacity: 0 chars' noise."""
r = _result() # no prompt_chars
out = _render_query_human(r, "q")
assert "capacity:" not in out
def test_render_capacity_thousand_separators():
"""Large prompts must format with thousand-separator commas so
a 60K prompt reads as '60,000' not '60000' — operator legibility
on the daily render."""
r = _result(
prompt_chars={
"system_prompt": 1500,
"grounding_reminder": 0,
"user_question": 50,
"evidence_or_context": 60000,
"messages_total": 61550,
},
answer_chars=1234,
)
out = _render_query_human(r, "q")
assert "61,550 chars" in out
assert "60,000" in out
assert "1,234 chars" in out
# ---------------------------------------------------------------------------
# label discipline — claim_lattice* modes get EVIDENCE-LINKED relabel
# ---------------------------------------------------------------------------
def test_render_label_strict_lattice_no_violations_is_evidence_warranted():
"""claim-lattice STRICT with no soft demotes lands at the top
ladder rung (EVIDENCE-WARRANTED) — pointer verified, warrant
passed where it ran, no other demotes pulled the verdict back."""
r = _result(
audit_mode="STRICT",
verifier_method="claim_lattice",
n_quotes=1, n_verified=1,
violations=[],
)
out = _render_query_human(r, "q")
assert "EVIDENCE-WARRANTED" in out
assert "via claim_lattice" in out
# The bare STRICT token should NOT appear on the summary line.
summary_line = out.splitlines()[1]
assert "STRICT" not in summary_line
def test_render_label_strict_lattice_warrant_missing_is_pointer_linked():
"""claim-lattice STRICT (or HYBRID) with WARRANT_MISSING in
violations drops to POINTER-LINKED — the pointer resolved but
the cited evidence didn't anchor the claim."""
r = _result(
audit_mode="HYBRID",
verifier_method="claim_lattice_pointer",
n_quotes=2, n_verified=1,
violations=[{"kind": "WARRANT_MISSING", "claim_idx": 0}],
)
out = _render_query_human(r, "q")
assert "POINTER-LINKED-PARTIAL" in out
assert "via claim_lattice_pointer" in out
def test_render_label_strict_lattice_soft_demote_is_anchor_warranted():
"""claim-lattice STRICT/HYBRID with a soft-demote violation
(LAZY_ANCHOR_DEMOTED, POINTER_OVERFLOW_TRIMMED, TOO_MANY_CLAIMS)
but no WARRANT_MISSING lands at ANCHOR-WARRANTED — warrant
passed where it ran, but other demotes apply."""
r = _result(
audit_mode="STRICT",
verifier_method="claim_lattice",
n_quotes=3, n_verified=3,
violations=[{"kind": "LAZY_ANCHOR_DEMOTED"}],
)
out = _render_query_human(r, "q")
assert "ANCHOR-WARRANTED" in out
assert "via claim_lattice" in out
def test_render_label_hybrid_lattice_evidence_warranted_partial():
"""HYBRID + no violations + lattice mode → EVIDENCE-WARRANTED-PARTIAL.
The -PARTIAL suffix shows the audit didn't reach STRICT but the
claim that did verify reached the top rung."""
r = _result(
audit_mode="HYBRID",
verifier_method="claim_lattice",
n_quotes=3, n_verified=1,
violations=[],
)
out = _render_query_human(r, "q")
assert "EVIDENCE-WARRANTED-PARTIAL" in out
def test_render_label_ungrounded_in_claim_lattice_stays_ungrounded():
"""UNGROUNDED already names what it means — no rung-relabel."""
r = _result(
audit_mode="UNGROUNDED",
verifier_method="claim_lattice",
n_quotes=2, n_verified=0,
violations=[],
)
out = _render_query_human(r, "q")
assert "UNGROUNDED" in out
assert "via claim_lattice" in out
def test_render_shows_reference_frame_notes_when_detected():
"""When frame_detection.kind == 'reference', the renderer
surfaces the named reference work so an operator can see the
substrate routed to a fictional / reference source."""
r = _result(
audit_mode="STRICT",
verifier_method="claim_lattice",
frame_detection={
"kind": "reference",
"reference_title": "Nineteen Eighty-Four",
"reference_uri": "https://en.wikipedia.org/wiki/Nineteen_Eighty-Four",
"confidence": 0.8,
},
)
out = _render_query_human(r, "has oceania always been at war with east asia")
assert "reference frame: Nineteen Eighty-Four" in out
def test_render_omits_reference_frame_notes_for_literal_query():
"""Literal-frame queries (no allusion) don't render the frame
notes line — keeps the daily render clean for the common case."""
r = _result(
audit_mode="STRICT",
verifier_method="claim_lattice",
frame_detection={
"kind": "no_phrase_route",
"reference_title": None,
"reference_uri": None,
"confidence": 0.0,
},
)
out = _render_query_human(r, "what is the capital of france?")
assert "reference frame:" not in out
def test_render_omits_reference_frame_notes_for_ambiguous():
"""Ambiguous frame (phrase route fired but no clear reference
work) → no frame notes line. The signal isn't strong enough
to claim a named reference."""
r = _result(
audit_mode="STRICT",
verifier_method="claim_lattice",
frame_detection={
"kind": "ambiguous",
"reference_title": None,
"reference_uri": None,
"confidence": 0.4,
},
)
out = _render_query_human(r, "some allusion-shape query")
assert "reference frame:" not in out
def test_render_label_quote_mode_keeps_audit_mode_token():
"""Quote / span / entity / paraphrase modes verify against
pinned spans, not synthesis. STRICT in quote mode IS a strong
claim about evidence units — keep the label as-is."""
r = _result(
audit_mode="STRICT",
verifier_method="quote",
n_quotes=2, n_verified=2,
)
out = _render_query_human(r, "q")
assert "STRICT" in out
assert "via quote" in out
# Don't relabel quote-mode STRICT to either ladder rung.
assert "EVIDENCE-WARRANTED" not in out
assert "POINTER-LINKED" not in out
assert "ANCHOR-WARRANTED" not in out
def test_render_label_format_collapsed_surfaces_tail():
"""FORMAT_COLLAPSED soft-demotes STRICT → HYBRID and the renderer
surfaces a `· format collapsed` tail so an operator can tell from
the audit line whether UNGROUNDED meant 'tried & failed' vs
'abandoned the protocol entirely.' Pairs with WARRANT_MISSING /
TITLE_MISMATCH tails — multiple can coexist."""
r = _result(
audit_mode="UNGROUNDED",
verifier_method="claim_lattice_pointer",
n_quotes=2, n_verified=0,
violations=[{"kind": "FORMAT_COLLAPSED", "meaningful_lines": 50,
"bracket_count": 0}],
)
out = _render_query_human(r, "winners of all major sports?")
assert "format collapsed" in out
assert "UNGROUNDED" in out