2026-04-17·18 min read·

DORA Art.13: Learning and Evolving — Post-Incident Reviews, Lessons Learned, ICT Risk Framework Updates, and Security Awareness for Financial Services (2026)

Post #406 in the sota.io EU Cyber Compliance Series

DORA Chapter II defines a six-domain ICT risk management framework. Article 13 closes the loop: where Art.10 requires detection, Art.11 requires response, and Art.12 requires backup recovery, Art.13 mandates that financial entities learn from what happened — formalising the feedback cycle from ICT incidents, threat intelligence, and testing back into the risk management framework itself.

The regulatory logic is explicit: a financial entity that recovers from an incident but does not update its defences, document lessons, or train its staff has not achieved digital operational resilience. It has merely survived. Art.13 operationalises the distinction between survival and learning.

This guide covers all five substantive Art.13 obligations, explains the post-incident review process that satisfies NCA audit requirements, provides a Python self-assessment tool (DORALearningEvolvingChecker), maps to NIS2 Art.21(2) for dual-regulated entities, and closes with a 25-item NCA audit checklist.


1. Who Is Subject to DORA Art.13

DORA Art.2(1) scope covers approximately 22,000 EU financial entities:

CategoryExamples
Credit institutionsBanks, savings banks, credit unions
Investment firmsBrokers, asset managers, trading firms
Insurance/reinsuranceAll Art.2(1)(c) undertakings
Payment institutionsPSPs, e-money issuers
Crypto-asset service providersCASPs under MiCA
Central counterpartiesCCPs, CSDs
ICT third-party service providersCloud, SaaS providers in scope via Art.30

Micro-enterprises (fewer than 10 employees, annual turnover ≤ €2 million) may apply a simplified ICT risk management framework under Art.16, but Art.13 obligations apply to all entities regardless of size — the intensity of implementation scales with proportionality (Art.4), not the existence of the obligation.


2. The Five Art.13 Obligations at a Glance

Art.13 imposes five distinct requirements with separate audit evidence trails:

ParagraphObligationEvidence Required
Art.13(1)Gather threat intelligence; analyse impact on digital operational resilienceThreat intel subscription evidence, analysis reports
Art.13(2)Conduct post-incident reviews after major ICT incidentsRCA reports with root cause, impact, corrective actions
Art.13(3)Update ICT risk management framework at least annually or after significant changesFramework revision history, management sign-off
Art.13(4)ICT security awareness programs and digital operational resilience trainingTraining records, completion rates, management body participation
Art.13(5)Draw lessons from peers and sector-wide ICT incidents and testingInformation sharing participation evidence, TLPT feedback integration

3. Art.13(1): Threat Intelligence Gathering and Analysis

Art.13(1) requires financial entities to maintain capabilities and staff to:

3.1 Threat Intelligence Sources

A compliant threat intelligence program must integrate multiple source tiers:

THREAT INTELLIGENCE ARCHITECTURE (Art.13(1) Evidence)

Tier 1 — Strategic Intelligence (Management + Board level)
  ├── ENISA Threat Landscape (annual, sector reports)
  ├── ECB Cyber Resilience Oversight Expectations (CROE)
  ├── EBA/ESMA/EIOPA sector-specific threat publications
  └── National CSIRT advisories (CERT-DE, CERT-FR, NCSC-UK, etc.)

Tier 2 — Operational Intelligence (ICT Risk Function)
  ├── MITRE ATT&CK Financial Services profile updates
  ├── FS-ISAC (Financial Services Information Sharing and Analysis Center)
  ├── Sector peer sharing (TIBER-EU intelligence packages)
  └── Commercial threat feeds (OSINT + premium) with CVE/NVD correlation

Tier 3 — Technical Intelligence (SOC / Security Engineering)
  ├── CVE/NVD vulnerability feeds (≤24h SLA for critical)
  ├── Vendor security advisories (patching SLA per criticality tier)
  ├── Dark web monitoring (credential exposure, sector targeting)
  └── Internal SIEM threat indicators (Art.10 feedback loop)

3.2 Impact Analysis Requirements

Gathering intelligence is necessary but not sufficient — Art.13(1) requires financial entities to analyse the impact on their digital operational resilience. This means:

  1. Asset mapping: Each threat must be evaluated against the ICT asset inventory (Art.8(1))
  2. Business function exposure: Impact analysis links threats to critical or important business functions (Art.8(2))
  3. Dependency chain analysis: Third-party ICT service providers in scope via Art.30 must be included in impact assessments
  4. Escalation criteria: Threats meeting defined severity thresholds trigger management body notification (Art.5(2))

4. Art.13(2): Post-Incident Reviews After Major ICT Incidents

Art.13(2) is the core learning obligation: after every major ICT-related incident (classification per Art.18(1)), financial entities must conduct a post-incident review to determine:

4.1 The 5-Phase Post-Incident Review Process

