Skip to content

Comments

groups: exploring venter-like pattern#5334

Open
arthyn wants to merge 19 commits intodevelopfrom
hm/exploring-venter
Open

groups: exploring venter-like pattern#5334
arthyn wants to merge 19 commits intodevelopfrom
hm/exploring-venter

Conversation

@arthyn
Copy link
Member

@arthyn arthyn commented Dec 4, 2025

Summary

Addresses TLON-4432. There's lots to be said here.

  • One of the main problems that the client has is that we have to listen and guess when a subscription fact is associated with a particular action we took. This is far from ideal and means we have some brittle machinery to be able to do what should be a simple operation.
  • Another problem is that the client has no way of knowing if a foreign operation errored. This was just not built into our API, but now we have that capability for these agents
  • Knowing the "status" of an operation, truly this is somewhat unexplored because it requires some UI/UX thinking of how to display in-progress actions, but this new API allows us to do something like this.
  • Being able to use HTTP requests is just plain easier and more ergonomic, especially if we can get a success response right away while also reducing dependency on an active SSE connection.

This does not create a library to facilitate these things yet. Instead we've manually added handling for request/response to both %groups and %channels. This does not implement venter in the way laid out by by ~niblyx-malnus here: https://github.com/niblyx-malnus/venter-pattern but it does use the basic principles.

The major thing still in progress is writing backend tests. May want to use new aqua testing not sure yet.

Finishing up:

  • some cleanup of commented out code in various places
  • request cleanup in channels-server
  • new author types that were never realized
  • random examples of showing errors, not sure we want this in all the places I put it
  • cleanup of random logs both hoon and client side

Things left undone for follow-up PRs:

  • modifying the message component to actually use delivery failure reasons
  • converting all past usages of actions to use new actions
  • using for group joins
  • implementing in other agents
  • trying to standardize the pattern such that it could be used as a library

Changes

  • added new types in %groups and %channels to facilitate request/response
  • modified agents to keep track of requests and also cleanup after requests have finished
  • modified client to use new request/response paradigm in APIs with new types
  • fixed click scripts newline parsing
  • made a groups commit script for AI tools to be able to compile hoon themselves
  • CLAUDE.md modifications to enable claude code to self compile and read errors

How did I test?

Manually through the UI trying different actions in various configurations between two ships.

Risks and impact

  • Safe to rollback without consulting PR author? No
  • Affects important code area:
    • Onboarding
    • State / providers
    • Message sync
    • Channel display
    • Notifications
    • Other:

Rollback plan

Rollback would require protocol shenanigans and migrations

Screenshots / videos

Copy link
Collaborator

@mikolajpp mikolajpp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking very nice so far. Some nits in the comments.

:: $incoming-request: a request coming into the agent
::
:: .id: request id
:: .http-id: http request id, if any. means we should give a response
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment line is a bit confusing: what means we should give a response? If request-id is present?

Comment on lines +325 to +330
=/ =flag:g
?- -.c-groups
%group flag.c-groups
%ask flag.c-groups
%join flag.c-groups
%leave flag.c-groups
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is wild we have to do this in this way. I think if %create was not there, flag it could still be referenced without dispatching on the type...

Comment on lines +2249 to +2250
%not-authorized
~['Cannot ban or unban an admin ship']
Copy link
Collaborator

@mikolajpp mikolajpp Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use of %not-authorized here is confusing (we use it both for authorized ships who try to access privileged commands, and for an unauthorized ship trying to join the group). I think we should make a semantic distinction and introduce something like HTTP 400 Bad Request, here and elesewhere.

Comment on lines 3838 to 3839
:: this is always a request from ourselves, never others
::
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is not clear to me: what does it refer to? Since we are in +go-core, everything we receive are responses to our own requests.

