## Thinking Path
> - Paperclip is the open source control plane people use to manage
AI-agent companies and their work
> - The inbox subsystem must let agents act for a responsible user
without silently granting access to every company user's tasks
> - Existing authorization had no inbox-specific action, target-user
scope, or per-user agent policy
> - Inbox archive data also needs company-safe ownership and replay-safe
schema changes before API mutations can rely on it
> - This pull request adds the database policy foundation and a
fail-closed `inbox:manage` authorization decision
> - The benefit is a least-privilege core for later inbox archive
endpoints, including explicit cross-user grants and low-trust denial
## Linked Issues or Issue Description
### Subsystem affected
Cross-cutting (`packages/db`, `packages/shared`, and `server`).
### Problem or motivation
Agents need to manage inbox state for the user responsible for their
run, but the control plane lacks an inbox-specific permission model and
user-targeted grant scope. A generic mutation path would risk cross-user
access or inconsistent policy enforcement.
### Proposed solution
Add inbox archive ownership and per-user agent policies, introduce
`inbox:manage`, and evaluate responsible-user defaults,
disabled/allowlist policies, active membership, low-trust presets, and
scoped cross-user grants in one authorization decision.
### Alternatives considered
Reusing generic issue mutation permissions was rejected because it
cannot express user-targeted inbox scope. Requiring grants for all
self-user access was rejected because it would make the responsible-user
path closed by default instead of using the requested per-user policy
model.
### Roadmap alignment
`ROADMAP.md` contains no overlapping inbox archive or inbox
authorization item; this is incremental control-plane authorization
work.
### Additional context
This PR provides the authorization and schema foundation. Route and UI
behavior can build on this decision without duplicating access-control
rules.
## What Changed
- Builds on the merged migration `0172_inbox_archive_agent_policies`
(#9654) for company/user-scoped inbox archives and per-user agent policy
rows.
- Added replay-safe migration `0173_inbox_policy_agent_cleanup` with a
GIN allowlist index and GIN-backed database cleanup that removes deleted
agent IDs from policy allowlists.
- Added Drizzle schema exports for inbox agent policies and
responsible-user ownership on inbox archives.
- Added the shared `inbox:manage` permission key and `scope.userIds`
evaluation for user-targeted grants.
- Added fail-closed inbox authorization for unresolved targets, inactive
memberships, low-trust agents, disabled policies, allowlist misses, and
ungranted cross-user access.
- Added migration replay coverage and the full inbox authorization
decision matrix.
## Verification
- `pnpm exec vitest run
packages/db/src/inbox-archive-agent-policies-migration.test.ts
server/src/__tests__/authorization-service.test.ts` — 50 tests passed.
- `pnpm --filter @paperclipai/db typecheck`
- `pnpm --filter @paperclipai/shared typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `git diff --check origin/master...HEAD`
## Risks
- The merged `0172` migration changed inbox archive uniqueness from
agent-owned to responsible-user-owned rows; `0173` is additive (index +
cleanup trigger) and idempotent, and replay coverage verifies both
remain safe for databases that already applied an earlier form.
- `scope.userIds` uses the existing JSON grant-scope parser, so
malformed privileged grant payloads continue to fail through the shared
parsing behavior rather than a dedicated schema.
- Cross-user grants intentionally act as board-admin overrides;
responsible-user default access remains bounded by disabled and
allowlist policies.
- The authorization action is not yet wired to public mutation routes,
limiting immediate behavioral impact while establishing the contract
those routes must use.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex with `gpt-5.6-sol`, high reasoning effort, CLI tool use,
code execution, GitHub CLI, and Paperclip control-plane integration.
Context window size is not exposed by the configured adapter.
## 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
- [x] All Paperclip CI gates are green
- [x] 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>
Server tests that need a real PostgreSQL process must use
./helpers/embedded-postgres.ts instead of constructing embedded-postgres
directly.
The shared helper creates a throwaway data directory and a reserved-safe
loopback port for each test database. This protects the live Paperclip
control-plane Postgres from server vitest runs; see PAP-2033 for the incident
that introduced this guard.