Governance, compliance & bias
Defenses keep a system safe; governance makes it accountable: EU AI Act risk tiers, model cards, audit trails, bias auditing, and the incident runbook a regulated org needs.
Learning objectives
- Explain the governance an org needs around a live AI system.
- Place a system in the EU AI Act risk tiers and know the obligations.
- Write a model card and keep an audit trail.
- Audit for bias and fairness, and run an AI incident.
code/rt5-governance/ in the course, with a README. Run the scripts or copy the configs directly.From safe to accountable advanced
Picture the moment every AI team dreads. Your support agent has been live for six months. Then a customer forwards a screenshot: the bot approved a refund it should have escalated, quoting a policy that doesn't exist. Legal calls. Your VP asks three questions in a row — Who signed off on this going live? Can you show me exactly what the model was told and what it decided? What happens now, this hour, to stop it? — and you realize you can't answer any of them. The model was safe in the sense that it had guardrails. It was not accountable: no one owned it, nothing was written down, and there was no plan for the bad day. That gap between "we added defenses" and "we can stand behind this in front of a regulator" is exactly what this chapter closes.
Here is the distinction in one line: defenses reduce the chance of harm; governance is how the organization takes responsibility for it. Think of it like a commercial airliner. Anti-lock brakes and redundant hydraulics are the defenses — engineering that makes the plane safer to fly. But you'd never board a plane that had only those. What lets it carry passengers is the governance around it: a certified type rating, a maintenance logbook every mechanic signs, a checklist the crew runs before every takeoff, and an accident-investigation process for when something does go wrong. Governance is the paperwork, the ownership, and the process that turn "probably safe" into "answerable to someone."
Concretely, governance for a live AI system is four things, and every later section of this chapter is one of them: a risk classification (how much scrutiny does this system legally require?), documentation (what is it for, and what did we test?), an audit trail (what actually happened, provably?), and an incident process (what do we do the day it breaks?). The common mistake engineers make is treating all of this as bureaucracy that slows shipping — a tax paid to a compliance team after the real work is done. It's the opposite: governance decided early shapes the architecture. If you know on day one that a hiring tool is legally high-risk, you build in human review, logging, and fairness testing from the first commit, rather than bolting them on in a panic after launch. This is why it belongs in a red-team track and not just a legal binder: the engineer who understands governance builds systems that are defensible by design.
Defenses (RT4) keep a system safe; governance makes it accountable — the documentation, compliance, and process a regulated org (like a financial-services company) needs. It extends O4 and W14 into legal and ethical obligations.
Read it left to right: start from what you already have, add the four middle boxes, and arrive at the payoff on the right.
- The blue box on the left is the RT4 defenses — guardrails that reduce the chance of harm. That is where the previous chapter stopped.
- The four white boxes in the middle are the four things governance adds: a risk tier (how much legal scrutiny), documentation (the model card), an audit trail (a provable record of what happened), and an incident process (the plan for the bad day). Each is a numbered section later on this page.
- The green box on the right is the payoff: only with all four does "probably safe" become accountable — something you can stand behind in front of a regulator.
In short: safe is engineering; accountable is safe plus the four governance pillars.
EU AI Act risk tiers advanced
Two teams build a chatbot. One ships in a week; the other spends three months in review. Why? Because one bot answers product questions and the other decides who gets a mortgage. Same technology, wildly different consequences if it's wrong — and the law noticed. Regulators realized long ago that you cannot write one rule for "AI," any more than you'd write one safety rule for both a toaster and a nuclear reactor. So instead of regulating the technology, they regulate the stakes: the more a system can hurt someone, the more you have to prove before you turn it on.
That is the whole idea behind a risk-tiered framework, and the EU AI Act is the one that has become the global reference point (much as GDPR became the default for privacy — companies far outside Europe adopt it because it's the strictest bar, and clearing it clears the others). It sorts every AI system into four buckets by potential for harm. Think of it as a customs form: before your system crosses into production, you declare what it does, and that declaration determines how much inspection it gets. A spam filter waves through; a credit-scoring model gets opened and searched.
- Unacceptable — uses judged to violate fundamental rights, like government social-scoring of citizens. These aren't regulated, they're banned. The answer is no.
- High-risk — systems that make consequential decisions about people's lives: credit, hiring, medical, education, law enforcement. Allowed, but only with heavy obligations (documentation, testing, human oversight, audit).
- Limited — systems where the main risk is deception, like a chatbot a user might mistake for a human. The obligation is transparency: disclose that it's AI.
- Minimal — everything else, like a spam filter or a game AI. Few or no requirements.
The reason this matters to you as a builder and not just a lawyer: your tier is a design constraint, not a paperwork step at the end. The single most expensive mistake here is discovering late that your "simple internal tool" is actually high-risk — because it screens job applicants, say — after you've already built it with no logging, no human-in-the-loop, and no fairness data. Now you're re-architecting under deadline pressure. Classify honestly on day one and the obligations become a checklist you build toward, not a wall you hit. And classify to the use, not the tech: the same LLM is minimal-risk writing marketing copy and high-risk ranking loan applications.
The EU AI Act classifies AI systems by risk, and the tier sets your obligations. Even outside the EU it's becoming the reference framework.
Read the stack from the bottom up — that's the direction of increasing stakes, marked by the arrow on the right.
- The green bar at the bottom is Minimal risk (spam filters, game AI) — few or no requirements. The blue bar above it is Limited risk, where the only duty is transparency: tell the user it's AI.
- The amber bar is High-risk — consequential decisions about people (credit, hiring, medical) that carry the heavy obligations: conformity assessment, documentation, human oversight, and audit. The red bar at the top is Unacceptable: not regulated but outright banned.
- The bars narrow as they rise because fewer systems sit in each higher tier, and the color shift from green to red is the escalating consequence of being wrong.
In short: your tier is a design constraint — classify honestly on day one and the obligations become a checklist, not a wall.
| Tier | Examples | Obligation |
|---|---|---|
| Unacceptable | social scoring | banned |
| High-risk | credit scoring, hiring, medical | conformity assessment, docs, human oversight |
| Limited | chatbots | transparency (disclose it's AI) |
| Minimal | spam filters | few requirements |
Model cards & audit trails expert
A new engineer joins your team and inherits a model that's been in production for a year. They ask the reasonable questions: What is this thing actually supposed to do? What data was it trained on? What does it explicitly not handle? How good is it, and for whom? If the only answers live in the head of someone who left the company, you don't have a model — you have a liability with an API. This is the everyday failure a model card prevents: undocumented systems that no one can safely change, audit, or defend.
A model card is the equivalent of the nutrition label on a food package, or the datasheet that ships with an electronic component. A resistor's datasheet tells you its resistance, its tolerance, and the voltage beyond which it burns out — the honest operating envelope. A model card does the same for an AI system: its intended use, its out-of-scope uses, the data it learned from (and the gaps in that data), how it scored in evaluation, how it performed per segment for fairness, the known risks, and — crucially — a named human owner. The format is deliberately short and boring; the value isn't in the prose, it's in forcing someone to write down the answers before shipping, where the empty fields expose the real gaps.
But a card describes intentions, and intentions aren't proof. That's the second half of this pairing: the audit trail. Where the model card says "here's what this system is supposed to do," the audit trail is the flight recorder that logs what it actually did — every prompt in, every response out, every decision, timestamped and retained. Why you need both is the key insight: the card lets you reason about the system in advance; the trail lets you reconstruct a specific incident afterward. When that refund-approval goes wrong from the opening story, the card tells the reviewer "this was never meant to auto-approve" and the trail proves exactly which input triggered which output at 3:47pm. One without the other leaves you half-blind. The common mistake is logging everything into an ordinary mutable database and calling it an audit trail — but if anyone (including an attacker or a panicked engineer) can quietly edit a past entry, it proves nothing. That's why real audit trails are append-only and often tamper-evident, a property you'll build with hash-chaining in the practice ladder below.
A model card documents what a model is for, its data, its limits, and its evaluations — the artifact that makes a system reviewable. Pair it with an audit trail (every prompt/response logged, from W14) so decisions are traceable after the fact.
Read the two boxes side by side — left is written before launch, right is written continuously as the system runs — then follow both arrows down to the shared payoff.
- The left blue box is the model card: intended and out-of-scope use, training data and its gaps, evaluation with per-segment fairness, and a named owner. It captures intentions, so you can reason about the system in advance.
- The right purple box is the audit trail: every prompt, response, and decision, timestamped and retained. It's append-only and hash-chained so it's tamper-evident — that's what lets it reconstruct a specific incident afterward.
- Both arrows converge on the green bar: you need both. The card tells a reviewer "this was never meant to do that"; the trail proves exactly which input caused which output.
In short: the card reasons forward, the trail reconstructs backward — accountability needs the pair.
MODEL_CARD.md# MODEL_CARD.md
## Model: support-triage-classifier v3
- **Intended use:** classify inbound support tickets by severity. NOT for
automated account actions.
- **Out-of-scope:** legal/medical advice; any irreversible action without review.
- **Training data:** 12k internal tickets (2023-24), PII-scrubbed. Known gap:
under-represents enterprise-tier tickets.
- **Evaluation:** accuracy 0.91 overall; see fairness section for per-segment.
- **Fairness:** measured across ticket language + region; max gap 3.2% (below
our 5% threshold). Re-audited each release.
- **Risks & mitigations:** indirect injection via ticket body -> input guardrail
+ isolation (RT4). Human review for severity=critical.
- **Owner / contact:** platform-safety@company; last review 2026-09.
A model card is a short document that says what an AI system is for, what data it used, its limits, and how it's evaluated — the artifact that makes a system reviewable and accountable. This is a filled-in skeleton you can copy for any project. It's plain Markdown, not code; the ## and - just format headings and bullets.
- Intended use / Out-of-scope — states exactly what the model should do (triage tickets by severity) and, just as importantly, what it must not do (no automated account actions, no legal/medical advice). Drawing the boundary is half the safety.
- Training data — where the data came from, that PII was scrubbed, and a known gap (under-represents enterprise tickets). Honest limitations are a feature of a good card, not a weakness.
- Evaluation / Fairness — an overall accuracy number plus a per-segment fairness check with a stated threshold (max gap 3.2%, below the 5% limit), re-run every release. Aggregate scores can hide that a model fails one group.
- Risks & mitigations / Owner — names each real risk (indirect injection via the ticket body) and the exact defense from RT4 (input guardrail + isolation), plus a named owner and review date so someone is accountable.
Try this: Write one of these for a course project before you ship it. If any line is hard to fill in (What's out of scope? Who owns it? What's the fairness gap?), that's a gap in the project, not just the doc.
Bias & fairness auditing expert
Here is the trap that catches good teams. Your model reports 91% accuracy, the dashboard is green, everyone celebrates, and you ship. Six months later a journalist shows that it approves 70% of applications from one group and 35% from another. Both facts are true at once: the aggregate looked fine because the large, well-served group drowned out the small, badly-served one in the average. A single headline number is a blender — it purées every group into one smooth score and hides exactly the disparity that gets you sued. Bias auditing is the discipline of refusing to be fooled by that average.
The core move is simple and you already have the instinct for it: don't ask "how well does it work?" — ask "how well does it work for each group?" Break your evaluation data into segments (by language, region, or protected-attribute proxies) and compute the metric separately for each. It's the same reason a doctor checks blood pressure in both arms rather than averaging them: the average can be normal while one side is dangerous. A model that's excellent overall but consistently worse for one demographic isn't "mostly fine" — it's discriminatory, and increasingly, illegally so.
To turn that instinct into a defensible number, fairness work leans on standard, court-tested metrics rather than gut feeling. The classic one is the four-fifths rule (also called the 80% rule), borrowed from decades of US employment-discrimination law: take the group the model favors most as the benchmark, and if any other group's selection rate falls below 80% of that benchmark, you have a disparate impact flag worth investigating. It's not a verdict — a sub-0.8 ratio can have a legitimate explanation — but it's a defensible tripwire that says "stop and look here," which you'll implement in the practice ladder. The essential discipline, and the mistake teams make, is treating this as a launch-day formality run once. Data drifts, populations shift, and a model that was fair at launch can drift unfair. So you set a threshold, measure it every release, and record the result in the model card — turning fairness from a one-time hope into a monitored, documented property.
Measure outcomes across segments (language, region, demographic proxies) — an aggregate metric can hide that a model works well for one group and poorly for another. Set a fairness threshold, measure every release, and record it in the model card.
Start at the green box on the left, follow the "split" arrow rightward, then read the two group rows and the verdict on the far right.
- The green box is the tempting single number — 0.91 accuracy, dashboard green. On its own it says nothing about who the model serves badly.
- The two white rows are the same evaluation split by group: Group A's selection rate is 0.66, Group B's is 0.33. The dashed line marks the four-fifths rule — take the most-favored rate (A = 0.66) as the reference and draw the tripwire at 80% of it.
- The red row and red box are the payoff: B's ratio against A is 0.50, below 0.8, so the four-fifths rule raises a disparate-impact flag — a defensible signal to investigate and document, not an automatic verdict.
In short: don't ask "how well does it work?" — ask "for each group?" and check the 80% line every release.
The AI incident runbook expert
It's 2am and your agent is doing something it shouldn't. Maybe a prompt injection has it leaking other customers' data; maybe a model update quietly regressed and it's giving dangerous advice. The pager goes off. In that moment, the worst possible thing is a room full of smart people improvising — arguing about whether to pull the whole service down, hunting for who has the access to do it, no one writing anything down. Panic doesn't scale, and improvisation under pressure is how a contained problem becomes a headline. The fix is the same one hospitals, fire departments, and airlines figured out long ago: when the stakes are high and adrenaline is running, you don't rely on judgment — you run a checklist.
An incident runbook is that checklist for the day your AI system misbehaves. It's written calmly, in advance, precisely so that the frightened 2am version of you doesn't have to think — just execute. The value isn't the document; it's that the hard decisions (What's our threshold for pulling the plug? Who's allowed to? What do we owe customers?) were made when everyone was rested and rational, not mid-crisis. The steps below map onto the same phases every mature incident process uses, adapted for AI:
- Detect — you can only respond to what you can see, so this leans entirely on the monitoring and alerting from W14/O4. No observability, no incident response.
- Contain — stop the bleeding before you understand it fully. This is the kill switch: a pre-built, pre-tested toggle that disables the feature, agent alias, or tool in one action. The cardinal rule — you must build and rehearse the kill switch before the incident, because you cannot invent one at 2am.
- Assess — now that it's contained, use the audit trail to work out what actually happened and the blast radius: who was affected, how many, how badly.
- Remediate — fix the layer that failed (a defense from RT4) and add a regression test (RT3) so the same hole can't reopen silently.
- Review — a blameless postmortem (the goal is a better system, not a scapegoat) that feeds its lessons back into the model card and the runbook itself.
Two ideas make this more than a to-do list. First, containment comes before understanding — the instinct to "just figure out what's wrong first" is exactly backwards; you hit the kill switch while the damage clock is still running, then investigate the stopped system safely. Second, the loop closes: every incident makes the runbook and the model card better, so your governance compounds instead of decaying. The mistake that undoes all of this is declaring victory too early — "we patched it, incident over" — without eradicating the root cause or writing down what happened. That's why the exercises below gate an incident from closing until Detect, Contain, Eradicate, and Document are all genuinely done.
Follow the solid arrows left to right through the five steps, then note the dashed arrow that loops from the last box back to the first.
- The five boxes are the phases in order: Detect (alerts from W14/O4), Contain (the red box — the pre-built kill switch), Assess (use the audit trail), Remediate (fix the failed layer plus a regression test), and Review (a blameless postmortem).
- Notice that the red Contain box sits before the blue Assess box: the "↑ stop the bleeding first" note marks the counterintuitive rule — you contain before you understand, so the damage stops while you investigate safely.
- The dashed muted arrow from Review back to Detect is the closing loop: each incident's lessons feed the model card and the runbook, so governance compounds instead of decaying.
In short: contain first, assess second, and always close the loop — an incident isn't over until it has made the system better.
When something goes wrong
- Detect: monitoring/alerts (W14, O4) flag anomalous behavior or a breach.
- Contain: hit the kill switch — disable the feature/agent alias/tool (W14).
- Assess: what happened, blast radius, who/what was affected (audit trail).
- Remediate: fix the layer that failed (RT4); add a regression test (RT3).
- Review: blameless postmortem + update the model card and runbook.
Exercise RT5.1 — Govern a project
Context: Governing a real project ties the whole track together: you classify its tier, document it, and plan for the day it misbehaves — including naming the kill switch before you need it.
Your task: For a course project, classify its EU AI Act tier, write a model card, and draft a one-page incident runbook naming the kill switch — and if it's high-risk, list the extra obligations you'd owe.
Requirements:
- Classify the project into an EU AI Act tier with justification
- Write a model card covering purpose, data, limits, and evaluations
- Draft a one-page incident runbook that explicitly names the kill switch
- If the project is high-risk, list the extra obligations (bias audit, human oversight, documentation, audit trail)
💡 Hint: Naming the kill switch concretely — the exact toggle or action that stops the system — is what separates a real runbook from a wish.
🪜 Practice ladder beginner → industry
Six graded exercises, easy to real-world. Try each before opening its solution.
Context: Under the EU AI Act, a system's risk tier sets its obligations — and classifying early shapes the whole design. Credit scoring and hiring are high-risk; a chatbot is limited; a spam filter is minimal; social scoring is banned.
Your task: Write risk_tier(use_case) that maps a use case to unacceptable | high | limited | minimal using the lesson's examples.
Requirements:
- Map social scoring → unacceptable, credit scoring / hiring / medical → high, chatbot → limited, spam filter → minimal
- Default unknown use cases cautiously (e.g. to limited)
- Return both the tier and its obligations
- Obligations reflect the tier: high → conformity assessment, docs, human oversight; limited → transparency (disclose it's AI); minimal → few requirements; unacceptable → banned
- Note that high-risk (credit/hiring) makes documentation, testing, oversight, and audit requirements, not extras — directly relevant to FICO-style use cases
💡 Hint: Two lookup tables — use case to tier, tier to obligations — keep the policy explicit and easy to audit.
Show solution
Classifying early shapes the whole design:
TIERS = {
"social_scoring": "unacceptable",
"credit_scoring": "high", "hiring": "high", "medical": "high",
"chatbot": "limited",
"spam_filter": "minimal",
}
OBLIGATION = {
"unacceptable": "banned",
"high": "conformity assessment, docs, human oversight",
"limited": "transparency (disclose it's AI)",
"minimal": "few requirements",
}
def risk_tier(use_case):
tier = TIERS.get(use_case, "limited") # default cautiously
return tier, OBLIGATION[tier]
print(risk_tier("credit_scoring")) # ('high', 'conformity assessment, docs, human oversight')
print(risk_tier("chatbot")) # ('limited', "transparency (disclose it's AI)")
A credit or hiring model being high-risk means documentation, testing, human oversight and audit are requirements, not extras — directly relevant to FICO-style use cases.
Context: A model card documents purpose, data, limits, and evals so a system is reviewable. Encoding it as validated data means an incomplete card can fail CI, just like a missing test.
Your task: Define a model_card dict with the required fields and a validator that fails if any required section is missing or empty.
Requirements:
- Include required fields: name, intended use, out-of-scope, training data, limitations, evaluations, and owner
- Populate a realistic example card with concrete values
validate(card)collects any required key that is missing or empty- Raise an error naming the missing sections, or return valid when complete
- Make the point that the card as validated data lets an incomplete card fail CI
💡 Hint: The validator is a list comprehension over the required keys checking for falsy values; the discipline is in treating documentation like a test.
Show solution
The card is the artifact that makes a system auditable:
REQUIRED = ["name", "intended_use", "out_of_scope", "training_data",
"limitations", "evaluations", "owner"]
card = {
"name": "refund-classifier-v3",
"intended_use": "Triage refund requests for human agents.",
"out_of_scope": "Auto-approving refunds without review.",
"training_data": "12k anonymized 2024 tickets; English only.",
"limitations": "Lower recall on non-English; no PII redaction upstream.",
"evaluations": {"accuracy": 0.91, "false_approve_rate": 0.02},
"owner": "trust-and-safety@acme",
}
def validate(card):
missing = [k for k in REQUIRED if not card.get(k)]
if missing:
raise ValueError(f"model card incomplete: {missing}")
return "valid"
print(validate(card)) # valid
Encoding the card as validated data means an incomplete card can fail CI, just like a missing test.
Context: Audit trails make a system reviewable after the fact, and hash-chaining gives cheap tamper-evidence without a database: each entry chains a hash of the previous one, so any edit breaks the chain from that point forward.
Your task: Build an append-only log where each entry chains a hash of the previous one, and show verification passing, then failing after a forged edit.
Requirements:
- Each entry stores its event plus a hash computed from the previous hash and the event contents
- The first entry chains from a fixed genesis value
- Hash deterministically (e.g. sha256 over the previous hash plus a canonical JSON of the event)
verify()recomputes the chain and returns True when intact- Demonstrate verification returning True, then False after forging an earlier entry
- Note that any edit breaks the chain from that point on, proving the recorded decisions are the ones actually made
💡 Hint: Serialize each event with sorted keys before hashing so the hash is stable, and verify by walking the chain forward from genesis recomputing each link.
Show solution
Hash-chaining gives cheap tamper-evidence without a database:
import hashlib, json
def _h(prev, entry):
return hashlib.sha256((prev + json.dumps(entry, sort_keys=True)).encode()).hexdigest()
class AuditLog:
def __init__(self):
self.entries = []
def add(self, event):
prev = self.entries[-1]["hash"] if self.entries else "GENESIS"
self.entries.append({"event": event, "hash": _h(prev, event)})
def verify(self):
prev = "GENESIS"
for e in self.entries:
if e["hash"] != _h(prev, e["event"]):
return False
prev = e["hash"]
return True
log = AuditLog()
log.add({"action": "decision", "id": 1, "outcome": "approve"})
log.add({"action": "decision", "id": 2, "outcome": "deny"})
print(log.verify()) # True
log.entries[0]["event"]["outcome"] = "deny" # forge
print(log.verify()) # False
Any edit breaks the chain from that point forward, so the log can prove the recorded decisions are the ones actually made.
Context: Fairness auditing uses standard, defensible metrics on your own eval data. The four-fifths rule — any group's selection rate below 80% of the most-favored group's — is the classic disparate-impact flag.
Your task: Given labeled outcomes per group, compute the selection rate per group and apply the four-fifths (80%) rule disparate-impact check, flagging groups below the threshold.
Requirements:
selection_rates(rows)computes approved/total per group from (group, approved) rows- Take the most-favored group's rate as the reference
- Compute each group's ratio against that reference
- Flag any group whose ratio is below 0.8 as DISPARATE IMPACT, others as OK
- Demonstrate a group falling below the threshold being flagged
- Note that a sub-0.8 ratio is a signal to investigate, mitigate, and document in the model card
💡 Hint: Two passes: aggregate rates per group, then divide every rate by the maximum rate and compare against 0.8.
Show solution
Standard, defensible fairness metrics on your own eval data:
def selection_rates(rows):
# rows: list of (group, approved:bool)
agg = {}
for g, ok in rows:
n, k = agg.get(g, (0, 0))
agg[g] = (n + 1, k + int(ok))
return {g: k / n for g, (n, k) in agg.items()}
def four_fifths(rates):
ref = max(rates.values()) # most-favored group
flags = {g: r / ref for g, r in rates.items()}
return {g: (ratio, "OK" if ratio >= 0.8 else "DISPARATE IMPACT")
for g, ratio in flags.items()}
rows = [("A", True), ("A", True), ("A", False),
("B", True), ("B", False), ("B", False)]
rates = selection_rates(rows)
print(rates) # {'A': 0.666..., 'B': 0.333...}
for g, (ratio, verdict) in four_fifths(rates).items():
print(g, f"{ratio:.2f}", verdict) # B 0.50 DISPARATE IMPACT
A ratio below 0.8 versus the most-favored group is the classic disparate-impact flag — a signal to investigate, mitigate, and document in the model card.
Context: When a live system misbehaves you need a runbook. Encoding the incident phases as an ordered checklist with a closure gate stops the common failure mode of "we patched it" without eradication or a written record.
Your task: Encode the incident phases as an ordered checklist with a gate that won't let you close an incident until the detect / contain / eradicate / document steps are all done.
Requirements:
- Define the ordered list of phases (detect, triage, contain, eradicate, recover, document, postmortem)
- Track completed phases for an incident
complete(phase)validates the phase name before recording itcan_close()returns whether the required phases are all done and which are missing- Require at least detect, contain, eradicate, and document before closure is allowed
- Demonstrate a close being refused with missing phases, then allowed once they're complete
💡 Hint: A set of completed phases and a required subset is the whole model; can_close is just a set difference against the required phases.
Show solution
A machine-checkable runbook keeps incident response disciplined:
PHASES = ["detect", "triage_severity", "contain", "eradicate",
"recover", "document", "postmortem"]
class Incident:
def __init__(self, title):
self.title = title
self.done = set()
def complete(self, phase):
assert phase in PHASES, f"unknown phase {phase}"
self.done.add(phase)
def can_close(self):
required = {"detect", "contain", "eradicate", "document"}
missing = required - self.done
return (not missing), sorted(missing)
inc = Incident("prompt-injection leak in support bot")
for p in ["detect", "triage_severity", "contain"]:
inc.complete(p)
print(inc.can_close()) # (False, ['document', 'eradicate'])
inc.complete("eradicate"); inc.complete("document")
print(inc.can_close()) # (True, [])
Gating closure on the required phases stops the common failure mode of "we patched it" without eradication or a written record.
Context: One governance gate encodes the org's release policy for high-risk AI: a regulated system can't ship until its tier is classified, the model card validates, a bias audit passed, and an audit log is enabled — enforced by the pipeline, not left to memory.
Your task: Write governance_gate(system) returning pass/fail with the list of unmet obligations, so a regulated system can't ship until every obligation is satisfied.
Requirements:
- Fail if the tier isn't classified (or is unacceptable) or the model card isn't valid
- For high-risk systems, additionally require a passed bias audit and configured human oversight
- Require an enabled audit trail regardless of tier
- Return a (passed, unmet) pair listing every unmet obligation
- Demonstrate a ready system passing and a not-ready one failing with its specific gaps
- Make the point that high-risk systems carry extra blocking obligations so accountability is enforced by the pipeline
💡 Hint: Accumulate unmet obligations into a list and gate on it being empty; the high-risk branch simply adds more required checks.
Show solution
One gate that encodes the org's release policy for high-risk AI:
def governance_gate(system):
unmet = []
if system.get("tier") not in {"minimal", "limited", "high"}:
unmet.append("tier not classified (or unacceptable)")
if not system.get("model_card_valid"):
unmet.append("model card incomplete")
if system.get("tier") == "high":
if not system.get("bias_audit_passed"):
unmet.append("bias audit not passed (required for high-risk)")
if not system.get("human_oversight"):
unmet.append("human oversight not configured (required for high-risk)")
if not system.get("audit_log_enabled"):
unmet.append("audit trail not enabled")
return (not unmet), unmet
ready = {"tier": "high", "model_card_valid": True, "bias_audit_passed": True,
"human_oversight": True, "audit_log_enabled": True}
notready = {"tier": "high", "model_card_valid": True, "bias_audit_passed": False,
"human_oversight": False, "audit_log_enabled": True}
print(governance_gate(ready)) # (True, [])
print(governance_gate(notready)) # (False, ['bias audit...', 'human oversight...'])
High-risk systems carry extra obligations (bias audit, human oversight); the gate makes them blocking, so accountability is enforced by the pipeline, not left to memory.
✓ Checkpoint — you can move on when you can…
- Explain the governance a live AI system needs.
- Place a system in the EU AI Act tiers and state its obligations.
- Write a model card and keep an audit trail.
- Audit for bias and run an AI incident with a kill switch.
Knowledge check check yourself
What are the EU AI Act risk tiers described in the lesson, and what obligation does the high-risk tier carry?
Show answer
What is a model card, and what are the five steps of the AI incident runbook the lesson prescribes?