Skip to content

Commit 0ce5d7b

Browse files
committed
feat(cnf): as-of history fold + daemon :as-of op (thread 019f100f-eefe, part B)
live-as-of S = {claims born <= S} minus {claims with a cnf-supersedes marker committed <= S}. Because retraction is append-only (a marker, never a delete), this is EXACT: a claim later superseded/withdrawn is naturally RE-SEEN at an earlier S — its tombstone hadn't been written yet. live-as-of-lp is the as-of twin of live-cids-lp for one (te,pid) group. Folds the in-store tail, so it is bounded by the snapshot floor, never O(total history). Daemon :as-of {:seq S :query Q} runs the SAME q/run oracle over an as-of EDB (the engine is untouched — it is just fed a historical claims vec); :as-of {:seq S :te T :p P} resolves one group as-of, elected like :resolved. cnf_causality (b): now-view shows only the latest; as-of before an overwrite re-sees the superseded value; as-of current == live; as-of 0 is empty. 17/17. Daemon loads clean.
1 parent c4c14bd commit 0ce5d7b

3 files changed

Lines changed: 79 additions & 0 deletions

File tree

cnf_coord.clj

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,33 @@
105105
(defn causal-key [co cid]
106106
[(or (observed-of co cid) (seq-of co cid)) cid (str (agent-of co cid))])
107107

108+
;; --- as-of: the history fold (thread H, Part B) ------------------------------
109+
;; "What was live AS OF seq S?" A claim is live-as-of-S iff it was BORN at a seq <= S
110+
;; AND no cnf-supersedes marker for it was committed at a seq <= S. Because retraction is
111+
;; append-only (a marker, never a delete), this is EXACT: a claim later superseded/withdrawn
112+
;; is naturally RE-SEEN at an earlier S — its tombstone hadn't been written yet (acceptance
113+
;; b). Folds the in-store tail, so it is bounded by thread D's snapshot floor (history
114+
;; compacted below a snapshot is gone: as-of before the floor is unavailable, not wrong) —
115+
;; never O(total history) (acceptance f).
116+
(defn- tx-seq-of [m cid] (get-in m [:txs (get (:tx-of m) cid) :seq] 0))
117+
(defn superseded-as-of [co s]
118+
(let [m @(store co) sup (:supersedes-pred m)]
119+
(set (for [[cid cl] (:claims m)
120+
:when (and (= (:p cl) sup) (<= (tx-seq-of m cid) s))]
121+
(:r cl)))))
122+
(defn live-as-of [co s]
123+
(let [m @(store co) sup (:supersedes-pred m) gone (superseded-as-of co s)]
124+
(set (for [[cid cl] (:claims m)
125+
:when (and (<= (tx-seq-of m cid) s)
126+
(not= (:p cl) sup) ; the markers are bookkeeping, not data
127+
(not (contains? gone cid)))]
128+
cid))))
129+
;; the live cids of ONE (te,pid) group as of S — the as-of twin of live-cids-lp.
130+
(defn live-as-of-lp [co s te pid]
131+
(let [m @(store co) all (live-as-of co s)]
132+
(filterv (fn [cid] (let [cl (get (:claims m) cid)] (and (= (:l cl) te) (= (:p cl) pid))))
133+
all)))
134+
108135
;; --- views-as-claims (thread E): per-branch isolation over the same log ------
109136
;; A VIEW is a first-class subject; (view selects @cid) claims are its OVERLAY —
110137
;; the cids it treats as facts. The object IS a claim id: cids live in the same

