@learncard/credential-library 2.0.2 → 2.0.4
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/README.md +2 -1
- package/dist/credential-library.cjs.development.js +139 -20
- package/dist/credential-library.cjs.development.js.map +4 -4
- package/dist/credential-library.cjs.production.min.js +1 -1
- package/dist/credential-library.cjs.production.min.js.map +4 -4
- package/dist/credential-library.esm.js +127 -8
- package/dist/credential-library.esm.js.map +4 -4
- package/dist/fixtures/clr/provisional-transcript.d.ts +7 -1
- package/dist/fixtures/clr/provisional-transcript.d.ts.map +1 -1
- package/dist/fixtures/index.d.ts +2 -1
- package/dist/fixtures/index.d.ts.map +1 -1
- package/dist/fixtures/obv3/rubric-aligned-badge.d.ts +3 -0
- package/dist/fixtures/obv3/rubric-aligned-badge.d.ts.map +1 -0
- package/dist/prepare.d.ts +0 -18
- package/dist/prepare.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/__tests__/registry.test.ts +43 -0
- package/src/fixtures/clr/provisional-transcript.ts +16 -3
- package/src/fixtures/index.ts +3 -0
- package/src/fixtures/obv3/rubric-aligned-badge.ts +105 -0
- package/src/prepare.ts +15 -0
|
@@ -13,8 +13,14 @@ import type { CredentialFixture } from '../../types';
|
|
|
13
13
|
* the fixture carries an inline context fragment — DIDKit's data-loss detection
|
|
14
14
|
* otherwise refuses to sign.
|
|
15
15
|
*/
|
|
16
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* Inline JSON-LD context fragment for terms the published contexts leave undefined.
|
|
18
|
+
* `partial` is in the CLR v2 spec (https://www.imsglobal.org/spec/clr/v2p0/#clrcredential)
|
|
19
|
+
* but the live purl.imsglobal.org CLR context only maps five type names, so without this
|
|
20
|
+
* the signer's data-loss check drops it.
|
|
21
|
+
*/
|
|
17
22
|
export declare const REFRESH_SERVICE_INLINE_CONTEXT: {
|
|
23
|
+
readonly partial: "https://purl.imsglobal.org/spec/clr/v2p0#partial";
|
|
18
24
|
readonly LearnCardCredentialRefresh2026: "https://learncard.com/refresh#LearnCardCredentialRefresh2026";
|
|
19
25
|
readonly authorization: {
|
|
20
26
|
readonly '@id': "https://purl.imsglobal.org/spec/ob/v3p0#authorization";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provisional-transcript.d.ts","sourceRoot":"","sources":["../../../src/fixtures/clr/provisional-transcript.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkC,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD;;;;;;;;;;;;GAYG;AAEH
|
|
1
|
+
{"version":3,"file":"provisional-transcript.d.ts","sourceRoot":"","sources":["../../../src/fixtures/clr/provisional-transcript.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkC,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD;;;;;;;;;;;;GAYG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B;;;;;;;;;CAUjC,CAAC;AAaX,eAAO,MAAM,wBAAwB,EAAE,iBAuGtC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,GACpC,aAAa,UAAU,EACvB,UAAS;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,KACrC,UA0CF,CAAC"}
|
package/dist/fixtures/index.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { obv3CourseCompletion } from './obv3/course-completion';
|
|
|
17
17
|
import { obv3StandaloneFullCourse } from './obv3/standalone-full-course';
|
|
18
18
|
import { obv3K12Diploma } from './obv3/k12-diploma';
|
|
19
19
|
import { obv3EndorsementCredential } from './obv3/endorsement';
|
|
20
|
+
import { obv3RubricAlignedBadge } from './obv3/rubric-aligned-badge';
|
|
20
21
|
import { clrMinimal } from './clr/minimal';
|
|
21
22
|
import { clrMultiAchievement } from './clr/multi-achievement';
|
|
22
23
|
import { clrUniversityTranscript } from './clr/university-transcript';
|
|
@@ -42,5 +43,5 @@ import { clrProvisionalTranscript, buildFinalTranscriptVariant, REFRESH_SERVICE_
|
|
|
42
43
|
import { clrDemoIsdDiplomaAssessments } from './clr/demo-isd-diploma-assessments';
|
|
43
44
|
import { sdJwtVcCourseCompletion } from './sd-jwt-vc/course-completion';
|
|
44
45
|
export declare const ALL_FIXTURES: LibraryFixture[];
|
|
45
|
-
export { vcV1Basic, vcV1WithStatus, vcV2Basic, vcV2WithEvidence, vcV2MultipleSubjects, vcV2RenderMethodExample, obv3MinimalBadge, obv3FullBadge, obv3WithAlignment, obv3WithEndorsement, obv3PlugfestJff2, clrMinimal, clrMultiAchievement, boostBasic, boostId, boostWithSkills, invalidMissingContext, invalidEmptyType, invalidMissingIssuer, clrNdStudentTranscript, obv3OneedtechFull, obv3ProfessionalCert, obv3MicroCredential, obv3CourseCompletion, obv3StandaloneFullCourse, obv3K12Diploma, obv3EndorsementCredential, clrUniversityTranscript, boostCommunityAward, boostDelegate, vcV1AlumniCredential, vcV2EmploymentCredential, vcV2EducationDegree, vcV2DigitalId, vcV2MembershipCredential, vcV2LicenseCredential, clrGreatPlainsFull, clrWestbridgeFull, clrCompetencyAligned, clrProvisionalTranscript, buildFinalTranscriptVariant, REFRESH_SERVICE_INLINE_CONTEXT, clrDemoIsdDiplomaAssessments, sdJwtVcCourseCompletion, };
|
|
46
|
+
export { vcV1Basic, vcV1WithStatus, vcV2Basic, vcV2WithEvidence, vcV2MultipleSubjects, vcV2RenderMethodExample, obv3MinimalBadge, obv3FullBadge, obv3WithAlignment, obv3WithEndorsement, obv3PlugfestJff2, clrMinimal, clrMultiAchievement, boostBasic, boostId, boostWithSkills, invalidMissingContext, invalidEmptyType, invalidMissingIssuer, clrNdStudentTranscript, obv3OneedtechFull, obv3ProfessionalCert, obv3MicroCredential, obv3CourseCompletion, obv3StandaloneFullCourse, obv3K12Diploma, obv3EndorsementCredential, obv3RubricAlignedBadge, clrUniversityTranscript, boostCommunityAward, boostDelegate, vcV1AlumniCredential, vcV2EmploymentCredential, vcV2EducationDegree, vcV2DigitalId, vcV2MembershipCredential, vcV2LicenseCredential, clrGreatPlainsFull, clrWestbridgeFull, clrCompetencyAligned, clrProvisionalTranscript, buildFinalTranscriptVariant, REFRESH_SERVICE_INLINE_CONTEXT, clrDemoIsdDiplomaAssessments, sdJwtVcCourseCompletion, };
|
|
46
47
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fixtures/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAG/C,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAGrD,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAC;AAGhF,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fixtures/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rubric-aligned-badge.d.ts","sourceRoot":"","sources":["../../../src/fixtures/obv3/rubric-aligned-badge.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAKrD,eAAO,MAAM,sBAAsB,EAAE,iBAiGpC,CAAC"}
|
package/dist/prepare.d.ts
CHANGED
|
@@ -12,24 +12,6 @@ export interface PrepareOptions {
|
|
|
12
12
|
/** Generate fresh UUIDs for id fields (defaults to true) */
|
|
13
13
|
freshIds?: boolean;
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
16
|
-
* Takes a fixture's credential and returns a fresh unsigned VC with real DIDs,
|
|
17
|
-
* current timestamps, and fresh UUIDs — ready to pass to
|
|
18
|
-
* `wallet.invoke.issueCredential()`.
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* ```typescript
|
|
22
|
-
* import { getFixture, prepareFixture } from '@learncard/credential-library';
|
|
23
|
-
*
|
|
24
|
-
* const fixture = getFixture('obv3/full-badge');
|
|
25
|
-
* const unsigned = prepareFixture(fixture, {
|
|
26
|
-
* issuerDid: wallet.id.did(),
|
|
27
|
-
* subjectDid: recipientDid,
|
|
28
|
-
* });
|
|
29
|
-
* const signed = await wallet.invoke.issueCredential(unsigned);
|
|
30
|
-
* await wallet.store.LearnCloud.uploadEncrypted(signed);
|
|
31
|
-
* ```
|
|
32
|
-
*/
|
|
33
15
|
export declare const prepareFixture: (fixture: LibraryFixture, options: PrepareOptions) => UnsignedVC;
|
|
34
16
|
/**
|
|
35
17
|
* Shorthand that combines getFixture + prepareFixture.
|
package/dist/prepare.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prepare.d.ts","sourceRoot":"","sources":["../src/prepare.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAoB,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AAOhE,MAAM,WAAW,cAAc;IAC3B,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAElB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,2CAA2C;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,OAAO,CAAC;CACtB;
|
|
1
|
+
{"version":3,"file":"prepare.d.ts","sourceRoot":"","sources":["../src/prepare.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAoB,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AAOhE,MAAM,WAAW,cAAc;IAC3B,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAElB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,2CAA2C;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,OAAO,CAAC;CACtB;AAsHD,eAAO,MAAM,cAAc,GAAI,SAAS,cAAc,EAAE,SAAS,cAAc,KAAG,UAsEjF,CAAC;AAMF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,kBAAkB,GAAI,WAAW,MAAM,EAAE,SAAS,cAAc,KAAG,UAC9B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@learncard/credential-library",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"description": "Comprehensive library of Verifiable Credential fixtures for testing, development, and regression prevention across the VC ecosystem",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/credential-library.esm.js",
|
|
@@ -26,18 +26,18 @@
|
|
|
26
26
|
"url": "https://github.com/learningeconomy/LearnCard/issues"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@learncard/didkit-plugin": "1.
|
|
30
|
-
"@learncard/init": "2.
|
|
29
|
+
"@learncard/didkit-plugin": "1.10.1",
|
|
30
|
+
"@learncard/init": "2.5.0",
|
|
31
31
|
"@types/node": "^17.0.38",
|
|
32
32
|
"esbuild": "^0.27.1",
|
|
33
33
|
"jose": "^5.9.6",
|
|
34
34
|
"shx": "^0.3.4",
|
|
35
35
|
"typescript": "5.6.2",
|
|
36
|
-
"vitest": "4.1.
|
|
36
|
+
"vitest": "4.1.11"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@learncard/sd-jwt-vc-plugin": "0.2.
|
|
40
|
-
"@learncard/types": "5.
|
|
39
|
+
"@learncard/sd-jwt-vc-plugin": "0.2.14",
|
|
40
|
+
"@learncard/types": "5.21.0",
|
|
41
41
|
"@sd-jwt/sd-jwt-vc": "^0.19.0",
|
|
42
42
|
"zod": "4.1.13"
|
|
43
43
|
},
|
|
@@ -508,6 +508,49 @@ describe('Spec coverage', () => {
|
|
|
508
508
|
// ---------------------------------------------------------------------------
|
|
509
509
|
|
|
510
510
|
describe('prepareFixture', () => {
|
|
511
|
+
it.each(['validUntil', 'expirationDate'])(
|
|
512
|
+
'refreshes stale %s even with a historical validFrom',
|
|
513
|
+
field => {
|
|
514
|
+
const fixture = getFixture('vc-v2/basic');
|
|
515
|
+
const original = {
|
|
516
|
+
...fixture,
|
|
517
|
+
credential: { ...fixture.credential, [field]: '2001-01-01T00:00:00Z' },
|
|
518
|
+
};
|
|
519
|
+
const before = Date.now();
|
|
520
|
+
const prepared = prepareFixture(original, {
|
|
521
|
+
issuerDid: 'did:example:issuer',
|
|
522
|
+
validFrom: '2000-01-01T00:00:00Z',
|
|
523
|
+
});
|
|
524
|
+
const expiry = new Date(prepared[field] as string).getTime();
|
|
525
|
+
expect(expiry).toBeGreaterThanOrEqual(before + 180 * 24 * 60 * 60 * 1000);
|
|
526
|
+
expect(original.credential[field]).toBe('2001-01-01T00:00:00Z');
|
|
527
|
+
}
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
it.each(['validUntil', 'expirationDate'])(
|
|
531
|
+
'keeps regenerated %s after a future validFrom',
|
|
532
|
+
field => {
|
|
533
|
+
const fixture = getFixture('vc-v2/basic');
|
|
534
|
+
const from = new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString();
|
|
535
|
+
const prepared = prepareFixture(
|
|
536
|
+
{ ...fixture, credential: { ...fixture.credential, [field]: from } },
|
|
537
|
+
{ issuerDid: 'did:example:issuer', validFrom: from }
|
|
538
|
+
);
|
|
539
|
+
expect(new Date(prepared[field] as string).getTime()).toBe(
|
|
540
|
+
new Date(from).getTime() + 180 * 24 * 60 * 60 * 1000
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
);
|
|
544
|
+
|
|
545
|
+
it.each(['validUntil', 'expirationDate'])('preserves an explicit past %s override', field => {
|
|
546
|
+
const fixture = getFixture('vc-v2/basic');
|
|
547
|
+
const expiry = '2001-01-01T00:00:00Z';
|
|
548
|
+
const prepared = prepareFixture(
|
|
549
|
+
{ ...fixture, credential: { ...fixture.credential, [field]: expiry } },
|
|
550
|
+
{ issuerDid: 'did:example:issuer', validUntil: expiry }
|
|
551
|
+
);
|
|
552
|
+
expect(prepared[field]).toBe(expiry);
|
|
553
|
+
});
|
|
511
554
|
const issuerDid = 'did:key:z6MkTestIssuer123';
|
|
512
555
|
const subjectDid = 'did:key:z6MkTestSubject456';
|
|
513
556
|
|
|
@@ -16,8 +16,14 @@ import type { CredentialFixture } from '../../types';
|
|
|
16
16
|
* otherwise refuses to sign.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Inline JSON-LD context fragment for terms the published contexts leave undefined.
|
|
21
|
+
* `partial` is in the CLR v2 spec (https://www.imsglobal.org/spec/clr/v2p0/#clrcredential)
|
|
22
|
+
* but the live purl.imsglobal.org CLR context only maps five type names, so without this
|
|
23
|
+
* the signer's data-loss check drops it.
|
|
24
|
+
*/
|
|
20
25
|
export const REFRESH_SERVICE_INLINE_CONTEXT = {
|
|
26
|
+
partial: 'https://purl.imsglobal.org/spec/clr/v2p0#partial',
|
|
21
27
|
'LearnCardCredentialRefresh2026':
|
|
22
28
|
'https://learncard.com/refresh#LearnCardCredentialRefresh2026',
|
|
23
29
|
authorization: {
|
|
@@ -71,6 +77,8 @@ export const clrProvisionalTranscript: CredentialFixture = {
|
|
|
71
77
|
name: 'Ridgeview Community College — Office of the Registrar',
|
|
72
78
|
},
|
|
73
79
|
validFrom: '2026-01-15T00:00:00Z',
|
|
80
|
+
validUntil: '2026-05-31T00:00:00Z',
|
|
81
|
+
partial: true,
|
|
74
82
|
refreshService: PLACEHOLDER_REFRESH_SERVICE,
|
|
75
83
|
credentialSubject: {
|
|
76
84
|
id: 'did:example:student-ridgeview-042',
|
|
@@ -92,7 +100,8 @@ export const clrProvisionalTranscript: CredentialFixture = {
|
|
|
92
100
|
id: 'urn:uuid:7d2e9f41-3c58-4e21-9b6a-1f0c5d8e2a49',
|
|
93
101
|
type: ['ResultDescription'],
|
|
94
102
|
name: 'Course Status',
|
|
95
|
-
resultType: '
|
|
103
|
+
resultType: 'Status',
|
|
104
|
+
allowedValue: ['InProgress', 'Provisional', 'Completed'],
|
|
96
105
|
},
|
|
97
106
|
],
|
|
98
107
|
},
|
|
@@ -163,6 +172,8 @@ export const buildFinalTranscriptVariant = (
|
|
|
163
172
|
'Final official transcript certified by the registrar. Supersedes the provisional record.';
|
|
164
173
|
|
|
165
174
|
if (options.validFrom) final.validFrom = options.validFrom;
|
|
175
|
+
delete final.validUntil;
|
|
176
|
+
delete final.partial;
|
|
166
177
|
|
|
167
178
|
const subject = final.credentialSubject;
|
|
168
179
|
|
|
@@ -180,7 +191,9 @@ export const buildFinalTranscriptVariant = (
|
|
|
180
191
|
next.credentialSubject = {
|
|
181
192
|
...next.credentialSubject,
|
|
182
193
|
result: next.credentialSubject.result.map((result: unknown) =>
|
|
183
|
-
isRecord(result)
|
|
194
|
+
isRecord(result)
|
|
195
|
+
? { ...result, status: 'Completed', value: 'Completed' }
|
|
196
|
+
: result
|
|
184
197
|
),
|
|
185
198
|
};
|
|
186
199
|
}
|
package/src/fixtures/index.ts
CHANGED
|
@@ -23,6 +23,7 @@ import { obv3CourseCompletion } from './obv3/course-completion';
|
|
|
23
23
|
import { obv3StandaloneFullCourse } from './obv3/standalone-full-course';
|
|
24
24
|
import { obv3K12Diploma } from './obv3/k12-diploma';
|
|
25
25
|
import { obv3EndorsementCredential } from './obv3/endorsement';
|
|
26
|
+
import { obv3RubricAlignedBadge } from './obv3/rubric-aligned-badge';
|
|
26
27
|
|
|
27
28
|
// CLR v2
|
|
28
29
|
import { clrMinimal } from './clr/minimal';
|
|
@@ -94,6 +95,7 @@ export const ALL_FIXTURES: LibraryFixture[] = [
|
|
|
94
95
|
obv3StandaloneFullCourse,
|
|
95
96
|
obv3K12Diploma,
|
|
96
97
|
obv3EndorsementCredential,
|
|
98
|
+
obv3RubricAlignedBadge,
|
|
97
99
|
|
|
98
100
|
// CLR v2
|
|
99
101
|
clrMinimal,
|
|
@@ -161,6 +163,7 @@ export {
|
|
|
161
163
|
obv3StandaloneFullCourse,
|
|
162
164
|
obv3K12Diploma,
|
|
163
165
|
obv3EndorsementCredential,
|
|
166
|
+
obv3RubricAlignedBadge,
|
|
164
167
|
clrUniversityTranscript,
|
|
165
168
|
boostCommunityAward,
|
|
166
169
|
boostDelegate,
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { UnsignedAchievementCredentialValidator } from '@learncard/types';
|
|
2
|
+
|
|
3
|
+
import type { CredentialFixture } from '../../types';
|
|
4
|
+
|
|
5
|
+
const RESULT_DESCRIPTION_ID = 'urn:uuid:7cc1519e-87f4-4cae-b7a5-786ec15db769';
|
|
6
|
+
const PROFICIENT_LEVEL_ID = 'urn:uuid:eb753d86-9277-478d-b9c4-3d589c82aff8';
|
|
7
|
+
|
|
8
|
+
export const obv3RubricAlignedBadge: CredentialFixture = {
|
|
9
|
+
id: 'obv3/rubric-aligned-badge',
|
|
10
|
+
name: 'Rubric-Aligned OBv3 Badge',
|
|
11
|
+
description:
|
|
12
|
+
'Open Badges v3 credential with rubric levels, an achieved level, and a CTDL result alignment',
|
|
13
|
+
spec: 'obv3',
|
|
14
|
+
profile: 'badge',
|
|
15
|
+
features: ['alignment', 'results'],
|
|
16
|
+
source: 'synthetic',
|
|
17
|
+
signed: false,
|
|
18
|
+
validity: 'valid',
|
|
19
|
+
validator: UnsignedAchievementCredentialValidator,
|
|
20
|
+
tags: ['rubric', 'open-skill-alignment'],
|
|
21
|
+
|
|
22
|
+
credential: {
|
|
23
|
+
'@context': [
|
|
24
|
+
'https://www.w3.org/ns/credentials/v2',
|
|
25
|
+
'https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json',
|
|
26
|
+
],
|
|
27
|
+
id: 'urn:uuid:b3e5babd-f68e-4095-979d-3e910a444e73',
|
|
28
|
+
type: ['VerifiableCredential', 'OpenBadgeCredential'],
|
|
29
|
+
name: 'Applied Data Analysis',
|
|
30
|
+
description: 'Recognizes applied data analysis at the proficient rubric level.',
|
|
31
|
+
issuer: {
|
|
32
|
+
id: 'did:example:issuer123',
|
|
33
|
+
type: ['Profile'],
|
|
34
|
+
name: 'Example Skills Institute',
|
|
35
|
+
url: 'https://example.org',
|
|
36
|
+
},
|
|
37
|
+
validFrom: '2026-01-15T00:00:00Z',
|
|
38
|
+
credentialSubject: {
|
|
39
|
+
id: 'did:example:learner456',
|
|
40
|
+
type: ['AchievementSubject'],
|
|
41
|
+
achievement: {
|
|
42
|
+
id: 'urn:uuid:e736a10c-aa36-466f-99af-e52440f32d8e',
|
|
43
|
+
type: ['Achievement'],
|
|
44
|
+
achievementType: 'Badge',
|
|
45
|
+
name: 'Applied Data Analysis',
|
|
46
|
+
description: 'Analyze a real-world dataset and communicate supported conclusions.',
|
|
47
|
+
criteria: {
|
|
48
|
+
narrative:
|
|
49
|
+
'Submit an analysis that meets at least the proficient rubric level.',
|
|
50
|
+
},
|
|
51
|
+
resultDescription: [
|
|
52
|
+
{
|
|
53
|
+
id: RESULT_DESCRIPTION_ID,
|
|
54
|
+
type: ['ResultDescription'],
|
|
55
|
+
name: 'Data Analysis Rubric',
|
|
56
|
+
resultType: 'RubricCriterionLevel',
|
|
57
|
+
valueMin: '1',
|
|
58
|
+
valueMax: '4',
|
|
59
|
+
rubricCriterionLevel: [
|
|
60
|
+
{
|
|
61
|
+
id: 'urn:uuid:a4993932-d453-4417-b5aa-52b1bc1ea80e',
|
|
62
|
+
type: ['RubricCriterionLevel'],
|
|
63
|
+
name: 'Developing',
|
|
64
|
+
level: '2',
|
|
65
|
+
points: '2',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: PROFICIENT_LEVEL_ID,
|
|
69
|
+
type: ['RubricCriterionLevel'],
|
|
70
|
+
name: 'Proficient',
|
|
71
|
+
level: '3',
|
|
72
|
+
points: '3',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 'urn:uuid:217fc04f-383b-48a0-a2e5-1cf1b016f7d4',
|
|
76
|
+
type: ['RubricCriterionLevel'],
|
|
77
|
+
name: 'Advanced',
|
|
78
|
+
level: '4',
|
|
79
|
+
points: '4',
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
alignment: [
|
|
83
|
+
{
|
|
84
|
+
type: ['Alignment'],
|
|
85
|
+
targetName: 'Analyze data using appropriate methods',
|
|
86
|
+
targetUrl:
|
|
87
|
+
'https://credentialengineregistry.org/resources/ce-2f5dce8d-29ad-40cb-a9e1-5a5517ff79a7',
|
|
88
|
+
targetType: 'CTDL',
|
|
89
|
+
targetFramework: 'Credential Engine Registry',
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
result: [
|
|
96
|
+
{
|
|
97
|
+
type: ['Result'],
|
|
98
|
+
resultDescription: RESULT_DESCRIPTION_ID,
|
|
99
|
+
value: '3',
|
|
100
|
+
achievedLevel: PROFICIENT_LEVEL_ID,
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
};
|
package/src/prepare.ts
CHANGED
|
@@ -138,6 +138,8 @@ const patchSubject = (subject: unknown, subjectDid: string): unknown => {
|
|
|
138
138
|
* await wallet.store.LearnCloud.uploadEncrypted(signed);
|
|
139
139
|
* ```
|
|
140
140
|
*/
|
|
141
|
+
const FIXTURE_VALIDITY_MS = 180 * 24 * 60 * 60 * 1000;
|
|
142
|
+
|
|
141
143
|
export const prepareFixture = (fixture: LibraryFixture, options: PrepareOptions): UnsignedVC => {
|
|
142
144
|
if (isSdJwtVcFixture(fixture)) {
|
|
143
145
|
throw new Error(
|
|
@@ -192,6 +194,19 @@ export const prepareFixture = (fixture: LibraryFixture, options: PrepareOptions)
|
|
|
192
194
|
} else {
|
|
193
195
|
credential.validUntil = validUntil;
|
|
194
196
|
}
|
|
197
|
+
} else {
|
|
198
|
+
// A fixture's hard-coded expiry goes stale; keep provisional records valid
|
|
199
|
+
// relative to when they are prepared so verification does not fail on age.
|
|
200
|
+
const from = new Date(
|
|
201
|
+
(credential.validFrom ?? credential.issuanceDate) as string
|
|
202
|
+
).getTime();
|
|
203
|
+
const baseline = Math.max(from, Date.now());
|
|
204
|
+
for (const field of ['validUntil', 'expirationDate']) {
|
|
205
|
+
const expiry = credential[field];
|
|
206
|
+
if (typeof expiry === 'string' && new Date(expiry).getTime() <= baseline) {
|
|
207
|
+
credential[field] = new Date(baseline + FIXTURE_VALIDITY_MS).toISOString();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
195
210
|
}
|
|
196
211
|
|
|
197
212
|
return credential as UnsignedVC;
|