LinkML logoGovernance

Who Approved That Meaning? Governing a LinkML Model You Can Defend

Sooner or later somebody senior asks a question that sounds simple and is not: *how do you know the column in the warehouse means what the model says it means, and who decided that?*

Who Approved That Meaning? Governing a LinkML Model You Can Defend

Sooner or later somebody senior asks a question that sounds simple and is not: how do you know the column in the warehouse means what the model says it means, and who decided that?

"We're careful" is not an answer. Neither is a diagram. The only answer that survives the follow-up is a trail: this meaning came from this term, bound by this person; this projection into DDL relaxed these constraints, and here is the record; this estate was checked against the model on this run, and here is what did not line up.

Governed LinkML produces that trail as a by-product of ordinary work. Five properties do it.

Import adds; it does not overwrite

A LinkML import writes through the same profile layer everything else uses, creating the types, elements, taxonomies, and relations the document describes — and it upserts rather than duplicates: the encoder snapshots the relations that already exist first, so importing the same model twice does not produce two of anything. That matters more than it sounds; a governance process that punishes re-running the import gets bypassed.

The role split is enforced per project, not per good intention: writing a schema requires Admin, reading one out in any format requires Viewer, and the stateless mapping endpoint scopes authorization only, writing nothing.

Meaning changes are gated by a human, always

The interesting part of a LinkML model is not its shape but its meaning: the meaning, class_uri, and slot_uri bindings that tie a term to a published ontology. Those are carried in the graph as mapsTo values, and every path that can create one is deliberately narrow.

To ground a node, suggest_ontology_terms searches the public EMBL-EBI Ontology Lookup Service and returns ranked candidates — IRI, CURIE, label, description, ontology, type — in the order the service returned them. It is read-only and says so in its own response: suggestions are advisory only, nothing was written.

Binding is a separate, explicit act. bind_ontology_term requires Admin, is served only on the admin MCP endpoint, and writes exactly one mapsTo value — standard ols:<ontology>, URI the term IRI you named. It makes no external call; it records a decision a person made. unbind_ontology_term removes that one binding and nothing else.

The payoff shows up on the way out. Because the binding lives on the same carrier the transform stack reads, the next LinkML export emits it in the wire idiom: class_uri on the class, slot_uri on the slot, meaning on the permissible value — a compact CURIE when a declared prefix matches, the full IRI when none does. A decision made once, by a named person, is visible in every projection afterwards.

Loss is recorded, not absorbed

No two schema languages are the same size, so every crossing loses something. The governance question is not "did it lose anything" but "did it tell me."

Every transform call returns a lossiness array; each entry names a kind, a path, and a plain-language explanation. The four kinds: StructuralDrop (something had no home in the target), TypeApproximation (represented by a close-but-not-exact type), ConstraintRelaxation (a rule could not be enforced and was relaxed), and SemanticNarrowing (meaning was narrowed or guessed).

For LinkML, both directions have a published inventory. Coming in: an is_a naming a class this schema does not define drops the link, keeps the name verbatim, and records a narrowing; declared mixins are preserved by name but their slots not merged in; top-level slots: referenced by a class are flattened into inline attributes, as is any slot_usage refinement the flattening cannot apply — kept verbatim rather than quietly discarded; an unrecognized range approximates as a string and keeps the original range text. Going out: components have no LinkML construct and are dropped; free-standing relation instances are narrowed; a term hierarchy flattens because permissible_values are flat; nullability and collection bounds relax because LinkML expresses neither.

Unknown LinkML keys are not treated as loss at all — they ride along verbatim and are re-emitted where they were found. A decode fails only when the document is genuinely not a LinkML schema.

The rule the surface hangs on: success: true means the call ran, not that nothing changed. Read the ledger every time, as a review checklist rather than a warning log.

Nothing deploys before it is reviewable

The write-adjacent operations are dry-run-first by construction. The map endpoint never writes, so exploring a transformation costs a call. Map-import supports a dry run returning the plan, the would-be summary, and the ledger before anything lands.

And the mapping itself is an artifact. Every strategy — an authored guide, label-and-type inference, or an AI proposal — produces a plan; one universal validation gate accepts or rejects it; the gate's output executes deterministically; the executed plan comes back in the response. Attach it to a review, store it, replay it through the plan-execute endpoint, where it passes the identical gate again. A stored plan earns no shortcut.

Neither does an AI-proposed one. The ai mapping kind has a model propose a plan, then hands it to the same gate, with at most one repair attempt — a rejected repair is a rejection, and the gate is never relaxed for the proposer. A plan may carry a self-reported confidence score; the gate does not trust it. Because that path sends schema content to an external model API server-side, it also requires Editor or Admin membership.

Drift becomes evidence

Once the LinkML model is governed, the rest of the estate can be measured against it. Vendor artifacts — a dbt manifest, warehouse information-schema rows, registry subjects — are audited against the governed model and come back with coverage, drift, and conformance findings carrying stable codes such as field-unmapped, field-type-drift, enum-narrowed, and enum-widened. The audit is strictly read-only; recording the run in the history is opt-in; in CI, a non-zero errorCount is the fail signal.

The re-audit verb closes the loop for a model owner. It re-checks the estate snapshot stored at import time against the current governed model, needs no fresh artifacts, and always records the run. So when a curator changes the model, you can ask directly: given this change, what in the estate no longer conforms? Drift stops being an incident report and becomes a dated series you can point at.

That is the trust argument. Not that the tooling is careful — that its carefulness leaves a trail somebody else can check.

The transform and integration quickstarts in the CoreModels documentation show each of these steps as a single call.