XAML $max_facts is the next wall behind #222 — raising the tree budget alone will turn truncated_tree into node_limit_reached on the same files #223
Labels
No labels
code-review
correctness
dos
performance
security
severity/high
severity/low
severity/medium
tech-debt
Kind/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
h-dv/code-index#223
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Filed alongside #222 so that fixing one does not silently produce the other and read as a regression.
The situation
The XAML extractor declares two independent budgets that report two different codes:
plus a third, unnamed guard inside the scan loop, checked on every iteration:
Today the tree budget cuts first on every large file measured in production — six probes, all
extract.truncated_tree, nevernode_limit_reached. That means$max_factsand the$bpguard are currently untested against real markup: nothing reaches them, so nobody knows where they bite.Why this must be settled with #222 rather than after it
#222 raises
$max_tree_bytesso files up to some new size complete. The moment the tree stops cutting, the fact budget becomes the binding constraint on exactly the files #222 is meant to fix — the theme dictionaries.Generic.xamlat 246.6 KB andBaseTheme.Net.xamlat 221.2 KB are densex:Name/x:Class/Clickmarkup, which is precisely what mints facts.The observable outcome would be: the same files still incomplete, the same tools still missing symbols, and a different diagnostic code. To anyone watching the code rather than the outcome that looks like a new defect introduced by the fix. To anyone watching the outcome it looks like the fix did nothing.
Both budgets must be sized against the same measured corpus, in one change, or neither number means anything.
What must be measured, not reasoned about
For real markup at 100 KB, 250 KB and 1.3 MB (the production sizes in #222):
$bp > 196608guard — that guard is a third ceiling and it is not one of the two documented knobs;(memory 8)= 512 KiB initial withsrc_offsetat 262,144, shared by source + tree + facts. The tree budget is the largest single consumer, so raising it moves this too.If the answer is that
Generic.xaml-scale files cannot fit any budget triple within one wasm instance's memory, that is a legitimate finding and should be stated rather than papered over with a number that merely moves the wall past the current corpus. A streaming or chunked walk would then be the real fix, and this issue becomes its motivation.What a fix must prove
$max_factswhile the tree budget is comfortably clear, so thenode_limit_reachedpath is graded at all — it currently is not, against real markup.$bpbuffer guard, or a written argument (with the arithmetic) for why$max_factsmust always bind first. Right now that guard is a silent third ceiling.truncated_treepath still fires and still discloses, for a file beyond whatever new tree budget lands.index_coverage, not just at the extractor boundary.Mutations
$max_factsabove anything reachable → the fact-budget assertion must go RED, or that path is untested.$bpguard above reachable → its assertion must go RED. If no test fails, the guard is ungraded and the fix has not covered the third ceiling.truncated_treewhere it meansnode_limit_reached→ the code-discrimination test must go RED. The two codes carry different operator meaning ("too big to read" vs "too rich to report") and must not be interchangeable.Cost, same as #222
Any change here is a package edit: every byte under
tests/packages/xaml/is packed, so the.cipdigest moves,tests/packages/xaml.digestmust be re-recorded, the package version bumps, and the release notes must say so or operators meet a baredigest_mismatch. Doing this once, together with #222, is materially cheaper than doing it twice.$max_tree_bytestruncates real-world files from ~80 KB — measured on a 14,056-file production repo, ~50 files affected #222Fixed in
ea74dbc, together with #222 — which is the whole point of this issue existing separately.The premise held, and both budgets moved together
$max_facts4,000 → 48,000, and the unnamed$bp > 196608guard became a named$max_fact_bytes= 2,097,152 — a LENGTH from the buffer base rather than a raw address, and a global parameterised exactly like the other two so tests can drive it.src_offsetmoved 262,144 → 2,162,688 (= 33 pages, matching the declared(memory 33)), which is what actually bought the fact buffer its room. That was the knob this issue was looking for.The arithmetic this issue asked for DOES NOT EXIST, and that is the answer
This issue offered a choice: "a fixture that exceeds the
$bpbuffer guard, or a written argument with the arithmetic for why$max_factsmust always bind first."There is no such argument. A fact record spans 30 to 4,130 bytes (
29 + name/34 + name, name ceiling 4,096) — a 138× spread. No pair of constants can order a count against a byte total across that range. Which output budget binds first is a property of the document, not of the numbers.So it is graded with a fixture instead, and both ceilings stay reachable on real documents: the count binds below a mean record of 2,097,152/48,000 = 43.7 bytes, which is — to two decimals — the measured production mean of 43.67. The byte ceiling binds above it. That is a much better place to sit than either budget being unreachable.
The third ceiling is named and graded, not silent
This issue's core worry was that raising the tree budget would move the wall invisibly. Measured, before → after, all under the old triple:
truncated_treetruncated_treetruncated_treetruncated_treeThe walk cut first every time under the old budgets, confirming this issue's claim that
$max_factswas ungraded against real markup. Both codes now fire on their own fixtures and are asserted end to end throughsymbols_overlapping().extraction_diagnostics— the exact carrierindex_coveragereads — sotruncated_treeandnode_limit_reachedare distinguishable at the surface, not merely at the extractor boundary.extract.node_limit_reachedis reachable at 49,000 × 7-bytex:Name(1,029,008 bytes of source, 48.8 facts/KB). The tree disclosure stays reachable inside the indexer's own 2 MiB limit via 880 KB of<b/>filler at a 20.00 expansion ratio.The finding that outranks the budgets
Mutating the byte term out of the guard let 38,000 facts emit 2,432,000 buffer bytes — 271,384 of them past
src_offset, over the source. It survived only because the walk is forward and the overwritten prefix had already been read.That guard is a memory boundary wearing a budget's clothes. This issue called it "a silent third ceiling"; it is worse than that — it was the only thing preventing a buffer overrun into the guest's own view of the input. The new layout keeps 59,334 bytes of unreachable margin above the worst-case overshoot (guard plus one maximal 4,130-byte record).
Correcting this issue's memory model
Both errors were mine and both pointed the fix in the wrong direction:
extract; the budget bounds fuel, not bytes.(memory 8)= 512 KiB initial … shared by source, tree and facts" — the real ceiling isMEMORY_CEILING_BYTES= 64 MiB. 512 KiB bounded only the fact buffer.Consequence: this issue's speculation that
Generic.xamlscale might be "architecturally out of range" and need a streaming walk was wrong. At 2 MiB of dense markup with the new budgets: 29,845 facts, no diagnostic. Memory at the extreme is ~14.8 MB against 64 MiB.Eight mutations run, all RED, including both directions on each budget and a code-swap proving the two diagnostics are not interchangeable. Closing.