85 lines
4.7 KiB
Markdown
85 lines
4.7 KiB
Markdown
# System Architecture
|
|
## SOP Governance Tool — Stage 1
|
|
|
|
## 1. Stack (fixed — do not substitute)
|
|
|
|
| Layer | Choice | Why |
|
|
|---|---|---|
|
|
| Frontend + backend | **Next.js 14+ (App Router, TypeScript)** | One codebase for pages and API routes; industry standard |
|
|
| Database / Auth / Storage | **Supabase** (Postgres + Auth + Storage + RLS) | Hands a beginner the hard parts; RLS becomes Stage-2 tenant isolation |
|
|
| Hosting | **Vercel** | Push-to-deploy; free tier sufficient |
|
|
| AI | **Anthropic API** (claude-sonnet for drafting/translation) | Called ONLY from server routes |
|
|
| Styling | **Tailwind CSS + shadcn/ui** | Fast, consistent, well-documented |
|
|
|
|
## 2. High-Level Diagram
|
|
|
|
```
|
|
[Browser: Admin/Editor desktop UI · Staff mobile UI]
|
|
│ (HTTPS)
|
|
▼
|
|
[Vercel: Next.js App]
|
|
├─ Server Components / Pages ──────────► reads via Supabase client (RLS enforced)
|
|
├─ Route Handlers /api/* ─────────────► writes + workflow logic (service role where needed)
|
|
│ └─ /api/ai/* ───────────────────► Anthropic API (server-side key)
|
|
▼
|
|
[Supabase]
|
|
├─ Postgres (all tables, RLS policies)
|
|
├─ Auth (email/password, JWT with role + org_id claims)
|
|
└─ Storage (bucket: sop-photos)
|
|
```
|
|
|
|
## 3. Key Architectural Decisions
|
|
|
|
**AD-1: org_id everywhere, single org today.**
|
|
Every table carries `org_id`. Stage 1 seeds exactly one org row and all data references it. RLS policies already filter by org_id. Stage 2 = add org signup + billing; data layer barely changes. Do NOT skip this "because we only have one company."
|
|
|
|
**AD-2: Roles live in a `profiles` table, mirrored into the JWT.**
|
|
`profiles(id → auth.users.id, org_id, role, department_id, preferred_language, full_name, active)`. A Postgres function copies role/org_id into JWT claims (custom access token hook) so RLS can check them cheaply.
|
|
|
|
**AD-3: Reads through RLS; privileged writes through route handlers.**
|
|
Simple reads (my SOPs, dashboard queries) use the anon/user Supabase client and rely on RLS. Workflow mutations (submit/approve/publish/assign/acknowledge) go through `/api/*` route handlers that (a) re-verify the session and role, (b) run the transition logic in one transaction via the service-role client, (c) write the audit_log row in the same transaction. This keeps state-machine rules in ONE place.
|
|
|
|
**AD-4: Published content is a frozen snapshot.**
|
|
`sop_versions.content` stores the full JSON of all sections at publish time. The editor works on the draft row; publish copies it into an immutable version row. Acknowledgements and translations reference the version, never the live draft.
|
|
|
|
**AD-5: AI calls are stateless, logged, and cheap-by-default.**
|
|
One route per capability (`/api/ai/draft`, `/api/ai/translate`). Each call: validate role + rate limit → build prompt from templates in `lib/ai/prompts.ts` → call Anthropic → validate/parse JSON output (retry once on parse failure) → log tokens to `ai_log`. Use a smaller/cheaper model for translation, a stronger one for drafting.
|
|
|
|
**AD-6: No client-side secrets, no localStorage for auth.**
|
|
Supabase handles session cookies. `ANTHROPIC_API_KEY` and `SUPABASE_SERVICE_ROLE_KEY` exist only in Vercel env vars and are only imported in server files.
|
|
|
|
## 4. Directory Layout
|
|
|
|
```
|
|
/app
|
|
/(auth)/login
|
|
/(admin)/dashboard /sops /sops/[id]/edit /approvals /users /audit
|
|
/(staff)/my-sops /my-sops/[id]
|
|
/api/sops/... /api/ai/... /api/ack ...
|
|
/components (ui/ = shadcn, sop/ = editor pieces, staff/ = mobile viewer)
|
|
/lib
|
|
supabase/ (browser client, server client, service client)
|
|
ai/ (anthropic client, prompts, parsers)
|
|
workflow.ts (status machine: canTransition(), nextVersion())
|
|
audit.ts (writeAudit())
|
|
/db
|
|
schema.sql policies.sql seed.sql
|
|
/docs (this spec pack)
|
|
CLAUDE.md
|
|
```
|
|
|
|
## 5. Environments
|
|
|
|
- **Local:** `supabase start` (local Postgres) or a free "dev" Supabase project; `.env.local` for keys.
|
|
- **Production:** Supabase project in **Singapore region** (PDPA/data-residency story), Vercel production.
|
|
- One branch (`main`) is fine for a solo dev; Vercel preview deploys per push give you a test URL.
|
|
|
|
## 6. Security Checklist (must all be true before real staff data goes in)
|
|
|
|
- [ ] RLS enabled on every table; anonymous access returns zero rows
|
|
- [ ] Staff role cannot read drafts, other users' profiles beyond names, or the audit log
|
|
- [ ] All /api routes verify session server-side (no trust of client-sent role)
|
|
- [ ] Storage bucket: authenticated read, Editor+ write, no public listing
|
|
- [ ] Service-role key and Anthropic key absent from client bundle (`next build` + search)
|
|
- [ ] Supabase daily backups enabled (dashboard toggle) and a restore has been tested once
|