Files
PaperClipAI/maps/feature-map/app-permissions.md
T
DottaandPaperclip 58308a3e63 perf(apps): scope sidebar review counts to one connection (#18688)
## Thinking Path

> - Paperclip helps people manage AI agents and their governed app
access.
> - The Apps sidebar displays a Review badge for one connection.
> - It polls the full company attention service every 30 seconds to get
two counts.
> - That service also loads every profile, rule and active catalog
entry.
> - This pull request adds an authorized connection read for only those
counts.
> - The same badge now avoids unrelated profile evaluation and database
result decoding.

## Linked Issues or Issue Description

**Current behavior**

The contextual sidebar polls company-wide attention. It needs only one
connection's signed pending action count and whether any catalog entries
are quarantined.

**Proposed behavior**

Both sidebar variants poll a connection-scoped count endpoint with
existing connection read authority. The full attention API stays
unchanged.

**Reason and benefit**

The retained 200-profile fixture performs seven service queries and
returns 80,115 rows for this badge. The new reader performs one query
and returns one row. This reduces local service CPU and avoids
unnecessary decoded profile/rule data. Related merged evaluator fixes
are already in the baseline: Refs #18673, Refs #18681. A search found no
existing PR for this scoped count reader. ROADMAP.md contains no
duplicate planned work.

## What Changed

- Add the narrow shared DTO, typed client, guarded REST/OpenAPI route
and catalog-owned reader.
- Count independent aggregates with both company boundaries and exact
signed-argument semantics. Keep archived counts at zero.
- Update both contextual sidebar variants. Keep the exact badge, 30
second interval and existing attention-prefix invalidation.
- Add PostgreSQL differential tests, authenticated HTTP tests, sidebar
tests, a repeatable benchmark and access documentation.

## Verification

- Baseline: `3f8ebfff8aa9febd0733c6f65b07485dcadfd0e2`, including both
merged evaluator fixes. Service treatment: `0827f7d0b3`; route/fixture
refinement: `230c42ee15`; final local existence gate: `390a218254`.
- Passed: focused PostgreSQL differential test; assembled HTTP test with
board session and agent JWT; 14 sidebar component tests; token gates;
map checks; diff check.
- Fixture: 200 profiles, 76,000 rules, 1,900 active tools, 695 removed
references, 12 agents, a sibling connection and 2,000 unrelated
historical invocations. Complete normalized two-count results are equal
on both paths.
- Quiet local measurement: seven alternating warm pairs after two warmup
pairs, Node 24.22.2, macOS, real disposable PostgreSQL. Legacy/new
service CPU median: 213.891/1.680 ms; ranges:
209.329–228.552/0.515–6.137 ms. Wall median: 156.867/1.248 ms; ranges:
151.739–162.289/1.115–1.386 ms. Queries: 7/1. Returned rows: 80,115/1.
- Run `cd server && node --expose-gc --import
./node_modules/tsx/dist/loader.mjs
scripts/benchmark-connection-review-counts.ts`. See
`doc/connection-review-counts.md` for scope and failure history.
Byte-volume accounting runs outside timing; no HTTP payload reduction is
claimed.
- Initial raw scalar SQL failed with ambiguous columns. Typed subqueries
fixed it and passed real PostgreSQL equality. Initial fixture agents
lacked permissions and correctly received 403. The final fixture grants
explicit authority and tests revocation. An initial benchmark included
byte serialization in timing; the reported run excludes it.
- Passed required local `pnpm -r typecheck` and `pnpm build`. The
initial sandbox typecheck could not open the tsx IPC socket; the
escalated rerun passed. Full monolithic `pnpm test:run` was not run
locally: repeating a 15–25 minute suite serially across both performance
PRs adds no distinct evidence. Broad suite acceptance uses final-head
CI, which runs the same general/serialized test partitions and the
native Runner lane. Final-head CI is in progress.
- Passed built-static Chromium journey on an owned local-trusted server
and disposable PostgreSQL: Review 3 appears, only scoped counts are
requested by this sidebar, resolving requests/clearing quarantines then
reloading removes the badge, and archived counts are zero. The first ad
hoc launcher omitted required decision-service options and failed at
startup; using the normal no-op wake callback fixed that harness.
- Greptile identified an invalid resolved-request fixture label.
Corrected `denied` to the declared `rejected`, then reran the real
PostgreSQL differential test. The count SQL and non-pending exclusion
are unchanged.
- The first final-head CI failed the static cross-tenant
existence-oracle guard because the new explicit resource company
assertion had no nearby `hasCompanyAccess` gate. Existing management
admission already rejects foreign resources with 404. The route now also
follows the standard local two-step gate. The unchanged guard plus
authenticated HTTP negative cases passed (3 tests). No allowlist or test
was weakened.
- These local service measurements do not prove hosted performance. No
deployment is included.

## Risks

Low risk. New read admission reuses existing current-authority checks.
The old attention API is unchanged. Counts still require database work
over relevant pending/catalog rows. No cache, schema migration or
telemetry is added. Roll back this PR if the badge differs or scoped
reads deny previously permitted users.

## Model Used

OpenAI `gpt-6.1-sol` (Sol 6.1), with reasoning, tool use and code
execution. The host does not expose the configured context-window size.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-02 17:12:22 -06:00

11 KiB
Raw Blame History

navigation
navigation
section sectionOrder order
Apps, channels, and extensions 50 20

App access and action permissions

After connecting an app, an operator or authorized agent decides which agents may use it and whether each action is off, requires approval, or is allowed. Activity and action tests help verify the resulting access.

Feature ID: app-permissions.

Implementation: app detail, permissions, activity.

Agent access: companion.

Sub-features

  • agent-access: app-permissions/agent-access — grant the connection to all or selected eligible agents.
  • action-policy: app-permissions/action-policy — set off/ask/allowed independently for available actions.
  • catalog-change: app-permissions/catalog-change — review newly discovered/quarantined actions before enabling them.
  • test-action: app-permissions/test-action — choose an actor and inputs and inspect a real action result.
  • account-maintenance: app-permissions/account-maintenance — inspect accounts, reconnect/revoke, and review connection activity.

Changing the connection's agent list preserves profile assignments for agents that remain installed. Saving the list must not restore a removed app-profile assignment. New installations retain the current app-profile defaults. Non-empty saves also remove legacy connection-wide include rules, including unchanged lists and removals with no new targets.

How to get to it (user POV)

permissions

Open an app at /apps/:connectionId/permissions; this is the default connected-app view.

test-and-maintain

Use the action test control in Permissions, then the connection’s Activity and available account/advanced controls.

delegated-permissions

Ask an agent to change access or action policy for a named connection through the existing APIs. The responsible person's current authority, the agent's management grants, resource scope and all affected targets bound the request. An explicitly requested, authorized policy change may target that same agent.

Driving it

Preconditions: follow the baseline. Prepare a connection to a fixture or test provider, two agents, and a harmless action. Record provider and credential ownership.

permissions

The page loads saved profiles for the displayed connection through the configuration read. It preserves complete app bindings for saves and the install fallback when bindings are empty. It does not request profile summaries or pending-review counts.

Automated: app detail covers agent/action controls, quarantined actions, and navigation using mocked requests.

Installation permission regressions exercise the real install API with a local operator and PostgreSQL. They check that adding, removing, or retaining agents preserves removed app-profile grants and denies write tools for agents with read-only profiles. They also check company installations, new-agent defaults, legacy connection-wide include cleanup, and access-extension audit records. These fixtures do not qualify an agent-authenticated request or a live provider.

Runtime assignment tests use PostgreSQL to verify committed assignment notifications, bounded snapshots, rollback, and concurrent AI adoption. The owning lifecycle plugin can read standing assignments through ctx.agents.getRuntime; this does not grant tool execution access or replace task-specific authorization.

Tool-definition runtime tests cover catalog invalidation, scoped policy/profile-entry recipients, ignored bookkeeping, profile-entry edits concurrent with setup finalization, and profile replacement concurrent with application deletion using PostgreSQL. They do not qualify live provider access.

Connector access runtime tests cover grants, delegation, membership cleanup, referenced vault changes, and agent-scoped secret bindings. OAuth and Railway tests exercise the corresponding credential lock order. Slack search and membership cleanup tests cover lock contention and complete transaction retries. Provider calls use fixtures; no live credentials are qualified by this coverage.

Manual: Grant one agent access, leave another excluded, and exercise off, ask, and allowed states for a harmless action. Reload between changes. Verify actual denied calls stay denied and ask-first creates a decision rather than executing early.

For Braintrust, distinguish reviewed action risk from effective policy: S4 methods retain the shared Allowed defaults. Explicit Off and Ask-first choices must survive refresh and reconnect. Exercise the read, write, and destructive classifications with a fixture, including billable tests classified as writes and dataset row edits/automation activation classified as destructive. After initial discovery, newly added or changed tools remain quarantined until reviewed. Use synthetic data for provider calls; see Braintrust. Live US OAuth proof covers bounded reads, a synthetic dataset insert/readback through actual process-agent gateway calls, and denial after local removal. Off, Ask-first, and changed-tool denial use fixture evidence; the other three methods still need live account qualification.

test-and-maintain

Automated: app detail tests cover test-dialog/UI contracts. Real provider execution and revocation require the selected provider’s live check.

AI account details share the Permissions page's grants query. Account and grants reads have a 15-second deadline. The AI panel retries a transient failure once, then shows an error with Retry. A background failure keeps loaded controls visible. AI account loading tests exercise stalled account/grants reads, cancellation, retry recovery and cached grants.

The account-details section shows the provider email and organization when available, beside the sign-in method and Paperclip ownership. Anthropic profile permission denial shows an explicit unavailable message without changing the connection's connected state. Account identity loads independently of maintenance controls, has a bounded wait and a separate retry for temporary failures, and is refreshed after a credential grant changes. Account-details tests cover these states and prevent one grant's email from appearing for another.

Manual: Run a read-only action with explicit inputs and inspect returned data and activity. Refresh discovery and review new actions. Revoke or disconnect only the disposable credential, then verify subsequent use fails with an actionable reconnect state.

For Daytona, opening an older saved catalog discovers the current native contract. Removed diagnostic actions disappear from Permissions and agent discovery; new or changed actions require review. Existing Off/Allowed choices remain in place. Daytona regressions cover shared/personal catalog upgrades, failed refresh, permission preservation, run-bound file transfers, and revocation with fixture provider responses.

Unikraft actions follow the same review rules: deletion, stop/suspend/detach and sandbox command execution are destructive, and catalog updates quarantine new or changed actions. Unikraft regressions cover classification, Off policies, revocation and stale contracts with fixture provider responses.

delegated-permissions

Automated: production-authenticated integration covers actual key/JWT policy edits, retained targets, caller/target test policies, human approval, scope and revocation using PostgreSQL and provider fixtures.

Manual: Request a bounded access or policy change through an actual agent, reload Permissions, and verify the saved audience, action choice and agent audit actor. Include an explicitly requested self-targeted policy edit. Repeat with missing management grants, an excluded target/resource, and Ask or Plan mode; writes must be denied. A denied tool call must not cause an unsolicited policy relaxation. An Ask-first action still requires the authorized human's decision; the agent cannot approve its own governed action or edit its own principal permissions. Verify caller and target policies both apply when testing as another agent.

Gotchas

  • Connected does not imply an agent has access or an action is allowed.
  • Credential-only connections may replace tool-action controls with account-specific controls.
  • See connection setup for auth and questions and approvals for pending action review.

Storybook verification

catalog-change

Capability ID: app-permissions/catalog-change.

Scope: ui Storybook: Mount PermissionsPanel/AppDetail with quarantined actions, selection/review, refresh pending, empty and failed discovery states. Existing auto-allowed NewToolsAllowed examples are not quarantine review. Production: Exercise catalog refresh and new-tool review with permitted/denied actors; unreviewed actions stay quarantined, prior Off/Ask choices survive refresh, and approved review persists. Sources: Source: PermissionsPanel.tsx, AppDetail.tsx, tool-access.ts. Tests: AppDetail.test.tsx, tool-access-service.test.ts, tool-access-policy-service.test.ts

delegated-configuration

See the canonical delegated configuration guidance and acceptance evidence.

The contextual connection sidebar reads only the selected connection's pending signed action request and quarantined action counts. Its Review badge is the pending request count plus one when any action is quarantined. It refreshes every 30 seconds and after existing attention invalidations. Archived connections show no badge. The full Apps attention view keeps its existing company-wide read.