docs← Back to article

Markdown for LLMs

Coming from Cedar

The source Markdown for this article. Copy it into your assistant or download it as a text file.

Download this articlePlain text ↗
# Coming from Cedar

**In short:** Cedar gives you three fixed decision rules (deny by
default, a forbid always wins, any satisfied permit allows) and returns
the deciding policies with the answer. In Arxo none of these is built
in: each is written as a rule or a priority, with a name and a stated
reason. The result is the same decision on ordinary requests, but "no
permit applied" and "a forbid applied" stay two different supports for
the refusal, instead of one Deny. This page follows the authorization
example of the [Cedar comparison](/comparisons/cedar/): groups with
inheritance, permits that check clearance and team, an explicit forbid,
an optional attribute behind a `has` guard, and template-linked policies.
The Arxo package quoted here passes `law engine check`; its scenarios and
the Cedar side of the experiment have not been run yet, so the page shows
how the policies are written, not measured outcomes.

## Concept map

| Cedar | Arxo | What changes for you |
|---|---|---|
| `permit` policy | Strict rules that establish a basis (`permitted_by`, then `access_basis` with the attribute checks) and one defeasible rule, `AccessAllowed`, that concludes `access_allowed` from the basis | The permit is split into "which group grants this" and "do the attributes match", each a named rule |
| `forbid` policy | A defeasible rule with head `not access_allowed(...)` and a `priority` over the allowing rule with `reason explicit_exception` | "A forbid always wins" is a declaration you can read, not an engine rule |
| Deny by default (no satisfied permit) | A presumption: a defeasible rule that refuses every request, beaten by `AccessAllowed` through a `priority` with `reason lex_specialis` | A refusal by default and a refusal by forbid are supported by different rules and stay distinguishable |
| `principal in Group::"g"` through the entity hierarchy | Two strict rules, direct membership and inheritance, with the recursive step marked `monotone(...)` | The hierarchy is ordinary rules over facts about groups |
| `has` guard on an optional attribute | No `has` introspection: the adapter supplies a presence fact, and a `closure` over the subject register turns its absence into an explicit "not present" | Absence of an attribute is a statement about a complete register, not a property of the entity record |
| Template-linked policy | A fact recording the link between template, principal and resource, supplied with the case | Template editing that applies instantly is outside the example |
| Deciding policies in the answer | The proof of the answer names the rules that support it | The witness lists rules and facts, including the presumption when nothing else applied |
| Error in a policy: skipped, reported in diagnostics | A missing attribute is a missing fact: the rule that needs it does not fire. Malformed policy text has no skip-on-error counterpart | Errors are a different stage on each side, not a different verdict |
| Allow and Deny as the decision | In addition, deontic positions: a liberty for an allowed access and a prohibition for a forbidden one | The answer can be read as "may" and "must not", with the same precedence |
| JSON form of policies, `unspecified` placeholders | No counterpart: a policy is `.law` text and every request is typed | Nothing to port |

## Group hierarchy

In Cedar, `principal in Group::"g"` follows the entity hierarchy for you.
In Arxo the hierarchy is two rules over the membership and inheritance
facts of the case. (The `label` lines of the rules on this page carry
Russian descriptions and are omitted from the excerpts.)

```law
rule HoldsDirect strict {
    for p: Principal; for g: Group;
    when member(p, g);
    then holds(p, g);
}

rule HoldsInherited strict {
    for p: Principal; for child: Group; for parent: Group;
    when monotone(holds(p, child)) and inherits(child, parent);
    then holds(p, parent);
}
```

The recursive step is wrapped in `monotone(...)`: a bare atom inside
the cycle would be rejected by the compiler.

## Permit, forbid and the default

A Cedar permit combines a scope (principal, action, resource) with
conditions. The Arxo package splits it: first, which group of the
subject grants or forbids the action; then, whether the attributes
match.

```law
rule PermittedBy strict {
    for p: Principal; for g: Group; for a: CedarAction; for res: Resource;
    when access_requested(p, a, res) and holds(p, g) and permits(g, a, res);
    then permitted_by(p, a, res);
}

rule DeniedBy strict {
    for p: Principal; for g: Group; for a: CedarAction; for res: Resource;
    when access_requested(p, a, res) and holds(p, g) and forbids(g, a, res);
    then denied_by(p, a, res);
}

rule AttrBasis strict {
    for p: Principal; for a: CedarAction; for res: Resource; for t: Team;
    for c: Integer; for k: Integer;
    when permitted_by(p, a, res)
        and clearance(p, c) and security_level(res, k) and (c >= k)
        and team_of_principal(p, t)
        and team_of_resource(res, t);
    then access_basis(p, a, res);
}
```

