# myform — the universal graph, and documents that communicate > **Spec of record** for the post-1.0 horizon, as of 2026-08-05: drafted, reviewed by the operator, and revised against decisions **D1–D6** and follow-ups **O1–O3** (§7). No question is open. > Drafted against `myform 1.0.0`. > Successor horizon to [`myform-spec.md`](myform-spec.md), which this document assumes and does not restate. > Origin: operator ask — *cannibalize corpus `mdsite`/`myst-report` into myform; assess the universal-translator claim; extend the graph to HTML, wikitext, and Obsidian.* > O3 extended that set to LaTeX, docx, and the incidental Markdown flavors (§P10–P12, §3.11). ## `1.` Where we actually stand ### `1.1.` The good news is structural `myform 1.0.0` is not "a translator that happens to support four formats." It is a hub, and the hub is the whole point: - **Adding a format costs 2 units of work, not N.** One reader, one writer. The 4×4 matrix is 16 cells built from 8 modules. - **Each new format adds `2N-1` cells for free.** Going from 4 formats to 7 takes the graph from 16 cells to **49** — for six new modules, not thirty-three new converters. - **The audit grows itself.** `tests/test_matrix_sweep.py` reads `Reader.REGISTRY` and the writer registry *at collection time*. A new format registers and the sweep expands without anyone editing the test. That is the bold claim already paid for. Everything below is an argument about what to spend the next increment on, not about whether the architecture holds. Two corrections this document later makes to its own arithmetic, flagged here so the optimistic version above is not read alone. **Seven is not the horizon** — §3.11 puts nine engines plausibly in scope and §P10 adds dialects on top, so the real figure is nearer 169 cells than 49. And **the self-growing audit is a liability at that size** as surely as it is an asset at this one: a sweep that expands without being edited also expands without being budgeted. §4 makes both explicit and prices them. ### `1.2.` Three gaps stand between here and "universal translator" **Gap A — the hub has no web terminal.** Every format myform speaks is an *authoring* format. HTML is the universal *sink*: it is where documents go in order to be read by someone. A translator that cannot emit HTML can translate documents but cannot finish the job of communicating them. One nuance, added after D4: the *renderer* was never the problem. `mdsite` builds on Sphinx, and should — that is the institutional center, not a workaround. What is a workaround is the 300-odd lines of regex that **patch `mdsite`'s inputs into shape** before Sphinx sees them, because nothing upstream could parse the documents it was collecting. The gap is a parser gap, not a renderer gap. **Gap B — there is no collection layer.** The entire public surface is `convert(text, src, dst)`: one document, no identity, no neighbors. Real communication artifacts are *N documents with links between them*. `mdsite`'s `manifest.json` plus `doc_url()` is an embryonic cross-document resolver, hand-rolled in a script, outside the type system. **Gap C — "95% native" is currently undefinable, let alone unmeasured.** The parts exist — `Capability` per (writer, node kind), `MappingGrade` per reader construct, a registry-driven sweep — but there is no denominator, no weighting rule, no corpus of record, and no floor. The claim cannot be made honestly today. It also cannot be *disproved*, which is worse. ### `1.3.` One thing that is nearly there Nothing a reader saw is ever unrecoverable in the tree: `Unknown`, `Ignored`, and `SyntaxRaw` (with its `MappingGrade`) guarantee that. But at the **writer** boundary the fates collapse to `APPROX`, `CARRY`, `DROP` — all three of which are *in-band*: the content is approximated in place, reduced in place, or deleted. The operator's phrase was "the rest being safely demarcated and linked." That is a fourth fate — *preserved out-of-band, referenced in-band* — and it does not exist yet. It is the single most valuable missing primitive in the system (§P2). ## `2.` The ask, restated Three things, treated as one investigation: 1. Absorb corpus `mdsite` and `myst_report` as first-class myform functionality. 2. Assess and extend the translator graph toward HTML. 3. Add wikitext (MediaWiki) and Obsidian-flavored Markdown. **Assumption, since confirmed.** The parenthetical "(& mas?)" in the original ask was read as *"y más"* — "and more?" — and the operator confirmed it. The follow-ups filled it in: wikitext and Obsidian first, then **O3**'s LaTeX, docx, and "incidental flavors of markdown." §P10–P12 propose those, and §3.11 discharges O3's second half by recording, per format, exactly what makes it cheap or expensive. ### `2.1.` Two terminals, not one pipeline *(O1, answered)* > **O1 answered:** *to-PDF and to-HTML are separate goals that only sometimes coincidentally share infrastructure.* That is a sharper ruling than the "both, per document" resolution §P5 originally proposed, and it settles a question that would otherwise be re-litigated at every format: | Terminal | Purpose | Document scale | Collection scale | | :------- | :------------------------------------------------- | :------------------------- | :------------------------- | | **to-HTML** | be *read* on the web — searchable, linkable, indexed | an HTML fragment (P1) | a Sphinx site (P5) | | **to-PDF** | be *typeset* for print — pagination, typography | `typst compile`; LaTeX (P11) | Sphinx's own LaTeX builder | **What "separate goals" forbids.** Neither terminal is a fallback for the other. A PDF is not compensation for what HTML could not render, and an HTML page is not a preview of a PDF. Concretely, a compiled artifact a site links **is a link, not an `ENCLAVE`** (§P2): an enclave exists because a target *cannot express* a construct, whereas a Typst document translated to a MyST page **was** expressed — the PDF beside it is a peer deliverable the same collection happened to produce. Routing it through the enclave mechanism would re-couple precisely what this ruling decouples. **What "sometimes coincidentally share infra" permits.** Both terminals consume the same `Doc` and the same `Collection` (P4), and one `myform site` run may legitimately produce both. Sharing the front half of the pipeline is fine; deriving one goal's quality bar from the other's is not. **Consequence for §5.4.** `corpus.typst`'s shelling of the `typst` binary is **to-PDF infrastructure** — feature-detected and optional, and its absence degrades the PDF goal only, never the site. This is also why P1 and P11 are not redundant with Sphinx's own builders: myform's writers are *document*-scale termini, Sphinx's builders are *collection*-scale ones, and neither displaces the other. ## `3.` Proposals Ordered by dependency, not by size. **P2 and P4 carry the architecture**; everything else is cheaper once they land. P1 was originally the third load-bearing member, but D4 removed the dependency that made it so, and O2 moved it to wave 2 accordingly — see §P1 and §7. P1–P9 answer the original ask; **P10–P12 answer O3**, and §3.11 records what O3 asked to have recorded. ### `3.1.` P1 — The HTML writer **Proposal.** `Doc → semantic HTML5`, as an ordinary writer in the existing seam. Scope: a document *fragment* by default, an optional standalone wrapper; `Target` nodes become stable `id` attributes; `Code` becomes `
`; `Math` emits MathML or delimiter-preserved spans for a client-side renderer (decision D3); no CSS framework, no JavaScript, no theme opinions — those belong to P5.
**What the D4 reversal cost it, stated plainly.** Two of P1's four original justifications were downstream of the native-site plan and died with it. It no longer "closes the sink" — Sphinx emits the site's HTML. And the sidecar-free `` enclave is now a P1-only convenience rather than the primary path (§P2).
**What survives, and still justifies building it.**
- **HTML is the capability ceiling, and that makes it a measurement instrument.** HTML can render essentially every node kind in the tree natively. Once that is true and tested, coverage for every *other* target is measured against a known-achievable top rather than an imagined one. P3 is much weaker without it.
- **`convert(text, ..., 'html')` is a real want independent of any site** — a fragment for an email, a paste, an issue comment, a CMS. That is the ordinary translator use case, and it needs no site machinery at all.
- Cleanest possible seam fit: a `Writer` subclass and a capability table, lazy-loaded exactly like `RstWriter`/`TypstWriter` via `_DIRECT_WRITERS`. `test_matrix_sweep.py` picks it up with no edit — 4 audited cells immediately, 7 once §3.7–3.9 land.
**Sequencing, settled (O2 — delegated to the author, §7).** P1's wave-1 rationale *was* "unblocks P5," and D4 made that false; nothing in wave 1 depends on P1. **P1 therefore trades its wave-1 slot with P7 and moves to wave 2, immediately ahead of P3.** The reason is not that P1 matters less — it is that P1's one surviving structural justification is *being P3's measurement ceiling*, and P3 sits in wave 2. Splitting a dependency pair across waves would leave the dependency idle for an entire wave; moving P1 puts it adjacent to its only dependent, and leaves wave 1 as three mechanisms with no idle member (§7, D1 as amended).
**Bonus.** EPUB is HTML in a zip with a manifest. Once P1 exists, an EPUB writer is close to free — the only frontier-tier format worth an early card (§3.10).
### `3.2.` P2 — `ENCLAVE`: the fifth capability
**Proposal.** Add a fifth `Capability` variant meaning: *this content cannot be rendered natively, so it is preserved verbatim out-of-band and referenced in-band by a stable, typed marker.*
Per-target realization:
| Target | In-band marker | Out-of-band carrier |
| :--------------------------- | :------------------------------------------- | :-------------------------------- |
| **MyST → Sphinx site *(primary)*** | admonition linking to the enclave page | **the sidecar is a real staged page** |
| MyST / GFM *(standalone file)* | admonition or blockquote + relative link | `.enclaves/.` |
| rST | `.. note::` + `:download:` or link | same sidecar |
| Typst | bordered `#block` + link | same sidecar |
| HTML (P1) | `` disclosure, labelled by source fmt | none needed — inline |
D4 promotes the first row from bonus to **primary**: inside a site, an enclave is a staged page plus an admonition pointing at it — navigable, searchable, and indexed by Sphinx like any other document. The `` form is now a P1-only convenience.
Three required properties:
- **Reversible.** An enclave round-trips back to the original node when the reverse conversion runs. This is what makes it *safe* rather than merely polite.
- **Addressable.** Stable content-hash id — the same id every build, so links do not churn.
- **Reportable.** One `ConversionReport` entry, obeying the existing AC4 zero-silent-loss invariant unchanged.
**Implemented carrier contract.** `convert_artifact()` and
`Session.convert_artifact()` return a `ConversionArtifact`: target text, its report, and validated
typed `EnclaveRecord` carriers. Each record uses a full SHA-256 id and the canonical relative path
`enclaves/.json`; exact reader syntax is retained when `source_raw` exists. Each id binds the
canonical typed payload, source format, and structural occurrence, while `content_hash` remains
position-independent. An act is `ENCLOSED` only when the lowering returns that exact carrier,
the report row binds its id and path, and the writer recovers a final typed target-native link.
Marker-only legacy rules and raw path substrings are ordinary lossy replacements. Legacy text and
text-plus-report APIs raise `ArtifactRequiredError` rather than discard a real carrier. No
built-in writer adopts `ENCLAVE` until its target-specific marker/materialization contract has
end-to-end tests.
**Why it matters more than it looks.** Today a construct that a target cannot express is *lost information presented as a translation*. An enclave is the opposite: honest, complete, and navigable. It converts the failure mode of the whole system from "quietly wrong" to "visibly deferred." It is also the mechanism that makes wikitext tractable at all (§P8) — MediaWiki templates go into enclaves rather than into a promise we cannot keep.
**Versioning consequence — flag for decision.** `Capability` is part of the extension surface published at v1.0.0. Adding a variant is additive to the enum but changes the exhaustiveness contract for any third-party writer matching on it. Recommendation: **v1.1, additive, with a documented fallback** (a writer that does not know `ENCLAVE` degrades it to `CARRY`), rather than holding it for a v2. See D2.
### `3.3.` P3 — A coverage instrument, and an honest definition of 95%
**Proposal.** Define the metric before building the tool, then publish both.
**The definition.** End-to-end nativeness is not a writer property. A construct read at `MappingGrade.LOOSE` and written at `Capability.NATIVE` was *not* natively translated — the loss happened on the way in. Therefore:
> **nativeness(node) = min(read grade, write capability)**, projected onto a common ordinal scale.
**The denominator: characters, not nodes.** Node-count coverage flatters the number badly — a document is mostly `Text` nodes, so counting nodes measures how much prose is prose. Weight by **characters of rendered text content** under each node. That is the fraction a human would recognize as "how much of my document came through."
**The corpus of record.** Explicitly *not* `tests/data/testcases.md` — that file is adversarial by construction and coverage measured on it is meaninglessly low. Measure on real documents:
- `~/my/corpus/journal/reports/**` — the fleet's own prose, the actual use case.
- A pinned public sample per format: the CommonMark spec suite, a real Sphinx docs tree, a small Wikipedia article set, a sample Obsidian vault.
**The surface.** `myform coverage [--from X] [--to Y] [--corpus PATH]`, emitting a per-cell table and a JSON sidecar; published as a CI artifact so the matrix table in the README can stop being hand-maintained prose and start being generated truth.
**The floor.** Per-cell and tiered (§4), not one global number. A single global "95%" would be gamed by the easy cells the moment it was adopted.
### `3.4.` P4 — `Collection`: the missing abstraction above `Doc`
**Proposal.** A typed container for *N documents with relationships*:
```python
class Collection(BaseModel):
root: Path
docs: dict[DocId, DocEntry] # id -> source path, format, output rel, Doc
order: NavTree # explicit or derived ordering/nesting
```
It owns two things no `Doc` can own alone — source→output mapping and **intra-collection reference resolution** — plus an ordering hint that P5 turns into a `toctree`.
**D4 makes this smaller than first specced.** Navigation rendering and full-text search are *not* Collection's job: Sphinx's `toctree` and its search index already own both. What remains is sharper for having been narrowed — resolve source-side references, and emit **Sphinx-native** cross-references from them.
**Why this is an architecture proposal and not a site feature.** Consider what a cross-document reference does today. An Obsidian `[[Note]]`, a MyST `{doc}` role, a Sphinx `:ref:`, a wikitext `[[Article]]`, a `toctree` entry — every one of them is a *promise about a document that is not this one*. With only a `Doc`, that promise is unresolvable, so every such construct is at best a `CARRY`.
Give the writer a `Collection` and the identical construct resolves to a real MyST `{doc}` / `:ref:` cross-reference, which Sphinx then wires up: **`CARRY` becomes `NATIVE` for an entire class of nodes across every format simultaneously.**
That means P4 is not a feature that sits beside P3 — **P4 moves the P3 number**, and it is the only proposal here that does so for many formats at once. It is also the precondition for Obsidian being worth anything (a vault is a collection or it is nothing) and for wikitext links resolving at all.
#### `3.4.1.` How a writer reaches a `Collection` — settled before coding
`Writer.write(doc, options)` has no collection parameter, and it must not grow one: that is a signature change across every writer and every third-party extension compiled against the v1.0.0 surface. The alternative that suggests itself — smuggling a live `Collection` through the `options` mapping — is worse, because `MarkdownWriter.write` forwards `options` straight into `build_engine`, i.e. into mdformat's own options dict.
The seam already exists, and the architecture already names the pattern. `Session._convert_with_report` runs `read → lower → write`, where `lower()` exists precisely so that *"writers never contain 'if this can't be rendered' branches: the constraint lives in this one declared gate"* (`writers/base.py`). Cross-document references get the identical treatment, one stage earlier:
```python
doc = reader.read(text, options=source_options)
doc = resolve(doc, collection) # P4: cross-doc references become concrete
lowered, report = lower(doc, writer.capabilities, ...)
rendered = writer.write(lowered, options=target_options)
```
**This is what makes the `CARRY → NATIVE` claim true rather than merely asserted.** `resolve()` rewrites an unresolved cross-document reference into an ordinary `Link` or cross-reference node *before* `lower()` consults the capability table. The table therefore never sees the exotic kind at all — it sees a kind every writer already renders `NATIVE`. One pass upgrades every format simultaneously **because no writer is edited**, which is the same reason the degradation engine can add a lowering rule without touching a backend.
Two properties follow for free, and both matter:
- **Additive in the strict sense.** With no `Collection` supplied, `resolve()` is the identity: the reference keeps its unresolved kind and the capability tables treat it exactly as they do today. Single-document `convert()` is byte-for-byte unchanged.
- **Reachable.** The pass runs inside the real pipeline via `Session.convert_in_collection`, which returns three values rather than two. A pass that only tests can call is not a landed feature, however green its own suite.
- **Reportable, but in its own report.** An earlier draft of this section said a dangling reference should flow through `ConversionReport`. Building it showed that to be wrong: a degradation report records what a *target* could not express, whereas a dangling reference is neither approximated, replaced, dropped, nor enclosed — it is a promise the *collection* could not keep. Merging the two would make `ConversionReport.lossy` mean two different things at once. `resolve()` therefore returns its own `ResolutionReport`, and collection-wide link checking is a filter over those rather than a fourth pass.
### `3.5.` P5 — `myform site`: cannibalizing `mdsite` *(revised — D4 answered)*
**D4 was answered "Sphinx stays," and the original proposal here was wrong.** Sphinx, rST, and ExecutableBooks/MyST are the institutional center this project sits inside, alongside Typst; re-implementing a renderer that covers Sphinx's interface is a bad trade at any size. And yes — `mdsite` does already build on Sphinx today. It stays there.
**The corrected architecture.** myform's job is translation *into* Sphinx, never replacement *of* Sphinx:
```
Typst ─┐
Obsidian ─┤ ┌─ conf.py + toctrees (generated)
wikitext ─┼→ myform readers → Doc → MyST writer ──┤
GFM ─┤ └─ sphinx-build → furo HTML
rST/MyST ─┘ (already native; pass through)
```
The unlock the native path was reaching for survives intact, and is better served this way: **Sphinx cannot read Typst, Obsidian, or wikitext — so myform converts them into MyST, and Sphinx does what it has always done.** No renderer is rewritten. The input set grows from two formats to seven.
**So `myform site` is:** `Collection` (P4) + translate-every-source-to-MyST + generate `conf.py` and toctrees + shell `sphinx-build` + serve.
Which is to say: *`mdsite`'s existing architecture, with myform's parser where the regex massage stage used to be.*
**The engineering win survives the pivot whole — and is now the main one.** `mdsite._massage` exists because Sphinx needs its markdown pre-patched into compliance: regex-injecting missing `# H1`s, rewriting ad-hoc `## title:` lines into YAML, masking fences so heading detection does not misfire. myform does not patch text; it **parses** it. A missing H1 is a `Doc` with no `Heading` at depth 1, fixed by a typed transform. That whole regex layer — and much of the `suppress_warnings` list beneath it — is deleted rather than reimplemented, because what reaches Sphinx is canonical MyST rather than agent-authored markdown of unknown quality.
**Typst fidelity — O1, answered (§2.1).** The question was whether routing `typst → MyST → Sphinx` regresses `mdsite._render_typst`'s compiled SVG+PDF output, which is the flagship path (`corpus md → typst → PDF`, me-myself.ai). The ruling is that **to-PDF and to-HTML are separate goals**, so the framing was wrong rather than the answer being close:
- `myform site` serves the **to-HTML** goal. A `.typ` source in a collection is translated to a MyST page: searchable, linkable, indexed by Sphinx like any other document. This is not a lossy approximation of the PDF — it is a different artifact with a different purpose.
- The **to-PDF** goal compiles that same `.typ` (or a `Doc` routed through the Typst writer) with the `typst` binary, when the operator asks for a PDF. It is a peer deliverable, not fidelity insurance.
- When both goals run over one collection, the site may **link** the compiled artifact. That link is an ordinary link — explicitly **not** an `ENCLAVE`, since nothing about the document was inexpressible (§2.1).
The practical consequence for P5 is a simplification: there is no per-document "translated or typeset?" toggle to design, because the two are not alternatives. `myform site` builds a site; `myform pdf` (or the existing corpus wrapper) builds PDFs; a collection may be handed to both. Typst compilation is feature-detected — no `typst` binary degrades the PDF goal only, never the site.
**Packaging.** `myform[site]` extra pulling sphinx + myst-parser + furo, every import function-local. `import myform` never touches them, so §5.1's budget holds by construction rather than by vigilance.
**There is no Stage B.** The "native backend later" idea is withdrawn, not deferred.
### `3.6.` P6 — `myform site new`: absorbing `myst-report`
`corpus/myst_report.py` is 188 lines that scaffold a Sphinx directory and shell `sphinx-build`. Under P5 both halves are already present. **Recommendation: do not port it as a separate verb** — it becomes `myform site new `: a template plus a `Collection`. Corpus keeps a thin `myst-report` wrapper (§5.4) so the command and its muscle memory survive.
### `3.7.` P7 — Obsidian-flavored Markdown *(wave 1, per O2)*
**Proposal.** A dialect over the existing markdown engine (§P10) — not a new engine, but not free either: **Extension band**, because four of its constructs have no plugin behind them.
| Obsidian construct | Maps to |
| :------------------------ | :---------------------------------------------------------------- |
| `[[Note]]`, `[[Note\|alias]]` | `Link`, resolved through `Collection` — **NATIVE with P4, CARRY without** |
| `![[Note]]`, `![[Note#^blk]]` | transclusion — new `Embed` node, or `Directive{include}` |
| `> [!tip]- Title` | `Directive` admonition; fold state `+`/`-` → a dropdown option |
| `%%comment%%` | `Comment` — MyST `%` is the exact sibling |
| `==highlight==` | new phrasing node (or a `Role`) |
| `#tag`, `^blockid` | frontmatter tags / `Target` |
| properties frontmatter | `FrontMatter` (already modeled) |
*(Syntax verified against current Obsidian docs, 2026-08-05.)*
**What building it settled.** The dialect's declaration is `('gfm', 'footnote', 'front_matters', 'obsidian')` — **Obsidian is GFM, plus footnotes, plus YAML properties, plus one plugin.** Everything the table above does not mention (tables, task lists, strikethrough, autolinks, front matter) arrives from the stack beneath, and only the five constructs Obsidian actually invents needed new code. That is P10's cost-band claim surviving contact with a real format, and it is why this is the *proof* of the dialect layer rather than merely its first customer.
Two model decisions worth carrying to later dialects:
- **A wikilink is a `Link` with a spelling flag, not a node kind.** `Link.wiki` is additive and defaults false, so every writer without wikilink syntax stays literally unmodified while `obsidian → obsidian` round-trips exactly. Minting a `WikiLink` node would have forced an entry into all five capability tables to say the same thing. Resolution defines collision behavior explicitly: an unqualified `[[Note]]` falls back to unique-basename lookup after exact path/id lookup fails, exactly one match resolves `NATIVE`, and multiple matches leave the spelling untouched with an `AMBIGUOUS` resolution entry naming every candidate in stable document-identity order -- reported, never guessed.
- **`==highlight==` and `![[embed]]` are genuinely new kinds**, because nothing in the tree meant either. They cost one node class and one bridge registration each — the honest price of a construct the model did not have, and the counterexample that keeps the first decision from being a rule.
Deferred, and recorded rather than silently missing: inline `%%comments%%` mid-paragraph, `#tag` inline tags, and `^blockid` identifiers. Dollar math is implemented rather than deferred: the Obsidian reader installs the `dollarmath` tokenizer before highlight parsing, and dedicated typed renderers emit inline and display math as `$...$` and `$$...$$`. Those math nodes remain report-bearing `APPROX` because MyST equation-label semantics are not promised identical in Obsidian.
**Why first — on value, not on cheapness.** An earlier draft argued Obsidian was the cheapest new format. Checking the shelf disproved that: callouts have `mdit_py_plugins.admon` behind them, but **wikilinks, `==highlight==`, `%%comment%%`, and `![[embed]]` have no plugin at all**, so P7 needs a real `markdown-it` extension module. Pandoc-flavored is the cheaper first dialect (§3.11). P7 still goes first, for the reason that actually holds:
**MyST and Obsidian are the closest pair in the entire matrix.** Callouts are admonitions. `%%...%%` is `%`. Wikilinks are cross-references. Properties are frontmatter. `myst ↔ obsidian` should be *near-lossless in both directions*, which makes it an excellent Tier-1 correctness cell and by far the strongest early demonstration of the thesis — and, because its headline construct is the wikilink, the first real exercise of P4's `resolve()` pass. Cheapest and most valuable are different questions, and this spec answers the second one.
### `3.8.` P8 — Wikitext (MediaWiki), bounded
**The honest assessment.** MediaWiki has no formal grammar; its specification is the behavior of the PHP parser. This is the hardest format in the set by a wide margin, and the proposal is scoped accordingly.
**What `mwparserfromhell` gives us** (verified 2026-08-05): a node tree of `Template`, `Wikilink`, `ExternalLink`, `Heading`, `Tag` (including style `''`/`'''` and wikitables), `Comment`, `HTMLEntity`, `Argument`, `Text` — and lossless round-trip by construction, since `str(wikicode)` reconstructs the source exactly. That round-trip property is a very good fit for `SyntaxRaw` + `MappingGrade`, which already exists for the Typst CST.
**What it does not give us, and this is the load-bearing finding:** it is a *wikicode* parser, not a document-structure parser — its tree is **flat and marker-oriented, never containment-oriented**. Verified empirically against `mwparserfromhell 0.7.2` (2026-08-05):
- A list marker `*` is a `Tag` node, and the item's text is a **sibling** `Text` node, not a child of it. There is no `List`, no `ListItem`, and no nesting depth anywhere in the tree.
- An entire wikitable arrives as **one opaque `Tag`** holding its raw source — no rows, no cells, no header structure.
- Paragraph breaks and indentation are undifferentiated `Text`.
- Round-trip *is* lossless (`str(parse(src)) == src`), which is the property that makes `SyntaxRaw` + `MappingGrade` a good fit.
So the reader is necessarily **three-layer**: `mwparserfromhell` for the template/link/style surface, a line-oriented block pass to assemble flat markers into real `List`/`ListItem`/`DefinitionList` containment, and a dedicated sub-parser for the opaque table blobs. Any plan that assumes one library does the job is wrong, and would discover it late — after committing to a wave.
**Templates: promise nothing.** A template is a function call into a wiki-specific library; expanding it correctly requires *that wiki*. Spec position:
- Default: every `Template` becomes an **enclave** (P2) — preserved verbatim, marked, linked.
- Optional: a configurable known-template map for the common shapes (`{{cite ...}}` → citation, `{{quote}}` → blockquote, infobox → table).
- **Never**: general template expansion.
This is the cleanest justification for P2 existing at all, and the reason P2 must land before P8.
**Write before read.** Emitting wikitext is a plain writer and is *far* easier than parsing it. If an early win is wanted, `* → wikitext` is available well before `wikitext → *`.
### `3.9.` P9 — The HTML reader, scoped
**Proposal.** Read *semantic* HTML: our own P1 output, and reasonably structured documents (`article`, `section`, `h1`–`h6`, `p`, `ul`/`ol`, `table`, `pre`, `figure`, `blockquote`).
**Explicitly out of scope: the open web.** Arbitrary page HTML is tag soup, and "read HTML" silently becomes "decide what is content and what is chrome" — a readability/extraction problem, not a translation problem, and a different tool. Saying so in the spec prevents an unbounded subproject from growing inside a bounded one.
**Target property:** `read_html(write_html(doc)) ≈ doc` — round-trip against our own output, tested as a bridge property in the style of the existing token-bridge tests.
### `3.10.` P10 — The Markdown dialect layer *(O3, first half)*
**A finding first, because it changes the proposal.** The dialect layer is not something to build. It is something already built twice and never named:
| Module | Lines | Content |
| :---------------------- | ----: | :-------------------------------- |
| `myform/readers/myst.py` | **30** | a `name` and an `extensions` tuple |
| `myform/readers/gfm.py` | **29** | a `name` and an `extensions` tuple |
| `myform/writers/myst.py` | **36** | the above plus a capability table |
| `myform/writers/gfm.py` | **49** | the above plus a capability table |
| `myform/readers/typst.py` | *964* | *(an engine, for contrast)* |
MyST and GFM are not two engines. **They are two dialects of one engine**, declared over the shared `build_engine`, and the cost of a third is a tuple and a table. That is why O3's "add those where we can do so easily" is a real offer rather than an aspiration — and it also tells us exactly where "easily" stops.
**The gate, stated as one question:** *does the flavor's syntax already have a `markdown-it` / mdformat plugin behind it?* Verified 2026-08-05, the installed shelf is `deflist`, `footnote`, `front_matters`, `gfm`, `myst`, `tables`; `mdit_py_plugins` additionally ships `attrs`, `container`, `colon_fence`, `admon`, `dollarmath`, `texmath`, `field_list`, `tasklists`, and `subscript` — parsers with no mdformat renderer attached to them yet.
Three cost bands follow, and every entry in §3.11 is classified by them:
| Band | Cost | Meaning |
| :---------- | :------------------------------------ | :----------------------------------------------- |
| **Declaration** | a tuple and a table, ~50 lines | every construct already has a plugin |
| **Extension** | + one `markdown-it` plugin module | some constructs need a parse rule and a renderer |
| **Engine** | a reader/writer module pair, ~700–950 | not markdown at all; a different grammar |
**Proposal.** Promote the pattern to an explicit, documented seam: a `Dialect` declaration carrying its extension tuple, its capability table, and the one knob the current shape lacks — **writer-side spelling choice**. When two dialects can both express a construct but spell it differently (an admonition as a MyST directive, a GFM alert, or a Pandoc fenced div), that choice belongs in the declaration, not in a branch inside a writer. Dialects register through the existing `Reader.register` / `Writer.register` decorators, so nothing downstream changes — with one consequence that does need handling, in §4.
### `3.11.` P11 — LaTeX *(O3)*
**Write: a peer of the Typst writer, and a to-PDF terminal in its own right.** `writers/typst.py` is 942 lines and is exactly the shape to copy — a direct backend, lazy-loaded through `_DIRECT_WRITERS`, with its own capability table. Per §2.1 this is a *document*-scale to-PDF terminus; Sphinx's LaTeX builder remains the *collection*-scale one, and the two do not compete any more than P1 competes with Sphinx's HTML builder.
**Read: bounded, for a reason this spec has now met twice before.** `\newcommand` makes LaTeX macro expansion Turing-complete and package-dependent: reading arbitrary LaTeX correctly requires *that document's* preamble and packages, exactly as reading wikitext correctly requires *that wiki* (§P8). The position is therefore identical — parse the document-structure surface (sectioning, environments, `\emph`, math, tables, `\label`/`\ref`), map a curated set of known commands, and make **every unrecognized macro or environment an enclave** (P2). Never expand.
Candidate parser: `pylatexenc`, which walks macros without expanding them. **To be verified before P11 is scheduled**, not assumed here.
### `3.12.` P12 — docx *(O3)*
**Write: mechanical, and the frontier format with the largest audience outside this fleet.** A `.docx` is a zip holding `document.xml` and a handful of sibling parts. Emitting the subset myform's tree needs — headings, paragraphs, runs with bold/italic/code, lists via `numbering.xml`, tables, images, hyperlinks — is bounded XML generation with no parsing in it. `python-docx` behind a `myform[docx]` extra removes the package plumbing entirely; a hand-rolled minimal package avoids the dependency. **Recommend the extra**, consistent with §5.1.
**Read: honestly lossy, and the reason is structural rather than incidental.** Word's model is *presentational*. A heading is a paragraph wearing a style named `Heading 1`, and nothing stops a document from styling its headings by hand instead — in which case the semantic heading is simply not present in the file. Recovery is therefore heuristic, which is precisely the thing `MappingGrade` exists to record: a styled document reads `EXACT`, a hand-formatted one reads `RESONANT` or worse, and the grade is *reported* rather than hidden.
This makes docx the first format whose read grade will routinely vary **within a single document**, which is a genuinely useful stress test of §P3's `min(read grade, write capability)` definition — the one place where a naive per-format average would lie.
### `3.13.` Frontier tier — what is left after O3
O3 promoted LaTeX (P11) and docx (P12) out of this tier. What remains: **AsciiDoc, Org-mode, man, Textile, Djot, EPUB.** The architecture must not preclude them; this increment must not wait for them.
**Recommended cut, unchanged: EPUB write only**, and only because it is nearly free once P1 exists (HTML in a zip with a manifest) — and note that EPUB is a *to-HTML* terminal, so it inherits P1 rather than P11. Of the rest, **Djot is the most tractable engine-tier candidate** by a wide margin, being the one with a clean formal grammar rather than a reference implementation standing in for a specification. Everything else is deferred with no work now.
### `3.14.` The specialized-work ledger *(O3, second half)*
O3 asked to "record the outstanding cases that might require specialized work." This is that record: every format considered, its band (§P10), and the specific thing that makes it cheap or expensive.
| Format | Band | What decides it | Position |
| :-------------------------- | :---------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------- |
| CommonMark (strict) | Declaration | the GFM stack minus its extensions | free; take it with P10 |
| **Pandoc-flavored** | Extension | `deflist`/`footnote`/`tables` shipped; fenced divs and bracketed spans have mdit parsers (`container`, `attrs`) but no mdformat renderers; line blocks, grid tables, and `[@cite]` need rules | **the cheapest proof of P10** |
| Quarto (`.qmd`) | Extension | Pandoc-flavored + YAML header + executable cells (`Code` with attributes) + `{{< shortcodes >}}` | near-free once Pandoc lands |
| **Obsidian** | Extension | callouts have `admon`; wikilinks, `==highlight==`, `%%comment%%`, `![[embed]]` have nothing | P7, wave 1 |
| kramdown / MultiMarkdown | Extension | `{: .class}` inline attribute lists are `attrs`-adjacent | opportunistic |
| **Quarkdown** | Extension (write) | a **CommonMark/GFM superset**, so valid GFM output is *already* valid Quarkdown — the writer is a spelling choice plus optional `.doctype`/layout emission | write is nearly free |
| Quarkdown | *bounded* (read) | Kotlin/JVM reference implementation, no Python parser, no independent spec, and `.function` calls make it **Turing-complete** | GFM read + a function-call surface rule |
| Djot | Engine | a clean formal grammar — the tractable one | frontier |
| **LaTeX** | Engine | write is a Typst-writer peer; read needs macro expansion (Turing-complete) | P11 |
| **docx** | Engine | write is mechanical XML; read is style-heuristic and grade-varying | P12 |
| wikitext | Engine | no grammar at all; the parser's tree is flat, so a three-layer reader | P8, Tier 3 |
| AsciiDoc, Org, man, Textile | Engine | own grammars, no compelling Python parser story yet | frontier, no work now |
| *arbitrary web HTML* | — | not a translation problem at all | **refused** (§P9) |
**The pattern worth naming, because it is the same problem three times.** Quarkdown's `.function` calls, MediaWiki's `{{templates}}`, and LaTeX's `\macros` are each a Turing-complete expansion layer living inside a document format. Evaluating any of them correctly requires the *host environment* — that wiki, that preamble, that standard library — which a translator does not have and should not pretend to.
myform's answer is identical in all three cases, and it is P2:
1. **Never expand.** Not as a limitation — as a correctness position.
2. **Always enclave.** The construct is preserved verbatim, marked, and linked (§P2).
3. **Optionally map a curated set** of known shapes, per format, by configuration.
P2 was justified once, by wikitext. O3 has now justified it three times over, in formats that share nothing else. That is the strongest evidence available that `ENCLAVE` is a primitive rather than a workaround — and it is why P2 leading wave 1 was the right call independent of everything else in this document.
## `4.` The graph past N=7: unequal investment, and an audit that must not grow quadratically
§1.1's arithmetic — "four formats to seven takes 16 cells to 49" — had the right shape and the wrong horizon. §3.11 puts **nine engines** plausibly in scope, and P10's dialects register through the very same `Reader.register` / `Writer.register` decorators, so each one lands in `test_matrix_sweep.py` at collection time too. Thirteen registered formats is **169 cells**.
So the self-growing audit praised in §1.1 is also the thing most likely to become the slowest test in this repo, and the tier policy alone does not fix that: Tier 3 imposes no floor, but it still *runs* every cell it covers. Two rules, both cheap, both wanted **before P10 registers its first dialect** — retrofitting them after the sweep is slow is strictly worse:
- **A dialect shares its base format's audit.** A `Dialect` declares its base engine; the sweep runs its full cross product over *engines*, and for dialects only the cells touching that dialect's own distinguishing constructs. Pandoc-flavored need not be swept against Typst — GFM already was, and the delta is an extension tuple.
- **Tier 3 is sampled, not exhaustive.** Fixed-seed sampling across Tier-3 cells per run, with the exhaustive sweep behind a marker for CI and releases. `test_matrix_sweep.py` calls itself *"an audit, not a correctness suite"*; an audit is allowed to sample, and saying so keeps that docstring honest at N=13 rather than quietly abandoning it.
Three tiers, updated for O3:
| Tier | Cells | Gate |
| :----------------------------------- | :--------------------------------------------------------------------------------------------- | :----------------------------------------------------------------- |
| **1 — daily drivers** (full correctness) | `myst↔myst`, `gfm↔gfm`, `myst↔gfm`, `myst→html`, `obsidian↔myst` | byte-level expectations; existing corpus suites in full force |
| **2 — supported** (coverage floor) | everything touching rst, typst, html-read, `obsidian↔gfm`, pandoc cells, latex-write, docx-write | audit-green **and** ≥95% content-weighted nativeness (P3) |
| **3 — audited** (no floor, sampled) | all wikitext cells, quarkdown, latex-read, docx-read, exotic pairs (`typst→wikitext`) | must not crash; must report honestly; coverage measured, not gated |
A bold spec is credible when it says where it is *not* spending. This is that.
## `5.` Constraints and tripwires
### `5.1.` The import budget is a hard shape constraint, not a packaging detail
`myform-spec.md` §10 gates `import myform` at baseline + 20%, with a 0.216 s cold-start warn and a 0.432 s hard ceiling. Sphinx, myst-parser, furo, mwparserfromhell, `python-docx`, `pylatexenc`, and any HTML parser as *dependencies* would obliterate that outright.
Therefore: every proposal here ships as an **optional extra with function-local imports**, following the `_DIRECT_WRITERS` lazy-loading precedent already in `myform/writers/__init__.py`. This constrains the *design* of P5 (no import-time theme registry, no eager Collection scan), not merely its `pyproject.toml` entry.
O3 sharpens this from a rule into a **selection criterion**. §3.11's Declaration band costs nothing at import time — a tuple and a table — while every Engine-band format brings a parser. That asymmetry is not incidental: it is why "add the flavors where we can do so easily" and "keep the import budget" turn out to be the *same* constraint read from two directions, and why P10 can absorb an arbitrary number of dialects without moving the number at all.
### `5.2.` The published v1.0.0 extension surface
`Capability` (P2) and the writer/reader registries are public as of v1.0.0. P2's enum addition wants a documented fallback and a minor-version bump — see D2.
### `5.3.` README canonicality
`test_cli__readme` asserts the repository README formats to itself. Any change to the format table must survive myform's own formatter — including sequential ordered-list numbering under `--number`.
### `5.4.` Corpus couplings to cut, and who keeps the policy
`mdsite.py` imports `corpus.ai_home` (hardcoding `~/ai/site`, `~/ai/scratch`), `corpus.cli_common`, and `corpus.typst` (which shells the `typst` binary).
Its fourth import, `my.regex`, is *not* a coupling to cut — `my-basis>=1.0.0` is already a myform dependency per `myform-spec.md` §2.5, so `RegexStore` comes along in-family. **A PyPI library cannot hardcode `~/ai`** — those are fleet-instance policy.
**Recommendation (N-02, policy/mechanism):** myform owns the *mechanism* — Collection, HTML, staging, search, serve — parameterized by config and CLI. Corpus keeps `mdsite` and `myst-report` as **thin wrappers** that supply the `~/ai` roots, the Diátaxis `journal/reports` default, and the port. The commands keep working, the fleet policy stays where fleet policy belongs, and `~/my/corpus/skills/myst-reporting/SKILL.md` needs only a light edit rather than a rewrite. Typst rendering becomes feature-detected and optional.
### `5.5.` Three tripwires P7 hit, which every later dialect will hit too
All three were found by building or by hostile review, not by reasoning, and each is cheap once known and expensive once forgotten — which is what earns them a place here rather than in a commit message.
**A capability table says one thing per node *kind*; some distinctions are per *node*.** This is the tripwire that produced P7's only critical defect, and it will recur for every dialect that partially supports a construct. Obsidian has callouts, not directives — so `mystDirective` is `NATIVE` for `> [!tip] Title` and *not* for `{code-block}` or for an admonition carrying `:name:`. A table cannot say that, and the writer's `NATIVE` default meant it claimed all of them: unparsed bodies and unrendered options were deleted with an **empty report**, the exact failure this package exists to prevent.
Worse, the existing audit could not catch it. `test_matrix_sweep.py`'s AC4 check verifies that every *non-`NATIVE`* node produced a report entry — so a wrong `NATIVE` claim is unfalsifiable by construction, and the sweep's MyST sample happened to contain only the admonition shape that works. **A per-node distinction belongs in a `register_preflight` rule, never in the table**, and a partially-supported construct is a signal to reach for one.
**A built-in dialect self-registers only when its myform reader loads.** `mdformat.plugins` eagerly imports every published `mdformat.parser_extension` and checks renderer conflicts across the installed set, even when a user did not enable that flavor. Obsidian deliberately has no distribution entry point: `ObsidianReader` imports `register_obsidian_extension()` after the dialect module exists, and that idempotent helper adds the name without displacing a real third-party plugin. If a different plugin already owns `obsidian`, myform leaves it registered and refuses its built-in reader with a diagnosed conflict; it never combines foreign parser semantics with the built-in writer contract. The myform API can therefore enable Obsidian without process-wide conflict warnings, while bare `mdformat --extensions obsidian` remains explicitly outside the contract.
**The corpus format-on-edit hook is not dialect-aware.** It runs the *MyST* formatter over every `.md` written through an agent's edit tools, and MyST correctly escapes `[[` — so an Obsidian test fixture silently becomes `\[[Note]\]` on write. Dialect fixtures must be written through a path the hook does not cover. This is a corpus-policy question rather than a myform defect (myform's escaping is right), but P10 turns it from an annoyance into a recurring tax: every Extension-band flavor in §3.11 ships fixtures in a syntax the default formatter will fight.
## `6.` Sizing, so the decisions below are answerable
Rough orders of magnitude, bracketed by what comparable modules already cost in this repo (`writers/rst.py` is 682 lines, `writers/typst.py` is 942, `readers/rst.py` is 792).
| Proposal | Shape | New dependency | v1.0.0 surface |
| :---------------- | :------------------------------------------------------------------------------------------------------------ | :--------------------------------------------- | :-------------------------- |
| **P1** HTML writer | 1 module, ~700–900 lines + capability table + tests | none (stdlib) | additive |
| **P2** `ENCLAVE` | enum + `degrade/engine.py` + `lowerings.py` + 1 emitter per target | none | **breaking-ish** — the only one |
| **P3** coverage | 1 module + 1 CLI verb + a pinned corpus | none | additive |
| **P4** `Collection` | 1 new model module + a `resolve()` pass beside `lower()` — **smaller under D4** (no nav, no search); no writer edited | none | additive |
| **P5** `myform site` | staging + `conf.py`/toctree generation + server; **no theme, no search index, no JS** — Sphinx and furo own those | `sphinx`, `myst-parser`, `furo` (`myform[site]` extra) | additive |
| **P6** `site new` | folded into P5 | none | — |
| **P7** Obsidian | 1 dialect pair + **1 `markdown-it` plugin** (wikilinks, `==`, `%%`, embeds) — Extension band, not free | none | additive |
| **P8** wikitext | 1 three-layer reader + 1 writer | `mwparserfromhell` (extra) | additive |
| **P9** HTML reader | 1 module | possibly none (stdlib `html.parser`) | additive |
| **P10** dialect layer | naming an existing pattern + a spelling knob + the §4 sweep rules; then ~50 lines per Declaration-band flavor | none | additive |
| **P11** LaTeX | 1 writer (a `writers/typst.py` peer); reader bounded, macros → enclaves | `pylatexenc` for read (extra, unverified) | additive |
| **P12** docx | 1 writer (OOXML package); reader style-heuristic, grade-varying | `python-docx` (`myform[docx]` extra) | additive |
Three things worth reading off that table.
**Every new dependency lives in an extra** — `myform[site]`, `myform[wikitext]`, `myform[docx]`, `myform[latex]` — and `import myform` never touches any of them, so §5.1's budget holds by construction rather than by vigilance. (`sphinx`, `myst-parser`, and `furo` are *already* dev dependencies of this repo's docs group, so P5's extra promotes existing pins rather than adding new ones.)
**P2 remains the only proposal that touches the published surface**, which is why D2 was the one decision that could not be deferred past the first wave — and O3 has since justified it twice more (§3.11).
And **the largest single item in the table is now P1**, which is the other half of why O2 moved it out of wave 1: wave 1 is three mechanisms, none of them a rendering slog.
## `7.` Decisions taken; nothing left open
### `7.1.` Answered by the operator, 2026-08-05
| ID | Decision |
| :-- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **D1** | Wave 1 = **P1 + P2 + P4**, with **P7** (Obsidian) immediately after as proof a new format is now cheap. — ***amended by O2***, below: P1 and P7 trade places, so wave 1 = **P2 + P4 + P7** and wave 2 opens with **P1 + P3**. D1's intent is preserved: P7 still lands immediately after P4. |
| **D2** | `ENCLAVE` ships **additive in v1.1**, with a documented `CARRY` fallback for writers unaware of it. |
| **D3** | **MathML by default**, delimiter-preserved spans configurable. |
| **D4** | **Sphinx stays.** myform translates *into* Sphinx; it does not replace it. §P5 rewritten; §P4 narrowed; §P2's table reordered. |
| **D5** | Corpus `mdsite`/`myst-report` survive as **thin wrappers** holding the `~/ai` policy (§5.4). |
| **D6** | Wikitext takes the **easy win first** — the `* → wikitext` writer ships ahead of the three-layer reader. |
### `7.2.` Follow-ups, answered 2026-08-05
| ID | Resolution |
| :-- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **O1** | **to-PDF and to-HTML are separate goals** that only sometimes coincidentally share infrastructure. Recorded as §2.1 and applied to §P5, §P11, and §5.4. Neither terminal is a fallback for the other, and a linked artifact is a link — never an `ENCLAVE`. |
| **O2** | Delegated to the author. **P1 and P7 trade wave slots** (§P1). Wave 1 = **P2 + P4 + P7**; wave 2 opens **P1 + P3**, keeping P1 adjacent to its only dependent instead of idle for a wave. D1 amended above. |
| **O3** | **LaTeX (P11) and docx (P12) promoted** out of the frontier tier. Markdown flavors handled by **P10**, whose three cost bands make "where we can do so easily" a decidable question. §3.11 is the requested record of what needs specialized work, and why. |
**O1 is the one that changed a design rather than a schedule.** The original §P5 proposed a per-document toggle between a translated page and a compiled artifact — which quietly assumed the two were alternatives competing on fidelity. They are not. Once they are named as separate goals, the toggle disappears: `myform site` builds a site, the PDF path builds PDFs, and a collection may be handed to both. A question that looked like it needed a feature turned out to need a distinction.
**O3's second half produced the most reusable finding in this revision.** Asked to record what requires specialized work, §3.11 found that Quarkdown's `.function` calls, MediaWiki's `{{templates}}`, and LaTeX's `\macros` are *the same problem three times*: a Turing-complete expansion layer inside a document format, evaluable only with the host environment a translator does not have. The answer is the same in all three — never expand, always enclave, optionally map known shapes. P2 was justified once by wikitext; it is now justified three times over by formats that share nothing else with it.
### `7.3.` Nothing is open
D4 remains the consequential decision, and it improved the design.
The argument that had motivated going native — Sphinx cannot read Typst, Obsidian, or wikitext — is answered *better* by translation than by replacement: myform converts them to MyST and Sphinx does what it has always done.
Nothing is reimplemented, and the input set grows from two formats to seven, with §3.11's ledger showing where the next six would come from and what each would cost.
## `8.` Problem space
- **The universal graph** — *D1–D6 and O1–O3 all answered; nothing open; wave 1 in motion* ⭐
- **Wave 1 (D1, as amended by O2)** — *P2 + P4 + P7, in that dependency order* ⭐
- **P2 `ENCLAVE` capability** — *the "safely demarcated and linked" primitive; v1.1 additive per D2; blocks P8, P11, and Quarkdown-read* ✅
- **P4.1 `Collection`** — *landed: `myform/collect`, a `resolve()` pass beside `lower()`; no writer edited, identity without a collection, dangling refs in their own report* ✅
- **P4.2 the label index** — *`{ref}`/`{numref}`/`{eq}` take a **label**, not a path, so P4.1 deliberately resolves `{doc}` only; labels need a second index and a second resolution kind* 💤
- **P7 Obsidian dialect** — *promoted into wave 1 by O2; near-lossless against MyST; the first real exercise of `resolve()`* ⏳
- **Wave 2** — *the measurement pair, kept together* 💤
- **P1 HTML writer** — *moved here by O2; P3's ceiling and a standalone convert target; the to-HTML terminal at document scale (§2.1)* 💤
- **P3 coverage instrument** — *needs P1's ceiling; makes the 95% claim defensible or kills it; docx-read will stress its `min(read, write)` rule* 💤
- **P10 dialect layer** — *names a pattern already built twice; three cost bands decide O3's "easily"; needs §4's sweep rules first* 💤
- **Pandoc-flavored** — *the cheapest proof of P10; most constructs already have plugins* 💤
- **Quarto, kramdown, MultiMarkdown, CommonMark-strict** — *near-free once Pandoc lands* 💤
- **Quarkdown** — *write is nearly free (a GFM superset); read is GFM + a function-call rule, unknown functions enclaved* 💤
- **P5 `myform site`** — *Sphinx-backed per D4; needs P4; deletes the massage stage; O1 removed its one design question* 💤
- **P9 HTML reader** — *scoped to semantic HTML; open-web extraction explicitly refused* 💤
- **P11 LaTeX / P12 docx** — *promoted by O3; both write-first, both read-bounded for structural reasons* ⛔
- **P8 wikitext** — *writer first per D6; three-layer reader later; templates as enclaves; Tier 3* ⛔
- **Frontier (AsciiDoc, Org, man, Textile, Djot, EPUB)** — *architecture stays open; no work now. EPUB write inherits P1; Djot is the tractable engine* ⛔
- **Repo metadata drift** — *README, `pyproject.toml` description, and the stale predecessor-spec pointer refreshed to v1.0.0 facts; `myform-spec.md`'s launch matrix marked superseded* ✅
- **`plane` dialect — out-of-band, not a numbered P-proposal** — *operator-directed: a direct writer+reader pair for Plane's Tiptap `description_html`, anti-idiomatic by design (exact vendor wire-shape, not semantic HTML) and architecturally closer to rst/typst's direct model walk than to P10's shared-markdown-engine dialects. Landed both steps (typed `ListItem.checked` primitive; the writer/reader themselves, registered in the standard matrix sweep). Deliberately not folded into P1/P9's semantic-HTML scope, and not this document's concern to migrate -- corpus's own call-site swap is a separate follow-up card* ✅
**Keystone: P7.** With P2 and P4 both landed, wave 1's remaining item is the Obsidian dialect — and it is now the load-bearing one, since it is what first exercises `resolve()` against a real format's own reference syntax rather than against MyST's. Its `markdown-it` plugin (wikilinks, `==highlight==`, `%%comment%%`, `![[embed]]`) is also the concrete template every later Extension-band flavor in §3.11 copies, so what P7 costs once, Pandoc and Quarto do not pay again.