From b939819ccbd1fed287544ee50dcab06736d648c7 Mon Sep 17 00:00:00 2001 From: mintaka Date: Sat, 3 Oct 2026 22:40:23 -0400 Subject: [PATCH 1/2] fix(comms): resolve update_members channel by name for agent tools (RIG-3030) Agent tools address channels by name only. UpdateChannelMembersAsAccount now resolves channel_id with ChannelByNameForViewer within the caller's visible set before the id-typed handler, matching post and list. Unknown or invisible names return NOT_FOUND, ambiguous names INVALID_ARGUMENT, and there is no home default. Co-authored-by: Matt Wilkinson --- go/internal/comms/agent_caller.go | 20 ++++--- go/internal/comms/org_mgmt_pgtest_test.go | 63 +++++++++++++++++++++-- 2 files changed, 72 insertions(+), 11 deletions(-) diff --git a/go/internal/comms/agent_caller.go b/go/internal/comms/agent_caller.go index 0399ac150..7eef788ba 100644 --- a/go/internal/comms/agent_caller.go +++ b/go/internal/comms/agent_caller.go @@ -12,6 +12,7 @@ import ( "fmt" "connectrpc.com/connect" + "google.golang.org/protobuf/proto" compassv1 "github.com/RigelBuild/compass/go/gen/compass/v1" "github.com/RigelBuild/compass/go/internal/store" @@ -275,12 +276,11 @@ func (c *Comms) CreateChannelAsAccount( } // UpdateChannelMembersAsAccount executes one agent-initiated UpdateChannelMembers -// as account, mirroring UpdatePinnedBoardAsAccount: WithActor + the shared -// UpdateChannelMembers handler path, so the membership authz, the store ops, and -// the ChannelChanged fan-out are identical to a human caller's. A non-member or -// invisible channel collapses to the same code a human gets. The request always -// names its channel explicitly (channel_id), so there is no home-channel -// defaulting here. +// as account. Agent tools address channels by NAME, so channel_id is resolved +// within account's visible set first (unknown or invisible → CodeNotFound, +// ambiguous → CodeInvalidArgument), with no home default. The resolved request +// then runs the shared handler under WithActor, so authz and fan-out match a +// human caller's. func (c *Comms) UpdateChannelMembersAsAccount( ctx context.Context, account store.AccountID, @@ -289,7 +289,13 @@ func (c *Comms) UpdateChannelMembersAsAccount( if account == "" { return nil, errNoActor } - resp, err := c.UpdateChannelMembers(WithActor(ctx, account), connect.NewRequest(req)) + ch, err := c.store.ChannelByNameForViewer(ctx, account, req.GetChannelId()) + if err != nil { + return nil, edgeError(err) + } + resolved := proto.CloneOf(req) + resolved.ChannelId = string(ch.ID) + resp, err := c.UpdateChannelMembers(WithActor(ctx, account), connect.NewRequest(resolved)) if err != nil { return nil, err } diff --git a/go/internal/comms/org_mgmt_pgtest_test.go b/go/internal/comms/org_mgmt_pgtest_test.go index da3ee6eba..abd049319 100644 --- a/go/internal/comms/org_mgmt_pgtest_test.go +++ b/go/internal/comms/org_mgmt_pgtest_test.go @@ -117,7 +117,7 @@ func TestUpdateChannelMembersAsAccountAddsMember(t *testing.T) { events := firstEventAfterBoundary(t, h, owner.ID, &compassv1.SubscribeCommsRequest{SinceSeq: 0}) resp, err := h.svc.UpdateChannelMembersAsAccount(ctx, agent.ID, &compassv1.UpdateChannelMembersRequest{ - ChannelId: string(ch.ID), + ChannelId: ch.Name, AddMemberHandles: []string{newcomer.Handle}, }) if err != nil { @@ -155,7 +155,7 @@ func TestUpdateChannelMembersAsAccountUnknownMemberHandleIsNotFound(t *testing.T } _, err = svc.UpdateChannelMembersAsAccount(ctx, agent.ID, &compassv1.UpdateChannelMembersRequest{ - ChannelId: string(ch.ID), + ChannelId: ch.Name, AddMemberHandles: []string{"ghost"}, }) connectNotFoundFor(t, err, "ghost", "UpdateChannelMembersAsAccount with an unresolvable member handle") @@ -185,7 +185,7 @@ func TestUpdateChannelMembersAsAccountInvisibleMemberHandleIsNotFound(t *testing } _, err = svc.UpdateChannelMembersAsAccount(ctx, agent.ID, &compassv1.UpdateChannelMembersRequest{ - ChannelId: string(ch.ID), + ChannelId: ch.Name, AddMemberHandles: []string{otherAgent.Handle}, }) connectNotFoundFor(t, err, otherAgent.Handle, "UpdateChannelMembersAsAccount with a foreign-owner (invisible) member handle") @@ -214,12 +214,67 @@ func TestUpdateChannelMembersAsAccountNonMemberIsNotFound(t *testing.T) { } _, err = svc.UpdateChannelMembersAsAccount(ctx, strangerAgent.ID, &compassv1.UpdateChannelMembersRequest{ - ChannelId: string(ch.ID), + ChannelId: ch.Name, AddMemberHandles: []string{stranger.Handle}, }) connectCodeIs(t, err, connect.CodeNotFound, "UpdateChannelMembersAsAccount on invisible channel") } +// Agent tools name channels; a bare id is not a name, so it must miss rather +// than let a model bypass the viewer-scoped resolve. +func TestUpdateChannelMembersAsAccountChannelIDIsNotFound(t *testing.T) { + svc, st := newHandler(t) + ctx := context.Background() + owner := mustUser(t, st, "owner") + agent := mustAgent(t, st, owner.ID, "manager") + newcomer := mustUser(t, st, "newcomer") + + ch, err := st.CreateChannel(ctx, agent.ID, store.NewChannel{Name: "room", Kind: store.ChannelKindChannel}) + if err != nil { + t.Fatalf("CreateChannel: %v", err) + } + + _, err = svc.UpdateChannelMembersAsAccount(ctx, agent.ID, &compassv1.UpdateChannelMembersRequest{ + ChannelId: string(ch.ID), + AddMemberHandles: []string{newcomer.Handle}, + }) + connectCodeIs(t, err, connect.CodeNotFound, "UpdateChannelMembersAsAccount by channel id") +} + +// Two visible channels sharing a name must be refused, never silently picked. +func TestUpdateChannelMembersAsAccountAmbiguousChannelIsInvalidArgument(t *testing.T) { + svc, st := newHandler(t) + ctx := context.Background() + owner := mustUser(t, st, "owner") + agent := mustAgent(t, st, owner.ID, "manager") + newcomer := mustUser(t, st, "newcomer") + for range 2 { + if _, err := st.CreateChannel(ctx, agent.ID, store.NewChannel{Name: "dupe", Kind: store.ChannelKindChannel}); err != nil { + t.Fatalf("CreateChannel(dupe): %v", err) + } + } + + _, err := svc.UpdateChannelMembersAsAccount(ctx, agent.ID, &compassv1.UpdateChannelMembersRequest{ + ChannelId: "dupe", + AddMemberHandles: []string{newcomer.Handle}, + }) + connectCodeIs(t, err, connect.CodeInvalidArgument, "UpdateChannelMembersAsAccount on ambiguous channel name") +} + +// An empty channel name has no home default: it misses like any unknown name. +func TestUpdateChannelMembersAsAccountEmptyChannelHasNoHomeDefault(t *testing.T) { + svc, st := newHandler(t) + ctx := context.Background() + owner := mustUser(t, st, "owner") + agent := mustAgent(t, st, owner.ID, "manager") + newcomer := mustUser(t, st, "newcomer") + + _, err := svc.UpdateChannelMembersAsAccount(ctx, agent.ID, &compassv1.UpdateChannelMembersRequest{ + AddMemberHandles: []string{newcomer.Handle}, + }) + connectCodeIs(t, err, connect.CodeNotFound, "UpdateChannelMembersAsAccount with empty channel") +} + // TestCreateChannelGroupAsAccountReturnsGroup: an agent creates a top-level group // → the ChannelGroup is returned, created under the agent's account (the store // stamps owner = the actor account, resolved server-side from the actor context), From 8b1f007b0bfdccf683f7bee198f90cac8f5a1242 Mon Sep 17 00:00:00 2001 From: mintaka Date: Sat, 3 Oct 2026 23:00:01 -0400 Subject: [PATCH 2/2] fix(comms): name the update_members agent path ByName and document name-typed arms (RIG-3030) Co-authored-by: Matt Wilkinson --- go/internal/comms/agent_caller.go | 8 +-- go/internal/comms/org_mgmt_pgtest_test.go | 62 +++++++++---------- .../gen/compass/v1/agent_gateway.pb.go | 3 + go/internal/runnerhub/helpers_test.go | 2 +- go/internal/runnerhub/hub.go | 4 +- go/internal/runnerhub/relay_comms.go | 2 +- go/internal/runnerhub/relay_org_mgmt_test.go | 2 +- .../src/gen/compass/v1/agent_gateway_pb.ts | 4 ++ proto/compass/v1/agent_gateway.proto | 3 + 9 files changed, 51 insertions(+), 39 deletions(-) diff --git a/go/internal/comms/agent_caller.go b/go/internal/comms/agent_caller.go index 7eef788ba..396b553a8 100644 --- a/go/internal/comms/agent_caller.go +++ b/go/internal/comms/agent_caller.go @@ -275,13 +275,13 @@ func (c *Comms) CreateChannelAsAccount( return resp.Msg, nil } -// UpdateChannelMembersAsAccount executes one agent-initiated UpdateChannelMembers +// UpdateChannelMembersAsAccountByName executes one agent-initiated UpdateChannelMembers // as account. Agent tools address channels by NAME, so channel_id is resolved // within account's visible set first (unknown or invisible → CodeNotFound, // ambiguous → CodeInvalidArgument), with no home default. The resolved request // then runs the shared handler under WithActor, so authz and fan-out match a // human caller's. -func (c *Comms) UpdateChannelMembersAsAccount( +func (c *Comms) UpdateChannelMembersAsAccountByName( ctx context.Context, account store.AccountID, req *compassv1.UpdateChannelMembersRequest, @@ -300,7 +300,7 @@ func (c *Comms) UpdateChannelMembersAsAccount( return nil, err } if resp == nil { - return nil, connect.NewError(connect.CodeInternal, errors.New("comms UpdateChannelMembersAsAccount: UpdateChannelMembers returned nil response")) + return nil, connect.NewError(connect.CodeInternal, errors.New("comms UpdateChannelMembersAsAccountByName: UpdateChannelMembers returned nil response")) } return resp.Msg, nil } @@ -331,7 +331,7 @@ func (c *Comms) CreateChannelGroupAsAccount( } // OpenDMAsAccount executes one agent-initiated OpenDM as account, mirroring -// UpdateChannelMembersAsAccount: WithActor + the shared OpenDM handler path, so +// UpdatePinnedBoardAsAccount: WithActor + the shared OpenDM handler path, so // the peer resolve, the same-owner authz, the reserved-DM-group upsert, and the // post-commit ChannelChanged fan-out are identical to a human caller's. An // unknown, cross-owner, or self peer collapses to the same code a human gets. The diff --git a/go/internal/comms/org_mgmt_pgtest_test.go b/go/internal/comms/org_mgmt_pgtest_test.go index abd049319..e0b51755d 100644 --- a/go/internal/comms/org_mgmt_pgtest_test.go +++ b/go/internal/comms/org_mgmt_pgtest_test.go @@ -99,10 +99,10 @@ func TestCreateChannelAsAccountUnknownMemberHandleIsNotFound(t *testing.T) { connectNotFoundFor(t, err, "ghost", "CreateChannelAsAccount with an unresolvable member handle") } -// TestUpdateChannelMembersAsAccountAddsMember: an agent adds a member to a channel +// TestUpdateChannelMembersAsAccountByNameAddsMember: an agent adds a member to a channel // it authored (and so can mutate) → the updated Channel carries the new member, // and a ChannelChanged is fanned out (parity with the human caller's path). -func TestUpdateChannelMembersAsAccountAddsMember(t *testing.T) { +func TestUpdateChannelMembersAsAccountByNameAddsMember(t *testing.T) { h := newStreamHarness(t) ctx := context.Background() owner := mustUser(t, h.store, "owner") @@ -116,12 +116,12 @@ func TestUpdateChannelMembersAsAccountAddsMember(t *testing.T) { events := firstEventAfterBoundary(t, h, owner.ID, &compassv1.SubscribeCommsRequest{SinceSeq: 0}) - resp, err := h.svc.UpdateChannelMembersAsAccount(ctx, agent.ID, &compassv1.UpdateChannelMembersRequest{ + resp, err := h.svc.UpdateChannelMembersAsAccountByName(ctx, agent.ID, &compassv1.UpdateChannelMembersRequest{ ChannelId: ch.Name, AddMemberHandles: []string{newcomer.Handle}, }) if err != nil { - t.Fatalf("UpdateChannelMembersAsAccount: %v", err) + t.Fatalf("UpdateChannelMembersAsAccountByName: %v", err) } if !slices.Contains(resp.GetChannel().GetMemberAccountIds(), string(newcomer.ID)) { t.Fatalf("member set = %v, want it to contain the added %q", resp.GetChannel().GetMemberAccountIds(), newcomer.ID) @@ -137,13 +137,13 @@ func TestUpdateChannelMembersAsAccountAddsMember(t *testing.T) { } } -// TestUpdateChannelMembersAsAccountUnknownMemberHandleIsNotFound: an agent adds a +// TestUpdateChannelMembersAsAccountByNameUnknownMemberHandleIsNotFound: an agent adds a // member naming a handle that resolves to no account → the T3 batch resolver // (AccountsByHandles, OQ-2) fails the whole call with the oracle-safe CodeNotFound // naming the submitted handle, identical to the code a human caller gets. The // org-management adapter inherits that resolution; it never partially applies a // member set with an unresolved handle in it. -func TestUpdateChannelMembersAsAccountUnknownMemberHandleIsNotFound(t *testing.T) { +func TestUpdateChannelMembersAsAccountByNameUnknownMemberHandleIsNotFound(t *testing.T) { svc, st := newHandler(t) ctx := context.Background() owner := mustUser(t, st, "owner") @@ -154,14 +154,14 @@ func TestUpdateChannelMembersAsAccountUnknownMemberHandleIsNotFound(t *testing.T t.Fatalf("CreateChannel: %v", err) } - _, err = svc.UpdateChannelMembersAsAccount(ctx, agent.ID, &compassv1.UpdateChannelMembersRequest{ + _, err = svc.UpdateChannelMembersAsAccountByName(ctx, agent.ID, &compassv1.UpdateChannelMembersRequest{ ChannelId: ch.Name, AddMemberHandles: []string{"ghost"}, }) - connectNotFoundFor(t, err, "ghost", "UpdateChannelMembersAsAccount with an unresolvable member handle") + connectNotFoundFor(t, err, "ghost", "UpdateChannelMembersAsAccountByName with an unresolvable member handle") } -// TestUpdateChannelMembersAsAccountInvisibleMemberHandleIsNotFound: an agent adds +// TestUpdateChannelMembersAsAccountByNameInvisibleMemberHandleIsNotFound: an agent adds // a member naming a handle that IS a real account but one the caller cannot see — // an agent living only under a DIFFERENT owner's per-owner namespace (DL-271). The // bare handle misses the global user index and misses the caller-owner agent index, @@ -169,7 +169,7 @@ func TestUpdateChannelMembersAsAccountUnknownMemberHandleIsNotFound(t *testing.T // entirely-unknown handle gets: the caller cannot distinguish "no such handle" from // "a handle I'm not allowed to see", so it cannot probe another owner's roster by // naming its agents as members. -func TestUpdateChannelMembersAsAccountInvisibleMemberHandleIsNotFound(t *testing.T) { +func TestUpdateChannelMembersAsAccountByNameInvisibleMemberHandleIsNotFound(t *testing.T) { svc, st := newHandler(t) ctx := context.Background() owner := mustUser(t, st, "owner") @@ -184,24 +184,24 @@ func TestUpdateChannelMembersAsAccountInvisibleMemberHandleIsNotFound(t *testing t.Fatalf("CreateChannel: %v", err) } - _, err = svc.UpdateChannelMembersAsAccount(ctx, agent.ID, &compassv1.UpdateChannelMembersRequest{ + _, err = svc.UpdateChannelMembersAsAccountByName(ctx, agent.ID, &compassv1.UpdateChannelMembersRequest{ ChannelId: ch.Name, AddMemberHandles: []string{otherAgent.Handle}, }) - connectNotFoundFor(t, err, otherAgent.Handle, "UpdateChannelMembersAsAccount with a foreign-owner (invisible) member handle") + connectNotFoundFor(t, err, otherAgent.Handle, "UpdateChannelMembersAsAccountByName with a foreign-owner (invisible) member handle") } -// TestUpdateChannelMembersAsAccountEmptyAccountIsNoActor: an empty account → +// TestUpdateChannelMembersAsAccountByNameEmptyAccountIsNoActor: an empty account → // errNoActor (CodeInvalidArgument). -func TestUpdateChannelMembersAsAccountEmptyAccountIsNoActor(t *testing.T) { +func TestUpdateChannelMembersAsAccountByNameEmptyAccountIsNoActor(t *testing.T) { svc, _ := newHandler(t) - _, err := svc.UpdateChannelMembersAsAccount(context.Background(), "", &compassv1.UpdateChannelMembersRequest{ChannelId: "ch-1"}) - connectCodeIs(t, err, connect.CodeInvalidArgument, "UpdateChannelMembersAsAccount empty account") + _, err := svc.UpdateChannelMembersAsAccountByName(context.Background(), "", &compassv1.UpdateChannelMembersRequest{ChannelId: "ch-1"}) + connectCodeIs(t, err, connect.CodeInvalidArgument, "UpdateChannelMembersAsAccountByName empty account") } -// TestUpdateChannelMembersAsAccountNonMemberIsNotFound: an agent mutating a +// TestUpdateChannelMembersAsAccountByNameNonMemberIsNotFound: an agent mutating a // channel it cannot see collapses to the SAME CodeNotFound a human non-member gets. -func TestUpdateChannelMembersAsAccountNonMemberIsNotFound(t *testing.T) { +func TestUpdateChannelMembersAsAccountByNameNonMemberIsNotFound(t *testing.T) { svc, st := newHandler(t) ctx := context.Background() owner := mustUser(t, st, "owner") @@ -213,16 +213,16 @@ func TestUpdateChannelMembersAsAccountNonMemberIsNotFound(t *testing.T) { t.Fatalf("CreateChannel: %v", err) } - _, err = svc.UpdateChannelMembersAsAccount(ctx, strangerAgent.ID, &compassv1.UpdateChannelMembersRequest{ + _, err = svc.UpdateChannelMembersAsAccountByName(ctx, strangerAgent.ID, &compassv1.UpdateChannelMembersRequest{ ChannelId: ch.Name, AddMemberHandles: []string{stranger.Handle}, }) - connectCodeIs(t, err, connect.CodeNotFound, "UpdateChannelMembersAsAccount on invisible channel") + connectCodeIs(t, err, connect.CodeNotFound, "UpdateChannelMembersAsAccountByName on invisible channel") } -// Agent tools name channels; a bare id is not a name, so it must miss rather -// than let a model bypass the viewer-scoped resolve. -func TestUpdateChannelMembersAsAccountChannelIDIsNotFound(t *testing.T) { +// Agent tools address channels by name only; a bare id must miss, not silently +// work. +func TestUpdateChannelMembersAsAccountByNameChannelIDIsNotFound(t *testing.T) { svc, st := newHandler(t) ctx := context.Background() owner := mustUser(t, st, "owner") @@ -234,15 +234,15 @@ func TestUpdateChannelMembersAsAccountChannelIDIsNotFound(t *testing.T) { t.Fatalf("CreateChannel: %v", err) } - _, err = svc.UpdateChannelMembersAsAccount(ctx, agent.ID, &compassv1.UpdateChannelMembersRequest{ + _, err = svc.UpdateChannelMembersAsAccountByName(ctx, agent.ID, &compassv1.UpdateChannelMembersRequest{ ChannelId: string(ch.ID), AddMemberHandles: []string{newcomer.Handle}, }) - connectCodeIs(t, err, connect.CodeNotFound, "UpdateChannelMembersAsAccount by channel id") + connectCodeIs(t, err, connect.CodeNotFound, "UpdateChannelMembersAsAccountByName by channel id") } // Two visible channels sharing a name must be refused, never silently picked. -func TestUpdateChannelMembersAsAccountAmbiguousChannelIsInvalidArgument(t *testing.T) { +func TestUpdateChannelMembersAsAccountByNameAmbiguousChannelIsInvalidArgument(t *testing.T) { svc, st := newHandler(t) ctx := context.Background() owner := mustUser(t, st, "owner") @@ -254,25 +254,25 @@ func TestUpdateChannelMembersAsAccountAmbiguousChannelIsInvalidArgument(t *testi } } - _, err := svc.UpdateChannelMembersAsAccount(ctx, agent.ID, &compassv1.UpdateChannelMembersRequest{ + _, err := svc.UpdateChannelMembersAsAccountByName(ctx, agent.ID, &compassv1.UpdateChannelMembersRequest{ ChannelId: "dupe", AddMemberHandles: []string{newcomer.Handle}, }) - connectCodeIs(t, err, connect.CodeInvalidArgument, "UpdateChannelMembersAsAccount on ambiguous channel name") + connectCodeIs(t, err, connect.CodeInvalidArgument, "UpdateChannelMembersAsAccountByName on ambiguous channel name") } // An empty channel name has no home default: it misses like any unknown name. -func TestUpdateChannelMembersAsAccountEmptyChannelHasNoHomeDefault(t *testing.T) { +func TestUpdateChannelMembersAsAccountByNameEmptyChannelHasNoHomeDefault(t *testing.T) { svc, st := newHandler(t) ctx := context.Background() owner := mustUser(t, st, "owner") agent := mustAgent(t, st, owner.ID, "manager") newcomer := mustUser(t, st, "newcomer") - _, err := svc.UpdateChannelMembersAsAccount(ctx, agent.ID, &compassv1.UpdateChannelMembersRequest{ + _, err := svc.UpdateChannelMembersAsAccountByName(ctx, agent.ID, &compassv1.UpdateChannelMembersRequest{ AddMemberHandles: []string{newcomer.Handle}, }) - connectCodeIs(t, err, connect.CodeNotFound, "UpdateChannelMembersAsAccount with empty channel") + connectCodeIs(t, err, connect.CodeNotFound, "UpdateChannelMembersAsAccountByName with empty channel") } // TestCreateChannelGroupAsAccountReturnsGroup: an agent creates a top-level group diff --git a/go/internal/gen/compass/v1/agent_gateway.pb.go b/go/internal/gen/compass/v1/agent_gateway.pb.go index 9d05cffbf..d8d517e62 100644 --- a/go/internal/gen/compass/v1/agent_gateway.pb.go +++ b/go/internal/gen/compass/v1/agent_gateway.pb.go @@ -278,6 +278,9 @@ type isCommsCallRequest_Call interface { } type CommsCallRequest_Post struct { + // post, list and update_members carry a channel NAME in channel_id, resolved + // within the caller's visible set: unknown or invisible is NOT_FOUND, + // ambiguous is INVALID_ARGUMENT. Only list defaults an empty name to home. Post *v1.PostMessageRequest `protobuf:"bytes,2,opt,name=post,proto3,oneof"` } diff --git a/go/internal/runnerhub/helpers_test.go b/go/internal/runnerhub/helpers_test.go index 48d68ed4d..d69438a4b 100644 --- a/go/internal/runnerhub/helpers_test.go +++ b/go/internal/runnerhub/helpers_test.go @@ -320,7 +320,7 @@ func (f *fakeCommsCaller) CreateChannelAsAccount(_ context.Context, account stor return f.createChannelResp, nil } -func (f *fakeCommsCaller) UpdateChannelMembersAsAccount(_ context.Context, account store.AccountID, req *compassv1.UpdateChannelMembersRequest) (*compassv1.UpdateChannelMembersResponse, error) { +func (f *fakeCommsCaller) UpdateChannelMembersAsAccountByName(_ context.Context, account store.AccountID, req *compassv1.UpdateChannelMembersRequest) (*compassv1.UpdateChannelMembersResponse, error) { f.mu.Lock() defer f.mu.Unlock() f.calls = append(f.calls, commsCall{account: account, updateMembers: req}) diff --git a/go/internal/runnerhub/hub.go b/go/internal/runnerhub/hub.go index 4c8954019..eb4029397 100644 --- a/go/internal/runnerhub/hub.go +++ b/go/internal/runnerhub/hub.go @@ -332,7 +332,9 @@ type CommsCaller interface { //nolint:interfacebloat // one method per agent-com SetStatusAsAccount(ctx context.Context, account store.AccountID, activity string) (string, error) UpdatePinnedBoardAsAccount(ctx context.Context, account store.AccountID, req *compassv1.UpdatePinnedBoardRequest) (*compassv1.UpdatePinnedBoardResponse, error) CreateChannelAsAccount(ctx context.Context, account store.AccountID, req *compassv1.CreateChannelRequest) (*compassv1.CreateChannelResponse, error) - UpdateChannelMembersAsAccount(ctx context.Context, account store.AccountID, req *compassv1.UpdateChannelMembersRequest) (*compassv1.UpdateChannelMembersResponse, error) + // UpdateChannelMembersAsAccountByName is the agent-tool path: channel_id is a + // channel NAME, resolved within account's visible set, with no home default. + UpdateChannelMembersAsAccountByName(ctx context.Context, account store.AccountID, req *compassv1.UpdateChannelMembersRequest) (*compassv1.UpdateChannelMembersResponse, error) CreateChannelGroupAsAccount(ctx context.Context, account store.AccountID, req *compassv1.CreateChannelGroupRequest) (*compassv1.CreateChannelGroupResponse, error) // OpenDMAsAccount resolves-or-creates the two-party peer DM between account // and the request's peer handle (RIG-2962 T3), same-owner authz enforced diff --git a/go/internal/runnerhub/relay_comms.go b/go/internal/runnerhub/relay_comms.go index d28ff83dd..541e3cad8 100644 --- a/go/internal/runnerhub/relay_comms.go +++ b/go/internal/runnerhub/relay_comms.go @@ -824,7 +824,7 @@ func (h *Hub) executeCall( Result: &compassv1internal.CommsCallResult_CreateChannel{CreateChannel: resp}, }, nil case *compassv1internal.CommsCallRequest_UpdateMembers: - resp, err := h.comms.UpdateChannelMembersAsAccount(ctx, account, c.UpdateMembers) + resp, err := h.comms.UpdateChannelMembersAsAccountByName(ctx, account, c.UpdateMembers) if err != nil { return nil, err } diff --git a/go/internal/runnerhub/relay_org_mgmt_test.go b/go/internal/runnerhub/relay_org_mgmt_test.go index f3b6fbeca..b7eaec001 100644 --- a/go/internal/runnerhub/relay_org_mgmt_test.go +++ b/go/internal/runnerhub/relay_org_mgmt_test.go @@ -4,7 +4,7 @@ package runnerhub // The org-management relay arms (RIG-2673 T3): RelayCommsCall dispatches a // create_channel call to CreateChannelAsAccount, an update_members call to -// UpdateChannelMembersAsAccount, and a create_channel_group call to +// UpdateChannelMembersAsAccountByName, and a create_channel_group call to // CreateChannelGroupAsAccount — each under the bound account, wrapping the // matching result oneof, with call_id round-tripped. A tool error on an arm is // rendered in-band as a CommsCallError, never a transport teardown. Driven diff --git a/packages/compass-agent/src/gen/compass/v1/agent_gateway_pb.ts b/packages/compass-agent/src/gen/compass/v1/agent_gateway_pb.ts index 3784def42..f0568cc59 100644 --- a/packages/compass-agent/src/gen/compass/v1/agent_gateway_pb.ts +++ b/packages/compass-agent/src/gen/compass/v1/agent_gateway_pb.ts @@ -68,6 +68,10 @@ export type CommsCallRequest = Message<"compass.v1.CommsCallRequest"> & { */ call: { /** + * post, list and update_members carry a channel NAME in channel_id, resolved + * within the caller's visible set: unknown or invisible is NOT_FOUND, + * ambiguous is INVALID_ARGUMENT. Only list defaults an empty name to home. + * * @generated from field: compass.v1.PostMessageRequest post = 2; */ value: PostMessageRequest; diff --git a/proto/compass/v1/agent_gateway.proto b/proto/compass/v1/agent_gateway.proto index 9ce2bb132..4b7f8b861 100644 --- a/proto/compass/v1/agent_gateway.proto +++ b/proto/compass/v1/agent_gateway.proto @@ -106,6 +106,9 @@ service AgentGateway { message CommsCallRequest { string call_id = 1; oneof call { + // post, list and update_members carry a channel NAME in channel_id, resolved + // within the caller's visible set: unknown or invisible is NOT_FOUND, + // ambiguous is INVALID_ARGUMENT. Only list defaults an empty name to home. PostMessageRequest post = 2; ListMessagesRequest list = 3; GetRosterRequest roster = 4;