|
| 1 | +# 2026-09-19 — The check now proves its own reading of the loader, instead of trusting it |
| 2 | + |
| 3 | +Round: `rustjava-assert-loadable-rederivation-did-not-come-up-short` |
| 4 | +Adopted proposal: `2026-09-19-loadable-set-source-of-truth#p0` |
| 5 | + |
| 6 | +The proposal has **no `how` field** — `title`, `plainSummary`, `userBenefit`, `why`, `tradeoff`, |
| 7 | +`effort`, `target` only. What follows says where this implementation matches its `why` and where it |
| 8 | +deliberately departs from its `tradeoff`. |
| 9 | + |
| 10 | +## First: is anything short today? |
| 11 | + |
| 12 | +No. Measured on `origin/main` @ `ad9eb1a6`, four independent counts of the same thing: |
| 13 | + |
| 14 | +| count | value | |
| 15 | +|---|---| |
| 16 | +| `_proto()` occurrences in `loader.rs` | **268** | |
| 17 | +| `_proto(),` lines | 268 | |
| 18 | +| `crate::classes::` occurrences | 268 | |
| 19 | +| registrations `REGISTERED` parses | 268 | |
| 20 | +| distinct names resolved | **268** | |
| 21 | + |
| 22 | +Also measured, because they are what would make the assertion fire *wrongly*: registrations sharing |
| 23 | +a line **0**, `_proto()` without a trailing comma **0**, `_proto()` inside a comment **0**, duplicate |
| 24 | +resolved names **0**. |
| 25 | + |
| 26 | +So the assertion starts green and guards a regression rather than fixing a present defect. The |
| 27 | +proposal says as much; this round confirms it with numbers rather than assuming it. |
| 28 | + |
| 29 | +## What was added — two axes, both fail-closed |
| 30 | + |
| 31 | +1. **Parsed vs. witnessed.** `REGISTERED` is the pattern under suspicion, so counting its own matches |
| 32 | + proves nothing. `PROTO_CALL` counts the same calls a second way, by the one token a registration |
| 33 | + cannot be written without. Mismatch ⇒ `cannot measure` (exit 2). |
| 34 | +2. **Registrations vs. distinct names.** Two registrations resolving to one name means the resolution |
| 35 | + is wrong — it is exactly what bare-name keying did. Mismatch ⇒ exit 2, **naming the collapsed |
| 36 | + pairs** so a genuine duplicate registration can be told from a mis-attribution at a glance. |
| 37 | + |
| 38 | +**Changed: 1 file, +38/−2.** |
| 39 | + |
| 40 | +## Axis — bidirectional, on the product script, by re-creating the two real defects |
| 41 | + |
| 42 | +| the script, mutated back to a defect it actually had | result | |
| 43 | +|---|---| |
| 44 | +| `((?:as\|list)_proto)` → `(as_proto)` (the first draft) | **rc 2** — `265 registrations parsed but 268 proto calls are in rustjava-runtime/src/loader.rs` | |
| 45 | +| key by bare type name (the first draft) | **rc 2** — `268 registrations resolved to only 263 names`, then names them: `java/util/Formatter <- java::util::Formatter::as_proto(), java::util::logging::Formatter::as_proto()`, … | |
| 46 | +| unmutated | **rc 0** — `✓ 43 named exception class(es) across 846 call site(s); all 268 loadable` | |
| 47 | + |
| 48 | +The second message is the point of the round: gate 2 found that colliding pair by reading the source |
| 49 | +alongside the script. The script now says it. |
| 50 | + |
| 51 | +## The three questions the brief asked |
| 52 | + |
| 53 | +**⒜ What is it compared against?** The source itself — a second count of the same file. Not a stored |
| 54 | +baseline, not the previous run. |
| 55 | + |
| 56 | +**⒝ First run, and legitimate decreases?** They do not arise, and that is *why* this shape was |
| 57 | +chosen. A remembered number would have to answer both; a self-contained invariant answers neither |
| 58 | +because it never remembers anything. Removing a class legitimately drops all counts together and |
| 59 | +stays green. |
| 60 | + |
| 61 | +**⒞ Die or speak?** **Die — exit 2, `cannot measure`.** The sibling round |
| 62 | +(`2026-09-18-nonliteral-exception-call-sites#p0`) chose "count and print, never fail" for the |
| 63 | +non-literal blind spot, and this round deliberately differs: that is a *known limitation* being |
| 64 | +sized, this is the check *mis-reading its own input*. The file already has a category for the |
| 65 | +latter — it dies on a registered entry whose name cannot be resolved — and this is the same failure |
| 66 | +one step earlier. A check whose loadable set is short reports real classes as unloadable and missing |
| 67 | +ones as present; printing that and exiting 0 would be the silent pass the file's docstring is about. |
| 68 | + |
| 69 | +## What this costs |
| 70 | + |
| 71 | +- **Two more numbers that have to stay true.** If `loader.rs` ever calls `as_proto()` outside the |
| 72 | + registration array, `PROTO_CALL` counts it and the check goes red on a correct tree. Measured |
| 73 | + today: every one of the 268 calls is a registration (`crate::classes::` count matches exactly). |
| 74 | +- **A false red is possible for a genuine duplicate registration** — two entries deliberately naming |
| 75 | + one class. There are none today, and the message names them, but it would be a red on a tree that |
| 76 | + is arguably fine. |
| 77 | +- **It is a floor, not a proof** — the proposal's own words. A registration mapped to a *wrong but |
| 78 | + distinct* name keeps both counts at 268 and passes. This catches undercounts, which is what both |
| 79 | + measured false greens were. |
| 80 | +- Runtime: **no significant change** — before 6.47 / 8.01 / 4.57 s, after 6.35 / 5.68 / 7.53 s |
| 81 | + (overlapping; the machine is loaded and the spread is wider than the effect). |
| 82 | + |
| 83 | +## Departure from the proposal's `tradeoff` |
| 84 | + |
| 85 | +It predicted the check would be *"coupled to the textual shape of `loader.rs` (one registration per |
| 86 | +line), which is true today and is not guaranteed."* That coupling was avoidable and was avoided: |
| 87 | +counting `_proto()` **occurrences** rather than lines makes the witness independent of line layout |
| 88 | +and of trailing-comma style. Measured equal to the line count today (268 = 268), so nothing is lost |
| 89 | +by the more robust form. |
0 commit comments