@entelekheia/ref-id 0.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.
@@ -0,0 +1,1488 @@
1
+ {
2
+ "specVersion": "1.1.0",
3
+ "scheme": "ref",
4
+ "grammar": {
5
+ "dialect": "ecmascript-2018",
6
+ "expression": "^ref:(?:(?<version>[0-9]+):)?(?<type>[a-z][a-z0-9+.-]*):(?<locator>[^;#\\r\\n]+)(?:;(?<state>[^#\\r\\n]*))?(?:#(?<fragment>[^\\r\\n]*))?$",
7
+ "groups": [
8
+ "version",
9
+ "type",
10
+ "locator",
11
+ "state",
12
+ "fragment"
13
+ ],
14
+ "adaptationsApplyTo": "every pattern in this file — the expression, the pair grammars, the forms, the dispatch and refinement patterns",
15
+ "anchors": "the canonical dialect ends a pattern with $ meaning end of input; an engine whose $ also matches before a final line break declares the replacement above",
16
+ "adaptations": {
17
+ "ecmascript-2018": {
18
+ "replace": []
19
+ },
20
+ "pcre2": {
21
+ "replace": [
22
+ [
23
+ "$",
24
+ "\\z"
25
+ ]
26
+ ]
27
+ },
28
+ "rust-regex": {
29
+ "replace": []
30
+ },
31
+ "python-re": {
32
+ "replace": [
33
+ [
34
+ "(?<",
35
+ "(?P<"
36
+ ],
37
+ [
38
+ "$",
39
+ "\\Z"
40
+ ]
41
+ ]
42
+ },
43
+ "swift-regex": {
44
+ "replace": []
45
+ }
46
+ },
47
+ "state": {
48
+ "separator": ";",
49
+ "pair": "^(?<key>[a-z][a-z0-9-]*)=(?<value>[^\\r\\n]*)$",
50
+ "unknownKey": "carry-through",
51
+ "repeatedKey": "malformed"
52
+ },
53
+ "fragment": {
54
+ "separator": ";",
55
+ "pair": "^(?<key>[a-z][a-z0-9-]*)=(?<value>[^\\r\\n]*)$",
56
+ "path": "the text before the first separator, verbatim",
57
+ "repeatedKey": "malformed"
58
+ }
59
+ },
60
+ "version": {
61
+ "default": 1,
62
+ "supported": [
63
+ 1
64
+ ],
65
+ "mints": [
66
+ "normalisation or percent-encoding",
67
+ "separators or shape"
68
+ ],
69
+ "note": "A leading digit can only be a version because a type starts with a letter; ref:v2:... is type v2."
70
+ },
71
+ "encoding": {
72
+ "locator": {
73
+ "reserved": [],
74
+ "note": "a locator is handed to its validator verbatim — a format's own percent-encoding is the format's, and a value that needs a reserved character cannot be a locator"
75
+ },
76
+ "nested": {
77
+ "reserved": [
78
+ ";",
79
+ "#",
80
+ "%"
81
+ ],
82
+ "table": {
83
+ ";": "%3B",
84
+ "#": "%23",
85
+ "%": "%25"
86
+ },
87
+ "strict": "every % in a nested value begins one of the forms in the table; any other % is malformed at the key"
88
+ }
89
+ },
90
+ "dispatch": {
91
+ "pkg": {
92
+ "locator": "package-url",
93
+ "validator": "package-url",
94
+ "delegate": "type-prefixed",
95
+ "corpus": "the nearest manifest declaring a name, skipping any ancestor manifest whose own workspace or package configuration excludes the file; unversioned, the Package URL names the living package and state= carries the frozen one",
96
+ "reference": "https://github.com/package-url/purl-spec"
97
+ },
98
+ "folder": {
99
+ "locator": "declared-corpus-name",
100
+ "validator": "declared-name",
101
+ "delegate": "verbatim",
102
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$",
103
+ "declaredBy": "one line in the repository configuration, per subtree, nearest ancestor wins",
104
+ "corpus": "only where no manifest declares a name"
105
+ },
106
+ "domain": {
107
+ "locator": "hostname-path",
108
+ "validator": "declared-name",
109
+ "delegate": "verbatim",
110
+ "pattern": "^(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z][a-z0-9-]{1,62}(?:/[a-z0-9][a-z0-9._~-]*)*(?:@[A-Za-z0-9][A-Za-z0-9._+-]*)?$",
111
+ "declaredBy": "the owner of the host names what lives under it: each path segment is a declared name, never a URL path, and @version is optional on the last segment",
112
+ "corpus": "a manifest that declares a domain names the corpus by that domain; the package name stays the identifier of the software",
113
+ "reference": "RFC 1123 host labels, lowercase, internationalised labels in their punycode form"
114
+ },
115
+ "email": {
116
+ "locator": "addr-spec",
117
+ "validator": "declared-name",
118
+ "delegate": "verbatim",
119
+ "pattern": "^[A-Za-z0-9._%+-]+@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z][a-z0-9-]{1,62}$",
120
+ "declaredBy": "a mailbox identifies a person or a role; nothing lives under it — what a mailbox publishes belongs to the ecosystem that publishes it",
121
+ "reference": "RFC 5322 addr-spec, dot-atom local part"
122
+ },
123
+ "dot-agent": {
124
+ "locator": "agent-id",
125
+ "validator": "declared-name",
126
+ "delegate": "verbatim",
127
+ "pattern": "^(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z][a-z0-9-]{1,62}(?:/[A-Za-z0-9][A-Za-z0-9._-]*)?|[A-Za-z0-9._%+-]+@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z][a-z0-9-]{1,62}|unknown)/[A-Za-z0-9][A-Za-z0-9._-]*(?::[A-Za-z0-9][A-Za-z0-9._+-]*)?$",
128
+ "declaredBy": "the agent identifier grammar namespace/name[:version]: the namespace is a domain, a platform/user under a domain, a mailbox, or the literal unknown; the ~digest of the full form is carried by state= instead",
129
+ "corpus": "the agent bundle",
130
+ "reference": "dot-agent agent-id reference"
131
+ }
132
+ },
133
+ "typeRegistry": "a type names the naming system that owns the locator grammar; an unregistered type parses to uncovered, never to malformed, and registering its validator here is what promotes it — the registry is this table",
134
+ "delegation": {
135
+ "type-prefixed": "the validator receives <type>:<locator> verbatim — the type token is also the delegated format's own scheme",
136
+ "verbatim": "the validator receives the locator alone"
137
+ },
138
+ "statuses": [
139
+ "ok",
140
+ "malformed",
141
+ "uncovered",
142
+ "unsupported"
143
+ ],
144
+ "parts": [
145
+ "grammar",
146
+ "type",
147
+ "state",
148
+ "fragment",
149
+ "locator",
150
+ "member",
151
+ "<qualifier key>",
152
+ "<refinement key>"
153
+ ],
154
+ "unknownType": "uncovered",
155
+ "roles": {
156
+ "identity": [
157
+ "type",
158
+ "locator",
159
+ "fragment.path"
160
+ ],
161
+ "location": [
162
+ "path",
163
+ "byte range"
164
+ ],
165
+ "freeze": [
166
+ "state"
167
+ ],
168
+ "moment": [
169
+ "when"
170
+ ]
171
+ },
172
+ "qualifiers": {
173
+ "state": {
174
+ "role": "freeze",
175
+ "forms": [
176
+ "swhid",
177
+ "git",
178
+ "content-hash",
179
+ "none"
180
+ ]
181
+ },
182
+ "by": {
183
+ "role": "instrument",
184
+ "forms": [
185
+ "ref",
186
+ "sha256"
187
+ ]
188
+ },
189
+ "over": {
190
+ "role": "population",
191
+ "forms": [
192
+ "ref",
193
+ "sha256"
194
+ ]
195
+ },
196
+ "when": {
197
+ "role": "moment",
198
+ "forms": [
199
+ "rfc3339"
200
+ ]
201
+ }
202
+ },
203
+ "forms": {
204
+ "swhid": {
205
+ "pattern": "^swh:1:(cnt|dir|rev|rel|snp):[0-9a-f]{40}$",
206
+ "reference": "ISO/IEC 18670 core identifier, no SWHID qualifiers"
207
+ },
208
+ "sha256": {
209
+ "pattern": "^sha256:[0-9a-f]{64}$",
210
+ "digest": true
211
+ },
212
+ "git": {
213
+ "pattern": "^git:[0-9a-f]{7,40}$",
214
+ "reference": "a commit object name, abbreviated or full — a freeze the repository can resolve, weaker than a SWHID only in that it names no archive"
215
+ },
216
+ "content-hash": {
217
+ "pattern": "^(sha256|blake3):[0-9a-f]{64}$",
218
+ "reference": "a hash of the content itself, for what no version control holds; never a digest of members, so it declares no digest: true"
219
+ },
220
+ "none": {
221
+ "pattern": "^none$",
222
+ "reference": "the declared level for a state nobody captured — an unknown state recorded honestly, worth more than a properties hash presented as proof of content"
223
+ },
224
+ "rfc3339": {
225
+ "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]+)?Z$",
226
+ "reference": "RFC 3339, UTC only — a moment, never a state"
227
+ },
228
+ "ref": {
229
+ "nested": true,
230
+ "depth": 1,
231
+ "encoding": "nested"
232
+ }
233
+ },
234
+ "refinements": {
235
+ "lines": {
236
+ "pattern": "^[0-9]+(,[0-9]+)?$",
237
+ "range": "ascending",
238
+ "boundSeparator": ",",
239
+ "reference": "RFC 5147 placement"
240
+ },
241
+ "item": {
242
+ "pattern": "^[0-9]+$"
243
+ },
244
+ "para": {
245
+ "pattern": "^[0-9]+$"
246
+ }
247
+ },
248
+ "unknownRefinement": "carry-through",
249
+ "resolutionStates": [
250
+ "resolved",
251
+ "unresolved",
252
+ "uncovered",
253
+ "archival"
254
+ ],
255
+ "stateLevels": [
256
+ "properties",
257
+ "content-hash",
258
+ "none"
259
+ ],
260
+ "fragmentGrammars": {
261
+ "governed-record": {
262
+ "declaredName": "<provider>/<type>@<template version>/<name>, every part from the record's own front matter stamp; the provider of the type declares the form of <name>",
263
+ "tieBreak": "none"
264
+ },
265
+ "markdown": {
266
+ "declaredName": "the heading text",
267
+ "tieBreak": "ordinal on collision within one document"
268
+ },
269
+ "code-symbol": {
270
+ "declaredName": "the fully-qualified name, no path",
271
+ "tieBreak": "per the language"
272
+ },
273
+ "behavior-state": {
274
+ "declaredName": "the state name within its agent, never within its file",
275
+ "tieBreak": "none"
276
+ },
277
+ "data-declared-id": {
278
+ "declaredName": "an id field the record declares in its own data — a case slug, a capability name — not its front matter",
279
+ "tieBreak": "none"
280
+ }
281
+ },
282
+ "digest": {
283
+ "algorithm": "sha256",
284
+ "over": "identifier-strings",
285
+ "order": "declared",
286
+ "join": "\n",
287
+ "encoding": "utf-8",
288
+ "dedupe": false,
289
+ "output": "sha256:<64 lowercase hex>"
290
+ },
291
+ "canonicalisation": {
292
+ "form": "json-sorted-keys-compact",
293
+ "rules": [
294
+ "object keys sorted by UTF-16 code unit order",
295
+ "no whitespace outside strings",
296
+ "strings escaped as JSON.stringify does",
297
+ "numbers are integers only",
298
+ "UTF-8 bytes of the result are what the digest covers"
299
+ ],
300
+ "sidecar": "ref-id.json.sha256 holds the digest of this file; the file never contains its own digest"
301
+ },
302
+ "envelope": {
303
+ "fields": {
304
+ "id": "required",
305
+ "sets": "required when the identifier carries a digest",
306
+ "alsoKnownAs": "optional, a declaration never a similarity match",
307
+ "data": "opaque"
308
+ },
309
+ "selfReference": "id",
310
+ "setsField": "sets",
311
+ "digestForms": "every form declaring digest: true",
312
+ "invariant": "for every qualifier whose value is a digest, sets[qualifier] exists and digest(sets[qualifier]) equals it; and id equals the identifier the envelope was requested under",
313
+ "onFailure": "refuse at ingestion"
314
+ },
315
+ "vectors": {
316
+ "parse": [
317
+ {
318
+ "name": "pkg with declared name",
319
+ "input": "ref:pkg:npm/@acme/scanner-core@0.1.0#Observation",
320
+ "expect": {
321
+ "status": "ok",
322
+ "version": 1,
323
+ "qualifiers": [],
324
+ "fragment": {
325
+ "path": "Observation",
326
+ "refinements": []
327
+ },
328
+ "type": "pkg",
329
+ "locator": "npm/@acme/scanner-core@0.1.0",
330
+ "delegated": "pkg:npm/@acme/scanner-core@0.1.0"
331
+ }
332
+ },
333
+ {
334
+ "name": "pkg with versioned declared-name path",
335
+ "input": "ref:pkg:npm/@acme/trait-citation-fidelity@0.0.1#citation-fidelity@1/not-invented",
336
+ "expect": {
337
+ "status": "ok",
338
+ "version": 1,
339
+ "qualifiers": [],
340
+ "fragment": {
341
+ "path": "citation-fidelity@1/not-invented",
342
+ "refinements": []
343
+ },
344
+ "type": "pkg",
345
+ "locator": "npm/@acme/trait-citation-fidelity@0.0.1"
346
+ }
347
+ },
348
+ {
349
+ "name": "folder corpus with declared name",
350
+ "input": "ref:folder:acme-governance#learnings/a-chunk-carrying-no-answer-means-death-or-health",
351
+ "expect": {
352
+ "status": "ok",
353
+ "version": 1,
354
+ "qualifiers": [],
355
+ "fragment": {
356
+ "path": "learnings/a-chunk-carrying-no-answer-means-death-or-health",
357
+ "refinements": []
358
+ },
359
+ "type": "folder",
360
+ "locator": "acme-governance"
361
+ }
362
+ },
363
+ {
364
+ "name": "folder with state and by digest",
365
+ "input": "ref:folder:acme-tools;state=swh:1:rev:7e29bb6000000000000000000000000000000000;by=sha256:41b9caaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
366
+ "expect": {
367
+ "status": "ok",
368
+ "version": 1,
369
+ "qualifiers": [
370
+ [
371
+ "state",
372
+ "swh:1:rev:7e29bb6000000000000000000000000000000000"
373
+ ],
374
+ [
375
+ "by",
376
+ "sha256:41b9caaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
377
+ ]
378
+ ],
379
+ "fragment": null,
380
+ "type": "folder",
381
+ "locator": "acme-tools"
382
+ }
383
+ },
384
+ {
385
+ "name": "explicit version 1 is version 1",
386
+ "input": "ref:1:pkg:npm/x@1.0.0",
387
+ "expect": {
388
+ "status": "ok",
389
+ "version": 1,
390
+ "qualifiers": [],
391
+ "fragment": null,
392
+ "type": "pkg",
393
+ "locator": "npm/x@1.0.0",
394
+ "delegated": "pkg:npm/x@1.0.0",
395
+ "explicitVersion": true
396
+ }
397
+ },
398
+ {
399
+ "name": "version outside the supported range is unsupported, not malformed",
400
+ "input": "ref:2:pkg:npm/x@1.0.0",
401
+ "expect": {
402
+ "status": "unsupported",
403
+ "version": 2,
404
+ "type": "pkg",
405
+ "locator": "npm/x@1.0.0"
406
+ }
407
+ },
408
+ {
409
+ "name": "letter-led token is a type, not a version",
410
+ "input": "ref:v2:pkg:npm/x",
411
+ "expect": {
412
+ "status": "uncovered",
413
+ "version": 1,
414
+ "type": "v2",
415
+ "locator": "pkg:npm/x"
416
+ }
417
+ },
418
+ {
419
+ "name": "unknown type is uncovered, never an exception",
420
+ "input": "ref:doi:10.1000/182",
421
+ "expect": {
422
+ "status": "uncovered",
423
+ "version": 1,
424
+ "type": "doi",
425
+ "locator": "10.1000/182"
426
+ }
427
+ },
428
+ {
429
+ "name": "unknown qualifier key is carried through",
430
+ "input": "ref:folder:acme-tools;zz=1;state=swh:1:rev:7e29bb6000000000000000000000000000000000",
431
+ "expect": {
432
+ "status": "ok",
433
+ "version": 1,
434
+ "qualifiers": [
435
+ [
436
+ "zz",
437
+ "1"
438
+ ],
439
+ [
440
+ "state",
441
+ "swh:1:rev:7e29bb6000000000000000000000000000000000"
442
+ ]
443
+ ],
444
+ "fragment": null,
445
+ "type": "folder",
446
+ "locator": "acme-tools"
447
+ }
448
+ },
449
+ {
450
+ "name": "nested by carrying a fragment",
451
+ "input": "ref:folder:acme-tools;by=ref:pkg:npm/@acme/profiles@0.1.0%23profile/conformance@1",
452
+ "expect": {
453
+ "status": "ok",
454
+ "version": 1,
455
+ "qualifiers": [
456
+ [
457
+ "by",
458
+ "ref:pkg:npm/@acme/profiles@0.1.0%23profile/conformance@1"
459
+ ]
460
+ ],
461
+ "fragment": null,
462
+ "type": "folder",
463
+ "locator": "acme-tools",
464
+ "nested": {
465
+ "by": "ref:pkg:npm/@acme/profiles@0.1.0#profile/conformance@1"
466
+ }
467
+ }
468
+ },
469
+ {
470
+ "name": "nested by carrying its own qualifier",
471
+ "input": "ref:folder:acme-tools;by=ref:pkg:npm/x@1.0.0%3Bstate=swh:1:rev:7e29bb6000000000000000000000000000000000%23S",
472
+ "expect": {
473
+ "status": "ok",
474
+ "version": 1,
475
+ "qualifiers": [
476
+ [
477
+ "by",
478
+ "ref:pkg:npm/x@1.0.0%3Bstate=swh:1:rev:7e29bb6000000000000000000000000000000000%23S"
479
+ ]
480
+ ],
481
+ "fragment": null,
482
+ "type": "folder",
483
+ "locator": "acme-tools",
484
+ "nested": {
485
+ "by": "ref:pkg:npm/x@1.0.0;state=swh:1:rev:7e29bb6000000000000000000000000000000000#S"
486
+ }
487
+ }
488
+ },
489
+ {
490
+ "name": "two different nested values are two different identifiers",
491
+ "input": "ref:folder:acme-tools;by=ref:pkg:npm/x@1.0.0%3Bstate=swh:1:rev:7e29bb6000000000000000000000000000000000",
492
+ "expect": {
493
+ "status": "ok",
494
+ "version": 1,
495
+ "qualifiers": [
496
+ [
497
+ "by",
498
+ "ref:pkg:npm/x@1.0.0%3Bstate=swh:1:rev:7e29bb6000000000000000000000000000000000"
499
+ ]
500
+ ],
501
+ "fragment": null,
502
+ "type": "folder",
503
+ "locator": "acme-tools",
504
+ "nested": {
505
+ "by": "ref:pkg:npm/x@1.0.0;state=swh:1:rev:7e29bb6000000000000000000000000000000000"
506
+ }
507
+ }
508
+ },
509
+ {
510
+ "name": "a purl keeps its own percent-encoding intact",
511
+ "input": "ref:pkg:npm/%40acme/x@1.0.0#S",
512
+ "expect": {
513
+ "status": "ok",
514
+ "version": 1,
515
+ "qualifiers": [],
516
+ "fragment": {
517
+ "path": "S",
518
+ "refinements": []
519
+ },
520
+ "type": "pkg",
521
+ "locator": "npm/%40acme/x@1.0.0",
522
+ "delegated": "pkg:npm/%40acme/x@1.0.0"
523
+ }
524
+ },
525
+ {
526
+ "name": "an identifier is one line",
527
+ "input": "ref:folder:acme-tools\nx",
528
+ "expect": {
529
+ "status": "malformed",
530
+ "part": "grammar"
531
+ }
532
+ },
533
+ {
534
+ "name": "nesting deeper than one level is malformed",
535
+ "input": "ref:folder:b;by=ref:folder:a%3Bby=ref:pkg:npm/x@1.0.0%2523S",
536
+ "expect": {
537
+ "status": "malformed",
538
+ "part": "by"
539
+ }
540
+ },
541
+ {
542
+ "name": "a reversed line range is malformed",
543
+ "input": "ref:folder:acme-tools#AGENTS.md;lines=20,10",
544
+ "expect": {
545
+ "status": "malformed",
546
+ "part": "lines"
547
+ }
548
+ },
549
+ {
550
+ "name": "an unknown key keeps an encoded separator in its value",
551
+ "input": "ref:folder:acme-tools;zz=a%3Bb;state=swh:1:rev:7e29bb6000000000000000000000000000000000",
552
+ "expect": {
553
+ "status": "ok",
554
+ "version": 1,
555
+ "qualifiers": [
556
+ [
557
+ "zz",
558
+ "a%3Bb"
559
+ ],
560
+ [
561
+ "state",
562
+ "swh:1:rev:7e29bb6000000000000000000000000000000000"
563
+ ]
564
+ ],
565
+ "fragment": null,
566
+ "type": "folder",
567
+ "locator": "acme-tools"
568
+ }
569
+ },
570
+ {
571
+ "name": "refinement after the declared-name path",
572
+ "input": "ref:folder:acme-tools#AGENTS.md;lines=10,20",
573
+ "expect": {
574
+ "status": "ok",
575
+ "version": 1,
576
+ "qualifiers": [],
577
+ "fragment": {
578
+ "path": "AGENTS.md",
579
+ "refinements": [
580
+ [
581
+ "lines",
582
+ "10,20"
583
+ ]
584
+ ]
585
+ },
586
+ "type": "folder",
587
+ "locator": "acme-tools"
588
+ }
589
+ },
590
+ {
591
+ "name": "qualifier before the hash, refinement after it",
592
+ "input": "ref:folder:acme-tools;state=swh:1:cnt:3404a00f00000000000000000000000000000000#AGENTS.md;lines=1",
593
+ "expect": {
594
+ "status": "ok",
595
+ "version": 1,
596
+ "qualifiers": [
597
+ [
598
+ "state",
599
+ "swh:1:cnt:3404a00f00000000000000000000000000000000"
600
+ ]
601
+ ],
602
+ "fragment": {
603
+ "path": "AGENTS.md",
604
+ "refinements": [
605
+ [
606
+ "lines",
607
+ "1"
608
+ ]
609
+ ]
610
+ },
611
+ "type": "folder",
612
+ "locator": "acme-tools"
613
+ }
614
+ },
615
+ {
616
+ "name": "unknown refinement key is carried through",
617
+ "input": "ref:folder:acme-tools#AGENTS.md;zz=q",
618
+ "expect": {
619
+ "status": "ok",
620
+ "version": 1,
621
+ "qualifiers": [],
622
+ "fragment": {
623
+ "path": "AGENTS.md",
624
+ "refinements": [
625
+ [
626
+ "zz",
627
+ "q"
628
+ ]
629
+ ]
630
+ },
631
+ "type": "folder",
632
+ "locator": "acme-tools"
633
+ }
634
+ },
635
+ {
636
+ "name": "over as a digest",
637
+ "input": "ref:folder:acme-tools;over=sha256:41b9caaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
638
+ "expect": {
639
+ "status": "ok",
640
+ "version": 1,
641
+ "qualifiers": [
642
+ [
643
+ "over",
644
+ "sha256:41b9caaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
645
+ ]
646
+ ],
647
+ "fragment": null,
648
+ "type": "folder",
649
+ "locator": "acme-tools"
650
+ }
651
+ },
652
+ {
653
+ "name": "a validator failure outranks an unknown type",
654
+ "input": "ref:doi:10.1000/182;state=swh:1:rev:7e29bb6",
655
+ "expect": {
656
+ "status": "malformed",
657
+ "part": "state"
658
+ }
659
+ },
660
+ {
661
+ "name": "an unsupported version is not validated further",
662
+ "input": "ref:2:pkg:npm/x@1.0.0;state=swh:1:rev:7e29bb6",
663
+ "expect": {
664
+ "status": "unsupported",
665
+ "version": 2,
666
+ "type": "pkg",
667
+ "locator": "npm/x@1.0.0",
668
+ "qualifiers": [
669
+ [
670
+ "state",
671
+ "swh:1:rev:7e29bb6"
672
+ ]
673
+ ]
674
+ }
675
+ },
676
+ {
677
+ "name": "an uncovered type still carries its qualifiers and fragment",
678
+ "input": "ref:doi:10.1000/182;state=swh:1:rev:7e29bb6000000000000000000000000000000000#S",
679
+ "expect": {
680
+ "status": "uncovered",
681
+ "version": 1,
682
+ "type": "doi",
683
+ "locator": "10.1000/182",
684
+ "qualifiers": [
685
+ [
686
+ "state",
687
+ "swh:1:rev:7e29bb6000000000000000000000000000000000"
688
+ ]
689
+ ],
690
+ "fragment": {
691
+ "path": "S",
692
+ "refinements": []
693
+ }
694
+ }
695
+ },
696
+ {
697
+ "name": "percent in a nested value is itself encoded",
698
+ "input": "ref:folder:acme-tools;by=ref:pkg:npm/x@1.0.0%2523src/y",
699
+ "expect": {
700
+ "status": "ok",
701
+ "version": 1,
702
+ "qualifiers": [
703
+ [
704
+ "by",
705
+ "ref:pkg:npm/x@1.0.0%2523src/y"
706
+ ]
707
+ ],
708
+ "fragment": null,
709
+ "type": "folder",
710
+ "locator": "acme-tools",
711
+ "nested": {
712
+ "by": "ref:pkg:npm/x@1.0.0%23src/y"
713
+ }
714
+ }
715
+ },
716
+ {
717
+ "name": "empty fragment is malformed",
718
+ "input": "ref:folder:acme-tools#",
719
+ "expect": {
720
+ "status": "malformed",
721
+ "part": "fragment"
722
+ }
723
+ },
724
+ {
725
+ "name": "empty qualifier is malformed",
726
+ "input": "ref:folder:acme-tools;",
727
+ "expect": {
728
+ "status": "malformed",
729
+ "part": "state"
730
+ }
731
+ },
732
+ {
733
+ "name": "qualifier without a value separator is malformed",
734
+ "input": "ref:folder:acme-tools;at",
735
+ "expect": {
736
+ "status": "malformed",
737
+ "part": "state"
738
+ }
739
+ },
740
+ {
741
+ "name": "a nested value that is not a ref and not a digest fails the by validator",
742
+ "input": "ref:folder:acme-tools;by=rumdl",
743
+ "expect": {
744
+ "status": "malformed",
745
+ "part": "by"
746
+ }
747
+ },
748
+ {
749
+ "name": "a refinement key on the qualifier side is malformed",
750
+ "input": "ref:folder:acme-tools;lines=1#AGENTS.md",
751
+ "expect": {
752
+ "status": "malformed",
753
+ "part": "lines"
754
+ }
755
+ },
756
+ {
757
+ "name": "a qualifier key on the fragment side is malformed",
758
+ "input": "ref:folder:acme-tools#AGENTS.md;state=swh:1:rev:7e29bb6000000000000000000000000000000000",
759
+ "expect": {
760
+ "status": "malformed",
761
+ "part": "state"
762
+ }
763
+ },
764
+ {
765
+ "name": "an empty path before a refinement is malformed",
766
+ "input": "ref:folder:acme-tools#;lines=1",
767
+ "expect": {
768
+ "status": "malformed",
769
+ "part": "fragment"
770
+ }
771
+ },
772
+ {
773
+ "name": "a nested identifier of an unsupported version is carried, not rejected",
774
+ "input": "ref:folder:acme-tools;by=ref:2:pkg:npm/x@1.0.0",
775
+ "expect": {
776
+ "status": "ok",
777
+ "version": 1,
778
+ "qualifiers": [
779
+ [
780
+ "by",
781
+ "ref:2:pkg:npm/x@1.0.0"
782
+ ]
783
+ ],
784
+ "fragment": null,
785
+ "type": "folder",
786
+ "locator": "acme-tools",
787
+ "nested": {
788
+ "by": "ref:2:pkg:npm/x@1.0.0"
789
+ }
790
+ }
791
+ },
792
+ {
793
+ "name": "a version token round-trips as written",
794
+ "input": "ref:01:pkg:npm/x@1.0.0",
795
+ "expect": {
796
+ "status": "ok",
797
+ "version": 1,
798
+ "qualifiers": [],
799
+ "fragment": null,
800
+ "type": "pkg",
801
+ "locator": "npm/x@1.0.0",
802
+ "explicitVersion": true,
803
+ "versionText": "01"
804
+ }
805
+ },
806
+ {
807
+ "name": "a trailing line break is malformed in every engine",
808
+ "input": "ref:folder:acme-tools\n",
809
+ "expect": {
810
+ "status": "malformed",
811
+ "part": "grammar"
812
+ }
813
+ },
814
+ {
815
+ "name": "a repeated qualifier key is malformed at that key",
816
+ "input": "ref:folder:acme-tools;state=swh:1:rev:7e29bb6000000000000000000000000000000000;state=swh:1:rev:7e29bb6000000000000000000000000000000000",
817
+ "expect": {
818
+ "status": "malformed",
819
+ "part": "state"
820
+ }
821
+ },
822
+ {
823
+ "name": "a repeated refinement key is malformed at that key",
824
+ "input": "ref:folder:acme-tools#AGENTS.md;lines=1;lines=2",
825
+ "expect": {
826
+ "status": "malformed",
827
+ "part": "lines"
828
+ }
829
+ },
830
+ {
831
+ "name": "a raw percent in a nested value is malformed at the key",
832
+ "input": "ref:folder:acme-tools;by=ref:pkg:npm/%40acme/x@1.0.0",
833
+ "expect": {
834
+ "status": "malformed",
835
+ "part": "by"
836
+ }
837
+ },
838
+ {
839
+ "name": "a nested value carries the inner percent-encoding doubled",
840
+ "input": "ref:folder:acme-tools;by=ref:pkg:npm/%2540acme/x@1.0.0",
841
+ "expect": {
842
+ "status": "ok",
843
+ "version": 1,
844
+ "qualifiers": [
845
+ [
846
+ "by",
847
+ "ref:pkg:npm/%2540acme/x@1.0.0"
848
+ ]
849
+ ],
850
+ "fragment": null,
851
+ "type": "folder",
852
+ "locator": "acme-tools",
853
+ "nested": {
854
+ "by": "ref:pkg:npm/%40acme/x@1.0.0"
855
+ }
856
+ }
857
+ },
858
+ {
859
+ "name": "when carries a moment in UTC",
860
+ "input": "ref:pkg:npm/@acme/scanner-core;when=2026-08-12T18:55:27.811Z#governance@0.0.1/markdown-link",
861
+ "expect": {
862
+ "status": "ok",
863
+ "version": 1,
864
+ "qualifiers": [
865
+ [
866
+ "when",
867
+ "2026-08-12T18:55:27.811Z"
868
+ ]
869
+ ],
870
+ "fragment": {
871
+ "path": "governance@0.0.1/markdown-link",
872
+ "refinements": []
873
+ },
874
+ "type": "pkg",
875
+ "locator": "npm/@acme/scanner-core",
876
+ "delegated": "pkg:npm/@acme/scanner-core"
877
+ }
878
+ },
879
+ {
880
+ "name": "a moment with an offset is malformed at when",
881
+ "input": "ref:folder:acme-tools;when=2026-08-12T15:55:27-03:00",
882
+ "expect": {
883
+ "status": "malformed",
884
+ "part": "when"
885
+ }
886
+ },
887
+ {
888
+ "name": "a typed governed-record fragment on an unversioned package corpus",
889
+ "input": "ref:pkg:npm/acme-tools#acme/adr@2/0019",
890
+ "expect": {
891
+ "status": "ok",
892
+ "version": 1,
893
+ "qualifiers": [],
894
+ "fragment": {
895
+ "path": "acme/adr@2/0019",
896
+ "refinements": []
897
+ },
898
+ "type": "pkg",
899
+ "locator": "npm/acme-tools",
900
+ "delegated": "pkg:npm/acme-tools"
901
+ }
902
+ },
903
+ {
904
+ "name": "dot-agent id in the unknown namespace tier",
905
+ "input": "ref:dot-agent:unknown/MentorUniversitario:1.4.1",
906
+ "expect": {
907
+ "status": "ok",
908
+ "version": 1,
909
+ "qualifiers": [],
910
+ "fragment": null,
911
+ "type": "dot-agent",
912
+ "locator": "unknown/MentorUniversitario:1.4.1"
913
+ }
914
+ },
915
+ {
916
+ "name": "dot-agent id under a domain, its digest carried by state",
917
+ "input": "ref:dot-agent:acme.example/doctor:v1.0;state=git:a1b2c3d4",
918
+ "expect": {
919
+ "status": "ok",
920
+ "version": 1,
921
+ "qualifiers": [
922
+ [
923
+ "state",
924
+ "git:a1b2c3d4"
925
+ ]
926
+ ],
927
+ "fragment": null,
928
+ "type": "dot-agent",
929
+ "locator": "acme.example/doctor:v1.0"
930
+ }
931
+ },
932
+ {
933
+ "name": "dot-agent id under a platform user and under a mailbox",
934
+ "input": "ref:dot-agent:github.example/someone/doctor",
935
+ "expect": {
936
+ "status": "ok",
937
+ "version": 1,
938
+ "qualifiers": [],
939
+ "fragment": null,
940
+ "type": "dot-agent",
941
+ "locator": "github.example/someone/doctor"
942
+ }
943
+ },
944
+ {
945
+ "name": "dot-agent id with a mailbox namespace",
946
+ "input": "ref:dot-agent:someone@mail.example/doctor:v2",
947
+ "expect": {
948
+ "status": "ok",
949
+ "version": 1,
950
+ "qualifiers": [],
951
+ "fragment": null,
952
+ "type": "dot-agent",
953
+ "locator": "someone@mail.example/doctor:v2"
954
+ }
955
+ },
956
+ {
957
+ "name": "a dotted namespace reads as the domain tier — whether the domain exists is the ecosystem's check, never the grammar's",
958
+ "input": "ref:dot-agent:mentor.pessoal.agent/MentorUniversitario:1.4.1",
959
+ "expect": {
960
+ "status": "ok",
961
+ "version": 1,
962
+ "qualifiers": [],
963
+ "fragment": null,
964
+ "type": "dot-agent",
965
+ "locator": "mentor.pessoal.agent/MentorUniversitario:1.4.1"
966
+ }
967
+ },
968
+ {
969
+ "name": "a domain names a corpus",
970
+ "input": "ref:domain:portfolio.example",
971
+ "expect": {
972
+ "status": "ok",
973
+ "version": 1,
974
+ "qualifiers": [],
975
+ "fragment": null,
976
+ "type": "domain",
977
+ "locator": "portfolio.example"
978
+ }
979
+ },
980
+ {
981
+ "name": "a declared name under a domain, versioned, with a section fragment",
982
+ "input": "ref:domain:portfolio.example/case-xpto@2#results",
983
+ "expect": {
984
+ "status": "ok",
985
+ "version": 1,
986
+ "qualifiers": [],
987
+ "fragment": {
988
+ "path": "results",
989
+ "refinements": []
990
+ },
991
+ "type": "domain",
992
+ "locator": "portfolio.example/case-xpto@2"
993
+ }
994
+ },
995
+ {
996
+ "name": "an uppercase host is malformed at the locator",
997
+ "input": "ref:domain:Portfolio.example/case",
998
+ "expect": {
999
+ "status": "malformed",
1000
+ "part": "locator"
1001
+ }
1002
+ },
1003
+ {
1004
+ "name": "a mailbox identifies a person",
1005
+ "input": "ref:email:someone@mail.example",
1006
+ "expect": {
1007
+ "status": "ok",
1008
+ "version": 1,
1009
+ "qualifiers": [],
1010
+ "fragment": null,
1011
+ "type": "email",
1012
+ "locator": "someone@mail.example"
1013
+ }
1014
+ },
1015
+ {
1016
+ "name": "nothing lives under a mailbox",
1017
+ "input": "ref:email:someone@mail.example/doctor",
1018
+ "expect": {
1019
+ "status": "malformed",
1020
+ "part": "locator"
1021
+ }
1022
+ },
1023
+ {
1024
+ "name": "an unregistered naming system is uncovered",
1025
+ "input": "ref:spotify:track/4uLU6hMCjMI75M1A2tKUQC",
1026
+ "expect": {
1027
+ "status": "uncovered",
1028
+ "version": 1,
1029
+ "type": "spotify",
1030
+ "locator": "track/4uLU6hMCjMI75M1A2tKUQC"
1031
+ }
1032
+ },
1033
+ {
1034
+ "name": "a content hash freezes what no version control holds",
1035
+ "input": "ref:domain:models.example/quant-q4;state=blake3:0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f",
1036
+ "expect": {
1037
+ "status": "ok",
1038
+ "version": 1,
1039
+ "qualifiers": [
1040
+ [
1041
+ "state",
1042
+ "blake3:0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f"
1043
+ ]
1044
+ ],
1045
+ "fragment": null,
1046
+ "type": "domain",
1047
+ "locator": "models.example/quant-q4"
1048
+ }
1049
+ },
1050
+ {
1051
+ "name": "none is a declared state level",
1052
+ "input": "ref:domain:models.example/quant-q4;state=none",
1053
+ "expect": {
1054
+ "status": "ok",
1055
+ "version": 1,
1056
+ "qualifiers": [
1057
+ [
1058
+ "state",
1059
+ "none"
1060
+ ]
1061
+ ],
1062
+ "fragment": null,
1063
+ "type": "domain",
1064
+ "locator": "models.example/quant-q4"
1065
+ }
1066
+ },
1067
+ {
1068
+ "name": "a state in an undeclared form is malformed at state",
1069
+ "input": "ref:domain:models.example/quant-q4;state=sha1:0f0f0f0f",
1070
+ "expect": {
1071
+ "status": "malformed",
1072
+ "part": "state"
1073
+ }
1074
+ },
1075
+ {
1076
+ "name": "uppercase type is malformed",
1077
+ "input": "ref:Pkg:npm/x@1.0.0",
1078
+ "expect": {
1079
+ "status": "malformed",
1080
+ "part": "grammar"
1081
+ }
1082
+ },
1083
+ {
1084
+ "name": "missing scheme is malformed",
1085
+ "input": "pkg:npm/x@1.0.0",
1086
+ "expect": {
1087
+ "status": "malformed",
1088
+ "part": "grammar"
1089
+ }
1090
+ },
1091
+ {
1092
+ "name": "empty locator is malformed",
1093
+ "input": "ref:pkg:",
1094
+ "expect": {
1095
+ "status": "malformed",
1096
+ "part": "grammar"
1097
+ }
1098
+ },
1099
+ {
1100
+ "name": "abbreviated SWHID fails the state validator",
1101
+ "input": "ref:folder:acme-tools;state=swh:1:rev:7e29bb6",
1102
+ "expect": {
1103
+ "status": "malformed",
1104
+ "part": "state"
1105
+ }
1106
+ },
1107
+ {
1108
+ "name": "invalid purl fails the locator validator",
1109
+ "input": "ref:pkg:not-a-purl",
1110
+ "expect": {
1111
+ "status": "malformed",
1112
+ "part": "locator"
1113
+ }
1114
+ },
1115
+ {
1116
+ "name": "malformed refinement value",
1117
+ "input": "ref:folder:acme-tools#AGENTS.md;lines=ten",
1118
+ "expect": {
1119
+ "status": "malformed",
1120
+ "part": "lines"
1121
+ }
1122
+ },
1123
+ {
1124
+ "name": "corpus name with a path separator is malformed",
1125
+ "input": "ref:folder:acme-tools/docs#AGENTS.md",
1126
+ "expect": {
1127
+ "status": "malformed",
1128
+ "part": "locator"
1129
+ }
1130
+ }
1131
+ ],
1132
+ "roundtrip": [
1133
+ "ref:pkg:npm/@acme/scanner-core@0.1.0#Observation",
1134
+ "ref:1:pkg:npm/x@1.0.0",
1135
+ "ref:folder:acme-tools;zz=1;state=swh:1:rev:7e29bb6000000000000000000000000000000000",
1136
+ "ref:folder:acme-tools;by=ref:pkg:npm/@acme/profiles@0.1.0%23profile/conformance@1",
1137
+ "ref:folder:acme-tools;by=ref:pkg:npm/x@1.0.0%3Bstate=swh:1:rev:7e29bb6000000000000000000000000000000000%23S",
1138
+ "ref:pkg:npm/%40acme/x@1.0.0#S",
1139
+ "ref:folder:acme-tools;zz=a%3Bb;state=swh:1:rev:7e29bb6000000000000000000000000000000000",
1140
+ "ref:folder:acme-tools;state=swh:1:cnt:3404a00f00000000000000000000000000000000#AGENTS.md;lines=1",
1141
+ "ref:folder:acme-tools#AGENTS.md;zz=q",
1142
+ "ref:doi:10.1000/182",
1143
+ "ref:dot-agent:unknown/MentorUniversitario:1.4.1",
1144
+ "ref:domain:portfolio.example/case-xpto@2;state=none#results",
1145
+ "ref:email:someone@mail.example",
1146
+ "ref:2:pkg:npm/x@1.0.0",
1147
+ "ref:01:pkg:npm/x@1.0.0",
1148
+ "ref:pkg:npm/@acme/scanner-core;when=2026-08-12T18:55:27.811Z#governance@0.0.1/markdown-link",
1149
+ "ref:pkg:npm/acme-tools#acme/adr@2/0019",
1150
+ "ref:folder:acme-tools;by=ref:2:pkg:npm/x@1.0.0",
1151
+ "ref:doi:10.1000/182;state=swh:1:rev:7e29bb6000000000000000000000000000000000#S",
1152
+ "ref:folder:acme-tools;by=ref:pkg:npm/x@1.0.0%2523src/y"
1153
+ ],
1154
+ "build": [
1155
+ {
1156
+ "name": "same declared name through two paths is one identifier",
1157
+ "parts": {
1158
+ "type": "pkg",
1159
+ "locator": "npm/x@1.0.0",
1160
+ "fragment": "Observation",
1161
+ "location": {
1162
+ "path": "src/a.ts"
1163
+ }
1164
+ },
1165
+ "expect": "ref:pkg:npm/x@1.0.0#Observation"
1166
+ },
1167
+ {
1168
+ "name": "same declared name through two paths is one identifier (other path)",
1169
+ "parts": {
1170
+ "type": "pkg",
1171
+ "locator": "npm/x@1.0.0",
1172
+ "fragment": "Observation",
1173
+ "location": {
1174
+ "path": "lib/b.ts",
1175
+ "lines": "4,9"
1176
+ }
1177
+ },
1178
+ "expect": "ref:pkg:npm/x@1.0.0#Observation"
1179
+ },
1180
+ {
1181
+ "name": "nested qualifier value is encoded by the builder",
1182
+ "parts": {
1183
+ "type": "folder",
1184
+ "locator": "acme-tools",
1185
+ "qualifiers": [
1186
+ [
1187
+ "by",
1188
+ {
1189
+ "nested": "ref:pkg:npm/x@1.0.0;state=swh:1:rev:7e29bb6000000000000000000000000000000000#S"
1190
+ }
1191
+ ]
1192
+ ]
1193
+ },
1194
+ "expect": "ref:folder:acme-tools;by=ref:pkg:npm/x@1.0.0%3Bstate=swh:1:rev:7e29bb6000000000000000000000000000000000%23S"
1195
+ },
1196
+ {
1197
+ "name": "a pkg locator given as the bare group builds as is",
1198
+ "parts": {
1199
+ "type": "pkg",
1200
+ "locator": "npm/x@1.0.0",
1201
+ "fragment": "Observation"
1202
+ },
1203
+ "expect": "ref:pkg:npm/x@1.0.0#Observation"
1204
+ },
1205
+ {
1206
+ "name": "an intact purl given as locator has its scheme folded into the type",
1207
+ "parts": {
1208
+ "type": "pkg",
1209
+ "locator": "pkg:npm/x@1.0.0",
1210
+ "fragment": "S"
1211
+ },
1212
+ "expect": "ref:pkg:npm/x@1.0.0#S"
1213
+ },
1214
+ {
1215
+ "name": "a locator carrying a reserved character is refused",
1216
+ "parts": {
1217
+ "type": "pkg",
1218
+ "locator": "pkg:npm/x@1.0.0#src/y",
1219
+ "fragment": "S"
1220
+ },
1221
+ "expect": {
1222
+ "error": "locator"
1223
+ }
1224
+ },
1225
+ {
1226
+ "name": "a fragment path carrying the separator is refused",
1227
+ "parts": {
1228
+ "type": "folder",
1229
+ "locator": "acme-tools",
1230
+ "fragment": "a;b"
1231
+ },
1232
+ "expect": {
1233
+ "error": "fragment"
1234
+ }
1235
+ },
1236
+ {
1237
+ "name": "a nested identifier passed as a plain string is refused",
1238
+ "parts": {
1239
+ "type": "folder",
1240
+ "locator": "acme-tools",
1241
+ "qualifiers": [
1242
+ [
1243
+ "by",
1244
+ "ref:pkg:npm/x@1.0.0#S"
1245
+ ]
1246
+ ]
1247
+ },
1248
+ "expect": {
1249
+ "error": "by"
1250
+ }
1251
+ },
1252
+ {
1253
+ "name": "a type carrying the field separator is refused",
1254
+ "parts": {
1255
+ "type": "1:folder",
1256
+ "locator": "a"
1257
+ },
1258
+ "expect": {
1259
+ "error": "type"
1260
+ }
1261
+ },
1262
+ {
1263
+ "name": "a dot-agent identifier builds with its digest in state",
1264
+ "parts": {
1265
+ "type": "dot-agent",
1266
+ "locator": "acme.example/doctor:v1.0",
1267
+ "qualifiers": [
1268
+ [
1269
+ "state",
1270
+ "git:a1b2c3d4"
1271
+ ]
1272
+ ]
1273
+ },
1274
+ "expect": "ref:dot-agent:acme.example/doctor:v1.0;state=git:a1b2c3d4"
1275
+ },
1276
+ {
1277
+ "name": "a content hash is accepted as a state",
1278
+ "parts": {
1279
+ "type": "domain",
1280
+ "locator": "models.example/quant-q4",
1281
+ "qualifiers": [
1282
+ [
1283
+ "state",
1284
+ "sha256:0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f"
1285
+ ]
1286
+ ]
1287
+ },
1288
+ "expect": "ref:domain:models.example/quant-q4;state=sha256:0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f"
1289
+ },
1290
+ {
1291
+ "name": "a digest value passes through the builder",
1292
+ "parts": {
1293
+ "type": "folder",
1294
+ "locator": "acme-tools",
1295
+ "qualifiers": [
1296
+ [
1297
+ "over",
1298
+ "sha256:41b9caaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
1299
+ ]
1300
+ ]
1301
+ },
1302
+ "expect": "ref:folder:acme-tools;over=sha256:41b9caaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
1303
+ }
1304
+ ],
1305
+ "digest": [
1306
+ {
1307
+ "name": "two members in declared order",
1308
+ "members": [
1309
+ "ref:folder:acme-tools#AGENTS.md;state=swh:1:cnt:3404a00f00000000000000000000000000000000",
1310
+ "ref:folder:acme-tools#GOVERNANCE.md;state=swh:1:cnt:48db124700000000000000000000000000000000"
1311
+ ],
1312
+ "expect": "sha256:75433bb5329808aa4064084a54173f0328926eb38ddce742f61cbd4f0ebba71e"
1313
+ },
1314
+ {
1315
+ "name": "same members, other order, different digest",
1316
+ "members": [
1317
+ "ref:folder:acme-tools#GOVERNANCE.md;state=swh:1:cnt:48db124700000000000000000000000000000000",
1318
+ "ref:folder:acme-tools#AGENTS.md;state=swh:1:cnt:3404a00f00000000000000000000000000000000"
1319
+ ],
1320
+ "expect": "sha256:83eee8e9bcff34577a3d305bddebef2685a2e6bb20644e5a2ff29c9a8602a25b"
1321
+ },
1322
+ {
1323
+ "name": "a repeated member is not deduplicated",
1324
+ "members": [
1325
+ "ref:folder:acme-tools#AGENTS.md;state=swh:1:cnt:3404a00f00000000000000000000000000000000",
1326
+ "ref:folder:acme-tools#AGENTS.md;state=swh:1:cnt:3404a00f00000000000000000000000000000000"
1327
+ ],
1328
+ "expect": "sha256:32e30e39f2ea3cf01cc2602487229fb097846ba1a9f1037e738f61757eb3e8b6"
1329
+ },
1330
+ {
1331
+ "name": "a single member",
1332
+ "members": [
1333
+ "ref:folder:acme-tools#AGENTS.md;state=swh:1:cnt:3404a00f00000000000000000000000000000000"
1334
+ ],
1335
+ "expect": "sha256:b89a7d58a6ed779ac53f03eafd0b9cddfac76aad11bfe510688680bfeff45a5f"
1336
+ },
1337
+ {
1338
+ "name": "a member whose content changed changes the digest",
1339
+ "members": [
1340
+ "ref:folder:acme-tools#AGENTS.md;state=swh:1:cnt:3404a01000000000000000000000000000000000",
1341
+ "ref:folder:acme-tools#GOVERNANCE.md;state=swh:1:cnt:48db124700000000000000000000000000000000"
1342
+ ],
1343
+ "expect": "sha256:59ecd4cdaa3ffcaf60103a20f555c4b9fa5755a0705c83a3748f60557c2c3518"
1344
+ },
1345
+ {
1346
+ "name": "empty sequence",
1347
+ "members": [],
1348
+ "expect": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
1349
+ },
1350
+ {
1351
+ "name": "a member carrying the join character is refused",
1352
+ "members": [
1353
+ "ref:folder:acme-tools#AGENTS.md;state=swh:1:cnt:3404a00f00000000000000000000000000000000\nref:folder:acme-tools#GOVERNANCE.md;state=swh:1:cnt:48db124700000000000000000000000000000000"
1354
+ ],
1355
+ "expect": {
1356
+ "error": "member"
1357
+ }
1358
+ }
1359
+ ],
1360
+ "envelope": [
1361
+ {
1362
+ "name": "members recompute to the digest",
1363
+ "requestedId": "ref:folder:acme-tools;over=sha256:75433bb5329808aa4064084a54173f0328926eb38ddce742f61cbd4f0ebba71e",
1364
+ "envelope": {
1365
+ "id": "ref:folder:acme-tools;over=sha256:75433bb5329808aa4064084a54173f0328926eb38ddce742f61cbd4f0ebba71e",
1366
+ "sets": {
1367
+ "over": [
1368
+ "ref:folder:acme-tools#AGENTS.md;state=swh:1:cnt:3404a00f00000000000000000000000000000000",
1369
+ "ref:folder:acme-tools#GOVERNANCE.md;state=swh:1:cnt:48db124700000000000000000000000000000000"
1370
+ ]
1371
+ }
1372
+ },
1373
+ "expect": "admissible"
1374
+ },
1375
+ {
1376
+ "name": "a member changed and the digest did not",
1377
+ "requestedId": "ref:folder:acme-tools;over=sha256:75433bb5329808aa4064084a54173f0328926eb38ddce742f61cbd4f0ebba71e",
1378
+ "envelope": {
1379
+ "id": "ref:folder:acme-tools;over=sha256:75433bb5329808aa4064084a54173f0328926eb38ddce742f61cbd4f0ebba71e",
1380
+ "sets": {
1381
+ "over": [
1382
+ "ref:folder:acme-tools#AGENTS.md;state=swh:1:cnt:3404a01000000000000000000000000000000000",
1383
+ "ref:folder:acme-tools#GOVERNANCE.md;state=swh:1:cnt:48db124700000000000000000000000000000000"
1384
+ ]
1385
+ }
1386
+ },
1387
+ "expect": "refused"
1388
+ },
1389
+ {
1390
+ "name": "members reordered",
1391
+ "requestedId": "ref:folder:acme-tools;over=sha256:75433bb5329808aa4064084a54173f0328926eb38ddce742f61cbd4f0ebba71e",
1392
+ "envelope": {
1393
+ "id": "ref:folder:acme-tools;over=sha256:75433bb5329808aa4064084a54173f0328926eb38ddce742f61cbd4f0ebba71e",
1394
+ "sets": {
1395
+ "over": [
1396
+ "ref:folder:acme-tools#GOVERNANCE.md;state=swh:1:cnt:48db124700000000000000000000000000000000",
1397
+ "ref:folder:acme-tools#AGENTS.md;state=swh:1:cnt:3404a00f00000000000000000000000000000000"
1398
+ ]
1399
+ }
1400
+ },
1401
+ "expect": "refused"
1402
+ },
1403
+ {
1404
+ "name": "sets missing for a digest-carrying qualifier",
1405
+ "requestedId": "ref:folder:acme-tools;over=sha256:75433bb5329808aa4064084a54173f0328926eb38ddce742f61cbd4f0ebba71e",
1406
+ "envelope": {
1407
+ "id": "ref:folder:acme-tools;over=sha256:75433bb5329808aa4064084a54173f0328926eb38ddce742f61cbd4f0ebba71e"
1408
+ },
1409
+ "expect": "refused"
1410
+ },
1411
+ {
1412
+ "name": "served under an identifier that is not its own",
1413
+ "requestedId": "ref:folder:acme-tools#AGENTS.md",
1414
+ "envelope": {
1415
+ "id": "ref:folder:acme-tools#GOVERNANCE.md"
1416
+ },
1417
+ "expect": "refused"
1418
+ },
1419
+ {
1420
+ "name": "no digest, no sets required",
1421
+ "requestedId": "ref:folder:acme-tools;state=swh:1:rev:7e29bb6000000000000000000000000000000000",
1422
+ "envelope": {
1423
+ "id": "ref:folder:acme-tools;state=swh:1:rev:7e29bb6000000000000000000000000000000000",
1424
+ "data": {
1425
+ "anything": true
1426
+ }
1427
+ },
1428
+ "expect": "admissible"
1429
+ },
1430
+ {
1431
+ "name": "two digest qualifiers, both must recompute",
1432
+ "requestedId": "ref:folder:acme-tools;by=sha256:b89a7d58a6ed779ac53f03eafd0b9cddfac76aad11bfe510688680bfeff45a5f;over=sha256:27c2b1674a091639b11e51152103ce945bcd1edbb34ef36e549befcd8c0ce0a3",
1433
+ "envelope": {
1434
+ "id": "ref:folder:acme-tools;by=sha256:b89a7d58a6ed779ac53f03eafd0b9cddfac76aad11bfe510688680bfeff45a5f;over=sha256:27c2b1674a091639b11e51152103ce945bcd1edbb34ef36e549befcd8c0ce0a3",
1435
+ "sets": {
1436
+ "by": [
1437
+ "ref:folder:acme-tools#AGENTS.md;state=swh:1:cnt:3404a00f00000000000000000000000000000000"
1438
+ ],
1439
+ "over": [
1440
+ "ref:folder:acme-tools#AGENTS.md;state=swh:1:cnt:3404a00f00000000000000000000000000000000"
1441
+ ]
1442
+ }
1443
+ },
1444
+ "expect": "refused"
1445
+ },
1446
+ {
1447
+ "name": "a requested identifier that does not parse is refused",
1448
+ "requestedId": "not-a-ref",
1449
+ "envelope": {
1450
+ "id": "not-a-ref"
1451
+ },
1452
+ "expect": "refused"
1453
+ },
1454
+ {
1455
+ "name": "a requested identifier that is malformed is refused",
1456
+ "requestedId": "ref:folder:acme-tools;state=swh:1:rev:7e29bb6",
1457
+ "envelope": {
1458
+ "id": "ref:folder:acme-tools;state=swh:1:rev:7e29bb6"
1459
+ },
1460
+ "expect": "refused"
1461
+ },
1462
+ {
1463
+ "name": "an uncovered requested identifier is admissible",
1464
+ "requestedId": "ref:doi:10.1000/182",
1465
+ "envelope": {
1466
+ "id": "ref:doi:10.1000/182"
1467
+ },
1468
+ "expect": "admissible"
1469
+ },
1470
+ {
1471
+ "name": "alsoKnownAs is opaque to the invariant",
1472
+ "requestedId": "ref:folder:acme-tools;over=sha256:b89a7d58a6ed779ac53f03eafd0b9cddfac76aad11bfe510688680bfeff45a5f",
1473
+ "envelope": {
1474
+ "id": "ref:folder:acme-tools;over=sha256:b89a7d58a6ed779ac53f03eafd0b9cddfac76aad11bfe510688680bfeff45a5f",
1475
+ "sets": {
1476
+ "over": [
1477
+ "ref:folder:acme-tools#AGENTS.md;state=swh:1:cnt:3404a00f00000000000000000000000000000000"
1478
+ ]
1479
+ },
1480
+ "alsoKnownAs": [
1481
+ "ref:pkg:npm/acme-tools@0.6.0"
1482
+ ]
1483
+ },
1484
+ "expect": "admissible"
1485
+ }
1486
+ ]
1487
+ }
1488
+ }