Skip to content

Commit d7fc02f

Browse files
adityamukhoclaude
andcommitted
docs: sync all docs for Phase 7.1 completion (not / not-join)
- Bump version to v0.10.0 - CHANGELOG: add [0.10.0] entry for Phase 7.1a+7.1b - README: update phase badge, status line, test count (407), roadmap table - TEST_COVERAGE: update totals, add negation_test/not_join_test sections, update "what's not tested", update conclusion - ROADMAP: mark Phase 7 in progress, 7.1a+7.1b complete, add v0.10.0 release, update timeline and current focus - CLAUDE.md: update phase status, test counts, architecture notes for stratification.rs, WhereClause::Not/NotJoin, StratifiedEvaluator Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 6a6ad3a commit d7fc02f

6 files changed

Lines changed: 203 additions & 65 deletions

File tree

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.10.0] - 2026-03-24
9+
10+
### Added
11+
- `src/query/datalog/stratification.rs``DependencyGraph` and `stratify()`: analyse rule dependency graphs at registration time; programs with negative cycles are rejected with a clear error
12+
- `WhereClause::Not(Vec<WhereClause>)` and `WhereClause::NotJoin { join_vars, clauses }` variants in `types.rs`; all exhaustive matches updated
13+
- `(not clause…)` in `:where` and rule bodies — stratified negation where all body variables must be pre-bound by outer clauses
14+
- `(not-join [?v…] clause…)` — existentially-quantified negation with explicit join-variable declaration; body variables not in `join_vars` are fresh/unbound
15+
- Safety check at parse time: every `not` body variable must be bound by an outer clause; every `join_vars` variable in `not-join` must be bound by an outer clause
16+
- Nesting constraint: `not-join` cannot appear inside `not` or another `not-join` — rejected at parse time
17+
- `StratifiedEvaluator` in `evaluator.rs`: stratifies rules, runs positive rules first, then applies `not`/`not-join` filters per binding for mixed rules
18+
- `evaluate_not_join` free function in `evaluator.rs`: builds partial binding from `join_vars`, converts `Pattern` and `RuleInvocation` body clauses to patterns, runs `PatternMatcher`; returns `true` if body is satisfiable (reject outer binding)
19+
- `rule_invocation_to_pattern` extracted as `pub(super)` free function from `RecursiveEvaluator`
20+
- Two not-post-filter sites in `executor.rs` now handle both `Not` and `NotJoin` via `evaluate_not_join`
21+
- `tests/negation_test.rs` — 10 integration tests for `not` (Phase 7.1a): basic absence, multi-clause, rule body, time-travel, negative cycle rejection
22+
- `tests/not_join_test.rs` — 14 integration tests for `not-join` (Phase 7.1b): basic exclusion, multiple join vars, multi-clause body, rule body, `:as-of`, `:valid-at`, negative cycle at registration, `not`+`not-join` coexistence, `RuleInvocation` in body end-to-end
23+
24+
### Changed
25+
- `Rule.body` changed from `Vec<EdnValue>` to `Vec<WhereClause>` to support negation clauses alongside patterns
26+
- `executor.rs` `execute_query_with_rules` now delegates to `StratifiedEvaluator` instead of `RecursiveEvaluator` directly
27+
- `rules.rs` `register_rule` runs `stratify()` after each registration; returns `Err` on negative cycle (rules are not registered on error)
28+
829
## [0.9.0] - 2026-03-23
930

1031
### Added

CLAUDE.md

Lines changed: 64 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
66

77
Minigraf is a tiny, portable **bi-temporal graph database with Datalog queries** written in Rust. It's designed to be the embedded graph memory layer for AI agents, mobile apps, and the browser — built on the SQLite philosophy: embedded, single-file, reliable, with time travel capabilities.
88

