ADR-0081 — A medication withhold is a time-bounded hold¶
- Status: Accepted
- Date: 2026-10-21
- Spec version at acceptance: 0.83
- Issues: #803 (feeds #804) — a maintainer decision of 2026-10-21; design:
docs/superpowers/specs/2026-10-21-medication-withhold-803-design.md - Derives from: principle 3 (paper-parity, §1.2: the paper "W" on a drug chart) and principle 4 (acknowledged uncertainty: "until reviewed" is an explicit value)
- Applies: ADR-0012 (additive-only evolution, the legibility twin) ·
ADR-0039 (the authored twin on every event; the cessation twin,
cessation.rs, is the no-drug-name precedent) · ADR-0043 (cross-human suppression is refused) · ADR-0047 (lines are reconciled groups) · ADR-0049 (sign-off is a commitment) · ADR-0052 (born-sealed bodies) · ADR-0053 (per-write human authorship)
Context¶
On a paper inpatient drug chart a drug in doubt is not deleted: someone writes W (withheld) with a reason, and the drug is not given while the W stands. A doctor may write it in advance, a nurse at the bedside after a pre-dose check, or a doctor pro tem until an indication has been discussed. Afterwards the drug is simply given again, with no one acting; only the pro tem kind ends with an explicit act. Cairn had only cessation, which is terminal, so a clinician who wanted to pause a drug had to cease it, or leave it and hope.
Decision¶
1. Two new event types¶
clinical.medication-withhold.asserted(schemaclinical.medication-withhold/1). Payload:medication_id(uuid string, required);reason(required, non-empty after trim);from(optional instant);until(required: an instant, or the literal string"reviewed"). Optional fields are omitted, nevernull.clinical.medication-withhold-release.asserted(schemaclinical.medication-withhold-release/1). Payload:medication_id(required);releases(a non-empty array of withhold event ids, uuid strings);reason(optional).
Both are additive with targets_other_author FALSE: a W is the withholder's own act on the thread and does not edit
or suppress anyone else's prescription (ADR-0043). No existing event type changes (ADR-0012). The legibility twins
carry no drug name, following the cessation precedent: the name lives on the thread's assertion and a copy could
disagree with a later correction of it. Withhold: Withheld medication from <from|"now"> until <until|"reviewed"> —
<reason>. Release: Released withheld medication (1 hold) or (<n> holds), plus — <reason> when given.
A release names the withholds it ends by event id, not content address. It still ends only holds its author saw,
and event ids are what the CLI prints (medication-withhold's success line, and each hold medication-list shows,
with the thread it is on) and cairn-node event <id> reads.
2. A hold is a time span; until is required; exact instants only¶
until is required and is either an instant or "reviewed", the explicit open end. A clinician who does not know when
a hold should end says so with "reviewed"; they are never asked to invent an end, and a missing field never stands in
for it (principle 4). Only an explicit "reviewed" may outlive time.
from and until are exact instants: ISO-8601 with an explicit UTC offset (the cairn_t_effective pin), and an
offset-less value is refused. This departs from the {value, precision} times elsewhere in the medication stream, on
purpose: a hold is compared against now, and a day-precision until has no defined instant of expiry. A future
from (a doctor's advance W for tonight) goes in the payload, not the envelope: a t_effective after t_recorded is
not refused at a self-asserted clock grade, but it is recorded as a clock-ceiling anomaly (db/005), and an advance hold
is not one — it would misuse the field. Without from, the hold starts at the authoring node's physical clock,
carried as t_effective; a release's instant is carried the same way. The authoring node never stamps either from
its HLC wall: a peer whose clock runs fast can push that wall hours ahead, and a "W from now" would then start in the
future. Nor does db/059: it refuses, at both doors, a withhold with neither from nor t_effective and a release
without t_effective (another client's, or a hand-built body), rather than fall back to the HLC wall.
3. Expiry is a read-time fold: no event, default resume¶
A hold is in force at now when from <= now (absent from means the event's effective time), until is
"reviewed" or until > now (strictly: at until == now the hold has ended), and no release naming it has an
effective time <= now. Instants compare as instants, whatever their offsets. Nothing stores "withheld" as a fact;
the status is computed against the node's clock. When a span ends the line reads as it did before the W, with no act by
anyone: the default is resume, never "stays held". Several holds can be in force at once; all are shown, latest
start first. A hold not yet started is shown as "W from
4. A release names withholds by event id¶
A release ends the named holds from its effective time onward. It never ends a hold it does not name, so a W that reached the node after the releaser decided cannot be ended unseen. A named hold whose span already ended is a no-op, not an error. A named hold that has not started yet is cancelled and never comes into force: a prescriber who writes "W from 20:00" and releases it at 14:00 means the dose to be given. A release that arrives by sync before the withhold it names is admitted, and ends that hold once it arrives.
5. Cessation wins over any hold¶
A standing cessation makes the line ceased, whatever holds exist (cessation is terminal; the existing rule is unchanged).
6. The hold sits beside the active/ceased status¶
MedicationStatus stays {Active, Ceased}; a row carries a list of holds (holds: Vec<MedicationHold>) beside it.
There is no Withheld variant. Existing readers treat status == Active as "on the current list, signable, ceasable";
a third variant would silently drop withheld drugs out of sign-off, which is the very failure this design avoids. A
withheld line therefore stays current and signable. A line is withheld if a hold is in force on any member thread of its
reconciled group (ADR-0047). A group reads active or ceased by ADR-0047's latest-effective-wins, so a group with a
ceased member can read active, and a withhold is refused only on a line that reads ceased. The floor and the CLI
accept a withhold on any member thread; writing it once, on the line's representative thread, is the UI gesture's
rule (#832).
7. Sign-off¶
Both event types join the sign-off commitment (ADR-0049), so a W or a release written after a sign-off makes that line's sign-off stale. Threads with no withhold events keep their commitment, so there is no mass staleness. A hold expiring changes no event and so does not stale a sign-off: the signer took responsibility for "W until 20:00", which includes what happens at 20:00. Signing a line upholds every W in force on it.
8. Structural refusals at both doors; state refusals at the local door only¶
Every refusal is legible, and where it runs depends on what it reads. A refusal that reads only the payload is
structural and runs at both doors (db/059's cairn_check_medication_withhold; at sync apply, wherever the node
can open the body), because such a body is malformed on every node: an empty reason, a missing or malformed until, a
malformed from (a JSON null included), until not after from when both are given, a withhold with neither from
nor t_effective, a bad medication_id, an empty or malformed releases, a release reason that is present but null,
blank or not a string, a release without t_effective. A refusal that reads what this node holds is clinical
state and runs at the local door only (db/059's apply functions): a withhold on a line that reads ceased, an until
not after the hold's start once an absent from is resolved to the event's time, a release naming something that is
not a withhold on the same thread (when it is a withhold on a sibling thread, the refusal names that thread), and the
thread patient guard every medication verb shares (#192). Sync apply never refuses on clinical state, because a node-local veto would fork the event set; the fold is
defined for every arrival order instead.
9. Wording¶
"Withheld" is the clinical word and means the hold only. Clinician-facing text where it meant hidden is reworded to "not shown", "left out of sign-off" or "hidden". Internal identifiers are not renamed.
Consequences¶
- A node on schema generation 58 admits the new events uninterpreted and shows a withheld drug as active. Acceptable pre-clinically (no node runs clinical care yet).
- A node with a wrong clock expires holds early or late. The list prints
until, so a human can judge. - The hold's start is the authoring node's physical clock: a node whose own clock is fast starts its holds late. The list prints the times.
- There is no administration record (MAR) and no dose slots; a time span stands in for "the slots the W was written into". The ward-level list of new withholds across patients, and any forced acknowledgement of a nurse's W, are later work or not wanted.
- Anyone may withhold; restricting it is local policy above the floor.
- The
untilvocabulary is closed. The floor checks a body by its event type, not its schema version, so a node on this generation refuses anuntilit does not know, at both doors. A new kind of end (say, "at discharge") is therefore a new event type, never a/2of this one.