Books / Constructions / Chapter 4
Part I · Chapter 4
Why We Got This Answer
Contents of Constructions
We have already obtained several answers about the same water. With both abilities established, the model concluded that water was amphiprotic. When one piece of information was missing, the rule could not yield that conclusion. An explicit refutation of an ability gave a ground for a negative conclusion, while a conflict could stop both rules.
Now let us try explaining these differences to another person. Naming the state alone is not enough: we must show which information we used, how the answer followed from it, and where the rule itself came from. Start with the familiar positive case, then examine why the conclusion is missing in another case.
What the explanation shows
In scenario 14 both abilities of water are recorded. The explain
command from the second chapter shows the following answer. The
long rule-application identifier is shortened here with an
ellipsis:
the species is amphiprotic(h2o): TRUE_ONLY
rule_application the species is amphiprotic(h2o)
(urn:proof:apply:AmphiproticDonatesAndAccepts:f103e243…)
assertion the species donates a proton to another compound(h2o)
(urn:proof:assert:urn:openstax:clir:chemistry-acids#k14-1)
assertion the species accepts a proton(h2o)
(urn:proof:assert:urn:openstax:clir:chemistry-acids#k14-2)
Author’s paraphrase of the engine output above: water’s
amphiproticity is established (TRUE_ONLY) by applying the rule
AmphiproticDonatesAndAccepts to the two input entries — k14-1
about donating a proton and k14-2 about accepting one.
Read it bottom-up. Assertion k14-1 reports donating a proton,
k14-2 accepting one. They concern one object. Above is the
application of AmphiproticDonatesAndAccepts: the rule links these
abilities to amphiproticity. The first line shows the result,
TRUE_ONLY.
Such an analysis answers the question “what did this conclusion come from?” We can find the particular input entries and see the step from them to the conclusion. But another question remains: why does the model contain this rule?
Above its declaration we already saw this line:
@source(OS_14_1_AMPHIPROTIC)
This is a separate excerpt from the declaration, not a rule of its
own. It points at the fragment named OS_14_1_AMPHIPROTIC in the
same package’s sources.law file[^chapter-4-source]. Find it there:
the locator field records 14.1/amphiprotic, and the text field
holds the OpenStax Chemistry 2e paragraph about water’s two roles
and amphiprotic particles. We will examine the structure of
such entries in detail in chapter 12. For now it is enough
to find, via the reference, the text the rule rests on.
There are two linked checks. The explanation lets us retrace the conclusion from the information in the case. The rule’s source reference lets us compare the formal rule with the textbook text. The paragraph defining amphiproticity does not replace checking the trustworthiness of each input assertion. In our example the information about water is taken from the textbook description; in another case we would need to investigate the grounds for each input assertion.
How to ask about a conclusion that failed
Return to the case where only the ability to donate a proton
is known. The answer about amphiproticity will be NEITHER, as in
the second chapter. Now we care about the reason: what kept the
positive conclusion from coming about?
For that, there is the why_not question[^chapter-4-why]. It
stands in place of the familiar truth. Here is scenario 20
in full from the check set:
test "urn:query:openstax-chem-acids-20-book-water-why-not-without-acceptance" {
given {
context {
decision_time @2026-09-06T12:00:00Z;
knowledge_time @2026-09-06T12:00:00Z;
legal_time @2026-09-06;
timezone "UTC";
}
assert donates_proton(entity_ref("urn:case:chem:h2o")) { id "k20-1"; origin case_input; }
}
evaluate why_not(amphiprotic(entity_ref("urn:case:chem:h2o")));
expect truth_status == NEITHER;
expect blocked_by("AmphiproticDonatesAndAccepts");
}
In given one entry about water remains. The line
evaluate why_not(amphiprotic(…)) asks why
amphiproticity is not established. The name inside the file uses
an underscore: why_not.
The new expectation blocked_by("AmphiproticDonatesAndAccepts")
checks that an entry about the named rule is present in the
why_not result. Here the name is passed as a quoted string. That
check alone neither establishes that the rule failed to fire nor
checks the particular reason. It must be read together with the
expected answer status and the condition’s
explanation[^chapter-4-blocked].
First run the check over the whole set from the reader companion directory:
law engine test \
corpus/laws/org/openstax/chemistry-acids/tests/openstax-chemistry-acids.lawtest \
--program corpus/clir/openstax-chemistry-acids.lawir.json --imports \
contexts/openstax-chemistry-acids.json
Among the results there will be:
test PASS: urn:query:openstax-chem-acids-20-book-water-why-not-without-acceptance
Author’s paraphrase of the engine output above: the check of
scenario 20 passed — the NEITHER status and the mention of the
rule AmphiproticDonatesAndAccepts in the why_not result matched
the expectations.
To read the reason, make a copy of the check file,
water-why.lawtest, in the reader companion directory. Keep in it the three
opening language, package, namespace lines and only scenario
20. This is the same way of preparing a single scenario we used
for explain.
chapter4_why_not=$(mktemp -d)
python3 reader_tools/prepare_question.py water-why.lawtest \
--program corpus/clir/openstax-chemistry-acids.lawir.json --imports \
contexts/openstax-chemistry-acids.json --out "$chapter4_why_not"
law engine why-not "$chapter4_why_not"
The command’s name uses a hyphen: why-not. The command asks why
a positive conclusion is missing; it can also be applied to a single
scenario with a truth question. Below is the substantive part of
the answer. The closing section on kinds of diagnostics is omitted
here.
статус результата: NEITHER
блокираторов, подтверждённых документом, нет
не определено (1) — условие НЕ опровергнуто, вывода просто нет:
urn:openstax:clir:chemistry-acids#AmphiproticDonatesAndAccepts
правило a species capable of either donating or accepting protons is amphiprotic (14.1) не сработало, но условие НЕ опровергнуто: the species accepts a proton(h2o) — пара опор §62: NEITHER
Author’s paraphrase of the engine output above (the engine’s text
is in Russian and is quoted verbatim; what follows is the author’s
English retelling): the result status is NEITHER, with no
document-confirmed blockers. The rule
AmphiproticDonatesAndAccepts did not fire, but its condition was
not refuted either — the premise about water accepting a proton
has NEITHER support (no ground for or against, cf. §62), so the
conclusion is simply missing for lack of information.
The answer names the rule and the particular premise:
accepts_proton(h2o). Its state is NEITHER: no ground either for
or against accepting a proton. So the rule’s condition is not
established, but not refuted either.
Together, “no blockers” and “undetermined” mean that the condition
is not refuted; the conclusion is missing for lack of information. The
blocked_by expectation still passes because the analysis contains an
entry about the named rule. The name guarantees less than it may suggest:
finding the rule in the analysis does not establish that it was blocked,
or explain why it did or did not apply.
The §62 reference in the last line points at the specification’s
description of the four states. The familiar NEITHER suffices for
reading the example. The missing ground did not become a negation,
and the explanation pinned down which piece of information is
lacking.
What an extra entry changes
Now check the reverse situation: both needed abilities are already recorded, and we add one more piece of information. The vocabulary holds a relation:
pub relation conjugate_acid_of(acid: Species, base: Species) kind empirical {
label ru unofficial "первая частица — сопряжённая кислота второй";
label en unofficial "the first species is the conjugate acid of the second";
}
It takes two arguments of type Species: first the particle that
acts as the conjugate acid, then its base. The argument order sets
the meaning of the link. This is a small extension of the familiar
form: where a relation used to describe one particle, it now links
two.
In scenario 21 an assertion is added to water’s two abilities:
assert conjugate_acid_of(entity_ref("urn:case:chem:h3o"), entity_ref("urn:case:chem:h2o")) { id "k21-3"; origin case_input; }
It reports that hydronium h3o is the conjugate acid of water
h2o. This is a separate excerpt; the complete scenario lives in
the same check file. The question is still about water’s
amphiproticity.
The rule AmphiproticDonatesAndAccepts has two premises: donating
and accepting a proton. In this package there is no chain of rules
that would use the added link for an answer about amphiproticity.
So the result stays TRUE_ONLY.
Check scenario 21 and view its explanation the same way as scenario
14: keep only the needed block in the copy and call explain. The
explanation will hold assertions k21-1 and k21-2, while k21-3
will not appear. Deleting the third entry while keeping the rest
changes neither this scenario’s answer nor its explanation.
That observation concerns the particular question and model. In another model the link could turn out to be a premise of a separate rule. To call information extraneous, check whether it takes part — directly or through further conclusions — in obtaining the answer.
Where to look for an error in the entry
There is a change that looks smaller still. In a copy of scenario
14, replace only in the assertion about accepting a proton
urn:case:chem:h2o with urn:case:chem:h20: the last character
becomes a zero instead of the letter “o”.
To a reader, that is a typo. To the engine, it is a valid
identifier of a different object. Now donating belongs to h2o and
accepting to h20. The two premises no longer come together for water,
and the answer about its amphiproticity becomes NEITHER.
The old TRUE_ONLY and rule-application expectations will fail:
the check prints FAIL. Then the why-not command will point at
the unknown status of proton acceptance by h2o in particular. It will not say
that we mistyped the last character, nor offer to equate
the two names automatically. We discover the typo by matching the named premise
against the input entries.
So expectation and explanation help each other. The expectation
catches a mismatch with the intended result; the explanation
narrows the search to a particular premise. In this exercise the
identifier needs fixing. Simply replacing the expectation with
NEITHER would make the check pass but leave the wrongly recorded
case in place.

What a table or a program needs
Two abilities can be kept in a table, with the third property computed by a formula. In Python a short program suffices for that too. The question is which distinctions the chosen data representation keeps.
Consider a small self-contained Python example. Lines with >>>
are commands being entered; the following lines, the result.
Here one entry links a particle’s name to the value True:
>>> recorded = {"h2o": True}
>>> recorded.get("nh3", False)
False
>>> records = [("h2o", True), ("h2o", False)]
>>> dict(records)["h2o"]
False
In the first case ammonia is absent from the dictionary. The get
call returns the preselected default, False. Missing information
thus becomes indistinguishable from a recorded negation. In the
second case two entries about one name collapse into one: the
dictionary keeps the last value. A conflict is no longer visible in
such a representation.
Both effects follow from how the information is stored. In Python, support for an assertion and support against it can be kept separately to recover all four states. In a table, separate columns can be added for them. To reproduce this chapter’s explanation, the input entries’ names, information about applied rules, and source references are needed as well.
Arxo sets a shared way to link these elements: assertion states, rule-application conditions, and result explanations. The model’s author remains responsible for the choice of rules and input information. When a model is handed to another person, the shared conventions let them read the entry and check how the conclusion was reached without knowing how a particular program is built.
Try it yourself
In each exercise work with a separate copy holding the file header
and one scenario. Substitute the copy’s name in the commands; the
model path after --program stays the same. Try each exercise before
reading the discussion that follows.
- Take scenario 18 from the last chapter: accepting a proton is
recorded, while the question asks about donating. Call
why-not. How does the reason for thisNEITHERresult differ from the one in scenario 20? Is there a rule in the vocabulary that could derive donation of a proton? - Reproduce the typo in scenario 14. First check the old
expectations, then read
why-notand find the mismatching identifiers in the input entries. Fix the typo and make sure the old expectations hold again. - Delete only entry
k21-3from a copy of scenario 21. Compare the answer and the explanation before and after deletion. Name both premises that keep supporting the conclusion.
Check your predictions
In the first exercise the explanation will point out that this
program has no rule with donates_proton as its conclusion: the information
must come from the case. In scenario 20 there is a rule for the
sought property, but it lacks a premise. In the second exercise
fixing the name returns TRUE_ONLY and the rule’s application. In
the third, deleting the entry keeps both grounds and the old
answer.
We have walked the first small example from vocabulary to explanation. Now try telling someone who has not read the chapter about it: what information is set, what follows by rule, why a missing premise differs from a refuted one, and where to find the text the rule refers to.
In the next chapter the subject changes: instead of water’s properties, triangle side lengths appear. But start with the familiar question — whether the input conditions suffice for the conclusion.
[^chapter-4-source]: The model’s source entries, fragment OS_14_1_AMPHIPROTIC; its use — in the rule’s declaration.
[^chapter-4-why]: Arxo specification, §§183–185: explanation and why-not. A missing condition must not be declared false.
[^chapter-4-blocked]: Arxo specification, §267.4: expectation-observations. Example — scenario 20 in the complete check set.
Contents · PDF · EPUB · Original text under CC BY 4.0 · Source-material terms