The corpus job can compare again, and the engine renders composite figures - #39
Merged
Merged
Conversation
… them The guard that decides whether a corpus run compared a whole corpus reads the spec's example pages and counts their `::: compare` blocks, because a count derived from the directory under test would move with the directory and could not detect truncation. It read them from docs/examples/. markup-carve/carve#1194 made those pages generated sources and filed them under resources/examples/, beside the other generator inputs. The read is fatal when it misses, by design, so the corpus job stopped at the first page instead of comparing anything: no corpus source page at .../docs/examples/core.md: no such file or directory. tests/corpus is generated from these pages; if the spec moved them, this helper has to move with them That is the failure the guard is supposed to produce, and it is also why every real divergence behind it went unreported: the job has been red since 2026-08-14 with a wiring error on top of a stale artifact, and only the wiring error was visible. The helper now takes the route corpus_ast_test.go already takes to resources/ast-schema.json. Measured against carve main: the job goes from fatal-before-first-comparison to 1023 pairs compared, 81 of them diverging.
…ot render
This module has no parser. It embeds a wasm32-wasip1 build of carve-rs and
renders by driving it over WASI stdio, so its conformance is the embedded
artifact's conformance and "implementing" a construct here means rebuilding
against a carve-rs that implements it.
Rebuilt from carve-rs c83d64922bcb080d5a85aecd2f53adf506fbe8c6 (main). Measured
against carve main, with the population guard fixed so the comparison runs at
all: 81 of 1023 corpus documents diverged before, 13 after. The 68 that this
closes are the ordinary staleness the corpus job exists to catch - tables,
captions, attribute parsing, footnote and reference resolution.
The 13 that remain are not staleness. They are two constructs carve-rs itself
does not implement, so no rebuild reaches them:
- 318-composite-figures{,-2,-3,-4,-5,-6,-9,-10,-11}: PART 9 §4c, composite
figures. carve-rs#986 was the port and was closed unmerged.
- 319-cell-attributes-bind-after-the-kind-and-alignment-markers{,-2,-3,-6}:
an attribute block after a cell's `=` marker is still rendered as cell text.
Two of the eleven composite-figure documents pass, and they are worth naming
because of WHY they pass: -7 (a `^ ` line after a `::: note` closer is an
ordinary paragraph) and -8 (an opener carrying a title or a label stays a
generic container) both assert the PRE-§4c behavior, which this engine has
always had. -8 in particular is the control for the whole clause, and today it
passes vacuously: making its openers bare changes nothing in the output but the
title and label lines, because the engine has no rule that tells a bare opener
from a decorated one. It will only start meaning something once §4c lands
upstream.
The corpus job stays strict. Nothing here suppresses those 13; the job is red
for them, which is the accurate report.
TestToHTML_Table asserted a bare <th>. The header cell carries scope="col" now,
pinned by the corpus documents this rebuild brings into line, so the hand-written
expectation moves with it - the failure mode corpus_test.go's comment predicts
for the hand-written half of this suite.
THE NATIVE COMPARISON NOW REQUIRES CARVE_BIN. TestToHTML_ByteIdenticalToNative
and the static byte check assert "the wasm renders what the native engine
renders", which is a statement about ONE revision - the native side has to be
the carve-rs internal/wasm/REV names. The helper picked its own reference off
the filesystem instead: two hardcoded target/ paths, then `carve` on PATH, none
of which is that revision by construction. It failed exactly that way here, with
a stale local build predating scope="col" reporting a byte mismatch on tables
against a wasm the corpus proves correct. A check that chooses its own reference
cannot tell "the wasm is wrong" from "the thing I found is old", and it reported
the first. It now skips until a caller vouches for a binary, which is what CI
already did on every run.
Contributor
Author
|
CI verdict on this head: 3 of 4 checks green, That failure is the intended report, not a defect in the branch: those constructs are unimplemented in carve-rs, and this module renders what carve-rs renders. Making the job green would mean either an allowlist this repository does not have and should not gain, or a claim that is not true. So this needs a maintainer call rather than an automated merge, and the choice is between two shapes of red on
The branch does not fix the red. It makes it say something. |
…ibutes The previous rebuild in this branch left thirteen corpus documents diverging, all of them naming two constructs the engine did not yet have. Both have since landed in carve-rs: markup-carve/carve-rs#986 (PART 9 section 4c, composite figures) and markup-carve/carve-rs#996 (a cell's attributes bind after its kind and alignment markers). Rebuilt via this repository's own build-wasm.sh, which writes REV and the digest in the same step as the bytes. REV moves from c83d64922bcb080d5a85aecd2f53adf506fbe8c6 to 98de7874ad2e81f69e57764562c83f4918522ac2 (carve-rs main). Measured against carve 1436f39a, 1025 corpus pairs: before this commit 13 diverging after 0 diverging The thirteen that closed are the nine `318-composite-figures*` documents that were failing and the four `319-cell-attributes-*` documents that were failing. All eleven `318-composite-figures` documents and all six `319-cell-attributes` documents now match the spec. The control is no longer vacuous. `318-composite-figures-8` asserts that an opener carrying a title or a `[label]` stays a generic container. Before this rebuild it passed for the wrong reason: the engine had no rule separating a bare opener from a decorated one, so stripping the title and the label changed nothing but two metadata lines. It now discriminates structurally. The document as written renders: <div class="figure"> <p class="admonition-title">A titled figure div</p> <figure> <img src="a.png" alt="one"> <figcaption>(a) One</figcaption> </figure> </div> <p>^ Not a group caption</p> and the same document with the title and the label removed, so both openers are bare, renders a different production entirely: <figure class="carve-figure-group"> <div class="carve-figure-panels"> <figure class="carve-figure-panel"> <img src="a.png" alt="one"> <figcaption>(a) One</figcaption> </figure> </div> <figcaption>Not a group caption</figcaption> </figure> No allowlist and no drift file exists in this repository, and none is added.
dereuromark
added a commit
that referenced
this pull request
Aug 15, 2026
expectedNodeTypes is this package's own record of what the language emits, and the assertion over it is one-directional by design: a recorded type going missing fails, an unrecorded new type is invisible. So the engine rebuild that brought PART 9 section 4c in (#39) added a node type nothing here names. Recording it is a tightening, not a widening. `figure_group` appears in no carve-rs source file at the revision this package embedded before that rebuild (c83d6492, zero hits under src) and in eleven at the one it embeds now (98de7874), so this line would have failed the suite before the rebuild and passes after it. From here on, a rebuild that drops the group node fails with the type named rather than leaving nine corpus documents to explain it. The second assertion in this file gains the same reach for free: it requires the spec's ast-schema.json to define a shape for every type recorded here, and the schema does define figure_group. Measured with CARVE_SPEC_CORPUS against carve 1436f39a: 1025 pairs, whole package green.
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.
Part of markup-carve/carve#1220 (secondary-engine tracker); spec markup-carve/carve#1215, design markup-carve/carve#1122.
What this repository can and cannot do about composite figures
carve-go has no parser. It embeds a
wasm32-wasip1build of carve-rs and renders by driving it over the WASI stdio contract, and the README states the consequence: "The Go output is therefore byte-for-byte the output of the engine it wraps." So there is no port to write here. Implementing PART 9 section 4c in this module means rebuilding against a carve-rs that implements it, and carve-rs now does: markup-carve/carve-rs#986 landed, with markup-carve/carve-rs#996 (cell attributes) on top. The tracker's checklist line for this repository ("port parser/renderers/writer per section 4c") describes a native engine; this one is a wrapper.The three changes
The corpus population guard reads the pages where the spec keeps them. The guard counts
::: compareblocks in the spec's example pages so the corpus size is checked against something the runner does not itself write. It read them fromdocs/examples/; markup-carve/carve#1194 filed them underresources/examples/. The read is fatal when it misses, by design, so the job stopped at the first page:That is the guard working. It is also why the drift behind it was invisible: the job had been red since 2026-08-14 with a wiring error stacked on top of a stale artifact, and only the wiring error was legible. The helper now takes the same route to
resources/thatcorpus_ast_test.goalready takes forast-schema.json.The embedded wasm is rebuilt, via this repository's own
build-wasm.sh, which writesREVand the digest in the same step as the bytes.REVmoves frome2b53a5b(main) throughc83d6492to98de7874, current carve-rs main.TestToHTML_Tableasserted a bare<th>; the header cell carriesscope="col", pinned by the corpus documents this rebuild brings into line.Measured
Against carve
1436f39a, 1025 corpus pairs:c83d649298de7874The 13 that closed in the last step were the two constructs carve-rs itself had not implemented when the earlier rebuild was taken: nine
318-composite-figures*documents and four319-cell-attributes-*documents. All eleven318-composite-figuresdocuments and all six319-cell-attributes-bind-after-the-kind-and-alignment-markersdocuments now match the spec.The corpus job stays strict. Nothing here suppresses anything; this repository has no allowlist and does not gain one.
Proof, both directions
Reverted. With the artifact built from
c83d6492and everything else in this branch unchanged, exactly those 13 documents diverge, and each diverges by producing the pre-section-4c shape.318-composite-figures-4:rendered a generic container and left the caret line a paragraph:
Restored. With the artifact built from
98de7874, all 1025 pairs match, including that one.The control now discriminates, and it did not before
318-composite-figures-8is the control for the whole clause: an opener carrying a title or a[label]must stay a generic container rather than become a figure group. It passed before this rebuild too, but vacuously - the engine had no rule separating a bare opener from a decorated one, so mutating the document to make both openers bare changed nothing in the output but two metadata lines. Both spellings still rendered<div class="figure">.Re-run against the rebuilt artifact, the same mutation changes the production. The document as written:
and with the title and the
[label]stripped, so both openers are bare:Different element, different class, and the caret line is consumed as the group caption instead of surviving as a paragraph. The control asserts something now.
Also in the diff
nativeCarveBinnow requiresCARVE_BINinstead of picking a binary off the filesystem. That check asserts "the wasm renders what the native engine renders", a statement about one revision - the native side has to be the carve-rsinternal/wasm/REVnames. It fell back to two hardcodedtarget/paths and thencarveon PATH, none of which is that revision by construction, and it failed that way here: a stale local build predatingscope="col"reported a byte mismatch on tables against a wasm the corpus proves correct. A check that chooses its own reference cannot tell "the wasm is wrong" from "the thing I found is old", and it reported the first. It now skips until a caller vouches for a binary, which is what CI already did on every run, having neither checkout.No CHANGELOG entry: this repository keeps no CHANGELOG (the last rebuild,
#38, touched only the artifact files).