Skip to content

Quickstart

In about ten minutes you will have a cryptographically verified release of a regulatory corpus running on your machine, answering a legal question with citations — and you will have watched it refuse a tampered copy of the same release, which is the part worth trusting.

Everything after the install runs offline. That is not a convenience: an artifact you can only check by asking a server whether it is genuine is an artifact you cannot check.

You need git and uv, which manages the Python 3.12 toolchain the engine is built on. Everything else the commands below install for you.

Terminal window
git --version
uv --version
Terminal window
git clone https://github.com/openregs/openregs.git
cd openregs
uv sync
export PATH="$PWD/.venv/bin:$PATH"
openregs --version
openregs 0.1.0
schema majors: 1
commit: b053990a6e17cc9e7ac291e33ce379eee1103fd4

The commit is not decoration. A release is a function of the commit that built it, and the CLI prints which one it is so that any artifact it produces can be traced back to the exact source that produced it.

openregs pull copies a release out of a registry and verifies the whole chain over the copied bytes before any of them reach the target directory. A release that fails a check is never unpacked.

Terminal window
openregs pull fixreg@2025.04 --from fixtures/registry --into ~/openregs-releases
pull: fixreg@2025.04 from fixtures/registry/fixreg/2025.04
staged 21 file(s), 246770 byte(s) -> ~/openregs-releases/.pull-fixreg-2025.04.partial
verify: fixreg@2025.04 — ~/openregs-releases/.pull-fixreg-2025.04.partial
signature valid 8 blob signature(s) under ecdsa-p256-sha256 key 24f0b959505940ce
trust anchored signing key openregs-fixture (24f0b959505940ce) and log key
openregs-local-log (02801639ad00ebed) are in config/trust.yaml
digests valid 5 artifact digest(s) and 8 signed blob(s) recomputed from the bytes on disk
provenance complete 2025.04 -> 0f466185ac19 -> run fixture-registry-1 -> 3/3 snapshot(s),
every hash the corpus cites is in the attested SOURCES.lock
log inclusion proven 8 entries proven in openregs.io/fixreg at tree size 8, root f94e81357156a4ff
verified 5 of 5 check(s) passed
unpack -> ~/openregs-releases/fixreg/2025.04
artifacts corpus.sqlite, graph.idx, embeddings.parquet, constants-src, 2025.04.diff.json
pull: OK — fixreg@2025.04 verified, then unpacked into ~/openregs-releases/fixreg/2025.04

Five independent checks, and each one answers a different question:

CheckThe question it answers
signaturewere these exact bytes signed?
trustby a key this checkout was told to accept, rather than one the release brought along?
digestsdoes every artifact match the digest its own manifest declares?
provenancedoes the tag lead to a commit, to a CI run, to the source snapshots the corpus cites?
log inclusionis each signature in the transparency log — proven against the checkpoint bundled in the release, so no network is needed?

Long paths above are shortened for reading; the real output prints them in full.

The repository also ships the same release with one byte of corpus.sqlite changed. Nothing else about it is different — same layout, same manifest, same attestation directory.

Terminal window
openregs pull fixreg@2025.04 --from fixtures/registry-tampered --into /tmp/openregs-tampered
echo "exit: $?"
FAIL corpus.sqlite: signature does not verify under key 24f0b959505940ce — the bytes, the
signature or the key is not the one this release was signed with
FAIL corpus.sqlite: sha256 is 81ded414141632a7268bbb57a87bde59f3deb97144b52683aabab36c16e3c0a3,
release-meta.yaml says 76032f7f0f665de313ed0c261bb5bdfdc48359e03a8c1c58cbba615f89912758
FAIL corpus.sqlite: sha256 is 81ded414141632a7268bbb57a87bde59f3deb97144b52683aabab36c16e3c0a3,
signature-manifest.yaml says 76032f7f0f665de313ed0c261bb5bdfdc48359e03a8c1c58cbba615f89912758
pull: FAIL — fixreg@2025.04: 3 problem(s); nothing was unpacked and
/tmp/openregs-tampered/fixreg/2025.04 was not written
exit: 1

Exit code 1, three named problems, and — the part that matters — /tmp/openregs-tampered/ is empty. The staging directory was removed and the target was left exactly as it was. Verification that runs after unpacking is verification you have already lost.

