Feature Specification: Explicit Team-Scoped Search Capability
Feature Branch: 2026-06-03-explicit-search-capability (spec authored on current working branch)
Created: 2026-06-03
Status: Draft
Input: User observation: "two problems with mcp tools — when I stopped sharing caipe_kb with a team the Generic user was still able to see and search the default and caipe_kb, that would be a violation. The search itself should be enabled and disabled like ingestor enabled/disable."
Context & Problem
The Knowledge Base Search feature (the built-in search / fetch_document MCP tools, custom search tools like caipe_kb, and the /v1/query + /v1/mcp/invoke data path) has no explicit capability gate. Anyone who clears the coarse rag route gate can reach search; the only protection is the per-datasource result filter (constrainSearchBody → readable data_source ids). Two concrete problems follow:
- No off switch. There is no single, auditable capability that says "this principal may use search." Search is implicitly on for every user with RAG access. Built-in
search/fetch_documenthave nomcp_toolobject at all, so themcp_tool#can_callgate never applies to them. - Tool-share revocation does not fully revoke search. A custom search tool (e.g.
caipe_kb) shared org-wide writesorganization:<key>#member caller, grantingcan_callto every org member. Un-sharing the tool from one team leaves that org-wide grant in place, so users retain access — perceived (correctly) as an authorization violation. Even absent the org-wide grant, a user could still call the un-gated built-insearch.
This mirrors the gap the explicit data source author capability (organization#can_ingest, spec 2026-06-03-explicit-ingest-capability) closed for creating data sources. Search needs the same treatment: an explicit, team-granted, admin-controlled capability.
Goal
Introduce a dedicated, explicit, auditable capability — organization-level can_search, granted to teams only and only by org admins (explicit opt-in, no backfill) — that:
- Gates the Search sidebar tab via a single explicit capability check (no datasource enumeration).
- Gates the data path (
/v1/queryand/v1/mcp/invoke) at both the BFF and the RAG server, covering built-in search tools AND custom search tools (e.g.caipe_kb), so revoking the capability turns search off regardless of any per-tool share. - Leaves the per-datasource result ACL (
data_source#can_read/constrainSearchBody) and the per-toolmcp_tool#can_callgate intact as additional, narrower checks layered on top.
Per-tool mcp_tool#can_call continues to mean "may use this specific tool"; can_search means "may use the search feature at all."
Default Posture (decided)
Opt-in, mirroring can_ingest exactly: can_search = searcher or admin, with searcher directly assignable to team#member / team#admin only. On day one, non-admins have no search access until an org admin opts their team in. Org admins retain implicit can_search (subject to the RAG_ADMIN_BYPASS_DISABLED kill switch). No auto-backfill — this is a deliberate behavior change so the prior over-broad default is closed.
User Scenarios & Testing (mandatory)
User Story 1 - Org admin grants a team the search capability (Priority: P1)
An org admin opens a team in the Admin dashboard and toggles an explicit "Allow this team to search knowledge bases" control (sibling to the data-source-author toggle). Members of that team can now see and use Search; teams without the toggle cannot.
Why this priority: Core of the request — make search an explicit, admin-controlled on/off capability.
Independent Test: As org admin, enable the toggle for Team A and leave it off for Team B. A member of Team A sees the Search tab and can run a query; a member of only Team B sees a disabled tab and is denied at the data path.
Acceptance Scenarios:
- Given a team with no search capability, When any member opens Knowledge Bases, Then the Search tab is hidden/disabled and
can_searchis false. - Given an org admin enables the search toggle for a team, When a member reloads, Then the Search tab appears and
can_searchis true. - Given a non-org-admin (including a team admin), When they attempt to toggle the capability, Then the request is rejected (403).
- Given a user who can
can_calla specific shared tool but whose teams lackcan_search, When they invoke that tool, Then the data path denies the call (403) —can_callalone does not grant search.
User Story 2 - Search data path enforces the capability (Priority: P1)
A member of a search-enabled team runs a query or invokes a search tool. The BFF and RAG server both confirm organization#can_search before executing; results are still filtered to the caller's readable datasources.
Why this priority: Without server-side enforcement, the capability is only cosmetic and the violation persists for direct API/agent callers.
Independent Test: With the capability on, a Team A member's /v1/query and /v1/mcp/invoke (built-in search and custom caipe_kb) succeed and are datasource-filtered. With it off, both return 403 regardless of tool shares.
Acceptance Scenarios:
- Given a caller without
can_search, When they POST/v1/query, Then the request is denied (403) before any retrieval. - Given a caller without
can_search, When they POST/v1/mcp/invokefor the built-insearchtool, Then the request is denied (403). - Given a caller without
can_searchbut withcan_callon a custom tool (org-wide or team share), When they invoke it, Then the request is still denied (403). - Given a caller with
can_search, When they query, Then results are limited to datasources they can read (existing ACL unchanged). - Given an agent principal invoking search on a user's behalf, When the effective principal lacks
can_search, Then the call is denied. (Agent search authorization follows the same capability; see Assumptions.)
User Story 3 - Revoking the capability removes search (Priority: P2)
An org admin disables the search toggle for a team. Members lose the Search tab and are denied at the data path; data ownership and per-tool shares are untouched.
Why this priority: Capability lifecycle must be reversible without destroying tool ownership or datasource grants.
Independent Test: Disable the toggle for Team A; a member can no longer search, but existing tool ownership/shares and datasource grants remain.
Acceptance Scenarios:
- Given a search-enabled team, When the org admin disables the toggle, Then the capability tuple is removed and members'
can_searchbecomes false on reload. - Given the capability is revoked, When members access tools/datasources, Then existing
mcp_toolshares anddata_sourcegrants are unaffected (they simply cannot search until re-granted).
Edge Cases
can_callwithoutcan_search(the reported violation): a tool shared org-wide grantscan_call, but the data path additionally requirescan_search; lacking it, the invoke is denied. This is the primary regression test.- Built-in
search/fetch_document(nomcp_toolobject): now gated bycan_searchat the data path even though they have no per-tool object. - Org-admin kill switch (
RAG_ADMIN_BYPASS_DISABLED=true): org-admin implicit search is disabled along with other bypasses; admins must then be members of a search-enabled team. - OpenFGA unavailable: gate fails closed (Search tab hidden); server/BFF data path fails closed (deny), never fail-open.
- Stale session: a user granted the capability while logged in must refresh/re-login (gates re-fetch) before the tab appears.
- Graph tools (
graph_*): out of scope for this iteration unless they route through/v1/queryor/v1/mcp/invoke; if they do, they are gated identically.
Requirements (mandatory)
Functional Requirements
- FR-001: The OpenFGA model MUST define an organization-level search capability:
organization#searcher(directly assignable toteam#memberandteam#adminonly) andorganization#can_search = searcher or admin. - FR-002: Granting/revoking the capability MUST be restricted to org admins and MUST write/delete the tuple
team:<slug>#member → searcher → organization:<key>. - FR-003: The Admin Team dialog MUST expose an explicit, clearly-labeled toggle to enable/disable the search capability for a team, reflecting current state and separate from the data-source-author and per-KB assignment controls.
- FR-004: The Search tab gate (
/api/rbac/kb-tab-gates) MUST be computed from an explicitcan_searchcheck onorganization:<key>. - FR-005: The RAG server MUST enforce
organization#can_searchon/v1/queryand/v1/mcp/invokefor both built-in and custom tools, before retrieval, in addition to existing per-datasource and per-tool checks. - FR-006: The BFF RAG proxy MUST enforce
organization#can_searchon the same paths before forwarding (defense in depth + early 403), with org-admin bypass under the existing convention. - FR-007:
mcp_tool#can_callMUST remain a narrower per-tool check; holdingcan_callMUST NOT, by itself, permit search whencan_searchis absent. - FR-008: The system MUST NOT auto-backfill the new capability; teams gain it only by explicit admin opt-in (deliberate behavior change).
- FR-009: All authorization paths MUST remain fail-closed on OpenFGA error (deny / hide), never fail-open.
- FR-010: A new endpoint MAY return per-team capability state for the admin toggle (GET), mirroring the ingest-capability route.
- FR-011: The stale org-wide grant on
mcp_tool:caipe_kb(organization:<key>#member reader/user/caller) MUST be removed (one-time remediation) and the tool re-scoped to its owning team. (Completed during diagnosis; recorded here for traceability.) - FR-012: The RBAC reference documentation MUST be updated (per the repository's RBAC living-documentation rule) to record the new capability, grant flow, gate, data-path enforcement, and new files.
Key Entities
- Organization search capability:
organization#searcher/organization#can_search— the explicit "may use search" capability, team-granted. - Search-enabled team set: teams a user is a member of that hold the search capability; drives the Search tab gate and data-path authorization.
- Data path:
/v1/queryand/v1/mcp/invoke(built-in + custom tools) — the enforcement points forcan_search. - Layered checks:
can_search(feature) ⊃mcp_tool#can_call(per-tool) ⊃data_source#can_read(per-datasource result filter).
Success Criteria (mandatory)
Measurable Outcomes
- SC-001: With the toggle off, 0 members of a team (with any tool shares, including org-wide) can search via UI, direct
/v1/query, or/v1/mcp/invoke. - SC-002: With the toggle on, 100% of that team's members see the Search tab and can run a datasource-filtered query after a gate refresh.
- SC-003: A caller holding only
mcp_tool#can_call(nocan_search) is denied at the data path in 100% of tested built-in and custom-tool cases — closing the reported violation. - SC-004: The
kb-tab-gatessearch decision performs a single capability check with no/v1/datasourcesenumeration for the search gate. - SC-005: Revoking the capability hides/disables the tab on reload and denies the data path while leaving
mcp_toolshares anddata_sourcegrants unchanged in 100% of tested cases.
Assumptions
- Teams are the only grantee for the search capability in this iteration (no direct user grants).
- The organization object key is the existing singleton resolved by
organizationObjectId(). - Agent-initiated search is authorized by the same
can_searchcapability resolved for the effective principal; if agents require a distinct posture, it is tracked separately. - The org-admin super-grant and
RAG_ADMIN_BYPASS_DISABLEDkill switch continue to apply. - Existing per-datasource ACL (
constrainSearchBody) and per-toolcan_callgate semantics remain unchanged and continue to apply after thecan_searchcheck.
Out of Scope
- Auto-backfilling the capability for existing users/teams (explicitly rejected — this is the intended behavior change).
- Direct (per-user) search grants.
- Re-architecting built-in tools to have
mcp_toolobjects. - Changing retrieval/ranking or the search runtime.
- Graph-only tool paths that do not traverse
/v1/queryor/v1/mcp/invoke.