NCA expectations for post-incident review documentation align with industry standard root cause analysis practices. A compliant process covers five phases:

POST-INCIDENT REVIEW PROCESS (Art.13(2) Evidence Framework)

Phase 1 — Incident Timeline Reconstruction (Days 1-3 post-resolution)
  ├── Timeline: First detection → Containment → Recovery → Closure
  ├── Evidence preservation: Log archives, ticket trails, change records
  ├── Stakeholder interviews: SOC, ICT operations, business function owners
  └── Output: Verified chronological event log (immutable, signed)

Phase 2 — Root Cause Analysis (Days 3-10)
  ├── Technique: 5-Whys + Fishbone (Ishikawa) for complex incidents
  ├── Categories: Technical (CVE/misconfiguration), Process (gaps/failures),
  │               Human (training/awareness), Third-party (vendor/supplier)
  ├── Contributing factors: Distinguish root cause from proximate causes
  └── Output: Root cause statement with evidence chain

Phase 3 — Impact Assessment (Concurrent with Phase 2)
  ├── Service disruption duration × affected business functions
  ├── Data exposure/loss: Classification, volume, affected data subjects
  ├── Financial impact: Direct losses, recovery costs, regulatory fines risk
  ├── Reputational impact: Client notifications, press coverage, regulator contact
  └── Output: Quantified impact report (feeds Art.19 classification review)

Phase 4 — Corrective Action Planning (Days 10-20)
  ├── Immediate fixes: Patch, configuration change, access revocation
  ├── Short-term controls: Enhanced monitoring, temporary compensating controls
  ├── Long-term systemic changes: Framework updates (Art.13(3) trigger)
  ├── Owner assignment: Named responsible party per action
  └── Output: Corrective action plan (CAP) with SMART milestones

Phase 5 — Lessons Learned Dissemination (Days 20-30)
  ├── Internal: ICT risk function, management body, business function owners
  ├── Framework integration: CAP items that modify the ICTRMF → Art.13(3)
  ├── Training updates: Incidents revealing training gaps → Art.13(4) update
  ├── Peer sharing: Anonymised lessons to FS-ISAC / sector bodies (Art.13(5))
  └── Output: Lessons learned report (signed by management body)

4.2 Review Timing Requirements

Art.13(2) does not specify an explicit deadline, but NCA supervisory expectations (aligned with EBA/ESMA supervisory convergence tools) consistently expect:

Review ComponentExpected Timeline
Preliminary incident report72 hours post-resolution
Full root cause analysis30 days post-resolution
Corrective action plan45 days post-resolution
Lessons learned report (management sign-off)60 days post-resolution
CAP implementation verificationPer-action milestone dates

4.3 Third-Party Incident Reviews

When a major ICT incident involves a third-party ICT service provider, Art.13(2) requires that provider's cooperation in the review process. This obligation should be embedded in ICT service contracts (Art.30 requirements):

CONTRACT CLAUSE (Art.30 + Art.13(2) alignment):

"Following any ICT-related incident classified as 'major' under DORA Art.18(1)
affecting services provided to [Financial Entity], the ICT third-party service
provider shall:
(a) provide full incident timeline data within 5 business days of request;
(b) participate in joint root cause analysis sessions within 15 business days;
(c) deliver a written root cause report within 30 business days;
(d) implement agreed corrective actions within timelines agreed with [Financial Entity];
(e) provide written evidence of corrective action completion."

5. Art.13(3): Updating the ICT Risk Management Framework

Art.13(3) mandates that financial entities update their ICT risk management framework at least:

5.1 Framework Update Cadence

ICTRMF UPDATE CADENCE (Art.13(3) Evidence)

Annual Scheduled Review (Q4 each year)
  ├── Trigger: Calendar-based (fixed date, management body agenda item)
  ├── Scope: Full framework review — all six DORA Chapter II domains
  ├── Input: Threat landscape changes (Art.13(1)), incident history (Art.13(2)),
  │          testing results (Art.25/Art.26/Art.27), regulatory updates
  ├── Process: ICT risk function draft → Management body review → Sign-off
  └── Output: Framework revision document (versioned, dated, signed)

Incident-Triggered Update (within 60 days of major incident)
  ├── Trigger: Post-incident RCA identifies systemic framework gap
  ├── Scope: Targeted — only affected framework domains
  ├── Input: Corrective Action Plan (Phase 4 output from Art.13(2) process)
  ├── Process: Expedited — ICT risk function + management body approval
  └── Output: Framework amendment record (references incident RCA)

Supervisory-Triggered Update (per regulatory timeline)
  ├── Trigger: NCA supervisory assessment, on-site inspection finding
  ├── Scope: Per supervisory instruction scope
  ├── Input: Supervisory findings, peer review outcomes
  ├── Process: Remediation plan + evidence of implementation
  └── Output: Supervisory response documentation

