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

1. Create inputC: input_edb_create · Python: ruleset.edb

A fresh EDB holds the facts for this request only.

2. Add factsC: ADD_FACT · Python: add_facts

Record the relationship and the user’s sensitivity level. Check every insertion.

3. SolveC: session_solve_edb · Python: ruleset.solve

Evaluate the policy over this input. Python prepares and submits the input through the facade.

4. QueryC: QUERY · Python: contains_fact

Test allow(user, document), with both arguments fixed: a ground query.

5. ReleaseC: free · Python: context manager and close

Close the result and input EDB. Keep the prepared policy for the next request.

Helper function

CODEEvaluate ownership or sharing
/* 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.

CODERun three independent requests
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:

CODE
alice (level 4, owns): ALLOW
bob (level 2, owns): DENY
mallory (level 5, owns): DENY

Add 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:

CODESelect 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:

CODETest sharing without ownership
/* 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:

CODE
carol (level 3, shared): ALLOW

The 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.

Continue