The decision itself comes from three defeasible rules, all about
`access_allowed(p, a, res)`: `AccessAllowed` concludes it from a request
with a basis; `ExplicitDeny` concludes its negation from a request the
subject's group forbids; `DefaultDeny` concludes its negation from the
request alone. Cedar's two fixed combining rules become two priorities:

```law
priority BasisRebutsDefaultDeny {
    prefer AccessAllowed over DefaultDeny;
    reason lex_specialis;
}

priority DenyOverAllow {
    prefer ExplicitDeny over AccessAllowed;
    reason explicit_exception;
}
```

For a request with a basis and no forbid, the basis beats the
presumption. For a request with both, the forbid beats the basis. For a
request with neither, the presumption stands. Each refusal is supported
by a different rule, so "not permitted" and "prohibited" stay apart in
the answer. If the forbid priority is removed, the package no longer says
which side wins and the conflict is kept as `BOTH`; Cedar, by design,
still answers Deny. The experiment includes that mutant to show the
difference, not to rank it.

## Optional attributes and `has`

Cedar guards an optional attribute with `has` so that a missing value
does not become an evaluation error. Arxo has no introspection on an
entity record. The adapter supplies a fact saying the attribute is
present, and a closure over the register of subjects says that a subject
on file without that fact does not have the attribute:

```law
closure TitlePresence {
    predicate title_present;
    domain principal_on_file;
    snapshot "urn:snapshot:exp-cedar-authz:subjects:2026-09-30";
    complete_as_of @2026-09-30T00:00:00Z;
    derive_explicit_negative true;
}

rule GuardedBasis strict {
    for p: Principal; for a: CedarAction; for res: Resource;
    when permitted_by(p, a, res)
        and title_present(p)
        and title(p, "VP");
    then access_basis(p, a, res);
}
```

Outside the domain of the closure, silence stays silence: the premise is
not established, and the rule does not fire.

## Allow and Deny as positions

Cedar's decision is Allow or Deny. The Arxo package also states what the
decision means for the subject: an allowed access is a liberty, a
forbidden one is a prohibition, and the prohibition wins for the same
reason the forbid does.

```law
rule AllowedIsLiberty strict {
    for p: Principal; for a: CedarAction; for res: Resource;
    when access_allowed(p, a, res);
    then liberty MayAccess {
        holder p;
        action access_requested(p, a, res);
        window [@2000-01-01, infinity);
    };
}

rule DeniedIsProhibition strict {
    for p: Principal; for a: CedarAction; for res: Resource;
    when access_requested(p, a, res) and denied_by(p, a, res);
    then prohibition MustNotAccess {
        bearer p;
        action access_requested(p, a, res);
        window [@2000-01-01, infinity);
    };
}

priority DenyOverridesAllow {
    prefer DeniedIsProhibition over AllowedIsLiberty;
    reason explicit_exception;
}
```

## What you gain, what you give up

- **Gain: refusals that keep their reason.** "No permit applied" and "a
  forbid applied" are different supports, so a downstream reader can tell
  them apart without inspecting the deciding-policy set.
- **Gain: precedence and conflicts you can read.** The combining rules
  are declarations with named reasons; removing one leaves a visible
  conflict instead of a silent change of verdict.
- **Give up: set-level analysis.** Cedar's policy-set analysis
  (never-errors, always-allows, subsumption, equivalence, with
  counterexamples) and its formalized authorizer and validator have no
  counterpart here. Arxo's evidence is per answer: a derivation that
  costs more to produce and proves less about the whole policy set.
- **Different scope.** Time and editions of the policy text are outside
  Cedar's purpose and outside this comparison.

## Next steps

- [Cedar](/comparisons/cedar/): the full comparison, the studied
  versions and what is still open.
- [Priority](/constructs/priority/),
  [Presumptions and fictions](/constructs/presumption-fiction/),
  [Negation and truth statuses](/constructs/negation-and-status/): the
  forms behind default deny and forbid-overrides.
- [Prohibitions](/constructs/prohibition/) and
  [Liberties and immunities](/constructs/liberty-immunity/): positions
  in more detail.
- [Guide](/guide/): getting started with Arxo.