JSONSchema didn't start as its own thing. It exists because ApptiveGrid needed OpenAPI, and OpenAPI needs a way to describe request and response bodies - which means it needs JSON Schema. Building a JSON Schema engine in Pharo was table stakes for having a documented API at all, so that's what got built: a small, generic core, cleanly separate from ApptiveGrid itself, plus a thin layer on top that lets ApptiveGrid's own types describe themselves as schemas.
What that actually means day to day: JSON Schema is a way to describe the shape a JSON document is supposed to have - which fields exist, what type each one is, which ones are required - and then check a real document against that description. In Pharo, you either build a schema with a small DSL or load one straight from a JSON Schema document, and either way you get back an object you can validate values against:
schema := JSONSchema fromString: '{
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "integer", "minimum": 0 }
},
"required": [ "name" ]
}'.
schema validate: (NeoJSONReader fromString: '{ "name": "Ada" }').
"passes, returns the schema"
schema validate: (NeoJSONReader fromString: '{ "age": 30 }').
"fails, raises JSONSchemaMissingRequiredProperty"
The same schema can also read a JSON document straight into typed Pharo objects instead of just yes/no validating it, and write one back out - which is the part ApptiveGrid actually leans on, to keep its API's request and response bodies both self-describing and enforced rather than just documented and hoped for.
Then ApptiveGrid moved on, as it does. Not because JSONSchema was finished, but because it did its one job well enough and there were always more pressing things to work on. It sat there for years, quietly validating request bodies, untouched.
At some point Udo S. did something more ambitious than the day-to-day ever asked for: he ported the official, language-agnostic JSON Schema Test Suite into Pharo, generating a test class for every one of its scenarios - over a thousand individual test methods in the end. That's a genuinely different kind of diligence. It turns "we're pretty sure this validates JSON Schema correctly" into "here's exactly which of the specification's own documented cases we pass and which we don't." And then, like the core before it, it sat there too. Nobody had the time to chase down what it actually found.
That's where it stayed for years - long enough that reloading it recently meant dragging five years of Pharo version drift along with it. Surprisingly, none of that drift had actually broken anything; the real surprise was in the numbers once the suite ran: roughly a third of the thousand-plus cases passing, the rest failing for one of two reasons - a genuine bug in something already implemented, or a keyword from the specification that simply didn't exist in the code yet.
Chasing that down, bucket by bucket, turned out to be exactly the kind of work Claude Code is good at. The domain doesn't move - the specification and its test suite are fixed and external. Every outcome is unambiguous - a test passes or it doesn't, there's no taste involved. And the buckets are naturally ordered by cost versus payoff: cheap one-line bugs first, then keywords whose wiring already existed and only needed logic, then the larger, self-contained keywords, then format validators. Pick the cheapest failing bucket, implement or fix it, rerun the suite, repeat. It came close to running itself.
Close, but not automatic. Three moments along the way are worth calling out, because they're the reason this wasn't just "run the suite, watch it turn green."
Pharo's test framework lets you mark a test as "expected to still fail" so it doesn't count as a real failure while you work on it. It turns out that same flag also swallows crashes that happen before the test even starts, not just failed checks - so a feature that isn't implemented at all, and crashes immediately, can look exactly as green as one that's genuinely done. Trusting the green checkmark by itself would have been wrong more than once.
Reloading the test suite into an already-running Pharo session didn't fully update it either: some old test data stayed behind even after it had been deleted from the real source on disk. The running session and the actual code disagreed about what was true, and only the disk was right. The fix was simple once noticed - always check the source, never the session - but it cost time to notice.
And the tool used to create a new class will silently overwrite an existing one if you happen to reuse its name, with no warning at all. That nearly destroyed a working, unrelated piece of code, and it only came to light because running the full test suite afterward showed unrelated tests failing that had no reason to.
None of these three are about the AI being clever. They're the ordinary discipline of double-checking success instead of trusting it - just applied consistently enough to get through a thousand test cases without missing one.
The suite went from about a third passing to something like two-thirds genuinely implemented, keyword by keyword: boolean schemas, `oneOf`/`anyOf`/`not`/`pattern`, `dependencies`, `const`, `contains`, `patternProperties`, `if`/`then`/`else`, tuple-shaped `items`, and a long list of format validators.
Then, deliberately, it stopped. Rather than chase the specification to full completeness, the real question got asked: what does ApptiveGrid's own OpenAPI document actually use? The answer was only the simplest form of `$ref`, nothing from the remaining fancier corner of the spec - no external references, no newer 2020-12 vocabulary, nothing that isn't already covered. So that's where it stands: not "complete" against the specification, but done for what it actually needs to do, with the gap that's left written down instead of quietly ignored.
None of this would have been worth doing as a big, open-ended redesign. What made it tractable is that JSON Schema conformance isn't a design problem, it's a checkable one - a fixed target with a thousand independent yes/no answers. That's the shape of work where an AI coding agent stops being a novelty and starts being genuine leverage: not for deciding what the right design is, but for closing out a large, well-specified backlog against an external, unambiguous target.
The code is on github if you want to read ahead - the README there is worth a look on its own, it documents the DSL, the supported keywords and formats, and what's deliberately not supported, in more depth than this post does.