Skip to content

v1.37.28 — the parts of a shared record nobody could see - #838

Merged
MBombeck merged 11 commits into
mainfrom
release/v1.37.28
Aug 24, 2026
Merged

v1.37.28 — the parts of a shared record nobody could see#838
MBombeck merged 11 commits into
mainfrom
release/v1.37.28

Conversation

@MBombeck

Copy link
Copy Markdown
Owner

Eleven parts of a shared record were selectable and invisible. Someone ticked "Lab values", sent the link to their doctor, and the page showed nothing — while the same link's PDF download carried them. The data had been arriving on the page all along, from the same aggregator the PDF uses.

The eleven now render: identity, emergency data, lab results, GLP-1 therapy, logged doses, illness episodes, visits, immunizations, family history, mood and cycle.

An empty section now says which kind of empty it is. Not selected stays absent entirely, because naming a section is itself a disclosure. Selected with nothing recorded says so. Selected but switched off in the owner's account says that instead, so a recipient does not read an empty card as an empty life. The module verdict had to be carried beside the payload to make this possible: the aggregator ANDs the selection and the module map and returns the same null either way.

Two corrections to the original framing, both established rather than assumed. Insurance cannot be one of the eleven — it is refused at share-link creation with a 422 naming the leaf, so no link can carry it. Logged medication doses were the one that was missed: they reached the FHIR download and neither the page nor the report, so it was the only control whose effect a person could see only by opening the bundle in another program.

A live defect found while mirroring the PDF's content. Three surfaces built a visit's kind label by interpolating an enum member into a key space whose entries are lower case. EncounterKind is ROUTINE; the key is routine. So a routine appointment printed as encounters.kind.ROUTINE in a document a practice files and on a phone's lock screen. The enum-derived i18n guard gained an arm for key spaces whose mapping is a function rather than a prefix, which is the shape that made this invisible.

The guard. DISPOSITIONS is keyed by StructuredLeafId, so an eighteenth leaf fails typecheck until someone writes down whether it renders or why it cannot. Per leaf it renders the real view over a real fixture, then re-renders with that leaf removed from the selection and the payload untouched, and asserts it is gone. Five break-proofs confirmed red then restored, plus a sixth that renders all six locales and fails on any dotted key path in reader-visible text.

Also here

GET /api/insights/derived takes a windowDays parameter. The ceiling is 90, which is the last day before the read leaves the bucketed rollup tier for an unbounded scan; beyond it the request is refused rather than clamped. A test proves a longer window still goes through the canonical recovery resolution — without it an account with a worn band double-counts every night, and it would look right in every fixture.

The wellness-score value object is modelled in the published contract. It was an open record documented for one metric, which is why a client team read the contract, built what they could see, and reported two fields as missing that were in the payload.

Numbers

Bundle 3477 of 3500. Per-route unchanged to the byte: /page 450, /insights/mood 450, /insights 437, /measurements 435. Every new component is server-rendered, so this family ships no new client JavaScript.

Gate: typecheck, lint, format, knip, openapi:check, 22,121 tests, build, bundle-check.

Three surfaces built the label key by interpolation — the doctor-report
PDF's visit table, the daily digest's upcoming-visit line and the
clinician share view. `EncounterKind` is `ROUTINE` and the bundle leaf is
`routine`, so all three rendered `encounters.kind.ROUTINE` verbatim: into
a document a practice files, onto a lock screen, and onto the page.

Route them through `encounterKindLabelKey`, a literal switch beside the
existing `encounterKindLabel`, so the mapping is written down once.

The enum-derived i18n guard could not have caught this: it only knows
key spaces of the form `prefix + member`, and this one is not. It gains a
second arm for spaces whose mapping is a function, with `encounters.kind`
as its first entry. Break-proof: point one arm of the resolver back at
the uppercase key and six locale cases go red.
A leaf on a share link can be absent from the payload for two reasons
that look identical downstream: the owner shared it and recorded
nothing, or they shared it and the domain is switched off on their
account. The aggregator ANDs the selection and the module map and
returns the same null either way, so the recipient could not tell an
empty section from one that never had a chance to carry anything.

