feat: Prosa ist kein Identifier - Link-Taxonomie als Enum, generierte Regionen mit Markern (4.0.0)
CI / verify (push) Successful in 55s
Release / release (push) Successful in 38s

Files changed:
- .gitea/workflows/ci.yml
- AGENTS.md
- CHANGES.md
- VERSION
- instructions/CONTRACT.md
- instructions/link-taxonomy.md
- instructions/migrations/4.0.0-link-taxonomy.md
- instructions/setup-instance.md
- kb/CONTRACT.md
- kb/CONVENTIONS.md
- kb/CONVENTIONS.md.template
- kb/comparisons/COLLECTION.md
- kb/concepts/COLLECTION.md
- kb/entities/COLLECTION.md
- kb/sources/COLLECTION.md
- tools/CONTRACT.md
- tools/README.md
- tools/chemenu/blocks.py
- tools/chemenu/cli.py
- tools/chemenu/commands/cite_cmd.py
- tools/chemenu/commands/dist_cmd.py
- tools/chemenu/commands/docs_verify.py
- tools/chemenu/commands/doctor.py
- tools/chemenu/commands/links_cmd.py
- tools/chemenu/commands/migrate_cmd.py
- tools/chemenu/commands/new_page.py
- tools/chemenu/commands/page_ops.py
- tools/chemenu/commands/run_budget.py
- tools/chemenu/commands/xref.py
- tools/chemenu/conventions.py
- tools/chemenu/corpus_diff.py
- tools/chemenu/frontmatter_io.py
- tools/chemenu/kb_collections.py
- tools/chemenu/kb_state.py
- tools/chemenu/links.py
- tools/chemenu/lint_core.py
- tools/chemenu/provenance.py
- tools/chemenu/sections.py
- tools/chemenu/tests/conftest.py
- tools/chemenu/tests/test_blocks.py
- tools/chemenu/tests/test_cite_cmd.py
- tools/chemenu/tests/test_conventions.py
- tools/chemenu/tests/test_dist_cmd.py
- tools/chemenu/tests/test_doctor.py
- tools/chemenu/tests/test_migrate_cmd.py
- tools/chemenu/tests/test_new_page.py
- tools/chemenu/tests/test_pipeline_l0.py
- tools/chemenu/tests/test_types_cmd.py
- tools/chemenu/tests/test_xref.py
- types/concept.schema.yaml
- types/entity.md
- types/entity.schema.yaml
- types/instruction.schema.yaml
- types/type-spec.md
- work/link-taxonomy-migration/README.md
- work/link-taxonomy-migration/plan.md
This commit is contained in:
2026-09-02 18:39:22 +02:00
parent 502971d147
commit 177c7e9ce8
56 changed files with 2692 additions and 750 deletions
+53 -27
View File
@@ -12,9 +12,8 @@ second half is the cut: what is written here is enforced by `tools/wikitool` or
how it works, so it is identical everywhere and `dist export` ships it verbatim.
**What an instance decides for itself is next door, in
[kb/CONVENTIONS.md](CONVENTIONS.md)** - the language pages are written in and its three
tool-owned section headings, the naming forms, the tone, the relationship-label vocabulary, the
confidence rubric. That file binds exactly as this one does; it is simply owned by the instance
[kb/CONVENTIONS.md](CONVENTIONS.md)** - the language pages are written in, the headings its two
generated regions render under, the naming forms, the tone, the confidence rubric. That file binds exactly as this one does; it is simply owned by the instance
rather than by the stack, so the distribution ships only its `.template` and the instance writes
the real one. Read both, plus the target collection's `kb/<name>/COLLECTION.md` (also
instance-owned), before writing or editing a page.
@@ -99,7 +98,8 @@ decision record - is the instance's, in
- [ ] Carry a clear, descriptive title and a summary near the top
- [ ] Use consistent terminology with the rest of the wiki
- [ ] Link to every entity and concept it mentions, and be linked to in return
- [ ] Link to the entities and concepts it mentions, and declare an edge where the relationship
is worth naming - in the direction this page asserts it, not in both
- [ ] Cite its hard facts (see [Provenance and citation](#provenance-and-citation))
- [ ] Duplicate no existing page
- [ ] Appear in the catalog (guaranteed by `wikitool index rebuild`)
@@ -141,36 +141,62 @@ instance records - see [kb/CONVENTIONS.md § Language](CONVENTIONS.md#language).
evidence *about* a source, not a substitute for it. Quote verbatim in the original language and
record the raw file's language in `source_language:`.
### Section headings
### Generated regions
Three headings are a vocabulary the tool owns rather than prose an author picks: `xref add`
writes into Relationships and See Also, and `cite add` owns the trailing Footnotes block. They
follow the KB language like everything else, so **the instance names them**, in
`kb/CONVENTIONS.md`'s `sections:` frontmatter. `tools/chemenu/conventions.py` reads that
declaration and `tools/chemenu/sections.py` is what the rest of the compiler asks - there is no
heading text in the compiler itself.
Two regions of a page body are **generated**, not authored: the links region `xref` owns and the
footnotes region `cite` owns. Each sits between a marker pair:
Each has aliases the tool still *recognizes* but no longer writes, which is what lets the corpus
be translated page by page: a page still carrying `## Relationships` is found and appended to
correctly, and `cite sync` leaves an untranslated `## Footnotes` heading alone rather than
retitling it. The recognized set is the canonical name, any `section_aliases:` the instance
declared, and the names this stack wrote before the declaration existed. Renaming a heading is
the translation pass's job, never a side effect of another command. Any *other* heading an
author adds is ordinary prose and is translated with the rest.
```markdown
<!-- wikitool:links -->
## Beziehungen
- **depends-on:** [[Hermes]]
<!-- /wikitool:links -->
```
The marker is what the tool locates the region by, and everything between the markers -
**heading included** - is replaced wholesale on the next write. An author never edits inside
them; anything left there is overwritten without warning, exactly as in `kb/index.md`. A region
with nothing to show is absent rather than empty.
The heading is therefore a *rendering* value, taken from `kb/CONVENTIONS.md`'s `sections:`. No
heading text exists in the compiler, and nothing matches on it: changing the declaration
re-renders the words on the next write and cannot split a page.
That is not how it used to work. The tool located these regions by matching their heading text,
which made a translated heading a structural fact - and made the region's *end* a guess. It ran
to the next heading, and before that to the end of the file, which silently deleted whatever sat
after it on eight pages. Any *other* heading a page carries is ordinary prose.
## Linking
Every page links to what it mentions, in both directions. Cross-references are created with
`tools/wikitool xref add --a "<A>" --b "<B>" --rel-a "<label>" --rel-b "<label>"`, never by
hand-editing the `related:` array or the Relationships/See Also bullets.
**An edge is authored in one direction**, on the page that asserts it, and carries a label that
is a machine value rather than prose:
Use a typed relationship label rather than a generic one. The label is free text as far as the
tool is concerned - it is written into a `- **label:** [[Title]]` bullet and no code matches on
it - so which vocabulary this instance uses is
[kb/CONVENTIONS.md § Relationship labels](CONVENTIONS.md#relationship-labels)'s to list.
```yaml
related:
- depends-on: Hermes
```
A page is expected to have at least one inbound link; `wikitool lint` reports orphans.
Comparison pages are exempt - they are reached through the catalog.
Created with `tools/wikitool xref add --a "<A>" --b "<B>" --rel <label>`, never by hand-editing
`related:` or the rendered bullet. Say the sentence before choosing the label - `[A] <label>
[B]` - and if it only reads true backwards, the edge belongs on the other page.
**A reverse edge is a separate decision, not a mirror.** Write one when it independently helps a
reader at the other end; do not write one to make the graph symmetric. Navigation does not
depend on it either way: `index rebuild` renders the inbound view from the graph, completely and
without maintenance.
Which labels exist is [instructions/link-taxonomy.md](../instructions/link-taxonomy.md), a
palette that binds nothing. Which of them a page may *use* is its own collection's `outbound:`
block, per destination - the **source** collection decides, because the rules that govern an
edge are the rules of the collection asserting it. `xref add` refuses an unauthorised label and
`lint` reports one.
A page is expected to have at least one inbound edge; `wikitool lint` reports orphans.
Comparison pages are exempt - they are reached through the catalog. Directional edges mean more
pages qualify than under the old mirrored model, and that is the check measuring reachability
rather than measuring whether `xref` ran.
Renaming a page, deleting one, or dropping a single reference are tool operations with their
own procedure: see [instructions/page-lifecycle.md](../instructions/page-lifecycle.md).
+22 -29
View File
@@ -2,8 +2,7 @@
language: de
profile: german
sections:
relationships: Beziehungen
see_also: Siehe auch
links: Beziehungen
footnotes: Fußnoten
---
@@ -21,13 +20,11 @@ Adopted from the `german` profile in
[instructions/kb-profiles.md](../instructions/kb-profiles.md). That catalogue is a palette, not
an enum - what is written here is what holds, whether or not a profile says the same thing.
The frontmatter above is the one machine-read part. `sections:` names the three headings
`wikitool xref` and `wikitool cite` write into; `tools/chemenu/conventions.py` reads them and
`tools/chemenu/sections.py` is what the rest of the compiler asks. Renaming one here changes
what the tool *writes*; what it still *recognizes* is the union of that name, any
`section_aliases:` declared beside it, and the names this stack wrote before this file existed.
That asymmetry is the translation path: a page keeps working under its old heading until it is
itself translated.
The frontmatter above is the one machine-read part. `sections:` names the headings the two
**generated regions** render under - the links region `wikitool xref` owns and the footnotes
region `wikitool cite` owns. Each sits between a marker pair, and the marker is what the tool
locates it by, so the heading here is a display value: changing it re-renders the words above
those regions and nothing else. Nothing matches on this text.
## Language
@@ -52,12 +49,13 @@ material, not a second rule - every entry in it is a decision that was made wron
### Section headings
The canonical names are the frontmatter's: `## Beziehungen`, `## Siehe auch`, `## Fußnoten`.
The English forms this stack wrote before the corpus was translated are still recognized, so a
page carrying `## Relationships` is found and appended to correctly and `cite sync` leaves an
untranslated `## Footnotes` alone. Renaming such a heading is the translation pass's job, never
a side effect of another command. Any *other* heading an author adds is ordinary prose and is
translated with the rest.
The two generated regions render under `## Beziehungen` and `## Fußnoten`. An author never
writes inside them - they are rebuilt from frontmatter on every write, exactly like
`kb/index.md` - and never has to write the heading either. Any *other* heading on a page is
ordinary prose and is translated with the rest.
There is no `## Siehe auch` region any more. It was the reciprocal half of a bidirectional
`xref add`; under authored directional edges, `see-also` is a *label* inside the links region.
## Naming
@@ -92,15 +90,11 @@ The blockquote cap is not here: `wikitool lint` reports it, so it is the contrac
## Relationship labels
`tools/wikitool xref add --rel-a/--rel-b` takes a free-text label. This instance uses a typed
one rather than a generic one:
`hängt ab von` · `verwendet` · `implementiert` · `erweitert` · `ersetzt` · `steht in Konflikt mit`
· `benötigt` · `erzeugt` · `konsumiert` · `besitzt` · `pflegt` · `läuft auf` · `verwandt mit`
(last resort)
The labels are prose written into a `- **label:** [[Title]]` bullet; no code matches on them, so
an untranslated page's English label is stale wording, not a broken reference.
**Not this file's to list, and not localized.** A label is a machine value in `related:`, drawn
from [instructions/link-taxonomy.md](../instructions/link-taxonomy.md) and authorised per
destination in each `kb/<name>/COLLECTION.md`'s `outbound:` block. `- **depends-on:** [[Hermes]]`
is what a German page carries, and that is deliberate: the label is an identifier, so translating
it would make the graph's semantics depend on the prose again.
## Confidence rubric
@@ -119,8 +113,7 @@ write "unsicher"/"unbestätigt".
## Keeping this file honest
Change it when a convention actually changes, and treat a change to `sections:` as a corpus
migration rather than an edit: existing pages keep their old headings until something translates
them, and the alias list is what carries them in the meantime. `wikitool doctor` FAILs on a
missing or unfilled file, and `wikitool docs verify` refuses a `sections:` block that does not
name all three slots.
Change it when a convention actually changes. `sections:` is safe to change at any time - the
regions are located by their markers and re-rendered under the new words on the next write.
`wikitool doctor` FAILs on a missing or unfilled file, and `wikitool docs verify` refuses a
`sections:` block that does not name both regions.
+11 -26
View File
@@ -3,16 +3,8 @@
language: en
profile: none
sections:
relationships: Relationships
see_also: See Also
links: Relationships
footnotes: Footnotes
# Headings this instance no longer writes but still recognizes, so a corpus can
# be translated page by page instead of all at once. Optional; the names this
# stack wrote before this file existed are always recognized anyway.
# section_aliases:
# relationships: [Beziehungen]
# see_also: [Siehe auch]
# footnotes: [Fußnoten]
---
# kb/ - Authoring Conventions of This Instance
@@ -29,9 +21,9 @@ Ready-made answers to every section below - including a complete German profile
[instructions/kb-profiles.md](../instructions/kb-profiles.md). That catalogue is a palette, not
an enum: adopt an entry, adapt it, or write your own. What is written *here* is what holds.
The frontmatter above is the one machine-read part. `sections:` names the three headings
`wikitool xref` and `wikitool cite` write into. Set them before the first page is written:
afterwards, changing one is a corpus migration rather than an edit.
The frontmatter above is the one machine-read part. `sections:` names the headings the two
generated regions render under. Safe to change at any time - each region is located by its
marker pair, so a rename re-renders words and nothing else.
## Language
@@ -52,10 +44,8 @@ is the one those terms are already in.}
### Section headings
The canonical names are the frontmatter's. Any name this instance previously wrote stays
recognized through `section_aliases:`, which is what lets a corpus be translated page by page.
Renaming such a heading is the translation pass's job, never a side effect of another command.
Any *other* heading an author adds is ordinary prose.
The two generated regions render under the frontmatter's headings. An author never writes inside
them - they are rebuilt from frontmatter on every write. Any *other* heading is ordinary prose.
## Naming
@@ -80,13 +70,9 @@ Bad: {the same sentence written the way it must not be.}
## Relationship labels
`tools/wikitool xref add --rel-a/--rel-b` takes a free-text label. Listing the ones this
instance uses is what keeps a graph typed rather than a wiki full of "related to":
{the label vocabulary, in the KB language}
No code matches on these, so an old label on an untranslated page is stale wording, not a
broken reference.
**Not this file's to list, and not localized.** A label is a machine value in `related:`, drawn
from [instructions/link-taxonomy.md](../instructions/link-taxonomy.md) and authorised per
destination in each `kb/<name>/COLLECTION.md`'s `outbound:` block.
## Confidence rubric
@@ -99,6 +85,5 @@ contract's. What the number *means* is this instance's:
## Keeping this file honest
Change it when a convention actually changes, and treat a change to `sections:` as a corpus
migration rather than an edit. `wikitool doctor` FAILs on a missing or unfilled file, and
`wikitool docs verify` refuses a `sections:` block that does not name all three slots.
Change it when a convention actually changes. `wikitool doctor` FAILs on a missing or unfilled
file, and `wikitool docs verify` refuses a `sections:` block that does not name both regions.
+16 -3
View File
@@ -1,5 +1,7 @@
---
profile: comparisons
outbound:
any: [compares-with, contrasts, see-also]
required_by_stack: false
---
@@ -37,11 +39,22 @@ alphabetically.
- State the trade-off, not a winner. Where a recommendation is genuinely warranted, scope it:
"for X workload", not "better".
## Authorised labels
The `outbound:` block above is what `wikitool lint` and `xref add` check: which labels a page in
this collection may use, per destination. The catalogue they are drawn from - and what each one
asserts - is [instructions/link-taxonomy.md](../../instructions/link-taxonomy.md), which binds
nothing on its own.
Narrow for the opposite reason: a comparison's substance is its table, and its links to the compared subjects are the one relationship it asserts.
Adding a label here is a deliberate contract change, not a way around a refusal.
## Outbound linking
A comparison links to every subject with `related to`, and each subject links back. Comparison
pages are **exempt from the orphan check** - they are reached through `index.md` rather than
through inbound prose links.
A comparison links to every subject with `compares-with`. The subjects do not have to link back:
a comparison is reached through the catalog, and each subject's inbound view renders the edge
anyway. Comparison pages are **exempt from the orphan check** for the same reason.
## What does not belong here
+18 -2
View File
@@ -1,5 +1,10 @@
---
profile: concepts
outbound:
concepts: [extends, grounds, rests-on, enables, precondition, exemplifies, abstracted-from, contrasts, compares-with, contradicts, composition, part-of, supersedes, derived-from, adapted-from, see-also]
entities: [operationalized-from, mechanism, procedure, applies-when, operates-on, invokes, exemplifies, see-also]
sources: [evidenced-by, derived-from, adapted-from, defined-in, see-also]
comparisons: [compares-with, see-also]
required_by_stack: false
---
@@ -33,8 +38,19 @@ An architectural decision is a concept page, prefixed as
- **Status** - proposed / accepted / deprecated / superseded.
- Links to every entity the decision affects.
A superseded ADR is never deleted or rewritten; a new one supersedes it and both link to the
other with `replaces` / `replaced by`.
A superseded ADR is never deleted or rewritten. The new one declares `supersedes` pointing at
it; the old one needs no edge back, because its inbound view renders the replacement.
## Authorised labels
The `outbound:` block above is what `wikitool lint` and `xref add` check: which labels a page in
this collection may use, per destination. The catalogue they are drawn from - and what each one
asserts - is [instructions/link-taxonomy.md](../../instructions/link-taxonomy.md), which binds
nothing on its own.
The widest authorisation in this instance, because argumentation is what concept pages do. Note that the operational labels are absent: a concept does not `depend-on` anything - the entity implementing it does.
Adding a label here is a deliberate contract change, not a way around a refusal.
## Outbound linking
+16
View File
@@ -1,5 +1,10 @@
---
profile: entities
outbound:
entities: [depends-on, required-by, runs-on, hosts, uses, produces, consumes, maintains, owns, part-of, composition, supersedes, see-also]
concepts: [implements, exemplifies, rests-on, applies-when, operates-on, invokes, see-also]
sources: [evidenced-by, defined-in, see-also]
comparisons: [compares-with, see-also]
required_by_stack: false
---
@@ -43,6 +48,17 @@ These are areas, not collections: they inherit this contract and carry no `COLLE
- **People** - role, affiliation, and the projects or decisions they are connected to. Nothing
personal beyond what the source states.
## Authorised labels
The `outbound:` block above is what `wikitool lint` and `xref add` check: which labels a page in
this collection may use, per destination. The catalogue they are drawn from - and what each one
asserts - is [instructions/link-taxonomy.md](../../instructions/link-taxonomy.md), which binds
nothing on its own.
Operational labels dominate here because an entity's relationships are mostly to other concrete things. `implements` points *out* to a concept; the concept does not point back unless that direction is a statement of its own.
Adding a label here is a deliberate contract change, not a way around a refusal.
## Outbound linking
An entity links to the technologies it uses, the systems it runs on, the projects that depend
+13
View File
@@ -1,5 +1,7 @@
---
profile: sources
outbound:
any: [is-evidence-for, defined-in, see-also]
required_by_stack: true
---
@@ -38,6 +40,17 @@ The `raw_files:`/`source_url:`/citation rules are shared and live in
- `tools/wikitool sources trace --raw <path>` answers "what did we learn from this?";
`tools/wikitool sources coverage` lists raw files no source page claims yet.
## Authorised labels
The `outbound:` block above is what `wikitool lint` and `xref add` check: which labels a page in
this collection may use, per destination. The catalogue they are drawn from - and what each one
asserts - is [instructions/link-taxonomy.md](../../instructions/link-taxonomy.md), which binds
nothing on its own.
Deliberately narrow. A source page is evidence *about* a source; almost everything it would want to say is already carried by `raw_files:`, `sources:` and `[^cite-id]`, which are the mechanical provenance path rather than authored edges.
Adding a label here is a deliberate contract change, not a way around a refusal.
## Outbound linking
A source page links to every entity and concept it produced or updated.