Skip to content

Commit 54ebcc5

Browse files
committed
docs: decide R5 (na_reason) and R13 (tag normalization)
R5 -- require na_reason, hint at the template, waive it on self-explaining lines. Adopts the comment's own carve-out: template_items.conditional waives na_reason on lines whose prompt already states its condition ("if applicable: ..."), which is metadata in the same class as section 12's gate_for_phase, not a forms designer. The decision turns on rereading the comment's central complaint as a feature. The friction is load-bearing. na_reason stays FREE TEXT - deliberately unlike R4's not_performed_reason - because a picker would make the structural N/A cheap, and cheap is exactly wrong: the friction is the pressure that gets the template split. After R4 took out_of_scope and satisfied_by_other_evidence, what remains under na is "this line does not apply to this subject," which is either structural or a one-off; neither wants a vocabulary. The systemic case is answered by a derived template hint - a line that is na on every tag of its type - keyed on the repetition rather than the reason text, so junk reasons cannot defeat it. That is ADR 0009's reasoning one grain finer: an inapplicable phase is phases_na, an inapplicable line is a template that wants splitting. Option (b), requiring the reason only when siblings differ, is rejected on a ground the comment did not reach: the field tier is offline-capable, so a rule whose applicability depends on other tags' data cannot be evaluated on the device it is enforced against. R13 -- three tiers with pinned normalization. Exact match resolves silently; a normalized match is offered as a CANDIDATE, one tap, never auto-applied; a numeric-core match under a DIFFERENT PREFIX is raised as a QUESTION and never as an acceptable candidate, because the prefix states what the device is and two vendors disagreeing about it is a real finding, not a typo. Everything else stays the existing non-existent-tag warning. The normalization is pinned exactly - uppercase, strip non-alphanumerics, strip leading zeros from each digit run - on ADR 0010's reasoning about its canonical form: a matching rule that behaves differently in the core, the sidecar and an importer is worse than none. Never auto-correct and never rewrite the stored value; the ledger keeps what the vendor actually wrote, the same principle as R6's verbatim narrative and section 7's frozen value text. On CSV import candidates are a review step, never a bulk rewrite. Normalized forms are derived, not stored, and a stored column is deliberately not built ahead. Remaining open: R14, R16-R20.
1 parent a6d3a44 commit 54ebcc5

2 files changed

Lines changed: 98 additions & 5 deletions

File tree

