From 3b940da39669cf590a931853cabeb3953ae3ea86 Mon Sep 17 00:00:00 2001 From: Michael Moffett Date: Mon, 10 Aug 2026 10:57:57 -0600 Subject: [PATCH 01/15] test(mpp): add RFC 3339 expires conformance corpus and wire the harness loader Issue #111 asks for RFC 3339 conformance coverage on the MPP `expires` field. This repo has no vector corpus for it: the canonical files in harness/vectors/mpp-protocol cover challenge, header, receipt, base64url and challenge.id, but nothing exercises the date-time grammar that `expires` is specified in. This commit adds that corpus and the loader that reads it. expires-rfc3339-corpus.json carries 228 scenarios, 116 of them for date-time, 74 for full-date and 38 for full-time, merged from four upstream sources. Every scenario records a provenance object naming where the input came from and what authority decided its verdict, so a reviewer can tell an input imported verbatim from a suite that already ships the answer apart from one whose verdict was derived here against the grammar in RFC 3339. Scenarios also carry `applies_to`, which is load-bearing rather than decorative: a verdict only means what it says inside its own production, and MPP `expires` holds a date-time, so a consumer that wants `expires` behaviour has to filter on that field instead of reading the file as one flat list. The corpus arrives under two upstream licences, added beside it as LICENSE.json-schema-test-suite, LICENSE.go-stdlib and PATENTS.go-stdlib. The Go standard library grant is split across a LICENSE and a PATENTS file and shipping one without the other would be an incomplete grant, so both are included. Both README files in the affected directories are updated to say which files came from which upstream, because the vector directory previously had a single provenance and now has two. vectors.ts gains loadExpiresRfc3339() and collectExpiresCases() on the same shape as the loaders already there, plus an `expires.parse` operation for the corpus to hang from. That operation is not in upstream conformance/operations.json, which is not vendored here, so it is proposed rather than asserted; the divergence is called out in a comment at the point of definition and the name is trivially changeable if you would rather it land upstream or read differently. --- harness/src/protocol/README.md | 27 +- harness/src/protocol/vectors.ts | 127 +- .../vectors/mpp-protocol/LICENSE.go-stdlib | 27 + .../LICENSE.json-schema-test-suite | 19 + .../vectors/mpp-protocol/PATENTS.go-stdlib | 22 + harness/vectors/mpp-protocol/README.md | 66 + .../mpp-protocol/expires-rfc3339-corpus.json | 7447 +++++++++++++++++ 7 files changed, 7733 insertions(+), 2 deletions(-) create mode 100644 harness/vectors/mpp-protocol/LICENSE.go-stdlib create mode 100644 harness/vectors/mpp-protocol/LICENSE.json-schema-test-suite create mode 100644 harness/vectors/mpp-protocol/PATENTS.go-stdlib create mode 100644 harness/vectors/mpp-protocol/expires-rfc3339-corpus.json diff --git a/harness/src/protocol/README.md b/harness/src/protocol/README.md index 7f6a0c17d..b0cc5e54b 100644 --- a/harness/src/protocol/README.md +++ b/harness/src/protocol/README.md @@ -14,7 +14,7 @@ base64url encode/decode, and the challenge-id HMAC. | File | Role | |------|------| -| `vectors.ts` | Loads the vendored canonical vectors and flattens them into dispatchable `ProtocolCase`s (handles the `tests.: true \| {success:false,error_type}` shape, constructed `wire` shorthand, `adapters` allow-lists, and `maxDurationMs*` budgets). | +| `vectors.ts` | Loads the vendored canonical vectors and flattens them into dispatchable `ProtocolCase`s (handles the `tests.: true \| {success:false,error_type}` shape, constructed `wire` shorthand, `adapters` allow-lists, and `maxDurationMs*` budgets). Also loads the RFC 3339 `expires` corpus via `collectExpiresCases()`, filtered to `applies_to == "date-time"`. | | `driver.ts` | Transport-agnostic case runner. `exact` compare for base64url / challenge.id; `semantic` compare for parse/format (re-parses both wires through the paired parse op, with credential request-encoding normalization) — mirrors the canonical runner's `compare_*_semantic`. Times the primary op and enforces the scenario's duration budget like `compare_duration`. | | `flow-driver.ts` | HTTP 402 FLOW orchestration: a TypeScript port of the normative mpp-tools `flow_runner.py`. Performs the initial request / 402 / credential retry / receipt exchange itself, routing only `challenge.parse` / `credential.format` / `receipt.parse` through a `ProtocolAdapter`, and compares recorded results against `harness/vectors/mpp-protocol-flows/golden-results.json` after the canonical normalization. | | `runners/typescript.ts` | TypeScript REFERENCE runner. In-process `ProtocolAdapter` over `mppx` (pay-kit's TS protocol core) + a stdin/stdout CLI speaking the canonical adapter ABI. | @@ -47,6 +47,31 @@ Operations (canonical, from `mpp-tools/conformance/operations.json`): `error_type` vocabulary: `parse_error` (`.parse`), `format_error` (`.format`), `encoding_error` (base64url), `generation_error` (`challenge.id`). +### `expires.parse` — proposed by this PR, NOT canonical + +| op | input | success result | compare | +|----|-------|----------------|---------| +| `expires.parse` | `{ expires }` | `{ valid: true }` | exact | + +RFC 3339 `expires` parse conformance (issue #111), driven by +`harness/vectors/mpp-protocol/expires-rfc3339-corpus.json` through +`collectExpiresCases()`. **This operation is not in upstream +`mpp-tools/conformance/operations.json`** — it is proposed here so the corpus +has an operation to hang from, and the name, the input/result shape, and the +`exact` comparison are all the maintainer's to overrule. On rejection the +`error_type` is `parse_error`, reusing the `.parse` vocabulary above rather +than introducing a new one. + +The corpus covers three RFC 3339 productions; `collectExpiresCases()` admits +only the 116 `applies_to == "date-time"` vectors, because `full-date` and +`full-time` verdicts are correct statements about a different production and an +`expires` parser is right to reject those inputs. + +No adapter implements `expires.parse` yet, so these cases are deliberately +**not** in `collectProtocolCases()` — folding them in would turn the green +reference suite red. Per-language `expires` tests call `collectExpiresCases()` +directly. + ## Running ``` diff --git a/harness/src/protocol/vectors.ts b/harness/src/protocol/vectors.ts index d90342cff..fc53a58da 100644 --- a/harness/src/protocol/vectors.ts +++ b/harness/src/protocol/vectors.ts @@ -15,6 +15,17 @@ export const VECTORS_DIR = join(here, "..", "..", "vectors", "mpp-protocol"); // Canonical adapter-ABI operation identifiers. These match // `conformance/operations.json` in mpp-tools verbatim. +// +// DIVERGENCE (this PR): `expires.parse` is the one member below that is NOT +// in upstream `conformance/operations.json`. It is proposed here, by this PR, +// to give the RFC 3339 `expires` corpus (issue #111) an operation to hang +// from; `operations.json` is not vendored in this repo (it lives upstream in +// tempoxyz/mpp-tools), so nothing here can be checked against it. The +// maintainer may want the operation added upstream, may want it named +// differently, or may want it kept as a pay-kit-local extension — all three +// are their call, and the name is trivially renameable (this union, the +// OPERATION_COMPARISON entry below, and `collectExpiresCases`). The rest of +// the union does still match upstream verbatim. export type ProtocolOperation = | "challenge.parse" | "challenge.format" @@ -24,7 +35,8 @@ export type ProtocolOperation = | "receipt.format" | "base64url.encode" | "base64url.decode" - | "challenge.id"; + | "challenge.id" + | "expires.parse"; // Comparison discipline per operation, taken from operations.json: // exact -> result string/object must equal the golden byte-for-byte @@ -42,6 +54,14 @@ export const OPERATION_COMPARISON: Record = { "base64url.encode": "exact", "base64url.decode": "exact", "challenge.id": "exact", + // `exact`: an `expires.parse` success result is the fixed literal + // `{ valid: true }` (see ExpiresScenario below) — there is no header + // whitespace, param order, or request encoding for `semantic` to + // neutralize. `semantic` in this file is also *implemented* via + // `reparseWith`, which needs a paired `*.format` op; `expires.parse` has + // no `expires.format` counterpart, so `semantic` is not mechanically + // available to it. Same shape as base64url / challenge.id above. + "expires.parse": "exact", }; // ── Raw vector file shapes ── @@ -132,6 +152,38 @@ export type ChallengeIdScenario = { expected: string; }; +// RFC 3339 `expires` scenario (issue #111). Same `{ name, description, tags, +// tests }` spine as every other scenario in this directory; the input field is +// named for its domain (`input`, a single untrusted string) the way header +// scenarios name theirs `wire` and base64url names its `decoded` / `encoded`. +// +// `applies_to` is load-bearing and MUST be read before `tests.parse`. The +// corpus carries vectors for three different RFC 3339 productions — +// `date-time`, `full-date`, `full-time` — and a verdict only means what it +// says inside its own production. MPP `expires` holds a `date-time`; a +// `full-date` ACCEPT such as `1963-06-19` is a correct statement about a +// different production and is NOT an instruction to accept that value as an +// `expires`. `collectExpiresCases` filters on this field for exactly that +// reason. See the corpus's own `scope` block. +// +// No `object` golden is carried for an ACCEPT: the corpus is deliberately a +// verdict corpus, not an instant-normalization corpus (leap seconds have no +// representable instant in most date libraries and sub-nanosecond precision +// truncates differently per language). #111 asks for ACCEPT/REJECT parity +// across SDKs and nothing more. +export type ExpiresScenario = { + name: string; + description?: string; + tags?: string[]; + // Which RFC 3339 production this scenario's verdict answers. + applies_to: "date-time" | "full-date" | "full-time"; + // The single untrusted string under test. + input: string; + source?: string; + provenance?: Record; + tests: TestFlags; +}; + type VectorFile = { version: string; spec_ref: string; @@ -164,6 +216,14 @@ export function loadChallengeId(): VectorFile { return load("challenge-id.json"); } +export function loadExpiresRfc3339(): VectorFile { + return load("expires-rfc3339-corpus.json"); +} + +// The RFC 3339 production MPP `expires` holds. Everything else in the corpus +// answers a different question than an `expires` field asks. +export const EXPIRES_APPLIES_TO = "date-time"; + // A single dispatchable unit of work derived from a vector scenario: the // op to run, the adapter input envelope, and either the golden success // output (for a happy-path) or the expected error_type. @@ -337,3 +397,68 @@ export function collectProtocolCases(): ProtocolCase[] { return cases; } + +// Expand the RFC 3339 `expires` corpus into `expires.parse` cases (issue +// #111: assert ACCEPT and REJECT outcomes match across SDKs for each vector). +// +// FILTER. Only `applies_to === "date-time"` vectors are admitted. The corpus +// ships 228 scenarios covering three RFC 3339 productions; 112 of them answer +// `full-date` / `full-time` questions, and 29 of those carry ACCEPT for inputs +// no `expires` parser may accept (`1963-06-19`, `08:30:06Z`, ...). Running the +// unfiltered file against an `expires` parser produces failures that are not +// defects. The filter is on the first-class `applies_to` field — never on a +// name prefix, a description string, or any other heuristic. +// +// ABI shape, PROPOSED BY THIS PR — the maintainer should overrule any of it: +// input : { expires: "" } +// (mirrors `{ header }` / `{ text }`: the field is named for what +// it holds) +// success : { valid: true } +// The corpus supplies no golden parsed value on purpose, so the +// success result carries the verdict itself rather than a +// normalized instant. Compared `exact` (see OPERATION_COMPARISON). +// failure : error_type "parse_error" +// NOT a new taxonomy: `parse_error` is the only `error_type` value +// that appears anywhere in this vectors directory (24/24 +// occurrences), it is the documented vocabulary for `.parse` ops +// in `harness/src/protocol/README.md`, and the TypeScript +// reference runner already maps any `*.parse` throw to it +// (`runners/typescript.ts`, catch block). The corpus encodes it +// directly in each REJECT scenario's `tests.parse`. +// +// Deliberately NOT folded into `collectProtocolCases()`. That list is driven +// end-to-end against the TypeScript reference adapter by +// `test/protocol-conformance.test.ts`, and no adapter implements +// `expires.parse` yet — folding these in would add 116 red cases to a green +// suite. Per-language `expires` tests call this collector directly; when the +// adapters implement the op, absorbing it is a one-line spread into `cases` +// above. +export function collectExpiresCases(): ProtocolCase[] { + const cases: ProtocolCase[] = []; + + for (const s of loadExpiresRfc3339().scenarios) { + if (s.applies_to !== EXPIRES_APPLIES_TO) continue; + + const input = { expires: s.input }; + const parseErr = expectsError(s.tests.parse); + if (parseErr) { + cases.push({ + op: "expires.parse", + scenario: s.name, + input, + expectSuccess: false, + errorType: parseErr, + }); + } else if (expectsSuccess(s.tests.parse)) { + cases.push({ + op: "expires.parse", + scenario: s.name, + input, + expectSuccess: true, + golden: { valid: true }, + }); + } + } + + return cases; +} diff --git a/harness/vectors/mpp-protocol/LICENSE.go-stdlib b/harness/vectors/mpp-protocol/LICENSE.go-stdlib new file mode 100644 index 000000000..2a7cf70da --- /dev/null +++ b/harness/vectors/mpp-protocol/LICENSE.go-stdlib @@ -0,0 +1,27 @@ +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/harness/vectors/mpp-protocol/LICENSE.json-schema-test-suite b/harness/vectors/mpp-protocol/LICENSE.json-schema-test-suite new file mode 100644 index 000000000..c28adbadd --- /dev/null +++ b/harness/vectors/mpp-protocol/LICENSE.json-schema-test-suite @@ -0,0 +1,19 @@ +Copyright (c) 2012 Julian Berman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/harness/vectors/mpp-protocol/PATENTS.go-stdlib b/harness/vectors/mpp-protocol/PATENTS.go-stdlib new file mode 100644 index 000000000..733099041 --- /dev/null +++ b/harness/vectors/mpp-protocol/PATENTS.go-stdlib @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/harness/vectors/mpp-protocol/README.md b/harness/vectors/mpp-protocol/README.md index 058073bd0..0162d62af 100644 --- a/harness/vectors/mpp-protocol/README.md +++ b/harness/vectors/mpp-protocol/README.md @@ -1,5 +1,11 @@ # MPP protocol conformance vectors (canonical) +> **Two provenances live in this directory.** Every file except +> `expires-rfc3339-corpus.json` is imported verbatim from `tempoxyz/mpp-tools` +> and is described immediately below. `expires-rfc3339-corpus.json` is a +> separate import with its own upstreams and its own licence files — see +> [RFC 3339 `expires` corpus](#rfc-3339-expires-corpus). + These JSON vector files are **imported verbatim** from [`tempoxyz/mpp-tools`](https://github.com/tempoxyz/mpp-tools) (`conformance/vectors/`), the canonical IETF-spec conformance suite for the @@ -52,9 +58,69 @@ may carry: ## Source / attribution +Applies to every file in this directory **except** `expires-rfc3339-corpus.json`. + - Upstream: `tempoxyz/mpp-tools`, `conformance/vectors/` - Upstream commit: `b15fea4ee3f12da7ece735dc778ad84102af679c` (2026-06-08) - License: MIT, Copyright (c) 2026 Tempo — see `LICENSE.mpp-tools` in this directory. Do not hand-edit these files. To refresh, re-copy from the upstream repo and bump the commit reference above. + +## RFC 3339 `expires` corpus + +| File | Operation(s) | Spec reference | +|------|--------------|----------------| +| `expires-rfc3339-corpus.json` | `expires.parse` | RFC 3339 §5.6 (grammar), §5.7, §5.8, §4.2–§4.3, App. C, App. D | + +RFC 3339 date / time / date-time **parse** conformance vectors for the MPP +`expires` field (issue #111). Read by `loadExpiresRfc3339()` / +`collectExpiresCases()` in `harness/src/protocol/vectors.ts`. It uses the same +`{ version, spec_ref, description, commands, scenarios }` file shape and the +same `tests.parse` verdict encoding as the canonical files above: + +``` +"tests": { "parse": true } // ACCEPT +"tests": { "parse": { "success": false, "error_type": "parse_error" } } // REJECT +``` + +**Read `applies_to` before you read `tests.parse`.** The corpus holds vectors +for three different RFC 3339 productions — `date-time` (116), `full-date` (74), +`full-time` (38) — and a verdict only means what it says inside its own +production. MPP `expires` holds a `date-time`. **Filter to +`applies_to == "date-time"` before running anything against an `expires` +parser**; 29 of the other 112 carry ACCEPT for inputs (`1963-06-19`, +`08:30:06Z`, …) that an `expires` parser is *correct* to reject. +`collectExpiresCases()` applies that filter. The corpus's own `scope` block +states this at length. + +No `object` golden is supplied for ACCEPT scenarios — deliberately. #111 is a +verdict-alignment problem, not an instant-normalisation problem. + +### Source / attribution (this file only) + +Three upstreams, none of them mpp-tools. The corpus's own top-level +`attribution` object carries the full notices; the summary: + +- **RFC 3339** — Klyne & Newman, July 2002, retrieved 2026-08-07 from + `https://www.rfc-editor.org/rfc/rfc3339.txt`. Copyright (C) The Internet + Society (2002); reproduction permitted per the RFC's own notice, carried + verbatim in `attribution.rfc_3339.notice`. +- **JSON Schema Test Suite** — + [`json-schema-org/JSON-Schema-Test-Suite`](https://github.com/json-schema-org/JSON-Schema-Test-Suite), + `tests/draft2020-12/optional/format/date-time.json`. Upstream commit + `15fe552d6cf76e29cc8165306fb6a72503fd360b` (2026-08-06). MIT, Copyright (c) + 2012 Julian Berman — see `LICENSE.json-schema-test-suite` in this directory. +- **Go standard library** — [`golang/go`](https://github.com/golang/go), + `src/time/format_test.go`. Upstream commit + `c19862e5f8415b4f24b189d065ed739517c548ba` (tag `go1.26.5`). BSD-3-Clause, + Copyright 2009 The Go Authors — see `LICENSE.go-stdlib` in this directory; + additional patent grant in `PATENTS.go-stdlib`. + +Every scenario additionally carries its own `provenance` object naming the +source repo, commit sha, commit date, file, and (for extracted rows) the exact +source line. + +Do not hand-edit this file. To refresh, re-run the import from the upstream +repos and bump the commit references in the corpus's `attribution` block and +above. diff --git a/harness/vectors/mpp-protocol/expires-rfc3339-corpus.json b/harness/vectors/mpp-protocol/expires-rfc3339-corpus.json new file mode 100644 index 000000000..97e6f0175 --- /dev/null +++ b/harness/vectors/mpp-protocol/expires-rfc3339-corpus.json @@ -0,0 +1,7447 @@ +{ + "version": "1.0.0", + "spec_ref": "RFC 3339 §5.6 (grammar), §5.7 (restrictions), §5.8 (examples), §4.2-§4.3 (offsets), Appendix C (leap years), Appendix D (leap seconds)", + "description": "RFC 3339 date / time / date-time PARSE conformance vectors for the MPP `expires` field, merged from four sources. EVERY scenario carries a `provenance` object naming where its input came from and what authority decided its verdict. Read `provenance.type` before trusting any row: `imported_verbatim` and `extracted_from_source` came from third-party test suites at a pinned commit; `hand_authored` and `derived_from_rfc_text` were written against the text of RFC 3339 and each carries the section that decides it.", + "scope": { + "read_this_before_tests_parse": "This corpus holds vectors for THREE different RFC 3339 productions, not one: `full-date` (a bare YYYY-MM-DD), `full-time` (a bare time with an offset), and `date-time` (full-date \"T\" full-time). Every scenario carries an `applies_to` field naming which of the three its verdict answers. Read `applies_to` before you read `tests.parse`. A verdict only means what it says inside its own production.", + "applies_to_values": { + "full-date": "The verdict answers RFC 3339 §5.6 `full-date` — a bare YYYY-MM-DD.", + "full-time": "The verdict answers RFC 3339 §5.6 `full-time` — a bare time with an offset.", + "date-time": "The verdict answers RFC 3339 §5.6 `date-time` = full-date \"T\" full-time. This is the production MPP `expires` holds." + }, + "expires_holds_a_date_time": "The MPP `expires` field holds a `date-time`. A `full-date` verdict and a `full-time` verdict are NOT `expires` verdicts. They are correct statements about a different production, imported verbatim from suites that test that production.", + "an_expires_parser_is_correct_to_reject_full_date_and_full_time_inputs": "An `expires` parser is CORRECT to reject every `full-date` input and every `full-time` input in this file. `2020-01-31` is not an `expires` value. `08:30:06Z` is not an `expires` value. If your parser rejects them, it is right and this corpus agrees with you. Do not file a bug against a parser for rejecting them, and do not \"fix\" a parser to accept them.", + "how_to_test_an_expires_implementation": { + "rule": "Filter to `applies_to == \"date-time\"` before running anything. That slice, and only that slice, is a set of `expires` verdicts. Running the unfiltered file against an `expires` parser will produce failures that are not defects.", + "date_time_slice_size": 116, + "corpus_total": 228, + "filter_python": "expires_vectors = [s for s in corpus[\"scenarios\"] if s[\"applies_to\"] == \"date-time\"]", + "filter_jq": "jq '.scenarios | map(select(.applies_to == \"date-time\"))' expires-rfc3339-corpus.json", + "filter_typescript": "const expiresVectors = corpus.scenarios.filter(s => s.applies_to === \"date-time\");" + }, + "two_classes_that_bite_if_you_skip_the_filter": [ + { + "class": "ACCEPT verdicts on inputs that are not date-times at all", + "count": 29, + "what_it_is": "29 vectors carry ACCEPT (`tests.parse: true`) for inputs that are bare dates or bare times — `1963-06-19`, `2020-01-31`, `08:30:06Z`, `23:59:60Z` and the rest. Every one of them is a correct `full-date` or `full-time` verdict.", + "why_it_bites": "Read as `expires` verdicts they are catastrophic: they instruct an implementer to ACCEPT values an `expires` parser MUST reject. Filtering to `applies_to == \"date-time\"` removes all 29.", + "how_to_list_them": "jq '.scenarios | map(select(.applies_to != \"date-time\" and .tests.parse == true)) | map(.name)' expires-rfc3339-corpus.json", + "by_applies_to": { + "full-date": 16, + "full-time": 13 + }, + "example_names": [ + "jsts_date_001", + "jsts_date_002", + "jsts_date_004", + "jsts_date_006", + "jsts_date_008", + "jsts_date_010" + ] + }, + { + "class": "a REJECT verdict on a fully conforming timestamp", + "count": 1, + "scenario_name": "jsts_date_039", + "input": "2020-11-28T23:55:45Z", + "applies_to": "full-date", + "upstream_file": "tests/draft2020-12/optional/format/date.json", + "upstream_group_description": "validation of date strings", + "upstream_case_description": "an invalid time string in date-time format", + "what_it_is": "Scenario `jsts_date_039`, input `2020-11-28T23:55:45Z`, carries REJECT. That input is a fully conforming RFC 3339 date-time. Upstream ships it in tests/draft2020-12/optional/format/date.json under the group \"validation of date strings\", as a negative case for the `full-date` production: a date-time is not a bare YYYY-MM-DD, so rejecting it is the right `full-date` answer. Its `applies_to` is therefore \"full-date\". (Note the upstream case description, kept verbatim, reads \"an invalid time string in date-time format\" — that wording describes the input's shape, not the production being tested.)", + "why_it_bites": "This is the single most damaging row in the file for an `expires` consumer. `2020-11-28T23:55:45Z` is exactly the shape an `expires` value takes. Run unfiltered, it asserts that a correct SDK is broken. As an `expires` verdict it is inverted; as the `full-date` verdict it actually is, it is correct. Filtering to `applies_to == \"date-time\"` removes it. If you are looking at a mystified failure on `2020-11-28T23:55:45Z`, this paragraph is the answer." + } + ], + "what_this_does_not_change": "No upstream `description` in this file was rewritten. They are verbatim imports under MIT and BSD-3 and they are true statements inside their own production. `applies_to` is what supplies the frame." + }, + "commands": { + "parse": "parse-expires" + }, + "verdict_encoding": { + "accept": "\"tests\": { \"parse\": true }", + "reject": "\"tests\": { \"parse\": { \"success\": false, \"error_type\": \"parse_error\" } }", + "note": "Identical to the encoding used by www-authenticate.json, authorization.json and receipt.json in this directory, and read by harness/src/protocol/vectors.ts (type TestExpectation)." + }, + "provenance_types": { + "imported_verbatim": "Input and upstream verdict copied byte-for-byte from a third-party test suite at a pinned commit. Nothing was authored. `provenance.upstream_valid` is the source's own judgement; the `tests` block is a mechanical restatement of it. Source: JSON Schema Test Suite (S2).", + "extracted_from_source": "Input lifted from a test TABLE in third-party SOURCE code at a pinned commit, and transformed into this schema. Every row carries the exact source line it came from. The verdict is the upstream project's, not ours (`provenance.verdict_authority`). Source: Go stdlib `format_test.go` (S3).", + "hand_authored": "Input written for this corpus from the ABNF in RFC 3339 §5.6 and the restrictions in §5.7, to close a boundary the imports left open. Not copied from any project's test data. Each carries `provenance.rfc_ref`. Source: P036 S5 residual pass.", + "derived_from_rfc_text": "Input derived from the text of RFC 3339 itself — the §5.8 worked examples and the boundaries the §5.6 grammar and §5.7 restrictions generate — with the deciding section cited per row in `provenance.rfc_ref`. These predate the imports and were independently re-derived by a second seat with zero disagreements. They are NOT imported and this file never claims they are." + }, + "dedupe": { + "rule": "Byte-exact on the `input` string. No normalisation and no case folding: two vectors differing only by `T` vs `t`, or by surrounding whitespace, are two vectors.", + "precedence": [ + "imported_verbatim", + "extracted_from_source", + "hand_authored", + "derived_from_rfc_text" + ], + "tie_break": "Same provenance type: the earlier position in the source file wins.", + "duplicates_dropped": 6, + "verdict_disagreements": 0, + "collisions": [ + { + "input": "22:59:60Z", + "kept": { + "name": "jsts_time_006", + "provenance_type": "imported_verbatim", + "verdict": "REJECT" + }, + "dropped": { + "name": "jsts_time_029", + "provenance_type": "imported_verbatim", + "verdict": "REJECT", + "rfc_ref": null + }, + "verdicts_agree": true, + "rule": "same provenance type; earlier source-file position" + }, + { + "input": "23:58:60Z", + "kept": { + "name": "jsts_time_007", + "provenance_type": "imported_verbatim", + "verdict": "REJECT" + }, + "dropped": { + "name": "jsts_time_030", + "provenance_type": "imported_verbatim", + "verdict": "REJECT", + "rfc_ref": null + }, + "verdicts_agree": true, + "rule": "same provenance type; earlier source-file position" + }, + { + "input": "2020-11-28T23:55:45Z", + "kept": { + "name": "jsts_date_039", + "provenance_type": "imported_verbatim", + "verdict": "REJECT" + }, + "dropped": { + "name": "jsts_time_041", + "provenance_type": "imported_verbatim", + "verdict": "REJECT", + "rfc_ref": null + }, + "verdicts_agree": true, + "rule": "same provenance type; earlier source-file position" + }, + { + "input": "1937-01-01T12:00:27.87+00:20", + "kept": { + "name": "jsts_date_time_003", + "provenance_type": "imported_verbatim", + "verdict": "ACCEPT" + }, + "dropped": { + "name": "rfc_5_8_example_twenty_minute_offset", + "provenance_type": "derived_from_rfc_text", + "verdict": "ACCEPT", + "rfc_ref": "RFC 3339 §5.8" + }, + "verdicts_agree": true, + "rule": "higher provenance precedence" + }, + { + "input": "1998-12-31T23:59:60Z", + "kept": { + "name": "jsts_date_time_005", + "provenance_type": "imported_verbatim", + "verdict": "ACCEPT" + }, + "dropped": { + "name": "leap_second_december_month_end", + "provenance_type": "derived_from_rfc_text", + "verdict": "ACCEPT", + "rfc_ref": "RFC 3339 §5.7; Appendix D" + }, + "verdicts_agree": true, + "rule": "higher provenance precedence" + }, + { + "input": "", + "kept": { + "name": "jsts_date_047", + "provenance_type": "imported_verbatim", + "verdict": "REJECT" + }, + "dropped": { + "name": "empty_string", + "provenance_type": "derived_from_rfc_text", + "verdict": "REJECT", + "rfc_ref": "RFC 3339 §5.6 (date-time)" + }, + "verdicts_agree": true, + "rule": "higher provenance precedence" + } + ] + }, + "excluded_unresolved_provenance": { + "rule": "A vector whose verdict could not be tied to an RFC 3339 section by the S4 audit does not ship here. It travels in EVIDENCE.md as a finding about the specification, which is its entire purpose.", + "count": 1, + "vectors": [ + { + "input": "1582-10-10", + "source": "json-schema-test-suite", + "upstream_file": "tests/draft2020-12/optional/format/date.json", + "s4_row": 97, + "reason": "UNRESOLVED-PROVENANCE. RFC 3339 does not decide whether a date inside the 1582 Julian-to-Gregorian reform gap is valid: §5.6 is purely syntactic, §5.7 constrains only the maximum mday, and neither §2 nor Appendix C qualifies \"the Gregorian calendar\" as proleptic or historical. The upstream suite asserts valid: true. Excluded from the corpus; travels in EVIDENCE.md as a finding (S4 §4 U1).", + "name": "jsts_date_070", + "source_verdict": "ACCEPT" + } + ] + }, + "audit": { + "step": "P036 S4", + "document": "agents/audit_engineer/outbox/T-P036-S4-REDERIVE-IMPORTS-2026-08-09_result.md", + "scope": "All 170 imported vectors (143 JSON Schema + 27 Go), each verdict re-derived by hand from RFC 3339 at sha256 9ab2b8864a85dca73a88f49b0927bc7bc85f596926e4fd1890905777924e700a.", + "result": "169 AGREE, 0 DIVERGE, 1 UNRESOLVED-PROVENANCE.", + "verdicts_changed_by_this_merge": 0 + }, + "sources": [ + { + "provenance_type": "imported_verbatim", + "step": "P036 S2", + "repo": "json-schema-org/JSON-Schema-Test-Suite", + "repo_url": "https://github.com/json-schema-org/JSON-Schema-Test-Suite", + "subdirectory": "tests/draft2020-12/optional/format/", + "files": [ + "tests/draft2020-12/optional/format/date-time.json", + "tests/draft2020-12/optional/format/date.json", + "tests/draft2020-12/optional/format/time.json" + ], + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "license": "MIT", + "license_holder": "Copyright (c) 2012 Julian Berman", + "license_file": "LICENSE.json-schema-test-suite", + "import_rule": "Exactly one filter was applied: upstream cases whose `data` is not a JSON string were dropped, because this corpus is about parsing strings and a non-string input is not an RFC 3339 question. The filter tested the JSON type of `data`, not the wording of `description`. NO other filter was applied — no case was dropped for redundancy, for disagreement with its verdict, or for duplicating an existing vector. Deduplication against the existing corpus is deliberately NOT done here.", + "refresh": "python3 import_jsts_build.py — re-extracts from a clone of the upstream repo at the sha above. Bump the sha here and in that script together; verify with import_jsts_verify.py." + }, + { + "provenance_type": "extracted_from_source", + "step": "P036 S3", + "repo": "https://github.com/golang/go", + "repo_url": "https://github.com/golang/go", + "subdirectory": "src/time/", + "files": [ + "src/time/format_test.go" + ], + "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", + "commit_date": "2026-07-07T19:29:04Z", + "commit_sha_tag": "go1.26.5", + "license": "BSD-3-Clause", + "license_holder": "Copyright 2009 The Go Authors.", + "license_file": "LICENSE.go-stdlib", + "additional_grant_file": "PATENTS.go-stdlib", + "extraction_rule": "A Go test table row is extracted ONLY if its Go layout argument is the `RFC3339` or `RFC3339Nano` constant. A row using any other layout makes no claim about RFC 3339 and is not extracted. The ACCEPT/REJECT verdict is read off what the enclosing Test function treats as a failure: a table whose test fails on `err != nil` asserts ACCEPT; a table whose test fails on `err == nil` asserts REJECT. Every vector records the exact source line so a reviewer can open the file at the pinned sha and see the literal. Provenance is `extracted_from_source`, never `imported_verbatim`, because Go tables are source code and not data: each vector is a transformation performed by this repository and therefore a claim about someone else's source.", + "refresh": "python3 extract_go_tables.py import-go-stdlib.json, then re-run this script. Bump the sha here and in the command together." + }, + { + "provenance_type": "hand_authored", + "step": "P036 S5", + "source_document": "RFC 3339", + "source_document_sha256": "9ab2b8864a85dca73a88f49b0927bc7bc85f596926e4fd1890905777924e700a", + "license_file": null, + "note": "No third-party licence obligation: every input literal was written for this corpus. See `hand_authoring_rule` in hand-authored-residual.json." + }, + { + "provenance_type": "derived_from_rfc_text", + "step": "P036 S1 (trimmed by T-P036-TRIM-53-2026-08-08)", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "license_file": null, + "note": "No third-party licence obligation. The Internet Society notice for RFC 3339 travels under `attribution.rfc_3339` below." + } + ], + "source_notes": { + "classification_rule": "Every verdict here is decided by the text of RFC 3339 and by nothing else. Each shipped scenario carries an `rfc_ref` naming the section that decides it, and the verdict must follow from that section's text rather than from any parser's behaviour. Where the RFC and a widely-deployed parser disagree, the RFC governs and the disagreement is recorded under `rfc_vs_implementation_findings` below. A literal whose verdict cannot be pinned to a section is NOT shipped as a scenario; it is recorded in `disputed` instead. Every input literal in this corpus was written for this corpus from the RFC's own grammar; none is copied from another project's test data.", + "rfc_vs_implementation_findings": [ + "Lower-case 't' and 'z' separators. The §5.6 NOTE states that the \"T\" and \"Z\" characters in this syntax may alternatively be lower case \"t\" or \"z\", so RFC 3339 ACCEPTS them; the same section adds that applications which GENERATE this format SHOULD use upper case, which is a generation constraint and not a parse constraint. Parsers built against a fixed upper-case layout string reject them. The three lowercase_* vectors here are ACCEPT on the RFC's authority, and this NOTE is the only thing that makes them ACCEPT rather than REJECT.", + "Leap second (time-second = 60). §5.7 permits time-second = 60 at the end of months in which a leap second occurs, and the §5.8 worked examples carry 1990-12-31T23:59:60Z and its -08:00 restatement verbatim, so RFC 3339 ACCEPTS. Parsers backed by a calendar type with no representable 61st second reject it as out of range. The four leap-second vectors here are ACCEPT on the RFC's authority.", + "Fractional digits beyond nine. §5.6 time-secfrac = \".\" 1*DIGIT sets a lower bound of one digit and no upper bound, so any digit count is ACCEPT as grammar. Implementations backed by a fixed nanosecond field truncate at nine, and implementations that accumulate the fraction into a signed 64-bit integer before scaling overflow at nineteen digits. Neither behaviour is encoded in these verdicts; secfrac_10_digits and secfrac_19_digits_exceeds_int64 exercise both boundaries." + ], + "known_go_vs_rfc_divergences": [ + { + "id": 1, + "divergence": "lower-case 't' and 'z' separators", + "rfc_3339": "ACCEPTS — §5.6 NOTE permits lower case 't' and 'z'", + "go": "REJECTS", + "affects_vectors_here": "none — no table row in this extraction uses a lower-case separator" + }, + { + "id": 2, + "divergence": "leap second value 60 in the seconds field", + "rfc_3339": "ACCEPTS — §5.7 permits :60 for a positive leap second", + "go": "REJECTS — 'second out of range'", + "affects_vectors_here": "none directly; the nearest row, parseErrorTests :67, is out of range under both" + }, + { + "id": 3, + "divergence": "more than 9 fractional-second digits", + "rfc_3339": "ACCEPTS — §5.6 time-secfrac is '.' 1*DIGIT with no upper bound", + "go": "ACCEPTS but TRUNCATES to nanosecond precision", + "affects_vectors_here": "the longFractionalDigitsTests rows tagged `go-truncates`" + } + ], + "json_schema_verdict_authority_warning": "THE `tests` BLOCK ON EVERY SCENARIO IS A MECHANICAL RESTATEMENT OF THE UPSTREAM `valid` BOOLEAN AND IS NOT THIS PROJECT'S VERDICT. It was produced by the rule `valid:true -> parse:true` / `valid:false -> parse:{success:false}` with no judgement applied. The authoritative, unmodified upstream value is carried separately at `upstream.valid` on each scenario. These verdicts have NOT been adjudicated against the text of RFC 3339; that adjudication is a separate step. Scenarios are tagged `unadjudicated` for that reason.", + "json_schema_no_rfc_ref_note": "Scenarios in this file deliberately carry NO `rfc_ref` key, unlike expires-rfc3339.json. Assigning an RFC 3339 section to an imported verdict is an act of adjudication, not of import, and is out of scope for the importing step.", + "hand_authoring_rule": { + "provenance": "hand_authored", + "rfc_text_sha256": "9ab2b8864a85dca73a88f49b0927bc7bc85f596926e4fd1890905777924e700a", + "rfc_text_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "rfc_text_retrieved_on": "2026-08-09", + "not_fabricated": "Issue #111's scope line reads `Do NOT propose fabricated vectors.` These are not fabricated. Each input below is (a) an edge case named in #111's own list or routed by a prior step of this order, (b) derived from the ABNF grammar and restrictions in the RFC 3339 text at the sha256 above, and (c) cited to the specific section that decides it. Every literal was checked against the 22 Go-derived literals deleted in the 2026-08-08 trim; none is a reuse.", + "boundary_rule": "A boundary vector without its counterpart tests nothing. Each boundary below is authored with both sides present -- either both sides are authored here, or the counterpart already ships in a named file and that file is named in the scenario description.", + "deliberately_not_authored": "Classes already covered elsewhere, and questions the RFC does not decide, are listed in the S5 result document rather than given a verdict here. The `disputed` block of expires-rfc3339.json holds the undecided questions by design and is NOT overturned by this file." + } + }, + "counts": { + "scenarios_total": 228, + "by_provenance_type": { + "imported_verbatim": 139, + "extracted_from_source": 27, + "hand_authored": 4, + "derived_from_rfc_text": 58 + }, + "by_applies_to": { + "date-time": 116, + "full-date": 74, + "full-time": 38 + }, + "by_verdict": { + "ACCEPT": 83, + "REJECT": 145 + }, + "by_provenance_type_and_verdict": { + "imported_verbatim": { + "ACCEPT": 37, + "REJECT": 102 + }, + "extracted_from_source": { + "ACCEPT": 18, + "REJECT": 9 + }, + "hand_authored": { + "ACCEPT": 3, + "REJECT": 1 + }, + "derived_from_rfc_text": { + "ACCEPT": 25, + "REJECT": 33 + } + }, + "inputs_considered": 235, + "excluded_unresolved_provenance": 1, + "duplicates_dropped": 6, + "disputed_not_shipped": 5 + }, + "attribution": { + "rfc_3339": { + "source": "RFC 3339, \"Date and Time on the Internet: Timestamps\", G. Klyne, C. Newman, July 2002.", + "retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "retrieved_on": "2026-08-07", + "notice": "Copyright (C) The Internet Society (2002). All Rights Reserved. This document and translations of it may be copied and furnished to others, and derivative works that comment on or otherwise explain it or assist in its implementation may be prepared, copied, published and distributed, in whole or in part, without restriction of any kind, provided that the above copyright notice and this paragraph are included on all such copies and derivative works." + }, + "json_schema_test_suite": { + "source": "JSON Schema Test Suite, json-schema-org/JSON-Schema-Test-Suite.", + "retrieved_from": "https://github.com/json-schema-org/JSON-Schema-Test-Suite", + "commit": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "retrieved_on": "2026-08-09", + "license": "MIT License, Copyright (c) 2012 Julian Berman — see LICENSE.json-schema-test-suite in this directory.", + "notice": "Do not hand-edit these scenarios. To refresh, re-import from the upstream repo and bump the commit reference above." + }, + "go_stdlib": { + "source": "The Go Programming Language standard library, src/time/format_test.go", + "repo": "https://github.com/golang/go", + "sha": "c19862e5f8415b4f24b189d065ed739517c548ba", + "sha_is_tag": "go1.26.5", + "path": "src/time/format_test.go", + "license": "BSD-3-Clause — see LICENSE.go-stdlib in this directory", + "copyright": "Copyright 2009 The Go Authors.", + "additional_grant": "See PATENTS.go-stdlib in this directory.", + "per_file_header": "// Copyright 2009 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.", + "note": "Do not hand-edit this file. To refresh, re-run extract_go_tables.py against a newer pinned sha and bump the sha recorded above." + } + }, + "scenarios": [ + { + "name": "jsts_date_time_001", + "description": "a valid date-time string", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date-time", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "date-time", + "input": "1963-06-19T08:30:06.283185Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date-time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of date-time strings", + "upstream_case_description": "a valid date-time string" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_time_002", + "description": "a valid date-time string without second fraction", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date-time", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "date-time", + "input": "1963-06-19T08:30:06Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date-time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of date-time strings", + "upstream_case_description": "a valid date-time string without second fraction" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_time_003", + "description": "a valid date-time string with plus offset", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date-time", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "date-time", + "input": "1937-01-01T12:00:27.87+00:20", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date-time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of date-time strings", + "upstream_case_description": "a valid date-time string with plus offset" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_time_004", + "description": "a valid date-time string with minus offset", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date-time", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "date-time", + "input": "1990-12-31T15:59:50.123-08:00", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date-time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of date-time strings", + "upstream_case_description": "a valid date-time string with minus offset" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_time_005", + "description": "a valid date-time with a leap second, UTC", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date-time", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "date-time", + "input": "1998-12-31T23:59:60Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date-time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of date-time strings", + "upstream_case_description": "a valid date-time with a leap second, UTC" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_time_006", + "description": "a valid date-time with a leap second, with minus offset", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date-time", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "date-time", + "input": "1998-12-31T15:59:60.123-08:00", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date-time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of date-time strings", + "upstream_case_description": "a valid date-time with a leap second, with minus offset" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_time_007", + "description": "an invalid date-time past leap second, UTC", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "date-time", + "input": "1998-12-31T23:59:61Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date-time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date-time strings", + "upstream_case_description": "an invalid date-time past leap second, UTC" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_008", + "description": "an invalid date-time with leap second on a wrong minute, UTC", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "date-time", + "input": "1998-12-31T23:58:60Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date-time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date-time strings", + "upstream_case_description": "an invalid date-time with leap second on a wrong minute, UTC" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_009", + "description": "an invalid date-time with leap second on a wrong hour, UTC", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "date-time", + "input": "1998-12-31T22:59:60Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date-time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date-time strings", + "upstream_case_description": "an invalid date-time with leap second on a wrong hour, UTC" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_010", + "description": "an invalid day in date-time string", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "date-time", + "input": "1990-02-31T15:59:59.123-08:00", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date-time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date-time strings", + "upstream_case_description": "an invalid day in date-time string" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_011", + "description": "an invalid offset in date-time string", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "date-time", + "input": "1990-12-31T15:59:59-24:00", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date-time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date-time strings", + "upstream_case_description": "an invalid offset in date-time string" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_012", + "description": "an invalid closing Z after time-zone offset", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "date-time", + "input": "1963-06-19T08:30:06.28123+01:00Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date-time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date-time strings", + "upstream_case_description": "an invalid closing Z after time-zone offset" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_013", + "description": "an invalid hour in date-time string", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "date-time", + "input": "1990-12-31T24:00:00Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date-time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date-time strings", + "upstream_case_description": "an invalid hour in date-time string" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_014", + "description": "an invalid minute in date-time string", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "date-time", + "input": "1990-12-31T15:60:00Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date-time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date-time strings", + "upstream_case_description": "an invalid minute in date-time string" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_015", + "description": "an invalid offset minute in date-time string", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "date-time", + "input": "1990-12-31T10:00:00+10:60", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date-time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date-time strings", + "upstream_case_description": "an invalid offset minute in date-time string" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_016", + "description": "an invalid date-time string", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "date-time", + "input": "06/19/1963 08:30:06 PST", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date-time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date-time strings", + "upstream_case_description": "an invalid date-time string" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_017", + "description": "case-insensitive T and Z", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date-time", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "date-time", + "input": "1963-06-19t08:30:06.283185z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date-time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of date-time strings", + "upstream_case_description": "case-insensitive T and Z" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_time_018", + "description": "only RFC3339 not all of ISO 8601 are valid", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "date-time", + "input": "2013-350T01:01:01", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date-time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date-time strings", + "upstream_case_description": "only RFC3339 not all of ISO 8601 are valid" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_019", + "description": "invalid non-padded month dates", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "date-time", + "input": "1963-6-19T08:30:06.283185Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date-time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date-time strings", + "upstream_case_description": "invalid non-padded month dates" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_020", + "description": "invalid non-padded day dates", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "date-time", + "input": "1963-06-1T08:30:06.283185Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date-time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date-time strings", + "upstream_case_description": "invalid non-padded day dates" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_021", + "description": "invalid non-ASCII '৪' (a Bengali 4) in date portion", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "date-time", + "input": "1963-06-1৪T00:00:00Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date-time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date-time strings", + "upstream_case_description": "invalid non-ASCII '৪' (a Bengali 4) in date portion" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_022", + "description": "invalid non-ASCII '৪' (a Bengali 4) in time portion", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "date-time", + "input": "1963-06-11T0৪:00:00Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date-time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date-time strings", + "upstream_case_description": "invalid non-ASCII '৪' (a Bengali 4) in time portion" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_023", + "description": "invalid extended year", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "date-time", + "input": "+11963-06-19T08:30:06.283185Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date-time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date-time strings", + "upstream_case_description": "invalid extended year" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_024", + "description": "a numeric offset without minutes is invalid", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "date-time", + "input": "1985-04-12T23:20:50+01", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date-time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date-time strings", + "upstream_case_description": "a numeric offset without minutes is invalid" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_025", + "description": "hour 24 is invalid even with a leap second", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "date-time", + "input": "2016-12-31T24:59:60+01:00", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date-time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date-time strings", + "upstream_case_description": "hour 24 is invalid even with a leap second" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_026", + "description": "a second fraction of fifteen nines is valid", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date-time", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "date-time", + "input": "1985-04-12T00:59:59.999999999999999Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date-time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of date-time strings", + "upstream_case_description": "a second fraction of fifteen nines is valid" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_time_027", + "description": "a trailing newline is invalid", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "date-time", + "input": "1985-04-12T23:20:50Z\n", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date-time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date-time strings", + "upstream_case_description": "a trailing newline is invalid" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_001", + "description": "a valid date string", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "1963-06-19", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "a valid date string" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_002", + "description": "a valid date string with 31 days in January", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-01-31", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "a valid date string with 31 days in January" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_003", + "description": "a invalid date string with 32 days in January", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-01-32", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "a invalid date string with 32 days in January" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_004", + "description": "a valid date string with 28 days in February (normal)", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2021-02-28", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "a valid date string with 28 days in February (normal)" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_005", + "description": "a invalid date string with 30 days in February (leap)", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-02-30", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "a invalid date string with 30 days in February (leap)" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_006", + "description": "a valid date string with 31 days in March", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-03-31", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "a valid date string with 31 days in March" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_007", + "description": "a invalid date string with 32 days in March", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-03-32", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "a invalid date string with 32 days in March" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_008", + "description": "a valid date string with 30 days in April", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-04-30", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "a valid date string with 30 days in April" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_009", + "description": "a invalid date string with 31 days in April", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-04-31", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "a invalid date string with 31 days in April" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_010", + "description": "a valid date string with 31 days in May", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-05-31", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "a valid date string with 31 days in May" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_011", + "description": "a invalid date string with 32 days in May", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-05-32", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "a invalid date string with 32 days in May" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_012", + "description": "a valid date string with 30 days in June", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-06-30", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "a valid date string with 30 days in June" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_013", + "description": "a invalid date string with 31 days in June", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-06-31", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "a invalid date string with 31 days in June" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_014", + "description": "a valid date string with 31 days in July", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-07-31", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "a valid date string with 31 days in July" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_015", + "description": "a invalid date string with 32 days in July", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-07-32", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "a invalid date string with 32 days in July" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_016", + "description": "a valid date string with 31 days in August", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-08-31", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "a valid date string with 31 days in August" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_017", + "description": "a invalid date string with 32 days in August", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-08-32", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "a invalid date string with 32 days in August" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_018", + "description": "a valid date string with 30 days in September", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-09-30", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "a valid date string with 30 days in September" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_019", + "description": "a invalid date string with 31 days in September", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-09-31", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "a invalid date string with 31 days in September" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_020", + "description": "a valid date string with 31 days in October", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-10-31", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "a valid date string with 31 days in October" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_021", + "description": "a invalid date string with 32 days in October", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-10-32", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "a invalid date string with 32 days in October" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_022", + "description": "a valid date string with 30 days in November", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-11-30", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "a valid date string with 30 days in November" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_023", + "description": "a invalid date string with 31 days in November", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-11-31", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "a invalid date string with 31 days in November" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_024", + "description": "a valid date string with 31 days in December", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-12-31", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "a valid date string with 31 days in December" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_025", + "description": "a invalid date string with 32 days in December", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-12-32", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "a invalid date string with 32 days in December" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_026", + "description": "an invalid date string", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "06/19/1963", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "an invalid date string" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_027", + "description": "only RFC3339 not all of ISO 8601 are valid", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2013-350", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "only RFC3339 not all of ISO 8601 are valid" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_028", + "description": "non-padded month dates are not valid", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "1998-1-20", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "non-padded month dates are not valid" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_029", + "description": "non-padded day dates are not valid", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "1998-01-1", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "non-padded day dates are not valid" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_030", + "description": "invalid month", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "1998-13-01", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid month" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_031", + "description": "2021 is not a leap year", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2021-02-29", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "2021 is not a leap year" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_032", + "description": "2020 is a leap year", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-02-29", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "2020 is a leap year" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_033", + "description": "invalid non-ASCII '৪' (a Bengali 4)", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "1963-06-1৪", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid non-ASCII '৪' (a Bengali 4)" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_034", + "description": "invalid: non-ASCII Bengali digit in month field", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-0৪-01", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: non-ASCII Bengali digit in month field" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_035", + "description": "ISO8601 / non-RFC3339: YYYYMMDD without dashes (2023-03-28)", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "20230328", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "ISO8601 / non-RFC3339: YYYYMMDD without dashes (2023-03-28)" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_036", + "description": "ISO8601 / non-RFC3339: week number implicit day of week (2023-01-02)", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2023-W01", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "ISO8601 / non-RFC3339: week number implicit day of week (2023-01-02)" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_037", + "description": "ISO8601 / non-RFC3339: week number with day of week (2023-03-28)", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2023-W13-2", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "ISO8601 / non-RFC3339: week number with day of week (2023-03-28)" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_038", + "description": "ISO8601 / non-RFC3339: week number rollover to next year (2023-01-01)", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2022W527", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "ISO8601 / non-RFC3339: week number rollover to next year (2023-01-01)" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_039", + "description": "an invalid time string in date-time format", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-11-28T23:55:45Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "an invalid time string in date-time format" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_040", + "description": "century year 0100 is not a leap year", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "0100-02-29", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "century year 0100 is not a leap year" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_041", + "description": "century year 0400 is a leap year", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "0400-02-29", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "century year 0400 is a leap year" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_042", + "description": "century year 2100 is not a leap year", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2100-02-29", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "century year 2100 is not a leap year" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_043", + "description": "invalid: leading whitespace is not permitted", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": " 2024-01-15", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: leading whitespace is not permitted" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_044", + "description": "invalid: trailing whitespace is not permitted", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2024-01-15 ", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: trailing whitespace is not permitted" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_045", + "description": "invalid: month 00 is not valid per date-month range 01-12", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2024-00-15", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: month 00 is not valid per date-month range 01-12" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_046", + "description": "invalid: day 00 is not valid per date-mday minimum of 01", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2024-01-00", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: day 00 is not valid per date-mday minimum of 01" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_047", + "description": "invalid: empty string", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: empty string" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_048", + "description": "invalid: embedded whitespace between year and month", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020 -01-01", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: embedded whitespace between year and month" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_049", + "description": "invalid: trailing character after valid full-date", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-01-01X", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: trailing character after valid full-date" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_050", + "description": "invalid: trailing Z after full-date", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-01-01Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: trailing Z after full-date" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_051", + "description": "invalid: full-date followed by space and time component", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-01-01 00:00:00Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: full-date followed by space and time component" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_052", + "description": "valid: four-digit year 0001", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "0001-01-01", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "valid: four-digit year 0001" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_053", + "description": "invalid: two-digit year (N-2 digits)", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "20-01-01", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: two-digit year (N-2 digits)" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_054", + "description": "invalid: three-digit year (N-1 digits)", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "998-01-01", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: three-digit year (N-1 digits)" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_055", + "description": "invalid: five-digit year (N+1 digits)", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "12020-01-01", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: five-digit year (N+1 digits)" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_056", + "description": "invalid: positive sign prefix on year", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "+2020-01-01", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: positive sign prefix on year" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_057", + "description": "invalid: negative sign prefix on year", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "-2020-01-01", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: negative sign prefix on year" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_058", + "description": "invalid: non-ASCII Bengali digit in year field", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "২020-01-01", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: non-ASCII Bengali digit in year field" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_059", + "description": "invalid: alphabetic characters in year field", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "YYYY-01-01", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: alphabetic characters in year field" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_060", + "description": "invalid: three-digit month (N+1 digits)", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-001-01", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: three-digit month (N+1 digits)" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_061", + "description": "invalid: alphabetic characters in month field", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-MM-01", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: alphabetic characters in month field" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_062", + "description": "invalid: three-digit day (N+1 digits)", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-01-001", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: three-digit day (N+1 digits)" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_063", + "description": "invalid: alphabetic characters in day field", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-01-DD", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: alphabetic characters in day field" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_064", + "description": "invalid: colon separators", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020:01:01", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: colon separators" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_065", + "description": "invalid: dot separators", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020.01.01", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: dot separators" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_066", + "description": "invalid: space separators", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020 01 01", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: space separators" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_067", + "description": "invalid: mixed slash and hyphen separators", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-01/01", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: mixed slash and hyphen separators" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_068", + "description": "invalid: duplicated first hyphen", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020--01-01", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: duplicated first hyphen" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_069", + "description": "invalid: duplicated second hyphen", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-01--01", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: duplicated second hyphen" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_071", + "description": "invalid: year field numerically larger than a 32-bit signed integer", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2147483648-01-01", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: year field numerically larger than a 32-bit signed integer" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_072", + "description": "invalid: character just above '9' as the second digit of the day", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-01-0:", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: character just above '9' as the second digit of the day" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_073", + "description": "invalid: non-ASCII digit leaving ten UTF-8 bytes but eight characters", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "২0-01-01", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: non-ASCII digit leaving ten UTF-8 bytes but eight characters" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_074", + "description": "invalid: en dash separators instead of hyphen-minus", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020–01–01", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: en dash separators instead of hyphen-minus" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_075", + "description": "invalid: NUL character after an otherwise valid full-date", + "tags": [ + "imported", + "json-schema-test-suite", + "format-date", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-date", + "input": "2020-01-01\u0000", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/date.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of date strings", + "upstream_case_description": "invalid: NUL character after an otherwise valid full-date" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_time_001", + "description": "a valid time string", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "08:30:06Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "a valid time string" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_time_002", + "description": "invalid time string with extra leading zeros", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "008:030:006Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "invalid time string with extra leading zeros" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_time_003", + "description": "invalid time string with no leading zero for single digit", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "8:3:6Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "invalid time string with no leading zero for single digit" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_time_004", + "description": "hour, minute, second must be two digits", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "8:0030:6Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "hour, minute, second must be two digits" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_time_005", + "description": "a valid time string with leap second, Zulu", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "23:59:60Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "a valid time string with leap second, Zulu" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_time_006", + "description": "invalid leap second, Zulu (wrong hour)", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "22:59:60Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "invalid leap second, Zulu (wrong hour)" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_time_007", + "description": "invalid leap second, Zulu (wrong minute)", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "23:58:60Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "invalid leap second, Zulu (wrong minute)" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_time_008", + "description": "valid leap second, zero time-offset", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "23:59:60+00:00", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "valid leap second, zero time-offset" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_time_009", + "description": "invalid leap second, zero time-offset (wrong hour)", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "22:59:60+00:00", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "invalid leap second, zero time-offset (wrong hour)" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_time_010", + "description": "invalid leap second, zero time-offset (wrong minute)", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "23:58:60+00:00", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "invalid leap second, zero time-offset (wrong minute)" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_time_011", + "description": "valid leap second, positive time-offset", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "01:29:60+01:30", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "valid leap second, positive time-offset" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_time_012", + "description": "valid leap second, large positive time-offset", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "23:29:60+23:30", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "valid leap second, large positive time-offset" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_time_013", + "description": "invalid leap second, positive time-offset (wrong hour)", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "23:59:60+01:00", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "invalid leap second, positive time-offset (wrong hour)" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_time_014", + "description": "invalid leap second, positive time-offset (wrong minute)", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "23:59:60+00:30", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "invalid leap second, positive time-offset (wrong minute)" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_time_015", + "description": "valid leap second, negative time-offset", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "15:59:60-08:00", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "valid leap second, negative time-offset" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_time_016", + "description": "valid leap second, large negative time-offset", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "00:29:60-23:30", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "valid leap second, large negative time-offset" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_time_017", + "description": "invalid leap second, negative time-offset (wrong hour)", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "23:59:60-01:00", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "invalid leap second, negative time-offset (wrong hour)" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_time_018", + "description": "invalid leap second, negative time-offset (wrong minute)", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "23:59:60-00:30", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "invalid leap second, negative time-offset (wrong minute)" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_time_019", + "description": "a valid time string with second fraction", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "23:20:50.52Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "a valid time string with second fraction" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_time_020", + "description": "a valid time string with precise second fraction", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "08:30:06.283185Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "a valid time string with precise second fraction" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_time_021", + "description": "a valid time string with plus offset", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "08:30:06+00:20", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "a valid time string with plus offset" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_time_022", + "description": "a valid time string with minus offset", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "08:30:06-08:00", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "a valid time string with minus offset" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_time_023", + "description": "time with unknown local offset is valid", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "12:34:56-00:00", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "time with unknown local offset is valid" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_time_024", + "description": "hour, minute in time-offset must be two digits", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "08:30:06-8:000", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "hour, minute in time-offset must be two digits" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_time_025", + "description": "a valid time string with case-insensitive Z", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-valid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "08:30:06z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": true, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "a valid time string with case-insensitive Z" + }, + "tests": { + "parse": true + } + }, + { + "name": "jsts_time_026", + "description": "an invalid time string with invalid hour", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "24:00:00Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "an invalid time string with invalid hour" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_time_027", + "description": "an invalid time string with invalid minute", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "00:60:00Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "an invalid time string with invalid minute" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_time_028", + "description": "an invalid time string with invalid second", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "00:00:61Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "an invalid time string with invalid second" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_time_031", + "description": "an invalid time string with invalid time numoffset hour", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "01:02:03+24:00", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "an invalid time string with invalid time numoffset hour" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_time_032", + "description": "an invalid time string with invalid time numoffset minute", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "01:02:03+00:60", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "an invalid time string with invalid time numoffset minute" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_time_033", + "description": "an invalid time string with invalid time with both Z and numoffset", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "01:02:03Z+00:30", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "an invalid time string with invalid time with both Z and numoffset" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_time_034", + "description": "an invalid offset indicator", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "08:30:06 PST", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "an invalid offset indicator" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_time_035", + "description": "only RFC3339 not all of ISO 8601 are valid", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "01:01:01,1111", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "only RFC3339 not all of ISO 8601 are valid" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_time_036", + "description": "no time offset", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "12:00:00", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "no time offset" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_time_037", + "description": "no time offset with second fraction", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "12:00:00.52", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "no time offset with second fraction" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_time_038", + "description": "invalid non-ASCII '২' (a Bengali 2)", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "1২:00:00Z", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "invalid non-ASCII '২' (a Bengali 2)" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_time_039", + "description": "offset not starting with plus or minus", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "08:30:06#00:20", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "offset not starting with plus or minus" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_time_040", + "description": "contains letters", + "tags": [ + "imported", + "json-schema-test-suite", + "format-time", + "upstream-invalid", + "unadjudicated" + ], + "applies_to": "full-time", + "input": "ab:cd:ef", + "source": "json-schema-test-suite", + "provenance": { + "type": "imported_verbatim", + "source_repo": "json-schema-org/JSON-Schema-Test-Suite", + "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", + "commit_date": "2026-08-06T22:48:41-07:00", + "file": "tests/draft2020-12/optional/format/time.json", + "license": "MIT", + "license_file": "LICENSE.json-schema-test-suite", + "verdict_authority": "json-schema-test-suite", + "upstream_valid": false, + "upstream_group_description": "validation of time strings", + "upstream_case_description": "contains letters" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "go_parsetests_2010_02_04t21_00_57_08_00", + "description": "Go stdlib parseTests row with layout time.RFC3339. TestParse fails the test if Parse returns an error, so the table asserts Go accepts this input under the RFC3339 layout.", + "tags": [ + "accept", + "go-stdlib", + "parse-table" + ], + "applies_to": "date-time", + "input": "2010-02-04T21:00:57-08:00", + "source": "go-stdlib-format_test.go", + "provenance": { + "type": "extracted_from_source", + "source_repo": "github.com/golang/go", + "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", + "commit_sha_tag": "go1.26.5", + "file": "src/time/format_test.go", + "line": 274, + "table": "parseTests", + "license": "BSD-3-Clause", + "license_file": "LICENSE.go-stdlib", + "additional_grant_file": "PATENTS.go-stdlib", + "verdict_authority": "go-stdlib" + }, + "tests": { + "parse": true + } + }, + { + "name": "go_parsetests_2010_02_04t21_00_57_012345678_08_00", + "description": "Go stdlib parseTests row with layout time.RFC3339. TestParse fails the test if Parse returns an error, so the table asserts Go accepts this input under the RFC3339 layout.", + "tags": [ + "accept", + "go-stdlib", + "parse-table" + ], + "applies_to": "date-time", + "input": "2010-02-04T21:00:57.012345678-08:00", + "source": "go-stdlib-format_test.go", + "provenance": { + "type": "extracted_from_source", + "source_repo": "github.com/golang/go", + "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", + "commit_sha_tag": "go1.26.5", + "file": "src/time/format_test.go", + "line": 283, + "table": "parseTests", + "license": "BSD-3-Clause", + "license_file": "LICENSE.go-stdlib", + "additional_grant_file": "PATENTS.go-stdlib", + "verdict_authority": "go-stdlib" + }, + "tests": { + "parse": true + } + }, + { + "name": "go_parseerror_2006_01_02t15_04_05z07_00", + "description": "Go stdlib parseErrorTests row with layout time.RFC3339. TestParseErrors fails the test if Parse returns no error, so the table asserts Go rejects this input under the RFC3339 layout. Go's expected error substring: 'extra text: \"07:00\"'", + "tags": [ + "reject", + "go-stdlib", + "parse-error-table" + ], + "applies_to": "date-time", + "input": "2006-01-02T15:04:05Z07:00", + "source": "go-stdlib-format_test.go", + "provenance": { + "type": "extracted_from_source", + "source_repo": "github.com/golang/go", + "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", + "commit_sha_tag": "go1.26.5", + "file": "src/time/format_test.go", + "line": 652, + "table": "parseErrorTests", + "license": "BSD-3-Clause", + "license_file": "LICENSE.go-stdlib", + "additional_grant_file": "PATENTS.go-stdlib", + "verdict_authority": "go-stdlib" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "go_parseerror_2006_01_02t15_04_abc", + "description": "Go stdlib parseErrorTests row with layout time.RFC3339. TestParseErrors fails the test if Parse returns no error, so the table asserts Go rejects this input under the RFC3339 layout. Go's expected error substring: 'cannot parse \"_abc\" as \":\"'", + "tags": [ + "reject", + "go-stdlib", + "parse-error-table" + ], + "applies_to": "date-time", + "input": "2006-01-02T15:04_abc", + "source": "go-stdlib-format_test.go", + "provenance": { + "type": "extracted_from_source", + "source_repo": "github.com/golang/go", + "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", + "commit_sha_tag": "go1.26.5", + "file": "src/time/format_test.go", + "line": 653, + "table": "parseErrorTests", + "license": "BSD-3-Clause", + "license_file": "LICENSE.go-stdlib", + "additional_grant_file": "PATENTS.go-stdlib", + "verdict_authority": "go-stdlib" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "go_parseerror_2006_01_02t15_04_05_abc", + "description": "Go stdlib parseErrorTests row with layout time.RFC3339. TestParseErrors fails the test if Parse returns no error, so the table asserts Go rejects this input under the RFC3339 layout. Go's expected error substring: 'cannot parse \"_abc\" as \"Z07:00\"'", + "tags": [ + "reject", + "go-stdlib", + "parse-error-table" + ], + "applies_to": "date-time", + "input": "2006-01-02T15:04:05_abc", + "source": "go-stdlib-format_test.go", + "provenance": { + "type": "extracted_from_source", + "source_repo": "github.com/golang/go", + "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", + "commit_sha_tag": "go1.26.5", + "file": "src/time/format_test.go", + "line": 654, + "table": "parseErrorTests", + "license": "BSD-3-Clause", + "license_file": "LICENSE.go-stdlib", + "additional_grant_file": "PATENTS.go-stdlib", + "verdict_authority": "go-stdlib" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "go_parseerror_2006_01_02t15_04_05z_abc", + "description": "Go stdlib parseErrorTests row with layout time.RFC3339. TestParseErrors fails the test if Parse returns no error, so the table asserts Go rejects this input under the RFC3339 layout. Go's expected error substring: 'extra text: \"_abc\"'", + "tags": [ + "reject", + "go-stdlib", + "parse-error-table" + ], + "applies_to": "date-time", + "input": "2006-01-02T15:04:05Z_abc", + "source": "go-stdlib-format_test.go", + "provenance": { + "type": "extracted_from_source", + "source_repo": "github.com/golang/go", + "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", + "commit_sha_tag": "go1.26.5", + "file": "src/time/format_test.go", + "line": 655, + "table": "parseErrorTests", + "license": "BSD-3-Clause", + "license_file": "LICENSE.go-stdlib", + "additional_grant_file": "PATENTS.go-stdlib", + "verdict_authority": "go-stdlib" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "go_parseerror_2010_02_04t21_00_67_012345678_08_00", + "description": "Go stdlib parseErrorTests row with layout time.RFC3339. TestParseErrors fails the test if Parse returns no error, so the table asserts Go rejects this input under the RFC3339 layout. Go's expected error substring: 'second out of range'", + "tags": [ + "reject", + "go-stdlib", + "parse-error-table" + ], + "applies_to": "date-time", + "input": "2010-02-04T21:00:67.012345678-08:00", + "source": "go-stdlib-format_test.go", + "provenance": { + "type": "extracted_from_source", + "source_repo": "github.com/golang/go", + "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", + "commit_sha_tag": "go1.26.5", + "file": "src/time/format_test.go", + "line": 657, + "table": "parseErrorTests", + "license": "BSD-3-Clause", + "license_file": "LICENSE.go-stdlib", + "additional_grant_file": "PATENTS.go-stdlib", + "verdict_authority": "go-stdlib" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "go_parseerror_0000_01_01t00_00_0_00_00", + "description": "Go stdlib parseErrorTests row with layout time.RFC3339. TestParseErrors fails the test if Parse returns no error, so the table asserts Go rejects this input under the RFC3339 layout. Go's expected error substring: 'cannot parse \".0+00:00\" as \"05\"'", + "tags": [ + "reject", + "go-stdlib", + "parse-error-table" + ], + "applies_to": "date-time", + "input": "0000-01-01T00:00:.0+00:00", + "source": "go-stdlib-format_test.go", + "provenance": { + "type": "extracted_from_source", + "source_repo": "github.com/golang/go", + "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", + "commit_sha_tag": "go1.26.5", + "file": "src/time/format_test.go", + "line": 659, + "table": "parseErrorTests", + "license": "BSD-3-Clause", + "license_file": "LICENSE.go-stdlib", + "additional_grant_file": "PATENTS.go-stdlib", + "verdict_authority": "go-stdlib" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "go_parseerror_double_quote_char", + "description": "Go stdlib parseErrorTests row with layout time.RFC3339. TestParseErrors fails the test if Parse returns no error, so the table asserts Go rejects this input under the RFC3339 layout. Go's expected error substring: 'cannot parse \"\\\\\"\" as \"2006\"'", + "tags": [ + "reject", + "go-stdlib", + "parse-error-table" + ], + "applies_to": "date-time", + "input": "\"", + "source": "go-stdlib-format_test.go", + "provenance": { + "type": "extracted_from_source", + "source_repo": "github.com/golang/go", + "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", + "commit_sha_tag": "go1.26.5", + "file": "src/time/format_test.go", + "line": 670, + "table": "parseErrorTests", + "license": "BSD-3-Clause", + "license_file": "LICENSE.go-stdlib", + "additional_grant_file": "PATENTS.go-stdlib", + "verdict_authority": "go-stdlib" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "go_parseerror_0000_01_01t00_00_00_00_0", + "description": "Go stdlib parseErrorTests row with layout time.RFC3339. TestParseErrors fails the test if Parse returns no error, so the table asserts Go rejects this input under the RFC3339 layout. Go's expected error substring: 'cannot parse \"+00:+0\" as \"Z07:00\"'", + "tags": [ + "reject", + "go-stdlib", + "parse-error-table" + ], + "applies_to": "date-time", + "input": "0000-01-01T00:00:00+00:+0", + "source": "go-stdlib-format_test.go", + "provenance": { + "type": "extracted_from_source", + "source_repo": "github.com/golang/go", + "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", + "commit_sha_tag": "go1.26.5", + "file": "src/time/format_test.go", + "line": 673, + "table": "parseErrorTests", + "license": "BSD-3-Clause", + "license_file": "LICENSE.go-stdlib", + "additional_grant_file": "PATENTS.go-stdlib", + "verdict_authority": "go-stdlib" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "go_parseerror_0000_01_01t00_00_00_0_00", + "description": "Go stdlib parseErrorTests row with layout time.RFC3339. TestParseErrors fails the test if Parse returns no error, so the table asserts Go rejects this input under the RFC3339 layout. Go's expected error substring: 'cannot parse \"+-0:00\" as \"Z07:00\"'", + "tags": [ + "reject", + "go-stdlib", + "parse-error-table" + ], + "applies_to": "date-time", + "input": "0000-01-01T00:00:00+-0:00", + "source": "go-stdlib-format_test.go", + "provenance": { + "type": "extracted_from_source", + "source_repo": "github.com/golang/go", + "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", + "commit_sha_tag": "go1.26.5", + "file": "src/time/format_test.go", + "line": 674, + "table": "parseErrorTests", + "license": "BSD-3-Clause", + "license_file": "LICENSE.go-stdlib", + "additional_grant_file": "PATENTS.go-stdlib", + "verdict_authority": "go-stdlib" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "go_longfrac_9digits_000000000", + "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 9 fractional digits; Go's expected Nanosecond() is 0.", + "tags": [ + "accept", + "go-stdlib", + "secfrac" + ], + "applies_to": "date-time", + "input": "2021-09-29T16:04:33.000000000Z", + "source": "go-stdlib-format_test.go", + "provenance": { + "type": "extracted_from_source", + "source_repo": "github.com/golang/go", + "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", + "commit_sha_tag": "go1.26.5", + "file": "src/time/format_test.go", + "line": 957, + "table": "longFractionalDigitsTests", + "license": "BSD-3-Clause", + "license_file": "LICENSE.go-stdlib", + "additional_grant_file": "PATENTS.go-stdlib", + "verdict_authority": "go-stdlib" + }, + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_9digits_000000001", + "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 9 fractional digits; Go's expected Nanosecond() is 1.", + "tags": [ + "accept", + "go-stdlib", + "secfrac" + ], + "applies_to": "date-time", + "input": "2021-09-29T16:04:33.000000001Z", + "source": "go-stdlib-format_test.go", + "provenance": { + "type": "extracted_from_source", + "source_repo": "github.com/golang/go", + "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", + "commit_sha_tag": "go1.26.5", + "file": "src/time/format_test.go", + "line": 958, + "table": "longFractionalDigitsTests", + "license": "BSD-3-Clause", + "license_file": "LICENSE.go-stdlib", + "additional_grant_file": "PATENTS.go-stdlib", + "verdict_authority": "go-stdlib" + }, + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_9digits_100000000", + "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 9 fractional digits; Go's expected Nanosecond() is 100000000.", + "tags": [ + "accept", + "go-stdlib", + "secfrac" + ], + "applies_to": "date-time", + "input": "2021-09-29T16:04:33.100000000Z", + "source": "go-stdlib-format_test.go", + "provenance": { + "type": "extracted_from_source", + "source_repo": "github.com/golang/go", + "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", + "commit_sha_tag": "go1.26.5", + "file": "src/time/format_test.go", + "line": 959, + "table": "longFractionalDigitsTests", + "license": "BSD-3-Clause", + "license_file": "LICENSE.go-stdlib", + "additional_grant_file": "PATENTS.go-stdlib", + "verdict_authority": "go-stdlib" + }, + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_9digits_100000001", + "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 9 fractional digits; Go's expected Nanosecond() is 100000001.", + "tags": [ + "accept", + "go-stdlib", + "secfrac" + ], + "applies_to": "date-time", + "input": "2021-09-29T16:04:33.100000001Z", + "source": "go-stdlib-format_test.go", + "provenance": { + "type": "extracted_from_source", + "source_repo": "github.com/golang/go", + "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", + "commit_sha_tag": "go1.26.5", + "file": "src/time/format_test.go", + "line": 960, + "table": "longFractionalDigitsTests", + "license": "BSD-3-Clause", + "license_file": "LICENSE.go-stdlib", + "additional_grant_file": "PATENTS.go-stdlib", + "verdict_authority": "go-stdlib" + }, + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_9digits_999999999", + "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 9 fractional digits; Go's expected Nanosecond() is 999999999.", + "tags": [ + "accept", + "go-stdlib", + "secfrac" + ], + "applies_to": "date-time", + "input": "2021-09-29T16:04:33.999999999Z", + "source": "go-stdlib-format_test.go", + "provenance": { + "type": "extracted_from_source", + "source_repo": "github.com/golang/go", + "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", + "commit_sha_tag": "go1.26.5", + "file": "src/time/format_test.go", + "line": 961, + "table": "longFractionalDigitsTests", + "license": "BSD-3-Clause", + "license_file": "LICENSE.go-stdlib", + "additional_grant_file": "PATENTS.go-stdlib", + "verdict_authority": "go-stdlib" + }, + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_9digits_012345678", + "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 9 fractional digits; Go's expected Nanosecond() is 12345678.", + "tags": [ + "accept", + "go-stdlib", + "secfrac" + ], + "applies_to": "date-time", + "input": "2021-09-29T16:04:33.012345678Z", + "source": "go-stdlib-format_test.go", + "provenance": { + "type": "extracted_from_source", + "source_repo": "github.com/golang/go", + "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", + "commit_sha_tag": "go1.26.5", + "file": "src/time/format_test.go", + "line": 962, + "table": "longFractionalDigitsTests", + "license": "BSD-3-Clause", + "license_file": "LICENSE.go-stdlib", + "additional_grant_file": "PATENTS.go-stdlib", + "verdict_authority": "go-stdlib" + }, + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_10digits_0000000000", + "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 10 fractional digits; Go's expected Nanosecond() is 0, i.e. Go ACCEPTS and TRUNCATES past 9 digits.", + "tags": [ + "accept", + "go-stdlib", + "secfrac", + "go-truncates" + ], + "applies_to": "date-time", + "input": "2021-09-29T16:04:33.0000000000Z", + "source": "go-stdlib-format_test.go", + "provenance": { + "type": "extracted_from_source", + "source_repo": "github.com/golang/go", + "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", + "commit_sha_tag": "go1.26.5", + "file": "src/time/format_test.go", + "line": 964, + "table": "longFractionalDigitsTests", + "license": "BSD-3-Clause", + "license_file": "LICENSE.go-stdlib", + "additional_grant_file": "PATENTS.go-stdlib", + "verdict_authority": "go-stdlib" + }, + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_10digits_0000000001", + "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 10 fractional digits; Go's expected Nanosecond() is 0, i.e. Go ACCEPTS and TRUNCATES past 9 digits.", + "tags": [ + "accept", + "go-stdlib", + "secfrac", + "go-truncates" + ], + "applies_to": "date-time", + "input": "2021-09-29T16:04:33.0000000001Z", + "source": "go-stdlib-format_test.go", + "provenance": { + "type": "extracted_from_source", + "source_repo": "github.com/golang/go", + "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", + "commit_sha_tag": "go1.26.5", + "file": "src/time/format_test.go", + "line": 965, + "table": "longFractionalDigitsTests", + "license": "BSD-3-Clause", + "license_file": "LICENSE.go-stdlib", + "additional_grant_file": "PATENTS.go-stdlib", + "verdict_authority": "go-stdlib" + }, + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_10digits_1000000000", + "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 10 fractional digits; Go's expected Nanosecond() is 100000000, i.e. Go ACCEPTS and TRUNCATES past 9 digits.", + "tags": [ + "accept", + "go-stdlib", + "secfrac", + "go-truncates" + ], + "applies_to": "date-time", + "input": "2021-09-29T16:04:33.1000000000Z", + "source": "go-stdlib-format_test.go", + "provenance": { + "type": "extracted_from_source", + "source_repo": "github.com/golang/go", + "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", + "commit_sha_tag": "go1.26.5", + "file": "src/time/format_test.go", + "line": 966, + "table": "longFractionalDigitsTests", + "license": "BSD-3-Clause", + "license_file": "LICENSE.go-stdlib", + "additional_grant_file": "PATENTS.go-stdlib", + "verdict_authority": "go-stdlib" + }, + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_10digits_1000000009", + "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 10 fractional digits; Go's expected Nanosecond() is 100000000, i.e. Go ACCEPTS and TRUNCATES past 9 digits.", + "tags": [ + "accept", + "go-stdlib", + "secfrac", + "go-truncates" + ], + "applies_to": "date-time", + "input": "2021-09-29T16:04:33.1000000009Z", + "source": "go-stdlib-format_test.go", + "provenance": { + "type": "extracted_from_source", + "source_repo": "github.com/golang/go", + "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", + "commit_sha_tag": "go1.26.5", + "file": "src/time/format_test.go", + "line": 967, + "table": "longFractionalDigitsTests", + "license": "BSD-3-Clause", + "license_file": "LICENSE.go-stdlib", + "additional_grant_file": "PATENTS.go-stdlib", + "verdict_authority": "go-stdlib" + }, + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_10digits_9999999999", + "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 10 fractional digits; Go's expected Nanosecond() is 999999999, i.e. Go ACCEPTS and TRUNCATES past 9 digits.", + "tags": [ + "accept", + "go-stdlib", + "secfrac", + "go-truncates" + ], + "applies_to": "date-time", + "input": "2021-09-29T16:04:33.9999999999Z", + "source": "go-stdlib-format_test.go", + "provenance": { + "type": "extracted_from_source", + "source_repo": "github.com/golang/go", + "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", + "commit_sha_tag": "go1.26.5", + "file": "src/time/format_test.go", + "line": 968, + "table": "longFractionalDigitsTests", + "license": "BSD-3-Clause", + "license_file": "LICENSE.go-stdlib", + "additional_grant_file": "PATENTS.go-stdlib", + "verdict_authority": "go-stdlib" + }, + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_10digits_0123456789", + "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 10 fractional digits; Go's expected Nanosecond() is 12345678, i.e. Go ACCEPTS and TRUNCATES past 9 digits.", + "tags": [ + "accept", + "go-stdlib", + "secfrac", + "go-truncates" + ], + "applies_to": "date-time", + "input": "2021-09-29T16:04:33.0123456789Z", + "source": "go-stdlib-format_test.go", + "provenance": { + "type": "extracted_from_source", + "source_repo": "github.com/golang/go", + "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", + "commit_sha_tag": "go1.26.5", + "file": "src/time/format_test.go", + "line": 969, + "table": "longFractionalDigitsTests", + "license": "BSD-3-Clause", + "license_file": "LICENSE.go-stdlib", + "additional_grant_file": "PATENTS.go-stdlib", + "verdict_authority": "go-stdlib" + }, + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_11digits_10000000000", + "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 11 fractional digits; Go's expected Nanosecond() is 100000000, i.e. Go ACCEPTS and TRUNCATES past 9 digits.", + "tags": [ + "accept", + "go-stdlib", + "secfrac", + "go-truncates" + ], + "applies_to": "date-time", + "input": "2021-09-29T16:04:33.10000000000Z", + "source": "go-stdlib-format_test.go", + "provenance": { + "type": "extracted_from_source", + "source_repo": "github.com/golang/go", + "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", + "commit_sha_tag": "go1.26.5", + "file": "src/time/format_test.go", + "line": 971, + "table": "longFractionalDigitsTests", + "license": "BSD-3-Clause", + "license_file": "LICENSE.go-stdlib", + "additional_grant_file": "PATENTS.go-stdlib", + "verdict_authority": "go-stdlib" + }, + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_11digits_00123456789", + "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 11 fractional digits; Go's expected Nanosecond() is 1234567, i.e. Go ACCEPTS and TRUNCATES past 9 digits.", + "tags": [ + "accept", + "go-stdlib", + "secfrac", + "go-truncates" + ], + "applies_to": "date-time", + "input": "2021-09-29T16:04:33.00123456789Z", + "source": "go-stdlib-format_test.go", + "provenance": { + "type": "extracted_from_source", + "source_repo": "github.com/golang/go", + "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", + "commit_sha_tag": "go1.26.5", + "file": "src/time/format_test.go", + "line": 972, + "table": "longFractionalDigitsTests", + "license": "BSD-3-Clause", + "license_file": "LICENSE.go-stdlib", + "additional_grant_file": "PATENTS.go-stdlib", + "verdict_authority": "go-stdlib" + }, + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_12digits_000123456789", + "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 12 fractional digits; Go's expected Nanosecond() is 123456, i.e. Go ACCEPTS and TRUNCATES past 9 digits.", + "tags": [ + "accept", + "go-stdlib", + "secfrac", + "go-truncates" + ], + "applies_to": "date-time", + "input": "2021-09-29T16:04:33.000123456789Z", + "source": "go-stdlib-format_test.go", + "provenance": { + "type": "extracted_from_source", + "source_repo": "github.com/golang/go", + "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", + "commit_sha_tag": "go1.26.5", + "file": "src/time/format_test.go", + "line": 974, + "table": "longFractionalDigitsTests", + "license": "BSD-3-Clause", + "license_file": "LICENSE.go-stdlib", + "additional_grant_file": "PATENTS.go-stdlib", + "verdict_authority": "go-stdlib" + }, + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_16digits_9999999999999999", + "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 16 fractional digits; Go's expected Nanosecond() is 999999999, i.e. Go ACCEPTS and TRUNCATES past 9 digits.", + "tags": [ + "accept", + "go-stdlib", + "secfrac", + "go-truncates" + ], + "applies_to": "date-time", + "input": "2021-09-29T16:04:33.9999999999999999Z", + "source": "go-stdlib-format_test.go", + "provenance": { + "type": "extracted_from_source", + "source_repo": "github.com/golang/go", + "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", + "commit_sha_tag": "go1.26.5", + "file": "src/time/format_test.go", + "line": 976, + "table": "longFractionalDigitsTests", + "license": "BSD-3-Clause", + "license_file": "LICENSE.go-stdlib", + "additional_grant_file": "PATENTS.go-stdlib", + "verdict_authority": "go-stdlib" + }, + "tests": { + "parse": true + } + }, + { + "name": "offset_max_syntactic_plus_23_59", + "description": "Maximum SYNTACTIC offset. §5.6 gives time-numoffset = (\"+\" / \"-\") time-hour \":\" time-minute, with time-hour = 2DIGIT ; 00-23 and time-minute = 2DIGIT ; 00-59, so +23:59 is the largest offset the grammar generates. This is well outside the range of any real timezone. The REJECT counterpart one minute above this boundary already ships as `offset_hour_out_of_range` (2026-01-29T12:00:00+24:00) in expires-rfc3339.json. NOTE for consumers: the offsets named as extremes in the upstream issue, +14:00 and -12:00, are the TIMEZONE extremes and already ship as `offset_plus_fourteen` and `offset_minus_twelve`; they are not the grammar's extremes. An implementation that range-checks offsets against tz data rather than against §5.6 will reject this conforming input. The base date is mid-range so that the offset-adjusted instant cannot overflow date-fullyear, which is a separate and deliberately undecided question held in the `disputed` block of expires-rfc3339.json.", + "tags": [ + "accept", + "offset-boundary", + "syntactic-extreme", + "hand-authored" + ], + "applies_to": "date-time", + "input": "2026-01-29T12:00:00+23:59", + "source": "rfc-3339-text", + "provenance": { + "type": "hand_authored", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_sha256": "9ab2b8864a85dca73a88f49b0927bc7bc85f596926e4fd1890905777924e700a", + "rfc_ref": "RFC 3339 §5.6 (time-numoffset = (\"+\" / \"-\") time-hour \":\" time-minute; time-hour = 2DIGIT ; 00-23; time-minute = 2DIGIT ; 00-59)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S5 (hand-authored-residual.json)" + }, + "tests": { + "parse": true + } + }, + { + "name": "offset_min_syntactic_minus_23_59", + "description": "Minimum SYNTACTIC offset, the negative arm of the same §5.6 boundary. time-numoffset admits (\"+\" / \"-\") equally, so -23:59 is as conforming as +23:59. The REJECT counterpart on this arm already ships in import-json-schema-test-suite.json as `jsts_date_time_011` (1990-12-31T15:59:59-24:00, upstream valid:false), where the offset hour 24 exceeds time-hour's 00-23 range. Together with offset_max_syntactic_plus_23_59 this closes the last-legal / first-illegal pair on both signs.", + "tags": [ + "accept", + "offset-boundary", + "syntactic-extreme", + "hand-authored" + ], + "applies_to": "date-time", + "input": "2026-01-29T12:00:00-23:59", + "source": "rfc-3339-text", + "provenance": { + "type": "hand_authored", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_sha256": "9ab2b8864a85dca73a88f49b0927bc7bc85f596926e4fd1890905777924e700a", + "rfc_ref": "RFC 3339 §5.6 (time-numoffset = (\"+\" / \"-\") time-hour \":\" time-minute; time-hour = 2DIGIT ; 00-23; time-minute = 2DIGIT ; 00-59)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S5 (hand-authored-residual.json)" + }, + "tests": { + "parse": true + } + }, + { + "name": "leap_second_offset_rolls_local_date_forward", + "description": "A leap second in date-time form where the zone offset pushes the LOCAL date across midnight. §5.7: \"Further, in time zones other than 'Z', the leap second point is shifted by the zone offset (so it happens at the same instant around the globe).\" 1999-01-01T00:59:60+01:00 resolves to 1998-12-31T23:59:60Z, which is the final row of the Appendix D leap second table and which already ships as an ACCEPT in three files. The only variable this vector changes is that the local date reads 1999-01-01 while the UTC date is 1998-12-31, so the leap second no longer sits at a visible month end. §5.7's month-end condition is a condition on the UTC instant, which the shifting sentence makes explicit. This is the case an implementation gets wrong when it validates the :60 against the LOCAL date rather than the offset-adjusted one. No vector in expires-rfc3339.json, import-json-schema-test-suite.json or import-go-stdlib.json rolls the date: every other date-time :60 vector keeps the local and UTC dates equal, because §5.8's worked example 1990-12-31T15:59:60-08:00 uses a negative offset that cannot roll forward, and a bare full-time has no date to roll.", + "tags": [ + "accept", + "leap-second", + "offset-arithmetic", + "date-rollover", + "hand-authored" + ], + "applies_to": "date-time", + "input": "1999-01-01T00:59:60+01:00", + "source": "rfc-3339-text", + "provenance": { + "type": "hand_authored", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_sha256": "9ab2b8864a85dca73a88f49b0927bc7bc85f596926e4fd1890905777924e700a", + "rfc_ref": "RFC 3339 §5.7 (leap second point is shifted by the zone offset) + Appendix D (1998-12-31 leap second table row)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S5 (hand-authored-residual.json)" + }, + "tests": { + "parse": true + } + }, + { + "name": "leap_second_offset_rolls_local_date_forward_wrong_offset", + "description": "The REJECT counterpart of leap_second_offset_rolls_local_date_forward, and the reason that vector proves anything. Character for character this differs only in the offset hour: 1999-01-01T00:59:60+02:00 resolves to 1998-12-31T22:59:60Z, which is not the leap second point. §5.7: \"At all other times the maximum value of time-second is '59'.\" An implementation that accepts the ACCEPT vector by ignoring the offset entirely, rather than by applying it, will also accept this one -- so the pair distinguishes offset-aware handling from a blanket :60 permission. Both sides of the boundary are authored here.", + "tags": [ + "reject", + "leap-second", + "offset-arithmetic", + "date-rollover", + "hand-authored" + ], + "applies_to": "date-time", + "input": "1999-01-01T00:59:60+02:00", + "source": "rfc-3339-text", + "provenance": { + "type": "hand_authored", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_sha256": "9ab2b8864a85dca73a88f49b0927bc7bc85f596926e4fd1890905777924e700a", + "rfc_ref": "RFC 3339 §5.7 (\"At all other times the maximum value of time-second is '59'\"; leap second point is shifted by the zone offset)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S5 (hand-authored-residual.json)" + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "rfc_5_8_example_fractional_z", + "description": "RFC 3339 §5.8 worked example: 20 minutes and 50.52 seconds after the 23rd hour of April 12th, 1985 in UTC.", + "tags": [ + "accept", + "rfc-example", + "secfrac" + ], + "applies_to": "date-time", + "input": "1985-04-12T23:20:50.52Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.8", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": true + } + }, + { + "name": "rfc_5_8_example_negative_offset", + "description": "RFC 3339 §5.8 worked example: -08:00 offset from UTC (Pacific Standard Time).", + "tags": [ + "accept", + "rfc-example", + "offset" + ], + "applies_to": "date-time", + "input": "1996-12-19T16:39:57-08:00", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.8", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": true + } + }, + { + "name": "rfc_5_8_example_leap_second_z", + "description": "RFC 3339 §5.8 worked example: the leap second inserted at the end of 1990.", + "tags": [ + "accept", + "rfc-example", + "leap-second" + ], + "applies_to": "date-time", + "input": "1990-12-31T23:59:60Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.8; §5.7 (time-second may be 60 at the end of December)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": true + } + }, + { + "name": "rfc_5_8_example_leap_second_offset", + "description": "RFC 3339 §5.8 worked example: the same leap second in Pacific Standard Time, 8 hours behind UTC. Per §5.7 the leap second point is shifted by the zone offset.", + "tags": [ + "accept", + "rfc-example", + "leap-second", + "offset" + ], + "applies_to": "date-time", + "input": "1990-12-31T15:59:60-08:00", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.8; §5.7", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": true + } + }, + { + "name": "leap_second_june_month_end", + "description": "Leap second at a June month end. RFC 3339 §5.7 names XXXX-06-30T23:59:60Z explicitly; 1972-06-30 is the first row of the Appendix D leap-second table.", + "tags": [ + "accept", + "leap-second" + ], + "applies_to": "date-time", + "input": "1972-06-30T23:59:60Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.7; Appendix D", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": true + } + }, + { + "name": "lowercase_t_separator", + "description": "Lowercase 't' as the date/time separator. §5.6 NOTE: \"the 'T' and 'Z' characters in this syntax may alternatively be lower case 't' or 'z' respectively\".", + "tags": [ + "accept", + "case", + "separator" + ], + "applies_to": "date-time", + "input": "1985-04-12t23:20:50.52Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (NOTE on lower case)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": true + } + }, + { + "name": "lowercase_z_offset", + "description": "Lowercase 'z' as the UTC designator. Same §5.6 NOTE.", + "tags": [ + "accept", + "case", + "offset" + ], + "applies_to": "date-time", + "input": "1985-04-12T23:20:50.52z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (NOTE on lower case)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": true + } + }, + { + "name": "lowercase_t_and_z", + "description": "Both separators lower case. Same §5.6 NOTE. §5.6 adds that applications which GENERATE this format SHOULD use upper case; that is a generation constraint, not a parse constraint.", + "tags": [ + "accept", + "case", + "separator", + "offset" + ], + "applies_to": "date-time", + "input": "1985-04-12t23:20:50.52z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (NOTE on lower case)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": true + } + }, + { + "name": "secfrac_1_digit", + "description": "One fractional digit. §5.6 time-secfrac = \".\" 1*DIGIT.", + "tags": [ + "accept", + "secfrac" + ], + "applies_to": "date-time", + "input": "2021-09-29T16:04:33.0Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (time-secfrac)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": true + } + }, + { + "name": "secfrac_7_digits", + "description": "Seven fractional digits. §5.6 time-secfrac = \".\" 1*DIGIT places no upper bound on digit count.", + "tags": [ + "accept", + "secfrac" + ], + "applies_to": "date-time", + "input": "2021-09-29T16:04:33.1234567Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (time-secfrac)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": true + } + }, + { + "name": "secfrac_8_digits", + "description": "Eight fractional digits. §5.6 time-secfrac = \".\" 1*DIGIT places no upper bound on digit count.", + "tags": [ + "accept", + "secfrac" + ], + "applies_to": "date-time", + "input": "2021-09-29T16:04:33.12345678Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (time-secfrac)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": true + } + }, + { + "name": "secfrac_9_digits", + "description": "Nine fractional digits. §5.6 time-secfrac = \".\" 1*DIGIT places no upper bound on digit count.", + "tags": [ + "accept", + "secfrac" + ], + "applies_to": "date-time", + "input": "2021-09-29T16:04:33.123456789Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (time-secfrac)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": true + } + }, + { + "name": "secfrac_point_with_no_digits", + "description": "A decimal point with no digits after it. §5.6 time-secfrac = \".\" 1*DIGIT requires at least one DIGIT.", + "tags": [ + "reject", + "secfrac" + ], + "applies_to": "date-time", + "input": "2021-09-29T16:04:33.Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (time-secfrac)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "secfrac_comma_separator", + "description": "Comma as the fraction separator. §5.6 time-secfrac fixes the separator as \".\". ISO 8601 permits a comma (see RFC 3339 Appendix A time-fraction = (\",\" / \".\") 1*DIGIT) but Appendix A is explicitly informational and is NOT the §5.6 profile.", + "tags": [ + "reject", + "secfrac" + ], + "applies_to": "date-time", + "input": "2021-09-29T16:04:33,5Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (time-secfrac)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "offset_plus_zero", + "description": "Offset +00:00. §5.6 time-numoffset; §4.3 notes +00:00 and Z both imply UTC is the preferred reference point.", + "tags": [ + "accept", + "offset" + ], + "applies_to": "date-time", + "input": "2026-01-29T12:00:00+00:00", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (time-numoffset); §4.3", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": true + } + }, + { + "name": "offset_minus_zero", + "description": "Offset -00:00, the unknown-local-offset convention. §4.3: \"If the time in UTC is known, but the offset to local time is unknown, this can be represented with an offset of -00:00.\" It is grammatically valid under §5.6 and semantically distinct from Z and +00:00.", + "tags": [ + "accept", + "offset", + "unknown-local-offset" + ], + "applies_to": "date-time", + "input": "2026-01-29T12:00:00-00:00", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §4.3; §5.6 (time-numoffset)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": true + } + }, + { + "name": "offset_plus_fourteen", + "description": "Offset +14:00. §5.6 time-numoffset = (\"+\" / \"-\") time-hour \":\" time-minute, time-hour = 2DIGIT ; 00-23. 14 is in range.", + "tags": [ + "accept", + "offset" + ], + "applies_to": "date-time", + "input": "2026-01-29T12:00:00+14:00", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (time-numoffset, time-hour 00-23)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": true + } + }, + { + "name": "offset_minus_twelve", + "description": "Offset -12:00. Same §5.6 production; 12 is in range.", + "tags": [ + "accept", + "offset" + ], + "applies_to": "date-time", + "input": "2026-01-29T12:00:00-12:00", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (time-numoffset, time-hour 00-23)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": true + } + }, + { + "name": "offset_hour_out_of_range", + "description": "Offset +24:00. §5.6 time-hour = 2DIGIT ; 00-23. 24 is out of range.", + "tags": [ + "reject", + "offset" + ], + "applies_to": "date-time", + "input": "2026-01-29T12:00:00+24:00", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (time-hour 00-23)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "offset_minute_out_of_range", + "description": "Offset +00:60. §5.6 time-minute = 2DIGIT ; 00-59. 60 is out of range.", + "tags": [ + "reject", + "offset" + ], + "applies_to": "date-time", + "input": "2026-01-29T12:00:00+00:60", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (time-minute 00-59)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "offset_missing_colon", + "description": "Offset written without the colon. §5.6 time-numoffset requires a literal \":\" between time-hour and time-minute.", + "tags": [ + "reject", + "offset" + ], + "applies_to": "date-time", + "input": "2026-01-29T12:00:00+0000", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (time-numoffset)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "offset_hour_only", + "description": "Offset written as hour only. §5.6 time-numoffset requires both time-hour and time-minute.", + "tags": [ + "reject", + "offset" + ], + "applies_to": "date-time", + "input": "2026-01-29T12:00:00-08", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (time-numoffset)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "offset_absent", + "description": "No offset at all. §5.6 full-time = partial-time time-offset; time-offset is not optional.", + "tags": [ + "reject", + "offset", + "unqualified-local-time" + ], + "applies_to": "date-time", + "input": "2026-01-29T12:00:00", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (full-time); §4.4", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "offset_alphabetic_zone", + "description": "Alphabetic zone abbreviation. §5.6 time-offset = \"Z\" / time-numoffset admits no alphabetic name other than Z; §4.2 records that alphabetic offset labels produced poor interoperability and that numeric offsets are mandatory.", + "tags": [ + "reject", + "offset" + ], + "applies_to": "date-time", + "input": "2026-01-29T12:00:00PST", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (time-offset); §4.2", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "year_9999", + "description": "Year 9999. §5.6 date-fullyear = 4DIGIT; 9999 is exactly four digits and §5.7 places no restriction on the year value itself.", + "tags": [ + "accept", + "year-boundary" + ], + "applies_to": "date-time", + "input": "9999-12-31T23:59:59Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (date-fullyear = 4DIGIT)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": true + } + }, + { + "name": "year_10000", + "description": "Year 10000. §5.6 date-fullyear = 4DIGIT is exactly four digits; a five-digit year is not in the grammar.", + "tags": [ + "reject", + "year-boundary" + ], + "applies_to": "date-time", + "input": "10000-01-01T00:00:00Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (date-fullyear = 4DIGIT)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "year_0000", + "description": "Year 0000. §5.6 date-fullyear = 4DIGIT; 0000 is exactly four digits. §5.7 restricts date-mday and time-second only, not the year value.", + "tags": [ + "accept", + "year-boundary" + ], + "applies_to": "date-time", + "input": "0000-01-01T00:00:00Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (date-fullyear = 4DIGIT)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": true + } + }, + { + "name": "year_two_digit", + "description": "Two-digit year. §5.6 date-fullyear = 4DIGIT. §3 (\"Two Digit Years\") independently rules two-digit years out for new protocols.", + "tags": [ + "reject", + "year-boundary" + ], + "applies_to": "date-time", + "input": "26-01-29T12:00:00Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (date-fullyear = 4DIGIT); §3", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "year_sign_prefixed", + "description": "ISO 8601 expanded-year sign prefix. §5.6 date-fullyear = 4DIGIT, and DIGIT admits only %x30-39; \"+\" is not a DIGIT.", + "tags": [ + "reject", + "year-boundary" + ], + "applies_to": "date-time", + "input": "+2026-01-29T12:00:00Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (date-fullyear = 4DIGIT)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "feb_30", + "description": "February 30th. §5.7 maximum-value table: February normal 28, February leap year 29. 30 exceeds both.", + "tags": [ + "reject", + "calendar" + ], + "applies_to": "date-time", + "input": "2026-02-30T00:00:00Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.7 (date-mday maximum table)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "sep_31", + "description": "September 31st. §5.7 maximum-value table: September 30.", + "tags": [ + "reject", + "calendar" + ], + "applies_to": "date-time", + "input": "2026-09-31T00:00:00Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.7 (date-mday maximum table)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "leap_year_2000_feb_29", + "description": "2000-02-29. §5.7 permits date-mday 29 in a leap February; Appendix C's leap_year() is (year%4==0 && (year%100!=0 || year%400==0)) and 2000%400==0, so 2000 is a leap year. This is the century case a naive %4-only or %100-only implementation gets wrong.", + "tags": [ + "accept", + "calendar", + "leap-year" + ], + "applies_to": "date-time", + "input": "2000-02-29T00:00:00Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.7; Appendix C", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": true + } + }, + { + "name": "non_leap_year_1900_feb_29", + "description": "1900-02-29. Appendix C: 1900%4==0 but 1900%100==0 and 1900%400!=0, so 1900 is NOT a leap year and §5.7 caps February at 28. This is the century case a naive %4-only implementation gets wrong.", + "tags": [ + "reject", + "calendar", + "leap-year" + ], + "applies_to": "date-time", + "input": "1900-02-29T00:00:00Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.7; Appendix C", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "leap_year_2024_feb_29", + "description": "2024-02-29. Appendix C: 2024%4==0 and 2024%100!=0, so 2024 is a leap year; §5.7 permits date-mday 29.", + "tags": [ + "accept", + "calendar", + "leap-year" + ], + "applies_to": "date-time", + "input": "2024-02-29T00:00:00Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.7; Appendix C", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": true + } + }, + { + "name": "non_leap_year_2026_feb_29", + "description": "2026-02-29. Appendix C: 2026%4!=0, so 2026 is not a leap year and §5.7 caps February at 28.", + "tags": [ + "reject", + "calendar", + "leap-year" + ], + "applies_to": "date-time", + "input": "2026-02-29T00:00:00Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.7; Appendix C", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "apr_31", + "description": "April 31st. §5.7 maximum-value table: April 30.", + "tags": [ + "reject", + "calendar" + ], + "applies_to": "date-time", + "input": "2026-04-31T00:00:00Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.7 (date-mday maximum table)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jan_31", + "description": "January 31st. §5.7 maximum-value table: January 31.", + "tags": [ + "accept", + "calendar" + ], + "applies_to": "date-time", + "input": "2026-01-31T00:00:00Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.7 (date-mday maximum table)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": true + } + }, + { + "name": "month_00", + "description": "Month 00. §5.6 date-month = 2DIGIT ; 01-12.", + "tags": [ + "reject", + "calendar" + ], + "applies_to": "date-time", + "input": "2026-00-15T00:00:00Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (date-month 01-12)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "month_13", + "description": "Month 13. §5.6 date-month = 2DIGIT ; 01-12.", + "tags": [ + "reject", + "calendar" + ], + "applies_to": "date-time", + "input": "2026-13-15T00:00:00Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (date-month 01-12)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "mday_00", + "description": "Day 00. §5.6 date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on month/year. The minimum is 01.", + "tags": [ + "reject", + "calendar" + ], + "applies_to": "date-time", + "input": "2026-01-00T00:00:00Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (date-mday); §5.7", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "mday_32", + "description": "Day 32. §5.7 maximum-value table caps January at 31.", + "tags": [ + "reject", + "calendar" + ], + "applies_to": "date-time", + "input": "2026-01-32T00:00:00Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.7 (date-mday maximum table)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "time_00_00_00", + "description": "Start of day. §5.6 time-hour 00-23, time-minute 00-59, time-second 00-59; all three at their minimum.", + "tags": [ + "accept", + "time-of-day", + "boundary" + ], + "applies_to": "date-time", + "input": "2026-01-29T00:00:00Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (partial-time)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": true + } + }, + { + "name": "time_23_59_59", + "description": "Last non-leap second of the day. §5.6 time-hour 00-23, time-minute 00-59, time-second 00-59; all three at their non-leap maximum.", + "tags": [ + "accept", + "time-of-day", + "boundary" + ], + "applies_to": "date-time", + "input": "2026-01-29T23:59:59Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (partial-time); §5.7", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": true + } + }, + { + "name": "hour_24", + "description": "Hour 24. §5.7: \"Although ISO 8601 permits the hour to be 24, this profile of ISO 8601 only allows values between 00 and 23 for the hour in order to reduce confusion.\"", + "tags": [ + "reject", + "time-of-day", + "boundary" + ], + "applies_to": "date-time", + "input": "2026-01-29T24:00:00Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.7 (hour 00-23); §5.6 (time-hour)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "minute_60", + "description": "Minute 60. §5.6 time-minute = 2DIGIT ; 00-59.", + "tags": [ + "reject", + "time-of-day" + ], + "applies_to": "date-time", + "input": "2026-01-29T12:60:00Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (time-minute 00-59)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "second_61", + "description": "Second 61. §5.6 time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second rules; 61 exceeds every branch.", + "tags": [ + "reject", + "time-of-day" + ], + "applies_to": "date-time", + "input": "2026-01-29T12:00:61Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (time-second); §5.7", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "seconds_omitted", + "description": "Seconds omitted. §5.6 partial-time = time-hour \":\" time-minute \":\" time-second [time-secfrac]; only time-secfrac is optional.", + "tags": [ + "reject", + "structure" + ], + "applies_to": "date-time", + "input": "2026-01-29T12:00Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (partial-time)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "basic_format_no_separators", + "description": "ISO 8601 basic format with no separators. §5.6 full-date and partial-time both carry literal \"-\" and \":\" separators.", + "tags": [ + "reject", + "structure" + ], + "applies_to": "date-time", + "input": "20260129T120000Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (full-date, partial-time)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "date_only", + "description": "Date with no time part. §5.6 date-time = full-date \"T\" full-time; the time part is not optional.", + "tags": [ + "reject", + "structure" + ], + "applies_to": "date-time", + "input": "2026-01-29", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (date-time)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "non_date_string", + "description": "Arbitrary non-date text. §5.6 date-time requires a full-date, a separator and a full-time.", + "tags": [ + "reject", + "structure" + ], + "applies_to": "date-time", + "input": "not-a-timestamp", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (date-time)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "trailing_garbage_after_offset", + "description": "Trailing text after a complete date-time. §5.6 date-time = full-date \"T\" full-time and full-time = partial-time time-offset; the production ends at time-offset and admits nothing after it. This is the defect an unanchored regex or a prefix-consuming scanner has: the leading 20 characters are a valid date-time, so a parser that stops at the first complete match accepts the whole string.", + "tags": [ + "reject", + "structure", + "trailing-text" + ], + "applies_to": "date-time", + "input": "2026-01-29T12:00:00Z_abc", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (date-time; full-time)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "trailing_newline_after_offset", + "description": "A complete date-time followed by a single LF (U+000A). Same §5.6 defect as trailing_garbage_after_offset, in the shape a JSON payment field actually delivers: a trailing newline survives naive line reads and log round-trips. §5.6 date-time ends at time-offset, and LF is not part of any production.", + "tags": [ + "reject", + "structure", + "trailing-text", + "whitespace" + ], + "applies_to": "date-time", + "input": "2026-01-29T12:00:00Z\n", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (date-time; full-time)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "trailing_duplicate_z", + "description": "A second UTC designator after a complete date-time. §5.6 time-offset = \"Z\" / time-numoffset matches exactly one designator, and date-time ends there.", + "tags": [ + "reject", + "structure", + "trailing-text", + "offset" + ], + "applies_to": "date-time", + "input": "2026-01-29T12:00:00ZZ", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (date-time; time-offset)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "second_elided_secfrac_retained", + "description": "The seconds field elided but its fraction retained. §5.6 partial-time = time-hour \":\" time-minute \":\" time-second [time-secfrac]; time-second = 2DIGIT is mandatory and only time-secfrac is optional, so a fraction cannot stand in for the seconds it qualifies.", + "tags": [ + "reject", + "structure", + "secfrac" + ], + "applies_to": "date-time", + "input": "2026-01-29T12:00:.0Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (partial-time; time-second = 2DIGIT)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "offset_signed_minute", + "description": "A sign injected inside the offset minute field. §5.6 time-numoffset = (\"+\" / \"-\") time-hour \":\" time-minute carries its sign once, before time-hour; time-minute = 2DIGIT admits only %x30-39, and \"+0\" is a sign followed by one DIGIT.", + "tags": [ + "reject", + "offset" + ], + "applies_to": "date-time", + "input": "2026-01-29T12:00:00+00:+0", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (time-numoffset; time-minute = 2DIGIT)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "offset_double_sign", + "description": "Two signs at the head of the offset. §5.6 time-numoffset = (\"+\" / \"-\") time-hour \":\" time-minute takes exactly one leading sign, and time-hour = 2DIGIT must follow it, so \"-0\" is neither a second sign nor a 2DIGIT hour.", + "tags": [ + "reject", + "offset" + ], + "applies_to": "date-time", + "input": "2026-01-29T12:00:00+-0:00", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (time-numoffset; time-hour = 2DIGIT)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "secfrac_10_digits", + "description": "Ten fractional digits, one past nanosecond resolution. §5.6 time-secfrac = \".\" 1*DIGIT states a lower bound of one digit and no upper bound, so this is grammatically well formed and the verdict is ACCEPT. A parser backed by a fixed nanosecond field truncates here; truncation is an implementation property and is NOT encoded in this verdict.", + "tags": [ + "accept", + "secfrac", + "precision-loss" + ], + "applies_to": "date-time", + "input": "2026-01-29T12:00:00.1234567890Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (time-secfrac = \".\" 1*DIGIT)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": true + } + }, + { + "name": "secfrac_19_digits_exceeds_int64", + "description": "Nineteen fractional nines. §5.6 time-secfrac = \".\" 1*DIGIT sets no upper bound, so the verdict is ACCEPT. Read as an integer the fraction is 9999999999999999999, which exceeds the signed 64-bit maximum 9223372036854775807; a parser that accumulates the fraction into a fixed-width integer before scaling overflows on this input rather than truncating. The overflow is an implementation property and is NOT encoded in this verdict.", + "tags": [ + "accept", + "secfrac", + "precision-loss", + "overflow" + ], + "applies_to": "date-time", + "input": "2026-01-29T12:00:00.9999999999999999999Z", + "source": "rfc-3339-text", + "provenance": { + "type": "derived_from_rfc_text", + "source_document": "RFC 3339", + "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", + "source_document_retrieved_on": "2026-08-07", + "rfc_ref": "RFC 3339 §5.6 (time-secfrac = \".\" 1*DIGIT)", + "verdict_authority": "rfc-3339-text", + "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", + "frozen_file": "expires-rfc3339.json", + "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", + "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." + }, + "tests": { + "parse": true + } + } + ], + "disputed": [ + { + "input": "2026-01-29 12:00:00Z", + "candidate_verdicts": [ + "reject", + "accept" + ], + "reason": "RFC 3339 §5.6 ABNF is date-time = full-date \"T\" full-time, which mandates a literal \"T\". But the second NOTE in the same section reads: \"ISO 8601 defines date and time separated by 'T'. Applications using this syntax may choose, for the sake of readability, to specify a full-date and full-time separated by (say) a space character.\" The NOTE neither amends the date-time production (as the lower-case NOTE explicitly does) nor states that the space form is a valid date-time. The verdict therefore cannot be pinned to a single section.", + "sections_consulted": [ + "RFC 3339 §5.6 (date-time production)", + "RFC 3339 §5.6 (NOTE on the space separator)" + ] + }, + { + "input": "2026-03-15T23:59:60Z", + "candidate_verdicts": [ + "reject", + "accept" + ], + "reason": "Second = 60 on a date that is not a June or December month end. §5.7 permits time-second = 60 \"at the end of months in which a leap second occurs -- to date: June (XXXX-06-30T23:59:60Z) or December (XXXX-12-31T23:59:60Z)\" and adds \"At all other times the maximum value of time-second is 59\". Read as a grammar restriction that is REJECT; but the same section says leap seconds \"cannot be predicted far into the future\" and are announced by IERS bulletin, and §5.7 records that first preference is June/December and SECOND preference March/September (Appendix D). Whether a parser is expected to enforce the month restriction, or only the 00-60 range, is not settled by the text.", + "sections_consulted": [ + "RFC 3339 §5.7 (time-second and leap-second rules)", + "RFC 3339 §5.6 (time-second = 2DIGIT ; 00-58, 00-59, 00-60)", + "RFC 3339 Appendix D" + ] + }, + { + "input": "2026-06-30T23:59:60Z", + "candidate_verdicts": [ + "accept", + "reject" + ], + "reason": "Second = 60 at a June month end in a year for which no leap second was announced. Positionally this is exactly the shape §5.7 names as permitted (XXXX-06-30T23:59:60Z), so a grammar-only reading ACCEPTS. But §5.7 conditions the permission on \"months in which a leap second occurs\", and no leap second occurred at 2026-06-30. A parser cannot hold the IERS bulletin history, so the section cannot decide this literal. Contrast the two SHIPPED leap-second vectors, which use 1972-06-30 and 1998-12-31 -- both rows of the Appendix D table, where the leap second demonstrably did occur.", + "sections_consulted": [ + "RFC 3339 §5.7 (time-second and leap-second rules)", + "RFC 3339 Appendix D (USNO leap-second table)" + ] + }, + { + "input": " 2026-01-29T12:00:00Z ", + "candidate_verdicts": [ + "reject", + "accept" + ], + "reason": "Surrounding whitespace. RFC 3339 §5.6 defines the date-time production and nothing else; it does not say whether a protocol field carrying a date-time may be padded. Whether the padding is stripped before the value reaches the parser is a question about the carrying protocol's field-value semantics, not about RFC 3339. No RFC 3339 section decides it.", + "sections_consulted": [ + "RFC 3339 §5.6 (date-time production)" + ] + }, + { + "input": [ + "9999-12-31T23:59:59+00:01", + "0000-01-01T00:00:00-00:01" + ], + "candidate_verdicts": [ + "accept", + "reject" + ], + "reason": "Lexically valid date-times whose offset-adjusted UTC instant falls outside the four-digit year range (year 10000 and year -0001 respectively). §5.6 date-fullyear = 4DIGIT constrains the LEXICAL year only. §5.7 restricts date-mday and time-second and says nothing about whether the offset-adjusted instant must also remain representable. Grammatically ACCEPT, semantically an overflow. No section decides which reading governs.", + "sections_consulted": [ + "RFC 3339 §5.6 (date-fullyear = 4DIGIT, time-numoffset)", + "RFC 3339 §5.7 (Restrictions)", + "RFC 3339 §4.2 (Local Offsets)" + ] + } + ], + "disputed_note": "Carried verbatim from expires-rfc3339.json. These are NOT shipped scenarios: each is a literal whose verdict RFC 3339 does not settle, recorded with its candidate verdicts so the question is visible rather than quietly decided. S6 did not add to, remove from, or re-adjudicate this block." +} From 6b59cd4593f54ef8674ea7dde206a9988a2e50fb Mon Sep 17 00:00:00 2001 From: Michael Moffett Date: Mon, 10 Aug 2026 10:58:57 -0600 Subject: [PATCH 02/15] test(mpp): add expires RFC 3339 conformance tests to seven SDK trees The corpus added in the previous commit is inert until something reads it. This commit points seven SDK trees at it, covering Go, Lua, PHP, Python, Ruby, Rust and TypeScript, so every tree asserts the same accept and reject verdicts against the same vectors. A divergence between two SDKs then shows up as a failing test in exactly one of them rather than as silence. Each test appends to the file in that tree which already covers the neighbouring behaviour, so it runs under the suite runner the tree already uses and no new entrypoint, fixture directory or dependency is introduced. Each drives that tree's own shipped parsing path rather than a bare library call, since the question is what the SDK does with an untrusted `expires` string, not what the language's date library does with it. Every tree selects the `applies_to == "date-time"` slice of the corpus before asserting, because that is the production MPP `expires` is specified in; `1963-06-19` is a valid RFC 3339 full-date and no date-time parser should accept it. Selection is on the first-class `applies_to` field rather than on a name prefix or a description string, and each tree also guards the filter itself, so a regression that silently empties or widens the slice fails the suite instead of passing vacuously. On divergence a test reports the offending input, the expected verdict and the scenario name, so a failure names the string that broke rather than only a count. --- go/protocols/mpp/wire/challenge_test.go | 113 +++++++++++++++ lua/tests/expires_rfc3339_spec.lua | 75 ++++++++++ php/tests/PayCore/Rfc3339Test.php | 82 +++++++++++ python/tests/test_expires.py | 93 ++++++++++++ ruby/test/pay_core/expires_rfc3339_test.rb | 56 ++++++++ .../kit/src/mpp/protocol/core/challenge.rs | 136 ++++++++++++++++++ .../client-charge-validation.test.ts | 87 +++++++++++ 7 files changed, 642 insertions(+) diff --git a/go/protocols/mpp/wire/challenge_test.go b/go/protocols/mpp/wire/challenge_test.go index 6cb98c0e6..474dcc381 100644 --- a/go/protocols/mpp/wire/challenge_test.go +++ b/go/protocols/mpp/wire/challenge_test.go @@ -1,6 +1,8 @@ package wire import ( + "encoding/json" + "os" "testing" "time" ) @@ -161,3 +163,114 @@ func TestNewPaymentCredentialRejectsUnmarshalablePayload(t *testing.T) { t.Fatal("expected marshal error") } } + +// ── Cross-SDK RFC 3339 conformance corpus (issue #111) ── +// +// Vectors live in `harness/vectors/mpp-protocol/expires-rfc3339-corpus.json` +// under the `expires.parse` operation. Every SDK asserts the same ACCEPT / +// REJECT verdict against the same vectors, so a divergence between two SDKs +// shows up as a failing test in exactly one of them rather than as silence. +// +// Only the `applies_to == "date-time"` slice is run here. The corpus also +// carries `full-date` and `full-time` scenarios, which answer a different +// question than an `expires` field asks — `1963-06-19` is a valid RFC 3339 +// `full-date` and no `date-time` parser should accept it. Filtering is on the +// first-class `applies_to` field, never on a name prefix or a description +// string. +// +// The verdict is read out of this package's own `PaymentChallenge.IsExpired`, +// not out of a bare `time.Parse`. `IsExpired` fails closed — it returns true +// both for "parsed, and in the past" and for "did not parse" — but `now` is a +// parameter, so passing a reference instant before every year the corpus can +// express (0000..9999) makes every parse success sort strictly after `now`. +// IsExpired then returns false for a parse success and true for a parse +// failure, and the verdict comes from the shipped function. + +const conformanceCorpusPath = "../../../../harness/vectors/mpp-protocol/expires-rfc3339-corpus.json" + +// rfc3339ConformanceReference is far enough in the past that every instant the +// corpus can express sorts after it, so IsExpired's time comparison never fires +// on a parse success and only the parse outcome is observed. +var rfc3339ConformanceReference = time.Date(-9999, time.January, 1, 0, 0, 0, 0, time.UTC) + +type rfc3339Scenario struct { + Name string `json:"name"` + Description string `json:"description"` + AppliesTo string `json:"applies_to"` + Input string `json:"input"` + Tests json.RawMessage `json:"tests"` +} + +type rfc3339Corpus struct { + Scenarios []rfc3339Scenario `json:"scenarios"` +} + +// wantsAccept reports whether the scenario's `tests.parse` encodes ACCEPT. +// `true` is ACCEPT; an object carrying `success: false` is REJECT. Mirrors the +// encoding used by the other vector files in the same directory. +func (s rfc3339Scenario) wantsAccept(t *testing.T) bool { + t.Helper() + var probe struct { + Parse json.RawMessage `json:"parse"` + } + if err := json.Unmarshal(s.Tests, &probe); err != nil { + t.Fatalf("%s: tests block did not decode: %v", s.Name, err) + } + var accepted bool + if err := json.Unmarshal(probe.Parse, &accepted); err == nil { + return accepted + } + var rejection struct { + Success bool `json:"success"` + } + if err := json.Unmarshal(probe.Parse, &rejection); err != nil { + t.Fatalf("%s: tests.parse was neither a bool nor an object: %v", s.Name, err) + } + return rejection.Success +} + +func loadRFC3339DateTimeVectors(t *testing.T) []rfc3339Scenario { + t.Helper() + raw, err := os.ReadFile(conformanceCorpusPath) + if err != nil { + t.Fatalf("conformance corpus unreadable at %s: %v", conformanceCorpusPath, err) + } + var corpus rfc3339Corpus + if err := json.Unmarshal(raw, &corpus); err != nil { + t.Fatalf("conformance corpus did not decode: %v", err) + } + var dateTime []rfc3339Scenario + for _, scenario := range corpus.Scenarios { + if scenario.AppliesTo == "date-time" { + dateTime = append(dateTime, scenario) + } + } + if len(dateTime) == 0 { + t.Fatal("conformance corpus admitted zero date-time scenarios") + } + return dateTime +} + +func TestRFC3339ConformanceCorpus(t *testing.T) { + t.Parallel() + for _, scenario := range loadRFC3339DateTimeVectors(t) { + t.Run(scenario.Name, func(t *testing.T) { + t.Parallel() + challenge := PaymentChallenge{Expires: scenario.Input} + accepted := !challenge.IsExpired(rfc3339ConformanceReference) + want := scenario.wantsAccept(t) + if accepted != want { + t.Fatalf("%s (%s): input %q — corpus expects %s, IsExpired reports %s", + scenario.Name, scenario.Description, scenario.Input, + conformanceVerdictName(want), conformanceVerdictName(accepted)) + } + }) + } +} + +func conformanceVerdictName(accepted bool) string { + if accepted { + return "ACCEPT" + } + return "REJECT" +} diff --git a/lua/tests/expires_rfc3339_spec.lua b/lua/tests/expires_rfc3339_spec.lua index b4be18cc6..c837a4e81 100644 --- a/lua/tests/expires_rfc3339_spec.lua +++ b/lua/tests/expires_rfc3339_spec.lua @@ -60,3 +60,78 @@ t.test('expires.is_expired returns true on unparseable input', function() local expires = require('pay_kit.protocols.mpp.expires') t.assert_equal(expires.is_expired('not-a-timestamp', 0), true) end) + +-- ── Cross-SDK RFC 3339 conformance corpus (issue #111) ── +-- +-- Vectors live in `harness/vectors/mpp-protocol/expires-rfc3339-corpus.json` +-- under the `expires.parse` operation. Every SDK asserts the same ACCEPT / +-- REJECT verdict against the same vectors, so a divergence between two SDKs +-- shows up as a failing test in exactly one of them rather than as silence. +-- +-- Only the `applies_to == "date-time"` slice runs here. The corpus also carries +-- `full-date` and `full-time` scenarios, which answer a different question than +-- an `expires` field asks -- `1963-06-19` is a valid RFC 3339 `full-date` and no +-- `date-time` parser should accept it. Selection is on the first-class +-- `applies_to` field, never on a name prefix or a description string. +-- +-- Decoding uses the SDK's own `pay_kit.util.json`, so the suite gains no new +-- dependency. The runner has no fixture-loading facility and no per-vector +-- test-registration idiom, so the corpus runs as a single aggregate test that +-- collects EVERY divergence and reports them all in one error. Failing on the +-- first mismatch would hide the rest. + +local function corpus_path() + -- `tests/run.lua` is driven from `lua/`, but the load path it sets also + -- allows a repo-root cwd. Locate the corpus from this file instead of from + -- the working directory. + local source = debug.getinfo(1, 'S').source:gsub('^@', '') + local dir = source:match('^(.*)/[^/]*$') or '.' + return dir .. '/../../harness/vectors/mpp-protocol/expires-rfc3339-corpus.json' +end + +t.test('expires.parse_rfc3339 matches the cross-SDK RFC 3339 corpus (date-time slice)', function() + local expires = require('pay_kit.protocols.mpp.expires') + local json = require('pay_kit.util.json') + + local path = corpus_path() + local handle = io.open(path, 'r') + t.assert_true(handle ~= nil, 'conformance corpus unreadable at ' .. path) + local body = handle:read('*a') + handle:close() + + local corpus = json.decode(body) + local admitted = 0 + local divergences = {} + + for i = 1, #corpus.scenarios do + local scenario = corpus.scenarios[i] + if scenario.applies_to == 'date-time' then + admitted = admitted + 1 + -- `"tests": {"parse": true}` is ACCEPT; `{"parse": {"success": false, …}}` + -- is REJECT. Identical to the encoding the other vector files in the same + -- directory use. + local expect_accept = scenario.tests.parse == true + local ok, value = pcall(expires.parse_rfc3339, scenario.input) + -- A crash on hostile input is a result, and it is a REJECT. + local accepted = ok and value ~= nil + if accepted ~= expect_accept then + divergences[#divergences + 1] = string.format( + '%s: input %q -- corpus expects %s, parse_rfc3339 reports %s', + scenario.name, scenario.input, + expect_accept and 'ACCEPT' or 'REJECT', + accepted and 'ACCEPT' or 'REJECT' + ) + end + end + end + + t.assert_true(admitted > 0, 'corpus admitted zero date-time scenarios') + t.assert_true(admitted < #corpus.scenarios, 'date-time filter admitted every scenario') + + if #divergences > 0 then + error(string.format( + '%d of %d date-time vectors diverge from the cross-SDK corpus:\n %s', + #divergences, admitted, table.concat(divergences, '\n ') + )) + end +end) diff --git a/php/tests/PayCore/Rfc3339Test.php b/php/tests/PayCore/Rfc3339Test.php index c8744cd18..7d9ecbae1 100644 --- a/php/tests/PayCore/Rfc3339Test.php +++ b/php/tests/PayCore/Rfc3339Test.php @@ -78,4 +78,86 @@ public function testRejectsInvalidRfc3339(string $input): void { $this->assertNull(Rfc3339Parser::parse($input)); } + + // ── Cross-SDK RFC 3339 conformance corpus (issue #111) ── + // + // Vectors live in `harness/vectors/mpp-protocol/expires-rfc3339-corpus.json` + // under the `expires.parse` operation. Every SDK asserts the same ACCEPT / + // REJECT verdict against the same vectors, so a divergence between two SDKs + // shows up as a failing test in exactly one of them rather than as silence. + + private const CORPUS_PATH = __DIR__ . '/../../../harness/vectors/mpp-protocol/expires-rfc3339-corpus.json'; + + /** + * The `applies_to == "date-time"` slice of the shared corpus. + * + * The corpus also carries `full-date` and `full-time` scenarios, which + * answer a different question than an `expires` field asks — `1963-06-19` + * is a valid RFC 3339 `full-date` and no `date-time` parser should accept + * it. Selection is on the first-class `applies_to` field, never on a name + * prefix or a description string. + * + * Verdict encoding, identical to the other vector files in the same + * directory: `"tests": {"parse": true}` is ACCEPT, and + * `"tests": {"parse": {"success": false, ...}}` is REJECT. + * + * @return array + */ + public static function conformanceVectors(): array + { + $raw = file_get_contents(self::CORPUS_PATH); + if ($raw === false) { + throw new \RuntimeException('conformance corpus unreadable at ' . self::CORPUS_PATH); + } + $corpus = json_decode($raw, true, 512, JSON_THROW_ON_ERROR); + + $vectors = []; + foreach ($corpus['scenarios'] as $scenario) { + if ($scenario['applies_to'] !== 'date-time') { + continue; + } + $vectors[$scenario['name']] = [ + $scenario['name'], + $scenario['input'], + $scenario['tests']['parse'] === true, + $scenario['description'], + ]; + } + + return $vectors; + } + + #[DataProvider('conformanceVectors')] + public function testMatchesCrossSdkConformanceCorpus( + string $name, + string $input, + bool $expectAccept, + string $description + ): void { + $accepted = Rfc3339Parser::parse($input) !== null; + + $this->assertSame($expectAccept, $accepted, sprintf( + '%s (%s): input "%s" — corpus expects %s, Rfc3339Parser::parse reports %s', + $name, + $description, + $input, + $expectAccept ? 'ACCEPT' : 'REJECT', + $accepted ? 'ACCEPT' : 'REJECT' + )); + } + + /** Guard the filter itself so a regression in it cannot go silent. */ + public function testCorpusAdmitsOnlyTheDateTimeSlice(): void + { + $corpus = json_decode(file_get_contents(self::CORPUS_PATH), true, 512, JSON_THROW_ON_ERROR); + $expected = 0; + foreach ($corpus['scenarios'] as $scenario) { + if ($scenario['applies_to'] === 'date-time') { + $expected++; + } + } + + $this->assertSame($expected, count(self::conformanceVectors())); + $this->assertLessThan(count($corpus['scenarios']), count(self::conformanceVectors())); + } } diff --git a/python/tests/test_expires.py b/python/tests/test_expires.py index 585d6e3d2..03599c95b 100644 --- a/python/tests/test_expires.py +++ b/python/tests/test_expires.py @@ -2,7 +2,11 @@ from __future__ import annotations +import json from datetime import UTC, datetime +from pathlib import Path + +import pytest from solana_pay_kit.protocols.mpp.core.expires import days, hours, minutes, seconds, weeks @@ -130,3 +134,92 @@ def test_invalid_month_rejected(self): # Lexically valid RFC 3339 shape, but month 13 fails the calendar # check delegated to datetime.fromisoformat. assert self._make_challenge("2099-13-01T00:00:00Z").is_expired() is True + + +# ── Cross-SDK RFC 3339 conformance corpus (issue #111) ── + +_CORPUS_PATH = ( + Path(__file__).resolve().parents[2] + / "harness" + / "vectors" + / "mpp-protocol" + / "expires-rfc3339-corpus.json" +) + + +def _load_date_time_vectors() -> list[tuple[str, str, bool, str]]: + """Return the ``applies_to == "date-time"`` slice of the shared corpus. + + Yields ``(name, input, expect_accept, description)``. The corpus also + carries ``full-date`` and ``full-time`` scenarios; those answer a different + question than an ``expires`` field asks — ``1963-06-19`` is a valid RFC 3339 + ``full-date`` and no ``date-time`` parser should accept it. Selection is on + the first-class ``applies_to`` field, never on a name prefix or a + description string. + + Verdict encoding, identical to the other vector files in the same + directory: ``"tests": {"parse": true}`` is ACCEPT, and + ``"tests": {"parse": {"success": false, ...}}`` is REJECT. + """ + corpus = json.loads(_CORPUS_PATH.read_text(encoding="utf-8")) + vectors: list[tuple[str, str, bool, str]] = [] + for scenario in corpus["scenarios"]: + if scenario["applies_to"] != "date-time": + continue + expectation = scenario["tests"]["parse"] + expect_accept = expectation is True + vectors.append( + (scenario["name"], scenario["input"], expect_accept, scenario["description"]) + ) + return vectors + + +_DATE_TIME_VECTORS = _load_date_time_vectors() + + +class TestRFC3339ConformanceCorpus: + """Every SDK asserts the same ACCEPT/REJECT verdict on the same vectors. + + A divergence between two SDKs then shows up as a failing test in exactly + one of them rather than as silence. + + This class covers ``_parse_rfc3339`` in + ``solana_pay_kit.protocols.mpp.core.types`` — the grammar + ``PaymentChallenge.is_expired`` delegates to, and therefore the one an + ``expires`` field is actually checked against. It raises ``ValueError`` on + a reject and returns a ``datetime`` on an accept, so the verdict is + unambiguous. + + Note for reviewers: this SDK carries a *second* RFC 3339 grammar, + ``_ISO8601_RE`` in ``solana_pay_kit.protocols.mpp.core.headers``, which + gates receipt timestamps. It is a different regex and it is not covered + here. Wiring the same corpus to that surface is a separate change. + """ + + @pytest.mark.parametrize( + ("name", "value", "expect_accept", "description"), + _DATE_TIME_VECTORS, + ids=[vector[0] for vector in _DATE_TIME_VECTORS], + ) + def test_vector(self, name: str, value: str, expect_accept: bool, description: str): + from solana_pay_kit.protocols.mpp.core.types import _parse_rfc3339 + + try: + _parse_rfc3339(value) + accepted = True + except ValueError: + accepted = False + + assert accepted is expect_accept, ( + f"{name} ({description}): input {value!r} — " + f"corpus expects {'ACCEPT' if expect_accept else 'REJECT'}, " + f"_parse_rfc3339 reports {'ACCEPT' if accepted else 'REJECT'}" + ) + + def test_corpus_admits_only_the_date_time_slice(self): + """Guard the filter itself: a loader regression must not go silent.""" + corpus = json.loads(_CORPUS_PATH.read_text(encoding="utf-8")) + assert len(_DATE_TIME_VECTORS) == sum( + 1 for scenario in corpus["scenarios"] if scenario["applies_to"] == "date-time" + ) + assert len(_DATE_TIME_VECTORS) < len(corpus["scenarios"]) diff --git a/ruby/test/pay_core/expires_rfc3339_test.rb b/ruby/test/pay_core/expires_rfc3339_test.rb index e15d74ddb..b558711dc 100644 --- a/ruby/test/pay_core/expires_rfc3339_test.rb +++ b/ruby/test/pay_core/expires_rfc3339_test.rb @@ -5,6 +5,7 @@ # (canonical JSON) and RFC 3339 (expires) live in dedicated files. # Battle-tested vector imports are tracked separately (see follow-up # issue referenced on the same PR thread). +require "json" require_relative "../test_helper" class ExpiresRfc3339Test < Minitest::Test @@ -82,3 +83,58 @@ def test_expires_strict_rfc3339_branches assert c7.expired? end end + +# ── Cross-SDK RFC 3339 conformance corpus (issue #111) ── +# +# Vectors live in `harness/vectors/mpp-protocol/expires-rfc3339-corpus.json` +# under the `expires.parse` operation. Every SDK asserts the same ACCEPT / +# REJECT verdict against the same vectors, so a divergence between two SDKs +# shows up as a failing test in exactly one of them rather than as silence. +# +# Only the `applies_to == "date-time"` slice runs here. The corpus also carries +# `full-date` and `full-time` scenarios, which answer a different question than +# an `expires` field asks — `1963-06-19` is a valid RFC 3339 `full-date` and no +# `date-time` parser should accept it. Selection is on the first-class +# `applies_to` field, never on a name prefix or a description string. +# +# The verdict comes straight out of `PayCore::Rfc3339Parser.parse`, which +# returns a `Time` on success and `nil` on any parse failure. That is the +# parser `Challenge#expired?` delegates to, so it is the grammar an `expires` +# field is actually checked against. +class ExpiresRfc3339CorpusTest < Minitest::Test + CORPUS_PATH = File.expand_path( + "../../../harness/vectors/mpp-protocol/expires-rfc3339-corpus.json", __dir__ + ) + + def self.date_time_vectors + corpus = JSON.parse(File.read(CORPUS_PATH)) + corpus.fetch("scenarios").select { |scenario| scenario["applies_to"] == "date-time" } + end + + DATE_TIME_VECTORS = date_time_vectors.freeze + + # Guard the filter itself so a regression in it cannot go silent. + def test_corpus_admits_only_the_date_time_slice + all = JSON.parse(File.read(CORPUS_PATH)).fetch("scenarios") + assert_equal all.count { |s| s["applies_to"] == "date-time" }, DATE_TIME_VECTORS.length + assert_operator DATE_TIME_VECTORS.length, :<, all.length + end + + DATE_TIME_VECTORS.each do |scenario| + # `"tests": {"parse": true}` is ACCEPT; `{"parse": {"success": false, …}}` + # is REJECT. Identical to the encoding the other vector files in the same + # directory use. + expect_accept = scenario.dig("tests", "parse") == true + + define_method(:"test_corpus_#{scenario.fetch("name")}") do + parsed = ::PayCore::Rfc3339Parser.parse(scenario.fetch("input")) + accepted = !parsed.nil? + assert_equal expect_accept, accepted, + format( + "%s (%s): input %p — corpus expects %s, Rfc3339Parser.parse reports %s", + scenario.fetch("name"), scenario.fetch("description"), scenario.fetch("input"), + expect_accept ? "ACCEPT" : "REJECT", accepted ? "ACCEPT" : "REJECT" + ) + end + end +end diff --git a/rust/crates/kit/src/mpp/protocol/core/challenge.rs b/rust/crates/kit/src/mpp/protocol/core/challenge.rs index 13db89482..e38ccbecd 100644 --- a/rust/crates/kit/src/mpp/protocol/core/challenge.rs +++ b/rust/crates/kit/src/mpp/protocol/core/challenge.rs @@ -811,4 +811,140 @@ mod tests { // A bare Receipt round-trips through ReceiptKind::Charge. assert!(matches!(parsed, ReceiptKind::Charge(_))); } + + // ── Cross-SDK RFC 3339 conformance corpus (issue #111) ── + // + // Vectors live in `harness/vectors/mpp-protocol/expires-rfc3339-corpus.json` + // under the `expires.parse` operation. Every SDK asserts the same ACCEPT / + // REJECT verdict against the same vectors, so a divergence between two SDKs + // shows up as a failing test in exactly one of them rather than as silence. + // + // Only the `applies_to == "date-time"` slice runs here. The corpus also + // carries `full-date` and `full-time` scenarios, which answer a different + // question than an `expires` field asks — `1963-06-19` is a valid RFC 3339 + // `full-date` and no `date-time` parser should accept it. Selection is on + // the first-class `applies_to` field, never on a name prefix or a + // description string. + // + // WHY THE VERDICT IS READ OFF `OffsetDateTime::parse` AND NOT OFF + // `is_expired`. `is_expired` (this file, `PaymentChallenge::is_expired`) + // collapses the outcome into a fail-closed `bool` and reads + // `OffsetDateTime::now_utc()` internally, with no parameter to inject a + // reference instant — unlike go's `IsExpired(now)`. It therefore cannot + // distinguish "parsed, and in the past" from "did not parse", and most of + // the corpus is in the past. So the ACCEPT/REJECT axis is asserted against + // the exact expression `is_expired` delegates to. + // + // The fail-closed coupling to the shipped surface is asserted separately + // and unambiguously: every REJECT vector must make `is_expired()` true. + // That direction has no ambiguity, because a parse failure is the only way + // a REJECT vector can reach the comparison. + + const CONFORMANCE_CORPUS: &str = concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../../harness/vectors/mpp-protocol/expires-rfc3339-corpus.json" + ); + + /// `(name, input, expect_accept, description)` for the `date-time` slice. + fn conformance_date_time_vectors() -> Vec<(String, String, bool, String)> { + let raw = std::fs::read_to_string(CONFORMANCE_CORPUS) + .unwrap_or_else(|e| panic!("conformance corpus unreadable at {CONFORMANCE_CORPUS}: {e}")); + let corpus: serde_json::Value = + serde_json::from_str(&raw).expect("conformance corpus must be valid JSON"); + + corpus["scenarios"] + .as_array() + .expect("corpus must carry a `scenarios` array") + .iter() + .filter(|scenario| scenario["applies_to"] == "date-time") + .map(|scenario| { + // `"tests": {"parse": true}` is ACCEPT; `{"parse": {"success": + // false, …}}` is REJECT. Identical to the encoding the other + // vector files in the same directory use. + let expect_accept = scenario["tests"]["parse"] == serde_json::Value::Bool(true); + ( + scenario["name"].as_str().unwrap_or_default().to_string(), + scenario["input"].as_str().unwrap_or_default().to_string(), + expect_accept, + scenario["description"].as_str().unwrap_or_default().to_string(), + ) + }) + .collect() + } + + #[test] + fn rfc3339_conformance_corpus() { + let vectors = conformance_date_time_vectors(); + assert!( + !vectors.is_empty(), + "conformance corpus admitted zero date-time scenarios" + ); + + let mut divergences = Vec::new(); + for (name, input, expect_accept, description) in &vectors { + let accepted = time::OffsetDateTime::parse( + input, + &time::format_description::well_known::Rfc3339, + ) + .is_ok(); + if accepted != *expect_accept { + divergences.push(format!( + "{name} ({description}): input {input:?} — corpus expects {}, \ + OffsetDateTime::parse reports {}", + if *expect_accept { "ACCEPT" } else { "REJECT" }, + if accepted { "ACCEPT" } else { "REJECT" }, + )); + } + } + + assert!( + divergences.is_empty(), + "{} of {} date-time vectors diverge from the cross-SDK corpus:\n {}", + divergences.len(), + vectors.len(), + divergences.join("\n "), + ); + } + + #[test] + fn rfc3339_conformance_corpus_rejects_fail_closed() { + // Ties the corpus to the shipped surface. A REJECT vector must make + // `is_expired()` true; a parse failure is the only route to that + // outcome for an unparseable value, so this direction is unambiguous. + let vectors = conformance_date_time_vectors(); + let rejects: Vec<_> = vectors.iter().filter(|vector| !vector.2).collect(); + assert!(!rejects.is_empty(), "corpus admitted zero REJECT vectors"); + + let mut leaked = Vec::new(); + for (name, input, _, description) in rejects { + let challenge = PaymentChallenge::new( + "id", + "realm", + "solana", + "charge", + Base64UrlJson::from_value(&serde_json::json!({})).unwrap(), + ) + .with_expires(input.clone()); + if !challenge.is_expired() { + leaked.push(format!("{name} ({description}): input {input:?}")); + } + } + + assert!( + leaked.is_empty(), + "{} REJECT vectors did not fail closed through is_expired():\n {}", + leaked.len(), + leaked.join("\n "), + ); + } + + /// Guard the filter itself so a regression in it cannot go silent. + #[test] + fn rfc3339_conformance_corpus_admits_only_the_date_time_slice() { + let raw = std::fs::read_to_string(CONFORMANCE_CORPUS).expect("corpus unreadable"); + let corpus: serde_json::Value = serde_json::from_str(&raw).expect("corpus must be JSON"); + let total = corpus["scenarios"].as_array().unwrap().len(); + let admitted = conformance_date_time_vectors().len(); + assert!(admitted < total, "date-time filter admitted every scenario"); + } } diff --git a/typescript/packages/mpp/src/__tests__/client-charge-validation.test.ts b/typescript/packages/mpp/src/__tests__/client-charge-validation.test.ts index a5b2962ce..2ddfed9e8 100644 --- a/typescript/packages/mpp/src/__tests__/client-charge-validation.test.ts +++ b/typescript/packages/mpp/src/__tests__/client-charge-validation.test.ts @@ -440,3 +440,90 @@ test('#10 createCredential rejects a network that does not match expectedNetwork }); await expect(method.createCredential({ challenge })).rejects.toThrow(/does not match the expected network/); }); + +// ── Cross-SDK RFC 3339 conformance corpus (issue #111) ── +// +// Vectors live in `harness/vectors/mpp-protocol/expires-rfc3339-corpus.json` +// under the `expires.parse` operation. Every SDK asserts the same ACCEPT / +// REJECT verdict against the same vectors, so a divergence between two SDKs +// shows up as a failing test in exactly one of them rather than as silence. +// +// Only the `applies_to == "date-time"` slice runs here. The corpus also carries +// `full-date` and `full-time` scenarios, which answer a different question than +// an `expires` field asks — `1963-06-19` is a valid RFC 3339 `full-date` and no +// `date-time` parser should accept it. Selection is on the first-class +// `applies_to` field, never on a name prefix or a description string. +// +// WHAT THIS ASSERTS, PRECISELY. This package has no RFC 3339 parser module. +// The two expiry call sites delegate the whole parse to the JS engine: +// +// client/Charge.ts:404 assertChallengeNotExpired +// -> new Date(expires).getTime(); NaN => "malformed" +// server/Session.ts:347 assertChallengeOpenNotExpired +// -> Date.parse(expires); NaN => "must be an RFC3339 timestamp" +// +// Both guards are module-private and neither package exposes a public surface +// that returns a parse verdict, so this test evaluates the same two engine +// expressions those call sites evaluate. The verdict source is therefore the +// *delegated* parser (the engine), not an SDK-owned parser — a real +// distinction, and the reason `expires` conformance here is a property of the +// Node/V8 version as much as of this SDK. If a parser is ever exported, repoint +// `verdicts` at it and the vectors carry over unchanged. +// +// Both call sites are checked, because they are two different expressions and a +// divergence between them would itself be a client/server protocol bug. + +import { readFileSync } from 'node:fs'; + +interface ConformanceScenario { + name: string; + description: string; + applies_to: string; + input: string; + tests: { parse: true | { success: boolean } }; +} + +const CORPUS_URL = new URL( + '../../../../../harness/vectors/mpp-protocol/expires-rfc3339-corpus.json', + import.meta.url, +); + +const corpus = JSON.parse(readFileSync(CORPUS_URL, 'utf8')) as { scenarios: ConformanceScenario[] }; +const dateTimeVectors = corpus.scenarios.filter((scenario) => scenario.applies_to === 'date-time'); + +// `"tests": {"parse": true}` is ACCEPT; `{"parse": {"success": false, …}}` is +// REJECT. Identical to the encoding the other vector files in the same +// directory use. +const expectsAccept = (scenario: ConformanceScenario) => scenario.tests.parse === true; + +// Guard the filter itself so a regression in it cannot go silent. +test('RFC 3339 corpus admits only the date-time slice', () => { + expect(dateTimeVectors.length).toBe( + corpus.scenarios.filter((scenario) => scenario.applies_to === 'date-time').length, + ); + expect(dateTimeVectors.length).toBeLessThan(corpus.scenarios.length); +}); + +for (const scenario of dateTimeVectors) { + test(`RFC 3339 corpus / charge call site / ${scenario.name}`, () => { + // client/Charge.ts:404 + const accepted = !Number.isNaN(new Date(scenario.input).getTime()); + expect( + accepted, + `${scenario.name} (${scenario.description}): input ${JSON.stringify(scenario.input)} — ` + + `corpus expects ${expectsAccept(scenario) ? 'ACCEPT' : 'REJECT'}, ` + + `new Date(...).getTime() reports ${accepted ? 'ACCEPT' : 'REJECT'}`, + ).toBe(expectsAccept(scenario)); + }); + + test(`RFC 3339 corpus / session call site / ${scenario.name}`, () => { + // server/Session.ts:347 + const accepted = !Number.isNaN(Date.parse(scenario.input)); + expect( + accepted, + `${scenario.name} (${scenario.description}): input ${JSON.stringify(scenario.input)} — ` + + `corpus expects ${expectsAccept(scenario) ? 'ACCEPT' : 'REJECT'}, ` + + `Date.parse(...) reports ${accepted ? 'ACCEPT' : 'REJECT'}`, + ).toBe(expectsAccept(scenario)); + }); +} From e2f2b1d0b8414c202c49628fb4abb2b58c1745e7 Mon Sep 17 00:00:00 2001 From: Michael Moffett Date: Mon, 10 Aug 2026 12:26:15 -0600 Subject: [PATCH 03/15] test(mpp): correct the expires collector comment and cover its filter The comment above collectExpiresCases() in harness/src/protocol/vectors.ts said that per-language `expires` tests call the collector directly, and harness/src/protocol/README.md repeated the claim. That is not what the code does. Nothing calls the collector: each SDK test loads expires-rfc3339-corpus.json itself and applies the same `applies_to == "date-time"` filter independently, which is why the collector appears nowhere outside the harness. Both places now say so, and say that the collector is the TypeScript side of the same contract, kept for the day an adapter implements `expires.parse`. The surrounding rationale is unchanged and still holds: the cases stay out of collectProtocolCases() because folding them in would add 116 red cases to a green suite, and absorbing them later is still a one-line spread. That comment could go wrong unnoticed because nothing exercised the collector. Its `applies_to` filter and its accept/reject mapping could both regress without a test going red. test/conformance.test.ts gains a block that drives collectExpiresCases() directly and asserts that it returns exactly the date-time scenarios and strictly fewer than all of them, that every case carries the `expires.parse` op, and that a scenario whose parse expectation is an error yields expectSuccess false with that scenario's own error type while a success scenario yields expectSuccess true. Every count is derived from the corpus at runtime, from the scenario list and from the corpus's own counts block, so a corpus edit moves the expectation along with it. The block reads the vendored corpus and nothing else: it spawns no runner and contributes no case to the driver in that file, so the cross-SDK run is unchanged. It lives there rather than in a new file because that is the harness test file the workflows in this repo actually execute. The corpus is byte-for-byte untouched. --- harness/src/protocol/README.md | 7 ++- harness/src/protocol/vectors.ts | 8 ++- harness/test/conformance.test.ts | 104 +++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 5 deletions(-) diff --git a/harness/src/protocol/README.md b/harness/src/protocol/README.md index b0cc5e54b..3a5babb41 100644 --- a/harness/src/protocol/README.md +++ b/harness/src/protocol/README.md @@ -69,8 +69,11 @@ only the 116 `applies_to == "date-time"` vectors, because `full-date` and No adapter implements `expires.parse` yet, so these cases are deliberately **not** in `collectProtocolCases()` — folding them in would turn the green -reference suite red. Per-language `expires` tests call `collectExpiresCases()` -directly. +reference suite red. Nothing calls `collectExpiresCases()` today: each SDK's +`expires` test loads the corpus JSON itself and applies the same +`applies_to == "date-time"` filter independently. The collector is the +TypeScript side of that contract, kept for the day an adapter implements the +op. ## Running diff --git a/harness/src/protocol/vectors.ts b/harness/src/protocol/vectors.ts index fc53a58da..ba0fe9ba9 100644 --- a/harness/src/protocol/vectors.ts +++ b/harness/src/protocol/vectors.ts @@ -430,9 +430,11 @@ export function collectProtocolCases(): ProtocolCase[] { // end-to-end against the TypeScript reference adapter by // `test/protocol-conformance.test.ts`, and no adapter implements // `expires.parse` yet — folding these in would add 116 red cases to a green -// suite. Per-language `expires` tests call this collector directly; when the -// adapters implement the op, absorbing it is a one-line spread into `cases` -// above. +// suite. Nothing calls this collector today: each SDK's `expires` test loads +// the corpus JSON itself and applies this same `applies_to == "date-time"` +// filter independently. The collector is the TypeScript side of that contract, +// kept for the day an adapter implements `expires.parse` — at which point +// absorbing it is a one-line spread into `cases` above. export function collectExpiresCases(): ProtocolCase[] { const cases: ProtocolCase[] = []; diff --git a/harness/test/conformance.test.ts b/harness/test/conformance.test.ts index aa5e86f09..bb8d7819b 100644 --- a/harness/test/conformance.test.ts +++ b/harness/test/conformance.test.ts @@ -25,6 +25,11 @@ import { } from "../src/conformance/contract-schema"; import { discoverRunners } from "../src/conformance/runners"; import { parseLanguageAllowlist } from "../src/conformance/select"; +import { + EXPIRES_APPLIES_TO, + collectExpiresCases, + loadExpiresRfc3339, +} from "../src/protocol/vectors"; import type { ConformanceVector, RunnerResult, @@ -360,3 +365,102 @@ describe("cross-SDK conformance vectors", () => { }); } }); + +// ── RFC 3339 `expires` corpus collector (issue #111) ── +// +// `collectExpiresCases()` (harness/src/protocol/vectors.ts) is the TypeScript +// side of the cross-SDK `expires` contract. No adapter implements +// `expires.parse` yet, so its cases sit deliberately outside +// `collectProtocolCases()` — folding them into the protocol driver would add +// one red case per date-time vector to a green suite. That also means nothing +// dispatches the collector, so its `applies_to` filter and its ACCEPT/REJECT +// mapping would regress silently. They are asserted directly here instead. +// +// This block spawns no runner and contributes no vector to the driver above. +// It reads the vendored corpus and checks the collector's output against it. +// Every count is derived at runtime — from the scenario list and from the +// corpus's own `counts` block — so a corpus edit moves the expectation with it +// rather than turning this red for the wrong reason. +describe("mpp-protocol RFC 3339 expires corpus collector", () => { + const corpus = loadExpiresRfc3339(); + const scenarios = corpus.scenarios; + const dateTime = scenarios.filter( + (scenario) => scenario.applies_to === EXPIRES_APPLIES_TO, + ); + const cases = collectExpiresCases(); + + // The corpus states its own tally; cross-check the filter against both that + // and a recount, so a scenario added without updating `counts` is caught too. + const declared = ( + corpus as unknown as { + counts: { + scenarios_total: number; + by_applies_to: Record; + }; + } + ).counts; + + it("admits exactly the date-time slice", () => { + expect(scenarios).toHaveLength(declared.scenarios_total); + expect(dateTime).toHaveLength(declared.by_applies_to[EXPIRES_APPLIES_TO]); + expect(cases).toHaveLength(dateTime.length); + expect(cases.map((testCase) => testCase.scenario).sort()).toEqual( + dateTime.map((scenario) => scenario.name).sort(), + ); + }); + + it("admits strictly fewer than the whole corpus", () => { + // A filter that silently degrades to pass-everything fails here, before it + // can feed `full-date` / `full-time` verdicts to an `expires` parser. + expect(cases.length).toBeGreaterThan(0); + expect(cases.length).toBeLessThan(scenarios.length); + }); + + it("tags every admitted case with the expires.parse op", () => { + for (const testCase of cases) { + expect(testCase.op, testCase.scenario).toBe("expires.parse"); + } + }); + + it("maps every ACCEPT and REJECT verdict faithfully", () => { + const byScenario = new Map(cases.map((testCase) => [testCase.scenario, testCase])); + + let accepts = 0; + let rejects = 0; + for (const scenario of dateTime) { + const testCase = byScenario.get(scenario.name); + expect(testCase, `no case emitted for ${scenario.name}`).toBeDefined(); + if (!testCase) continue; + + const parse = scenario.tests.parse; + if (parse === true) { + accepts += 1; + expect(testCase.expectSuccess, scenario.name).toBe(true); + if (testCase.expectSuccess) { + expect(testCase.golden, scenario.name).toEqual({ valid: true }); + } + expect(testCase.input, scenario.name).toEqual({ expires: scenario.input }); + continue; + } + + expect( + typeof parse === "object" && parse.success === false, + `${scenario.name}: unexpected tests.parse encoding`, + ).toBe(true); + if (typeof parse !== "object") continue; + + rejects += 1; + expect(testCase.expectSuccess, scenario.name).toBe(false); + if (!testCase.expectSuccess) { + expect(testCase.errorType, scenario.name).toBe(parse.error_type); + } + expect(testCase.input, scenario.name).toEqual({ expires: scenario.input }); + } + + // Both verdicts must actually be represented: a mapping that collapsed one + // direction into the other would otherwise pass every assertion above. + expect(accepts).toBeGreaterThan(0); + expect(rejects).toBeGreaterThan(0); + expect(accepts + rejects).toBe(dateTime.length); + }); +}); From 7bd86747e107c756a80c5e47f9b66d37cb0afa1f Mon Sep 17 00:00:00 2001 From: Michael Moffett Date: Tue, 11 Aug 2026 11:43:43 -0600 Subject: [PATCH 04/15] test(mpp): re-encode the expires vectors in the canonical file shape The corpus added in this branch's first commit shipped as expires-rfc3339-corpus.json in a shape of its own: 228 scenarios spanning three RFC 3339 productions, a provenance object on every row, and an `applies_to` field that each consumer had to filter on before the verdicts meant anything. That is not how the five files beside it read, and a vector directory whose files do not share a shape is one a reader has to learn twice. This commit replaces it with expires.json, in the shape the canonical files already use: { version, spec_ref, description, commands, scenarios } at the top level, { name, description, tags, input, tests } per row, and the same verdict encoding those files use: "tests": { "parse": true } "tests": { "parse": { "success": false, "error_type": "parse_error" } } The file is 124 scenarios, 54 ACCEPT and 70 REJECT, and every one of them is an `expires` verdict. Because the file holds nothing else, `applies_to` is gone and the filtering step with it: there is no slice to select and no scenario a consumer should skip, so there is no longer a way to read the file as a flat list and get a wrong answer. Eight of the rejections are well-formed full-date and full-time strings, kept because a parser that accepts `1963-06-19` for `expires` is the bug #111 is about; the other wrong-production rows are excluded rather than shipped and skipped, since eleven month-length variants are one failure shape to an `expires` parser and not eleven. No `object` golden is supplied for an ACCEPT. That is deliberate and the README says why: #111 is a verdict-alignment problem rather than an instant-normalisation one, and leap seconds have no representable instant in most date libraries. Per-row provenance moves to the directory README, which now records the three upstreams, their pinned commits, their licences, the counts per provenance class, the dedupe rule and the rows it dropped. Recording it once there rather than on every row is what lets the vector file read like its neighbours. The harness loader and the seven SDK tests follow the file to its new path and shape and drop the filter they no longer need. No verdict changed in this re-encode. --- go/protocols/mpp/wire/challenge_test.go | 38 +- harness/src/protocol/README.md | 16 +- harness/src/protocol/vectors.ts | 51 +- harness/test/conformance.test.ts | 56 +- harness/vectors/mpp-protocol/README.md | 276 +- .../mpp-protocol/expires-rfc3339-corpus.json | 7447 ----------------- harness/vectors/mpp-protocol/expires.json | 1336 +++ lua/tests/expires_rfc3339_spec.lua | 59 +- php/tests/PayCore/Rfc3339Test.php | 40 +- python/tests/test_expires.py | 37 +- ruby/test/pay_core/expires_rfc3339_test.rb | 36 +- .../kit/src/mpp/protocol/core/challenge.rs | 45 +- .../client-charge-validation.test.ts | 34 +- 13 files changed, 1735 insertions(+), 7736 deletions(-) delete mode 100644 harness/vectors/mpp-protocol/expires-rfc3339-corpus.json create mode 100644 harness/vectors/mpp-protocol/expires.json diff --git a/go/protocols/mpp/wire/challenge_test.go b/go/protocols/mpp/wire/challenge_test.go index 474dcc381..bf8cd4bf3 100644 --- a/go/protocols/mpp/wire/challenge_test.go +++ b/go/protocols/mpp/wire/challenge_test.go @@ -166,17 +166,13 @@ func TestNewPaymentCredentialRejectsUnmarshalablePayload(t *testing.T) { // ── Cross-SDK RFC 3339 conformance corpus (issue #111) ── // -// Vectors live in `harness/vectors/mpp-protocol/expires-rfc3339-corpus.json` -// under the `expires.parse` operation. Every SDK asserts the same ACCEPT / -// REJECT verdict against the same vectors, so a divergence between two SDKs -// shows up as a failing test in exactly one of them rather than as silence. +// Vectors live in `harness/vectors/mpp-protocol/expires.json` under the +// `expires.parse` operation. Every SDK asserts the same ACCEPT / REJECT verdict +// against the same vectors, so a divergence between two SDKs shows up as a +// failing test in exactly one of them rather than as silence. // -// Only the `applies_to == "date-time"` slice is run here. The corpus also -// carries `full-date` and `full-time` scenarios, which answer a different -// question than an `expires` field asks — `1963-06-19` is a valid RFC 3339 -// `full-date` and no `date-time` parser should accept it. Filtering is on the -// first-class `applies_to` field, never on a name prefix or a description -// string. +// Every scenario in the file is run. There is no slice to select and no +// scenario to skip. // // The verdict is read out of this package's own `PaymentChallenge.IsExpired`, // not out of a bare `time.Parse`. `IsExpired` fails closed — it returns true @@ -186,7 +182,7 @@ func TestNewPaymentCredentialRejectsUnmarshalablePayload(t *testing.T) { // IsExpired then returns false for a parse success and true for a parse // failure, and the verdict comes from the shipped function. -const conformanceCorpusPath = "../../../../harness/vectors/mpp-protocol/expires-rfc3339-corpus.json" +const conformanceCorpusPath = "../../../../harness/vectors/mpp-protocol/expires.json" // rfc3339ConformanceReference is far enough in the past that every instant the // corpus can express sorts after it, so IsExpired's time comparison never fires @@ -196,7 +192,6 @@ var rfc3339ConformanceReference = time.Date(-9999, time.January, 1, 0, 0, 0, 0, type rfc3339Scenario struct { Name string `json:"name"` Description string `json:"description"` - AppliesTo string `json:"applies_to"` Input string `json:"input"` Tests json.RawMessage `json:"tests"` } @@ -229,7 +224,10 @@ func (s rfc3339Scenario) wantsAccept(t *testing.T) bool { return rejection.Success } -func loadRFC3339DateTimeVectors(t *testing.T) []rfc3339Scenario { +// loadRFC3339Vectors returns every scenario in the corpus. The zero-length +// check guards the loader: a truncated or empty read fails here rather than +// passing quietly with nothing left to run. +func loadRFC3339Vectors(t *testing.T) []rfc3339Scenario { t.Helper() raw, err := os.ReadFile(conformanceCorpusPath) if err != nil { @@ -239,21 +237,15 @@ func loadRFC3339DateTimeVectors(t *testing.T) []rfc3339Scenario { if err := json.Unmarshal(raw, &corpus); err != nil { t.Fatalf("conformance corpus did not decode: %v", err) } - var dateTime []rfc3339Scenario - for _, scenario := range corpus.Scenarios { - if scenario.AppliesTo == "date-time" { - dateTime = append(dateTime, scenario) - } + if len(corpus.Scenarios) == 0 { + t.Fatal("conformance corpus carried zero scenarios") } - if len(dateTime) == 0 { - t.Fatal("conformance corpus admitted zero date-time scenarios") - } - return dateTime + return corpus.Scenarios } func TestRFC3339ConformanceCorpus(t *testing.T) { t.Parallel() - for _, scenario := range loadRFC3339DateTimeVectors(t) { + for _, scenario := range loadRFC3339Vectors(t) { t.Run(scenario.Name, func(t *testing.T) { t.Parallel() challenge := PaymentChallenge{Expires: scenario.Input} diff --git a/harness/src/protocol/README.md b/harness/src/protocol/README.md index 3a5babb41..9d20ee3fc 100644 --- a/harness/src/protocol/README.md +++ b/harness/src/protocol/README.md @@ -14,7 +14,7 @@ base64url encode/decode, and the challenge-id HMAC. | File | Role | |------|------| -| `vectors.ts` | Loads the vendored canonical vectors and flattens them into dispatchable `ProtocolCase`s (handles the `tests.: true \| {success:false,error_type}` shape, constructed `wire` shorthand, `adapters` allow-lists, and `maxDurationMs*` budgets). Also loads the RFC 3339 `expires` corpus via `collectExpiresCases()`, filtered to `applies_to == "date-time"`. | +| `vectors.ts` | Loads the vendored canonical vectors and flattens them into dispatchable `ProtocolCase`s (handles the `tests.: true \| {success:false,error_type}` shape, constructed `wire` shorthand, `adapters` allow-lists, and `maxDurationMs*` budgets). Also loads the RFC 3339 `expires` vectors via `collectExpiresCases()`. | | `driver.ts` | Transport-agnostic case runner. `exact` compare for base64url / challenge.id; `semantic` compare for parse/format (re-parses both wires through the paired parse op, with credential request-encoding normalization) — mirrors the canonical runner's `compare_*_semantic`. Times the primary op and enforces the scenario's duration budget like `compare_duration`. | | `flow-driver.ts` | HTTP 402 FLOW orchestration: a TypeScript port of the normative mpp-tools `flow_runner.py`. Performs the initial request / 402 / credential retry / receipt exchange itself, routing only `challenge.parse` / `credential.format` / `receipt.parse` through a `ProtocolAdapter`, and compares recorded results against `harness/vectors/mpp-protocol-flows/golden-results.json` after the canonical normalization. | | `runners/typescript.ts` | TypeScript REFERENCE runner. In-process `ProtocolAdapter` over `mppx` (pay-kit's TS protocol core) + a stdin/stdout CLI speaking the canonical adapter ABI. | @@ -54,7 +54,7 @@ Operations (canonical, from `mpp-tools/conformance/operations.json`): | `expires.parse` | `{ expires }` | `{ valid: true }` | exact | RFC 3339 `expires` parse conformance (issue #111), driven by -`harness/vectors/mpp-protocol/expires-rfc3339-corpus.json` through +`harness/vectors/mpp-protocol/expires.json` through `collectExpiresCases()`. **This operation is not in upstream `mpp-tools/conformance/operations.json`** — it is proposed here so the corpus has an operation to hang from, and the name, the input/result shape, and the @@ -62,18 +62,14 @@ has an operation to hang from, and the name, the input/result shape, and the `error_type` is `parse_error`, reusing the `.parse` vocabulary above rather than introducing a new one. -The corpus covers three RFC 3339 productions; `collectExpiresCases()` admits -only the 116 `applies_to == "date-time"` vectors, because `full-date` and -`full-time` verdicts are correct statements about a different production and an -`expires` parser is right to reject those inputs. +Every scenario in the file is an `expires` verdict and `collectExpiresCases()` +admits all of them. There is no slice to select and no scenario to skip. No adapter implements `expires.parse` yet, so these cases are deliberately **not** in `collectProtocolCases()` — folding them in would turn the green reference suite red. Nothing calls `collectExpiresCases()` today: each SDK's -`expires` test loads the corpus JSON itself and applies the same -`applies_to == "date-time"` filter independently. The collector is the -TypeScript side of that contract, kept for the day an adapter implements the -op. +`expires` test loads the vector JSON itself. The collector is the TypeScript +side of that contract, kept for the day an adapter implements the op. ## Running diff --git a/harness/src/protocol/vectors.ts b/harness/src/protocol/vectors.ts index ba0fe9ba9..77b4e21ba 100644 --- a/harness/src/protocol/vectors.ts +++ b/harness/src/protocol/vectors.ts @@ -153,20 +153,11 @@ export type ChallengeIdScenario = { }; // RFC 3339 `expires` scenario (issue #111). Same `{ name, description, tags, -// tests }` spine as every other scenario in this directory; the input field is -// named for its domain (`input`, a single untrusted string) the way header +// …, tests }` spine as every other scenario in this directory; the input field +// is named for its domain (`input`, a single untrusted string) the way header // scenarios name theirs `wire` and base64url names its `decoded` / `encoded`. // -// `applies_to` is load-bearing and MUST be read before `tests.parse`. The -// corpus carries vectors for three different RFC 3339 productions — -// `date-time`, `full-date`, `full-time` — and a verdict only means what it -// says inside its own production. MPP `expires` holds a `date-time`; a -// `full-date` ACCEPT such as `1963-06-19` is a correct statement about a -// different production and is NOT an instruction to accept that value as an -// `expires`. `collectExpiresCases` filters on this field for exactly that -// reason. See the corpus's own `scope` block. -// -// No `object` golden is carried for an ACCEPT: the corpus is deliberately a +// No `object` golden is carried for an ACCEPT: the file is deliberately a // verdict corpus, not an instant-normalization corpus (leap seconds have no // representable instant in most date libraries and sub-nanosecond precision // truncates differently per language). #111 asks for ACCEPT/REJECT parity @@ -175,12 +166,8 @@ export type ExpiresScenario = { name: string; description?: string; tags?: string[]; - // Which RFC 3339 production this scenario's verdict answers. - applies_to: "date-time" | "full-date" | "full-time"; // The single untrusted string under test. input: string; - source?: string; - provenance?: Record; tests: TestFlags; }; @@ -216,14 +203,10 @@ export function loadChallengeId(): VectorFile { return load("challenge-id.json"); } -export function loadExpiresRfc3339(): VectorFile { - return load("expires-rfc3339-corpus.json"); +export function loadExpires(): VectorFile { + return load("expires.json"); } -// The RFC 3339 production MPP `expires` holds. Everything else in the corpus -// answers a different question than an `expires` field asks. -export const EXPIRES_APPLIES_TO = "date-time"; - // A single dispatchable unit of work derived from a vector scenario: the // op to run, the adapter input envelope, and either the golden success // output (for a happy-path) or the expected error_type. @@ -401,13 +384,8 @@ export function collectProtocolCases(): ProtocolCase[] { // Expand the RFC 3339 `expires` corpus into `expires.parse` cases (issue // #111: assert ACCEPT and REJECT outcomes match across SDKs for each vector). // -// FILTER. Only `applies_to === "date-time"` vectors are admitted. The corpus -// ships 228 scenarios covering three RFC 3339 productions; 112 of them answer -// `full-date` / `full-time` questions, and 29 of those carry ACCEPT for inputs -// no `expires` parser may accept (`1963-06-19`, `08:30:06Z`, ...). Running the -// unfiltered file against an `expires` parser produces failures that are not -// defects. The filter is on the first-class `applies_to` field — never on a -// name prefix, a description string, or any other heuristic. +// Every scenario in the file is admitted. There is no slice to select: each row +// carries an `expires` verdict and nothing else does. // // ABI shape, PROPOSED BY THIS PR — the maintainer should overrule any of it: // input : { expires: "" } @@ -429,18 +407,15 @@ export function collectProtocolCases(): ProtocolCase[] { // Deliberately NOT folded into `collectProtocolCases()`. That list is driven // end-to-end against the TypeScript reference adapter by // `test/protocol-conformance.test.ts`, and no adapter implements -// `expires.parse` yet — folding these in would add 116 red cases to a green -// suite. Nothing calls this collector today: each SDK's `expires` test loads -// the corpus JSON itself and applies this same `applies_to == "date-time"` -// filter independently. The collector is the TypeScript side of that contract, -// kept for the day an adapter implements `expires.parse` — at which point -// absorbing it is a one-line spread into `cases` above. +// `expires.parse` yet — folding these in would add one red case per vector to +// a green suite. Nothing calls this collector today: each SDK's `expires` test +// loads the vector JSON itself. The collector is the TypeScript side of that +// contract, kept for the day an adapter implements `expires.parse` — at which +// point absorbing it is a one-line spread into `cases` above. export function collectExpiresCases(): ProtocolCase[] { const cases: ProtocolCase[] = []; - for (const s of loadExpiresRfc3339().scenarios) { - if (s.applies_to !== EXPIRES_APPLIES_TO) continue; - + for (const s of loadExpires().scenarios) { const input = { expires: s.input }; const parseErr = expectsError(s.tests.parse); if (parseErr) { diff --git a/harness/test/conformance.test.ts b/harness/test/conformance.test.ts index bb8d7819b..dfea820bc 100644 --- a/harness/test/conformance.test.ts +++ b/harness/test/conformance.test.ts @@ -25,11 +25,7 @@ import { } from "../src/conformance/contract-schema"; import { discoverRunners } from "../src/conformance/runners"; import { parseLanguageAllowlist } from "../src/conformance/select"; -import { - EXPIRES_APPLIES_TO, - collectExpiresCases, - loadExpiresRfc3339, -} from "../src/protocol/vectors"; +import { collectExpiresCases, loadExpires } from "../src/protocol/vectors"; import type { ConformanceVector, RunnerResult, @@ -372,50 +368,30 @@ describe("cross-SDK conformance vectors", () => { // side of the cross-SDK `expires` contract. No adapter implements // `expires.parse` yet, so its cases sit deliberately outside // `collectProtocolCases()` — folding them into the protocol driver would add -// one red case per date-time vector to a green suite. That also means nothing -// dispatches the collector, so its `applies_to` filter and its ACCEPT/REJECT -// mapping would regress silently. They are asserted directly here instead. +// one red case per vector to a green suite. That also means nothing dispatches +// the collector, so its ACCEPT/REJECT mapping would regress silently. It is +// asserted directly here instead. // // This block spawns no runner and contributes no vector to the driver above. // It reads the vendored corpus and checks the collector's output against it. -// Every count is derived at runtime — from the scenario list and from the -// corpus's own `counts` block — so a corpus edit moves the expectation with it -// rather than turning this red for the wrong reason. +// Every count is derived at runtime from the scenario list, so a corpus edit +// moves the expectation with it rather than turning this red for the wrong +// reason. describe("mpp-protocol RFC 3339 expires corpus collector", () => { - const corpus = loadExpiresRfc3339(); + const corpus = loadExpires(); const scenarios = corpus.scenarios; - const dateTime = scenarios.filter( - (scenario) => scenario.applies_to === EXPIRES_APPLIES_TO, - ); const cases = collectExpiresCases(); - // The corpus states its own tally; cross-check the filter against both that - // and a recount, so a scenario added without updating `counts` is caught too. - const declared = ( - corpus as unknown as { - counts: { - scenarios_total: number; - by_applies_to: Record; - }; - } - ).counts; - - it("admits exactly the date-time slice", () => { - expect(scenarios).toHaveLength(declared.scenarios_total); - expect(dateTime).toHaveLength(declared.by_applies_to[EXPIRES_APPLIES_TO]); - expect(cases).toHaveLength(dateTime.length); + it("emits a case for every scenario in the file", () => { + // A collector that silently degrades to emitting nothing, or that drops a + // scenario, fails here rather than reporting a smaller green suite. + expect(scenarios.length).toBeGreaterThan(0); + expect(cases).toHaveLength(scenarios.length); expect(cases.map((testCase) => testCase.scenario).sort()).toEqual( - dateTime.map((scenario) => scenario.name).sort(), + scenarios.map((scenario) => scenario.name).sort(), ); }); - it("admits strictly fewer than the whole corpus", () => { - // A filter that silently degrades to pass-everything fails here, before it - // can feed `full-date` / `full-time` verdicts to an `expires` parser. - expect(cases.length).toBeGreaterThan(0); - expect(cases.length).toBeLessThan(scenarios.length); - }); - it("tags every admitted case with the expires.parse op", () => { for (const testCase of cases) { expect(testCase.op, testCase.scenario).toBe("expires.parse"); @@ -427,7 +403,7 @@ describe("mpp-protocol RFC 3339 expires corpus collector", () => { let accepts = 0; let rejects = 0; - for (const scenario of dateTime) { + for (const scenario of scenarios) { const testCase = byScenario.get(scenario.name); expect(testCase, `no case emitted for ${scenario.name}`).toBeDefined(); if (!testCase) continue; @@ -461,6 +437,6 @@ describe("mpp-protocol RFC 3339 expires corpus collector", () => { // direction into the other would otherwise pass every assertion above. expect(accepts).toBeGreaterThan(0); expect(rejects).toBeGreaterThan(0); - expect(accepts + rejects).toBe(dateTime.length); + expect(accepts + rejects).toBe(scenarios.length); }); }); diff --git a/harness/vectors/mpp-protocol/README.md b/harness/vectors/mpp-protocol/README.md index 0162d62af..454268b61 100644 --- a/harness/vectors/mpp-protocol/README.md +++ b/harness/vectors/mpp-protocol/README.md @@ -1,8 +1,7 @@ # MPP protocol conformance vectors (canonical) -> **Two provenances live in this directory.** Every file except -> `expires-rfc3339-corpus.json` is imported verbatim from `tempoxyz/mpp-tools` -> and is described immediately below. `expires-rfc3339-corpus.json` is a +> **Two provenances live in this directory.** Every file except `expires.json` is imported +> verbatim from `tempoxyz/mpp-tools` and is described immediately below. `expires.json` is a > separate import with its own upstreams and its own licence files — see > [RFC 3339 `expires` corpus](#rfc-3339-expires-corpus). @@ -58,7 +57,7 @@ may carry: ## Source / attribution -Applies to every file in this directory **except** `expires-rfc3339-corpus.json`. +Applies to every file in this directory **except** `expires.json`. - Upstream: `tempoxyz/mpp-tools`, `conformance/vectors/` - Upstream commit: `b15fea4ee3f12da7ece735dc778ad84102af679c` (2026-06-08) @@ -71,56 +70,253 @@ bump the commit reference above. | File | Operation(s) | Spec reference | |------|--------------|----------------| -| `expires-rfc3339-corpus.json` | `expires.parse` | RFC 3339 §5.6 (grammar), §5.7, §5.8, §4.2–§4.3, App. C, App. D | +| `expires.json` | `expires.parse` | RFC 3339 §5.6 | -RFC 3339 date / time / date-time **parse** conformance vectors for the MPP -`expires` field (issue #111). Read by `loadExpiresRfc3339()` / -`collectExpiresCases()` in `harness/src/protocol/vectors.ts`. It uses the same -`{ version, spec_ref, description, commands, scenarios }` file shape and the -same `tests.parse` verdict encoding as the canonical files above: +RFC 3339 **parse** conformance vectors for the MPP `expires` field (issue #111): 124 scenarios, 54 ACCEPT and 70 REJECT. Same +`{ version, spec_ref, description, commands, scenarios }` file shape, same `{ name, description, tags, … , tests }` +row shape and the same verdict encoding as the canonical files above: ``` "tests": { "parse": true } // ACCEPT "tests": { "parse": { "success": false, "error_type": "parse_error" } } // REJECT ``` -**Read `applies_to` before you read `tests.parse`.** The corpus holds vectors -for three different RFC 3339 productions — `date-time` (116), `full-date` (74), -`full-time` (38) — and a verdict only means what it says inside its own -production. MPP `expires` holds a `date-time`. **Filter to -`applies_to == "date-time"` before running anything against an `expires` -parser**; 29 of the other 112 carry ACCEPT for inputs (`1963-06-19`, -`08:30:06Z`, …) that an `expires` parser is *correct* to reject. -`collectExpiresCases()` applies that filter. The corpus's own `scope` block -states this at length. - -No `object` golden is supplied for ACCEPT scenarios — deliberately. #111 is a -verdict-alignment problem, not an instant-normalisation problem. +Every scenario in the file is an `expires` verdict and every scenario is exercised. There is no +filter to apply and no scenario a consumer should skip. No `object` golden is supplied for an +ACCEPT — deliberately: #111 is a verdict-alignment problem, not an instant-normalisation one, and +leap seconds have no representable instant in most date libraries. ### Source / attribution (this file only) -Three upstreams, none of them mpp-tools. The corpus's own top-level -`attribution` object carries the full notices; the summary: +Three upstreams, none of them mpp-tools. -- **RFC 3339** — Klyne & Newman, July 2002, retrieved 2026-08-07 from - `https://www.rfc-editor.org/rfc/rfc3339.txt`. Copyright (C) The Internet - Society (2002); reproduction permitted per the RFC's own notice, carried - verbatim in `attribution.rfc_3339.notice`. +- **RFC 3339** — "Date and Time on the Internet: Timestamps", Klyne & Newman, July 2002. + Retrieved 2026-08-07 from `https://www.rfc-editor.org/rfc/rfc3339.txt`. Copyright (C) The + Internet Society (2002); reproduction and derivative works permitted per the RFC's own notice. - **JSON Schema Test Suite** — [`json-schema-org/JSON-Schema-Test-Suite`](https://github.com/json-schema-org/JSON-Schema-Test-Suite), - `tests/draft2020-12/optional/format/date-time.json`. Upstream commit - `15fe552d6cf76e29cc8165306fb6a72503fd360b` (2026-08-06). MIT, Copyright (c) - 2012 Julian Berman — see `LICENSE.json-schema-test-suite` in this directory. + `tests/draft2020-12/optional/format/date-time.json` and `.../date.json` and `.../time.json`. + Commit `15fe552d6cf76e29cc8165306fb6a72503fd360b` (2026-08-06). MIT, Copyright (c) 2012 Julian + Berman — see `LICENSE.json-schema-test-suite` in this directory. - **Go standard library** — [`golang/go`](https://github.com/golang/go), - `src/time/format_test.go`. Upstream commit - `c19862e5f8415b4f24b189d065ed739517c548ba` (tag `go1.26.5`). BSD-3-Clause, - Copyright 2009 The Go Authors — see `LICENSE.go-stdlib` in this directory; + `src/time/format_test.go`, tables `parseTests`, `parseErrorTests` and + `longFractionalDigitsTests`. Commit `c19862e5f8415b4f24b189d065ed739517c548ba` (tag `go1.26.5`). + BSD-3-Clause, Copyright 2009 The Go Authors — see `LICENSE.go-stdlib` in this directory; additional patent grant in `PATENTS.go-stdlib`. -Every scenario additionally carries its own `provenance` object naming the -source repo, commit sha, commit date, file, and (for extracted rows) the exact -source line. +### Per-row provenance + +Provenance is recorded here rather than on every row, so that the vector file reads like its +neighbours. Each shipped scenario belongs to exactly one class: + +| class | shipped rows | what it means | verdict authority | +|---|---|---|---| +| `imported_verbatim` | 27 | input and verdict copied byte-for-byte from the JSON Schema Test Suite at the pinned commit; descriptions are the upstream's own and are not rewritten | JSON Schema Test Suite | +| `extracted_from_source` | 27 | input lifted from a test table in Go stdlib source at the pinned commit. `parseTests` and `longFractionalDigitsTests` rows fail the Go test on a parse error, so the table asserts Go ACCEPTS them; `parseErrorTests` rows fail the Go test when parsing succeeds, so the table asserts Go REJECTS them | Go stdlib | +| `derived_from_rfc_text` | 58 | derived from the §5.6 grammar, the §5.7 restrictions and the §5.8 worked examples, with the deciding section recorded per row | RFC 3339 | +| `hand_authored` | 4 | written against the ABNF to close a boundary the imports left open | RFC 3339 | +| authored for this file | 8 | the wrong-production rejections and the empty string, below | RFC 3339 §5.6 | + +### Dedupe rule + +Byte-exact on the `input` string. No normalisation and no case folding: two vectors differing only by `T` vs `t`, or by surrounding whitespace, are two vectors. + +Precedence when two sources carry the same input: `imported_verbatim` → `extracted_from_source` → `hand_authored` → `derived_from_rfc_text`. Tie-break: same provenance type: the earlier position in the source file wins. + +**6 duplicates dropped, 0 verdict disagreements** between sources. + +| input | kept | dropped | verdicts agree | +|---|---|---|---| +| `22:59:60Z` | `jsts_time_006` (imported_verbatim, REJECT) | `jsts_time_029` (imported_verbatim) | yes | +| `23:58:60Z` | `jsts_time_007` (imported_verbatim, REJECT) | `jsts_time_030` (imported_verbatim) | yes | +| `2020-11-28T23:55:45Z` | `jsts_date_039` (imported_verbatim, REJECT) | `jsts_time_041` (imported_verbatim) | yes | +| `1937-01-01T12:00:27.87+00:20` | `jsts_date_time_003` (imported_verbatim, ACCEPT) | `rfc_5_8_example_twenty_minute_offset` (derived_from_rfc_text) | yes | +| `1998-12-31T23:59:60Z` | `jsts_date_time_005` (imported_verbatim, ACCEPT) | `leap_second_december_month_end` (derived_from_rfc_text) | yes | +| (the empty string) | `jsts_date_047` (imported_verbatim, REJECT) | `empty_string` (derived_from_rfc_text) | yes | + +### Independent audit + +All 170 imported vectors were re-derived by hand from the text of RFC 3339 by a second author who did not +build the import. **Result: 169 AGREE, 0 DIVERGE, 1 UNRESOLVED-PROVENANCE.** Verdicts changed by the merge: 0. +The RFC text used was pinned at sha256 +`9ab2b8864a85dca73a88f49b0927bc7bc85f596926e4fd1890905777924e700a`. + +### Not shipped — and why. Nothing was deleted. + +The import considered 235 inputs and settled 228 scenarios across three RFC 3339 +productions. This file ships the 124 that are `expires` verdicts. The other 104 are listed individually +below, with the rule that excluded each one. + +#### Unresolved provenance (1) + +A vector whose verdict could not be tied to an RFC 3339 section by the S4 audit does not ship here. It travels in EVIDENCE.md as a finding about the specification, which is its entire purpose. + +- **`jsts_date_070`**, input `1582-10-10` (upstream ACCEPT) — UNRESOLVED-PROVENANCE. RFC 3339 does not decide whether a date inside the 1582 Julian-to-Gregorian reform gap is valid: §5.6 is purely syntactic, §5.7 constrains only the maximum mday, and neither §2 nor Appendix C qualifies "the Gregorian calendar" as proleptic or historical. The upstream suite asserts valid: true. + +#### Disputed — RFC 3339 does not settle these (5) + +Recorded rather than quietly decided. Each is a literal whose verdict the text does not pin down; a +maintainer wanting one of them decided should say which way. + +| input | candidate verdicts | why it is not settled | +|---|---|---| +| `2026-01-29 12:00:00Z` | reject / accept | RFC 3339 §5.6 ABNF is date-time = full-date "T" full-time, which mandates a literal "T". But the second NOTE in the same section reads: "ISO 8601 defines date and time separated by 'T'. | +| `2026-03-15T23:59:60Z` | reject / accept | Second = 60 on a date that is not a June or December month end. §5.7 permits time-second = 60 "at the end of months in which a leap second occurs -- to date: June (XXXX-06-30T23:59:60Z) or December (XXXX-12-31T23:59:60Z)" and adds "At all other times the maximum value of time-second is 59". | +| `2026-06-30T23:59:60Z` | accept / reject | Second = 60 at a June month end in a year for which no leap second was announced. Positionally this is exactly the shape §5.7 names as permitted (XXXX-06-30T23:59:60Z), so a grammar-only reading ACCEPTS. | +| ` 2026-01-29T12:00:00Z ` | reject / accept | Surrounding whitespace. RFC 3339 §5.6 defines the date-time production and nothing else; it does not say whether a protocol field carrying a date-time may be padded. | +| `9999-12-31T23:59:59+00:01` and `0000-01-01T00:00:00-00:01` | accept / reject | Lexically valid date-times whose offset-adjusted UTC instant falls outside the four-digit year range (year 10000 and year -0001 respectively). §5.6 date-fullyear = 4DIGIT constrains the LEXICAL year only. | + +#### Wrong production, or a duplicate failure shape (104) + +The import covered three RFC 3339 productions because the upstream suites do: `date-time` (the one +`expires` holds), `full-date` (a bare `YYYY-MM-DD`) and `full-time` (a bare time with an offset). A +`full-date` or `full-time` verdict is a correct statement about a different production and is **not** +an `expires` verdict. Those rows are admitted here only when their `expires` verdict follows +mechanically from §5.6 `date-time = full-date "T" full-time` — that is, when the input is well +formed in its own production and therefore cannot also be a `date-time`. Seven such rows ship, one +per distinct failure shape, plus one restoration: + +| shipped as | input | the failure shape it covers | +|---|---|---| +| `bare_date_no_time` | `1963-06-19` | a well-formed bare full-date | +| `bare_date_leap_day` | `2020-02-29` | a bare full-date on a calendar edge — catches a parser that checks the calendar before the shape | +| `bare_time_zulu` | `08:30:06Z` | a well-formed bare full-time | +| `bare_time_lowercase_z` | `08:30:06z` | catches a parser keying on an upper-case `Z` | +| `bare_time_numeric_offset` | `08:30:06-08:00` | catches a parser keying on `Z` at all | +| `bare_time_secfrac` | `23:20:50.52Z` | catches a parser keying on the `.` | +| `bare_time_leap_second` | `23:59:60Z` | catches a leap-second special-case path | +| `empty_string` | `(the empty string)` | restores the `date-time` empty-string row that the dedupe rule above absorbed into `jsts_date_047` | + +The remaining rows are listed below. Every one of them still carries a correct verdict for its own +production; none is retracted and none is deleted. + +##### `verdict-would-flip` (1) + +**Excluded because re-expressing it would flip a verdict.** + +`jsts_date_039`, input `2020-11-28T23:55:45Z`, production `full-date`, verdict REJECT. That input is a fully conforming RFC 3339 +date-time. Upstream ships it in `.../format/date.json` as a negative case for the `full-date` +production — a date-time is not a bare `YYYY-MM-DD`, so REJECT is the right `full-date` answer. As an +`expires` verdict it would invert to ACCEPT. It is excluded rather than re-adjudicated. + +##### `wrong-production-shape-already-represented` (22) + +**Well formed in its own production, but its `expires` failure shape is already covered by one of the eight rows above.** The eleven month-length variants are one failure shape to an `expires` parser, not eleven. + +| name | input | production | its own verdict | +|---|---|---|---| +| `jsts_date_002` | `2020-01-31` | `full-date` | ACCEPT | +| `jsts_date_004` | `2021-02-28` | `full-date` | ACCEPT | +| `jsts_date_006` | `2020-03-31` | `full-date` | ACCEPT | +| `jsts_date_008` | `2020-04-30` | `full-date` | ACCEPT | +| `jsts_date_010` | `2020-05-31` | `full-date` | ACCEPT | +| `jsts_date_012` | `2020-06-30` | `full-date` | ACCEPT | +| `jsts_date_014` | `2020-07-31` | `full-date` | ACCEPT | +| `jsts_date_016` | `2020-08-31` | `full-date` | ACCEPT | +| `jsts_date_018` | `2020-09-30` | `full-date` | ACCEPT | +| `jsts_date_020` | `2020-10-31` | `full-date` | ACCEPT | +| `jsts_date_022` | `2020-11-30` | `full-date` | ACCEPT | +| `jsts_date_024` | `2020-12-31` | `full-date` | ACCEPT | +| `jsts_date_041` | `0400-02-29` | `full-date` | ACCEPT | +| `jsts_date_052` | `0001-01-01` | `full-date` | ACCEPT | +| `jsts_time_008` | `23:59:60+00:00` | `full-time` | ACCEPT | +| `jsts_time_011` | `01:29:60+01:30` | `full-time` | ACCEPT | +| `jsts_time_012` | `23:29:60+23:30` | `full-time` | ACCEPT | +| `jsts_time_015` | `15:59:60-08:00` | `full-time` | ACCEPT | +| `jsts_time_016` | `00:29:60-23:30` | `full-time` | ACCEPT | +| `jsts_time_020` | `08:30:06.283185Z` | `full-time` | ACCEPT | +| `jsts_time_021` | `08:30:06+00:20` | `full-time` | ACCEPT | +| `jsts_time_023` | `12:34:56-00:00` | `full-time` | ACCEPT | + +##### `wrong-production-malformed` (81) + +**Malformed within its own production.** Every parser rejects these for a reason that has nothing to do with production confusion, so they add no `expires` coverage. + +| name | input | production | its own verdict | +|---|---|---|---| +| `jsts_date_003` | `2020-01-32` | `full-date` | REJECT | +| `jsts_date_005` | `2020-02-30` | `full-date` | REJECT | +| `jsts_date_007` | `2020-03-32` | `full-date` | REJECT | +| `jsts_date_009` | `2020-04-31` | `full-date` | REJECT | +| `jsts_date_011` | `2020-05-32` | `full-date` | REJECT | +| `jsts_date_013` | `2020-06-31` | `full-date` | REJECT | +| `jsts_date_015` | `2020-07-32` | `full-date` | REJECT | +| `jsts_date_017` | `2020-08-32` | `full-date` | REJECT | +| `jsts_date_019` | `2020-09-31` | `full-date` | REJECT | +| `jsts_date_021` | `2020-10-32` | `full-date` | REJECT | +| `jsts_date_023` | `2020-11-31` | `full-date` | REJECT | +| `jsts_date_025` | `2020-12-32` | `full-date` | REJECT | +| `jsts_date_026` | `06/19/1963` | `full-date` | REJECT | +| `jsts_date_027` | `2013-350` | `full-date` | REJECT | +| `jsts_date_028` | `1998-1-20` | `full-date` | REJECT | +| `jsts_date_029` | `1998-01-1` | `full-date` | REJECT | +| `jsts_date_030` | `1998-13-01` | `full-date` | REJECT | +| `jsts_date_031` | `2021-02-29` | `full-date` | REJECT | +| `jsts_date_033` | `1963-06-1৪` | `full-date` | REJECT | +| `jsts_date_034` | `2020-0৪-01` | `full-date` | REJECT | +| `jsts_date_035` | `20230328` | `full-date` | REJECT | +| `jsts_date_036` | `2023-W01` | `full-date` | REJECT | +| `jsts_date_037` | `2023-W13-2` | `full-date` | REJECT | +| `jsts_date_038` | `2022W527` | `full-date` | REJECT | +| `jsts_date_040` | `0100-02-29` | `full-date` | REJECT | +| `jsts_date_042` | `2100-02-29` | `full-date` | REJECT | +| `jsts_date_043` | ` 2024-01-15` | `full-date` | REJECT | +| `jsts_date_044` | `2024-01-15 ` | `full-date` | REJECT | +| `jsts_date_045` | `2024-00-15` | `full-date` | REJECT | +| `jsts_date_046` | `2024-01-00` | `full-date` | REJECT | +| `jsts_date_048` | `2020 -01-01` | `full-date` | REJECT | +| `jsts_date_049` | `2020-01-01X` | `full-date` | REJECT | +| `jsts_date_050` | `2020-01-01Z` | `full-date` | REJECT | +| `jsts_date_051` | `2020-01-01 00:00:00Z` | `full-date` | REJECT | +| `jsts_date_053` | `20-01-01` | `full-date` | REJECT | +| `jsts_date_054` | `998-01-01` | `full-date` | REJECT | +| `jsts_date_055` | `12020-01-01` | `full-date` | REJECT | +| `jsts_date_056` | `+2020-01-01` | `full-date` | REJECT | +| `jsts_date_057` | `-2020-01-01` | `full-date` | REJECT | +| `jsts_date_058` | `২020-01-01` | `full-date` | REJECT | +| `jsts_date_059` | `YYYY-01-01` | `full-date` | REJECT | +| `jsts_date_060` | `2020-001-01` | `full-date` | REJECT | +| `jsts_date_061` | `2020-MM-01` | `full-date` | REJECT | +| `jsts_date_062` | `2020-01-001` | `full-date` | REJECT | +| `jsts_date_063` | `2020-01-DD` | `full-date` | REJECT | +| `jsts_date_064` | `2020:01:01` | `full-date` | REJECT | +| `jsts_date_065` | `2020.01.01` | `full-date` | REJECT | +| `jsts_date_066` | `2020 01 01` | `full-date` | REJECT | +| `jsts_date_067` | `2020-01/01` | `full-date` | REJECT | +| `jsts_date_068` | `2020--01-01` | `full-date` | REJECT | +| `jsts_date_069` | `2020-01--01` | `full-date` | REJECT | +| `jsts_date_071` | `2147483648-01-01` | `full-date` | REJECT | +| `jsts_date_072` | `2020-01-0:` | `full-date` | REJECT | +| `jsts_date_073` | `২0-01-01` | `full-date` | REJECT | +| `jsts_date_074` | `2020–01–01` | `full-date` | REJECT | +| `jsts_date_075` | `2020-01-01\x00` | `full-date` | REJECT | +| `jsts_time_002` | `008:030:006Z` | `full-time` | REJECT | +| `jsts_time_003` | `8:3:6Z` | `full-time` | REJECT | +| `jsts_time_004` | `8:0030:6Z` | `full-time` | REJECT | +| `jsts_time_006` | `22:59:60Z` | `full-time` | REJECT | +| `jsts_time_007` | `23:58:60Z` | `full-time` | REJECT | +| `jsts_time_009` | `22:59:60+00:00` | `full-time` | REJECT | +| `jsts_time_010` | `23:58:60+00:00` | `full-time` | REJECT | +| `jsts_time_013` | `23:59:60+01:00` | `full-time` | REJECT | +| `jsts_time_014` | `23:59:60+00:30` | `full-time` | REJECT | +| `jsts_time_017` | `23:59:60-01:00` | `full-time` | REJECT | +| `jsts_time_018` | `23:59:60-00:30` | `full-time` | REJECT | +| `jsts_time_024` | `08:30:06-8:000` | `full-time` | REJECT | +| `jsts_time_026` | `24:00:00Z` | `full-time` | REJECT | +| `jsts_time_027` | `00:60:00Z` | `full-time` | REJECT | +| `jsts_time_028` | `00:00:61Z` | `full-time` | REJECT | +| `jsts_time_031` | `01:02:03+24:00` | `full-time` | REJECT | +| `jsts_time_032` | `01:02:03+00:60` | `full-time` | REJECT | +| `jsts_time_033` | `01:02:03Z+00:30` | `full-time` | REJECT | +| `jsts_time_034` | `08:30:06 PST` | `full-time` | REJECT | +| `jsts_time_035` | `01:01:01,1111` | `full-time` | REJECT | +| `jsts_time_036` | `12:00:00` | `full-time` | REJECT | +| `jsts_time_037` | `12:00:00.52` | `full-time` | REJECT | +| `jsts_time_038` | `1২:00:00Z` | `full-time` | REJECT | +| `jsts_time_039` | `08:30:06#00:20` | `full-time` | REJECT | +| `jsts_time_040` | `ab:cd:ef` | `full-time` | REJECT | + +--- -Do not hand-edit this file. To refresh, re-run the import from the upstream -repos and bump the commit references in the corpus's `attribution` block and -above. +Do not hand-edit `expires.json`. To refresh, re-run the import against newer pinned commits and +bump the shas above. diff --git a/harness/vectors/mpp-protocol/expires-rfc3339-corpus.json b/harness/vectors/mpp-protocol/expires-rfc3339-corpus.json deleted file mode 100644 index 97e6f0175..000000000 --- a/harness/vectors/mpp-protocol/expires-rfc3339-corpus.json +++ /dev/null @@ -1,7447 +0,0 @@ -{ - "version": "1.0.0", - "spec_ref": "RFC 3339 §5.6 (grammar), §5.7 (restrictions), §5.8 (examples), §4.2-§4.3 (offsets), Appendix C (leap years), Appendix D (leap seconds)", - "description": "RFC 3339 date / time / date-time PARSE conformance vectors for the MPP `expires` field, merged from four sources. EVERY scenario carries a `provenance` object naming where its input came from and what authority decided its verdict. Read `provenance.type` before trusting any row: `imported_verbatim` and `extracted_from_source` came from third-party test suites at a pinned commit; `hand_authored` and `derived_from_rfc_text` were written against the text of RFC 3339 and each carries the section that decides it.", - "scope": { - "read_this_before_tests_parse": "This corpus holds vectors for THREE different RFC 3339 productions, not one: `full-date` (a bare YYYY-MM-DD), `full-time` (a bare time with an offset), and `date-time` (full-date \"T\" full-time). Every scenario carries an `applies_to` field naming which of the three its verdict answers. Read `applies_to` before you read `tests.parse`. A verdict only means what it says inside its own production.", - "applies_to_values": { - "full-date": "The verdict answers RFC 3339 §5.6 `full-date` — a bare YYYY-MM-DD.", - "full-time": "The verdict answers RFC 3339 §5.6 `full-time` — a bare time with an offset.", - "date-time": "The verdict answers RFC 3339 §5.6 `date-time` = full-date \"T\" full-time. This is the production MPP `expires` holds." - }, - "expires_holds_a_date_time": "The MPP `expires` field holds a `date-time`. A `full-date` verdict and a `full-time` verdict are NOT `expires` verdicts. They are correct statements about a different production, imported verbatim from suites that test that production.", - "an_expires_parser_is_correct_to_reject_full_date_and_full_time_inputs": "An `expires` parser is CORRECT to reject every `full-date` input and every `full-time` input in this file. `2020-01-31` is not an `expires` value. `08:30:06Z` is not an `expires` value. If your parser rejects them, it is right and this corpus agrees with you. Do not file a bug against a parser for rejecting them, and do not \"fix\" a parser to accept them.", - "how_to_test_an_expires_implementation": { - "rule": "Filter to `applies_to == \"date-time\"` before running anything. That slice, and only that slice, is a set of `expires` verdicts. Running the unfiltered file against an `expires` parser will produce failures that are not defects.", - "date_time_slice_size": 116, - "corpus_total": 228, - "filter_python": "expires_vectors = [s for s in corpus[\"scenarios\"] if s[\"applies_to\"] == \"date-time\"]", - "filter_jq": "jq '.scenarios | map(select(.applies_to == \"date-time\"))' expires-rfc3339-corpus.json", - "filter_typescript": "const expiresVectors = corpus.scenarios.filter(s => s.applies_to === \"date-time\");" - }, - "two_classes_that_bite_if_you_skip_the_filter": [ - { - "class": "ACCEPT verdicts on inputs that are not date-times at all", - "count": 29, - "what_it_is": "29 vectors carry ACCEPT (`tests.parse: true`) for inputs that are bare dates or bare times — `1963-06-19`, `2020-01-31`, `08:30:06Z`, `23:59:60Z` and the rest. Every one of them is a correct `full-date` or `full-time` verdict.", - "why_it_bites": "Read as `expires` verdicts they are catastrophic: they instruct an implementer to ACCEPT values an `expires` parser MUST reject. Filtering to `applies_to == \"date-time\"` removes all 29.", - "how_to_list_them": "jq '.scenarios | map(select(.applies_to != \"date-time\" and .tests.parse == true)) | map(.name)' expires-rfc3339-corpus.json", - "by_applies_to": { - "full-date": 16, - "full-time": 13 - }, - "example_names": [ - "jsts_date_001", - "jsts_date_002", - "jsts_date_004", - "jsts_date_006", - "jsts_date_008", - "jsts_date_010" - ] - }, - { - "class": "a REJECT verdict on a fully conforming timestamp", - "count": 1, - "scenario_name": "jsts_date_039", - "input": "2020-11-28T23:55:45Z", - "applies_to": "full-date", - "upstream_file": "tests/draft2020-12/optional/format/date.json", - "upstream_group_description": "validation of date strings", - "upstream_case_description": "an invalid time string in date-time format", - "what_it_is": "Scenario `jsts_date_039`, input `2020-11-28T23:55:45Z`, carries REJECT. That input is a fully conforming RFC 3339 date-time. Upstream ships it in tests/draft2020-12/optional/format/date.json under the group \"validation of date strings\", as a negative case for the `full-date` production: a date-time is not a bare YYYY-MM-DD, so rejecting it is the right `full-date` answer. Its `applies_to` is therefore \"full-date\". (Note the upstream case description, kept verbatim, reads \"an invalid time string in date-time format\" — that wording describes the input's shape, not the production being tested.)", - "why_it_bites": "This is the single most damaging row in the file for an `expires` consumer. `2020-11-28T23:55:45Z` is exactly the shape an `expires` value takes. Run unfiltered, it asserts that a correct SDK is broken. As an `expires` verdict it is inverted; as the `full-date` verdict it actually is, it is correct. Filtering to `applies_to == \"date-time\"` removes it. If you are looking at a mystified failure on `2020-11-28T23:55:45Z`, this paragraph is the answer." - } - ], - "what_this_does_not_change": "No upstream `description` in this file was rewritten. They are verbatim imports under MIT and BSD-3 and they are true statements inside their own production. `applies_to` is what supplies the frame." - }, - "commands": { - "parse": "parse-expires" - }, - "verdict_encoding": { - "accept": "\"tests\": { \"parse\": true }", - "reject": "\"tests\": { \"parse\": { \"success\": false, \"error_type\": \"parse_error\" } }", - "note": "Identical to the encoding used by www-authenticate.json, authorization.json and receipt.json in this directory, and read by harness/src/protocol/vectors.ts (type TestExpectation)." - }, - "provenance_types": { - "imported_verbatim": "Input and upstream verdict copied byte-for-byte from a third-party test suite at a pinned commit. Nothing was authored. `provenance.upstream_valid` is the source's own judgement; the `tests` block is a mechanical restatement of it. Source: JSON Schema Test Suite (S2).", - "extracted_from_source": "Input lifted from a test TABLE in third-party SOURCE code at a pinned commit, and transformed into this schema. Every row carries the exact source line it came from. The verdict is the upstream project's, not ours (`provenance.verdict_authority`). Source: Go stdlib `format_test.go` (S3).", - "hand_authored": "Input written for this corpus from the ABNF in RFC 3339 §5.6 and the restrictions in §5.7, to close a boundary the imports left open. Not copied from any project's test data. Each carries `provenance.rfc_ref`. Source: P036 S5 residual pass.", - "derived_from_rfc_text": "Input derived from the text of RFC 3339 itself — the §5.8 worked examples and the boundaries the §5.6 grammar and §5.7 restrictions generate — with the deciding section cited per row in `provenance.rfc_ref`. These predate the imports and were independently re-derived by a second seat with zero disagreements. They are NOT imported and this file never claims they are." - }, - "dedupe": { - "rule": "Byte-exact on the `input` string. No normalisation and no case folding: two vectors differing only by `T` vs `t`, or by surrounding whitespace, are two vectors.", - "precedence": [ - "imported_verbatim", - "extracted_from_source", - "hand_authored", - "derived_from_rfc_text" - ], - "tie_break": "Same provenance type: the earlier position in the source file wins.", - "duplicates_dropped": 6, - "verdict_disagreements": 0, - "collisions": [ - { - "input": "22:59:60Z", - "kept": { - "name": "jsts_time_006", - "provenance_type": "imported_verbatim", - "verdict": "REJECT" - }, - "dropped": { - "name": "jsts_time_029", - "provenance_type": "imported_verbatim", - "verdict": "REJECT", - "rfc_ref": null - }, - "verdicts_agree": true, - "rule": "same provenance type; earlier source-file position" - }, - { - "input": "23:58:60Z", - "kept": { - "name": "jsts_time_007", - "provenance_type": "imported_verbatim", - "verdict": "REJECT" - }, - "dropped": { - "name": "jsts_time_030", - "provenance_type": "imported_verbatim", - "verdict": "REJECT", - "rfc_ref": null - }, - "verdicts_agree": true, - "rule": "same provenance type; earlier source-file position" - }, - { - "input": "2020-11-28T23:55:45Z", - "kept": { - "name": "jsts_date_039", - "provenance_type": "imported_verbatim", - "verdict": "REJECT" - }, - "dropped": { - "name": "jsts_time_041", - "provenance_type": "imported_verbatim", - "verdict": "REJECT", - "rfc_ref": null - }, - "verdicts_agree": true, - "rule": "same provenance type; earlier source-file position" - }, - { - "input": "1937-01-01T12:00:27.87+00:20", - "kept": { - "name": "jsts_date_time_003", - "provenance_type": "imported_verbatim", - "verdict": "ACCEPT" - }, - "dropped": { - "name": "rfc_5_8_example_twenty_minute_offset", - "provenance_type": "derived_from_rfc_text", - "verdict": "ACCEPT", - "rfc_ref": "RFC 3339 §5.8" - }, - "verdicts_agree": true, - "rule": "higher provenance precedence" - }, - { - "input": "1998-12-31T23:59:60Z", - "kept": { - "name": "jsts_date_time_005", - "provenance_type": "imported_verbatim", - "verdict": "ACCEPT" - }, - "dropped": { - "name": "leap_second_december_month_end", - "provenance_type": "derived_from_rfc_text", - "verdict": "ACCEPT", - "rfc_ref": "RFC 3339 §5.7; Appendix D" - }, - "verdicts_agree": true, - "rule": "higher provenance precedence" - }, - { - "input": "", - "kept": { - "name": "jsts_date_047", - "provenance_type": "imported_verbatim", - "verdict": "REJECT" - }, - "dropped": { - "name": "empty_string", - "provenance_type": "derived_from_rfc_text", - "verdict": "REJECT", - "rfc_ref": "RFC 3339 §5.6 (date-time)" - }, - "verdicts_agree": true, - "rule": "higher provenance precedence" - } - ] - }, - "excluded_unresolved_provenance": { - "rule": "A vector whose verdict could not be tied to an RFC 3339 section by the S4 audit does not ship here. It travels in EVIDENCE.md as a finding about the specification, which is its entire purpose.", - "count": 1, - "vectors": [ - { - "input": "1582-10-10", - "source": "json-schema-test-suite", - "upstream_file": "tests/draft2020-12/optional/format/date.json", - "s4_row": 97, - "reason": "UNRESOLVED-PROVENANCE. RFC 3339 does not decide whether a date inside the 1582 Julian-to-Gregorian reform gap is valid: §5.6 is purely syntactic, §5.7 constrains only the maximum mday, and neither §2 nor Appendix C qualifies \"the Gregorian calendar\" as proleptic or historical. The upstream suite asserts valid: true. Excluded from the corpus; travels in EVIDENCE.md as a finding (S4 §4 U1).", - "name": "jsts_date_070", - "source_verdict": "ACCEPT" - } - ] - }, - "audit": { - "step": "P036 S4", - "document": "agents/audit_engineer/outbox/T-P036-S4-REDERIVE-IMPORTS-2026-08-09_result.md", - "scope": "All 170 imported vectors (143 JSON Schema + 27 Go), each verdict re-derived by hand from RFC 3339 at sha256 9ab2b8864a85dca73a88f49b0927bc7bc85f596926e4fd1890905777924e700a.", - "result": "169 AGREE, 0 DIVERGE, 1 UNRESOLVED-PROVENANCE.", - "verdicts_changed_by_this_merge": 0 - }, - "sources": [ - { - "provenance_type": "imported_verbatim", - "step": "P036 S2", - "repo": "json-schema-org/JSON-Schema-Test-Suite", - "repo_url": "https://github.com/json-schema-org/JSON-Schema-Test-Suite", - "subdirectory": "tests/draft2020-12/optional/format/", - "files": [ - "tests/draft2020-12/optional/format/date-time.json", - "tests/draft2020-12/optional/format/date.json", - "tests/draft2020-12/optional/format/time.json" - ], - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "license": "MIT", - "license_holder": "Copyright (c) 2012 Julian Berman", - "license_file": "LICENSE.json-schema-test-suite", - "import_rule": "Exactly one filter was applied: upstream cases whose `data` is not a JSON string were dropped, because this corpus is about parsing strings and a non-string input is not an RFC 3339 question. The filter tested the JSON type of `data`, not the wording of `description`. NO other filter was applied — no case was dropped for redundancy, for disagreement with its verdict, or for duplicating an existing vector. Deduplication against the existing corpus is deliberately NOT done here.", - "refresh": "python3 import_jsts_build.py — re-extracts from a clone of the upstream repo at the sha above. Bump the sha here and in that script together; verify with import_jsts_verify.py." - }, - { - "provenance_type": "extracted_from_source", - "step": "P036 S3", - "repo": "https://github.com/golang/go", - "repo_url": "https://github.com/golang/go", - "subdirectory": "src/time/", - "files": [ - "src/time/format_test.go" - ], - "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", - "commit_date": "2026-07-07T19:29:04Z", - "commit_sha_tag": "go1.26.5", - "license": "BSD-3-Clause", - "license_holder": "Copyright 2009 The Go Authors.", - "license_file": "LICENSE.go-stdlib", - "additional_grant_file": "PATENTS.go-stdlib", - "extraction_rule": "A Go test table row is extracted ONLY if its Go layout argument is the `RFC3339` or `RFC3339Nano` constant. A row using any other layout makes no claim about RFC 3339 and is not extracted. The ACCEPT/REJECT verdict is read off what the enclosing Test function treats as a failure: a table whose test fails on `err != nil` asserts ACCEPT; a table whose test fails on `err == nil` asserts REJECT. Every vector records the exact source line so a reviewer can open the file at the pinned sha and see the literal. Provenance is `extracted_from_source`, never `imported_verbatim`, because Go tables are source code and not data: each vector is a transformation performed by this repository and therefore a claim about someone else's source.", - "refresh": "python3 extract_go_tables.py import-go-stdlib.json, then re-run this script. Bump the sha here and in the command together." - }, - { - "provenance_type": "hand_authored", - "step": "P036 S5", - "source_document": "RFC 3339", - "source_document_sha256": "9ab2b8864a85dca73a88f49b0927bc7bc85f596926e4fd1890905777924e700a", - "license_file": null, - "note": "No third-party licence obligation: every input literal was written for this corpus. See `hand_authoring_rule` in hand-authored-residual.json." - }, - { - "provenance_type": "derived_from_rfc_text", - "step": "P036 S1 (trimmed by T-P036-TRIM-53-2026-08-08)", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "license_file": null, - "note": "No third-party licence obligation. The Internet Society notice for RFC 3339 travels under `attribution.rfc_3339` below." - } - ], - "source_notes": { - "classification_rule": "Every verdict here is decided by the text of RFC 3339 and by nothing else. Each shipped scenario carries an `rfc_ref` naming the section that decides it, and the verdict must follow from that section's text rather than from any parser's behaviour. Where the RFC and a widely-deployed parser disagree, the RFC governs and the disagreement is recorded under `rfc_vs_implementation_findings` below. A literal whose verdict cannot be pinned to a section is NOT shipped as a scenario; it is recorded in `disputed` instead. Every input literal in this corpus was written for this corpus from the RFC's own grammar; none is copied from another project's test data.", - "rfc_vs_implementation_findings": [ - "Lower-case 't' and 'z' separators. The §5.6 NOTE states that the \"T\" and \"Z\" characters in this syntax may alternatively be lower case \"t\" or \"z\", so RFC 3339 ACCEPTS them; the same section adds that applications which GENERATE this format SHOULD use upper case, which is a generation constraint and not a parse constraint. Parsers built against a fixed upper-case layout string reject them. The three lowercase_* vectors here are ACCEPT on the RFC's authority, and this NOTE is the only thing that makes them ACCEPT rather than REJECT.", - "Leap second (time-second = 60). §5.7 permits time-second = 60 at the end of months in which a leap second occurs, and the §5.8 worked examples carry 1990-12-31T23:59:60Z and its -08:00 restatement verbatim, so RFC 3339 ACCEPTS. Parsers backed by a calendar type with no representable 61st second reject it as out of range. The four leap-second vectors here are ACCEPT on the RFC's authority.", - "Fractional digits beyond nine. §5.6 time-secfrac = \".\" 1*DIGIT sets a lower bound of one digit and no upper bound, so any digit count is ACCEPT as grammar. Implementations backed by a fixed nanosecond field truncate at nine, and implementations that accumulate the fraction into a signed 64-bit integer before scaling overflow at nineteen digits. Neither behaviour is encoded in these verdicts; secfrac_10_digits and secfrac_19_digits_exceeds_int64 exercise both boundaries." - ], - "known_go_vs_rfc_divergences": [ - { - "id": 1, - "divergence": "lower-case 't' and 'z' separators", - "rfc_3339": "ACCEPTS — §5.6 NOTE permits lower case 't' and 'z'", - "go": "REJECTS", - "affects_vectors_here": "none — no table row in this extraction uses a lower-case separator" - }, - { - "id": 2, - "divergence": "leap second value 60 in the seconds field", - "rfc_3339": "ACCEPTS — §5.7 permits :60 for a positive leap second", - "go": "REJECTS — 'second out of range'", - "affects_vectors_here": "none directly; the nearest row, parseErrorTests :67, is out of range under both" - }, - { - "id": 3, - "divergence": "more than 9 fractional-second digits", - "rfc_3339": "ACCEPTS — §5.6 time-secfrac is '.' 1*DIGIT with no upper bound", - "go": "ACCEPTS but TRUNCATES to nanosecond precision", - "affects_vectors_here": "the longFractionalDigitsTests rows tagged `go-truncates`" - } - ], - "json_schema_verdict_authority_warning": "THE `tests` BLOCK ON EVERY SCENARIO IS A MECHANICAL RESTATEMENT OF THE UPSTREAM `valid` BOOLEAN AND IS NOT THIS PROJECT'S VERDICT. It was produced by the rule `valid:true -> parse:true` / `valid:false -> parse:{success:false}` with no judgement applied. The authoritative, unmodified upstream value is carried separately at `upstream.valid` on each scenario. These verdicts have NOT been adjudicated against the text of RFC 3339; that adjudication is a separate step. Scenarios are tagged `unadjudicated` for that reason.", - "json_schema_no_rfc_ref_note": "Scenarios in this file deliberately carry NO `rfc_ref` key, unlike expires-rfc3339.json. Assigning an RFC 3339 section to an imported verdict is an act of adjudication, not of import, and is out of scope for the importing step.", - "hand_authoring_rule": { - "provenance": "hand_authored", - "rfc_text_sha256": "9ab2b8864a85dca73a88f49b0927bc7bc85f596926e4fd1890905777924e700a", - "rfc_text_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "rfc_text_retrieved_on": "2026-08-09", - "not_fabricated": "Issue #111's scope line reads `Do NOT propose fabricated vectors.` These are not fabricated. Each input below is (a) an edge case named in #111's own list or routed by a prior step of this order, (b) derived from the ABNF grammar and restrictions in the RFC 3339 text at the sha256 above, and (c) cited to the specific section that decides it. Every literal was checked against the 22 Go-derived literals deleted in the 2026-08-08 trim; none is a reuse.", - "boundary_rule": "A boundary vector without its counterpart tests nothing. Each boundary below is authored with both sides present -- either both sides are authored here, or the counterpart already ships in a named file and that file is named in the scenario description.", - "deliberately_not_authored": "Classes already covered elsewhere, and questions the RFC does not decide, are listed in the S5 result document rather than given a verdict here. The `disputed` block of expires-rfc3339.json holds the undecided questions by design and is NOT overturned by this file." - } - }, - "counts": { - "scenarios_total": 228, - "by_provenance_type": { - "imported_verbatim": 139, - "extracted_from_source": 27, - "hand_authored": 4, - "derived_from_rfc_text": 58 - }, - "by_applies_to": { - "date-time": 116, - "full-date": 74, - "full-time": 38 - }, - "by_verdict": { - "ACCEPT": 83, - "REJECT": 145 - }, - "by_provenance_type_and_verdict": { - "imported_verbatim": { - "ACCEPT": 37, - "REJECT": 102 - }, - "extracted_from_source": { - "ACCEPT": 18, - "REJECT": 9 - }, - "hand_authored": { - "ACCEPT": 3, - "REJECT": 1 - }, - "derived_from_rfc_text": { - "ACCEPT": 25, - "REJECT": 33 - } - }, - "inputs_considered": 235, - "excluded_unresolved_provenance": 1, - "duplicates_dropped": 6, - "disputed_not_shipped": 5 - }, - "attribution": { - "rfc_3339": { - "source": "RFC 3339, \"Date and Time on the Internet: Timestamps\", G. Klyne, C. Newman, July 2002.", - "retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "retrieved_on": "2026-08-07", - "notice": "Copyright (C) The Internet Society (2002). All Rights Reserved. This document and translations of it may be copied and furnished to others, and derivative works that comment on or otherwise explain it or assist in its implementation may be prepared, copied, published and distributed, in whole or in part, without restriction of any kind, provided that the above copyright notice and this paragraph are included on all such copies and derivative works." - }, - "json_schema_test_suite": { - "source": "JSON Schema Test Suite, json-schema-org/JSON-Schema-Test-Suite.", - "retrieved_from": "https://github.com/json-schema-org/JSON-Schema-Test-Suite", - "commit": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "retrieved_on": "2026-08-09", - "license": "MIT License, Copyright (c) 2012 Julian Berman — see LICENSE.json-schema-test-suite in this directory.", - "notice": "Do not hand-edit these scenarios. To refresh, re-import from the upstream repo and bump the commit reference above." - }, - "go_stdlib": { - "source": "The Go Programming Language standard library, src/time/format_test.go", - "repo": "https://github.com/golang/go", - "sha": "c19862e5f8415b4f24b189d065ed739517c548ba", - "sha_is_tag": "go1.26.5", - "path": "src/time/format_test.go", - "license": "BSD-3-Clause — see LICENSE.go-stdlib in this directory", - "copyright": "Copyright 2009 The Go Authors.", - "additional_grant": "See PATENTS.go-stdlib in this directory.", - "per_file_header": "// Copyright 2009 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.", - "note": "Do not hand-edit this file. To refresh, re-run extract_go_tables.py against a newer pinned sha and bump the sha recorded above." - } - }, - "scenarios": [ - { - "name": "jsts_date_time_001", - "description": "a valid date-time string", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date-time", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "date-time", - "input": "1963-06-19T08:30:06.283185Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date-time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of date-time strings", - "upstream_case_description": "a valid date-time string" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_date_time_002", - "description": "a valid date-time string without second fraction", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date-time", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "date-time", - "input": "1963-06-19T08:30:06Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date-time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of date-time strings", - "upstream_case_description": "a valid date-time string without second fraction" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_date_time_003", - "description": "a valid date-time string with plus offset", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date-time", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "date-time", - "input": "1937-01-01T12:00:27.87+00:20", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date-time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of date-time strings", - "upstream_case_description": "a valid date-time string with plus offset" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_date_time_004", - "description": "a valid date-time string with minus offset", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date-time", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "date-time", - "input": "1990-12-31T15:59:50.123-08:00", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date-time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of date-time strings", - "upstream_case_description": "a valid date-time string with minus offset" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_date_time_005", - "description": "a valid date-time with a leap second, UTC", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date-time", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "date-time", - "input": "1998-12-31T23:59:60Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date-time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of date-time strings", - "upstream_case_description": "a valid date-time with a leap second, UTC" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_date_time_006", - "description": "a valid date-time with a leap second, with minus offset", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date-time", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "date-time", - "input": "1998-12-31T15:59:60.123-08:00", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date-time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of date-time strings", - "upstream_case_description": "a valid date-time with a leap second, with minus offset" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_date_time_007", - "description": "an invalid date-time past leap second, UTC", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "date-time", - "input": "1998-12-31T23:59:61Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date-time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date-time strings", - "upstream_case_description": "an invalid date-time past leap second, UTC" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_time_008", - "description": "an invalid date-time with leap second on a wrong minute, UTC", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "date-time", - "input": "1998-12-31T23:58:60Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date-time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date-time strings", - "upstream_case_description": "an invalid date-time with leap second on a wrong minute, UTC" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_time_009", - "description": "an invalid date-time with leap second on a wrong hour, UTC", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "date-time", - "input": "1998-12-31T22:59:60Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date-time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date-time strings", - "upstream_case_description": "an invalid date-time with leap second on a wrong hour, UTC" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_time_010", - "description": "an invalid day in date-time string", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "date-time", - "input": "1990-02-31T15:59:59.123-08:00", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date-time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date-time strings", - "upstream_case_description": "an invalid day in date-time string" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_time_011", - "description": "an invalid offset in date-time string", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "date-time", - "input": "1990-12-31T15:59:59-24:00", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date-time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date-time strings", - "upstream_case_description": "an invalid offset in date-time string" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_time_012", - "description": "an invalid closing Z after time-zone offset", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "date-time", - "input": "1963-06-19T08:30:06.28123+01:00Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date-time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date-time strings", - "upstream_case_description": "an invalid closing Z after time-zone offset" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_time_013", - "description": "an invalid hour in date-time string", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "date-time", - "input": "1990-12-31T24:00:00Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date-time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date-time strings", - "upstream_case_description": "an invalid hour in date-time string" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_time_014", - "description": "an invalid minute in date-time string", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "date-time", - "input": "1990-12-31T15:60:00Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date-time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date-time strings", - "upstream_case_description": "an invalid minute in date-time string" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_time_015", - "description": "an invalid offset minute in date-time string", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "date-time", - "input": "1990-12-31T10:00:00+10:60", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date-time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date-time strings", - "upstream_case_description": "an invalid offset minute in date-time string" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_time_016", - "description": "an invalid date-time string", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "date-time", - "input": "06/19/1963 08:30:06 PST", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date-time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date-time strings", - "upstream_case_description": "an invalid date-time string" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_time_017", - "description": "case-insensitive T and Z", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date-time", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "date-time", - "input": "1963-06-19t08:30:06.283185z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date-time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of date-time strings", - "upstream_case_description": "case-insensitive T and Z" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_date_time_018", - "description": "only RFC3339 not all of ISO 8601 are valid", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "date-time", - "input": "2013-350T01:01:01", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date-time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date-time strings", - "upstream_case_description": "only RFC3339 not all of ISO 8601 are valid" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_time_019", - "description": "invalid non-padded month dates", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "date-time", - "input": "1963-6-19T08:30:06.283185Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date-time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date-time strings", - "upstream_case_description": "invalid non-padded month dates" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_time_020", - "description": "invalid non-padded day dates", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "date-time", - "input": "1963-06-1T08:30:06.283185Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date-time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date-time strings", - "upstream_case_description": "invalid non-padded day dates" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_time_021", - "description": "invalid non-ASCII '৪' (a Bengali 4) in date portion", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "date-time", - "input": "1963-06-1৪T00:00:00Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date-time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date-time strings", - "upstream_case_description": "invalid non-ASCII '৪' (a Bengali 4) in date portion" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_time_022", - "description": "invalid non-ASCII '৪' (a Bengali 4) in time portion", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "date-time", - "input": "1963-06-11T0৪:00:00Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date-time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date-time strings", - "upstream_case_description": "invalid non-ASCII '৪' (a Bengali 4) in time portion" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_time_023", - "description": "invalid extended year", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "date-time", - "input": "+11963-06-19T08:30:06.283185Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date-time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date-time strings", - "upstream_case_description": "invalid extended year" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_time_024", - "description": "a numeric offset without minutes is invalid", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "date-time", - "input": "1985-04-12T23:20:50+01", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date-time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date-time strings", - "upstream_case_description": "a numeric offset without minutes is invalid" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_time_025", - "description": "hour 24 is invalid even with a leap second", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "date-time", - "input": "2016-12-31T24:59:60+01:00", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date-time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date-time strings", - "upstream_case_description": "hour 24 is invalid even with a leap second" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_time_026", - "description": "a second fraction of fifteen nines is valid", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date-time", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "date-time", - "input": "1985-04-12T00:59:59.999999999999999Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date-time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of date-time strings", - "upstream_case_description": "a second fraction of fifteen nines is valid" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_date_time_027", - "description": "a trailing newline is invalid", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "date-time", - "input": "1985-04-12T23:20:50Z\n", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date-time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date-time strings", - "upstream_case_description": "a trailing newline is invalid" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_001", - "description": "a valid date string", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "1963-06-19", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "a valid date string" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_date_002", - "description": "a valid date string with 31 days in January", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-01-31", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "a valid date string with 31 days in January" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_date_003", - "description": "a invalid date string with 32 days in January", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-01-32", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "a invalid date string with 32 days in January" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_004", - "description": "a valid date string with 28 days in February (normal)", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2021-02-28", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "a valid date string with 28 days in February (normal)" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_date_005", - "description": "a invalid date string with 30 days in February (leap)", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-02-30", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "a invalid date string with 30 days in February (leap)" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_006", - "description": "a valid date string with 31 days in March", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-03-31", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "a valid date string with 31 days in March" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_date_007", - "description": "a invalid date string with 32 days in March", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-03-32", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "a invalid date string with 32 days in March" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_008", - "description": "a valid date string with 30 days in April", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-04-30", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "a valid date string with 30 days in April" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_date_009", - "description": "a invalid date string with 31 days in April", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-04-31", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "a invalid date string with 31 days in April" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_010", - "description": "a valid date string with 31 days in May", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-05-31", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "a valid date string with 31 days in May" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_date_011", - "description": "a invalid date string with 32 days in May", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-05-32", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "a invalid date string with 32 days in May" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_012", - "description": "a valid date string with 30 days in June", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-06-30", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "a valid date string with 30 days in June" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_date_013", - "description": "a invalid date string with 31 days in June", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-06-31", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "a invalid date string with 31 days in June" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_014", - "description": "a valid date string with 31 days in July", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-07-31", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "a valid date string with 31 days in July" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_date_015", - "description": "a invalid date string with 32 days in July", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-07-32", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "a invalid date string with 32 days in July" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_016", - "description": "a valid date string with 31 days in August", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-08-31", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "a valid date string with 31 days in August" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_date_017", - "description": "a invalid date string with 32 days in August", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-08-32", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "a invalid date string with 32 days in August" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_018", - "description": "a valid date string with 30 days in September", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-09-30", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "a valid date string with 30 days in September" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_date_019", - "description": "a invalid date string with 31 days in September", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-09-31", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "a invalid date string with 31 days in September" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_020", - "description": "a valid date string with 31 days in October", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-10-31", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "a valid date string with 31 days in October" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_date_021", - "description": "a invalid date string with 32 days in October", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-10-32", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "a invalid date string with 32 days in October" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_022", - "description": "a valid date string with 30 days in November", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-11-30", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "a valid date string with 30 days in November" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_date_023", - "description": "a invalid date string with 31 days in November", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-11-31", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "a invalid date string with 31 days in November" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_024", - "description": "a valid date string with 31 days in December", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-12-31", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "a valid date string with 31 days in December" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_date_025", - "description": "a invalid date string with 32 days in December", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-12-32", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "a invalid date string with 32 days in December" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_026", - "description": "an invalid date string", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "06/19/1963", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "an invalid date string" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_027", - "description": "only RFC3339 not all of ISO 8601 are valid", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2013-350", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "only RFC3339 not all of ISO 8601 are valid" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_028", - "description": "non-padded month dates are not valid", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "1998-1-20", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "non-padded month dates are not valid" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_029", - "description": "non-padded day dates are not valid", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "1998-01-1", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "non-padded day dates are not valid" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_030", - "description": "invalid month", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "1998-13-01", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid month" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_031", - "description": "2021 is not a leap year", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2021-02-29", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "2021 is not a leap year" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_032", - "description": "2020 is a leap year", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-02-29", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "2020 is a leap year" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_date_033", - "description": "invalid non-ASCII '৪' (a Bengali 4)", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "1963-06-1৪", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid non-ASCII '৪' (a Bengali 4)" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_034", - "description": "invalid: non-ASCII Bengali digit in month field", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-0৪-01", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: non-ASCII Bengali digit in month field" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_035", - "description": "ISO8601 / non-RFC3339: YYYYMMDD without dashes (2023-03-28)", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "20230328", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "ISO8601 / non-RFC3339: YYYYMMDD without dashes (2023-03-28)" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_036", - "description": "ISO8601 / non-RFC3339: week number implicit day of week (2023-01-02)", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2023-W01", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "ISO8601 / non-RFC3339: week number implicit day of week (2023-01-02)" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_037", - "description": "ISO8601 / non-RFC3339: week number with day of week (2023-03-28)", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2023-W13-2", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "ISO8601 / non-RFC3339: week number with day of week (2023-03-28)" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_038", - "description": "ISO8601 / non-RFC3339: week number rollover to next year (2023-01-01)", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2022W527", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "ISO8601 / non-RFC3339: week number rollover to next year (2023-01-01)" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_039", - "description": "an invalid time string in date-time format", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-11-28T23:55:45Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "an invalid time string in date-time format" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_040", - "description": "century year 0100 is not a leap year", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "0100-02-29", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "century year 0100 is not a leap year" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_041", - "description": "century year 0400 is a leap year", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "0400-02-29", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "century year 0400 is a leap year" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_date_042", - "description": "century year 2100 is not a leap year", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2100-02-29", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "century year 2100 is not a leap year" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_043", - "description": "invalid: leading whitespace is not permitted", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": " 2024-01-15", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: leading whitespace is not permitted" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_044", - "description": "invalid: trailing whitespace is not permitted", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2024-01-15 ", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: trailing whitespace is not permitted" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_045", - "description": "invalid: month 00 is not valid per date-month range 01-12", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2024-00-15", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: month 00 is not valid per date-month range 01-12" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_046", - "description": "invalid: day 00 is not valid per date-mday minimum of 01", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2024-01-00", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: day 00 is not valid per date-mday minimum of 01" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_047", - "description": "invalid: empty string", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: empty string" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_048", - "description": "invalid: embedded whitespace between year and month", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020 -01-01", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: embedded whitespace between year and month" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_049", - "description": "invalid: trailing character after valid full-date", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-01-01X", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: trailing character after valid full-date" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_050", - "description": "invalid: trailing Z after full-date", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-01-01Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: trailing Z after full-date" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_051", - "description": "invalid: full-date followed by space and time component", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-01-01 00:00:00Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: full-date followed by space and time component" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_052", - "description": "valid: four-digit year 0001", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "0001-01-01", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "valid: four-digit year 0001" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_date_053", - "description": "invalid: two-digit year (N-2 digits)", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "20-01-01", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: two-digit year (N-2 digits)" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_054", - "description": "invalid: three-digit year (N-1 digits)", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "998-01-01", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: three-digit year (N-1 digits)" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_055", - "description": "invalid: five-digit year (N+1 digits)", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "12020-01-01", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: five-digit year (N+1 digits)" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_056", - "description": "invalid: positive sign prefix on year", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "+2020-01-01", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: positive sign prefix on year" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_057", - "description": "invalid: negative sign prefix on year", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "-2020-01-01", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: negative sign prefix on year" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_058", - "description": "invalid: non-ASCII Bengali digit in year field", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "২020-01-01", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: non-ASCII Bengali digit in year field" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_059", - "description": "invalid: alphabetic characters in year field", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "YYYY-01-01", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: alphabetic characters in year field" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_060", - "description": "invalid: three-digit month (N+1 digits)", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-001-01", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: three-digit month (N+1 digits)" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_061", - "description": "invalid: alphabetic characters in month field", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-MM-01", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: alphabetic characters in month field" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_062", - "description": "invalid: three-digit day (N+1 digits)", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-01-001", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: three-digit day (N+1 digits)" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_063", - "description": "invalid: alphabetic characters in day field", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-01-DD", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: alphabetic characters in day field" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_064", - "description": "invalid: colon separators", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020:01:01", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: colon separators" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_065", - "description": "invalid: dot separators", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020.01.01", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: dot separators" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_066", - "description": "invalid: space separators", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020 01 01", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: space separators" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_067", - "description": "invalid: mixed slash and hyphen separators", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-01/01", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: mixed slash and hyphen separators" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_068", - "description": "invalid: duplicated first hyphen", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020--01-01", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: duplicated first hyphen" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_069", - "description": "invalid: duplicated second hyphen", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-01--01", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: duplicated second hyphen" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_071", - "description": "invalid: year field numerically larger than a 32-bit signed integer", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2147483648-01-01", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: year field numerically larger than a 32-bit signed integer" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_072", - "description": "invalid: character just above '9' as the second digit of the day", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-01-0:", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: character just above '9' as the second digit of the day" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_073", - "description": "invalid: non-ASCII digit leaving ten UTF-8 bytes but eight characters", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "২0-01-01", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: non-ASCII digit leaving ten UTF-8 bytes but eight characters" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_074", - "description": "invalid: en dash separators instead of hyphen-minus", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020–01–01", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: en dash separators instead of hyphen-minus" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_date_075", - "description": "invalid: NUL character after an otherwise valid full-date", - "tags": [ - "imported", - "json-schema-test-suite", - "format-date", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-date", - "input": "2020-01-01\u0000", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/date.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of date strings", - "upstream_case_description": "invalid: NUL character after an otherwise valid full-date" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_time_001", - "description": "a valid time string", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "08:30:06Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "a valid time string" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_time_002", - "description": "invalid time string with extra leading zeros", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "008:030:006Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "invalid time string with extra leading zeros" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_time_003", - "description": "invalid time string with no leading zero for single digit", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "8:3:6Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "invalid time string with no leading zero for single digit" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_time_004", - "description": "hour, minute, second must be two digits", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "8:0030:6Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "hour, minute, second must be two digits" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_time_005", - "description": "a valid time string with leap second, Zulu", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "23:59:60Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "a valid time string with leap second, Zulu" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_time_006", - "description": "invalid leap second, Zulu (wrong hour)", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "22:59:60Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "invalid leap second, Zulu (wrong hour)" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_time_007", - "description": "invalid leap second, Zulu (wrong minute)", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "23:58:60Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "invalid leap second, Zulu (wrong minute)" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_time_008", - "description": "valid leap second, zero time-offset", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "23:59:60+00:00", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "valid leap second, zero time-offset" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_time_009", - "description": "invalid leap second, zero time-offset (wrong hour)", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "22:59:60+00:00", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "invalid leap second, zero time-offset (wrong hour)" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_time_010", - "description": "invalid leap second, zero time-offset (wrong minute)", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "23:58:60+00:00", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "invalid leap second, zero time-offset (wrong minute)" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_time_011", - "description": "valid leap second, positive time-offset", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "01:29:60+01:30", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "valid leap second, positive time-offset" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_time_012", - "description": "valid leap second, large positive time-offset", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "23:29:60+23:30", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "valid leap second, large positive time-offset" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_time_013", - "description": "invalid leap second, positive time-offset (wrong hour)", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "23:59:60+01:00", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "invalid leap second, positive time-offset (wrong hour)" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_time_014", - "description": "invalid leap second, positive time-offset (wrong minute)", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "23:59:60+00:30", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "invalid leap second, positive time-offset (wrong minute)" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_time_015", - "description": "valid leap second, negative time-offset", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "15:59:60-08:00", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "valid leap second, negative time-offset" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_time_016", - "description": "valid leap second, large negative time-offset", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "00:29:60-23:30", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "valid leap second, large negative time-offset" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_time_017", - "description": "invalid leap second, negative time-offset (wrong hour)", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "23:59:60-01:00", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "invalid leap second, negative time-offset (wrong hour)" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_time_018", - "description": "invalid leap second, negative time-offset (wrong minute)", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "23:59:60-00:30", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "invalid leap second, negative time-offset (wrong minute)" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_time_019", - "description": "a valid time string with second fraction", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "23:20:50.52Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "a valid time string with second fraction" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_time_020", - "description": "a valid time string with precise second fraction", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "08:30:06.283185Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "a valid time string with precise second fraction" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_time_021", - "description": "a valid time string with plus offset", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "08:30:06+00:20", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "a valid time string with plus offset" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_time_022", - "description": "a valid time string with minus offset", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "08:30:06-08:00", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "a valid time string with minus offset" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_time_023", - "description": "time with unknown local offset is valid", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "12:34:56-00:00", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "time with unknown local offset is valid" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_time_024", - "description": "hour, minute in time-offset must be two digits", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "08:30:06-8:000", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "hour, minute in time-offset must be two digits" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_time_025", - "description": "a valid time string with case-insensitive Z", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-valid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "08:30:06z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": true, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "a valid time string with case-insensitive Z" - }, - "tests": { - "parse": true - } - }, - { - "name": "jsts_time_026", - "description": "an invalid time string with invalid hour", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "24:00:00Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "an invalid time string with invalid hour" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_time_027", - "description": "an invalid time string with invalid minute", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "00:60:00Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "an invalid time string with invalid minute" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_time_028", - "description": "an invalid time string with invalid second", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "00:00:61Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "an invalid time string with invalid second" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_time_031", - "description": "an invalid time string with invalid time numoffset hour", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "01:02:03+24:00", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "an invalid time string with invalid time numoffset hour" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_time_032", - "description": "an invalid time string with invalid time numoffset minute", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "01:02:03+00:60", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "an invalid time string with invalid time numoffset minute" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_time_033", - "description": "an invalid time string with invalid time with both Z and numoffset", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "01:02:03Z+00:30", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "an invalid time string with invalid time with both Z and numoffset" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_time_034", - "description": "an invalid offset indicator", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "08:30:06 PST", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "an invalid offset indicator" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_time_035", - "description": "only RFC3339 not all of ISO 8601 are valid", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "01:01:01,1111", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "only RFC3339 not all of ISO 8601 are valid" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_time_036", - "description": "no time offset", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "12:00:00", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "no time offset" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_time_037", - "description": "no time offset with second fraction", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "12:00:00.52", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "no time offset with second fraction" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_time_038", - "description": "invalid non-ASCII '২' (a Bengali 2)", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "1২:00:00Z", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "invalid non-ASCII '২' (a Bengali 2)" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_time_039", - "description": "offset not starting with plus or minus", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "08:30:06#00:20", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "offset not starting with plus or minus" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jsts_time_040", - "description": "contains letters", - "tags": [ - "imported", - "json-schema-test-suite", - "format-time", - "upstream-invalid", - "unadjudicated" - ], - "applies_to": "full-time", - "input": "ab:cd:ef", - "source": "json-schema-test-suite", - "provenance": { - "type": "imported_verbatim", - "source_repo": "json-schema-org/JSON-Schema-Test-Suite", - "commit_sha": "15fe552d6cf76e29cc8165306fb6a72503fd360b", - "commit_date": "2026-08-06T22:48:41-07:00", - "file": "tests/draft2020-12/optional/format/time.json", - "license": "MIT", - "license_file": "LICENSE.json-schema-test-suite", - "verdict_authority": "json-schema-test-suite", - "upstream_valid": false, - "upstream_group_description": "validation of time strings", - "upstream_case_description": "contains letters" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "go_parsetests_2010_02_04t21_00_57_08_00", - "description": "Go stdlib parseTests row with layout time.RFC3339. TestParse fails the test if Parse returns an error, so the table asserts Go accepts this input under the RFC3339 layout.", - "tags": [ - "accept", - "go-stdlib", - "parse-table" - ], - "applies_to": "date-time", - "input": "2010-02-04T21:00:57-08:00", - "source": "go-stdlib-format_test.go", - "provenance": { - "type": "extracted_from_source", - "source_repo": "github.com/golang/go", - "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", - "commit_sha_tag": "go1.26.5", - "file": "src/time/format_test.go", - "line": 274, - "table": "parseTests", - "license": "BSD-3-Clause", - "license_file": "LICENSE.go-stdlib", - "additional_grant_file": "PATENTS.go-stdlib", - "verdict_authority": "go-stdlib" - }, - "tests": { - "parse": true - } - }, - { - "name": "go_parsetests_2010_02_04t21_00_57_012345678_08_00", - "description": "Go stdlib parseTests row with layout time.RFC3339. TestParse fails the test if Parse returns an error, so the table asserts Go accepts this input under the RFC3339 layout.", - "tags": [ - "accept", - "go-stdlib", - "parse-table" - ], - "applies_to": "date-time", - "input": "2010-02-04T21:00:57.012345678-08:00", - "source": "go-stdlib-format_test.go", - "provenance": { - "type": "extracted_from_source", - "source_repo": "github.com/golang/go", - "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", - "commit_sha_tag": "go1.26.5", - "file": "src/time/format_test.go", - "line": 283, - "table": "parseTests", - "license": "BSD-3-Clause", - "license_file": "LICENSE.go-stdlib", - "additional_grant_file": "PATENTS.go-stdlib", - "verdict_authority": "go-stdlib" - }, - "tests": { - "parse": true - } - }, - { - "name": "go_parseerror_2006_01_02t15_04_05z07_00", - "description": "Go stdlib parseErrorTests row with layout time.RFC3339. TestParseErrors fails the test if Parse returns no error, so the table asserts Go rejects this input under the RFC3339 layout. Go's expected error substring: 'extra text: \"07:00\"'", - "tags": [ - "reject", - "go-stdlib", - "parse-error-table" - ], - "applies_to": "date-time", - "input": "2006-01-02T15:04:05Z07:00", - "source": "go-stdlib-format_test.go", - "provenance": { - "type": "extracted_from_source", - "source_repo": "github.com/golang/go", - "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", - "commit_sha_tag": "go1.26.5", - "file": "src/time/format_test.go", - "line": 652, - "table": "parseErrorTests", - "license": "BSD-3-Clause", - "license_file": "LICENSE.go-stdlib", - "additional_grant_file": "PATENTS.go-stdlib", - "verdict_authority": "go-stdlib" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "go_parseerror_2006_01_02t15_04_abc", - "description": "Go stdlib parseErrorTests row with layout time.RFC3339. TestParseErrors fails the test if Parse returns no error, so the table asserts Go rejects this input under the RFC3339 layout. Go's expected error substring: 'cannot parse \"_abc\" as \":\"'", - "tags": [ - "reject", - "go-stdlib", - "parse-error-table" - ], - "applies_to": "date-time", - "input": "2006-01-02T15:04_abc", - "source": "go-stdlib-format_test.go", - "provenance": { - "type": "extracted_from_source", - "source_repo": "github.com/golang/go", - "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", - "commit_sha_tag": "go1.26.5", - "file": "src/time/format_test.go", - "line": 653, - "table": "parseErrorTests", - "license": "BSD-3-Clause", - "license_file": "LICENSE.go-stdlib", - "additional_grant_file": "PATENTS.go-stdlib", - "verdict_authority": "go-stdlib" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "go_parseerror_2006_01_02t15_04_05_abc", - "description": "Go stdlib parseErrorTests row with layout time.RFC3339. TestParseErrors fails the test if Parse returns no error, so the table asserts Go rejects this input under the RFC3339 layout. Go's expected error substring: 'cannot parse \"_abc\" as \"Z07:00\"'", - "tags": [ - "reject", - "go-stdlib", - "parse-error-table" - ], - "applies_to": "date-time", - "input": "2006-01-02T15:04:05_abc", - "source": "go-stdlib-format_test.go", - "provenance": { - "type": "extracted_from_source", - "source_repo": "github.com/golang/go", - "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", - "commit_sha_tag": "go1.26.5", - "file": "src/time/format_test.go", - "line": 654, - "table": "parseErrorTests", - "license": "BSD-3-Clause", - "license_file": "LICENSE.go-stdlib", - "additional_grant_file": "PATENTS.go-stdlib", - "verdict_authority": "go-stdlib" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "go_parseerror_2006_01_02t15_04_05z_abc", - "description": "Go stdlib parseErrorTests row with layout time.RFC3339. TestParseErrors fails the test if Parse returns no error, so the table asserts Go rejects this input under the RFC3339 layout. Go's expected error substring: 'extra text: \"_abc\"'", - "tags": [ - "reject", - "go-stdlib", - "parse-error-table" - ], - "applies_to": "date-time", - "input": "2006-01-02T15:04:05Z_abc", - "source": "go-stdlib-format_test.go", - "provenance": { - "type": "extracted_from_source", - "source_repo": "github.com/golang/go", - "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", - "commit_sha_tag": "go1.26.5", - "file": "src/time/format_test.go", - "line": 655, - "table": "parseErrorTests", - "license": "BSD-3-Clause", - "license_file": "LICENSE.go-stdlib", - "additional_grant_file": "PATENTS.go-stdlib", - "verdict_authority": "go-stdlib" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "go_parseerror_2010_02_04t21_00_67_012345678_08_00", - "description": "Go stdlib parseErrorTests row with layout time.RFC3339. TestParseErrors fails the test if Parse returns no error, so the table asserts Go rejects this input under the RFC3339 layout. Go's expected error substring: 'second out of range'", - "tags": [ - "reject", - "go-stdlib", - "parse-error-table" - ], - "applies_to": "date-time", - "input": "2010-02-04T21:00:67.012345678-08:00", - "source": "go-stdlib-format_test.go", - "provenance": { - "type": "extracted_from_source", - "source_repo": "github.com/golang/go", - "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", - "commit_sha_tag": "go1.26.5", - "file": "src/time/format_test.go", - "line": 657, - "table": "parseErrorTests", - "license": "BSD-3-Clause", - "license_file": "LICENSE.go-stdlib", - "additional_grant_file": "PATENTS.go-stdlib", - "verdict_authority": "go-stdlib" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "go_parseerror_0000_01_01t00_00_0_00_00", - "description": "Go stdlib parseErrorTests row with layout time.RFC3339. TestParseErrors fails the test if Parse returns no error, so the table asserts Go rejects this input under the RFC3339 layout. Go's expected error substring: 'cannot parse \".0+00:00\" as \"05\"'", - "tags": [ - "reject", - "go-stdlib", - "parse-error-table" - ], - "applies_to": "date-time", - "input": "0000-01-01T00:00:.0+00:00", - "source": "go-stdlib-format_test.go", - "provenance": { - "type": "extracted_from_source", - "source_repo": "github.com/golang/go", - "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", - "commit_sha_tag": "go1.26.5", - "file": "src/time/format_test.go", - "line": 659, - "table": "parseErrorTests", - "license": "BSD-3-Clause", - "license_file": "LICENSE.go-stdlib", - "additional_grant_file": "PATENTS.go-stdlib", - "verdict_authority": "go-stdlib" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "go_parseerror_double_quote_char", - "description": "Go stdlib parseErrorTests row with layout time.RFC3339. TestParseErrors fails the test if Parse returns no error, so the table asserts Go rejects this input under the RFC3339 layout. Go's expected error substring: 'cannot parse \"\\\\\"\" as \"2006\"'", - "tags": [ - "reject", - "go-stdlib", - "parse-error-table" - ], - "applies_to": "date-time", - "input": "\"", - "source": "go-stdlib-format_test.go", - "provenance": { - "type": "extracted_from_source", - "source_repo": "github.com/golang/go", - "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", - "commit_sha_tag": "go1.26.5", - "file": "src/time/format_test.go", - "line": 670, - "table": "parseErrorTests", - "license": "BSD-3-Clause", - "license_file": "LICENSE.go-stdlib", - "additional_grant_file": "PATENTS.go-stdlib", - "verdict_authority": "go-stdlib" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "go_parseerror_0000_01_01t00_00_00_00_0", - "description": "Go stdlib parseErrorTests row with layout time.RFC3339. TestParseErrors fails the test if Parse returns no error, so the table asserts Go rejects this input under the RFC3339 layout. Go's expected error substring: 'cannot parse \"+00:+0\" as \"Z07:00\"'", - "tags": [ - "reject", - "go-stdlib", - "parse-error-table" - ], - "applies_to": "date-time", - "input": "0000-01-01T00:00:00+00:+0", - "source": "go-stdlib-format_test.go", - "provenance": { - "type": "extracted_from_source", - "source_repo": "github.com/golang/go", - "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", - "commit_sha_tag": "go1.26.5", - "file": "src/time/format_test.go", - "line": 673, - "table": "parseErrorTests", - "license": "BSD-3-Clause", - "license_file": "LICENSE.go-stdlib", - "additional_grant_file": "PATENTS.go-stdlib", - "verdict_authority": "go-stdlib" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "go_parseerror_0000_01_01t00_00_00_0_00", - "description": "Go stdlib parseErrorTests row with layout time.RFC3339. TestParseErrors fails the test if Parse returns no error, so the table asserts Go rejects this input under the RFC3339 layout. Go's expected error substring: 'cannot parse \"+-0:00\" as \"Z07:00\"'", - "tags": [ - "reject", - "go-stdlib", - "parse-error-table" - ], - "applies_to": "date-time", - "input": "0000-01-01T00:00:00+-0:00", - "source": "go-stdlib-format_test.go", - "provenance": { - "type": "extracted_from_source", - "source_repo": "github.com/golang/go", - "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", - "commit_sha_tag": "go1.26.5", - "file": "src/time/format_test.go", - "line": 674, - "table": "parseErrorTests", - "license": "BSD-3-Clause", - "license_file": "LICENSE.go-stdlib", - "additional_grant_file": "PATENTS.go-stdlib", - "verdict_authority": "go-stdlib" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "go_longfrac_9digits_000000000", - "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 9 fractional digits; Go's expected Nanosecond() is 0.", - "tags": [ - "accept", - "go-stdlib", - "secfrac" - ], - "applies_to": "date-time", - "input": "2021-09-29T16:04:33.000000000Z", - "source": "go-stdlib-format_test.go", - "provenance": { - "type": "extracted_from_source", - "source_repo": "github.com/golang/go", - "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", - "commit_sha_tag": "go1.26.5", - "file": "src/time/format_test.go", - "line": 957, - "table": "longFractionalDigitsTests", - "license": "BSD-3-Clause", - "license_file": "LICENSE.go-stdlib", - "additional_grant_file": "PATENTS.go-stdlib", - "verdict_authority": "go-stdlib" - }, - "tests": { - "parse": true - } - }, - { - "name": "go_longfrac_9digits_000000001", - "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 9 fractional digits; Go's expected Nanosecond() is 1.", - "tags": [ - "accept", - "go-stdlib", - "secfrac" - ], - "applies_to": "date-time", - "input": "2021-09-29T16:04:33.000000001Z", - "source": "go-stdlib-format_test.go", - "provenance": { - "type": "extracted_from_source", - "source_repo": "github.com/golang/go", - "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", - "commit_sha_tag": "go1.26.5", - "file": "src/time/format_test.go", - "line": 958, - "table": "longFractionalDigitsTests", - "license": "BSD-3-Clause", - "license_file": "LICENSE.go-stdlib", - "additional_grant_file": "PATENTS.go-stdlib", - "verdict_authority": "go-stdlib" - }, - "tests": { - "parse": true - } - }, - { - "name": "go_longfrac_9digits_100000000", - "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 9 fractional digits; Go's expected Nanosecond() is 100000000.", - "tags": [ - "accept", - "go-stdlib", - "secfrac" - ], - "applies_to": "date-time", - "input": "2021-09-29T16:04:33.100000000Z", - "source": "go-stdlib-format_test.go", - "provenance": { - "type": "extracted_from_source", - "source_repo": "github.com/golang/go", - "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", - "commit_sha_tag": "go1.26.5", - "file": "src/time/format_test.go", - "line": 959, - "table": "longFractionalDigitsTests", - "license": "BSD-3-Clause", - "license_file": "LICENSE.go-stdlib", - "additional_grant_file": "PATENTS.go-stdlib", - "verdict_authority": "go-stdlib" - }, - "tests": { - "parse": true - } - }, - { - "name": "go_longfrac_9digits_100000001", - "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 9 fractional digits; Go's expected Nanosecond() is 100000001.", - "tags": [ - "accept", - "go-stdlib", - "secfrac" - ], - "applies_to": "date-time", - "input": "2021-09-29T16:04:33.100000001Z", - "source": "go-stdlib-format_test.go", - "provenance": { - "type": "extracted_from_source", - "source_repo": "github.com/golang/go", - "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", - "commit_sha_tag": "go1.26.5", - "file": "src/time/format_test.go", - "line": 960, - "table": "longFractionalDigitsTests", - "license": "BSD-3-Clause", - "license_file": "LICENSE.go-stdlib", - "additional_grant_file": "PATENTS.go-stdlib", - "verdict_authority": "go-stdlib" - }, - "tests": { - "parse": true - } - }, - { - "name": "go_longfrac_9digits_999999999", - "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 9 fractional digits; Go's expected Nanosecond() is 999999999.", - "tags": [ - "accept", - "go-stdlib", - "secfrac" - ], - "applies_to": "date-time", - "input": "2021-09-29T16:04:33.999999999Z", - "source": "go-stdlib-format_test.go", - "provenance": { - "type": "extracted_from_source", - "source_repo": "github.com/golang/go", - "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", - "commit_sha_tag": "go1.26.5", - "file": "src/time/format_test.go", - "line": 961, - "table": "longFractionalDigitsTests", - "license": "BSD-3-Clause", - "license_file": "LICENSE.go-stdlib", - "additional_grant_file": "PATENTS.go-stdlib", - "verdict_authority": "go-stdlib" - }, - "tests": { - "parse": true - } - }, - { - "name": "go_longfrac_9digits_012345678", - "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 9 fractional digits; Go's expected Nanosecond() is 12345678.", - "tags": [ - "accept", - "go-stdlib", - "secfrac" - ], - "applies_to": "date-time", - "input": "2021-09-29T16:04:33.012345678Z", - "source": "go-stdlib-format_test.go", - "provenance": { - "type": "extracted_from_source", - "source_repo": "github.com/golang/go", - "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", - "commit_sha_tag": "go1.26.5", - "file": "src/time/format_test.go", - "line": 962, - "table": "longFractionalDigitsTests", - "license": "BSD-3-Clause", - "license_file": "LICENSE.go-stdlib", - "additional_grant_file": "PATENTS.go-stdlib", - "verdict_authority": "go-stdlib" - }, - "tests": { - "parse": true - } - }, - { - "name": "go_longfrac_10digits_0000000000", - "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 10 fractional digits; Go's expected Nanosecond() is 0, i.e. Go ACCEPTS and TRUNCATES past 9 digits.", - "tags": [ - "accept", - "go-stdlib", - "secfrac", - "go-truncates" - ], - "applies_to": "date-time", - "input": "2021-09-29T16:04:33.0000000000Z", - "source": "go-stdlib-format_test.go", - "provenance": { - "type": "extracted_from_source", - "source_repo": "github.com/golang/go", - "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", - "commit_sha_tag": "go1.26.5", - "file": "src/time/format_test.go", - "line": 964, - "table": "longFractionalDigitsTests", - "license": "BSD-3-Clause", - "license_file": "LICENSE.go-stdlib", - "additional_grant_file": "PATENTS.go-stdlib", - "verdict_authority": "go-stdlib" - }, - "tests": { - "parse": true - } - }, - { - "name": "go_longfrac_10digits_0000000001", - "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 10 fractional digits; Go's expected Nanosecond() is 0, i.e. Go ACCEPTS and TRUNCATES past 9 digits.", - "tags": [ - "accept", - "go-stdlib", - "secfrac", - "go-truncates" - ], - "applies_to": "date-time", - "input": "2021-09-29T16:04:33.0000000001Z", - "source": "go-stdlib-format_test.go", - "provenance": { - "type": "extracted_from_source", - "source_repo": "github.com/golang/go", - "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", - "commit_sha_tag": "go1.26.5", - "file": "src/time/format_test.go", - "line": 965, - "table": "longFractionalDigitsTests", - "license": "BSD-3-Clause", - "license_file": "LICENSE.go-stdlib", - "additional_grant_file": "PATENTS.go-stdlib", - "verdict_authority": "go-stdlib" - }, - "tests": { - "parse": true - } - }, - { - "name": "go_longfrac_10digits_1000000000", - "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 10 fractional digits; Go's expected Nanosecond() is 100000000, i.e. Go ACCEPTS and TRUNCATES past 9 digits.", - "tags": [ - "accept", - "go-stdlib", - "secfrac", - "go-truncates" - ], - "applies_to": "date-time", - "input": "2021-09-29T16:04:33.1000000000Z", - "source": "go-stdlib-format_test.go", - "provenance": { - "type": "extracted_from_source", - "source_repo": "github.com/golang/go", - "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", - "commit_sha_tag": "go1.26.5", - "file": "src/time/format_test.go", - "line": 966, - "table": "longFractionalDigitsTests", - "license": "BSD-3-Clause", - "license_file": "LICENSE.go-stdlib", - "additional_grant_file": "PATENTS.go-stdlib", - "verdict_authority": "go-stdlib" - }, - "tests": { - "parse": true - } - }, - { - "name": "go_longfrac_10digits_1000000009", - "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 10 fractional digits; Go's expected Nanosecond() is 100000000, i.e. Go ACCEPTS and TRUNCATES past 9 digits.", - "tags": [ - "accept", - "go-stdlib", - "secfrac", - "go-truncates" - ], - "applies_to": "date-time", - "input": "2021-09-29T16:04:33.1000000009Z", - "source": "go-stdlib-format_test.go", - "provenance": { - "type": "extracted_from_source", - "source_repo": "github.com/golang/go", - "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", - "commit_sha_tag": "go1.26.5", - "file": "src/time/format_test.go", - "line": 967, - "table": "longFractionalDigitsTests", - "license": "BSD-3-Clause", - "license_file": "LICENSE.go-stdlib", - "additional_grant_file": "PATENTS.go-stdlib", - "verdict_authority": "go-stdlib" - }, - "tests": { - "parse": true - } - }, - { - "name": "go_longfrac_10digits_9999999999", - "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 10 fractional digits; Go's expected Nanosecond() is 999999999, i.e. Go ACCEPTS and TRUNCATES past 9 digits.", - "tags": [ - "accept", - "go-stdlib", - "secfrac", - "go-truncates" - ], - "applies_to": "date-time", - "input": "2021-09-29T16:04:33.9999999999Z", - "source": "go-stdlib-format_test.go", - "provenance": { - "type": "extracted_from_source", - "source_repo": "github.com/golang/go", - "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", - "commit_sha_tag": "go1.26.5", - "file": "src/time/format_test.go", - "line": 968, - "table": "longFractionalDigitsTests", - "license": "BSD-3-Clause", - "license_file": "LICENSE.go-stdlib", - "additional_grant_file": "PATENTS.go-stdlib", - "verdict_authority": "go-stdlib" - }, - "tests": { - "parse": true - } - }, - { - "name": "go_longfrac_10digits_0123456789", - "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 10 fractional digits; Go's expected Nanosecond() is 12345678, i.e. Go ACCEPTS and TRUNCATES past 9 digits.", - "tags": [ - "accept", - "go-stdlib", - "secfrac", - "go-truncates" - ], - "applies_to": "date-time", - "input": "2021-09-29T16:04:33.0123456789Z", - "source": "go-stdlib-format_test.go", - "provenance": { - "type": "extracted_from_source", - "source_repo": "github.com/golang/go", - "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", - "commit_sha_tag": "go1.26.5", - "file": "src/time/format_test.go", - "line": 969, - "table": "longFractionalDigitsTests", - "license": "BSD-3-Clause", - "license_file": "LICENSE.go-stdlib", - "additional_grant_file": "PATENTS.go-stdlib", - "verdict_authority": "go-stdlib" - }, - "tests": { - "parse": true - } - }, - { - "name": "go_longfrac_11digits_10000000000", - "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 11 fractional digits; Go's expected Nanosecond() is 100000000, i.e. Go ACCEPTS and TRUNCATES past 9 digits.", - "tags": [ - "accept", - "go-stdlib", - "secfrac", - "go-truncates" - ], - "applies_to": "date-time", - "input": "2021-09-29T16:04:33.10000000000Z", - "source": "go-stdlib-format_test.go", - "provenance": { - "type": "extracted_from_source", - "source_repo": "github.com/golang/go", - "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", - "commit_sha_tag": "go1.26.5", - "file": "src/time/format_test.go", - "line": 971, - "table": "longFractionalDigitsTests", - "license": "BSD-3-Clause", - "license_file": "LICENSE.go-stdlib", - "additional_grant_file": "PATENTS.go-stdlib", - "verdict_authority": "go-stdlib" - }, - "tests": { - "parse": true - } - }, - { - "name": "go_longfrac_11digits_00123456789", - "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 11 fractional digits; Go's expected Nanosecond() is 1234567, i.e. Go ACCEPTS and TRUNCATES past 9 digits.", - "tags": [ - "accept", - "go-stdlib", - "secfrac", - "go-truncates" - ], - "applies_to": "date-time", - "input": "2021-09-29T16:04:33.00123456789Z", - "source": "go-stdlib-format_test.go", - "provenance": { - "type": "extracted_from_source", - "source_repo": "github.com/golang/go", - "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", - "commit_sha_tag": "go1.26.5", - "file": "src/time/format_test.go", - "line": 972, - "table": "longFractionalDigitsTests", - "license": "BSD-3-Clause", - "license_file": "LICENSE.go-stdlib", - "additional_grant_file": "PATENTS.go-stdlib", - "verdict_authority": "go-stdlib" - }, - "tests": { - "parse": true - } - }, - { - "name": "go_longfrac_12digits_000123456789", - "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 12 fractional digits; Go's expected Nanosecond() is 123456, i.e. Go ACCEPTS and TRUNCATES past 9 digits.", - "tags": [ - "accept", - "go-stdlib", - "secfrac", - "go-truncates" - ], - "applies_to": "date-time", - "input": "2021-09-29T16:04:33.000123456789Z", - "source": "go-stdlib-format_test.go", - "provenance": { - "type": "extracted_from_source", - "source_repo": "github.com/golang/go", - "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", - "commit_sha_tag": "go1.26.5", - "file": "src/time/format_test.go", - "line": 974, - "table": "longFractionalDigitsTests", - "license": "BSD-3-Clause", - "license_file": "LICENSE.go-stdlib", - "additional_grant_file": "PATENTS.go-stdlib", - "verdict_authority": "go-stdlib" - }, - "tests": { - "parse": true - } - }, - { - "name": "go_longfrac_16digits_9999999999999999", - "description": "Go stdlib longFractionalDigitsTests row. TestParseFractionalSecondsLongerThanNineDigits parses this value with BOTH time.RFC3339 and time.RFC3339Nano and fails the test on error, so the table asserts Go accepts it. 16 fractional digits; Go's expected Nanosecond() is 999999999, i.e. Go ACCEPTS and TRUNCATES past 9 digits.", - "tags": [ - "accept", - "go-stdlib", - "secfrac", - "go-truncates" - ], - "applies_to": "date-time", - "input": "2021-09-29T16:04:33.9999999999999999Z", - "source": "go-stdlib-format_test.go", - "provenance": { - "type": "extracted_from_source", - "source_repo": "github.com/golang/go", - "commit_sha": "c19862e5f8415b4f24b189d065ed739517c548ba", - "commit_sha_tag": "go1.26.5", - "file": "src/time/format_test.go", - "line": 976, - "table": "longFractionalDigitsTests", - "license": "BSD-3-Clause", - "license_file": "LICENSE.go-stdlib", - "additional_grant_file": "PATENTS.go-stdlib", - "verdict_authority": "go-stdlib" - }, - "tests": { - "parse": true - } - }, - { - "name": "offset_max_syntactic_plus_23_59", - "description": "Maximum SYNTACTIC offset. §5.6 gives time-numoffset = (\"+\" / \"-\") time-hour \":\" time-minute, with time-hour = 2DIGIT ; 00-23 and time-minute = 2DIGIT ; 00-59, so +23:59 is the largest offset the grammar generates. This is well outside the range of any real timezone. The REJECT counterpart one minute above this boundary already ships as `offset_hour_out_of_range` (2026-01-29T12:00:00+24:00) in expires-rfc3339.json. NOTE for consumers: the offsets named as extremes in the upstream issue, +14:00 and -12:00, are the TIMEZONE extremes and already ship as `offset_plus_fourteen` and `offset_minus_twelve`; they are not the grammar's extremes. An implementation that range-checks offsets against tz data rather than against §5.6 will reject this conforming input. The base date is mid-range so that the offset-adjusted instant cannot overflow date-fullyear, which is a separate and deliberately undecided question held in the `disputed` block of expires-rfc3339.json.", - "tags": [ - "accept", - "offset-boundary", - "syntactic-extreme", - "hand-authored" - ], - "applies_to": "date-time", - "input": "2026-01-29T12:00:00+23:59", - "source": "rfc-3339-text", - "provenance": { - "type": "hand_authored", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_sha256": "9ab2b8864a85dca73a88f49b0927bc7bc85f596926e4fd1890905777924e700a", - "rfc_ref": "RFC 3339 §5.6 (time-numoffset = (\"+\" / \"-\") time-hour \":\" time-minute; time-hour = 2DIGIT ; 00-23; time-minute = 2DIGIT ; 00-59)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S5 (hand-authored-residual.json)" - }, - "tests": { - "parse": true - } - }, - { - "name": "offset_min_syntactic_minus_23_59", - "description": "Minimum SYNTACTIC offset, the negative arm of the same §5.6 boundary. time-numoffset admits (\"+\" / \"-\") equally, so -23:59 is as conforming as +23:59. The REJECT counterpart on this arm already ships in import-json-schema-test-suite.json as `jsts_date_time_011` (1990-12-31T15:59:59-24:00, upstream valid:false), where the offset hour 24 exceeds time-hour's 00-23 range. Together with offset_max_syntactic_plus_23_59 this closes the last-legal / first-illegal pair on both signs.", - "tags": [ - "accept", - "offset-boundary", - "syntactic-extreme", - "hand-authored" - ], - "applies_to": "date-time", - "input": "2026-01-29T12:00:00-23:59", - "source": "rfc-3339-text", - "provenance": { - "type": "hand_authored", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_sha256": "9ab2b8864a85dca73a88f49b0927bc7bc85f596926e4fd1890905777924e700a", - "rfc_ref": "RFC 3339 §5.6 (time-numoffset = (\"+\" / \"-\") time-hour \":\" time-minute; time-hour = 2DIGIT ; 00-23; time-minute = 2DIGIT ; 00-59)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S5 (hand-authored-residual.json)" - }, - "tests": { - "parse": true - } - }, - { - "name": "leap_second_offset_rolls_local_date_forward", - "description": "A leap second in date-time form where the zone offset pushes the LOCAL date across midnight. §5.7: \"Further, in time zones other than 'Z', the leap second point is shifted by the zone offset (so it happens at the same instant around the globe).\" 1999-01-01T00:59:60+01:00 resolves to 1998-12-31T23:59:60Z, which is the final row of the Appendix D leap second table and which already ships as an ACCEPT in three files. The only variable this vector changes is that the local date reads 1999-01-01 while the UTC date is 1998-12-31, so the leap second no longer sits at a visible month end. §5.7's month-end condition is a condition on the UTC instant, which the shifting sentence makes explicit. This is the case an implementation gets wrong when it validates the :60 against the LOCAL date rather than the offset-adjusted one. No vector in expires-rfc3339.json, import-json-schema-test-suite.json or import-go-stdlib.json rolls the date: every other date-time :60 vector keeps the local and UTC dates equal, because §5.8's worked example 1990-12-31T15:59:60-08:00 uses a negative offset that cannot roll forward, and a bare full-time has no date to roll.", - "tags": [ - "accept", - "leap-second", - "offset-arithmetic", - "date-rollover", - "hand-authored" - ], - "applies_to": "date-time", - "input": "1999-01-01T00:59:60+01:00", - "source": "rfc-3339-text", - "provenance": { - "type": "hand_authored", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_sha256": "9ab2b8864a85dca73a88f49b0927bc7bc85f596926e4fd1890905777924e700a", - "rfc_ref": "RFC 3339 §5.7 (leap second point is shifted by the zone offset) + Appendix D (1998-12-31 leap second table row)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S5 (hand-authored-residual.json)" - }, - "tests": { - "parse": true - } - }, - { - "name": "leap_second_offset_rolls_local_date_forward_wrong_offset", - "description": "The REJECT counterpart of leap_second_offset_rolls_local_date_forward, and the reason that vector proves anything. Character for character this differs only in the offset hour: 1999-01-01T00:59:60+02:00 resolves to 1998-12-31T22:59:60Z, which is not the leap second point. §5.7: \"At all other times the maximum value of time-second is '59'.\" An implementation that accepts the ACCEPT vector by ignoring the offset entirely, rather than by applying it, will also accept this one -- so the pair distinguishes offset-aware handling from a blanket :60 permission. Both sides of the boundary are authored here.", - "tags": [ - "reject", - "leap-second", - "offset-arithmetic", - "date-rollover", - "hand-authored" - ], - "applies_to": "date-time", - "input": "1999-01-01T00:59:60+02:00", - "source": "rfc-3339-text", - "provenance": { - "type": "hand_authored", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_sha256": "9ab2b8864a85dca73a88f49b0927bc7bc85f596926e4fd1890905777924e700a", - "rfc_ref": "RFC 3339 §5.7 (\"At all other times the maximum value of time-second is '59'\"; leap second point is shifted by the zone offset)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S5 (hand-authored-residual.json)" - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "rfc_5_8_example_fractional_z", - "description": "RFC 3339 §5.8 worked example: 20 minutes and 50.52 seconds after the 23rd hour of April 12th, 1985 in UTC.", - "tags": [ - "accept", - "rfc-example", - "secfrac" - ], - "applies_to": "date-time", - "input": "1985-04-12T23:20:50.52Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.8", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": true - } - }, - { - "name": "rfc_5_8_example_negative_offset", - "description": "RFC 3339 §5.8 worked example: -08:00 offset from UTC (Pacific Standard Time).", - "tags": [ - "accept", - "rfc-example", - "offset" - ], - "applies_to": "date-time", - "input": "1996-12-19T16:39:57-08:00", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.8", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": true - } - }, - { - "name": "rfc_5_8_example_leap_second_z", - "description": "RFC 3339 §5.8 worked example: the leap second inserted at the end of 1990.", - "tags": [ - "accept", - "rfc-example", - "leap-second" - ], - "applies_to": "date-time", - "input": "1990-12-31T23:59:60Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.8; §5.7 (time-second may be 60 at the end of December)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": true - } - }, - { - "name": "rfc_5_8_example_leap_second_offset", - "description": "RFC 3339 §5.8 worked example: the same leap second in Pacific Standard Time, 8 hours behind UTC. Per §5.7 the leap second point is shifted by the zone offset.", - "tags": [ - "accept", - "rfc-example", - "leap-second", - "offset" - ], - "applies_to": "date-time", - "input": "1990-12-31T15:59:60-08:00", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.8; §5.7", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": true - } - }, - { - "name": "leap_second_june_month_end", - "description": "Leap second at a June month end. RFC 3339 §5.7 names XXXX-06-30T23:59:60Z explicitly; 1972-06-30 is the first row of the Appendix D leap-second table.", - "tags": [ - "accept", - "leap-second" - ], - "applies_to": "date-time", - "input": "1972-06-30T23:59:60Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.7; Appendix D", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": true - } - }, - { - "name": "lowercase_t_separator", - "description": "Lowercase 't' as the date/time separator. §5.6 NOTE: \"the 'T' and 'Z' characters in this syntax may alternatively be lower case 't' or 'z' respectively\".", - "tags": [ - "accept", - "case", - "separator" - ], - "applies_to": "date-time", - "input": "1985-04-12t23:20:50.52Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (NOTE on lower case)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": true - } - }, - { - "name": "lowercase_z_offset", - "description": "Lowercase 'z' as the UTC designator. Same §5.6 NOTE.", - "tags": [ - "accept", - "case", - "offset" - ], - "applies_to": "date-time", - "input": "1985-04-12T23:20:50.52z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (NOTE on lower case)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": true - } - }, - { - "name": "lowercase_t_and_z", - "description": "Both separators lower case. Same §5.6 NOTE. §5.6 adds that applications which GENERATE this format SHOULD use upper case; that is a generation constraint, not a parse constraint.", - "tags": [ - "accept", - "case", - "separator", - "offset" - ], - "applies_to": "date-time", - "input": "1985-04-12t23:20:50.52z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (NOTE on lower case)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": true - } - }, - { - "name": "secfrac_1_digit", - "description": "One fractional digit. §5.6 time-secfrac = \".\" 1*DIGIT.", - "tags": [ - "accept", - "secfrac" - ], - "applies_to": "date-time", - "input": "2021-09-29T16:04:33.0Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (time-secfrac)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": true - } - }, - { - "name": "secfrac_7_digits", - "description": "Seven fractional digits. §5.6 time-secfrac = \".\" 1*DIGIT places no upper bound on digit count.", - "tags": [ - "accept", - "secfrac" - ], - "applies_to": "date-time", - "input": "2021-09-29T16:04:33.1234567Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (time-secfrac)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": true - } - }, - { - "name": "secfrac_8_digits", - "description": "Eight fractional digits. §5.6 time-secfrac = \".\" 1*DIGIT places no upper bound on digit count.", - "tags": [ - "accept", - "secfrac" - ], - "applies_to": "date-time", - "input": "2021-09-29T16:04:33.12345678Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (time-secfrac)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": true - } - }, - { - "name": "secfrac_9_digits", - "description": "Nine fractional digits. §5.6 time-secfrac = \".\" 1*DIGIT places no upper bound on digit count.", - "tags": [ - "accept", - "secfrac" - ], - "applies_to": "date-time", - "input": "2021-09-29T16:04:33.123456789Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (time-secfrac)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": true - } - }, - { - "name": "secfrac_point_with_no_digits", - "description": "A decimal point with no digits after it. §5.6 time-secfrac = \".\" 1*DIGIT requires at least one DIGIT.", - "tags": [ - "reject", - "secfrac" - ], - "applies_to": "date-time", - "input": "2021-09-29T16:04:33.Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (time-secfrac)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "secfrac_comma_separator", - "description": "Comma as the fraction separator. §5.6 time-secfrac fixes the separator as \".\". ISO 8601 permits a comma (see RFC 3339 Appendix A time-fraction = (\",\" / \".\") 1*DIGIT) but Appendix A is explicitly informational and is NOT the §5.6 profile.", - "tags": [ - "reject", - "secfrac" - ], - "applies_to": "date-time", - "input": "2021-09-29T16:04:33,5Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (time-secfrac)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "offset_plus_zero", - "description": "Offset +00:00. §5.6 time-numoffset; §4.3 notes +00:00 and Z both imply UTC is the preferred reference point.", - "tags": [ - "accept", - "offset" - ], - "applies_to": "date-time", - "input": "2026-01-29T12:00:00+00:00", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (time-numoffset); §4.3", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": true - } - }, - { - "name": "offset_minus_zero", - "description": "Offset -00:00, the unknown-local-offset convention. §4.3: \"If the time in UTC is known, but the offset to local time is unknown, this can be represented with an offset of -00:00.\" It is grammatically valid under §5.6 and semantically distinct from Z and +00:00.", - "tags": [ - "accept", - "offset", - "unknown-local-offset" - ], - "applies_to": "date-time", - "input": "2026-01-29T12:00:00-00:00", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §4.3; §5.6 (time-numoffset)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": true - } - }, - { - "name": "offset_plus_fourteen", - "description": "Offset +14:00. §5.6 time-numoffset = (\"+\" / \"-\") time-hour \":\" time-minute, time-hour = 2DIGIT ; 00-23. 14 is in range.", - "tags": [ - "accept", - "offset" - ], - "applies_to": "date-time", - "input": "2026-01-29T12:00:00+14:00", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (time-numoffset, time-hour 00-23)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": true - } - }, - { - "name": "offset_minus_twelve", - "description": "Offset -12:00. Same §5.6 production; 12 is in range.", - "tags": [ - "accept", - "offset" - ], - "applies_to": "date-time", - "input": "2026-01-29T12:00:00-12:00", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (time-numoffset, time-hour 00-23)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": true - } - }, - { - "name": "offset_hour_out_of_range", - "description": "Offset +24:00. §5.6 time-hour = 2DIGIT ; 00-23. 24 is out of range.", - "tags": [ - "reject", - "offset" - ], - "applies_to": "date-time", - "input": "2026-01-29T12:00:00+24:00", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (time-hour 00-23)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "offset_minute_out_of_range", - "description": "Offset +00:60. §5.6 time-minute = 2DIGIT ; 00-59. 60 is out of range.", - "tags": [ - "reject", - "offset" - ], - "applies_to": "date-time", - "input": "2026-01-29T12:00:00+00:60", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (time-minute 00-59)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "offset_missing_colon", - "description": "Offset written without the colon. §5.6 time-numoffset requires a literal \":\" between time-hour and time-minute.", - "tags": [ - "reject", - "offset" - ], - "applies_to": "date-time", - "input": "2026-01-29T12:00:00+0000", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (time-numoffset)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "offset_hour_only", - "description": "Offset written as hour only. §5.6 time-numoffset requires both time-hour and time-minute.", - "tags": [ - "reject", - "offset" - ], - "applies_to": "date-time", - "input": "2026-01-29T12:00:00-08", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (time-numoffset)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "offset_absent", - "description": "No offset at all. §5.6 full-time = partial-time time-offset; time-offset is not optional.", - "tags": [ - "reject", - "offset", - "unqualified-local-time" - ], - "applies_to": "date-time", - "input": "2026-01-29T12:00:00", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (full-time); §4.4", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "offset_alphabetic_zone", - "description": "Alphabetic zone abbreviation. §5.6 time-offset = \"Z\" / time-numoffset admits no alphabetic name other than Z; §4.2 records that alphabetic offset labels produced poor interoperability and that numeric offsets are mandatory.", - "tags": [ - "reject", - "offset" - ], - "applies_to": "date-time", - "input": "2026-01-29T12:00:00PST", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (time-offset); §4.2", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "year_9999", - "description": "Year 9999. §5.6 date-fullyear = 4DIGIT; 9999 is exactly four digits and §5.7 places no restriction on the year value itself.", - "tags": [ - "accept", - "year-boundary" - ], - "applies_to": "date-time", - "input": "9999-12-31T23:59:59Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (date-fullyear = 4DIGIT)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": true - } - }, - { - "name": "year_10000", - "description": "Year 10000. §5.6 date-fullyear = 4DIGIT is exactly four digits; a five-digit year is not in the grammar.", - "tags": [ - "reject", - "year-boundary" - ], - "applies_to": "date-time", - "input": "10000-01-01T00:00:00Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (date-fullyear = 4DIGIT)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "year_0000", - "description": "Year 0000. §5.6 date-fullyear = 4DIGIT; 0000 is exactly four digits. §5.7 restricts date-mday and time-second only, not the year value.", - "tags": [ - "accept", - "year-boundary" - ], - "applies_to": "date-time", - "input": "0000-01-01T00:00:00Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (date-fullyear = 4DIGIT)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": true - } - }, - { - "name": "year_two_digit", - "description": "Two-digit year. §5.6 date-fullyear = 4DIGIT. §3 (\"Two Digit Years\") independently rules two-digit years out for new protocols.", - "tags": [ - "reject", - "year-boundary" - ], - "applies_to": "date-time", - "input": "26-01-29T12:00:00Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (date-fullyear = 4DIGIT); §3", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "year_sign_prefixed", - "description": "ISO 8601 expanded-year sign prefix. §5.6 date-fullyear = 4DIGIT, and DIGIT admits only %x30-39; \"+\" is not a DIGIT.", - "tags": [ - "reject", - "year-boundary" - ], - "applies_to": "date-time", - "input": "+2026-01-29T12:00:00Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (date-fullyear = 4DIGIT)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "feb_30", - "description": "February 30th. §5.7 maximum-value table: February normal 28, February leap year 29. 30 exceeds both.", - "tags": [ - "reject", - "calendar" - ], - "applies_to": "date-time", - "input": "2026-02-30T00:00:00Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.7 (date-mday maximum table)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "sep_31", - "description": "September 31st. §5.7 maximum-value table: September 30.", - "tags": [ - "reject", - "calendar" - ], - "applies_to": "date-time", - "input": "2026-09-31T00:00:00Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.7 (date-mday maximum table)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "leap_year_2000_feb_29", - "description": "2000-02-29. §5.7 permits date-mday 29 in a leap February; Appendix C's leap_year() is (year%4==0 && (year%100!=0 || year%400==0)) and 2000%400==0, so 2000 is a leap year. This is the century case a naive %4-only or %100-only implementation gets wrong.", - "tags": [ - "accept", - "calendar", - "leap-year" - ], - "applies_to": "date-time", - "input": "2000-02-29T00:00:00Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.7; Appendix C", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": true - } - }, - { - "name": "non_leap_year_1900_feb_29", - "description": "1900-02-29. Appendix C: 1900%4==0 but 1900%100==0 and 1900%400!=0, so 1900 is NOT a leap year and §5.7 caps February at 28. This is the century case a naive %4-only implementation gets wrong.", - "tags": [ - "reject", - "calendar", - "leap-year" - ], - "applies_to": "date-time", - "input": "1900-02-29T00:00:00Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.7; Appendix C", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "leap_year_2024_feb_29", - "description": "2024-02-29. Appendix C: 2024%4==0 and 2024%100!=0, so 2024 is a leap year; §5.7 permits date-mday 29.", - "tags": [ - "accept", - "calendar", - "leap-year" - ], - "applies_to": "date-time", - "input": "2024-02-29T00:00:00Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.7; Appendix C", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": true - } - }, - { - "name": "non_leap_year_2026_feb_29", - "description": "2026-02-29. Appendix C: 2026%4!=0, so 2026 is not a leap year and §5.7 caps February at 28.", - "tags": [ - "reject", - "calendar", - "leap-year" - ], - "applies_to": "date-time", - "input": "2026-02-29T00:00:00Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.7; Appendix C", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "apr_31", - "description": "April 31st. §5.7 maximum-value table: April 30.", - "tags": [ - "reject", - "calendar" - ], - "applies_to": "date-time", - "input": "2026-04-31T00:00:00Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.7 (date-mday maximum table)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "jan_31", - "description": "January 31st. §5.7 maximum-value table: January 31.", - "tags": [ - "accept", - "calendar" - ], - "applies_to": "date-time", - "input": "2026-01-31T00:00:00Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.7 (date-mday maximum table)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": true - } - }, - { - "name": "month_00", - "description": "Month 00. §5.6 date-month = 2DIGIT ; 01-12.", - "tags": [ - "reject", - "calendar" - ], - "applies_to": "date-time", - "input": "2026-00-15T00:00:00Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (date-month 01-12)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "month_13", - "description": "Month 13. §5.6 date-month = 2DIGIT ; 01-12.", - "tags": [ - "reject", - "calendar" - ], - "applies_to": "date-time", - "input": "2026-13-15T00:00:00Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (date-month 01-12)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "mday_00", - "description": "Day 00. §5.6 date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on month/year. The minimum is 01.", - "tags": [ - "reject", - "calendar" - ], - "applies_to": "date-time", - "input": "2026-01-00T00:00:00Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (date-mday); §5.7", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "mday_32", - "description": "Day 32. §5.7 maximum-value table caps January at 31.", - "tags": [ - "reject", - "calendar" - ], - "applies_to": "date-time", - "input": "2026-01-32T00:00:00Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.7 (date-mday maximum table)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "time_00_00_00", - "description": "Start of day. §5.6 time-hour 00-23, time-minute 00-59, time-second 00-59; all three at their minimum.", - "tags": [ - "accept", - "time-of-day", - "boundary" - ], - "applies_to": "date-time", - "input": "2026-01-29T00:00:00Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (partial-time)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": true - } - }, - { - "name": "time_23_59_59", - "description": "Last non-leap second of the day. §5.6 time-hour 00-23, time-minute 00-59, time-second 00-59; all three at their non-leap maximum.", - "tags": [ - "accept", - "time-of-day", - "boundary" - ], - "applies_to": "date-time", - "input": "2026-01-29T23:59:59Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (partial-time); §5.7", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": true - } - }, - { - "name": "hour_24", - "description": "Hour 24. §5.7: \"Although ISO 8601 permits the hour to be 24, this profile of ISO 8601 only allows values between 00 and 23 for the hour in order to reduce confusion.\"", - "tags": [ - "reject", - "time-of-day", - "boundary" - ], - "applies_to": "date-time", - "input": "2026-01-29T24:00:00Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.7 (hour 00-23); §5.6 (time-hour)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "minute_60", - "description": "Minute 60. §5.6 time-minute = 2DIGIT ; 00-59.", - "tags": [ - "reject", - "time-of-day" - ], - "applies_to": "date-time", - "input": "2026-01-29T12:60:00Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (time-minute 00-59)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "second_61", - "description": "Second 61. §5.6 time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second rules; 61 exceeds every branch.", - "tags": [ - "reject", - "time-of-day" - ], - "applies_to": "date-time", - "input": "2026-01-29T12:00:61Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (time-second); §5.7", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "seconds_omitted", - "description": "Seconds omitted. §5.6 partial-time = time-hour \":\" time-minute \":\" time-second [time-secfrac]; only time-secfrac is optional.", - "tags": [ - "reject", - "structure" - ], - "applies_to": "date-time", - "input": "2026-01-29T12:00Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (partial-time)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "basic_format_no_separators", - "description": "ISO 8601 basic format with no separators. §5.6 full-date and partial-time both carry literal \"-\" and \":\" separators.", - "tags": [ - "reject", - "structure" - ], - "applies_to": "date-time", - "input": "20260129T120000Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (full-date, partial-time)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "date_only", - "description": "Date with no time part. §5.6 date-time = full-date \"T\" full-time; the time part is not optional.", - "tags": [ - "reject", - "structure" - ], - "applies_to": "date-time", - "input": "2026-01-29", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (date-time)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "non_date_string", - "description": "Arbitrary non-date text. §5.6 date-time requires a full-date, a separator and a full-time.", - "tags": [ - "reject", - "structure" - ], - "applies_to": "date-time", - "input": "not-a-timestamp", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (date-time)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "trailing_garbage_after_offset", - "description": "Trailing text after a complete date-time. §5.6 date-time = full-date \"T\" full-time and full-time = partial-time time-offset; the production ends at time-offset and admits nothing after it. This is the defect an unanchored regex or a prefix-consuming scanner has: the leading 20 characters are a valid date-time, so a parser that stops at the first complete match accepts the whole string.", - "tags": [ - "reject", - "structure", - "trailing-text" - ], - "applies_to": "date-time", - "input": "2026-01-29T12:00:00Z_abc", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (date-time; full-time)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "trailing_newline_after_offset", - "description": "A complete date-time followed by a single LF (U+000A). Same §5.6 defect as trailing_garbage_after_offset, in the shape a JSON payment field actually delivers: a trailing newline survives naive line reads and log round-trips. §5.6 date-time ends at time-offset, and LF is not part of any production.", - "tags": [ - "reject", - "structure", - "trailing-text", - "whitespace" - ], - "applies_to": "date-time", - "input": "2026-01-29T12:00:00Z\n", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (date-time; full-time)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "trailing_duplicate_z", - "description": "A second UTC designator after a complete date-time. §5.6 time-offset = \"Z\" / time-numoffset matches exactly one designator, and date-time ends there.", - "tags": [ - "reject", - "structure", - "trailing-text", - "offset" - ], - "applies_to": "date-time", - "input": "2026-01-29T12:00:00ZZ", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (date-time; time-offset)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "second_elided_secfrac_retained", - "description": "The seconds field elided but its fraction retained. §5.6 partial-time = time-hour \":\" time-minute \":\" time-second [time-secfrac]; time-second = 2DIGIT is mandatory and only time-secfrac is optional, so a fraction cannot stand in for the seconds it qualifies.", - "tags": [ - "reject", - "structure", - "secfrac" - ], - "applies_to": "date-time", - "input": "2026-01-29T12:00:.0Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (partial-time; time-second = 2DIGIT)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "offset_signed_minute", - "description": "A sign injected inside the offset minute field. §5.6 time-numoffset = (\"+\" / \"-\") time-hour \":\" time-minute carries its sign once, before time-hour; time-minute = 2DIGIT admits only %x30-39, and \"+0\" is a sign followed by one DIGIT.", - "tags": [ - "reject", - "offset" - ], - "applies_to": "date-time", - "input": "2026-01-29T12:00:00+00:+0", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (time-numoffset; time-minute = 2DIGIT)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "offset_double_sign", - "description": "Two signs at the head of the offset. §5.6 time-numoffset = (\"+\" / \"-\") time-hour \":\" time-minute takes exactly one leading sign, and time-hour = 2DIGIT must follow it, so \"-0\" is neither a second sign nor a 2DIGIT hour.", - "tags": [ - "reject", - "offset" - ], - "applies_to": "date-time", - "input": "2026-01-29T12:00:00+-0:00", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (time-numoffset; time-hour = 2DIGIT)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": { - "success": false, - "error_type": "parse_error" - } - } - }, - { - "name": "secfrac_10_digits", - "description": "Ten fractional digits, one past nanosecond resolution. §5.6 time-secfrac = \".\" 1*DIGIT states a lower bound of one digit and no upper bound, so this is grammatically well formed and the verdict is ACCEPT. A parser backed by a fixed nanosecond field truncates here; truncation is an implementation property and is NOT encoded in this verdict.", - "tags": [ - "accept", - "secfrac", - "precision-loss" - ], - "applies_to": "date-time", - "input": "2026-01-29T12:00:00.1234567890Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (time-secfrac = \".\" 1*DIGIT)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": true - } - }, - { - "name": "secfrac_19_digits_exceeds_int64", - "description": "Nineteen fractional nines. §5.6 time-secfrac = \".\" 1*DIGIT sets no upper bound, so the verdict is ACCEPT. Read as an integer the fraction is 9999999999999999999, which exceeds the signed 64-bit maximum 9223372036854775807; a parser that accumulates the fraction into a fixed-width integer before scaling overflows on this input rather than truncating. The overflow is an implementation property and is NOT encoded in this verdict.", - "tags": [ - "accept", - "secfrac", - "precision-loss", - "overflow" - ], - "applies_to": "date-time", - "input": "2026-01-29T12:00:00.9999999999999999999Z", - "source": "rfc-3339-text", - "provenance": { - "type": "derived_from_rfc_text", - "source_document": "RFC 3339", - "source_document_retrieved_from": "https://www.rfc-editor.org/rfc/rfc3339.txt", - "source_document_retrieved_on": "2026-08-07", - "rfc_ref": "RFC 3339 §5.6 (time-secfrac = \".\" 1*DIGIT)", - "verdict_authority": "rfc-3339-text", - "authored_by": "P036 S1, trimmed by T-P036-TRIM-53-2026-08-08", - "frozen_file": "expires-rfc3339.json", - "frozen_file_sha256": "c7cc201cfc0291e0a7f3926bd1dbe7668bce7663a3025469a10702ed8a9e94de", - "independently_rederived": "All 61 re-derived against the RFC text by a second seat (T-P036-S2-4b-gate-trimmed-corpus-2026-08-08) with zero disagreements." - }, - "tests": { - "parse": true - } - } - ], - "disputed": [ - { - "input": "2026-01-29 12:00:00Z", - "candidate_verdicts": [ - "reject", - "accept" - ], - "reason": "RFC 3339 §5.6 ABNF is date-time = full-date \"T\" full-time, which mandates a literal \"T\". But the second NOTE in the same section reads: \"ISO 8601 defines date and time separated by 'T'. Applications using this syntax may choose, for the sake of readability, to specify a full-date and full-time separated by (say) a space character.\" The NOTE neither amends the date-time production (as the lower-case NOTE explicitly does) nor states that the space form is a valid date-time. The verdict therefore cannot be pinned to a single section.", - "sections_consulted": [ - "RFC 3339 §5.6 (date-time production)", - "RFC 3339 §5.6 (NOTE on the space separator)" - ] - }, - { - "input": "2026-03-15T23:59:60Z", - "candidate_verdicts": [ - "reject", - "accept" - ], - "reason": "Second = 60 on a date that is not a June or December month end. §5.7 permits time-second = 60 \"at the end of months in which a leap second occurs -- to date: June (XXXX-06-30T23:59:60Z) or December (XXXX-12-31T23:59:60Z)\" and adds \"At all other times the maximum value of time-second is 59\". Read as a grammar restriction that is REJECT; but the same section says leap seconds \"cannot be predicted far into the future\" and are announced by IERS bulletin, and §5.7 records that first preference is June/December and SECOND preference March/September (Appendix D). Whether a parser is expected to enforce the month restriction, or only the 00-60 range, is not settled by the text.", - "sections_consulted": [ - "RFC 3339 §5.7 (time-second and leap-second rules)", - "RFC 3339 §5.6 (time-second = 2DIGIT ; 00-58, 00-59, 00-60)", - "RFC 3339 Appendix D" - ] - }, - { - "input": "2026-06-30T23:59:60Z", - "candidate_verdicts": [ - "accept", - "reject" - ], - "reason": "Second = 60 at a June month end in a year for which no leap second was announced. Positionally this is exactly the shape §5.7 names as permitted (XXXX-06-30T23:59:60Z), so a grammar-only reading ACCEPTS. But §5.7 conditions the permission on \"months in which a leap second occurs\", and no leap second occurred at 2026-06-30. A parser cannot hold the IERS bulletin history, so the section cannot decide this literal. Contrast the two SHIPPED leap-second vectors, which use 1972-06-30 and 1998-12-31 -- both rows of the Appendix D table, where the leap second demonstrably did occur.", - "sections_consulted": [ - "RFC 3339 §5.7 (time-second and leap-second rules)", - "RFC 3339 Appendix D (USNO leap-second table)" - ] - }, - { - "input": " 2026-01-29T12:00:00Z ", - "candidate_verdicts": [ - "reject", - "accept" - ], - "reason": "Surrounding whitespace. RFC 3339 §5.6 defines the date-time production and nothing else; it does not say whether a protocol field carrying a date-time may be padded. Whether the padding is stripped before the value reaches the parser is a question about the carrying protocol's field-value semantics, not about RFC 3339. No RFC 3339 section decides it.", - "sections_consulted": [ - "RFC 3339 §5.6 (date-time production)" - ] - }, - { - "input": [ - "9999-12-31T23:59:59+00:01", - "0000-01-01T00:00:00-00:01" - ], - "candidate_verdicts": [ - "accept", - "reject" - ], - "reason": "Lexically valid date-times whose offset-adjusted UTC instant falls outside the four-digit year range (year 10000 and year -0001 respectively). §5.6 date-fullyear = 4DIGIT constrains the LEXICAL year only. §5.7 restricts date-mday and time-second and says nothing about whether the offset-adjusted instant must also remain representable. Grammatically ACCEPT, semantically an overflow. No section decides which reading governs.", - "sections_consulted": [ - "RFC 3339 §5.6 (date-fullyear = 4DIGIT, time-numoffset)", - "RFC 3339 §5.7 (Restrictions)", - "RFC 3339 §4.2 (Local Offsets)" - ] - } - ], - "disputed_note": "Carried verbatim from expires-rfc3339.json. These are NOT shipped scenarios: each is a literal whose verdict RFC 3339 does not settle, recorded with its candidate verdicts so the question is visible rather than quietly decided. S6 did not add to, remove from, or re-adjudicate this block." -} diff --git a/harness/vectors/mpp-protocol/expires.json b/harness/vectors/mpp-protocol/expires.json new file mode 100644 index 000000000..889040f17 --- /dev/null +++ b/harness/vectors/mpp-protocol/expires.json @@ -0,0 +1,1336 @@ +{ + "version": "2.0.0", + "spec_ref": "RFC 3339 §5.6", + "description": "Challenge expires timestamp parsing (RFC 3339 date-time)", + "commands": { + "parse": "parse-expires" + }, + "scenarios": [ + { + "name": "jsts_date_time_001", + "description": "a valid date-time string", + "tags": ["happy-path", "secfrac"], + "input": "1963-06-19T08:30:06.283185Z", + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_time_002", + "description": "a valid date-time string without second fraction", + "tags": ["happy-path"], + "input": "1963-06-19T08:30:06Z", + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_time_003", + "description": "a valid date-time string with plus offset", + "tags": ["happy-path", "secfrac", "offset"], + "input": "1937-01-01T12:00:27.87+00:20", + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_time_004", + "description": "a valid date-time string with minus offset", + "tags": ["happy-path", "secfrac", "offset"], + "input": "1990-12-31T15:59:50.123-08:00", + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_time_005", + "description": "a valid date-time with a leap second, UTC", + "tags": ["happy-path", "leap-second"], + "input": "1998-12-31T23:59:60Z", + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_time_006", + "description": "a valid date-time with a leap second, with minus offset", + "tags": ["happy-path", "leap-second", "offset"], + "input": "1998-12-31T15:59:60.123-08:00", + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_time_007", + "description": "an invalid date-time past leap second, UTC", + "tags": ["error", "leap-second"], + "input": "1998-12-31T23:59:61Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_008", + "description": "an invalid date-time with leap second on a wrong minute, UTC", + "tags": ["error", "leap-second"], + "input": "1998-12-31T23:58:60Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_009", + "description": "an invalid date-time with leap second on a wrong hour, UTC", + "tags": ["error", "leap-second"], + "input": "1998-12-31T22:59:60Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_010", + "description": "an invalid day in date-time string", + "tags": ["error", "calendar"], + "input": "1990-02-31T15:59:59.123-08:00", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_011", + "description": "an invalid offset in date-time string", + "tags": ["error", "offset", "boundary"], + "input": "1990-12-31T15:59:59-24:00", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_012", + "description": "an invalid closing Z after time-zone offset", + "tags": ["error", "offset", "trailing-text"], + "input": "1963-06-19T08:30:06.28123+01:00Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_013", + "description": "an invalid hour in date-time string", + "tags": ["error", "time-of-day"], + "input": "1990-12-31T24:00:00Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_014", + "description": "an invalid minute in date-time string", + "tags": ["error", "time-of-day"], + "input": "1990-12-31T15:60:00Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_015", + "description": "an invalid offset minute in date-time string", + "tags": ["error", "offset", "boundary"], + "input": "1990-12-31T10:00:00+10:60", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_016", + "description": "an invalid date-time string", + "tags": ["error", "structure"], + "input": "06/19/1963 08:30:06 PST", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_017", + "description": "case-insensitive T and Z", + "tags": ["happy-path", "case"], + "input": "1963-06-19t08:30:06.283185z", + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_time_018", + "description": "only RFC3339 not all of ISO 8601 are valid", + "tags": ["error", "structure"], + "input": "2013-350T01:01:01", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_019", + "description": "invalid non-padded month dates", + "tags": ["error", "calendar", "structure"], + "input": "1963-6-19T08:30:06.283185Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_020", + "description": "invalid non-padded day dates", + "tags": ["error", "calendar", "structure"], + "input": "1963-06-1T08:30:06.283185Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_021", + "description": "invalid non-ASCII '৪' (a Bengali 4) in date portion", + "tags": ["error", "unicode"], + "input": "1963-06-1৪T00:00:00Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_022", + "description": "invalid non-ASCII '৪' (a Bengali 4) in time portion", + "tags": ["error", "unicode"], + "input": "1963-06-11T0৪:00:00Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_023", + "description": "invalid extended year", + "tags": ["error", "year-boundary"], + "input": "+11963-06-19T08:30:06.283185Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_024", + "description": "a numeric offset without minutes is invalid", + "tags": ["error", "offset"], + "input": "1985-04-12T23:20:50+01", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_025", + "description": "hour 24 is invalid even with a leap second", + "tags": ["error", "leap-second", "time-of-day"], + "input": "2016-12-31T24:59:60+01:00", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jsts_date_time_026", + "description": "a second fraction of fifteen nines is valid", + "tags": ["happy-path", "secfrac"], + "input": "1985-04-12T00:59:59.999999999999999Z", + "tests": { + "parse": true + } + }, + { + "name": "jsts_date_time_027", + "description": "a trailing newline is invalid", + "tags": ["error", "trailing-text", "whitespace"], + "input": "1985-04-12T23:20:50Z\n", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "go_parsetests_2010_02_04t21_00_57_08_00", + "description": "Timestamp with a negative offset and no fraction", + "tags": ["happy-path", "offset"], + "input": "2010-02-04T21:00:57-08:00", + "tests": { + "parse": true + } + }, + { + "name": "go_parsetests_2010_02_04t21_00_57_012345678_08_00", + "description": "Nanosecond fraction with a negative offset", + "tags": ["happy-path", "secfrac", "offset"], + "input": "2010-02-04T21:00:57.012345678-08:00", + "tests": { + "parse": true + } + }, + { + "name": "go_parseerror_2006_01_02t15_04_05z07_00", + "description": "Rejects a layout string used as a value", + "tags": ["error", "trailing-text"], + "input": "2006-01-02T15:04:05Z07:00", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "go_parseerror_2006_01_02t15_04_abc", + "description": "Rejects text where the seconds field belongs", + "tags": ["error", "structure"], + "input": "2006-01-02T15:04_abc", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "go_parseerror_2006_01_02t15_04_05_abc", + "description": "Rejects text where the offset belongs", + "tags": ["error", "offset"], + "input": "2006-01-02T15:04:05_abc", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "go_parseerror_2006_01_02t15_04_05z_abc", + "description": "Rejects trailing text after the Z designator", + "tags": ["error", "trailing-text"], + "input": "2006-01-02T15:04:05Z_abc", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "go_parseerror_2010_02_04t21_00_67_012345678_08_00", + "description": "Rejects a seconds value of 67", + "tags": ["error", "time-of-day"], + "input": "2010-02-04T21:00:67.012345678-08:00", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "go_parseerror_0000_01_01t00_00_0_00_00", + "description": "Rejects a fraction standing in for the seconds", + "tags": ["error", "secfrac"], + "input": "0000-01-01T00:00:.0+00:00", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "go_parseerror_double_quote_char", + "description": "Rejects a bare double-quote character", + "tags": ["error", "structure"], + "input": "\"", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "go_parseerror_0000_01_01t00_00_00_00_0", + "description": "Rejects a sign inside the offset minute field", + "tags": ["error", "offset"], + "input": "0000-01-01T00:00:00+00:+0", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "go_parseerror_0000_01_01t00_00_00_0_00", + "description": "Rejects two signs at the head of the offset", + "tags": ["error", "offset"], + "input": "0000-01-01T00:00:00+-0:00", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "go_longfrac_9digits_000000000", + "description": "Nine fractional digits, all zero", + "tags": ["happy-path", "secfrac"], + "input": "2021-09-29T16:04:33.000000000Z", + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_9digits_000000001", + "description": "Nine fractional digits, one nanosecond", + "tags": ["happy-path", "secfrac"], + "input": "2021-09-29T16:04:33.000000001Z", + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_9digits_100000000", + "description": "Nine fractional digits, one tenth of a second", + "tags": ["happy-path", "secfrac"], + "input": "2021-09-29T16:04:33.100000000Z", + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_9digits_100000001", + "description": "Nine fractional digits, tenth plus a nanosecond", + "tags": ["happy-path", "secfrac"], + "input": "2021-09-29T16:04:33.100000001Z", + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_9digits_999999999", + "description": "Nine fractional digits, all nines", + "tags": ["happy-path", "secfrac"], + "input": "2021-09-29T16:04:33.999999999Z", + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_9digits_012345678", + "description": "Nine fractional digits with a leading zero", + "tags": ["happy-path", "secfrac"], + "input": "2021-09-29T16:04:33.012345678Z", + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_10digits_0000000000", + "description": "Ten fractional digits, all zero", + "tags": ["happy-path", "secfrac", "precision-loss"], + "input": "2021-09-29T16:04:33.0000000000Z", + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_10digits_0000000001", + "description": "Ten fractional digits below nanosecond resolution", + "tags": ["happy-path", "secfrac", "precision-loss"], + "input": "2021-09-29T16:04:33.0000000001Z", + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_10digits_1000000000", + "description": "Ten fractional digits, one tenth of a second", + "tags": ["happy-path", "secfrac", "precision-loss"], + "input": "2021-09-29T16:04:33.1000000000Z", + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_10digits_1000000009", + "description": "Ten fractional digits, last digit truncated away", + "tags": ["happy-path", "secfrac", "precision-loss"], + "input": "2021-09-29T16:04:33.1000000009Z", + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_10digits_9999999999", + "description": "Ten fractional digits, all nines", + "tags": ["happy-path", "secfrac", "precision-loss"], + "input": "2021-09-29T16:04:33.9999999999Z", + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_10digits_0123456789", + "description": "Ten fractional digits with a leading zero", + "tags": ["happy-path", "secfrac", "precision-loss"], + "input": "2021-09-29T16:04:33.0123456789Z", + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_11digits_10000000000", + "description": "Eleven fractional digits, one tenth of a second", + "tags": ["happy-path", "secfrac", "precision-loss"], + "input": "2021-09-29T16:04:33.10000000000Z", + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_11digits_00123456789", + "description": "Eleven fractional digits with two leading zeros", + "tags": ["happy-path", "secfrac", "precision-loss"], + "input": "2021-09-29T16:04:33.00123456789Z", + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_12digits_000123456789", + "description": "Twelve fractional digits with three leading zeros", + "tags": ["happy-path", "secfrac", "precision-loss"], + "input": "2021-09-29T16:04:33.000123456789Z", + "tests": { + "parse": true + } + }, + { + "name": "go_longfrac_16digits_9999999999999999", + "description": "Sixteen fractional digits, all nines", + "tags": ["happy-path", "secfrac", "precision-loss"], + "input": "2021-09-29T16:04:33.9999999999999999Z", + "tests": { + "parse": true + } + }, + { + "name": "offset_max_syntactic_plus_23_59", + "description": "Offset +23:59, the largest the grammar generates", + "tags": ["happy-path", "offset", "boundary"], + "input": "2026-01-29T12:00:00+23:59", + "tests": { + "parse": true + } + }, + { + "name": "offset_min_syntactic_minus_23_59", + "description": "Offset -23:59, the smallest the grammar generates", + "tags": ["happy-path", "offset", "boundary"], + "input": "2026-01-29T12:00:00-23:59", + "tests": { + "parse": true + } + }, + { + "name": "leap_second_offset_rolls_local_date_forward", + "description": "Leap second whose offset rolls the local date forward", + "tags": ["happy-path", "leap-second", "date-rollover"], + "input": "1999-01-01T00:59:60+01:00", + "tests": { + "parse": true + } + }, + { + "name": "leap_second_offset_rolls_local_date_forward_wrong_offset", + "description": "Rejects a :60 second one hour off the leap second point", + "tags": ["error", "leap-second", "date-rollover"], + "input": "1999-01-01T00:59:60+02:00", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "rfc_5_8_example_fractional_z", + "description": "RFC 3339 §5.8 worked example, fraction in UTC", + "tags": ["happy-path", "rfc-example", "secfrac"], + "input": "1985-04-12T23:20:50.52Z", + "tests": { + "parse": true + } + }, + { + "name": "rfc_5_8_example_negative_offset", + "description": "RFC 3339 §5.8 worked example, -08:00 offset", + "tags": ["happy-path", "rfc-example", "offset"], + "input": "1996-12-19T16:39:57-08:00", + "tests": { + "parse": true + } + }, + { + "name": "rfc_5_8_example_leap_second_z", + "description": "RFC 3339 §5.8 worked example, leap second in UTC", + "tags": ["happy-path", "rfc-example", "leap-second"], + "input": "1990-12-31T23:59:60Z", + "tests": { + "parse": true + } + }, + { + "name": "rfc_5_8_example_leap_second_offset", + "description": "RFC 3339 §5.8 worked example, leap second at -08:00", + "tags": ["happy-path", "rfc-example", "leap-second"], + "input": "1990-12-31T15:59:60-08:00", + "tests": { + "parse": true + } + }, + { + "name": "leap_second_june_month_end", + "description": "Leap second at a June month end", + "tags": ["happy-path", "leap-second"], + "input": "1972-06-30T23:59:60Z", + "tests": { + "parse": true + } + }, + { + "name": "lowercase_t_separator", + "description": "Lower-case t as the date and time separator", + "tags": ["happy-path", "case"], + "input": "1985-04-12t23:20:50.52Z", + "tests": { + "parse": true + } + }, + { + "name": "lowercase_z_offset", + "description": "Lower-case z as the UTC designator", + "tags": ["happy-path", "case"], + "input": "1985-04-12T23:20:50.52z", + "tests": { + "parse": true + } + }, + { + "name": "lowercase_t_and_z", + "description": "Both the separator and the designator lower case", + "tags": ["happy-path", "case"], + "input": "1985-04-12t23:20:50.52z", + "tests": { + "parse": true + } + }, + { + "name": "secfrac_1_digit", + "description": "One fractional digit", + "tags": ["happy-path", "secfrac"], + "input": "2021-09-29T16:04:33.0Z", + "tests": { + "parse": true + } + }, + { + "name": "secfrac_7_digits", + "description": "Seven fractional digits", + "tags": ["happy-path", "secfrac"], + "input": "2021-09-29T16:04:33.1234567Z", + "tests": { + "parse": true + } + }, + { + "name": "secfrac_8_digits", + "description": "Eight fractional digits", + "tags": ["happy-path", "secfrac"], + "input": "2021-09-29T16:04:33.12345678Z", + "tests": { + "parse": true + } + }, + { + "name": "secfrac_9_digits", + "description": "Nine fractional digits", + "tags": ["happy-path", "secfrac"], + "input": "2021-09-29T16:04:33.123456789Z", + "tests": { + "parse": true + } + }, + { + "name": "secfrac_point_with_no_digits", + "description": "Rejects a decimal point with no digits after it", + "tags": ["error", "secfrac"], + "input": "2021-09-29T16:04:33.Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "secfrac_comma_separator", + "description": "Rejects a comma as the fraction separator", + "tags": ["error", "secfrac", "separator"], + "input": "2021-09-29T16:04:33,5Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "offset_plus_zero", + "description": "Offset +00:00", + "tags": ["happy-path", "offset"], + "input": "2026-01-29T12:00:00+00:00", + "tests": { + "parse": true + } + }, + { + "name": "offset_minus_zero", + "description": "Offset -00:00, the unknown local offset", + "tags": ["happy-path", "offset"], + "input": "2026-01-29T12:00:00-00:00", + "tests": { + "parse": true + } + }, + { + "name": "offset_plus_fourteen", + "description": "Offset +14:00, the eastmost real timezone", + "tags": ["happy-path", "offset"], + "input": "2026-01-29T12:00:00+14:00", + "tests": { + "parse": true + } + }, + { + "name": "offset_minus_twelve", + "description": "Offset -12:00, the westmost real timezone", + "tags": ["happy-path", "offset"], + "input": "2026-01-29T12:00:00-12:00", + "tests": { + "parse": true + } + }, + { + "name": "offset_hour_out_of_range", + "description": "Rejects an offset hour of 24", + "tags": ["error", "offset", "boundary"], + "input": "2026-01-29T12:00:00+24:00", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "offset_minute_out_of_range", + "description": "Rejects an offset minute of 60", + "tags": ["error", "offset", "boundary"], + "input": "2026-01-29T12:00:00+00:60", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "offset_missing_colon", + "description": "Rejects an offset written without the colon", + "tags": ["error", "offset", "separator"], + "input": "2026-01-29T12:00:00+0000", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "offset_hour_only", + "description": "Rejects an offset written as the hour alone", + "tags": ["error", "offset"], + "input": "2026-01-29T12:00:00-08", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "offset_absent", + "description": "Rejects a timestamp with no offset at all", + "tags": ["error", "offset"], + "input": "2026-01-29T12:00:00", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "offset_alphabetic_zone", + "description": "Rejects an alphabetic zone abbreviation", + "tags": ["error", "offset"], + "input": "2026-01-29T12:00:00PST", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "year_9999", + "description": "Year 9999, the largest four-digit year", + "tags": ["happy-path", "year-boundary"], + "input": "9999-12-31T23:59:59Z", + "tests": { + "parse": true + } + }, + { + "name": "year_10000", + "description": "Rejects a five-digit year", + "tags": ["error", "year-boundary"], + "input": "10000-01-01T00:00:00Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "year_0000", + "description": "Year 0000, the smallest four-digit year", + "tags": ["happy-path", "year-boundary"], + "input": "0000-01-01T00:00:00Z", + "tests": { + "parse": true + } + }, + { + "name": "year_two_digit", + "description": "Rejects a two-digit year", + "tags": ["error", "year-boundary"], + "input": "26-01-29T12:00:00Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "year_sign_prefixed", + "description": "Rejects an ISO 8601 expanded-year sign prefix", + "tags": ["error", "year-boundary"], + "input": "+2026-01-29T12:00:00Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "feb_30", + "description": "Rejects February 30th", + "tags": ["error", "calendar"], + "input": "2026-02-30T00:00:00Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "sep_31", + "description": "Rejects September 31st", + "tags": ["error", "calendar"], + "input": "2026-09-31T00:00:00Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "leap_year_2000_feb_29", + "description": "February 29th in 2000, a leap year by the 400 rule", + "tags": ["happy-path", "calendar", "leap-year"], + "input": "2000-02-29T00:00:00Z", + "tests": { + "parse": true + } + }, + { + "name": "non_leap_year_1900_feb_29", + "description": "Rejects February 29th in 1900, not a leap year", + "tags": ["error", "calendar", "leap-year"], + "input": "1900-02-29T00:00:00Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "leap_year_2024_feb_29", + "description": "February 29th in 2024, an ordinary leap year", + "tags": ["happy-path", "calendar", "leap-year"], + "input": "2024-02-29T00:00:00Z", + "tests": { + "parse": true + } + }, + { + "name": "non_leap_year_2026_feb_29", + "description": "Rejects February 29th in a non-leap year", + "tags": ["error", "calendar", "leap-year"], + "input": "2026-02-29T00:00:00Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "apr_31", + "description": "Rejects April 31st", + "tags": ["error", "calendar"], + "input": "2026-04-31T00:00:00Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "jan_31", + "description": "January 31st, the longest month at its last day", + "tags": ["happy-path", "calendar"], + "input": "2026-01-31T00:00:00Z", + "tests": { + "parse": true + } + }, + { + "name": "month_00", + "description": "Rejects month 00", + "tags": ["error", "calendar"], + "input": "2026-00-15T00:00:00Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "month_13", + "description": "Rejects month 13", + "tags": ["error", "calendar"], + "input": "2026-13-15T00:00:00Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "mday_00", + "description": "Rejects day 00", + "tags": ["error", "calendar"], + "input": "2026-01-00T00:00:00Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "mday_32", + "description": "Rejects day 32", + "tags": ["error", "calendar"], + "input": "2026-01-32T00:00:00Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "time_00_00_00", + "description": "Midnight, every time field at its minimum", + "tags": ["happy-path", "time-of-day"], + "input": "2026-01-29T00:00:00Z", + "tests": { + "parse": true + } + }, + { + "name": "time_23_59_59", + "description": "The last non-leap second of the day", + "tags": ["happy-path", "time-of-day"], + "input": "2026-01-29T23:59:59Z", + "tests": { + "parse": true + } + }, + { + "name": "hour_24", + "description": "Rejects hour 24", + "tags": ["error", "time-of-day"], + "input": "2026-01-29T24:00:00Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "minute_60", + "description": "Rejects minute 60", + "tags": ["error", "time-of-day"], + "input": "2026-01-29T12:60:00Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "second_61", + "description": "Rejects second 61", + "tags": ["error", "time-of-day"], + "input": "2026-01-29T12:00:61Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "seconds_omitted", + "description": "Rejects a timestamp with the seconds omitted", + "tags": ["error", "time-of-day"], + "input": "2026-01-29T12:00Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "basic_format_no_separators", + "description": "Rejects ISO 8601 basic format with no separators", + "tags": ["error", "structure", "separator"], + "input": "20260129T120000Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "date_only", + "description": "Rejects a bare date with no time part", + "tags": ["error", "structure"], + "input": "2026-01-29", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "non_date_string", + "description": "Rejects arbitrary non-date text", + "tags": ["error", "structure"], + "input": "not-a-timestamp", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "trailing_garbage_after_offset", + "description": "Rejects trailing text after a complete timestamp", + "tags": ["error", "trailing-text"], + "input": "2026-01-29T12:00:00Z_abc", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "trailing_newline_after_offset", + "description": "Rejects a trailing newline after a complete timestamp", + "tags": ["error", "trailing-text", "whitespace"], + "input": "2026-01-29T12:00:00Z\n", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "trailing_duplicate_z", + "description": "Rejects a second UTC designator", + "tags": ["error", "trailing-text"], + "input": "2026-01-29T12:00:00ZZ", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "second_elided_secfrac_retained", + "description": "Rejects a fraction standing in for the seconds", + "tags": ["error", "secfrac", "time-of-day"], + "input": "2026-01-29T12:00:.0Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "offset_signed_minute", + "description": "Rejects a sign inside the offset minute field", + "tags": ["error", "offset"], + "input": "2026-01-29T12:00:00+00:+0", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "offset_double_sign", + "description": "Rejects two signs at the head of the offset", + "tags": ["error", "offset"], + "input": "2026-01-29T12:00:00+-0:00", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "secfrac_10_digits", + "description": "Ten fractional digits, one past nanosecond resolution", + "tags": ["happy-path", "secfrac", "precision-loss"], + "input": "2026-01-29T12:00:00.1234567890Z", + "tests": { + "parse": true + } + }, + { + "name": "secfrac_19_digits_exceeds_int64", + "description": "Nineteen fractional digits, past the int64 maximum", + "tags": ["happy-path", "secfrac", "overflow"], + "input": "2026-01-29T12:00:00.9999999999999999999Z", + "tests": { + "parse": true + } + }, + { + "name": "bare_date_no_time", + "description": "Rejects a well-formed bare date with no time part", + "tags": ["error", "wrong-production"], + "input": "1963-06-19", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "bare_date_leap_day", + "description": "Rejects a bare leap day, valid as a date but not a timestamp", + "tags": ["error", "wrong-production", "calendar"], + "input": "2020-02-29", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "bare_time_zulu", + "description": "Rejects a well-formed bare time with no date part", + "tags": ["error", "wrong-production"], + "input": "08:30:06Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "bare_time_lowercase_z", + "description": "Rejects a bare time closed with a lower-case z", + "tags": ["error", "wrong-production", "case"], + "input": "08:30:06z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "bare_time_numeric_offset", + "description": "Rejects a bare time carrying a numeric offset", + "tags": ["error", "wrong-production", "offset"], + "input": "08:30:06-08:00", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "bare_time_secfrac", + "description": "Rejects a bare time carrying a second fraction", + "tags": ["error", "wrong-production", "secfrac"], + "input": "23:20:50.52Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "bare_time_leap_second", + "description": "Rejects a bare time carrying a leap second", + "tags": ["error", "wrong-production", "leap-second"], + "input": "23:59:60Z", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + }, + { + "name": "empty_string", + "description": "Rejects the empty string", + "tags": ["error", "structure"], + "input": "", + "tests": { + "parse": { + "success": false, + "error_type": "parse_error" + } + } + } + ] +} diff --git a/lua/tests/expires_rfc3339_spec.lua b/lua/tests/expires_rfc3339_spec.lua index c837a4e81..67f58a914 100644 --- a/lua/tests/expires_rfc3339_spec.lua +++ b/lua/tests/expires_rfc3339_spec.lua @@ -63,16 +63,13 @@ end) -- ── Cross-SDK RFC 3339 conformance corpus (issue #111) ── -- --- Vectors live in `harness/vectors/mpp-protocol/expires-rfc3339-corpus.json` --- under the `expires.parse` operation. Every SDK asserts the same ACCEPT / --- REJECT verdict against the same vectors, so a divergence between two SDKs --- shows up as a failing test in exactly one of them rather than as silence. +-- Vectors live in `harness/vectors/mpp-protocol/expires.json` under the +-- `expires.parse` operation. Every SDK asserts the same ACCEPT / REJECT verdict +-- against the same vectors, so a divergence between two SDKs shows up as a +-- failing test in exactly one of them rather than as silence. -- --- Only the `applies_to == "date-time"` slice runs here. The corpus also carries --- `full-date` and `full-time` scenarios, which answer a different question than --- an `expires` field asks -- `1963-06-19` is a valid RFC 3339 `full-date` and no --- `date-time` parser should accept it. Selection is on the first-class --- `applies_to` field, never on a name prefix or a description string. +-- Every scenario in the file runs. There is no slice to select and no scenario +-- to skip. -- -- Decoding uses the SDK's own `pay_kit.util.json`, so the suite gains no new -- dependency. The runner has no fixture-loading facility and no per-vector @@ -86,10 +83,10 @@ local function corpus_path() -- the working directory. local source = debug.getinfo(1, 'S').source:gsub('^@', '') local dir = source:match('^(.*)/[^/]*$') or '.' - return dir .. '/../../harness/vectors/mpp-protocol/expires-rfc3339-corpus.json' + return dir .. '/../../harness/vectors/mpp-protocol/expires.json' end -t.test('expires.parse_rfc3339 matches the cross-SDK RFC 3339 corpus (date-time slice)', function() +t.test('expires.parse_rfc3339 matches the cross-SDK RFC 3339 corpus', function() local expires = require('pay_kit.protocols.mpp.expires') local json = require('pay_kit.util.json') @@ -105,32 +102,32 @@ t.test('expires.parse_rfc3339 matches the cross-SDK RFC 3339 corpus (date-time s for i = 1, #corpus.scenarios do local scenario = corpus.scenarios[i] - if scenario.applies_to == 'date-time' then - admitted = admitted + 1 - -- `"tests": {"parse": true}` is ACCEPT; `{"parse": {"success": false, …}}` - -- is REJECT. Identical to the encoding the other vector files in the same - -- directory use. - local expect_accept = scenario.tests.parse == true - local ok, value = pcall(expires.parse_rfc3339, scenario.input) - -- A crash on hostile input is a result, and it is a REJECT. - local accepted = ok and value ~= nil - if accepted ~= expect_accept then - divergences[#divergences + 1] = string.format( - '%s: input %q -- corpus expects %s, parse_rfc3339 reports %s', - scenario.name, scenario.input, - expect_accept and 'ACCEPT' or 'REJECT', - accepted and 'ACCEPT' or 'REJECT' - ) - end + admitted = admitted + 1 + -- `"tests": {"parse": true}` is ACCEPT; `{"parse": {"success": false, …}}` + -- is REJECT. Identical to the encoding the other vector files in the same + -- directory use. + local expect_accept = scenario.tests.parse == true + local ok, value = pcall(expires.parse_rfc3339, scenario.input) + -- A crash on hostile input is a result, and it is a REJECT. + local accepted = ok and value ~= nil + if accepted ~= expect_accept then + divergences[#divergences + 1] = string.format( + '%s: input %q -- corpus expects %s, parse_rfc3339 reports %s', + scenario.name, scenario.input, + expect_accept and 'ACCEPT' or 'REJECT', + accepted and 'ACCEPT' or 'REJECT' + ) end end - t.assert_true(admitted > 0, 'corpus admitted zero date-time scenarios') - t.assert_true(admitted < #corpus.scenarios, 'date-time filter admitted every scenario') + -- Guard the loader: a truncated or empty read fails here rather than passing + -- quietly with nothing to run, and every scenario in the file is exercised. + t.assert_true(admitted > 0, 'corpus carried zero scenarios') + t.assert_true(admitted == #corpus.scenarios, 'not every corpus scenario was exercised') if #divergences > 0 then error(string.format( - '%d of %d date-time vectors diverge from the cross-SDK corpus:\n %s', + '%d of %d vectors diverge from the cross-SDK corpus:\n %s', #divergences, admitted, table.concat(divergences, '\n ') )) end diff --git a/php/tests/PayCore/Rfc3339Test.php b/php/tests/PayCore/Rfc3339Test.php index 7d9ecbae1..e94f0a0dd 100644 --- a/php/tests/PayCore/Rfc3339Test.php +++ b/php/tests/PayCore/Rfc3339Test.php @@ -81,21 +81,18 @@ public function testRejectsInvalidRfc3339(string $input): void // ── Cross-SDK RFC 3339 conformance corpus (issue #111) ── // - // Vectors live in `harness/vectors/mpp-protocol/expires-rfc3339-corpus.json` - // under the `expires.parse` operation. Every SDK asserts the same ACCEPT / - // REJECT verdict against the same vectors, so a divergence between two SDKs - // shows up as a failing test in exactly one of them rather than as silence. + // Vectors live in `harness/vectors/mpp-protocol/expires.json` under the + // `expires.parse` operation. Every SDK asserts the same ACCEPT / REJECT + // verdict against the same vectors, so a divergence between two SDKs shows + // up as a failing test in exactly one of them rather than as silence. - private const CORPUS_PATH = __DIR__ . '/../../../harness/vectors/mpp-protocol/expires-rfc3339-corpus.json'; + private const CORPUS_PATH = __DIR__ . '/../../../harness/vectors/mpp-protocol/expires.json'; /** - * The `applies_to == "date-time"` slice of the shared corpus. + * Every scenario in the shared corpus. * - * The corpus also carries `full-date` and `full-time` scenarios, which - * answer a different question than an `expires` field asks — `1963-06-19` - * is a valid RFC 3339 `full-date` and no `date-time` parser should accept - * it. Selection is on the first-class `applies_to` field, never on a name - * prefix or a description string. + * Every scenario in the file is an `expires` verdict; there is no slice to + * select and no scenario to skip. * * Verdict encoding, identical to the other vector files in the same * directory: `"tests": {"parse": true}` is ACCEPT, and @@ -113,9 +110,6 @@ public static function conformanceVectors(): array $vectors = []; foreach ($corpus['scenarios'] as $scenario) { - if ($scenario['applies_to'] !== 'date-time') { - continue; - } $vectors[$scenario['name']] = [ $scenario['name'], $scenario['input'], @@ -146,18 +140,16 @@ public function testMatchesCrossSdkConformanceCorpus( )); } - /** Guard the filter itself so a regression in it cannot go silent. */ - public function testCorpusAdmitsOnlyTheDateTimeSlice(): void + /** + * Guard the loader so a regression in it cannot go silent: every scenario + * in the file is exercised, and a truncated or empty read fails here rather + * than passing quietly with nothing to run. + */ + public function testEveryCorpusScenarioIsExercised(): void { $corpus = json_decode(file_get_contents(self::CORPUS_PATH), true, 512, JSON_THROW_ON_ERROR); - $expected = 0; - foreach ($corpus['scenarios'] as $scenario) { - if ($scenario['applies_to'] === 'date-time') { - $expected++; - } - } - $this->assertSame($expected, count(self::conformanceVectors())); - $this->assertLessThan(count($corpus['scenarios']), count(self::conformanceVectors())); + $this->assertSame(count($corpus['scenarios']), count(self::conformanceVectors())); + $this->assertGreaterThan(0, count(self::conformanceVectors())); } } diff --git a/python/tests/test_expires.py b/python/tests/test_expires.py index 03599c95b..8cddc21d4 100644 --- a/python/tests/test_expires.py +++ b/python/tests/test_expires.py @@ -143,19 +143,16 @@ def test_invalid_month_rejected(self): / "harness" / "vectors" / "mpp-protocol" - / "expires-rfc3339-corpus.json" + / "expires.json" ) -def _load_date_time_vectors() -> list[tuple[str, str, bool, str]]: - """Return the ``applies_to == "date-time"`` slice of the shared corpus. +def _load_vectors() -> list[tuple[str, str, bool, str]]: + """Return every scenario in the shared corpus. - Yields ``(name, input, expect_accept, description)``. The corpus also - carries ``full-date`` and ``full-time`` scenarios; those answer a different - question than an ``expires`` field asks — ``1963-06-19`` is a valid RFC 3339 - ``full-date`` and no ``date-time`` parser should accept it. Selection is on - the first-class ``applies_to`` field, never on a name prefix or a - description string. + Yields ``(name, input, expect_accept, description)``. Every scenario in the + file is an ``expires`` verdict; there is no slice to select and no scenario + to skip. Verdict encoding, identical to the other vector files in the same directory: ``"tests": {"parse": true}`` is ACCEPT, and @@ -164,8 +161,6 @@ def _load_date_time_vectors() -> list[tuple[str, str, bool, str]]: corpus = json.loads(_CORPUS_PATH.read_text(encoding="utf-8")) vectors: list[tuple[str, str, bool, str]] = [] for scenario in corpus["scenarios"]: - if scenario["applies_to"] != "date-time": - continue expectation = scenario["tests"]["parse"] expect_accept = expectation is True vectors.append( @@ -174,7 +169,7 @@ def _load_date_time_vectors() -> list[tuple[str, str, bool, str]]: return vectors -_DATE_TIME_VECTORS = _load_date_time_vectors() +_VECTORS = _load_vectors() class TestRFC3339ConformanceCorpus: @@ -198,8 +193,8 @@ class TestRFC3339ConformanceCorpus: @pytest.mark.parametrize( ("name", "value", "expect_accept", "description"), - _DATE_TIME_VECTORS, - ids=[vector[0] for vector in _DATE_TIME_VECTORS], + _VECTORS, + ids=[vector[0] for vector in _VECTORS], ) def test_vector(self, name: str, value: str, expect_accept: bool, description: str): from solana_pay_kit.protocols.mpp.core.types import _parse_rfc3339 @@ -216,10 +211,12 @@ def test_vector(self, name: str, value: str, expect_accept: bool, description: s f"_parse_rfc3339 reports {'ACCEPT' if accepted else 'REJECT'}" ) - def test_corpus_admits_only_the_date_time_slice(self): - """Guard the filter itself: a loader regression must not go silent.""" + def test_every_corpus_scenario_is_exercised(self): + """Guard the loader: a regression in it must not go silent. + + A truncated or empty read fails here rather than passing quietly with + nothing left to run. + """ corpus = json.loads(_CORPUS_PATH.read_text(encoding="utf-8")) - assert len(_DATE_TIME_VECTORS) == sum( - 1 for scenario in corpus["scenarios"] if scenario["applies_to"] == "date-time" - ) - assert len(_DATE_TIME_VECTORS) < len(corpus["scenarios"]) + assert len(_VECTORS) == len(corpus["scenarios"]) + assert len(_VECTORS) > 0 diff --git a/ruby/test/pay_core/expires_rfc3339_test.rb b/ruby/test/pay_core/expires_rfc3339_test.rb index b558711dc..db39914cd 100644 --- a/ruby/test/pay_core/expires_rfc3339_test.rb +++ b/ruby/test/pay_core/expires_rfc3339_test.rb @@ -86,16 +86,13 @@ def test_expires_strict_rfc3339_branches # ── Cross-SDK RFC 3339 conformance corpus (issue #111) ── # -# Vectors live in `harness/vectors/mpp-protocol/expires-rfc3339-corpus.json` -# under the `expires.parse` operation. Every SDK asserts the same ACCEPT / -# REJECT verdict against the same vectors, so a divergence between two SDKs -# shows up as a failing test in exactly one of them rather than as silence. +# Vectors live in `harness/vectors/mpp-protocol/expires.json` under the +# `expires.parse` operation. Every SDK asserts the same ACCEPT / REJECT verdict +# against the same vectors, so a divergence between two SDKs shows up as a +# failing test in exactly one of them rather than as silence. # -# Only the `applies_to == "date-time"` slice runs here. The corpus also carries -# `full-date` and `full-time` scenarios, which answer a different question than -# an `expires` field asks — `1963-06-19` is a valid RFC 3339 `full-date` and no -# `date-time` parser should accept it. Selection is on the first-class -# `applies_to` field, never on a name prefix or a description string. +# Every scenario in the file runs. There is no slice to select and no scenario +# to skip. # # The verdict comes straight out of `PayCore::Rfc3339Parser.parse`, which # returns a `Time` on success and `nil` on any parse failure. That is the @@ -103,24 +100,25 @@ def test_expires_strict_rfc3339_branches # field is actually checked against. class ExpiresRfc3339CorpusTest < Minitest::Test CORPUS_PATH = File.expand_path( - "../../../harness/vectors/mpp-protocol/expires-rfc3339-corpus.json", __dir__ + "../../../harness/vectors/mpp-protocol/expires.json", __dir__ ) - def self.date_time_vectors - corpus = JSON.parse(File.read(CORPUS_PATH)) - corpus.fetch("scenarios").select { |scenario| scenario["applies_to"] == "date-time" } + def self.vectors + JSON.parse(File.read(CORPUS_PATH)).fetch("scenarios") end - DATE_TIME_VECTORS = date_time_vectors.freeze + VECTORS = vectors.freeze - # Guard the filter itself so a regression in it cannot go silent. - def test_corpus_admits_only_the_date_time_slice + # Guard the loader so a regression in it cannot go silent: every scenario in + # the file is exercised, and a truncated or empty read fails here rather than + # passing quietly with nothing to run. + def test_every_corpus_scenario_is_exercised all = JSON.parse(File.read(CORPUS_PATH)).fetch("scenarios") - assert_equal all.count { |s| s["applies_to"] == "date-time" }, DATE_TIME_VECTORS.length - assert_operator DATE_TIME_VECTORS.length, :<, all.length + assert_equal all.length, VECTORS.length + assert_operator VECTORS.length, :>, 0 end - DATE_TIME_VECTORS.each do |scenario| + VECTORS.each do |scenario| # `"tests": {"parse": true}` is ACCEPT; `{"parse": {"success": false, …}}` # is REJECT. Identical to the encoding the other vector files in the same # directory use. diff --git a/rust/crates/kit/src/mpp/protocol/core/challenge.rs b/rust/crates/kit/src/mpp/protocol/core/challenge.rs index e38ccbecd..181e2a738 100644 --- a/rust/crates/kit/src/mpp/protocol/core/challenge.rs +++ b/rust/crates/kit/src/mpp/protocol/core/challenge.rs @@ -814,17 +814,13 @@ mod tests { // ── Cross-SDK RFC 3339 conformance corpus (issue #111) ── // - // Vectors live in `harness/vectors/mpp-protocol/expires-rfc3339-corpus.json` - // under the `expires.parse` operation. Every SDK asserts the same ACCEPT / - // REJECT verdict against the same vectors, so a divergence between two SDKs - // shows up as a failing test in exactly one of them rather than as silence. + // Vectors live in `harness/vectors/mpp-protocol/expires.json` under the + // `expires.parse` operation. Every SDK asserts the same ACCEPT / REJECT + // verdict against the same vectors, so a divergence between two SDKs shows + // up as a failing test in exactly one of them rather than as silence. // - // Only the `applies_to == "date-time"` slice runs here. The corpus also - // carries `full-date` and `full-time` scenarios, which answer a different - // question than an `expires` field asks — `1963-06-19` is a valid RFC 3339 - // `full-date` and no `date-time` parser should accept it. Selection is on - // the first-class `applies_to` field, never on a name prefix or a - // description string. + // Every scenario in the file runs. There is no slice to select and no + // scenario to skip. // // WHY THE VERDICT IS READ OFF `OffsetDateTime::parse` AND NOT OFF // `is_expired`. `is_expired` (this file, `PaymentChallenge::is_expired`) @@ -842,11 +838,11 @@ mod tests { const CONFORMANCE_CORPUS: &str = concat!( env!("CARGO_MANIFEST_DIR"), - "/../../../harness/vectors/mpp-protocol/expires-rfc3339-corpus.json" + "/../../../harness/vectors/mpp-protocol/expires.json" ); - /// `(name, input, expect_accept, description)` for the `date-time` slice. - fn conformance_date_time_vectors() -> Vec<(String, String, bool, String)> { + /// `(name, input, expect_accept, description)` for every corpus scenario. + fn conformance_vectors() -> Vec<(String, String, bool, String)> { let raw = std::fs::read_to_string(CONFORMANCE_CORPUS) .unwrap_or_else(|e| panic!("conformance corpus unreadable at {CONFORMANCE_CORPUS}: {e}")); let corpus: serde_json::Value = @@ -856,7 +852,6 @@ mod tests { .as_array() .expect("corpus must carry a `scenarios` array") .iter() - .filter(|scenario| scenario["applies_to"] == "date-time") .map(|scenario| { // `"tests": {"parse": true}` is ACCEPT; `{"parse": {"success": // false, …}}` is REJECT. Identical to the encoding the other @@ -874,11 +869,8 @@ mod tests { #[test] fn rfc3339_conformance_corpus() { - let vectors = conformance_date_time_vectors(); - assert!( - !vectors.is_empty(), - "conformance corpus admitted zero date-time scenarios" - ); + let vectors = conformance_vectors(); + assert!(!vectors.is_empty(), "conformance corpus carried zero scenarios"); let mut divergences = Vec::new(); for (name, input, expect_accept, description) in &vectors { @@ -899,7 +891,7 @@ mod tests { assert!( divergences.is_empty(), - "{} of {} date-time vectors diverge from the cross-SDK corpus:\n {}", + "{} of {} vectors diverge from the cross-SDK corpus:\n {}", divergences.len(), vectors.len(), divergences.join("\n "), @@ -911,7 +903,7 @@ mod tests { // Ties the corpus to the shipped surface. A REJECT vector must make // `is_expired()` true; a parse failure is the only route to that // outcome for an unparseable value, so this direction is unambiguous. - let vectors = conformance_date_time_vectors(); + let vectors = conformance_vectors(); let rejects: Vec<_> = vectors.iter().filter(|vector| !vector.2).collect(); assert!(!rejects.is_empty(), "corpus admitted zero REJECT vectors"); @@ -938,13 +930,16 @@ mod tests { ); } - /// Guard the filter itself so a regression in it cannot go silent. + /// Guard the loader so a regression in it cannot go silent: every scenario + /// in the file is exercised, and a truncated or empty read fails here + /// rather than passing quietly with nothing left to run. #[test] - fn rfc3339_conformance_corpus_admits_only_the_date_time_slice() { + fn rfc3339_conformance_corpus_exercises_every_scenario() { let raw = std::fs::read_to_string(CONFORMANCE_CORPUS).expect("corpus unreadable"); let corpus: serde_json::Value = serde_json::from_str(&raw).expect("corpus must be JSON"); let total = corpus["scenarios"].as_array().unwrap().len(); - let admitted = conformance_date_time_vectors().len(); - assert!(admitted < total, "date-time filter admitted every scenario"); + let admitted = conformance_vectors().len(); + assert!(total > 0, "corpus carried zero scenarios"); + assert_eq!(admitted, total, "not every corpus scenario was exercised"); } } diff --git a/typescript/packages/mpp/src/__tests__/client-charge-validation.test.ts b/typescript/packages/mpp/src/__tests__/client-charge-validation.test.ts index 2ddfed9e8..3b9167665 100644 --- a/typescript/packages/mpp/src/__tests__/client-charge-validation.test.ts +++ b/typescript/packages/mpp/src/__tests__/client-charge-validation.test.ts @@ -443,16 +443,13 @@ test('#10 createCredential rejects a network that does not match expectedNetwork // ── Cross-SDK RFC 3339 conformance corpus (issue #111) ── // -// Vectors live in `harness/vectors/mpp-protocol/expires-rfc3339-corpus.json` -// under the `expires.parse` operation. Every SDK asserts the same ACCEPT / -// REJECT verdict against the same vectors, so a divergence between two SDKs -// shows up as a failing test in exactly one of them rather than as silence. +// Vectors live in `harness/vectors/mpp-protocol/expires.json` under the +// `expires.parse` operation. Every SDK asserts the same ACCEPT / REJECT verdict +// against the same vectors, so a divergence between two SDKs shows up as a +// failing test in exactly one of them rather than as silence. // -// Only the `applies_to == "date-time"` slice runs here. The corpus also carries -// `full-date` and `full-time` scenarios, which answer a different question than -// an `expires` field asks — `1963-06-19` is a valid RFC 3339 `full-date` and no -// `date-time` parser should accept it. Selection is on the first-class -// `applies_to` field, never on a name prefix or a description string. +// Every scenario in the file runs. There is no slice to select and no scenario +// to skip. // // WHAT THIS ASSERTS, PRECISELY. This package has no RFC 3339 parser module. // The two expiry call sites delegate the whole parse to the JS engine: @@ -478,33 +475,32 @@ import { readFileSync } from 'node:fs'; interface ConformanceScenario { name: string; description: string; - applies_to: string; input: string; tests: { parse: true | { success: boolean } }; } const CORPUS_URL = new URL( - '../../../../../harness/vectors/mpp-protocol/expires-rfc3339-corpus.json', + '../../../../../harness/vectors/mpp-protocol/expires.json', import.meta.url, ); const corpus = JSON.parse(readFileSync(CORPUS_URL, 'utf8')) as { scenarios: ConformanceScenario[] }; -const dateTimeVectors = corpus.scenarios.filter((scenario) => scenario.applies_to === 'date-time'); +const vectors = corpus.scenarios; // `"tests": {"parse": true}` is ACCEPT; `{"parse": {"success": false, …}}` is // REJECT. Identical to the encoding the other vector files in the same // directory use. const expectsAccept = (scenario: ConformanceScenario) => scenario.tests.parse === true; -// Guard the filter itself so a regression in it cannot go silent. -test('RFC 3339 corpus admits only the date-time slice', () => { - expect(dateTimeVectors.length).toBe( - corpus.scenarios.filter((scenario) => scenario.applies_to === 'date-time').length, - ); - expect(dateTimeVectors.length).toBeLessThan(corpus.scenarios.length); +// Guard the loader so a regression in it cannot go silent: every scenario in +// the file is exercised, and a truncated or empty read fails here rather than +// passing quietly with nothing to run. +test('every RFC 3339 corpus scenario is exercised', () => { + expect(vectors.length).toBe(corpus.scenarios.length); + expect(vectors.length).toBeGreaterThan(0); }); -for (const scenario of dateTimeVectors) { +for (const scenario of vectors) { test(`RFC 3339 corpus / charge call site / ${scenario.name}`, () => { // client/Charge.ts:404 const accepted = !Number.isNaN(new Date(scenario.input).getTime()); From 7752cf7464c0656b30a8421f8c9096285c4f6e1d Mon Sep 17 00:00:00 2001 From: Michael Moffett Date: Tue, 11 Aug 2026 16:13:24 -0600 Subject: [PATCH 05/15] docs(mpp): add the RFC 3339 expires conformance results The vectors this branch adds carry verdicts and the seven SDK tests assert them, but what each tree actually does with an `expires` string was written down nowhere. A reader who wanted to know which parse paths diverge, and on which inputs, had to run the suites and work it out. This commit records that result under docs/, beside mpp-protocol-conformance-results.md, where the equivalent result for the rest of the protocol already lives. The document drives all eight `expires` parse paths, seven trees with two call sites in one of them, across the 124 date-time scenarios in expires.json: 992 cells, of which 120 diverge from the corpus. It names the parse path per tree by file and line, the runtime each number was measured on, and the count that tree failed, then nine findings, each with the input that produced it, the verdicts the trees returned and the RFC 3339 section the expected verdict derives from. Several of those findings are behaviours no single accept-or-reject vector can express: an offsetless date-time whose instant depends on the host, a path that evaluates ECMA-262 date-time strings rather than RFC 3339 ones, an empty `expires` that returns not-expired without being parsed, and two trees that accept the same input and denote different instants. One vector is separated out because RFC 3339 does not decide it, so no tree can be called wrong about it. A limitations section states what was not measured, and a reproduce section gives the command per tree with its prerequisites and expected exit status, so every number here can be rerun rather than taken. The vectors README ended its provenance section by pointing at a file that is not in this repository. It now points at this document. --- docs/mpp-expires-rfc3339-conformance.md | 509 ++++++++++++++++++++++++ harness/vectors/mpp-protocol/README.md | 2 +- 2 files changed, 510 insertions(+), 1 deletion(-) create mode 100644 docs/mpp-expires-rfc3339-conformance.md diff --git a/docs/mpp-expires-rfc3339-conformance.md b/docs/mpp-expires-rfc3339-conformance.md new file mode 100644 index 000000000..f3cebdb36 --- /dev/null +++ b/docs/mpp-expires-rfc3339-conformance.md @@ -0,0 +1,509 @@ +# RFC 3339 `expires` conformance: pay-kit SDK parse paths + +Divergence results from driving every pay-kit `expires` parse path against the RFC 3339 `date-time` +vectors vendored at `harness/vectors/mpp-protocol/expires.json` (issue #111). + +- Oracle: RFC 3339, pinned text sha256 `9ab2b8864a85dca73a88f49b0927bc7bc85f596926e4fd1890905777924e700a` (35,064 B). +- Vectors: `harness/vectors/mpp-protocol/expires.json`, version `2.0.0`, **124 scenarios** (54 ACCEPT, + 70 REJECT), 36,470 B, sha256 `72f90dc9c9e6d7c373fcae7893e1d9f0c537ecc69fc7397773d5b006a78e0e3d`. +- Parse paths: go, lua, php, python, ruby, rust, typescript; typescript has two call sites, so eight. +- Operation `expires.parse`. Verdict axis ACCEPT or REJECT. Cells: 124 x 8 = 992. +- Source lines resolve at `16777d465f4446c7ac519378663d6d4bbf394877`, the merge base. +- Sources, upstream shas, licences, the dedupe rule and every settled scenario not shipped are in + `harness/vectors/mpp-protocol/README.md`. + +## Headline + +**The corpus is not green: 120 of 992 cells diverge, 12.1%. One parse path of eight passes all 124.** +Every divergence is a verdict disagreement, not a crash; no cell produced anything but ACCEPT or REJECT. + +| parse path | scenarios | pass | **fail** | +|---|---|---|---| +| rust | 124 | 124 | **0** | +| python | 124 | 115 | **9** | +| go | 124 | 108 | **16** | +| lua | 124 | 108 | **16** | +| php | 124 | 107 | **17** | +| ruby | 124 | 106 | **18** | +| typescript, charge call site | 124 | 102 | **22** | +| typescript, session call site | 124 | 102 | **22** | +| **total** | **992** | **872** | **120** | + +**Scope.** Every scenario in the file is an `expires` verdict, so there is no filter and no skip; each +tree asserts that the count it exercised equals the count in the file. The 104 settled `full-date` and +`full-time` scenarios are not `expires` verdicts and are not in the file, and +`harness/vectors/mpp-protocol/README.md` lists each with the rule that excluded it. + +**The rust row** means rust never disagreed with the corpus about validity, not that rust is correct: +finding 8 records rust clamping the RFC's own §5.8 leap second to `23:59:59.999999999`. It was checked as +a suspected instrument defect first, and emits mechanism-specific rejection text on four mechanisms +(`the 'year' component could not be parsed`, `the 'separator' component could not be parsed`, `offset +minute was not in range`, `second was not in range`) with minority verdicts in both directions. + +## Per-tree results + +`repo-parser`: the verdict came from an SDK function whose return value distinguishes parse failure from +a valid past date. `delegated-parser`: the SDK wrapper cannot, so the runner evaluated the parse +expression the SDK delegates to. The harness contains no RFC 3339 parser written for this work. + +| tree | parse path (file:line) | verdict source | runtime | **fail of 124** | +|---|---|---|---|---| +| rust | `rust/crates/kit/src/mpp/protocol/core/challenge.rs:173-184` `PaymentChallenge::is_expired` to `time::OffsetDateTime::parse(s, &Rfc3339)` | `delegated-parser` | `cargo 1.96.0`, `time` crate `0.3.55` | **0** | +| python | `python/src/solana_pay_kit/protocols/mpp/core/types.py:28` `_parse_rfc3339` (regex `_RFC3339_RE` at `:17`, then `datetime.fromisoformat`) | `repo-parser` | `Python 3.12.13` | **9** | +| go | `go/protocols/mpp/wire/challenge.go:110-119` `PaymentChallenge.IsExpired(now)` | `repo-parser` | `go1.26.5 darwin/arm64` | **16** | +| lua | `lua/pay_kit/protocols/mpp/expires.lua:27` `M.parse_rfc3339` | `repo-parser` | `Lua 5.5.1` | **16** | +| php | `php/src/PayCore/Rfc3339Parser.php:37` `Rfc3339Parser::parse` | `repo-parser` | `PHP 8.5.9 (cli)` | **17** | +| ruby | `ruby/lib/pay_core/rfc3339_parser.rb:28` `Rfc3339Parser.parse` | `repo-parser` | `ruby 4.0.6` | **18** | +| typescript, charge | `typescript/packages/mpp/src/client/Charge.ts:402` `assertChallengeNotExpired` to `new Date(expires).getTime()` at `:404` | `delegated-parser` | `node v26.0.0` | **22** | +| typescript, session | `typescript/packages/mpp/src/server/Session.ts:346` `assertChallengeOpenNotExpired` to `Date.parse(expires)` at `:348` | `delegated-parser` | `node v26.0.0` | **22** | + +**Every row is a property of (SDK source x that runtime), not of the SDK alone.** +`rust/crates/kit/Cargo.toml:153` pins `time = "0.3"` with no lockfile, so a fresh resolve takes whatever +`0.3.x` is current; this run resolved `0.3.55`. pay-kit's Lua rockspecs target Lua 5.1 and 5.4; this run +used Lua 5.5.1. + +Two SDKs carry a second RFC 3339 grammar. Both are reachable; neither is counted in the 992. + +| tree | second path | why it exists | +|---|---|---| +| go | raw `time.Parse(time.RFC3339, v)` | isolates stdlib behaviour from the wrapper's early-return guard (finding 3) | +| python | `_ISO8601_RE` at `headers.py:32`, used at `headers.py:229` to gate receipt timestamps | the SDK's second RFC 3339 grammar (finding 5) | + +**Python 3.9 has no measurement.** `types.py:7` executes `from datetime import UTC` and raises +`ImportError: cannot import name 'UTC' from 'datetime'`; `datetime.UTC` is Python 3.11 and later. +Reported as unwired rather than as 124 disagreements. + +## Findings + +Nine findings, each a measurement over the shipped file unless stated otherwise. Divergence is measured, +exploitability is not. Consequence is inference, at most once per finding, in a `> **Inference:**` block. + +### 1. An offsetless date-time is host-dependent in the typescript path + +`offset_absent` (`2026-01-29T12:00:00`, corpus REJECT) is the only offsetless date-time in the file, +found by regex `^\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}:\d{2}(\.\d+)?$` over all 124 inputs, not by name. Six +trees reject it: rust, go, python, ruby, php, lua. TypeScript accepts it and reads it in host local time: + +``` +TZ=UTC 2026-01-29T12:00:00 -> 2026-01-29T12:00:00.000Z +TZ=America/New_York 2026-01-29T12:00:00 -> 2026-01-29T17:00:00.000Z +TZ=Asia/Tokyo 2026-01-29T12:00:00 -> 2026-01-29T03:00:00.000Z +``` + +§5.6 makes the offset mandatory in `date-time` and §4.4 states why; ECMA-262 reads an offsetless form as +local. **Limit:** one vector, written from the RFC text, since neither import has an offsetless +date-time. + +> **Inference:** for any input omitting an offset, the verdict and instant this path produces are +> properties of the host as well as the input. How such an input reaches this path is not measured. + +### 2. The typescript path evaluates ECMA-262 date-time strings, not RFC 3339 + +`Charge.ts:402` `assertChallengeNotExpired` evaluates `new Date(expires).getTime()` at `:404`; +`Session.ts:346` `assertChallengeOpenNotExpired` evaluates `Date.parse(expires)` at `:348`. Both resolve +at `16777d46`, are the same V8 algorithm, and return byte-identical verdicts on all 124 scenarios. +**Every one of the 22 divergences at each call site is an over-acceptance, an input the corpus rejects +that this path accepts; not one is a parse failure.** Rows 1-2 are direct probes, rows 3-5 the shipped +vectors `jsts_date_time_010`, `jsts_date_time_013`, `jsts_date_time_016`. + +| input | `Date.parse` result | RFC 3339 | +|---|---|---| +| `2026-01-01` | `2026-01-01T00:00:00.000Z`, accepted | REJECT, a date-only form is not a `date-time` | +| `2026-01-01T12:00` | `2026-01-01T18:00:00.000Z`, accepted | REJECT, `time-second` is mandatory | +| `1990-02-31T15:59:59.123-08:00` | `1990-03-03T23:59:59.123Z`, rolled over | REJECT, §5.7 February maximum | +| `1990-12-31T24:00:00Z` | `1991-01-01T00:00:00.000Z`, rolled over | REJECT, §5.7 hour 00-23 | +| `06/19/1963 08:30:06 PST` | `1963-06-19T16:30:06.000Z`, accepted | REJECT, not §5.6 syntax at all | + +Five shipped vectors take the calendar-rollover path: all corpus REJECT, all accepted, `Session.ts` +matching `Charge.ts` on each. `epochMs` is read from the results file; the instant beside it is decoded. + +| vector | input | `Charge.ts` result | rolls to | +|---|---|---|---| +| `feb_30` | `2026-02-30T00:00:00Z` | ACCEPT, `epochMs=1772409600000` | `2026-03-02T00:00:00.000Z` | +| `sep_31` | `2026-09-31T00:00:00Z` | ACCEPT, `epochMs=1790812800000` | `2026-10-01T00:00:00.000Z` | +| `apr_31` | `2026-04-31T00:00:00Z` | ACCEPT, `epochMs=1777593600000` | `2026-05-01T00:00:00.000Z` | +| `non_leap_year_1900_feb_29` | `1900-02-29T00:00:00Z` | ACCEPT, `epochMs=-2203891200000` | `1900-03-01T00:00:00.000Z` | +| `hour_24` | `2026-01-29T24:00:00Z` | ACCEPT, `epochMs=1769731200000` | `2026-01-30T00:00:00.000Z` | + +**Three further divergences are bare-production forms. The third is the sharpest in the finding.** + +| vector | input | corpus | `new Date(x)` / `Date.parse(x)` resolves to | +|---|---|---|---| +| `bare_date_no_time` | `1963-06-19` | REJECT | `1963-06-19T00:00:00.000Z`, silently becomes midnight UTC | +| `bare_date_leap_day` | `2020-02-29` | REJECT | `2020-02-29T00:00:00.000Z`, same | +| `bare_time_leap_second` | `23:59:60Z` | REJECT | **`1960-01-01T23:59:00.000Z`**, read as year 1960 with the leap second truncated to `:00` | + +An `expires` of `23:59:60Z` does not fail here; it becomes an instant in 1960, which every comparison +reads as long expired. `bare_time_zulu` (`08:30:06Z`) and the other three bare-time shapes are correctly +rejected, so the defect is specific to the leap-second form. + +### 3. An empty `expires` short-circuits the go guard and returns not-expired without parsing + +`go/protocols/mpp/wire/challenge.go`, read off disk at `16777d46`: + +```go +110: func (c PaymentChallenge) IsExpired(now time.Time) bool { +111: if strings.TrimSpace(c.Expires) == "" { +112: return false +113: } +114: expiresAt, err := time.Parse(time.RFC3339, c.Expires) +``` + +**The guard at `:111-113` returns `false`, not expired; `time.Parse` at `:114` is not reached.** Line 111 +tests `TrimSpace`, so the early-return class is empty or whitespace-only, and the value handed to +`time.Parse` is untrimmed, which is why a trailing-newline input passes in go. `empty_string` is corpus +REJECT: six trees reject it, go reports ACCEPT, and that one vector is the whole difference between the +go wrapper and raw `time.Parse`, which errors on `""` and agrees on every other input. + +**A semantic question, not a plain defect.** `IsExpired` is a field-level surface where `expires: ""` +means *no expiry set*, not *malformed*; the other six drive the parser, which rejects `""` as a +`date-time`. Python's suite asserts `test_empty_expires_never_expired` at the field level, so both +readings are deliberate in this repository, and **a boolean corpus carries only one.** + +**Reachability**, client path only, from untrusted input. Paths are relative to `go/protocols/mpp/`. + +| hop | file:line | what happens | +|---|---|---| +| 1 | `client/transport.go:38` | `PaymentTransport.RoundTrip`, an `http.RoundTripper` installed by `NewClient` (`:110`), on every request | +| 2 | `client/transport.go:56-60` | a remote `402` is parsed; `resp.Header` is whatever the remote sent | +| 3 | `wire/headers.go:81` | `ParseWWWAuthenticate(value)`, per header value | +| 4 | `wire/headers.go:60` | `Expires: params["expires"]`, a `map[string]string`: absent yields `""`, so absent and empty collapse | +| 5 | `wire/headers.go:68-70` | the required-field gate covers `ID`, `Realm`, `Intent`; `Expires` is required by nothing | +| 6 | `client/transport.go:65` to `:105-107` | `isSupportedChargeChallenge` filters on method and intent only; `grep -n "Expires" client/transport.go` returns nothing | +| 7 | `client/charge.go:295` | `challenge.IsExpired(time.Now())` is called | +| 8 | `wire/challenge.go:111-112` | the early return fires, returns `false`, the client proceeds to sign | + +A second entry point reaches the same guard: `ParseSessionChallenge` (`client/session.go:335`) and +`challenge_selection.go:112` feed the same unfiltered type. + +**The server path is blocked three ways, each sufficient alone.** (1) HMAC ordering: `Verify` precedes +the guard, `server/server.go:457` before `:461` and `server/session_method.go:380` before `:383`. +(2) `expires` is in the HMAC preimage, `challenge.go:105` passing `c.Expires` to `ComputeChallengeID` as +the sixth field (`:147`), so blanking it changes the ID and `Verify` fails first. (3) Both issuers +default it to five minutes, `server/server.go:375-378` and `server/session_method.go:349-352`; the Rust +issuers likewise at `charge.rs:537-538` and `:651-652`. + +**Defensive depth, not a vulnerability.** The early return is documented as deliberate two lines above +the call site (`client/charge.go:293-294`): + +```go +293 // #10: always refuse to sign an expired challenge. Challenges with no +294 // expiry are still accepted (the protocol allows omitting it). +``` + +Three confirmations that "no expiry means not expired" is convention rather than a Go defect: that +comment; Rust's `None => false` at `challenge.rs:175`; named tests in both trees (`challenge_test.go:46` +`TestIsExpiredEmptyString`, `challenge.rs:534` `challenge_not_expired_when_no_expires`). + +**The residual is the actionable part.** `NewChallengeWithSecret` (`wire/challenge.go:63-64`) is exported +public API hardcoding `expires: ""` into `NewChallengeWithSecretFull`, re-exported as +`mpp.NewChallengeWithSecret` (`core/mpp.go:68`). Its only non-test callers are the two issuers above, +which default the field, **so the safe default lives in the callers, not the constructor.** A downstream +server built on the exported constructor issues challenges HMAC-valid over an empty `expires` that reach +`server.go:461` / `session_method.go:383` and return "not expired" indefinitely. + +**A related Go / Rust divergence, recorded without taking a side.** + +| wire input | Rust, `server/charge.rs:855-857` | Go, `challenge.go:111` | +|---|---|---| +| `expires` absent | `None`, `if let` skipped, accepted | `""`, early return, accepted | +| `expires: ""` present | `Some("")`, `OffsetDateTime::parse` fails, rejected | `""`, early return, accepted | + +Rust's `Option` (`challenge.rs:17`) represents absent, present-empty and present-valid; Go's bare +`string` (`challenge.go:27`) cannot. The distinction does not survive the wire: `challenge.rs:212` folds +`None` and `Some("")` into one preimage with `expires.unwrap_or("")`, giving an identical challenge ID, +and `#[serde(skip_serializing_if = "Option::is_none")]` at `challenge.rs:16` means Rust never emits +`expires: ""`. **Which side is right is the maintainer's call.** Adding `expires` to the required-field +gate at `headers.go:68-70` makes the guard unreachable from the wire; conversely any server-side issuance +path emitting an empty `expires` raises the severity above defensive depth. The convention reading rests +on two implementations, two test suites and one source comment, not a written spec. + +### 4. php preserves six fractional digits exactly, truncates 7 to 9, and rejects 10 or more + +Issue #111 names the limit by hand: *"PHP's `%u` format only accepts 6"*. The import supplied a six-digit +vector, `jsts_date_time_001` = `1963-06-19T08:30:06.283185Z`, corpus ACCEPT. **All seven trees accept it, +and php preserves all six digits exactly.** + +| tree | verdict | parsed value | +|---|---|---| +| rust | ACCEPT | `1963-06-19 8:30:06.283185 +00:00:00` | +| go | ACCEPT | `1963-06-19T08:30:06.283185Z` | +| python | ACCEPT | `datetime(1963, 6, 19, 8, 30, 6, 283185, tzinfo=utc)` | +| ruby | ACCEPT | `1963-06-19T08:30:06.283185000Z` (nsec=283185000) | +| **php** | **ACCEPT** | **`1963-06-19T08:30:06.283185+00:00`** | +| lua | ACCEPT | `epoch=-206292594` = `1963-06-19T08:30:06Z` (lua's return type is a whole-second epoch) | +| typescript | ACCEPT | `epochMs=-206292593717` = `1963-06-19T08:30:06.283Z` | + +The limit is real and sits one digit further along. Executed against PHP 8.5.9: + +| input | result | +|---|---| +| `…06.283185Z` (6 digits) | **ACCEPT, `.283185` preserved exactly** | +| `…33.1234567Z` (7 digits) | ACCEPT, truncated to `.123456` | +| `…33.123456789Z` (9 digits) | ACCEPT, truncated to `.123456` | +| `…33.0000000000Z` (10 digits) | **REJECT (`parse()` returns null)** | +| `…33.9999999999999999Z` (16 digits) | **REJECT** | + +All five rows are shipped vectors: `jsts_date_time_001`, `secfrac_7_digits`, `secfrac_9_digits`, +`go_longfrac_10digits_0000000000`, `go_longfrac_16digits_9999999999999999`. All five are corpus ACCEPT, +so php's rejection of the last two is two of its 17 divergences. Two mechanisms, two lines: the regex at +`php/src/PayCore/Rfc3339Parser.php:26` bounds `time-secfrac` to `(?:\.(\d{1,9}))?`, producing the +rejection past 9; the truncation is `substr($frac, 0, 6)` at `:86`. On the same 9-digit input rust, go, +python and ruby preserve all nine digits (ruby `nsec=123456789`); lua and typescript truncate by return +type. **php is the only tree that loses fractional digits it claims to support.** + +### 5. The python SDK carries two RFC 3339 grammars and they disagree + +`_RFC3339_RE` at `types.py:17`, gating `_parse_rfc3339` at `types.py:28`, is the wired path. +`_ISO8601_RE` at `headers.py:32`, used at `headers.py:229` to gate receipt timestamps, is a second +grammar in the same package. Same interpreter, same run, same 124 inputs: **`_ISO8601_RE` disagrees with +the corpus on 33 of 124 where the wired `_parse_rfc3339` disagrees on 9.** + +Both directions fire. `_ISO8601_RE` is purely lexical and accepts calendar-impossible dates that +`_parse_rfc3339` rejects: `feb_30`, `month_13`, `non_leap_year_1900_feb_29`, `minute_60`, +`offset_hour_out_of_range` (`+24:00`); `_parse_rfc3339` accepts lower-case forms `_ISO8601_RE` rejects. +Two trailing-newline inputs, `jsts_date_time_027` and `trailing_newline_after_offset`, are corpus REJECT +and both match `_ISO8601_RE`, because python's `$` matches before a final newline. The 33 also include +**non-ASCII digits**: `jsts_date_time_021` = `1963-06-1৪T00:00:00Z` matches, Python's `\d` being +Unicode-aware. They gate different fields, so `_ISO8601_RE` is a second path, not part of the 992. + +> **Inference:** whether a timestamp is valid in that SDK depends on which function reaches it. Which +> function reaches a given field in production is not measured here. + +### 6. The leap second whose offset rolls the local date across midnight, and its control + +These two vectors are the sharpest discriminator in the file. + +| vector | input | corpus | rust | go | python | ruby | php | lua | typescript | +|---|---|---|---|---|---|---|---|---|---| +| `leap_second_offset_rolls_local_date_forward` | `1999-01-01T00:59:60+01:00` | **ACCEPT** | **ACCEPT** | REJECT | REJECT | ACCEPT | ACCEPT | ACCEPT | REJECT | +| `…_wrong_offset` (control) | `1999-01-01T00:59:60+02:00` | **REJECT** | **REJECT** | REJECT | REJECT | **ACCEPT** | **ACCEPT** | **ACCEPT** | REJECT | + +`00:59:60+01:00` resolves to `1998-12-31T23:59:60Z`, the final row of the RFC's Appendix D table; +`00:59:60+02:00` resolves to `1998-12-31T22:59:60Z`, not the leap-second point, and §5.7 says *"At all +other times the maximum value of time-second is '59'."* + +**Read as a pair: rust is the only tree that gets both right.** go, python and typescript reject every +`:60` and pass the control for the wrong reason, having no representable 61st second; ruby, php and lua +accept every `:60` and pass the first for the wrong reason, normalising `:60` away silently. rust rejects +the control with `time::Parse error: second was not in range`, the only implementation here that +evaluates the leap second after applying the offset. **Without the control, ruby, php and lua would have +scored a pass identical in shape to rust's and opposite in meaning.** + +### 7. A clean negative: no tree range-checks offsets against timezone data + +`time-numoffset = ("+" / "-") time-hour ":" time-minute` with `time-hour = 2DIGIT ; 00-23` and +`time-minute = 2DIGIT ; 00-59`, so `±23:59` is the widest offset §5.6 generates, well outside the ±14:00 +range any real timezone uses. `offset_max_syntactic_plus_23_59` and `offset_min_syntactic_minus_23_59` +are both corpus ACCEPT. **All seven trees accept both, with correct arithmetic.** Every tree resolved +`+23:59` to the same instant: lua `epoch=1769601660`, typescript `epochMs=1769601660000`, rust +`+23:59:00`, go `+23:59`, python `timedelta(seconds=86340)`, ruby `+23:59`, php `+23:59`; `1769601660` +decodes to `2026-01-28T12:01:00Z`, which is `2026-01-29T12:00:00` minus 23h59m. + +An adjacent case does fire. `offset_minute_out_of_range` (`+00:60`) is REJECT under §5.6, and go, python +and ruby accept it; python's returned value is the proof text, +`tzinfo=timezone(timedelta(seconds=3600))`, so `+00:60` became exactly `+01:00`. The import corroborated +this in a form this corpus had not written: `jsts_date_time_015` = `1990-12-31T10:00:00+10:60`, corpus +REJECT, accepted by the same three trees by the same mechanism. + +### 8. Trees that ACCEPT the same input and denote different instants + +The corpus verdict is ACCEPT or REJECT and nothing else, so this divergence class scores PASS everywhere. +On `rfc_5_8_example_leap_second_z` = `1990-12-31T23:59:60Z`, the RFC §5.8 worked example, corpus ACCEPT: + +| tree | verdict | instant returned | +|---|---|---| +| rust | ACCEPT | `1990-12-31 23:59:59.999999999 +00:00:00`, clamped down | +| php | ACCEPT | `1990-12-31T23:59:59.000000+00:00`, clamped down, fraction dropped | +| ruby | ACCEPT | `1991-01-01T00:00:00.000000000Z`, rolled forward into the next year | +| lua | ACCEPT | `epoch=662688000` = `1991-01-01T00:00:00Z`, rolled forward | +| go / python / typescript | REJECT | (none) | + +**Four trees accept and return two different instants, one second apart, across a year boundary, and a +boolean corpus scores all four as PASS.** On the finding 6 control vector, which the corpus rejects, the +three accepting trees split again: ruby `01:00:00+02:00`, php `00:59:59+02:00`, lua `epoch=915145200` +(`1999-01-01T01:00:00+02:00`). + +> **Inference:** for a field whose purpose is to say when a challenge stops being valid, a difference in +> the denoted instant is a difference in when it stops being valid. This corpus does not measure it. + +### 9. A space where §5.6 mandates a `T`, and a five-two split + +`2020-01-01 00:00:00Z`, imported from the upstream `date.json` suite with verdict REJECT. **rust and +typescript accept it; go, python, ruby, php and lua reject it.** + +``` +$ rust_runner probe.tsv +space_sep 1 2020-01-01 0:00:00.0 +00:00:00 +``` + +The `time` crate's `Rfc3339` well-known format accepts a space separator. §5.6's ABNF mandates `T` while +the same section's second NOTE contemplates separating full-date and full-time "by (say) a space +character", and one verdict cannot satisfy both; the upstream `date.json` case answers it sideways, +through a REJECT that judges `date` rather than `date-time`. **This vector does not ship, and the finding +is recorded on that basis:** it sits in the disputed list in `harness/vectors/mpp-protocol/README.md`, +and **how it should be verdicted is an open question, not settled here.** + +## The one vector RFC 3339 does not decide + +**`1582-10-10`**, from the upstream `date.json` suite, which asserts `valid: true` and describes it as +*"valid: date inside the Julian to Gregorian reform gap"*, a deliberate position. **It is marked +`UNRESOLVED-PROVENANCE` and does not ship in the vector file**, its absence independently verified. The +date fell inside the ten days, 5 to 14 October 1582, skipped when the Gregorian calendar replaced the +Julian one: under a proleptic Gregorian calendar it exists, under the historical calendar it never +occurred. RFC 3339 says which calendar it profiles and never says which reading applies. + +| section | what it says | why it does not decide | +|---|---|---| +| §5.6 | `full-date = date-fullyear "-" date-month "-" date-mday`, `4DIGIT`/`2DIGIT` fields | Purely syntactic. `1582-10-10` matches. The grammar cannot express "this day did not occur". | +| §5.7 | the `date-mday` maximum table: October to 31 | Constrains the maximum day number only; 10 is at most 31. Reading its silence as permission is inference, not citation. | +| §2 | "leap year" defined on the Gregorian calendar, with the /4, /100, /400 rule | Arithmetic about leap years, not calendar adoption. "The Gregorian calendar" is unqualified. | +| Appendix C | the same rule in sample C code | Says nothing about 1582. | +| §1 / Abstract | "…using the Gregorian calendar" | One unqualified noun phrase; it does not distinguish the two readings. | +| §3, Appendix D | two-digit years; leap seconds | Neither is about calendar adoption. | + +**The strongest argument for deciding it, and why it was not taken.** §2's leap-year arithmetic is +applied in this same corpus to years more than a millennium before the Gregorian calendar existed: +`0100-02-29` is REJECT and `0400-02-29` is ACCEPT, decided purely by the /100 and /400 rule. Year 0100 +*was* a Julian leap year, so `0100-02-29` was a real date and RFC 3339's rule rejects it. **RFC 3339 +therefore applies Gregorian arithmetic proleptically, and the word "proleptic" appears nowhere in it** +(`grep -niE "gregorian|julian|proleptic|calendar"` over the pinned text returns four hits, none +addressing the reform). That is an inference from the document's structure, not a statement in it. +**The suite is right that an implementation has to do something. It is not right that RFC 3339 told it +what.** + +## Limitations + +- **The boolean axis is blind to the instant divergence in finding 8.** No golden instant is asserted for + ACCEPT scenarios, so finding 8's four instants all score PASS. Same limit on php's truncation in + finding 4: it accepts 7- and 9-digit fractions the corpus accepts, truncates both to six, passes both. +- **Two trees cannot be conformance-tested through their own public surfaces.** rust's `is_expired()` + collapses parse failure and past date into one fail-closed bool and reads `OffsetDateTime::now_utc()` + internally; `server/session.rs:64` collapses both into `Error::ChallengeExpired`; `server/charge.rs:857` + distinguishes but is reachable only after a constant-time HMAC challenge-ID check. typescript's two + expiry checks are module-private and the package ships no RFC 3339 parser module. Hence + `delegated-parser` on both. +- **Kotlin and Swift are not measured.** Neither is on `langs` in `harness/divergence-raw.json` nor in + `harness/protocol-runners/`. pay-kit carries nine SDK trees; seven are measured here. +- **Python 3.9 has no measurement.** The SDK does not load on it. +- **No `expires` verdict is compared against a normalised instant, a timezone database, or a clock.** The + corpus asserts grammar and calendar validity per RFC 3339 and nothing else. +- **Neither import contains a leap second in a positive offset that rolls the local date across + midnight.** The only vectors covering it are the two in finding 6, written for this corpus; the + upstream `time` file covers the offset arithmetic on `full-time`, which has no date to roll. + +## Reproduce + +**Six of the seven trees exit non-zero, and that is the correct result**, so the commands are not chained +with `&&`. **Every line states the number of tests it must run**, because a wrong `-run` pattern or a +stale path exits 0 on several of these toolchains. + +``` +# 1. Hash gate. If it does not match, nothing below applies. From the repository root: +shasum -a 256 harness/vectors/mpp-protocol/expires.json +# 72f90dc9c9e6d7c373fcae7893e1d9f0c537ecc69fc7397773d5b006a78e0e3d exit 0 + +# 2. The seven trees. Run each; none depends on another. + +# go from the repository root; -C go because the go module root is go/. -v or no count is printed +go -C go test -v ./protocols/mpp/wire/ -run TestRFC3339ConformanceCorpus # exit 1, 16 FAIL of 124 + +# python from python/ +pytest tests/test_expires.py # exit 1, 9 FAIL of 124 + +# rust from rust/ +cargo test -p solana-pay-kit rfc3339_conformance_corpus # exit 0, 0 FAIL of 124 + +# typescript from typescript/ +npx vitest run packages/mpp/src/__tests__/client-charge-validation.test.ts # exit 1, 44 FAIL of 248 + +# ruby from ruby/; needs ruby >= 3.1, see the prerequisites table +bundle exec ruby test/pay_core/expires_rfc3339_test.rb # exit 1, 18 FAIL of 124 + +# php from php/; composer install first +./vendor/bin/phpunit tests/PayCore/Rfc3339Test.php # exit 1, 17 FAIL of 124 + +# lua from lua/; the suite's runner is tests/test_helper.lua in-repo, not busted +lua -e "package.path=table.concat({'./?.lua','./?/init.lua',package.path},';'); \ + require('tests.expires_rfc3339_spec'); require('tests.test_helper').run()" +# exit 1, 1 of 8 tests fails, printing "16 of 124 vectors diverge" +``` + +The numbers those commands printed, not a prediction. The `fail` column counts corpus vectors and equals +the headline table row for row. + +| tree | working directory | exit | tests run | corpus vectors driven | pass | **fail** | +|---|---|---|---|---|---|---| +| go | repo root | 1 | 125 (`-v` to see them) | 124 | 108 | **16** | +| python | `python/` | 1 | 144 | 124 | 135 | **9** | +| rust | `rust/` | **0** | 3 | 124 | 3 | **0** | +| typescript | `typescript/` | 1 | 272 | 248 (124 x 2) | 228 | **44** (22 per call site) | +| ruby | `ruby/` | 1 | 130 | 124 | 112 | **18** | +| php | `php/` | 1 | 145 | 124 | 128 | **17** | +| lua | `lua/` | 1 | 8 | 124 | 7 | **1**, reporting 16 of 124 diverging | + +**Every tree drives all 124 scenarios. There is no filter and no skip.** Six enforce it by assertion, as +in rust's `rfc3339_conformance_corpus_exercises_every_scenario` or lua's +`t.assert_true(admitted == #corpus.scenarios, ...)`. **go is the exception:** it ranges directly over the +decoded array, so its count is visible in the run rather than asserted. + +**Two invocation notes, each of which cost a wrong answer once.** `-run Expires` matches none of the 14 +test functions in `challenge_test.go` and go reports `PASS … [no tests to run]` with exit 0; the symbol +is `TestRFC3339ConformanceCorpus`. The rust filter must be `rfc3339_conformance_corpus`, not `expires`, +which selects 16 unrelated helper tests and runs none of the three conformance tests. + +### The same eight commands, as one command + +Same commands, same directories, from the repository root. **No `&&`**, and each entry runs in its own +subshell so no `cd` leaks. The go line carries `-v` because without it go prints no count. + +``` +for t in gate go python rust typescript ruby php lua; do \ + printf '\n===== %s =====\n' "$t"; \ + ( case $t in \ + gate) shasum -a 256 harness/vectors/mpp-protocol/expires.json ;; \ + go) go -C go test -v ./protocols/mpp/wire/ -run TestRFC3339ConformanceCorpus ;; \ + python) cd python; pytest tests/test_expires.py ;; \ + rust) cd rust; cargo test -p solana-pay-kit rfc3339_conformance_corpus ;; \ + typescript) cd typescript; npx vitest run packages/mpp/src/__tests__/client-charge-validation.test.ts ;; \ + ruby) cd ruby; bundle exec ruby test/pay_core/expires_rfc3339_test.rb ;; \ + php) cd php; ./vendor/bin/phpunit tests/PayCore/Rfc3339Test.php ;; \ + lua) cd lua; lua -e "package.path=table.concat({'./?.lua','./?/init.lua',package.path},';'); require('tests.expires_rfc3339_spec'); require('tests.test_helper').run()" ;; \ + esac ); \ + printf '===== %s exit %d =====\n' "$t" "$?"; \ +done +``` + +Each entry's output prints in full between the delimiters, greppable with `| grep '^====='`. Executed +from the repository root, the eight delimiters printed: + +``` +===== gate exit 0 ===== # 72f90dc9…e0e3d +===== go exit 1 ===== # 125 "=== RUN" (1 parent + 124 subtests), 108 PASS, 16 FAIL +===== python exit 1 ===== # 9 failed, 135 passed (144) +===== rust exit 0 ===== # running 3 tests; 3 passed, 0 failed +===== typescript exit 1 ===== # 44 failed | 228 passed (272) +===== ruby exit 1 ===== # 130 runs, 155 assertions, 18 failures, 0 errors +===== php exit 1 ===== # Tests: 145, Assertions: 153, Failures: 17 +===== lua exit 1 ===== # 7 tests passed, 0 skipped, 1 failed, "16 of 124 vectors diverge" +``` + +### Prerequisites per tree + +Every row was established by running the command above, not by reading a manifest. + +| tree | prerequisite | probe | +|---|---|---| +| go | none beyond the toolchain | `go version` gives `go1.26.5 darwin/arm64` | +| python | the SDK with dev extras (`uv sync --extra dev`, or `pip install -e ".[dev]"` in a 3.12 venv) | `python3.12 --version` gives `Python 3.12.13` | +| rust | a resolvable workspace. **It does not resolve offline**: `cargo test --offline` fails on a `solana-bpf-loader-program` version conflict via `litesvm`, pre-existing and unrelated to these vectors | `cargo --version` gives `1.96.0` | +| typescript | `pnpm install` in `typescript/` | `node --version` gives `v26.0.0` | +| ruby | `bundle install`, **and ruby >= 3.1**, see below | `ruby --version` | +| php | `composer install` in `php/` | `php --version` gives `8.5.9` | +| lua | none. The runner is `lua/tests/test_helper.lua`, in-repo; busted is not a dependency. **`lua tests/run.lua` (the whole suite) additionally needs `luasodium` from luarocks; the single-spec invocation above does not** | `lua -v` gives `Lua 5.5.1` | + +**The ruby row.** `bundle exec` cannot run under ruby 2.6.10: the gemspec requires `ed25519 ~> 1.4` +(ruby >= 3.0) and `minitest ~> 5.25` (ruby >= 3.1), so the suite will not load, and a reader on system +ruby sees `Gem::GemNotFoundException` rather than a result. The row was measured under ruby 4.0.6, and +ruby's parser returns the same 18 failures on the same 124 vectors under 2.6.10p210, so the count is not +runtime-sensitive. diff --git a/harness/vectors/mpp-protocol/README.md b/harness/vectors/mpp-protocol/README.md index 454268b61..7a0269e10 100644 --- a/harness/vectors/mpp-protocol/README.md +++ b/harness/vectors/mpp-protocol/README.md @@ -149,7 +149,7 @@ below, with the rule that excluded each one. #### Unresolved provenance (1) -A vector whose verdict could not be tied to an RFC 3339 section by the S4 audit does not ship here. It travels in EVIDENCE.md as a finding about the specification, which is its entire purpose. +A vector whose verdict could not be tied to an RFC 3339 section does not ship here. It travels in the RFC 3339 `expires` conformance results at [`docs/mpp-expires-rfc3339-conformance.md`](../../../docs/mpp-expires-rfc3339-conformance.md) as a finding about the specification, which is its entire purpose. - **`jsts_date_070`**, input `1582-10-10` (upstream ACCEPT) — UNRESOLVED-PROVENANCE. RFC 3339 does not decide whether a date inside the 1582 Julian-to-Gregorian reform gap is valid: §5.6 is purely syntactic, §5.7 constrains only the maximum mday, and neither §2 nor Appendix C qualifies "the Gregorian calendar" as proleptic or historical. The upstream suite asserts valid: true. From a81ad60568ed817ddf8f4dfdef8719ff9f05bf0e Mon Sep 17 00:00:00 2001 From: Michael Moffett Date: Tue, 11 Aug 2026 18:06:45 -0600 Subject: [PATCH 06/15] chore(mpp): run prettier and rustfmt on the expires conformance tests Two files this branch added were committed without the repo's formatters having been run over them, and CI is red on both checks. typescript/packages/mpp/src/__tests__/client-charge-validation.test.ts fails `pnpm format:check`: the corpus URL constant was hand-wrapped across four lines where prettier's print width puts it on one. rust/crates/kit/src/mpp/protocol/core/challenge.rs fails `cargo fmt --check` with four diffs, at the corpus read, the description field extraction, the non-empty assertion and the Rfc3339 parse call. That crate's 842 library tests pass; the job is red on formatting alone. Both fixes are the formatter's own output, produced by `prettier --write` on the one file and `cargo fmt` from rust/, not by hand-editing toward what the formatter was expected to want. No other file in the tree was reformatted. Nothing in either file changed except layout. Stripping all whitespace from both versions leaves, in the TypeScript file, one removed trailing comma and nothing else; and in the Rust file, one removed trailing comma plus the braces rustfmt wraps a closure body in when it moves that body onto its own line. No identifier, literal, operator or call site differs. The Rust library suite is 842 passed, 0 failed, before and after. The prettier failure is the one named in review. The cargo fmt failure is the same defect from the same cause in the other tree, so it is fixed here rather than left for a second round. --- .../kit/src/mpp/protocol/core/challenge.rs | 23 +++++++++++-------- .../client-charge-validation.test.ts | 5 +--- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/rust/crates/kit/src/mpp/protocol/core/challenge.rs b/rust/crates/kit/src/mpp/protocol/core/challenge.rs index 181e2a738..94c7b0cbc 100644 --- a/rust/crates/kit/src/mpp/protocol/core/challenge.rs +++ b/rust/crates/kit/src/mpp/protocol/core/challenge.rs @@ -843,8 +843,9 @@ mod tests { /// `(name, input, expect_accept, description)` for every corpus scenario. fn conformance_vectors() -> Vec<(String, String, bool, String)> { - let raw = std::fs::read_to_string(CONFORMANCE_CORPUS) - .unwrap_or_else(|e| panic!("conformance corpus unreadable at {CONFORMANCE_CORPUS}: {e}")); + let raw = std::fs::read_to_string(CONFORMANCE_CORPUS).unwrap_or_else(|e| { + panic!("conformance corpus unreadable at {CONFORMANCE_CORPUS}: {e}") + }); let corpus: serde_json::Value = serde_json::from_str(&raw).expect("conformance corpus must be valid JSON"); @@ -861,7 +862,10 @@ mod tests { scenario["name"].as_str().unwrap_or_default().to_string(), scenario["input"].as_str().unwrap_or_default().to_string(), expect_accept, - scenario["description"].as_str().unwrap_or_default().to_string(), + scenario["description"] + .as_str() + .unwrap_or_default() + .to_string(), ) }) .collect() @@ -870,15 +874,16 @@ mod tests { #[test] fn rfc3339_conformance_corpus() { let vectors = conformance_vectors(); - assert!(!vectors.is_empty(), "conformance corpus carried zero scenarios"); + assert!( + !vectors.is_empty(), + "conformance corpus carried zero scenarios" + ); let mut divergences = Vec::new(); for (name, input, expect_accept, description) in &vectors { - let accepted = time::OffsetDateTime::parse( - input, - &time::format_description::well_known::Rfc3339, - ) - .is_ok(); + let accepted = + time::OffsetDateTime::parse(input, &time::format_description::well_known::Rfc3339) + .is_ok(); if accepted != *expect_accept { divergences.push(format!( "{name} ({description}): input {input:?} — corpus expects {}, \ diff --git a/typescript/packages/mpp/src/__tests__/client-charge-validation.test.ts b/typescript/packages/mpp/src/__tests__/client-charge-validation.test.ts index 3b9167665..aa42c0ace 100644 --- a/typescript/packages/mpp/src/__tests__/client-charge-validation.test.ts +++ b/typescript/packages/mpp/src/__tests__/client-charge-validation.test.ts @@ -479,10 +479,7 @@ interface ConformanceScenario { tests: { parse: true | { success: boolean } }; } -const CORPUS_URL = new URL( - '../../../../../harness/vectors/mpp-protocol/expires.json', - import.meta.url, -); +const CORPUS_URL = new URL('../../../../../harness/vectors/mpp-protocol/expires.json', import.meta.url); const corpus = JSON.parse(readFileSync(CORPUS_URL, 'utf8')) as { scenarios: ConformanceScenario[] }; const vectors = corpus.scenarios; From aed2638147a957f422835361c7d3a0f5639e1dd8 Mon Sep 17 00:00:00 2001 From: Michael Moffett Date: Tue, 11 Aug 2026 19:13:10 -0600 Subject: [PATCH 07/15] docs(mpp): correct the direction of the typescript expires divergences Finding 2 claimed every one of the 22 typescript divergences was an over-acceptance and that not one was a parse failure. Counted over the 124 scenarios in harness/vectors/mpp-protocol/expires.json with the same two expressions the call sites evaluate, the split is 16 over-acceptances and 6 the other way. The 6 are the leap-second date-times -- jsts_date_time_005, jsts_date_time_006, leap_second_june_month_end, leap_second_offset_rolls_local_date_forward, rfc_5_8_example_leap_second_offset, rfc_5_8_example_leap_second_z -- which the corpus accepts under RFC 3339 section 5.7 and which V8 rejects, having no representable 61st second. The document already carried the correction elsewhere and contradicted itself: finding 6's table records leap_second_offset_rolls_local_date_forward as corpus ACCEPT / typescript REJECT, and finding 6's prose states that go, python and typescript reject every :60. Finding 2 now agrees with both. Docs only. The corpus, the vectors and every test are unchanged. --- docs/mpp-expires-rfc3339-conformance.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/mpp-expires-rfc3339-conformance.md b/docs/mpp-expires-rfc3339-conformance.md index f3cebdb36..f82cd5164 100644 --- a/docs/mpp-expires-rfc3339-conformance.md +++ b/docs/mpp-expires-rfc3339-conformance.md @@ -102,9 +102,14 @@ date-time. `Charge.ts:402` `assertChallengeNotExpired` evaluates `new Date(expires).getTime()` at `:404`; `Session.ts:346` `assertChallengeOpenNotExpired` evaluates `Date.parse(expires)` at `:348`. Both resolve at `16777d46`, are the same V8 algorithm, and return byte-identical verdicts on all 124 scenarios. -**Every one of the 22 divergences at each call site is an over-acceptance, an input the corpus rejects -that this path accepts; not one is a parse failure.** Rows 1-2 are direct probes, rows 3-5 the shipped -vectors `jsts_date_time_010`, `jsts_date_time_013`, `jsts_date_time_016`. +**16 of the 22 divergences at each call site are over-acceptances, an input the corpus rejects that this +path accepts; the remaining 6 run the other way.** Those 6 are the leap-second `date-time`s — +`jsts_date_time_005`, `jsts_date_time_006`, `leap_second_june_month_end`, +`leap_second_offset_rolls_local_date_forward`, `rfc_5_8_example_leap_second_offset`, +`rfc_5_8_example_leap_second_z` — all corpus ACCEPT under §5.7 and all rejected here, V8 having no +representable 61st second; finding 6 reads one of them against its control. The over-acceptances follow. +Rows 1-2 are direct probes, rows 3-5 the shipped vectors `jsts_date_time_010`, `jsts_date_time_013`, +`jsts_date_time_016`. | input | `Date.parse` result | RFC 3339 | |---|---|---| From 6b4778104f494abffe938faef133545d08199245 Mon Sep 17 00:00:00 2001 From: Michael Moffett Date: Tue, 8 Sep 2026 08:29:37 -0600 Subject: [PATCH 08/15] test(mpp): use the shared RFC 3339 parser in the expires corpus --- .../client-charge-validation.test.ts | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/typescript/packages/mpp/src/__tests__/client-charge-validation.test.ts b/typescript/packages/mpp/src/__tests__/client-charge-validation.test.ts index aa42c0ace..93465fef7 100644 --- a/typescript/packages/mpp/src/__tests__/client-charge-validation.test.ts +++ b/typescript/packages/mpp/src/__tests__/client-charge-validation.test.ts @@ -451,27 +451,28 @@ test('#10 createCredential rejects a network that does not match expectedNetwork // Every scenario in the file runs. There is no slice to select and no scenario // to skip. // -// WHAT THIS ASSERTS, PRECISELY. This package has no RFC 3339 parser module. -// The two expiry call sites delegate the whole parse to the JS engine: +// WHAT THIS ASSERTS, PRECISELY. The verdict source is `parseRfc3339`, the +// package's own parser. Both expiry call sites delegate their whole parse to it: // // client/Charge.ts:404 assertChallengeNotExpired -// -> new Date(expires).getTime(); NaN => "malformed" -// server/Session.ts:347 assertChallengeOpenNotExpired -// -> Date.parse(expires); NaN => "must be an RFC3339 timestamp" +// server/Session.ts:358 assertChallengeOpenNotExpired // // Both guards are module-private and neither package exposes a public surface -// that returns a parse verdict, so this test evaluates the same two engine -// expressions those call sites evaluate. The verdict source is therefore the -// *delegated* parser (the engine), not an SDK-owned parser — a real -// distinction, and the reason `expires` conformance here is a property of the -// Node/V8 version as much as of this SDK. If a parser is ever exported, repoint -// `verdicts` at it and the vectors carry over unchanged. +// that returns a parse verdict, so the corpus is asserted against the parser +// those two consume rather than against either guard. Evaluating an engine +// expression here instead would report the Node/V8 version's conformance and +// stay green whether or not this SDK was ever fixed. // -// Both call sites are checked, because they are two different expressions and a -// divergence between them would itself be a client/server protocol bug. +// `shared/rfc3339.ts` arrives with #286; until that lands this file has nothing +// to import and does not typecheck, which is the intended failure. +// +// Both call sites are checked, because a divergence between them would itself +// be a client/server protocol bug. import { readFileSync } from 'node:fs'; +import { parseRfc3339 } from '../shared/rfc3339.js'; + interface ConformanceScenario { name: string; description: string; @@ -500,23 +501,23 @@ test('every RFC 3339 corpus scenario is exercised', () => { for (const scenario of vectors) { test(`RFC 3339 corpus / charge call site / ${scenario.name}`, () => { // client/Charge.ts:404 - const accepted = !Number.isNaN(new Date(scenario.input).getTime()); + const accepted = !Number.isNaN(parseRfc3339(scenario.input)); expect( accepted, `${scenario.name} (${scenario.description}): input ${JSON.stringify(scenario.input)} — ` + `corpus expects ${expectsAccept(scenario) ? 'ACCEPT' : 'REJECT'}, ` + - `new Date(...).getTime() reports ${accepted ? 'ACCEPT' : 'REJECT'}`, + `parseRfc3339(...) reports ${accepted ? 'ACCEPT' : 'REJECT'}`, ).toBe(expectsAccept(scenario)); }); test(`RFC 3339 corpus / session call site / ${scenario.name}`, () => { - // server/Session.ts:347 - const accepted = !Number.isNaN(Date.parse(scenario.input)); + // server/Session.ts:358 + const accepted = !Number.isNaN(parseRfc3339(scenario.input)); expect( accepted, `${scenario.name} (${scenario.description}): input ${JSON.stringify(scenario.input)} — ` + `corpus expects ${expectsAccept(scenario) ? 'ACCEPT' : 'REJECT'}, ` + - `Date.parse(...) reports ${accepted ? 'ACCEPT' : 'REJECT'}`, + `parseRfc3339(...) reports ${accepted ? 'ACCEPT' : 'REJECT'}`, ).toBe(expectsAccept(scenario)); }); } From 19ac605f1ce542c2bc8b575a69faa9a3f0e9101a Mon Sep 17 00:00:00 2001 From: Michael Moffett Date: Tue, 8 Sep 2026 08:29:43 -0600 Subject: [PATCH 09/15] docs(mpp): re-derive expires conformance results on main --- docs/mpp-expires-rfc3339-conformance.md | 200 ++++++++++++++---------- 1 file changed, 119 insertions(+), 81 deletions(-) diff --git a/docs/mpp-expires-rfc3339-conformance.md b/docs/mpp-expires-rfc3339-conformance.md index f82cd5164..479f49500 100644 --- a/docs/mpp-expires-rfc3339-conformance.md +++ b/docs/mpp-expires-rfc3339-conformance.md @@ -8,26 +8,40 @@ vectors vendored at `harness/vectors/mpp-protocol/expires.json` (issue #111). 70 REJECT), 36,470 B, sha256 `72f90dc9c9e6d7c373fcae7893e1d9f0c537ecc69fc7397773d5b006a78e0e3d`. - Parse paths: go, lua, php, python, ruby, rust, typescript; typescript has two call sites, so eight. - Operation `expires.parse`. Verdict axis ACCEPT or REJECT. Cells: 124 x 8 = 992. -- Source lines resolve at `16777d465f4446c7ac519378663d6d4bbf394877`, the merge base. +- Source lines resolve at `48ea8aa43fb555d55fd9c3365a4a045ea96f1618`, this branch merged with main + `c143bfabef82dbdf01dcff7a52ab29bf166d7eb4`. +- **The typescript rows measure `parseRfc3339`, which arrives with #286** (head `cda74a47`) and is not on + main. It was applied to the worktree to take the measurement and is not committed here. Without it the + typescript suite fails to load and runs no tests; it does not go green. +- **php was not re-executed on this run.** composer is absent from the host, so `php/vendor/` and + `vendor/bin/phpunit` do not exist. `git diff --name-only 16777d46 c143bfab -- php/` returns nothing, so + the php parse path is byte-identical and the row is carried from the `16777d46` measurement. Every table + that uses it says so. - Sources, upstream shas, licences, the dedupe rule and every settled scenario not shipped are in `harness/vectors/mpp-protocol/README.md`. ## Headline -**The corpus is not green: 120 of 992 cells diverge, 12.1%. One parse path of eight passes all 124.** +**The corpus is not green: 88 of 992 cells diverge, 8.9%. One parse path of eight passes all 124.** Every divergence is a verdict disagreement, not a crash; no cell produced anything but ACCEPT or REJECT. | parse path | scenarios | pass | **fail** | |---|---|---|---| | rust | 124 | 124 | **0** | +| typescript, charge call site | 124 | 118 | **6** | +| typescript, session call site | 124 | 118 | **6** | | python | 124 | 115 | **9** | | go | 124 | 108 | **16** | | lua | 124 | 108 | **16** | -| php | 124 | 107 | **17** | +| php (carried, see header) | 124 | 107 | **17** | | ruby | 124 | 106 | **18** | -| typescript, charge call site | 124 | 102 | **22** | -| typescript, session call site | 124 | 102 | **22** | -| **total** | **992** | **872** | **120** | +| **total** | **992** | **904** | **88** | + +**Both typescript rows moved, 22 to 6 each, and #286 is the whole reason.** Its `parseRfc3339` rejects +every one of the 16 over-acceptances the engine admitted; the 6 that remain are the leap seconds, which no +`Date`-backed implementation can represent. Nothing on main moved a count: `#283` rewrote +`server/Session.ts`, `core/types.py` and `challenge.rs`, and go, lua, php, python, ruby and rust returned +the same counts they did at `16777d46`. **Scope.** Every scenario in the file is an `expires` verdict, so there is no filter and no skip; each tree asserts that the count it exercised equals the count in the file. The 104 settled `full-date` and @@ -48,17 +62,22 @@ expression the SDK delegates to. The harness contains no RFC 3339 parser written | tree | parse path (file:line) | verdict source | runtime | **fail of 124** | |---|---|---|---|---| -| rust | `rust/crates/kit/src/mpp/protocol/core/challenge.rs:173-184` `PaymentChallenge::is_expired` to `time::OffsetDateTime::parse(s, &Rfc3339)` | `delegated-parser` | `cargo 1.96.0`, `time` crate `0.3.55` | **0** | +| rust | `rust/crates/kit/src/mpp/protocol/core/challenge.rs:173-184` `PaymentChallenge::is_expired` to `time::OffsetDateTime::parse(s, &Rfc3339)` | `delegated-parser` | `cargo 1.96.1`, `time` crate `0.3.55` | **0** | +| typescript, charge | `typescript/packages/mpp/src/client/Charge.ts:402` `assertChallengeNotExpired`, a consumer of `parseRfc3339` under #286 | `repo-parser` | `node v26.0.0` | **6** | +| typescript, session | `typescript/packages/mpp/src/server/Session.ts:356` `assertChallengeOpenNotExpired`, the other consumer | `repo-parser` | `node v26.0.0` | **6** | | python | `python/src/solana_pay_kit/protocols/mpp/core/types.py:28` `_parse_rfc3339` (regex `_RFC3339_RE` at `:17`, then `datetime.fromisoformat`) | `repo-parser` | `Python 3.12.13` | **9** | | go | `go/protocols/mpp/wire/challenge.go:110-119` `PaymentChallenge.IsExpired(now)` | `repo-parser` | `go1.26.5 darwin/arm64` | **16** | | lua | `lua/pay_kit/protocols/mpp/expires.lua:27` `M.parse_rfc3339` | `repo-parser` | `Lua 5.5.1` | **16** | -| php | `php/src/PayCore/Rfc3339Parser.php:37` `Rfc3339Parser::parse` | `repo-parser` | `PHP 8.5.9 (cli)` | **17** | +| php | `php/src/PayCore/Rfc3339Parser.php:37` `Rfc3339Parser::parse` | `repo-parser` | `PHP 8.5.9 (cli)`, carried | **17** | | ruby | `ruby/lib/pay_core/rfc3339_parser.rb:28` `Rfc3339Parser.parse` | `repo-parser` | `ruby 4.0.6` | **18** | -| typescript, charge | `typescript/packages/mpp/src/client/Charge.ts:402` `assertChallengeNotExpired` to `new Date(expires).getTime()` at `:404` | `delegated-parser` | `node v26.0.0` | **22** | -| typescript, session | `typescript/packages/mpp/src/server/Session.ts:346` `assertChallengeOpenNotExpired` to `Date.parse(expires)` at `:348` | `delegated-parser` | `node v26.0.0` | **22** | + +**The two typescript line numbers are the guards on the merged tree, where they still call the engine.** +`parseRfc3339` lives at `typescript/packages/mpp/src/shared/rfc3339.ts` on #286, which repoints both to it; +that file is not on `48ea8aa` and the numbers above will shift by one when it lands. Both rows are +`repo-parser` on that basis, and byte-identical: one function now answers for both call sites. **Every row is a property of (SDK source x that runtime), not of the SDK alone.** -`rust/crates/kit/Cargo.toml:153` pins `time = "0.3"` with no lockfile, so a fresh resolve takes whatever +`rust/crates/kit/Cargo.toml:163` pins `time = "0.3"` with no lockfile, so a fresh resolve takes whatever `0.3.x` is current; this run resolved `0.3.55`. pay-kit's Lua rockspecs target Lua 5.1 and 5.4; this run used Lua 5.5.1. @@ -78,11 +97,12 @@ Reported as unwired rather than as 124 disagreements. Nine findings, each a measurement over the shipped file unless stated otherwise. Divergence is measured, exploitability is not. Consequence is inference, at most once per finding, in a `> **Inference:**` block. -### 1. An offsetless date-time is host-dependent in the typescript path +### 1. An offsetless date-time was host-dependent in the typescript path, and #286 closes it `offset_absent` (`2026-01-29T12:00:00`, corpus REJECT) is the only offsetless date-time in the file, found by regex `^\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}:\d{2}(\.\d+)?$` over all 124 inputs, not by name. Six -trees reject it: rust, go, python, ruby, php, lua. TypeScript accepts it and reads it in host local time: +trees reject it: rust, go, python, ruby, php, lua. Measured at `16777d46`, the engine accepted it and read +it in host local time: ``` TZ=UTC 2026-01-29T12:00:00 -> 2026-01-29T12:00:00.000Z @@ -91,60 +111,65 @@ TZ=Asia/Tokyo 2026-01-29T12:00:00 -> 2026-01-29T03:00:00.000Z ``` §5.6 makes the offset mandatory in `date-time` and §4.4 states why; ECMA-262 reads an offsetless form as -local. **Limit:** one vector, written from the RFC text, since neither import has an offsetless -date-time. +local. **`parseRfc3339` rejects it**, its grammar terminating in `[Zz]` or a `±hh:mm` offset with no third +alternative, so all seven trees now agree and the host-dependence is gone. **Limit:** one vector, written +from the RFC text, since neither import has an offsetless date-time. -> **Inference:** for any input omitting an offset, the verdict and instant this path produces are -> properties of the host as well as the input. How such an input reaches this path is not measured. +### 2. The typescript path evaluated ECMA-262 date-time strings, not RFC 3339; #286 closes 16 of 22 -### 2. The typescript path evaluates ECMA-262 date-time strings, not RFC 3339 +At `16777d46` `Charge.ts:402` `assertChallengeNotExpired` evaluated `new Date(expires).getTime()` and +`Session.ts:346` `assertChallengeOpenNotExpired` evaluated `Date.parse(expires)` — the same V8 algorithm, +byte-identical verdicts, 22 divergences each. **16 of the 22 were over-acceptances, an input the corpus +rejects that the path accepted; the remaining 6 ran the other way.** #286's `parseRfc3339` rejects all 16 +and leaves the 6, so both rows are now 6. This is the finding the PR was filed on and the measurement is +kept; what changed is that only the second half of it is still live. -`Charge.ts:402` `assertChallengeNotExpired` evaluates `new Date(expires).getTime()` at `:404`; -`Session.ts:346` `assertChallengeOpenNotExpired` evaluates `Date.parse(expires)` at `:348`. Both resolve -at `16777d46`, are the same V8 algorithm, and return byte-identical verdicts on all 124 scenarios. -**16 of the 22 divergences at each call site are over-acceptances, an input the corpus rejects that this -path accepts; the remaining 6 run the other way.** Those 6 are the leap-second `date-time`s — -`jsts_date_time_005`, `jsts_date_time_006`, `leap_second_june_month_end`, -`leap_second_offset_rolls_local_date_forward`, `rfc_5_8_example_leap_second_offset`, -`rfc_5_8_example_leap_second_z` — all corpus ACCEPT under §5.7 and all rejected here, V8 having no -representable 61st second; finding 6 reads one of them against its control. The over-acceptances follow. -Rows 1-2 are direct probes, rows 3-5 the shipped vectors `jsts_date_time_010`, `jsts_date_time_013`, -`jsts_date_time_016`. +**The 16 over-acceptances, all now REJECT.** Rows 1-2 and 5 were direct probes at `16777d46`; the rest are +shipped vectors. Every `parseRfc3339` verdict below is from a run of the corpus through #286's file. -| input | `Date.parse` result | RFC 3339 | -|---|---|---| -| `2026-01-01` | `2026-01-01T00:00:00.000Z`, accepted | REJECT, a date-only form is not a `date-time` | -| `2026-01-01T12:00` | `2026-01-01T18:00:00.000Z`, accepted | REJECT, `time-second` is mandatory | -| `1990-02-31T15:59:59.123-08:00` | `1990-03-03T23:59:59.123Z`, rolled over | REJECT, §5.7 February maximum | -| `1990-12-31T24:00:00Z` | `1991-01-01T00:00:00.000Z`, rolled over | REJECT, §5.7 hour 00-23 | -| `06/19/1963 08:30:06 PST` | `1963-06-19T16:30:06.000Z`, accepted | REJECT, not §5.6 syntax at all | +| input | at `16777d46` | `parseRfc3339` | RFC 3339 | +|---|---|---|---| +| `2026-01-01` | `2026-01-01T00:00:00.000Z`, accepted | REJECT | REJECT, a date-only form is not a `date-time` | +| `2026-01-01T12:00` | `2026-01-01T18:00:00.000Z`, accepted | REJECT | REJECT, `time-second` is mandatory | +| `1990-02-31T15:59:59.123-08:00` | `1990-03-03T23:59:59.123Z`, rolled over | REJECT | REJECT, §5.7 February maximum | +| `1990-12-31T24:00:00Z` | `1991-01-01T00:00:00.000Z`, rolled over | REJECT | REJECT, §5.7 hour 00-23 | +| `06/19/1963 08:30:06 PST` | `1963-06-19T16:30:06.000Z`, accepted | REJECT | REJECT, not §5.6 syntax at all | -Five shipped vectors take the calendar-rollover path: all corpus REJECT, all accepted, `Session.ts` -matching `Charge.ts` on each. `epochMs` is read from the results file; the instant beside it is decoded. +Five shipped vectors took the calendar-rollover path: all corpus REJECT, all accepted at `16777d46`, +`Session.ts` matching `Charge.ts` on each. All five are REJECT under `parseRfc3339`, which range-checks +`date-mday` against a leap-year-aware month table rather than letting `Date` normalise the overflow away. -| vector | input | `Charge.ts` result | rolls to | -|---|---|---|---| -| `feb_30` | `2026-02-30T00:00:00Z` | ACCEPT, `epochMs=1772409600000` | `2026-03-02T00:00:00.000Z` | -| `sep_31` | `2026-09-31T00:00:00Z` | ACCEPT, `epochMs=1790812800000` | `2026-10-01T00:00:00.000Z` | -| `apr_31` | `2026-04-31T00:00:00Z` | ACCEPT, `epochMs=1777593600000` | `2026-05-01T00:00:00.000Z` | -| `non_leap_year_1900_feb_29` | `1900-02-29T00:00:00Z` | ACCEPT, `epochMs=-2203891200000` | `1900-03-01T00:00:00.000Z` | -| `hour_24` | `2026-01-29T24:00:00Z` | ACCEPT, `epochMs=1769731200000` | `2026-01-30T00:00:00.000Z` | +| vector | input | at `16777d46` | rolled to | `parseRfc3339` | +|---|---|---|---|---| +| `feb_30` | `2026-02-30T00:00:00Z` | ACCEPT, `epochMs=1772409600000` | `2026-03-02T00:00:00.000Z` | REJECT | +| `sep_31` | `2026-09-31T00:00:00Z` | ACCEPT, `epochMs=1790812800000` | `2026-10-01T00:00:00.000Z` | REJECT | +| `apr_31` | `2026-04-31T00:00:00Z` | ACCEPT, `epochMs=1777593600000` | `2026-05-01T00:00:00.000Z` | REJECT | +| `non_leap_year_1900_feb_29` | `1900-02-29T00:00:00Z` | ACCEPT, `epochMs=-2203891200000` | `1900-03-01T00:00:00.000Z` | REJECT | +| `hour_24` | `2026-01-29T24:00:00Z` | ACCEPT, `epochMs=1769731200000` | `2026-01-30T00:00:00.000Z` | REJECT | -**Three further divergences are bare-production forms. The third is the sharpest in the finding.** +**Three further divergences were bare-production forms. The third was the sharpest in the finding.** -| vector | input | corpus | `new Date(x)` / `Date.parse(x)` resolves to | -|---|---|---|---| -| `bare_date_no_time` | `1963-06-19` | REJECT | `1963-06-19T00:00:00.000Z`, silently becomes midnight UTC | -| `bare_date_leap_day` | `2020-02-29` | REJECT | `2020-02-29T00:00:00.000Z`, same | -| `bare_time_leap_second` | `23:59:60Z` | REJECT | **`1960-01-01T23:59:00.000Z`**, read as year 1960 with the leap second truncated to `:00` | +| vector | input | corpus | at `16777d46` | `parseRfc3339` | +|---|---|---|---|---| +| `bare_date_no_time` | `1963-06-19` | REJECT | `1963-06-19T00:00:00.000Z`, silently midnight UTC | REJECT | +| `bare_date_leap_day` | `2020-02-29` | REJECT | `2020-02-29T00:00:00.000Z`, same | REJECT | +| `bare_time_leap_second` | `23:59:60Z` | REJECT | **`1960-01-01T23:59:00.000Z`**, read as year 1960 with the leap second truncated to `:00` | REJECT | -An `expires` of `23:59:60Z` does not fail here; it becomes an instant in 1960, which every comparison -reads as long expired. `bare_time_zulu` (`08:30:06Z`) and the other three bare-time shapes are correctly -rejected, so the defect is specific to the leap-second form. +An `expires` of `23:59:60Z` did not fail at `16777d46`; it became an instant in 1960, which every +comparison reads as long expired. `bare_time_zulu` (`08:30:06Z`) and the other three bare-time shapes were +already rejected, so the defect was specific to the leap-second form. All three are REJECT under +`parseRfc3339`. + +**The 6 that remain**, all corpus ACCEPT under §5.7, all REJECT through `parseRfc3339`: +`jsts_date_time_005`, `jsts_date_time_006`, `leap_second_june_month_end`, +`leap_second_offset_rolls_local_date_forward`, `rfc_5_8_example_leap_second_offset`, +`rfc_5_8_example_leap_second_z`. The mechanism is unchanged and is not a regex: `parseRfc3339` bounds +`second` at 59 and, past that gate, still returns `Date.parse` of a normalised string, so it inherits +V8's lack of a representable 61st second. Finding 6 reads one of them against its control. Tracked in #284. ### 3. An empty `expires` short-circuits the go guard and returns not-expired without parsing -`go/protocols/mpp/wire/challenge.go`, read off disk at `16777d46`: +`go/protocols/mpp/wire/challenge.go`, read off disk at `48ea8aa`, byte-identical to `16777d46`: ```go 110: func (c PaymentChallenge) IsExpired(now time.Time) bool { @@ -186,7 +211,7 @@ the guard, `server/server.go:457` before `:461` and `server/session_method.go:38 (2) `expires` is in the HMAC preimage, `challenge.go:105` passing `c.Expires` to `ComputeChallengeID` as the sixth field (`:147`), so blanking it changes the ID and `Verify` fails first. (3) Both issuers default it to five minutes, `server/server.go:375-378` and `server/session_method.go:349-352`; the Rust -issuers likewise at `charge.rs:537-538` and `:651-652`. +issuers likewise at `charge.rs:541-542` and `:655-656`. **Defensive depth, not a vulnerability.** The early return is documented as deliberate two lines above the call site (`client/charge.go:293-294`): @@ -197,8 +222,8 @@ the call site (`client/charge.go:293-294`): ``` Three confirmations that "no expiry means not expired" is convention rather than a Go defect: that -comment; Rust's `None => false` at `challenge.rs:175`; named tests in both trees (`challenge_test.go:46` -`TestIsExpiredEmptyString`, `challenge.rs:534` `challenge_not_expired_when_no_expires`). +comment; Rust's `None => false` at `challenge.rs:175`; named tests in both trees (`challenge_test.go:48` +`TestIsExpiredEmptyString`, `challenge.rs:610` `challenge_not_expired_when_no_expires`). **The residual is the actionable part.** `NewChallengeWithSecret` (`wire/challenge.go:63-64`) is exported public API hardcoding `expires: ""` into `NewChallengeWithSecretFull`, re-exported as @@ -332,10 +357,12 @@ three accepting trees split again: ruby `01:00:00+02:00`, php `00:59:59+02:00`, > **Inference:** for a field whose purpose is to say when a challenge stops being valid, a difference in > the denoted instant is a difference in when it stops being valid. This corpus does not measure it. -### 9. A space where §5.6 mandates a `T`, and a five-two split +### 9. A space where §5.6 mandates a `T`, and a six-one split -`2020-01-01 00:00:00Z`, imported from the upstream `date.json` suite with verdict REJECT. **rust and -typescript accept it; go, python, ruby, php and lua reject it.** +`2020-01-01 00:00:00Z`, imported from the upstream `date.json` suite with verdict REJECT. **rust accepts +it; go, python, ruby, php, lua and typescript reject it.** At `16777d46` typescript accepted it too and +the split was five-two; `parseRfc3339` mandates `[Tt]` between `full-date` and `full-time`, so rust is now +alone. ``` $ rust_runner probe.tsv @@ -382,12 +409,12 @@ what.** - **The boolean axis is blind to the instant divergence in finding 8.** No golden instant is asserted for ACCEPT scenarios, so finding 8's four instants all score PASS. Same limit on php's truncation in finding 4: it accepts 7- and 9-digit fractions the corpus accepts, truncates both to six, passes both. -- **Two trees cannot be conformance-tested through their own public surfaces.** rust's `is_expired()` +- **One tree cannot be conformance-tested through its own public surface.** rust's `is_expired()` collapses parse failure and past date into one fail-closed bool and reads `OffsetDateTime::now_utc()` - internally; `server/session.rs:64` collapses both into `Error::ChallengeExpired`; `server/charge.rs:857` - distinguishes but is reachable only after a constant-time HMAC challenge-ID check. typescript's two - expiry checks are module-private and the package ships no RFC 3339 parser module. Hence - `delegated-parser` on both. + internally; `server/session.rs:67-69` collapses both into `Error::ChallengeExpired`; `server/charge.rs:857` + distinguishes but is reachable only after a constant-time HMAC challenge-ID check. Hence + `delegated-parser` on rust. typescript's two expiry guards are still module-private, but #286 exports + `parseRfc3339` beneath them, which is what the corpus now drives. - **Kotlin and Swift are not measured.** Neither is on `langs` in `harness/divergence-raw.json` nor in `harness/protocol-runners/`. pay-kit carries nine SDK trees; seven are measured here. - **Python 3.9 has no measurement.** The SDK does not load on it. @@ -399,9 +426,10 @@ what.** ## Reproduce -**Six of the seven trees exit non-zero, and that is the correct result**, so the commands are not chained -with `&&`. **Every line states the number of tests it must run**, because a wrong `-run` pattern or a -stale path exits 0 on several of these toolchains. +**Five of the seven trees exit non-zero on a divergence, and that is the correct result**, so the commands +are not chained with `&&`. rust exits 0; php exits 127 on this host because its runner is not installed, +which is an absence, not a result. **Every line states the number of tests it must run**, because a wrong +`-run` pattern or a stale path exits 0 on several of these toolchains. ``` # 1. Hash gate. If it does not match, nothing below applies. From the repository root: @@ -419,13 +447,13 @@ pytest tests/test_expires.py # exit # rust from rust/ cargo test -p solana-pay-kit rfc3339_conformance_corpus # exit 0, 0 FAIL of 124 -# typescript from typescript/ -npx vitest run packages/mpp/src/__tests__/client-charge-validation.test.ts # exit 1, 44 FAIL of 248 +# typescript from typescript/; needs #286 merged, else the suite fails to load and runs nothing +npx vitest run packages/mpp/src/__tests__/client-charge-validation.test.ts # exit 1, 12 FAIL of 248 # ruby from ruby/; needs ruby >= 3.1, see the prerequisites table bundle exec ruby test/pay_core/expires_rfc3339_test.rb # exit 1, 18 FAIL of 124 -# php from php/; composer install first +# php from php/; composer install first. NOT RE-RUN on this pass, see the prerequisites table ./vendor/bin/phpunit tests/PayCore/Rfc3339Test.php # exit 1, 17 FAIL of 124 # lua from lua/; the suite's runner is tests/test_helper.lua in-repo, not busted @@ -442,11 +470,15 @@ the headline table row for row. | go | repo root | 1 | 125 (`-v` to see them) | 124 | 108 | **16** | | python | `python/` | 1 | 144 | 124 | 135 | **9** | | rust | `rust/` | **0** | 3 | 124 | 3 | **0** | -| typescript | `typescript/` | 1 | 272 | 248 (124 x 2) | 228 | **44** (22 per call site) | +| typescript | `typescript/` | 1 | 272 | 248 (124 x 2) | 260 | **12** (6 per call site) | | ruby | `ruby/` | 1 | 130 | 124 | 112 | **18** | -| php | `php/` | 1 | 145 | 124 | 128 | **17** | +| php | `php/` | — | — | — | — | **not re-run**, composer absent | | lua | `lua/` | 1 | 8 | 124 | 7 | **1**, reporting 16 of 124 diverging | +**The typescript row is with `shared/rfc3339.ts` from #286 in the worktree.** Without it the run is exit 1, +`Cannot find module '../shared/rfc3339.js'`, 1 failed suite and **no tests** — the suite fails closed +rather than passing on an engine expression, which is the point of driving the SDK's own parser. + **Every tree drives all 124 scenarios. There is no filter and no skip.** Six enforce it by assertion, as in rust's `rfc3339_conformance_corpus_exercises_every_scenario` or lua's `t.assert_true(admitted == #corpus.scenarios, ...)`. **go is the exception:** it ranges directly over the @@ -480,19 +512,25 @@ done ``` Each entry's output prints in full between the delimiters, greppable with `| grep '^====='`. Executed -from the repository root, the eight delimiters printed: +from the repository root with #286's `shared/rfc3339.ts` in the worktree, the eight delimiters printed: ``` ===== gate exit 0 ===== # 72f90dc9…e0e3d ===== go exit 1 ===== # 125 "=== RUN" (1 parent + 124 subtests), 108 PASS, 16 FAIL ===== python exit 1 ===== # 9 failed, 135 passed (144) ===== rust exit 0 ===== # running 3 tests; 3 passed, 0 failed -===== typescript exit 1 ===== # 44 failed | 228 passed (272) +===== typescript exit 1 ===== # 12 failed | 260 passed (272) ===== ruby exit 1 ===== # 130 runs, 155 assertions, 18 failures, 0 errors -===== php exit 1 ===== # Tests: 145, Assertions: 153, Failures: 17 +===== php exit 127 ===== # ./vendor/bin/phpunit: no such file or directory ===== lua exit 1 ===== # 7 tests passed, 0 skipped, 1 failed, "16 of 124 vectors diverge" ``` +**php is `127`, not `1`: the binary is absent, so nothing ran.** composer is not installed on this host and +`php/vendor/` was never created. The php row in every table above is the `16777d46` measurement, carried +because no file under `php/` differs between `16777d46` and `c143bfab`. Re-running the same loop **without** +#286's file moves only the typescript line, to `exit 1` with `Cannot find module '../shared/rfc3339.js'` +and no tests run. + ### Prerequisites per tree Every row was established by running the command above, not by reading a manifest. @@ -501,10 +539,10 @@ Every row was established by running the command above, not by reading a manifes |---|---|---| | go | none beyond the toolchain | `go version` gives `go1.26.5 darwin/arm64` | | python | the SDK with dev extras (`uv sync --extra dev`, or `pip install -e ".[dev]"` in a 3.12 venv) | `python3.12 --version` gives `Python 3.12.13` | -| rust | a resolvable workspace. **It does not resolve offline**: `cargo test --offline` fails on a `solana-bpf-loader-program` version conflict via `litesvm`, pre-existing and unrelated to these vectors | `cargo --version` gives `1.96.0` | -| typescript | `pnpm install` in `typescript/` | `node --version` gives `v26.0.0` | -| ruby | `bundle install`, **and ruby >= 3.1**, see below | `ruby --version` | -| php | `composer install` in `php/` | `php --version` gives `8.5.9` | +| rust | a resolvable workspace. **It does not resolve offline**: `cargo test --offline` fails on a `solana-bpf-loader-program` version conflict via `litesvm`, pre-existing and unrelated to these vectors | `cargo --version` gives `1.96.1` | +| typescript | `pnpm install` in `typescript/`, **and #286 merged** for `../shared/rfc3339.js` to resolve | `node --version` gives `v26.0.0` | +| ruby | `bundle install`, **and ruby >= 3.1**, see below | `ruby --version` gives `4.0.6` | +| php | `composer install` in `php/`. **composer is absent from the host this pass ran on**, so the php row was not re-measured | `php --version` gives `8.5.9`; `which composer` gives nothing | | lua | none. The runner is `lua/tests/test_helper.lua`, in-repo; busted is not a dependency. **`lua tests/run.lua` (the whole suite) additionally needs `luasodium` from luarocks; the single-spec invocation above does not** | `lua -v` gives `Lua 5.5.1` | **The ruby row.** `bundle exec` cannot run under ruby 2.6.10: the gemspec requires `ed25519 ~> 1.4` From f4befc9b1480f6c214a5c9165c2a6ef999415767 Mon Sep 17 00:00:00 2001 From: Michael Moffett Date: Tue, 8 Sep 2026 09:22:57 -0600 Subject: [PATCH 10/15] test(mpp): one corpus loop, comments to file norm --- .../client-charge-validation.test.ts | 49 ++----------------- 1 file changed, 3 insertions(+), 46 deletions(-) diff --git a/typescript/packages/mpp/src/__tests__/client-charge-validation.test.ts b/typescript/packages/mpp/src/__tests__/client-charge-validation.test.ts index 93465fef7..f5d589666 100644 --- a/typescript/packages/mpp/src/__tests__/client-charge-validation.test.ts +++ b/typescript/packages/mpp/src/__tests__/client-charge-validation.test.ts @@ -441,33 +441,8 @@ test('#10 createCredential rejects a network that does not match expectedNetwork await expect(method.createCredential({ challenge })).rejects.toThrow(/does not match the expected network/); }); -// ── Cross-SDK RFC 3339 conformance corpus (issue #111) ── -// -// Vectors live in `harness/vectors/mpp-protocol/expires.json` under the -// `expires.parse` operation. Every SDK asserts the same ACCEPT / REJECT verdict -// against the same vectors, so a divergence between two SDKs shows up as a -// failing test in exactly one of them rather than as silence. -// -// Every scenario in the file runs. There is no slice to select and no scenario -// to skip. -// -// WHAT THIS ASSERTS, PRECISELY. The verdict source is `parseRfc3339`, the -// package's own parser. Both expiry call sites delegate their whole parse to it: -// -// client/Charge.ts:404 assertChallengeNotExpired -// server/Session.ts:358 assertChallengeOpenNotExpired -// -// Both guards are module-private and neither package exposes a public surface -// that returns a parse verdict, so the corpus is asserted against the parser -// those two consume rather than against either guard. Evaluating an engine -// expression here instead would report the Node/V8 version's conformance and -// stay green whether or not this SDK was ever fixed. -// -// `shared/rfc3339.ts` arrives with #286; until that lands this file has nothing -// to import and does not typecheck, which is the intended failure. -// -// Both call sites are checked, because a divergence between them would itself -// be a client/server protocol bug. +// Cross-SDK RFC 3339 conformance corpus (issue #111). +// Vectors: harness/vectors/mpp-protocol/expires.json, operation `expires.parse`. import { readFileSync } from 'node:fs'; @@ -485,33 +460,15 @@ const CORPUS_URL = new URL('../../../../../harness/vectors/mpp-protocol/expires. const corpus = JSON.parse(readFileSync(CORPUS_URL, 'utf8')) as { scenarios: ConformanceScenario[] }; const vectors = corpus.scenarios; -// `"tests": {"parse": true}` is ACCEPT; `{"parse": {"success": false, …}}` is -// REJECT. Identical to the encoding the other vector files in the same -// directory use. const expectsAccept = (scenario: ConformanceScenario) => scenario.tests.parse === true; -// Guard the loader so a regression in it cannot go silent: every scenario in -// the file is exercised, and a truncated or empty read fails here rather than -// passing quietly with nothing to run. test('every RFC 3339 corpus scenario is exercised', () => { expect(vectors.length).toBe(corpus.scenarios.length); expect(vectors.length).toBeGreaterThan(0); }); for (const scenario of vectors) { - test(`RFC 3339 corpus / charge call site / ${scenario.name}`, () => { - // client/Charge.ts:404 - const accepted = !Number.isNaN(parseRfc3339(scenario.input)); - expect( - accepted, - `${scenario.name} (${scenario.description}): input ${JSON.stringify(scenario.input)} — ` + - `corpus expects ${expectsAccept(scenario) ? 'ACCEPT' : 'REJECT'}, ` + - `parseRfc3339(...) reports ${accepted ? 'ACCEPT' : 'REJECT'}`, - ).toBe(expectsAccept(scenario)); - }); - - test(`RFC 3339 corpus / session call site / ${scenario.name}`, () => { - // server/Session.ts:358 + test(`RFC 3339 corpus / ${scenario.name}`, () => { const accepted = !Number.isNaN(parseRfc3339(scenario.input)); expect( accepted, From 87c3f47ff341a4b45f25e68c459ba6783680f1e9 Mon Sep 17 00:00:00 2001 From: Michael Moffett Date: Tue, 8 Sep 2026 09:26:57 -0600 Subject: [PATCH 11/15] docs(mpp): report expires results at this sha --- docs/mpp-expires-rfc3339-conformance.md | 183 ++++++++++-------------- 1 file changed, 75 insertions(+), 108 deletions(-) diff --git a/docs/mpp-expires-rfc3339-conformance.md b/docs/mpp-expires-rfc3339-conformance.md index 479f49500..24970c0d1 100644 --- a/docs/mpp-expires-rfc3339-conformance.md +++ b/docs/mpp-expires-rfc3339-conformance.md @@ -10,38 +10,29 @@ vectors vendored at `harness/vectors/mpp-protocol/expires.json` (issue #111). - Operation `expires.parse`. Verdict axis ACCEPT or REJECT. Cells: 124 x 8 = 992. - Source lines resolve at `48ea8aa43fb555d55fd9c3365a4a045ea96f1618`, this branch merged with main `c143bfabef82dbdf01dcff7a52ab29bf166d7eb4`. -- **The typescript rows measure `parseRfc3339`, which arrives with #286** (head `cda74a47`) and is not on - main. It was applied to the worktree to take the measurement and is not committed here. Without it the - typescript suite fails to load and runs no tests; it does not go green. -- **php was not re-executed on this run.** composer is absent from the host, so `php/vendor/` and - `vendor/bin/phpunit` do not exist. `git diff --name-only 16777d46 c143bfab -- php/` returns nothing, so - the php parse path is byte-identical and the row is carried from the `16777d46` measurement. Every table - that uses it says so. +- #286 (`parseRfc3339`) is not on main; applied to the worktree it takes both TypeScript rows to 6 (leap + seconds only). +- php was not re-executed: composer is absent from the host, `git diff --name-only 16777d46 c143bfab -- php/` + returns no paths, so the row is carried from the `16777d46` measurement. - Sources, upstream shas, licences, the dedupe rule and every settled scenario not shipped are in `harness/vectors/mpp-protocol/README.md`. ## Headline -**The corpus is not green: 88 of 992 cells diverge, 8.9%. One parse path of eight passes all 124.** +**The corpus is not green: 120 of 992 cells diverge, 12.1%. One parse path of eight passes all 124.** Every divergence is a verdict disagreement, not a crash; no cell produced anything but ACCEPT or REJECT. | parse path | scenarios | pass | **fail** | |---|---|---|---| | rust | 124 | 124 | **0** | -| typescript, charge call site | 124 | 118 | **6** | -| typescript, session call site | 124 | 118 | **6** | | python | 124 | 115 | **9** | | go | 124 | 108 | **16** | | lua | 124 | 108 | **16** | -| php (carried, see header) | 124 | 107 | **17** | +| php | 124 | 107 | **17** | | ruby | 124 | 106 | **18** | -| **total** | **992** | **904** | **88** | - -**Both typescript rows moved, 22 to 6 each, and #286 is the whole reason.** Its `parseRfc3339` rejects -every one of the 16 over-acceptances the engine admitted; the 6 that remain are the leap seconds, which no -`Date`-backed implementation can represent. Nothing on main moved a count: `#283` rewrote -`server/Session.ts`, `core/types.py` and `challenge.rs`, and go, lua, php, python, ruby and rust returned -the same counts they did at `16777d46`. +| typescript, charge call site | 124 | 102 | **22** | +| typescript, session call site | 124 | 102 | **22** | +| **total** | **992** | **872** | **120** | **Scope.** Every scenario in the file is an `expires` verdict, so there is no filter and no skip; each tree asserts that the count it exercised equals the count in the file. The 104 settled `full-date` and @@ -63,18 +54,13 @@ expression the SDK delegates to. The harness contains no RFC 3339 parser written | tree | parse path (file:line) | verdict source | runtime | **fail of 124** | |---|---|---|---|---| | rust | `rust/crates/kit/src/mpp/protocol/core/challenge.rs:173-184` `PaymentChallenge::is_expired` to `time::OffsetDateTime::parse(s, &Rfc3339)` | `delegated-parser` | `cargo 1.96.1`, `time` crate `0.3.55` | **0** | -| typescript, charge | `typescript/packages/mpp/src/client/Charge.ts:402` `assertChallengeNotExpired`, a consumer of `parseRfc3339` under #286 | `repo-parser` | `node v26.0.0` | **6** | -| typescript, session | `typescript/packages/mpp/src/server/Session.ts:356` `assertChallengeOpenNotExpired`, the other consumer | `repo-parser` | `node v26.0.0` | **6** | | python | `python/src/solana_pay_kit/protocols/mpp/core/types.py:28` `_parse_rfc3339` (regex `_RFC3339_RE` at `:17`, then `datetime.fromisoformat`) | `repo-parser` | `Python 3.12.13` | **9** | | go | `go/protocols/mpp/wire/challenge.go:110-119` `PaymentChallenge.IsExpired(now)` | `repo-parser` | `go1.26.5 darwin/arm64` | **16** | | lua | `lua/pay_kit/protocols/mpp/expires.lua:27` `M.parse_rfc3339` | `repo-parser` | `Lua 5.5.1` | **16** | -| php | `php/src/PayCore/Rfc3339Parser.php:37` `Rfc3339Parser::parse` | `repo-parser` | `PHP 8.5.9 (cli)`, carried | **17** | +| php | `php/src/PayCore/Rfc3339Parser.php:37` `Rfc3339Parser::parse` | `repo-parser` | `PHP 8.5.9 (cli)` | **17** | | ruby | `ruby/lib/pay_core/rfc3339_parser.rb:28` `Rfc3339Parser.parse` | `repo-parser` | `ruby 4.0.6` | **18** | - -**The two typescript line numbers are the guards on the merged tree, where they still call the engine.** -`parseRfc3339` lives at `typescript/packages/mpp/src/shared/rfc3339.ts` on #286, which repoints both to it; -that file is not on `48ea8aa` and the numbers above will shift by one when it lands. Both rows are -`repo-parser` on that basis, and byte-identical: one function now answers for both call sites. +| typescript, charge | `typescript/packages/mpp/src/client/Charge.ts:402` `assertChallengeNotExpired` to `new Date(expires).getTime()` at `:404` | `delegated-parser` | `node v26.0.0` | **22** | +| typescript, session | `typescript/packages/mpp/src/server/Session.ts:356` `assertChallengeOpenNotExpired` to `Date.parse(expires)` at `:358` | `delegated-parser` | `node v26.0.0` | **22** | **Every row is a property of (SDK source x that runtime), not of the SDK alone.** `rust/crates/kit/Cargo.toml:163` pins `time = "0.3"` with no lockfile, so a fresh resolve takes whatever @@ -97,12 +83,11 @@ Reported as unwired rather than as 124 disagreements. Nine findings, each a measurement over the shipped file unless stated otherwise. Divergence is measured, exploitability is not. Consequence is inference, at most once per finding, in a `> **Inference:**` block. -### 1. An offsetless date-time was host-dependent in the typescript path, and #286 closes it +### 1. An offsetless date-time is host-dependent in the typescript path `offset_absent` (`2026-01-29T12:00:00`, corpus REJECT) is the only offsetless date-time in the file, found by regex `^\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}:\d{2}(\.\d+)?$` over all 124 inputs, not by name. Six -trees reject it: rust, go, python, ruby, php, lua. Measured at `16777d46`, the engine accepted it and read -it in host local time: +trees reject it: rust, go, python, ruby, php, lua. TypeScript accepts it and reads it in host local time: ``` TZ=UTC 2026-01-29T12:00:00 -> 2026-01-29T12:00:00.000Z @@ -111,61 +96,56 @@ TZ=Asia/Tokyo 2026-01-29T12:00:00 -> 2026-01-29T03:00:00.000Z ``` §5.6 makes the offset mandatory in `date-time` and §4.4 states why; ECMA-262 reads an offsetless form as -local. **`parseRfc3339` rejects it**, its grammar terminating in `[Zz]` or a `±hh:mm` offset with no third -alternative, so all seven trees now agree and the host-dependence is gone. **Limit:** one vector, written -from the RFC text, since neither import has an offsetless date-time. +local. **Limit:** one vector, written from the RFC text, since neither import has an offsetless +date-time. -### 2. The typescript path evaluated ECMA-262 date-time strings, not RFC 3339; #286 closes 16 of 22 +> **Inference:** for any input omitting an offset, the verdict and instant this path produces are +> properties of the host as well as the input. How such an input reaches this path is not measured. -At `16777d46` `Charge.ts:402` `assertChallengeNotExpired` evaluated `new Date(expires).getTime()` and -`Session.ts:346` `assertChallengeOpenNotExpired` evaluated `Date.parse(expires)` — the same V8 algorithm, -byte-identical verdicts, 22 divergences each. **16 of the 22 were over-acceptances, an input the corpus -rejects that the path accepted; the remaining 6 ran the other way.** #286's `parseRfc3339` rejects all 16 -and leaves the 6, so both rows are now 6. This is the finding the PR was filed on and the measurement is -kept; what changed is that only the second half of it is still live. +### 2. The typescript path evaluates ECMA-262 date-time strings, not RFC 3339 -**The 16 over-acceptances, all now REJECT.** Rows 1-2 and 5 were direct probes at `16777d46`; the rest are -shipped vectors. Every `parseRfc3339` verdict below is from a run of the corpus through #286's file. - -| input | at `16777d46` | `parseRfc3339` | RFC 3339 | -|---|---|---|---| -| `2026-01-01` | `2026-01-01T00:00:00.000Z`, accepted | REJECT | REJECT, a date-only form is not a `date-time` | -| `2026-01-01T12:00` | `2026-01-01T18:00:00.000Z`, accepted | REJECT | REJECT, `time-second` is mandatory | -| `1990-02-31T15:59:59.123-08:00` | `1990-03-03T23:59:59.123Z`, rolled over | REJECT | REJECT, §5.7 February maximum | -| `1990-12-31T24:00:00Z` | `1991-01-01T00:00:00.000Z`, rolled over | REJECT | REJECT, §5.7 hour 00-23 | -| `06/19/1963 08:30:06 PST` | `1963-06-19T16:30:06.000Z`, accepted | REJECT | REJECT, not §5.6 syntax at all | +`Charge.ts:402` `assertChallengeNotExpired` evaluates `new Date(expires).getTime()` at `:404`; +`Session.ts:356` `assertChallengeOpenNotExpired` evaluates `Date.parse(expires)` at `:358`. Both resolve +at `48ea8aa`, are the same V8 algorithm, and return byte-identical verdicts on all 124 scenarios. +**16 of the 22 divergences at each call site are over-acceptances, an input the corpus rejects that this +path accepts; the remaining 6 run the other way.** Those 6 are the leap-second `date-time`s — +`jsts_date_time_005`, `jsts_date_time_006`, `leap_second_june_month_end`, +`leap_second_offset_rolls_local_date_forward`, `rfc_5_8_example_leap_second_offset`, +`rfc_5_8_example_leap_second_z` — all corpus ACCEPT under §5.7 and all rejected here, V8 having no +representable 61st second; finding 6 reads one of them against its control. The over-acceptances follow. +Rows 1-2 are direct probes, rows 3-5 the shipped vectors `jsts_date_time_010`, `jsts_date_time_013`, +`jsts_date_time_016`. -Five shipped vectors took the calendar-rollover path: all corpus REJECT, all accepted at `16777d46`, -`Session.ts` matching `Charge.ts` on each. All five are REJECT under `parseRfc3339`, which range-checks -`date-mday` against a leap-year-aware month table rather than letting `Date` normalise the overflow away. +| input | `Date.parse` result | RFC 3339 | +|---|---|---| +| `2026-01-01` | `2026-01-01T00:00:00.000Z`, accepted | REJECT, a date-only form is not a `date-time` | +| `2026-01-01T12:00` | `2026-01-01T18:00:00.000Z`, accepted | REJECT, `time-second` is mandatory | +| `1990-02-31T15:59:59.123-08:00` | `1990-03-03T23:59:59.123Z`, rolled over | REJECT, §5.7 February maximum | +| `1990-12-31T24:00:00Z` | `1991-01-01T00:00:00.000Z`, rolled over | REJECT, §5.7 hour 00-23 | +| `06/19/1963 08:30:06 PST` | `1963-06-19T16:30:06.000Z`, accepted | REJECT, not §5.6 syntax at all | -| vector | input | at `16777d46` | rolled to | `parseRfc3339` | -|---|---|---|---|---| -| `feb_30` | `2026-02-30T00:00:00Z` | ACCEPT, `epochMs=1772409600000` | `2026-03-02T00:00:00.000Z` | REJECT | -| `sep_31` | `2026-09-31T00:00:00Z` | ACCEPT, `epochMs=1790812800000` | `2026-10-01T00:00:00.000Z` | REJECT | -| `apr_31` | `2026-04-31T00:00:00Z` | ACCEPT, `epochMs=1777593600000` | `2026-05-01T00:00:00.000Z` | REJECT | -| `non_leap_year_1900_feb_29` | `1900-02-29T00:00:00Z` | ACCEPT, `epochMs=-2203891200000` | `1900-03-01T00:00:00.000Z` | REJECT | -| `hour_24` | `2026-01-29T24:00:00Z` | ACCEPT, `epochMs=1769731200000` | `2026-01-30T00:00:00.000Z` | REJECT | +Five shipped vectors take the calendar-rollover path: all corpus REJECT, all accepted, `Session.ts` +matching `Charge.ts` on each. `epochMs` is read from the results file; the instant beside it is decoded. -**Three further divergences were bare-production forms. The third was the sharpest in the finding.** +| vector | input | `Charge.ts` result | rolls to | +|---|---|---|---| +| `feb_30` | `2026-02-30T00:00:00Z` | ACCEPT, `epochMs=1772409600000` | `2026-03-02T00:00:00.000Z` | +| `sep_31` | `2026-09-31T00:00:00Z` | ACCEPT, `epochMs=1790812800000` | `2026-10-01T00:00:00.000Z` | +| `apr_31` | `2026-04-31T00:00:00Z` | ACCEPT, `epochMs=1777593600000` | `2026-05-01T00:00:00.000Z` | +| `non_leap_year_1900_feb_29` | `1900-02-29T00:00:00Z` | ACCEPT, `epochMs=-2203891200000` | `1900-03-01T00:00:00.000Z` | +| `hour_24` | `2026-01-29T24:00:00Z` | ACCEPT, `epochMs=1769731200000` | `2026-01-30T00:00:00.000Z` | -| vector | input | corpus | at `16777d46` | `parseRfc3339` | -|---|---|---|---|---| -| `bare_date_no_time` | `1963-06-19` | REJECT | `1963-06-19T00:00:00.000Z`, silently midnight UTC | REJECT | -| `bare_date_leap_day` | `2020-02-29` | REJECT | `2020-02-29T00:00:00.000Z`, same | REJECT | -| `bare_time_leap_second` | `23:59:60Z` | REJECT | **`1960-01-01T23:59:00.000Z`**, read as year 1960 with the leap second truncated to `:00` | REJECT | +**Three further divergences are bare-production forms. The third is the sharpest in the finding.** -An `expires` of `23:59:60Z` did not fail at `16777d46`; it became an instant in 1960, which every -comparison reads as long expired. `bare_time_zulu` (`08:30:06Z`) and the other three bare-time shapes were -already rejected, so the defect was specific to the leap-second form. All three are REJECT under -`parseRfc3339`. +| vector | input | corpus | `new Date(x)` / `Date.parse(x)` resolves to | +|---|---|---|---| +| `bare_date_no_time` | `1963-06-19` | REJECT | `1963-06-19T00:00:00.000Z`, silently becomes midnight UTC | +| `bare_date_leap_day` | `2020-02-29` | REJECT | `2020-02-29T00:00:00.000Z`, same | +| `bare_time_leap_second` | `23:59:60Z` | REJECT | **`1960-01-01T23:59:00.000Z`**, read as year 1960 with the leap second truncated to `:00` | -**The 6 that remain**, all corpus ACCEPT under §5.7, all REJECT through `parseRfc3339`: -`jsts_date_time_005`, `jsts_date_time_006`, `leap_second_june_month_end`, -`leap_second_offset_rolls_local_date_forward`, `rfc_5_8_example_leap_second_offset`, -`rfc_5_8_example_leap_second_z`. The mechanism is unchanged and is not a regex: `parseRfc3339` bounds -`second` at 59 and, past that gate, still returns `Date.parse` of a normalised string, so it inherits -V8's lack of a representable 61st second. Finding 6 reads one of them against its control. Tracked in #284. +An `expires` of `23:59:60Z` does not fail here; it becomes an instant in 1960, which every comparison +reads as long expired. `bare_time_zulu` (`08:30:06Z`) and the other three bare-time shapes are correctly +rejected, so the defect is specific to the leap-second form. ### 3. An empty `expires` short-circuits the go guard and returns not-expired without parsing @@ -357,12 +337,10 @@ three accepting trees split again: ruby `01:00:00+02:00`, php `00:59:59+02:00`, > **Inference:** for a field whose purpose is to say when a challenge stops being valid, a difference in > the denoted instant is a difference in when it stops being valid. This corpus does not measure it. -### 9. A space where §5.6 mandates a `T`, and a six-one split +### 9. A space where §5.6 mandates a `T`, and a five-two split -`2020-01-01 00:00:00Z`, imported from the upstream `date.json` suite with verdict REJECT. **rust accepts -it; go, python, ruby, php, lua and typescript reject it.** At `16777d46` typescript accepted it too and -the split was five-two; `parseRfc3339` mandates `[Tt]` between `full-date` and `full-time`, so rust is now -alone. +`2020-01-01 00:00:00Z`, imported from the upstream `date.json` suite with verdict REJECT. **rust and +typescript accept it; go, python, ruby, php and lua reject it.** ``` $ rust_runner probe.tsv @@ -409,12 +387,12 @@ what.** - **The boolean axis is blind to the instant divergence in finding 8.** No golden instant is asserted for ACCEPT scenarios, so finding 8's four instants all score PASS. Same limit on php's truncation in finding 4: it accepts 7- and 9-digit fractions the corpus accepts, truncates both to six, passes both. -- **One tree cannot be conformance-tested through its own public surface.** rust's `is_expired()` +- **Two trees cannot be conformance-tested through their own public surfaces.** rust's `is_expired()` collapses parse failure and past date into one fail-closed bool and reads `OffsetDateTime::now_utc()` internally; `server/session.rs:67-69` collapses both into `Error::ChallengeExpired`; `server/charge.rs:857` - distinguishes but is reachable only after a constant-time HMAC challenge-ID check. Hence - `delegated-parser` on rust. typescript's two expiry guards are still module-private, but #286 exports - `parseRfc3339` beneath them, which is what the corpus now drives. + distinguishes but is reachable only after a constant-time HMAC challenge-ID check. typescript's two + expiry checks are module-private and the package ships no RFC 3339 parser module. Hence + `delegated-parser` on both. - **Kotlin and Swift are not measured.** Neither is on `langs` in `harness/divergence-raw.json` nor in `harness/protocol-runners/`. pay-kit carries nine SDK trees; seven are measured here. - **Python 3.9 has no measurement.** The SDK does not load on it. @@ -426,10 +404,9 @@ what.** ## Reproduce -**Five of the seven trees exit non-zero on a divergence, and that is the correct result**, so the commands -are not chained with `&&`. rust exits 0; php exits 127 on this host because its runner is not installed, -which is an absence, not a result. **Every line states the number of tests it must run**, because a wrong -`-run` pattern or a stale path exits 0 on several of these toolchains. +**Six of the seven trees exit non-zero, and that is the correct result**, so the commands are not chained +with `&&`. **Every line states the number of tests it must run**, because a wrong `-run` pattern or a +stale path exits 0 on several of these toolchains. ``` # 1. Hash gate. If it does not match, nothing below applies. From the repository root: @@ -447,13 +424,13 @@ pytest tests/test_expires.py # exit # rust from rust/ cargo test -p solana-pay-kit rfc3339_conformance_corpus # exit 0, 0 FAIL of 124 -# typescript from typescript/; needs #286 merged, else the suite fails to load and runs nothing -npx vitest run packages/mpp/src/__tests__/client-charge-validation.test.ts # exit 1, 12 FAIL of 248 +# typescript from typescript/; drives `parseRfc3339`, so needs #286 in the worktree +npx vitest run packages/mpp/src/__tests__/client-charge-validation.test.ts # exit 1, 6 FAIL of 148 # ruby from ruby/; needs ruby >= 3.1, see the prerequisites table bundle exec ruby test/pay_core/expires_rfc3339_test.rb # exit 1, 18 FAIL of 124 -# php from php/; composer install first. NOT RE-RUN on this pass, see the prerequisites table +# php from php/; composer install first ./vendor/bin/phpunit tests/PayCore/Rfc3339Test.php # exit 1, 17 FAIL of 124 # lua from lua/; the suite's runner is tests/test_helper.lua in-repo, not busted @@ -470,15 +447,11 @@ the headline table row for row. | go | repo root | 1 | 125 (`-v` to see them) | 124 | 108 | **16** | | python | `python/` | 1 | 144 | 124 | 135 | **9** | | rust | `rust/` | **0** | 3 | 124 | 3 | **0** | -| typescript | `typescript/` | 1 | 272 | 248 (124 x 2) | 260 | **12** (6 per call site) | +| typescript | `typescript/` | 1 | 148 | 124 | 142 | **6**, `parseRfc3339` under #286 | | ruby | `ruby/` | 1 | 130 | 124 | 112 | **18** | -| php | `php/` | — | — | — | — | **not re-run**, composer absent | +| php | `php/` | — carried | 145 | 124 | 128 | **17** | | lua | `lua/` | 1 | 8 | 124 | 7 | **1**, reporting 16 of 124 diverging | -**The typescript row is with `shared/rfc3339.ts` from #286 in the worktree.** Without it the run is exit 1, -`Cannot find module '../shared/rfc3339.js'`, 1 failed suite and **no tests** — the suite fails closed -rather than passing on an engine expression, which is the point of driving the SDK's own parser. - **Every tree drives all 124 scenarios. There is no filter and no skip.** Six enforce it by assertion, as in rust's `rfc3339_conformance_corpus_exercises_every_scenario` or lua's `t.assert_true(admitted == #corpus.scenarios, ...)`. **go is the exception:** it ranges directly over the @@ -512,25 +485,19 @@ done ``` Each entry's output prints in full between the delimiters, greppable with `| grep '^====='`. Executed -from the repository root with #286's `shared/rfc3339.ts` in the worktree, the eight delimiters printed: +from the repository root, the eight delimiters printed: ``` ===== gate exit 0 ===== # 72f90dc9…e0e3d ===== go exit 1 ===== # 125 "=== RUN" (1 parent + 124 subtests), 108 PASS, 16 FAIL ===== python exit 1 ===== # 9 failed, 135 passed (144) ===== rust exit 0 ===== # running 3 tests; 3 passed, 0 failed -===== typescript exit 1 ===== # 12 failed | 260 passed (272) +===== typescript exit 1 ===== # 6 failed | 142 passed (148) ===== ruby exit 1 ===== # 130 runs, 155 assertions, 18 failures, 0 errors ===== php exit 127 ===== # ./vendor/bin/phpunit: no such file or directory ===== lua exit 1 ===== # 7 tests passed, 0 skipped, 1 failed, "16 of 124 vectors diverge" ``` -**php is `127`, not `1`: the binary is absent, so nothing ran.** composer is not installed on this host and -`php/vendor/` was never created. The php row in every table above is the `16777d46` measurement, carried -because no file under `php/` differs between `16777d46` and `c143bfab`. Re-running the same loop **without** -#286's file moves only the typescript line, to `exit 1` with `Cannot find module '../shared/rfc3339.js'` -and no tests run. - ### Prerequisites per tree Every row was established by running the command above, not by reading a manifest. @@ -540,9 +507,9 @@ Every row was established by running the command above, not by reading a manifes | go | none beyond the toolchain | `go version` gives `go1.26.5 darwin/arm64` | | python | the SDK with dev extras (`uv sync --extra dev`, or `pip install -e ".[dev]"` in a 3.12 venv) | `python3.12 --version` gives `Python 3.12.13` | | rust | a resolvable workspace. **It does not resolve offline**: `cargo test --offline` fails on a `solana-bpf-loader-program` version conflict via `litesvm`, pre-existing and unrelated to these vectors | `cargo --version` gives `1.96.1` | -| typescript | `pnpm install` in `typescript/`, **and #286 merged** for `../shared/rfc3339.js` to resolve | `node --version` gives `v26.0.0` | -| ruby | `bundle install`, **and ruby >= 3.1**, see below | `ruby --version` gives `4.0.6` | -| php | `composer install` in `php/`. **composer is absent from the host this pass ran on**, so the php row was not re-measured | `php --version` gives `8.5.9`; `which composer` gives nothing | +| typescript | `pnpm install` in `typescript/` | `node --version` gives `v26.0.0` | +| ruby | `bundle install`, **and ruby >= 3.1**, see below | `ruby --version` | +| php | `composer install` in `php/` | `php --version` gives `8.5.9` | | lua | none. The runner is `lua/tests/test_helper.lua`, in-repo; busted is not a dependency. **`lua tests/run.lua` (the whole suite) additionally needs `luasodium` from luarocks; the single-spec invocation above does not** | `lua -v` gives `Lua 5.5.1` | **The ruby row.** `bundle exec` cannot run under ruby 2.6.10: the gemspec requires `ed25519 ~> 1.4` From faf32506f3738ebc167bcae30602bf38671f4079 Mon Sep 17 00:00:00 2001 From: Michael Moffett Date: Tue, 8 Sep 2026 09:51:48 -0600 Subject: [PATCH 12/15] docs(mpp): make the two typescript headline rows reproducible; reconcile the php carry F1: the Reproduce block's only typescript command drives parseRfc3339 under #286 and returns 6, while the headline rows are the merged tree's 22 each, so the sentence binding the two was false for 44 of 120 cells. Adds the direct-evaluation command that prints the 22, rather than only disclosing the gap: no harness adapter implements expires.parse. F2-F4: php was not re-executed this pass. Three statements had lost the clauses that said so - exit 127 read as a correct result, the command comment claimed exit 1 against a delimiter block recording 127, and the prerequisites table asserted a probe that was never run. --- docs/mpp-expires-rfc3339-conformance.md | 28 ++++++++++++++++++------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/docs/mpp-expires-rfc3339-conformance.md b/docs/mpp-expires-rfc3339-conformance.md index 24970c0d1..2b5d9e57f 100644 --- a/docs/mpp-expires-rfc3339-conformance.md +++ b/docs/mpp-expires-rfc3339-conformance.md @@ -404,9 +404,10 @@ what.** ## Reproduce -**Six of the seven trees exit non-zero, and that is the correct result**, so the commands are not chained -with `&&`. **Every line states the number of tests it must run**, because a wrong `-run` pattern or a -stale path exits 0 on several of these toolchains. +**Six of the seven trees exit non-zero**, so the commands are not chained with `&&`. For five that is the +correct result; php's `127` is an absent runner, not a divergence. **Every line states the number of +tests it must run**, because a wrong `-run` pattern or a stale path exits 0 on several of these +toolchains. ``` # 1. Hash gate. If it does not match, nothing below applies. From the repository root: @@ -430,8 +431,8 @@ npx vitest run packages/mpp/src/__tests__/client-charge-validation.test.ts # exi # ruby from ruby/; needs ruby >= 3.1, see the prerequisites table bundle exec ruby test/pay_core/expires_rfc3339_test.rb # exit 1, 18 FAIL of 124 -# php from php/; composer install first -./vendor/bin/phpunit tests/PayCore/Rfc3339Test.php # exit 1, 17 FAIL of 124 +# php from php/; composer install first. NOT RE-RUN on this pass: composer absent, exit 127 +./vendor/bin/phpunit tests/PayCore/Rfc3339Test.php # 17 FAIL of 124, carried # lua from lua/; the suite's runner is tests/test_helper.lua in-repo, not busted lua -e "package.path=table.concat({'./?.lua','./?/init.lua',package.path},';'); \ @@ -439,8 +440,19 @@ lua -e "package.path=table.concat({'./?.lua','./?/init.lua',package.path},';'); # exit 1, 1 of 8 tests fails, printing "16 of 124 vectors diverge" ``` -The numbers those commands printed, not a prediction. The `fail` column counts corpus vectors and equals -the headline table row for row. +The numbers those commands printed, not a prediction; php's are carried from `16777d46`, per the header. +The `fail` column counts corpus vectors and equals the headline table row for row, except typescript: +the `npx vitest` line drives `parseRfc3339` under #286 and returns 6, where the headline rows are the +merged tree's 22 each. No adapter implements `expires.parse` (`harness/src/protocol/README.md:68`), so those two +are evaluated directly, from the repository root: + +``` +node -e 'const s=require("./harness/vectors/mpp-protocol/expires.json").scenarios; + for(const[n,f]of[["charge",x=>new Date(x).getTime()],["session",x=>Date.parse(x)]]) + console.log(n,s.filter(v=>!Number.isNaN(f(v.input))!==(v.tests.parse===true)).length)' +# charge 22 +# session 22 exit 0 +``` | tree | working directory | exit | tests run | corpus vectors driven | pass | **fail** | |---|---|---|---|---|---|---| @@ -509,7 +521,7 @@ Every row was established by running the command above, not by reading a manifes | rust | a resolvable workspace. **It does not resolve offline**: `cargo test --offline` fails on a `solana-bpf-loader-program` version conflict via `litesvm`, pre-existing and unrelated to these vectors | `cargo --version` gives `1.96.1` | | typescript | `pnpm install` in `typescript/` | `node --version` gives `v26.0.0` | | ruby | `bundle install`, **and ruby >= 3.1**, see below | `ruby --version` | -| php | `composer install` in `php/` | `php --version` gives `8.5.9` | +| php | `composer install` in `php/`. **Not met on this host**, so the row is carried | `php --version` gives `8.5.9`; `which composer` exits 1 | | lua | none. The runner is `lua/tests/test_helper.lua`, in-repo; busted is not a dependency. **`lua tests/run.lua` (the whole suite) additionally needs `luasodium` from luarocks; the single-spec invocation above does not** | `lua -v` gives `Lua 5.5.1` | **The ruby row.** `bundle exec` cannot run under ruby 2.6.10: the gemspec requires `ed25519 ~> 1.4` From 061154c0e067d3d483bd279545d1c0d31c838a0c Mon Sep 17 00:00:00 2001 From: Michael Moffett Date: Thu, 8 Oct 2026 18:15:19 -0600 Subject: [PATCH 13/15] test(go): read the expires corpus verdict from time.Parse IsExpired treats an empty expires as no expiry, so the runner scored empty_string as ACCEPT without parsing it. The verdict now comes from time.Parse(time.RFC3339, ...), as in the other runners; empty_string passes and go reports 15 of 124. --- go/protocols/mpp/wire/challenge_test.go | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/go/protocols/mpp/wire/challenge_test.go b/go/protocols/mpp/wire/challenge_test.go index bf8cd4bf3..c460a0344 100644 --- a/go/protocols/mpp/wire/challenge_test.go +++ b/go/protocols/mpp/wire/challenge_test.go @@ -174,21 +174,13 @@ func TestNewPaymentCredentialRejectsUnmarshalablePayload(t *testing.T) { // Every scenario in the file is run. There is no slice to select and no // scenario to skip. // -// The verdict is read out of this package's own `PaymentChallenge.IsExpired`, -// not out of a bare `time.Parse`. `IsExpired` fails closed — it returns true -// both for "parsed, and in the past" and for "did not parse" — but `now` is a -// parameter, so passing a reference instant before every year the corpus can -// express (0000..9999) makes every parse success sort strictly after `now`. -// IsExpired then returns false for a parse success and true for a parse -// failure, and the verdict comes from the shipped function. +// The verdict is read from `time.Parse` with `time.RFC3339`, the parser +// `IsExpired` calls. It is not read from `IsExpired` itself, which treats an +// empty `expires` as "no expiry" by design and so cannot report a parse +// failure for "". const conformanceCorpusPath = "../../../../harness/vectors/mpp-protocol/expires.json" -// rfc3339ConformanceReference is far enough in the past that every instant the -// corpus can express sorts after it, so IsExpired's time comparison never fires -// on a parse success and only the parse outcome is observed. -var rfc3339ConformanceReference = time.Date(-9999, time.January, 1, 0, 0, 0, 0, time.UTC) - type rfc3339Scenario struct { Name string `json:"name"` Description string `json:"description"` @@ -248,11 +240,11 @@ func TestRFC3339ConformanceCorpus(t *testing.T) { for _, scenario := range loadRFC3339Vectors(t) { t.Run(scenario.Name, func(t *testing.T) { t.Parallel() - challenge := PaymentChallenge{Expires: scenario.Input} - accepted := !challenge.IsExpired(rfc3339ConformanceReference) + _, err := time.Parse(time.RFC3339, scenario.Input) + accepted := err == nil want := scenario.wantsAccept(t) if accepted != want { - t.Fatalf("%s (%s): input %q — corpus expects %s, IsExpired reports %s", + t.Fatalf("%s (%s): input %q — corpus expects %s, time.Parse reports %s", scenario.Name, scenario.Description, scenario.Input, conformanceVerdictName(want), conformanceVerdictName(accepted)) } From 6bab39383b464b5821e6e23f14b28182a3384d14 Mon Sep 17 00:00:00 2001 From: Michael Moffett Date: Thu, 8 Oct 2026 18:15:19 -0600 Subject: [PATCH 14/15] test(harness): replace year_0000 with year_0001 #315 (python) and #320 (php) reject year 0 on purpose. year_0001 is the smallest year every SDK represents, so the year-boundary case stays covered and the corpus stays at 124 scenarios. --- harness/vectors/mpp-protocol/expires.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/harness/vectors/mpp-protocol/expires.json b/harness/vectors/mpp-protocol/expires.json index 889040f17..c15869ff7 100644 --- a/harness/vectors/mpp-protocol/expires.json +++ b/harness/vectors/mpp-protocol/expires.json @@ -877,10 +877,10 @@ } }, { - "name": "year_0000", - "description": "Year 0000, the smallest four-digit year", + "name": "year_0001", + "description": "Year 0001, the smallest year every SDK represents", "tags": ["happy-path", "year-boundary"], - "input": "0000-01-01T00:00:00Z", + "input": "0001-01-01T00:00:00Z", "tests": { "parse": true } From 1b700fcd30964b57527f77b9603b40f411d8b20f Mon Sep 17 00:00:00 2001 From: Michael Moffett Date: Thu, 8 Oct 2026 18:15:19 -0600 Subject: [PATCH 15/15] docs(mpp): re-run the expires counts after review Every suite re-run on this branch: python 8, go 15, php 16 (now executed, no longer carried); total 117 of 992. New corpus hash 613fdb3f. --- docs/mpp-expires-rfc3339-conformance.md | 61 +++++++++++++------------ 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/docs/mpp-expires-rfc3339-conformance.md b/docs/mpp-expires-rfc3339-conformance.md index 2b5d9e57f..300ccf59a 100644 --- a/docs/mpp-expires-rfc3339-conformance.md +++ b/docs/mpp-expires-rfc3339-conformance.md @@ -5,34 +5,34 @@ vectors vendored at `harness/vectors/mpp-protocol/expires.json` (issue #111). - Oracle: RFC 3339, pinned text sha256 `9ab2b8864a85dca73a88f49b0927bc7bc85f596926e4fd1890905777924e700a` (35,064 B). - Vectors: `harness/vectors/mpp-protocol/expires.json`, version `2.0.0`, **124 scenarios** (54 ACCEPT, - 70 REJECT), 36,470 B, sha256 `72f90dc9c9e6d7c373fcae7893e1d9f0c537ecc69fc7397773d5b006a78e0e3d`. + 70 REJECT), 36,480 B, sha256 `613fdb3f31faffd65db0bdd44693112e5c8c2b99655455f3549900afb19696cc`. - Parse paths: go, lua, php, python, ruby, rust, typescript; typescript has two call sites, so eight. - Operation `expires.parse`. Verdict axis ACCEPT or REJECT. Cells: 124 x 8 = 992. - Source lines resolve at `48ea8aa43fb555d55fd9c3365a4a045ea96f1618`, this branch merged with main `c143bfabef82dbdf01dcff7a52ab29bf166d7eb4`. - #286 (`parseRfc3339`) is not on main; applied to the worktree it takes both TypeScript rows to 6 (leap seconds only). -- php was not re-executed: composer is absent from the host, `git diff --name-only 16777d46 c143bfab -- php/` - returns no paths, so the row is carried from the `16777d46` measurement. +- Counts were re-run after review: `year_0000` became `year_0001` (#315 and #320 reject year 0 on purpose), + and the go runner reads its verdict from `time.Parse`. php was executed on this run, not carried. - Sources, upstream shas, licences, the dedupe rule and every settled scenario not shipped are in `harness/vectors/mpp-protocol/README.md`. ## Headline -**The corpus is not green: 120 of 992 cells diverge, 12.1%. One parse path of eight passes all 124.** +**The corpus is not green: 117 of 992 cells diverge, 11.8%. One parse path of eight passes all 124.** Every divergence is a verdict disagreement, not a crash; no cell produced anything but ACCEPT or REJECT. | parse path | scenarios | pass | **fail** | |---|---|---|---| | rust | 124 | 124 | **0** | -| python | 124 | 115 | **9** | -| go | 124 | 108 | **16** | +| python | 124 | 116 | **8** | +| go | 124 | 109 | **15** | | lua | 124 | 108 | **16** | -| php | 124 | 107 | **17** | +| php | 124 | 108 | **16** | | ruby | 124 | 106 | **18** | | typescript, charge call site | 124 | 102 | **22** | | typescript, session call site | 124 | 102 | **22** | -| **total** | **992** | **872** | **120** | +| **total** | **992** | **875** | **117** | **Scope.** Every scenario in the file is an `expires` verdict, so there is no filter and no skip; each tree asserts that the count it exercised equals the count in the file. The 104 settled `full-date` and @@ -54,10 +54,10 @@ expression the SDK delegates to. The harness contains no RFC 3339 parser written | tree | parse path (file:line) | verdict source | runtime | **fail of 124** | |---|---|---|---|---| | rust | `rust/crates/kit/src/mpp/protocol/core/challenge.rs:173-184` `PaymentChallenge::is_expired` to `time::OffsetDateTime::parse(s, &Rfc3339)` | `delegated-parser` | `cargo 1.96.1`, `time` crate `0.3.55` | **0** | -| python | `python/src/solana_pay_kit/protocols/mpp/core/types.py:28` `_parse_rfc3339` (regex `_RFC3339_RE` at `:17`, then `datetime.fromisoformat`) | `repo-parser` | `Python 3.12.13` | **9** | -| go | `go/protocols/mpp/wire/challenge.go:110-119` `PaymentChallenge.IsExpired(now)` | `repo-parser` | `go1.26.5 darwin/arm64` | **16** | +| python | `python/src/solana_pay_kit/protocols/mpp/core/types.py:28` `_parse_rfc3339` (regex `_RFC3339_RE` at `:17`, then `datetime.fromisoformat`) | `repo-parser` | `Python 3.12.13` | **8** | +| go | `go/protocols/mpp/wire/challenge.go:114` `time.Parse(time.RFC3339, …)`, called by `PaymentChallenge.IsExpired(now)` | `delegated-parser` | `go1.26.5 darwin/arm64` | **15** | | lua | `lua/pay_kit/protocols/mpp/expires.lua:27` `M.parse_rfc3339` | `repo-parser` | `Lua 5.5.1` | **16** | -| php | `php/src/PayCore/Rfc3339Parser.php:37` `Rfc3339Parser::parse` | `repo-parser` | `PHP 8.5.9 (cli)` | **17** | +| php | `php/src/PayCore/Rfc3339Parser.php:37` `Rfc3339Parser::parse` | `repo-parser` | `PHP 8.5.9 (cli)` | **16** | | ruby | `ruby/lib/pay_core/rfc3339_parser.rb:28` `Rfc3339Parser.parse` | `repo-parser` | `ruby 4.0.6` | **18** | | typescript, charge | `typescript/packages/mpp/src/client/Charge.ts:402` `assertChallengeNotExpired` to `new Date(expires).getTime()` at `:404` | `delegated-parser` | `node v26.0.0` | **22** | | typescript, session | `typescript/packages/mpp/src/server/Session.ts:356` `assertChallengeOpenNotExpired` to `Date.parse(expires)` at `:358` | `delegated-parser` | `node v26.0.0` | **22** | @@ -67,11 +67,10 @@ expression the SDK delegates to. The harness contains no RFC 3339 parser written `0.3.x` is current; this run resolved `0.3.55`. pay-kit's Lua rockspecs target Lua 5.1 and 5.4; this run used Lua 5.5.1. -Two SDKs carry a second RFC 3339 grammar. Both are reachable; neither is counted in the 992. +Python carries a second RFC 3339 grammar. It is reachable and not counted in the 992. | tree | second path | why it exists | |---|---|---| -| go | raw `time.Parse(time.RFC3339, v)` | isolates stdlib behaviour from the wrapper's early-return guard (finding 3) | | python | `_ISO8601_RE` at `headers.py:32`, used at `headers.py:229` to gate receipt timestamps | the SDK's second RFC 3339 grammar (finding 5) | **Python 3.9 has no measurement.** `types.py:7` executes `from datetime import UTC` and raises @@ -163,7 +162,9 @@ rejected, so the defect is specific to the leap-second form. tests `TrimSpace`, so the early-return class is empty or whitespace-only, and the value handed to `time.Parse` is untrimmed, which is why a trailing-newline input passes in go. `empty_string` is corpus REJECT: six trees reject it, go reports ACCEPT, and that one vector is the whole difference between the -go wrapper and raw `time.Parse`, which errors on `""` and agrees on every other input. +go wrapper and raw `time.Parse`, which errors on `""` and agrees on every other input. The go runner +reads its verdict from `time.Parse`, so the corpus scores go's parser; the `IsExpired` early return is +reported here and not counted. **A semantic question, not a plain defect.** `IsExpired` is a field-level surface where `expires: ""` means *no expiry set*, not *malformed*; the other six drive the parser, which rejects `""` as a @@ -256,7 +257,7 @@ The limit is real and sits one digit further along. Executed against PHP 8.5.9: All five rows are shipped vectors: `jsts_date_time_001`, `secfrac_7_digits`, `secfrac_9_digits`, `go_longfrac_10digits_0000000000`, `go_longfrac_16digits_9999999999999999`. All five are corpus ACCEPT, -so php's rejection of the last two is two of its 17 divergences. Two mechanisms, two lines: the regex at +so php's rejection of the last two is two of its 16 divergences. Two mechanisms, two lines: the regex at `php/src/PayCore/Rfc3339Parser.php:26` bounds `time-secfrac` to `(?:\.(\d{1,9}))?`, producing the rejection past 9; the truncation is `substr($frac, 0, 6)` at `:86`. On the same 9-digit input rust, go, python and ruby preserve all nine digits (ruby `nsec=123456789`); lua and typescript truncate by return @@ -267,7 +268,7 @@ type. **php is the only tree that loses fractional digits it claims to support.* `_RFC3339_RE` at `types.py:17`, gating `_parse_rfc3339` at `types.py:28`, is the wired path. `_ISO8601_RE` at `headers.py:32`, used at `headers.py:229` to gate receipt timestamps, is a second grammar in the same package. Same interpreter, same run, same 124 inputs: **`_ISO8601_RE` disagrees with -the corpus on 33 of 124 where the wired `_parse_rfc3339` disagrees on 9.** +the corpus on 33 of 124 where the wired `_parse_rfc3339` disagrees on 8.** Both directions fire. `_ISO8601_RE` is purely lexical and accepts calendar-impossible dates that `_parse_rfc3339` rejects: `feb_30`, `month_13`, `non_leap_year_1900_feb_29`, `minute_60`, @@ -405,19 +406,19 @@ what.** ## Reproduce **Six of the seven trees exit non-zero**, so the commands are not chained with `&&`. For five that is the -correct result; php's `127` is an absent runner, not a divergence. **Every line states the number of +correct result. **Every line states the number of tests it must run**, because a wrong `-run` pattern or a stale path exits 0 on several of these toolchains. ``` # 1. Hash gate. If it does not match, nothing below applies. From the repository root: shasum -a 256 harness/vectors/mpp-protocol/expires.json -# 72f90dc9c9e6d7c373fcae7893e1d9f0c537ecc69fc7397773d5b006a78e0e3d exit 0 +# 613fdb3f31faffd65db0bdd44693112e5c8c2b99655455f3549900afb19696cc exit 0 # 2. The seven trees. Run each; none depends on another. # go from the repository root; -C go because the go module root is go/. -v or no count is printed -go -C go test -v ./protocols/mpp/wire/ -run TestRFC3339ConformanceCorpus # exit 1, 16 FAIL of 124 +go -C go test -v ./protocols/mpp/wire/ -run TestRFC3339ConformanceCorpus # exit 1, 15 FAIL of 124 # python from python/ pytest tests/test_expires.py # exit 1, 9 FAIL of 124 @@ -431,8 +432,8 @@ npx vitest run packages/mpp/src/__tests__/client-charge-validation.test.ts # exi # ruby from ruby/; needs ruby >= 3.1, see the prerequisites table bundle exec ruby test/pay_core/expires_rfc3339_test.rb # exit 1, 18 FAIL of 124 -# php from php/; composer install first. NOT RE-RUN on this pass: composer absent, exit 127 -./vendor/bin/phpunit tests/PayCore/Rfc3339Test.php # 17 FAIL of 124, carried +# php from php/; composer install first +./vendor/bin/phpunit tests/PayCore/Rfc3339Test.php # exit 1, 16 FAIL of 124 # lua from lua/; the suite's runner is tests/test_helper.lua in-repo, not busted lua -e "package.path=table.concat({'./?.lua','./?/init.lua',package.path},';'); \ @@ -440,7 +441,7 @@ lua -e "package.path=table.concat({'./?.lua','./?/init.lua',package.path},';'); # exit 1, 1 of 8 tests fails, printing "16 of 124 vectors diverge" ``` -The numbers those commands printed, not a prediction; php's are carried from `16777d46`, per the header. +The numbers those commands printed, not a prediction. The `fail` column counts corpus vectors and equals the headline table row for row, except typescript: the `npx vitest` line drives `parseRfc3339` under #286 and returns 6, where the headline rows are the merged tree's 22 each. No adapter implements `expires.parse` (`harness/src/protocol/README.md:68`), so those two @@ -456,12 +457,12 @@ node -e 'const s=require("./harness/vectors/mpp-protocol/expires.json").scenario | tree | working directory | exit | tests run | corpus vectors driven | pass | **fail** | |---|---|---|---|---|---|---| -| go | repo root | 1 | 125 (`-v` to see them) | 124 | 108 | **16** | -| python | `python/` | 1 | 144 | 124 | 135 | **9** | +| go | repo root | 1 | 125 (`-v` to see them) | 124 | 109 | **15** | +| python | `python/` | 1 | 144 | 124 | 136 | **8** | | rust | `rust/` | **0** | 3 | 124 | 3 | **0** | | typescript | `typescript/` | 1 | 148 | 124 | 142 | **6**, `parseRfc3339` under #286 | | ruby | `ruby/` | 1 | 130 | 124 | 112 | **18** | -| php | `php/` | — carried | 145 | 124 | 128 | **17** | +| php | `php/` | 1 | 145 | 124 | 129 | **16** | | lua | `lua/` | 1 | 8 | 124 | 7 | **1**, reporting 16 of 124 diverging | **Every tree drives all 124 scenarios. There is no filter and no skip.** Six enforce it by assertion, as @@ -500,13 +501,13 @@ Each entry's output prints in full between the delimiters, greppable with `| gre from the repository root, the eight delimiters printed: ``` -===== gate exit 0 ===== # 72f90dc9…e0e3d -===== go exit 1 ===== # 125 "=== RUN" (1 parent + 124 subtests), 108 PASS, 16 FAIL -===== python exit 1 ===== # 9 failed, 135 passed (144) +===== gate exit 0 ===== # 613fdb3f…696cc +===== go exit 1 ===== # 125 "=== RUN" (1 parent + 124 subtests), 109 PASS, 15 FAIL +===== python exit 1 ===== # 8 failed, 136 passed (144) ===== rust exit 0 ===== # running 3 tests; 3 passed, 0 failed ===== typescript exit 1 ===== # 6 failed | 142 passed (148) ===== ruby exit 1 ===== # 130 runs, 155 assertions, 18 failures, 0 errors -===== php exit 127 ===== # ./vendor/bin/phpunit: no such file or directory +===== php exit 1 ===== # Tests: 145, Assertions: 153, Failures: 16 ===== lua exit 1 ===== # 7 tests passed, 0 skipped, 1 failed, "16 of 124 vectors diverge" ``` @@ -521,7 +522,7 @@ Every row was established by running the command above, not by reading a manifes | rust | a resolvable workspace. **It does not resolve offline**: `cargo test --offline` fails on a `solana-bpf-loader-program` version conflict via `litesvm`, pre-existing and unrelated to these vectors | `cargo --version` gives `1.96.1` | | typescript | `pnpm install` in `typescript/` | `node --version` gives `v26.0.0` | | ruby | `bundle install`, **and ruby >= 3.1**, see below | `ruby --version` | -| php | `composer install` in `php/`. **Not met on this host**, so the row is carried | `php --version` gives `8.5.9`; `which composer` exits 1 | +| php | `composer install` in `php/` | `php --version` gives `8.5.9` | | lua | none. The runner is `lua/tests/test_helper.lua`, in-repo; busted is not a dependency. **`lua tests/run.lua` (the whole suite) additionally needs `luasodium` from luarocks; the single-spec invocation above does not** | `lua -v` gives `Lua 5.5.1` | **The ruby row.** `bundle exec` cannot run under ruby 2.6.10: the gemspec requires `ed25519 ~> 1.4`