Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
222 commits
Select commit Hold shift + click to select a range
d70a7c3
dev: make randomToml fully deterministic by replacing Math.random() w…
DecimalTurn Aug 12, 2026
90a4968
test: remove non-reproducible fuzz seed numbers from test names and c…
DecimalTurn Aug 12, 2026
076e929
test: add regression test for re-parse failure from fuzz harness (see…
DecimalTurn Aug 12, 2026
e21e048
test: add toEqual(dedent) check for seed 187 regression test
DecimalTurn Aug 12, 2026
e1e3005
fix: remove sibling KVs when truncating dotted key to scalar
DecimalTurn Aug 12, 2026
e35e95e
test: add reduced repro for writer position corruption (seed 176)
DecimalTurn Aug 12, 2026
3f575c5
test: add inspect-fuzz-seed.ts utility for inspecting individual fuzz…
DecimalTurn Aug 12, 2026
42e9d14
fix: stale loc in remove() extra calculation when prior removal has p…
DecimalTurn Aug 12, 2026
a2ac085
feat(fuzz): deepEqualWithFormat normalises truncateZeroTimeInDates an…
DecimalTurn Aug 12, 2026
7130b9c
fix: materialise implicit parent when dotted KeyValue's last segment …
DecimalTurn Aug 12, 2026
a1da86a
test: add targeted tests for delete-key mutation patterns
DecimalTurn Aug 12, 2026
6b46d98
test: add .fails tests for delete-key fuzz failures
DecimalTurn Aug 12, 2026
272923b
test: use dedent and toEqual guidelines for .fails tests
DecimalTurn Aug 12, 2026
97a89c4
fix: unwrap InlineItem→InlineArray parent in Remove handler (seeds 20…
DecimalTurn Aug 12, 2026
a4e23f3
test: adjust whitespace
DecimalTurn Aug 12, 2026
a1ae4a6
fix: restore bracket spacing after inline container moves
DecimalTurn Aug 12, 2026
074e08c
fix: resolve structural container when all path prefixes match a dott…
DecimalTurn Aug 12, 2026
67998a2
fix: materialise AOT-relative empty tables for dotted key removals
DecimalTurn Aug 12, 2026
d0f398a
test(fuzz): preserve Date subclasses when deep-cloning
DecimalTurn Aug 12, 2026
4e5fdd6
test: regression for deleting last segment of dotted key inside AOT e…
DecimalTurn Aug 12, 2026
e442ea1
test: regression for AOT-relative materialisation scope
DecimalTurn Aug 12, 2026
49cf08b
test: distilled repro for remaining seed 20 diff (multiline string + …
DecimalTurn Aug 12, 2026
3cbb347
feat: add mixed line ending conversion warning
DecimalTurn Aug 12, 2026
240e2ff
test(fuzz): normalise string line endings when newLine is set
DecimalTurn Aug 12, 2026
d9f8f13
fix(patch): resolve Add parent past prefix-matched dotted KVs (fuzz s…
DecimalTurn Aug 12, 2026
3683729
fix(patch): materialise emptied root dotted parents after root KVs (f…
DecimalTurn Aug 12, 2026
d400fe6
fix(patch): keep emptied dotted prefixes in inline tables as {} (fuzz…
DecimalTurn Aug 12, 2026
3751c9d
fix(patch): survive moves/removals around multiline strings (fuzz see…
DecimalTurn Aug 12, 2026
d3afdc2
test(fuzz): skip mutations that make AOT entries non-tables (seeds 52…
DecimalTurn Aug 12, 2026
a1e930c
fix(to-toml): never tab-convert multiline string content lines (seed 70)
DecimalTurn Aug 12, 2026
31c10ae
fix(patch): flush pending offsets before adding to emptied inline con…
DecimalTurn Aug 12, 2026
1d4969e
fix(to-js): keep empty key segments in validateKey candidate paths (s…
DecimalTurn Aug 12, 2026
6bb0326
fix(patch): derive emptied inline-table prefix from the removed path …
DecimalTurn Aug 12, 2026
502c338
fix(patch): truncate inline dotted keys on subtree type changes (seed…
DecimalTurn Aug 12, 2026
41d10e4
fix(patch): skip key-truncation exit offset for multiline KVs (seed 139)
DecimalTurn Aug 12, 2026
733874f
fix: materialise emptied implicit parent when removing prefix of long…
DecimalTurn Aug 13, 2026
a6ad9f3
fix: fall back to structural parent when remove's logical parent has …
DecimalTurn Aug 13, 2026
72f6ea1
fix: clamp root-KV insert index before InlineItem unwrap
DecimalTurn Aug 13, 2026
c0d4b83
test: align fuzz regression tests with dedent/toEqual conventions
DecimalTurn Aug 13, 2026
25295b0
chore: add tracked fuzz investigation script
DecimalTurn Aug 13, 2026
060864c
fix: diff simulated array values when a move leaves an edit behind
DecimalTurn Aug 13, 2026
2d9f933
fix: restore missing dotted-key prefix when adding into an inline table
DecimalTurn Aug 13, 2026
ab75030
fix: fall back to structural handling when an edit's replacement is u…
DecimalTurn Aug 13, 2026
06ccc08
fix: sink a KV-to-section edit below the root-table scope
DecimalTurn Aug 13, 2026
f188ee5
fix: perLine insert after a multiline string stays on its end line
DecimalTurn Aug 13, 2026
74d1456
fix: keep inline rows anchored when moving a subtree with multiline i…
DecimalTurn Aug 13, 2026
d38537c
fix: insert all items when a structural edit renders multiple root nodes
DecimalTurn Aug 13, 2026
9311ae4
fix: align regenerated key values onto the generated key's line
DecimalTurn Aug 13, 2026
6ef4361
fix: regenerate nested values as inline tables (seed 735)
DecimalTurn Aug 13, 2026
781a63f
fix: cancel leaked offsets when moving a multiline inline item (seed …
DecimalTurn Aug 13, 2026
bec59a5
fix: resolve pending offsets before implicit-parent materialisation (…
DecimalTurn Aug 13, 2026
9fe1be1
fix: merge structural-edit rows into a surviving same-key table (seed…
DecimalTurn Aug 13, 2026
bb63378
test: distilled regression for seed 1406, marked .fails
DecimalTurn Aug 13, 2026
09f56f8
fix: edit surplus array duplicates in place instead of Add + Move cha…
DecimalTurn Aug 13, 2026
a05f8af
fix: join structural edits into implicit tables as dotted keys (seed …
DecimalTurn Aug 13, 2026
cbb124a
fix: keep date components when time-only value is replaced by a date …
DecimalTurn Aug 13, 2026
3a408da
fix: keep empty inline table when its last dotted-key child is remove…
DecimalTurn Aug 13, 2026
795227a
fix: remove sibling dotted-key segments when a prefix key is deleted …
DecimalTurn Aug 13, 2026
51f2e70
fix: rename only the final segment when a dotted key is renamed (fuzz…
DecimalTurn Aug 13, 2026
a9458a8
fix: regenerate key when an array-of-tables becomes a plain array (fu…
DecimalTurn Aug 13, 2026
178fcb0
fix: remove section siblings sharing a deleted prefix key (fuzz seed …
DecimalTurn Aug 13, 2026
614d134
fix: remove table sections under a deleted prefix key (fuzz seed 3463)
DecimalTurn Aug 13, 2026
7526462
test: escape backtick in seed 3463 regression test
DecimalTurn Aug 13, 2026
bfe2631
fix: remove conflicting duplicate and section siblings when a dotted …
DecimalTurn Aug 13, 2026
01927f2
fix: keep added rows inside multiline inline tables whose brace share…
DecimalTurn Aug 13, 2026
04ccada
fix: keep the closing bracket after edit + truncate on a single-line …
DecimalTurn Aug 13, 2026
abac8f7
fix: scope the inline-array end fixup to fully single-line content (f…
DecimalTurn Aug 13, 2026
e44d6f9
fix: keep materialised sub-table headers below their parent table row…
DecimalTurn Aug 13, 2026
356dc8d
fix: remove array elements in place instead of move-chains when multi…
DecimalTurn Aug 13, 2026
87de9b9
fix: keep the closing bracket on the container row when a multiline i…
DecimalTurn Aug 13, 2026
44d1287
fix: regenerate AOT-entry sections and table replacements with full k…
DecimalTurn Aug 13, 2026
1c8f60b
fix: resolve pending offsets before materialising in-place and shift …
DecimalTurn Aug 14, 2026
841bdb9
fix: resolve surplus duplicates in place after in-place deletions and…
DecimalTurn Aug 14, 2026
cbb113c
fix: keep the closing bracket after bracket-row tail removal and skip…
DecimalTurn Aug 14, 2026
04300b7
fix: inline-table sibling removal, index-0 enter-offset flush, clampe…
DecimalTurn Aug 14, 2026
abece7b
fix: compensate pending exit offset in last-item removal gap (fuzz se…
DecimalTurn Aug 14, 2026
3ea7f2a
fix: bound dotted-key truncation by the row's relative path (fuzz see…
DecimalTurn Aug 14, 2026
c556312
fix: re-derive tail displacement after inline-table emptied-prefix ma…
DecimalTurn Aug 14, 2026
3a626ed
fix: realign inline-container tails after moves in shared-line contai…
DecimalTurn Aug 14, 2026
35dfa46
fix: anchored rows restore key/value nodes; AOT append skips previous…
DecimalTurn Aug 14, 2026
9a159ae
test: add failing test for reordering dotted-keys sinde an AOT entry
DecimalTurn Aug 14, 2026
57c9d99
fix: remove sibling sections on table-to-scalar collapse; distinguish…
DecimalTurn Aug 14, 2026
7f7a008
dev: add compare branch size script
DecimalTurn Aug 14, 2026
39f9f1f
test(patch): distill fuzz seed 30330 collapsing multiline-array dotte…
DecimalTurn Aug 15, 2026
3ebc568
fix(patch): reserve column for preserved trailing comma when replacin…
DecimalTurn Aug 15, 2026
1eab01c
docs: Add Fuzzing details
DecimalTurn Aug 15, 2026
c1bdfdc
dev: add generate-seed-test script
DecimalTurn Aug 15, 2026
82db3a1
test(patch): distill fuzz seed 31662 collapsing dotted key over secti…
DecimalTurn Aug 15, 2026
851d258
fix(patch): iterate a snapshot when sweeping truncated-key sibling se…
DecimalTurn Aug 15, 2026
c3e2e46
test(patch): distill fuzz seed 32801 collapsing Date key over dotted-…
DecimalTurn Aug 15, 2026
e16fffa
fix(patch): drop sibling keys/sections when a key collapses to a scal…
DecimalTurn Aug 15, 2026
0e9f58e
test(patch): distill fuzz seed 35943 duplicate-scalar removal over ne…
DecimalTurn Aug 15, 2026
7861a66
fix(patch): remove surplus duplicate scalars in place instead of chai…
DecimalTurn Aug 15, 2026
f1b1bf5
test(patch): add failing regression for fuzz seed 37465
DecimalTurn Aug 15, 2026
bc67615
fix(patch): merge regenerated inline table into surviving table secti…
DecimalTurn Aug 15, 2026
0189544
test(patch): add failing regression for fuzz seed 39363
DecimalTurn Aug 15, 2026
eb52712
fix(patch): drop sibling sections when a key collapses to a non-table…
DecimalTurn Aug 15, 2026
d75fe9e
test(diff): add failing regression for fuzz seed 40181
DecimalTurn Aug 15, 2026
e212d3a
fix(diff): treat nested arrays as multiline proxies in array diffs (s…
DecimalTurn Aug 15, 2026
48c4181
test(parse-toml): add failing regression for fuzz seed 40593
DecimalTurn Aug 15, 2026
21e1f00
fix(parse-toml): only merge date+time for a bare time token (seed 40593)
DecimalTurn Aug 15, 2026
5d300f1
test(patch): add failing regression for fuzz seed 41613
DecimalTurn Aug 15, 2026
7830e9e
fix(patch): extend regenerated section key with parent prefix (seed 4…
DecimalTurn Aug 15, 2026
2af26d9
test(patch): add failing regression for fuzz seed 43159
DecimalTurn Aug 15, 2026
ef5e05a
fix(patch): flush offsets before sinking a table below root KVs (seed…
DecimalTurn Aug 15, 2026
133b285
test(patch): add failing regression for fuzz seed 43199
DecimalTurn Aug 15, 2026
5caa206
fix(patch): convert inline-table rows to dotted KVs when colliding wi…
DecimalTurn Aug 15, 2026
debb9ec
test: add wip test with multi-line empty array
DecimalTurn Aug 15, 2026
856c369
test(patch): add failing regression for fuzz seed 46522
DecimalTurn Aug 15, 2026
1e8aadf
fix(patch): shift equals and value when a rename grows a dotted key s…
DecimalTurn Aug 15, 2026
36f40a9
test(patch): add failing regression for fuzz seed 54607
DecimalTurn Aug 15, 2026
7906814
fix(writer): account for pending offset when detecting same-line inli…
DecimalTurn Aug 15, 2026
9fceee9
test(patch): add failing regression for fuzz seed 61827
DecimalTurn Aug 15, 2026
0eaac4a
fix(patch): remove inline-table siblings matching a removed dotted-ke…
DecimalTurn Aug 15, 2026
c3f0243
test(patch): add failing regression for fuzz seed 62163
DecimalTurn Aug 15, 2026
f4c4089
fix(patch): materialise emptied nested AOT inside its parent entry (s…
DecimalTurn Aug 15, 2026
f05c88d
test(patch): add failing regression for fuzz seed 62263
DecimalTurn Aug 15, 2026
3a50eec
fix(diff): add instead of move when patching after in-place array rem…
DecimalTurn Aug 15, 2026
fc095bd
test(to-js): add failing regression for empty-key vs dot-key collision
DecimalTurn Aug 15, 2026
aa60d62
fix(to-js): use unambiguous key encoding for table/inline-table track…
DecimalTurn Aug 15, 2026
d1b5896
test(patch): add failing regression for fuzz seed 65785
DecimalTurn Aug 15, 2026
a6d3ca7
fix(writer): compensate stale next line with pending exit offset when…
DecimalTurn Aug 15, 2026
01dba87
test(patch): add failing regression for fuzz seed 67221
DecimalTurn Aug 15, 2026
784d591
fix(patch): materialise emptied AOT inside its host table with a rela…
DecimalTurn Aug 15, 2026
4b2cb5e
test(patch): add failing regression for fuzz seed 68244
DecimalTurn Aug 15, 2026
bfb3f7e
fix(patch): convert inline table to dotted KVs when its key prefixes …
DecimalTurn Aug 15, 2026
5c7de41
test(patch): add failing regression for fuzz seed 68861
DecimalTurn Aug 15, 2026
566c642
fix(writer): stay inline when inserting between same-line items in a …
DecimalTurn Aug 15, 2026
d8a7cc0
test(patch): add failing regressions for fuzz seeds 78079, 79938
DecimalTurn Aug 15, 2026
efd4e6d
fix(patch): merge table-collapse value into surviving parent section …
DecimalTurn Aug 15, 2026
c2d5770
fix(patch): remove siblings extending a fully-removed key prefix (see…
DecimalTurn Aug 15, 2026
7cba134
docs: add sweep notes
DecimalTurn Aug 15, 2026
a4a99e9
test(patch): add failing regressions for fuzz seeds 80004, 82825, 865…
DecimalTurn Aug 15, 2026
87bf422
fix(patch): handle AOT->array and inline-table sibling collisions (se…
DecimalTurn Aug 15, 2026
eb576d6
docs: add sweep notes
DecimalTurn Aug 15, 2026
2185192
test(patch): add failing regression for fuzz seed 121096
DecimalTurn Aug 15, 2026
82c0d35
fix(patch): regenerate inline-array element rendered as AOT (seed 121…
DecimalTurn Aug 15, 2026
8db3a7f
test(patch): add failing regression for fuzz seed 129645
DecimalTurn Aug 15, 2026
916cd8a
fix(patch): navigate full path when regenerating nested AOT entry (se…
DecimalTurn Aug 15, 2026
303afbb
test(patch): add failing regression for fuzz seed 136292
DecimalTurn Aug 15, 2026
cd1f987
fix(patch): sweep prefix-extending sections when deleting a table (se…
DecimalTurn Aug 15, 2026
e01937f
test(patch): add failing regression for fuzz seed 136865
DecimalTurn Aug 16, 2026
4eb1a98
fix(patch): coalesce AOT->mixed-array replacement incl. nested entry …
DecimalTurn Aug 16, 2026
4cec08a
docs: add sweep notes for 120000-160000
DecimalTurn Aug 16, 2026
cd265fa
fixup! docs: Add Fuzzing details
DecimalTurn Aug 16, 2026
0948270
fix: resolve typecheck errors
DecimalTurn Aug 16, 2026
ae66cd9
fix: resolve linting issues
DecimalTurn Aug 16, 2026
4fb376e
test(patch): add failing regression for fuzz seed 179377
DecimalTurn Aug 16, 2026
b262249
fix(diff): account for interspersed adds in array removal source indi…
DecimalTurn Aug 16, 2026
9aeef6f
test(patch): add failing regression for fuzz seed 186384
DecimalTurn Aug 16, 2026
45c0659
fix(patch): shift inline-container end on dotted-key rename growth (s…
DecimalTurn Aug 16, 2026
56f9041
revert: drop incorrect diff.ts fix for seed 179377 (introduced regres…
DecimalTurn Aug 16, 2026
f96d950
chore(fuzz): commit fuzz-runner script with --fast-fail mode
DecimalTurn Aug 16, 2026
0fa79a5
fix(diff): cancel remove shift on refused-move add (seed 179377)
DecimalTurn Aug 16, 2026
339e0a4
docs: add sweep notes for 160000-200000
DecimalTurn Aug 16, 2026
eceac25
test(patch): add failing regression for fuzz seed 208822
DecimalTurn Aug 16, 2026
ee892da
fix(diff): cancel remove shift on fallback add (seed 208822)
DecimalTurn Aug 16, 2026
310c6e4
test(patch): add failing regression for fuzz seed 224081
DecimalTurn Aug 16, 2026
43905a5
fix(patch): align inline-table relative prefix across AOT entry index…
DecimalTurn Aug 16, 2026
75cd4e7
docs: add sweep notes for 200000-250000
DecimalTurn Aug 16, 2026
f06afcb
test(patch): add failing regression for fuzz seed 272851
DecimalTurn Aug 16, 2026
d94758e
fix(patch): collapse emptied multiline inline table before re-add (se…
DecimalTurn Aug 16, 2026
bb7a269
test(patch): add failing regressions for fuzz seed 299772
DecimalTurn Aug 16, 2026
e0a7492
fix: drop sibling AOT entries when entry collapses to scalar, and nev…
DecimalTurn Aug 16, 2026
94b262a
docs: add sweep notes for 250000-300000
DecimalTurn Aug 16, 2026
f612e57
test: add toEqaul assertion and dedent
DecimalTurn Aug 16, 2026
7db2c25
test: move fuzz regression tests to patch.fuzz.test.ts
DecimalTurn Aug 16, 2026
6ca3dea
docs: document fuzz debugging strategies and tool selection
DecimalTurn Aug 16, 2026
1d85f9a
test(patch): add failing regression for fuzz seeds 358055/362151
DecimalTurn Aug 16, 2026
91af950
fix(patch): resolve pending offsets before hoisting a collapsed-table…
DecimalTurn Aug 16, 2026
f3f9726
docs: add sweep notes for 350000-400000
DecimalTurn Aug 16, 2026
a02b56f
docs: note that sweep notes must be committed
DecimalTurn Aug 16, 2026
7085561
fix(fuzz): match AOT guard on string-segment path to skip scalar-into…
DecimalTurn Aug 16, 2026
f48f37c
test(patch): add failing regression for fuzz seed 421965
DecimalTurn Aug 16, 2026
9c5fdd0
fix(patch): realign tail when a multiline item moves to the front of …
DecimalTurn Aug 16, 2026
91efe75
docs: add sweep notes for 400000-450000
DecimalTurn Aug 16, 2026
86b4d44
fix: keep appended array-of-tables entries on table->AOT replacement …
DecimalTurn Aug 16, 2026
db6fb4b
docs: add fuzz sweep 450000-500000 round-trip fix notes
DecimalTurn Aug 16, 2026
8fa24a4
docs: add fuzz sweep 500000-550000 notes (clean)
DecimalTurn Aug 16, 2026
a9e0f6a
fix(comment-ownership): restore interior rows of moved multiline inli…
DecimalTurn Aug 16, 2026
494f1c5
docs: add fuzz sweep 550000-650000 round-trip fix notes
DecimalTurn Aug 16, 2026
5212a58
fix(patch): place appended AOT entry after non-contiguous sub-tables …
DecimalTurn Aug 16, 2026
44461ca
docs: add fuzz sweep 650000-750000 round-trip fix notes
DecimalTurn Aug 16, 2026
2f81f06
test(parse): add array-of-tables with non-contiguous nested sub-table…
DecimalTurn Aug 16, 2026
22bec33
fix(patch): collapse multiline inline-table end on dotted-key removal…
DecimalTurn Aug 17, 2026
c446ca6
docs: add fuzz sweep 750000-1000000 round-trip fix notes
DecimalTurn Aug 17, 2026
fa5a6b3
fix(patch): derive AOT sub-table parent from CST key on remove (seed …
DecimalTurn Aug 17, 2026
62653b8
test(patch): add failing regression for fuzz seed 1024477
DecimalTurn Aug 17, 2026
07ab26a
fix(formatter): place extracted sub-tables after their parent entry (…
DecimalTurn Aug 17, 2026
4ec51d4
docs: add sweep notes for 1000000-1100000 round-trip fixes
DecimalTurn Aug 17, 2026
e5d8e33
docs: clarify that re-sweeping a window is unnecessary after failing …
DecimalTurn Aug 17, 2026
e8b96bb
fix(patch): drop prefix-extended sub-tables when AOT collapses to sta…
DecimalTurn Aug 17, 2026
d2803b9
test(patch): add regressions for fuzz seeds 1137525 and 1285105
DecimalTurn Aug 17, 2026
b5e8d7c
docs: add sweep notes for 1100000-1300000 round-trip fixes
DecimalTurn Aug 17, 2026
6ad718d
test(patch): distill seed 1137525 repro to simple string values
DecimalTurn Aug 17, 2026
da11dea
dev: add progress flag
DecimalTurn Aug 17, 2026
a9a9342
fix(patch): keep reordered removes from leaping past same-array adds …
DecimalTurn Aug 17, 2026
6283d15
docs: mark seed 1137525 fixed in 1100000-1300000 sweep notes
DecimalTurn Aug 17, 2026
0328338
dev: improve fuzz test reporting
DecimalTurn Aug 17, 2026
8e64955
test(patch): add failing regression for compact-array comma loss (see…
DecimalTurn Aug 17, 2026
64dff9e
test: add comma spacing preservation test
DecimalTurn Aug 17, 2026
fef2ce4
feat(patch): preserve compact comma spacing when editing inline array…
DecimalTurn Aug 17, 2026
d9bcfb8
fix(patch): strip AOT entry indices when resolving implicit-key remov…
DecimalTurn Aug 18, 2026
d91d5f5
test(patch): add regression for seed 1428499 (delete implicit AOT sub…
DecimalTurn Aug 18, 2026
6af7390
docs: add sweep notes for 1300000-1500000 round-trip fixes
DecimalTurn Aug 18, 2026
0abd5c7
fix(patch): resolve prefix-matched parent to structural container bef…
DecimalTurn Aug 18, 2026
f0b97a5
test(patch): add regression for seed 1674968 (implicit table collapse…
DecimalTurn Aug 18, 2026
78ed32c
fix: preserve AOT structural edit tail for seed 1657445
DecimalTurn Aug 18, 2026
2065d68
docs: add sweep notes for 1500000-1750000 round-trip fixes
DecimalTurn Aug 18, 2026
e0cb628
test: add failing distilled regression for seed 1845422
DecimalTurn Aug 18, 2026
119ceb7
fix: strip array index segments from dotted-key materialization
DecimalTurn Aug 18, 2026
cd66fb9
test: distill regression for fuzz seed 1947810
DecimalTurn Aug 18, 2026
398eaf0
fix: reuse explicit parent table when collapsing nested AOT entry
DecimalTurn Aug 18, 2026
565fd83
refactor: reorder tests by seed number
DecimalTurn Aug 19, 2026
2329361
test: add alternative tests
DecimalTurn Aug 19, 2026
d5d3412
test: add full toEqual check on result
DecimalTurn Aug 19, 2026
84338a9
fix: ensure multi-line literal string proper collapse when deleted in…
DecimalTurn Aug 19, 2026
637b63b
fix: make absolute path cache checks explicit
DecimalTurn Aug 19, 2026
a930653
fix: preserve pending enter offset maps
DecimalTurn Aug 19, 2026
d8cd5e6
docs: add changelog
DecimalTurn Aug 19, 2026
d455694
test(fuzz): stop rounding minimum decimals
DecimalTurn Aug 19, 2026
7c739ba
fix(script): honor cleanup failure handling
DecimalTurn Aug 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Stringify: Fix extra blank lines when extracting nested inline tables via `inlineTableStart` ([#281])
- Patching: Fix extra blank lines after tables emptied ([#281])
- Patching: Fix dotted-key truncation and whitespace bugs ([#282])
- Patching: Fixing 54+ patching bugs discovered with fuzzing harness (2M seeds) ([#288])

### Changed
- Improve stringification performance ([#280])
Expand Down Expand Up @@ -389,4 +390,5 @@ This first forked version from [timhall/toml-patch](https://github.com/timhall/t
[#280]: https://github.com/DecimalTurn/toml-patch/pull/280
[#281]: https://github.com/DecimalTurn/toml-patch/pull/281
[#282]: https://github.com/DecimalTurn/toml-patch/pull/282
[#288]: https://github.com/DecimalTurn/toml-patch/pull/288
[0e66e68]: https://github.com/DecimalTurn/toml-patch/commit/0e66e68cbf42a07bc44445e46c3ea7bea97f95c1
276 changes: 276 additions & 0 deletions docs/Fuzz-Testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
# Fuzz Testing for Patching

## The idea

The `patch` function has a hard guarantee to uphold: apply an edit to a TOML
document while preserving its comments, whitespace and formatting. But while those are nice to keep, the crucial part remains to make sure that we don't mangle the data as this is what actually matters in the end.

So, there's clearly a need to harden the patch function and a good way to discover bugs remain fuzzing: we *generate* TOML documents and
mutations at random, apply `patch`, and check that the result round-trips
correctly (see `src/__tests__/fuzz-patch.ts` and its `randomizer.ts`).

## The method

1. **Generate** a random TOML document and a random set of mutations (add key,
delete key, change value, change type, add/remove array item, etc.).
2. **Apply** `patch(src, mutatedObj)` and verify the output re-parses to the
expected object and preserves the source's comments and formatting.
3. When a seed fails, **distil** the failure into the smallest readable TOML +
mutation that still reproduces the bug (see the distillation process below),
and add it as a test marked `.fails`.
4. **Confirm** the distilled test fails in the same way as the original seed.
5. **Fix** the bug associated with that test.
6. **Run** the test to make sure it now passes, *and* re-run the specific seed
to confirm the seed passes too.

Once every failing seed in a window is fixed and each previously-failing seed
re-runs clean, the window is done — there is no need to re-sweep the *entire*
range a second time. Checking just the previously-failing seeds (via
`fuzz-run.ts --seed <N> --to <N>`) is sufficient; a fresh full sweep only
matters when you change core logic and want a broad regression signal.

## Fixing the bugs discovered by the fuzzing suite

When a seed fails, the first step is to create a reproduction of the TOML input
and the mutations that triggered it. We then run that test to confirm that it
fails, so we are sure it replicates the bug the seed flagged.


### Generating the seed's test scaffold automatically

Rather than copying a seed's TOML and mutations by hand, a helper script
generates a test from a seed and appends it to `src/__tests__/patch.fuzz.test.ts`:

```powershell
npx -y tsx scripts/generate-seed-test.ts --seed <N> [--mutations <N>] [--dry-run]
```

What it does:

- recomputes the seed's deterministic TOML document and mutations using the
same RNG offsets as `fuzzOne` (`seed + mutationCount * 1000000`);
- emits a `test('regression for fuzz seed <N>', …)` that holds the TOML in a
`dedent` template, and replays each mutation as a plain-JS statement
(`obj.x = …`, `delete obj.x`, `obj.arr.splice(…)`) — no fuzz internals are
needed in the test file;
- asserts `expect(parse(result)).toEqual(obj)`;
- when the patch round-trips cleanly, it also emits an exact
`expect(result).toEqual(dedent…)` expectation; when it does not (a failing
seed), it leaves a `// TODO` placeholder instead, reminding the next agent to
fill in the exact-output expectation after the bug is fixed.

Use `--dry-run` to print the generated body to stdout instead of writing to the
file.

Be aware that the script only produces the *scaffold*: the seed's full TOML is
rarely the minimal repro, so the distillation step below must be applied.

### Distillation of seed tests

Because the TOML input might be too big to be a minimal example of the bug, we
must start removing elements of the TOML input, simplifying some keys to
make it more readable to a human (unless the keys special characters or arrangement are needed to trigger the bug) and remove mutations that don't contribute to the bug. After every change to the originally created test, we make
sure that the test still fails in the same way as the original. When we reach
the simplest version of the test we can think of, that is when the test is
considered distilled.
Once the test is distilled, commit it (in its failing, `.fails`-marked state) as
a self-contained regression: the distilled TOML + mutations together with the
`src/__tests__/patch.fuzz.test.ts` entry. This keeps a clean history where every bug
lands as one "failing test" commit followed by one "fix" commit, and lets the
distillation be reviewed independently of the eventual code change.
Note that if we then fix the code to make the test pass and the seed still
doesn't pass, this would indicate that the seed was illustrating more than one
problem, so we would need to restart the process of distillation for this issue
as well, starting from the full TOML input.


### Fixing the bug

Once the test is distilled and confirmed to fail, the associated bug in
`patch.ts` (or elsewhere in the source) is fixed. After the fix:

- the distilled test must pass, and
- the original seed must also pass.

If the test passes but the seed still does not, the seed was exhibiting more
than one distinct bug, so the distillation loop above starts again from the
full TOML input.

## Why the diff algorithm struggles with simultaneous edits

A core design choice makes the public API almost trivially easy to use: `patch`
accepts a plain JavaScript object and applies all of its differences to the
TOML document in one go. For the consumer that is very convenient, the object
encodes an arbitrary amount of changes at once.

But it comes at the price of predictability. The alternative, the approach
taken by the `toml-edit` package (a single modification at a time), makes it
far easier to anticipate how offsets shift as the document is rewritten. When
many changes are applied simultaneously, deletions, renames and moves can
collapse onto and overlap each other, producing a much larger and subtler space
of edge cases.

In short, the diff algorithm was never really designed to accommodate too many
changes at the same time and that mismatch is where a lot of the patching
bugs come from.

## The downside

Fuzzing converges on a fix fast, but the fixes tend to be narrow. AI agents, in
particular, will often add a few lines of guarded code to handle the exact case
the seed exposed, rather than rethinking the underlying logic. Over many seeds
this leads to:

- **Code bloat** — many small conditional branches accumulate in `patch.ts`,
inflating the shipped bundle even though each individual fix is tiny. This
shows up as a larger `dist/toml-patch.js`. To measure the impact of a branch
of fixes against the `latest` branch, run:

```powershell
pnpm compare-branch-size latest
```

(see `scripts/compare-branch-size.mjs`, which builds both branches and prints
a minified and gzipped size comparison table).

- **Performance cost** — each added check runs on *every* patch, even though
most only matter for rare edge cases. The common path pays a small toll for
correctness in the long tail.

This tendency to patch the symptom rather than the architecture is the reason
the edge cases keep multiplying. Agents, left unattended, naturally avoid
restructuring the diff paradigm and instead go straight into `patch.ts` to add
conditional logic for whichever case just surfaced. At a small scale this is
fine, but the sheer volume of changes needed to make `patch` robust means the
edge cases (caused by deletions, renames, moves, collapsing and overlapping
rows) never really end.

> **Important:** if there is a paradigm-level way to rethink
> how edits are applied that would eliminate a whole class of bugs, prefer that
> over endlessly adding new lines to `patch.ts`. Narrow guards accumulate into
> bloat and slow down the common path, and they do not fix the underlying
> mismatch explained above.

## The upside

In exchange, bugs are fixed fast, and the patch feature stays trustworthy. A
single mangled TOML document can be catastrophic for a user who depends on
format preservation (config files, generated content, round-trip edits). Fuzz
testing keeps the core promise intact, one distilled regression test at a time.

## Debugging strategies

A distilled `.fails` test narrows the *what*, but not the *why*; the remaining
work is finding the faulty line in the writer/diff and the exact offset that is
wrong. These techniques have repeatedly shortened that loop:

### 1. Use the right tool for the right job: `fuzz-run.ts` to detect, `fuzz-investigate.ts` to diagnose

The two scripts answer different questions and must not be confused:

- **`fuzz-run.ts`** (`fuzzOne`) is the *detection* tool and the authority on
"is this seed actually broken?". It compares with `deepEqualWithFormat`,
which normalises `\r\n`↔`\n` and truncates zero-time dates per
`truncateZeroTimeInDates` — the same tolerance the suite enforces. It also
replicates `fuzzOne`'s AOT-entry mutation guard. Use it to sweep ranges and
to confirm a fix ("0 failures").
- **`fuzz-investigate.ts`** is the *diagnosis* tool for a seed you *already know*
is bad. It uses a stricter raw `diffPaths` comparison, dumps the expected vs
re-parsed objects, and prints the **full original and full patched TOML
line-numbered** — the fastest way to see exactly *where* (line/column) the
output went wrong before you start dumping the CST. Its raw comparison is a
*feature*, not a bug: it surfaces discrepancies `fuzzOne` intentionally
ignores (e.g. `\n` vs `\r\n`), which is sometimes precisely what you need while
tracing. It also re-derives mutations deterministically, so it never applies a
mutation that `fuzzOne` would have skipped — which is why a "DIFFS: N" here can
be a false positive relative to the sweep.

So: **prefer `fuzz-run.ts` for "is it broken?"** (if they disagree, `fuzz-run.ts`
wins), and **prefer `fuzz-investigate.ts` for "what changed, line by line?"** once
you're drilling into a confirmed failure.

### 2. Reproduce the seed faithfully, don't transcribe it

Hand-transcribing a seed's TOML (or its array literals) flips the diff — a
`null` vs `-inf`, or a re-ordered literal, changes `compareArrays`' behaviour
and makes the bug vanish (or appear different). Recompute the exact input and
mutations from the RNG instead, mirroring `fuzzOne`:

```ts
const generated = randomToml({ seed });
const obj = deepClone(parse(generated.toml));
const mutationRng = new SeededRandom(seed + mutationCount * 1000000);
// apply generateMutation/applyMutation in a loop, exactly like fuzzOne
// pass randomTomlFormat(new SeededRandom(seed + 500000)) — the FORMAT matters:
// truncateZeroTimeInDates / minimumDecimals / newLine all change the output.
```

The **format** is a first-class input: many "DIFF" results only reproduce with
the seed's `randomTomlFormat`, not the default (e.g. seed 299772's `0NaN-NaN-NaN`
LocalTime corruption only appeared with `truncateZeroTimeInDates: true`).

### 3. `dedent`-based fixtures strip the trailing newline

Tests written with `dedent` (most of the suite) produce input with **no**
trailing newline, and `patch` round-trips that faithfully. When asserting an
exact `toEqual(dedent…)`, compute the expected string from the *actual* output
(not a guessed `\n`-suffixed string) — a stray trailing `\n` or a `2` vs `2.0`
(`minimumDecimals`) is the classic way a freshly-written exact-output assertion
fails.

### 4. Dump the CST to see the *positions*, not just the rendered text

The rendered string is the symptom; the bug is almost always a stale
`loc` (start/end line/column) on some node. `parseDocument(src).cst` exposes the
tree, and it is quick to write a small recursive dumper that prints each node's
`type`, `key`, and `loc`. Comparing the CST **before** vs **after** `patch`
(and against the node you expect to move) pinpoints exactly which node kept a
stale end line/column — the root cause behind the inline-table-absorption bug
(seed 272851) and most writer-offset bugs.

### 5. Trace the offsets through the writer

`writer.remove()`/`writer.insert()` register *enter/exit* offsets that
`applyWrites` then applies in a single depth-first pass with running
`offsetLines`/`offsetColumns` accumulators. When a collapse "doesn't propagate"
up to an enclosing container's sibling, the offset is being zeroed or placed on
the wrong target (an enter offset on the inner container vs. an exit offset that
reaches the sibling). Knowing the target selection rules in `remove()`
(`previous` → exit offset; first item of a Table/TableArray → the table's key;
otherwise the parent itself) is the fastest way to see *why* a shift is missing
or double-counted.

### 6. Simplify nesting level-by-level, keeping each failure

Reach a minimal repro by deleting structure while re-running after each change;
only continue when the distilled test still fails in the same way. If a
reduction *stops* failing, you removed something load-bearing (e.g. the nested
inline table, the trailing sibling, or the multiline-string value) — put it back
and cut elsewhere. A seed often encodes **more than one** independent bug (seed
299772 had both an AOT-entry-collapse bug and a `LocalTime` truncation bug); fix
one, and re-run the full seed to see whether a second remains — then distill and
repeat.

### 7. Guarded fixes: keep them as narrow as possible, and prefer `instanceof`

Narrow guards are the norm (see "The downside"), but scope them to the exact
shape that reproduces the seed (e.g. skip truncation only for `value
instanceof LocalTime`, not "any value with year < 1"). A broad fix that looks
more general almost always regresses other seeds — the `addedBefore` diff change
regressed ~38 seeds and was reverted; the surviving fix touched only the one
refused-move Add branch.

### 8. Commit discipline keeps the history reviewable

One distilled `.fails` test commit, then one fix commit (test flipped to
`test`), then the sweep-notes doc. If the seed still fails after the fix, it has
more than one bug — start a new `.fails` from the *full* seed, not the
just-distilled fragment.

The sweep-notes doc **must be committed** too — write it to
`docs/bug-notes/fuzz-sweep-<from>-<to>-roundtrip-fixes.md` (one per window, e.g.
`fuzz-sweep-350000-400000-roundtrip-fixes.md`) and commit it with a
`docs: add sweep notes for <from>-<to>` message. A *clean* window (0 failures)
gets no notes file, but a window that needed fixes gets one, listing each seed,
its distilled minimal repro, and the root cause + fix commit.

61 changes: 61 additions & 0 deletions docs/bug-notes/fuzz-sweep-1000000-1100000-roundtrip-fixes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Fuzz sweep 1000000–1100000 — round-trip fixes

## Range

Seeds `1000000` through `1100000` (inclusive), 100001 seeds, `mutationCount` = 3.

## Failures fixed in this sweep

### Seed 1024477 — table → array-of-tables misplaces a nested sub-table

**Symptom:** `patch()` round-trip returned:

```
.tp6.0.k41: extra in got
.tp6.1.k41: missing in expected
```

`k41` (a nested object inside entry 0 of a freshly-converted array-of-tables)
was reassigned to entry 1 on re-parse.

**Root cause:** The seed's `change-type` mutation replaces `tp6` (a plain table)
with an array of objects whose first entry holds a nested object `k41 = { k96 =
… }`. With `inlineTableStart: 2`, that nested object is too deep to stay inline,
so it is extracted into a separate `[tp6.k41]` section. `formatNestedTablesMultiline`
(`src/formatter.ts`) collected every extracted sub-table into a single flat
`additionalTables` list and appended it at the **end** of the document. For an
AOT with multiple entries that pushes a sub-table of entry 0 (`[tp6.k41]`)
*after* entry 1 (`[[tp6]]`), so the re-parse nests `k41` under the second entry.

This is a general ordering bug, not specific to AOTs: even two top-level tables
rendered `[server]\n…\n[other]\n…\n[server.database]\n[other.nested]` instead of
keeping each sub-table after its own parent.

**Fix:** `formatNestedTablesMultiline` now records each extracted sub-table
together with its parent (the `Table`/`TableArray` it was extracted from) and
inserts it **immediately after its parent block**, not at the document end. A
per-parent counter keeps multiple children of the same parent in the correct
order.

**Files changed:**
- `src/formatter.ts` — positional insertion of extracted sub-tables in
`formatNestedTablesMultiline` / `processTableForNestedInlines`.
- `src/__tests__/patch.fuzz.test.ts` — added `regression for fuzz seed 1024477`.

## Debugging notes

Minimal repro:

```toml
tp6.":" = 451070
```

with `obj.tp6 = [{ k61: 439, k41: { k96: Date } }, { k76: -2790, k10: 1301, k89: 798.6 }]`
and format `{ inlineTableStart: 2, minimumDecimals: 2 }`. The `[tp6.k41]` section
must land between `[[tp6]]` entry 0 and entry 1.

Note: the seed's deterministic `add-array-item` at `I]GdO.1` (a scalar append
into an AOT) is **skipped** by `fuzzOne`'s AOT-entry guard, so it is not part of
the actual failing mutation set — `fuzz-investigate.ts` (which does not
replicate that guard) reports it, but the authoritative `fuzz-run.ts`/`fuzzOne`
does not apply it.
Loading
Loading