Add DEPLOYMENT-MODES.md with canonical mode taxonomy. Update CLI.md, DEVELOPING.md, PRODUCT.md, and SPEC-implementation.md with local_trusted/ authenticated nomenclature. Revise humans-and-permissions plan with Better Auth choice, bootstrap flow, unified invite semantics, and expanded criteria. Add implementation guide and additional plan documents for cursor cloud adapter and deployment auth mode consolidation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
18 KiB
18 KiB
Humans and Permissions Plan
Status: Draft Date: 2026-02-21 Owner: Server + UI + Shared + DB
Goal
Add first-class human users and permissions while preserving two deployment modes:
- local trusted single-user mode with no login friction
- cloud-hosted multi-user mode with mandatory authentication and authorization
Why this plan
Current V1 assumptions are centered on one board operator. We now need:
- multi-human collaboration with per-user permissions
- safe cloud deployment defaults (no accidental loginless production)
- local mode that still feels instant (
npx paperclip runand go) - agent-to-human task delegation, including a human inbox
- one user account with access to multiple companies in one deployment
- instance admins who can manage company access across the instance
- join approvals surfaced as actionable inbox alerts, not buried in admin-only pages
- a symmetric invite-and-approve onboarding path for both humans and agents
- one shared membership and permission model for both humans and agents
Product constraints
- Keep company scoping strict for every new table, endpoint, and permission check.
- Preserve existing control-plane invariants:
- single-assignee task model
- approval gates
- budget hard-stop behavior
- mutation activity logging
- Keep local mode easy and trusted, but prevent unsafe cloud posture.
Deployment modes
Mode A: local_trusted
Behavior:
- no login UI
- browser opens directly into board context
- embedded DB and local storage defaults remain
- a local implicit human actor exists for attribution
- local implicit actor has effective
instance_adminauthority for that instance - full invite/approval/permission settings flows remain available in local mode (including agent enrollment)
Guardrails:
- server binds to loopback by default
- fail startup if mode is
local_trustedwith non-loopback bind - UI shows a persistent "Local trusted mode" badge
Mode B: cloud_hosted
Behavior:
- login required for all human endpoints
- Better Auth for human auth
- initial auth method: email + password
- email verification is not required for initial release
- hosted DB and remote deployment supported
- multi-user sessions and role/permission enforcement
Guardrails:
- fail startup if auth provider/session config is missing
- fail startup if insecure auth bypass flag is set
- health payload includes mode and auth readiness
Authentication choice
- use Better Auth for human users
- start with email/password login only
- no email confirmation requirement in V1
- keep implementation structured so social/SSO providers can be added later without changing membership/permission semantics
Auth and actor model
Unify request actors into a single model:
user(authenticated human)agent(API key)local_board_implicit(local trusted mode only)
Rules:
- in
cloud_hosted, onlyuserandagentare valid actors - in
local_trusted, unauthenticated browser/API requests resolve tolocal_board_implicit local_board_implicitis authorized as an instance admin principal for local operations- all mutating actions continue writing
activity_logwith actor type/id
First admin bootstrap
Problem:
- new cloud deployments need a safe, explicit first human admin path
- app cannot assume a pre-existing admin account
local_trusteddoes not use bootstrap flow because implicit local instance admin already exists
Bootstrap flow:
- If no
instance_adminuser exists for the deployment, instance is inbootstrap_pendingstate. - CLI command
pnpm paperclip auth bootstrap-ceocreates a one-time CEO onboarding invite URL for that instance. pnpm paperclip onboardruns this bootstrap check and prints the invite URL automatically whenbootstrap_pending.- Visiting the app while
bootstrap_pendingshows a blocking setup page with the exact CLI command to run (pnpm paperclip onboard). - Accepting that CEO invite creates the first admin user and exits bootstrap mode.
Security rules:
- bootstrap invite is single-use, short-lived, and token-hash stored at rest
- only one active bootstrap invite at a time per instance (regeneration revokes prior token)
- bootstrap actions are audited in
activity_log
Data model additions
New tables
users
- identity record for human users (email-based)
- optional instance-level role field (or companion table) for admin rights
company_memberships
company_id,principal_type(user | agent),principal_id- status (
pending | active | suspended), role metadata - stores effective access state for both humans and agents
- many-to-many: one principal can belong to multiple companies
invites
company_id,invite_type(company_join | bootstrap_ceo), token hash, expires_at, invited_by, revoked_at, accepted_at- one-time share link (no pre-bound invite email)
allowed_join_types(human | agent | both) forcompany_joinlinks- optional defaults payload keyed by join type:
- human defaults: initial permissions/membership role
- agent defaults: proposed role/title/adapter defaults
principal_permission_grants
company_id,principal_type(user | agent),principal_id,permission_key- explicit grants such as
agents:create - includes scope payload for chain-of-command limits
- normalized table (not JSON blob) for auditable grant/revoke history
join_requests
invite_id,company_id,request_type(human | agent)status(pending_approval | approved | rejected)- common review metadata:
request_ipapproved_by_user_id,approved_at,rejected_by_user_id,rejected_at
- human request fields:
requesting_user_id,request_email_snapshot
- agent request fields:
agent_name,adapter_type,capabilities,created_agent_idnullable until approved
- each consumed invite creates exactly one join request record after join type is selected
issuesextension
- add
assignee_user_idnullable - preserve single-assignee invariant with XOR check:
- exactly zero or one of
assignee_agent_id/assignee_user_id
- exactly zero or one of
Compatibility
- existing
created_by_user_id/author_user_idfields remain and become fully active - agent API keys remain auth credentials; membership + grants remain authorization source
Permission model (initial set)
Principle:
- humans and agents use the same membership + grant evaluation engine
- permission checks resolve against
(company_id, principal_type, principal_id)for both actor types - this avoids separate authz codepaths and keeps behavior consistent
Role layers:
instance_admin: deployment-wide admin, can access/manage all companies and user-company access mappingcompany_member: company-scoped permissions only
Core grants:
agents:createusers:inviteusers:manage_permissionstasks:assigntasks:assign_scope(org-constrained delegation)joins:approve(approve/reject human and agent join requests)
Additional behavioral rules:
- instance admins can promote/demote instance admins and manage user access across companies
- board-level users can manage company grants inside companies they control
- non-admin principals can only act within explicit grants
- assignment checks apply to both agent and human assignees
Chain-of-command scope design
Initial approach:
- represent assignment scope as an allow rule over org hierarchy
- examples:
subtree:<agentId>(can assign into that manager subtree)exclude:<agentId>(cannot assign to protected roles, e.g., CEO)
Enforcement:
- resolve target assignee org position
- evaluate allow/deny scope rules before assignment mutation
- return
403for out-of-scope assignments
Invite and signup flow
- Authorized user creates one
company_joininvite share link with optional defaults + expiry. - System sends invite URL containing one-time token.
- Invite landing page presents two paths:
Join as humanorJoin as agent(subject toallowed_join_types). - Requester selects join path and submits required data.
- Submission consumes token and creates a
pending_approvaljoin request (no access yet). - Join request captures review metadata:
- human: authenticated email
- both: source IP
- agent: proposed agent metadata
- Company admin/instance admin reviews request and approves or rejects.
- On approval:
- human: activate
company_membershipand apply permission grants - agent: create agent record and enable API-key claim flow
- Link is one-time and cannot be reused.
- Inviter/admin can revoke invite before acceptance.
Security rules:
- store invite token hashed at rest
- one-time use token with short expiry
- all invite lifecycle events logged in
activity_log - pending users cannot read or mutate any company data until approved
Join approval inbox
- join requests generate inbox alerts for eligible approvers (
joins:approveor admin role) - alerts appear in both:
- global/company inbox feed
- dedicated pending-approvals UI
- each alert includes approve/reject actions inline (no context switch required)
- alert payload must include:
- requester email when
request_type=human - source IP
- request type (
human | agent)
- requester email when
Human inbox and agent-to-human delegation
Behavior:
- agents can assign tasks to humans when policy permits
- humans see assigned tasks in inbox view (including in local trusted mode)
- comment and status transitions follow same issue lifecycle guards
Agent join path (via unified invite link)
- Authorized user shares one
company_joininvite link (withallowed_join_typesincludingagent). - Agent operator opens link, chooses
Join as agent, and submits join payload (name/role/adapter metadata). - System creates
pending_approvalagent join request and captures source IP. - Approver sees alert in inbox and approves or rejects.
- On approval, server creates the agent record and mints a long-lived API key.
- API key is shown exactly once via secure claim flow with explicit "save now" instruction.
Long-lived token policy:
- default to long-lived revocable API keys (hash stored at rest)
- show plaintext key once only
- support immediate revoke/regenerate from admin UI
- optionally add expirations/rotation policy later without changing join flow
API additions (proposed):
GET /companies/:companyId/inbox(human actor scoped to self; includes task items + pending join approval alerts when authorized)POST /companies/:companyId/issues/:issueId/assign-userPOST /companies/:companyId/invitesGET /invites/:token(invite landing payload withallowed_join_types)POST /invites/:token/accept(body includesrequestType=human|agentand request metadata)POST /invites/:inviteId/revokeGET /companies/:companyId/join-requests?status=pending_approval&requestType=human|agentPOST /companies/:companyId/join-requests/:requestId/approvePOST /companies/:companyId/join-requests/:requestId/rejectPOST /join-requests/:requestId/claim-api-key(approved agent requests only)GET /companies/:companyId/members(returns both human and agent principals)PATCH /companies/:companyId/members/:memberId/permissionsPOST /admin/users/:userId/promote-instance-adminPOST /admin/users/:userId/demote-instance-adminPUT /admin/users/:userId/company-access(set accessible companies for a user)GET /admin/users/:userId/company-access
Local mode UX policy
- no login prompt or account setup required
- local implicit board user is auto-provisioned for audit attribution
- local operator can still use instance settings and company settings as effective instance admin
- invite, join approval, and permission-management UI is available in local mode
- agent onboarding is expected in local mode, including creating invite links and approving join requests
- public/untrusted network ingress is out of scope for V1 local mode
Cloud agents in this model
- cloud agents continue authenticating through
agent_api_keys - same-company boundary checks remain mandatory
- agent ability to assign human tasks is permission-gated, not implicit
Instance settings surface
This plan introduces instance-level concerns (for example bootstrap state, instance admins, invite defaults, and token policy). There is no dedicated UI surface today.
V1 approach:
- add a minimal
Instance Settingspage for instance admins - expose key instance settings in API + CLI (
paperclip configure/paperclip onboard) - show read-only instance status indicators in the main UI until full settings UX exists
Implementation phases
Phase 1: Mode and guardrails
- add explicit deployment mode config (
local_trusted | cloud_hosted) - enforce startup safety checks and health visibility
- implement actor resolution for local implicit board
- map local implicit board actor to instance-admin authorization context
- add bootstrap status signal in health/config surface (
ready | bootstrap_pending) - add minimal instance settings API/CLI surface and read-only UI indicators
Phase 2: Human identity and memberships
- add schema + migrations for users/memberships/invites
- wire auth middleware for cloud mode
- add membership lookup and company access checks
- implement Better Auth email/password flow (no email verification)
- implement first-admin bootstrap invite command and onboard integration
- implement one-time share-link invite acceptance flow with
pending_approvaljoin requests
Phase 3: Permissions and assignment scope
- add shared principal grant model and enforcement helpers
- add chain-of-command scope checks for assignment APIs
- add tests for forbidden assignment (for example, cannot assign to CEO)
- add instance-admin promotion/demotion and global company-access management APIs
- add
joins:approvepermission checks for human and agent join approvals
Phase 4: Invite workflow
- unified
company_joininvite create/landing/accept/revoke endpoints - join request approve/reject endpoints with review metadata (email when applicable, IP)
- one-time token security and revocation semantics
- UI for invite management, pending join approvals, and membership permissions
- inbox alert generation for pending join requests
- ensure invite and approval UX is enabled in both
cloud_hostedandlocal_trusted
Phase 5: Human inbox + task assignment updates
- extend issue assignee model for human users
- inbox API and UI for:
- task assignments
- pending join approval alerts with inline approve/reject actions
- agent-to-human assignment flow with policy checks
Phase 6: Agent self-join and token claim
- add agent join path on unified invite landing page
- capture agent join requests and admin approval flow
- create one-time API-key claim flow after approval (display once)
Acceptance criteria
local_trustedstarts with no login and shows board UI immediately.local_trusteddoes not expose optional human login UX in V1.local_trustedlocal implicit actor can manage instance settings, invite links, join approvals, and permission grants.cloud_hostedcannot start without auth configured.- No request in
cloud_hostedcan mutate data without authenticated actor. - If no initial admin exists, app shows bootstrap instructions with CLI command.
pnpm paperclip onboardoutputs a CEO onboarding invite URL when bootstrap is pending.- One
company_joinlink supports both human and agent onboarding via join-type selection on the invite landing page. - Invite delivery in V1 is copy-link only (no built-in email delivery).
- Share-link acceptance creates a pending join request; it does not grant immediate access.
- Pending join requests appear as inbox alerts with inline approve/reject actions.
- Admin review view includes join metadata before decision (human email when applicable, source IP, and agent metadata for agent requests).
- Only approved join requests unlock access:
- human: active company membership + permission grants
- agent: agent creation + API-key claim eligibility
- Agent enrollment follows the same link -> pending approval -> approve flow.
- Approved agents can claim a long-lived API key exactly once, with plaintext display-once semantics.
- Agent API keys are indefinite by default in V1 and revocable/regenerable by admins.
- Public/untrusted ingress for
local_trustedis not supported in V1 (loopback-only local server). - One user can hold memberships in multiple companies.
- Instance admins can promote another user to instance admin.
- Instance admins can manage which companies each user can access.
- Permissions can be granted/revoked per member principal (human or agent) through one shared grant system.
- Assignment scope prevents out-of-hierarchy or protected-role assignments.
- Agents can assign tasks to humans only when allowed.
- Humans can view assigned tasks in inbox and act on them per permissions.
- All new mutations are company-scoped and logged in
activity_log.
V1 decisions (locked)
local_trustedwill not support login UX in V1; implicit local board actor only.- Permissions use a normalized shared table:
principal_permission_grantswith scoped grants. - Invite delivery is copy-link only in V1 (no built-in email sending).
- Bootstrap invite creation should require local shell access only (CLI path only, no HTTP bootstrap endpoint).
- Approval review shows source IP only; no GeoIP/country lookup in V1.
- Agent API-key lifetime is indefinite by default in V1, with explicit revoke/regenerate controls.
- Local mode keeps full admin/settings/invite capabilities through the implicit local instance-admin actor.
- Public/untrusted ingress for local mode is out of scope for V1; no
--dangerous-agent-ingressin V1.