@learncard/types 5.19.0 → 5.21.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.
- package/dist/auth.d.ts +35 -2
- package/dist/auth.d.ts.map +1 -1
- package/dist/clr.d.ts +8 -8
- package/dist/credential-refresh.d.ts +65 -12
- package/dist/credential-refresh.d.ts.map +1 -1
- package/dist/did.d.ts +2 -2
- package/dist/lcn.d.ts +5218 -2351
- package/dist/lcn.d.ts.map +1 -1
- package/dist/obv3.d.ts +8 -8
- package/dist/types.cjs.development.cjs +417 -201
- package/dist/types.cjs.development.cjs.map +3 -3
- package/dist/types.cjs.production.min.cjs +23 -23
- package/dist/types.cjs.production.min.cjs.map +4 -4
- package/dist/types.esm.js +1261 -1045
- package/dist/types.esm.js.map +3 -3
- package/dist/vc.d.ts +13 -13
- package/dist/vc.d.ts.map +1 -1
- package/dist/vc.test.d.ts +2 -0
- package/dist/vc.test.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/auth.ts +55 -4
- package/src/credential-refresh.ts +35 -1
- package/src/lcn.ts +283 -18
- package/src/vc.test.ts +35 -0
- package/src/vc.ts +14 -2
package/src/lcn.ts
CHANGED
|
@@ -4,6 +4,11 @@ import { z } from 'zod/v4';
|
|
|
4
4
|
import { PaginationResponseValidator } from './mongo';
|
|
5
5
|
import { StringQuery } from './queries';
|
|
6
6
|
import { UnsignedVCValidator, VCValidator, VPValidator } from './vc';
|
|
7
|
+
import {
|
|
8
|
+
ManagedCredentialRefreshReceiptValidator,
|
|
9
|
+
InboxCredentialRefreshReceiptValidator,
|
|
10
|
+
ManagedCredentialRefreshServiceValidator,
|
|
11
|
+
} from './credential-refresh';
|
|
7
12
|
|
|
8
13
|
export const LCNProfileDisplayValidator = z.object({
|
|
9
14
|
backgroundColor: z.string().optional(),
|
|
@@ -466,7 +471,7 @@ export const AutoBoostConfigValidator = z.object({
|
|
|
466
471
|
});
|
|
467
472
|
export type AutoBoostConfig = z.infer<typeof AutoBoostConfigValidator>;
|
|
468
473
|
|
|
469
|
-
const SendBoostTemplateValidator = BoostValidator.partial()
|
|
474
|
+
export const SendBoostTemplateValidator = BoostValidator.partial()
|
|
470
475
|
.omit({ uri: true, claimPermissions: true })
|
|
471
476
|
.extend({
|
|
472
477
|
credential: VCValidator.or(UnsignedVCValidator),
|
|
@@ -538,6 +543,20 @@ export const SendBoostInputValidator = z
|
|
|
538
543
|
),
|
|
539
544
|
templateData: z.record(z.string(), z.unknown()).optional(),
|
|
540
545
|
integrationId: z.string().optional().describe('Integration ID for activity tracking'),
|
|
546
|
+
refresh: z
|
|
547
|
+
.boolean()
|
|
548
|
+
.optional()
|
|
549
|
+
.describe(
|
|
550
|
+
'Request managed credential refresh for this send. Profile/DID recipients use immediate issuance; email/phone recipients use deferred Universal Inbox signing and bind the holder at claim.'
|
|
551
|
+
),
|
|
552
|
+
idempotencyKey: z
|
|
553
|
+
.string()
|
|
554
|
+
.min(1)
|
|
555
|
+
.max(200)
|
|
556
|
+
.optional()
|
|
557
|
+
.describe(
|
|
558
|
+
'Caller-chosen key that makes a managed refresh send (refresh: true) safe to retry as a whole: retries with the same key reuse the same boost, refresh allocation and result. Reusing a key for a different request is rejected. With signedCredential, requires prior tRPC prepareRefreshableSend; direct REST callers omit the key and retry the exact signed credential and templateUri.'
|
|
559
|
+
),
|
|
541
560
|
})
|
|
542
561
|
.refine(data => data.templateUri || data.template || data.signedCredential, {
|
|
543
562
|
message: 'Either templateUri, template, or signedCredential must be provided.',
|
|
@@ -554,11 +573,16 @@ export const SendBoostInputValidator = z
|
|
|
554
573
|
message: 'guardianEmail must differ from recipient (self-approval not allowed)',
|
|
555
574
|
path: ['options', 'guardianEmail'],
|
|
556
575
|
}
|
|
557
|
-
)
|
|
576
|
+
)
|
|
577
|
+
.refine(data => !data.idempotencyKey || data.refresh === true, {
|
|
578
|
+
message: 'idempotencyKey is only supported with refresh: true.',
|
|
579
|
+
path: ['idempotencyKey'],
|
|
580
|
+
});
|
|
558
581
|
export type SendBoostInput = z.infer<typeof SendBoostInputValidator>;
|
|
559
582
|
|
|
560
583
|
// Inbox-specific response fields (only present when sent via email/phone)
|
|
561
584
|
export const SendInboxResponseValidator = z.object({
|
|
585
|
+
refresh: InboxCredentialRefreshReceiptValidator.optional(),
|
|
562
586
|
issuanceId: z.string(),
|
|
563
587
|
status: z.enum(['PENDING', 'ISSUED', 'EXPIRED', 'DELIVERED', 'CLAIMED']),
|
|
564
588
|
claimUrl: z.string().url().optional().describe('Present when suppressDelivery=true'),
|
|
@@ -576,9 +600,41 @@ export const SendBoostResponseValidator = z.object({
|
|
|
576
600
|
inbox: SendInboxResponseValidator.optional().describe(
|
|
577
601
|
'Present when sent via email/phone (Universal Inbox)'
|
|
578
602
|
),
|
|
603
|
+
refresh: ManagedCredentialRefreshReceiptValidator.optional().describe(
|
|
604
|
+
'Present when managed refresh was requested: issuance metadata the issuer keeps to publish future updates'
|
|
605
|
+
),
|
|
579
606
|
});
|
|
580
607
|
export type SendBoostResponse = z.infer<typeof SendBoostResponseValidator>;
|
|
581
608
|
|
|
609
|
+
export const PrepareRefreshableSendInputValidator = z
|
|
610
|
+
.object({
|
|
611
|
+
recipient: z.string(),
|
|
612
|
+
templateUri: z.string().optional(),
|
|
613
|
+
template: SendBoostTemplateValidator.optional(),
|
|
614
|
+
contractUri: z.string().optional(),
|
|
615
|
+
templateData: z.record(z.string(), z.unknown()).optional(),
|
|
616
|
+
integrationId: z.string().optional(),
|
|
617
|
+
/** Credential ID to allocate for; generated server-side when omitted. */
|
|
618
|
+
credentialId: z.string().min(1).optional(),
|
|
619
|
+
idempotencyKey: z.string().min(1).max(200).optional(),
|
|
620
|
+
})
|
|
621
|
+
.refine(data => Boolean(data.templateUri) !== Boolean(data.template), {
|
|
622
|
+
message: 'Provide exactly one of templateUri or template.',
|
|
623
|
+
});
|
|
624
|
+
export type PrepareRefreshableSendInput = z.infer<typeof PrepareRefreshableSendInputValidator>;
|
|
625
|
+
|
|
626
|
+
export const PrepareRefreshableSendResultValidator = z.object({
|
|
627
|
+
boostUri: z.string(),
|
|
628
|
+
credentialId: z.string(),
|
|
629
|
+
refreshId: z.string(),
|
|
630
|
+
refreshService: ManagedCredentialRefreshServiceValidator,
|
|
631
|
+
/** The DID to use as credentialSubject.id (recipient DID, or the profile's did:web). */
|
|
632
|
+
holderDid: z.string(),
|
|
633
|
+
/** Present when this idempotencyKey already completed: return it without signing. */
|
|
634
|
+
completed: SendBoostResponseValidator.optional(),
|
|
635
|
+
});
|
|
636
|
+
export type PrepareRefreshableSendResult = z.infer<typeof PrepareRefreshableSendResultValidator>;
|
|
637
|
+
|
|
582
638
|
// Plugin-level discriminated union (for extensibility)
|
|
583
639
|
export const SendInputValidator = z.discriminatedUnion('type', [SendBoostInputValidator]);
|
|
584
640
|
export type SendInput = z.infer<typeof SendInputValidator>;
|
|
@@ -707,21 +763,6 @@ export const PaginatedConsentFlowDataValidator = PaginationResponseValidator.ext
|
|
|
707
763
|
});
|
|
708
764
|
export type PaginatedConsentFlowData = z.infer<typeof PaginatedConsentFlowDataValidator>;
|
|
709
765
|
|
|
710
|
-
export const ConsentFlowContractDataForDidValidator = z.object({
|
|
711
|
-
credentials: z.object({ category: z.string(), uri: z.string() }).array(),
|
|
712
|
-
personal: z.record(z.string(), z.string()).default({}),
|
|
713
|
-
date: z.string(),
|
|
714
|
-
contractUri: z.string(),
|
|
715
|
-
});
|
|
716
|
-
export type ConsentFlowContractDataForDid = z.infer<typeof ConsentFlowContractDataForDidValidator>;
|
|
717
|
-
|
|
718
|
-
export const PaginatedConsentFlowDataForDidValidator = PaginationResponseValidator.extend({
|
|
719
|
-
records: ConsentFlowContractDataForDidValidator.array(),
|
|
720
|
-
});
|
|
721
|
-
export type PaginatedConsentFlowDataForDid = z.infer<
|
|
722
|
-
typeof PaginatedConsentFlowDataForDidValidator
|
|
723
|
-
>;
|
|
724
|
-
|
|
725
766
|
export const ConsentFlowTermValidator = z.object({
|
|
726
767
|
sharing: z.boolean().optional(),
|
|
727
768
|
shared: z.string().array().optional(),
|
|
@@ -771,6 +812,42 @@ export const PaginatedConsentFlowTermsValidator = PaginationResponseValidator.ex
|
|
|
771
812
|
});
|
|
772
813
|
export type PaginatedConsentFlowTerms = z.infer<typeof PaginatedConsentFlowTermsValidator>;
|
|
773
814
|
|
|
815
|
+
export const ConsentFlowGuardianApprovalValidator = z.object({
|
|
816
|
+
guardianProfileId: z.string(),
|
|
817
|
+
guardianDid: z.string(),
|
|
818
|
+
approvedAt: z.string().datetime(),
|
|
819
|
+
contractUpdatedAt: z.string(),
|
|
820
|
+
});
|
|
821
|
+
export type ConsentFlowGuardianApproval = z.infer<typeof ConsentFlowGuardianApprovalValidator>;
|
|
822
|
+
|
|
823
|
+
export const ConsentFlowContractDataForDidValidator = z.object({
|
|
824
|
+
credentials: z.object({ category: z.string(), uri: z.string() }).array(),
|
|
825
|
+
personal: z.record(z.string(), z.string()).default({}),
|
|
826
|
+
date: z.string(),
|
|
827
|
+
createdAt: z.string().optional(),
|
|
828
|
+
contractUpdatedAt: z.string(),
|
|
829
|
+
contractExpiresAt: z.string().optional(),
|
|
830
|
+
reasonForAccessing: z.string().optional(),
|
|
831
|
+
guardian: z.object({
|
|
832
|
+
required: z.boolean(),
|
|
833
|
+
approved: z.boolean(),
|
|
834
|
+
approval: ConsentFlowGuardianApprovalValidator.optional(),
|
|
835
|
+
}),
|
|
836
|
+
contractUri: z.string(),
|
|
837
|
+
termsUri: z.string(),
|
|
838
|
+
status: ConsentFlowTermsStatusValidator,
|
|
839
|
+
expiresAt: z.string().optional(),
|
|
840
|
+
terms: ConsentFlowTermsValidator,
|
|
841
|
+
});
|
|
842
|
+
export type ConsentFlowContractDataForDid = z.infer<typeof ConsentFlowContractDataForDidValidator>;
|
|
843
|
+
|
|
844
|
+
export const PaginatedConsentFlowDataForDidValidator = PaginationResponseValidator.extend({
|
|
845
|
+
records: ConsentFlowContractDataForDidValidator.array(),
|
|
846
|
+
});
|
|
847
|
+
export type PaginatedConsentFlowDataForDid = z.infer<
|
|
848
|
+
typeof PaginatedConsentFlowDataForDidValidator
|
|
849
|
+
>;
|
|
850
|
+
|
|
774
851
|
export const ConsentFlowContractQueryValidator = z.object({
|
|
775
852
|
read: z
|
|
776
853
|
.object({
|
|
@@ -884,6 +961,7 @@ export const ConsentFlowTransactionValidator = z.object({
|
|
|
884
961
|
expiresAt: z.string().optional(),
|
|
885
962
|
oneTime: z.boolean().optional(),
|
|
886
963
|
terms: ConsentFlowTermsValidator.optional(),
|
|
964
|
+
guardianApproval: ConsentFlowGuardianApprovalValidator.optional(),
|
|
887
965
|
id: z.string(),
|
|
888
966
|
action: ConsentFlowTransactionActionValidator,
|
|
889
967
|
date: z.string(),
|
|
@@ -1052,6 +1130,7 @@ export const LCNNotificationValidator = z.object({
|
|
|
1052
1130
|
export type LCNNotification = z.infer<typeof LCNNotificationValidator>;
|
|
1053
1131
|
|
|
1054
1132
|
export const AUTH_GRANT_AUDIENCE_DOMAIN_PREFIX = 'auth-grant:';
|
|
1133
|
+
export const ACT_AS_HEADER = 'X-LearnCard-Act-As';
|
|
1055
1134
|
|
|
1056
1135
|
export const AuthGrantValidator = z.object({
|
|
1057
1136
|
id: z.string(),
|
|
@@ -1069,6 +1148,7 @@ export const AuthGrantValidator = z.object({
|
|
|
1069
1148
|
},
|
|
1070
1149
|
}),
|
|
1071
1150
|
scope: z.string(),
|
|
1151
|
+
actAs: z.string().optional(),
|
|
1072
1152
|
createdAt: z.iso.datetime({ error: 'createdAt must be a valid ISO 8601 datetime string' }),
|
|
1073
1153
|
expiresAt: z.iso
|
|
1074
1154
|
.datetime({ error: 'expiresAt must be a valid ISO 8601 datetime string' })
|
|
@@ -1195,6 +1275,8 @@ export type CreateContactMethodSessionResponseType = z.infer<
|
|
|
1195
1275
|
|
|
1196
1276
|
// Inbox Credentials
|
|
1197
1277
|
export const InboxCredentialValidator = z.object({
|
|
1278
|
+
refresh: InboxCredentialRefreshReceiptValidator.optional(),
|
|
1279
|
+
refreshId: z.string().optional(),
|
|
1198
1280
|
id: z.string(),
|
|
1199
1281
|
credential: z.string().optional(),
|
|
1200
1282
|
isSigned: z.boolean(),
|
|
@@ -1274,6 +1356,13 @@ export const IssueInboxCredentialValidator = z
|
|
|
1274
1356
|
'URI of a boost template to use for issuance. The boost credential will be resolved and used. Mutually exclusive with credential field.'
|
|
1275
1357
|
),
|
|
1276
1358
|
|
|
1359
|
+
refresh: z
|
|
1360
|
+
.boolean()
|
|
1361
|
+
.optional()
|
|
1362
|
+
.describe(
|
|
1363
|
+
'Allocate managed refresh before signing. Requires unsigned content and a registered signing authority; binds the holder on claim.'
|
|
1364
|
+
),
|
|
1365
|
+
idempotencyKey: z.string().min(1).max(200).optional(),
|
|
1277
1366
|
// === OPTIONAL FEATURES ===
|
|
1278
1367
|
// Add major, distinct features at the top level.
|
|
1279
1368
|
//consentRequest: ConsentRequestValidator.optional(),
|
|
@@ -1282,6 +1371,13 @@ export const IssueInboxCredentialValidator = z
|
|
|
1282
1371
|
// HOW should this issuance be handled?
|
|
1283
1372
|
configuration: z
|
|
1284
1373
|
.object({
|
|
1374
|
+
guardianEmail: z
|
|
1375
|
+
.string()
|
|
1376
|
+
.email()
|
|
1377
|
+
.optional()
|
|
1378
|
+
.describe(
|
|
1379
|
+
'Require approval from this guardian before the recipient can claim. Must differ from the recipient email.'
|
|
1380
|
+
),
|
|
1285
1381
|
signingAuthority: IssueInboxSigningAuthorityValidator.optional().describe(
|
|
1286
1382
|
'The signing authority to use for the credential. If not provided, the users default signing authority will be used if the credential is not signed.'
|
|
1287
1383
|
),
|
|
@@ -1388,14 +1484,28 @@ export const IssueInboxCredentialValidator = z
|
|
|
1388
1484
|
'Configuration for the credential issuance. If not provided, the default configuration will be used.'
|
|
1389
1485
|
),
|
|
1390
1486
|
})
|
|
1487
|
+
.refine(data => !data.idempotencyKey || data.refresh === true, {
|
|
1488
|
+
message: 'idempotencyKey requires refresh: true.',
|
|
1489
|
+
})
|
|
1391
1490
|
.refine(data => data.credential || data.templateUri, {
|
|
1392
1491
|
message: 'Either credential or templateUri must be provided.',
|
|
1393
1492
|
path: ['credential'],
|
|
1394
|
-
})
|
|
1493
|
+
})
|
|
1494
|
+
.refine(
|
|
1495
|
+
data =>
|
|
1496
|
+
!data.configuration?.guardianEmail ||
|
|
1497
|
+
data.recipient.type !== 'email' ||
|
|
1498
|
+
data.configuration.guardianEmail.toLowerCase() !== data.recipient.value.toLowerCase(),
|
|
1499
|
+
{
|
|
1500
|
+
message: 'guardianEmail must differ from recipient (self-approval not allowed)',
|
|
1501
|
+
path: ['configuration', 'guardianEmail'],
|
|
1502
|
+
}
|
|
1503
|
+
);
|
|
1395
1504
|
|
|
1396
1505
|
export type IssueInboxCredentialType = z.infer<typeof IssueInboxCredentialValidator>;
|
|
1397
1506
|
|
|
1398
1507
|
export const IssueInboxCredentialResponseValidator = z.object({
|
|
1508
|
+
refresh: InboxCredentialRefreshReceiptValidator.optional(),
|
|
1399
1509
|
issuanceId: z.string(),
|
|
1400
1510
|
status: LCNInboxStatusEnumValidator,
|
|
1401
1511
|
recipient: ContactMethodQueryValidator,
|
|
@@ -1403,6 +1513,160 @@ export const IssueInboxCredentialResponseValidator = z.object({
|
|
|
1403
1513
|
recipientDid: z.string().optional(),
|
|
1404
1514
|
});
|
|
1405
1515
|
|
|
1516
|
+
// Do not apply delivery defaults before merging: omitted per-item fields inherit batch defaults.
|
|
1517
|
+
const InboxBatchConfigurationValidator = IssueInboxCredentialValidator.shape.configuration
|
|
1518
|
+
.unwrap()
|
|
1519
|
+
.extend({
|
|
1520
|
+
refresh: IssueInboxCredentialValidator.shape.refresh.describe(
|
|
1521
|
+
'Enable managed refresh by default. An item configuration.refresh overrides this value, including false.'
|
|
1522
|
+
),
|
|
1523
|
+
delivery: IssueInboxCredentialValidator.shape.configuration
|
|
1524
|
+
.unwrap()
|
|
1525
|
+
.shape.delivery.unwrap()
|
|
1526
|
+
.extend({ suppress: z.boolean().optional() })
|
|
1527
|
+
.optional(),
|
|
1528
|
+
});
|
|
1529
|
+
|
|
1530
|
+
const InboxBatchItemConfigurationValidator = InboxBatchConfigurationValidator.extend({
|
|
1531
|
+
guardianEmail: InboxBatchConfigurationValidator.shape.guardianEmail
|
|
1532
|
+
.nullable()
|
|
1533
|
+
.describe(
|
|
1534
|
+
'Require guardian approval, or set null to clear a batch-level guardianEmail for this item.'
|
|
1535
|
+
),
|
|
1536
|
+
});
|
|
1537
|
+
|
|
1538
|
+
export const IssueInboxCredentialBatchItemValidator = z
|
|
1539
|
+
.object({
|
|
1540
|
+
...IssueInboxCredentialValidator.shape,
|
|
1541
|
+
configuration: InboxBatchItemConfigurationValidator.optional(),
|
|
1542
|
+
idempotencyKey: z.string().max(256).optional(),
|
|
1543
|
+
})
|
|
1544
|
+
.refine(data => data.credential || data.templateUri, {
|
|
1545
|
+
message: 'Either credential or templateUri must be provided.',
|
|
1546
|
+
path: ['credential'],
|
|
1547
|
+
})
|
|
1548
|
+
.describe(
|
|
1549
|
+
'One issuance: provide credential or templateUri. Invalid input is rejected at submission with its item index.'
|
|
1550
|
+
);
|
|
1551
|
+
|
|
1552
|
+
export const IssueInboxCredentialBatchValidator = z
|
|
1553
|
+
.object({
|
|
1554
|
+
requestId: z.string().min(1).max(256).optional(),
|
|
1555
|
+
items: z.array(IssueInboxCredentialBatchItemValidator).min(1).max(100),
|
|
1556
|
+
configuration: InboxBatchConfigurationValidator.optional(),
|
|
1557
|
+
})
|
|
1558
|
+
.superRefine((batch, ctx) => {
|
|
1559
|
+
batch.items.forEach((item, index) => {
|
|
1560
|
+
const itemGuardian = item.configuration?.guardianEmail;
|
|
1561
|
+
const guardian =
|
|
1562
|
+
itemGuardian === null
|
|
1563
|
+
? undefined
|
|
1564
|
+
: (itemGuardian ?? batch.configuration?.guardianEmail);
|
|
1565
|
+
if (
|
|
1566
|
+
guardian &&
|
|
1567
|
+
item.recipient.type === 'email' &&
|
|
1568
|
+
guardian.toLowerCase() === item.recipient.value.toLowerCase()
|
|
1569
|
+
) {
|
|
1570
|
+
ctx.addIssue({
|
|
1571
|
+
code: 'custom',
|
|
1572
|
+
path: ['items', index, 'configuration', 'guardianEmail'],
|
|
1573
|
+
message: 'guardianEmail must differ from recipient (self-approval not allowed)',
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1576
|
+
});
|
|
1577
|
+
});
|
|
1578
|
+
export type IssueInboxCredentialBatch = z.infer<typeof IssueInboxCredentialBatchValidator>;
|
|
1579
|
+
|
|
1580
|
+
export const InboxBatchErrorReasonValidator = z.enum([
|
|
1581
|
+
'DUPLICATE_KEY',
|
|
1582
|
+
'IDEMPOTENCY_MISMATCH',
|
|
1583
|
+
'IN_PROGRESS',
|
|
1584
|
+
'UNCONFIRMED',
|
|
1585
|
+
]);
|
|
1586
|
+
export type InboxBatchErrorReason = z.infer<typeof InboxBatchErrorReasonValidator>;
|
|
1587
|
+
|
|
1588
|
+
export const IssueInboxCredentialBatchItemResultValidator = z.discriminatedUnion('success', [
|
|
1589
|
+
IssueInboxCredentialResponseValidator.extend({
|
|
1590
|
+
success: z.literal(true),
|
|
1591
|
+
index: z.number().int().nonnegative(),
|
|
1592
|
+
deduplicated: z.boolean().optional(),
|
|
1593
|
+
guardianStatus: GuardianStatusValidator.optional(),
|
|
1594
|
+
idempotencyKey: z.string().optional(),
|
|
1595
|
+
}),
|
|
1596
|
+
z.object({
|
|
1597
|
+
success: z.literal(false),
|
|
1598
|
+
index: z.number().int().nonnegative(),
|
|
1599
|
+
idempotencyKey: z.string().optional(),
|
|
1600
|
+
recipient: ContactMethodQueryValidator.optional(),
|
|
1601
|
+
error: z.object({
|
|
1602
|
+
code: z.string(),
|
|
1603
|
+
message: z.string(),
|
|
1604
|
+
reason: InboxBatchErrorReasonValidator.optional(),
|
|
1605
|
+
}),
|
|
1606
|
+
issuanceId: z
|
|
1607
|
+
.string()
|
|
1608
|
+
.optional()
|
|
1609
|
+
.describe(
|
|
1610
|
+
'Present when issuance completed but replay storage could not be confirmed. Reconcile this issuance; do not issue again with a new key.'
|
|
1611
|
+
),
|
|
1612
|
+
claimUrl: z
|
|
1613
|
+
.string()
|
|
1614
|
+
.url()
|
|
1615
|
+
.optional()
|
|
1616
|
+
.describe(
|
|
1617
|
+
'Claim URL of the completed issuance, if available, when replay storage could not be confirmed.'
|
|
1618
|
+
),
|
|
1619
|
+
}),
|
|
1620
|
+
]);
|
|
1621
|
+
export type IssueInboxCredentialBatchItemResult = z.infer<
|
|
1622
|
+
typeof IssueInboxCredentialBatchItemResultValidator
|
|
1623
|
+
>;
|
|
1624
|
+
|
|
1625
|
+
export const IssueInboxCredentialBatchResponseValidator = z.object({
|
|
1626
|
+
results: z.array(IssueInboxCredentialBatchItemResultValidator),
|
|
1627
|
+
summary: z.object({
|
|
1628
|
+
total: z.number(),
|
|
1629
|
+
succeeded: z.number(),
|
|
1630
|
+
failed: z.number(),
|
|
1631
|
+
deduplicated: z.number(),
|
|
1632
|
+
}),
|
|
1633
|
+
});
|
|
1634
|
+
export type IssueInboxCredentialBatchResponse = z.infer<
|
|
1635
|
+
typeof IssueInboxCredentialBatchResponseValidator
|
|
1636
|
+
>;
|
|
1637
|
+
|
|
1638
|
+
/** Submission acknowledges durable storage, not completed credential delivery. */
|
|
1639
|
+
export const InboxBatchReceiptValidator = z.object({
|
|
1640
|
+
batchId: z.string(),
|
|
1641
|
+
status: z.enum(['QUEUED', 'PROCESSING', 'COMPLETED', 'NEEDS_RECONCILIATION']),
|
|
1642
|
+
createdAt: z.string(),
|
|
1643
|
+
});
|
|
1644
|
+
export type InboxBatchReceipt = z.infer<typeof InboxBatchReceiptValidator>;
|
|
1645
|
+
|
|
1646
|
+
export const InboxBatchStatusValidator = z.object({
|
|
1647
|
+
batchId: z.string(),
|
|
1648
|
+
createdAt: z.string(),
|
|
1649
|
+
done: z
|
|
1650
|
+
.boolean()
|
|
1651
|
+
.describe(
|
|
1652
|
+
'True when no queued or processing items remain, including unconfirmed outcomes.'
|
|
1653
|
+
),
|
|
1654
|
+
status: z.enum(['QUEUED', 'PROCESSING', 'COMPLETED', 'NEEDS_RECONCILIATION']),
|
|
1655
|
+
items: z.array(
|
|
1656
|
+
z.object({
|
|
1657
|
+
index: z.number().int().nonnegative(),
|
|
1658
|
+
state: z.enum(['QUEUED', 'PROCESSING', 'COMPLETED', 'NEEDS_RECONCILIATION']),
|
|
1659
|
+
result: IssueInboxCredentialBatchItemResultValidator.optional(),
|
|
1660
|
+
})
|
|
1661
|
+
),
|
|
1662
|
+
summary: IssueInboxCredentialBatchResponseValidator.shape.summary.extend({
|
|
1663
|
+
completed: z.number(),
|
|
1664
|
+
pending: z.number(),
|
|
1665
|
+
unconfirmed: z.number(),
|
|
1666
|
+
}),
|
|
1667
|
+
});
|
|
1668
|
+
export type InboxBatchStatus = z.infer<typeof InboxBatchStatusValidator>;
|
|
1669
|
+
|
|
1406
1670
|
export type IssueInboxCredentialResponseType = z.infer<
|
|
1407
1671
|
typeof IssueInboxCredentialResponseValidator
|
|
1408
1672
|
>;
|
|
@@ -2223,6 +2487,7 @@ export const CredentialActivityValidator = z.object({
|
|
|
2223
2487
|
eventType: CredentialActivityEventTypeValidator,
|
|
2224
2488
|
timestamp: z.string(),
|
|
2225
2489
|
actorProfileId: z.string().optional(),
|
|
2490
|
+
onBehalfOf: z.string().optional(),
|
|
2226
2491
|
recipientType: CredentialActivityRecipientTypeValidator,
|
|
2227
2492
|
recipientIdentifier: z.string(),
|
|
2228
2493
|
boostUri: z.string().optional(),
|
package/src/vc.test.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { expect, test } from 'vitest';
|
|
2
|
+
import { ProofValidator } from './vc';
|
|
3
|
+
|
|
4
|
+
test('only final ecdsa-rdfc-2019 DataIntegrityProofs may omit created', () => {
|
|
5
|
+
const proof = {
|
|
6
|
+
type: 'DataIntegrityProof',
|
|
7
|
+
cryptosuite: 'ecdsa-rdfc-2019',
|
|
8
|
+
proofPurpose: 'authentication',
|
|
9
|
+
verificationMethod: 'did:example:holder#key',
|
|
10
|
+
proofValue: 'zSignature',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
expect(ProofValidator.parse(proof)).toEqual(proof);
|
|
14
|
+
const legacy = { ...proof, cryptosuite: 'eddsa-rdfc-2022' };
|
|
15
|
+
expect(ProofValidator.safeParse(legacy).success).toBe(false);
|
|
16
|
+
expect(ProofValidator.safeParse({ ...legacy, created: '2026-01-01T00:00:00Z' }).success).toBe(
|
|
17
|
+
true
|
|
18
|
+
);
|
|
19
|
+
expect(ProofValidator.safeParse({ ...proof, type: 'Ed25519Signature2018' }).success).toBe(
|
|
20
|
+
false
|
|
21
|
+
);
|
|
22
|
+
expect(ProofValidator.safeParse({ ...proof, created: null }).success).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('malformed final-suite proofs identify missing required fields rather than created', () => {
|
|
26
|
+
const result = ProofValidator.safeParse({
|
|
27
|
+
type: 'DataIntegrityProof',
|
|
28
|
+
cryptosuite: 'ecdsa-rdfc-2019',
|
|
29
|
+
proofPurpose: 'authentication',
|
|
30
|
+
proofValue: 'zSignature',
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
expect(result.success).toBe(false);
|
|
34
|
+
expect(result.error?.issues.map(issue => issue.path)).toEqual([['verificationMethod']]);
|
|
35
|
+
});
|
package/src/vc.ts
CHANGED
|
@@ -188,7 +188,7 @@ export type UnsignedVC = z.infer<typeof UnsignedVCValidator>;
|
|
|
188
188
|
export const ProofValidator = z
|
|
189
189
|
.object({
|
|
190
190
|
type: z.string(),
|
|
191
|
-
created: z.string(),
|
|
191
|
+
created: z.string().optional(),
|
|
192
192
|
challenge: z.string().optional(),
|
|
193
193
|
domain: z.string().optional(),
|
|
194
194
|
nonce: z.string().optional(),
|
|
@@ -196,7 +196,19 @@ export const ProofValidator = z
|
|
|
196
196
|
verificationMethod: z.string(),
|
|
197
197
|
jws: z.string().optional(),
|
|
198
198
|
})
|
|
199
|
-
.catchall(z.any())
|
|
199
|
+
.catchall(z.any())
|
|
200
|
+
.superRefine((proof, ctx) => {
|
|
201
|
+
if (
|
|
202
|
+
proof.created === undefined &&
|
|
203
|
+
(proof.type !== 'DataIntegrityProof' || proof.cryptosuite !== 'ecdsa-rdfc-2019')
|
|
204
|
+
) {
|
|
205
|
+
ctx.addIssue({
|
|
206
|
+
code: 'custom',
|
|
207
|
+
path: ['created'],
|
|
208
|
+
message: 'created is required for this proof suite',
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
});
|
|
200
212
|
export type Proof = z.infer<typeof ProofValidator>;
|
|
201
213
|
|
|
202
214
|
export const VCValidator = UnsignedVCValidator.extend({
|