content: trust-gated federation + conflict tests (Phase 4 complete, roadmap done, 230/230)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 40s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 40s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,7 @@ if [ ! -x "$SX_SERVER" ]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
SUITES=(block doc render api store crdt sync)
|
SUITES=(block doc render api store crdt sync fed)
|
||||||
|
|
||||||
OUT_JSON="lib/content/scoreboard.json"
|
OUT_JSON="lib/content/scoreboard.json"
|
||||||
OUT_MD="lib/content/scoreboard.md"
|
OUT_MD="lib/content/scoreboard.md"
|
||||||
@@ -45,6 +45,7 @@ run_suite() {
|
|||||||
(load "lib/content/store.sx")
|
(load "lib/content/store.sx")
|
||||||
(load "lib/content/crdt.sx")
|
(load "lib/content/crdt.sx")
|
||||||
(load "lib/content/sync.sx")
|
(load "lib/content/sync.sx")
|
||||||
|
(load "lib/content/fed.sx")
|
||||||
(epoch 2)
|
(epoch 2)
|
||||||
(eval "(define content-test-pass 0)")
|
(eval "(define content-test-pass 0)")
|
||||||
(eval "(define content-test-fail 0)")
|
(eval "(define content-test-fail 0)")
|
||||||
|
|||||||
68
lib/content/fed.sx
Normal file
68
lib/content/fed.sx
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
;; content-on-sx — federated documents: trust-gated peer-authored ops.
|
||||||
|
;;
|
||||||
|
;; A peer-authored op carries provenance (:author, and a :sig stub). We never
|
||||||
|
;; auto-accept: a peer op is applied only if it passes a trust gate. The gate is
|
||||||
|
;; a predicate (fn op -> bool) so acl-on-sx can inject real trust facts later;
|
||||||
|
;; the convenience form takes an explicit trusted-actor list (the stub).
|
||||||
|
;;
|
||||||
|
;; Accepted ops flow through the CvRDT merge (Phase 3), so concurrent local and
|
||||||
|
;; external edits reconcile deterministically (same-field LWW, order-independent).
|
||||||
|
;;
|
||||||
|
;; Requires (loaded by harness): crdt.sx (and its deps).
|
||||||
|
|
||||||
|
;; tag an op with provenance
|
||||||
|
(define content/authored (fn (op author) (assoc op :author author)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
content/signed
|
||||||
|
(fn (op author sig) (assoc (assoc op :author author) :sig sig)))
|
||||||
|
|
||||||
|
;; explicit trust stub: membership in a trusted-actor list
|
||||||
|
(define content/trusted? (fn (trust author) (crdt-member? author trust)))
|
||||||
|
|
||||||
|
;; general form: accept? is a predicate (fn op -> bool). Applies accepted ops
|
||||||
|
;; through the CRDT; quarantines the rest. Returns
|
||||||
|
;; {:state :accepted (ops) :rejected (ops)}.
|
||||||
|
(define
|
||||||
|
content/-merge-peer-loop
|
||||||
|
(fn
|
||||||
|
(state accept? ops accepted rejected)
|
||||||
|
(if
|
||||||
|
(= (len ops) 0)
|
||||||
|
{:state state :accepted (reverse accepted) :rejected (reverse rejected)}
|
||||||
|
(let
|
||||||
|
((op (first ops)))
|
||||||
|
(if
|
||||||
|
(accept? op)
|
||||||
|
(content/-merge-peer-loop
|
||||||
|
(crdt-apply state op)
|
||||||
|
accept?
|
||||||
|
(rest ops)
|
||||||
|
(cons op accepted)
|
||||||
|
rejected)
|
||||||
|
(content/-merge-peer-loop
|
||||||
|
state
|
||||||
|
accept?
|
||||||
|
(rest ops)
|
||||||
|
accepted
|
||||||
|
(cons op rejected)))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
content/merge-peer-with
|
||||||
|
(fn
|
||||||
|
(state accept? ops)
|
||||||
|
(content/-merge-peer-loop state accept? ops (list) (list))))
|
||||||
|
|
||||||
|
;; convenience: trust = list of trusted actor ids
|
||||||
|
(define
|
||||||
|
content/merge-peer
|
||||||
|
(fn
|
||||||
|
(state trust ops)
|
||||||
|
(content/merge-peer-with
|
||||||
|
state
|
||||||
|
(fn (op) (content/trusted? trust (get op :author)))
|
||||||
|
ops)))
|
||||||
|
|
||||||
|
(define content/accepted (fn (res) (get res :accepted)))
|
||||||
|
(define content/rejected (fn (res) (get res :rejected)))
|
||||||
|
(define content/peer-state (fn (res) (get res :state)))
|
||||||
@@ -6,9 +6,10 @@
|
|||||||
"api": {"pass": 26, "fail": 0},
|
"api": {"pass": 26, "fail": 0},
|
||||||
"store": {"pass": 29, "fail": 0},
|
"store": {"pass": 29, "fail": 0},
|
||||||
"crdt": {"pass": 34, "fail": 0},
|
"crdt": {"pass": 34, "fail": 0},
|
||||||
"sync": {"pass": 14, "fail": 0}
|
"sync": {"pass": 14, "fail": 0},
|
||||||
|
"fed": {"pass": 20, "fail": 0}
|
||||||
},
|
},
|
||||||
"total_pass": 210,
|
"total_pass": 230,
|
||||||
"total_fail": 0,
|
"total_fail": 0,
|
||||||
"total": 210
|
"total": 230
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,4 +11,5 @@ _Generated by `lib/content/conformance.sh`_
|
|||||||
| store | 29 | 0 | 29 |
|
| store | 29 | 0 | 29 |
|
||||||
| crdt | 34 | 0 | 34 |
|
| crdt | 34 | 0 | 34 |
|
||||||
| sync | 14 | 0 | 14 |
|
| sync | 14 | 0 | 14 |
|
||||||
| **Total** | **210** | **0** | **210** |
|
| fed | 20 | 0 | 20 |
|
||||||
|
| **Total** | **230** | **0** | **230** |
|
||||||
|
|||||||
148
lib/content/tests/fed.sx
Normal file
148
lib/content/tests/fed.sx
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
;; Phase 4 — federated documents: trust-gated peer ops + concurrent-external-
|
||||||
|
;; edit conflict resolution via the CRDT.
|
||||||
|
|
||||||
|
(st-bootstrap-classes!)
|
||||||
|
(content-bootstrap-blocks!)
|
||||||
|
(content-bootstrap-doc!)
|
||||||
|
(content-bootstrap-render!)
|
||||||
|
|
||||||
|
(define same? (fn (a b) (= (get a :elements) (get b :elements))))
|
||||||
|
|
||||||
|
;; base shared document, then a local edit
|
||||||
|
(define
|
||||||
|
base
|
||||||
|
(crdt-insert
|
||||||
|
(crdt-insert
|
||||||
|
(crdt-empty)
|
||||||
|
"h"
|
||||||
|
"heading"
|
||||||
|
(crdt-pos 1 0)
|
||||||
|
(list (list "level" 1) (list "text" "T"))
|
||||||
|
1
|
||||||
|
0)
|
||||||
|
"p"
|
||||||
|
"text"
|
||||||
|
(crdt-pos 2 0)
|
||||||
|
(list (list "text" "Body"))
|
||||||
|
1
|
||||||
|
0))
|
||||||
|
(define local (crdt-update base "p" "text" "local" 5 1))
|
||||||
|
|
||||||
|
;; ── provenance ──
|
||||||
|
(content-test
|
||||||
|
"authored tags author"
|
||||||
|
(get (content/authored (crdt-op-delete "h") "ed") :author)
|
||||||
|
"ed")
|
||||||
|
(content-test
|
||||||
|
"signed tags sig"
|
||||||
|
(get (content/signed (crdt-op-delete "h") "ed" "sig1") :sig)
|
||||||
|
"sig1")
|
||||||
|
(content-test "trusted? yes" (content/trusted? (list "ed" "al") "ed") true)
|
||||||
|
(content-test "trusted? no" (content/trusted? (list "ed") "mal") false)
|
||||||
|
|
||||||
|
;; peer ops: ed is trusted, mal is not
|
||||||
|
(define
|
||||||
|
peer-ops
|
||||||
|
(list
|
||||||
|
(content/authored
|
||||||
|
(crdt-op-update "p" "text" "peer-ed" 7 2)
|
||||||
|
"ed")
|
||||||
|
(content/authored
|
||||||
|
(crdt-op-insert
|
||||||
|
"x"
|
||||||
|
"text"
|
||||||
|
(crdt-pos 3 0)
|
||||||
|
(list (list "text" "X"))
|
||||||
|
8
|
||||||
|
2)
|
||||||
|
"ed")
|
||||||
|
(content/authored (crdt-op-delete "h") "mal")))
|
||||||
|
|
||||||
|
(define res (content/merge-peer local (list "ed") peer-ops))
|
||||||
|
|
||||||
|
;; ── trust gate: only ed's ops applied ──
|
||||||
|
(content-test "accepted count" (len (content/accepted res)) 2)
|
||||||
|
(content-test "rejected count" (len (content/rejected res)) 1)
|
||||||
|
(content-test
|
||||||
|
"rejected is mal's"
|
||||||
|
(get (first (content/rejected res)) :author)
|
||||||
|
"mal")
|
||||||
|
|
||||||
|
;; ── resulting document ──
|
||||||
|
(define rdoc (crdt-materialize "d" (content/peer-state res)))
|
||||||
|
(content-test "untrusted delete blocked: h survives" (doc-has? rdoc "h") true)
|
||||||
|
(content-test "trusted insert applied: x present" (doc-has? rdoc "x") true)
|
||||||
|
(content-test "result order" (doc-ids rdoc) (list "h" "p" "x"))
|
||||||
|
(content-test
|
||||||
|
"trusted edit wins (ts7 > ts5)"
|
||||||
|
(str (blk-send (doc-find rdoc "p") "text"))
|
||||||
|
"peer-ed")
|
||||||
|
|
||||||
|
;; ── order-independence of accepted peer ops ──
|
||||||
|
(define res-rev (content/merge-peer local (list "ed") (reverse peer-ops)))
|
||||||
|
(content-test
|
||||||
|
"peer merge order-independent"
|
||||||
|
(same? (content/peer-state res) (content/peer-state res-rev))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; ── trust = nobody → nothing applied, state unchanged ──
|
||||||
|
(define res0 (content/merge-peer local (list) peer-ops))
|
||||||
|
(content-test
|
||||||
|
"no trust accepts none"
|
||||||
|
(len (content/accepted res0))
|
||||||
|
0)
|
||||||
|
(content-test
|
||||||
|
"no trust rejects all"
|
||||||
|
(len (content/rejected res0))
|
||||||
|
3)
|
||||||
|
(content-test
|
||||||
|
"no trust state unchanged"
|
||||||
|
(same? (content/peer-state res0) local)
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; ── pluggable predicate gate (acl-on-sx hook) ──
|
||||||
|
(define
|
||||||
|
res-pred
|
||||||
|
(content/merge-peer-with
|
||||||
|
local
|
||||||
|
(fn (op) (= (get op :author) "ed"))
|
||||||
|
peer-ops))
|
||||||
|
(content-test
|
||||||
|
"predicate gate == list gate"
|
||||||
|
(same? (content/peer-state res-pred) (content/peer-state res))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; ── conflict on concurrent external edit: local vs external, same field ──
|
||||||
|
;; external (peer) state edits p concurrently with a later ts; CRDT reconciles.
|
||||||
|
(define
|
||||||
|
external
|
||||||
|
(crdt-update base "p" "text" "external" 9 2))
|
||||||
|
(content-test
|
||||||
|
"conflict LWW deterministic"
|
||||||
|
(str
|
||||||
|
(blk-send
|
||||||
|
(doc-find (crdt-materialize "d" (crdt-merge local external)) "p")
|
||||||
|
"text"))
|
||||||
|
"external")
|
||||||
|
(content-test
|
||||||
|
"conflict merge commutes"
|
||||||
|
(same? (crdt-merge local external) (crdt-merge external local))
|
||||||
|
true)
|
||||||
|
(content-test
|
||||||
|
"conflict merge idempotent"
|
||||||
|
(same?
|
||||||
|
(crdt-merge (crdt-merge local external) external)
|
||||||
|
(crdt-merge local external))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; concurrent external edit with LOWER ts loses to local
|
||||||
|
(define
|
||||||
|
external-old
|
||||||
|
(crdt-update base "p" "text" "stale" 3 2))
|
||||||
|
(content-test
|
||||||
|
"older external loses to local"
|
||||||
|
(str
|
||||||
|
(blk-send
|
||||||
|
(doc-find (crdt-materialize "d" (crdt-merge local external-old)) "p")
|
||||||
|
"text"))
|
||||||
|
"local")
|
||||||
@@ -19,7 +19,7 @@ injected adapter, not core.
|
|||||||
|
|
||||||
## Status (rolling)
|
## Status (rolling)
|
||||||
|
|
||||||
`bash lib/content/conformance.sh` → **210/210** (Phases 1–3 complete + Phase 4 Ghost adapter)
|
`bash lib/content/conformance.sh` → **230/230** (Phases 1–4 COMPLETE: blocks, doc, render, api, persist op log, CRDT merge, Ghost sync, federation)
|
||||||
|
|
||||||
## Ground rules
|
## Ground rules
|
||||||
|
|
||||||
@@ -72,11 +72,19 @@ lib/content/api.sx ── (content/edit) (content/render) (content/history) ─
|
|||||||
|
|
||||||
## Phase 4 — External sync + federation
|
## Phase 4 — External sync + federation
|
||||||
- [x] Ghost/CMS sync via injected adapter (import/export)
|
- [x] Ghost/CMS sync via injected adapter (import/export)
|
||||||
- [ ] federated documents (peer-authored blocks) — trust-gated stub
|
- [x] federated documents (peer-authored blocks) — trust-gated stub
|
||||||
- [~] tests: round-trip import/export (done), conflict on concurrent external edit (pending)
|
- [x] tests: round-trip import/export, conflict on concurrent external edit
|
||||||
|
|
||||||
## Progress log
|
## Progress log
|
||||||
|
|
||||||
|
- 2026-06-07 — Phase 4 `fed.sx` (**Phase 4 COMPLETE — roadmap done**):
|
||||||
|
trust-gated federation. Peer ops carry provenance (`:author`, `:sig` stub);
|
||||||
|
none are auto-accepted. The trust gate is a pluggable predicate (acl-on-sx
|
||||||
|
hook) with a trusted-actor-list convenience stub. `content/merge-peer[-with]`
|
||||||
|
applies only accepted ops through the CvRDT and quarantines the rest
|
||||||
|
(`{:state :accepted :rejected}`). Concurrent local/external edits reconcile
|
||||||
|
deterministically: same-field LWW by (ts,actor), commutative, idempotent;
|
||||||
|
untrusted ops never touch state. 20 tests; suite 230/230.
|
||||||
- 2026-06-07 — Phase 4 `sync.sx` (cb1): external CMS sync via an injected
|
- 2026-06-07 — Phase 4 `sync.sx` (cb1): external CMS sync via an injected
|
||||||
adapter. Core defines the shape — `{:import :export}` — and delegates;
|
adapter. Core defines the shape — `{:import :export}` — and delegates;
|
||||||
`content/import` / `content/export` / `content/round-trip` know nothing about
|
`content/import` / `content/export` / `content/round-trip` know nothing about
|
||||||
|
|||||||
Reference in New Issue
Block a user