Legal norms: strict vs default vs defeater
Area: Defeasible reasoning Teaches: the three rule strengths of defeasible reasoning on one head — a strict (unattackable) clause, an overridable
#[default]clause, and a#[defeats]directive that attacks the default per-tuple. RFD 0028’s “honest heads”: no rule spells the conclusion it denies. Prerequisites: refinement (iff/where), andderiverule bodies (see first-class relations). Run:ox build examples/legal_norms_can_vote && ox run-scenario examples/legal_norms_can_vote
Who can vote? Three norms speak to can_vote, and they conflict. A pre-defeasibility data language forces you to fold the exceptions into the rule body by hand (Adult(p), not Felon(p), not …), which becomes unmaintainable as exceptions accumulate. Argon keeps each norm a separate honest rule and resolves the conflict with directives.
What to read in norms.ar
The strict clause is unmarked — it is unattackable. Special-class members vote regardless of any disenfranchisement edge:
pub derive can_vote(p) :- SpecialClass(p);
The default clause is overridable. Adults vote by default — #[default] marks the clause as defeasible, and #[label(adult)] gives it an identity an attacker can name:
#[default]
#[label(adult)]
pub derive can_vote(p) :- Adult(p);
The exception is its own honest rule, and the attack is a directive. disenfranchised reads true on its own terms — it does not spell not can_vote. The attack lives in #[defeats(can_vote(p))], a meta-level statement about rules that resolves per-tuple against the bound p:
#[defeats(can_vote(p))]
pub derive disenfranchised(p) :- Felon(p);
So for a given person, disenfranchised defeats the default can_vote clause — but the strict clause is not a default, so the attack cannot touch it.
Running it
The scenario registers four people, one per case:
alice age=25 felon=false special=false → can_vote (default holds, unattacked)
bob age=12 felon=false special=false → not (not an Adult — no clause fires)
carol age=30 felon=true special=false → not (default defeated by disenfranchised)
dave age=45 felon=true special=true → can_vote (strict clause; the attack can't reach it)
so voters returns 2 rows — alice (default survives) and dave (strict overrides the very defeat that blocks carol). The decisive contrast is carol vs dave: both are felons, both are disenfranchised, but dave’s vote comes from the strict clause that #[defeats] cannot attack.
This example is compiled and run in CI; its can_vote extent under the strict/default/defeater interplay is pinned by a corpus test (oxc-runtime/tests/examples_corpus.rs), so the resolution can’t drift from the language.