Gate House ruled all three questions within a day, attributing the speed to the request being filed before the architecture with candidate answers and their costs. R1 PEP-shaped, confirmed as proposed. The ruling settles the shape; the layer stays ours to declare, so layer.yaml is written in this repository's voice rather than transcribed from the reply. R2 yes to a presentation claim, no second catalog row, under three limits now declared in layer.yaml and tested. Limit 2 — the claim must never be an input to the decision it presents for — is load-bearing: our self-dealing argument was accepted because it holds, not despite it. Limit 3 drives architecture, since here the actor being audited and the evidence source are the same component. R3 (b) with the authority rule: binding digest authoritative for what the request is, view_hash only for what was shown, neither substitutable, and a disagreement between them is a finding against the presenting surface rather than a fact about the request. Linkage is co-reference; nesting was refused because it reproduces the GH-DEC-2026-008 hash cycle. Built to v0.8 obligation 3 rather than migrating later: axis enumerated, unknown resolves to fail_closed, absent distinguishable from unknown in the record, and published-equals-shipped asserted by test rather than claimed. Every stance is fail_closed, which is a conclusion not a shortcut — ops-warden can justify fail_open on a continuity argument that does not exist here. GH-DEC-2026-010 inherited as a declared gap in four documents: a decision cannot today be proven to have come from access-engine. The decision path must not be described as validated while FLEX-WP-0024 is open. 46 tests pass. T05 and T07 unblocked. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V3W1dQG7GFFM9d94jFx7iR Assistant: claude-code Assistant-Model: opus Assistant-Process: 1565372@bnt-lap001 Assistant-Session: 16bb2f25-b34c-49ef-8e94-5fec3567a568
182 lines
6.4 KiB
Python
182 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)
|