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
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:
- Death or serious harm to a person — 10 working days to report
- Serious disruption of critical infrastructure, or harm to a large number of people — 2 working days
- Other serious incidents affecting fundamental rights — 15 working days
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:
| Attribute | Values | Why |
|---|---|---|
ai.decision_type | high-risk / standard | Only high-risk AI triggers Art.73 |
ai.affects_fundamental_rights | boolean | Determines harm category |
ai.output_category | employment / credit / healthcare / safety | Maps to Annex III |
eu.article | 73 | Enables 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:
| Severity | Urgency | Escalation Policy |
|---|---|---|
critical_infra (2 days) | High | Page CTO immediately, 30-min escalation |
death_or_serious_harm (10 days) | High | Page DPO + Legal within 4 hours |
other_serious (15 days) | Low | Slack alert to compliance team, 24h SLA |
Step 6: Grafana Dashboard for NCA Submission Tracking
Add an Art.73 compliance dashboard. Minimum panels:
- Incident count by severity (bar chart, last 30 days)
- Open incidents by deadline proximity (table: incidents due in <2 days / <5 days / <15 days)
- Reporting status funnel (
detected→reported→closed) - AI decision anomaly rate (time series:
ai_decision_errors_totalvs 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:
- OTel instrumentation: All high-risk AI calls wrapped with
instrumentedAIDecision() - Severity classifier:
classifyArt73Incident()function deployed and unit-tested - Prometheus metrics:
ai_decision_errors_total,ai_decisions_fundamental_rights_affected_total,ai_decisions_critical_infra_impact_totalemitted - Alert rules:
HighRiskAIDecisionErrors,AIFundamentalRightsImpact,AIActCriticalInfraImpactlive in Prometheus - Incident log API:
POST /api/ai-incident-logendpoint live, writing to DB - PagerDuty service:
PAGERDUTY_AI_ACT_ROUTING_KEYconfigured, escalation policy live - Grafana dashboard: Art.73 compliance dashboard published
- Runbook: Published and linked from PagerDuty alerts
- Working-days calculator:
addWorkingDays()function unit-tested (weekends excluded) - GDPR alignment: Incident log stores pseudonymised user IDs and input hashes only
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:
- Post #1406: 9-Week Sprint Plan
- Post #1407: Art.9 Risk Management System Implementation
- Post #1408: Art.50 Transparency Complete Implementation Guide
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.