aboutsummaryrefslogblamecommitdiffstats
path: root/docs/core-design-and-pipeline.org
blob: eda5429fed150905974f500d23ebe026ed9a34f6 (plain) (tree)












































































































                                                                     


                                                                 
                                        
                                                               
                                                              



                                                                








                                                                   








                                                                    
#+TITLE: Core Design and Pipeline

* About

This document explains why Sol is built the way it is and what the
pipeline does to a program, stage by stage. It builds on the language
specification, on the decisions in the integration document, and on
the implementation in =lib/=, which realizes the first version of
every stage described here.

* Why Sol is not Turing complete

A database must answer. A query language with unbounded recursion or
unbounded search imports the halting problem into every question you
ask it, and the guarantee that RNT's execution core is named after
disappears. Sol refuses that trade at the language level, not by
convention.

The bet behind the refusal: the programs a database language actually
needs are total. Arithmetic, aggregation, structural recursion over
finite data, integrity checking, parsing, normalization, all of it
lives comfortably inside primitive recursion. What genuinely needs
partiality does not belong in the database; it belongs in the
application layer, and Karuta keeps it there.

* Where Sol sits among its ancestors

- From Prolog: relations as the unit of meaning, unification as the
  join mechanism, rules as definition. Removed: unbounded search,
  negation as failure over open goals, the cut, and every other
  operational leak that makes Prolog programs mean what the
  interpreter does instead of what the logic says.
- From Datalog: the discipline that queries over finite data
  terminate. Added: structured terms (records, sequences), real
  arithmetic with backward solving, and recursion beyond
  stratification, admitted per rule by a termination proof.
- From Coq: totality as an admission requirement, not a hope. A rule
  without a decrease witness has the same status as a non
  structurally recursive Fixpoint: it does not enter the system.
  Rocq later takes the metatheory itself (decision 10).
- From relational algebra: the finite first order fragment is plan
  shaped, and lowering to RNT's FOL core (Codd's theorem justifies
  the move) is a performance decision, never a semantic one
  (decision 9).

* Types are membership, membership is proof

Sol has no type layer beside the logic. =[Integer value: X]= is a
proposition, a constraint, and a typing judgment at once; a schema
criterion like =([Natural value: S] :and [LessThanEqual left: S
right: 100])= is a dependent refinement checked by the solver against
the same semantics every query uses. Foreign keys, range checks and
type checks are one mechanism, membership, applied at different
moments. Subtyping is proven inclusion (decision 12); values keep
one canonical shape so inclusion is literal, never coerced.

* The three modes of a relation

The implementation keeps the specification's central distinction
mechanical:

- membership :: decidable check of a ground tuple. Domains check
  against the host; stored relations scan; rule relations unfold.
- constraint :: a goal not yet decidable waits. The planner defers
  it, other goals bind its variables, and it runs when ground.
- generation :: only bounded forms may enumerate. =Between= is the
  first; every future generator must carry its bound in its
  arguments.

A goal that fits none of the three is a non total plan and the query
is rejected with the stuck goals named. Nothing diverges, nothing
silently enumerates an infinite domain.

* The pipeline

Source text moves through these stages, each one total:

1. lexing and parsing (=lib/lexer=, =lib/parser=): one uniform node
   category under the delimiter law. The grammar does not decide
   what a name is.
2. classification and resolution (=lib/world=): context decides.
   Names in term position become variables; blocks become
   applications; =Exists= lowers to conjunction; quantified
   variables rename apart, unforgeably.
3. admission (=lib/world=, =lib/program=): the gate where totality
   is enforced. Schemas register; facts must be ground, match their
   schema, satisfy primitive domains immediately and membership
   criteria against the complete extension; recursive rules must
   exhibit a structural decrease witness; mutual recursion is cut;
   every reference must resolve. A program that passes admission
   owns a world; one that does not, does not exist as a program.
4. planning and solving (=lib/vm=): dynamic goal selection over the
   admitted world. Selection is the plan; it reorders freely because
   conjunction is commutative, and it re-derives executability as
   bindings grow, which is what makes rule unfolding safe: the
   witness argument is ground and strictly shrinking at every
   unfolding.
5. reading answers: solutions are the query's free variables
   resolved to canonical terms.

Sakura's fuller pipeline (cardinality tags, join ordering, TAKE,
lowering to =Plan.t=) refines stages 3 and 4 without changing their
meaning; the =WORLD= functor is the seam it plugs into.

* Totality strategies, honestly ranked

What the implementation enforces today is rung one and part of rung
two of the ladder in decision 10:

- lexicographic structural decrease across call graph components,
  with every measure position demanded ground at unfolding; self
  recursion and mutual recursion are the same check;
- bounded enumeration through =Between=;
- decidable negation and universals over decidable quantifiers;
- residuation: open membership returns as residual constraints
  instead of failing the plan;
- interval propagation over residue: contradictions fail, finite
  integer ranges enumerate;
- symbolic discharge, first fragment: universals over infinite
  domains decided by kind inclusion, interval entailment, and the
  unit identities of the numeric relations, with refutations backed
  by counterexamples.

Not yet present: symbolic discharge beyond that fragment (richer
algebraic theories, arithmetic decrease measures) and Rocq
certificates as admission evidence. Each arrives as a strengthening
of admission or planning; none changes the semantics of what is
already admitted.

* What embedding means

The engine is a library over =WORLD= (decision 4). The standalone
binary is one host with an in-memory catalog and no transitions. RNT
is the other host: its catalog answers =find=, queries arrive as
compiled expressions through =World.expr_of_node=, and commit stays
the kernel's single effect outside the language. The embedding test
in =test/test_embedding.ml= is the working sketch of that shape.