gdc-sdk-node-ts 2.4.23 → 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 +18 -7
  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 +39 -4
  12. package/dist/node-runtime-client.js +142 -51
  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
@@ -1,3 +1,4 @@
1
+ import { type ConsentRule } from 'gdc-common-utils-ts/models/consent-rule';
1
2
  import { type IndividualControllerCredentialInput, type IndividualControllerVpPayloadInput, type IndividualSubjectCredentialInput } from 'gdc-common-utils-ts';
2
3
  import { type NodeRuntimeClient, type PollOptions, type SubmitAndPollResult, type SubmitPayload } from './client-port.js';
3
4
  import type { FamilyOrganizationSummary } from 'gdc-common-utils-ts/utils/family-organization-summary';
@@ -7,7 +8,7 @@ import type { IndividualOrganizationConfirmOrderInput, RouteContext } from '../i
7
8
  import type { IndividualOrganizationBootstrapInput, IndividualOrganizationStartResult } from '../individual-start.js';
8
9
  import type { NodeCapability } from '../session.js';
9
10
  import type { IndividualOrganizationLifecycleInput } from 'gdc-sdk-core-ts';
10
- import type { BlockchainArtifactRegistrationInput, ClinicalBundleSearchInput, ClinicalSectionUpdateInput, ClinicalSummaryReadResult, ClinicalSummaryRequestInput, ClinicalSummaryUpdateInput, CommunicationIngestionInput, CommunicationParticipantRuntimeSearchInput, DigitalTwinGenerationInput, GrantProfessionalAccessInput, GrantProfessionalAccessResult, IndividualMemberLifecycleInput, IndividualMemberLicenseAddInput, IndividualMemberLicenseInvitationInput, IndividualMemberLicenseTransitionInput, IpsOrFhirImportInput, LicenseListRuntimeSearchInput, LicenseOfferRuntimeSearchInput, LicenseOrderRuntimeSearchInput, RevokeProfessionalAccessInput, RevokeProfessionalAccessResult, ProfessionalAccessRequestDecisionInput, ProfessionalAccessRequestSearchInput, RelatedPersonUpsertInput } from '../resource-operations.js';
11
+ import type { BlockchainArtifactRegistrationInput, ClinicalBundleSearchInput, ClinicalSectionUpdateInput, ClinicalSummaryReadResult, ClinicalSummaryRequestInput, ClinicalSummaryUpdateInput, CommunicationIngestionInput, CommunicationParticipantRuntimeSearchInput, DigitalTwinGenerationInput, DigitalTwinSecondaryUseConsentInput, DigitalTwinSecondaryUseConsentResult, DigitalTwinSubjectLinkPurgeInput, DigitalTwinSubjectLinkPurgeResult, GrantProfessionalAccessInput, GrantProfessionalAccessResult, IndividualMemberLifecycleInput, IndividualMemberLicenseAddInput, IndividualMemberLicenseInvitationInput, IndividualMemberLicenseTransitionInput, IpsOrFhirImportInput, LicenseListRuntimeSearchInput, LicenseOfferRuntimeSearchInput, LicenseOrderRuntimeSearchInput, RevokeProfessionalAccessInput, RevokeProfessionalAccessResult, ProfessionalAccessRequestDecisionInput, ProfessionalAccessRequestSearchInput, RelatedPersonUpsertInput } from '../resource-operations.js';
11
12
  import type { SmartTokenExchangeResult, SmartTokenRequestInput } from '../smart-token.js';
12
13
  /**
13
14
  * Individual-controller oriented facade over a `NodeRuntimeClient`.
@@ -117,10 +118,35 @@ export declare class IndividualControllerSdk {
117
118
  */
118
119
  searchCommunicationParticipants(ctx: RouteContext, input: CommunicationParticipantRuntimeSearchInput): Promise<SubmitAndPollResult>;
119
120
  /**
120
- * Explicitly packages subject data for a study/transfer flow.
121
- * Normal clinical ingestion is projected into the tenant research index by GW.
121
+ * Legacy direct Composition transfer hook.
122
+ *
123
+ * @deprecated Canonical twins are created only by GW from current subject
124
+ * data after `setDigitalTwinSecondaryUseConsent(..., { decision: 'permit' })`.
125
+ * The Node runtime intentionally does not publish canonical Compositions.
122
126
  */
123
127
  generateDigitalTwinFromSubjectData(ctx: RouteContext, input: DigitalTwinGenerationInput): Promise<SubmitAndPollResult>;
128
+ /**
129
+ * Enables or disables the subject's secondary-use digital-twin projection.
130
+ * This is the canonical patient-side operation; application code must not
131
+ * publish a canonical Composition directly into the research index.
132
+ */
133
+ setDigitalTwinSecondaryUseConsent(ctx: RouteContext, input: DigitalTwinSecondaryUseConsentInput): Promise<DigitalTwinSecondaryUseConsentResult>;
134
+ /**
135
+ * Offboards the subject from the current index provider. This removes only
136
+ * the private subject/twin correspondence; it never deletes anonymous twin
137
+ * data already shared for research.
138
+ */
139
+ purgeDigitalTwinSubjectLink(ctx: RouteContext, input: DigitalTwinSubjectLinkPurgeInput): Promise<DigitalTwinSubjectLinkPurgeResult>;
140
+ /** Returns whether the subject currently permits projection by its index provider. */
141
+ getDigitalTwinSecondaryUseConsentStatus(ctx: RouteContext, input: Readonly<{
142
+ subjectDid: string;
143
+ indexProviderOrganizationDid: string;
144
+ consentIdentifier: string;
145
+ }>): Promise<Readonly<{
146
+ exists: boolean;
147
+ enabled: boolean;
148
+ consent?: ConsentRule;
149
+ }>>;
124
150
  /**
125
151
  * Searches indexed clinical bundles for the current subject/controller context.
126
152
  */
