c2s(ActivityPub client-to-server)가 단순한 ‘다른 클라이언트 API’로 구현될 때의 함정을 짚고, 데이터 호스팅과 해석을 분리해 클라이언트의 자유·혁신·모더레이션 선택권을 넓히는 방향을 제안한다.
URL: https://w.on-t.work/activitypub/c2s
i’m concerned with people’s perception of how c2s works and should be implemented, and i’m worried that we may be locking ourselves out of some very nifty behavior if implementations move over without thinking things through.
will inevitably be disruptive, even if just to client developers, and i feel like that disruption ought to result in actual benefits as opposed to some
you may object to me saying “the change will inevitably be disruptive” above. after all, what’s stopping any existing software from Just exposing posts as as:Notes and the timeline as a collection in GET /users/[you]/inbox
well, this is part of the problem i mentioned about how people think of c2s. it’s not Just another Client API that your Phone App uses. not a complete replacement for The Mastodon API. well, ok. it can be implemented like that and there are existing implementations which do implement it like that, but that kind of implementation is unable to expose the real benefits of c2s, and only serves to frustrate developers with unnecessary churn.
i’ve previously referred to this as the “json-ld flavored mastodon api” problem, because implementing c2s on top of most contemporary implementations will result in an api that is just as strict and limited as the existing api, but without
if (unmodified) mastodon were to Simply replace their api with c2s, you still would not be able to send litepub:EmojiReactions to posts. the (unmodified) mastodon database
the real gain from c2s is the ability to separate data hosting from data interpretation. that part is what lets you log in to radically different interfaces. that part is what gives clients the freedom to do their own thing.
this migration could be easier for some existing software. the pleroma family already stores data in an activitystreams-like format internally, and already supports a subset of c2s. however, since their dialect predates mastodon’s switch to activitypub, they still have to perform a bunch of compatibility transformations.
so, my proposed solution? c2s servers should
about the data they’re hosting. they should really just be glorified static sites that can also handle collection pagination, http signatures, and webfinger.
the interpretation of the data should happen on a separate index maintained by the client. luckily we already have a method of getting the data from the server to the client index, called actors and inboxes. the client can then decide to interpret the data as it sees fit, and can expose it’s own api endpoints for the user experiences it wishes to promote.
ok, back up for a second, we do not really have a way of getting data to client indexes. not when the data is private, at least. you could ask the c2s server, but it could very well choose to lie to you, resulting in one actor poisoning your entire client’s knowledge of one object. i tried gauging interest for a solution to this, but it hasn’t really went anywhere. oh well.
you may be able to get away with cache-control here, interpreting it as a private cache would, but most contemporary deployments only use cache-control to instruct public middleware caches in front of them, so your cache hit rate would be abysmal compared to how long current implementations cache proxied objects, increasing load on instances which may not be prepared for it.
now, here’s an interesting problem with activitypub authentication: you can alter how an object looks for different actors. this is the backbone behind authorized fetch, hacks like $INSTANCE$host,
therefore,
. instead, the client would have to ask the c2s server to proxy more objects in it’s behalf. this can be done by the client index only returning IDs and the frontend Just hammering proxyUrl repeatedly to fetch the objects, but with many objects and slow connections
. for this, i have three proposals:
수화를 위한 특수 “제어 프로퍼티”: c2s 서버가 여러 요청을 합쳐 처리하는 복잡성을 담당하게 하여, 배치 처리를 쓰더라도 최소 2번은 필요할 요청 대신 한 번의 요청으로 필요한 데이터를 모두 받게 한다
커스텀 컨텍스트로 json-ld 재-컴팩팅: c2s 서버가 json-ld의 복잡성을 담당해 더 엄격한 구조로 클라이언트에 반환한다. 또한 컨텍스트에 @vocab 폴백을 정의하지 않으면 컨텍스트에 없는 프로퍼티를 걸러내어 불필요한 속성을 필터링할 수 있다,
now, here’s an interesting twist: this “understand as little as possible” includes not implementing parts of the spec. in particular, side effects of activities such as as:Like. instead, leave the client as the maintainer of the likes collection (and equivalents), so it can use it’s own logic to determine which likes are “valid”, and which aren’t.
there is an interesting problem here: who maintains followers and blocks?. actually hold on, we’re getting ahead of ourselves. who maintains the collections?. the client, right? which one? if the user has uses multiple clients, how are we supposed to know which one is responsible for maintaining objects?
here, i propose letting clients take ownership of objects. you can then hook this into authorization in order to determine if clients are allowed to control objects they don’t own. then, a c2s-server specific management interface could let you override client ownership for objects if, for example, your old client is no longer available and you need another client to modify the objects it owns.
this still does not solve the following/followers collections, and blocks. i think we have to leave the responsibility here to the c2s server, as those affect all clients.
and, similarly, let’s not define any more actor-global state if possible. looking at you, all the feature negotiation specs.
you may have noticed that all these proposals have a very strong assumption that clients will have a server component that reacts to activities, indexes data, and exposes their own bespoke api endpoints. i am a very strong believer that
to this day, i use a phone from late 2018. being a “mid-range budget flagship” from 2018, the battery life
. additionally, there are virtually no unlimited mobile data plans available in the country i live in. as such,
.
i will let you guess how well this setup would handle iterating through my inbox every time i open my “home timeline”, filtering out all the as:Likes and only showing me as:Create(as:Note)s and as:Announce(as:Note)s, but filtering them out if they’re replies to someone i’m not also following.
and when i click on an as:Note, i don’t want to wait for each reply to load individually. the mastodon api gives me the entire tree in one go, why the regression?
and the big one:
unless you’re selfhosting or on a large general-purpose instance, fedi instances are communities themselves, with local timelines, bubble timelines, and moderation decisions. in a c2s world, where does this community live? where does the moderation live?
making the client a server, and letting it maintain things like timelines and reply trees, allows it to act as a layer for moderation interventions. staff can hide posts and users,
, and overall curate their community as they seem fit.
and if you don’t like it? you can Just log in to another client. temporarily, just to see how different moderation affects a reply tree, or permanently, in case you disagree with a decision. separating your data from the moderators’ interpretation of the network brings you freedom to choose the moderation you want to be applied to you. even if you’re self-hosting.
the c2s server would still likely need access control infrastructure, either to keep out the absolute Worst of the Worst, or just as an personal layer of moderation on top of the client’s layer of moderation, but overall i would expect most moderation to shift to the client’s view of the network.
additionally, letting c2s servers enforce a client’s access controls to objects the client owns can be an interesting area to explore, but i do not have any proposals for how that could work right now.
polish is important. you want the person using your client to, you know, actually like doing so. i have a few opinions on client behavior that i hope people take into account, one of them being that urls must be readable. having your url be an opaque uuid, a bunch of query parameters, or just two urls concatenated together is clunky. especially for social media. people share urls around, link to them in chats and blogs, and do fake quote posts.
my proposal here is to:
항상 액터의 객체를 그 액터의 id 아래에 네임스페이스로 두기
클라이언트에서 사람이 읽기 쉬운 url을 구성할 때, 액터 id 접두사를 webfinger 핸들로 치환하기
an example could be helpful: this would mean that the object
https://wetdry.world/users/kopper/statuses/114189828144196201
owned by the actor https://wetdry.world/users/kopper with the webfinger handle @kopper@wetdry.world would be accessible under the url
https://client.example/@kopper@wetdry.world/statuses/114189828144196201
if the actor id is not a prefix of the url, or the actor does not have a webfinger handle, you can then fall back into using the full object id as the path directly. which is no worse than not doing anything about it.
나는 솔직히 이런 네임스페이싱은 의무여야 한다고 생각한다. 어떤 식으로든
두 객체가 같은 곳에서 왔는지 검증할 방법이 없는 것은, 내가 보기엔 사람들이 결함이 있는 보안 모델(예: origin 기반 보안 모델)에 기대게 되는 주된 이유다. 단일 사용자 또는 사용자 없는 activitypub 소프트웨어는 웹 오리진 루트에 하나의 액터를 두어 지금과 같은 동작을 얻을 수 있지만, 멀티테넌트 소프트웨어는 네임스페이싱을 해야 한다.
추가로, 네임스페이싱이 있다면 클라이언트가 id를 선택하도록 할 수 있고, 이는
와 결합하면, 객체와 그 객체가 참조하는(예: likes/shares/replies 컬렉션) 다른 객체들을 한 번의 웹 요청으로 as:Create할 수 있게 해 클라이언트 반응성을 개선할 수 있다.
https://github.com/swicg/activitypub-api/issues의 오픈 이슈를 읽어보면, 사람들은 c2s가 초안(drafts), 트렌드(trends), 타임라인(timelines) 같은 것들의 엔드포인트를 표준화하길 원하는 것 같다. 나는 그게 도움이 될 거라고 생각하지 않는다.
내가 주장하는 모델에서는, 어떤 기능을 제공할지 결정하는 쪽은 서버가 아니라 클라이언트다. 이는 클라이언트가 기능과 사용자 경험의 디테일에서 자유롭게 혁신할 수 있게 하고, 엔드포인트를 표준화하는 시점에는 미처 떠올리지 못할 수도 있는 것들을 시도하게 해준다. 또한 가치가 없다고 판단한 기능은 거부할 수도 있다. 그렇다 해도, 몇 가지 관례를 정의하는 것은 여전히 도움이 될 수 있다. 예를 들어 as:OrderedCollectionPage는 어떤 클라이언트 API의 페이지네이션에도 유용할 수 있다
oh well
i think this is all i have to say. if you want to know more about my vision for c2s, you can find it scattered amongst https://codeberg.org/outpost. in particular, both branches of the ois repo and it’s readme (but the other repos are also interesting pieces of the puzzle!). this mess is an attempt to bring all that thinking into one place, but i am sure there are details missing from here. some of you may find them interesting.
i am very likely not going to get ois and the wider outpost project anywhere close to “federating”, let alone production ready. i can barely get myself to maintain the already existing fedi software i’m responsible for (sorry, mia. i’ll get on fixing the migrations eventually)
that said, feel free to take the code and run with it in your own fork. one tip: you may want to replace the http signature code with the one from middleap, as i’ve found and hopefully fixed several missing pieces when copying it over. the first issue has a helpful list of things left to do.
just don’t vibe code it. we deserve better than that.