diff --git a/next/genesis/activity-types/delete.sx b/next/genesis/activity-types/delete.sx new file mode 100644 index 00000000..22c04c70 --- /dev/null +++ b/next/genesis/activity-types/delete.sx @@ -0,0 +1,13 @@ +;; next/genesis/activity-types/delete.sx +;; +;; Bootstrap definition of the Delete verb per design §3 and §12.2. +;; Read as data by the bundler — never evaluated as code here. The +;; :schema and :semantics bodies are SX source; the validator +;; pipeline (Step 6) and projection scheduler (Step 7) evaluate them +;; at the appropriate times. + +(DefineActivity + :name "Delete" + :doc "Tombstone an existing object. :object is the CID of the\n target. Projections fold Delete by removing the object from\n their working indexes; the underlying log line is never\n erased — durability of the historical record is independent\n of projection state." + :schema (fn (act) (string? (-> act :object))) + :semantics (fn (state act) state)) diff --git a/next/genesis/activity-types/update.sx b/next/genesis/activity-types/update.sx new file mode 100644 index 00000000..90e2c77d --- /dev/null +++ b/next/genesis/activity-types/update.sx @@ -0,0 +1,15 @@ +;; next/genesis/activity-types/update.sx +;; +;; Bootstrap definition of the Update verb per design §3 and §12.2. +;; Read as data by the bundler — never evaluated as code here. The +;; :schema and :semantics bodies are SX source; the validator +;; pipeline (Step 6) and projection scheduler (Step 7) evaluate them +;; at the appropriate times. + +(DefineActivity + :name "Update" + :doc "Patch or replace an existing object. :object is the CID of\n the target; :patch is the field-level edit. Behaviour is\n delegated to per-object-type semantics — e.g. an Update of a\n DefineActivity supersedes the prior registry entry; an\n Update of a Person actor rotates keys via :patch :add-publicKey\n + :patch :supersede." + :schema (fn + (act) + (and (string? (-> act :object)) (not (nil? (-> act :patch))))) + :semantics (fn (state act) state)) diff --git a/next/genesis/manifest.sx b/next/genesis/manifest.sx index bd62daa3..607197f2 100644 --- a/next/genesis/manifest.sx +++ b/next/genesis/manifest.sx @@ -18,7 +18,9 @@ (GenesisManifest :version "0.0.1" :kernel-version "1.0.0-m1" - :activity-types ("activity-types/create.sx") + :activity-types ("activity-types/create.sx" + "activity-types/update.sx" + "activity-types/delete.sx") :object-types () :projections () :validators () diff --git a/next/tests/genesis_parse.sh b/next/tests/genesis_parse.sh index d10f1ac6..eb1fc5d7 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. 5 cases. +# these forms directly as data. 10 cases. set -uo pipefail cd "$(git rev-parse --show-toplevel)" @@ -32,6 +32,16 @@ cat > "$TMPFILE" <<'EPOCHS' (eval "(get (apply dict (rest (parse (file-read \"next/genesis/activity-types/create.sx\")))) :name)") (epoch 14) (eval "(get (apply dict (rest (parse (file-read \"next/genesis/manifest.sx\")))) :version)") +(epoch 15) +(eval "(first (parse (file-read \"next/genesis/activity-types/update.sx\")))") +(epoch 16) +(eval "(get (apply dict (rest (parse (file-read \"next/genesis/activity-types/update.sx\")))) :name)") +(epoch 17) +(eval "(first (parse (file-read \"next/genesis/activity-types/delete.sx\")))") +(epoch 18) +(eval "(get (apply dict (rest (parse (file-read \"next/genesis/activity-types/delete.sx\")))) :name)") +(epoch 19) +(eval "(len (get (apply dict (rest (parse (file-read \"next/genesis/manifest.sx\")))) :activity-types))") EPOCHS OUTPUT=$(timeout 30 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) @@ -60,6 +70,11 @@ check 11 "create.sx head form" "DefineActivity" check 12 "manifest lists create.sx" "activity-types/create.sx" check 13 "create.sx name is Create" "Create" check 14 "manifest version present" "0.0.1" +check 15 "update.sx head form" "DefineActivity" +check 16 "update.sx name is Update" "Update" +check 17 "delete.sx head form" "DefineActivity" +check 18 "delete.sx name is Delete" "Delete" +check 19 "manifest has 3 activity-types" "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 1a00e992..5c0eca16 100644 --- a/plans/fed-sx-milestone-1.md +++ b/plans/fed-sx-milestone-1.md @@ -245,7 +245,11 @@ replay(LogState, InitAcc, Fun) -> ... **Sub-deliverables:** - [x] **4a** — Seed genesis SX file authoring: `next/genesis/manifest.sx` + `next/genesis/activity-types/create.sx`. Manifest uses bare parenthesised paths (data lists, not `(list ...)` calls — consumed by `parse`, not `eval`). `next/tests/genesis_parse.sh` (5 cases). -- [ ] **4b** — Author remaining activity-types (Update / Delete) + object-types (DefineActivity, DefineObject, DefineProjection, …) + projections, validators, codecs, sig-suites, audience predicates +- [x] **4b-act** — Remaining activity-types: `update.sx` + `delete.sx`, manifest updated, parse tests (10 cases total in `genesis_parse.sh`) +- [ ] **4b-obj** — Object-types: SXArtifact, Note, Tombstone, DefineActivity, DefineObject, DefineProjection, DefineValidator, DefineCodec, DefineSigSuite, Snapshot +- [ ] **4b-proj** — Projections: activity-log, by-type, by-actor, by-object, actor-state, define-registry, audience-graph +- [ ] **4b-vld** — Validators: envelope-shape, signature, type-schema +- [ ] **4b-cod** — Codecs + sig-suites + audience predicates - [ ] **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) @@ -951,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-act: bootstrap activity-types complete — `update.sx` (Update verb, requires :object CID + :patch) + `delete.sx` (Delete verb, requires :object CID) authored as DefineActivity forms matching the Create shape. Manifest updated; `next/tests/genesis_parse.sh` 10/10. Step 4b broken into act/obj/proj/vld/cod sub-deliverables on the plan. Erlang conformance 729/729. - **2026-05-27** — Step 4a: genesis bundle seeded. `next/genesis/manifest.sx` (GenesisManifest with eight section keys, only `:activity-types` populated for now) + `next/genesis/activity-types/create.sx` (DefineActivity{Create} with :schema/:semantics SX bodies). `next/tests/genesis_parse.sh` 5/5. Step 3b parked behind a substrate-level term-codec gap — Blockers note added under Step 3; in-memory log from 3a unblocks Step 5+ which only need the API surface. Erlang conformance 729/729. - **2026-05-27** — Step 3a: `log:open/2 append/2 tip/1 replay/3 entries/1` over an in-memory state (per-actor seq, replay in append order, round-trip activities). `next/tests/log_memory.sh` 12/12. Pivoted from on-disk in this iteration: this port's `atom_to_list`/`integer_to_list` return SX strings rather than Erlang charlists, `binary_to_list` is unregistered, and `$X` char literals decode to nil — so a term codec needs a workaround. Captured as the Step 3b risk note in the plan. Erlang conformance 729/729. - **2026-05-26** — Step 2c: `envelope:verify_signature/2` — time-aware key lookup over `public_keys` (created ≤ published < superseded_at), MAC recompute via `crypto:hash(sha256, KeyMaterial ++ canonical_bytes)`, compared against `signature.value`. Returns ok or one of `no_signature | no_key_id | no_published | no_keys | no_active_key | bad_signature`. `next/tests/envelope_sig.sh` 11/11 pass. Erlang conformance 729/729.