gdc-sdk-node-ts 2.4.24 → 2.4.30

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 (32) hide show
  1. package/README.md +15 -6
  2. package/dist/constants/lifecycle.d.ts +8 -0
  3. package/dist/constants/lifecycle.js +8 -0
  4. package/dist/device-activation.d.ts +48 -1
  5. package/dist/device-activation.js +157 -1
  6. package/dist/digital-twin.d.ts +4 -0
  7. package/dist/digital-twin.js +16 -4
  8. package/dist/host-onboarding.d.ts +7 -0
  9. package/dist/node-managed-wallet.d.ts +41 -1
  10. package/dist/node-managed-wallet.js +52 -0
  11. package/dist/node-runtime-client.d.ts +29 -1
  12. package/dist/node-runtime-client.js +92 -1
  13. package/dist/orchestration/client-port.d.ts +11 -1
  14. package/dist/orchestration/digital-twin-sdk.js +3 -1
  15. package/dist/orchestration/host-onboarding-sdk.d.ts +9 -3
  16. package/dist/orchestration/host-onboarding-sdk.js +10 -1
  17. package/dist/orchestration/individual-controller-sdk.d.ts +29 -3
  18. package/dist/orchestration/individual-controller-sdk.js +48 -2
  19. package/dist/orchestration/organization-controller-sdk.d.ts +19 -6
  20. package/dist/orchestration/organization-controller-sdk.js +27 -6
  21. package/dist/organization-controller-recovery.d.ts +4 -2
  22. package/dist/organization-controller-recovery.js +14 -8
  23. package/dist/organization-employee-lifecycle.d.ts +4 -0
  24. package/dist/organization-employee-lifecycle.js +17 -1
  25. package/dist/organization-license-order.d.ts +8 -3
  26. package/dist/resource-operations.d.ts +67 -0
  27. package/dist/resource-operations.js +62 -1
  28. package/dist/runtime-client-paths.d.ts +10 -0
  29. package/dist/runtime-client-paths.js +10 -0
  30. package/dist/server-profile-session.d.ts +32 -3
  31. package/dist/server-profile-session.js +21 -17
  32. package/package.json +1 -1
package/README.md CHANGED
@@ -238,6 +238,11 @@ Current runtime boundary:
238
238
  - `OrganizationControllerSdk.confirmOrganizationLicenseOrder(...)` now uses the
239
239
  public host `Order/_batch` route used by GW CORE for portal-managed
240
240
  post-payment seat activation
241
+ - that route is commercial routing only: `iss` remains the exact controller DID
242
+ registered by tenant DCR, `aud` remains the tenant id, and GW resolves its
243
+ JWS/JWE key identifiers from the tenant rather than the host
244
+ - applications use the high-level SDK method and configured transport profile;
245
+ they do not populate `meta.jws`, move public keys, or construct DIDComm by hand
241
246
  - the long root lifecycle is still not fully closed because the suite does not
242
247
  yet orchestrate the whole `license list -> pay -> confirm -> relist -> two
243
248
  employees -> selective purge -> cleanup` dialogue as one single test
@@ -640,8 +645,8 @@ modules below.
640
645
  - types: `SmartTokenRequestInput`, `SmartTokenExchangeResult`
641
646
  - function: `requestSmartTokenWithDeps(...)`
642
647
  - [`src/resource-operations.ts`](src/resource-operations.ts)
643
- - types: `OrganizationEmployeeCreationInput`, `IpsOrFhirImportInput`, `RelatedPersonUpsertInput`, `CommunicationIngestionInput`, `ClinicalDateRange`, `ClinicalBundleSearchInput`, `ConsentActorTargetInput`, `GrantProfessionalAccessInput`, `GrantProfessionalAccessResult`, `DigitalTwinGenerationInput`
644
- - functions: `createOrganizationEmployeeWithDeps(...)`, `importIpsOrFhirAndUpdateIndexWithDeps(...)`, `upsertRelatedPersonAndPollWithDeps(...)`, `ingestCommunicationAndUpdateIndexWithDeps(...)`, `searchClinicalBundleWithDeps(...)`, `searchLatestIpsWithDeps(...)`, `grantProfessionalAccessWithDeps(...)`, `generateDigitalTwinFromSubjectDataWithDeps(...)`
648
+ - types: `OrganizationEmployeeCreationInput`, `IpsOrFhirImportInput`, `RelatedPersonUpsertInput`, `CommunicationIngestionInput`, `ClinicalDateRange`, `ClinicalBundleSearchInput`, `ConsentActorTargetInput`, `GrantProfessionalAccessInput`, `GrantProfessionalAccessResult`, `DigitalTwinSecondaryUseConsentInput`, `DigitalTwinSubjectLinkPurgeInput`
649
+ - functions: `createOrganizationEmployeeWithDeps(...)`, `importIpsOrFhirAndUpdateIndexWithDeps(...)`, `upsertRelatedPersonAndPollWithDeps(...)`, `ingestCommunicationAndUpdateIndexWithDeps(...)`, `searchClinicalBundleWithDeps(...)`, `searchLatestIpsWithDeps(...)`, `grantProfessionalAccessWithDeps(...)`, `setDigitalTwinSecondaryUseConsentWithDeps(...)`, `purgeDigitalTwinSubjectLinkWithDeps(...)`
645
650
  - [`src/digital-twin.ts`](src/digital-twin.ts)
646
651
  - types: `DigitalTwinSearchInput`, `DigitalTwinSelectionInput`, `DigitalTwinResearchTag`, `DigitalTwinMaterializationInput`
