Four Facts in a Line: What Hand-Copied Shapes Throw Away
Read one triple constraint out of a ShExC file: a predicate, a value constraint, a cardinality marker, a semicolon. It looks like a field declaration. It is denser than that.
Four Facts in a Line: What Hand-Copied Shapes Throw Away
Read one triple constraint out of a ShExC file: a predicate, a value constraint, a cardinality marker, a semicolon. It looks like a field declaration. It is denser than that.
That one line makes four separate machine-checkable assertions. The predicate is an IRI, not a field name — a globally resolvable identifier unrelated systems agree on. The value constraint names a datatype from a published vocabulary, not a local type alias. The cardinality marker is exact: a bare constraint means exactly one, ? at most one, * any number including zero, + at least one, and {n,m} states the range in full. And a constraint pointing at another shape declares that this value is not a scalar at all, but a conforming structure of its own.
Four assertions, one line. That precision is why people write shapes — and why the customary way of moving shapes around does so much damage.
The file has exactly one reader
In most organizations, a shape file is consumed by validators and nothing else. The warehouse does not read it, the event bus does not read it, the API gateway does not read it. So the contents get re-expressed by hand — somebody reads the shapes and writes the tables, or the reverse — and that transcription becomes the only bridge between the semantic estate and the operational one.
What survives a transcription is the vocabulary. What dies is the grammar.
The IRI dies first: schema:name becomes a column called name, which keeps the word and discards the identifier, and a year later nothing demonstrates that the two were ever the same concept. Cardinality dies next, because it is one character wide and reads like punctuation — "at most one" and "any number" both flatten into "a field," and the difference reappears as a rejected submission the first time anything checks. The closed list dies quietly: a value set enumerating exactly which values conform becomes free text, or an enum in one service that trails the shape by a release. Specialization flattens last, because EXTENDS retyped by hand becomes duplicated fields, with the relationship gone.
And nothing goes red when the copies diverge. A transcription is accurate the day it is written; the problem is that two artifacts now describe one entity, in different repositories under different review processes, with no build step comparing them. Divergence is not a risk with a probability attached. It is a certainty with a schedule, and the notification arrives from outside.
The obligation runs both ways, and the reverse hurts more
Plenty of teams meet this from the other side. Their model is governed in tables and JSON Schema, and then an obligation lands — a research data commons that reviews submissions as shapes, a linked-data partner, an interoperability clause in a contract — and the question flips from "who can read our shapes" to "who here can write ShExC at all?" The syntax reads pleasantly and authors like a specialist skill, because it is one. Standing that skill up to hand-maintain a parallel copy of a model you already govern buys a second instance of the decay problem, in a language fewer people can review.
What we built
In CoreModels, shex is a first-class schema format key in the transform engine, and it moves both ways: ShExC text in, ShExC text out.
On import, a ShExC document stops being a file and becomes governed structure. Each shape becomes a Type. Each triple constraint becomes an Element that already knows whether it is required or optional and whether it holds one value or many, because the cardinality markers are read as facts — numeric ranges included, where a minimum of at least one means required and a maximum above one means a collection. Each inline value set becomes a Taxonomy carrying its terms in order, a shape reference becomes a typed reference to another model rather than an undocumented join, and EXTENDS becomes real inheritance in the graph.
Writing into a project takes the Admin role; reading a schema back out is a Viewer read, so generated artifacts stay cheap while the definition stays accountable. Export runs the same road in reverse from any governed project — including one that started as CREATE TABLE statements and has never seen a triple. The encoder synthesizes shape names where the model has none, declares exactly the prefixes it used, and emits plain ShExC that conformant tooling consumes.
The dividend nobody has to fund
Here is the part that changes the arithmetic. Because ShEx predicates and shape names are already IRIs, the importer lifts them directly into the model's cross-standard mapping annotations — no sidecar file, no proprietary keyword, no separately budgeted curation workstream. A shape named for a public class maps its Type to that class; a schema:name predicate maps its Element to that vocabulary term. Purely structural predicates such as a and rdf:type are deliberately skipped, since they carry no domain meaning worth recording.
From there the meaning travels with the model. Ask the same project for JSON Schema and the property arrives carrying its vocabulary URI as a machine-readable annotation. Ask for LinkML and it arrives as a slot URI on the attribute and a class URI on the class. Ask for an OWL ontology and the shape's class equivalence is stated outright.
The seams, stated out loud
Moving between a graph-shape language and record-shaped formats costs something, and hiding the cost would reproduce the exact failure the retyped copy created. Every import and export returns a lossiness ledger beside the result: records naming the kind of change, the exact path, and a plain-English reason.
ShEx exercises that ledger in two characteristic ways on the way in. A shape declaring more than one parent is narrowed to a single parent, recorded as a semantic narrowing that names the shape and its parent count. A value constraint with no equivalent primitive is approximated as a string, recorded against that element — with the original token preserved, so the export re-emits what you sent rather than our approximation of it. An empty ledger means a clean conversion; a non-empty one is a review checklist delivered at conversion time instead of discovered in production.
That is the need this format support answers. Not a logo on a compatibility matrix — the gap between an organization's most precise description of an entity and the systems that process it, and the hand-maintained document that keeps falling into that gap.
The transform quickstarts at https://coremodels.example.com walk both directions end to end.