; -*- mode: markdown -*- # fed-sx host-type federation — substrate design + build log How a host's typed-post graph (refinement types declared in `lib/host`'s metamodel) flows across fed-sx nodes: a type is published as a content-addressed `DefineType` activity, peers cache its record, serve it over the wire, and validate inbound objects against the declared refinement schema before appending them. This document is both the design and the running build log for `loops/fed-sx-types`. The companion build sheet is `plans/agent-briefings/fed-sx-types-loop.md`. ## Vocabulary - **Type record** — `{name, fields, refinement-schema, instance-type}`. The parsed `:object` payload of a `DefineType` activity. Immutable per CID: an updated type is a new CID (no in-place evolution). - **Type CID** — content-address of the type record's wire form. The stable handle a `SubtypeOf` edge or an object's `{type, _}` field references. - **Refinement schema** — a predicate over an object's field-values; the extra constraint a refinement type adds on top of its base `instance-type` (e.g. a `Post` is a `Note` whose `:title` is a non-empty string). ## Scope Substrate side only — everything under `next/**`. The host-side adapters (`lib/host/fed_sx_outbox.sx`, `lib/host/fed_sx_inbox.sx`) are a deliberate follow-up that consumes this branch's public surface (`DefineType` / `SubtypeOf` verbs, `peer_types`, the `/types/` route) once `loops/host`'s metamodel settles. **This loop does not touch `lib/host/`.** ## Steps ### Step 1 — `DefineType` + `SubtypeOf` genesis activity-types — DONE New `DefineActivity`-form genesis files, parsed as data by `bootstrap.erl` at startup (no kernel change yet): - `next/genesis/activity-types/define_type.sx` — declares the `DefineType` verb. `:schema` accepts an activity whose `:object` carries a string `:name` and an optional list `:fields`. - `next/genesis/activity-types/subtype_of.sx` — declares the `SubtypeOf` verb. `:schema` accepts an `:object` carrying both `:child-type-cid` and `:parent-type-cid` as strings. Schema bodies are SX source written with nested `get` (not keyword-threading) so they are directly evaluatable: keywords are not callable getters in the kernel and `(-> d :k)` does not get. Both are registered in `next/genesis/manifest.sx` (activity-types now 7) and the bundle counts in the bootstrap suites were bumped accordingly. Tests: `next/tests/define_type.sh`, `next/tests/subtype_of.sh` — parse shape, schema accept/reject, and a `term_codec` envelope round-trip. ### Step 2 — `peer_types.erl` receiver-side cache — DONE `next/kernel/peer_types.erl`, a mirror of `peer_actors.erl` keyed by type CID. State `[{TypeCidBytes, TypeRecord}, ...]`. Pure API (`new/2`-threaded `lookup`/`store`/`evict`/`types`/`lookup_or_fetch`) plus a registered gen_server (`put`, `lookup`, `state_for`, `known_types`, `lookup_or_fetch`). On a miss `lookup_or_fetch` pulls a Cfg-supplied `type_fetch_fn :: fun ((TypeCid, Cfg) -> {ok, Bytes} | {error, _})`, decodes the wire bytes via `term_codec`, and caches the record. No fn → `{error, no_fetch_fn}`; fetch error or bad bytes do not poison the cache. Test: `next/tests/peer_types.sh`. ### Step 3 — `/types/` route + `discovery_type_fetch.erl` — TODO `http_server.erl` serves `GET /types/` with `Accept: application/vnd.fed-sx.type-doc`: the cached TypeRecord `term_codec`-encoded, 404 if not cached. `discovery_type_fetch.erl` holds the live-HTTP closure that `peer_types:lookup_or_fetch` calls. Tests: `next/tests/peer_types_route.sh`, `next/tests/discovery_type_fetch.sh`. ### Step 4 — object-schema validation stage in `pipeline.erl` — TODO A new `apply_object_schema/2` stage between activity-type validation and the kernel append. When an inbound object carries `{type, TypeName}`, resolve the TypeRecord (local Define-name index → CID → `peer_types:lookup_or_fetch`) and apply its refinement schema to the object's field-values. Default `strict_object_schema = false`: an unresolvable type is let through with a `validation_skipped` log; opt-in strict mode rejects. Test: `next/tests/object_schema.sh`. ## Out of scope (deliberately) - Host-side outbox/inbox adapters (`lib/host/**`). - Type evolution / version migration — schemas are immutable per CID; the "name → currently-valid CID" routing layer is a separate problem. - Subtype-of unification / rendering across nodes — the graph data lands via `SubtypeOf` activities; dedup/display is a consumer concern. ## What the host-side adapter loop gets Once all four steps land, the follow-up `loops/host` adapter work can treat the following as stable public surface: - `DefineType` / `SubtypeOf` activity verbs (publish a type, link two). - `peer_types` gen_server (cache a peer's type, look it up). - `GET /types/` (serve a type the node knows). - `pipeline`'s object-schema stage (inbound objects validated against their declared refinement type when resolvable).