Testing-Triggered Update (after each TLPT cycle)
  ├── Trigger: Threat-Led Penetration Test (Art.26) completion
  ├── Scope: Targeted — areas where TLPT identified gaps
  ├── Input: TLPT remediation plan (Art.26(7))
  ├── Process: Gap closure + framework update within TLPT remediation timeline
  └── Output: TLPT remediation evidence + framework update record

5.2 Framework Versioning Requirements

A version-controlled ICT risk management framework is a practical NCA audit requirement. Each version must record:

FieldContent
Version numberSemantic (e.g., v2.3.1)
Effective dateWhen the version takes effect
Review typeAnnual / Incident / Supervisory / Testing
Triggering eventIncident reference / Supervisory finding ref / TLPT ref
Changes summaryDelta from previous version (section-level)
ApproverManagement body sign-off (name, role, date)
Next review dateScheduled next review

6. Art.13(4): ICT Security Awareness and Digital Operational Resilience Training

Art.13(4) requires financial entities to conduct regular ICT security awareness programs and digital operational resilience training. Key requirements:

6.1 Mandatory Program Components

TRAINING PROGRAM REQUIREMENTS (Art.13(4) Evidence)

All Staff Training (Annual minimum)
  ├── ICT security awareness: Phishing, social engineering, credential hygiene
  ├── Incident reporting: How to report suspected incidents (Art.17 procedures)
  ├── Data handling: Classification, access control, third-party sharing
  └── Format: E-learning + annual attestation (completion records mandatory)

Role-Based Training (Risk-proportionate frequency)
  ├── ICT operations staff: Technical resilience, change management, backup testing
  ├── Business function owners: BCP activation, RTO/RPO responsibilities
  ├── Incident response team: Tabletop exercises, playbook rehearsals (≥annual)
  └── Third-party contract managers: DORA Art.30 supply chain risk awareness

Management Body Training (Mandatory under Art.5(3))
  ├── Cybersecurity risk fundamentals (not just IT — systemic risk framing)
  ├── DORA obligations: Chapter II, Art.17-23 (incident reporting), Art.24-27 (TLPT)
  ├── Digital operational resilience strategy (Art.6(8))
  └── Format: Dedicated session ≥ annually + briefings after major incidents

Specialist Training (Role-specific certification track)
  ├── CISO / ICT risk function: DORA regulatory updates, NCA supervisory priorities
  ├── SOC analysts: Threat intelligence analysis, incident classification (Art.18)
  ├── Pen test / TLPT participants: TIBER-EU framework, Art.26 requirements
  └── Procurement: Third-party ICT risk assessment (Art.28-30 obligations)

6.2 Training Records (NCA Audit Evidence)

Art.13(4) does not specify a retention period, but cross-referencing Art.6(8) (ICT risk management framework documentation) and DORA's general supervisory access requirements suggests a minimum 5-year retention for training records:

Record TypeMinimum ContentRetention
Completion certificateStaff ID, course name, completion date, score (if applicable)5 years
Attendance registerSession date, facilitator, attendee list (signed)5 years
Training planAnnual plan, roles covered, frequency, format5 years
Management body attestationIndividual sign-off on completion5 years
Incident-triggered trainingReference to triggering incident, updated content7 years (matches incident records)

7. Art.13(5): Drawing Lessons from Peer and Sector-Wide Experience

Art.13(5) requires financial entities to draw lessons from:

7.1 Information Sharing Participation

DORA Art.45 explicitly encourages financial entities to participate in voluntary information sharing arrangements. Art.13(5) creates a learning obligation that implicitly requires access to such information — meaning participation in sharing arrangements is a practical compliance requirement:

ForumTypeRelevance
FS-ISACGlobal financial sector ISACIncident patterns, threat intelligence
TIBER-EU / TLPT programsNational TIBER programsTLPT intelligence packages
ECB cyber resilience forumsEU banking sectorSupervisory intelligence sharing
National CSIRT sector sharingCountry-levelNational threat actor activity
EBA/ESMA/EIOPA supervisory convergenceRegulatoryCross-sector incident learning

7.2 Integrating Sector Lessons into the ICTRMF

For sector-level learning to satisfy Art.13(5), it must be documented as input to the framework update process (Art.13(3)):

SECTOR LEARNING INTEGRATION (Art.13(3) + Art.13(5) linkage)

Input: Sector incident report / threat intelligence bulletin
  ↓
Analysis: Does this incident pattern apply to our ICT asset inventory?
  ├── Yes → Gap assessment: Does our ICTRMF address this attack vector?
  │   ├── Gap identified → Corrective action → Art.13(3) targeted update
  │   └── No gap → Document: "Assessed, not applicable, rationale: [reason]"
  └── No → Document: "Assessed, not applicable, rationale: [reason]"
  ↓
Output: Threat assessment memo (filed in ICTRMF documentation)
  ↓
Periodic review: Sector learning register (annual summary for management body)

