@cosyte/terminology 0.0.11 → 0.0.12

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,183 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.0.12
4
+
5
+ ### Patch Changes
6
+
7
+ - db98da6: Repository PHI commit-gate only, with no runtime impact: the sweep now walks `scripts/`, so the scanner is under its own scan, and any run that had targets and read none of them refuses instead of printing its clean line.
8
+
9
+ Two false-green doors, both the same shape. The gate reported clean over something it never looked
10
+ at. Both were pre-existing, both were found by the refuters on the previous change to this scanner,
11
+ and every reading below was taken back to back on a clone of this repository at `7e68603`.
12
+
13
+ DOOR 1: THE SCANNER NEVER SCANNED ITSELF
14
+
15
+ `scripts/` was under no scan root. The recogniser's own patterns, the allow-list this scanner refuses
16
+ to run without, and the override log it points a developer at all live there, so the one directory
17
+ guaranteed to hold PHI-shaped text was the one nothing enumerated. Measured: `scripts/planted.ts`
18
+ carrying a dashed SSN and an off-domain address exited **0** `OK: no hits` in all-mode, while naming
19
+ the same file directly reported both at exit **1** over the same bytes.
20
+
21
+ The fix is the declaration and nothing else. Both completeness rules already read `SCAN_ROOTS`: the
22
+ per-root observation rule and the reconciliation against `git ls-files`. Adding `scripts` to that one
23
+ list widens the walk, the per-root floor and the reconciliation together, with no new machinery, and
24
+ each of the three is pinned separately rather than assumed to have followed.
25
+
26
+ The scope was re-derived and this root is not shaped like `test/`. `git ls-files scripts` returns 8
27
+ paths and they are not all TypeScript: two `.ts`, three `.mjs`, two `.sh`, one `.txt`. So it is the
28
+ first root partly outside the source-literal view, and the two halves of the previous widening land
29
+ unevenly: `.ts` and `.mjs` get the raw floor and the escape-decoded view, `.sh` and `.txt` get the
30
+ raw floor only. That is the right answer for a `.txt`, which is its own document, and a residual for
31
+ a `.sh`, which can spell a value through a shell escape. Recorded rather than guarded, and pinned by
32
+ an anti-fabrication case so the boundary is visible instead of inferred.
33
+
34
+ No exemption was needed and none was added. All 8 files were measured against both recognisers, raw
35
+ and decoded, before the root was declared: zero hits, so the widening lands green on its own bytes
36
+ rather than on a new carve-out. The scanner is now under its own scan, so a real-looking value
37
+ written into a comment in it reds the gate. That is the intended pressure.
38
+
39
+ DOOR 2: A BYPASS WITH NO PATH SCANNED ZERO FILES AND PASSED
40
+
41
+ `--allow-fixture <path>` seeds the positional path set, so the flag means "scan this, but allow it"
42
+ rather than a silent no-op. With no positional path the whole target list was that one path, the
43
+ bypass subtracted it, and the run printed `[phi-scan] OK: no hits` at exit **0** over zero files:
44
+ byte-identical stdout and exit code to the genuine clean sweep on the same tree. Measured with the
45
+ path logged in the override file exactly as this scanner's own rejection message instructs, so the
46
+ false green is reached by following the printed remedy.
47
+
48
+ It is fixed with the rule this repository already had, at the scope of the whole invocation, and not
49
+ with a new mechanism. That rule refuses a sweep that observed nothing under a root; the refusal now
50
+ also covers a run that observed nothing at all. A zero-target sweep is a refusal at exit 2, never a
51
+ pass. The per-root tier does not reach the path or staged modes, which enumerate no root, and the
52
+ whole-invocation tier says nothing about any individual root, so neither subsumes the other and both
53
+ are kept.
54
+
55
+ A denominator is still not the answer. Printing "0 files scanned" and exiting 0 is the same defect
56
+ with better telemetry, because a count counts the targets that did exist. The distinction that
57
+ matters is existence against observation, and the term this rule reads is never printed as a count
58
+ and is never compared to the number read.
59
+
60
+ One legitimate zero is named rather than inferred: a staged run whose commit touches nothing in
61
+ scope, which is the pre-commit hook's ordinary markdown-only case. A staged run whose in-scope files
62
+ were all withdrawn by a bypass had work and did none of it, and refuses exactly like a path run does.
63
+
64
+ Deliberately unchanged: the pre-commit route's scope predicate, which admits neither `test/**` nor
65
+ `scripts/**` and whose widening changes what a developer's commit is blocked on. No structured
66
+ detector was added, so a green sweep still means "no SSN or email shapes found", never "no PHI".
67
+
68
+ - 7e68603: Repository PHI commit-gate only, with no runtime impact: the all-mode sweep now walks `test/` as well as `src/`, reads each TypeScript source as the document its string literals spell rather than only as raw bytes, and reconciles what it read against `git ls-files` instead of vouching for itself.
69
+
70
+ `pnpm phi-scan` with no arguments is what CI runs. It walked `src` and nothing else, and the
71
+ pre-commit route's scope predicate admits `src/**.ts` plus `test/fixtures/**`, a directory that has
72
+ never existed in this repository. **So every tracked file under `test/` was enumerated by neither
73
+ route.** Measured on `d97a3de`: `git ls-files test/ | wc -l` is **50**, and all 50 are `.ts`. Back to
74
+ back on that sha, a dashed SSN written to `test/planted.ts` exited **0** `OK: no hits` in all-mode
75
+ while naming the same file directly exited **1** over the same bytes.
76
+
77
+ **The scope was re-derived here rather than ported, and it came out different from every sibling's.**
78
+ There is no `PID|` literal anywhere in this tree, so the residual lists that drove the sibling repos
79
+ describe those repos and not this one. Here the whole corpus is inline TypeScript, and exactly one
80
+ file carries violator shapes.
81
+
82
+ **Enumerating the files buys the SSN/email floor and nothing else, which is why the widening is
83
+ two-sided.** Both recognisers match raw file text, which assumes the file IS the document. That holds
84
+ for a fixture that is its own file and breaks for one that is a string literal, because a literal can
85
+ spell any character as an escape. Every fixture here is a `.ts` literal, so admitting 50 more files
86
+ without the other half would have carried the blind spot into all of them instead of closing it.
87
+ `decodeSourceLiterals` therefore scans the decoded document **in addition to** the raw text, never
88
+ instead, for source containers only. Proved red before and green after in `src/` as well as `test/`,
89
+ so it is not merely a consequence of the new root. Only values the raw pass did not itself report are
90
+ added, deduped against **that pass's own hits** rather than against the raw text: both recognisers
91
+ are word-boundary anchored, so a value can sit in the file as a substring the raw pass correctly
92
+ declines to report, and comparing against the text discarded a real escape-spelled SSN.
93
+
94
+ **An escaped backslash is consumed first.** A doubled backslash in source is one literal backslash,
95
+ so a doubled backslash followed by `u002D` decodes to a backslash and that text, not to a dash; a
96
+ naive global regex replace of the four-hex-digit escape gets that backwards. That ordering rule is
97
+ exact, but it is a statement about the decoder and **not** a guarantee about hits, and it is written
98
+ that way deliberately.
99
+
100
+ **The view is not a literal parser, and both directions of that are recorded rather than glossed.**
101
+ It reports on text that is not an escape at all, in a `String.raw` template and in any comment
102
+ quoting an escape sequence, so the value named can be one the document does not contain. It also
103
+ misses two ordinary spellings that do evaluate to a dashed SSN: string concatenation, and a line
104
+ continuation. Recording a false-positive class is only honest if the printed remedy works, and it did
105
+ not: the allow-list's `ids` set was declared and consulted nowhere, so the only way to clear an SSN
106
+ hit was to edit the source, which is the "gate whose own remedy leaves it refusing" shape this file
107
+ refuses in four other places. The floor's dashed-SSN check now consults it, as a whole-value match
108
+ against a reviewed committed declaration, never a pattern. All of it is pinned by tests.
109
+
110
+ **The sweep is now reconciled against an independent enumeration.** The per-root observation rule
111
+ from the previous change is a floor of ONE file, so declaring `test` a root would otherwise be
112
+ satisfied by any single file under it while the other 49 went unread: the same defect one level down.
113
+ A printed denominator is not the remedy and was refuted as one, because a count is derived from the
114
+ walk and therefore agrees with the walk by construction. Reconciling against `git ls-files` is the
115
+ only version that can disagree. **Three limits previously recorded as open are closed by it, for
116
+ tracked files:** a directory missing from inside a root, a root that is itself a symlink to a
117
+ directory (which the lexical path attribution used to satisfy), and the floor of one. All three
118
+ printed `OK: no hits` at exit 0 before and refuse at exit 2 now, naming the unread paths.
119
+
120
+ **Both rules are kept and neither subsumes the other:** a root with zero tracked files reconciles
121
+ vacuously and still has to starve. **What it deliberately does not claim** is recorded rather than
122
+ glossed: an untracked file under a root is walked, read and scanned, so it cannot hide PHI, but its
123
+ absence is invisible to both rules. The markdown skip and the gitignore filter are shared with the
124
+ walk, so the rule can never demand a file the walk would refuse to read. A tracked gitlink under a
125
+ root is demanded like any other tracked path and the printed remedy does not really fit it, which is
126
+ recorded as an edge rather than guessed at. And `git check-ignore` reports nothing for a path in the index
127
+ (measured), so a gitignore pattern does not excuse a tracked file, which is pinned because the
128
+ opposite is the intuitive guess and would open a hole a one-line `.gitignore` could drive through.
129
+
130
+ **One file is exempt from the shape passes, by path.** This scanner's own suite carries a dashed SSN
131
+ and two non-test-domain addresses on purpose, as the positive half of its tests, and sweeping it
132
+ would red the gate forever. The exemption is a path list and must stay one, because an extension rule
133
+ cannot tell a file that carries violator literals on purpose from one that carries them by accident.
134
+ Allow-listing the values instead is refused: the allow-list's email entry is global, so declaring
135
+ that domain would switch the detector off for the whole corpus while the suite's own positive case
136
+ asserts that address is reported. **The exemption is applied at the scan, not at the enumeration**, so
137
+ the file is still walked, still read, and still counts as observed and reconciled. **And it is scoped
138
+ to the sweep**: naming that file directly still reports its hits, because an unscoped exemption
139
+ deleted a detection the base had, which is "instead of" where this work may only be "in addition to".
140
+ The anti-vacuity control scans its exact bytes under another name and gets a hit.
141
+
142
+ **The retired-root refusal is now inert for its only entry and is kept anyway.** `test/fixtures` sits
143
+ under the new `test` root, so the refusal cannot fire from this repository's configuration. That is
144
+ the silencing its own remedy promises, and the outcome is strictly stronger: a corpus arriving there
145
+ is walked and scanned rather than refused as unaccountable, and a dangling link there is now refused
146
+ by name where the previous guard could not see it at all. The machinery is general, so it is
147
+ exercised against a copy of the scanner with a root retired outside every scan root, rather than left
148
+ as an untested claim.
149
+
150
+ **Two pre-existing minors are folded in.** Exit 1 is reserved for hits, and a directory the walk
151
+ cannot list (a non-directory root, or an unreadable directory at any depth) and an absent or
152
+ unreadable allow-list all used to reach node's default handler and exit **1**, printing a stack trace
153
+ under the code a caller reads as a verdict about PHI. All are exit **2** now, derived from this
154
+ scanner's own stated contract rather than ported, because siblings disagree with each other on this
155
+ exact case. Separately, the two direction-specific `invertGem` messages were pinned with a substring
156
+ match against this repository's own rule; both messages contain both direction tokens, so the pin was
157
+ weak twice over, and they are now pinned by whole-message equality. A third minor carried by siblings
158
+ was measured **not open here**: the staged route already runs `--diff-filter=AMTU`, so an unmerged
159
+ path is already enumerated and refused.
160
+
161
+ **Deliberately unchanged:** the `--staged` scope predicate. A staged file under `test/` outside
162
+ `test/fixtures/` is therefore scanned by CI and not by the pre-commit hook. Nothing regressed, since
163
+ that path was in neither route before, but the two sets now overlap differently and the pre-commit
164
+ one is narrower. Widening it changes what a developer's commit is blocked on, which is a decision
165
+ about the hook rather than the walk. **And no structured detector was added:** a green sweep still
166
+ means "no SSN/email shapes found", never "no PHI".
167
+
168
+ Tests run on throwaway repositories, so no violator is written into the committed corpus, except the
169
+ one exempt file that already carried them. Several are red against the superseded scanner, and the
170
+ anti-fabrication and anti-vacuity controls exist so that no case proves merely that a rule never
171
+ fires.
172
+
173
+ - d97a3de: Rewrote every em dash out of the package's prose and added a CI gate that keeps them out.
174
+
175
+ The em dash is banned across every Cosyte surface, and until now this package carried 1,327 of them: in the README, in the documentation pages, and in the `src/` JSDoc that compiles into the published type declarations and renders on hover in a consumer's editor. All of those are rewritten with a period, a colon, a comma or parentheses. No documented behaviour, code, warning code or type changes.
176
+
177
+ Three strings a consumer can observe do change wording, in punctuation only: the two `invertGem` refusal messages, the general `invertGem` fallback message, and the `reason` on a `parseUcum` failure for an unbalanced parenthesis. Each now uses a colon where it used a dash. Diagnostic codes are untouched, and no code was renamed.
178
+
179
+ A new check, `pnpm check:no-emdash`, scans every tracked file and every tracked filename, and on a pull request the title, body and commit messages as well. It is zero-dependency Node so that no shell tooling can silently narrow what it reads, it assembles every banned spelling from the character's own codepoint so that it holds its own source to the same rule, and it refuses to report a clean result whenever it cannot prove it read its subject. The dated history at the foot of `CHANGELOG.md` is out of scope on purpose: those entries are byte identical to the tarballs they shipped in.
180
+
3
181
  ## 0.0.11
