Skip to content

Wire #2091's vtable-partition licensing boundary into CI - #2101

Merged
andrewboudreau merged 1 commit into
mainfrom
tools/wire-partition-vtable-tests
Aug 31, 2026
Merged

Wire #2091's vtable-partition licensing boundary into CI#2101
andrewboudreau merged 1 commit into
mainfrom
tools/wire-partition-vtable-tests

Conversation

@andrewboudreau

Copy link
Copy Markdown
Collaborator

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, and audit_tu_object's validated_vtable_policies parameter, none of which exist on main before it. It 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. 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_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. 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, 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():def f(self): plus one indent level, and the plain assert statements 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 manifest section is 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 what 0b0e319 removed. The surviving sectionIndex equality 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:

drop the sectionIndex invariant                  -> failures: ['test_partition_licensing_pins_section_index_not_section_name']
restore the section-NAME compare (pre-0b0e319)   -> failures: ['test_object_audit_licenses_only_validated_manifest_vtable_partitions',
                                                               'test_partition_licensing_pins_section_index_not_section_name']

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 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 into test_tu_production.py" when I merged #2091. On inspection that module tests tu_production.py, not tubuild.py, and test_tubuild_owned_relocs.py is too narrowly named to absorb them. A correctly-named new module gets the same outcome — they execute in CI — without putting tubuild tests behind a tu_production name. Flagging it because I said otherwise in public.

2. Step 3 — "wire tools.test_objisolate if it is green and TestCase-shaped" — is dropped on measurement, not forgotten. It is TestCase-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 in objisolate.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:

Ran 519 tests in 61.934s
OK (skipped=3)
EXIT 0
python-names: 265 tracked file(s) checked
  0 unresolvable names, 0 unparseable files, 48 advisory finding(s)
python-names: PASS

check_dead_references: 385 prose file(s), 3598 repo-rooted path reference(s), 132 that do not resolve;
185 markdown link(s), 0 that do not resolve relative to their own file
  no new dead references
  no broken markdown links

One provenance note on the workflow's totals comment, since I would block someone else for it: the 514 in that line was measured in the CI container and I did not re-measure it here. The +4 is arithmetic on it, and the comment now says so instead of asserting a new absolute. The 519 above is the Windows-with-toolchain figure and is stated as such.

Line endings verified unchanged: main stores 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

#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
@tangos-validator

tangos-validator Bot commented Aug 31, 2026

Copy link
Copy Markdown

✅ PR validation — Passed

noverify: no source/build-data changes in this PR

Each changed src/*.c|*.cpp is compiled and its relocated bytes compared to the binary data on a private build box. Passing requires every changed file to reproduce the ROM byte-for-byte with correct relocation targets — this catches WRONG-DEST relocations and non-reproducing near-misses that ledger-scoped linkcheck skips.

@andrewboudreau
andrewboudreau merged commit b250e59 into main Aug 31, 2026
7 checks passed
@andrewboudreau
andrewboudreau deleted the tools/wire-partition-vtable-tests branch August 31, 2026 19:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant