gdc-sdk-node-ts 2.9.11 → 2.9.12

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.
@@ -29,8 +29,9 @@ export type IndividualOrganizationOrderResult = SubmitAndPollResult & Readonly<{
29
29
  activationCode: string;
30
30
  /**
31
31
  * Governed `RelatedPerson.identifier` automatically materialized by GW for
32
- * the principal Organization owner/controller. Current GW values use the
33
- * `urn:uuid:<UUID>` form.
32
+ * the principal Organization owner/controller. GW reuses the bare UUID from
33
+ * `Organization.owner.identifier.value`; document helpers expose it as an
34
+ * `urn:uuid:<UUID>` FHIR reference only when a reference is required.
34
35
  */
35
36
  controllerRelatedPersonIdentifier: string;
36
37
  /**
@@ -53,6 +53,12 @@ export type IndividualOrganizationRegistrationInput = {
53
53
  * a product gateway extension.
54
54
  */
55
55
  controllerTelephone?: string;
56
+ /**
57
+ * Stable UUID of the principal controller/RESPRSN assignment. In a self
58
+ * registration this may be the same UUID already assigned to the person.
59
+ * Omit only for a brand-new assignment so the SDK creates it once.
60
+ */
61
+ controllerIdentifier?: string;
56
62
  controllerRole?: string;
57
63
  additionalClaims?: Record<string, unknown>;
58
64
  timeoutSeconds?: number;
@@ -1,6 +1,7 @@
1
1
  // Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
2
+ import { randomUUID } from 'node:crypto';
2
3
  import { ClaimsOfferSchemaorg, ClaimsOrganizationSchemaorg, ClaimsPersonSchemaorg, ClaimsServiceSchemaorg, } from 'gdc-common-utils-ts/constants';
3
- import { buildIndividualDidWeb, buildSecureIdValueIndividual, extractPrimaryClaims, readFamilyOrganizationSummaryFromResponseBody, SecureIdTypesIndividual, } from 'gdc-common-utils-ts';
4
+ import { buildIndividualDidWeb, buildSecureIdValueIndividual, extractPrimaryClaims, normalizeUuid, readFamilyOrganizationSummaryFromResponseBody, SecureIdTypesIndividual, } from 'gdc-common-utils-ts';
4
5
  export { buildIndividualMemberDidWebFromPrivateIdentifiers } from 'gdc-common-utils-ts';
5
6
  import { GwCoreLifecycleRequestType } from './constants/lifecycle.js';
6
7
  import { resolvePollOptionsFromSeconds } from './poll-options.js';
@@ -32,10 +33,14 @@ export async function registerIndividualOrganizationWithDeps(deps) {
32
33
  throw new Error('registerIndividualOrganization requires controllerEmail, or controllerTelephone only for compatibility/extension flows.');
33
34
  }
34
35
  const controllerRole = String(deps.input.controllerRole || 'RESPRSN').trim();
36
+ const controllerIdentifier = canonicalControllerUuid(deps.input.controllerIdentifier
37
+ || deps.input.additionalClaims?.[ClaimsOrganizationSchemaorg.ownerIdentifierValue]
38
+ || randomUUID());
35
39
  const claims = {
36
40
  '@context': 'org.schema',
37
41
  ...(deps.input.additionalClaims || {}),
38
42
  [ClaimsOrganizationSchemaorg.alternateName]: alternateName,
43
+ [ClaimsOrganizationSchemaorg.ownerIdentifierValue]: controllerIdentifier,
39
44
  [ClaimsServiceSchemaorg.category]: deps.routeCtx.sector,
40
45
  [ClaimsPersonSchemaorg.hasOccupationalRoleValue]: controllerRole,
41
46
  ...(controllerEmail
@@ -93,6 +98,19 @@ export async function registerIndividualOrganizationWithDeps(deps) {
93
98
  identity: readIndividualOrganizationBootstrapIdentity(registration.poll.body),
94
99
  };
95
100
  }
101
+ function canonicalControllerUuid(value) {
102
+ const hexadecimal = normalizeUuid(String(value || '').trim());
103
+ if (!hexadecimal) {
104
+ throw new TypeError('controllerIdentifier must be a UUID or urn:uuid identifier.');
105
+ }
106
+ return [
107
+ hexadecimal.slice(0, 8),
108
+ hexadecimal.slice(8, 12),
109
+ hexadecimal.slice(12, 16),
110
+ hexadecimal.slice(16, 20),
111
+ hexadecimal.slice(20),
112
+ ].join('-');
113
+ }
96
114
  /** @deprecated Use `registerIndividualOrganizationWithDeps`. */
97
115
  export async function startIndividualOrganizationWithDeps(deps) {
98
116
  return registerIndividualOrganizationWithDeps(deps);
@@ -9,6 +9,15 @@ import type { NodeCapability } from '../session.js';
9
9
  import type { IndividualOrganizationLifecycleInput } from 'gdc-sdk-core-ts';
10
10
  import type { BlockchainArtifactRegistrationInput, ClinicalBundleSearchInput, ClinicalSectionUpdateInput, SubjectSectionUpdateInput, 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
11
  import type { SmartTokenExchangeResult, SmartTokenRequestInput } from '../smart-token.js';
12
+ /** Profile-owned defaults applied to subject-section mutations after unlock. */
13
+ export type IndividualControllerProfileDefaults = Readonly<{
14
+ attester: SubjectSectionUpdateInput['attester'];
15
+ }>;
16
+ /**
17
+ * High-level section input. An opened controller profile supplies its own
18
+ * protected RESPRSN attester; standalone facades must still provide one.
19
+ */
20
+ export type IndividualControllerSubjectSectionUpdateInput = Omit<SubjectSectionUpdateInput, 'attester'> & Partial<Pick<SubjectSectionUpdateInput, 'attester'>>;
12
21
  /**
13
22
  * Individual-controller oriented facade over a `NodeRuntimeClient`.
14
23
  *
@@ -18,10 +27,11 @@ import type { SmartTokenExchangeResult, SmartTokenRequestInput } from '../smart-
18
27
  export declare class IndividualControllerSdk {
19
28
  private readonly client;
20
29
  private readonly capabilities?;
30
+ private readonly profileDefaults?;
21
31
  /**
22
32
  * @param client Runtime client implementation used to submit and poll GW flows.
23
33
  */
24
- constructor(client: NodeRuntimeClient, capabilities?: readonly NodeCapability[] | undefined);
34
+ constructor(client: NodeRuntimeClient, capabilities?: readonly NodeCapability[] | undefined, profileDefaults?: IndividualControllerProfileDefaults | undefined);
25
35
  /**
26
36
  * Registers the personal organization/subject index and returns its Offer.
27
37
  * Wallet creation, activation exchange, DCR, and session opening are later
@@ -118,7 +128,7 @@ export declare class IndividualControllerSdk {
118
128
  * appointments or contracts, while preserving the indexed Composition
119
129
  * author/attester compatibility contract.
120
130
  */
121
- updateSubjectSection(ctx: RouteContext, input: SubjectSectionUpdateInput): Promise<SubmitAndPollResult>;
131
+ updateSubjectSection(ctx: RouteContext, input: IndividualControllerSubjectSectionUpdateInput): Promise<SubmitAndPollResult>;
122
132
  /**
123
133
  * Updates the multi-section summary through a Composition-first document.
124
134
  * On a direct call, use the authenticated profile `actorDid` as `sender` and
@@ -18,9 +18,10 @@ export class IndividualControllerSdk {
18
18
  /**
19
19
  * @param client Runtime client implementation used to submit and poll GW flows.
20
20
  */
21
- constructor(client, capabilities) {
21
+ constructor(client, capabilities, profileDefaults) {
22
22
  this.client = client;
23
23
  this.capabilities = capabilities;
24
+ this.profileDefaults = profileDefaults;
24
25
  }
25
26
  /**
26
27
  * Registers the personal organization/subject index and returns its Offer.
@@ -173,7 +174,11 @@ export class IndividualControllerSdk {
173
174
  */
174
175
  updateSubjectSection(ctx, input) {
175
176
  assertFacadeCapability(this.capabilities, ActorCapabilities.IndividualIngestCommunication, ActorKinds.IndividualController, 'updateSubjectSection');
176
- return requireClientMethod(this.client, 'updateSubjectSection')(ctx, input);
177
+ const attester = input.attester || this.profileDefaults?.attester;
178
+ if (!attester) {
179
+ throw new Error('updateSubjectSection requires an explicit attester unless the facade comes from an opened controller profile.');
180
+ }
181
+ return requireClientMethod(this.client, 'updateSubjectSection')(ctx, { ...input, attester });
177
182
  }
178
183
  /**
179
184
  * Updates the multi-section summary through a Composition-first document.
@@ -11,7 +11,8 @@ import { ProfessionalSdk } from './orchestration/professional-sdk.js';
11
11
  import { DigitalTwinSdk } from './orchestration/digital-twin-sdk.js';
12
12
  import type { SmartTokenExchangeResult } from './smart-token.js';
13
13
  import type { DigitalTwinSearchInput, DigitalTwinSearchResult } from './digital-twin.js';
14
- import type { RouteContext } from './individual-onboarding.js';
14
+ import type { IndividualOrganizationOrderResult, RouteContext } from './individual-onboarding.js';
15
+ import type { IndividualOrganizationRegistrationResult } from './individual-start.js';
15
16
  import type { HostRouteContext } from './host-onboarding.js';
16
17
  import type { SecureDidcommTransportAdapter } from 'gdc-sdk-core-ts';
17
18
  import { type PinProtectedProfileSecret, type ProfileProtectionOptions, type ServerProfileSealer } from './server-profile-protection.js';
@@ -145,6 +146,13 @@ export type ServerProfileEnrollmentInput = Readonly<{
145
146
  idToken: string;
146
147
  /** One-time code returned by the completed organization flow or employee `License/_issue`. */
147
148
  activationCode: string;
149
+ /**
150
+ * Principal controller identifier projected by the SDK from
151
+ * `Organization.owner.identifier.value` and the matching automatic
152
+ * `RelatedPerson.identifier`. Individual-controller callers pass this
153
+ * identifier; the SDK constructs protected document-attester metadata.
154
+ */
155
+ controllerRelatedPersonIdentifier?: string;
148
156
  /**
149
157
  * Optional explicit installation identity. Normally omit it so the SDK
150
158
  * derives the identity from the wallet key it creates for this profile.
@@ -292,6 +300,20 @@ export type OpenedServerIndividualController = Readonly<{
292
300
  session: ResolvedServerProfileSession;
293
301
  profile: ServerProfileRecord;
294
302
  sdk: IndividualControllerSdk;
303
+ /** Returns the protected RelatedPerson URI for FHIR document attestation. */
304
+ getAttesterUriForDocs(): string;
305
+ }>;
306
+ /** One-time self-controller enrollment. It never opens a working session. */
307
+ export type ServerSelfIndividualControllerEnrollmentInput = Readonly<{
308
+ ownerId: string;
309
+ profileId: string;
310
+ registration: Pick<IndividualOrganizationRegistrationResult, 'identity'>;
311
+ order: Pick<IndividualOrganizationOrderResult, 'activationCode' | 'controllerRelatedPersonIdentifier'>;
312
+ routeContext: RouteContext;
313
+ pin: string;
314
+ idToken: string;
315
+ redirectUris: string[];
316
+ clientName: string;
295
317
  }>;
296
318
  /** Server-owned role evidence used to sign a fresh professional VP. */
297
319
  export type ServerProfessionalProofInput = Readonly<{
@@ -383,6 +405,13 @@ export declare class ServerProfileSessionManager {
383
405
  */
384
406
  sourceAuthor?: ClinicalSourceAuthorSelection;
385
407
  }>): Promise<ClinicalCreatorIpsExport>;
408
+ /**
409
+ * Enrolls the controller of a self-managed individual from SDK-owned
410
+ * registration and Order projections. The caller never parses claims or
411
+ * constructs FHIR attester metadata, and this operation does not open the
412
+ * resulting profile.
413
+ */
414
+ enrollSelfIndividualController(input: ServerSelfIndividualControllerEnrollmentInput): Promise<ServerProfileRecord>;
386
415
  enroll(input: ServerProfileEnrollmentInput): Promise<ServerProfileRecord>;
387
416
  /**
388
417
  * Rotates an employee wallet after a fresh OTP-authenticated GW recovery.
@@ -24,20 +24,23 @@ import { ProfilePinRejectedError, openServerProfileSecret, protectServerProfileS
24
24
  * RelatedPerson/PractitionerRole assignment returned by GW.
25
25
  */
26
26
  export function buildProfileAttester(input) {
27
- const hexadecimal = normalizeUuid(String(input.assignmentIdentifier || '').trim());
28
- if (!hexadecimal) {
29
- throw new TypeError('Profile attester assignmentIdentifier must be a UUID returned by RelatedPerson or PractitionerRole data.');
30
- }
31
- const uuid = [
32
- hexadecimal.slice(0, 8),
33
- hexadecimal.slice(8, 12),
34
- hexadecimal.slice(12, 16),
35
- hexadecimal.slice(16, 20),
36
- hexadecimal.slice(20),
37
- ].join('-');
27
+ const assignmentIdentifier = String(input.assignmentIdentifier || '').trim();
28
+ const hexadecimal = normalizeUuid(assignmentIdentifier);
29
+ const reference = hexadecimal
30
+ ? `${UrnPrefixes.Uuid}${[
31
+ hexadecimal.slice(0, 8),
32
+ hexadecimal.slice(8, 12),
33
+ hexadecimal.slice(12, 16),
34
+ hexadecimal.slice(16, 20),
35
+ hexadecimal.slice(20),
36
+ ].join('-')}`
37
+ : assignmentIdentifier;
38
+ if (!/^(?:urn:|https?:\/\/)/i.test(reference)) {
39
+ throw new TypeError('Profile attester assignmentIdentifier must be a governed RelatedPerson or PractitionerRole URI.');
40
+ }
38
41
  return Object.freeze({
39
42
  mode: input.mode,
40
- party: Object.freeze({ reference: `${UrnPrefixes.Uuid}${uuid}` }),
43
+ party: Object.freeze({ reference }),
41
44
  });
42
45
  }
43
46
  /**
@@ -76,11 +79,46 @@ export class ServerProfileSessionManager {
76
79
  async exportClinicalCreatorIps(input) {
77
80
  return exportServerProfileClinicalCreatorIps(await this.requireOwnedProfile(input.ownerId, input.profileId), { sourceAuthor: input.sourceAuthor });
78
81
  }
82
+ /**
83
+ * Enrolls the controller of a self-managed individual from SDK-owned
84
+ * registration and Order projections. The caller never parses claims or
85
+ * constructs FHIR attester metadata, and this operation does not open the
86
+ * resulting profile.
87
+ */
88
+ async enrollSelfIndividualController(input) {
89
+ const identity = input.registration.identity;
90
+ if (!identity) {
91
+ throw new Error('Self individual-controller enrollment requires the registered individual identity.');
92
+ }
93
+ return this.enroll({
94
+ ownerId: input.ownerId,
95
+ profileId: input.profileId,
96
+ actorKind: ActorKinds.IndividualController,
97
+ actorMode: 'self',
98
+ actorDid: identity.subjectDid,
99
+ profileDid: identity.subjectDid,
100
+ providerDid: identity.providerDidWeb,
101
+ routeContext: input.routeContext,
102
+ allowedSubjectDids: [identity.subjectDid],
103
+ pin: input.pin,
104
+ idToken: input.idToken,
105
+ activationCode: input.order.activationCode,
106
+ controllerRelatedPersonIdentifier: input.order.controllerRelatedPersonIdentifier,
107
+ redirectUris: input.redirectUris,
108
+ clientName: input.clientName,
109
+ });
110
+ }
79
111
  async enroll(input) {
80
112
  const normalizedClinicalCreatorBinding = input.clinicalCreatorBinding
81
113
  ? normalizeClinicalCreatorBinding(input.clinicalCreatorBinding)
82
114
  : undefined;
83
- const attester = input.attester || (normalizedClinicalCreatorBinding
115
+ const controllerAttester = input.controllerRelatedPersonIdentifier
116
+ ? buildProfileAttester({
117
+ assignmentIdentifier: input.controllerRelatedPersonIdentifier,
118
+ mode: CompositionAttesterModes.Personal,
119
+ })
120
+ : undefined;
121
+ const attester = controllerAttester || input.attester || (normalizedClinicalCreatorBinding
84
122
  ? buildProfileAttester({
85
123
  assignmentIdentifier: normalizedClinicalCreatorBinding.authorIdentifier,
86
124
  mode: normalizedClinicalCreatorBinding.kind === FhirIpsCreatorKinds.Professional
@@ -562,6 +600,10 @@ export class ServerProfileSessionManager {
562
600
  || (profile.actorMode !== 'self' && profile.actorMode !== 'controller')) {
563
601
  throw new Error('Profile is not an individual controller.');
564
602
  }
603
+ const profileAttester = profile.attester;
604
+ if (!profileAttester) {
605
+ throw new Error('The opened individual-controller profile has no RelatedPerson attester.');
606
+ }
565
607
  const client = new NodeHttpClient({
566
608
  baseUrl: this.options.gatewayBaseUrl,
567
609
  ctx: profile.routeContext,
@@ -571,7 +613,20 @@ export class ServerProfileSessionManager {
571
613
  transportProfile: TransportProfiles.DidcommEncryptedForm,
572
614
  secureTransportAdapter: session.secureTransportAdapter,
573
615
  });
574
- return { session, profile, sdk: new IndividualControllerSdk(client) };
616
+ return {
617
+ session,
618
+ profile,
619
+ sdk: new IndividualControllerSdk(client, undefined, {
620
+ attester: profileAttester,
621
+ }),
622
+ getAttesterUriForDocs: () => {
623
+ const reference = String(profileAttester.party.reference || '').trim();
624
+ if (!reference) {
625
+ throw new Error('The opened individual-controller profile has no RelatedPerson attester URI.');
626
+ }
627
+ return reference;
628
+ },
629
+ };
575
630
  }
576
631
  /**
577
632
  * Opens the high-level organization-controller API without exposing wallet,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdc-sdk-node-ts",
3
- "version": "2.9.11",
3
+ "version": "2.9.12",
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.",