Terminal window
openregs serve \
--release fixreg@2025.04 \
--path ~/openregs-releases/fixreg/2025.04 \
--port 8787
serve: bound http://127.0.0.1:8787 — GET /healthz answers now; GET /readyz is 503 until a
verified release is loaded
serve: verify fixreg@2025.04 — ~/openregs-releases/fixreg/2025.04
signature valid 8 blob signature(s) under ecdsa-p256-sha256 key 24f0b959505940ce
...
serve: verified — 5 of 5 check(s) passed before the listener opened
serve: fixreg@2025.04 (commit 0f466185ac19) loaded, 55 units
serve: listening on http://127.0.0.1:8787
serve: POST /v1/answer, POST /v1/unit, GET /readyz, GET /healthz, GET /metrics

The release is verified again, from scratch, before the socket serves anything. /healthz answers from boot so a load balancer can see the process; /readyz stays 503 until a release has passed all five checks.

In another terminal:

Terminal window
curl -s -X POST http://127.0.0.1:8787/v1/answer \
-H 'content-type: application/json' \
-d '{"query":"When must an operator report an incident?","as_of":"2025-06-01"}' \
| python3 -c "
import json, sys
answer = json.load(sys.stdin)
print(answer['verification'], answer['release'], answer['as_of'])
for block in answer['blocks'][:3]:
text = ' '.join(block['text'].split())
print(' ', block['citation']['eid'].ljust(16), text[:88])
"
verified fixreg@2025.04 2025-06-01
art_5__para_3 Operators must report to the authority any incident causing losses above EUR 10000.
art_5 This Article applies to operators providing one or more critical services. Operators mus
art_2__point_2 'incident' means an event that compromises the availability, integrity or confidentialit

Three things came back that a search engine would not have given you:

  • The provision that answers the question, art_5__para_3, with the release tag and the date it was answered for stamped on the citation.
  • The article it sits in, so the answer is not a sentence torn out of its context.
  • The definition of “incident” from Article 2, because the answer uses the word and the definition was in force on that date.

Drop the python3 filter to see the whole response: the retrieval closure, the obligation atoms, whatever amends or disapplies each unit, and the disclaimer the server carries on every answer. The full response also reports applicability honestly:

FIXREG-Art5.2-Ob1 -> art_9 | retrieved: False
FIXREG-Art5.3-Ob1 -> art_5__para_3 | retrieved: True

The first line is the engine volunteering that Article 9 disapplies the register-keeping duty for small operators — a duty that did not match the query, reported anyway because whether it binds you is a question no retrieval score can answer. Name an entity profile in the request and it decides instead of reporting.

Terminal window
for date in 2025-01-15 2025-06-01; do
curl -s -X POST http://127.0.0.1:8787/v1/answer \
-H 'content-type: application/json' \
-d "{\"query\":\"incident reporting threshold\",\"as_of\":\"$date\"}" \
| python3 -c "
import json, sys
answer = json.load(sys.stdin)
text = next(b['text'] for b in answer['blocks'] if b['citation']['eid'] == 'art_5__para_3')
print(answer['as_of'], ' ', text)
"
done
2025-01-15 Operators must report to the authority any incident causing losses above EUR 5000.
2025-06-01 Operators must report to the authority any incident causing losses above EUR 10000.

One release, two dates, two different laws — because an amending act changed the threshold on 2025-03-01 and the release carries every version of every unit with the window it was in force for. This is what “point-in-time” means in practice, and it is the question compliance actually asks: not what does the law say but what did the law say on the day the thing happened.

The same release, the same verification, over the Model Context Protocol on stdin/stdout:

Terminal window
openregs serve --mcp \
--release fixreg@2025.04 \
--path ~/openregs-releases/fixreg/2025.04

It exposes five tools — search_regulation, get_unit, get_atom, diff_releases and list_obligations — over the same retrieval closure the REST door serves, so an agent’s answers carry the same citations. In an MCP client’s configuration that is:

{
"mcpServers": {
"openregs": {
"command": "openregs",
"args": [
"serve", "--mcp",
"--release", "fixreg@2025.04",
"--path", "/absolute/path/to/openregs-releases/fixreg/2025.04"
]
}
}
}

Use an absolute path: an MCP client does not start the server in your shell’s working directory.

You verified a signed corpus offline, watched a single flipped byte be caught and refused, and got two different legally correct answers to the same question because you asked about two different dates. No step trusted a server, and no answer arrived without a citation.