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
162 lines
7.5 KiB
Python
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()
|