Document access
Chapter 3 — Evaluation and sharing
Assemble the application and evaluate ownership, blocks and sharing.Continue from Chapter 2 — Verified manifest loading. Keep the declarations, policy files and helpers from the earlier chapters.
Evaluate a request
What happens per request
C: input_edb_create · Python: ruleset.edbA fresh EDB holds the facts for this request only.
C: ADD_FACT · Python: add_factsRecord the relationship and the user’s sensitivity level. Check every insertion.
C: session_solve_edb · Python: ruleset.solveEvaluate the policy over this input. Python prepares and submits the input through the facade.
C: QUERY · Python: contains_factTest allow(user, document), with both arguments fixed: a ground query.
C: free · Python: context manager and closeClose the result and input EDB. Keep the prepared policy for the next request.
Helper function
/* Return 1 only for a successfully derived authorization fact. */
static int check_access(
maelys_datalog_session_t *session, const char *user,
const char *doc, int sensitivity, int shared)
{
maelys_datalog_input_edb_t *edb = NULL;
maelys_datalog_result_t *result = NULL;
maelys_datalog_diagnostic_t diagnostic = MAELYS_DATALOG_DIAGNOSTIC_INIT;
int allowed = 0;
maelys_datalog_status_t rc = maelys_datalog_input_edb_create(&edb);
if (rc != MAELYS_DATALOG_STATUS_OK) goto cleanup;
const char *relation = shared ? "shared_with" : "owns";
rc = MAELYS_DATALOG_ADD_FACTS(edb, &diagnostic,
MAELYS_DATALOG_FACT(relation, user, doc),
MAELYS_DATALOG_FACT("sensitivity_level", user, sensitivity));
if (rc != MAELYS_DATALOG_STATUS_OK) goto cleanup;
rc = maelys_datalog_session_solve_edb(session, edb, &result, &diagnostic);
if (rc != MAELYS_DATALOG_STATUS_OK) goto cleanup;
rc = MAELYS_DATALOG_QUERY(result, &allowed, "allow", user, doc);
cleanup:
if (rc != MAELYS_DATALOG_STATUS_OK) {
fprintf(stderr, "document access: %s: %s\n",
maelys_datalog_status_name(rc), diagnostic.message);
allowed = 0;
}
if (result != NULL) (void)maelys_datalog_result_free(result);
(void)maelys_datalog_input_edb_free(edb);
return allowed;
}Test the policy
Assemble the domain declarations, the manifest loader, the request helper and the startup code below into one program. The registration fragment is already included here; do not paste it twice. Run the program from the directory containing policies/, using the compiler or Python environment prepared in the quickstart.
int main(void) {
maelys_datalog_session_t *session = NULL;
maelys_datalog_diagnostic_t diagnostic = MAELYS_DATALOG_DIAGNOSTIC_INIT;
maelys_datalog_status_t rc = maelys_datalog_domain_register(&domain);
if (rc != MAELYS_DATALOG_STATUS_OK) {
fprintf(stderr, "register domain: %s\n", maelys_datalog_status_name(rc));
return 1;
}
rc = load_policy(&session, &diagnostic);
if (rc != MAELYS_DATALOG_STATUS_OK) {
fprintf(stderr, "load: %s: %s\n", maelys_datalog_status_name(rc),
diagnostic.message);
return 1;
}
const char *users[] = {"alice", "bob", "mallory"};
const int levels[] = {4, 2, 5};
for (size_t i = 0; i < 3; ++i)
printf("%s (level %d, owns): %s\n", users[i], levels[i],
check_access(session, users[i], "roadmap.pdf", levels[i], 0)
? "ALLOW" : "DENY");
(void)maelys_datalog_session_free(session);
return 0;
}Expected output:
alice (level 4, owns): ALLOW
bob (level 2, owns): DENY
mallory (level 5, owns): DENYAdd the sharing path
The helper already has a sharing option. Its input changes from owns to shared_with; the policy does not change. These are the two lines inside the helper that select the relationship:
const char *relation = shared ? "shared_with" : "owns";
rc = MAELYS_DATALOG_ADD_FACT(edb, &diagnostic, relation, user, doc);
if (rc != MAELYS_DATALOG_STATUS_OK) goto cleanup;Then add a test case:
/* Insert before session_free() in main(). */
printf("carol (level 3, shared): %s\n",
check_access(session, "carol", "roadmap.pdf", 3, 1) ? "ALLOW" : "DENY");Expected output:
carol (level 3, shared): ALLOWThe same solver run evaluates both rules. The solver does not short-circuit —
it derives all facts that follow from the policy. If either the owns rule or
the shared_with rule applies, allow is derived.
What you have learned
Keep this model
Use one policy for different document requests- Supply the complete request
Ownership, sharing and sensitivity are input facts for the current request. The block on Mallory is already part of the loaded policy.
- Check the decision, not just the API call
Authorize only after solving and querying succeed and the requested allow fact is present. A missing allow fact denies access; an API error must also deny.
- Sharing is another path through the same conditions
Carol can qualify through shared_with instead of owns, but the sensitivity threshold and policy block still apply.
- Reuse the prepared policy
Release the result after its queries are finished. The prepared session can then evaluate another request with a new complete set of input facts.