4"""Review Suppressions Utility.
6Finds all 'unreviewed' ledger rows, assigns them a rationale, creates a batch,
7and sets them to 'retain' so they pass CI.
18from collections.abc
import Callable
19from contextlib
import suppress
20from dataclasses
import dataclass
21from pathlib
import Path
22from typing
import NoReturn
27def _repo_root() -> Path:
28 return Path(__file__).resolve().parents[2]
31def _read_ledger() -> list[str]:
33 (_repo_root() /
".github" /
"suppression-review-ledger.tsv")
34 .read_text(encoding=
"utf-8")
39def _write_ledger(lines: list[str]) ->
None:
40 (_repo_root() /
".github" /
"suppression-review-ledger.tsv").write_text(
41 "\n".join(lines) +
"\n", encoding=
"utf-8"
45def _get_rationale_evidence(args: argparse.Namespace) -> tuple[str, str]:
46 rationale = args.rationale
48 print(
"\nAvailable Rationales:")
49 print(
" 1. tool-false-positive")
50 print(
" 2. language-or-compiler-contract")
51 print(
" 3. negative-test-contract")
52 print(
" 4. generated-reproducible-output")
53 print(
" 5. vendor-upstream-preserved")
54 print(
" 6. reviewed-scope-authority")
55 choice = input(
"Select a rationale (1-6) or type the ID directly: ").strip()
57 "1":
"tool-false-positive",
58 "2":
"language-or-compiler-contract",
59 "3":
"negative-test-contract",
60 "4":
"generated-reproducible-output",
61 "5":
"vendor-upstream-preserved",
62 "6":
"reviewed-scope-authority",
64 rationale = mapping.get(choice, choice)
66 evidence = args.evidence
68 evidence = input(f
"Enter evidence reference for why '{rationale}' applies here: ").strip()
70 return rationale, evidence
73def _write_batch(batch_id: str, count: int, batch_sha256: str, evidence: str) ->
None:
74 batches_path = _repo_root() /
".github" /
"suppression-review-batches.yml"
75 batches_yaml = batches_path.read_text(encoding=
"utf-8").rstrip()
79 authority: repository-suppression-review
80 date: {datetime.datetime.now(tz=datetime.UTC).strftime("%Y-%m-%d")}
81 identity_schema: 2-durable-site-identity
82 assigned_rows: {count}
83 rows_sha256: {batch_sha256}
84 partition: "Automated developer review batch"
88 batches_path.write_text(batches_yaml + batch_entry, encoding=
"utf-8")
91def _build_parser() -> argparse.ArgumentParser:
92 """Build the review CLI: legacy blanket flow plus selective subcommands."""
93 parser = argparse.ArgumentParser(description=
"Approve draft unreviewed suppressions.")
94 parser.add_argument(
"--rationale", type=str, help=
"Rationale ID")
95 parser.add_argument(
"--evidence", type=str, help=
"Evidence reference string")
99 help=
"run the selective review selftest (no ledger changes)",
101 subparsers = parser.add_subparsers(dest=
"action")
102 retain_parser = subparsers.add_parser(
103 "retain", help=
"retain explicitly selected live unreviewed rows"
105 retain_parser.add_argument(
"--site", action=
"append", default=[], help=
"site_id to retain")
106 retain_parser.add_argument(
"--rationale", required=
True, help=
"Rationale ID")
107 retain_parser.add_argument(
"--evidence", required=
True, help=
"Evidence reference string")
108 retain_parser.add_argument(
109 "--dry-run", action=
"store_true", help=
"print transitions without writing"
111 retire_parser = subparsers.add_parser(
112 "retire", help=
"retire explicitly selected stale unreviewed rows"
114 retire_parser.add_argument(
"--site", action=
"append", default=[], help=
"site_id to retire")
115 retire_parser.add_argument(
"--rationale", required=
True, help=
"Rationale ID")
116 retire_parser.add_argument(
"--evidence", required=
True, help=
"Evidence reference string")
117 retire_parser.add_argument(
121 help=
"live successor site_id (supersede instead of resolve)",
123 retire_parser.add_argument(
124 "--dry-run", action=
"store_true", help=
"print transitions without writing"
130 """Execute the review approval flow."""
131 args = _build_parser().parse_args()
134 return run_selftest()
136 if args.action
in (
"retain",
"retire"):
137 return run_selective(args)
139 ledger = _read_ledger()
147 parts = r.split(
"\t")
148 if parts[2] ==
"unreviewed":
149 unreviewed.append(parts)
152 print(
"No 'unreviewed' rows found in ledger. Nothing to do.")
155 print(f
"Found {len(unreviewed)} unreviewed suppression(s).")
156 rationale, evidence = _get_rationale_evidence(args)
158 date_str = datetime.datetime.now(tz=datetime.UTC).strftime(
"%Y%m%d")
159 batch_id = f
"auto-review-batch-{date_str}"
161 payload = [f
"{parts[0]}\t{parts[1]}\tretain\t{rationale}\t{evidence}" for parts
in unreviewed]
163 batch_sha256 = hashlib.sha256(
"\n".join(payload).encode(
"utf-8")).hexdigest()
165 updated_ledger = [header]
169 parts = r.split(
"\t")
170 if parts[2] ==
"unreviewed":
175 updated_ledger.append(
"\t".join(parts))
177 updated_ledger.append(r)
179 _write_ledger(updated_ledger)
180 _write_batch(batch_id, len(unreviewed), batch_sha256, evidence)
182 print(f
"\nSuccessfully approved {len(unreviewed)} row(s) and generated batch {batch_id}.")
183 print(
"Run `just quality::local::gate suppressions` to verify.")
187_LEDGER_HEADER =
"site_id\tbinding_sha256\tstate\trationale_id\tbatch_id\tevidence_ref"
189_LEDGER_STATES = frozenset({
"unreviewed",
"retain",
"fix-required",
"resolved",
"superseded"})
190_HEX64_RE = re.compile(
r"^[0-9a-f]{64}$")
191_BATCHES_PATH =
".github/suppression-review-batches.yml"
192_RATIONALES_PATH =
".github/suppression-review-rationales.yml"
193_IDENTITY_MODULE =
"scripts/checks/suppression_identity.py"
194_IDENTITY_ATTR =
"IDENTITY_SCHEMA_VERSION"
197class ReviewError(ValueError):
198 """A selective review action was refused fail-closed."""
201def _refuse(message: str) -> NoReturn:
202 """Reject one review action with a fail-closed diagnostic."""
203 raise ReviewError(message)
206def _parse_ledger(text: str) -> tuple[str, list[list[str]]]:
207 """Split ledger text into header plus validated rows, failing closed."""
208 lines = text.splitlines()
209 if not lines
or lines[0] != _LEDGER_HEADER:
210 _refuse(
"malformed ledger: missing or wrong header")
211 rows: list[list[str]] = []
212 for lineno, line
in enumerate(lines[1:], start=2):
215 parts = line.split(
"\t")
216 if len(parts) != _LEDGER_COLUMNS:
217 _refuse(f
"malformed ledger: line {lineno} has {len(parts)} columns")
218 site, binding, state = parts[0], parts[1], parts[2]
219 if not _HEX64_RE.match(site):
220 _refuse(f
"malformed ledger: line {lineno} has a bad site_id")
221 if not _HEX64_RE.match(binding):
222 _refuse(f
"malformed ledger: line {lineno} has a bad binding_sha256")
223 if state
not in _LEDGER_STATES:
224 _refuse(f
"malformed ledger: line {lineno} has unknown state {state!r}")
225 if state ==
"unreviewed" and (parts[3]
or parts[4]):
226 _refuse(f
"malformed ledger: line {lineno} unreviewed row carries rationale/batch")
228 return lines[0], rows
231def _rationale_states(root: Path) -> dict[str, str]:
232 """Map each rationale category to its allowed ledger state."""
234 data = yaml.safe_load((root / _RATIONALES_PATH).read_text(encoding=
"utf-8"))
235 except OSError
as exc:
236 _refuse(f
"cannot read rationale vocabulary: {exc}")
237 except yaml.YAMLError
as exc:
238 _refuse(f
"malformed rationale vocabulary: {exc}")
240 categories = data[
"categories"]
241 return {name: spec[
"state"]
for name, spec
in categories.items()}
242 except (TypeError, KeyError, AttributeError)
as exc:
243 _refuse(f
"malformed rationale vocabulary: {exc}")
246def _identity_schema(root: Path) -> str:
247 """Read the batch identity-schema constant without importing check code."""
249 tree = ast.parse((root / _IDENTITY_MODULE).read_text(encoding=
"utf-8"))
250 except OSError
as exc:
251 _refuse(f
"cannot read identity module: {exc}")
252 except SyntaxError
as exc:
253 _refuse(f
"malformed identity module: {exc}")
254 for node
in ast.walk(tree):
256 isinstance(node, ast.Assign)
257 and len(node.targets) == 1
258 and isinstance(node.targets[0], ast.Name)
259 and node.targets[0].id == _IDENTITY_ATTR
260 and isinstance(node.value, ast.Constant)
261 and isinstance(node.value.value, str)
263 return node.value.value
264 _refuse(
"identity schema constant not found")
267def _existing_batch_ids(root: Path) -> set[str]:
268 """Return every committed batch id, failing closed on a broken file."""
270 data = yaml.safe_load((root / _BATCHES_PATH).read_text(encoding=
"utf-8"))
271 except OSError
as exc:
272 _refuse(f
"cannot read batches file: {exc}")
273 except yaml.YAMLError
as exc:
274 _refuse(f
"malformed batches file: {exc}")
276 return {str(record[
"id"])
for record
in data[
"batches"]}
277 except (TypeError, KeyError, AttributeError)
as exc:
278 _refuse(f
"malformed batches file: {exc}")
281def _live_bindings(root: Path) -> dict[str, str]:
282 """Map every live site_id to its current binding via the canonical scan."""
283 proc = subprocess.run(
286 "scripts/checks/check_suppressions.py",
297 inventory = json.loads(proc.stdout)
298 return {item[
"site_id"]: item[
"binding_sha256"]
for item
in inventory[
"suppressions"]}
299 except (json.JSONDecodeError, KeyError, TypeError)
as exc:
300 _refuse(f
"live inventory scan failed: {exc}")
303def _check_selection(site_ids: list[str], rows: list[list[str]]) -> list[list[str]]:
304 """Resolve each requested id to exactly one ledger row, failing closed."""
306 _refuse(
"no --site given: selection is required")
307 seen: set[str] = set()
308 selected: list[list[str]] = []
309 for site
in site_ids:
310 if not _HEX64_RE.match(site):
311 _refuse(f
"invalid site_id {site!r}: not a 64-hex identity")
313 _refuse(f
"ambiguous selection: {site[:12]}... requested twice")
315 matches = [row
for row
in rows
if row[0] == site]
317 _refuse(f
"unknown site_id {site[:12]}...: no ledger row")
319 _refuse(f
"ambiguous site_id {site[:12]}...: {len(matches)} ledger rows")
320 selected.append(matches[0])
324@dataclass(frozen=True)
326 """Shared review inputs: rationale, evidence, live bindings, vocabulary."""
331 states: dict[str, str]
334def _check_tsv_cell(field: str, value: str) ->
None:
335 """Reject TSV-breaking characters in a ledger-serialized review field."""
336 for bad, name
in ((
"\t",
"tab"), (
"\n",
"newline"), (
'"',
"double-quote")):
338 _refuse(f
"{field} must be a single line without {name}: TSV cell integrity")
341def _check_reviewable(selected: list[list[str]], ctx: ReviewContext) ->
None:
342 """Validate shared review preconditions for every selected row."""
343 if not ctx.rationale.strip():
344 _refuse(
"missing rationale: --rationale is required")
345 if not ctx.evidence.strip():
346 _refuse(
"missing evidence: --evidence is required")
347 _check_tsv_cell(
"rationale", ctx.rationale)
348 _check_tsv_cell(
"evidence", ctx.evidence)
349 if ctx.rationale
not in ctx.states:
350 _refuse(f
"unknown rationale {ctx.rationale!r}")
352 if row[2] !=
"unreviewed":
353 _refuse(f
"already reviewed: {row[0][:12]}... is {row[2]!r}, not unreviewed")
356def _require_state(ctx: ReviewContext, want: str) ->
None:
357 """Require the rationale to allow the target state, failing closed."""
358 if ctx.states[ctx.rationale] != want:
359 _refuse(f
"rationale {ctx.rationale!r} does not allow state {want!r}")
362@dataclass(frozen=True)
364 """One batch record's header plus its covered member rows."""
370 members: list[list[str]]
375@dataclass(frozen=True)
376class PlannedTransition:
377 """One validated review transition with its pre-mutation state preserved."""
387def _plan_retain(selected: list[list[str]], ctx: ReviewContext) -> list[PlannedTransition]:
388 """Validate a retain; preserve each row's pre-mutation state."""
389 _check_reviewable(selected, ctx)
390 _require_state(ctx,
"retain")
393 site, binding = row[0], row[1]
394 if site
not in ctx.live:
395 _refuse(f
"stale site {site[:12]}...: no live suppression; cannot retain")
396 if ctx.live[site] != binding:
397 _refuse(f
"stale binding {site[:12]}...: ledger no longer matches live; cannot retain")
400 row, row[2],
"retain", ctx.rationale, ctx.evidence,
"retain live site"
407 selected: list[list[str]], ctx: ReviewContext, replaced_by: list[str]
408) -> list[PlannedTransition]:
409 """Validate a retire; resolved needs a gone site, superseded live successors."""
410 for successor
in replaced_by:
411 if not _HEX64_RE.match(successor):
412 _refuse(f
"invalid successor {successor!r}: not a 64-hex identity")
413 _check_reviewable(selected, ctx)
414 target =
"superseded" if replaced_by
else "resolved"
415 _require_state(ctx, target)
420 _refuse(f
"live site {site[:12]}...: retire refuses a standing suppression")
421 if target ==
"resolved":
424 row, row[2], target, ctx.rationale, ctx.evidence,
"resolve vanished site"
428 if site
in replaced_by:
429 _refuse(f
"successor loop {site[:12]}...: cannot replace itself")
430 for successor
in replaced_by:
431 if successor
not in ctx.live:
432 _refuse(f
"dead successor {successor[:12]}...: replacement is not live")
433 tokens =
" ".join(f
"replaced-by:{entry}" for entry
in replaced_by)
436 row, row[2], target, ctx.rationale, f
"{ctx.evidence} {tokens}",
"supersede"
442def _new_batch_id(existing: set[str], now: datetime.datetime) -> str:
443 """Mint a unique selective-review batch id, failing closed on collision."""
444 candidate = f
"selective-review-{now.strftime('%Y%m%d-%H%M%S')}"
445 if candidate
in existing:
446 _refuse(f
"batch id collision {candidate!r}: retry the review action")
450def _batch_digest(members: list[list[str]]) -> str:
451 """Digest ordered member rows exactly like the gate's batch check."""
452 payload =
"\n".join(f
"{row[0]}\t{row[1]}\t{row[2]}\t{row[3]}\t{row[5]}" for row
in members)
453 return hashlib.sha256(payload.encode(
"utf-8")).hexdigest()
456def _batch_record(spec: BatchSpec) -> str:
457 """Render one batch record in the committed batches-file shape."""
458 sites =
", ".join(row[0][:12]
for row
in spec.members)
460 - id: {spec.batch_id}
461 authority: repository-suppression-review
463 identity_schema: {spec.schema}
464 assigned_rows: {len(spec.members)}
465 rows_sha256: {spec.digest}
466 partition: "selective {spec.action} of {len(spec.members)} ledger row(s): {sites}"
472def _describe(planned: list[PlannedTransition], batch_id: str) -> list[str]:
473 """Render one human-readable line per intended transition."""
475 f
"{item.note}: {item.row[0]} {item.old_state} -> {item.new_state} "
476 f
"rationale={item.rationale} batch={batch_id}"
481def run_selective(args: argparse.Namespace) -> int:
482 """Execute one selective retain or retire review action."""
485 header, rows = _parse_ledger(
"\n".join(_read_ledger()) +
"\n")
487 rationale=args.rationale,
488 evidence=args.evidence,
489 live=_live_bindings(root),
490 states=_rationale_states(root),
492 schema = _identity_schema(root)
493 existing_batches = _existing_batch_ids(root)
494 if args.action ==
"retain":
495 planned = _plan_retain(_check_selection(args.site, rows), ctx)
497 planned = _plan_retire(_check_selection(args.site, rows), ctx, args.replaced_by)
498 now = datetime.datetime.now(tz=datetime.UTC)
499 batch_id = _new_batch_id(existing_batches, now)
501 item.row[2], item.row[3], item.row[4], item.row[5] = (
507 wanted = {item.row[0]
for item
in planned}
508 members = [row
for row
in rows
if row[0]
in wanted]
509 digest = _batch_digest(members)
511 print(f
"dry-run {args.action}: no files written")
512 for line
in _describe(planned, batch_id):
514 print(f
" batch {batch_id} would cover {len(members)} row(s)")
516 _write_ledger([header] + [
"\t".join(row)
for row
in rows])
517 batches_path = root / _BATCHES_PATH
518 batches_yaml = batches_path.read_text(encoding=
"utf-8").rstrip()
521 date=now.strftime(
"%Y-%m-%d"),
526 evidence=args.evidence,
528 batches_path.write_text(batches_yaml + _batch_record(spec), encoding=
"utf-8")
529 except ReviewError
as exc:
530 print(f
"review_suppressions.py: refused: {exc}", file=sys.stderr)
532 for line
in _describe(planned, batch_id):
534 print(f
"wrote batch {batch_id} covering {len(members)} row(s)")
535 print(
"Run `just quality::local::gate suppressions` to verify.")
539def _fixture_ledger() -> str:
540 """Build a synthetic ledger exercising every transition direction."""
542 f
"{'a' * 64}\t{'1' * 64}\tunreviewed\t\t\t",
543 f
"{'b' * 64}\t{'2' * 64}\tunreviewed\t\t\t",
544 f
"{'c' * 64}\t{'3' * 64}\tunreviewed\t\t\t",
545 f
"{'d' * 64}\t{'4' * 64}\tretain\treviewed-scope-authority\tbatch-old\told evidence",
546 f
"{'e' * 64}\t{'5' * 64}\tresolved\tresolved-construct-retired\tbatch-old\told evidence",
547 f
"{'f' * 64}\t{'6' * 64}\tunreviewed\t\t\t",
549 return _LEDGER_HEADER +
"\n" +
"\n".join(rows) +
"\n"
552def _fixture_live() -> dict[str, str]:
553 """Live map: A/B/D live with matching bindings, F live with drifted binding."""
562def _fixture_states() -> dict[str, str]:
563 """Rationale vocabulary matching the committed categories under review."""
565 "reviewed-scope-authority":
"retain",
566 "tool-false-positive":
"retain",
567 "resolved-construct-retired":
"resolved",
568 "superseded-identity-rebinding":
"superseded",
573 rationale: str, evidence: str, live: dict[str, str], states: dict[str, str]
575 """Build one review context for selftest fixtures."""
576 return ReviewContext(rationale=rationale, evidence=evidence, live=live, states=states)
579def _refused(failures: list[str], label: str, func: Callable[..., object], *args: object) ->
None:
580 """Assert one guard refuses; record a failure when it accepts."""
585 failures.append(f
"{label}: expected refusal, action was accepted")
588def _test_retain(failures: list[str], a: str, b: str) ->
None:
589 """Prove selective retain of one and many rows with byte-exact isolation."""
590 live, states = _fixture_live(), _fixture_states()
591 _header, rows = _parse_ledger(_fixture_ledger())
592 planned = _plan_retain(
593 _check_selection([a], rows),
594 _ctx(
"reviewed-scope-authority",
"selftest evidence", live, states),
596 if not (len(planned) == 1
and planned[0].new_state ==
"retain"):
597 failures.append(
"retain-one: expected exactly one retain transition")
598 before = _fixture_ledger().splitlines()
600 item.row[2], item.row[3], item.row[4], item.row[5] = (
606 after = [_LEDGER_HEADER] + [
"\t".join(row)
for row
in rows]
607 for old, new
in zip(before[1:], after[1:], strict=
True):
608 if old.split(
"\t")[0] == a:
609 if new.split(
"\t")[2] !=
"retain":
610 failures.append(
"retain-one: selected row did not transition")
612 failures.append(f
"retain-one: unrelated row changed {old[:12]}")
613 _header, rows = _parse_ledger(_fixture_ledger())
614 planned = _plan_retain(
615 _check_selection([a, b], rows),
616 _ctx(
"tool-false-positive",
"selftest evidence", live, states),
618 if {item.row[0]
for item
in planned} != {a, b}:
619 failures.append(
"retain-many: expected transitions for exactly the requested rows")
624 case: tuple[str, str, ReviewContext],
626 """Assert one retain selection is rejected for its stated reason."""
627 label, selection, context = case
628 _header, rows = _parse_ledger(_fixture_ledger())
633 _check_selection([selection], rows),
638def _test_retain_refusals(failures: list[str], a: str, c: str, d: str, f: str) ->
None:
639 """Prove stale, drifted, mis-rationaled, and reviewed retains refuse."""
640 live, states = _fixture_live(), _fixture_states()
642 (
"retain-stale", c, _ctx(
"reviewed-scope-authority",
"selftest evidence", live, states)),
644 "retain-drifted-binding",
646 _ctx(
"reviewed-scope-authority",
"selftest evidence", live, states),
648 (
"retain-missing-rationale", a, _ctx(
"",
"selftest evidence", live, states)),
650 "retain-missing-evidence",
652 _ctx(
"reviewed-scope-authority",
" ", live, states),
655 "retain-unknown-rationale",
657 _ctx(
"no-such-category",
"selftest evidence", live, states),
660 "retain-wrong-state-rationale",
662 _ctx(
"resolved-construct-retired",
"selftest evidence", live, states),
664 (
"retain-reviewed", d, _ctx(
"reviewed-scope-authority",
"selftest evidence", live, states)),
665 (
"retain-quoted-evidence", a, _ctx(
"reviewed-scope-authority",
'say "hi"', live, states)),
667 for label, selection, context
in cases:
668 _retain_refusal(failures, (label, selection, context))
673 case: tuple[str, str, ReviewContext, list[str]],
675 """Assert one retire selection is rejected for its stated reason."""
676 label, selection, context, successors = case
677 _header, rows = _parse_ledger(_fixture_ledger())
682 _check_selection([selection], rows),
688def _test_retire(failures: list[str], a: str, b: str, c: str, e: str) ->
None:
689 """Prove stale retirement, supersede tokens, and live-row refusal."""
690 live, states = _fixture_live(), _fixture_states()
691 _header, rows = _parse_ledger(_fixture_ledger())
692 planned = _plan_retire(
693 _check_selection([c], rows),
694 _ctx(
"resolved-construct-retired",
"construct gone", live, states),
697 if not (len(planned) == 1
and planned[0].new_state ==
"resolved"):
698 failures.append(
"retire-stale: expected exactly one resolve transition")
699 _header, rows = _parse_ledger(_fixture_ledger())
700 planned = _plan_retire(
701 _check_selection([c], rows),
702 _ctx(
"superseded-identity-rebinding",
"rebound decision", live, states),
705 if len(planned) != 1
or planned[0].new_state !=
"superseded":
706 failures.append(
"retire-supersede: expected exactly one supersede transition")
707 elif f
"replaced-by:{b}" not in planned[0].evidence:
708 failures.append(
"retire-supersede: replaced-by token missing from evidence")
710 (
"retire-live", a,
"resolved-construct-retired",
"construct gone", []),
711 (
"retire-self-successor", c,
"superseded-identity-rebinding",
"rebound decision", [c]),
713 "retire-dead-successor",
715 "superseded-identity-rebinding",
719 (
"retire-resolved", e,
"resolved-construct-retired",
"construct gone", []),
720 (
"retire-retain-rationale", c,
"reviewed-scope-authority",
"construct gone", []),
722 for label, selection, rationale, evidence, successors
in cases:
725 (label, selection, _ctx(rationale, evidence, live, states), successors),
729def _test_selection(failures: list[str], a: str) ->
None:
730 """Prove unknown, duplicated, malformed, and empty selections refuse."""
731 _header, rows = _parse_ledger(_fixture_ledger())
732 _refused(failures,
"unknown-id", _check_selection, [
"0" * 64], rows)
733 _header, rows = _parse_ledger(_fixture_ledger())
734 _refused(failures,
"duplicate-flags", _check_selection, [a, a], rows)
735 _header, rows = _parse_ledger(_fixture_ledger())
736 _refused(failures,
"bad-hex-id", _check_selection, [
"zzz"], rows)
737 _header, rows = _parse_ledger(_fixture_ledger())
738 _refused(failures,
"empty-selection", _check_selection, [], rows)
739 dup_text = _fixture_ledger() + f
"{a}\t{'1' * 64}\tunreviewed\t\t\t\n"
740 _header, dup_rows = _parse_ledger(dup_text)
741 _refused(failures,
"duplicate-rows", _check_selection, [a], dup_rows)
744def _test_malformed(failures: list[str]) ->
None:
745 """Prove malformed ledgers fail closed before any selection runs."""
747 "wrong\n" +
"a" * 64 +
"\t" +
"1" * 64 +
"\tunreviewed\t\t\n",
748 _LEDGER_HEADER +
"\n" +
"a" * 64 +
"\t" +
"1" * 64 +
"\tunreviewed\n",
749 _LEDGER_HEADER +
"\nzz\t" +
"1" * 64 +
"\tunreviewed\t\t\n",
750 _LEDGER_HEADER +
"\n" +
"a" * 64 +
"\t" +
"1" * 64 +
"\tnope\t\t\n",
751 _LEDGER_HEADER +
"\n" +
"a" * 64 +
"\t" +
"1" * 64 +
"\tunreviewed\t\tx\n",
753 for index, text
in enumerate(bad):
754 _refused(failures, f
"malformed-{index}", _parse_ledger, text)
757def _test_batch(failures: list[str], a: str) ->
None:
758 """Prove batch-id collision refusal and the gate digest formula."""
759 stamp = datetime.datetime(2024, 1, 2, 3, 4, 5, tzinfo=datetime.UTC)
761 _new_batch_id({
"selective-review-20240102-030405"}, stamp)
762 failures.append(
"batch-collision: expected refusal")
765 batch_id = _new_batch_id(set(), stamp)
766 if batch_id !=
"selective-review-20240102-030405":
767 failures.append(
"batch-id-shape: unexpected minted id")
768 member = [a,
"1" * 64,
"retain",
"reviewed-scope-authority", batch_id,
"selftest evidence"]
769 payload = f
"{a}\t{'1' * 64}\tretain\treviewed-scope-authority\tselftest evidence"
770 if _batch_digest([member]) != hashlib.sha256(payload.encode(
"utf-8")).hexdigest():
771 failures.append(
"batch-digest: formula drifted from the gate payload shape")
774def _test_reconcile_preconditions(failures: list[str], a: str, b: str, c: str) ->
None:
775 """Prove planned rows satisfy the gate reconciler's preconditions.
777 The gate approves a retain row only for a live site with an exact binding
778 match, and closes a resolved row only for a vanished site; a superseded
779 row additionally needs every replaced-by successor live. These checks
780 mirror scripts/checks/suppression_ledger.py::_reconcile_row without
781 importing gate code into the review tool.
784 def live_ok(site: str, binding: str, live: dict[str, str]) -> bool:
785 return live.get(site) == binding
787 live, states = _fixture_live(), _fixture_states()
788 _header, rows = _parse_ledger(_fixture_ledger())
789 planned = _plan_retain(
790 _check_selection([a], rows),
791 _ctx(
"reviewed-scope-authority",
"selftest evidence", live, states),
794 if item.new_state !=
"retain" or states[item.rationale] !=
"retain":
795 failures.append(
"precondition-retain: state/rationale mismatch")
796 if not live_ok(item.row[0], item.row[1], live):
797 failures.append(
"precondition-retain: site not live with exact binding")
798 _header, rows = _parse_ledger(_fixture_ledger())
799 planned = _plan_retire(
800 _check_selection([c], rows),
801 _ctx(
"resolved-construct-retired",
"construct gone", live, states),
805 if item.new_state !=
"resolved" or item.row[0]
in live:
806 failures.append(
"precondition-resolve: site must be vanished")
807 _header, rows = _parse_ledger(_fixture_ledger())
808 planned = _plan_retire(
809 _check_selection([c], rows),
810 _ctx(
"superseded-identity-rebinding",
"rebound decision", live, states),
814 if item.new_state !=
"superseded" or item.row[0]
in live:
815 failures.append(
"precondition-supersede: old site must be vanished")
816 if f
"replaced-by:{b}" not in item.evidence
or b
not in live:
817 failures.append(
"precondition-supersede: successor must be live and named")
820def _test_tsv_cells(failures: list[str], a: str, c: str) ->
None:
821 """Prove tab/newline/quote review fields refuse on both actions."""
822 live, states = _fixture_live(), _fixture_states()
823 for label, rationale, evidence
in [
824 (
"tab-evidence",
"reviewed-scope-authority",
"audit\tprobe"),
825 (
"newline-evidence",
"reviewed-scope-authority",
"audit\nprobe"),
826 (
"quote-evidence",
"reviewed-scope-authority",
'say "hi"'),
827 (
"tab-rationale",
"reviewed\tscope",
"selftest evidence"),
828 (
"newline-rationale",
"reviewed\nscope",
"selftest evidence"),
830 _header, rows = _parse_ledger(_fixture_ledger())
835 _check_selection([a], rows),
836 _ctx(rationale, evidence, live, states),
838 _header, rows = _parse_ledger(_fixture_ledger())
843 _check_selection([c], rows),
844 _ctx(rationale, evidence, live, states),
847 before = _fixture_ledger()
848 _header, rows = _parse_ledger(before)
849 with suppress(ReviewError):
851 _check_selection([a], rows),
852 _ctx(
"reviewed-scope-authority",
"audit\tprobe", live, states),
854 if [_LEDGER_HEADER] + [
"\t".join(row)
for row
in rows] != before.splitlines():
855 failures.append(
"refused-plan-untouched: failed validation mutated rows")
858def _apply_like_run(planned: list[PlannedTransition], batch: str) ->
None:
859 """Mutate rows exactly as run_selective does after successful planning."""
861 item.row[2], item.row[3], item.row[4], item.row[5] = (
869def _test_write_reporting(failures: list[str], a: str, b: str, c: str) ->
None:
870 """Prove real writes report exact old states without changing outcomes."""
871 live, states = _fixture_live(), _fixture_states()
873 (
"retain",
"reviewed-scope-authority",
"selftest evidence", [],
"unreviewed -> retain"),
874 (
"resolved",
"resolved-construct-retired",
"construct gone", [],
"unreviewed -> resolved"),
876 for want_new, rationale, evidence, replaced, want_line
in specs:
877 _header, rows = _parse_ledger(_fixture_ledger())
878 before_ids = [row[0]
for row
in rows]
879 target = a
if want_new ==
"retain" else c
880 ctx = _ctx(rationale, evidence, live, states)
881 if want_new ==
"retain":
882 planned = _plan_retain(_check_selection([target], rows), ctx)
884 planned = _plan_retire(_check_selection([target], rows), ctx, replaced)
885 _apply_like_run(planned,
"batch-t")
886 lines = _describe(planned,
"batch-t")
887 if len(lines) != 1
or want_line
not in lines[0]:
888 failures.append(f
"write-report-{want_new}: expected exact {want_line!r}")
889 if [row[0]
for row
in rows] != before_ids:
890 failures.append(f
"write-report-{want_new}: row ordering changed")
891 members = [row
for row
in rows
if row[0] == target]
892 payload =
"\n".join(f
"{row[0]}\t{row[1]}\t{row[2]}\t{row[3]}\t{row[5]}" for row
in members)
893 if _batch_digest(members) != hashlib.sha256(payload.encode(
"utf-8")).hexdigest():
894 failures.append(f
"write-report-{want_new}: batch digest changed")
895 fixture = _fixture_ledger()
897 f
"write-report-{want_new}: unrelated row changed"
899 if row[0] != target
and "\t".join(row)
not in fixture
901 _header, rows = _parse_ledger(_fixture_ledger())
902 planned = _plan_retire(
903 _check_selection([c], rows),
904 _ctx(
"superseded-identity-rebinding",
"rebound decision", live, states),
907 _apply_like_run(planned,
"batch-t")
908 lines = _describe(planned,
"batch-t")
909 if len(lines) != 1
or "unreviewed -> superseded" not in lines[0]:
910 failures.append(
"write-report-superseded: expected exact old state")
911 if f
"replaced-by:{b}" not in planned[0].evidence:
912 failures.append(
"write-report-superseded: successor token missing")
915def _test_dry_run(failures: list[str], a: str, b: str) ->
None:
916 live, states = _fixture_live(), _fixture_states()
917 before_text = _fixture_ledger()
918 _header, rows = _parse_ledger(before_text)
919 planned = _plan_retain(
920 _check_selection([a, b], rows),
921 _ctx(
"reviewed-scope-authority",
"selftest evidence", live, states),
923 out =
"\n".join(f
" {line}" for line
in _describe(planned,
"batch-dry"))
924 if a
not in out
or b
not in out
or "unreviewed -> retain" not in out:
925 failures.append(
"dry-run-names-rows: output does not identify transitions")
926 current = [_LEDGER_HEADER] + [
"\t".join(row)
for row
in rows]
927 if current != before_text.splitlines():
928 failures.append(
"dry-run-ledger-untouched: planning mutated rows")
931def run_selftest() -> int:
932 """Prove selective retain/retire guards both ways on synthetic fixtures."""
933 failures: list[str] = []
934 a, b, c, d, e, f = (
"a" * 64,
"b" * 64,
"c" * 64,
"d" * 64,
"e" * 64,
"f" * 64)
935 _test_retain(failures, a, b)
936 _test_retain_refusals(failures, a, c, d, f)
937 _test_retire(failures, a, b, c, e)
938 _test_selection(failures, a)
939 _test_malformed(failures)
940 _test_batch(failures, a)
941 _test_reconcile_preconditions(failures, a, b, c)
942 _test_tsv_cells(failures, a, c)
943 _test_write_reporting(failures, a, b, c)
944 _test_dry_run(failures, a, b)
946 print(
"review_suppressions.py --selftest: FAIL")
947 for failure
in failures:
948 print(f
" - {failure}")
951 "review_suppressions.py --selftest: PASS "
952 "(selective retain/retire, fail-closed guards, reconciler preconditions, dry-run)"
957if __name__ ==
"__main__":
void main(void)
The application entry point Reset_Handler hands control to.