gdc-common-utils-ts 2.0.2 → 2.0.5

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 (40) hide show
  1. package/README.md +45 -0
  2. package/dist/constants/verifiable-credentials.d.ts +6 -0
  3. package/dist/constants/verifiable-credentials.js +6 -0
  4. package/dist/examples/employee.d.ts +15 -0
  5. package/dist/examples/employee.js +15 -0
  6. package/dist/examples/ica-verify-response.d.ts +5 -0
  7. package/dist/examples/ica-verify-response.js +6 -0
  8. package/dist/examples/index.d.ts +2 -0
  9. package/dist/examples/index.js +2 -0
  10. package/dist/examples/individual-controller.d.ts +25 -0
  11. package/dist/examples/individual-controller.js +23 -0
  12. package/dist/examples/legal-organization-verification-transaction.d.ts +1 -0
  13. package/dist/examples/legal-organization-verification-transaction.js +76 -0
  14. package/dist/examples/organization-did-binding.d.ts +1 -0
  15. package/dist/examples/organization-did-binding.js +14 -0
  16. package/dist/examples/shared.d.ts +10 -0
  17. package/dist/examples/shared.js +11 -1
  18. package/dist/utils/bundle-reader.d.ts +2 -0
  19. package/dist/utils/bundle-reader.js +14 -0
  20. package/dist/utils/clinical-bundle-summary.d.ts +20 -0
  21. package/dist/utils/clinical-bundle-summary.js +34 -0
  22. package/dist/utils/didcomm-submit-policy.d.ts +20 -3
  23. package/dist/utils/didcomm-submit-policy.js +37 -6
  24. package/dist/utils/didcomm-submit.d.ts +10 -3
  25. package/dist/utils/didcomm-submit.js +12 -5
  26. package/dist/utils/family-organization-summary.d.ts +24 -0
  27. package/dist/utils/family-organization-summary.js +59 -0
  28. package/dist/utils/index.d.ts +6 -0
  29. package/dist/utils/index.js +6 -0
  30. package/dist/utils/legal-organization-onboarding-editor.d.ts +156 -0
  31. package/dist/utils/legal-organization-onboarding-editor.js +350 -0
  32. package/dist/utils/legal-organization-verification-transaction.d.ts +130 -0
  33. package/dist/utils/legal-organization-verification-transaction.js +118 -0
  34. package/dist/utils/organization-did-binding.d.ts +60 -0
  35. package/dist/utils/organization-did-binding.js +103 -0
  36. package/dist/utils/professional-smart.d.ts +21 -0
  37. package/dist/utils/professional-smart.js +37 -0
  38. package/dist/utils/related-person-list.d.ts +14 -0
  39. package/dist/utils/related-person-list.js +31 -0
  40. package/package.json +1 -1
package/README.md CHANGED
@@ -10,6 +10,26 @@ Short rule:
10
10
  - do not add ad hoc literals in `101` tests when `gdc-common-utils-ts` can own
11
11
  the reusable value instead
12
12
 
13
+ ## Shared Workspace
14
+
15
+ Recommended local layout for the shared ICA/GDC repos and fixture PDFs:
16
+
17
+ ```text
18
+ ~/GITS/gdc-workspace/
19
+ dataspace-ica-ts/
20
+ ica-client-sdk-ts/
21
+ gdc-common-utils-ts/
22
+ examples/
23
+ <example-pdf-1>.pdf
24
+ <example-pdf-2>.pdf
25
+ ```
26
+
27
+ This is recommended because:
28
+
29
+ - cross-repo docs and fixture-based tests often refer to sibling repos
30
+ - real PDF examples are expected under `~/GITS/gdc-workspace/examples/`
31
+ - keeping a single shared workspace reduces path drift between repos
32
+
13
33
  Employee shared examples live in `src/examples/employee.ts`.
14
34
  Employee pure helper functions live in `src/utils/employee.ts`.
15
35
 
@@ -61,6 +81,31 @@ They are not interchangeable:
61
81
  Production-grade flows should prefer ICA-issued representative VCs that carry
62
82
  both dimensions.
63
83
 
84
+ ## Legal Organization Verification Transaction
85
+
86
+ The first host-side legal-organization onboarding step now has one canonical
87
+ shared payload builder in this package:
88
+
89
+ - `buildLegalOrganizationVerificationTransactionBundle(...)`
90
+ - `EXAMPLE_LEGAL_ORGANIZATION_VERIFICATION_TRANSACTION_BUNDLE`
91
+
92
+ This builder owns the business payload only:
93
+
94
+ - signed PDF evidence attachment references
95
+ - `controller.publicKeyJwk` as the controller business binding key
96
+ - optional `organization.publicKeyJwk`
97
+ - legal representative payload
98
+ - `meta.claims` business claims
99
+
100
+ It intentionally does not own:
101
+
102
+ - `fetch`
103
+ - polling
104
+ - JOSE transport execution
105
+ - BFF/frontend runtime crypto
106
+
107
+ Those runtime concerns belong in `gdc-sdk-node-ts`, `gdc-sdk-front-ts`, or GW.
108
+
64
109
  Step by step:
65
110
 
66
111
  1. ICA verifies the signed PDF and emits the representative VC.
