Inspiration

An international student's life in the UK is a chain of dependencies, and almost nobody can see the whole chain. One date moves and, weeks later, three unrelated things break. A course end date slipping to February can quietly cost the lawful in-UK window to apply for the Graduate route. A right-to-rent share code has to be a different type from the right-to-work one, and finding that out at a letting agency is too late.

Two things pushed me from "this is annoying" to "this is buildable". First, the pain is real and specific: I collected 37 first-person accounts of students hitting these exact cascades, most of them on the Graduate-route timing. Second, the domain churns. When I checked the rules against a standard AI assistant, it stated six facts confidently and got them wrong: it talked about BRP cards that have been replaced by the eVisa, it cited a gov.uk URL that does not exist, and it had the Immigration Skills Charge, the maintenance figure, and the ATAS country count all out of date. A static tool rots the moment the Home Office changes a rule. That churn is the reason to build this on a graph that carries provenance, not on a model's memory.

What it does

Marg models the rules and one student's case as a lineage graph in DataHub, then answers the question nobody knows to ask: what breaks downstream when this changes?

Change one fact, and the agent walks the graph through DataHub's MCP server, finds every downstream rule that fact touches, and reports each with a verified primary-law citation. Where it should not speak, it says so. A settlement change that is still only a proposal gets refused ("not law yet"). A rule whose source page has drifted degrades to "verify at source". A case-specific determination that only a regulated adviser can legally make gets routed to one, not guessed. Then a human approves, and the agent writes its finding back into DataHub as governed metadata, closing the loop.

It is not a chatbot. A chatbot waits for a question, and the whole problem is that people do not know what to ask.

How I used DataHub

DataHub is not a swappable store here. It is the mechanism. Take it out and the product stops existing.

Every rule and every case fact is a DataHub dataset node, so the dependency edges are native DataHub lineage. The cascade is a lineage traversal: the agent calls the MCP server's get_lineage tool and walks downstream from the changed fact. Provenance rides on each node as custom properties (source_url, status, confidence_tier, effective_date, content_hash, checked_at), which is what lets the agent decide whether it is allowed to assert a rule. The governed write-back goes back through the MCP server too, with update_description to record the finding and add_tags to flag the affected nodes, behind a human-approve gate because DataHub's own Approval Workflows are Cloud-only. The same cascade the agent reports shows up natively in DataHub's Impact Analysis UI, because it is the same lineage.

Required components used: DataHub OSS plus the DataHub MCP Server, all self-hosted, no Cloud.

How I built it

The decision path is deliberately deterministic. A Python classifier reads each node's status and confidence_tier and returns one of four verdicts: assert, confirm-with-adviser, verify-at-source, or refuse. No language model decides what counts as law, because a model that hallucinates a legal rule is worse than useless in this domain. There is an optional Claude narration layer that rephrases findings the classifier has already decided, and it is off unless an API key is set. It can never change a verdict.

Two pieces make the "self-updating" claim real rather than a slide. A citation check fetches every cited URL and confirms it resolves to its own path, not a redirect or a 404, so a dead link can never ship as a warning. A freshness sweep hashes the visible text of each enacted rule's source page and stores the fingerprint on the node; if the text later changes, the rule flips to stale and the agent stops asserting it. Hashing visible text rather than raw HTML matters, because raw HTML changes on every request (session tokens, timestamps) and false-flagged every page the first time I tried it.

Stack: Python 3.11, DataHub OSS, the DataHub MCP Server, the DataHub Python SDK for ingestion, Streamlit for the UI, and Claude for optional narration. Managed with uv, gated by ruff and black, covered by pytest.

Challenges I ran into

The hardest part was knowing what not to say. Unregulated individualised immigration advice is a criminal offence in the UK (Immigration and Asylum Act 1999, s84 and s91), so a tool that confidently answers a case-specific question is not just wrong, it is illegal. I learned this the concrete way: on one interpretation-tier clause, my own multi-vote verification returned three-to-zero one run and one-to-two the next, on the identical text. If I cannot get a stable answer, the machine has no business asserting one. That is exactly why interpretation-tier rules are quoted and routed to a regulated adviser rather than answered.

The curtailment rule was a smaller version of the same lesson. University welfare pages state a firm "your visa is cut to 4 months" or "60 days". The actual Immigration Rule (Part 9, SUI 32.1) says permission "may be cancelled", a discretionary power, not an automatic countdown. So Marg cites the rule and refuses to promise the outcome.

And the dead URL. During research a fetch silently followed a redirect and a gov.uk page that does not exist earned a "verified" stamp. That single bug became a feature: the citation check now exists so it can never happen in a shipped warning.

Accomplishments I'm proud of

The safety invariant holds at zero across the eval: Marg never once states a proposal, a stale rule, or a case-specific interpretation as settled law. Every asserted citation resolves live. And the freshness loop genuinely closes: I can break a source's seal, run the sweep, watch the rule flip to stale, and watch the agent stop asserting it, then restore it and watch it come back. That is the difference between a governance graph that describes the world and one that keeps itself honest about it.

What I learned

DataHub's lineage generalises far past data tables. A dependency graph with provenance and governance is the right shape for any bureaucratic chain of rules, and immigration law is one nobody had pointed it at. I also learned that in a regulated domain the interesting engineering is in the refusals, not the answers, and that provenance is a product feature rather than plumbing.

What's next

Broadening the rule coverage is data entry against the existing schema, not a rebuild. Beyond that: a second entry that gives the agent a persistent memory in CockroachDB off the same core, packaging the rules-as-datasets ingestion as a reusable open-source contribution back to DataHub, and replacing my hand-labelled ground truth with one reviewed by an IAA-regulated adviser.

Built With

Share this project:

Updates