2026-05-30·5 min read·sota.io Team

EU AI Act Art.73 Incident Reporting: Building Your Monitoring Stack for August 2026

Post #1409 in the sota.io EU AI Act August 2026 Sprint Series

EU AI Act Art.73 monitoring stack integration diagram showing Prometheus, OpenTelemetry, and PagerDuty connected to an incident reporting pipeline

The August 2, 2026 EU AI Act deadline is 64 days away. Most SaaS developers have read the compliance guides. Fewer have asked: when a serious incident actually happens at 3am, how does my monitoring system know?

This post covers the technical implementation of Art.73-ready incident detection. The previous posts in this sprint covered what triggers reporting (definitions), when to report (2/10/15-day timelines), and what to write (templates). This post answers: how do you wire your existing monitoring stack to detect and escalate Art.73 events automatically?

The Compliance Gap Most Teams Miss

Your Prometheus alert rules likely fire on: CPU spikes, error rates, latency. They don't fire on: AI harm events, unexpected model behavior, or decisions that affect fundamental rights.

Under Art.73, a "serious incident" from a high-risk AI system includes:

Your monitoring stack almost certainly doesn't have alert rules for any of these categories. Here's how to add them.

Step 1: Instrument Your AI Decisions with OpenTelemetry

Before you can alert on harmful decisions, your AI model calls need structured telemetry. Add an OpenTelemetry span around every AI decision that could trigger Art.73:

import { trace, SpanStatusCode } from '@opentelemetry/api'

const tracer = trace.getTracer('ai-decision-tracer', '1.0.0')

interface AIDecisionContext {
  modelId: string
  userId: string
  decisionType: 'high-risk' | 'standard'
  outputCategory: string
  affectsRights: boolean
}

export async function instrumentedAIDecision<T>(
  ctx: AIDecisionContext,
  fn: () => Promise<T>
): Promise<T> {
  return tracer.startActiveSpan('ai.decision', async (span) => {
    span.setAttributes({
      'ai.model_id': ctx.modelId,
      'ai.decision_type': ctx.decisionType,
      'ai.affects_fundamental_rights': ctx.affectsRights,
      'ai.output_category': ctx.outputCategory,
      'eu.regulation': 'ai_act',
      'eu.article': '73',
    })

    try {
      const result = await fn()
      span.setStatus({ code: SpanStatusCode.OK })
      return result
    } catch (err) {
      span.setStatus({ code: SpanStatusCode.ERROR, message: String(err) })
      span.recordException(err as Error)
      throw err
    } finally {
      span.end()
    }
  })
}

Key attributes to always set on AI decision spans:

AttributeValuesWhy
ai.decision_typehigh-risk / standardOnly high-risk AI triggers Art.73
ai.affects_fundamental_rightsbooleanDetermines harm category
ai.output_categoryemployment / credit / healthcare / safetyMaps to Annex III
eu.article73Enables filtered dashboards

Step 2: Define Art.73 Severity Levels in Your Codebase

Create a shared enum that maps your AI system's harm categories to Art.73 reporting timelines:

// lib/ai-act/incident-severity.ts

export enum Art73Severity {
  /** Critical infrastructure disruption or large-scale harm: 2 working days */
  CRITICAL_INFRA = 'critical_infra',
  /** Death or serious irreversible harm: 10 working days */
  DEATH_OR_SERIOUS_HARM = 'death_or_serious_harm',
  /** Other fundamental rights violations: 15 working days */
  OTHER_SERIOUS = 'other_serious',
  /** Below threshold: no Art.73 report needed */
  BELOW_THRESHOLD = 'below_threshold',
}

export const REPORTING_DAYS: Record<Art73Severity, number | null> = {
  [Art73Severity.CRITICAL_INFRA]: 2,
  [Art73Severity.DEATH_OR_SERIOUS_HARM]: 10,
  [Art73Severity.OTHER_SERIOUS]: 15,
  [Art73Severity.BELOW_THRESHOLD]: null,
}

export function classifyArt73Incident(event: {
  affectsCriticalInfrastructure: boolean
  affectsLargeNumberOfPeople: boolean
  deathOrIrreversibleHarm: boolean
  fundamentalRightsViolation: boolean
  highRiskCategory: boolean
}): Art73Severity {
  if (!event.highRiskCategory) return Art73Severity.BELOW_THRESHOLD

  if (event.affectsCriticalInfrastructure || event.affectsLargeNumberOfPeople) {
    return Art73Severity.CRITICAL_INFRA
  }

  if (event.deathOrIrreversibleHarm) {
    return Art73Severity.DEATH_OR_SERIOUS_HARM
  }

  if (event.fundamentalRightsViolation) {
    return Art73Severity.OTHER_SERIOUS
  }

  return Art73Severity.BELOW_THRESHOLD
}

This function becomes the single source of truth for severity classification. Feed it into your observability pipeline.

