From 9a3f6dd892caae16106bbe596391963cfbd2dbf0 Mon Sep 17 00:00:00 2001 From: AOS Architecture Agent Date: Sat, 29 Aug 2026 15:19:01 -0400 Subject: [PATCH] Correct D-003 provenance import closure --- decisions/d003_manifest_v2.py | 2 +- decisions/d003_v3_v2.py | 2 +- decisions/test_d003_manifest_v2.py | 16 ++++++++++++++++ decisions/test_d003_v3_v2.py | 21 ++++++++++++++++++++- 4 files changed, 38 insertions(+), 3 deletions(-) diff --git a/decisions/d003_manifest_v2.py b/decisions/d003_manifest_v2.py index 64e2d7833d..d1c8065a2b 100644 --- a/decisions/d003_manifest_v2.py +++ b/decisions/d003_manifest_v2.py @@ -710,7 +710,7 @@ def validate_o_value(o,m,manifest_raw): return True def validate_o_bytes(raw,m,manifest_raw): o=parse_bytes(raw);validate_o_value(o,m,manifest_raw);return o -STDLIB_IMPORTS={"ast","base64","copy","hashlib","json","pathlib","re","sys","unicodedata"} +STDLIB_IMPORTS={"__future__","ast","base64","binascii","copy","hashlib","json","pathlib","re","sys","typing","unicodedata"} def derive_inventory(files,kind): if not isinstance(files,dict) or not files:raise ManifestError(kind+" byte map missing") out=[] diff --git a/decisions/d003_v3_v2.py b/decisions/d003_v3_v2.py index b6c8fef7fe..b6cf28cb8f 100644 --- a/decisions/d003_v3_v2.py +++ b/decisions/d003_v3_v2.py @@ -16,7 +16,7 @@ E_GATES = ["v3_byte_provenance","pairwise_inventory_overlap","exact_typed_inputs V3_INPUTS = [("M","MANIFEST"),("O1","SEMANTIC_OUTPUT"),("O2","SEMANTIC_OUTPUT"),("I1","IDENTITY_ENVELOPE"),("I2","IDENTITY_ENVELOPE")] E_INPUTS = V3_INPUTS + [("V3","VERIFIER_REPORT")] ALLOWED_SHARE = {"decisions/D-003-PACKET-v2.md","decisions/d003-capability-manifest-v2.json"} -STDLIB = {"__future__","ast","base64","copy","hashlib","json","re","unicodedata","pathlib","typing"} +STDLIB = {"__future__","ast","base64","binascii","copy","hashlib","json","re","unicodedata","pathlib","typing"} EMPTY_DELTA={"add":{},"delete":{},"set_add":{},"update":{}} FAULTS=[["malformed"],["bad_witness"],["revoked","moved","replay"],["replay","wrong_audience"],["wrong_audience","wrong_owner"],["wrong_owner","missing_activation"],["missing_activation"],["wrong_commitment","wrong_world"],["wrong_world","wrong_target"],["wrong_target","wrong_right"],["wrong_right","wrong_generation"],["wrong_generation","expired"],["expired"],[]] FAULT_STATUSES=["DENY_MALFORMED","DENY_BAD_WITNESS","DENY_REVOKED","DENY_REPLAY","DENY_AUDIENCE","DENY_NOT_OWNER","DENY_ACTIVATION","DENY_COMMITMENT","DENY_WORLD","DENY_TARGET","DENY_RIGHT","DENY_GENERATION","DENY_CONSTRAINT","ALLOW"] diff --git a/decisions/test_d003_manifest_v2.py b/decisions/test_d003_manifest_v2.py index f051bf9628..de552acc34 100644 --- a/decisions/test_d003_manifest_v2.py +++ b/decisions/test_d003_manifest_v2.py @@ -247,6 +247,22 @@ class Provenance(unittest.TestCase): with self.assertRaisesRegex(d.ManifestError,'undeclared import'):d.derive_import_closure(q['source'],q['dependency'],'X') q=prov('X');q['dependency']['X/extra.py']=b'X=1\n' with self.assertRaisesRegex(d.ManifestError,'dependency closure'):d.derive_import_closure(q['source'],q['dependency'],'X') + def test_real_r2_source_only_with_packet_and_manifest_locks(self): + root='R2';source={root+'/d003_r2_v2.py':(HERE/'d003_r2_v2.py').read_bytes()} + locks={root+'/D-003-PACKET-v2.md':(HERE/'D-003-PACKET-v2.md').read_bytes(),root+'/d003-capability-manifest-v2.json':RAW} + self.assertEqual(d.derive_import_closure({**source,**locks},{},root),set()) + def test_stdlib_policy_removal_and_nonstdlib_substitution_rejected(self): + source={'X/main.py':(HERE/'d003_r2_v2.py').read_bytes()} + for name in ('__future__','binascii','typing'): + with self.subTest(name=name): + old=d.STDLIB_IMPORTS;d.STDLIB_IMPORTS=old-{name} + try: + with self.assertRaisesRegex(d.ManifestError,'undeclared import'):d.derive_import_closure(source,{},'X') + finally:d.STDLIB_IMPORTS=old + bad=source['X/main.py'].replace(b'import binascii',b'import clearly_nonstdlib',1) + with self.assertRaisesRegex(d.ManifestError,'undeclared import'):d.derive_import_closure({'X/main.py':bad},{},'X') + def test_undeclared_local_import_still_fails(self): + with self.assertRaisesRegex(d.ManifestError,'undeclared import'):d.derive_import_closure({'X/main.py':b'import local_missing\n'},{},'X') def test_git_blob_tree_commit_dirty(self): q=prov('X');r=copy.deepcopy(q['repository']);r['blob_ids']['X/main.py']='0'*40 with self.assertRaisesRegex(d.ManifestError,'wrong blob'):d.verify_repository({**q['source'],**q['dependency'],**q['lock']},r) diff --git a/decisions/test_d003_v3_v2.py b/decisions/test_d003_v3_v2.py index 5855c11faa..82e6d10866 100644 --- a/decisions/test_d003_v3_v2.py +++ b/decisions/test_d003_v3_v2.py @@ -125,6 +125,23 @@ class ReplayTests(unittest.TestCase): with self.assertRaises(v.VerificationError):v.parse_canonical(raw[:-1]+b' \n') class ProvenanceTests(unittest.TestCase): + def test_actual_r2_source_bytes_have_no_undeclared_dependencies(self): + r2_source={'decisions/d003_r2_v2.py':(ROOT/'d003_r2_v2.py').read_bytes()} + v.validate_import_closure(r2_source,{}) + + def test_nonstdlib_import_still_fails(self): + with self.assertRaises(v.VerificationError): + v.validate_import_closure({'main.py':b'import clearly_undeclared\n'}, {}) + + def test_dependency_fixed_point_and_unreachable_extra_remain_strict(self): + source={'a.py':b'import b\n'} + deps={'b.py':b'import c\n','c.py':b'VALUE=1\n'} + v.validate_import_closure(source,deps) + with self.assertRaises(v.VerificationError): + v.validate_import_closure(source,deps|{'unused.py':b'VALUE=2\n'}) + with self.assertRaises(v.VerificationError): + v.validate_import_closure(source,{'b.py':b'import c\n'}) + def test_transitive_import_escape_and_unreachable_extra(self): with self.assertRaises(v.VerificationError):v.validate_import_closure({'a.py':b'import b\n'},{'b.py':b'import missing\n'}) with self.assertRaises(v.VerificationError):v.validate_import_closure({'pkg/a.py':b'from .. import x\n'}, {}) @@ -134,7 +151,9 @@ class ProvenanceTests(unittest.TestCase): self.assertEqual(v.inventory({},'LOCK'),[]) def test_pairwise_permitted_share_only(self): with self.assertRaises(v.VerificationError):v._overlap([{'x.py':b'a'},{'x.py':b'a'}]) - v._overlap([{next(iter(v.ALLOWED_SHARE)):b'a'},{next(iter(v.ALLOWED_SHARE)):b'a'}]) + shared=next(iter(v.ALLOWED_SHARE)) + v._overlap([{shared:b'a'},{shared:b'a'}]) + with self.assertRaises(v.VerificationError):v._overlap([{shared:b'a'},{shared:b'b'}]) def test_source_guards(self): src=inspect.getsource(v) forbidden=('d003_manifest_v2','d003_v3.py','D001','D002','R1','R2') -- 2.54.0