Markdown for LLMs
Negation: when "no" must be proven
The source Markdown for this article. Copy it into your assistant or download it as a text file.
# Negation: when "no" must be proven
In the last tutorial the negative support came from the case: the ethics
committee revoked the accreditation, and `accredited(Ivanova)` became
`FALSE_ONLY`. But the case is not the only source of "no", nor the main
one. More often "no" is spoken by the norm itself: "issue is not made",
"is not recognised", "is not entitled". In the corpus such a negative
head stands in two and a half thousand rules across 271 packages
(measured 05.09.2026).
This page is about three things formalisers confuse most often: where
negative support comes from, what `not p(x)` in a body actually reads,
and how "not established" differs from "refuted".
The same Archive of Veliky Ustin. Home issue of documents and overdue
returns join the registry and accreditation.
```law
language "law.core" version "0.2";
package tutorial.archive version "0.2.1";
namespace "urn:law:tutorial:archive";
entity Person;
relation in_researcher_registry(p: Person) kind institutional;
relation accredited(p: Person) kind institutional;
relation overdue_item(p: Person) kind empirical;
relation may_borrow(p: Person) kind institutional;
relation must_settle_before_visit(p: Person) kind institutional;
relation reference_check_required(p: Person) kind institutional;
```
## A norm that refuses
The issue rules say: a reader who has not returned a document on time is
not issued items for home use. This is not a missing permission but
a direct refusal, and it is written with a negative head.
```law
rule OverdueBlocksBorrowing strict {
for p: Person;
when overdue_item(p);
then not may_borrow(p);
}
```
| Facts | `may_borrow` |
|---|---|
| overdue item | `FALSE_ONLY` |
Having fired, the rule places a negative support on `may_borrow(Ivanova)`,
the same kind the committee's revocation gave in the last tutorial. The
engine does not care where the support came from: a norm head, a case
fact, and a completeness closure (more on it two pages ahead) all write
into the same "against" column. The specification recognises exactly
three sources, no fourth, and that is the main thing to remember from
this page.
## What `not` reads in a body
A researcher refused issue must settle the debt before visiting. Here
the refusal is a premise, standing in the body with the same `not`.
```law
rule SettleBeforeVisit strict {
for p: Person;
when in_researcher_registry(p) and not may_borrow(p);
then must_settle_before_visit(p);
}
```
| Facts | `must_settle_before_visit` |
|---|---|
| listed, overdue | `TRUE_ONLY` |
| listed, no overdue | `NEITHER` |
| listed, refusal recorded in the case | `TRUE_ONLY` |
| listed, overdue plus a "issue allowed" record | `NEITHER` |
A bare literal in a body reads as `established`; we saw that across the
four states. `not p(x)` reads symmetrically, as `refuted(p(x))`: the body
needs an established negative support, `FALSE_ONLY`, and nothing else.
**The second row is the most important on the page.** For
`may_borrow(Ivanova)` there is neither an overdue item nor a record. The
rule does not "fire with a no answer"; it stays silent. There is no
negation by failure in the core: what nobody permitted is not yet
forbidden, and the silence of rules remains silence.
The third row is a refusal written into the case by hand, like the
committee's revocation in the last tutorial: `assert not may_borrow(…)`.
For the body it is the same "against" support as a norm's conclusion.
The fourth is `BOTH`. The librarian recorded "issue allowed" while the
norm refused; `may_borrow` now holds both supports. `not may_borrow(p)`
requires `FALSE_ONLY`, not "any support against at all", and on
a contradictory input the rule draws no conclusion, just as it drew none
on `BOTH` in a positive premise.
## A rule that will never fire
Remove the refusing norm and keep everything else. The compiler is
content, the layer is as before, but `SettleBeforeVisit` is dead: with
an overdue item, with anything, the answer is `NEITHER`. Only a refusal
written into the case by hand can revive it.
| What is in the package | `must_settle_before_visit` when overdue |
|---|---|
| with `OverdueBlocksBorrowing` | `TRUE_ONLY` |
| without it | `NEITHER` |
This is the formaliser's most frequent mistake, and it is caught by
a question worth asking of every `not p(x)` in a body: **who produces
`not p`?** A norm head, a case fact, or closure. If there is no answer,
yet you meant to write "unless otherwise established", then this is not
negation. It is ignorance, covered in the next section.
With the refusing norm cut out the answer is `NEITHER`: the lesson of
the dead rule holds just like the lesson of the live one.
## Ignorance is not negation
While nothing is established about a researcher's accreditation, the
archive requests a background check. That can be called neither a refusal
nor a ban: the norm acts on the gap case.
```law
rule ReferenceCheck strict {
for p: Person;
when in_researcher_registry(p) and not_known(accredited(p));
then reference_check_required(p);
}
```
| Facts | `reference_check_required` | `accredited` |
|---|---|---|
| listed | `TRUE_ONLY` | `NEITHER` |
| listed, accredited | `NEITHER` | `TRUE_ONLY` |
| listed, accreditation revoked | `TRUE_ONLY` | `FALSE_ONLY` |
`not_known(P)` is true when `P` has no positive support after all its
producers have run. The question is different: not "is it refuted"
but "is it established". Hence two consequences, both visible in
the table.
`not_known` refutes nothing. The check is requested, while accreditation
in the first row stayed `NEITHER` and did not become `FALSE_ONLY`.
Writing `not_known` where a true refusal is needed yields a rule whose
head fires while the "no" never appears.
`not_known` does not mean "nothing is known". Third row: the committee
revoked the accreditation, there is no positive support, and the check is
requested, although plenty is known about the reader. If the norm says
"while there is no evidence at all", a status test `unknown(P)` is
needed: it is true only at `NEITHER`.
For "not established" the engine pays with ordering: to answer that
there is no positive support, it must wait for all producers of
`accredited`. That is why the layer view of this page shows
**L1**, not L0, with the note "default negation `not_known`".
A cycle through `not_known` of the form "A if B is not known; B if A is
not known" is rejected statically by the compiler: `LDC-E4102`.
## Three compiler refusals
`not` takes exactly one atom. The compiler does not apply de Morgan's
laws, and `not (A and B)` in a body is not a shorthand but an error:
```text
error LDC-E1305: not поверх не-атома в теле — вне L0-среза (§190)
```
The diagnostic reads: "`not` over a non-atom in a body — outside the L0 slice".
Write `not A or not B`, or introduce a predicate with a rule and negate
it.
Negation does not bind a variable. Leave in `SettleBeforeVisit`'s body
only `not may_borrow(p)` and `p` has no source of values:
```text
error LDC-E4101: SettleBeforeVisit: head использует несвязанные переменные [p]
— правило не range-restricted (§190: связывание только позитивным
established-конъюнктом конечного источника)
```
The diagnostic reads: "`SettleBeforeVisit`: head uses unbound variables [p] — the rule is not range-restricted: a variable must be bound by a positive established conjunct of a finite source".
Next to a negation there must stand a positive literal enumerating the
candidates; here it is the registry. Both refusals above belong to the
language itself: the compiler rejects such variants with exactly
these codes.
The third refusal is softer and hence more dangerous. `not_known` is
allowed only in a body; placed in a head, it yields warning `LDC-E1302`,
`check` stays green, and the whole rule drops out of the CLIR. The norm
looks written and does not exist. Read the `check` warnings: the broken
variant still compiles, and the `LDC-E1302` code must sound in them.
## Next
A negative head can refuse, but answers only for itself. Put next to it
a general norm that permits, and the answer will be `BOTH`, as in the
fourth row of the second table. For one norm to defeat another, defeat
and priority are needed: [the next tutorial](/tutorials/defeaters/).
The exercise for this page is [/tutorials/exercise-negation/](/tutorials/exercise-negation/).