Integrity

A rule that should never fire, and a clear alarm when it does.

integrity.py · output · proof · check · try it in the playground


The question

A bank keeps a simple promise: no account may have a negative balance. Alice has 30 in her account; Bob has −5.

A rule like that is an integrity constraint: not a way to derive new facts, but a condition the data must never break. If it is broken, we want to know at once, and know which record broke it.

Does the data keep the promise, and if not, who breaks it?


What we tell eyekind

Two accounts and the constraint:

fact(account('alice', 30))
fact(account('bob', -5))
contradiction(account(Owner, Balance), lt(Balance, 0))

What eyekind concludes

// Halted: a contradiction() holds; the facts derived until then, ending with 'false'.
'false'

The first line is a comment saying what follows: the run was halted because a contradiction holds, and it lists what was derived up to that moment, ending with 'false'. Here nothing else was derived first, so 'false' is the only line.

The program also stops with exit code 65. An exit code is the number a program hands back to whatever started it; 0 means “all fine”. eyekind uses 65 to say “a 'false' was derived: the data breaks a constraint”. A script or pipeline can notice that and stop.


The proof, line by line

Even an alarm comes with its reason. eyekind --proof writes it down; this is the complete proof:

// Halted: a contradiction() holds; the facts derived until then, ending with 'false'.
'false'

// Clauses: the program clauses the steps cite, by clause number.
clause(2, fact(account('bob', -5)))
clause(3, contradiction(account(Owner, Balance), lt(Balance, 0)))

// Steps: each goal, what justifies it (a clause, a builtin, a control, or an
// absence or collection taken on trust), the bindings and the goals it uses.
step('false', clause(3), {Owner: 'bob', Balance: -5}, [account('bob', -5), lt(-5, 0)])
step(account('bob', -5), clause(2), {}, [])
step(lt(-5, 0), 'builtin', {}, [])

It has three parts:

Read the steps as sentences:

  1. The data contradicts itself — by rule 3, with Owner being Bob and Balance being −5, because Bob’s account holds −5 and −5 is less than 0.
  2. Bob’s account holds −5 — fact 2; it uses nothing.
  3. −5 is less than 0 — 'builtin': a calculation, which anyone checking the proof simply redoes.

Checked: what the report says

A proof is only worth something if someone else can check it. eyekind -c generates the proof and checks it against the program; --check-proof checks a saved proof. The checker never reasons by itself: it only confirms each step against the program. This is the complete report:

// Checked answers: these follow from the input program.
'false'

// C1-C8: resolution, acyclicity, justification, coverage,
// re-decision, boundary consistency, relevance and witness integrity.
condition('C1', 'resolution', 'ok', 2)
condition('C2', 'well_founded', 'ok', 3)
condition('C3', 'justification', 'ok', 3)
condition('C4', 'coverage', 'ok', 3)
condition('C5', 're_decision', 'ok', 1)
condition('C6', 'boundary_consistency', 'ok', 0)
condition('C7', 'relevance', 'ok', 4)
condition('C8', 'witness_integrity', 'ok', 0)

// Proof statistics: recorded steps and how they were checked.
steps(3)
verified(2)
recomputed(1)
composed(0)
trusted(0)
claims(1)

// This verdict applies to the claims above; it does not establish completeness.
verdict('checked')

Each condition line names a check, says 'ok', and counts what it looked at:

The statistics repeat this: 3 steps, 2 verified against the program, 1 recomputed, none taken on trust. The verdict is checked: the alarm really follows from the data. The proof is sound; it is the data that is wrong. The verdict does not say Bob’s is the only bad account: eyekind stops at the first violation it finds.


Try it

eyekind examples/integrity.py; echo "exit code: $?"
eyekind --proof examples/integrity.py

Change Bob’s balance to fact(account('bob', 5)) and run again: eyekind prints only // No conclusions: the program derives no facts., and the exit code is 0. Make Alice’s balance −30 as well: the run still halts with 'false', but now the proof points at Alice, the first account eyekind checks; Bob’s case is never reached.


Takeaway

A good constraint fails loudly and explains itself. Here the run stops with a distinct exit code, and the checked proof points straight at Bob’s −5.