core/tests/test_disclosure_claim.py
Shay 8811917280 feat(epistemic-disclosure): P0-2 — the DisclosureClaim axis
A served response carries two ORTHOGONAL governance properties: ReachLevel (how
far past grounded fact it reaches) and DisclosureClaim (the epistemic claim it
makes about its own truth status). "verified" is NOT a reach level — it is a
proof-state claim. Keeping the axes separate is the locked Stage-2 decision (no
ReachLevel.VERIFIED; a proven answer never inherits an [approximate] surface).

core/epistemic_disclosure/disclosure_claim.py:
- DisclosureClaim(str, Enum): none | verified | approximate | proposal_only.
  Default = none. str-valued for stable serialization (EpistemicState convention).
- Discipline "no claim without a producer": every member has a real (APPROXIMATE
  = cognition Step E; PROPOSAL_ONLY = teaching/proposals) or imminent (VERIFIED =
  Phase 1 target) emitter. PROVEN and ESTIMATED are intentionally ABSENT — nothing
  emits them; ESTIMATED is a future split of APPROXIMATE, added only when a real
  estimator producer exists (docs-note only).

Also folds the P0-1 review nit: _KIND_TO_STATE / _ACTION_TO_TERMINAL tightened
from dict[str, ...] to their Literal key types.

9 tests, non-vacuous: default is NONE; verified is not a ReachLevel (structural);
claim axis disjoint from ResolutionAction; PROVEN and ESTIMATED absent; exactly the
approved four; str-enum serialization; module imports nothing (off-serving).

No bus behaviour, no ServedDisposition mapping (that is P0-3). Off-serving; smoke
green (90 passed) locally.
2026-06-08 15:48:20 -07:00

94 lines
3.5 KiB
Python

"""P0-2 — tests for the DisclosureClaim axis.
Each test guards a specific confusion: that ``verified`` is a reach level, that the
claim axis is entangled with the resolution-action axis, that a value with no
producer (``PROVEN``) crept in, or that the default is anything but ``NONE``. None
passes under the confusion it is written to catch.
"""
from __future__ import annotations
import ast
import pathlib
from enum import Enum
from typing import get_args
import core.epistemic_disclosure.disclosure_claim as claim_module
from core.epistemic_disclosure.disclosure_claim import (
DEFAULT_DISCLOSURE_CLAIM,
DisclosureClaim,
)
from core.epistemic_disclosure.limitation import ResolutionAction
from core.response_governance.policy import ReachLevel
def test_default_claim_is_none():
assert DEFAULT_DISCLOSURE_CLAIM is DisclosureClaim.NONE
def test_verified_is_not_a_reach_level():
"""The locked Stage-2 decision: VERIFIED is an epistemic claim, never a reach."""
assert not any(level.name == "VERIFIED" for level in ReachLevel)
assert "verified" not in {level.value for level in ReachLevel}
assert not isinstance(DisclosureClaim.VERIFIED, ReachLevel)
assert DisclosureClaim is not ReachLevel
def test_disclosure_claim_independent_of_resolution_action():
"""Orthogonal axes: disjoint value spaces (knowing the action doesn't fix the claim)."""
claim_values = {c.value for c in DisclosureClaim}
action_values = set(get_args(ResolutionAction))
assert claim_values.isdisjoint(action_values)
def test_no_proven_claim_without_a_producer():
"""Discipline: the spine enforces on itself what it enforces on answers."""
assert not hasattr(DisclosureClaim, "PROVEN")
def test_members_are_exactly_the_approved_four():
# Four claims, each with a real or imminent producer. ESTIMATED and PROVEN are
# intentionally absent (no producer → no declared label).
assert {c.name for c in DisclosureClaim} == {
"NONE",
"VERIFIED",
"APPROXIMATE",
"PROPOSAL_ONLY",
}
def test_estimated_absent_no_claim_without_a_producer():
# ESTIMATED is a future split of APPROXIMATE; not declared until a producer exists.
assert not hasattr(DisclosureClaim, "ESTIMATED")
def test_values_serialize_stably_as_snake_case():
for c in DisclosureClaim:
assert c.value == c.value.lower()
assert " " not in c.value
assert "-" not in c.value
def test_is_str_enum_for_stable_serialization():
assert issubclass(DisclosureClaim, str)
assert issubclass(DisclosureClaim, Enum)
# str-valued: a member compares/serializes as its value
assert DisclosureClaim.VERIFIED == "verified"
def test_module_is_off_serving_and_dependency_free():
"""P0-2 is the bare axis: it imports nothing, so it trivially cannot touch serving."""
module_path = claim_module.__file__
assert module_path is not None
src = pathlib.Path(module_path).read_text()
tree = ast.parse(src)
from_imports = [n.module or "" for n in ast.walk(tree) if isinstance(n, ast.ImportFrom)]
plain_imports = [a.name for n in ast.walk(tree) if isinstance(n, ast.Import) for a in n.names]
all_modules = from_imports + plain_imports
forbidden = [
m for m in all_modules
if "generate.derivation" in m or "reliability_gate" in m or m.endswith("verify")
]
assert forbidden == []
# the only import is the stdlib enum — no project coupling at all (P0-2 is the bare axis)
assert all(m in {"", "__future__", "enum"} for m in all_modules), all_modules