647
652
  - functions: `searchDigitalTwinsWithDeps(...)`, `saveDigitalTwinSelectionWithDeps(...)`, `materializeDigitalTwinWithDeps(...)`
@@ -791,10 +796,14 @@ Live E2E legal PDF source:
791
796
  Recovery-specific live rule:
792
797
 
793
798
  - `Organization/_issue` can succeed and still be followed by `_exchange` failure
794
- if the controller `id_token` is not production-shaped enough
795
- - the current GW `_exchange` path expects at least:
796
- - syntactically valid JWT format
797
- - `tenant_id` claim matching the target tenant
799
+ if the controller `id_token` is invalid, expired or does not prove the
800
+ expected actor/contact
801
+ - the current GW `_exchange` path expects a valid IdP token but takes tenant
802
+ authority from the already validated request route
803
+ - a custom `tenant_id` claim is optional; when present it must match the route
804
+ and cannot select another tenant
805
+ - a failed poll `OperationOutcome` is surfaced before the helper checks for
806
+ `initial_access_token`, preserving the real GW diagnostic
798
807
  - the bundled recovery runner generates a syntactically valid demo JWT if
799
808
  `CONTROLLER_ID_TOKEN` is not provided, but production/staging should use a
800
809
  real IdP-issued token
@@ -16,7 +16,11 @@ export declare const GwCoreLifecycleAction: Readonly<{
16
16
  readonly Transaction: "_transaction";
17
17
  readonly TransactionResponse: "_transaction-response";
18
18
  readonly Disable: "_disable";
19
+ readonly Enable: "_enable";
19
20
  readonly Purge: "_purge";
21
+ readonly Status: "_status";
22
+ readonly DisableDescendants: "_disable-descendants";
23
+ readonly PurgeDescendants: "_purge-descendants";
20
24
  }>;
21
25
  /**
22
26
  * Entry request methods currently used by GW CORE lifecycle handlers.
@@ -36,6 +40,10 @@ export declare const GwCoreLifecycleRequestType: Readonly<{
36
40
  readonly IndividualOrganizationDisable: "Family-disable-request-v1.0";
37
41
  readonly IndividualOrganizationPurge: "Family-purge-request-v1.0";
38
42
  readonly IndividualMemberPurge: "RelatedPerson-purge-request-v1.0";
43
+ readonly TenantStatus: "Organization-lifecycle-status-request-v1.0";
44
+ readonly TenantEnable: "Organization-enable-request-v1.0";
45
+ readonly TenantDisableDescendants: "Organization-disable-descendants-request-v1.0";
46
+ readonly TenantPurgeDescendants: "Organization-purge-descendants-request-v1.0";
39
47
  }>;
40
48
  /**
41
49
  * Named TODO ids kept close to the current lifecycle implementation so the
@@ -18,7 +18,11 @@ export const GwCoreLifecycleAction = Object.freeze({
18
18
  Transaction: '_transaction',
19
19
  TransactionResponse: '_transaction-response',
20
20
  Disable: '_disable',
21
+ Enable: '_enable',
21
22
  Purge: '_purge',
23
+ Status: '_status',
24
+ DisableDescendants: '_disable-descendants',
25
+ PurgeDescendants: '_purge-descendants',
22
26
  });
23
27
  /**
24
28
  * Entry request methods currently used by GW CORE lifecycle handlers.
@@ -38,6 +42,10 @@ export const GwCoreLifecycleRequestType = Object.freeze({
38
42
  IndividualOrganizationDisable: 'Family-disable-request-v1.0',
39
43
  IndividualOrganizationPurge: 'Family-purge-request-v1.0',
40
44
  IndividualMemberPurge: LifecycleRequestType.RelatedPersonPurge,
45
+ TenantStatus: 'Organization-lifecycle-status-request-v1.0',
46
+ TenantEnable: 'Organization-enable-request-v1.0',
47
+ TenantDisableDescendants: 'Organization-disable-descendants-request-v1.0',
48
+ TenantPurgeDescendants: 'Organization-purge-descendants-request-v1.0',
41
49
  });
42
50
  /**
43
51
  * Named TODO ids kept close to the current lifecycle implementation so the
@@ -12,10 +12,57 @@ export type EmployeeDeviceActivationRequestInput = {
12
12
  sector?: string;
13
13
  activationCode: string;
14
14
  idToken: string;
15
- dcrPayload: Record<string, unknown>;
15
+ /** Canonical high-level description converted to OpenID DCR metadata by the SDK. */
16
+ deviceRegistration?: ProfileDeviceRegistrationInput;
17
+ /**
18
+ * @deprecated Low-level OpenID escape hatch. Use
19
+ * `createProfileDeviceActivationRequest(...).set...build()` instead.
20
+ */
21
+ dcrPayload?: Record<string, unknown>;
16
22
  timeoutSeconds?: number;
17
23
  intervalSeconds?: number;
18
24
  };