9-
**Current Status: Phase 6.5 COMPLETE ✅ → Phase 7 Next** - On-disk B+tree indexes (file format v6) (note: Phase 6.3 query optimization was completed as part of Phase 6.1):
9+
**Current Status: Phase 7.1 COMPLETE ✅ → Phase 7.2 Next** - Stratified negation (`not` / `not-join`) (note: Phase 6.3 query optimization was completed as part of Phase 6.1):
1010
- ✅ Phase 1: Property graph PoC (in-memory)
1111
- ✅ Phase 2: Persistent storage (`.graph` file format, embedded API)
1212
- ✅ Phase 3: Datalog core (EAV model, recursive rules) - COMPLETE!
@@ -17,7 +17,10 @@ Minigraf is a tiny, portable **bi-temporal graph database with Datalog queries**
1717
-**Phase 6.4a: Retraction semantics fix + edge case tests - COMPLETE!**
1818
-**Phase 6.4b: Criterion benchmarks + light publish prep - COMPLETE!**
1919
-**Phase 6.5: On-disk B+tree indexes (file format v6) - COMPLETE!**
20-
- 🎯 Phase 7: Datalog Completeness (negation, aggregation, disjunction; ≥90% branch coverage target)
20+
-**Phase 7.1a: Stratified negation — `not` - COMPLETE!**
21+
-**Phase 7.1b: Stratified negation — `not-join` - COMPLETE!**
22+
- 🎯 Phase 7.2: Aggregation (`count`, `sum`, `min`, `max`, `distinct`, `:with`)
23+
- 🎯 Phase 7.3–7.7: Disjunction, optimizer improvements, prepared statements, temporal metadata
2124
- 🎯 v1.0.0: 9-12 months
2225

2326
## Core Philosophy - CRITICAL
@@ -159,20 +162,25 @@ The codebase is organized into the following modules:
159162
- `CommittedFactLoaderImpl`: resolves `FactRef` via page cache
160163
- Auto-migrates v1/v2/v3/v4/v5 on open
161164

162-
3. **Query Module (`src/query/datalog/`)** - Phase 3-6.1 (Datalog + optimizer) ✅:
165+
3. **Query Module (`src/query/datalog/`)** - Phase 3-7.1 (Datalog + optimizer + negation) ✅:
163166
- `parser.rs`: EDN/Datalog parser
164167
- Parses `transact`, `retract`, `query`, `rule` commands
165168
- Supports `:as-of` (tx counter or ISO 8601 timestamp), `:valid-at`
166169
- EDN maps `{:key val}` for transaction-level valid time options
167170
- Per-fact 4-element vector override for valid time
171+
- `(not …)` and `(not-join [?v…] …)` clauses with safety checks (Phase 7.1)
168172
- `executor.rs`: Datalog query executor
169173
- Pattern matching with variable unification
170174
- Rule registration and invocation
171175
- 3-step temporal filter: tx-time → asserted exclusion → valid-time
176+
- not/not-join post-filter sites in both pure-query and rule-query paths (Phase 7.1)
172177
- `matcher.rs`: Pattern matching engine with variable binding
173-
- `evaluator.rs`: `RecursiveEvaluator` - semi-naive fixed-point iteration
174-
- `rules.rs`: `RuleRegistry` - thread-safe rule management
175-
- `types.rs`: `EdnValue`, `Pattern`, `DatalogQuery`, `AsOf`, `ValidAt`
178+
- `evaluator.rs`: `RecursiveEvaluator` + `StratifiedEvaluator` + `evaluate_not_join` (Phase 7.1)
179+
- Semi-naive fixed-point iteration for positive rules
180+
- Stratification: evaluates strata in order; applies not/not-join filters per binding in mixed strata
181+
- `stratification.rs`: `DependencyGraph`, `stratify()` — negative dependency edges + cycle detection (Phase 7.1)
182+
- `rules.rs`: `RuleRegistry` - thread-safe rule management; calls `stratify()` on registration
183+
- `types.rs`: `EdnValue`, `Pattern`, `DatalogQuery`, `AsOf`, `ValidAt`, `WhereClause` (incl. `Not`, `NotJoin`)
176184
- `optimizer.rs`: Query plan optimizer (Phase 6.1)
177185
- `IndexHint` enum, `select_index()`, `plan()` with selectivity-based join reordering
178186
- Disabled under `wasm` feature flag
@@ -341,17 +349,18 @@ WAL sidecar <db>.wal (present while uncommitted writes exist):
341349