8. Python Implementation: DORALearningEvolvingChecker

"""
DORALearningEvolvingChecker — DORA Article 13 Compliance Self-Assessment Tool

Evaluates an organisation's learning and evolving capabilities against
all five Art.13 obligations. Designed for ICT risk functions conducting
internal gap assessments prior to NCA supervisory review.

Usage:
    checker = DORALearningEvolvingChecker(entity_config)
    report = checker.run_full_assessment()
    print(report.to_markdown())
"""

from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum
from datetime import date, timedelta
from typing import Optional


class ComplianceStatus(Enum):
    COMPLIANT = "COMPLIANT"
    PARTIAL = "PARTIAL"
    NON_COMPLIANT = "NON_COMPLIANT"
    NOT_ASSESSED = "NOT_ASSESSED"


class ThreatIntelTier(Enum):
    STRATEGIC = "strategic"    # Board/management level
    OPERATIONAL = "operational"  # ICT risk function
    TECHNICAL = "technical"    # SOC/engineering


class IncidentSeverity(Enum):
    MAJOR = "major"         # Art.18(1) — reportable, Art.13(2) review required
    SIGNIFICANT = "significant"  # Internal threshold — review recommended
    MINOR = "minor"         # No mandatory review


@dataclass
class ThreatIntelSource:
    name: str
    tier: ThreatIntelTier
    update_frequency_days: int
    last_reviewed: Optional[date] = None
    feeds_into_framework: bool = False

    @property
    def is_stale(self) -> bool:
        if self.last_reviewed is None:
            return True
        return (date.today() - self.last_reviewed).days > self.update_frequency_days


@dataclass
class PostIncidentReview:
    incident_id: str
    incident_date: date
    severity: IncidentSeverity
    resolution_date: Optional[date] = None
    rca_completed: bool = False
    rca_completion_date: Optional[date] = None
    corrective_action_plan: bool = False
    cap_completion_date: Optional[date] = None
    lessons_learned_report: bool = False
    ll_sign_off_date: Optional[date] = None
    framework_update_triggered: bool = False
    third_party_involved: bool = False
    third_party_cooperation_documented: bool = False

    @property
    def days_since_resolution(self) -> Optional[int]:
        if self.resolution_date is None:
            return None
        return (date.today() - self.resolution_date).days

    def check_timeliness(self) -> dict[str, ComplianceStatus]:
        results = {}
        if self.resolution_date is None:
            return {"all": ComplianceStatus.NOT_ASSESSED}

        days = self.days_since_resolution

        # RCA: expected within 30 days
        if self.rca_completed and self.rca_completion_date:
            rca_days = (self.rca_completion_date - self.resolution_date).days
            results["rca_timeliness"] = (
                ComplianceStatus.COMPLIANT if rca_days <= 30
                else ComplianceStatus.NON_COMPLIANT
            )
        elif days and days > 30 and not self.rca_completed:
            results["rca_timeliness"] = ComplianceStatus.NON_COMPLIANT
        else:
            results["rca_timeliness"] = ComplianceStatus.NOT_ASSESSED

        # CAP: expected within 45 days
        if self.corrective_action_plan and self.cap_completion_date:
            cap_days = (self.cap_completion_date - self.resolution_date).days
            results["cap_timeliness"] = (
                ComplianceStatus.COMPLIANT if cap_days <= 45
                else ComplianceStatus.NON_COMPLIANT
            )
        elif days and days > 45 and not self.corrective_action_plan:
            results["cap_timeliness"] = ComplianceStatus.NON_COMPLIANT
        else:
            results["cap_timeliness"] = ComplianceStatus.NOT_ASSESSED

        # Lessons learned: expected within 60 days
        if self.lessons_learned_report and self.ll_sign_off_date:
            ll_days = (self.ll_sign_off_date - self.resolution_date).days
            results["ll_timeliness"] = (
                ComplianceStatus.COMPLIANT if ll_days <= 60
                else ComplianceStatus.NON_COMPLIANT
            )
        elif days and days > 60 and not self.lessons_learned_report:
            results["ll_timeliness"] = ComplianceStatus.NON_COMPLIANT
        else:
            results["ll_timeliness"] = ComplianceStatus.NOT_ASSESSED

        return results


@dataclass
class FrameworkRevision:
    version: str
    effective_date: date
    review_type: str  # "annual" | "incident" | "supervisory" | "testing"
    triggering_event: Optional[str] = None
    management_sign_off: bool = False
    next_review_date: Optional[date] = None


@dataclass
class TrainingRecord:
    program_name: str
    target_audience: str  # "all_staff" | "management_body" | "ict_ops" | "specialist"
    last_completed: Optional[date] = None
    completion_rate_pct: float = 0.0
    management_body_included: bool = False
    frequency_days: int = 365

    @property
    def is_overdue(self) -> bool:
        if self.last_completed is None:
            return True
        return (date.today() - self.last_completed).days > self.frequency_days


