Convert legislation into evaluatable rules using an LLM extraction pipeline. Runs conditions as JSON Logic against a typed fact schema, with provenance grounding back to the source article.
Status: v0.4.0 - All 5 domains (EU AI Act, NDB, Privacy APPs, SSA bereavement, SIS death benefits) extracted, reviewed, and live in the Vue sandbox UI, with an AKN source toggle showing the real lex-au corpus XML behind each AKN-domain rule.
- Depends on (AKN domains only): lex-au (AKN 3.0 XML corpus; HTML domains like EU AI Act and NDB ingest directly from source HTML instead, no lex-au dependency)
Full stack map: lex-au-search's STACK.md and lex-au's FUTURE.md.
- v0.4.0 - Privacy APPs, SSA bereavement, SIS death benefits extraction complete (165 rules); all 5 domains wired into the Vue sandbox UI; AKN source toggle (raw corpus XML per rule) for AKN-sourced domains.
- v0.3.0 - AKN XML ingest path; Privacy APPs, SSA bereavement, SIS death benefits schemas (extraction deferred).
- v0.2.0 - AKN vs plain-text comparison mode, validated against NDB s.26WA branching.
- v0.1.0 - EU AI Act and NDB scheme extraction, JSON Logic engine, provenance grounding, FastAPI, Vue 3 sandbox.
| Domain | Source | Rules | Scenarios |
|---|---|---|---|
| EU AI Act | Regulation (EU) 2024/1689, Arts 5, 6, Annex I, III | 32 | 5 |
| NDB scheme | Privacy Act 1988 (Cth), ss.26WA-26WR | 54 | 4 |
| Privacy APPs | Privacy Act 1988 (Cth), Schedule 1 (APPs 1-13) | 73 | - |
| SSA bereavement | Social Security Act 1991 (Cth), bereavement provisions | 45 | - |
| SIS death benefits | Superannuation Industry (Supervision) Act 1993 (Cth), ss.55A-55C, 68AA-68AAF | 47 | - |
legislation source
│
├── HTML (EUR-Lex, legislation.gov.au EPUB)
│ ingest.py → chunk_by_article
│
└── AKN XML (lex-au corpus)
ingest.py → chunk_by_section_akn / chunk_by_schedule_clause
│
extract.py Claude claude-opus-4-8 → Rule[] (JSON Logic conditions)
│
provenance.py verify each docref against source chunks
│
rules/{domain}.json + rules/{domain}.REVIEW.md
│
api.py FastAPI GET /domains/{d}/rules POST /domains/{d}/evaluate
│
web/ Vue 3 sandbox UI (fact form → matched rules)
AKN XML source files are read from the corpus/xml/ directory (gitignored). Fresh clones need no lex-au checkout — download the pre-built XML from Hugging Face straight into corpus/:
pip install huggingface_hub
python -c "from huggingface_hub import snapshot_download; snapshot_download(repo_id='cchew/lex-au', repo_type='dataset', local_dir='corpus', allow_patterns='xml/*')"If you're building lex-au from source alongside this repo instead, point at its corpus directly: python -m src.pipeline --domain privacy-apps --corpus-dir ../../lex-au/repo/corpus/xml.
The API server (src/api.py) reads from the same default corpus/xml/ — set CLAUSEKIT_CORPUS_DIR to override it (e.g. CLAUSEKIT_CORPUS_DIR=../../lex-au/repo/corpus/xml uvicorn src.api:app --reload) if you built lex-au from source instead of downloading the corpus into this repo.
Python 3.12+ required.
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
cp .env.example .env
# add ANTHROPIC_API_KEY to .envjson-logic patch: The json-logic 0.6.2 package ships Python 2 code (dict.keys()[0]). After creating the venv, patch line ~21 of .venv/lib/python3.x/site-packages/json_logic/__init__.py:
# before
op = data.keys()[0]
# after
op = list(data.keys())[0]If you recreate the venv, re-apply this patch. The engine will fail at runtime without it.
Pre-built rule sets are committed to rules/. Re-run extraction only if you change the source or prompt.
Before running a new or changed domain, validate the fact schema against the source text first — cheap (haiku), no rules written, catches schema-vs-source mismatches before spending on opus extraction:
python -m src.pipeline --domain privacy-apps --validate-schemaThis was added after a 2026-07-17 extraction pass found 3 schema-vs-source mismatches only after the fact (a missing agency/organisation variable, missing payday-timing/second-death variables, and a missing allowlist section) — all of which this check would have caught for a few cents instead of a full extraction run.
Expect high noise, filter by hand. The check runs per-chunk and can't tell a real structural gap (a binary distinction that recurs across sections and silently breaks a "high"-rated rule, like the agency/organisation miss above) from a vague standard that's correctly meant to fall through to codifiability: low by design ("reasonable in the circumstances", "frivolous or vexatious", etc — see Limitations below). It will flag both at equal volume. Scan the output for facts that repeat across multiple sections or gate a binary either/or outcome; ignore one-off subjective-standard flags.
HTML domains (source file required):
python -m src.pipeline --domain eu-ai-act --source data/eu-ai-act.html
python -m src.pipeline --domain ndb --source data/ndb.htmlSource HTML files are gitignored. Download them separately:
- EU AI Act: EUR-Lex
- NDB scheme: legislation.gov.au EPUB HTML (Privacy Act 1988, series C2004A03712)
AKN domains (reads from lex-au corpus, no --source needed):
python -m src.pipeline --domain privacy-apps
python -m src.pipeline --domain ssa-bereavement
python -m src.pipeline --domain sis-death-benefits.venv/bin/pytest # 76 tests
.venv/bin/pytest -v # verboseEach rule in rules/{domain}.json:
{
"rule_id": "eu-ai-act-001",
"label": "Prohibited: Real-time remote biometric identification in public spaces",
"condition": {"and": [{"var": "real_time_biometric"}, {"var": "publicly_accessible_spaces"}]},
"obligation": "Prohibited unless Article 5(1)(a) exceptions apply",
"scope": "Deployers of AI systems",
"exceptions": ["Law enforcement with prior judicial authorisation"],
"codifiability": "high",
"docref": {
"source_doc": "EU AI Act",
"article": "Article 5",
"section": "5(1)(a)",
"url": "https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32024R1689",
"provision_uri": "http://data.europa.eu/eli/reg/2024/1689/art_5/par_1"
}
}condition is null for codifiability: "low" rules (vague standard, not mechanically evaluable). provision_uri uses ELI for EU legislation and AKN FRBR for Australian legislation.
JSON Logic expressiveness ceiling. JSON Logic (==, in, and, or, var) cannot represent:
- Temporal constraints (obligations with deadlines or sequencing)
- Modal operators (obligatory / permitted / prohibited)
- Defeasibility (rule A applies unless rule B overrides it)
- Role-relative obligations (same provision applying differently to providers vs deployers)
Provisions requiring these constructs are classified low codifiability and return matched: null.
Fact schema bounds expressiveness. The fact schema is defined before extraction runs. The LLM can only produce conditions over the variables in the schema - it cannot discover new variables. Rules whose trigger conditions fall outside the schema are classified low regardless of their legislative clarity.
sis-death-benefits schema is broader than what's extracted. SisDeathBenefitsFacts includes member_status and death_benefit_nomination, but no rule in this domain currently uses either. s.59 (added 2026-07-17) grants a trustee's permission to accept a binding nomination — it doesn't determine payment outcomes based on an existing nomination's status, which is what those two fields describe. That determination logic may not exist in this Act's provisions at all within the current allowlist; same honest-gap pattern as NDB's all-null rules, not a bug.
Codifiability is LLM-assigned, not human-validated. Classifications have not been independently verified against the source legislation by domain experts.
Not a compliance tool. ClauseKit is a demonstration of a pipeline, not legal advice. matched: true means the rule's coded condition fired - it does not mean you have a legal obligation. Consult a lawyer.