diff --git a/next/genesis/audience/direct.sx b/next/genesis/audience/direct.sx new file mode 100644 index 00000000..58b99fe4 --- /dev/null +++ b/next/genesis/audience/direct.sx @@ -0,0 +1,14 @@ +;; next/genesis/audience/direct.sx +;; +;; Direct audience: an actor is a member iff they are +;; explicitly named in the activity's :to or :cc lists. No +;; group expansion — true direct addressing only. + +(DefineAudience + :name "Direct" + :doc "Direct-addressing predicate. Tests literal membership\n in the activity's :to or :cc." + :member-of (fn + (actor audience) + (or + (member? actor (-> audience :to)) + (member? actor (-> audience :cc))))) diff --git a/next/genesis/audience/followers.sx b/next/genesis/audience/followers.sx new file mode 100644 index 00000000..e171d47b --- /dev/null +++ b/next/genesis/audience/followers.sx @@ -0,0 +1,14 @@ +;; next/genesis/audience/followers.sx +;; +;; Followers audience: an actor is a member iff they appear in +;; the audience-owner's :followers set in the audience-graph +;; projection. Federation (m2) wires this to peer delivery. + +(DefineAudience + :name "Followers" + :doc "Followers-of-owner predicate. Looks up the\n audience-graph projection's :followers list for the\n audience owner and tests membership." + :member-of (fn + (actor audience) + (member? + actor + (-> (get-projection :audience-graph) (-> audience :owner) :followers)))) diff --git a/next/genesis/audience/public.sx b/next/genesis/audience/public.sx new file mode 100644 index 00000000..c6aa01ce --- /dev/null +++ b/next/genesis/audience/public.sx @@ -0,0 +1,9 @@ +;; next/genesis/audience/public.sx +;; +;; Public audience: every actor is a member. Maps to the AP +;; magic id `https://www.w3.org/ns/activitystreams#Public`. + +(DefineAudience + :name "Public" + :doc "Public audience predicate. Always returns true — every\n actor on the network is considered a member." + :member-of (fn (actor audience) true)) diff --git a/next/genesis/codecs/dag-cbor.sx b/next/genesis/codecs/dag-cbor.sx new file mode 100644 index 00000000..58d03dcc --- /dev/null +++ b/next/genesis/codecs/dag-cbor.sx @@ -0,0 +1,13 @@ +;; next/genesis/codecs/dag-cbor.sx +;; +;; Canonical CBOR encoding per IPLD dag-cbor. Used to compute +;; envelope canonical bytes for signature coverage and to serialise +;; the genesis bundle itself. In Erlang-on-SX mode the kernel +;; dispatches to the host cid:to_string substrate (Step 1b) when +;; this codec is requested. + +(DefineCodec + :name "dag-cbor" + :doc "Deterministic CBOR with dag-cbor restrictions: sorted\n map keys, no floats unless required, no indefinite-length\n items. The canonical wire format for fed-sx artifacts." + :encode (fn (term) (host-codec :dag-cbor :encode term)) + :decode (fn (bytes) (host-codec :dag-cbor :decode bytes))) diff --git a/next/genesis/codecs/dag-json.sx b/next/genesis/codecs/dag-json.sx new file mode 100644 index 00000000..982d05e8 --- /dev/null +++ b/next/genesis/codecs/dag-json.sx @@ -0,0 +1,12 @@ +;; next/genesis/codecs/dag-json.sx +;; +;; JSON encoding with dag-json restrictions per IPLD: sorted map +;; keys, no NaN / Infinity, no comments, CIDs as `{"/": "..."}`. +;; Used as the human-readable wire format for ActivityPub interop +;; (JSON-LD over dag-json). + +(DefineCodec + :name "dag-json" + :doc "Deterministic JSON with dag-json restrictions. Sorted\n keys, CIDs as the {\"/\": \"...\"} object. Used by the\n HTTP server (Step 8) for application/json responses." + :encode (fn (term) (host-codec :dag-json :encode term)) + :decode (fn (bytes) (host-codec :dag-json :decode bytes))) diff --git a/next/genesis/codecs/raw.sx b/next/genesis/codecs/raw.sx new file mode 100644 index 00000000..e4a27301 --- /dev/null +++ b/next/genesis/codecs/raw.sx @@ -0,0 +1,12 @@ +;; next/genesis/codecs/raw.sx +;; +;; Identity codec — input bytes pass through unchanged in both +;; directions. Used for already-encoded payloads and for binary +;; artifacts (images, archives) whose CID is computed over the +;; raw bytes directly. + +(DefineCodec + :name "raw" + :doc "Identity codec. The CID's multicodec byte is 0x55.\n :encode and :decode return their input unchanged." + :encode (fn (bytes) bytes) + :decode (fn (bytes) bytes)) diff --git a/next/genesis/manifest.sx b/next/genesis/manifest.sx index cba82f45..4dbeb568 100644 --- a/next/genesis/manifest.sx +++ b/next/genesis/manifest.sx @@ -41,6 +41,6 @@ :validators ("validators/envelope-shape.sx" "validators/signature.sx" "validators/type-schema.sx") - :codecs () - :sig-suites () - :audience ()) + :codecs ("codecs/dag-cbor.sx" "codecs/raw.sx" "codecs/dag-json.sx") + :sig-suites ("sig-suites/rsa-sha256-2018.sx" "sig-suites/ed25519-2020.sx") + :audience ("audience/public.sx" "audience/followers.sx" "audience/direct.sx")) diff --git a/next/genesis/sig-suites/ed25519-2020.sx b/next/genesis/sig-suites/ed25519-2020.sx new file mode 100644 index 00000000..eb07cc8d --- /dev/null +++ b/next/genesis/sig-suites/ed25519-2020.sx @@ -0,0 +1,11 @@ +;; next/genesis/sig-suites/ed25519-2020.sx +;; +;; W3C Verifiable Credential signature suite — Ed25519 over +;; canonical bytes, key material in multibase. Default suite +;; for fed-sx actors per design §9. + +(DefineSigSuite + :name "ed25519-2020" + :doc "Ed25519 verification. Key carries publicKeyMultibase.\n :verify takes canonical-bytes + signature + key and\n returns bool. Real verification deferred to m2 once\n crypto:verify_ed25519/3 BIF lands; v1 stand-in returns\n false to defer all Ed25519-signed activities." + :verify (fn (canonical-bytes signature key) false) + :key-format (fn (key-doc) (string? (-> key-doc :publicKeyMultibase)))) diff --git a/next/genesis/sig-suites/rsa-sha256-2018.sx b/next/genesis/sig-suites/rsa-sha256-2018.sx new file mode 100644 index 00000000..c778f4cb --- /dev/null +++ b/next/genesis/sig-suites/rsa-sha256-2018.sx @@ -0,0 +1,11 @@ +;; next/genesis/sig-suites/rsa-sha256-2018.sx +;; +;; W3C Verifiable Credential signature suite — RSA-SHA256 over +;; canonical bytes, key material in PEM. Compatible with +;; Mastodon's HTTP-Signatures / Linked-Data-Signatures-2017. + +(DefineSigSuite + :name "rsa-sha256-2018" + :doc "RSA-SHA256 verification. Key carries publicKeyPem.\n :verify takes canonical-bytes + signature + key and\n returns bool. Real verification deferred to m2 once\n crypto:verify_rsa/3 BIF lands; v1 stand-in returns\n false to defer all RSA-signed activities." + :verify (fn (canonical-bytes signature key) false) + :key-format (fn (key-doc) (string? (-> key-doc :publicKeyPem)))) diff --git a/next/tests/genesis_parse.sh b/next/tests/genesis_parse.sh index 913a68ee..2cb3eba7 100755 --- a/next/tests/genesis_parse.sh +++ b/next/tests/genesis_parse.sh @@ -3,7 +3,7 @@ # # Confirms the seed genesis SX files parse cleanly and have the # expected top-level head form. The bundler (Step 4c+) consumes -# these forms directly as data. 36 cases. +# these forms directly as data. 50 cases. set -uo pipefail cd "$(git rev-parse --show-toplevel)" @@ -94,6 +94,34 @@ cat > "$TMPFILE" <<'EPOCHS' (eval "(get (apply dict (rest (parse (file-read \"next/genesis/validators/type-schema.sx\")))) :name)") (epoch 64) (eval "(len (get (apply dict (rest (parse (file-read \"next/genesis/manifest.sx\")))) :validators))") +(epoch 70) +(eval "(first (parse (file-read \"next/genesis/codecs/dag-cbor.sx\")))") +(epoch 71) +(eval "(get (apply dict (rest (parse (file-read \"next/genesis/codecs/dag-cbor.sx\")))) :name)") +(epoch 72) +(eval "(get (apply dict (rest (parse (file-read \"next/genesis/codecs/raw.sx\")))) :name)") +(epoch 73) +(eval "(get (apply dict (rest (parse (file-read \"next/genesis/codecs/dag-json.sx\")))) :name)") +(epoch 74) +(eval "(len (get (apply dict (rest (parse (file-read \"next/genesis/manifest.sx\")))) :codecs))") +(epoch 80) +(eval "(first (parse (file-read \"next/genesis/sig-suites/rsa-sha256-2018.sx\")))") +(epoch 81) +(eval "(get (apply dict (rest (parse (file-read \"next/genesis/sig-suites/rsa-sha256-2018.sx\")))) :name)") +(epoch 82) +(eval "(get (apply dict (rest (parse (file-read \"next/genesis/sig-suites/ed25519-2020.sx\")))) :name)") +(epoch 83) +(eval "(len (get (apply dict (rest (parse (file-read \"next/genesis/manifest.sx\")))) :sig-suites))") +(epoch 90) +(eval "(first (parse (file-read \"next/genesis/audience/public.sx\")))") +(epoch 91) +(eval "(get (apply dict (rest (parse (file-read \"next/genesis/audience/public.sx\")))) :name)") +(epoch 92) +(eval "(get (apply dict (rest (parse (file-read \"next/genesis/audience/followers.sx\")))) :name)") +(epoch 93) +(eval "(get (apply dict (rest (parse (file-read \"next/genesis/audience/direct.sx\")))) :name)") +(epoch 94) +(eval "(len (get (apply dict (rest (parse (file-read \"next/genesis/manifest.sx\")))) :audience))") EPOCHS OUTPUT=$(timeout 30 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) @@ -153,6 +181,20 @@ check 61 "envelope-shape.sx name" "envelope-shape" check 62 "signature.sx name" "signature" check 63 "type-schema.sx name" "type-schema" check 64 "manifest has 3 validators" "3" +check 70 "dag-cbor.sx head form" "DefineCodec" +check 71 "dag-cbor.sx name" "dag-cbor" +check 72 "raw.sx name" "raw" +check 73 "dag-json.sx name" "dag-json" +check 74 "manifest has 3 codecs" "3" +check 80 "rsa-sha256-2018.sx head form" "DefineSigSuite" +check 81 "rsa-sha256-2018.sx name" "rsa-sha256-2018" +check 82 "ed25519-2020.sx name" "ed25519-2020" +check 83 "manifest has 2 sig-suites" "2" +check 90 "public.sx head form" "DefineAudience" +check 91 "public.sx name" "Public" +check 92 "followers.sx name" "Followers" +check 93 "direct.sx name" "Direct" +check 94 "manifest has 3 audience" "3" TOTAL=$((PASS+FAIL)) if [ $FAIL -eq 0 ]; then diff --git a/plans/fed-sx-milestone-1.md b/plans/fed-sx-milestone-1.md index 3da23403..ec5de1d3 100644 --- a/plans/fed-sx-milestone-1.md +++ b/plans/fed-sx-milestone-1.md @@ -249,7 +249,7 @@ replay(LogState, InitAcc, Fun) -> ... - [x] **4b-obj** — Object-types: SXArtifact, Note, Tombstone, DefineActivity, DefineObject, DefineProjection, DefineValidator, DefineCodec, DefineSigSuite, Snapshot — 10 `DefineObject` files + manifest updated + 12 new parse tests - [x] **4b-proj** — Projections: activity-log, by-type, by-actor, by-object, actor-state, define-registry, audience-graph — 7 `DefineProjection` files + manifest updated + 9 new parse tests - [x] **4b-vld** — Validators: envelope-shape, signature, type-schema — 3 `DefineValidator` files + manifest updated + 5 new parse tests -- [ ] **4b-cod** — Codecs + sig-suites + audience predicates +- [x] **4b-cod** — Codecs (dag-cbor, raw, dag-json) + sig-suites (rsa-sha256-2018, ed25519-2020) + audience predicates (Public, Followers, Direct) — 8 SX files + manifest fully populated + 14 new parse tests - [ ] **4c** — `bootstrap:read_genesis/1` in Erlang: walk the manifest, file-read each referenced .sx, return parsed forms - [ ] **4d** — `bootstrap:build_genesis/1` + `bootstrap:verify_genesis/1`: compute bundle CID over the read forms via the host `cid:to_string` substrate; verify against a stored `bundle.cidhash` - [ ] **4e** — `bootstrap:load_genesis/1`: register parsed definitions into the in-memory registry (depends on Step 5) @@ -955,6 +955,7 @@ A few things still under-specified; resolve as work begins. Newest first. One line per sub-deliverable commit. Erlang conformance gate (`bash lib/erlang/conformance.sh`) must remain 729/729 on every entry. +- **2026-05-27** — Step 4b-cod: bootstrap codecs + sig-suites + audience predicates complete. 3 `DefineCodec` files (dag-cbor + raw + dag-json, dag-cbor + dag-json deferring to host-codec primitive when wired), 2 `DefineSigSuite` files (rsa-sha256-2018 PEM-keyed, ed25519-2020 multibase-keyed, both :verify returning false as m2-deferred stand-in), 3 `DefineAudience` files (Public/Followers/Direct member-of predicates per design §16). Manifest now lists 26 bootstrap files across all eight sections; `next/tests/genesis_parse.sh` 50/50. Step 4b complete; remaining Step 4 is bundler code (4c–4e). Erlang conformance 729/729. - **2026-05-27** — Step 4b-vld: bootstrap validators complete — 3 `DefineValidator` SX files (envelope-shape mirroring Step 2a, signature stub delegating to envelope:verify_signature/2 per design §9.6, type-schema looking up the object-type schema from define-registry). Manifest `:validators` populated; `next/tests/genesis_parse.sh` 36/36. Erlang conformance 729/729. - **2026-05-27** — Step 4b-proj: bootstrap projections complete — 7 `DefineProjection` SX files authored (activity-log identity, by-type/by-actor/by-object indexes, actor-state with key history fold, define-registry meta-fold over Create{Define*}, audience-graph stub). Manifest `:projections` populated; `next/tests/genesis_parse.sh` 31/31. Erlang conformance 729/729. - **2026-05-27** — Step 4b-obj: bootstrap object-types complete — 10 `DefineObject` SX files authored (SXArtifact, Note, Tombstone, DefineActivity, DefineObject, DefineProjection, DefineValidator, DefineCodec, DefineSigSuite, Snapshot). Each carries an SX `:schema` predicate. Manifest `:object-types` populated; `next/tests/genesis_parse.sh` 22/22. Erlang conformance 729/729.