gdc-common-utils-ts 2.7.7 → 2.8.0

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.
@@ -34,6 +34,14 @@ export declare const SmartPostDcrActions: Readonly<{
34
34
  readonly TokenResponse: "token-response";
35
35
  readonly LegacyBatchResponse: "_batch-response";
36
36
  }>;
37
+ /** Client authentication methods accepted by the SMART token endpoint. */
38
+ export declare const SmartClientAssertionTypes: Readonly<{
39
+ readonly PrivateKeyJwt: "private_key_jwt";
40
+ }>;
41
+ /** OIDC4VP ACR values used by SMART authorization profiles. */
42
+ export declare const SmartOpenIdAcrValues: Readonly<{
43
+ readonly Individual: "urn:antifraud:acr:openid4vp:individual";
44
+ }>;
37
45
  /** Wire-level request keys used by identity/auth operations. */
38
46
  export declare const IdentityAuthRequestFields: Readonly<{
39
47
  readonly SubjectToken: "subject_token";
@@ -34,6 +34,14 @@ export const SmartPostDcrActions = Object.freeze({
34
34
  TokenResponse: 'token-response',
35
35
  LegacyBatchResponse: '_batch-response',
36
36
  });
37
+ /** Client authentication methods accepted by the SMART token endpoint. */
38
+ export const SmartClientAssertionTypes = Object.freeze({
39
+ PrivateKeyJwt: 'private_key_jwt',
40
+ });
41
+ /** OIDC4VP ACR values used by SMART authorization profiles. */
42
+ export const SmartOpenIdAcrValues = Object.freeze({
43
+ Individual: 'urn:antifraud:acr:openid4vp:individual',
44
+ });
37
45
  /** Wire-level request keys used by identity/auth operations. */
38
46
  export const IdentityAuthRequestFields = Object.freeze({
39
47
  SubjectToken: 'subject_token',
@@ -0,0 +1,89 @@
1
+ export declare const FhirIpsCreatorKinds: Readonly<{
2
+ readonly IndividualSubject: "individual-subject";
3
+ readonly IndividualMember: "individual-member";
4
+ readonly Professional: "professional";
5
+ }>;
6
+ export type FhirIpsCreatorKind = typeof FhirIpsCreatorKinds[keyof typeof FhirIpsCreatorKinds];
7
+ export type FhirIpsCreatorBundleEntry = Readonly<{
8
+ fullUrl: string;
9
+ resource: Readonly<Record<string, unknown>>;
10
+ }>;
11
+ type FhirIpsCreatorBase = Readonly<{
12
+ /** Stable imported or locally generated actor identifier. */
13
+ actorIdentifier: string;
14
+ /** Stable identifier referenced by Composition.author for this role/relationship. */
15
+ authorIdentifier: string;
16
+ }>;
17
+ export type FhirIpsCreatorAuthorInput = (FhirIpsCreatorBase & Readonly<{
18
+ kind: typeof FhirIpsCreatorKinds.IndividualSubject;
19
+ subjectReference: string;
20
+ }>) | (FhirIpsCreatorBase & Readonly<{
21
+ kind: typeof FhirIpsCreatorKinds.IndividualMember;
22
+ subjectReference: string;
23
+ role: string;
24
+ }>) | (FhirIpsCreatorBase & Readonly<{
25
+ kind: typeof FhirIpsCreatorKinds.Professional;
26
+ organizationReference: string;
27
+ role: string;
28
+ }>);
29
+ export type FhirIpsCreatorAuthor = Readonly<{
30
+ authorReference: string;
31
+ entries: readonly FhirIpsCreatorBundleEntry[];
32
+ }>;
33
+ /**
34
+ * Builds the ordinary FHIR R4 resources referenced by an IPS
35
+ * `Composition.author`.
36
+ *
37
+ * The helper preserves imported `urn:uuid` identifiers. It never derives an
38
+ * author from email, telephone, an OIDC subject, DCR client id or key id.
39
+ * ONESELF references the existing Patient/subject entry. A professional
40
+ * references PractitionerRole while retaining the underlying Practitioner.
41
+ * An individual member references RelatedPerson.
42
+ */
43
+ export declare function buildFhirIpsCreatorAuthor(input: FhirIpsCreatorAuthorInput): FhirIpsCreatorAuthor;
44
+ type ClinicalCreatorChannelAliases = Readonly<{
45
+ /** Operational DIDs returned by authenticated profiles; never the stable FHIR identifier. */
46
+ actorDids?: readonly string[];
47
+ /** Verified contact hashes used only to find this binding. */
48
+ verifiedContactIdentifiers?: readonly string[];
49
+ /** Registered OAuth/DCR clients used only to find this binding. */
50
+ dcrClientIds?: readonly string[];
51
+ /** Registered signing keys used only to find this binding. */
52
+ keyIds?: readonly string[];
53
+ }>;
54
+ /**
55
+ * Durable clinical-creator identity stored behind the authenticated profile.
56
+ * `actorIdentifier` identifies the person/member. `authorIdentifier` identifies
57
+ * the concrete role or relationship assignment used as the FHIR author.
58
+ * Contacts, DCR clients, keys and operational DIDs are aliases only.
59
+ */
60
+ export type ClinicalCreatorBinding = ClinicalCreatorChannelAliases & Readonly<{
61
+ kind: FhirIpsCreatorKind;
62
+ actorIdentifier: string;
63
+ authorIdentifier: string;
64
+ /** Legal organization DID/URN or individual subject urn:uuid owning the assignment. */
65
+ ownerIdentifier: string;
66
+ /** Canonical governed ISCO/HL7 role claim; ONESELF is the subject relationship. */
67
+ role: string;
68
+ }>;
69
+ export type AuthenticatedClinicalCreatorEvidence = Readonly<{
70
+ actorDid?: string;
71
+ verifiedContactIdentifiers?: readonly string[];
72
+ dcrClientId?: string;
73
+ keyId?: string;
74
+ }>;
75
+ export type ClinicalCreatorPermissionActor = Readonly<{
76
+ /** UUID of the exact role/relationship assignment, never a contact address. */
77
+ actorIdentifier: string;
78
+ /** Governed role retained separately from the assignment UUID. */
79
+ actorRole: string;
80
+ }>;
81
+ /** Builds the stable actor-and-role pair consumed by Consent permission rules. */
82
+ export declare function buildClinicalCreatorPermissionActor(binding: ClinicalCreatorBinding): ClinicalCreatorPermissionActor;
83
+ /**
84
+ * Resolves already-authenticated channel evidence to one stable clinical
85
+ * creator binding. Callers must verify the OIDC contact, DCR client or signing
86
+ * key before invoking this deterministic matcher.
87
+ */
88
+ export declare function resolveClinicalCreatorBinding(bindings: readonly ClinicalCreatorBinding[], evidence: AuthenticatedClinicalCreatorEvidence): ClinicalCreatorBinding | undefined;
89
+ export {};
@@ -0,0 +1,133 @@
1
+ // Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
2
+ import { getHealthcareRoleByClaim } from '../constants/healthcare.js';
3
+ import { isStableActorIdentifier } from './actor-identifier.js';
4
+ import { normalizeUuid } from './normalize-uuid.js';
5
+ export const FhirIpsCreatorKinds = Object.freeze({
6
+ IndividualSubject: 'individual-subject',
7
+ IndividualMember: 'individual-member',
8
+ Professional: 'professional',
9
+ });
10
+ /**
11
+ * Builds the ordinary FHIR R4 resources referenced by an IPS
12
+ * `Composition.author`.
13
+ *
14
+ * The helper preserves imported `urn:uuid` identifiers. It never derives an
15
+ * author from email, telephone, an OIDC subject, DCR client id or key id.
16
+ * ONESELF references the existing Patient/subject entry. A professional
17
+ * references PractitionerRole while retaining the underlying Practitioner.
18
+ * An individual member references RelatedPerson.
19
+ */
20
+ export function buildFhirIpsCreatorAuthor(input) {
21
+ const actorIdentifier = requireUuidUrn(input.actorIdentifier, 'actorIdentifier');
22
+ const authorIdentifier = requireUuidUrn(input.authorIdentifier, 'authorIdentifier');
23
+ if (input.kind === FhirIpsCreatorKinds.IndividualSubject) {
24
+ const subjectReference = requireUuidUrn(input.subjectReference, 'subjectReference');
25
+ if (actorIdentifier !== subjectReference || authorIdentifier !== subjectReference) {
26
+ throw new TypeError('ONESELF author must reference the individual subject identifier.');
27
+ }
28
+ return { authorReference: subjectReference, entries: [] };
29
+ }
30
+ const role = requireRoleCoding(input.role);
31
+ if (input.kind === FhirIpsCreatorKinds.IndividualMember) {
32
+ const subjectReference = requireUuidUrn(input.subjectReference, 'subjectReference');
33
+ return {
34
+ authorReference: authorIdentifier,
35
+ entries: [{
36
+ fullUrl: authorIdentifier,
37
+ resource: {
38
+ resourceType: 'RelatedPerson',
39
+ id: uuidValue(authorIdentifier),
40
+ identifier: [{ value: actorIdentifier }],
41
+ patient: { reference: subjectReference },
42
+ relationship: [{ coding: [role] }],
43
+ },
44
+ }],
45
+ };
46
+ }
47
+ const organizationReference = requireReference(input.organizationReference, 'organizationReference');
48
+ return {
49
+ authorReference: authorIdentifier,
50
+ entries: [
51
+ {
52
+ fullUrl: actorIdentifier,
53
+ resource: {
54
+ resourceType: 'Practitioner',
55
+ id: uuidValue(actorIdentifier),
56
+ identifier: [{ value: actorIdentifier }],
57
+ },
58
+ },
59
+ {
60
+ fullUrl: authorIdentifier,
61
+ resource: {
62
+ resourceType: 'PractitionerRole',
63
+ id: uuidValue(authorIdentifier),
64
+ identifier: [{ value: authorIdentifier }],
65
+ practitioner: { reference: actorIdentifier },
66
+ organization: { reference: organizationReference },
67
+ code: [{ coding: [role] }],
68
+ },
69
+ },
70
+ ],
71
+ };
72
+ }
73
+ /** Builds the stable actor-and-role pair consumed by Consent permission rules. */
74
+ export function buildClinicalCreatorPermissionActor(binding) {
75
+ requireUuidUrn(binding.actorIdentifier, 'binding.actorIdentifier');
76
+ const actorIdentifier = requireUuidUrn(binding.authorIdentifier, 'binding.authorIdentifier');
77
+ requireReference(binding.ownerIdentifier, 'binding.ownerIdentifier');
78
+ requireRoleCoding(binding.role);
79
+ return { actorIdentifier, actorRole: binding.role };
80
+ }
81
+ /**
82
+ * Resolves already-authenticated channel evidence to one stable clinical
83
+ * creator binding. Callers must verify the OIDC contact, DCR client or signing
84
+ * key before invoking this deterministic matcher.
85
+ */
86
+ export function resolveClinicalCreatorBinding(bindings, evidence) {
87
+ const evidenceContacts = new Set((evidence.verifiedContactIdentifiers || [])
88
+ .map((value) => String(value || '').trim())
89
+ .filter(isStableActorIdentifier));
90
+ const actorDid = String(evidence.actorDid || '').trim();
91
+ const dcrClientId = String(evidence.dcrClientId || '').trim();
92
+ const keyId = String(evidence.keyId || '').trim();
93
+ const matches = bindings.filter((binding) => {
94
+ requireUuidUrn(binding.actorIdentifier, 'binding.actorIdentifier');
95
+ requireUuidUrn(binding.authorIdentifier, 'binding.authorIdentifier');
96
+ requireReference(binding.ownerIdentifier, 'binding.ownerIdentifier');
97
+ requireRoleCoding(binding.role);
98
+ return Boolean((actorDid && binding.actorDids?.includes(actorDid))
99
+ || (dcrClientId && binding.dcrClientIds?.includes(dcrClientId))
100
+ || (keyId && binding.keyIds?.includes(keyId))
101
+ || binding.verifiedContactIdentifiers?.some((identifier) => evidenceContacts.has(identifier)));
102
+ });
103
+ if (matches.length > 1) {
104
+ throw new Error('Authenticated channel resolves more than one clinical creator binding.');
105
+ }
106
+ return matches[0];
107
+ }
108
+ function requireUuidUrn(value, label) {
109
+ const normalized = String(value || '').trim().toLowerCase();
110
+ if (!normalized.startsWith('urn:uuid:') || !normalizeUuid(normalized)) {
111
+ throw new TypeError(`${label} must be a canonical urn:uuid identifier.`);
112
+ }
113
+ return normalized;
114
+ }
115
+ function uuidValue(identifier) {
116
+ return identifier.slice('urn:uuid:'.length);
117
+ }
118
+ function requireReference(value, label) {
119
+ const normalized = String(value || '').trim();
120
+ if (!normalized)
121
+ throw new TypeError(`${label} is required.`);
122
+ return normalized;
123
+ }
124
+ function requireRoleCoding(roleClaim) {
125
+ const descriptor = getHealthcareRoleByClaim(String(roleClaim || '').trim());
126
+ if (!descriptor)
127
+ throw new TypeError('role must be a canonical governed healthcare role.');
128
+ return {
129
+ system: descriptor.codingSystem,
130
+ code: descriptor.code,
131
+ ...(descriptor.titleEn ? { display: descriptor.titleEn } : {}),
132
+ };
133
+ }
@@ -32,6 +32,7 @@ export * from './discovery-normalization';
32
32
  export * from './format-converter';
33
33
  export * from './fhir-cid';
34
34
  export * from './fhir-search';
35
+ export * from './fhir-ips-creator-identity';
35
36
  export * from './family-organization-summary';
36
37
  export * from './gw-core-path';
37
38
  export * from './gw-core-commercial-contract';
@@ -32,6 +32,7 @@ export * from './discovery-normalization.js';
32
32
  export * from './format-converter.js';
33
33
  export * from './fhir-cid.js';
34
34
  export * from './fhir-search.js';
35
+ export * from './fhir-ips-creator-identity.js';
35
36
  export * from './family-organization-summary.js';
36
37
  export * from './gw-core-path.js';
37
38
  export * from './gw-core-commercial-contract.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdc-common-utils-ts",
3
- "version": "2.7.7",
3
+ "version": "2.8.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },