C Stable API

Manifests

Load inline policies or governed manifests into an opaque policy set.

The opaque loader returns a maelys_datalog_policy_t *. It can parse one inline source or a governed manifest containing several policies. This page covers the stable calls in <maelys/datalog.h>. The manifest concept explains why a policy set exists.

Types

Defined on this page

TypePurpose
maelys_datalog_policy_tOpaque handle for the loaded policy set.

Used from other pages

These types are defined on the linked pages; this page uses them in the roles below.

TypeUse on this pageDefined in
maelys_datalog_diagnostic_tOptional output argument receiving loading details.Errors

Functions

FunctionPurpose
maelys_datalog_diagnostic_initializer()Create the optional diagnostic value before calling a loader.
maelys_datalog_policy_load_inline()Load one policy from source bytes.
maelys_datalog_policy_load_manifest()Load a verified policy set from files.

Initialize maelys_datalog_diagnostic_t with maelys_datalog_diagnostic_initializer() before passing its address to a loader. It sets the required size and ABI version; {0} alone does not. Pass NULL instead if no detailed error information is needed. The diagnostic is separate from the loaded policy handle.

The C Advanced API additionally loads a manifest from memory; it is not a function of <maelys/datalog.h>.

Policy-set handle

C · opaque
maelys_datalog_policy_t
typedef struct maelys_datalog_policy maelys_datalog_policy_t;
Opaque handle owning one loaded policy or a manifest set. Select policies by index through sessions; release the handle with policy_free().

Initialize the output pointer to NULL before either loader. On success it owns one policy or a manifest set; release it with maelys_datalog_policy_free() when no new session needs it. Each loader returns a maelys_datalog_status_t: only MAELYS_DATALOG_STATUS_OK makes the output handle usable. On failure there is no partial policy; inspect the status and, if supplied, the diagnostic instead.

Choose a loading path

The stable header offers two ways to load a policy. Advanced C includes the stable header, so a check in both columns means the same stable call is also available to an advanced-C program. The complete loading architecture compares the additional in-memory and historical variants.

PathVariantStable CAdvanced C
Manifestfiles on disk✓✓
ManifestJSON and source in memory✕✓
Inlinesource bytes; registered domain✓✓
Inlineregister domain + load source in one call✕✕

A green check means the installed SDK exposes that path; a red cross means it does not. In the last row, the crosses refer only to the missing combined call: both APIs already accept a static predicate table and source bytes in memory, using registration followed by inline loading. The two stable calls are illustrated below:

Registered domain
predicate vocabulary and source atoms
Manifest · files
  1. Verified files
    policy_load_manifest
Inline · source
  1. Policy source bytes
    policy_load_inline

The manifest-file path reads JSON metadata and policy files from disk, verifies their declared SHA-256 digests, and applies the manifest's Public Query Whitelist. The inline-source path takes one policy's source bytes in memory without a manifest or whitelist. Both require a previously registered domain: the inline call names it directly; the manifest names it in JSON. Neither gives the caller a mutable parser or ruleset.

Register a domain once when several policies share its vocabulary. Registration copies the declarations, including policy-source atoms; using a static table does not mean loading without a copy. Inline loading reads the supplied source bytes with no filesystem access. The WASM binding uses this same sequence: registerDomain(), then loadPolicy().

Registration and loading are separate operations, not one transaction. If registration succeeds but loading fails, the domain remains registered and no new policy is returned. The Advanced loading architecture explains the historical combined call.

If the manifest JSON and policy sources are already in memory, C Advanced API offers the buffer variant with the same manifest checks. That buffer variant is not raw inline source.

Loading a policy

Use inline loading when the application already has one policy's source bytes. No manifest is read, so there is no digest or whitelist metadata on this path.

C · function
Load an inline policy
maelys_datalog_status_t
maelys_datalog_policy_load_inline(
const char * domain,
const char * policy_id,
const char * source,
size_t source_length,
maelys_datalog_policy_t ** out_policy,
maelys_datalog_diagnostic_t * out_diagnostic
);
Loads one policy directly from Datalog source text and validates it against a registered domain. This path does not read a manifest.
Arguments
domainconst char *
Previously registered domain name.
policy_idconst char *
Stable application-level identifier for this policy.
sourceconst char *
Exact MAELYS-DATALOG-v2 source bytes.
source_lengthsize_t
Number of bytes in source.
out_policymaelys_datalog_policy_t **
Receives an opaque policy on success and NULL on failure.
out_diagnosticmaelys_datalog_diagnostic_t *
Optional structured load diagnostic.
Return value
maelys_datalog_status_t

MAELYS_DATALOG_STATUS_OK on success; otherwise a fail-closed public status.

Use manifest loading when files, digests and an explicit public-query whitelist are part of the deployment contract. A single manifest can name several policies.

C · function
Load a manifest policy set
maelys_datalog_status_t
maelys_datalog_policy_load_manifest(
const char * manifest_path,
unsigned int flags,
maelys_datalog_policy_t ** out_policy,
maelys_datalog_diagnostic_t * out_diagnostic
);
Loads the policies listed in a manifest file, checking their source hashes, domain declarations and public query permissions.
Arguments
manifest_pathconst char *
Path to the policy-set manifest.
flagsunsigned int
Permission mask; unknown bits are rejected.
out_policymaelys_datalog_policy_t **
Receives the complete opaque policy set.
out_diagnosticmaelys_datalog_diagnostic_t *
Optional structured manifest or policy diagnostic.
Return value
maelys_datalog_status_t

