gdc-sdk-node-ts 2.4.31 → 2.4.33

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.
@@ -3,14 +3,6 @@ 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
- /**
7
- * Creates the opaque FHIR identifier for the provider-level secondary-use
8
- * Consent. Call this exactly once when the server creates the subject's index
9
- * enrollment, persist the result there, and reuse it for every permit/deny.
10
- * It is intentionally random and must not be derived from a subject or tenant
11
- * identifier.
12
- */
13
- export declare function createDigitalTwinSecondaryUseConsentIdentifier(): string;
14
6
  /** Returns whether a value can be used as a pseudonymous digital-twin subject. */
15
7
  export declare function isDigitalTwinSubjectId(value: unknown): value is string;
16
8
  /** Rejects operational DIDs and malformed or caller-invented subject shapes. */
@@ -3,16 +3,6 @@ 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
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
- /**
7
- * Creates the opaque FHIR identifier for the provider-level secondary-use
8
- * Consent. Call this exactly once when the server creates the subject's index
9
- * enrollment, persist the result there, and reuse it for every permit/deny.
10
- * It is intentionally random and must not be derived from a subject or tenant
11
- * identifier.
12
- */
13
- export function createDigitalTwinSecondaryUseConsentIdentifier() {
14
- return `urn:uuid:${randomUUID()}`;
15
- }
16
6
  /** Returns whether a value can be used as a pseudonymous digital-twin subject. */
