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.
@@ -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
- id: string;
80
- meta: {
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
- id: record.resourceId,
73
- meta: {
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
+ });
@@ -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';
@@ -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: readonly [{
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
  }>;
@@ -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
- resource: {
127
- data: [{
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";
@@ -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
+ }
@@ -118,3 +118,4 @@ export * from './wallet-memory-queue';
118
118
  export * from './profile-outbox-memory-repository';
119
119
  export * from './backend-message-manager-mem';
120
120
  export * from './profile-manager-mem';
121
+ export * from './break-glass-policy';
@@ -118,3 +118,4 @@ export * from './wallet-memory-queue.js';
118
118
  export * from './profile-outbox-memory-repository.js';
119
119
  export * from './backend-message-manager-mem.js';
120
120
  export * from './profile-manager-mem.js';
121
+ export * from './break-glass-policy.js';
@@ -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
- /** Extracts GW search rows regardless of async job envelope depth. */
7
- export declare function extractGwSearchResources(value: unknown): ReadonlyArray<Record<string, unknown>>;
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
- /** Extracts GW search rows regardless of async job envelope depth. */
29
- export function extractGwSearchResources(value) {
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
- const data = record(candidate.resource)?.data || candidate.data;
32
- if (Array.isArray(data))
33
- return data.map(record).filter(isRecord);
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 = extractGwSearchResources(input.licenseResponse);
40
- return extractGwSearchResources(input.employeeResponse).map((employee) => {
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') || segments.includes('petd');
14
+ return segments.includes('card');
15
15
  }
16
16
  function isIndividualIdentityDid(value) {
17
17
  return isDidWeb(value) && !isPhysicalSupportDid(value);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdc-common-utils-ts",
3
- "version": "2.6.1",
3
+ "version": "2.6.3",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },