ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
suppression_selftest_integrity.py
Go to the documentation of this file.
1# SPDX-License-Identifier: MIT
2# Copyright (c) 2026 Brighton Sikarskie
3"""Identity, ownership, and split-integrity assertions for suppression selftests."""
4
5from __future__ import annotations
6
7import ast
8import hashlib
9import json
10from dataclasses import replace
11from pathlib import Path
12
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,
19 FIXTURES,
20 fixture_digest,
21)
22from suppression_validate import deduplicate, validate_cppcheck_anchors
23
24
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="")
32 expect(
33 record.fingerprint != later.fingerprint,
34 "quiet: interim foundation identity includes source location",
35 failures,
36 )
37 duplicate = Inventory(suppressions=[record, replace(record)])
38 validate_fingerprints(duplicate)
39 expect(
40 any(item.code == "duplicate-fingerprint" for item in duplicate.findings),
41 "must fire: duplicate public fingerprints are rejected",
42 failures,
43 )
44 same_line = Inventory(
45 suppressions=[record, replace(record, column=record.column + 1, fingerprint="")]
46 )
47 deduplicate(same_line)
48 expect(
49 not any(item.code == "duplicate-directive" for item in same_line.findings),
50 "quiet: distinct same-line source columns remain distinct controls",
51 failures,
52 )
53 expect(
54 inventory.as_dict()["schema_version"] == "2-durable-site-identity",
55 "quiet: schema discloses durable site identity",
56 failures,
57 )
58 hex_width = len(hashlib.sha256(b"").hexdigest())
59 expect(
60 all(
61 len(item.site_id) == hex_width and len(item.binding_sha256) == hex_width
62 for item in inventory.suppressions
63 ),
64 "must fire: every row carries a full-width site and binding identity",
65 failures,
66 )
67
68
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}
72 expect(
73 owners["port/threadx/vendor.c"] == "vendor",
74 "quiet: ThreadX C source is vendor-owned",
75 failures,
76 )
77 expect(
78 owners["port/threadx/CMakeLists.txt"] == "first-party",
79 "quiet: ThreadX build glue remains first-party",
80 failures,
81 )
82 generated = "libs/ra8_c6link/src/ra8_media_download.pb-c.c"
83 expect(
84 owners[generated] == "generated",
85 "quiet: exact generated source uses canonical PATH_CLASS evidence",
86 failures,
87 )
88
89
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"))
96 assignments = {
97 target.id
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)
102 }
103 expect(
104 not {"FIXTURES", "CORE_FIXTURES"} & assignments,
105 "quiet: fixture data lives outside the selftest driver",
106 failures,
107 )
108 expect(
109 fixture_digest(FIXTURES) == EXPECTED_FIXTURE_SHA256,
110 "must fire: structural split preserves every fixture byte",
111 failures,
112 )
113 inventory = scan_paths(root, sorted(FIXTURES))
114 payload = json.dumps(
115 inventory.as_dict(), sort_keys=True, separators=(",", ":"), ensure_ascii=False
116 ).encode()
117 expect(
118 hashlib.sha256(payload).hexdigest() == EXPECTED_FIXTURE_INVENTORY_SHA256,
119 "must fire: structural split preserves the exact fixture inventory bytes",
120 failures,
121 )
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)}
124 expect(
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",
127 failures,
128 )
129
130
131def _assert_cppcheck_anchor_validation(
132 root: Path,
133 inventory: Inventory,
134 failures: list[str],
135 expected_findings: int,
136) -> None:
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"
140 anchor_root.mkdir()
141 (anchor_root / "target.c").write_text("one\ntwo\n", encoding="ascii")
142 probe = Inventory(
143 suppressions=[
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"),
148 ]
149 )
150 validate_cppcheck_anchors(probe, anchor_root)
151 messages = [item.message for item in probe.findings]
152 expect(
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",
157 failures,
158 )
159
160
161def assert_identity_and_structure(
162 root: Path,
163 inventory: Inventory,
164 failures: list[str],
165 expected_dead_anchor_findings: int,
166) -> None:
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)