--- type: types/instruction.md name: 4.0.0-link-taxonomy description: Move every relationship from free-text prose in a body bullet to a labelled edge in related:, and every tool-owned body region from heading-matching to a marker pair. manual: true migrates_to: 4.0.0 migration_kind: assisted obligation: required --- # Move relationships into the data, and generated regions behind markers (4.0.0) Until 4.0.0 the stack used **prose as an identifier** in three places, and each one cost something measurable: | Was the identifier | Cost | |---|---| | A section's heading text (`## Beziehungen`) | The KB language was a compiler constant, and the region's *end* was a guess. Content sitting after it was silently deleted on eight pages | | A relationship label in a body bullet (`- **hängt ab von:**`) | Nothing could check the vocabulary, so it drifted to **152 distinct labels** across 337 bullets against thirteen that were documented | | The reciprocal half of every edge | `xref add` mirrored every link, which made per-collection label authorisation impossible and filled `## Siehe auch` with 555 unlabelled bullets, 353 of them provably redundant | 4.0.0 replaces all three. A region is delimited by a marker pair and rendered from frontmatter; a label is a machine value in `related:`, drawn from a catalogue and authorised per destination by the source collection; an edge is authored in one direction and the inbound view is computed. **This one touches pages.** Unlike 3.0.0 it is not a contract reshuffle: every `related:` entry and every tool-owned body region changes. It is `assisted` because there is no mapping table - mapping free-text German onto a 35-label catalogue is a judgment call per edge, and a large minority of the old labels are reverse directions that under the new model are not stored at all. ## When to run After installing 4.0.0 over an instance on 3.x. `tools/wikitool migrate status` names it, and `lint` reports `unlabelled_edges` for every unconverted edge - that count reaching zero is how you know the run is finished. **Nothing breaks while it is outstanding.** Unlabelled edges and undelimited regions are read, not rejected: `links.py` treats a bare title as an edge whose label is not declared yet, and `provenance.split_cite_block` falls back to the pre-marker layout. That is deliberate - a corpus has to stay readable while it is being converted - and it is why the two lint findings stay advisory for as long as `kb_version` is below 4.0.0, which is exactly as long as this document is outstanding. ## Steps 1. **Rewrite `kb/CONVENTIONS.md`'s `sections:` block.** Three slots become two, because the See Also region is gone: ```yaml sections: links: footnotes: ``` Delete `section_aliases:` if you have one - nothing matches on heading text any more, so there is nothing to alias. The heading is now a *rendering* value: changing it re-renders the words above each region on the next write and can no longer split a page. 2. **Add an `outbound:` block to every `kb//COLLECTION.md`.** Which labels a page may use, per destination collection, with `any` as a wildcard: ```yaml outbound: entities: [depends-on, runs-on, uses, see-also] concepts: [implements, see-also] ``` The catalogue to draw from is [link-taxonomy.md](../link-taxonomy.md); the four contracts in the origin repo are worked examples. **The source collection decides** - that is what makes a 35-label palette usable, and it is why the reverse edge can no longer be written automatically. A destination you do not list authorises nothing, which is a real answer. 3. **Fix your page type-spec templates.** If you adopted the 3.0.0 templates, they contain `## {section.relationships}` and `## {section.see_also}`. Those variables no longer exist and would be written into new pages literally. **Delete both sections from the `## Template` block** - a template must not scaffold a tool-owned region at all: it is generated between markers on the first `xref add` / `cite add` and re-rendered on every write. 4. **Convert the corpus**, following [migrate-corpus.md](../migrate-corpus.md). Cut it into units sized against the iteration budget; the origin repo used four, ~45 pages each. Per page: - For each labelled bullet under the old relationships heading: say the sentence `[this page]