gdc-common-utils-ts 2.4.1 → 2.5.1

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.
Files changed (41) hide show
  1. package/README.md +15 -6
  2. package/dist/constants/healthcare.d.ts +13 -12
  3. package/dist/constants/healthcare.js +25 -25
  4. package/dist/constants/schemaorg.d.ts +2 -0
  5. package/dist/constants/schemaorg.js +2 -0
  6. package/dist/constants/verifiable-credentials.d.ts +8 -0
  7. package/dist/constants/verifiable-credentials.js +10 -0
  8. package/dist/examples/employee.d.ts +1 -1
  9. package/dist/examples/employee.js +2 -2
  10. package/dist/examples/ica-activation-proof.d.ts +1 -3
  11. package/dist/examples/ica-activation-proof.js +1 -3
  12. package/dist/examples/ica-verify-response.d.ts +2 -2
  13. package/dist/examples/ica-verify-response.js +1 -1
  14. package/dist/models/device-license.d.ts +2 -2
  15. package/dist/models/did.d.ts +2 -0
  16. package/dist/models/index.d.ts +1 -0
  17. package/dist/models/index.js +1 -0
  18. package/dist/models/subject-identifier-ledger.d.ts +13 -2
  19. package/dist/models/subject-identity.d.ts +36 -0
  20. package/dist/models/subject-identity.js +1 -0
  21. package/dist/utils/activation-policy.d.ts +2 -5
  22. package/dist/utils/activation-policy.js +4 -12
  23. package/dist/utils/actor-identifier.d.ts +9 -13
  24. package/dist/utils/actor-identifier.js +20 -21
  25. package/dist/utils/index.d.ts +2 -0
  26. package/dist/utils/index.js +2 -0
  27. package/dist/utils/jwk-thumbprint.d.ts +8 -0
  28. package/dist/utils/jwk-thumbprint.js +11 -0
  29. package/dist/utils/legal-organization-onboarding-editor.d.ts +2 -0
  30. package/dist/utils/legal-organization-onboarding-editor.js +1 -3
  31. package/dist/utils/legal-organization-verification-result.d.ts +45 -0
  32. package/dist/utils/legal-organization-verification-result.js +78 -0
  33. package/dist/utils/legal-organization-verification-transaction.d.ts +13 -0
  34. package/dist/utils/legal-organization-verification-transaction.js +6 -0
  35. package/dist/utils/organization-registration-authorization.d.ts +65 -0
  36. package/dist/utils/organization-registration-authorization.js +112 -0
  37. package/dist/utils/subject-identity.d.ts +30 -0
  38. package/dist/utils/subject-identity.js +127 -0
  39. package/dist/utils/vp-token.d.ts +14 -0
  40. package/dist/utils/vp-token.js +19 -1
  41. package/package.json +1 -1
package/README.md CHANGED
@@ -243,11 +243,18 @@ const lookupMultihash = encodeMultibaseSha3('DL|US-CA|D1234567');
243
243
  const blockchainAssetId = buildRawCidV1FromUtf8String('DL|US-CA|D1234567');
