chore: bump the engine pin, and give the drift job something that can fail - #20
Merged
dereuromark merged 2 commits intoAug 17, 2026
Merged
Conversation
… fail The pin sat at a carve-js revision from 2026-08-05, 333 commits behind main. Measured through the artifact rather than the revision distance, that engine rendered 256 of 1131 spec-corpus documents differently; carve-js main renders 0 of them differently, so every one of the 256 was attributable to the pin. Largest single family was table header cells losing their scope attribute, but it also covered footnote body columns, tab handling, raw blocks, reference definitions and continuation markers. Bumping it needs two source changes, both of them upstream removals rather than anything this repo did: - a block quote no longer carries an attribution field. A caption on a quote makes a FIGURE (carve#1213), and the figure branch of the substitution walk already handles that target, so the quote branch simply loses the arm. - beforeRender now takes the read-only context the extension contract describes (spec section 2.2). page.ts unrolls carveToHtml, so it has to build that context itself: a hook runs before any render starts, and without it a hook emitting output of its own renders with defaults rather than with the options the page is rendered under. Only HTML is ever the target here and there is no static mode, so the mode fields are constant. The second half is why nobody noticed. engine-drift.yml compared revisions and reported the lag as a warning, which cannot fail a job, so every scheduled run was green throughout. A warning is the right instrument where the remedy is upstream and unavailable locally, which is what the carve-php satellites are looking at. It is the wrong one here: the remedy is one line of package.json. So the job now renders carve main's corpus through the engine AS INSTALLED and fails over a tolerance. It counts only what a bump here would fix, taking the difference against the same corpus rendered through carve-js main - a gate that fails for upstream debt gets its tolerance raised until it means nothing. The population guard is the other half of a check that can fail. An absent or truncated corpus renders zero documents, finds zero divergences and reads as a clean run, so the count is compared for EQUALITY against the number of ::: compare blocks in the spec's own example pages, which is where the corpus is generated from. Counting the corpus directory to decide how big the corpus should be would move both sides together and guard nothing, and a hardcoded 1131 goes stale the day an example lands upstream. Both halves were mutation-proved: the gate run against the previous pin fails with 256 of 1131 attributable, and against a corpus cut to 378 documents it reports the shortfall instead of a clean sweep.
…eader Three things the pin bump moves, all of them the engine catching up with the spec rather than anything this repo chose: - twenty-one new engine exports, none of them extension factories: the AST-JSON and AST-patch error classes, the BBCode and HTML import limits and their errors, the smart-quote locale table, and the AST-patch, HTML-import, source-layout and migration utilities. Each gets a reason in the playground's exclusion map, which is what the drift guard reads. - smartQuotes IS a real extension, and it is excluded anyway. The playground exists to show what this site renders, and the site's stack does not enable smart quotes, so enabling it only in the playground would make the two disagree on the same source - the reason defaultAttributes is excluded. - semantic-span is a real extension the engine now ships, and it belongs in the playground stack, so it joins the expected list rather than the exclusions. The table-scroll expectation loses nothing: a header cell in a body row is a ROW header and the engine now says so, which is one of the 256 documents the old pin got wrong.
dereuromark
deleted the
chore/engine-pin-and-a-drift-gate-that-can-fail
branch
August 17, 2026 09:40
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.
The pin was a carve-js revision from 2026-08-05, 333 commits behind main. Measured through the artifact rather than through the revision distance:
The bump is not mechanical - two upstream removals land with it. A block quote no longer carries an attribution field, since a caption on a quote makes a figure (markup-carve/carve#1213) and the figure branch of the substitution walk already covers that target. And
beforeRendernow takes the read-only context from the extension contract;page.tsunrollscarveToHtml, so it builds that context itself, with the mode fields constant because HTML is the only target here and there is no static mode.The half that let it drift
engine-drift.ymlcompared revisions and reported the lag as a warning annotation. A warning cannot fail a job, so every scheduled run was green for the whole 333 commits.Warning-only is defensible where the remedy is upstream - that is what the carve-php satellites do, because only a release can close their gap. Here the remedy is one line of
package.json, so the job now renders carve main's corpus through the engine as installed and fails over a tolerance. It counts only what a bump here would fix, by taking the difference against the same corpus through carve-js main: a gate that fails for upstream debt gets its tolerance raised until it stops meaning anything.The population guard is the other half. An absent or truncated corpus renders zero documents, finds zero divergences and reads as a clean run, so the count is compared for equality against the number of
::: compareblocks in the spec's example pages, which is what the corpus is generated from.Mutation proof
Against the previous pin, with everything else unchanged:
Against the new pin:
attributable=0, gate passes. And against a corpus cut to 378 documents the population guard reports the shortfall rather than a clean sweep:Closes #19