@@ -30,5 +30,11 @@ export declare const ActivationCredentialTypes: Readonly<{
30
30
  LegalRepresentativeCredential: "LegalRepresentativeCredential";
31
31
  PersonCredential: "PersonCredential";
32
32
  }>;
33
+ /**
34
+ * Canonical credential subtype names used by professional/member access flows.
35
+ */
36
+ export declare const ProfessionalCredentialTypes: Readonly<{
37
+ EmployeeCredential: "EmployeeCredential";
38
+ }>;
33
39
  export declare const ORGANIZATION_ACTIVATION_VC_TYPES: readonly ("OrganizationCredential" | "LegalOrganizationCredential")[];
34
40
  export declare const REPRESENTATIVE_ACTIVATION_VC_TYPES: readonly ("LegalRepresentativeCredential" | "PersonCredential")[];
@@ -32,6 +32,12 @@ export const ActivationCredentialTypes = Object.freeze({
32
32
  LegalRepresentativeCredential: 'LegalRepresentativeCredential',
33
33
  PersonCredential: 'PersonCredential',
34
34
  });
35
+ /**
36
+ * Canonical credential subtype names used by professional/member access flows.
37
+ */
38
+ export const ProfessionalCredentialTypes = Object.freeze({
39
+ EmployeeCredential: 'EmployeeCredential',
40
+ });
35
41
  export const ORGANIZATION_ACTIVATION_VC_TYPES = Object.freeze([
36
42
  ActivationCredentialTypes.OrganizationCredential,
37
43
  ActivationCredentialTypes.LegalOrganizationCredential,
@@ -39,3 +39,18 @@ export declare const EXAMPLE_EMPLOYEE_DIRECTORY_RECORDS: readonly [Readonly<{
39
39
  status: "active" | "inactive" | "purged";
40
40
  }>];
41
41
  export declare function buildExampleEmployeeClaims(record: ExampleEmployeeRecord): Readonly<Record<string, string>>;
42
+ /**
43
+ * Canonical employee search/list response body reused by runtime and doc tests.
44
+ *
45
+ * This keeps one stable GW-style envelope for high-level tutorials that want
46
+ * to show employee directory flows without reauthoring `meta.claims` by hand.
47
+ */
48
+ export declare const EXAMPLE_EMPLOYEE_SEARCH_RESPONSE_BODY: Readonly<{
49
+ readonly data: {
50
+ id: string;
51
+ meta: {
52
+ status: "active" | "inactive" | "purged";
53
+ claims: Readonly<Record<string, string>>;
54
+ };
55
+ }[];
56
+ }>;
@@ -37,3 +37,18 @@ export function buildExampleEmployeeClaims(record) {
37
37
  [ClaimsPersonSchemaorg.hasOccupationalRoleValue]: record.role,
38
38
  });
39
39
  }
40
+ /**
41
+ * Canonical employee search/list response body reused by runtime and doc tests.
42
+ *
43
+ * This keeps one stable GW-style envelope for high-level tutorials that want
44
+ * to show employee directory flows without reauthoring `meta.claims` by hand.
45
+ */
46
+ export const EXAMPLE_EMPLOYEE_SEARCH_RESPONSE_BODY = Object.freeze({
47
+ data: EXAMPLE_EMPLOYEE_DIRECTORY_RECORDS.map((record) => ({
48
+ id: record.identifier,
49
+ meta: {
50
+ status: record.status,
51
+ claims: buildExampleEmployeeClaims(record),
52
+ },
53
+ })),
54
+ });
@@ -136,6 +136,7 @@ export declare const EXAMPLE_VERIFY_RESPONSE_PERSON_PUBLIC_KEY_JWK: Readonly<{
136
136
  alg: "ES384";
137
137
  kid: "controller-es384-001";
138
138
  }>;
