AdvancedMulti-tenancyAuthorizationRealtimePaginationAudit logs

Project Management Platform

Tenant-safe projects, tasks, comments, permissions, activity, and collaborative updates.

Product overview

Coordinate tenant-scoped projects, tasks, comments, attachments, permissions, activity, and live updates without losing edits or leaking workspace data.

Main actors

Member, Project admin, Guest, Workspace owner.

Assumptions

  • Workspace is the tenant boundary
  • Task editing is field-level optimistic collaboration, not a CRDT text editor
  • Activity is derived from accepted commands
  • Attachments use object storage

Non-goals

  • Source control
  • General document editing
  • Payroll
  • Exactly-once notifications

Requirements

Must-have

  • Workspace and project membership
  • Task CRUD and workflow
  • Comments and mentions
  • Activity feed
  • Notifications and search

Should-have

  • Custom fields
  • Saved views
  • Dependencies
  • Guest access

Future scope

  • Offline editing
  • Automations
  • Portfolio planning
  • Rich collaborative documents

Non-functional decisions

ConcernDecision
Tenant isolationAll objects carry workspace_id; authorization includes workspace and project membership.
ConsistencyCommand writes are strong; activity, search, unread counts, and realtime presence are eventual.
LatencyTask reads/writes stay interactive; bulk exports are jobs.
AuditabilityPermission, membership, deletion, and automation actions record actor and reason.
AvailabilityRealtime and notification outages do not block core task writes.

User roles

RoleAccess boundary
Workspace ownerBilling, security, members, all projects
Project adminProject settings and membership
MemberAuthorized projects and task actions
GuestExplicitly shared projects only

Core workflows

Plan work
Create project → Invite members/guests → Create tasks → Set assignee/status/dates → Publish activity → Fan out live update
Edit concurrently
Client reads task and version → User changes fields → PATCH includes expected version → Server authorizes and conditionally updates → Conflict returns current representation → Client merges or retries

Domain model

EntitiesRelationship
Workspace → MembershipTenant role and account link
Workspace → Project → TaskTask cannot cross workspace/project
Task → Comment / AttachmentChild content inherits but rechecks task access
Task → TaskEventAppend-only activity fact after command
User → NotificationPer-recipient delivery/read projection

Database design

PostgreSQL remains the transactional source of truth. Add specialized stores only for the workloads called out below.

tasks — Mutable work item

tasks ----- id UUID PK workspace_id UUID FK project_id UUID FK title TEXT status TEXT assignee_id UUID NULL rank TEXT version INT deleted_at TIMESTAMPTZ NULL Constraints: FK(workspace_id, project_id) preserves tenant; version > 0 Indexes: (workspace_id, project_id, status, rank); (workspace_id, assignee_id, status)

project_memberships — Project-level access

project_memberships ------------------- workspace_id UUID project_id UUID user_id UUID role TEXT Constraints: PK(project_id, user_id); composite tenant FKs Indexes: (workspace_id, user_id)

task_events — Activity and integration fact

task_events ----------- id UUID PK workspace_id UUID task_id UUID actor_id UUID kind TEXT data JSONB created_at TIMESTAMPTZ Constraints: append-only Indexes: (workspace_id, task_id, id); (workspace_id, created_at DESC, id DESC)

notifications — Recipient projection

notifications ------------- id UUID PK workspace_id UUID recipient_id UUID event_id UUID read_at TIMESTAMPTZ NULL Constraints: UNIQUE(recipient_id, event_id) Indexes: (recipient_id, read_at, id DESC)

Transaction and retention boundaries

  • Conditional task update, activity record, and outbox commit together
  • Membership removal and share-token revocation are atomic
  • Soft-delete tasks for restore/audit; hard-delete expired upload staging and sanitized content after retention

API design

GET /v1/projects/:id/tasks?cursor=…&status=… POST /v1/projects/:id/tasks PATCH /v1/tasks/:id POST /v1/tasks/:id/comments GET /v1/activity?cursor=… DELETE /v1/projects/:id/members/:userId
EndpointPurpose and permissionIdempotency / status
GET /v1/projects/:id/tasks?cursor=…&status=…Stable keyset-paginated task list
Project member
Not required
200, 403
POST /v1/projects/:id/tasksCreate task
Project contributor
Client-generated request id recommended
201, 403, 422
PATCH /v1/tasks/:idUpdate fields with If-Match version
Authorized project member
Conditional version
200, 409, 412
POST /v1/tasks/:id/commentsAdd comment and mentions
Task viewer with comment right
Client mutation id
201, 403
GET /v1/activity?cursor=…Workspace activity by created_at/id cursor
Filter every event to visible projects
Not required
200, 403
DELETE /v1/projects/:id/members/:userIdRevoke project membership
Project admin
Delete is idempotent
204, 403

List endpoints use opaque keyset cursors unless a small bounded collection is explicit. Unknown objects return 404 where 403 would leak existence.

Architecture

System context

Project Management Platform system context
Project Management Platform system context

Main application architecture

