Add spec pack

This commit is contained in:
Weei Han
2026-07-30 14:35:52 +08:00
commit 641fbdadeb
9 changed files with 803 additions and 0 deletions
+84
View File
@@ -0,0 +1,84 @@
# 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