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
| Type | Purpose |
|---|---|
maelys_datalog_policy_t | Opaque 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.
| Type | Use on this page | Defined in |
|---|---|---|
maelys_datalog_diagnostic_t | Optional output argument receiving loading details. | Errors |
Functions
| Function | Purpose |
|---|---|
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
typedef struct maelys_datalog_policy maelys_datalog_policy_t;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.
| Path | Variant | Stable C | Advanced C |
|---|---|---|---|
| Manifest | files on disk | ✓ | ✓ |
| Manifest | JSON and source in memory | ✕ | ✓ |
| Inline | source bytes; registered domain | ✓ | ✓ |
| Inline | register 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:
- Verified filespolicy_load_manifest
- Policy source bytespolicy_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.
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
);maelys_datalog_status_tMAELYS_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.
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
);maelys_datalog_status_tMAELYS_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:
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:
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 flags | Permission granted |
|---|---|
MAELYS_DATALOG_PUBLIC_ALLOW_NONE | None: require declared policy constants and reject enabled test-only policies. |
MAELYS_DATALOG_PUBLIC_ALLOW_TEST_ONLY | Admit enabled entries whose manifest mode is test_only. |
MAELYS_DATALOG_PUBLIC_ALLOW_UNDECLARED_POLICY_ATOMS | Admit 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.
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.
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.