342350
## Test Coverage
343351

344-
**Current Tests (Phase 6.5)**: 331 tests passing ✅
345-
- **Unit tests** (222 tests):
352+
**Current Tests (Phase 7.1)**: 407 tests passing ✅
353+
- **Unit tests** (297 tests):
346354
- `src/graph/types.rs`: Fact types, Value types, EAV model, temporal fields
347355
- `src/graph/storage.rs`: FactStorage, CRUD, history, tx_count, temporal methods, CommittedFactReader integration
348356
- `src/temporal.rs`: UTC timestamp parsing and formatting
349-
- `src/query/datalog/parser.rs`: EDN/Datalog syntax, rules, `:as-of`, `:valid-at`, EDN maps
350-
- `src/query/datalog/types.rs`: Pattern, WhereClause, DatalogQuery, AsOf, ValidAt
357+
- `src/query/datalog/parser.rs`: EDN/Datalog syntax, rules, `:as-of`, `:valid-at`, EDN maps, `not`, `not-join` (Phase 7.1)
358+
- `src/query/datalog/types.rs`: Pattern, WhereClause (incl. Not, NotJoin), DatalogQuery, AsOf, ValidAt
351359
- `src/query/datalog/matcher.rs`: Pattern matching, variable unification
352-
- `src/query/datalog/executor.rs`: Query execution, rule registration, temporal filtering, retraction net-view
360+
- `src/query/datalog/executor.rs`: Query execution, rule registration, temporal filtering, retraction net-view, not/not-join post-filter
353361
- `src/query/datalog/rules.rs`: RuleRegistry, rule management
354-
- `src/query/datalog/evaluator.rs`: Semi-naive evaluation, transitive closure
362+
- `src/query/datalog/evaluator.rs`: Semi-naive evaluation, transitive closure, StratifiedEvaluator, evaluate_not_join (Phase 7.1)
363+
- `src/query/datalog/stratification.rs`: DependencyGraph, stratify(), negative cycle detection (Phase 7.1)
355364
- `src/storage/index.rs`: EAVT/AEVT/AVET/VAET keys, FactRef, encode_value sort order
356365
- `src/storage/btree.rs`: B+tree roundtrip, multi-page, sort order preservation
357366
- `src/storage/btree_v6.rs`: On-disk B+tree insert/range-scan, concurrent range scan correctness (Phase 6.5)
@@ -361,7 +370,7 @@ WAL sidecar <db>.wal (present while uncommitted writes exist):
361370
- `src/wal.rs`: WAL entry serialization, CRC32, replay logic
362371
- `src/db.rs`: WriteTransaction, checkpoint, crash recovery, `check_fact_sizes` early validation
363372

364-
- **Integration tests** (103 tests):
373+
- **Integration tests** (104 tests):
365374
- `tests/bitemporal_test.rs` (10 tests): Bi-temporal queries, time travel, valid time
366375
- `tests/complex_queries_test.rs` (10 tests): Multi-pattern joins, self-joins, edge cases
367376
- `tests/recursive_rules_test.rs` (9 tests): Transitive closure, cycles, long chains, family trees
@@ -372,6 +381,8 @@ WAL sidecar <db>.wal (present while uncommitted writes exist):
372381
- `tests/retraction_test.rs` (7 tests): Retraction semantics in Datalog queries (Phase 6.4a)
373382
- `tests/edge_cases_test.rs` (4 tests): Oversized-fact file-backed error, MAX_FACT_BYTES boundary
374383
- `tests/btree_v6_test.rs` (8 tests): B+tree insert/scan, concurrent range scan, v5→v6 migration (Phase 6.5)
384+
- `tests/negation_test.rs` (10 tests): `not` — basic absence, multi-clause, rule body, time-travel, negative cycle rejection (Phase 7.1a)
385+
- `tests/not_join_test.rs` (14 tests): `not-join` — existential negation, join vars, rule body, time-travel, cycle rejection (Phase 7.1b)
375386

