Duty: who, what, and by when
Until now every norm derived a fact: admitted, refused, check requested.
But more often law does not state but prescribes: “must return”, “must
keep”, “must notify in time”. Such a prescription has a bearer, an aim,
and a window, and its answer is not “yes” or “no” but a status: not yet
due, active, fulfilled, violated. This is a normative position,
and then duty is the corpus’s second most frequent head:
3,023 occurrences in 234 packages out of 508 (measured 05.09.2026).
The archive issues documents for home use. A researcher must return a document by its term and keep it intact for the whole issue period.
language "law.core" version "0.2";package tutorial.archive version "0.5.0";namespace "urn:law:tutorial:archive";
entity Person;
relation document_lent(p: Person, issued: Date, due: Date) kind empirical { key(p); }relation document_returned(p: Person) kind empirical;relation document_intact(p: Person) kind empirical;relation extension_granted(p: Person) kind institutional;relation non_return_recorded(p: Person, on: Date) kind empirical { key(p); }relation late_fee_due(p: Person) kind institutional;Do by the term
Section titled “Do by the term”The return duty is an achievement aim: the condition must become
established inside the window. The window here is not a literal but two
dates from the issue fact — the corpus’s most frequent form. Extending
the issue removes the duty; the exception is written with an unless
clause, so the rule is defeasible.
rule ReturnRule defeasible { for p: Person; for issued, due: Date; when document_lent(p, issued, due); then duty Return { bearer p; goal achievement { condition document_returned(p); window [issued, due]; } }; unless document_lent(p, issued, due) and extension_granted(p);}bearer is the duty-holder, the only mandatory party field;
beneficiary is not named here. condition is the fulfilment
predicate, and it is absent from the rule body: the aim reads
a conclusion, not a premise. Write in condition the same
document_lent as in when and the duty will be fulfilled at the moment
it arises, requiring nothing.
Issue from 1 to 31 March 2026. Let us ask the position’s status on different dates of law.
legal_time | Facts beyond issue | Return |
|---|---|---|
| 15 February | — | PENDING |
| 15 March | — | ACTIVE |
| 15 March | return | SATISFIED |
| 10 April | — | UNDETERMINED |
| 10 April | non-return recorded 5 April | VIOLATED |
| 15 March | non-return recorded 10 March | ACTIVE |
| 10 April | extension | DEFEATED |
The second row in the case language looks like this (why the block does not compile together with the page — below):
test "срок не вышел — обязанность активна, не нарушена" { given { context { decision_time @2026-05-01T09:00:00+05:00; knowledge_time @2026-05-01T09:00:00+05:00; legal_time @2026-03-15; timezone "Asia/Almaty"; } assert document_lent(entity_ref("urn:tutorial:ivanova"), @2026-03-01, @2026-03-31) { id "assert-lent"; origin case_input; } } evaluate positions(); expect position(Return, ACTIVE); expect not position(Return, VIOLATED); expect evaluation_status == COMPUTED;}The test name reads: “Term unexpired — the duty is active, not violated.”
The positions() query collects all positions of the case; the
position(Template, STATUS) observation is targeted — it concerns one
duty. The summary normative_status field with several norms in the case
answers UNDETERMINED, and one position cannot be checked with it. This
observation lives only in the test file: inside the page-package,
position(…) would read as a call of an undeclared function, and check
would refuse. So the block above is text, while the ready-made test
file holding all twelve scenarios of this page is executed separately.
The fourth row is the point of the page. The term has expired,
nothing is known about return, and the answer is UNDETERMINED, not
VIOLATED. A violation is not derived from silence: absence of
proof of fulfilment is not proof of non-fulfilment. For VIOLATED, the
aim condition must hold a negative support — the very kind the negation
tutorial was about. Here a control act gives it: an archive officer
recorded the non-return, and the norm translates the record into “not
returned”.
rule NonReturnRecorded strict { for p: Person; for on: Date; when non_return_recorded(p, on); then not document_returned(p);}Legally it must be so: non-return is established by control, not by the
calendar. The sixth row is the same record, but the term has not yet
expired: the duty is alive, ACTIVE. The date of law must stand after
the window.
The seventh row is the extension. The defeater produced by the unless
clause defeats the rule’s application, and the position shows with the
DEFEATED status. This is not “the duty was and
ceased”: the very ground on which it would have arisen is defeated. Note
the defeater must bind every payload variable — here the bearer and both
window bounds — which is why unless repeats the issue fact. A defeater
binding only the bearer would defeat positions it said nothing about,
and the compiler will not accept it.
Hold the state
Section titled “Hold the state”Intactness is a maintenance aim: the condition must hold across the whole window, not become true once.
rule KeepIntactRule strict { for p: Person; for issued, due: Date; when document_lent(p, issued, due); then duty KeepIntact { bearer p; goal maintenance { condition document_intact(p); window [issued, due]; } };}legal_time | Facts beyond issue | KeepIntact |
|---|---|---|
| 15 March | damage established | VIOLATED |
| 15 March | — | ACTIVE |
| 10 April | — | UNDETERMINED |
The asymmetry is deliberate. Maintenance can be violated by one
counterexample, read at once without waiting for the window’s end. It
cannot be fulfilled by one fact: saying SATISFIED needs completeness
of observation over the whole window — a monitoring certificate, a
closure covering the window entirely, or an authority’s decision. Without
them, UNDETERMINED remains after the window. “Nobody noticed damage”
does not mean “there was no damage”.
The status vocabulary
Section titled “The status vocabulary”| Status | When |
|---|---|
PENDING | the window has not started |
ACTIVE | the window runs, the condition is not established |
SATISFIED | achievement: the condition established in the window; maintenance: observation completeness confirmed |
VIOLATED | achievement: the window closed and non-fulfilment established; maintenance: a counterexample in the window |
EXPIRED | the window closed on a liberty or a ban — they are neither fulfilled nor violated |
UNDETERMINED | the window closed, with nothing to establish either fulfilment or violation |
DEFEATED | the position’s ground is defeated by a defeater |
CONFLICTED | incompatible statuses established at one moment |
Statuses are not an enum field but institutional literals with their own
support: violated(Return) holds the same four states as any
statement, and CONFLICTED is BOTH on a status.
A consequence is a separate norm
Section titled “A consequence is a separate norm”The overdue fine is not a property of the duty but another norm, on consequences. It is written from the same established facts: the non-return record is dated past the term.
rule LateReturnFee strict { for p: Person; for issued, due, on: Date; when document_lent(p, issued, due) and non_return_recorded(p, on) and on > due; then late_fee_due(p);}| Non-return record | late_fee_due |
|---|---|
| 5 April | TRUE_ONLY |
| 10 March | NEITHER |
The first row is the test from this page, byte for byte; it is an
ordinary truth query, and it compiles together with the page:
test "штраф: невозврат зафиксирован после срока — TRUE_ONLY" { given { context { decision_time @2026-05-01T09:00:00+05:00; knowledge_time @2026-05-01T09:00:00+05:00; legal_time @2026-04-10; timezone "Asia/Almaty"; } assert document_lent(entity_ref("urn:tutorial:ivanova"), @2026-03-01, @2026-03-31) { id "assert-lent"; origin case_input; } assert non_return_recorded(entity_ref("urn:tutorial:ivanova"), @2026-04-05) { id "assert-non-return"; origin case_input; } } evaluate truth(late_fee_due(entity_ref("urn:tutorial:ivanova"))); expect truth_status == TRUE_ONLY;}So violation, liability, and sanction stay different layers: a VIOLATED
on the position says the duty was not fulfilled; the fine says what is
due for it; and whether there are grounds to release from the fine is
a third norm, cancelling neither the first nor the second.
A window is not a norm’s validity term
Section titled “A window is not a norm’s validity term”A duty holds two different times, and confusing them is the costliest
mistake on this layer. The rule’s effective (the time tutorial) says
from which date the norm exists; the aim’s window says by when to
fulfil. Outside effective there is no position at all; outside
window there is one — PENDING before and UNDETERMINED or EXPIRED
after. “The norm does not yet exist” and “the term has not started” are
different answers, and both are needed.
Hence the rule: the “from 1 January 2027” milestone goes into the rule’s
effective, while the aim window counts from the event — issue, notice,
entry into force for this person. A duty’s window must not be taken from
an edition’s validity term: the term to return a document does not match
the archive rules’ validity term.
Compiler refusals
Section titled “Compiler refusals”There are two mandatory fields, with a refusal for each missing:
error LDC-E1305: duty "Return" без bearer (§123)error LDC-E1305: goal нормы "KeepIntact" без window (§124: обязателен)The diagnostics read: ‘duty “Return” without bearer’ and ‘the “KeepIntact” norm’s goal without window’.
A window boundary is a value: a date, a rule variable, or infinity.
A bare name found among neither binders nor constants is rejected by the
compiler:
error LDC-E2115: имя "moment" в границе интервала §20 не объявлено: оно несвязано ни `for` правила, ни `let`, и не объявлено ни `const` §54 …The diagnostic reads: ‘name “moment” in an interval boundary is not declared: it is bound by neither the rule’s for nor let, and declared by no const …’.
A position’s exception lives only in a defeasible rule. Make ReturnRule
strict, keeping unless — a refusal:
error LDC-E4110: ReturnRule: `unless` на strict-правиле (§149) — сделайтеправило defeasible либо внесите условие применимости в strict-телоThe diagnostic reads: “‘ReturnRule’: unless on a strict rule — make the rule defeasible or move the applicability condition into the strict body”.
A position has no contrary form: unless … then not Return is
a refusal, because a “non-duty” means nothing. Opposing positions goes by
the conflict patterns, not by negation:
error LDC-E1302: rule "ReturnRule": контрарная форма `unless … then …` принорм-голове не определена (§107.1): комплемента у позиции нетThe diagnostic reads: ‘rule “ReturnRule”: the unless … then … contrary form on a norm head is undefined: a position has no complement’.
And that very defeater that bound only the bearer. Replace unless with
a bare extension_granted(p):
error LDC-E4101: ReturnRule/unless/0: норм-голова "Return" (поля:goal.window) использует несвязанные переменные [issued, due]The diagnostic reads: ‘ReturnRule/unless/0: the “Return” norm head (fields: goal.window) uses unbound variables [issued, due]’.
All six refusals belong to the language. One lesson cannot be pinned
the same way: a second goal clause in one duty silently replaces the
first, check is clean, the CLIR keeps the last. Two aims are two rules.
Duty is one of four modalities. Ban, liberty, power, and immunity are the track’s next page; there it will turn out that a ban in the core is not violated by itself, and violation is expressed by a paired duty. And how to count an aim window not from a fact’s date but “within ten business days” is told by the tutorial on terms.
The exercise for this page is /tutorials/exercise-duty/.
Documentation for Arxo. Writings — blog.arxo.io.
Anonymous visit counts on stats.arxo.io, no cookies.