Wire #2091's vtable-partition licensing boundary into CI - #2101
Merged
Conversation
#2091 added a fail-closed boundary -- validated_vtable_partition_symbols, manifest_vtable_partition_rows, and audit_tu_object's validated_vtable_policies parameter, none of which exist on main before it -- that refuses a manifest-declared symbol carved out of a vtable's public range unless partition_vtable_rebiases independently validated it against content-bound baseline ELF metadata. Six tests describe that boundary. Five of them never execute: test_tu_production.py 1 test module IS in tool-tests.yml -> runs test_objisolate.py 1 test module NOT in the list -> never runs test_tubuild.py 4 tests module runs, finds 0 tests -> never run test_tubuild.py has bare `def test_` functions and no unittest.TestCase, so `python -m unittest tools.test_tubuild` prints "Ran 0 tests ... OK" and exits 0. That matters now rather than eventually. Measured on #2091's merge tree, the boundary licenses nothing at all -- 0 of 97 manifest entries, 0 partition_symbols rows tree-wide -- and #2096 introduces the first two rows that have ever existed (FlyGuy_VT7, FlyGuy_VT14). It goes from unexercised to load-bearing in one PR, with a decorative safety net. tools/test_tubuild_vtable_partitions.py -- new, four tests, 4 of 4 run in CI and none skips. Mock- and dict-driven: no compiler, no ROM, no config/, no build/. Three of the four are #2091's own toolchain-free tests, moved verbatim out of test_tubuild.py -- bodies unchanged, `def f():` to `def f(self):` plus one indent level, plain asserts kept -- so the move reviews as a move. The fourth is new and pins the invariant that is easiest to re-break: licensing compares sectionIndex, never section NAMES. A linked ELF names a vtable's output section after its overlay (OV036/OV047/OV070, measured on the baseline link) while a manifest section is one of .rodata/.init/.ctor/.data/ .bss. Comparing them refuses every real input, which is what made the whole path unreachable, and 0b0e319 removed exactly that comparison. Mutation-checked in both directions: drop the sectionIndex invariant -> caught by the new test restore the section-NAME compare -> caught by the new test AND by the moved licensing test tools/test_tubuild.py -- the three moved tests removed (58 -> 55 functions), and all sixteen `if not _toolchain(): return` guards converted to `raise unittest.SkipTest(...)`. A bare return reports PASS having asserted nothing; `pytest -rs` cannot see it and neither can a reviewer reading a green log. This does NOT make the module wirable -- it still has no TestCase and a third of it still compiles with mwccarm -- and the workflow comments that implied otherwise are corrected. .github/workflows/tool-tests.yml -- the new module added to the invocation list (28 -> 29), plus the inventory entry, and every claim in the header that this change falsified. tools.test_objisolate is deliberately still NOT wired: it is a single @unittest.skipUnless(_compiler(), ...) class, so on a runner it contributes 35 tests and 35 skips -- a green that asserts nothing, the exact shape the header refuses a glob for. Full CI invocation, run locally on a tree WITH the toolchain: Ran 519 tests in 61.934s OK (skipped=3) check_python_names.py PASS. check_dead_references.py: no new dead references, no broken markdown links. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WdCK1xgrJdiJzPCh3bAJfQ
✅ PR validation — Passednoverify: no source/build-data changes in this PR Each changed |
This was referenced Aug 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is the wiring I committed to on #2091 when I merged it. Tools-only, no source file touched, no byte claim.
Why
#2091 added a fail-closed licensing boundary —
validated_vtable_partition_symbols,manifest_vtable_partition_rows, andaudit_tu_object'svalidated_vtable_policiesparameter, none of which exist onmainbefore it. It refuses a manifest-declared symbol carved out of a vtable's public range unlesspartition_vtable_rebiasesindependently validated it against content-bound baseline ELF metadata.Six tests describe that boundary. Five of them never execute:
test_tubuild.pyhas baredef test_functions and nounittest.TestCase, sopython -m unittest tools.test_tubuildprintsRan 0 tests ... OKand exits 0. The workflow's own comments already said as much.That matters now rather than eventually. Measured on #2091's merge tree, the boundary licenses nothing at all — 0 of 97 manifest entries, 0
partition_symbolsrows tree-wide — and #2096 introduces the first two rows that have ever existed (FlyGuy_VT7,FlyGuy_VT14). It goes from unexercised to load-bearing in one PR, with a decorative safety net. This closes that gap before the first entry leans on it.What is in the diff
tools/test_tubuild_vtable_partitions.py— new, four tests, 4 of 4 run in CI and none skips. Mock- and dict-driven: no compiler, no ROM, noconfig/, nobuild/.Three of the four are #2091's own toolchain-free tests, moved verbatim out of
test_tubuild.py: bodies unchanged,def f():→def f(self):plus one indent level, and the plainassertstatements deliberately kept so the move reviews as a mechanical move rather than a rewrite.The fourth is new and pins the invariant that is easiest to re-break: licensing compares
sectionIndex, never section NAMES. A linked ELF names a vtable's output section after its overlay (OV036/OV047/OV070, measured on the baseline link) while a manifestsectionis one of.rodata .init .ctor .data .bss. The two vocabularies never intersect, so comparing them refuses every real input — which is exactly what made the whole path unreachable, and exactly what0b0e319removed. The survivingsectionIndexequality is the check holding the line; this test is what keeps someone from "restoring" the tight-looking form.Mutation-checked in both directions, because a test that pins an invariant is worth nothing until it has been shown to fail:
tools/test_tubuild.py— the three moved tests removed (58 → 55 functions), and all sixteenif not _toolchain(): returnguards converted toraise unittest.SkipTest(...). A barereturnreports PASS having asserted nothing;pytest -rscannot see it and neither can a reviewer reading a green log. This does not make the module wirable — it still has noTestCaseand a third of it still compiles with mwccarm — and the workflow comment that implied converting those returns would make it a candidate is corrected in this diff rather than left to mislead..github/workflows/tool-tests.yml— the new module added to the invocation list (28 → 29), an inventory entry, and every claim in the header that this change falsified.Two deviations from the plan I posted on #2091, both deliberate
1. The tests went into a new module, not
test_tu_production.py. I said "move them intotest_tu_production.py" when I merged #2091. On inspection that module teststu_production.py, nottubuild.py, andtest_tubuild_owned_relocs.pyis too narrowly named to absorb them. A correctly-named new module gets the same outcome — they execute in CI — without puttingtubuildtests behind atu_productionname. Flagging it because I said otherwise in public.2. Step 3 — "wire
tools.test_objisolateif it is green and TestCase-shaped" — is dropped on measurement, not forgotten. It isTestCase-shaped. It is also a single@unittest.skipUnless(_compiler(), "mwccarm not present")class and nothing else, so on a runner it contributes 35 tests and 35 skips — a green that asserts nothing, which is the exact shape this workflow's header refuses a glob for. Wiring it would still catch a collection error inobjisolate.py; that is not worth a hollow green. It stays out, and the measurement is recorded in the workflow comment so the next person does not re-litigate it.Verification
Full CI invocation run locally, all 29 modules, on a tree with the toolchain:
One provenance note on the workflow's totals comment, since I would block someone else for it: the
514in that line was measured in the CI container and I did not re-measure it here. The+4is arithmetic on it, and the comment now says so instead of asserting a new absolute. The519above is the Windows-with-toolchain figure and is stated as such.Line endings verified unchanged:
mainstores all three files CRLF and so does this branch, so the diff is content only.🤖 Generated with Claude Code
https://claude.ai/code/session_01WdCK1xgrJdiJzPCh3bAJfQ