{
  "markdown": "# Lex\n\n[![CI](https://img.shields.io/github/actions/workflow/status/SFHAJJI/lex/ci.yml?branch=main&label=tests&style=flat-square)](https://github.com/SFHAJJI/lex/actions/workflows/ci.yml)\n[![License](https://img.shields.io/badge/license-Apache--2.0-blue?style=flat-square)](LICENSE)\n[![Live](https://img.shields.io/badge/live-law.soufien.lu-e0705f?style=flat-square)](https://law.soufien.lu)\n[![MCP](https://img.shields.io/badge/MCP-read--only%20tools-6f42c1?style=flat-square)](https://law.soufien.lu/developers)\n[![Coverage](https://img.shields.io/badge/corpus-live%20coverage-brightgreen?style=flat-square)](https://law.soufien.lu/coverage)\n\n**Point-in-time retrieval of regulatory text.** Regulators publish the current\nrule; every audit, investigation and dispute is about a **past date**. Lex keeps\nevery version it has seen and answers *\"what did this say on 15 March 2022?\"*\nwith the exact validity interval, the timeline, the instrument that changed it,\nand a hashed provenance record, and an honest, machine-readable refusal when it\ncannot know.\n\n**[Live demo](https://law.soufien.lu)** ·\n**[Search held law](https://law.soufien.lu)** ·\n**[MCP endpoint](https://law.soufien.lu/mcp)** ·\n**[Dataset (CC-BY)](https://github.com/SFHAJJI/lex-articles)** ·\n**[Examples](https://github.com/SFHAJJI/lex-articles/tree/main/examples)** ·\n**[Architecture dossier](https://law.soufien.lu/built)** ·\n**[Benchmarks](https://law.soufien.lu/benchmarks)** ·\n**[Verify it yourself](https://law.soufien.lu/verify)** ·\n**[Program](docs/hybrid-eu-roadmap.md)** ·\n**[Retrieval + agent plan](docs/retrieval-agent-enrichment-plan.md)** ·\n**[Spec (D1-D82)](docs/lex-spec-v4.md)** ·\n**[Corpus revalidation](docs/corpus-revalidation.md)** ·\n**[Snapshot retention](docs/snapshot-retention.md)**\n\n## 74-second engineering demo\n\n[![Lex live temporal search, hybrid retrieval, comparison and deployment evidence](https://github.com/SFHAJJI/lex/releases/download/v1.2.1/lex-interviewer-preview-v2.gif)](https://github.com/SFHAJJI/lex/releases/download/v1.2.1/law-soufien-interviewer-demo-v2.mp4)\n\n*Dated retrieval → deterministic keyword or optional local hybrid search → exact\nEU article → verified temporal diff → evidence export → deployed architecture.*\n[Watch the narrated, continuous-browser MP4](https://github.com/SFHAJJI/lex/releases/download/v1.2.1/law-soufien-interviewer-demo-v2.mp4)\nor read the [release evidence](https://github.com/SFHAJJI/lex/releases/tag/v1.2.1).\n\n## Try it in 30 seconds\n\nGive any MCP-capable AI the full toolset, no key, no install:\n\n```\nclaude mcp add --transport http lex https://law.soufien.lu/mcp\n```\n\nModern clients such as VS Code and Cursor connect to the hosted endpoint directly:\n\n```json\n{ \"servers\": { \"lex\": { \"type\": \"http\", \"url\": \"https://law.soufien.lu/mcp\" } } }\n```\n\nFor a client that only accepts local stdio servers, bridge to the same hosted\nendpoint with a pinned version of the third-party `mcp-remote` adapter (Node.js 18+):\n\n```\nnpx -y mcp-remote@0.1.38 https://law.soufien.lu/mcp\n```\n\nThe hosted endpoint is canonical: no legal corpus, vector files or Azure\ncredentials are downloaded to the client. Lex also publishes its remote-server\nmetadata to the official MCP Registry from GitHub Actions using OIDC. Lex\nintentionally does not publish an npm package: the command above is a\ncompatibility bridge for older clients, not a second implementation.\n\nThe following is a historical V2 example. The public assistant is temporarily\nunavailable while Lex installs its deterministic V3 answer path; search, direct\nlaw pages and MCP remain available. Historical answer, verbatim:\n\n> **Q: What did CRR Article 92 require as capital ratios on 1 March 2020, and has that text changed since?**\n>\n> Quoted verbatim (Article 92(1)) from the CRR version in force on that date:\n> *\"Subject to Articles 93 and 94, institutions shall at all times satisfy the\n> following own funds requirements: (a) a Common Equity Tier 1 capital ratio of\n> 4,5 %; (b) a Tier 1 capital ratio of 6 %; (c) a total capital ratio of 8 %.\"*\n>, `eu-eurlex:32013r0575:2019-12-25` (valid 2019-12-25 → 2020-06-26),\n> [permalink](https://law.soufien.lu/eu-eurlex/32013r0575/2019-12-25#art_92).\n>\n> Article 92 has had **seven distinct texts**: 2013-06-26 → 2013-06-27,\n> 2013-06-28 → 2019-12-24, 2019-12-25 → 2020-06-26, 2020-06-27 → 2021-06-28,\n> 2021-06-29 → 2022-12-31, 2023-01-01 → 2024-12-31 and 2025-01-01 onward, each\n> with its own permalink and sha256.\n\nEvery claim in that historical answer came from a deterministic tool call (the\ntrace was shown under each reply); the model did not answer from its own memory.\nDo not take this file's word for it, the numbers above are checkable in one call,\nand if they ever drift from the live system, that is a bug worth reporting:\n\n```bash\ncurl -s -X POST https://law.soufien.lu/mcp -H 'Content-Type: application/json' \\\n  -H 'Accept: application/json, text/event-stream' \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\"name\":\"article_history\",\n       \"arguments\":{\"work\":\"eu-eurlex:32013r0575\",\"anchor\":\"art_92\"}}}'\n```\n\n## Who uses this\n\n- **A compliance officer** checking which text of an obligation was in force on\n  the date of the facts, with a permalink and a hash for the file.\n- **A legal-tech developer** building RAG over law that must not hallucinate\n  versions: per-article chunks with `valid_from`/`valid_to` to filter *before*\n  similarity ([dataset](https://github.com/SFHAJJI/lex-articles)).\n- **An external AI agent** using the public MCP tools directly. The site's own\n  assistant is unavailable during the V3 answer-path replacement.\n- **A researcher** tracking how one article's text evolved across amendments\n  (`article_history`: every distinct text state, dated).\n\n## What it never does\n\nLex answers *what the rule was*. It does not decide \"were we compliant?\", \"does\nthis apply to me?\", or \"what does this mean?\", those are professional opinions.\nThe evidence, index and MCP layers never generate or interpret legal text\n(fitness rule F10). The reviewed V3 assistant design may later explain retrieved\nevidence, but that path is currently contained and is not part of the record.\n\n## Architecture (one screen)\n\n```\nAPPS        Lex.Ingest (CLI)   Lex.Mcp.Stdio (local host)   Lex.Web (site + HTTP MCP)   Lex.Ask (V3 replacement, contained)\nPROTOCOL    Lex.Mcp (legal tools + official MCP SDK bridge; transport-neutral library)\nDERIVED     Lex.Derive, evidence -> provision Markdown+JSON (immutable profiles include akn-lu/1, akn-lu/2, akn-lu-identical-scl-duplicate/1, akn-lu-document/1, pdf-memorial-lu/2, fmx4-eu/1, xhtml-eu/1)\nADAPTERS    Lex.Sources.Legilux (Tier A, SPARQL)   Lex.Sources.EurLex (Tier A, Cellar + Formex)\nMODEL       Lex.Law, Publisher, Work, Version, Expression, Observation. No publisher names.\nFOUNDATION  Lex.Temporal (interval algebra)   Lex.Index (SQLite: filter-first, verified artifacts)\n```\n\n- **One corpus repo per publisher**; the corpus is human-readable JSON + git. The tree carries the legislative history, `git log` carries the ingest history, and the two are deliberately not the same ([why](https://law.soufien.lu/decisions)).\n- **Bitemporal**: valid time is the publisher's; transaction time is ours, as\n  append-only observation chains inside hashed content. Nothing is ever\n  overwritten; publisher corrections become visible events.\n- **Two layers**: verbatim publisher bytes (evidence) → deterministic\n  per-article extraction (consumption). Every derived article hash-chains to\n  the exact bytes the state published; `lex verify derive` re-derives and\n  byte-compares.\n- **Legal and temporal eligibility before fusion and final ranking**, enforced by\n  a non-optional `FilterSet` on the only query entry point. Hybrid may use a\n  bounded binary-vector preselection for speed, but an ineligible candidate can\n  never enter the fused result set.\n- **Signed whole-artifact manifests** (ECDSA P-256): a trust root pinned in the\n  application verifies indexes, vectors, embedding assets, scope, benchmark\n  and source commits before any file is mounted. The embedded index stamp\n  remains public provenance, not its own trust root.\n- **Release-gated assistant behavior**: 25 frozen scenarios validate the typed\n  plan, arguments, outcomes, UI effects, answer and latency against an immutable\n  zero-traffic candidate. The catalog records author `Lex release engineering`;\n  Evaluation reviewer Soufien Hajji uses a separate signing authority. This\n  project-owner review is not a third-party audit. Candidate and release-grader tokens are budgeted\n  separately. The current maximum reservation is EUR 0.5362647 under an outer\n  EUR 10 preflight and measured-use ceiling, not a live billing cutoff. The\n  [release dossier](https://law.soufien.lu/built/release) shows the CI/CD flow and\n  [evaluation mechanics](docs/assistant-evaluation.md).\n- **Honest refusals**: `no_version_for_date`, `anchor_not_in_version`,\n  `outside_observed_window`, `text_not_available`, `text_withheld`, a flagged wrong answer is still\n  a wrong answer, so Lex refuses instead.\n\n## Current coverage\n\n**Luxembourg** (Legilux, Tier A): every work and dated version currently mounted\nfrom the publisher's `Consolidation` catalogue. Nothing in that collection is\nfiltered out by legal form. Counts, dates, corpus commit and extraction-profile\nmix are read from the index on the\n[live coverage page](https://law.soufien.lu/coverage), rather than copied into\nproduct prose that becomes stale after the next publisher run.\n\nThe consolidation catalogue is not all Luxembourg law. The same official\nendpoint exposes 150,187 resources classified as `Act`, including laws,\ngrand-ducal regulations, ministerial regulations and orders that may never have\nreceived a consolidation record. That broad number also contains notices and\nother material that should not all enter lawyer-facing search. The measured\nboundary and the proposed normative-act increment are documented in\n[Luxembourg scope](docs/luxembourg-scope.md).\n\nWhere official XML exists, text is retained as verbatim Akoma Ntoso. The\ndeterministic `pdf-lu/1` fallback handles eligible born-digital consolidated\nPDFs and records that article boundaries came from typography rather than\npublisher markup. Narrow `pdf-memorial-lu/2` recovery first verifies the requested\nact inside an official-gazette issue, then exposes only a strongly identified\nsection and visibly labels its inferred boundaries in the reader. Thematic\nfolders, unverified gazette matches and fileless records remain metadata-only;\nLex does not trade provenance for a larger text count. Exact text availability\nand extraction-profile mix are reported from the mounted artifact on the\ncoverage page.\n\n**EU** (EUR-Lex/Cellar, Tier A): a reviewed Luxembourg-facing scope spanning\nfinancial services, AML, corporate, competition, tax, employment, consumer,\nprocurement, environmental, judicial-cooperation, intellectual-property, data,\ndigital, cyber and energy law, plus bounded legal-history relationships. The mounted index and\n[live coverage page](https://law.soufien.lu/coverage) are the source of truth for\nwork and version counts. Full text comes from the Publications Office's\n**Formex 4** structural XML where served, including large consolidations the\nXHTML channel cannot carry. The present EU limit is scope, not format.\n\nThe derived dataset publishes its current counts and source commits in its\n[release catalog](https://github.com/SFHAJJI/lex-articles/blob/main/catalog.json).\nThe broader Luxembourg original-act catalogue and approved EU scope are tracked\nby the [temporal expansion program](docs/hybrid-eu-roadmap.md). Communal regulations are deliberately out of scope: 17,232\nexist as published acts, none is ever consolidated, so there is no point-in-time\nhistory to hold. The fallback ladder for XML-less versions is spec D49.\n\n## Run it\n\n```\nLEX_CODE_COMMIT=$(git rev-parse HEAD)\nLEX_ARTICLES_COMMIT=$(git -C ../lex-articles rev-parse HEAD)\nLEX_LU_CORPUS_COMMIT=$(git -C ../lex-corpus-lu-legilux rev-parse HEAD)\nLEX_EU_CORPUS_COMMIT=$(git -C ../lex-corpus-eu-eurlex rev-parse HEAD)\n# One exact completed-enumeration identity. Reuse it only when retrying that same run.\nLEX_INGEST_RUN_ID=manual-example-001\n\n# ingest (paced, sequential; official open-data channels only)\ndotnet run --project src/Lex.Ingest -- ingest --publisher lu-legilux \\\n    --corpus ../lex-corpus-lu-legilux --code-commit \"$LEX_CODE_COMMIT\" \\\n    --run-id \"$LEX_INGEST_RUN_ID\"\n\n# derive the per-article layer, build the signed index\ndotnet run --project src/Lex.Ingest -- derive --publisher lu-legilux --corpus ../lex-corpus-lu-legilux --out ../lex-articles\ndotnet run --project src/Lex.Ingest -- index --corpus ../lex-corpus-lu-legilux --articles ../lex-articles \\\n    --out indexes/index-lu-legilux.db --keyfile signing-key.pem \\\n    --capability-policy deploy/capability-policy.json \\\n    --code-commit \"$LEX_CODE_COMMIT\" --articles-commit \"$LEX_ARTICLES_COMMIT\" \\\n    --corpus-commit \"$LEX_LU_CORPUS_COMMIT\"\n\n# resumable large semantic backfill on a reviewed Windows DirectML adapter\ndotnet build src/Lex.Ingest -c Release -p:UseDirectML=true\nsrc/Lex.Ingest/bin/Release/net10.0/Lex.Ingest index \\\n    --corpus ../lex-corpus-eu-eurlex --articles ../lex-articles \\\n    --out indexes/index-eu-eurlex.db --embedding-model model \\\n    --vectors indexes/index-eu-eurlex.vectors \\\n    --embedding-directml-device 1 --embedding-batch-size 256 \\\n    --embedding-max-batch-tokens 32768 \\\n    --embedding-cache build-cache/eu-eurlex-embeddings.db \\\n    --capability-policy deploy/capability-policy.json \\\n    --code-commit \"$LEX_CODE_COMMIT\" --articles-commit \"$LEX_ARTICLES_COMMIT\" \\\n    --corpus-commit \"$LEX_EU_CORPUS_COMMIT\"\n\n# The chunker fixes legal-text boundaries before the GPU groups immutable chunks\n# into 32/64/128/256/512-token inference buckets. A fixed padded-token budget reduces\n# the item count for long buckets so one reviewed batch size cannot exhaust the GPU.\n# Masked padding is never stored.\n\n# web demo + MCP (stdio) locally\nLEX_INDEX_DIR=indexes dotnet run --project src/Lex.Web\nLEX_INDEX_DIR=indexes dotnet run --project src/Lex.Mcp.Stdio\n```\n\n`Lex.Mcp` contains the legal tools and official SDK bridge, not a deployment entry point.\nThe standalone stdio executable is isolated in `Lex.Mcp.Stdio`; production composes the same\nlibrary into `Lex.Web` for Streamable HTTP. Co-hosting is deliberate while site and MCP traffic\nshare one immutable index set and one scale/SLA boundary. D67 records the measured triggers for\nextracting an independently deployed MCP service rather than adding a second runtime for optics.\n\nThe generated key above is for local development only. Production publication\nuses GitHub OIDC to ask the non-exportable Azure Key Vault key to sign the\nwhole-artifact manifest, then deploys a zero-traffic candidate revision.\n\n## MCP tools\n\n`as_of` (full / outline / per-article select) · `timeline` · `in_force_on` ·\n`diff` · `search` · `article_history` · `provenance` · `coverage` ·\n`cited_by` · `changes_in_period`.\n\nThe same read-only tools cover Luxembourg and EU material. Search spans every\nmounted publisher by default and can filter jurisdiction, date, hierarchy,\nlegal form, binding status, domain and language. Keyword retrieval is\ndeterministic FTS5/BM25. Hybrid adds the pinned local encoder and fixed rank\nfusion when verified vectors are mounted; no generative model participates in\nretrieval. A unique segment of an official publisher short title such as `RGPD`,\n`GDPR`, `DORA`, or `AI Act` resolves deterministically; collisions require\nclarification. Official publisher subjects, EuroVoc relations, and directory\ncoordinates support weak discovery but never become legal-text evidence or work\nidentity. No manually curated legal aliases are loaded. Model-derived weak discovery is\nnot active, and keyword remains the production default because the signed hybrid\nholdout gate has not passed. The reviewed target V3 assistant uses the same tools,\nclaim-typed composition and a separately gated grounding step. It is not active until\nthe complete answer-dossier path passes review and promotion. Application code retains\nwork resolution, tool authorization, citation and gap authority. `coverage` exists to say what Lex does **not** have, because a system\nthat cannot state its own gaps cannot be trusted with a completeness question.\n\n## Contributing\n\nIssues and PRs welcome, the highest-leverage areas:\n\n- **A new publisher adapter** (`ISourceAdapter`, ~200 lines): any jurisdiction\n  with an official machine-readable channel. The seam is publisher-pure by\n  fitness test; adapters never touch files or git.\n- **Assistant release cases** ([evals/assistant-cases-v3.json](evals/assistant-cases-v3.json)):\n  frozen typed-operation judgments, digest-attested by a project-owner reviewer identity distinct\n  from the catalog author and run with the strict\n  [release evaluator](docs/assistant-evaluation.md). The gate has no keyword or grader fallback;\n  cases specify the exact operation contract or refusal boundary expected from a natural question.\n- **Extraction improvements**: profiles are immutable; improvements ship as a\n  *new* profile beside the old (see `fmx4-eu/1` beside `xhtml-eu/1`).\n\nContributions are accepted under the Developer Certificate of Origin\n(`git commit -s`).\n\n## Licence\n\nCode: **Apache-2.0** ([LICENSE](LICENSE)). The code licence does **not** extend\nto corpus data or index artefacts, see each corpus repository's `NOTICE`\n(three layers: official acts outside copyright / Lex's compilation rights /\ncode licence inapplicable). Derived dataset: CC-BY-4.0 (LU) and EU\nreuse-with-attribution, licence inline in every file.\n\n## Support\n\nThis is free and open, and it stays that way whatever you decide. It is also not free to run:\nthe live site, the nightly jobs and the storage sit on Azure infrastructure I pay for out of\npocket, and I maintain it on my own time.\n\nIf it saved you an afternoon, you can [buy me a coffee ☕](https://buymeacoffee.com/shajji)\nand put it towards the hosting bill. Starring the repo helps just as much, and costs nothing.\n",
  "bytes": 18140,
  "sha": "c1ae10add1ea89aa47433d29ac33f57328f4a3bea5be1138edf2a787ee2756ae",
  "repo_slug": "sfhajji/lex",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_sfhajji_lex_08a5a379/readme"
}