smartledger-bsv 3.2.1 → 3.3.1
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/CHANGELOG.md +147 -0
- package/README.md +289 -55
- package/architecture_demo.js +247 -0
- package/bsv-covenant.min.js +10 -0
- package/bsv-gdaf.min.js +37 -0
- package/bsv-ltp.min.js +37 -0
- package/bsv-script-helper.min.js +10 -0
- package/bsv-security.min.js +31 -0
- package/bsv-shamir.min.js +12 -0
- package/bsv-smartcontract.min.js +37 -0
- package/bsv.bundle.js +9 -9
- package/bsv.min.js +3 -3
- package/build/bsv-covenant.min.js +10 -0
- package/build/bsv-script-helper.min.js +10 -0
- package/build/bsv-security.min.js +31 -0
- package/build/bsv-smartcontract.min.js +39 -0
- package/build/bsv.bundle.js +39 -0
- package/build/bsv.min.js +39 -0
- package/build/webpack.bundle.config.js +22 -0
- package/build/webpack.config.js +18 -0
- package/build/webpack.covenant.config.js +27 -0
- package/build/webpack.gdaf.config.js +54 -0
- package/build/webpack.ltp.config.js +17 -0
- package/build/webpack.script-helper.config.js +27 -0
- package/build/webpack.security.config.js +23 -0
- package/build/webpack.smartcontract.config.js +25 -0
- package/build/webpack.subproject.config.js +6 -0
- package/bundle-entry.js +341 -0
- package/complete_ltp_demo.js +511 -0
- package/covenant-entry.js +44 -0
- package/docs/pushtx-key-insights.md +106 -0
- package/gdaf-entry.js +54 -0
- package/index.js +272 -5
- package/lib/crypto/shamir.js +360 -0
- package/lib/gdaf/attestation-signer.js +461 -0
- package/lib/gdaf/attestation-verifier.js +600 -0
- package/lib/gdaf/did-resolver.js +382 -0
- package/lib/gdaf/index.js +471 -0
- package/lib/gdaf/schema-validator.js +682 -0
- package/lib/gdaf/smartledger-anchor.js +486 -0
- package/lib/gdaf/zk-prover.js +507 -0
- package/lib/ltp/anchor.js +438 -0
- package/lib/ltp/claim.js +1026 -0
- package/lib/ltp/index.js +470 -0
- package/lib/ltp/obligation.js +945 -0
- package/lib/ltp/proof.js +828 -0
- package/lib/ltp/registry.js +702 -0
- package/lib/ltp/right.js +765 -0
- package/lib/smart_contract/API_REFERENCE.md +1 -1
- package/lib/smart_contract/EXAMPLES.md +2 -2
- package/lib/smart_contract/QUICK_START.md +2 -2
- package/lib/smart_contract/README.md +1 -1
- package/lib/smart_contract/index.js +4 -0
- package/ltp-entry.js +92 -0
- package/package.json +91 -20
- package/script-helper-entry.js +49 -0
- package/security-entry.js +70 -0
- package/shamir-entry.js +173 -0
- package/shamir_demo.js +121 -0
- package/simple_demo.js +204 -0
- package/smartcontract-entry.js +133 -0
- package/test_shamir.js +221 -0
- package/test_standalone_shamir.html +83 -0
- package/tests/bundle-completeness-test.html +131 -0
- package/tests/bundle-demo.html +476 -0
- package/tests/smartcontract-test.html +239 -0
- package/tests/standalone-modules-test.html +260 -0
- package/tests/test.html +612 -0
- package/tests/unpkg-demo.html +194 -0
- package/docs/nchain.md +0 -958
|
@@ -0,0 +1,945 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
var bsv = require('../../')
|
|
4
|
+
var Hash = bsv.crypto.Hash
|
|
5
|
+
var ECDSA = bsv.crypto.ECDSA
|
|
6
|
+
var PrivateKey = bsv.PrivateKey
|
|
7
|
+
var $ = bsv.util.preconditions
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Legal Token Protocol - Obligation Creation Primitives
|
|
11
|
+
*
|
|
12
|
+
* Provides primitives for legal obligation token creation, validation, and management
|
|
13
|
+
* without direct blockchain publishing. External systems handle obligation storage
|
|
14
|
+
* and blockchain anchoring operations.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Obligation Types
|
|
19
|
+
*/
|
|
20
|
+
var ObligationTypes = {
|
|
21
|
+
CORRELATIVE_OBLIGATION: 'CorrelativeObligation',
|
|
22
|
+
CONTRACTUAL_OBLIGATION: 'ContractualObligation',
|
|
23
|
+
PAYMENT_OBLIGATION: 'PaymentObligation',
|
|
24
|
+
DELIVERY_OBLIGATION: 'DeliveryObligation',
|
|
25
|
+
PERFORMANCE_OBLIGATION: 'PerformanceObligation',
|
|
26
|
+
MAINTENANCE_OBLIGATION: 'MaintenanceObligation',
|
|
27
|
+
COMPLIANCE_OBLIGATION: 'ComplianceObligation',
|
|
28
|
+
REPORTING_OBLIGATION: 'ReportingObligation',
|
|
29
|
+
CONFIDENTIALITY_OBLIGATION: 'ConfidentialityObligation',
|
|
30
|
+
NON_COMPETE_OBLIGATION: 'NonCompeteObligation',
|
|
31
|
+
INDEMNIFICATION_OBLIGATION: 'IndemnificationObligation',
|
|
32
|
+
WARRANTY_OBLIGATION: 'WarrantyObligation',
|
|
33
|
+
SUPPORT_OBLIGATION: 'SupportObligation',
|
|
34
|
+
REGULATORY_OBLIGATION: 'RegulatoryObligation',
|
|
35
|
+
FIDUCIARY_OBLIGATION: 'FiduciaryObligation'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Obligation Priority Levels
|
|
40
|
+
*/
|
|
41
|
+
var ObligationPriority = {
|
|
42
|
+
CRITICAL: 'CRITICAL',
|
|
43
|
+
HIGH: 'HIGH',
|
|
44
|
+
MEDIUM: 'MEDIUM',
|
|
45
|
+
LOW: 'LOW',
|
|
46
|
+
DEFERRED: 'DEFERRED'
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Obligation Status Values
|
|
51
|
+
*/
|
|
52
|
+
var ObligationStatus = {
|
|
53
|
+
PENDING: 'PENDING',
|
|
54
|
+
ACTIVE: 'ACTIVE',
|
|
55
|
+
IN_PROGRESS: 'IN_PROGRESS',
|
|
56
|
+
COMPLETED: 'COMPLETED',
|
|
57
|
+
OVERDUE: 'OVERDUE',
|
|
58
|
+
BREACHED: 'BREACHED',
|
|
59
|
+
WAIVED: 'WAIVED',
|
|
60
|
+
TERMINATED: 'TERMINATED'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Legal Obligation Token
|
|
65
|
+
*/
|
|
66
|
+
var ObligationToken = {
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Prepare legal obligation token for external processing
|
|
70
|
+
* @param {String} type - Obligation type (from ObligationTypes)
|
|
71
|
+
* @param {String} issuerDID - Issuer DID
|
|
72
|
+
* @param {String} obligorDID - Obligor DID (who has the obligation)
|
|
73
|
+
* @param {Object} obligation - Obligation details
|
|
74
|
+
* @param {PrivateKey} issuerPrivateKey - Issuer's private key
|
|
75
|
+
* @param {Object} options - Additional options
|
|
76
|
+
* @returns {Object} Prepared legal obligation token data
|
|
77
|
+
*/
|
|
78
|
+
prepareObligationToken: function(type, issuerDID, obligorDID, obligation, issuerPrivateKey, options) {
|
|
79
|
+
options = options || {}
|
|
80
|
+
|
|
81
|
+
$.checkArgument(typeof type === 'string', 'Obligation type must be string')
|
|
82
|
+
$.checkArgument(typeof issuerDID === 'string', 'Issuer DID must be string')
|
|
83
|
+
$.checkArgument(typeof obligorDID === 'string', 'Obligor DID must be string')
|
|
84
|
+
$.checkArgument(obligation && typeof obligation === 'object', 'Obligation must be object')
|
|
85
|
+
$.checkArgument(issuerPrivateKey instanceof PrivateKey, 'Invalid issuer private key')
|
|
86
|
+
|
|
87
|
+
// Validate obligation type
|
|
88
|
+
var validTypes = Object.values(ObligationTypes)
|
|
89
|
+
$.checkArgument(validTypes.includes(type), 'Invalid obligation type: ' + type)
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
var obligationToken = {
|
|
93
|
+
'@context': [
|
|
94
|
+
'https://www.w3.org/2018/credentials/v1',
|
|
95
|
+
'https://smartledger.technology/contexts/ltp/v1'
|
|
96
|
+
],
|
|
97
|
+
id: 'urn:uuid:' + this._generateUUID(),
|
|
98
|
+
type: ['VerifiableCredential', 'LegalObligationToken', type],
|
|
99
|
+
issuer: issuerDID,
|
|
100
|
+
issuanceDate: new Date().toISOString(),
|
|
101
|
+
credentialSubject: {
|
|
102
|
+
id: obligorDID,
|
|
103
|
+
obligationType: type,
|
|
104
|
+
obligation: obligation,
|
|
105
|
+
jurisdiction: options.jurisdiction || 'US',
|
|
106
|
+
priority: options.priority || ObligationPriority.MEDIUM,
|
|
107
|
+
status: ObligationStatus.PENDING,
|
|
108
|
+
dueDate: options.dueDate || null,
|
|
109
|
+
effectiveDate: options.effectiveDate || new Date().toISOString(),
|
|
110
|
+
expirationDate: options.expirationDate || null,
|
|
111
|
+
correlativeRight: options.correlativeRight || null,
|
|
112
|
+
rightHolder: options.rightHolder || null,
|
|
113
|
+
enforceable: options.enforceable !== false,
|
|
114
|
+
penalties: options.penalties || null,
|
|
115
|
+
remedies: options.remedies || null
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Add metadata
|
|
120
|
+
if (options.metadata) {
|
|
121
|
+
obligationToken.credentialSubject.metadata = options.metadata
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Sign the token
|
|
125
|
+
var signedToken = this._signToken(obligationToken, issuerPrivateKey)
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
success: true,
|
|
129
|
+
obligationToken: signedToken,
|
|
130
|
+
tokenHash: signedToken.tokenHash,
|
|
131
|
+
metadata: {
|
|
132
|
+
type: type,
|
|
133
|
+
issuer: issuerDID,
|
|
134
|
+
obligor: obligorDID,
|
|
135
|
+
priority: obligationToken.credentialSubject.priority,
|
|
136
|
+
status: obligationToken.credentialSubject.status,
|
|
137
|
+
jurisdiction: obligationToken.credentialSubject.jurisdiction,
|
|
138
|
+
dueDate: obligationToken.credentialSubject.dueDate,
|
|
139
|
+
correlativeRight: obligationToken.credentialSubject.correlativeRight
|
|
140
|
+
},
|
|
141
|
+
externalOperations: {
|
|
142
|
+
storeToken: {
|
|
143
|
+
endpoint: 'POST /obligations/tokens',
|
|
144
|
+
data: {
|
|
145
|
+
token: signedToken,
|
|
146
|
+
metadata: {
|
|
147
|
+
type: type,
|
|
148
|
+
issuer: issuerDID,
|
|
149
|
+
obligor: obligorDID,
|
|
150
|
+
priority: obligationToken.credentialSubject.priority,
|
|
151
|
+
status: obligationToken.credentialSubject.status,
|
|
152
|
+
jurisdiction: obligationToken.credentialSubject.jurisdiction,
|
|
153
|
+
dueDate: obligationToken.credentialSubject.dueDate,
|
|
154
|
+
correlativeRight: obligationToken.credentialSubject.correlativeRight
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
indexToken: {
|
|
159
|
+
endpoint: 'POST /obligations/index',
|
|
160
|
+
data: {
|
|
161
|
+
tokenId: signedToken.id,
|
|
162
|
+
tokenHash: signedToken.tokenHash,
|
|
163
|
+
type: type,
|
|
164
|
+
issuer: issuerDID,
|
|
165
|
+
obligor: obligorDID,
|
|
166
|
+
issuedAt: signedToken.issuanceDate,
|
|
167
|
+
dueDate: obligationToken.credentialSubject.dueDate,
|
|
168
|
+
priority: obligationToken.credentialSubject.priority
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
notifyObligor: {
|
|
172
|
+
endpoint: 'POST /notifications/obligation-issued',
|
|
173
|
+
data: {
|
|
174
|
+
tokenId: signedToken.id,
|
|
175
|
+
issuer: issuerDID,
|
|
176
|
+
obligor: obligorDID,
|
|
177
|
+
obligationType: type,
|
|
178
|
+
dueDate: obligationToken.credentialSubject.dueDate,
|
|
179
|
+
priority: obligationToken.credentialSubject.priority
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
scheduleReminders: obligationToken.credentialSubject.dueDate ? {
|
|
183
|
+
endpoint: 'POST /obligations/schedule-reminders',
|
|
184
|
+
data: {
|
|
185
|
+
tokenId: signedToken.id,
|
|
186
|
+
obligor: obligorDID,
|
|
187
|
+
dueDate: obligationToken.credentialSubject.dueDate,
|
|
188
|
+
reminderSchedule: options.reminderSchedule || ['7d', '3d', '1d']
|
|
189
|
+
}
|
|
190
|
+
} : null
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
} catch (error) {
|
|
195
|
+
return {
|
|
196
|
+
success: false,
|
|
197
|
+
error: error.message
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Prepare obligation verification for external processing
|
|
204
|
+
* @param {Object} token - Obligation token to verify
|
|
205
|
+
* @param {Object} options - Verification options
|
|
206
|
+
* @returns {Object} Prepared verification data
|
|
207
|
+
*/
|
|
208
|
+
prepareObligationVerification: function(token, options) {
|
|
209
|
+
options = options || {}
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
var errors = []
|
|
213
|
+
var warnings = []
|
|
214
|
+
|
|
215
|
+
// Basic structure validation
|
|
216
|
+
this._validateTokenStructure(token, errors)
|
|
217
|
+
|
|
218
|
+
// Signature verification
|
|
219
|
+
this._verifyTokenSignature(token, errors)
|
|
220
|
+
|
|
221
|
+
// Temporal validation
|
|
222
|
+
this._validateTokenTemporal(token, errors, warnings)
|
|
223
|
+
|
|
224
|
+
// Type validation
|
|
225
|
+
this._validateObligationType(token, errors)
|
|
226
|
+
|
|
227
|
+
// Status validation
|
|
228
|
+
this._validateObligationStatus(token, errors, warnings)
|
|
229
|
+
|
|
230
|
+
// Priority validation
|
|
231
|
+
this._validateObligationPriority(token, warnings)
|
|
232
|
+
|
|
233
|
+
var verification = {
|
|
234
|
+
valid: errors.length === 0,
|
|
235
|
+
errors: errors,
|
|
236
|
+
warnings: warnings,
|
|
237
|
+
issuerDID: token.issuer,
|
|
238
|
+
obligorDID: token.credentialSubject ? token.credentialSubject.id : null,
|
|
239
|
+
obligationType: token.credentialSubject ? token.credentialSubject.obligationType : null,
|
|
240
|
+
status: token.credentialSubject ? token.credentialSubject.status : null,
|
|
241
|
+
priority: token.credentialSubject ? token.credentialSubject.priority : null,
|
|
242
|
+
isOverdue: this._checkIfOverdue(token),
|
|
243
|
+
verifiedAt: new Date().toISOString()
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
success: true,
|
|
248
|
+
verification: verification,
|
|
249
|
+
externalOperations: {
|
|
250
|
+
recordVerification: {
|
|
251
|
+
endpoint: 'POST /obligations/verification-record',
|
|
252
|
+
data: {
|
|
253
|
+
tokenId: token.id,
|
|
254
|
+
result: verification.valid,
|
|
255
|
+
errors: errors,
|
|
256
|
+
warnings: warnings,
|
|
257
|
+
verifiedAt: verification.verifiedAt,
|
|
258
|
+
isOverdue: verification.isOverdue
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
auditVerification: {
|
|
262
|
+
endpoint: 'POST /audit/obligation-verification',
|
|
263
|
+
data: {
|
|
264
|
+
tokenId: token.id,
|
|
265
|
+
obligationType: verification.obligationType,
|
|
266
|
+
issuer: verification.issuerDID,
|
|
267
|
+
obligor: verification.obligorDID,
|
|
268
|
+
result: verification.valid,
|
|
269
|
+
verifiedAt: verification.verifiedAt
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
updateStatus: verification.isOverdue && token.credentialSubject.status === ObligationStatus.ACTIVE ? {
|
|
273
|
+
endpoint: 'PUT /obligations/tokens/' + token.id + '/status',
|
|
274
|
+
data: {
|
|
275
|
+
status: ObligationStatus.OVERDUE,
|
|
276
|
+
updatedAt: verification.verifiedAt,
|
|
277
|
+
reason: 'Due date passed'
|
|
278
|
+
}
|
|
279
|
+
} : null
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
} catch (error) {
|
|
284
|
+
return {
|
|
285
|
+
success: false,
|
|
286
|
+
error: 'Verification preparation failed: ' + error.message
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Prepare obligation fulfillment for external processing
|
|
293
|
+
* @param {Object} token - Obligation token
|
|
294
|
+
* @param {Object} fulfillment - Fulfillment details
|
|
295
|
+
* @param {PrivateKey} obligorKey - Obligor's private key
|
|
296
|
+
* @param {Object} options - Fulfillment options
|
|
297
|
+
* @returns {Object} Prepared fulfillment data
|
|
298
|
+
*/
|
|
299
|
+
prepareObligationFulfillment: function(token, fulfillment, obligorKey, options) {
|
|
300
|
+
options = options || {}
|
|
301
|
+
|
|
302
|
+
$.checkArgument(token && typeof token === 'object', 'Invalid token')
|
|
303
|
+
$.checkArgument(fulfillment && typeof fulfillment === 'object', 'Invalid fulfillment')
|
|
304
|
+
$.checkArgument(obligorKey instanceof PrivateKey, 'Invalid obligor key')
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
// Verify current token
|
|
308
|
+
var verification = this.prepareObligationVerification(token, {})
|
|
309
|
+
if (!verification.success || !verification.verification.valid) {
|
|
310
|
+
return {
|
|
311
|
+
success: false,
|
|
312
|
+
error: 'Cannot fulfill invalid obligation: ' + verification.verification.errors.join(', ')
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Check if obligation can be fulfilled
|
|
317
|
+
if (token.credentialSubject.status === ObligationStatus.COMPLETED) {
|
|
318
|
+
return {
|
|
319
|
+
success: false,
|
|
320
|
+
error: 'Obligation already fulfilled'
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (token.credentialSubject.status === ObligationStatus.TERMINATED) {
|
|
325
|
+
return {
|
|
326
|
+
success: false,
|
|
327
|
+
error: 'Obligation has been terminated'
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Create fulfillment record
|
|
332
|
+
var fulfillmentRecord = {
|
|
333
|
+
'@context': [
|
|
334
|
+
'https://www.w3.org/2018/credentials/v1',
|
|
335
|
+
'https://smartledger.technology/contexts/ltp/v1'
|
|
336
|
+
],
|
|
337
|
+
id: 'urn:uuid:' + this._generateUUID(),
|
|
338
|
+
type: ['VerifiableCredential', 'ObligationFulfillment'],
|
|
339
|
+
issuer: token.credentialSubject.id, // Obligor
|
|
340
|
+
issuanceDate: new Date().toISOString(),
|
|
341
|
+
credentialSubject: {
|
|
342
|
+
id: token.id, // References the obligation
|
|
343
|
+
fulfillmentType: fulfillment.type || 'FULL_FULFILLMENT',
|
|
344
|
+
fulfillmentDate: new Date().toISOString(),
|
|
345
|
+
evidence: fulfillment.evidence || null,
|
|
346
|
+
description: fulfillment.description || '',
|
|
347
|
+
completionPercentage: fulfillment.completionPercentage || 100,
|
|
348
|
+
attestations: fulfillment.attestations || [],
|
|
349
|
+
notes: fulfillment.notes || null
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Sign fulfillment with obligor key
|
|
354
|
+
var signedFulfillment = this._signToken(fulfillmentRecord, obligorKey)
|
|
355
|
+
|
|
356
|
+
// Determine new obligation status
|
|
357
|
+
var newStatus = fulfillmentRecord.credentialSubject.completionPercentage >= 100 ?
|
|
358
|
+
ObligationStatus.COMPLETED :
|
|
359
|
+
ObligationStatus.IN_PROGRESS
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
success: true,
|
|
363
|
+
fulfillmentRecord: signedFulfillment,
|
|
364
|
+
newStatus: newStatus,
|
|
365
|
+
fulfillmentId: signedFulfillment.id,
|
|
366
|
+
externalOperations: {
|
|
367
|
+
recordFulfillment: {
|
|
368
|
+
endpoint: 'POST /obligations/fulfillments',
|
|
369
|
+
data: {
|
|
370
|
+
obligationId: token.id,
|
|
371
|
+
fulfillmentRecord: signedFulfillment,
|
|
372
|
+
obligor: token.credentialSubject.id,
|
|
373
|
+
fulfillmentDate: fulfillmentRecord.credentialSubject.fulfillmentDate,
|
|
374
|
+
completionPercentage: fulfillmentRecord.credentialSubject.completionPercentage
|
|
375
|
+
}
|
|
376
|
+
},
|
|
377
|
+
updateStatus: {
|
|
378
|
+
endpoint: 'PUT /obligations/tokens/' + token.id + '/status',
|
|
379
|
+
data: {
|
|
380
|
+
status: newStatus,
|
|
381
|
+
fulfillmentId: signedFulfillment.id,
|
|
382
|
+
updatedAt: fulfillmentRecord.credentialSubject.fulfillmentDate,
|
|
383
|
+
completionPercentage: fulfillmentRecord.credentialSubject.completionPercentage
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
notifyRightHolder: token.credentialSubject.rightHolder ? {
|
|
387
|
+
endpoint: 'POST /notifications/obligation-fulfilled',
|
|
388
|
+
data: {
|
|
389
|
+
obligationId: token.id,
|
|
390
|
+
fulfillmentId: signedFulfillment.id,
|
|
391
|
+
obligor: token.credentialSubject.id,
|
|
392
|
+
rightHolder: token.credentialSubject.rightHolder,
|
|
393
|
+
completionPercentage: fulfillmentRecord.credentialSubject.completionPercentage,
|
|
394
|
+
newStatus: newStatus
|
|
395
|
+
}
|
|
396
|
+
} : null,
|
|
397
|
+
auditFulfillment: {
|
|
398
|
+
endpoint: 'POST /audit/obligation-fulfillment',
|
|
399
|
+
data: {
|
|
400
|
+
obligationId: token.id,
|
|
401
|
+
fulfillmentId: signedFulfillment.id,
|
|
402
|
+
obligor: token.credentialSubject.id,
|
|
403
|
+
fulfilledAt: fulfillmentRecord.credentialSubject.fulfillmentDate,
|
|
404
|
+
completionPercentage: fulfillmentRecord.credentialSubject.completionPercentage
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
} catch (error) {
|
|
411
|
+
return {
|
|
412
|
+
success: false,
|
|
413
|
+
error: error.message
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Prepare obligation breach assessment for external processing
|
|
420
|
+
* @param {Object} token - Obligation token
|
|
421
|
+
* @param {Object} breach - Breach details
|
|
422
|
+
* @param {Object} assessor - Assessor information
|
|
423
|
+
* @returns {Object} Prepared breach assessment data
|
|
424
|
+
*/
|
|
425
|
+
prepareObligationBreachAssessment: function(token, breach, assessor) {
|
|
426
|
+
$.checkArgument(token && typeof token === 'object', 'Invalid token')
|
|
427
|
+
$.checkArgument(breach && typeof breach === 'object', 'Invalid breach details')
|
|
428
|
+
$.checkArgument(assessor && typeof assessor === 'object', 'Invalid assessor')
|
|
429
|
+
|
|
430
|
+
try {
|
|
431
|
+
var breachAssessment = {
|
|
432
|
+
'@context': [
|
|
433
|
+
'https://www.w3.org/2018/credentials/v1',
|
|
434
|
+
'https://smartledger.technology/contexts/ltp/v1'
|
|
435
|
+
],
|
|
436
|
+
id: 'urn:uuid:' + this._generateUUID(),
|
|
437
|
+
type: ['VerifiableCredential', 'ObligationBreachAssessment'],
|
|
438
|
+
issuer: assessor.did || assessor.id,
|
|
439
|
+
issuanceDate: new Date().toISOString(),
|
|
440
|
+
credentialSubject: {
|
|
441
|
+
id: token.id, // References the obligation
|
|
442
|
+
breachType: breach.type || 'MATERIAL_BREACH',
|
|
443
|
+
breachDate: breach.date || new Date().toISOString(),
|
|
444
|
+
description: breach.description || '',
|
|
445
|
+
severity: breach.severity || 'MODERATE',
|
|
446
|
+
evidence: breach.evidence || [],
|
|
447
|
+
assessor: {
|
|
448
|
+
did: assessor.did || assessor.id,
|
|
449
|
+
name: assessor.name || 'Unknown',
|
|
450
|
+
authority: assessor.authority || null,
|
|
451
|
+
jurisdiction: assessor.jurisdiction || null
|
|
452
|
+
},
|
|
453
|
+
damages: breach.damages || null,
|
|
454
|
+
recommendedRemedies: breach.recommendedRemedies || [],
|
|
455
|
+
assessmentDate: new Date().toISOString()
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
var breachHash = Hash.sha256(Buffer.from(JSON.stringify(breachAssessment))).toString('hex')
|
|
460
|
+
breachAssessment.credentialSubject.assessmentHash = breachHash
|
|
461
|
+
|
|
462
|
+
return {
|
|
463
|
+
success: true,
|
|
464
|
+
breachAssessment: breachAssessment,
|
|
465
|
+
breachId: breachAssessment.id,
|
|
466
|
+
severity: breach.severity || 'MODERATE',
|
|
467
|
+
externalOperations: {
|
|
468
|
+
recordBreach: {
|
|
469
|
+
endpoint: 'POST /obligations/breaches',
|
|
470
|
+
data: {
|
|
471
|
+
obligationId: token.id,
|
|
472
|
+
breachAssessment: breachAssessment,
|
|
473
|
+
obligor: token.credentialSubject.id,
|
|
474
|
+
rightHolder: token.credentialSubject.rightHolder,
|
|
475
|
+
breachDate: breach.date || new Date().toISOString(),
|
|
476
|
+
severity: breach.severity || 'MODERATE'
|
|
477
|
+
}
|
|
478
|
+
},
|
|
479
|
+
updateStatus: {
|
|
480
|
+
endpoint: 'PUT /obligations/tokens/' + token.id + '/status',
|
|
481
|
+
data: {
|
|
482
|
+
status: ObligationStatus.BREACHED,
|
|
483
|
+
breachId: breachAssessment.id,
|
|
484
|
+
breachDate: breach.date || new Date().toISOString(),
|
|
485
|
+
updatedAt: new Date().toISOString()
|
|
486
|
+
}
|
|
487
|
+
},
|
|
488
|
+
notifyParties: {
|
|
489
|
+
endpoint: 'POST /notifications/obligation-breached',
|
|
490
|
+
data: {
|
|
491
|
+
obligationId: token.id,
|
|
492
|
+
breachId: breachAssessment.id,
|
|
493
|
+
obligor: token.credentialSubject.id,
|
|
494
|
+
rightHolder: token.credentialSubject.rightHolder,
|
|
495
|
+
breachType: breach.type || 'MATERIAL_BREACH',
|
|
496
|
+
severity: breach.severity || 'MODERATE'
|
|
497
|
+
}
|
|
498
|
+
},
|
|
499
|
+
escalateBreach: breach.severity === 'CRITICAL' ? {
|
|
500
|
+
endpoint: 'POST /legal/escalate-breach',
|
|
501
|
+
data: {
|
|
502
|
+
obligationId: token.id,
|
|
503
|
+
breachId: breachAssessment.id,
|
|
504
|
+
severity: breach.severity,
|
|
505
|
+
jurisdiction: token.credentialSubject.jurisdiction
|
|
506
|
+
}
|
|
507
|
+
} : null,
|
|
508
|
+
auditBreach: {
|
|
509
|
+
endpoint: 'POST /audit/obligation-breach',
|
|
510
|
+
data: {
|
|
511
|
+
obligationId: token.id,
|
|
512
|
+
breachId: breachAssessment.id,
|
|
513
|
+
obligor: token.credentialSubject.id,
|
|
514
|
+
assessedAt: breachAssessment.credentialSubject.assessmentDate,
|
|
515
|
+
severity: breach.severity || 'MODERATE'
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
} catch (error) {
|
|
522
|
+
return {
|
|
523
|
+
success: false,
|
|
524
|
+
error: error.message
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
},
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Prepare obligation monitoring report for external processing
|
|
531
|
+
* @param {Array} obligations - Array of obligation tokens
|
|
532
|
+
* @param {Object} criteria - Monitoring criteria
|
|
533
|
+
* @returns {Object} Prepared monitoring report data
|
|
534
|
+
*/
|
|
535
|
+
prepareObligationMonitoringReport: function(obligations, criteria) {
|
|
536
|
+
criteria = criteria || {}
|
|
537
|
+
|
|
538
|
+
$.checkArgument(Array.isArray(obligations), 'Obligations must be array')
|
|
539
|
+
|
|
540
|
+
try {
|
|
541
|
+
var report = {
|
|
542
|
+
reportId: 'rpt_' + this._generateUUID(),
|
|
543
|
+
generatedAt: new Date().toISOString(),
|
|
544
|
+
criteria: criteria,
|
|
545
|
+
summary: {
|
|
546
|
+
totalObligations: obligations.length,
|
|
547
|
+
byStatus: {},
|
|
548
|
+
byPriority: {},
|
|
549
|
+
byType: {},
|
|
550
|
+
overdue: 0,
|
|
551
|
+
dueSoon: 0,
|
|
552
|
+
completed: 0
|
|
553
|
+
},
|
|
554
|
+
obligations: [],
|
|
555
|
+
alerts: [],
|
|
556
|
+
recommendations: []
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
var now = new Date()
|
|
560
|
+
var sevenDaysFromNow = new Date(now.getTime() + (7 * 24 * 60 * 60 * 1000))
|
|
561
|
+
|
|
562
|
+
obligations.forEach(function(obligation) {
|
|
563
|
+
var obligationData = {
|
|
564
|
+
id: obligation.id,
|
|
565
|
+
type: obligation.credentialSubject.obligationType,
|
|
566
|
+
status: obligation.credentialSubject.status,
|
|
567
|
+
priority: obligation.credentialSubject.priority,
|
|
568
|
+
obligor: obligation.credentialSubject.id,
|
|
569
|
+
dueDate: obligation.credentialSubject.dueDate,
|
|
570
|
+
isOverdue: this._checkIfOverdue(obligation),
|
|
571
|
+
isDueSoon: false
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Check if due soon
|
|
575
|
+
if (obligation.credentialSubject.dueDate) {
|
|
576
|
+
var dueDate = new Date(obligation.credentialSubject.dueDate)
|
|
577
|
+
obligationData.isDueSoon = dueDate <= sevenDaysFromNow && dueDate > now
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Update summaries
|
|
581
|
+
report.summary.byStatus[obligationData.status] = (report.summary.byStatus[obligationData.status] || 0) + 1
|
|
582
|
+
report.summary.byPriority[obligationData.priority] = (report.summary.byPriority[obligationData.priority] || 0) + 1
|
|
583
|
+
report.summary.byType[obligationData.type] = (report.summary.byType[obligationData.type] || 0) + 1
|
|
584
|
+
|
|
585
|
+
if (obligationData.isOverdue) report.summary.overdue++
|
|
586
|
+
if (obligationData.isDueSoon) report.summary.dueSoon++
|
|
587
|
+
if (obligationData.status === ObligationStatus.COMPLETED) report.summary.completed++
|
|
588
|
+
|
|
589
|
+
// Generate alerts
|
|
590
|
+
if (obligationData.isOverdue && obligationData.priority === ObligationPriority.CRITICAL) {
|
|
591
|
+
report.alerts.push({
|
|
592
|
+
type: 'CRITICAL_OVERDUE',
|
|
593
|
+
obligationId: obligation.id,
|
|
594
|
+
message: 'Critical obligation is overdue',
|
|
595
|
+
priority: 'HIGH'
|
|
596
|
+
})
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
if (obligationData.isDueSoon && obligationData.priority === ObligationPriority.HIGH) {
|
|
600
|
+
report.alerts.push({
|
|
601
|
+
type: 'HIGH_PRIORITY_DUE_SOON',
|
|
602
|
+
obligationId: obligation.id,
|
|
603
|
+
message: 'High priority obligation due soon',
|
|
604
|
+
priority: 'MEDIUM'
|
|
605
|
+
})
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
report.obligations.push(obligationData)
|
|
609
|
+
}.bind(this))
|
|
610
|
+
|
|
611
|
+
// Generate recommendations
|
|
612
|
+
if (report.summary.overdue > 0) {
|
|
613
|
+
report.recommendations.push({
|
|
614
|
+
type: 'OVERDUE_MANAGEMENT',
|
|
615
|
+
message: 'Consider prioritizing overdue obligations',
|
|
616
|
+
count: report.summary.overdue
|
|
617
|
+
})
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
if (report.summary.dueSoon > 5) {
|
|
621
|
+
report.recommendations.push({
|
|
622
|
+
type: 'WORKLOAD_MANAGEMENT',
|
|
623
|
+
message: 'High number of obligations due soon - consider workload distribution',
|
|
624
|
+
count: report.summary.dueSoon
|
|
625
|
+
})
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
return {
|
|
629
|
+
success: true,
|
|
630
|
+
report: report,
|
|
631
|
+
externalOperations: {
|
|
632
|
+
storeReport: {
|
|
633
|
+
endpoint: 'POST /obligations/monitoring-reports',
|
|
634
|
+
data: {
|
|
635
|
+
report: report,
|
|
636
|
+
generatedAt: report.generatedAt
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
sendAlerts: report.alerts.length > 0 ? {
|
|
640
|
+
endpoint: 'POST /notifications/obligation-alerts',
|
|
641
|
+
data: {
|
|
642
|
+
reportId: report.reportId,
|
|
643
|
+
alerts: report.alerts,
|
|
644
|
+
summary: report.summary
|
|
645
|
+
}
|
|
646
|
+
} : null,
|
|
647
|
+
updateDashboard: {
|
|
648
|
+
endpoint: 'PUT /dashboard/obligation-metrics',
|
|
649
|
+
data: {
|
|
650
|
+
summary: report.summary,
|
|
651
|
+
lastUpdated: report.generatedAt
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
} catch (error) {
|
|
658
|
+
return {
|
|
659
|
+
success: false,
|
|
660
|
+
error: error.message
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
},
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Get available obligation types
|
|
667
|
+
* @returns {Object} Obligation types
|
|
668
|
+
*/
|
|
669
|
+
getObligationTypes: function() {
|
|
670
|
+
return ObligationTypes
|
|
671
|
+
},
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Get obligation priority levels
|
|
675
|
+
* @returns {Object} Priority levels
|
|
676
|
+
*/
|
|
677
|
+
getObligationPriority: function() {
|
|
678
|
+
return ObligationPriority
|
|
679
|
+
},
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Get obligation status values
|
|
683
|
+
* @returns {Object} Status values
|
|
684
|
+
*/
|
|
685
|
+
getObligationStatus: function() {
|
|
686
|
+
return ObligationStatus
|
|
687
|
+
},
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Sign a token with private key
|
|
691
|
+
* @private
|
|
692
|
+
*/
|
|
693
|
+
_signToken: function(token, privateKey) {
|
|
694
|
+
// Create canonical hash
|
|
695
|
+
var canonicalToken = this._canonicalizeToken(token)
|
|
696
|
+
var tokenHash = Hash.sha256(Buffer.from(canonicalToken))
|
|
697
|
+
|
|
698
|
+
// Sign with ECDSA
|
|
699
|
+
var ecdsa = new ECDSA()
|
|
700
|
+
ecdsa.hashbuf = tokenHash
|
|
701
|
+
ecdsa.privkey = privateKey
|
|
702
|
+
ecdsa.pubkey = privateKey.toPublicKey()
|
|
703
|
+
|
|
704
|
+
ecdsa.sign()
|
|
705
|
+
var signature = ecdsa.sig
|
|
706
|
+
|
|
707
|
+
// Create proof
|
|
708
|
+
var proof = {
|
|
709
|
+
type: 'EcdsaSecp256k1Signature2019',
|
|
710
|
+
created: new Date().toISOString(),
|
|
711
|
+
verificationMethod: 'did:smartledger:' + privateKey.toPublicKey().toString() + '#keys-1',
|
|
712
|
+
proofPurpose: 'assertionMethod',
|
|
713
|
+
jws: this._createJWS(tokenHash, signature)
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// Add proof to token
|
|
717
|
+
var signedToken = JSON.parse(JSON.stringify(token))
|
|
718
|
+
signedToken.proof = proof
|
|
719
|
+
signedToken.tokenHash = tokenHash.toString('hex')
|
|
720
|
+
|
|
721
|
+
return signedToken
|
|
722
|
+
},
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* Create JWS signature
|
|
726
|
+
* @private
|
|
727
|
+
*/
|
|
728
|
+
_createJWS: function(hash, signature) {
|
|
729
|
+
var header = {
|
|
730
|
+
alg: 'ES256K',
|
|
731
|
+
typ: 'JWT'
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
var headerB64 = Buffer.from(JSON.stringify(header)).toString('base64url')
|
|
735
|
+
var payloadB64 = hash.toString('base64url')
|
|
736
|
+
var signatureB64 = signature.toDER().toString('base64url')
|
|
737
|
+
|
|
738
|
+
return headerB64 + '..' + signatureB64
|
|
739
|
+
},
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Canonicalize token for hashing
|
|
743
|
+
* @private
|
|
744
|
+
*/
|
|
745
|
+
_canonicalizeToken: function(token) {
|
|
746
|
+
// Remove proof and hash fields
|
|
747
|
+
var canonical = JSON.parse(JSON.stringify(token))
|
|
748
|
+
delete canonical.proof
|
|
749
|
+
delete canonical.tokenHash
|
|
750
|
+
|
|
751
|
+
// Sort keys recursively
|
|
752
|
+
return JSON.stringify(this._sortObjectKeys(canonical))
|
|
753
|
+
},
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* Sort object keys recursively
|
|
757
|
+
* @private
|
|
758
|
+
*/
|
|
759
|
+
_sortObjectKeys: function(obj) {
|
|
760
|
+
if (Array.isArray(obj)) {
|
|
761
|
+
return obj.map(this._sortObjectKeys.bind(this))
|
|
762
|
+
} else if (obj !== null && typeof obj === 'object') {
|
|
763
|
+
var sorted = {}
|
|
764
|
+
Object.keys(obj).sort().forEach(function(key) {
|
|
765
|
+
sorted[key] = this._sortObjectKeys(obj[key])
|
|
766
|
+
}.bind(this))
|
|
767
|
+
return sorted
|
|
768
|
+
}
|
|
769
|
+
return obj
|
|
770
|
+
},
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* Validate token structure
|
|
774
|
+
* @private
|
|
775
|
+
*/
|
|
776
|
+
_validateTokenStructure: function(token, errors) {
|
|
777
|
+
if (!token['@context']) {
|
|
778
|
+
errors.push('Missing @context')
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
if (!token.id) {
|
|
782
|
+
errors.push('Missing id')
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
if (!token.type || !Array.isArray(token.type)) {
|
|
786
|
+
errors.push('Missing or invalid type')
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
if (!token.issuer) {
|
|
790
|
+
errors.push('Missing issuer')
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
if (!token.issuanceDate) {
|
|
794
|
+
errors.push('Missing issuanceDate')
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
if (!token.credentialSubject) {
|
|
798
|
+
errors.push('Missing credentialSubject')
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
if (!token.proof) {
|
|
802
|
+
errors.push('Missing proof')
|
|
803
|
+
}
|
|
804
|
+
},
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* Verify token signature
|
|
808
|
+
* @private
|
|
809
|
+
*/
|
|
810
|
+
_verifyTokenSignature: function(token, errors) {
|
|
811
|
+
try {
|
|
812
|
+
if (!token.proof || !token.proof.jws) {
|
|
813
|
+
errors.push('Missing signature proof')
|
|
814
|
+
return
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// Recreate canonical hash
|
|
818
|
+
var canonicalToken = this._canonicalizeToken(token)
|
|
819
|
+
var expectedHash = Hash.sha256(Buffer.from(canonicalToken))
|
|
820
|
+
|
|
821
|
+
// Compare with stored hash if available
|
|
822
|
+
if (token.tokenHash && token.tokenHash !== expectedHash.toString('hex')) {
|
|
823
|
+
errors.push('Token hash mismatch')
|
|
824
|
+
return
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// TODO: Verify JWS signature with issuer's public key
|
|
828
|
+
// This would require DID resolution to get the public key
|
|
829
|
+
|
|
830
|
+
} catch (error) {
|
|
831
|
+
errors.push('Signature verification failed: ' + error.message)
|
|
832
|
+
}
|
|
833
|
+
},
|
|
834
|
+
|
|
835
|
+
/**
|
|
836
|
+
* Validate temporal aspects
|
|
837
|
+
* @private
|
|
838
|
+
*/
|
|
839
|
+
_validateTokenTemporal: function(token, errors, warnings) {
|
|
840
|
+
var now = new Date()
|
|
841
|
+
var issuanceDate = new Date(token.issuanceDate)
|
|
842
|
+
|
|
843
|
+
if (issuanceDate > now) {
|
|
844
|
+
errors.push('Token issued in the future')
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
if (token.credentialSubject.effectiveDate) {
|
|
848
|
+
var effectiveDate = new Date(token.credentialSubject.effectiveDate)
|
|
849
|
+
if (now < effectiveDate) {
|
|
850
|
+
warnings.push('Obligation not yet effective')
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
if (token.credentialSubject.expirationDate) {
|
|
855
|
+
var expirationDate = new Date(token.credentialSubject.expirationDate)
|
|
856
|
+
if (now > expirationDate) {
|
|
857
|
+
errors.push('Obligation expired')
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
},
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* Validate obligation type
|
|
864
|
+
* @private
|
|
865
|
+
*/
|
|
866
|
+
_validateObligationType: function(token, errors) {
|
|
867
|
+
if (!token.credentialSubject.obligationType) {
|
|
868
|
+
errors.push('Missing obligationType')
|
|
869
|
+
return
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
var validTypes = Object.values(ObligationTypes)
|
|
873
|
+
if (!validTypes.includes(token.credentialSubject.obligationType)) {
|
|
874
|
+
errors.push('Invalid obligation type: ' + token.credentialSubject.obligationType)
|
|
875
|
+
}
|
|
876
|
+
},
|
|
877
|
+
|
|
878
|
+
/**
|
|
879
|
+
* Validate obligation status
|
|
880
|
+
* @private
|
|
881
|
+
*/
|
|
882
|
+
_validateObligationStatus: function(token, errors, warnings) {
|
|
883
|
+
if (!token.credentialSubject.status) {
|
|
884
|
+
errors.push('Missing status')
|
|
885
|
+
return
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
var validStatuses = Object.values(ObligationStatus)
|
|
889
|
+
if (!validStatuses.includes(token.credentialSubject.status)) {
|
|
890
|
+
errors.push('Invalid obligation status: ' + token.credentialSubject.status)
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// Check for overdue obligations
|
|
894
|
+
if (this._checkIfOverdue(token) && token.credentialSubject.status === ObligationStatus.ACTIVE) {
|
|
895
|
+
warnings.push('Obligation is overdue but status is still ACTIVE')
|
|
896
|
+
}
|
|
897
|
+
},
|
|
898
|
+
|
|
899
|
+
/**
|
|
900
|
+
* Validate obligation priority
|
|
901
|
+
* @private
|
|
902
|
+
*/
|
|
903
|
+
_validateObligationPriority: function(token, warnings) {
|
|
904
|
+
if (token.credentialSubject.priority) {
|
|
905
|
+
var validPriorities = Object.values(ObligationPriority)
|
|
906
|
+
if (!validPriorities.includes(token.credentialSubject.priority)) {
|
|
907
|
+
warnings.push('Invalid obligation priority: ' + token.credentialSubject.priority)
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
},
|
|
911
|
+
|
|
912
|
+
/**
|
|
913
|
+
* Check if obligation is overdue
|
|
914
|
+
* @private
|
|
915
|
+
*/
|
|
916
|
+
_checkIfOverdue: function(token) {
|
|
917
|
+
if (!token.credentialSubject.dueDate) {
|
|
918
|
+
return false
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
var now = new Date()
|
|
922
|
+
var dueDate = new Date(token.credentialSubject.dueDate)
|
|
923
|
+
|
|
924
|
+
return now > dueDate && token.credentialSubject.status !== ObligationStatus.COMPLETED
|
|
925
|
+
},
|
|
926
|
+
|
|
927
|
+
/**
|
|
928
|
+
* Generate UUID
|
|
929
|
+
* @private
|
|
930
|
+
*/
|
|
931
|
+
_generateUUID: function() {
|
|
932
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
933
|
+
var r = Math.random() * 16 | 0
|
|
934
|
+
var v = c === 'x' ? r : (r & 0x3 | 0x8)
|
|
935
|
+
return v.toString(16)
|
|
936
|
+
})
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// Export static references for compatibility
|
|
941
|
+
ObligationToken.ObligationTypes = ObligationTypes
|
|
942
|
+
ObligationToken.ObligationPriority = ObligationPriority
|
|
943
|
+
ObligationToken.ObligationStatus = ObligationStatus
|
|
944
|
+
|
|
945
|
+
module.exports = ObligationToken
|