Markdown for LLMs
Queries: truth, collect, positions, why_not and neighbours
The source Markdown for this article. Copy it into your assistant or download it as a text file.
# Queries: truth, collect, positions, why_not and neighbours
**In one sentence:** these constructs answer the question "what to
ask the law and in which shape the answer arrives", without changing the law
itself: take them when the case is already described by facts and a
question must be put — check a proposition (`truth`), gather values
(`collect`), take a norm-position slice (`positions`, `duties`, …), explain a
non-firing (`why_not`) — and read the answer as a document with result kinds
and statuses.
A case is questioned in four shapes — check a proposition, collect values, slice norm positions, explain a non-firing — each answered as a typed document. The nearest pages are [lifecycle statuses](/constructs/lifecycle-statuses/) (what the positional kinds ask) and [expressions and quantities](/constructs/expressions-quantities/) (terms and `collect` generators).
## 1. When to take it and when not to
| Instead of | Selection rule |
|---|---|
| `truth(P)` vs `why_not(P)` | `truth` asks "does it hold" and answers with a status (`PROPOSITION`); `why_not` asks "why not concluded" and answers with a blocker graph (`GRAPH`). An unfired rule is taken apart via `why_not`, not a second `truth`: the status is one, but there is no explanation |
| `truth(P)` vs `positions()` | `truth` is a point question about one proposition; `positions()` is a slice of all the case's norm positions (no literal). Duty/prohibition states are taken with `positions()`, a fact with `truth` |
| `collect v: T where G` vs `truth` with a concrete value | `collect` lists ALL values the generator concludes (`COLLECTION`); `truth` checks one presented value. "Which books are overdue" is `collect`, "is this one overdue" is `truth` |
| `focused_truth` vs `truth` | In 0.2 `focused_truth` does NOT execute: refusal `FOCUS_UNSUPPORTED_SEMANTICS` without a result. The question cone is a later-semantics feature. In 0.2 always `truth` |
| `query` declaration vs standard kind | A named `query` with `let`/`return` is computation over the law (`COMPLIANCE`, `DATA`); the standard kinds are questions to the law itself. `query` adds no support to the legal state |
## 2. Minimal example
Package `research.queries.truth_why_not`: a reader is admitted to book issue when
registered and no debt is established (`not_known`).
```law
entity Reader {
label ru-KZ official "Читатель";
}
relation registered(r: Reader) kind empirical {
label ru-KZ official "зарегистрирован в библиотеке";
}
relation has_debt(r: Reader) kind empirical {
label ru-KZ official "имеет задолженность";
}
relation may_borrow(r: Reader) kind institutional {
label ru-KZ official "допускается к выдаче книг";
}
rule BorrowAllowed strict {
label ru-KZ official "Зарегистрированный читатель без задолженности допускается к выдаче книг";
for r: Reader;
when registered(r);
when not_known(has_debt(r));
then may_borrow(r);
}
```
Case-01 facts: `registered(urn:case:research:queries:anna)`.
Query: `evaluate truth(may_borrow(...))`.
Case-02 facts: plus `has_debt(...)` of the same reader.
Queries: `evaluate truth(...)`, then `evaluate why_not(...)`.
Observed engine answer (installed `law`, semantics `law.core/0.2`):
```text
law test research.queries.truth_why_not: мир research.queries.truth_why_not
ok [research.queries.truth_why_not#authored] tests/01-borrow-granted.lawtest / urn:query:research-queries-01
ok [research.queries.truth_why_not#authored] tests/02-borrow-blocked.lawtest / urn:query:research-queries-02
итого: 2 проверено, 2 прошли, 0 не прошли, 0 не исполнены; код 0
```
`law engine check` — `check OK`, no warnings. Case-02 expectations:
`truth` — `PROPOSITION` / `NEITHER` / `COMPUTED`; `why_not` — `GRAPH` /
`NEITHER` / `COMPUTED` plus the `blocked_by(BorrowAllowed)` observation.
Sensitivity: cases 01 and 02 differ by exactly one `has_debt` fact —
it flips `truth` from `TRUE_ONLY` to `NEITHER` and lights the rule blocker in
`why_not`. The example is not vacuous: removing `registered` from case 01 also
gives `NEITHER` (premise not established).
Nearest wrong outcome (caught during this research before the fix):
a `when not has_debt(r)` premise instead of `when not_known(has_debt(r))`
gives `NEITHER` even in case 01 — strict `not` requires a refutation,
and a missing fact is not a refutation. The "unless otherwise established"
form is only `not_known`.
## 3. Example by domain
- **Law:** Kazakh Civil Code art. 10 (package `kz.corpus.civilcode`, Kazakhstan) — `evaluate positions()` expecting `position(ObjedinenieVObshchestvennyeOrganizatsiiPotrebiteley, ACTIVE)` (see `corpus-forms.md`).
- **Law:** Uzbek Civil Code (package `uz.civil_code`, Uzbekistan) — a `truth` `TRUE_ONLY`/`NEITHER` pair on one predicate (positive/negative with one line of `result_kind`/`truth_status`/`evaluation_status` expectations).
- **Duty and collection:** `research.queries.collect_positions` — the same device in miniature: `collect t: Text where overdue_title(...)` (`COLLECTION`, `collected_count(2)` vs `collected_count(0)`) and `positions()` (`NORM_POSITION`, `position(ReturnOnTime, ACTIVE)` vs `position_count(ReturnOnTime, ACTIVE, 0)`). Observed engine answer:
```text
law test research.queries.collect_positions: мир research.queries.collect_positions
ok [research.queries.collect_positions#authored] tests/01-overdue-collected.lawtest / urn:query:research-queries-c1
ok [research.queries.collect_positions#authored] tests/02-nothing-overdue.lawtest / urn:query:research-queries-c2
ok [research.queries.collect_positions#authored] tests/03-duty-active.lawtest / urn:query:research-queries-c3
ok [research.queries.collect_positions#authored] tests/04-no-member.lawtest / urn:query:research-queries-c4
итого: 4 проверено, 4 прошли, 0 не прошли, 0 не исполнены; код 0
```
`law engine check` — `check OK`. Double sensitivity: `overdue_title` facts flip
`collected_count(2)` to `collected_count(0)` (cases c1/c2), the
`member_enrolled` fact flips `position(ReturnOnTime, ACTIVE)` to the `0` counter
(cases c3/c4).
- **Non-firing explanation:** the blocked-borrow scenario test — `why_not` with `blocked_by(BorrowAllowed)`; a conformance scenario with the same `GRAPH`/`NEITHER`/`COMPUTED` shape.
## 4. Writing questions in `.lawtest`
A question is written as `evaluate <kind>(…);`, followed by `expect` expectations
on the nearest preceding query: one test may hold several "question —
expectations" pairs (the specimen is case 02 of the `truth-why-not`
example, where `truth` with three `expect` is followed by `why_not` with four).
The test program is presented as a `[[tests]]` set via `world`; the case inside
a test is the `given` body: a `context` with time axes and `assert` facts
with `origin`.
The observation dictionary is closed and grows additively: `result_kind`, `truth_status`, `evaluation_status`,
`applicability_status`, `normative_status`, `applied(R)` / `any_applied(R)`,
`blocked_by(R)` (on a transition blocker — with the step reason: `attempt`,
`from`, `on`, `guard`, `requires`, `join`), `position(T, S)`,
`position_count(T, S, n)`, `collected_count(n)`, `collected(…)` /
`collected_exactly(…)`, `judgment_requests(n)`, `conflicts(n)` with the
`conflict_*` family, `certificate(…)`, `refused(CODE)` (on a refusal the other
expectations of the same step are unsatisfiable by construction).
A standalone question to a case package outside a test carries an explicit `queryId` in the case-package namespace
(`<namespace>#case/<Case>/query/<name>`); a missing ID, another namespace
or an explicit-ID vs completed mismatch is a preparation refusal `LDC-E1356`.
In `.lawtest` the ID derives from the test header, so two forms of one
question (JSON and the `evaluate` string) give one canonical `query`.
## 5. How the engine answers
Table — observed runs of this directory's examples (installed `law`,
semantics `law.core/0.2`):
| Facts | Question | Answer | Why |
|---|---|---|---|
| `registered` | `truth(may_borrow)` | `PROPOSITION` / `TRUE_ONLY` / `COMPUTED` | the `not_known` rule fired |
| `registered` + `has_debt` | `truth(may_borrow)` | `PROPOSITION` / `NEITHER` / `COMPUTED` | `not_known` false, the rule stays silent |
| same | `why_not(may_borrow)` | `GRAPH` / `NEITHER` / `COMPUTED`, `blocked_by(BorrowAllowed)` | root classified as `truth`, the blocker is the rule |
| 2 `overdue_title` facts | `collect t: Text where …` | `COLLECTION` / `COMPUTED`, `collected_count(2)` | the generator listed both values |
| no `overdue_title` facts | same `collect` | `COLLECTION` / `COMPUTED`, `collected_count(0)` | an empty collection is also an answer, not a refusal |
| `member_enrolled` | `positions()` | `NORM_POSITION` / `COMPUTED`, `position(ReturnOnTime, ACTIVE)` | the duty concluded by a rule |
| no facts | `positions()` | `NORM_POSITION` / `COMPUTED`, `0` counter | no positions, same result kind |
| no facts (pure computation) | `evaluate -7 mod 3` | `DATA` / `COMPUTED`, `value == 2` | a term asked directly, with no case (earlier "euclidean remainder" scenario) |
- `why_not` classifies the goal the same as `truth` on the same case: the same
`truthStatus`, status, `judgmentRequests`, `missingInputs`;
next to `blockers` on a candidate term error lies `termErrors`.
- A candidate-rule term error with `NEITHER` on `truth`/`why_not` raises
`evaluation_status` up the status ladder (`MISSING_INPUT`, `MISSING_POLICY`,
`NON_EXECUTABLE`, …, otherwise `RUNTIME_ERROR`); `positions`/`collect` do not
carry a term error in their status — a named but unclosed boundary.
- A missing dimension is absent, not filled with a fictitious `NEITHER`.
A runtime result mismatching the `query`-declaration contract is a
conformance error.
- Term-computation refusals use the `issue(CODE)` observation: `1000 KZT +
5 USD` → `issue(CURRENCY_MISMATCH)`, `7 mod 0` → `issue(DIVISION_BY_ZERO)`,
a sum over an empty collection → `RUNTIME_ERROR` (`EMPTY_AGGREGATE`: an empty
sum is not zero).
- A question outside the question schema and a wrong-arity question are
rejected BEFORE computation with a `QUERY_INVALID` error object; literal values are read by the value reader, refusal — the same
`QUERY_INVALID`.
## 6. Common mistakes
1. `when not P` instead of `when not_known(P)` for "unless established" — silent
`NEITHER` instead of a conclusion (in detail — `pitfalls.md`, item 1; caught
by run).
2. Wrong-arity question — refusal `QUERY_INVALID`, not `NEITHER` (`pitfalls.md`,
item 2).
3. `focused_truth` in 0.2 — refusal `FOCUS_UNSUPPORTED_SEMANTICS` without a
result (`pitfalls.md`, item 3; `boundaries.md`).
4. Two-argument `position_count` — `LDC-E1316`, three needed (`pitfalls.md`,
item 4; caught by run).
5. Expecting `truth_status` on `positions()`/`collect` — these kinds have none
(`pitfalls.md`, item 5).
6. Uppercase `bearer`/`holder` — the examples use only
lowercase keywords (`pitfalls.md`, item 6).
## 7. References
- Tutorial: [writing tests](/tutorials/writing-tests/).