journal
An append-only, hash-chained, tenant-partitioned event log and the deterministic projection of that log into state. Open or attach a journal over a `store` backend, `append` attributed events (each returning a committed `[entry]` with its sequence, content hash, and predecessor link), `read` / `slice` / `query` ranges, and `fold` the log into a state projection. The chain is deterministic — replayable, dry-runnable, and hash-verifiable — which is the entire reason it exists. `snapshot`, `retain`, and `compact` accelerate and bound replay as derived artifacts; they never edit the live chain, so append-only and chain integrity hold permanently.
journal:open
[$journal:open] -> element — Open a journal over a `store` backend URL for one tenant, creating or attaching to its hash chain.
[?lib 'cx-stdlib/journal']
[?let [= $j [$journal:open "mem://acme" "acme"]]
[$journal:append $j [do :refund] {actor: "ops" authority: "d-77"}]]
[entry seq=1 tenant=acme actor=ops authority=d-77 ts='epoch:00:00:01' prev-hash=b3:GENESIS hash=sha256:bdc6c60e64d8bc43a17fc1762633a915d8816ca2dffd429b45ce19dda82d6178 [event [do :refund]]]
journal:attach
[$journal:attach] -> element — Attach a journal to an already-open `store` handle for one tenant, reusing its connection.
[?lib 'cx-stdlib/store']
[?lib 'cx-stdlib/journal']
[?let [= $s [$store:open "mem://r"]]
[= $j1 [$journal:attach $s "acme"]]
[= $a [$journal:append $j1 [a 1] {actor: "u" authority: "g"}]]
[= $b [$journal:append $j1 [b 2] {actor: "u" authority: "g"}]]
[= $c [$journal:close $j1]]
[= $j2 [$journal:attach $s "acme"]]
[$journal:verify $j2]]
[verification valid=true checked-from=1 checked-to=2 head-hash='sha256:2bcb8230b14039e30cc935fb9af79aa4047d317167b0e6edb95e674a82b7e8c2']
journal:close
[$journal:close] -> null — Close the journal handle; subsequent operations on it raise CXER4612.
[?lib 'cx-stdlib/journal']
[?let [= $j [$journal:open "mem://t" "acme"]]
[= $x [$journal:close $j]]
[$journal:append $j [a] {actor: "u" authority: "g"}]]
cx-err:CXER4612
journal:append
[$journal:append] -> element — Commit an attributed event, returning the new `[entry]` with its seq, content hash, and predecessor link.
[?lib 'cx-stdlib/journal']
[?let [= $j [$journal:open "mem://acme" "acme"]]
[$journal:append $j [do :refund] {actor: "ops" authority: "d-77"}]]
[entry seq=1 tenant=acme actor=ops authority=d-77 ts='epoch:00:00:01' prev-hash=b3:GENESIS hash=sha256:bdc6c60e64d8bc43a17fc1762633a915d8816ca2dffd429b45ce19dda82d6178 [event [do :refund]]]
journal:read
[$journal:read] -> element — Return the single `[entry]` at `seq`, or the absence channel if out of range.
[?lib 'cx-stdlib/journal']
[?let [= $j [$journal:open "mem://t" "acme"]]
[= $e1 [$journal:append $j [a 1] {actor: "u" authority: "g"}]]
[= $e2 [$journal:append $j [b 2] {actor: "u" authority: "g"}]]
[$journal:read $j 2]]
[entry seq=2 tenant=acme actor=u authority=g ts='epoch:00:00:02' prev-hash=sha256:f0d42b6c75733de803ac02bb66eecf005add595cadca2f7151f1c682ecb440d0 hash='sha256:2bcb8230b14039e30cc935fb9af79aa4047d317167b0e6edb95e674a82b7e8c2' [event [b 2]]]
journal:slice
[$journal:slice] -> [sequence element] — Return the entries with `from ≤ seq ≤ to` (inclusive, 1-based), in sequence order.
[?lib 'cx-stdlib/journal']
[?let [= $j [$journal:open "mem://t" "acme"]]
[= $e1 [$journal:append $j [a] {actor: "u" authority: "g"}]]
[= $e2 [$journal:append $j [b] {actor: "u" authority: "g"}]]
[= $e3 [$journal:append $j [c] {actor: "u" authority: "g"}]]
[$journal:slice $j 2 3]]
([entry seq=2 tenant=acme actor=u authority=g ts='epoch:00:00:02' prev-hash='sha256:117af1f326b2d1ee4d4b56b66b3fa13dabfbc55045e4cc57740c7c5f026f163d' hash='sha256:9cd1fca3841e0ea3f6db09fc032c901cd1f9b84a7089ebb90c82753c06ef56aa' [event [b]]], [entry seq=3 tenant=acme actor=u authority=g ts='epoch:00:00:03' prev-hash='sha256:9cd1fca3841e0ea3f6db09fc032c901cd1f9b84a7089ebb90c82753c06ef56aa' hash='sha256:6c29ae866f4b35ead2bc4de4bb388382d1c4542ec46828c568a0d63f60117c43' [event [c]]])
journal:since
[$journal:since] -> [sequence element] — Return the tail of the log from `from` to the current head.
[?lib 'cx-stdlib/journal']
[?let [= $j [$journal:open "mem://t" "acme"]]
[= $e1 [$journal:append $j [a] {actor: "u" authority: "g"}]]
[= $e2 [$journal:append $j [b] {actor: "u" authority: "g"}]]
[$journal:since $j 2]]
([entry seq=2 tenant=acme actor=u authority=g ts='epoch:00:00:02' prev-hash='sha256:117af1f326b2d1ee4d4b56b66b3fa13dabfbc55045e4cc57740c7c5f026f163d' hash='sha256:9cd1fca3841e0ea3f6db09fc032c901cd1f9b84a7089ebb90c82753c06ef56aa' [event [b]]])
journal:query
[$journal:query] -> [sequence element] — Return the entries whose `[event …]` payload or envelope matches a CXPath filter.
[?lib 'cx-stdlib/journal']
[?let [= $j [$journal:open "mem://t" "acme"]]
[= $e1 [$journal:append $j [do :refund] {actor: "u" authority: "g"}]]
[= $e2 [$journal:append $j [note "hi"] {actor: "u" authority: "g"}]]
[$journal:query $j "/event/do"]]
([entry seq=1 tenant=acme actor=u authority=g ts='epoch:00:00:01' prev-hash=b3:GENESIS hash=sha256:fcd34ab5565c2823273d24922fe5e7b53d3ffe02fd62695a99b214d409933c30 [event [do :refund]]])
journal:head
[$journal:head] -> element — Return the current head `[entry]`, or absence if the log is empty (pure — reads the cached head).
[?lib 'cx-stdlib/journal']
[?let [= $j [$journal:open "mem://t" "acme"]]
[$journal:head $j]]
()
journal:streams
[$journal:streams] -> [sequence element] — List the distinct stream names present in the journal.
[?lib 'cx-stdlib/journal']
[?let [= $j [$journal:open "mem://t" "acme"]]
[= $a [$journal:append $j [x 1] {actor: "u" authority: "g" stream: "s1"}]]
[= $b [$journal:append $j [x 2] {actor: "u" authority: "g" stream: "s1"}]]
[= $c [$journal:append $j [y 1] {actor: "u" authority: "g" stream: "s2"}]]
[$journal:streams $j]]
('s1', 's2')
journal:fold
[$journal:fold] -> any — Fold the whole log into a state projection with a pure reducer and initial value.
[?lib 'cx-stdlib/journal']
[?let [= $j [$journal:open "mem://t" "acme"]]
[= $e1 [$journal:append $j [a] {actor: "u" authority: "g"}]]
[= $e2 [$journal:append $j [b] {actor: "u" authority: "g"}]]
[= $e3 [$journal:append $j [c] {actor: "u" authority: "g"}]]
[$journal:fold $j [?fn ($a $e) [+ $a 1]] 0]]
3
journal:fold-slice
[$journal:fold-slice] -> any — Fold a `from..to` range of the log into a state projection.
[?lib 'cx-stdlib/journal']
[?let [= $j [$journal:open "mem://t" "acme"]]
[= $e1 [$journal:append $j [a] {actor: "u" authority: "g"}]]
[= $e2 [$journal:append $j [b] {actor: "u" authority: "g"}]]
[= $e3 [$journal:append $j [c] {actor: "u" authority: "g"}]]
[$journal:fold-slice $j [?fn ($a $e) [+ $a 1]] 0 2 3]]
2
journal:fold-value
[$journal:fold-value] -> any — Fold an already-read entry sequence into state, without touching the journal.
[?lib 'cx-stdlib/journal']
[?let [= $j [$journal:open "mem://t" "acme"]]
[= $e1 [$journal:append $j [a] {actor: "u" authority: "g"}]]
[= $e2 [$journal:append $j [b] {actor: "u" authority: "g"}]]
[= $es [$journal:since $j 1]]
[$journal:fold-value $es [?fn ($a $e) [+ $a 10]] 0]]
20
journal:replay
[$journal:replay] -> any — Deterministically re-execute the log into state, honouring replay options such as `at-seq`.
[?lib 'cx-stdlib/journal']
[?let [= $j [$journal:open "mem://t" "acme"]]
[= $e1 [$journal:append $j [a] {actor: "u" authority: "g"}]]
[= $e2 [$journal:append $j [b] {actor: "u" authority: "g"}]]
[= $e3 [$journal:append $j [c] {actor: "u" authority: "g"}]]
[$journal:replay $j [?fn ($a $e) [+ $a 1]] 0 {at-seq: 2}]]
2
journal:dry-run
[$journal:dry-run] -> element — Preview the state and `[entry]` an append would produce, without committing it.
[?lib 'cx-stdlib/journal']
[?let [= $j [$journal:open "mem://t" "acme"]]
[= $e1 [$journal:append $j [a] {actor: "u" authority: "g"}]]
[$journal:dry-run $j [b] {actor: "u" authority: "g"} [?fn ($a $e) [+ $a 1]] 0]]
[dry-run [state 2] [entry seq=2 tenant=acme actor=u authority=g ts='epoch:00:00:02' prev-hash='sha256:117af1f326b2d1ee4d4b56b66b3fa13dabfbc55045e4cc57740c7c5f026f163d' hash='sha256:9cd1fca3841e0ea3f6db09fc032c901cd1f9b84a7089ebb90c82753c06ef56aa' [event [b]]]]
journal:verify
[$journal:verify] -> element — Verify hash-chain integrity end to end, returning a `[verification]` finding (not an error on corruption).
[?lib 'cx-stdlib/journal']
[?let [= $j [$journal:open "mem://t" "acme"]]
[= $e1 [$journal:append $j [a] {actor: "u" authority: "g"}]]
[= $e2 [$journal:append $j [b] {actor: "u" authority: "g"}]]
[$journal:verify $j {}]]
[verification valid=true checked-from=1 checked-to=2 head-hash='sha256:9cd1fca3841e0ea3f6db09fc032c901cd1f9b84a7089ebb90c82753c06ef56aa']
journal:verify-slice
[$journal:verify-slice] -> element — Verify hash-chain integrity over a `from..to` range.
[?lib 'cx-stdlib/journal']
[?let [= $j [$journal:open "mem://t" "acme"]]
[= $e1 [$journal:append $j [a] {actor: "u" authority: "g"}]]
[= $e2 [$journal:append $j [b] {actor: "u" authority: "g"}]]
[= $e3 [$journal:append $j [c] {actor: "u" authority: "g"}]]
[$journal:verify-slice $j 2 3]]
[verification valid=true checked-from=2 checked-to=3 head-hash='sha256:6c29ae866f4b35ead2bc4de4bb388382d1c4542ec46828c568a0d63f60117c43']
journal:snapshot
[$journal:snapshot] -> element — Fold the log up to a sequence into a signed, derived state checkpoint so fold need not re-run from genesis.
[?lib 'cx-stdlib/journal']
[?let [= $j [$journal:open "mem://t" "acme"]]
[= $e1 [$journal:append $j [a] {actor: "u" authority: "g"}]]
[$journal:snapshot $j [?fn ($a $e) [+ $a 1]] 0 {sign: "false"}]]
[snapshot tenant=acme at-seq=1 anchor-hash='sha256:117af1f326b2d1ee4d4b56b66b3fa13dabfbc55045e4cc57740c7c5f026f163d' hash-algo=sha256 sig-algo=none [state 1]]
journal:snapshot-verify
[$journal:snapshot-verify] -> element — Check a `[snapshot]` against the chain and against its own signature.
[?lib 'cx-stdlib/journal']
[?let [= $j [$journal:open "mem://t" "acme"]]
[= $e1 [$journal:append $j [a] {actor: "u" authority: "g"}]]
[= $bad [snapshot tenant=acme at-seq=1 anchor-hash="sha256:deadbeef" hash-algo=sha256 sig-algo=none [state 99]]]
[$journal:snapshot-verify $j $bad]]
[snapshot-verification valid=false at-seq=1 reason=':anchor-mismatch']
journal:fold-from
[$journal:fold-from] -> any — Reconstruct state fast: verify a snapshot, then fold only the tail of entries after it.
[?lib 'cx-stdlib/journal']
[?let [= $j [$journal:open "mem://t" "acme"]]
[= $e1 [$journal:append $j [a] {actor: "u" authority: "g"}]]
[= $e2 [$journal:append $j [b] {actor: "u" authority: "g"}]]
[= $e3 [$journal:append $j [c] {actor: "u" authority: "g"}]]
[= $snap [$journal:snapshot $j [?fn ($a $e) [+ $a 1]] 0 {sign: "false" at-seq: 1}]]
[= $full [$journal:fold $j [?fn ($a $e) [+ $a 1]] 0]]
[= $fast [$journal:fold-from $j $snap [?fn ($a $e) [+ $a 1]]]]
[equiv full=$full fast=$fast]]
[equiv full=3 fast=3]
journal:retain
[$journal:retain] -> element — Record a prune-behind-a-snapshot `[retention]` policy and check its snapshot coverage; deletes nothing.
[?lib 'cx-stdlib/journal']
[?let [= $j [$journal:open "mem://t" "acme"]]
[= $e1 [$journal:append $j [a] {actor: "u" authority: "g"}]]
[= $e2 [$journal:append $j [b] {actor: "u" authority: "g"}]]
[= $snap [$journal:snapshot $j [?fn ($a $e) [+ $a 1]] 0 {signing-key: "0101010101010101010101010101010101010101010101010101010101010101" at-seq: 2}]]
[$journal:retain $j {keep-after-seq: 2 snapshot: $snap}]]
[retention boundary=2 covered-by-seq=2]
journal:compact
[$journal:compact] -> element — Copy a snapshot plus the retained tail forward into a new segment, leaving the source chain intact.
[?lib 'cx-stdlib/journal']
[?let [= $j [$journal:open "mem://src" "acme"]]
[= $e1 [$journal:append $j [a] {actor: "u" authority: "g"}]]
[= $e2 [$journal:append $j [b] {actor: "u" authority: "g"}]]
[= $e3 [$journal:append $j [c] {actor: "u" authority: "g"}]]
[= $snap [$journal:snapshot $j [?fn ($a $e) [+ $a 1]] 0 {signing-key: "0101010101010101010101010101010101010101010101010101010101010101" at-seq: 2}]]
[= $ret [$journal:retain $j {keep-after-seq: 2 snapshot: $snap}]]
[= $seg [$journal:compact $j {retention: $ret target: "mem://seg"}]]
[$journal:verify $j {}]]
[verification valid=true checked-from=1 checked-to=3 head-hash='sha256:6c29ae866f4b35ead2bc4de4bb388382d1c4542ec46828c568a0d63f60117c43']