cnf_coord_daemon.clj

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,6 +1283,33 @@
12831283
{:value (when (seq live) (render (elect @co live)))
12841284
:members (count vals) :ambiguous? (> (count vals) 1)
12851285
:values vals :version (current-seq @co)})
1286+
;; :as-of — time-travel READ (thread H, Part B). Reconstruct the view as of seq S
1287+
;; (cnf_coord/live-as-of: born <= S, no supersede/withdraw marker <= S) and either
1288+
;; run a Datalog :query over that historical EDB (SAME q/run oracle, just fed an
1289+
;; as-of claims vec — the engine is untouched) or resolve one (:te,:p) group as-of.
1290+
;; Retraction-as-append makes this exact: a later-withdrawn claim is RE-SEEN at an
1291+
;; earlier S. Bounded by the snapshot floor (live-as-of folds the in-store tail).
1292+
:as-of (let [s (:seq req) st (:store @co)]
1293+
(cond
1294+
(nil? s) {:error ":as-of needs :seq"}
1295+
(:query req)
1296+
(let [cids (live-as-of @co s)
1297+
claims (vec (keep (fn [cid] (when-let [t (claim->triple st cid)]
1298+
(ck/->Claim (nth t 0) (nth t 1) (nth t 2))))
1299+
cids))
1300+
res (q/run claims (:query req))]
1301+
(assoc res :as-of s :version (current-seq @co)))
1302+
(:te req)
1303+
(let [e (s/resolve-name st (:te req))
1304+
pid (c/value-id st (:p req))
1305+
live (if (and e pid) (live-as-of-lp @co s e pid) [])
1306+
render (fn [cid] (let [r (:r (c/claim-of st cid))]
1307+
(if (c/value-object? st r) (c/literal st r) (s/name-of st r))))
1308+
vals (mapv render live)]
1309+
{:value (when (seq live) (render (elect @co (vec live))))
1310+
:members (count vals) :ambiguous? (> (count vals) 1)
1311+
:values vals :as-of s :version (current-seq @co)})
1312+
:else {:error ":as-of needs :query or :te/:p"}))
12861313
{:error "unknown op"}))))
12871314

12881315
;; ---- socket server (verbatim shape from the proven coord.clj) ---------------

tests/cnf_causality_test.clj

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,31 @@
7272
(chk "(a) the elected member's value is the earliest decider's (B)"
7373
(= "B" (c/literal (store co) (:r (c/claim-of (store co) causal-win))))))
7474

75+
;; ============================================================================
76+
;; (b) as-of — reconstruct a historical view; a later-superseded claim is RE-SEEN.
77+
;; ============================================================================
78+
(let [log "/tmp/cnf-causality-b.log"
79+
co (new-coord log)
80+
_ (register-pred! co "status" "single" "literal") ; single -> overwrite supersedes
81+
pid (c/value-id (store co) "status")
82+
tid (fn [] (s/resolve-name (store co) "TB"))
83+
r1 (commit! co "w" "TB" "status" :assert "open" 0) ; born at seq S1
84+
s1 (:ok r1)
85+
mid (current-seq co) ; a seq AFTER open, BEFORE closed
86+
r2 (commit! co "w" "TB" "status" :assert "closed" s1); supersedes "open" at seq S2 > S1
87+
s2 (:ok r2)
88+
val-of (fn [cids] (mapv #(c/literal (store co) (:r (c/claim-of (store co) %))) cids))]
89+
(chk "(b) NOW: only the latest value is live (open superseded)"
90+
(= ["closed"] (val-of (live-cids-lp co (tid) pid))))
91+
(chk "(b) as-of a seq BEFORE the overwrite RE-SEES the superseded 'open'"
92+
(= ["open"] (val-of (live-as-of-lp co mid (tid) pid))))
93+
(chk "(b) as-of the CURRENT seq == the live view ('closed')"
94+
(= ["closed"] (val-of (live-as-of-lp co s2 (tid) pid))))
95+
(chk "(b) as-of seq 0 (pre-history) sees nothing on this group"
96+
(empty? (live-as-of-lp co 0 (tid) pid)))
97+
(chk "(b) as-of is bounded: live-as-of folds the in-store tail, returns a set"
98+
(set? (live-as-of co s2))))
99+
75100
;; ---- summary ---------------------------------------------------------------
76101
(let [cs @checks fails (remove second cs)]
77102
(doseq [[nm ok] cs] (println (if ok " [PASS] " " [FAIL] ") nm))

0 commit comments

Comments
 (0)