4
182
 
5
183
  ### Patch Changes
package/README.md CHANGED
@@ -7,19 +7,19 @@
7
7
 
8
8
  # @cosyte/terminology
9
9
 
10
- > A **zero-dependency terminology engine** for US healthcare code systems — FHIR-shaped, BYO-data,
10
+ > A **zero-dependency terminology engine** for US healthcare code systems: FHIR-shaped, BYO-data,
11
11
  > and it **never fabricates a code**.
12
12
 
13
13
  `@cosyte/terminology` is **not a wire parser**. It mirrors the FHIR **Terminology Module**
14
14
  (`$translate`, `$lookup`, `$validate-code`, `$expand`, …), operating over **consumer-supplied** FHIR
15
15
  resources. It is the sibling engine `@cosyte/transform` and, later, the parsers' code-system
16
- recognition consume. It ships the **engine, and no code-system release** — SNOMED CT, CPT, LOINC,
16
+ recognition consume. It ships the **engine, and no code-system release**: SNOMED CT, CPT, LOINC,
17
17
  UMLS/RxNorm, and VSAC value sets are strictly bring-your-own. It is not content-free, though: what it
18
18
  _does_ bundle, including the UCUM unit table, the code-system _identities_ (OID ↔ canonical URI), the
19
19
  SNOMED CT concepts the crosswalk resolver names and RxNorm's relationship and term-type names, is
