Repository navigation
fix: keep the index dense and hash every other table keyed by numbers through the seed - #308
Merged
Merged
Conversation
A record struct's own hash is a fixed sum of its fields: PdfObjectId's is Number * -1521134295 + Generation, which a file inverts to put identifiers in one bucket of any table keyed by them, or to give two of them the same hash outright; PdfDiagnostic's adds the position last, so that reports alike but for where they lie share a bucket when placed a table's size apart. Both types now hash through System.HashCode, which .NET seeds once a process as it does strings, the position's two halves combined. Equality is the record's, unchanged; a hash differs from one process to the next. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
The index was a Dictionary<int, XRefEntry>. An int is its own hash, and .NET picks a key's bucket modulo a prime from a public list, so object numbers a multiple of that prime apart all fall in one bucket: 34,167 one-row subsections took 1.9 s to open against 9 ms numbered in order, and 36,350 objects with no table 8.2 s to rebuild against 65 ms. The entries now lie in two arrays, in the order the table first recorded their numbers, which stays the order it enumerates them in. A number finds its entry in a slot while numbers run in order, the slots stretching only while they stay within twice the entries held or made room for, and past them through a map hashed through the process's seed (SeededComparer). An entry takes 40 bytes against 52 to 63: opening 300,000 objects allocates 15.0 MB against 16.9 for a table and 25.5 against 29.5 for a stream, budgets recalibrated. IndexBenchmarks gains a table of numbers three apart, which the map serves. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
Past the index, the reader's object cache, its object streams and its sets of objects loading, cut, unproduced or reported, its memos keyed by offsets, and the validator's walks of the page tree, the reachable objects, the object streams' dependencies and the Arlington model were keyed by numbers hashed as themselves: 200,000 references to 20,000 objects a cache's size apart took 14.6 s to resolve against 88 ms, and 100,000 references to 30,000 free numbers chosen alike 6.3 s to validate against 96 ms. SeededComparer now also compares offsets, both halves of a long mixed in apart, since a long hashes as its halves exclusive-ored, and the four tuple shapes the reader keys by. Every one of those tables takes it, the copies the reader marks and restores included. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
A test reads the sources and refuses any Dictionary or HashSet keyed by an int, a long or a tuple holding one that is built without SeededComparer: a declaration given [] or new(), a new without it, a copy without it, a name declared with such a type and given [] or new() later, and any LINQ operator that hashes. A second opens, rebuilds and validates a document and checks the comparer of every such table the reader holds, and of the tables they hold. Planting a set built with [] or new HashSet<long>() fails both, naming the file and line. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
ADR 52 records the maintainer's four decisions: the index kept dense, every other table keyed by numbers hashed through the process's seed, the public PdfObjectId and PdfDiagnostic hashed through it too, and a test that reads the sources; with the measurements, the alternatives measured and rejected, and what would reopen it. The threat model's known gap becomes a section of its own among the shared defenses, and a row in the object syntax, indexing, resolving and validator tables, each naming its tests. The lazy-reading concept and the reader limits reference give the index's new cost: 40 bytes an object, about 80 at most when a rebuild grew it, 335 MB at the default bound against 436. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
ReserveIndex capped the room at one past the highest number read when the newest /Size was stale. A chain whose older sections each number one past the highest read so far then made room for one entry more at each section, and the index, now arrays sized exactly, was copied whole each time: a newest section of 100,000 objects under a /Size of 1 and a thousand older one-row sections allocated 3.6 GB in 3.5 s. The map it replaced grew by .NET's primes, about 1.2 times a step, and hid it. A size the sections have read past is stale and bounds nothing, as if absent: the room doubles, 12.5 MB for the same file. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
The slots doubled from one up to the first power of two past the highest number, even where a section had made room for every row: an index numbered in order kept 40 to 44 bytes an object and allocated up to 52. They now stretch at once to the room made, still within twice it and the slack: 40 bytes an object, allocated and kept. Opening 300,000 objects allocates 12.0 MB for a classic table and 22.5 MB for a cross-reference stream, against 16.9 and 29.5 with the map it replaced; budgets recalibrated. A number the slots come to reach leaves the map in place, rather than the map being copied whole whenever one does. A copy's slots reach the highest number they hold and no further. XRefTableTests now check where a number lives, the slots made once, and the enumeration through every interface. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
…omparer The reading of the sources let a table through when another argument of the same statement named SeededComparer, and never saw one whose type the source does not write: reverting the set an out var receives in NullWhileDecoding, or a copy in MarkReports, failed no test. The runtime check it leaned on opened a sound classic file and saw twenty fields. A test now reads the compiled library's IL: every Dictionary, HashSet, ConcurrentDictionary, OrderedDictionary or Hashtable over a key a file can steer, an integer, a tuple holding one or a struct hashed as the compiler writes a record struct's hash, must be built with SeededComparer.Instance as its comparer, and no LINQ, frozen or immutable operator may hash. Both reverts fail it; samples hold what it refuses. The source reading, kept for the file and line it names, looks for the comparer among a constructor's own arguments, and knows more table types and hashing operators. PdfDiagnostic's position halves, the tuple comparisons whose first items differ, and an open timed against the seeded map itself gain tests; the timing tests run both files in turn until neither gets faster. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
…e code hold The public documentation of MaxIndexEntries, the architecture and the threat model's row on declared counts still gave the hash table's 52 to 108 bytes an object; lazy-reading's measured section still gave its figures and budgets. The index now costs 40 bytes an object numbered in order, allocated and kept, about 80 when a rebuild grew it and about 96 for numbers spread far apart; 12.0, 22.5 and 24.2 MB to open the generated indexes, held to 12.6, 23.6 and 25.5 MB. ADR 52 and the threat model say which shapes the timing tests hold, that a number in the map costs more than it did, that a stale /Size bounds nothing, that tables enumerate in an order their adds and removes set, and what the two tests that read the code refuse. A record struct's hash is a fixed linear combination of its fields, not a sum. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
…he rest Since the last fix, a /Size the sections had read past bounded nothing as soon as the newest section numbered past it. An update chain that kept its original /Size, and whose newest sections number the highest objects, then doubled the index past its count: three sections of 60,000 rows made room for 240,008 entries, 520 of 300 for 309,248, where one past the highest number read had landed on 180,000 and 156,000 exactly. One past the highest number read stands for a stale size again, until an older section numbers past the newer ones: from then on it bounds nothing, so that a chain whose older sections each number one further still doubles the index rather than copying it at each. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
Numbers the slots come to reach leave the map in place, which kept the room it grew to for the life of the document whenever one number stayed past the slots: 3.1 MB for one far number beside a chain of a thousand sections. Once the numbers that stay are fewer than a quarter of its room, the map is trimmed to them, one copy of those that stay; a stretch that moves a few numbers out of many leaves it as it is. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
…parer The compiled reading let through a comparer handed on one path only, as a ternary or a ?? gives it, any comparer an operator was given, the frozen operators whose comparer is optional, immutable and frozen factories and their Empty, a table over a type parameter, a base constructor, and keys that hash as numbers it did not list: nullable numbers, System.Tuple, char, floating point numbers, record classes. No test held the condition that tells the seeded comparer from any other. A hash collection over a steerable key, built by a constructor, a base constructor, a LINQ operator, a factory, Empty or Activator, now passes only when the comparer it takes last is SeededComparer.Instance, the instruction before it the property's getter and itself no branch's target; over a type parameter, when it takes a comparer at all. Samples hold each shape refused and each let be. The source reading blanks literals as it blanks comments, closes an assignment at its matching bracket, reads an initializer as a new's arguments, knows more numeric keys, and asks a hashing operator to name a comparer rather than refusing it whatever it is given. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
The second shape measured as this work began, the 34,167 one-row subsections validated once open, was measured but held by no test: 4.2 s against 2.3 ms on main. It now validates in about the time of the same rows numbered in order. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
ADR 34 still priced the index at the hash table's 52 to 108 bytes an object. The figures quoted for main predated the last batch's stream changes: a cross-reference stream allocated 27.4 MB there, not 29.5, and the grown table 32.6 MB. The threat model's 40 bytes a number the guard lets the index take are 44 where one number lies twice the room away. A number in the map costs 20 to 35 bytes more than a slot, not twice one, and the slots follow the newest /Size's last step, up to 160 bytes an object. ADR 52 and the threat model say what the code readings now refuse, how a stale /Size bounds the room, and which shapes the timing tests hold. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
No test told the flag that stops a stale /Size from bounding the room from one recomputed at each section, nor from one an older section's /XRefStm stream never sets: a chain whose older sections alternate between one row past the highest read and none, or whose streams number past it while their tables number below, made room one entry at a time again, 1.8 GB and 3.6 GB, and every test passed. Both chains now open within 5 % of the 12.6 MB and 14.3 MB they allocate. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
The compiled reading let through a hashing operator taken as a method group, an immutable dictionary handed a seeded value comparer and the default key comparer, a table over a type parameter handed null or the default comparer, a comparer of a number handed to the library's own code, a table type given to its generic code, and anonymous keys; and took the lookups of an immutable dictionary's factory class for builders, and a frozen set's empty one for a table to seed. A method group now takes no comparer; the comparer an operator or factory takes last must be its key's, as declared; over a type parameter it must be one the method was given, an argument or a field; a call into the library's own code is read as any table is; anonymous types hash as tuples do. Each shape has its sample, refused or let be, and each condition a sample that fails without it. The source reading reads an interpolated string's holes as code, a verbatim string before a raw one, an apostrophe that closes nothing as no character literal, a tuple element's type rather than its name, and lets string.Join, Path.Join and arrays of tables be. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
A number in the index's map keeps 20 to about 42 bytes beside its entry and slot, by where the map's count falls against .NET's prime steps, not 20 to 35: an object numbered apart keeps about 60 to 85 bytes in subsections of ten rows or more, 90 to 115 in one-row subsections, and a rebuild over such numbers allocates about 125 to 220, not 155 to 190. ADR 52 and the threat model say what the compiled reading now refuses, that ADR 52's first measurements are single cold runs, and ReserveIndex how a hybrid file's newest stream and a size below zero are bounded. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
…nothing The flag's stickiness was held on the classic line only: recomputed at each /XRefStm stream, it was cleared by a stream holding no rows behind a table numbering one past the highest read, and the next table's room was bounded again, one entry at a time, 3.6 GB. A third chain, older tables numbering past and their streams numbering nothing, opens within 5 % of its 14.1 MB. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
…ilds Reading a comparer handed to generic code through to its callers never closed: this, a field set to the default comparer, a value comparer of the key's type, a comparer in a closure all passed, and a table type reached new() through a nested type argument, Lazy or LazyInitializer unread. The library builds no hash collection over a type parameter, so the reading now refuses any, whatever its comparer, and refuses a table over a steerable key built for a type argument by new(), Activator, Lazy or LazyInitializer without a factory, nested or not. A library call that hands a comparer is no longer read apart: what it builds is. A value comparer of the key's type is no longer taken for the key's, a builder factory and a nullable tuple key are held by samples, and every condition of the reading by a sample that fails without it. The source reading reads a nullable tuple key as a tuple, a character literal up to '\U0000FFFF', an interpolation's format clause as text and an array's initializer as the tables it builds, and lets only string.Join and Path.Join be, qualified or not. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
…osts ADR 52 and the threat model say what the compiled reading refuses since the last change: any hash collection over a type parameter, a table built for a type argument by new(), Activator, Lazy or LazyInitializer, and an immutable collection's Empty, not a frozen one's. A rebuild over numbers spread far apart allocates about 110 to 220 bytes an object, not 125 to 220; one-row subsections keep 85 to 115, the more the wider the numbers; and lazy-reading gives the kept figures by shape rather than a flat 96. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
ADR 52 gave the prototype's 5 to 8 % for validating 1,000 pages. Measured on the batch's last code against main, a medium job of each twice, alternated: the rules alone over a document already read take 2.9 to 3.2 ms against 2.6 to 2.7, about 13 % more; opening and validating it 7.0 to 7.3 ms against 6.7 to 6.8, about 5 % more, the lighter index paying back part of it. Opening an index of 300,000 objects takes 8 to 13 % less, and lazy-reading's BenchmarkDotNet rows give what indexing 1,000 pages costs now: 200 to 222 µs and 83 KB, 3.7 ms and 1.7 MB to read every page. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
The source reading took a whole array initializer, to its statement's end, for one table's arguments: one seeded element, or a comparer another argument of the call took, cleared every other. Each element an initializer builds target-typed is now read with its own arguments. The compiled reading refused Lazy's value constructor, which builds nothing: the value it is handed is read where it is built. A guard that changed no outcome is gone. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
The introduction and the features page still gave 229 µs and 393 KB to index a thousand pages and 6.2 ms and 5.9 MB to read them, figures older than the last batches: about 210 µs and 83 KB, and 3.7 ms and 1.7 MB, now, the features page pointing to the measured table. ADR 52's validation figure is about 15 % by its own medium runs, and one-row subsections of spread numbers keep 80 to 110 bytes an object. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
Under coverage, the instrumentation writes a probe at each sequence point, between the comparer's getter and the table's constructor, so the reading of the compiled library took the seeded comparer as wedged there and refused a table that hands it straight. The reading now drops the probes before it judges, and a branch that reached into a probe lands on the instruction the probe guarded. A test emits a probed method and a truly wedged one, and only the second is refused. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
…t builds them The source reading split an initializer's elements counting angle brackets, which a comparison, a shift or a lambda's arrow inside an element threw off: an array of seeded tables was refused, and an unseeded new() after an element holding an operator let be. It read only an element that starts with new( or a collection expression, so a table a conditional, a coalescing operator, parentheses or a multidimensional array's row built was let be, and it passed over an array of nullable tables and a declared array given a collection expression, reading their whole statement as one table. It now splits at the commas no parenthesis, bracket or brace holds, reads each target-typed new( an element starts with or a conditional or coalescing operator gives, and an element that is a collection expression, through a row, for a new array, a nullable one, and a declared one alike. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
… or not ADR 52 and the threat model say that no table that enumerates by hash, concurrent, frozen or immutable, is keyed by numbers, so that the seed, which differs from one process to the next, orders nothing the library reports or writes. It held, read in the code, but no test held it: the compiled reading let such a table be when it was seeded, and samples asserted so. A concurrent dictionary, or a frozen or immutable hash collection, its factories and its builder included, over a key a file can steer is now refused whatever its comparer, by both readings; the seeded samples of those shapes move to the refused ones. A factory is refused before its comparer is read, so the reading no longer asks which comparer a factory takes last. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
… it is one M23 still quoted the budgets and figures of the hash table the index was: 17.7, 31.0 and 32.1 MB, 16.9, 29.5 and 30.6 measured, where the tests now hold 12.6, 23.6 and 25.5 MB and measure 12.0, 22.5 and 24.2. Lazy-reading dated the chain's grown figure, 33.2 MB, to the day of the others, where it was measured on 2026-10-14. Five comments still called the whole index a map that doubles or resizes, the word now kept for the seeded map past the slots. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
MapCapacity was read only on tables that had made a map, so its answer for one that never did, the common case, went untested: the one line of the batch's patch that coverage left partial. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
ADR 52 measured the rules about 15 % slower, against the prototype's 5 to 8 %, and left it there. The debt filed under M23 says where the cost lies, the validator's walks keyed by object number, and what to measure first: a set indexed by the index's slots. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
…les declared first The source reading stopped at the ? of a nullable array of tables given a collection expression, and let its unseeded tables be; read a jagged array's row as one table, refusing an empty row and letting a seeded table beside an unseeded one hide it; and refused a concurrent table seeded or not only where a new built it inline, not where a name declared first was given one later. It now passes over a nullable annotation after each rank, reads a row as a row while ranks remain, and holds a name declared with a concurrent, frozen or immutable type to the same rule. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
The loop stand-in had been changed to build its table at the loop head, which holds a rule weakened to a strict comparison, but no longer a branch landing ahead of the construction, on the comparer it takes: a rule weakened to "not itself a branch target" passed every test. A second stand-in keeps that shape, each failing the mutant the other lets through. Wrapper's summary, which the last change left above the interface inserted before it, is back on Wrapper; two test comments still said "before any branch" and "a member that hands it out is refused", where the rule says ahead of every branch target and a call to such a member. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
…amples The reading knew the LINQ operators that hash by name, but not .NET 10's asynchronous ToHashSetAsync, ToDictionaryAsync and ToLookupAsync: a set of numbers built through one hashed them as themselves. Both readings know them now; their cancellation token follows the comparer, so one is refused even seeded, a shape the library does not use. Four conditions held by no sample: a key typed ValueType; a lookup let be only where declared on the table or a view of it, not a member named like one that hands it out; the table built by the static constructor, not an instance one that rebuilds it at each instance; and a branch that lands on a coverage probe moved to what follows it, which keeps a comparer handed on one path refused. Each has a sample now, whose mutant fails. ADR 52 said the source reading names an operator over a numeric key only, where it names one whatever the key; two comments gave a tuple's hash as the compiler's and the reaching rule as refusing members rather than calls and static fields. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
… a sample A mutation of each condition and set entry of the compiled reading found these held by no sample: twelve of the sixteen number types and fifteen of the eighteen hashing operators, any of which could be dropped from its set; a key of an anonymous type in a table that does not enumerate by hash; a tuple that holds a number beside another item; a comparer handed as an object to a method whose new() builds the table; a long backward branch and a switch closing the loop that builds the interning table; and a probe taking out the instruction that follows it. Each has a sample now, and its mutant fails. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
…s it Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
…nditions A Rune, an NFloat and a Complex hash as the number they hold, a code point or a floating point value, yet neither reading counted them among the numbers: a table keyed by code points from a file's text would have been let be. Both readings count them now. A mutation sweep of the compiled reading found conditions held by no sample: the reaching rule never met a field load, a field address or a non-virtual method group, nor the table two levels deep, a constructor of its pairs or a lookup on a container of it; the table rule never met a virtual call building a table for its type argument; two asynchronous operators had no sample; and a switch closing a loop at the construction itself. Each has a sample now, whose mutant fails. A sweep of the source reading found 198 surviving mutants. Every number name, every hashing operator, a comparer other than the seeded one, the four table types no sample held, the line the reading names and fifteen realistic shapes are held now; the rest, each a shape the compiled reading refuses, is filed under M23. Three comments described rules an earlier change had altered. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
…steer .NET hashes a struct that keeps the default hash and holds a reference, a floating point number or padding by its first field alone: two thousand KeyValuePair<long, int> sharing their key took one hash, and a set of twenty thousand took 25 s against 6 ms. Neither reading counted such a key, a pair among them, so a set of them was let be. The compiled reading counts every struct that keeps the default hash, the source reading a KeyValuePair as it does a tuple, with samples; none in the library is refused. A comparer named like the seeded one but not it had no sample on a table a new builds; two comments named only calls and method groups where the reading refuses static fields of a type built over the interning table's pairs; two summaries said more than the reading holds; ADR 52 said every numeric key where it meant every key it lists. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
…ace key A struct that hides GetHashCode with new was read as hashing itself, though a table calls the virtual slot, still ValueType's, and hashes it by its first field: the reading now follows the slot. A key typed as an interface was counted only where a number implements it, though a boxed tuple, record or struct keeping the default hash may stand behind any interface; every interface key is counted now, and none in the library is refused. No pair sample was free of numbers, so a reading that counted a pair only for a number it holds passed every test; a pair of strings is a sample now. The source reading counts a pair whatever it holds, as it hashes by its first field, and reads a key written with global:: or as ValueTuple. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
…can steer A class deriving from a tuple hashes as the tuple it derives from, so the reading of the compiled library now reads the tuple clause on the type whose hash a table calls, not only on the key's own definition. A record that is not sealed stands for every record derived from it, whose hash the compiler writes, so it counts as steerable whatever its own hash; a sealed record hashed by hand is let be. The source reading recognizes a number or a tuple written after global::. Samples hold each condition, and the documents name both keys, a table built for a type argument refused only over such a key, a type parameter, or one that enumerates by hash. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
…its own Both samples of a type deriving from a tuple derived from it directly, so a reading that took the key's base type for the type whose hash a table calls passed every test; a key deriving from a tuple of a string and a number through a class of its own is a sample now. A sealed record hashed by hand was read as hashed by the compiler when it derived from a record so hashed, since the attribute the compiler puts on a hash was looked for through the base types too; it is looked for on the hash itself, with a sample. The source reading recognizes a number written after its own namespace, System.Numerics or System.Text, and a Nullable written out, each with a sample. The documents say a type deriving from a tuple counts when that tuple holds a number a file steers, and two summaries say which tables the reading gives no key: a non-generic hash table, a StringDictionary among them. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
…erializers The compiled reading knew Activator, Lazy and LazyInitializer as builders of a type argument without a factory, but not Lazy<T, TMetadata>, whose constructors taking only metadata go on to Lazy<T>'s, nor a ConditionalWeakTable's GetOrCreateValue. A sweep of .NET 10's shared framework, reading every method body, found two more that may build a table: a BindingList, for AddNew, and Marshal.PtrToStructure. Each is refused now over a table a file can steer or that enumerates by hash; a BindingList where it is built too, since its non-generic interface reaches AddNew out of sight. Lazy's constructors are read on their definition, so that metadata of the value's own type is not taken for a value handed. Such a builder over a type parameter with no new() constraint is refused too: what its callers give it is not read. A generic JSON deserializer of System.Text.Json or System.Net.Http.Json is refused whatever its type argument, since it builds every table that type holds with .NET's default comparer, which no option changes. The library uses none of these; samples hold each condition, and the documents list the builders. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
A store alone into a static field of a generic type runs its static initializer for the type arguments it names, which may build one through new(); the reading read loads of a static field and their addresses, not stores, so a table a type parameter with new() stood for could be built unread. Stores are read now, with a sample. A Lazy or a BindingList that Activator, new(), LazyInitializer, an outer Lazy or a ConditionalWeakTable builds builds its value, or an item for AddNew, without a factory: such a builder over a Lazy or a BindingList of a table counts as building that table. Only Lazy's sample held a builder over a type parameter with no new() constraint; Activator's, LazyInitializer's, Marshal's, the ConditionalWeakTable's and the BindingList's have one each too. The documents said a JSON deserializer builds its tables with .NET's default comparer whatever its options; a contract resolver, a converter or populating a table the type holds can choose another, out of the reading's sight, and the library reads no JSON, which is why every such call is refused. A comment left out that only a type parameter with no new() constraint is refused at the builder. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
A builder over a type parameter with the new() constraint was let be, its callers read where they give it a type. They are not all read: a static abstract member called on another type parameter, a constrained call on a default struct, and a default interface implementation each reached a new() that built a table of numbers with .NET's default comparer, no test failing. The maintainer chose to close the whole family: the library builds no type parameter it is given, whatever its constraints, and generic code takes a factory instead. The new() samples that were let be are refused now, and a Lazy over a type parameter that Activator builds has a sample of its own. Three summaries are brought in line: the reading's own summary names the Lazy or the BindingList a builder builds, the library test gives the whole JSON rationale, and the instructions considered say why each is. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
…tself A DictionaryBase keeps a Hashtable keyed by any object, which it enumerates by hash, and a type derived from it builds that table without naming a table constructor: it is among the non-generic tables now, with a sample. A non-generic hash table a method returns under its own type was counted only where CollectionsUtil built it, though framework getters such as a TraceSource's Attributes or a DataTable's ExtendedProperties build and return a StringDictionary or a Hashtable alike: any such method is counted now, with a sample. The documents say that an UnsafeAccessor, whose body the runtime writes, is reflection and not read, and that a new() constraint met at a call site is read for a table type it is given, a type parameter handed to it being held where the library's own new() builds it. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
…ollection The clause that counts a non-generic hash table a method returns under its own type had samples returning a Hashtable or a StringDictionary exactly, so a reading that matched the exact type alone passed every test, though a DataTable's ExtendedProperties returns a PropertyCollection derived from a Hashtable: that getter is a sample now. An XmlSchemaCollection keeps its schemas in a Hashtable keyed by their namespaces, which it enumerates by hash, so the process's seed orders it; obsolete, it is named among the non-generic tables, with a sample, its warning suppressed where it is named. The instructions considered say that a virtual method group is read for such a table too. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
A sweep of 243 mutants over the compiled reading left conditions no sample held, beside survivors equivalent by construction: - a builder over a non-generic table that does not hand it back as one, a BindingList of Hashtables; - a builder over a table keyed by its first type argument, a dictionary of numbers to strings that Activator builds; - a tuple whose number is not its first item, a type deriving from a tuple whose number is, and a tuple of three; - a key typed by a generic interface; - the deserializers whose name is not Deserialize, GetFromJsonAsync or ReadFromJsonAsync; - LazyInitializer's guarded overload and Lazy's constructor that takes a flag; - a new() constraint on a second, or a first of two, type parameters; - a virtual getter that returns a Hashtable. Each has a sample now, whose mutant fails. Two lists of the collections that enumerate by hash name the XmlSchemaCollection too. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
A second sweep, 325 mutants over the compiled reading and the code added since the first, left these conditions held by no sample: - the value a Lazy<T, TMetadata> builds, read from its first type argument, whose one sample had metadata of the value's own type; - a key that holds a type parameter without being one, in a table a builder builds; - a hashing operator of System.Linq beside Enumerable's, a parallel one; - the interning table enumerated through ToArray, Values or CopyTo; - a getter returning an XmlSchemaCollection; - a local an instruction addresses by two bytes, whose high byte read as an instruction would hide the table built after it. Each has a sample now, whose mutant fails. An anonymous type is known by the name the compiler gives it rather than by an attribute a source can put on a type of its own. The documents say a parallel query is not read, the library running none, and that a pair hashes by its first field only when it holds a reference, a floating point number or padding. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
…store A third mutation sweep left three conditions of the compiled reading that no test held: an eight-byte floating-point constant read as eight bytes, an anonymous key whose steerable member is not its first, and an interning table stored through an instance field rather than a static one. Each now has a sample the reading must find. The threat model and the decision record now say what the reading holds of a parallel query: its key and comparer, as for Enumerable, but not the order an unordered one gives. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
… constant A fourth mutation sweep of the compiled reading, 415 mutants, left conditions no sample held: the key a LINQ operator is read by, taken by name rather than by place; a tuple's items read beyond one level, a nullable number among them; reference tuples of more than two items; a Lazy a ConditionalWeakTable, a BindingList or another builder builds two levels down; a four-byte floating-point constant read as four bytes; and the interning table handed on beside a static field it loads rather than stored into one. Each now has a sample the reading must refuse or find. The threat model says the Hashtable family is refused when a method returns one under its own type, as the decision record does, not however it is returned. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
A fifth mutation sweep, 521 mutants, left conditions no sample held: a type parameter two levels below the key; a tuple's or a nullable's item steerable as a nested tuple, an object, a struct or a record; an immutable dictionary's Empty and its builder; an OrderedDictionary, or a non-generic table other than a Hashtable, built by a builder; the ToDictionary of an immutable array, which another System.Linq class declares; a join's key whose outer element is not a number; and a loop that only the last case of a switch's jump table closes. Each now has a sample the reading must refuse or find. The guard test says, as the decision record does, that the library builds no hash collection over a type parameter, not no collection. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
A sixth mutation sweep applied a closed catalog of operators to every site of the compiled reading, 492 new mutants, and left combinations no sample held: each kind of steerable key reached through a builder or as a tuple's or a derived tuple's item; each kind of table a framework builder, a new() constraint or a new() type may build; a non-generic table a method returns and hands on without a call; a loop that only the first case of a switch's jump table closes; and the interning table in the wrappers the rule names, at each place a member names it. Each now has a sample the reading must refuse or find, 70 in all. The comments, the decision record and the threat model now say that a key holding a type parameter at any depth is refused, that a key typed ValueType is, not any value type, and which five lookups on the interning table are let be. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
…lpers A seventh mutation sweep specialized every helper of the compiled reading per call site, 642 mutants, and left what no sample held: each number type as a builder's key, a nullable's value, a tuple's item and a derived tuple's item; a framework type derived from a non-generic table built by a builder; a seeded FrozenDictionary factory and an ImmutableDictionary factory over a key no file steers; a view of the interning table stored into a static field; a key deriving from a tuple of three items or hiding its hash with new; and a comparer handed to another call beside a table built without one. A new test counts the nineteen numbers at each of the four places. The decision record and the threat model name the five lookups let be on the interning table, whatever they are handed. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
The samples hold each condition of the reading, each arm of a helper and each element of a set it reads, dropped, negated or narrowed at one place it is read; a change along one call path and not another, or to two conditions at once, is outside that bound, which the maintainer set on 2026-10-30 after seven mutation sweeps each went one level deeper. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
…ge makes Mutation testing becomes one finite step of a pull request's verification, apart from its review. A fixed, first-order catalog runs once over the lines of src/ the pull request changes, and over the conditions it adds to or changes in a test that reads the library's code. A helper is mutated once, in its body; no mutant changes two things at once. A survivor is a gap in the tests, killed by a test or classed equivalent with its reason, never reopened, and never a review finding. Only survivors are run again, and the tests written to kill them are not mutated in their turn. The record keeps why: batch 17's review swept its guard with a catalog refined every round, each survivor counted as a minor finding, and the code mutated grew with each fix, so the review had no fixed point. CLAUDE.md states the rule among its conventions. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
…hand-hashed record The thirty-second round reran the thirty-first's survivors, the fixed ones all failing a test and the settled ones all surviving, then swept the reading again within the bound: 687 mutants, 545 failing a test. Of the 142 survivors, 78 are equivalent, the same as one settled, or only widen what is refused; 64 are not, and samples now fail each of them: - the record arm narrowed to concrete records: an abstract record hashed by hand, its sealed child keyed through it; - the generic arms of Mentions narrowed to static methods, and existences over type arguments or parameters narrowed to their first or last item; - each arm of Steerable, Holds' element arm, BuildsTable's type parameter, CreatesTable's key test and Constructed's flag narrowed at one place it is read: comparable interfaces, key-value pairs, abstract records, record structs and reference tuples as keys, items and children; a class with a new() constraint; tables a generic class builds over its own parameter. The sample for a key holding a type parameter is keyed by (T, string), which a type argument can make steerable. The ref-returning view of the interning table is held at its return place too. The bound the main test states now sets aside the mutants that only widen what is refused. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
…neric interface too ADR 52 and the threat model said a GetOrAdd factory's argument reaches the factory as an object only, which the end of the same sentence contradicts: the table also reaches it typed as a non-generic interface, and neither is followed by the reading. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
… run it The maintainer settled what the first text of the rule left open. Every pull request that changes src/ runs the mutation step. A test that reads the library's code is mutated on the conditions the pull request adds to it or changes in it, a new one whole once. A survivor ends killed, classed equivalent, classed widening when it only makes such a test refuse more, or filed as debt when only an input no test can build tells it apart. No condition is narrowed to a sub-case of it. An independent reading of the record asked for the rest: the review meant is the pull request's own, not the milestone's; the history since batch 5 says what was run and how each survivor ended, the debt it filed included; the thirty-second round's 64 survivors, all narrowings, are in the record; the terms are defined where first used, and the coupling effect and the tools are cited for what they show. The bullet in CLAUDE.md states the operators and the four ways a survivor ends. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
…s merge The journal of 2026-10-28 gives the batch whole: the question and its measures, the maintainer's decisions of 2026-10-28 to 2026-10-30, the thirty-two review rounds and the mutation sweeps that ADR 53 ended, what was done, the tests, the mutation, the debts filed and the checks: unit 5,573 on the committed corpus and 7,945 with the remote documents, the site's 266 pages, and Remote corpus run 93 whole on the branch's last code. At a glance, the batch joins the milestone's list, the tests, coverage, CI and measurements take its figures, and a bullet states the mutation rule. Batch 16's record gains its merge and the end of the per-commit check. Refs #155 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiFf9bygtdCAcN4va1ss8T
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
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.
What this changes
Batch 17 of M02's step 4: numbers a file chooses so that they share a hash table's bucket (ADR 52).
PdfXRefTable). Numbers and entries lie in two arrays, in the order thetable first recorded them, which stays the order it enumerates them in; a number finds its entry in a slot while
numbers run in order — the slots no longer than twice the entries held or made room for, and 1,024 more —, and past
them in a map hashed through the process's seed, which gives back its room once the slots have taken most of its
numbers. 40 bytes an object numbered in order, against 52 to 63: opening 300,000 objects allocates 12.0, 22.5 and
24.2 MB for a table, a stream and a chain of a thousand sections, against 16.9, 27.4 and 30.6 MB, budgets
recalibrated.
/Sizekeeps bounding the room until an older section numbers past the newer ones. A/Sizethesections number past stands for one past the highest number they gave; once an older section, or its
/XRefStmstream, numbers past the newer ones, it bounds nothing, so that the index doubles rather than growing by one entry at
each section — which, with arrays sized exactly, copied the whole index each time: 3.6 GB in 3.5 s for a thousand
sections, found by the first review round.
SeededComparer). The reader's cache,its object streams, its sets and memos keyed by numbers or offsets, the copies it marks and restores, and the
validator's walks:
System.HashCode, drawn once a process as .NET does for strings, both halves of an offset mixedin apart.
PdfObjectIdandPdfDiagnostichash through the seed. A record struct's own hash is a fixed linear combinationa file inverts; equality stays the record's, and a hash now differs from one process to the next. No signature
changes.
SeededComparerTests). One reads the compiled library's IL and refuses every hashcollection over a key a file can steer — a number wider than a byte, a tuple or a pair, a struct keeping .NET's
default hash, a record's compiler-written hash, a key typed by any interface — unless the comparer of its key, taken
last, is
SeededComparer.Instancehanded straight; and always a table keyed by any object, a collection over a typeparameter, or one built for a type argument over such a key, and every collection that enumerates by hash — concurrent, frozen, immutable, a
StringDictionary—, whatever its keyand comparer, but
PdfName's interning table, built once and called on only to find or add a name, so that no order aseed sets reaches an output (invariant 6). It takes out the probes code coverage inserts, by their exact shape. The
other reads the sources and names the file and line.
ReaderTimingTests, each against the same file numbered in order, and the stale/Size's room byDocumentReaderTestson three chains;IndexBenchmarksgains a table of numbers three apart.src/(ADR 53, a line inCLAUDE.md).This batch's review swept its guard test with mutants from its twenty-fifth round on, each sweep's catalog finer than
the last and each survivor counting as a finding, so that no round came back without one. The maintainer bounded it
on 2026-10-30, then made the rule: a fixed, first-order catalog over the lines of
src/a pull request changes, andin a test that reads the library's code over the conditions it adds or changes; a survivor killed by a test, classed
equivalent or, in such a test, widening, each with its reason, or filed as debt, and never a review finding.
Why
Closes #155
Measured on
mainate3e2034, each shape a single cold run against the same file numbered in order: 34,167 one-rowsubsections opened in 1.9 s against 0.04 s and validated in 4.0 s against 0.09 s; 36,350 objects and no table rebuilt
in 8.0 s against 0.07 s; a million references to those numbers resolved in 56 s against 0.73 s. No guard bounds it.
Four ways were prototyped and measured; the maintainer settled the four questions as recommended on 2026-10-28: the
index dense and a seeded hash elsewhere, the process's seed,
PdfObjectIdandPdfDiagnosticseeded, and a test thatreads the code. 32 adversarial review rounds, 286 findings, each put to a refuter; the first round's three
majors fixed with a test that failed before its fix, none after. The last eight rounds swept the guard test with
3,676 mutants, until ADR 53 made the survivors the mutation step's to settle rather than the review's. The journal of
2026-10-28 in
docs/status.mdlists them.Filed under M23: #305, the seeded hash the validator's walks pay (see below); #306, the source reading's mutation
survivors, shapes it misses that the compiled reading refuses. Filed under M02: #307, the test of a disposed document's memory, which
failed once in CI's
compat (.NET 11)job ond7d3697(run 836), a commit that changes onlySeededComparerTests,the process letting go of 92 MB between the test's first two measures; run 837, on the next commit, passed it.
Definition of done
The project's definition of done applies to every change:
docs/status.mdrecords the batch: the journal of 2026-10-28 and the figures at a glancedocs/websitematches what now exists — 266 pages built, the site's check cleandotnet formathas been runAnything reviewers should look at closely
mainand the branch alternated, the rules over 1,000 pagesalready read take 2.9 to 3.2 ms against 2.6 to 2.7, about 15 % more, where the prototype the decision rested on had
measured 5 to 8 %; opening and validating 7.0 to 7.3 ms against 6.7 to 6.8, about 5 % more, the lighter index paying
part of it back; the Arlington walk alone 2 to 6 % more; allocation the same. The cost is the seeded hash in the
validator's thirty sets and maps keyed by object number. The validator's walks pay a seeded hash for every object number they meet: the rules take about 15 % longer since #155 #305 files it under M23, with a set indexed by the index's
slots as the way to measure first.
300,000 objects 8 to 13 % faster and 4 to 6 MB lighter; numbers spread far apart, which the map serves, keep 60 to 110
bytes an object (
concepts/lazy-reading.mdgives the figures by shape).32849ecto3109cb5(runs 813 to 819): under coverage, as CI runs the unit suite, theinstrumentation puts a probe between the comparer's getter and the table's constructor, which the reading of the
compiled library took for code the source wrote.
3abfe27reads through the probes, with a test that emits a probedmethod and a truly wedged one; run 820 and the runs after it are green.
8de9e16failsdotnet formattoo, anendless loop spaced otherwise than the formatter writes it, which
dbee093corrects. CI ran on 49 of the 64 commitsas each was pushed; no commit was checked alone otherwise, a check the maintainer dropped on 2026-10-29 since batches
are squashed. A rebase merge would leave those commits failing; a squash would not.
src/99.43 % (8,224 of 8,271) on the committed corpus.31d0870, whose library code the branch ends with (later commits change comments and tests);mainand the branch, twice each: the same 2,475 lines on all four runs;/Sizeflag's assignments and every condition of the compiled reading, each failing a testwritten for it but those judged equivalent; eight sweeps of the compiled reading, 3,676 mutants, every survivor C#
reaches failing a sample written for it, the others classed equivalent or widening with their reasons; the source
reading's survivors are held by samples or filed as The source reading of tables keyed by numbers holds some of its own conditions with no sample #306;
dotnet format;Remote corpusruns 87, 89, 90 and 93, whole at their first attempt, runs 90 and 93 on thelibrary code this ends with.
Merge: squash recommended, as a
fix:(no public signature changes; the hashes ofPdfObjectIdandPdfDiagnosticchange value, which a caller must not have stored).Generated by Claude Code