Appearance
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
| Upstream | Landed in | Evidence | Note |
|---|---|---|---|
| R-12 | D-108 | the nitrile-versus-latex failure, reached here by omitting the absence rather than by paraphrasing the span | ADR-0002 landed R-12 in D-02; ADR-0046 cites it for the same failure reached by a third route |