244
244
  ```
245
245
 
246
- Individual lookup tokens keep the identifier type and jurisdiction separate:
247
- `org.hl7.terminology.CodeSystem.v2-0203.NN|ES|12345678Z`. Import
248
- `buildIndividualIdentifierLedgerAssetId(...)` when a GW must derive the opaque
249
- SHA3-384 `urn:multibase` key used by the subject-identifier ledger. The raw
250
- identifier must never be written to that ledger.
246
+ Canonical Subject-collection lookup tokens use `codingSystem|codeValue`, for
247
+ example `org.hl7.terminology.CodeSystem.v2-0203.NN.ES|12345678Z` or
248
+ `urn:iso:std:iso:11784-11785|981020000123456`. Import
249
+ `buildSubjectIdentifierAssetId(...)` when a GW must derive the opaque SHA3-384
250
+ `urn:multibase` key used by the distributed subject index. The semantic
251
+ `Person`/`Animal` entry keeps the private claims and its `sameAs` points to the
252
+ stable public unified card; the raw identifier must never be written to the
253
+ ledger.
254
+
255
+ `buildIndividualIdentifierLedgerAssetId(...)` remains the older
256
+ type/jurisdiction/value compatibility helper. Do not use its former
257
+ `Organization.sameAs` convention for new Subject collection writes.
251
258
 
252
259
  Both helpers hash the exact UTF-8 bytes supplied by the caller. Identifier,
253
260
  FHIR-token or JSON canonicalization belongs to the contract that owns the
@@ -443,8 +450,10 @@ The canonical API contract should live in JSDoc on exported code. The README act
443
450
  - [`buildSmartCompositionReadScope(...)`](src/utils/smart-scope.ts)
444
451
  - Builds the current CORE GW pinned SMART root scope for `organization/Composition...` token requests.
445
452
  - This is the preferred first scope to teach when the backend only needs subject-scoped read access.
446
- - [`getOrganizationCredentialFromVpToken(...)`, `getLegalRepresentativeCredentialFromVpToken(...)`](src/utils/vp-token.ts)
453
+ - [`getOrganizationCredentialFromVpToken(...)`, `getLegalRepresentativeCredentialFromVpToken(...)`, `getOrganizationControllerCredentialFromVpToken(...)`](src/utils/vp-token.ts)
447
454
  - Extract typed VC objects from a VP token when GW/SDK flows carry canonical proof only in `vp_token`.
455
+ - [`readOrganizationControllerCredentialsFromResponseBody(...)`](src/utils/legal-organization-verification-result.ts)
456
+ - Reads every independently issued controller service VC from direct ICA or projected GW responses; companion helpers expose `owner.sameAs` and its JWK-thumbprint binding without falling back to the legal representative.
448
457
  - [`docs/101-VP_TOKEN.md`](docs/101-VP_TOKEN.md)
449
458
  - Step-by-step guide for building the canonical compact `vp_token` string from organization and representative VCs.
450
459
  - [`validateCommunicationResourceFhirR4(...)`](src/utils/communication-fhir-r4.ts)
@@ -1711,7 +1711,8 @@ export declare const HealthcareConsentPurposes: Readonly<{
1711
1711
  readonly RecordsManagement: "RECORDMGT";
1712
1712
  }>;
1713
1713
  export declare const HealthcareActorRoles: Readonly<{
1714
- readonly Controller: "ISCO-08|1120";
1714
+ /** Bare HL7 v3 RoleCode used for an organization controller. */
1715
+ readonly Controller: "RESPRSN";
1715
1716
  readonly MedicalDoctors: "ISCO-08|221";
1716
1717
  readonly GeneralistMedicalPractitioner: "ISCO-08|2211";
1717
1718
  readonly SpecialistMedicalPractitioner: "ISCO-08|2212";
@@ -1728,7 +1729,7 @@ export declare const HealthcareActorRoles: Readonly<{
1728
1729
  readonly MedicalSecretary: "ISCO-08|3344";
1729
1730
  }>;
1730
1731
  export declare const HealthcareActorRoleCodes: Readonly<{
1731
- readonly Controller: "1120";
1732
+ readonly Controller: "RESPRSN";
1732
1733
  readonly MedicalDoctors: "221";
1733
1734
  readonly GeneralistMedicalPractitioner: "2211";
1734
1735
  readonly SpecialistMedicalPractitioner: "2212";
@@ -1779,16 +1780,16 @@ export declare const HealthcareRolesByFamily: Readonly<{
1779
1780
  }>;
1780
1781
  export declare const HealthcareAllRolesByClaim: Readonly<Record<string, HealthcareActorRoleDescriptor>>;
1781
1782
  export declare const HealthcareProfessionalRoleCodesBySector: Readonly<{
1782
- readonly "health-care": readonly ("1120" | "221" | "2211" | "2212" | "2221" | "2222" | "2240" | "3344")[];
1783
- readonly "health-research": readonly ("1120" | "221" | "2211" | "2212" | "3344")[];
1784
- readonly "health-tech": readonly "1120"[];
1785
- readonly "health-insurance": readonly "1120"[];
1786
- readonly "animal-care": readonly ("1120" | "2250")[];
1787
- readonly "animal-research": readonly ("1120" | "2250")[];
1788
- readonly "animal-insurance": readonly "1120"[];
1789
- readonly "animal-tech": readonly "1120"[];
1790
- readonly "onehealth-research": readonly ("1120" | "221" | "2211" | "2212" | "2250" | "3344")[];
1791
- readonly "onehealth-tech": readonly "1120"[];
1783
+ readonly "health-care": readonly ("RESPRSN" | "221" | "2211" | "2212" | "2221" | "2222" | "2240" | "3344")[];
1784
+ readonly "health-research": readonly ("RESPRSN" | "221" | "2211" | "2212" | "3344")[];
1785
+ readonly "health-tech": readonly "RESPRSN"[];
1786
+ readonly "health-insurance": readonly "RESPRSN"[];
1787
+ readonly "animal-care": readonly ("RESPRSN" | "2250")[];
1788
+ readonly "animal-research": readonly ("RESPRSN" | "2250")[];
1789
+ readonly "animal-insurance": readonly "RESPRSN"[];
1790
+ readonly "animal-tech": readonly "RESPRSN"[];
1791
+ readonly "onehealth-research": readonly ("RESPRSN" | "221" | "2211" | "2212" | "2250" | "3344")[];
1792
+ readonly "onehealth-tech": readonly "RESPRSN"[];
1792
1793
  }>;
1793
1794
  export declare const HealthcareProfessionalRolesBySector: Readonly<{
1794
1795
  readonly "health-care": Readonly<Record<string, Readonly<{
@@ -343,7 +343,8 @@ export const HealthcareConsentPurposes = Object.freeze({
343
343
  RecordsManagement: 'RECORDMGT',
344
344
  });
345
345
  export const HealthcareActorRoles = Object.freeze({
346
- Controller: 'ISCO-08|1120',
346
+ /** Bare HL7 v3 RoleCode used for an organization controller. */
347
+ Controller: 'RESPRSN',
347
348
  MedicalDoctors: 'ISCO-08|221',
348
349
  GeneralistMedicalPractitioner: 'ISCO-08|2211',
349
350
  SpecialistMedicalPractitioner: 'ISCO-08|2212',
@@ -360,7 +361,7 @@ export const HealthcareActorRoles = Object.freeze({
360
361
  MedicalSecretary: 'ISCO-08|3344',
361
362
  });
362
363
  export const HealthcareActorRoleCodes = Object.freeze({
363
- Controller: '1120',
364
+ Controller: 'RESPRSN',
364
365
  MedicalDoctors: '221',
365
366
  GeneralistMedicalPractitioner: '2211',
366
367
  SpecialistMedicalPractitioner: '2212',
@@ -382,17 +383,6 @@ export const HealthcareActorRoleCodes = Object.freeze({
382
383
  });
383
384
  function buildProfessionalIscoRoles() {
384
385
  const entries = [
385
- [
386
- HealthcareActorRoleCodes.Controller,
387
- Object.freeze({
388
- family: HealthcareRoleFamilies.ProfessionalOccupationIsco08,
389
- codingSystem: ISCO08_CODING_SYSTEM,
390
- code: HealthcareActorRoleCodes.Controller,
391
- claim: HealthcareActorRoles.Controller,
392
- i18nKey: `${ISCO08_I18N_NAMESPACE}.${HealthcareActorRoleCodes.Controller}`,
393
- titleEn: 'Controller',
394
- }),
395
- ],
396
386
  [
397
387
  HealthcareActorRoleCodes.MedicalDoctors,
398
388
  Object.freeze({
@@ -519,15 +509,25 @@ export const HealthcareRolesByFamily = Object.freeze({
519
509
  });
520
510
  export const HealthcareAllRolesByClaim = Object.freeze(Object.fromEntries(Object.values(HealthcareRolesByFamily)
521
511
  .flatMap((catalog) => Object.values(catalog))
522
- .map((descriptor) => [descriptor.claim, descriptor])));
512
+ .flatMap((descriptor) => [
513
+ [descriptor.claim, descriptor],
514
+ [descriptor.code, descriptor],
515
+ ])));
523
516
  function pickRoleCatalogByCodes(catalog, codes) {
524
517
  return Object.freeze(Object.fromEntries(codes
525
518
  .map((code) => [code, catalog[code]])
526
519
  .filter((entry) => Boolean(entry[1]))));
527
520
  }
528
521
  function reindexRoleCatalogByClaim(catalog) {
529
- return Object.freeze(Object.fromEntries(Object.values(catalog).map((descriptor) => [descriptor.claim, descriptor])));
522
+ return Object.freeze(Object.fromEntries(Object.values(catalog).flatMap((descriptor) => [
523
+ [descriptor.claim, descriptor],
524
+ [descriptor.code, descriptor],
525
+ ])));
530
526
  }
527
+ const HealthcareProfessionalAndControllerRoles = Object.freeze({
528
+ ...HealthcareRolesByFamily[HealthcareRoleFamilies.ProfessionalOccupationIsco08],
529
+ [HealthcareActorRoleCodes.Controller]: HealthcareRolesByFamily[HealthcareRoleFamilies.LegalRepresentativeHl7][HealthcareActorRoleCodes.Controller],
530
+ });
531
531
  export const HealthcareProfessionalRoleCodesBySector = Object.freeze({
532
532
  [DataspaceSectors.HealthCare]: Object.freeze([
533
533
  HealthcareActorRoleCodes.Controller,
@@ -569,16 +569,16 @@ export const HealthcareProfessionalRoleCodesBySector = Object.freeze({
569
569
  [DataspaceSectors.OneHealthTech]: Object.freeze([HealthcareActorRoleCodes.Controller]),
570
570
  });
571
571
  export const HealthcareProfessionalRolesBySector = Object.freeze({
572
- [DataspaceSectors.HealthCare]: pickRoleCatalogByCodes(HealthcareRolesByFamily[HealthcareRoleFamilies.ProfessionalOccupationIsco08], HealthcareProfessionalRoleCodesBySector[DataspaceSectors.HealthCare]),
573
- [DataspaceSectors.HealthResearch]: pickRoleCatalogByCodes(HealthcareRolesByFamily[HealthcareRoleFamilies.ProfessionalOccupationIsco08], HealthcareProfessionalRoleCodesBySector[DataspaceSectors.HealthResearch]),
574
- [DataspaceSectors.HealthTech]: pickRoleCatalogByCodes(HealthcareRolesByFamily[HealthcareRoleFamilies.ProfessionalOccupationIsco08], HealthcareProfessionalRoleCodesBySector[DataspaceSectors.HealthTech]),
575
- [DataspaceSectors.HealthInsurance]: pickRoleCatalogByCodes(HealthcareRolesByFamily[HealthcareRoleFamilies.ProfessionalOccupationIsco08], HealthcareProfessionalRoleCodesBySector[DataspaceSectors.HealthInsurance]),
576
- [DataspaceSectors.AnimalCare]: pickRoleCatalogByCodes(HealthcareRolesByFamily[HealthcareRoleFamilies.ProfessionalOccupationIsco08], HealthcareProfessionalRoleCodesBySector[DataspaceSectors.AnimalCare]),
577
- [DataspaceSectors.AnimalResearch]: pickRoleCatalogByCodes(HealthcareRolesByFamily[HealthcareRoleFamilies.ProfessionalOccupationIsco08], HealthcareProfessionalRoleCodesBySector[DataspaceSectors.AnimalResearch]),
578
- [DataspaceSectors.AnimalInsurance]: pickRoleCatalogByCodes(HealthcareRolesByFamily[HealthcareRoleFamilies.ProfessionalOccupationIsco08], HealthcareProfessionalRoleCodesBySector[DataspaceSectors.AnimalInsurance]),
579
- [DataspaceSectors.AnimalTech]: pickRoleCatalogByCodes(HealthcareRolesByFamily[HealthcareRoleFamilies.ProfessionalOccupationIsco08], HealthcareProfessionalRoleCodesBySector[DataspaceSectors.AnimalTech]),
580
- [DataspaceSectors.OneHealthResearch]: pickRoleCatalogByCodes(HealthcareRolesByFamily[HealthcareRoleFamilies.ProfessionalOccupationIsco08], HealthcareProfessionalRoleCodesBySector[DataspaceSectors.OneHealthResearch]),
581
- [DataspaceSectors.OneHealthTech]: pickRoleCatalogByCodes(HealthcareRolesByFamily[HealthcareRoleFamilies.ProfessionalOccupationIsco08], HealthcareProfessionalRoleCodesBySector[DataspaceSectors.OneHealthTech]),
572
+ [DataspaceSectors.HealthCare]: pickRoleCatalogByCodes(HealthcareProfessionalAndControllerRoles, HealthcareProfessionalRoleCodesBySector[DataspaceSectors.HealthCare]),
573
+ [DataspaceSectors.HealthResearch]: pickRoleCatalogByCodes(HealthcareProfessionalAndControllerRoles, HealthcareProfessionalRoleCodesBySector[DataspaceSectors.HealthResearch]),
574
+ [DataspaceSectors.HealthTech]: pickRoleCatalogByCodes(HealthcareProfessionalAndControllerRoles, HealthcareProfessionalRoleCodesBySector[DataspaceSectors.HealthTech]),
575
+ [DataspaceSectors.HealthInsurance]: pickRoleCatalogByCodes(HealthcareProfessionalAndControllerRoles, HealthcareProfessionalRoleCodesBySector[DataspaceSectors.HealthInsurance]),
576
+ [DataspaceSectors.AnimalCare]: pickRoleCatalogByCodes(HealthcareProfessionalAndControllerRoles, HealthcareProfessionalRoleCodesBySector[DataspaceSectors.AnimalCare]),
577
+ [DataspaceSectors.AnimalResearch]: pickRoleCatalogByCodes(HealthcareProfessionalAndControllerRoles, HealthcareProfessionalRoleCodesBySector[DataspaceSectors.AnimalResearch]),
578
+ [DataspaceSectors.AnimalInsurance]: pickRoleCatalogByCodes(HealthcareProfessionalAndControllerRoles, HealthcareProfessionalRoleCodesBySector[DataspaceSectors.AnimalInsurance]),
579
+ [DataspaceSectors.AnimalTech]: pickRoleCatalogByCodes(HealthcareProfessionalAndControllerRoles, HealthcareProfessionalRoleCodesBySector[DataspaceSectors.AnimalTech]),
580
+ [DataspaceSectors.OneHealthResearch]: pickRoleCatalogByCodes(HealthcareProfessionalAndControllerRoles, HealthcareProfessionalRoleCodesBySector[DataspaceSectors.OneHealthResearch]),
581
+ [DataspaceSectors.OneHealthTech]: pickRoleCatalogByCodes(HealthcareProfessionalAndControllerRoles, HealthcareProfessionalRoleCodesBySector[DataspaceSectors.OneHealthTech]),
582
582
  });
583
583
  export const HealthcareProfessionalRolesBySectorAndClaim = Object.freeze(Object.fromEntries(Object.values(DataspaceSectors).map((sector) => [
584
584
  sector,
@@ -175,6 +175,8 @@ export declare enum ClaimsPersonSchemaorg {
175
175
  identifier = "org.schema.Person.identifier",// the URN (composed by the provider)
176
176
  identifierType = "org.schema.Person.identifier.additionalType",// retrieved from a form
177
177
  identifierValue = "org.schema.Person.identifier.value",// retrieved from a form
178
+ /** Stable public unified-card URI shared by private identity representations. */
179
+ sameAs = "org.schema.Person.sameAs",
178
180
  /** ICAO transliteration of official given name (including middlenames), family name and addtional surname */
179
181
  name = "org.schema.Person.name",
180
182
  memberOf = "org.schema.Person.memberOf",// for employees
@@ -183,6 +183,8 @@ export var ClaimsPersonSchemaorg;
183
183
  ClaimsPersonSchemaorg["identifier"] = "org.schema.Person.identifier";
184
184
  ClaimsPersonSchemaorg["identifierType"] = "org.schema.Person.identifier.additionalType";
185
185
  ClaimsPersonSchemaorg["identifierValue"] = "org.schema.Person.identifier.value";
186
+ /** Stable public unified-card URI shared by private identity representations. */
187
+ ClaimsPersonSchemaorg["sameAs"] = "org.schema.Person.sameAs";
186
188
  /** ICAO transliteration of official given name (including middlenames), family name and addtional surname */
187
189
  ClaimsPersonSchemaorg["name"] = "org.schema.Person.name";
188
190
  ClaimsPersonSchemaorg["memberOf"] = "org.schema.Person.memberOf";
@@ -28,6 +28,7 @@ export declare const ActivationCredentialTypes: Readonly<{
28
28
  OrganizationCredential: "OrganizationCredential";
29
29
  LegalOrganizationCredential: "LegalOrganizationCredential";
30
30
  LegalRepresentativeCredential: "LegalRepresentativeCredential";
31
+ OrganizationControllerCredential: "OrganizationControllerCredential";
31
32
  PersonCredential: "PersonCredential";
32
33
  }>;
33
34
  /**
@@ -59,6 +60,13 @@ export declare const IndividualCredentialTypes: Readonly<{
59
60
  */
60
61
  export declare const ContractCredentialTypes: Readonly<{
61
62
  InterTenantAccessContractCredential: "InterTenantAccessContractCredential";
63
+ /** Host/ICA-issued evidence that permits one controller to submit tenant registration. */
64
+ OrganizationRegistrationAuthorizationCredential: "OrganizationRegistrationAuthorizationCredential";
62
65
  }>;
63
66
  export declare const ORGANIZATION_ACTIVATION_VC_TYPES: readonly ("OrganizationCredential" | "LegalOrganizationCredential")[];
64
67
  export declare const REPRESENTATIVE_ACTIVATION_VC_TYPES: readonly ("LegalRepresentativeCredential" | "PersonCredential")[];
68
+ /**
69
+ * Canonical credential subtype used to bind an organization tenant service to
70
+ * one independently authenticated controller actor and its signing key.
71
+ */
72
+ export declare const ORGANIZATION_CONTROLLER_ACTIVATION_VC_TYPES: readonly "OrganizationControllerCredential"[];
@@ -30,6 +30,7 @@ export const ActivationCredentialTypes = Object.freeze({
30
30
  OrganizationCredential: 'OrganizationCredential',
31
31
  LegalOrganizationCredential: 'LegalOrganizationCredential',
32
32
  LegalRepresentativeCredential: 'LegalRepresentativeCredential',
33
+ OrganizationControllerCredential: 'OrganizationControllerCredential',
33
34
  PersonCredential: 'PersonCredential',
34
35
  });
35
36
  /**
@@ -61,6 +62,8 @@ export const IndividualCredentialTypes = Object.freeze({
61
62
  */
62
63
  export const ContractCredentialTypes = Object.freeze({
63
64
  InterTenantAccessContractCredential: 'InterTenantAccessContractCredential',
65
+ /** Host/ICA-issued evidence that permits one controller to submit tenant registration. */
66
+ OrganizationRegistrationAuthorizationCredential: 'OrganizationRegistrationAuthorizationCredential',
64
67
  });
65
68
  export const ORGANIZATION_ACTIVATION_VC_TYPES = Object.freeze([
66
69
  ActivationCredentialTypes.OrganizationCredential,
@@ -70,3 +73,10 @@ export const REPRESENTATIVE_ACTIVATION_VC_TYPES = Object.freeze([
70
73
  ActivationCredentialTypes.LegalRepresentativeCredential,
71
74
  ActivationCredentialTypes.PersonCredential,
72
75
  ]);
76
+ /**
77
+ * Canonical credential subtype used to bind an organization tenant service to
78
+ * one independently authenticated controller actor and its signing key.
79
+ */
80
+ export const ORGANIZATION_CONTROLLER_ACTIVATION_VC_TYPES = Object.freeze([
81
+ ActivationCredentialTypes.OrganizationControllerCredential,
82
+ ]);
@@ -27,7 +27,7 @@ export declare const ExampleEmployeeCredentialMaterials: Readonly<{
27
27
  readonly SharedProfessionalSigningKid: "did:key:z6Mkt2p7vKov6R6By7QjFv2F5x8f4H9g2m3c4n5p6q7r8s9t#z6Mkt2p7vKov6R6By7QjFv2F5x8f4H9g2m3c4n5p6q7r8s9t";
28
28
  }>;
29
29
  export declare const ExampleEmployeeRoles: Readonly<{
30
- readonly Controller: "ISCO-08|1120";
30
+ readonly Controller: "RESPRSN";
31
31
  readonly Doctor: "ISCO-08|2211";
32
32
  }>;
33
33
  export declare const EXAMPLE_EMPLOYEE_CONTROLLER_ACTIVE: ExampleEmployeeRecord;
@@ -9,12 +9,12 @@ export const ExampleEmployeeCredentialMaterials = Object.freeze({
9
9
  SharedProfessionalSigningKid: 'did:key:z6Mkt2p7vKov6R6By7QjFv2F5x8f4H9g2m3c4n5p6q7r8s9t#z6Mkt2p7vKov6R6By7QjFv2F5x8f4H9g2m3c4n5p6q7r8s9t',
10
10
  });
11
11
  export const ExampleEmployeeRoles = Object.freeze({
12
- Controller: 'ISCO-08|1120',
12
+ Controller: 'RESPRSN',
13
13
  Doctor: 'ISCO-08|2211',
14
14
  });
15
15
  export const EXAMPLE_EMPLOYEE_CONTROLLER_ACTIVE = Object.freeze({
16
16
  resourceId: 'urn:uuid:11111111-1111-4111-8111-111111111111',
17
- identifier: 'did:web:api.example.org:employee:shared.professional@example.org:ISCO-08|1120',
17
+ identifier: 'did:web:api.example.org:employee:shared.professional@example.org:RESPRSN',
18
18
  email: ExampleEmployeeEmails.SharedProfessional,
19
19
  role: ExampleEmployeeRoles.Controller,
20
20
  status: 'active',
@@ -24,8 +24,6 @@ export declare const EXAMPLE_ORG_CONTROLLER_SIGNING_KEY_ID: "urn:ietf:params:oau
24
24
  export declare const EXAMPLE_PRESENTATION_AUDIENCE_HOST_ID: "host:node-operator-es";
25
25
  export declare const EXAMPLE_ORGANIZATION_TAX_ID: "ESB00112233";
26
26
  export declare const EXAMPLE_REPRESENTATIVE_ROLE_CODE: "RESPRSN";
27
- export declare const EXAMPLE_REPRESENTATIVE_ISCO08_ROLE_CODE: "1120";
28
- export declare const EXAMPLE_REPRESENTATIVE_ISCO08_ROLE_IDENTIFIER: "urn:ilo:ilostat:isco-08:1120";
29
27
  export declare const EXAMPLE_REPRESENTATIVE_IDENTIFIER: "IDCES-99999999R";
30
28
  export declare const EXAMPLE_REPRESENTATIVE_EMAIL: "legal.rep@example.org";
31
29
  export declare const EXAMPLE_REPRESENTATIVE_SUBJECT_URN: "urn:person:identifier:IDCES-99999999R";
@@ -57,7 +55,7 @@ export declare const EXAMPLE_ORG_ACTIVATION_LEGAL_REPRESENTATIVE_CREDENTIAL: Rea
57
55
  taxID: "ESB00112233";
58
56
  };
59
57
  hasOccupation: {
60
- identifier: "urn:ilo:ilostat:isco-08:1120";
58
+ identifier: "RESPRSN";
61
59
  };
62
60
  hasCredential: {
63
61
  material: "urn:ietf:params:oauth:jwk-thumbprint:sha-256:Q0ZfM0V4YW1wbGVUaHVtYnByaW50X2Jhc2U2NHVybA";
@@ -30,8 +30,6 @@ export const EXAMPLE_ORG_CONTROLLER_SIGNING_KEY_ID = `${UrnPrefixes.JwkThumbprin
30
30
  export const EXAMPLE_PRESENTATION_AUDIENCE_HOST_ID = 'host:node-operator-es';
31
31
  export const EXAMPLE_ORGANIZATION_TAX_ID = 'ESB00112233';
32
32
  export const EXAMPLE_REPRESENTATIVE_ROLE_CODE = 'RESPRSN';
33
- export const EXAMPLE_REPRESENTATIVE_ISCO08_ROLE_CODE = '1120';
34
- export const EXAMPLE_REPRESENTATIVE_ISCO08_ROLE_IDENTIFIER = 'urn:ilo:ilostat:isco-08:1120';
35
33
  export const EXAMPLE_REPRESENTATIVE_IDENTIFIER = 'IDCES-99999999R';
36
34
  export const EXAMPLE_REPRESENTATIVE_EMAIL = 'legal.rep@example.org';
37
35
  export const EXAMPLE_REPRESENTATIVE_SUBJECT_URN = `urn:person:identifier:${EXAMPLE_REPRESENTATIVE_IDENTIFIER}`;
@@ -69,7 +67,7 @@ export const EXAMPLE_ORG_ACTIVATION_LEGAL_REPRESENTATIVE_CREDENTIAL = Object.fre
69
67
  taxID: EXAMPLE_ORGANIZATION_TAX_ID,
70
68
  },
