gdc-common-utils-ts 2.6.1 → 2.6.3
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/dist/constants/employee-lifecycle.d.ts +5 -0
- package/dist/constants/employee-lifecycle.js +5 -0
- package/dist/examples/break-glass.d.ts +10 -0
- package/dist/examples/break-glass.js +11 -0
- package/dist/examples/employee.d.ts +34 -2
- package/dist/examples/employee.js +24 -2
- package/dist/examples/index.d.ts +1 -0
- package/dist/examples/index.js +1 -0
- package/dist/examples/license.d.ts +43 -2
- package/dist/examples/license.js +18 -4
- package/dist/examples/shared.d.ts +3 -0
- package/dist/examples/shared.js +3 -0
- package/dist/utils/break-glass-policy.d.ts +50 -0
- package/dist/utils/break-glass-policy.js +85 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/organization-employee-lifecycle.d.ts +10 -2
- package/dist/utils/organization-employee-lifecycle.js +35 -7
- package/dist/utils/subject-identity-binding.js +1 -1
- package/package.json +1 -1
|
@@ -16,6 +16,11 @@ export declare const EmployeeLifecycleActions: Readonly<{
|
|
|
16
16
|
export declare const EmployeeLifecycleQueryActions: Readonly<{
|
|
17
17
|
readonly List: "list";
|
|
18
18
|
}>;
|
|
19
|
+
/** Stable response-entry types for the shared employee and seat directory. */
|
|
20
|
+
export declare const OrganizationEmployeeSearchResponseEntryTypes: Readonly<{
|
|
21
|
+
readonly Employee: "Employee-search-response-v1.0";
|
|
22
|
+
readonly License: "License-search-response-v1.0";
|
|
23
|
+
}>;
|
|
19
24
|
export type EmployeeLifecycleAction = typeof EmployeeLifecycleActions[keyof typeof EmployeeLifecycleActions];
|
|
20
25
|
/** Version tag for routing metadata stored outside the high-entropy GW credential. */
|
|
21
26
|
export declare const EmployeeActivationGrantVersions: Readonly<{
|
|
@@ -15,6 +15,11 @@ export const EmployeeLifecycleActions = Object.freeze({
|
|
|
15
15
|
export const EmployeeLifecycleQueryActions = Object.freeze({
|
|
16
16
|
List: 'list',
|
|
17
17
|
});
|
|
18
|
+
/** Stable response-entry types for the shared employee and seat directory. */
|
|
19
|
+
export const OrganizationEmployeeSearchResponseEntryTypes = Object.freeze({
|
|
20
|
+
Employee: 'Employee-search-response-v1.0',
|
|
21
|
+
License: 'License-search-response-v1.0',
|
|
22
|
+
});
|
|
18
23
|
/** Version tag for routing metadata stored outside the high-entropy GW credential. */
|
|
19
24
|
export const EmployeeActivationGrantVersions = Object.freeze({
|
|
20
25
|
V1: 'employee-activation-grant-v1',
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Stable product-neutral incident identifier for emergency-access examples. */
|
|
2
|
+
export declare const EXAMPLE_BREAK_GLASS_INCIDENT_ID: "incident-example-001";
|
|
3
|
+
/** Stable product-neutral justification for emergency-access examples. */
|
|
4
|
+
export declare const EXAMPLE_BREAK_GLASS_JUSTIFICATION: "Immediate governed emergency access is required.";
|
|
5
|
+
/** Stable clock instant for deterministic emergency-access examples. */
|
|
6
|
+
export declare const EXAMPLE_BREAK_GLASS_ISSUED_AT: "2026-08-31T12:00:00.000Z";
|
|
7
|
+
/** Stable notifier result for deterministic emergency-access examples. */
|
|
8
|
+
export declare const EXAMPLE_BREAK_GLASS_NOTIFICATION_ID: "notification-example-001";
|
|
9
|
+
/** Stable product-neutral ledger channel for emergency-access examples. */
|
|
10
|
+
export declare const EXAMPLE_BREAK_GLASS_LEDGER_DATA_CHANNEL: "clinical-evidence-eu";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
+
/** Stable product-neutral incident identifier for emergency-access examples. */
|
|
3
|
+
export const EXAMPLE_BREAK_GLASS_INCIDENT_ID = 'incident-example-001';
|
|
4
|
+
/** Stable product-neutral justification for emergency-access examples. */
|
|
5
|
+
export const EXAMPLE_BREAK_GLASS_JUSTIFICATION = 'Immediate governed emergency access is required.';
|
|
6
|
+
/** Stable clock instant for deterministic emergency-access examples. */
|
|
7
|
+
export const EXAMPLE_BREAK_GLASS_ISSUED_AT = '2026-08-31T12:00:00.000Z';
|
|
8
|
+
/** Stable notifier result for deterministic emergency-access examples. */
|
|
9
|
+
export const EXAMPLE_BREAK_GLASS_NOTIFICATION_ID = 'notification-example-001';
|
|
10
|
+
/** Stable product-neutral ledger channel for emergency-access examples. */
|
|
11
|
+
export const EXAMPLE_BREAK_GLASS_LEDGER_DATA_CHANNEL = 'clinical-evidence-eu';
|
|
@@ -75,11 +75,43 @@ export declare function buildExampleEmployeeClaims(record: ExampleEmployeeRecord
|
|
|
75
75
|
* to show employee directory flows without reauthoring `meta.claims` by hand.
|
|
76
76
|
*/
|
|
77
77
|
export declare const EXAMPLE_EMPLOYEE_SEARCH_RESPONSE_BODY: Readonly<{
|
|
78
|
+
readonly resourceType: "Bundle";
|
|
79
|
+
readonly type: "batch-response";
|
|
80
|
+
readonly total: 3;
|
|
78
81
|
readonly data: {
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
type: "Employee-search-response-v1.0";
|
|
83
|
+
resource: {
|
|
84
|
+
id: string;
|
|
81
85
|
status: "active" | "inactive" | "purged";
|
|
82
86
|
claims: Readonly<Record<string, string>>;
|
|
87
|
+
meta: {
|
|
88
|
+
status: "active" | "inactive" | "purged";
|
|
89
|
+
claims: Readonly<Record<string, string>>;
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
response: {
|
|
93
|
+
status: "200";
|
|
83
94
|
};
|
|
84
95
|
}[];
|
|
85
96
|
}>;
|
|
97
|
+
/** @deprecated Read-only fixture for GW versions that nested search rows. */
|
|
98
|
+
export declare const EXAMPLE_EMPLOYEE_SEARCH_RESPONSE_BODY_LEGACY: Readonly<{
|
|
99
|
+
readonly data: readonly [{
|
|
100
|
+
readonly type: "Employee-search-response-v1.0";
|
|
101
|
+
readonly resource: {
|
|
102
|
+
readonly total: 3;
|
|
103
|
+
readonly data: {
|
|
104
|
+
id: string;
|
|
105
|
+
status: "active" | "inactive" | "purged";
|
|
106
|
+
claims: Readonly<Record<string, string>>;
|
|
107
|
+
meta: {
|
|
108
|
+
status: "active" | "inactive" | "purged";
|
|
109
|
+
claims: Readonly<Record<string, string>>;
|
|
110
|
+
};
|
|
111
|
+
}[];
|
|
112
|
+
};
|
|
113
|
+
readonly response: {
|
|
114
|
+
readonly status: "200";
|
|
115
|
+
};
|
|
116
|
+
}];
|
|
117
|
+
}>;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { OrganizationEmployeeSearchResponseEntryTypes } from '../constants/employee-lifecycle.js';
|
|
1
2
|
import { ClaimsPersonSchemaorg } from '../constants/schemaorg.js';
|
|
3
|
+
import { EXAMPLE_BUNDLE_RESOURCE_TYPE, EXAMPLE_BUNDLE_TYPE_BATCH_RESPONSE, ExampleHttpStatusText, } from './shared.js';
|
|
2
4
|
export const ExampleEmployeeEmails = Object.freeze({
|
|
3
5
|
SharedProfessional: 'shared.professional@example.org',
|
|
4
6
|
});
|
|
@@ -68,11 +70,31 @@ export function buildExampleEmployeeClaims(record) {
|
|
|
68
70
|
* to show employee directory flows without reauthoring `meta.claims` by hand.
|
|
69
71
|
*/
|
|
70
72
|
export const EXAMPLE_EMPLOYEE_SEARCH_RESPONSE_BODY = Object.freeze({
|
|
73
|
+
resourceType: EXAMPLE_BUNDLE_RESOURCE_TYPE,
|
|
74
|
+
type: EXAMPLE_BUNDLE_TYPE_BATCH_RESPONSE,
|
|
75
|
+
total: EXAMPLE_EMPLOYEE_DIRECTORY_RECORDS.length,
|
|
71
76
|
data: EXAMPLE_EMPLOYEE_DIRECTORY_RECORDS.map((record) => ({
|
|
72
|
-
|
|
73
|
-
|
|
77
|
+
type: OrganizationEmployeeSearchResponseEntryTypes.Employee,
|
|
78
|
+
resource: {
|
|
79
|
+
id: record.resourceId,
|
|
74
80
|
status: record.status,
|
|
75
81
|
claims: buildExampleEmployeeClaims(record),
|
|
82
|
+
meta: {
|
|
83
|
+
status: record.status,
|
|
84
|
+
claims: buildExampleEmployeeClaims(record),
|
|
85
|
+
},
|
|
76
86
|
},
|
|
87
|
+
response: { status: ExampleHttpStatusText.Ok },
|
|
77
88
|
})),
|
|
78
89
|
});
|
|
90
|
+
/** @deprecated Read-only fixture for GW versions that nested search rows. */
|
|
91
|
+
export const EXAMPLE_EMPLOYEE_SEARCH_RESPONSE_BODY_LEGACY = Object.freeze({
|
|
92
|
+
data: [{
|
|
93
|
+
type: OrganizationEmployeeSearchResponseEntryTypes.Employee,
|
|
94
|
+
resource: {
|
|
95
|
+
total: EXAMPLE_EMPLOYEE_SEARCH_RESPONSE_BODY.total,
|
|
96
|
+
data: EXAMPLE_EMPLOYEE_SEARCH_RESPONSE_BODY.data.map(entry => entry.resource),
|
|
97
|
+
},
|
|
98
|
+
response: { status: ExampleHttpStatusText.Ok },
|
|
99
|
+
}],
|
|
100
|
+
});
|
package/dist/examples/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export * from './license';
|
|
|
12
12
|
export * from './organization-role-license';
|
|
13
13
|
export * from './invoice';
|
|
14
14
|
export * from './inter-tenant-access-contract';
|
|
15
|
+
export * from './break-glass';
|
|
15
16
|
export * from './subject-identity-binding';
|
|
16
17
|
export * from './related-person';
|
|
17
18
|
export * from './consent-access';
|
package/dist/examples/index.js
CHANGED
|
@@ -12,6 +12,7 @@ export * from './license.js';
|
|
|
12
12
|
export * from './organization-role-license.js';
|
|
13
13
|
export * from './invoice.js';
|
|
14
14
|
export * from './inter-tenant-access-contract.js';
|
|
15
|
+
export * from './break-glass.js';
|
|
15
16
|
export * from './subject-identity-binding.js';
|
|
16
17
|
export * from './related-person.js';
|
|
17
18
|
export * from './consent-access.js';
|
|
@@ -141,9 +141,50 @@ export declare const EXAMPLE_LICENSE_ISSUE_RESPONSE_BODY: Readonly<{
|
|
|
141
141
|
}>;
|
|
142
142
|
/** Shared license search response containing the default two-installation state. */
|
|
143
143
|
export declare const EXAMPLE_LICENSE_LIST_RESPONSE_BODY_WITH_DEVICES: Readonly<{
|
|
144
|
+
readonly body: {
|
|
145
|
+
readonly resourceType: "Bundle";
|
|
146
|
+
readonly type: "batch-response";
|
|
147
|
+
readonly total: 1;
|
|
148
|
+
readonly data: readonly [{
|
|
149
|
+
readonly type: "License-search-response-v1.0";
|
|
150
|
+
readonly resource: {
|
|
151
|
+
readonly id: "8a8a5e1b-0d8e-4a7c-8c39-3b8034440001";
|
|
152
|
+
readonly meta: {
|
|
153
|
+
readonly status: "active";
|
|
154
|
+
readonly subjectId: string;
|
|
155
|
+
readonly maxDevices: 2;
|
|
156
|
+
readonly deviceBindings: readonly [Readonly<{
|
|
157
|
+
clientId: "client-employee-primary";
|
|
158
|
+
clientInstanceId: "installation-employee-primary";
|
|
159
|
+
status: "active";
|
|
160
|
+
deviceInfo: Readonly<{
|
|
161
|
+
clientInstanceId: "installation-employee-primary";
|
|
162
|
+
model: "Shared browser profile";
|
|
163
|
+
}>;
|
|
164
|
+
activatedAt: 1786570800;
|
|
165
|
+
}>, Readonly<{
|
|
166
|
+
clientId: "client-employee-secondary";
|
|
167
|
+
clientInstanceId: "installation-employee-secondary";
|
|
168
|
+
status: "revoked";
|
|
169
|
+
deviceInfo: Readonly<{
|
|
170
|
+
clientInstanceId: "installation-employee-secondary";
|
|
171
|
+
}>;
|
|
172
|
+
activatedAt: 1786570900;
|
|
173
|
+
revokedAt: 1786571000;
|
|
174
|
+
}>];
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
readonly response: {
|
|
178
|
+
readonly status: "200";
|
|
179
|
+
};
|
|
180
|
+
}];
|
|
181
|
+
};
|
|
182
|
+
}>;
|
|
183
|
+
/** @deprecated Read-only fixture for GW versions that nested search rows. */
|
|
184
|
+
export declare const EXAMPLE_LICENSE_LIST_RESPONSE_BODY_WITH_DEVICES_LEGACY: Readonly<{
|
|
144
185
|
readonly body: {
|
|
145
186
|
readonly resource: {
|
|
146
|
-
readonly data:
|
|
187
|
+
readonly data: {
|
|
147
188
|
readonly id: "8a8a5e1b-0d8e-4a7c-8c39-3b8034440001";
|
|
148
189
|
readonly meta: {
|
|
149
190
|
readonly status: "active";
|
|
@@ -169,7 +210,7 @@ export declare const EXAMPLE_LICENSE_LIST_RESPONSE_BODY_WITH_DEVICES: Readonly<{
|
|
|
169
210
|
revokedAt: 1786571000;
|
|
170
211
|
}>];
|
|
171
212
|
};
|
|
172
|
-
}];
|
|
213
|
+
}[];
|
|
173
214
|
};
|
|
174
215
|
};
|
|
175
216
|
}>;
|
package/dist/examples/license.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { DeviceAppTypes, DeviceBindingStatuses, DeviceUserClasses } from '../constants/device.js';
|
|
2
|
+
import { OrganizationEmployeeSearchResponseEntryTypes } from '../constants/employee-lifecycle.js';
|
|
2
3
|
import { ClaimsOrderSchemaorg, ClaimsIndividualProductSchemaorg, ClaimsOfferSchemaorg, ClaimsPersonSchemaorg, } from '../constants/schemaorg.js';
|
|
3
4
|
import { buildLicenseIssueClaims, buildLicensePurchaseClaims, LicenseClaimContext, LicenseCategories, LicenseStatuses, } from '../utils/license.js';
|
|
4
5
|
import { createLicenseOfferOrderEditor } from '../utils/license-offer-order.js';
|
|
5
|
-
import { EXAMPLE_LICENSE_ACCEPTED_OFFER_ID, EXAMPLE_LICENSE_AMOUNT, EXAMPLE_LICENSE_CHECKOUT_URL, EXAMPLE_LICENSE_CURRENCY, EXAMPLE_EMAIL_CONTROLLER_ORG, EXAMPLE_EMPLOYEE_ACTIVATION_CODE, EXAMPLE_EMPLOYEE_DEVICE_CLIENT_ID_PRIMARY, EXAMPLE_EMPLOYEE_DEVICE_CLIENT_ID_SECONDARY, EXAMPLE_EMPLOYEE_DEVICE_INSTANCE_ID_PRIMARY, EXAMPLE_EMPLOYEE_DEVICE_INSTANCE_ID_SECONDARY, EXAMPLE_EMPLOYEE_DEVICE_MODEL_PRIMARY, EXAMPLE_HEALTHCARE_ACTOR_ROLE_GENERALIST_MEDICAL_PRACTITIONER, EXAMPLE_LICENSE_INVOICE_ID, EXAMPLE_LICENSE_OFFER_ID, EXAMPLE_LICENSE_PLAN_DEFAULT, EXAMPLE_LICENSE_PAYMENT_METHOD_INVOICE, EXAMPLE_LICENSE_PAYMENT_URL, EXAMPLE_LICENSE_RENEWAL_CYCLE_YEARLY, EXAMPLE_LICENSE_PLAN_NAME, EXAMPLE_LICENSE_SEAT_UUID_ACTIVE, EXAMPLE_LICENSE_SEAT_UUID_AVAILABLE, EXAMPLE_LICENSE_SEAT_UUID_SECONDARY, EXAMPLE_LICENSE_SKU, EXAMPLE_LICENSE_SUBJECT_ID_ACTIVE, EXAMPLE_LICENSE_SUBJECT_ID_AVAILABLE, EXAMPLE_TENANT_IDENTIFIER, } from './shared.js';
|
|
6
|
+
import { EXAMPLE_LICENSE_ACCEPTED_OFFER_ID, EXAMPLE_LICENSE_AMOUNT, EXAMPLE_LICENSE_CHECKOUT_URL, EXAMPLE_LICENSE_CURRENCY, EXAMPLE_EMAIL_CONTROLLER_ORG, EXAMPLE_EMPLOYEE_ACTIVATION_CODE, EXAMPLE_EMPLOYEE_DEVICE_CLIENT_ID_PRIMARY, EXAMPLE_EMPLOYEE_DEVICE_CLIENT_ID_SECONDARY, EXAMPLE_EMPLOYEE_DEVICE_INSTANCE_ID_PRIMARY, EXAMPLE_EMPLOYEE_DEVICE_INSTANCE_ID_SECONDARY, EXAMPLE_EMPLOYEE_DEVICE_MODEL_PRIMARY, EXAMPLE_BUNDLE_RESOURCE_TYPE, EXAMPLE_BUNDLE_TYPE_BATCH_RESPONSE, EXAMPLE_HEALTHCARE_ACTOR_ROLE_GENERALIST_MEDICAL_PRACTITIONER, EXAMPLE_LICENSE_INVOICE_ID, EXAMPLE_LICENSE_OFFER_ID, EXAMPLE_LICENSE_PLAN_DEFAULT, EXAMPLE_LICENSE_PAYMENT_METHOD_INVOICE, EXAMPLE_LICENSE_PAYMENT_URL, EXAMPLE_LICENSE_RENEWAL_CYCLE_YEARLY, EXAMPLE_LICENSE_PLAN_NAME, EXAMPLE_LICENSE_SEAT_UUID_ACTIVE, EXAMPLE_LICENSE_SEAT_UUID_AVAILABLE, EXAMPLE_LICENSE_SEAT_UUID_SECONDARY, EXAMPLE_LICENSE_SKU, EXAMPLE_LICENSE_SUBJECT_ID_ACTIVE, EXAMPLE_LICENSE_SUBJECT_ID_AVAILABLE, EXAMPLE_TENANT_IDENTIFIER, ExampleHttpStatusText, } from './shared.js';
|
|
6
7
|
import { DEFAULT_LICENSE_DEVICE_ALLOWANCE } from '../utils/license.js';
|
|
7
8
|
import { EXAMPLE_EMPLOYEE_CONTROLLER_ACTIVE } from './employee.js';
|
|
8
9
|
export const EXAMPLE_EMPLOYEE_DEVICE_BINDINGS = Object.freeze([
|
|
@@ -123,8 +124,12 @@ export const EXAMPLE_LICENSE_ISSUE_RESPONSE_BODY = Object.freeze({
|
|
|
123
124
|
/** Shared license search response containing the default two-installation state. */
|
|
124
125
|
export const EXAMPLE_LICENSE_LIST_RESPONSE_BODY_WITH_DEVICES = Object.freeze({
|
|
125
126
|
body: {
|
|
126
|
-
|
|
127
|
-
|
|
127
|
+
resourceType: EXAMPLE_BUNDLE_RESOURCE_TYPE,
|
|
128
|
+
type: EXAMPLE_BUNDLE_TYPE_BATCH_RESPONSE,
|
|
129
|
+
total: 1,
|
|
130
|
+
data: [{
|
|
131
|
+
type: OrganizationEmployeeSearchResponseEntryTypes.License,
|
|
132
|
+
resource: {
|
|
128
133
|
id: EXAMPLE_LICENSE_ACTIVE_RECORD.id,
|
|
129
134
|
meta: {
|
|
130
135
|
status: EXAMPLE_LICENSE_ACTIVE_RECORD.status,
|
|
@@ -132,7 +137,16 @@ export const EXAMPLE_LICENSE_LIST_RESPONSE_BODY_WITH_DEVICES = Object.freeze({
|
|
|
132
137
|
maxDevices: DEFAULT_LICENSE_DEVICE_ALLOWANCE,
|
|
133
138
|
deviceBindings: EXAMPLE_EMPLOYEE_DEVICE_BINDINGS,
|
|
134
139
|
},
|
|
135
|
-
}
|
|
140
|
+
},
|
|
141
|
+
response: { status: ExampleHttpStatusText.Ok },
|
|
142
|
+
}],
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
/** @deprecated Read-only fixture for GW versions that nested search rows. */
|
|
146
|
+
export const EXAMPLE_LICENSE_LIST_RESPONSE_BODY_WITH_DEVICES_LEGACY = Object.freeze({
|
|
147
|
+
body: {
|
|
148
|
+
resource: {
|
|
149
|
+
data: EXAMPLE_LICENSE_LIST_RESPONSE_BODY_WITH_DEVICES.body.data.map(entry => entry.resource),
|
|
136
150
|
},
|
|
137
151
|
},
|
|
138
152
|
});
|
|
@@ -141,6 +141,8 @@ export declare const EXAMPLE_PROVIDER_ORGANIZATION_DID: "did:web:hospital.acme.o
|
|
|
141
141
|
export declare const EXAMPLE_PROVIDER_ORGANIZATION_URL: "https://hospital.acme.org";
|
|
142
142
|
export declare const EXAMPLE_GATEWAY_PUBLIC_ORIGIN: "https://gateway.example.com";
|
|
143
143
|
export declare const EXAMPLE_HOST_PUBLIC_HOSTNAME: "host.example.com";
|
|
144
|
+
export declare const EXAMPLE_PERSON_CARD_URI: "urn:example:card:person:subject-001";
|
|
145
|
+
export declare const EXAMPLE_ANIMAL_CARD_URI: "urn:example:card:animal:subject-002";
|
|
144
146
|
export declare const EXAMPLE_PROVIDER_TAX_ID: "VATES-B00112233";
|
|
145
147
|
export declare const EXAMPLE_PROVIDER_DOMAIN: "health-care.provider.example.org";
|
|
146
148
|
/** Synthetic public portal domain used to derive organization path DIDs. */
|
|
@@ -245,6 +247,7 @@ export declare const EXAMPLE_CONTENT_TYPE_FHIR_JSON: "application/fhir+json";
|
|
|
245
247
|
export declare const EXAMPLE_IPS_BUNDLE_ATTACHMENT_TITLE: "IPS Document Bundle";
|
|
246
248
|
export declare const EXAMPLE_BUNDLE_RESOURCE_TYPE: "Bundle";
|
|
247
249
|
export declare const EXAMPLE_BUNDLE_TYPE_BATCH: "batch";
|
|
250
|
+
export declare const EXAMPLE_BUNDLE_TYPE_BATCH_RESPONSE: "batch-response";
|
|
248
251
|
export declare const EXAMPLE_BUNDLE_TYPE_DOCUMENT: "document";
|
|
249
252
|
export declare const EXAMPLE_MEDICATION_STATEMENT_UUID: "urn:uuid:medication-statement-example-001";
|
|
250
253
|
export declare const EXAMPLE_MEDICATION_STATEMENT_IDENTIFIER: "urn:uuid:medication-statement-example-001";
|
package/dist/examples/shared.js
CHANGED
|
@@ -159,6 +159,8 @@ export const EXAMPLE_PROVIDER_ORGANIZATION_DID = 'did:web:hospital.acme.org';
|
|
|
159
159
|
export const EXAMPLE_PROVIDER_ORGANIZATION_URL = 'https://hospital.acme.org';
|
|
160
160
|
export const EXAMPLE_GATEWAY_PUBLIC_ORIGIN = 'https://gateway.example.com';
|
|
161
161
|
export const EXAMPLE_HOST_PUBLIC_HOSTNAME = 'host.example.com';
|
|
162
|
+
export const EXAMPLE_PERSON_CARD_URI = 'urn:example:card:person:subject-001';
|
|
163
|
+
export const EXAMPLE_ANIMAL_CARD_URI = 'urn:example:card:animal:subject-002';
|
|
162
164
|
export const EXAMPLE_PROVIDER_TAX_ID = 'VATES-B00112233';
|
|
163
165
|
export const EXAMPLE_PROVIDER_DOMAIN = 'health-care.provider.example.org';
|
|
164
166
|
/** Synthetic public portal domain used to derive organization path DIDs. */
|
|
@@ -287,6 +289,7 @@ export const EXAMPLE_CONTENT_TYPE_FHIR_JSON = 'application/fhir+json';
|
|
|
287
289
|
export const EXAMPLE_IPS_BUNDLE_ATTACHMENT_TITLE = 'IPS Document Bundle';
|
|
288
290
|
export const EXAMPLE_BUNDLE_RESOURCE_TYPE = 'Bundle';
|
|
289
291
|
export const EXAMPLE_BUNDLE_TYPE_BATCH = 'batch';
|
|
292
|
+
export const EXAMPLE_BUNDLE_TYPE_BATCH_RESPONSE = 'batch-response';
|
|
290
293
|
export const EXAMPLE_BUNDLE_TYPE_DOCUMENT = 'document';
|
|
291
294
|
export const EXAMPLE_MEDICATION_STATEMENT_UUID = 'urn:uuid:medication-statement-example-001';
|
|
292
295
|
export const EXAMPLE_MEDICATION_STATEMENT_IDENTIFIER = EXAMPLE_MEDICATION_STATEMENT_UUID;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Subject categories accepted by the shared emergency-access contract. */
|
|
2
|
+
export declare const BreakGlassSubjectKinds: Readonly<{
|
|
3
|
+
readonly Human: "human";
|
|
4
|
+
readonly Animal: "animal";
|
|
5
|
+
}>;
|
|
6
|
+
export type BreakGlassSubjectKind = typeof BreakGlassSubjectKinds[keyof typeof BreakGlassSubjectKinds];
|
|
7
|
+
/** Governed reasons accepted by the shared emergency-access contract. */
|
|
8
|
+
export declare const BreakGlassReasonCodes: Readonly<{
|
|
9
|
+
readonly LifeThreatening: "life-threatening";
|
|
10
|
+
readonly SeriousImminentHarm: "serious-imminent-harm";
|
|
11
|
+
readonly UnconsciousOrIncapacitated: "unconscious-or-incapacitated";
|
|
12
|
+
readonly AnimalEmergency: "animal-emergency";
|
|
13
|
+
}>;
|
|
14
|
+
export type BreakGlassReasonCode = typeof BreakGlassReasonCodes[keyof typeof BreakGlassReasonCodes];
|
|
15
|
+
/** Transport-neutral emergency request data evaluated before persistence. */
|
|
16
|
+
export type BreakGlassRequest = Readonly<{
|
|
17
|
+
incidentId: string;
|
|
18
|
+
subjectKind: BreakGlassSubjectKind;
|
|
19
|
+
reasonCode: BreakGlassReasonCode;
|
|
20
|
+
justification: string;
|
|
21
|
+
}>;
|
|
22
|
+
export type BreakGlassPolicyInput = Readonly<{
|
|
23
|
+
routeSector: string;
|
|
24
|
+
subjectKind: BreakGlassSubjectKind;
|
|
25
|
+
professionalRole: string;
|
|
26
|
+
requestedScope: string;
|
|
27
|
+
reasonCode: BreakGlassReasonCode;
|
|
28
|
+
}>;
|
|
29
|
+
export type BreakGlassPolicyDecision = Readonly<{
|
|
30
|
+
allowed: true;
|
|
31
|
+
maxLifetimeSeconds: 900;
|
|
32
|
+
}> | Readonly<{
|
|
33
|
+
allowed: false;
|
|
34
|
+
reason: string;
|
|
35
|
+
}>;
|
|
36
|
+
/** Extension hook owned by a domain package that recognizes its exact subject identifier. */
|
|
37
|
+
export type BreakGlassSubjectKindMatcher = (subjectDid: string, subjectKind: BreakGlassSubjectKind) => boolean;
|
|
38
|
+
/** Matches only neutral subject identifiers. Product/card formats require an injected matcher. */
|
|
39
|
+
export declare const matchesCanonicalBreakGlassSubjectKind: BreakGlassSubjectKindMatcher;
|
|
40
|
+
/**
|
|
41
|
+
* Resolves one subject identifier through the neutral matcher plus optional
|
|
42
|
+
* domain matchers. Shared code never imports a product-specific card parser.
|
|
43
|
+
*/
|
|
44
|
+
export declare function matchesBreakGlassSubjectKind(subjectDid: string, subjectKind: BreakGlassSubjectKind, domainMatchers?: readonly BreakGlassSubjectKindMatcher[]): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Evaluates the reusable sector, role, reason and read-only scope policy.
|
|
47
|
+
* GW services orchestrate persistence and audit; domain packages may extend
|
|
48
|
+
* identifier recognition through the shared matcher interface.
|
|
49
|
+
*/
|
|
50
|
+
export declare function evaluateBreakGlassPolicy(input: BreakGlassPolicyInput): BreakGlassPolicyDecision;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
+
import { HealthcareActorRoleCodes } from '../constants/healthcare.js';
|
|
3
|
+
import { DataspaceSectors } from '../constants/sectors.js';
|
|
4
|
+
/** Subject categories accepted by the shared emergency-access contract. */
|
|
5
|
+
export const BreakGlassSubjectKinds = Object.freeze({
|
|
6
|
+
Human: 'human',
|
|
7
|
+
Animal: 'animal',
|
|
8
|
+
});
|
|
9
|
+
/** Governed reasons accepted by the shared emergency-access contract. */
|
|
10
|
+
export const BreakGlassReasonCodes = Object.freeze({
|
|
11
|
+
LifeThreatening: 'life-threatening',
|
|
12
|
+
SeriousImminentHarm: 'serious-imminent-harm',
|
|
13
|
+
UnconsciousOrIncapacitated: 'unconscious-or-incapacitated',
|
|
14
|
+
AnimalEmergency: 'animal-emergency',
|
|
15
|
+
});
|
|
16
|
+
const RESEARCH_SECTORS = new Set([
|
|
17
|
+
DataspaceSectors.HealthResearch,
|
|
18
|
+
DataspaceSectors.AnimalResearch,
|
|
19
|
+
DataspaceSectors.OneHealthResearch,
|
|
20
|
+
]);
|
|
21
|
+
function occupationCode(value) {
|
|
22
|
+
const normalized = String(value || '').trim();
|
|
23
|
+
return (normalized.includes('|') ? normalized.slice(normalized.lastIndexOf('|') + 1) : normalized)
|
|
24
|
+
.replace(/[^0-9]/g, '');
|
|
25
|
+
}
|
|
26
|
+
function isReadOnlyScope(scope) {
|
|
27
|
+
const tokens = String(scope || '').split(/\s+/).filter(Boolean);
|
|
28
|
+
return tokens.length > 0 && tokens.every((token) => {
|
|
29
|
+
const head = token.split('?', 1)[0] || '';
|
|
30
|
+
const permission = head.slice(head.lastIndexOf('.') + 1).toLowerCase();
|
|
31
|
+
return permission === 'r' || permission === 'rs';
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/** Matches only neutral subject identifiers. Product/card formats require an injected matcher. */
|
|
35
|
+
export const matchesCanonicalBreakGlassSubjectKind = (subjectDid, subjectKind) => {
|
|
36
|
+
const normalized = String(subjectDid || '').trim().toLowerCase();
|
|
37
|
+
return subjectKind === BreakGlassSubjectKinds.Human
|
|
38
|
+
? normalized.includes(':individual:')
|
|
39
|
+
: normalized.includes(':animal:');
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Resolves one subject identifier through the neutral matcher plus optional
|
|
43
|
+
* domain matchers. Shared code never imports a product-specific card parser.
|
|
44
|
+
*/
|
|
45
|
+
export function matchesBreakGlassSubjectKind(subjectDid, subjectKind, domainMatchers = []) {
|
|
46
|
+
return [matchesCanonicalBreakGlassSubjectKind, ...domainMatchers]
|
|
47
|
+
.some((matcher) => matcher(subjectDid, subjectKind));
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Evaluates the reusable sector, role, reason and read-only scope policy.
|
|
51
|
+
* GW services orchestrate persistence and audit; domain packages may extend
|
|
52
|
+
* identifier recognition through the shared matcher interface.
|
|
53
|
+
*/
|
|
54
|
+
export function evaluateBreakGlassPolicy(input) {
|
|
55
|
+
const routeSector = String(input.routeSector || '').trim().toLowerCase();
|
|
56
|
+
if (RESEARCH_SECTORS.has(routeSector)) {
|
|
57
|
+
return { allowed: false, reason: 'research_sector_forbidden' };
|
|
58
|
+
}
|
|
59
|
+
if (!isReadOnlyScope(input.requestedScope)) {
|
|
60
|
+
return { allowed: false, reason: 'read_only_scope_required' };
|
|
61
|
+
}
|
|
62
|
+
const role = occupationCode(input.professionalRole);
|
|
63
|
+
if (input.subjectKind === BreakGlassSubjectKinds.Human) {
|
|
64
|
+
if (routeSector !== DataspaceSectors.HealthCare) {
|
|
65
|
+
return { allowed: false, reason: 'human_sector_mismatch' };
|
|
66
|
+
}
|
|
67
|
+
if (!role.startsWith(HealthcareActorRoleCodes.MedicalDoctors)) {
|
|
68
|
+
return { allowed: false, reason: 'professional_role_not_authorized' };
|
|
69
|
+
}
|
|
70
|
+
if (input.reasonCode === BreakGlassReasonCodes.AnimalEmergency) {
|
|
71
|
+
return { allowed: false, reason: 'reason_subject_mismatch' };
|
|
72
|
+
}
|
|
73
|
+
return { allowed: true, maxLifetimeSeconds: 900 };
|
|
74
|
+
}
|
|
75
|
+
if (routeSector !== DataspaceSectors.AnimalCare) {
|
|
76
|
+
return { allowed: false, reason: 'animal_sector_mismatch' };
|
|
77
|
+
}
|
|
78
|
+
if (role !== HealthcareActorRoleCodes.Veterinarian) {
|
|
79
|
+
return { allowed: false, reason: 'professional_role_not_authorized' };
|
|
80
|
+
}
|
|
81
|
+
if (input.reasonCode !== BreakGlassReasonCodes.AnimalEmergency) {
|
|
82
|
+
return { allowed: false, reason: 'reason_subject_mismatch' };
|
|
83
|
+
}
|
|
84
|
+
return { allowed: true, maxLifetimeSeconds: 900 };
|
|
85
|
+
}
|
package/dist/utils/index.d.ts
CHANGED
package/dist/utils/index.js
CHANGED
|
@@ -3,8 +3,16 @@ import type { EmployeeDeviceRevocationTarget, OrganizationEmployeeLifecycleRecor
|
|
|
3
3
|
export declare function buildEmployeeDeviceRevocationBody(target: EmployeeDeviceRevocationTarget): Readonly<Record<string, string>>;
|
|
4
4
|
/** Reads an employee activation credential from canonical and legacy GW envelopes. */
|
|
5
5
|
export declare function readEmployeeActivationCode(value: unknown): string;
|
|
6
|
-
/**
|
|
7
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Extracts primary resources from a DIDComm FHIR-like response Bundle.
|
|
8
|
+
*
|
|
9
|
+
* Canonical search matches live at `body.data[].resource`. The deprecated
|
|
10
|
+
* `resource.{total,data}` aggregate remains a read-only compatibility path so
|
|
11
|
+
* an SDK can survive a rolling GW deployment; producers must not emit it.
|
|
12
|
+
*/
|
|
13
|
+
export declare function extractBundleSearchResources(value: unknown): ReadonlyArray<Record<string, unknown>>;
|
|
14
|
+
/** @deprecated Use the transport-neutral `extractBundleSearchResources`. */
|
|
15
|
+
export declare const extractGwSearchResources: typeof extractBundleSearchResources;
|
|
8
16
|
/** Combines employee directory and license search results into one typed view. */
|
|
9
17
|
export declare function projectOrganizationEmployeeLifecycle(input: Readonly<{
|
|
10
18
|
employeeResponse: unknown;
|
|
@@ -25,19 +25,47 @@ export function readEmployeeActivationCode(value) {
|
|
|
25
25
|
}
|
|
26
26
|
return '';
|
|
27
27
|
}
|
|
28
|
-
/**
|
|
29
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Extracts primary resources from a DIDComm FHIR-like response Bundle.
|
|
30
|
+
*
|
|
31
|
+
* Canonical search matches live at `body.data[].resource`. The deprecated
|
|
32
|
+
* `resource.{total,data}` aggregate remains a read-only compatibility path so
|
|
33
|
+
* an SDK can survive a rolling GW deployment; producers must not emit it.
|
|
34
|
+
*/
|
|
35
|
+
export function extractBundleSearchResources(value) {
|
|
30
36
|
for (const candidate of listNestedRecords(value)) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
if (!Array.isArray(candidate.data))
|
|
38
|
+
continue;
|
|
39
|
+
const entries = candidate.data.map(record).filter(isRecord);
|
|
40
|
+
if (entries.length === 0)
|
|
41
|
+
return [];
|
|
42
|
+
const resources = entries.flatMap((entry) => {
|
|
43
|
+
const resource = record(entry.resource);
|
|
44
|
+
if (!resource)
|
|
45
|
+
return [];
|
|
46
|
+
const legacyRows = resource.total !== undefined && Array.isArray(resource.data)
|
|
47
|
+
? resource.data.map(record).filter(isRecord)
|
|
48
|
+
: undefined;
|
|
49
|
+
return legacyRows ?? [resource];
|
|
50
|
+
});
|
|
51
|
+
if (resources.length > 0)
|
|
52
|
+
return resources;
|
|
53
|
+
// Compatibility for older SDK fixtures that passed already-extracted rows.
|
|
54
|
+
return entries;
|
|
55
|
+
}
|
|
56
|
+
for (const candidate of listNestedRecords(value)) {
|
|
57
|
+
const legacyRows = record(candidate.resource)?.data;
|
|
58
|
+
if (Array.isArray(legacyRows))
|
|
59
|
+
return legacyRows.map(record).filter(isRecord);
|
|
34
60
|
}
|
|
35
61
|
return [];
|
|
36
62
|
}
|
|
63
|
+
/** @deprecated Use the transport-neutral `extractBundleSearchResources`. */
|
|
64
|
+
export const extractGwSearchResources = extractBundleSearchResources;
|
|
37
65
|
/** Combines employee directory and license search results into one typed view. */
|
|
38
66
|
export function projectOrganizationEmployeeLifecycle(input) {
|
|
39
|
-
const licenses =
|
|
40
|
-
return
|
|
67
|
+
const licenses = extractBundleSearchResources(input.licenseResponse);
|
|
68
|
+
return extractBundleSearchResources(input.employeeResponse).map((employee) => {
|
|
41
69
|
const claims = record(employee.claims) || record(record(employee.meta)?.claims) || {};
|
|
42
70
|
const resourceId = text(employee.id);
|
|
43
71
|
const employeeDid = text(claims[ClaimsPersonSchemaorg.identifier]);
|
|
@@ -11,7 +11,7 @@ function isDidWeb(value) {
|
|
|
11
11
|
}
|
|
12
12
|
function isPhysicalSupportDid(value) {
|
|
13
13
|
const segments = value.toLowerCase().split(':');
|
|
14
|
-
return segments.includes('card')
|
|
14
|
+
return segments.includes('card');
|
|
15
15
|
}
|
|
16
16
|
function isIndividualIdentityDid(value) {
|
|
17
17
|
return isDidWeb(value) && !isPhysicalSupportDid(value);
|