@kanonak-protocol/sdk 4.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -3
- package/vectors/decode.mjs +115 -0
- package/vectors/full-form-vectors.json +254 -0
- package/vectors/lexical-vectors.json +81 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kanonak-protocol/sdk",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "TypeScript SDK for the Kanonak Protocol — parse, resolve, validate, reason over, and render .kan.yml packages.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -89,10 +89,14 @@
|
|
|
89
89
|
"./browser": {
|
|
90
90
|
"types": "./dist/browser.d.ts",
|
|
91
91
|
"default": "./dist/browser.js"
|
|
92
|
-
}
|
|
92
|
+
},
|
|
93
|
+
"./vectors/lexical-vectors.json": "./vectors/lexical-vectors.json",
|
|
94
|
+
"./vectors/full-form-vectors.json": "./vectors/full-form-vectors.json",
|
|
95
|
+
"./vectors/decode.mjs": "./vectors/decode.mjs"
|
|
93
96
|
},
|
|
94
97
|
"files": [
|
|
95
98
|
"dist/**/*",
|
|
99
|
+
"vectors/**/*",
|
|
96
100
|
"README.md"
|
|
97
101
|
],
|
|
98
102
|
"scripts": {
|
|
@@ -122,7 +126,7 @@
|
|
|
122
126
|
"yaml-parser"
|
|
123
127
|
],
|
|
124
128
|
"dependencies": {
|
|
125
|
-
"@kanonak-protocol/types": "^4.
|
|
129
|
+
"@kanonak-protocol/types": "^4.1.0",
|
|
126
130
|
"ignore": "^7.0.5",
|
|
127
131
|
"js-yaml": "^4.1.0",
|
|
128
132
|
"yaml": "^2.7.0"
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decoder for the canonical-form full-form vectors' typed-value INPUT model
|
|
3
|
+
* (issue #55, option 1). The input is language-neutral and carries datatype
|
|
4
|
+
* explicitly, so a port tests canonicalization in isolation (no parser needed):
|
|
5
|
+
*
|
|
6
|
+
* subject := { uri, statements: [ { predicate, value } ] }
|
|
7
|
+
* value := { lit, datatype } typed scalar — raw lexical token + datatype URI
|
|
8
|
+
* | { raw } untyped/open-world scalar (no carrier, raw token)
|
|
9
|
+
* | { ref } reference to an entity (full canonical URI)
|
|
10
|
+
* | { embed: { name?, statements } } embedded node
|
|
11
|
+
* | { list: [ value ] } ordered list
|
|
12
|
+
*
|
|
13
|
+
* It builds the SDK object model the same way the parser's range-directed
|
|
14
|
+
* typing does (carrier from the datatype URI via `carrierOf`), so
|
|
15
|
+
* `canonicalForm`/`canonicalHash` over the result is the authoritative output.
|
|
16
|
+
* This file is also the reference each `kanonak-canonical` port mirrors to map
|
|
17
|
+
* the input model into its own representation.
|
|
18
|
+
*/
|
|
19
|
+
import {
|
|
20
|
+
SubjectKanonak,
|
|
21
|
+
ReferenceKanonak,
|
|
22
|
+
EmbeddedKanonak,
|
|
23
|
+
LiteralKanonak,
|
|
24
|
+
StringStatement,
|
|
25
|
+
ReferenceStatement,
|
|
26
|
+
EmbeddedStatement,
|
|
27
|
+
ListStatement,
|
|
28
|
+
carrierOf,
|
|
29
|
+
} from '@kanonak-protocol/sdk';
|
|
30
|
+
|
|
31
|
+
/** "publisher/package@ver/name" or "publisher/package/name" → { publisher, package_, name }. */
|
|
32
|
+
function entityUri(uriStr) {
|
|
33
|
+
const idx = uriStr.lastIndexOf('/');
|
|
34
|
+
const name = uriStr.slice(idx + 1);
|
|
35
|
+
const head = uriStr.slice(0, idx); // publisher/package@ver
|
|
36
|
+
const publisher = head.slice(0, head.indexOf('/'));
|
|
37
|
+
const pkg = head.slice(head.indexOf('/') + 1).split('@')[0];
|
|
38
|
+
return { publisher, package_: pkg, name };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A scalar statement. canonicalForm reads `(carrier, lexical)` off any
|
|
43
|
+
* ScalarStatement, so a single StringStatement faithfully represents every
|
|
44
|
+
* scalar carrier — the canonical output depends on the carrier+lexical, not the
|
|
45
|
+
* statement class.
|
|
46
|
+
*/
|
|
47
|
+
function scalar(predicateUri, carrier, lexical) {
|
|
48
|
+
const s = new StringStatement();
|
|
49
|
+
s.predicate = ReferenceKanonak.parse(predicateUri);
|
|
50
|
+
s.object = lexical;
|
|
51
|
+
if (carrier) {
|
|
52
|
+
s.carrier = carrier;
|
|
53
|
+
s.lexical = lexical;
|
|
54
|
+
}
|
|
55
|
+
return s;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function buildEmbedded(e) {
|
|
59
|
+
const emb = new EmbeddedKanonak();
|
|
60
|
+
if (e.name) emb.name = e.name;
|
|
61
|
+
emb.statement = (e.statements ?? []).map((st) => buildStatement(st.predicate, st.value));
|
|
62
|
+
return emb;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function buildItem(v) {
|
|
66
|
+
if ('ref' in v) return ReferenceKanonak.parse(v.ref);
|
|
67
|
+
if ('embed' in v) return buildEmbedded(v.embed);
|
|
68
|
+
const lit = new LiteralKanonak();
|
|
69
|
+
if ('lit' in v) {
|
|
70
|
+
lit.value = v.lit;
|
|
71
|
+
lit.carrier = carrierOf(entityUri(v.datatype));
|
|
72
|
+
lit.lexical = v.lit;
|
|
73
|
+
} else if ('raw' in v) {
|
|
74
|
+
lit.value = v.raw;
|
|
75
|
+
} else {
|
|
76
|
+
throw new Error(`decode: unknown list item shape ${JSON.stringify(v)}`);
|
|
77
|
+
}
|
|
78
|
+
return lit;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function buildStatement(predicateUri, v) {
|
|
82
|
+
if ('lit' in v) return scalar(predicateUri, carrierOf(entityUri(v.datatype)), v.lit);
|
|
83
|
+
if ('raw' in v) return scalar(predicateUri, undefined, v.raw);
|
|
84
|
+
if ('ref' in v) {
|
|
85
|
+
const s = new ReferenceStatement();
|
|
86
|
+
s.predicate = ReferenceKanonak.parse(predicateUri);
|
|
87
|
+
s.object = ReferenceKanonak.parse(v.ref);
|
|
88
|
+
return s;
|
|
89
|
+
}
|
|
90
|
+
if ('embed' in v) {
|
|
91
|
+
const s = new EmbeddedStatement();
|
|
92
|
+
s.predicate = ReferenceKanonak.parse(predicateUri);
|
|
93
|
+
s.object = buildEmbedded(v.embed);
|
|
94
|
+
return s;
|
|
95
|
+
}
|
|
96
|
+
if ('list' in v) {
|
|
97
|
+
const s = new ListStatement();
|
|
98
|
+
s.predicate = ReferenceKanonak.parse(predicateUri);
|
|
99
|
+
s.object = v.list.map(buildItem);
|
|
100
|
+
return s;
|
|
101
|
+
}
|
|
102
|
+
throw new Error(`decode: unknown value shape ${JSON.stringify(v)}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Build the SubjectKanonak[] the canonical form consumes from a vector's `input`. */
|
|
106
|
+
export function decodeSubjects(input) {
|
|
107
|
+
return input.subjects.map((s) => {
|
|
108
|
+
const subj = new SubjectKanonak();
|
|
109
|
+
const idx = s.uri.lastIndexOf('/');
|
|
110
|
+
subj.namespace = s.uri.slice(0, idx);
|
|
111
|
+
subj.name = s.uri.slice(idx + 1);
|
|
112
|
+
subj.statement = (s.statements ?? []).map((st) => buildStatement(st.predicate, st.value));
|
|
113
|
+
return subj;
|
|
114
|
+
});
|
|
115
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
{
|
|
2
|
+
"canonicalFormVersion": "1",
|
|
3
|
+
"hashAlgorithm": "sha256",
|
|
4
|
+
"description": "Golden conformance vectors for canonicalForm/canonicalHash over the typed-value input model (issue #55). input -> expectedCanonicalForm (exact UTF-8 JSON bytes) -> expectedHash. The same file drives every kanonak-canonical port. See decode.mjs for the input-model decoder. Expected values are authoritative.",
|
|
5
|
+
"vectors": [
|
|
6
|
+
{
|
|
7
|
+
"id": "typed-scalars-mixed",
|
|
8
|
+
"description": "One subject with integer/decimal/boolean/dateTime/string/anyURI properties — the typed blob in a full form; statements emitted in predicate UTF-8 byte order.",
|
|
9
|
+
"input": {
|
|
10
|
+
"subjects": [
|
|
11
|
+
{
|
|
12
|
+
"uri": "example.org/data@1.0.0/Tick",
|
|
13
|
+
"statements": [
|
|
14
|
+
{
|
|
15
|
+
"predicate": "example.org/data@1.0.0/qty",
|
|
16
|
+
"value": {
|
|
17
|
+
"lit": "0100",
|
|
18
|
+
"datatype": "kanonak.org/core-xsd/integer"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"predicate": "example.org/data@1.0.0/price",
|
|
23
|
+
"value": {
|
|
24
|
+
"lit": "1.230",
|
|
25
|
+
"datatype": "kanonak.org/core-xsd/decimal"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"predicate": "example.org/data@1.0.0/active",
|
|
30
|
+
"value": {
|
|
31
|
+
"lit": "1",
|
|
32
|
+
"datatype": "kanonak.org/core-xsd/boolean"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"predicate": "example.org/data@1.0.0/at",
|
|
37
|
+
"value": {
|
|
38
|
+
"lit": "2026-05-31T13:00:00+01:00",
|
|
39
|
+
"datatype": "kanonak.org/core-xsd/dateTime"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"predicate": "example.org/data@1.0.0/label",
|
|
44
|
+
"value": {
|
|
45
|
+
"lit": "hello",
|
|
46
|
+
"datatype": "kanonak.org/core-xsd/string"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
"expectedCanonicalForm": "{\"subjects\":[{\"uri\":\"example.org/data@1.0.0/Tick\",\"statements\":[{\"predicate\":\"example.org/data@1.0.0/active\",\"type\":\"typed\",\"carrier\":\"boolean\",\"value\":\"true\"},{\"predicate\":\"example.org/data@1.0.0/at\",\"type\":\"typed\",\"carrier\":\"dateTime\",\"value\":\"2026-05-31T12:00:00Z\"},{\"predicate\":\"example.org/data@1.0.0/label\",\"type\":\"typed\",\"carrier\":\"string\",\"value\":\"hello\"},{\"predicate\":\"example.org/data@1.0.0/price\",\"type\":\"typed\",\"carrier\":\"decimal\",\"value\":\"1.23\"},{\"predicate\":\"example.org/data@1.0.0/qty\",\"type\":\"typed\",\"carrier\":\"integer\",\"value\":\"100\"}]}]}",
|
|
54
|
+
"expectedHash": "sha256:e55ab7be11235de58360a8c8b069c8c19f21bb5fcde7c4638abd2f731ea8b704"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"id": "decimal-five",
|
|
58
|
+
"description": "A decimal-valued 5.0 — distinct identity from the integer 5 below (carrier tag differs).",
|
|
59
|
+
"input": {
|
|
60
|
+
"subjects": [
|
|
61
|
+
{
|
|
62
|
+
"uri": "example.org/data@1.0.0/N",
|
|
63
|
+
"statements": [
|
|
64
|
+
{
|
|
65
|
+
"predicate": "example.org/data@1.0.0/v",
|
|
66
|
+
"value": {
|
|
67
|
+
"lit": "5.0",
|
|
68
|
+
"datatype": "kanonak.org/core-xsd/decimal"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
"expectedCanonicalForm": "{\"subjects\":[{\"uri\":\"example.org/data@1.0.0/N\",\"statements\":[{\"predicate\":\"example.org/data@1.0.0/v\",\"type\":\"typed\",\"carrier\":\"decimal\",\"value\":\"5\"}]}]}",
|
|
76
|
+
"expectedHash": "sha256:705b20070647ddf964b713b5e68207c4c1767ba83be597ccfdf8d7e55a183c94"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"id": "integer-five",
|
|
80
|
+
"description": "An integer-valued 5 — same lexical \"5\" as decimal-five but a different carrier, so a different hash.",
|
|
81
|
+
"input": {
|
|
82
|
+
"subjects": [
|
|
83
|
+
{
|
|
84
|
+
"uri": "example.org/data@1.0.0/N",
|
|
85
|
+
"statements": [
|
|
86
|
+
{
|
|
87
|
+
"predicate": "example.org/data@1.0.0/v",
|
|
88
|
+
"value": {
|
|
89
|
+
"lit": "5",
|
|
90
|
+
"datatype": "kanonak.org/core-xsd/integer"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
},
|
|
97
|
+
"expectedCanonicalForm": "{\"subjects\":[{\"uri\":\"example.org/data@1.0.0/N\",\"statements\":[{\"predicate\":\"example.org/data@1.0.0/v\",\"type\":\"typed\",\"carrier\":\"integer\",\"value\":\"5\"}]}]}",
|
|
98
|
+
"expectedHash": "sha256:f32b37ab13e5ccb84afc2a1c6ce3fd2ee13dcf53dda0dc17fa1d4a0bc798c7e3"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"id": "long-hundred",
|
|
102
|
+
"description": "xsd:long 0100 — collapses to the integer carrier, equal identity to integer-hundred.",
|
|
103
|
+
"input": {
|
|
104
|
+
"subjects": [
|
|
105
|
+
{
|
|
106
|
+
"uri": "example.org/data@1.0.0/N",
|
|
107
|
+
"statements": [
|
|
108
|
+
{
|
|
109
|
+
"predicate": "example.org/data@1.0.0/v",
|
|
110
|
+
"value": {
|
|
111
|
+
"lit": "0100",
|
|
112
|
+
"datatype": "kanonak.org/core-xsd/long"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
"expectedCanonicalForm": "{\"subjects\":[{\"uri\":\"example.org/data@1.0.0/N\",\"statements\":[{\"predicate\":\"example.org/data@1.0.0/v\",\"type\":\"typed\",\"carrier\":\"integer\",\"value\":\"100\"}]}]}",
|
|
120
|
+
"expectedHash": "sha256:653ec69910e2f7022208305c0ff44915f6eddf0163771a38f4af26161476caa4"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"id": "integer-hundred",
|
|
124
|
+
"description": "xsd:integer 100 — same carrier+value as long-hundred, so an identical hash.",
|
|
125
|
+
"input": {
|
|
126
|
+
"subjects": [
|
|
127
|
+
{
|
|
128
|
+
"uri": "example.org/data@1.0.0/N",
|
|
129
|
+
"statements": [
|
|
130
|
+
{
|
|
131
|
+
"predicate": "example.org/data@1.0.0/v",
|
|
132
|
+
"value": {
|
|
133
|
+
"lit": "100",
|
|
134
|
+
"datatype": "kanonak.org/core-xsd/integer"
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
]
|
|
140
|
+
},
|
|
141
|
+
"expectedCanonicalForm": "{\"subjects\":[{\"uri\":\"example.org/data@1.0.0/N\",\"statements\":[{\"predicate\":\"example.org/data@1.0.0/v\",\"type\":\"typed\",\"carrier\":\"integer\",\"value\":\"100\"}]}]}",
|
|
142
|
+
"expectedHash": "sha256:653ec69910e2f7022208305c0ff44915f6eddf0163771a38f4af26161476caa4"
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"id": "extra-untyped-raw-token",
|
|
146
|
+
"description": "An untyped/open-world scalar (no datatype): the raw token \"1.10\" is preserved verbatim, NOT decimal-collapsed to \"1.1\" — the open-world tier.",
|
|
147
|
+
"input": {
|
|
148
|
+
"subjects": [
|
|
149
|
+
{
|
|
150
|
+
"uri": "example.org/data@1.0.0/N",
|
|
151
|
+
"statements": [
|
|
152
|
+
{
|
|
153
|
+
"predicate": "example.org/data@1.0.0/x",
|
|
154
|
+
"value": {
|
|
155
|
+
"raw": "1.10"
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
]
|
|
161
|
+
},
|
|
162
|
+
"expectedCanonicalForm": "{\"subjects\":[{\"uri\":\"example.org/data@1.0.0/N\",\"statements\":[{\"predicate\":\"example.org/data@1.0.0/x\",\"type\":\"string\",\"value\":\"1.10\"}]}]}",
|
|
163
|
+
"expectedHash": "sha256:0b48e715952fb38d57d944b6bcbda77932f424b49868fce687728b3fa212e3e8"
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
"id": "list-order-preserved",
|
|
167
|
+
"description": "A list of references keeps source order [B, A] (lists carry semantic order).",
|
|
168
|
+
"input": {
|
|
169
|
+
"subjects": [
|
|
170
|
+
{
|
|
171
|
+
"uri": "example.org/data@1.0.0/L",
|
|
172
|
+
"statements": [
|
|
173
|
+
{
|
|
174
|
+
"predicate": "example.org/data@1.0.0/items",
|
|
175
|
+
"value": {
|
|
176
|
+
"list": [
|
|
177
|
+
{
|
|
178
|
+
"ref": "example.org/data@1.0.0/B"
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
"ref": "example.org/data@1.0.0/A"
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
]
|
|
189
|
+
},
|
|
190
|
+
"expectedCanonicalForm": "{\"subjects\":[{\"uri\":\"example.org/data@1.0.0/L\",\"statements\":[{\"predicate\":\"example.org/data@1.0.0/items\",\"type\":\"list\",\"items\":[{\"type\":\"ref\",\"value\":\"example.org/data@1.0.0/B\"},{\"type\":\"ref\",\"value\":\"example.org/data@1.0.0/A\"}]}]}]}",
|
|
191
|
+
"expectedHash": "sha256:ea18909364cd74f16e1537d9796964508cc3821fbe530993a9f6784bf79afc0c"
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"id": "ref-and-embedded",
|
|
195
|
+
"description": "A reference property and an embedded node property.",
|
|
196
|
+
"input": {
|
|
197
|
+
"subjects": [
|
|
198
|
+
{
|
|
199
|
+
"uri": "example.org/data@1.0.0/R",
|
|
200
|
+
"statements": [
|
|
201
|
+
{
|
|
202
|
+
"predicate": "example.org/data@1.0.0/owner",
|
|
203
|
+
"value": {
|
|
204
|
+
"ref": "example.org/data@1.0.0/Alice"
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"predicate": "example.org/data@1.0.0/meta",
|
|
209
|
+
"value": {
|
|
210
|
+
"embed": {
|
|
211
|
+
"name": "m",
|
|
212
|
+
"statements": [
|
|
213
|
+
{
|
|
214
|
+
"predicate": "example.org/data@1.0.0/n",
|
|
215
|
+
"value": {
|
|
216
|
+
"lit": "7",
|
|
217
|
+
"datatype": "kanonak.org/core-xsd/integer"
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
]
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
]
|
|
225
|
+
}
|
|
226
|
+
]
|
|
227
|
+
},
|
|
228
|
+
"expectedCanonicalForm": "{\"subjects\":[{\"uri\":\"example.org/data@1.0.0/R\",\"statements\":[{\"predicate\":\"example.org/data@1.0.0/meta\",\"type\":\"embedded\",\"name\":\"m\",\"statements\":[{\"predicate\":\"example.org/data@1.0.0/n\",\"type\":\"typed\",\"carrier\":\"integer\",\"value\":\"7\"}]},{\"predicate\":\"example.org/data@1.0.0/owner\",\"type\":\"ref\",\"value\":\"example.org/data@1.0.0/Alice\"}]}]}",
|
|
229
|
+
"expectedHash": "sha256:fd4700f77a5825ba362394c328c25b3eefd795926101aa18bf0af1804432908f"
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"id": "nfc-normalization",
|
|
233
|
+
"description": "A string property whose value is authored as NFD (e + combining acute) canonicalizes to NFC.",
|
|
234
|
+
"input": {
|
|
235
|
+
"subjects": [
|
|
236
|
+
{
|
|
237
|
+
"uri": "example.org/data@1.0.0/N",
|
|
238
|
+
"statements": [
|
|
239
|
+
{
|
|
240
|
+
"predicate": "example.org/data@1.0.0/label",
|
|
241
|
+
"value": {
|
|
242
|
+
"lit": "é",
|
|
243
|
+
"datatype": "kanonak.org/core-xsd/string"
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
]
|
|
247
|
+
}
|
|
248
|
+
]
|
|
249
|
+
},
|
|
250
|
+
"expectedCanonicalForm": "{\"subjects\":[{\"uri\":\"example.org/data@1.0.0/N\",\"statements\":[{\"predicate\":\"example.org/data@1.0.0/label\",\"type\":\"typed\",\"carrier\":\"string\",\"value\":\"é\"}]}]}",
|
|
251
|
+
"expectedHash": "sha256:25b63ea3f42aa11307cbe4793caa56009be337707db7254fa64f7b6ae8b06cce"
|
|
252
|
+
}
|
|
253
|
+
]
|
|
254
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"canonicalFormVersion": "1",
|
|
3
|
+
"description": "Golden conformance vectors for per-carrier canonical lexical forms (issue #55). Language-neutral: each vector is (carrier, input raw token) -> expected canonical lexical string, OR (carrier, input) -> expectError for inputs that must fail loud. Every kanonak-canonical port asserts canonicalScalarLexical(carrier, input) === expected for these. Expected values are authoritative — they define the form.",
|
|
4
|
+
"vectors": [
|
|
5
|
+
{ "id": "integer-plain", "carrier": "integer", "input": "100", "expected": "100" },
|
|
6
|
+
{ "id": "integer-leading-zeros", "carrier": "integer", "input": "0100", "expected": "100" },
|
|
7
|
+
{ "id": "integer-explicit-plus", "carrier": "integer", "input": "+100", "expected": "100" },
|
|
8
|
+
{ "id": "integer-negative", "carrier": "integer", "input": "-42", "expected": "-42" },
|
|
9
|
+
{ "id": "integer-negative-zero", "carrier": "integer", "input": "-0", "expected": "0" },
|
|
10
|
+
{ "id": "integer-zero", "carrier": "integer", "input": "0", "expected": "0" },
|
|
11
|
+
{ "id": "integer-epoch-nanos-beyond-2^53", "carrier": "integer", "input": "1770000000000000001", "expected": "1770000000000000001" },
|
|
12
|
+
{ "id": "integer-rejects-decimal-point", "carrier": "integer", "input": "1.0", "expectError": true },
|
|
13
|
+
{ "id": "integer-rejects-exponent", "carrier": "integer", "input": "1e3", "expectError": true },
|
|
14
|
+
|
|
15
|
+
{ "id": "decimal-trailing-zero", "carrier": "decimal", "input": "1.10", "expected": "1.1" },
|
|
16
|
+
{ "id": "decimal-all-fraction-zeros", "carrier": "decimal", "input": "1.00", "expected": "1" },
|
|
17
|
+
{ "id": "decimal-leading-fraction-zero", "carrier": "decimal", "input": "0.10", "expected": "0.1" },
|
|
18
|
+
{ "id": "decimal-integer-valued", "carrier": "decimal", "input": "100", "expected": "100" },
|
|
19
|
+
{ "id": "decimal-integer-dot-zero", "carrier": "decimal", "input": "100.0", "expected": "100" },
|
|
20
|
+
{ "id": "decimal-negative-zero", "carrier": "decimal", "input": "-0.00", "expected": "0" },
|
|
21
|
+
{ "id": "decimal-bare-fraction", "carrier": "decimal", "input": ".5", "expected": "0.5" },
|
|
22
|
+
{ "id": "decimal-negative-trailing", "carrier": "decimal", "input": "-0.250", "expected": "-0.25" },
|
|
23
|
+
{ "id": "decimal-subpenny-exact", "carrier": "decimal", "input": "1.234500", "expected": "1.2345" },
|
|
24
|
+
{ "id": "decimal-tenth-exact", "carrier": "decimal", "input": "0.1", "expected": "0.1" },
|
|
25
|
+
{ "id": "decimal-rejects-exponent", "carrier": "decimal", "input": "1e3", "expectError": true },
|
|
26
|
+
|
|
27
|
+
{ "id": "double-integer-valued", "carrier": "double", "input": "1000", "expected": "1000" },
|
|
28
|
+
{ "id": "double-exponent-form", "carrier": "double", "input": "1e3", "expected": "1000" },
|
|
29
|
+
{ "id": "double-fraction", "carrier": "double", "input": "1.5", "expected": "1.5" },
|
|
30
|
+
{ "id": "double-negative-zero", "carrier": "double", "input": "-0", "expected": "0" },
|
|
31
|
+
{ "id": "double-nan", "carrier": "double", "input": "NaN", "expected": "NaN" },
|
|
32
|
+
{ "id": "double-inf", "carrier": "double", "input": "INF", "expected": "INF" },
|
|
33
|
+
{ "id": "double-neg-inf", "carrier": "double", "input": "-INF", "expected": "-INF" },
|
|
34
|
+
{ "id": "double-rejects-Infinity-spelling", "carrier": "double", "input": "Infinity", "expectError": true },
|
|
35
|
+
|
|
36
|
+
{ "id": "float-fraction", "carrier": "float", "input": "1.5", "expected": "1.5" },
|
|
37
|
+
{ "id": "float-nan", "carrier": "float", "input": "NaN", "expected": "NaN" },
|
|
38
|
+
|
|
39
|
+
{ "id": "boolean-true", "carrier": "boolean", "input": "true", "expected": "true" },
|
|
40
|
+
{ "id": "boolean-one", "carrier": "boolean", "input": "1", "expected": "true" },
|
|
41
|
+
{ "id": "boolean-false", "carrier": "boolean", "input": "false", "expected": "false" },
|
|
42
|
+
{ "id": "boolean-zero", "carrier": "boolean", "input": "0", "expected": "false" },
|
|
43
|
+
{ "id": "boolean-rejects-uppercase", "carrier": "boolean", "input": "TRUE", "expectError": true },
|
|
44
|
+
|
|
45
|
+
{ "id": "string-ascii-unchanged", "carrier": "string", "input": "hello", "expected": "hello" },
|
|
46
|
+
{ "id": "string-nfd-to-nfc", "carrier": "string", "input": "é", "expected": "é" },
|
|
47
|
+
{ "id": "string-nfc-stable", "carrier": "string", "input": "é", "expected": "é" },
|
|
48
|
+
{ "id": "string-no-whitespace-collapse", "carrier": "string", "input": "a b\t", "expected": "a b\t" },
|
|
49
|
+
|
|
50
|
+
{ "id": "anyURI-nfc", "carrier": "anyURI", "input": "https://example.org/é", "expected": "https://example.org/é" },
|
|
51
|
+
|
|
52
|
+
{ "id": "hexBinary-lowercase-to-upper", "carrier": "hexBinary", "input": "0fb7", "expected": "0FB7" },
|
|
53
|
+
{ "id": "hexBinary-already-upper", "carrier": "hexBinary", "input": "0FB7", "expected": "0FB7" },
|
|
54
|
+
{ "id": "hexBinary-empty", "carrier": "hexBinary", "input": "", "expected": "" },
|
|
55
|
+
{ "id": "hexBinary-rejects-odd-length", "carrier": "hexBinary", "input": "abc", "expectError": true },
|
|
56
|
+
|
|
57
|
+
{ "id": "base64-canonical", "carrier": "base64Binary", "input": "aGVsbG8=", "expected": "aGVsbG8=" },
|
|
58
|
+
{ "id": "base64-line-wrap-collapses", "carrier": "base64Binary", "input": "aGVs\nbG8=", "expected": "aGVsbG8=" },
|
|
59
|
+
|
|
60
|
+
{ "id": "dateTime-offset-to-utc-z", "carrier": "dateTime", "input": "2026-05-31T13:00:00+01:00", "expected": "2026-05-31T12:00:00Z" },
|
|
61
|
+
{ "id": "dateTime-z-stable", "carrier": "dateTime", "input": "2026-05-31T12:00:00Z", "expected": "2026-05-31T12:00:00Z" },
|
|
62
|
+
{ "id": "dateTime-plus-zero-to-z", "carrier": "dateTime", "input": "2026-05-31T12:00:00+00:00", "expected": "2026-05-31T12:00:00Z" },
|
|
63
|
+
{ "id": "dateTime-rolls-back-a-day", "carrier": "dateTime", "input": "2026-05-31T01:00:00+02:00", "expected": "2026-05-30T23:00:00Z" },
|
|
64
|
+
{ "id": "dateTime-rolls-year", "carrier": "dateTime", "input": "2026-12-31T23:00:00-02:00", "expected": "2027-01-01T01:00:00Z" },
|
|
65
|
+
{ "id": "dateTime-floating-no-z", "carrier": "dateTime", "input": "2026-05-31T12:00:00", "expected": "2026-05-31T12:00:00" },
|
|
66
|
+
{ "id": "dateTime-nanos-preserved", "carrier": "dateTime", "input": "2026-01-01T00:00:00.123456789Z", "expected": "2026-01-01T00:00:00.123456789Z" },
|
|
67
|
+
{ "id": "dateTime-frac-trailing-zeros-trimmed", "carrier": "dateTime", "input": "2026-01-01T00:00:00.1200Z", "expected": "2026-01-01T00:00:00.12Z" },
|
|
68
|
+
{ "id": "dateTime-frac-all-zeros-dropped", "carrier": "dateTime", "input": "2026-01-01T00:00:00.000Z", "expected": "2026-01-01T00:00:00Z" },
|
|
69
|
+
{ "id": "dateTime-frac-survives-shift", "carrier": "dateTime", "input": "2026-01-01T00:30:00.500+01:00", "expected": "2025-12-31T23:30:00.5Z" },
|
|
70
|
+
|
|
71
|
+
{ "id": "date-no-shift", "carrier": "date", "input": "2026-05-31", "expected": "2026-05-31" },
|
|
72
|
+
{ "id": "date-plus-zero-to-z", "carrier": "date", "input": "2026-05-31+00:00", "expected": "2026-05-31Z" },
|
|
73
|
+
{ "id": "date-offset-preserved-not-shifted", "carrier": "date", "input": "2026-05-31-05:00", "expected": "2026-05-31-05:00" },
|
|
74
|
+
|
|
75
|
+
{ "id": "time-no-shift", "carrier": "time", "input": "13:00:00-05:00", "expected": "13:00:00-05:00" },
|
|
76
|
+
{ "id": "time-frac-trimmed", "carrier": "time", "input": "23:30:00.500Z", "expected": "23:30:00.5Z" },
|
|
77
|
+
{ "id": "time-frac-all-zeros-dropped", "carrier": "time", "input": "00:00:00.000", "expected": "00:00:00" },
|
|
78
|
+
{ "id": "time-24-to-00", "carrier": "time", "input": "24:00:00", "expected": "00:00:00" },
|
|
79
|
+
{ "id": "time-plus-zero-to-z", "carrier": "time", "input": "12:00:00+00:00", "expected": "12:00:00Z" }
|
|
80
|
+
]
|
|
81
|
+
}
|