Out-of-Service Criteria and OOS Hold Triggers
An out-of-service (OOS) defect is not a severity opinion — it is a condition the North American Standard Out-of-Service Criteria declare unsafe to operate, and a vehicle carrying one may not be driven until the defect is corrected. When a DVIR pipeline classifies a defect tagged DOT_OOS_CRITERIA, or scores any defect at or above the critical floor of 70, the system must block dispatch the instant the classification lands, not at the next scheduled sync. A dispatch that slips out between classification and hold is a vehicle operated with a known OOS defect, and under 49 CFR § 396.9 roadside authority that is exactly the condition an inspector places out of service on the spot. This page implements the OOS evaluator and the dispatch guard that enforce the hold, the state transition into OOS_HOLD, and the certified-repair gate that is the only way out of it. It is the enforcement complement to the Severity Scoring Algorithms for DVIR Defects engine, whose 0–100 score this guard reads.
The rule that governs everything here is asymmetric: a single OOS defect voids dispatch regardless of the vehicle’s overall score. A truck can be flawless on every other system and still be barred from the road by one fabric-exposed tire or one brake out of adjustment. The hold cannot be cleared by re-inspection alone, and it cannot be cleared by the driver — only a certified repair recorded under 49 CFR § 396.11©(2) releases it, and the release itself is written to an immutable log so an auditor can see who cleared the hold, when, and against which certification.
Prerequisites
Anchor link to "Prerequisites"- Python 3.10+ with
dataclasses,enum, and stdlibhashlib/datetimefor the immutable hold log. - A classified defect — carrying
severity_score,defect_category,compliance_guardrail, and anis_oos_criteriaflag from the Standardized DVIR JSON Schema Design. - The canonical model — bands
0–34 minor,35–69 major,70–100 critical; anyDOT_OOS_CRITERIAdefect holds a critical floor of 70. - A repair-certification record — produced by the certifying-repairs process under § 396.11©(2), the only artifact that clears a hold.
Step-by-Step Implementation
Anchor link to "Step-by-Step Implementation"Step 1 — Evaluate the OOS condition
Anchor link to "Step 1 — Evaluate the OOS condition"The evaluator answers one question: must this defect place the vehicle out of service? Two independent triggers force OOS — the DOT_OOS_CRITERIA guardrail, and a severity_score at or above the critical floor of 70 — and either alone is sufficient. Specific CVSA thresholds, such as a tire with less than 2/32-inch tread on a steer axle or a brake out of adjustment beyond the pushrod limit, resolve to the is_oos_criteria flag upstream; the evaluator honors that flag without second-guessing it.
from dataclasses import dataclass
CRITICAL_FLOOR = 70 # 0-100 scale; the immovable OOS floor
@dataclass(frozen=True)
class ClassifiedDefect:
defect_id: str
vehicle_vin: str
defect_category: str
severity_score: int
is_oos_criteria: bool # CVSA OOS threshold met upstream
compliance_guardrail: str # "OOS" | "conditional" | "monitor_only"
def is_out_of_service(defect: ClassifiedDefect) -> bool:
"""Either trigger is sufficient. A single OOS defect voids dispatch."""
return (
defect.is_oos_criteria
or defect.compliance_guardrail == "OOS"
or defect.severity_score >= CRITICAL_FLOOR
)
Step 2 — Model the OOS hold as a state transition
Anchor link to "Step 2 — Model the OOS hold as a state transition"A vehicle moves from DISPATCHABLE to OOS_HOLD the moment an OOS defect is classified, and it may leave OOS_HOLD only through a certified repair. Model the states explicitly so an illegal transition — clearing a hold without a certification, or dispatching from OOS_HOLD — is impossible to express rather than merely discouraged.
from enum import Enum
class VehicleState(str, Enum):
DISPATCHABLE = "dispatchable"
OOS_HOLD = "oos_hold"
def apply_defect(state: VehicleState, defect: ClassifiedDefect) -> VehicleState:
"""Transition to OOS_HOLD the instant an OOS defect is classified."""
if is_out_of_service(defect):
return VehicleState.OOS_HOLD # one OOS defect is enough
return state
Step 3 — Guard dispatch and record the immutable hold
Anchor link to "Step 3 — Guard dispatch and record the immutable hold"The dispatch guard is the enforcement point. It refuses to release any vehicle in OOS_HOLD, and it writes the hold to an append-only log with a SHA-256 audit_hash chained to the prior entry, so the hold record cannot be altered or backdated after the fact. Raise on a blocked dispatch — do not return a falsy value a caller might ignore.
from datetime import datetime, timezone
import hashlib
import json
class DispatchBlocked(Exception):
"""Raised when a vehicle in OOS_HOLD is offered for dispatch."""
@dataclass(frozen=True)
class HoldRecord:
vehicle_vin: str
defect_id: str
event: str # "hold_placed" | "hold_cleared"
actor: str
timestamp: str # ISO-8601, tz-aware
prev_hash: str
audit_hash: str
def _hash_entry(payload: dict, prev_hash: str) -> str:
body = {**payload, "prev_hash": prev_hash}
canonical = json.dumps(body, sort_keys=True, separators=(",", ":"))
return hashlib.sha256(canonical.encode()).hexdigest()
def place_hold(defect: ClassifiedDefect, actor: str, prev_hash: str) -> HoldRecord:
"""Write an immutable hold entry chained to the prior log hash."""
payload = {
"vehicle_vin": defect.vehicle_vin, "defect_id": defect.defect_id,
"event": "hold_placed", "actor": actor,
"timestamp": datetime.now(timezone.utc).isoformat(),
}
return HoldRecord(**payload, prev_hash=prev_hash,
audit_hash=_hash_entry(payload, prev_hash))
def guard_dispatch(state: VehicleState) -> None:
"""Block dispatch of any held vehicle. § 396.9."""
if state is VehicleState.OOS_HOLD:
raise DispatchBlocked("vehicle is under an OOS hold; dispatch refused")
Step 4 — Clear the hold only on a certified repair
Anchor link to "Step 4 — Clear the hold only on a certified repair"A hold clears only when a repair certification under § 396.11©(2) is presented — a signed record that the defect was corrected. The clearing function rejects any attempt to release the hold without a valid certification, refuses to auto-clear on a bare re-inspection, and appends a hold_cleared entry to the same immutable log so the release is as traceable as the hold.
@dataclass(frozen=True)
class RepairCertification:
defect_id: str
certified_by: str # authorized mechanic / carrier official
certified_at: str # ISO-8601
signature: str # attestation the defect was corrected
def clear_hold(
defect: ClassifiedDefect,
cert: RepairCertification | None,
actor: str,
prev_hash: str,
) -> tuple[VehicleState, HoldRecord]:
"""Release an OOS hold only against a matching certified repair."""
if cert is None or not cert.signature:
# No certification -> the hold stands. Re-inspection alone is not enough.
raise DispatchBlocked("cannot clear OOS hold without a repair certification")
if cert.defect_id != defect.defect_id:
raise DispatchBlocked("certification does not match the held defect")
payload = {
"vehicle_vin": defect.vehicle_vin, "defect_id": defect.defect_id,
"event": "hold_cleared", "actor": actor,
"timestamp": datetime.now(timezone.utc).isoformat(),
}
record = HoldRecord(**payload, prev_hash=prev_hash,
audit_hash=_hash_entry(payload, prev_hash))
return VehicleState.DISPATCHABLE, record
Verification and Testing
Anchor link to "Verification and Testing"Test the three properties that keep an OOS vehicle off the road: an OOS defect blocks dispatch, a hold cannot be cleared without a certification, and clearing is logged with a chained hash. Run these under pytest in CI and fail the build on any regression.
import pytest
def _defect(**over) -> ClassifiedDefect:
base = dict(defect_id="d1", vehicle_vin="1FUJGLDR1CLBP8834",
defect_category="brakes_out_of_adjustment", severity_score=92,
is_oos_criteria=True, compliance_guardrail="OOS")
return ClassifiedDefect(**{**base, **over})
def test_oos_defect_blocks_dispatch():
state = apply_defect(VehicleState.DISPATCHABLE, _defect())
assert state is VehicleState.OOS_HOLD
with pytest.raises(DispatchBlocked):
guard_dispatch(state)
def test_single_oos_defect_voids_dispatch_despite_low_score():
# is_oos_criteria alone is sufficient even if the numeric score is low.
d = _defect(severity_score=10, compliance_guardrail="conditional")
assert is_out_of_service(d) is True
def test_hold_cannot_clear_without_certification():
d = _defect()
with pytest.raises(DispatchBlocked):
clear_hold(d, cert=None, actor="dispatcher", prev_hash="0" * 64)
def test_reinspection_without_cert_does_not_clear():
d = _defect()
empty = RepairCertification(defect_id="d1", certified_by="",
certified_at="2026-07-16T00:00:00Z", signature="")
with pytest.raises(DispatchBlocked):
clear_hold(d, cert=empty, actor="inspector", prev_hash="0" * 64)
def test_clearing_is_logged_and_chained():
d = _defect()
cert = RepairCertification(defect_id="d1", certified_by="mech-7",
certified_at="2026-07-16T00:00:00Z", signature="sig")
state, record = clear_hold(d, cert, actor="mech-7", prev_hash="abc")
assert state is VehicleState.DISPATCHABLE
assert record.event == "hold_cleared"
assert record.prev_hash == "abc" # chained to the prior entry
assert len(record.audit_hash) == 64 # SHA-256 over the entry
Common Failure Modes and Gotchas
Anchor link to "Common Failure Modes and Gotchas"- A single OOS defect voids dispatch regardless of overall score. The most common design error is averaging or scoring the whole vehicle and releasing it when the composite looks acceptable. OOS is not a composite — one qualifying defect bars the vehicle. The evaluator returns
Trueonis_oos_criteriaalone, even when the numericseverity_scoreis low, because the CVSA criteria are pass/fail per defect, not a fleet-wide average. - Tire and brake thresholds resolve upstream, not in the guard. Specific limits — a steer-tire tread below 2/32 inch, a brake out of adjustment past the pushrod stroke limit, a flat or fabric-exposed tire — are CVSA thresholds evaluated when the defect is classified and captured in
is_oos_criteria. The dispatch guard must trust that flag, not re-derive thresholds from raw measurements, so the OOS determination has a single authoritative source. - Auto-clearing on re-inspection without certification. A hold must never clear because a later inspection happened to pass. Re-inspection is not repair, and § 396.11©(2) requires a certification that the defect was corrected. The clearing function rejects a missing or unsigned certification and refuses a certification whose
defect_iddoes not match the held defect, so a pass on a different defect can never release the wrong hold. - Mutable or backdated hold logs. If the hold record can be edited, an auditor cannot trust that the vehicle was actually held between classification and repair. Chain each log entry with a SHA-256 hash over the prior entry so any deletion, reorder, or backdate breaks the chain and is detectable, matching the tamper-evidence the rest of the pipeline applies to its decisions.
Frequently Asked Questions
Anchor link to "Frequently Asked Questions"What is the difference between a critical score and an out-of-service defect?
A critical score (70–100) is the internal severity tier; an out-of-service defect is a condition the North American Standard Out-of-Service Criteria declare unsafe to operate. They overlap by design — any DOT_OOS_CRITERIA defect holds a critical floor of 70 — but OOS is stricter in one direction: a single OOS defect bars the vehicle regardless of its overall score, whereas a high composite score without a qualifying OOS defect routes to urgent repair without necessarily being a federal OOS condition.
Can a driver or dispatcher clear an OOS hold after re-inspecting the vehicle?
No. An OOS hold clears only against a repair certification under 49 CFR § 396.11©(2) — a signed record that the defect was actually corrected. Re-inspection alone, even a passing one, does not clear the hold, and the clearing function rejects any attempt without a matching certification. The release is written to the same immutable log as the hold, so who cleared it and against which certification is permanently recorded.
Why block dispatch the instant a defect is classified instead of at the next sync?
Because the gap between classification and enforcement is exactly when an OOS vehicle can slip onto the road. If the hold waits for a scheduled sync, a dispatch issued in that window puts a vehicle with a known OOS defect into service, which is an operating-with-a-known-defect violation under § 396.9. The dispatch guard raises immediately on any held vehicle, so there is no window in which a classified OOS defect and an authorized dispatch can coexist.
Related
Anchor link to "Related"- Severity Scoring Algorithms for DVIR Defects — produces the 0–100 score whose critical floor this guard enforces.
- Building a Weighted Defect Scoring Model in Python — the scoring function that emits the OOS floor this page acts on.
- CSA Score Impact and BASIC Category Mapping — the CSA exposure a dispatched OOS defect creates at a roadside inspection.
- Certifying Repairs Under 396.11©(2) — the certification record that is the only way to clear a hold.
Part of the Severity Scoring Algorithms for DVIR Defects guide. Back to Defect Classification & Repair Order Routing.