@@ -1,8 +1,11 @@
1
1
  // Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
2
2
  import { ActorCapabilities, ActorKinds } from 'gdc-common-utils-ts/constants/actor-session';
3
+ import { HealthcareConsentPurposes, ServiceCapability } from 'gdc-common-utils-ts/constants';
4
+ import { ClaimConsent } from 'gdc-common-utils-ts/models/consent-rule';
3
5
  import { buildIndividualControllerIdentityVpPayload, buildUnsignedIndividualControllerIdentityVpJwt, getIndividualControllerIdentitySameAs, getIndividualControllerIdentityVC, getIndividualSubjectVC, } from 'gdc-common-utils-ts';
4
6
  import { requireClientMethod, submitAndPollWithClient, } from './client-port.js';
5
7
  import { assertFacadeCapability } from './capability-guard.js';
8
+ import { GatewayActiveConsentProvider } from '../gateway-active-consent-provider.js';
6
9
  import { buildProfessionalAccessRequestDecisionGrant } from '../resource-operations.js';
7
10
  import { buildProfessionalAccessRequestSearchInput } from '../resource-operations.js';
8
11
  /**
@@ -175,13 +178,56 @@ export class IndividualControllerSdk {
175
178
  return requireClientMethod(this.client, 'searchCommunicationParticipants')(ctx, input);
176
179
  }
177
180
  /**
178
- * Explicitly packages subject data for a study/transfer flow.
179
- * Normal clinical ingestion is projected into the tenant research index by GW.
181
+ * Legacy direct Composition transfer hook.
182
+ *
183
+ * @deprecated Canonical twins are created only by GW from current subject
184
+ * data after `setDigitalTwinSecondaryUseConsent(..., { decision: 'permit' })`.
185
+ * The Node runtime intentionally does not publish canonical Compositions.
180
186
  */
181
187
  generateDigitalTwinFromSubjectData(ctx, input) {
182
188
  assertFacadeCapability(this.capabilities, ActorCapabilities.IndividualGenerateDigitalTwin, ActorKinds.IndividualController, 'generateDigitalTwinFromSubjectData');
183
189
  return requireClientMethod(this.client, 'generateDigitalTwinFromSubjectData')(ctx, input);
184
190
  }
191
+ /**
192
+ * Enables or disables the subject's secondary-use digital-twin projection.
193
+ * This is the canonical patient-side operation; application code must not
194
+ * publish a canonical Composition directly into the research index.
195
+ */
196
+ setDigitalTwinSecondaryUseConsent(ctx, input) {
197
+ assertFacadeCapability(this.capabilities, ActorCapabilities.IndividualGenerateDigitalTwin, ActorKinds.IndividualController, 'setDigitalTwinSecondaryUseConsent');
198
+ return requireClientMethod(this.client, 'setDigitalTwinSecondaryUseConsent')(ctx, input);
199
+ }
200
+ /**
201
+ * Offboards the subject from the current index provider. This removes only
202
+ * the private subject/twin correspondence; it never deletes anonymous twin
203
+ * data already shared for research.
204
+ */
205
+ purgeDigitalTwinSubjectLink(ctx, input) {
206
+ assertFacadeCapability(this.capabilities, ActorCapabilities.IndividualGenerateDigitalTwin, ActorKinds.IndividualController, 'purgeDigitalTwinSubjectLink');
207
+ return requireClientMethod(this.client, 'purgeDigitalTwinSubjectLink')(ctx, input);
208
+ }
209
+ /** Returns whether the subject currently permits projection by its index provider. */
210
+ async getDigitalTwinSecondaryUseConsentStatus(ctx, input) {
211
+ assertFacadeCapability(this.capabilities, ActorCapabilities.IndividualGenerateDigitalTwin, ActorKinds.IndividualController, 'getDigitalTwinSecondaryUseConsentStatus');
212
+ const activeConsents = await new GatewayActiveConsentProvider(this.client, ctx)
213
+ .getActiveConsentsForSubject(input.subjectDid);
214
+ const consentIdentifier = String(input.consentIdentifier || '').trim();
215
+ if (!consentIdentifier)
216
+ throw new Error('consentIdentifier is required to distinguish the portal rule from study consents.');
217
+ const indexProviderOrganizationDid = String(input.indexProviderOrganizationDid || '').trim();
218
+ if (!indexProviderOrganizationDid)
219
+ throw new Error('indexProviderOrganizationDid is required.');
220
+ const consent = activeConsents.find((rule) => {
221
+ const actions = String(rule[ClaimConsent.action] || '').split(',').map((value) => value.trim());
222
+ return String(rule[ClaimConsent.identifier] || '').trim() === consentIdentifier
223
+ && String(rule[ClaimConsent.actorIdentifier] || '').trim() === indexProviderOrganizationDid
224
+ && String(rule[ClaimConsent.purpose] || '').trim().toUpperCase() === String(HealthcareConsentPurposes.Research).toUpperCase()
225
+ && actions.includes(ServiceCapability.DigitalTwinReader);
226
+ });
227
+ return consent
228
+ ? { exists: true, enabled: String(consent[ClaimConsent.decision] || '').trim().toLowerCase() === 'permit', consent }
229
+ : { exists: false, enabled: false };
230
+ }
185
231
  /**
186
232
  * Searches indexed clinical bundles for the current subject/controller context.
187
233
  */
