Concepts
Concepts and integration
Understand the engine's model before choosing a C, Python, or JavaScript integration surface.Maelys DL exposes a small, fail-closed integration surface for embedding the Datalog engine in native applications and browser runtimes.
The current native interfaces are the opaque C Stable API and its Advanced extensions. Python and typed JavaScript/WASM use the same policy model, with different exposed operations.
The shared lifecycle is: declare a domain, load a policy, prepare a session, supply request facts, solve, query, and optionally obtain Why-true or Why-false explanations.
Integration surfaces
Maelys DL is designed to be embedded rather than run as a separate service. The same policy engine can be used from native C code, server-side runtimes, tests, and browser-based WASM environments.
| Surface | How it integrates | Typical use |
|---|---|---|
| C Stable API | Include <maelys/datalog.h> and use opaque policies, sessions, input buffers, and results. | New servers, agents, gateways, embedded policy checks. |
| C Advanced API | Include <maelys/datalog_advanced.h> for in-memory manifests, extension points and event-window adapters. | Native integrations needing these additional controls. |
| Typed WASM binding | Use the SDK's JavaScript wrapper and TypeScript declarations. | Browser playgrounds, web apps and sandboxed policy evaluation. |
| Python bindings | Native cffi binding over the same C API, in-process, no browser or WASM runtime. | Research pipelines, notebooks, offline analysis, long-running native services. |
| File manifest loading | Load a manifest JSON and policy files from disk. | Production deployments with versioned policy bundles. |
| Buffer manifest loading | Advanced C loads manifest JSON and source bytes from memory, retaining integrity and observation checks. | Native in-memory policy stores and tests; not exposed by the typed WASM wrapper. |
| Example domains | Optional graph and decision domains for examples and standalone tests. | Documentation, samples, test fixtures, playgrounds. |
Choose a documentation path
The conceptual pages explain the domain, manifest, ruleset, runtime EDB, solve, and query boundaries without asking you to adopt a particular language API. Implementation details then follow the interface you actually use:
| Need | Start here |
|---|---|
| Understand the model | Registries, manifests, rulesets, runtime EDB, solving, querying, and event windows. |
| Write new native C code | C Stable API — registries, manifests, rulesets, EDB, solving, querying, and errors. |
| Write Python code | Python binding — the current maelys_datalog package; Python V1 is historical. |
| Write JavaScript/browser code | JavaScript and WASM binding. |
The language pages are separate, not synchronized C/Python/JavaScript tabs: the bindings share engine concepts but do not expose identical functions or the same stability guarantees. Side-by-side tabs remain useful for examples where two interfaces genuinely perform the same task.
Lifecycle
Load and prepare the policy once. Then repeat the request stages with new input facts. Loading can use a manifest or an inline source; a manifest is not a mandatory stage for every integration.
Domainvocabulary + source atoms
Load policymanifest or inline source
Prepare sessionreusable rules + workspace
Request EDBcomplete input snapshot
Solvederive the result
Query and explainread decision + optional evidence
- Domain → Load policy
- Load policy → Prepare session
- Prepare session → Request EDB
- Request EDB → Solve
- Solve → Query and explain
The session can be reused after its live result is released. The policy is not reloaded for every request. The input buffer and returned result are different objects: clearing the buffer does not release the result.
The pipeline is fail-closed at every stage: a failure anywhere yields DENY, never an implicit ALLOW.
Three layers, one pipeline
Vocabulary, logic and runtime data answer different questions. Maelys DL separates them into three distinct layers with different lifetimes and different owners.
Domain Registry
vocabulary · arity · kind
Declares which predicates can exist, with what arity and kind. A rule or fact using an undeclared predicate is rejected. The registry is the closed vocabulary: a deployment manifest can select a registered domain and bound public observation, but it never creates predicates.
edge/2 EDB
path/2 IDB
allow/1 IDB | QUERY
blocker/1 POLICY_FACTRuleset
logic · rules · deduction
Contains the Datalog rules and direct policy facts written by the policy author. It defines how IDB facts are derived, is loaded once, and is reused across requests. Runtime data never belongs here.
blocker("b").
path(X,Y) :- edge(X,Y).
path(X,Z) :- path(X,Y), edge(Y,Z).
allow(X) :- path("root",X), not(blocker(X)).EDB
runtime facts · input · request context
Holds facts supplied for one evaluation. The application provides a complete, trustworthy request snapshot; the engine does not invent missing evidence.
edge("root", "a").
edge("a", "b").A predicate kind is exactly one of EDB, POLICY_FACT, or IDB, optionally
combined with QUERY — QUERY never stands alone.
For this graph example, declare "root" and "b" as policy-source atoms.
Runtime values such as "a" do not need that declaration unless the source
also names them.
The three layers combine in one evaluation for each request:
Domaindeclare the vocabulary
Rulesetload rules + policy facts
Sessionprepare the selected policy
Request EDBsupply runtime facts
Resultinput snapshot + conclusions
Queryread the decision
- Domain → Ruleset
- Ruleset → Session
- Session → Request EDB
- Request EDB → Result
- Result → Query
Using the example above:
Registry declares: edge/2 EDB, path/2 IDB, allow/1 IDB|QUERY, blocker/1 POLICY_FACT
Ruleset defines: blocker(b). path(X,Z) :- path(X,Y), edge(Y,Z). allow(X) :- path(root,X), not(blocker(X)).
EDB contains: edge(root,a) edge(a,b)
Solver derives:
path(root, a) ← edge(root,a)
path(a, b) ← edge(a,b)
path(root, b) ← path(root,a) + edge(a,b)
allow(a) ← path(root,a), not(blocker(a)) ✓
allow(b) blocked — blocker(b) is true ✗
Query result: allow("a") = true / allow("b") = falseWhy the separation matters
Soufflé can put relation declarations, rules and input/output directives in
one program. An .input directive can still read data from a separate file:
/* Soufflé — declarations and input/output directives in source */
.decl edge(x:symbol, y:symbol)
.input edge
path(x,y) :- edge(x,y).
path(x,z) :- path(x,y), edge(y,z).
.output path
In Maelys DL the three concerns are deliberately separated:
| Concern | Soufflé | Maelys DL |
|---|---|---|
| Vocabulary declarations | .decl in source file | Domain declaration selected by the loader |
| Facts / data | External .input data or inline facts | EDB per request, plus separately declared policy facts |
| Rules | Inline in file | Ruleset (loaded once) |
| Outputs | .output | Domain QUERY permission, narrowed by a manifest whitelist when present |
This separation means:
The registry fixes the perimeter. (what can be said)
The ruleset reasons within that perimeter. (how to conclude)
The EDB injects only conforming facts. (what is true now)
The solver derives what the rules justify. (what follows)A policy cannot reason about admin_override(...) or secret_backend(...)
unless those predicates were declared in the domain registry. An EDB cannot
inject a POLICY_FACT predicate — that slot belongs to the policy source
alone.
Documentation
| Section | Lifecycle step | Description |
|---|---|---|
| Registries | 1 — Registry | Define the domain vocabulary: predicates, arities, and kinds. |
| Manifest loading | 2 — Manifest | Load and validate policy manifests from files or memory. |
| Rulesets | 3 — Ruleset | Parse and manage Datalog policies. |
| Runtime EDB | 4 — EDB | Supply request-specific input facts. |
| Solving | 5 — Solve | Compute the least fixpoint. |
| Querying | 6 — Query | Inspect query-enabled facts and optionally explain their presence or absence. |
| Event windows | Repeated snapshots | Retain bounded accepted events and recompute a complete runtime input. |
| Bindings overview | — | Compare the WASM and Python bindings and see where each fits. |
| WASM bindings | — | Use the engine from JavaScript and browser runtimes. |
| Python binding | — | Use the engine natively from Python — no browser, no WASM. |
| C errors | — | Statuses and diagnostics for the C API. |
Choosing an entry point
| Use case | Recommended entry point |
|---|---|
| Production policies on disk | File manifest loading. |
| Policies already in memory | Buffer manifest loading. |
| Browser or playground integration | Typed JavaScript/WASM wrapper, explicit domain declaration and inline source loading. |
| Native research/automation tooling | Python bindings + native domain builder. |
| Standalone tests and examples | An explicit small domain and inline source, or a native in-memory manifest when its checks are needed. |
| Application-specific authorization | Custom domain registry + explicit query predicates. |
Integration contract
Every integration should preserve these rules:
Install domains before loading policies.
Prepare the selected policy's session before solving.
Supply the request's complete facts before solving.
Release the previous result before the next solve on that session.
Query only predicates declared as public queries.
Deny on every error path.What you have learned
KEEP THIS MODEL
One policy, a new decision for each request- Declare what the policy can name
The domain declares predicates, their number of arguments and their role. Register it before loading a policy.
- Prepare the policy once
The policy contains reusable logic. Its session keeps the prepared policy and the working space needed to evaluate requests.
- Supply what is true for this request
The input EDB is the complete set of request facts. A new solve does not implicitly inherit facts from the previous request.
- Authorize from a successful answer
After a successful solve, require a successful query and the expected allow fact. Explanations help describe the answer; they do not replace that decision.
- Reuse the session, not the old answer
Release explanations and the previous result, then solve the next input snapshot with the same session.