Step 3: Prometheus Alert Rules for AI System Incidents

Add these alerting rules to your prometheus/alerts.yaml. They fire when your OpenTelemetry metrics cross thresholds associated with serious incidents:

# prometheus/alerts/ai-act-art73.yaml
groups:
  - name: eu_ai_act_art73
    rules:

      # Fires when high-risk AI decision error rate exceeds threshold
      - alert: HighRiskAIDecisionErrors
        expr: |
          rate(ai_decision_errors_total{decision_type="high-risk"}[5m]) > 0.05
        for: 2m
        labels:
          severity: warning
          regulation: eu_ai_act
          article: "73"
        annotations:
          summary: "High-risk AI decision error rate elevated"
          description: "Error rate {{ $value | humanizePercentage }} on high-risk AI decisions. Investigate for Art.73 reporting triggers."
          runbook_url: "https://internal/runbooks/ai-act-art73"

      # Fires when AI system makes decisions flagged as rights-affecting
      - alert: AIFundamentalRightsImpact
        expr: |
          increase(ai_decisions_fundamental_rights_affected_total[1h]) > 0
        for: 0m
        labels:
          severity: critical
          regulation: eu_ai_act
          article: "73"
          reporting_window: "15_working_days"
        annotations:
          summary: "AI decision flagged as affecting fundamental rights"
          description: "{{ $value }} decision(s) in the last hour tagged as affecting fundamental rights. Art.73 reporting may be required within 15 working days."

      # Critical: fires when AI system affects critical infrastructure
      - alert: AIActCriticalInfraImpact
        expr: |
          increase(ai_decisions_critical_infra_impact_total[1h]) > 0
        for: 0m
        labels:
          severity: critical
          regulation: eu_ai_act
          article: "73"
          reporting_window: "2_working_days"
        annotations:
          summary: "CRITICAL: AI impact on critical infrastructure detected"
          description: "Art.73 CRITICAL: AI decision flagged as affecting critical infrastructure. Initial NCA report required within 2 WORKING DAYS."

The metrics these rules reference must be emitted from your application. Add counters in your AI decision path:

import { metrics } from '@opentelemetry/api'

const meter = metrics.getMeter('ai-act-compliance', '1.0.0')

export const aiMetrics = {
  decisionErrors: meter.createCounter('ai_decision_errors_total', {
    description: 'Total AI decision errors by type',
  }),
  fundamentalRightsImpact: meter.createCounter(
    'ai_decisions_fundamental_rights_affected_total',
    { description: 'Decisions flagged as affecting fundamental rights' }
  ),
  criticalInfraImpact: meter.createCounter(
    'ai_decisions_critical_infra_impact_total',
    { description: 'Decisions flagged as affecting critical infrastructure' }
  ),
}

Step 4: Structured Incident Log for NCA Submissions

Your incident logging format should produce a record that can be directly attached to an NCA report. Create a dedicated incident log endpoint:

// app/api/ai-incident-log/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import { classifyArt73Incident, REPORTING_DAYS } from '@/lib/ai-act/incident-severity'

interface IncidentEvent {
  modelId: string
  inputHash: string        // SHA-256 of input — NOT the input itself
  outputSummary: string    // Non-PII summary of AI output
  userId: string           // Pseudonymised
  sessionId: string
  affectsCriticalInfrastructure: boolean
  affectsLargeNumberOfPeople: boolean
  deathOrIrreversibleHarm: boolean
  fundamentalRightsViolation: boolean
  highRiskCategory: boolean
  context: Record<string, unknown>
}

export async function POST(req: NextRequest) {
  const event: IncidentEvent = await req.json()

  const severity = classifyArt73Incident(event)
  const reportingDays = REPORTING_DAYS[severity]

  if (reportingDays === null) {
    return NextResponse.json({ logged: false, reason: 'below_threshold' })
  }

  const incident = await prisma.art73IncidentLog.create({
    data: {
      modelId: event.modelId,
      inputHash: event.inputHash,
      outputSummary: event.outputSummary,
      pseudonymisedUserId: event.userId,
      severity: severity,
      reportingDeadlineDays: reportingDays,
      detectedAt: new Date(),
      reportDueBy: addWorkingDays(new Date(), reportingDays),
      contextJson: JSON.stringify(event.context),
      status: 'detected',
    },
  })

  // Trigger PagerDuty/OpsGenie alert for incidents requiring reporting
  await triggerIncidentAlert(incident)

  return NextResponse.json({
    incidentId: incident.id,
    severity,
    reportDueBy: incident.reportDueBy,
    reportingDays,
  })
}

function addWorkingDays(date: Date, days: number): Date {
  let d = new Date(date)
  let added = 0
  while (added < days) {
    d.setDate(d.getDate() + 1)
    const day = d.getDay()
    if (day !== 0 && day !== 6) added++ // skip weekends
  }
  return d
}

