Liking cljdoc? Tell your friends :D

Changelog

0.19.0 — 2026-09-14 — "a row an earlier core wrote reads back as the current record"

The first cut of this adapter since 0.16.0; it sat 0.17.0 through 0.18.1 out. No table or column moves, and a database written under 0.16.0 opens unchanged.

  • A row written under an earlier core reads back as the current record. A frame is the whole record nippy froze, so a row carries every field the record type had when it was written. Core 0.19.0 drops a sentex's :polarity, a rule's :sentence and a justification's :out, and a rule firing's justification no longer lists the rule handle among its :antecedents. A fetch now passes the thawed record through the engine's codec/decode-sentex / codec/decode-justification, which drop those fields and take the rule out of :antecedents, so a fetch from an upgraded database returns the record core 0.19.0 would have written. Class: Fix. Migration: none; requires core 0.19.0.

  • The dependencies move to Clojure 1.12.6, nippy 3.9.0 and sqlite-jdbc 3.53.4.0. nippy matches the engine's pinned 3.9.0, so the reflection check compiles the engine against the nippy it ships with. Class: Fix. Migration: none.

  • The backend suite declares its rule forward. Core 0.18.0 made a bare implies rule backward-only, so the persist and recover tests' rule stopped materializing its conclusion on assert and the tests read empty. populate! asserts the rule with a forward direction. Class: Fix — a test; no table, column or call moves. Migration: none.

  • The prose check's P4 refuses a copula with are straight after it. A tree-wide substitution that rewrites a phrase without fixing its verb leaves the old copula standing before are, and no sentence needs the pair. A clojure.test require that refers both the is and the are macros, and a hyphenated compound ending in a copula, are excluded. Class: Additive — lint only. Migration: none.

The number. This adapter ships at the engine's version and pins core 0.19.0, which carries five Breaking entries and four Refusals. Three of the Breaking entries change the fields of the records this store freezes: a sentex drops :polarity, a rule drops :sentence, and a justification drops :out and the rule handle from :antecedents. The first entry above is how those three reach this store. The other two Breaking entries and the four Refusals read a KB rather than a store and reach nothing here.

0.16.0 — 2026-09-04 — "the prose says what the code does, and nothing else moves"

No behaviour change, no schema change and nothing a caller can observe. A database written under 0.15.0 opens unchanged and the tables do not move.

  • The prose here is reworded to say what the code does. Comments, docstrings, changelog entries and test names now state the mechanism in literal technical language, and scripts/check-prose.py — the engine's check, adapted to this tree's layout — runs as the prose row in scripts/lint.sh with a lein lint-prose alias. The two metaphors this repo carried are replaced by what each one named: the engine's storage protocols (RecordStore, SnapshotSink / SnapshotSource) and the public entry point a caller reaches them through. The evaluative phrasings become statements of what the assertion checks. Class: Fix — docstrings and test names only; no table, column or call moves. Migration: none.

The number. The engine, the plugin and both adapters ship one version string, checked at the cut. Requires core 0.16.0, which carries three Breaking entries and one Refusal: a :missing-adapter refusal split out of :unknown-backend, a CLI operand count moved from :unknown-option to :bad-args, different no longer provable against an unpinned indeterminate_term, and a rule concluding an indeterminacy from a different antecedent refused as :not-stratified. The first reaches this repo in one direction only: with this adapter on the classpath, a :sqlite records axis resolves and no refusal is raised. The other three read a KB rather than a store and reach nothing here.

0.15.0 — 2026-09-01 — "nothing moved, and the family ships one number"

The first cut of this adapter since 0.13.0 — it sat 0.14.0 out — and it carries no behaviour change, no schema change and nothing a caller can observe. A database written under 0.13.0 opens unchanged and the tables do not move.

  • The sentex fixtures name the engine's slot, which is now :polarity. The engine renamed the sentex map's :truth slot to :polarity with :positive / :negative values, and renamed the AtomicSentex record to LiteralSentex. Neither reaches this store, and the reason is the property the store is built on: it freezes the whole record with nippy and thaws it back type-identical, so what a field is called is not something it has an opinion about, and a record frozen under 0.13.0 thaws back under the new class without a migration. The only places the old spellings appeared were the hand-built test fixtures and the schema docstring that promises the round-trip. Class: Fix — a test and a docstring; no table, column or call moves.

The number. The engine, the plugin and both adapters ship one version string, checked at the cut. Requires core 0.15.0.

0.13.0 — 2026-08-25 — "the absent database, and the entry points that stay quiet"

  • A source over a database no sink has written answers absent, and cleanup is the no-op it claims. read-manifest on a file with no image tables answers nil — the protocol's absent case — so a first-run load-index! reports {:index :rebuild :reason :absent} instead of throwing no such table, and drop-image! skips a table that is not there, as its docstring says. Class: Fix. Migration: none.

  • The four quiet entry points are quiet. put-provenance, delete-provenance!, mark-premise and unmark-premise! gate on id-ok? the way every fetch does, so a handle this store could never have issued — an informant keyword — is a no-op rather than a driver error or a ClassCastException out of the cache key. Class: Fix. Migration: none.

  • A failed open releases its connection. sqlite-record-store and sqlite-sink close the connection they borrowed when construction throws past the borrow, so a retried open of a corrupt or locked file no longer leaks a handle and its WAL per attempt. Class: Fix. Migration: none.

