Loading files
This 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())
26e259c5cThis file is pinned to the selected release. Download the original to open it in your design tool.