nedb-engine 2.7.2__tar.gz → 2.8.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/PKG-INFO +146 -3
  2. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/README.md +145 -2
  3. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/client/node/README.md +65 -1
  4. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/client/python/README.md +60 -1
  5. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/pyproject.toml +1 -1
  6. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/python/nedb/__init__.py +1 -1
  7. nedb_engine-2.8.1/python/nedb/_native.abi3.so +0 -0
  8. nedb_engine-2.8.1/python/nedb/nedbd-v2 +0 -0
  9. nedb_engine-2.8.1/python/nedb/nedbd-v2-darwin-arm64 +0 -0
  10. nedb_engine-2.8.1/python/nedb/nedbd-v2-darwin-x64 +0 -0
  11. nedb_engine-2.8.1/python/nedb/nedbd-v2.exe +0 -0
  12. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/rust/nedb-v2/README.md +119 -0
  13. nedb_engine-2.7.2/python/nedb/_native.abi3.so +0 -0
  14. nedb_engine-2.7.2/python/nedb/nedbd-v2 +0 -0
  15. nedb_engine-2.7.2/python/nedb/nedbd-v2-darwin-arm64 +0 -0
  16. nedb_engine-2.7.2/python/nedb/nedbd-v2-darwin-x64 +0 -0
  17. nedb_engine-2.7.2/python/nedb/nedbd-v2.exe +0 -0
  18. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/.gitignore +0 -0
  19. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/LICENSE +0 -0
  20. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/examples/mini-chain/README.md +0 -0
  21. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/python/nedb/autoindex.py +0 -0
  22. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/python/nedb/backends/__init__.py +0 -0
  23. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/python/nedb/backends/redis_backend.py +0 -0
  24. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/python/nedb/cascade.py +0 -0
  25. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/python/nedb/client.py +0 -0
  26. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/python/nedb/concurrent.py +0 -0
  27. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/python/nedb/crypto.py +0 -0
  28. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/python/nedb/engine.py +0 -0
  29. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/python/nedb/index.py +0 -0
  30. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/python/nedb/log.py +0 -0
  31. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/python/nedb/merkle.py +0 -0
  32. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/python/nedb/mongo.py +0 -0
  33. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/python/nedb/proof.py +0 -0
  34. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/python/nedb/query.py +0 -0
  35. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/python/nedb/redis_compat.py +0 -0
  36. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/python/nedb/relations.py +0 -0
  37. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/python/nedb/resp2.py +0 -0
  38. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/python/nedb/server.py +0 -0
  39. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/python/nedb/snapshot.py +0 -0
  40. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/python/nedb/sql.py +0 -0
  41. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/python/nedb/store.py +0 -0
  42. {nedb_engine-2.7.2 → nedb_engine-2.8.1}/python/nedb/wrap_redis.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nedb-engine
3
- Version: 2.7.2
3
+ Version: 2.8.1
4
4
  Summary: NEDB — a versioned, self-compressing, time-traveling embedded database (replay-protected, idempotent, relational, searchable) with durable AOF persistence and a server daemon (nedbd).
5
5
  Project-URL: Homepage, https://github.com/aiassistsecure/nedb
6
6
  Project-URL: Repository, https://github.com/aiassistsecure/nedb
@@ -41,9 +41,29 @@ One Rust core → ships to **PyPI** and **npm** from a single source.
41
41
 
42
42
  ---
43
43
 
44
- ## NEDB v2.5.34 — Production Stable
44
+ ## NEDB v2.8.0 — Production Stable
45
45
 