0.12.0 — 2026-08-23 — "a KB image and a live store, in SQLite"

First release of the SQLite sibling (com.vaelii/sqlite) — an Apache-2.0 adapter on the SSPL engine. It depends on core; core never depends on it. Requires core 0.12.0: the record store answers protocols that land there — the bulk sink, the tallies, the two bare BulkAnnotating writes — and reads vaelii.impl.profile/record-fetch, which lands in 0.11.0. The family releases in lockstep, one version string across the engine, the plugin and the two adapters, which is why a first release is numbered 0.12.0.

  • A bulk load is a transaction per batch, and import! takes it. A put here is a transaction of its own — a commit and a WAL write per record — which is the whole of what a corpus load pays. The store answers core's BulkLoading protocol (open-sentex-sink / open-justification-sink) with a sink that lands :batch rows per transaction in one execute-batch!, so an import! writes its records through it without naming this namespace. It is a load and not an upsert — a plain INSERT, so a handle the store already holds raises on the primary key rather than being replaced silently, which is the contract the Postgres adapter's COPY has. Through import! on a 30k-record dump, {:belief? false}: 7,444 → 16,572 records/s, against core's :disk at 6,790. Class: Additive — a new optional capability; a caller that does not open a sink writes exactly as it did.

  • The premise marks and the provenance batch too. A statement here is a transaction of its own, so core's BulkAnnotating lands as one execute-batch! inside one transaction for each — the same UPDATE and the same upsert the one-row ops run, with the same WHERE guarding against marking a handle that has no sentex. It is what an import! at {:belief? true} or {:belief? :stored} writes its marks and its provenance through. Class: Additive — a caller that does not ask for a batch writes exactly as it did.

  • The roster is compressed, and the tallies leave it alone. The three enumerations answer core's vaelii.impl.roster rather than a PersistentHashSet<Long> — at 48–75 bytes a handle the roster of a large store is the caller's heap, and handles arrive in the near-contiguous run next-id mints, which a bitmap holds at a fraction of a byte apiece and probes faster. The walk goes through jdbc/plan rather than execute!, so it never builds a result map per row on the way to discarding it. Beside it, core's Tallying: open-kb asks how many records and is there one at all twice before the KB has answered anything, and each is now a count(*) or a LIMIT 1 instead of a table scan and a roster built out of it. Class: Additive — the enumerations answer the same handles, and the protocol's contract is what says a set may be either shape.

  • A SQLite snapshot sink and source. vaelii.sqlite.snapshot provides sqlite-sink (a SnapshotSink writing a KB image to a single-file database) and sqlite-source (a read-only SnapshotSource reading it back), over the engine's snapshot protocol (vaelii.impl.io.snapshot). It holds the index projection today and any of the protocol's named sections as they land. A section written through this sink reads back frame-identical through any source — file, memory, or SQLite — and a mismatched image is discarded and rebuilt, never trusted (the engine's shared snapshot/decision). Class: Additive — a new snapshot target; nothing in core changes.

  • A live embedded-SQLite record store, wired as the :sqlite backend. vaelii.sqlite.record_store provides a RecordStore over a single-file <dir>/records.sqlite — the durable ground truth a KB reads and writes. Core resolves it lazily under {:records :sqlite} (the sugar :sqlite), so the engine carries no JDBC dependency and a KB that never asks for it loads none; off the classpath the backend refuses by name with the coordinate to add. A durable :disk index over :sqlite records is refused exactly as it is over :memory. Class: Additive — a new records backend; :memory and :disk are unchanged. Verified end to end through core (test/vaelii/sqlite/backend_test.clj).

    Three properties the fetch LRU in front of it owes, each with a test:

    • A cache entry is keyed by the number, not by how it was boxed. Integer 5 and Long 5 are equal numbers and different java.util.Map keys, so a delete whose handle arrives as one must still evict an entry stored under the other — otherwise a record the store no longer holds goes on answering get-sentex for the life of the store. Clojure's own maps normalize integer hashing, which is why the RAM and disk backends have nothing to do here.
    • The three fetches are tallied on the protocol method, as RecordStore requires, so vaelii.impl.profile's :fetches counts what a caller asked for and is comparable with what the other backends report.
    • The connection is released on JVM exit, through the engine's durability daemon, so a process that exits without a close! still lets go of the file and its WAL. fsync is a no-op with a reason: SQLite's synchronous=NORMAL decides when a write lands, and there is no client-side buffer for the engine to force.

Pick by shape: a snapshot is a frozen image you re-export (backup, shipping a corpus); a record store is an always-current backend the KB reads and writes.

Docs: this repo's README.md; the protocol is core's storage.md.

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close