Permission Catalog
This page is the canonical catalog of Reply Pilot application permissions.
The permission code is the string used by backend and Flask authorization
checks. Keep this page in sync with public.app_permission,
AppPermissionCodes, backend authorization checks, Flask permission gates, and
seeded role grants.
Permission List
| Code | Definition | Scope and important behavior |
|---|---|---|
company.view_assigned |
User can view companies assigned to them. | Assigned company means party_organization.default_email_sales_user_id = app_user.id. Companies without an assigned default email sales user are not visible through this permission alone. Backend read-model company and exact-task reads also include the company linked to any active Jira task assigned to the current local user or Jira-account profile. This task-derived exception is read-only; company collaboration remains limited to active assigned Reply Pilot Task items. Normalized Done tasks never delegate access. |
company.view_all |
User can discover all companies. Sales users receive only a summary for unrelated companies. | Bypasses assigned-company discovery filters. For sales, full detail and task access remain subject to the existing relationship scope; for other roles it includes task search. |
company.write |
User can perform global company write operations. | Also allows own Pippa scouting conversations and shared candidate decisions with reasons. Accepted for creating companies (Pippa can select another owner with global full-detail access), creating company-linked Reply Pilot Task issues, and existing-company and linked-task mutations, where backend still checks company visibility. |
company.write_assigned |
User can create self-assigned companies and write existing companies and linked tasks within their company visibility scope. | Also allows own Pippa scouting conversations and shared public candidate decisions with reasons. New companies default to the current user as default_email_sales_user_id; Pippa onboarding permits another owner only with global full-detail access. For sales users (including those with company.view_all) and other users without company.view_all, existing writes remain limited to companies where the user is the default email communication owner; this also covers creating company-linked Reply Pilot Task issues for visible assigned companies. |
company.ai_assist |
Legacy compatibility permission; no longer used as an access gate. | Existing seeded assignments remain. Full company-profile access grants the company scope for Pippa; a valid personal Pippa token and mcp.access are additionally required. Assigning this legacy permission alone grants nothing. |
mcp.access |
User can manage their own personal MCP tokens and authenticate MCP clients. | Required for both Pippa and other tokens, including automatic Pippa-token creation on page entry. Every request also checks current company/task permissions. Does not grant company visibility or writes. Token management is blocked during impersonation. |
company.merge |
User can merge duplicate companies. | Merge endpoints still require access to the affected company records. |
email.inbox.view |
User can view the dedicated Inbox screen. | Required by /inbox and its inbox-specific backend list, detail, and attachment endpoints. It does not restrict /emails, replies, or forwarding. |
email.inbox.review |
User can mark an unresolved email thread as manually reviewed. | Required together with email.inbox.view for the durable per-thread review action in the unassigned-email queue. It does not grant Inbox read access or permission to mutate companies, people, or contacts by itself. |
security.manage_roles |
User can manage role assignments. | Required for listing all roles, reading another user's assigned role IDs, and replacing user roles. Backend prevents removing the last role manager. |
security.impersonate_user |
User can start a controlled impersonation session for another user. | Kept separate from admin. Cannot impersonate self, users with security.manage_roles, or users with security.impersonate_user. Starts are recorded in app_user_impersonation_audit. |
task.email_thread_reply.unlinked |
User can view and edit an Email Thread Reply task whose thread currently resolves to no company. | Does not grant company access. If canonical thread resolution later adds a company, normal company visibility and write rules apply immediately. Seeded only to sales_int. |
task.supplier_onboarding.ask_question |
User can ask a merchant a question from Supplier Onboarding. | Required by the backend mutation that adds a comment, assigns the task to the selected default merchant, and moves a Rollout or In Development task to Waiting for Info. The task must still be editable by the current user. |
user.manage_skills |
User can manage application-user skill assignments. | Required for reading another user's skills and replacing user skill assignments. The authenticated skill catalog and the current user's own assignments remain readable for profile display. Skills control business-work eligibility and do not grant application access. |
Delegated task read exception: backend read-model company endpoints may also
expose the company linked to any active Jira task when the current app user is
its local assignee or the cached Jira assignee matches the user's Jira profile.
This includes the company's notes and activities. Exact-task reads use the same
active-assignee rule for all Jira work types. The assignee may also trigger
POST /api/tasks/{taskId}/refresh-from-jira as read-side cache synchronization;
the backend always uses the Jira key stored on that task and ignores any client
supplied key. Delegation does not expose unrelated companies or unrelated tasks
at the same company and excludes unassigned or normalized Done tasks.
Delegated company collaboration remains narrower than delegated company read:
only an active assigned Reply Pilot Task
allows its assignee to add company notes, create/update/remove company contact
methods, create a new person linked to the company, and remove a person-company
relationship. A related person's profile, notes, and contacts may be edited only
when the user can collaborate with every active, unmerged company linked through
CONTACT_FOR; a standalone person requires global company.write. Delegation
does not allow company fields, visibility, identifiers, requirements, ownership,
merge, task creation, task-company relinking, or email-thread relinking.
Delegated task mutation exception: assignment to any active Jira task permits
the existing operations for that exact task type, including Supplier Onboarding
edit/reply, Email Reply edit/status/reassignment/send/forward, and Reply Pilot
resolve/reassignment. Reply Pilot close (Dokončit) is available to every
authenticated user who can read that exact task, including users without company
write permission. It does not require being the requester or assignee, nor a
stored requester. The close endpoint enforces the same task read scope as the
detail; it does not grant access to otherwise hidden tasks. Jira keys and
work types are loaded and validated server-side; legacy client jira_key fields
are ignored. An Email Reply task has no independent company link and cannot be
relinked; its companies always follow canonical thread resolution. Reassignment
or a transition to normalized Done immediately removes assignment-derived
access. The unlinked Email Reply permission and Supplier Onboarding question
permission remain separate grants.
An authorized Email Thread Reply transition from Reply Pilot to Waiting for
Reply or Done also attempts to mark the linked conversation read in every
connected mailbox containing the same RFC Message-IDs. Personal mailboxes must
have sync_inbox enabled; this side effect does not grant the acting user read
access to another mailbox or expose its contents. Gmail modification still
requires that mailbox's Google OAuth grant with gmail.modify. Direct Jira
changes and cache refreshes do not trigger this operation.
Email replies (RP-8805) use the selected original incoming message's TO/CC/BCC
recipients to choose the sender. If velkoobchody@internet-handel.cz appears,
only that shared sender is allowed. Otherwise, an imported personal message may
be answered only by its source mailbox owner, with sync_inbox enabled, a valid
Google send grant, and edit access to the linked task. Company or task access
does not grant permission to send from another user's mailbox. The backend checks
these conditions again for both drafts and sends; changing outgoing recipients
or submitted sender/account fields cannot change mailbox identity. Shared replies
retain company visibility and linked-task edit checks. New emails and forwards
remain shared-only.
New emails from the composer are allowed for sales only when every recipient
resolves to a company the current user can write. The backend resolves each address
from stored company/person contacts or registered company domains and checks current
company-write scope before calling Gmail. Global visibility, a delegated read/task
assignment, or a submitted company/task ID does not bypass that check. Unknown
recipients and recipients belonging to an inaccessible company are rejected before
delivery. The existing non-sales sending behavior is unchanged.
After sending, sales may create the Email Thread Reply task only for an imported
shared Gmail thread with a canonical link to a writable company. This reuses the
existing email-task company scope; personal-mailbox threads and threads without a
writable company do not qualify. Authorization is checked again before creating or
returning an existing task. If task creation fails after delivery, the composer
explicitly reports that the email was sent and must not be resent. No new permission
or seeded role grant is introduced.
Seeded System Roles
| Role | Permissions | Notes |
|---|---|---|
admin |
company.view_assigned, company.view_all, company.write, company.write_assigned, company.merge, email.inbox.view, email.inbox.review, security.manage_roles, task.supplier_onboarding.ask_question, user.manage_skills, mcp.access |
Normal administration of company-linked records. Unlinked Email Thread Reply tasks are reserved for sales_int; security.impersonate_user is also intentionally not included. |
sales |
company.view_assigned, company.view_all, company.write_assigned, company.ai_assist, mcp.access |
Can discover all available companies and read their basic Summary. Full detail requires ownership or an active assigned task. Company writes remain limited to assigned companies; existing task-derived collaboration rules apply. |
sales_int (Obchodník int) |
company.view_assigned, company.view_all, company.write, company.write_assigned, company.merge, email.inbox.view, email.inbox.review, task.email_thread_reply.unlinked, company.ai_assist, mcp.access |
Can view and mutate all companies, merge companies, open Inbox, close unresolved threads as reviewed, and handle Email Thread Reply tasks whose thread resolves to no company. Existing sales_manager assignments are migrated to this role. |
loader (Zavaděč) |
company.view_all, company.write, task.supplier_onboarding.ask_question, mcp.access |
Can view and mutate all companies and ask the merchant a question from an assigned Supplier Onboarding task. Cannot merge companies or manage roles. |
support_impersonator |
security.impersonate_user, user.manage_skills |
Dedicated support role for impersonation and user-skill administration. |
Sales company access (RP-5471)
Migration 0088 grants company.view_all to sales. An active sales role
applies the following limits even when the user also holds other roles:
- Search and the company directory can find all available companies. Unrelated companies expose only the existing basic Summary, including CME contacts. Private notes, local contacts, relationships, requirements, email history, tasks and attachments are not exposed through global company visibility.
- Full company detail requires the user to be the assigned salesperson
(
default_email_sales_user_id) or the assignee of an active task linked to that company. This reuses the existing local/Jira-account assignment and normalizedDoneexclusion, including canonical email-task company links. company.view_alldoes not expand sales write rights. Assigned-company writes, exact-task edits and task-derived collaboration retain their existing checks. An active generic Reply Pilot Task allows company collaboration; other active assigned task types grant company read access only.- Tasks of all types remain subject to the existing task scope: owning the company or assignment to that exact active task. Assignment to one task does not expose other tasks belonging to somebody else's company.
- Direct activity, email-thread and attachment reads require a related company. Global inbox, unscoped email operations and the meeting wizard remain blocked. New-email sends and follow-up reply-task creation have the company-scoped exceptions described above; outbound Gmail drafts and unscoped forwarding remain blocked. Sales login still opens My Tasks.
- Search receives
sales_company_access: companies use global visibility, while tasks use assigned scope before pagination. The backend rechecks each result and removes private company snippets for unrelated companies. Release backend and search together before applying the role grant.
Other roles retain their existing behavior. The sales limitations above take precedence over the general permission descriptions in this catalog.
Maintenance Rules
- When adding, renaming, deleting, or changing the meaning of a permission, update this page in the same change.
- When changing seeded role grants, update the role table above in the same change.
- When changing authorization behavior without changing the database grant, update the scope and important behavior column.
- Keep permission labels and descriptions aligned with Liquibase seed data.
Personal MCP tokens
Migration 0092 grants mcp.access to the four normal application roles above.
Users can manage only their own credentials through Profile → Tokens, including
reveal, copy and revoke. Neither administrators nor impersonation sessions receive
access to another user's token originals. Removing mcp.access immediately blocks
subsequent MCP requests and token management; it does not cancel a running call.
Token type (pippa/other) changes selection by Pippa, not authorization.
All MCP tools resolve the actor from the bearer hash and reload existing resource
permissions. The old actor-delegation headers are no longer accepted. See MCP.
Pippa company conversations (RP-8820)
Full access to a company profile defines the company scope for Pippa:
conversation lists, history, summaries, context, questions and retries, including
all company-related notes, activities, imported email threads, tasks and documents.
No separate company.ai_assist grant or per-source application permission check
is required. This includes full company access derived from an assigned task.
Company Summary access alone is insufficient. Opening Pippa, reading ordinary chat
history, submitting questions and retries also require mcp.access and an active,
decryptable personal Pippa token. This applies to company, onboarding and scouting
screens. During impersonation the token must belong to the effective user; the real
administrator's token cannot substitute for it. Admin history retains its separate
admin gate. On page entry, an authorized user without an active Pippa token gets
one automatically (name Pippa, no expiration), including during impersonation.
This exception does not expose the effective user's credentials to the impersonator;
manual management stays blocked. Concurrent opens reuse one token. Polling and
background work only validate tokens. Revoked/expired tokens stay invalid; reopening
Pippa can create a replacement, so remove mcp.access to prevent further use. The backend checks company access
on every request and before and after background generation; conversation and
question IDs remain scoped to their company and conversation.
Stored history inherits the conversation's company access, including saved source snapshots; it is not hidden when an individual source is removed or its separate permissions change. New context is assembled only from the selected company's related records. AI source IDs are resolved only within that server-built manifest. Lists do not load turns, inspect source records, or contact Jira/Google. Live file retrieval still uses the existing Google/Jira integrations, with the acting user's Jira OAuth grant and task/attachment identity validation. Missing provider access makes a live file unavailable; it does not hide saved history.
This rule is specific to Pippa. Direct source screens retain their existing
permissions. Pippa chat read access alone grants no mailbox send rights. Its company/contact write tools
require the existing company collaboration permission in addition to full profile
access, exactly like the corresponding forms. A read grant alone does not enable
writes; no new permission or role is introduced.
list_assignment_users requires company-write permission. reassign_company
requires full company access and current company-write scope, like the existing
reassignment form; task-derived contact collaboration alone is insufficient.
It changes the default email sales user and all open company tickets, preserving
meeting-assignee skill checks and closed tickets. Jira writes use the authenticated
actor, not the new assignee. In Pippa onboarding, selecting another default email
sales user additionally requires global full-detail company access (company.view_all
without the sales-role summary restriction). Users limited to assigned companies
continue to create for themselves. No permission codes or role grants are added.
add_company_note requires the same company collaboration rights and full company
access as the note form. It records the authenticated token owner as author;
neither a model nor an external MCP client can supply a different author. It
creates only a Reply Pilot company note and needs no Jira OAuth grant. The same
tool is available after company creation in onboarding, with that conversation's
existing author and company-write gates. Scouting cannot create company notes.
send_email requires full company-profile access plus current company-write scope
(company.write or applicable company.write_assigned), even when retrieving a stored
send result. Every recipient must resolve to that company. An existing communication
ticket must belong to the company, be editable by the actor, and include the selected
source thread. Company collaboration delegated by a task alone does not grant sending.
Only the fixed shared mailbox is used; no personal mailbox/OAuth grant is exposed.
The actor's profile signature is used. For a new communication ticket, the actor's valid
Jira OAuth account is both creator and explicit Reporter. A missing/invalid grant or
inactive Jira account allows a narrowly scoped fallback to the configured application
Jira account for creation, description update and the Waiting for Reply transition.
The backend validates the chosen identity before Gmail and logs fallback at WARN with
the requesting user ID, created Jira key/ID and reason. Permission errors or provider
outages do not enable fallback. Existing tickets keep their Reporter and require the
actor's grant; Jira attachments and live onboarding checks retain their delegated-access
requirements. Application company/task permissions are always checked as the requesting
user. A clear user send
request authorizes the email and its communication-ticket update, never a mere draft
request. Attachment references are restricted to the current company manifest and the
existing Google/Jira retrieval checks. list_email_attachments requires full company
read access only. See MCP email for retry and result rules.
The close_company_opportunity tool instead requires current company-write scope
(company.write or applicable company.write_assigned) and task access for the
company's tickets. Task-derived contact collaboration alone does not grant closure.
get_company_closure requires full company-profile and scoped task-read access.
Live Jira reads, result updates, comments and transitions use the acting user's
Jira OAuth grant; no service credential bypasses the user's provider permissions.
Migration 0089's company.ai_assist permission and seeded role assignments remain
for compatibility but no longer control Pippa access.
The personal /pippa/conversations page and GET /api/pippa/conversations require
authentication and list only conversations created by the acting user (including
when impersonating). The shared mcp.access and personal Pippa token gate applies. Existing full company-read
access still applies to company-linked conversations; standalone onboarding and
scouting require company.write or company.write_assigned. The personal list
does not grant administrator access or expose another author's conversations.
The dedicated /pippa/companies onboarding workflow uses company.write or
company.write_assigned to start a company (self-assigned by default; selecting
another owner requires the global full-detail access described above). Before creation only
the conversation's author can access it. After creation full company access
grants read access; follow-up questions, draft changes and approvals additionally
require the original author and current company-write scope. No new permission
or role is seeded. Duplicate discovery exposes profile details only with full
company access; a hidden/summary-only duplicate reveals no record identifiers.
Explicit onboarding approval includes creating the Supplier Onboarding ticket through the author's delegated Jira grant. A separate email approval permits one first-contact email through the existing shared sender and its automatic communication-ticket workflow. This company-scoped action requires the recipient to resolve to the newly created company and does not grant general mailbox or unscoped compose access. Before company creation AI research has no mutation tools. After creation the author can explicitly request opportunity closure or add company notes or contacts, create linked people and update their company-specific roles through the same scoped tools as ordinary company chat. These note/contact tools retain company collaboration checks; the onboarding conversation additionally retains its author and company-write gates. The tools remain available after email completion. Approving the initial company form already includes saving its selected contacts; no separate chat request or new permission is needed for that step. A closed onboarding ticket also blocks approved initial email sending. The backend stores the send claim before the external call; uncertainty blocks resend. Onboarding audit conversations cannot be deleted through ordinary Pippa deletion.
Deleting a Pippa conversation requires the active admin role (Administrator)
and full access to its company. This is a role check, not a grant implied by
company write access, role-management permission, or the conversation's author.
The list exposes can_delete for that user; DELETE /api/companies/{partyId}/pippa/{conversationId}
checks the role again and scopes deletion to the company. Deletion permanently
removes the conversation and all its turns, saved answers and source snapshots.
The separate /admin/pippa page and /api/admin/pippa API grant an active admin
global access to list, read and individually delete all Pippa history, including
another author's standalone onboarding conversations. Company scope and author-only
workflow rules do not restrict this administrative history view; they still apply
to ordinary screens and workflow actions. It provides no question/retry/send/approval
actions. The role is checked on every backend request and for the frontend link
and routes, using the effective user during impersonation. security.manage_roles
or company write permissions alone do not grant access. No new permission or role
is seeded. Deletion uses CSRF protection and a confirmation dialog and is rejected
while a question or onboarding side effect is in progress. Company, Jira and email
records are retained when their conversation history is deleted.
Pippa scouting
/pippa/scouting and /api/pippa/scouting use company.write or
company.write_assigned. These grants now also allow public-source candidate
research and shared candidate decisions. No new permission or seeded role is added.
The author alone can read, continue, retry and change a scouting conversation;
admin history access remains a separate read/delete path. Every tool reloads
current grants, and generation checks access before and after the provider call.
Candidate data is shared public research. An actor may act on a candidate only through their own scouting conversation; permanent rejection/reopening is shared across searches and authors and requires a reason. Search-only skip/restore does not alter global eligibility. Candidate onboarding requires the same author and company-write rules as ordinary onboarding. A second author cannot take over an existing standalone onboarding conversation. Company search retains existing visibility filtering; exact hidden-company matches reveal only a blocking notice. Admin deletion removes chat history and search membership, retaining canonical candidates, permanent decisions and company associations.