diff --git a/AGENTS.md b/AGENTS.md index 8bae8d1..fb200c3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -317,7 +317,7 @@ someone spells it or pins it. node once rather than once per level above it (18). The node reference is the key, which holds because the parse builds one object per position; `adfToMarkdown` passes no memo, where a consumer's document may hold one node at two positions (4b). `text` and `spelling` carry no depth - and `headroom` is affine in it, so a read above the depth that filled the entry rebases; a read + and `headroom` is affine in it, so a read at or above the depth that filled the entry rebases; a read below re-spells, because a hit skips the depth guards the walk it replaces runs and an ordered list past the marker cap gives way, spending two emitter levels where the parser spent one. A give-way is kept too and serves any depth, reading the node's shape alone. Only what succeeded is diff --git a/src/markdown/parse/markdown-to-adf.test.ts b/src/markdown/parse/markdown-to-adf.test.ts index 62d88ae..283be1e 100644 --- a/src/markdown/parse/markdown-to-adf.test.ts +++ b/src/markdown/parse/markdown-to-adf.test.ts @@ -690,6 +690,9 @@ test('refuses input nested deeper than the parser carries', () => { const overflowed = (panels: number): string => asking(overflowing(overflowing(asking(`---\n${nest(repeated('panel', panels), 'Part.\n')}`)))) assert.equal(code(markdownToAdf(overflowed(493))), 'unsupported-node-shape') assert.equal(code(markdownToAdf(overflowed(494))), 'unsupported-nesting-depth') + const rebasing = (panels: number): string => asking(`---\n${overflowing(asking(`---\n${nest(repeated('panel', panels), 'Part.\n')}`))}`) + assert.ok(markdownToAdf(rebasing(494)).ok) + assert.equal(code(markdownToAdf(rebasing(495))), 'unsupported-nesting-depth') assert.ok(markdownToAdf(`${'- '.repeat(largestNesting)}a\n`).ok) assert.equal(code(markdownToAdf(`${'- '.repeat(largestNesting + 1)}a\n`)), 'unsupported-nesting-depth') }) diff --git a/todo-history.md b/todo-history.md index 90925aa..d2bf5d0 100644 --- a/todo-history.md +++ b/todo-history.md @@ -840,12 +840,14 @@ The done `todo.md` items in full, as they were written. `todo.md` keeps a one-li 250 nested directive lists fall from 1.22 s to 0.04 s at 16.1 kB and from 5.20 s to 0.12 s at 261 kB; a panel between every pair of lists 1.00 s to 0.05 s, an opaque carry in every item 1.08 s to 0.03 s. No figure enters the gate (§14), as 4c settled for a behaviour-preserving - cost fix; what the gate holds is the refusal. The rebase's sign is pinned by the two - depth-boundary tests already there, its magnitude by a third case the review found: an ordered - list past the marker cap gives way, so the emitter spends two levels where the parser spent - one, and two such lists put a read below the depth that filled its entry — which a hit would - answer without the depth guards the walk runs, naming a different refusal at a different path. - That read re-spells (the stability-reviewer, 2026-09-19). The one list accounting was not + cost fix; what the gate holds is the refusal. An ordered list past the marker cap gives way, + so the emitter spends two levels where the parser spent one: one such list between an ask and + a kept entry cancels the credit the directive form starts with, and two put the read below the + depth that filled it — which a hit would answer without the depth guards the walk runs. That + read re-spells. Three cases hold the arithmetic between them: the two depth-boundary tests + already there pin the sign, the two-overflow case pins the guard, and the one-overflow case + pins the magnitude, a constant rebase accepting there a document the emitter then refuses + (the stability-reviewer, 2026-09-19). The one list accounting was not taken: 4b settled that accounting the day this was filed, and reopening it is an ask rather than a chunk.