139
+ export declare const EXAMPLE_VERIFY_RESPONSE_ORG_AUTHORIZED_CATEGORY: "health-care";
139
140
  export declare const EXAMPLE_VERIFY_RESPONSE_ORG_CREDENTIAL: Readonly<{
140
141
  id: "urn:uuid:org-vc-001";
141
142
  '@context': ("https://www.w3.org/ns/credentials/v2" | "https://schema.org")[];
@@ -154,6 +155,10 @@ export declare const EXAMPLE_VERIFY_RESPONSE_ORG_CREDENTIAL: Readonly<{
154
155
  url: "health-care.provider.example.org";
155
156
  alternateName: string;
156
157
  additionalType: "sector=onehealth;section=dataprovider;kind=clinic;action=_index-provider,_research-provider";
158
+ makesOffer: {
159
+ '@type': string;
160
+ category: "health-care";
161
+ };
157
162
  address: {
158
163
  '@type': "PostalAddress";
159
164
  addressCountry: "ES";
@@ -2,6 +2,7 @@
2
2
  // Always create JSDoc, do not use strings inline in keys nor values, use types instead, and reuse the data test examples.
3
3
  import { ActivationCredentialTypes, W3cCredentialContexts, W3cCredentialTypes, } from '../constants/verifiable-credentials.js';
4
4
  import { ResourceTypesFhirR4 } from '../constants/fhir-resource-types.js';
5
+ import { DataspaceSectors } from '../constants/sectors.js';
5
6
  import { IssueSeverity, IssueType } from '../models/issue.js';
6
7
  import { EXAMPLE_DEFAULT_ICA_DID, EXAMPLE_BUNDLE_RESOURCE_TYPE, EXAMPLE_PROVIDER_DOMAIN, EXAMPLE_PROVIDER_LEGAL_NAME, EXAMPLE_PROVIDER_TAX_ID, EXAMPLE_TENANT_SERVICE_DID, } from './shared.js';
7
8
  import { EXAMPLE_ORG_CONTROLLER_SIGNING_KEY_ID, EXAMPLE_REPRESENTATIVE_IDENTIFIER, EXAMPLE_REPRESENTATIVE_SAME_AS, EXAMPLE_REPRESENTATIVE_SUBJECT_URN, } from './ica-activation-proof.js';
@@ -102,6 +103,7 @@ export const EXAMPLE_VERIFY_RESPONSE_PERSON_PUBLIC_KEY_JWK = Object.freeze({
102
103
  alg: 'ES384',
103
104
  kid: EXAMPLE_VERIFY_RESPONSE_PERSON_KID,
104
105
  });
106
+ export const EXAMPLE_VERIFY_RESPONSE_ORG_AUTHORIZED_CATEGORY = DataspaceSectors.HealthCare;
105
107
  export const EXAMPLE_VERIFY_RESPONSE_ORG_CREDENTIAL = Object.freeze({
106
108
  id: EXAMPLE_VERIFY_RESPONSE_ORG_VC_ID,
107
109
  '@context': [W3cCredentialContexts.V2, EXAMPLE_VERIFY_RESPONSE_SCHEMA_ORG_CONTEXT],
@@ -120,6 +122,10 @@ export const EXAMPLE_VERIFY_RESPONSE_ORG_CREDENTIAL = Object.freeze({
120
122
  url: EXAMPLE_PROVIDER_DOMAIN,
121
123
  alternateName: 'example-provider',
122
124
  additionalType: EXAMPLE_VERIFY_RESPONSE_ORG_ADDITIONAL_TYPE,
125
+ makesOffer: {
126
+ '@type': 'Offer',
127
+ category: EXAMPLE_VERIFY_RESPONSE_ORG_AUTHORIZED_CATEGORY,
128
+ },
123
129
  address: {
124
130
  '@type': EXAMPLE_VERIFY_RESPONSE_ADDRESS_TYPE,
125
131
  addressCountry: EXAMPLE_VERIFY_RESPONSE_ADDRESS_COUNTRY,
@@ -3,6 +3,8 @@ export * from './ica-activation-proof';
3
3
  export * from './ica-verify-response';
4
4
  export * from './dataspace-discovery';
5
5
  export * from './organization-controller';
6
+ export * from './organization-did-binding';
7
+ export * from './legal-organization-verification-transaction';
6
8
  export * from './individual-controller';
7
9
  export * from './professional';
8
10
  export * from './employee';
@@ -3,6 +3,8 @@ export * from './ica-activation-proof.js';
3
3
  export * from './ica-verify-response.js';
4
4
  export * from './dataspace-discovery.js';
5
5
  export * from './organization-controller.js';
6
+ export * from './organization-did-binding.js';
7
+ export * from './legal-organization-verification-transaction.js';
6
8
  export * from './individual-controller.js';
7
9
  export * from './professional.js';
8
10
  export * from './employee.js';
@@ -98,3 +98,28 @@ export declare const EXAMPLE_DIGITAL_TWIN_COMPOSITION_INPUT: {
98
98
  };
99
99
  export declare const EXAMPLE_CLINICAL_BUNDLE_SEARCH_INPUT: ExampleClinicalBundleSearchInput;
100
100
  export declare const EXAMPLE_LATEST_IPS_SEARCH_INPUT: ExampleLatestIpsSearchInput;
101
+ export declare const EXAMPLE_FAMILY_ORGANIZATION_SEARCH_INPUT: {
102
+ readonly controllerPhone: "+34600000001";
103
+ readonly usualname: "Ana";
104
+ readonly birthDate: "2010-05-20";
105
+ };
106
+ export declare const EXAMPLE_FAMILY_ORGANIZATION_SEARCH_RESPONSE_BODY: {
107
+ readonly body: {
108
+ readonly data: readonly [{
109
+ readonly type: "Family-search-result-v1.0";
110
+ readonly meta: {
111
+ readonly claims: {
112
+ readonly 'org.schema.FamilyRegistration.status': "already_exists";
113
+ readonly 'org.schema.Offer.identifier': "offer-uuid-001";
114
+ readonly 'org.schema.Organization.identifier.value': "org-uuid-001";
115
+ readonly 'org.schema.Organization.alternateName': "Ana";
116
+ readonly 'org.schema.Organization.owner.telephone': "+34600000001";
117
+ readonly 'org.schema.Organization.foundingDate': "2010-05-20";
118
+ };
119
+ };
120
+ readonly resource: {
121
+ readonly id: "org-uuid-001";
122
+ };
123
+ }];
124
+ };
125
+ };
@@ -69,3 +69,26 @@ export const EXAMPLE_CLINICAL_BUNDLE_SEARCH_INPUT = {
69
69
  export const EXAMPLE_LATEST_IPS_SEARCH_INPUT = {
70
70
  subject: EXAMPLE_SUBJECT_DID,
71
71
  };
72
+ export const EXAMPLE_FAMILY_ORGANIZATION_SEARCH_INPUT = {
73
+ controllerPhone: '+34600000001',
74
+ usualname: 'Ana',
75
+ birthDate: '2010-05-20',
76
+ };
77
+ export const EXAMPLE_FAMILY_ORGANIZATION_SEARCH_RESPONSE_BODY = {
78
+ body: {
79
+ data: [{
80
+ type: 'Family-search-result-v1.0',
81
+ meta: {
82
+ claims: {
83
+ 'org.schema.FamilyRegistration.status': 'already_exists',
84
+ 'org.schema.Offer.identifier': 'offer-uuid-001',
85
+ 'org.schema.Organization.identifier.value': 'org-uuid-001',
86
+ 'org.schema.Organization.alternateName': EXAMPLE_FAMILY_ORGANIZATION_SEARCH_INPUT.usualname,
87
+ 'org.schema.Organization.owner.telephone': EXAMPLE_FAMILY_ORGANIZATION_SEARCH_INPUT.controllerPhone,
88
+ 'org.schema.Organization.foundingDate': EXAMPLE_FAMILY_ORGANIZATION_SEARCH_INPUT.birthDate,
89
+ },
90
+ },
91
+ resource: { id: 'org-uuid-001' },
92
+ }],
93
+ },
94
+ };
@@ -0,0 +1 @@
1
+ export declare const EXAMPLE_LEGAL_ORGANIZATION_VERIFICATION_TRANSACTION_BUNDLE: import("..").BundleJsonApi<import("..").ErrorEntry | import("..").BundleEntry>;
@@ -0,0 +1,76 @@
1
+ // Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
2
+ import { ClaimsOrganizationSchemaorg, ClaimsPersonSchemaorg, ClaimsServiceSchemaorg, } from '../constants/schemaorg.js';
3
+ import { serializeServiceCapabilityTokens, ServiceCapability, } from '../constants/service-capabilities.js';
4
+ import { buildLegalOrganizationVerificationTransactionBundle, } from '../utils/legal-organization-verification-transaction.js';
5
+ import { EXAMPLE_CONTROLLER_BINDING, EXAMPLE_EMAIL_CONTROLLER_ORG, EXAMPLE_JURISDICTION, EXAMPLE_ORGANIZATION_LEGAL_NAME, EXAMPLE_LEGAL_ORGANIZATION_TAX_ID, EXAMPLE_SECTOR, EXAMPLE_SIGNED_TERMS_PDF_URL, EXAMPLE_TENANT_SERVICE_DID, } from './shared.js';
6
+ /**
7
+ * Canonical host-onboarding transaction example for the new ICA verification
8
+ * first step.
9
+ *
10
+ * Why this exists:
11
+ * - BFF/portal integrations need one stable example of the GW CORE request
12
+ * that wraps an ICA `_verify`
13
+ * - the example keeps the controller business binding separate from any
14
+ * technical DIDComm communication key
15
+ *
16
+ * Contract note:
17
+ * - the same request must already declare the requested tenant service
18
+ * capabilities because GW uses them to prepare the pending Offer that the
19
+ * client will later confirm via `Order/_batch`
20
+ */
21
+ const exampleControllerBinding = {
22
+ did: EXAMPLE_CONTROLLER_BINDING.did,
23
+ sameAs: EXAMPLE_CONTROLLER_BINDING.sameAs,
24
+ publicKeyJwk: { ...EXAMPLE_CONTROLLER_BINDING.publicKeyJwk },
25
+ ...(EXAMPLE_CONTROLLER_BINDING.jwks
26
+ ? {
27
+ jwks: {
28
+ keys: EXAMPLE_CONTROLLER_BINDING.jwks.keys.map((item) => ({ ...item })),
29
+ },
30
+ }
31
+ : {}),
32
+ };
33
+ export const EXAMPLE_LEGAL_ORGANIZATION_VERIFICATION_TRANSACTION_BUNDLE = buildLegalOrganizationVerificationTransactionBundle({
34
+ claims: {
35
+ '@context': 'org.schema',
36
+ [ClaimsOrganizationSchemaorg.legalName]: EXAMPLE_ORGANIZATION_LEGAL_NAME,
37
+ [ClaimsOrganizationSchemaorg.identifierType]: 'taxID',
38
+ [ClaimsOrganizationSchemaorg.identifierValue]: EXAMPLE_LEGAL_ORGANIZATION_TAX_ID,
39
+ [ClaimsOrganizationSchemaorg.taxId]: EXAMPLE_LEGAL_ORGANIZATION_TAX_ID,
40
+ [ClaimsOrganizationSchemaorg.addressCountry]: EXAMPLE_JURISDICTION,
41
+ [ClaimsPersonSchemaorg.email]: EXAMPLE_EMAIL_CONTROLLER_ORG,
42
+ [ClaimsServiceSchemaorg.category]: EXAMPLE_SECTOR,
43
+ [ClaimsServiceSchemaorg.identifier]: EXAMPLE_TENANT_SERVICE_DID,
44
+ [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
+ ]),
49
+ },
50
+ controller: exampleControllerBinding,
51
+ organization: {
52
+ did: EXAMPLE_TENANT_SERVICE_DID,
53
+ publicKeyJwk: {
54
+ kid: 'organization-signing-es384-001',
55
+ kty: 'EC',
56
+ crv: 'P-384',
57
+ x: '<organization-sign-x>',
58
+ y: '<organization-sign-y>',
59
+ alg: 'ES384',
60
+ use: 'sig',
61
+ },
62
+ },
63
+ legalRepresentativePayload: {
64
+ email: EXAMPLE_EMAIL_CONTROLLER_ORG,
65
+ },
66
+ verification: {
67
+ resourceType: 'contract',
68
+ },
69
+ attachments: [{
70
+ id: 'signed-terms-pdf-001',
71
+ media_type: 'application/pdf',
72
+ data: {
73
+ links: [EXAMPLE_SIGNED_TERMS_PDF_URL],
74
+ },
75
+ }],
76
+ });
@@ -0,0 +1 @@
1
+ export declare const EXAMPLE_ORGANIZATION_DID_BINDING_BUNDLE: import("..").BundleJsonApi<import("..").ErrorEntry | import("..").BundleEntry>;
@@ -0,0 +1,14 @@
1
+ // Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
2
+ import { buildOrganizationDidBindingBundle } from '../utils/organization-did-binding.js';
3
+ import { EXAMPLE_CONTROLLER_BINDING, EXAMPLE_TENANT_SERVICE_DID, } from './shared.js';
4
+ export const EXAMPLE_ORGANIZATION_DID_BINDING_BUNDLE = buildOrganizationDidBindingBundle({
5
+ organization: {
6
+ url: [
7
+ 'https://provider.example.org',
8
+ EXAMPLE_TENANT_SERVICE_DID,
9
+ ],
10
+ },
11
+ controller: {
12
+ sameAs: EXAMPLE_CONTROLLER_BINDING.sameAs,
13
+ },
14
+ });
@@ -17,6 +17,8 @@ export declare const EXAMPLE_NETWORK_TYPE: "test";
17
17
  export declare const EXAMPLE_ROUTE_VERSION: "v1";
18
18
  export declare const EXAMPLE_SECTOR: "health-care";
19
19
  export declare const EXAMPLE_EMAIL_CONTROLLER_ORG: "controller@acme.org";
20
+ export declare const EXAMPLE_ORGANIZATION_LEGAL_NAME: "ACME HEALTH SL";
21
+ export declare const EXAMPLE_LEGAL_ORGANIZATION_TAX_ID: "VATES-B00112233";
20
22
  export declare const EXAMPLE_EMAIL_CONTROLLER_INDIVIDUAL: "ana.parent@example.org";
21
23
  export declare const EXAMPLE_INTEROPERABLE_CONTEXT_FHIR_API: "org.hl7.fhir.api";
22
24
  export declare const EXAMPLE_SELF_REGISTERED_INDIVIDUAL_ALTERNATE_NAME: "Jane";
@@ -27,6 +29,7 @@ export declare const EXAMPLE_SELF_REGISTERED_INDIVIDUAL_BIRTH_YEAR: "1980";
27
29
  export declare const EXAMPLE_REGISTERED_SUBJECT_ALTERNATE_NAME: "Doraemon";
28
30
  export declare const EXAMPLE_REGISTERED_SUBJECT_BIRTH_YEAR: "2020";
29
31
  export declare const EXAMPLE_PDF_CONSENT_DATE: "2026-06-08";
32
+ export declare const EXAMPLE_SIGNED_TERMS_PDF_URL: "https://portal.example.org/files/legal-organization-signed-terms.pdf";
30
33
  /**
31
34
  * Public provider domain selected during autodiscovery.
32
35
  *
@@ -88,6 +91,13 @@ export declare const EXAMPLE_TENANT_ROUTE_CONTEXT: {
88
91
  export declare const EXAMPLE_HOST_ROUTE_CONTEXT: {
89
92
  readonly hostCoverageScope: "EU";
90
93
  readonly jurisdiction: "ES";
94
+ /**
95
+ * Host route segment used in `/host/cds-{hostCoverageScope}/v1/{hostNetwork}/...`.
96
+ *
97
+ * This is intentionally not the tenant business sector.
98
+ */
99
+ readonly hostNetwork: "test";
100
+ /** @deprecated Use `hostNetwork`. */
91
101
  readonly sector: "test";
92
102
  };
93
103
  export declare const EXAMPLE_CONTROLLER_DID: "did:web:people.acme.org:controllers:primary";
@@ -32,6 +32,8 @@ export const EXAMPLE_NETWORK_TYPE = HostNetworkTypes.Test;
32
32
  export const EXAMPLE_ROUTE_VERSION = 'v1';
33
33
  export const EXAMPLE_SECTOR = DataspaceSectors.HealthCare;
34
34
  export const EXAMPLE_EMAIL_CONTROLLER_ORG = 'controller@acme.org';
35
+ export const EXAMPLE_ORGANIZATION_LEGAL_NAME = 'ACME HEALTH SL';
36
+ export const EXAMPLE_LEGAL_ORGANIZATION_TAX_ID = 'VATES-B00112233';
35
37
  export const EXAMPLE_EMAIL_CONTROLLER_INDIVIDUAL = 'ana.parent@example.org';
36
38
  export const EXAMPLE_INTEROPERABLE_CONTEXT_FHIR_API = Format.FHIR_API;
37
39
  export const EXAMPLE_SELF_REGISTERED_INDIVIDUAL_ALTERNATE_NAME = 'Jane';
@@ -42,6 +44,7 @@ export const EXAMPLE_SELF_REGISTERED_INDIVIDUAL_BIRTH_YEAR = '1980';
42
44
  export const EXAMPLE_REGISTERED_SUBJECT_ALTERNATE_NAME = 'Doraemon';
43
45
  export const EXAMPLE_REGISTERED_SUBJECT_BIRTH_YEAR = '2020';
44
46
  export const EXAMPLE_PDF_CONSENT_DATE = '2026-06-08';
47
+ export const EXAMPLE_SIGNED_TERMS_PDF_URL = 'https://portal.example.org/files/legal-organization-signed-terms.pdf';
45
48
  /**
46
49
  * Public provider domain selected during autodiscovery.
47
50
  *
@@ -103,7 +106,14 @@ export const EXAMPLE_TENANT_ROUTE_CONTEXT = {
103
106
  export const EXAMPLE_HOST_ROUTE_CONTEXT = {
104
107
  hostCoverageScope: EXAMPLE_HOST_COVERAGE_SCOPE,
105
108
  jurisdiction: EXAMPLE_JURISDICTION,
106
- sector: HostNetworkTypes.Test,
109
+ /**
110
+ * Host route segment used in `/host/cds-{hostCoverageScope}/v1/{hostNetwork}/...`.
111
+ *
112
+ * This is intentionally not the tenant business sector.
113
+ */
114
+ hostNetwork: EXAMPLE_NETWORK_TYPE,
115
+ /** @deprecated Use `hostNetwork`. */
116
+ sector: EXAMPLE_NETWORK_TYPE,
107
117
  };
108
118
  export const EXAMPLE_CONTROLLER_DID = 'did:web:people.acme.org:controllers:primary';
109
119
  export const EXAMPLE_CONTROLLER_EMAIL = EXAMPLE_EMAIL_CONTROLLER_ORG;
@@ -84,3 +84,5 @@ export declare class BundleReader {
84
84
  private buildSeverityBucket;
85
85
  private resolveEntryIdentifier;
86
86
  }
87
+ export declare function unwrapBundleLikeResponseBody(input: unknown): Record<string, unknown>;
88
+ export declare function readFirstBundleResourceFromResponseBody(input: unknown): Record<string, unknown> | undefined;
@@ -221,3 +221,17 @@ export class BundleReader {
221
221
  function normalizeOptionalString(value) {
222
222
  return typeof value === 'string' && value.trim() ? value.trim() : undefined;
223
223
  }
224
+ export function unwrapBundleLikeResponseBody(input) {
225
+ const body = input && typeof input === 'object' ? input : {};
226
+ const nested = body.body && typeof body.body === 'object' ? body.body : undefined;
227
+ const candidate = nested && (Array.isArray(nested.data) || Array.isArray(nested.entry))
228
+ ? nested
229
+ : body;
230
+ return candidate && typeof candidate === 'object' ? candidate : {};
231
+ }
232
+ export function readFirstBundleResourceFromResponseBody(input) {
233
+ const reader = new BundleReader(unwrapBundleLikeResponseBody(input));
234
+ const first = reader.getEntries()[0];
235
+ const resource = first?.resource;
236
+ return resource && typeof resource === 'object' ? resource : undefined;
237
+ }
@@ -0,0 +1,20 @@
1
+ import type { ClinicalResourceBundleLike } from './clinical-resource-view.js';
2
+ export type ClinicalBundleTypeSummary = Readonly<{
3
+ resourceType: string;
4
+ count: number;
5
+ }>;
6
+ export type ClinicalBundleSummary = Readonly<{
7
+ totalEntries: number;
8
+ resourceTypes: ClinicalBundleTypeSummary[];
9
+ xhtmlEntries: number;
10
+ notedEntries: number;
11
+ }>;
12
+ /**
13
+ * Builds one high-level summary from a FHIR/IPS bundle already normalized to
14
+ * the common clinical-resource view contract.
15
+ *
16
+ * Intended for BFF/frontend/call-center code that needs to announce simple
17
+ * menu facts such as "how many medications are present" without hand-reading
18
+ * bundle entries.
19
+ */
20
+ export declare function summarizeClinicalBundle(bundle: ClinicalResourceBundleLike): ClinicalBundleSummary;
@@ -0,0 +1,34 @@
1
+ // Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
2
+ import { toClinicalResourceExpandedViews } from './clinical-resource-view.js';
3
+ /**
4
+ * Builds one high-level summary from a FHIR/IPS bundle already normalized to
5
+ * the common clinical-resource view contract.
6
+ *
7
+ * Intended for BFF/frontend/call-center code that needs to announce simple
8
+ * menu facts such as "how many medications are present" without hand-reading
9
+ * bundle entries.
10
+ */
11
+ export function summarizeClinicalBundle(bundle) {
12
+ const views = toClinicalResourceExpandedViews(bundle);
13
+ const counts = new Map();
14
+ let xhtmlEntries = 0;
15
+ let notedEntries = 0;
16
+ views.forEach((view) => {
17
+ const resourceType = String(view.common.resourceType || 'Unknown').trim() || 'Unknown';
18
+ counts.set(resourceType, (counts.get(resourceType) || 0) + 1);
19
+ if (String(view.xhtml || '').trim()) {
20
+ xhtmlEntries += 1;
21
+ }
22
+ if (Array.isArray(view.notes) && view.notes.length > 0) {
23
+ notedEntries += 1;
24
+ }
25
+ });
26
+ return {
27
+ totalEntries: views.length,
28
+ resourceTypes: [...counts.entries()]
29
+ .map(([resourceType, count]) => ({ resourceType, count }))
30
+ .sort((left, right) => left.resourceType.localeCompare(right.resourceType)),
31
+ xhtmlEntries,
32
+ notedEntries,
33
+ };
34
+ }
@@ -1,10 +1,27 @@
1
- export type CommunicationMode = 'plain' | 'strict' | 'auto-detect';
1
+ export declare const DIDCOMM_COMMUNICATION_MODES: Readonly<{
2
+ readonly Plain: "plain";
3
+ readonly Strict: "strict";
4
+ readonly AutoDetect: "auto-detect";
5
+ }>;
6
+ export type CommunicationMode = typeof DIDCOMM_COMMUNICATION_MODES[keyof typeof DIDCOMM_COMMUNICATION_MODES];
7
+ export declare const DIDCOMM_SUBMIT_KINDS: Readonly<{
8
+ readonly Plain: "plain";
9
+ readonly Encrypted: "encrypted";
10
+ }>;
11
+ export type DidcommSubmitKind = typeof DIDCOMM_SUBMIT_KINDS[keyof typeof DIDCOMM_SUBMIT_KINDS];
2
12
  export type DidcommSubmissionCapabilities = {
3
13
  hasRecipientEncryptionJwk: boolean;
4
14
  };
15
+ export declare const DIDCOMM_SUBMISSION_REASONS: Readonly<{
16
+ readonly PlainMode: "plain-mode";
17
+ readonly StrictMode: "strict-mode";
18
+ readonly AutoDetectEncrypted: "auto-detect-encrypted";
19
+ readonly AutoDetectPlain: "auto-detect-plain";
20
+ }>;
21
+ export type DidcommSubmissionReason = typeof DIDCOMM_SUBMISSION_REASONS[keyof typeof DIDCOMM_SUBMISSION_REASONS];
5
22
  export type DidcommSubmissionPlan = {
6
23
  mode: CommunicationMode;
7
- submitKind: 'plain' | 'encrypted';
8
- reason: 'plain-mode' | 'strict-mode' | 'auto-detect-encrypted' | 'auto-detect-plain';
24
+ submitKind: DidcommSubmitKind;
25
+ reason: DidcommSubmissionReason;
9
26
  };
10
27
  export declare function resolveDidcommSubmissionPlan(mode: CommunicationMode, capabilities: DidcommSubmissionCapabilities): DidcommSubmissionPlan;
@@ -1,15 +1,46 @@
1
+ export const DIDCOMM_COMMUNICATION_MODES = Object.freeze({
2
+ Plain: 'plain',
3
+ Strict: 'strict',
4
+ AutoDetect: 'auto-detect',
5
+ });
6
+ export const DIDCOMM_SUBMIT_KINDS = Object.freeze({
7
+ Plain: 'plain',
8
+ Encrypted: 'encrypted',
9
+ });
10
+ export const DIDCOMM_SUBMISSION_REASONS = Object.freeze({
11
+ PlainMode: 'plain-mode',
12
+ StrictMode: 'strict-mode',
13
+ AutoDetectEncrypted: 'auto-detect-encrypted',
14
+ AutoDetectPlain: 'auto-detect-plain',
15
+ });
1
16
  export function resolveDidcommSubmissionPlan(mode, capabilities) {
2
- if (mode === 'plain') {
3
- return { mode, submitKind: 'plain', reason: 'plain-mode' };
17
+ if (mode === DIDCOMM_COMMUNICATION_MODES.Plain) {
18
+ return {
19
+ mode,
20
+ submitKind: DIDCOMM_SUBMIT_KINDS.Plain,
21
+ reason: DIDCOMM_SUBMISSION_REASONS.PlainMode,
22
+ };
4
23
  }
5
- if (mode === 'strict') {
24
+ if (mode === DIDCOMM_COMMUNICATION_MODES.Strict) {
6
25
  if (!capabilities.hasRecipientEncryptionJwk) {
7
26
  throw new Error('strict mode requires recipient encryption JWK.');
8
27
  }
9
- return { mode, submitKind: 'encrypted', reason: 'strict-mode' };
28
+ return {
29
+ mode,
30
+ submitKind: DIDCOMM_SUBMIT_KINDS.Encrypted,
31
+ reason: DIDCOMM_SUBMISSION_REASONS.StrictMode,
32
+ };
10
33
  }
11
34
  if (capabilities.hasRecipientEncryptionJwk) {
12
- return { mode, submitKind: 'encrypted', reason: 'auto-detect-encrypted' };
35
+ return {
36
+ mode,
37
+ submitKind: DIDCOMM_SUBMIT_KINDS.Encrypted,
38
+ reason: DIDCOMM_SUBMISSION_REASONS.AutoDetectEncrypted,
39
+ };
13
40
  }
14
- return { mode, submitKind: 'plain', reason: 'auto-detect-plain' };
41
+ return {
42
+ mode,
43
+ submitKind: DIDCOMM_SUBMIT_KINDS.Plain,
44
+ reason: DIDCOMM_SUBMISSION_REASONS.AutoDetectPlain,
45
+ };
15
46
  }
@@ -1,4 +1,11 @@
1
- import { CommunicationMode } from './didcomm-submit-policy';
1
+ import { CommunicationMode, type DidcommSubmitKind } from './didcomm-submit-policy';
2
+ export declare const DIDCOMM_PLAINTEXT_JSON_MEDIA_TYPE: "application/didcomm-plaintext+json";
3
+ export declare const DIDCOMM_ENCRYPTED_JSON_MEDIA_TYPE: "application/didcomm-encrypted+json";
4
+ export declare const DIDCOMM_DEFAULT_ACCEPT_HEADER: "application/json, application/didcomm-plaintext+json, */*";
5
+ export declare const DIDCOMM_CONTENT_TYPE_BY_SUBMIT_KIND: Readonly<{
6
+ readonly plain: "application/didcomm-plaintext+json";
7
+ readonly encrypted: "application/didcomm-encrypted+json";
8
+ }>;
2
9
  export type DidcommFetchInit = {
3
10
  method: 'POST';
4
11
  headers: Record<string, string>;
@@ -28,8 +35,8 @@ export type DidcommSubmitResult = {
28
35
  status: number;
29
36
  location?: string;
30
37
  body: unknown;
31
- submitKind: 'plain' | 'encrypted';
32
- contentType: 'application/didcomm-plaintext+json' | 'application/didcomm-encrypted+json';
38
+ submitKind: DidcommSubmitKind;
39
+ contentType: typeof DIDCOMM_PLAINTEXT_JSON_MEDIA_TYPE | typeof DIDCOMM_ENCRYPTED_JSON_MEDIA_TYPE;
33
40
  };
34
41
  /**
35
42
  * Submits a DIDComm payload using either plaintext or encrypted transport,
@@ -1,4 +1,11 @@
1
- import { resolveDidcommSubmissionPlan } from './didcomm-submit-policy.js';
1
+ import { DIDCOMM_SUBMIT_KINDS, resolveDidcommSubmissionPlan, } from './didcomm-submit-policy.js';
2
+ export const DIDCOMM_PLAINTEXT_JSON_MEDIA_TYPE = 'application/didcomm-plaintext+json';
3
+ export const DIDCOMM_ENCRYPTED_JSON_MEDIA_TYPE = 'application/didcomm-encrypted+json';
4
+ export const DIDCOMM_DEFAULT_ACCEPT_HEADER = `application/json, ${DIDCOMM_PLAINTEXT_JSON_MEDIA_TYPE}, */*`;
5
+ export const DIDCOMM_CONTENT_TYPE_BY_SUBMIT_KIND = Object.freeze({
6
+ [DIDCOMM_SUBMIT_KINDS.Plain]: DIDCOMM_PLAINTEXT_JSON_MEDIA_TYPE,
7
+ [DIDCOMM_SUBMIT_KINDS.Encrypted]: DIDCOMM_ENCRYPTED_JSON_MEDIA_TYPE,
8
+ });
2
9
  function getHeaderValue(headers, name) {
3
10
  if (!headers || typeof headers.get !== 'function') {
4
11
  return undefined;
@@ -40,12 +47,12 @@ export async function submitDidcomm(input) {
40
47
  });
41
48
  const headers = {
42
49
  ...(input.defaultHeaders ?? {}),
43
- Accept: 'application/json, application/didcomm-plaintext+json, */*',
50
+ Accept: DIDCOMM_DEFAULT_ACCEPT_HEADER,
44
51
  };
45
52
  let body;
46
53
  let contentType;
47
- if (plan.submitKind === 'plain') {
48
- contentType = 'application/didcomm-plaintext+json';
54
+ if (plan.submitKind === DIDCOMM_SUBMIT_KINDS.Plain) {
55
+ contentType = DIDCOMM_CONTENT_TYPE_BY_SUBMIT_KIND[DIDCOMM_SUBMIT_KINDS.Plain];
49
56
  body = JSON.stringify(input.payload);
50
57
  }
51
58
  else {
@@ -60,7 +67,7 @@ export async function submitDidcomm(input) {
60
67
  }
61
68
  const compactJws = await input.signCompactJws(input.payload);
62
69
  body = await input.encryptCompactJwe(compactJws, input.recipientEncryptionJwk);
63
- contentType = 'application/didcomm-encrypted+json';
70
+ contentType = DIDCOMM_CONTENT_TYPE_BY_SUBMIT_KIND[DIDCOMM_SUBMIT_KINDS.Encrypted];
64
71
  }
65
72
  headers['Content-Type'] = contentType;
66
73
  if (input.bearerToken) {