`loadShareViewData` now resolves the whole module map — it needed the
`doctorReport` key from it anyway — and returns `unavailableLeaves`: the
leaves the link DOES carry whose owning module is off. Only selected
leaves appear, because a withheld leaf's module state is not the
recipient's business in any direction.

The map is deliberately NOT handed to the aggregator. The third argument
is the frozen selection and there is no fourth, which is what keeps this
surface from growing an options object that widens what it asks for; the
resolver memoises its reads per request, so the aggregator resolving its
own map again costs no round-trip.

The module-gate suite moves off `isModuleEnabled` with it, and gains
three cases for the new verdict, including the one that proves an
unshared leaf is never named.
Someone ticks "Lab values" in the selection, sends the link, and the
doctor opens a page that says nothing about lab values — while the PDF
download from the same link prints them. Eleven of the seventeen
structured leaves were like that: the control existed, the data reached
the page object, and no component read it. The person had every reason
to believe they had shared something they had not.

Rendered, in catalogue-group order, which is also the PDF's:

  - emergency information, framed and first, as it is page one there;
  - personal details (name, date of birth, gender, height);
  - lab values, with the reference window each reading was judged
    against, printed as the source report printed it;
  - GLP-1 therapy and the logged-dose ledger;
  - conditions and illnesses, visits, immunizations;
  - family history, mood, the menstrual cycle.

What each shows follows the PDF, which has already settled what belongs
in a section and in what order. The layout does not: a six-column table
becomes a labelled block, a four-column one becomes a row with its
detail composed onto the value side.

Absence is stated, never implied. A leaf the link does not carry renders
nothing at all — the recipient was never promised it. A leaf on the link
with nothing behind it renders its heading and says so, and one whose
domain is switched off on the owner's account says that instead. This
extends what the health-profile section has always done per fact to the
section level, and the glucose, medication and allergy cards adopt it
too; the measurement groups deliberately stay silent, because a group
card stands for up to seventeen leaves and twelve empty ones is noise.

Every card now asks the frozen selection directly through `LeafScope`
rather than inferring consent from the presence of data. The aggregator
already applies the selection, but a section that reads only "is there
data" trusts a gate it cannot see.

Three rows on the emergency card are composed from other leaves — severe
allergies, the drug list, chronic conditions. Each appears only when its
own leaf is on the link: printing "Not recorded" for a leaf the owner
withheld states an absence in the record where the truth is an absence
in the share, and on that card it is the dangerous direction to get
wrong.

The glucose card's heading was "Lab values", which was the wrong name
for it even before the actual lab results arrived; it is the glucose
group's own label now.

`INSURANCE` gets no renderer and must not get one: the create route
refuses the leaf outright, so no link can carry it.
Eleven leaves stayed invisible for eleven releases because nothing
connected the two lists. The picker-to-aggregator guard was satisfied and
kept being satisfied; the page was not on either end of it.

`DISPOSITIONS` is `Record<StructuredLeafId, …>`, so an eighteenth
structured leaf fails `pnpm typecheck` until someone writes down whether
it renders or why it cannot. Per leaf the suite then renders the real
view over a real fixture and asserts the marker appears; re-renders with
that leaf alone removed from the frozen selection, payload untouched, and
asserts it is gone; and for a refusal, reads the list that enforces it
rather than trusting the note.

Once over the whole set: a carried leaf with nothing behind it says so, a
leaf whose module is off says something else, and a leaf the link never
carried says nothing at all.

The `share-downloads` row is asserted here too. Its test id had zero
references anywhere in the tree, so the two buttons a practice clicks
could have lost a link, pointed at the wrong path, or vanished from a
documents-only share unnoticed.

