Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
e4d010b16This file is pinned to the selected release. Download the original to open it in your design tool.
#!/usr/bin/env python3
"""Verify every fix the defect ledger claims is actually in the tree.
Run: scripts/shift-left-check (exit 1 if a claimed fix is missing)
`docs/lessons.md` records every blocking defect we have hit and where the fix
landed. A ledger nobody checks rots: a fix gets reverted during a refactor, or
was never really landed, and the entry keeps claiming otherwise. This asserts
each one against the source.
It also prints the open defects — the ones that are currently *detected* but
not yet impossible. Detection is a smoke alarm, not a fix: a defect we only
find afterwards is one every user pays a build cycle for, forever. The count of
open entries is the honest measure of how much of what these three boards
taught us has actually reached the platform.
"""
from __future__ import annotations
import re
import sys
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
#: (ledger #, what it prevents, file, regex that must match)
#: The regex is the *proof* the fix is present. Keep it specific enough that a
#: revert breaks it and loose enough that a rename does not.
CLAIMED_FIXES: list[tuple[int, str, str, str]] = [
(
1,
"USB-C alignment drills are inside a keepout the router must respect",
"packages/golden-blocks/blocks/usb-c-power/usb-c-power.tsx",
r"<keepout",
),
(
2,
"pour hole-margin is derived from the 32-gon chord, not guessed",
"packages/golden-blocks/blocks/glue.tsx",
r"POUR_CUTOUT_MARGIN_MM",
),
(
7,
"a new project's first board uses MountingHole, which carries a keepout",
"skills/circuitcode/templates/project_skeleton/boards/main.tsx",
r"<MountingHole",
),
(
3,
"placement plans from measured block boxes, not assumed-centred sizes",
"skills/circuitcode/circuitlib/layout.py",
r"def place_board",
),
(
4,
"a routing-class failure retries at higher router effort",
"packages/circuitpy/src/circuitpy/generation.py",
r"autorouterEffortLevel",
),
(
5,
"the planner cannot put two USB-C entries on one board",
"skills/circuitcode/circuitlib/blocks.py",
r"Never place alongside usb-c-data",
),
(
9,
"the converted board is held to the fab's silkscreen floors before plotting",
"packages/circuitpy/src/circuitpy/kicad_normalize.py",
r"def normalize_for_fab",
),
(
9,
"stage 3a actually calls the normaliser",
"packages/circuitpy/src/circuitpy/generation.py",
r"kicad_normalize\.normalize_for_fab",
),
(
8,
"the planner refuses a board whose debug pins reach nothing",
"skills/circuitcode/circuitlib/helpers.py",
r"debug_unreachable",
),
(
13,
"the plan states the copper its own peak current needs",
"skills/circuitcode/circuitlib/helpers.py",
r"power_trace_width",
),
(
13,
"a parametric block carries a per-unit peak, not a BLOCK.md sentence",
"skills/circuitcode/circuitlib/blocks.py",
r"peak_per_unit_ma",
),
(
14,
"a plan that would cook the regulator is not buildable",
"skills/circuitcode/circuitlib/helpers.py",
r"def overheats",
),
(
10,
"mask slivers are judged between different parts, not inside one footprint",
"packages/verify/src/verifylib/gerber_truth.py",
r"gerber_mask_sliver_in_footprint",
),
(
6,
"the RP2040 crystal sits inside the 10mm XIN limit",
"packages/golden-blocks/blocks/rp2040-core/rp2040-core.tsx",
r"(?i)crystal|Y1",
),
(
8,
"there is a helper that lands SWCLK/SWD on copper a probe can reach",
"packages/golden-blocks/blocks/glue.tsx",
r"export const DebugPort",
),
(
21,
"a new project's first board declares the router effort it needs",
"skills/circuitcode/templates/project_skeleton/boards/main.tsx",
r'autorouterEffortLevel="5x"',
),
(
21,
"every golden block is judged at that same effort",
"packages/golden-blocks/tests/test_gauntlet.py",
r"autorouterEffortLevel=.5x.",
),
(
22,
"a via's drill is measured against copper, at JLC's via figure",
"packages/circuitpy/src/circuitpy/fab.py",
r"min_via_to_copper_mm",
),
(
23,
"a slot drill is measured as a slot, not as a circle",
"packages/circuitpy/src/circuitpy/checks.py",
r"def _stadium",
),
(
24,
"the hole rule exempts the hole's own net, and the corpus pins it both ways",
"packages/circuitpy/tests/test_failure_corpus.py",
r"hole-rule-applied-to-the-hole-s-own-pad",
),
(
25,
"the router halo does not push a connector away from the board edge",
"skills/circuitcode/circuitlib/layout.py",
r"face = EDGE_MARGIN_MM if edge else margin",
),
(
26,
"footprint IoU is not graded through a rotation the metric cannot see",
"packages/circuitpy/tests/test_failure_corpus.py",
r"footprint-iou-graded-through-a-rotation",
),
(
28,
"the skeleton ships the effort that measured best, not the highest one",
"skills/circuitcode/templates/project_skeleton/boards/main.tsx",
r'autorouterEffortLevel="5x"',
),
(
26,
"the corpus harness actually runs the IoU bander",
"packages/circuitpy/tests/test_failure_corpus.py",
r"checks\.iou_warnings\(elements, PROFILE\)",
),
(
29,
"the pushbutton declares its internal pairing, so the schematic folds "
"same-terminal pins instead of drawing a short across the switch",
"packages/golden-blocks/blocks/sw-tact/sw-tact.tsx",
r"internallyConnectedPins",
),
(
29,
"rp2040-core's BOOTSEL/RESET buttons declare the same pairing",
"packages/golden-blocks/blocks/rp2040-core/rp2040-core.tsx",
r"internallyConnectedPins",
),
(
29,
"a wire drawn between two pins of a switch symbol is flagged on every board",
"packages/circuitpy/src/circuitpy/checks.py",
r"def schematic_truth_warnings",
),
(
29,
"stage 1 actually runs the schematic-truth scan",
"packages/circuitpy/src/circuitpy/generation.py",
r"checks\.schematic_truth_warnings",
),
(
29,
"sw-tact wires diagonally, correct under either side-pairing",
"packages/golden-blocks/blocks/sw-tact/sw-tact.tsx",
r"diagonal",
),
(
29,
"the golden-block netlist graph honours declared internal ties",
"packages/golden-blocks/tests/conftest.py",
r"source_component_internal_connection",
),
(
31,
"the fab profile owns the power-net width floor, warn-band",
"packages/circuitpy/src/circuitpy/fab.py",
r"warn_power_trace_mm",
),
(
31,
"stage 4 holds every routed rail to that floor (poured ground exempt)",
"packages/circuitpy/src/circuitpy/checks.py",
r"def power_width_warnings",
),
(
30,
"the planner knows the sample-subsidy cliff and names the money",
"skills/circuitcode/circuitlib/layout.py",
r"def price_tier_warnings",
),
(
30,
"the number has one owner on the skill side",
"skills/circuitcode/circuitlib/tables.py",
r"JLC_SAMPLE_TIER_MM",
),
(
30,
"the fab profile carries the tier table and stage 4 names the overage",
"packages/circuitpy/src/circuitpy/fab.py",
r"price_tiers",
),
(
30,
"a board over every tier is told the money at stage 4",
"packages/circuitpy/src/circuitpy/checks.py",
r"dfm_price_tier",
),
(
32,
"the BOM ships one line per part, designators grouped, JLC's shape",
"packages/circuitpy/src/circuitpy/fab.py",
r"def group_bom_rows",
),
(
32,
"a golden-file test pins the exact BOM bytes JLC receives",
"packages/circuitpy/tests/test_fab.py",
r"test_bom_golden_file_jlc_sample_shape",
),
(
32,
"the CPL follows JLC's documented column order",
"packages/circuitpy/tests/test_fab.py",
r"test_cpl_golden_file_jlc_sample_shape",
),
(
12,
"every board reports how much of a diff pair runs as a pair",
"packages/verify/src/verifylib/netclass.py",
r"def _diff_pair_coupling",
),
(
33,
"a closed polygon pad ring is opened before anything judges the board",
"packages/circuitpy/src/circuitpy/circuit_normalize.py",
r"def normalize_circuit_json",
),
(
33,
"stage 0a actually calls it, on every compile",
"packages/circuitpy/src/circuitpy/generation.py",
r"circuit_normalize\.normalize_circuit_json",
),
(
33,
"a via-in-pad claim the geometry disproves is dropped, and one it "
"confirms survives — both directions pinned",
"packages/circuitpy/tests/test_circuit_normalize.py",
r"def test_a_via_genuinely_inside_the_pad_keeps_its_error",
),
(
34,
"the exported schematic is held to the design's own netlist",
"packages/circuitpy/src/circuitpy/kicad_schematic.py",
r"def normalize_schematic_truth",
),
(
34,
"stage 3b actually calls it, on every board kicad-cli can read",
"packages/circuitpy/src/circuitpy/generation.py",
r"kicad_schematic\.normalize_schematic_truth",
),
(
34,
"a drawn pin's identity is its name, not its number — the crystal "
"runs num 1..4 against name 4,2,1,3",
"packages/circuitpy/tests/test_kicad_schematic.py",
r"def test_crystal_pin_name_beats_pin_number",
),
(
34,
"internal units are reached the way KiCad reaches them, by "
"multiplying — dividing is off by 100nm and bonds nothing",
"packages/circuitpy/tests/test_kicad_schematic.py",
r"def test_internal_units_multiply_the_way_kicad_multiplies",
),
(
35,
"the CLI's Math.random is seeded from the board's own source, so a "
"minted id is a function of the board",
"toolchain/determinism/deterministic-run.mjs",
r"Math\.random = function seededRandom",
),
(
35,
"stage 0 actually canonicalises, before anything reads the file",
"packages/circuitpy/src/circuitpy/generation.py",
r"_canonicalise_file\(built_circuit_json\)",
),
(
35,
"the pipeline records whether the seed *reached* the CLI, not whether "
"it asked for one — NODE_OPTIONS is ignored under bun",
"packages/circuitpy/src/circuitpy/generation.py",
r"DETERMINISM_MARKER in build_result\.output",
),
(
35,
"the ruler refuses to call an empty route reproducible",
"toolchain/determinism/run_repeat.py",
r"no route to compare",
),
(
35,
"wall clock is a safety valve, not a budget — both timeouts are sized "
"for a loaded machine",
"packages/circuitpy/tests/test_determinism.py",
r"def test_build_timeouts_are_sized_for_a_loaded_machine",
),
(
35,
"a toolchain bump cannot quietly reintroduce a wall-clock budget in "
"the router",
"packages/circuitpy/tests/test_determinism.py",
r"def test_the_router_search_is_bounded_by_iterations_not_by_the_clock",
),
(
41,
"the hole gate reads a pad as the shape the fab plots — rotated, "
"cornered, and its own radius",
"packages/circuitpy/src/circuitpy/checks.py",
r"def _pad_copper",
),
(
41,
"a pad's ccw_rotation reaches the geometry instead of being dropped",
"packages/circuitpy/src/circuitpy/checks.py",
r"def _stadium\(x: float, y: float, width: float, height: float,\n"
r"\s+rotation: float = 0\.0",
),
(
41,
"both directions of the rotated-pad defect stay pinned",
"packages/circuitpy/tests/test_failure_corpus.py",
r'id="a-pad-measured-unrotated"',
),
(
49,
"the planner asks a fixed 2mm floor for any block adjacent to "
"rp2040-core — three engineers independently discovered 5mm by hand",
"skills/circuitcode/circuitlib/layout.py",
r'BLOCK_GAP_OVERRIDE_MM: dict\[str, float\] = \{\s*\n\s*"rp2040-core": 5\.0,',
),
]
#: (ledger #, what it prevents, glob, regex that must NOT appear anywhere)
#: The other half of a landed fix: a constant only has one owner while nobody
#: writes the number down again. #15 was three copies of 0.25mm.
BANNED_LITERALS: list[tuple[int, str, str, str]] = [
(
15,
"the pour margin is never a copied literal — one owner, POUR_CUTOUT_MARGIN_MM",
"**/*.tsx",
r'cutoutMargin="0\.\d+mm"',
),
(
29,
"no board or block ties both pads of a switch terminal — the diagonal "
"shape is what makes a wrong pad-pairing survivable",
"**/sw-tact/sw-tact.tsx",
r"TR_\$\{sw\}_p[23]",
),
(
33,
"no golden block closes a polygon pad's ring — a repeated first/last "
"vertex is a zero-length edge, and every via then reads as inside it",
"packages/golden-blocks/**/*.tsx",
r'points=\{\[\{ x: "([^"]+)", y: "([^"]+)" \}[^\]]*'
r'\{ x: "\1", y: "\2" \}\]\}',
),
]
def open_entries() -> list[tuple[str, str]]:
"""Parse the Open table out of the ledger, so the two cannot drift."""
text = (REPO / "docs" / "lessons.md").read_text()
try:
section = text.split("## Open —", 1)[1].split("## The rule", 1)[0]
except IndexError:
return []
rows = []
for line in section.splitlines():
line = line.strip()
if not line.startswith("|") or line.startswith("|---") or "Defect" in line:
continue
cells = [c.strip() for c in line.strip("|").split("|")]
if len(cells) >= 4 and cells[0].isdigit():
defect = re.sub(r"\*\*", "", cells[1])
where = cells[3].split(".")[0].replace("**", "")
rows.append((defect[:96], where))
return rows
def main() -> int:
print("Claimed fixes — is each one still in the tree?\n")
missing = 0
for num, what, rel, pattern in sorted(CLAIMED_FIXES):
path = REPO / rel
if not path.exists():
print(f" MISSING #{num} {what}\n file is gone: {rel}")
missing += 1
continue
if re.search(pattern, path.read_text()):
print(f" ok #{num} {what}")
else:
print(f" MISSING #{num} {what}\n {rel} no longer matches /{pattern}/")
missing += 1
for num, what, glob, pattern in BANNED_LITERALS:
hits = [
path for path in sorted(REPO.glob(glob))
if "node_modules" not in path.parts and ".circuit" not in path.parts
and re.search(pattern, path.read_text(errors="replace"))
]
if hits:
print(f" BACK #{num} {what}")
for path in hits:
print(f" {path.relative_to(REPO)} matches /{pattern}/")
missing += 1
else:
print(f" ok #{num} {what}")
rows = open_entries()
if rows:
print(f"\nOpen — detected but a user can still hit these ({len(rows)}):\n")
for defect, where in rows:
print(f" open {defect}")
print(f" belongs in: {where}")
print()
if missing:
print(f"FAIL — {missing} claimed fix(es) are not in the tree.")
print("Either restore the fix or move its ledger entry back to Open.")
return 1
print(f"PASS — {len(CLAIMED_FIXES)} fixes verified, {len(rows)} defects still open.")
if rows:
print("Open defects are not failures. They are the backlog that decides")
print("whether the next user's board hits what these three boards taught us.")
return 0
if __name__ == "__main__":
raise SystemExit(main())