Rendered from docs/evaluations/linkml-worked-example.md in the Headwater
corpus. Every document on this half of the site is typed by the taxonomy
the descriptor names: corpus.json.
The Headwater taxonomy in LinkML — a worked example
Evidence for Q13. HW-EVAL-adjacent-work claimed LinkML covers the "structural half" of spec 2 and none of the "governance half". Writing it out shows that framing was wrong — and the real boundary is more useful than the one I guessed.
The schema
A meaningful subset of the default taxonomy: three kinds, the core facets, four relations, one heterogeneous shelf, identifiers, and profiles.
id: https://w3id.org/headwater/taxonomy/standard
name: headwater_standard
title: Headwater standard taxonomy
description: >-
The default documentation taxonomy, expressed in LinkML. Governance semantics
that LinkML has no metaslot for are carried in `annotations` under the headwater
prefix; see the analysis for which of those it can and cannot act on.
license: https://creativecommons.org/publicdomain/zero/1.0/
prefixes:
linkml: https://w3id.org/linkml/
headwater: https://w3id.org/headwater/taxonomy/
prov: http://www.w3.org/ns/prov#
skos: http://www.w3.org/2004/02/skos/core#
default_prefix: headwater
default_range: string
imports:
- linkml:types
settings:
namespace: "[A-Z]{2,6}"
seq: "\\d{4}"
# ---------------------------------------------------------------- vocabularies
enums:
LifecycleState:
description: Where a document sits in its lifecycle.
permissible_values:
draft:
description: Being written; not yet authoritative.
annotations:
headwater:role: initial
current:
description: Authoritative and believed true.
annotations:
headwater:role: live
superseded:
description: Replaced by a successor; retained for lineage.
annotations:
headwater:role: terminal-retained
deprecated:
description: No longer applicable; retained.
annotations:
headwater:role: terminal-retained
EvidenceBasis:
description: How the rationale in a document is grounded.
permissible_values:
evidenced: {description: An external auditable artifact supports this.}
reconstructed: {description: Written after the fact; basis must be stated.}
gap: {description: No evidence exists and none is claimed.}
GovernanceDocType:
description: Discriminator for the heterogeneous governance shelf.
permissible_values:
standard: {description: A prescriptive rule set.}
methodology: {description: A repeatable procedure for producing something.}
runbook: {description: Steps to carry out an operational task.}
register: {description: A list maintained as the record of something.}
# ----------------------------------------------------------------------- facets
slots:
id:
description: Stable, globally unique, resolvable without its document.
identifier: true
structured_pattern:
syntax: "{namespace}-DR-{seq}"
interpolated: true
status:
description: Lifecycle state.
range: LifecycleState
required: true
annotations:
headwater:role: state
last_verified:
description: The date a human last confirmed this document is true.
range: date
required: true
annotations:
headwater:role: freshness
headwater:stale_after_days: 180
summary:
description: One sentence. The corpus's scent surface — routing and indexes show this.
required: true
annotations:
headwater:role: scent
audience:
description: Who this is written for.
recommended: true # advisory severity, natively
multivalued: true
doc_type:
description: Which kind this is, on a shelf that holds several.
range: GovernanceDocType
designates_type: true # LinkML's own discriminator concept
evidence_basis:
range: EvidenceBasis
required: true
# -------------------------------------------------------------------- relations
supersedes:
description: This decision replaces the target.
range: Decision
multivalued: true
slot_uri: prov:wasRevisionOf
annotations:
headwater:family: succession
headwater:nuclearity: multinuclear
headwater:dominance: source
headwater:inverse: superseded_by
headwater:reciprocal: required
headwater:created_by: scaffold
superseded_by:
range: Decision
slot_uri: prov:wasRevisionOf
conflicts_with:
description: This decision contradicts the target.
range: Decision
multivalued: true
annotations:
headwater:family: association
headwater:reciprocal: symmetric
headwater:invalid_when: both_current
derives_from:
description: A generated artifact and the canonical source it projects.
range: Standard
slot_uri: prov:wasDerivedFrom
annotations:
headwater:family: derivation
headwater:nuclearity: nucleus-satellite
headwater:nucleus: target
headwater:inherits: "status, last_verified"
headwater:created_by: generator
governs:
description: Source paths this document is authoritative for.
multivalued: true
annotations:
headwater:family: governance
headwater:dominance: source
headwater:created_by: author
# ------------------------------------------------------------------------ kinds
classes:
Document:
abstract: true
description: Anything in the corpus.
slots: [id, status, last_verified, summary, audience]
unique_keys:
primary:
unique_key_slots: [id]
Decision:
is_a: Document
description: Why a choice was made and what it forecloses.
class_uri: headwater:Decision
exact_mappings: [adr:ArchitectureDecisionRecord] # SKOS mapping, natively
close_mappings: [platform:DesignRecord]
slots: [supersedes, superseded_by, conflicts_with, evidence_basis]
annotations:
headwater:purpose: rationale
headwater:voice: declarative
headwater:lifecycle: standard
headwater:authority: 20
headwater:sections_required: "Context, Decision, Consequences"
rules:
- description: >-
A superseded decision must name its successor. This one LinkML can
enforce, because both slots belong to the same instance.
preconditions:
slot_conditions:
status: {equals_string: superseded}
postconditions:
slot_conditions:
superseded_by: {value_presence: PRESENT}
Specification:
is_a: Document
description: What a component does, as it is now.
slots: [governs]
annotations:
headwater:purpose: behavior
headwater:voice: declarative
headwater:authority: 10
headwater:contracts_allowed: "true"
Standard:
is_a: Document
description: A prescriptive rule set on the governance shelf.
slots: [doc_type, governs]
in_subset: [service_repo, docs_only]
annotations:
headwater:purpose: constraint
headwater:authority: 15
# --------------------------------------------------------------------- profiles
subsets:
service_repo:
description: A repository that authors its own specs and standards.
docs_only:
description: A documentation-only repository.
What LinkML does natively, and does well
Four of these were genuinely surprising — not "can be encoded" but "is the same concept, already named".
| Headwater concept | LinkML | Note |
|---|---|---|
| Kind | classes with is_a, abstract |
Direct |
| Facet | slots with range, required, pattern |
Direct |
| Controlled vocabulary | enums + permissible_values |
Plus meaning: for ontology grounding, which we did not have |
| Advisory facet severity | recommended: true |
A native metaslot for exactly our warn-level |
| Heterogeneous-shelf discriminator | designates_type: true |
The identical concept, independently arrived at |
| Cross-taxonomy mapping | exact_mappings, close_mappings, broad_mappings… |
SKOS mappings on every element, natively — change #9, for free |
| Identifier scheme | identifier: true + structured_pattern + settings |
Better than our sketch: the pattern is composable |
| Profile | subsets + in_subset |
Direct |
| Lineage semantics | slot_uri: prov:wasRevisionOf |
PROV alignment, natively — change #11, for free |
| Relation endpoints and cardinality | range to a class, multivalued |
Direct |
Three of the twenty research-derived changes (SKOS mappings, PROV alignment, advisory severity) are things LinkML already ships. That is a real argument for adoption, and also mild evidence the modeling instincts in spec 2 were conventional rather than eccentric.
Where it stops — and the boundary is not the one I claimed
HW-EVAL-adjacent-work said the split was structural versus governance. It is not. Look at what actually fails:
- Reciprocity.
supersedesrequires the target to link back. LinkML cannot say this. Neither can SHACL without dropping to SPARQL. conflicts_withinvalid when both endpoints are current. The rule above enforces the one constraint whose slots live on a single instance. The constraint we actually want reads the target'sstatus— and per-instance validation does not see it.- Satellite inheritance. "A satellite's freshness follows its nucleus" is a statement about a pair, resolved by traversal.
- Sequence expectations. "A current decision acquires an implementing specification within 90 days" is a query over the graph and over time.
- Overlays, core, compatibility. Operations on the schema itself, not statements in it.
None of that is "governance" as opposed to "structure". Reciprocity is as structural as anything in spec 2. The actual line is:
LinkML, SHACL, and JSON Schema all validate one instance against a shape. Everything Headwater does that they cannot is a property of the graph as a whole, or of the corpus over time.
That reframing matters, because it turns Q13 from "does LinkML cover enough?" into a better question: is a two-layer architecture — a standard shape layer plus a Headwater graph layer — better than one custom layer? Every mature validation stack in this space has that shape. It is not a compromise. It is the normal answer.
The annotations problem
Everything Headwater-specific above sits in annotations, and annotations are untyped pass-through. LinkML carries them and does nothing with them. It gives no validation, no generator output, and no error when headwater:nuclearity is misspelled or set to a value that does not exist.
So for precisely the half that is ours, the meta-schema benefit — the main reason to adopt LinkML — evaporates. We would still write a validator for the annotation vocabulary. Authors would then face two languages in one file, with no visual distinction between the half that is checked and the half that is not. That scores badly on role-expressiveness and error-proneness, which is exactly what the cognitive-dimensions walkthrough in Q2 is meant to catch.
Writing it out is what made this concrete. It reads fine until you notice that a third of the semantics is inert.
The option this exercise surfaced
Neither "adopt LinkML" nor "stay independent" is right. A third option:
Author in Headwater's language; emit LinkML as a compilation target.
The resolved taxonomy compiles to a LinkML schema covering the shape layer. That schema then compiles onward to JSON Schema, SHACL, OWL, and Pydantic through LinkML's own generators. The graph and temporal layers stay in the Headwater engine, where they were always going to live.
That gets the interoperability without the two-languages problem: one authoring surface, fully validated, with a standards-based export that other tooling can consume. It also inverts the risk. Adopting LinkML as the authoring surface is close to irreversible. Emitting it is a generator we can add, change, or drop.
Worth noting how this rhymes with the position already taken on distribution: a resolved artifact, emitted, not authored.
Recommendation
Take option 3 into the Q2 walkthrough as the leading candidate, with these consequences to weigh:
- It removes the Q1 tension entirely. A Rust core emitting LinkML YAML has no dependency on LinkML's Python tooling.
- The SHACL question resolves itself. SHACL becomes an output artifact for external consumers, so its poor error messages never reach a Headwater author.
- The cost is a generator plus fidelity tests proving the emitted schema accepts exactly the documents Headwater accepts, which is a real and ongoing cost.
- The shape/graph boundary needs to be stated in spec 6 as an architectural seam, because it is one.