183 lines
6.4 KiB
Python
183 lines
6.4 KiB
Python
|
|
"""Layer and stance conformance.
|
||
|
|
|
||
|
|
GH-DEC-2026-012 confirmed this repository is PEP-shaped and told it to build to
|
||
|
|
v0.8 obligation 3 rather than migrate to it later. These tests pin the parts of
|
||
|
|
that obligation a test can actually hold:
|
||
|
|
|
||
|
|
- the published map equals the shipped map (obligation 3, a MUST);
|
||
|
|
- the axis is enumerated, not defaulted;
|
||
|
|
- ``unknown`` resolves to ``fail_closed``;
|
||
|
|
- an absent scope is distinguishable in the record from an unknown one;
|
||
|
|
- the inherited GH-DEC-2026-010 attributability gap is declared open rather
|
||
|
|
than described as satisfied.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import pathlib
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
yaml = pytest.importorskip("yaml")
|
||
|
|
|
||
|
|
from informed_decision.stance import AXIS, AXIS_VALUES, STANCE, BindingLevelState, resolve
|
||
|
|
|
||
|
|
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
||
|
|
|
||
|
|
|
||
|
|
def load(name: str) -> dict:
|
||
|
|
return yaml.safe_load((ROOT / name).read_text(encoding="utf-8"))
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.fixture(scope="module")
|
||
|
|
def stance_doc() -> dict:
|
||
|
|
return load("pep-stance.yaml")
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.fixture(scope="module")
|
||
|
|
def layer_doc() -> dict:
|
||
|
|
return load("layer.yaml")
|
||
|
|
|
||
|
|
|
||
|
|
# --------------------------------------------------------------------------
|
||
|
|
# Obligation 3 — published equals shipped. This is the MUST.
|
||
|
|
# --------------------------------------------------------------------------
|
||
|
|
|
||
|
|
|
||
|
|
def test_published_stance_equals_shipped_stance(stance_doc):
|
||
|
|
assert stance_doc["stance"] == STANCE, (
|
||
|
|
"pep-stance.yaml has drifted from informed_decision/stance.py. "
|
||
|
|
"A published map that may differ from the code is worse than none."
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def test_published_axis_equals_shipped_axis(stance_doc):
|
||
|
|
assert stance_doc["axis"] == AXIS
|
||
|
|
|
||
|
|
|
||
|
|
# --------------------------------------------------------------------------
|
||
|
|
# Obligation 3 — totality by enumeration, no catch-all, no implicit default.
|
||
|
|
# --------------------------------------------------------------------------
|
||
|
|
|
||
|
|
|
||
|
|
def test_every_axis_value_has_an_explicit_stance():
|
||
|
|
for value in AXIS_VALUES:
|
||
|
|
assert value in STANCE, f"{value} has no declared stance"
|
||
|
|
|
||
|
|
|
||
|
|
def test_stance_map_has_no_entries_beyond_the_axis_and_the_two_outcomes():
|
||
|
|
allowed = set(AXIS_VALUES) | {"unknown", "absent"}
|
||
|
|
assert set(STANCE) == allowed
|
||
|
|
|
||
|
|
|
||
|
|
def test_unknown_resolves_to_fail_closed(stance_doc):
|
||
|
|
"""v0.8 obligation 3 makes this a MUST; v0.7 permitted fail_open."""
|
||
|
|
assert STANCE["unknown"] == "fail_closed"
|
||
|
|
assert stance_doc["stance"]["unknown"] == "fail_closed"
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.parametrize("value", sorted(STANCE))
|
||
|
|
def test_no_stance_is_permissive(value):
|
||
|
|
"""Not required by the standard — required by this repository.
|
||
|
|
|
||
|
|
Binding an identity without an authorization decision is the failure this
|
||
|
|
surface exists to prevent, so there is no level at which proceeding is the
|
||
|
|
safer error. If this test is ever relaxed, the reasoning in pep-stance.yaml
|
||
|
|
must be rewritten first.
|
||
|
|
"""
|
||
|
|
assert STANCE[value] == "fail_closed"
|
||
|
|
|
||
|
|
|
||
|
|
# --------------------------------------------------------------------------
|
||
|
|
# Obligation 3 — absent must be distinguishable from unknown IN THE RECORD,
|
||
|
|
# even though both resolve to the same stance.
|
||
|
|
# --------------------------------------------------------------------------
|
||
|
|
|
||
|
|
|
||
|
|
def test_absent_and_unknown_resolve_the_same_but_record_differently():
|
||
|
|
absent_stance, absent_state = resolve(None)
|
||
|
|
unknown_stance, unknown_state = resolve("notalevel")
|
||
|
|
assert absent_stance == unknown_stance == "fail_closed"
|
||
|
|
assert absent_state is BindingLevelState.ABSENT
|
||
|
|
assert unknown_state is BindingLevelState.UNKNOWN
|
||
|
|
assert absent_state != unknown_state
|
||
|
|
|
||
|
|
|
||
|
|
def test_empty_string_is_absent_not_unknown():
|
||
|
|
assert resolve("")[1] is BindingLevelState.ABSENT
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.parametrize("value", AXIS_VALUES)
|
||
|
|
def test_known_axis_values_record_as_present(value):
|
||
|
|
stance, state = resolve(value)
|
||
|
|
assert state is BindingLevelState.PRESENT
|
||
|
|
assert stance == "fail_closed"
|
||
|
|
|
||
|
|
|
||
|
|
def test_published_map_declares_the_two_states_distinguishable(stance_doc):
|
||
|
|
absence = stance_doc["scope_absence"]
|
||
|
|
assert absence["distinguishable"] is True
|
||
|
|
assert absence["recorded_as"]["absent"] != absence["recorded_as"]["unknown"]
|
||
|
|
|
||
|
|
|
||
|
|
# --------------------------------------------------------------------------
|
||
|
|
# Inherited gap — GH-DEC-2026-010. Must be declared open, not glossed.
|
||
|
|
# --------------------------------------------------------------------------
|
||
|
|
|
||
|
|
|
||
|
|
def test_attributability_gap_is_declared_open_in_the_stance(stance_doc):
|
||
|
|
gap = stance_doc["inherited_gap"]
|
||
|
|
assert gap["decision_attributable_today"] is False
|
||
|
|
assert gap["tracked_by"] == "FLEX-WP-0024"
|
||
|
|
|
||
|
|
|
||
|
|
def test_attributability_gap_is_declared_open_in_the_layer(layer_doc):
|
||
|
|
gaps = {g["id"]: g for g in layer_doc["inherited_gaps"]}
|
||
|
|
gap = gaps["GH-DEC-2026-010-attributability"]
|
||
|
|
assert gap["status"] == "open"
|
||
|
|
|
||
|
|
|
||
|
|
def test_stance_records_whether_the_decision_was_attributable(stance_doc):
|
||
|
|
assert "decision_attributable" in stance_doc["on_apply"]["recorded_fields"]
|
||
|
|
|
||
|
|
|
||
|
|
# --------------------------------------------------------------------------
|
||
|
|
# R1 / R2 / R3 — the ruling's limits are declared, not merely remembered.
|
||
|
|
# --------------------------------------------------------------------------
|
||
|
|
|
||
|
|
|
||
|
|
def test_layer_declares_pep_shaped_and_no_decision_surface(layer_doc):
|
||
|
|
assert layer_doc["role"] == "pep-shaped"
|
||
|
|
assert layer_doc["decision_surfaces_exposed"] == "none"
|
||
|
|
|
||
|
|
|
||
|
|
def test_presentation_claim_carries_all_three_limits(layer_doc):
|
||
|
|
limits = {limit["id"] for limit in layer_doc["presentation_claim"]["limits"]}
|
||
|
|
assert limits == {
|
||
|
|
"L1-presentation-only",
|
||
|
|
"L2-not-an-input",
|
||
|
|
"L3-independent-evidence-path",
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def test_binding_digest_relationship_is_co_reference_not_nesting(layer_doc):
|
||
|
|
rel = layer_doc["binding_digest_relationship"]
|
||
|
|
assert rel["linkage"] == "co-reference"
|
||
|
|
assert rel["substitutable"] is False
|
||
|
|
assert "nesting_forbidden" in rel
|
||
|
|
|
||
|
|
|
||
|
|
def test_residual_is_declared_not_closed(layer_doc):
|
||
|
|
assert layer_doc["evidence"]["residual_closed"] is False
|
||
|
|
|
||
|
|
|
||
|
|
# --------------------------------------------------------------------------
|
||
|
|
# §13.1 / GH-DEC-2026-011 — a dated coverage figure beside the stance.
|
||
|
|
# --------------------------------------------------------------------------
|
||
|
|
|
||
|
|
|
||
|
|
def test_classification_coverage_is_dated_and_complete_against_the_axis(stance_doc):
|
||
|
|
cov = stance_doc["classification_coverage"]
|
||
|
|
assert cov["as_of"]
|
||
|
|
assert cov["axis_values_enumerated"] == cov["axis_values_in_schema"] == len(AXIS_VALUES)
|