persist: Phase 1 — log + kv facets on injectable in-memory backend + 28 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m3s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m3s
event/backend/log/kv/api over one injected backend protocol (mem default). log: append/read/read-from, sequential per-stream seq, stream isolation. kv: get/put/delete/has?/keys/get-or/update. conformance.sh + 3 suites, 28/28. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
10
lib/persist/api.sx
Normal file
10
lib/persist/api.sx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
; persist/api — the public entry point. persist/open returns a backend (the
|
||||||
|
; in-memory one by default; pass a custom backend to inject file/pg/ipfs-ref).
|
||||||
|
; All facet functions take this backend as their first argument.
|
||||||
|
; Requires: lib/persist/backend.sx, lib/persist/log.sx, lib/persist/kv.sx.
|
||||||
|
|
||||||
|
(define
|
||||||
|
persist/open
|
||||||
|
(fn
|
||||||
|
(&rest args)
|
||||||
|
(if (= (len args) 0) (persist/mem-backend) (first args))))
|
||||||
21
lib/persist/backend.sx
Normal file
21
lib/persist/backend.sx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
; persist/backend — the injected storage protocol. Every facet (log, kv,
|
||||||
|
; snapshot) goes through a backend dict, never touching storage directly, so
|
||||||
|
; file/pg/ipfs-ref backends swap in unchanged. A backend is a dict of fns:
|
||||||
|
; {:append :read :kv-get :kv-put :kv-delete :kv-has? :kv-keys}
|
||||||
|
; The in-memory backend is the test default. State is two dicts held in a
|
||||||
|
; closure and mutated with set!: logs (stream -> event list) and kv.
|
||||||
|
|
||||||
|
(define
|
||||||
|
persist/mem-backend
|
||||||
|
(fn () (let ((logs {}) (kv {})) {:kv-keys (fn () (keys kv)) :read (fn (stream) (let ((cur (get logs stream))) (if cur cur (list)))) :kv-has? (fn (key) (has-key? kv key)) :append (fn (stream event) (let ((cur (get logs stream))) (set! logs (assoc logs stream (append (if cur cur (list)) event))))) :kv-delete (fn (key) (set! kv (dissoc kv key))) :kv-put (fn (key val) (set! kv (assoc kv key val))) :kv-get (fn (key) (get kv key))})))
|
||||||
|
|
||||||
|
; protocol accessors — call a backend op by keyword
|
||||||
|
(define
|
||||||
|
persist/backend-append
|
||||||
|
(fn (b stream event) ((get b :append) stream event)))
|
||||||
|
(define persist/backend-read (fn (b stream) ((get b :read) stream)))
|
||||||
|
(define persist/backend-kv-get (fn (b key) ((get b :kv-get) key)))
|
||||||
|
(define persist/backend-kv-put (fn (b key val) ((get b :kv-put) key val)))
|
||||||
|
(define persist/backend-kv-delete (fn (b key) ((get b :kv-delete) key)))
|
||||||
|
(define persist/backend-kv-has? (fn (b key) ((get b :kv-has?) key)))
|
||||||
|
(define persist/backend-kv-keys (fn (b) ((get b :kv-keys))))
|
||||||
113
lib/persist/conformance.sh
Executable file
113
lib/persist/conformance.sh
Executable file
@@ -0,0 +1,113 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# lib/persist/conformance.sh — run persist test suites, emit scoreboard.json + scoreboard.md.
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
|
||||||
|
SX_SERVER="${SX_SERVER:-/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe}"
|
||||||
|
if [ ! -x "$SX_SERVER" ]; then
|
||||||
|
SX_SERVER="hosts/ocaml/_build/default/bin/sx_server.exe"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$SX_SERVER" ]; then
|
||||||
|
echo "ERROR: sx_server.exe not found." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SUITES=(event log kv)
|
||||||
|
|
||||||
|
OUT_JSON="lib/persist/scoreboard.json"
|
||||||
|
OUT_MD="lib/persist/scoreboard.md"
|
||||||
|
|
||||||
|
run_suite() {
|
||||||
|
local suite=$1
|
||||||
|
local file="lib/persist/tests/${suite}.sx"
|
||||||
|
local TMP
|
||||||
|
TMP=$(mktemp)
|
||||||
|
cat > "$TMP" << EPOCHS
|
||||||
|
(epoch 1)
|
||||||
|
(load "spec/stdlib.sx")
|
||||||
|
(load "lib/r7rs.sx")
|
||||||
|
(load "lib/persist/event.sx")
|
||||||
|
(load "lib/persist/backend.sx")
|
||||||
|
(load "lib/persist/log.sx")
|
||||||
|
(load "lib/persist/kv.sx")
|
||||||
|
(load "lib/persist/api.sx")
|
||||||
|
(epoch 2)
|
||||||
|
(eval "(define persist-test-pass 0)")
|
||||||
|
(eval "(define persist-test-fail 0)")
|
||||||
|
(eval "(define persist-test (fn (name got expected) (if (equal? got expected) (set! persist-test-pass (+ persist-test-pass 1)) (set! persist-test-fail (+ persist-test-fail 1)))))")
|
||||||
|
(epoch 3)
|
||||||
|
(load "${file}")
|
||||||
|
(epoch 4)
|
||||||
|
(eval "(list persist-test-pass persist-test-fail)")
|
||||||
|
EPOCHS
|
||||||
|
|
||||||
|
local OUTPUT
|
||||||
|
OUTPUT=$(timeout 300 "$SX_SERVER" < "$TMP" 2>/dev/null)
|
||||||
|
rm -f "$TMP"
|
||||||
|
|
||||||
|
local LINE
|
||||||
|
LINE=$(echo "$OUTPUT" | awk '/^\(ok-len 4 / {getline; print; exit}')
|
||||||
|
if [ -z "$LINE" ]; then
|
||||||
|
LINE=$(echo "$OUTPUT" | grep -E '^\(ok 4 \([0-9]+ [0-9]+\)\)' | tail -1 \
|
||||||
|
| sed -E 's/^\(ok 4 //; s/\)$//')
|
||||||
|
fi
|
||||||
|
|
||||||
|
local P F
|
||||||
|
P=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\1/')
|
||||||
|
F=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\2/')
|
||||||
|
P=${P:-0}
|
||||||
|
F=${F:-0}
|
||||||
|
echo "${P} ${F}"
|
||||||
|
}
|
||||||
|
|
||||||
|
declare -A SUITE_PASS
|
||||||
|
declare -A SUITE_FAIL
|
||||||
|
TOTAL_PASS=0
|
||||||
|
TOTAL_FAIL=0
|
||||||
|
|
||||||
|
echo "Running persist conformance suite..." >&2
|
||||||
|
for s in "${SUITES[@]}"; do
|
||||||
|
read -r p f < <(run_suite "$s")
|
||||||
|
SUITE_PASS[$s]=$p
|
||||||
|
SUITE_FAIL[$s]=$f
|
||||||
|
TOTAL_PASS=$((TOTAL_PASS + p))
|
||||||
|
TOTAL_FAIL=$((TOTAL_FAIL + f))
|
||||||
|
printf " %-12s %d/%d\n" "$s" "$p" "$((p+f))" >&2
|
||||||
|
done
|
||||||
|
|
||||||
|
# scoreboard.json
|
||||||
|
{
|
||||||
|
printf '{\n'
|
||||||
|
printf ' "suites": {\n'
|
||||||
|
first=1
|
||||||
|
for s in "${SUITES[@]}"; do
|
||||||
|
if [ $first -eq 0 ]; then printf ',\n'; fi
|
||||||
|
printf ' "%s": {"pass": %d, "fail": %d}' "$s" "${SUITE_PASS[$s]}" "${SUITE_FAIL[$s]}"
|
||||||
|
first=0
|
||||||
|
done
|
||||||
|
printf '\n },\n'
|
||||||
|
printf ' "total_pass": %d,\n' "$TOTAL_PASS"
|
||||||
|
printf ' "total_fail": %d,\n' "$TOTAL_FAIL"
|
||||||
|
printf ' "total": %d\n' "$((TOTAL_PASS + TOTAL_FAIL))"
|
||||||
|
printf '}\n'
|
||||||
|
} > "$OUT_JSON"
|
||||||
|
|
||||||
|
# scoreboard.md
|
||||||
|
{
|
||||||
|
printf '# persist Conformance Scoreboard\n\n'
|
||||||
|
printf '_Generated by `lib/persist/conformance.sh`_\n\n'
|
||||||
|
printf '| Suite | Pass | Fail | Total |\n'
|
||||||
|
printf '|-------|-----:|-----:|------:|\n'
|
||||||
|
for s in "${SUITES[@]}"; do
|
||||||
|
p=${SUITE_PASS[$s]}
|
||||||
|
f=${SUITE_FAIL[$s]}
|
||||||
|
printf '| %s | %d | %d | %d |\n' "$s" "$p" "$f" "$((p+f))"
|
||||||
|
done
|
||||||
|
printf '| **Total** | **%d** | **%d** | **%d** |\n' "$TOTAL_PASS" "$TOTAL_FAIL" "$((TOTAL_PASS + TOTAL_FAIL))"
|
||||||
|
} > "$OUT_MD"
|
||||||
|
|
||||||
|
echo "Wrote $OUT_JSON and $OUT_MD" >&2
|
||||||
|
echo "Total: $TOTAL_PASS pass, $TOTAL_FAIL fail" >&2
|
||||||
|
|
||||||
|
[ "$TOTAL_FAIL" -eq 0 ]
|
||||||
13
lib/persist/event.sx
Normal file
13
lib/persist/event.sx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
; persist/event — an event is the unit of the log facet:
|
||||||
|
; {:stream :seq :type :at :data}
|
||||||
|
; stream = which append-only stream, seq = 1-based position within it,
|
||||||
|
; type = event kind, at = caller-supplied timestamp (never a clock here:
|
||||||
|
; replay must stay pure), data = payload dict.
|
||||||
|
|
||||||
|
(define persist/event (fn (stream seq type at data) {:data data :type type :at at :stream stream :seq seq}))
|
||||||
|
|
||||||
|
(define persist/event-stream (fn (e) (get e :stream)))
|
||||||
|
(define persist/event-seq (fn (e) (get e :seq)))
|
||||||
|
(define persist/event-type (fn (e) (get e :type)))
|
||||||
|
(define persist/event-at (fn (e) (get e :at)))
|
||||||
|
(define persist/event-data (fn (e) (get e :data)))
|
||||||
26
lib/persist/kv.sx
Normal file
26
lib/persist/kv.sx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
; persist/kv — the kv facet: current-state values, no history. For things
|
||||||
|
; whose history does NOT matter (stock counts, config, profiles, session
|
||||||
|
; blobs) and where projections materialize their read models.
|
||||||
|
; Requires: lib/persist/backend.sx.
|
||||||
|
|
||||||
|
(define persist/kv-get (fn (b key) (persist/backend-kv-get b key)))
|
||||||
|
(define
|
||||||
|
persist/kv-put
|
||||||
|
(fn (b key val) (begin (persist/backend-kv-put b key val) val)))
|
||||||
|
(define persist/kv-delete (fn (b key) (persist/backend-kv-delete b key)))
|
||||||
|
(define persist/kv-has? (fn (b key) (persist/backend-kv-has? b key)))
|
||||||
|
(define persist/kv-keys (fn (b) (persist/backend-kv-keys b)))
|
||||||
|
|
||||||
|
; get with a default when the key is absent
|
||||||
|
(define
|
||||||
|
persist/kv-get-or
|
||||||
|
(fn
|
||||||
|
(b key dflt)
|
||||||
|
(if (persist/kv-has? b key) (persist/kv-get b key) dflt)))
|
||||||
|
|
||||||
|
; read-modify-write: apply f to the current value (or dflt if absent), store result
|
||||||
|
(define
|
||||||
|
persist/kv-update
|
||||||
|
(fn
|
||||||
|
(b key dflt f)
|
||||||
|
(persist/kv-put b key (f (persist/kv-get-or b key dflt)))))
|
||||||
37
lib/persist/log.sx
Normal file
37
lib/persist/log.sx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
; persist/log — the log facet: append-only event streams. seq is assigned
|
||||||
|
; sequentially per stream (1-based). Reads return events oldest-first.
|
||||||
|
; Requires: lib/persist/event.sx, lib/persist/backend.sx.
|
||||||
|
|
||||||
|
; current length of a stream
|
||||||
|
(define
|
||||||
|
persist/stream-len
|
||||||
|
(fn (b stream) (len (persist/backend-read b stream))))
|
||||||
|
|
||||||
|
; last seq in a stream (0 if empty)
|
||||||
|
(define persist/last-seq (fn (b stream) (persist/stream-len b stream)))
|
||||||
|
|
||||||
|
; append an event, auto-assigning the next seq. Returns the stored event.
|
||||||
|
(define
|
||||||
|
persist/append
|
||||||
|
(fn
|
||||||
|
(b stream type at data)
|
||||||
|
(let
|
||||||
|
((seq (+ 1 (persist/last-seq b stream))))
|
||||||
|
(let
|
||||||
|
((ev (persist/event stream seq type at data)))
|
||||||
|
(begin (persist/backend-append b stream ev) ev)))))
|
||||||
|
|
||||||
|
; read all events in a stream, oldest-first
|
||||||
|
(define persist/read (fn (b stream) (persist/backend-read b stream)))
|
||||||
|
|
||||||
|
; read events with seq >= from
|
||||||
|
(define
|
||||||
|
persist/read-from
|
||||||
|
(fn
|
||||||
|
(b stream from)
|
||||||
|
(filter
|
||||||
|
(fn (e) (>= (persist/event-seq e) from))
|
||||||
|
(persist/read b stream))))
|
||||||
|
|
||||||
|
; number of events in a stream
|
||||||
|
(define persist/count (fn (b stream) (persist/stream-len b stream)))
|
||||||
10
lib/persist/scoreboard.json
Normal file
10
lib/persist/scoreboard.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"suites": {
|
||||||
|
"event": {"pass": 6, "fail": 0},
|
||||||
|
"log": {"pass": 9, "fail": 0},
|
||||||
|
"kv": {"pass": 13, "fail": 0}
|
||||||
|
},
|
||||||
|
"total_pass": 28,
|
||||||
|
"total_fail": 0,
|
||||||
|
"total": 28
|
||||||
|
}
|
||||||
10
lib/persist/scoreboard.md
Normal file
10
lib/persist/scoreboard.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# persist Conformance Scoreboard
|
||||||
|
|
||||||
|
_Generated by `lib/persist/conformance.sh`_
|
||||||
|
|
||||||
|
| Suite | Pass | Fail | Total |
|
||||||
|
|-------|-----:|-----:|------:|
|
||||||
|
| event | 6 | 0 | 6 |
|
||||||
|
| log | 9 | 0 | 9 |
|
||||||
|
| kv | 13 | 0 | 13 |
|
||||||
|
| **Total** | **28** | **0** | **28** |
|
||||||
30
lib/persist/tests/event.sx
Normal file
30
lib/persist/tests/event.sx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
; Phase 1 — event record accessors. Uses the persist-test harness
|
||||||
|
; (persist-test name got expected) provided by conformance.sh.
|
||||||
|
|
||||||
|
(persist-test
|
||||||
|
"event-stream"
|
||||||
|
(persist/event-stream
|
||||||
|
(persist/event "s" 1 "t" 0 {}))
|
||||||
|
"s")
|
||||||
|
(persist-test
|
||||||
|
"event-seq"
|
||||||
|
(persist/event-seq (persist/event "s" 3 "t" 0 {}))
|
||||||
|
3)
|
||||||
|
(persist-test
|
||||||
|
"event-type"
|
||||||
|
(persist/event-type
|
||||||
|
(persist/event "s" 1 "create" 0 {}))
|
||||||
|
"create")
|
||||||
|
(persist-test
|
||||||
|
"event-at"
|
||||||
|
(persist/event-at (persist/event "s" 1 "t" 42 {}))
|
||||||
|
42)
|
||||||
|
(persist-test
|
||||||
|
"event-data"
|
||||||
|
(persist/event-data
|
||||||
|
(persist/event "s" 1 "t" 0 {:x 9}))
|
||||||
|
{:x 9})
|
||||||
|
(persist-test
|
||||||
|
"event is a dict with all fields"
|
||||||
|
(len (keys (persist/event "s" 1 "t" 0 {})))
|
||||||
|
5)
|
||||||
86
lib/persist/tests/kv.sx
Normal file
86
lib/persist/tests/kv.sx
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
; Phase 1 — kv facet: get/put/delete/has?/keys, get-or, update.
|
||||||
|
|
||||||
|
(persist-test "absent key reads nil" (persist/kv-get (persist/open) "x") nil)
|
||||||
|
(persist-test
|
||||||
|
"has? false when absent"
|
||||||
|
(persist/kv-has? (persist/open) "x")
|
||||||
|
false)
|
||||||
|
(persist-test
|
||||||
|
"put then get"
|
||||||
|
(let
|
||||||
|
((b (persist/open)))
|
||||||
|
(begin (persist/kv-put b "x" 7) (persist/kv-get b "x")))
|
||||||
|
7)
|
||||||
|
(persist-test
|
||||||
|
"put returns value"
|
||||||
|
(let ((b (persist/open))) (persist/kv-put b "x" 9))
|
||||||
|
9)
|
||||||
|
(persist-test
|
||||||
|
"has? true after put"
|
||||||
|
(let
|
||||||
|
((b (persist/open)))
|
||||||
|
(begin (persist/kv-put b "x" 1) (persist/kv-has? b "x")))
|
||||||
|
true)
|
||||||
|
(persist-test
|
||||||
|
"put overwrites"
|
||||||
|
(let
|
||||||
|
((b (persist/open)))
|
||||||
|
(begin
|
||||||
|
(persist/kv-put b "x" 1)
|
||||||
|
(persist/kv-put b "x" 2)
|
||||||
|
(persist/kv-get b "x")))
|
||||||
|
2)
|
||||||
|
(persist-test
|
||||||
|
"delete removes key"
|
||||||
|
(let
|
||||||
|
((b (persist/open)))
|
||||||
|
(begin
|
||||||
|
(persist/kv-put b "x" 1)
|
||||||
|
(persist/kv-delete b "x")
|
||||||
|
(persist/kv-has? b "x")))
|
||||||
|
false)
|
||||||
|
(persist-test
|
||||||
|
"delete then get is nil"
|
||||||
|
(let
|
||||||
|
((b (persist/open)))
|
||||||
|
(begin
|
||||||
|
(persist/kv-put b "x" 1)
|
||||||
|
(persist/kv-delete b "x")
|
||||||
|
(persist/kv-get b "x")))
|
||||||
|
nil)
|
||||||
|
(persist-test
|
||||||
|
"keys lists stored keys"
|
||||||
|
(let
|
||||||
|
((b (persist/open)))
|
||||||
|
(begin
|
||||||
|
(persist/kv-put b "a" 1)
|
||||||
|
(persist/kv-put b "b" 2)
|
||||||
|
(len (persist/kv-keys b))))
|
||||||
|
2)
|
||||||
|
(persist-test
|
||||||
|
"get-or returns default when absent"
|
||||||
|
(persist/kv-get-or (persist/open) "x" 99)
|
||||||
|
99)
|
||||||
|
(persist-test
|
||||||
|
"get-or returns value when present"
|
||||||
|
(let
|
||||||
|
((b (persist/open)))
|
||||||
|
(begin
|
||||||
|
(persist/kv-put b "x" 5)
|
||||||
|
(persist/kv-get-or b "x" 99)))
|
||||||
|
5)
|
||||||
|
(persist-test
|
||||||
|
"kv-update applies fn over default"
|
||||||
|
(let
|
||||||
|
((b (persist/open)))
|
||||||
|
(begin
|
||||||
|
(persist/kv-update b "n" 0 (fn (v) (+ v 1)))
|
||||||
|
(persist/kv-update b "n" 0 (fn (v) (+ v 1)))
|
||||||
|
(persist/kv-get b "n")))
|
||||||
|
2)
|
||||||
|
(persist-test
|
||||||
|
"kv facet does not touch log"
|
||||||
|
(let
|
||||||
|
((b (persist/open)))
|
||||||
|
(begin (persist/kv-put b "x" 1) (persist/count b "x")))
|
||||||
|
0)
|
||||||
81
lib/persist/tests/log.sx
Normal file
81
lib/persist/tests/log.sx
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
; Phase 1 — log facet: append/read/read-from, sequential seq, stream isolation.
|
||||||
|
; Note: map returns an array-backed list not equal? to a (list ...) literal,
|
||||||
|
; so assertions build their compared list with list/nth, not map.
|
||||||
|
|
||||||
|
(persist-test
|
||||||
|
"empty stream reads empty"
|
||||||
|
(len (persist/read (persist/open) "orders"))
|
||||||
|
0)
|
||||||
|
(persist-test
|
||||||
|
"last-seq empty is 0"
|
||||||
|
(persist/last-seq (persist/open) "orders")
|
||||||
|
0)
|
||||||
|
(persist-test
|
||||||
|
"append returns event with seq 1"
|
||||||
|
(persist/event-seq
|
||||||
|
(persist/append (persist/open) "orders" "placed" 0 {:id 1}))
|
||||||
|
1)
|
||||||
|
(persist-test
|
||||||
|
"append assigns sequential seqs"
|
||||||
|
(let
|
||||||
|
((b (persist/open)))
|
||||||
|
(begin
|
||||||
|
(persist/append b "orders" "placed" 0 {})
|
||||||
|
(persist/append b "orders" "placed" 1 {})
|
||||||
|
(persist/event-seq
|
||||||
|
(persist/append b "orders" "placed" 2 {}))))
|
||||||
|
3)
|
||||||
|
(persist-test
|
||||||
|
"read returns events oldest-first"
|
||||||
|
(let
|
||||||
|
((b (persist/open)))
|
||||||
|
(begin
|
||||||
|
(persist/append b "s" "a" 0 {:n 1})
|
||||||
|
(persist/append b "s" "b" 0 {:n 2})
|
||||||
|
(let
|
||||||
|
((es (persist/read b "s")))
|
||||||
|
(list
|
||||||
|
(get (persist/event-data (nth es 0)) :n)
|
||||||
|
(get (persist/event-data (nth es 1)) :n)))))
|
||||||
|
(list 1 2))
|
||||||
|
(persist-test
|
||||||
|
"count tracks appends"
|
||||||
|
(let
|
||||||
|
((b (persist/open)))
|
||||||
|
(begin
|
||||||
|
(persist/append b "s" "a" 0 {})
|
||||||
|
(persist/append b "s" "a" 0 {})
|
||||||
|
(persist/count b "s")))
|
||||||
|
2)
|
||||||
|
(persist-test
|
||||||
|
"streams are isolated"
|
||||||
|
(let
|
||||||
|
((b (persist/open)))
|
||||||
|
(begin
|
||||||
|
(persist/append b "s1" "a" 0 {})
|
||||||
|
(persist/append b "s2" "a" 0 {})
|
||||||
|
(persist/append b "s2" "a" 0 {})
|
||||||
|
(list (persist/count b "s1") (persist/count b "s2"))))
|
||||||
|
(list 1 2))
|
||||||
|
(persist-test
|
||||||
|
"read-from filters by seq"
|
||||||
|
(let
|
||||||
|
((b (persist/open)))
|
||||||
|
(begin
|
||||||
|
(persist/append b "s" "a" 0 {})
|
||||||
|
(persist/append b "s" "a" 0 {})
|
||||||
|
(persist/append b "s" "a" 0 {})
|
||||||
|
(let
|
||||||
|
((es (persist/read-from b "s" 2)))
|
||||||
|
(list
|
||||||
|
(persist/event-seq (nth es 0))
|
||||||
|
(persist/event-seq (nth es 1))))))
|
||||||
|
(list 2 3))
|
||||||
|
(persist-test
|
||||||
|
"read-from past end is empty"
|
||||||
|
(let
|
||||||
|
((b (persist/open)))
|
||||||
|
(begin
|
||||||
|
(persist/append b "s" "a" 0 {})
|
||||||
|
(len (persist/read-from b "s" 5))))
|
||||||
|
0)
|
||||||
@@ -42,7 +42,7 @@ read models (feeds, indices, audit logs) update incrementally.
|
|||||||
|
|
||||||
## Status (rolling)
|
## Status (rolling)
|
||||||
|
|
||||||
`bash lib/persist/conformance.sh` → **0/0** (not yet started)
|
`bash lib/persist/conformance.sh` → **28/28** (Phase 1 done)
|
||||||
|
|
||||||
## Ground rules
|
## Ground rules
|
||||||
|
|
||||||
@@ -87,11 +87,11 @@ lib/persist/backend.sx lib/persist/api.sx
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Phase 1 — Log + kv + in-memory backend
|
## Phase 1 — Log + kv + in-memory backend
|
||||||
- [ ] `event.sx` — event record, stream/seq helpers
|
- [x] `event.sx` — event record, stream/seq helpers
|
||||||
- [ ] `backend.sx` — injectable protocol + in-memory impl (log + kv)
|
- [x] `backend.sx` — injectable protocol + in-memory impl (log + kv)
|
||||||
- [ ] `log.sx` — `append` (optimistic seq), `read`, `read-from`
|
- [x] `log.sx` — `append` (optimistic seq), `read`, `read-from`
|
||||||
- [ ] `kv.sx` — `get`/`put`/`delete` current-state
|
- [x] `kv.sx` — `get`/`put`/`delete` current-state
|
||||||
- [ ] `api.sx` + tests + scoreboard + conformance.sh
|
- [x] `api.sx` + tests + scoreboard + conformance.sh
|
||||||
|
|
||||||
## Phase 2 — Projections + subscriptions
|
## Phase 2 — Projections + subscriptions
|
||||||
- [ ] `project.sx` — `(project stream step seed)`, incremental fold
|
- [ ] `project.sx` — `(project stream step seed)`, incremental fold
|
||||||
@@ -113,7 +113,19 @@ feed/-log, flow store, mod/audit, search index, acl grants, identity sessions al
|
|||||||
become `persist` log or kv. Track each migration in that subsystem's plan.
|
become `persist` log or kv. Track each migration in that subsystem's plan.
|
||||||
|
|
||||||
## Progress log
|
## Progress log
|
||||||
(loop fills this in)
|
- **Phase 1 complete (28/28).** `event.sx` (event record + accessors),
|
||||||
|
`backend.sx` (injectable protocol + in-memory log/kv impl, closure state via
|
||||||
|
set!), `log.sx` (append/read/read-from, sequential per-stream seq, stream
|
||||||
|
isolation), `kv.sx` (get/put/delete/has?/keys/get-or/update), `api.sx`
|
||||||
|
(`persist/open` — mem default, backend injectable). conformance.sh + three
|
||||||
|
suites (event/log/kv). Gotcha logged in Blockers: `map` returns an
|
||||||
|
array-backed list not `equal?` to a `(list ...)` literal — assertions build
|
||||||
|
compared lists with list/nth.
|
||||||
|
|
||||||
## Blockers
|
## Blockers
|
||||||
(loop fills this in)
|
- **Not a blocker, a testing convention:** `map` returns an array-backed list
|
||||||
|
that is NOT `equal?` to a `(list ...)` cons-literal (two `map` results do
|
||||||
|
compare equal to each other). When asserting list-shaped results against a
|
||||||
|
`(list ...)` literal, build the compared value with `list`/`nth`/`cons`, not
|
||||||
|
`map`. `into`/list-coercion needs the IO bridge and is unusable in the
|
||||||
|
pure-eval harness.
|
||||||
|
|||||||
Reference in New Issue
Block a user