@learncard/types 5.22.0 → 5.23.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@learncard/types",
3
- "version": "5.22.0",
3
+ "version": "5.23.0",
4
4
  "description": "Shared types for learn card",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -25,6 +25,7 @@
25
25
  "scripts": {
26
26
  "build": "node ./scripts/build.mjs && shx cp ./scripts/mixedEntypoint.js ./dist/index.cjs && tsc --p tsconfig.json && shx cp ./dist/index.d.ts ./dist/index.d.cts",
27
27
  "typecheck": "tsc --noEmit -p tsconfig.json",
28
+ "test": "vitest run",
28
29
  "start": "aqu watch"
29
30
  },
30
31
  "author": "Learning Economy Foundation (www.learningeconomy.io)",
@@ -0,0 +1,13 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { UnsupportedSignInOperationError } from '../auth';
3
+
4
+ describe('UnsupportedSignInOperationError', () => {
5
+ it('preserves the operation and provider for callers', () => {
6
+ const error = new UnsupportedSignInOperationError('sendPhoneOtp', 'keycloak');
7
+ expect(error).toBeInstanceOf(Error);
8
+ expect(error.name).toBe('UnsupportedSignInOperationError');
9
+ expect(error.operation).toBe('sendPhoneOtp');
10
+ expect(error.providerType).toBe('keycloak');
11
+ expect(error.message).toContain('sendPhoneOtp');
12
+ });
13
+ });
package/src/auth.ts CHANGED
@@ -32,6 +32,17 @@ export class AuthSessionError extends Error {
32
32
  // Auth Provider
33
33
  // ---------------------------------------------------------------------------
34
34
 
35
+ /** An action that the selected sign-in provider does not support. */
36
+ export class UnsupportedSignInOperationError extends Error {
37
+ constructor(
38
+ public readonly operation: string,
39
+ public readonly providerType: string
40
+ ) {
41
+ super(`${operation} is not supported by ${providerType}`);
42
+ this.name = 'UnsupportedSignInOperationError';
43
+ }
44
+ }
45
+
35
46
  /**
36
47
  * Auth provider identifier. Known values: 'firebase', 'supertokens', 'keycloak', 'oidc'.
37
48
  * Use any string to support custom auth providers without modifying this type.
@@ -288,10 +299,21 @@ export const EscrowPinStatusValidator = z.object({
288
299
  });
289
300
  export type EscrowPinStatus = z.infer<typeof EscrowPinStatusValidator>;
290
301
 
302
+ /**
303
+ * Reason a stored escrow blob no longer matches the server's active enclave:
304
+ * `mode-mismatch` — blob was sealed for `software`/`nitro` but the server now
305
+ * runs the other backend (e.g. the P6 software→Nitro migration, or a rollback);
306
+ * `key-rotated` — blob's `enclaveKeyId` no longer matches the active
307
+ * attestation's key (e.g. a PCR/measurement rotation).
308
+ */
309
+ export type EscrowBlobStaleReason = 'mode-mismatch' | 'key-rotated';
310
+
291
311
  /** Enrollment details for PIN-aware strategies. Legacy strategies may still return a string. */
292
312
  export interface EscrowEnrollmentState {
293
- state: 'enrolled' | 'not-enrolled' | 'opted-out' | 'disabled';
313
+ state: 'enrolled' | 'not-enrolled' | 'opted-out' | 'disabled' | 'stale';
294
314
  escrowPin?: EscrowPinStatus;
315
+ /** Present only when `state === 'stale'`. */
316
+ staleReason?: EscrowBlobStaleReason;
295
317
  }
296
318
 
297
319
  /**
@@ -309,6 +331,7 @@ export interface ServerKeyStatus {
309
331
  maskedRecoveryEmail?: string | null;
310
332
  escrowOptedOut?: boolean;
311
333
  escrowPin?: EscrowPinStatus;
334
+ escrowStale?: EscrowBlobStaleReason;
312
335
  sssActivationState?: SssActivationState | null;
313
336
  }
314
337
 
@@ -0,0 +1,163 @@
1
+ import { describe, expect, expectTypeOf, it } from 'vitest';
2
+
3
+ import {
4
+ AssociationValidator,
5
+ ClrCredentialValidator,
6
+ ClrSubjectValidator,
7
+ UnsignedClrCredentialValidator,
8
+ type Association,
9
+ } from './clr';
10
+
11
+ const association = {
12
+ type: 'Association' as const,
13
+ associationType: 'isPartOf',
14
+ sourceId: 'https://example.org/achievements/course',
15
+ targetId: 'https://example.org/achievements/program',
16
+ };
17
+
18
+ const supportedTypeNames = [
19
+ 'Association',
20
+ 'https://purl.imsglobal.org/spec/vc/clr/vocab.html#Association',
21
+ 'https://example.org/ExtendedAssociation',
22
+ 'CustomAssociation',
23
+ ];
24
+
25
+ const subject = {
26
+ id: 'did:example:learner',
27
+ type: ['ClrSubject'],
28
+ association: [association],
29
+ };
30
+
31
+ const unsignedClr = {
32
+ '@context': [
33
+ 'https://www.w3.org/ns/credentials/v2',
34
+ 'https://purl.imsglobal.org/spec/clr/v2p0/context.json',
35
+ 'https://purl.imsglobal.org/spec/ob/v3p0/context.json',
36
+ ],
37
+ id: 'https://example.org/credentials/clr',
38
+ type: ['VerifiableCredential', 'ClrCredential'],
39
+ issuer: { id: 'did:example:issuer', type: ['Profile'], name: 'Example University' },
40
+ validFrom: '2026-01-01T00:00:00Z',
41
+ credentialSubject: subject,
42
+ };
43
+
44
+ const proof = {
45
+ type: 'DataIntegrityProof',
46
+ cryptosuite: 'eddsa-rdfc-2022',
47
+ created: '2026-01-01T00:00:00Z',
48
+ proofPurpose: 'assertionMethod',
49
+ verificationMethod: 'did:example:issuer#key',
50
+ proofValue: 'zExampleSignature',
51
+ };
52
+
53
+ describe('CLR Association.type', () => {
54
+ it('accepts the canonical scalar without changing it or extension fields', () => {
55
+ // CLR 2.0 $defs.Association.properties.type requires the scalar "Association".
56
+ // https://purl.imsglobal.org/spec/clr/v2p0/schema/json/clr_v2p0_clrcredential_schema.json
57
+ const input = { ...association, 'ext:description': 'Course belongs to program' };
58
+
59
+ expect(AssociationValidator.parse(input)).toEqual(input);
60
+ });
61
+
62
+ it.each(supportedTypeNames)(
63
+ 'preserves scalar and singleton-array type values through full CLR parsing: %s',
64
+ typeName => {
65
+ for (const type of [typeName, [typeName]]) {
66
+ const input = { ...association, type };
67
+ const credential = {
68
+ ...unsignedClr,
69
+ credentialSubject: { ...subject, association: [input] },
70
+ };
71
+
72
+ // This SDK validator checks structure; the official CLR schema is stricter.
73
+ expect(AssociationValidator.parse(input)).toEqual(input);
74
+ expect(UnsignedClrCredentialValidator.parse(credential)).toEqual(credential);
75
+ expect(ClrCredentialValidator.parse({ ...credential, proof })).toEqual({
76
+ ...credential,
77
+ proof,
78
+ });
79
+ }
80
+ }
81
+ );
82
+
83
+ it.each(
84
+ [
85
+ ['Association'],
86
+ ['Association', 'https://example.org/ExtendedAssociation'],
87
+ ['https://example.org/LegacyAssociation'],
88
+ [''], // Existing arrays accept every string; preserve that contract.
89
+ ].map(type => ({ type }))
90
+ )('preserves supported legacy arrays: $type', ({ type }) => {
91
+ const input = { ...association, type };
92
+
93
+ expect(AssociationValidator.parse(input)).toEqual(input);
94
+ });
95
+
96
+ it.each(
97
+ [undefined, null, false, 42, '', {}, [], ['Association', 42], [['Association']]].map(
98
+ type => ({ type })
99
+ )
100
+ )('rejects invalid values: $type', ({ type }) => {
101
+ expect(AssociationValidator.safeParse({ ...association, type }).success).toBe(false);
102
+ });
103
+
104
+ it('exposes scalar and legacy array types to callers', () => {
105
+ expectTypeOf<Association['type']>().toEqualTypeOf<string | string[]>();
106
+ });
107
+ });
108
+
109
+ describe('CLR credentials with associations', () => {
110
+ it('parses a subject containing both canonical and legacy associations', () => {
111
+ const input = {
112
+ ...subject,
113
+ association: [association, { ...association, type: ['Association'] }],
114
+ };
115
+
116
+ expect(ClrSubjectValidator.parse(input)).toEqual(input);
117
+ });
118
+
119
+ it.each([false, true])('parses unsigned and signed CLR subject arrays: %s', subjectArray => {
120
+ const input = {
121
+ ...unsignedClr,
122
+ credentialSubject: subjectArray ? [subject] : subject,
123
+ };
124
+
125
+ expect(UnsignedClrCredentialValidator.parse(input)).toEqual(input);
126
+ expect(ClrCredentialValidator.parse({ ...input, proof })).toEqual({ ...input, proof });
127
+ });
128
+
129
+ it('preserves nested CLR content and validates its associations when parsed directly', () => {
130
+ const nested = {
131
+ ...unsignedClr,
132
+ id: 'https://example.org/credentials/nested-clr',
133
+ credentialSubject: {
134
+ ...subject,
135
+ association: [{ ...association, type: supportedTypeNames[1] }],
136
+ },
137
+ proof,
138
+ };
139
+ const input = {
140
+ ...unsignedClr,
141
+ credentialSubject: { ...subject, verifiableCredential: [nested] },
142
+ proof,
143
+ };
144
+
145
+ expect(ClrCredentialValidator.parse(input)).toEqual(input);
146
+ // Embedded verifiableCredential values are intentionally opaque in ClrSubjectValidator.
147
+ expect(ClrCredentialValidator.parse(nested)).toEqual(nested);
148
+ });
149
+
150
+ it.each([[], '', ['Association', null]].map(type => ({ type })))(
151
+ 'rejects invalid associations through full CLR parsing: $type',
152
+ ({ type }) => {
153
+ const invalidSubject = { ...subject, association: [{ ...association, type }] };
154
+
155
+ for (const credentialSubject of [invalidSubject, [invalidSubject]]) {
156
+ const input = { ...unsignedClr, credentialSubject };
157
+
158
+ expect(UnsignedClrCredentialValidator.safeParse(input).success).toBe(false);
159
+ expect(ClrCredentialValidator.safeParse({ ...input, proof }).success).toBe(false);
160
+ }
161
+ }
162
+ );
163
+ });
package/src/clr.ts CHANGED
@@ -27,7 +27,8 @@ export type AssociationType = z.infer<typeof AssociationTypeValidator>;
27
27
 
28
28
  export const AssociationValidator = z
29
29
  .object({
30
- type: z.string().array().nonempty(),
30
+ // Accept compacted scalars while retaining the existing permissive array contract.
31
+ type: z.string().min(1).or(z.string().array().nonempty()),
31
32
  associationType: AssociationTypeValidator,
32
33
  sourceId: z.string().optional(),
33
34
  targetId: z.string(),
package/src/lcn.ts CHANGED
@@ -714,12 +714,14 @@ export const ConsentFlowContractDetailsValidator = z.object({
714
714
  expiresAt: z.string().optional(),
715
715
  autoBoosts: z.string().array().optional(),
716
716
  writers: z.array(LCNProfileValidator).optional(),
717
+ recipients: z.array(LCNPublicProfileValidator.extend({ did: z.string() })).optional(),
718
+ audienceVersion: z.number().int().nonnegative().optional(),
717
719
  });
718
720
  export type ConsentFlowContractDetails = z.infer<typeof ConsentFlowContractDetailsValidator>;
719
721
  export type ConsentFlowContractDetailsInput = z.input<typeof ConsentFlowContractDetailsValidator>;
720
722
 
721
723
  export const ConsentFlowContractRequestStatusValidator = z
722
- .enum(['pending', 'accepted', 'denied'])
724
+ .enum(['pending', 'accepted', 'denied', 'cancelled'])
723
725
  .nullable();
724
726
  export type ConsentFlowContractRequestStatus = z.infer<
725
727
  typeof ConsentFlowContractRequestStatusValidator
@@ -730,7 +732,51 @@ export type ConsentFlowContractRequestReadStatus = z.infer<
730
732
  typeof ConsentFlowContractRequestReadStatusValidator
731
733
  >;
732
734
 
733
- export const ConsentFlowContractRequestForProfileValidator = z.object({
735
+ /** Durable referral identity captured on consent history and correlated events. */
736
+ export const ConsentFlowReferralValidator = z.object({
737
+ requestId: z.string(),
738
+ requestedBy: z.string(),
739
+ externalReferenceId: z.string().max(256).optional(),
740
+ });
741
+ export type ConsentFlowReferral = z.infer<typeof ConsentFlowReferralValidator>;
742
+
743
+ export const ContractRequestFieldsValidator = ConsentFlowReferralValidator.partial().extend({
744
+ requestedAt: z.string().optional(),
745
+ message: z.string().max(500).optional(),
746
+ });
747
+ export type ContractRequestFields = z.infer<typeof ContractRequestFieldsValidator>;
748
+
749
+ export const SendContractRequestValidator = z.object({
750
+ contractUri: z.string(),
751
+ targetProfileId: z.string(),
752
+ externalReferenceId: z.string().trim().min(1).max(256).optional(),
753
+ message: z.string().trim().max(500).optional(),
754
+ });
755
+ export type SendContractRequest = z.infer<typeof SendContractRequestValidator>;
756
+
757
+ export const ConsentFlowWebhookMetadataValidator = z.object({
758
+ eventId: z.string(),
759
+ deliveryKey: z.string(),
760
+ event: z.enum([
761
+ 'request_sent',
762
+ 'request_accepted',
763
+ 'request_denied',
764
+ 'request_cancelled',
765
+ 'consent_created',
766
+ 'consent_updated',
767
+ 'consent_withdrawn',
768
+ 'credentials_synced',
769
+ ]),
770
+ contractUri: z.string(),
771
+ termsUri: z.string().optional(),
772
+ requestId: z.string().optional(),
773
+ requestedBy: z.string().optional(),
774
+ externalReferenceId: z.string().optional(),
775
+ recipientRole: z.enum(['owner', 'recipient', 'requester', 'target']),
776
+ });
777
+ export type ConsentFlowWebhookMetadata = z.infer<typeof ConsentFlowWebhookMetadataValidator>;
778
+
779
+ export const ConsentFlowContractRequestForProfileValidator = ContractRequestFieldsValidator.extend({
734
780
  profile: LCNProfileValidator,
735
781
  status: ConsentFlowContractRequestStatusValidator,
736
782
  readStatus: ConsentFlowContractRequestReadStatusValidator.optional(),
@@ -803,6 +849,7 @@ export const PaginatedConsentFlowTermsValidator = PaginationResponseValidator.ex
803
849
  expiresAt: z.string().optional(),
804
850
  oneTime: z.boolean().optional(),
805
851
  terms: ConsentFlowTermsValidator,
852
+ referral: ConsentFlowReferralValidator.optional(),
806
853
  contract: ConsentFlowContractDetailsValidator,
807
854
  uri: z.string(),
808
855
  consenter: LCNProfileValidator,
@@ -962,6 +1009,7 @@ export const ConsentFlowTransactionValidator = z.object({
962
1009
  oneTime: z.boolean().optional(),
963
1010
  terms: ConsentFlowTermsValidator.optional(),
964
1011
  guardianApproval: ConsentFlowGuardianApprovalValidator.optional(),
1012
+ referral: ConsentFlowReferralValidator.optional(),
965
1013
  id: z.string(),
966
1014
  action: ConsentFlowTransactionActionValidator,
967
1015
  date: z.string(),
@@ -974,6 +1022,7 @@ export const HolderExportConsentRecordValidator = z.object({
974
1022
  status: ConsentFlowTermsStatusValidator,
975
1023
  contract: ConsentFlowContractDetailsValidator,
976
1024
  terms: ConsentFlowTermsValidator,
1025
+ referral: ConsentFlowReferralValidator.optional(),
977
1026
  transactions: ConsentFlowTransactionValidator.array(),
978
1027
  });
979
1028
  export type HolderExportConsentRecord = z.infer<typeof HolderExportConsentRecordValidator>;
@@ -2188,7 +2237,7 @@ export const AppStoreListingSubmitterValidator = z.object({
2188
2237
 
2189
2238
  export type AppStoreListingSubmitter = z.infer<typeof AppStoreListingSubmitterValidator>;
2190
2239
 
2191
- export const AppStoreListingValidator = z.object({
2240
+ const AppStoreListingFieldsValidator = z.object({
2192
2241
  listing_id: z.string(),
2193
2242
  slug: z.string().optional(),
2194
2243
  display_name: z.string(),
@@ -2215,12 +2264,35 @@ export const AppStoreListingValidator = z.object({
2215
2264
  contact_email: z.string().email().optional(),
2216
2265
  });
2217
2266
 
2267
+ export const PendingListingUpdateStatusValidator = z.enum(['DRAFT', 'PENDING_REVIEW']);
2268
+ export type PendingListingUpdateStatus = z.infer<typeof PendingListingUpdateStatusValidator>;
2269
+
2270
+ /** Changes to a live app that only take effect once an admin approves them. */
2271
+ export const PendingListingUpdateValidator = z.object({
2272
+ status: PendingListingUpdateStatusValidator,
2273
+ submitted_at: z.string().optional(),
2274
+ changes: AppStoreListingFieldsValidator.omit({
2275
+ listing_id: true,
2276
+ app_listing_status: true,
2277
+ promotion_level: true,
2278
+ submitted_at: true,
2279
+ submitter: true,
2280
+ }).partial(),
2281
+ manifest_version: z.number().int().min(1).optional(),
2282
+ });
2283
+ export type PendingListingUpdate = z.infer<typeof PendingListingUpdateValidator>;
2284
+
2285
+ export const AppStoreListingValidator = AppStoreListingFieldsValidator.extend({
2286
+ pending_update: PendingListingUpdateValidator.optional(),
2287
+ });
2288
+
2218
2289
  export type AppStoreListing = z.infer<typeof AppStoreListingValidator>;
2219
2290
 
2220
2291
  export const AppStoreListingCreateValidator = AppStoreListingValidator.omit({
2221
2292
  listing_id: true,
2222
2293
  app_listing_status: true,
2223
2294
  promotion_level: true,
2295
+ pending_update: true,
2224
2296
  });
2225
2297
 
2226
2298
  export type AppStoreListingCreateType = z.infer<typeof AppStoreListingCreateValidator>;
@@ -2229,6 +2301,7 @@ export const AppStoreListingUpdateValidator = AppStoreListingValidator.partial()
2229
2301
  listing_id: true,
2230
2302
  app_listing_status: true,
2231
2303
  promotion_level: true,
2304
+ pending_update: true,
2232
2305
  });
2233
2306
 
2234
2307
  export type AppStoreListingUpdateType = z.infer<typeof AppStoreListingUpdateValidator>;
@@ -2264,13 +2337,116 @@ export const AppBoostValidator = z.object({
2264
2337
  export type AppBoost = z.infer<typeof AppBoostValidator>;
2265
2338
 
2266
2339
  // App Event Types (discriminated union for type safety)
2267
- export const SendCredentialEventValidator = z.object({
2340
+ export const InlineWalletCategoryValidator = z.enum([
2341
+ 'Achievement',
2342
+ 'Skill',
2343
+ 'ID',
2344
+ 'Learning History',
2345
+ 'Work History',
2346
+ 'Social Badges',
2347
+ 'Membership',
2348
+ 'Accomplishment',
2349
+ 'Experiences',
2350
+ 'Accommodation',
2351
+ 'Family',
2352
+ ]);
2353
+
2354
+ export const InlineAlignmentValidator = z
2355
+ .object({
2356
+ name: z.string(),
2357
+ url: z.string(),
2358
+ framework: z.string().optional(),
2359
+ code: z.string().optional(),
2360
+ })
2361
+ .passthrough();
2362
+
2363
+ export const InlineEvidenceValidator = z
2364
+ .object({
2365
+ id: z.string().optional(),
2366
+ name: z.string().optional(),
2367
+ narrative: z.string().optional(),
2368
+ })
2369
+ .passthrough();
2370
+
2371
+ export const InlineWalletSkillTagValidator = z
2372
+ .object({
2373
+ frameworkId: z.string(),
2374
+ id: z.string(),
2375
+ proficiencyLevel: z.string().optional(),
2376
+ })
2377
+ .passthrough();
2378
+
2379
+ export const SimpleInlineCredentialTemplateValidator = z
2380
+ .object({
2381
+ rawCredential: z.undefined().optional(),
2382
+ name: z.string(),
2383
+ description: z.string().optional(),
2384
+ image: z.string().optional(),
2385
+ issuerName: z.string().optional(),
2386
+ achievementType: z.string().optional(),
2387
+ category: InlineWalletCategoryValidator.optional(),
2388
+ criteria: z
2389
+ .object({
2390
+ narrative: z.string().optional(),
2391
+ url: z.string().optional(),
2392
+ })
2393
+ .passthrough()
2394
+ .optional(),
2395
+ alignments: z.array(InlineAlignmentValidator).optional(),
2396
+ tags: z.array(z.string()).optional(),
2397
+ activity: z
2398
+ .object({
2399
+ startDate: z.string().optional(),
2400
+ endDate: z.string().optional(),
2401
+ })
2402
+ .passthrough()
2403
+ .optional(),
2404
+ credits: z
2405
+ .object({
2406
+ earned: z.union([z.number(), z.string()]).optional(),
2407
+ available: z.union([z.number(), z.string()]).optional(),
2408
+ })
2409
+ .passthrough()
2410
+ .optional(),
2411
+ validUntil: z.string().optional(),
2412
+ evidence: z.array(InlineEvidenceValidator).optional(),
2413
+ walletSkills: z.array(InlineWalletSkillTagValidator).optional(),
2414
+ })
2415
+ .passthrough();
2416
+
2417
+ export const RawInlineCredentialTemplateValidator = z
2418
+ .object({
2419
+ rawCredential: z.record(z.string(), z.unknown()),
2420
+ category: InlineWalletCategoryValidator.optional(),
2421
+ walletSkills: z.array(InlineWalletSkillTagValidator).optional(),
2422
+ })
2423
+ .passthrough();
2424
+
2425
+ export const InlineCredentialTemplateValidator = z.union([
2426
+ RawInlineCredentialTemplateValidator,
2427
+ SimpleInlineCredentialTemplateValidator,
2428
+ ]);
2429
+
2430
+ export const LegacySendCredentialEventValidator = z.object({
2268
2431
  type: z.literal('send-credential'),
2269
2432
  templateAlias: z.string(),
2270
2433
  templateData: z.record(z.string(), z.unknown()).optional(),
2271
2434
  preventDuplicateClaim: z.boolean().optional(),
2272
2435
  });
2273
2436
 
2437
+ export const InlineSendCredentialEventValidator = z.object({
2438
+ type: z.literal('send-credential'),
2439
+ alias: z.string(),
2440
+ template: InlineCredentialTemplateValidator,
2441
+ templateData: z.record(z.string(), z.unknown()).optional(),
2442
+ preventDuplicateClaim: z.boolean().optional(),
2443
+ });
2444
+
2445
+ export const SendCredentialEventValidator = z.union([
2446
+ LegacySendCredentialEventValidator,
2447
+ InlineSendCredentialEventValidator,
2448
+ ]);
2449
+
2274
2450
  export type SendCredentialEvent = z.infer<typeof SendCredentialEventValidator>;
2275
2451
 
2276
2452
  export const CheckCredentialEventValidator = z
@@ -2425,8 +2601,168 @@ export const GetCountersEventValidator = z.object({
2425
2601
 
2426
2602
  export type GetCountersEvent = z.infer<typeof GetCountersEventValidator>;
2427
2603
 
2604
+ export const ConsentRequestWalletCategoryValidator = z.enum([
2605
+ 'Achievement',
2606
+ 'Skill',
2607
+ 'ID',
2608
+ 'Learning History',
2609
+ 'Work History',
2610
+ 'Social Badges',
2611
+ 'Membership',
2612
+ 'Accomplishment',
2613
+ 'Experiences',
2614
+ 'Accommodation',
2615
+ 'Family',
2616
+ ]);
2617
+
2618
+ export const ConsentRequestPersonalFieldValidator = z.enum([
2619
+ 'name',
2620
+ 'email',
2621
+ 'phone',
2622
+ 'birthDate',
2623
+ 'country',
2624
+ 'avatar',
2625
+ ]);
2626
+
2627
+ export const ConsentRequestValidator = z.object({
2628
+ read: z
2629
+ .object({
2630
+ credentialCategories: z.array(ConsentRequestWalletCategoryValidator).optional(),
2631
+ personalFields: z.array(ConsentRequestPersonalFieldValidator).optional(),
2632
+ })
2633
+ .optional(),
2634
+ write: z
2635
+ .object({
2636
+ credentialCategories: z.array(ConsentRequestWalletCategoryValidator).optional(),
2637
+ })
2638
+ .optional(),
2639
+ reason: z.string().optional(),
2640
+ });
2641
+
2642
+ export type ConsentRequest = z.infer<typeof ConsentRequestValidator>;
2643
+
2644
+ export const NormalizedConsentScopesValidator = z.object({
2645
+ read: z.object({
2646
+ credentialCategories: z.array(ConsentRequestWalletCategoryValidator).default([]),
2647
+ personalFields: z.array(ConsentRequestPersonalFieldValidator).default([]),
2648
+ }),
2649
+ write: z.object({
2650
+ credentialCategories: z.array(ConsentRequestWalletCategoryValidator).default([]),
2651
+ }),
2652
+ });
2653
+
2654
+ export type NormalizedConsentScopes = z.infer<typeof NormalizedConsentScopesValidator>;
2655
+
2656
+ export const CapturedTemplateRecordValidator = z
2657
+ .object({
2658
+ alias: z.string(),
2659
+ template: InlineCredentialTemplateValidator,
2660
+ version: z.number().int().min(1),
2661
+ lastUsedAt: z.string(),
2662
+ })
2663
+ .passthrough();
2664
+
2665
+ export type CapturedTemplateRecord = z.infer<typeof CapturedTemplateRecordValidator>;
2666
+
2667
+ export const CapturedConsentRecordValidator = z
2668
+ .object({
2669
+ scopes: NormalizedConsentScopesValidator,
2670
+ reason: z.string().optional(),
2671
+ lastUsedAt: z.string(),
2672
+ })
2673
+ .passthrough();
2674
+
2675
+ export type CapturedConsentRecord = z.infer<typeof CapturedConsentRecordValidator>;
2676
+
2677
+ export const AppManifestValidator = z
2678
+ .object({
2679
+ manifestVersion: z.literal(1),
2680
+ appUrl: z.string(),
2681
+ appKey: z.string().max(200).optional(),
2682
+ suggestedName: z.string().optional(),
2683
+ suggestedIconUrl: z.string().optional(),
2684
+ permissions: z.array(z.string()).default([]),
2685
+ templates: z.array(CapturedTemplateRecordValidator).default([]),
2686
+ consentRequests: z.array(CapturedConsentRecordValidator).default([]),
2687
+ usedConsentContract: z.boolean().optional(),
2688
+ featuresLaunched: z.array(z.string()).default([]),
2689
+ counterKeys: z.array(z.string()).default([]),
2690
+ usedLearnerContext: z.boolean(),
2691
+ usedNotifications: z.boolean(),
2692
+ firstCapturedAt: z.string(),
2693
+ lastUpdatedAt: z.string(),
2694
+ })
2695
+ .passthrough();
2696
+
2697
+ export type AppManifest = z.infer<typeof AppManifestValidator>;
2698
+
2699
+ export const AppManifestVersionStatusValidator = z.enum(['draft', 'active', 'superseded']);
2700
+ export type AppManifestVersionStatus = z.infer<typeof AppManifestVersionStatusValidator>;
2701
+
2702
+ export const AppManifestVersionValidator = z.object({
2703
+ id: z.string(),
2704
+ version: z.number().int().min(1),
2705
+ manifestHash: z.string(),
2706
+ manifest: AppManifestValidator,
2707
+ status: AppManifestVersionStatusValidator,
2708
+ createdAt: z.string(),
2709
+ activatedAt: z.string().optional(),
2710
+ });
2711
+
2712
+ export type AppManifestVersion = z.infer<typeof AppManifestVersionValidator>;
2713
+
2714
+ const AppManifestStringDiffValidator = z.object({
2715
+ added: z.array(z.string()),
2716
+ removed: z.array(z.string()),
2717
+ changed: z.array(z.string()),
2718
+ });
2719
+
2720
+ export const AppManifestTemplateRefValidator = z.object({
2721
+ alias: z.string(),
2722
+ version: z.number().int().min(1),
2723
+ });
2724
+
2725
+ export type AppManifestTemplateRef = z.infer<typeof AppManifestTemplateRefValidator>;
2726
+
2727
+ export const AppManifestTemplateChangeValidator = z.object({
2728
+ alias: z.string(),
2729
+ fromVersion: z.number().int().min(1),
2730
+ toVersion: z.number().int().min(1),
2731
+ });
2732
+
2733
+ export type AppManifestTemplateChange = z.infer<typeof AppManifestTemplateChangeValidator>;
2734
+
2735
+ export const AppManifestDiffValidator = z.object({
2736
+ permissions: AppManifestStringDiffValidator,
2737
+ templates: z.object({
2738
+ added: z.array(AppManifestTemplateRefValidator),
2739
+ removed: z.array(AppManifestTemplateRefValidator),
2740
+ changed: z.array(AppManifestTemplateChangeValidator),
2741
+ }),
2742
+ consentScopes: AppManifestStringDiffValidator,
2743
+ featurePaths: AppManifestStringDiffValidator,
2744
+ counterKeys: AppManifestStringDiffValidator,
2745
+ requiresReview: z.boolean(),
2746
+ });
2747
+
2748
+ export type AppManifestDiff = z.infer<typeof AppManifestDiffValidator>;
2749
+
2750
+ export const UpsertConsentContractEventValidator = z.object({
2751
+ type: z.literal('upsert-consent-contract'),
2752
+ scopes: ConsentRequestValidator,
2753
+ });
2754
+
2755
+ export type UpsertConsentContractEvent = z.infer<typeof UpsertConsentContractEventValidator>;
2756
+
2757
+ export const UpsertConsentContractResponseValidator = z.object({
2758
+ contractUri: z.string(),
2759
+ created: z.boolean(),
2760
+ });
2761
+
2762
+ export type UpsertConsentContractResponse = z.infer<typeof UpsertConsentContractResponseValidator>;
2763
+
2428
2764
  // Add new event types here as the union grows
2429
- export const AppEventValidator = z.discriminatedUnion('type', [
2765
+ export const AppEventValidator = z.union([
2430
2766
  SendCredentialEventValidator,
2431
2767
  CheckCredentialEventValidator,
2432
2768
  CheckIssuanceStatusEventValidator,
@@ -2437,6 +2773,7 @@ export const AppEventValidator = z.discriminatedUnion('type', [
2437
2773
  IncrementCounterEventValidator,
2438
2774
  GetCounterEventValidator,
2439
2775
  GetCountersEventValidator,
2776
+ UpsertConsentContractEventValidator,
2440
2777
  ]);
2441
2778
 
2442
2779
  export type AppEvent = z.infer<typeof AppEventValidator>;