376387
- **Doc tests** (6 tests): Inline documentation examples
377388

@@ -575,10 +586,21 @@ When implementing features, always ask:
575586
- ✅ Version bumped to v0.9.0
576587
- ✅ 331 comprehensive tests
577588

578-
**Phase 7** (6-8 weeks): Datalog Completeness
579-
- Stratified negation (`not` / `not-join`)
580-
- Aggregation (`count`, `sum`, `min`, `max`, `distinct`, `:with`)
581-
- Disjunction (`or` / `or-join`)
589+
**Phase 7.1****COMPLETE** - Stratified Negation (`not` / `not-join`)
590+
-`src/query/datalog/stratification.rs`: `DependencyGraph`, `stratify()` — negative dependency edges + cycle detection
591+
-`WhereClause::Not` + `WhereClause::NotJoin { join_vars, clauses }` variants; all match arms updated
592+
- ✅ Parser: `(not …)` and `(not-join [?v…] …)`, safety validation, nesting constraint
593+
-`StratifiedEvaluator` + `evaluate_not_join` (handles `Pattern` and `RuleInvocation` body clauses)
594+
-`tests/negation_test.rs` (10) + `tests/not_join_test.rs` (14) — 24 new integration tests
595+
- ✅ Version bumped to v0.10.0
596+
- ✅ 407 comprehensive tests
597+
598+
**Phase 7** (in progress): Datalog Completeness
599+
- ✅ Phase 7.1a: Stratified negation — `not`
600+
- ✅ Phase 7.1b: Stratified negation — `not-join`
601+
- 🎯 Phase 7.2: Aggregation (`count`, `sum`, `min`, `max`, `distinct`, `:with`)
602+
- 🎯 Phase 7.3: Disjunction (`or` / `or-join`)
603+
- 🎯 Phase 7.4–7.7: Optimizer improvements, prepared statements, temporal metadata
582604

583605
**Phase 8** (3-4 months): Cross-platform
584606
- WASM (browser via wasm-pack + npm; server-side via WASI)
@@ -666,7 +688,8 @@ Before publishing the crate, verify all of the following:
666688

667689
### Minimum Bar (do not publish before Phase 6.5)
668690
- [x] **Phase 6.4 benchmarks complete** — Criterion benchmarks at 10K/100K/1M facts documented in `BENCHMARKS.md`. ✅ Phase 6.4b complete.
669-
- [x] **Phase 6.5 complete** — On-disk B+tree indexes, file format v6, 331 tests passing. ✅ Phase 6.5 complete.
691+
- [x] **Phase 6.5 complete** — On-disk B+tree indexes, file format v6. ✅ Complete.
692+
- [x] **Phase 7.1 complete** — Stratified negation (`not` / `not-join`), 407 tests passing. ✅ Complete.
670693
- [ ] **Edge case tests passing** — Oversized-fact error path exercised ✅; checkpoint-during-crash recovery not yet verified.
671694
- [ ] **Error-path coverage** — Still ~82%; storage and WAL error paths to be prioritised in Phase 7.
672695
- [x] **GitHub Discussions enabled** — ✅ Done in Phase 6.4b.
@@ -694,6 +717,29 @@ Before publishing the crate, verify all of the following:
694717
- [ ] `cargo doc --no-deps` builds without warnings
695718
- [ ] No `unwrap()`/`expect()` in library code paths (only in tests/binary)
696719

