Parser Backend Audit

cargo xtask parser-audit compares mdwright's production pulldown-cmark backend with cmark-gfm, using the vendored GFM spec expected HTML as the primary oracle. The audit renders mdwright through the opt-in cmark-gfm render profile so renderer spelling drift is separated from parser-tree drift. It does not replace mdwright-document as the production parser boundary.

cmark-gfm is the primary oracle because crates/mdwright/tests/gfm-spec/spec.txt is vendored from cmark-gfm and the GFM ecosystem treats its rendered HTML as the reference. comrak is optional diagnostic evidence for rendered HTML and source-position behaviour; it is not a release gate unless a future audit shows it catches mdwright-relevant risks that cmark-gfm cannot expose.

Running

cargo xtask parser-audit \
  --case-set all \
  --output target/mdwright/parser-audit \
  --ensure-tools \
  --include-comrak

The command builds a pinned cmark-gfm under target/mdwright/tools/ when --ensure-tools is passed. To use an already-built binary explicitly, pass --cmark-gfm-bin <path>.

Reports are written to:

  • target/mdwright/parser-audit/parser-audit.json
  • target/mdwright/parser-audit/parser-audit.md

Examples marked disabled in the vendored GFM spec are still reported, but cmark-gfm binary drift from the expected HTML for those cases is not a command failure because the upstream spec does not treat the rendered checkbox spelling as a strict conformance assertion.

The audit also checks source-position envelopes for constructs mdwright uses as formatter/linter facts. It maps cmark-gfm data-sourcepos line/column ranges back to source bytes and compares them against mdwright document facts by construct kind. This is a risk gate, not exact AST equality: a difference is reported only when mdwright has no overlapping fact for a rewrite/lint-owned construct.

Status Values

  • pulldown-html-mismatch: mdwright's pulldown-backed HTML differs from cmark-gfm expected HTML.
  • mdwright-policy: mdwright intentionally differs from the cmark-gfm oracle for a documented parser policy.
  • extension-gap: the compared parser does not implement the construct.
  • sourcepos-risk: rendered output matches, but coordinate facts may affect formatter/lint safety.
  • event-only: internal event/AST shape differs while rendered HTML and semantic signatures match.
  • upstream-panic: parser panic or crash contained by mdwright-document.
  • needs-mdwright-mitigation: upstream behaviour is unsafe for mdwright and still needs a fix.
  • fixed: the difference should no longer appear; the audit fails if it does.

Classifications

Current gfm-spec audit snapshot with mdwright's cmark-gfm render profile:

MetricCount
Cases673
HTML mismatches15
Sourcepos envelopes checked1071
Sourcepos differences0
Unclassified differences0

Observed difference classes:

ObservedCount
pulldown-html-mismatch:emphasis-resolution9
pulldown-html-mismatch:html-block-rendering3
pulldown-html-mismatch:tasklist-rendering2
pulldown-html-mismatch:table-rendering1
upstream-panic1
Case SetKeyObservedStatusOwnerResolution
**mdwright-policy:gfm-bare-autolinks-enabledfixeddocumentParser-audit now mirrors the cmark-gfm extension set per spec case, so default production GFM policy no longer creates non-extension CommonMark audit drift.
**mdwright-policy:gfm-email-autolinks-disabledfixeddocumentGFM email autolinks are recognised by mdwright-document's source-positioned GFM overlay.
**mdwright-policy:gfm-tagfilter-disabledfixeddocumentGFM tagfiltering is enabled by default in mdwright-document's render/signature policy.
**pulldown-html-mismatch:gfm-autolinkfixeddocumentGFM URL and email autolink mismatches should be handled by mdwright-document's GFM autolink overlay.
**pulldown-html-mismatch:gfm-tagfilterfixeddocumentGFM tagfilter mismatches should be handled by mdwright-document's GFM tagfilter overlay.
**pulldown-html-mismatch:quote-escapingfixeddocumentThe cmark-gfm render profile escapes double quotes in text/code contexts where cmark-gfm emits &quot;.
**pulldown-html-mismatch:href-escapingfixeddocumentThe cmark-gfm render profile percent-encodes link destinations where cmark-gfm percent-encodes them.
gfm-specTables (extension)pulldown-html-mismatch:table-renderingfixeddocumentThe cmark-gfm render profile spells ordinary GFM table markup with cmark-gfm row, alignment, and body layout.
gfm-speccase-160pulldown-html-mismatch:table-renderingpulldown-html-mismatchdocumentThis is a raw HTML table containing indented code, not a GFM table. The remaining drift is parser/backend handling of blank raw-HTML text around child blocks, not formatter rewrite risk.
gfm-speccase-279, case-280pulldown-html-mismatch:tasklist-renderingpulldown-html-mismatchdocumentThese spec examples are marked disabled; cmark-gfm's binary output and mdwright's cmark-gfm profile match, while the vendored expected HTML intentionally does not assert the checkbox spelling.
**extension-gap:myst-definition-listextension-gapdocumentcmark-gfm does not own MyST directive syntax; mdwright's default definition-list recognition can make directive-heavy fixtures render differently through pulldown HTML, while formatter preservation is handled by mdwright document facts.
corpusexternal:jupyter_book_minimal/admonitions.md, external:jupyter_book_minimal/asides.md, external:jupyter_book_minimal/blocks.md, external:jupyter_book_minimal/directives.mdsourcepos-risk:paragraphextension-gapdocumentcmark-gfm reports MyST directive/admonition syntax as ordinary paragraph source ranges, while mdwright treats the same bytes as extension-owned containers or preservation facts. The corpus rows pin that non-GFM coordinate drift so it cannot silently expand.
gfm-speccase-120, case-152, case-153pulldown-html-mismatch:html-block-renderingpulldown-html-mismatchdocumentpulldown's event stream omits leading indentation on raw HTML blocks that cmark-gfm preserves in rendered HTML. mdwright accepts this as backend render drift because source-coordinate facts remain stable.
gfm-speccase-144pulldown-html-mismatch:html-block-renderingfixeddocumentThe cmark-gfm render profile now matches cmark-gfm's newline placement for this list/raw-HTML case.
gfm-speccase-398, case-426, case-434, case-435, case-436, case-473, case-474, case-475, case-477pulldown-html-mismatch:emphasis-resolutionpulldown-html-mismatchdocumentpulldown's emphasis resolution differs from cmark-gfm on these delimiter-stack edge cases; mdwright currently treats this as a parser-backend conformance gap, not a formatter-local bug.
operationalknown-pulldown-link-ref-tab-panicupstream-panicupstream-panicdocumentpulldown-cmark issue 1095 is contained by mdwright-document::ParseError; product paths do not panic.

The cmark-gfm render profile is an HTML spelling profile. It fixes quote escaping, link-destination escaping, ordinary GFM table spelling, task-list checkbox spelling, and one newline-placement case where the parser already exposes enough structure. It does not change emphasis resolution or source-position semantics. Full cmark-gfm parser equivalence would require upstream pulldown changes, a maintained fork, or a backend switch.

Replacement Criteria

Do not replace pulldown-cmark based on event-shape differences alone. A replacement candidate must improve at least one release-relevant axis without regressing the others:

  • fewer unclassified or policy-relevant HTML mismatches against cmark-gfm;
  • safer behaviour on malformed/user input;
  • stable byte/source coordinates sufficient for formatter rewrite ownership;
  • extension coverage at least as good as the current document facts;
  • acceptable runtime and dependency footprint.