@nuggetslife/vc 0.0.30 → 0.2.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.
Files changed (66) hide show
  1. package/Cargo.toml +11 -0
  2. package/W3C_CONFORMANCE.md +6 -5
  3. package/bench/frame_compare.mjs +203 -0
  4. package/bench/v2_internals.mjs +115 -0
  5. package/bench/vc_ops.mjs +308 -0
  6. package/interop-allowlist.json +3 -0
  7. package/interop-smoke-allowlist.json +3 -0
  8. package/package.json +8 -7
  9. package/scripts/fetch-w3c-tests.sh +8 -0
  10. package/src/jsonld.rs +211 -140
  11. package/src/ld_signatures.rs +11 -2
  12. package/test-fixtures/interop/README.md +46 -0
  13. package/test-fixtures/interop/_contexts/README.md +51 -0
  14. package/test-fixtures/interop/_contexts/bbs-bound-v1.jsonld +92 -0
  15. package/test-fixtures/interop/_contexts/citizenship-v1.jsonld +58 -0
  16. package/test-fixtures/interop/_contexts/credentials-v1.jsonld +316 -0
  17. package/test-fixtures/interop/_contexts/elm-edc-ap.jsonld +809 -0
  18. package/test-fixtures/interop/_contexts/essif-schemas-vc-2020-v1.jsonld +47 -0
  19. package/test-fixtures/interop/_contexts/identity-v2.jsonld +195 -0
  20. package/test-fixtures/interop/_contexts/nuggets-identity-v1.jsonld +175 -0
  21. package/test-fixtures/interop/_contexts/nuggets-kyb-v1.jsonld +333 -0
  22. package/test-fixtures/interop/_contexts/openbadges-v3.jsonld +445 -0
  23. package/test-fixtures/interop/_contexts/security-bbs-v1.jsonld +93 -0
  24. package/test-fixtures/interop/ebsi/diploma-elm/README.md +29 -0
  25. package/test-fixtures/interop/ebsi/diploma-elm/expected.nq +70 -0
  26. package/test-fixtures/interop/ebsi/diploma-elm/frame.jsonld +24 -0
  27. package/test-fixtures/interop/ebsi/diploma-elm/input.jsonld +444 -0
  28. package/test-fixtures/interop/ebsi/diploma-simple/README.md +19 -0
  29. package/test-fixtures/interop/ebsi/diploma-simple/expected.nq +9 -0
  30. package/test-fixtures/interop/ebsi/diploma-simple/frame.jsonld +13 -0
  31. package/test-fixtures/interop/ebsi/diploma-simple/input.jsonld +24 -0
  32. package/test-fixtures/interop/idv2/full-disclosure/README.md +9 -0
  33. package/test-fixtures/interop/idv2/full-disclosure/expected.nq +59 -0
  34. package/test-fixtures/interop/idv2/full-disclosure/frame.jsonld +9 -0
  35. package/test-fixtures/interop/idv2/full-disclosure/input.jsonld +82 -0
  36. package/test-fixtures/interop/nuggets/identity-v1/README.md +17 -0
  37. package/test-fixtures/interop/nuggets/identity-v1/expected.nq +21 -0
  38. package/test-fixtures/interop/nuggets/identity-v1/frame.jsonld +17 -0
  39. package/test-fixtures/interop/nuggets/identity-v1/input.jsonld +31 -0
  40. package/test-fixtures/interop/nuggets/kyb-v1/README.md +15 -0
  41. package/test-fixtures/interop/nuggets/kyb-v1/expected.nq +18 -0
  42. package/test-fixtures/interop/nuggets/kyb-v1/frame.jsonld +24 -0
  43. package/test-fixtures/interop/nuggets/kyb-v1/input.jsonld +60 -0
  44. package/test-fixtures/interop/openbadges-v3/basic-achievement/README.md +17 -0
  45. package/test-fixtures/interop/openbadges-v3/basic-achievement/expected.nq +12 -0
  46. package/test-fixtures/interop/openbadges-v3/basic-achievement/frame.jsonld +17 -0
  47. package/test-fixtures/interop/openbadges-v3/basic-achievement/input.jsonld +25 -0
  48. package/test-fixtures/interop/openbadges-v3/with-allowed-values/README.md +11 -0
  49. package/test-fixtures/interop/openbadges-v3/with-allowed-values/expected.nq +25 -0
  50. package/test-fixtures/interop/openbadges-v3/with-allowed-values/frame.jsonld +22 -0
  51. package/test-fixtures/interop/openbadges-v3/with-allowed-values/input.jsonld +40 -0
  52. package/test-fixtures/interop/vp/single-vc-wrap/README.md +6 -0
  53. package/test-fixtures/interop/vp/single-vc-wrap/expected.nq +7 -0
  54. package/test-fixtures/interop/vp/single-vc-wrap/frame.jsonld +17 -0
  55. package/test-fixtures/interop/vp/single-vc-wrap/input.jsonld +27 -0
  56. package/test-fixtures/interop/w3c-vc-v1/permanent-resident-card/README.md +5 -0
  57. package/test-fixtures/interop/w3c-vc-v1/permanent-resident-card/expected.nq +13 -0
  58. package/test-fixtures/interop/w3c-vc-v1/permanent-resident-card/frame.jsonld +14 -0
  59. package/test-fixtures/interop/w3c-vc-v1/permanent-resident-card/input.jsonld +29 -0
  60. package/test_interop.mjs +184 -0
  61. package/test_interop_smoke.mjs +388 -0
  62. package/test_jsonld_crossverify.mjs +8 -2
  63. package/test_w3c_conformance.mjs +80 -7
  64. package/tools/regen_expected.mjs +108 -0
  65. package/w3c-baseline.json +881 -263
  66. package/w3c-denylist.json +6 -1
