{
  "$type": "com.whtwnd.blog.entry",
  "theme": "github-light",
  "title": "Draft: Amalgam Specification",
  "content": "# Amalgam, version 0.1\nThis document defines the Amalgam social-media ecosystem. It is defined as a non-Bluesky profile of the Authenticated Transfer Protocol (AT Protocol, or ATProto). As such, it directly inherits the following traits from AT Protocol:\n* Personal data servers (PDSes). Amalgam PDSes are expected to be fully-compatible ATProto PDSes, including support for repositories and accounts.\n* DID-based identification and the associated repository cryptography.\n* The `atproto-proxy` header and its associated inter-service token.\n* XRPC and Lexicons.\n\nThe following sections detail changes, including but not limited to:\n* The NSID `placeholder.amalgam.deliverActivity`, which is for an XRPC procedure which accepts an Activity.\n* The NSID `placeholder.amalgam.handleActivity`, which is for an XRPC procedure triggered by remote servers with an Activity that should be handled.\n* The NSID `placeholder.amalgam.object`, which is for a repo collection and record type representing an Object in storage, in crawling, and in Activities.\n* Object representations of [ActivityStreams2][as2] and [ActivityPub][ap] Activities.\n* The NSID `placeholder.amalgam.resolveDelegatedHandle`, which is an XRPC query that resolves a delegated handle, also known as a sub-handle.\n* The NSID `placeholder.amalgam.relay.publishRecord`, which is an XRPC procedure that instructs a relay to publish the current state of a single record.\n* The NSID `placeholder.amalgam.relay.publishRecords`, which is an XRPC procedure that instructs a relay to publish the current state of multiple records.\n\n# Ecosystem Roles\nThe following ecosystem roles are defined with Amalgam. Others may exist that are not defined here, and which may overlap partially with some functions of these roles.\n\nAn **AppView** is a server which handles conveniences, collecting posts from the user's follows, disseminating Activities that the user publishes, counting likes, and approving followers.\n\nA **personal data server**, or **PDS**, is a server which handles the user's personal data. It is fully compatible with ATProto, to allow for re-use of PDS server software and even existing PDSes and repositories.\n\nA **search provider** is a server which indexes the network and allows users to search it. Sometimes, the AppView also fills this role.\n\nA **feed generator** or **feed provider** is a server which indexes discoverable posts from the network and creates \"feeds\" of posts from them, which the user can browse. This role is inherited from the Bluesky profile of ATProto.\n\nA **labeler** or **moderation service** is a server which applies \"labels\" to posts (records) and repos. These follow the ATProto [Labels][labels] specification.\n\nA **relay** crawls or is told by AppViews about posts and publishes them in a firehose format. Labelers and feed generators, as well as Bluesky AppViews, often use this to index posts. Amalgam encourages relays to not use a \"bootstrapping\" mechanism, instead allowing for individual AppViews or users to opt-in to it. Further, Amalgam encourages relays to only re-publish \"public\" or \"discoverable\" data, or data which was once designated as \"public\" or \"discoverable,\" particularly to change the visibility.\n\n# Federation model\nBluesky's profile of ATProto relies on a fundamentally centralized \"federation\" model: all posts must go through a **relay** to be effectively published. The AppView is also expected to accumulate a lot of information for every user on the network. While it has its practical advantages, it is financially unsustainable, and prohibitive for smaller entrants into the network.\n\nAmalgam takes [Christine Webber's advice][cwebber] and makes ATProto function more like ActivityPub. Here's our message-passing federation model. Look familiar?\n1. Client interacts with an AppView (`deliverActivity` or a more specialized API).\n2. The AppView figures out who a given Activity should be sent to, and tries to send it to each of them using a token-authenticated `handleActivity` procedure. <!-- Should it send to followers, or let them crawl me? -->\n3. Each receiving server files it away, notifies its users, or discards it depending on its relevance.\n\n## `handleActivity` inter-server token\nIt can't be the same as the one used for `atproto-proxy`, it may need to be longer-lived. It shall consist of the SHA-256 hash of the CBOR content <!-- check this -->, which shall be signed with the sending server's key as noted in the _user's_ DID document. This is to be used as a bearer token; that is, it follows `Authorization: Bearer `.\n\nThe server key, which MAY differ between users on the same server, SHALL be cached for some time on the receiving server. It SHOULD be kept for longer, ideally permanently, if the receiving server has a user following the sending user. **Note:** these keys MAY differ between users on the same server, and any given user MAY be associated with multiple Amalgam AppViews. While it may not be necessary to do so, you MAY go ahead and cache all of that users' keys. In storage, they should be keyed by _both_ destination server (i.e. domain) _and_ user.\n\n# Delegated handle (sub-handle) resolution\nThe `resolveDelegatedHandle` query resolves a \"delegated handle,\" which in Amalgam is a [handle][handle] of the form `@username@domain.tld`. In an AT URI, a delegated handle appears with the form `at://username@domain.tld`. It's called \"delegated\" because it is based on the identity registered for `domain.tld`, and they have given `username` access to use the domain. This is easier to configure than a domain, and it is useful to semantically separate people at an organization (i.e. `@mary@contoso.com`) from the organization itself or its projects (i.e. `@nytimes.com` or `@dept.college.edu`).\n\nA delegated handle consists of a **username** and a **domain**. In the first example, the username is \"username\", and the domain is \"domain.tld\".\n\nTo resolve such a handle:\n1. Acquire the handle. You've probably already done that.\n2. Use the [ATProto handle specification][handle] to look up the DID document for the _domain_ part.\n3. Look for a service of type `AmalgamHandleDelegator`. Append `/xrpc/placeholder.amalgam.resolveDelegatedHandle` to the `serviceEndpoint`. Fill out and send the query according to its Lexicon<!-- TODO: write it -->.\n4. Look up the DID document for the given DID, if there is any, or handle any errors.\n\nThis is an optional feature that may not always be supported. If the endpoint fails to return a DID, or if the DID does not map to the handle being requested, the handle is **invalid**.\n\nAmalgam does not currently specify any way to request or acquire such a handle. \n\n# Delivery rules\nWhen an AppView is delivering an Activity (`deliverActivity` or another API):\n* The Activity MUST be in DAG-CBOR and, after necessary modifications are made, signed by the key specified in the sending user's DID document for the sending server.\n* It MUST be delivered to every user (specified by repo AT URI) in the \"to\" and \"cc\" fields, by delivering it to their server\n* It MUST also be delivered to every user (specified by repo AT URI) in the \"bcc\" field.\n* The \"bcc\" field, if present, MUST be stripped before delivery. It MAY be saved for the sending user to reference later, but it MUST NOT be visible to anyone else. **Note:** receiving AppViews may simply dismiss Activities which do not concern them.\n* If there is no \"to\", the Activity MUST NOT be sent, and an appropriate error message SHOULD be returned to the user's client.\n* If there is no \"from\", it MUST be added before signing.\n* The target Object(s) being referenced, if the Activity is public, SHOULD already exist (or, for Delete, MUST no longer exist) in the appropriate repositories, and MUST be valid Objects of their base Types. (For example, a Create activity MUST NOT reference a record outside of its sender's repository.)\n  * Notable exceptions to this rule include Deletes (which MUST no longer exist), Likes, Follows, Follow responses, and other Activities which ought to remain ephemeral.\n* The target Object(s) SHOULD be included in the Activity when it is being sent, and they MUST match the actual Object record. It is the AppView's responsibility to fill in and correct the data. Notable exceptions are for Likes, follow requests, follow request responses, deletions,\n* Failures to deliver to certain users MUST be mentioned, but MUST NOT inhibit delivery to other users (UNLESS the reason for failure is on the sender's side, such as a network failure, if and only if this can be determined).\n* Any additional delivery instructions for a Type of Activity or Object MUST also be followed.\n\n**NOTE:** Some to/cc/bcc addresses MAY not point to a repository, but instead they may point to an ActivityPub Actor or inbox, or to a record which may specify a group.\n\nWhen an AppView receives an Activity (`handleActivity`):\n* If no-one associated with the AppView follows the sender, the message SHOULD be discarded or filed into a public feed. **After this, the below rules SHOULD NOT apply** -- consider carefully each rule you decide to continue processing! _This is a known spam vector in the [Fediverse][ap]!_\n* If the Activity is addressed (in \"to\" or \"cc\") to more than some threshold (such as 15) recipients total, all relevant notifications SHOULD be silent or simply not sent. In fact, these Activities MAY be dropped entirely! At least, the rule(s) below about the \"discoverable\" token SHOULD NOT be applied.\n* Rate limits should apply, and they should be much stricter when no user on the server follows the sending user (or any user associated with the sending server).\n* If the Activity is addressed \"to\" the \"discoverable\" token, the Activity SHOULD be put into an appropriate \"global\" or \"discovery\" feed. It MAY then be sent to a relay.\n* If the Activity is addressed \"to\" an associated user, whether or not that user follows the sender, they SHOULD be notified or alerted of the Object or Activity.\n* Users MUST never be notified of Deletes.\n* If the Activity is a follow request, the AppView MAY automatically deliver an Accept in response, BUT ONLY IF the user has requested it (or otherwise fully expects it) to do so.\n* Any additional handling or re-delivery instructions for a Type of Activity or Object MUST also be followed.\n\n*STILL more to do...*\n\n<!-- --- -->\n\n[cwebber]: https://dustycloud.org/blog/re-re-bluesky-decentralization/\n\n[ap]: https://www.w3.org/TR/activitypub/\n[as2]: https://www.w3.org/TR/activitystreams-core/\n\n[atproto]: https://atproto.com\n[handle]: https://atproto.com/specs/handle\n[labels]: https://atproto.com/specs/label",
  "createdAt": "2025-03-06T19:19:25.035Z",
  "visibility": "author"
}