MAELYS_DATALOG_STATUS_OK on success; otherwise a fail-closed public status.

Use maelys_datalog_policy_count() before selecting an index from a manifest that contains multiple enabled policies.

Example calls

Choose one loading path for a given handle. For source bytes already in memory:

CODE
maelys_datalog_policy_t *policy = NULL;
maelys_datalog_diagnostic_t diagnostic =
    maelys_datalog_diagnostic_initializer();
maelys_datalog_status_t status = maelys_datalog_policy_load_inline(
    "documents", "documents.main", source, strlen(source),
    &policy, &diagnostic);

For a governed set stored as files, use this instead:

CODE
maelys_datalog_policy_t *policy = NULL;
maelys_datalog_diagnostic_t diagnostic =
    maelys_datalog_diagnostic_initializer();
maelys_datalog_status_t status = maelys_datalog_policy_load_manifest(
    "policies/manifest.json", MAELYS_DATALOG_PUBLIC_ALLOW_NONE,
    &policy, &diagnostic);

If you need both loaded sets simultaneously, give each its own output variable and later free each handle.

Loading permissions

The flags argument of maelys_datalog_policy_load_manifest() is a mask of independent permissions, not a choice between global loading modes. MAELYS_DATALOG_PUBLIC_ALLOW_NONE has the permanent value 0u and means that no optional permission is granted.

C flagsPermission granted
MAELYS_DATALOG_PUBLIC_ALLOW_NONENone: require declared policy constants and reject enabled test-only policies.
MAELYS_DATALOG_PUBLIC_ALLOW_TEST_ONLYAdmit enabled entries whose manifest mode is test_only.
MAELYS_DATALOG_PUBLIC_ALLOW_UNDECLARED_POLICY_ATOMSAdmit policy-local string constants absent from the domain's atoms.
Both bits combined with |Grant both permissions independently.

Test-only is a loading guard, not a simulation. An enabled entry marked "mode": "test_only" is rejected with MAELYS_DATALOG_STATUS_FORBIDDEN unless the caller grants the permission. Once loaded, it evaluates normally and can produce real decisions. The engine does not detect a production environment: the application must keep this permission out of production configuration. Granting it does not bypass SHA-256 checks or authorize undeclared atoms.

Policy atoms belong to the registered domain, not the manifest. For example, blocked("mallory"). in policy source requires "mallory" in maelys_datalog_domain_t.atoms by default. Registering the string permits its use; it does not create a blocked-user fact. A literal used as a predicate argument inside a rule has the same requirement, including for EDB predicates. Special standard-filter pattern arguments have their own validation; they are not policy atoms.

By contrast, "mallory" supplied by an EDB insertion is runtime data and does not need an atom declaration. Variables such as User and integer values are not string atoms either. ALLOW_UNDECLARED_POLICY_ATOMS relaxes only the source-constant check: predicates still need declaration, capacities remain bounded, and the permission does not modify the global domain or change request-data handling.

Inline loading guarantees declared policy constants. maelys_datalog_policy_load_inline() has no flags parameter: its built-in Datalog path always checks source constants against the registered domain and exposes no opt-out. It reads no manifest metadata such as test_only. Among these two public loaders, only the manifest loader exposes the permissions above.

Unknown bits fail explicitly. The public manifest loader rejects any unsupported bit with MAELYS_DATALOG_STATUS_INVALID_ARGUMENT; it does not ignore it. A program compiled with a future nonzero permission bit therefore cannot silently lose that permission when used with an older library. Do not confuse this permission mask with the unrelated predicate-kind flags or the numeric policy index used to create a session.

See the document-access comparison for the same blocked-user fact supplied as EDB data versus written as a policy fact.

Example — document access

After registering the seven-predicate documents domain on registries, load the same policy used by the quickstart. The inline path returns a one-policy handle; its index is 0.

CODE
static const char source[] =
    "can_read(User, Doc) :-\n"
    "    owns(User, Doc) or delegated(User, Doc),\n"
    "    not(blocked(User)).\n"
    "has_any_document(User) :- owns(User, _).\n"
    "allow(User, Doc) :- user(User), can_read(User, Doc).\n";

The loader receives these source bytes and an initialized diagnostic for optional error details.

CODE
maelys_datalog_policy_t *policy = NULL;
maelys_datalog_diagnostic_t diagnostic =
    maelys_datalog_diagnostic_initializer();
maelys_datalog_status_t status = maelys_datalog_policy_load_inline(
    "documents", "documents.main", source, sizeof(source) - 1u,
    &policy, &diagnostic);
if (status != MAELYS_DATALOG_STATUS_OK) {
    /* Inspect diagnostic; do not create a session. */
}

For a file manifest, the domain field selects the same registered documents vocabulary and the policy file contains these same source bytes. The manifest adds policy-set metadata and can select more than one policy; it does not change the meaning of the rules. This policy contains no quoted source atoms, so the domain's atom list remains empty.