gdc-common-utils-ts 2.0.17 → 2.1.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.
Files changed (71) hide show
  1. package/README.md +0 -1
  2. package/dist/constants/verifiable-credentials.d.ts +7 -0
  3. package/dist/constants/verifiable-credentials.js +7 -0
  4. package/dist/examples/bundle-didcomm-payload.d.ts +11 -0
  5. package/dist/examples/bundle-didcomm-payload.js +13 -0
  6. package/dist/examples/communication-attached-bundle-session.js +2 -1
  7. package/dist/examples/communication-didcomm-payload.d.ts +35 -0
  8. package/dist/examples/communication-didcomm-payload.js +38 -0
  9. package/dist/examples/contract-examples.d.ts +1 -0
  10. package/dist/examples/contract-examples.js +1 -0
  11. package/dist/examples/index.d.ts +5 -0
  12. package/dist/examples/index.js +5 -0
  13. package/dist/examples/inter-tenant-access-contract.d.ts +124 -0
  14. package/dist/examples/inter-tenant-access-contract.js +134 -0
  15. package/dist/examples/profile-manager-mem.d.ts +45 -0
  16. package/dist/examples/profile-manager-mem.js +29 -0
  17. package/dist/examples/shared.d.ts +6 -0
  18. package/dist/examples/shared.js +6 -0
  19. package/dist/examples/wallet-mem.d.ts +38 -0
  20. package/dist/examples/wallet-mem.js +40 -0
  21. package/dist/index.d.ts +1 -0
  22. package/dist/index.js +1 -0
  23. package/dist/interfaces/IProfileOutboxRepository.d.ts +36 -0
  24. package/dist/interfaces/IProfileOutboxRepository.js +2 -0
  25. package/dist/interfaces/IWallet.d.ts +74 -0
  26. package/dist/interfaces/IWallet.js +2 -0
  27. package/dist/interfaces/IWalletQueue.d.ts +30 -0
  28. package/dist/interfaces/IWalletQueue.js +2 -0
  29. package/dist/interfaces/index.d.ts +8 -0
  30. package/dist/interfaces/index.js +8 -0
  31. package/dist/models/communication-attached-bundle-session.d.ts +193 -0
  32. package/dist/models/communication-attached-bundle-session.js +34 -0
  33. package/dist/models/index.d.ts +4 -0
  34. package/dist/models/index.js +4 -0
  35. package/dist/models/inter-tenant-access-contract.d.ts +105 -0
  36. package/dist/models/inter-tenant-access-contract.js +78 -0
  37. package/dist/models/interoperable-claims/diagnostic-report-claims.d.ts +1 -2
  38. package/dist/models/interoperable-claims/diagnostic-report-claims.js +1 -2
  39. package/dist/models/profile-manager.d.ts +85 -0
  40. package/dist/models/profile-manager.js +2 -0
  41. package/dist/models/wallet.d.ts +59 -0
  42. package/dist/models/wallet.js +22 -0
  43. package/dist/utils/backend-message-manager-mem.d.ts +67 -0
  44. package/dist/utils/backend-message-manager-mem.js +227 -0
  45. package/dist/utils/bundle-didcomm-payload.d.ts +29 -0
  46. package/dist/utils/bundle-didcomm-payload.js +48 -0
  47. package/dist/utils/communication-attached-bundle-session-helpers.d.ts +54 -0
  48. package/dist/utils/communication-attached-bundle-session-helpers.js +517 -0
  49. package/dist/utils/communication-attached-bundle-session.d.ts +59 -302
  50. package/dist/utils/communication-attached-bundle-session.js +114 -839
  51. package/dist/utils/communication-consent-access-editor.d.ts +85 -0
  52. package/dist/utils/communication-consent-access-editor.js +244 -0
  53. package/dist/utils/communication-didcomm-payload.d.ts +33 -0
  54. package/dist/utils/communication-didcomm-payload.js +75 -0
  55. package/dist/utils/evidence-blockchain-references.d.ts +2 -0
  56. package/dist/utils/evidence-blockchain-references.js +4 -0
  57. package/dist/utils/index.d.ts +10 -0
  58. package/dist/utils/index.js +10 -0
  59. package/dist/utils/inter-tenant-access-contract.d.ts +44 -0
  60. package/dist/utils/inter-tenant-access-contract.js +358 -0
  61. package/dist/utils/organization-authorization-urn.d.ts +43 -0
  62. package/dist/utils/organization-authorization-urn.js +87 -0
  63. package/dist/utils/profile-manager-mem.d.ts +69 -0
  64. package/dist/utils/profile-manager-mem.js +79 -0
  65. package/dist/utils/profile-outbox-memory-repository.d.ts +34 -0
  66. package/dist/utils/profile-outbox-memory-repository.js +57 -0
  67. package/dist/utils/wallet-mem.d.ts +93 -0
  68. package/dist/utils/wallet-mem.js +277 -0
  69. package/dist/utils/wallet-memory-queue.d.ts +32 -0
  70. package/dist/utils/wallet-memory-queue.js +82 -0
  71. package/package.json +1 -1
