Most organisations that build or deploy AI now keep some form of obligation register: a list of the rules that apply to each system and the date each one starts. The register is only as good as the process that keeps it current. A regulatory watch is that process. It notices when a law is proposed, amended, adopted, published, delayed or interpreted by a regulator; decides whether the change matters to you; and turns the ones that do into a dated, reviewed change to the register.

This article is about building the watch as a system, with sources, collectors, change detection, triage, an optional language-model assistant and metrics that show whether it is missing things. The register itself, applicability rules and the mapping from obligations to controls are covered in the AI regulation deep dive; read it alongside this one. Dates below were checked against public reporting in October 2026 and should be confirmed against the official text before you act on them. Nothing here is legal advice.

A law is a lifecycle, not a date

The commonest failure in regulatory tracking is collapsing a law into a single date. A measure passes through stages, and each stage carries a different meaning for engineering work:

StageWhat it meansTypical action
ProposedText exists, may change, no obligationPlanning note; estimate impact
Agreed politicallyText close to finalStart design work on likely controls
AdoptedFinal text approvedDraft register change
PublishedOfficial text and dates fixedMerge register change, open tickets
In forceLaw exists; parts may not apply yetTrack each application date separately
AppliesObligation binds youControls must pass
Guidance and standardsHow regulators read itAdjust controls and evidence
EnforcementWhat regulators actually pursueRe-rank risks

Entry into force and application are different dates. The EU AI Act entered into force on 1 August 2024, but its prohibitions applied from 2 February 2025, general-purpose model obligations from 2 August 2025, and most high-risk obligations later still. A watch that records only "in force 2024" tells engineering nothing useful. Each item in the watch therefore has a stage and a set of dated events, never a single date.

Reference architecture

A workable watch has four automated stages and two human ones. Collectors fetch each source on a schedule and keep an immutable snapshot. A normaliser extracts text, dates and stage, and a differ compares it with the previous snapshot. A classifier decides which changes are candidates. Everything that survives reaches a triage queue, where an analyst and, for anything that changes obligations, counsel decide the outcome.

A regulatory watch pipeline: from source change to register pull requestSource cataloguejournals, regulators, SDOsCollectorsfetch, snapshot, hashNormalise + difftext, dates, stageClassifierrules first, LLM assistQuote verifierevery claim cites source textTriage queuescore, owner, SLAAnalyst + counselconfirm, decide actionRegister pull requestdated obligations changePlanning noteproposal tracked, no changeClosed: not relevantreason recordedBlue boxes are automated, yellow boxes need a human, green boxes are outcomes. Fetched text is untrusted input to the classifier.
Figure 1. The watch feeds the obligation register through reviewed pull requests; it never edits the register directly.

Three design rules keep it honest. First, snapshots are kept, so you can later prove what the source said on the day you decided. Second, the watch never writes to the register; it opens a change that a person approves, as the governance council page describes for policy changes generally. Third, every outcome, including "not relevant", is recorded with a reason, because the closed items are the evidence that the watch was looking.

The source catalogue

Rank sources by authority, not by convenience. Secondary commentary from law firms and trade press is fast and well summarised, and it is the usual way people first hear about a change, but it is interpretation. The register should cite primary text.

TierExamplesUse
Primary lawOfficial Journal of the EU via EUR-Lex, US Federal Register, state legislature bill pagesDates and operative text
Regulator outputEU AI Office, national authorities, US FTC, UK ICO guidance and decisionsInterpretation, enforcement
StandardsCEN-CENELEC JTC 21, ISO/IEC JTC 1/SC 42, NIST AI publicationsHow to evidence compliance
SecondaryLaw firm trackers, trade press, industry bodiesEarly warning only

Each catalogue entry records the jurisdiction, the owner responsible for it, the fetch method (feed, API or page snapshot), the expected publishing cadence and the date it was last confirmed to be working. That last field matters: sources move, feeds die quietly, and a silent source is indistinguishable from a quiet one unless you check.

The item model

Each tracked measure is one item with a stage and a list of dated events. A minimal schema:

