Repository navigation
Let a Bot's owner give it their own apps - #845
Closed
guidovizoso wants to merge 22 commits into
Closed
guidovizoso wants to merge 22 commits into
guidovizoso wants to merge 22 commits into
Conversation
… decide which apps are
…blingReads line in the allow-list
…ile owners decide
… take-back or a rule change
…withdrawn with the grant or the rule
… the team account
Collaborator
Author
|
Landed in #847. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
Stacked on the connect-card-resume PR, which this branch merges in; review that one first, and read this diff against it.
Giving a Bot an app has been an administrator's job, even when the app is one each person connects for themselves. So somebody who made a Bot five minutes ago and asked it to read their Gmail got "Ask an admin", for a mailbox that is theirs. Now a Bot's owner can give it their own apps, without an administrator, within limits an administrator sets.
What an owner can do
DELETE /api/plugins/for/:agentId/apps/:app/give). An administrator's grant stays.The limits
mcp_servers.owner_grants(null means the default for the app's account, so an app switched from Personal to Shared falls back to admin-only by itself).plugin_grants.granted_as = owner, and a call under it on a Personal app always uses the asking person's own account, even on the owner's published Team Bot (credentialActorFor). Publishing a Bot must not hand every teammate the owner's mailbox. An administrator's grant on a Team Bot still uses the owner's account where the owner connected it, as before. A Shared app is the deployment's team account whoever the credential actor is (accountFornever reads it forshared), so the owner marker can't send it to a personal account.anyis usable at once, with no separate audience approval. It records the Shared-account audience itself (shared_use_approvals.approved_as = owner): the people who can use the Bot when it is given (the owner for a private Bot, its published audience for a Team Bot) and never outside input. It is a snapshot like an administrator's approval, so publishing the Bot further later is still caught by the gate and asked of an administrator. It never overwrites an approval already there, and an administrator granting over it makes it theirs. The existing "writes ask first" team rule on Shared apps is unchanged. The audience goes when the owner takes the app back, or when an administrator switches the rule away fromany(the route withdraws every owner-set audience at the app, andapprovalForignores one left behind while the rule is notany). Pressing give again after the rule is reopened puts it back. The admin page's line for a Shared app says "Bot owners who give it to their Bots let those Bots use the team account."ownerApps, on by default), lets an administrator turn the feature off for people.The app card gains an eighth state,
owner_grantable, for an app the person looking may give themselves; nothing is filed for it.Where it runs
mcp_servers.owner_grantsandplugin_grants.granted_as(migration0063_owner_app_grants), andshared_use_approvals.approved_as(migration0064_owner_shared_audience, null read asadmin). TheownerAppscapability is stored with the other capabilities. Nothing is held in process.owner_grants, capability) is read from Postgres on the request, so any process answers the same. During a rolling deploy, a replica from before this change writes grants withoutgranted_as, which is read asadmin, which is what that replica only ever made. One gap to know about: an old replica'scredentialActorFordoes not readgranted_as, so while old and new replicas run side by side, a teammate's call on a published Team Bot, under a grant the owner made on a new replica, could go out on the owner's account if it lands on an old replica. It closes once the rollout finishes; a deployment with no published Team Bots is unaffected. Likewise an old replica approving over an owner-set audience leaves it markedowner, so the owner's take-back would remove it; that also closes with the rollout.(kind, ref, agent)primary key: an owner's does nothing on a conflict, so two presses can't duplicate a grant or overwrite an administrator's. Taking back deletes only rows still markedowner, in one conditional delete, so an administrator's re-grant in between survives. Not serialised: two gives racing for the same Bot and app each pass the "already holds it" check, and each writes anapp_access.owner_grantedrow, though only the press that inserted a grant writes itsplugin_grantedrow.Boundary and audit
credentialActorFor, which that path already uses.app_access.refused, and gives and take-backs writeapp_access.owner_granted/app_access.owner_revokedbeside aplugin_granted/plugin_revokedrow per action, markedgrantedAs: owner. A Shared-app audience set by an owner's grant writesshared_use.owner_approved(the owner, the Bot, the app, the audience), and its withdrawalshared_use.owner_withdrawnwithwhy(owner_took_backorowner_grants_changed). But an owner refused onPOST /grants(adding one action) gets a 403 without a row, the same as every other refusal on that route today.owner_grants, the capability and the list of advertised actions are all read on the server; the client names only the Bot and the app, or one ref that has to match an advertised action.Changelog
Unreleased, naming both migrations, with a paragraph on Shared apps.docs/plugins/composio.md"Shared apps" says owners can decide if an administrator lets them.Merge notes
This and the handoff-progress-row PR both add a Drizzle migration numbered 0063 (
0063_owner_app_grantsand0064_owner_shared_audiencehere,0063_work_items_open_threadthere). Whichever merges second has to regenerate its migrations with the next numbers; the SQL is unchanged.How it was checked
server/tests/owner-app-grants.integration.test.ts, against Postgres: the card's state is the owner's to give, and asks them to connect first when they haven't; one press gives the read-only actions marked as the owner's, with a trail; somebody else's Bot, an app kept for admins and an app nobody turned on are refused; the grant route lets an owner add one action and take back only their own grants; it refuses a ref the app doesn't advertise and records nothing over an admin's grant; the apps a person could give are the ones they connected and open to owners; and a Team Bot whose owner gave it Gmail uses each teammate's own account or asks them to connect, never the owner's, while the same Bot given Gmail by an admin uses the owner's, as before. For a Shared app whose rule isany: the owner's give is allowed by the shared-use gate at once with no admin approval, for the owner only on a private Bot and for teammates on a Team Bot published before the grant (not one published after); taking it back withdraws the audience, but leaves one an admin approved; the admin switching the rule toadminwithdraws it and refuses calls, and a give after reopening restores it; an owner-set audience left behind is not read while the rule is notany; a Personal app's give sets none; an owner adding one action throughPOST /grantssets the same audience.app/tests/admin-owner-grants-line.test.tsx: the admin page's line for a Shared app, a Personal app, and an admin-only app.server/tests/app-access-state.test.ts: who may give an app by default for each kind of account, an administrator's choice winning either way.app/tests/app-access-action.test.tsx,bot-access-your-apps.test.tsx,new-bot-sheet.test.tsx,plugin-mutation-refresh.test.ts: the card's question and one press, Connect and allow, the Your apps switches, and the New Bot sheet.@mastra/coreinstalled locally, and the twolearning-langgraphreal-provider tests time out waiting for a local provider.assertion-shape.test.tspasses. The app suite: 1,647 pass, 0 fail.