eyeon Specification
Document: eyeon-spec-0001
Category: Language Specification
Language version: 0.0.8
Date: 2026-10-18
Table of contents
- Status of This Memo
- 1. Conventions and conformance
- 2. Program model
- 3. Lexical conventions and terms
- 4. Clauses and binding
- 5. Builtins and arithmetic
- 6. Inference and negation
- 7. Queries and aggregation
- 8. Explanations
- 9. Checks
- 10. Errors and command-line interface
Status of This Memo
This project specification is not an IETF RFC. It defines the subset of Prolog implemented by eyeon. It does not claim full ISO Prolog conformance or compatibility with every Prolog program.
1. Conventions and conformance
MUST, MUST NOT, SHOULD, and MAY express the requirement levels of
RFC 2119 and
RFC 8174.
A conforming implementation MUST implement the syntax and behavior below.
Answer order and the first selected successful derivation are unspecified.
A ground term contains no variables. A predicate is identified by its atom name
and arity; parent/1 and parent/2 are distinct predicates.
2. Program model
Source files are UTF-8 and conventionally use .pl. Programs use these forms:
human(socrates). % ground fact
mortal(Person) :- human(Person). % clause
?- mortal(Who). % query
?- human(X), mortal(X). % conjunction
?- why. % explain previous answers
?- why(mortal(socrates)). % explain a goal
:- assertion(mortal(socrates)). % executable check
:- table fibonacci/2, ackermann/2. % tabled predicate declarations
All statements MUST end in ., followed by whitespace, a comment, or end of
file. The interpreter MUST parse the entire source, load all facts, complete
requested inference, answer queries in source order, and evaluate checks in
source order. A query MAY use later facts or clauses.
Checks MUST NOT assert facts. Queries and checks MUST request the backward
computation they need. why follow-ups refer to the preceding ordinary query,
even when checks or other follow-ups intervene.
why/0, why/1, and assertion/1 have the special
top-level meanings described here; they are not ordinary user-defined control
predicates.
why MAY wrap a parenthesized conjunction. Directives other than table and
assertion MUST be rejected. Query forms belong in program files in this
interpreter; other Prolog systems may require entering them at a top level.
3. Lexical conventions and terms
Names are case-sensitive. Unquoted atoms match [a-z][A-Za-z0-9_]*.
Variables match [A-Z_][A-Za-z0-9_]*. A variable’s scope is one clause, query,
or assertion. Repeating a variable name MUST require the same term. Every _
MUST be a fresh variable. Names beginning with _ MUST remain shared when
repeated, but MUST be omitted from displayed query bindings.
Single quotes delimit atoms, including Unicode names and punctuation:
'red-fox', 'Élodie', '7'. Quoted and unquoted spellings of the same atom
MUST unify. Double quotes delimit strings, which MUST remain distinct from
atoms and numbers. Quotes MAY be doubled inside the corresponding quoted
value. Supported backslash escapes are \n, \r, \t, \b, \f, \\,
\', \", and Unicode \uXXXX / \UXXXXXXXX.
Comments start with % and run to the end of the line, or use /* ... */.
Block comments are not nested. Quoted comment markers are literal content.
Comments and multiline terms MUST preserve diagnostic source lines.
Integers are decimal digits with an optional unary sign and MUST be exact,
using arbitrary-precision arithmetic limited by available resources. Reals
have a decimal fraction or exponent and MUST use finite binary64 values.
Integers and reals are distinct terms: 1 = 1.0 fails, while 1 =:= 1.0
succeeds. Real overflow and non-finite arithmetic MUST raise an error.
A term MAY be an atom, integer, real, string, variable, compound term such as
point(2, 3), or list. Compounds have a fixed atom functor and one or more
arguments. Arity-zero predicates use a bare atom. Lists use [], [a, b],
and [Head | Tail]; tails MUST be lists or variables. Improper lists are
outside this subset. Nested compounds and lists MUST match structurally.
The quoted constructor '.'(Head, Tail) is equivalent to [Head | Tail]
and has the same tail restrictions. The empty list [] is distinct from
the quoted atom '[]'.
Unification MUST perform an occurs check and MUST NOT create cyclic terms.
Ground facts are loaded directly. Unit clauses containing variables, such as
append([], List, List)., MUST act as demand-driven clauses with an empty
body; their calls MUST supply enough bindings to produce ground answers.
Compound arithmetic syntax in a fact is an ordinary compound term and MUST NOT
be implicitly evaluated.
4. Clauses and binding
A clause has one ordinary predicate head and one or more body goals, separated
by commas. Goals MAY be grouped in parentheses. Multiple clauses for one
predicate provide alternative derivations. There is no separate forward-rule
syntax. Arithmetic expressions in ordinary arguments remain terms; use an
explicit is/2 step to compute an argument or a result:
:- table fibonacci/2.
fibonacci(0, 0).
fibonacci(1, 1).
fibonacci(N, Value) :-
N > 1,
PreviousIndex is N - 1,
EarlierIndex is N - 2,
fibonacci(PreviousIndex, Previous),
fibonacci(EarlierIndex, Earlier),
Value is Previous + Earlier.
Ordinary positive goals bind variables by structural matching. Left = Right
unifies terms. Left is Expression evaluates the right side and unifies its
numeric result with the left. Arithmetic inputs MUST be bound at that point;
computations MUST NOT solve backward. A successful materialized head MUST be
ground; calls that leave output variables unresolved MUST raise an error.
5. Builtins and arithmetic
eyeon provides 20 builtin predicates, 39 arithmetic operations, and
two arithmetic constants. The operations comprise 18 unary/binary operator
forms and 22 named functions; xor/2 appears in both groups and is counted once.
Special forms are listed separately below.
Arithmetic expressions are data until evaluated by is/2, a numeric comparison,
or an aggregate’s numeric template. An arithmetic function is not a callable
goal: sqrt(9) alone does not compute a square root.
5.1. Builtin predicates and special forms
The following builtin predicates MAY appear in clause bodies, queries, and assertions. Inputs described as bound MUST have their required values when the goal is reached. These predicates MUST NOT be redefined by facts or clauses.
| Predicate | Syntax and behavior |
|---|---|
true/0 |
true always succeeds, without binding variables. |
false/0 |
false always fails. |
=/2 |
Left = Right unifies terms structurally, with an occurs check. Either operand may contain variables. |
\=/2 |
Left \= Right succeeds if the terms cannot unify at this point. Trial bindings MUST be discarded. It does not constrain later bindings: X \= a fails while X is free. |
is/2 |
Value is Expression evaluates the bound expression and unifies the numeric result with Value. A bound result must have the same numeric term type and value. |
</2, =</2, >/2, >=/2 |
Numeric less than, less than or equal, greater than, and greater than or equal. Both expressions are evaluated. |
=:=/2, =\=/2 |
Numeric equality and inequality. Both expressions are evaluated; 1 =:= 1.0 succeeds. |
@</2, @=</2, @>/2, @>=/2 |
Lexicographic less than, less than or equal, greater than, and greater than or equal. Both operands MUST be bound atoms, or both MUST be bound strings. Ordering compares UTF-16 code units; it does not perform locale collation or general term ordering. |
contains_text/2 |
contains_text(Text, Part) requires two bound strings. It lowercases both using Unicode default case conversion, then tests literal substring containment. No Unicode normalization is performed. An empty Part succeeds. |
atom_codes/2 |
atom_codes(Atom, Codes) relates an atom to its list of Unicode scalar values as integers. A bound atom produces codes; otherwise a bound proper list of codes produces an atom. |
string_codes/2 |
string_codes(String, Codes) provides the same conversion for strings, preserving their distinction from atoms. |
=../2 |
Term =.. Parts decomposes a nonvariable term into a list of its functor and arguments, or constructs a term from such a list. It does not evaluate or call the constructed term. |
aggregate_all/3 |
aggregate_all(Template, Goal, Result) computes count, sum(Value), min(Value), max(Value), or list(Term) over completed, distinct producer tuples. See Section 7 for binding, scope, and empty-result rules. |
For the two text conversions, a bound first argument MUST have the specified
text type. Its codes are unified with the second argument, which MAY contain
variables. If the first argument is a variable, the second MUST be a ground
proper list of integers from 0 through 1114111, excluding surrogate values
55296 through 57343. Text containing an unpaired surrogate MUST raise an
error. Supplementary characters occupy one code; empty text corresponds to
[]. Conversion MUST preserve code points without Unicode normalization.
Invalid decoding inputs and calls with neither required input supplied MUST
raise an error; a valid conversion with an incompatible output simply fails.
For =../2, a compound decomposes to [Functor | Arguments], for example
point(2, 3) =.. [point, 2, 3]. An atom, number, or string decomposes to a
singleton list containing itself. The empty list decomposes to [[]];
a nonempty list decomposes to ['.', Head, Tail]. In the construction
direction, Parts MUST have a known, nonempty, proper list spine. A
singleton MUST contain a bound atom, number, string, or []; a longer list
MUST begin with a bound atom functor. Arguments MAY contain variables that
are bound by subsequent goals. Constructing '.'/2 MUST obey the list-tail
restrictions in Section 3. Neither direction introduces cyclic terms, and
displayed answers MUST still be ground. Missing inputs or invalid parts
MUST raise an error; incompatible terms and parts fail by unification.
The following special forms complete the language’s built-in facilities; they are not additional ordinary predicates:
| Form | Context and meaning |
|---|---|
\+ Goal |
Negation of one goal, with the completion and binding requirements in Section 6. |
?- why. |
Explain the preceding ordinary query’s answers (Section 8). |
?- why(Goal). |
Query and explain Goal, which may be a conjunction (Section 8). |
:- assertion(Goal). |
Require a successful solution of Goal (Section 9). |
:- table Name/Arity, ... . |
Declare ordinary predicates demand-driven and tabled (Section 6). |
Conjunction with , and parenthesized goal groups are supported syntax.
The predicates above introduce no mutation or I/O effects. A failed test
produces no solution; an invalid arithmetic input or an unmet input requirement
MUST raise an error rather than silently failing.
5.2. Arithmetic operators
The following table is exhaustive. Rows run from lower to higher precedence;
operators on the same row share precedence. Parentheses override precedence.
All operands MUST evaluate to numbers; the integer-only operations reject reals,
even those with an integral value such as 3.0.
| Binary operators | Associativity | Meaning |
|---|---|---|
\/, xor, /\, <<, >> |
Left | Integer bitwise OR, XOR, AND, left shift, right shift. |
+, - |
Left | Addition and subtraction. |
*, /, //, div, rem, mod |
Left | Multiplication, real division, truncating integer quotient, flooring integer quotient, truncating remainder, flooring remainder. |
**, ^ |
** non-associative; ^ right |
Real power; power preserving exact integers for integer operands and a nonnegative exponent. |
Unary + preserves its operand; unary - negates it. Unary \ is integer
bitwise complement. Unary operators bind more tightly than multiplication and
less tightly than powers: -2^2 evaluates to -4.
Addition, subtraction, and multiplication of two integers MUST be exact.
Unary signs MUST preserve the operand’s numeric type. / and ** MUST produce
reals. ^ MUST produce an exact integer for integer operands and a nonnegative
exponent, and a real otherwise. Other mixed integer/real arithmetic MUST convert
its operands to binary64; conversion and results MUST remain finite.
//, div, rem, mod, and bit operations MUST require integers.
// truncates toward zero; div floors. rem has the dividend’s sign;
mod has the divisor’s sign. With -7 and 3, the four results are -2,
-3, -1, and 2. Division or remainder by zero MUST raise an error.
Bit operations MUST use unbounded signed two’s-complement semantics.
Shift counts MUST be nonnegative; right shift preserves the sign.
5.3. Arithmetic functions and constants
Functions use ordinary argument syntax inside arithmetic expressions:
sqrt(9), min(A, B). The following table lists every supported function.
Arguments MUST evaluate to numbers, subject to the additional requirements
shown. Integer and real result types remain distinct terms.
| Function | Meaning and result |
|---|---|
abs/1 |
Absolute value, preserving the integer or real type. |
sign/1 |
Integer -1, 0, or 1 according to the argument’s sign. |
sqrt/1 |
Real square root of a nonnegative argument. |
log/1, exp/1 |
Real natural logarithm of a positive argument; real exponential. |
sin/1, cos/1, tan/1 |
Real trigonometric functions, with input angles in radians. |
asin/1, acos/1, atan/1 |
Real principal inverse trigonometric functions in radians. asin and acos require inputs in [-1, 1]; their result ranges are [-pi/2, pi/2] and [0, pi]. atan returns a value in [-pi/2, pi/2]. |
atan2/2 |
atan2(Y, X) returns the real signed angle of (X, Y) in [-pi, pi]; atan2(0, 0) returns 0.0. |
floor/1, ceiling/1 |
Integer rounding toward negative and positive infinity respectively. |
round/1, truncate/1 |
Integer rounding to nearest with halfway cases away from zero; integer rounding toward zero. |
float_integer_part/1 |
Real integral part, truncated toward zero. |
float_fractional_part/1 |
Real argument minus its truncated integral part. |
min/2, max/2 |
Smaller or larger operand, preserving its type and exact value; select the first on numeric equality. |
gcd/2 |
Nonnegative integer greatest common divisor of two integers, including gcd(0, 0) = 0. |
xor/2 |
Integer bitwise XOR, equivalent to the infix xor operator. |
pi and e evaluate to the binary64 approximations
3.141592653589793 and 2.718281828459045. Outside arithmetic evaluation they
remain ordinary atoms. Transcendental functions return binary64 approximations;
their last result bit MAY differ between implementations.
floor, ceiling, round, and truncate MUST retain every digit of an
integer input. float_integer_part and float_fractional_part convert integer
inputs to binary64 and MUST return reals. Numeric comparisons and min/max
MUST preserve an integer operand’s exact value rather than first rounding both
operands to binary64. Atoms, strings, and lists MUST NOT be coerced to numbers.
Non-finite conversions, overflow, and real function domain errors MUST raise
an error. Unsupported arithmetic functions MUST report their name and arity.
5.4. Ordinary predicates
Predicates outside the listed builtin indicators and special forms have no implicit implementation. Programs MAY define them with facts and clauses; without a matching definition, a ground call has no solution. Term-type tests and structural identity tests are not provided as builtins.
Integer ranges, list operations, and polar-angle normalization can be defined using ordinary clauses. For example, with bound integer endpoints:
:- table between/3.
between(Low, High, Low) :- Low =< High.
between(Low, High, Value) :-
Low < High,
Next is Low + 1,
between(Next, High, Value).
?- between(2, 4, Value).
This defines an ordinary between/3 predicate with answers 2, 3, and 4.
Its arithmetic inputs MUST be bound, as with any other clause using arithmetic.
6. Inference and negation
6.1. Herbrand semantics
eyeon MUST use
Herbrand semantics
for ordinary predicates, with the interpreted builtins specified in Section 5.
Ground terms denote themselves:
socrates denotes the atom socrates, and point(2, 3) denotes that compound
term. A functor constructs data; it does not implicitly call a function.
Distinct ground terms MUST remain distinct after the lexical normalization
and structural matching described in Section 3. For example, socrates and
plato are different values, while socrates and 'socrates' are the same
atom. Asserting a relation between two terms does not make them identical.
The Herbrand universe is the set of finite ground terms constructible from
the language’s constants and constructors, including its supported numeric
values, strings, and proper lists. Variables range over these terms. The
Herbrand base is the set of all ordinary predicate applications to ground
terms, such as human(socrates). These applications are called ground atoms;
here, “atom” means a proposition, distinct from an atom used as a term.
A Herbrand interpretation specifies which ground atoms are true.
For positive clauses, the program’s meaning MUST be the least Herbrand
model, relative to the specified builtins: the smallest set of ground atoms
containing the facts and closed under rule application. Clause variables are
universally quantified: whenever a ground instance of a rule’s body holds,
its head holds. Thus the facts and clause in Section 2 make
human(socrates) and mortal(socrates) true. A query returns ground bindings
that make its goals true, subject to the input requirements of this subset.
Arithmetic builtins have their specified numeric interpretation. For example,
1 + 2 remains a compound term in an ordinary argument, while Value is 1 + 2
binds Value to the integer 3. 1 = 1.0 fails because the terms differ;
1 =:= 1.0 succeeds by numeric comparison. Stratified negation extends the
positive semantics by testing absence only after the relevant producers
complete, as specified below. The universe can be infinite; these semantics
do not require enumerating it or guarantee that evaluation terminates.
6.2. Evaluation and negation
Facts and answers have set semantics. Every distinct ground tuple MUST be stored at most once, regardless of the number of derivations. Positive recursion MUST reach a least fixed point when its domain is finite.
The implementation evaluates range-restricted clauses forward: each head
variable and each builtin input can be bound from preceding positive goals.
It MUST evaluate other clauses using demand-driven tables. A table declaration
MUST make every clause of the declared predicate demand-driven, regardless of
where the declaration appears. Declarations are optional for clauses already
requiring inputs, and useful to prevent eager enumeration of recursive list
constructors. All demand-driven calls MUST be tabled. Calls differing only in
variable spelling MUST share evaluation, preserving repeated-variable
constraints. Recursive consumers MUST suspend and resume as answers arrive,
without requiring an unbounded chain of nested calls.
Tables MUST be local to each run.
This execution model differs from conventional Prolog depth-first search: clause order MUST NOT change the answer set for terminating positive programs whose calls supply required inputs. Answer order, duplicate derivations, cut, and procedural side effects are not provided.
\+ Goal holds when no matching ground positive answer exists after its
producers complete. Negative ordinary goals MUST be ground when reached.
Negation of a conjunction and nested negation are outside this subset; define
a helper predicate. Negated tests MUST NOT introduce bindings. Negated is/2
and unification require bound operands, as do negated atom_codes/2,
string_codes/2, and =../2; negated aggregate goals require a
helper predicate.
The implementation MUST reject dependency cycles through negation or aggregation. A negative consumer MUST run after its producer strata finish. Dependency analysis distinguishes predicates by name/arity and excludes producer heads with incompatible atomic argument constants; other argument constraints MAY be treated conservatively. Arithmetic tests have no fact producers. Absence means no evidence in this completed program, not a claim about the world.
No semantic depth limit is defined. Arithmetic, compound construction, or expanding call patterns can create infinite computation. Tabling MUST NOT be described as guaranteeing termination for arbitrary programs. The playground MUST allow cancellation by terminating its computation worker.
7. Queries and aggregation
A query MUST return all distinct tuples of its displayed variables, once each.
Variables beginning with _ are existentially projected out. Successful queries
with no displayed variables yield true.; unsuccessful queries yield false..
false. describes query failure, not an explicitly asserted negative fact.
Conjunctions share bindings and process goals from left to right.
?- aggregate_all(count, mortal(Person), Count).
?- aggregate_all(sum(Value), score(Person, Value), Total).
?- aggregate_all(min(Value), score(Person, Value), Minimum).
?- aggregate_all(max(Value), score(Person, Value), Maximum).
?- aggregate_all(list(result(Person, Value)), score(Person, Value), Results).
aggregate_all/3 MAY appear in a body, query, or assertion. Its goal MAY be a
parenthesized conjunction. It MUST complete all matching producer work before
aggregating, including demand-driven definitions. A bound result constrains the
computed answer. Existing outer bindings MAY constrain the producer call;
local producer variables MUST NOT escape into the caller.
Aggregation operates on distinct tuples of all producer variables under the set semantics of this interpreter. It does not count repeated proof paths. Equal values from distinct subjects contribute separately. Empty counts and sums yield integer zero; empty minima and maxima fail. Counts and integer sums MUST be exact; sums and extrema also support finite real arithmetic. A producer that requires unavailable input bindings MUST report an error.
list(Term) MUST collect one instance of Term per distinct producer tuple,
without evaluating arithmetic expressions inside that term. Elements MAY be
atoms, strings, numbers, compounds, or lists, but MUST be ground. Empty
collections MUST yield []. Equal projected terms from different producer
tuples MUST remain repeated list elements; repeated facts or proof paths MUST
NOT add elements. List order is unspecified. A supplied result or list pattern
MUST unify with the computed list; it MUST NOT bind producer variables or
filter the producer before collection. Unlike Prolog’s findall/3, this
operation does not collect nonground answers or repeated derivations.
8. Explanations
?- why. MUST explain all answers of the preceding non-follow-up query.
Consecutive follow-ups MUST retain that context. Without a previous query,
it MUST prompt for a query; after an unsuccessful query, it MUST report that
there is no successful answer to explain. ?- why(Goal). MUST explain all
successful answers to that goal and MUST become the current context.
Every derived tuple MUST retain a successful rule and its supporting evidence. Supplied facts, completed absence searches, computed tests, and aggregates MUST be identified separately. All successful explanations MUST use one numbered format: each rule is listed once, and each step is shown once before the steps that use it. This includes explanations of single supplied facts. Derived steps MUST identify their source clause, actual supporting goals, and local variable bindings when present. Unit clauses have no supporting steps. Ordinary predicate steps MUST show actual ground terms, and arithmetic evidence MUST include concrete operands and results. Explanations MUST NOT silently omit steps or numeric digits. Explanations MUST use the proof certificate format below. There is one certificate per run, shared across all requested successful explanations. It records one selected derivation per goal. Repeated follow-ups MUST NOT repeat claims or steps. The Reason section is the certificate; the Check section MUST include its verification report after the program’s assertions.
8.1. Proof certificates
Certificate generation MUST record successful instances of queries selected
by why, including explicit why(Goal) queries. A why follow-up MUST refer
to the preceding query rather than introduce another claim. Generation MUST first complete inference and validate the
program’s assertions. Assertions are not automatically included as certificate
claims. Without a requested successful explanation, no certificate or proof
check report is emitted; the existing empty or unsuccessful explanation
message applies. Non-certificate messages in Reason MUST be comments so that
a successful certificate can be extracted and checked as Prolog data.
A version 1 certificate MUST contain only Prolog data records:
certificate(eyeon, 1).
claim(query(1), [mortal(socrates)]).
clause(1, human(socrates), []).
clause(2, mortal(var('Person')), [human(var('Person'))]).
% step 1
step(human(socrates), fact(1), [], []).
% step 2
step(mortal(socrates), rule(2), ['='('Person', socrates)], [human(socrates)]).
There MUST be exactly one certificate(eyeon, 1) header. Each
claim(Request, Goals) identifies a nonempty proper list of successful goals.
query(N) refers to the Nth source query, counting explanation follow-ups;
check(N) MAY identify the Nth source assertion in a manually supplied
certificate. The claimed conjunction MUST preserve variable sharing in that
request and ground its displayed variables. Claims MUST NOT represent false.
query results or assert that every answer has been found.
Source facts and clauses MUST be numbered together in source order, starting
at 1. Queries, directives, and generated helper rules MUST NOT receive source
clause numbers. Optional clause(Number, Head, Body) display records MUST
agree with the source. Body is a list; source variables appear as
var('Name'), and open list templates as list(Items, Tail). These display
records MUST NOT supply authority for checking.
Each step(Goal, By, Bindings, Uses) supplies one justification for a goal.
Bindings is a proper list of '='('SourceVariable', Value) pairs with
distinct variable names. Uses is a proper list of recorded premises.
Ordinary predicate steps MUST be ground. fact(N) cites a source unit clause;
rule(N) cites a source clause and its body instance. builtin identifies a
pure builtin whose result MUST be recomputed. absent identifies a ground
negated ordinary goal and MUST have no bindings or uses.
An aggregate goal is represented as data with its producer goals in a list,
for example aggregate_all(sum(V), [score(P, V)], 9). Its justification is
collected, its bindings MUST be empty, and each member of Uses MUST be a
list containing one successful instance of the producer conjunction. Those
instances MUST have supporting steps and distinct producer-variable tuples.
The checker MUST recompute the aggregate from the recorded tuples.
Completeness of that collection remains a trust obligation.
Generated steps MUST appear after their supporting steps, with sequential
% step N labels. These labels aid reading and are not authority for checking.
Certificate variables are local to a record and MAY be consistently renamed.
Goal identity MUST preserve variable sharing while ignoring variable spelling.
Arithmetic compounds MUST be written in explicit function notation, such as
'**'(-3.0, 2.0), to preserve their parsed structure. The certificate MUST
retain full numeric values and the complete reachable derivation.
8.2. Independent checking
The checker MUST follow the recorded premises without invoking the inference engine, searching source rule bodies for missing proofs, or executing source assertions. It MAY share parsing, term operations, and pure builtin arithmetic with the reasoner; those facilities form part of its trusted implementation. Every generated certificate MUST pass checking before it is returned.
Every report MUST identify all seven conditions, their coverage counts, and their failure counts:
| Condition | Requirement |
|---|---|
| C1: resolution | Each cited step and its premises instantiate the original source clause under consistent bindings; display records agree with that source. |
| C2: well-foundedness | The recorded dependency graph has no cycle. |
| C3: justification | The certificate has the supported shape and justification kinds, with no duplicate steps or claims. |
| C4: coverage | Each claim and each used premise has a recorded supporting step. |
| C5: re-decision | Pure builtin results and aggregate calculations agree with independent recomputation over the recorded inputs. |
| C6: boundary consistency | Absences and collections do not contradict source unit clauses or the evidence already in the certificate. |
| C7: relevance | Every claim answers its cited source request, and every step serves a claim. |
Negation of an ordinary predicate MUST remain an absent obligation: recorded
positive evidence cannot establish that the complete program has no answer.
Aggregate membership MUST be justified, but an omitted derivable tuple may be
invisible without further inference. Collection completeness MUST therefore
remain a collected obligation. C6 MUST detect direct contrary evidence and
missing collection tuples supported by available positive evidence and pure
builtin computation. It MUST NOT be described as proving completeness.
Reports MUST use ordinary Prolog data records: condition/4, failure/3,
obligation/3, counts, and verdict/1. Failure and obligation records SHOULD
retain the goal as a structured term, with variables and open lists encoded
as templates as in Section 8.1 so that the report remains ground data.
A valid certificate without
obligations yields verdict(checked). A valid certificate with obligations
yields verdict(checked_with_obligations). Invalid certificates yield
verdict(failed(Count)). Strict checking MUST reject all trust obligations.
A report’s valid field means that the recorded conditions passed under its
listed obligations; callers MUST inspect those obligations or request strict
checking when unconditional checking is required.
Checking establishes derivability of the recorded claims relative to the supplied source and interpreted builtins, subject to any listed obligations. It MUST NOT claim that source facts are true, that all answers were found, that unsuccessful queries have no answers, or that the shared parser and builtin implementation are independently verified. Certificates are not cryptographic signatures or source-authenticity guarantees.
9. Checks
:- assertion(Goal). MUST test the existence of a successful solution.
A parenthesized conjunction MAY use local variables and calculations. Checks
MUST run after query inference, in source order, without changing explanation
context. Certificate generation and checking MUST finish before output is
returned or printed. A successful check emits
check passed: Goal.. The first failed check MUST stop the run and identify its
source line with Check failed: Goal.. Runtime errors in assertions SHOULD
identify the assertion’s source line. Checks use the same inference engine;
programs SHOULD use independent expected values or mathematical identities.
10. Errors and command-line interface
For installation and invocation instructions, see the project introduction.
The CLI MUST accept exactly one program path or - for standard input. -h/--help
and -v/--version display usage and the version respectively.
Successful queries, including false., MUST exit zero. Errors MUST go to stderr
and produce a nonzero status. The CLI MUST prepare the complete transcript
before printing it, so failed checks produce no partial successful transcript.
The CLI and playground MUST group output under % Answer, % Reason, and
% Check, in that order. Answers and assertion results retain source order;
requested explanations share one certificate and one proof check report. In
% Answer, each result block MUST be preceded by its query in ?- Goal. form,
including results with no displayed variables and unsuccessful queries. Query
formatting MAY normalize whitespace and parentheses. Empty sections MUST
explain what was not requested. Section grouping MUST NOT change
preceding-query explanation context. Malformed or unfinished statements MUST
report a source line. Unsupported syntax and runtime operations MUST fail
explicitly. The earlier English-like syntax is not supported.