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
+15 -2
View File
@@ -27,8 +27,21 @@ properties:
related:
type: array
items:
type: string
description: Related concept and entity titles
oneOf:
- type: string
- type: object
minProperties: 1
maxProperties: 1
additionalProperties:
type: string
description: >-
Declared outbound edges. Each entry is either `<label>: <page title>` - the
label drawn from instructions/link-taxonomy.md and authorised per
destination by the source collection's `outbound:` block - or a bare page
title for an edge whose label has not been declared yet. The bare form is
the pre-4.0.0 shape and is what `lint` reports until the migration reaches
the page; it is accepted rather than rejected so that a corpus stays
readable while it is being converted.
sources:
type: array
items:
+4 -15
View File
@@ -77,12 +77,6 @@ TODO: 1-2 Absätze dazu, was diese Entity ist und wozu sie dient.
- **Verantwortlich:** TODO (falls zutreffend)
- **Repository:** TODO (falls zutreffend)
## {section.relationships}
- **Hängt ab von:** TODO
- **Verwendet von:** TODO
- **Verwandt mit:** TODO
## Details
TODO: Ausführliche Informationen, nach sinnvollen Abschnitten gegliedert
@@ -90,17 +84,12 @@ TODO: Ausführliche Informationen, nach sinnvollen Abschnitten gegliedert
## Historie
- [{today}] - Page created via wikitool
## {section.see_also}
- TODO: Verwandte Seiten
```
Die beiden `{section.…}`-Platzhalter sind toolgeführte Abschnitte: `wikitool xref` schreibt in
genau sie hinein, und wie sie heißen, entscheidet die Instanz in `kb/CONVENTIONS.md`
(`sections:`) - nicht dieser Type-Spec und nicht der Compiler. `wikitool new` setzt den
aktuellen Namen ein. Der Wert hinter `**Typ:**` bleibt der englische Enum-Wert - danach filtert
`search --field`.
Der Beziehungsabschnitt steht bewusst **nicht** im Template: er ist eine generierte Region, die
`wikitool xref` beim ersten Kanteneintrag zwischen Markern anlegt und aus `related:` neu
rendert. Ein Autor schreibt dort nie hinein. Der Wert hinter `**Typ:**` bleibt der englische
Enum-Wert - danach filtert `search --field`.
---
+15 -2
View File
@@ -27,8 +27,21 @@ properties:
related:
type: array
items:
type: string
description: Related page titles
oneOf:
- type: string
- type: object
minProperties: 1
maxProperties: 1
additionalProperties:
type: string
description: >-
Declared outbound edges. Each entry is either `<label>: <page title>` - the
label drawn from instructions/link-taxonomy.md and authorised per
destination by the source collection's `outbound:` block - or a bare page
title for an edge whose label has not been declared yet. The bare form is
the pre-4.0.0 shape and is what `lint` reports until the migration reaches
the page; it is accepted rather than rejected so that a corpus stays
readable while it is being converted.
sources:
type: array
items:
+15
View File
@@ -43,6 +43,21 @@ properties:
scriptable; `assisted` needs a judgment call per page and is therefore an
agent procedure. Today this is a description rather than an execution
promise - there is no `migrate run`.
obligation:
type: string
enum: [required, offered]
default: required
description: >-
Whether the migration must run at all - a separate axis from
`migration_kind`, which says only how the work is done. `required`
(the default) is the original meaning: the content must reach the new
shape or it no longer fits the machinery, so `migrate status` counts it
as outstanding and `migrate done` advances `kb_version` through it.
`offered` is an upgrade the instance may decline: a file it owns still
works as it is, and the stack is proposing a better default. An offered
migration never blocks, never appears in the outstanding chain, and is
not a link in the version chain - it is listed separately so an operator
can take it when they want it.
required:
- type
- name
+28 -5
View File
@@ -40,6 +40,30 @@ under `kb/`. It is deliberately **not a collection** and carries no `COLLECTION.
no per-collection type surface, and `tools/wikitool docs verify` fails if a contract appears
here.
### Who owns a type-spec
`types/` holds two kinds of file, and the line between them is `root:` — already in the
frontmatter before anyone drew it:
| Type-spec | Describes | Owned by | Ships as |
|---|---|---|---|
| `root: kb` (`entity`, `concept`, `source`, `comparison`) | A page **this instance** writes | The instance | `types/<name>.md.template` plus its `.schema.yaml.template`, adopted by a rename |
| `root: repo` (`instruction`), no `base_dir` (`lint-report`), and `type-spec` itself | A stack artifact | The stack | Verbatim |
A page type-spec's prose, its `## Template` body and its language are therefore the instance's
to rewrite — an instance writing its pages in another language simply translates the file, and
an upgrade does not take that back. Improvements to a shipped default reach it as an *offered*
migration ([instructions/CONTRACT.md](../instructions/CONTRACT.md#instructionsmigrations)),
never by overwriting.
**What the stack still requires of the type layer is one line.** There must be a type-spec
declaring `name: source` whose schema requires `raw_files:` — the whole `raw/``kb/`
provenance path (`sources coverage`, `[^cite-id]` resolution, `kb/provenance.md`) asks
`page.kind == "source"`, so without it nothing resolves. `docs verify` checks exactly that and
nothing beyond it: not the directory, not the title prefix, not a word of the prose. Which
collection is stack-required is *derived* from where that type writes rather than listed
separately, so renaming it stays consistent instead of tripping a hardcoded name.
**Quality goal:** a type-spec is the single source of truth for its type. No structural fact
about a page type may be restated anywhere else — not in `AGENTS.md`, not in a skill, not in
Python. Adding a type must require no code change.
@@ -98,11 +122,10 @@ The `## Template` block is filled from the page's own frontmatter, plus `{name}`
`{entities|table_cells}`. `{field|literal text}` falls back to the literal when the field is
absent.
Three further variables come from the instance rather than from the page:
`{section.relationships}`, `{section.see_also}` and `{section.footnotes}`, filled from
`kb/CONVENTIONS.md`'s `sections:` declaration. A template writes a tool-owned heading through
one of those and never as literal text - that is what lets an instance change the KB language
without editing anything under `types/`.
**A template never contains a tool-owned region.** The links and footnotes regions are generated
between markers by `xref` and `cite`, rendered from frontmatter, and re-rendered on every write -
so scaffolding them would create a section an author is forbidden to edit and the tool would
replace anyway. See `tools/chemenu/blocks.py`.
### Ownership boundary