19.8. Conformity assessment (ISO/IEC Guide 98-4, JCGM 106:2012)
A coverage interval says how far the measurand may lie from its estimate. It does not say what to
do with that at a limit, and that gap is where guard bands come from. The decision rule "accept
iff the estimate is below the limit" ignores the uncertainty entirely and runs a consumer's risk of
essentially 50 % at the limit; every practitioner therefore applies a margin, and the margin is
almost always a chosen factor rather than a derived one. A chosen factor cannot be checked,
cannot be shown to be too tight or too loose, and cannot shrink as evidence accumulates, so it
charges the first measurement's ignorance forever. ISO/IEC Guide 98-4 (JCGM 106:2012) is the
standard that closes the gap, and Conformity.lean is its executable face — Mathlib-free and
Float-carried like the Evidence module it builds on — with ConformityLadder.lean supplying,
over ℝ, the argument that a margin derived this way may safely tighten itself. No requirement of
the R1–R28 catalogue claims this layer; it sits beside R18 as what a coverage factor is for once
a limit is in view.
JCGM 106's vocabulary, as the module carries it. The tolerance limit T (§3.3.4) is the
specified bound on permissible values of the measurand — given, not computed. The acceptance
limit A (§3.3.8) is the bound the decision actually compares against. The guard band
g = |T - A| (§3.3.10), applied inward, is guarded acceptance: the acceptance interval sits inside
the tolerance interval and the consumer's risk falls; applied outward it is guarded rejection. The
specific consumer's risk R_C(y) (§9.5.2) is, for a measured value y with standard
uncertainty u, the probability that the measurand is past T anyway; its counterpart, the
specific producer's risk, is the probability that a value rejected at A would in fact have
conformed. Nothing here is a naked number: the limits are Quantity k values of the measurand, a
probability and a coverage factor ride distinct dimension-one kinds, so reading 1.96 as 0.95
or comparing an estimate against a limit of another kind is a type error rather than a review
finding.
Tolerance limits as roles. A Tolerance k R is an optional LowerBound k R and an optional
UpperBound k R — the directional roles of Bounds.lean, not bare endpoints — so each limit can
only ever be used from its own side, and an absent bound is none rather than an infinity.
Tolerance.admits asks whether a value conforms (the question a measurement cannot answer, which
is why the rest of the module exists), and Tolerance.guardedBy derives the acceptance limits: it
subtracts a band from an upper limit and adds it to a lower one. That direction is forced by the
role, not chosen by convention, so "applied the guard band the wrong way and turned guarded
acceptance into guarded rejection" is not a mistake a caller can make while still typechecking.
Lean code for Definition19.8.1●1 definition
Associated Lean declarations
-
structuredefined in PropertyKindCalculus/Uncertainty/Conformity.leancomplete
structure PropertyKindCalculus.Uncertainty.Conformity.Tolerance (k : PropertyKindCalculus.KindOfProperty) (R : Type) : Type
structure PropertyKindCalculus.Uncertainty.Conformity.Tolerance (k : PropertyKindCalculus.KindOfProperty) (R : Type) : Type
**The tolerance limits** (JCGM 106 §3.3.4) — the specified bounds on permissible values of the measurand, carried as `Bounds.lean` *roles* so that each endpoint can only ever be used from its own side. Either bound may be absent: a one-sided requirement is the common case (a memory budget bounds from above and not from below), and an absent bound is `none` rather than an infinity, so "unbounded below" cannot be confused with a bound whose value was lost. **These limits are IMPOSED, not asserted**, and it matters as soon as one is written down. A tolerance limit — and the guarded acceptance limit `guardedBy` derives from it — is a constraint a value must satisfy, not a claim about what some quantity holds. So the safe direction to shorten one is *inward*: `Decimal`'s `UpperBound.roundedDownAsRequirement` and `LowerBound.roundedUpAsRequirement`, and **not** the `roundedUp`/`roundedDown` pair, which is right for the asserted reading and would here buy a larger consumer's risk than the number says it buys. `Bounds` records which side an endpoint is on and cannot record which of the two readings it is; that is why both pairs exist and neither is a default.
Fields
lower : Option (PropertyKindCalculus.LowerBound k R)
The lower tolerance limit `T_L`, if there is one.
upper : Option (PropertyKindCalculus.UpperBound k R)
The upper tolerance limit `T_U`, if there is one.
Realized: Conformity.Tolerance with atMost/atLeast/ofIcc constructors (scale-gated by
OrderKind, since a limit presupposes an order to be on one side of), Tolerance.admits through
each endpoint's own directional decider, and Tolerance.guardedBy gated by DifferenceKind, the
metrological licence to form the difference at all.
The guard band g = k\,u, a quantity of the measurand's own kind. It is built through
Quantity.mul on expansionLaw, so the kind of the product is derived from a stated law — a
coverage factor is dimension one in the role of expanding an uncertainty — rather than asserted.
The input is a Dispersion and the output is not: a guard band is an expanded uncertainty, but
what it is used as is a displacement of a limit, and the change of role happens once, here.
accepts is then the decision — accept iff the estimate lies within the
guarded acceptance limits — with the estimate and its uncertainty at
their roles, so the two cannot be exchanged into an inverted rule that guards enormously on a
small uncertainty and not at all on a large one. assess packages the whole thing: an evidence
record, a tolerance and a target risk in; the factor, the band, the acceptance limits, the
decision and the risk actually run out — the shape that makes the guard band an output.
Lean code for Definition19.8.2●1 definition
Associated Lean declarations
-
defdefined in PropertyKindCalculus/Uncertainty/Conformity.leancomplete
def PropertyKindCalculus.Uncertainty.Conformity.guardBand {k : PropertyKindCalculus.KindOfProperty} (u : PropertyKindCalculus.Uncertainty.Dispersion k Float) (factor : PropertyKindCalculus.Quantity PropertyKindCalculus.Uncertainty.coverageFactor Float) (hk : k.IsRational := by rfl) : PropertyKindCalculus.Quantity k Float
def PropertyKindCalculus.Uncertainty.Conformity.guardBand {k : PropertyKindCalculus.KindOfProperty} (u : PropertyKindCalculus.Uncertainty.Dispersion k Float) (factor : PropertyKindCalculus.Quantity PropertyKindCalculus.Uncertainty.coverageFactor Float) (hk : k.IsRational := by rfl) : PropertyKindCalculus.Quantity k Float
**The guard band** `g = k·u` — a quantity of the measurand's own kind, since it is an offset applied to a limit on that measurand. Built through `Quantity.mul` on `expansionLaw`, so the kind of the product is derived from a stated law rather than asserted: a coverage factor is dimension one *in the specific role of expanding an uncertainty*, and that role is what the law records. The input is a `Dispersion` and the output is not. A guard band **is** an expanded uncertainty — which is why the body is `Dispersion.expanded` and not a second copy of `k·u` — but what it is *used as* is a displacement of a limit, and `Tolerance.guardedBy` wants a displacement. The unwrapping is the change of role, and it happens once, here.
Realized: Conformity.guardBand as Dispersion.expanded unwrapped to a Quantity,
Conformity.accepts over Tolerance.guardedBy, consumerRisk/producerRisk/riskAtLimit as the
risks actually run, and Conformity.assess returning none — a refusal, not a factor nothing
justifies — when the evidence cannot support a coverage factor at the target risk.
The band from a stated risk. factorForRisk inverts §9.5.2: to hold the specific consumer's
risk at a one-sided acceptance limit to p, the factor is k = \Phi^{-1}(1 - p) — none unless
0 < p < 1, since a risk of zero demands an infinite band and a risk of one is not a decision
rule. The factor stops being an input and becomes a consequence of a risk the operator names.
riskForFactor is its exact inverse, 1 - \Phi(k), and prices a band somebody else chose.
Both are Gaussian, which is exact only when u is known; factorForEvidence and
riskForFactorAt are the same pair at the evidence's own \nu (a Student t that is the
Gaussian at \nu = \infty, so the correction appears and disappears by itself), and
factorForRiskRectangular/riskForFactorRectangular are the closed-form pair for GUM 4.3.7's
bounded posterior, which saturates at \sqrt{3}\,u where the risk is exactly zero.
Lean code for Definition19.8.3●1 definition
Associated Lean declarations
-
defdefined in PropertyKindCalculus/Uncertainty/Conformity.leancomplete
def PropertyKindCalculus.Uncertainty.Conformity.factorForRisk (p : PropertyKindCalculus.Quantity PropertyKindCalculus.Uncertainty.probability Float) : Option (PropertyKindCalculus.Quantity PropertyKindCalculus.Uncertainty.coverageFactor Float)
def PropertyKindCalculus.Uncertainty.Conformity.factorForRisk (p : PropertyKindCalculus.Quantity PropertyKindCalculus.Uncertainty.probability Float) : Option (PropertyKindCalculus.Quantity PropertyKindCalculus.Uncertainty.coverageFactor Float)
**The coverage factor that holds the specific consumer's risk to `p`** at a one-sided acceptance limit: `k = Φ⁻¹(1 − p)`, the inverse of JCGM 106 §9.5.2 read at the limit. Naming a risk therefore *determines* the guard band; nothing is left to be chosen. `none` unless `0 < p < 1`: a risk of zero demands an infinite band, and a risk of one is not a decision rule.
Realized: Conformity.factorForRisk and riskForFactor through Sampling.normalQuantile and
normalCDF; riskForFactorAt through Sampling.studentTCDF, returning none when \nu is
NaN or below one (fewer than two indications support no statement about a tail — the same
refusal Evidence.coverageFactorOneSided makes, not a Gaussian fallback); and the rectangular pair
around rectangularFactorLimit = \sqrt{3}. The direction in which a small \nu is dangerous is
the pricing one: at \nu = 4 a band of 4.5\,u buys 5.4 \times 10^{-3}, not the
3.4 \times 10^{-6} a Gaussian reads off it.
The risk from the costs, when nobody knows what risk to ask for. Accepting a non-conforming value
costs C_a; rejecting a conforming one costs C_r; at a measured value the expected costs are
R_C\,C_a and (1 - R_C)\,C_r, so accepting is the cheaper action exactly while
R_C \le C_r/(C_a + C_r), and that break-even risk is the target to hand to
factorForRisk. The two costs must be quantities of one kind, and the
step from their ratio to a probability is an authored crossing — nothing about a ratio of two
durations makes it a probability except the decision-theoretic argument. Strongly asymmetric costs
then produce the conclusion that is usually asserted: a wrong acceptance a hundred times as costly
as a wrong rejection gives p^* \approx 1\,\% and a one-sided 2.33\,u, a derivation auditable in
a way "we used 0.9" is not.
Lean code for Definition19.8.4●1 definition
Associated Lean declarations
-
defdefined in PropertyKindCalculus/Uncertainty/Conformity.leancomplete
def PropertyKindCalculus.Uncertainty.Conformity.riskFromCosts {kc : PropertyKindCalculus.KindOfProperty} (costOfWrongAccept costOfWrongReject : PropertyKindCalculus.Quantity kc Float) (hc : kc.IsRational := by rfl) : Option (PropertyKindCalculus.Quantity PropertyKindCalculus.Uncertainty.probability Float)
def PropertyKindCalculus.Uncertainty.Conformity.riskFromCosts {kc : PropertyKindCalculus.KindOfProperty} (costOfWrongAccept costOfWrongReject : PropertyKindCalculus.Quantity kc Float) (hc : kc.IsRational := by rfl) : Option (PropertyKindCalculus.Quantity PropertyKindCalculus.Uncertainty.probability Float)
**The break-even risk implied by the two costs of being wrong.** Accepting a non-conforming value costs `costOfWrongAccept`; rejecting a conforming one costs `costOfWrongReject`. At a measured value the two expected costs are `R_C·C_a` and `(1 − R_C)·C_r`, so accepting is the cheaper action exactly while `R_C ≤ C_r/(C_a + C_r)` — which is the risk target to hand `factorForRisk`. This is how an asymmetric risk becomes a number instead of an adjective. The two costs are usually *known* even when the acceptable risk is not: a wrong acceptance that loses a whole run against a wrong rejection that adds one more unit of work is a ratio the operator can state, and it produces the one-sided high-coverage band that would otherwise be asserted. The costs are quantities of one kind `kc`, and the step from their ratio to a probability is `costRatioIsRisk` — an authored crossing, written here because nothing about a ratio of two durations makes it a probability except the decision-theoretic argument above. Requiring both costs to share `kc` is the other half: a wall-clock cost divided by a monetary one is a number with no meaning, and exactly the kind that looks fine in a spreadsheet.
Realized: Conformity.riskFromCosts, dividing through costRatioIsRisk kc hc, none unless both
costs are positive numbers.
Reading a band that already exists. readBand g u divides a deployed margin by the measurand's
own standard uncertainty — a quotient of two k-quantities, which bandReadingLaw declares to be
a coverage factor — and says what kind of thing the band is. A BandReading is one of three
constructors. coverage factor risk: a band at a few u is a coverage statement, dispersion that
shrinks as evidence accumulates, and the risk it buys is attached. systematic factor: a band at
tens of u is not — no risk target produces it under any posterior, so the margin is standing in
for a systematic the model does not carry, which does not shrink with evidence, and tightening it
as though it did is how a fleet walks into a correlated failure. unstated: the band cannot be read
at all. The two readable cases have opposite repairs, which is why the distinction is a constructor
and not a comment; systematicThreshold (6, where the Gaussian risk reaches 10^{-9}) is the
judgement they turn on, taken as an argument so that it applies uniformly. Evidence.readBand e g
is the same reading taken against a record's own evidence, priced at its \nu through
riskForFactorAt instead of at the Gaussian limit, and declared in
Evidence's namespace so the \nu cannot be dropped at the call site. BandReading.label
forgets the evidence to a BandReadingLabel — the three-valued classification a report line or a
record field states.
Lean code for Definition19.8.5●1 definition
Associated Lean declarations
-
inductivedefined in PropertyKindCalculus/Uncertainty/Conformity.leancomplete
inductive PropertyKindCalculus.Uncertainty.Conformity.BandReading : Type
inductive PropertyKindCalculus.Uncertainty.Conformity.BandReading : Type
**What a deployed margin turns out to be**, once divided by the measurand's own standard uncertainty. The two readings have opposite repairs, which is why the distinction is a constructor and not a comment.
Constructors
PropertyKindCalculus.Uncertainty.Conformity.BandReading.coverage (factor : PropertyKindCalculus.Quantity PropertyKindCalculus.Uncertainty.coverageFactor Float) (risk : PropertyKindCalculus.Quantity PropertyKindCalculus.Uncertainty.probability Float) : PropertyKindCalculus.Uncertainty.Conformity.BandReading
A coverage statement: `g = k·u` for a plausible `k`, buying the stated specific consumer's risk. This band is *dispersion*, so it shrinks as evidence accumulates.
PropertyKindCalculus.Uncertainty.Conformity.BandReading.systematic (factor : PropertyKindCalculus.Quantity PropertyKindCalculus.Uncertainty.coverageFactor Float) : PropertyKindCalculus.Uncertainty.Conformity.BandReading
Not a coverage statement. No risk target produces a factor this large, so the margin is standing in for a **systematic the model does not carry** — a term that is missing, not a measurement that is imprecise. It does not shrink with evidence, and tightening it as though it did is how a fleet walks into a correlated failure.
PropertyKindCalculus.Uncertainty.Conformity.BandReading.unstated : PropertyKindCalculus.Uncertainty.Conformity.BandReading
The band cannot be read at all — no `u` to divide by, a quotient that is not a number (an unbounded band over an unbounded `u`), a threshold that is not a number, or a `u` that nothing qualifies (a `ν` below one, which supports no statement about a tail). Not the same as a band of zero: it is a band whose meaning is unavailable, and the causes are one statement from the consumer's side, because a factor nobody can price is not a reading.
Realized: Conformity.BandReading, BandReadingLabel, BandReading.label, systematicThreshold,
readBand (pricing with riskForFactor) and Evidence.readBand (pricing with riskForFactorAt
at e.dof), both one-line calls into classifyBand. The band is a
plain Quantity and the u a Dispersion, which is what stops g/u from being transposed into
u/g — both well-kinded coverage factors, and the transposition turns a 42\,u systematic into
1/42 of a u that every downstream rule would happily shrink.
The one band-classification rule, and the NaN rule inside it.
classifyBand g u hk systematicAt price is the single place a band is classified: the guard
(u must be a positive number), the quotient g/u through bandReadingLaw, the comparison
against the systematic threshold, and the pricing of a coverage statement through the price
function the caller supplies — readBand passes the Gaussian riskForFactor, Evidence.readBand
passes riskForFactorAt at the record's own \nu. Nothing else differs between the two entry
points of the band reading.
The NaN rule. A coverage factor that is not a number — \infty/\infty, an unbounded band over
an unbounded u — reads as unstated, and so does any band read against a systematic threshold
that is not a number. The rule cannot be left to the comparison: NaN ≤ t and NaN > t are both
false, so a rule written "\le t ⇒ coverage, else systematic" and the same rule written
"> t ⇒ systematic, else coverage" classify the same band in opposite directions — one as a
systematic that must never be tightened, the other as a coverage statement every downstream rule
may shrink. Neither is right. unstated is chosen over the conservative systematic because a
systematic is a positive claim — a term the model does not carry — that a factor nobody can price
does not support; and over the permissive coverage because that is the reading whose repair,
tightening, is the dangerous one. A factor nobody can price is not a reading, and a threshold nobody
stated classifies nothing. Because the guard lives in classifyBand and both entry points are
one-line calls to it, no caller can re-derive the wrong direction; and for every factor and
threshold that are numbers the rule is unchanged — the guard falls through to exactly the
comparison it always made, so \infty/1 is still a systematic and the threshold itself is still
a coverage statement.
Lean code for Definition19.8.6●1 definition
Associated Lean declarations
-
defdefined in PropertyKindCalculus/Uncertainty/Conformity.leancomplete
def PropertyKindCalculus.Uncertainty.Conformity.classifyBand {k : PropertyKindCalculus.KindOfProperty} (g : PropertyKindCalculus.Quantity k Float) (u : PropertyKindCalculus.Uncertainty.Dispersion k Float) (hk : k.IsRational) (systematicAt : PropertyKindCalculus.Quantity PropertyKindCalculus.Uncertainty.coverageFactor Float) (price : PropertyKindCalculus.Quantity PropertyKindCalculus.Uncertainty.coverageFactor Float → Option (PropertyKindCalculus.Quantity PropertyKindCalculus.Uncertainty.probability Float)) : PropertyKindCalculus.Uncertainty.Conformity.BandReading
def PropertyKindCalculus.Uncertainty.Conformity.classifyBand {k : PropertyKindCalculus.KindOfProperty} (g : PropertyKindCalculus.Quantity k Float) (u : PropertyKindCalculus.Uncertainty.Dispersion k Float) (hk : k.IsRational) (systematicAt : PropertyKindCalculus.Quantity PropertyKindCalculus.Uncertainty.coverageFactor Float) (price : PropertyKindCalculus.Quantity PropertyKindCalculus.Uncertainty.coverageFactor Float → Option (PropertyKindCalculus.Quantity PropertyKindCalculus.Uncertainty.probability Float)) : PropertyKindCalculus.Uncertainty.Conformity.BandReading
**The one band-classification rule**, shared by `readBand` and `Evidence.readBand`, which differ only in how a coverage statement is *priced* — `price` is the Gaussian `riskForFactor` for the first and `riskForFactorAt` at the record's own `ν` for the second. Everything else is decided here, once: the guard (`u` must be a positive number), the division `g/u` through `bandReadingLaw`, the systematic threshold, and what a factor that is not a number means. That last case is `unstated`, and it is stated explicitly because it cannot be left to the comparison: `∞/∞` is `NaN`, and `NaN ≤ t` and `NaN > t` are *both* false, so a rule written "`≤ t` ⇒ coverage, else systematic" and the same rule written "`> t` ⇒ systematic, else coverage" read the same band in opposite directions. Neither reading is right — a factor nobody can price is not a reading — and having the guard here means no caller can re-derive the wrong one. The same holds of a threshold that is not a number: `systematicAt` is the judgement the whole reading turns on, and a judgement nobody made classifies nothing, so it too is `unstated` rather than whichever side of a comparison with `NaN` happens to fall through.
Realized: Conformity.classifyBand, with the isNaN guards on the threshold and on the quotient
ahead of the > comparison, and none from price (a \nu nothing qualifies) also read as
unstated. The checked facts are the probes of PropertyKindCalculus.Tests.Uncertainty.Evidence,
each a #guard that fails the build if a reading moves: \infty/\infty reads unstated through
readBand and through Evidence.readBand at \nu = 4 — the case where a second copy of the
rule had disagreed; 41\,u under a NaN threshold reads unstated through both; \infty/1
reads systematic; 4.5\,u and 4.39\,u read coverage (at 3.4 \times 10^{-6} under the
Gaussian, 5.4 \times 10^{-3} at \nu = 4) and 41\,u reads systematic through both; the
threshold 6\,u itself reads coverage at the Gaussian limit and at \nu = 4; and a u of
zero, or a u qualified by \nu = 0.5, reads unstated.
Why an acceptance limit may be allowed to tighten itself. Deriving the band from a stated risk and
pooling evidence so that u falls gives a deployment that gets less conservative the longer it
runs — exactly the automatic loosening that should not be allowed near anything that can be killed
for using too much memory. Over ℝ, with the guarded upper acceptance limit A(u) = L - k\,u:
the limit rises as u falls and never past L, so the mechanism is a ratchet with a ceiling;
and — the whole content — the specific consumer's risk at the acceptance limit is Q(k) for
every u \neq 0, independent of u, because band and uncertainty shrink together and the
standardized deviate they are read at is exactly k. A rule built for a target risk therefore
runs that target at every stage of evidence, the cold start included: with one Type B prior the
loop is safe and merely inefficient. What is not claimed: that the risk is unchanged by evidence is
a statement about the rule, not about the world — if the pooled u is wrong, a
systematic reading is the one thing the loop must never tighten,
and a floor on u is the operational guard the theorem leaves to the deployment.
Lean code for Theorem19.8.7●1 theorem
Associated Lean declarations
-
theoremdefined in PropertyKindCalculus/Uncertainty/ConformityLadder.leancomplete
theorem PropertyKindCalculus.Uncertainty.ConformityLadder.riskAt_acceptanceLimit (Q : ℝ → ℝ) (L k : ℝ) {u : ℝ} (hu : u ≠ 0) : PropertyKindCalculus.Uncertainty.ConformityLadder.riskAt Q L (PropertyKindCalculus.Uncertainty.ConformityLadder.acceptanceLimit L k u) u = Q k
theorem PropertyKindCalculus.Uncertainty.ConformityLadder.riskAt_acceptanceLimit (Q : ℝ → ℝ) (L k : ℝ) {u : ℝ} (hu : u ≠ 0) : PropertyKindCalculus.Uncertainty.ConformityLadder.riskAt Q L (PropertyKindCalculus.Uncertainty.ConformityLadder.acceptanceLimit L k u) u = Q k
**The risk at the acceptance limit does not depend on the uncertainty.** `(L − A)/u = k` for every `u ≠ 0`, so the specific consumer's risk there is `Q k` whatever the evidence has done to `u`. This is the theorem the compounding layer rests on. "The guard band tightens as calibration accumulates" is, without it, a smaller number with no argument behind it; with it, the fleet's speedup is a *corollary of a risk bound* — the deployment buys back wall clock and keeps the same consumer's risk, because those are the same statement. `u ≠ 0` rather than `0 < u`: a negative `u` is not meaningful but the algebra does not care, and requiring positivity here would be a hypothesis the result does not use. What the algebra does care about is that `u` is not zero — with no dispersion there is no standardized deviate, and the conformity question has a definite answer that this expression is not the way to compute.
Realized over ℝ in ConformityLadder: acceptanceLimit_antitone and acceptanceLimit_le (the
ratchet and its ceiling), riskAt_acceptanceLimit (the invariance, by the algebra
(L - A(u))/u = k, with the tail Q an arbitrary function), riskAt_acceptanceLimit_le (the
target survives every update of u), riskAt_le_of_accepted and
riskAt_acceptanceLimit_antitone_in_factor (needing only Antitone Q, which the Gaussian, t
and rectangular tails all satisfy), and acceptanceLimit_le_of_floor (a floor on u is a floor
on the limit).