sos-data-utils-ts 0.2.1 → 0.2.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.2
4
+
5
+ - Add the product-neutral `speciesId7 + cardNumber20` preimage and lowercase
6
+ SHA3-256 asset-id builder shared by human and animal subject cards.
7
+ - Bind human cards implicitly to NCBI Taxonomy `9606` as `0009606`, without
8
+ displaying species or asking a person for it.
9
+ - Keep species outside every printed 21-digit card and exclude its final Damm
10
+ check digit from blockchain identity material.
11
+
3
12
  ## 0.2.1
4
13
 
5
14
  - Add the shared product-neutral Task artifact schema backed by canonical flat
package/README.md CHANGED
@@ -22,6 +22,19 @@ The shared Task authoring schema uses canonical flat FHIR-like claims from
22
22
  correlation and output claims remain optional so the same contract supports
23
23
  human work items and machine-created asynchronous jobs.
24
24
 
25
+ Subject-card index resolution uses one product-neutral 27-digit preimage:
26
+
27
+ ```text
28
+ speciesId7 + first20DigitsOfValidatedCardNumber21
29
+ ```
30
+
31
+ The final printed Damm digit is validation only and is excluded. Animal
32
+ products provide the animal's NCBI Taxonomy identifier separately from the
33
+ printed card. Human-health products use Homo sapiens (`9606`, projected as
34
+ `0009606`) implicitly and never display or request species. Only the lowercase
35
+ SHA3-256 digest of the 27 ASCII digits may cross the blockchain boundary; it
36
+ locates the active index provider and grants no data access.
37
+
25
38
  The organization-verification contract resolves policy by business sector and
26
39
  ISO country. The governed CA/US profiles are:
27
40
 
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './bff-api.js';
2
2
  export * from './organization-verification-policy.js';
3
3
  export * from './task-artifact-schema.js';
4
+ export * from './subject-card-index.js';
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './bff-api.js';
2
2
  export * from './organization-verification-policy.js';
3
3
  export * from './task-artifact-schema.js';
