Error codes
maelys_result_t return codes are returned by every API function.
| Code | Value | Meaning |
|---|---|---|
MAELYS_OK | 0 | Success. |
MAELYS_ERR_INVALID_ARGUMENT | -1 | A required argument is null or structurally invalid. |
MAELYS_ERR_INVALID_FIELD | -2 | A field value has the wrong type, an invalid format, or a constraint violation (e.g. SHA-256 mismatch). |
MAELYS_ERR_NOT_FOUND | -3 | A referenced resource (file, policy ID, domain) does not exist. |
MAELYS_ERR_UNSUPPORTED | -7 | The requested operation or domain is not supported. |
MAELYS_ERR_FORBIDDEN | -8 | The operation is explicitly prohibited (e.g. a test-only policy loaded without the explicit flag). |
MAELYS_ERR_PAYLOAD_TOO_LARGE | -9 | A static engine limit was exceeded (policy count, rule count, variable count, etc.). |
MAELYS_ERR_IO | -10 | A file read or write failure. |
MAELYS_ERR_INTERNAL | -11 | An internal allocation or engine failure. |
MAELYS_ERR_INVALID_STATE | -13 | An operation was attempted in a state that does not permit it. |
All loader, parser, solver, and query failures must be interpreted as DENY.
There is no valid path from an error to an authorization.
Fail-closed model
Loader error
Parser error
Solver error
Query error
↓
DENYDiagnostic codes
When a function accepts a maelys_datalog_diagnostic_t *out_diag parameter,
it populates the diagnostic on failure with a precise code and context fields.
Diagnostics do not change behavior — loading remains fail-closed.
Every diagnostic carries both a message and a corrective hint field. In
WASM, both are accessible as pg.diagMessage / pg.diagHint — see
WASM bindings — Diagnostics.
The tables below are exhaustive: every message/hint pair below was
verified first-hand against the engine source (every call site that sets a
maelys_datalog_diag_code_t, across the lexer, parser, manifest loader, and
registry). One code can cover several distinct situations — each row is a
distinct verified message/hint pair; the code repeats when it covers more
than one situation. Rows are listed in source order, not alphabetically.
To convert a code to a string for logging:
const char *name = maelys_datalog_diag_code_name(diag.code);
/* e.g. "MANIFEST_SHA_MISMATCH" */Manifest
| Code | message | hint |
|---|---|---|
MANIFEST_INVALID_JSON | invalid manifest JSON | fix manifest JSON syntax |
MANIFEST_INVALID_FIELD | invalid manifest field | policies must be an array |
MANIFEST_INVALID_FIELD | invalid required policy field | enabled policies require policy_id and file strings |
MANIFEST_INVALID_FIELD | invalid policy file path | policy files must use safe relative paths |
MANIFEST_INVALID_FIELD | invalid manifest field | predicate declarations must be arrays |
MANIFEST_INVALID_FIELD | invalid predicate declaration | predicate declarations require name and arity |
MANIFEST_INVALID_FIELD | invalid required policy field | provide all required policy fields with the expected type |
MANIFEST_INVALID_FIELD | invalid enabled field | enabled must be a boolean |
MANIFEST_INVALID_FIELD | invalid policy mode | mode must be enforce, shadow, or test_only |
MANIFEST_INVALID_FIELD | invalid policy SHA-256 field | sha256 must be lowercase hexadecimal |
MANIFEST_UNKNOWN_FIELD | unknown policy entry field | remove unsupported fields from the policy entry |
MANIFEST_UNKNOWN_DOMAIN | unknown policy domain | install a domain registry or disable the policy |
MANIFEST_SHA_MISMATCH | policy SHA mismatch | update manifest sha256 after policy edit |
MANIFEST_POLICY_NOT_FOUND | policy file not found | ensure the manifest policy file path exists |
MANIFEST_POLICY_NOT_FOUND | manifest file not found | ensure the manifest path exists |
MANIFEST_POLICY_NOT_FOUND | policy source not found | provide a bundle entry matching policy_id |
MANIFEST_TEST_ONLY_REJECTED | test-only policy rejected in production mode | load with test-only permission only for test manifests |
Lexer
| Code | message | hint |
|---|---|---|
LEXER_INVALID_TOKEN | unterminated block comment | close the block comment before end of file |
LEXER_INVALID_TOKEN | unterminated string literal | close the string literal before end of file |
LEXER_INVALID_TOKEN | invalid token | use :- to introduce a rule body |
LEXER_INVALID_TOKEN | invalid token | use MAELYS-DATALOG-TEXT-v1 syntax |
LEXER_INVALID_TOKEN | expected term | use a variable, string atom, integer, or boolean term |
LEXER_INVALID_TOKEN | expected predicate argument list | add parentheses after the predicate name |
LEXER_INVALID_TOKEN | expected closing parenthesis | close the predicate argument list |
LEXER_INVALID_TOKEN | expected ')' in arithmetic expression | close the parenthesized arithmetic expression |
LEXER_INVALID_TOKEN | expected '(' after not | use not(predicate(X)) syntax |
LEXER_INVALID_TOKEN | expected ')' to close not() | close the not() form with a parenthesis |
LEXER_INVALID_TOKEN | unexpected token in rule body | expected a predicate atom or comparison |
LEXER_UNSUPPORTED_CONSTRUCT | unsupported Datalog construct | construct is not supported by MAELYS-DATALOG-TEXT-v1 |
LEXER_UNSUPPORTED_CONSTRUCT | unsupported numeric literal | floats are not supported by MAELYS-DATALOG-TEXT-v1 |
LEXER_UNSUPPORTED_CONSTRUCT | unsupported string escape | string escapes are not supported by MAELYS-DATALOG-TEXT-v1 |
LEXER_UNSUPPORTED_CONSTRUCT | unsupported Datalog construct | underscore is only supported as an isolated anonymous variable |
LEXER_UNSUPPORTED_CONSTRUCT | unsupported directive | construct is not supported by MAELYS-DATALOG-TEXT-v1 |
LEXER_UNSUPPORTED_CONSTRUCT | unsupported rule neck | use :- to introduce a rule body |
LEXER_INVALID_UTF8 | invalid UTF-8 in policy text | ensure the policy text is valid UTF-8 |
LEXER_STRING_TOO_LONG | string literal too long | shorten the string atom or register a bounded atom |
Note: LEXER_INVALID_UTF8 is verified as emitted from the manifest loader
(maelys_datalog_manifest_buffer.c), as a pre-lex validation step — not from
maelys_datalog_lexer.c itself.
Parser
| Code | message | hint |
|---|---|---|
PARSER_EXPECTED_PREDICATE | expected predicate | start facts and rule heads with a declared predicate |
PARSER_UNKNOWN_PREDICATE | unknown predicate | declare predicate in the domain registry |
PARSER_UNKNOWN_PREDICATE | predicate name too long | predicate names must be shorter than 64 characters |
PARSER_ARITY_MISMATCH | predicate arity exceeds maximum | reduce predicate arity or update the accepted predicate declaration |
PARSER_ARITY_MISMATCH | predicate arity mismatch | check registry or manifest arity |
PARSER_UNKNOWN_ATOM | unknown atom | register the atom in the domain registry |
PARSER_RULE_HEAD_EDB_FORBIDDEN | base predicate used in rule head | EDB/runtime and policy fact predicates cannot appear in rule heads |
PARSER_RULE_BODY_LITERAL_OVERFLOW | rule body literal limit exceeded | split rule into IDB helper predicates |
PARSER_UNSAFE_VARIABLE | comparison variable not bound by positive body atom | bind all comparison variables in positive body atoms first |
PARSER_UNSAFE_VARIABLE | negated atom variable not bound by positive body atom | bind all not() variables in positive body atoms first |
PARSER_UNSAFE_VARIABLE | head variable not bound by positive body atom | bind every head variable in a positive body atom |
PARSER_INVALID_COMPARISON | arithmetic expression node limit exceeded | simplify the arithmetic expression |
PARSER_INVALID_COMPARISON | arithmetic expression depth limit exceeded | reduce parentheses or split the expression |
PARSER_INVALID_COMPARISON | expected integer arithmetic expression | use integer literals, variables, +, -, *, and parentheses |
PARSER_INVALID_COMPARISON | expected comparison operator | use a supported comparison between typed terms |
PARSER_INVALID_COMPARISON | arithmetic comparison requires integer expressions | do not mix arithmetic expressions with symbols or booleans |
PARSER_INVALID_COMPARISON | expected comparison terms | use simple typed terms or integer arithmetic expressions |
PARSER_INVALID_COMPARISON | comparison term kind mismatch | compare terms of the same kind |
PARSER_INVALID_COMPARISON | invalid comparison operator for term kind | symbols and booleans support equality and inequality only |
PARSER_EXPECTED_DOT | expected clause terminator | terminate facts and rules with a dot |
PARSER_EXPECTED_NECK | expected rule neck | end direct facts with a dot or introduce a rule body with :- |
PARSER_FACT_USES_NON_BASE_PREDICATE | direct fact uses non-policy predicate | direct .dl facts must use policy fact predicates |
PARSER_ANONYMOUS_VARIABLE_IN_HEAD | anonymous variable is not allowed in rule heads | use named head variables bound by positive body atoms |
PARSER_ANONYMOUS_VARIABLE_IN_COMPARISON | anonymous variable is not allowed in comparisons | use a named variable bound by a positive body atom |
PARSER_ANONYMOUS_VARIABLE_IN_COMPARISON | anonymous variable is not allowed in arithmetic expressions | use a named variable bound by a positive body atom |
PARSER_ANONYMOUS_VARIABLE_IN_COMPARISON | anonymous variable not allowed in not() | bind variables via positive atoms before using not() |
PARSER_ANONYMOUS_VARIABLE_IN_FACT | anonymous variable is not allowed in direct facts | direct .dl facts must be ground policy facts |
PARSER_ANONYMOUS_VARIABLE_IN_FACT | variables are not allowed in direct facts | direct .dl facts must be ground policy facts |
PARSER_TOO_MANY_VARIABLES | rule variable limit exceeded | reduce anonymous variables or split the rule |
Policy
| Code | message | hint |
|---|---|---|
POLICY_NOT_STRATIFIABLE | negation stratum limit exceeded | remove recursion through negation or reduce negation depth |
POLICY_NOT_STRATIFIABLE | negation stratum limit exceeded | reduce stratification depth below MAELYS_DATALOG_MAX_STRATA |
POLICY_NOT_STRATIFIABLE | policy is not stratifiable | remove recursion through negation |
Solver
| Code | Meaning |
|---|---|
MAELYS_DATALOG_SOLVE_DIAG_COMPARISON_TYPE_ERROR | Runtime cross-type comparison: a variable resolved to a type incompatible with the operator. Always a hard DENY. solve_once_ex() returns MAELYS_ERR_INVALID_FIELD, result is NULL. Diagnostic fields: lhs_kind, rhs_kind, comparison_op. Denial reason: MAELYS_DATALOG_DENY_COMPARISON_TYPE_ERROR. See Comparison operators. |
MAELYS_DATALOG_DIAG_RUNTIME_INVALID_COMPARISON exists in the
maelys_datalog_diag_code_t enum but is not currently emitted by any traced
code path in the lexer, parser, manifest loader, or registry (verified by an
exhaustive search across src/core, src/manifest, and src/wasm). It is a
separate enum member from SOLVE_DIAG_COMPARISON_TYPE_ERROR above, which
belongs to solve_once_ex()'s own lightweight diagnostic struct, not this
one. Documented here for completeness rather than omitted silently.
Registry
| Code | message | hint |
|---|---|---|
REGISTRY_CONFLICT | query whitelist predicate rejected | queries must reference domain QUERY predicates with matching arity |
REGISTRY_CONFLICT | query whitelist predicate is not query-capable | queries may only expose predicates marked QUERY by the domain |
REGISTRY_CONFLICT | domain predicate registry install failed | check domain predicate declarations |
REGISTRY_MUTATION_AFTER_FREEZE | predicate registry is not frozen | freeze the registry before parsing policy text |