@dataclass
class EntityConfig:
    entity_name: str
    threat_intel_sources: list[ThreatIntelSource] = field(default_factory=list)
    post_incident_reviews: list[PostIncidentReview] = field(default_factory=list)
    framework_revisions: list[FrameworkRevision] = field(default_factory=list)
    training_records: list[TrainingRecord] = field(default_factory=list)
    isac_membership: bool = False
    sector_learning_register_maintained: bool = False


@dataclass
class AssessmentResult:
    article: str
    obligation: str
    status: ComplianceStatus
    findings: list[str] = field(default_factory=list)
    evidence_items: list[str] = field(default_factory=list)
    remediation: list[str] = field(default_factory=list)


class DORALearningEvolvingChecker:
    """DORA Art.13 compliance checker — all five obligations."""

    TARGET_ARTICLES = [
        "Art.13(1): Threat Intelligence",
        "Art.13(2): Post-Incident Reviews",
        "Art.13(3): Framework Updates",
        "Art.13(4): Security Awareness Training",
        "Art.13(5): Sector Learning",
    ]

    def __init__(self, config: EntityConfig):
        self.config = config

    def check_threat_intelligence(self) -> AssessmentResult:
        """Art.13(1): Validate threat intelligence gathering and analysis."""
        findings, evidence, remediation = [], [], []

        tiers_covered = {source.tier for source in self.config.threat_intel_sources}
        required_tiers = {ThreatIntelTier.STRATEGIC, ThreatIntelTier.OPERATIONAL, ThreatIntelTier.TECHNICAL}
        missing_tiers = required_tiers - tiers_covered

        if missing_tiers:
            findings.append(f"Missing threat intel coverage for tiers: {[t.value for t in missing_tiers]}")
            remediation.append("Add threat intelligence sources for each missing tier")
        else:
            evidence.append(f"All three threat intelligence tiers covered ({len(self.config.threat_intel_sources)} sources)")

        stale_sources = [s for s in self.config.threat_intel_sources if s.is_stale]
        if stale_sources:
            findings.append(f"{len(stale_sources)} threat intel sources not reviewed within frequency window")
            remediation.append("Establish regular threat intel review cadence per source frequency")
        
        feeds_framework = [s for s in self.config.threat_intel_sources if s.feeds_into_framework]
        if not feeds_framework:
            findings.append("No threat intel sources documented as feeding into ICT risk management framework")
            remediation.append("Establish documented process linking threat intel analysis to Art.13(3) framework updates")
        else:
            evidence.append(f"{len(feeds_framework)} sources feed into ICTRMF update process")

        status = (
            ComplianceStatus.COMPLIANT if not findings
            else ComplianceStatus.PARTIAL if len(findings) <= 1
            else ComplianceStatus.NON_COMPLIANT
        )
        return AssessmentResult("Art.13(1)", "Threat Intelligence", status, findings, evidence, remediation)

    def check_post_incident_reviews(self) -> AssessmentResult:
        """Art.13(2): Validate post-incident review completeness and timeliness."""
        findings, evidence, remediation = [], [], []

        major_incidents = [
            r for r in self.config.post_incident_reviews
            if r.severity == IncidentSeverity.MAJOR
        ]

        if not major_incidents:
            evidence.append("No major ICT incidents recorded — no Art.13(2) review obligation triggered")
            return AssessmentResult("Art.13(2)", "Post-Incident Reviews",
                                    ComplianceStatus.COMPLIANT, [], evidence, [])

        for review in major_incidents:
            timeliness = review.check_timeliness()

            if not review.rca_completed:
                findings.append(f"Incident {review.incident_id}: RCA not completed")
                remediation.append(f"Complete root cause analysis for incident {review.incident_id}")
            elif timeliness.get("rca_timeliness") == ComplianceStatus.NON_COMPLIANT:
                findings.append(f"Incident {review.incident_id}: RCA completed after 30-day window")

            if not review.corrective_action_plan:
                findings.append(f"Incident {review.incident_id}: Corrective Action Plan missing")
                remediation.append(f"Develop CAP with SMART milestones for incident {review.incident_id}")

            if not review.lessons_learned_report:
                findings.append(f"Incident {review.incident_id}: Lessons Learned Report missing")
                remediation.append(f"Complete Lessons Learned Report for incident {review.incident_id}")

            if review.third_party_involved and not review.third_party_cooperation_documented:
                findings.append(f"Incident {review.incident_id}: Third-party involved but cooperation not documented")
                remediation.append("Document third-party cooperation per Art.13(2) requirement")

            if review.rca_completed and review.corrective_action_plan and review.lessons_learned_report:
                evidence.append(f"Incident {review.incident_id}: Full review completed (RCA + CAP + LL)")

        status = (
            ComplianceStatus.COMPLIANT if not findings
            else ComplianceStatus.PARTIAL if len(findings) <= len(major_incidents)
            else ComplianceStatus.NON_COMPLIANT
        )
        return AssessmentResult("Art.13(2)", "Post-Incident Reviews", status, findings, evidence, remediation)

    def check_framework_updates(self) -> AssessmentResult:
        """Art.13(3): Validate ICT risk management framework update cadence."""
        findings, evidence, remediation = [], [], []

        if not self.config.framework_revisions:
            findings.append("No ICT risk management framework revisions documented")
            remediation.append("Establish ICTRMF versioning with annual review cycle and management sign-off")
            return AssessmentResult("Art.13(3)", "Framework Updates",
                                    ComplianceStatus.NON_COMPLIANT, findings, evidence, remediation)

        latest = max(self.config.framework_revisions, key=lambda r: r.effective_date)
        days_since_update = (date.today() - latest.effective_date).days

        if days_since_update > 365:
            findings.append(f"ICTRMF last updated {days_since_update} days ago — exceeds annual review requirement")
            remediation.append("Schedule immediate annual ICTRMF review with management body sign-off")
        else:
            evidence.append(f"ICTRMF updated {days_since_update} days ago (version {latest.version})")

        unsigned = [r for r in self.config.framework_revisions if not r.management_sign_off]
        if unsigned:
            findings.append(f"{len(unsigned)} framework revisions lack management body sign-off")
            remediation.append("Obtain retroactive management body sign-off or document approval evidence")

        annual_reviews = [r for r in self.config.framework_revisions if r.review_type == "annual"]
        if not annual_reviews:
            findings.append("No annual scheduled reviews documented — only incident/other triggered updates")
            remediation.append("Establish dedicated annual review as separate scheduled process")
        else:
            evidence.append(f"{len(annual_reviews)} annual reviews documented in revision history")

        status = (
            ComplianceStatus.COMPLIANT if not findings
            else ComplianceStatus.PARTIAL if len(findings) <= 1
            else ComplianceStatus.NON_COMPLIANT
        )
        return AssessmentResult("Art.13(3)", "Framework Updates", status, findings, evidence, remediation)

    def check_security_awareness_training(self) -> AssessmentResult:
        """Art.13(4): Validate ICT security awareness and resilience training programs."""
        findings, evidence, remediation = [], [], []

        if not self.config.training_records:
            findings.append("No ICT security awareness training records documented")
            remediation.append("Implement annual security awareness program with completion tracking")
            return AssessmentResult("Art.13(4)", "Security Awareness Training",
                                    ComplianceStatus.NON_COMPLIANT, findings, evidence, remediation)

        all_staff = [t for t in self.config.training_records if t.target_audience == "all_staff"]
        if not all_staff:
            findings.append("No all-staff ICT security awareness training program found")
            remediation.append("Implement mandatory annual all-staff security awareness training")
        else:
            overdue = [t for t in all_staff if t.is_overdue]
            if overdue:
                findings.append(f"{len(overdue)} all-staff training programs overdue")
                remediation.append("Reschedule overdue training programs immediately")
            low_completion = [t for t in all_staff if t.completion_rate_pct < 90.0]
            if low_completion:
                findings.append(f"{len(low_completion)} training programs below 90% completion rate")
                remediation.append("Escalate non-completion to business function managers; consider mandatory completion policy")
            else:
                evidence.append(f"All-staff training completion rate ≥ 90% across {len(all_staff)} programs")

        mgmt_training = [t for t in self.config.training_records if t.target_audience == "management_body"]
        if not mgmt_training:
            findings.append("No management body ICT security training documented (Art.5(3) + Art.13(4))")
            remediation.append("Implement mandatory annual management body cybersecurity training session")
        else:
            mgmt_incomplete = [t for t in mgmt_training if not t.management_body_included]
            if mgmt_incomplete:
                findings.append("Management body training records lack individual completion attestation")
                remediation.append("Collect signed attestations from each management body member")
            else:
                evidence.append("Management body training with individual attestation documented")

        status = (
            ComplianceStatus.COMPLIANT if not findings
            else ComplianceStatus.PARTIAL if len(findings) <= 2
            else ComplianceStatus.NON_COMPLIANT
        )
        return AssessmentResult("Art.13(4)", "Security Awareness Training", status, findings, evidence, remediation)

    def check_sector_learning(self) -> AssessmentResult:
        """Art.13(5): Validate sector-wide learning and information sharing."""
        findings, evidence, remediation = [], [], []

        if not self.config.isac_membership:
            findings.append("No FS-ISAC or equivalent sector information sharing membership")
            remediation.append("Join FS-ISAC or national financial sector ISAC; document participation as Art.13(5) evidence")
        else:
            evidence.append("FS-ISAC / sector ISAC membership active")

        if not self.config.sector_learning_register_maintained:
            findings.append("No sector learning register maintained — cannot evidence Art.13(5) compliance")
            remediation.append("Implement sector learning register: log each sector threat bulletin reviewed + action taken")
        else:
            evidence.append("Sector learning register maintained with review and action documentation")

        status = (
            ComplianceStatus.COMPLIANT if not findings
            else ComplianceStatus.PARTIAL if len(findings) == 1
            else ComplianceStatus.NON_COMPLIANT
        )
        return AssessmentResult("Art.13(5)", "Sector Learning", status, findings, evidence, remediation)

    def run_full_assessment(self) -> "AssessmentReport":
        results = [
            self.check_threat_intelligence(),
            self.check_post_incident_reviews(),
            self.check_framework_updates(),
            self.check_security_awareness_training(),
            self.check_sector_learning(),
        ]
        return AssessmentReport(entity=self.config.entity_name, results=results)