{
  "id": "eu-ai-act-omnibus",
  "jurisdiction": "EU",
  "title": "Digital omnibus on AI (amends the AI Act)",
  "stage": "published",
  "events": [
    {"date": "2025-11",    "stage": "proposed", "source": "snap/2025-11-19/ec-proposal.html"},
    {"date": "2026-06-29", "stage": "adopted",  "source": "snap/2026-06-29/council.html"},
    {"date": "2026-07-24", "stage": "published", "source": "snap/2026-07-24/oj.html"}
  ],
  "affects": ["hiring-screener", "credit-assist"],
  "owner": "eu-reg-analyst",
  "register_pr": "reg#412",
  "status": "closed:register-updated"
}

The affects field links the item to systems in your inventory, which is what lets the watch route work to the right team. The source of every event points to a stored snapshot, not a live URL.

Change detection

Change detection is mostly unglamorous text processing. Fetch, strip boilerplate such as navigation and cookie banners, normalise whitespace and dates, hash, and compare with the previous snapshot. Only changed documents move on.

import hashlib, re, difflib

def normalise(html_text):
    text = re.sub(r"<(script|style|nav|footer)[^>]*>.*?</\1>", " ", html_text, flags=re.S | re.I)
    text = re.sub(r"<[^>]+>", " ", text)
    return re.sub(r"\s+", " ", text).strip()

def check(source, fetch, store):
    raw = fetch(source.url)
    text = normalise(raw)
    digest = hashlib.sha256(text.encode()).hexdigest()
    prev = store.latest(source.id)
    store.save(source.id, raw, text, digest)          # always keep the snapshot
    if prev is None or prev.digest == digest:
        return None
    diff = difflib.unified_diff(prev.text.split(". "), text.split(". "), lineterm="", n=1)
    return {"source": source.id, "diff": "\n".join(diff)}

Page-level diffs are noisy, because a changed date in a sidebar triggers them. Two refinements help. Extract only the content region for each source, using a selector you maintain per source. And diff extracted facts as well as text: dates, stage words such as "adopted" or "postponed", and article numbers. A change in an extracted date is high signal even if the surrounding text barely moves.

Triage and service levels

Triage decides who looks at what, and how fast. Score each candidate on three factors: relevance to your inventory, proximity of any dated event, and impact if it applies.

def triage_score(item, inventory, today):
    relevance = len(set(item.affects) & inventory.live_systems) / max(1, len(inventory.live_systems))
    days = min((e.date - today).days for e in item.upcoming_events()) if item.upcoming_events() else 999
    proximity = 1.0 if days <= 90 else 0.6 if days <= 365 else 0.2
    impact = {"prohibition": 1.0, "high-risk": 0.8, "transparency": 0.5, "guidance": 0.3}[item.kind]
    stage_weight = {"proposed": 0.3, "adopted": 0.8, "published": 1.0}.get(item.stage, 0.6)
    return round(relevance * proximity * impact * stage_weight, 3)

The weights are illustrative; calibrate them against your own history. Map score bands to service levels that someone has agreed to meet, for example triage within two working days for the top band and two weeks for the bottom. Anything that moves an application date for a live system skips the queue.

Using a language model safely

Language models are good at the middle of this pipeline: summarising a forty-page amendment, tagging which systems it might affect, and extracting dates. They are also an attack surface and a source of confident errors, which is why this page sits in a security category.

Treat every fetched document as untrusted input. A page can contain text written to steer a model, and a model that both reads web content and proposes register changes is an indirect prompt injection target. Give the classifier no tools and no write access; its only output is a structured proposal that a person reviews.

Then require evidence for every extracted fact. Each date or obligation the model proposes must come with a verbatim quote, and a deterministic check confirms the quote exists in the stored snapshot:

def verify_claims(claims, snapshot_text):
    norm = lambda s: re.sub(r"\s+", " ", s).strip().lower()
    body = norm(snapshot_text)
    for claim in claims:
        claim["verified"] = norm(claim["quote"]) in body
        if claim["verified"] and claim.get("date"):
            claim["verified"] = claim["date_text"].lower() in norm(claim["quote"])
    return [c for c in claims if not c["verified"]]   # route these to a human