Break-proofs, each confirmed red then restored: drop a section element
(that leaf's render case); gate `LeafSection` on data instead of scope
(thirteen withholding cases); collapse the switched-off arm (the
distinction case); empty `SHARE_LINK_FORBIDDEN_LEAVES` (the refusal
case); add an eighteenth leaf (typecheck).
Both routes were exempt from the contract as `shareLink` — "authenticated
by the URL credential, for a recipient with no account". That reason
holds for the page a person opens. It does not hold for two routes whose
whole purpose is to hand a practice a file it files into another system,
which is exactly the audience a contract has. `/c/{token}/d/{id}` was
published all along, one route away in the same tree.

Each entry states the gate it shares with the page, the frozen selection
it can never exceed, the flat 404 that covers every miss class including
a documents-only link, and the 20/h per-link bucket. The FHIR entry says
plainly that it is a download and not a REST face, and names the two
resource families the bundle deliberately omits.

The create-request description claimed "there is no FHIR or other
machine-readable face behind a share token", which stopped being true
when these routes landed. It now describes what a link actually serves
and records that the insurance leaf is refused outright.

The coverage guard gains the other half of its staleness check. It caught
an exemption for a route that had disappeared, and never the reverse — an
exemption left behind after the route was published, which is how these
two would have stayed on the list while the contract already carried
them. Break-proof: re-add either entry and it goes red naming it.
A reading judged against the window its own report printed, beside a
saved band that states different limits, is two answers about one
number. The PDF names both — the range in the column, the saved band in
a footnote under the table — and says in as many words that showing one
of them is a partial answer. The page carried only the first.

Both fit on the value line here, so there is no footnote to place.
Break-proof: force the divergence branch off and the case goes red.
Most labels on this page are resolved from a runtime value, not written
as a literal: an illness type, a lifecycle, a visit kind, a vaccine slug,
a blood-type constant, a cycle phase, a GLP-1 side-effect tag. The
call-site guard cannot see any of them, and the enum-derived guard can
only cover a space whose members it can enumerate from a source. A key
that does not exist therefore renders as its own dot notation, in front
of a doctor, with every other guard green — which is exactly how
`encounters.kind.ROUTINE` reached three surfaces.

One fixture with every section populated, six renders, one assertion:
nothing in the reader-visible text may look like a dotted key path.

Break-proof: put the interpolated visit-kind key back and all six go red
naming it.
`GET /api/insights/derived` accepts `windowDays`, bounded to 1..90, and
threads it to the dispatcher. Omitting it changes nothing: every engine
keeps its own default, so a caller that asks for nothing reads exactly
what it read before.

Ninety is where the tier this route promises runs out. The rollup router
opens its WEEK floor at 91 days, and the baseline engines cannot compose
a spread from WEEK buckets, so a 91-day request falls through to a raw
`measurement.findMany` with no row cap — for a densely sampled type that
is a full history scan, and the analytics-read budget allows 120 of them
a minute per account. The trailing `series` is capped at 30 points
regardless, so past the ceiling a wider window would only broaden the
mean behind the value. Out of range is a 422 rather than a clamp.

An unhonoured window is visible rather than silent. `provenance.windowDays`
is the window the engine actually used and `coverage.historyDays` the days
that actually backed it, so a three-week record answering a thirty-day
request reports both numbers instead of implying coverage it does not
have. `HEALTH_SCORE` composes fixed per-pillar windows and reports the
widest; a new suite freezes that it is the only metric that does, so an
arm added later cannot quietly drop the parameter.

Recovery keeps its canonical resolution under the wider window. A worn
band stamps the wake morning and the computed proxy the night before, and
only `resolveCanonicalRecovery` pairs them; a test seeds forty such
nights, asks for sixty days, and holds the answer to forty nights with a
flat trend — a bypass leaves eighty rows and a false climb.

Contract: model `WellnessScoreValue` field for field and publish it. The
envelope's `value` has to stay an open record because one route serves
eighteen differently-shaped metrics, so nothing can `$ref` the shape and
it went out undocumented — `series`, `daysInWindow` and `asOf` were on
the wire with no way to find them short of inspecting a live response. It
is registered in the forced-components slot instead, held against the
runtime interface by a test, and the fields it does not cover are named
in the schema comment so the remaining gap is known rather than invisible.
@MBombeck
MBombeck merged commit 3ce5411 into main Aug 24, 2026
23 checks passed
@MBombeck
MBombeck deleted the release/v1.37.28 branch August 24, 2026 17:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant