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.

SurfaceHow it integratesTypical use
C Stable APIInclude <maelys/datalog.h> and use opaque policies, sessions, input buffers, and results.New servers, agents, gateways, embedded policy checks.
C Advanced APIInclude <maelys/datalog_advanced.h> for in-memory manifests, extension points and event-window adapters.Native integrations needing these additional controls.
Typed WASM bindingUse the SDK's JavaScript wrapper and TypeScript declarations.Browser playgrounds, web apps and sandboxed policy evaluation.
Python bindingsNative 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 loadingLoad a manifest JSON and policy files from disk.Production deployments with versioned policy bundles.
Buffer manifest loadingAdvanced 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 domainsOptional 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:

NeedStart here
Understand the modelRegistries, manifests, rulesets, runtime EDB, solving, querying, and event windows.
Write new native C codeC Stable API — registries, manifests, rulesets, EDB, solving, querying, and errors.
Write Python codePython binding — the current maelys_datalog package; Python V1 is historical.
Write JavaScript/browser codeJavaScript 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.

ARCHITECTUREPrepare a policy, then evaluate requests
    • 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

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_FACT

Ruleset

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:

ARCHITECTUREFrom vocabulary and logic to a decision
    • Domaindeclare the vocabulary
    • Rulesetload rules + policy facts
    • Sessionprepare the selected policy
    • Request EDBsupply runtime facts
    • Resultinput snapshot + conclusions
    • Queryread the decision

Using the example above:

CODE
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") = false

Why 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:

DATALOG
/* 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:

ConcernSouffléMaelys DL
Vocabulary declarations.decl in source fileDomain declaration selected by the loader
Facts / dataExternal .input data or inline factsEDB per request, plus separately declared policy facts
RulesInline in fileRuleset (loaded once)
Outputs.outputDomain QUERY permission, narrowed by a manifest whitelist when present

This separation means:

CODE
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

SectionLifecycle stepDescription
Registries1 — RegistryDefine the domain vocabulary: predicates, arities, and kinds.
Manifest loading2 — ManifestLoad and validate policy manifests from files or memory.
Rulesets3 — RulesetParse and manage Datalog policies.
Runtime EDB4 — EDBSupply request-specific input facts.
Solving5 — SolveCompute the least fixpoint.
Querying6 — QueryInspect query-enabled facts and optionally explain their presence or absence.
Event windowsRepeated snapshotsRetain 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 caseRecommended entry point
Production policies on diskFile manifest loading.
Policies already in memoryBuffer manifest loading.
Browser or playground integrationTyped JavaScript/WASM wrapper, explicit domain declaration and inline source loading.
Native research/automation toolingPython bindings + native domain builder.
Standalone tests and examplesAn explicit small domain and inline source, or a native in-memory manifest when its checks are needed.
Application-specific authorizationCustom domain registry + explicit query predicates.

Integration contract

Every integration should preserve these rules:

CODE
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.