diff --git a/.changeset/wild-pears-refresh.md b/.changeset/wild-pears-refresh.md new file mode 100644 index 0000000..097c203 --- /dev/null +++ b/.changeset/wild-pears-refresh.md @@ -0,0 +1,11 @@ +--- +"@cosyte/transform": patch +--- + +Consume `@cosyte/hl7` from the registry at `0.0.10` instead of a vendored tarball at `0.0.1`, and prove by measurement that nothing this package emits moved with it. No emitted FHIR value changes, no diagnostic changes, no issue code is added, renamed or removed, and no mapping is touched: `src/` is byte-for-byte unchanged by this release. + +The parser was nine published versions behind, pinned to a `pnpm pack` archive committed into the repository, which meant the version the tests exercised was watched by no dependency route and could only move by hand. It is now a plain devDependency resolved through the lockfile, the archive is deleted, and no copy of the library source remains in the tree. `@cosyte/fhir` is unaffected and stays vendored, for the one reason it always was: the registry does not have it. + +Because this package sits in a PHI dataflow and its promise is never a confident wrong FHIR value, a green suite was not accepted as evidence on its own. Every HL7 v2 fixture the repository carries was collected mechanically into a frozen corpus, four synthetic inputs were authored for the malformed, truncated, empty and wrong-version classes, and all of it was transformed once on each side of the bump. The compared surface, meaning the FHIR output plus every diagnostic in emission order, is byte-identical across all 128 members. Both captures are committed beside the record so the comparison can be rerun rather than believed. + +Two suites are added and none is removed or relaxed. One asserts the fail-safe rule class by class for malformed input, and sweeps every diagnostic the whole corpus raises to confirm each carries a registry-static message at a positional locator and no field content. The other records, as a call that compiles and runs, two upstream capabilities whose earlier absence shaped this package: an ADT message builder and a typed composite encoder both exist now. Neither is adopted here. The visit-carrying `Patient` plus `Encounter` output stays deferred, and the reverse path keeps building its fields by hand, because a refresh whose whole claim is that nothing moved cannot also change what is emitted. diff --git a/.gitattributes b/.gitattributes index 9495c33..1bd59f1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,9 +1,12 @@ -# The vendored `@cosyte/hl7` and `@cosyte/fhir` tarballs are third-party archives that this -# repository consumes for dev and test (ADR 0001; refresh them with `pnpm vendor:refresh`). -# Declaring them keeps git from ever treating them as text, and it is the declaration that +# The vendored `@cosyte/fhir` tarball is a third-party archive that this repository consumes for +# dev and test (ADR 0001; refresh it with `pnpm vendor:refresh`). `@cosyte/hl7` used to sit beside +# it and no longer does: it is a registry devDependency resolved through `pnpm-lock.yaml`, so +# nothing vendored carries it. The glob is kept rather than narrowed to one filename because it is +# the shape `vendor/` has always had, and a rename must not silently escape it. +# Declaring the archive keeps git from ever treating it as text, and it is the declaration that # `scripts/check-no-emdash.mjs` partitions on: a DEFLATE stream can hold the em dash's UTF-8 -# bytes by coincidence, `vendor/cosyte-hl7-0.0.0.tgz` does, and there is no edit that removes -# a byte from someone else's compressed stream. +# bytes by coincidence, and there is no edit that removes a byte from someone else's compressed +# stream. # # THIS FILE IS NOT A SILENCER. The em-dash gate REFUSES any `binary` declaration outside # `vendor/`, so widening the exclusion means editing that script deliberately rather than diff --git a/CLAUDE.md b/CLAUDE.md index b13a916..a76d54c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -43,10 +43,14 @@ as a trap is clinical-safety content. `TRANSFORM_NO_V2_MESSAGE_EMITTED` instead of returning an empty success. **The usage cells behind those rows are asserted, NOT extracted** (the pass that wrote them had no network egress), so re-extract before trusting or widening them. The `Patient` + `Encounter` visit-carrying ADT is - **deferred, not dropped**: the vendored parser exports no ADT assembly entry point (measured, zero - occurrences in its `dist/`), and hand-assembling PID + PV1 here would invert the tier split. Every - measurement, the refusal set, and the deferral: - `documentation/agent-notes.md#the-reverse-direction-and-what-it-does-not-claim`. Phase **8 + **deferred, not dropped**, and **▶ THE UPSTREAM REASON FOR THE DEFERRAL IS GONE WHILE THE SHAPE IS + STILL NOT BUILT**: the parser exported no ADT assembly entry point when it was deferred, and + `@cosyte/hl7` `0.0.10` exports `buildAdt` (measured by a call that compiles and runs, in + `test/upstream-capabilities.test.ts`). Nothing else about the deferral changed, so do not read + "the builder exists" as "the shape is ready": hand-assembling PID + PV1 here would still invert + the tier split, and the IG grounding is still owed. Every measurement, the refusal set, and the + deferral: `documentation/agent-notes.md#the-reverse-direction-and-what-it-does-not-claim`; the + dependency refresh that took the measurement: `documentation/hl7-refresh/record.md`. Phase **8 (profiles)** and deeper terminology remain deferred. - **Never quote a version here.** This line read "not yet published to npm" for several releases after first publish, which is part of why a `VERSION` constant stuck at `"0.0.0"` shipped unnoticed. @@ -63,11 +67,15 @@ as a trap is clinical-safety content. never recall: `npm view @cosyte/fhir version`. **Visibility and publish state are independent**; never infer one from the other. Why: `documentation/agent-notes.md#publish-state-and-the-stale-claim-inside-it`. -- **Consumes two cosyte siblings** (`@cosyte/hl7`, `@cosyte/fhir`) as **peer dependencies**, vendored - as `pnpm pack` tarballs in `vendor/` for dev/test (ADR 0001 + umbrella ADR 0008): refresh with - `pnpm vendor:refresh`. Pinned shas: hl7 `46d50eb`, fhir `7a099b2`. **They are not both unpublished, - and that wording was stale**; `@cosyte/hl7` is on the registry and it is the `fhir` peer alone that - makes this package uninstallable. **Third-party runtime deps: zero.** +- **Consumes two cosyte siblings** (`@cosyte/hl7`, `@cosyte/fhir`) as **peer dependencies**, and the + two are no longer consumed the same way for dev/test. **`@cosyte/hl7` is a plain registry + devDependency** resolved through `pnpm-lock.yaml`; **▶ DO NOT RE-VENDOR IT**, and do not add + `vendor/cosyte-hl7-*.tgz` back, because a second copy of that library in this tree is what + removing it was for. **`@cosyte/fhir` alone stays a vendored `pnpm pack` tarball** in `vendor/` + (ADR 0001 + umbrella ADR 0008; refresh with `pnpm vendor:refresh`, pinned sha `7a099b2`), for one + reason and one only: the registry does not have it. **They were never both unpublished, and that + wording was stale**; it is the `fhir` peer alone that makes this package uninstallable. + **Third-party runtime deps: zero.** ## Tech Stack (the shared `@cosyte/*` standard) diff --git a/docs-content/installation.md b/docs-content/installation.md index 8795338..3a03533 100644 --- a/docs-content/installation.md +++ b/docs-content/installation.md @@ -37,9 +37,10 @@ the package is not on the registry at all; dropping it and installing this packa succeed, but leaves both peers genuinely absent at runtime, so the first conversion you attempt fails. -Until `@cosyte/fhir` publishes, this repo's own tests build against **vendored tarballs** in -`vendor/` (regenerated by `pnpm vendor:refresh`): the same interim mechanism `@cosyte/mllp` uses, -replaced by a real npm install once the peer is on the registry. +This repo's own tests install `@cosyte/hl7` from the registry, exactly as a consumer does. Until +`@cosyte/fhir` publishes, that one peer alone builds from a **vendored tarball** in `vendor/` +(regenerated by `pnpm vendor:refresh`): the same interim mechanism `@cosyte/mllp` uses, replaced by +a real npm install once the peer is on the registry. ## Smoke test diff --git a/documentation/agent-notes.md b/documentation/agent-notes.md index 5ab156d..f912b81 100644 --- a/documentation/agent-notes.md +++ b/documentation/agent-notes.md @@ -136,15 +136,24 @@ wrong field number it fires on every conversion rather than once. `Do\S\e\F\Public` and reads back identically, where a hand-joined `"Do^e"` string would have become two components. Nothing in `src/reverse` writes a `^` or a `~`. -**Deferred, and why: the `Patient` + `Encounter` visit-carrying ADT.** The vendored `@cosyte/hl7` -this repository actually builds and tests against exports no ADT assembly entry point (no -`buildAdt`, and no `buildOru`/`encodeComposite` either): measured on the installed package, zero -occurrences in both `dist/index.d.ts` and `dist/index.mjs`. Assembling PID + PV1 by hand instead +**Deferred, and why: the `Patient` + `Encounter` visit-carrying ADT.** The `@cosyte/hl7` this +repository built and tested against at the time (`0.0.1`, vendored) exported no ADT assembly entry +point (no `buildAdt`, and no `buildOru`/`encodeComposite` either): measured on the installed package, +zero occurrences in both `dist/index.d.ts` and `dist/index.mjs`. Assembling PID + PV1 by hand instead would be this package inventing a message-structure layout that the parser tier owns, which is the opposite of the tier split ADR 0001 draws. The mapping itself is not the blocker and the deferral is -dated and written out in `documentation/decisions/`. **Re-measure the vendored package before -picking it up again** (`pnpm vendor:refresh` is a by-hand job, and the tarballs are unwatched by both -Dependabot routes): the entry point may exist in a later parser release than the one vendored here. +dated and written out in `documentation/decisions/`. + +**▶ THAT BLOCKER IS GONE, AND THE SHAPE IS STILL NOT BUILT: DO NOT READ ONE FROM THE OTHER.** +`@cosyte/hl7` `0.0.10` exports `buildAdt(event, init)` with typed `AdtPatient` / `AdtVisit` / `AdtEvent` +bodies, and `buildOru` and `encodeComposite` with it. `test/upstream-capabilities.test.ts` is the +measurement, kept as a call that compiles and runs rather than a sentence: it builds an ADT, parses +it back, and reads the values out again. What that removes is the UPSTREAM reason for the deferral, +nothing else. The visit-carrying `Patient` + `Encounter` output is still not implemented here, it is +still owed the same IG grounding as every other shape, and the refresh that measured the builder +deliberately did not start it: a dependency refresh whose whole claim is "no emitted FHIR value +moved" cannot also add a message graph. The full record of the refresh, the capability inventory it +enumerated and where the shape is routed next: `documentation/hl7-refresh/record.md`. ## Publish state, and the stale claim inside it @@ -318,9 +327,16 @@ cannot hold `security-events: write`) has been observed. repo showed **zero** open Dependabot PRs, which meant nothing was looking, not that nothing was stale. Two limits are written into that file rather than left to be discovered: automatic **security** update PRs are a repo setting that currently reads `disabled`, and **Dependabot never resolves a -`file:vendor/*.tgz` specifier**, so the vendored `@cosyte/hl7` and `@cosyte/fhir` tarballs -- the -versions the tests actually exercise -- are unwatched by both the `file:` route and the peer-dep route -and stay a `pnpm vendor:refresh` job by hand. +`file:vendor/*.tgz` specifier**, so a vendored tarball -- the version the tests actually exercise -- +is unwatched by both the `file:` route and the peer-dep route and stays a `pnpm vendor:refresh` job by +hand. + +**That limit now covers ONE sibling rather than two, and the halves diverged deliberately.** +`@cosyte/hl7` is a registry devDependency resolved through `pnpm-lock.yaml`, so the `npm` ecosystem +route in `.github/dependabot.yml` reaches it like any other package and the by-hand job no longer +applies to it. `@cosyte/fhir` is still `file:vendor/cosyte-fhir-0.0.0.tgz` and still unwatched, and it +stays that way until the registry has it. **Do not read the two as one dependency any more**: a +sentence that treats them together was true when both were vendored and is not true now. ## The `attw` guardrail, in full @@ -1055,13 +1071,16 @@ an exemption that silently grows to cover the generated half. The archive is als **on-disk canary**: a scan that reports it clean has gone blind rather than found good news, and the gate refuses rather than reporting that as a pass. -**`vendor/cosyte-hl7-0.0.0.tgz` (1), declared `binary` in `.gitattributes`.** A DEFLATE stream can -hold `E2 80 94` by coincidence and this one does; there is no edit that removes a byte from someone -else's compressed archive, and the tarball is third-party content this repo consumes rather than -authors. `vendor/cosyte-fhir-0.0.0.tgz` is declared alongside it and carries none today. -**`.gitattributes` is not a silencer**: the gate REFUSES any `binary` declaration outside -`vendor/`, so widening the exclusion means editing the gate deliberately. And a declaration about a -file's BYTES says nothing about its NAME, so tracked filenames are scanned whatever that file says. +**`vendor/cosyte-fhir-0.0.0.tgz`, declared `binary` in `.gitattributes`, and it is the ONLY declared +path now.** A DEFLATE stream can hold `E2 80 94` by coincidence; there is no edit that removes a byte +from someone else's compressed archive, and a vendored tarball is third-party content this repo +consumes rather than authors. The fhir archive carries none today. **The measurement that used to +sit here was taken on `vendor/cosyte-hl7-0.0.0.tgz` (1 occurrence)**; that path is gone, because +`@cosyte/hl7` became a registry devDependency, so the declared set is one path rather than two and +the count it carried retired with it. **`.gitattributes` is not a silencer**: the gate REFUSES any +`binary` declaration outside `vendor/`, so widening the exclusion means editing the gate +deliberately. And a declaration about a file's BYTES says nothing about its NAME, so tracked +filenames are scanned whatever that file says. **Nothing else was skipped.** `CLAUDE.md` (47) and this file (68) were swept like any other tracked file. The banner at the top of this file protects its **claims** from being softened; it is not a diff --git a/documentation/hl7-refresh/record.md b/documentation/hl7-refresh/record.md new file mode 100644 index 0000000..8e94b51 --- /dev/null +++ b/documentation/hl7-refresh/record.md @@ -0,0 +1,614 @@ +# The `@cosyte/hl7` refresh record: 0.0.1 to 0.0.10 + +What moved, what was measured, and what a reviewer reruns to disbelieve any of it. + +The deliverable here is not the version number. It is the evidence that the version number is safe: +this package sits in a PHI dataflow and its promise is never a confident wrong FHIR value, so a +nine-versions-stale parser cannot be bumped on the strength of a green suite alone. + +- **Date:** 2026-08-21 +- **Baseline commit:** `76984bba9dbb852d2854fd82ebd68422b8ad98f3` (`main` at the time; the working + branch was cut from it) +- **Before:** `@cosyte/hl7@0.0.1`, consumed as `file:vendor/cosyte-hl7-0.0.0.tgz` +- **After:** `@cosyte/hl7@0.0.10`, consumed as a registry devDependency through `pnpm-lock.yaml` +- **Toolchain:** unchanged. `node >=22.0.0`, `packageManager: pnpm@10.0.0`, both as declared before. +- **Headline result:** the compared surface over 128 corpus members is **byte-identical** before and + after. No emitted FHIR value moved. No diagnostic moved. No call site needed adapting. +- **One test expectation was edited, and it tracks no upstream difference.** It is a repository + INVENTORY count that this refresh's own required end state moved: deleting the vendored tarball + took the tree from two declared-binary paths to one. Before and after form, cause and authority: + section 5b. The count of adopted diagnostic differences is still **zero** and section 5 is + unchanged by it. + +> **On dash characters in this file.** This repository bans the em dash in every spelling. One +> upstream diagnostic quoted below contains an EN dash in a version range; it is written here as +> `[en]` so this file carries neither character, and the verbatim bytes are in +> `documentation/hl7-refresh/surface-pre-refresh.json`, which is where a reader should check the +> quotation rather than trusting this prose. + +--- + +## 1. The adopted version, from the registry + +The version to adopt was taken from the registry, not from any expectation recorded elsewhere. + +``` +$ pnpm view @cosyte/hl7 version +0.0.10 +``` + +The full metadata query (`pnpm view @cosyte/hl7 --json`) reports, among the rest: + +- `"version": "0.0.10"`, `"dist-tags": { "latest": "0.0.10" }` +- ten published versions, `0.0.1` through `0.0.10`, `0.0.10` published `2026-08-07T02:35:40.560Z` +- `"dist".."integrity": "sha512-bUVmjMvtqlNw5yv0ozwo+BRQ9Ej141t6wmpzbeN9anrEJhmZCuVJV+OBBznKqdpbDz8vqqvpT5DFEA8+v9F1rA=="` +- `"engines": { "node": ">=22.0.0" }`, which matches this package's own floor + +**Discrepancy: none.** The umbrella's interface card for `hl7` carried `v0.0.10` as its expectation +and the registry agrees, so nothing had to be resolved in the registry's favour. The card value is +a generated expectation either way; the query above is the evidence. + +**Nine versions stale, confirmed rather than assumed.** The vendored tarball resolved to `0.0.1` +(`pnpm install` reported `+ @cosyte/hl7 0.0.1`), and `0.0.1` is the first of the ten published +versions. + +## 2. The required end state, and which branch it started from + +**The checkout started from the in-tree-copy branch.** `@cosyte/hl7` was a `file:` devDependency +pointing at `vendor/cosyte-hl7-0.0.0.tgz`, a `pnpm pack` tarball of the sibling repository at +commit `46d50eb775dc6576cec8ca5a2315720a65cb7418`. It is now a registry devDependency (`^0.0.10`), +and the tarball is deleted. Declaring it as a registry dependency IS the end state this refresh +exists to reach; it is not a new external dependency, and no other package was added. + +**Half one, the lockfile: exactly one version, and it is the adopted one.** + +``` +$ grep -n "cosyte/hl7" pnpm-lock.yaml +27: '@cosyte/hl7': +191: '@cosyte/hl7@0.0.10': +2007: '@cosyte/hl7@0.0.10': {} +``` + +Line 191 onward: + +``` + '@cosyte/hl7@0.0.10': + resolution: {integrity: sha512-bUVmjMvtqlNw5yv0ozwo+BRQ9Ej141t6wmpzbeN9anrEJhmZCuVJV+OBBznKqdpbDz8vqqvpT5DFEA8+v9F1rA==} + engines: {node: '>=22.0.0'} +``` + +The integrity hash is the one the registry query in section 1 reported, so the lockfile confirms the +adopted version rather than defining it. One `@cosyte/hl7` entry, no second version arriving +transitively, and therefore no `pnpm.overrides` or `resolutions` entry was written. None was needed +and none would have been acceptable. + +**Half two, the tree: no in-tree copy of the library anywhere in the checkout.** Both a tracked-path +inspection and a filesystem inspection, and the filesystem one covers the whole checkout: + +``` +$ git ls-files vendor +vendor/cosyte-fhir-0.0.0.tgz + +$ git ls-files | grep -i hl7 +documentation/hl7-refresh/surface-pre-refresh.json +scripts/hl7-refresh-capture.ts +scripts/hl7-refresh-collect-corpus.ts +test/_support/hl7-baseline-corpus.json +test/_support/hl7-corpus-probe.ts +test/_support/hl7-malformed-classes.ts + +$ find . -not -path './node_modules/*' -not -path './.git/*' -not -path './dist/*' -iname '*hl7*' +./scripts/hl7-refresh-capture.ts +./scripts/hl7-refresh-collect-corpus.ts +./documentation/hl7-refresh +./test/_support/hl7-malformed-classes.ts +./test/_support/hl7-corpus-probe.ts +./test/_support/hl7-baseline-corpus.json +``` + +**The exclusion is stated so a reviewer can tell it from an oversight.** The filesystem inspection +excludes `node_modules/`, `dist/` and `.git/`. `node_modules/@cosyte/hl7` is the INSTALLED +dependency: resolving the package from the registry is what materializes it, so a literal +whole-tree reading would make the end state unreachable by its own first half. `dist/` is untracked +build output. `.git/` holds history, which is where the pre-refresh source is deliberately preserved +(section 4). Nothing else is excluded, and every path the inspection did return is this refresh's +own regression harness, named after the dependency rather than a copy of it. + +**`@cosyte/fhir` is untouched and still vendored.** It is absent from the registry, so it has no +registry route; that is a separate, disclosed problem and this refresh did not touch it. + +## 3. What the refresh consumed: the capability inventory and the regression plan + +Every `@cosyte/hl7` capability this package uses, one row each, with the check that establishes it +still behaves the same and the command a reviewer runs. + +The single strongest check appears in most rows and is stated once here: `hl7-refresh-capture` +transforms all 128 corpus members and writes the FHIR output plus every diagnostic, and the two +captures are byte-identical. A capability that had changed behaviour on any of the 124 fixtures +would have moved that file. + +``` +$ pnpm exec tsx scripts/hl7-refresh-capture.ts