=. requests
(~(put by requests) id u.request(result `body.response))
:: this should only ever be from ourselves
=/ =path :(weld /v1 go-area /request/(scot %uv id))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be a +weld less.

Suggested change
=/ =path :(weld /v1 go-area /request/(scot %uv id))
=/ =path (weld [~.v1 go-area] /request/(scot %uv id))

@arthyn arthyn marked this pull request as ready for review January 22, 2026 22:18
@arthyn arthyn requested review from Fang- and mikolajpp January 22, 2026 23:54
@Fang-
Copy link
Member

Fang- commented Jan 23, 2026

Action and command obviously overlay cleanly onto our existing "seven things" model, but response conflicts. We can understand the addition of venter to move us to a "nine things" model, right? Would be good to explicate in the comment at the top of the sur file. Something like the following. (Forgive the name suggestions, I can't help myself, but we should probably resolve the conflict somehow to aid conversation.)

::        --action-->     --command-->
::       <··status··      <··result··
::    client       subscriber       publisher
::      <--response--     <--update--

@arthyn
Copy link
Member Author

arthyn commented Jan 23, 2026

@Fang- I think response was probably the wrong term to use back in the 7 things model, since most of the time its not directly responses from things we've done but sometimes what others have done. maybe event would have been better? idk

@arthyn
Copy link
Member Author

arthyn commented Jan 23, 2026

maybe revision would fit?

@mikolajpp
Copy link
Collaborator

On the other hand, I don't think response was ever being predicated on being generated in response to our action? The explainer in /sur/channels.hoon merely stipulates that an action, whether local or remote, may become a response, which I think is a good enough mental model.

Copy link
Member

@Fang- Fang- left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly looked at server side so far, haven't done a full pass over the client agents yet. Some comments/questions below.

(my %groups^[~.groups^%3 ~ ~] ~)
%- agent:dbug
%^ verb | %warn
%^ verb & %dbug
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably shouldn't be committing this amount of verbosity. (;

Comment on lines +888 to +900
++ ca-init-req
|= req=request-id:v10:cv
^+ ca-core
ca-core(request-id req)
::
++ ca-give-response-update
|= body=response-update-body:v10:cv
^+ ca-core
=/ =response-update:v10:cv [request-id body]
=/ =path /request/(scot %p src.bowl)/(scot %uv request-id)
=. ca-core
(emit (tell:log %dbug ~['giving response update for request ' >request-id< >response-update<] ~))
(give %fact ~[path] channel-response-update+!>(response-update))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kinda spooky how, if we forget to +ca-init-req, the request-id can just be the bunt and we wouldn't know. Certainly weird to give out a fact in that case, don't you think?

Of course there's limited entry points that want to set it, but doesn't that suggest we want those (just +ca-create and +ca-c-channel?) to take a request-id argument, instead of having a separate arm for setting it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, the assumption with putting src.bowl in the path here instead of remembering the source that originated the request in state, is that presently all requests get handled synchronously, right? Probably want to ::NOTE that assumption here.

But if they get handled synchronously, why do we need to store them in requests? (During %channel-command and +watch handling.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also also, we never write into requests beyond the %sending state we gut:by during poke handling. Don't we want to update the request state here to contain the body?

Comment on lines +642 to +651
[%request ship=@ id=@ ~]
:: handle request response subscription
=/ =ship (slav %p ship.pole)
?> =(ship src.bowl)
=/ id=request-id:v10:cv (slav %uv id.pole)
=/ request=incoming-request:v10:cv
(~(gut by requests) id [id ~ %sending ~ ~ |])
=. requests
(~(put by requests) id request)
cor
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The handling of requests here is identical to the %channel-command poke case. That doesn't seem right. I don't think we should invent a request based on a subscription for it coming in. Additionally, we probably want to send an initial fact if it did already exist.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noting that this is also the case in groups.

$: %14
=v-channels:v9:cv
=hooks:h
=requests:v10:cv
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Presently we never clean these up/out, but we should periodically check all entries' final-at for that, right?

Right, that's one of the remaining tasks you noted:

request cleanup in channels-server

Comment on lines +527 to +530
=/ =incoming-request:v10:cv
(~(gut by requests) request-id [request-id ~ %sending ~ ~ |])
=. requests
(~(put by requests) request-id incoming-request)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that a %channel-command is always a new request, not clear to me why we gut:by here. We'd want to just overwrite whatever's there, right?

[%error type=action-error message=tang]
==
::
+$ requests (map request-id incoming-request)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a mismatch here, where the subscription path is per source ship per request-id, but we only store them by request-id. Doesn't this let people clobber each other's request statuses?

[group.new now.bowl %channel nest %add channel]
[now.bowl %default readers.new |]
=/ =action:v10:gv
[(end [3 4] eny.bowl) %group group.new %channel nest %add channel]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't care to remember the requests, shouldn't we just keep sending plain unidentified actions? Alternatively, just pass *@uv or something similar that makes it obvious we don't care and won't use it.

%invalid-name
%request-too-large
%unknown
==
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since these are basically identical between the agents, and we expect them to remain that way, could be worth factoring out into a /sur/vent or what have you. The result=(unit response-body) will be different, but easy to parameterize with a |$ type constructor.

Comment on lines +889 to +890
:: successful: discard after 5 minutes
?: |(?=([~ %ok *] result.req) ?=([~ %no-change *] result.req))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the reduced expiration time for successful requests? Is the assumption that in the success case, the result was near-instant and/or the delivery of the result most likely?

Copy link
Collaborator

@mikolajpp mikolajpp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the groups agent. This line of work looks quite promising! Left quite some comments, most of them minor.

# read from file, escape 's, replace newlines w/ gaps
# Use paste for portable line joining (works on both GNU and BSD systems)
HOON="$(cat $INPUT | sed "s;';\\\\';g" | paste -sd' ' -)"
HOON="$(cat $INPUT | sed "s;';\\\\';g" | perl -pe 's/\n/ /g')"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am curious to know why this is better?

=/ m (strand ,vase)
;< our=ship bind:m get-our
;< ~ bind:m (poke [our %hood] kiln-commit+!>([%groups |]))
(pure:m !>(%ok))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
(pure:m !>(%ok))
(pure:m !>(&))

Noting in passing (also for myself, to fix this in the backend test runner) that this makes for a weird terminal output from click: it will show the numeric value of %ok, rather than the term itself.

::
:: versions
::
++ v10
Copy link
Collaborator

@mikolajpp mikolajpp Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We must be careful here when merging to bump the version number.

Edit: this is because subscription lifecycle PR, which likely will go in before this PR, is currently at v11. v10 was already merged with some other PR.

++ v10
=, v9
|%
++ response
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
++ response
++ response

Comment on lines +87 to +89
%ok (r-group r-group.body)
%error (error +.body)
%pending s+status.body
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
%ok (r-group r-group.body)
%error (error +.body)
%pending s+status.body
%ok (r-group r-group.body)
%error (error +.body)
%pending s+status.body

Comment on lines -3262 to +3597
[%pass wire %agent dock %poke group-command+!>(`c-groups:g`[%leave flag])]
=/ req-id (end [3 8] eny.bowl)
[%pass wire %agent dock %poke group-command+!>(`command:g`[req-id %leave flag])]
Copy link
Collaborator

@mikolajpp mikolajpp Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is different from the logic introduced in +go-send-command. Should +go-send-command be used here? Though I guess after issuing +leave-group we are usually going to delete the group altogether, so perhaps this is fine.

=/ request (~(get by requests) id)
=. cor (tell:l %dbug ~['receiving cmd poke ack' >id< >cmd.i.t.t.wire<])
?~ p.sign
?~ request go-core
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not have a request entry for an issued command with venter? If this is unexpected we should log at least a warning or even an error here.

=? cor ?=(^ http-id.u.request)
(give-http-response u.http-id.u.request response)
:: mark as fetched after delivering final error response to client
=/ new-req (~(got by requests) id)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have just put a modified request into requests on L3986. We can just preserve it for use here.

Comment on lines +4095 to +4100
%kick
=/ id=request-id:v10:gv (slav %uv i.t.wire)
=/ =^wire (weld go-area /request/(scot %uv id))
=/ =path (weld go-server-path /request/(scot %uv id))
=/ =dock [p.flag server]
=. cor ((safe-watch wire dock path) &)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the server not kick us after fulfilling request? If so, we should probably condition the resubscription on the request status.

~> %spin.['join']
^- card
=/ =wire (weld fi-area /join/[?~(tok %public (scot %uv u.tok))])
=/ req-id (end [3 8] eny.bowl)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using entropy for id means that we could overwrite requests if we poke more than once in an event. I am not sure if this would be a problem for internally issued actions, so perhaps this is fine?

Or is entropy different on each agent activation. A cursory look at gall does not make me think this is the case. CC @Fang-

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each agent activation has a fresh eny, yes.

It's generally considered good practice to hash or otherwise tweak the entropy, rather than taking raw bytes from eny directly. Here, for example, you might consider using the +mug of the action as a salt, to avoid the case you describe, a la (end 3^8 (shas (mug action) eny.bowl)).

Copy link
Collaborator

@mikolajpp mikolajpp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some more comments. On the way to finish the review: only channels and channels-server remain.


1. Copy the changed files to the pier: `rsync -avL desk/* <path-to-pier>/groups/`
2. Run `backend/commit-groups.sh` passing the pier path as an argument to compile the synced changes
3. Wait and check for changes to the logs, sometimes commits can take upwards of 30s to 2 mins.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The drag on productivity for agents due to unbearably slow compilation is going to be even worse that it is for us. (That is, unless they can one-shot Hoon) Is it usable at this point for any non-trivial changes?

::
[%server %groups ship=@ name=@ %request rest=*]
=. cor (tell:l %dbug ~['received watch request' >pole<])
=+ ship=(slav %p ship.pole)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A check is missing here.

Suggested change
=+ ship=(slav %p ship.pole)
=+ ship=(slav %p ship.pole)
?> =(our.bowl ship)

:: $r-channels: channel responses
::
:: responses are generated in response to a channel change, and are
:: disseminated to clients we ourselves are using.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
:: disseminated to clients we ourselves are using.
:: disseminated to client subscribers.

:: change
:: $a-channels: channels actions
::
:: actions are requests to the agent as a channel client. most actions
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is confusing: there are in fact two different agents, unlike with groups.

Suggested change
:: actions are requests to the agent as a channel client. most actions
:: actions are requests to the %channels agent. most actions

Comment on lines +372 to +373
:: commands are requests to the agent as a channel host. they are checked
:: for permissions.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
:: commands are requests to the agent as a channel host. they are checked
:: for permissions.
:: commands are requests to the %channels-server agent. they are checked
:: for permissions.

::
:: some actions happen to be the same as commands, but this can freely
:: change
:: $a-channels: channels actions
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
:: $a-channels: channels actions
:: $a-channels: channel actions

Comment on lines +642 to +651
[%request ship=@ id=@ ~]
:: handle request response subscription
=/ =ship (slav %p ship.pole)
?> =(ship src.bowl)
=/ id=request-id:v10:cv (slav %uv id.pole)
=/ request=incoming-request:v10:cv
(~(gut by requests) id [id ~ %sending ~ ~ |])
=. requests
(~(put by requests) id request)
cor
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noting that this is also the case in groups.

|$ [data]
[rev=@ud data]
::
++ v10
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With recent type versions in groups I used a convention where: (1) we document the reason for type version change, such us "X depends on Y", or "X modified ...". (2) for new types we include the original comment.
See /sur/groups-ver.hoon for examples.

This creates a paper-trail accesible right in the code, and is really helpful for understanding changes between versions.

/- cv=channels-ver
/+ j=channel-json
|_ =c-channels:c
|_ =command:v10:cv
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we shield this type with lib-negotiate, it seems like this should not be versioned? Types that are in any way tied up with the client interface should not leak beyond the channels agent.

(Also see similar comments in groups)

Comment on lines +753 to +755
:: should we return bunts here or crash?
=/ =reply-meta:v9:cv
?~ post=(~(get by v-posts) id.update) *reply-meta:v9:cv
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that venter is supposed to give us an ability to return response errors back to the requester, we should probably crash, if that would be returned to the requester as a response error.

Copy link
Collaborator

@mikolajpp mikolajpp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another batch of comments: reviewed %channels.

?> from-self
cor
?: ?=(%toggle-post -.a-channels)
?> from-self
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is probably a mistake, it would allow remote ships to hide our own posts.

:: TODO: add transfer/import channels
%channel-action-1
=+ !<(=a-channels:v9:cv vase)
$(+< channel-action-2+!>(`action:v10:cv`[(end [3 4] eny.bowl) a-channels]))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is at odds with behavior in groups: there we use 0v0 as the id, which in particular means repeated requests will overwrite.

Comment on lines +1165 to +1167
=/ new-req (~(got by requests) request-id)
=. requests
(~(put by requests) request-id new-req(fetched &))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't mutate request in scope here, so we should either define an alias or simply use it as is.

Suggested change
=/ new-req (~(got by requests) request-id)
=. requests
(~(put by requests) request-id new-req(fetched &))
=. requests
(~(put by requests) request-id u.request(fetched &))

Comment on lines +1718 to +1719
[=kind:c ship=@ name=@ %request id=@ ~]
?> ?=([%behn %wake ~] sign)
Copy link
Collaborator

@mikolajpp mikolajpp Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand we only ever receive timeouts on this wire, but I think it could be worth listening on /request/$id/timeout wire for legibility and to make any future changes easier.

:: so that we have negotiated a matching version. If we want to do
:: anything in particular on a mismatched version, we can call
:: +can-poke:neg.
:: if we have a request-id, subscribe to responses and wrap command
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This says "if we have a request-id", but there is no conditional. Is there something missing here?

Comment on lines +2401 to +2404
=/ request=incoming-request:v10:cv
(~(gut by requests) req-id [req-id ~ %nacked ~ ~ |])
=. result.request
`[%error %unknown u.p.sign]
Copy link
Collaborator

@mikolajpp mikolajpp Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't set the status as %nacked unless we don't actually have the request. Is this correct? (We do the same in groups)

=. final-at.request `now.bowl
=. requests
(~(put by requests) req-id request)
:: the behn timer will still fire for the timeout, but it should be fine
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Saying "(...) for the request timout" would make it clearer. Also should be fine feels like we are not sure.
It would be better if it says something like "but we safely ignore it", and make sure that is true.

Comment on lines +2416 to +2419
=/ posts=v-posts:v9:cv
?. ?=(%ok -.body.response-update)
posts.channel
posts.channel:(ca-u-channels update.body.response-update)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interestingly, we preemptively update our local state without waiting for the fact. Does this mean each time a subscriber sends an action he will apply the same update twice, first from the response, second from the channel update fact?

I think we are guaranteed that these two updates will arrive one after another, without any intervening facts in between?

CC @Fang-

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we have any hard ordering guarantees here, the facts will travel on two separate ames flows. (Because they happen on two different subscriptions with two different wires.)

So, probably theoretically possible to hear other stuff in between the two. (But, you obviously won't hear two facts from the same subscription out of order.)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then the following scenario is possible:
If Alice's action A caused update fact B (with update B* arriving as request response), and a subsequent Kevin's action K caused fact update L, it is possible that Alice is going to hear the facts in either of the orders:

  1. B* < B < L
  2. B < L < B*

While we are guaranteed that B < L, because they arrive on the same flow, in case (2) we apply L followed by B* out of order, which can cause problems. While it is possible that our update operation is idempotent (though this needs a careful check – it is not a given. creating a resource twice, for instance, could cause crashes), they are definitely not commutative.

For instance, in channels, if B = create a post, and L = delete a post, in scenario (2) Alice ends up recreating the post wrongly in her local state. In groups, on the other hand, (2) will cause a resync, which triggers on updates arriving out of order.

These problems could be avoided in groups by checking timestamps in those two cases to avoid applying duplicated update fact B in (1), and in (2) to drop the out-of-order update B*. It seems like channels receive updates out of order by design, so this solution might not apply straight away.

result `body.response
final-at ?.(is-final ~ `now.bowl)
==
:: if this is a successful create response, set up the channel
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not quite sure what are we doing here: I think the comment should be attached to the branch where we do handle the creation, because here we actually handle a few different things.

==
:: if this is a successful create response, set up the channel
=* body body.response-update
=^ meta ca-core
Copy link
Collaborator

@mikolajpp mikolajpp Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think handling creation in a separate arm, like we used to, would prove to be less confusing. Are there good reasons why we decided to unify those two arms?

Copy link
Collaborator

@mikolajpp mikolajpp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Last batch of comments regarding channels-server!
Found a few things which looked like could be an issue, other than that mostly nits.

Comment on lines +376 to +377
=/ =incoming-request:v10:gv
(~(gut by requests) request-id [request-id ~ %sending ~ ~ |])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this still leaves the question whether we should not perhaps just overwrite it, as @Fang- noted in channels-server.

Comment on lines -797 to +821
~& "%channel-server: revoke perms for {<affected>}"
=. cor (emit (tell:log %dbug ~['%channel-server: revoke perms for' >affected<] ~))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good changes!

Comment on lines +1016 to +1019
:: if we have no update, ca-core should already have cards
:: for an errored response
?~ update
ca-core
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is hard to understand what this comment refers to without some more context. Are we saying a possible error was already generated somewhere else, for example in +ca-c-post?

Comment on lines +1027 to +1028
:: if a command leaves the data unchanged we still want to give a
:: response so that the client knows the operation completed
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally this comment should be attached to the place it refers to: it is enough to attach it to the first conditional below.

Comment on lines +865 to +868
|_ $: =nest:c
channel=v-channel:c
gone=_|
=request-id:v10:cv
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ordering of arguments here is different from the one in groups. We should choose one and stick to it. (I prefer this ordering to the one from groups, as it groups channel-related data together)

Comment on lines +1169 to +1175
?. |(=(src.bowl author.u.post) (is-admin:ca-perms src.bowl))
:- ~
%- ca-give-response-update
:* %error
%not-authorized
~['Cannot edit post from other authors unless admin']
==
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this the same error as on L1155 above? We should either combine these two conditionals into one, or unify (alternatively, meaningfully distinguish) the error messages

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before this change there was only one assertion (corresponding to L1169 after change), now there are two. Are we fixing some bug at the same time?

Comment on lines +1322 to +1323
::TODO send response based on hook outcome
((slog p.result) [[~ replies] ca-core])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason we +slog here? Given the logging facilities we have now, I think we should stop using +slog on principle and prefer to log with appropriate priority.

Comment on lines +1352 to +1358
?. |(=(src.bowl author.u.reply) (is-admin:ca-perms src.bowl))
:- [~ replies]
%- ca-give-response-update
:* %error
%not-authorized
~['Cannot modify reply from other authors unless admin']
==
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recall that above in +ca-c-post arm there were two conditionals rather than one, one comparing with the author of the existing reply, another with the author of the reply command. Does not this mean here we can modify our own reply to appear as originating from another user?

(run-hooks event nest 'edit blocked')
?: ?=(%.n -.result)
((slog p.result) [~ replies])
((slog p.result) [[~ replies] ca-core])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's log rather than slog :-)

Comment on lines +1404 to +1405
::TODO send response based on hook outcome
((slog p.result) [[~ replies] ca-core])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slog we should not, let's log! 🎵

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants