3"""Identity, ownership, and split-integrity assertions for suppression selftests."""
5from __future__
import annotations
10from dataclasses
import replace
11from pathlib
import Path
13from selftest_assert
import expect
14from suppression_model
import Inventory
15from suppression_scan
import scan_paths, validate_fingerprints
16from suppression_selftest_fixtures
import (
17 EXPECTED_FIXTURE_INVENTORY_SHA256,
18 EXPECTED_FIXTURE_SHA256,
22from suppression_validate
import deduplicate, validate_cppcheck_anchors
25def _assert_foundation_schema(inventory: Inventory, failures: list[str]) ->
None:
26 """Assert governance placeholders remain present and deliberately unresolved."""
27 record = inventory.suppressions[0]
28 expect(bool(record.fingerprint),
"quiet: stable fingerprint is populated", failures)
29 expect(record.match_count == 1,
"quiet: match count is explicit", failures)
30 expect(record.disposition ==
"unreviewed",
"must fire: disposition is unresolved", failures)
31 later = replace(record, line=record.line + 1, fingerprint=
"")
33 record.fingerprint != later.fingerprint,
34 "quiet: interim foundation identity includes source location",
37 duplicate = Inventory(suppressions=[record, replace(record)])
38 validate_fingerprints(duplicate)
40 any(item.code ==
"duplicate-fingerprint" for item
in duplicate.findings),
41 "must fire: duplicate public fingerprints are rejected",
44 same_line = Inventory(
45 suppressions=[record, replace(record, column=record.column + 1, fingerprint=
"")]
47 deduplicate(same_line)
49 not any(item.code ==
"duplicate-directive" for item
in same_line.findings),
50 "quiet: distinct same-line source columns remain distinct controls",
54 inventory.as_dict()[
"schema_version"] ==
"2-durable-site-identity",
55 "quiet: schema discloses durable site identity",
58 hex_width = len(hashlib.sha256(b
"").hexdigest())
61 len(item.site_id) == hex_width
and len(item.binding_sha256) == hex_width
62 for item
in inventory.suppressions
64 "must fire: every row carries a full-width site and binding identity",
69def _assert_ownership(inventory: Inventory, failures: list[str]) ->
None:
70 """Assert canonical evidence handles mixed-owned and generated paths."""
71 owners = {item.path: item.owner
for item
in inventory.suppressions}
73 owners[
"port/threadx/vendor.c"] ==
"vendor",
74 "quiet: ThreadX C source is vendor-owned",
78 owners[
"port/threadx/CMakeLists.txt"] ==
"first-party",
79 "quiet: ThreadX build glue remains first-party",
82 generated =
"libs/ra8_c6link/src/ra8_media_download.pb-c.c"
84 owners[generated] ==
"generated",
85 "quiet: exact generated source uses canonical PATH_CLASS evidence",
90def _assert_structural_split(root: Path, failures: list[str]) ->
None:
91 """Authenticate fixture bytes, scan parity, and module ownership after the split."""
92 directory = Path(__file__).parent
93 driver = ast.parse((directory /
"suppression_selftest.py").read_text(encoding=
"utf-8"))
94 scanner = ast.parse((directory /
"suppression_scan.py").read_text(encoding=
"utf-8"))
95 inline = ast.parse((directory /
"suppression_inline_scan.py").read_text(encoding=
"utf-8"))
98 for node
in ast.walk(driver)
99 if isinstance(node, (ast.Assign, ast.AnnAssign))
100 for target
in (node.targets
if isinstance(node, ast.Assign)
else [node.target])
101 if isinstance(target, ast.Name)
104 not {
"FIXTURES",
"CORE_FIXTURES"} & assignments,
105 "quiet: fixture data lives outside the selftest driver",
109 fixture_digest(FIXTURES) == EXPECTED_FIXTURE_SHA256,
110 "must fire: structural split preserves every fixture byte",
113 inventory = scan_paths(root, sorted(FIXTURES))
114 payload = json.dumps(
115 inventory.as_dict(), sort_keys=
True, separators=(
",",
":"), ensure_ascii=
False
118 hashlib.sha256(payload).hexdigest() == EXPECTED_FIXTURE_INVENTORY_SHA256,
119 "must fire: structural split preserves the exact fixture inventory bytes",
122 inline_names = {node.name
for node
in ast.walk(inline)
if isinstance(node, ast.FunctionDef)}
123 scanner_names = {node.name
for node
in ast.walk(scanner)
if isinstance(node, ast.FunctionDef)}
125 "_scan_comments" in inline_names
and "_scan_comments" not in scanner_names,
126 "quiet: inline recognizers live only in the bounded inline-scan module",
131def _assert_cppcheck_anchor_validation(
133 inventory: Inventory,
135 expected_findings: int,
137 """Assert literal central-list anchors cannot decay unnoticed."""
138 central = next(item
for item
in inventory.suppressions
if item.provenance ==
"central-list")
139 anchor_root = root /
"anchor-validation"
141 (anchor_root /
"target.c").write_text(
"one\ntwo\n", encoding=
"ascii")
144 replace(central, scope=
"target.c:2"),
145 replace(central, scope=
"target.c:3"),
146 replace(central, scope=
"missing.c:1"),
147 replace(central, scope=
"src/*.c:999"),
150 validate_cppcheck_anchors(probe, anchor_root)
151 messages = [item.message
for item
in probe.findings]
153 len(messages) == expected_findings
154 and any(
"past EOF" in message
for message
in messages)
155 and any(
"missing source" in message
for message
in messages),
156 "must fire: missing and past-EOF cppcheck anchors are rejected",
161def assert_identity_and_structure(
163 inventory: Inventory,
165 expected_dead_anchor_findings: int,
167 """Run the identity, ownership, module-split, and anchor assertions."""
168 _assert_structural_split(root, failures)
169 _assert_cppcheck_anchor_validation(root, inventory, failures, expected_dead_anchor_findings)
170 _assert_foundation_schema(inventory, failures)
171 _assert_ownership(inventory, failures)