Prisma schema addition:

model Art73IncidentLog {
  id                   String   @id @default(cuid())
  modelId              String
  inputHash            String   // SHA-256, NOT raw input
  outputSummary        String
  pseudonymisedUserId  String
  severity             String
  reportingDeadlineDays Int
  detectedAt           DateTime
  reportDueBy          DateTime
  contextJson          String
  status               String   // detected | reported | closed
  ncaReferenceNumber   String?
  createdAt            DateTime @default(now())
  updatedAt            DateTime @updatedAt

  @@index([severity, status])
  @@index([reportDueBy])
}

Step 5: PagerDuty / OpsGenie Escalation for Art.73 Deadlines

The incident is detected. Now you need an escalation chain that respects the 2/10/15-day windows. Here's a PagerDuty automation trigger:

// lib/ai-act/alert-trigger.ts

interface Art73Alert {
  incidentId: string
  severity: Art73Severity
  reportDueBy: Date
  modelId: string
  outputSummary: string
}

export async function triggerIncidentAlert(incident: {
  id: string
  severity: string
  reportDueBy: Date
  modelId: string
  outputSummary: string
}) {
  const urgency = incident.severity === 'critical_infra' ? 'high' : 'low'

  // PagerDuty Events API v2
  const payload = {
    routing_key: process.env.PAGERDUTY_AI_ACT_ROUTING_KEY,
    event_action: 'trigger',
    dedup_key: `art73-${incident.id}`,
    payload: {
      summary: `[EU AI Act Art.73] ${incident.severity} incident — report due ${incident.reportDueBy.toISOString().split('T')[0]}`,
      severity: urgency === 'high' ? 'critical' : 'warning',
      source: incident.modelId,
      custom_details: {
        incident_id: incident.id,
        regulation: 'EU AI Act Art.73',
        reporting_deadline: incident.reportDueBy.toISOString(),
        output_summary: incident.outputSummary,
        runbook: 'https://internal/runbooks/ai-act-art73-reporting',
      },
    },
  }

  await fetch('https://events.pagerduty.com/v2/enqueue', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })
}

Configure separate PagerDuty services for each severity tier:

SeverityUrgencyEscalation Policy
critical_infra (2 days)HighPage CTO immediately, 30-min escalation
death_or_serious_harm (10 days)HighPage DPO + Legal within 4 hours
other_serious (15 days)LowSlack alert to compliance team, 24h SLA

Step 6: Grafana Dashboard for NCA Submission Tracking

Add an Art.73 compliance dashboard. Minimum panels:

  1. Incident count by severity (bar chart, last 30 days)
  2. Open incidents by deadline proximity (table: incidents due in <2 days / <5 days / <15 days)
  3. Reporting status funnel (detectedreportedclosed)
  4. AI decision anomaly rate (time series: ai_decision_errors_total vs baseline)

Key Grafana query for deadline tracking:

# Incidents due within 48 hours (working days)
count by (severity) (
  art73_open_incidents{
    report_due_within_hours=~"0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48"
  }
)

Step 7: The Art.73 Incident Response Runbook

Add this as a page in your internal wiki, linked from every PagerDuty alert:

# Art.73 Incident Response Runbook

## On alert receipt
1. Open incident log: https://app.internal/admin/art73-incidents
2. Confirm severity classification (did classifyArt73Incident() get it right?)
3. Adjust if needed — update `severity` and `reportDueBy`

## Within 24 hours of detection
- [ ] Legal team notified
- [ ] DPO notified
- [ ] Incident documentation started (use Art.73 template)
- [ ] Preliminary root cause hypothesis

## Before reporting deadline
- [ ] NCA contact identified (see: our market surveillance authority)
- [ ] Art.73 report drafted (use template from [post #1391])
- [ ] Legal review complete
- [ ] Report submitted to NCA via official portal

## After submission
- [ ] `ncaReferenceNumber` entered in Art73IncidentLog
- [ ] Status updated to `reported`
- [ ] Post-mortem scheduled
- [ ] Corrective action plan documented

Implementation Checklist for August 2026

Copy this to your sprint board:

What's Next: Sprint Week 5

The next post in this sprint covers the complete August 2026 compliance checklist — a final consolidated reference covering Art.9, Art.50, Art.73, and the technical documentation requirements, so you can hand it to your legal team and say "we're ready."


Previous in this sprint:

Need EU-compliant hosting for your AI-powered SaaS? sota.io is a managed PaaS built for European developers — GDPR-native, no US parent, no CLOUD Act exposure. Deploy any language from €9/mo.

EU-Native Hosting

Ready to move to EU-sovereign infrastructure?

sota.io is a German-hosted PaaS — no CLOUD Act exposure, no US jurisdiction, full GDPR compliance by design. Deploy your first app in minutes.