gdc-sdk-node-ts 2.4.42 → 2.4.44
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.
- package/README.md +3 -3
- package/dist/digital-twin.d.ts +18 -5
- package/dist/digital-twin.js +8 -4
- package/dist/gateway-active-consent-provider.js +1 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/node-runtime-client.d.ts +3 -1
- package/dist/node-runtime-client.js +9 -1
- package/dist/orchestration/client-port.d.ts +2 -1
- package/dist/orchestration/digital-twin-sdk.d.ts +8 -2
- package/dist/orchestration/digital-twin-sdk.js +17 -3
- package/dist/orchestration/individual-controller-sdk.d.ts +5 -1
- package/dist/orchestration/individual-controller-sdk.js +5 -1
- package/dist/organization-registration-authorization.d.ts +16 -0
- package/dist/organization-registration-authorization.js +34 -0
- package/dist/profile-recovery-envelope.d.ts +37 -0
- package/dist/profile-recovery-envelope.js +113 -0
- package/dist/resource-operations.d.ts +24 -0
- package/dist/resource-operations.js +44 -0
- package/dist/server-profile-session.d.ts +15 -0
- package/dist/server-profile-session.js +32 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -650,13 +650,13 @@ modules below.
|
|
|
650
650
|
- types: `SmartTokenRequestInput`, `SmartTokenExchangeResult`
|
|
651
651
|
- function: `requestSmartTokenWithDeps(...)`
|
|
652
652
|
- [`src/resource-operations.ts`](src/resource-operations.ts)
|
|
653
|
-
- types: `OrganizationEmployeeCreationInput`, `IpsOrFhirImportInput`, `RelatedPersonUpsertInput`, `CommunicationIngestionInput`, `ClinicalDateRange`, `ClinicalBundleSearchInput`, `ConsentActorTargetInput`, `GrantProfessionalAccessInput`, `GrantProfessionalAccessResult`, `DigitalTwinSecondaryUseConsentInput`, `DigitalTwinSubjectLinkPurgeInput`
|
|
654
|
-
- functions: `createOrganizationEmployeeWithDeps(...)`, `importIpsOrFhirAndUpdateIndexWithDeps(...)`, `upsertRelatedPersonAndPollWithDeps(...)`, `ingestCommunicationAndUpdateIndexWithDeps(...)`, `searchClinicalBundleWithDeps(...)`, `searchLatestIpsWithDeps(...)`, `grantProfessionalAccessWithDeps(...)`, `setDigitalTwinSecondaryUseConsentWithDeps(...)`, `purgeDigitalTwinSubjectLinkWithDeps(...)`
|
|
653
|
+
- types: `OrganizationEmployeeCreationInput`, `IpsOrFhirImportInput`, `RelatedPersonUpsertInput`, `CommunicationIngestionInput`, `ClinicalDateRange`, `ClinicalBundleSearchInput`, `ConsentActorTargetInput`, `GrantProfessionalAccessInput`, `GrantProfessionalAccessResult`, `DigitalTwinSecondaryUseConsentInput`, `DigitalTwinSubjectLinkPurgeInput`, `SubjectConsentSearchInput`
|
|
654
|
+
- functions: `createOrganizationEmployeeWithDeps(...)`, `importIpsOrFhirAndUpdateIndexWithDeps(...)`, `upsertRelatedPersonAndPollWithDeps(...)`, `ingestCommunicationAndUpdateIndexWithDeps(...)`, `searchClinicalBundleWithDeps(...)`, `searchSubjectConsentsWithDeps(...)`, `searchLatestIpsWithDeps(...)`, `grantProfessionalAccessWithDeps(...)`, `setDigitalTwinSecondaryUseConsentWithDeps(...)`, `purgeDigitalTwinSubjectLinkWithDeps(...)`
|
|
655
655
|
- [`src/digital-twin.ts`](src/digital-twin.ts)
|
|
656
656
|
- types: `DigitalTwinSearchInput`, `DigitalTwinSelectionInput`, `DigitalTwinResearchTag`, `DigitalTwinMaterializationInput`
|
|
657
657
|
- functions: `searchDigitalTwinsWithDeps(...)`, `saveDigitalTwinSelectionWithDeps(...)`, `materializeDigitalTwinWithDeps(...)`
|
|
658
658
|
- [`src/orchestration/digital-twin-sdk.ts`](src/orchestration/digital-twin-sdk.ts)
|
|
659
|
-
- class: `DigitalTwinSdk` (`requestSmartToken`, `search`, `saveSelection`, `materialize`)
|
|
659
|
+
- class: `DigitalTwinSdk` (`requestSmartToken`, `search`, `saveSelection`, `searchSelections`, `materialize`); both searches expose `ResearchSubject` through FHIR Parameters, with its canonical internal Composition available as `composition`
|
|
660
660
|
- [`src/session.ts`](src/session.ts)
|
|
661
661
|
- types: `NodeCapability`, `NodeActorSessionContext`, `ActorSessionContext`
|
|
662
662
|
- classes: `ActorSession`, `NodeActorSession`
|
package/dist/digital-twin.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export type DigitalTwinFhirFormat = 'org.hl7.fhir.r4' | 'org.hl7.fhir.api';
|
|
|
7
7
|
export declare function isDigitalTwinSubjectId(value: unknown): value is string;
|
|
8
8
|
/** Rejects operational DIDs and malformed or caller-invented subject shapes. */
|
|
9
9
|
export declare function assertDigitalTwinSubjectId(value: unknown): asserts value is string;
|
|
10
|
-
/** Search parameters
|
|
10
|
+
/** Search parameters evaluated against a ResearchSubject's canonical Composition index. */
|
|
11
11
|
export declare const DigitalTwinSearchParameter: Readonly<{
|
|
12
12
|
Section: "section";
|
|
13
13
|
DateFrom: "date-from";
|
|
@@ -20,7 +20,8 @@ export type DigitalTwinSearchInput = {
|
|
|
20
20
|
accessToken?: string;
|
|
21
21
|
thid?: string;
|
|
22
22
|
format?: DigitalTwinFhirFormat;
|
|
23
|
-
|
|
23
|
+
/** Public twin search resource; exposed for low-level request inspection only. */
|
|
24
|
+
resourceType?: 'ResearchSubject';
|
|
24
25
|
/** One or more IPS section tokens. Basic search uses OR across sections. */
|
|
25
26
|
sections?: readonly string[];
|
|
26
27
|
/** Inclusive clinical-event lower bound as ISO date or dateTime. */
|
|
@@ -33,11 +34,19 @@ export type DigitalTwinSearchInput = {
|
|
|
33
34
|
filters?: Readonly<Record<string, string | readonly string[] | undefined>>;
|
|
34
35
|
pollOptions?: PollOptions;
|
|
35
36
|
};
|
|
36
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* One public ResearchSubject digital twin returned by search.
|
|
39
|
+
*
|
|
40
|
+
* `composition` is the canonical Composition GW uses as the aggregate's
|
|
41
|
+
* internal index document. It joins the ResearchSubject to the projected
|
|
42
|
+
* clinical resources later returned by `ResearchSubject/$summary`.
|
|
43
|
+
*/
|
|
37
44
|
export type DigitalTwinSearchMatch = Record<string, unknown> & {
|
|
45
|
+
resourceType?: 'ResearchSubject';
|
|
38
46
|
id?: string;
|
|
39
47
|
[CompositionClaim.Subject]: string;
|
|
40
48
|
[CompositionClaim.Section]?: string;
|
|
49
|
+
composition?: Record<string, unknown>;
|
|
41
50
|
meta?: {
|
|
42
51
|
tag?: DigitalTwinResearchTag[];
|
|
43
52
|
};
|
|
@@ -120,11 +129,15 @@ export type DigitalTwinRuntimeDeps = {
|
|
|
120
129
|
body: Record<string, unknown>;
|
|
121
130
|
}, pollOptions?: PollOptions) => Promise<SubmitAndPollResult>;
|
|
122
131
|
};
|
|
123
|
-
/** Opens the GW async response and exposes the matched
|
|
132
|
+
/** Opens the GW async response and exposes the matched ResearchSubjects directly. */
|
|
124
133
|
export declare function readDigitalTwinSearchResult(operation: SubmitAndPollResult): DigitalTwinSearchResult;
|
|
125
134
|
/** Persists one tagged researcher working copy through `digitaltwin/Composition/_batch`. */
|
|
126
135
|
export declare function saveDigitalTwinSelectionWithDeps(ctx: RouteContext, input: DigitalTwinSelectionInput, deps: DigitalTwinRuntimeDeps): Promise<SubmitAndPollResult>;
|
|
127
|
-
/**
|
|
136
|
+
/**
|
|
137
|
+
* Searches ResearchSubject digital twins through the public asynchronous route.
|
|
138
|
+
* GW filters their canonical Composition index documents internally; callers
|
|
139
|
+
* never switch to a separate public Composition search surface.
|
|
140
|
+
*/
|
|
128
141
|
export declare function searchDigitalTwinsWithDeps(ctx: RouteContext, input: DigitalTwinSearchInput, deps: DigitalTwinRuntimeDeps): Promise<SubmitAndPollResult>;
|
|
129
142
|
/** Materializes one selected pseudonymous twin as a research summary Bundle. */
|
|
130
143
|
export declare function materializeDigitalTwinWithDeps(ctx: RouteContext, input: DigitalTwinMaterializationInput, deps: DigitalTwinRuntimeDeps): Promise<SubmitAndPollResult>;
|
package/dist/digital-twin.js
CHANGED
|
@@ -13,7 +13,7 @@ export function assertDigitalTwinSubjectId(value) {
|
|
|
13
13
|
throw new Error('Digital twin subject must be a valid urn:uuid identifier.');
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
-
/** Search parameters
|
|
16
|
+
/** Search parameters evaluated against a ResearchSubject's canonical Composition index. */
|
|
17
17
|
export const DigitalTwinSearchParameter = Object.freeze({
|
|
18
18
|
Section: 'section',
|
|
19
19
|
DateFrom: 'date-from',
|
|
@@ -59,7 +59,7 @@ function normalizeResearchTags(tags) {
|
|
|
59
59
|
}
|
|
60
60
|
return normalized;
|
|
61
61
|
}
|
|
62
|
-
/** Opens the GW async response and exposes the matched
|
|
62
|
+
/** Opens the GW async response and exposes the matched ResearchSubjects directly. */
|
|
63
63
|
export function readDigitalTwinSearchResult(operation) {
|
|
64
64
|
const body = operation?.poll?.body;
|
|
65
65
|
const responseEntries = Array.isArray(body?.data)
|
|
@@ -129,10 +129,14 @@ export async function saveDigitalTwinSelectionWithDeps(ctx, input, deps) {
|
|
|
129
129
|
: { resourceType: 'Bundle', type: 'batch', entry: [entry] },
|
|
130
130
|
}, input.pollOptions);
|
|
131
131
|
}
|
|
132
|
-
/**
|
|
132
|
+
/**
|
|
133
|
+
* Searches ResearchSubject digital twins through the public asynchronous route.
|
|
134
|
+
* GW filters their canonical Composition index documents internally; callers
|
|
135
|
+
* never switch to a separate public Composition search surface.
|
|
136
|
+
*/
|
|
133
137
|
export async function searchDigitalTwinsWithDeps(ctx, input, deps) {
|
|
134
138
|
const format = input.format || 'org.hl7.fhir.r4';
|
|
135
|
-
const resourceType = String(input.resourceType || '
|
|
139
|
+
const resourceType = String(input.resourceType || 'ResearchSubject').trim();
|
|
136
140
|
if (!resourceType)
|
|
137
141
|
throw new Error('Digital twin resourceType is required.');
|
|
138
142
|
const usesBasicSearch = Boolean(input.sections || input.dateFrom || input.dateTo || input.text);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
-
import { ResourceTypesFhirR4 } from 'gdc-common-utils-ts/constants/fhir-resource-types';
|
|
3
2
|
import { ClaimConsent } from 'gdc-common-utils-ts/models/consent-rule';
|
|
4
3
|
import { isConsentRuleActive } from 'gdc-common-utils-ts/utils/consent';
|
|
5
4
|
import { requireClientMethod } from './orchestration/client-port.js';
|
|
@@ -20,10 +19,7 @@ export class GatewayActiveConsentProvider {
|
|
|
20
19
|
if (!normalizedSubject.startsWith('did:')) {
|
|
21
20
|
throw new Error('Active Consent lookup requires a subject DID.');
|
|
22
21
|
}
|
|
23
|
-
const result = await requireClientMethod(this.client, '
|
|
24
|
-
subject: normalizedSubject,
|
|
25
|
-
includedTypes: [ResourceTypesFhirR4.Consent],
|
|
26
|
-
});
|
|
22
|
+
const result = await requireClientMethod(this.client, 'searchSubjectConsents')(this.routeContext, { subject: normalizedSubject });
|
|
27
23
|
return extractConsentRules(result.poll.body)
|
|
28
24
|
.filter((rule) => isConsentRuleActive(rule, { subject: normalizedSubject }));
|
|
29
25
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export * from './identity-bootstrap.js';
|
|
|
4
4
|
export * from './async-polling.js';
|
|
5
5
|
export * from './backend-profile-runtime.js';
|
|
6
6
|
export * from './server-profile-session.js';
|
|
7
|
+
export * from './profile-recovery-envelope.js';
|
|
7
8
|
export * from './server-profile-protection.js';
|
|
8
9
|
export * from './backend-profile-workspace.js';
|
|
9
10
|
export * from './individual-controller-backend-runtime.js';
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ export * from './identity-bootstrap.js';
|
|
|
5
5
|
export * from './async-polling.js';
|
|
6
6
|
export * from './backend-profile-runtime.js';
|
|
7
7
|
export * from './server-profile-session.js';
|
|
8
|
+
export * from './profile-recovery-envelope.js';
|
|
8
9
|
export * from './server-profile-protection.js';
|
|
9
10
|
export * from './backend-profile-workspace.js';
|
|
10
11
|
export * from './individual-controller-backend-runtime.js';
|
|
@@ -12,7 +12,7 @@ import { type SmartTokenRequestInput } from './smart-token.js';
|
|
|
12
12
|
import { type EmployeeDeviceActivationRequestInput, type EmployeeDeviceActivationResult, type EmployeeDeviceOtpRecoveryInput, type EmployeeDeviceOtpRecoveryResult, type EmployeeDeviceRevocationInput } from './device-activation.js';
|
|
13
13
|
import { type OrganizationLicenseOrderConfirmInput } from './organization-license-order.js';
|
|
14
14
|
import { type IndividualOrganizationBootstrapInput, type IndividualOrganizationStartResult } from './individual-start.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
|
+
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 SubjectConsentSearchInput, 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';
|
|
16
16
|
import type { LegalOrganizationOrderInput } from './host-onboarding.js';
|
|
17
17
|
import type { SmartTokenExchangeResult } from './smart-token.js';
|
|
18
18
|
import { type OrganizationEmployeeProvisioningInput, type OrganizationEmployeeProvisioningResult } from './organization-employee-lifecycle.js';
|
|
@@ -447,6 +447,8 @@ export declare class HttpRuntimeClient implements NodeRuntimeClient {
|
|
|
447
447
|
setDigitalTwinSecondaryUseConsent(ctx: RouteContext, input: DigitalTwinSecondaryUseConsentInput): Promise<DigitalTwinSecondaryUseConsentResult>;
|
|
448
448
|
/** Offboards the subject from this index provider without deleting the anonymous twin. */
|
|
449
449
|
purgeDigitalTwinSubjectLink(ctx: RouteContext, input: DigitalTwinSubjectLinkPurgeInput): Promise<DigitalTwinSubjectLinkPurgeResult>;
|
|
450
|
+
/** Reads the subject-owned Consent records through Communication -> Subject/_search. */
|
|
451
|
+
searchSubjectConsents(ctx: RouteContext, input: SubjectConsentSearchInput): Promise<SubmitAndPollResult>;
|
|
450
452
|
/**
|
|
451
453
|
* Closes an existing professional consent by setting its period end and
|
|
452
454
|
* resubmitting the updated consent resource.
|
|
@@ -10,7 +10,7 @@ import { activateEmployeeDeviceWithActivationCodeWithDeps, activateEmployeeDevic
|
|
|
10
10
|
import { extractOfferIdFromResponseBody, extractOfferPreviewFromResponseBody, } from './order-offer-summary.js';
|
|
11
11
|
import { confirmOrganizationLicenseOrderWithDeps } from './organization-license-order.js';
|
|
12
12
|
import { startIndividualOrganizationWithDeps } from './individual-start.js';
|
|
13
|
-
import { createOrganizationEmployeeWithDeps, issueOrganizationEmployeeLicenseWithDeps, disableIndividualMemberWithDeps, disableIndividualOrganizationWithDeps, disableOrganizationEmployeeWithDeps, listIndividualLicenseOffersWithDeps, listIndividualLicenseOrdersWithDeps, listIndividualLicensesWithDeps, addFreeIndividualMemberLicensesWithDeps, issueIndividualMemberLicenseWithDeps, transitionIndividualMemberLicenseWithDeps, listOrganizationLicenseOffersWithDeps, listOrganizationLicenseOrdersWithDeps, listOrganizationLicensesWithDeps, grantProfessionalAccessWithDeps, setDigitalTwinSecondaryUseConsentWithDeps, purgeDigitalTwinSubjectLinkWithDeps, buildClinicalSectionUpdateIngestion, buildClinicalSummaryUpdateIngestion, ingestCommunicationAndUpdateIndexWithDeps, requestProfessionalAccessWithDeps, buildVitalSignBatchCommunicationFromSearchResponse, registerBlockchainArtifactAndUpdateIndexWithDeps, requestClinicalSummaryWithDeps, revokeProfessionalAccessWithDeps, searchCommunicationParticipantsWithDeps, purgeIndividualMemberWithDeps, purgeIndividualOrganizationWithDeps, purgeOrganizationEmployeeWithDeps, prepareEmployeeLifecycleMessageForTransport, searchIndividualLicensesWithDeps, searchIndividualLicenseOffersWithDeps, searchIndividualLicenseOrdersWithDeps, searchOrganizationLicensesWithDeps, searchOrganizationLicenseOffersWithDeps, searchOrganizationLicenseOrdersWithDeps, searchOrganizationEmployeesWithDeps, searchClinicalBundleWithDeps, searchLatestIpsWithDeps, upsertRelatedPersonAndPollWithDeps, normalizeCommunicationPathFormatSegment, addFreeOrganizationEmployeeLicensesWithDeps, requestOrganizationEmployeeLicenseOfferWithDeps, } from './resource-operations.js';
|
|
13
|
+
import { createOrganizationEmployeeWithDeps, issueOrganizationEmployeeLicenseWithDeps, disableIndividualMemberWithDeps, disableIndividualOrganizationWithDeps, disableOrganizationEmployeeWithDeps, listIndividualLicenseOffersWithDeps, listIndividualLicenseOrdersWithDeps, listIndividualLicensesWithDeps, addFreeIndividualMemberLicensesWithDeps, issueIndividualMemberLicenseWithDeps, transitionIndividualMemberLicenseWithDeps, listOrganizationLicenseOffersWithDeps, listOrganizationLicenseOrdersWithDeps, listOrganizationLicensesWithDeps, grantProfessionalAccessWithDeps, setDigitalTwinSecondaryUseConsentWithDeps, purgeDigitalTwinSubjectLinkWithDeps, searchSubjectConsentsWithDeps, buildClinicalSectionUpdateIngestion, buildClinicalSummaryUpdateIngestion, ingestCommunicationAndUpdateIndexWithDeps, requestProfessionalAccessWithDeps, buildVitalSignBatchCommunicationFromSearchResponse, registerBlockchainArtifactAndUpdateIndexWithDeps, requestClinicalSummaryWithDeps, revokeProfessionalAccessWithDeps, searchCommunicationParticipantsWithDeps, purgeIndividualMemberWithDeps, purgeIndividualOrganizationWithDeps, purgeOrganizationEmployeeWithDeps, prepareEmployeeLifecycleMessageForTransport, searchIndividualLicensesWithDeps, searchIndividualLicenseOffersWithDeps, searchIndividualLicenseOrdersWithDeps, searchOrganizationLicensesWithDeps, searchOrganizationLicenseOffersWithDeps, searchOrganizationLicenseOrdersWithDeps, searchOrganizationEmployeesWithDeps, searchClinicalBundleWithDeps, searchLatestIpsWithDeps, upsertRelatedPersonAndPollWithDeps, normalizeCommunicationPathFormatSegment, addFreeOrganizationEmployeeLicensesWithDeps, requestOrganizationEmployeeLicenseOfferWithDeps, } from './resource-operations.js';
|
|
14
14
|
import { listOrganizationEmployeeLifecycleWithDeps, provisionOrganizationEmployeeWithDeps, } from './organization-employee-lifecycle.js';
|
|
15
15
|
import { buildRuntimeHeaders, fetchWithTimeout, parseResponseBody, postRenderedWithRuntimeConfig, } from './runtime-transport.js';
|
|
16
16
|
import { activateOrganizationInGatewayFromIcaProofWithDeps, submitLegalOrganizationCredentialReissuanceWithDeps, submitLegalOrganizationVerificationTransactionWithDeps, submitOrganizationDidBindingWithDeps, } from './runtime-host-submission.js';
|
|
@@ -860,6 +860,14 @@ export class HttpRuntimeClient {
|
|
|
860
860
|
submitAndPoll: this.submitAndPoll.bind(this),
|
|
861
861
|
});
|
|
862
862
|
}
|
|
863
|
+
/** Reads the subject-owned Consent records through Communication -> Subject/_search. */
|
|
864
|
+
async searchSubjectConsents(ctx, input) {
|
|
865
|
+
return searchSubjectConsentsWithDeps(ctx, input, {
|
|
866
|
+
individualCommunicationBatchPath: this.paths.individualCommunicationBatchPath.bind(this.paths),
|
|
867
|
+
individualCommunicationPollPath: this.paths.individualCommunicationPollPath.bind(this.paths),
|
|
868
|
+
submitAndPoll: this.submitAndPoll.bind(this),
|
|
869
|
+
});
|
|
870
|
+
}
|
|
863
871
|
/**
|
|
864
872
|
* Closes an existing professional consent by setting its period end and
|
|
865
873
|
* resubmitting the updated consent resource.
|
|
@@ -19,7 +19,7 @@ import type { OrganizationLicenseOrderConfirmInput } from '../organization-licen
|
|
|
19
19
|
import type { SmartTokenExchangeResult, SmartTokenRequestInput } from '../smart-token.js';
|
|
20
20
|
import type { DigitalTwinMaterializationInput, DigitalTwinSearchInput, DigitalTwinSelectionInput } from '../digital-twin.js';
|
|
21
21
|
import type { AuthorizedIndividualSubject, AuthorizedIndividualSubjectDirectoryInput } from '../authorized-subject-directory.js';
|
|
22
|
-
import type { CommunicationIngestionInput, BlockchainArtifactRegistrationInput, CommunicationParticipantRuntimeSearchInput, ClinicalBundleSearchInput, ClinicalSectionUpdateInput, ClinicalSummaryReadResult, ClinicalSummaryRequestInput, ClinicalSummaryUpdateInput, VitalSignBatchCommunicationFromSearchResponseInput, DigitalTwinGenerationInput, DigitalTwinSecondaryUseConsentInput, DigitalTwinSecondaryUseConsentResult, DigitalTwinSubjectLinkPurgeInput, DigitalTwinSubjectLinkPurgeResult, GrantProfessionalAccessInput, GrantProfessionalAccessResult, IndividualMemberLifecycleInput, IndividualMemberLicenseAddInput, IndividualMemberLicenseInvitationInput, IndividualMemberLicenseTransitionInput, IpsOrFhirImportInput, OrganizationEmployeeCreationInput, OrganizationEmployeeLicenseAddInput, OrganizationEmployeeLicenseOfferInput, OrganizationEmployeeLicenseInvitationInput, OrganizationEmployeeLifecycleInput, OrganizationEmployeeSearchInput, ProfessionalAccessRequestInput, ProfessionalAccessRequestResult, RevokeProfessionalAccessInput, RevokeProfessionalAccessResult, RelatedPersonUpsertInput } from '../resource-operations.js';
|
|
22
|
+
import type { CommunicationIngestionInput, BlockchainArtifactRegistrationInput, CommunicationParticipantRuntimeSearchInput, ClinicalBundleSearchInput, ClinicalSectionUpdateInput, ClinicalSummaryReadResult, ClinicalSummaryRequestInput, ClinicalSummaryUpdateInput, VitalSignBatchCommunicationFromSearchResponseInput, DigitalTwinGenerationInput, DigitalTwinSecondaryUseConsentInput, DigitalTwinSecondaryUseConsentResult, DigitalTwinSubjectLinkPurgeInput, DigitalTwinSubjectLinkPurgeResult, GrantProfessionalAccessInput, GrantProfessionalAccessResult, IndividualMemberLifecycleInput, IndividualMemberLicenseAddInput, IndividualMemberLicenseInvitationInput, IndividualMemberLicenseTransitionInput, IpsOrFhirImportInput, OrganizationEmployeeCreationInput, OrganizationEmployeeLicenseAddInput, OrganizationEmployeeLicenseOfferInput, OrganizationEmployeeLicenseInvitationInput, OrganizationEmployeeLifecycleInput, OrganizationEmployeeSearchInput, ProfessionalAccessRequestInput, ProfessionalAccessRequestResult, RevokeProfessionalAccessInput, RevokeProfessionalAccessResult, RelatedPersonUpsertInput, SubjectConsentSearchInput } from '../resource-operations.js';
|
|
23
23
|
/**
|
|
24
24
|
* Shared node-runtime activation input.
|
|
25
25
|
*
|
|
@@ -140,6 +140,7 @@ export type RuntimeClient = {
|
|
|
140
140
|
generateDigitalTwinFromSubjectData?: (ctx: RouteContext, input: DigitalTwinGenerationInput) => Promise<SubmitAndPollResult>;
|
|
141
141
|
setDigitalTwinSecondaryUseConsent?: (ctx: RouteContext, input: DigitalTwinSecondaryUseConsentInput) => Promise<DigitalTwinSecondaryUseConsentResult>;
|
|
142
142
|
purgeDigitalTwinSubjectLink?: (ctx: RouteContext, input: DigitalTwinSubjectLinkPurgeInput) => Promise<DigitalTwinSubjectLinkPurgeResult>;
|
|
143
|
+
searchSubjectConsents?: (ctx: RouteContext, input: SubjectConsentSearchInput) => Promise<SubmitAndPollResult>;
|
|
143
144
|
searchClinicalBundle?: (ctx: RouteContext, input: ClinicalBundleSearchInput) => Promise<SubmitAndPollResult>;
|
|
144
145
|
searchDigitalTwins?: (ctx: RouteContext, input: DigitalTwinSearchInput) => Promise<SubmitAndPollResult>;
|
|
145
146
|
saveDigitalTwinSelection?: (ctx: RouteContext, input: DigitalTwinSelectionInput) => Promise<SubmitAndPollResult>;
|
|
@@ -11,7 +11,9 @@ export declare class DigitalTwinSdk {
|
|
|
11
11
|
/** Requests the SMART token used by subsequent digital-twin operations. */
|
|
12
12
|
requestSmartToken(input: SmartTokenRequestInput): Promise<SmartTokenExchangeResult>;
|
|
13
13
|
/**
|
|
14
|
-
* Searches
|
|
14
|
+
* Searches the public `ResearchSubject/_search` aggregate with FHIR
|
|
15
|
+
* Parameters. Each match includes the canonical Composition GW uses
|
|
16
|
+
* internally to index that ResearchSubject and connect its resources.
|
|
15
17
|
*
|
|
16
18
|
* MVP basic search supplies `sections`, `dateFrom`, optional `dateTo`, and
|
|
17
19
|
* `text`. GW resolves an omitted `dateTo` to its current time, applies OR
|
|
@@ -24,7 +26,11 @@ export declare class DigitalTwinSdk {
|
|
|
24
26
|
* The canonical clinical twin is never modified.
|
|
25
27
|
*/
|
|
26
28
|
saveSelection(ctx: RouteContext, input: DigitalTwinSelectionInput): Promise<SubmitAndPollResult>;
|
|
27
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Reopens this employee's saved ResearchSubjects through the same public
|
|
31
|
+
* route. The tag is combined with an author filter bound to the actor DID;
|
|
32
|
+
* GW verifies that author against the authenticated SMART `sub`.
|
|
33
|
+
*/
|
|
28
34
|
searchSelections(ctx: RouteContext, input: Omit<DigitalTwinSearchInput, 'filters'> & {
|
|
29
35
|
section: string;
|
|
30
36
|
tag: Readonly<{
|
|
@@ -16,11 +16,18 @@ export class DigitalTwinSdk {
|
|
|
16
16
|
throw new Error('DigitalTwinSdk actorDid must match the actor session.');
|
|
17
17
|
}
|
|
18
18
|
const actorDid = this.researcherDid || requestedActorDid;
|
|
19
|
+
const requestedScopes = input.scopes?.length
|
|
20
|
+
? input.scopes
|
|
21
|
+
: [ServiceCapability.DigitalTwinReader];
|
|
19
22
|
const result = await requireClientMethod(this.client, 'requestSmartToken')({
|
|
20
23
|
...input,
|
|
21
24
|
actorDid,
|
|
22
25
|
purpose: input.purpose || HealthcareConsentPurposes.Research,
|
|
23
|
-
|
|
26
|
+
// GW root scopes are explicit queries. Complete the common bare SDK
|
|
27
|
+
// capability so callers do not have to know the SMART wire syntax.
|
|
28
|
+
scopes: requestedScopes.map((scope) => scope === ServiceCapability.DigitalTwinReader
|
|
29
|
+
? `${scope}?subject=*`
|
|
30
|
+
: scope),
|
|
24
31
|
});
|
|
25
32
|
if (result.accessToken)
|
|
26
33
|
this.smartAccessToken = result.accessToken;
|
|
@@ -29,7 +36,9 @@ export class DigitalTwinSdk {
|
|
|
29
36
|
return result;
|
|
30
37
|
}
|
|
31
38
|
/**
|
|
32
|
-
* Searches
|
|
39
|
+
* Searches the public `ResearchSubject/_search` aggregate with FHIR
|
|
40
|
+
* Parameters. Each match includes the canonical Composition GW uses
|
|
41
|
+
* internally to index that ResearchSubject and connect its resources.
|
|
33
42
|
*
|
|
34
43
|
* MVP basic search supplies `sections`, `dateFrom`, optional `dateTo`, and
|
|
35
44
|
* `text`. GW resolves an omitted `dateTo` to its current time, applies OR
|
|
@@ -59,6 +68,7 @@ export class DigitalTwinSdk {
|
|
|
59
68
|
}
|
|
60
69
|
const operation = await requireClientMethod(this.client, 'searchDigitalTwins')(ctx, {
|
|
61
70
|
...input,
|
|
71
|
+
resourceType: 'ResearchSubject',
|
|
62
72
|
filters,
|
|
63
73
|
accessToken: input.accessToken || this.smartAccessToken,
|
|
64
74
|
});
|
|
@@ -80,7 +90,11 @@ export class DigitalTwinSdk {
|
|
|
80
90
|
accessToken: input.accessToken || this.smartAccessToken,
|
|
81
91
|
});
|
|
82
92
|
}
|
|
83
|
-
/**
|
|
93
|
+
/**
|
|
94
|
+
* Reopens this employee's saved ResearchSubjects through the same public
|
|
95
|
+
* route. The tag is combined with an author filter bound to the actor DID;
|
|
96
|
+
* GW verifies that author against the authenticated SMART `sub`.
|
|
97
|
+
*/
|
|
84
98
|
searchSelections(ctx, input) {
|
|
85
99
|
const section = String(input.section || '').trim();
|
|
86
100
|
const system = String(input.tag?.system || '').trim();
|
|
@@ -138,7 +138,11 @@ export declare class IndividualControllerSdk {
|
|
|
138
138
|
* data already shared for research.
|
|
139
139
|
*/
|
|
140
140
|
purgeDigitalTwinSubjectLink(ctx: RouteContext, input: DigitalTwinSubjectLinkPurgeInput): Promise<DigitalTwinSubjectLinkPurgeResult>;
|
|
141
|
-
/**
|
|
141
|
+
/**
|
|
142
|
+
* Returns the current decision for one portal, software or research study.
|
|
143
|
+
* Consent is read through Communication -> individual `Subject/_search`;
|
|
144
|
+
* this does not query a clinical Bundle or the twin ResearchSubject index.
|
|
145
|
+
*/
|
|
142
146
|
getDigitalTwinSecondaryUseConsentStatus(ctx: RouteContext, input: Readonly<{
|
|
143
147
|
subjectDid: string;
|
|
144
148
|
indexProviderOrganizationDid: string;
|
|
@@ -208,7 +208,11 @@ export class IndividualControllerSdk {
|
|
|
208
208
|
assertFacadeCapability(this.capabilities, ActorCapabilities.IndividualGenerateDigitalTwin, ActorKinds.IndividualController, 'purgeDigitalTwinSubjectLink');
|
|
209
209
|
return requireClientMethod(this.client, 'purgeDigitalTwinSubjectLink')(ctx, input);
|
|
210
210
|
}
|
|
211
|
-
/**
|
|
211
|
+
/**
|
|
212
|
+
* Returns the current decision for one portal, software or research study.
|
|
213
|
+
* Consent is read through Communication -> individual `Subject/_search`;
|
|
214
|
+
* this does not query a clinical Bundle or the twin ResearchSubject index.
|
|
215
|
+
*/
|
|
212
216
|
async getDigitalTwinSecondaryUseConsentStatus(ctx, input) {
|
|
213
217
|
assertFacadeCapability(this.capabilities, ActorCapabilities.IndividualGenerateDigitalTwin, ActorKinds.IndividualController, 'getDigitalTwinSecondaryUseConsentStatus');
|
|
214
218
|
const consents = await new GatewayActiveConsentProvider(this.client, ctx)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { VerifiableCredentialV2 } from 'gdc-common-utils-ts/models/verifiable-credential';
|
|
2
|
+
import type { IWallet, WalletExecutionContext, WalletKeySelection } from 'gdc-sdk-core-ts';
|
|
3
|
+
/** Input required to add one human-authorizer proof to the out-of-band VC. */
|
|
4
|
+
export type SignOrganizationRegistrationAuthorizationInput = Readonly<{
|
|
5
|
+
credential: VerifiableCredentialV2;
|
|
6
|
+
wallet: IWallet;
|
|
7
|
+
context: WalletExecutionContext;
|
|
8
|
+
key: WalletKeySelection;
|
|
9
|
+
verificationMethod: string;
|
|
10
|
+
createdAt?: string;
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* Adds one detached JWS proof made by the already-unlocked professional
|
|
14
|
+
* profile. The browser never receives the private key or clear wallet seed.
|
|
15
|
+
*/
|
|
16
|
+
export declare function signOrganizationRegistrationAuthorizationCredential(input: SignOrganizationRegistrationAuthorizationInput): Promise<VerifiableCredentialV2>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { canonicalizeOrganizationRegistrationAuthorizationCredential, } from 'gdc-common-utils-ts/utils/organization-registration-authorization';
|
|
2
|
+
/**
|
|
3
|
+
* Adds one detached JWS proof made by the already-unlocked professional
|
|
4
|
+
* profile. The browser never receives the private key or clear wallet seed.
|
|
5
|
+
*/
|
|
6
|
+
export async function signOrganizationRegistrationAuthorizationCredential(input) {
|
|
7
|
+
if (!input.wallet.signDetachedJws) {
|
|
8
|
+
throw new Error('Wallet does not support detached organization authorization proofs.');
|
|
9
|
+
}
|
|
10
|
+
const verificationMethod = String(input.verificationMethod || '').trim();
|
|
11
|
+
if (!verificationMethod)
|
|
12
|
+
throw new Error('Organization authorization proof requires verificationMethod.');
|
|
13
|
+
const payload = canonicalizeOrganizationRegistrationAuthorizationCredential(input.credential);
|
|
14
|
+
const jws = await input.wallet.signDetachedJws(input.context, {
|
|
15
|
+
payload,
|
|
16
|
+
header: { typ: 'application/vc+ld+json' },
|
|
17
|
+
key: input.key,
|
|
18
|
+
});
|
|
19
|
+
const existingProofs = Array.isArray(input.credential.proof)
|
|
20
|
+
? input.credential.proof
|
|
21
|
+
: input.credential.proof
|
|
22
|
+
? [input.credential.proof]
|
|
23
|
+
: [];
|
|
24
|
+
return {
|
|
25
|
+
...input.credential,
|
|
26
|
+
proof: [...existingProofs, {
|
|
27
|
+
type: 'JsonWebSignature2020',
|
|
28
|
+
created: input.createdAt || new Date().toISOString(),
|
|
29
|
+
proofPurpose: 'contractAgreement',
|
|
30
|
+
verificationMethod,
|
|
31
|
+
jws,
|
|
32
|
+
}],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type PortableProfileRecoveryEnvelope = Readonly<{
|
|
2
|
+
version: 'gdc-portable-profile-recovery-v1';
|
|
3
|
+
profileId: string;
|
|
4
|
+
walletKeyDerivationId: string;
|
|
5
|
+
kdf: Readonly<{
|
|
6
|
+
name: 'scrypt';
|
|
7
|
+
saltBase64Url: string;
|
|
8
|
+
cost: number;
|
|
9
|
+
blockSize: number;
|
|
10
|
+
parallelization: number;
|
|
11
|
+
keyLength: 32;
|
|
12
|
+
}>;
|
|
13
|
+
cipher: 'A256GCM';
|
|
14
|
+
ivBase64Url: string;
|
|
15
|
+
ciphertextBase64Url: string;
|
|
16
|
+
tagBase64Url: string;
|
|
17
|
+
}>;
|
|
18
|
+
export type PortableProfileRecoveryProtection = Readonly<{
|
|
19
|
+
cost?: number;
|
|
20
|
+
blockSize?: number;
|
|
21
|
+
parallelization?: number;
|
|
22
|
+
}>;
|
|
23
|
+
export declare function createPortableProfileRecoveryEnvelope(input: Readonly<{
|
|
24
|
+
profileId: string;
|
|
25
|
+
walletSeed: string;
|
|
26
|
+
walletKeyDerivationId: string;
|
|
27
|
+
recoverySecret: string;
|
|
28
|
+
protection?: PortableProfileRecoveryProtection;
|
|
29
|
+
}>): PortableProfileRecoveryEnvelope;
|
|
30
|
+
export declare function openPortableProfileRecoveryEnvelope(input: Readonly<{
|
|
31
|
+
envelope: PortableProfileRecoveryEnvelope;
|
|
32
|
+
recoverySecret: string;
|
|
33
|
+
}>): Readonly<{
|
|
34
|
+
profileId: string;
|
|
35
|
+
walletSeed: string;
|
|
36
|
+
walletKeyDerivationId: string;
|
|
37
|
+
}>;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
+
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'node:crypto';
|
|
3
|
+
export function createPortableProfileRecoveryEnvelope(input) {
|
|
4
|
+
const profileId = requiredText(input.profileId, 'profileId');
|
|
5
|
+
const walletKeyDerivationId = requiredText(input.walletKeyDerivationId, 'walletKeyDerivationId');
|
|
6
|
+
requireSeed(input.walletSeed);
|
|
7
|
+
requireRecoverySecret(input.recoverySecret);
|
|
8
|
+
const kdf = {
|
|
9
|
+
name: 'scrypt',
|
|
10
|
+
saltBase64Url: randomBytes(16).toString('base64url'),
|
|
11
|
+
cost: input.protection?.cost ?? 65536,
|
|
12
|
+
blockSize: input.protection?.blockSize ?? 8,
|
|
13
|
+
parallelization: input.protection?.parallelization ?? 1,
|
|
14
|
+
keyLength: 32,
|
|
15
|
+
};
|
|
16
|
+
const key = deriveKey(input.recoverySecret, kdf);
|
|
17
|
+
const iv = randomBytes(12);
|
|
18
|
+
const aad = envelopeAad(profileId, walletKeyDerivationId);
|
|
19
|
+
try {
|
|
20
|
+
const cipher = createCipheriv('aes-256-gcm', key, iv);
|
|
21
|
+
cipher.setAAD(aad);
|
|
22
|
+
const ciphertext = Buffer.concat([
|
|
23
|
+
cipher.update(JSON.stringify({ walletSeed: input.walletSeed }), 'utf8'),
|
|
24
|
+
cipher.final(),
|
|
25
|
+
]);
|
|
26
|
+
return {
|
|
27
|
+
version: 'gdc-portable-profile-recovery-v1',
|
|
28
|
+
profileId,
|
|
29
|
+
walletKeyDerivationId,
|
|
30
|
+
kdf,
|
|
31
|
+
cipher: 'A256GCM',
|
|
32
|
+
ivBase64Url: iv.toString('base64url'),
|
|
33
|
+
ciphertextBase64Url: ciphertext.toString('base64url'),
|
|
34
|
+
tagBase64Url: cipher.getAuthTag().toString('base64url'),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
key.fill(0);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function openPortableProfileRecoveryEnvelope(input) {
|
|
42
|
+
const envelope = input.envelope;
|
|
43
|
+
try {
|
|
44
|
+
validateEnvelope(envelope);
|
|
45
|
+
requireRecoverySecret(input.recoverySecret);
|
|
46
|
+
const key = deriveKey(input.recoverySecret, envelope.kdf);
|
|
47
|
+
try {
|
|
48
|
+
const decipher = createDecipheriv('aes-256-gcm', key, Buffer.from(envelope.ivBase64Url, 'base64url'));
|
|
49
|
+
decipher.setAAD(envelopeAad(envelope.profileId, envelope.walletKeyDerivationId));
|
|
50
|
+
decipher.setAuthTag(Buffer.from(envelope.tagBase64Url, 'base64url'));
|
|
51
|
+
const cleartext = Buffer.concat([
|
|
52
|
+
decipher.update(Buffer.from(envelope.ciphertextBase64Url, 'base64url')),
|
|
53
|
+
decipher.final(),
|
|
54
|
+
]).toString('utf8');
|
|
55
|
+
const payload = JSON.parse(cleartext);
|
|
56
|
+
const walletSeed = String(payload.walletSeed || '');
|
|
57
|
+
requireSeed(walletSeed);
|
|
58
|
+
return {
|
|
59
|
+
profileId: envelope.profileId,
|
|
60
|
+
walletSeed,
|
|
61
|
+
walletKeyDerivationId: envelope.walletKeyDerivationId,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
key.fill(0);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
throw new Error('Portable profile recovery envelope could not be opened.');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function deriveKey(secret, kdf) {
|
|
73
|
+
return scryptSync(secret, Buffer.from(kdf.saltBase64Url, 'base64url'), kdf.keyLength, {
|
|
74
|
+
N: kdf.cost,
|
|
75
|
+
r: kdf.blockSize,
|
|
76
|
+
p: kdf.parallelization,
|
|
77
|
+
maxmem: Math.max(128 * 1024 * 1024, 256 * kdf.cost * kdf.blockSize),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function envelopeAad(profileId, walletKeyDerivationId) {
|
|
81
|
+
return Buffer.from(JSON.stringify({
|
|
82
|
+
version: 'gdc-portable-profile-recovery-v1',
|
|
83
|
+
profileId,
|
|
84
|
+
walletKeyDerivationId,
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
function requireRecoverySecret(value) {
|
|
88
|
+
const secret = String(value || '');
|
|
89
|
+
if (secret.length < 16 || /^\d+$/.test(secret)) {
|
|
90
|
+
throw new Error('Portable recovery requires a high-entropy recovery secret, not a short PIN.');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function requireSeed(value) {
|
|
94
|
+
if (!/^[A-Za-z0-9_-]{43}$/.test(value) || Buffer.from(value, 'base64url').byteLength !== 32) {
|
|
95
|
+
throw new Error('Portable recovery requires a 32-byte base64url wallet seed.');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function requiredText(value, name) {
|
|
99
|
+
const normalized = String(value || '').trim();
|
|
100
|
+
if (!normalized)
|
|
101
|
+
throw new Error(`Portable recovery requires ${name}.`);
|
|
102
|
+
return normalized;
|
|
103
|
+
}
|
|
104
|
+
function validateEnvelope(value) {
|
|
105
|
+
if (value?.version !== 'gdc-portable-profile-recovery-v1'
|
|
106
|
+
|| value.cipher !== 'A256GCM'
|
|
107
|
+
|| value.kdf?.name !== 'scrypt'
|
|
108
|
+
|| value.kdf.keyLength !== 32
|
|
109
|
+
|| !value.profileId
|
|
110
|
+
|| !value.walletKeyDerivationId) {
|
|
111
|
+
throw new Error('Unsupported portable profile recovery envelope.');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -532,6 +532,14 @@ export type DigitalTwinSubjectLinkPurgeInput = {
|
|
|
532
532
|
};
|
|
533
533
|
/** Async GW result for a subject/twin binding purge. */
|
|
534
534
|
export type DigitalTwinSubjectLinkPurgeResult = SubmitAndPollResult;
|
|
535
|
+
/** Input for reading the subject-owned Consent records through its individual aggregate. */
|
|
536
|
+
export type SubjectConsentSearchInput = Readonly<{
|
|
537
|
+
subject: string;
|
|
538
|
+
pollOptions?: {
|
|
539
|
+
timeoutMs?: number;
|
|
540
|
+
intervalMs?: number;
|
|
541
|
+
};
|
|
542
|
+
}>;
|
|
535
543
|
export declare function createOrganizationEmployeeWithDeps(routeCtx: RouteContext, input: OrganizationEmployeeCreationInput, options: {
|
|
536
544
|
timeoutMs?: number;
|
|
537
545
|
intervalMs?: number;
|
|
@@ -953,6 +961,22 @@ export declare function purgeDigitalTwinSubjectLinkWithDeps(routeCtx: RouteConte
|
|
|
953
961
|
intervalMs?: number;
|
|
954
962
|
}) => Promise<SubmitAndPollResult>;
|
|
955
963
|
}): Promise<DigitalTwinSubjectLinkPurgeResult>;
|
|
964
|
+
/**
|
|
965
|
+
* Reads Consent through the individual `Subject/_search` aggregate carried by
|
|
966
|
+
* an auditable Communication. `Subject` is the individual aggregate; it is
|
|
967
|
+
* intentionally separate from the public digital-twin `ResearchSubject`
|
|
968
|
+
* aggregate, whose canonical index document is a Composition.
|
|
969
|
+
*/
|
|
970
|
+
export declare function searchSubjectConsentsWithDeps(routeCtx: RouteContext, input: SubjectConsentSearchInput, deps: {
|
|
971
|
+
individualCommunicationBatchPath: (ctx: RouteContext, pathFormatSegment: string) => string;
|
|
972
|
+
individualCommunicationPollPath: (ctx: RouteContext, pathFormatSegment: string) => string;
|
|
973
|
+
submitAndPoll: (submitPath: string, pollPath: string, payload: {
|
|
974
|
+
thid?: string;
|
|
975
|
+
} & Record<string, unknown>, pollOptions?: {
|
|
976
|
+
timeoutMs?: number;
|
|
977
|
+
intervalMs?: number;
|
|
978
|
+
}) => Promise<SubmitAndPollResult>;
|
|
979
|
+
}): Promise<SubmitAndPollResult>;
|
|
956
980
|
export declare function revokeProfessionalAccessWithDeps(routeCtx: RouteContext, input: RevokeProfessionalAccessInput, deps: {
|
|
957
981
|
individualConsentR4BatchPath: (ctx: RouteContext) => string;
|
|
958
982
|
individualConsentR4PollPath: (ctx: RouteContext) => string;
|
|
@@ -865,6 +865,50 @@ export async function purgeDigitalTwinSubjectLinkWithDeps(routeCtx, input, deps)
|
|
|
865
865
|
},
|
|
866
866
|
}, input.pollOptions);
|
|
867
867
|
}
|
|
868
|
+
/**
|
|
869
|
+
* Reads Consent through the individual `Subject/_search` aggregate carried by
|
|
870
|
+
* an auditable Communication. `Subject` is the individual aggregate; it is
|
|
871
|
+
* intentionally separate from the public digital-twin `ResearchSubject`
|
|
872
|
+
* aggregate, whose canonical index document is a Composition.
|
|
873
|
+
*/
|
|
874
|
+
export async function searchSubjectConsentsWithDeps(routeCtx, input, deps) {
|
|
875
|
+
const subject = String(input.subject || '').trim();
|
|
876
|
+
if (!subject.startsWith('did:')) {
|
|
877
|
+
throw new Error('Consent search requires a subject DID.');
|
|
878
|
+
}
|
|
879
|
+
const format = 'org.hl7.fhir.r4';
|
|
880
|
+
const parameters = {
|
|
881
|
+
resourceType: 'Parameters',
|
|
882
|
+
parameter: [{ name: 'subject', valueString: subject }],
|
|
883
|
+
};
|
|
884
|
+
const thid = `subject-consent-search-${createRuntimeUuid()}`;
|
|
885
|
+
const communication = {
|
|
886
|
+
resourceType: 'Communication',
|
|
887
|
+
status: 'completed',
|
|
888
|
+
subject: { reference: subject },
|
|
889
|
+
payload: [{
|
|
890
|
+
contentReference: {
|
|
891
|
+
reference: 'individual/org.hl7.fhir.api/Subject/_search',
|
|
892
|
+
},
|
|
893
|
+
contentAttachment: {
|
|
894
|
+
contentType: 'application/fhir+json',
|
|
895
|
+
title: 'subject-consent-search-parameters.json',
|
|
896
|
+
data: Buffer.from(JSON.stringify(parameters), 'utf8').toString('base64'),
|
|
897
|
+
},
|
|
898
|
+
}],
|
|
899
|
+
};
|
|
900
|
+
return deps.submitAndPoll(deps.individualCommunicationBatchPath(routeCtx, format), deps.individualCommunicationPollPath(routeCtx, format), {
|
|
901
|
+
thid,
|
|
902
|
+
body: {
|
|
903
|
+
resourceType: 'Bundle',
|
|
904
|
+
type: 'batch',
|
|
905
|
+
entry: [{
|
|
906
|
+
request: { method: 'POST', url: 'Communication' },
|
|
907
|
+
resource: communication,
|
|
908
|
+
}],
|
|
909
|
+
},
|
|
910
|
+
}, input.pollOptions);
|
|
911
|
+
}
|
|
868
912
|
export async function revokeProfessionalAccessWithDeps(routeCtx, input, deps) {
|
|
869
913
|
const revokedClaims = ConsentClaims
|
|
870
914
|
.fromClaims(input.consentClaims)
|
|
@@ -74,6 +74,15 @@ export type ServerEmployeeProfileOtpRotationInput = Readonly<{
|
|
|
74
74
|
clientName: string;
|
|
75
75
|
}>;
|
|
76
76
|
export type ServerEmployeeProfileOtpRotationResult = ServerProfileRecord;
|
|
77
|
+
export type ServerProfilePinReplacementInput = Readonly<{
|
|
78
|
+
ownerId: string;
|
|
79
|
+
profileId: string;
|
|
80
|
+
/** Server-authorized seed obtained from a passkey, active wallet session or recovery envelope. */
|
|
81
|
+
authorizedWalletSeed: string;
|
|
82
|
+
/** Required when the durable profile still carries a separately protected legacy VP. */
|
|
83
|
+
authorizedVpToken?: string;
|
|
84
|
+
newPin: string;
|
|
85
|
+
}>;
|
|
77
86
|
export type ServerProfileEnrollmentInput = Readonly<{
|
|
78
87
|
/** Stable confidential id of the authenticated portal account that owns and lists this profile. */
|
|
79
88
|
ownerId: string;
|
|
@@ -335,6 +344,12 @@ export declare class ServerProfileSessionManager {
|
|
|
335
344
|
/** @deprecated Use `submitLegalOrganizationCredentialReissuanceWithBootstrapWallet`. */
|
|
336
345
|
submitLegalOrganizationIssueWithBootstrapWallet(input: ServerProfileOrganizationIssueInput): Promise<SubmitAndPollResult>;
|
|
337
346
|
listProfiles(ownerId: string): Promise<ServerProfileRecord[]>;
|
|
347
|
+
/**
|
|
348
|
+
* Rewraps the exact registered wallet under a new PIN after a separate
|
|
349
|
+
* server-authorized recovery factor has released its secrets. No GW call or
|
|
350
|
+
* DCR occurs because proof-of-possession and every public key remain intact.
|
|
351
|
+
*/
|
|
352
|
+
replaceProfilePinFromAuthorizedSecrets(input: ServerProfilePinReplacementInput): Promise<ServerProfileRecord>;
|
|
338
353
|
unlock(input: ServerProfileUnlockInput): Promise<ResolvedServerProfileSession>;
|
|
339
354
|
/**
|
|
340
355
|
* Renews the short SMART bearer while retaining the already-open server
|
|
@@ -256,6 +256,38 @@ export class ServerProfileSessionManager {
|
|
|
256
256
|
listProfiles(ownerId) {
|
|
257
257
|
return this.options.store.listProfiles(ownerId);
|
|
258
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* Rewraps the exact registered wallet under a new PIN after a separate
|
|
261
|
+
* server-authorized recovery factor has released its secrets. No GW call or
|
|
262
|
+
* DCR occurs because proof-of-possession and every public key remain intact.
|
|
263
|
+
*/
|
|
264
|
+
async replaceProfilePinFromAuthorizedSecrets(input) {
|
|
265
|
+
const profile = await this.requireOwnedProfile(input.ownerId, input.profileId);
|
|
266
|
+
const seed = String(input.authorizedWalletSeed || '').trim();
|
|
267
|
+
requireBase64UrlSeed32(seed);
|
|
268
|
+
if (!this.options.store.deleteSessionsForProfile) {
|
|
269
|
+
throw new Error('Profile PIN replacement requires profile-session invalidation support.');
|
|
270
|
+
}
|
|
271
|
+
if (profile.protectedVpToken && !String(input.authorizedVpToken || '').trim()) {
|
|
272
|
+
throw new Error('Profile PIN replacement requires the authorized VP token.');
|
|
273
|
+
}
|
|
274
|
+
const walletKeyDerivationId = normalizedWalletKeyDerivationId(profile.walletKeyDerivationId, profile.profileId);
|
|
275
|
+
const wallet = await this.createWallet(walletKeyDerivationId, seed);
|
|
276
|
+
await requireRegisteredProfileKeys(wallet, walletContext(walletKeyDerivationId), profile);
|
|
277
|
+
const updated = {
|
|
278
|
+
...profile,
|
|
279
|
+
protectedWalletSeed: await protectServerProfileSecret(seed, input.newPin, `${profile.profileId}:wallet-seed`, this.options.sealer, this.options.profileProtection),
|
|
280
|
+
...(profile.protectedVpToken ? {
|
|
281
|
+
protectedVpToken: await protectServerProfileSecret(String(input.authorizedVpToken), input.newPin, `${profile.profileId}:vp-token`, this.options.sealer, this.options.profileProtection),
|
|
282
|
+
} : {}),
|
|
283
|
+
failedUnlocks: 0,
|
|
284
|
+
lockedUntil: undefined,
|
|
285
|
+
updatedAt: this.now().toISOString(),
|
|
286
|
+
};
|
|
287
|
+
await this.options.store.putProfile(updated);
|
|
288
|
+
await this.options.store.deleteSessionsForProfile(profile.profileId);
|
|
289
|
+
return updated;
|
|
290
|
+
}
|
|
259
291
|
async unlock(input) {
|
|
260
292
|
let profile = await this.requireOwnedProfile(input.ownerId, input.profileId);
|
|
261
293
|
this.requireSubject(profile, input.subjectDid);
|