guide
AI governance for engineers, not lawyers
Translate AI governance into systems you can build: inventory reconciled against runtime telemetry, deployment gates that block, human oversight you can measure, and evidence that survives review. With EU AI Act dates as amended in July 2026.
Governance that is not expressed as a mechanism does not exist. Every governance noun has to reduce to three things you can point at: a system that holds the record, a gate that can block a change, and an artifact a reviewer can diff. This guide performs that translation for AI systems, and spends most of its length on the three hardest cases: inventory, human oversight, and model dependencies.
Scope, and how to read the labels
This is written for the engineer who gets handed "we need AI governance" by someone who then leaves the room. You have a git-based change process, CI, and either a service catalogue or the ability to create one. Your organisation consumes hosted models through APIs — Amazon Bedrock, Microsoft Foundry, Vertex, direct provider APIs — more often than it trains its own. You have no authority to create a committee, and you should not want one.
In scope: inventory, classification, ownership, data lineage, provider dependencies, intended and prohibited use, security testing, human oversight, change management, evaluation, monitoring, incident handling, exceptions, retirement, and the evidence trail that connects them.
Out of scope, deliberately:
- Compliance procedure. No conformity assessment walkthrough, no notified-body process, no ISO/IEC 42001 certification roadmap.
- Legal advice. Regulation appears only to fix dates and to show which mechanisms have a legal trigger. Nothing below is a legal opinion, and the sequencing of your obligations depends on facts about your company that this article does not know.
- Framework exposition. NIST AI RMF, the ISO/IEC 42000 series and the EU AI Act appear as constraints on mechanism design. If a section reads like a summary of a framework rather than an instruction for building something, it has failed.
- Prompt injection and agent security. Their own engineering problems with their own controls. Governance decides that a threat model exists and that its findings are tracked; it does not replace the threat model.
Because the most damaging thing an engineer can do with a governance document is mistake an internal preference for a legal requirement, every normative statement below carries one of four labels.
| Label | Meaning | How to treat it |
|---|---|---|
| [Legal] | A requirement of a named instrument, with date and article | Verify against the instrument and your own jurisdiction before relying on it |
| [Standard] | A recommendation from NIST, ISO/IEC or OWASP, named explicitly | Not binding; useful as a checklist and as an argument in review |
| [CloudSecOps] | An engineering recommendation from this consultancy, with reasoning | Disagree freely; the reasoning is given so you can |
| [Your risk decision] | A threshold or boundary your organisation must set | A defensible default is offered and identified as a default |
Where a mechanism has a legal floor and an engineering recommendation above it, both labels appear. Log retention is the standing example: [Legal] at least six months for deployers of high-risk systems under Article 26 of the EU AI Act; [CloudSecOps] thirteen months for the systems whose failure would actually hurt you; [Your risk decision] whether prompt and completion content is retained at all, which is a data protection question before it is a governance one.
One further reading instruction, because this guide is full of tables and tables are a persuasive format. Where a number carries [Legal], it comes from a named instrument and the citation is in the references. Every other number here is a starting value chosen for a stated reason, and the reason is the part worth copying. None of them are benchmarks. None are derived from a study or from measured engagement data. Where this guide has no defensible number to offer, it says so instead of supplying one.
The translation table
The rest of this guide is one table expanded. Each row takes a governance noun — the kind of word that appears in a policy PDF — and names the mechanism it has to become, the system that holds its record, the artifact it produces, and the shape of its failure.
| Governance noun | Mechanism | System of record | Evidence produced | Failure signature |
|---|---|---|---|---|
| Inventory | Declared record in git, reconciled against runtime telemetry | Service catalogue, sourced from repo | ai-system YAML per system; reconciliation diff | A production system in the invocation logs and not in the catalogue |
| Accountability | A named owner, resolved to an on-call rotation | Catalogue plus identity provider group | Owner field with a resolvable group; a page that reaches a human | Owner resolves to a disbanded team or someone who left |
| Classification | Decision tree run in CI, output written back to the record | Repo, versioned with the code | Tier field plus the recorded rationale | Every system is the same tier, so the tier carries no information |
| Risk management | Threat model with tracked findings and a dated acceptance record | Issue tracker plus repo | Findings with owners and states; acceptance signed by a named person | Findings exist, none are closed, no acceptance is recorded |
| Transparency | Disclosure rendered from the record, not written by hand | Application code, sourced from the record | Rendered notice; DOM test in CI | Disclosure copy and the record disagree about what the system does |
| Human oversight | An approval or override point that is instrumented | Application database plus metrics pipeline | Approval rate, time-to-decision, modification rate, per-approver spread | Approval rate is 1.00 and nobody noticed |
| Change management | A required CI check on the record and the model pin | CI provider | Check run history; block-rate metric | The check exists and has never failed |
| Evaluation | A frozen test set, executed on defined triggers, with thresholds | Evaluation runner plus evidence store | Scored run artifacts tied to a commit and a model version | Evaluations run, no threshold is defined, so no result can fail |
| Monitoring | Named metrics with alert thresholds and a review cadence | Observability platform | Alert definitions; alert history; review notes | "We monitor the system" with no metric name attached |
| Vendor management | A model pin, a deprecation calendar, a notice-to-action path | Repo plus calendar with owners | Calendar entries with EOL dates and migration state | A 410 Gone in production is the first sign of a retirement |
| Incident response | AI-specific triage in the existing IR process, with reporting clocks | IR platform | Timeline, classification, reporting decision with timestamps | AI incidents are triaged as generic outages and the clock is missed |
| Exceptions | An expiring record with a compensating control and a named accepter | Repo, enforced by the gate it waives | Exception YAML with a mandatory expiry | Exceptions never expire, so the gate is decorative |
| Retirement | A decommission checklist with evidence outliving the system | Catalogue plus evidence store | Retirement record; disposition of keys, data and access | The system is "gone" but the API key still works |
Four of these translations are harder than they look, and they are the ones that get skipped.
Accountability is the easiest to fake. A record with owner: platform-team satisfies a reviewer and pages nobody. The test is mechanical: resolve the owner string to a group in your identity provider, resolve that group to an on-call schedule, and check the schedule has a human in it this week. [CloudSecOps] Make that resolution a CI check rather than a quarterly audit, because ownership decays continuously and audits are discrete.
Transparency fails through drift. A team writes "this response was generated by AI" into a component, changes the system six months later, and nothing forces the copy to change with it. Render the disclosure from the record that drives classification, so that changing the system's purpose changes the notice or breaks a test.
Risk management in the regulatory sense is not a document. Article 9 of the EU AI Act describes "a continuous iterative process planned and run throughout the entire lifecycle" with testing "against prior defined metrics and probabilistic thresholds", ending in a residual risk that is "judged to be acceptable" [Legal]. Read as an engineer: a threat model that gets re-run, tests with thresholds defined before the test, and an acceptance record with a name and a date on it. The acceptance record is the part teams omit, and it is the only part that establishes who decided.
Monitoring degrades into a sentence faster than any other row. The discipline that prevents it is refusing to write the word without naming a metric, a threshold, an alert destination, and a review cadence in the same breath.
What is actually in force, and for whom
This section demonstrates the guide's own thesis before making its argument, because the demonstration is free.
On the verification date, two sources describing the same law disagreed. The Commission's regulatory framework page, last updated four days earlier, states that the high-risk rules for Annex III use cases "have been extended to 2 December 2027". A widely cited third-party implementation timeline, fetched the same day, still shows the original schedule and carries a last-updated date of 1 August 2024. One of those is the regulator. The other is a governance artifact nobody re-verified for two years, which is the failure mode described in every section below.
[Legal] The Digital Omnibus on AI — Regulation (EU) 2026/1744 of 8 July 2026, amending Regulation (EU) 2024/1689 — entered into force on 27 July 2026 and moved the high-risk application dates. If your internal deck says high-risk obligations began on 2 August 2026, it is wrong. That date is real, but it governs general applicability and the Article 50 transparency obligations.
| Date | What applies | Who it binds | Status on 2026-08-07 |
|---|---|---|---|
| 2 Feb 2025 | Prohibited practices (Art 5); AI literacy (Art 4) | Providers and deployers in scope | In force |
| 2 Aug 2025 | Governance rules; GPAI model obligations; penalties framework | GPAI model providers; Member States | In force |
| 2 Aug 2026 | General applicability; Art 50 transparency obligations; AI Office and national authorities supervise and enforce | Broad | In force |
| Dec 2026 | New Art 5 prohibitions on non-consensual intimate imagery and CSAM generation, added by Reg (EU) 2026/1744 | Broad | Not yet |
| 2 Dec 2027 | Annex III high-risk obligations — biometrics, critical infrastructure, education, employment, migration, asylum, border control | Providers and deployers of those systems | Deferred from 2 Aug 2026 |
| 2 Aug 2028 | Annex I high-risk obligations — AI as a safety component of regulated products | Providers of those products | Deferred from 2 Aug 2027 |
Three other Omnibus changes have engineering consequences. Annex VIII registration was simplified for systems a provider has assessed as non-high-risk under Article 6(3), while retaining the database requirement for market surveillance. SMEs, startups and small mid-caps may supply technical documentation elements in simplified form and notified bodies must accept it, which gives Article 17(2)'s proportionality clause a concrete expression and grounds the small-team plan later in this guide. And [Legal] the Commission's empowerment to adopt a binding implementing act for the Article 72 post-market monitoring plan template was removed and replaced by an obligation to publish guidance, including a voluntary template, by 2 September 2027. Recital 41 of the amending regulation frames the deletion as offering providers more flexibility rather than as withdrawing the template. Any plan that says "wait for the Commission's monitoring template" needs rewriting: the structure is yours to choose now, and the voluntary template is a later cross-check rather than a thing to block on.
The sequencing most US engineering teams have backwards
For a US-headquartered company with EU exposure, the near-term forcing function is not the EU.
[Legal] Colorado SB24-205, the Consumer Protections for Artificial Intelligence act, was signed on 17 May 2024 with an original effective date of 1 February 2026. SB25B-004, signed 28 August 2025 and itself effective 25 November 2025, moved that to 30 June 2026. SB 26-189, signed 14 May 2026, repealed and reenacted it before that date arrived, replacing the high-risk AI system regime with a narrower one keyed on automated decision-making technology that materially influences a consequential decision — education, employment, housing, financial services, insurance, health care, government benefits. It applies to decisions made on or after 1 January 2027.
What survives is transparency and process. Developers supply technical documentation to deployers. Deployers give notice before the interaction, and an explanation within 30 days of an adverse outcome. Consumers can request correction of inaccurate personal data and meaningful human review of an adverse decision. What was removed is the part most governance decks were sized against: the duty of reasonable care against algorithmic discrimination, the risk management programme, the impact assessments, and the notification to the Attorney General on discovering algorithmic discrimination. Attorney General rulemaking on disclosure content and sector-specific guidance is required by 1 January 2027 and had not been formally proposed on the verification date; pre-rulemaking input closed 13 July 2026. Enforcement is separately stayed: in xAI v. Weiser the court granted a joint motion on 27 April 2026 suspending enforcement pending a preliminary injunction ruling, and the stay reaches the legislation replacing SB24-205 as well as SB24-205 itself. [CloudSecOps] Build to the notice, correction and human-review duties on the 1 January 2027 date; treat the litigation as a reason to watch rather than a reason to wait.
[Legal] Texas HB 149, the Texas Responsible Artificial Intelligence Governance Act, took effect 1 January 2026 per the enrolled bill text. It is intent-based rather than disparate-impact-based: it prohibits intentional unlawful discrimination, manipulation toward self-harm or crime, biometric capture without consent, and CSAM generation. Those prohibitions bind private entities with a Texas nexus as well as government. The AI-interaction disclosure duty does not. Section 552.051(b) puts it on a governmental agency that makes an AI system available to interact with consumers, and the only private-sector disclosure duty in the enacted bill is the separate one at §552.051(f), covering AI used in health care services. The general private-sector consumer-notification and impact-assessment requirements that appeared in earlier drafts did not survive into the enrolled text. [CloudSecOps] Disclosing that a user is talking to a model is usually the right product decision anyway, but if your disclosure control is justified in a control narrative by TRAIGA and you are neither a governmental agency nor a health care provider, the justification is wrong even where the control is not.
The state clocks still run ahead of the EU one. Texas has been in force since 1 January 2026, and Colorado's replacement duties attach eleven months before the EU Annex III date. [CloudSecOps] If you are building a roadmap around 2 December 2027 and you have Colorado or Texas users, the roadmap is ordered wrong.
Three caveats, stated because leaving them out would repeat the stale tracker's error. The Colorado paragraph above is itself a correction: at an earlier check the state Attorney General's AI resources page returned 404 on two attempts and the 2026-session amendment could not be verified, which is precisely how a guide ends up describing a repealed regime as though it were in force. The page resolves now and documents SB 26-189. What remains genuinely unresolved is the rulemaking and the outcome of the xAI litigation. CEN-CENELEC harmonised standards, which give a presumption of conformity for high-risk systems and whose availability was a stated driver of the Omnibus delay, were not checked at all. And this covers three jurisdictions out of the many now legislating on AI. Verify all of it against primary sources before relying on the sequencing above.
Two things engineers can use directly rather than through a third party: the AI Act Service Desk run by the European AI Office, which hosts an AI Act Explorer, a Compliance Checker and guidance on high-risk classification; and the Official Journal text itself. Neither produces an authoritative determination for your system, and both beat a tracker that stopped updating in 2024.
The governance control plane
Governance needs the same architectural treatment as any other cross-cutting concern: name the authoritative system for each record type, name the write path, and name the mechanism that keeps it fresh. A record with no freshness mechanism is a snapshot, and every snapshot is wrong by the time someone reads it.
Figure 1. The governance control plane. Alt text: declared state lives in git as system records, exception records and policy; CI and the evaluation runner act as control points and write to an evidence store; runtime telemetry, provider invocation logs and billing feed a reconciliation job whose output flows back into the catalogue and raises tickets. The reconciliation edge from observed state back to declared state is the only thing preventing inventory rot.
| Record type | Authoritative system | Write path | Freshness mechanism | Retention |
|---|---|---|---|---|
| System declaration | Git repository | Pull request, reviewed | CI fails on review.next_due in the past | Life of system plus retention period |
| System identity and ownership | Service catalogue | Ingested from git | Owner group resolved against IdP in CI | Life of system |
| Classification and tier | Git, alongside the code | PR, with recorded rationale | Re-run of the decision tree on any spec change | Life of system plus retention |
| Model pin | Git, in application config | PR | Deprecation calendar due date; provider notice | Life of deployment |
| Evaluation results | Evidence store | Written by evaluation runner | Tied to commit SHA and model version | [CloudSecOps] 13 months |
| Decision logs | Observability platform or object storage | Written by the application | Continuous | [Legal] at least 6 months for high-risk deployers; longer is a privacy decision |
| Exceptions | Git | PR with named accepter | Gate fails on expiry date passed | Life of system plus retention |
| Approvals and overrides | Application database | Written by the oversight UI | Continuous | Match the decision-log retention |
[CloudSecOps] On the thirteen months: the number is not a benchmark, it is one annual cycle plus a month, so that any annual review can always reach the equivalent period in the prior year without a gap at the boundary. If your review cycle is quarterly, four months plus a month is the same argument with different arithmetic.
Reconciliation, or how you find the system nobody told you about
Every inventory rots. The question is whether it rots silently. Declared inventory only captures systems whose owners chose to declare them, which excludes the two categories you most want: the feature a team shipped without knowing it counted as an AI system, and the shadow deployment.
The mechanism is a scheduled job that derives observed inventory from runtime signals and diffs it against the declared set. Three signal sources, in decreasing order of fidelity:
- Provider invocation logs. On Bedrock these give you
modelIdandidentity.arnper invocation, which is close to ground truth. One catch that has caught many teams: Bedrock model invocation logging is disabled by default. Nothing is being recorded until someone enables it. The console configuration selects modalities — Text, Image, Embedding, Video — and logs data for all models supporting the chosen modalities. Delivery is to CloudWatch Logs, Amazon S3 or both, and only destinations in the same account and Region are supported, so a multi-account estate needs the job to run per account. - Application telemetry. OpenTelemetry GenAI spans carry
gen_ai.provider.nameandgen_ai.operation.nameas Required attributes andgen_ai.request.modelas Conditionally Required. This covers providers whose own logging you do not control, but only for services that are instrumented, so absence of a span is not absence of a system. - Billing and network egress. The lowest-fidelity and highest-coverage signal. A cost line item or a TLS connection to a model provider from a service with no declared AI dependency is a lead, not a finding.
Illustrative — CloudWatch Logs Insights query over Amazon Bedrock model invocation logs. Not executed against a live log group during writing. Validate the log group name and field paths against your own delivery configuration; the log schema is ModelInvocationLog schemaVersion 1.0, and S3-delivered logs are queried with Athena rather than Logs Insights.
fields @timestamp, modelId, identity.arn as principal, operation
| filter ispresent(modelId)
| stats count(*) as invocations,
min(@timestamp) as first_seen,
max(@timestamp) as last_seen
by modelId, principal, operation
| sort invocations desc
| limit 200
The output is a set of (principal, modelId) pairs. Map each principal to a service through your existing role-to-service convention, then diff the service set against declared records. [CloudSecOps] Treat three outcomes differently:
- Observed, not declared. A ticket against the owning team with a deadline, not a block. Blocking production traffic to punish a missing YAML file makes governance an adversary.
- Declared, not observed, for a period longer than the system's normal idle gap. A candidate for retirement. This is how you find the systems still holding credentials after the experiment ended. Pick the period from your own traffic patterns; a seasonal batch job and a customer-facing endpoint do not share a threshold.
- Declared model differs from observed
modelId. Investigate immediately. Either the pin is not being honoured, or the provider changed something.
The reconciliation job is a small amount of code with an outsized effect, because it converts inventory from a thing people maintain into a thing the environment maintains. [CloudSecOps] Run it weekly and alert on a growing "observed, not declared" count rather than on any single instance. One undeclared system is a Tuesday; a trend is a broken intake process.
What reconciliation cannot see
The job's coverage is exactly the coverage of its inputs, and three blind spots are worth naming before you present its output as an inventory.
An account with logging disabled contributes nothing and looks identical to an account with no AI systems. Across more than a handful of accounts, enablement has to be checked as its own control: a Config rule or equivalent sweep asserting that invocation logging is configured in every account and Region where a model provider is reachable. Without it, the job's clean output is unfalsifiable.
Direct provider APIs bypass the cloud control plane. A service calling a model vendor over the internet with a personal API key produces no invocation log you can read, no billing line under your organisation, and a span only if someone instrumented it. Egress inspection is the remaining signal, and it degrades to "a TLS connection to a known provider domain": enough to open a conversation, not to close a finding.
Reconciliation identifies systems, not risk. It tells you a principal called a model, not whether that call reaches a customer, moves money, or feeds a decision about a person. That is the classification step, and it needs a human for the first pass on every system the job surfaces.
The AI system record
One file per AI system, in the repository that holds the system's code, reviewed through the same pull request process as everything else. Putting it in a GRC platform, a wiki or a spreadsheet creates a parallel registry, and a parallel registry is where inventory rot starts, because nothing forces it to change when the code changes.
The envelope is deliberately Backstage-compatible: apiVersion, kind, metadata, spec, with metadata.name inside the 1–63 character constraint and custom keys under a domain-prefixed annotation namespace, since the backstage.io/ prefix is reserved. If you use a different catalogue, keep the shape and change the envelope. If you use no catalogue, this file is your catalogue. The vocabulary borrows from SageMaker Model Cards rather than inventing a new one — intended uses paired with explicitly non-recommended uses, and a four-value risk rating — so the record can be generated from or exported to a model card without a translation layer.
Illustrative — AI system record. A shape to adapt, not a schema to adopt unchanged; field names must match whatever your policy gate and catalogue expect. Values are hypothetical.
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: claims-triage-assistant
description: Ranks inbound claims by likely complexity to order a human queue
annotations:
example.com/ai-system: "true"
example.com/ai-record-version: "3"
tags: [ai-system, tier-2]
spec:
type: ai-system
lifecycle: production
owner: group:default/claims-platform # must resolve in the IdP
system: claims
ai:
# --- Classification: two independent outputs, never collapsed ---
classification:
internal_tier: T2 # your control tier
internal_tier_rationale: >-
Output orders a human work queue and is visible to staff. No automated
adverse action. Failure degrades throughput, does not deny a claim.
legal_trigger:
eu_ai_act:
annex_iii_area: null # null = not in an Annex III area
assessment: not-high-risk
basis: "Art 6(3)(d) preparatory task; no profiling of natural persons"
assessed_on: 2026-07-14
assessed_by: user:default/a.rivera
documented_at: docs/ai/claims-triage-6-3-assessment.md
us_state:
colorado_sb26_189: admt-consequential-decision-review-pending
texas_hb149_disclosure_required: false # §552.051(b) binds gov agencies
# --- Intended and prohibited use ---
intended_use:
- Ordering the human claims queue by predicted handling complexity
non_recommended_use:
- Any automated decision to approve, deny or price a claim
- Any use where the ranking is shown to the claimant
risk_rating: Medium # Low | Medium | High | Unknown
# --- Model and provider dependency ---
model:
provider: aws-bedrock
model_id: <pinned-dated-snapshot-id> # pinned, never an alias, at T2+
deployment_type: on-demand
auto_upgrade: false
provider_eol_date: 2027-03-31 # tracked in the deprecation calendar
region_scope: [us-east-1, eu-west-1]
# --- Data lineage ---
data:
inputs:
- name: claim-summary-text
classification: confidential
contains_personal_data: true
lawful_basis_ref: privacy/claims-triage-dpia.md
retrieval_sources:
- name: policy-handbook-index
store: opensearch://handbook-v4
training_or_tuning: none
egress:
crosses_org_boundary: true
provider_data_retention: "see provider terms; zero-retention not assumed"
# --- Controls asserted, each with a verifiable reference ---
controls:
threat_model_ref: security/threat-models/claims-triage.md
threat_model_reviewed: 2026-06-02
output_handling: "rendered as text, never as HTML; no tool invocation"
access_control: "service role claims-triage-invoke; no human API keys"
human_oversight:
type: human-on-the-loop
instrumented: true
metrics_dashboard: obs://dashboards/claims-triage-oversight
# --- Evaluation ---
evaluation:
suite_ref: evals/claims-triage/
frozen_test_set: s3://evidence/evals/claims-triage/testset-v4.jsonl
thresholds:
ranking_correlation_min: 0.72
refusal_rate_max: 0.03
pii_leak_findings_max: 0
triggers: [pr-to-main, model-version-change, weekly-schedule]
last_run: s3://evidence/evals/claims-triage/2026-08-04T0312Z/
# --- Review cadence: the anti-rot field ---
review:
last_reviewed: 2026-07-14
next_due: 2026-10-14
cadence_days: 92
The values in evaluation.thresholds are placeholders. There is no correct ranking correlation for your system; there is only the number your suite produced on a model you accepted, written down before the next run.
Five fields do most of the work. non_recommended_use is more operationally valuable than intended_use, because intended use is what the team will tell you anyway and non-recommended use is the sentence someone points at when a product manager proposes the thing you were worried about. model_id pinned to a dated snapshot rather than an alias makes a version change a reviewable event. data.egress.crosses_org_boundary determines whether a privacy review is a formality or the long pole. evaluation.thresholds exists because an evaluation without a pre-declared threshold cannot fail, and an evaluation that cannot fail is a report. review.next_due is what the CI gate reads to make staleness a build failure rather than an observation.
Note what is absent: no free-text risk narrative, no maturity score, no control-framework mapping table. Those are outputs you generate from this record when someone asks; carrying them in the record means maintaining them by hand, and hand-maintained fields go stale first.
Classification that means something
Most classification schemes fail the same way: every system ends up in the middle tier, so the tier conveys nothing and its controls are applied uniformly, which is the same as having no tiers.
The structural fix is to stop asking one question. There are two independent determinations, and collapsing them is what makes tiering useless.
The legal trigger asks whether a named instrument imposes duties on this system. It is a jurisdictional fact that changes when legislators change it, as it did on 27 July 2026 when the Annex III high-risk application date moved by sixteen months. Nothing about the engineering risk of any system changed that day.
The internal control tier asks how much control this system's failure justifies. It is an engineering judgment about blast radius, reversibility and the presence of a human between output and consequence. It should be stable across regulatory churn.
[CloudSecOps] Keep them as two fields and never derive one from the other. A team that sets internal_tier: T3 because the legal trigger came back "not high-risk" has made a category error that will produce exactly one incident, and it will be the interesting kind.
Figure 2. Two determinations from one walk. Alt text: the left branch derives an internal control tier from whether output reaches an external person, whether harm is irreversible, and whether the system acts autonomously, producing tiers T0 through T3. The right branch derives an EU AI Act legal trigger by testing Annex III membership, the significant-risk threshold and the four Article 6(3) conditions, and the profiling carve-out, producing either a high-risk determination or a documented non-high-risk assessment that still carries registration duties under Article 49(2).
The Article 6(3) trap
[Legal] Article 6(3) has a threshold clause that engineers routinely skip, and skipping it inverts the test. An Annex III system is not high-risk only "where it does not pose a significant risk of harm to the health, safety or fundamental rights of natural persons, including by not materially influencing the outcome of decision making" — and where one of four conditions is met: a narrow procedural task; improving the result of a previously completed human activity; detecting decision-making patterns or deviations from them without being meant to replace or influence the previous human assessment "without proper human review"; or a preparatory task to an assessment relevant to an Annex III use case. Meeting condition (a) does not by itself put you outside high-risk. The significant-risk test comes first.
[Legal] There is then one carve-out that swallows a lot of proposed derogations: "an AI system referred to in Annex III shall always be considered to be high-risk where the AI system performs profiling of natural persons."
Two consequences engineers consistently get backwards.
[Legal] Article 6(4): "A provider who considers that an AI system referred to in Annex III is not high-risk shall document its assessment before that system is placed on the market or put into service." Deciding you are out of scope is itself a documented decision, produced before deployment, not reconstructed afterwards.
[Legal] The same paragraph continues: "Such provider shall be subject to the registration obligation set out in Article 49(2)." Regulation (EU) 2026/1744 simplified the Annex VIII content for those systems but retained the database requirement for market surveillance. Concluding you are out of scope does not remove you from the register.
The engineering implication is small and specific: the classification decision needs a persisted artifact with a date and an author, produced by a gate that runs before first deployment, whichever way the decision goes. That artifact is legal_trigger.documented_at in the record above.
Internal tiers, and what attaches to them
| Tier | Trigger | Required controls | Gate behaviour | Review cadence |
|---|---|---|---|---|
| T0 | Output can cause irreversible or adverse outcome to a person | Threat model, adversarial evaluation, instrumented oversight, decision logging, pinned model, named accepter for residual risk | Blocking on all gates; second approver required | 90 days |
| T1 | Autonomous action: tool use, writes, money movement, outbound messages | Threat model, evaluation suite with thresholds, scoped credentials, decision logging, pinned model | Blocking on all gates | 90 days |
| T2 | Output reaches an external person, reversible, human between output and consequence | Record complete, evaluation suite, pinned model, disclosure where required | Blocking on record and pin; advisory on evaluation delta | 180 days |
| T3 | Internal or experimental; output does not leave the building team | Record with owner and expiry; no production data unless declared | Advisory only; blocks on expiry | Expires unless promoted |
[Your risk decision] The cadences above are arbitrary intervals attached to a non-arbitrary idea: a review that lands inside a planning cycle someone already runs gets done, and one that lands between cycles does not. Ninety and 180 days suit an organisation planning quarterly. If yours plans annually, both numbers are wrong.
[Your risk decision] The tier boundaries are questions, not quotas. If your distribution puts most systems in one tier, the questions are wrong for your estate rather than the systems being uniform. Recalibrate the questions, not the count.
[CloudSecOps] Make T3 expire. An experimental tier without an expiry becomes the tier everyone declares, and six months later your highest-risk system is labelled an experiment. The specific interval matters less than the property: it must be shorter than the interval at which someone would otherwise notice.
Deployment gates that block
A gate is a check that runs at a defined point in the change process, evaluates declared state against policy, and takes an action on failure. The action is what makes it a gate. A check that reports and proceeds is a linter, which is a fine thing to be, and it should not appear in a control narrative as though it prevents anything.
| Gate | What it checks | Blocks or advises | Evidence emitted | Owner |
|---|---|---|---|---|
| Record presence | An ai-system record exists for any service invoking a model provider | Blocks at T0–T2; advises at T3 | Check run; record path | Platform |
| Record validity | Schema valid; owner resolves in IdP; review.next_due in the future | Blocks | Check run with failure reason | Platform |
| Classification freshness | Decision tree re-run when spec.ai changes; tier rationale present | Blocks on missing rationale | Classification artifact with date | Service owner |
| Model pin | model_id is a dated snapshot, not an alias, at T2 and above; auto_upgrade: false | Blocks at T2+ | Diff of pin across commits | Service owner |
| Approved model list | Declared model appears on the organisation's reviewed list | Blocks | List version and match result | Security |
| Evaluation threshold | Suite ran on this commit and model version; all thresholds met | Blocks at T0–T1; advises at T2 | Scored run artifact in evidence store | Service owner |
| Threat model currency | threat_model_reviewed within tier cadence | Advises, then blocks after grace period | Date comparison result | Security |
| Exception validity | Any exception referenced is unexpired and has a compensating control | Blocks | Exception record and expiry | Security |
Writing the policy so it fails safely
The gate is a policy evaluation over the record. Rego with Conftest is the common shape; the important part is not the language but the failure semantics.
Illustrative — Rego for OPA 1.x, evaluated by Conftest against the AI system record. Written against OPA v1.17.0 (released 28 May 2026) but not executed during writing: run opa check and the negative fixtures below before adopting. Field names must match your record schema.
package aisystem.gate
import rego.v1
# `import rego.v1` is a no-op under OPA 1.x and keeps the file loadable by the
# 0.x toolchains some CI images still pin.
tier := object.get(input, ["spec", "ai", "classification", "internal_tier"], "UNSET")
known_tiers := {"T0", "T1", "T2", "T3"}
blocking_tiers := {"T0", "T1", "T2"}
date_shape := `^\d{4}-\d{2}-\d{2}$`
# A dated snapshot carries YYYYMMDD or YYYY-MM-DD somewhere in the identifier.
snapshot_date := `\d{8}|\d{4}-\d{2}-\d{2}`
# --- Ownership must resolve, not merely exist ---
deny contains msg if {
not valid_owner
msg := "spec.owner must be group:<namespace>/<name> with a non-empty name"
}
valid_owner if {
is_string(input.spec.owner)
startswith(input.spec.owner, "group:")
count(trim_space(substring(input.spec.owner, 6, -1))) > 0
}
# --- Shape is checked before the value is parsed ---
deny contains msg if {
not has_review_date
msg := "spec.ai.review.next_due is required and must be YYYY-MM-DD"
}
has_review_date if {
next_due := input.spec.ai.review.next_due
is_string(next_due)
regex.match(date_shape, next_due)
}
# --- Staleness is a build failure, not an observation ---
deny contains msg if {
has_review_date
next_due := input.spec.ai.review.next_due
time.parse_rfc3339_ns(sprintf("%sT00:00:00Z", [next_due])) < time.now_ns()
msg := sprintf("record review overdue: next_due %s is in the past", [next_due])
}
# --- An absent or unrecognised tier denies; it never defaults open ---
deny contains msg if {
not tier in known_tiers
msg := sprintf("spec.ai.classification.internal_tier must be one of T0-T3, got %v", [tier])
}
# --- Model pinning at T2 and above ---
deny contains msg if {
tier in blocking_tiers
not has_pinned_model
msg := sprintf("tier %s requires a pinned dated model snapshot in spec.ai.model.model_id", [tier])
}
has_pinned_model if {
id := input.spec.ai.model.model_id
is_string(id)
id != ""
regex.match(snapshot_date, id)
not endswith(id, "-latest")
input.spec.ai.model.auto_upgrade == false
}
# --- Evaluation thresholds must exist before they can be met ---
deny contains msg if {
tier in {"T0", "T1"}
count(object.get(input, ["spec", "ai", "evaluation", "thresholds"], {})) == 0
msg := sprintf("tier %s requires at least one declared evaluation threshold", [tier])
}
Four properties of this policy are the reason to copy it rather than the rules themselves.
Every not X is paired with a positively-defined X that type-checks its inputs. Writing not input.spec.ai.model.model_id looks equivalent to not has_pinned_model and is not: it is satisfied when the field is undefined, when the path above it is undefined, and when the value is false, and it silently passes when the field contains an empty string. [CloudSecOps] In a governance gate, "undefined" and "explicitly set to a bad value" must produce the same outcome, and only an explicit presence-and-type check gives you that.
The same reasoning is why the tier is denied outright when it is unrecognised. Reading it with object.get and a default of "UNSET" keeps the policy from erroring on a malformed record, but "UNSET" is in neither blocking_tiers nor {"T0", "T1"}, so without the known_tiers denial a record that simply omits internal_tier would skip every tier-gated rule and pass. Deleting a field is the cheapest way to escape a gate keyed on it, so the gate has to treat a missing key as the most severe case rather than the most lenient one.
A pin is tested by asserting a date, not by rejecting a suffix. not endswith(id, "-latest") rejects exactly one naming convention and passes every other alias, and most provider aliases — undated family names, cross-region inference profile identifiers — carry no -latest suffix at all. A gate written that way blocks close to nothing while reading in a control narrative as though it enforces pinning. snapshot_date is deliberately coarse and will accept any identifier containing eight consecutive digits; the approved-model-list gate in the table above is the precise version of the same control, and where you have that list, matching against it is the better test.
Shape validation runs before parsing. time.parse_rfc3339_ns on a value that is not an RFC 3339 timestamp raises a built-in error, and OPA suppresses built-in errors by default: the surrounding expression becomes undefined, which in a deny rule means the record passes. Separating the shape check from the comparison makes a malformed date produce its own denial. The residual case is a calendar-invalid value like 2026-13-45, which matches the shape and still errors; add a range check, or evaluate with opa eval --strict-builtin-errors, if that matters to you.
Version-dependent negation. OPA v1.17.0 introduced an opt-in future.keywords.not import, described in its release notes as making the negated body unsatisfiable when any sub-expression is undefined, so that not succeeds. The policy above does not import it and does not need to, because the positively-defined helper pattern behaves identically under both semantics. If you inherit a policy that does import it, re-read every negation in that file.
Test against deliberately broken fixtures: missing owner; owner set to group: with nothing after it; next_due set to "soon"; next_due in the past; a record with no internal_tier at all, and one with a tier of "T4"; model_id ending in -latest; model_id set to an undated alias with no -latest suffix, such as claude-sonnet-4-5 or gpt-4o; auto_upgrade: true; a T0 record with an empty thresholds object. A governance gate with no negative test cases is an untested control.
Illustrative — GitHub Actions fragment showing blocking versus advisory wiring. Pin action versions to a commit SHA in production use.
name: ai-governance-gate
on: [pull_request]
jobs:
record-policy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate AI system records
run: |
conftest test catalog-info.yaml \
--policy policy/aisystem/ \
--all-namespaces \
--output github
evaluation:
runs-on: ubuntu-latest
needs: record-policy
steps:
- uses: actions/checkout@v4
- name: Read tier
id: tier
run: |
TIER=$(yq '.spec.ai.classification.internal_tier' catalog-info.yaml)
echo "tier=$TIER" >> "$GITHUB_OUTPUT"
- name: Run evaluation suite
# Advisory at T2 and below; the step still runs and still writes evidence,
# but only a T0/T1 failure fails the job.
continue-on-error: ${{ steps.tier.outputs.tier != 'T0' && steps.tier.outputs.tier != 'T1' }}
run: promptfoo eval -c evals/claims-triage/promptfooconfig.yaml --fail-on-error
- name: Upload evidence
if: always()
run: aws s3 cp ./output/ "s3://evidence/evals/${GITHUB_REPOSITORY##*/}/${GITHUB_SHA}/" --recursive
The evidence upload runs if: always(). A failed evaluation is more valuable evidence than a passing one, and a gate that only records its successes produces an audit trail that lies by omission.
Figure 3. A model version bump through the gates, including the exception path. Alt text: an engineer's pull request passes policy evaluation but fails the evaluation threshold, blocking the merge; the engineer files an exception record with a compensating control and expiry, which the policy engine validates and routes to a named approver, whose approval is written to the evidence store before the check passes.
The gate itself is an attack surface
A gate is only as strong as the configuration that makes it required, and that configuration is usually invisible in the evidence trail. Three weaknesses, none exotic, all applying to CI-based controls generally.
A required check is a repository setting, not a property of the policy file. Anyone who can edit branch protection can make the gate advisory in a click, and the resulting merge looks identical to one where the gate passed. [CloudSecOps] Snapshot the branch protection and required-check configuration into the evidence store on a schedule and diff it. Without that, you can prove the policy existed and cannot prove it was enforced.
Whoever can merge to the policy repository can write their own exemption. The policy bundle needs at least the review requirements of the systems it governs, and ideally a different set of approvers, because the person requesting an exception should not be able to change the rule instead.
Evidence written by the pipeline is trusted because the pipeline wrote it. A compromised runner, or a workflow modified in the same pull request it evaluates, can emit a passing artifact for a record that never passed. Use a pinned, non-modifiable workflow for the gate job where your CI supports it, restrict which identity can write to the evidence prefix, and prefer object-lock or versioned storage so an artifact cannot be quietly replaced.
This is why a control narrative should say "the gate blocked this change on this date, and here is the check run" rather than "we have a gate".
A gate that has never blocked anything is not a gate
[CloudSecOps] Measure your gates. For each one, compute the block rate — the fraction of triggering changes it failed — over a rolling quarter.
A block rate of zero has exactly three explanations, and you need to know which one applies. The gate is misconfigured and never evaluates anything meaningful, which is common and easy to check by feeding it a deliberately broken record. Or the condition it checks genuinely never occurs, in which case the gate costs CI minutes and reviewer attention for no return and should be removed or made advisory. Or engineers have learned to route around it, which shows up as a rising exception count against that specific gate.
A block rate high enough that engineers experience blocking as the normal outcome is a different problem: the gate is catching what the development process should have prevented earlier, and the fix is upstream — a template, a scaffold, a pre-commit hook — rather than a stricter gate. [Your risk decision] This guide has no defensible number for "high enough" and does not offer one. The readable signal is the exception count per gate, which rises before anyone files a complaint.
Human oversight you can measure
"A human reviews the output before it is used" is the most common governance claim and the least frequently instrumented. It is also the claim regulation is least willing to take on trust.
[Legal] Article 14(4)(b) of the EU AI Act requires that oversight enable the assigned person "to remain aware of the possible tendency of automatically relying or over-relying on the output produced by a high-risk AI system (automation bias)". Subparagraphs (d) and (e) require the ability "to decide, in any particular situation, not to use the high-risk AI system or to otherwise disregard, override or reverse the output", and to interrupt the system through a stop button or similar procedure. [Legal] Article 26(2) requires deployers to assign oversight to natural persons "who have the necessary competence, training and authority, as well as the necessary support", which means the reviewer must be able to say no without escalating.
Regulation names the failure mode. It does not tell you how to detect it. That translation is engineering work, and it starts by treating oversight as a control with a measurable operating characteristic, like a rate limiter.
The instrumentation
Every approval or override point writes a row. Minimum fields: decision id, system id, model version, approver identity, decision (approved / rejected / modified), timestamp of presentation, timestamp of decision, and — the field everyone omits — a reference to the outcome, populated later.
Illustrative — derived oversight metrics over an approvals table, in PostgreSQL dialect. Adapt column names; the shape matters more than the dialect.
SELECT
approver_id,
count(*) AS decisions,
avg(CASE WHEN decision = 'approved' THEN 1.0 ELSE 0 END) AS approval_rate,
avg(CASE WHEN decision = 'modified' THEN 1.0 ELSE 0 END) AS modification_rate,
percentile_cont(0.5) WITHIN GROUP (
ORDER BY extract(epoch FROM decided_at - presented_at)
) AS median_seconds_to_decision,
min(extract(epoch FROM decided_at - presented_at)) AS fastest_decision_seconds
FROM ai_oversight_decisions
WHERE system_id = 'claims-triage-assistant'
AND decided_at > now() - interval '30 days'
GROUP BY approver_id
HAVING count(*) >= 20
ORDER BY approval_rate DESC;
| Metric | Definition | What a reading proves | Signal the control is absent |
|---|---|---|---|
| Approval rate | Approved / total decisions | Nothing on its own; only movement against your own baseline | 1.00 sustained across a large sample: the step distinguishes nothing |
| Median time to decision | Median seconds from presentation to decision | Whether the reviewer had time to read the item | Median below a timed read of a representative item |
| Modification rate | Share of decisions where the human changed the output | That the interface permits modification and someone uses it | Exactly zero across all approvers |
| Per-approver spread | Range of approval rates across approvers | That approvers exercise individual judgment | All approvers statistically indistinguishable |
| Seeded-case detection rate | Share of deliberately bad items rejected | Direct evidence the control operates | Near zero, whatever the approval rate says |
[Your risk decision] There is no published healthy range for any of these and this guide does not invent one. Two readings are decisive by logic rather than by data: an approval rate of 1.00 across a large sample means the step separates nothing, and a median decision time below the time needed to read the item means the item was not read. Everything else is a baseline problem. Measure for your first 30 days, record the observed distribution in the system record next to the thresholds, and alert on deviation from your own numbers. Any band you take from an article — including this one — describes a stranger's system.
[CloudSecOps] A 100 percent approval rate is not evidence the model is good. It is evidence the approval step carries no information, and a control that carries no information is not a control regardless of how a policy document describes it.
When the numbers say the human is not helping
Three responses, in the order to consider them.
Sample rather than review everything. A reviewer facing 500 items a day will approve nearly all of them, because that is what the throughput requires. A reviewer facing a sampled subset with time to consider each produces a usable signal about the population. [CloudSecOps] For T1 and T2 systems, statistical sampling with an escalation path beats exhaustive review that has degraded into clicking. Size the sample from the defect rate you need to detect, not from what feels thorough.
Inject adversarial cases. Seed a small known fraction of items that should be rejected: degraded outputs, outputs violating a stated constraint, outputs from an older model version. The detection rate on seeded cases directly measures whether the control operates, and unlike approval rate it cannot be explained away by "the model really is that good". Two conditions make this safe. Seeded items must be handled so they never reach a real consequence and must be distinguishable in your data store so they never contaminate quality metrics. And the practice must be disclosed to reviewers in advance, without telling them which items are seeded, because covertly testing employees has employment-law and works-council implications in several jurisdictions.
Remove the human where they add nothing, and say so. If measured outcomes show the reviewer performs at or below chance, the honest move is to delete the step and reallocate the control budget: a stricter output filter, a narrower action scope, a rollback mechanism. Keeping a human in a loop where they demonstrably add no signal is worse than removing them, because it produces an unearned claim of oversight that other controls are then sized against.
That third option has a legal boundary. [Legal] Where a system falls under Annex III high-risk obligations, Article 14 requires human oversight measures, and removing the human is not available in the same way. The measurement still matters: the requirement is for oversight that is effective, and evidence that it is not is a finding, not a licence to switch it off.
Model and provider dependencies
An AI system has a dependency that behaves like no other in your stack: it can change without a deploy, without a version bump in your lockfile, and without any event in your change management system. This is documented provider behaviour, not speculation.
Microsoft Foundry / Azure OpenAI is the clearest documented case. Deployments of type Global Standard, Data Zone Standard and Standard are auto-upgraded by Microsoft when a model version is retired, "scheduled on a rolling, region-by-region basis", with the schedule published in advance. Global Provisioned, Provisioned and Data Zone Provisioned deployments are not auto-upgraded and must be migrated manually. The behaviour is controlled by a versionUpgradeOption property with three values: OnceNewDefaultVersionAvailable, OnceCurrentVersionExpired, and NoAutoUpgrade — the last meaning the deployment stops working at retirement rather than silently changing. Notice is at least 60 days for GA models and 30 days for preview. After retirement, inference returns 410 Gone.
The consequence of "rolling, region-by-region" deserves stating plainly: on a default deployment type, two regions can serve different model versions of the same logical deployment at the same time, and nothing in your application code changed. That is a change management event with no entry in your change management system.
Amazon Bedrock uses three statuses — Active, Legacy, EOL — with published minimums: a model remains available for at least 12 months after launch before its EOL date, and stays in Legacy for at least 6 months before EOL. New customers cannot start on a Legacy model, existing customers may lose access after 15 days of inactivity, and no new Provisioned Throughput is issued for Legacy models. For models with EOL dates after 1 February 2026, after a minimum of three months in Legacy the model enters a public extended access period at higher, provider-set pricing. After EOL, requests fail. AWS states plainly that "migration will not happen automatically".
Anthropic commits to at least 60 days' notice before retiring publicly released models, emailed to customers with active deployments, with a console usage page that exports usage as CSV broken down by API key and model — directly useful as a reconciliation input. Model IDs are dated, so pinning is the default shape rather than something to opt into.
| Provider | Minimum notice | Default upgrade behaviour | At retirement | Verified |
|---|---|---|---|---|
| Microsoft Foundry / Azure OpenAI | 60 days GA, 30 days preview | Auto-upgrade on Standard tiers; manual on provisioned | 410 Gone | Yes, provider docs |
| Amazon Bedrock | 12 months on platform; 6 months in Legacy before EOL | No auto-migration; code must change | Requests fail; extended access at higher price for post-Feb-2026 EOLs | Yes, provider docs |
| Anthropic | 60 days for public models | No auto-migration; dated model IDs | Retired IDs stop serving | Yes, provider docs |
| Google Vertex / Gemini | Not verified | Documents lifecycle stages and migration paths; distinguishes stable versions from auto-updated aliases | Not verified | Partial — page relocated; figures not retrievable on two fetches |
| OpenAI | Not verified | Not verified | Not verified | No — deprecations page returned HTTP 403 on two attempts |
The blank cells are deliberate. Filling them from memory would make the table more useful and less true. [CloudSecOps] Check every provider you actually use against its own current documentation and record the numbers in your deprecation calendar rather than trusting any table, including this one.
The durable rule, which survives the product churn: notice periods are measured in weeks to months, default upgrade behaviour differs by deployment type, and at least one major platform will change the model version for you unless you opt out.
Pinning policy
[CloudSecOps] Pin to a dated snapshot at T2 and above. Aliases and auto-updating deployment types are acceptable at T3, where the blast radius is a broken experiment. Above T3, the documented rolling regional upgrade behaviour means an alias converts a reviewable change into an unobservable one, and the premise of a change gate collapses.
The counter-argument is real. Pinning means sitting on a model with known weaknesses until someone does the migration work, and unpinned deployments get quality and safety improvements automatically. [Your risk decision] The trade is between unreviewed improvement and reviewed staleness. The tier boundary above is where CloudSecOps places it; a team with a strong regression suite and a fast migration cadence can defensibly place it higher, and a team with neither should place it lower.
Pinning is worthless without the second half: a deprecation calendar with a named owner per entry.
Illustrative — deprecation calendar entry, stored in git next to the system records so the same gate can read it.
- model_id: <pinned-dated-snapshot-id>
provider: aws-bedrock
used_by: [claims-triage-assistant, agent-support-summariser]
status: legacy # active | legacy | extended-access | eol
eol_date: 2027-03-31
legacy_since: 2026-09-30
notice_received: 2026-08-01
owner: group:default/claims-platform
migration:
target_model_id: <replacement-snapshot-id>
regression_suite: evals/claims-triage/
status: not-started # not-started | in-progress | validated | done
must_start_by: 2026-12-31 # eol_date minus a full evaluation cycle
The must_start_by field is the one that does work. An EOL date twelve months out generates no urgency; a start-by date computed backwards from it, owned by a named group, and checked by the same CI job that checks record freshness, generates a build warning at the right time. Compute it from your own measured migration duration, not from a number in a guide.
Detecting a change you were not told about
Two detectors, both cheap.
The first is divergence between the model you asked for and the model that answered. OpenTelemetry's GenAI semantic conventions carry gen_ai.request.model and gen_ai.response.model as separate attributes precisely because they can differ. Alert when they diverge, and alert when the distinct set of gen_ai.response.model values for a service changes against a rolling baseline.
Illustrative — divergence detector expressed as SQL over a flattened span export. Most tracing backends store OTel attributes in a map rather than as columns, so the column names below are an assumption about your export, not a schema. Attribute names are at "Development" stability and may change.
-- Any span where the served model differs from the requested model
SELECT service_name,
gen_ai_request_model,
gen_ai_response_model,
count(*) AS spans,
min(start_time) AS first_seen
FROM otel_spans
WHERE start_time > now() - interval '24 hours'
AND gen_ai_response_model IS NOT NULL
AND gen_ai_request_model IS NOT NULL
AND gen_ai_response_model <> gen_ai_request_model
GROUP BY 1, 2, 3
ORDER BY spans DESC;
-- New response-model values not present in the prior 7 days.
-- The IS NOT NULL in the subquery is load-bearing: a single NULL in a
-- NOT IN subquery makes the whole predicate return no rows, and the
-- detector fails silently.
SELECT service_name, gen_ai_response_model, count(*) AS spans
FROM otel_spans
WHERE start_time > now() - interval '24 hours'
AND gen_ai_response_model IS NOT NULL
AND gen_ai_response_model NOT IN (
SELECT DISTINCT gen_ai_response_model
FROM otel_spans
WHERE start_time BETWEEN now() - interval '8 days' AND now() - interval '1 day'
AND gen_ai_response_model IS NOT NULL
)
GROUP BY 1, 2;
A stability caveat that matters for anyone building a control on this: nearly every gen_ai.* attribute is marked at "Development" stability, and the GenAI conventions have already moved out of the main OpenTelemetry semantic-conventions repository into a dedicated one. gen_ai.operation.name and gen_ai.provider.name are Required; gen_ai.request.model is Conditionally Required; gen_ai.response.model is only Recommended, which means it may be absent depending on your instrumentation — and a detector keyed on an absent attribute reports silence, not safety. Build it, and expect to maintain it.
The second detector is the Bedrock invocation log's modelId, diffed against the declared pin. This is ground truth for the AWS path and needs no application instrumentation, at the cost of requiring invocation logging to be enabled, which it is not by default.
Figure 4. Model version change response loop. Alt text: four trigger sources — provider notice, calendar due date, telemetry divergence, and invocation-log drift — route into a regression evaluation against a frozen test set; passing leads to a pinned update through normal gates, failing leads either to holding the current pin with migration work opened, or, if the pin is no longer available, to an exception with a compensating control and hard expiry, or to disabling the feature.
The point of the loop is that a provider notice produces a test run rather than a meeting. [CloudSecOps] If the response to "the provider is retiring our model" is a calendar invite, the organisation does not have a regression suite, and building one is more urgent than the migration.
An honest limitation: no primary source consulted during research confirms or refutes whether any provider changes the behaviour of a pinned, dated snapshot in place. Treat in-place behavioural drift on a pinned model as an engineering assumption to test rather than a fact. The scheduled regression evaluation is justified as cheap insurance whether or not the drift occurs.
Self-hosted and open-weight models
If you run weights yourself, this section changes shape rather than disappearing, and the substitutions are mechanical.
The pin becomes a content digest rather than a dated model ID, which is strictly better because you can verify it. The deprecation calendar becomes a maintenance calendar keyed on the serving stack rather than the model — inference server, CUDA and driver stack, tokenizer library — because that is where CVEs and breaking changes now arrive, and nobody sends a 60-day notice for them. Reconciliation loses the provider invocation log and gains better signals: model-server request metrics, GPU scheduling records, and the artifact registry holding the weights. The evidence trail gains the provenance of the weights themselves, for which a CycloneDX ML-BOM is the machine-readable form.
What you lose is the provider's evaluation and safety work, which shifts onto your own suite. What you gain is that nothing changes underneath you without a deploy. [CloudSecOps] For a T0 or T1 system, that property alone is often worth the operational cost.
Evaluation, monitoring, and the evidence trail
Evaluation with a threshold declared first
The rule that makes evaluation useful is procedural rather than technical: the threshold is declared in the record before the run, and the run is scored against the declared threshold. An evaluation whose pass criterion is decided after seeing the result is a report.
Four triggers, at minimum: any pull request touching the prompt, the retrieval configuration or the model pin; any model version change; a scheduled run against a frozen test set to detect drift; and any incident, as part of the postmortem.
For threat coverage, map test cases against two sources rather than one, because they are organised differently. [Standard] NIST AI 600-1, the Generative AI Profile published July 2024, enumerates twelve risk categories organised by harm, including confabulation, data privacy, harmful bias or homogenization, human-AI configuration, information integrity and value chain integration. [Standard] OWASP's Top 10 for LLM Applications 2025 is organised by attack surface: prompt injection, sensitive information disclosure, supply chain, data and model poisoning, improper output handling, excessive agency, system prompt leakage, vector and embedding weaknesses, misinformation, unbounded consumption. A suite derived from only one leaves a visible gap. [Standard] NIST SP 800-218A, finalised 26 July 2024, is the SSDF community profile for generative AI, and is the right document to map existing secure-development practices onto rather than inventing an AI-specific SDLC.
Microsoft's documented evaluation approach is a useful reference shape because it is vendor-documented rather than theoretical: benchmark-based model selection, pre-production evaluation with curated datasets and edge cases plus red teaming built on PyRIT, then post-production continuous evaluation of sampled production traffic with scheduled drift runs against a test dataset. [Standard] The evaluator families it names — general quality, retrieval groundedness and relevance, safety and security, and agent measures like tool call accuracy and task completion — are a reasonable checklist whichever tooling you use. Inspect, from the UK AI Security Institute, and promptfoo, a declarative CLI with CI integration, are both usable as the thing a deployment gate calls; neither page displayed a version number during research, so they are named by capability.
Monitoring that names its metrics
[CloudSecOps] Refuse to use the word "monitoring" in a governance artifact without naming, in the same sentence, a metric, a threshold, an alert destination, and a review cadence. This one editorial rule eliminates most governance theatre in the monitoring row.
A minimum set for a T1 or T2 system: refusal rate; output length distribution; retrieval hit rate and groundedness score where retrieval is used; tool call error rate for agentic systems; token consumption per request against the declared rate limit; the model version divergence detector above; and the oversight metrics from the previous section. Each with a threshold, each alerting somewhere a human is paged or a ticket is created, each reviewed on the tier's cadence.
[Legal] Article 72 requires providers of high-risk systems to establish a post-market monitoring plan. Note the change: Regulation (EU) 2026/1744 removed the Commission's empowerment to adopt an implementing act specifying the template for that plan, and replaced it with an obligation to publish guidance, including a voluntary template, by 2 September 2027. The plan is yours to structure, and nothing in it waits on the Commission.
Decision logging and the retention question
[Legal] Article 12 requires that high-risk AI systems "technically allow for the automatic recording of events (logs) over the lifetime of the system", to enable identification of risk situations and to facilitate post-market monitoring. [Legal] Article 26(6) requires deployers of high-risk systems to keep logs automatically generated by that system, "to the extent such logs are under their control, for a period appropriate to the intended purpose of the high-risk AI system, of at least six months, unless provided otherwise in applicable Union or national law".
Two qualifiers in that sentence do engineering work. "Under their control" means that if the provider holds the invocation record and you do not, the obligation reaches only what you hold, which is an argument for enabling your own logging rather than a reason to skip it. "Unless provided otherwise" means sector rules can override the floor in either direction; regulated financial deployers in particular meet this through existing documentation obligations.
What to log is harder than how long. OpenTelemetry's guidance states that model instructions, user messages and model outputs "are considered sensitive and are often large in size", and that instrumentations should not capture them by default but should offer opt-in. That is the correct default. [Your risk decision] Retaining prompts and completions is a data protection decision before it is a governance one: you are creating a durable store of whatever users typed, which for many applications includes personal data they were not asked to provide.
[CloudSecOps] A workable middle position: always log the metadata — request id, timestamp, principal, gen_ai.request.model, gen_ai.response.model, token counts, finish reason, latency, and a content hash. Log content selectively, for T0 and T1 systems or on a sampled basis or only on error and override paths, with shorter retention than the metadata and a documented lawful basis. On Bedrock, invocation logging captures input.inputBodyJson and output.outputBodyJson up to 100 KB inline, with larger or binary bodies written to S3 under a data prefix. Turning it on is a data-retention decision as much as an observability one.
| Lifecycle stage | Artifact | Where stored | Minimum retention |
|---|---|---|---|
| Design | Threat model; data lineage; classification rationale | Git, alongside code | Life of system plus 3 years [CloudSecOps] |
| Pre-deployment | Classification assessment with date and author; evaluation baseline; residual risk acceptance | Git plus evidence store | Same |
| Deployment | Gate check runs; branch protection snapshots; approvals; exception records | CI history plus evidence store | 13 months [CloudSecOps] |
| Operation | Decision logs; oversight metrics; alert history | Observability platform, object storage | [Legal] at least 6 months, high-risk deployer; content shorter [Your risk decision] |
| Change | Model version change records; regression run artifacts | Evidence store | 13 months [CloudSecOps] |
| Incident | Timeline; classification; reporting decision with timestamps | IR platform | Per existing IR policy |
| Retirement | Decommission record; data and key disposition | Evidence store | Longest applicable retention of the data it processed |
[Standard] Annex IV lists the technical documentation for high-risk systems: general description and intended purpose, development process and architecture, human oversight measures, predetermined changes, validation and testing procedures, capabilities and limitations, foreseeable risks, performance metrics, lifecycle changes made by the provider, standards applied, and the post-market monitoring plan. Read as an evidence checklist rather than a document outline, every item maps to something in the table above. If you produce the artifacts as a by-product of engineering work, the technical documentation is an assembly job. If you do not, it is a writing project, and writing projects produce documents that describe a system nobody built.
Incidents and exceptions
Incident clocks are a paging requirement
[Legal] Article 73 sets reporting clocks for serious incidents: 15 days generally; 2 days where the incident involves widespread infringement or disruption of critical infrastructure; and 10 days where a person has died. Each period runs from when the provider — "or, where applicable, the deployer" — becomes aware of the incident.
Read the addressee carefully, because it changes who is on the clock. The Article 73 filing obligation sits on providers of high-risk systems placed on the Union market. If you are a deployer, your direct duty under Article 26(5) is to inform the provider and the relevant authorities without undue delay, which means your clock is shorter than the provider's and is set by how fast you can reach them. Neither role gets to discover the incident slowly.
A two-day clock is not a policy sentence. It is a paging requirement: someone has to classify an incident as reportable within hours, at 3am, without a lawyer available. [CloudSecOps] That translation has three parts — a triage question in the incident runbook that fires on AI system involvement, a pre-identified escalation contact who can make the reporting call, and a timestamped record of when the incident was known, because every clock runs from awareness and reconstructing awareness afterwards is both hard and unconvincing.
AI incidents differ from ordinary security incidents in a way existing triage does not capture. The system may be behaving exactly as designed and still producing the harm: no compromise, no unauthorised access, no vulnerability to patch, just an output that caused a consequence. A severity matrix keyed on confidentiality, integrity and availability has no row for "correct system, wrong answer, real damage". Add one, and add a triage step that captures the model version in use at the time, because a postmortem that cannot identify which model produced the output cannot conclude anything.
Exceptions with teeth
Every gate needs an exception path, because a gate with no exception path gets disabled the first time it blocks something urgent. The exception is a record, subject to the same gate it waives.
Illustrative — exception record. Expiry, compensating control and named accepter are the mandatory fields; everything else is convenience. Values are hypothetical.
apiVersion: example.com/v1
kind: GovernanceException
metadata:
name: exc-2026-0143
spec:
system_ref: component:default/claims-triage-assistant
control_waived: evaluation-threshold-ranking-correlation
reason: >-
Replacement model scores 0.64 against a 0.72 threshold on the frozen test
set. Current pinned model reaches extended-access pricing on 2026-11-30 and
the cost delta is material. Deploying the replacement with reduced scope.
compensating_control: >-
Ranking confidence below 0.8 routes to unranked FIFO queue rather than
displaying a low-confidence ranking. Sampling review raised from 5% to 25%
for the exception period.
compensating_control_verified_by: user:default/s.okafor
compensating_control_evidence: s3://evidence/exceptions/exc-2026-0143/
accepted_by: user:default/m.chen # named human, not a group
accepted_role: director-of-engineering
accepted_on: 2026-08-04
expires_on: 2026-11-04 # hard stop, enforced by the gate
review_before_expiry: 2026-10-21
Three properties make this real rather than decorative.
Expiry is enforced by the same gate. The policy that checks the exception's validity fails once expires_on passes, so an unrenewed exception blocks the next deployment. Without that, exceptions accumulate and the gate becomes advisory by attrition.
The accepter is a named individual. accepted_by: group:default/security accepts nothing. The purpose of the field is to put a person's name next to a decision they will be asked about.
The compensating control has evidence. "We increased monitoring" is not a compensating control; "sampling review raised from 5 to 25 percent, dashboard link, verified by name" is. [CloudSecOps] Require that the compensating control be verified by someone other than the requester, and that the verification produce an artifact.
Track exception count and median exception age per gate. [CloudSecOps] A rising exception count against one specific gate is the clearest available signal that the gate is miscalibrated: it is catching things the organisation has repeatedly decided it is willing to accept, which means the policy should change rather than the exceptions continuing.
Who owns what
[CloudSecOps] Do not start with a review board. A board is the default recommendation in most governance material and it is usually the wrong first move for an engineering organisation, for a structural reason: a board is a batch process with a queue, and it becomes the bottleneck teams route around, which produces the worst outcome — a control that delays honest teams and does not catch dishonest ones.
Start with named service owners, automated gates, and a narrow escalation path. Add a board only when you have a class of decision that genuinely cannot be resolved by a gate and an owner, which in practice means novel harms, cross-organisational trade-offs, and residual risk acceptance for T0 systems.
| Activity | Service owner | Security | Legal and privacy | Escalation |
|---|---|---|---|---|
| System record and internal tier | Accountable | Reviews at T0–T1 | — | Platform lead |
| Legal trigger assessment | Contributes facts | Consulted | Accountable | External counsel |
| Threat model | Accountable | Accountable jointly | — | Security lead |
| Evaluation suite and thresholds | Accountable | Reviews thresholds at T0–T1 | — | Security lead |
| Model pin and deprecation migration | Accountable | Informed | — | Platform lead |
| Human oversight design and instrumentation | Accountable | Consulted | Consulted at T0 | Security lead |
| Exception request | Requests | Verifies compensating control | Consulted if legal trigger | Named accepter, director level or above |
| Residual risk acceptance | Proposes | Reviews | Reviews at T0 | Named accepter, recorded |
| Incident classification and reporting decision | Contributes facts | Accountable | Accountable for reporting call | Incident commander |
| Retirement and data disposition | Accountable | Verifies key and access revocation | Confirms retention obligations | Platform lead |
The smallest set of roles that works: a service owner per system who is a real on-call group, one security engineer who owns the policy bundle and the gates, and one named individual with authority to accept residual risk. Below that, you have governance that depends on one person's memory. Above that, before you have systems to govern, you have a process looking for work.
How this fails
Each of these is common, each is detectable with a number you can compute this week, and each has a fix smaller than the problem suggests.
| Failure mode | Detection signal | Fix |
|---|---|---|
| Governance theatre: a policy document, a spreadsheet, no CI check referencing either | Count of gates with a nonzero block rate: zero | One blocking gate on record presence; accept that everything else waits |
| Inventory rot: the catalogue was accurate at the time of the audit | Observed-not-declared count, trending up | Reconcile weekly; ticket the owner, do not block |
| Unfalsifiable inventory: reconciliation returns nothing because its inputs are off | Accounts and Regions where invocation logging is not enabled | Make logging enablement its own config rule |
| Rubber-stamp oversight: "a human reviews every output" with no dashboard | Approval rate at 1.00 over a large sample; median decision time below reading time | Seed adversarial cases; move to sampling; measure detection rate |
| Gate erosion: exceptions have quietly become the normal path | Exception count per gate per quarter, trending up; median age exceeding expiry | Change the policy the exceptions are all against; enforce expiry |
| Unenforced gate: the check exists but is not a required check | Diff of branch protection configuration over time | Snapshot that configuration into the evidence store on a schedule |
| Tier collapse: one tier holds most of the estate | Distribution of tiers across records | Recalibrate the decision tree questions; make T3 expire |
| Stale legal claims: an internal deck cites deadlines that moved | Date of last verification, versus the regulator's page | Date-stamp every regulatory claim; cite the regulator, never a tracker |
| Silent model change: behaviour shifts, the postmortem stalls | gen_ai.response.model values absent from the prior baseline | Pin at T2+; build the divergence detector; alert on it |
Undetected retirement: 410 Gone in production is the first sign | Pinned models with no deprecation calendar entry | Every pin gets a calendar entry with a must_start_by date |
Three more belong on the list without needing a row of their own: write-only threat models, detected by the median age of threat_model_reviewed across the estate; orphaned ownership, detected by the share of records whose spec.owner resolves to a group with a live on-call schedule; and evidence that only records successes, detected by looking for a single failed check run in the store.
Every detection signal here is a query against data you already have, except the reconciliation job, which is a scheduled script. The fixes are single mechanisms rather than programmes, which matters: a governance improvement that requires a programme will not happen at a company that does not already have one.
Minimum viable governance
[Legal] Article 17(2) states that implementation of the quality management system "shall be proportionate to the size of the provider's organisation", and Regulation (EU) 2026/1744 extended that principle by allowing SMEs, startups and small mid-caps to supply technical documentation elements in simplified form on a Commission-established form which notified bodies must accept. Proportionality is written into the instrument. Use it.
Here is what a twenty-engineer company should build in thirty days, and — the more valuable half — what it should deliberately skip.
Week 1: find out what you have. Enable provider invocation logging where it is off, which on Bedrock is everywhere by default. Write the reconciliation query. Produce a list of every service that has talked to a model provider in the last 90 days. This is the only week that produces a surprise, and the surprise is the point.
Week 2: one record per system. Write the ai-system YAML for each service on the list, in that service's repository. Fill in owner, intended use, non-recommended use, model pin, and a review date. Leave the rest empty. Classify each system with the four-question internal tier tree; skip the legal trigger branch unless you have EU users or a Colorado or Texas nexus, in which case do that branch first for the systems touching consequential decisions.
Week 3: two gates. Gate one: a record exists and is schema-valid, with a resolvable owner and a future review date. Gate two: the model is pinned to a dated snapshot at T2 and above. Both blocking. Nothing else. Two gates that block are worth more than eight that advise.
Week 4: one measurement and one calendar. Instrument the human oversight point on your highest-tier system — approval rate and time to decision, nothing more. Create the deprecation calendar with one entry per pinned model, each with an owner and a must_start_by date. Schedule the reconciliation job weekly.
| Capability | What the enterprise adds | What the small team skips, and why |
|---|---|---|
| Inventory | Federation across accounts, plus a logging-enablement check per account | Nothing — this is load-bearing at any size |
| Classification | Both branches for every system; counsel review of the legal branch | Formal impact assessments; they come after tiering |
| Gates | The full gate table, tier-differentiated, with branch-protection evidence | Evaluation gates in month one; you need a suite before you can gate on it |
| Evaluation | Suites per system, scheduled drift runs, red teaming | Comprehensive coverage; a real suite on one system beats a stub on ten |
| Oversight | Every approval point instrumented, with seeded adversarial cases | Seeding, initially; add it in month two |
| Evidence | Versioned store with retention per class | An evidence management platform; a bucket and a naming convention suffice |
| Roles | The full matrix, plus a board for T0 residual risk only | The review board; most commonly recommended, most commonly bypassed |
What both columns share is the reconciliation job, the pinned models, and at least one gate that has actually blocked something. [CloudSecOps] If a governance programme at any size cannot point to a specific change a gate stopped, it has not started.
Below about five engineers the plan compresses further, and honestly: run the reconciliation job, pin the models, and accept that everything else is a person paying attention. A three-person team writing its own records, evaluations and approvals has separation of duties in form only, and a plan that pretends otherwise is worse than one that names the gap.
Retirement, and what you keep
Retirement is the least-implemented lifecycle stage in every governance programme, because nothing forces it. A system that has stopped being used raises no alert, and the reconciliation job's "declared but not observed" output is usually the only signal you get.
Figure 5. AI system lifecycle states. Alt text: a system moves from proposed to approved to production through gates; model version changes loop within production via the regression gate; gate failures route through an exception-granted state that either returns to production or, on expiry without renewal, moves to retired; systems reach legacy through supersession or provider end-of-life and then retired via a decommission checklist, with evidence retained beyond the system's life.
The decommission checklist, in the order things get forgotten:
- Credentials. Revoke the service role, provider API keys and long-lived tokens. Verify by attempting an invocation, not by reading an IAM policy. This item fails most often, and a working key on a retired system is a credential with no owner and no monitoring.
- Data. Delete or archive retrieval indexes, embedding stores, fine-tuning datasets and cached completions per the retention obligations of the underlying data, not per the system's own lifecycle. Embedding stores are the commonly missed one: they hold a lossy but real representation of whatever was indexed.
- Egress. Remove the network path and any provider account entitlement, so a redeployment of old code fails loudly rather than silently working.
- Evidence. Move evaluation artifacts, gate history, approvals and exceptions into their retention location. Their clock runs from the data the system processed and any applicable reporting obligation, not from the shutdown date.
- The record. Set
lifecycle: retiredrather than deleting the file. A deleted record makes the reconciliation job's memory shorter than the incident timeline you may later need to reconstruct. - Downstream consumers. Check who reads the system's outputs. A retired ranker whose stale scores are still cached in a downstream table has stopped being maintained and has not stopped having effects.
Limits of this approach
Mechanism-first governance is the right default for an engineering organisation and it under-serves in five identifiable places. Stating them is part of the recommendation.
Novel harms have no metric yet. Everything above assumes you can name what you are measuring. The failures that will matter most in the next two years are the ones nobody has a threshold for, and a system built entirely from thresholds is structurally blind to them. The partial compensation is qualitative: a red team exercise with no success criteria, a scheduled session where someone describes how the system could hurt somebody in a way the test suite would not catch, and a route for that finding to become a tracked item. Weaker than a metric, and not nothing.
No gate fixes an organisational decision. If a product leader has decided the feature ships, a blocking gate produces an exception rather than a different outcome. The mechanism gives you attribution, not prevention: a named accepter, a date, and a compensating control. That is a smaller claim than "governance prevents bad deployments" and it is the honest one.
At small scale, the gate and the builder are the same person. A three-engineer team where one person writes the system, the record, the evaluation and the approval has separation of duties in form only. The realistic mitigation is external: periodic review by someone outside the team, or a peer from another team as the named accepter for T0 and T1 systems.
This assumes a git-and-CI shape. An organisation whose AI systems are built in a low-code platform, a SaaS workflow tool or a vendor's own console has no pull request to gate and no repository to hold the record. The record still works as an artifact; the enforcement does not. What remains is periodic attestation plus whatever signal the platform emits, which is materially weaker and should be described that way rather than dressed up as the same control.
Provider opacity bounds everything. When a hosted model changes in ways the provider does not document, your regression suite detects the effect and not the cause, your incident review terminates at "the provider changed something", and your evidence trail records an unexplained shift. The mechanisms above make that visible rather than invisible, which is a genuine improvement and is not the same as control.
One limit specific to this guide: the labelling convention is a discipline, not a guarantee. Every [Legal] statement was checked against a primary source on the verification date, and every one will need re-checking. The most useful habit to take from this guide is the one demonstrated in its own third section — when two sources disagree about the law, check which one is the regulator, and check when it was last updated.
References
Regulation and official guidance
- European Commission — regulatory framework for AI, current application dates
- European Commission — AI Omnibus enters into force, 27 July 2026
- Regulation (EU) 2026/1744 of 8 July 2026, Digital Omnibus on AI, EUR-Lex
- Regulation (EU) 2026/1744 full text, HTML
- Regulation (EU) 2024/1689, the AI Act, EUR-Lex record
- EU AI Act Article 6 — classification rules for high-risk AI systems
- EU AI Act Article 9 — risk management system
- EU AI Act Article 12 — record-keeping
- EU AI Act Article 14 — human oversight
- EU AI Act Article 17 — quality management system
- EU AI Act Article 26 — obligations of deployers
- EU AI Act Article 49 — registration
- EU AI Act Article 72 — post-market monitoring
- EU AI Act Article 73 — reporting of serious incidents
- EU AI Act Annex IV — technical documentation
- AI Act Service Desk, European AI Office
- General-Purpose AI Code of Practice, European Commission
- Colorado SB24-205, Consumer Protections for Artificial Intelligence
- Colorado SB25B-004, delaying SB24-205 to 30 June 2026
- Colorado SB26-189, Automated Decision-Making Technology, repealing and reenacting SB24-205
- Colorado Attorney General — AI rulemaking and effective dates
- Texas HB 149, Texas Responsible Artificial Intelligence Governance Act, enrolled text
Standards and frameworks
- NIST AI Risk Management Framework 1.0 overview
- NIST Trustworthy and Responsible AI Resource Center
- NIST AI 600-1, Generative AI Profile, July 2024 (PDF)
- NIST SP 800-218A, Secure Software Development Practices for Generative AI
- ISO/IEC 42001:2023, AI management system
- ISO/IEC 23894:2023, AI risk management guidance
- ISO/IEC 42005:2025, AI system impact assessment
- OWASP Top 10 for LLM Applications 2025
Provider documentation
- Amazon Bedrock model lifecycle and end-of-life
- Amazon Bedrock model invocation logging
- Amazon SageMaker Model Cards
- Anthropic model deprecations and notice periods
- Microsoft Foundry model deprecations and retirements
- Microsoft Foundry evaluation approach for generative AI
- Google model versions, stable versions and auto-updated aliases
Tooling and specifications
- OpenTelemetry GenAI semantic conventions, relocation notice
- OpenTelemetry GenAI span attributes and stability levels
- Backstage software catalog descriptor format
- Open Policy Agent releases
- Open Policy Agent documentation
- CycloneDX ML-BOM and AI-BOM capability
- Inspect evaluation framework, UK AI Security Institute
- promptfoo evaluation and red-teaming documentation
Validity and revision
Verification date: 2026-08-09. Every regulatory date, provider commitment, standard designation and version number in this guide was checked against the primary source linked above. Most were checked on 2026-08-07, two days after the 2026-08-06 publication date in the frontmatter. The US state law section and the Article 72 monitoring template were re-checked on 2026-08-09 and corrected: the Colorado paragraph had described a regime that SB 26-189 repealed and reenacted on 14 May 2026, the Texas paragraph had attributed a governmental-agency disclosure duty to private entities, and the Article 72 note had recorded the removal of the mandatory monitoring template without the guidance obligation that replaced it. The corrections are in the text above; the failure is the one this guide's third section is about.
What was not verified, and is marked as such in the text: OpenAI's deprecation notice period (page returned HTTP 403 on two attempts); Google's notice period in days or months (page relocated and returned truncated content on two attempts); Colorado Attorney General rulemaking under SB 26-189, which had not been formally proposed on the verification date, and the outcome of the enforcement stay in xAI v. Weiser; CEN-CENELEC harmonised standards status; and whether any provider alters the behaviour of a pinned, dated snapshot in place.
What is version-dependent or short-lived:
- EU AI Act dates. The high-risk application dates of 2 December 2027 (Annex III) and 2 August 2028 (Annex I) are the post-Omnibus values set by Regulation (EU) 2026/1744, in force from 27 July 2026. They have already moved once. The December 2026 prohibition on non-consensual intimate imagery and CSAM generation is not yet in force, and the Commission's Article 72 guidance with its voluntary post-market monitoring template is not due until 2 September 2027.
- US state law. Colorado's regime has moved three times: SB24-205's 1 February 2026 date, SB25B-004's 30 June 2026 date, and now SB 26-189, which repeals and reenacts SB24-205 and applies to decisions made on or after 1 January 2027, with enforcement stayed in xAI v. Weiser. Texas HB 149's 1 January 2026 effective date is taken from the enrolled bill text, and its AI-interaction disclosure duty binds governmental agencies rather than private deployers. State AI legislation is the fastest-moving item in this guide.
- NIST AI RMF remains at 1.0 (AI 100-1, 26 January 2023) and is under revision with no published target date. There is no 2.0. NIST SP 800-218 Rev. 1 is an initial public draft and should not be cited as settled.
- Provider lifecycle terms. Microsoft's 60/30-day notice, Bedrock's 12-month and 6-month minimums, and Anthropic's 60-day commitment are current provider policy and change without a versioned artifact.
- OpenTelemetry
gen_ai.*attributes are at "Development" stability and the conventions have relocated to a dedicated repository. Any control built on these attribute names will need maintenance. - Tool versions. OPA v1.17.0 (28 May 2026), whose opt-in
future.keywords.notimport changes negation semantics. Inspect and promptfoo are referenced by capability because neither page displayed a version. - Product names. During research, Anthropic's developer docs moved to
platform.claude.com, Google's Vertex model-versioning docs redirected to a Gemini Enterprise Agent Platform path, and Microsoft's docs render the product as "Microsoft Foundry" while the URL path remainsazure/ai-foundry/. Any governance artifact that hardcodes these links is already decaying.
Recommended review date: 2027-02-09, and immediately if the EU AI Act is amended again, if the NIST AI RMF revision is published, or if a provider you depend on changes its deprecation policy.
- ai-governance
- ai-security
- eu-ai-act
- nist-ai-rmf
- iso-42001
- policy-as-code
- model-risk
- mlops
The service behind this work
Cloud and AI security training
Instructor-led private cohorts for your engineers and your security team — cloud security, AI and agent security, threat modelling, detection engineering — taught against real misconfigurations, with architecture review and AI security programme design when the gaps turn out to be structural.
Related reading
All articles →Detection engineering in AI-era clouds
Most evidence you need to detect attacks on AI workloads is off by default, billable, capped or sampled. A field guide to the telemetry that exists today.
· 77 min read
DevSecOps practice notes
Thirty-six field notes on DevSecOps practice: pipeline identity, build provenance, secret blast radius, and vulnerability triage after NVD stopped enriching a third of new CVEs. Each note states the condition under which it stops being true.
· 65 min read
Kubernetes guardrails with OPA Gatekeeper
Writing the constraint is the easy part. An operational guide to running Gatekeeper as admission control you can afford to have fail: fail-open defaults, cold caches, audit blind spots, staged rollout, and the silent-drop bugs that make a green CI run mean nothing.
· 63 min read