‎docs/adr/0018-multi-vendor-startup-evidence-model.md‎

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ directly afterwards, on 2026-08-04, and the reading is recorded in
1717
[`0018-review-notes.md`](0018-review-notes.md). Every observation was confirmed.
1818
Four readings have now happened — a summary, a text-layer pass, a corrected
1919
render pass, and a full render of the valve reports and clean-water checklists.
20-
Comments **R2, R6, R7, R8, R21 and R22 are folded in below**; **R3, R4, R9–R12
21-
and R15 were decided** and appear as §4, §5 and §§13–14; **R1 proposed reversing
22-
a recommendation and was decided against**; **R5, R13, R14 and R16–R20 remain
20+
Comments **R2, R6, R7, R8, R21 and R22 are folded in below**; **R3–R5, R9–R13
21+
and R15 were decided** and appear as §4, §5, §11 and §§13–14; **R1 proposed
22+
reversing a recommendation and was decided against**; **R14 and R16–R20 remain
2323
open** for the maintainer.
2424
The Context section below is left as originally written rather than retrofitted —
2525
what the ADR knew when, and from what, is itself part of the record. Three of
@@ -156,6 +156,39 @@ is to separate the two moments:
156156
- **`check_items.na_reason`** (text) is **required when `result = "na"`**,
157157
server-enforced by the `check_items` createRule. "N/A" with no reason is the
158158
second most ambiguous mark on a paper form.
159+
160+
**It stays free text, and the friction is deliberate** (**R5**, resolved
161+
2026-08-04). R4 gave `not_performed` a closed vocabulary because its reasons
162+
recur. `na`'s do not: after `out_of_scope` and `satisfied_by_other_evidence`
163+
moved to `not_performed`, what remains under `na` is "this line does not apply
164+
to this subject" — which is either **structural** (the line should not be on
165+
this `tag_type`'s template) or a genuine one-off. Giving it a picker would make
166+
the structural case *cheap*, and cheap is exactly wrong: the friction is the
167+
pressure that gets the template fixed. A closed vocabulary here would let six
168+
identical N/As per unit, across dozens of units, become one tap and stay
169+
forever.
170+
171+
**The systemic case is answered by a template hint, not an exemption**
172+
(R5 option (c)). The template page flags a line that is `na` on **every tag of
173+
its type** in a project — "consider a template split" — derived on the fly,
174+
nothing stored. This is ADR 0009's reasoning one grain finer: an inapplicable
175+
*phase* is `phases_na`, and an inapplicable *line* is a template that wants
176+
splitting. The hint keys on the repetition, not on the reason text, so junk
177+
reasons ("n/a", "-") cannot defeat it.
178+
179+
**Carve-out, adopted:** `template_items` gains **`conditional`** (bool), and
180+
`na_reason` is **waived on flagged lines**. A prompt that already states its
181+
own condition — the source's "(if applicable: …)" lines — is self-explaining,
182+
and a typed reason there buys nothing. The `result` is still explicitly `na`;
183+
only the prose is waived. This is metadata, not a forms designer: no branching,
184+
no conditional logic engine, the same class of template flag as §12's
185+
`gate_for_phase`.
186+
187+
**Rejected — requiring the reason only when siblings differ** (R5 option (b)).
188+
It makes the capture rule depend on data the device may not have: the field
189+
tier is offline-capable, and a rule that changes based on other tags' state is
190+
unpredictable to the person it is being enforced against. Server-enforceable
191+
and always-the-same beats clever.
159192
- **A note is never an answer**
160193
([R3](0018-review-notes.md#r3--pass-with-exception-is-a-real-answer-the-state-vocabulary-has-no-home-for--decided--derived-not-a-state-adr-4)).
161194
A line carries a `result`, and separately may carry a `note`; prose in the
@@ -520,6 +553,39 @@ is invisible to a register check alone:
520553
register and warns loudly on a miss, offering "create this tag" or "fix the
521554
number." A warning, never a block: a real tag missing from the register is
522555
itself a finding worth recording.
556+
557+
**Resolution normalizes first, in three tiers** (**R13**, resolved
558+
2026-08-04). An exact-match register check reports the source's real
559+
disagreements as "non-existent tag," which is true and useless — the tag
560+
exists and the reader is being asked to spot a one-character difference.
561+
562+
1. **Exact match** — resolves silently, as today.
563+
2. **Normalized match** — offered as a **candidate** ("did you mean …?"), one
564+
tap to accept. This catches an omitted separator or zero-padding, e.g. a
565+
float tag written `LSH01130` against a register holding `LSH-01130`.
566+
3. **Numeric-core match under a different prefix** — surfaced as a **question,
567+
never a candidate to accept**. The prefix says what the device *is* (ISA
568+
convention: an on/off valve prefix is not a flow-control-valve prefix), so
569+
the source's `XV`-vs-`FCV` disagreement on the same number is two parties
570+
describing the equipment differently, not a typo. A human answers that.
571+
572+
Anything else stays the miss above.
573+
574+
**The normalization is pinned exactly**, for the same reason ADR 0010 pins its
575+
canonical form: uppercase → strip all non-alphanumerics → strip leading zeros
576+
from each maximal digit run. Unpinned, "did you mean" behaves differently in
577+
the core, the sidecar, and an importer, and a matching rule nobody can predict
578+
is worse than none.
579+
580+
**Never auto-correct, and never rewrite the stored value.** The ledger keeps
581+
what the human or the vendor actually wrote, verbatim — the same principle as
582+
R6's narrative and §7's frozen `value` text. Normalization exists to *find*
583+
candidates, never to silently agree with one. On CSV import, candidates are a
584+
**review step**, never a bulk rewrite.
585+
586+
Normalized forms are **derived, not stored**. If a stored normalized column
587+
ever becomes necessary for import performance it must be server-maintained on
588+
write and never read as authoritative — deliberately **not built ahead**.
523589
- **Self-consistency within one report.** A tag claimed **twice inside the same
524590
report, against two different descriptions**, is a warning. In the source one
525591
flow-control-valve tag is listed as two different valves and one analyzer tag

‎docs/adr/0018-review-notes.md‎

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,24 @@ content.
273273
punch-item creation (already the behavior in `check.html`) and that the
274274
turnover QC flags a failed line with no linked punch item.
275275

276-
## R5 — Required `na_reason` will be heavier than the ADR assumes · *open*
276+
## R5 — Required `na_reason` will be heavier than the ADR assumes · **DECIDED — (a) + (c) + the conditional carve-out (ADR §4)**
277+
278+
**Resolved by the maintainer, 2026-08-04: require it, hint at the template, and
279+
waive it on self-explaining lines.** The comment's own carve-out is adopted —
280+
`template_items.conditional`, waiving `na_reason` on lines whose prompt already
281+
states its condition.
282+
283+
The decision turns on something this comment framed as a cost and is better read
284+
as a feature: **the friction is load-bearing.** A closed vocabulary or a blanket
285+
exemption would make the *structural* N/A cheap, and cheap is exactly wrong —
286+
the friction is the pressure that gets the template split. So `na_reason` stays
287+
free text (unlike R4's `not_performed_reason`, whose reasons genuinely recur),
288+
the systemic case is answered by a derived template hint keyed on repetition
289+
rather than on the reason text, and only the self-explaining case is waived.
290+
291+
Option (b) is rejected on a ground the comment did not reach: the field tier is
292+
offline-capable, so a rule whose applicability depends on sibling tags' data
293+
cannot be evaluated on the device it is enforced against.
277294

278295
**ADR location:** §4, "`na_reason` required when `result = "na"`".
279296

@@ -591,7 +608,17 @@ missing rows.
591608
The remaining seven are refinements to decisions already made, not new
592609
structure. Recorded so they are not rediscovered a fourth time.
593610

594-
## R13 — Tag matching must normalize before it warns · *open*
611+
## R13 — Tag matching must normalize before it warns · **DECIDED — three tiers, pinned normalization (ADR §11)**
612+
613+
**Resolved by the maintainer, 2026-08-04**, with the comment's core accepted and
614+
one distinction added: normalization resolves separators, case and zero-padding,
615+
but it **cannot** resolve a prefix disagreement — and should not try. A
616+
normalized match is offered as a candidate; a numeric-core match under a
617+
different prefix is raised as a **question**, because the prefix states what the
618+
device is and two vendors disagreeing about it is a real finding, not a typo.
619+
The normalization itself is pinned exactly, on ADR 0010's reasoning about its
620+
canonical form: a matching rule that behaves differently in the core, the
621+
sidecar and an importer is worse than none.
595622

596623
**ADR location:** §11, "Non-existent tag warning."
597624

0 commit comments

Comments
 (0)