docs← Back to article

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.

Download this articlePlain text ↗
# 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/).