Project Management Platform application architecture
Project Management Platform application architecture

Critical workflow sequence

Project Management Platform workflow sequence
Project Management Platform workflow sequence

Event model

EventProducer → consumersDelivery
TaskChangedTask module → Realtime, search, activity, automationOutbox; task version lets projections discard old events
CommentAddedComment module → Mention notifications, searchConsumer inbox prevents duplicate notification
MembershipRevokedAuthorization module → Realtime session revocation, auditHigh priority; reads still reauthorize at source

Failure modes

Lost update

Failure
Two members overwrite the same task.

Mitigation
Expected version; return 412 with current task for merge.

Revoked guest socket

Failure
Guest remains connected after access removal.

Mitigation
Publish revocation and reauthorize subscriptions/events; source reads always enforce access.

Duplicate mention

Failure
Comment event is replayed.

Mitigation
Unique recipient/event notification and idempotent email job.

Reorder collision

Failure
Many tasks move to the same rank.

Mitigation
Fractional ranks plus asynchronous bounded rebalance with version checks.

Search leak

Failure
Index contains content after permission change.

Mitigation
Search filters by current ACL tokens and sensitive result hydration reauthorizes.

Upload orphan

Failure
Attachment upload succeeds but task attach fails.

Mitigation
Staging objects expire; finalize only after metadata transaction.

Consistency and concurrency

Strong consistency / transactions

  • Membership authorization
  • Task command/version
  • Project deletion
  • attachment ownership

Eventual convergence

  • Search
  • activity views
  • notifications
  • presence/unread counts

Concurrency controls

  • Optimistic version on task PATCH
  • Composite tenant foreign keys prevent cross-workspace references
  • Unique client mutation id makes comment retry safe
  • Rank rebalance never requires a global distributed lock

Caching and background work

Safe to cache

  • Workspace/project configuration by version
  • User avatars
  • Permission-independent static assets

Never a cache authority

  • Authorization decision beyond a very short session epoch
  • Task version used for writes
  • Private search result body

Cache-aside configuration using workspace/version keys. Invalidate membership epochs and recheck object access.

Background jobs

  • Search indexing
  • Mention/email delivery
  • Attachment scan and thumbnail
  • Rank rebalance
  • Workspace export
  • Trash retention purge

Security boundaries

Security claims here are design controls, not certification. Threat modeling and jurisdiction-specific review remain required.

  • Tenant and project object authorization on every route and socket subscription
  • Composite foreign keys prevent cross-tenant attachment
  • Signed short-lived upload URLs constrained by key and size
  • Sanitize rich text and rendered links
  • Audit membership, exports, token creation, and deletions
  • Rate-limit invitations, mentions, and public shares
  • Revoke sessions/share links when membership changes

Observability

Logs

  • Task mutation with workspace/project ids, expected/current version, changed fields, and authorization result
  • Membership/share changes and audited export/delete commands
  • Realtime/search consumer event version and stale-event decision

Metrics

  • Task optimistic-conflict rate by client/version
  • Realtime publish-to-delivery lag and disconnected subscriber count
  • Search indexing delay and permission-filter rejection rate
  • Notification backlog, mention dedupe, and attachment scan age

Traces

  • Trace task command through PostgreSQL, activity/outbox, realtime, and search projection
  • Trace attachment finalize through scan, metadata link, and preview

Alerts

  • Membership revocations fail to reach session gateways
  • Search index lag or authorization-filter anomalies
  • Hot workspace fan-out causes sustained delivery backlog

Testing strategy

LayerWhat it proves
UnitPermission matrix, state transitions, rank generation, cursor encoding
IntegrationTenant FKs, conditional update, outbox, upload finalize
ContractVersioned realtime and webhook events
ConcurrencyParallel edits, moves, membership revocation
FailureIndexer lag, socket disconnect, orphan upload, duplicate event
End-to-end/loadInvite → task → comment → search; hot-project fan-out

Do not mock PostgreSQL constraints, transaction isolation, object-store semantics, or provider contract fixtures in the tests intended to prove those boundaries.

Deployment evolution

Stage 1
Next.js, modular API, PostgreSQL, object storage, worker, WebSocket gateway
Stage 2
API/realtime replicas, Redis presence, search engine, dedicated workers, read replica
Stage 3
Workspace-cohort databases and partitioned event/realtime infrastructure

Scale evolution

These are architecture scenarios, not throughput claims. Each transition should follow measured workload and reliability pressure.

ScenarioArchitecture may evolve towardDecision rule
1K usersOne API, PostgreSQL with workspace-scoped rows, a WebSocket gateway, a notification and indexing workerAuthorize every read and write against the workspace; version rows so concurrent edits are detected, not overwritten
100K usersSplit realtime, indexing, and attachment workers; retain one write authority per workspaceSplit realtime fan-out and search indexing when activity bursts delay notifications or slow task writes
1M+ usersPartition workspaces into cells; route all tenant data and events to its cell, with sanitized aggregate analyticsRoute whole workspaces to cells; permissions, events, and search indexes never cross a tenant boundary