@dataclass
class AssessmentReport:
    entity: str
    results: list[AssessmentResult]

    def summary(self) -> dict:
        counts = {s: 0 for s in ComplianceStatus}
        for r in self.results:
            counts[r.status] += 1
        return {
            "entity": self.entity,
            "total_checks": len(self.results),
            "compliant": counts[ComplianceStatus.COMPLIANT],
            "partial": counts[ComplianceStatus.PARTIAL],
            "non_compliant": counts[ComplianceStatus.NON_COMPLIANT],
            "overall_status": (
                "COMPLIANT" if counts[ComplianceStatus.NON_COMPLIANT] == 0 and counts[ComplianceStatus.PARTIAL] == 0
                else "PARTIAL" if counts[ComplianceStatus.NON_COMPLIANT] == 0
                else "NON_COMPLIANT"
            )
        }

    def to_markdown(self) -> str:
        lines = [f"# DORA Art.13 Assessment: {self.entity}\n"]
        for r in self.results:
            icon = {"COMPLIANT": "✅", "PARTIAL": "⚠️", "NON_COMPLIANT": "❌", "NOT_ASSESSED": "⬜"}
            lines.append(f"## {icon.get(r.status.value, '?')} {r.article} — {r.obligation}: {r.status.value}")
            if r.findings:
                lines.append("**Findings:**")
                for f in r.findings:
                    lines.append(f"- {f}")
            if r.evidence_items:
                lines.append("**Evidence:**")
                for e in r.evidence_items:
                    lines.append(f"- {e}")
            if r.remediation:
                lines.append("**Remediation:**")
                for rem in r.remediation:
                    lines.append(f"- {rem}")
            lines.append("")
        return "\n".join(lines)