720+
### Testing Conventions
721+
722+
**Never use `{:?}` debug format of `Result`, `Fact`, `Value`, `EdnValue`, or any type that may transitively contain `Uuid` in `assert!`/`assert_eq!` message strings.**
723+
724+
CodeQL flags this as `rust/cleartext-logging` (alert `rust/cleartext-logging`). It is a false positive in tests, but it pollutes the security scan and blocks CI.
725+
726+
```rust
727+
// BAD — triggers CodeQL:
728+
assert!(result.is_ok(), "parse failed: {:?}", result);
729+
730+
// GOOD — plain string message:
731+
assert!(result.is_ok(), "parse failed");
732+
733+
// GOOD — use unwrap/expect instead (panic message not flagged):
734+
result.unwrap();
735+
result.expect("parse failed");
736+
737+
// GOOD — assert on count/bool only:
738+
assert_eq!(results.len(), 3, "expected 3 results");
739+
```
740+
741+
This applies to all inline `#[cfg(test)]` modules and all `tests/*.rs` integration test files.
742+
697743
### Versioning
698744
- [ ] Publish as `0.x` — no backwards-compat promise until v1.0.0
699745
- [ ] Stable API target is v1.0.0 (after Phase 8 cross-platform work)

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "minigraf"
3-
version = "0.9.0"
3+
version = "0.10.0"
44
edition = "2024"
55
description = "Zero-config, single-file, embedded graph database with bi-temporal Datalog queries"
66
license = "MIT OR Apache-2.0"

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[![Clippy Status](https://github.com/adityamukho/minigraf/actions/workflows/rust-clippy.yml/badge.svg)](https://github.com/adityamukho/minigraf/actions/workflows/rust-clippy.yml)
55
[![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](https://github.com/adityamukho/minigraf#license)
66
[![Rust Edition](https://img.shields.io/badge/rust-2024-orange.svg)](https://blog.rust-lang.org/2024/10/17/Rust-1.82.0.html)
7-
[![Phase](https://img.shields.io/badge/phase-6.5%20complete-blue.svg)](https://github.com/adityamukho/minigraf/blob/main/ROADMAP.md)
7+
[![Phase](https://img.shields.io/badge/phase-7.1%20complete-blue.svg)](https://github.com/adityamukho/minigraf/blob/main/ROADMAP.md)
88

99
> **Embedded graph memory for AI agents, mobile apps, and the browser** — the SQLite of bi-temporal graph databases
1010
@@ -18,7 +18,7 @@ Minigraf is a **single-file embedded graph database** that lets you:
1818
-**Embed anywhere** - Native, WASM, mobile, IoT - one `.graph` file
1919
-**Zero configuration** - Just `Minigraf::open("data.graph")` and you're done
2020

21-
**Status**: Phase 6.5 complete — crash-safe bi-temporal Datalog engine with on-disk B+tree indexes, packed storage, LRU page cache, and validated performance at 1K–1M facts (331 tests). Next: Phase 7 (Datalog completeness — negation, aggregation, disjunction).
21+
**Status**: Phase 7.1 complete — stratified negation (`not` / `not-join`) added to the bi-temporal Datalog engine (407 tests). Next: Phase 7.2 (aggregation — `count`, `sum`, `min`, `max`, `distinct`).
2222

2323
## Why Datalog?
2424

@@ -66,7 +66,7 @@ db.execute(r#"(rule [(reachable ?a ?b) [?a :friend ?b]])
6666

6767
```bash
6868
cargo run # interactive Datalog REPL
69-
cargo test # run 331 tests
69+
cargo test # run 407 tests
7070
cargo run < demo_recursive.txt # recursive rules demo
7171
```
7272

@@ -125,7 +125,8 @@ Minigraf will **not** be (by design):
125125
|---|---|---|
126126
| 1–5 | ✅ Complete | Property graph, persistent storage, Datalog core, bi-temporal, ACID + WAL |
127127
| 6.1–6.5 | ✅ Complete | Covering indexes, packed pages, LRU cache, retraction fix, benchmarks, on-disk B+tree (v6) |
128-
| **7** | 🎯 Next | Datalog completeness — negation, aggregation, disjunction |
128+
| 7.1 | ✅ Complete | Stratified negation — `not` and `not-join` |
129+
| **7.2–7.7** | 🎯 Next | Aggregation, disjunction, optimizer, prepared statements, temporal metadata |
129130
| 8 | 🎯 Planned | Cross-platform — WASM, iOS, Android, language bindings |
130131
| v1.0 | 🎯 ~12 months | Stable API + file format |
131132

0 commit comments

Comments
 (0)