@@ -0,0 +1,358 @@
1
+ // Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
2
+ import { ContractCredentialTypes, W3cCredentialContexts, W3cCredentialTypes } from '../constants/verifiable-credentials.js';
3
+ import { ClaimConsent } from '../models/consent-rule.js';
4
+ import { ClaimInterTenantAccessContract } from '../models/inter-tenant-access-contract.js';
5
+ import { sanitizeBlockchainReference } from './evidence-blockchain-references.js';
6
+ import { getVpCredentials } from './vp-token.js';
7
+ const ACTIVE_CONTRACT_STATUSES = new Set(['executed', 'amended', 'appended']);
8
+ const PROVIDER_ROLE = 'provider';
9
+ const CONSUMER_ROLE = 'consumer';
10
+ const PROVIDER_CONTROLLER_ROLE = 'provider-controller';
11
+ const CONSUMER_CONTROLLER_ROLE = 'consumer-controller';
12
+ function splitCsv(value) {
13
+ return Array.from(new Set(String(value || '')
14
+ .split(',')
15
+ .map((item) => item.trim())
16
+ .filter(Boolean)));
17
+ }
18
+ function readClaimWithAliases(claims, canonicalKey, aliases = []) {
19
+ const direct = String(claims[canonicalKey] || '').trim();
20
+ if (direct)
21
+ return direct;
22
+ for (const alias of aliases) {
23
+ const value = String(claims[alias] || '').trim();
24
+ if (value)
25
+ return value;
26
+ }
27
+ return undefined;
28
+ }
29
+ function firstIdentifierValue(input) {
30
+ if (Array.isArray(input)) {
31
+ for (const candidate of input) {
32
+ const found = firstIdentifierValue(candidate);
33
+ if (found)
34
+ return found;
35
+ }
36
+ return undefined;
37
+ }
38
+ if (!input || typeof input !== 'object')
39
+ return undefined;
40
+ const direct = String(input.value || input.id || '').trim();
41
+ return direct || undefined;
42
+ }
43
+ function extractRoleTexts(input) {
44
+ const items = Array.isArray(input) ? input : input ? [input] : [];
45
+ return items
46
+ .flatMap((item) => {
47
+ if (!item || typeof item !== 'object')
48
+ return [];
49
+ const text = String(item.text || '').trim();
50
+ const codingItems = Array.isArray(item.coding) ? item.coding : item.coding ? [item.coding] : [];
51
+ const codingValues = codingItems
52
+ .map((coding) => String(coding?.code || coding?.display || '').trim())
53
+ .filter(Boolean);
54
+ return [text, ...codingValues].filter(Boolean);
55
+ });
56
+ }
57
+ function extractCapabilityValues(input) {
58
+ const items = Array.isArray(input) ? input : input ? [input] : [];
59
+ return Array.from(new Set(items.flatMap((item) => {
60
+ if (!item || typeof item !== 'object')
61
+ return [];
62
+ const text = String(item.text || '').trim();
63
+ const codingItems = Array.isArray(item.coding) ? item.coding : item.coding ? [item.coding] : [];
64
+ const codingValues = codingItems
65
+ .map((coding) => String(coding?.code || coding?.display || '').trim())
66
+ .filter(Boolean);
67
+ return [text, ...codingValues].flatMap((value) => splitCsv(value));
68
+ })));
69
+ }
70
+ function extractOfferPartyReference(contract, expectedRole) {
71
+ const terms = Array.isArray(contract.term) ? contract.term : contract.term ? [contract.term] : [];
72
+ for (const term of terms) {
73
+ const parties = Array.isArray(term?.offer?.party) ? term.offer.party : term?.offer?.party ? [term.offer.party] : [];
74
+ for (const party of parties) {
75
+ const roleTexts = extractRoleTexts(party?.role).map((value) => value.toLowerCase());
76
+ if (!roleTexts.includes(expectedRole))
77
+ continue;
78
+ const reference = String(party?.reference?.reference || party?.reference || '').trim();
79
+ if (reference)
80
+ return reference;
81
+ }
82
+ }
83
+ return undefined;
84
+ }
85
+ function extractSignerReference(contract, expectedRole) {
86
+ const signers = Array.isArray(contract.signer) ? contract.signer : contract.signer ? [contract.signer] : [];
87
+ for (const signer of signers) {
88
+ const roleTexts = extractRoleTexts(signer?.type).map((value) => value.toLowerCase());
89
+ if (!roleTexts.includes(expectedRole))
90
+ continue;
91
+ const reference = String(signer?.party?.reference || signer?.party?.identifier?.value || '').trim();
92
+ if (reference)
93
+ return reference;
94
+ }
95
+ return undefined;
96
+ }
97
+ function extractPurposes(contract) {
98
+ const terms = Array.isArray(contract.term) ? contract.term : contract.term ? [contract.term] : [];
99
+ return Array.from(new Set(terms.flatMap((term) => {
100
+ const typeItems = Array.isArray(term?.type) ? term.type : term?.type ? [term.type] : [];
101
+ return typeItems.flatMap((item) => {
102
+ if (!item || typeof item !== 'object')
103
+ return [];
104
+ const text = String(item.text || '').trim();
105
+ const codingItems = Array.isArray(item.coding) ? item.coding : item.coding ? [item.coding] : [];
106
+ const codingValues = codingItems
107
+ .map((coding) => String(coding?.code || coding?.display || '').trim())
108
+ .filter(Boolean);
109
+ return [text, ...codingValues].flatMap((value) => splitCsv(value));
110
+ });
111
+ })));
112
+ }
113
+ function extractCapabilities(contract) {
114
+ const terms = Array.isArray(contract.term) ? contract.term : contract.term ? [contract.term] : [];
115
+ return Array.from(new Set(terms.flatMap((term) => extractCapabilityValues(term?.offer?.securityLabel))));
116
+ }
117
+ function normalizeNow(input) {
118
+ if (input instanceof Date)
119
+ return input.getTime();
120
+ if (input)
121
+ return new Date(input).getTime();
122
+ return Date.now();
123
+ }
124
+ export function buildInterTenantAccessContractResource(claims) {
125
+ const identifier = readClaimWithAliases(claims, ClaimInterTenantAccessContract.identifier) || '';
126
+ const status = readClaimWithAliases(claims, ClaimInterTenantAccessContract.status) || 'executed';
127
+ const issued = readClaimWithAliases(claims, ClaimInterTenantAccessContract.issued) || '';
128
+ const appliesStart = readClaimWithAliases(claims, ClaimInterTenantAccessContract.appliesStart, ['Contract.applies.start']) || '';
129
+ const appliesEnd = readClaimWithAliases(claims, ClaimInterTenantAccessContract.appliesEnd, ['Contract.applies.end']) || '';
130
+ const providerOrganization = readClaimWithAliases(claims, ClaimInterTenantAccessContract.providerOrganization, ['Contract.term.offer.party.provider']) || '';
131
+ const consumerOrganization = readClaimWithAliases(claims, ClaimInterTenantAccessContract.consumerOrganization, ['Contract.term.offer.party.consumer']) || '';
132
+ const providerController = readClaimWithAliases(claims, ClaimInterTenantAccessContract.providerController, ['Contract.signer.provider']) || '';
133
+ const consumerController = readClaimWithAliases(claims, ClaimInterTenantAccessContract.consumerController, ['Contract.signer.consumer']) || '';
134
+ const instantiatesUri = readClaimWithAliases(claims, ClaimInterTenantAccessContract.instantiatesUri, ['Contract.instantiatesUri']) || '';
135
+ const capabilities = splitCsv(readClaimWithAliases(claims, ClaimInterTenantAccessContract.capability, ['Contract.term.offer.securityLabel']));
136
+ const purposes = splitCsv(readClaimWithAliases(claims, ClaimInterTenantAccessContract.purpose, ['Contract.term.type']));
137
+ return {
138
+ resourceType: 'Contract',
139
+ id: identifier || undefined,
140
+ identifier: identifier ? [{ value: identifier }] : undefined,
141
+ status,
142
+ issued: issued || undefined,
143
+ instantiatesUri: instantiatesUri || undefined,
144
+ applies: {
145
+ ...(appliesStart ? { start: appliesStart } : {}),
146
+ ...(appliesEnd ? { end: appliesEnd } : {}),
147
+ },
148
+ signer: [
149
+ providerController
150
+ ? {
151
+ type: [{ text: PROVIDER_CONTROLLER_ROLE }],
152
+ party: { reference: providerController },
153
+ }
154
+ : undefined,
155
+ consumerController
156
+ ? {
157
+ type: [{ text: CONSUMER_CONTROLLER_ROLE }],
158
+ party: { reference: consumerController },
159
+ }
160
+ : undefined,
161
+ ].filter(Boolean),
162
+ term: [{
163
+ type: purposes.map((purpose) => ({ text: purpose })),
164
+ offer: {
165
+ party: [
166
+ providerOrganization
167
+ ? {
168
+ reference: { reference: providerOrganization },
169
+ role: [{ text: PROVIDER_ROLE }],
170
+ }
171
+ : undefined,
172
+ consumerOrganization
173
+ ? {
174
+ reference: { reference: consumerOrganization },
175
+ role: [{ text: CONSUMER_ROLE }],
176
+ }
177
+ : undefined,
178
+ ].filter(Boolean),
179
+ securityLabel: capabilities.map((capability) => ({ text: capability })),
180
+ },
181
+ }],
182
+ };
183
+ }
184
+ export function buildInterTenantAccessContractCredential(input) {
185
+ return {
186
+ '@context': [W3cCredentialContexts.V2],
187
+ type: [
188
+ W3cCredentialTypes.VerifiableCredential,
189
+ ContractCredentialTypes.InterTenantAccessContractCredential,
190
+ ],
191
+ issuer: String(input.issuer || '').trim(),
192
+ validFrom: String(input.validFrom || '').trim(),
193
+ ...(input.validUntil ? { validUntil: String(input.validUntil).trim() } : {}),
194
+ credentialSubject: buildInterTenantAccessContractResource(input.claims),
195
+ ...(input.additionalCredential || {}),
196
+ };
197
+ }
198
+ export function summarizeInterTenantAccessContract(credential) {
199
+ if (!credential || typeof credential !== 'object')
200
+ return undefined;
201
+ const typeRaw = Array.isArray(credential.type) ? credential.type : [credential.type].filter(Boolean);
202
+ if (!typeRaw.includes(ContractCredentialTypes.InterTenantAccessContractCredential))
203
+ return undefined;
204
+ const subject = (credential.credentialSubject || {});
205
+ if (String(subject.resourceType || '').trim() !== 'Contract')
206
+ return undefined;
207
+ return {
208
+ identifier: firstIdentifierValue(subject.identifier) || String(subject.id || '').trim() || undefined,
209
+ status: String(subject.status || '').trim() || undefined,
210
+ issued: String(subject.issued || '').trim() || undefined,
211
+ appliesStart: String(subject.applies?.start || '').trim() || undefined,
212
+ appliesEnd: String(subject.applies?.end || '').trim() || undefined,
213
+ providerOrganizationDid: extractOfferPartyReference(subject, PROVIDER_ROLE),
214
+ consumerOrganizationDid: extractOfferPartyReference(subject, CONSUMER_ROLE),
215
+ providerControllerDid: extractSignerReference(subject, PROVIDER_CONTROLLER_ROLE),
216
+ consumerControllerDid: extractSignerReference(subject, CONSUMER_CONTROLLER_ROLE),
217
+ capabilities: extractCapabilities(subject),
218
+ purposes: extractPurposes(subject),
219
+ };
220
+ }
221
+ export function isInterTenantAccessContractActive(summary, options = {}) {
222
+ if (!summary)
223
+ return false;
224
+ const status = String(summary.status || '').trim().toLowerCase();
225
+ if (!ACTIVE_CONTRACT_STATUSES.has(status))
226
+ return false;
227
+ const nowMs = normalizeNow(options.now);
228
+ const startMs = summary.appliesStart ? Date.parse(summary.appliesStart) : NaN;
229
+ const endMs = summary.appliesEnd ? Date.parse(summary.appliesEnd) : NaN;
230
+ if (!Number.isNaN(startMs) && startMs > nowMs)
231
+ return false;
232
+ if (!Number.isNaN(endMs) && endMs < nowMs)
233
+ return false;
234
+ return true;
235
+ }
236
+ export function matchesInterTenantAccessContract(summary, criteria) {
237
+ if (!isInterTenantAccessContractActive(summary, { now: criteria.now }))
238
+ return false;
239
+ if (String(summary?.providerOrganizationDid || '').trim() !== String(criteria.providerOrganizationDid || '').trim())
240
+ return false;
241
+ if (String(summary?.consumerOrganizationDid || '').trim() !== String(criteria.consumerOrganizationDid || '').trim())
242
+ return false;
243
+ const requiredCapabilities = Array.from(new Set((criteria.requiredCapabilities || []).map((value) => String(value || '').trim()).filter(Boolean)));
244
+ if (requiredCapabilities.length > 0) {
245
+ const contractCapabilities = new Set((summary?.capabilities || []).map((value) => String(value || '').trim()));
246
+ if (!requiredCapabilities.every((value) => contractCapabilities.has(value)))
247
+ return false;
248
+ }
249
+ const purpose = String(criteria.purpose || '').trim();
250
+ if (purpose) {
251
+ const contractPurposes = new Set((summary?.purposes || []).map((value) => String(value || '').trim()));
252
+ if (contractPurposes.size > 0 && !contractPurposes.has(purpose))
253
+ return false;
254
+ }
255
+ return true;
256
+ }
257
+ export function getMatchingInterTenantAccessContractFromVpToken(vpToken, criteria) {
258
+ const credentials = getVpCredentials(vpToken);
259
+ return credentials.find((credential) => matchesInterTenantAccessContract(summarizeInterTenantAccessContract(credential), criteria));
260
+ }
261
+ function splitClaimCsv(value) {
262
+ return Array.from(new Set(String(value || '')
263
+ .split(',')
264
+ .map((item) => item.trim())
265
+ .filter(Boolean)));
266
+ }
267
+ function isConsentLikeRuleActive(rule, now) {
268
+ const nowMs = normalizeNow(now);
269
+ const startMs = Date.parse(String(rule[ClaimConsent.periodStart] || '').trim());
270
+ const endMs = Date.parse(String(rule[ClaimConsent.periodEnd] || '').trim());
271
+ if (!Number.isNaN(startMs) && startMs > nowMs)
272
+ return false;
273
+ if (!Number.isNaN(endMs) && endMs < nowMs)
274
+ return false;
275
+ return true;
276
+ }
277
+ /**
278
+ * Returns the public blockchain-safe reference that a reused consent-style
279
+ * authorization rule should store under `Consent.source-reference` when it
280
+ * delegates use of one inter-tenant contract VC to an employee/researcher.
281
+ *
282
+ * Source-of-truth order:
283
+ * - VC `id`
284
+ * - FHIR `Contract.identifier`
285
+ *
286
+ * In both cases the reference is normalized through the same blockchain-safe
287
+ * sanitization rule already used by consent-access assets.
288
+ */
289
+ export function getInterTenantAccessContractBlockchainReference(credential) {
290
+ if (!credential || typeof credential !== 'object')
291
+ return undefined;
292
+ const vcId = sanitizeBlockchainReference(credential.id);
293
+ if (vcId)
294
+ return vcId;
295
+ const summary = summarizeInterTenantAccessContract(credential);
296
+ return sanitizeBlockchainReference(summary?.identifier);
297
+ }
298
+ /**
299
+ * Evaluates one consent-style rule reused as an organization-to-employee
300
+ * delegation for inter-tenant contract usage.
301
+ *
302
+ * Shared semantic contract:
303
+ * - `Consent.subject` = consumer organization DID
304
+ * - `Consent.actor-identifier` = delegated employee/researcher identifier
305
+ * - `Consent.action` = allowed capabilities/scopes
306
+ * - `Consent.purpose` = allowed purpose
307
+ * - `Consent.source-reference` = blockchain-safe hash/reference of the
308
+ * underlying contract VC
309
+ */
310
+ export function matchesInterTenantContractAuthorizationConsentRule(rule, contractCredential, criteria) {
311
+ const summary = summarizeInterTenantAccessContract(contractCredential);
312
+ if (!isInterTenantAccessContractActive(summary, { now: criteria.now })) {
313
+ return false;
314
+ }
315
+ if (String(rule[ClaimConsent.decision] || '').trim() !== 'permit')
316
+ return false;
317
+ if (!isConsentLikeRuleActive(rule, criteria.now))
318
+ return false;
319
+ if (String(rule[ClaimConsent.subject] || '').trim() !== String(criteria.consumerOrganizationDid || '').trim())
320
+ return false;
321
+ if (String(rule[ClaimConsent.actorIdentifier] || '').trim() !== String(criteria.actorIdentifier || '').trim())
322
+ return false;
323
+ const expectedReference = getInterTenantAccessContractBlockchainReference(contractCredential);
324
+ const ruleReference = String(rule[ClaimConsent.sourceReference]
325
+ || rule[ClaimConsent.eventBasedOn]
326
+ || '').trim();
327
+ if (!expectedReference || ruleReference !== expectedReference)
328
+ return false;
329
+ const purpose = String(criteria.purpose || '').trim();
330
+ if (purpose) {
331
+ const rulePurpose = String(rule[ClaimConsent.purpose] || '').trim();
332
+ if (rulePurpose && rulePurpose !== purpose)
333
+ return false;
334
+ }
335
+ const actorRole = String(criteria.actorRole || '').trim();
336
+ if (actorRole) {
337
+ const allowedRoles = splitClaimCsv(rule[ClaimConsent.actorRole]);
338
+ if (allowedRoles.length > 0 && !allowedRoles.includes(actorRole))
339
+ return false;
340
+ }
341
+ const requiredCapabilities = Array.from(new Set((criteria.requiredCapabilities || []).map((value) => String(value || '').trim()).filter(Boolean)));
342
+ if (requiredCapabilities.length > 0) {
343
+ const allowedCapabilities = new Set(splitClaimCsv(rule[ClaimConsent.action]));
344
+ if (!requiredCapabilities.every((value) => allowedCapabilities.has(value) || allowedCapabilities.has('*')))
345
+ return false;
346
+ }
347
+ if (requiredCapabilities.length > 0) {
348
+ const contractCapabilities = new Set((summary?.capabilities || []).map((value) => String(value || '').trim()));
349
+ if (!requiredCapabilities.every((value) => contractCapabilities.has(value)))
350
+ return false;
351
+ }
352
+ if (purpose) {
353
+ const contractPurposes = new Set((summary?.purposes || []).map((value) => String(value || '').trim()));
354
+ if (contractPurposes.size > 0 && !contractPurposes.has(purpose))
355
+ return false;
356
+ }
357
+ return true;
358
+ }
@@ -0,0 +1,43 @@
1
+ import type { ClaimsRecord } from '../models/resource-document';
2
+ export type OrganizationAuthorizationUrnInput = Readonly<{
3
+ identifierType: string;
4
+ identifierValue: string;
5
+ }>;
6
+ export type MemberAuthorizationUrnInput = Readonly<{
7
+ organizationUrn?: string;
8
+ identifierType?: string;
9
+ identifierValue?: string;
10
+ memberId: string;
11
+ /** Optional compatibility input. Role must be persisted separately, not inside the canonical member URN. */
12
+ roleCode?: string;
13
+ }>;
14
+ /**
15
+ * Canonical organization authorization identifier used by ledger, consent-like
16
+ * rules, and inter-tenant authorization persistence.
17
+ *
18
+ * Format:
19
+ * - `urn:org:<identifierType-lowercase>:<identifierValue-as-canonical-input>`
20
+ *
21
+ * Examples:
22
+ * - `urn:org:tax:VATES-B12345678`
23
+ * - `urn:org:tax:acme-id`
24
+ */
25
+ export declare function buildOrganizationAuthorizationUrn(input: OrganizationAuthorizationUrnInput): string;
26
+ /**
27
+ * Accepts either the legacy `TYPE|VALUE` input or an already-canonical
28
+ * `urn:org:...` identifier and always returns the canonical URN form.
29
+ */
30
+ export declare function normalizeOrganizationAuthorizationUrn(input: string): string;
31
+ export declare function buildOrganizationAuthorizationUrnFromClaims(claims?: ClaimsRecord): string;
32
+ /**
33
+ * Canonical member authorization identifier rooted in one canonical
34
+ * `urn:org:...`.
35
+ *
36
+ * Format:
37
+ * - `urn:org:<type>:<value>:member:<memberId>`
38
+ *
39
+ * Important rule:
40
+ * - role must be stored as a separate claim, not inside the canonical member
41
+ * identifier
42
+ */
43
+ export declare function buildMemberAuthorizationUrn(input: MemberAuthorizationUrnInput): string;
@@ -0,0 +1,87 @@
1
+ // Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
2
+ import { ClaimsOrganizationSchemaorg } from '../constants/schemaorg.js';
3
+ const ORGANIZATION_URN_PREFIX = 'urn:org:';
4
+ function normalizeTypeSegment(identifierType) {
5
+ return String(identifierType || '').trim().toLowerCase();
6
+ }
7
+ function normalizeValueSegment(identifierValue) {
8
+ return String(identifierValue || '').trim();
9
+ }
10
+ /**
11
+ * Canonical organization authorization identifier used by ledger, consent-like
12
+ * rules, and inter-tenant authorization persistence.
13
+ *
14
+ * Format:
15
+ * - `urn:org:<identifierType-lowercase>:<identifierValue-as-canonical-input>`
16
+ *
17
+ * Examples:
18
+ * - `urn:org:tax:VATES-B12345678`
19
+ * - `urn:org:tax:acme-id`
20
+ */
21
+ export function buildOrganizationAuthorizationUrn(input) {
22
+ const identifierType = normalizeTypeSegment(input.identifierType);
23
+ const identifierValue = normalizeValueSegment(input.identifierValue);
24
+ if (!identifierType || !identifierValue) {
25
+ throw new Error('Organization authorization URN requires identifierType and identifierValue.');
26
+ }
27
+ return `${ORGANIZATION_URN_PREFIX}${identifierType}:${identifierValue}`;
28
+ }
29
+ /**
30
+ * Accepts either the legacy `TYPE|VALUE` input or an already-canonical
31
+ * `urn:org:...` identifier and always returns the canonical URN form.
32
+ */
33
+ export function normalizeOrganizationAuthorizationUrn(input) {
34
+ const normalized = String(input || '').trim();
35
+ if (!normalized) {
36
+ throw new Error('Organization authorization identifier cannot be empty.');
37
+ }
38
+ if (normalized.toLowerCase().startsWith(ORGANIZATION_URN_PREFIX)) {
39
+ const parts = normalized.split(':');
40
+ const identifierType = String(parts[2] || '').trim();
41
+ const value = parts.slice(3).join(':').trim();
42
+ return buildOrganizationAuthorizationUrn({
43
+ identifierType,
44
+ identifierValue: value,
45
+ });
46
+ }
47
+ const legacyMatch = normalized.match(/^([^|:]+)\|(.+)$/);
48
+ if (legacyMatch) {
49
+ return buildOrganizationAuthorizationUrn({
50
+ identifierType: legacyMatch[1],
51
+ identifierValue: legacyMatch[2],
52
+ });
53
+ }
54
+ throw new Error(`Unsupported organization authorization identifier format: ${normalized}`);
55
+ }
56
+ export function buildOrganizationAuthorizationUrnFromClaims(claims) {
57
+ const identifierType = String(claims?.[ClaimsOrganizationSchemaorg.identifierType] || '').trim();
58
+ const identifierValue = String(claims?.[ClaimsOrganizationSchemaorg.identifierValue] || '').trim();
59
+ return buildOrganizationAuthorizationUrn({
60
+ identifierType,
61
+ identifierValue,
62
+ });
63
+ }
64
+ /**
65
+ * Canonical member authorization identifier rooted in one canonical
66
+ * `urn:org:...`.
67
+ *
68
+ * Format:
69
+ * - `urn:org:<type>:<value>:member:<memberId>`
70
+ *
71
+ * Important rule:
72
+ * - role must be stored as a separate claim, not inside the canonical member
73
+ * identifier
74
+ */
75
+ export function buildMemberAuthorizationUrn(input) {
76
+ const organizationUrn = input.organizationUrn
77
+ ? normalizeOrganizationAuthorizationUrn(input.organizationUrn)
78
+ : buildOrganizationAuthorizationUrn({
79
+ identifierType: String(input.identifierType || '').trim(),
80
+ identifierValue: String(input.identifierValue || '').trim(),
81
+ });
82
+ const memberId = String(input.memberId || '').trim();
83
+ if (!memberId) {
84
+ throw new Error('Member authorization URN requires memberId.');
85
+ }
86
+ return `${organizationUrn}:member:${memberId}`;
87
+ }
@@ -0,0 +1,69 @@
1
+ import type { IWallet } from '../interfaces/IWallet';
2
+ import type { IProfileOutboxRepository } from '../interfaces/IProfileOutboxRepository';
3
+ import type { BackendMessageResponseRecord, BackendMessageTransport, ProfileOutboxMessageRecord, ProfileManagerMemProfile, QueueProfileMessageInput } from '../models/profile-manager';
4
+ import type { WalletQueuedMessage } from '../models/wallet';
5
+ import { BackendMessageManagerMem } from './backend-message-manager-mem';
6
+ export type ProfileManagerMemOptions = Readonly<{
7
+ profile: ProfileManagerMemProfile;
8
+ wallet: IWallet;
9
+ transport: BackendMessageTransport;
10
+ outboxRepository?: IProfileOutboxRepository;
11
+ }>;
12
+ /**
13
+ * Minimal memory-backed profile manager for low-level tests.
14
+ *
15
+ * Responsibility:
16
+ * - own one logical profile
17
+ * - ensure the profile keys exist in the wallet
18
+ * - delegate queue/submit/response operations to `BackendMessageManagerMem`
19
+ *
20
+ * Non-goal:
21
+ * - actor-specific orchestration belongs to higher SDK layers
22
+ */
23
+ export declare class ProfileManagerMem {
24
+ readonly profile: ProfileManagerMemProfile;
25
+ readonly wallet: IWallet;
26
+ readonly messageManager: BackendMessageManagerMem;
27
+ readonly outboxRepository: IProfileOutboxRepository;
28
+ constructor(options: ProfileManagerMemOptions);
29
+ /**
30
+ * Provisions the local profile keys before any queue or transport operation.
31
+ */
32
+ initialize(): Promise<void>;
33
+ /**
34
+ * Queues one outbound profile payload.
35
+ */
36
+ queueMessage(input: QueueProfileMessageInput): Promise<WalletQueuedMessage>;
37
+ /**
38
+ * Returns the current queued messages in processing order.
39
+ */
40
+ listQueuedMessages(): Promise<WalletQueuedMessage[]>;
41
+ /**
42
+ * Submits the next pending message and optionally stores the decoded response.
43
+ */
44
+ submitNextPendingMessage(recipientEncryptionJwk: Record<string, unknown>): Promise<{
45
+ message: WalletQueuedMessage;
46
+ response?: BackendMessageResponseRecord;
47
+ pending?: boolean;
48
+ }>;
49
+ /**
50
+ * Reads the latest decoded response for one thread id.
51
+ */
52
+ getResponseByThreadId(thid: string): BackendMessageResponseRecord | undefined;
53
+ /**
54
+ * Reads one decoded response by thread id from memory or repository storage.
55
+ */
56
+ readResponseByThreadId(thid: string): Promise<BackendMessageResponseRecord | undefined>;
57
+ /**
58
+ * Lists historical outbox records already submitted through this profile manager.
59
+ */
60
+ listOutboxHistory(): Promise<ProfileOutboxMessageRecord[]>;
61
+ /**
62
+ * Polls one asynchronous outbox record by id.
63
+ */
64
+ pollSubmittedMessage(messageId: string): Promise<{
65
+ pending: boolean;
66
+ response?: BackendMessageResponseRecord;
67
+ record: ProfileOutboxMessageRecord;
68
+ }>;
69
+ }
@@ -0,0 +1,79 @@
1
+ // Copyright 2026 Conectate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
2
+ import { BackendMessageManagerMem } from './backend-message-manager-mem.js';
3
+ import { MemoryProfileOutboxRepository } from './profile-outbox-memory-repository.js';
4
+ /**
5
+ * Minimal memory-backed profile manager for low-level tests.
6
+ *
7
+ * Responsibility:
8
+ * - own one logical profile
9
+ * - ensure the profile keys exist in the wallet
10
+ * - delegate queue/submit/response operations to `BackendMessageManagerMem`
11
+ *
12
+ * Non-goal:
13
+ * - actor-specific orchestration belongs to higher SDK layers
14
+ */
15
+ export class ProfileManagerMem {
16
+ profile;
17
+ wallet;
18
+ messageManager;
19
+ outboxRepository;
20
+ constructor(options) {
21
+ this.profile = options.profile;
22
+ this.wallet = options.wallet;
23
+ this.outboxRepository = options.outboxRepository ?? new MemoryProfileOutboxRepository();
24
+ this.messageManager = new BackendMessageManagerMem({
25
+ wallet: options.wallet,
26
+ entityId: options.profile.profileId,
27
+ transport: options.transport,
28
+ outboxRepository: this.outboxRepository,
29
+ });
30
+ }
31
+ /**
32
+ * Provisions the local profile keys before any queue or transport operation.
33
+ */
34
+ async initialize() {
35
+ await this.wallet.provisionKeys(this.profile.profileId);
36
+ }
37
+ /**
38
+ * Queues one outbound profile payload.
39
+ */
40
+ async queueMessage(input) {
41
+ return this.messageManager.queueMessage(input);
42
+ }
43
+ /**
44
+ * Returns the current queued messages in processing order.
45
+ */
46
+ async listQueuedMessages() {
47
+ return this.messageManager.listQueuedMessages();
48
+ }
49
+ /**
50
+ * Submits the next pending message and optionally stores the decoded response.
51
+ */
52
+ async submitNextPendingMessage(recipientEncryptionJwk) {
53
+ return this.messageManager.submitNextPendingMessage(recipientEncryptionJwk);
54
+ }
55
+ /**
56
+ * Reads the latest decoded response for one thread id.
57
+ */
58
+ getResponseByThreadId(thid) {
59
+ return this.messageManager.getResponseByThreadId(thid);
60
+ }
61
+ /**
62
+ * Reads one decoded response by thread id from memory or repository storage.
63
+ */
64
+ async readResponseByThreadId(thid) {
65
+ return this.messageManager.readResponseByThreadId(thid);
66
+ }
67
+ /**
68
+ * Lists historical outbox records already submitted through this profile manager.
69
+ */
70
+ async listOutboxHistory() {
71
+ return this.messageManager.listOutboxHistory();
72
+ }
73
+ /**
74
+ * Polls one asynchronous outbox record by id.
75
+ */
76
+ async pollSubmittedMessage(messageId) {
77
+ return this.messageManager.pollSubmittedMessage(messageId);
78
+ }
79
+ }
@@ -0,0 +1,34 @@
1
+ import type { IProfileOutboxRepository } from '../interfaces/IProfileOutboxRepository';
2
+ import type { BackendMessageResponseRecord, ProfileOutboxMessageRecord } from '../models/profile-manager';
3
+ /**
4
+ * In-memory profile outbox repository used by low-level tests and as the
5
+ * default history/response store for `ProfileManagerMem`.
6
+ */
7
+ export declare class MemoryProfileOutboxRepository implements IProfileOutboxRepository {
8
+ private readonly messages;
9
+ private readonly responses;
10
+ /**
11
+ * Stores one immutable message history record.
12
+ */
13
+ putMessage(record: ProfileOutboxMessageRecord): Promise<ProfileOutboxMessageRecord>;
14
+ /**
15
+ * Reads one message history record.
16
+ */
17
+ getMessage(messageId: string): Promise<ProfileOutboxMessageRecord | undefined>;
18
+ /**
19
+ * Lists all message history records in insertion order.
20
+ */
21
+ listMessages(): Promise<ProfileOutboxMessageRecord[]>;
22
+ /**
23
+ * Stores one decoded response record grouped by thread id.
24
+ */
25
+ putResponse(record: BackendMessageResponseRecord): Promise<BackendMessageResponseRecord>;
26
+ /**
27
+ * Returns the latest decoded response for the provided thread id.
28
+ */
29
+ getLatestResponseByThreadId(thid: string): Promise<BackendMessageResponseRecord | undefined>;
30
+ /**
31
+ * Lists all decoded responses across all threads.
32
+ */
33
+ listResponses(): Promise<BackendMessageResponseRecord[]>;
34
+ }