# --- Example Usage ---

if __name__ == "__main__":
    config = EntityConfig(
        entity_name="Example EU Payment Institution",
        threat_intel_sources=[
            ThreatIntelSource("ENISA Threat Landscape", ThreatIntelTier.STRATEGIC, 365,
                              date(2026, 2, 1), feeds_into_framework=True),
            ThreatIntelSource("FS-ISAC", ThreatIntelTier.OPERATIONAL, 7,
                              date(2026, 4, 10), feeds_into_framework=True),
            ThreatIntelSource("CVE/NVD Feed", ThreatIntelTier.TECHNICAL, 1,
                              date(2026, 4, 16), feeds_into_framework=False),
        ],
        post_incident_reviews=[
            PostIncidentReview(
                incident_id="INC-2026-001",
                incident_date=date(2026, 1, 15),
                severity=IncidentSeverity.MAJOR,
                resolution_date=date(2026, 1, 17),
                rca_completed=True,
                rca_completion_date=date(2026, 2, 5),   # 19 days — compliant
                corrective_action_plan=True,
                cap_completion_date=date(2026, 2, 28),  # 42 days — compliant
                lessons_learned_report=True,
                ll_sign_off_date=date(2026, 3, 15),     # 57 days — compliant
                framework_update_triggered=True,
                third_party_involved=True,
                third_party_cooperation_documented=True,
            ),
        ],
        framework_revisions=[
            FrameworkRevision("v2.0", date(2025, 11, 1), "annual",
                              management_sign_off=True, next_review_date=date(2026, 11, 1)),
            FrameworkRevision("v2.1", date(2026, 2, 20), "incident",
                              "INC-2026-001", management_sign_off=True),
        ],
        training_records=[
            TrainingRecord("Annual Security Awareness", "all_staff",
                           date(2026, 3, 1), completion_rate_pct=96.0),
            TrainingRecord("Board Cybersecurity Briefing", "management_body",
                           date(2026, 2, 15), completion_rate_pct=100.0,
                           management_body_included=True),
            TrainingRecord("DORA Obligations — Contract Managers", "specialist",
                           date(2026, 1, 20), completion_rate_pct=88.0),
        ],
        isac_membership=True,
        sector_learning_register_maintained=True,
    )

    checker = DORALearningEvolvingChecker(config)
    report = checker.run_full_assessment()
    print(report.to_markdown())
    print(report.summary())