@@ -0,0 +1,18 @@
1
+ <did:example:test-company> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://schemas.nuggets.life/kybV1.json#Kyb> .
2
+ <did:example:test-company> <https://schemas.nuggets.life/kybV1.json#result> _:c14n0 .
3
+ <https://issuer.nuggets.life/credentials/kyb-v1/9876> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://schemas.nuggets.life/kybV1.json#KybCredential> .
4
+ <https://issuer.nuggets.life/credentials/kyb-v1/9876> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://www.w3.org/2018/credentials#VerifiableCredential> .
5
+ <https://issuer.nuggets.life/credentials/kyb-v1/9876> <https://www.w3.org/2018/credentials#credentialSubject> <did:example:test-company> .
6
+ <https://issuer.nuggets.life/credentials/kyb-v1/9876> <https://www.w3.org/2018/credentials#issuanceDate> "2024-02-20T00:00:00Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
7
+ <https://issuer.nuggets.life/credentials/kyb-v1/9876> <https://www.w3.org/2018/credentials#issuer> <did:example:nuggets-kyb-issuer> .
8
+ _:c14n0 <https://schemas.nuggets.life/kybV1.json#businessInfo> _:c14n2 .
9
+ _:c14n0 <https://schemas.nuggets.life/kybV1.json#director> _:c14n1 .
10
+ _:c14n0 <https://schemas.nuggets.life/kybV1.json#director> _:c14n3 .
11
+ _:c14n1 <http://schema.org/familyName> "JONES" .
12
+ _:c14n1 <http://schema.org/givenName> "BOB" .
13
+ _:c14n1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://schemas.nuggets.life/kybV1.json#director> .
14
+ _:c14n2 <http://schema.org/identifier> "12345678" .
15
+ _:c14n2 <http://schema.org/name> "Test Limited" .
16
+ _:c14n3 <http://schema.org/familyName> "SMITH" .
17
+ _:c14n3 <http://schema.org/givenName> "ALICE" .
18
+ _:c14n3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://schemas.nuggets.life/kybV1.json#director> .
@@ -0,0 +1,24 @@
1
+ {
2
+ "@context": [
3
+ "https://www.w3.org/2018/credentials/v1",
4
+ "https://schemas.nuggets.life/kybV1.json"
5
+ ],
6
+ "type": ["VerifiableCredential", "KybCredential"],
7
+ "credentialSubject": {
8
+ "@explicit": true,
9
+ "type": "Kyb",
10
+ "result": {
11
+ "@explicit": true,
12
+ "businessInfo": {
13
+ "@explicit": true,
14
+ "businessName": {},
15
+ "dunsNumber": {}
16
+ },
17
+ "director": [{
18
+ "@explicit": true,
19
+ "givenName": {},
20
+ "familyName": {}
21
+ }]
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,60 @@
1
+ {
2
+ "@context": [
3
+ "https://www.w3.org/2018/credentials/v1",
4
+ "https://schemas.nuggets.life/kybV1.json"
5
+ ],
6
+ "id": "https://issuer.nuggets.life/credentials/kyb-v1/9876",
7
+ "type": ["VerifiableCredential", "KybCredential"],
8
+ "issuer": "did:example:nuggets-kyb-issuer",
9
+ "issuanceDate": "2024-02-20T00:00:00Z",
10
+ "credentialSubject": {
11
+ "id": "did:example:test-company",
12
+ "type": "Kyb",
13
+ "result": {
14
+ "businessAddress": {
15
+ "country": "GB",
16
+ "address1": "1 Corporate Way",
17
+ "city": "London",
18
+ "region": "Greater London",
19
+ "postalCode": "EC1A 1AA"
20
+ },
21
+ "businessInfo": {
22
+ "businessName": "Test Limited",
23
+ "legalStatus": "Private Limited Company",
24
+ "dunsNumber": "12345678",
25
+ "domain": "test-limited.example"
26
+ },
27
+ "director": [
28
+ {
29
+ "type": "https://schemas.nuggets.life/kybV1.json#director",
30
+ "givenName": "ALICE",
31
+ "familyName": "SMITH",
32
+ "nationality": "GB"
33
+ },
34
+ {
35
+ "type": "https://schemas.nuggets.life/kybV1.json#director",
36
+ "givenName": "BOB",
37
+ "familyName": "JONES",
38
+ "nationality": "GB"
39
+ }
40
+ ],
41
+ "evidence": {
42
+ "duns": "DUN-12345678",
43
+ "registeredAddress": {
44
+ "addressCountry": {
45
+ "name": "United Kingdom",
46
+ "isoAlpha2Code": "GB"
47
+ },
48
+ "addressLocality": {
49
+ "name": "London"
50
+ },
51
+ "postalCode": "EC1A 1AA",
52
+ "streetAddress": {
53
+ "line1": "1 Corporate Way",
54
+ "line2": ""
55
+ }
56
+ }
57
+ }
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,17 @@
1
+ # OpenBadges v3 — basic-achievement
2
+
3
+ Baseline OB v3 `OpenBadgeCredential` (a.k.a. `AchievementCredential`) wrapping a single `Achievement`. Adapted from the canonical IMS Global OB v3 spec example with synthetic data.
4
+
5
+ - Upstream spec: `https://www.imsglobal.org/spec/ob/v3p0/`
6
+ - Upstream context: `https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json`
7
+ - Retrieval date: 2026-05-06
8
+
9
+ ## What this fixture exercises
10
+
11
+ - OB v3 type-scoped contexts (`OpenBadgeCredential`, `AchievementSubject`, `Achievement`, `Profile`) layered on top of the W3C VC v1 base context.
12
+ - A single-`Achievement` shape with `name` + `description` revealed via a BBS+-style frame (other achievement fields, the issuer's `Profile`, and the credential id stay hidden).
13
+ - `criteria` modelled as a plain IRI reference (the upstream context types `criteria` as `@type: @id`).
14
+
15
+ ## Expected status
16
+
17
+ **Pass.** This is the baseline; if it goes red, the OB v3 context bundle or the type-scoped layering itself is broken before we can meaningfully test `@list`/other features.
@@ -0,0 +1,12 @@
1
+ <did:example:ebfeb1f712ebc6f1c276e12ec21> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://purl.imsglobal.org/spec/vc/ob/vocab.html#AchievementSubject> .
2
+ <did:example:ebfeb1f712ebc6f1c276e12ec21> <https://purl.imsglobal.org/spec/vc/ob/vocab.html#achievement> <https://example.edu/achievements/sample-achievement> .
3
+ <http://example.edu/credentials/3732> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://purl.imsglobal.org/spec/vc/ob/vocab.html#OpenBadgeCredential> .
4
+ <http://example.edu/credentials/3732> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://www.w3.org/2018/credentials#VerifiableCredential> .
5
+ <http://example.edu/credentials/3732> <https://www.w3.org/2018/credentials#credentialSubject> <did:example:ebfeb1f712ebc6f1c276e12ec21> .
6
+ <http://example.edu/credentials/3732> <https://www.w3.org/2018/credentials#issuanceDate> "2020-04-01T00:00:00Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
7
+ <http://example.edu/credentials/3732> <https://www.w3.org/2018/credentials#issuer> <https://example.edu/issuers/14> .
8
+ <https://example.edu/achievements/sample-achievement> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://purl.imsglobal.org/spec/vc/ob/vocab.html#Achievement> .
9
+ <https://example.edu/achievements/sample-achievement> <https://schema.org/description> "A simple achievement for interop testing." .
10
+ <https://example.edu/achievements/sample-achievement> <https://schema.org/name> "Sample Achievement" .
11
+ <https://example.edu/issuers/14> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://purl.imsglobal.org/spec/vc/ob/vocab.html#Profile> .
12
+ <https://example.edu/issuers/14> <https://schema.org/name> "Example University" .
@@ -0,0 +1,17 @@
1
+ {
2
+ "@context": [
3
+ "https://www.w3.org/2018/credentials/v1",
4
+ "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json"
5
+ ],
6
+ "type": ["VerifiableCredential", "OpenBadgeCredential"],
7
+ "credentialSubject": {
8
+ "@explicit": true,
9
+ "type": "AchievementSubject",
10
+ "achievement": {
11
+ "@explicit": true,
12
+ "type": "Achievement",
13
+ "name": {},
14
+ "description": {}
15
+ }
16
+ }
17
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "@context": [
3
+ "https://www.w3.org/2018/credentials/v1",
4
+ "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json"
5
+ ],
6
+ "id": "http://example.edu/credentials/3732",
7
+ "type": ["VerifiableCredential", "OpenBadgeCredential"],
8
+ "issuer": {
9
+ "id": "https://example.edu/issuers/14",
10
+ "type": "Profile",
11
+ "name": "Example University"
12
+ },
13
+ "issuanceDate": "2020-04-01T00:00:00Z",
14
+ "credentialSubject": {
15
+ "id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
16
+ "type": "AchievementSubject",
17
+ "achievement": {
18
+ "id": "https://example.edu/achievements/sample-achievement",
19
+ "type": "Achievement",
20
+ "name": "Sample Achievement",
21
+ "description": "A simple achievement for interop testing.",
22
+ "criteria": "https://example.edu/criteria/pass-the-interop-test"
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,11 @@
1
+ # OpenBadges v3 — with-allowed-values
2
+
3
+ OpenBadges v3 `OpenBadgeCredential` containing a `ResultDescription` whose `allowedValue: ["F", "D", "C", "B", "A"]` is the only `@list`-typed field in the OB v3 3.0.3 context. The reveal frame discloses the achievement name + the result description's `allowedValue` list, so framing must preserve the list ordering and identity.
4
+
5
+ This fixture exercises `@list` semantics specifically. If V2 native diverges from jsonld.js here, the gap is in `@list` handling — track in `interop-allowlist.json` with a Phase C issue.
6
+
7
+ ## Provenance
8
+
9
+ - `input.jsonld`: synthetic, hand-authored against OB v3 3.0.3 schema.
10
+ - `frame.jsonld`: BBS+-style selective disclosure.
11
+ - `expected.nq`: regenerate with `node tools/regen_expected.mjs test-fixtures/interop/openbadges-v3/with-allowed-values` from `vc/js/`. Do not hand-edit.
@@ -0,0 +1,25 @@
1
+ <did:example:s2> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://purl.imsglobal.org/spec/vc/ob/vocab.html#AchievementSubject> .
2
+ <did:example:s2> <https://purl.imsglobal.org/spec/vc/ob/vocab.html#achievement> <https://example.edu/achievements/grading-test> .
3
+ <http://example.edu/credentials/4823> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://purl.imsglobal.org/spec/vc/ob/vocab.html#OpenBadgeCredential> .
4
+ <http://example.edu/credentials/4823> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://www.w3.org/2018/credentials#VerifiableCredential> .
5
+ <http://example.edu/credentials/4823> <https://www.w3.org/2018/credentials#credentialSubject> <did:example:s2> .
6
+ <http://example.edu/credentials/4823> <https://www.w3.org/2018/credentials#issuanceDate> "2020-04-01T00:00:00Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
7
+ <http://example.edu/credentials/4823> <https://www.w3.org/2018/credentials#issuer> <https://example.edu/issuers/14> .
8
+ <https://example.edu/achievements/grading-test> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://purl.imsglobal.org/spec/vc/ob/vocab.html#Achievement> .
9
+ <https://example.edu/achievements/grading-test> <https://purl.imsglobal.org/spec/vc/ob/vocab.html#resultDescription> <https://example.edu/result-descriptions/letter-grade> .
10
+ <https://example.edu/achievements/grading-test> <https://schema.org/name> "Letter-Graded Achievement" .
11
+ <https://example.edu/issuers/14> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://purl.imsglobal.org/spec/vc/ob/vocab.html#Profile> .
12
+ <https://example.edu/issuers/14> <https://schema.org/name> "Example University" .
13
+ <https://example.edu/result-descriptions/letter-grade> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://purl.imsglobal.org/spec/vc/ob/vocab.html#ResultDescription> .
14
+ <https://example.edu/result-descriptions/letter-grade> <https://purl.imsglobal.org/spec/vc/ob/vocab.html#allowedValue> _:c14n2 .
15
+ <https://example.edu/result-descriptions/letter-grade> <https://schema.org/name> "Letter Grade" .
16
+ _:c14n0 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "B" .
17
+ _:c14n0 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:c14n4 .
18
+ _:c14n1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "D" .
19
+ _:c14n1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:c14n3 .
20
+ _:c14n2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "F" .
21
+ _:c14n2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:c14n1 .
22
+ _:c14n3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "C" .
23
+ _:c14n3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:c14n0 .
24
+ _:c14n4 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "A" .
25
+ _:c14n4 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> .
@@ -0,0 +1,22 @@
1
+ {
2
+ "@context": [
3
+ "https://www.w3.org/2018/credentials/v1",
4
+ "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json"
5
+ ],
6
+ "type": ["VerifiableCredential", "OpenBadgeCredential"],
7
+ "credentialSubject": {
8
+ "@explicit": true,
9
+ "type": "AchievementSubject",
10
+ "achievement": {
11
+ "@explicit": true,
12
+ "type": "Achievement",
13
+ "name": {},
14
+ "resultDescription": {
15
+ "@explicit": true,
16
+ "type": "ResultDescription",
17
+ "name": {},
18
+ "allowedValue": {}
19
+ }
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,40 @@
1
+ {
2
+ "@context": [
3
+ "https://www.w3.org/2018/credentials/v1",
4
+ "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json"
5
+ ],
6
+ "id": "http://example.edu/credentials/4823",
7
+ "type": ["VerifiableCredential", "OpenBadgeCredential"],
8
+ "issuer": {
9
+ "id": "https://example.edu/issuers/14",
10
+ "type": "Profile",
11
+ "name": "Example University"
12
+ },
13
+ "issuanceDate": "2020-04-01T00:00:00Z",
14
+ "credentialSubject": {
15
+ "id": "did:example:s2",
16
+ "type": "AchievementSubject",
17
+ "achievement": {
18
+ "id": "https://example.edu/achievements/grading-test",
19
+ "type": "Achievement",
20
+ "name": "Letter-Graded Achievement",
21
+ "criteria": { "narrative": "Earn at least a passing letter grade." },
22
+ "resultDescription": [
23
+ {
24
+ "id": "https://example.edu/result-descriptions/letter-grade",
25
+ "type": "ResultDescription",
26
+ "name": "Letter Grade",
27
+ "resultType": "LetterGrade",
28
+ "allowedValue": ["F", "D", "C", "B", "A"]
29
+ }
30
+ ]
31
+ },
32
+ "result": [
33
+ {
34
+ "type": "Result",
35
+ "resultDescription": "https://example.edu/result-descriptions/letter-grade",
36
+ "value": "A"
37
+ }
38
+ ]
39
+ }
40
+ }
@@ -0,0 +1,6 @@
1
+ # VerifiablePresentation — single VC wrap
2
+
3
+ A VP wrapping one PermanentResidentCard. Tests that frame_native handles the canonical VP shape (`type: VerifiablePresentation` with a `verifiableCredential` array). This is the highest-risk Phase A fixture — VPs commonly trip the nested-graph regressions that V2 native doesn't yet handle.
4
+
5
+ If this fixture goes green: VP-shaped credentials work cross-stack out of the box.
6
+ If it fails: it's tracked in `interop-allowlist.json` and gets fixed in Phase C.
@@ -0,0 +1,7 @@
1
+ <did:example:b34ca6cd37bbf23> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> _:c14n1 .
2
+ <did:example:b34ca6cd37bbf23> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://w3id.org/citizenship#PermanentResident> _:c14n1 .
3
+ <https://issuer.oidp.uscis.gov/credentials/83627465> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://w3id.org/citizenship#PermanentResidentCard> _:c14n1 .
4
+ <https://issuer.oidp.uscis.gov/credentials/83627465> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://www.w3.org/2018/credentials#VerifiableCredential> _:c14n1 .
5
+ <https://issuer.oidp.uscis.gov/credentials/83627465> <https://www.w3.org/2018/credentials#credentialSubject> <did:example:b34ca6cd37bbf23> _:c14n1 .
6
+ _:c14n0 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://www.w3.org/2018/credentials#VerifiablePresentation> .
7
+ _:c14n0 <https://www.w3.org/2018/credentials#verifiableCredential> _:c14n1 .
@@ -0,0 +1,17 @@
1
+ {
2
+ "@context": [
3
+ "https://www.w3.org/2018/credentials/v1",
4
+ "https://w3id.org/citizenship/v1",
5
+ "https://w3id.org/security/bbs/v1"
6
+ ],
7
+ "type": "VerifiablePresentation",
8
+ "verifiableCredential": {
9
+ "@explicit": true,
10
+ "type": ["VerifiableCredential", "PermanentResidentCard"],
11
+ "credentialSubject": {
12
+ "@explicit": true,
13
+ "givenName": {},
14
+ "familyName": {}
15
+ }
16
+ }
17
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "@context": [
3
+ "https://www.w3.org/2018/credentials/v1",
4
+ "https://w3id.org/citizenship/v1",
5
+ "https://w3id.org/security/bbs/v1"
6
+ ],
7
+ "type": "VerifiablePresentation",
8
+ "verifiableCredential": [
9
+ {
10
+ "@context": [
11
+ "https://www.w3.org/2018/credentials/v1",
12
+ "https://w3id.org/citizenship/v1",
13
+ "https://w3id.org/security/bbs/v1"
14
+ ],
15
+ "id": "https://issuer.oidp.uscis.gov/credentials/83627465",
16
+ "type": ["VerifiableCredential", "PermanentResidentCard"],
17
+ "issuer": "did:example:489398593",
18
+ "issuanceDate": "2019-12-03T12:19:52Z",
19
+ "credentialSubject": {
20
+ "id": "did:example:b34ca6cd37bbf23",
21
+ "type": ["PermanentResident", "Person"],
22
+ "givenName": "JOHN",
23
+ "familyName": "SMITH"
24
+ }
25
+ }
26
+ ]
27
+ }
@@ -0,0 +1,5 @@
1
+ # W3C VC v1.1 — PermanentResidentCard
2
+
3
+ The reference fixture used end-to-end by `vc-rust-test` and the desktop / iOS / Android Detox suites. Source: W3C VC v1.1 spec example, with the W3C Citizenship + BBS+ contexts attached. The reveal frame discloses `givenName` + `familyName` only.
4
+
5
+ This fixture is the **green baseline** — if the interop harness ever fails on this fixture, something has regressed at a fundamental level.
@@ -0,0 +1,13 @@
1
+ <did:example:b34ca6cd37bbf23> <http://schema.org/familyName> "SMITH" .
2
+ <did:example:b34ca6cd37bbf23> <http://schema.org/givenName> "JOHN" .
3
+ <did:example:b34ca6cd37bbf23> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
4
+ <did:example:b34ca6cd37bbf23> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://w3id.org/citizenship#PermanentResident> .
5
+ <https://issuer.oidp.uscis.gov/credentials/83627465> <http://schema.org/description> "Government of Example Permanent Resident Card." .
6
+ <https://issuer.oidp.uscis.gov/credentials/83627465> <http://schema.org/identifier> "83627465" .
7
+ <https://issuer.oidp.uscis.gov/credentials/83627465> <http://schema.org/name> "Permanent Resident Card" .
8
+ <https://issuer.oidp.uscis.gov/credentials/83627465> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://w3id.org/citizenship#PermanentResidentCard> .
9
+ <https://issuer.oidp.uscis.gov/credentials/83627465> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://www.w3.org/2018/credentials#VerifiableCredential> .
10
+ <https://issuer.oidp.uscis.gov/credentials/83627465> <https://www.w3.org/2018/credentials#credentialSubject> <did:example:b34ca6cd37bbf23> .
11
+ <https://issuer.oidp.uscis.gov/credentials/83627465> <https://www.w3.org/2018/credentials#expirationDate> "2029-12-03T12:19:52Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
12
+ <https://issuer.oidp.uscis.gov/credentials/83627465> <https://www.w3.org/2018/credentials#issuanceDate> "2019-12-03T12:19:52Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
13
+ <https://issuer.oidp.uscis.gov/credentials/83627465> <https://www.w3.org/2018/credentials#issuer> <did:example:489398593> .
@@ -0,0 +1,14 @@
1
+ {
2
+ "@context": [
3
+ "https://www.w3.org/2018/credentials/v1",
4
+ "https://w3id.org/citizenship/v1",
5
+ "https://w3id.org/security/bbs/v1"
6
+ ],
7
+ "type": ["VerifiableCredential", "PermanentResidentCard"],
8
+ "credentialSubject": {
9
+ "@explicit": true,
10
+ "type": ["PermanentResident", "Person"],
11
+ "givenName": {},
12
+ "familyName": {}
13
+ }
14
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "@context": [
3
+ "https://www.w3.org/2018/credentials/v1",
4
+ "https://w3id.org/citizenship/v1",
5
+ "https://w3id.org/security/bbs/v1"
6
+ ],
7
+ "id": "https://issuer.oidp.uscis.gov/credentials/83627465",
8
+ "type": ["VerifiableCredential", "PermanentResidentCard"],
9
+ "issuer": "did:example:489398593",
10
+ "identifier": "83627465",
11
+ "name": "Permanent Resident Card",
12
+ "description": "Government of Example Permanent Resident Card.",
13
+ "issuanceDate": "2019-12-03T12:19:52Z",
14
+ "expirationDate": "2029-12-03T12:19:52Z",
15
+ "credentialSubject": {
16
+ "id": "did:example:b34ca6cd37bbf23",
17
+ "type": ["PermanentResident", "Person"],
18
+ "givenName": "JOHN",
19
+ "familyName": "SMITH",
20
+ "gender": "Male",
21
+ "image": "data:image/png;base64,iVBORw0KGgokJggg==",
22
+ "residentSince": "2015-01-01",
23
+ "lprCategory": "C09",
24
+ "lprNumber": "999-999-999",
25
+ "commuterClassification": "C1",
26
+ "birthCountry": "Bahamas",
27
+ "birthDate": "1958-07-17"
28
+ }
29
+ }
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/env node
2
+ // Cross-stack BBS+ interop test harness.
3
+ //
4
+ // For each fixture in `test-fixtures/interop/<family>/<name>/`:
5
+ // 1. V2 native: JsonLd.frame(input, frame) → canonize → string
6
+ // 2. Compare byte-equal against the fixture's `expected.nq` (committed
7
+ // ground truth, generated by `tools/regen_expected.mjs` from jsonld.js)
8
+ // 3. Mismatch → REGRESSION, exit 1 (unless `expect_fail` allowlist matches —
9
+ // added in Task 5).
10
+ //
11
+ // Usage:
12
+ // node test_interop.mjs --check (CI default)
13
+ // node test_interop.mjs --report (local dev; prints diff per failure)
14
+
15
+ import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs';
16
+ import { resolve, dirname, join } from 'node:path';
17
+ import { fileURLToPath } from 'node:url';
18
+ import { JsonLd } from './index.js';
19
+
20
+ const __dirname = dirname(fileURLToPath(import.meta.url));
21
+ const fixturesRoot = resolve(__dirname, 'test-fixtures', 'interop');
22
+ const args = process.argv.slice(2);
23
+ const checkMode = args.includes('--check');
24
+ const reportMode = args.includes('--report');
25
+ if (!checkMode && !reportMode) {
26
+ console.error('Usage: test_interop.mjs [--check | --report]');
27
+ process.exit(2);
28
+ }
29
+
30
+ const allowlistPath = resolve(__dirname, 'interop-allowlist.json');
31
+ const allowlist = existsSync(allowlistPath)
32
+ ? JSON.parse(readFileSync(allowlistPath, 'utf8'))
33
+ : { expect_fail: [] };
34
+ const expectFail = new Set((allowlist.expect_fail || []).map((e) => e.fixture));
35
+
36
+ // Same offline loader shape as regen_expected.mjs. Loads bundled contexts
37
+ // from `_contexts/` and surfaces them as additional contexts to JsonLd.
38
+ function loadOfflineContexts() {
39
+ const dir = join(fixturesRoot, '_contexts');
40
+ const out = {};
41
+ if (!existsSync(dir)) return out;
42
+ for (const f of readdirSync(dir)) {
43
+ if (!f.endsWith('.jsonld') && !f.endsWith('.json')) continue;
44
+ const doc = JSON.parse(readFileSync(join(dir, f), 'utf8'));
45
+ if (!doc.__url__) continue;
46
+ const url = doc.__url__;
47
+ delete doc.__url__;
48
+ out[url] = doc;
49
+ }
50
+ return out;
51
+ }
52
+
53
+ function discoverFixtures(root) {
54
+ const out = [];
55
+ if (!existsSync(root)) return out;
56
+ for (const family of readdirSync(root).sort()) {
57
+ if (family.startsWith('_')) continue;
58
+ const familyDir = join(root, family);
59
+ if (!statSync(familyDir).isDirectory()) continue;
60
+ for (const name of readdirSync(familyDir).sort()) {
61
+ const fixtureDir = join(familyDir, name);
62
+ if (!statSync(fixtureDir).isDirectory()) continue;
63
+ out.push({ id: `${family}/${name}`, dir: fixtureDir });
64
+ }
65
+ }
66
+ return out;
67
+ }
68
+
69
+ function unifiedDiff(expected, actual, label) {
70
+ const e = expected.split('\n');
71
+ const a = actual.split('\n');
72
+ const max = Math.max(e.length, a.length);
73
+ const lines = [`--- expected.nq`, `+++ actual (V2 native, ${label})`];
74
+ for (let i = 0; i < max; i++) {
75
+ if (e[i] === a[i]) continue;
76
+ if (e[i] !== undefined) lines.push(`-${e[i]}`);
77
+ if (a[i] !== undefined) lines.push(`+${a[i]}`);
78
+ }
79
+ return lines.join('\n');
80
+ }
81
+
82
+ async function runFixture(fixture, contexts) {
83
+ const inputPath = join(fixture.dir, 'input.jsonld');
84
+ const framePath = join(fixture.dir, 'frame.jsonld');
85
+ const expectedPath = join(fixture.dir, 'expected.nq');
86
+ if (!existsSync(inputPath) || !existsSync(framePath) || !existsSync(expectedPath)) {
87
+ return { id: fixture.id, status: 'skip', reason: 'missing fixture file' };
88
+ }
89
+ const input = JSON.parse(readFileSync(inputPath, 'utf8'));
90
+ const frame = JSON.parse(readFileSync(framePath, 'utf8'));
91
+ const expected = readFileSync(expectedPath, 'utf8');
92
+
93
+ const proc = new JsonLd({ contexts });
94
+ let actual;
95
+ try {
96
+ const framed = await proc.frame(input, frame);
97
+ actual = await proc.canonize(framed, {
98
+ algorithm: 'URDNA2015',
99
+ format: 'application/n-quads',
100
+ });
101
+ if (typeof actual !== 'string') actual = String(actual);
102
+ if (!actual.endsWith('\n')) actual += '\n';
103
+ } catch (err) {
104
+ return { id: fixture.id, status: 'error', error: err.message || String(err) };
105
+ }
106
+ return {
107
+ id: fixture.id,
108
+ status: actual === expected ? 'pass' : 'fail',
109
+ expected,
110
+ actual,
111
+ };
112
+ }
113
+
114
+ async function main() {
115
+ const contexts = loadOfflineContexts();
116
+ const fixtures = discoverFixtures(fixturesRoot);
117
+ if (fixtures.length === 0) {
118
+ console.error(`no fixtures found under ${fixturesRoot}`);
119
+ process.exit(2);
120
+ }
121
+ const results = [];
122
+ for (const fx of fixtures) {
123
+ process.stderr.write(` running ${fx.id}…\n`);
124
+ results.push(await runFixture(fx, contexts));
125
+ }
126
+ const pass = results.filter((r) => r.status === 'pass').length;
127
+ const fail = results.filter((r) => r.status === 'fail').length;
128
+ const error = results.filter((r) => r.status === 'error').length;
129
+ const skip = results.filter((r) => r.status === 'skip').length;
130
+
131
+ console.log(`\n========== INTEROP CONFORMANCE ==========`);
132
+ console.log(`pass: ${pass}`);
133
+ console.log(`fail: ${fail}`);
134
+ console.log(`error: ${error}`);
135
+ console.log(`skip: ${skip}`);
136
+
137
+ const regressions = [];
138
+ const improvements = [];
139
+ const expectedFails = [];
140
+ const incomplete = [];
141
+ for (const r of results) {
142
+ if (r.status === 'pass') {
143
+ if (expectFail.has(r.id)) improvements.push(r);
144
+ } else if (r.status === 'fail' || r.status === 'error') {
145
+ if (expectFail.has(r.id)) expectedFails.push(r);
146
+ else regressions.push(r);
147
+ } else if (r.status === 'skip') {
148
+ incomplete.push(r);
149
+ }
150
+ }
151
+
152
+ if (reportMode) {
153
+ for (const r of [...regressions, ...expectedFails]) {
154
+ console.log(`\n--- ${r.id} (${r.status}${r.error ? ': ' + r.error : ''}) ---`);
155
+ if (r.expected !== undefined && r.actual !== undefined) {
156
+ const diff = unifiedDiff(r.expected, r.actual, r.id).split('\n').slice(0, 42).join('\n');
157
+ console.log(diff);
158
+ }
159
+ }
160
+ console.log(`\n[--report] ${regressions.length} divergence(s); ${expectedFails.length} expected-fail; ${improvements.length} improvement(s); ${incomplete.length} incomplete.`);
161
+ process.exit(0);
162
+ }
163
+
164
+ // --check mode
165
+ if (improvements.length) {
166
+ console.log(`\n${improvements.length} fixture(s) newly passing — please ratchet interop-allowlist.json:`);
167
+ for (const r of improvements) console.log(` + ${r.id}`);
168
+ process.exit(1);
169
+ }
170
+ if (regressions.length) {
171
+ console.log(`\n${regressions.length} REGRESSION(S):`);
172
+ for (const r of regressions) console.log(` - ${r.id} (${r.status}${r.error ? ': ' + r.error : ''})`);
173
+ process.exit(1);
174
+ }
175
+ if (incomplete.length) {
176
+ console.log(`\n${incomplete.length} incomplete fixture(s) — missing input.jsonld / frame.jsonld / expected.nq:`);
177
+ for (const r of incomplete) console.log(` ! ${r.id}: ${r.reason || 'missing fixture file'}`);
178
+ console.log(`\nRun \`node tools/regen_expected.mjs <fixture-dir>\` to generate missing expected.nq.`);
179
+ process.exit(1);
180
+ }
181
+ console.log(`\nAll non-expect-fail fixtures match expected.nq.${expectedFails.length ? ' (' + expectedFails.length + ' tracked expect-fail.)' : ''}`);
182
+ }
183
+
184
+ main().catch((e) => { console.error(e); process.exit(1); });