20
20
  listed with its copyright under [What is bundled](#what-is-bundled).
21
21
 
22
- > **Status:** pre-alpha (`0.0.x`), **published on npm.** The **engine is complete** — every
22
+ > **Status:** pre-alpha (`0.0.x`), **published on npm.** The **engine is complete**, every
23
23
  > operation below ships today. The surface:
24
24
  >
25
25
  > - the code-system **identity resolver** (`resolveSystem`) and the ConceptMap **`$translate`** engine
@@ -29,17 +29,17 @@ listed with its copyright under [What is bundled](#what-is-bundled).
29
29
  > - **ValueSet** binding (`compose` / **`$expand`** / **`$validate-code`**), subsumption read from the
30
30
  > release's own hierarchy;
31
31
  > - **UCUM** unit validation and canonicalization (`validateUcum` / `ucumEqual`, recognition only, **no
32
- > magnitude conversion**) — the official UCUM functional-test suite is the conformance gate;
33
- > - the **crosswalk resolvers** — the CMS **ICD-9↔ICD-10 GEMs** (`loadGems` / `applyGem`,
32
+ > magnitude conversion**): the official UCUM functional-test suite is the conformance gate;
33
+ > - the **crosswalk resolvers**: the CMS **ICD-9↔ICD-10 GEMs** (`loadGems` / `applyGem`,
34
34
  > public-domain) and the NLM **SNOMED CT → ICD-10-CM complex map** (`loadComplexMap` /
35
- > `applyComplexMap`, BYO — no SNOMED CT refset bundled), never inverted;
35
+ > `applyComplexMap`, BYO: no SNOMED CT refset bundled), never inverted;
36
36
  > - the **RxNorm drug graph** (`loadRxNormGraph` + `ingredientsOf` / `genericFor` / `brandsFor` /
37
37
  > `doseFormsOf` / `consistsOf` / `resolveNdc` / `approximateMatch`) over a **BYO** RxNorm RRF release,
38
38
  > edges read in RxNorm's documented direction and never inverted, an absent `RXCUI`/NDC typed, never
39
39
  > fabricated.
40
40
  >
41
41
  > **Bring your own data.** The engine is whole; **no code-system release is bundled.** It runs over
42
- > the FHIR resources and standard releases _you_ supply — SNOMED CT, CPT, LOINC, UMLS/RxNorm, the CMS
42
+ > the FHIR resources and standard releases _you_ supply: SNOMED CT, CPT, LOINC, UMLS/RxNorm, the CMS
43
43
  > GEM files, the NLM complex-map refset and VSAC value sets are all bring-your-own, under whatever
44
44
  > terms their steward sets. The content packs that would change that (RxNorm Prescribable, ICD-10-CM)
45
45
  > are not bundled either. For the things that **are** bundled, and under whose copyright, see
@@ -72,7 +72,7 @@ const map = loadConceptMap(myFhirConceptMapJson); // a standard FHIR R4 ConceptM
72
72
  const result = translate({ system: "http://loinc.org", code: "2160-0" }, map);
73
73
 
74
74
  if (result.unmapped) {
75
- // Typed, surfaced outcome — carries any group.unmapped fallback mode. Never a guessed target.
75
+ // Typed, surfaced outcome: carries any group.unmapped fallback mode. Never a guessed target.
76
76
  } else {
77
77
  for (const m of result.matches) {
78
78
  m.target; // a frozen Coding, drawn verbatim from the map
@@ -89,7 +89,7 @@ if (result.unmapped) {
89
89
  import { loadCodeSystem, lookup, validateCode } from "@cosyte/terminology";
90
90
 
91
91
  // Load a consumer-supplied release: RRF (RxNorm/UMLS), CSV (LOINC), fixed-width
92
- // (ICD-10-CM order file — see ICD10CM_ORDER_FILE_FIELDS), or a FHIR CodeSystem JSON.
92
+ // (ICD-10-CM order file, see ICD10CM_ORDER_FILE_FIELDS), or a FHIR CodeSystem JSON.
93
93
  const cs = loadCodeSystem({ format: "fhir", resource: myFhirCodeSystemJson });
94
94
 
95
95
  const hit = lookup(cs, "2160-0"); // $lookup
@@ -97,48 +97,48 @@ if (hit.found) {
97
97
  hit.display; // the preferred display, verbatim from the release
98
98
  hit.status; // deprecated / header-not-billable / obsolete / … carried, never presented clean
99
99
  } else {
100
- hit.code; // "TERM_CODE_UNKNOWN" — never a fabricated display
100
+ hit.code; // "TERM_CODE_UNKNOWN": never a fabricated display
101
101
  }
102
102
 
103
- validateCode(cs, "2160-0").valid; // $validate-code — true iff present; never a guessed true
103
+ validateCode(cs, "2160-0").valid; // $validate-code: true iff present; never a guessed true
104
104
  ```
105
105
 
106
106
  ## Validate and compare UCUM units
107
107
 
108
- Recognition and canonicalization only — **no magnitude conversion** (`5 mg/dL` → `mmol/L` needs the
108
+ Recognition and canonicalization only: **no magnitude conversion** (`5 mg/dL` → `mmol/L` needs the
109
109
  analyte's molar mass, a clinical computation this engine refuses).
110
110
 
111
111
  ```ts
112
112
  import { validateUcum, ucumEqual } from "@cosyte/terminology";
113
113
 
114
114
  const v = validateUcum("mmol/L");
115
- v.valid; // true — v.canonical is a stable canonical descriptor
116
- validateUcum("mg/dl/").valid; // false — code "TERM_UCUM_INVALID", never a guessed "nearest" unit
115
+ v.valid; // true: v.canonical is a stable canonical descriptor
116
+ validateUcum("mg/dl/").valid; // false: code "TERM_UCUM_INVALID", never a guessed "nearest" unit
117
117
 
118
- ucumEqual("N", "kg.m/s2"); // true — same unit, different spelling
119
- ucumEqual("mmol/L", "mmol.L-1"); // true — annotations inert, operators normalized
120
- ucumEqual("mg", "g"); // false — different units (this is not conversion)
121
- ucumEqual("Cel", "K"); // false — a special (non-linear) unit is never equated with a linear one
118
+ ucumEqual("N", "kg.m/s2"); // true: same unit, different spelling
119
+ ucumEqual("mmol/L", "mmol.L-1"); // true: annotations inert, operators normalized
120
+ ucumEqual("mg", "g"); // false: different units (this is not conversion)
121
+ ucumEqual("Cel", "K"); // false: a special (non-linear) unit is never equated with a linear one
122
122
  ```
123
123
 
124
124
  The UCUM table is the official `ucum-essence.xml` (version 2.2), and it **is bundled with this
125
- package** — embedded verbatim in the published build and parsed at runtime. It is copyright
125
+ package**: embedded verbatim in the published build and parsed at runtime. It is copyright
126
126
  ©1999–2024 Regenstrief Institute, Inc., reproduced under the UCUM Copyright Notice and License
127
127
  (<https://ucum.org/license>); the engine ships no derivative of it and passes the official UCUM
128
- functional-test suite. The full notice ships in the package at `vendor/ucum/NOTICE.md` — see
128
+ functional-test suite. The full notice ships in the package at `vendor/ucum/NOTICE.md`: see
129
129
  [What is bundled](#what-is-bundled).
130
130
 
131
- > **Case-sensitive (c/s) mode only.** UCUM's normative interchange mode — the one FHIR/HL7 bind to —
131
+ > **Case-sensitive (c/s) mode only.** UCUM's normative interchange mode (the one FHIR/HL7 bind to)
132
132
  > is case-sensitive (`m` = metre, `M` = mega, `Pa` = pascal, `pA` = picoampere). The
133
133
  > case-**insensitive** (c/i) spelling variant is **not** supported; a c/i-only string returns a typed
134
- > `TERM_UCUM_INVALID` (fail-safe — never silently reinterpreted). Magnitude conversion is likewise a
134
+ > `TERM_UCUM_INVALID` (fail-safe, never silently reinterpreted). Magnitude conversion is likewise a
135
135
  > deliberate non-goal (recognition and canonicalization only).
136
136
 
137
137
  ## Navigate the RxNorm drug graph
138
138
 
139
139
  Load a **bring-your-own** RxNorm RRF release and walk the ingredient / brand / clinical-drug /
140
140
  dose-form graph. Relationships are read in RxNorm's documented direction (`RELA` is the relationship
141
- the **second** `RXCUI` has to the **first**); navigation follows only authored edges — the engine
141
+ the **second** `RXCUI` has to the **first**); navigation follows only authored edges: the engine
142
142
  never synthesizes an inverse, and never fabricates a concept, edge, or NDC.
143
143
 
144
144
  Authored edges only means the graph you get is RxNorm's, including where its shape surprises you.
@@ -182,7 +182,7 @@ ingredientsOf(graph, "314076"); // an SCD → { found: true, targets: [] }, beca
182
182
  consistsOf(graph, "314076"); // → the SCDC, the first of the two hops to the ingredient
183
183
  genericFor(graph, "104377"); // an SBD → its SCD, via the authored tradename_of edge
184
184
  resolveNdc(graph, "00000000001"); // { resolved: true, rxcui: "314076", status: "active", asOf: "RXNORM_2026AA" }
185
- ingredientsOf(graph, "99999999"); // { found: false, code: "TERM_RXNORM_UNKNOWN_RXCUI" } — never a guess
185
+ ingredientsOf(graph, "99999999"); // { found: false, code: "TERM_RXNORM_UNKNOWN_RXCUI" }, never a guess
186
186
  ```
187
187
 
188
188
  The engine ships **no** RxNorm release; you supply it (the public-domain Current Prescribable
@@ -202,19 +202,19 @@ _names_, listed under [What is bundled](#what-is-bundled).
202
202
  - **Liberal load, conservative assertion.** Malformed input degrades to a typed diagnostic; a 1:many
203
203
  mapping returns the full candidate set, never collapsed to one.
204
204
  - **Value-free diagnostic messages.** Nothing you configured and nothing your release or resource
205
- contained reaches a `message`, `detail`, `reason` or `err.stack` — not a column name, not a
206
- canonical URI, not a unit atom — at any length. What sits beside a message is a locus: a line
205
+ contained reaches a `message`, `detail`, `reason` or `err.stack` (not a column name, not a
206
+ canonical URI, not a unit atom) at any length. What sits beside a message is a locus: a line
207
207
  number, a column count, an index path into your own resource (`compose.include[2]`), or a fixed
208
208
  token naming which file of a release it came from. The
209
209
  **objects** are a different matter: a typed `unknown`/`unmapped` outcome names the code, unit or
210
- NDC you asked about, so log the `code` and the locus rather than the whole result — a code _in
210
+ NDC you asked about, so log the `code` and the locus rather than the whole result: a code _in
211
211
  patient context_ can be PHI.
212
212
  - **Zero runtime dependencies. Dual ESM + CJS.** Node stdlib only; built with `tsup`, validated with
213
213
  `attw`. A release you load is frozen, its indexes included: a code system's concepts, a GEM's or a
214
214
  complex map's entries and a drug graph's concepts, edges and NDCs are each a read-only **view** of a
215
215
  map rather than the map itself, so nothing holding one can add, delete or clear an entry, whichever
216
216
  way it tries. A view is not a `Map` instance, so a model carrying one cannot be `structuredClone`d
217
- or posted to a worker — copy out what you need (`new Map(cs.concepts)`). The bundled unit table is frozen deeply, atom by atom and definition by
217
+ or posted to a worker: copy out what you need (`new Map(cs.concepts)`). The bundled unit table is frozen deeply, atom by atom and definition by
218
218
  definition, because it is shared with the engine's own reducer; its two lookup maps refuse `set`,
219
219
  `delete` and `clear` by name, and an entry forced past that through `Map.prototype` changes nothing
220
220
  the engine reads, because `parseUcum` resolves an atom by scanning the frozen atom array rather than
@@ -227,14 +227,14 @@ _names_, listed under [What is bundled](#what-is-bundled).
227
227
  The engine bundles **no code-system release**. What it _does_ bundle includes the following, named
228
228
  with its copyright:
229
229
 
230
- - **The UCUM unit table** — `ucum-essence.xml`, version 2.2, revision-date 2024-06-17. The Unified
230
+ - **The UCUM unit table**: `ucum-essence.xml`, version 2.2, revision-date 2024-06-17. The Unified
231
231
  Code for Units of Measure (UCUM) is copyright ©1999–2024 Regenstrief Institute, Inc., all rights
232
232
  reserved, and is reproduced **verbatim** under the UCUM Copyright Notice and License
233
233
  (<https://ucum.org/license>). It is embedded byte-for-byte in the published build and parsed at
234
234
  runtime; no modified or derivative copy is distributed. The UCUM Specification is provided "as is"
235
- **without warranty of any kind** — see the License for the full disclaimer. The complete notice
235
+ **without warranty of any kind**: see the License for the full disclaimer. The complete notice
236
236
  ships with this package at `vendor/ucum/NOTICE.md`.
237
- - **Code-system identity facts** — the OID ↔ canonical-URI pairings (`SYSTEM_IDENTITIES`). These
237
+ - **Code-system identity facts**: the OID ↔ canonical-URI pairings (`SYSTEM_IDENTITIES`). These
238
238
  identify the systems; they are not any system's code list.
239
239
  - **The SNOMED CT concepts the crosswalk resolver names**, each with its description: the four
240
240
  map-category concepts in `MAP_CATEGORIES` (`447637006`, `447638001`, `447639009`, `447640006`) that
@@ -243,7 +243,7 @@ with its copyright:
243
243
  ICD-10-CM codes, appear in the API documentation examples, which are distributed in the compiled
244
244
  declaration files and in the build's sourcemaps. SNOMED CT is copyright
245
245
  © International Health Terminology Standards Development Organisation. **No SNOMED CT release or
246
- refset is bundled** — you supply the map rows under your own SNOMED CT licence.
246
+ refset is bundled**: you supply the map rows under your own SNOMED CT licence.
247
247
  - **The RxNorm relationship and term-type names** the drug-graph API is written against: the
248
248
  relationship names in `RELA` / `RELA_INVERSE` (`has_ingredient`, `tradename_of`, …), and the
249
249
  term-type codes in `TERM_TYPES`, each with its name as published by the U.S. National Library of
@@ -257,6 +257,6 @@ no claim about whether any particular use of these materials is permitted to you
257
257
 
258
258
  ## License
259
259
 
260
- MIT © Cosyte — this package's own code. The bundled third-party materials above stay under their own
260
+ MIT © Cosyte: this package's own code. The bundled third-party materials above stay under their own
261
261
  copyright and licences; the `LICENSE` file that ships with this package carries the MIT text and a
262
262
  third-party notices section naming each of them.