25
+ /** Application concepts required to bind one portal/app installation. */
26
+ export type ProfileDeviceRegistrationInput = Readonly<{
27
+ clientInstanceId: string;
28
+ clientName: string;
29
+ applicationType: 'native' | 'web';
30
+ redirectUris: readonly string[];
31
+ publicJwks: readonly Record<string, unknown>[];
32
+ deviceName?: string;
33
+ actorDid?: string;
34
+ profileDid?: string;
35
+ }>;
36
+ /** Advanced typed editor used by SDK runtimes that already own profile keys. */
37
+ export interface ProfileDeviceActivationDraft {
38
+ setClientInstanceId(value: string): ProfileDeviceActivationDraft;
39
+ setClientName(value: string): ProfileDeviceActivationDraft;
40
+ setApplicationType(value: 'native' | 'web'): ProfileDeviceActivationDraft;
41
+ setRedirectUris(values: readonly string[]): ProfileDeviceActivationDraft;
42
+ setPublicJwks(values: readonly Record<string, unknown>[]): ProfileDeviceActivationDraft;
43
+ setDeviceName(value: string): ProfileDeviceActivationDraft;
44
+ setActorDid(value: string): ProfileDeviceActivationDraft;
45
+ setProfileDid(value: string): ProfileDeviceActivationDraft;
46
+ setTimeoutSeconds(value: number): ProfileDeviceActivationDraft;
47
+ setIntervalSeconds(value: number): ProfileDeviceActivationDraft;
48
+ build(): EmployeeDeviceActivationRequestInput & {
49
+ deviceRegistration: ProfileDeviceRegistrationInput;
50
+ };
51
+ }
52
+ /**
53
+ * Starts an advanced profile-device activation request.
54
+ *
55
+ * Product portals should normally use `ServerProfileSessionManager.enroll`,
56
+ * which provisions the wallet and calls this editor internally. This surface
57
+ * exists for runtimes that already own and select the profile public keys.
58
+ */
59
+ export declare function createProfileDeviceActivationRequest(input: Readonly<{
60
+ activationCode: string;
61
+ idToken: string;
62
+ tenantId?: string;
63
+ jurisdiction?: string;
64
+ sector?: string;
65
+ }>): ProfileDeviceActivationDraft;
19
66
  export type EmployeeDeviceActivationResult = {
20
67
  initialAccessToken: string;
21
68
  exchange: SubmitAndPollResult;
@@ -2,6 +2,60 @@
2
2
  import { resolvePollOptionsFromSeconds } from './poll-options.js';
3
3
  import { IdentityAuthRequestFields, IdentityAuthResponseFields, IdentityDcrMetadataFields, IdentityDeviceInfoFields, } from 'gdc-common-utils-ts/constants/identity-auth';
4
4
  import { buildEmployeeDeviceRevocationBody } from 'gdc-common-utils-ts/utils/organization-employee-lifecycle';
5
+ /**
6
+ * Starts an advanced profile-device activation request.
7
+ *
8
+ * Product portals should normally use `ServerProfileSessionManager.enroll`,
9
+ * which provisions the wallet and calls this editor internally. This surface
10
+ * exists for runtimes that already own and select the profile public keys.
11
+ */
12
+ export function createProfileDeviceActivationRequest(input) {
13
+ const activationCode = requiredText(input.activationCode, 'activation code');
14
+ const idToken = requiredText(input.idToken, 'signed identity token');
15
+ let clientInstanceId = '';
16
+ let clientName = '';
17
+ let applicationType;
18
+ let redirectUris = [];
19
+ let publicJwks = [];
20
+ let deviceName = '';
21
+ let actorDid = '';
22
+ let profileDid = '';
23
+ let timeoutSeconds;
24
+ let intervalSeconds;
25
+ const draft = {
26
+ setClientInstanceId(value) { clientInstanceId = normalizedText(value); return draft; },
27
+ setClientName(value) { clientName = normalizedText(value); return draft; },
28
+ setApplicationType(value) { applicationType = value; return draft; },
29
+ setRedirectUris(values) { redirectUris = uniqueText(values); return draft; },
30
+ setPublicJwks(values) { publicJwks = values.map((value) => ({ ...value })); return draft; },
31
+ setDeviceName(value) { deviceName = normalizedText(value); return draft; },
32
+ setActorDid(value) { actorDid = normalizedText(value); return draft; },
33
+ setProfileDid(value) { profileDid = normalizedText(value); return draft; },
34
+ setTimeoutSeconds(value) { timeoutSeconds = positiveNumber(value, 'timeout seconds'); return draft; },
35
+ setIntervalSeconds(value) { intervalSeconds = positiveNumber(value, 'interval seconds'); return draft; },
36
+ build() {
37
+ const registration = {
38
+ clientInstanceId: requiredText(clientInstanceId, 'client instance id'),
39
+ clientName: requiredText(clientName, 'client name'),
40
+ applicationType: applicationType || failRequired('application type'),
41
+ redirectUris: requiredList(redirectUris, 'redirect URI'),
42
+ publicJwks: requiredList(publicJwks, 'public JWK'),
43
+ ...(deviceName ? { deviceName } : {}),
44
+ ...(actorDid ? { actorDid } : {}),
45
+ ...(profileDid ? { profileDid } : {}),
46
+ };
47
+ return {
48
+ ...input,
49
+ activationCode,
50
+ idToken,
51
+ deviceRegistration: registration,
52
+ ...(timeoutSeconds !== undefined ? { timeoutSeconds } : {}),
53
+ ...(intervalSeconds !== undefined ? { intervalSeconds } : {}),
54
+ };
55
+ },
56
+ };
57
+ return draft;
58
+ }
5
59
  export async function revokeEmployeeDeviceWithDeps(deps) {
6
60
  const licenseId = String(deps.input.licenseId || '').trim();
7
61
  const clientId = String(deps.input.clientId || '').trim();
@@ -28,6 +82,16 @@ export async function activateEmployeeDeviceWithActivationCodeWithDeps(deps) {
28
82
  [IdentityAuthRequestFields.ClientInstanceId]: clientInstanceId,
29
83
  };
30
84
  const exchange = await deps.submitAndPollWithBearerToken(deps.input.idToken, deps.identityTokenExchangePath(deps.routeCtx), deps.identityTokenExchangePollPath(deps.routeCtx), exchangePayload, deps.input.pollOptions);
85
+ const exchangeDiagnostics = firstFailedOperationOutcomeDiagnostic(exchange.poll.body)
86
+ || firstFailedOperationOutcomeDiagnostic(exchange.submit.body);
87
+ if (exchangeDiagnostics) {
88
+ throw new Error(`activateEmployeeDeviceWithActivationCode: exchange failed: ${exchangeDiagnostics}`);
89
+ }
90
+ const failedExchangeStatus = [exchange.submit.status, exchange.poll.status]
91
+ .find((status) => status < 200 || status >= 300);
92
+ if (failedExchangeStatus !== undefined) {
93
+ throw new Error(`activateEmployeeDeviceWithActivationCode: exchange failed (HTTP ${failedExchangeStatus}).`);
94
+ }
31
95
  const pollBody = exchange.poll.body || {};
32
96
  const exchangeBody = (pollBody.body || pollBody);
33
97
  const initialAccessToken = String(exchangeBody[IdentityAuthResponseFields.InitialAccessToken]
@@ -56,10 +120,58 @@ export async function activateEmployeeDeviceWithActivationRequestWithDeps(deps)
56
120
  return deps.activateEmployeeDeviceWithActivationCode(deps.routeCtx, {
57
121
  activationCode: deps.input.activationCode,
58
122
  idToken: deps.input.idToken,
59
- dcrPayload: deps.input.dcrPayload,
123
+ dcrPayload: resolveDcrPayload(deps.input),
60
124
  pollOptions,
61
125
  });
62
126
  }
127
+ function resolveDcrPayload(input) {
128
+ if (input.deviceRegistration && input.dcrPayload) {
129
+ throw new Error('Device activation accepts either deviceRegistration or the deprecated dcrPayload, not both.');
130
+ }
131
+ if (input.deviceRegistration) {
132
+ const registration = input.deviceRegistration;
133
+ return {
134
+ [IdentityDcrMetadataFields.ApplicationType]: registration.applicationType,
135
+ [IdentityDcrMetadataFields.ClientName]: registration.clientName,
136
+ [IdentityDcrMetadataFields.RedirectUris]: [...registration.redirectUris],
137
+ [IdentityDcrMetadataFields.Jwks]: { keys: registration.publicJwks.map((value) => ({ ...value })) },
138
+ [IdentityDcrMetadataFields.ExtendedDeviceInfo]: {
139
+ [IdentityDeviceInfoFields.DeviceId]: registration.clientInstanceId,
140
+ device_name: registration.deviceName || registration.clientName,
141
+ },
142
+ ...(registration.actorDid ? { [IdentityDcrMetadataFields.ActorDid]: registration.actorDid } : {}),
143
+ ...(registration.profileDid ? { [IdentityDcrMetadataFields.ProfileDid]: registration.profileDid } : {}),
144
+ };
145
+ }
146
+ if (input.dcrPayload)
147
+ return { ...input.dcrPayload };
148
+ throw new Error('Device activation requires a device registration built by createProfileDeviceActivationRequest.');
149
+ }
150
+ function normalizedText(value) {
151
+ return typeof value === 'string' ? value.trim() : '';
152
+ }
153
+ function requiredText(value, label) {
154
+ const normalized = normalizedText(value);
155
+ if (!normalized)
156
+ throw new Error(`Profile device activation requires ${label}.`);
157
+ return normalized;
158
+ }
159
+ function uniqueText(values) {
160
+ return [...new Set((values || []).map((value) => normalizedText(value)).filter(Boolean))];
161
+ }
162
+ function requiredList(values, label) {
163
+ if (!values.length)
164
+ throw new Error(`Profile device activation requires at least one ${label}.`);
165
+ return [...values];
166
+ }
167
+ function positiveNumber(value, label) {
168
+ if (!Number.isFinite(value) || value <= 0)
169
+ throw new Error(`Profile device activation ${label} must be positive.`);
170
+ return value;
171
+ }
172
+ function failRequired(label) {
173
+ throw new Error(`Profile device activation requires ${label}.`);
174
+ }
63
175
  function createRuntimeUuid() {
64
176
  const fromCrypto = globalThis.crypto?.randomUUID?.();
65
177
  if (fromCrypto) {
@@ -67,3 +179,47 @@ function createRuntimeUuid() {
67
179
  }
68
180
  return `fallback-${Date.now()}-${Math.random().toString(16).slice(2)}`;
69
181
  }
182
+ /** Preserves a terminal async GW diagnostic before checking success fields. */
183
+ function firstFailedOperationOutcomeDiagnostic(value) {
184
+ if (typeof value === 'string') {
185
+ const candidate = value.trim();
186
+ if (!candidate || (candidate[0] !== '{' && candidate[0] !== '['))
187
+ return undefined;
188
+ try {
189
+ return firstFailedOperationOutcomeDiagnostic(JSON.parse(candidate));
190
+ }
191
+ catch {
192
+ return undefined;
193
+ }
194
+ }
195
+ if (!value || typeof value !== 'object')
196
+ return undefined;
197
+ if (Array.isArray(value)) {
198
+ for (const child of value) {
199
+ const diagnostic = firstFailedOperationOutcomeDiagnostic(child);
200
+ if (diagnostic)
201
+ return diagnostic;
202
+ }
203
+ return undefined;
204
+ }
205
+ const record = value;
206
+ if (record.resourceType === 'OperationOutcome' && Array.isArray(record.issue)) {
207
+ for (const rawIssue of record.issue) {
208
+ if (!rawIssue || typeof rawIssue !== 'object')
209
+ continue;
210
+ const issue = rawIssue;
211
+ const severity = String(issue.severity || '').trim().toLowerCase();
212
+ if (severity !== 'error' && severity !== 'fatal')
213
+ continue;
214
+ const diagnostic = String(issue.diagnostics || '').trim();
215
+ if (diagnostic)
216
+ return diagnostic;
217
+ }
218
+ }
219
+ for (const child of Object.values(record)) {
220
+ const diagnostic = firstFailedOperationOutcomeDiagnostic(child);
221
+ if (diagnostic)
222
+ return diagnostic;
223
+ }
224
+ return undefined;
225
+ }
@@ -3,6 +3,10 @@ import type { MetaTagCoding } from 'gdc-common-utils-ts/models/confidential-stor
3
3
  import type { RouteContext } from './individual-onboarding.js';
4
4
  import type { PollOptions, SubmitAndPollResult } from './orchestration/client-port.js';
5
5
  export type DigitalTwinFhirFormat = 'org.hl7.fhir.r4' | 'org.hl7.fhir.api';
6
+ /** Returns whether a value can be used as a pseudonymous digital-twin subject. */
7
+ export declare function isDigitalTwinSubjectId(value: unknown): value is string;
8
+ /** Rejects operational DIDs and malformed or caller-invented subject shapes. */
9
+ export declare function assertDigitalTwinSubjectId(value: unknown): asserts value is string;
6
10
  /** Search parameters added by the digital-twin Composition profile. */
7
11
  export declare const DigitalTwinSearchParameter: Readonly<{
8
12
  Section: "section";
@@ -2,6 +2,17 @@
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { HealthcareDocumentTypes } from 'gdc-common-utils-ts/constants';
4
4
  import { CompositionClaim } from 'gdc-common-utils-ts/models';
5
+ const DIGITAL_TWIN_SUBJECT_URN_UUID = /^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
6
+ /** Returns whether a value can be used as a pseudonymous digital-twin subject. */
7
+ export function isDigitalTwinSubjectId(value) {
8
+ return DIGITAL_TWIN_SUBJECT_URN_UUID.test(String(value || '').trim());
9
+ }
10
+ /** Rejects operational DIDs and malformed or caller-invented subject shapes. */
11
+ export function assertDigitalTwinSubjectId(value) {
12
+ if (!isDigitalTwinSubjectId(value)) {
13
+ throw new Error('Digital twin subject must be a valid urn:uuid identifier.');
14
+ }
15
+ }
5
16
  /** Search parameters added by the digital-twin Composition profile. */
6
17
  export const DigitalTwinSearchParameter = Object.freeze({
7
18
  Section: 'section',
@@ -61,6 +72,9 @@ export function readDigitalTwinSearchResult(operation) {
61
72
  throw new Error('Digital twin search did not return a Composition result set.');
62
73
  }
63
74
  const matches = responseEntry.resource.data;
75
+ for (const match of matches) {
76
+ assertDigitalTwinSubjectId(match?.[CompositionClaim.Subject]);
77
+ }
64
78
  const parsedTotal = Number(responseEntry.resource.total);
65
79
  return {
66
80
  total: Number.isFinite(parsedTotal) ? parsedTotal : matches.length,
@@ -73,8 +87,7 @@ export async function saveDigitalTwinSelectionWithDeps(ctx, input, deps) {
73
87
  const twinSubjectId = String(input.twinSubjectId || '').trim();
74
88
  const section = String(input.section || '').trim();
75
89
  const authorDid = String(input.authorDid || '').trim();
76
- if (!twinSubjectId)
77
- throw new Error('twinSubjectId is required.');
90
+ assertDigitalTwinSubjectId(twinSubjectId);
78
91
  if (!section)
79
92
  throw new Error('Digital twin selection section is required.');
80
93
  if (!authorDid)
@@ -146,8 +159,7 @@ export async function searchDigitalTwinsWithDeps(ctx, input, deps) {
146
159
  /** Materializes one selected pseudonymous twin as a research summary Bundle. */
147
160
  export async function materializeDigitalTwinWithDeps(ctx, input, deps) {
148
161
  const twinSubjectId = String(input.twinSubjectId || '').trim();
149
- if (!twinSubjectId)
150
- throw new Error('twinSubjectId is required.');
162
+ assertDigitalTwinSubjectId(twinSubjectId);
151
163
  const format = input.format || 'org.hl7.fhir.r4';
152
164
  const thid = String(input.thid || randomUUID());
153
165
  const parameters = [
@@ -7,3 +7,10 @@
7
7
  */
8
8
  export { confirmLegalOrganizationOrderWithDeps, HostLifecycleRequestType, HostedTenantLifecycleRequestType, submitHostedTenantLifecycleWithDeps, } from 'gdc-sdk-core-ts';
9
9
  export type { HostingControllerFacade, HostLifecycleInput, HostRouteContext, HostedTenantLifecycleInput, LegalOrganizationOrderInput, } from 'gdc-sdk-core-ts';
10
+ /** Descendant groups that a controller can clean explicitly before tenant shutdown. */
11
+ /**
12
+ * Tenant-owned subject group supported by the generic cleanup endpoint.
13
+ * Employees use their dedicated encrypted lifecycle so license release and
14
+ * audit metadata cannot be bypassed.
15
+ */
16
+ export type HostedTenantDescendantKind = 'individuals';
@@ -1,6 +1,6 @@
1
1
  import { CryptographyService } from 'gdc-common-utils-ts/CryptographyService';
2
2
  import type { ICryptoHelper } from 'gdc-common-utils-ts/interfaces/ICryptoHelper';
3
- import type { IWallet, WalletAlgorithm, WalletCompactJweRequest, WalletCompactJwsRequest, WalletDetachedJwsRequest, WalletExecutionContext, WalletKeyDescriptor, WalletKeyPurpose, WalletKeySelection, WalletPackOptions, WalletProvisionRequest, WalletUnpackOptions } from 'gdc-sdk-core-ts';
3
+ import type { IWallet, WalletAlgorithm, WalletCompactJweRequest, WalletCompactJwsRequest, WalletDetachedJwsRequest, WalletExecutionContext, WalletKeyDescriptor, WalletKeyPurpose, WalletKeySelection, WalletPackOptions, WalletProvisionMode, WalletProvisionRequest, WalletUnpackOptions } from 'gdc-sdk-core-ts';
4
4
  import type { JWK, JwkSet } from 'gdc-common-utils-ts/models/jwk';
5
5
  export type NodeManagedWalletPolicy = {
6
6
  defaults: Partial<Record<WalletKeyPurpose, WalletAlgorithm>>;
@@ -11,6 +11,20 @@ export type NodeManagedWalletOptions = {
11
11
  resolveRecipientJwk?: (recipientDid: string) => Promise<JWK>;
12
12
  policy?: Partial<NodeManagedWalletPolicy>;
13
13
  };
14
+ export type NodeCommunicationWalletInitialization = {
15
+ /**
16
+ * Stable secret used to reconstruct the same communication keys after a
17
+ * process restart. Persist it only through the portal's protected wallet
18
+ * store; never send it to ICA or GW.
19
+ */
20
+ seedMaterial?: string | Uint8Array;
21
+ /**
22
+ * Defaults to deterministic when seedMaterial is present, otherwise random.
23
+ * Random keys are suitable only when the wallet implementation itself is
24
+ * durably persisted.
25
+ */
26
+ mode?: WalletProvisionMode;
27
+ };
14
28
  /**
15
29
  * Node-focused managed wallet implementation for BFF, portal, and backend flows.
16
30
  *
@@ -20,6 +34,11 @@ export type NodeManagedWalletOptions = {
20
34
  * - OpenID/JWT signing
21
35
  * - DIDComm-style transport wrapping
22
36
  * - confidential document protection
37
+ *
38
+ * OpenID boundary: managing an `openid-id-token-signing` key and producing a
39
+ * compact JWT does not turn this wallet into an OpenID Provider. The hosting
40
+ * portal/BFF must authenticate the account, verify any asserted email, publish
41
+ * provider metadata and JWKS, and be explicitly trusted by the receiving GW.
23
42
  */
24
43
  export declare class NodeManagedWallet implements IWallet {
25
44
  private readonly cryptoHelper;
@@ -47,6 +66,27 @@ export declare class NodeManagedWallet implements IWallet {
47
66
  * Returns the currently available public JWKs for the selected context and filter.
48
67
  */
49
68
  getPublicJwks(context?: WalletExecutionContext, filter?: WalletKeySelection): Promise<WalletKeyDescriptor[]>;
69
+ /**
70
+ * Initializes the runtime communication wallet and returns exactly the
71
+ * public signing/encryption JWKS accepted as `controller.publicKeys` by the
72
+ * legacy organization activation builder.
73
+ *
74
+ * This does not replace the professional-role signing key supplied as
75
+ * `controller.publicSignKey`. The role key identifies the historical legal
76
+ * representative; these runtime keys protect DIDComm communications.
77
+ *
78
+ * The caller owns durable wallet custody. When deterministic provisioning is
79
+ * used, protect `seedMaterial` in the portal wallet store (for example behind
80
+ * the user's PIN and a server-held KEK) so the same private keys can be
81
+ * reconstructed after restart. ICA and GW receive only this returned public
82
+ * JWKS.
83
+ */
84
+ initializeCommunicationJsonWebKeySet(context: WalletExecutionContext, options?: NodeCommunicationWalletInitialization): Promise<JwkSet>;
85
+ /**
86
+ * Returns only the public DIDComm signing/encryption keys for a previously
87
+ * initialized runtime wallet. Private material never leaves the wallet.
88
+ */
89
+ getCommunicationJsonWebKeySet(context: WalletExecutionContext): Promise<JwkSet>;
50
90
  /**
51
91
  * Computes a digest of a string using the configured runtime helper.
52
92
  */
@@ -25,6 +25,11 @@ const DEFAULT_POLICY = {
25
25
  * - OpenID/JWT signing
26
26
  * - DIDComm-style transport wrapping
27
27
  * - confidential document protection
28
+ *
29
+ * OpenID boundary: managing an `openid-id-token-signing` key and producing a
30
+ * compact JWT does not turn this wallet into an OpenID Provider. The hosting
31
+ * portal/BFF must authenticate the account, verify any asserted email, publish
32
+ * provider metadata and JWKS, and be explicitly trusted by the receiving GW.
28
33
  */
29
34
  export class NodeManagedWallet {
30
35
  /**
@@ -128,6 +133,53 @@ export class NodeManagedWallet {
128
133
  }
129
134
  return descriptors;
130
135
  }
136
+ /**
137
+ * Initializes the runtime communication wallet and returns exactly the
138
+ * public signing/encryption JWKS accepted as `controller.publicKeys` by the
139
+ * legacy organization activation builder.
140
+ *
141
+ * This does not replace the professional-role signing key supplied as
142
+ * `controller.publicSignKey`. The role key identifies the historical legal
143
+ * representative; these runtime keys protect DIDComm communications.
144
+ *
145
+ * The caller owns durable wallet custody. When deterministic provisioning is
146
+ * used, protect `seedMaterial` in the portal wallet store (for example behind
147
+ * the user's PIN and a server-held KEK) so the same private keys can be
148
+ * reconstructed after restart. ICA and GW receive only this returned public
149
+ * JWKS.
150
+ */
151
+ async initializeCommunicationJsonWebKeySet(context, options = {}) {
152
+ if (!context.runtime?.runtimeId) {
153
+ throw new Error('Communication wallet initialization requires context.runtime.runtimeId.');
154
+ }
155
+ await this.provisionManagedKeys(context, {
156
+ ownerScope: 'runtime',
157
+ purposes: ['comm-signing', 'comm-encryption'],
158
+ ...(options.seedMaterial !== undefined ? { seedMaterial: options.seedMaterial } : {}),
159
+ mode: options.mode ?? (options.seedMaterial !== undefined ? 'deterministic' : 'random'),
160
+ });
161
+ return this.getCommunicationJsonWebKeySet(context);
162
+ }
163
+ /**
164
+ * Returns only the public DIDComm signing/encryption keys for a previously
165
+ * initialized runtime wallet. Private material never leaves the wallet.
166
+ */
167
+ async getCommunicationJsonWebKeySet(context) {
168
+ if (!context.runtime?.runtimeId) {
169
+ throw new Error('Communication JWKS lookup requires context.runtime.runtimeId.');
170
+ }
171
+ const descriptors = await this.getPublicJwks(context, { ownerScope: 'runtime' });
172
+ return {
173
+ keys: descriptors
174
+ .filter((entry) => entry.purpose === 'comm-signing' || entry.purpose === 'comm-encryption')
175
+ .map((entry) => ({
176
+ ...entry.publicJwk,
177
+ kid: entry.publicJwk.kid ?? entry.kid,
178
+ use: entry.use,
179
+ alg: entry.publicJwk.alg ?? entry.alg,
180
+ })),
181
+ };
182
+ }
131
183
  /**
132
184
  * Computes a digest of a string using the configured runtime helper.
133
185
  */
@@ -3,6 +3,7 @@ import type { AppInfo } from 'gdc-sdk-core-ts';
3
3
  import type { IndividualOrganizationLifecycleInput } from 'gdc-sdk-core-ts';
4
4
  import { type ResolvedAppInfo, type CommunicationClinicalFormatRenderers, type SecureDidcommTransportAdapter, type TransportProfile } from 'gdc-sdk-core-ts';
5
5
  import { type HostRouteContext, type HostedTenantLifecycleInput } from './host-onboarding.js';
6
+ import type { HostedTenantDescendantKind } from './host-onboarding.js';
6
7
  import type { NodeLegalOrganizationVerificationTransactionInput, NodeOrganizationDidBindingInput, NodeOrganizationActivationInput } from './orchestration/client-port.js';
7
8
  import { type IndividualOrganizationConfirmOrderInput, type RouteContext } from './individual-onboarding.js';
8
9
  import { type EnsureFamilyOrganizationRegistrationInput } from './family-organization-registration.js';
@@ -11,7 +12,7 @@ import { type SmartTokenRequestInput } from './smart-token.js';
11
12
  import { type EmployeeDeviceActivationRequestInput, type EmployeeDeviceActivationResult, type EmployeeDeviceRevocationInput } from './device-activation.js';
12
13
  import { type OrganizationLicenseOrderConfirmInput } from './organization-license-order.js';
13
14
  import { type IndividualOrganizationBootstrapInput, type IndividualOrganizationStartResult } from './individual-start.js';
14
- import { type CommunicationIngestionInput, type BlockchainArtifactRegistrationInput, type VitalSignBatchCommunicationFromSearchResponseInput, type CommunicationParticipantRuntimeSearchInput, type ClinicalBundleSearchInput, type ClinicalSectionUpdateInput, type ClinicalSummaryReadResult, type ClinicalSummaryRequestInput, type ClinicalSummaryUpdateInput, type GrantProfessionalAccessInput, type GrantProfessionalAccessResult, type IndividualMemberLifecycleInput, type IndividualMemberLicenseAddInput, type IndividualMemberLicenseInvitationInput, type IndividualMemberLicenseTransitionInput, type LicenseListRuntimeSearchInput, type LicenseOfferRuntimeSearchInput, type LicenseOrderRuntimeSearchInput, type OrganizationEmployeeCreationInput, type OrganizationEmployeeLicenseAddInput, type OrganizationEmployeeLicenseOfferInput, type OrganizationEmployeeLicenseInvitationInput, type OrganizationEmployeeLifecycleInput, type OrganizationEmployeeSearchInput, type ProfessionalAccessRequestInput, type ProfessionalAccessRequestResult, type RevokeProfessionalAccessInput, type RevokeProfessionalAccessResult, type RelatedPersonUpsertInput } from './resource-operations.js';
15
+ import { type CommunicationIngestionInput, type BlockchainArtifactRegistrationInput, type VitalSignBatchCommunicationFromSearchResponseInput, type CommunicationParticipantRuntimeSearchInput, type ClinicalBundleSearchInput, type ClinicalSectionUpdateInput, type ClinicalSummaryReadResult, type ClinicalSummaryRequestInput, type ClinicalSummaryUpdateInput, type GrantProfessionalAccessInput, type GrantProfessionalAccessResult, type DigitalTwinSecondaryUseConsentInput, type DigitalTwinSecondaryUseConsentResult, type DigitalTwinSubjectLinkPurgeInput, type DigitalTwinSubjectLinkPurgeResult, type IndividualMemberLifecycleInput, type IndividualMemberLicenseAddInput, type IndividualMemberLicenseInvitationInput, type IndividualMemberLicenseTransitionInput, type LicenseListRuntimeSearchInput, type LicenseOfferRuntimeSearchInput, type LicenseOrderRuntimeSearchInput, type OrganizationEmployeeCreationInput, type OrganizationEmployeeLicenseAddInput, type OrganizationEmployeeLicenseOfferInput, type OrganizationEmployeeLicenseInvitationInput, type OrganizationEmployeeLifecycleInput, type OrganizationEmployeeSearchInput, type ProfessionalAccessRequestInput, type ProfessionalAccessRequestResult, type RevokeProfessionalAccessInput, type RevokeProfessionalAccessResult, type RelatedPersonUpsertInput } from './resource-operations.js';
15
16
  import type { LegalOrganizationOrderInput } from './host-onboarding.js';
16
17
  import type { SmartTokenExchangeResult } from './smart-token.js';
17
18
  import { type OrganizationEmployeeProvisioningInput, type OrganizationEmployeeProvisioningResult } from './organization-employee-lifecycle.js';
@@ -254,10 +255,23 @@ export declare class HttpRuntimeClient implements NodeRuntimeClient {
254
255
  * have already been disabled/purged.
255
256
  */
256
257
  disableTenant(hostCtx: HostRouteContext, input: HostedTenantLifecycleInput, pollOptions?: PollOptions): Promise<SubmitAndPollResult>;
258
+ /** Re-enables one suspended hosted tenant without changing its legal identity. */
259
+ enableTenant(hostCtx: HostRouteContext, input: HostedTenantLifecycleInput, pollOptions?: PollOptions): Promise<SubmitAndPollResult>;
257
260
  /**
258
261
  * Purges one already-disabled hosted tenant through the host registry.
259
262
  */
260
263
  purgeTenant(hostCtx: HostRouteContext, input: HostedTenantLifecycleInput, pollOptions?: PollOptions): Promise<SubmitAndPollResult>;
264
+ /** Reads authoritative tenant and descendant lifecycle state from the host registry. */
265
+ getTenantLifecycleStatus(hostCtx: HostRouteContext, input: HostedTenantLifecycleInput, pollOptions?: PollOptions): Promise<SubmitAndPollResult>;
266
+ /** Explicitly disables one descendant group; tenant disable itself never cascades. */
267
+ disableTenantDescendants(hostCtx: HostRouteContext, input: HostedTenantLifecycleInput & {
268
+ descendantKind: HostedTenantDescendantKind;
269
+ }, pollOptions?: PollOptions): Promise<SubmitAndPollResult>;
270
+ /** Explicitly purges one descendant group; tenant purge itself never cascades. */
271
+ purgeTenantDescendants(hostCtx: HostRouteContext, input: HostedTenantLifecycleInput & {
272
+ descendantKind: HostedTenantDescendantKind;
273
+ }, pollOptions?: PollOptions): Promise<SubmitAndPollResult>;
274
+ private submitTenantDescendantLifecycle;
261
275
  /**
262
276
  * Creates an employee or professional entry under an existing organization tenant.
263
277
  */
@@ -419,6 +433,13 @@ export declare class HttpRuntimeClient implements NodeRuntimeClient {
419
433
  * Creates and submits a consent-oriented access grant for a professional actor.
420
434
  */
421
435
  grantProfessionalAccess(ctx: RouteContext, input: GrantProfessionalAccessInput): Promise<GrantProfessionalAccessResult>;
436
+ /**
437
+ * Applies the subject's reversible secondary-use decision and lets GW
438
+ * rebuild or pause the tenant-private digital-twin projection.
439
+ */
440
+ setDigitalTwinSecondaryUseConsent(ctx: RouteContext, input: DigitalTwinSecondaryUseConsentInput): Promise<DigitalTwinSecondaryUseConsentResult>;
441
+ /** Offboards the subject from this index provider without deleting the anonymous twin. */
442
+ purgeDigitalTwinSubjectLink(ctx: RouteContext, input: DigitalTwinSubjectLinkPurgeInput): Promise<DigitalTwinSubjectLinkPurgeResult>;
422
443
  /**
423
444
  * Closes an existing professional consent by setting its period end and
424
445
  * resubmitting the updated consent resource.
@@ -439,6 +460,11 @@ export declare class HttpRuntimeClient implements NodeRuntimeClient {
439
460
  * actor-neutral and is used by portal and telephone profile runtimes.
440
461
  */
441
462
  activateProfileDeviceWithActivationRequest(input: EmployeeDeviceActivationRequestInput): Promise<EmployeeDeviceActivationResult>;
463
+ /**
464
+ * Actor-facade name retained for organization controllers and employees.
465
+ * The implementation is actor-neutral and shares the profile-device flow.
466
+ */
467
+ activateEmployeeDeviceWithActivationRequest(input: EmployeeDeviceActivationRequestInput): Promise<EmployeeDeviceActivationResult>;
442
468
  /**
443
469
  * Creates or updates a `RelatedPerson` for non-employee family/caregiver
444
470
  * roles such as a grandfather, guardian, or external caregiver.
@@ -616,6 +642,8 @@ export declare class HttpRuntimeClient implements NodeRuntimeClient {
616
642
  individualRelatedPersonPurgePollPath(ctx?: RouteContext): string;
617
643
  individualConsentR4BatchPath(ctx: RouteContext): string;
618
644
  individualConsentR4PollPath(ctx: RouteContext): string;
645
+ individualResearchSubjectPurgePath(ctx: RouteContext): string;
646
+ individualResearchSubjectPurgePollPath(ctx: RouteContext): string;
619
647
  individualCommunicationBatchPath(ctx: RouteContext, format: string): string;
620
648
  individualCommunicationPollPath(ctx: RouteContext, format: string): string;
621
649
  individualCommunicationSearchPath(ctx: RouteContext): string;