# Advanced Markdown & XML {#root}

@status:doc/work @audience:user,author

[p01] This page takes a package you write in Markdown and shows you the XML your agent reads in its place. On the way you learn why VibeVM turns every text into XML, and why that costs an author nothing. You learn how to name a rule so that a machine can point at it. And you learn to split a long text into a short header and a long body, the way C and C++ programmers split a library. You build one small package by hand, watch it compile, and prove that the two forms are one. Plan for about half an hour, with no agent required.

## What you need {#what-you-need}

[p02]
| What | Why | Where to get it |
| --- | --- | --- |
| `vibe` | creates the project, converts the text and compiles it | [Install vibe](../start/install-vibe.xml) |
| a text editor | you write three short files by hand | any |
| about thirty minutes | the whole walk, with no agent in it |  |

## Why everything becomes XML {#why-xml}

[p03] In a project that asks for it, every text a package brings lands on disk as XML, whatever its author wrote it in. The texts are [specifications](../glossary/index.xml#specification), the rules a package states for the agents that work under it, and the reason for the conversion is the reader. A model that reads `<TESTS-FIRST fact="true" status="spec/done">` knows where the rule starts, where it ends, what it is called and what state it is in, without guessing at layout. Prose asks the model to infer all four.

[p04] The measurement agrees with the instinct. In a benchmark study, Yuan Sui and colleagues gave GPT-3.5 and GPT-4 the same tables written six ways, from plain text with separators to CSV, JSON, XML, HTML and Markdown ([WSDM 2024](https://arxiv.org/abs/2305.13062)). Across seven kinds of question, markup that names its parts beat the same content in prose. HTML did best overall, by 6.76 percent in the paper's own figure. On one task, telling where a table's parts begin and end, XML scored 96.00 percent against 93.00 for plain text and 92.33 for Markdown. The study is about tables, and that is exactly as far as this page takes it.

[p05] Vendor advice says the same from the other side. Anthropic's prompting guide for Claude, guidance rather than research, tells you to wrap each kind of content in its own XML tag so that the model parses a long prompt without ambiguity ([the guide](https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/claude-prompting-best-practices)). Oleg Chirukhin, the author of VibeVM, found this in practice before any published work said it. The `xml` target is the form the projects of this manual use, and the one the specification names as the future primary form.

> [p06] **`xml` target:** every source — including Markdown —
> emits as dialect XML through the pivot. Mixed input translating into
> CLEAN XML (never mixed output) is this target's acceptance bar, because
> it is the future primary.
>
> <spec://org.vibevm.core/vibevm/common/PROP-045#TARGET-XML>

[p07] The names matter as much as the brackets. A section becomes an element named after itself, `<tests-first title="Tests before fixes">`. A rule becomes an element named after its id with one marker attribute, `fact="true"`, so that a reader who knows nothing of your vocabulary finds every rule by one test. The first reader of this dialect is an agent, and a tag that says what it holds needs no legend.

> [p08] **Decision (ADR-part; owner ruling 2026-08-22,
> verbatim): «Гораздо логичней `<three-bands title=\"…\">`. … Вся суть XML
> нотации в том, что у тебя названия тэгов несут названия сущностей, это
> упрощает работу нейросети».** A section serialises with its ANCHOR as the
> element name — `<three-bands title="1. The three bands">` — because the
> dialect's first reader is an agent, and a tag that names its entity is
> self-describing where an endless `<section>` river is not. The generic
> form `<section id="…" title="…">` remains in the dialect as the REQUIRED
> fallback for the two cases XML itself forbids or the grammar reserves: an
> anchor that is not a valid XML name (leading digit) and an anchor
> colliding with the structural vocabulary (`spec`,`title`,`status`,
> `section`,`p`,`fact`,`list`,`item`,`table`,`tr`,`td`,`fence`,`quote») —
> measured over the live corpus, that tail is 2 anchors of 1393; the
> emitter writes the named form everywhere else, the readers accept both.
> The converter recipe bumps (`specdoc/1` → `specdoc/2`), so every
> transformed slot re-materialises by the derived-manifest law rather than
> lingering in the old shape. The owner's next call arrived the same
> day — facts follow, see `##NAMED-FACT-ELEMENTS`. Landed: the emitter
> writes named sections everywhere the predicate allows (the live redbook
> README golden carries 7 named / 0 generic), both readers accept both
> forms, and the engine mirrors the predicate verbatim across the
> separability seam.
>
> <spec://org.vibevm.core/vibevm/common/PROP-045#NAMED-SECTION-ELEMENTS>

> [p09] **Decision (ADR-part; owner ruling 2026-08-22,
> verbatim): «сконвертируй и факты тоже. Предлагаю такой формат
> `<fact-name fact="true" ...>`. Таким образом кастомный XML-парсер всегда
> может найти соответствующие элементы».** A fact serialises with its ID
> as the element name, carrying the DISCRIMINATOR attribute —
> `<THE-LAW fact="true" status="impl/done">body</THE-LAW>` — so a reader
> that knows nothing of the vocabulary still finds every fact by one
> attribute test. The recognition law: an element IS a fact iff its name
> is `fact` (the generic form, which stays in the dialect) or it carries
> `fact="true"`. The named form is emitted whenever the id passes the
> same elementability predicate sections use (fact-id grammar already
> forbids leading digits, so the fallback tail is vocabulary collisions
> only); the typed-fact fence binding stays by id and does not change.
> The owner's second clause binds the scanners: the progress machinery
> must work when a fact's SOURCE — not a materialised copy — is authored
> XML; the host lane holds by construction (XML sources enter progress
> through the canonical MD projection) and is PINNED by explicit tests
> (an observed .xml source scans unit-for-unit equal to its MD twin),
> while the specmap engine's native reader learns the named form
> mirror-wise. The converter recipe bumps again (`specdoc/2` →
> `specdoc/3`); the host re-materialises once, after both shapes land.
> The owner's third clause (2026-08-22, same sitting) binds the boot
> lanes: «статические и динамические лоадеры должны хорошо работать с
> новым синтаксисом фактов» — pinned at the transition's landing by (a)
> the static-splice determinism test running over a NAMED-shape snippet
> whose projected facts survive into STATIC, (b) the vibe-spec
> normal-closure byte-equality test running over BOTH serialisations
> (generic and named) of one dependency, and (c) the polygon re-run at
> specdoc/3, whose control package auto-adopts the named shape through
> to_xml — INDEX targets, STATIC splice and every machine loader then
> exercise the final syntax end-to-end; the agent half of the dynamic
> router is §5a's measurement, deliberately run AFTER this transition so
> it measures the shape that ships. Landed: the recipe is `specdoc/3`,
> the host's 37 slots re-materialised once with named facts live (the
> redbook README golden pins 45), the recognition law holds in both
> readers with `fact="false"` a loud error, progress holds full
> ParsedDoc parity between an XML source and its hand-pinned MD twin
> across two scans, and pins (a)–(c) are in the tree — the splice
> snippet ships `<BOOT-RULE fact="true">`, the normal closure compiles
> three lanes byte-equal, the polygon re-ran 3/3. A live lesson worth
> its line: XML reserves every case-insensitive `xml`-prefixed name, so
> `XMLBOOT` cannot be an element — the predicate refuses it and the
> generic form carries such ids.
>
> <spec://org.vibevm.core/vibevm/common/PROP-045#NAMED-FACT-ELEMENTS>

[p10] One caution for the readers who train models. The evidence above is about text a model reads. For text a model writes, it runs the other way: forcing an answer into JSON, XML or YAML can cost reasoning quality ([Tam and colleagues, EMNLP 2024](https://arxiv.org/abs/2408.02442)). VibeVM shapes what an agent reads and never what it answers, so that finding does not touch it.

## Markdown and XML: one model underneath {#one-model}

[p11] Under the hood the two forms are one thing. Every document, Markdown or XML, is parsed into one tree: a title, a status, and sections nested by heading depth. Inside the sections sit paragraphs, lists, tables, fenced code and quotes, some carrying a [fact](../glossary/index.xml#fact), one named rule with a status. vibe never rewrites Markdown text into XML text. It parses into the tree and prints from it, in either direction.

> [p12] **Decision (ADR-part).** There is ONE internal document
> model — the pivot — and every format is a frontend (parse into it) or a
> backend (emit from it). The pivot is the progress-markup semantic tree the
> tree already owns: document → nested sections (the heading hierarchy with
> anchors) → blocks (paragraph / list / table / fence / quote) → facts
> (anchored units with status and body spans), plus the `<status>` document
> element and fragment wrappers. Conversion between formats is always
> parse → pivot → emit; there is no direct MD↔XML text rewriting.
> Alternatives weighed: per-pair converters (N² growth, drift between pairs)
> and a lossless-CST pivot preserving all whitespace (cost without a
> consumer; the degradation law below makes semantic-level fidelity the
> contract). **Resolved by the XML-MEASURE map (2026-08-21): there is no
> single Markdown frontend to widen — FOUR independent families read
> different MD subsets today (progress-core's scanner, the vendored
> specmap engine's mdspec, the boot/tree directive readers, vibe-check's
> point scanners), with real dialect drift already between them (fence
> grammar run-matching vs prefix-toggling). The pivot is therefore a NEW
> shared crate — `vibe-specdoc` — owning the document IR and both
> frontends/backends; host consumers converge on it. The vendored specmap
> engine is engine-workspace territory (sync-engines law): its XML
> frontend is built in the AUTHORED engine workspace and写-throughs as its
> own slice (S4b), never patched in the vendored copy.*
>
> <spec://org.vibevm.core/vibevm/common/PROP-045#PIVOT-MODEL>

[p13] Compiler writers call such a tree an *intermediate representation*, or IR: the one form that every front end parses into and every back end prints from. A compiler for three languages and four processors needs three front ends and four back ends, not twelve translators, and a rule stated once about the tree holds for every language. VibeVM has the same shape with two front ends and two back ends, Markdown and XML on each side.

> [p14] Everything downstream operates on a single **document IR**: a DOM-like tree. Markdown and (future) XML are two frontends parsed into the same tree, so algorithms written against the IR scale to deeply nested XML for free.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-035#DOCUMENT-IR>

[p15] It needs the tree for the same two reasons. Without it, each pair of forms would need its own converter, and converters drift. A survey of the code in 2026 found four readers of Markdown inside vibe, each accepting a slightly different dialect: the disease one shared tree is meant to cure. And every tool that reads a specification reads XML through the same tree: the checker of facts, the compiler that builds an agent's reading list, the router that resolves an address. So an XML document and its Markdown twin give every tool the same answer.

> [p16] **Decision (ADR-part): scanners read XML through its
> canonical Markdown projection — one dispatch layer above the parser, no
> dependency cycle.** `vibe-specdoc` depends on `progress-core` (its MD
> frontend is the adapter), so progress-core cannot itself call specdoc.
> The consumers dispatch instead: a `.xml` spec entering any scanner
> (progress, check, specmap-host, show) is first projected
> `from_xml → to_markdown` — deterministic and canonical by S1's emitter —
> and the projection feeds the existing MD machinery; units, facts,
> anchors, hashes and verdict staleness all work unchanged, and a source
> edit moves the projection exactly when it moves meaning. Alternatives
> weighed: a native XML unit-walker in every scanner (a fifth and sixth
> parser family — the disease the measure named), and inverting the crate
> dependency (progress-core consuming specdoc — a cycle). RECORDED
> DEGRADATION, honest: a diagnostic for an XML source cites
> projection-relative line numbers, and v1 marks such diagnostics with
> the projection notice rather than pretending; native source positions
> are follow-up work riding the specmap-engine slice (S4b).
>
> <spec://org.vibevm.core/vibevm/common/PROP-045#PROJECTION-READ>

[p17] The tree has a future beyond conversion. The compiler names its levels, from one document's text up to the whole reachable closure of a project, and a compiler plugin may read and rewrite them. A long-horizon agent could plan over that closure one day: not a wall of text but a graph of named units with addresses. That is a direction, not a feature you can run today. And VibeVM itself is not an agent. It never reads your rules to act on them; it prepares the text, the tree and the addresses for whichever agent you run.

> [p18] The compiler's IR is **multi-level** (the MLIR shape, because compilation is progressive lowering): **`source`** — one document's raw text plus its spec address; **`document`** — one parsed tree; **`closure`** — the whole reachable compilation unit and graph; **`lane`** — the assembled ordered nodes and provenance; **`emitted`** — serialised bytes per artifact. R3 made these explicit typed carriers and R6.2 froze their epoch-1 wire.
>
> <spec://org.vibevm.core/vibevm/common/PROP-054#IR-LEVELS>

> [p19] **Decision (owner ruling 2026-08-25; implemented through R6.5-D).** A plugin may be a **full compiler extension** — a pass with complete access to the packages' IR, free to change it arbitrarily: new optimisations, new frontends, new backends, the LLVM posture verbatim. Users can build the compiler onward from here. The price of the power is explicitness: the pass tier exists only behind a dedicated manifest flag, only under host activation, only in-process (`builtin`/`native`), and always on the record (§7.4.3). D1 exposes exact selected native backends through `vibe extensions compile`; D2 commissions a genuine installed dependency whose first-class TXT frontend feeds its JSON backend. Evidence: `ee7f6f2d`, `def9909a`, `56307492`.
>
> <spec://org.vibevm.core/vibevm/common/PROP-054#PASS-TIER-LAW>

[p20] The consequence for you is plain. Write the specifications of your own packages in Markdown, the form your editor and your reviewers already handle. Every property of the XML form arrives on its own: named elements, machine-checked facts, an address on every rule. The steps below do exactly that, and end by proving it.

## Step 1: a project that materialises into XML {#create-project}

[p21] 1. Create a project in an empty folder, as on [Create your first project](../start/first-project.xml). The name becomes the folder:

[p22]
```sh
vibe init review-lab
```

```output
Initializing project `review-lab` in `review-lab`
  ✓ created  vibevm/vibespecs/boot/00-core.md
  ✓ created  vibevm/vibespecs/boot/90-user.md
  ✓ created  vibe.toml
  ✓ created  vibe.lock
  ✓ created  .vibe/.gitignore
  ✓ created  .gitignore
  ✓ created  vibevm/vibespecs/boot/INDEX.md
  ✓ created  CLAUDE.md
  ✓ created  AGENTS.md
  ✓ created  GEMINI.md

Done. Project `review-lab`: 10 files created, 0 kept.

Next:
  • edit vibevm/vibespecs/boot/00-core.md and vibevm/vibespecs/common as your project takes shape
  • install packages with `vibe install <kind>:<name>` (e.g. flow:wal)
```

[p23] 2. Open `review-lab/vibe.toml`, the project's [manifest](../glossary/index.xml#manifest), and add one line under `[project]`:

[p24]
```toml
[project]
spec_format = "xml"
name = "review-lab"
```

[p25] 3. Work inside the folder from here on: `cd review-lab`.

[p26] The line decides the form of every text a package brings in. With `xml`, vibe converts what an author wrote in Markdown as it copies the package in, and copies XML as it is. Your own files are never converted. Without the line, each file keeps its author's form.

> [p27] **The setting and its home (revised by the measure —
> reproducibility rules).** The materialisation format is a REPRODUCIBLE
> project property, so its canonical home is the project manifest:
> `vibe.toml [project] spec_format = "mixed" | "markdown" | "xml"`, with
> the effective value recorded in the slot record defined by PROP-054 §9.2
> so two machines materialise identically. The user-config
> family supplies only the operator DEFAULT for projects that do not pin
> one (`[install] spec_format`, beside `slot_integrity`), per the standing
> precedence CLI > env > project > user > built-in; vibe-settings is
> barred from this key by PROP-040's own boundary (app prefs never extend
> vibe.toml). **`mixed` is the built-in default** — for an all-MD world
> byte-for-byte today's behaviour; the flip of the default to XML is the
> owner's word, never silent.
>
> <spec://org.vibevm.core/vibevm/common/PROP-045#SETTING>

> [p28] **`xml` target:** every source — including Markdown —
> emits as dialect XML through the pivot. Mixed input translating into
> CLEAN XML (never mixed output) is this target's acceptance bar, because
> it is the future primary.
>
> <spec://org.vibevm.core/vibevm/common/PROP-045#TARGET-XML>

## Step 2: scaffold the package {#scaffold}

[p29] 1. Add a package to the project. It is a `flow`, a package of working rules for an agent, and it asks for the `normal` format, whose meaning step 4 explains:

[p30]
```sh
vibe init package org.acme/review --kind flow --format normal
```

```output
Creating package `org.acme/review` in `<TMP>/work/review-lab`
  ✓ created  vibevm/vibepacks/org.acme/review/v0.1.0/vibe.toml
  ✓ created  vibevm/vibepacks/org.acme/review/v0.1.0/vibevm/vibespecs/boot/10-flow-review.md
  ✓ created  vibevm/vibepacks/org.acme/review/v0.1.0/README.md
  • kept     vibevm/vibespecs/boot/INDEX.md (regenerated)
  • kept     CLAUDE.md (regenerated)
  • kept     AGENTS.md (regenerated)
  • kept     GEMINI.md (regenerated)

Done. Project `org.acme/review`: 3 files created, 4 kept.

Next:
  • edit vibevm/vibespecs/boot/00-core.md and vibevm/vibespecs/common as your project takes shape
  • install packages with `vibe install <kind>:<name>` (e.g. flow:wal)
```

[p31] 2. Open the manifest the scaffold wrote:

[p32]
```sh
cat vibevm/vibepacks/org.acme/review/v0.1.0/vibe.toml
```

```output
[package]
group = "org.acme"
name = "review"
kind = "flow"
version = "0.1.0"
epoch = 1
authors = ["vibevm docs fixtures"]
license = "UPL-1.0"
description = ""
format = "normal"

[boot_snippet]
source = "vibevm/vibespecs/boot/10-flow-review.md"
category = "flow"
link = "dynamic"
```

[p33] 3. Change two lines: give the package a `description`, and set `link = "static"`. The end of the file then reads:

[p34]
```toml
description = "The team's code review rules, as a contract with its reasons."
format = "normal"

[boot_snippet]
source = "vibevm/vibespecs/boot/10-flow-review.md"
category = "flow"
link = "static"
```

[p35] The [link type](../glossary/index.xml#link-type) says how the package's text reaches the agent. With `static`, vibe compiles the text into the one file an agent reads first, in full, at every session start. With `dynamic`, the scaffold's choice, it lists the file for the agent to open on its own. This page uses `static`, so that you can watch the compiler work.

> [p36] `link = "static"` — the contribution's boot text is compiled into `STATIC.md` ahead of time (whole, anchor-qualified — §2.3). Read first, one read, maximum attention weight. The **emergency priority lane** — for top-level skills and critical disciplines whose priority must be guaranteed by position, not by trusting agent-side resolution. Used sparingly; it duplicates the text on disk.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-009#LINK-STATIC>

> [p37] `link = "dynamic"` — **the default.** `vibe` resolves the contribution to a concrete path in `INDEX.md`; the agent reads it dynamically, on demand. An optional `when` condition gates the read: with a `when` it is a **conditional** INCLUDE (loaded only when the condition holds) — mechanically the subskill `lazy-pull` delivery mode; without one it is read unconditionally. The `when` draws on the subskill `[activation]` probe vocabulary (PROP-003 §2.5) — one probe grammar across both mechanisms. **v1 implements the `os:` probe end-to-end** — `when = "os:windows"` matches the session's operating system (`windows` / `macos` / `linux`); the remaining probes are reserved until PROP-003's activation engine is built.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-009#LINK-DYNAMIC>

## Step 3: anchors and facts, in Markdown {#anchors-and-facts}

[p38] The package needs two documents. The first is the note an agent reads at session start, the package's [boot snippet](../glossary/index.xml#boot-snippet). The second is the contract: the rules themselves, each with an address.

[p39] 1. Replace the scaffold's note, `vibevm/vibepacks/org.acme/review/v0.1.0/vibevm/vibespecs/boot/10-flow-review.md`, with this:

[p40]
```markdown
# Review flow

Before you open a change for review, hold it to the review rules. This
note pulls the rules in; each rule is one fact with an address, and the
reasons follow the rules. Cite a rule by its address, for example
`spec://org.acme/review/contract/REVIEW#ONE-IDEA`.

#use spec://org.acme/review/contract/REVIEW#root
```

[p41] The line that starts with `#use` is a directive: an instruction to the compiler, not prose. It pulls the contract in ahead of the note, so that the agent meets the rules before the note referring to them.

[p42] 2. Create `vibevm/vibespecs/contract/REVIEW.md` inside the same package:

[p43]
```markdown
# Review rules {#root}

<status stage="spec" state="done"/>

The rules a reviewer holds every change to. Each rule is one anchored
fact with a status.

## One idea per change {#one-idea}

@fact:ONE-IDEA A change under review carries one idea, named in its first line. @status:spec/done

## Tests before fixes {#tests-first}

@fact:TESTS-FIRST A change that alters behaviour carries a test that fails without it. @status:spec/done
```

[p44] Read the file as a machine does. A heading carries an [anchor](../glossary/index.xml#anchor) in braces, `{#one-idea}`: the name a citation uses, the part after `#` in an address. A paragraph that opens with `@fact:ONE-IDEA` is a fact, one anchored rule with a status. The `@status:spec/done` at its end says that the rule is settled and not yet built. The `<status>` element under the title is the same marker for the whole document. Heading anchors and fact ids share one namespace, so no two may repeat in a document.

> [p45] **Fact anchors — the anchored-when-marked law** *(owner, 2026-07-24;
>      spelling amended 2026-08-06)*. A stable fact address is written
>      `@fact:<ID>` as the **first token** of a paragraph or list item. The
>      **legacy** spelling `##<ID>` means the same and is still read.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-facts/PROP-043#FACT-ANCHOR-SYNTAX>

> [p46] **Every unit that carries a status marker — paragraph or list item —
>      MUST also carry a `@fact:<ID>` anchor**; a marked, anchor-less unit is a
>      `check` error.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-facts/PROP-043#ANCHORED-WHEN-MARKED>

> [p47] `@status:<stage>/<state>` and `@status:<stage>` are macro-equivalents of a
> point marker. The **legacy** spellings `@<stage>/<state>` and `@<stage>` mean
> exactly the same and are still read, so a document written before the
> qualified form keeps parsing.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-facts/PROP-043#SHORTHAND-FORMS>

> [p48] `<ID>` is
>      `[A-Za-z][A-Za-z0-9_-]*`; the unit is then addressable as
>      `spec://…/<doc>#<ID>`, sharing one address space with the heading
>      `{#anchor}`s — a duplicate across both forms is a `check` error. The
>      **address is unchanged by the spelling**: it names the id, never the
>      opener.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-facts/PROP-043#FACT-ID-GRAMMAR>

[p49] The case of an id is a signal. Upper-case ids mark rules with binding weight; lower-case ids mark headings, lead-ins and notes. The first rule's address is `spec://org.acme/review/contract/REVIEW#ONE-IDEA`. It joins the package's [coordinate](../glossary/index.xml#coordinate), the document's path under `vibevm/vibespecs/` without its extension, and the anchor. The address does not change when the file changes form.

> [p50] **Decision — two anchor-id registers (owner ruling, 2026-07-24).**
>      `@fact:UPPER-SLUG` names a **normative fact** (a law, rule, carrier,
>      changelog entry — content with binding weight); `@fact:kebab-case`
>      names a **service unit** (status lines, lead-ins, connective
>      prose).
>
> <spec://org.vibevm.core/vibevm/modules/vibe-facts/PROP-043#DECISION-TWO-REGISTERS>

> [p51] `spec://` addressing is format-blind: anchors
> are `id` attributes in XML and `{#…}`/first-token anchors in MD, minted
> into the same address space; a document's address does not change when
> its serialisation does.
>
> <spec://org.vibevm.core/vibevm/common/PROP-045#ADDRESSING-UNCHANGED>

[p52] 3. Check the markup of the package:

[p53]
```sh
vibe facts check --path vibevm/vibepacks/org.acme/review/v0.1.0
```

```output
progress check: clean (2 files, 0 warning(s))
```

[p54] The check reads every document under the package's `vibevm/vibespecs/`. A marker on a paragraph without an anchor, and an id defined twice, are errors that name the line.

[p55] 4. Install the package into its own project:

[p56]
```sh
vibe install org.acme/review --assume-yes
```

```output
Resolving 1 root package…

Materialising 1 package into vibedeps/:
  org.acme/review@0.1.0

closure diff:
  → + org.acme/review@0.1.0 (root-edge)
  → lane vibevm/vibespecs/boot/INDEX.md: 737 -> 781 B
  → lane vibevm/vibespecs/boot/STATIC.xml: absent -> 2809 B

Materialised 1 package into vibedeps/; regenerated boot artifacts for 1 node(s).
```

[p57] The install copies the package into `vibevm/vibedeps/`, converting both documents to XML. It also compiles the [boot lane](../glossary/index.xml#boot-lane), the ordered list of files an agent reads at session start. The last two lines of the diff are that lane: `INDEX.md` gained the line that names the compiled file, and `STATIC.xml` appeared.

[p58] 5. Open the contract as the agent will read it:

[p59]
```sh
cat vibevm/vibedeps/org.acme.review/0.1.0/vibevm/vibespecs/contract/REVIEW.xml
```

```output
<?xml version="1.0" encoding="UTF-8"?>
<spec xmlns="https://vibevm.org/spec/1">
  <title id="root">Review rules</title>
  <status stage="spec" state="done"/>
  <p>The rules a reviewer holds every change to. Each rule is one anchored
fact with a status.</p>
  <one-idea title="One idea per change">
    <p><ONE-IDEA fact="true" status="spec/done">A change under review carries one idea, named in its first line.</ONE-IDEA></p>
  </one-idea>
  <tests-first title="Tests before fixes">
    <p><TESTS-FIRST fact="true" status="spec/done">A change that alters behaviour carries a test that fails without it.</TESTS-FIRST></p>
  </tests-first>
</spec>
```

[p60] Every part is named after itself. The section `{#one-idea}` became the element `<one-idea>`, with its title as an attribute. The fact became `<ONE-IDEA fact="true" status="spec/done">`. The `@fact:` prefix and the `@status:` suffix are gone from the text: they were spelling, and the tree keeps only meaning. Inline Markdown, such as backticks and links, rides inside the text unchanged.

> [p61] **Decision (ADR-part).** Inline content —
> emphasis, inline code, links, `##NAME` citations, `spec://` addresses —
> rides INSIDE text nodes as literal Markdown conventions, in both
> directions. The pivot does not model inline grammar. Why: the markup
> contract already treats inline code as opaque; round-tripping stays
> byte-stable at the text level; XML authorship needs no inline vocabulary;
> and every consumer that reads fact bodies today keeps reading the same
> strings. Alternative weighed — a full inline element vocabulary
> (`<code>`, `<a>`, `<b>`) — rejected as cost without a consumer and a
> fresh drift surface between two inline grammars.
>
> <spec://org.vibevm.core/vibevm/common/PROP-045#INLINE-STAYS-MARKDOWN>

## A header and its implementation {#headers}

[p62] A contract that says everything is expensive. Every word in the boot lane is read by every agent at every session start. So the text that must always be present wants to be short, and the reasoning behind it wants to sit where an agent can reach it when it asks. C solved a problem of this shape in the 1970s with two files, and C++ kept the answer. A header, `.h`, declares in a few lines what a library offers; a translation unit, `.c` or `.cpp`, carries the implementation. Everyone who uses the library includes the header, and nobody pastes the implementation into their own code.

> [p63] Inspired by C/C++ `.h` / `.cpp`:
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-035#hpp-cpp-inspiration>

[p64] VibeVM borrows the split for text. A package in the `normal` format keeps two folders under `vibevm/vibespecs/`. `contract/` is the header: small, cheap to load, the surface other packages and agents see. `source/` is the implementation: the heavy body, pulled in only when something asks for it. A C compiler sees the whole program and matches declarations to definitions itself; vibe has no such view of your text, so the contract names its implementation with a `#source` directive. The default format, `simple`, has none of this: such a package is carried whole and read because it is present.

> [p65] **`contract/`** — small, simple, boot-snippet-like. The surface a package exposes outward; short files, cheap to load. The analogue of a header.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-035#DIR-CONTRACT>

> [p66] **`source/`** — large, heavy. The full implementation; pulled only when actually needed. The analogue of a translation unit.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-035#DIR-SOURCE>

> [p67] Because the structural executor lacks a C++ compiler's global view of the source tree, we use a deliberate hack: **the contract author declares what implements it, via `#source`** (§7.3). The author hand-draws edges a globally-aware compiler would infer.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-035#SOURCE-HACK>

> [p68] **`format = "simple"`** — **the default** (absent `format`, a package is `simple`). Legacy / adapted prompts, carried **whole**, with no VibeVM-specific structure — for importing existing corpora without rewriting them, and the fail-safe posture. Rules: inclusion in `[requires.packages]` means (a) structural — the agent reads the file; (b) static — its text is compiled into the target. If `[boot_snippet].source` names a file, only that file is read/spliced; **absent even that, every file in the package is read/spliced by a recursive walk** — the over-load is the author's problem, the deliberate cost of not adopting `normal`.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-035#FORMAT-SIMPLE>

> [p69] **`format = "normal"`** — the VibeVM-native form, **opt-in**: the `contract` / `source` split (§4), directives (§7), and the compiler (§8). A `normal` package is **not read just because it is present** — it participates only when something actually `#use`s it (§7.2). This is tree-shaking; the optimized posture for authors who understand the machinery, at the price of structuring the package correctly.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-035#FORMAT-NORMAL>

[p70] The specifications call this machinery inheritance, as in C++: one document builds on another without copying its text. Two directives do the pulling, and you have met one. `#use` names a document or a section that must be read before the text that uses it; the compiler copies it in ahead, and the copy brings along whatever the copied document pulls in itself. `#source` names the implementation of a contract, and the compiler compiles it in behind the contract. In the compiled file both directives are gone, consumed. A third, `#embed`, splices exactly one addressed node into the place where it stands, a macro rather than an include.

> [p71] **Inline mode.** The same, statically: the `#use`d library's text is **fully copied higher up in `STATIC.md`** so it is available before the user.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-035#USE-INLINE>

> [p72] Like a C++ interface, but with section-level merging. `contract` sections are the exposed surface; `#source` names the file(s) that implement them. Sections are treated as the analogue of class methods, needing a merge (in the static build) or virtual-lookup (in structural mode) mechanism.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-035#SOURCE-DEF>

> [p73] **`#embed` has arbitrary granularity** — it splices exactly the addressed node, no more.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-035#EMBED-EXACT-RULE>

## Step 4: split the contract from its reasons {#split}

[p74] 1. Create `vibevm/vibespecs/source/details.md` in the package, the implementation of the contract:

[p75]
```markdown
# Review rules, the reasons {#details}

## Why one idea per change {#one-idea-why}

@fact:ONE-IDEA-WHY A change with two ideas cannot be reverted one idea at a time, and its review takes twice as long. @status:spec/done

## Why tests before fixes {#tests-first-why}

@fact:TESTS-FIRST-WHY A fix without a failing test proves nothing: the test states what was wrong. @status:spec/done

## What a reviewer checks {#checklist}

- @fact:CHECK-SCOPE The first line names the one idea, and every hunk serves it. @status:spec/done
- @fact:CHECK-TEST The test fails on the parent commit and passes on this one. @status:spec/done
```

[p76] Every anchor here differs from the contract's, `one-idea-why` beside `one-idea`; the edge cases below say why. The last section is a list in which every item is a fact.

[p77] 2. In `contract/REVIEW.md`, add one line after the first paragraph:

[p78]
```markdown
#source spec://org.acme/review/source/details
```

[p79] The address names a whole document, with no anchor, so the compiler takes it from the title down.

[p80] 3. Install again:

[p81]
```sh
vibe install org.acme/review --assume-yes
```

```output
Resolving 1 root package…

Materialising 1 package into vibedeps/:
  org.acme/review@0.1.0

closure diff:
  → lane vibevm/vibespecs/boot/STATIC.xml: 2809 -> 4557 B

Materialised 1 package into vibedeps/; regenerated boot artifacts for 1 node(s).
```

[p82] No package was added, so the lane is the only line of the diff: the compiled file grew by the implementation.

[p83] 4. Ask vibe what an agent reads first:

[p84]
```sh
vibe tree --plain
```

```output
project: <TMP>/work/review-lab
STATIC.xml: 4557 bytes, 69 lines, 1 contribution(s)
packages: 1   roots: 1
columns: load  T=transitive  C=condition  S=in STATIC.xml

org.acme/review  static   .  .  x
```

[p85] One package, linked `static`, and the `x` says that its text is compiled into `STATIC.xml`.

[p86] 5. Open the compiled file:

[p87]
```sh
cat vibevm/vibespecs/boot/STATIC.xml
```

```output
<!-- vibe:c1 vibevm/vibespecs/boot/STATIC.xml — generated by vibe, do not edit. -->
<!-- vibe:c1 The static boot lane (PROP-009 §2.3): the highest-priority -->
<!-- vibe:c1 contributions, compiled anchor-qualified. Read this first, in full. -->

<!-- vibe:c1 RESOLUTION RULES — read these five lines before anything else:
  1. Labels in this file are qualified: <origin-slug>-%2D<original>. The origin
     is named by the provenance comment above each block; the original short
     label is the tail after the last `-%2D`.
  2. A short label you cannot find here → check the RENAMED ANCHORS table
     below for its qualified heirs; never guess among look-alikes.
  3. Full spec:// addresses resolve against package SOURCES under vibevm/vibedeps/,
     never against this generated file. This file is a cache, not a target.
  4. `#use spec://… as X` binds a file-local alias; `@!X` is a mandatory read
     of X's target (same rules as @spec://…). In this compiled file every @!X
     is already rewritten to its full address.
  5. An ambiguous or unresolvable short reference is an ERROR to surface with
     candidates — never silently pick one. -->

<!-- vibe:c1 RENAMED ANCHORS (short → qualified heirs):
  CHECK-SCOPE → org-acme-%2Dreview-%2DCHECK-SCOPE (org.acme/review)
  CHECK-TEST → org-acme-%2Dreview-%2DCHECK-TEST (org.acme/review)
  ONE-IDEA → org-acme-%2Dreview-%2DONE-IDEA (org.acme/review)
  ONE-IDEA-WHY → org-acme-%2Dreview-%2DONE-IDEA-WHY (org.acme/review)
  TESTS-FIRST → org-acme-%2Dreview-%2DTESTS-FIRST (org.acme/review)
  TESTS-FIRST-WHY → org-acme-%2Dreview-%2DTESTS-FIRST-WHY (org.acme/review)
  checklist → org-acme-%2Dreview-%2Dchecklist (org.acme/review)
  details → org-acme-%2Dreview-%2Ddetails (org.acme/review)
  one-idea → org-acme-%2Dreview-%2Done-idea (org.acme/review)
  one-idea-why → org-acme-%2Dreview-%2Done-idea-why (org.acme/review)
  root → org-acme-%2Dreview-%2Droot (org.acme/review)
  tests-first → org-acme-%2Dreview-%2Dtests-first (org.acme/review)
  tests-first-why → org-acme-%2Dreview-%2Dtests-first-why (org.acme/review) -->

<!-- vibe:c1 vibe:static org.acme/review — vibevm/vibedeps/org.acme.review/0.1.0/vibevm/vibespecs/boot/10-flow-review.xml -->

<?xml version="1.0" encoding="UTF-8"?>
<spec xmlns="https://vibevm.org/spec/1">
  <title id="org-acme--review--root">Review rules</title>
  <status stage="spec" state="done"/>
  <p>The rules a reviewer holds every change to. Each rule is one anchored
fact with a status.</p>
  <org-acme--review--one-idea title="One idea per change">
    <p><org-acme--review--ONE-IDEA fact="true" status="spec/done">A change under review carries one idea, named in its first line.</org-acme--review--ONE-IDEA></p>
  </org-acme--review--one-idea>
  <org-acme--review--tests-first title="Tests before fixes">
    <p><org-acme--review--TESTS-FIRST fact="true" status="spec/done">A change that alters behaviour carries a test that fails without it.</org-acme--review--TESTS-FIRST></p>
  </org-acme--review--tests-first>
  <org-acme--review--details title="Review rules, the reasons">
    <org-acme--review--one-idea-why title="Why one idea per change">
      <p><org-acme--review--ONE-IDEA-WHY fact="true" status="spec/done">A change with two ideas cannot be reverted one idea at a time, and its review takes twice as long.</org-acme--review--ONE-IDEA-WHY></p>
    </org-acme--review--one-idea-why>
    <org-acme--review--tests-first-why title="Why tests before fixes">
      <p><org-acme--review--TESTS-FIRST-WHY fact="true" status="spec/done">A fix without a failing test proves nothing: the test states what was wrong.</org-acme--review--TESTS-FIRST-WHY></p>
    </org-acme--review--tests-first-why>
    <org-acme--review--checklist title="What a reviewer checks">
      <facts ordered="false">
        <org-acme--review--CHECK-SCOPE fact="true" status="spec/done">The first line names the one idea, and every hunk serves it.</org-acme--review--CHECK-SCOPE>
        <org-acme--review--CHECK-TEST fact="true" status="spec/done">The test fails on the parent commit and passes on this one.</org-acme--review--CHECK-TEST>
      </facts>
    </org-acme--review--checklist>
  </org-acme--review--details>
  <section title="Review flow">
    <p>Before you open a change for review, hold it to the review rules. This
note pulls the rules in; each rule is one fact with an address, and the
reasons follow the rules. Cite a rule by its address, for example
`spec://org.acme/review/contract/REVIEW#ONE-IDEA`.</p>
  </section>
</spec>
```

[p88] Read it from the top. The comments come first: the rules an agent applies to the labels in this file, then a table of every renamed anchor. XML forbids two hyphens inside a comment, so the `--` of a label is written `-%2D` there; the elements below carry the real `--`. Then one XML document: the contract first, then the implementation compiled in behind it as one nested section. Last comes the note from the snippet, as a plain `<section>`, because its heading had no anchor. The `#source` and `#use` lines are gone.

> [p89] **In source, absent in contract** — always counted; structural: readable at will; static: compiled in whole. *(Calling a section that exists only in the implementation is poor taste, but permitted — we deliberately impose no `private`/`public` access control.)*
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-035#MERGE-SOURCE-ONLY>

[p90] Every anchor now carries the prefix `org-acme--review--`, the label of its origin. Two packages that both name a section `root` cannot collide in one file, and the table at the top says what each short name became. Cite the source document, never this file: it is a cache that changes whenever a package changes.

> [p91] **Compiled labels are origin-qualified (B-011).** A compiled block's heading anchors and fact ids carry the §8 qualify phase's `<origin-slug>--` prefix, so the compiled document's label namespace is collision-free by construction; reversibility survives because the block's own marker key names the origin, and stripping that block's prefix restores the source labels.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-035#COMPILED-LABELS-ARE-QUALIFIED>

> [p92] **A generated `STATIC.md` is not a citation target** — authored text never cites `spec://…/boot/STATIC#…`; the lane is compiler output, and source-of-truth is the package source under `vibedeps/` (PROP-035 §11's lint, B-011 §6.1).
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-009#COMPILED-LANE-IS-NOT-A-CITATION-TARGET>

[p93] 6. Open the record vibe keeps beside its copy of the package:

[p94]
```sh
cat vibevm/vibedeps/org.acme.review/0.1.0/.vibe-slot.toml
```

```output
schema = 1
source_hash = "sha256:30fef78663fe343057c2d36d65514d3708d2f5053a2d41b713a749a0b28cc8eb"
spec_format = "xml"
converter_recipe = "specdoc/4"
derived_hash = "sha256:e420b6841c71fab586dfd72fe4720881b77d056e8f7a86217d1174b5fde0feba"

[[file]]
path = "README.xml"
sha256 = "c26e751356947bca54346b4099dc297dcdcac44831087d06436b1cb5f51ecec1"
disposition = "converted"
source = "README.md"

[[file]]
path = "vibe.toml"
sha256 = "42cb5b83bdc0e8ef61bdbb46095688593b05e4ad07bc61b8b346c13be0e753c6"
disposition = "copied"
source = "vibe.toml"

[[file]]
path = "vibevm/vibespecs/boot/10-flow-review.xml"
sha256 = "925cd961b99debb27cd179a42908bdf006c5103422c2a216ee8fc874d30865d3"
disposition = "converted"
source = "vibevm/vibespecs/boot/10-flow-review.md"

[[file]]
path = "vibevm/vibespecs/contract/REVIEW.xml"
sha256 = "ebcda3b670be21b409aa07b2600582ceb57cdc7eef8dbb3ef0dfe92dbc4f7a11"
disposition = "converted"
source = "vibevm/vibespecs/contract/REVIEW.md"

[[file]]
path = "vibevm/vibespecs/source/details.xml"
sha256 = "d35c2052468004ff1fb0dfc7724a496d8773aa280e686ce597fd14615acf6a2d"
disposition = "converted"
source = "vibevm/vibespecs/source/details.md"
```

[p95] `spec_format` names the target and `converter_recipe` the converter's version. Each row says whether a file was `copied` or `converted`, with the hash of what landed. Keep the file in mind: step 5 reads it again.

> [p96] **The hash law under transformation.** Source identity is unchanged: lockfile `content_hash` and the machine store hash the source form. A transformed slot is a derived artifact whose identity and owned footprint are recorded in the single `.vibe-slot.toml`: `source_hash`, `spec_format`, versioned `converter_recipe`, optional `overlay_hash`, `derived_hash`, and per-file source/output/disposition/SHA-256 rows. The legacy `.vibe-derived.toml` is no longer written; its schema-1 reader remains only as a read-only compatibility tombstone until a real rematerialisation migrates the slot. Mixed slots verify their recorded source identity and payload; transformed slots additionally verify recipe, representation and `derived_hash`. A missing legacy record triggers one final migration, a malformed new record refuses, and mismatched valid state rematerialises through the owned diff; changing `spec_format` can never earn a presence skip. Semantic equivalence remains the converter's proof through the shared IR, never the hash's job.
>
> <spec://org.vibevm.core/vibevm/common/PROP-045#HASH-LAW>

## The equivalent forms {#equivalent-forms}

[p97] The constructs this page used, side by side, from its own files:

[p98]
| Markdown | XML |
| --- | --- |
| `# Review rules {#root}` | `<title id="root">Review rules</title>` |
| `<status stage="spec" state="done"/>` | the same element, unchanged |
| `## One idea per change {#one-idea}` and the text under it | `<one-idea title="One idea per change">…</one-idea>` |
| a paragraph | `<p>…</p>` |
| `@fact:ONE-IDEA … @status:spec/done` | `<p><ONE-IDEA fact="true" status="spec/done">…</ONE-IDEA></p>` |
| `- @fact:CHECK-SCOPE … @status:spec/done`, a list of facts | `<facts ordered="false"><CHECK-SCOPE fact="true" status="spec/done">…</CHECK-SCOPE></facts>` |
| `#source spec://…`, a directive | `<p>#source spec://…</p>`, a plain paragraph |
| `# Review flow`, a title without an anchor | `<title>Review flow</title>` |
| backticks, emphasis, links, `spec://` addresses | the same characters inside the text |

[p99] The dialect is closed, and it is small on purpose. It expresses exactly what Markdown can express, so that converting in either direction loses nothing in meaning, and an element outside it is a loud error, never a silent skip. Two anchors have no element of their own: one that starts with a digit, and one that collides with a word of the dialect such as `title` or `list`. For those, a generic `<section id="…">` or `<fact id="…">` stands in, and every reader accepts both spellings. The one exception to the rule of equivalence is the vocabulary of documentation packages, this manual among them: their runnable examples and live citations have no Markdown form, and project to Markdown one way only.

> [p100] **Decision (ADR-part).** The XML dialect
> is deliberately ISOMORPHIC to the Markdown-expressible structure — exactly
> the constructs the markup contract names, in XML syntax, and nothing more.
> A schema-foreign element or attribute is a loud parse error, never a
> silent skip (the same closed-vocabulary law the typed-fact grammar took).
> This is what makes the owner's degradation law hold by construction:
> XML→MD loses nothing semantic because the dialect cannot express what MD
> cannot; «всё невыразимое — не поддерживается» is enforced by the schema,
> not by a lossy converter. **Reopened once, 2026-09-11, for exactly one
> genre** — §7 `##DOC-VOCAB-REOPENING`: the documentation vocabulary of
> PROP-057 is additive, gated by the package kind `doc`, and one-way to
> Markdown by law; for the spec vocabulary this decision stands unchanged.
>
> <spec://org.vibevm.core/vibevm/common/PROP-045#XML-DIALECT-IS-THE-MD-SUBSET>

> [p101] **Decision (ADR-part; owner ruling 2026-08-22,
> near-verbatim): «Не правильней ли не включать внутри list элементы item,
> а сразу ставить в тело list элементы типа `<THE-LAW fact="true"...>`? И
> вместо `<list>` использовать тэг `<facts>` — это новое слово для
> зарезервированного словарика… Если же список состоит из обычного текста
> (там могут даже иногда встречаться факты), то все элементы — это item и
> группировка — list, а факты в нём рендерятся как сейчас».** The law: a
> list whose every item is exactly one meaningful fact materialises as the
> vocabulary element `<facts ordered="…">` with the fact elements (named
> or generic) directly in its body — no `<item>` wrappers; any other list
> (plain text, or mixed with occasional facts) keeps today's
> `<list>`/`<item>` shape with facts rendered inside items. `facts` joins
> the reserved vocabulary (an anchor named `facts` falls back to the
> generic form); `ordered` carries over exactly as on `<list>`; the model
> is unchanged — both shapes parse to the same `Block::List`, so the
> reader accepts BOTH forms (old materialisations in the wild stay
> readable) and a rewrite normalises the all-fact shape to `<facts>`.
> Loud errors guard the grouping: a non-fact child inside `<facts>`, bare
> text inside `<facts>`, an empty `<facts>`. Both readers — the pivot and
> the specmap engine's native one — learn the form mirror-wise; the
> converter recipe bumps `specdoc/3` → `specdoc/4` and the host
> re-materialises once. Landed: the writer branch, the `facts_block`
> parser (split into the pivot's own `xml_facts.rs` along the engine's
> seam) and the vocabulary word sit in both readers with the four loud
> errors pinned; the engine proves model-identity by content hash
> between the two shapes; the redbook README golden re-pins with two
> `<facts>` groups and the same 45 named facts; the host's 37 slots
> re-materialised at `specdoc/4`; and the §5a stand re-ran as the
> regression tool it was left as — polygons rebuilt on the new shape
> (121 files carry groups), the sensitive tier (`gpt-5.5`@`low`) swept
> 9/9 with the negative control clean.
>
> <spec://org.vibevm.core/vibevm/common/PROP-045#FACTS-GROUP-ELEMENT>

> [p102] **Decision (ADR-part; the recorded reopening of `##XML-DIALECT-IS-THE-MD-SUBSET`, 2026-09-11).**
> The decision-records law lets a decision be reopened only by a named trigger,
> and the trigger is named: a consumer appeared that needs constructs Markdown
> cannot express — the documentation genre of
> [PROP-057](PROP-057-documentation-packages-and-site.xml): a verifiable
> example with its expected output, a live citation of a specification rule, a
> generated reference block, an agent prompt with asserts. The dialect gains a
> **second, additive vocabulary** for that genre. The spec vocabulary keeps the
> MD-subset law unchanged; the documentation vocabulary is open only inside
> packages of kind `doc` and projects to Markdown one way, by law, not by
> defect.
>
> <spec://org.vibevm.core/vibevm/common/PROP-045#DOC-VOCAB-REOPENING>

## Step 5: convert, and see nothing change {#round-trip}

[p103] You wrote Markdown and shipped XML. The last step shows that the XML you would have written by hand is the same file, to the byte.

[p104] 1. Ask the converter what a conversion would lose:

[p105]
```sh
vibe refactor convert-source --to xml --dry-run vibevm/vibepacks/org.acme/review/v0.1.0/vibevm/vibespecs
```

```output
dry-run ir-stable-loss vibevm/vibepacks/org.acme/review/v0.1.0/vibevm/vibespecs/boot/10-flow-review.md
--- source
+++ reverse-projection
@@ -8,2 +8,3 @@
 #use spec://org.acme/review/contract/REVIEW#root

+

dry-run ir-stable-loss vibevm/vibepacks/org.acme/review/v0.1.0/vibevm/vibespecs/contract/REVIEW.md
--- source
+++ reverse-projection
@@ -16,2 +16,3 @@
 @fact:TESTS-FIRST A change that alters behaviour carries a test that fails without it. @status:spec/done

+

dry-run ir-stable-loss vibevm/vibepacks/org.acme/review/v0.1.0/vibevm/vibespecs/source/details.md
--- source
+++ reverse-projection
@@ -14,2 +14,3 @@
 - @fact:CHECK-TEST The test fails on the parent commit and passes on this one. @status:spec/done

+

summary converted=0 already=0 lossy-confirmed=0 refused=0 skipped-generated=0 skipped-foreign=0 skipped-harness=0 dry-run=3
```

[p106] The converter parses each file into the tree, prints the XML, reads that back and prints Markdown again, then compares. The verdict `ir-stable-loss` means that the tree survived and the bytes did not, and the diff shows what changed. Here it is one blank line at the end of each file, added by the Markdown printer. Without `--force` the command refuses a file over such a loss. A change of meaning it refuses always, because that would be a defect of the converter, never of your file.

> [p107] The destructiveness check is the owner's law,
> implemented by reverse reconversion through the pivot: for a source
> `S`, parse to IR, emit the target form, read the target form back, and
> project it into the SOURCE form again; compare. Three classes:
> **(1) byte-stable** — the back-projection equals `S` byte-for-byte:
> converts silently. **(2) IR-stable loss** — the back-projection
> re-parses to the SAME IR but differs in bytes (dropped MD/XML comments,
> normalised layout): the verb REFUSES with a per-file description of
> what is lost, and proceeds only on interactive confirmation or
> `--force`. **(3) IR-divergent** — the back-projection re-parses to a
> DIFFERENT IR: always refused, `--force` does not apply; that class is a
> `vibe-specdoc` defect to file (the pivot broke its own round-trip law),
> never a corpus to damage.
>
> <spec://org.vibevm.core/vibevm/common/PROP-051#HONESTY-BY-REVERSE>

> [p108] On a TTY, class-2 files prompt per file
> (yes / no / all); off a TTY, class-2 without `--force` is an error
> listing every lossy file and what each loses. `--force` waives class 2
> only. `--dry-run` classifies and reports every file, writes nothing,
> and always exits 0 (an inventory, not an attempt); a real run exits 0
> iff every requested conversion landed, non-zero when any file was
> refused or declined.
>
> <spec://org.vibevm.core/vibevm/common/PROP-051#FORCE-AND-PROMPT>

[p109] 2. Convert the three documents. The command writes each `.xml` beside its `.md` and deletes the `.md` in one act. The tree never holds a document in both forms:

[p110]
```shell
vibe refactor convert-source --to xml --force vibevm/vibepacks/org.acme/review/v0.1.0/vibevm/vibespecs
```

> [p111] A conversion writes the sibling
> serialisation and deletes the original in the same act, so the
> one-document-one-form law (PROP-045 ##TARGET-MIXED, the pair-collision
> check) holds at every instant — the tree never holds `X.md` and `X.xml`
> together, not even transiently between files. The verb assumes version
> control underneath it and keeps no backups of its own.
>
> <spec://org.vibevm.core/vibevm/common/PROP-051#ONE-DOCUMENT-ONE-FORM-ON-CONVERT>

[p112] 3. In the package manifest, point `source` at the new file, `vibevm/vibespecs/boot/10-flow-review.xml`.

[p113] 4. Install again:

[p114]
```sh
vibe install org.acme/review --assume-yes
```

```output
Resolving 1 root package…

Materialising 1 package into vibedeps/:
  org.acme/review@0.1.0

  → closure unchanged (1 packages)

Materialised 1 package into vibedeps/; regenerated boot artifacts for 1 node(s).
```

[p115] 5. Open the record again:

[p116]
```sh
cat vibevm/vibedeps/org.acme.review/0.1.0/.vibe-slot.toml
```

```output
schema = 1
source_hash = "sha256:6c7b5d64df7efadb3ebb7ba950902e3e3e1cc1a0a9c0a06f464c55a9f01bd209"
spec_format = "xml"
converter_recipe = "specdoc/4"
derived_hash = "sha256:908b2213850eba6789f655e7cfac4722328659e1eb5ea5017b8eb71055a152dc"

[[file]]
path = "README.xml"
sha256 = "c26e751356947bca54346b4099dc297dcdcac44831087d06436b1cb5f51ecec1"
disposition = "converted"
source = "README.md"

[[file]]
path = "vibe.toml"
sha256 = "018fe59c3ed0b41a24ad236798f5b8198c6ad983a58a9c72d7f73897a4d56e62"
disposition = "copied"
source = "vibe.toml"

[[file]]
path = "vibevm/vibespecs/boot/10-flow-review.xml"
sha256 = "925cd961b99debb27cd179a42908bdf006c5103422c2a216ee8fc874d30865d3"
disposition = "copied"
source = "vibevm/vibespecs/boot/10-flow-review.xml"

[[file]]
path = "vibevm/vibespecs/contract/REVIEW.xml"
sha256 = "ebcda3b670be21b409aa07b2600582ceb57cdc7eef8dbb3ef0dfe92dbc4f7a11"
disposition = "copied"
source = "vibevm/vibespecs/contract/REVIEW.xml"

[[file]]
path = "vibevm/vibespecs/source/details.xml"
sha256 = "d35c2052468004ff1fb0dfc7724a496d8773aa280e686ce597fd14615acf6a2d"
disposition = "copied"
source = "vibevm/vibespecs/source/details.xml"
```

[p117] Compare it with the record of step 4. Each of the three documents is now `copied`, and the hash of each is the hash it had as `converted`. What the converter wrote from your Markdown is what the install wrote from it, to the byte. The compiled file did not change either:

[p118]
```sh
vibe tree --plain
```

```output
project: <TMP>/work/review-lab
STATIC.xml: 4557 bytes, 69 lines, 1 contribution(s)
packages: 1   roots: 1
columns: load  T=transitive  C=condition  S=in STATIC.xml

org.acme/review  static   .  .  x
```

[p119] The same bytes, the same lines. This is what one model underneath means in practice. The form you write in is your choice; the text that reaches the agent is the same either way. A project with `spec_format = "markdown"` runs the same road the other way. The Markdown it writes from your XML is your original file plus that one blank line.

> [p120] **The C++-inheritance machinery is
> format-blind — verified, not assumed (owner clause 2026-08-22,
> verbatim: «проверь что все механизмы "наследования как в C++" которые
> мы сделали для Markdown, точно так же работают и для XML, включая
> новый синтаксис секций и фактов. Если нет, это тоже нужно
> улучшить»).** The B-011 family — `#use spec://… as X` aliasing, `@!X`
> references, the qualified splice (rename-on-splice with every
> reference kept valid), hoist/elision stubs, de-substitution of covered
> units, rename tombstones, and the dynamic-STATIC case — must produce
> BYTE-IDENTICAL compiled closures whether a dependency is authored in
> Markdown or in dialect XML (generic and named shapes both). Pinned by
> a twin-test family in vibe-spec's pipeline: each mechanism runs over
> an MD twin and its to_xml serialisation, outputs compared
> byte-for-byte; mixed trees (one dep MD, one XML) ride the same pins.
> Gaps found by the twins are fixed in the machinery, never by relaxing
> the assert. Landed: eight twins (four in vibe-spec's pipeline — alias +
> `@!X`, an alias declared inside the projected node over a mixed tree,
> the three-way same-short-anchor splice, fact-grain through an alias —
> comparing lane AND rename map; four on the bootgen floor — the
> static-transitive zone, single-copy hoist, de-substitution over a mixed
> lane, and the dynamic-STATIC install case), every twin minting its XML
> at run time so the family always carries the live dialect form. Parity
> held out of the box at the compile floor: byte-identical, no machinery
> change. The twins NAMED the lawful residue at the install floor —
> `vibe:static` provenance comments cite the true source file (extension
> included) and INDEX raw-snippet paths carry the materialised extension,
> while the dynamic-STATIC target stays the generated, extension-stable
> `STATIC.md` — honest provenance, not a format leak.
>
> <spec://org.vibevm.core/vibevm/common/PROP-045#INHERITANCE-PARITY>

> [p121] **`markdown` target:** XML sources emit as Markdown through
> the pivot (nested sections → heading levels, facts → anchored units,
> fences/tables/quotes → their MD forms); MD sources copy verbatim. This
> target exists for tooling that cannot read XML or mixed trees — named in
> the mandate as load-bearing, and it stays supported for as long as this
> PROP stands.
>
> <spec://org.vibevm.core/vibevm/common/PROP-045#TARGET-MD>

## What appeared on disk {#what-appeared}

[p122] Your package lives under `vibevm/vibepacks/org.acme/review/v0.1.0/`: a manifest, a README and three XML documents, yours to edit. vibe's copy of it lives under `vibevm/vibedeps/org.acme.review/0.1.0/`, with the record `.vibe-slot.toml` beside it, and is rewritten at every install. `vibevm/vibespecs/boot/` holds the compiled `STATIC.xml` and `INDEX.md`, whose `static` line names the compiled file, next to your two boot files, which no install touches. `vibe.lock`, the project's [lock file](../glossary/index.xml#lock-file), pins the package to its version and its hash.

> [p123] **Decision.** A node's authored `spec/` and its materialised dependencies live in physically separate trees. `vibe install` **never writes into any node's authored `spec/`**.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-009#TWO-TREES>

## Edge cases and rules {#edge-cases}

[p124] Under `spec_format = "xml"`, a source section must not reuse an anchor of the contract, and the note's title carries no anchor. The compiler merges sections that share an anchor, and the merged result does not compile into `STATIC.xml` today. The install stops with `fact id … is defined twice`; the copy of the package is already on disk, and the lock is not written. Two documents whose titles are both `{#root}` collide the same way. Under the default `spec_format` the same package compiles.

[p125] A dot inside an anchor is a path, not a character. `{#verification.timeout}` cannot be addressed, while `#verification.timeout` reaches a section `timeout` nested under a section `verification`. An anchor is a letter followed by letters, digits, `_` and `-`.

> [p126] `#<anchor>.<sub>…` is a **tree path** into the document IR (§5).
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-035#URI-TREE-PATH>

[p127] `vibe init package` writes `link = "dynamic"` whatever `--link` you pass. Set the link in the manifest, as step 2 does.

[p128] With `link = "dynamic"` there is no compiled file. `INDEX.md` names the note itself, and the agent that opens it meets the `#use` line and must follow it on its own.

[p129] `vibe explain` answers only for a package that carries a [traceability map](../glossary/index.xml#traceability-map). For this one it says so and stops.

[p130] `vibe facts check` catches a misspelt state in the element form, `state="finished"`, and names the value. In the shorthand, `@status:spec/finished` is not read as a marker at all, and the file passes as clean. The states are `plan`, `work`, `done`, `hold` and `void`; the stages are `idea`, `spec`, `impl`, `test`, `doc`, `freeze` and `unknown`.

> [p131] One XML-shaped element, embedded in Markdown (and, later, native in XML
> documents — the frontend duality of PROP-035 §5):
>
> <spec://org.vibevm.core/vibevm/modules/vibe-facts/PROP-043#STATUS-ELEMENT>

