Markdown for LLMs
Exercise 16. An extension moves the term, not cancels the duty
The source Markdown for this article. Copy it into your assistant or download it as a text file.
# Exercise 16. An extension moves the term, not cancels the duty
An exercise for the [duty](/tutorials/duty/) page. On the page an
extension removes the document-return duty, which is imprecise: an
extension grants a **new** term, and the duty lives until it.
**Setup.** Replace the "extension granted" fact with a dated "extended
until" fact, and write a second duty: return the document by the new
date. The overdue fine must count from the new date. Expectations: with
an issue due 31 March and an extension to 30 April, a 5 May non-return
yields a fine, a 15 April non-return does not; on 10 April the duty to
return by the extended term is active, the original one is not.
**Hint.** The `unless` clause must bind the extension date itself. The
fine reads two dates, and the comparison goes against the later one.
Below is the solution. Try it yourself first.
## Solution
```law
language "law.core" version "0.2";
package tutorial.archive version "0.5.1";
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 extension_granted_until(p: Person, until: Date) kind empirical { key(p); }
relation non_return_recorded(p: Person, on: Date) kind empirical { key(p); }
relation extension_granted(p: Person) kind institutional;
relation late_fee_due(p: Person) kind institutional;
```
The original duty is removed by the extension, as on the page, but
`unless` now reads the date:
```law
rule ReturnRule defeasible {
for p: Person;
for issued, due, until: 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_until(p, until);
}
```
The second duty, with a window to the new date:
```law
rule ReturnAfterExtension strict {
for p: Person;
for issued, due, until: Date;
when document_lent(p, issued, due) and extension_granted_until(p, until);
then duty ReturnExtended {
bearer p;
goal achievement {
condition document_returned(p);
window [issued, until];
}
};
}
rule NonReturnRecorded strict {
for p: Person;
for on: Date;
when non_return_recorded(p, on);
then not document_returned(p);
}
```
The fine is two norms, one per term. Without an extension it counts from
the original term; with one from the new, and the first norm stays silent
under an extension. For the first norm to ask "no extension", the dated
fact folds into a dateless flag:
```law
rule ExtensionExists strict {
for p: Person;
for until: Date;
when extension_granted_until(p, until);
then extension_granted(p);
}
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
and not_known(extension_granted(p));
then late_fee_due(p);
}
rule LateReturnFeeAfterExtension strict {
for p: Person;
for issued, due, until, on: Date;
when document_lent(p, issued, due) and extension_granted_until(p, until)
and non_return_recorded(p, on) and on > until;
then late_fee_due(p);
}
```
The first test, on the fine past the extended term:
```law
test "невозврат после продлённого срока — штраф" {
given {
context {
decision_time @2026-06-01T09:00:00+05:00;
knowledge_time @2026-06-01T09:00:00+05:00;
legal_time @2026-05-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 extension_granted_until(entity_ref("urn:tutorial:ivanova"), @2026-04-30) {
id "assert-extension";
origin case_input;
}
assert non_return_recorded(entity_ref("urn:tutorial:ivanova"), @2026-05-05) {
id "assert-non-return";
origin case_input;
}
}
evaluate truth(late_fee_due(entity_ref("urn:tutorial:ivanova")));
expect truth_status == TRUE_ONLY;
expect evaluation_status == COMPUTED;
}
```
The test name reads: "Non-return past the extended term — a fine."
## Check
Three tests in `tests/46-exercise-duty.lawtest`: the fine past the new
term, positions on 10 April (`ReturnExtended` active, `Return` gone),
and a pre-new-term non-return without a fine. The positions test is shown
not on the page but in the file: the `position(...)` observation does not
compile into the package.
Why the `extension_granted` flag. Asking
`not_known(extension_granted_until(p, until))` will not do: `until` is
bound by nothing in this norm, and with `due` substituted the question
would become "no extension to exactly the old term", which an extension
to any other date would not defeat. This page's first version was written
exactly so, and the third test caught it: the fine accrued on a 15 April
non-return, inside the extended term.
What the exercise teaches. An extension is not a cancellation but a new
duty with a new window, and the fine must follow the window. Positions
show this more directly than facts: on 10 April one duty is active, the
other removed.