v0.7.0
Real boards, and the refusals that stop them.
Upgrading from 0.6.0: read the 0.7.0 migration notes first. Five changes break something a working 0.6.0 deployment can depend on.
Highlights
ROUTER_VERSION moves to astar-grid/0.7.0. Every held candidate_id and bundle_id stops reproducing. No path geometry moved — this is the one change in the release that invalidates stored data, and it has its own migration section.
One obstacle budget was metering three different populations. 61 of 93 route refusals were the routed net's own copper charged to a budget named for copper it must avoid — a category error, not a capacity problem. Split into three discriminated budgets, with the obstacle model scoped to a routing region and the search confined to the same region, which is what makes the scoping sound rather than merely cheaper.
A locked (group …) now refuses instead of silently unlocking its members. BOARD_ITEM::IsLocked() derives lock from the parent group, so a locked group locks every member transitively with nothing in the member's own s-expression saying so. Members would have converted at locked=False past three authorization gates.
Pad zone_connect overrides that attach are accepted; the one that detaches still refuses. Accepting 0 could have made Board IR assert attachment over a deliberately isolated pad. Along the way: seven named pad refusals turned out to be unreachable dead code behind a generic allowlist check, so the message the original issue quoted was one the adapter could not emit.
Net-tie copper converts as a netless obstacle — something to route around, never a connection. The two deliberately shorted nets are never reported as connected.
Off-grid refusals now carry evidence: the pad, the lattice pitch, the signed per-axis miss in nanometres, and the largest representable step. The measurement that produced it refuted the hypothesis it was meant to confirm — re-previewing all 18 real-board off-grid refusals at exactly their own representable step, with the node budget at its ceiling, routed none of them. The lattice is where the refusal is reported, not where the constraint is.
Our own DRC payload failed our own published schema, and the schema then failed the other way too: clean: true beside a nonzero finding count validated. Both derived flags are now enforced in both directions, so a schema-only consumer cannot accept what the runtime refuses.
Honest numbers
Board IR converts 11 of the 12 boards in the #116 survey set, which is 11 of all 17 boards in that corpus as saved today. Six typed refusals across three constructs, each tracked: edge-connector pads (#138), root board properties on four saves (#140), a root copper graphic (#141).
Route preview routes 0 of 385 previews on that corpus today. An earlier 0 → 14 figure was measured on the corpus as it stood on 2026-08-07; those boards have since changed. An old-code/new-code differential on today's boards returns byte-identical verdicts across all seven codes, so nothing here regressed routing — the corpus moved, not the code.
Route preview runs one net at a time on F.Cu against the unrouted snapshot, so candidates are not mutually compatible and this is not a whole-board completion result. Placement preview ran without rules, so a clean verdict means legal-as-found, not placement quality. Neither surface makes an electrical, thermal, signal-integrity or manufacturing claim.
Gates
2,518 tests, 1 skipped, 760 subtests locally including the real-KiCad nodes; 2,438 passed with 81 environment skips across Python 3.11, 3.12 and 3.13 on hosted runners. Ruff, strict mypy over 97 source files, four repository checkers, CodeQL, dependency and secret audit, and OpenSSF Scorecard — all green on the exact source commit. The full evidence, including the one gate that could not run locally, is in the release ledger.
One thing the release build caught
The default source distribution was 124 MB across 20,452 files — it had swept in twenty-eight agent worktrees, because hatchling reads .gitignore and this repository excludes them through .git/info/exclude. The sdist now ships an explicit allowlist rather than an exclusion list: 717 files, 4.8 MB.