Relational execution contracts

For compact dictionary-backed relation containers and optional Arrow exchange, see Compact relation batches and Arrow exchange. These do not add new evaluator semantics. The separate optional closure-only experiment is documented in Experimental Polars reasoning; it does not replace the default evaluator. For opt-in prepared SCC schedules with native witnesses and detailed indexed telemetry, see Reusable indexed reasoning plans. The original structural inspection API below remains available unchanged.

The additive gestaltdb.reasoning.relational module defines the gestaltdb-relational-v1 structural IR. It is independent of storage and optional dependencies. RelationSchema describes ordered, uniquely named, non-null entity columns with set semantics. RelationalAtom and RelationalRule support positive arities, constant relation names and range-restricted single heads. Strings, including "?x", remain constants; use Variable for variables.

This is a compilation boundary, not an n-ary execution API. The existing DatalogEngine still consumes binary Program values. Literal columns, negation, aggregates, physical operators and SCC scheduling are not introduced by this contract. Unknown representations fail explicitly rather than approximate.

Structural inspection

from gestaltdb.reasoning import Atom, Program, Rule, Variable
from gestaltdb.reasoning.protocols import InputContext
from gestaltdb.reasoning.relational import (
    CompilationIdentity, RelationalProgram, explain_plan,
)

x, y = Variable("x"), Variable("y")
program = Program([Rule("copy", [Atom(x, "parent", y)], Atom(x, "ancestor", y))])
ir = RelationalProgram.from_program(program)
# Supplied by an already validated adapter; strings alone authenticate nothing.
context = InputContext("example-horizon", "default", "ontology-v1", "policy-v1", "mapping-v1")
identity = CompilationIdentity(
    program.digest, context, "dictionary-generation-1",
    "positive-binary-entity-v1", "indexed-python-v1",
)
description = explain_plan(program, identity=identity)
assert description["kind"] == "structural-only"
assert description["rules"][0]["body"][0]["actual_rows"] is None
assert description["relational_digest"] == ir.digest
identity.require_compatible(identity)

explain_plan returns detached JSON-safe presentation data, not executable or persisted plan state. Positions refer to original body occurrences, including duplicates. Every occurrence can serve as a delta pivot; actual applicability and join order depend on runtime bindings/bucket sizes. Unknown estimated/actual rows and selected indexes are None, never guessed costs. Dynamic telemetry and SCC-aware execution are future work.

Identities and evidence

Legacy Program.digest retains its existing gestaltdb-datalog-v1 encoding. The relational IR uses a separate versioned namespace; lowering does not mutate or replace the original program. Schema/catalog ordering is canonical, while column order, rule IDs, body order and variable names participate in identity. Unused declared relations also participate. Digests express structure, not logical-equivalence detection.

CompilationIdentity binds a program digest, the complete existing InputContext, dictionary generation, semantics profile and executor version. Use require_compatible before reusing context-bound state. The schema version is included in its digest. This is a compatibility check, not a capability token: adapters still authenticate their actual pinned evidence and rule/source horizon. The structural description accepts only the currently implemented binary profile and indexed Python executor; future executors must declare their own support.

Logical facts are set-valued; existing Evidence.reference_id retains exact input identities independently. Dictionary generations are not evidence IDs, RocksDB sequence numbers are not authenticated application horizons, and a witness trace is not an all-support maintenance index. No database extraction, snapshot acquisition, publication or cache access occurs during structural inspection.

Capability boundary (RE-01)

The baseline register at planning/reasoning-engine/baseline-review.json audits every GR-001–136 exactly once against onto-dev commit c71f043. Statuses are qualified by evidence and notes; “available” never means the wider workbook acceptance criterion is certified beyond the named fragment.

Operation

Existing indexed evaluator

New relational contract

Constants/repeated variables

Executable, binary entities

Represented at positive arities

Safe positive recursion

Bounded complete fixpoint

Validated rules, not execution

Selection/projection/joins

Python indexed joins

Logical atom/head structure

Union/distinct/difference

Python fact sets

Set-valued schema declaration

Body delta occurrences

Every occurrence dynamically

Original positions retained

Join choice

Smallest current bound bucket

Description, no cost estimates

Witnesses

Bounded single witness

Existing Evidence stays separate

Context/dictionary identity

Adapter context checks

Full compilation compatibility key

Literals/negation/aggregates

Unsupported

Rejected/not represented

SCC/parallel/vectorized/spill

Unsupported

Future executor work

Reproducible reference budgets

Before choosing an optimized executor, RE-01 freezes engineering budgets in planning/reasoning-engine/performance-budgets.json. These are reference-machine acceptance gates, not universal library latency promises. Each case runs both closure-only and bounded-witness modes in five fresh worker processes:

Case

Size

Pipeline P95 cap

Cold worker P95 cap

Worker RSS cap

Interactive hierarchy

256

0.25s

2s

256 MiB

Batch tree

1024

3s

5s

512 MiB

Dense output

128

3s

5s

512 MiB

Update reference

512

1s

3s

256 MiB

Reproduce from the repository:

uv run python planning/reasoning-engine/check_roadmap.py
uv run python -m benchmarks.reasoning_contracts --output /tmp/opencode/reasoning-baseline.json

Use --repeats 1 only for smoke checks; the report identifies insufficient acceptance repeats. The harness exits unsuccessfully for missing metrics, wrong answers, timeouts or exceeded budgets. CI unit tests check fixtures and budget accounting, not host-dependent timing assertions. P95 is nearest-rank; the maximum worker RSS must satisfy its cap. Native means the existing Python engine in the inherited benchmark nomenclature, not C++ or Rust.

The update case inserts one link, then removes its final support, comparing exact analytical answers at each step. It performs full rematerialization, not incremental propagation, and performs no persistence/publication. Future incremental implementations must report their maintenance mode separately. The other cases reuse the existing analytical ontology workloads and staged worker measurements, rather than introducing another solver/reference oracle.

Pipeline includes compilation, conversion, fixpoint and decoding; imports, fixture construction and validation are separate. Cold worker time includes interpreter startup/imports. RSS is the full process high-water mark, not a subtraction of startup high-water. Reports include source/harness digests, Python/platform, dependency/source footprint and explicit thread environment. No cold-cache storage or zero-copy claim is implied. Source extraction, native operator/copy/spill counters, installed-wheel footprint and persistent incremental/publication costs remain later phase gates; see Ontology solver benchmarks and v1 decision for existing claim-extraction and solver comparison evidence.

The committed benchmarks/results/reasoning-re01-baseline.json records 40 successful rows (clean source 99be3cb, Python 3.13). Pipeline P95s for closure/witness modes were 4.45/12.39ms interactive, 90.23/177.10ms sparse batch, 559.84/677.52ms dense output and 6.49/12.69ms for the complete three-state rematerialization update workflow. Worker RSS stayed below 57 MiB. These are synthetic reference observations on the recorded host, not optimized-engine speedups, production-scale guarantees or incremental update latency claims.