Skip to content
Classification & Routing

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.

  • Python 3.10+ with dataclasses, enum, and stdlib hashlib / datetime for the immutable hold log.
  • A classified defect — carrying severity_score, defect_category, compliance_guardrail, and an is_oos_criteria flag from the Standardized DVIR JSON Schema Design.
  • The canonical model — bands 0–34 minor, 35–69 major, 70–100 critical; any DOT_OOS_CRITERIA defect 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 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.

python
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.

python
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.

python
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.

python
@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

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.

python
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 True on is_oos_criteria alone, even when the numeric severity_score is 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_id does 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.
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.

Part of the Severity Scoring Algorithms for DVIR Defects guide. Back to Defect Classification & Repair Order Routing.