46
- **Current stable: 2.5.34** — NEDB ships as **three version-aligned distributions** on one tag — `nedb-engine` (flagship), `crypto-database` (verifiable v2/v3 DAG), and `aof-db` (fast append-only) — across npm / PyPI / crates.io with full mac + linux + windows native addons (see [**Releasing**](#releasing) below). On the engine side it remains a polish release on the complete cross-platform line. The `nedbd-v2` daemon now does **real CLI parsing** — `--dag-v3`, `--data`, `--fast-fsync`, `--help`, `--version` are recognized flags instead of being silently swallowed as the positional data dir — and `npm test` ships a **cinematic native smoke test** that tours v1→v2 migration, the v2 DAG, the v3 segment store, and a causal-provenance audit. All native wheels (Linux + Windows on GitHub Actions; macOS arm64 + x86_64 on Codemagic M2 Mac Minis) **plus** the universal pure-Python wheel ship from a single `v*` tag, with the `nedbd-v2` binary bundled inside `pip install nedb-engine`.
46
+ **Current stable: 2.8.0** — NEDB ships as **three version-aligned distributions** on one tag — `nedb-engine` (flagship), `crypto-database` (verifiable v2/v3 DAG), and `aof-db` (fast append-only) — across npm / PyPI / crates.io with full mac + linux + windows native addons (see [**Releasing**](#releasing) below). All native wheels (Linux + Windows on GitHub Actions; macOS arm64 + x86_64 on Codemagic M2 Mac Minis) **plus** the universal pure-Python wheel ship from a single `v*` tag, with the `nedbd-v2` binary bundled inside `pip install nedb-engine`.
47
+
48
+ ### New in 2.8.0 — Cast: the database understands English
49
+
50
+ `POST /v1/databases/<name>/cast` turns a short English prompt into NQL, using a **3.33M-parameter model that runs locally on CPU**. No API key, no network call, no per-token bill.
51
+
52
+ ```bash
53
+ nedbd --dag --cast ./data # requires: cargo install nedb-engine --features cast
54
+
55
+ curl -X POST localhost:7070/v1/databases/shop/cast \
56
+ -d '{"prompt":"orders over 100"}'
57
+ # → {"nql":"FROM orders WHERE total > 100","valid":true,"collection_known":true,"executed":false}
58
+ ```
59
+
60
+ The model ([**nedb-cast-slm**](https://github.com/aiassistsecure/nedb-cast-slm)) was trained on NQL using NEDB's own parser as generator, grader, and gate — then shipped to PyPI, crates.io, and npm, all three loading the identical weights.
61
+
62
+ **Why it lives in the engine and not in a client:** the hard part of natural-language querying is knowing the schema, and the engine already holds the live collection list. A plan naming a collection that doesn't exist returns **422 with the reason**, never a silently empty result set. See [**Cast**](#cast--natural-language-into-nql) below.
63
+
64
+ Off by default — feature-gated at compile time, flag-gated at runtime, and `execute` defaults to `false` so you review the plan before it runs.
65
+
66
+ **Also in 2.8.0 — an engine bug the feature exposed.** `IdIndex::collections()` did a bare `read_dir` while every other read path overlaid the WAL write buffer, so a brand-new collection was invisible until the 1s flush ticker fired. Unreachable by hand (the ticker fires between keystrokes) but reliable from a script, and latent in `Db::compact()` too, where a missed collection's live objects would be reclaimed as garbage. Fixed, with regression tests that seed *without* flushing.
47
67
 
48
68
  **New in 2.5.x:**
49
69
 
@@ -325,6 +345,8 @@ Alongside the daemon, `cargo install nedb-engine` ships **`nedb-cli`** — opera
325
345
  | `NEDBD_TOKEN` | unset | Optional bearer token; required on every `/v1/*` request when set. |
326
346
  | `NEDB_TMK` | unset | 32-byte hex AES-256-GCM at-rest encryption key. |
327
347
  | `NEDBD_DATA` | `./nedb-data` | Root directory. v2 creates `dag/`, IdIndex sharded across **256 subdirectories**, and a small `MANIFEST` file. |
348
+ | `NEDBD_CAST` | `0` | Set `1` to enable the `/cast` natural-language planner. Same as `--cast`. Requires a build with `--features cast`. See [**Cast**](#cast--natural-language-into-nql). |
349
+ | `NEDBD_CAST_MODEL` | unset | Explicit path to a `model.cast` container. Otherwise searched in the data dir, `$CAST_HOME`, and `~/.cache/nedb-cast-slm/`. |
328
350
 
329
351
  ```bash
330
352
  # Create a database with seed data and relations
@@ -381,6 +403,127 @@ db.query('FROM policy AS OF 200 VALID AS OF "2024-02-15"')
381
403
 
382
404
  ---
383
405
 
406
+ ## Cast — natural language into NQL
407
+
408
+ *New in 2.8.0. Optional, feature-gated, off by default.*
409
+
410
+ Ten clauses and six operators. That's the whole grammar above — small enough that a **3.33M-parameter** model can learn it completely, and small enough that shipping every query to a frontier model is an absurd amount of machinery.
411
+
412
+ So we trained one. It runs on CPU, in-process, in milliseconds.
413
+
414
+ ```bash
415
+ curl -X POST localhost:7070/v1/databases/shop/cast \
416
+ -H 'Content-Type: application/json' \
417
+ -d '{"prompt":"orders over 100"}'
418
+ ```
419
+
420
+ ```json
421
+ {
422
+ "prompt": "orders over 100",
423
+ "nql": "FROM orders WHERE total > 100",
424
+ "valid": true,
425
+ "collection": "orders",
426
+ "collection_known": true,
427
+ "collections": ["orders"],
428
+ "executed": false,
429
+ "seq": 3,
430
+ "head": "262fd9…"
431
+ }
432
+ ```
433
+
434
+ ### NEDB is on both ends of this
435
+
436
+ The model is [**nedb-cast-slm**](https://github.com/aiassistsecure/nedb-cast-slm), and NEDB built it as much as it consumes it.
437
+
438
+ **NEDB's parser was the training pipeline.** It generated the corpus (sample a random plan → render NQL → render a human paraphrase; 200,000 pairs in 16.5 seconds, perfect labels, zero annotation cost). It was the grader — scoring *parsed plan* equality, not string equality, so `FROM orders WHERE total > 99` and `from orders where total>99` both earn full credit. And it was the gate: no example entered the corpus unless it round-tripped through the real parser to a canonically identical plan.
439
+
440
+ > Most text-to-DSL projects hand-write a verifier and hope it's right. We didn't write one — it already shipped, and it's the same code the database runs in production.
441
+
442
+ **Training lineage lives in NEDB too**, chained by `caused_by`:
443
+
444
+ ```
445
+ datasets ──▶ training_runs ──▶ checkpoints ──▶ evals
446
+ ```
447
+
448
+ ```python
449
+ db.query("FROM evals TRACE caused_by") # the exact data behind any score
450
+ ```
451
+
452
+ ### Why the planner lives in the engine
453
+
454
+ The hard part of natural-language querying is not the model. It's the **schema** — and a client has to fetch the collection list and pass it in, where it's stale on arrival. The engine already holds the live list.
455
+
456
+ So the plan is checked against collections that actually exist, at the moment of the call:
457
+
458
+ ```json
459
+ { "prompt": "show me all stylists",
460
+ "nql": "FROM stylists",
461
+ "valid": true,
462
+ "collection_known": false,
463
+ "error": "collection \"stylists\" does not exist in \"shop\"" }
464
+ ```
465
+
466
+ HTTP **422**. Not zero rows — zero rows reads as *"no matching data"*, which would be a lie. The query was perfectly well-formed; the collection was imagined. That's the model's known failure mode on an unfamiliar schema, and the engine is the one component positioned to catch it.
467
+
468
+ Every `nedbd` client — Python, Node, Studio, `curl` — inherits this without writing a line.
469
+
470
+ ### Three safety properties
471
+
472
+ | | |
473
+ |---|---|
474
+ | **The model never executes** | It emits text. The text goes to the same `nql::query` path a hand-typed query uses. No second executor exists to audit. |
475
+ | **Validation is parsing** | `nql::parse` and `nql::execute` share one code path, so they cannot disagree about what is well-formed. Invalid output returns 422 *with the offending text*. |
476
+ | **`execute` defaults to false** | You get a plan for review. Running a guess silently is worse than admitting uncertainty. |
477
+
478
+ That last default earns its keep. A real miss, from a real run:
479
+
480
+ ```
481
+ prompt "paid orders over 100"
482
+ nql FROM orders WHERE status = "paid" LIMIT 100 ← wrong
483
+ correct FROM orders WHERE status = "paid" AND total > 100
484
+ ```
485
+
486
+ It read *"over 100"* as `LIMIT 100` and dropped the predicate. The count still came back **2** — because both paid orders happened to exceed 100. A count-only assertion would have scored it a pass. A human reading `LIMIT 100` catches it in a heartbeat; an auto-executing client does not.
487
+
488
+ Multi-predicate `WHERE` is the model's weakest clause: **85.1%** exact-plan match on eval, 61.2% on adversarial holdout. The [model card](https://github.com/aiassistsecure/nedb-cast-slm#what-it-gets-wrong) documents every failure mode with examples.
489
+
490
+ To run it anyway, ask:
491
+
492
+ ```bash
493
+ curl -X POST localhost:7070/v1/databases/shop/cast \
494
+ -d '{"prompt":"orders over 100","execute":true}'
495
+ # → { …, "executed": true, "count": 2, "rows": [ … ] }
496
+ ```
497
+
498
+ ### Enabling it
499
+
500
+ Two gates, because most deployments want neither the model dependency nor the weights:
501
+
502
+ ```bash
503
+ # compile-time
504
+ cargo install nedb-engine --features cast
505
+
506
+ # weights (~13 MB) — GitHub release asset, checksum-verified on load
507
+ curl -L -o ./data/model.cast \
508
+ https://github.com/aiassistsecure/nedb-cast-slm/releases/download/v10.30.90/model.cast
509
+
510
+ # runtime
511
+ nedbd --dag --cast ./data
512
+ # cast enabled — 3.33M params, vocab 581, ./data/model.cast
513
+ ```
514
+
515
+ Search order: `$NEDBD_CAST_MODEL` → `<data_dir>/model.cast` → `$CAST_HOME/model.cast` → `~/.cache/nedb-cast-slm/v10.30.90/model.cast` (the Python/npm cache location, so a machine that has run either package is already ready).
516
+
517
+ Built without the feature, the route returns **501** rather than 404 — clients can detect the capability instead of guessing. Built with it but missing weights, the daemon logs loudly and serves everything else normally.
518
+
519
+ **Verify the whole path:**
520
+
521
+ ```bash
522
+ ./scripts/test-cast.sh --boot # boots a daemon, seeds, casts, executes, checks failure modes
523
+ ```
524
+
525
+ ---
526
+
384
527
  ## Performance
385
528
 
386
529
  **v2 DAG Rust server (v2.2.31, Intel iMac — 10k writes / 100k reads / 30k objects, AES-256-GCM on):**
@@ -20,9 +20,29 @@ One Rust core → ships to **PyPI** and **npm** from a single source.
20
20
 
21
21
  ---
22
22
 
23
- ## NEDB v2.5.34 — Production Stable
23
+ ## NEDB v2.8.0 — Production Stable
24
24
 
25
- **Current stable: 2.5.34** — NEDB ships as **three version-aligned distributions** on one tag — `nedb-engine` (flagship), `crypto-database` (verifiable v2/v3 DAG), and `aof-db` (fast append-only) — across npm / PyPI / crates.io with full mac + linux + windows native addons (see [**Releasing**](#releasing) below). On the engine side it remains a polish release on the complete cross-platform line. The `nedbd-v2` daemon now does **real CLI parsing** — `--dag-v3`, `--data`, `--fast-fsync`, `--help`, `--version` are recognized flags instead of being silently swallowed as the positional data dir — and `npm test` ships a **cinematic native smoke test** that tours v1→v2 migration, the v2 DAG, the v3 segment store, and a causal-provenance audit. All native wheels (Linux + Windows on GitHub Actions; macOS arm64 + x86_64 on Codemagic M2 Mac Minis) **plus** the universal pure-Python wheel ship from a single `v*` tag, with the `nedbd-v2` binary bundled inside `pip install nedb-engine`.
25
+ **Current stable: 2.8.0** — NEDB ships as **three version-aligned distributions** on one tag — `nedb-engine` (flagship), `crypto-database` (verifiable v2/v3 DAG), and `aof-db` (fast append-only) — across npm / PyPI / crates.io with full mac + linux + windows native addons (see [**Releasing**](#releasing) below). All native wheels (Linux + Windows on GitHub Actions; macOS arm64 + x86_64 on Codemagic M2 Mac Minis) **plus** the universal pure-Python wheel ship from a single `v*` tag, with the `nedbd-v2` binary bundled inside `pip install nedb-engine`.
26
+
27
+ ### New in 2.8.0 — Cast: the database understands English
28
+
29
+ `POST /v1/databases/<name>/cast` turns a short English prompt into NQL, using a **3.33M-parameter model that runs locally on CPU**. No API key, no network call, no per-token bill.
30
+
31
+ ```bash
32
+ nedbd --dag --cast ./data # requires: cargo install nedb-engine --features cast
33
+
34
+ curl -X POST localhost:7070/v1/databases/shop/cast \
35
+ -d '{"prompt":"orders over 100"}'
36
+ # → {"nql":"FROM orders WHERE total > 100","valid":true,"collection_known":true,"executed":false}
37
+ ```
38
+
39
+ The model ([**nedb-cast-slm**](https://github.com/aiassistsecure/nedb-cast-slm)) was trained on NQL using NEDB's own parser as generator, grader, and gate — then shipped to PyPI, crates.io, and npm, all three loading the identical weights.
40
+
41
+ **Why it lives in the engine and not in a client:** the hard part of natural-language querying is knowing the schema, and the engine already holds the live collection list. A plan naming a collection that doesn't exist returns **422 with the reason**, never a silently empty result set. See [**Cast**](#cast--natural-language-into-nql) below.
42
+
43
+ Off by default — feature-gated at compile time, flag-gated at runtime, and `execute` defaults to `false` so you review the plan before it runs.
44
+
45
+ **Also in 2.8.0 — an engine bug the feature exposed.** `IdIndex::collections()` did a bare `read_dir` while every other read path overlaid the WAL write buffer, so a brand-new collection was invisible until the 1s flush ticker fired. Unreachable by hand (the ticker fires between keystrokes) but reliable from a script, and latent in `Db::compact()` too, where a missed collection's live objects would be reclaimed as garbage. Fixed, with regression tests that seed *without* flushing.
26
46
 
27
47
  **New in 2.5.x:**
28
48
 
@@ -304,6 +324,8 @@ Alongside the daemon, `cargo install nedb-engine` ships **`nedb-cli`** — opera
304
324
  | `NEDBD_TOKEN` | unset | Optional bearer token; required on every `/v1/*` request when set. |
305
325
  | `NEDB_TMK` | unset | 32-byte hex AES-256-GCM at-rest encryption key. |
306
326
  | `NEDBD_DATA` | `./nedb-data` | Root directory. v2 creates `dag/`, IdIndex sharded across **256 subdirectories**, and a small `MANIFEST` file. |
327
+ | `NEDBD_CAST` | `0` | Set `1` to enable the `/cast` natural-language planner. Same as `--cast`. Requires a build with `--features cast`. See [**Cast**](#cast--natural-language-into-nql). |
328
+ | `NEDBD_CAST_MODEL` | unset | Explicit path to a `model.cast` container. Otherwise searched in the data dir, `$CAST_HOME`, and `~/.cache/nedb-cast-slm/`. |
307
329
 
308
330
  ```bash
309
331
  # Create a database with seed data and relations
@@ -360,6 +382,127 @@ db.query('FROM policy AS OF 200 VALID AS OF "2024-02-15"')
360
382
 
361
383
  ---
362
384
 
385
+ ## Cast — natural language into NQL
386
+
387
+ *New in 2.8.0. Optional, feature-gated, off by default.*
388
+
389
+ Ten clauses and six operators. That's the whole grammar above — small enough that a **3.33M-parameter** model can learn it completely, and small enough that shipping every query to a frontier model is an absurd amount of machinery.
390
+
391
+ So we trained one. It runs on CPU, in-process, in milliseconds.
392
+
393
+ ```bash
394
+ curl -X POST localhost:7070/v1/databases/shop/cast \
395
+ -H 'Content-Type: application/json' \
396
+ -d '{"prompt":"orders over 100"}'
397
+ ```
398
+
399
+ ```json
400
+ {
401
+ "prompt": "orders over 100",
402
+ "nql": "FROM orders WHERE total > 100",
403
+ "valid": true,
404
+ "collection": "orders",
405
+ "collection_known": true,
406
+ "collections": ["orders"],
407
+ "executed": false,
408
+ "seq": 3,
409
+ "head": "262fd9…"
410
+ }
411
+ ```
412
+
413
+ ### NEDB is on both ends of this
414
+
415
+ The model is [**nedb-cast-slm**](https://github.com/aiassistsecure/nedb-cast-slm), and NEDB built it as much as it consumes it.
416
+
417
+ **NEDB's parser was the training pipeline.** It generated the corpus (sample a random plan → render NQL → render a human paraphrase; 200,000 pairs in 16.5 seconds, perfect labels, zero annotation cost). It was the grader — scoring *parsed plan* equality, not string equality, so `FROM orders WHERE total > 99` and `from orders where total>99` both earn full credit. And it was the gate: no example entered the corpus unless it round-tripped through the real parser to a canonically identical plan.
418
+
419
+ > Most text-to-DSL projects hand-write a verifier and hope it's right. We didn't write one — it already shipped, and it's the same code the database runs in production.
420
+
421
+ **Training lineage lives in NEDB too**, chained by `caused_by`:
422
+
423
+ ```
424
+ datasets ──▶ training_runs ──▶ checkpoints ──▶ evals
425
+ ```
426
+
427
+ ```python
428
+ db.query("FROM evals TRACE caused_by") # the exact data behind any score
429
+ ```
430
+
431
+ ### Why the planner lives in the engine
432
+
433
+ The hard part of natural-language querying is not the model. It's the **schema** — and a client has to fetch the collection list and pass it in, where it's stale on arrival. The engine already holds the live list.
434
+
435
+ So the plan is checked against collections that actually exist, at the moment of the call:
436
+
437
+ ```json
438
+ { "prompt": "show me all stylists",
439
+ "nql": "FROM stylists",
440
+ "valid": true,
441
+ "collection_known": false,
442
+ "error": "collection \"stylists\" does not exist in \"shop\"" }
443
+ ```
444
+
445
+ HTTP **422**. Not zero rows — zero rows reads as *"no matching data"*, which would be a lie. The query was perfectly well-formed; the collection was imagined. That's the model's known failure mode on an unfamiliar schema, and the engine is the one component positioned to catch it.
446
+
447
+ Every `nedbd` client — Python, Node, Studio, `curl` — inherits this without writing a line.
448
+
449
+ ### Three safety properties
450
+
451
+ | | |
452
+ |---|---|
453
+ | **The model never executes** | It emits text. The text goes to the same `nql::query` path a hand-typed query uses. No second executor exists to audit. |
454
+ | **Validation is parsing** | `nql::parse` and `nql::execute` share one code path, so they cannot disagree about what is well-formed. Invalid output returns 422 *with the offending text*. |
455
+ | **`execute` defaults to false** | You get a plan for review. Running a guess silently is worse than admitting uncertainty. |
456
+
457
+ That last default earns its keep. A real miss, from a real run:
458
+
459
+ ```
460
+ prompt "paid orders over 100"
461
+ nql FROM orders WHERE status = "paid" LIMIT 100 ← wrong
462
+ correct FROM orders WHERE status = "paid" AND total > 100
463
+ ```
464
+
465
+ It read *"over 100"* as `LIMIT 100` and dropped the predicate. The count still came back **2** — because both paid orders happened to exceed 100. A count-only assertion would have scored it a pass. A human reading `LIMIT 100` catches it in a heartbeat; an auto-executing client does not.
466
+
467
+ Multi-predicate `WHERE` is the model's weakest clause: **85.1%** exact-plan match on eval, 61.2% on adversarial holdout. The [model card](https://github.com/aiassistsecure/nedb-cast-slm#what-it-gets-wrong) documents every failure mode with examples.
468
+
469
+ To run it anyway, ask:
470
+
471
+ ```bash
472
+ curl -X POST localhost:7070/v1/databases/shop/cast \
473
+ -d '{"prompt":"orders over 100","execute":true}'
474
+ # → { …, "executed": true, "count": 2, "rows": [ … ] }
475
+ ```
476
+
477
+ ### Enabling it
478
+
479
+ Two gates, because most deployments want neither the model dependency nor the weights:
480
+
481
+ ```bash
482
+ # compile-time
483
+ cargo install nedb-engine --features cast
484
+
485
+ # weights (~13 MB) — GitHub release asset, checksum-verified on load
486
+ curl -L -o ./data/model.cast \
487
+ https://github.com/aiassistsecure/nedb-cast-slm/releases/download/v10.30.90/model.cast
488
+
489
+ # runtime
490
+ nedbd --dag --cast ./data
491
+ # cast enabled — 3.33M params, vocab 581, ./data/model.cast
492
+ ```
493
+
494
+ Search order: `$NEDBD_CAST_MODEL` → `<data_dir>/model.cast` → `$CAST_HOME/model.cast` → `~/.cache/nedb-cast-slm/v10.30.90/model.cast` (the Python/npm cache location, so a machine that has run either package is already ready).
495
+
496
+ Built without the feature, the route returns **501** rather than 404 — clients can detect the capability instead of guessing. Built with it but missing weights, the daemon logs loudly and serves everything else normally.
497
+
498
+ **Verify the whole path:**
499
+
500
+ ```bash
501
+ ./scripts/test-cast.sh --boot # boots a daemon, seeds, casts, executes, checks failure modes
502
+ ```
503
+
504
+ ---
505
+
363
506
  ## Performance
364
507
 
365
508
  **v2 DAG Rust server (v2.2.31, Intel iMac — 10k writes / 100k reads / 30k objects, AES-256-GCM on):**
@@ -60,9 +60,13 @@ npm install nedb-engine # or: pip install nedb-engine
60
60
  # v2 DAG engine (recommended — instant cold start, tamper-evident)
61
61
  NEDBD_DAG=1 nedbd --data ./data
62
62
 
63
+ # With natural-language planning (see `cast` below)
64
+ # needs a build with --features cast, plus model.cast in the data dir
65
+ NEDBD_DAG=1 NEDBD_CAST=1 nedbd --data ./data
66
+
63
67
  # Check health
64
68
  curl http://127.0.0.1:7070/health
65
- # {"ok":true,"version":"2.0.8","service":"nedbd","encrypted":true}
69
+ # {"ok":true,"version":"2.8.0","service":"nedbd","encrypted":true}
66
70
  ```
67
71
 
68
72
  ---
@@ -126,6 +130,65 @@ FROM <collection>
126
130
  [SEARCH "text"] full-text search
127
131
  ```
128
132
 
133
+ ### Cast — natural language into NQL
134
+
135
+ | Method | Description |
136
+ |--------|-------------|
137
+ | `db.cast(prompt, opts?)` | English → `CastResult` (the plan). `opts.execute` defaults to `false` |
138
+ | `db.castNql(prompt)` | English → just the NQL string |
139
+
140
+ Requires a daemon built with `--features cast` and started with `--cast`. A **3.33M-parameter** model ([nedb-cast-slm](https://github.com/aiassistsecure/nedb-cast-slm)) runs server-side on CPU — no API key, no per-token bill.
141
+
142
+ ```typescript
143
+ const plan = await db.cast("orders over 100");
144
+ // {
145
+ // nql: 'FROM orders WHERE total > 100',
146
+ // valid: true,
147
+ // collection: 'orders',
148
+ // collection_known: true,
149
+ // collections: ['orders'],
150
+ // executed: false,
151
+ // seq: 3, head: '262fd9…'
152
+ // }
153
+ ```
154
+
155
+ **`execute` defaults to `false` on purpose.** You get a plan to look at, not a query that already ran:
156
+
157
+ ```typescript
158
+ if (plan.valid && plan.collection_known) {
159
+ const rows = await db.query(plan.nql); // review, then run
160
+ }
161
+
162
+ const run = await db.cast("orders over 100", { execute: true });
163
+ run.count; // 2
164
+ run.rows; // [...]
165
+ ```
166
+
167
+ That default matters. A real miss from a real run:
168
+
169
+ ```
170
+ prompt "paid orders over 100"
171
+ nql FROM orders WHERE status = "paid" LIMIT 100 ← wrong
172
+ correct FROM orders WHERE status = "paid" AND total > 100
173
+ ```
174
+
175
+ It read *"over 100"* as `LIMIT 100`. The count still came back correct **by coincidence** — both paid orders exceeded 100. Multi-predicate `WHERE` is the model's weakest clause (85.1% eval / 61.2% holdout). Show the plan to a human when you can.
176
+
177
+ **Errors are explicit, never empty results.** The server checks the plan against the collections this database actually has:
178
+
179
+ ```typescript
180
+ try {
181
+ await db.cast("show me all stylists");
182
+ } catch (e) {
183
+ if (e instanceof NedbError) {
184
+ e.status; // 422
185
+ e.message; // collection "stylists" does not exist in "shop" (generated: "FROM stylists")
186
+ }
187
+ }
188
+ ```
189
+
190
+ Zero rows would read as *"no matching data"* — a lie, when the truth is the collection was imagined. Status **501** means the daemon was built without the feature, so you can detect the capability rather than guess at it.
191
+
129
192
  ### Integrity
130
193
 
131
194
  | Method | Description |
@@ -184,6 +247,7 @@ One HTTP request, multiple ops — best throughput for bulk ingestion.
184
247
 
185
248
  - **Engine:** [pip install nedb-engine](https://pypi.org/project/nedb-engine/) · [npm install nedb-engine](https://www.npmjs.com/package/nedb-engine)
186
249
  - **Python client:** [pip install nedb-engine-client](https://pypi.org/project/nedb-engine-client/)
250
+ - **Cast model:** [nedb-cast-slm](https://github.com/aiassistsecure/nedb-cast-slm) — the 3.3M-param planner behind `db.cast()`
187
251
  - **Source:** [github.com/Eth-Interchained/nedb](https://github.com/Eth-Interchained/nedb)
188
252
  - **Studio:** [studio.interchained.org](https://studio.interchained.org)
189
253
 
@@ -75,9 +75,13 @@ NEDBD_DAG=1 nedbd --data ./data
75
75
  # With AES-256-GCM encryption
76
76
  NEDBD_DAG=1 NEDB_TMK=<32-byte-hex> nedbd --data ./data
77
77
 
78
+ # With natural-language planning (see `cast` below)
79
+ # needs a build with --features cast, plus model.cast in the data dir
80
+ NEDBD_DAG=1 NEDBD_CAST=1 nedbd --data ./data
81
+
78
82
  # Check health
79
83
  curl http://127.0.0.1:7070/health
80
- # {"ok":true,"version":"2.0.8","service":"nedbd","encrypted":true}
84
+ # {"ok":true,"version":"2.8.0","service":"nedbd","encrypted":true}
81
85
  ```
82
86
 
83
87
  ---
@@ -141,6 +145,60 @@ FROM <collection>
141
145
  [SEARCH "text"] full-text search
142
146
  ```
143
147
 
148
+ ### Cast — natural language into NQL
149
+
150
+ | Method | Description |
151
+ |--------|-------------|
152
+ | `await db.cast(prompt, execute=False)` | English → dict (the plan) |
153
+ | `await db.cast_nql(prompt)` | English → just the NQL string |
154
+
155
+ Requires a daemon built with `--features cast` and started with `--cast`. A **3.33M-parameter** model ([nedb-cast-slm](https://github.com/aiassistsecure/nedb-cast-slm)) runs server-side on CPU — no API key, no per-token bill.
156
+
157
+ ```python
158
+ plan = await db.cast("orders over 100")
159
+ # {'prompt': 'orders over 100',
160
+ # 'nql': 'FROM orders WHERE total > 100',
161
+ # 'valid': True,
162
+ # 'collection': 'orders',
163
+ # 'collection_known': True,
164
+ # 'collections': ['orders'],
165
+ # 'executed': False,
166
+ # 'seq': 3, 'head': '262fd9…'}
167
+ ```
168
+
169
+ **`execute` defaults to `False` on purpose.** You get a plan to look at, not a query that already ran:
170
+
171
+ ```python
172
+ if plan["valid"] and plan["collection_known"]:
173
+ rows = await db.query(plan["nql"]) # review, then run
174
+
175
+ run = await db.cast("orders over 100", execute=True)
176
+ run["count"] # 2
177
+ run["rows"] # [...]
178
+ ```
179
+
180
+ That default matters. A real miss from a real run:
181
+
182
+ ```
183
+ prompt "paid orders over 100"
184
+ nql FROM orders WHERE status = "paid" LIMIT 100 ← wrong
185
+ correct FROM orders WHERE status = "paid" AND total > 100
186
+ ```
187
+
188
+ It read *"over 100"* as `LIMIT 100`. The count still came back correct **by coincidence** — both paid orders exceeded 100. Multi-predicate `WHERE` is the model's weakest clause (85.1% eval / 61.2% holdout). Show the plan to a human when you can.
189
+
190
+ **Errors are explicit, never empty results.** The server checks the plan against the collections this database actually has:
191
+
192
+ ```python
193
+ try:
194
+ await db.cast("show me all stylists")
195
+ except NedbError as e:
196
+ e.status # 422
197
+ e.message # collection "stylists" does not exist in "shop" (generated: 'FROM stylists')
198
+ ```
199
+
200
+ Zero rows would read as *"no matching data"* — a lie, when the truth is the collection was imagined. Status **501** means the daemon was built without the feature, so you can detect the capability rather than guess at it.
201
+
144
202
  ### Integrity
145
203
 
146
204
  | Method | Description |
@@ -183,6 +241,7 @@ Queries on missing collections return `[]` rather than raising — resilient by
183
241
 
184
242
  - **Engine:** [pip install nedb-engine](https://pypi.org/project/nedb-engine/) · [npm install nedb-engine](https://www.npmjs.com/package/nedb-engine)
185
243
  - **JS/TS client:** [npm install nedb-engine-client](https://www.npmjs.com/package/nedb-engine-client)
244
+ - **Cast model:** [nedb-cast-slm](https://github.com/aiassistsecure/nedb-cast-slm) — the 3.3M-param planner behind `db.cast()`
186
245
  - **Source:** [github.com/Eth-Interchained/nedb](https://github.com/Eth-Interchained/nedb)
187
246
  - **Studio:** [studio.interchained.org](https://studio.interchained.org)
188
247
 
@@ -10,7 +10,7 @@ build-backend = "hatchling.build"
10
10
 
11
11
  [project]
12
12
  name = "nedb-engine"
13
- version = "2.7.2"
13
+ version = "2.8.1"
14
14
  description = "NEDB — a versioned, self-compressing, time-traveling embedded database (replay-protected, idempotent, relational, searchable) with durable AOF persistence and a server daemon (nedbd)."
15
15
  readme = "README.md"
16
16
  requires-python = ">=3.8"
@@ -94,4 +94,4 @@ __all__ = [
94
94
  "verify_proof", "fold_head",
95
95
  "_native", "__has_native__",
96
96
  ]
97
- __version__ = "2.7.2"
97
+ __version__ = "2.8.1"
Binary file
@@ -57,6 +57,7 @@ nedbd [OPTIONS] [data_dir]
57
57
  |---|---|---|
58
58
  | `data_dir` | `./nedb-data` | Directory for database files |
59
59
  | `--dag` | off | Use the v2 content-addressed DAG engine |
60
+ | `--cast` | off | Enable natural-language query planning (requires `--features cast`) |
60
61
  | `--doctor` | — | Diagnose environment, print fix commands |
61
62
 
62
63
  ### Environment variables
@@ -69,6 +70,8 @@ nedbd [OPTIONS] [data_dir]
69
70
  | `NEDB_TMK` | — | 64-char hex master key for AES-256-GCM encryption at rest |
70
71
  | `NEDBD_MEMORY` | — | `1` = pure in-memory mode (no disk I/O) |
71
72
  | `NEDBD_DAG` | — | `1` = same as `--dag` flag |
73
+ | `NEDBD_CAST` | — | `1` = same as `--cast` flag |
74
+ | `NEDBD_CAST_MODEL` | — | Explicit path to a `model.cast` container |
72
75
 
73
76
  ---
74
77
 
@@ -86,12 +89,128 @@ POST /v1/databases/<name>/put {coll, id, doc, caused_by?}
86
89
  POST /v1/databases/<name>/query {nql}
87
90
  POST /v1/databases/<name>/link {frm, rel, to}
88
91
  POST /v1/databases/<name>/neighbors {node, rel}
92
+ POST /v1/databases/<name>/cast {prompt, execute?} # feature = "cast"
89
93
  GET /v1/databases/<name>/verify
90
94
  POST /v1/databases/<name>/checkpoint
91
95
  ```
92
96
 
93
97
  ---
94
98
 
99
+ ## Cast — natural language into NQL
100
+
101
+ *Optional. Feature-gated, off by default.*
102
+
103
+ ```bash
104
+ curl -X POST localhost:7070/v1/databases/shop/cast \
105
+ -H 'Content-Type: application/json' \
106
+ -d '{"prompt":"orders over 100"}'
107
+ ```
108
+
109
+ ```json
110
+ {
111
+ "prompt": "orders over 100",
112
+ "nql": "FROM orders WHERE total > 100",
113
+ "valid": true,
114
+ "collection": "orders",
115
+ "collection_known": true,
116
+ "collections": ["orders"],
117
+ "executed": false,
118
+ "seq": 3,
119
+ "head": "262fd9…"
120
+ }
121
+ ```
122
+
123
+ A **3.33M-parameter** model ([nedb-cast-slm](https://github.com/aiassistsecure/nedb-cast-slm)) turns a short English prompt into NQL. It runs locally on CPU in a few milliseconds. No API key, no network call, no tokens billed to anyone.
124
+
125
+ ### Why this lives in the engine
126
+
127
+ The hard part of natural-language querying isn't the model — it's **knowing the schema**. A client has to fetch the collection list and pass it in, and it's stale the moment it arrives. The engine already holds the live list.
128
+
129
+ So the planner is constrained against real collections at the instant of the call:
130
+
131
+ ```json
132
+ { "prompt": "show me all stylists",
133
+ "nql": "FROM stylists",
134
+ "valid": true,
135
+ "collection_known": false,
136
+ "error": "collection \"stylists\" does not exist in \"shop\"" }
137
+ ```
138
+
139
+ HTTP **422**. Not an empty result set — an empty result set reads as *"no matching rows"*, which would be a lie. The query was fine; the collection was imaginary.
140
+
141
+ Every `nedbd` client inherits this for free instead of reimplementing it.
142
+
143
+ ### Three safety properties
144
+
145
+ **1 · The model never executes anything.** It emits text. That text goes to the same `nql::query` path a hand-typed query uses. There is no second executor to audit.
146
+
147
+ **2 · Validation is parsing, not pattern-matching.** `nql::parse` and `nql::execute` share one code path, so they cannot disagree about what is well-formed. Unparseable output returns 422 *with the offending text attached*.
148
+
149
+ **3 · `execute` defaults to `false`.** You get a plan for review. Opt in explicitly:
150
+
151
+ ```bash
152
+ curl -X POST localhost:7070/v1/databases/shop/cast \
153
+ -d '{"prompt":"orders over 100","execute":true}'
154
+ # → { …, "executed": true, "count": 2, "rows": [ … ] }
155
+ ```
156
+
157
+ That default is not decoration. Here is a real miss, from a real run:
158
+
159
+ ```
160
+ prompt "paid orders over 100"
161
+ nql FROM orders WHERE status = "paid" LIMIT 100 ← wrong
162
+ correct FROM orders WHERE status = "paid" AND total > 100
163
+ ```
164
+
165
+ It read *"over 100"* as `LIMIT 100` and dropped the second predicate. The row count still came back **2**, because both paid orders happened to exceed 100 — a count-only check would have called that a pass. A human reading `LIMIT 100` catches it instantly. An auto-executing client does not.
166
+
167
+ Multi-predicate `WHERE` is the model's weakest clause (**85.1%** eval / 61.2% holdout). Ship accordingly.
168
+
169
+ ### Enabling it
170
+
171
+ Off by default because it pulls a model dependency and expects weights at runtime — most deployments want neither.
172
+
173
+ ```bash
174
+ # 1. build with the feature
175
+ cargo install nedb-engine --features cast
176
+
177
+ # 2. get the weights (~13 MB)
178
+ curl -L -o model.cast \
179
+ https://github.com/aiassistsecure/nedb-cast-slm/releases/download/v10.30.90/model.cast
180
+
181
+ # 3. run
182
+ nedbd --dag --cast ./data
183
+ # cast enabled — 3.33M params, vocab 581, ./data/model.cast
184
+ ```
185
+
186
+ Model search order, first hit wins:
187
+
188
+ 1. `$NEDBD_CAST_MODEL`
189
+ 2. `<data_dir>/model.cast`
190
+ 3. `$CAST_HOME/model.cast`
191
+ 4. `~/.cache/nedb-cast-slm/v10.30.90/model.cast` — where the Python and npm packages cache it, so a machine that has run either one is already ready
192
+
193
+ The container is checksum-verified on load. A silently corrupt model would emit plausible-but-wrong queries, which is the worst possible failure mode for a query planner.
194
+
195
+ Without the feature the route still exists and returns **501** — so a client can detect the capability instead of guessing. Missing weights on a `--cast` build is loud but non-fatal: the daemon starts and serves everything else normally.
196
+
197
+ ### As a library
198
+
199
+ ```rust
200
+ use nedb_engine::cast::Caster;
201
+
202
+ let caster = Caster::load(&data_dir)?;
203
+ let out = caster.cast_checked("orders over 100", &db.id_index.collections());
204
+
205
+ if out.collection_known && nedb_engine::nql::parse(&out.nql).is_ok() {
206
+ let (rows, count) = nedb_engine::nql::query(&db, &out.nql)?;
207
+ }
208
+ ```
209
+
210
+ Decoding is greedy and deterministic — a DSL has exactly one right answer, so sampling could only hurt. The same prompt always produces the same plan, which makes the endpoint safe to cache.
211
+
212
+ ---
213
+
95
214
  ## NQL — NEDB Query Language
96
215
 
97
216
  ```sql
Binary file
Binary file
File without changes
File without changes