A library gets proven by people using it. A specification is a harder claim to back up: someone else has to build from your rules alone, without being able to ask you what you meant.
I wanted Omnist to become a real document and schema standard, not just a library that happened to do well. So I planned for that early on: get the design out of one language, and check that it actually holds up in several others, before I called any of it a standard.
Why I didn't just write the spec first
The obvious shortcut would've been to write the spec before writing any code. I didn't do that, and I don't think it would have worked. A spec is a bet that you already know the right rules, and I didn't — not yet. There were too many things I hadn't figured out, starting with whether the whole model actually held together once real logic and real behavior were built on top of it, not just designed on paper. Writing rules down before I'd built anything would have meant standardizing my guesses.
So the first version was working software, not a document — a single reference implementation in Python, where I could actually watch the model, the logic, and the behavior work together. It wasn't a throwaway prototype either. I built it to be usable on real problems, not just to prove a point. That's really the only way I know to find out if a design is any good, or worth anything to anyone: use it.
What you can't see from inside one language
Python lets you get away with a lot. It handles things implicitly that another language would make you spell out. If you only ever read your own Python code, it's genuinely hard to tell which parts are the actual design and which parts are just Python quietly doing you a favor. I only found that boundary by hitting it — by writing the same logic in a language that didn't behave the same way.
So I built two more ports, and picked them for how different they are from Python and from each other. TypeScript: high-level, and the language most platforms are actually built on, which made it a real test of whether Omnist could reach people where they already are. Rust: low-level, no garbage collector doing you favors, nothing implicit to lean on.
Both were built by porting the Python code directly — reading it, translating it, and filing an issue against the original whenever a port disagreed with it. Between the three, most of what Python had been doing implicitly came out into the open: sometimes as an explicit choice, sometimes as a compiler error, sometimes as a rule I realized I'd never actually written down anywhere.
Writing down what the three of them agreed on
Once I had three implementations, the interesting thing wasn't any one of them on its own. It was the places where all three landed on the same answer, for a rule I'd never stated. That agreement told me something: the rule already existed, I just hadn't written it down yet.
So I extracted it, directly from those first three ports, as its own document: a language-agnostic specification, independent of any one codebase. Two more pieces went with it, because a spec without them isn't something you can actually check.
- A divergence ledger — a tracked list of exactly where implementations are allowed to differ, and why. Not every difference is a bug. Some are legitimate, language-specific choices. The ledger's job is to say, honestly, which is which.
- A conformance test suite — a shared set of test cases every port has to pass, regardless of language. "Spec-compliant" needed to mean something I could run, not just something I could claim.
Where the languages actually disagreed
A few concrete examples show why I had to find this stuff by building, not by thinking it through in advance.
JavaScript has one numeric type. Python has separate integers and floats. Representing Omnist's integers in TypeScript without quietly losing precision meant using JavaScript's BigInt instead of number — not an obvious choice, and one the spec ended up having to state outright, because "just use a number" breaks silently on large values.
OSD, Omnist's schema-definition format, is supposed to allow exactly one root declaration per schema. The Python implementation never actually enforced that — a second root declaration would just silently override the first. Nobody had decided that was fine. It was just what the code happened to do. Once the spec had to state a rule instead of just describing behavior, that case surfaced, got settled as an error, and got enforced the same way everywhere.
Reading XML was another one. Omnist's document model has no place for attributes or namespace prefixes, so the Python reader just dropped them, silently, and had always done so. The spec now requires every implementation to actually report that when it happens, instead of quietly losing data.
I didn't find any of these by rereading the spec, or the code. I found them from the conformance suite — the same test cases run against every implementation, which flags it immediately when one language passes a case and another fails, or quietly does something different. That's the real value of the suite: it doesn't just write the rules down, it catches the moment two implementations stop agreeing.
The real test: build it from the spec, not the other ports
None of that actually proves the spec is enough on its own. It only proves the spec accurately describes three implementations that already existed. The real test was building something new from nothing but the document.
Go was built solely from the spec, with no reference to the Python, TypeScript, or Rust source while writing it. Wherever the spec was silent, ambiguous, or just wrong, I fixed the spec — not Go. If a builder follows the document correctly and still ends up with the wrong answer, that's not the builder's fault. It's a defect in the document.
Java went through the same process — built from scratch, from the spec alone — but this time by a different AI, one with no exposure to how the spec had come together and no visibility into any of the other four ports. It had to read the spec and make sense of it entirely on its own, and wherever it found something ambiguous or wrong, it filed that back against the spec, the same rule Go had followed. One clean build from the spec shows it can be done. A second one, by someone starting from nothing, shows the first wasn't a fluke. The same conformance suite that had been checking the first three ports against each other now checked Go and Java against the spec directly — which was really the test that mattered all along.
Bringing in an outside audit
Once all five ports existed, I brought in AI auditors who hadn't written any of the code, and had them review all five for quality, correctness, performance, and security. That's really the whole point of an outside audit — they had no reason to assume the code was already fine, because none of it was theirs.
They filed real issues, and the useful part was where those issues actually landed. Not just on whichever port a bug happened to show up in — on the specification itself, whenever the real cause was the spec being unclear rather than one implementation being careless. A gap found in one language usually turned out to apply to all five, the same way a gap in a blind port usually did. Fixing it once, in the spec, beat fixing the same mistake separately in five different languages.
Making the spec itself easier to read
A spec that's technically correct but exhausting to read fails in a quieter way than a wrong one does — people just stop reading it, and then nobody catches what's actually wrong with it either. So the last pass wasn't about adding more rules. Mostly, it was cutting things: content that had gone stale, language that had drifted vague over several rounds of edits. I wanted someone reading it for the first time, with none of the context I had, to be able to get through it and actually build something correct.
That was different work from building any of the ports. Code that's wrong tends to fail loudly. A spec that's just unclear fails quietly, months later, as an argument nobody can settle because the document never actually said. Getting the spec precise and readable turned out to matter about as much as getting it correct in the first place.
What this actually showed
None of this was ever really about ending up with five codebases. It was about being able to say, honestly, that the design itself is right — not just that one implementation happened to get it right. Two deliberately different ports showed me what Python had been hiding. Writing the spec down, with an honest ledger and a test suite that actually runs, turned an informal agreement between three codebases into something I could check. Two blind builds tested whether the spec was actually enough on its own, not just accurate. An outside audit checked all of it with no reason to assume any of it was already fine. And a last pass made sure the whole thing was something a person could actually sit down and read.
That's the real difference between a standard and a library that happened to work out in five languages: every one of those steps was there on purpose, specifically to try to prove it wrong.
The specification is at spec.omnist.dev. All five ports — Python, TypeScript, Rust, Go, and Java — are open source at github.com/omnist-dev. If you build a sixth port and the spec lets you down somewhere, I'd genuinely like to hear about it — that's exactly the kind of scrutiny it's supposed to survive. Find me on X (@lee_tom) or LinkedIn.
Top comments (0)