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
| Concern | Decision |
|---|---|
| Tenant isolation | All objects carry workspace_id; authorization includes workspace and project membership. |
| Consistency | Command writes are strong; activity, search, unread counts, and realtime presence are eventual. |
| Latency | Task reads/writes stay interactive; bulk exports are jobs. |
| Auditability | Permission, membership, deletion, and automation actions record actor and reason. |
| Availability | Realtime and notification outages do not block core task writes. |
User roles
| Role | Access boundary |
|---|---|
| Workspace owner | Billing, security, members, all projects |
| Project admin | Project settings and membership |
| Member | Authorized projects and task actions |
| Guest | Explicitly shared projects only |
Core workflows
Domain model
| Entities | Relationship |
|---|---|
Workspace → Membership | Tenant role and account link |
Workspace → Project → Task | Task cannot cross workspace/project |
Task → Comment / Attachment | Child content inherits but rechecks task access |
Task → TaskEvent | Append-only activity fact after command |
User → Notification | Per-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
project_memberships — Project-level access
task_events — Activity and integration fact
notifications — Recipient projection
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
| Endpoint | Purpose and permission | Idempotency / status |
|---|---|---|
GET /v1/projects/:id/tasks?cursor=…&status=… | Stable keyset-paginated task list Project member | Not required 200, 403 |
POST /v1/projects/:id/tasks | Create task Project contributor | Client-generated request id recommended 201, 403, 422 |
PATCH /v1/tasks/:id | Update fields with If-Match version Authorized project member | Conditional version 200, 409, 412 |
POST /v1/tasks/:id/comments | Add 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/:userId | Revoke 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
Main application architecture
Critical workflow sequence
Event model
| Event | Producer → consumers | Delivery |
|---|---|---|
TaskChanged | Task module → Realtime, search, activity, automation | Outbox; task version lets projections discard old events |
CommentAdded | Comment module → Mention notifications, search | Consumer inbox prevents duplicate notification |
MembershipRevoked | Authorization module → Realtime session revocation, audit | High 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
| Layer | What it proves |
|---|---|
| Unit | Permission matrix, state transitions, rank generation, cursor encoding |
| Integration | Tenant FKs, conditional update, outbox, upload finalize |
| Contract | Versioned realtime and webhook events |
| Concurrency | Parallel edits, moves, membership revocation |
| Failure | Indexer lag, socket disconnect, orphan upload, duplicate event |
| End-to-end/load | Invite → 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
Scale evolution
These are architecture scenarios, not throughput claims. Each transition should follow measured workload and reliability pressure.
| Scenario | Architecture may evolve toward | Decision rule |
|---|---|---|
| 1K users | One API, PostgreSQL with workspace-scoped rows, a WebSocket gateway, a notification and indexing worker | Authorize every read and write against the workspace; version rows so concurrent edits are detected, not overwritten |
| 100K users | Split realtime, indexing, and attachment workers; retain one write authority per workspace | Split realtime fan-out and search indexing when activity bursts delay notifications or slow task writes |
| 1M+ users | Partition workspaces into cells; route all tenant data and events to its cell, with sanitized aggregate analytics | Route whole workspaces to cells; permissions, events, and search indexes never cross a tenant boundary |