#!/usr/bin/env python3 """Measured U1 integration regression and exact pre-update preservation audit.""" import hashlib import json from pathlib import Path import re import tempfile import uuid import pcbnew as p from shapely.geometry import Polygon from add_device_silkscreen import add_outlines, NS as SILK_NS, xy, outline_lines from update_u1_graphics import verify, NS as U1_NS from remove_draft_markers import expected_tree ROOT = Path(__file__).resolve().parent PCB = ROOT / "serial-carrier.kicad_pcb" BASE = ROOT / "validation/pcb-before-u1-measured.kicad_pcb" def parse(path): tokens = iter(re.findall(r'"(?:\\.|[^"\\])*"|[()]|[^\s()]+', path.read_text())) def group(): result = [] for token in tokens: if token == ')': return result result.append(group() if token == '(' else token) raise ValueError("Unterminated S-expression") assert next(tokens) == '(' return group() def ident(item): return next((v[1].strip('"') for v in item if isinstance(v,list) and v[0]=='uuid'), None) def canonical(item, excluded=frozenset()): # KiCad can reorder independent board items when saving; compare their full # parsed contents, not byte order. Pad coordinates/netnames/UUIDs remain exact. if not isinstance(item, list): return item values = [canonical(v, excluded) for v in item if not isinstance(v,list) or ident(v) not in excluded] if item and item[0] in ("kicad_pcb", "footprint"): return ([v for v in values if not isinstance(v,list)] + sorted((v for v in values if isinstance(v,list)), key=repr)) # Coordinates, polygon vertex order, text and other ordered values must # remain ordered: sorting these could conceal an X/Y swap or zone edit. return values def main(): assert hashlib.sha256(BASE.read_bytes()).hexdigest() == "53e36538c0669604457b9e1a9ff1b1e20422970e8f9f356088c4581868ad5045", "Pre-update snapshot changed" assert hashlib.sha256((ROOT/"serial-carrier.kicad_pro").read_bytes()).hexdigest() == "9ad5b992785ebfe01fa7ea4e1aa391a2c7d7f1f2c3b1c13136173c301ecb6516", "Project settings changed" before = PCB.read_bytes() board = p.LoadBoard(str(PCB)) verify(board) silk_ids = {str(uuid.uuid5(SILK_NS,str(i))) for i in range(4096)} u1_ids = {str(uuid.uuid5(U1_NS,str(i))) for i in range(6)} # The original baseline retains historical draft labels. Permit only the # exact requested title/revision/comment edits and removal of the silk label. # Keep proving the original integration against its original evidence, then # audit the explicitly authorized subsequent placement/routing delta. from validate_group_alignment import audit, BASE as GROUP_BASE assert canonical(expected_tree(parse(BASE)), silk_ids) == canonical(parse(GROUP_BASE), silk_ids | u1_ids), "Historical U1 integration changed" audit() generated = [g for g in board.GetDrawings() if g.m_Uuid.AsString() in silk_ids] assert not any(isinstance(g,p.PCB_TEXT) and g.GetText()=="HEADER GUIDES" for g in board.GetDrawings()) carrier = Polygon([(136,100),(182,100),(182,180),(100,180),(100,119),(136,119)]) mouths = [] for g in generated: assert g.GetLayer() == p.F_SilkS for edge in outline_lines(g): assert carrier.covers(edge.buffer(p.ToMM(g.GetWidth())/2)), "Silk outside carrier/notch" if g.GetWidth() == p.FromMM(0.3): mouths.append(g) assert len(mouths) == 2 assert sorted((xy(g.GetStart()),xy(g.GetEnd())) for g in mouths) == [ ((110.795,177.705),(119.665,177.705)), ((122.195,177.705),(131.065,177.705))] # A second refresh must reproduce all drawing geometry and UUIDs. add_outlines(board) with tempfile.TemporaryDirectory(prefix="u1-regression-") as temporary: result = Path(temporary)/PCB.name assert p.SaveBoard(str(result),board,True) assert canonical(parse(result)) == canonical(parse(PCB)), "Silkscreen refresh not idempotent" u1 = next(f for f in board.GetFootprints() if f.GetReference()=="U1") shape = next(g for g in u1.GraphicalItems() if isinstance(g,p.PCB_SHAPE)) shape.SetWidth(shape.GetWidth()+1) try: verify(board) except AssertionError: pass else: raise AssertionError("Stale embedded geometry was not rejected") assert PCB.read_bytes() == before assert not (ROOT/"~serial-carrier.kicad_pcb.lck").exists() summary = dict(measured_library_matches_embedded_u1=True, historical_u1_integration_and_subsequent_authorized_group_delta_verified=True, presentation_label_cleanup=True, silk_refresh_idempotent=True, stale_geometry_negative_control=True, silk_clipped_to_carrier=True, usb_mouth_markers=2, generated_silk_items=len(generated), baseline_sha256=hashlib.sha256(BASE.read_bytes()).hexdigest(), board_sha256=hashlib.sha256(before).hexdigest(), project_sha256=hashlib.sha256((ROOT/"serial-carrier.kicad_pro").read_bytes()).hexdigest()) (ROOT/"validation/pcb-u1-group-regression.json").write_text(json.dumps(summary,indent=2)+"\n") print(json.dumps(summary,indent=2)) if __name__ == "__main__": main()