Carve extensions contract (NORMATIVE)
This document is normative. The conformance corpus (tests/corpus) remains the authority for Tier-1 output; the optional Tier-2 corpus (tests/corpus-optional) pins configuration-dependent outputs per feature id. This document defines the feature taxonomy and the extension mechanism every implementation realizes.
1. Feature taxonomy
| Tier | Definition | Default | Conformance |
|---|---|---|---|
| 1 · Core | Normative syntax in resources/grammar.ebnf + the corpus; identical output everywhere. | Always on | Mandatory (corpus) |
| 2 · Standard-recommended | Spec-listed behaviors every impl SHOULD offer but ship off/passthrough. | Off / passthrough | Optional corpus when enabled |
| 3 · App extension | Not in the spec, not conformance-tested, may exist in one impl only. | Off | Never |
Invariant: a feature's tier is identical in every language; a Tier-1 feature is core-and-default-on everywhere and its default output is corpus-pinned.
- Tier 1: corpus categories 01–88 (admonitions, footnotes, cross-references, list-item attributes,
::: |verse,<…>autolinks, the:name[…]/::: nameextension syntax). Recognized:::type words (the eight admonitions +line-block) are catalogued inexamples.md. Smart typography and@mention/#tag/:emoji:parsing are also default-on and corpus-pinned, but per grammar PART 19 a processor MAY disable them. - Tier 2: configuration over Tier-1 syntax — mention/tag→URL, emoji glyph map, locale smart-quote sets, bare-URL autolinking.
- Tier 3: Mermaid, Tabs, CodeGroup, TableOfContents, HeadingPermalinks, HeadingLevelShift, ExternalLinks, DefaultAttributes, Wikilinks, SemanticSpan.
Inline and sidenote footnotes are not Tier 3. They are deferred core reserved syntax ([^…] inline, [>…] sidenote; resources/grammar.ebnf PART 9 §16), not an app extension. The djot-php […]{.fn} form maps onto carve's [^…]; see native-features-analysis.md.
2. Extension system
An extension is a named unit contributing any subset of four things, run as:
parse (core + extension MATCHERS)
→ afterParse TRANSFORM → beforeRender TRANSFORM
→ render (core + extension RENDERERS)
2.1 Matchers (parse stage) — scanner-function contract
- inline:
(text, pos, ctx) -> { node, end } | null - block:
(lines, ctx) -> { node, linesConsumed } | null ctxexposes: definition tables (link/footnote/abbr); recursiveparseInlines(text)/parseBlocks(lines); the extension's config.- Precedence: core matchers run first at each position; extension matchers are tried only where core does not consume (extensions add syntax, never hijack core). Extension matchers run in registration order; optional integer priority is the escape hatch.
2.2 Transforms
- afterParse
(Document) -> Document(collection/inspection) - beforeRender
(Document) -> Document(mutation) - Every extension's afterParse runs before any extension's beforeRender; within a phase, registration order.
2.3 Renderers
- Registered per node type / extension name; receive the node, emit implementation-specific output. Renderers are the impl-idiomatic half; matchers and transforms are the portable contract.
2.4 Registration & config
- Impl-idiomatic (PHP
addExtension/ctor; JSextensions: [...]option), declaring the same lifecycle contributions.
3. Home, conformance, per-impl
- This document is the normative home for the taxonomy + contract.
- syntax.md §4.20 is the non-normative narrative.
- Conformance: Tier-1 = existing corpus (mandatory); Tier-2 =
tests/corpus-optional+manifest.json, run per enabled feature; Tier-3 = never in any corpus.