# datalogkit — a stratified Datalog engine, differentially verified Wizard: **null** · Lane: Codeforge (hard coding and agentic reasoning) `datalog.py` is a dependency-free Datalog engine: hand-written parser, static checks (arity, rule safety, stratification), Tarjan-SCC stratification, and **semi-naive** bottom-up evaluation with stratified negation. No imports beyond the standard library. ## Files datalog.py the engine (module + JSON/text CLI) test_datalog.py the verification suite — its exit code is the verdict evidence_run.py reproduces every number in EVIDENCE.txt examples/reach.dl CLI demo program EVIDENCE.txt captured output of the above (measured, not claimed) ## Run it python3 datalog.py examples/reach.dl --text --stats python3 datalog.py examples/reach.dl -q unreachable # JSON, one predicate python3 test_datalog.py # 0 = all checks pass ## Language % comment edge(a, b). % fact path(X, Y) :- edge(X, Y). % rule path(X, Y) :- edge(X, Z), path(Z, Y). unreach(X) :- node(X), not reach(X). % '!' is accepted as 'not' Terms: `?X` variable, `?_` anonymous (independent per occurrence), bare lowercase atom, `"quoted"` string, integer. Head and negated-literal variables must be bound by a positive body literal; a rule with no positive body literal is rejected. Negation must be stratified — a negated literal may not sit inside a recursive cycle. ## Verification (this is the point of the artifact) `test_datalog.py` = 40 tests, 0 failures. The load-bearing part is **differential testing against an independent oracle** written from scratch in the test file: stratification via reflexive-transitive reachability closure plus Kahn waves (the engine uses iterative Tarjan SCC), evaluation via cartesian-product naive fixpoint (the engine uses delta-driven semi-naive). The two share only the parser. | check | result | |---|---| | unittest suite | 40 tests, 0 failures, 0 errors | | random programs, engine vs oracle (seeds 20261024 / 20261025) | 600 programs, 61 of them unstratified — verdicts and 3234 predicate-relations identical, 0 mismatches | | fresh-seed re-run (evidence_run.py, seed 999) | 400 programs, 43 unstratified, 2142 relations, 0 mismatches | | semi-naive actually saves work (chain transitive closure, 12 nodes) | 858 vs 5687 body bindings enumerated — 6.6x; at n=20, 3990 vs 47291 — 11.9x | | KATs | transitive closure, non-linear same-generation recursion, stratified negation, anonymous vars in negation, zero-arity, repeated variables, order independence | Numbers above are read off `EVIDENCE.txt`, which `evidence_run.py` regenerates in one run. ## Bug this suite actually caught The first run of the differential test failed on 1 of 300 random programs: the engine derived `p0(c, c)` where the oracle derived nothing. Root cause: the join reorders positive body literals smallest-relation-first, so a literal like `p1(X, X)` can be joined *first*; the unifier bound `X` from the first argument into a private dict while checking the second argument against the *original* environment, so a repeated variable inside a single literal did not constrain both positions. Fixed in `_match` (bindings are now visible to later arguments of the same literal); the same latent flaw was removed from the oracle, and the exact failing program is now a regression KAT (`test_repeated_variable_inside_one_literal`). ## Stated limits (not hidden) * String constants compare as written — no unescaping, so two spellings of the same escaped string are distinct terms. * `-` is a token only as part of an integer literal; no arithmetic or builtins. * No functors, no aggregation, no `$ref`-style imports. * The engine reports `rounds`/`bindings`/`derived`/`rule_runs`; it is not tuned for large relations (semi-naive delta is tracked per predicate, joins are unindexed).