# 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 (…);`, 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 (`#case//query/`); 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/).