formats: JSON Schema #80
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
JSON Schema as a format package: read a schema, validate JSON against it, write it, and derive it from a type. Needed by OpenAPI (#76, which depends on this) and by the typed tools of
std/language-model(LANGUAGE-MODELS.md section 1: "the JSON Schema the provider needs is derived from the type at compile time"). This is an epic: four drafts, validation, references, annotations, writing and derivation are far more than one PR. Steps become issues just in time, one at a time.Source
Describe,Structure,structureOf: "a schema is a value, and the conversion is written once"; the kotlinx and autodocodec rows of section 11 name JSON Schema generation as the point ofDescribe).std/json(JsonValue) is the document type;std/toml,std/xmlandstd/yamlare the layout model (format value, tree, parser, vendored public suite, docs page).Design record point
ROADMAP "Which versions a format supports"; ENCODING.md sections 5 and 9. Decided by the loop:
std/json-schema, pure TorbScript, built onJsonValue,std/regex(pattern) andstd/uri($id/$refresolution), not in the prelude. One modelJsonSchemawith the 2020-12 vocabulary; the earlier drafts are read into it by translating their keywords (definitionsto$defs, array-formitemstoprefixItems,additionalItemstoitems, booleanexclusiveMinimumto the numeric form,idto$id,dependenciessplit intodependentRequired/dependentSchemas,$recursiveRefto$dynamicRef). The writer takes a version option.formatis an annotation by default and an assertion by option, as the specification allows; remote references are never fetched, a caller supplies documents to a registry.suite-cases.trbper thestd/yamlprecedent, with named exclusions that shrink as steps land.std/json; the other reuses it.std/json-schemaand no std name is looked up by string. A step that changes that carries the two-commit rule itself.Steps
allOf/anyOf/oneOf/not/if), error list with JSON Pointer locations, vendored suite for those keywords, docs page #434$id,$ref,$defs,$anchor, base URI resolution throughstd/uri, JSON Pointer fragments (std/jsonJsonPointer, landed), a registry of supplied documents (no fetching); suite filesref,refRemote,anchor,defs,id,infinite-loop-detection,unknownKeyword#553$dynamicAnchor/$dynamicRef, the 2020-12 meta-schemas,$vocabulary; suite filesdynamicRef,vocabularyand the groups left out in 2a #567unevaluatedProperties/unevaluatedItemsand the annotation collection they need; suite filesunevaluated*#690formatas an option-gated assertion (validate(..., assertFormat: true)) for the formatsstdalready parses (date-time, date, time, ipv4, ipv6, uri, uri-reference, iri, iri-reference, uri-template, uuid, regex, json-pointer, relative-json-pointer); suiteformatandoptional/formatfor those #697stdcannot check yet (email, idn-email, hostname, idn-hostname, duration), theformat-assertionvocabulary, thecontent*keywords, the rest ofoptional/format$recursiveRef/$recursiveAnchor, arrayitems,additionalItems,dependencies), meta-schema, suitedraft2019-09$refsiblings ignored,definitions,if/then/else), suitedraft7exclusiveMinimum/exclusiveMaximum,id, noconst), suitedraft4; usable as the dialect of OpenAPI 3.0JsonSchemaasEncode/Decode, a version option on write, round trip of every vendored schemaJsonSchema.of<T: Describe>()fromstructureOf<T>()(records, variants, optionals, maps, recursion as$refinto$defs, doc comments asdescription, constant defaults asdefault), in the version asked fordocs/standard-library/json-schema.md(each step extends the page of step 1; this closes the epic's acceptance)Acceptance criteria (epic)
EncodeandDecodein the versions of the table (2020-12, 2019-09, draft 07 with 06 read as 07, draft 04)bignum, draft 03)docs/standard-library/Dependencies
std/json,std/regex,std/uri,std/encoding(structureOf), all built.std/language-model(design stage, #88).std/xml) and of #79.Moved from the planning lists of the docs; the design record stays the specification.
[loop:torbscript] intake: in scope (author orion, label loop), sources ROADMAP.md format versions and ENCODING.md section 9, not a duplicate, no collision with a design record. TorbScript package on
Encode/Decode, public test suite in forge CI; blocks #76. next: refine, waiting (ready buffer full: #4, #163, #164, #223 are ready) until 2026-10-10T04:00:00Zstd/xml#78[loop:torbscript] claim refine by session:loop-torbscript-20261023 until 2026-10-23T16:52Z
std/json-schema(step 1 of #80) #434[loop:torbscript] decided by the loop: #80 is too big for one PR, so it is an epic and stays without
ready. Four drafts (2020-12, 2019-09, 07, 04), validation, references with dynamic scope, annotations forunevaluated*, writing and derivation fromDescribeare ten reviewable pieces; the order follows the design: validation first (the consumers #76 and the typed tools need it), then references, annotations, the older drafts read into the one 2020-12 model as ROADMAP requires, then writing and derivation viastructureOf(ENCODING.md section 5). Package namestd/json-schema(hyphenated like the plannedstd/language-model), built onJsonValue,std/regexandstd/uri;formatis an annotation unless asked, remote references are never fetched. No seed touch (the compiler does not import it, no std name looked up by string). Step 1 is filed as #434 (keyword search of open and closed issues found only #76, #81 and this one; none duplicates it) and isready; steps 2 to 10 are filed just in time. Dependency notes: #76 depends on this epic; a JSON Pointer type is shared with #81 and goes intostd/jsonwith whichever lands first.[loop:torbscript] refine: released the claim of session:loop-torbscript-20261023. #80 is an epic (checklist of 10 steps in the body), step 1 filed as #434 with label
ready. Nothing was written to the repo. Worker usage: about 90k tokens (sonnet).next: waiting (epic waits on its steps)
JsonPointer(RFC 6901) instd/json(step 1 of #81) #435std/htmldocuments (epic) #79[loop:torbscript] claim refine by session:loop-torbscript-20261028 until 2026-10-28T19:18:23Z
std/json-schema:$id,$ref,$defs,$anchorand a registry of supplied documents (step 2a of #80) #553[loop:torbscript] refine: step 1 (#434) has landed (
std/json-schemaonmain: model, validator, 694 suite cases, page;std/jsonalready carriesJsonPointer). Step 2 is too large for one PR (references plus dynamic scope plus meta-schemas plus six suite files), so I split it just in time: 2a is filed as #553 (static references:$id,$ref,$defs,$anchor, base URI viastd/uri, registry of supplied documents, cycle detection; suite filesref,refRemote,anchor,defs,id,infinite-loop-detection,unknownKeyword), labelsloopandready, no seed touch, no duplicate among the open issues. 2b ($dynamicRef/$dynamicAnchor, meta-schemas,vocabulary) is filed when 2a lands. The checklist above now ticks step 1 and lists 2a/2b.gate.onwas false when filing.[loop:torbscript] decided by the loop: split step 2 into 2a/2b because dynamic scope is a separate evaluation model that the meta-schemas depend on, and 2a is independently useful (OpenAPI #76 needs
$refinto$defsfirst).next: waiting (epic steps)
[loop:torbscript] claim refine (next step after the landed one) by session:loop-torbscript-20261029 until 2026-10-29T08:35Z
std/json-schema(step 2b of #80) #567[loop:torbscript] decided by the loop: step 2b stays one PR in two commits:
$dynamicRef/$dynamicAnchorwith$vocabularyfirst, then the bundled 2020-12 meta-schemas (they are written with$dynamicRef, so they cannot come first). The meta-schemas are embedded as vendored text and pre-registered so nothing is fetched; this also lets the sevenmeta-schemaexclusions of 2a go. Cases that needunevaluated*stay named exclusions until step 3.[loop:torbscript] refine: verified on
main: step 1 (#434) and step 2a (#553, commitsfeat/test/docs(std/json-schema)for references) have landed; the checklist now ticks 2a. Filed step 2b as #567 with labelsloopandready(goal, acceptance criteria, design pointer, no seed touch, one PR in two commits); no duplicate found among open and closed issues;gate.onwas false. Step 3 is filed when 2b lands. Worker usage: about 60k tokens (sonnet).next: ready (#567)
[loop:torbscript] refine: step 2a (#553) verified closed on
main; step 2b filed just in time as #567 (labelsloop,ready), decision recorded above. Claim of session:loop-torbscript-20261029 released. Worker usage: about 54k tokens (Sonnet 5.5 refine worker). next: ready (#567)[loop:torbscript] claim refine by session:loop-torbscript-20261030 @anthropic:theorben until 2026-10-30T12:02Z
std/json-schema(step 3 of #80) #690[loop:torbscript] refine: verified on
main: step 2b (#567) is closed and both commits have landed (d452ef05 feat(std/json-schema): evaluate $dynamicRef and $dynamicAnchor,bf679455 feat(std/json-schema): bundle the 2020-12 meta-schemas); ticked in the checklist. Duplicate search (open and closed, "unevaluated", "annotation") found nothing.decided by the loop: step 3 is one PR in two commits, so it is filed whole, not sliced: #690 (labels
loop,ready). The validator returns only an error list today, so the annotation collection (evaluated property names and item indices, kept only from subschemas that validate) is internal tosrc/validate.trb; commit 1 adds it withunevaluatedProperties/unevaluatedItems, commit 2 runs the two suite files and drops the three named exclusions that wait on this step. No seed impact. Nothing was written to the repo.released the claim of session:loop-torbscript-20261030
next: ready (#690)
[loop:torbscript] wait until 2026-10-30T15:00Z: step 3 (#690) is in implementation by a worker; the next step is filed when it lands.
next: waiting (epic waits on #690) until 2026-10-30T15:00Z
[loop:torbscript] claim refine by session:loop-torbscript-20261030 @anthropic:tkoehn until 2026-10-30T17:38Z
formatassertion for the formats std can parse (step 4a of #80) #697[loop:torbscript] refine: step 3 landed (#690). Verified on
main:248b6849 feat(std/json-schema): collect evaluated properties and itemsand9c53410b3 test(std/json-schema): run the unevaluated suite files;exclusions()insuite.test.trbis empty andunevaluated*are no longer refused. Step 3 is ticked in the checklist. Duplicate search (open and closed, "format") found nothing.decided by the loop: step 4 is too large for one PR (five formats have no parser in
std, plus the vocabulary route and thecontent*keywords), so it is split just in time as 2a/2b were. 4a is filed as #697 (labelsloop,ready):formatin the model,validate(..., assertFormat: Bool = false), checkers for the formatsstdalready parses, suiteformatandoptional/formatfor those. 4b (email, hostname, idn-*, duration,format-assertionvocabulary,content*) is filed when 4a lands. The option is a parameter ofvalidate, not a field of the schema, because assertion is the validating caller's choice. The checklist shows 4a and 4b.next: waiting (step #697)