Skip to content

ADR-0047: A missing PPE material is data, not prose, and a curated field never displaces its span

Status: Accepted Date: 2026-09-10

Decisions

D-108 The client carries PPE items with their nullable material, and states the absence as a fact about this document

material === null renders as a stated absence — this SDS names no material — rather than as a conditional note about what the app does when a document is silent. The same shape D-51 uses for a missing revision date, end to end.

D-109 A curated field that describes a Source Span never displaces it, and where the two disagree the span is what a person reads

material exists to make absence representable. It is not the thing displayed. A component that renders the field instead of the span has turned a curator's note into the safety answer.

Context

D-52 requires that where an SDS names no PPE material, the generic span is displayed verbatim and the absence of a material is stated. What the app rendered was a conditional policy note on every PPE field:

ข้อความ PPE ข้างต้นแสดงตามที่เอกสาร SDS เขียนไว้ทุกคำ ถ้าเอกสารไม่ได้ระบุชนิดวัสดุ…

"If the document does not specify the material…" — a statement about what the app does in that case, never a statement that this document is in it. It is therefore never false, and D-52 is never satisfied.

Why the requirement exists. The glossary asks for PPE per item with its material — "nitrile gloves" rather than "gloves". A Handler reading "wear gloves", not told the document named no material, will assume any glove does. For acetone the wrong glove fails. This is the nitrile-versus-latex case from ADR-0002 arriving through omission rather than through paraphrase, and D-52 exists to close it.

The gap was in transit, not in the model. The API's PpeItem already carries material String?, with the guards written into the schema — never approximated, and no field for a material taken from an external standard (D-53). The snapshot serialiser already emits it. The client model has no per-item PPE structure at all: curatedFields is a flat map of spans. So the absence was representable on the server, crossed the wire, and was discarded before anything could render it — which left prose as the only option, and prose could only be conditional.

A correction made while deciding this. The first proposal here was a boolean — does this span name a material? — on the reasoning that storing "nitrile" puts a derived assertion into a structured field. That was argued without reading the API model, which had already chosen the nullable string and reasoned it. The server is right: material is set by a curator while reading the span they are selecting (D-50), which is a Curation-time judgement under review rather than a runtime derivation, and that is the line D-02 draws. A boolean would also have discarded the reason span, which is Safety-Critical Content with the correct shape already.

D-109 exists because the worry behind the boolean was real even though the conclusion was wrong. A field that describes a span can drift from it, and the answer is to fix which one is authoritative rather than to refuse the field.

Decision

The client carries PPE items and their material. A null material renders as a stated absence. The span remains what a person reads, and the field never replaces it.

Rejected options

  • A boolean flag instead of the material — rejected after checking the model. It solves the same problem with less information, loses the reason span, and its stated advantage does not survive: the field is curator-set at selection time, not derived at display time.
  • Fix the rendering only — not available. A component cannot state an absence the client has no way to know about.
  • Narrow D-52 to drop the "stated" requirement — rejected. ADR-0025 already conceded the per-item aspiration; conceding the absence statement as well leaves "wear gloves" reading as complete guidance, which is the failure the rule was written for.
  • Infer the absence from the span's text at display time — rejected. That is display-time safety logic, which D-50 puts at Curation, and it would be a regex deciding what a Handler wears.

This ruling may not be re-decided

If a change contradicts this ADR: stop and raise it. Do not implement over it.

Specifically: do not render material in place of its span, which is what D-109 forbids; do not add a field for a material drawn from an external standard, which D-53 forbids and the schema already refuses; and do not restore a conditional note in place of a stated absence, because a conditional is what nine days of this defect looked like.

Consequences

What becomes easy. The absence is now the same kind of thing as a missing revision date — a fact in the data with one display, rather than a policy explained in prose. It is also testable: a generic PPE span must render a stated absence, and a span with a material must not.

What this cost. One boolean-shaped judgement per PPE span at Curation, by a curator already reading the span in order to select it. Against D-58's ninety to a hundred and eighty minutes of review per document, it does not register.

What it revealed about the seams. The Seam 2 test asserting the note's presence passed against a span naming nitrile, because a conditional note is true either way. A test can assert the presence of the wrong thing and be green forever; this one was, until the fixture was varied deliberately. That is worth remembering the next time a passing assertion is taken as coverage.

Coverage

UpstreamLanded inEvidenceNote
R-12D-108the nitrile-versus-latex failure, reached here by omitting the absence rather than by paraphrasing the spanADR-0002 landed R-12 in D-02; ADR-0046 cites it for the same failure reached by a third route