<?xml version="1.0" encoding="UTF-8"?>
<spec xmlns="https://vibevm.org/spec/1">
  <title id="root">PROP-057 — Documentation packages and the documentation site</title>
  <status stage="spec" state="work" comment="commissioned by the owner 2026-09-09 and 2026-09-10 (mandate quoted in §1 verbatim); contract authored 2026-09-11 from the campaign vision after the phase-0 spikes of the docs-2026-09 campaign; the kind register itself grows only by the owner&apos;s hand in VIBEVM-SPEC.md §4.1 (the amendment is prepared, not yet applied); the shell-embedding mechanism of §12 is proposed until the owner confirms it; implementation is phases 2 to 5 of the campaign"/>
  <p p="1"><fact id="self-uri" status="spec/done">`spec://org.vibevm.core/vibevm/common/PROP-057`</fact></p>
  <p p="2"><fact id="status-line" status="spec/work">**Status:** contract authored, not yet implemented. This PROP is the norm for two new package kinds (`doc`, `app`), the companion role of documentation packages, the relation between a documentation package and its subject, localization by package, discovery and the official/community hierarchy, the package card, the two documentation levels, the public site `vibevm.org/doc`, the Rust content pipeline, the local reader, the web stack, the SEO contract, observability without judging, the fate of the old `docs/` tree, and the style law of the documentation prose. Every decision below was taken by the owner in the campaign vision; where a decision is still a proposal, its facts carry `spec/work`.</fact></p>
  <p p="3"><fact id="related" status="spec/done">**Related:** [PROP-000 §6](PROP-000.xml) (`##KIND-SET`, `##INV-VOCABULARY` — the kind register mirror), [PROP-028](PROP-028-package-families.xml) (the family roles this PROP extends with the `-docs` companion), [PROP-045](PROP-045-xml-spec-sources.xml) (the XML dialect this PROP widens with a documentation vocabulary), [PROP-043](../modules/vibe-facts/PROP-043-facts-markup.xml) (the `audience` vocabulary gaining `agent`), [PROP-047](../modules/vibe-progress/PROP-047-progress-campaigns.xml) (`##DOC-COVERAGE-RATCHET`, `##DEBT-MUST-BE-ASKABLE`), [PROP-003 §2.7](../modules/vibe-resolver/PROP-003-dep-evolution.xml) (BCP-47 tags and the preference chain the localization reuses), [PROP-024](PROP-024-code-bearing-packages.xml) (a package is its own source tree — no build artifacts), [PROP-044 §6.2](PROP-044-change-native-formats.xml) (`##FMT-MANIFEST` — the manifest is the most expensive format), [PROP-052](PROP-052-directory-layout.xml) (the `vibevm/` root the pages live under), [PROP-019](PROP-019-version-manager.xml) (the `~/.vibe/opt/vibevm/` store the shell asset joins), [PROP-025](../modules/vibe-workspace/PROP-025-binary-delivery.xml) (release assets and their manifest), [PROP-009](../modules/vibe-workspace/PROP-009-loading-model.xml) (the boot lane documentation never enters), the design rationale [`design/documentation-vision.xml`](../design/documentation-vision.xml) (the campaign vision with its thirty decision records — this PROP wins where they disagree), and the campaign zone `campaigns/docs-2026-09/` (plan, ledger, findings, journal).</fact></p>
  <section id="mandate" title="1. Owner mandate, verbatim">
    <p p="4"><fact id="MANDATE-GOAL" status="spec/done">The owner opened the campaign (2026-09-09): «я хочу следующим этапом начать писать документацию. И я хочу понять, как правильно ее писать, чтобы она а) хорошо анализировалась ИИ агентами б) идеально читалась людьми». The same sitting named the three abilities the documentation owes: jumping from a document to the exact rule of a specification, «как часть делают в книгах по C++, где упрощенное описание в учебной книге ссылается на точные строки стандарта C++»; examples that let a user understand a feature without long thought; and an infrastructure for agents «и через веб, и через локальный доступ», so that a future skill can send an agent to the documentation as well as to the specs.</fact></p>
    <p p="5"><fact id="MANDATE-DOCS-RS" status="spec/done">The owner set the shape of the site (2026-09-09): «сделать аналог docs.rs который генерирует документацию для ВСЕГО что лежит в репозитории — это очень круто и очень хотелось бы сделать так. Но важно, что пакеты в центральном репозитории vibespecs меняются и технически сайт должен уметь это всё обновлять у себя. Возможно, опциональная документация должна идти сопроводительным пакетом типа org.vibevm.world.docs/multi-user-planning (или выработать еще какую-то конвенцию более правильную, если эта не подходит)».</fact></p>
    <p p="6"><fact id="MANDATE-FIVE-POINTS" status="spec/done">The owner's five points (2026-09-09): «1) вводим kind doc 2) пакет org.vibevm.doc/web, сайт vibevm.org/doc 3) было бы неплохо в будущем сделать этот сайт запускаемым и локально, чтобы потом сделать приложение для чтения документации (например, плагин для vscode как встроенный iframe на это локальное приложение) — это нужно для чтения документации закрытых проприетарных пакетов, которых нет в репозитории 4) всё должно быть сделано для максимального SEO, включая LLM SEO для того чтобы краулеры Anthropic и OpenAI находили быстрее: llms.txt, llms-full.txt для базового корпуса документации, и другие приёмы 5) технически стек для веба — https://qwik.dev, а точнее — его свежая версия 2.0: https://next.qwik.dev/ (да, она в бете, это нормально)».</fact></p>
    <p p="7"><fact id="MANDATE-HOST-CHANNEL" status="spec/done">On the host channel (2026-09-09): «канал хоста — пока что только тот репозиторий который мы указали в настройках при запуске/генерации (по умолчанию — гитхаб), зеркала и прочее — когда-нибудь в будущем».</fact></p>
    <p p="8"><fact id="MANDATE-OFFICIAL-EDGE" status="spec/done">On officiality (2026-09-09): «я боюсь что если ребро документации будет исходить из самого пакета, то три разных человека законтрибьютят три разных пакета документации, и непонятно будет — какой "официальный" пакет показывать на нашем сайте. Может быть, ребро должно исходить и из самого документируемого пакета тоже? То есть, пакет указывает свою "официальную" документацию (и там может быть одна штука выбрана как "основная" документация, и сколько угодно как дополнительные "официальные"). Но при этом остается возможность самим пакетам с документацией сделать обратное ребро тоже — и тогда на сайте мы сможем сделать раздел с "неофициальной" документацией (community docs)».</fact></p>
    <p p="9"><fact id="MANDATE-LOCALIZATION" status="spec/done">On localization (2026-09-10): «скорей всего, нужно каждую из локализаций иметь отдельным пакетом, а сайту показывать селектор локализации. А в пакете иметь официальную ссылку на каждый из пакетов для разных языков», and: «неофициальные переводы тоже должны искаться сайтом (просто отображаться как переводы сообщества, а не официальные). Эта иерархия официальной и неофициальной документации, их официальных и неофициальных переводов должна как-то понятно и наглядно отражаться в интерфейсе (например, звездочки на "официальных" элементах)».</fact></p>
    <p p="10"><fact id="MANDATE-CARD" status="spec/done">On the card (2026-09-10): documentation may carry its own human-readable name, «таким как оно будет выглядеть в интерфейсе сайта»; «обычно для библиотеки документации лучше иметь человекочитаемое название и человекочитаемый абстракт — так же как это делают поисковики по arxiv.org»; an optional large square icon and an optional wide banner «как баннер профиля в твиттере 1500x500», with generated placeholders when absent; and the Open Graph preview «отдельной опцией», because the banner's proportions do not fit a link preview.</fact></p>
    <p p="11"><fact id="MANDATE-EMBEDDING-CHOICES" status="spec/done">The owner's answers to the vision's forks (2026-09-10): «2) вариант Б, а приложения и VSCode-плагины будут вставлять в себя этот интерфейс через webview 3) сразу A, и спланировать самые частые вещи 5) вариант А, добавить agent 6) Б для локального читателя, А для сборки публичного сайта на сервере, где Node есть. Но прежде чем подтвержу, скажи как именно ты собрался встраивать оболочку vibe» — the last clause is why §12's embedding mechanism carries `spec/work`.</fact></p>
    <p p="12"><fact id="MANDATE-VERSIONS-ARE-CONTRACTS" status="spec/done">On versions (2026-09-10): «мы очень редко обновляем версию Vibe. Узнать что версия изменилась нельзя почти никак, и это фича. […] Мы постоянно делаем trunk based development с переписыванием истории». And: «я предлагаю тебе не рассчитывать ни на что кроме самого номера версии. То есть, разница считается между номерами версий. […] Она происходит из осознанного желания владельца поменять номер версии». And: «эти данные — это всё нужно для разработчиков документации. А пользователи всей этой внутренней кухни видеть не должны. Они видят версию 1.0.0 и воспринимают это как контракт».</fact></p>
    <p p="13"><fact id="MANDATE-NO-RELEASE-GATE" status="spec/done">On maintenance (2026-09-10): «Единственное что мне не нравится твое правило "продукт не выходит без обновления документации". Это неправда в нашем случае. Мы можем релизить новые версии 10 раз в день и мерджить по 100 пулл-риквестов в день. Нет никаких шансов, что документация не будет дрейфовать. Этот риск мы принимаем. Мы просто обещаем себе чисто исходя из процессов нашей команды (не технически) время от времени проводить полную проверку».</fact></p>
    <p p="14"><fact id="MANDATE-STYLE" status="spec/done">On style (2026-09-10): «Мы пишем документацию для умных, технологически продвинутых людей, многие из которых — senior developers или имеют академический бэкграунд в ИИ. И часто даже они не понимают, что написала Claude. Потому что агенты Claude обычно пишут исходя из неверного предположения, что человек вначале прочитал всю документацию и все спеки […]. Поэтому технические места лучше описывать словами: ASD-STE100 Simplified Technical English (STE), открыто и просто говорить как делаются те или иные вещи (без "посмотрите в спецификацию, прочитайте все и сами поймете). […] писать для них нужно как в лучших научно-популярных журналах — ярко, броско и с юмором […]. Важно: исходный текст английский, все остальные языки (включая русский!) это адаптации английского».</fact></p>
    <p p="15"><fact id="MANDATE-LANDING-AND-WAVES" status="spec/done">On the landing and the order of work (2026-09-10): «В ходе кампании нужно наш лендинг vibevm-org тоже переделать на Qwik чтобы было однообразно и хорошо композировалось»; «вначале сделай всю документацию на английском, русский перевод будет следующей волной»; «вначале написала все красивые тексты на английском, и дальше мы полностью переключимся на Опус и будем работать в нем над всей разработческой частью».</fact></p>
    <p p="16"><fact id="MANDATE-PROMPT-FIRST" status="spec/done">On scenario pages (2026-09-10): «теперь любое действие можно сделать не только вручную, но и агентом. Поэтому для сценариев имеет смысл вначале писать, каким простым промптом достичь результата […], и только потом уже разворачивать механику работы без агентов целиком вручную (если это вообще нужно! иногда не нужно!). […] Это важное отличие от документации прошлого, где все делалось только руками».</fact></p>
  </section>
  <section id="kinds" title="2. Two new kinds: doc and app">
    <p p="17"><fact id="KIND-REGISTER-LAW" status="spec/done">The kind register is `VIBEVM-SPEC.md` §4.1 and grows only by the owner's hand; PROP-000 `##KIND-SET` and `##INV-VOCABULARY` mirror it. This PROP prepares the amendment admitting `doc` and `app` and defines their semantics; the register entry itself is the owner's edit (campaign atom A1.1).</fact></p>
    <p p="18"><fact id="KIND-DOC-LEAD" status="spec/done">**`doc` — documentation as a package.** A package of kind `doc` documents one or more other packages (its *subjects*) and is read, never executed.</fact></p>
    <list ordered="false" p="19">
      <item><fact id="KIND-DOC-MUST-DOCUMENT" status="spec/done" action="continue" actionstage="doc" audience="author">A `doc` package MUST declare at least one subject in `[[documents]]` (§4) and MUST carry `title` and `abstract` (§7).</fact></item>
      <item><fact id="KIND-DOC-MUST-NOT-EXECUTE" status="spec/done" action="continue" actionstage="doc" audience="author">A `doc` package MUST NOT declare `[boot_snippet]`, `[[mcp_server]]` or `[[binary]]`; `vibe check` rejects the manifest. Documentation never enters a boot lane (§14).</fact></item>
      <item><fact id="KIND-DOC-MAY-DECLARE" status="spec/done" action="continue" actionstage="doc" audience="author">A `doc` package MAY declare `[[skill]]`, `[translates]` (§5) and `[media]` (§7).</fact></item>
      <item><fact id="KIND-DOC-PAGES-LOCATION" status="spec/done" action="continue" actionstage="doc" audience="author">Its pages live under `vibevm/vibespecs/` of its own tree like the specs of any package (PROP-052); a document belongs to the documentation genre by the kind of the package that contains it, and only there the documentation vocabulary of the dialect is open (PROP-045 `##DOC-VOCAB-BY-KIND`).</fact></item>
      <item><fact id="KIND-DOC-NOT-INSTALLED" status="spec/done" action="continue" actionstage="doc" audience="user">`vibe install` MUST refuse a `doc` package with a hint naming the warm-up command; documentation is warmed into the machine store with `vibe cache add` (§11), and the local reader and `vibe explain` read the store.</fact></item>
      <item><fact id="KIND-DOC-KNOWN-LIMIT" status="spec/done">Known limit: the store is per machine, not per project; a teammate who clones a project does not get the same documentation version automatically. The skill names the warm-up command; a project-level «consult this documentation» declaration is not part of this wave.</fact></item>
    </list>
    <p p="20"><fact id="KIND-DOC-DECISION" status="spec/done">**Decision:** documentation is a package kind, not a genre marker inside other kinds.</fact></p>
    <list ordered="false" p="21">
      <item><fact id="kind-doc-why" status="spec/done">**Why:** the owner's three requirements — local reading, a skill, a site — are met by one mechanism: local reading is a store warm-up, the skill is `[[skill]]` in the manifest, the site renders the same bytes. Keeping documentation out of `vibedeps/` keeps consumer trees thin and keeps tutorials out of an agent's `grep` over dependencies. The kind, not the document genre, drives behaviour because tools know the kind before they read a file: the index, the publish gate, `vibe init`, `vibe list`.</fact></item>
      <item><fact id="kind-doc-rejected" status="spec/done">**Considered and rejected:** documentation as kind-less content (the shape of the earlier phase-G draft) — tools could not tell it from a `flow` and could not forbid a boot snippet; materialising `doc` packages into `vibedeps/` by default — it bloats every consumer's committed tree; a fifth layout root `vibevm/vibedocs/` — it breaks the one-module layout law of PROP-052 for no gain, since addressing, scanners and the pivot already work over `vibevm/vibespecs/`.</fact></item>
      <item><fact id="kind-doc-revisit" status="spec/done">**Revisit when:** a consumer needs documentation in the committed project tree or reproducibly across a team's machines — observed as a BACKLOG request or a real `vibe install` refusal in the field. Then add a project-level declaration and a materialisation mode without changing the default.</fact></item>
    </list>
    <p p="22"><fact id="KIND-APP-LEAD" status="spec/done">**`app` — a standalone product.** A package of kind `app` is a product with its own deployment profile in the build, package and deploy planes of PROP-054; the site package `org.vibevm.doc/web` is of kind `app`.</fact></p>
    <list ordered="false" p="23">
      <item><fact id="KIND-APP-VS-TOOL" status="spec/done" action="continue" actionstage="doc" audience="author">The boundary with `tool` is mechanical: a `tool` lives in a project and runs through `vibe bin exec` by the lock file; an `app` runs nowhere in a consumer project and is built and deployed on its own.</fact></item>
      <item><fact id="kind-app-rejected" status="spec/done">**Considered and rejected:** treating the site as a `tool` — a site has no meaning as «execute in the project by the lock file».</fact></item>
      <item><fact id="kind-app-revisit" status="spec/done">**Revisit when:** a second `app` with a different launch mechanism appears.</fact></item>
    </list>
    <p p="24"><fact id="KIND-CODE-LAW" status="spec/done">In code the two kinds join the closed `PackageKind` enum and every exhaustive `match` grows a branch; a wildcard branch hiding a kind is forbidden. The index side (`vibe-index`, an open vocabulary with `Unknown(_)`) stays open — that is its own law. The behavioural wire vocabulary (`package_kind` in `formats/vocabularies.json`) widens through `cargo xtask codegen`, never by hand in the generated tree.</fact></p>
  </section>
  <section id="companion" title="3. The companion role outside family unison">
    <p p="25"><fact id="COMPANION-LEAD" status="spec/done">Documentation and its translations are *companions* of the packages they document: they share a name stem for the default case of officiality and nothing else.</fact></p>
    <list ordered="false" p="26">
      <item><fact id="COMPANION-NAME" status="spec/done" action="continue" actionstage="doc" audience="author">The official-by-default documentation of a subject `&lt;group&gt;/&lt;name&gt;` is named `&lt;group&gt;/&lt;name&gt;-docs`, in the same group. The core documentation is `org.vibevm.core/vibevm-docs`, the companion of the host coordinate `org.vibevm.core/vibevm`.</fact></item>
      <item><fact id="COMPANION-TRANSLATION-NAME" status="spec/done" action="continue" actionstage="doc" audience="author">The official-by-default translation of a documentation `&lt;group&gt;/&lt;docname&gt;` into a language `&lt;lang&gt;` is named `&lt;group&gt;/&lt;docname&gt;-&lt;lang&gt;`, where `&lt;lang&gt;` is a BCP-47 tag in lower case (`ru`, `pt-br`, `zh-hans`).</fact></item>
      <item><fact id="COMPANION-ANY-NAME" status="spec/done">Any other documentation or translation may carry any name in any group: the naming convention exists only for officiality by default, and the displayed name is the card's `title` (§7).</fact></item>
      <item><fact id="COMPANION-OUTSIDE-UNISON" status="spec/done">PROP-028 gains the role `&lt;family&gt;-docs` as a **companion**: it does not take part in the family's unison versioning, it keeps its own version line, and it expresses compatibility with its subject through the version constraint in `[[documents]]` (§4).</fact></item>
    </list>
    <p p="27"><fact id="COMPANION-DECISION" status="spec/done">**Decision:** a suffix in the subject's group, outside unison.</fact></p>
    <list ordered="false" p="28">
      <item><fact id="companion-why" status="spec/done">**Why:** a suffix in the same group is already the family rule beside `-lang` and `-mcp`; no new naming grammar is needed. Only the group's owner can publish into the subject's group, so officiality cannot be forged through a name. Family unison is the law for a *tested set* of code; prose and translations move in a different rhythm.</fact></item>
      <item><fact id="companion-rejected" status="spec/done">**Considered and rejected:** a subgroup `org.vibevm.world.docs/&lt;name&gt;` (the DefinitelyTyped shape) — it copies another owner's namespace by agreement, carries no subject version, does not work for third-party authors, and needs a new naming law; including `-docs` in family unison — every documentation edit would bump the family's code; encoding the language in the group (`org.vibevm.core.ru/…`) — the same subgroup mistake.</fact></item>
      <item><fact id="companion-revisit" status="spec/done">**Revisit when:** PROP-028 gains a fourth code role and suffixes get crowded — observed as a stem conflict in the registry.</fact></item>
    </list>
  </section>
  <section id="relation" title="4. The relation between documentation and its subject">
    <p p="29"><fact id="REL-DECLARED-BOTH-WAYS" status="spec/done">The relation is declared in both directions by manifest fields, not by name:</fact></p>
    <fence lang="toml" p="30"># in a package of kind = "doc"
[[documents]]
package = "org.vibevm.world/multi-user-planning"
version = "^1.0"

# in the subject, of any kind
[documentation]
primary  = "org.vibevm.world/multi-user-planning-docs"
official = ["org.vibevm.world/multi-user-planning-tutorials"]</fence>
    <list ordered="false" p="31">
      <item><fact id="REL-DOCUMENTS-REQUIRED" status="spec/done" action="continue" actionstage="doc" audience="author">`[[documents]]` is REQUIRED in a `doc` package, may list several subjects, and its `version` is a semver constraint.</fact></item>
      <item><fact id="REL-DOCUMENTATION-UNVERSIONED" status="spec/done" action="continue" actionstage="doc" audience="author">`[documentation]` in the subject names coordinates **without versions**; `primary` names at most one package, `official` any number.</fact></item>
      <item><fact id="REL-OFFICIAL-IS-CONVERGENCE" status="spec/done" action="continue" actionstage="doc" audience="author">**Official** documentation is the one whose edges converge: the subject named the package and the package declared the subject. **Community** documentation has only the edge from the documentation. An edge from the subject alone reads «not published or an error», and the site shows a warning.</fact></item>
      <item><fact id="REL-DEFAULT-CONVENTION" status="spec/done" action="continue" actionstage="doc" audience="author">**Default convention:** when the subject declares no `[documentation]`, the package `&lt;name&gt;-docs` in the same group counts as official and primary. A declared `[documentation]` replaces the convention entirely.</fact></item>
      <item><fact id="REL-VERSION-SELECTION" status="spec/done">For a subject version V the site shows the documentation versions whose `[[documents]]` constraint admits V, choosing the newest.</fact></item>
      <item><fact id="REL-WARMUP-CLOSURE" status="spec/done" action="continue" actionstage="doc" audience="user">Warming a `doc` package with `vibe cache add` warms its subjects too, so that `spec://` citations resolve offline.</fact></item>
      <item><fact id="REL-INDEX-FIELDS" status="spec/done">The relation fields enter the registry index entry (`title`, `abstract`, `documents`, `documentation`, `translates`, `media`; the `version_entry` vocabulary of `formats/vocabularies.json` widens through `cargo xtask codegen`), so that reverse links are built from `primary.jsonl` and never by downloading packages.</fact></item>
      <item><fact id="REL-REVERSE-QUERIES-SITE-SIDE" status="spec/done">Reverse questions — «who documents X», «which translations does Y have» — and officiality are computed on the site's side and in `vibe-doc` by one fold over `primary.jsonl` in memory at build time; the index gains no new routes or folders, and the local reader takes the same path offline.</fact></item>
      <item><fact id="REL-HOST-SUBJECT" status="spec/done">The subject `org.vibevm.core/vibevm` of the core documentation is a **project** coordinate (the host root is `[project]`, not a registry package — `design/host-as-package.xml`); `vibe check` verifies the form of a subject coordinate and does not require the subject to be selectable from a registry; the site knows the host coordinate from its configuration (§9).</fact></item>
      <item><fact id="REL-NO-OFFICIAL-FLAG" status="spec/done">Officiality is never stored as a flag: it is computed from the convergence of edges at every render. A field `official = true` in a manifest or an index is a design error.</fact></item>
      <item><fact id="REL-FIELD-PLACEMENT" status="spec/work">Placement in the manifest: `[[documents]]`, `[documentation]`, `[translates]` and `[media]` are top-level tables beside `[boot_snippet]` and `[[skill]]`; `title` and `abstract` sit inside `[package]` beside `description`. Recommended and drafted so; the owner's confirmation is open (vision §10, question 4).</fact></item>
    </list>
    <p p="32"><fact id="REL-DECISION" status="spec/done">**Decision:** two edges, officiality by convergence, an unversioned pointer from the subject, a default convention by name.</fact></p>
    <list ordered="false" p="33">
      <item><fact id="rel-why" status="spec/done">**Why:** the edge from the subject removes the owner's fear — three contributors cannot fight over «official», because the subject assigns it. The pointer carries no version because documentation almost always ships after the code. The default convention spares the subject a re-release for the obvious case. Precedent: the `documentation` field of `Cargo.toml`.</fact></item>
      <item><fact id="rel-rejected" status="spec/done">**Considered and rejected:** a capability `docs:&lt;coordinate&gt;` in `provides` — the capability grammar admits only kebab-case in both halves (`crates/vibe-core/src/capability_ref.rs`) and a coordinate does not fit; an ordinary `requires` dependency on the subject — a dependency does not mean «I document»; an officiality mark in the registry index outside the package bytes — a second source of truth that vanishes in local mode and private registries; the name convention alone — zero guarantees.</fact></item>
      <item><fact id="rel-revisit" status="spec/done">**Revisit when:** a third relation of the same nature appears beside `documents` and `translates` — then generalise into one relation table instead of adding fields.</fact></item>
    </list>
  </section>
  <section id="localization" title="5. Localization: one package per language">
    <p p="34"><fact id="LOC-PACKAGE-PER-LANGUAGE" status="spec/done" action="continue" actionstage="doc" audience="author">A translation of documentation is a separate package of kind `doc`:</fact></p>
    <fence lang="toml" p="35">[package]
name  = "vibevm-docs-ru"
group = "org.vibevm.core"
kind  = "doc"
title = "Руководство VibeVM"
abstract = "…"

[i18n]
canonical = "ru"                  # the package's language — the existing PROP-003 field, a BCP-47 tag

[[documents]]                     # the same subject as the source
package = "org.vibevm.core/vibevm"
version = "^1.0"

[translates]
package = "org.vibevm.core/vibevm-docs"
version = "^0.3"</fence>
    <list ordered="false" p="36">
      <item><fact id="LOC-LANGUAGE-FIELD" status="spec/done" action="continue" actionstage="doc" audience="author">The language of a `doc` package is the existing `[i18n].canonical` of PROP-003 §2.7 (default `en`); there is no separate `lang` field. `[i18n].available` of a `doc` package is empty by construction: a translation is another package.</fact></item>
      <item><fact id="LOC-NO-TRANSLATIONS-TABLE" status="spec/done">The source stores no list of its translations. Which translations a documentation has, the site and the local reader compute from the `translates` edges at every render — exactly as officiality from `documents` and `documentation`.</fact></item>
      <item><fact id="LOC-SIDECAR-NOT-APPLIED" status="spec/done">The sidecar layout of PROP-003 §2.7.1 does not apply to `doc` packages; the BCP-47 tags, the preference chain (`I18nDecl`, `project_preference_chain`, `resolve_localised`) and the lock file's `[meta].language_chain` are reused unchanged.</fact></item>
      <item><fact id="LOC-MIRROR" status="spec/done" action="continue" actionstage="doc" audience="author">A translation MUST mirror the source tree file for file: the same paths, the same anchors, the same fact identifiers, the same number and kinds of blocks. Adding or removing an anchor is forbidden. A set difference of anchors is a check error, not a warning.</fact></item>
      <item><fact id="LOC-NO-REVISION" status="spec/done">A translation stores neither a revision nor a hash of the source page (§14: a «since then» comparison needs a history the product does not keep). `vibe doc check --translations` verifies structure only — paths, anchors, count and kinds of blocks; whether the adaptation lags in meaning is a human's question at full reconciliation, and the site shows the adaptation's last read-aloud date from `reviews.toml`.</fact></item>
      <item><fact id="LOC-EXAMPLE-REF" status="spec/done" action="continue" actionstage="doc" audience="author">A translation MUST NOT author examples: it uses `example ref="&lt;id&gt;"` pointing at the source's example, so that command output is checked once, on the source (PROP-045 `##ROW-DOCVOCAB-EXAMPLE-REF`).</fact></item>
      <item><fact id="LOC-DOCUMENTS-MATCH" status="spec/done" action="continue" actionstage="doc" audience="author">The `documents` of a translation MUST equal the `documents` of its source; `vibe check` verifies it.</fact></item>
      <item><fact id="LOC-OFFICIAL-TRANSLATION" status="spec/done" action="continue" actionstage="doc" audience="author">An **official** translation is one that declared `translates` on the source and is published by the **same group** as the source under the name `&lt;docname&gt;-&lt;lang&gt;`; everything else is a community translation (§6).</fact></item>
      <item><fact id="LOC-SITE" status="spec/done">On the site the language is a path segment (§9), a selector stands on every page, the fallback to the source language is per page and marked, never a 404; `hreflang` and `x-default` are emitted; `llms.txt` exists per language.</fact></item>
      <item><fact id="LOC-LOCAL" status="spec/done" action="continue" actionstage="doc" audience="user">The local reader serves the same from the store; the preference comes from the project's `[i18n].preferred` or a launch flag.</fact></item>
      <item><fact id="LOC-NORMATIVE-STAYS" status="spec/done">Normative specifications stay in the specification's language: a `rule` shows the rule's text in the source language with a mark; translating normative text is outside this wave.</fact></item>
    </list>
    <p p="37"><fact id="LOC-DECISION" status="spec/done">**Decision:** one package per language, a block-for-block mirror, examples by reference.</fact></p>
    <list ordered="false" p="38">
      <item><fact id="loc-why" status="spec/done">**Why:** the owner's goals — different authors, different rhythms, officiality per language — are goals of ownership, and the unit of ownership in this project is the package. The mirror and reference-example rules take from the sidecar model what made it safe: matching anchors, per-page fallback, one source of command output.</fact></item>
      <item><fact id="loc-rejected" status="spec/done">**Considered and rejected:** sidecar files inside one package (PROP-003 as is) — the package grows with every language, a translator needs rights on the package, any translation edit bumps the whole documentation, and coverage cannot see staleness; a hybrid «official as sidecar, third-party as packages» — two mechanisms; a translation declared by the subject — a subject re-release per language; own examples in translations — divergent outputs per language.</fact></item>
      <item><fact id="loc-revisit" status="spec/done">**Revisit when:** a request to translate normative specs appears — observed as a translation package trying to mirror a spec tree instead of a documentation tree.</fact></item>
    </list>
  </section>
  <section id="discovery" title="6. Discovery and the official/community hierarchy">
    <p p="39"><fact id="DISC-BY-EDGES" status="spec/done">The site finds documentation and translations **by edges**: every package of kind `doc` in the index whose `documents` or `translates` points at a coordinate, from any group. A star appears only where the edge is confirmed from above.</fact></p>
    <table p="40">
      <tr>
        <td>Level</td>
        <td>Status</td>
        <td>Who confirms</td>
      </tr>
      <tr>
        <td><fact id="ROW-DISC-DOCS-LEVEL" status="spec/done">documentation of a subject</fact></td>
        <td><fact id="ROW-DISC-DOCS-STATUS" status="spec/done">★ primary · ★ official · community</fact></td>
        <td><fact id="ROW-DISC-DOCS-CONFIRMS" status="spec/done">the subject, through `[documentation]` or the `&lt;name&gt;-docs` convention in its own group</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-DISC-TRANS-LEVEL" status="spec/done">translation of a documentation</fact></td>
        <td><fact id="ROW-DISC-TRANS-STATUS" status="spec/done">★ official · community</fact></td>
        <td><fact id="ROW-DISC-TRANS-CONFIRMS" status="spec/done">the source documentation, through the `&lt;docname&gt;-&lt;lang&gt;` convention in its own group</fact></td>
      </tr>
    </table>
    <list ordered="false" p="41">
      <item><fact id="DISC-FOUR-COMBINATIONS" status="spec/done">All four combinations are visible: official documentation with an official translation; official with a community translation; community documentation with a translation its author named official; community documentation with a community translation. A star on a translation means «named by the author of this documentation», not «approved by the subject», and the tooltip says so in words.</fact></item>
      <item><fact id="DISC-THREE-SIGNALS" status="spec/done">Three signals agree and never contradict: the star, the caption «official» or «community», and the order «primary, official, community».</fact></item>
      <item><fact id="DISC-PUBLISHER-VISIBLE" status="spec/done">The publisher is always visible: the package's group is printed beside the title.</fact></item>
      <item><fact id="DISC-LANGUAGE-SELECTOR" status="spec/done">The language selector shows every language found: starred first, then community, each with its publisher.</fact></item>
      <item><fact id="DISC-SHELVES" status="spec/done">The shelves on a package page — primary, official extra, community — repeat the scheme for documentation; inside a shelf the same marks serve translations.</fact></item>
      <item><fact id="DISC-MACHINE-MIRROR" status="spec/done">The machine mirror: the page manifest carries the status on both levels (`primary`, `official`, `community`) and the language; `llms.txt` marks official items so that an agent prefers them but sees the rest; the index carries the relation fields, the language, `title` and `abstract`.</fact></item>
      <item><fact id="DISC-NO-MODERATION" status="spec/done">There is no moderation: the community shelf shows everything that was found; the protection against impersonation is the visible publisher; exclusion lists are deferred.</fact></item>
    </list>
    <p p="42"><fact id="DISC-DECISION" status="spec/done">**Decision:** completeness by edges, unambiguity by assignment from above, visibility by stars and order.</fact></p>
    <list ordered="false" p="43">
      <item><fact id="disc-why" status="spec/done">**Why:** the owner's word — unofficial work must be found and shown as community, and the hierarchy must be plain to see.</fact></item>
      <item><fact id="disc-rejected" status="spec/done">**Considered and rejected:** showing only official items; officiality marks in the index; moderation in this wave.</fact></item>
      <item><fact id="disc-revisit" status="spec/done">**Revisit when:** the first abuse of the community shelf is observed as a complaint to a package owner.</fact></item>
    </list>
  </section>
  <section id="card" title="7. The card: title, abstract, description, media">
    <p p="44"><fact id="CARD-FIELDS" status="spec/done" action="continue" actionstage="doc" audience="author">The package manifest gains card fields; for kind `doc` `title` and `abstract` are REQUIRED, for the other kinds optional, and `[media]` is optional for all:</fact></p>
    <fence lang="toml" p="45">[package]
title       = "VibeVM Manual"
description = "The operator's guide to vibe: install, lifecycle, registries, agents."
abstract    = """
What it covers, for whom, what it assumes known, what it leaves out.
Three to six sentences, one language — the package's own.
"""

[media]
icon    = "media/icon.png"       # square, 256–1024 px, PNG/JPEG/WebP, up to 256 KB
banner  = "media/banner.jpg"     # 3:1, recommended 1500×500, up to 1 MB
preview = "media/preview.png"    # 1.91:1, recommended 1200×630, up to 1 MB</fence>
    <list ordered="false" p="46">
      <item><fact id="CARD-TITLE" status="spec/done">`title` is the display name on shelves, in the selector, in the page heading and in the catalogue; uniqueness is not checked, identity stays the coordinate, and the publisher is shown beside it.</fact></item>
      <item><fact id="CARD-DESCRIPTION-AND-ABSTRACT" status="spec/done">`description` stays the one-line subtitle for lists and the page's meta description; `abstract` answers four questions — what it covers, for whom, what it assumes known, what it leaves out — and is bounded at about a thousand characters. The entry page does not restate the abstract; it inserts it through `derived kind="manifest-field"`.</fact></item>
      <item><fact id="CARD-NO-AUDIENCE-DECLARATION" status="spec/done">Audiences and languages are not declared on the card: they are derived from the page markup and the `translates` edges.</fact></item>
      <item><fact id="CARD-AUTHORSHIP" status="impl/done" action="continue" actionstage="doc" audience="author">A `doc` package MAY declare `authorship` in `[package]`: `human`, `ai` or `mixed` — who wrote the prose the package carries. It is metadata of the document, kept for the reader who filters a shelf by it; it is never an attribution of the commits or of the repository, whose authorship law is `spec://org.vibevm.core/vibevm/common/PROP-000#commits`. Absent means unknown: the site shows no badge and a filter by authorship leaves the package out of both named groups.</fact></item>
      <item><fact id="CARD-MEDIA-SOURCE" status="spec/done" action="continue" actionstage="doc" audience="author">Images are source files in the package tree, in PNG, JPEG or WebP; SVG is forbidden in this wave because it can carry scripts and the local reader serves the images of proprietary packages as they are. `vibe check` and the publish gate verify existence, format signature, proportions and size — by signature and dimensions, never by file extension. The limits are small on purpose: packages of ordinary kinds are materialised and committed at consumers.</fact></item>
      <item><fact id="CARD-MEDIA-ROLES" status="spec/done">`icon` shows in the package page header and on shelf cards; `banner` heads the package page; `preview` serves `og:image`, `twitter:image` with the `summary_large_image` card, and `image` in JSON-LD.</fact></item>
      <item><fact id="CARD-PLACEHOLDERS-GENERATED" status="spec/done">Placeholders are generated, never stored: a gradient or pattern for the banner and a glyph for the icon are computed from the hash of the coordinate, so a package looks the same on the site and in the local reader and different packages are distinguishable; the glyph depends on the kind — a book for `doc`, its own sign for every other kind; inline SVG at render time, no files, no network.</fact></item>
      <item><fact id="CARD-PREVIEW-COMPOSED" status="spec/done">When `preview` is absent, the site build composes one from the placeholder, the icon and the title — never by cropping the banner. One preview per package in the first wave; per-page cards are deferred.</fact></item>
      <item><fact id="CARD-TRANSLATION-INHERITS" status="spec/done">A translation inherits the images of its source unless it declares its own.</fact></item>
      <item><fact id="CARD-SITE-COPIES" status="spec/done">The site build copies images under hashed names for caching and never loads them from foreign addresses; the alt text is the `title`.</fact></item>
    </list>
    <p p="47"><fact id="CARD-DECISION" status="spec/done">**Decision:** a card like arXiv's and like a social profile — title and abstract in the manifest, a separate preview, generated placeholders.</fact></p>
    <list ordered="false" p="48">
      <item><fact id="card-why" status="spec/done">**Why:** the index and the shelf must show the title and abstract without downloading the package; the banner's and the link preview's proportions are incompatible, so the preview is its own option; generated placeholders follow the law that derived things are never kept by hand.</fact></item>
      <item><fact id="card-rejected" status="spec/done">**Considered and rejected:** showing `description` instead of a title; taking the title from the entry page; the abstract as a page section with a copy in the index; a static set of placeholders; a preview cropped from the banner; the field name `og_image` (more than Open Graph reads the image); SVG in this wave.</fact></item>
      <item><fact id="card-revisit" status="spec/done">**Revisit when:** a request for sanitised SVG icons; a request for multilingual titles inside one package — observed in BACKLOG.</fact></item>
    </list>
  </section>
  <section id="levels" title="8. Two levels of documentation">
    <list ordered="false" p="49">
      <item><fact id="LEVEL-ZERO" status="spec/done" action="continue" actionstage="doc" audience="author">**Level 0.** The site renders any published version of any package from its own bytes: the manifest as a reference page, the README, the boot snippet marked «read by the session», the specs with anchors and fact highlighting by address, the declared skills, binaries and MCP servers, the dependants, «explained in», «translated into», and the images of `[media]`. Versions sit in the side navigation; `latest` is an alias.</fact></item>
      <item><fact id="LEVEL-ZERO-MARKED" status="impl/done" action="continue" actionstage="doc" audience="author">The manifest of a level-zero rendering says so: `projection = true`, so a shelf can tell a page the site derived from a package's own bytes from a page an author wrote, and mark the first as generated. A bridge's rendering carries the two authorships the bridge keeps apart.</fact></item>
      <item><fact id="LEVEL-ONE" status="spec/done">**Level 1.** Extended documentation is a separate package of kind `doc`, any number per subject. The site stitches the two levels on one package page: the level-0 overview above, the documentation shelves below.</fact></item>
      <item><fact id="levels-why" status="spec/done">**Why:** level 0 is free for the author and covers the whole registry at once, as rustdoc covers every crate; level 1 is needed only where somebody wants more, and forces nobody else to write anything.</fact></item>
      <item><fact id="levels-rejected" status="spec/done">**Considered and rejected:** level 1 only — an empty site at the start; level 0 only — no room for tutorials and examples.</fact></item>
      <item><fact id="levels-revisit" status="spec/done">**Revisit when:** never within this wave; the levels are orthogonal.</fact></item>
    </list>
  </section>
  <section id="site" title="9. The site: addresses, sources, the host channel, hosting">
    <section id="addresses" title="9.1 Addresses">
      <p p="50"><fact id="SITE-MOUNT" status="spec/done" action="continue" actionstage="doc" audience="user">The site is mounted under the path `/doc` of the main domain. The language is the path segment after `/doc/`; the source language of a documentation carries no prefix. The address map is deterministic and needs no index:</fact></p>
      <fence p="51">spec://&lt;group&gt;/&lt;name&gt;@&lt;version&gt;/&lt;document&gt;#&lt;anchor&gt;
  → https://vibevm.org/doc/&lt;group&gt;/&lt;name&gt;/&lt;version&gt;/&lt;document&gt;/#&lt;anchor&gt;
spec://&lt;group&gt;/&lt;name&gt;/&lt;document&gt;#&lt;anchor&gt;
  → https://vibevm.org/doc/&lt;group&gt;/&lt;name&gt;/latest/&lt;document&gt;/#&lt;anchor&gt;
the ru translation of the same page
  → https://vibevm.org/doc/ru/&lt;group&gt;/&lt;name&gt;/&lt;version&gt;/&lt;document&gt;/#&lt;anchor&gt;</fence>
      <list ordered="false" p="52">
        <item><fact id="SITE-TRAILING-SLASH" status="spec/done">A page address ends with a slash (`…/&lt;document&gt;/`); the projections lie beside it as files (`…/&lt;document&gt;.md`, `…/&lt;document&gt;.xml`); an address without the slash receives a 308 redirect on the web and locally. The static adapter of the chosen stack generates correctly in this mode only (§12).</fact></item>
        <item><fact id="SITE-CANONICAL-LATEST" status="spec/done">Pages of older versions carry `rel=canonical` to `latest` within their language. The local reader uses the same scheme with another origin.</fact></item>
        <item><fact id="SITE-VERSION-SHOWS-CURRENT" status="spec/done" action="continue" actionstage="doc" audience="user">An address with a version number always shows the **current** content of that version in the registry: one number may be published ten times a day, and the site shows the last publication. There are no permanent links to past publications — they do not exist in the registry either (§14).</fact></item>
        <item><fact id="VERSION-OVERWRITE-POLICY" status="impl/done" action="continue" actionstage="doc" audience="author">Until the owner decides otherwise (ruling 2026-09-14), every package this repository publishes carries the number `1.0.0`, and a change is published under the same number in place: the registry keeps no past publication, the site shows the current content of the number, and the changelog records what changed by date rather than by a new number. A consumer who pinned the number keeps the bytes the lock file recorded, and a fresh install gets the current ones (`##SITE-VERSION-SHOWS-CURRENT`, PROP-002 `##PUBLISH-MUTABLE-VERSIONS`).</fact></item>
        <item><fact id="SITE-ONE-SITE" status="spec/done">The whole domain is one site: the landing `vibevm.org/` and `vibevm.org/ru/` and the documentation `/doc/…` are built by one build from one package. The root machine files — `robots.txt`, `llms.txt`, `llms-full.txt`, `sitemap.xml`, `feed.xml`, the IndexNow key file — are generated by the same build, keeping the addresses and content of the present landing: `robots.txt` is one per domain, ASCII-only, an allow-list of crawlers, and carries `Sitemap:` lines for `/sitemap.xml` and `/doc/sitemap.xml`; the root `llms.txt` opens with the paragraph disambiguating the name «VibeVM» and links `/doc/llms.txt`; the documentation publishes its own files under `/doc/`: `/doc/sitemap.xml`, `/doc/llms.txt`, `/doc/llms-full.txt`, `/doc/manifest.json`. The root `llms-full.txt` is the landing, not a copy of the documentation.</fact></item>
        <item><fact id="site-addresses-why" status="spec/done">**Why:** a path under the main domain shares its authority for SEO; the same scheme on the web and locally means a link in the documentation works in both worlds; the language in the path, not in a parameter, is how it is indexed and cited; the landing is already indexed and carries the name disambiguation for AI crawlers, so its addresses and root files survive the move byte for byte where possible and are checked by the landing parity test.</fact></item>
        <item><fact id="site-addresses-rejected" status="spec/done">**Considered and rejected:** a subdomain `docs.vibevm.org`; the language in a query parameter; the language in a subdomain; two sites on one domain — an Astro landing plus a Qwik documentation with a three-line contract between repositories.</fact></item>
        <item><fact id="site-addresses-revisit" status="spec/done">**Revisit when:** never within the wave; changing the address scheme is a breaking change with redirects.</fact></item>
      </list>
    </section>
    <section id="sources" title="9.2 Sources and the host channel">
      <p p="53"><fact id="SITE-TWO-SOURCES" status="spec/done">The site has two sources, both configured in the form of the project's `[[registry]]`:</fact></p>
      <list ordered="false" p="54">
        <item><fact id="SITE-SOURCE-REGISTRY" status="spec/done">**the package registry** — one, by default the GitHub organisation `vibespecs`; its index is a change feed: the site polls `repomd.json` and `primary.jsonl` (or receives a webhook), compares the pairs «coordinate, content hash» with what is rendered, and rebuilds only what changed;</fact></item>
        <item><fact id="SITE-SOURCE-HOST" status="spec/done">**the host source repository** — one, by default `github.com/vibevm/vibevm`; from it the host `org.vibevm.core/vibevm` is rendered by the **current state of the branch `main`**, and only by it.</fact></item>
      </list>
      <list ordered="false" p="55">
        <item><fact id="SITE-HOST-CHECKOUT" status="spec/done">The host channel is a **checkout on disk**: the deploy runs `git checkout main &amp;&amp; git pull` in the renderer's directory, and the renderer reads the host from there as a *project* (level 0: `README.md`, `vibevm/vibespecs/**`, the boot snippet, the `[project]` manifest) and the core documentation as an in-tree package through the project-local registry `vibevm/vibepacks`. The host is not published to the registry for the sake of rendering, no git source of packages is used, and `vibe cache add` is not run on the server. The host has no release tags; the release is what `main` holds now.</fact></item>
        <item><fact id="SITE-RENDER-KEY" status="spec/done">The render cache is keyed by an **internal content hash** of the source (the store's recorded hash for store packages, a recomputation for the checkout), never by `(group, name, version)`: in-tree packages keep their version number while their content moves. The key is never shown to a reader (§14).</fact></item>
        <item><fact id="SITE-RENDER-IDEMPOTENT" status="spec/done">Rendering is idempotent and cached; a render error is shown as the version's page; reverse links — dependants, «explained in», «translated into» — come from the index and from the maps packages carry (`vibe specmap`), never by hand.</fact></item>
        <item><fact id="SITE-HOST-POLL" status="spec/work">The site polls the host branch and, when it changed, rebuilds the host render with a debounce, keeping one current render plus the previous one until the new one completes; no history of states is kept. The poll interval is a configuration value of the site build; the recommended default is once an hour (campaign atom A5.1, the measured render cost of the phase-0 spike A0.28 is the evidence), pending the owner's word.</fact></item>
        <item><fact id="SITE-MIRRORS-FUTURE" status="spec/done">Mirrors and a second registry are the future; the owner opens them.</fact></item>
        <item><fact id="site-sources-why" status="spec/done">**Why:** this is how docs.rs stands over the crates.io index, and cheaper here — a version is frozen by content hash. The host is not a registry package, and forcing it to become one for the site's sake would be a false generalisation; the owner's word is «the repository named in the settings, GitHub by default».</fact></item>
        <item><fact id="site-sources-rejected" status="spec/done">**Considered and rejected:** a full rebuild on a schedule; publishing the host into the registry for the render; reading the host from a mirror; rendering by release tags (there are none); keeping host states by tree or commit hash (a feature that needs history — §14).</fact></item>
        <item><fact id="site-sources-revisit" status="spec/done">**Revisit when:** the owner opens mirrors or a second registry.</fact></item>
      </list>
    </section>
    <section id="hosting" title="9.3 Hosting and deploy">
      <p p="56"><fact id="SITE-HOSTING-FORM" status="spec/done">The site lives on the same server and domain as today's landing, by the existing «bring up the site» runbook of the owner's private infrastructure document. **The content of that document — addresses, ports, traffic scheme, VPN — is never transferred into this repository, its campaign zone or its commits**; only the form of the decision is recorded here.</fact></p>
      <list ordered="false" p="57">
        <item><fact id="SITE-TWO-CONTAINERS" status="spec/done">Two containers instead of today's one. Serving: a stock `nginx:alpine` with a volume holding the rendered site for the whole domain — `/`, `/ru/`, `/doc/…`, the root machine files. Rendering: `vibevm-site-renderer`, an image in which `vibe` is built from the current `main` and the shell is built; it fills the volume with `vibe doc build-site` (level 0 and the documentation from the registry and the host) and the static Qwik build. In the first wave the renderer runs from the deploy script after the pattern of today's landing script (`git pull` of the checkout → `docker compose run --rm vibevm-site-renderer` → `docker compose up -d &lt;serving service&gt;`); a timer or an index webhook is the second wave.</fact></item>
        <item><fact id="SITE-CUTOVER" status="spec/done">Until the new site is ready the domain is served by the present Astro landing from `vibevm-org`. The cutover is one step: the serving container of the new site takes the place of the landing's container — the same compose service and the same port, so that the host nginx is not touched — after a green landing parity test and a local rehearsal of the full stack. Rollback is restoring the previous service. Neither cutover nor rollback touches the host nginx, the ports, the certificates, the neighbours' compose blocks or the VPN: that is the rule of the runbook itself and the boundary of the owner's VPN directive.</fact></item>
        <item><fact id="SITE-CONTAINER-NGINX" status="spec/done">The site's container nginx inherits `vibevm-org/nginx.conf`: `charset utf-8` with `charset_types` for the text types; `absolute_redirect off; port_in_redirect off;` (TLS terminates outside); `expires -1` for HTML and an immutable cache for the hashed asset directories and `/fonts/`; the redirect `/en/` → `/` (301); `X-Content-Type-Options nosniff`; `Referrer-Policy strict-origin-when-cross-origin`; `X-Frame-Options SAMEORIGIN`; `error_page 404`; `try_files` with the directory index; and `location = /q-manifest.json { return 404; }` (§12).</fact></item>
        <item><fact id="SITE-DEPLOY-MODEL" status="spec/done">Deploy is «a git checkout on the server plus `docker compose up -d --build`»: the checkout is the vibevm repository (the renderer needs `vibe` from source and the site package lives in its tree); the image is built on the server and pushed nowhere; the run is one command over SSH from the developer machine with the native Windows OpenSSH (the Git Bash `ssh` swallows output). After a deploy: `curl -sI https://vibevm.org/` and `/doc/` answer 200, no redirect carries `Location: http://`, `/llms.txt` is served with `charset=utf-8`, and IndexNow receives the changed URLs.</fact></item>
        <item><fact id="SITE-WHO-COMMITS-WHAT" status="spec/done">The renderer's `Dockerfile` and `docker/nginx.conf` are in the package `org.vibevm.doc/web` (build configuration, not an artifact); the compose services, the deploy script, the checkout switch and the private document's entry are done on the server by the owner's hand or under his explicit supervision; in `vibevm-org` only the final decommission commit lands after the cutover, by the owner's word.</fact></item>
        <item><fact id="SITE-DNS-TLS-EXIST" status="spec/done">DNS, TLS and the domain's ports already exist; nothing new is issued; the domain is not behind Cloudflare.</fact></item>
        <item><fact id="site-hosting-why" status="spec/done">**Why:** the owner's hosting exists, is documented and works; one site on the domain means one serving container and no proxy between containers; the renderer as a separate container because `vibe` (Rust) and Qwik (Node) must not live in the serving image; taking the landing container's place with the same service and port is the only way to switch the domain without touching the host nginx the VPN depends on.</fact></item>
        <item><fact id="site-hosting-rejected" status="spec/done">**Considered and rejected:** a separate domain or subdomain; third-party static hosting (domain authority, an existing infrastructure); edits to the host nginx; two sites on the domain with a `/doc/` proxy between containers; building the image on the developer machine and pushing it to an image registry; the `vibevm-org` checkout as the site source after the cutover.</fact></item>
        <item><fact id="site-hosting-revisit" status="spec/done">**Revisit when:** a move to another hosting or a CDN; a second server or a mirror.</fact></item>
      </list>
    </section>
    <section id="reader" title="9.4 The reader">
      <p p="58"><fact id="READER-LEAD" status="spec/done">A documentation page (level 1; the README and specs of level 0 in the same reader) behaves as a reader for long texts. The normative part of that behaviour:</fact></p>
      <list ordered="true" p="59">
        <item><fact id="READER-NUMBERED-BLOCKS" status="spec/done" action="continue" actionstage="doc" audience="user,agent">**Numbered blocks.** Every flow block of the pivot — paragraph, list, table, fence, quote, `example`, `rule`, `note`, `figure`, `prompt`; a heading keeps its own id and is not numbered (clarified 2026-09-12 at implementation, following `PIPE-NUMBERING`) — receives an ordinal number and the id `pNN` **at build time** in the Rust pipeline (§10), never by a client script. The number is the block's position in the **current** text of the page, counted before `when` filtering, so that `p12` names the same block in the build for any platform and agent and in every translation; gaps in the rendered output are accepted. The number appears in the HTML island, in the `.md` and `.xml` projections (as `[p12]` at the start of the block) and in `llms-full.txt`, so a human and an agent cite one place. After an edit an old `#p12` may point at a neighbouring block, as a link to a file line does after an edit; this is accepted and nothing tries to «remember» it. Headings keep their named anchors `{#id}` above the number; named anchors are immutable and renamed only by tombstone, positional `pNN` live by the current text.</fact></item>
        <item><fact id="READER-LANGUAGE-SWITCH-KEEPS-PLACE" status="spec/done">**Switching the language keeps the place.** The language selector leads to the same page in the other language **with the same fragment** (`#pNN` or `#id`), which the block-for-block mirror of §5 makes possible. When the page does not exist in the chosen language, the site serves the source language under the chosen language's address as a statically materialised fallback (`&lt;html lang&gt;` of the source, `rel=canonical` to the source page, `noindex`), shows a bilingual notice «this page is only in … so far» once per session, and rewrites internal links so navigation stays in the chosen language. The chosen language is remembered in the cookie `lang` for 365 days — the same name the landing uses, so the domain root and `/doc/` remember one choice.</fact></item>
        <item><fact id="READER-SETTINGS" status="spec/done">**Reading settings** — theme (dark, light, system), font size, column width, anchors on or off — are kept in `localStorage` (in embedded mode by the host through `postMessage`); the theme is applied before the stylesheet loads to avoid a flash. The default theme is the system's, with a manual switch.</fact></item>
        <item><fact id="READER-NO-AUTOSCROLL" status="spec/done">**Return to the reading place.** The nearest anchor above the top of the viewport is remembered while scrolling; on the next visit the reader **does not** scroll by itself — a «return to place» button appears and disappears as soon as the reader scrolls past the first heading.</fact></item>
        <item><fact id="READER-FOR-AGENT" status="spec/done">**The «for agent» button** shows the `spec://…#pNN` address of the current place, the links to the `.md`, `.xml` and `llms.txt` of the package, and copies with one click. In embedded mode the `prompt` block's «hand to agent» button sends the prompt text to the host (§11).</fact></item>
        <item><fact id="NAV-PINNED" status="impl/done" action="continue" actionstage="doc" audience="author">A documentation package MAY declare `[navigation]`: `pinned`, the document paths the site and the local reader list first, in the order given; and `[[navigation.section]]`, one row per top-level folder of the page tree with the title the navigation shows for it. Pinning changes only where the named pages stand; every other page keeps the manifest's order. A pinned path that names no page is an error of `vibe check`.</fact></item>
        <item><fact id="READER-META-AND-PRINT" status="spec/done">The page's meta block shows the publisher, the package version and whether it is `latest`, the publication date, for a translation the package it adapts, the audiences, the reading time, and the `.md`, `.xml` and «for agent» links. A print style prints without panels, with block numbers and with link addresses in footnotes. The page carries exactly two dates: when it was rendered and when it was last read aloud.</fact></item>
      </list>
      <p p="60"><fact id="READER-DESIGN-PROVISIONAL" status="spec/work">The visual language (tokens, fonts, components) and the remaining reader details are design, not contract: they live in the vision (`design/documentation-vision.xml`, D-21 and D-22) as a working hypothesis until the design review after the first live render, and the normative floor for the shell is in §12.</fact></p>
    </section>
    <section id="analytics" title="9.5 Analytics">
      <p p="61"><fact id="SITE-ANALYTICS" status="spec/done">Public documentation pages carry the same self-hosted first-party Umami tag as the landing — the same website id, moved from the landing's base layout into the site configuration; the paths `/u/s.js` and `/u/e` are served by the domain, the site does not touch them. Nothing beyond: no GA, no Metrika, no consent modal — Umami runs without cookies and personal data. The local reader and the embedded mode carry no tag.</fact></p>
      <list ordered="false" p="62">
        <item><fact id="site-analytics-rejected" status="spec/done">**Considered and rejected:** an own analytics instance; GA and Metrika; reading events (which paragraphs are read) — deferred.</fact></item>
        <item><fact id="site-analytics-revisit" status="spec/done">**Revisit when:** a request for events (search, copying an address for an agent).</fact></item>
      </list>
    </section>
  </section>
  <section id="pipeline" title="10. The content pipeline: a Rust library and thin surfaces">
    <p p="63"><fact id="PIPE-LIBRARY" status="spec/done">Everything with content lives in a Rust library, working name `vibe-doc`: the HTML backend of the pivot that produces a page's «island»; the JSON page manifest; `llms.txt` of every tier and language; the Markdown and XML projections; the checks of examples, citations, translations, coverage, media and style; placeholder and preview generation; the reading of sources (the store, the lock file, a registry, the checkout). The surfaces above it — the CLI `vibe doc build | serve | check | manifest | todo | surface | diff | shell install`, the MCP tools, the HTTP server — are projections of the library (PROP-000 §21).</fact></p>
    <list ordered="false" p="64">
      <item><fact id="PIPE-SHELL-PARSES-NOTHING" status="spec/done">The Qwik shell parses nothing: it receives the island as finished HTML and its data by a JTD contract with generated TypeScript types. Every wire between Rust and TypeScript is a JTD schema registered in `formats/REGISTRY.toml`; the types are generated by `cargo xtask codegen` (PROP-000 §16, PROP-044). Launch parameters reach the page as a non-executable block `&lt;script type="application/json" id="vibe-doc-config"&gt;`, so the content security policy needs no `'unsafe-inline'`.</fact></item>
      <item><fact id="PIPE-CRATES" status="spec/done">Crates: `vibe-doc` (the library) → `vibe-doc-shell` (the embedded shell, §12) → `vibe-doc-server` (the local HTTP server, §11). The server repeats the form of `vibe-index` (axum 0.8, one router builder, RFC 7807 errors, `oneshot` tests) without importing its code; axum never enters `vibe-doc`, `vibe-mcp` or the site generator. Each new crate follows the campaign's new-crate checklist: workspace membership, `#[spec]` marks, `specmap.toml` entry, `conform` gating, README, the self-check panel.</fact></item>
      <item><fact id="PIPE-EDGES-HOST-SIDE" status="spec/done">The `rule` element is read on the **host side**: a host module scans documentation pages and injects into the traceability engine through its public `CodeScanner` seam (`build_with_scanner`, `check_with_scanner`, `write_with_scanner`, `CompositeScanner`). The engine and its vendored copies are not edited and `sync-engines` is not run. Documentation pages lie **outside** `spec_roots`, the page-scan policy lives in a host file rather than in `specmap.toml`, and the scanner is injected at both points of `xtask/src/specmap.rs` — the main pass and the resolve gate — so the coverage gate cannot be green by emptiness. The edge's tail is a `CodeItem` with the sentinel `crate_name = "&lt;doc&gt;"`, after the precedent of the JTD scanner's `"&lt;schema&gt;"`; the engine's wire schema does not change.</fact></item>
      <item><fact id="PIPE-NUMBERING" status="spec/done">Block numbering is a pure function `number_blocks(&amp;SpecDoc) -&gt; Numbering` in `vibe-doc`, called after `derived` blocks are expanded and before `when` filtering and the backends; `BlockPath` is the section path plus the block index; the backends of `vibe-doc` take `(doc, numbering)` side by side; the pivot's own `to_xml` and `to_markdown` do not change and `Numbering` never enters the pivot. Every flow block is numbered; list items, table cells, the children of `prompt` and headings are not.</fact></item>
      <item><fact id="PIPE-EXAMPLE-RUNNER" status="spec/done">**The example runner** (`vibe doc check --examples`) runs every `example` against the built binary and compares exactly after the declared normalisation; there are no match templates — every non-deterministic line is closed by a named `replace` rule visible in review. A page carries `run`, `expect` (stdout) and an optional `stderr`; the attribute `exit` defaults to `0`; an absent `stderr` asserts «stderr is empty». A fixture is a directory with a tree and an `example.toml` declaring the normalisation rules and the map «`--json` document → JTD schema» by the `command` field. The command's working directory is a fresh sandbox with a copy of the fixture, never the source tree; the command names no paths; isolation is one variable `VIBE_SETTINGS` in the native path spelling plus `NO_COLOR`; the behavioural variables (`VIBE_OFFLINE`, `VIBE_UNATTENDED`, `VIBE_INVOKED_BY`, `VIBETERM`, `VIBEFRAME`) are cleared and the flags stand in the example itself; a tripwire checks that the real `~/.vibe` and the source tree stayed unchanged; stdout and stderr are captured separately; examples document the non-TTY branch of the product and interactive prompts are described in prose; a `--json` stream is parsed document by document and each is validated by the fixture's schema map with a JTD validator written in `vibe-doc` (none exists in `vibe-wire`, whose schemas are codegen input); a document without a schema is reported as unchecked, never as passed. Normalisation: `&lt;TMP&gt;`, `&lt;HOME&gt;`, `&lt;REPO&gt;`, slashes, CRLF in expected files, `vibe &lt;VERSION&gt;` (package versions untouched), ANSI, block sorting by a declared line form, the fixture's local `replace` rules; the order is path replacements before slash unification and sorting after all replacements. Loosening a comparison to turn a check green is forbidden: the normalisation or the product is fixed.</fact></item>
      <item><fact id="PIPE-DERIVED" status="spec/done">`derived` blocks — CLI help, JTD schemas, manifest fields — are generated at build time from the product and are never committed as page text; a divergence is red except under an explicit `--accept`. `expect` is committed as golden output.</fact></item>
      <item><fact id="pipe-why" status="spec/done">**Why:** the omnichannel law. If the site parsed Markdown anew, anchors and facts would lose their identity and the local and public renders would diverge.</fact></item>
      <item><fact id="pipe-rejected" status="spec/done">**Considered and rejected:** a static generator on the TypeScript side that reads the sources itself.</fact></item>
      <item><fact id="pipe-revisit" status="spec/done">**Revisit when:** never within the wave.</fact></item>
    </list>
  </section>
  <section id="local" title="11. The local reader and the embedding contract">
    <p p="65"><fact id="LOCAL-SERVE" status="spec/done" action="continue" actionstage="doc" audience="user">`vibe doc serve` starts an HTTP server **on 127.0.0.1 only**, serves the shell and, on every request, glues in the island rendered from the machine store, the current project's lock file or a private registry. The language preference comes from the project's `[i18n].preferred` when present. The mode is fully autonomous: no request to vibevm.org, no external CDN or fonts, everything in the bundle. The server serves files only from known roots (the store, the shell), without path traversal and without directory listing, with a content security policy naming no external source.</fact></p>
    <list ordered="false" p="66">
      <item><fact id="LOCAL-CSP" status="spec/done">The policy is sent as a header, not a `&lt;meta&gt;`: `default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; media-src 'self'; object-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors &lt;origin&gt;`, where `frame-ancestors` names only the origin of the host that launched the reader — the launch parameter `vibe doc serve --frame-ancestor &lt;origin&gt;`, because a webview's origin changes from window to window — and is `'none'` without the parameter. The reader has no CORS layer at all and sends `x-content-type-options: nosniff`. The public site is not embeddable in frames.</fact></item>
      <item><fact id="LOCAL-STATIC" status="spec/done">Shell statics are served through `ServeDir` with `append_index_html_on_directories(false)` for a shell from the store, and by exact name match for the embedded shell; pages are served only by the server's own routes; hashed names get `cache-control: immutable`, pages `no-store`. The form `data_dir.join(&lt;capture&gt;)` of a percent-decoded path capture is never repeated in the reader.</fact></item>
      <item><fact id="LOCAL-WARMUP" status="spec/done" action="continue" actionstage="doc" audience="user">The store is warmed with `vibe cache add &lt;coordinate&gt;` from a registry, or `vibe cache add --offline &lt;coordinate&gt;` from a project root whose project-local registry holds the package in-tree; the sources of `vibe-doc` are the store (`lookup`, `list_all`), the lock file (`Lockfile::read`, `slot_abs_path`), a registry (`resolve_and_fetch`) and a checkout (`LocalRegistry`).</fact></item>
      <item><fact id="LOCAL-OFFLINE-SHELL" status="spec/done">When the instance's shell pin names a shell that is not in the shell store and there is no network, the reader falls back to the bare shell and warns; it never contacts the network without consent (§12).</fact></item>
      <item><fact id="LOCAL-EMBEDDING-CONTRACT" status="spec/done">**The embedding contract for a webview:** relative addresses and a base path set at launch; the host opens an address through the URL fragment or `postMessage` `{ "open": "spec://…" }`; the reader sends `{ "openFile": "&lt;path&gt;" }` when a local link is clicked, so the host application opens the file in its editor; the theme is a launch parameter and `{ "theme": "dark" | "light" }` on the fly (a VS Code plugin follows the editor's theme); reading settings travel as `{ "settings": {…} }` in both directions; the language comes from `[i18n].preferred` or a parameter; the «hand to agent» button of a `prompt` block sends `{ "prompt": "&lt;text&gt;" }` outward and the host decides what to do with it; no external resources.</fact></item>
      <item><fact id="LOCAL-NO-TELEMETRY" status="spec/done">The local reader publishes nothing, contacts nothing, and carries no analytics tag.</fact></item>
      <item><fact id="local-why" status="spec/done">**Why:** this is how the documentation of proprietary packages is read; how a VS Code plugin gets the interface through an iframe; how content never leaves the machine and is never visible to other users of the machine.</fact></item>
      <item><fact id="local-rejected" status="spec/done">**Considered and rejected:** a separate «local application» with its own code; binding to `0.0.0.0`.</fact></item>
      <item><fact id="local-revisit" status="spec/done">**Revisit when:** the owner opens work on the VS Code plugin and wants a native interface instead of a webview.</fact></item>
    </list>
  </section>
  <section id="stack" title="12. The web stack and the shell">
    <p p="67"><fact id="STACK-QWIK" status="spec/done">The site is TypeScript under the `typescript-ai-native` discipline, on Qwik 2.0 beta (next.qwik.dev), pinned exactly together with the versions of Node and pnpm: `@qwik.dev/core` and `@qwik.dev/router` `2.0.0-beta.43` (the 2.0 line lives under the npm tag `beta`; `latest` is 1.x and is not used), Vite `8.2.1`, Node `24.18.0`, pnpm `10.33.2`, lockfile v9, `packageManager` in `package.json`; the static adapter is `ssg` (`@qwik.dev/router/adapters/ssg/vite`). One shell code base, two adapters: static for the server (every route prerendered, the island glued at build time) and embedded for `vibe`.</fact></p>
    <list ordered="false" p="68">
      <item><fact id="STACK-ONE-BASE" status="spec/done">One site is built with `base: "/"`, and `/`, `/ru/` and `/doc/…` are route directories; the embedded adapter is a separate configuration with `base: "/doc/"` and the documentation routes at the root, without the landing routes.</fact></item>
      <item><fact id="STACK-PAGE-COUNT-GATE" status="spec/done">The static generator silently under-generates with exit code 0 (`trailingSlash: false` yields one page of seven; a wrong `base` yields none and an empty sitemap), so the site build gate counts pages — the generator's `Generated: N pages` against the page manifest — and never trusts the exit code.</fact></item>
      <item><fact id="STACK-BUILD-HYGIENE" status="spec/done">`q-manifest.json` is removed from the build output and answered 404 by nginx; the starter's `public/manifest.json` is rewritten without a foreign `$schema`; `pnpm-workspace.yaml` declares `allowBuilds` with `sharp: false` and `@parcel/watcher: false`; the base path and every path in the environment and arguments of build scripts are passed in a form MSYS does not rewrite (`MSYS_NO_PATHCONV=1` or values without a leading `/`) — a rule for `DEV-GUIDE.md`.</fact></item>
      <item><fact id="STACK-WORKSPACE" status="spec/done">The package `org.vibevm.doc/web` is one pnpm workspace of two parts: `design/` — tokens, themes, fonts and components; `site/` — the Qwik application with the landing routes (`/`, `/ru/`) and the documentation routes (`/doc/…`). Header, footer, theme, language selector and fonts are shared components, not copies. The package is published as source only; built assets never enter it (PROP-024 §2.2).</fact></item>
      <item><fact id="STACK-FLOOR" status="spec/done">The web package passes the discipline's seven-step floor before every commit (`prettier → tsc → tests → eslint → conform → specmap → test-gate`), invoked from the host root through the host's built `typescript-ai-native` binary with `--path`; unit tests are `.test.ts` without JSX; component behaviour is proven by the island parity test on the Rust side and by the build; the `tsconfig` is the full floor of the discipline guide plus the Qwik blocks (`jsx`, `jsxImportSource`, `moduleResolution: "Bundler"`, `paths`), with `exactOptionalPropertyTypes` kept; `any`, unchecked `as`, `!` and `@ts-ignore` are forbidden in domain code; the resolution of the discipline's eslint plugin outside the repository layout is decided when the package is initialised.</fact></item>
      <item><fact id="STACK-DESIGN-FLOOR" status="spec/done">The design floor of the shell: colours only through semantic tokens (`--bg`, `--text`, `--text-2`, `--line`, `--accent`, …) — a colour literal in a component is a review error; two value maps (light, dark) in one palette file, switched by `prefers-color-scheme` and `[data-theme]`; both themes pass an APCA contrast audit as a floor step, with thresholds by role — body text Lc ≥ 75, secondary and tertiary text ≥ 60, interactive outlines ≥ 45; separators and the block numbers at reduced opacity are decorative and outside the gate; the APCA formula is the project's own implementation of the published algorithm (the reference library is AGPL and does not enter the tree); no Tailwind anywhere on the site; fonts self-hosted in the bundle as separate Latin and Cyrillic woff2 subsets with `unicode-range`, no external font service; motion only where meaningful and off under `prefers-reduced-motion`.</fact></item>
      <item><fact id="STACK-NODE-SERVER-ONLY" status="spec/done">Node is needed only for the server build, in Docker after the landing's pattern: a build stage on a Node image builds the shell and renders the pages, the serving stage is stock nginx (§9.3). A reader's machine needs no Node.</fact></item>
    </list>
    <p p="69"><fact id="STACK-SHELL-EMBEDDING-PROPOSED" status="spec/work">**The embedding mechanism (proposed; the owner asked to see it before confirming — §1).**</fact></p>
    <list ordered="true" p="70">
      <item><fact id="SHELL-XTASK-EMBED" status="spec/work">A step `cargo xtask embed-doc-shell` builds the web package from source (`pnpm build` with the embedded adapter) and places the result — the route template with the island slot, scripts, styles — in a directory the crate `vibe-doc-shell` includes into the binary through `include_dir` (0.7.4, MIT, four dependencies; `rust-embed` reads from disk in debug builds) behind the feature flag `embedded-shell`; a `build.rs` declares `rerun-if-changed=shell`, stops under the feature when `shell/index.html` is missing with the recipe in the message, and exports the shell's digest as the compile-time constant `VIBE_DOC_SHELL_SHA256`.</fact></item>
      <item><fact id="SHELL-RELEASE-BUILD" status="spec/work">A release build of `vibe` enables the flag and fails when the shell is absent. A plain `cargo build` without Node compiles with a fallback shell — bare HTML without scripts.</fact></item>
      <item><fact id="SHELL-INSTALL-COMMAND" status="spec/work">A build from source (`vibe self install`, first-run) does not stay with the bare shell forever: on the first `vibe doc serve` the reader offers to download the shell matching its version from the release assets; `vibe doc shell install [--assume-yes]` downloads it with consent after the pattern of `install`, verifies the digest against the pin, and stores it in the shared content-addressed directory `~/.vibe/opt/vibevm/doc-shell/&lt;sha256&gt;/` — outside the immutable version instance, which carries a `DOC-SHELL.lock.toml` written at staging. On refusal or offline the fallback shell works. Downloading happens only by explicit consent, never automatically.</fact></item>
      <item><fact id="SHELL-SERVE-SOURCES" status="spec/work">`vibe doc serve` serves the shell statics from the binary or from the shell store and glues the island rendered by the Rust pipeline into every page; one `Shell` type with three sources and a `provenance()` the command can report.</fact></item>
      <item><fact id="SHELL-PIN" status="spec/work">The shell's coordinate and digest are pinned beside `vibe`; the self-check compares the embedded shell with the pin; `vibe doc serve` can say what it carries.</fact></item>
      <item><fact id="SHELL-RELATIVE" status="spec/work">The shell is built with a configurable base path and relative addresses; it has no external scripts.</fact></item>
      <item><fact id="SHELL-PARITY-TEST" status="spec/work">A parity test runs both adapters over one package and compares the island byte for byte.</fact></item>
      <item><fact id="SHELL-RELEASE-ASSET" status="spec/work">In a release the shell is a **separate asset** `vibevm-doc-shell-&lt;version&gt;.zip` without a platform target, with its own manifest `DOC-SHELL.json` beside `DISTRIBUTIONS.json` and of the same form (`schema_version`, `product`, `repository`, `version`, `tag`, `source_commit`, `asset{name, size, digest}`), verified by the same digest check. `DISTRIBUTIONS.json` gains no field: its schema is closed, and every installed `vibe 1.0.0` would lose `self update`. The release check that counts the platform assets is extended to the additional shell asset.</fact></item>
    </list>
    <p p="71"><fact id="STACK-DECISION" status="spec/done">**Decision:** Qwik 2.0 beta with exact pins; one island shared by the public and the local render; the shell embedded at release and downloadable with consent.</fact></p>
    <list ordered="false" p="72">
      <item><fact id="stack-why" status="spec/done">**Why:** the owner chose Qwik 2.0, variant Б for the local reader and variant А for the server build; a beta is admissible under the project's law for a fresh but well-designed library, with an exact pin and a review trigger; the identity of the public and the local render rests on the shared island, not on a shared JavaScript runtime; the download step closes the hole where everyone building `vibe` from source would get a crippled reader.</fact></item>
      <item><fact id="stack-rejected" status="spec/done">**Considered and rejected:** Qwik SSR locally through an embedded JavaScript engine (QuickJS); the built shell beside the binary in the release zip instead of embedding; built assets inside the package `org.vibevm.doc/web`; requiring Node to build `vibe` from source.</fact></item>
      <item><fact id="stack-revisit" status="spec/done">**Revisit when:** the stable Qwik 2.0 ships, or a breaking change of the beta demands a rewrite; the pin moves only in its own commit with the reason recorded in the campaign ledger.</fact></item>
    </list>
  </section>
  <section id="seo" title="13. The SEO and LLM-SEO contract">
    <p p="73"><fact id="SEO-LEAD" status="spec/done">The public site MUST:</fact></p>
    <list ordered="false" p="74">
      <item><fact id="SEO-SSR" status="spec/done">serve fully server-rendered HTML, with no content behind scripts;</fact></item>
      <item><fact id="SEO-CANONICAL-HREFLANG" status="spec/done">keep one canonical page per fact and language: older versions carry `rel=canonical` to `latest` of their language; between languages, `hreflang` for every available language and `x-default` for the documentation's source language;</fact></item>
      <item><fact id="SEO-SITEMAP" status="spec/done">publish a sitemap index by packages and languages with `lastmod` from the publication date, listing `latest` only;</fact></item>
      <item><fact id="SEO-ROBOTS" status="spec/done">explicitly allow the crawlers of OpenAI, Anthropic, Google, Perplexity and the others in `robots.txt`; the list of agent names is **verified against the providers' documentation at every build**, never rewritten from memory, and the verification is recorded in the campaign ledger; `robots.txt` is one per domain, generated by the site build together with the root `llms.txt`, `sitemap.xml` and `feed.xml`, kept ASCII-only as the earlier landing kept it; the domain is not behind Cloudflare, so no crawler block is to be lifted there;</fact></item>
      <item><fact id="SEO-CHARSET-AND-REDIRECTS" status="spec/done">serve every text format with `charset=utf-8` and every redirect relative (`absolute_redirect off; port_in_redirect off;`) — TLS terminates outside the container, and an absolute `Location: http://…` sends strict fetchers into a redirect loop;</fact></item>
      <item><fact id="SEO-INDEXNOW" status="spec/done">after every deploy that changed pages, send the list of changed URLs to IndexNow with the landing's key (the key file already exists at the domain root);</fact></item>
      <item><fact id="SEO-STRUCTURED-DATA" status="spec/done">carry JSON-LD (`TechArticle` with `headline`, `abstract`, `author`, `inLanguage`, `keywords`, `datePublished`, `image`; `SoftwareApplication`; `BreadcrumbList`; `FAQPage` where apt), Open Graph and a Twitter Card `summary_large_image` with the preview from `[media].preview` or the composed card (§7);</fact></item>
      <item><fact id="SEO-LLMS-FILES" status="spec/done" action="continue" actionstage="doc" audience="agent">publish `llms.txt` (an index with one-line summaries) and `llms-full.txt` for the base corpus, plus `llms-small.txt` and `llms-medium.txt` under a token budget, all derived from the same manifest as the navigation, the full corpus ordered by the layer law (stable before mutable); the same files per language and per package; the registry `llms.txt` is a catalogue of documentations in the arXiv style — title, officiality star, publisher, language, audiences, abstract, link;</fact></item>
      <item><fact id="SEO-RAW-PROJECTIONS" status="spec/done" action="continue" actionstage="doc" audience="agent">serve every page as clean Markdown at the address with the `.md` suffix and as raw XML at the `.xml` suffix;</fact></item>
      <item><fact id="SEO-MANIFEST-AND-RESOLVER" status="spec/done" action="continue" actionstage="doc" audience="agent">serve the JSON page manifest with officiality statuses, languages, audiences, genres, anchors and summaries (`/doc/manifest.json`), a resolver endpoint for `spec://` addresses (`/doc/resolve?uri=…`), and optionally an MCP server of the site;</fact></item>
      <item><fact id="SEO-LINK-GRAPH" status="spec/done">keep a dense internal link graph: dependants, «explained in», «translated into», specification ↔ documentation;</fact></item>
      <item><fact id="SEO-PAGE-TEMPLATE" status="spec/done">build pages by one template: the answer in the first sentence, one concept per page, a glossary of canonical terms, questions written as questions, examples with expected output; scenario pages prompt first, mechanism second, manual steps only when they are needed (§16).</fact></item>
    </list>
    <list ordered="false" p="75">
      <item><fact id="SEO-LOCAL-EXEMPT" status="spec/done">The local mode publishes none of this and contacts nothing external.</fact></item>
      <item><fact id="seo-why" status="spec/done">**Why:** the owner's word — maximal SEO including LLM SEO; every item is either a standard or a consequence of a project law.</fact></item>
      <item><fact id="seo-rejected" status="spec/done">**Considered and rejected:** blocking AI crawlers; hand-written page descriptions; a link preview cropped from the banner; a separate `robots.txt` under `/doc/`.</fact></item>
      <item><fact id="seo-revisit" status="spec/done">**Revisit when:** a new standard of machine index appears.</fact></item>
    </list>
  </section>
  <section id="observability" title="14. Observed, checked, covering obligations — never judged">
    <list ordered="false" p="76">
      <item><fact id="OBS-IN-SCOPE" status="spec/done">`doc` packages enter the include globs of `facts.toml` and the traceability map: the documentation is observed like every authored text of the project.</fact></item>
      <item><fact id="OBS-NOT-JUDGED" status="spec/done">They are **not judged**: their facts do not enter the judging debt, because the genre is non-normative. The mechanism is the key `[judging] exempt = ["&lt;glob&gt;", …]` in `facts.toml` — «observed, not judged» — read by the debt script now and by the shipped verb later (PROP-047 `##DEBT-MUST-BE-ASKABLE`); the corpus of `facts check` and the map do not change. A `vibe check` cell verifies that the path of every in-tree package of kind `doc` is covered by `exempt`, so the list cannot go stale.</fact></item>
      <item><fact id="OBS-RULE-EDGE-UNPINNED" status="spec/done" action="continue" actionstage="doc" audience="author">Every `rule` yields a `documents` edge **without a pin**: the citation is live, and the page shows the fact's current text by address at every render. `vibe doc check --citations` checks one thing — that the anchor exists; a vanished anchor without a tombstone fails the build. There are no revisions, no text hashes and no «the spec moved ahead»: such checks would need a history the project does not keep by design.</fact></item>
      <item><fact id="OBS-PROSE-STALENESS-IS-HUMAN" status="spec/done">Whether the **prose around** a citation went stale is a human's question at full reconciliation, not a machine's.</fact></item>
      <item><fact id="OBS-EXAMPLES-GOLDEN" status="spec/work">Examples with `expect` run in the self-check panel as golden tests — the only technical link between the product and the documentation. Whether to keep it there or move it to a measurer is the owner's open question (vision §10, question 12); until his word it stays in the panel.</fact></item>
      <item><fact id="OBS-COVERAGE-GATE" status="spec/done" action="continue" actionstage="doc" audience="author">**The coverage gate.** Spec facts marked `actionstage="doc"` with an audience are *obligations*; `vibe doc check --coverage` requires every obligation to be cited by a page for the same audience — this is the ratchet PROP-047 `##DOC-COVERAGE-RATCHET` names, and `vibe progress report --view doc --audience …` is its listing of obligations, a source for the gate and not a navigation. Marking the obligations in the spec corpus is a campaign atom of its own. The site's navigation is derived from the page manifest, never from this report.</fact></item>
      <item><fact id="OBS-AUDIENCE-AGENT" status="spec/done">The audience vocabulary of PROP-043 gains `agent`; `user`, `author` and `dev` stay. Text marked `audience="agent"` obeys the laws of agent text: a token budget, no narration, and **never in a boot prefix** — the site serves it in the «for agents» section and first in `llms.txt`. Audiences are not declared in the manifest; they are derived from the page markup.</fact></item>
      <item><fact id="OBS-VERSION-CONTRACT" status="spec/done">**A version is a behavioural contract, not a frozen set of files.** Inside a version the product changes as often as it likes — amend, rewritten history, `vibe self update --force` — and this is invisible by design. The only event by which the documentation computes «a difference between versions» is the owner's conscious bump of the version number: no checksums, no file hashes, no history.</fact></item>
      <item><fact id="OBS-SURFACE-SNAPSHOTS" status="spec/done">The pseudo-history of versions is the documentation developers' kitchen: the documentation package keeps a **surface snapshot** of the product per declared version — `maintenance/surface/&lt;version&gt;.json`: a structural description, not a hash — commands and flags from `--help`, manifest and lock-file fields, schemas, the text of the spec facts marked `actionstage="doc"`, the format registry. `vibe doc surface --record &lt;version&gt;` writes it at a version bump and at the end of every full reconciliation; `vibe doc diff &lt;old&gt; &lt;new&gt;` compares two snapshots and, through the citation graph, the `derived` sources and the coverage map, lists **the pages to update, each with its reason**. The same `vibe doc diff &lt;version&gt; now` may run inside a version as a hint to reconciliation — kitchen, never a fact for a reader.</fact></item>
      <item><fact id="OBS-NOTHING-LEAKS" status="spec/done">Nothing of the kitchen reaches a reader: no «checked against», no «stale since», no drift counters, no fingerprints, no hash permalinks, no «a new version is available». The only dates on a page are when it was rendered and when it was last read aloud. A human changelog between versions is written by hand from the output of `vibe doc diff`.</fact></item>
      <item><fact id="OBS-NO-RELEASE-LOCK" status="spec/done">**There are no locks.** No technical gate binds a product release to the documentation: `vibe doc todo` prints the gaps as a number; the checkbox «documentation: updated / debt recorded / not needed» in the pull-request template is a habit; only internal breakage of the documentation stays red — a red example, a `derived` that fails to build, a vanished citation anchor. Drift between full reconciliations is an accepted risk; the full reconciliation is a team promise on a calendar — once a quarter and before a major milestone — not a release condition.</fact></item>
      <item><fact id="OBS-MAINTENANCE-TOOLS" status="spec/done">The maintenance tools: `vibe doc todo` (the queue by the current state of product and documentation — coverage gaps, red examples, unresolvable citations, page age, debt, linter statistics), `vibe doc surface` and `vibe doc diff`, `reviews.toml` (the last read-aloud date per page and the «page of the week» order), the package `CHANGELOG.md` written from the journal, and documentation debt as `docs:` lines in `BACKLOG.md`. The maintenance regulation itself is derived from the campaign journal in phase 6 and becomes the next PROP; every rule of it cites a journal entry.</fact></item>
    </list>
    <p p="77"><fact id="OBS-DECISION" status="spec/done">**Decision:** observed and checked, never judged; coverage by obligations; versions as contracts; no release lock.</fact></p>
    <list ordered="false" p="78">
      <item><fact id="obs-why" status="spec/done">**Why:** a one-way relation without reverse discovery rots silently; coverage measured by a gate answers «is everything told», which navigation does not; the sixth edition of the vision confused a contract with a frozen file set and tried to distinguish the indistinguishable — the owner's word restored the version number as the only event; the release lock was rejected by the owner on 2026-09-10 as untrue for a project that releases ten times a day.</fact></item>
      <item><fact id="obs-rejected" status="spec/done">**Considered and rejected:** a hand-kept page list as the source of truth; a table of contents from the obligations report; judging documentation facts; checksums, tree and commit hashes, fingerprints as product identity; surface snapshots per change instead of per declared version; any state marks on pages for readers; permanent links to past publications; «new version» notifications; the technical gate «the product does not ship without documentation».</fact></item>
      <item><fact id="obs-revisit" status="spec/done">**Revisit when:** the owner wants to publish a machine changelog between versions for readers — then the output of `vibe doc diff` gets a human projection, still only by declared versions; a second project wants the same maintenance ritual for its `doc` packages — then the regulation becomes a flow package.</fact></item>
    </list>
  </section>
  <section id="legacy" title="15. The fate of docs/">
    <list ordered="false" p="79">
      <item><fact id="LEGACY-MOVE" status="spec/done">`docs/` moves to `docs-legacy/` in one move-only commit that changes nothing else — an archive, not a deletion, after the precedent of `legacy-spec/`.</fact></item>
      <item><fact id="LEGACY-REGRESSION-RULE" status="spec/done">The new documentation is written against it: a claim that was there and is absent from the new documentation is either dropped with a recorded reason or a regression to fix. The inventory (`campaigns/docs-2026-09/LEGACY-INVENTORY.md`: every claim of the old tree with its disposition) is closed before the documentation package ships — no empty rows.</fact></item>
      <item><fact id="LEGACY-NOT-NORMATIVE" status="spec/done">Nothing in the living corpus cites `docs-legacy/` as a normative source; it joins no include glob; it is history kept readable.</fact></item>
      <item><fact id="LEGACY-ROOT-FILES-STAY" status="spec/done">`README.md`, `DEV-GUIDE.md` and `RUNTIME-GUIDE.md` stay in place and are updated by the same-commit law of PROP-000 §19.</fact></item>
      <item><fact id="legacy-rejected" status="spec/done">**Considered and rejected:** deleting `docs/`; rewriting it in place.</fact></item>
      <item><fact id="legacy-revisit" status="spec/done">**Revisit when:** the regression list is closed and the owner confirms; then `docs-legacy/` may be deleted in its own commit.</fact></item>
    </list>
  </section>
  <section id="style" title="16. The style law of the documentation prose">
    <p p="80"><fact id="STYLE-NORM" status="spec/done">The style norm is the document `AUTHORING.md` of the package `org.vibevm.core/vibevm-docs` (during the campaign: `campaigns/docs-2026-09/STYLE.md`); this section states its binding facts.</fact></p>
    <list ordered="false" p="81">
      <item><fact id="STYLE-SOURCE-LANGUAGE" status="spec/done" action="continue" actionstage="doc" audience="author">**The source language is English.** `org.vibevm.core/vibevm-docs` carries `[i18n].canonical = "en"`. Every other language, Russian included, is an **adaptation** published as its own package (§5): a mirror block for block, free sentence for sentence, with its own jokes and its own glossary of terms. Machine translation may produce a draft and never the text.</fact></item>
      <item><fact id="STYLE-READER" status="spec/done">**The reader** is intelligent, busy and has read nothing of ours. A page MUST work as the only page the reader ever opens: terms introduced on the page, the explanation complete without the specs, the spec cited as confirmation and never as a deferral.</fact></item>
      <item><fact id="STYLE-CONTAINERS-AND-CORRIDORS" status="spec/done">**Where complexity lives.** Only in containers the reader sees coming: `rule` blocks, tables, `derived` blocks, fences, the glossary. Narrative paragraphs are corridors: a term is introduced before use (a link to its glossary entry or a gloss in the same sentence), at most two glossary terms per narrative sentence, one new idea per paragraph named in its first sentence, «see the specification» in place of an explanation is forbidden, concepts ordered as a ladder where each rung uses only the rungs below it.</fact></item>
      <item><fact id="STYLE-STE" status="spec/done">**Technical passages follow ASD-STE100:** one word, one meaning — technical nouns have no synonyms; one instruction per sentence, imperative, present tense, active voice; at most 20 words in a procedural sentence and 25 in a descriptive one, at most six sentences in a paragraph; a warning before the step; sequences as numbered lists.</fact></item>
      <item><fact id="STYLE-REGISTER" status="spec/done">**The register is an essay, not a manual**, after the writers who explain mechanisms to intelligent readers for a living: mechanism first and in plain words, short words over long, the first sentence carries the news, concrete detail over abstraction, honesty about trade-offs.</fact></item>
      <item><fact id="STYLE-HUMOUR" status="spec/done">**Humour is rare, dry and placed:** at most one joke per page, never in a procedure, a warning, a reference table, an explanation of an error, a `rule` block or a title; informative, understood by an educated person of any country without an IT background; replaced or dropped in an adaptation, never translated.</fact></item>
      <item><fact id="STYLE-TICS" status="spec/done">**Model tics are deleted on sight.** The banned words and phrases per language are data for the linter and live in the documentation package as `style/banned.en.txt` and `style/banned.ru.txt` (source, not generated). Headings named Overview, Summary, Conclusion, Next steps or Key takeaways, exclamation marks, emoji, bold in prose and a closing summary sentence are errors.</fact></item>
      <item><fact id="STYLE-PAGE-SKELETON" status="spec/done">**The page skeleton.** A concept page: a noun-phrase title; a first paragraph saying what this is and when it is needed with no glossary term at all (it is also the page's line in `llms.txt`; clarified 2026-09-12 at the first corpus check: a glossary word used in its ordinary English sense — package, project, kind, feature, workspace, translation — does not count, a word that means something only in vibe — lock file, manifest, registry, store, index, anchor, skill, contribution, fingerprint, receipt — does); an `example` with its `expect`; the mechanism as a ladder; edge cases through `rule`; questions only if real. A task page: an imperative title; the first paragraph without terms; then the `prompt` (§16 `##STYLE-PROMPT-FIRST`); «what happens» in three to six sentences as a corridor; «by hand» as numbered STE steps only where the manual road is worth walking; edge cases and questions. No page ends with a conclusion, a summary or a «next steps» box.</fact></item>
      <item><fact id="STYLE-PROMPT-FIRST" status="spec/done" action="continue" actionstage="doc" audience="author">**Prompt first.** Any action in VibeVM is done by an agent or by hand, and the agent is the main road. A scenario page opens with a `prompt` block: the plain request in the user's voice, self-contained (coordinates, paths and registry named, never implied), one prompt for one outcome, free of secrets, neutral to the agent (it works for any agent with the `vibevm` skill), at most sixty words; beside it `needs` (what the agent must have) and `outcome` (what the person sees when it worked), and at least one `assert` — a shell command that must exit zero after the agent's work, because a prompt cannot be checked by its output the way a shell example can. A prompt without an assert on a scenario page is a style-linter error; an illustrative prompt on an explanation page is marked `assert="none"`. `vibe doc check --prompts` runs every prompt through the configured agent runner (`[doc.prompts] runner`) in a clean temporary directory with the fixture and then runs the asserts; it is not part of the panel — it runs in the prose phase before acceptance, as a sample in the monthly loop, and in full at reconciliation. The skill `vibevm-docs` takes a page's `prompt` as the task when the user asks for what the page describes.</fact></item>
      <item><fact id="STYLE-WHO-WRITES" status="spec/done">**Who writes.** Prose — pages, first paragraphs, `title` and `abstract`, the glossary, questions, the skill text, adaptations — is written by the strongest model in the central session (the owner named Fable, Astra and Sol), never by a delegated worker; a worker's draft of prose is rewritten, not edited. Workers do the work around the prose: fixtures, expected output, the block mirrors of adaptations, code, checks, the shell.</fact></item>
      <item><fact id="STYLE-LINT" status="spec/done" action="continue" actionstage="doc" audience="author">**The mechanical check** `vibe doc check --style` gates a page: banned words and phrases by the page's language; sentence and paragraph length by block kind (errors in procedures and warnings, warnings in corridors); a glossary term used before its introduction; more than two glossary terms in one narrative sentence; deferral phrases («see the specification», «as described in») without an explanation on the same page; forbidden headings; exclamation marks, emoji, bold in prose; a readability score per page reported and not gated. A false positive is fixed in the linter's rule with a BACKLOG entry, never worked around in the text. A page is committed only after the author's self-edit against the norm and a green `--style`.</fact></item>
      <item><fact id="STYLE-OWNER-READ" status="spec/done">**The human check:** the owner reads three pages aloud at the phase gate — the newcomer's route, a reference page, an architecture explanation; a page that cannot be read aloud without stumbling goes back.</fact></item>
      <item><fact id="style-why" status="spec/done">**Why:** the owner's word — model prose suffers tics, but the greater harm is misplaced density: an agent writes as if the reader had read every spec; STE gives checkable rules for the technical places, the essay register for the rest, and the container/corridor split makes «where it is hard» predictable and checkable; English as the source is how the project is built and how AI crawlers read; adaptation rather than translation because jokes and rhythm do not translate; prose is the most expensive and most visible part of the work and the one worth the strongest model, since a gate can check code and fixtures but not text.</fact></item>
      <item><fact id="style-rejected" status="spec/done">**Considered and rejected:** Russian as the source language; literal translation; delegating prose to cheaper models for speed; pure infostyle for Russian (too dry — only its war on clutter is taken); developers' in-jokes; the Thing Explainer extreme (STE applies to technical places, not to the essay); AI «smoothing» of finished pages.</fact></item>
      <item><fact id="style-revisit" status="spec/done">**Revisit when:** the owner's style review at the phase gate yields remarks; a human editor appears; the linter starts to hinder more than help — two false positives in a row on one rule are a BACKLOG entry.</fact></item>
    </list>
  </section>
  <section id="invariants" title="Invariants">
    <p p="82"><fact id="INVARIANTS-LEAD" status="spec/done">(The load-bearing rules of this PROP in one place. If anything below seems violated in practice, stop and reconcile before proceeding.)</fact></p>
    <list ordered="true" p="83">
      <item><fact id="INV-DOC-CITES-NEVER-COPIES" status="spec/done">**Documentation cites an anchor and never restates a normative value** — a number, a flag, a path, a field name. Where the prose must mention a value, the same sentence carries the source address.</fact></item>
      <item><fact id="INV-DOC-NEVER-BOOTS" status="spec/done">**No documentation page enters `STATIC.xml`, `INDEX.md` or a boot snippet.** A `doc` package has no `[boot_snippet]`; agent-audience text is never in a boot prefix.</fact></item>
      <item><fact id="INV-ONE-WAY-SOURCE-TWO-WAY-RENDER" status="spec/done">**The relation is one-way in the source and two-way in the render.** A spec's text knows nothing of documentation; «explained in», «translated into» and «depends on» are computed from the index and the maps and never written by hand.</fact></item>
      <item><fact id="INV-OFFICIALITY-COMPUTED" status="spec/done">**Officiality is computed from the convergence of edges at every render and stored nowhere as a flag.**</fact></item>
      <item><fact id="INV-DERIVED-NEVER-HAND-KEPT" status="spec/done">**Derived things are generated, never kept by hand:** the command reference, field tables, navigation, page manifests, `llms*.txt`, placeholders, previews, reverse links, block numbers.</fact></item>
      <item><fact id="INV-EXAMPLES-RUN" status="spec/done">**Every example runs.** An `example` without `expect` is not an example; a comparison is never loosened to turn green.</fact></item>
      <item><fact id="INV-ANCHORS-IMMUTABLE" status="spec/done">**Anchors are immutable; a rename is a tombstone** — for published documentation pages as for specs, and for translations, which must match their source.</fact></item>
      <item><fact id="INV-TRANSLATION-MIRRORS" status="spec/done">**A translation mirrors the source tree file for file and block for block and authors no examples.**</fact></item>
      <item><fact id="INV-VERSION-IS-A-CONTRACT" status="spec/done">**A version is a behavioural contract.** Nothing in the documentation machinery depends on real history, checksums or tree hashes; the only version event is the owner's bump; readers never see the kitchen.</fact></item>
      <item><fact id="INV-NO-RELEASE-GATE" status="spec/done">**No technical gate binds a product release to the documentation.** Drift between reconciliations is accepted; only internal breakage of the documentation is red.</fact></item>
      <item><fact id="INV-LOCAL-IS-OFFLINE" status="spec/done">**The local reader listens on 127.0.0.1 only, serves only known roots, loads nothing external, and contacts the network only for a shell download the user explicitly confirmed.**</fact></item>
      <item><fact id="INV-PACKAGE-IS-SOURCE" status="spec/done">**A package carries source only:** no build artifacts in the web package, images as source files within the card limits.</fact></item>
      <item><fact id="INV-LOGIC-IN-THE-LIBRARY" status="spec/done">**Content logic lives in the Rust library; every surface — CLI, MCP, HTTP, the shell — is a thin projection over a JTD contract.**</fact></item>
      <item><fact id="INV-PROSE-BY-THE-STRONGEST" status="spec/done">**Prose is written by the strongest model in the central session and never by a worker; English is the source and every other language an adaptation.**</fact></item>
      <item><fact id="INV-INFRA-STAYS-PRIVATE" status="spec/done">**Nothing from the private infrastructure document enters the repository, and nothing in the campaign touches the host nginx, ports, certificates, neighbouring services or the VPN.**</fact></item>
    </list>
  </section>
</spec>
