DORA Incident Classification Thresholds in Code: Turning the RTS into a Decision Engine

The classification RTS turned “is this a major incident?” into arithmetic. Most firms still answer it with a tired human at 2am and a mental model of the thresholds. That is the wrong place to be keeping the rules.

DORA’s reporting obligation is live. Since 17 January 2025, an EU financial entity that suffers a major ICT-related incident has to notify its competent authority on a tight clock. The judgement that starts that clock — whether the incident is major at all — is not left to feel. Commission Delegated Regulation (EU) 2024/1772, the regulatory technical standard on classification, sets out the criteria and, for most of them, hard numbers. The decision is closer to a calculation than an opinion. Yet in most incident-response runbooks I read, the decision still lives in a person’s head, applied under pressure, at the worst possible hour, with no record of the working.

That is a governance defect wearing an operational badge. If the classification is a judgement call, it is inconsistent between responders, unauditable after the fact, and impossible to defend when a supervisor asks why an incident was — or was not — reported. The fix is to stop treating the RTS as prose and start treating it as a specification.

What the RTS actually specifies

The classification RTS defines seven criteria. One of them — whether critical or important services were affected — acts as a gateway. The other six carry materiality thresholds, and several of those thresholds are quantitative:

  • Clients, financial counterparts and transactions affected — met where more than 10% of relevant clients are affected, or more than 100,000 clients in absolute terms, or more than 30% of financial counterparts, or more than 10% of the daily number or value of transactions.
  • Duration and service downtime — an incident duration of more than 24 hours, or downtime of an affected critical or important service of more than 2 hours.
  • Geographical spread — an adverse impact in two or more EU Member States.
  • Data losses — an adverse impact on the availability, authenticity, integrity or confidentiality of data.
  • Economic impact — direct and indirect costs and losses exceeding EUR 100,000.
  • Reputational impact — the qualitative criterion: media coverage, repeated complaints, a failure to meet regulatory requirements, or a material loss of clients.

The combination rule is where firms most often get it wrong in the runbook, because the wording is easy to paraphrase loosely. An incident is major where it has affected critical services and either the data-losses threshold is met at the higher materiality level on its own, or two or more of the other thresholds are met together. The critical-services gateway is not optional and it is not one vote among seven. Get the structure wrong and you either over-report noise or, worse, miss a genuine major incident because you were counting criteria that should never have been in play without the gateway.

Why this belongs in code, not a runbook

Three things follow from encoding the criteria rather than documenting them. The classification becomes deterministic: the same incident data yields the same verdict regardless of who is on call. It becomes auditable: the engine records which thresholds were crossed and which were not, so the reasoning is captured at the moment of decision rather than reconstructed weeks later for a final report. And it becomes fast: the RTS reporting timelines are unforgiving — an initial notification within hours of classification, an intermediate report inside 72 hours, and a final report within a month — so the last thing you want is a human re-deriving the thresholds while the clock runs.

This is the same discipline I have argued for with the AML single rulebook: turning a rulebook into a configurable controls engine rather than a policy PDF. The thresholds are config; the combination logic is code; the verdict is evidence. It also assumes the plumbing around it exists — the detection, correlation and workflow that a proper incident reporting architecture and operating model provides. The classifier is one component in that pipeline, not a substitute for it.

The engine, in outline

Below is a minimal, deterministic classifier that encodes the RTS structure and, critically, logs its reasoning. It is not a product; it is the shape of the thing. The thresholds sit as named constants so they can be lifted into configuration, and every path returns the criteria it evaluated.

"""DORA major-incident classifier.

Encodes the criteria in Commission Delegated Regulation (EU) 2024/1772
(the RTS on classification of major ICT-related incidents). Article 8:
an incident is major where it has affected critical services AND EITHER
the data-losses threshold is met at the higher level, OR two or more of
the other materiality thresholds are met. Keep entity-specific overrides
in configuration, not in code.
"""
from dataclasses import dataclass


@dataclass
class Incident:
    critical_services_affected: bool
    clients_affected_pct: float        # share of relevant clients, 0-1
    clients_affected_count: int
    counterparts_affected_pct: float   # share of financial counterparts, 0-1
    transactions_affected_pct: float   # share of daily number or value, 0-1
    duration_hours: float              # total incident duration
    downtime_hours: float              # downtime of a critical/important service
    member_states: int                 # count of EU Member States impacted
    data_loss_high_impact: bool        # CIA/authenticity loss above RTS level
    economic_cost_eur: float           # direct plus indirect costs and losses
    reputational_impact: bool


def _clients(i: Incident) -> bool:
    return (i.clients_affected_pct > 0.10
            or i.clients_affected_count > 100_000
            or i.counterparts_affected_pct > 0.30
            or i.transactions_affected_pct > 0.10)


def classify(i: Incident) -> dict:
    # Gateway: no critical-service impact, cannot be major.
    if not i.critical_services_affected:
        return {"major": False,
                "reasons": ["Critical services not affected (gateway)"]}

    others = {
        "clients/counterparts/transactions": _clients(i),
        "duration/downtime": i.duration_hours > 24 or i.downtime_hours > 2,
        "geographical spread": i.member_states >= 2,
        "economic impact": i.economic_cost_eur > 100_000,
        "reputational impact": i.reputational_impact,
    }
    met = [name for name, hit in others.items() if hit]

    # Data losses at the higher level is sufficient alone; otherwise
    # two or more of the other thresholds must be satisfied.
    major = i.data_loss_high_impact or len(met) >= 2

    reasons = []
    if i.data_loss_high_impact:
        reasons.append("Data-losses threshold met (sufficient alone)")
    reasons += [f"Threshold met: {name}" for name in met]
    if not major:
        reasons.append(f"Only {len(met)} secondary threshold(s) met; two required")

    return {"major": major, "criteria_met": met,
            "data_loss_high_impact": i.data_loss_high_impact,
            "reasons": reasons}

The output is the point. Every classification produces a structured record — the verdict, the criteria that fired, and the reasoning — which is exactly what a final report has to reconstruct and exactly what a supervisor will ask to see. Store that record against the incident and the awkward question “why did you not report this one?” has a machine-generated answer with a timestamp.

Where the judgement really goes

Encoding the thresholds does not abolish judgement; it relocates it to where it belongs. The hard calls are in the inputs, not the arithmetic. Is this service critical or important under your own classification? What counts as a relevant client for the denominator? How do you estimate indirect costs at hour two of an incident when you barely understand the blast radius? Those are the decisions worth a human, and they are the decisions your engine should force to be made explicitly, with the assumption recorded, rather than smuggled into a gut-feel verdict. The reputational criterion stays qualitative for good reason — but even there, the engine documents that it was considered.

Do this once and it compounds. The same encoded thresholds feed the initial notification, the escalation to the board, and the metrics you carry into DORA’s steady state. And because ICT incidents rarely respect a single regime, the classified record slots straight into the wider cyber-incident reporting map that a board has to hold across DORA, NIS2 and sector rules — one incident, several clocks, one defensible source of truth.

The RTS already wrote the rules down as numbers. Leaving them in a runbook, to be applied by whoever happens to be awake, is a choice to be worse than the regulation requires.

Most technology problems are not technology problems. They are control problems.

The systems exist. The investment has been made. The question is whether leadership can understand, direct, evidence, and sustain what those systems produce. Find out where control exists — and where it only appears to.

Full Governance by Sixteen Pillars

Govern your business. Prove your compliance.

A board assurance cockpit for EU-regulated financial firms — tamper-evident, hash-chained proof of governance across DORA, GDPR, NIS2, ISO 27001, the EU AI Act and MiCA. In development.

See what's coming