ADR-0112: The Element-Kind Catalog — Semantics in the Model, Not Only in the Code¶
- Status: accepted (2026-08-09 — "I read the ADR. Looks good to me", with the §2b naming amendment folded in from the same reply)
- Date: 2026-08-09
- Deciders: Nicolas Burri (register #33k arc — "we do have an honest model issue here and only applying a frontend fix feels like a decision that will come back and bite us … I would therefore prefer a solution that fixes the underlying inconsistencies, even if this means that we might break functionality that was greenlit days ago"; option 1 chosen; extensibility: "ideally we should have the option to extend the catalog of well-known elements in the future … a medical device that is not software … stakeholder requirements and then probably system requirements")
- Relates to: ADR-0079/0082 (risk items and their chains), ADR-0086 (trace chains; the edge-type-set consolidation this ADR builds on), ADR-0095 (key-rule governance — unchanged), ADR-0084 (design kinds — a per-scope vocabulary precedent), ADR-0097/0109 (the wizard this re-founds), register #33k (the symptom trail), docs/planning/keyrules-config-mechanism-note.md (SUPERSEDED — its UI-only premise was rejected by exactly the red pen it asked for).
Context¶
The trace-item kinds (requirement, design-element, risk, risk-control, user-need,
validation-case, regulatory-ref) carry rich semantics — mitigates chains, the
stakeholder → satisfies → requirement → verifies walk, gap flags, matrix membership — but ALL
of it lives in derivation code. The model knows kinds only as flat prefix strings
(requirement_key_rule), and the wizard presents them as such. Symptoms found in one
play-test evening (#33k): may-define toggles exist only for the risk family (two-endpoint
history); key rules render disconnected from the types that give them meaning; a default
project's vocabulary (REQ- only) cannot express the chain its own traceability matrix is
built to walk. A UI-only fix was drafted and rejected: the inconsistency is in the model.
Decision¶
- A system-defined element-kind catalog. Each kind carries: identity (slug), display identity, default prefix, its semantic role in the derivations, and its dependencies. The catalog ships with the product (migrations), like the permission catalog (ADR-0012 closed-set pattern). It is NOT user-editable — the system is opinionated about what a risk is; that opinion is why it can derive anything at all.
- Scopes activate kinds; a key rule is the visible half of an activation. The wizard's "Special elements" section (the #33k target UX, unchanged in look) becomes the activation surface: activating a kind creates its key rule (prefix editable pre-first-use, then the ADR-0095 freeze exactly as today) and makes it offerable in the per-kind defining-types selector. Deactivation is refused while dependent kinds are active or items exist. 2b. Per-scope naming (his amendment). An activation carries the scope's OWN display label alongside its prefix — naming conventions differ between organizations ("I have seen quite different approaches to naming conventions"), so the project defines what its people see. Both label and prefix are editable only until the first item of that kind exists, then frozen together under the ADR-0095 governance (one rule, one freeze, one exception act). The freeze is not pedantry: a released document's vocabulary must not be renamed underneath the records that use it. The catalog keeps the canonical slug (what derivations key on — semantics never follow a label) and the default label/prefix a fresh activation starts from; a scope override is a single literal in the project's language.
- Dependencies are structural (activation pulls prerequisites; the wizard shows the pull in place): | kind | requires | |---|---| | risk ⇄ risk-control | each other (pair — activate together; his rule). Content stays free: acceptance-without-reduction needs no control items. | | user-need | requirement (satisfies needs a target) | | validation-case | user-need | | regulatory-ref | — (standalone) | | requirement, design-element | — (standalone; the supplier lane is the live proof) |
- Custom kinds — a deliberate, semantics-free tier. Operators may define scope-local kinds (own slug + prefix). They get: keyed authoring, save-time validation, citability, generic trace links (the link graph is already kind-agnostic data), audit-pack presence — and explicitly NO derived chains, gap flags, or matrix membership, stated as a visible badge ("custom — outside derived traceability"), never discovered as a disappointment. Use cases: assumptions, SOUP inventory, hazard lists, project-local registers.
- Extending the well-known catalog is a product act, and chains become data to make it
cheap (his requirement). A future kind — the hardware lane's
system-requirementbetween stakeholder needs and verification — arrives as catalog rows PLUS a chain definition (sequence of kind/edge hops) shipped in a release. Wave 1 records the existing chains as catalog data and keeps the current walkers consuming their current shapes; the walkers migrate onto the chain definitions opportunistically (ADR-0086's edge-type sets are the half-built bridge). The named future test: addingsystem-requirementwith its matrix must be catalog rows + a chain row + a lane preset — no derivation rewrite. Until a walker is data-driven, a product extension that needs it also extends that walker; the ADR's claim is the model makes this additive, not free. - Migration: existing
requirement_key_rulerows become kind activations (same prefixes, same freeze state). Existing scopes violating a dependency (a control rule without risk — possible today) are NOT mutated: the derived dead-end check names them (setup-status pattern) and the wizard offers the completion. May-define stays per (type, kind), edited in the per-kind selector; both historical endpoints remain (unification is not this ADR).
Clarification 2 (2026-08-12): key-rule FAMILIES — the counter belongs to the system¶
Decided by Nicolas from the PULSEMED walk (register #42j; his un-parking argument after a brief overfitting debate: "having different labels for different stakeholder requirements seems not too uncommon. And by removing 42j users need to start tracking e.g. the current number of the next free requirement in their section as we only track one global counter"). The live witness: PULSEMED's 72 stakeholder requirements across four key families (NA 9 / GA 41 / MA 18 / ZP 4 — Johner stakeholder classes), invisible to every surface and unsuggestable by the one counter.
- A kind's activation may carry N named FAMILIES (category label + prefix + sort), the §2b naming grammar applied once more: naming conventions are the project's own language. Every existing activation becomes exactly one family carrying its current prefix+label — single-family scopes behave byte-identically.
- An item's category is DERIVED, never stored: the family whose prefix
longest-prefix-matches its key. No content change, no new attribute — imported corpora
categorize RETROACTIVELY the moment their families are declared; the arrival bundle's
categorydata (already emitted) becomes validation, not storage. - Suggest-next-key becomes per-family (max suffix + 1 within the family), with a category picker when N > 1 — the counter is the system's job, never a human's head.
- The §2b freeze applies PER FAMILY: label+prefix freeze once any item matches the family's prefix; the ADR-0095 exception act covers families as it covers the kind.
- The #45 case-collision guard (same walk, same evening) operates at key level and is family-agnostic by construction.
Non-decisions, made explicit¶
- User-configured dependency chains (his option 2) are PARKED with a named trigger: a real customer chain the catalog cannot express. Config only at proven variation points — the catalog's dependencies-as-data is the substrate that option would need anyway.
- Matrix rendering for future kinds is decided with the first product extension that ships one, not here.
- The two may-define endpoints stay as they are (routed per kind); merging them is refactoring, not architecture, and waits for a reason.
Consequences¶
- A real arc, in waves: W1 catalog + activations + migration + the wizard section on the honest model + dead-end checks (+ journey/preset adaptation; supersedes the mechanism note's plan and breaks the config step greenlit days ago — accepted by him in those words). W2 the custom-kinds tier. Later walkers-on-chain-data, pulled by the first real catalog extension.
Clarification 1 (2026-08-10): the stakeholder/software split becomes catalog rows¶
Decided by Nicolas from the v0.9.4 play-test (register #37b — "Requirement is only present as SWREQ… still at odds with the requirements resolution chain"), with his naming proposal from the same reply ("call the other ones system requirements instead of software requirements to make it more versatile"). This is §5's own "named future test" arriving early, exercised on the requirement family instead of waiting for the hardware lane.
stakeholder-requirementjoins the well-known catalog (default label "Stakeholder requirement", default prefixSTK-— the seed's own convention;traceabilitygroup, sorted beforerequirementso the resolution chain reads top-down). Dependency:stakeholder-requirement → requires → requirement(satisfies needs a target — the user-need precedent,warn).- The kind becomes
system-requirement— slug, fence and label (his versatility rule — a medical device that is not software has system requirements; for a pure-software product the system is the software). As first written (same day), the slug stayedrequirement"frozen by reality — released documents are immutable." His challenge corrected the premise: there is NO productive system yet; every instance, corpus and backup is regenerable at will, so the freeze binds nobody today. Decided (his words): the meridian-qms import is a test corpus, updating it is fine; "once we officially start to have a productive system … these changes will require a lot more focus and documentation. But for now, we are still free." So the rename goes ALL the way while it still can: catalog slugsystem-requirement, content fence:::system-requirement(V093 + the mechanical sweep; the genus/species confusion dies in the developer-facing layer too, and a futuresoftware-requirementkind lands beside an honestly-named sibling). The supplier lane keeps IEC 62304's own term via §2b: the lane preset overrides the scope label to "Software requirement" (prefixREQ-unchanged). - The chain sheds its only attribute-qualified hop, and the legacy form dies at birth.
requirement-chainhop 1 keys on the KIND (stakeholder-requirement --satisfies(inbound)--> system-requirement), not onkind="stakeholder". As first written,kind="stakeholder"items were to be honored forever as a UNION — the same corrected premise applies: with no productive content in existence, the importer emits first-class:::stakeholder-requirementfences at the source, the corpora regenerate, and NO instance can produce the legacy form. The stakeholder predicate is therefore type-keyed only, thestakeholdervalue leaves the requirement-kind attribute vocabulary, and no compatibility layer ships for a past that no longer exists. (The freeze his challenge deferred becomes REAL at the first productive instance — from that day, this class of rename is a governed migration with its own focus and documentation, in his words.) - The §5 named future test inverts, additivity unchanged: with
requirementas the system level, a future software-below-system tier arrives as a newsoftware-requirementkind beneath it (catalog rows + a chain row + a preset — still no derivation rewrite). user-needstays a distinct kind (validation axis: user-need → validation-case) fromstakeholder-requirement(resolution axis: → satisfies → requirement). Unifying the two axes is not this clarification; if a real corpus proves them one concept, that is its own decision.- The lane presets become kind selections validated against the catalog (a preset can no longer express an incoherent vocabulary).
- The document-view risk/control affordances are untouched — semantics stay with the kinds.
- Manual: the birth-path and traceability topics gain the catalog story; the ops guide (#26) nothing (scope-level, not instance-level).