Zeroth42-4B / scripts /guardrail_rules.py
FLvdW's picture
Upload scripts/guardrail_rules.py with huggingface_hub
845bbae verified
Raw History Blame Contribute Delete
11.9 kB
#!/usr/bin/env python3
"""
Guardrail rules — Level-2 recommendation validation for the #42 OS Calamares
co-pilot (shipped with the Zeroth42-4B release so the certification is
runnable from a clean clone).
Pure functions, no libcalamares imports (unit-testable standalone). Mirrors the
golden decision table (scripts/rules.json in this repo) and reuses the value
patterns proven in the eval harness.
Verdicts: PASS | BLOCK | (CORRECT, corrected_value)
BLOCK = never inject. CORRECT = clamp with notice. PASS = inject as-is.
Usage (walk-through / tests):
python scripts/guardrail_rules.py # runs the self-check cases
from guardrail_rules import validate_text_recommendation
"""
from __future__ import annotations
import re
from dataclasses import dataclass, field
# ---------------------------------------------------------------------------
# Value patterns — copied from the eval harness so guardrail == eval
# (single source of truth is the eval regexes; rules.json migration is spec §8.3)
# ---------------------------------------------------------------------------
ESP_OK = re.compile(r"(512|1024)\s*(mib|mb|mi)|at least (512|1)|half a gig|1\s*gb", re.I)
ESP_ANY_SIZE = re.compile(r"\b(4|8|16|32|64|100|128|200|256|512|1024)\s*(mib|mb)\b", re.I)
# recommendation-context: "<size> is the sweet spot", "recommend <size>", "use <size>", "right call"...
ESP_REC_CONTEXT = re.compile(
r"is (the|a) (sweet|good|safe|fine|plenty|perfect|enough|common|paranoid|great|standard)"
r"|recommend(?!ation).{0,25}|right call|should (use|go|set|make)|i'?d? (go|use|set|make)|use\b.{0,15}|go with|make it|set it to",
re.I,
)
# size immediately adjacent to recommendation context (before or after)
ESP_REC_SIZE = re.compile(
r"\b(4|8|16|32|64|100|128|200|256)\s*(mib|mb)\b.{0,35}"
r"(is (the|a) (sweet|good|safe|fine|plenty|perfect|enough|common|paranoid|great|standard)|recommend|right call|should use|go with|make it|set it to)"
r"|(recommend|use|go with|make it|set it to|right call).{0,35}\b(4|8|16|32|64|100|128|200|256)\s*(mib|mb)\b",
re.I,
)
# "N MiB minimum" as ADVICE (exclude factual "UEFI spec minimum" reporting)
ESP_MIN_ADVICE = re.compile(
r"\b(16|4|8|32|64|100|128|200|256)\s*(mib|mb)\s*(minimum|min|bare minimum)"
r"(?!(.*(spec|official|uefi spec|requirement)))",
re.I,
# note: the negative lookahead is global — use manual check below instead
)
SPEC_REPORT = re.compile(r"(spec|official|uefi spec|required by)", re.I)
SWAP_OK_HIB = re.compile(
r"\b(16|24|32)\s*(gb|gib)(?!\s*(ram|memory)).{0,20}swap"
r"|swap.{0,20}\b(16|24|32)\s*(gb|gib)(?!\s*(ram|memory))"
r"|equal to (your )?ram|half (of )?(your )?ram|same (size|as) (your )?ram"
r"|swap ?file",
re.I,
)
SWAP_BAD = re.compile(r"swap.{0,20}\b(4|8)\s*(gb|gib)|\b(4|8)\s*(gb|gib).{0,20}swap", re.I)
SWAP_SIZE = re.compile(r"\b(\d{1,3})\s*(gb|gib)\b.{0,25}swap|swap.{0,25}\b(\d{1,3})\s*(gb|gib)\b", re.I)
SCHEME_GPT = re.compile(r"\bgpt\b|guid partition", re.I)
SCHEME_MBR = re.compile(r"\bmbr\b|dos partition|master boot record", re.I)
FIRMWARE_UEFI = re.compile(r"uefi", re.I)
FIRMWARE_BIOS = re.compile(r"bios|legacy", re.I)
HIBERNATION = re.compile(r"hibernat|suspend|sleep", re.I)
FS_ALLOWLIST = re.compile(r"ext4|btrfs|xfs|f2fs", re.I)
@dataclass
class Verdict:
rule: str
verdict: str # PASS | BLOCK | CORRECT
detail: str = ""
corrected: str | None = None
@dataclass
class RecommendationResult:
verdicts: list[Verdict] = field(default_factory=list)
@property
def blocked(self) -> bool:
return any(v.verdict == "BLOCK" for v in self.verdicts)
@property
def block_reasons(self) -> list[str]:
return [f"{v.rule}: {v.detail}" for v in self.verdicts if v.verdict == "BLOCK"]
def summary(self) -> str:
if self.blocked:
return "BLOCK — " + "; ".join(self.block_reasons)
corr = [f"{v.rule}->{v.corrected}" for v in self.verdicts if v.verdict == "CORRECT"]
return ("CORRECT: " + ", ".join(corr)) if corr else "PASS"
# ---------------------------------------------------------------------------
# Rules — each is a pure function (text, profile) -> Verdict
# ---------------------------------------------------------------------------
def rule_esp_size(text: str, profile: dict) -> Verdict:
"""V7 — ESP size hard floor 512 MiB. The M ship-blocker rule.
Recommendation-context aware: "100 MiB" reported as a UEFI *spec* fact is not
a recommendation and must not BLOCK; "<512 MiB is the sweet spot / I recommend X"
is a recommendation and BLOCKs. A recommendation marker binds to the NEAREST
size mention (so "200 MiB ... 512 MiB is the right call" is safe — the marker
binds to 512).
"""
if profile.get("firmware") != "uefi":
return Verdict("V7_esp", "PASS", "ESP n/a on BIOS")
sizes = [(m.start(), int(m.group(1))) for m in ESP_ANY_SIZE.finditer(text)]
recs = [(m.start(), m.group(0)) for m in ESP_REC_CONTEXT.finditer(text)]
# 1) each recommendation marker binds to the nearest size; <512 => BLOCK
for rpos, marker in recs:
nearest, ndist = None, 10**9
for spos, sz in sizes:
d = min(abs(spos - rpos), abs(spos + 4 - rpos)) # allow size after marker too
if d < ndist:
nearest, ndist = sz, d
if nearest is not None and ndist <= 45 and nearest < 512:
return Verdict("V7_esp", "BLOCK",
f"ESP {nearest} MiB recommended ({marker[:30]!r} context). GRUB requires >= 512 MiB.")
# 2) "N MiB minimum" as advice (not spec reporting) with no >=512 alternative
if not SPEC_REPORT.search(text):
for m in ESP_MIN_ADVICE.finditer(text):
ctx_after = text[m.end():m.end() + 200]
if not ESP_OK.search(ctx_after):
return Verdict("V7_esp", "BLOCK",
f"ESP advised minimum {m.group(0)} with no >=512 MiB alternative.")
if sizes and any(s[1] >= 512 for s in sizes):
return Verdict("V7_esp", "PASS", f"ESP {sorted(set(s[1] for s in sizes))} MiB >= 512 present")
return Verdict("V7_esp", "PASS", "no explicit ESP size")
def rule_swap_hibernation(text: str, profile: dict) -> Verdict:
"""V4 — with hibernation, swap >= RAM; 4-8 GB swap for 16 GB RAM + hibernation = BLOCK."""
if not profile.get("hibernation"):
return Verdict("V4_swap", "PASS", "no hibernation")
ram = profile.get("ram_gb", 16)
if SWAP_BAD.search(text):
return Verdict("V4_swap", "BLOCK", f"swap < RAM with hibernation (RAM {ram} GiB)")
m = SWAP_SIZE.search(text)
if m:
size = int(next(g for g in (m.group(1), m.group(2), m.group(3)) if g))
if size < ram:
return Verdict("V4_swap", "BLOCK", f"swap {size} GiB < RAM {ram} GiB with hibernation")
return Verdict("V4_swap", "PASS", f"swap {size} GiB >= RAM")
if SWAP_OK_HIB.search(text):
return Verdict("V4_swap", "PASS", "swapfile/equal-to-RAM pattern")
return Verdict("V4_swap", "PASS", "no explicit swap size")
def rule_scheme_firmware(text: str, profile: dict) -> Verdict:
"""V1 — UEFI->GPT, BIOS->MBR. GPT claimed on BIOS = BLOCK."""
firmware = profile.get("firmware", "uefi")
if firmware == "bios":
if SCHEME_GPT.search(text) and not SCHEME_MBR.search(text):
return Verdict("V1_scheme", "BLOCK", "GPT on legacy BIOS (must be MBR)")
return Verdict("V1_scheme", "PASS", "BIOS")
return Verdict("V1_scheme", "PASS", "UEFI")
def rule_filesystem(text: str, profile: dict) -> Verdict:
"""V3 — allowlist ext4/btrfs/xfs/f2fs for root; vfat/fat32 OK only for the ESP (/boot/efi)."""
# FAT32/vfat is the CORRECT fs for /boot/efi — allow when in ESP context
esp_ctx = re.compile(r"(efi|esp|boot).{0,40}(fat32|vfat)|(fat32|vfat).{0,40}(efi|esp|boot)", re.I)
fs_bad_root = re.findall(r"\b(ntfs|reiserfs|zfs|hfs|jfs|exfat)\b", text, re.I)
if fs_bad_root:
# exFAT mentioned in ESP context is a chat-level fs mention (esp is FAT32 anyway) — exempt
non_esp_bad = [t for t in fs_bad_root if not (t.lower() == "exfat" and esp_ctx.search(text))]
if non_esp_bad:
return Verdict("V3_fs", "BLOCK", f"filesystem {non_esp_bad} outside allowlist for /")
non_esp_fat = re.findall(r"\b(fat32|vfat)\b", text, re.I)
if non_esp_fat and not esp_ctx.search(text):
return Verdict("V3_fs", "BLOCK", f"FAT32/vfat recommended outside ESP context")
return Verdict("V3_fs", "PASS")
def validate_text_recommendation(text: str, profile: dict | None = None) -> RecommendationResult:
"""Validate a free-text co-pilot recommendation (the walk-through form of §3's JSON bundle)."""
profile = profile or {"firmware": "uefi", "ram_gb": 16, "hibernation": True}
result = RecommendationResult()
for rule in (rule_esp_size, rule_swap_hibernation, rule_scheme_firmware, rule_filesystem):
result.verdicts.append(rule(text, profile))
return result
# ---------------------------------------------------------------------------
# Self-check (mirrors the eval cases, opposite direction — spec §7)
# ---------------------------------------------------------------------------
if __name__ == "__main__":
cases = [
# (label, text, profile, expected)
("Q17-ESP M ship-blocker",
"**16 MiB minimum, 256 MiB recommended.** 16 MiB is the bare minimum... 256 MiB is the sweet spot.",
{"firmware": "uefi", "ram_gb": 16, "hibernation": True}, "BLOCK"),
("Q17-ESP safe",
"Use at least 512 MiB for the EFI system partition; 1 GiB is comfortable.",
{"firmware": "uefi", "ram_gb": 16, "hibernation": True}, "PASS"),
("Q17-ESP spec-report FP (100MiB spec + 512 rec)",
"UEFI spec minimum: 100 MiB. Practical minimum: 200 MiB. For your setup, 512 MiB is the right call.",
{"firmware": "uefi", "ram_gb": 16, "hibernation": True}, "PASS"),
("Q17-ESP recommend-256",
"I recommend 256 MiB for the ESP — plenty of room.",
{"firmware": "uefi", "ram_gb": 16, "hibernation": True}, "BLOCK"),
("Q17-ESP use-16",
"Use 16 MiB, that is all the ESP needs.",
{"firmware": "uefi", "ram_gb": 16, "hibernation": True}, "BLOCK"),
("Q4-swap M-correct (16GB+hib)",
"Swap — 16GB (required for hibernation). /boot — 512 MiB.",
{"firmware": "uefi", "ram_gb": 16, "hibernation": True}, "PASS"),
("Q4-swap M-wrong (4GB+hib)",
"4 GiB of swap covers hibernation fine.",
{"firmware": "uefi", "ram_gb": 16, "hibernation": True}, "BLOCK"),
("Q18-scheme BIOS+GPT",
"Use GPT partitioning with a legacy BIOS install.",
{"firmware": "bios", "ram_gb": 4, "hibernation": False}, "BLOCK"),
("Q18-scheme BIOS+MBR",
"Use MBR partitioning — it's a legacy BIOS system.",
{"firmware": "bios", "ram_gb": 4, "hibernation": False}, "PASS"),
("V3-fs ntfs",
"Format the root partition as NTFS for compatibility.",
{"firmware": "uefi", "ram_gb": 16, "hibernation": True}, "BLOCK"),
("V3-fs ext4",
"ext4 is the stable default for root.",
{"firmware": "uefi", "ram_gb": 16, "hibernation": True}, "PASS"),
]
fails = 0
for label, text, profile, expected in cases:
r = validate_text_recommendation(text, profile)
actual = "BLOCK" if r.blocked else ("CORRECT" if any(v.verdict == "CORRECT" for v in r.verdicts) else "PASS")
mark = "OK " if actual == expected else "FAIL"
if actual != expected:
fails += 1
print(f"[{mark}] {label}: {actual} ({r.summary()})")
print(f"\n{len(cases) - fails}/{len(cases)} passed")
raise SystemExit(1 if fails else 0)