Update project files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1305
.planning/research/ARCHITECTURE.md
Normal file
1305
.planning/research/ARCHITECTURE.md
Normal file
File diff suppressed because it is too large
Load Diff
492
.planning/research/FEATURES.md
Normal file
492
.planning/research/FEATURES.md
Normal file
@@ -0,0 +1,492 @@
|
||||
# Feature Research
|
||||
|
||||
**Domain:** Multi-tenancy, user accounts, and role-based access control
|
||||
**Researched:** 2026-03-18
|
||||
**Confidence:** HIGH
|
||||
|
||||
## Feature Landscape
|
||||
|
||||
### Table Stakes (Users Expect These)
|
||||
|
||||
Features users assume exist. Missing these = product feels incomplete.
|
||||
|
||||
| Feature | Why Expected | Complexity | Notes |
|
||||
|---------|--------------|------------|-------|
|
||||
| Individual user accounts | Standard expectation in any multi-user app; shared passwords are deprecated UX | MEDIUM | Email/password auth, JWT tokens (already have JWT infrastructure) |
|
||||
| Per-user project ownership | Users expect "my projects" vs "shared with me" distinction | MEDIUM | Requires user_id column on projects table, workspace isolation |
|
||||
| Invite-only registration | Standard pattern for self-hosted tools; prevents open registration abuse | LOW | Invite codes/tokens in database, validation before signup |
|
||||
| Basic role hierarchy (viewer/editor/admin) | Industry standard in collaborative tools (GitHub, Notion, Figma all use 3-tier) | MEDIUM | Role enum on project_user junction table, permission checks |
|
||||
| Edit attribution in history | Users expect to see "who changed what" in version history | LOW | Already have history table; add user_id foreign key |
|
||||
| Project sharing UI | Ability to add collaborators and set their role | MEDIUM | UI for managing project members, permission checks |
|
||||
| Session management | Users expect to see active sessions, logout from devices | LOW | JWT refresh tokens, token revocation list |
|
||||
| Password reset flow | Expected security hygiene for email/password auth | MEDIUM | Email service integration, reset tokens, secure workflows |
|
||||
|
||||
### Differentiators (Competitive Advantage)
|
||||
|
||||
Features that set the product apart. Not required, but valuable.
|
||||
|
||||
| Feature | Value Proposition | Complexity | Notes |
|
||||
|---------|-------------------|------------|-------|
|
||||
| Granular audit trail with rollback | Beyond basic attribution—show full change context and allow per-edit rollback | MEDIUM | Leverage existing history system; add user context to each snapshot |
|
||||
| Workspace-level dashboards | Per-user analytics showing activity across all their projects | LOW | Aggregate existing project stats by user |
|
||||
| Inherited permissions on requirement hierarchy | Child requirements inherit parent permissions; reduces management overhead | HIGH | Complex to implement correctly; easy to introduce leaks |
|
||||
| Anonymous viewer links | Share read-only project views without requiring account | MEDIUM | Signed tokens with expiry, no-auth view endpoints |
|
||||
| Activity feeds | "What changed since I last logged in" per project | MEDIUM | Requires tracking last_seen timestamps and filtering history |
|
||||
| Workspace templates | Users can save project configurations as templates for reuse | LOW | Export/import existing project structure |
|
||||
| Fine-grained permission customization | Beyond viewer/editor/admin—custom permission sets | HIGH | Significantly increases complexity; likely over-engineering for v2 |
|
||||
|
||||
### Anti-Features (Commonly Requested, Often Problematic)
|
||||
|
||||
Features that seem good but create problems.
|
||||
|
||||
| Feature | Why Requested | Why Problematic | Alternative |
|
||||
|---------|---------------|-----------------|-------------|
|
||||
| Real-time collaborative editing | "Like Google Docs" — users assume all modern tools have it | Massive architectural lift; requires WebSockets, operational transforms, conflict resolution; SQLite poor fit | Optimistic locking with "edited by X" warnings; auto-refresh on save |
|
||||
| Organization/team hierarchies | Enterprises expect nested orgs (company → department → team) | Dramatically increases permission model complexity; complicates billing/limits | Single workspace per user; flat project sharing model |
|
||||
| Social login (OAuth) | Convenient for users, reduces friction | Adds external dependencies; complicates self-hosted deployments; privacy concerns | Email/password sufficient for self-hosted tool; invite-only provides security |
|
||||
| Fine-grained field-level permissions | "Editor can change description but not acceptance criteria" | Explosion of permission checks; confusing UX; high maintenance burden | Role-based with clear responsibilities; don't split access within entities |
|
||||
| Public project discovery | "Browse all public projects like GitHub" | Privacy concerns for self-hosted tool; invites spam/abuse | Explicit sharing only; anonymous links for specific cases |
|
||||
| User-to-user direct messaging | "Chat with collaborators in-app" | Scope creep; users already have Slack/email/Discord | Keep tool focused; collaboration happens in comments/edits |
|
||||
|
||||
## Feature Dependencies
|
||||
|
||||
```
|
||||
Individual User Accounts (foundational)
|
||||
├──requires──> User Authentication Flow
|
||||
│ ├──requires──> Login/Logout
|
||||
│ ├──requires──> Password Reset
|
||||
│ └──requires──> Session Management
|
||||
│
|
||||
├──requires──> Per-User Project Ownership
|
||||
│ ├──requires──> Workspace Isolation (tenant_id pattern)
|
||||
│ └──requires──> "My Projects" vs "Shared With Me" views
|
||||
│
|
||||
├──requires──> Edit Attribution
|
||||
│ ├──enhances──> Existing History System
|
||||
│ └──requires──> User Foreign Keys on History Table
|
||||
│
|
||||
└──requires──> Invite-Only Registration
|
||||
├──requires──> Invite Token Generation
|
||||
├──requires──> Invite Validation
|
||||
└──requires──> Admin Controls
|
||||
|
||||
Role-Based Access Control
|
||||
├──requires──> Individual User Accounts (above)
|
||||
├──requires──> Project-User Junction Table
|
||||
│ └──stores──> Role (viewer/editor/admin/owner)
|
||||
│
|
||||
├──requires──> Permission Enforcement Layer
|
||||
│ ├──frontend──> Route guards, UI element visibility
|
||||
│ └──backend──> Endpoint authorization checks
|
||||
│
|
||||
└──enhances──> Project Sharing UI
|
||||
└──requires──> Member Management Interface
|
||||
|
||||
Project Sharing UI
|
||||
├──requires──> Role-Based Access Control (above)
|
||||
└──requires──> Invite Notification System
|
||||
|
||||
Audit Trail with User Context
|
||||
├──requires──> Edit Attribution (above)
|
||||
└──enhances──> Existing History Visualization
|
||||
```
|
||||
|
||||
### Dependency Notes
|
||||
|
||||
- **Individual User Accounts is foundational**: All other features depend on this being implemented first
|
||||
- **Workspace Isolation must be implemented correctly from the start**: Retrofitting tenant isolation is high-risk for data leaks
|
||||
- **Edit Attribution enhances existing history**: Already have requirement_history table; adding user_id is low-risk
|
||||
- **RBAC requires careful sequencing**: Must implement permission checks in backend before exposing sharing UI
|
||||
- **Invite-only registration can be parallel to RBAC**: These features are independent after user accounts exist
|
||||
|
||||
## MVP Definition
|
||||
|
||||
### Launch With (v2.0)
|
||||
|
||||
Minimum viable multi-tenancy — what's needed to validate the concept.
|
||||
|
||||
- [x] Individual user accounts with email/password authentication — Essential foundation
|
||||
- [x] Invite-only registration with admin-generated invite codes — Security requirement for self-hosted
|
||||
- [x] Per-user project ownership with workspace isolation — Core multi-tenancy feature
|
||||
- [x] Three-tier RBAC (owner/editor/viewer) on projects — Table stakes for collaboration
|
||||
- [x] Edit attribution in history (user_id on edits) — Minimal audit trail
|
||||
- [x] Project sharing UI with role assignment — Required to make RBAC usable
|
||||
- [x] Basic permission enforcement (backend + frontend) — Security requirement
|
||||
- [x] Password reset flow — Expected security hygiene
|
||||
|
||||
### Add After Validation (v2.x)
|
||||
|
||||
Features to add once core multi-tenancy is working.
|
||||
|
||||
- [ ] Activity feeds per project — Add once users report "losing track of changes"
|
||||
- [ ] Workspace-level dashboard — Add when users manage 5+ projects
|
||||
- [ ] Anonymous viewer links — Add when users request "share with client" workflows
|
||||
- [ ] Granular audit trail with per-edit rollback — Add when attribution proves insufficient
|
||||
- [ ] Session management UI — Add if users report security concerns with active sessions
|
||||
- [ ] Workspace templates — Add when users request "clone project structure"
|
||||
|
||||
### Future Consideration (v3+)
|
||||
|
||||
Features to defer until product-market fit is established.
|
||||
|
||||
- [ ] Fine-grained permission customization — Defer until 3-tier RBAC proves insufficient
|
||||
- [ ] Inherited permissions on hierarchy — Complex; defer until users report permission management overhead
|
||||
- [ ] Organization/team hierarchies — Defer until serving enterprise customers
|
||||
- [ ] OAuth/SSO providers — Defer until serving organizations requiring SSO
|
||||
- [ ] Real-time collaborative editing — Major architectural change; defer until core collaboration proves valuable
|
||||
|
||||
## Feature Prioritization Matrix
|
||||
|
||||
| Feature | User Value | Implementation Cost | Priority |
|
||||
|---------|------------|---------------------|----------|
|
||||
| Individual user accounts | HIGH | MEDIUM | P1 |
|
||||
| Invite-only registration | HIGH | LOW | P1 |
|
||||
| Per-user project ownership | HIGH | MEDIUM | P1 |
|
||||
| Three-tier RBAC (owner/editor/viewer) | HIGH | MEDIUM | P1 |
|
||||
| Edit attribution | HIGH | LOW | P1 |
|
||||
| Project sharing UI | HIGH | MEDIUM | P1 |
|
||||
| Permission enforcement layer | HIGH | HIGH | P1 |
|
||||
| Password reset flow | MEDIUM | MEDIUM | P1 |
|
||||
| Activity feeds | MEDIUM | MEDIUM | P2 |
|
||||
| Workspace dashboard | MEDIUM | LOW | P2 |
|
||||
| Anonymous viewer links | MEDIUM | MEDIUM | P2 |
|
||||
| Granular audit trail | LOW | MEDIUM | P2 |
|
||||
| Session management UI | LOW | LOW | P2 |
|
||||
| Workspace templates | LOW | LOW | P2 |
|
||||
| Fine-grained permissions | LOW | HIGH | P3 |
|
||||
| Inherited permissions | MEDIUM | HIGH | P3 |
|
||||
| Organization hierarchies | LOW | HIGH | P3 |
|
||||
| OAuth/SSO | LOW | HIGH | P3 |
|
||||
| Real-time collaboration | HIGH | VERY HIGH | P3 |
|
||||
|
||||
**Priority key:**
|
||||
- P1: Must have for v2.0 launch — core multi-tenancy
|
||||
- P2: Should have for v2.x — enhances collaboration
|
||||
- P3: Nice to have for v3+ — future consideration
|
||||
|
||||
## Expected Behavior and UX Patterns
|
||||
|
||||
### Invite-Only Registration
|
||||
|
||||
**Standard Flow:**
|
||||
1. Admin generates invite code/link from admin panel
|
||||
2. Invite stored in database with: code (UUID), email (optional), created_by, expires_at, used (boolean)
|
||||
3. Registration page requires valid invite code before showing signup form
|
||||
4. After successful signup, invite marked as used (single-use) or incremented (multi-use)
|
||||
5. Expired/invalid codes show clear error messages
|
||||
|
||||
**UX Patterns (2026):**
|
||||
- **Invite links preferred over codes**: Users click link → auto-fills invite code → shows signup form
|
||||
- **Email pre-fill**: If invite includes email, pre-populate signup form (can't be changed)
|
||||
- **Context in email**: Invitation email shows who invited them and to which workspace
|
||||
- **Clear expiry**: Show "This invite expires in X days" prominently
|
||||
- **Admin controls**: Dashboard showing pending/used/expired invites
|
||||
|
||||
**Sources:**
|
||||
- [User Onboarding Strategies in a B2B SaaS Application](https://auth0.com/blog/user-onboarding-strategies-b2b-saas/)
|
||||
- [How to onboard invited users and fast-track user engagement](https://www.appcues.com/blog/user-onboarding-strategies-invited-users)
|
||||
|
||||
### Per-User Project Ownership
|
||||
|
||||
**Standard Patterns:**
|
||||
1. **Shared database with tenant_id**: All users share same tables; user_id column differentiates ownership
|
||||
2. **Workspace views**: "My Projects" (owned), "Shared With Me" (collaborator), optionally "All Projects" (admin)
|
||||
3. **Default visibility**: Projects private by default; owner explicitly shares
|
||||
4. **Transfer ownership**: Admin capability to reassign project owner
|
||||
5. **Orphaned projects**: Handle deleted user accounts (reassign or archive)
|
||||
|
||||
**Data Model:**
|
||||
```sql
|
||||
projects:
|
||||
- owner_id (FK to users)
|
||||
- created_by (FK to users)
|
||||
- updated_by (FK to users)
|
||||
|
||||
project_users (junction):
|
||||
- project_id (FK to projects)
|
||||
- user_id (FK to users)
|
||||
- role (enum: owner, editor, viewer)
|
||||
- added_by (FK to users)
|
||||
- added_at (timestamp)
|
||||
```
|
||||
|
||||
**Critical Security Pattern:**
|
||||
- Every query MUST include `WHERE user_id = ? OR EXISTS (SELECT 1 FROM project_users WHERE project_id = ? AND user_id = ?)`
|
||||
- Enforce at ORM level or middleware layer, not per-endpoint (reduces chance of missing checks)
|
||||
|
||||
**Sources:**
|
||||
- [The developer's guide to SaaS multi-tenant architecture — WorkOS](https://workos.com/blog/developers-guide-saas-multi-tenant-architecture)
|
||||
- [Tenant Data Isolation: 5 Patterns That Actually Work](https://propelius.tech/blogs/tenant-data-isolation-patterns-and-anti-patterns/)
|
||||
|
||||
### Role-Based Access Control (Viewer/Editor/Admin)
|
||||
|
||||
**Industry Standard Three-Tier Model:**
|
||||
|
||||
| Role | Read | Create/Edit | Delete | Manage Members | Transfer Ownership |
|
||||
|------|------|-------------|--------|----------------|--------------------|
|
||||
| **Owner** | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| **Editor** | ✓ | ✓ | ✓ (own edits) | ✗ | ✗ |
|
||||
| **Viewer** | ✓ | ✗ | ✗ | ✗ | ✗ |
|
||||
|
||||
**Implementation Pattern:**
|
||||
```python
|
||||
# Backend: Permission decorator
|
||||
@require_project_permission(Permission.EDIT)
|
||||
def update_requirement(project_id, requirement_id, data):
|
||||
# Only called if user has editor+ role
|
||||
pass
|
||||
|
||||
# Frontend: Structural directives
|
||||
<button *ngIf="hasPermission('edit')">Edit</button>
|
||||
```
|
||||
|
||||
**UX Expectations:**
|
||||
- **Graceful degradation**: Viewers see read-only interface, not error messages
|
||||
- **Clear role badges**: Show user's role in project header ("You are an Editor")
|
||||
- **Contextual actions**: Only show actions user can perform (hide "Delete" for viewers)
|
||||
- **Permission feedback**: If user somehow triggers unauthorized action, show clear explanation
|
||||
|
||||
**Common Mistakes to Avoid:**
|
||||
- **Authorization only on frontend**: Always enforce on backend; frontend is convenience only
|
||||
- **Inconsistent enforcement**: Missing permission checks on some endpoints creates security holes
|
||||
- **Role explosion**: Don't create custom roles too early; three tiers sufficient for most cases
|
||||
- **Cache-based authorization**: Store role in user-controllable location (localStorage) for UI only; always verify on backend
|
||||
|
||||
**Sources:**
|
||||
- [How to Build a Role-Based Access Control Layer](https://www.osohq.com/learn/rbac-role-based-access-control)
|
||||
- [Role-Based Access Control Best Practices for 2026](https://www.techprescient.com/blogs/role-based-access-control-best-practices/)
|
||||
- [Guide to sharing and permissions – Figma Learn](https://help.figma.com/hc/en-us/articles/1500007609322-Guide-to-sharing-and-permissions)
|
||||
|
||||
### Edit Attribution
|
||||
|
||||
**Expected Behavior:**
|
||||
1. Every edit records: user_id, timestamp, before/after state
|
||||
2. History view shows: "John Doe edited requirement title at 2:34 PM"
|
||||
3. Hover/click shows full context: what changed, by whom, when
|
||||
4. Filter history: "Show only my edits" or "Show edits by John"
|
||||
|
||||
**Google Docs Pattern (Gold Standard):**
|
||||
- **Show Editors feature**: Right-click text → "Show Editors" → see who edited that specific range
|
||||
- **Named versions**: Users can create named checkpoints ("Before client review")
|
||||
- **Continuous recording**: Every change captured automatically (no manual "save version")
|
||||
- **Real-time attribution**: See colored cursors/highlights showing who's editing what
|
||||
|
||||
**Adapted for Non-Real-Time:**
|
||||
- Display "Last edited by X at Y" on each requirement card
|
||||
- History panel shows chronological list with user avatars
|
||||
- Diff view highlights changes with attribution ("Added by John", "Removed by Jane")
|
||||
- Filter/search history by user
|
||||
|
||||
**Implementation Notes:**
|
||||
- Already have requirement_history table
|
||||
- Add user_id foreign key to requirement_history
|
||||
- Add created_by, updated_by to requirement_nodes
|
||||
- Frontend displays user info from history records
|
||||
|
||||
**Sources:**
|
||||
- [Google Workspace Updates: "Show Editors" provides more context on changes made in Google Docs](https://workspaceupdates.googleblog.com/2021/05/view-more-context-on-google-docs-edits-with-show-editors.html)
|
||||
- [How to See Version History in Google Docs](https://www.geeksforgeeks.org/websites-apps/how-to-see-version-history-in-google-docs/)
|
||||
|
||||
### Project Sharing UI
|
||||
|
||||
**Standard Patterns:**
|
||||
1. **Share button**: Prominent in project header
|
||||
2. **Member list**: Shows current collaborators with roles
|
||||
3. **Add member flow**:
|
||||
- Input: Email or username
|
||||
- Select role: Owner/Editor/Viewer (with tooltips explaining each)
|
||||
- Optional: Add message to invitation
|
||||
- Send: Generates notification and adds to project
|
||||
4. **Role modification**: Click role dropdown → change role → confirm
|
||||
5. **Remove member**: Click X or "Remove" → confirm dialog → remove access
|
||||
|
||||
**GitHub Pattern:**
|
||||
- Settings → Collaborators → "Add people" → username → role → "Add"
|
||||
- Shows pending invitations separately from active members
|
||||
- Email notification when invited
|
||||
|
||||
**Notion Pattern:**
|
||||
- "Share" button → modal with member list
|
||||
- Inline role editing (click role → dropdown)
|
||||
- "Copy link" for anonymous sharing
|
||||
- Shows "X has access via Team Y" (inherited permissions)
|
||||
|
||||
**Figma Pattern:**
|
||||
- Share button → modal showing team/project/file hierarchy
|
||||
- Link sharing with permission level attached to link
|
||||
- "Anyone with link can view/edit"
|
||||
|
||||
**Recommended Approach:**
|
||||
- Hybrid: GitHub-style explicit invitations + Notion-style inline role editing
|
||||
- No link-based sharing in v2.0 (add in v2.x as anonymous viewer links)
|
||||
- Clear visual distinction between owner (1 per project) and collaborators
|
||||
|
||||
**Sources:**
|
||||
- [Guide to sharing and permissions – Figma Learn](https://help.figma.com/hc/en-us/articles/1500007609322-Guide-to-sharing-and-permissions)
|
||||
- [How to Add Collaborators in GitHub](https://www.rapidnative.com/blogs/how-to-add-collaborators-in-github)
|
||||
|
||||
## Complexity on Existing Features
|
||||
|
||||
### Impact on Existing Edit History
|
||||
|
||||
**Current State:**
|
||||
- requirement_history table stores snapshots (before/after JSON)
|
||||
- No user attribution — assumes single user
|
||||
|
||||
**Required Changes:**
|
||||
- Add user_id column (FK to users)
|
||||
- Migrate existing records: set user_id to admin or mark as "system"
|
||||
- Update create_history_record() to include current_user_id
|
||||
- Update history display to show user info
|
||||
|
||||
**Complexity: LOW** — Additive change, doesn't break existing functionality
|
||||
|
||||
### Impact on Existing Graph Visualization
|
||||
|
||||
**Current State:**
|
||||
- Graph shows all requirements in project
|
||||
- No permission-based filtering
|
||||
|
||||
**Required Changes:**
|
||||
- Filter requirements based on project access (user is owner/editor/viewer)
|
||||
- Show read-only indicators for viewers
|
||||
- Disable edit actions (drag, delete, create) for viewers
|
||||
- Show permission indicators in detail panel
|
||||
|
||||
**Complexity: MEDIUM** — Requires permission checks in graph component, but no architectural changes
|
||||
|
||||
### Impact on Existing CRUD Operations
|
||||
|
||||
**Current State:**
|
||||
- No user context on create/update/delete
|
||||
- No ownership checks
|
||||
|
||||
**Required Changes:**
|
||||
- Add user_id to CREATE operations (set owner)
|
||||
- Add permission checks to UPDATE operations (is editor+?)
|
||||
- Add permission checks to DELETE operations (is owner or editor with own edits?)
|
||||
- Add created_by, updated_by columns to requirement_nodes
|
||||
- Migrate existing records: set created_by to admin or null
|
||||
|
||||
**Complexity: MEDIUM** — Every endpoint needs permission middleware, but follows consistent pattern
|
||||
|
||||
### Impact on Existing Dashboard
|
||||
|
||||
**Current State:**
|
||||
- Shows all projects globally
|
||||
- Statistics aggregate all data
|
||||
|
||||
**Required Changes:**
|
||||
- Filter projects by ownership + shared access
|
||||
- Separate "My Projects" and "Shared With Me" views
|
||||
- Per-project statistics respect permissions
|
||||
- Add "Collaborators" count to project cards
|
||||
|
||||
**Complexity: LOW** — Mostly UI changes, backend already has project filtering
|
||||
|
||||
### Impact on Database Schema
|
||||
|
||||
**New Tables Required:**
|
||||
```sql
|
||||
users (
|
||||
id INTEGER PRIMARY KEY,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
name TEXT,
|
||||
created_at TIMESTAMP,
|
||||
last_login TIMESTAMP
|
||||
)
|
||||
|
||||
invite_codes (
|
||||
id INTEGER PRIMARY KEY,
|
||||
code TEXT UNIQUE NOT NULL,
|
||||
email TEXT, -- optional: pre-assign to email
|
||||
created_by INTEGER REFERENCES users(id),
|
||||
expires_at TIMESTAMP,
|
||||
used BOOLEAN DEFAULT FALSE,
|
||||
used_by INTEGER REFERENCES users(id),
|
||||
used_at TIMESTAMP
|
||||
)
|
||||
|
||||
project_users (
|
||||
id INTEGER PRIMARY KEY,
|
||||
project_id INTEGER REFERENCES projects(id) ON DELETE CASCADE,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
||||
role TEXT CHECK(role IN ('owner', 'editor', 'viewer')),
|
||||
added_by INTEGER REFERENCES users(id),
|
||||
added_at TIMESTAMP,
|
||||
UNIQUE(project_id, user_id)
|
||||
)
|
||||
```
|
||||
|
||||
**Modified Tables:**
|
||||
```sql
|
||||
projects:
|
||||
+ owner_id INTEGER REFERENCES users(id)
|
||||
+ created_by INTEGER REFERENCES users(id)
|
||||
+ updated_by INTEGER REFERENCES users(id)
|
||||
|
||||
requirement_nodes:
|
||||
+ created_by INTEGER REFERENCES users(id)
|
||||
+ updated_by INTEGER REFERENCES users(id)
|
||||
|
||||
requirement_history:
|
||||
+ user_id INTEGER REFERENCES users(id)
|
||||
|
||||
graph_layouts:
|
||||
+ user_id INTEGER REFERENCES users(id) -- per-user layouts
|
||||
```
|
||||
|
||||
**Migration Strategy:**
|
||||
1. Create users table first
|
||||
2. Create admin user (id=1)
|
||||
3. Add foreign key columns to existing tables (nullable initially)
|
||||
4. Set all existing records to admin user (id=1)
|
||||
5. Make columns NOT NULL after backfill
|
||||
6. Create invite_codes and project_users tables
|
||||
|
||||
**Complexity: MEDIUM** — Careful sequencing required, but no data loss risk if done correctly
|
||||
|
||||
## Sources
|
||||
|
||||
### UX and Design Patterns
|
||||
- [Best Sign Up Flows (2026): 15 UX Examples That Convert](https://www.eleken.co/blog-posts/sign-up-flow)
|
||||
- [Login & Signup UX: The 2025 Guide to Best Practices](https://www.authgear.com/post/login-signup-ux-guide)
|
||||
- [Designing an intuitive user flow for inviting teammates](https://pageflows.com/resources/invite-teammates-user-flow/)
|
||||
|
||||
### RBAC and Authorization
|
||||
- [How to Build a Role-Based Access Control Layer](https://www.osohq.com/learn/rbac-role-based-access-control)
|
||||
- [Role-Based Access Control Best Practices for 2026](https://www.techprescient.com/blogs/role-based-access-control-best-practices/)
|
||||
- [Role-based Access Control: Five Common Authorization Patterns](https://thenewstack.io/role-based-access-control-five-common-authorization-patterns/)
|
||||
- [Best Practices for Multi-Tenant Authorization](https://www.permit.io/blog/best-practices-for-multi-tenant-authorization)
|
||||
|
||||
### Multi-Tenancy Architecture
|
||||
- [The developer's guide to SaaS multi-tenant architecture — WorkOS](https://workos.com/blog/developers-guide-saas-multi-tenant-architecture)
|
||||
- [Multi-Tenant SaaS Architecture: Complete Guide](https://codeboxr.com/multi-tenant-saas-architecture-complete-guide-models-design-patterns-and-scaling-strategy/)
|
||||
- [Data Isolation in Multi-Tenant SaaS](https://redis.io/blog/data-isolation-multi-tenant-saas/)
|
||||
- [Tenant Data Isolation: 5 Patterns That Actually Work](https://propelius.tech/blogs/tenant-data-isolation-patterns-and-anti-patterns/)
|
||||
|
||||
### Security and Data Isolation
|
||||
- [Preventing Cross-Tenant Data Leakage in Multi-Tenant SaaS Systems](https://agnitestudio.com/blog/preventing-cross-tenant-leakage/)
|
||||
- [Multi-Tenant Leakage: When "Row-Level Security" Fails in SaaS](https://medium.com/@instatunnel/multi-tenant-leakage-when-row-level-security-fails-in-saas-da25f40c788c)
|
||||
- [Tenant Isolation in Multi-Tenant Systems](https://securityboulevard.com/2025/12/tenant-isolation-in-multi-tenant-systems-architecture-identity-and-security/)
|
||||
- [Access control vulnerabilities and privilege escalation](https://portswigger.net/web-security/access-control)
|
||||
|
||||
### Audit Trails and Attribution
|
||||
- [What Is an Audit Trail? Definition and Best Practices](https://trullion.com/blog/audit-trail-guide/)
|
||||
- [10 Essential Audit Trail Best Practices for 2026](https://signal.opshub.me/audit-trail-best-practices/)
|
||||
- [Google Workspace Updates: "Show Editors" in Google Docs](https://workspaceupdates.googleblog.com/2021/05/view-more-context-on-google-docs-edits-with-show-editors.html)
|
||||
- [How to See Version History in Google Docs](https://www.geeksforgeeks.org/websites-apps/how-to-see-version-history-in-google-docs/)
|
||||
|
||||
### User Onboarding
|
||||
- [User Onboarding Strategies in a B2B SaaS Application](https://auth0.com/blog/user-onboarding-strategies-b2b-saas/)
|
||||
- [How to onboard invited users and fast-track user engagement](https://www.appcues.com/blog/user-onboarding-strategies-invited-users)
|
||||
- [SaaS Onboarding Flows That Actually Convert in 2026](https://www.saasui.design/blog/saas-onboarding-flows-that-actually-convert-2026)
|
||||
|
||||
### Collaboration Patterns
|
||||
- [Guide to sharing and permissions – Figma Learn](https://help.figma.com/hc/en-us/articles/1500007609322-Guide-to-sharing-and-permissions)
|
||||
- [How to Add Collaborators in GitHub](https://www.rapidnative.com/blogs/how-to-add-collaborators-in-github)
|
||||
|
||||
---
|
||||
*Feature research for: req-planner v2.0 Multi-Tenancy & User Accounts*
|
||||
*Researched: 2026-03-18*
|
||||
443
.planning/research/PITFALLS.md
Normal file
443
.planning/research/PITFALLS.md
Normal file
@@ -0,0 +1,443 @@
|
||||
# Pitfalls Research
|
||||
|
||||
**Domain:** Adding multi-tenancy, user accounts, invite systems, and RBAC to existing single-user FastAPI+SQLite app
|
||||
**Researched:** 2026-03-18
|
||||
**Confidence:** MEDIUM-HIGH
|
||||
|
||||
## Critical Pitfalls
|
||||
|
||||
### Pitfall 1: Forgetting tenant_id Filters in Queries
|
||||
|
||||
**What goes wrong:**
|
||||
Endpoints return all data across all users/tenants instead of just the authenticated user's data. A single missing `.filter(owner_id == current_user.id)` exposes another user's projects, requirements, or edit history.
|
||||
|
||||
**Why it happens:**
|
||||
When migrating from single-user to multi-user, the existing codebase has no tenant scoping. Every SQLAlchemy query was written assuming global access. Developers update auth but forget to audit and update every single query in the application.
|
||||
|
||||
**How to avoid:**
|
||||
- Create base query functions that automatically inject tenant filters
|
||||
- Use SQLAlchemy events or custom query classes to enforce tenant scoping
|
||||
- Never trust frontend filtering — always enforce at backend/database level
|
||||
- Code review checklist: "Does this query filter by user/tenant?"
|
||||
- Write integration tests that attempt cross-tenant access
|
||||
|
||||
**Warning signs:**
|
||||
- "I can see another user's data in the API response"
|
||||
- Tests pass in isolation but fail when multiple users exist
|
||||
- Postman/curl requests with different tokens return overlapping data
|
||||
|
||||
**Phase to address:**
|
||||
Phase 1 (Database Schema & User Model) — Establish the tenant scoping pattern from the start, create helper functions/base classes that enforce it.
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 2: SQLite Foreign Key Constraint Failures During Migration
|
||||
|
||||
**What goes wrong:**
|
||||
Adding `user_id` foreign keys to existing tables (projects, requirements, history, layouts) causes migration failures or silently drops data. SQLite's ALTER TABLE limitations force table recreation, and foreign key constraints can cascade-delete dependent data unexpectedly.
|
||||
|
||||
**Why it happens:**
|
||||
SQLite doesn't support true ALTER TABLE — it creates a new table, copies data, drops the old one. If foreign keys are enabled (`PRAGMA foreign_keys = ON`) during this process, cascade relationships can destroy data. The pragma can't be toggled mid-transaction, and migration tools run in transactions.
|
||||
|
||||
**How to avoid:**
|
||||
- Use Alembic's batch mode for SQLite schema changes
|
||||
- For simple column additions, use `add_column` + `add_index` without foreign key constraints
|
||||
- Enforce user_id constraints at application level for SQLite
|
||||
- Consider backfilling data BEFORE adding constraints
|
||||
- Test migrations on production data copy, not empty database
|
||||
- Have rollback plan and backup before running migration
|
||||
|
||||
**Warning signs:**
|
||||
- Migration succeeds but table is empty
|
||||
- "FOREIGN KEY constraint failed" errors
|
||||
- Dependent tables lose records after migration
|
||||
- `__old_tablename` tables remain in schema
|
||||
|
||||
**Phase to address:**
|
||||
Phase 1 (Database Schema & User Model) — Design migration strategy carefully, test thoroughly with realistic data volumes.
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 3: Existing JWT Tokens Become Invalid
|
||||
|
||||
**What goes wrong:**
|
||||
After deploying user account changes, the single shared password JWT tokens become invalid. All active users are logged out. Worse, the JWT payload structure changes (adding user_id, roles) but the backend doesn't handle old token format, causing 401s or crashes.
|
||||
|
||||
**Why it happens:**
|
||||
Current JWTs encode the shared password authentication, not a user identity. After migration, the auth system expects `user_id` in the token payload. Old tokens lack this field. Token verification fails or returns None for current_user.
|
||||
|
||||
**How to avoid:**
|
||||
- Plan for token format versioning (include `version: 2` in new tokens)
|
||||
- Handle both old and new token formats during transition period
|
||||
- Accept that a forced re-login is sometimes necessary — communicate this
|
||||
- Use a migration window: deploy new auth, give users 24 hours to re-login before breaking old tokens
|
||||
- Consider token migration endpoint: exchange old token for new token
|
||||
|
||||
**Warning signs:**
|
||||
- All users logged out immediately after deployment
|
||||
- "Invalid token" errors spike in logs
|
||||
- `current_user.id` is None/undefined in dependency injection
|
||||
- Frontend can't decode JWT payload
|
||||
|
||||
**Phase to address:**
|
||||
Phase 2 (Authentication & Authorization) — Handle gracefully with versioned tokens or planned re-login window. Document in migration notes.
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 4: Orphaned Data Without Clear Ownership
|
||||
|
||||
**What goes wrong:**
|
||||
Existing projects, requirements, and layouts in the database have no `owner_id`. After adding user accounts, who owns this data? Assigning it all to the first admin is wrong. Leaving it NULL breaks foreign key constraints. Making it globally accessible defeats the purpose of multi-tenancy.
|
||||
|
||||
**Why it happens:**
|
||||
The migration script adds `user_id` columns but doesn't define a strategy for pre-existing data. It's a business logic decision (who should own legacy data?) disguised as a technical problem.
|
||||
|
||||
**How to avoid:**
|
||||
- Decide ownership strategy BEFORE migration:
|
||||
- Option A: Assign all to designated "admin" user (create during migration)
|
||||
- Option B: Create "shared" workspace owned by system user
|
||||
- Option C: Mark legacy data as owned by each user (copy to all users)
|
||||
- Option D: Delete legacy data (if acceptable)
|
||||
- Document the decision and rationale
|
||||
- Make it reversible if possible (backup tables)
|
||||
- Allow users to claim/import legacy data post-migration
|
||||
|
||||
**Warning signs:**
|
||||
- NULL user_id values in production
|
||||
- Foreign key constraint errors after migration
|
||||
- Users complain about missing historical data
|
||||
- Unclear who can edit legacy projects
|
||||
|
||||
**Phase to address:**
|
||||
Phase 1 (Database Schema & User Model) — Define ownership strategy as part of migration plan, not an afterthought.
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 5: Role Explosion and Privilege Creep
|
||||
|
||||
**What goes wrong:**
|
||||
Starting with simple roles (viewer, editor, admin) quickly becomes dozens of overly-specific roles: "ProjectOwnerCanEditGraphsButNotDelete", "EditorForPillarRequirementsOnly". Permission checking logic becomes unmaintainable. Users accumulate permissions as they're granted roles for different projects.
|
||||
|
||||
**Why it happens:**
|
||||
Creating new roles is easy. Removing or merging roles is hard. Each edge case ("what if user needs read on Project A but write on Project B?") tempts adding a new role instead of using proper RBAC patterns. Users change roles but old permissions aren't revoked.
|
||||
|
||||
**How to avoid:**
|
||||
- Start with MINIMAL roles: Owner, Editor, Viewer
|
||||
- Roles are per-project, not global
|
||||
- Don't create roles for every permission combination
|
||||
- Implement permission inheritance: Editor includes Viewer permissions
|
||||
- Use project-level assignments, not feature-level roles
|
||||
- Audit user roles quarterly, revoke unused permissions
|
||||
- Track role changes in audit log
|
||||
|
||||
**Warning signs:**
|
||||
- More roles than users
|
||||
- Role names longer than 3 words
|
||||
- Permission checks require nested if/else trees
|
||||
- Users have 5+ roles assigned
|
||||
- "I don't know what role to give this user"
|
||||
|
||||
**Phase to address:**
|
||||
Phase 3 (Project Ownership & Sharing) — Define minimal, composable role set from the start. Resist adding roles.
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 6: Invite System Security Weaknesses
|
||||
|
||||
**What goes wrong:**
|
||||
Invite codes are predictable (sequential IDs, simple UUIDs), reusable after first use, never expire, or lack the invite token in the registration endpoint. Attackers enumerate invite codes or reuse leaked ones to create unauthorized accounts.
|
||||
|
||||
**Why it happens:**
|
||||
Invite system feels like a simple feature: generate token, send email, validate token on signup. The security edge cases (expiration, single-use, brute force protection) get deprioritized as "nice to have."
|
||||
|
||||
**How to avoid:**
|
||||
- Use cryptographically secure random tokens (secrets.token_urlsafe(32))
|
||||
- Make invites single-use: mark as consumed on successful registration
|
||||
- Set expiration (7-30 days) and enforce it
|
||||
- Rate-limit invite generation and redemption endpoints
|
||||
- Don't expose invite codes in URLs if possible (use POST body)
|
||||
- Log invite creation/redemption for audit trail
|
||||
- Allow invite revocation before use
|
||||
|
||||
**Warning signs:**
|
||||
- Invite codes are short (< 20 chars) or sequential
|
||||
- Same invite works multiple times
|
||||
- Invites never expire
|
||||
- No rate limiting on /register endpoint
|
||||
- Invite validation happens client-side only
|
||||
|
||||
**Phase to address:**
|
||||
Phase 2 (Authentication & Authorization) — Build invite system with security as primary concern, not afterthought.
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 7: Frontend State Management Assumes Single User
|
||||
|
||||
**What goes wrong:**
|
||||
Angular services cache user context globally, localStorage keys don't include user_id, or the app doesn't clear state on logout. User A logs out, User B logs in on the same browser, and sees User A's cached project data.
|
||||
|
||||
**Why it happens:**
|
||||
Single-user app stored state globally (localStorage, service singletons) with no concept of user identity. After adding multi-user, the frontend team updates login/logout but forgets cached state.
|
||||
|
||||
**How to avoid:**
|
||||
- Clear localStorage/sessionStorage on logout
|
||||
- Namespace storage keys by user_id: `user_${userId}_graph_state`
|
||||
- Use request-scoped state, not global singletons where possible
|
||||
- Angular Guards should verify token validity on route change
|
||||
- Test user switching: logout → login as different user → verify no leaked state
|
||||
- Consider using Angular signals for reactive user-scoped state
|
||||
|
||||
**Warning signs:**
|
||||
- User sees previous user's data briefly after login
|
||||
- Editing as User B modifies User A's project
|
||||
- "My dashboard shows someone else's stats"
|
||||
- Graph layouts persist across user accounts
|
||||
|
||||
**Phase to address:**
|
||||
Phase 2 (Authentication & Authorization) — Update frontend state management to be user-aware.
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 8: Cascading Deletes Destroy Shared Data
|
||||
|
||||
**What goes wrong:**
|
||||
User A shares a project with User B. User A deletes their account. CASCADE DELETE removes the project, wiping out User B's work and edit history. Or worse, the delete fails with foreign key violations because of shared data.
|
||||
|
||||
**Why it happens:**
|
||||
Foreign key CASCADE DELETE seems convenient for cleanup. But in multi-tenant systems with sharing, data ownership is not 1:1. A project might have an owner but also collaborators. Deleting the owner shouldn't delete the project.
|
||||
|
||||
**How to avoid:**
|
||||
- Transfer ownership instead of deleting when user has owned projects
|
||||
- Use soft deletes (deleted_at timestamp) instead of CASCADE DELETE
|
||||
- Require explicit "delete all my data" confirmation with warnings
|
||||
- Archive user data instead of deleting (GDPR compliance)
|
||||
- For shared projects: transfer to co-owner or mark as archived
|
||||
- Test deletion scenarios: user with projects, user as collaborator, user as viewer
|
||||
|
||||
**Warning signs:**
|
||||
- User deletion is instant with no warnings
|
||||
- "I lost my project when the owner left the team"
|
||||
- Foreign key constraint errors on user deletion
|
||||
- No way to recover deleted data
|
||||
|
||||
**Phase to address:**
|
||||
Phase 3 (Project Ownership & Sharing) — Design deletion/archival strategy that respects data relationships.
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 9: Missing Edit Attribution and Audit Trail
|
||||
|
||||
**What goes wrong:**
|
||||
Edit history shows changes but not WHO made them. In a collaborative environment, users can't see "User B edited my requirement" or "Who broke the graph?" The blame/credit information is lost.
|
||||
|
||||
**Why it happens:**
|
||||
Single-user app had no concept of attribution — every edit was "me." The existing `requirement_history` table might have timestamps but no `edited_by_user_id` field.
|
||||
|
||||
**How to avoid:**
|
||||
- Add `created_by_user_id` and `modified_by_user_id` to all tables with user-generated content
|
||||
- Update history/audit tables to capture user identity
|
||||
- Display "Last edited by [username] at [time]" in UI
|
||||
- Allow filtering history by user
|
||||
- Consider including user's display name in history JSON (denormalized) to survive user deletions
|
||||
|
||||
**Warning signs:**
|
||||
- Edit history shows timestamps but no usernames
|
||||
- "Who made this change?" has no answer
|
||||
- Audit log exists but lacks user attribution
|
||||
- Collaboration features planned but no user tracking
|
||||
|
||||
**Phase to address:**
|
||||
Phase 1 (Database Schema & User Model) — Add attribution columns from the start.
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 10: Dependency Injection Fails to Scope Tenant Context
|
||||
|
||||
**What goes wrong:**
|
||||
FastAPI dependency that extracts `current_user` is request-scoped, but a service or background task uses a module-level database session. The tenant filter isn't applied, causing data leaks. Or, background tasks receive user objects with secrets instead of just user_id.
|
||||
|
||||
**Why it happens:**
|
||||
Mixing dependency lifespans: per-request (auth), application-lifespan (DB connection pool), and background tasks (out-of-request context). The tenant_id from the JWT is only available in request scope, but business logic runs elsewhere.
|
||||
|
||||
**How to avoid:**
|
||||
- Use per-request DB sessions, not global singletons
|
||||
- FastAPI dependencies: extract user, pass user_id to service layer
|
||||
- Background tasks: pass IDs, not objects; rehydrate via dependencies
|
||||
- Never store user context in global variables
|
||||
- Service layer methods take user_id as explicit parameter
|
||||
- Test with multiple concurrent users to catch scope leaks
|
||||
|
||||
**Warning signs:**
|
||||
- Background task sends email to wrong user
|
||||
- "Current user is None" errors in async tasks
|
||||
- Data visible to wrong user under concurrent load
|
||||
- DB session reused across requests
|
||||
|
||||
**Phase to address:**
|
||||
Phase 2 (Authentication & Authorization) — Establish dependency injection patterns that properly scope user context.
|
||||
|
||||
---
|
||||
|
||||
## Technical Debt Patterns
|
||||
|
||||
Shortcuts that seem reasonable but create long-term problems.
|
||||
|
||||
| Shortcut | Immediate Benefit | Long-term Cost | When Acceptable |
|
||||
|----------|-------------------|----------------|-----------------|
|
||||
| Skip invite system, use direct registration | Faster MVP | No growth control, spam signups, unclear user source | Never for v2 — stated requirement |
|
||||
| Check permissions in frontend only | Less backend code | Trivial to bypass, security theater | Never — always verify backend |
|
||||
| Use global admin toggle instead of RBAC | Simple boolean check | Can't do granular sharing, rewrite needed for collaboration | Never — RBAC is v2 requirement |
|
||||
| Store user_id in localStorage instead of JWT | Simpler token structure | Easily spoofed, XSS vulnerability, defeats authentication | Never — security risk |
|
||||
| Soft-delete users but not their projects | Cleaner user table | Orphaned projects, unclear ownership | Acceptable if projects transferred first |
|
||||
| SQLite without foreign keys | Easier migration | No referential integrity, manual cleanup, data inconsistency | Acceptable for MVP, fix in v2 |
|
||||
| Add `user_id` as nullable column | Non-breaking migration | Half the codebase forgets to filter by it | Only during transition, make NOT NULL ASAP |
|
||||
| Embed role in JWT instead of database | Fewer DB queries | Role changes require re-login, harder to audit | Acceptable if roles rarely change |
|
||||
|
||||
## Integration Gotchas
|
||||
|
||||
Common mistakes when connecting to external services.
|
||||
|
||||
| Integration | Common Mistake | Correct Approach |
|
||||
|-------------|----------------|------------------|
|
||||
| Gitea issue integration | Include user's Gitea token in JWT | Store Gitea token per-user in database, encrypt at rest |
|
||||
| Email (invite system) | Send from user's email address | Use system email (noreply@), track sent_by_user_id separately |
|
||||
| Frontend SPA | Auth state in component, not service | Centralized auth service with RxJS for reactive state |
|
||||
| Background tasks | Pass entire user object to task | Pass user_id only, re-fetch in task context |
|
||||
|
||||
## Performance Traps
|
||||
|
||||
Patterns that work at small scale but fail as usage grows.
|
||||
|
||||
| Trap | Symptoms | Prevention | When It Breaks |
|
||||
|------|----------|------------|----------------|
|
||||
| N+1 queries loading user for each project | 100s of queries for dashboard | Eager load user with project: `.options(joinedload(Project.owner))` | >50 projects per view |
|
||||
| Check permissions on every graph node render | Slow graph rendering | Bulk permission check on page load, cache results | >100 nodes |
|
||||
| No pagination on shared projects list | Slow API calls | Paginate, default limit 50 | User in >20 projects |
|
||||
| Recalculate full audit history on every edit | Edit lag increases | Incremental history, paginate old history | >1000 history entries |
|
||||
| JWT validation queries DB for user on every request | DB bottleneck | Cache user in request context after first validation | >10 requests/second |
|
||||
|
||||
## Security Mistakes
|
||||
|
||||
Domain-specific security issues beyond general web security.
|
||||
|
||||
| Mistake | Risk | Prevention |
|
||||
|---------|------|------------|
|
||||
| Include user_id in URL path (/projects/user/123/items) | Enumeration attack, user ID leak | Use /projects/me or /projects (filter by JWT user) |
|
||||
| Expose user emails in project collaborator API | Privacy leak, phishing target list | Return user IDs + display names only |
|
||||
| Allow project transfer without new owner consent | Unwanted ownership, storage quota bypass | Require new owner to accept transfer |
|
||||
| Reuse password reset tokens as invite tokens | Token confusion, security model mismatch | Separate token types with different validation |
|
||||
| No rate limit on invite endpoint | Spam, resource exhaustion | Rate limit: 10 invites/hour per user |
|
||||
| Allow changing your own role on a project | Privilege escalation | Only project owner can change roles, can't change own |
|
||||
| DELETE /users/:id accepts any user_id | Account takeover, data destruction | Only allow /users/me, verify JWT user matches |
|
||||
|
||||
## UX Pitfalls
|
||||
|
||||
Common user experience mistakes in this domain.
|
||||
|
||||
| Pitfall | User Impact | Better Approach |
|
||||
|---------|-------------|-----------------|
|
||||
| No indication of project owner vs. collaborator | Confusion about edit permissions | Badge/icon showing role, owner prominently displayed |
|
||||
| Can't tell who made an edit | Blame culture, lost context | "Edited by [name] [time ago]" on every change |
|
||||
| Force logout on every deployment | Frustration, work loss | 24-hour token expiry, graceful re-auth |
|
||||
| Invite links expire too quickly (1 day) | Missed invites, support burden | 7-day default, 30-day option |
|
||||
| No way to leave a shared project | Cluttered project list | "Leave project" button for non-owners |
|
||||
| Shared projects look same as owned projects | Can't find my projects | Visual distinction, filter by role |
|
||||
| Delete account is one-click | Accidental deletion, data loss | Require typing account name to confirm |
|
||||
|
||||
## "Looks Done But Isn't" Checklist
|
||||
|
||||
Things that appear complete but are missing critical pieces.
|
||||
|
||||
- [ ] **User registration:** Often missing email verification — verify emails are unique, consider confirmation
|
||||
- [ ] **Invite system:** Often missing invite revocation — verify admin can cancel pending invites
|
||||
- [ ] **RBAC implementation:** Often missing inheritance (editor includes viewer) — verify role permissions compose correctly
|
||||
- [ ] **Multi-user queries:** Often missing tenant filter on JOIN tables — verify cross_pillar_links filtered by project owner
|
||||
- [ ] **Logout:** Often missing token revocation/blacklist — decide if tokens are valid until expiry or revocable
|
||||
- [ ] **Project sharing:** Often missing "can they still edit after shared?" test — verify permissions persist correctly
|
||||
- [ ] **User deletion:** Often missing "what happens to their projects?" flow — verify ownership transfer or archival
|
||||
- [ ] **Edit history:** Often missing user attribution on old edits — verify migration backfills created_by/modified_by
|
||||
- [ ] **Role changes:** Often missing "does it take effect immediately?" — verify role updates don't require re-login
|
||||
- [ ] **Password reset:** Often missing rate limiting — verify can't spam reset emails
|
||||
- [ ] **Frontend state:** Often missing user-scoped caching — verify localStorage cleared on logout
|
||||
- [ ] **Audit log:** Often missing sensitive action logging (role changes, deletions) — verify admin actions tracked
|
||||
|
||||
## Recovery Strategies
|
||||
|
||||
When pitfalls occur despite prevention, how to recover.
|
||||
|
||||
| Pitfall | Recovery Cost | Recovery Steps |
|
||||
|---------|---------------|----------------|
|
||||
| Deployed without tenant filters | HIGH | Emergency rollback, add filters, test with multiple users, redeploy |
|
||||
| Migration destroyed data | HIGH | Restore from backup, fix migration script, re-run with copy of production data |
|
||||
| Role explosion (30+ roles) | MEDIUM | Audit actual role usage, merge similar roles, migrate users to simplified roles |
|
||||
| Invite codes leaked/enumerated | MEDIUM | Revoke all pending invites, generate new codes, notify legitimate users |
|
||||
| JWT tokens invalidated prematurely | LOW | Communicate forced re-login, monitor for issues, extend token expiry next time |
|
||||
| User deleted with active projects | MEDIUM | Soft-delete user instead, transfer projects to team lead, restore from backup if recent |
|
||||
| Frontend leaks previous user data | LOW | Force cache clear (version localStorage key), deploy fix, notify affected users |
|
||||
| Cascading delete destroyed shared data | HIGH | Restore from backup, change CASCADE to RESTRICT, add ownership transfer logic |
|
||||
|
||||
## Pitfall-to-Phase Mapping
|
||||
|
||||
How roadmap phases should address these pitfalls.
|
||||
|
||||
| Pitfall | Prevention Phase | Verification |
|
||||
|---------|------------------|--------------|
|
||||
| Forgetting tenant_id filters | Phase 1 (Database Schema) | Integration test: User A can't see User B's data |
|
||||
| SQLite migration failures | Phase 1 (Database Schema) | Test migration on production data copy, verify no data loss |
|
||||
| Invalid JWT tokens | Phase 2 (Auth & Authorization) | Test old token format handled gracefully |
|
||||
| Orphaned data without owner | Phase 1 (Database Schema) | Verify all rows have non-null user_id after migration |
|
||||
| Role explosion | Phase 3 (Project Ownership) | Verify <= 5 roles exist, permission matrix simple |
|
||||
| Invite system security | Phase 2 (Auth & Authorization) | Security audit: tokens secure, single-use, expire |
|
||||
| Frontend state leaks | Phase 2 (Auth & Authorization) | Test user switching, verify state cleared |
|
||||
| Cascading deletes | Phase 3 (Project Ownership) | Test user deletion with owned/shared projects |
|
||||
| Missing edit attribution | Phase 1 (Database Schema) | Verify edited_by_user_id on all history entries |
|
||||
| Tenant context leaks | Phase 2 (Auth & Authorization) | Load test with concurrent users, verify isolation |
|
||||
|
||||
## Sources
|
||||
|
||||
**Multi-Tenancy Patterns:**
|
||||
- [MergeBoard - Multitenancy with FastAPI, SQLAlchemy and PostgreSQL](https://mergeboard.com/blog/6-multitenancy-fastapi-sqlalchemy-postgresql/)
|
||||
- [Multi-Tenant Architecture with FastAPI: Design Patterns and Pitfalls](https://medium.com/@koushiksathish3/multi-tenant-architecture-with-fastapi-design-patterns-and-pitfalls-aa3f9e75bf8c)
|
||||
- [Python FastAPI Postgres SqlAlchemy Row Level Security Multitenancy](https://adityamattos.com/multi-tenancy-in-python-fastapi-and-sqlalchemy-using-postgres-row-level-security)
|
||||
|
||||
**SQLite Limitations:**
|
||||
- [The SQLite Renaissance: Why the World's Most Deployed Database Is Taking Over Production in 2026](https://dev.to/pockit_tools/the-sqlite-renaissance-why-the-worlds-most-deployed-database-is-taking-over-production-in-2026-3jcc)
|
||||
- [A mere add_foreign_key can wipe out your whole Rails+SQLite production table](https://kyrylo.org/software/2025/09/27/a-mere-add-foreign-key-can-wipe-out-your-whole-rails-sqlite-production-table.html)
|
||||
- [Fixing ALTER TABLE errors with Flask-Migrate and SQLite](https://blog.miguelgrinberg.com/post/fixing-alter-table-errors-with-flask-migrate-and-sqlite)
|
||||
|
||||
**JWT Security:**
|
||||
- [JWT vs PASETO vs Branca — The Future of Secure Tokens in 2026](https://mojoauth.com/blog/jwt-vs-paseto-vs-branca-the-future-of-secure-tokens-in-2026)
|
||||
- [Beyond the Secret: The Silent Risks of JWT and Machine Identity](https://medium.com/@instatunnel/beyond-the-secret-the-silent-risks-of-jwt-and-machine-identity-49bea4aa4547)
|
||||
- [JWT Security Best Practices:Checklist for APIs](https://curity.io/resources/learn/jwt-best-practices/)
|
||||
|
||||
**RBAC Implementation:**
|
||||
- [6 Common Role Based Access Control (RBAC) Implementation Pitfalls](https://idenhaus.com/rbac-implementation-pitfalls/)
|
||||
- [Role-Based Access Control Best Practices for 2026](https://www.techprescient.com/blogs/role-based-access-control-best-practices/)
|
||||
- [Common Challenges in Role-Based Access Control Implementation](https://censinet.com/perspectives/common-challenges-role-based-access-control-implementation)
|
||||
|
||||
**Invite System Security:**
|
||||
- [How can I enhance the privacy and security of my invite-only event?](https://help.rsvpify.com/en/articles/5517672-how-can-i-enhance-the-privacy-and-security-of-my-invite-only-event)
|
||||
|
||||
**Data Migration:**
|
||||
- [Top data migration mistakes to avoid in 2026](https://www.kellton.com/kellton-tech-blog/top-data-migration-mistakes-to-avoid)
|
||||
- [Top 10 Data Migration Risks and How to Avoid Them in 2026](https://medium.com/@kanerika/top-10-data-migration-risks-and-how-to-avoid-them-in-2026-fb5dc93c12f5)
|
||||
|
||||
**FastAPI Dependency Injection:**
|
||||
- [Dependency Injection in FastAPI: 2026 Playbook for Modular, Testable APIs](https://thelinuxcode.com/dependency-injection-in-fastapi-2026-playbook-for-modular-testable-apis/)
|
||||
- [How to Use Dependency Injection in FastAPI](https://oneuptime.com/blog/post/2026-02-02-fastapi-dependency-injection/view)
|
||||
|
||||
**Cascading Deletes:**
|
||||
- [Why on_delete=models.CASCADE Doesn't Work as Expected in Django's Multi-Tenant Architecture](https://medium.com/@kevinrawal/why-on-delete-models-cascade-doesnt-work-as-expected-in-django-s-multi-tenant-architecture-45aac3faad3d)
|
||||
- [Foreign Keys vs Performance (Part 3): The CASCADE DELETE Story](https://medium.com/@thyagodoliveiraperez/foreign-keys-vs-performance-part-3-the-cascade-delete-story-aac5cabd843b)
|
||||
|
||||
**Permission Inheritance:**
|
||||
- [SharePoint: Breaking and Managing Permission Inheritance](https://td.usnh.edu/TDClient/60/Portal/KB/ArticleDet?ID=3173)
|
||||
- [Top 5 Common SharePoint Permissions Mistakes and How to Fix Them](https://lightningtools.com/blog/top-5-common-sharepoint-permissions-mistakes-and-how-to-fix-them/)
|
||||
|
||||
**Angular State Management:**
|
||||
- [Angular State Management for 2025](https://nx.dev/blog/angular-state-management-2025)
|
||||
- [7 Angular 2026 Predictions That Could Redefine Frontend Architecture](https://dev.to/karol_modelski/7-angular-2026-predictions-that-could-redefine-frontend-architecture-e5n)
|
||||
|
||||
---
|
||||
*Pitfalls research for: req-planner v2.0 multi-tenancy migration*
|
||||
*Researched: 2026-03-18*
|
||||
302
.planning/research/STACK.md
Normal file
302
.planning/research/STACK.md
Normal file
@@ -0,0 +1,302 @@
|
||||
# Stack Research: Multi-Tenancy & User Accounts
|
||||
|
||||
**Domain:** Requirements management tool with multi-user support
|
||||
**Researched:** 2026-03-18
|
||||
**Confidence:** HIGH
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Adding user accounts, invite-only registration, RBAC, and tenant isolation to your existing FastAPI + SQLAlchemy + SQLite stack requires minimal new dependencies. The core stack remains unchanged - only authentication primitives need upgrading from shared password to individual accounts.
|
||||
|
||||
**Key additions:**
|
||||
- **pwdlib** with Argon2 for password hashing (replaces bcrypt/passlib)
|
||||
- **email-validator** for email validation in Pydantic models
|
||||
- **secrets** module (stdlib) for invite token generation
|
||||
- **Tenant isolation** via SQLAlchemy query filters (no library needed)
|
||||
|
||||
**No changes needed:** Your existing FastAPI, SQLAlchemy 2.0, SQLite, PyJWT, and Angular 17 stack remain unchanged.
|
||||
|
||||
## Recommended Stack Additions
|
||||
|
||||
### Authentication & Security
|
||||
|
||||
| Technology | Version | Purpose | Why Recommended |
|
||||
|------------|---------|---------|-----------------|
|
||||
| pwdlib | 0.4.0+ | Password hashing with Argon2 | Modern replacement for passlib (deprecated in Python 3.13). FastAPI official docs now recommend pwdlib. Argon2 is GPU-resistant and OWASP-recommended for 2026. |
|
||||
| email-validator | 2.2.0+ | Email validation for user registration | Required by Pydantic's EmailStr type. Standard for email validation with deliverability checks. |
|
||||
| secrets | stdlib | Secure invite token generation | Python standard library (3.6+). Cryptographically secure token generation with 256-bit randomness. Use `secrets.token_urlsafe(32)` for invite links. |
|
||||
|
||||
### NO Library Needed (Use Patterns)
|
||||
|
||||
| Capability | Implementation | Why No Library |
|
||||
|------------|----------------|----------------|
|
||||
| Multi-tenancy | SQLAlchemy query filters with `user_id` FK | Simple pattern - add `user_id` column to all tables, filter queries by current user. PostgreSQL Row-Level Security (RLS) not available in SQLite. |
|
||||
| RBAC | Enum + dependency injection | Three roles (viewer/editor/owner) fit in simple Enum. Use FastAPI Depends() to check role on routes. Libraries like fastapi-user-auth add unnecessary complexity for 3 roles. |
|
||||
| Edit attribution | Add `modified_by_user_id` column | You already track edit history - just add user reference to existing `requirement_history` table. |
|
||||
| Email sending | Python `smtplib` (stdlib) | Built-in library sufficient for invite emails. Use FastAPI BackgroundTasks for async sending. No need for Celery yet. |
|
||||
|
||||
## Unchanged Core Stack
|
||||
|
||||
Your existing stack is already optimal for this use case:
|
||||
|
||||
| Technology | Version | Status | Notes |
|
||||
|------------|---------|--------|-------|
|
||||
| FastAPI | Current | ✓ Keep | Dependency injection perfect for RBAC |
|
||||
| SQLAlchemy | 2.0+ | ✓ Keep | Event listeners support tenant filtering |
|
||||
| SQLite | 3.x | ✓ Keep | WAL mode supports concurrent readers + 1 writer (sufficient for small team) |
|
||||
| PyJWT | 2.x | ✓ Keep | Already used for JWT - extend payload to include user_id and role |
|
||||
| Angular | 17 | ✓ Keep | Frontend unchanged - JWT payload has new fields |
|
||||
| Pydantic | 2.x | ✓ Keep | Add email-validator extra for EmailStr |
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Backend additions only
|
||||
pip install "pwdlib[argon2]>=0.4.0"
|
||||
pip install "pydantic[email]>=2.0"
|
||||
|
||||
# No changes to frontend
|
||||
```
|
||||
|
||||
## Integration Patterns
|
||||
|
||||
### 1. Password Hashing (pwdlib + Argon2)
|
||||
|
||||
```python
|
||||
from pwdlib import PasswordHash
|
||||
|
||||
# Initialize once at startup
|
||||
password_hash = PasswordHash.recommended()
|
||||
|
||||
# Hash password on registration
|
||||
hashed = password_hash.hash("user_password")
|
||||
|
||||
# Verify on login
|
||||
is_valid = password_hash.verify("user_password", hashed)
|
||||
```
|
||||
|
||||
**Why this pattern:** FastAPI docs migrated from passlib to pwdlib in 2026. Argon2 is more secure than bcrypt and future-proof.
|
||||
|
||||
### 2. Email Validation (Pydantic EmailStr)
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, EmailStr
|
||||
|
||||
class UserCreate(BaseModel):
|
||||
email: EmailStr # Automatically validated
|
||||
password: str
|
||||
```
|
||||
|
||||
**Why this pattern:** Pydantic's EmailStr uses email-validator under the hood. No manual validation needed.
|
||||
|
||||
### 3. Invite Token Generation (secrets)
|
||||
|
||||
```python
|
||||
import secrets
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# Generate invite token
|
||||
invite_token = secrets.token_urlsafe(32) # 256 bits
|
||||
expires_at = datetime.utcnow() + timedelta(hours=24)
|
||||
|
||||
# Store in database
|
||||
invite = Invite(token=invite_token, email=email, expires_at=expires_at)
|
||||
```
|
||||
|
||||
**Why this pattern:** Standard library, cryptographically secure, URL-safe for email links.
|
||||
|
||||
### 4. Multi-Tenancy (SQLAlchemy Filters)
|
||||
|
||||
```python
|
||||
# Add to all models
|
||||
class RequirementNode(Base):
|
||||
__tablename__ = "requirement_nodes"
|
||||
id = Column(Integer, primary_key=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
||||
# ... other columns
|
||||
|
||||
# Filter queries by current user
|
||||
def get_requirements(db: Session, current_user: User):
|
||||
return db.query(RequirementNode).filter(
|
||||
RequirementNode.user_id == current_user.id
|
||||
).all()
|
||||
```
|
||||
|
||||
**Why this pattern:** SQLite doesn't support PostgreSQL's Row-Level Security. Manual filtering is explicit and debuggable. Add SQLAlchemy event listener for automatic filtering if needed later.
|
||||
|
||||
### 5. RBAC (Enum + Dependency Injection)
|
||||
|
||||
```python
|
||||
from enum import Enum
|
||||
from fastapi import Depends, HTTPException
|
||||
|
||||
class Role(str, Enum):
|
||||
VIEWER = "viewer"
|
||||
EDITOR = "editor"
|
||||
OWNER = "owner"
|
||||
|
||||
def require_role(required: Role):
|
||||
def check_role(current_user: User = Depends(get_current_user)):
|
||||
if current_user.role not in [required, Role.OWNER]: # Owner has all permissions
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
return current_user
|
||||
return check_role
|
||||
|
||||
# Use in routes
|
||||
@app.delete("/requirements/{id}")
|
||||
async def delete_requirement(
|
||||
id: int,
|
||||
current_user: User = Depends(require_role(Role.EDITOR))
|
||||
):
|
||||
...
|
||||
```
|
||||
|
||||
**Why this pattern:** FastAPI's Depends() makes role checks declarative. No library needed for 3 roles.
|
||||
|
||||
### 6. JWT Payload Extension
|
||||
|
||||
```python
|
||||
# Extend existing JWT to include user info
|
||||
def create_access_token(user: User) -> str:
|
||||
payload = {
|
||||
"sub": str(user.id),
|
||||
"email": user.email,
|
||||
"role": user.role.value,
|
||||
"exp": datetime.utcnow() + timedelta(hours=24)
|
||||
}
|
||||
return jwt.encode(payload, SECRET_KEY, algorithm="HS256")
|
||||
```
|
||||
|
||||
**Why this pattern:** Minimal change to existing JWT implementation. Frontend receives role for UI decisions.
|
||||
|
||||
## What NOT to Add
|
||||
|
||||
| Avoid | Why | Use Instead |
|
||||
|-------|-----|-------------|
|
||||
| FastAPI-Users | Over-engineered for your use case. Brings OAuth, 2FA, cookie auth you don't need. | Custom auth with pwdlib + PyJWT (you already have JWT logic) |
|
||||
| Alembic | SQLite migrations are low-risk. You're self-hosted single-user currently. | Direct SQL migration scripts for v2 upgrade |
|
||||
| Celery / Redis | Overkill for invite emails. Adds deployment complexity. | FastAPI BackgroundTasks + smtplib |
|
||||
| SQLAlchemy-History | You already have `requirement_history` table with snapshots. | Add `modified_by_user_id` FK to existing table |
|
||||
| PostgreSQL RLS libraries | SQLite doesn't support Row-Level Security. | Manual SQLAlchemy query filters |
|
||||
| Casbin (RBAC library) | Policy engine for 100+ permission combinations. You have 3 roles. | Python Enum + FastAPI Depends() |
|
||||
|
||||
## Database Schema Changes
|
||||
|
||||
### New Tables
|
||||
|
||||
```sql
|
||||
-- Users table
|
||||
CREATE TABLE users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
hashed_password TEXT NOT NULL,
|
||||
role TEXT NOT NULL DEFAULT 'viewer',
|
||||
is_active BOOLEAN NOT NULL DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Invite tokens table
|
||||
CREATE TABLE invites (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
token TEXT UNIQUE NOT NULL,
|
||||
email TEXT NOT NULL,
|
||||
role TEXT NOT NULL DEFAULT 'viewer',
|
||||
expires_at TIMESTAMP NOT NULL,
|
||||
used_at TIMESTAMP,
|
||||
created_by INTEGER REFERENCES users(id)
|
||||
);
|
||||
```
|
||||
|
||||
### Modified Tables
|
||||
|
||||
Add `user_id` FK to existing tables:
|
||||
- `requirement_nodes` - owner of requirement
|
||||
- `cross_pillar_links` - who created link
|
||||
- `projects` - project owner
|
||||
- `requirement_history` - who made the change (`modified_by_user_id`)
|
||||
- `graph_layouts` - who saved layout
|
||||
|
||||
**Migration strategy:** Existing data gets assigned to "system" user (id=1) created during migration.
|
||||
|
||||
## SQLite Concurrency Considerations
|
||||
|
||||
### WAL Mode (Already Recommended)
|
||||
|
||||
```python
|
||||
# Ensure WAL mode is enabled
|
||||
engine = create_engine(
|
||||
"sqlite:///data/planner.db",
|
||||
connect_args={"check_same_thread": False},
|
||||
echo=False
|
||||
)
|
||||
with engine.connect() as conn:
|
||||
conn.execute(text("PRAGMA journal_mode=WAL"))
|
||||
```
|
||||
|
||||
**Capabilities:**
|
||||
- Multiple concurrent readers (unlimited)
|
||||
- One writer at a time (sufficient for small team collaboration)
|
||||
- Writers don't block readers
|
||||
|
||||
**Limitations:**
|
||||
- No true concurrent writes (BEGIN CONCURRENT is experimental, not production-ready)
|
||||
- Single writer serialization means write-heavy workloads may see SQLITE_BUSY errors
|
||||
|
||||
**When to migrate to PostgreSQL:** If you exceed ~10 concurrent users with frequent writes (requirements updates). For now, SQLite + WAL handles your use case.
|
||||
|
||||
## Version Compatibility Matrix
|
||||
|
||||
| Package | Version | Compatible With | Notes |
|
||||
|---------|---------|-----------------|-------|
|
||||
| pwdlib | 0.4.0+ | Python 3.9+ | Requires `pwdlib[argon2]` extra for Argon2 support |
|
||||
| email-validator | 2.2.0+ | Pydantic 2.x | Required by `pydantic[email]` extra |
|
||||
| PyJWT | 2.8.0+ | FastAPI 0.100+ | Already in your stack - no changes needed |
|
||||
| SQLAlchemy | 2.0.0+ | Python 3.12+ | Already in your stack - no changes needed |
|
||||
| Pydantic | 2.0+ | FastAPI 0.100+ | Requires email-validator for EmailStr |
|
||||
|
||||
## Migration Checklist
|
||||
|
||||
- [ ] Install pwdlib and email-validator
|
||||
- [ ] Create `users` and `invites` tables
|
||||
- [ ] Add `user_id` FK to all existing tables
|
||||
- [ ] Migrate existing data to "system" user
|
||||
- [ ] Replace shared password logic with user registration/login
|
||||
- [ ] Add role-based route decorators
|
||||
- [ ] Update JWT payload to include user_id and role
|
||||
- [ ] Add query filters for tenant isolation
|
||||
- [ ] Update frontend to handle user-specific data
|
||||
- [ ] Test multi-user scenarios with WAL mode
|
||||
|
||||
## Security Checklist
|
||||
|
||||
- [ ] Use `pwdlib.PasswordHash.recommended()` for Argon2 defaults
|
||||
- [ ] Validate all user inputs with Pydantic (EmailStr, password length)
|
||||
- [ ] Use `secrets.token_urlsafe(32)` for invite tokens (256-bit)
|
||||
- [ ] Set invite token expiration (24 hours recommended)
|
||||
- [ ] Filter ALL queries by `user_id` (tenant isolation)
|
||||
- [ ] Use FastAPI Depends() for role checks on protected routes
|
||||
- [ ] Set JWT expiration (24 hours recommended)
|
||||
- [ ] Enable SQLite WAL mode for concurrent access
|
||||
- [ ] Rate limit registration and login endpoints (future: middleware)
|
||||
- [ ] Use HTTPS in production (Caddy/nginx reverse proxy)
|
||||
|
||||
## Sources
|
||||
|
||||
**HIGH Confidence (Official Documentation):**
|
||||
- [FastAPI OAuth2 with JWT](https://fastapi.tiangolo.com/tutorial/security/oauth2-jwt/) - Official security tutorial
|
||||
- [pwdlib introduction](https://github.com/frankie567/pwdlib/discussions/1) - Modern password hashing
|
||||
- [Python secrets module](https://docs.python.org/3/library/secrets.html) - Secure token generation
|
||||
- [Pydantic email validation](https://docs.pydantic.dev/latest/install/) - EmailStr documentation
|
||||
- [SQLite WAL mode](https://www.sqlite.org/wal.html) - Official SQLite concurrency documentation
|
||||
|
||||
**MEDIUM Confidence (Industry Best Practices 2026):**
|
||||
- [TestDriven.io FastAPI JWT Auth](https://testdriven.io/blog/fastapi-jwt-auth/) - Production patterns
|
||||
- [FastAPI RBAC Tutorial](https://www.permit.io/blog/fastapi-rbac-full-implementation-tutorial) - Role-based access control
|
||||
- [Tenant Isolation with SQLAlchemy](https://personal-web-9c834.web.app/blog/pg-tenant-isolation/) - Multi-tenancy patterns
|
||||
- [The New Way To Generate Secure Tokens](https://blog.miguelgrinberg.com/post/the-new-way-to-generate-secure-tokens-in-python) - Token generation security
|
||||
- [FastAPI Dependency Injection](https://fastapi.tiangolo.com/tutorial/security/get-current-user/) - Current user pattern
|
||||
|
||||
---
|
||||
*Stack research for: req-planner v2.0 Multi-Tenancy & User Accounts*
|
||||
*Researched: 2026-03-18*
|
||||
*Confidence: HIGH - All recommendations verified against official docs and 2026 best practices*
|
||||
321
.planning/research/SUMMARY.md
Normal file
321
.planning/research/SUMMARY.md
Normal file
@@ -0,0 +1,321 @@
|
||||
# Project Research Summary
|
||||
|
||||
**Project:** req-planner v2.0 - Multi-Tenancy & User Accounts
|
||||
**Domain:** Requirements management tool with collaborative features
|
||||
**Researched:** 2026-03-18
|
||||
**Confidence:** HIGH
|
||||
|
||||
## Executive Summary
|
||||
|
||||
The req-planner v2.0 upgrade adds user accounts, invite-only registration, role-based access control, and project sharing to an existing FastAPI + SQLAlchemy + SQLite stack. Research shows this is a **well-documented migration pattern** with established best practices from 2026. The core architecture requires minimal new dependencies (pwdlib, email-validator) and leverages existing JWT infrastructure. The main risk is **tenant isolation failures** — forgetting to filter queries by user context is the #1 cause of data leaks in multi-tenant systems. The recommended approach uses shared-schema multi-tenancy with explicit query filtering, three-tier RBAC (owner/editor/viewer), and refresh token rotation for security.
|
||||
|
||||
**Critical Success Factor:** Build tenant-scoped query patterns from day one. Every query must filter by user ownership or project membership. SQLite's limitations (no Row-Level Security) require manual enforcement, making systematic testing essential. The good news: this migration is additive — existing single-user functionality remains intact during the transition, allowing incremental deployment.
|
||||
|
||||
**Key Recommendation:** Structure the roadmap in dependency order: (1) Authentication foundation with user accounts, (2) Tenant isolation with project ownership, (3) RBAC for sharing, (4) Polish with attribution and invites. This sequence minimizes risk by validating tenant isolation before adding sharing complexity.
|
||||
|
||||
## Key Findings
|
||||
|
||||
### Recommended Stack
|
||||
|
||||
The existing stack (FastAPI, SQLAlchemy 2.0, SQLite, PyJWT, Angular 17) is optimal for this use case. **Only two new dependencies required:**
|
||||
|
||||
**Core additions:**
|
||||
- **pwdlib (Argon2)** — Password hashing, replaces deprecated passlib. FastAPI official docs recommend it as of 2026. Argon2 is GPU-resistant and OWASP-approved.
|
||||
- **email-validator** — Required for Pydantic EmailStr validation. Standard for registration forms.
|
||||
- **secrets (stdlib)** — Cryptographically secure invite token generation using `token_urlsafe(32)` for 256-bit randomness.
|
||||
|
||||
**No library needed (use patterns instead):**
|
||||
- Multi-tenancy via SQLAlchemy query filters with `user_id` FK (SQLite doesn't support PostgreSQL Row-Level Security)
|
||||
- RBAC via Enum + FastAPI dependency injection (three roles don't justify a library like Casbin)
|
||||
- Edit attribution via `modified_by_user_id` column on existing `requirement_history` table
|
||||
- Email sending via stdlib `smtplib` + FastAPI BackgroundTasks (Celery is overkill)
|
||||
|
||||
**Critical decision:** Stick with SQLite until 100+ concurrent users or 10GB+ database. WAL mode supports unlimited concurrent readers + 1 writer, sufficient for small team collaboration. Migration to PostgreSQL adds operational complexity — delay until necessary.
|
||||
|
||||
### Expected Features
|
||||
|
||||
**Must have for v2.0 launch (table stakes):**
|
||||
- Individual user accounts with email/password authentication
|
||||
- Invite-only registration with admin-generated tokens (security requirement for self-hosted)
|
||||
- Per-user project ownership with workspace isolation (core multi-tenancy)
|
||||
- Three-tier RBAC (owner/editor/viewer) on projects
|
||||
- Edit attribution in history showing who made each change
|
||||
- Project sharing UI with role assignment
|
||||
- Password reset flow (expected security hygiene)
|
||||
- Permission enforcement on both backend and frontend
|
||||
|
||||
**Should have for v2.x (competitive differentiators):**
|
||||
- Activity feeds per project ("what changed since I last logged in")
|
||||
- Workspace-level dashboard aggregating user's projects
|
||||
- Anonymous viewer links for sharing with clients without accounts
|
||||
- Granular audit trail with per-edit rollback capabilities
|
||||
|
||||
**Defer to v3+ (future consideration):**
|
||||
- Fine-grained permission customization beyond three tiers
|
||||
- Inherited permissions on requirement hierarchy (complex to implement correctly)
|
||||
- Organization/team hierarchies for enterprise customers
|
||||
- OAuth/SSO providers
|
||||
- Real-time collaborative editing (major architectural change)
|
||||
|
||||
**Anti-features (commonly requested but problematic):**
|
||||
- Real-time collaborative editing: Massive architectural lift, requires WebSockets and operational transforms
|
||||
- Organization hierarchies: Dramatically increases permission complexity
|
||||
- Social login (OAuth): Complicates self-hosted deployments
|
||||
- Field-level permissions: Explosion of permission checks, confusing UX
|
||||
|
||||
### Architecture Approach
|
||||
|
||||
The recommended architecture uses **four layers:** Authentication (user identity via JWT with refresh tokens), Authorization (RBAC via junction tables), Tenant Isolation (explicit query filtering by user context), and Migration Strategy (backwards-compatible schema changes). The key pattern is **shared-schema multi-tenancy** — single database with `user_id` columns and explicit filters, rather than database-per-tenant (which performs worse on SQLite's single-writer model).
|
||||
|
||||
**Major components:**
|
||||
|
||||
1. **Authentication Layer** — Extends existing JWT system with user accounts, adds refresh tokens (15min access, 30-day refresh), invite token validation, password reset flow. Uses pwdlib for Argon2 hashing.
|
||||
|
||||
2. **User Context Injection** — FastAPI dependency `get_current_user()` extracts user_id from JWT, verifies user active, returns immutable `UserContext(user_id, email, is_admin)`. All routes receive current_user as dependency.
|
||||
|
||||
3. **Tenant-Scoped Queries** — Manual filtering pattern: `query(Project).filter((Project.owner_id == user_id) | (Project.members.any(user_id)))`. Explicit over implicit (no hidden session middleware). SQLite lacks Row-Level Security.
|
||||
|
||||
4. **RBAC Layer** — `ProjectMember` junction table with role enum (owner/editor/viewer). Permission checks via `require_project_access(project_id, required_role)` dependency. Role hierarchy encoded: owner > editor > viewer.
|
||||
|
||||
5. **Migration Layer** — Add nullable `user_id` columns, seed default admin user, backfill existing data to admin, make columns NOT NULL. Dual auth during transition (old password endpoint + new email/password endpoint).
|
||||
|
||||
**Critical pattern:** Never trust client-provided user_id. Always extract from server-verified JWT. Never use global query filters in middleware (breaks admin operations, hard to debug). Never store refresh tokens in localStorage (XSS risk).
|
||||
|
||||
### Critical Pitfalls
|
||||
|
||||
1. **Forgetting tenant filters in queries** — #1 cause of data leaks. Every SQLAlchemy query needs `.filter(user_id == current_user.id)` or project membership check. Missing a single filter exposes another user's data. **Mitigation:** Create base query functions enforcing tenant scoping, use integration tests with multiple users, code review checklist: "Does this query filter by tenant?"
|
||||
|
||||
2. **SQLite foreign key constraint failures during migration** — Adding `user_id` FKs to existing tables causes migration failures or silent data loss. SQLite's ALTER TABLE limitations force table recreation with CASCADE risks. **Mitigation:** Use Alembic batch mode, test migration on production data copy, have rollback plan, consider enforcing constraints at application level initially.
|
||||
|
||||
3. **Existing JWT tokens become invalid** — Deploying new auth invalidates all current sessions. Token payload structure changes (adding user_id) breaks old token format. **Mitigation:** Version tokens (`version: 2`), handle both formats during transition, communicate forced re-login window, consider token migration endpoint.
|
||||
|
||||
4. **Orphaned data without clear ownership** — Existing projects/requirements have no `owner_id`. Assigning all to admin is wrong, leaving NULL breaks constraints. **Mitigation:** Decide ownership strategy before migration (assign to admin, create shared workspace, copy to all users, or delete legacy data), document rationale, make reversible.
|
||||
|
||||
5. **Role explosion and privilege creep** — Starting with 3 roles (viewer/editor/owner) becomes dozens of overly-specific roles. Users accumulate permissions. **Mitigation:** Resist adding roles, use project-level assignments not feature-level, implement permission inheritance, audit roles quarterly, track role changes in audit log.
|
||||
|
||||
## Implications for Roadmap
|
||||
|
||||
Based on research, the roadmap should follow a **dependency-ordered sequence** that validates tenant isolation before adding sharing complexity. The architecture naturally divides into four milestones that can be developed incrementally with clear validation gates.
|
||||
|
||||
### Phase 1: Authentication Foundation
|
||||
**Rationale:** User accounts are the prerequisite for all multi-tenancy features. Build the identity layer before touching data isolation.
|
||||
|
||||
**Delivers:** Users can register with invite tokens, login with email/password, receive JWT access + refresh tokens, and maintain sessions across browser sessions.
|
||||
|
||||
**Addresses (from FEATURES.md):**
|
||||
- Individual user accounts with email/password authentication
|
||||
- Invite-only registration (security requirement)
|
||||
- Session management with refresh tokens
|
||||
- Password reset flow
|
||||
|
||||
**Avoids (from PITFALLS.md):**
|
||||
- Pitfall #3: JWT token invalidation (handle old token format gracefully)
|
||||
- Pitfall #6: Invite system security weaknesses (cryptographically secure tokens, single-use, expiration)
|
||||
|
||||
**Implementation notes:**
|
||||
- Create `users`, `invite_tokens`, `refresh_tokens` tables
|
||||
- Implement pwdlib password hashing
|
||||
- Add `/auth/register`, `/auth/login`, `/auth/refresh`, `/auth/logout` endpoints
|
||||
- Update `get_current_user()` dependency to extract from JWT
|
||||
- Seed default admin user in migration
|
||||
|
||||
**Research flag:** No additional research needed — well-documented pattern in FastAPI ecosystem.
|
||||
|
||||
### Phase 2: Tenant Isolation & Project Ownership
|
||||
**Rationale:** Establish data separation before adding collaboration. Validate users can only see their own data before allowing sharing.
|
||||
|
||||
**Delivers:** Projects belong to specific users. Users see only projects they own. All queries filtered by user context. Existing data migrated to admin user.
|
||||
|
||||
**Addresses (from FEATURES.md):**
|
||||
- Per-user project ownership with workspace isolation
|
||||
- Basic permission enforcement (backend + frontend)
|
||||
|
||||
**Avoids (from PITFALLS.md):**
|
||||
- Pitfall #1: Forgetting tenant filters (THE critical pitfall)
|
||||
- Pitfall #4: Orphaned data without ownership
|
||||
- Pitfall #10: Dependency injection scope leaks
|
||||
|
||||
**Implementation notes:**
|
||||
- Add `owner_id` column to `projects` (nullable initially)
|
||||
- Add `created_by_id`, `updated_by_id` to `requirement_nodes`
|
||||
- Update all queries to filter by ownership
|
||||
- Migration: Assign existing projects to admin (id=1)
|
||||
- Make `owner_id` NOT NULL after backfill
|
||||
- Integration tests: User A cannot access User B's projects
|
||||
|
||||
**Research flag:** No additional research needed — standard SQLAlchemy patterns.
|
||||
|
||||
### Phase 3: RBAC & Project Sharing
|
||||
**Rationale:** Now that tenant isolation is validated, enable controlled sharing with role-based permissions.
|
||||
|
||||
**Delivers:** Project owners can invite collaborators with viewer/editor/owner roles. Permission checks enforce RBAC on all operations. UI shows user's role per project.
|
||||
|
||||
**Addresses (from FEATURES.md):**
|
||||
- Three-tier RBAC (owner/editor/viewer)
|
||||
- Project sharing UI with role assignment
|
||||
- Edit attribution in history (who made each change)
|
||||
|
||||
**Avoids (from PITFALLS.md):**
|
||||
- Pitfall #5: Role explosion (stick to 3 roles)
|
||||
- Pitfall #8: Cascading deletes destroying shared data
|
||||
- Pitfall #9: Missing edit attribution
|
||||
|
||||
**Implementation notes:**
|
||||
- Create `project_members` junction table with role enum
|
||||
- Add `POST /projects/{id}/members`, `GET /projects/{id}/members`, `DELETE /projects/{id}/members/{user_id}`
|
||||
- Implement `require_project_access(project_id, required_role)` dependency
|
||||
- Update all requirement routes to check RBAC
|
||||
- Add `user_id` to `requirement_history` for attribution
|
||||
- Update UI to display editor names and role badges
|
||||
|
||||
**Research flag:** RBAC patterns well-documented. May need phase-specific research for permission UI patterns if complex interactions emerge.
|
||||
|
||||
### Phase 4: Polish & User Management
|
||||
**Rationale:** With core multi-tenancy working, add user-facing features that improve collaboration experience.
|
||||
|
||||
**Delivers:** Users can invite others to the platform. Admins can manage invites. History shows detailed edit attribution. Enhanced security with rate limiting.
|
||||
|
||||
**Addresses (from FEATURES.md):**
|
||||
- Enhanced invite system (user-driven invites, not just admin)
|
||||
- Improved edit attribution in history UI
|
||||
- User profile management
|
||||
- Security hardening (rate limiting, token cleanup)
|
||||
|
||||
**Avoids (from PITFALLS.md):**
|
||||
- Pitfall #7: Frontend state management assumes single user
|
||||
- Performance traps (N+1 queries, pagination)
|
||||
|
||||
**Implementation notes:**
|
||||
- Add `POST /auth/invites` (users can invite others)
|
||||
- Add `GET /auth/invites` (list sent invites)
|
||||
- Integrate email sending (SMTP + BackgroundTasks)
|
||||
- Add rate limiting to auth endpoints (slowapi)
|
||||
- Scheduled task for token cleanup
|
||||
- Update history UI with user avatars and "edited by" attribution
|
||||
|
||||
**Research flag:** Email integration may need focused research on SMTP providers, deliverability, and template design if building production-grade invite emails.
|
||||
|
||||
### Phase Ordering Rationale
|
||||
|
||||
**Why this order:**
|
||||
1. **Authentication before isolation** — Can't isolate data without user identity
|
||||
2. **Isolation before sharing** — Must prove single-user works before adding multi-user
|
||||
3. **RBAC after ownership** — Roles are meaningless without established ownership model
|
||||
4. **Polish last** — User-facing improvements after core functionality validated
|
||||
|
||||
**How this avoids pitfalls:**
|
||||
- Validates tenant isolation (Pitfall #1) in Phase 2 before sharing in Phase 3
|
||||
- Addresses data migration (Pitfall #4) early in Phase 2 when schema is simple
|
||||
- Prevents role explosion (Pitfall #5) by defining minimal roles in Phase 3
|
||||
- Frontend state management (Pitfall #7) addressed progressively across phases
|
||||
|
||||
**Dependency chain:**
|
||||
- Phase 2 depends on Phase 1 (needs user_id from auth)
|
||||
- Phase 3 depends on Phase 2 (needs project ownership to share)
|
||||
- Phase 4 depends on Phase 3 (invites need roles to assign)
|
||||
|
||||
**Incremental deployment:**
|
||||
Each phase is independently deployable. Phase 1 can launch without breaking existing single-user mode. Phase 2 migrates data but doesn't force collaboration. Phase 3 adds sharing as opt-in. Phase 4 enhances without breaking.
|
||||
|
||||
### Research Flags
|
||||
|
||||
**Phases with standard patterns (skip `/gsd:research-phase`):**
|
||||
- **Phase 1 (Authentication):** FastAPI JWT auth is well-documented, official examples exist, pwdlib docs comprehensive
|
||||
- **Phase 2 (Tenant Isolation):** SQLAlchemy query filtering is standard ORM practice, multi-tenancy patterns mature
|
||||
- **Phase 3 (RBAC):** Three-role model is trivial, junction table pattern universal, permission checks straightforward
|
||||
|
||||
**Phases likely needing deeper research:**
|
||||
- **Phase 4 (Email Integration):** If building production-grade invite emails, may need research on:
|
||||
- SMTP provider selection (SendGrid vs Mailgun vs AWS SES)
|
||||
- Email deliverability (SPF, DKIM, DMARC configuration)
|
||||
- Template design and testing frameworks
|
||||
- Invite link generation security (signed URLs, token rotation)
|
||||
|
||||
However, for MVP, stdlib `smtplib` is sufficient — defer detailed research until email features become critical path.
|
||||
|
||||
**Overall assessment:** This is a low-research-intensity roadmap. All core patterns are well-established. Only Phase 4 email features might warrant focused research if expanding beyond basic SMTP.
|
||||
|
||||
## Confidence Assessment
|
||||
|
||||
| Area | Confidence | Notes |
|
||||
|------|------------|-------|
|
||||
| Stack | **HIGH** | Recommendations verified against FastAPI official docs (2026), pwdlib is official replacement for passlib, SQLAlchemy 2.0 patterns stable. No experimental dependencies. |
|
||||
| Features | **HIGH** | Feature categorization based on industry-standard collaboration tools (GitHub, Notion, Figma). Table stakes vs differentiators validated across multiple UX sources. Anti-features identified from documented failures. |
|
||||
| Architecture | **HIGH** | Multi-tenancy patterns sourced from official guides, 2026 FastAPI tutorials, SQLAlchemy events documented. Dependency injection is core FastAPI feature. Refresh token rotation is OAuth 2.0 standard. |
|
||||
| Pitfalls | **MEDIUM-HIGH** | Top pitfalls (#1 tenant filters, #2 SQLite migrations, #3 JWT invalidation) extensively documented across sources. Some pitfalls (#7 frontend state, #10 context scope) inferred from general patterns rather than domain-specific sources. |
|
||||
|
||||
**Overall confidence:** **HIGH**
|
||||
|
||||
The research is built on official documentation (FastAPI, SQLAlchemy, SQLite, Pydantic), authoritative 2026 guides, and multiple corroborating sources. Stack recommendations align with maintainer guidance. Architecture patterns are production-proven. Pitfalls are sourced from documented post-mortems and security analyses.
|
||||
|
||||
### Gaps to Address
|
||||
|
||||
**Minor gaps that need attention during implementation:**
|
||||
|
||||
1. **Email deliverability details:** Research covered email *sending* (smtplib) but not email *deliverability* (SPF/DKIM/DMARC setup). If invite emails must reach inbox (not spam), Phase 4 may need focused research on email infrastructure.
|
||||
- **How to handle:** Start with simple SMTP in development, use established email service (SendGrid/Mailgun) in production, defer deliverability research until user reports spam issues.
|
||||
|
||||
2. **SQLite concurrency limits:** Research confirms WAL mode supports multiple readers + 1 writer, but exact concurrency threshold (when to migrate to PostgreSQL) is deployment-specific.
|
||||
- **How to handle:** Monitor write contention in production (SQLITE_BUSY errors), plan PostgreSQL migration at 50+ concurrent writers or 10GB database, not earlier.
|
||||
|
||||
3. **Cascading delete policy:** Research identifies the risk (Pitfall #8) but ownership transfer strategy is product decision, not technical.
|
||||
- **How to handle:** Decide in Phase 3 planning: transfer to co-owner, mark as archived, or require explicit reassignment before user deletion.
|
||||
|
||||
4. **Frontend state management details:** Research identifies risk (Pitfall #7) but Angular 17 specifics (Signals vs Services for user context) are implementation details.
|
||||
- **How to handle:** Validate during Phase 2 frontend work, test user switching scenarios, follow Angular 17 best practices for auth guards.
|
||||
|
||||
5. **Password strength requirements:** Research recommends enforcement but exact policy (length, complexity) is security policy decision.
|
||||
- **How to handle:** Implement OWASP-recommended baseline (8 chars, mixed case, numbers) in Phase 1, allow customization in Phase 4 if self-hosted users request it.
|
||||
|
||||
**No critical blockers identified.** All gaps are solvable during implementation without requiring additional research phases.
|
||||
|
||||
## Sources
|
||||
|
||||
### Primary (HIGH confidence)
|
||||
|
||||
**Official Documentation:**
|
||||
- [FastAPI OAuth2 with JWT](https://fastapi.tiangolo.com/tutorial/security/oauth2-jwt/) — Official security tutorial for authentication patterns
|
||||
- [pwdlib introduction](https://github.com/frankie567/pwdlib/discussions/1) — Modern password hashing, FastAPI's recommended replacement for passlib
|
||||
- [Python secrets module](https://docs.python.org/3/library/secrets.html) — Secure token generation (stdlib)
|
||||
- [Pydantic email validation](https://docs.pydantic.dev/latest/install/) — EmailStr documentation
|
||||
- [SQLite WAL mode](https://www.sqlite.org/wal.html) — Official SQLite concurrency documentation
|
||||
|
||||
**2026 Technical Guides:**
|
||||
- [Introduction to Multi-Tenant Design with FastAPI](https://blog.greeden.me/en/2026/03/10/introduction-to-multi-tenant-design-with-fastapi-practical-patterns-for-tenant-isolation-authorization-database-strategy-and-audit-logs/) — Comprehensive 2026 multi-tenancy patterns
|
||||
- [Building a Modern User Permission Management System with FastAPI, SQLAlchemy 2.0](https://dev.to/mochafreddo/building-a-modern-user-permission-management-system-with-fastapi-sqlalchemy-and-mariadb-5fp1) — RBAC implementation guide
|
||||
- [JWT in FastAPI, the Secure Way (Refresh Tokens Explained)](https://medium.com/@jagan_reddy/jwt-in-fastapi-the-secure-way-refresh-tokens-explained-f7d2d17b1d17) — Jan 2026 refresh token pattern
|
||||
|
||||
### Secondary (MEDIUM confidence)
|
||||
|
||||
**Multi-Tenancy & Architecture:**
|
||||
- [MergeBoard: Multitenancy with FastAPI, SQLAlchemy and PostgreSQL](https://mergeboard.com/blog/6-multitenancy-fastapi-sqlalchemy-postgresql/) — Production patterns
|
||||
- [GitHub Discussion: How to implement Multi tenancy in FastAPI](https://github.com/fastapi/fastapi/discussions/6056) — Community approaches
|
||||
- [The developer's guide to SaaS multi-tenant architecture — WorkOS](https://workos.com/blog/developers-guide-saas-multi-tenant-architecture) — Tenant isolation strategies
|
||||
|
||||
**RBAC & Authorization:**
|
||||
- [FastAPI RBAC - Full Implementation Tutorial](https://www.permit.io/blog/fastapi-rbac-full-implementation-tutorial) — Permission layer patterns
|
||||
- [How to Build a Role-Based Access Control Layer](https://www.osohq.com/learn/rbac-role-based-access-control) — RBAC design principles
|
||||
- [Role-Based Access Control Best Practices for 2026](https://www.techprescient.com/blogs/role-based-access-control-best-practices/) — Industry best practices
|
||||
|
||||
**Security & Data Isolation:**
|
||||
- [Preventing Cross-Tenant Data Leakage in Multi-Tenant SaaS Systems](https://agnitestudio.com/blog/preventing-cross-tenant-leakage/) — Security patterns
|
||||
- [JWT Security Best Practices: Checklist for APIs](https://curity.io/resources/learn/jwt-best-practices/) — Token security
|
||||
- [6 Common Role Based Access Control (RBAC) Implementation Pitfalls](https://idenhaus.com/rbac-implementation-pitfalls/) — Documented failures
|
||||
|
||||
**UX & Collaboration Patterns:**
|
||||
- [Guide to sharing and permissions – Figma Learn](https://help.figma.com/hc/en-us/articles/1500007609322-Guide-to-sharing-and-permissions) — Collaboration UX patterns
|
||||
- [User Onboarding Strategies in a B2B SaaS Application](https://auth0.com/blog/user-onboarding-strategies-b2b-saas/) — Invite-only registration patterns
|
||||
- [Google Workspace Updates: "Show Editors" in Google Docs](https://workspaceupdates.googleblog.com/2021/05/view-more-context-on-google-docs-edits-with-show-editors.html) — Edit attribution patterns
|
||||
|
||||
### Tertiary (LOW confidence, needs validation)
|
||||
|
||||
**SQLite Limitations:**
|
||||
- [A mere add_foreign_key can wipe out your whole Rails+SQLite production table](https://kyrylo.org/software/2025/09/27/a-mere-add-foreign-key-can-wipe-out-your-whole-rails-sqlite-production-table.html) — Migration risks (Rails-specific but conceptually applicable)
|
||||
- [High Performance SQLite: Multi-tenancy](https://highperformancesqlite.com/watch/multi-tenancy) — Performance patterns (video, not peer-reviewed)
|
||||
|
||||
---
|
||||
|
||||
**Research completed:** 2026-03-18
|
||||
**Ready for roadmap creation:** YES
|
||||
|
||||
All four research files synthesized. Recommended phase structure validated against dependencies, pitfalls, and architecture patterns. No critical blockers identified. Roadmap can proceed with high confidence.
|
||||
Reference in New Issue
Block a user