gdc-common-utils-ts 2.5.16 → 2.5.19

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.
@@ -37,8 +37,8 @@ export declare const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CLAIMS: Readonly<{
37
37
  readonly "Contract.applies-end": "2027-06-29T00:00:00.000Z";
38
38
  readonly "Contract.provider-organization": "did:web:api.acme.org";
39
39
  readonly "Contract.consumer-organization": "did:web:api.lab.org";
40
- readonly "Contract.provider-controller": "did:web:people.acme.org:controllers:primary";
41
- readonly "Contract.consumer-controller": "did:web:people.lab.org:controllers:primary";
40
+ readonly "Contract.provider-authorized-signatory": "did:web:people.acme.org:controllers:primary";
41
+ readonly "Contract.consumer-authorized-signatory": "did:web:people.lab.org:controllers:primary";
42
42
  readonly "Contract.security-label": "organization/Composition.rs";
43
43
  readonly "Contract.term-type": "HRESCH";
44
44
  readonly "Contract.instantiates-uri": "https://portal.example.org/files/contracts/inter-tenant-contract-acme-lab-001.pdf";
@@ -47,8 +47,8 @@ export const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CLAIMS = Object.freeze({
47
47
  [ClaimInterTenantAccessContract.appliesEnd]: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_VALID_UNTIL,
48
48
  [ClaimInterTenantAccessContract.providerOrganization]: EXAMPLE_API_ORGANIZATION_DID,
49
49
  [ClaimInterTenantAccessContract.consumerOrganization]: EXAMPLE_RESEARCH_API_ORGANIZATION_DID,
50
- [ClaimInterTenantAccessContract.providerController]: EXAMPLE_CONTROLLER_DID,
51
- [ClaimInterTenantAccessContract.consumerController]: EXAMPLE_RESEARCH_CONTROLLER_DID,
50
+ [ClaimInterTenantAccessContract.providerAuthorizedSignatory]: EXAMPLE_CONTROLLER_DID,
51
+ [ClaimInterTenantAccessContract.consumerAuthorizedSignatory]: EXAMPLE_RESEARCH_CONTROLLER_DID,
52
52
  [ClaimInterTenantAccessContract.capability]: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_SCOPE,
53
53
  [ClaimInterTenantAccessContract.purpose]: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_PURPOSE,
54
54
  [ClaimInterTenantAccessContract.instantiatesUri]: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_AGREEMENT_PDF_URL,
@@ -61,6 +61,20 @@ export const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CREDENTIAL = Object.freeze(bui
61
61
  validUntil: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_VALID_UNTIL,
62
62
  additionalCredential: {
63
63
  id: EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_ID,
64
+ proof: [
65
+ {
66
+ type: 'JsonWebSignature2020',
67
+ proofPurpose: 'contractAgreement',
68
+ verificationMethod: `${EXAMPLE_CONTROLLER_DID}#contract-agreement`,
69
+ jws: 'provider-authorized-signatory-proof',
70
+ },
71
+ {
72
+ type: 'JsonWebSignature2020',
73
+ proofPurpose: 'contractAgreement',
74
+ verificationMethod: `${EXAMPLE_RESEARCH_CONTROLLER_DID}#contract-agreement`,
75
+ jws: 'consumer-authorized-signatory-proof',
76
+ },
77
+ ],
64
78
  },
65
79
  }));
66
80
  export const EXAMPLE_INTER_TENANT_ACCESS_CONTRACT_CONTEXT = Object.freeze({
@@ -1 +1,2 @@
1
+ export declare const EXAMPLE_LEGAL_ORGANIZATION_SERVICE_TYPE: string;
1
2
  export declare const EXAMPLE_LEGAL_ORGANIZATION_VERIFICATION_TRANSACTION_BUNDLE: import("..").BundleJsonApi<import("..").ErrorEntry | import("..").BundleEntry>;
@@ -30,6 +30,15 @@ const exampleControllerBinding = {
30
30
  }
31
31
  : {}),
32
32
  };
33
+ /** Canonical capabilities reused by organization-onboarding editor tests. */
34
+ const exampleLegalOrganizationServiceType = serializeServiceCapabilityTokens([
35
+ ServiceCapability.IndexProvider,
36
+ ServiceCapability.DigitalTwinReader,
37
+ ]);
38
+ if (!exampleLegalOrganizationServiceType) {
39
+ throw new Error('Canonical legal-organization example capabilities must not be empty.');
40
+ }
41
+ export const EXAMPLE_LEGAL_ORGANIZATION_SERVICE_TYPE = exampleLegalOrganizationServiceType;
33
42
  export const EXAMPLE_LEGAL_ORGANIZATION_VERIFICATION_TRANSACTION_BUNDLE = buildLegalOrganizationVerificationTransactionBundle({
34
43
  claims: {
35
44
  '@context': 'org.schema',
@@ -42,10 +51,7 @@ export const EXAMPLE_LEGAL_ORGANIZATION_VERIFICATION_TRANSACTION_BUNDLE = buildL
42
51
  [ClaimsServiceSchemaorg.category]: EXAMPLE_SECTOR,
43
52
  [ClaimsServiceSchemaorg.identifier]: EXAMPLE_TENANT_SERVICE_DID,
44
53
  [ClaimsServiceSchemaorg.url]: `https://operator.example.net/acme/cds-${String(EXAMPLE_JURISDICTION).toLowerCase()}/v1/${EXAMPLE_SECTOR}`,
45
- [ClaimsServiceSchemaorg.serviceType]: serializeServiceCapabilityTokens([
46
- ServiceCapability.IndexProvider,
47
- ServiceCapability.DigitalTwinReader,
48
- ]),
54
+ [ClaimsServiceSchemaorg.serviceType]: EXAMPLE_LEGAL_ORGANIZATION_SERVICE_TYPE,
49
55
  },
50
56
  controller: exampleControllerBinding,
51
57
  organization: {
@@ -109,6 +109,7 @@ export declare const EXAMPLE_TENANT_ENABLE_MESSAGE: {
109
109
  };
110
110
  readonly claims: {
111
111
  readonly '@context': "org.schema";
112
+ readonly "org.schema.Organization.identifier.additionalType": "taxID";
112
113
  readonly "org.schema.Organization.identifier.value": "{{tenantTaxId}}";
113
114
  readonly "org.schema.Organization.taxID": "{{tenantTaxId}}";
114
115
  readonly "org.schema.Organization.identifier": "{{tenantDid}}";
@@ -124,6 +125,7 @@ export declare const EXAMPLE_TENANT_DISABLE_MESSAGE: {
124
125
  };
125
126
  readonly claims: {
126
127
  readonly '@context': "org.schema";
128
+ readonly "org.schema.Organization.identifier.additionalType": "taxID";
127
129
  readonly "org.schema.Organization.identifier.value": "{{tenantTaxId}}";
128
130
  readonly "org.schema.Organization.taxID": "{{tenantTaxId}}";
129
131
  readonly "org.schema.Organization.identifier": "{{tenantDid}}";
@@ -144,6 +146,7 @@ export declare const EXAMPLE_TENANT_DELETE_MESSAGE: {
144
146
  };
145
147
  readonly claims: {
146
148
  readonly '@context': "org.schema";
149
+ readonly "org.schema.Organization.identifier.additionalType": "taxID";
147
150
  readonly "org.schema.Organization.identifier.value": "{{tenantTaxId}}";
148
151
  readonly "org.schema.Organization.taxID": "{{tenantTaxId}}";
149
152
  readonly "org.schema.Organization.identifier": "{{tenantDid}}";
@@ -391,6 +394,7 @@ export declare const EXAMPLE_LIFECYCLE_REFERENCE: {
391
394
  };
392
395
  readonly claims: {
393
396
  readonly '@context': "org.schema";
397
+ readonly "org.schema.Organization.identifier.additionalType": "taxID";
394
398
  readonly "org.schema.Organization.identifier.value": "{{tenantTaxId}}";
395
399
  readonly "org.schema.Organization.taxID": "{{tenantTaxId}}";
396
400
  readonly "org.schema.Organization.identifier": "{{tenantDid}}";
@@ -406,6 +410,7 @@ export declare const EXAMPLE_LIFECYCLE_REFERENCE: {
406
410
  };
407
411
  readonly claims: {
408
412
  readonly '@context': "org.schema";
413
+ readonly "org.schema.Organization.identifier.additionalType": "taxID";
409
414
  readonly "org.schema.Organization.identifier.value": "{{tenantTaxId}}";
410
415
  readonly "org.schema.Organization.taxID": "{{tenantTaxId}}";
411
416
  readonly "org.schema.Organization.identifier": "{{tenantDid}}";
@@ -426,6 +431,7 @@ export declare const EXAMPLE_LIFECYCLE_REFERENCE: {
426
431
  };
427
432
  readonly claims: {
428
433
  readonly '@context': "org.schema";
434
+ readonly "org.schema.Organization.identifier.additionalType": "taxID";
429
435
  readonly "org.schema.Organization.identifier.value": "{{tenantTaxId}}";
430
436
  readonly "org.schema.Organization.taxID": "{{tenantTaxId}}";
431
437
  readonly "org.schema.Organization.identifier": "{{tenantDid}}";
@@ -117,6 +117,7 @@ export const EXAMPLE_TENANT_ENABLE_MESSAGE = {
117
117
  },
118
118
  claims: {
119
119
  '@context': 'org.schema',
120
+ [ClaimsOrganizationSchemaorg.identifierType]: 'taxID',
120
121
  [ClaimsOrganizationSchemaorg.identifierValue]: EXAMPLE_LIFECYCLE_PLACEHOLDERS.tenantTaxId,
121
122
  [ClaimsOrganizationSchemaorg.taxId]: EXAMPLE_LIFECYCLE_PLACEHOLDERS.tenantTaxId,
122
123
  [ClaimsOrganizationSchemaorg.identifier]: EXAMPLE_LIFECYCLE_PLACEHOLDERS.tenantDid,
@@ -132,6 +133,7 @@ export const EXAMPLE_TENANT_DISABLE_MESSAGE = {
132
133
  },
133
134
  claims: {
134
135
  '@context': 'org.schema',
136
+ [ClaimsOrganizationSchemaorg.identifierType]: 'taxID',
135
137
  [ClaimsOrganizationSchemaorg.identifierValue]: EXAMPLE_LIFECYCLE_PLACEHOLDERS.tenantTaxId,
136
138
  [ClaimsOrganizationSchemaorg.taxId]: EXAMPLE_LIFECYCLE_PLACEHOLDERS.tenantTaxId,
137
139
  [ClaimsOrganizationSchemaorg.identifier]: EXAMPLE_LIFECYCLE_PLACEHOLDERS.tenantDid,
@@ -152,6 +154,7 @@ export const EXAMPLE_TENANT_DELETE_MESSAGE = {
152
154
  },
153
155
  claims: {
154
156
  '@context': 'org.schema',
157
+ [ClaimsOrganizationSchemaorg.identifierType]: 'taxID',
155
158
  [ClaimsOrganizationSchemaorg.identifierValue]: EXAMPLE_LIFECYCLE_PLACEHOLDERS.tenantTaxId,
156
159
  [ClaimsOrganizationSchemaorg.taxId]: EXAMPLE_LIFECYCLE_PLACEHOLDERS.tenantTaxId,
157
160
  [ClaimsOrganizationSchemaorg.identifier]: EXAMPLE_LIFECYCLE_PLACEHOLDERS.tenantDid,
@@ -44,13 +44,13 @@ export declare enum ClaimInterTenantAccessContract {
44
44
  * - in the current example this is `did:web:api.lab.org`
45
45
  */
46
46
  consumerOrganization = "Contract.consumer-organization",
47
- /**
48
- * DID of the controller who signed on behalf of the provider organization.
49
- */
47
+ /** DID of the person legally authorized to sign for the provider. */
48
+ providerAuthorizedSignatory = "Contract.provider-authorized-signatory",
49
+ /** DID of the person legally authorized to sign for the consumer. */
50
+ consumerAuthorizedSignatory = "Contract.consumer-authorized-signatory",
51
+ /** @deprecated Read-only compatibility alias. Technical control is not legal signing authority. */
50
52
  providerController = "Contract.provider-controller",
51
- /**
52
- * DID of the controller who signed on behalf of the consumer organization.
53
- */
53
+ /** @deprecated Read-only compatibility alias. Technical control is not legal signing authority. */
54
54
  consumerController = "Contract.consumer-controller",
55
55
  /**
56
56
  * Allowed operational capability, expressed with the same vocabulary used by
@@ -89,8 +89,14 @@ export type InterTenantAccessContractSummary = Readonly<{
89
89
  appliesEnd?: string;
90
90
  providerOrganizationDid?: string;
91
91
  consumerOrganizationDid?: string;
92
+ providerAuthorizedSignatoryDid?: string;
93
+ consumerAuthorizedSignatoryDid?: string;
94
+ /** @deprecated Use providerAuthorizedSignatoryDid. */
92
95
  providerControllerDid?: string;
96
+ /** @deprecated Use consumerAuthorizedSignatoryDid. */
93
97
  consumerControllerDid?: string;
98
+ hasProviderContractAgreementProof: boolean;
99
+ hasConsumerContractAgreementProof: boolean;
94
100
  capabilities: readonly string[];
95
101
  purposes: readonly string[];
96
102
  }>;
@@ -100,6 +106,8 @@ export type InterTenantAccessContractMatchCriteria = Readonly<{
100
106
  requiredCapabilities?: readonly string[];
101
107
  purpose?: string;
102
108
  now?: string | Date;
109
+ /** Defaults to true for access-token authorization. */
110
+ requireBilateralContractAgreementProofs?: boolean;
103
111
  }>;
104
112
  export type InterTenantContractAuthorizationConsentCriteria = Readonly<{
105
113
  consumerOrganizationDid: string;
@@ -46,13 +46,13 @@ export var ClaimInterTenantAccessContract;
46
46
  * - in the current example this is `did:web:api.lab.org`
47
47
  */
48
48
  ClaimInterTenantAccessContract["consumerOrganization"] = "Contract.consumer-organization";
49
- /**
50
- * DID of the controller who signed on behalf of the provider organization.
51
- */
49
+ /** DID of the person legally authorized to sign for the provider. */
50
+ ClaimInterTenantAccessContract["providerAuthorizedSignatory"] = "Contract.provider-authorized-signatory";
51
+ /** DID of the person legally authorized to sign for the consumer. */
52
+ ClaimInterTenantAccessContract["consumerAuthorizedSignatory"] = "Contract.consumer-authorized-signatory";
53
+ /** @deprecated Read-only compatibility alias. Technical control is not legal signing authority. */
52
54
  ClaimInterTenantAccessContract["providerController"] = "Contract.provider-controller";
53
- /**
54
- * DID of the controller who signed on behalf of the consumer organization.
55
- */
55
+ /** @deprecated Read-only compatibility alias. Technical control is not legal signing authority. */
56
56
  ClaimInterTenantAccessContract["consumerController"] = "Contract.consumer-controller";
57
57
  /**
58
58
  * Allowed operational capability, expressed with the same vocabulary used by
@@ -8,6 +8,11 @@ export type ActivationServiceAuthorizationPolicyError = {
8
8
  code: ActivationServiceAuthorizationPolicyErrorCode;
9
9
  message: string;
10
10
  };
11
+ /** Typed legal identifier used to bind one organization across credentials and lifecycle commands. */
12
+ export type LegalOrganizationIdentifier = Readonly<{
13
+ type: string;
14
+ value: string;
15
+ }>;
11
16
  /**
12
17
  * Extracts `credentialSubject` from a VC-like object.
13
18
  *
@@ -20,6 +25,26 @@ export declare function extractCredentialSubject(credential: unknown): Record<st
20
25
  * @param value Raw tax identifier value.
21
26
  */
22
27
  export declare function normalizeTaxIdentifier(value: unknown): string | undefined;
28
+ /**
29
+ * Normalizes a typed organization identifier for equality checks without
30
+ * changing the canonical value stored in credentials or tenant records.
31
+ */
32
+ export declare function normalizeLegalOrganizationIdentifier(identifier: Readonly<{
33
+ type?: unknown;
34
+ value?: unknown;
35
+ }> | undefined): LegalOrganizationIdentifier | undefined;
36
+ /** Extracts the typed legal identifier from an organization credential subject. */
37
+ export declare function extractOrganizationLegalIdentifier(organizationCredential: unknown): LegalOrganizationIdentifier | undefined;
38
+ /** Extracts the typed organization identifier from a representative `memberOf` binding. */
39
+ export declare function extractRepresentativeMemberOfLegalIdentifier(representativeCredential: unknown): LegalOrganizationIdentifier | undefined;
40
+ /** Compares typed legal identifiers without discarding punctuation from their values. */
41
+ export declare function legalOrganizationIdentifiersMatch(left: Readonly<{
42
+ type?: unknown;
43
+ value?: unknown;
44
+ }> | undefined, right: Readonly<{
45
+ type?: unknown;
46
+ value?: unknown;
47
+ }> | undefined): boolean;
23
48
  /**
24
49
  * Extracts an organization tax id from a VC-like organization credential.
25
50
  *
@@ -31,6 +31,44 @@ export function normalizeTaxIdentifier(value) {
31
31
  const normalized = String(value || '').trim().toUpperCase();
32
32
  return normalized || undefined;
33
33
  }
34
+ /**
35
+ * Normalizes a typed organization identifier for equality checks without
36
+ * changing the canonical value stored in credentials or tenant records.
37
+ */
38
+ export function normalizeLegalOrganizationIdentifier(identifier) {
39
+ const rawType = String(identifier?.type || '').trim().toUpperCase();
40
+ const type = rawType === 'TAXID' || rawType === 'TAX_ID' ? 'TAX' : rawType;
41
+ const value = String(identifier?.value || '').trim().toUpperCase();
42
+ return type && value ? { type, value } : undefined;
43
+ }
44
+ function extractLegalOrganizationIdentifier(value) {
45
+ const source = asObject(value) || {};
46
+ const identifier = asObject(source.identifier) || {};
47
+ const taxId = source.taxID ?? source.taxId;
48
+ return normalizeLegalOrganizationIdentifier(taxId !== undefined
49
+ ? { type: 'taxID', value: taxId }
50
+ : {
51
+ type: identifier.additionalType ?? identifier.type ?? source.identifierType,
52
+ value: identifier.value ?? source.identifierValue,
53
+ });
54
+ }
55
+ /** Extracts the typed legal identifier from an organization credential subject. */
56
+ export function extractOrganizationLegalIdentifier(organizationCredential) {
57
+ return extractLegalOrganizationIdentifier(extractCredentialSubject(organizationCredential));
58
+ }
59
+ /** Extracts the typed organization identifier from a representative `memberOf` binding. */
60
+ export function extractRepresentativeMemberOfLegalIdentifier(representativeCredential) {
61
+ const subject = extractCredentialSubject(representativeCredential) || {};
62
+ return extractLegalOrganizationIdentifier(subject.memberOf);
63
+ }
64
+ /** Compares typed legal identifiers without discarding punctuation from their values. */
65
+ export function legalOrganizationIdentifiersMatch(left, right) {
66
+ const normalizedLeft = normalizeLegalOrganizationIdentifier(left);
67
+ const normalizedRight = normalizeLegalOrganizationIdentifier(right);
68
+ return Boolean(normalizedLeft && normalizedRight
69
+ && normalizedLeft.type === normalizedRight.type
70
+ && normalizedLeft.value === normalizedRight.value);
71
+ }
34
72
  /**
35
73
  * Extracts an organization tax id from a VC-like organization credential.
36
74
  *
@@ -39,10 +77,8 @@ export function normalizeTaxIdentifier(value) {
39
77
  export function extractOrganizationTaxId(organizationCredential) {
40
78
  const subject = extractCredentialSubject(organizationCredential) || {};
41
79
  const identifier = asObject(subject.identifier);
42
- return (normalizeTaxIdentifier(subject.taxID)
43
- || normalizeTaxIdentifier(subject.taxId)
44
- || normalizeTaxIdentifier(identifier?.value)
45
- || normalizeTaxIdentifier(subject.identifierValue));
80
+ const value = String(subject.taxID ?? subject.taxId ?? identifier?.value ?? '').trim();
81
+ return normalizeTaxIdentifier(value) || undefined;
46
82
  }
47
83
  /**
48
84
  * Extracts the `memberOf` tax id from a representative credential.
@@ -53,10 +89,8 @@ export function extractRepresentativeMemberOfTaxId(representativeCredential) {
53
89
  const subject = extractCredentialSubject(representativeCredential) || {};
54
90
  const memberOf = asObject(subject.memberOf) || {};
55
91
  const identifier = asObject(memberOf.identifier);
56
- return (normalizeTaxIdentifier(memberOf.taxID)
57
- || normalizeTaxIdentifier(memberOf.taxId)
58
- || normalizeTaxIdentifier(identifier?.value)
59
- || normalizeTaxIdentifier(memberOf.identifierValue));
92
+ const value = String(memberOf.taxID ?? memberOf.taxId ?? identifier?.value ?? '').trim();
93
+ return normalizeTaxIdentifier(value) || undefined;
60
94
  }
61
95
  /**
62
96
  * Extracts the representative role code from `credentialSubject.hasOccupation`.
@@ -86,6 +86,7 @@ function deriveRuleEntriesFromConsentEntry(entry, options, bundleEvidenceReferen
86
86
  for (const purpose of purposes) {
87
87
  for (const role of normalizedRoles) {
88
88
  const ruleId = buildConsentAtomicRuleId({
89
+ sourceConsentIdentifier,
89
90
  subject,
90
91
  decision,
91
92
  actorIdentifier,
@@ -32,9 +32,12 @@ export type ConsentDuplicateRuleConflict = Readonly<{
32
32
  *
33
33
  * The identifier intentionally excludes section/resource targets so that two
34
34
  * entries that authorize different target sets for the same subject, decision,
35
- * actor, purpose, and role collide into the same duplicate group.
35
+ * actor, purpose, and role collide into the same duplicate group. It includes
36
+ * the stable source Consent identifier so independent portal or study
37
+ * consents never collapse into the same rule.
36
38
  */
37
39
  export declare function buildConsentAtomicRuleId(input: Readonly<{
40
+ sourceConsentIdentifier?: string;
38
41
  subject?: string;
39
42
  decision?: string;
40
43
  actorIdentifier: string;
@@ -9,10 +9,13 @@ const RESOURCE_TYPE_PREFIX = 'resourceType:';
9
9
  *
10
10
  * The identifier intentionally excludes section/resource targets so that two
11
11
  * entries that authorize different target sets for the same subject, decision,
12
- * actor, purpose, and role collide into the same duplicate group.
12
+ * actor, purpose, and role collide into the same duplicate group. It includes
13
+ * the stable source Consent identifier so independent portal or study
14
+ * consents never collapse into the same rule.
13
15
  */
14
16
  export function buildConsentAtomicRuleId(input) {
15
17
  return [
18
+ normalizeRuleKeyPart(input.sourceConsentIdentifier),
16
19
  normalizeRuleKeyPart(input.subject),
17
20
  normalizeDecision(input.decision).toLowerCase(),
18
21
  normalizeRuleKeyPart(input.actorIdentifier),
@@ -102,6 +105,7 @@ function deriveAtomicConsentRules(entry, entryIndex) {
102
105
  for (const role of normalizedRoles) {
103
106
  derived.push({
104
107
  ruleId: buildConsentAtomicRuleId({
108
+ sourceConsentIdentifier: identifier,
105
109
  subject,
106
110
  decision,
107
111
  actorIdentifier,
@@ -1,6 +1,11 @@
1
1
  import type { InterTenantAccessContractClaims, InterTenantContractAuthorizationConsentCriteria, InterTenantAccessContractMatchCriteria, InterTenantAccessContractSummary } from '../models/inter-tenant-access-contract';
2
2
  import type { VerifiableCredentialV2 } from '../models/verifiable-credential';
3
3
  type ContractLike = Record<string, any>;
4
+ /**
5
+ * Builds the canonical FHIR Contract. Signer entries identify legally
6
+ * authorized signatories; technical tenant controllers are not inferred as
7
+ * signatories. Historical controller role labels are accepted only on read.
8
+ */
4
9
  export declare function buildInterTenantAccessContractResource(claims: InterTenantAccessContractClaims): ContractLike;
5
10
  export declare function buildInterTenantAccessContractCredential(input: Readonly<{
6
11
  claims: InterTenantAccessContractClaims;
@@ -9,10 +14,16 @@ export declare function buildInterTenantAccessContractCredential(input: Readonly
9
14
  validUntil?: string;
10
15
  additionalCredential?: Record<string, unknown>;
11
16
  }>): VerifiableCredentialV2;
17
+ /** Summarizes tenant, authority and bilateral proof evidence from one Contract VC. */
12
18
  export declare function summarizeInterTenantAccessContract(credential: unknown): InterTenantAccessContractSummary | undefined;
13
19
  export declare function isInterTenantAccessContractActive(summary: InterTenantAccessContractSummary | undefined, options?: Readonly<{
14
20
  now?: string | Date;
15
21
  }>): boolean;
22
+ /**
23
+ * Matches an active contract and, by default, requires contractAgreement
24
+ * proofs whose verification methods belong to both named signatories.
25
+ * Cryptographic proof verification must occur before this structural match.
26
+ */
16
27
  export declare function matchesInterTenantAccessContract(summary: InterTenantAccessContractSummary | undefined, criteria: InterTenantAccessContractMatchCriteria): boolean;
17
28
  export declare function getMatchingInterTenantAccessContractFromVpToken(vpToken: string, criteria: InterTenantAccessContractMatchCriteria): Record<string, unknown> | undefined;
18
29
  /**
@@ -7,8 +7,10 @@ import { getVpCredentials } from './vp-token.js';
7
7
  const ACTIVE_CONTRACT_STATUSES = new Set(['executed', 'amended', 'appended']);
8
8
  const PROVIDER_ROLE = 'provider';
9
9
  const CONSUMER_ROLE = 'consumer';
10
- const PROVIDER_CONTROLLER_ROLE = 'provider-controller';
11
- const CONSUMER_CONTROLLER_ROLE = 'consumer-controller';
10
+ const PROVIDER_AUTHORIZED_SIGNATORY_ROLE = 'provider-authorized-signatory';
11
+ const CONSUMER_AUTHORIZED_SIGNATORY_ROLE = 'consumer-authorized-signatory';
12
+ const LEGACY_PROVIDER_CONTROLLER_ROLE = 'provider-controller';
13
+ const LEGACY_CONSUMER_CONTROLLER_ROLE = 'consumer-controller';
12
14
  function splitCsv(value) {
13
15
  return Array.from(new Set(String(value || '')
14
16
  .split(',')
@@ -108,6 +110,20 @@ function extractSignerReference(contract, expectedRole) {
108
110
  }
109
111
  return undefined;
110
112
  }
113
+ function extractSignerReferenceWithAliases(contract, canonicalRole, legacyRole) {
114
+ return extractSignerReference(contract, canonicalRole) || extractSignerReference(contract, legacyRole);
115
+ }
116
+ function hasContractAgreementProof(credential, signatoryDid) {
117
+ if (!signatoryDid)
118
+ return false;
119
+ const proofs = Array.isArray(credential.proof) ? credential.proof : credential.proof ? [credential.proof] : [];
120
+ return proofs.some((proof) => {
121
+ if (String(proof?.proofPurpose || '').trim() !== 'contractAgreement')
122
+ return false;
123
+ const verificationMethod = String(proof?.verificationMethod || '').trim();
124
+ return verificationMethod === signatoryDid || verificationMethod.startsWith(`${signatoryDid}#`);
125
+ });
126
+ }
111
127
  function extractPurposes(contract) {
112
128
  const terms = Array.isArray(contract.term) ? contract.term : contract.term ? [contract.term] : [];
113
129
  return Array.from(new Set(terms.flatMap((term) => {
@@ -135,6 +151,11 @@ function normalizeNow(input) {
135
151
  return new Date(input).getTime();
136
152
  return Date.now();
137
153
  }
154
+ /**
155
+ * Builds the canonical FHIR Contract. Signer entries identify legally
156
+ * authorized signatories; technical tenant controllers are not inferred as
157
+ * signatories. Historical controller role labels are accepted only on read.
158
+ */
138
159
  export function buildInterTenantAccessContractResource(claims) {
139
160
  const identifier = readClaimWithAliases(claims, ClaimInterTenantAccessContract.identifier) || '';
140
161
  const status = readClaimWithAliases(claims, ClaimInterTenantAccessContract.status) || 'executed';
@@ -144,8 +165,8 @@ export function buildInterTenantAccessContractResource(claims) {
144
165
  const appliesEnd = readClaimWithAliases(claims, ClaimInterTenantAccessContract.appliesEnd, ['Contract.applies.end']) || '';
145
166
  const providerOrganization = readClaimWithAliases(claims, ClaimInterTenantAccessContract.providerOrganization, ['Contract.term.offer.party.provider']) || '';
146
167
  const consumerOrganization = readClaimWithAliases(claims, ClaimInterTenantAccessContract.consumerOrganization, ['Contract.term.offer.party.consumer']) || '';
147
- const providerController = readClaimWithAliases(claims, ClaimInterTenantAccessContract.providerController, ['Contract.signer.provider']) || '';
148
- const consumerController = readClaimWithAliases(claims, ClaimInterTenantAccessContract.consumerController, ['Contract.signer.consumer']) || '';
168
+ const providerAuthorizedSignatory = readClaimWithAliases(claims, ClaimInterTenantAccessContract.providerAuthorizedSignatory, [ClaimInterTenantAccessContract.providerController, 'Contract.signer.provider']) || '';
169
+ const consumerAuthorizedSignatory = readClaimWithAliases(claims, ClaimInterTenantAccessContract.consumerAuthorizedSignatory, [ClaimInterTenantAccessContract.consumerController, 'Contract.signer.consumer']) || '';
149
170
  const instantiatesUri = readClaimWithAliases(claims, ClaimInterTenantAccessContract.instantiatesUri, ['Contract.instantiatesUri']) || '';
150
171
  const capabilities = splitCsv(readClaimWithAliases(claims, ClaimInterTenantAccessContract.capability, ['Contract.term.offer.securityLabel']));
151
172
  const purposes = splitCsv(readClaimWithAliases(claims, ClaimInterTenantAccessContract.purpose, ['Contract.term.type']));
@@ -162,16 +183,16 @@ export function buildInterTenantAccessContractResource(claims) {
162
183
  ...(appliesEnd ? { end: appliesEnd } : {}),
163
184
  },
164
185
  signer: [
165
- providerController
186
+ providerAuthorizedSignatory
166
187
  ? {
167
- type: [{ text: PROVIDER_CONTROLLER_ROLE }],
168
- party: { reference: providerController },
188
+ type: [{ text: PROVIDER_AUTHORIZED_SIGNATORY_ROLE }],
189
+ party: { reference: providerAuthorizedSignatory },
169
190
  }
170
191
  : undefined,
171
- consumerController
192
+ consumerAuthorizedSignatory
172
193
  ? {
173
- type: [{ text: CONSUMER_CONTROLLER_ROLE }],
174
- party: { reference: consumerController },
194
+ type: [{ text: CONSUMER_AUTHORIZED_SIGNATORY_ROLE }],
195
+ party: { reference: consumerAuthorizedSignatory },
175
196
  }
176
197
  : undefined,
177
198
  ].filter(Boolean),
@@ -211,6 +232,7 @@ export function buildInterTenantAccessContractCredential(input) {
211
232
  ...(input.additionalCredential || {}),
212
233
  };
213
234
  }
235
+ /** Summarizes tenant, authority and bilateral proof evidence from one Contract VC. */
214
236
  export function summarizeInterTenantAccessContract(credential) {
215
237
  if (!credential || typeof credential !== 'object')
216
238
  return undefined;
@@ -220,6 +242,8 @@ export function summarizeInterTenantAccessContract(credential) {
220
242
  const subject = (credential.credentialSubject || {});
221
243
  if (String(subject.resourceType || '').trim() !== 'Contract')
222
244
  return undefined;
245
+ const providerAuthorizedSignatoryDid = extractSignerReferenceWithAliases(subject, PROVIDER_AUTHORIZED_SIGNATORY_ROLE, LEGACY_PROVIDER_CONTROLLER_ROLE);
246
+ const consumerAuthorizedSignatoryDid = extractSignerReferenceWithAliases(subject, CONSUMER_AUTHORIZED_SIGNATORY_ROLE, LEGACY_CONSUMER_CONTROLLER_ROLE);
223
247
  return {
224
248
  identifier: firstIdentifierValue(subject.identifier) || String(subject.id || '').trim() || undefined,
225
249
  status: String(subject.status || '').trim() || undefined,
@@ -228,8 +252,12 @@ export function summarizeInterTenantAccessContract(credential) {
228
252
  appliesEnd: String(subject.applies?.end || '').trim() || undefined,
229
253
  providerOrganizationDid: extractOfferPartyReference(subject, PROVIDER_ROLE),
230
254
  consumerOrganizationDid: extractOfferPartyReference(subject, CONSUMER_ROLE),
231
- providerControllerDid: extractSignerReference(subject, PROVIDER_CONTROLLER_ROLE),
232
- consumerControllerDid: extractSignerReference(subject, CONSUMER_CONTROLLER_ROLE),
255
+ providerAuthorizedSignatoryDid,
256
+ consumerAuthorizedSignatoryDid,
257
+ providerControllerDid: providerAuthorizedSignatoryDid,
258
+ consumerControllerDid: consumerAuthorizedSignatoryDid,
259
+ hasProviderContractAgreementProof: hasContractAgreementProof(credential, providerAuthorizedSignatoryDid),
260
+ hasConsumerContractAgreementProof: hasContractAgreementProof(credential, consumerAuthorizedSignatoryDid),
233
261
  capabilities: extractCapabilities(subject),
234
262
  purposes: extractPurposes(subject),
235
263
  };
@@ -249,6 +277,11 @@ export function isInterTenantAccessContractActive(summary, options = {}) {
249
277
  return false;
250
278
  return true;
251
279
  }
280
+ /**
281
+ * Matches an active contract and, by default, requires contractAgreement
282
+ * proofs whose verification methods belong to both named signatories.
283
+ * Cryptographic proof verification must occur before this structural match.
284
+ */
252
285
  export function matchesInterTenantAccessContract(summary, criteria) {
253
286
  if (!isInterTenantAccessContractActive(summary, { now: criteria.now }))
254
287
  return false;
@@ -256,6 +289,9 @@ export function matchesInterTenantAccessContract(summary, criteria) {
256
289
  return false;
257
290
  if (String(summary?.consumerOrganizationDid || '').trim() !== String(criteria.consumerOrganizationDid || '').trim())
258
291
  return false;
292
+ if ((criteria.requireBilateralContractAgreementProofs ?? true)
293
+ && (!summary?.hasProviderContractAgreementProof || !summary?.hasConsumerContractAgreementProof))
294
+ return false;
259
295
  const requiredCapabilities = Array.from(new Set((criteria.requiredCapabilities || []).map((value) => String(value || '').trim()).filter(Boolean)));
260
296
  if (requiredCapabilities.length > 0) {
261
297
  const contractCapabilities = new Set((summary?.capabilities || []).map((value) => String(value || '').trim()));
@@ -10,6 +10,8 @@ export type LegalOrganizationFormTemplateFields = Readonly<{
10
10
  controllerRole?: string;
11
11
  serviceCategory?: string;
12
12
  serviceIdentifier?: string;
13
+ /** Canonical CSV of capabilities stored in `org.schema.Service.serviceType`. */
14
+ serviceType?: string;
13
15
  serviceUrl?: string;
14
16
  tenantAlias?: string;
15
17
  }>;
@@ -90,6 +92,8 @@ export interface LegalOrganizationOnboardingFacade {
90
92
  getServiceCategory(fields: LegalOrganizationFormTemplateFields): string | undefined;
91
93
  setServiceIdentifier(fields: LegalOrganizationFormTemplateFields, value: string): LegalOrganizationFormTemplateFields;
92
94
  getServiceIdentifier(fields: LegalOrganizationFormTemplateFields): string | undefined;
95
+ setServiceType(fields: LegalOrganizationFormTemplateFields, value: string): LegalOrganizationFormTemplateFields;
96
+ getServiceType(fields: LegalOrganizationFormTemplateFields): string | undefined;
93
97
  setServiceUrl(fields: LegalOrganizationFormTemplateFields, value: string): LegalOrganizationFormTemplateFields;
94
98
  getServiceUrl(fields: LegalOrganizationFormTemplateFields): string | undefined;
95
99
  /**
@@ -136,6 +140,8 @@ export interface LegalOrganizationOnboardingEditor {
136
140
  getServiceCategory(): string | undefined;
137
141
  setServiceIdentifier(value: string): LegalOrganizationOnboardingEditor;
138
142
  getServiceIdentifier(): string | undefined;
143
+ setServiceType(value: string): LegalOrganizationOnboardingEditor;
144
+ getServiceType(): string | undefined;
139
145
  setServiceUrl(value: string): LegalOrganizationOnboardingEditor;
140
146
  getServiceUrl(): string | undefined;
141
147
  setTenantAlias(value: string): LegalOrganizationOnboardingEditor;
@@ -51,6 +51,8 @@ function createEditorFromFacade(facade, initial) {
51
51
  getServiceCategory() { return facade.getServiceCategory(formFields); },
52
52
  setServiceIdentifier(value) { formFields = facade.setServiceIdentifier(formFields, value); return editor; },
53
53
  getServiceIdentifier() { return facade.getServiceIdentifier(formFields); },
54
+ setServiceType(value) { formFields = facade.setServiceType(formFields, value); return editor; },
55
+ getServiceType() { return facade.getServiceType(formFields); },
54
56
  setServiceUrl(value) { formFields = facade.setServiceUrl(formFields, value); return editor; },
55
57
  getServiceUrl() { return facade.getServiceUrl(formFields); },
56
58
  setTenantAlias(value) { formFields = facade.setTenantAlias(formFields, value); return editor; },
@@ -133,6 +135,12 @@ export function createLegalOrganizationOnboardingFacade() {
133
135
  getServiceIdentifier(fields) {
134
136
  return normalizeOptionalText(fields.serviceIdentifier);
135
137
  },
138
+ setServiceType(fields, value) {
139
+ return patchFields(fields, { serviceType: normalizeText(value) });
140
+ },
141
+ getServiceType(fields) {
142
+ return normalizeOptionalText(fields.serviceType);
143
+ },
136
144
  setServiceUrl(fields, value) {
137
145
  return patchFields(fields, { serviceUrl: normalizeText(value) });
138
146
  },
@@ -176,6 +184,9 @@ export function createLegalOrganizationOnboardingFacade() {
176
184
  if (normalizeOptionalText(fields.serviceIdentifier)) {
177
185
  claims[ClaimsServiceSchemaorg.identifier] = normalizeText(fields.serviceIdentifier);
178
186
  }
187
+ if (normalizeOptionalText(fields.serviceType)) {
188
+ claims[ClaimsServiceSchemaorg.serviceType] = normalizeText(fields.serviceType);
189
+ }
179
190
  if (normalizeOptionalText(fields.serviceUrl)) {
180
191
  claims[ClaimsServiceSchemaorg.url] = normalizeText(fields.serviceUrl);
181
192
  }
@@ -236,6 +247,14 @@ export function createLegalOrganizationOnboardingFacade() {
236
247
  field: 'serviceCategory',
237
248
  });
238
249
  }
250
+ if (!normalizeOptionalText(fields.serviceType)) {
251
+ issues.push({
252
+ severity: 'error',
253
+ code: 'missing-service-type',
254
+ message: 'serviceType is required because GW authorizes activation capabilities from it.',
255
+ field: 'serviceType',
256
+ });
257
+ }
239
258
  if (!normalizeOptionalText(fields.controllerRole)) {
240
259
  issues.push({
241
260
  severity: 'warning',
@@ -40,6 +40,8 @@ export declare class OrganizationLifecycleEditor {
40
40
  setContext(context: string): this;
41
41
  setIdentifier(identifier: string): this;
42
42
  setIdentifierValue(identifierValue: string): this;
43
+ /** Sets the jurisdiction-defined identifier type independently from its complete value. */
44
+ setIdentifierType(identifierType: string): this;
43
45
  setTaxId(taxId: string): this;
44
46
  setOperation(operation: OrganizationLifecycleOperation): this;
45
47
  setRequestType(requestType: string): this;
@@ -47,6 +49,8 @@ export declare class OrganizationLifecycleEditor {
47
49
  getContext(): string | undefined;
48
50
  getIdentifier(): string | undefined;
49
51
  getIdentifierValue(): string | undefined;
52
+ /** Returns the jurisdiction-defined identifier type used by lifecycle authorization. */
53
+ getIdentifierType(): string | undefined;
50
54
  getTaxId(): string | undefined;
51
55
  getOperation(): OrganizationLifecycleOperation;
52
56
  getRequestType(): string | undefined;
@@ -73,6 +73,16 @@ export class OrganizationLifecycleEditor {
73
73
  });
74
74
  return this;
75
75
  }
76
+ /** Sets the jurisdiction-defined identifier type independently from its complete value. */
77
+ setIdentifierType(identifierType) {
78
+ this.draft = patchDraft(this.draft, {
79
+ claims: {
80
+ ...this.draft.claims,
81
+ [ClaimsOrganizationSchemaorg.identifierType]: String(identifierType).trim(),
82
+ },
83
+ });
84
+ return this;
85
+ }
76
86
  setTaxId(taxId) {
77
87
  this.draft = patchDraft(this.draft, {
78
88
  claims: {
@@ -103,6 +113,10 @@ export class OrganizationLifecycleEditor {
103
113
  getIdentifierValue() {
104
114
  return normalizeText(this.draft.claims[ClaimsOrganizationSchemaorg.identifierValue]) || undefined;
105
115
  }
116
+ /** Returns the jurisdiction-defined identifier type used by lifecycle authorization. */
117
+ getIdentifierType() {
118
+ return normalizeText(this.draft.claims[ClaimsOrganizationSchemaorg.identifierType]) || undefined;
119
+ }
106
120
  getTaxId() {
107
121
  return normalizeText(this.draft.claims[ClaimsOrganizationSchemaorg.taxId]) || undefined;
108
122
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdc-common-utils-ts",
3
- "version": "2.5.16",
3
+ "version": "2.5.19",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },