gdc-sdk-node-ts 2.0.6 → 2.0.8

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 CHANGED
@@ -682,6 +682,19 @@ For legal-organization onboarding from a Node BFF/runtime, keep the host steps s
682
682
  Use `OrganizationControllerSdk.submitLegalOrganizationVerificationTransaction(...)` or
683
683
  `NodeHttpClient.submitLegalOrganizationVerificationTransaction(...)` for step 1.
684
684
 
685
+ Existing-tenant recovery/reverification:
686
+
687
+ - use `OrganizationControllerSdk.submitLegalOrganizationIssue(...)` or
688
+ `NodeHttpClient.submitLegalOrganizationIssue(...)` to refresh the ICA-backed
689
+ verification for an already registered tenant without creating a new Offer
690
+ - GW is expected to return one new controller activation code as
691
+ `org.schema.IndividualProduct.serialNumber`
692
+ - then continue with the existing helper chain:
693
+ `recoverOrganizationControllerWithIssueWithDeps(...)`
694
+ or directly `_exchange -> _dcr`
695
+ - for a reproducible local/staging runtime check, use:
696
+ `npm run test:e2e:live-gw:issue-recovery`
697
+
685
698
  Rules:
686
699
 
687
700
  - `_transaction` and `_activate` are different flows
@@ -714,3 +727,23 @@ Live E2E legal PDF source:
714
727
  - public URL: set `LIVE_GW_HOST_VERIFICATION_PDF_URL=https://.../file.pdf`
715
728
  - if both are present, the live suite prefers `LIVE_GW_HOST_VERIFICATION_PDF_URL`
716
729
  - Dropbox-style links are normalized to `dl=1` direct-download mode automatically
730
+
731
+ Recovery-specific live rule:
732
+
733
+ - `Organization/_issue` can succeed and still be followed by `_exchange` failure
734
+ if the controller `id_token` is not production-shaped enough
735
+ - the current GW `_exchange` path expects at least:
736
+ - syntactically valid JWT format
737
+ - `tenant_id` claim matching the target tenant
738
+ - the bundled recovery runner generates a syntactically valid demo JWT if
739
+ `CONTROLLER_ID_TOKEN` is not provided, but production/staging should use a
740
+ real IdP-issued token
741
+
742
+ Minimal live recovery command:
743
+
744
+ ```bash
745
+ PDF_PATH=/Users/fernando/GITS/gdc-workspace/examples/TEST-A4-Antifraud.pdf \
746
+ BASE_URL=http://127.0.0.1:3000 \
747
+ TENANT_ID=acme-id \
748
+ npm run test:e2e:live-gw:issue-recovery
749
+ ```
@@ -9,6 +9,8 @@
9
9
  export declare const GwCoreLifecycleAction: Readonly<{
10
10
  readonly Batch: "_batch";
11
11
  readonly BatchResponse: "_batch-response";
12
+ readonly Issue: "_issue";
13
+ readonly IssueResponse: "_issue-response";
12
14
  readonly Transaction: "_transaction";
13
15
  readonly TransactionResponse: "_transaction-response";
14
16
  readonly Disable: "_disable";
@@ -11,6 +11,8 @@ import { LifecycleRequestType } from 'gdc-common-utils-ts';
11
11
  export const GwCoreLifecycleAction = Object.freeze({
12
12
  Batch: '_batch',
13
13
  BatchResponse: '_batch-response',
14
+ Issue: '_issue',
15
+ IssueResponse: '_issue-response',
14
16
  Transaction: '_transaction',
15
17
  TransactionResponse: '_transaction-response',
16
18
  Disable: '_disable',
@@ -14,6 +14,7 @@ export async function activateEmployeeDeviceWithActivationCodeWithDeps(deps) {
14
14
  }
15
15
  const dcrPayload = {
16
16
  thid: `dcr-${createRuntimeUuid()}`,
17
+ code: deps.input.activationCode,
17
18
  ...deps.input.dcrPayload,
18
19
  };
19
20
  const dcr = await deps.submitAndPollWithBearerToken(initialAccessToken, deps.identityDeviceDcrPath(deps.routeCtx), deps.identityDeviceDcrPollPath(deps.routeCtx), dcrPayload, deps.input.pollOptions);
package/dist/index.d.ts CHANGED
@@ -13,6 +13,7 @@ export * from './family-organization-search.js';
13
13
  export * from './family-organization-registration.js';
14
14
  export * from './individual-onboarding.js';
15
15
  export * from './device-activation.js';
16
+ export * from './organization-controller-recovery.js';
16
17
  export * from './smart-token.js';
17
18
  export * from './order-offer-summary.js';
18
19
  export * from './organization-license-order.js';
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ export * from './family-organization-search.js';
14
14
  export * from './family-organization-registration.js';
15
15
  export * from './individual-onboarding.js';
16
16
  export * from './device-activation.js';
17
+ export * from './organization-controller-recovery.js';
17
18
  export * from './smart-token.js';
18
19
  export * from './order-offer-summary.js';
19
20
  export * from './organization-license-order.js';
@@ -127,6 +127,16 @@ export declare class HttpRuntimeClient implements NodeRuntimeClient {
127
127
  * - `_activate` remains available only for the older ICA `_verify` based flow
128
128
  */
129
129
  submitLegalOrganizationVerificationTransaction(hostCtx: HostRouteContext, input: NodeLegalOrganizationVerificationTransactionInput, pollOptions?: PollOptions): Promise<SubmitAndPollResult>;
130
+ /**
131
+ * Starts the host-side existing-tenant legal-organization reissue flow that
132
+ * GW CORE forwards to ICA `_verify`.
133
+ *
134
+ * Semantics:
135
+ * - reuse the same signed evidence/controller binding contract as `_transaction`
136
+ * - do not create a new Offer
137
+ * - expect GW CORE to reissue one controller activation code in the response
138
+ */
139
+ submitLegalOrganizationIssue(hostCtx: HostRouteContext, input: NodeLegalOrganizationVerificationTransactionInput, pollOptions?: PollOptions): Promise<SubmitAndPollResult>;
130
140
  /**
131
141
  * Submits one tenant-scoped DID document binding request.
132
142
  *
@@ -411,6 +421,8 @@ export declare class HttpRuntimeClient implements NodeRuntimeClient {
411
421
  private requireHostRouteContext;
412
422
  hostRegistryOrganizationTransactionPath(ctx?: HostRouteContext): string;
413
423
  hostRegistryOrganizationTransactionPollPath(ctx?: HostRouteContext): string;
424
+ hostRegistryOrganizationIssuePath(ctx?: HostRouteContext): string;
425
+ hostRegistryOrganizationIssuePollPath(ctx?: HostRouteContext): string;
414
426
  hostRegistryOrganizationActivatePath(ctx?: HostRouteContext): string;
415
427
  hostRegistryOrganizationActivatePollPath(ctx?: HostRouteContext): string;
416
428
  hostRegistryOrganizationDisablePath(ctx?: HostRouteContext): string;
@@ -465,6 +477,8 @@ export declare class HttpRuntimeClient implements NodeRuntimeClient {
465
477
  individualBundleSearchPollPath(ctx: RouteContext): string;
466
478
  identityTokenExchangePath(ctx: RouteContext): string;
467
479
  identityTokenExchangePollPath(ctx: RouteContext): string;
480
+ identityDeviceDcrPath(ctx: RouteContext): string;
481
+ identityDeviceDcrPollPath(ctx: RouteContext): string;
468
482
  identityOpenIdSmartTokenPath(ctx: RouteContext): string;
469
483
  identityOpenIdSmartTokenPollPath(ctx: RouteContext): string;
470
484
  private appendHttpTrace;
@@ -126,6 +126,27 @@ export class HttpRuntimeClient {
126
126
  });
127
127
  return this.submitAndPoll(this.hostRegistryOrganizationTransactionPath(hostCtx), this.hostRegistryOrganizationTransactionPollPath(hostCtx), payload, pollOptions);
128
128
  }
129
+ /**
130
+ * Starts the host-side existing-tenant legal-organization reissue flow that
131
+ * GW CORE forwards to ICA `_verify`.
132
+ *
133
+ * Semantics:
134
+ * - reuse the same signed evidence/controller binding contract as `_transaction`
135
+ * - do not create a new Offer
136
+ * - expect GW CORE to reissue one controller activation code in the response
137
+ */
138
+ async submitLegalOrganizationIssue(hostCtx, input, pollOptions) {
139
+ const thid = `organization-issue-${runtimeUuid()}`;
140
+ const jti = `organization-issue-jti-${runtimeUuid()}`;
141
+ const verificationBundle = buildLegalOrganizationVerificationGatewayRequestBundle(input);
142
+ const payload = this.wrapBundleAsGatewayTransactionMessage({
143
+ thid,
144
+ jti,
145
+ hostCtx,
146
+ bundle: verificationBundle,
147
+ });
148
+ return this.submitAndPoll(this.hostRegistryOrganizationIssuePath(hostCtx), this.hostRegistryOrganizationIssuePollPath(hostCtx), payload, pollOptions);
149
+ }
129
150
  /**
130
151
  * Submits one tenant-scoped DID document binding request.
131
152
  *
@@ -152,7 +173,7 @@ export class HttpRuntimeClient {
152
173
  *
153
174
  * Plaintext transport note:
154
175
  * - this Node runtime currently submits `_activate` as
155
- * `application/didcomm-plaintext+json`
176
+ * `application/didcomm-plain+json`
156
177
  * - because there is no real outer JWS/JWE envelope in that mode, the
157
178
  * runtime mirrors the technical communication metadata derived from
158
179
  * `controller.publicKeyJwk` / `controller.jwks` into `meta.jws.protected`
@@ -173,7 +194,7 @@ export class HttpRuntimeClient {
173
194
  const serviceClaims = activationDraft.buildServiceClaims();
174
195
  const transportMeta = buildDidcommPlaintextTransportMetadata({
175
196
  controller: input.controller,
176
- contentType: 'application/didcomm-plaintext+json',
197
+ contentType: 'application/didcomm-plain+json',
177
198
  });
178
199
  const payload = {
179
200
  thid,
@@ -895,6 +916,8 @@ export class HttpRuntimeClient {
895
916
  }
896
917
  hostRegistryOrganizationTransactionPath(ctx) { return this.hostRegistryPath(ctx, 'Organization', GwCoreLifecycleAction.Transaction); }
897
918
  hostRegistryOrganizationTransactionPollPath(ctx) { return this.hostRegistryPath(ctx, 'Organization', GwCoreLifecycleAction.TransactionResponse); }
919
+ hostRegistryOrganizationIssuePath(ctx) { return this.hostRegistryPath(ctx, 'Organization', GwCoreLifecycleAction.Issue); }
920
+ hostRegistryOrganizationIssuePollPath(ctx) { return this.hostRegistryPath(ctx, 'Organization', GwCoreLifecycleAction.IssueResponse); }
898
921
  hostRegistryOrganizationActivatePath(ctx) { return this.hostRegistryPath(ctx, 'Organization', '_activate'); }
899
922
  hostRegistryOrganizationActivatePollPath(ctx) { return this.hostRegistryPath(ctx, 'Organization', '_activate-response'); }
900
923
  hostRegistryOrganizationDisablePath(ctx) { return this.hostRegistryPath(ctx, 'Organization', GwCoreLifecycleAction.Disable); }
@@ -959,6 +982,12 @@ export class HttpRuntimeClient {
959
982
  identityTokenExchangePollPath(ctx) {
960
983
  return `/${encodeURIComponent('host')}/cds-${encodeURIComponent(ctx.jurisdiction)}/v1/${encodeURIComponent(ctx.sector)}/${encodeURIComponent(ctx.tenantId)}/identity/auth/_exchange-response`;
961
984
  }
985
+ identityDeviceDcrPath(ctx) {
986
+ return `/${encodeURIComponent('host')}/cds-${encodeURIComponent(ctx.jurisdiction)}/v1/${encodeURIComponent(ctx.sector)}/${encodeURIComponent(ctx.tenantId)}/identity/auth/_dcr`;
987
+ }
988
+ identityDeviceDcrPollPath(ctx) {
989
+ return `/${encodeURIComponent('host')}/cds-${encodeURIComponent(ctx.jurisdiction)}/v1/${encodeURIComponent(ctx.sector)}/${encodeURIComponent(ctx.tenantId)}/identity/auth/_dcr-response`;
990
+ }
962
991
  identityOpenIdSmartTokenPath(ctx) {
963
992
  return `/${encodeURIComponent(ctx.tenantId)}/cds-${encodeURIComponent(ctx.jurisdiction)}/v1/${encodeURIComponent(ctx.sector)}/identity/openid/smart/token`;
964
993
  }
@@ -54,6 +54,7 @@ export type NodeOrganizationDidBindingInput = OrganizationDidBindingInput;
54
54
  */
55
55
  export type RuntimeClient = {
56
56
  submitLegalOrganizationVerificationTransaction?: (hostCtx: HostRouteContext, input: NodeLegalOrganizationVerificationTransactionInput, pollOptions?: PollOptions) => Promise<SubmitAndPollResult>;
57
+ submitLegalOrganizationIssue?: (hostCtx: HostRouteContext, input: NodeLegalOrganizationVerificationTransactionInput, pollOptions?: PollOptions) => Promise<SubmitAndPollResult>;
57
58
  submitOrganizationDidBinding?: (ctx: RouteContext, input: NodeOrganizationDidBindingInput, pollOptions?: PollOptions) => Promise<SubmitAndPollResult>;
58
59
  activateOrganizationInGatewayFromIcaProof?: (hostCtx: HostRouteContext, input: NodeOrganizationActivationInput, pollOptions?: PollOptions) => Promise<SubmitAndPollResult>;
59
60
  confirmLegalOrganizationOrder?: (hostCtx: HostRouteContext, input: LegalOrganizationOrderInput, pollOptions?: PollOptions) => Promise<SubmitAndPollResult>;
@@ -29,6 +29,11 @@ export declare class OrganizationControllerSdk {
29
29
  * - `_activate` remains only for the legacy ICA `_verify` compatibility path
30
30
  */
31
31
  submitLegalOrganizationVerificationTransaction(hostCtx: HostRouteContext, input: NodeLegalOrganizationVerificationTransactionInput, pollOptions?: PollOptions): Promise<SubmitAndPollResult>;
32
+ /**
33
+ * Starts the host-side existing-tenant legal-organization reissue flow that
34
+ * refreshes verification and reissues one controller activation code.
35
+ */
36
+ submitLegalOrganizationIssue(hostCtx: HostRouteContext, input: NodeLegalOrganizationVerificationTransactionInput, pollOptions?: PollOptions): Promise<SubmitAndPollResult>;
32
37
  /**
33
38
  * Binds the current tenant organization DID document to one public alias
34
39
  * view.
@@ -28,6 +28,13 @@ export class OrganizationControllerSdk {
28
28
  submitLegalOrganizationVerificationTransaction(hostCtx, input, pollOptions) {
29
29
  return requireClientMethod(this.client, 'submitLegalOrganizationVerificationTransaction')(hostCtx, input, pollOptions);
30
30
  }
31
+ /**
32
+ * Starts the host-side existing-tenant legal-organization reissue flow that
33
+ * refreshes verification and reissues one controller activation code.
34
+ */
35
+ submitLegalOrganizationIssue(hostCtx, input, pollOptions) {
36
+ return requireClientMethod(this.client, 'submitLegalOrganizationIssue')(hostCtx, input, pollOptions);
37
+ }
31
38
  /**
32
39
  * Binds the current tenant organization DID document to one public alias
33
40
  * view.
@@ -0,0 +1,31 @@
1
+ import type { NodeLegalOrganizationVerificationTransactionInput, PollOptions, SubmitAndPollResult } from './orchestration/client-port.js';
2
+ import type { HostRouteContext } from './host-onboarding.js';
3
+ import type { RouteContext } from './individual-onboarding.js';
4
+ import type { EmployeeDeviceActivationResult } from './device-activation.js';
5
+ export type OrganizationControllerRecoveryInput = {
6
+ issueInput: NodeLegalOrganizationVerificationTransactionInput;
7
+ controllerIdToken: string;
8
+ dcrPayload: Record<string, unknown>;
9
+ issuePollOptions?: PollOptions;
10
+ activationPollOptions?: PollOptions;
11
+ };
12
+ export type OrganizationControllerRecoveryResult = {
13
+ issue: SubmitAndPollResult;
14
+ activationCode: string;
15
+ activation: EmployeeDeviceActivationResult;
16
+ };
17
+ type RecoverOrganizationControllerWithIssueDeps = {
18
+ hostCtx: HostRouteContext;
19
+ tenantCtx: RouteContext;
20
+ input: OrganizationControllerRecoveryInput;
21
+ submitLegalOrganizationIssue: (hostCtx: HostRouteContext, input: NodeLegalOrganizationVerificationTransactionInput, pollOptions?: PollOptions) => Promise<SubmitAndPollResult>;
22
+ identityTokenExchangePath: (ctx: RouteContext) => string;
23
+ identityTokenExchangePollPath: (ctx: RouteContext) => string;
24
+ identityDeviceDcrPath: (ctx: RouteContext) => string;
25
+ identityDeviceDcrPollPath: (ctx: RouteContext) => string;
26
+ submitAndPollWithBearerToken: (bearerToken: string | undefined, submitPath: string, pollPath: string, payload: {
27
+ thid?: string;
28
+ } & Record<string, unknown>, pollOptions?: PollOptions) => Promise<SubmitAndPollResult>;
29
+ };
30
+ export declare function recoverOrganizationControllerWithIssueWithDeps(deps: RecoverOrganizationControllerWithIssueDeps): Promise<OrganizationControllerRecoveryResult>;
31
+ export {};
@@ -0,0 +1,48 @@
1
+ // Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
2
+ import { activateEmployeeDeviceWithActivationCodeWithDeps } from './device-activation.js';
3
+ export async function recoverOrganizationControllerWithIssueWithDeps(deps) {
4
+ const issue = await deps.submitLegalOrganizationIssue(deps.hostCtx, deps.input.issueInput, deps.input.issuePollOptions);
5
+ const activationCode = readActivationCodeFromIssueResult(issue);
6
+ if (!activationCode) {
7
+ throw new Error('recoverOrganizationControllerWithIssue: missing org.schema.IndividualProduct.serialNumber in Organization/_issue response.');
8
+ }
9
+ const activation = await activateEmployeeDeviceWithActivationCodeWithDeps({
10
+ routeCtx: deps.tenantCtx,
11
+ input: {
12
+ activationCode,
13
+ idToken: deps.input.controllerIdToken,
14
+ dcrPayload: deps.input.dcrPayload,
15
+ pollOptions: deps.input.activationPollOptions,
16
+ },
17
+ identityTokenExchangePath: deps.identityTokenExchangePath,
18
+ identityTokenExchangePollPath: deps.identityTokenExchangePollPath,
19
+ identityDeviceDcrPath: deps.identityDeviceDcrPath,
20
+ identityDeviceDcrPollPath: deps.identityDeviceDcrPollPath,
21
+ submitAndPollWithBearerToken: deps.submitAndPollWithBearerToken,
22
+ });
23
+ return {
24
+ issue,
25
+ activationCode,
26
+ activation,
27
+ };
28
+ }
29
+ function readActivationCodeFromIssueResult(result) {
30
+ const pollBody = (result?.poll?.body || {});
31
+ const body = (pollBody.body || pollBody);
32
+ const data = Array.isArray(body.data) ? body.data : Array.isArray(pollBody.data) ? pollBody.data : [];
33
+ const firstEntry = data[0] || {};
34
+ const claims = firstEntry.meta?.claims;
35
+ const activationCode = String(claims?.['org.schema.IndividualProduct.serialNumber'] || '').trim();
36
+ if (activationCode) {
37
+ return activationCode;
38
+ }
39
+ const diagnostics = firstEntry.response?.outcome
40
+ ?.issue
41
+ ?.map((issue) => String(issue?.diagnostics || '').trim())
42
+ .filter(Boolean)
43
+ .join(' | ');
44
+ if (diagnostics) {
45
+ throw new Error(`recoverOrganizationControllerWithIssue: Organization/_issue failed: ${diagnostics}`);
46
+ }
47
+ return '';
48
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdc-sdk-node-ts",
3
- "version": "2.0.6",
3
+ "version": "2.0.8",
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.",
@@ -25,14 +25,15 @@
25
25
  "test:e2e:live-gw:legacy-fhir": "npm run build && RUN_LIVE_GW_E2E=1 LIVE_GW_E2E_TRANSPORT=legacy-fhir node --test tests/live-gw-node-runtime.e2e.test.mjs",
26
26
  "test:e2e:live-gw:all": "npm run build && RUN_LIVE_GW_E2E=1 LIVE_GW_E2E_TRANSPORT=all node --test tests/live-gw-node-runtime.e2e.test.mjs",
27
27
  "test:e2e:live-gw:host-transaction:clean": "bash ./scripts/run-live-gw-host-transaction-clean.sh",
28
+ "test:e2e:live-gw:issue-recovery": "npm run build && node ./scripts/run-live-organization-issue-recovery.mjs",
28
29
  "test:e2e:live-profile-runtime:individual": "npm run build && RUN_LIVE_PROFILE_RUNTIME_E2E=1 node --test tests/live-profile-runtime-individual.e2e.test.mjs",
29
30
  "test:e2e:live-profile-runtime:professional": "npm run build && RUN_LIVE_PROFILE_RUNTIME_PROFESSIONAL_E2E=1 node --test tests/live-profile-runtime-professional.e2e.test.mjs",
30
31
  "test:e2e:live-dialogue:consent-professional": "npm run build && RUN_LIVE_DIALOGUE_CONSENT_PROFESSIONAL_E2E=1 node --test tests/live-dialogue-consent-professional-access.e2e.test.mjs",
31
32
  "test:e2e:live-gw:clean": "bash ./scripts/run-live-gw-clean.sh"
32
33
  },
33
34
  "dependencies": {
34
- "gdc-common-utils-ts": "^2.0.6",
35
- "gdc-sdk-core-ts": "^2.0.5"
35
+ "gdc-common-utils-ts": "^2.0.10",
36
+ "gdc-sdk-core-ts": "^2.0.7"
36
37
  },
37
38
  "devDependencies": {
38
39
  "@types/node": "^20.14.10",