formats: JSON Schema #80

Open
opened 2026-10-05 21:07:17 +00:00 by orion · 14 comments
Collaborator

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

  • ROADMAP.md, format versions: JSON Schema 2020-12, 2019-09, draft 07 (draft 06 is read as 07), draft 04 (the dialect of OpenAPI 3.0 and Swagger 2.0); draft 03 and older are not supported. "The versions of one format share one model: an earlier version is read into the model of the current one, not into a second set of types"; the writer writes the current version unless the caller asks for an earlier one.
  • ENCODING.md section 9 (format families) and section 5 (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 of Describe).
  • std/json (JsonValue) is the document type; std/toml, std/xml and std/yaml are 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:

  • Package std/json-schema, pure TorbScript, built on JsonValue, std/regex (pattern) and std/uri ($id/$ref resolution), not in the prelude. One model JsonSchema with the 2020-12 vocabulary; the earlier drafts are read into it by translating their keywords (definitions to $defs, array-form items to prefixItems, additionalItems to items, boolean exclusiveMinimum to the numeric form, id to $id, dependencies split into dependentRequired/dependentSchemas, $recursiveRef to $dynamicRef). The writer takes a version option.
  • Validation comes first (it is what OpenAPI and the tool layer consume), then references, then annotations, then the older drafts, then writing, then derivation. format is 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.
  • Test oracle: the public JSON-Schema-Test-Suite (MIT), vendored as a generated suite-cases.trb per the std/yaml precedent, with named exclusions that shrink as steps land.
  • A shared JSON Pointer type is needed by the reference step and by JSON Patch (#81): whichever lands first adds it to std/json; the other reuses it.
  • Touches the seed? No for the whole epic: the compiler does not import std/json-schema and no std name is looked up by string. A step that changes that carries the two-commit rule itself.

Steps

  • 1. Package, model and validator for the in-place keywords of 2020-12 (types, numbers, strings, arrays, objects, allOf/anyOf/oneOf/not/if), error list with JSON Pointer locations, vendored suite for those keywords, docs page #434
  • 2a. Static references: $id, $ref, $defs, $anchor, base URI resolution through std/uri, JSON Pointer fragments (std/json JsonPointer, landed), a registry of supplied documents (no fetching); suite files ref, refRemote, anchor, defs, id, infinite-loop-detection, unknownKeyword #553
  • 2b. $dynamicAnchor/$dynamicRef, the 2020-12 meta-schemas, $vocabulary; suite files dynamicRef, vocabulary and the groups left out in 2a #567
  • 3. unevaluatedProperties/unevaluatedItems and the annotation collection they need; suite files unevaluated* #690
  • 4a. format as an option-gated assertion (validate(..., assertFormat: true)) for the formats std already parses (date-time, date, time, ipv4, ipv6, uri, uri-reference, iri, iri-reference, uri-template, uuid, regex, json-pointer, relative-json-pointer); suite format and optional/format for those #697
  • 4b. The formats std cannot check yet (email, idn-email, hostname, idn-hostname, duration), the format-assertion vocabulary, the content* keywords, the rest of optional/format
  • 5. Draft 2019-09 read into the model ($recursiveRef/$recursiveAnchor, array items, additionalItems, dependencies), meta-schema, suite draft2019-09
  • 6. Draft 07, with draft 06 read as 07 ($ref siblings ignored, definitions, if/then/else), suite draft7
  • 7. Draft 04 (boolean exclusiveMinimum/exclusiveMaximum, id, no const), suite draft4; usable as the dialect of OpenAPI 3.0
  • 8. Writing: JsonSchema as Encode/Decode, a version option on write, round trip of every vendored schema
  • 9. Derivation: JsonSchema.of<T: Describe>() from structureOf<T>() (records, variants, optionals, maps, recursion as $ref into $defs, doc comments as description, constant defaults as default), in the version asked for
  • 10. Page completed under docs/standard-library/json-schema.md (each step extends the page of step 1; this closes the epic's acceptance)

Acceptance criteria (epic)

  • The package on Encode and Decode in the versions of the table (2020-12, 2019-09, draft 07 with 06 read as 07, draft 04)
  • The format's public test suite passes for each draft, with no exclusion left that is not a documented non-goal (optional bignum, draft 03)
  • A page under docs/standard-library/

Dependencies

  • Needs: std/json, std/regex, std/uri, std/encoding (structureOf), all built.
  • Unblocks: #76 (OpenAPI, whose model embeds JSON Schema; its draft 04 dialect is step 7 here), and the typed tools of std/language-model (design stage, #88).
  • Shares a JSON Pointer with #81 (JSON Patch), otherwise independent of it, of #78 (std/xml) and of #79.

Moved from the planning lists of the docs; the design record stays the specification.

## 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 - [ROADMAP.md, format versions](https://git.torb.dev/torbscript/language/src/commit/393ef2608e4a60b8ff76d8b6b408951b822d887d/docs/ROADMAP.md#L185): JSON Schema 2020-12, 2019-09, draft 07 (draft 06 is read as 07), draft 04 (the dialect of OpenAPI 3.0 and Swagger 2.0); draft 03 and older are not supported. "The versions of one format share one model: an earlier version is read into the model of the current one, not into a second set of types"; the writer writes the current version unless the caller asks for an earlier one. - [ENCODING.md section 9](https://git.torb.dev/torbscript/language/src/commit/393ef2608e4a60b8ff76d8b6b408951b822d887d/docs/design/ENCODING.md#L709) (format families) and section 5 (`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 of `Describe`). - `std/json` (`JsonValue`) is the document type; `std/toml`, `std/xml` and `std/yaml` are 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: - Package `std/json-schema`, pure TorbScript, built on `JsonValue`, `std/regex` (`pattern`) and `std/uri` (`$id`/`$ref` resolution), not in the prelude. One model `JsonSchema` with the 2020-12 vocabulary; the earlier drafts are read into it by translating their keywords (`definitions` to `$defs`, array-form `items` to `prefixItems`, `additionalItems` to `items`, boolean `exclusiveMinimum` to the numeric form, `id` to `$id`, `dependencies` split into `dependentRequired`/`dependentSchemas`, `$recursiveRef` to `$dynamicRef`). The writer takes a version option. - Validation comes first (it is what OpenAPI and the tool layer consume), then references, then annotations, then the older drafts, then writing, then derivation. `format` is 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. - Test oracle: the public JSON-Schema-Test-Suite (MIT), vendored as a generated `suite-cases.trb` per the `std/yaml` precedent, with named exclusions that shrink as steps land. - A shared JSON Pointer type is needed by the reference step and by JSON Patch (#81): whichever lands first adds it to `std/json`; the other reuses it. - Touches the seed? No for the whole epic: the compiler does not import `std/json-schema` and no std name is looked up by string. A step that changes that carries the two-commit rule itself. ## Steps - [x] 1. Package, model and validator for the in-place keywords of 2020-12 (types, numbers, strings, arrays, objects, `allOf`/`anyOf`/`oneOf`/`not`/`if`), error list with JSON Pointer locations, vendored suite for those keywords, docs page #434 - [x] 2a. Static references: `$id`, `$ref`, `$defs`, `$anchor`, base URI resolution through `std/uri`, JSON Pointer fragments (`std/json` `JsonPointer`, landed), a registry of supplied documents (no fetching); suite files `ref`, `refRemote`, `anchor`, `defs`, `id`, `infinite-loop-detection`, `unknownKeyword` #553 - [x] 2b. `$dynamicAnchor`/`$dynamicRef`, the 2020-12 meta-schemas, `$vocabulary`; suite files `dynamicRef`, `vocabulary` and the groups left out in 2a #567 - [x] 3. `unevaluatedProperties`/`unevaluatedItems` and the annotation collection they need; suite files `unevaluated*` #690 - [ ] 4a. `format` as an option-gated assertion (`validate(..., assertFormat: true)`) for the formats `std` already parses (date-time, date, time, ipv4, ipv6, uri, uri-reference, iri, iri-reference, uri-template, uuid, regex, json-pointer, relative-json-pointer); suite `format` and `optional/format` for those #697 - [ ] 4b. The formats `std` cannot check yet (email, idn-email, hostname, idn-hostname, duration), the `format-assertion` vocabulary, the `content*` keywords, the rest of `optional/format` - [ ] 5. Draft 2019-09 read into the model (`$recursiveRef`/`$recursiveAnchor`, array `items`, `additionalItems`, `dependencies`), meta-schema, suite `draft2019-09` - [ ] 6. Draft 07, with draft 06 read as 07 (`$ref` siblings ignored, `definitions`, `if`/`then`/`else`), suite `draft7` - [ ] 7. Draft 04 (boolean `exclusiveMinimum`/`exclusiveMaximum`, `id`, no `const`), suite `draft4`; usable as the dialect of OpenAPI 3.0 - [ ] 8. Writing: `JsonSchema` as `Encode`/`Decode`, a version option on write, round trip of every vendored schema - [ ] 9. Derivation: `JsonSchema.of<T: Describe>()` from `structureOf<T>()` (records, variants, optionals, maps, recursion as `$ref` into `$defs`, doc comments as `description`, constant defaults as `default`), in the version asked for - [ ] 10. Page completed under `docs/standard-library/json-schema.md` (each step extends the page of step 1; this closes the epic's acceptance) ## Acceptance criteria (epic) - [ ] The package on `Encode` and `Decode` in the versions of the table (2020-12, 2019-09, draft 07 with 06 read as 07, draft 04) - [ ] The format's public test suite passes for each draft, with no exclusion left that is not a documented non-goal (optional `bignum`, draft 03) - [ ] A page under `docs/standard-library/` ## Dependencies - Needs: `std/json`, `std/regex`, `std/uri`, `std/encoding` (`structureOf`), all built. - Unblocks: #76 (OpenAPI, whose model embeds JSON Schema; its draft 04 dialect is step 7 here), and the typed tools of `std/language-model` (design stage, #88). - Shares a JSON Pointer with #81 (JSON Patch), otherwise independent of it, of #78 (`std/xml`) and of #79. _Moved from the planning lists of the docs; the design record stays the specification._
Author
Collaborator

[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:00Z

[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:00Z
Author
Collaborator

[loop:torbscript] claim refine by session:loop-torbscript-20261023 until 2026-10-23T16:52Z

[loop:torbscript] claim refine by session:loop-torbscript-20261023 until 2026-10-23T16:52Z
Author
Collaborator

[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 for unevaluated*, writing and derivation from Describe are 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 via structureOf (ENCODING.md section 5). Package name std/json-schema (hyphenated like the planned std/language-model), built on JsonValue, std/regex and std/uri; format is 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 is ready; 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 into std/json with whichever lands first.

[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 for `unevaluated*`, writing and derivation from `Describe` are 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 via `structureOf` (ENCODING.md section 5). Package name `std/json-schema` (hyphenated like the planned `std/language-model`), built on `JsonValue`, `std/regex` and `std/uri`; `format` is 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 is `ready`; 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 into `std/json` with whichever lands first.
Author
Collaborator

[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)

[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)
Author
Collaborator

[loop:torbscript] claim refine by session:loop-torbscript-20261028 until 2026-10-28T19:18:23Z

[loop:torbscript] claim refine by session:loop-torbscript-20261028 until 2026-10-28T19:18:23Z
Author
Collaborator

[loop:torbscript] refine: step 1 (#434) has landed (std/json-schema on main: model, validator, 694 suite cases, page; std/json already carries JsonPointer). 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 via std/uri, registry of supplied documents, cycle detection; suite files ref, refRemote, anchor, defs, id, infinite-loop-detection, unknownKeyword), labels loop and ready, 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.on was 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 $ref into $defs first).
next: waiting (epic steps)

[loop:torbscript] refine: step 1 (#434) has landed (`std/json-schema` on `main`: model, validator, 694 suite cases, page; `std/json` already carries `JsonPointer`). 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 https://git.torb.dev/torbscript/language/issues/553 (static references: `$id`, `$ref`, `$defs`, `$anchor`, base URI via `std/uri`, registry of supplied documents, cycle detection; suite files `ref`, `refRemote`, `anchor`, `defs`, `id`, `infinite-loop-detection`, `unknownKeyword`), labels `loop` and `ready`, 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.on` was 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 `$ref` into `$defs` first). next: waiting (epic steps)
Author
Collaborator

[loop:torbscript] claim refine (next step after the landed one) by session:loop-torbscript-20261029 until 2026-10-29T08:35Z

[loop:torbscript] claim refine (next step after the landed one) by session:loop-torbscript-20261029 until 2026-10-29T08:35Z
Author
Collaborator

[loop:torbscript] decided by the loop: step 2b stays one PR in two commits: $dynamicRef/$dynamicAnchor with $vocabulary first, 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 seven meta-schema exclusions of 2a go. Cases that need unevaluated* stay named exclusions until step 3.
[loop:torbscript] refine: verified on main: step 1 (#434) and step 2a (#553, commits feat/test/docs(std/json-schema) for references) have landed; the checklist now ticks 2a. Filed step 2b as #567 with labels loop and ready (goal, acceptance criteria, design pointer, no seed touch, one PR in two commits); no duplicate found among open and closed issues; gate.on was false. Step 3 is filed when 2b lands. Worker usage: about 60k tokens (sonnet).
next: ready (#567)

[loop:torbscript] decided by the loop: step 2b stays one PR in two commits: `$dynamicRef`/`$dynamicAnchor` with `$vocabulary` first, 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 seven `meta-schema` exclusions of 2a go. Cases that need `unevaluated*` stay named exclusions until step 3. [loop:torbscript] refine: verified on `main`: step 1 (#434) and step 2a (#553, commits `feat/test/docs(std/json-schema)` for references) have landed; the checklist now ticks 2a. Filed step 2b as https://git.torb.dev/torbscript/language/issues/567 with labels `loop` and `ready` (goal, acceptance criteria, design pointer, no seed touch, one PR in two commits); no duplicate found among open and closed issues; `gate.on` was false. Step 3 is filed when 2b lands. Worker usage: about 60k tokens (sonnet). next: ready (#567)
Author
Collaborator

[loop:torbscript] refine: step 2a (#553) verified closed on main; step 2b filed just in time as #567 (labels loop, 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] refine: step 2a (#553) verified closed on `main`; step 2b filed just in time as https://git.torb.dev/torbscript/language/issues/567 (labels `loop`, `ready`), decision recorded above. Claim of session:loop-torbscript-20261029 released. Worker usage: about 54k tokens (Sonnet 5.5 refine worker). next: ready (#567)
Author
Collaborator

[loop:torbscript] claim refine by session:loop-torbscript-20261030 @anthropic:theorben until 2026-10-30T12:02Z

[loop:torbscript] claim refine by session:loop-torbscript-20261030 @anthropic:theorben until 2026-10-30T12:02Z
Author
Collaborator

[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 to src/validate.trb; commit 1 adds it with unevaluatedProperties/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] 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: https://git.torb.dev/torbscript/language/issues/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 to `src/validate.trb`; commit 1 adds it with `unevaluatedProperties`/`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)
Author
Collaborator

[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] 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
Author
Collaborator

[loop:torbscript] claim refine by session:loop-torbscript-20261030 @anthropic:tkoehn until 2026-10-30T17:38Z

[loop:torbscript] claim refine by session:loop-torbscript-20261030 @anthropic:tkoehn until 2026-10-30T17:38Z
Author
Collaborator

[loop:torbscript] refine: step 3 landed (#690). Verified on main: 248b6849 feat(std/json-schema): collect evaluated properties and items and 9c53410b3 test(std/json-schema): run the unevaluated suite files; exclusions() in suite.test.trb is empty and unevaluated* 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 the content* keywords), so it is split just in time as 2a/2b were. 4a is filed as #697 (labels loop, ready): format in the model, validate(..., assertFormat: Bool = false), checkers for the formats std already parses, suite format and optional/format for those. 4b (email, hostname, idn-*, duration, format-assertion vocabulary, content*) is filed when 4a lands. The option is a parameter of validate, not a field of the schema, because assertion is the validating caller's choice. The checklist shows 4a and 4b.

next: waiting (step #697)

[loop:torbscript] refine: step 3 landed (#690). Verified on `main`: `248b6849 feat(std/json-schema): collect evaluated properties and items` and `9c53410b3 test(std/json-schema): run the unevaluated suite files`; `exclusions()` in `suite.test.trb` is empty and `unevaluated*` 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 the `content*` keywords), so it is split just in time as 2a/2b were. **4a** is filed as https://git.torb.dev/torbscript/language/issues/697 (labels `loop`, `ready`): `format` in the model, `validate(..., assertFormat: Bool = false)`, checkers for the formats `std` already parses, suite `format` and `optional/format` for those. **4b** (email, hostname, idn-*, duration, `format-assertion` vocabulary, `content*`) is filed when 4a lands. The option is a parameter of `validate`, not a field of the schema, because assertion is the validating caller's choice. The checklist shows 4a and 4b. next: waiting (step #697)
Sign in to join this conversation.
No labels
loop
ready
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
torbscript/language#80
No description provided.