9. DORA × NIS2 Art.21(2) Dual-Compliance Mapping

Financial entities subject to both DORA (as financial sector entities) and NIS2 (as operators of essential services) benefit from coordinated compliance. DORA is lex specialis for financial entities under NIS2 Art.3(1) — DORA compliance satisfies equivalent NIS2 requirements. The Art.13 learning loop maps directly to NIS2 Art.21(2) measures:

DORA Art.13 RequirementNIS2 Art.21(2) MappingDual-Compliance Note
Art.13(1): Threat intelligence gatheringArt.21(2)(b): Incident handling + threat monitoringDORA scope broader (all ICT threats, not just incidents)
Art.13(2): Post-incident RCAArt.21(2)(b): Post-incident analysisDORA requires RCA within defined timelines; NIS2 less prescriptive
Art.13(3): Framework update cadenceArt.21(2)(a): Risk analysis and ICT security policiesDORA mandates annual minimum; NIS2 references "appropriate" measures
Art.13(4): Security awareness trainingArt.21(2)(g): Cybersecurity awareness + basic trainingDORA requires management body training explicitly (Art.5(3) linkage)
Art.13(5): Sector learning / info sharingArt.21(2)(h): Vulnerability disclosure + information sharingDORA Art.45 voluntary sharing + Art.13(5) learning obligation
Post-incident review → ICTRMF updateArt.21(1): Risk-proportionate measures, regularly reviewedDORA "at least annually" > NIS2 "regularly" — DORA sets the higher bar
Third-party cooperation in reviewsArt.21(2)(h) + NIS2 Art.21(3): Supply chainDORA Art.30 contract clause requirement more specific than NIS2

Key lex specialis principle: NCA supervision under DORA satisfies equivalent NIS2 supervisory requirements for financial entities. A single audit trail covering Art.13 satisfies both frameworks — no duplication of evidence needed.


10. Common NCA Audit Failures — Art.13

Based on supervisory guidance and peer entity assessments, seven Art.13 failure patterns recur in NCA examinations:

  1. RCA without root cause: Post-incident reviews document what happened (timeline) but not why it happened (root cause). "Server went down" is a description; "Unpatched CVE-2025-XXXX in load balancer firmware, missed due to absent patch management process for network devices" is a root cause.

  2. CAP without owners: Corrective Action Plans list actions without named owners or measurable deadlines. NCAs require SMART milestones with accountability.

  3. Framework version without delta: New framework versions are issued without documenting what changed from the previous version. Version history must include change summaries, not just dates.

  4. Training completion without management body: Security awareness training covers all staff but management body participation is treated as optional or is not separately tracked. Art.5(3) makes management body training mandatory.

  5. Threat intel without analysis: Financial entities subscribe to threat feeds but cannot demonstrate that feed content was analysed for relevance to their asset inventory. Subscription receipts ≠ analysis evidence.

  6. Sector learning without a register: Entities participate in information sharing forums but have no documented register of sector incidents reviewed and actions taken. The obligation is to draw lessons, not merely to receive information.

  7. Incident-triggered updates without documentation: The ICTRMF is updated in response to incidents but the update is not formally documented as an "incident-triggered revision" with reference to the triggering RCA. The linkage must be explicit.


11. The 25-Item NCA Audit Checklist (Art.13)

Art.13(1): Threat Intelligence (5 items)

Art.13(2): Post-Incident Reviews (8 items)

Art.13(3): Framework Updates (5 items)

Art.13(4): Security Awareness Training (4 items)

Art.13(5): Sector Learning (3 items)


12. 12-Week Art.13 Implementation Timeline

WeekFocus AreaDeliverable
1-2Threat Intelligence AuditInventory all current sources; map to tiers; identify gaps
2-3TI Process DesignDocument analysis process; assign owners; define escalation criteria
3-4PIR PolicyDraft post-incident review policy; define timelines and templates
4-5PIR BacklogReview all prior major incidents; identify missing RCAs/CAPs/LLRs
5-6Framework VersioningImplement version control; produce current version with full delta history
6-7Annual Review ProcessSchedule annual review; define input sources; create management body agenda
7-8Training Gap AssessmentAudit existing programs; identify coverage gaps (management body, roles)
8-9Training Program UpdateUpdate content based on recent incidents; add management body session
9-10Sector Learning RegisterJoin FS-ISAC (if not member); implement register template
10-11DORALearningEvolvingCheckerDeploy self-assessment tool; run initial assessment
11-12Evidence PackageCompile Art.13 NCA evidence folder; run checklist; management sign-off

What Comes Next in the DORA Series

DORA Chapter II ends with Art.14 (Communication), which governs internal and external communication plans for ICT-related incidents — a distinct requirement from the incident reporting obligations of Art.17-23. The Chapter III series on threat-led penetration testing (Art.24-27) covers the TLPT program that directly feeds back into Art.13(3) via testing-triggered framework updates.