ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
suppression_debt_selftest.py
Go to the documentation of this file.
1# SPDX-License-Identifier: MIT
2# Copyright (c) 2026 Brighton Sikarskie
3"""Adversarial fixtures for immutable suppression-debt ceilings."""
4
5from __future__ import annotations
6
7from pathlib import Path
8
9from selftest_assert import expect
10from suppression_baseline_scan import ceiling_snapshot, scan_baseline_repository
11from suppression_model import Suppression
12
13TIDY = ".github/tidy-baseline.txt"
14COMPOUND = ".github/mcdc-compound-baseline.txt"
15TREE = ".github/tree-coverage-baseline.txt"
16FREESTANDING = ".github/freestanding-runtime-baseline.json"
17TIDY_HEADER = (
18 "# clang-tidy ratchet baseline -- per-file-per-check finding counts.\n"
19 "# Consumed by scripts/checks/tidy_ratchet.py --check (CI gate: tidy).\n"
20)
21COMPOUND_HEADER = (
22 "# MC/DC compound-decision ratchet baseline -- per-file-per-function counts.\n"
23 "# Consumed by scripts/checks/mcdc_compound_ratchet.py --check\n"
24 "# Total at this baseline: 1 uncovered compound decision\n"
25)
26TREE_HEADER = (
27 "# ONE coverage baseline for every first-party translation unit.\n"
28 "# Emitted by `python3 scripts/checks/check_tree_coverage.py --update`.\n"
29)
30
31
32def _write_probe(root: Path, rel: str, text: str = "") -> None:
33 """Write one ASCII baseline-integrity probe file."""
34 target = root / rel
35 target.parent.mkdir(parents=True, exist_ok=True)
36 target.write_text(text, encoding="ascii")
37
38
39def _scan_probe(
40 root: Path, paths: list[str], ledger: dict[str, tuple[str, str]] | None = None
41) -> tuple[list[Suppression], set[str]]:
42 """Run the production baseline scanner and return rows plus finding codes."""
43 rows, findings = scan_baseline_repository(
44 root,
45 paths,
46 enforce_floors=False,
47 ceiling_ledger=ledger,
48 )
49 return rows, {item.code for item in findings}
50
51
52def _assert_key_swaps(root: Path, failures: list[str]) -> None:
53 """Assert debt cannot move within or between baseline authorities."""
54 original_tidy = TIDY_HEADER + "libs/a.c\tmisc-a\t1\nlibs/b.c\tmisc-b\t4\n"
55 original_compound = COMPOUND_HEADER + "libs/c.c\tfn_c\t1\n"
56 _write_probe(root, TIDY, original_tidy)
57 _write_probe(root, COMPOUND, original_compound)
58 rows, original_codes = _scan_probe(root, [TIDY, COMPOUND])
59 ledger = ceiling_snapshot(rows)
60 _rows, frozen_codes = _scan_probe(root, [TIDY, COMPOUND], ledger)
61 expect(
62 not original_codes and not frozen_codes,
63 "quiet: unchanged canonical per-key ceilings validate",
64 failures,
65 )
66 changed = TIDY_HEADER + "libs/a.c\tmisc-new\t2\nlibs/b.c\tmisc-b\t3\n"
67 _write_probe(root, TIDY, changed)
68 _rows, within_codes = _scan_probe(root, [TIDY, COMPOUND], ledger)
69 expect(
70 "baseline-growth" in within_codes,
71 "must fire: same-total within-baseline bucket swaps cannot absorb debt",
72 failures,
73 )
74 changed = TIDY_HEADER + "libs/c.c\tfn_c\t1\nlibs/b.c\tmisc-b\t4\n"
75 _write_probe(root, TIDY, changed)
76 _write_probe(root, COMPOUND, COMPOUND_HEADER + "libs/a.c\tmisc-a\t1\n")
77 _rows, swap_codes = _scan_probe(root, [TIDY, COMPOUND], ledger)
78 expect(
79 "baseline-growth" in swap_codes,
80 "must fire: equal-count cross-baseline bucket swaps cannot absorb debt",
81 failures,
82 )
83
84
85def _assert_path_aliases(root: Path, failures: list[str]) -> None:
86 """Assert noncanonical spellings cannot mint alternate debt keys."""
87 for alias in ("./libs/a.c", "libs\\a.c", "."):
88 _write_probe(root, TIDY, TIDY_HEADER + f"{alias}\tmisc-a\t1\n")
89 _rows, alias_codes = _scan_probe(root, [TIDY])
90 expect(
91 "malformed-baseline-row" in alias_codes,
92 f"must fire: noncanonical path alias {alias!r} is rejected",
93 failures,
94 )
95
96
97def _assert_tree_controls(root: Path, failures: list[str]) -> None:
98 """Assert tree-coverage dimensions and path identity stay independent."""
99 _write_probe(
100 root,
101 TREE,
102 TREE_HEADER + "# rows: 1\nlibs/a.c\tMEASURED\t90\t100\t70\t80\n",
103 )
104 tree_rows, tree_original_codes = _scan_probe(root, [TREE])
105 tree_ledger = ceiling_snapshot(tree_rows)
106 _write_probe(
107 root,
108 TREE,
109 TREE_HEADER + "# rows: 1\nlibs/a.c\tMEASURED\t89\t100\t71\t80\n",
110 )
111 _rows, tree_swap_codes = _scan_probe(root, [TREE], tree_ledger)
112 expect(
113 not tree_original_codes and "baseline-growth" in tree_swap_codes,
114 "must fire: tree line debt cannot trade against branch burn-down",
115 failures,
116 )
117 _write_probe(
118 root,
119 TREE,
120 TREE_HEADER
121 + "# rows: 2\n"
122 + "libs/a.c\tMEASURED\t1\t2\t1\t1\n"
123 + "libs/a.c\tUNMEASURED\tplatform-cross-only\n",
124 )
125 _rows, duplicate_codes = _scan_probe(root, [TREE])
126 expect(
127 "duplicate-baseline-row" in duplicate_codes,
128 "must fire: tree coverage MEASURED/UNMEASURED share one path identity",
129 failures,
130 )
131
132
133def _assert_authority_controls(root: Path, failures: list[str]) -> None:
134 """Assert missing provenance and invented authorities fail closed."""
135 _write_probe(root, TIDY, "# header removed\nlibs/a.c\tmisc-a\t1\n")
136 _write_probe(root, ".github/future-ratchet.txt", "libs/a.c\tmisc-a\t1\n")
137 _rows, authority_codes = _scan_probe(root, [TIDY, ".github/future-ratchet.txt"])
138 expect(
139 {"missing-baseline-provenance", "unknown-baseline-file"} <= authority_codes,
140 "must fire: provenance removal and noncanonical ratchet authorities fail closed",
141 failures,
142 )
143
144
145def assert_baseline_ceiling_controls(base: Path, failures: list[str]) -> None:
146 """Assert canonical per-key ceilings reject swaps, aliases, and authority drift."""
147 root = base / "baseline-ceiling-probe"
148 for rel in (
149 "scripts/checks/tidy_ratchet.py",
150 "scripts/checks/mcdc_compound_ratchet.py",
151 "scripts/checks/check_tree_coverage.py",
152 "libs/a.c",
153 "libs/b.c",
154 "libs/c.c",
155 ):
156 _write_probe(root, rel)
157 _assert_key_swaps(root, failures)
158 _assert_path_aliases(root, failures)
159 _assert_tree_controls(root, failures)
160 _assert_authority_controls(root, failures)
161 _assert_freestanding_controls(root, failures)
162
163
164def _assert_freestanding_controls(root: Path, failures: list[str]) -> None:
165 """Assert the freestanding runtime baseline ratchets shrink-only."""
166 _write_probe(root, "scripts/checks/check_freestanding_runtime.py")
167 clean = (
168 '{"apps": {"probe_app": {"forbidden_symbols": [], "forbidden_archives": {},'
169 ' "sbrk_provider": "none", "end_symbol": false}}, "linker_script_exceptions": []}'
170 )
171 _write_probe(root, FREESTANDING, clean)
172 rows, quiet_codes = _scan_probe(root, [FREESTANDING])
173 ledger = ceiling_snapshot(rows)
174 _rows, frozen_codes = _scan_probe(root, [FREESTANDING], ledger)
175 expect(
176 not quiet_codes and not frozen_codes,
177 "quiet: registered freestanding baseline validates",
178 failures,
179 )
180 grown = clean.replace('"forbidden_symbols": []', '"forbidden_symbols": ["malloc"]')
181 _write_probe(root, FREESTANDING, grown)
182 _rows, grown_codes = _scan_probe(root, [FREESTANDING], ledger)
183 expect(
184 "baseline-growth" in grown_codes,
185 "must fire: freestanding debt growth cannot exceed its ceiling",
186 failures,
187 )
188 _write_probe(root, FREESTANDING, '{"apps": {}}')
189 _rows, malformed_codes = _scan_probe(root, [FREESTANDING])
190 expect(
191 "malformed-baseline-row" in malformed_codes,
192 "must fire: malformed freestanding baseline is rejected",
193 failures,
194 )
195 _write_probe(root, ".github/unregistered-baseline.json", clean)
196 _rows, unknown_codes = _scan_probe(root, [".github/unregistered-baseline.json"])
197 expect(
198 "unknown-baseline-file" in unknown_codes,
199 "must fire: unregistered baseline files stay fail-closed",
200 failures,
201 )