ubiquitous-invention/plans/Plan-multitenant-saas-hardening/Plan-multitenant-saas-hardening.md

71 lines
3.2 KiB
Markdown
Raw Normal View History

plans: scaffold daily-driver-finish, saas-hardening, agent-coordination Three new plan trees that fill in the gaps surfaced during repo review. Together they map out what remains between the current scaffold-with-stubs state and a daily-usable, multitenant, agent-coordinated app. * Plan-daily-driver-finish (P0): turn stubs into real data. Five tasks covering the lint/shared-types breakage, hardcoded dashboard mocks, AI-page setTimeout placeholder, post-signin landing decision, and a cross-browser collab smoke test against the deployed Hocuspocus instance. * Plan-multitenant-saas-hardening (P1): everything multitenant needs beyond what Plan-multitenant-cursor-sync already covers. Invites and role management, soft-delete + append-only audit log, rate limits on the auth + mutation hot paths, and a Vitest + GitHub Actions test foundation so PRs can't ship red. * Plan-agent-coordination (P2): the layer that makes a Task-*.md runnable, not just readable. Adds workflow_prompt with task -> epic -> plan inheritance, an agent_runs table for auditable sessions, and two new MCP tools (claim_task / complete_task) that replace the freeform update_object composition agents do today. Includes an intentionally-deferred Epic-optional-orchestrator that captures the Symphony-shaped runner as a decision point rather than an immediate build. Each task is bead-scale (one focused Cursor session) with explicit in-scope, out-of-scope, and anti-goal sections so a future agent can pick up a single Task-*.md and start without scrollback context. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 00:52:22 -04:00
---
kind: plan
slug: multitenant-saas-hardening
title: Multitenant SaaS hardening — invites, audit, guardrails, test foundation
status: ready
priority: P1
tenant_id: global
cursor_plan_id: null
updated_at: "2026-06-01"
---
# Plan overview
`Plan-multitenant-cursor-sync` covers the *Cursor sync layer* slice of multitenancy. This plan covers the rest: the operational hardening needed before Echodo can be safely used as a real multitenant SaaS rather than a homelab single-tenant app.
The unifying theme is **tenant blast-radius** — what's the worst thing a tenant (or a bug operating on behalf of a tenant) can do, and how do we cap it? Today the answer is "we don't really know," because there's no invite flow, no audit log, no rate limit, and no test runner.
## Objectives and success criteria
- A workspace owner can invite, role-change, and remove members from the app UI.
- Every mutation against a tenant-scoped table is recorded in an append-only audit log with actor user id, action, and target.
- Workspaces support soft-delete (archive) without losing data, and restore.
- Public mutation endpoints have rate limits that survive a brute-force attempt against credentials sign-in.
- `pnpm test` runs Vitest across the workspaces that have tests, and a minimal GitHub Actions CI runs lint + type-check + test on PRs.
## Scope and boundaries
**In scope**
- Invite + role management (DB + UI + tRPC).
- Audit log table, write path, minimal read view.
- Workspace soft-delete and restore.
- Rate-limiting middleware on critical mutations.
- Vitest bootstrap + GitHub Actions CI.
**Out of scope (this plan)**
- Billing / Stripe (a separate plan when there's actual revenue to charge for).
- SSO group-based role mapping (Authentik groups → workspace roles). Defer until manual invites are in.
- Full SOC2 audit posture. The audit log here is a foundation, not a compliance artifact.
## Cross-team collaborators
| Name / group | Role |
|--------------|------|
| Solo operator (you) | Everything. |
## Epics
| Epic | Link |
|------|------|
| Tenant lifecycle | `./Epic-tenant-lifecycle/Epic-tenant-lifecycle.md` |
| Test foundation | `./Epic-test-foundation/Epic-test-foundation.md` |
## Metrics and milestones
| Milestone | Target | Metric |
|-----------|--------|--------|
| Invites shipped | M1 | Owner can invite a new email and the recipient can accept and land in the workspace. |
| Audit visibility | M2 | Every mutation on `objects` / `workspaces` / `workspace_members` produces an audit row with `actor_user_id`. |
| Guardrails live | M3 | Brute-forcing credentials sign-in is rate-limited at the route handler. |
| CI green on PRs | M4 | A PR with `pnpm lint && pnpm type-check && pnpm test` failing cannot merge. |
## Risks and mitigations
| Risk | Mitigation |
|------|------------|
| Soft-delete semantics get confused with hard-delete | One column (`archived_at` timestamp); every workspace-scoped query adds `archived_at IS NULL` by default. No new "is_deleted" boolean. |
| Audit log becomes a write-amplification problem | Single append-only table with a partial index on `workspace_id, created_at`. Don't denormalize until you measure. |
| Vitest adoption stalls | Start with `packages/database` and `packages/shared` only. Don't try to test React components in v1. |