ash_graphlaw
ash_graphlaw is the Ash/BEAM membrane for GraphLaw.
It hosts the pinned graphlaw.wasm WASI module through Wasmex, preserves GraphLaw's JSON ABI, and
exposes admissions to Ash resources as a Spark extension with a Change, a Validation and a
Preparation. Semantic standards (RDF, SHACL, N3, RDFS, OWL-RL, hooks, plan admission, receipts) stay
inside GraphLaw; they are not reimplemented in Elixir.
Ash / Reactor / SA2A
|
v
ash_graphlaw Change / Validation / Preparation
|
v
Pool / Host (Wasmex + WASI)
|
v
graphlaw.wasm
|
v
PurRDF + Eyeron + GraphLaw
Core invariant
A caller proposes a request. GraphLaw derives and validates: it decides whether the projected
semantic state and the requested transition are admitted. ash_graphlaw projects the outcome as a
typed success (AshGraphLaw.Admitted, wrapped in AshGraphLaw.Evidence) or a typed refusal
(AshGraphLaw.Refusal). Nothing in this library grants authority; admission evidence is an
observation bound to an exact input digest, and AshGraphLaw.Standing never reports :ALIVE for
an admission alone.
request --> [ Change / Validation / Preparation ] --> Pool --> graphlaw.wasm
|
{:ok, %Admitted{}} <-- admitted, digest-bound evidence ---+
{:error, %Refusal{}} <-- closed code + class + broken_term-+
Typed capabilities
Every public GraphLaw operation has a typed, generated Elixir entry point, so no supported
operation needs AshGraphLaw.call/2. The surface is generated from the GraphLaw capability
registry (schema graphlaw.capability-registry/1, vendored in priv/graphlaw/) through the
graphlaw-ash-capability-pack; AshGraphLaw.Capability.Registry.names/0 lists the ops in ABI
order (14 at ABI 1), and every op has a request builder, a decoder and a
lossless AshGraphLaw.Result.* struct.
{:ok, %AshGraphLaw.Result.Shacl{conforms: true}} =
AshGraphLaw.shacl(data: turtle_text, shapes: shapes_text)
{:error, %AshGraphLaw.Refusal{code: code}} = AshGraphLaw.parse(text: "not rdf at all")
AshGraphLaw.Capability.API.sparql!(data: turtle_text, query: "SELECT * WHERE { ?s ?p ?o }")
- Root delegates:
parse/2 convert/2 canonical/2 sparql/2 shacl/2 shex/2 n3/2 entail/2 datalog/2 policy/2and their bang forms;call/2,capabilities/1,sniff/3,law/3andhooks/3keep their behaviour. - Raw stays lossless (
result.raw), unknown response fields and enum values decode without failing, and refusals carry the whole engine error inrefusal.raw. - Parity court:
mix ash_graphlaw.parityfails on any drift between the live engine, the vendored registry and the Elixir surface. Engine pinv26.9.29: parity against a pin older than the registry is reported, never masked. - Registry pin:
scripts/vendor_registry.shandscripts/import_registry.shcarry the registry from the GraphLaw repository intopriv/graphlaw/andontology.ttl(--checkin CI). - Semantics stay in GraphLaw:
ash_graphlawexposes, it never reimplements RDF, SPARQL, SHACL, ShEx, N3, Datalog, entailment or planning. A successful typed call is an observation, not standing.
Per-op pages: capabilities reference.
Installation
def deps do
[
{:ash_graphlaw, "~> 26.10.1"}
]
end
The package version is 26.10.1 (calendar versioning). The graphlaw.wasm binary
is not shipped in the Hex package; fetch the pinned release asset with the vendor task (see
Vendoring the engine). With Igniter available, mix igniter.install ash_graphlaw runs mix ash_graphlaw.install.
Configuration
# config/config.exs
config :ash_graphlaw,
start_pool: true
With start_pool: true the application supervises AshGraphLaw.Pool. The wasm path resolves in
this order: the :wasm_path option, the GRAPHLAW_WASM_PATH environment variable, the
:wasm_path application environment, then priv/graphlaw/graphlaw.wasm. The digest pin applies
to the vendored path and to environment or config supplied paths.
DSL
defmodule MyApp.Ticket do
use Ash.Resource,
domain: MyApp.Domain,
extensions: [AshGraphLaw.Resource]
graphlaw do
runtime do
timeout_ms 5_000
max_skew_secs 60
trusted_keys ["<64 hex characters: the public key of a lease signer you trust>"]
end
admission :ticket_close do
step :plan
ceiling :select
law MyApp.PlanLaw
end
end
actions do
update :close do
change {AshGraphLaw.Change.Admit, admission: :ticket_close}
end
end
end
MyApp.PlanLaw implements the AshGraphLaw.Law behaviour and returns the GraphLaw law steps
for the subject. The admission runs before the action, and a refusal becomes an Ash error.
Authority: signed leases
The ceiling is met only by a signed lease in the changeset context; a bare :select atom
grants only :observe. The engine verifies the signature against trusted_keys and judges expiry
on its own clock, and the resulting AshGraphLaw.Evidence records the lease identity and the
engine digest.
signed = MyApp.Leases.mint_signed_lease(subject: "ticket-42", ceiling: :select)
MyApp.Ticket
|> Ash.Changeset.for_update(ticket, :close, %{},
context: %{graphlaw_lease: %{signed_lease: signed}}
)
|> Ash.update()
MyApp.Leases.mint_signed_lease/1 stands for your own signer; ash_graphlaw verifies leases and
never mints authority. See authority boundary.
Handling refusals
case MyApp.Ticket |> Ash.Changeset.for_update(ticket, :close) |> Ash.update() do
{:ok, closed} ->
closed
{:error, error} ->
case Enum.find(Ash.Error.to_error_class(error).errors, &match?(%AshGraphLaw.Error.Refused{}, &1)) do
%AshGraphLaw.Error.Refused{refusal: refusal} ->
{refusal.code, refusal.class, refusal.broken_term}
nil ->
{:error, error}
end
end
Direct calls return {:ok, %AshGraphLaw.Admitted{}} or {:error, %AshGraphLaw.Refusal{}} and
AshGraphLaw.Standing.of/1 derives the standing from the result. The closed refusal vocabulary is
in typed refusals.
Introspect
AshGraphLaw.Info reads the declared graphlaw section of any resource without running the
engine:
AshGraphLaw.Info.admissions(MyApp.Ticket)
AshGraphLaw.Info.admission(MyApp.Ticket, :ticket_close)
See the DSL cheat sheet for every option.
Vendoring the engine
The engine binary is pinned by release tag v26.9.29 (asset graphlaw.wasm, ABI
- and by SHA-256. Fetch and check it:
mix ash_graphlaw.vendor # downloads graphlaw.wasm into priv/graphlaw/, checks the SHA-256
mix ash_graphlaw.verify # re-checks the digest and the WASI import allowlist
Source: https://github.com/seanchatmangpt/graphlaw/releases/download/v26.9.29/graphlaw.wasm
Expected SHA-256: 7bb2a7e5ebcef7584b0b960451272d56fa75414d76a12138d41e8973e126eee0. A mismatch is refused; the binary is never used
unverified. See vendor the wasm.
Verification ladder
Cheapest, highest-information check first:
mix format --check-formatted
mix compile --warnings-as-errors
mix credo --strict
mix test # wasm-free tests
mix test --include wasm --include slow # with the vendored engine
mix ash_graphlaw.verify
mix ash_graphlaw.parity # capability parity court
mix ash_graphlaw.mutate --require-killed # mutation catalog
mix docs
mix hex.build
A green ladder is an observation on one exact commit. It is not standing: :ALIVE requires an
exact-SHA receipt. See claims and evidence.
Documentation map
Documentation follows the Diataxis layout under documentation/ (the hub). Hosted docs: https://hexdocs.pm/ash_graphlaw.
| Kind | Start here |
|---|---|
| Tutorial | getting started, first admitted action |
| How-to | vendor the wasm, declare an admission, write a law module, run the pool, handle refusals |
| Reference | DSL, ABI, claims and evidence, support matrix, typed refusals, typed capabilities, DSL cheat sheet |
| Explanation | architecture, authority boundary, generation and residue, wasm host design |
Agent-facing guidance is in usage-rules.md and AGENTS.md.
Citing
See CITATION.cff for the citation metadata of this release.
Reproduce
REPRODUCE.md lists the exact commands and pins to rebuild and re-verify this release from a clean checkout.
Security
Report vulnerabilities as described in SECURITY.md. The engine is pinned by SHA-256 and the WASI import surface is allowlisted; the host never widens either.
Non-goals
- Not a reimplementation of RDF, SHACL, N3, OWL-RL or hooks in Elixir; those stay in GraphLaw.
- Not an authority source: it verifies signed leases and never mints them.
- Not a standing oracle: an admission alone never yields
:ALIVE. - Not a bundler of
graphlaw.wasm: the binary is vendored and digest-checked, not shipped in Hex. - Not a place to hand-edit projections: edit the ontology, queries or templates.
Manufactured provenance
- ash_graphlaw: v26.10.1
- GraphLaw court release: v26.9.29
- GraphLaw ABI: 1
- graphlaw.wasm SHA-256:
7bb2a7e5ebcef7584b0b960451272d56fa75414d76a12138d41e8973e126eee0 - ggen source pin:
ff96f04e8c7b851e5cca53f3faf5ce1d5f43ce6e - ggen-marketplace pin:
86735f4e2683cace11f92824af0ac3875ec5e3fb
Regeneration
This file is a projection. Edit ontology.ttl, queries/project.rq or
templates/README.md.tmpl, then run scripts/ggen_sync.sh (scripts/vendor_marketplace.sh first when vendor/ is absent). Do not edit README.md by hand.
License
MIT. See LICENSES/MIT.md.