ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
suppression_model.py
Go to the documentation of this file.
1# SPDX-License-Identifier: MIT
2# Copyright (c) 2026 Brighton Sikarskie
3"""Data model shared by the suppression inventory scanner and renderers."""
4
5from __future__ import annotations
6
7import hashlib
8from collections import Counter
9from dataclasses import asdict, dataclass, field
10
11
12@dataclass(frozen=True)
13class Suppression:
14 """One syntax-recognized suppression or waiver directive."""
15
16 path: str
17 line: int
18 column: int
19 family: str
20 tool: str
21 rule: str
22 directive: str
23 scope: str
24 reason: str
25 provenance: str
26 owner: str
27 concerns: tuple[str, ...] = ()
28 fingerprint: str = ""
29 evidence: tuple[str, ...] = ()
30 match_count: int = 1
31 recommendation: str = "manual-review"
32 disposition: str = "unreviewed"
33 site_id: str = ""
34 binding_sha256: str = ""
35 anchor: str = ""
36
37 def __post_init__(self) -> None:
38 """Derive a stable content identity when the scanner did not supply one."""
39 if self.fingerprint:
40 return
41 fields = (
42 self.path,
43 str(self.line),
44 str(self.column),
45 self.family,
46 self.tool,
47 self.rule,
48 self.directive,
49 self.scope,
50 self.provenance,
51 self.reason,
52 )
53 digest = hashlib.sha256("\0".join(fields).encode("utf-8")).hexdigest()[:20]
54 object.__setattr__(self, "fingerprint", digest)
55
56 def as_dict(self) -> dict[str, object]:
57 """Return a stable JSON-compatible representation."""
58 data = asdict(self)
59 data["concerns"] = list(self.concerns)
60 data["evidence"] = list(self.evidence)
61 return data
62
63 def duplicate_key(self) -> tuple[str, int, int, str, str, str]:
64 """Return fields that identify an accidental repeated directive."""
65 return (self.path, self.line, self.column, self.family, self.rule, self.scope)
66
67
68@dataclass(frozen=True)
69class Finding:
70 """A scanner problem that prevents the inventory from being authoritative."""
71
72 code: str
73 message: str
74 path: str = ""
75 line: int = 0
76
77 def as_dict(self) -> dict[str, object]:
78 """Return a stable JSON-compatible representation."""
79 return asdict(self)
80
81
82@dataclass
83class Inventory:
84 """Phase-one suppression inventory plus scanner integrity evidence."""
85
86 suppressions: list[Suppression] = field(default_factory=list)
87 findings: list[Finding] = field(default_factory=list)
88 files_scanned: int = 0
89 text_files: int = 0
90 binary_files: int = 0
91
92 def family_counts(self) -> dict[str, int]:
93 """Count inventory rows by suppression family."""
94 return dict(sorted(Counter(item.family for item in self.suppressions).items()))
95
96 def owner_counts(self) -> dict[str, int]:
97 """Count inventory rows by first-party, generated, or vendor ownership."""
98 return dict(sorted(Counter(item.owner for item in self.suppressions).items()))
99
100 def concern_counts(self) -> dict[str, int]:
101 """Count review concerns carried by recognized inventory rows."""
102 counts = Counter(concern for item in self.suppressions for concern in item.concerns)
103 return dict(sorted(counts.items()))
104
105 def as_dict(self) -> dict[str, object]:
106 """Return the deterministic public inventory document."""
107 ordered = sorted(self.suppressions, key=_suppression_sort_key)
108 findings = sorted(self.findings, key=_finding_sort_key)
109 return {
110 "schema_version": "2-durable-site-identity",
111 "summary": {
112 "files_scanned": self.files_scanned,
113 "text_files": self.text_files,
114 "binary_files": self.binary_files,
115 "suppressions": len(ordered),
116 "findings": len(findings),
117 "families": self.family_counts(),
118 "owners": self.owner_counts(),
119 "concerns": self.concern_counts(),
120 },
121 "suppressions": [item.as_dict() for item in ordered],
122 "findings": [item.as_dict() for item in findings],
123 }
124
125
126def _suppression_sort_key(item: Suppression) -> tuple[object, ...]:
127 """Return deterministic ordering fields for suppression rows."""
128 return (item.path, item.line, item.column, item.family, item.rule, item.directive)
129
130
131def _finding_sort_key(item: Finding) -> tuple[object, ...]:
132 """Return deterministic ordering fields for findings."""
133 return (item.path, item.line, item.code, item.message)