test(case): integrate the real net-kingdom 1.3 candidate with this kernel and catalog
Only lane promotion and the 1.3 parameter pin are simulated, in memory. All seven descriptors are admitted; draft lanes, a missing pin and a proposed type each refuse. The type check leaves only the two railiance-apps session_lane LANE-KIND findings once the drafts are promoted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 903097@bnt-lap001 Assistant-Session: aa320c8f-c3fa-46e7-8541-0b58e1e45d23
This commit is contained in:
parent
0a1e9907bb
commit
5abde81777
1 changed files with 143 additions and 0 deletions
143
tests/test_case_1_3_integration.py
Normal file
143
tests/test_case_1_3_integration.py
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
"""Integration: the real net-kingdom 1.3 candidate against this kernel and catalog.
|
||||
|
||||
Reads the actual 1.3 type, its forgejo example and the committed composition
|
||||
artifact from net-kingdom, plus this branch's real routing catalog. Only two things
|
||||
are simulated, both in memory: draft lanes flipped to active (promotion needs owner
|
||||
confirmation) and a 1.3 PARAMETER_PINS entry (PIN-1 waits for the founder's
|
||||
acknowledgment). Trust is fixture-only, and nothing is dispatched.
|
||||
"""
|
||||
from dataclasses import asdict
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from warden import case_inputs as inputs
|
||||
from warden.case_receipt_state import Refused
|
||||
from warden.case_run import Adapter, Descriptor, Executor, Journal, digest
|
||||
from warden.case_type import check_follow_through
|
||||
from warden.routing.catalog import load_catalog
|
||||
|
||||
NK = Path(os.environ.get("WARDEN_TEST_NETKINGDOM_ROOT",
|
||||
Path(__file__).resolve().parents[2] / "net-kingdom"))
|
||||
TYPE_DIR = NK / "security-case-type-revisions/1.3/oidc-browser-relying-party"
|
||||
ARTIFACT = NK / "sso-mfa/k8s/keycape/descriptor-composition-1.3.candidate.json"
|
||||
CATALOG = Path(__file__).resolve().parents[1] / "registry/routing/catalog.yaml"
|
||||
|
||||
pytestmark = pytest.mark.skipif(not (TYPE_DIR / "type.yaml").is_file() or not ARTIFACT.is_file(),
|
||||
reason="net-kingdom 1.3 candidate not available")
|
||||
|
||||
DRAFT_LANES = {"keycape-attended-case-change", "platform-secret-delivery",
|
||||
"consumer-owner-deployment", "apps-consumer-handover"}
|
||||
|
||||
|
||||
def encode(desc): # net-kingdom compose-1.2-descriptors.py encode()
|
||||
return json.dumps(asdict(desc), sort_keys=True, separators=(",", ":")).encode()
|
||||
|
||||
|
||||
def type_doc(status="acknowledged"):
|
||||
doc = yaml.safe_load((TYPE_DIR / "type.yaml").read_bytes())
|
||||
doc["spec"]["status"] = status
|
||||
return doc
|
||||
|
||||
|
||||
def catalog_bytes(promote):
|
||||
raw = yaml.safe_load(CATALOG.read_bytes())
|
||||
if promote:
|
||||
for entry in raw["entries"]:
|
||||
if entry["id"] in DRAFT_LANES and entry.get("status") == "draft":
|
||||
entry["status"] = "active"
|
||||
return yaml.safe_dump(raw).encode()
|
||||
|
||||
|
||||
def descriptors():
|
||||
art = json.loads(ARTIFACT.read_text())
|
||||
out = {}
|
||||
for row in art["steps"]:
|
||||
d = dict(row["descriptor"])
|
||||
for key in ("allowed_created", "revocation_scopes"):
|
||||
d[key] = tuple(d[key])
|
||||
out[row["step"]] = Descriptor(**d)
|
||||
return out
|
||||
|
||||
|
||||
def pin_13():
|
||||
params = type_doc()["spec"]["parameters"]
|
||||
fp = hashlib.sha256(json.dumps(params, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
|
||||
return mock.patch.dict(inputs.PARAMETER_PINS, {"1.3": fp})
|
||||
|
||||
|
||||
def unused(*_):
|
||||
raise AssertionError("admission must not dispatch")
|
||||
|
||||
|
||||
def admit(tmp_path, desc, *, promote=True, pinned=True, status="acknowledged"):
|
||||
case = yaml.safe_load((TYPE_DIR / "examples/forgejo.yaml").read_bytes())
|
||||
case["spec"].update(status="materialized", acceptance={
|
||||
"at": "2026-10-17", "record": "fixture-only acceptance, not an owner record",
|
||||
"by": [{"name": "fixture", "role": "owner"}, {"name": "fixture", "role": "founder"}]})
|
||||
adapter = Adapter(desc.adapter, desc.adapter_source, unused, unused)
|
||||
executor = Executor(Journal(tmp_path / desc.step), {adapter.reference: adapter},
|
||||
lambda purpose, source, evidence: True,
|
||||
browser_inputs=inputs.BrowserInputs(NK))
|
||||
with mock.patch.dict(os.environ), (pin_13() if pinned else mock.patch.dict({})):
|
||||
os.environ.pop("WARDEN_AGENT_ID", None)
|
||||
return executor.admit(yaml.safe_dump(case).encode(), yaml.safe_dump(type_doc(status)).encode(),
|
||||
encode(desc), catalog_bytes(promote), principal="founder",
|
||||
actor=desc.actor, lane=desc.lane,
|
||||
acceptance_evidence="e-acceptance", materialization_evidence="e-mason",
|
||||
descriptor_evidence="e-descriptor", session_evidence="e-session")
|
||||
|
||||
|
||||
def test_artifact_descriptors_are_the_type_map():
|
||||
descs = descriptors()
|
||||
assert len(descs) == 7
|
||||
assert {s: digest(encode(d)) for s, d in descs.items()} == type_doc()["spec"]["execution_descriptors"]
|
||||
|
||||
|
||||
def test_all_seven_admitted_once_promoted_and_pinned(tmp_path):
|
||||
for step, desc in descriptors().items():
|
||||
admitted, invocation, _ = admit(tmp_path, desc)
|
||||
assert admitted == desc
|
||||
assert (invocation.step, invocation.actor, invocation.target) == (step, "human", "forgejo")
|
||||
|
||||
|
||||
def test_draft_lane_steps_refused_until_promoted(tmp_path):
|
||||
for step, desc in descriptors().items():
|
||||
if desc.lane in DRAFT_LANES:
|
||||
with pytest.raises(Refused, match="descriptor lane is not active in the catalog"):
|
||||
admit(tmp_path, desc, promote=False)
|
||||
else:
|
||||
admit(tmp_path, desc, promote=False) # mint-secret's lane is already active
|
||||
|
||||
|
||||
def test_unpinned_1_3_refused_by_browser_validation(tmp_path):
|
||||
with pytest.raises(Refused, match="failed exact-version validation"):
|
||||
admit(tmp_path, descriptors()["mint-secret"], pinned=False)
|
||||
|
||||
|
||||
def test_proposed_type_refused(tmp_path):
|
||||
with pytest.raises(Refused, match="type is not acknowledged"):
|
||||
admit(tmp_path, descriptors()["mint-secret"], status="proposed")
|
||||
|
||||
|
||||
def test_type_check_findings_against_the_real_catalog(tmp_path):
|
||||
def findings(promote):
|
||||
path = tmp_path / f"catalog-{promote}.yaml"
|
||||
path.write_bytes(catalog_bytes(promote))
|
||||
report = check_follow_through(type_doc("proposed"), load_catalog(path))
|
||||
return {(f.code, f.where) for f in report.findings}
|
||||
|
||||
draft = findings(False)
|
||||
assert {code for code, _ in draft} == {"LANE-UNREVIEWED"}
|
||||
assert len(draft) == 6 # six steps use the four draft lanes; mint-secret's lane is active
|
||||
|
||||
promoted = findings(True)
|
||||
# Only the two railiance-apps lanes remain: their session_lane awaits owner confirmation.
|
||||
assert {code for code, _ in promoted} == {"LANE-KIND"}
|
||||
assert len(promoted) == 2
|
||||
assert all("deliver-consumer-secret" in where or "hand-over" in where for _, where in promoted)
|
||||
Loading…
Add table
Add a link
Reference in a new issue