@learncard/ler-rs-plugin 0.1.3
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/LICENSE +21 -0
- package/README.md +213 -0
- package/package.json +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Learning Economy Foundation <sdk@learningeconomy.io>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# @learncard/ler-rs-plugin
|
|
2
|
+
|
|
3
|
+
Create, package, and verify Learning & Employment Record Resume (LER-RS) credentials.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
This plugin is part of the LearnCard monorepo and is built with the workspace.
|
|
8
|
+
|
|
9
|
+
## API
|
|
10
|
+
|
|
11
|
+
- `createLerRecord(params: CreateLerRecordParams): Promise<VC>`
|
|
12
|
+
- `createLerPresentation(params: CreateLerPresentationParams): Promise<VP>`
|
|
13
|
+
- `verifyLerPresentation(params: VerifyLerPresentationParams): Promise<VerificationResult>`
|
|
14
|
+
|
|
15
|
+
See `src/types.ts` for types and `src/ler-rs.ts` for implementation details.
|
|
16
|
+
|
|
17
|
+
## Notes
|
|
18
|
+
|
|
19
|
+
- Follows the HR Open LER-RS container + verification pattern by embedding VCs in `verifications` arrays.
|
|
20
|
+
- Ensures credential and presentation `type` fields are non-empty arrays.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Requirements
|
|
25
|
+
|
|
26
|
+
- Your `LearnCard` must already have the VC plugin installed (provided by standard `@learncard/init` initializers).
|
|
27
|
+
- Add this plugin by passing the base LearnCard to the factory and then calling `addPlugin`:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { getLerRsPlugin } from '@learncard/ler-rs-plugin';
|
|
31
|
+
|
|
32
|
+
// baseLc should already include the VC plugin (e.g., via @learncard/init)
|
|
33
|
+
const lc = await baseLc.addPlugin(getLerRsPlugin(baseLc));
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The plugin captures `baseLc` internally to issue and verify VCs/VPs.
|
|
37
|
+
|
|
38
|
+
## Quick start
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import type {
|
|
42
|
+
PersonProfile,
|
|
43
|
+
WorkHistoryItem,
|
|
44
|
+
EducationHistoryItem,
|
|
45
|
+
CertificationItem,
|
|
46
|
+
} from '@learncard/ler-rs-plugin';
|
|
47
|
+
import { getLerRsPlugin } from '@learncard/ler-rs-plugin';
|
|
48
|
+
|
|
49
|
+
// 1) Add plugin
|
|
50
|
+
const lc = await baseLc.addPlugin(getLerRsPlugin(baseLc));
|
|
51
|
+
|
|
52
|
+
// 2) Build a LER-RS credential (self-asserted + optional embedded VCs)
|
|
53
|
+
const person: PersonProfile = {
|
|
54
|
+
id: 'did:example:alice',
|
|
55
|
+
givenName: 'Alice',
|
|
56
|
+
familyName: 'Anderson',
|
|
57
|
+
email: 'alice@example.com',
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const workHistory: WorkHistoryItem[] = [
|
|
61
|
+
{
|
|
62
|
+
position: 'Marketing Professional',
|
|
63
|
+
employer: 'ABC Company',
|
|
64
|
+
start: '2022-01-01',
|
|
65
|
+
end: '2024-06-01',
|
|
66
|
+
narrative: 'Led a multi-channel campaign with 200% ROI.',
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const educationHistory: EducationHistoryItem[] = [
|
|
71
|
+
{
|
|
72
|
+
institution: 'State University',
|
|
73
|
+
degree: 'B.S. Business',
|
|
74
|
+
specializations: ['Marketing Analytics'],
|
|
75
|
+
start: '2018-09-01',
|
|
76
|
+
end: '2022-05-15',
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
const certifications: CertificationItem[] = [
|
|
81
|
+
{
|
|
82
|
+
name: 'Google Analytics Certification',
|
|
83
|
+
issuingAuthority: 'Google',
|
|
84
|
+
status: 'active',
|
|
85
|
+
narrative: 'Validated proficiency in GA4 and attribution modeling.',
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
const skills = ['SEO/SEM', 'Content Strategy', 'Team Leadership'];
|
|
90
|
+
|
|
91
|
+
const lerVc = await lc.invoke.createLerRecord({
|
|
92
|
+
person,
|
|
93
|
+
workHistory,
|
|
94
|
+
educationHistory,
|
|
95
|
+
certifications,
|
|
96
|
+
skills,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// 3) Package into a VP (must include at least one LER-RS VC)
|
|
100
|
+
const vp = await lc.invoke.createLerPresentation({
|
|
101
|
+
credentials: [lerVc],
|
|
102
|
+
domain: 'apply.acme.com',
|
|
103
|
+
challenge: 'a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8',
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// 4) Verify the presentation
|
|
107
|
+
const verification = await lc.invoke.verifyLerPresentation({
|
|
108
|
+
presentation: vp,
|
|
109
|
+
domain: 'apply.acme.com',
|
|
110
|
+
challenge: 'a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8',
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
console.log(verification.verified);
|
|
114
|
+
for (const r of verification.credentialResults) {
|
|
115
|
+
console.log(r.credential.id, r.verified, r.isSelfIssued, r.errors);
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Wrapping existing VCs inside containers
|
|
120
|
+
|
|
121
|
+
Each container item (`workHistory`, `educationHistory`, `certifications`) can embed externally issued VCs as verifications. Provide `verifiableCredential` in the item to include it under `verifications`:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
// Assume employmentVc is a third-party or previously issued VC
|
|
125
|
+
const employmentVc = await lc.read.get('urn:uuid:employment-123');
|
|
126
|
+
|
|
127
|
+
const lerWithProof = await lc.invoke.createLerRecord({
|
|
128
|
+
person,
|
|
129
|
+
workHistory: [
|
|
130
|
+
{
|
|
131
|
+
narrative: 'My key responsibilities and outcomes.',
|
|
132
|
+
verifiableCredential: employmentVc,
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Schema mapping guidance
|
|
139
|
+
|
|
140
|
+
This plugin follows a "Container + Verifiable Proof" model. For `credentialSubject` (LER-RS payload), we map as follows (see `src/ler-rs.ts`):
|
|
141
|
+
|
|
142
|
+
- __person__
|
|
143
|
+
- From `params.person`
|
|
144
|
+
- Maps to `credentialSubject.person` with `name.given`, `name.family`, and `name.formattedName`
|
|
145
|
+
|
|
146
|
+
- __communication__
|
|
147
|
+
- If `person.email` is provided, becomes `credentialSubject.communication.email = [{ address: email }]`
|
|
148
|
+
|
|
149
|
+
- __skills__
|
|
150
|
+
- `params.skills: string[]` → `credentialSubject.skills = [{ name: string }]`
|
|
151
|
+
|
|
152
|
+
- __employmentHistories__ (from `params.workHistory`)
|
|
153
|
+
- `employer` → `container.organization.tradeName`
|
|
154
|
+
- `position`, `start`, `end` → `container.positionHistories = [{ title, start, end }]`
|
|
155
|
+
- `narrative` → `container.narrative`
|
|
156
|
+
- `verifiableCredential` or `verifications` → `container.verifications = [VC]`
|
|
157
|
+
- Any other keys on the item are merged into the container as-is
|
|
158
|
+
|
|
159
|
+
- __educationAndLearnings__ (from `params.educationHistory`)
|
|
160
|
+
- `institution` → `container.institution = { name: institution }`
|
|
161
|
+
- `start`, `end` → same key names on container
|
|
162
|
+
- `degree`, `specializations` → `container.educationDegrees = [{ name: degree, specializations: [{ name }] }]`
|
|
163
|
+
- `narrative` → `container.narrative`
|
|
164
|
+
- `verifiableCredential` or `verifications` → `container.verifications = [VC]`
|
|
165
|
+
|
|
166
|
+
- __certifications__ (from `params.certifications`)
|
|
167
|
+
- All provided keys are copied into the container
|
|
168
|
+
- `narrative` → `container.narrative`
|
|
169
|
+
- `verifiableCredential` or `verifications` → `container.verifications = [VC]`
|
|
170
|
+
|
|
171
|
+
Resulting VC shape (high-level):
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
{
|
|
175
|
+
'@context': [
|
|
176
|
+
'https://www.w3.org/ns/credentials/v2',
|
|
177
|
+
'http://schema.hropenstandards.org/4.5/recruiting/json/VerifiableCredentialLER-RSType.json',
|
|
178
|
+
],
|
|
179
|
+
type: ['VerifiableCredential', 'http://schema.hropenstandards.org/4.5/recruiting/json/LER-RSType.json'],
|
|
180
|
+
issuer: 'did:...issuer',
|
|
181
|
+
validFrom: '...',
|
|
182
|
+
credentialSubject: {
|
|
183
|
+
id: 'did:...subject',
|
|
184
|
+
type: 'http://schema.hropenstandards.org/4.5/recruiting/json/LER-RSType.json',
|
|
185
|
+
person: { name: { given, family, formattedName } },
|
|
186
|
+
communication?: { email?: [{ address }] },
|
|
187
|
+
skills?: [{ name }],
|
|
188
|
+
employmentHistories?: [{ ...container, verifications?: [VC] }],
|
|
189
|
+
educationAndLearnings?: [{ ...container, verifications?: [VC] }],
|
|
190
|
+
certifications?: [{ ...container, verifications?: [VC] }],
|
|
191
|
+
},
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Verification behavior
|
|
196
|
+
|
|
197
|
+
- `verifyLerPresentation` verifies the VP and each embedded VC.
|
|
198
|
+
- `VerificationResult.verified` is true when:
|
|
199
|
+
- The presentation verifies, and
|
|
200
|
+
- Every credential either verifies OR is considered self-issued.
|
|
201
|
+
- A credential is considered __self-issued__ when:
|
|
202
|
+
- It is recognized as an LER-RS credential by known legacy/current LER-RS types, or
|
|
203
|
+
- Its `issuer` DID equals the VP `holder` DID.
|
|
204
|
+
|
|
205
|
+
## Troubleshooting
|
|
206
|
+
|
|
207
|
+
- __Non-empty type arrays__: VC/VP `type` are always emitted as non-empty arrays to satisfy validators (e.g., Zod schemas that require `[string, ...string[]]`).
|
|
208
|
+
- __Missing VC plugin__: Ensure your base LearnCard already includes `@learncard/vc-plugin` (standard in `@learncard/init` initializers).
|
|
209
|
+
|
|
210
|
+
## Contributing
|
|
211
|
+
|
|
212
|
+
- Types live in `src/types.ts`. Implementation is in `src/ler-rs.ts`.
|
|
213
|
+
- Please add tests for new behaviors and keep examples in this README up-to-date with the code.
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@learncard/ler-rs-plugin",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "LER-RS plugin for LearnCard: create, package, and verify Learning & Employment Record Resumes",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/ler-rs-plugin.esm.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"author": "Learning Economy Foundation (www.learningeconomy.io)",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"homepage": "https://github.com/WeLibraryOS/LearnCard#readme",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/WeLibraryOS/LearnCard.git"
|
|
16
|
+
},
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/WeLibraryOS/LearnCard/issues"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/jest": "^29.2.2",
|
|
22
|
+
"@types/node": "^17.0.31",
|
|
23
|
+
"aqu": "0.4.3",
|
|
24
|
+
"esbuild": "^0.14.38",
|
|
25
|
+
"esbuild-jest": "^0.5.0",
|
|
26
|
+
"esbuild-plugin-copy": "^1.3.0",
|
|
27
|
+
"jest": "^29.3.0",
|
|
28
|
+
"rimraf": "^3.0.2",
|
|
29
|
+
"shx": "^0.3.4",
|
|
30
|
+
"ts-jest": "^29.0.3"
|
|
31
|
+
},
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@learncard/core": "9.4.12",
|
|
35
|
+
"@learncard/vc-plugin": "1.4.8",
|
|
36
|
+
"@learncard/types": "5.13.2"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "node ./scripts/build.mjs && shx cp ./scripts/mixedEntypoint.js ./dist/index.js && tsc --p tsconfig.json",
|
|
40
|
+
"test": "jest --passWithNoTests",
|
|
41
|
+
"test:watch": "jest --watch",
|
|
42
|
+
"test:coverage": "jest --silent --ci --coverage --coverageReporters=\"text\" --coverageReporters=\"text-summary\""
|
|
43
|
+
}
|
|
44
|
+
}
|