right, that’s what i’d like to avoid. traversing upwards may result in broken links or links which you are not authorized to see. if it’s relevant, there should be a way to get back to the root. i suppose it’s up to each author to decide if it’s relevant to advertise this root or not.
i generally support using context
for fetching the conversation from the root, as you describe in Nomad, but the problem is that
-
context
is not guaranteed to always resolve in the wild -
context
is not guaranteed to be a Collection - if it is a collection,
context
may contain Object or Activity, so you have to be prepared to possibly unwrap activities or deal with nonwrapped objects - collections are not strictly or consistently ordered except for
OrderedCollection
which MUST be reverse-chronological and not forward-chronological (see Stricter specifications for pagination of Collections and OrderedCollections for more discussion)
ideally, the conversation (regardless of which property it is referenced by) would always be a strictly ordered (forward chronological?) Collection where the first item (or last item, if OrderedCollection) is the root object or activity. unfortunately in practice there are five different possible paths to check depending on whether the context collection is paged or not, unordered or ordered or Ordered. so you end up having to deal with a lot of conditional checks and cases instead of just using one property and being done with it.
it could be served via both context
and nomad:conversation
but that doesn’t really solve the issue unless you define nomad:conversation
with stricter Collection ordering. it would be better to use context
and have some other consistent way of fetching the “root object” from a given context.
this sounds a bit ridiculous at first but perhaps context.context
could work? “the context for the context”.
-
alternatively, maybe something like
context.generator
, but that’s a bit less semantic (as it seems intended to point to the application that generated the activity or object, but it doesn’t have to be). -
we could also break the spec and say
context.origin
(plucking theorigin
out of the Activity properties and grafting it onto Object`), but this seems strictly worse.
also tangentially, for C2S I suppose you’d have to do something like
- Create a Collection
- Create an Object with
context
pointing to that Collection - Add the Object to that Collection
- Update the Collection so that
context
points to the Object you just created
with the caveat that a remote server might receive the Create and try to reply before step 4 is completed. so that’s a possible race condition in automated systems, but hopefully not likely to occur.
so i suppose that could work like so:
- if
context
is present and it is a (root) Object, stop and return - if
context
is present and it is a Collection, try to resolvecontext.context
- if you fail, try to traverse the
inReplyTo
chain upwards - if you fail, discard the comment (or treat it as a standalone reply)
in a comment world, the conversation authority might send you an Add Object to the context collection, or the root authority might send you an Add Object to a comments
collection, and then you could reprocess the object?
ok back to the fep again
I suppose we’re back to the question of dealing with an external observer trying to validate whether a dual-class comment-and-reply was approved… so far, i’ve been sort of punting it and assuming “if you care about comment approval on a dual-class object, just ask the conversation authority for their view of the conversation”. that’s probably not sufficient, though, so maybe a commentApproval
/commentOn
is still justified?
at least in a comment-only world using nomad:replyTo
your comment will be a DM and will not be visible to your own followers unless forwarded later, right?
long example
the case i'm particularly confused by is like so:- alice has a post
id: alice.com/posts/1
attributedTo: alice.com
content: "comment below with your thoughts about cheese"
context: alice.com/contexts/1
- bob makes a dual-class reply+comment:
id: bob.com/posts/2
attributedTo: bob.com
content: "i love cheese"
inReplyTo: alice.com/posts/1 # this makes it a reply
context: alice.com/contexts/1 # this makes it a comment?
audience: alice.com, alice.com/followers, bob.com/followers
- alice forwards bob’s post to alice’s followers, carrying implicit approval:
id: alice.com/activities/fe538744-110d-44bf-8f4e-efbeee5fc498
actor: alice.com
type: Add
object: bob.com/posts/2 # this should be fetchable by alice's followers or otherwise inlined with a proof
target: alice.com/contexts/1
to: alice.com/followers, bob.com
- bob’s followers need to validate either or both reply-approval and comment-approval
- if bob somehow forwards alice’s activity, it carries implicit approval for the comment, but it might be out-of-date as alice could remove it from the conversation and bob might not forward this removal
- if bob doesn’t forward alice’s activity, then bob needs to attach approval via some property
reply approval
in a reply-approval world, alice sends bob an Accept and bob sends an Update to bob’s followers:
id: alice.com/accepts/bob.com/posts/2
to: bob.com
actor: alice.com
type: Accept
object: bob.com/posts/2
inReplyTo: alice.com/posts/1 # currently in the fep
approvalCondition: # currently not in the fep, but we should be able to explicitly specify the conditions for approval
# basically saying "i approve this reply on these conditions"
# e.g. that `inReplyTo` has this value,
# that `content` has this exact hash,
# and so on.
# (idk what vocab to use for this though or what it should look like)
id: bob.com/posts/2/history/2
to: bob.com/followers
actor: bob.com
type: Update
object:
id: bob.com/posts/2
content: "i love cheese"
inReplyTo: alice.com/posts/1
context: alice.com/contexts/1
audience: alice.com, alice.com/followers, bob.com/followers
replyApproval: alice.com/accepts/bob.com/posts/2 # added by the update
- bob’s followers validate like so:
replyApproval.actor == inReplyTo.attributedTo
replyApproval.type == Accept
replyApproval.object == id
replyApproval.inReplyTo == inReplyTo # current FEP state -- ideally would be replaced by explicit approvalCondition
comment approval
in a comment-approval world, either
- bob’s followers check
context
for the whole conversation (which is bad for validating a single comment) - bob’s followers check
commentApproval
which is defined similarly toreplyApproval
- bob’s followers check a generic
approval
orconversationApproval
or whatever unity we can achieve between the two models - bob’s followers must also follow alice, as alice controls the entire conversation
taking a step back: bob’s followers still need to decide whose authority to respect.
or, in other words: bob’s followers need to decide whether to even respect bob’s post as a standalone reply if it exists in a specific conversational context that doesn’t belong to bob.
in effect, does the conversational “comment model” override the standalone “reply model”?
another attempt at reply-vs-comment and a bit of a breakthrough
there is also a possibility we haven’t considered yet – what if bob changes the context? in effect, anyone replying to bob would be part of bob’s conversation now. or part of no conversation at all.
in fact, maybe we can define the difference between “reply model” and “comment model” like so:
- comment model: the context is inherited (copied as-is from the parent, whether immediate parent or root parent)
- reply model: the context is replaced by the author’s own context or removed entirely
and we could extend it such that “approval” is tied to that conversational context. no context, no approval needed. context present? approval needed.
we could derive the following algorithm for dealing with conversational context, that applies to both replies and comments:
- when making a root post that doesn’t require approval, do not set
context
- when making a root post that requires approval, set
context
= some Collection- (in effect, this sets the authority via
context.attributedTo
) - (
context
is a Collection andcontext.context
should point to the root object in the conversation)
- (in effect, this sets the authority via
- when replying to a post that has
context
set:- copy the
context
of the immediate parent (inReplyTo.context
) to turn your reply into a comment on the same conversation as the immediate parent - copy the
context
of the root object (context.context.context
(yes i know)) to turn your reply into a comment on the root object - set the
context
to your own Collection to declare your post as a standalone reply that requires reply approval - drop the
context
to declare your post as a standalone reply that doesn’t require reply approval
- copy the
- when replying to a post that has no
context
:- leave the context off to declare your post as a standalone reply that doesn’t require approval
- set the
context
to your ownid
or Collection to declare your post as a standalone reply that requires reply approval
this gets us surprisingly far with pretty good coverage of the stated aims, with the following caveats:
- you have to explicitly choose between controlling your own replies vs. giving up control to another conversation authority. if you participate in someone else’s conversation, you don’t get to control who replies to you unless you opt out of that conversation and make your own.
- you cannot easily find the root object for any reply thread; you can only find the root object for a conversation
- note that as a consequence, a root object for a conversation MAY have
inReplyTo
set; this is just metadata, though, and should not be used for threading
- note that as a consequence, a root object for a conversation MAY have
bonus:
-
this could maybe help with managing group posts in a way that doesn’t strictly depend on FEP-400e. the conversation authority would be the group, and context inheritance would be strictly enforced (or else the group won’t add your post to the conversation). replace
target
on objects withcontext
. -
this could also be used with something similar to “quote tweet”/“quote post” and the desire to control who can “quote” you. in effect, a “quote post” is just a reply that changes the context to be part of its own conversation, turning
inReplyTo
into just metadata. such objects should be rendered as new top-level posts, with the replied-to post being rendered as a preview above (or possibly just a link, if it has aurl
). effectively this collapses “quote approval” and “reply approval” into the same problem space.- caveat: we now need to define “quote approval” in much the same way that the current “reply approval” is defined in the current FEP: with
replyApproval
(or is it nowquoteApproval
?) having to be obtained frominReplyTo.attributedTo
ifinReplyTo.approvesReplies
(or possiblyinReplyTo.approvesQuotes
). or, put another way, i guess what we need to do is disambiguate between in-context replies and out-of-context replies. disallowing “quote tweets”/“quote posts” is really in effect just disallowing out-of-context replies.
- caveat: we now need to define “quote approval” in much the same way that the current “reply approval” is defined in the current FEP: with
-
following a conversation should be easier because you can literally Follow a
context
if it is an actor. or maybe we define protocol-level expectations that if you send a Follow of thecontext
tocontext.attributedTo
then that should be supported. anyway i’m punting this point over to Unresolved issues surrounding Follow activities because there’s far more discussion there
ui/ux and related implications
- if
context
is present and dereferenceable, this should be shown to users before they reply as a hint that they are participating in someone else’s conversation, and their reply may not be accepted - the user should know which conversation they are participating in, or at least, who owns it
- mention-based addressing may no longer suffice. or, put another way, participating in a conversation implies that your post will be delivered to someone whose mention you are not allowed to drop