Files
chemenu/kb/CONTRACT.md
T
torben 2c4c2b1c7c
CI / verify (push) Failing after 57s
Release / release (push) Successful in 37s
stack: TOC-Pflicht fuer Referenzdateien ueber 100 Zeilen (docs toc); session-setup.md/gates.md nennen die tatsaechliche Budget-Ausnahmeliste (schliesst #73, #76)
Files changed:
- AGENTS.md
- CHANGES.md
- VERSION
- instructions/CONTRACT.md
- instructions/capture-session.md
- instructions/claude-code-model-selection.md
- instructions/dev/issue-tracking.md
- instructions/dev/testing-conventions.md
- instructions/dev/version-parts.md
- instructions/evolve-subtypes.md
- instructions/gates.md
- instructions/german-terminology.md
- instructions/ingest-large-tree.md
- instructions/kb-profiles.md
- instructions/link-taxonomy.md
- instructions/mcp-read-server.md
- instructions/migrate-corpus.md
- instructions/migrations/3.0.0-authoring-conventions.md
- instructions/migrations/4.0.0-link-taxonomy.md
- instructions/private-instance.md
- instructions/session-setup.md
- instructions/setup-instance.md
- kb/CONTRACT.md
- kb/CONVENTIONS.md
- kb/concepts/COLLECTION.md
- raw/CONTRACT.md
- tools/CONTRACT.md
- tools/chemenu/commands/docs_verify.py
- tools/chemenu/commands/instructions_cmd.py
- tools/chemenu/tests/test_docs_verify.py
- tools/chemenu/tests/test_instructions_cmd.py
- tools/chemenu/tests/test_toc.py
- tools/chemenu/toc.py
- types/type-spec.md
2026-09-09 20:38:42 +02:00

330 lines
19 KiB
Markdown

# kb/ - Knowledge Layer Contract
The compiled knowledge layer, and the third stage of the pipeline
`raw/` -> `kb/` -> `reports/`. Everything here is written and maintained by the LLM from
material in `raw/`, and is expected to stay correct without being re-derived.
**Quality goal:** a page should answer a future question *without* re-reading the source it
came from. If answering still requires the raw file, the page is incomplete.
This file holds the rules that apply in **every** collection **and in every instance**. That
second half is the cut: what is written here is enforced by `tools/wikitool` or follows from
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, 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.
The split is by **who may change the sentence**, not by what it is about. Language, tone and
naming used to sit here, which meant every instance that answered "not German" to
`setup-instance.md` was locally editing a file the stack also ships - and a merge from upstream
would quietly hand it back.
Structural facts (which frontmatter fields exist, which are required, what the body skeleton
looks like) are in neither - they belong to the type-specs and are printed by
`tools/wikitool types describe <type>`. Never hand-write frontmatter; scaffold with
`tools/wikitool new <type> --name "<Name>" --set field=value ...`.
<!-- wikitool:toc -->
## Contents
- [Collections](#collections)
- [Generated files](#generated-files)
- [Titles are identifiers](#titles-are-identifiers)
- [Every page should](#every-page-should)
- [Quotation cap](#quotation-cap)
- [Language and identifiers](#language-and-identifiers)
- [Generated regions](#generated-regions)
- [Linking](#linking)
- [Provenance and citation](#provenance-and-citation)
- [Confidence](#confidence)
- [Confidence against source standing](#confidence-against-source-standing)
- [What does not belong here](#what-does-not-belong-here)
<!-- /wikitool:toc -->
## Collections
`kb/` is a **namespace, not a collection**. It carries no `COLLECTION.md` of its own.
A directory under `kb/` is a **collection** exactly when it contains a `COLLECTION.md`. That
file is the local authoring contract for every page in the subtree, and it belongs to the
instance: it declares in its frontmatter which profile from
[instructions/kb-profiles.md](../instructions/kb-profiles.md) it adopted, and whether the stack
resolves against it by name.
| Field | Means |
|---|---|
| `profile:` | Which catalogue entry this contract started from, or `none`. Free text - the catalogue is a palette, not an enum, and a collection an instance invented has no entry to name |
| `required_by_stack:` | Whether `wikitool` itself depends on this collection *by name*. Not the instance's to choose: `docs verify` checks it against the stack's own list. `kb/sources/` is `true` - `sources coverage`, `[^cite-id]` resolution and `kb/provenance.md` all resolve against that name - and everything else is `false` |
- A subdirectory *inside* a collection is an **area**. It inherits the enclosing contract and
must not carry a `COLLECTION.md` of its own - `kb/entities/systems/` is an area of
`kb/entities/`.
- **An area is as deep as a page goes.** `kb/<collection>/<page>.md` and
`kb/<collection>/<area>/<page>.md` are the two depths a page may sit at; nothing goes a level
deeper. A further subdirectory is not a second-level area - it is invisible to the generated
catalog, which reads exactly two path segments below `kb/` and folds anything past them into
the area's own table silently, with no location of its own. That is why
`wikitool lint`'s `nested_pages` finding is a hard error rather than an advisory one like
`misplaced_pages`: a misplaced page still catalogs correctly from the wrong place, a nested
one makes the catalog itself wrong. A grouping axis that does not come from a type-spec's
`layout:` - project owner was the case that surfaced this - does not earn a second directory
level; it goes into frontmatter instead.
- A `COLLECTION.md` nested inside another collection is invalid.
- `COLLECTION.md` appears **nowhere outside `kb/`**. `raw/`, `types/`, `tools/`, `reports/`
and `instructions/` are not collections and carry a `CONTRACT.md` or a root type-spec
instead.
`tools/wikitool docs verify` enforces all three.
| Collection | Holds | Contract |
|------------|-------|----------|
| `kb/entities/` | Concrete things: projects, deployed systems, tools, technologies, people | [entities/COLLECTION.md](entities/COLLECTION.md) |
| `kb/concepts/` | Architectures, patterns, protocols, workflows, decisions, recurring problems | [concepts/COLLECTION.md](concepts/COLLECTION.md) |
| `kb/sources/` | One summary page per ingested source, carrying its `raw_files:` provenance | [sources/COLLECTION.md](sources/COLLECTION.md) |
| `kb/comparisons/` | Structured comparisons of two or more existing pages | [comparisons/COLLECTION.md](comparisons/COLLECTION.md) |
The four rows above are this instance's collections, not a fixed set. **Adding one:**
`mkdir kb/<name>` and write a `kb/<name>/COLLECTION.md` with the two fields above. Collections
are discovered by contract presence, so no code change is needed. A collection only becomes
*writable* once some type-spec declares a matching `base_dir:`. Renaming or dropping one is the
instance's call too - except where `required_by_stack: true` says otherwise.
**Where a page goes** is decided by its type-spec, never by hand - see
[types/type-spec.md](../types/type-spec.md).
## Generated files
Never hand-edit these; they are produced by `tools/wikitool`:
| File | Produced by |
|------|-------------|
| `kb/index.md` | `wikitool index rebuild` - the catalog **map**: statistics, counts, links |
| `kb/<collection>/INDEX.md` and `kb/<collection>/<area>/INDEX.md` | `wikitool index rebuild` - the page tables |
| `kb/log.md` | `wikitool log append` |
| `kb/provenance.md` | `wikitool sources rebuild-index` |
To *find* a page, search rather than read the catalog: `tools/wikitool search "<text>"`, or
`tools/wikitool search --field <predicate>` for a structured query over frontmatter.
## Titles are identifiers
**The filename stem *is* the page title, and `[[wikilinks]]` must match it exactly.** That is
not a naming preference; it is the wiki's only way to address a page. `wikitool lint` reports an
H1 that stops matching its title, `rename`/`rm` rewrite every reference to a stem, and a
`[^cite-id]` resolves through one.
Which *form* those titles take - spaces or kebab-case, singular or plural, what prefixes a
decision record - is the instance's, in
[kb/CONVENTIONS.md § Naming](CONVENTIONS.md#naming).
## Every page should
- [ ] Carry a clear, descriptive title and a summary near the top
- [ ] Use consistent terminology with the rest of the wiki
- [ ] 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`)
## Quotation cap
At most 2 blockquoted lines per page. `wikitool lint` reports overages as advisory, since
exceeding the cap can be a legitimate judgment call - but the page should carry the knowledge
itself, not delegate it to quotations. The cap is about how much of the page you let quotes
carry; it does not apply to text you are citing verbatim from a source.
The register those lines are written in - what counts as a buzzword, what filler is refused -
is the instance's, in [kb/CONVENTIONS.md § Tone](CONVENTIONS.md#tone).
## Language and identifiers
*Which* language pages are written in is [kb/CONVENTIONS.md](CONVENTIONS.md)'s to say. What
follows here is the part that is not a choice, because the tool resolves against it.
Every line of a page is either **prose** or an **identifier**. Only prose is translated.
**Prose:** descriptions and definitions, `## Key Information` values, `## Details` body text, a
source page's Summary / Key Takeaways / Action Items / Not Extracted, and `summary:`.
**Identifiers - never translated, in any language:**
| Identifier | Why |
|---|---|
| Page titles, and the H1 that repeats one | A title is the wiki's only identifier for a page and follows the subject's own established name - see [Titles are identifiers](#titles-are-identifiers). `wikitool lint` reports an H1 that stops matching its title |
| The subtype value on the generated `**Typ:**` line | It renders a schema enum value (`technology`, `workflow`), which `search --field` filters on. The label is prose; the value is not |
| `tags:` | Search keys, not prose |
| Commands, paths, config keys, hostnames, code | They are what they are |
| Quotations | Quoted verbatim in the source's own language |
Which foreign technical terms stay untranslated inside that prose is a judgment call the
instance records - see [kb/CONVENTIONS.md § Language](CONVENTIONS.md#language).
**A source in another language** is still summarized in the KB language: a source page is
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:`.
### Generated regions
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:
```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
**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:
```yaml
related:
- depends-on: Hermes
```
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).
## Provenance and citation
Every claim is either traceable to a raw file or explicitly marked as not.
- **`provenance:`** on every entity/concept page - `sourced` (every substantive claim traces
to a raw file), `general` (LLM general knowledge, no raw backing), or `mixed` (both; put the
unsourced part under a `## General Guidance (unsourced)` heading).
- **`raw_files:`** on every source page - concrete existing file paths under `raw/`, never a
directory and never a bare URL. For an external article also set `source_url:`, but
`raw_files:` must still point at the local copy under `raw/articles/`.
- **One source page may cover many raw files.** A folder of related documents becomes a single
page listing all of them, not one page per file.
- **A `[^cite-id]` footnote** appended to any *specific hard fact*: an IP, port, version, path,
command, or config value. `tools/wikitool cite add --page "<Title>" --source "Source - X"
[--file <qualifier>]` mints the id, upserts its `[[Source - X]]` (or
`[[Source - X|storage-model.md]]` for a multi-file source) definition in the page's trailing
Footnotes block (named per [Section headings](#section-headings)), and adds `Source - X` to
`sources:` - it prints the marker to paste at
the fact; placing it is still manual. Never hand-type a cite-id (AGENTS.md invariant 1). This
differs from a plain `[[Source - X]]` link, which only means "related to".
- **Notation inside code is notation, not a reference.** A `[^cite-id]` or a `[[wikilink]]`
written in backticks or a fenced block is read as an example: the citation does not count and
the link does not exist. That is what lets a page document this stack's own syntax. It also
means a marker appended to a line *inside* a fence cites nothing - put it on a source line
under the block (`<source-word>: [^cite-id]`, in the KB language), where it renders as a
footnote instead of travelling with the command when someone copies it.
- A source cited inline must also appear in the page's frontmatter `sources:` list;
`wikitool lint` checks this in both directions, and hard-errors on a leftover pre-migration
`^[[...]]` marker, an undefined `[^cite-id]` reference, or an orphaned Footnotes definition.
`tools/wikitool cite sync` reconciles a page's block after a prose edit changes which ids are
actually referenced.
- `tools/wikitool xref link-source --source "Source - X" --entities A,B,C` adds a new source
to every page it backs in one pass.
- Every raw file is expected to be claimed by some source page;
`tools/wikitool sources coverage` lists the ones that are not.
If no raw file or existing page backs an answer, say so explicitly rather than synthesizing
one - and never file the synthesized version back into the wiki.
## Confidence
`confidence_base` is the undecayed score set when a page's content is last confirmed;
`confidence` is *derived* from it by `tools/wikitool confidence decay` and must never be
edited directly.
Re-assess a page with `tools/wikitool touch --page "<Title>" --confidence-base <value>`.
What the number *means* - the base score, what raises it and by how much, and how to hedge in
prose below a threshold - is a rubric rather than a mechanism, so it is
[kb/CONVENTIONS.md § Confidence rubric](CONVENTIONS.md#confidence-rubric)'s.
### Confidence against source standing
The rubric already asks for an authority judgment ("+0.1 for official documentation, +0.05 for a
reputable secondary source") without anywhere to record it - source count lives in `sources:`,
recency in `modified:`, agreement is readable from the prose itself, but the authority term is
thought, applied, and forgotten; the next person re-derives it from scratch. `raw/CONTRACT.md`'s
capture fields (`fidelity`, `authority`) make it a recorded fact instead: `lint`
compares a page's `confidence_base` against the weakest capture standing of its cited sources and
reports one when the page claims more than its sources can carry.
**A ceiling, not a formula.** `confidence_base` stays hand-set (see above); this only bounds it.
The reasons are the same two, together: `confidence` is already derived from `confidence_base` by
one mechanism (`confidence decay`), and a second derivation of the same number would leave two
disagreeing answers to "what should this be" - and authority is a ceiling a page may fall under by
independent verification, never a determinant a formula could compute outright. The check is
therefore advisory, in the same class as `unsharded_collections`: it names a mismatch and asks a
human to look, it never fails a build.
The ceiling is stack vocabulary (`fidelity`/`authority` are - see `raw/CONTRACT.md`), not an
instance setting; an instance that disagrees with a specific finding overrides its judgment on
that one page, rather than reconfiguring the rubric:
| worst `authority` among a page's sources | ceiling | worst `fidelity` among a page's sources | ceiling |
|---|---|---|---|
| `normative` | (none) | `verbatim` | (none) |
| `reporting` | 0.8 | `published` | (none) |
| `opinion` | 0.6 | `secondhand` | 0.7 |
| `unknown` | no finding | `nontextual` | 0.7 |
| | | `unknown` | no finding |
The effective ceiling is the minimum of both columns. `unknown` never triggers a finding on its
own axis: a backfilled "we don't know" is not a claim about the source, and firing on it would
report every page citing a source captured before these two fields existed at once, which is
noise, not a finding.
## What does not belong here
- Raw source material - it stays immutable under `raw/`.
- Type definitions, frontmatter contracts, or templates - those live in `types/`.
- Procedures for operating the tooling - those live in `instructions/`.
- **Anything an instance would have to rewrite for itself** - language, naming forms, tone,
relationship labels, the confidence rubric. Those are `kb/CONVENTIONS.md`'s, and a sentence
of that kind here is a sentence the stack ships over the instance's own answer.
- Rules that apply to only one collection - those belong in that collection's
`COLLECTION.md`.
- Hand-edited generated files - see [Generated files](#generated-files).
- Generated lint reports - they are written to `reports/` and are not pages.
- Answers with no source behind them.