@@ -107,17 +107,18 @@ export declare class OrganizationControllerSdk {
107
107
  listLicenseOrders(ctx: RouteContext, input?: LicenseOrderRuntimeSearchInput): Promise<SubmitAndPollResult>;
108
108
  /**
109
109
  * Confirms an already paid organization-side license order so GW CORE can
110
- * activate additional tenant seats once the public route exists.
110
+ * activate additional tenant seats.
111
111
  *
112
112
  * The commercial/payment step happens outside GW CORE. This method models
113
113
  * the follow-up confirmation that should materialize new seats from the
114
114
  * accepted order.
115
115
  *
116
- * Current runtime note:
117
- * - search/list of organization license offers and orders already exists
118
- * - the public/write post-payment seat-activation route is not converged yet
119
- * - current runtime clients therefore throw an explicit unsupported-flow
120
- * error instead of fabricating an unstable payload contract
116
+ * Security/routing contract:
117
+ * - the request is routed through the host `Order/_batch` endpoint
118
+ * - the signed `iss` is still the exact controller DID registered by DCR for
119
+ * `ctx.tenantId`; its `kid`/`skid` keys belong to that tenant, not the host
120
+ * - the configured runtime transport profile applies unchanged, including
121
+ * DIDComm encryption in protected staging/production runtimes
121
122
  */
122
123
  confirmOrganizationLicenseOrder(ctx: RouteContext, input: OrganizationLicenseOrderConfirmInput, pollOptions?: PollOptions): Promise<SubmitAndPollResult>;
123
124
  /**
@@ -133,11 +134,23 @@ export declare class OrganizationControllerSdk {
133
134
  * employees or individual/member descendants remain.
134
135
  */
135
136
  disableTenant(hostCtx: HostRouteContext, input: HostedTenantLifecycleInput, pollOptions?: PollOptions): Promise<SubmitAndPollResult>;
137
+ /** Re-enables the same suspended tenant and immutable legal identifier. */
138
+ enableTenant(hostCtx: HostRouteContext, input: HostedTenantLifecycleInput, pollOptions?: PollOptions): Promise<SubmitAndPollResult>;
136
139
  /**
137
140
  * Purges the hosted tenant through the host registry after tenant disable and
138
141
  * descendant purges have both completed.
139
142
  */
140
143
  purgeTenant(hostCtx: HostRouteContext, input: HostedTenantLifecycleInput, pollOptions?: PollOptions): Promise<SubmitAndPollResult>;
144
+ /** Returns host-confirmed tenant state and descendant counters. */
145
+ getTenantLifecycleStatus(hostCtx: HostRouteContext, input: HostedTenantLifecycleInput, pollOptions?: PollOptions): Promise<SubmitAndPollResult>;
146
+ /** Explicitly disables individuals before tenant disable. Employees use their dedicated lifecycle. */
147
+ disableTenantDescendants(hostCtx: HostRouteContext, input: HostedTenantLifecycleInput & {
148
+ descendantKind: 'individuals';
149
+ }, pollOptions?: PollOptions): Promise<SubmitAndPollResult>;
150
+ /** Explicitly purges individuals before tenant purge. Employees use their dedicated lifecycle. */
151
+ purgeTenantDescendants(hostCtx: HostRouteContext, input: HostedTenantLifecycleInput & {
152
+ descendantKind: 'individuals';
153
+ }, pollOptions?: PollOptions): Promise<SubmitAndPollResult>;
141
154
  /**
142
155
  * Activates the employee device from a previously issued activation request.
143
156
  */
@@ -147,17 +147,18 @@ export class OrganizationControllerSdk {
147
147
  }
148
148
  /**
149
149
  * Confirms an already paid organization-side license order so GW CORE can
150
- * activate additional tenant seats once the public route exists.
150
+ * activate additional tenant seats.
151
151
  *
152
152
  * The commercial/payment step happens outside GW CORE. This method models
153
153
  * the follow-up confirmation that should materialize new seats from the
154
154
  * accepted order.
155
155
  *
156
- * Current runtime note:
157
- * - search/list of organization license offers and orders already exists
158
- * - the public/write post-payment seat-activation route is not converged yet
159
- * - current runtime clients therefore throw an explicit unsupported-flow
160
- * error instead of fabricating an unstable payload contract
156
+ * Security/routing contract:
157
+ * - the request is routed through the host `Order/_batch` endpoint
158
+ * - the signed `iss` is still the exact controller DID registered by DCR for
159
+ * `ctx.tenantId`; its `kid`/`skid` keys belong to that tenant, not the host
160
+ * - the configured runtime transport profile applies unchanged, including
161
+ * DIDComm encryption in protected staging/production runtimes
161
162
  */
162
163
  confirmOrganizationLicenseOrder(ctx, input, pollOptions) {
163
164
  return requireClientMethod(this.client, 'confirmOrganizationLicenseOrder')(ctx, input, pollOptions);
@@ -184,6 +185,11 @@ export class OrganizationControllerSdk {
184
185
  assertFacadeCapability(this.capabilities, ActorCapabilities.OrganizationDisableTenant, ActorKinds.OrganizationController, 'disableTenant');
185
186
  return requireClientMethod(this.client, 'disableTenant')(hostCtx, input, pollOptions);
186
187
  }
188
+ /** Re-enables the same suspended tenant and immutable legal identifier. */
189
+ enableTenant(hostCtx, input, pollOptions) {
190
+ assertFacadeCapability(this.capabilities, ActorCapabilities.OrganizationDisableTenant, ActorKinds.OrganizationController, 'enableTenant');
191
+ return requireClientMethod(this.client, 'enableTenant')(hostCtx, input, pollOptions);
192
+ }
187
193
  /**
188
194
  * Purges the hosted tenant through the host registry after tenant disable and
189
195
  * descendant purges have both completed.
@@ -192,6 +198,21 @@ export class OrganizationControllerSdk {
192
198
  assertFacadeCapability(this.capabilities, ActorCapabilities.OrganizationPurgeTenant, ActorKinds.OrganizationController, 'purgeTenant');
193
199
  return requireClientMethod(this.client, 'purgeTenant')(hostCtx, input, pollOptions);
194
200
  }
201
+ /** Returns host-confirmed tenant state and descendant counters. */
202
+ getTenantLifecycleStatus(hostCtx, input, pollOptions) {
203
+ assertFacadeCapability(this.capabilities, ActorCapabilities.OrganizationDisableTenant, ActorKinds.OrganizationController, 'getTenantLifecycleStatus');
204
+ return requireClientMethod(this.client, 'getTenantLifecycleStatus')(hostCtx, input, pollOptions);
205
+ }
206
+ /** Explicitly disables individuals before tenant disable. Employees use their dedicated lifecycle. */
207
+ disableTenantDescendants(hostCtx, input, pollOptions) {
208
+ assertFacadeCapability(this.capabilities, ActorCapabilities.OrganizationDisableTenant, ActorKinds.OrganizationController, 'disableTenantDescendants');
209
+ return requireClientMethod(this.client, 'disableTenantDescendants')(hostCtx, input, pollOptions);
210
+ }
211
+ /** Explicitly purges individuals before tenant purge. Employees use their dedicated lifecycle. */
212
+ purgeTenantDescendants(hostCtx, input, pollOptions) {
213
+ assertFacadeCapability(this.capabilities, ActorCapabilities.OrganizationPurgeTenant, ActorKinds.OrganizationController, 'purgeTenantDescendants');
214
+ return requireClientMethod(this.client, 'purgeTenantDescendants')(hostCtx, input, pollOptions);
215
+ }
195
216
  /**
196
217
  * Activates the employee device from a previously issued activation request.
197
218
  */
@@ -1,13 +1,15 @@
1
1
  import type { NodeLegalOrganizationVerificationTransactionInput, PollOptions, SubmitAndPollResult } from './orchestration/client-port.js';
2
2
  import type { HostRouteContext } from './host-onboarding.js';
3
3
  import type { RouteContext } from './individual-onboarding.js';
4
- import type { EmployeeDeviceActivationResult } from './device-activation.js';
4
+ import type { EmployeeDeviceActivationResult, ProfileDeviceRegistrationInput } from './device-activation.js';
5
5
  export type OrganizationControllerRecoveryInput = {
6
6
  credentialReissuanceInput?: NodeLegalOrganizationVerificationTransactionInput;
7
7
  /** @deprecated Use `credentialReissuanceInput`. */
8
8
  issueInput?: NodeLegalOrganizationVerificationTransactionInput;
9
9
  controllerIdToken: string;
10
- dcrPayload: Record<string, unknown>;
10
+ deviceRegistration?: ProfileDeviceRegistrationInput;
11
+ /** @deprecated Use a profile-device draft and pass its `deviceRegistration`. */
12
+ dcrPayload?: Record<string, unknown>;
11
13
  credentialReissuancePollOptions?: PollOptions;
12
14
  /** @deprecated Use `credentialReissuancePollOptions`. */
13
15
  issuePollOptions?: PollOptions;
@@ -1,5 +1,5 @@
1
1
  // Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
2
- import { activateEmployeeDeviceWithActivationCodeWithDeps } from './device-activation.js';
2
+ import { activateEmployeeDeviceWithActivationCodeWithDeps, activateEmployeeDeviceWithActivationRequestWithDeps, } from './device-activation.js';
3
3
  export async function recoverOrganizationControllerWithCredentialReissuanceWithDeps(deps) {
4
4
  /**
5
5
  * Existing-tenant controller recovery contract:
@@ -24,19 +24,25 @@ export async function recoverOrganizationControllerWithCredentialReissuanceWithD
24
24
  if (!activationCode) {
25
25
  throw new Error('recoverOrganizationControllerWithCredentialReissuance: missing org.schema.IndividualProduct.serialNumber in Organization/_issue response.');
26
26
  }
27
- const activation = await activateEmployeeDeviceWithActivationCodeWithDeps({
27
+ const activation = await activateEmployeeDeviceWithActivationRequestWithDeps({
28
28
  routeCtx: deps.tenantCtx,
29
29
  input: {
30
30
  activationCode,
31
31
  idToken: deps.input.controllerIdToken,
32
+ deviceRegistration: deps.input.deviceRegistration,
32
33
  dcrPayload: deps.input.dcrPayload,
33
- pollOptions: deps.input.activationPollOptions,
34
34
  },
35
- identityTokenExchangePath: deps.identityTokenExchangePath,
36
- identityTokenExchangePollPath: deps.identityTokenExchangePollPath,
37
- identityDeviceDcrPath: deps.identityDeviceDcrPath,
38
- identityDeviceDcrPollPath: deps.identityDeviceDcrPollPath,
39
- submitAndPollWithBearerToken: deps.submitAndPollWithBearerToken,
35
+ defaultTimeoutMs: deps.input.activationPollOptions?.timeoutMs,
36
+ defaultIntervalMs: deps.input.activationPollOptions?.intervalMs,
37
+ activateEmployeeDeviceWithActivationCode: (routeCtx, input) => activateEmployeeDeviceWithActivationCodeWithDeps({
38
+ routeCtx,
39
+ input,
40
+ identityTokenExchangePath: deps.identityTokenExchangePath,
41
+ identityTokenExchangePollPath: deps.identityTokenExchangePollPath,
42
+ identityDeviceDcrPath: deps.identityDeviceDcrPath,
43
+ identityDeviceDcrPollPath: deps.identityDeviceDcrPollPath,
44
+ submitAndPollWithBearerToken: deps.submitAndPollWithBearerToken,
45
+ }),
40
46
  });
41
47
  return {
42
48
  credentialReissuance,
@@ -21,6 +21,8 @@ export type OrganizationEmployeeProvisioningResult = Readonly<{
21
21
  license: SubmitAndPollResult;
22
22
  licenseOrder?: SubmitAndPollResult;
23
23
  activationCode: string;
24
+ /** Effective installation allowance returned by the reserved GW seat. */
25
+ maxDevices?: number;
24
26
  }>;
25
27
  /** Shared dependencies for the create plus seat issuance workflow. */
26
28
  export type OrganizationEmployeeProvisioningDeps = Readonly<{
@@ -34,6 +36,8 @@ export type OrganizationEmployeeProvisioningDeps = Readonly<{
34
36
  * returns the exact GW activation credential.
35
37
  */
36
38
  export declare function provisionOrganizationEmployeeWithDeps(routeContext: RouteContext, input: OrganizationEmployeeProvisioningInput, deps: OrganizationEmployeeProvisioningDeps): Promise<OrganizationEmployeeProvisioningResult>;
39
+ /** Reads the effective positive installation allowance returned by `License/_issue`. */
40
+ export declare function readEmployeeLicenseMaxDevices(value: unknown): number | undefined;
37
41
  /**
38
42
  * Reads the pending seat Offer from an employee-create response.
39
43
  *
@@ -35,7 +35,23 @@ export async function provisionOrganizationEmployeeWithDeps(routeContext, input,
35
35
  if (!activationCode) {
36
36
  throw new Error('provisionOrganizationEmployee: GW response did not contain an employee activation credential.');
37
37
  }
38
- return { employee, license, licenseOrder, activationCode };
38
+ const maxDevices = readEmployeeLicenseMaxDevices(license.poll.body);
39
+ return {
40
+ employee,
41
+ license,
42
+ licenseOrder,
43
+ activationCode,
44
+ ...(maxDevices ? { maxDevices } : {}),
45
+ };
46
+ }
47
+ /** Reads the effective positive installation allowance returned by `License/_issue`. */
48
+ export function readEmployeeLicenseMaxDevices(value) {
49
+ for (const candidate of nestedRecords(value)) {
50
+ const configured = Number(record(candidate.meta)?.maxDevices ?? candidate.maxDevices);
51
+ if (Number.isInteger(configured) && configured > 0)
52
+ return configured;
53
+ }
54
+ return undefined;
39
55
  }
40
56
  /**
41
57
  * Reads the pending seat Offer from an employee-create response.
@@ -14,11 +14,16 @@ import type { RouteContext } from './individual-onboarding.js';
14
14
  * Transport note:
15
15
  * - current GW CORE exposes this confirmation step through the host
16
16
  * `registry/org.schema/Order/_batch` route
17
- * - the organization controller still reasons in tenant context, so the
18
- * runtime adapts that higher-level intent onto the current host route
17
+ * - that host route owns commercial routing and persistence only; it does not
18
+ * change the security principal or move controller keys into the host vault
19
+ * - `issuerDid` must be the exact controller DID registered through DCR for the
20
+ * tenant in `routeCtx`; signed/encrypted transports therefore emit its
21
+ * registered `kid`/`skid`, and GW resolves those public keys from that tenant
22
+ * - `aud` remains the tenant id so GW cannot confuse a host-routed Order with a
23
+ * host-authored controller request
19
24
  */
20
25
  export type OrganizationLicenseOrderConfirmInput = Readonly<{
21
- /** Exact DCR-registered controller DID that signs the protected request. */
26
+ /** Exact tenant DCR-registered controller DID used as `iss` and key owner. */
22
27
  issuerDid: string;
23
28
  offerId: string;
24
29
  hostNetwork?: string;
@@ -501,6 +501,33 @@ export type DigitalTwinGenerationInput = {
501
501
  intervalMs?: number;
502
502
  };
503
503
  };
504
+ /** Subject decision controlling publication into the pseudonymous research index. */
505
+ export type DigitalTwinSecondaryUseConsentInput = {
506
+ subjectDid: string;
507
+ /** Organization/tenant that provides and stores the subject's index. */
508
+ indexProviderOrganizationDid: string;
509
+ decision: 'permit' | 'deny';
510
+ /** Server-owned FHIR Consent identifier. Never accept it from the browser. */
511
+ consentIdentifier: string;
512
+ consentDate?: string;
513
+ dataType?: string;
514
+ pollOptions?: {
515
+ timeoutMs?: number;
516
+ intervalMs?: number;
517
+ };
518
+ };
519
+ /** Result of enabling or disabling one subject's digital-twin publication. */
520
+ export type DigitalTwinSecondaryUseConsentResult = GrantProfessionalAccessResult;
521
+ /** Provider-offboarding command that destroys only the private subject/twin binding. */
522
+ export type DigitalTwinSubjectLinkPurgeInput = {
523
+ subjectDid: string;
524
+ pollOptions?: {
525
+ timeoutMs?: number;
526
+ intervalMs?: number;
527
+ };
528
+ };
529
+ /** Async GW result for a subject/twin binding purge. */
530
+ export type DigitalTwinSubjectLinkPurgeResult = SubmitAndPollResult;
504
531
  export declare function createOrganizationEmployeeWithDeps(routeCtx: RouteContext, input: OrganizationEmployeeCreationInput, options: {
505
532
  timeoutMs?: number;
506
533
  intervalMs?: number;
@@ -882,6 +909,46 @@ export declare function grantProfessionalAccessWithDeps(routeCtx: RouteContext,
882
909
  intervalMs?: number;
883
910
  }) => Promise<SubmitAndPollResult>;
884
911
  }): Promise<GrantProfessionalAccessResult>;
912
+ /**
913
+ * Enables or disables secondary research use for one subject.
914
+ *
915
+ * `permit` asks GW to build or rebuild the current pseudonymous projection;
916
+ * `deny` pauses later synchronization while preserving the published twin and
917
+ * tenant-private subject alias.
918
+ */
919
+ export declare function setDigitalTwinSecondaryUseConsentWithDeps(routeCtx: RouteContext, input: DigitalTwinSecondaryUseConsentInput, deps: {
920
+ buildConsentClaimsWithCid: (input: GrantProfessionalAccessInput, options?: {
921
+ consentIdentifierFactory: () => string;
922
+ }) => {
923
+ actorIdentifier: string;
924
+ subjectIdentifier: string;
925
+ consentClaims: Record<string, unknown>;
926
+ claimsCid?: string;
927
+ };
928
+ individualConsentR4BatchPath: (ctx: RouteContext) => string;
929
+ individualConsentR4PollPath: (ctx: RouteContext) => string;
930
+ submitAndPoll: (submitPath: string, pollPath: string, payload: {
931
+ thid?: string;
932
+ } & Record<string, unknown>, pollOptions?: {
933
+ timeoutMs?: number;
934
+ intervalMs?: number;
935
+ }) => Promise<SubmitAndPollResult>;
936
+ }): Promise<DigitalTwinSecondaryUseConsentResult>;
937
+ /**
938
+ * Removes the index provider's private individual-to-twin correspondence.
939
+ * The anonymous twin is intentionally not deleted. A later enrollment gets a
940
+ * new UUID and cannot reconnect the detached twin to the individual.
941
+ */
942
+ export declare function purgeDigitalTwinSubjectLinkWithDeps(routeCtx: RouteContext, input: DigitalTwinSubjectLinkPurgeInput, deps: {
943
+ individualResearchSubjectPurgePath: (ctx: RouteContext) => string;
944
+ individualResearchSubjectPurgePollPath: (ctx: RouteContext) => string;
945
+ submitAndPoll: (submitPath: string, pollPath: string, payload: {
946
+ thid?: string;
947
+ } & Record<string, unknown>, pollOptions?: {
948
+ timeoutMs?: number;
949
+ intervalMs?: number;
950
+ }) => Promise<SubmitAndPollResult>;
951
+ }): Promise<DigitalTwinSubjectLinkPurgeResult>;
885
952
  export declare function revokeProfessionalAccessWithDeps(routeCtx: RouteContext, input: RevokeProfessionalAccessInput, deps: {
886
953
  individualConsentR4BatchPath: (ctx: RouteContext) => string;
887
954
  individualConsentR4PollPath: (ctx: RouteContext) => string;
@@ -1,5 +1,5 @@
1
1
  // Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
2
- import { HealthcareBasicSections, ResourceTypesFhirR4 } from 'gdc-common-utils-ts/constants';
2
+ import { HealthcareBasicSections, HealthcareConsentPurposes, ResourceTypesFhirR4, ServiceCapability, } from 'gdc-common-utils-ts/constants';
3
3
  import { Format } from 'gdc-common-utils-ts/constants/Schemas';
4
4
  import { RelatedPersonClaim } from 'gdc-common-utils-ts/models/interoperable-claims/related-person-claims';
5
5
  import { CommunicationClaim } from 'gdc-common-utils-ts/models/interoperable-claims/communication-claims';
@@ -9,6 +9,8 @@ import { DeviceAppTypes, DeviceUserClasses } from 'gdc-common-utils-ts/constants
9
9
  import { addFhirResourceToCommunication, createCommunicationResource, buildClinicalSummaryCommunicationJob, buildPermissionRequestCommunication, createClinicalSectionUpdateOutboxJob, createClinicalSummaryUpdateOutboxJob, readClinicalSummaryOperationResult, TransportProfiles, } from 'gdc-sdk-core-ts';
10
10
  import { GwCoreLifecycleRequestMethod, GwCoreLifecycleRequestType, GwCoreLifecycleTodo, } from './constants/lifecycle.js';
11
11
  import { buildEmployeeSearchBundle, ConsentClaims, } from 'gdc-sdk-core-ts';
12
+ import { ClaimConsent } from 'gdc-common-utils-ts/models/consent-rule';
13
+ import { assignCidToClaimsId } from 'gdc-common-utils-ts/utils/fhir-cid';
12
14
  export function buildClinicalSectionUpdateIngestion(input) {
13
15
  return {
14
16
  communicationJob: createClinicalSectionUpdateOutboxJob(input),
@@ -801,6 +803,65 @@ export async function grantProfessionalAccessWithDeps(routeCtx, input, deps) {
801
803
  claimsCid: built.claimsCid,
802
804
  };
803
805
  }
806
+ /**
807
+ * Enables or disables secondary research use for one subject.
808
+ *
809
+ * `permit` asks GW to build or rebuild the current pseudonymous projection;
810
+ * `deny` pauses later synchronization while preserving the published twin and
811
+ * tenant-private subject alias.
812
+ */
813
+ export async function setDigitalTwinSecondaryUseConsentWithDeps(routeCtx, input, deps) {
814
+ if (!String(input.consentIdentifier || '').trim()) {
815
+ throw new Error('consentIdentifier is required for idempotent digital-twin consent updates.');
816
+ }
817
+ const indexProviderOrganizationDid = String(input.indexProviderOrganizationDid || '').trim();
818
+ if (!indexProviderOrganizationDid) {
819
+ throw new Error('indexProviderOrganizationDid is required.');
820
+ }
821
+ const buildFhirConsentClaimsWithoutLegacyAttachment = (consentInput, options) => {
822
+ const built = deps.buildConsentClaimsWithCid(consentInput, options);
823
+ const claims = { ...built.consentClaims };
824
+ delete claims[ClaimConsent.attachmentContentType];
825
+ delete claims[ClaimConsent.attachmentData];
826
+ const context = String(claims['@context'] || '').trim();
827
+ if (context) {
828
+ delete claims[`${context}.${ClaimConsent.attachmentContentType}`];
829
+ delete claims[`${context}.${ClaimConsent.attachmentData}`];
830
+ }
831
+ const assigned = assignCidToClaimsId(claims);
832
+ return { ...built, consentClaims: assigned.claims, claimsCid: assigned.cid };
833
+ };
834
+ return grantProfessionalAccessWithDeps(routeCtx, {
835
+ subjectDid: input.subjectDid,
836
+ actorId: indexProviderOrganizationDid,
837
+ actorRole: '*',
838
+ purpose: HealthcareConsentPurposes.Research,
839
+ actions: [ServiceCapability.DigitalTwinReader],
840
+ decision: input.decision,
841
+ consentIdentifier: input.consentIdentifier,
842
+ consentDate: input.consentDate,
843
+ dataType: input.dataType,
844
+ pollOptions: input.pollOptions,
845
+ }, { ...deps, buildConsentClaimsWithCid: buildFhirConsentClaimsWithoutLegacyAttachment });
846
+ }
847
+ /**
848
+ * Removes the index provider's private individual-to-twin correspondence.
849
+ * The anonymous twin is intentionally not deleted. A later enrollment gets a
850
+ * new UUID and cannot reconnect the detached twin to the individual.
851
+ */
852
+ export async function purgeDigitalTwinSubjectLinkWithDeps(routeCtx, input, deps) {
853
+ const subjectDid = String(input.subjectDid || '').trim();
854
+ if (!subjectDid)
855
+ throw new Error('subjectDid is required.');
856
+ const thid = `digital-twin-link-purge-${createRuntimeUuid()}`;
857
+ return deps.submitAndPoll(deps.individualResearchSubjectPurgePath(routeCtx), deps.individualResearchSubjectPurgePollPath(routeCtx), {
858
+ thid,
859
+ body: {
860
+ resourceType: 'Parameters',
861
+ parameter: [{ name: 'subject', valueString: subjectDid }],
862
+ },
863
+ }, input.pollOptions);
864
+ }
804
865
  export async function revokeProfessionalAccessWithDeps(routeCtx, input, deps) {
805
866
  const revokedClaims = ConsentClaims
806
867
  .fromClaims(input.consentClaims)
@@ -22,8 +22,16 @@ export declare class RuntimeClientPaths {
22
22
  hostRegistryOrganizationActivatePollPath(ctx?: HostRouteContext): string;
23
23
  hostRegistryOrganizationDisablePath(ctx?: HostRouteContext): string;
24
24
  hostRegistryOrganizationDisablePollPath(ctx?: HostRouteContext): string;
25
+ hostRegistryOrganizationEnablePath(ctx?: HostRouteContext): string;
26
+ hostRegistryOrganizationEnablePollPath(ctx?: HostRouteContext): string;
25
27
  hostRegistryOrganizationPurgePath(ctx?: HostRouteContext): string;
26
28
  hostRegistryOrganizationPurgePollPath(ctx?: HostRouteContext): string;
29
+ hostRegistryOrganizationStatusPath(ctx?: HostRouteContext): string;
30
+ hostRegistryOrganizationStatusPollPath(ctx?: HostRouteContext): string;
31
+ hostRegistryOrganizationDisableDescendantsPath(ctx?: HostRouteContext): string;
32
+ hostRegistryOrganizationDisableDescendantsPollPath(ctx?: HostRouteContext): string;
33
+ hostRegistryOrganizationPurgeDescendantsPath(ctx?: HostRouteContext): string;
34
+ hostRegistryOrganizationPurgeDescendantsPollPath(ctx?: HostRouteContext): string;
27
35
  hostRegistryOrderBatchPath(ctx?: HostRouteContext): string;
28
36
  hostRegistryOrderPollPath(ctx?: HostRouteContext): string;
29
37
  employeeBatchPath(ctx?: RouteContext): string;
@@ -75,6 +83,8 @@ export declare class RuntimeClientPaths {
75
83
  individualRelatedPersonPurgePollPath(ctx?: RouteContext): string;
76
84
  individualConsentR4BatchPath(ctx: RouteContext): string;
77
85
  individualConsentR4PollPath(ctx: RouteContext): string;
86
+ individualResearchSubjectPurgePath(ctx: RouteContext): string;
87
+ individualResearchSubjectPurgePollPath(ctx: RouteContext): string;
78
88
  individualCommunicationBatchPath(ctx: RouteContext, format: string): string;
79
89
  individualCommunicationPollPath(ctx: RouteContext, format: string): string;
80
90
  individualCommunicationSearchPath(ctx: RouteContext): string;
@@ -36,8 +36,16 @@ export class RuntimeClientPaths {
36
36
  hostRegistryOrganizationActivatePollPath(ctx) { return this.hostRegistryPath(ctx, 'Organization', '_activate-response'); }
37
37
  hostRegistryOrganizationDisablePath(ctx) { return this.hostRegistryPath(ctx, 'Organization', GwCoreLifecycleAction.Disable); }
38
38
  hostRegistryOrganizationDisablePollPath(ctx) { return this.hostRegistryPath(ctx, 'Organization', `${GwCoreLifecycleAction.Disable}-response`); }
39
+ hostRegistryOrganizationEnablePath(ctx) { return this.hostRegistryPath(ctx, 'Organization', GwCoreLifecycleAction.Enable); }
40
+ hostRegistryOrganizationEnablePollPath(ctx) { return this.hostRegistryPath(ctx, 'Organization', `${GwCoreLifecycleAction.Enable}-response`); }
39
41
  hostRegistryOrganizationPurgePath(ctx) { return this.hostRegistryPath(ctx, 'Organization', GwCoreLifecycleAction.Purge); }
40
42
  hostRegistryOrganizationPurgePollPath(ctx) { return this.hostRegistryPath(ctx, 'Organization', `${GwCoreLifecycleAction.Purge}-response`); }
43
+ hostRegistryOrganizationStatusPath(ctx) { return this.hostRegistryPath(ctx, 'Organization', GwCoreLifecycleAction.Status); }
44
+ hostRegistryOrganizationStatusPollPath(ctx) { return this.hostRegistryPath(ctx, 'Organization', `${GwCoreLifecycleAction.Status}-response`); }
45
+ hostRegistryOrganizationDisableDescendantsPath(ctx) { return this.hostRegistryPath(ctx, 'Organization', GwCoreLifecycleAction.DisableDescendants); }
46
+ hostRegistryOrganizationDisableDescendantsPollPath(ctx) { return this.hostRegistryPath(ctx, 'Organization', `${GwCoreLifecycleAction.DisableDescendants}-response`); }
47
+ hostRegistryOrganizationPurgeDescendantsPath(ctx) { return this.hostRegistryPath(ctx, 'Organization', GwCoreLifecycleAction.PurgeDescendants); }
48
+ hostRegistryOrganizationPurgeDescendantsPollPath(ctx) { return this.hostRegistryPath(ctx, 'Organization', `${GwCoreLifecycleAction.PurgeDescendants}-response`); }
41
49
  hostRegistryOrderBatchPath(ctx) { return this.hostRegistryPath(ctx, 'Order', '_batch'); }
42
50
  hostRegistryOrderPollPath(ctx) { return this.hostRegistryPath(ctx, 'Order', '_batch-response'); }
43
51
  employeeBatchPath(ctx) { return this.v1Path(ctx, 'entity', 'org.schema', 'Employee', GwCoreLifecycleAction.Batch); }
@@ -89,6 +97,8 @@ export class RuntimeClientPaths {
89
97
  individualRelatedPersonPurgePollPath(ctx) { return this.v1Path(ctx, 'individual', 'org.hl7.fhir.r4', 'RelatedPerson', '_purge-response'); }
90
98
  individualConsentR4BatchPath(ctx) { return this.v1Path(ctx, 'individual', 'org.hl7.fhir.r4', 'Consent', '_batch'); }
91
99
  individualConsentR4PollPath(ctx) { return this.v1Path(ctx, 'individual', 'org.hl7.fhir.r4', 'Consent', '_batch-response'); }
100
+ individualResearchSubjectPurgePath(ctx) { return this.v1Path(ctx, 'individual', 'org.hl7.fhir.r4', 'ResearchSubject', '_purge'); }
101
+ individualResearchSubjectPurgePollPath(ctx) { return this.v1Path(ctx, 'individual', 'org.hl7.fhir.r4', 'ResearchSubject', '_purge-response'); }
92
102
  individualCommunicationBatchPath(ctx, format) { return this.v1Path(ctx, 'individual', format, 'Communication', '_batch'); }
93
103
  individualCommunicationPollPath(ctx, format) { return this.v1Path(ctx, 'individual', format, 'Communication', '_batch-response'); }
94
104
  individualCommunicationSearchPath(ctx) { return this.v1Path(ctx, 'individual', 'org.hl7.fhir.r4', 'Communication', '_search'); }