ubiquitous-invention/plans/Plan-multitenant-saas-hardening/Plan-multitenant-saas-hardening.md
Randall Stillwell 778fe1d321 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-01 23:52:22 -05:00

70 lines
3.2 KiB
Markdown

---
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. |