net-kingdom/sso-mfa/k8s/keycape/test_compose_1_2_descriptors.py
tegwick 001a38ca2a
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
NK-WP-0048-T16 r5: regenerate the 1.3 map and both compositions; tests follow the r5 lanes
1.3 composes completely with the new lanes and an all-human actor set. 1.2 is
correctly refused on the three moved lanes and stays plan-only. The 1.2
admission test's unique checks move into test_admit_1_3_type.py. Tests run
against ops-warden 117fec9.

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
2026-10-17 12:01:18 +02:00

162 lines
7.5 KiB
Python

"""Synthetic positive and refusal fixtures for the T30 1.2 descriptor composer."""
from dataclasses import replace
import importlib.util
from pathlib import Path
import tempfile
import unittest
from warden.case_run import Descriptor
from warden.case_receipt_state import Refused
ROOT = Path(__file__).parent
spec = importlib.util.spec_from_file_location('compose_12', ROOT / 'compose-1.2-descriptors.py')
c = importlib.util.module_from_spec(spec)
spec.loader.exec_module(c)
CHECKER = 'sha256:' + 'b' * 64
PARAMS = {'consumer_owner_repo': 'railiance-apps'}
def synthetic(type_spec):
"""One kernel-valid descriptor per 1.2 step, owner/lane taken from the type."""
out = []
for entry in type_spec['follow_through']:
out.append((entry['id'], Descriptor(
'warden.case.execution.v1', entry['id'], entry['owner'].format(**PARAMS),
f"net-kingdom/{entry['id']}@v1", 'sha256:' + 'a' * 64, 'human',
entry.get('lane', 'proposed-lane'), 'client_id', 'change', ('created',),
False, ('parent-session',))))
return out
class ComposeTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.spec, cls.canonical = c.load_types()
cls.good = synthetic(cls.spec)
def compose(self, candidates, canonical=None):
return c.compose(self.spec, candidates, PARAMS, canonical or self.canonical)
def test_complete_seven_step_map(self):
composed = self.compose(self.good)
self.assertEqual(list(composed), [e['id'] for e in self.spec['follow_through']])
self.assertEqual(len(composed), 7)
self.assertTrue(all(v.startswith('sha256:') for v in composed.values()))
def test_partial_map_refused(self):
with self.assertRaisesRegex(Refused, 'partial'):
self.compose(self.good[:-1])
def test_duplicate_step_refused(self):
with self.assertRaisesRegex(Refused, 'duplicate'):
self.compose(self.good + [self.good[0]])
def test_unknown_step_refused(self):
bogus = ('rotate-secret', replace(self.good[0][1], step='rotate-secret'))
with self.assertRaisesRegex(Refused, 'unknown'):
self.compose(self.good[:-1] + [bogus])
def test_step_name_mismatch_refused(self):
bad = [(s, replace(d, step='bind-env') if s == 'ensure-groups' else d)
for s, d in self.good]
with self.assertRaisesRegex(Refused, 'names step'):
self.compose(bad)
def test_owner_mismatch_refused(self):
bad = [(s, replace(d, owner='apps') if s == 'hand-over' else d) for s, d in self.good]
with self.assertRaisesRegex(Refused, 'owner'):
self.compose(bad)
def test_lane_mismatch_refused(self):
bad = [(s, replace(d, lane='other-lane') if s == 'register-client' else d)
for s, d in self.good]
with self.assertRaisesRegex(Refused, 'lane'):
self.compose(bad)
def test_kernel_invalid_descriptor_refused(self):
bad = [(s, replace(d, revocation_scopes=()) if s == 'ensure-groups' else d)
for s, d in self.good]
with self.assertRaisesRegex(Refused, 'kernel'):
self.compose(bad)
def test_canonical_change_refused(self):
with self.assertRaisesRegex(Refused, '1.1'):
self.compose(self.good, canonical='sha256:' + '0' * 64)
def test_only_explicit_12_root(self):
with self.assertRaisesRegex(Refused, 'explicit 1.2'):
c.load_types(version_roots={'1.1': c.VERSION_ROOTS['1.2']})
with self.assertRaisesRegex(Refused, 'explicit 1.2'):
c.load_types(version_roots={})
def test_version_selection(self):
for bad in ('1.1', '1.4', '', None):
with self.assertRaises(Refused):
c.load_types(version=bad)
with self.assertRaises(Refused): # a 1.2 root cannot be selected as 1.3
c.load_types(version='1.3', version_roots={'1.2': c.VERSION_ROOTS['1.2']})
with tempfile.TemporaryDirectory() as missing, self.assertRaises(Refused):
c.load_types(version='1.3', version_roots={'1.3': Path(missing) / 'absent'})
type_spec, _ = c.load_types(version='1.3') # the 1.3 successor candidate exists now
self.assertEqual(type_spec['version'], '1.3')
with self.assertRaises(SystemExit):
c.main(['--check-config-digest', 'x', '--consumer-owner-repo', 'y', '--version', '1.1'])
def test_canonical_default_bytes_untouched(self):
path = c.DEFAULT_ROOT / c.TYPE_ID / 'type.yaml'
before = path.read_bytes()
c.artifact(CHECKER, PARAMS)
self.assertEqual(path.read_bytes(), before)
def test_1_2_composition_is_refused_on_the_r5_lanes(self):
# r5 (OW-1.3-C1) moved three net-kingdom steps to keycape-attended-case-change,
# which the acknowledged 1.2 type does not declare. 1.2 stays plan-only.
doc = c.artifact(CHECKER, PARAMS)
self.assertFalse(doc['complete_executable_descriptor_set'])
self.assertIsNone(doc['execution_descriptors'])
findings = {r['step']: r['findings'] for r in doc['steps'] if r['findings']}
self.assertEqual(findings, {step: ["lane 'keycape-attended-case-change' != type lane "
"'key-cape-oidc-login'"]
for step in ('ensure-groups', 'bind-env', 'register-client')})
def test_current_adapters_compose_for_1_3(self):
doc = c.artifact(CHECKER, PARAMS, version='1.3')
self.assertTrue(doc['complete_executable_descriptor_set'])
self.assertEqual(len(doc['execution_descriptors']), 7)
self.assertIsNone(doc['refusal'])
self.assertFalse(doc['admitted'] or doc['owner_review'] or doc['dispatch_permitted'])
self.assertEqual({r['step'] for r in doc['steps'] if r['findings']}, set())
by_step = {r['step']: r['descriptor'] for r in doc['steps']}
self.assertEqual({s: d['actor'] for s, d in by_step.items()},
{s: 'human' for s in by_step}) # RAPPS-1.3-C1: no agent step remains
self.assertEqual({s: d['lane'] for s, d in by_step.items()}, {
'ensure-groups': 'keycape-attended-case-change',
'bind-env': 'keycape-attended-case-change',
'register-client': 'keycape-attended-case-change',
'mint-secret': 'keycape-client-secret-generate',
'deliver-secret': 'platform-secret-delivery',
'deliver-consumer-secret': 'consumer-owner-deployment',
'hand-over': 'apps-consumer-handover'})
self.assertEqual(by_step['hand-over']['owner'], PARAMS['consumer_owner_repo'])
self.assertFalse(any(r['owner_confirmation_required'] for r in doc['steps']))
def test_committed_1_3_artifact_matches_regeneration(self):
import json
committed = json.loads((ROOT / 'descriptor-composition-1.3.candidate.json').read_text())
regenerated = c.artifact(committed['check_config_digest'], committed['params'], version='1.3')
self.assertEqual(json.loads(json.dumps(regenerated)), committed)
def test_committed_artifact_matches_regeneration(self):
import json
committed = json.loads((ROOT / 'descriptor-composition-1.2.candidate.json').read_text())
regenerated = c.artifact(committed['check_config_digest'], committed['params'])
self.assertEqual(json.loads(json.dumps(regenerated)), committed)
def test_adapters_need_bounded_inputs(self):
with self.assertRaises(Refused):
c.adapter_descriptors('not-a-digest', 'railiance-apps')
if __name__ == '__main__':
unittest.main()