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.
94 lines
3.5 KiB
Python
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
|