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
412 lines
15 KiB
Python
412 lines
15 KiB
Python
"""Meta-Cognition Preflight Guard — Ticket #000010 Phase 1 tests.
|
|
|
|
Pin the seven test cases from the source doc §14 plus per-detector
|
|
unit tests. All checks are deterministic; no LLM, no I/O.
|
|
|
|
Source: ``~/Downloads/meta-cognition_for_hermes(1).txt`` 2026-05-03.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from arborist.qa.metacognition import (
|
|
PREFLIGHT_VERSION,
|
|
QuestionState,
|
|
detect_contradiction,
|
|
detect_false_premise,
|
|
detect_out_of_corpus,
|
|
detect_temporal_sensitivity,
|
|
preflight_question,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------- temporal
|
|
|
|
@pytest.mark.parametrize("q", [
|
|
"Who is the current CEO of OpenAI?",
|
|
"What is the latest version of Python?",
|
|
"Who is the President of France today?",
|
|
"What is the current price of Bitcoin?",
|
|
"Who won the championship this year?",
|
|
"As of right now, who holds the world record?",
|
|
])
|
|
def test_temporal_high_sensitivity(q):
|
|
sens, matched = detect_temporal_sensitivity(q)
|
|
assert sens == "high", f"{q} → {sens}"
|
|
assert len(matched) >= 1
|
|
|
|
|
|
@pytest.mark.parametrize("q", [
|
|
"what is the capital of france?",
|
|
"who painted the mona lisa?",
|
|
"what is the speed of light?",
|
|
"who wrote the play hamlet?",
|
|
])
|
|
def test_temporal_low_sensitivity_for_factoids(q):
|
|
sens, _ = detect_temporal_sensitivity(q)
|
|
assert sens == "low"
|
|
|
|
|
|
# ---------------------------------------------------------------- contradiction
|
|
|
|
def test_contradiction_unmarried_spouse():
|
|
pairs = detect_contradiction("Which unmarried spouse is Homer married to?")
|
|
# `unmarried` + `spouse` AND `unmarried` + `married` both fire.
|
|
pair_set = set(pairs)
|
|
assert ("unmarried", "spouse") in pair_set
|
|
assert ("unmarried", "married") in pair_set
|
|
|
|
|
|
def test_contradiction_alive_dead():
|
|
pairs = detect_contradiction("Which character is alive and dead simultaneously?")
|
|
assert ("alive", "dead") in set(pairs)
|
|
|
|
|
|
def test_contradiction_no_false_positives_on_singletons():
|
|
"""`unmarried` alone shouldn't fire — needs both halves of a pair."""
|
|
pairs = detect_contradiction("Is George Washington still unmarried?")
|
|
assert pairs == ()
|
|
|
|
|
|
def test_contradiction_clean_questions_have_no_pairs():
|
|
pairs = detect_contradiction("who painted the mona lisa?")
|
|
assert pairs == ()
|
|
|
|
|
|
# ---------------------------------------------------------------- false premise
|
|
|
|
def test_false_premise_when_did_x_stop_y():
|
|
hints = detect_false_premise("when did Mr. Burns stop being Homer's father?")
|
|
assert len(hints) >= 1
|
|
h = hints[0]
|
|
assert h["kind"] == "stopped_doing"
|
|
assert "Mr. Burns" in h["subject"]
|
|
|
|
|
|
def test_false_premise_why_did_x_cause_y():
|
|
hints = detect_false_premise("why did the moon cause the tides?")
|
|
assert len(hints) >= 1
|
|
assert hints[0]["kind"] == "caused"
|
|
|
|
|
|
def test_false_premise_how_did_x_become_y():
|
|
hints = detect_false_premise("how did Mr. Burns become Homer's biological father?")
|
|
assert len(hints) >= 1
|
|
assert hints[0]["kind"] == "became"
|
|
assert "Mr. Burns" in hints[0]["subject"]
|
|
|
|
|
|
def test_false_premise_no_hint_on_neutral_question():
|
|
hints = detect_false_premise("who is bilbo baggins?")
|
|
assert hints == ()
|
|
|
|
|
|
# ---------------------------------------------------------------- out of corpus
|
|
|
|
@pytest.mark.parametrize("q", [
|
|
"What does my uploaded contract say about clause 9?",
|
|
"What is in my private notes from yesterday?",
|
|
"What does the file I sent you say about X?",
|
|
"In my email inbox, what did Alice write?",
|
|
])
|
|
def test_out_of_corpus_detected(q):
|
|
assert detect_out_of_corpus(q) is True
|
|
|
|
|
|
@pytest.mark.parametrize("q", [
|
|
"What is the capital of France?",
|
|
"Who wrote Hamlet?",
|
|
"What is the speed of light?",
|
|
])
|
|
def test_out_of_corpus_not_detected_on_encyclopedic(q):
|
|
assert detect_out_of_corpus(q) is False
|
|
|
|
|
|
# ---------------------------------------------------------------- preflight (full integration)
|
|
|
|
def test_preflight_factoid_is_well_formed():
|
|
state = preflight_question("what is the capital of france?")
|
|
assert "well_formed" in state.logical_statuses
|
|
assert state.preflight_result == "PREFLIGHT_OK"
|
|
assert state.question_shape in ("single_fact", "negation")
|
|
|
|
|
|
def test_preflight_broad_quantifier_unbounded():
|
|
"""Source doc §5.3 + #000008 §10.1: `winners of all major sports?`
|
|
classifies as broad-quantifier-unbounded."""
|
|
state = preflight_question("Winners of all major sports?")
|
|
assert "broad_quantifier_unbounded" in state.logical_statuses
|
|
assert state.preflight_result == "PREFLIGHT_PARTIAL"
|
|
assert state.quantifier_intensity == "ALL"
|
|
assert state.scope_bound_hint == "unbounded"
|
|
|
|
|
|
def test_preflight_broad_quantifier_bounded_does_not_fire_unbounded():
|
|
"""`name all members of the beatles` is a bounded universal.
|
|
Should classify as `under_specified` (broad but bounded), NOT
|
|
`broad_quantifier_unbounded`."""
|
|
state = preflight_question("name all members of the beatles")
|
|
assert "broad_quantifier_unbounded" not in state.logical_statuses
|
|
assert state.scope_bound_hint == "bounded"
|
|
|
|
|
|
def test_preflight_contradictory_question():
|
|
"""Source doc §5.2: `Which unmarried spouse is Homer married to?`
|
|
classifies as contradictory."""
|
|
state = preflight_question("Which unmarried spouse is Homer married to?")
|
|
assert "contradictory_question" in state.logical_statuses
|
|
assert state.contradiction_pairs # at least one pair
|
|
|
|
|
|
def test_preflight_false_premise_suspected():
|
|
"""Source doc §5.1: `When did Mr. Burns become Homer's biological
|
|
father?` classifies as false_premise_suspected."""
|
|
state = preflight_question(
|
|
"When did Mr. Burns become Homer's biological father?"
|
|
)
|
|
assert "false_premise_suspected" in state.logical_statuses
|
|
assert state.false_premise_hints
|
|
|
|
|
|
def test_preflight_time_sensitive_stale_risk():
|
|
"""Source doc §5.4: `Who is the current CEO of X?` classifies
|
|
as stale_risk."""
|
|
state = preflight_question("Who is the current CEO of OpenAI?")
|
|
assert "stale_risk" in state.logical_statuses
|
|
assert state.temporal_sensitivity == "high"
|
|
assert state.answer_constraints.get("requires_current_source") is True
|
|
|
|
|
|
def test_preflight_out_of_corpus_blocked():
|
|
"""Source doc §5.5: out-of-corpus references should BLOCK."""
|
|
state = preflight_question("What does my uploaded contract say?")
|
|
assert "out_of_corpus_risk" in state.logical_statuses
|
|
assert state.preflight_result == "PREFLIGHT_BLOCKED"
|
|
|
|
|
|
def test_preflight_reference_frame_ambiguous():
|
|
"""Source doc §5.6: when caller passes multiple reference frames,
|
|
classifier marks reference_frame_ambiguous."""
|
|
state = preflight_question(
|
|
"Has Oceania always been at war with East Asia?",
|
|
reference_frames=("literal_geography", "orwell_1984"),
|
|
)
|
|
assert "reference_frame_ambiguous" in state.logical_statuses
|
|
|
|
|
|
# ---------------------------------------------------------------- gating
|
|
|
|
def test_master_kill_disables_preflight():
|
|
"""policy={'metacognition_enabled': False} short-circuits with
|
|
a stub QuestionState. Logical statuses tuple is empty;
|
|
preflight_result is PREFLIGHT_OK so downstream isn't blocked."""
|
|
state = preflight_question(
|
|
"Winners of all major sports?",
|
|
policy={"metacognition_enabled": False},
|
|
)
|
|
assert state.logical_statuses == ()
|
|
assert state.preflight_result == "PREFLIGHT_OK"
|
|
assert state.question_shape == "metacognition_disabled"
|
|
|
|
|
|
def test_per_detector_disable():
|
|
"""Each detector can be turned off independently. Verify the
|
|
contradiction check obeys its switch."""
|
|
state = preflight_question(
|
|
"Which unmarried spouse is Homer married to?",
|
|
policy={"metacognition_contradiction_check": False},
|
|
)
|
|
# Contradiction detector skipped → no contradictory_question status.
|
|
assert "contradictory_question" not in state.logical_statuses
|
|
assert state.contradiction_pairs == ()
|
|
|
|
|
|
def test_block_on_contradiction_opt_in():
|
|
"""By default contradictory questions surface as PARTIAL (label
|
|
only). `metacognition_block_on_contradiction=True` flips to
|
|
BLOCKED."""
|
|
state_default = preflight_question(
|
|
"Which unmarried spouse is Homer married to?",
|
|
)
|
|
state_strict = preflight_question(
|
|
"Which unmarried spouse is Homer married to?",
|
|
policy={"metacognition_block_on_contradiction": True},
|
|
)
|
|
assert state_default.preflight_result == "PREFLIGHT_PARTIAL"
|
|
assert state_strict.preflight_result == "PREFLIGHT_BLOCKED"
|
|
|
|
|
|
# ---------------------------------------------------------------- determinism
|
|
|
|
def test_question_hash_stable():
|
|
"""Same question (modulo case + whitespace) → same hash."""
|
|
a = preflight_question("Who painted the Mona Lisa?")
|
|
b = preflight_question(" who painted the mona lisa? ")
|
|
assert a.question_hash == b.question_hash
|
|
|
|
|
|
def test_preflight_policy_hash_changes_with_policy_flip():
|
|
"""Flipping a metacognition policy field bumps the policy hash —
|
|
enables governance binding via _VERIFIER_POLICY_FIELDS (Phase 3)."""
|
|
a = preflight_question(
|
|
"Winners of all major sports?",
|
|
policy={"metacognition_temporal_check": True},
|
|
)
|
|
b = preflight_question(
|
|
"Winners of all major sports?",
|
|
policy={"metacognition_temporal_check": False},
|
|
)
|
|
assert a.preflight_policy_hash != b.preflight_policy_hash
|
|
|
|
|
|
# ---------------------------------------------------------------- versioning
|
|
|
|
def test_preflight_version_pinned():
|
|
state = preflight_question("any question")
|
|
assert state.classifier_version == PREFLIGHT_VERSION
|
|
assert PREFLIGHT_VERSION == "metacognition-v0.1"
|
|
|
|
|
|
# ---------------------------------------------------------------- empty / edge
|
|
|
|
def test_empty_question_blocked():
|
|
state = preflight_question("")
|
|
assert state.preflight_result == "PREFLIGHT_BLOCKED"
|
|
assert state.question_shape == "empty"
|
|
|
|
|
|
def test_whitespace_only_question_blocked():
|
|
state = preflight_question(" \n\t ")
|
|
assert state.preflight_result == "PREFLIGHT_BLOCKED"
|
|
|
|
|
|
def test_question_state_is_serializable():
|
|
"""to_dict() must produce JSON-friendly output for run-DAG /
|
|
bench JSONL persistence."""
|
|
import json
|
|
state = preflight_question("Winners of all major sports?")
|
|
payload = state.to_dict()
|
|
# Round-trip through json.dumps; failure here means a non-
|
|
# serializable type leaked in.
|
|
json.dumps(payload, ensure_ascii=False)
|
|
|
|
|
|
# ---------------------------------------------------------------- governance binding (Phase 3)
|
|
|
|
@pytest.mark.parametrize("field", [
|
|
"metacognition_enabled",
|
|
"metacognition_temporal_check",
|
|
"metacognition_contradiction_check",
|
|
"metacognition_false_premise_check",
|
|
"metacognition_out_of_corpus_check",
|
|
"metacognition_block_on_contradiction",
|
|
])
|
|
def test_metacognition_field_is_in_verifier_policy_fields(field):
|
|
"""All six policy fields must be in _VERIFIER_POLICY_FIELDS so
|
|
flipping any of them invalidates prior cache records on lookup."""
|
|
from arborist.qa.keys import _VERIFIER_POLICY_FIELDS
|
|
assert field in _VERIFIER_POLICY_FIELDS
|
|
|
|
|
|
def test_governance_hash_changes_when_metacognition_enabled_flips():
|
|
from arborist.qa.keys import _VERIFIER_POLICY_FIELDS, verifier_policy_hash
|
|
base = dict.fromkeys(_VERIFIER_POLICY_FIELDS, "default")
|
|
base["metacognition_enabled"] = True
|
|
h_on = verifier_policy_hash(base)
|
|
base["metacognition_enabled"] = False
|
|
h_off = verifier_policy_hash(base)
|
|
assert h_on != h_off
|
|
|
|
|
|
def test_governance_hash_changes_when_block_on_contradiction_flips():
|
|
from arborist.qa.keys import _VERIFIER_POLICY_FIELDS, verifier_policy_hash
|
|
base = dict.fromkeys(_VERIFIER_POLICY_FIELDS, "default")
|
|
base["metacognition_block_on_contradiction"] = False
|
|
h_off = verifier_policy_hash(base)
|
|
base["metacognition_block_on_contradiction"] = True
|
|
h_on = verifier_policy_hash(base)
|
|
assert h_off != h_on
|
|
|
|
|
|
def test_default_policy_has_metacognition_enabled():
|
|
"""Master switch default-on per ticket #000010 §7.3 — detectors
|
|
are pure-on-question so the cost is negligible."""
|
|
from arborist.qa.runner import DEFAULT_POLICY as RUNNER_POLICY
|
|
from arborist.qa.query import DEFAULT_QUERY_POLICY
|
|
assert RUNNER_POLICY["metacognition_enabled"] is True
|
|
assert DEFAULT_QUERY_POLICY["metacognition_enabled"] is True
|
|
|
|
|
|
def test_default_policy_block_on_contradiction_off():
|
|
"""Default to label-only on contradictions. False-positive risk
|
|
not yet bench-validated; opt-in via --block-on-contradiction."""
|
|
from arborist.qa.runner import DEFAULT_POLICY as RUNNER_POLICY
|
|
from arborist.qa.query import DEFAULT_QUERY_POLICY
|
|
assert RUNNER_POLICY["metacognition_block_on_contradiction"] is False
|
|
assert DEFAULT_QUERY_POLICY["metacognition_block_on_contradiction"] is False
|
|
|
|
|
|
# ---------------------------------------------------------------- audit-line tails (Phase 4)
|
|
|
|
def _result_with_question_state(state: QuestionState) -> dict:
|
|
"""Synthetic query() result with the minimum fields needed by
|
|
`_render_warrant_tail`."""
|
|
return {
|
|
"violations": [],
|
|
"claim_cap_applied": None,
|
|
"question_state": state.to_dict(),
|
|
}
|
|
|
|
|
|
def test_tail_renders_false_premise():
|
|
from arborist.cli import _render_warrant_tail
|
|
state = preflight_question(
|
|
"When did Mr. Burns become Homer's biological father?"
|
|
)
|
|
tail = _render_warrant_tail(_result_with_question_state(state))
|
|
assert "false premise" in tail
|
|
|
|
|
|
def test_tail_renders_contradictory():
|
|
from arborist.cli import _render_warrant_tail
|
|
state = preflight_question("Which unmarried spouse is Homer married to?")
|
|
tail = _render_warrant_tail(_result_with_question_state(state))
|
|
assert "contradictory" in tail
|
|
|
|
|
|
def test_tail_renders_stale_risk():
|
|
from arborist.cli import _render_warrant_tail
|
|
state = preflight_question("Who is the current CEO of OpenAI?")
|
|
tail = _render_warrant_tail(_result_with_question_state(state))
|
|
assert "stale risk" in tail
|
|
|
|
|
|
def test_tail_renders_out_of_corpus():
|
|
from arborist.cli import _render_warrant_tail
|
|
state = preflight_question("What does my uploaded contract say?")
|
|
tail = _render_warrant_tail(_result_with_question_state(state))
|
|
assert "out of corpus" in tail
|
|
|
|
|
|
def test_tail_omits_metacog_when_well_formed():
|
|
from arborist.cli import _render_warrant_tail
|
|
state = preflight_question("what is the capital of france?")
|
|
tail = _render_warrant_tail(_result_with_question_state(state))
|
|
# well_formed should NOT produce any metacog tail tokens.
|
|
assert "false premise" not in tail
|
|
assert "contradictory" not in tail
|
|
assert "stale risk" not in tail
|
|
assert "out of corpus" not in tail
|
|
|
|
|
|
def test_tail_combines_metacog_with_existing_kinds():
|
|
"""Multiple tails compose: a TITLE_MISMATCH from the verifier
|
|
plus a stale_risk from the preflight should both surface."""
|
|
from arborist.cli import _render_warrant_tail
|
|
state = preflight_question("Who is the current CEO of OpenAI?")
|
|
result = _result_with_question_state(state)
|
|
result["violations"] = [{"kind": "TITLE_MISMATCH"}]
|
|
tail = _render_warrant_tail(result)
|
|
assert "title mismatch" in tail
|
|
assert "stale risk" in tail
|