diff --git a/tools/sizes.py b/tools/sizes.py index 184e460..520b136 100755 --- a/tools/sizes.py +++ b/tools/sizes.py @@ -84,6 +84,20 @@ def collect() -> dict[str, list[tuple[str, int, int]]]: for match in SIZE_TEST.finditer((tree / "CTestTestfile.cmake").read_text()): found.setdefault(chip, []).append((match["name"], match["elf"], int(match["limit"]))) sizes = measure([elf for rows in found.values() for _, elf, _ in rows], tool) + # A chip's generated and reflect trees must answer with the same bytes + # (the identity invariant), so the same target measuring two sizes means + # a stale tree — or an identity breach. Either is a finding; picking one + # silently is how a gate reports another build's numbers as today's. + for chip, rows in found.items(): + seen: dict[str, tuple[int, str]] = {} + for name, elf, _ in rows: + if elf not in sizes: + continue + if name in seen and seen[name][0] != sizes[elf]: + sys.exit(f"{chip} {name}: {seen[name][0]} B in {seen[name][1]} but " + f"{sizes[elf]} B in {elf} — a stale tree (rebuild or remove it) " + f"or a cross-mode identity breach") + seen.setdefault(name, (sizes[elf], elf)) measured = { chip: sorted(((name, sizes[elf], limit) for name, elf, limit in rows if elf in sizes), key=lambda row: -row[1])