3"""Data model shared by the suppression inventory scanner and renderers."""
5from __future__
import annotations
8from collections
import Counter
9from dataclasses
import asdict, dataclass, field
12@dataclass(frozen=True)
14 """One syntax-recognized suppression or waiver directive."""
27 concerns: tuple[str, ...] = ()
29 evidence: tuple[str, ...] = ()
31 recommendation: str =
"manual-review"
32 disposition: str =
"unreviewed"
34 binding_sha256: str =
""
37 def __post_init__(self) -> None:
38 """Derive a stable content identity when the scanner did not supply one."""
53 digest = hashlib.sha256(
"\0".join(fields).encode(
"utf-8")).hexdigest()[:20]
54 object.__setattr__(self,
"fingerprint", digest)
56 def as_dict(self) -> dict[str, object]:
57 """Return a stable JSON-compatible representation."""
59 data[
"concerns"] = list(self.concerns)
60 data[
"evidence"] = list(self.evidence)
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)
68@dataclass(frozen=True)
70 """A scanner problem that prevents the inventory from being authoritative."""
77 def as_dict(self) -> dict[str, object]:
78 """Return a stable JSON-compatible representation."""
84 """Phase-one suppression inventory plus scanner integrity evidence."""
86 suppressions: list[Suppression] = field(default_factory=list)
87 findings: list[Finding] = field(default_factory=list)
88 files_scanned: int = 0
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()))
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()))
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()))
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)
110 "schema_version":
"2-durable-site-identity",
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(),
121 "suppressions": [item.as_dict()
for item
in ordered],
122 "findings": [item.as_dict()
for item
in findings],
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)
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)