Markdown for LLMs
Duty: who, what, and by when
The source Markdown for this article. Copy it into your assistant or download it as a text file.
# 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.
```law
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
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.
```law
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):
```law
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".
```law
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
Intactness is a maintenance aim: the condition must hold across the whole
window, not become true once.
```law
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
| 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
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.
```law
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:
```law
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
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
There are two mandatory fields, with a refusal for each missing:
```text
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:
```text
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:
```text
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:
```text
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)`:
```text
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.
## Next
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/](/tutorials/exercise-duty/).