Skip to main content
Authoring is invite-only private beta. Publishing with citations needs an Aethis API key with authoring scopes. Request access, or start on the open evaluate tier with your first decision — no key required. The two tiers are set out on Capabilities and access.
A criterion can declare citation keys — opaque labels such as BNA1981#Schedule1/P1.1 — in its source_refs. A key on its own says nothing: it is a placeholder for “this rule comes from somewhere”. Provenance is the step that resolves those placeholders into verified references: at publish time you supply, per key, the document it cites and the verbatim words it relies on. The engine fetches or loads the document, digests the exact bytes, checks the quote occurs in them, and records an immutable reference on the published cut. If any declared key fails, the whole publish is rejected and nothing is stored — including the references that did verify. Provenance fails closed.

The two kinds of citation target

Each entry names exactly one target. Both or neither is a 422. The split exists because the two answer different questions. A URL citation says “this rule comes from that public authority, and here is the text it relies on”. An artefact citation says “this rule comes from the document I gave you, and you still have it” — the guidance PDF, the internal policy note, the operator manual that has no public URL to point at.

Publish with citations

From the CLI

aethis publish --source-targets takes a YAML (or JSON) file keyed by citation key. Each entry names url or file; a file entry is uploaded to the project first and cited by the source_id it comes back with.
Everything checkable without the network is checked before the first API call — exactly-one-target, the required title / authority / licence / quote.exact, a readable file, an HTTPS URL, and unknown keys (a silently ignored typo is how a citation loses its locator on an immutable ruleset). A malformed targets file costs zero round trips. The CLI then prints what each key resolved to, keeping the two kinds visibly distinct — a url line is a public link, an artefact line is an authenticated download:
A file entry whose bytes match a source already on the project reuses that upload rather than duplicating it, and if the publish itself fails the uploads are not rolled back — retrying reuses them by digest.
--source-targets needs aethis-cli v0.31.0+. Check what PyPI serves today on Capabilities and access — if it is still behind, use the REST call below, which is what the CLI sends.

Over REST

source_targets is a map from citation key to target, on the publish body (illustrative — identifiers are placeholders; field names and shapes are exact):
Two behaviours worth knowing before you rely on the response:
  • Only keys the compiled ruleset declares are resolved. A target whose key no criterion declares is ignored, not rejected — so a typo’d key publishes successfully with that citation simply missing. Read the ruleset back with /explain and check the keys landed. (The CLI does this for you and reports the count.)
  • A private publish that supplies source_targets resolves them now. The references are recorded on the published cut, so a later public flip finds a citation-complete ruleset instead of a dead end. Supplying no targets on a private publish leaves the keys unresolved, which is fine until something needs them.
You can also supply source_targets on PATCH /rulesets/{ruleset_id}/visibility when flipping a ruleset public — the flip resolves and validates them fail-closed before it proceeds.

What a stored reference looks like

Published references come back on every criterion from GET /rulesets/{ruleset_id}/explain. A schema v1 reference — an HTTPS citation (illustrative):
A schema v2 reference — an uploaded-artefact citation (illustrative):
The fields that carry the weight:

url on a v2 reference is a relative, authenticated path

On a v2 reference, url is not a public link. It is the relative path of the authenticated download route — /api/v1/public/projects/{project_id}/sources/{source_id}/raw — which you resolve against the engine base URL you are calling, and which requires an API key with the projects:read scope. Rendering it as a clickable public link is wrong twice over: it is not absolute, and it is not anonymous.
Fetching the retained artefact, then, is:
The body is byte-identical to the upload, and X-Source-SHA256 carries its digest — verify it against the reference’s content_digest and you have proven, without trusting the engine, that you are holding the bytes the quote was checked against. 404 means the source predates artefact retention (only its extracted text survives); 500 means the source records a digest but its artefact is missing, which is an integrity error reported loudly rather than disguised as a legacy upload. Uploaded sources are also protected from disappearing under a live citation: DELETE on a source that any persisted ruleset cites returns 409. Mark it superseded or reference_only instead — it drops out of generation while its citations stay resolvable.

Public rulesets cite URLs only

A public ruleset’s references are served anonymously, and an artefact reference points at a private project source. So an artefact-backed ruleset can never become publicly resolvable. The engine rejects it — with reason code artefact_reference_public_visibility_forbidden — on every lane to public:
  • publishing a ruleset that will be public,
  • PATCH /rulesets/{ruleset_id}/visibility to public,
  • PATCH /rulebooks/{rulebook_id}/visibility to public (a public rulebook would expose a private member’s references),
  • attaching or promoting a member into an already-public rulebook.
The sweep covers stored references too, not just the ones in the request: a ruleset that ever published a v2 reference cannot later republish straight to public, because retired cuts stay readable. Publish a fresh ruleset citing public HTTPS URLs, or keep the ruleset private.

What verification does and does not attest

The check is verbatim after whitespace normalisation. Runs of whitespace collapse to single spaces on both sides; HTML is tag-, script- and style-stripped, because a quote cites prose rather than markup; PDFs are checked against extracted page text. There is no stemming, no case folding and no fuzzy matching — the quote’s words, in that order, must appear exactly.
A verified reference attests the retained snapshot at publish time, and nothing else. It proves that on verified_at, bytes with that content_digest contained that text at that authority. It does not assert that the provision is still in force, that the guidance has not been withdrawn, or that the rule is a correct reading of it. Currency and legal validity remain a human responsibility.
The snapshot is what makes a citation survive its source. When a page is rewritten or a URL goes dead, the quote and the digest still resolve to the bytes the engine kept, so a decision made a year ago remains explainable. Identical bytes are stored once, keyed by digest. Pin the bytes you reviewed with expected_digest on a target: a fetch whose digest differs then fails the publish rather than silently citing a changed document.

When a citation fails

Every failure is reported per key, with a stable reason_code, in a 422 carrying reason_code: source_reference_resolution_failed and a failures array. Nothing is published and no snapshot is retained. URL targets additionally fail closed on the fetch itself — a non-HTTPS scheme, credentials in the URL, an IP literal or private destination, too many redirects, an oversized body or a timeout each carry their own reason code.

Reading citations back

Every criterion carries source_refs (the declared keys) and source_references (the resolved, verified references):
Both fields are served by api.aethis.ai today, and the response above is what that call returns right now. The first-party showcase rulesets do not yet declare citation keys, so nothing resolves against them and both fields come back null — the reference shapes shown earlier on this page are taken from the live API schema, not from a showcase response. Treat the array as optional in client code: null and [] both mean “this criterion carries no verified reference”, never “verification failed”.

See also

  • Rule generation — the generation-time grounding report, which is a different thing: it scores how well generated rules trace to your uploaded sources, and never blocks. The citations on this page are publish-time, verified, immutable and fail-closed.
  • CLI — the --source-targets flag.
  • REST API — the projects:read scope that the artefact download route requires.
  • Decision envelope — the per-decision reproducibility fields that sit alongside provenance.