From 5abde817775d6304e3cf5a46948717a1606981bf Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 17 Oct 2026 12:17:48 +0200 Subject: [PATCH] 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 Assistant: claude-code Assistant-Model: opus Assistant-Process: 903097@bnt-lap001 Assistant-Session: aa320c8f-c3fa-46e7-8541-0b58e1e45d23 --- tests/test_case_1_3_integration.py | 143 +++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 tests/test_case_1_3_integration.py diff --git a/tests/test_case_1_3_integration.py b/tests/test_case_1_3_integration.py new file mode 100644 index 0000000..342bdda --- /dev/null +++ b/tests/test_case_1_3_integration.py @@ -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)