Getting Started
██████╗ ███████╗████████╗████████╗██╗███╗ ██╗ ██████╗ ██╔════╝ ██╔════╝╚══██╔══╝╚══██╔══╝██║████╗ ██║██╔════╝ ██║ ███╗█████╗ ██║ ██║ ██║██╔██╗ ██║██║ ███╗ ██║ ██║██╔══╝ ██║ ██║ ██║██║╚██╗██║██║ ██║ ╚██████╔╝███████╗ ██║ ██║ ██║██║ ╚████║╚██████╔╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ███████╗████████╗ █████╗ ██████╗ ████████╗███████╗██████╗ ██╔════╝╚══██╔══╝██╔══██╗██╔══██╗╚══██╔══╝██╔════╝██╔══██╗ ███████╗ ██║ ███████║██████╔╝ ██║ █████╗ ██║ ██║ ╚════██║ ██║ ██╔══██║██╔══██╗ ██║ ██╔══╝ ██║ ██║ ███████║ ██║ ██║ ██║██║ ██║ ██║ ███████╗██████╔╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚══════╝╚═════╝
Maelys DL is an embedded Datalog policy engine for C applications. You write authorization logic in a small declarative language, supply runtime facts about the current request, and the engine tells you whether the request is allowed.
This page gets you from zero to a working authorization check in under five minutes.
What you need
- A C99 compiler (clang or gcc)
- libmaelys_datalog.a (built from source with make)
- include/maelys_datalog.h (single public header)Link against the library and include the single public header. No other dependencies.
#include "include/maelys_datalog.h"The idea in one picture
You declare which predicates exist (domain), write rules that derive decisions (policy), supply the runtime context (EDB), run the solver, and query the result. Any failure at any step means DENY.
Step 1 — Define your vocabulary
Policies can only use predicates you declare. Create a static domain table:
#include "include/maelys_datalog.h"
static const maelys_datalog_predicate_def_t preds[] = {
/* runtime facts — supplied per request */
{ "user", 1, MAELYS_DATALOG_PRED_KIND_EDB },
{ "owns", 2, MAELYS_DATALOG_PRED_KIND_EDB },
/* policy fact — declared in the .dl source */
{ "blocked", 1, MAELYS_DATALOG_PRED_KIND_POLICY_FACT },
/* derived decision — queryable */
{ "allow", 2, MAELYS_DATALOG_PRED_KIND_IDB |
MAELYS_DATALOG_PRED_KIND_QUERY },
};Step 2 — Write a policy
Create a file policy.dl:
blocked("mallory").
allow(User, Doc) :-
user(User),
owns(User, Doc),
not blocked(User).Two things happen here. blocked("mallory") is a policy fact — it lives in the
source file, not in the runtime data. The allow rule derives a decision: a
user may access a document if they own it and are not blocked.
Step 3 — Load the policy
maelys_datalog_policy_set_t policy_set;
maelys_datalog_diagnostic_t diag = {0};
maelys_result_t rc = maelys_datalog_load_policy_inline_with_static_domain(
preds,
sizeof(preds) / sizeof(preds[0]),
"access", /* domain name */
"access.main", /* policy id */
policy_src, /* .dl source */
policy_src_len,
0,
&policy_set,
&diag);
if (rc != MAELYS_OK) {
/* diag.message explains the failure */
return DENY;
}load_policy_inline_with_static_domain registers the domain and loads the
policy in a single call. Use it for small embedded policies with a fixed
vocabulary. For production deployments with SHA verification and multiple
policies, see Manifest loading.
Step 4 — Supply runtime facts
For each request, create an EDB with the relevant facts:
static maelys_datalog_edb_t edb;
static maelys_datalog_fact_t pool[64];
maelys_datalog_edb_init(&edb, pool, 64,
&policy_set.policies[0].symbols,
&policy_set.policies[0].registry);
/* alice exists */
maelys_datalog_edb_add_runtime_symbol_fact(&edb, "user", "alice");
/* alice owns roadmap.pdf */
maelys_datalog_symbol_id_t user_id, doc_id;
maelys_datalog_symbol_intern(&policy_set.policies[0].symbols,
"alice", 5, &user_id);
maelys_datalog_symbol_intern(&policy_set.policies[0].symbols,
"roadmap.pdf", 11, &doc_id);
maelys_datalog_term_t terms[2];
terms[0].kind = MAELYS_DATALOG_TERM_SYMBOL; terms[0].as.symbol = user_id;
terms[1].kind = MAELYS_DATALOG_TERM_SYMBOL; terms[1].as.symbol = doc_id;
maelys_datalog_edb_add_fact(&edb, "owns", terms, 2);
maelys_datalog_edb_finalize(&edb);Step 5 — Solve and query
maelys_datalog_solve_result_t *result = NULL;
rc = maelys_datalog_solve_once(&policy_set.policies[0], &edb, &result);
if (rc != MAELYS_OK) return DENY;
bool allowed = false;
rc = maelys_datalog_query_solved_ground_fact(
result, "allow", terms, 2, &allowed);
maelys_datalog_solve_result_free(result);
if (rc != MAELYS_OK || !allowed) return DENY;
return ALLOW;That's it. allowed is true when the solver derived allow("alice", "roadmap.pdf"). If anything fails along the way, the result is DENY.
Complete example
#include "include/maelys_datalog.h"
#include <string.h>
#include <stdio.h>
static const maelys_datalog_predicate_def_t preds[] = {
{ "user", 1, MAELYS_DATALOG_PRED_KIND_EDB },
{ "owns", 2, MAELYS_DATALOG_PRED_KIND_EDB },
{ "blocked", 1, MAELYS_DATALOG_PRED_KIND_POLICY_FACT },
{ "allow", 2, MAELYS_DATALOG_PRED_KIND_IDB |
MAELYS_DATALOG_PRED_KIND_QUERY },
};
static const char policy_src[] =
"blocked(\"mallory\").\n"
"allow(User, Doc) :-\n"
" user(User), owns(User, Doc), not blocked(User).\n";
int main(void) {
maelys_datalog_policy_set_t ps;
maelys_datalog_diagnostic_t diag = {0};
/* Load */
maelys_result_t rc = maelys_datalog_load_policy_inline_with_static_domain(
preds, 4, "access", "access.main",
policy_src, sizeof(policy_src) - 1, 0, &ps, &diag);
if (rc != MAELYS_OK) { fprintf(stderr, "load: %s\n", diag.message); return 1; }
/* EDB */
static maelys_datalog_edb_t edb;
static maelys_datalog_fact_t pool[64];
maelys_datalog_edb_init(&edb, pool, 64,
&ps.policies[0].symbols, &ps.policies[0].registry);
maelys_datalog_edb_add_runtime_symbol_fact(&edb, "user", "alice");
maelys_datalog_symbol_id_t u, d;
maelys_datalog_symbol_intern(&ps.policies[0].symbols, "alice", 5, &u);
maelys_datalog_symbol_intern(&ps.policies[0].symbols, "roadmap.pdf", 11, &d);
maelys_datalog_term_t t[2] = {
{ .kind = MAELYS_DATALOG_TERM_SYMBOL, .as.symbol = u },
{ .kind = MAELYS_DATALOG_TERM_SYMBOL, .as.symbol = d },
};
maelys_datalog_edb_add_fact(&edb, "owns", t, 2);
maelys_datalog_edb_finalize(&edb);
/* Solve */
maelys_datalog_solve_result_t *result = NULL;
rc = maelys_datalog_solve_once(&ps.policies[0], &edb, &result);
if (rc != MAELYS_OK) return 1;
bool ok = false;
maelys_datalog_query_solved_ground_fact(result, "allow", t, 2, &ok);
maelys_datalog_solve_result_free(result);
printf("allow(alice, roadmap.pdf) = %s\n", ok ? "true" : "false");
return 0;
}Expected output:
allow(alice, roadmap.pdf) = trueTry changing "alice" to "mallory" — the policy blocks them, so the result
becomes false.
Next steps
- Tutorial — step-by-step walkthrough with multiple users, sensitivity levels, and production-style manifest loading.
- Registries — how to declare more complex vocabularies.
- Rulesets — comparison operators, negation, and variable safety.
- API Reference — complete function reference.