FEP-5624: Per-object reply control policies

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 the origin 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

  1. Create a Collection
  2. Create an Object with context pointing to that Collection
  3. Add the Object to that Collection
  4. 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:

  1. if context is present and it is a (root) Object, stop and return
  2. if context is present and it is a Collection, try to resolve context.context
  3. if you fail, try to traverse the inReplyTo chain upwards
  4. 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 to replyApproval
  • bob’s followers check a generic approval or conversationApproval 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 and context.context should point to the root object in the conversation)
  • 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
  • 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 own id 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

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 with context.

  • 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 a url). 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 now quoteApproval?) having to be obtained from inReplyTo.attributedTo if inReplyTo.approvesReplies (or possibly inReplyTo.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.
  • 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 the context to context.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