diff --git a/docs-site/concepts/connectors.mdx b/docs-site/concepts/connectors.mdx index cabcced5c0..606bfd9707 100644 --- a/docs-site/concepts/connectors.mdx +++ b/docs-site/concepts/connectors.mdx @@ -7,25 +7,13 @@ Open **Connectors** in Commonly. Channels carry messages between a pod and an outside chat. Tools let an agent read or act in another service after the connection owner grants access. -This live Connectors page shows a free account before any connection or tool -grant. Slack and Telegram are under **Channels**; GitHub and Linear are under -**Tools**. Other tools visible in the list are outside the walked guides here. - -
- Desktop Connectors page before setup, with Slack and Telegram channels above the tools list -
- -
- Mobile Connectors page before setup, with Add on Slack and Telegram and Connect on Linear -
+Slack and Telegram are channels; GitHub and Linear are tools. | Connector | Current path | |---|---| | [Telegram](/integrations/telegram) | Bind a private chat and choose which pods reach it. | -| [Slack](/integrations/slack) | Authorize a workspace, then confirm the connection. | -| [GitHub App](/integrations/github) | An instance administrator installs the app; the connection owner grants tools. On commonly.me this is limited to the instance's own repository. | +| [Slack](/integrations/slack) | Authorize a workspace. | +| [GitHub App](/integrations/github) | An instance administrator installs the app; the connection owner grants tools. | | [Linear](/integrations/linear) | Connect your account and grant read access to an eligible pod. | -The page also lists connectors that are unavailable on this instance. A listed -row is not a working connection. The [grants guide](/concepts/grants) explains -how a connection owner limits tool access and revokes it. +The [grants guide](/concepts/grants) explains how a connection owner limits tool access and revokes it. diff --git a/docs-site/concepts/grants.mdx b/docs-site/concepts/grants.mdx index 5c8a863e0e..62ed2e2fe8 100644 --- a/docs-site/concepts/grants.mdx +++ b/docs-site/concepts/grants.mdx @@ -1,60 +1,58 @@ --- title: Grants -description: Scope connector capabilities to a pod or seat and keep their use auditable. +description: A grant is scoped to a pod and the members allowed to use it, and can expire or be revoked. --- -A grant is a server-enforced capability record. It connects a connector +A grant is scoped to a pod and the members allowed to use it, and can expire or +be revoked. It is a server-enforced capability record: it connects a connector installation to a target, the tools that target may use, and the audience that -may redeem the capability. A grant is not a credential: it does not contain a -secret, and revoking it does not replace the credentials held by the connector. +may redeem the capability. It is not a credential. It does not contain a secret, +and revoking it does not replace the credentials held by the connector. ## The grant lifecycle 1. A connected integration owns the connection and its connector capability. -2. The connection owner creates a root grant for a pod or a seat. +2. The connection owner creates a root grant for a pod. 3. The grant names the allowed tools, write mode, audience, budget, and expiry. 4. An installed agent may attenuate a usable grant into a narrower child grant. 5. The broker checks the grant before each tool call and records the outcome. 6. The connection owner can revoke a grant; revocation cascades to its children. The root-grant route accepts connected GitHub App installations and supported -hosted MCP connections, including Linear. Linear grants are read-only and work -only in eligible private pods. The server derives the installation and broker -from the connection, rather than trusting those values from the request body. +hosted MCP connections, including Linear. Linear exposes only read tools. Every +root grant, Linear included, works only in an eligible private pod. The server +derives the installation and broker from the connection, rather than trusting +those values from the request body. ## Targets and audience -A grant targets either a pod or one seat: +A grant targets a pod: ~~~json { "kind": "pod", "id": "" } ~~~ -~~~json -{ "kind": "seat", "id": "" } -~~~ - Pod grants default to the target pod's current members. A caller can provide a smaller audience, but every listed identity must still be a current member of -the pod. A seat grant's audience is the target seat itself. +the pod. -The read boundary follows the target. Pod members can inspect a pod grant and -its call trail. A seat grant is visible to the connection owner and the target -seat. The API returns an effective audience rather than exposing a stale raw -membership snapshot. +The read boundary follows the pod. Anyone who can view the pod can inspect a +grant and its call trail. Older seat-targeted grants may appear in the list; +only the connection owner and that seat can read them. The API returns an +effective audience rather than exposing a stale raw membership snapshot. ## Grant records The projected grant record can include: - the grant and parent/root identifiers; -- the pod or seat target and effective audience; +- the pod target and effective audience; - allowed tools, write mode, and call budget; - creation, expiry, and revocation state; and - the installation identity and the connection owner who granted it. -The projection deliberately omits connection and broker internals. A grant -read is capability metadata, not a way to retrieve connector credentials. +The projection omits the connection and broker identifiers. A grant read is +capability metadata, not a way to retrieve connector credentials. ## API workflow @@ -63,8 +61,7 @@ agent runtime token only for operations available to an installed agent. ### Inspect grants in a pod -Members with access to the pod can list the grants targeting that pod or one of -its seats: +Anyone who can view the pod can list the grants targeting that pod: ~~~http GET /api/pods/:podId/grants @@ -88,17 +85,17 @@ Content-Type: application/json "tools": ["github.list_issues"], "writeMode": "read", "budget": { "calls": 100, "windowMs": 3600000 }, - "expiresAt": "2026-10-01T00:00:00.000Z" + "expiresAt": "2027-12-31T00:00:00.000Z" } ~~~ -The server verifies connection ownership, target membership, enabled tools, -and audience scope before returning the new grant. +`expiresAt` is required and must be a future time. The server verifies +connection ownership, target membership, enabled tools, and audience scope +before returning the new grant. ### Read a grant and its call trail -The owner, an eligible pod member, or the target seat can read a grant. The -same scope applies to its call trail: +Anyone who can view the pod can read a grant and its call trail: ~~~http GET /api/grants/:grantId @@ -108,12 +105,14 @@ GET /api/grants/:grantId/calls?limit=100 Authorization: Bearer ~~~ -Call records include the tool, outcome, reason, approval identifier, timing, -and an argument digest. They do not return the original tool arguments. +Each connector call through a grant is recorded. The record keeps a digest of +the arguments, not the arguments themselves. A call record includes the tool, +outcome, reason, approval identifier, timing, and an argument digest. ### Attenuate or revoke -An installed agent can delegate a narrower child grant. The child cannot widen +An installed agent can delegate a narrower child grant under the same pod +eligibility rule. The child cannot widen the parent's tools, audience, write mode, budget, or expiry: ~~~http @@ -137,11 +136,10 @@ These records have separate jobs: | Record | Owns | Does not do | |---|---|---| -| Connector installation | Which external capability is connected and where it is projected | Grant a pod or seat access by itself | -| Credential | Secret material and credential lineage | Describe which tools a caller may invoke | +| Connector installation | Which external capability is connected and where it is projected | Grant access by itself | +| Credential | A hashed agent token and its lineage | Describe which tools a caller may invoke | | Grant | Target, tools, audience, limits, and revocation | Store or reveal the connector secret | Keep actual secrets in the deployment's approved secret manager. Use -[Connectors and Grants](/concepts/connectors) for the installation and -credential model, and [Agent Tools](/agents/tools) for the runtime-facing tool -surface. +[Connectors](/concepts/connectors) to connect an integration, and +[Agent Tools](/agents/tools) for the runtime-facing tool surface. diff --git a/docs/agents/fleet-efficiency.md b/docs/agents/fleet-efficiency.md index bfe793e947..39c350f9bb 100644 --- a/docs/agents/fleet-efficiency.md +++ b/docs/agents/fleet-efficiency.md @@ -100,8 +100,9 @@ Earned 2026-10-25: 139 ready rows had no owner, with a median wait of 69 h (p90 every week. - **A public sentence names only what a non-author has walked on prod.** Credential, grant and record wording follows what is walked, not what is planned. Until per-task grants are walked, a grant is - "scoped to a pod and an agent, and can expire or be revoked". A record is "each connector call - through a grant is recorded, never what the agent sent". + "scoped to a pod and the members allowed to use it, and can expire or be revoked". A record is + "each connector call through a grant is recorded. The record keeps a digest of the arguments, not + the arguments themselves". - **About other products, only positive claims about ours.** No "first", "only" or "never". ## Where this comes from diff --git a/docs/audits/claim-table.md b/docs/audits/claim-table.md index 81840afd04..8dd8cb49d1 100644 --- a/docs/audits/claim-table.md +++ b/docs/audits/claim-table.md @@ -102,8 +102,76 @@ Owner: TASK-012. Source: `docs-site/bring-your-own/index.mdx`, `claude-code.mdx` Owner: TASK-014. Source: `docs-site/concepts/connectors.mdx` and `docs-site/concepts/grants.mdx`. +The connectors page is a first-touch page. A row marked `first-touch` in the Page column needs a walk on prod by someone who did not write the claim, with a 390 and a 1440 screenshot. A `file:line` does not meet that bar, so a marked row is TRUE on file:line only until its walk lands. + | Page | Claim | Verdict | Evidence | Date | |---|---|---|---|---| +| connectors (first-touch) | Open Connectors in Commonly | TRUE | `frontend/src/v2/components/V2NavRail.tsx:38` | 2026-10-26 | +| connectors (first-touch) | Channels carry messages between a pod and an outside chat | TRUE | `backend/services/slackApi.ts:69, :82`; `backend/routes/webhooks/telegram.ts:173–177` | 2026-10-26 | +| connectors (first-touch) | Tools let an agent read or act in another service after the connection owner grants access | TRUE | `backend/routes/grants.ts:320, :430` | 2026-10-27 | +| connectors (first-touch) | Slack and Telegram are channels; GitHub and Linear are tools | TRUE | `frontend/src/v2/components/V2ConnectorsPage.tsx:175` | 2026-10-26 | +| connectors (first-touch) | Telegram: bind a private chat and choose which pods reach it | TRUE | `backend/routes/webhooks/telegram.ts:173–177`; `frontend/src/v2/components/V2ConnectorsPage.tsx:616–641` | 2026-10-26 | +| connectors (first-touch) | Slack: authorize a workspace | TRUE | `backend/services/slackOAuthService.ts:53, :66` | 2026-10-26 | +| connectors | Slack: "then confirm the connection" | CUT | Not walked in time. This PR removes the sentence. | 2026-10-26 | +| connectors (first-touch) | GitHub App: an instance administrator installs the app | TRUE | `backend/routes/integrations.ts:239–262` | 2026-10-26 | +| connectors (first-touch) | GitHub App: the connection owner grants tools | TRUE | `backend/routes/grants.ts:320, :346` | 2026-10-26 | +| connectors | GitHub App on commonly.me: "limited to the instance's own repository" | CUT | Not walked in time. This PR removes the sentence. | 2026-10-26 | +| connectors (first-touch) | Linear: connect your account | TRUE | `backend/routes/hostedMcpConnect.ts:351, :697` | 2026-10-26 | +| connectors (first-touch) | Linear: grant read access to an eligible pod | TRUE | `backend/integrations/hostedMcp/linear.ts:28–31, :64, :92–93`; `backend/services/outsiderGrantPodEligibility.ts:46–54` | 2026-10-26 | +| connectors | Live page shows a free account before any connection or tool grant | CUT | Not walked in time. This PR removes the sentence. | 2026-10-26 | +| connectors | "Other tools visible in the list" | CUT | Not walked in time. This PR removes the sentence. | 2026-10-26 | +| connectors | Two screenshots and captions | CUT | Not walked in time. The images stay in `docs-site/images` and are no longer referenced. | 2026-10-26 | +| connectors | The page lists unavailable connectors; a listed row is not a working connection | CUT | Not walked in time. This PR removes the paragraph. | 2026-10-26 | +| connectors (first-touch) | The grants guide explains how a connection owner limits tool access and revokes it | TRUE | `backend/services/roomGrantService.ts:324–373`; `backend/routes/grants.ts:480–501` | 2026-10-27 | +| grants | A grant is scoped to a pod and the members allowed to use it, and can expire or be revoked | TRUE | `backend/models/RoomGrant.ts:28, :32–34` (target, audience snapshot, expiry); `backend/routes/grants.ts:371–379` (audience defaults to current members; each listed id must be one of them); `backend/services/roomGrantService.ts:233–239` (effective audience is the snapshot ∩ current members), `:242–243, :255–257` (expiry required and future), `:431` (expiry enforced on use); revoke `backend/routes/grants.ts:480–501`, `backend/services/roomGrantService.ts:390–396`, `backend/models/RoomGrant.ts:106–136` | 2026-10-27 | +| grants | A grant is a server-enforced capability record | TRUE | `backend/models/RoomGrant.ts:11–36` | 2026-10-26 | +| grants | It is not a credential, and revoking it does not replace the connector's credentials | TRUE | `backend/models/RoomGrant.ts:24–45` (no credential field); `:128–134` (revoke writes only `revokedAt` and `revokedBy`); `backend/models/AgentCredential.ts:41–58` | 2026-10-27 | +| grants | A connected integration's owner owns the connection | TRUE | `backend/routes/grants.ts:320` | 2026-10-26 | +| grants | Main's lifecycle: "creates a root grant for a pod or a seat" | FALSE | Fixed by this PR. `backend/services/outsiderGrantPodEligibility.ts:46`; `backend/routes/grants.ts:367–369, :405–417` | 2026-10-27 | +| grants | The connection owner creates a root grant for a pod | TRUE | `backend/routes/grants.ts:312, :320, :430` | 2026-10-27 | +| grants | The grant names tools, write mode, audience, budget, and expiry | TRUE | `backend/models/RoomGrant.ts:30–34` | 2026-10-26 | +| grants | An installed agent may attenuate a usable grant into a narrower child grant | TRUE | `backend/routes/grants.ts:438–473`; `backend/services/roomGrantService.ts:324–373` | 2026-10-27 | +| grants | The broker checks the grant before each tool call and records the outcome | TRUE | `backend/services/toolBrokerService.ts:834, :1105, :1210, :1430` | 2026-10-26 | +| grants | The connection owner can revoke; revocation cascades to children | TRUE | `backend/routes/grants.ts:480–501` (owner-or-admin check `:487–492`; routes `:500–501`); `backend/services/roomGrantService.ts:390–396`; `backend/models/RoomGrant.ts:106–136` | 2026-10-27 | +| grants | The root-grant route accepts GitHub App and hosted MCP, including Linear | TRUE | `backend/routes/grants.ts:346`; `backend/services/installable/toolInstallables.ts:248` | 2026-10-26 | +| grants | Linear exposes only read tools | TRUE | `backend/integrations/hostedMcp/linear.ts:28–31, :64, :92–93` | 2026-10-26 | +| grants | Every root grant, Linear included, works only in an eligible private pod | TRUE | `backend/services/outsiderGrantPodEligibility.ts:46–54`; `backend/routes/grants.ts:405` (unconditional call), `:457` (attenuation) | 2026-10-27 | +| grants | The server derives the installation and broker from the connection | TRUE | `backend/routes/grants.ts:321–322` (comment), `:350`, `:392–394` (caller's `brokerId` refused), `:428` (`brokerId` from the catalogue) | 2026-10-27 | +| grants | A grant targets a pod: `{ "kind": "pod", "id": "" }` | TRUE | `backend/services/roomGrantService.ts:13` | 2026-10-26 | +| grants | Main's seat target and seat audience | FALSE | Fixed by this PR. Create refuses seat targets at `backend/routes/grants.ts:367–369`, and the outsider gate refuses them too (`backend/services/outsiderGrantPodEligibility.ts:46`). The mint test asserts the 400 at `backend/__tests__/unit/routes/grants.mint.test.js:112–118` and the real gate at `:120–123`. | 2026-10-27 | +| grants | Main's seat grant is visible to its owner and seat | FALSE | Fixed by this PR. The read branch at `backend/routes/grants.ts:523–528` exists, but POST refuses seats (`:367–369`), and attenuation copies the parent's target (`backend/services/roomGrantService.ts:379`). | 2026-10-27 | +| grants | Pod grants default to the pod's current members | TRUE | `backend/routes/grants.ts:371–373` | 2026-10-26 | +| grants | Each listed audience identity must be a current member of the pod | TRUE | `backend/routes/grants.ts:374–379` | 2026-10-27 | +| grants | The read boundary follows the pod; anyone who can view it can inspect the grant and its call trail | TRUE | `backend/routes/grants.ts:514–533` (`gateGrantRead`; pod branch `:529–532`) | 2026-10-27 | +| grants | Older seat-targeted grants may appear in the list; only the connection owner and that seat can read them | TRUE | `backend/routes/grants.ts:367–369` (create refuses seat targets, so a seat grant predates that guard or comes from attenuating one, `backend/services/roomGrantService.ts:379`); `:523–528` (`gateGrantRead` seat branch: granter or that seat only); `:613–618` (the pod list includes seat grants whose target is a current member) | 2026-10-27 | +| grants | The API returns an effective audience, not a stale membership snapshot | TRUE | `backend/routes/grants.ts:100`; `backend/routes/grants.ts:586`, `:620–627` | 2026-10-27 | +| grants | The projected grant record fields | TRUE | `backend/routes/grants.ts:85–110` | 2026-10-26 | +| grants | The projection omits the connection and broker identifiers | TRUE | `backend/routes/grants.ts:94–107` (projected fields); `:578–579` (comment); `:588` (projection call) | 2026-10-27 | +| grants | `grantedBy` is the connection owner | TRUE | `backend/routes/grants.ts:119` | 2026-10-26 | +| grants | Use a human JWT for connection-owner and pod-management operations; use the agent runtime token only for operations available to an installed agent | TRUE | `backend/routes/grants.ts:312`, `:500–501`, `:600` (`auth`, which rejects agent tokens at `backend/middleware/auth.ts:44–45`); `:440–442` (attenuate, `agentRuntimeAuth`); `:540`, `:580` (reads, `dualAuth`) | 2026-10-27 | +| grants | Anyone who can view the pod can list the grants targeting it | TRUE | `backend/routes/grants.ts:600–608` | 2026-10-27 | +| grants | Example tool `github.list_issues` | TRUE | `backend/services/toolBrokerService.ts:198, :201` | 2026-10-26 | +| grants | Example `expiresAt` of 2026-10-01 | FALSE | Fixed by this PR, to 2027-12-31. 2026-10-01 is past and is rejected (`backend/services/roomGrantService.ts:255–257`). | 2026-10-26 | +| grants | `expiresAt` is required and must be in the future | TRUE | `backend/services/roomGrantService.ts:242–243, :255–257` | 2026-10-26 | +| grants | Create verifies connection ownership, target membership, enabled tools, and audience scope | TRUE | `backend/routes/grants.ts:320` (owner), `:367–370` (pod target and `ensureTargetAccess`), `:374–379` (audience), `:395–403` (enabled tools), `:405–417` (outsider gate) | 2026-10-27 | +| grants | An owner, an eligible pod member, or the target seat can read a grant | FALSE | Fixed by this PR. The pod branch has no owner exception (`backend/routes/grants.ts:529–532`). | 2026-10-27 | +| grants | Anyone who can view the pod can read a grant and its call trail | TRUE | `backend/routes/grants.ts:529–532` (gate); `:540` (calls route); `:580` (grant route) | 2026-10-27 | +| grants | Each connector call through a grant is recorded. The record keeps a digest of the arguments, not the arguments themselves | TRUE | `backend/services/toolBrokerService.ts:1078–1123` (`recordCall`, `argsDigest` at `:1113`); call sites `:1164, :1306, :1317, :1327, :1375, :1470`; `backend/models/ToolCall.ts:76, :99, :153–154`; the second writer `backend/services/approvalActionService.ts:608–633` also writes only `argsDigest`. Limit: a parked approval holds the raw arguments in `ApprovalAction.toolCall.canonicalArgs` from park (`backend/services/toolBrokerService.ts:1281`; `backend/models/ApprovalAction.ts:38–44`) until a terminal write unsets them (`backend/services/approvalActionService.ts:537, :857, :904, :1025`). These are call sites, not an exhaustive path audit. | 2026-10-27 | +| grants | A call record includes the tool, outcome, reason, approval identifier, timing, and an argument digest | TRUE | `backend/services/toolBrokerService.ts:1105–1121` (`ToolCall.create`: tool `:1112`, `argsDigest` `:1113`, `at` `:1114`, outcome `:1115`, reason `:1116`, approvalId `:1119`, durationMs `:1120`). The second writer (`backend/services/approvalActionService.ts:620–631`) sets no `at` or `durationMs`; `ToolCall.create` then stamps the write time (`backend/models/ToolCall.ts:258`, `record.at || new Date()`) and stores `duration_ms` as null (`:262`). So an approval-decision record has a time and no duration. | 2026-10-27 | +| grants | Attenuation is under the same pod eligibility rule | TRUE | `backend/routes/grants.ts:457` | 2026-10-27 | +| grants | The child cannot widen tools, audience, write mode, budget, or expiry | TRUE | `backend/services/roomGrantService.ts:338–339, :346–347, :349–351, :369–373` | 2026-10-26 | +| grants | Attenuation uses the agent runtime token | TRUE | `backend/routes/grants.ts:55–59, :440–442` | 2026-10-27 | +| grants | The owner revokes by DELETE or the POST revoke action, and both cascade | TRUE | `backend/routes/grants.ts:500–501` (both routes share `revokeHandler`, `:480–498`); `backend/models/RoomGrant.ts:106–136` | 2026-10-27 | +| grants | Connector installation does not grant access by itself | TRUE | `backend/routes/hostedMcpConnect.ts:36`; `backend/routes/integrations.ts:51`; `backend/services/installable/installableInstallationService.ts:21` (revoke only). The only grant creator is `backend/routes/grants.ts:430`. | 2026-10-27 | +| grants | Connector installation sets which capability is connected and where it is projected | TRUE | `backend/routes/integrations.ts:261–262`; `backend/services/installable/installableInstallationService.ts:165` | 2026-10-26 | +| grants | Credential owns "secret material and credential lineage" | FALSE | Fixed by this PR, reworded to "a hashed agent token and its lineage". Only a hash is stored (`backend/models/AgentCredential.ts:16, :43`); lineage is at `:27, :48, :87`. | 2026-10-26 | +| grants | Credential does not describe which tools a caller may invoke | TRUE | `backend/models/AgentCredential.ts:50`; `backend/controllers/authController.ts:275`; `backend/middleware/daemonAuth.ts:51–52` | 2026-10-26 | +| grants | Grant does not store or reveal the connector secret | TRUE | `backend/routes/grants.ts:85–110`; `backend/models/RoomGrant.ts:30–36` | 2026-10-26 | +| grants | Grant owns target, tools, audience, limits, and revocation | TRUE | `backend/models/RoomGrant.ts:30–36`; `backend/routes/grants.ts:500–501` | 2026-10-27 | +| grants | Link label "Connectors and Grants" points to `/concepts/connectors` | FALSE | Fixed by this PR. The label was at `docs-site/concepts/grants.mdx:145` on main; the page title is "Connectors" (`docs-site/concepts/connectors.mdx:2`). | 2026-10-26 | +| grants | The connectors page covers the installation and credential model | FALSE | Fixed by this PR. The page never mentioned credentials. | 2026-10-26 | +| grants | Agent Tools is the runtime-facing tool surface | TRUE | `docs-site/agents/tools.mdx:3` | 2026-10-26 | +| grants | The API routes listed in the code blocks | TRUE | `backend/routes/grants.ts:312` (POST `/`), `:439` (attenuate), `:500`, `:501` (revoke POST and DELETE), `:540` (calls), `:580` (grant read), `:600` (pod grants list) | 2026-10-27 | ## Self-host