71
69
  hasOccupation: {
72
- identifier: EXAMPLE_REPRESENTATIVE_ISCO08_ROLE_IDENTIFIER,
70
+ identifier: EXAMPLE_REPRESENTATIVE_ROLE_CODE,
73
71
  },
74
72
  hasCredential: {
75
73
  material: EXAMPLE_ORG_CONTROLLER_SIGNING_KEY_ID,
@@ -99,7 +99,7 @@ export declare const EXAMPLE_VERIFY_RESPONSE_ADDRESS_TYPE: "PostalAddress";
99
99
  export declare const EXAMPLE_VERIFY_RESPONSE_ADDRESS_COUNTRY: "ES";
100
100
  export declare const EXAMPLE_VERIFY_RESPONSE_OCCUPATION_TYPE: "Occupation";
101
101
  export declare const EXAMPLE_VERIFY_RESPONSE_OCCUPATION_NAME: "LegalRepresentative";
102
- export declare const EXAMPLE_VERIFY_RESPONSE_OCCUPATION_IDENTIFIER: "urn:ilo:ilostat:isco-08:1120";
102
+ export declare const EXAMPLE_VERIFY_RESPONSE_OCCUPATION_IDENTIFIER: "RESPRSN";
103
103
  export declare const EXAMPLE_VERIFY_RESPONSE_PROOF_TYPE: "JsonWebSignature2020";
104
104
  export declare const EXAMPLE_VERIFY_RESPONSE_PROOF_PURPOSE: "assertionMethod";
105
105
  export declare const EXAMPLE_VERIFY_RESPONSE_PROOF_VERIFICATION_METHOD: "did:web:localhost%3A3310#verification-key-001";
@@ -197,7 +197,7 @@ export declare const EXAMPLE_VERIFY_RESPONSE_PERSON_CREDENTIAL: Readonly<{
197
197
  hasOccupation: {
198
198
  '@type': "Occupation";
199
199
  name: "LegalRepresentative";
200
- identifier: "urn:ilo:ilostat:isco-08:1120";
200
+ identifier: "RESPRSN";
201
201
  };
202
202
  memberOf: {
203
203
  '@type': "Organization";
@@ -44,7 +44,7 @@ export const EXAMPLE_VERIFY_RESPONSE_ADDRESS_TYPE = 'PostalAddress';
44
44
  export const EXAMPLE_VERIFY_RESPONSE_ADDRESS_COUNTRY = 'ES';
45
45
  export const EXAMPLE_VERIFY_RESPONSE_OCCUPATION_TYPE = 'Occupation';
46
46
  export const EXAMPLE_VERIFY_RESPONSE_OCCUPATION_NAME = 'LegalRepresentative';
47
- export const EXAMPLE_VERIFY_RESPONSE_OCCUPATION_IDENTIFIER = 'urn:ilo:ilostat:isco-08:1120';
47
+ export const EXAMPLE_VERIFY_RESPONSE_OCCUPATION_IDENTIFIER = 'RESPRSN';
48
48
  export const EXAMPLE_VERIFY_RESPONSE_PROOF_TYPE = 'JsonWebSignature2020';
49
49
  export const EXAMPLE_VERIFY_RESPONSE_PROOF_PURPOSE = 'assertionMethod';
50
50
  export const EXAMPLE_VERIFY_RESPONSE_PROOF_VERIFICATION_METHOD = 'did:web:localhost%3A3310#verification-key-001';
@@ -162,8 +162,8 @@ export interface DeviceLicense {
162
162
  /** All DCR installations bound to this user/member seat. */
163
163
  deviceBindings?: DeviceBinding[];
164
164
  /**
165
- * Stable private actor identifier:
166
- * `urn:multibase:<hash(normalized email or phone)>:<professional|personal>`.
165
+ * Stable private contact identifier: `urn:multibase:<hash(normalized email or phone)>`.
166
+ * The canonical ISCO-08/FHIR-v3 role is stored separately in `issuedToRole`.
167
167
  * Portal DIDs, IdP subjects, wallets and DCR clients bind to this value but do not define it.
168
168
  */
169
169
  activatedBy?: string;
@@ -45,6 +45,8 @@ export interface DidDocument {
45
45
  '@context': string | string[];
46
46
  /** The DID URI itself. */
47
47
  id: string;
48
+ /** Independent DID subjects authorized to control this DID. */
49
+ controller?: string | string[];
48
50
  /** Public keys used for verifying digital signatures */
49
51
  verificationMethod?: VerificationMethod[];
50
52
  /**
@@ -51,6 +51,7 @@ export * from './resource-document';
51
51
  export * from './relationship-access';
52
52
  export * from './response';
53
53
  export * from './subject-identifier-ledger';
54
+ export * from './subject-identity';
54
55
  export * from './subject-identity-binding';
55
56
  export * from './terminology';
56
57
  export * from './urlPath';
@@ -51,6 +51,7 @@ export * from './resource-document.js';
51
51
  export * from './relationship-access.js';
52
52
  export * from './response.js';
53
53
  export * from './subject-identifier-ledger.js';
54
+ export * from './subject-identity.js';
54
55
  export * from './subject-identity-binding.js';
55
56
  export * from './terminology.js';
56
57
  export * from './urlPath.js';
@@ -6,11 +6,22 @@ export type SubjectIdentifierProviderPointer = Readonly<{
6
6
  /** Bare DNS domain: no URL scheme, path, DID or tenant identifier. */
7
7
  url: string;
8
8
  }>;
9
- /** Ledger payload. It deliberately contains no subject DID or raw identifier. */
9
+ /** Public unified-card pointer resolved from an exact known identifier. */
10
+ export type SubjectIdentifierCardPointer = Readonly<{
11
+ identifier: Readonly<{
12
+ value: string;
13
+ }>;
14
+ subjectKind: 'person' | 'animal' | 'property';
15
+ }>;
16
+ /** Ledger payload. It deliberately contains no raw private identifier. */
10
17
  export type SubjectIdentifierLedgerPayload = Readonly<{
18
+ card: SubjectIdentifierCardPointer;
11
19
  provider: SubjectIdentifierProviderPointer;
12
20
  }>;
13
- /** One individual/animal bundle entry expanded into one write per `sameAs` alias. */
21
+ /**
22
+ * @deprecated Parse canonical Person/Animal/Place Bundle entries with
23
+ * `readSubjectIdentityBundleEntry` instead. Kept for 2.x compatibility only.
24
+ */
14
25
  export type SubjectIdentifierLedgerBundleEntry = Readonly<{
15
26
  sameAs: string | readonly string[];
16
27
  }>;
@@ -0,0 +1,36 @@
1
+ import type { BundleEntry } from './bundle';
2
+ /** Subject categories supported by the neutral identity collection. */
3
+ export type SubjectKind = 'person' | 'animal' | 'property';
4
+ /** Semantic resource types stored inside the neutral Subject collection. */
5
+ export type SubjectIdentityResourceType = 'Person' | 'Animal' | 'Place';
6
+ /** Exact private identifier input linked to one public unified card. */
7
+ export type SubjectIdentityInput = Readonly<{
8
+ subjectKind: SubjectKind;
9
+ cardId: string;
10
+ /** Canonical identifier type/coding system. */
11
+ codingSystem: string;
12
+ /** ISO jurisdiction for scoped identifiers; empty for a global identifier. */
13
+ jurisdiction: string;
14
+ codeValue: string;
15
+ /** Additional encrypted claims; canonical identifier and sameAs keys cannot be overridden. */
16
+ additionalClaims?: Readonly<Record<string, unknown>>;
17
+ }>;
18
+ /** Normalized association recovered from one Subject collection entry. */
19
+ export type SubjectIdentityAssociation = SubjectIdentityInput & Readonly<{
20
+ resourceType: SubjectIdentityResourceType;
21
+ assetId: string;
22
+ }>;
23
+ /** Bundle entry used to write one semantic identity into the Subject collection. */
24
+ export type SubjectIdentityBundleEntry = BundleEntry & Readonly<{
25
+ type: 'Subject-identity-link-v1.0';
26
+ resource: NonNullable<BundleEntry['resource']> & Readonly<{
27
+ resourceType: SubjectIdentityResourceType;
28
+ meta: {
29
+ claims: Record<string, unknown>;
30
+ };
31
+ }>;
32
+ request: {
33
+ method: 'POST';
34
+ url: 'Subject';
35
+ };
36
+ }>;
@@ -0,0 +1 @@
1
+ export {};
@@ -88,11 +88,8 @@ export declare function extractDidWebFromCredential(credential: unknown): string
88
88
  * Checks whether the representative role expresses the activation-controller
89
89
  * semantics accepted by GW.
90
90
  *
91
- * Compatibility rules:
92
- * - historical GW payloads may still use HL7/FHIR `RESPRSN`
93
- * - current ICA person credentials may encode the legal representative role as
94
- * ISCO-08 `1120`, either tokenized (`ISCO-08|1120`) or as the canonical
95
- * ILO URN (`urn:ilo:ilostat:isco-08:1120`)
91
+ * The controller/legal-representative authorization is the bare HL7 v3 code
92
+ * `RESPRSN`. ISCO occupation `1120` is not a controller authorization role.
96
93
  *
97
94
  * @param roleCode Candidate role token extracted from the credential.
98
95
  * @param requiredCode Required GW compatibility code, defaults to `RESPRSN`.
@@ -187,22 +187,14 @@ export function extractDidWebFromCredential(credential) {
187
187
  * Checks whether the representative role expresses the activation-controller
188
188
  * semantics accepted by GW.
189
189
  *
190
- * Compatibility rules:
191
- * - historical GW payloads may still use HL7/FHIR `RESPRSN`
192
- * - current ICA person credentials may encode the legal representative role as
193
- * ISCO-08 `1120`, either tokenized (`ISCO-08|1120`) or as the canonical
194
- * ILO URN (`urn:ilo:ilostat:isco-08:1120`)
190
+ * The controller/legal-representative authorization is the bare HL7 v3 code
191
+ * `RESPRSN`. ISCO occupation `1120` is not a controller authorization role.
195
192
  *
196
193
  * @param roleCode Candidate role token extracted from the credential.
197
194
  * @param requiredCode Required GW compatibility code, defaults to `RESPRSN`.
198
195
  */
199
196
  export function hasActivationRepresentativeRole(roleCode, requiredCode = 'RESPRSN') {
200
- if (hasRoleCode(roleCode, requiredCode))
201
- return true;
202
- const normalized = String(roleCode || '').trim().toUpperCase();
203
- if (!normalized)
204
- return false;
205
- return /(?:^|[|:])1120$/.test(normalized);
197
+ return hasRoleCode(roleCode, requiredCode);
206
198
  }
207
199
  /**
208
200
  * Extracts the categories authorized by an ICA-issued organization credential.
@@ -354,7 +346,7 @@ export function validateActivationRepresentativePolicy(input) {
354
346
  if (!hasActivationRepresentativeRole(roleCode, input.requiredRoleCode || 'RESPRSN')) {
355
347
  errors.push({
356
348
  code: 'MISSING_REPRESENTATIVE_ROLE_RESPRSN',
357
- message: 'ICA-issued representative credential must include legal representative role semantics (RESPRSN or ISCO-08 1120) in credentialSubject.hasOccupation.',
349
+ message: 'ICA-issued representative credential must include controller role RESPRSN in credentialSubject.hasOccupation.',
358
350
  });
359
351
  }
360
352
  const binding = extractRepresentativeCredentialBinding(input.representativeCredential);
@@ -1,17 +1,11 @@
1
- /** Cross-portal actor classes. These are intentionally broader than job roles. */
2
- export declare const StableActorRoles: Readonly<{
3
- readonly Professional: "professional";
4
- readonly Personal: "personal";
5
- }>;
6
- export type StableActorRole = typeof StableActorRoles[keyof typeof StableActorRoles];
7
1
  export type StableActorContactKind = 'email' | 'phone';
8
2
  /**
9
3
  * Builds the private, stable actor identifier shared by independent portals.
10
4
  *
11
- * Wire format: `urn:multibase:<multibase(multihash(SHA3-256, normalized-contact))>:<role>`.
12
- * Examples end in `:professional` or `:personal`; the clear email/phone is
13
- * never embedded. A portal-specific `did:web`, IdP `sub`, wallet and DCR
14
- * `client_id` are bindings to this identifier, not replacements for it.
5
+ * Wire format: `urn:multibase:<multibase(multihash(SHA3-256, normalized-contact))>`.
6
+ * The clear email/phone and the actor role are never embedded. ISCO-08 or
7
+ * FHIR v3 roles are persisted separately in `Person.hasOccupation` and may be
8
+ * represented separately in an employee/member DID.
15
9
  *
16
10
  * Email is trimmed, stripped of `mailto:` and lower-cased. Phone is stripped
17
11
  * of `tel:` and formatting while preserving an E.164 leading `+`.
@@ -19,15 +13,17 @@ export type StableActorContactKind = 'email' | 'phone';
19
13
  export declare function buildStableActorIdentifier(input: {
20
14
  contactKind: StableActorContactKind;
21
15
  contact: string;
22
- role: StableActorRole;
23
16
  }): string;
24
17
  /**
25
- * Builds a portal-specific DID binding that carries the stable actor payload.
26
- * The portal root can change while the final actor payload remains identical.
18
+ * Builds a portal-specific employee DID binding. The stable actor payload and
19
+ * the canonical ISCO-08/FHIR-v3 role remain distinct path components.
20
+ *
21
+ * @deprecated Use `buildProfessionalDidWeb` for organization employees.
27
22
  */
28
23
  export declare function buildPortalActorDidWeb(input: {
29
24
  portalDidWeb: string;
30
25
  actorIdentifier: string;
26
+ role: string;
31
27
  }): string;
32
28
  /**
33
29
  * Extracts the stable actor URN from a canonical GDC portal actor DID.
@@ -1,26 +1,17 @@
1
1
  import { normalizePhone } from './consent.js';
2
- import { multibase58MultihashSha3_256 } from './same-as.js';
3
- /** Cross-portal actor classes. These are intentionally broader than job roles. */
4
- export const StableActorRoles = Object.freeze({
5
- Professional: 'professional',
6
- Personal: 'personal',
7
- });
2
+ import { normalizeSameAsHash, normalizeTelephoneHash } from './same-as.js';
8
3
  /**
9
4
  * Builds the private, stable actor identifier shared by independent portals.
10
5
  *
11
- * Wire format: `urn:multibase:<multibase(multihash(SHA3-256, normalized-contact))>:<role>`.
12
- * Examples end in `:professional` or `:personal`; the clear email/phone is
13
- * never embedded. A portal-specific `did:web`, IdP `sub`, wallet and DCR
14
- * `client_id` are bindings to this identifier, not replacements for it.
6
+ * Wire format: `urn:multibase:<multibase(multihash(SHA3-256, normalized-contact))>`.
7
+ * The clear email/phone and the actor role are never embedded. ISCO-08 or
8
+ * FHIR v3 roles are persisted separately in `Person.hasOccupation` and may be
9
+ * represented separately in an employee/member DID.
15
10
  *
16
11
  * Email is trimmed, stripped of `mailto:` and lower-cased. Phone is stripped
17
12
  * of `tel:` and formatting while preserving an E.164 leading `+`.
18
13
  */
19
14
  export function buildStableActorIdentifier(input) {
20
- const role = String(input.role || '').trim();
21
- if (!Object.values(StableActorRoles).includes(role)) {
22
- throw new Error('Stable actor role must be professional or personal.');
23
- }
24
15
  const rawContact = String(input.contact || '').trim();
25
16
  const normalizedContact = input.contactKind === 'email'
26
17
  ? rawContact.replace(/^mailto:/i, '').replace(/\s+/g, '').toLowerCase()
@@ -28,31 +19,39 @@ export function buildStableActorIdentifier(input) {
28
19
  if (!normalizedContact) {
29
20
  throw new Error(`Stable actor ${input.contactKind} is required.`);
30
21
  }
31
- return `urn:multibase:${multibase58MultihashSha3_256(normalizedContact)}:${role}`;
22
+ return input.contactKind === 'email'
23
+ ? normalizeSameAsHash(normalizedContact)
24
+ : normalizeTelephoneHash(normalizedContact);
32
25
  }
33
26
  /**
34
- * Builds a portal-specific DID binding that carries the stable actor payload.
35
- * The portal root can change while the final actor payload remains identical.
27
+ * Builds a portal-specific employee DID binding. The stable actor payload and
28
+ * the canonical ISCO-08/FHIR-v3 role remain distinct path components.
29
+ *
30
+ * @deprecated Use `buildProfessionalDidWeb` for organization employees.
36
31
  */
37
32
  export function buildPortalActorDidWeb(input) {
38
33
  const portalDidWeb = String(input.portalDidWeb || '').trim();
39
- const match = /^urn:multibase:(z[^:]+):(professional|personal)$/.exec(String(input.actorIdentifier || '').trim());
34
+ const match = /^urn:multibase:(z[^:]+)$/.exec(String(input.actorIdentifier || '').trim());
35
+ const role = String(input.role || '').trim();
40
36
  if (!portalDidWeb.startsWith('did:web:')) {
41
37
  throw new Error('Portal actor binding requires a did:web root.');
42
38
  }
43
39
  if (!match) {
44
40
  throw new Error('Portal actor binding requires a stable actor URN.');
45
41
  }
46
- return `${portalDidWeb}:actor:multibase:${match[1]}:${match[2]}`;
42
+ if (!role) {
43
+ throw new Error('Portal actor binding requires a canonical role.');
44
+ }
45
+ return `${portalDidWeb}:employee:${match[1]}:${role}`;
47
46
  }
48
47
  /**
49
48
  * Extracts the stable actor URN from a canonical GDC portal actor DID.
50
49
  * This is extraction, not reversal of the contact hash.
51
50
  */
52
51
  export function stableActorIdentifierFromDidWeb(didWeb) {
53
- const match = /:actor:multibase:(z[^:]+):(professional|personal)(?::|$)/.exec(String(didWeb || '').trim());
52
+ const match = /:employee:(z[^:]+):[^:]+(?:[:]|$)/.exec(String(didWeb || '').trim());
54
53
  if (!match) {
55
54
  throw new Error('DID does not contain a canonical GDC actor binding.');
56
55
  }
57
- return `urn:multibase:${match[1]}:${match[2]}`;
56
+ return `urn:multibase:${match[1]}`;
58
57
  }