120 """Load a baseline from disk. Returns ``None`` on missing/corrupt file."""
121 func =
"load_baseline"
122 _dbg(
"OCT-LINTER", func, f
"loading baseline from {baseline_path}", 3)
123 if not baseline_path.is_file():
126 raw = json.loads(baseline_path.read_text(encoding=
"utf-8"))
127 if not isinstance(raw, dict):
129 if raw.get(
"_schema_version") != _SCHEMA_VERSION:
131 entries_raw = raw.get(
"entries", [])
132 if not isinstance(entries_raw, list):
135 BaselineEntry(path=e[
"path"], rule=e[
"rule"], message=e[
"message"])
137 if isinstance(e, dict)
and "path" in e
and "rule" in e
141 oct_version=raw.get(
"oct_version",
""),
142 timestamp=raw.get(
"timestamp",
""),
143 profile=raw.get(
"profile",
""),
145 except (json.JSONDecodeError, OSError, KeyError):
151 baseline: LintBaseline,
152) -> tuple[list[tuple[str, str, str]], list[tuple[str, str, str]]]:
153 """Compare current *results* against *baseline*.
155 Returns ``(new_violations, resolved_violations)`` where each item is
156 ``(path, rule, message)``.
158 A violation is identified by its ``(path, rule)`` pair. If a
159 ``(path, rule)`` appears in current results but not in the baseline,
160 it is "new". If it appears in the baseline but not in current results,
163 func =
"diff_against_baseline"
164 _dbg(
"OCT-LINTER", func,
"diffing results against the baseline", 3)
165 baseline_set: set[tuple[str, str]] = set()
166 baseline_msgs: dict[tuple[str, str], str] = {}
167 for entry
in baseline.entries:
168 key = (entry.path, entry.rule)
169 baseline_set.add(key)
170 baseline_msgs[key] = entry.message
172 current_set: set[tuple[str, str]] = set()
173 current_msgs: dict[tuple[str, str], str] = {}
175 detail = r.get(
"json_detail", {})
176 rel_path = detail.get(
"path", r.get(
"rel",
""))
177 checks = detail.get(
"checks", {})
178 for rule_name, check
in checks.items():
179 if not check.get(
"pass",
True):
180 key = (rel_path, rule_name)
182 current_msgs[key] = check.get(
"message",
"")
184 new_keys = current_set - baseline_set
185 resolved_keys = baseline_set - current_set
187 new_violations = sorted(
188 [(p, r, current_msgs[(p, r)])
for p, r
in new_keys],
190 resolved_violations = sorted(
191 [(p, r, baseline_msgs[(p, r)])
for p, r
in resolved_keys],
193 return new_violations, resolved_violations
197 violations: list[tuple[str, str, str]],
199) -> list[tuple[str, str, str]]:
200 """The *violations* whose check is blocking in *results*.
202 ``oct lint --against-baseline`` exits 1 only for these (operator ruling
203 2026-09-28); a new advisory failure is listed but does not fail the
204 command. A check recording no severity counts as blocking, as the
205 compliance rule (FS-C4) treats it.
207 severity: dict[tuple[str, str], str] = {}
209 detail = r.get(
"json_detail", {})
210 rel_path = detail.get(
"path", r.get(
"rel",
""))
211 for rule_name, check
in detail.get(
"checks", {}).items():
212 severity[(rel_path, rule_name)] = check.get(
"severity", SEVERITY_BLOCKING)
214 v
for v
in violations
215 if severity.get((v[0], v[1]), SEVERITY_BLOCKING) == SEVERITY_BLOCKING