scripts/raw_handle_debt.py --no-raise-vs <base> compares recorded ceilings per path, and treats any path not present at the merge base as a raise:
crates/perry-runtime/src/object/native_module/vtable_access.rs: ceiling raised to 4 (was not listed at the merge base)
That is correct for new debt. It is wrong for a pure relocation, and the 2000-line file cap forces relocations regularly.
Concretely, while landing #10565 (five lines into object/native_module.rs, which sat at exactly 2000) the file had to be split. Moving the three NativeModuleVtable field-access impls out took their four recorded raw-handle sites with them. The ledger then needs native_module.rs's 4 line deleted (the bare run demands it: "ceiling of 4 matches nothing — the module is clean (or gone). DELETE its line") and the new module listed at 4. The bare run passes. --no-raise-vs then fails, because the new path was not listed at the base — even though:
- total debt is unchanged (
906 (baseline 906)), and
- the moved bodies are byte-identical.
So the two required invocations of this gate disagree about the same tree, and the only ways out are to pay down debt in code you are merely relocating, or to pick a different split. I picked a different split — the CJS default-export group, which happens to carry zero sites — but that is luck, not a strategy: it only worked because all four of this file's sites were in one block. A file whose debt is spread across it cannot be split at all without first paying the debt down.
Suggested fix: let the head ledger declare a relocation, e.g.
4 crates/perry-runtime/src/object/native_module/vtable_access.rs # moved-from: object/native_module.rs
and have --no-raise-vs credit moved-from's base ceiling against the new path, requiring the source path to be absent (or reduced by at least that much) at head. The total check already guards the thing that matters, so this stays monotone: the sum cannot rise, and a relocation cannot be used to launder new sites, because the source must actually give up the count.
A cheaper interim option is a --moved <old> <new> flag the splitting commit passes, but the in-ledger form is self-documenting and survives in the tree.
Found while landing merge train 216; the split that worked around it is object/native_module/cjs_default.rs.
scripts/raw_handle_debt.py --no-raise-vs <base>compares recorded ceilings per path, and treats any path not present at the merge base as a raise:That is correct for new debt. It is wrong for a pure relocation, and the 2000-line file cap forces relocations regularly.
Concretely, while landing #10565 (five lines into
object/native_module.rs, which sat at exactly 2000) the file had to be split. Moving the threeNativeModuleVtablefield-access impls out took their four recorded raw-handle sites with them. The ledger then needsnative_module.rs's4line deleted (the bare run demands it: "ceiling of 4 matches nothing — the module is clean (or gone). DELETE its line") and the new module listed at 4. The bare run passes.--no-raise-vsthen fails, because the new path was not listed at the base — even though:906 (baseline 906)), andSo the two required invocations of this gate disagree about the same tree, and the only ways out are to pay down debt in code you are merely relocating, or to pick a different split. I picked a different split — the CJS default-export group, which happens to carry zero sites — but that is luck, not a strategy: it only worked because all four of this file's sites were in one block. A file whose debt is spread across it cannot be split at all without first paying the debt down.
Suggested fix: let the head ledger declare a relocation, e.g.
and have
--no-raise-vscreditmoved-from's base ceiling against the new path, requiring the source path to be absent (or reduced by at least that much) at head. The total check already guards the thing that matters, so this stays monotone: the sum cannot rise, and a relocation cannot be used to launder new sites, because the source must actually give up the count.A cheaper interim option is a
--moved <old> <new>flag the splitting commit passes, but the in-ledger form is self-documenting and survives in the tree.Found while landing merge train 216; the split that worked around it is
object/native_module/cjs_default.rs.