17
7
  export function isDigitalTwinSubjectId(value) {
18
8
  return DIGITAL_TWIN_SUBJECT_URN_UUID.test(String(value || '').trim());
@@ -48,9 +48,11 @@ export function extractConsentRules(value) {
48
48
  ? record.claims
49
49
  : undefined;
50
50
  for (const candidate of [record, claims]) {
51
- if (!candidate || !looksLikeConsentRule(candidate))
51
+ if (!candidate)
52
+ continue;
53
+ const rule = normalizeConsentRule(candidate);
54
+ if (!looksLikeConsentRule(rule))
52
55
  continue;
53
- const rule = candidate;
54
56
  const key = String(rule[ClaimConsent.identifier] || JSON.stringify(rule));
55
57
  if (!seenRules.has(key)) {
56
58
  seenRules.add(key);
@@ -62,6 +64,18 @@ export function extractConsentRules(value) {
62
64
  visit(value);
63
65
  return candidates;
64
66
  }
67
+ function normalizeConsentRule(value) {
68
+ const normalized = { ...value };
69
+ const context = String(value['@context'] || '').trim();
70
+ if (context) {
71
+ const prefix = context.endsWith('.') ? context : `${context}.`;
72
+ for (const [key, claimValue] of Object.entries(value)) {
73
+ if (key.startsWith(prefix))
74
+ normalized[key.slice(prefix.length)] = claimValue;
75
+ }
76
+ }
77
+ return normalized;
78
+ }
65
79
  function looksLikeConsentRule(value) {
66
80
  return Boolean(String(value[ClaimConsent.subject] || '').trim()
67
81
  && String(value[ClaimConsent.actorIdentifier] || '').trim()
@@ -1,4 +1,3 @@
1
- import { type ConsentRule } from 'gdc-common-utils-ts/models/consent-rule';
2
1
  import { type IndividualControllerCredentialInput, type IndividualControllerVpPayloadInput, type IndividualSubjectCredentialInput } from 'gdc-common-utils-ts';
3
2
  import { type NodeRuntimeClient, type PollOptions, type SubmitAndPollResult, type SubmitPayload } from './client-port.js';
4
3
  import type { FamilyOrganizationSummary } from 'gdc-common-utils-ts/utils/family-organization-summary';
@@ -128,7 +127,9 @@ export declare class IndividualControllerSdk {
128
127
  /**
129
128
  * Enables or disables the subject's secondary-use digital-twin projection.
130
129
  * This is the canonical patient-side operation; application code must not
131
- * publish a canonical Composition directly into the research index.
130
+ * publish a canonical Composition directly into the research index. The
131
+ * caller supplies only the portal/software/study reference. GW owns and
132
+ * reuses the underlying FHIR Consent identifier.
132
133
  */
133
134
  setDigitalTwinSecondaryUseConsent(ctx: RouteContext, input: DigitalTwinSecondaryUseConsentInput): Promise<DigitalTwinSecondaryUseConsentResult>;
134
135
  /**
@@ -137,15 +138,14 @@ export declare class IndividualControllerSdk {
137
138
  * data already shared for research.
138
139
  */
139
140
  purgeDigitalTwinSubjectLink(ctx: RouteContext, input: DigitalTwinSubjectLinkPurgeInput): Promise<DigitalTwinSubjectLinkPurgeResult>;
140
- /** Returns whether the subject currently permits projection by its index provider. */
141
+ /** Returns the current decision for one portal, software or research study. */
141
142
  getDigitalTwinSecondaryUseConsentStatus(ctx: RouteContext, input: Readonly<{
142
143
  subjectDid: string;
143
144
  indexProviderOrganizationDid: string;
144
- consentIdentifier: string;
145
+ researchUseReference: string;
145
146
  }>): Promise<Readonly<{
146
147
  exists: boolean;
147
148
  enabled: boolean;
148
- consent?: ConsentRule;
149
149
  }>>;
150
150
  /**
151
151
  * Searches indexed clinical bundles for the current subject/controller context.
@@ -191,7 +191,9 @@ export class IndividualControllerSdk {
191
191
  /**
192
192
  * Enables or disables the subject's secondary-use digital-twin projection.
193
193
  * This is the canonical patient-side operation; application code must not
194
- * publish a canonical Composition directly into the research index.
194
+ * publish a canonical Composition directly into the research index. The
195
+ * caller supplies only the portal/software/study reference. GW owns and
196
+ * reuses the underlying FHIR Consent identifier.
195
197
  */
196
198
  setDigitalTwinSecondaryUseConsent(ctx, input) {
197
199
  assertFacadeCapability(this.capabilities, ActorCapabilities.IndividualGenerateDigitalTwin, ActorKinds.IndividualController, 'setDigitalTwinSecondaryUseConsent');
@@ -206,26 +208,27 @@ export class IndividualControllerSdk {
206
208
  assertFacadeCapability(this.capabilities, ActorCapabilities.IndividualGenerateDigitalTwin, ActorKinds.IndividualController, 'purgeDigitalTwinSubjectLink');
207
209
  return requireClientMethod(this.client, 'purgeDigitalTwinSubjectLink')(ctx, input);
208
210
  }
209
- /** Returns whether the subject currently permits projection by its index provider. */
211
+ /** Returns the current decision for one portal, software or research study. */
210
212
  async getDigitalTwinSecondaryUseConsentStatus(ctx, input) {
211
213
  assertFacadeCapability(this.capabilities, ActorCapabilities.IndividualGenerateDigitalTwin, ActorKinds.IndividualController, 'getDigitalTwinSecondaryUseConsentStatus');
212
- const activeConsents = await new GatewayActiveConsentProvider(this.client, ctx)
214
+ const consents = await new GatewayActiveConsentProvider(this.client, ctx)
213
215
  .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.');
216
+ const researchUseReference = String(input.researchUseReference || '').trim();
217
+ if (!researchUseReference)
218
+ throw new Error('researchUseReference is required to identify the portal, software or study consent.');
217
219
  const indexProviderOrganizationDid = String(input.indexProviderOrganizationDid || '').trim();
218
220
  if (!indexProviderOrganizationDid)
219
221
  throw new Error('indexProviderOrganizationDid is required.');
220
- const consent = activeConsents.find((rule) => {
222
+ const consent = consents.find((rule) => {
223
+ const claims = rule;
221
224
  const actions = String(rule[ClaimConsent.action] || '').split(',').map((value) => value.trim());
222
- return String(rule[ClaimConsent.identifier] || '').trim() === consentIdentifier
225
+ return String(claims[ClaimConsent.sourceReference] || '').trim() === researchUseReference
223
226
  && String(rule[ClaimConsent.actorIdentifier] || '').trim() === indexProviderOrganizationDid
224
227
  && String(rule[ClaimConsent.purpose] || '').trim().toUpperCase() === String(HealthcareConsentPurposes.Research).toUpperCase()
225
228
  && actions.includes(ServiceCapability.DigitalTwinReader);
226
229
  });
227
230
  return consent
228
- ? { exists: true, enabled: String(consent[ClaimConsent.decision] || '').trim().toLowerCase() === 'permit', consent }
231
+ ? { exists: true, enabled: String(consent[ClaimConsent.decision] || '').trim().toLowerCase() === 'permit' }
229
232
  : { exists: false, enabled: false };
230
233
  }
231
234
  /**
@@ -507,8 +507,12 @@ export type DigitalTwinSecondaryUseConsentInput = {
507
507
  /** Organization/tenant that provides and stores the subject's index. */
508
508
  indexProviderOrganizationDid: string;
509
509
  decision: 'permit' | 'deny';
510
- /** Server-owned FHIR Consent identifier. Never accept it from the browser. */
511
- consentIdentifier: string;
510
+ /**
511
+ * Stable URL or URI identifying the portal, software or research study whose
512
+ * permission is being changed. GW uses it as `Consent.source-reference` to
513
+ * find or create the rule; callers never manage `Consent.identifier`.
514
+ */
515
+ researchUseReference: string;
512
516
  consentDate?: string;
513
517
  dataType?: string;
514
518
  pollOptions?: {
@@ -811,8 +811,9 @@ export async function grantProfessionalAccessWithDeps(routeCtx, input, deps) {
811
811
  * tenant-private subject alias.
812
812
  */
813
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.');
814
+ const researchUseReference = String(input.researchUseReference || '').trim();
815
+ if (!researchUseReference) {
816
+ throw new Error('researchUseReference is required to identify the portal, software or study consent.');
816
817
  }
817
818
  const indexProviderOrganizationDid = String(input.indexProviderOrganizationDid || '').trim();
818
819
  if (!indexProviderOrganizationDid) {
@@ -827,7 +828,9 @@ export async function setDigitalTwinSecondaryUseConsentWithDeps(routeCtx, input,
827
828
  if (context) {
828
829
  delete claims[`${context}.${ClaimConsent.attachmentContentType}`];
829
830
  delete claims[`${context}.${ClaimConsent.attachmentData}`];
831
+ delete claims[`${context}.${ClaimConsent.identifier}`];
830
832
  }
833
+ delete claims[ClaimConsent.identifier];
831
834
  const assigned = assignCidToClaimsId(claims);
832
835
  return { ...built, consentClaims: assigned.claims, claimsCid: assigned.cid };
833
836
  };
@@ -838,7 +841,7 @@ export async function setDigitalTwinSecondaryUseConsentWithDeps(routeCtx, input,
838
841
  purpose: HealthcareConsentPurposes.Research,
839
842
  actions: [ServiceCapability.DigitalTwinReader],
840
843
  decision: input.decision,
841
- consentIdentifier: input.consentIdentifier,
844
+ sourceReference: researchUseReference,
842
845
  consentDate: input.consentDate,
843
846
  dataType: input.dataType,
844
847
  pollOptions: input.pollOptions,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdc-sdk-node-ts",
3
- "version": "2.4.31",
3
+ "version": "2.4.33",
4
4
  "description": "Next-generation Node runtime package for the GDC SDK family",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Antifraud Services Inc.",
@@ -1,16 +0,0 @@
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>;
@@ -1,34 +0,0 @@
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
- }