A hallucinated date fails the check because no such sentence exists. A real sentence attached to the wrong claim still passes, so the reviewer reads the quote, not the summary.

Worked example: the AI Act omnibus

Trace one real change through the pipeline: the EU's digital omnibus amending the AI Act. The Commission published the proposal in November 2025. The watch logged it as proposed, scored it high on relevance for any team with Annex III systems such as hiring or credit tools, but low on stage, so the outcome was a planning note: keep building towards the original 2 August 2026 date, because a proposal is not a deferral.

On 29 June 2026 the Council gave final approval. The item moved to adopted and a draft register change was prepared but not merged. The regulation was published in the Official Journal on 24 July 2026 and entered into force on 27 July 2026, six days before the original date. Publication triggered the merge: application of stand-alone high-risk obligations under Annex III moved from 2 August 2026 to 2 December 2027, and high-risk obligations for AI in products covered by Annex I to 2 August 2028.

Three lessons came out of that sequence. Teams that read the November proposal as a delay and paused work would have been non-compliant had adoption slipped past August. A deferral of some obligations is not a deferral of all of them: prohibitions and general-purpose model duties were already applying, and the register should show which rows moved and which did not. And the timing was close, which is exactly when a watch with stage tracking and snapshot evidence earns its keep.

Measuring the watch

A watch that never reports misses looks perfect. Measure it:

  • Time to detect: publication date to first snapshot showing the change.
  • Time to triage and time to register, against the agreed service levels.
  • Miss rate: changes your team first heard about from outside the watch, such as a customer question or a newsletter. Log every one and add the missing source.
  • Noise rate: share of triaged items closed as not relevant. Very high means sources or rules are too broad; near zero suggests you are filtering too hard.
  • Source health: sources with no successful fetch in twice their expected cadence.

Report these quarterly alongside the register to the group that owns AI risk, and keep the evidence ready for an examiner as described in audit preparation.

Failure modes

  • One-date thinking. Recording "in force" instead of each application date.
  • Proposal treated as law. Teams pause or start work on a text that later changes.
  • Commentary as source. The register cites a blog post that summarised a draft.
  • Silent sources. A feed breaks and the watch reports a quiet month.
  • Model output merged directly. An unverified date reaches the register, or injected page text steers the summary.
  • No owner per jurisdiction. Items sit in a shared queue that everyone assumes someone else is reading.
  • Closed items not recorded. You cannot show what the watch reviewed and rejected.

Trade-offs

Buying a commercial tracker gives breadth and legal summaries quickly, but it is still secondary material, it does not know your inventory, and you need the triage and register steps regardless. Building gives precise routing and evidence but costs engineering time and source maintenance. Most teams do both: a purchased feed as one early-warning source, and their own collectors on the handful of primary sources that drive real obligations.

Breadth trades against noise. Watching every jurisdiction where you have a single user floods triage; watching only your headquarters misses the market that sets the strictest rule. Start from where your systems are sold and used, recorded in the risk register, and widen deliberately.

What to do next

  1. List the jurisdictions where your AI systems are developed, sold or used, and name one owner for each.
  2. Build the source catalogue with tiers, fetch methods and expected cadence; prefer primary text.
  3. Store immutable snapshots for every fetch and add a source-health alert.
  4. Adopt the stage and dated-event model, and migrate existing register rows to cite snapshots.
  5. If you use a language model for summarising, remove its tools and write access, and add quote verification before anything reaches a reviewer.
  6. Agree triage service levels and start logging misses from day one.
  7. Connect merged register changes to tickets as in the AI governance programme, so a moved date changes delivery plans.
Key takeaway: A regulatory watch is the intake system for your obligation register. Track each measure as a lifecycle with dated events, prefer primary sources, keep snapshots, diff extracted facts, and triage by relevance, proximity and impact. Language models can summarise, but only behind quote verification and human review. Measure misses, not just throughput.