4
+ export * from './subject-card-index.js';
@@ -0,0 +1,41 @@
1
+ export type SubjectSpeciesId7 = string;
2
+ export type SubjectCardNumber21 = string;
3
+ export type SubjectCardIndexCanonicalNumericId27 = string;
4
+ export type SubjectCardIndexAssetId = string;
5
+ /** Canonical NCBI Taxonomy identifier for Homo sapiens. */
6
+ export declare const HUMAN_NCBI_TAXONOMY_ID: "9606";
7
+ /** Seven-digit taxonomy projection used implicitly for human-card ledger keys. */
8
+ export declare const HUMAN_SPECIES_ID7: "0009606";
9
+ /**
10
+ * Projects a positive NCBI Taxonomy identifier to exactly seven decimal digits.
11
+ *
12
+ * This helper is product-neutral and therefore accepts Homo sapiens. A
13
+ * veterinary caller remains responsible for rejecting `9606` as an animal.
14
+ */
15
+ export declare function normalizeSubjectSpeciesId7(ncbiTaxonomyId: string): SubjectSpeciesId7;
16
+ /**
17
+ * Builds the common 27-digit lookup preimage for a validated 21-digit card.
18
+ *
19
+ * The visible Damm digit is excluded because it detects transcription errors
20
+ * rather than contributing identity material. Species remains outside the
21
+ * printed number but prevents equal card payloads for different species from
22
+ * sharing one ledger key. Human callers pass `9606` (normally via the fixed
23
+ * `HUMAN_NCBI_TAXONOMY_ID` constant); they never ask a person for a species.
24
+ */
25
+ export declare function buildSubjectCardIndexCanonicalNumericId27(input: Readonly<{
26
+ ncbiTaxonomyId: string;
27
+ cardNumber21: string;
28
+ }>): SubjectCardIndexCanonicalNumericId27;
29
+ /**
30
+ * Returns lowercase hexadecimal SHA3-256 of the ASCII 27-digit preimage.
31
+ *
32
+ * Only this opaque digest crosses the blockchain boundary. The card number,
33
+ * taxonomy identifier and canonical preimage must not be written to the
34
+ * ledger. Resolving the digest locates an index provider; it grants no access.
35
+ */
36
+ export declare function buildSubjectCardIndexAssetId(input: Readonly<{
37
+ ncbiTaxonomyId: string;
38
+ cardNumber21: string;
39
+ }>): Promise<SubjectCardIndexAssetId>;
40
+ /** Validates the exact lowercase SHA3-256 hexadecimal ledger-key profile. */
41
+ export declare function isSubjectCardIndexAssetId(value: string): value is SubjectCardIndexAssetId;
@@ -0,0 +1,69 @@
1
+ // Copyright 2026 Connecting Solution & Applications Ltd under the Apache License, Version 2.0.
2
+ import { bytesToHexString } from 'gdc-common-utils-ts/utils/base-convert';
3
+ import { SHA3_256_MULTIHASH_PROFILE } from 'gdc-common-utils-ts/utils/multiformat-profile';
4
+ import { computeDammCheckDigit } from 'gdc-common-utils-ts/utils/unified-health-id';
5
+ /** Canonical NCBI Taxonomy identifier for Homo sapiens. */
6
+ export const HUMAN_NCBI_TAXONOMY_ID = '9606';
7
+ /** Seven-digit taxonomy projection used implicitly for human-card ledger keys. */
8
+ export const HUMAN_SPECIES_ID7 = '0009606';
9
+ /**
10
+ * Projects a positive NCBI Taxonomy identifier to exactly seven decimal digits.
11
+ *
12
+ * This helper is product-neutral and therefore accepts Homo sapiens. A
13
+ * veterinary caller remains responsible for rejecting `9606` as an animal.
14
+ */
15
+ export function normalizeSubjectSpeciesId7(ncbiTaxonomyId) {
16
+ const source = String(ncbiTaxonomyId || '').trim();
17
+ if (!/^\d{1,7}$/.test(source)) {
18
+ throw new TypeError('NCBI Taxonomy identifier must contain at most seven digits.');
19
+ }
20
+ const normalized = source.replace(/^0+/, '') || '0';
21
+ if (normalized === '0') {
22
+ throw new TypeError('NCBI Taxonomy identifier must be positive.');
23
+ }
24
+ return normalized.padStart(7, '0');
25
+ }
26
+ /**
27
+ * Builds the common 27-digit lookup preimage for a validated 21-digit card.
28
+ *
29
+ * The visible Damm digit is excluded because it detects transcription errors
30
+ * rather than contributing identity material. Species remains outside the
31
+ * printed number but prevents equal card payloads for different species from
32
+ * sharing one ledger key. Human callers pass `9606` (normally via the fixed
33
+ * `HUMAN_NCBI_TAXONOMY_ID` constant); they never ask a person for a species.
34
+ */
35
+ export function buildSubjectCardIndexCanonicalNumericId27(input) {
36
+ const speciesId7 = normalizeSubjectSpeciesId7(input.ncbiTaxonomyId);
37
+ const cardNumber21 = normalizeCardNumber21(input.cardNumber21);
38
+ return `${speciesId7}${cardNumber21.slice(0, 20)}`;
39
+ }
40
+ /**
41
+ * Returns lowercase hexadecimal SHA3-256 of the ASCII 27-digit preimage.
42
+ *
43
+ * Only this opaque digest crosses the blockchain boundary. The card number,
44
+ * taxonomy identifier and canonical preimage must not be written to the
45
+ * ledger. Resolving the digest locates an index provider; it grants no access.
46
+ */
47
+ export async function buildSubjectCardIndexAssetId(input) {
48
+ const canonicalNumericId27 = buildSubjectCardIndexCanonicalNumericId27(input);
49
+ const digest = SHA3_256_MULTIHASH_PROFILE.digest(new TextEncoder().encode(canonicalNumericId27));
50
+ return bytesToHexString(digest);
51
+ }
52
+ /** Validates the exact lowercase SHA3-256 hexadecimal ledger-key profile. */
53
+ export function isSubjectCardIndexAssetId(value) {
54
+ return /^[a-f0-9]{64}$/.test(String(value || ''));
55
+ }
56
+ function normalizeCardNumber21(value) {
57
+ const source = String(value || '').trim();
58
+ if (!source || !/^[0-9 -]+$/.test(source)) {
59
+ throw new TypeError('cardNumber21 must contain only decimal digits, spaces or hyphens.');
60
+ }
61
+ const digits = source.replace(/[ -]/g, '');
62
+ if (!/^\d{21}$/.test(digits)) {
63
+ throw new TypeError('cardNumber21 must contain exactly 21 digits.');
64
+ }
65
+ if (computeDammCheckDigit(digits.slice(0, 20)) !== digits.slice(20)) {
66
+ throw new TypeError('cardNumber21 has an invalid Damm check digit.');
67
+ }
68
+ return digits;
69
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sos-data-utils-ts",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Product-neutral SOS channel and BFF contract utilities",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -22,6 +22,10 @@
22
22
  "./task-artifact-schema": {
23
23
  "types": "./dist/task-artifact-schema.d.ts",
24
24
  "default": "./dist/task-artifact-schema.js"
25
+ },
26
+ "./subject-card-index": {
27
+ "types": "./dist/subject-card-index.d.ts",
28
+ "default": "./dist/subject-card-index.js"
25
29
  }
26
30
  },
27
31
  "files": [