The Cheapest Way to Break a Data Contract Is to Retype It
Here is one property from an ordinary data contract, written in the Open Data Contract Standard:
The Cheapest Way to Break a Data Contract Is to Retype It
Here is one property from an ordinary data contract, written in the Open Data Contract Standard:
a column named rcvr_id, marked primaryKey: true with primaryKeyPosition: 1, unique: true, physically a varchar(18), classification: restricted, with an encrypted counterpart column named enc_rcvr_id — and required: false.
Seven facts about one column. Three of them are not schema facts at all; they are governance facts. And one of them is a trap: in ODCS, being a primary key does not imply being required. The contract carries its own required flag, and here it says false. That is a deliberate statement by whoever wrote the contract, and it is exactly the kind of statement that does not survive a human retyping the column into a CREATE TABLE.
Watch the retype happen. The name survives. The varchar(18) survives. The uniqueness and the key survive, because DDL has words for those. The classification does not survive, because DDL has no word for it — maybe it lands in a comment, maybe in a ticket, usually nowhere. The encrypted twin becomes a follow-up task. And the primary key gets written NOT NULL, because that is what primary keys usually are, and the person doing the typing is being helpful.
Four fields in, and the copy already disagrees with the contract about who may see this column and whether it can be absent. Nobody logged any of it.
The parts that get dropped are the parts that make it a contract
Data contracts are not just schemas with better manners. The ODCS document that produced that column also carries quality checks with metrics, thresholds, and severities; business names and a granularity description for the table; tags and custom properties; partitioning positions; transform logic and source objects; a description block stating the contract's purpose, its limitations, and its intended usage; foreign-key relationships between objects; and whole sections describing the servers the data lives on, the team behind it, the roles, the SLA, support, and pricing.
Now ask where that goes in a JSON Schema. Or in a warehouse DDL. Or in a streaming schema. It does not go anywhere. Every one of those formats is a fine description of structure and a near-total silence about agreement. So each hand translation performs the same quiet amputation: it keeps the shape and drops the promise, and the resulting artifact looks complete, which is the worst possible property for an incomplete thing.
Naive automation fails in both directions
The obvious response is to script it. Two failure modes show up immediately, and teams learn the wrong lesson from both.
The first is brittleness. A converter that binds the contract to a fixed set of expected fields will hard-fail the first time it meets a key it does not know — a vendor extension, a field added by a newer revision of the standard, a section someone's platform team invented. The parse crashes on a document that is perfectly valid, and the contract gets a reputation as "the file that breaks the pipeline."
The second is silent stripping. A converter that reads only the schema section and ignores the rest produces output that runs fine and has quietly discarded servers, quality, SLA, and every custom property. Nobody notices for two quarters.
Both behaviors teach the same lesson: don't put contracts through tools. Which is how contracts end up back in a repository, reviewed at signing time, consulted at incident time, and connected to nothing in between.
And it has to run both ways
Many teams have the opposite problem from the one above. They do not have a contract to import; they have a governed model they have maintained for years, and a consumer who now wants a formal contract for it. Producing that by hand means learning the standard's required head, its valid kind, its logical-type vocabulary, and its structural rules — a week of spec reading to produce one YAML file that will be out of date the moment the model changes.
Hand-rolled scripts almost always do one direction. Real estates need both, often in the same quarter, sometimes for the same dataset.
What was missing was the receipt
Underneath all of this sits a simpler absence. Every one of these translations changes something. None of them tells you what.
That is the gap we built for. In CoreModels, ODCS is a first-class format — key odcs — and it works in both directions against the v3 line of the standard, emitting apiVersion: v3.1.0 with kind: DataContract.
Importing a contract (YAML or JSON) turns each schema object into a governed Type and each property into an Element, carrying required flags, array bounds read from the contract's own minItems/maxItems options, and nested object structures. The standard's logical types map onto the model's type system; where the mapping is approximate — the standard's time-of-day type, for instance — the approximation is recorded rather than assumed away. Physical names and types, key and uniqueness markers, partitioning, classification, encrypted names, examples, quality checks, relationships, tags, and custom properties all travel with the nodes they belong to.
Everything the model does not structurally hold is preserved verbatim rather than discarded, including whole sections like servers, team, roles, SLA, support, and pricing. An unrecognized key cannot break the parse and cannot vanish; it is kept and reported. And a document that is not a contract at all — the wrong kind, or none of the standard's identifying marks — is refused with a reason instead of being force-fitted into a model.
Going the other way, a governed model that never came from a contract still exports as a spec-valid document: the required head is minted with deterministic defaults where the model carries no contract history, so what you hand a consumer parses as a real ODCS contract in any conforming tool.
Every call comes back with a lossiness ledger: each entry a kind, an exact path, and a plain-English explanation of what could not be represented exactly. Not a warning banner — a list.
The boring outcome
The column we started with keeps its classification, keeps its encrypted twin, and keeps required: false, because nothing in the path had to make a judgment call, and everything that did make one wrote it down. The argument about which copy is authoritative stops being folklore, because the copies stop being copies.
To see the import and export calls in full, start with the schema transform documentation in the CoreModels docs.