Your incidents, resolved in seconds.
SentinelAI is an autonomous incident-response copilot. When a production alert fires, Sentinel investigates on its own: it scans your GitHub commit history to find the likely bad change, searches your uploaded runbooks for the right fix using semantic search, reasons over everything with Gemini, and posts a concise, actionable incident report straight to your Slack channel — no human trigger, no waiting for on-call to wake up.
The Slack alert looks like this:
🚨 Production Incident
Likely Cause: Deployment #418
Confidence: 87%
Most Relevant Commit: Fix authentication middleware
Affected Services: API Gateway, Authentication
Suggested Runbook: Authentication Outage Recovery
Next Steps: Rollback deployment, Restart auth service
Alert ─▶ FastAPI backend
│
├─▶ GitHub API → recent commits + deployments (listed in prompt — not vector search)
├─▶ ChromaDB → vector search over runbooks only (Gemini embeddings)
├─▶ Gemini Flash → picks likely bad commit + remediation from combined context
└─▶ Slack Webhook → formatted incident report in your channel
The frontend (React) lets users sign up with a username, create and join projects (GitHub repo, Slack webhook, runbooks — validated against GitHub and Slack before save), collaborate via roles and invitations, manage account settings, and run owner/admin-controlled incident workflows with assignment and fix approval. Supabase handles authentication, profiles, projects, team membership, invitations, persisted incidents, and runbook storage. The backend (FastAPI, Docker) runs the AI incident pipeline and persists ChromaDB vectors on a dedicated volume.
Recommended production layout: frontend on Vercel, backend on Render (or any Docker host with a persistent volume). The backend is not a good fit for serverless-only deploys because ChromaDB needs durable disk.
SentinelAI uses two different Gemini models, both via the same
GEMINI_API_KEY:
| Model (default) | Role | Used for |
|---|---|---|
gemini-embedding-001 |
Embeddings | Indexing runbooks in ChromaDB, semantic runbook search, and upload validation (checking the four required sections) |
gemini-2.5-flash |
Generation | Structured incident analysis (likely cause, confidence, next steps, Slack report) |
Override either default in backend/.env.local:
GEMINI_EMBEDDING_MODEL=gemini-embedding-001
GEMINI_MODEL=gemini-2.5-flashRAG means the LLM retrieves relevant documents from your own data, augments the prompt with that context, then generates an answer grounded in what it found.
In SentinelAI, RAG applies to runbooks only. Commits and deployments are not embedded or vector-searched — they are fetched from the GitHub REST API and passed to Gemini Flash as a plain list for the model to reason over.
The pipeline lives in backend/app/services/incident_service.py:
flowchart TB
subgraph index [1. Index — runbooks only]
RB[Runbook .md / .pdf] --> VAL[Semantic validation]
VAL --> EMB[Gemini embeddings]
EMB --> CHROMA[(ChromaDB volume)]
end
subgraph incident [2–4. At incident time]
ALERT[Alert text] --> VSEARCH["Vector search (top 3 runbooks)"]
CHROMA --> VSEARCH
GH["GitHub REST API"] --> RECENT["Recent commits (up to 30) + deployments (up to 5)"]
VSEARCH --> PROMPT[Build prompt]
RECENT --> PROMPT
PROMPT --> GEN["Gemini Flash → IncidentAnalysis JSON"]
GEN --> SLACK[Slack report]
end
| Data | How it is fetched | Vector search? | How the “best” item is chosen |
|---|---|---|---|
| Runbooks | Indexed in ChromaDB at upload | Yes | Alert text is embedded; top 3 runbooks by similarity (chroma_service.search_runbooks) |
| Commits | GitHub API — most recent N commits (github_service.list_recent_commits, default 30) |
No | Gemini Flash reads commit messages in the prompt and sets most_relevant_commit |
| Deployments | GitHub API — recent deployments (list_deployments, limit 5) |
No | Passed as context; helps Gemini tie the alert to a deployment |
So: vector search finds relevant runbooks; the likely bad commit is inferred by Gemini from the recent commit list, not from embedding similarity.
When you upload a runbook or trigger analysis on a project:
- The backend reads the file (
.mdor.pdfviarunbook_validation_service). - Validation — before indexing, Gemini embeddings check that the document semantically covers all four required sections (not just exact headings).
- Embedding — the full runbook text is embedded with
gemini-embedding-001and stored in ChromaDB (chroma_service.add_runbook). Vectors persist on the Dockerchroma-datavolume.
This is the “knowledge base” RAG retrieves from later.
On Analyze Incident (POST /api/incidents/analyze), the backend builds a
search query from the alert description (or deployment id, or a default phrase)
and calls chroma_service.search_runbooks(query, n=3):
- The query is embedded with the same embedding model used at index time.
- ChromaDB returns the top 3 runbooks by vector similarity (closest meaning, not keyword match).
Retrieved runbooks are combined with GitHub context (recent commits and
deployments) into a single prompt in gemini_service._build_prompt:
| Context added to the prompt | Source |
|---|---|
| Incident signal (alert text, optional deployment) | User / monitoring |
| Recent commits | GitHub API |
| Recent deployments | GitHub API |
| Candidate runbook titles | Top ChromaDB matches from step 2 |
Semantic search runs against the full runbook text in ChromaDB; the
generation step passes the titles of the best matches so Gemini can pick a
suggested_runbook and stay focused.
Gemini Flash (gemini-2.5-flash) receives the augmented prompt and returns
structured JSON mapped to IncidentAnalysis:
most_relevant_commit— chosen by the model from the listed commit messages (not vector search)suggested_runbook— chosen from the vector-retrieved runbook titles- Plus likely cause, confidence, affected services, and next steps
That output is shown in the UI and optionally posted to Slack. If the webhook is
invalid or revoked, analysis and incident save still succeed; Slack failure is
returned as slack_error and shown as a warning on the project page.
Why RAG for runbooks? Without retrieval, the model would invent runbook names
and fixes. Vector search grounds suggested_runbook in documents you uploaded.
Commit blame stays a separate step: recent history from GitHub + LLM reasoning.
SentinelAI/
├── src/ # Frontend — React + Vite + Tailwind (dark/green theme)
│ ├── components/
│ │ ├── AppHeader.tsx # Dashboard header (username, notifications, Settings, Sign out)
│ │ ├── AuthLayout.tsx # Sign-in / sign-up shell
│ │ ├── DeleteAccountModal.tsx
│ │ ├── DeleteProjectModal.tsx
│ │ ├── LeaveProjectModal.tsx
│ │ ├── TransferOwnershipModal.tsx
│ │ ├── NotificationBell.tsx # Pending project invitations (accept / decline)
│ │ ├── ProjectTeamModal.tsx # Team & permissions modal
│ │ ├── ProjectTeamSection.tsx
│ │ ├── ProjectEditRequestsSection.tsx
│ │ ├── ResolveIncidentModal.tsx
│ │ ├── PasswordRequirements.tsx
│ │ └── … # Navbar, Hero, Features, HowItWorks, etc.
│ ├── context/
│ │ ├── AuthContext.tsx # Supabase session + profile provider
│ │ └── PendingInvitationsContext.tsx
│ ├── lib/
│ │ ├── supabase.ts # Supabase client (auth, DB, storage)
│ │ ├── api.ts # Client for the FastAPI backend
│ │ ├── projectTeam.ts # Roles, invites, incidents, fixes, edit requests
│ │ ├── profile.ts # Profile helpers + login username lookup RPCs
│ │ ├── passwordValidation.ts
│ │ └── usernameValidation.ts
│ ├── pages/
│ │ ├── Landing.tsx
│ │ ├── Login.tsx # Username or email + password
│ │ ├── SignUp.tsx # Username, email, password + strength meter
│ │ ├── Dashboard.tsx # Owned + shared projects, role badges, delete (owner)
│ │ ├── Settings.tsx # Change username / email / password, delete account
│ │ ├── AddProject.tsx # Create & edit projects + runbook upload / edit requests
│ │ └── ProjectDetail.tsx # Incidents, team header actions, fix reviews
│ ├── App.tsx
│ └── index.css
│
├── backend/ # FastAPI incident-response service (Docker)
│ ├── app/
│ │ ├── main.py # App factory, CORS (localhost + FRONTEND_URL + Vercel)
│ │ ├── config.py
│ │ ├── models/schemas.py
│ │ ├── services/
│ │ │ ├── github_service.py
│ │ │ ├── slack_service.py
│ │ │ ├── chroma_service.py
│ │ │ ├── gemini_service.py
│ │ │ ├── runbook_validation_service.py # Semantic section checks + PDF parsing
│ │ │ └── incident_service.py
│ │ └── api/routes/ # health, github, slack, runbooks, incidents
│ ├── requirements.txt
│ ├── Dockerfile
│ ├── docker-compose.yml # Backend + chroma-data volume
│ └── .env.example
│
├── supabase/
│ └── schema.sql # profiles, projects, teams, incidents, RLS, RPCs
│
├── index.html
├── package.json
├── vite.config.ts
├── .env.example
└── README.md
# Not in the repo — created at runtime:
# chroma-data (Docker volume) # ChromaDB vectors at /app/data/chroma in the container
Backend runs in Docker. The FastAPI app (ChromaDB, Gemini, GitHub, Slack) is packaged into one image. Vector data lives in a separate named volume (
chroma-data), not in the repo or image.
- Docker + Docker Compose — required for the backend (see §3)
- Node.js 18+ and npm (frontend)
- A Supabase account (free tier is fine)
- A Gemini API key — https://aistudio.google.com/apikey
- A GitHub personal access token (repo read)
- A Slack incoming webhook — create an app at https://api.slack.com/apps
Supabase provides authentication, user profiles, the projects database, and runbook file storage.
-
Go to https://supabase.com/dashboard and create a New project.
-
Open SQL Editor → New query, paste the entire contents of
supabase/schema.sql, and click Run. The file is idempotent (safe to re-run) and includes:Section What it sets up 1–2 profilestable (with username), Row Level Security3 Triggers: create profilesrow only after email confirmation4 Backfill confirmed users; remove unconfirmed profile rows 5 projectstable + RLS6 runbooksprivate storage bucket + per-user policies7 project_members,project_invitations,incidents,incident_fixes+ team/incident RPCs8 project_edit_requests, ownership transfer, leave project, role management9 Security hardening — stricter RLS on projects/project_members,can_access_project, andSECURITY DEFINERRPCs so invited users can list and open shared projects (see Row Level Security below)10 Incident assignment workflow — assigned_toon incidents,incident_assignment_requests, admin-only incident creation, assign / request / review RPCs (see Incidents & fixes)11 Fix review feedback — review_incident_fixrequires feedback when declining / requesting changes12 Postmortem metadata — runbook_matches,postmortem_posted,assigned_at,mark_postmortem_postedRPCRPCs Auth: resolve_login_email,is_username_available,update_username,delete_own_accountTeams: invite_project_member,accept_project_invitation,get_my_pending_invitations,transfer_project_ownership,leave_project, …Access: get_my_projects(),get_accessible_project(uuid),get_my_project_role(uuid)Important: If you set up the database before team/invite fixes landed, re-run the full
supabase/schema.sqlin the SQL Editor (it is idempotent). Section 9 at the bottom must be applied so invitees see shared projects with the correct role.Username rules (enforced in app + DB): max 20 characters, no spaces, unique case-insensitively.
-
Enable email auth: Authentication → Providers → Email.
-
Grab credentials for frontend
.env.local:- Publishable key: Settings → API Keys → Publishable and secret API keys
- Project URL: Integrations → Data API → base URL (drop
/rest/v1)
See
.env.examplefor step-by-step dashboard navigation.
Supabase Row Level Security controls which rows each signed-in user can read or
write. SentinelAI uses RLS on profiles, projects, project_members,
invitations, incidents, and related tables so users only see data for projects
they own or were invited to.
Section 9 in supabase/schema.sql fhas:
| Piece | Purpose |
|---|---|
can_access_project(project_id) |
SECURITY DEFINER helper — true if the current user is the project owner or a member |
RLS on projects |
Select allowed for owners or members (not every authenticated user) |
RLS on project_members |
Users can read their own membership rows; teammates can list the team when can_access_project passes |
get_my_projects() |
SECURITY DEFINER — returns owned projects (role owner) union joined projects (role admin / user) for the dashboard |
get_accessible_project(uuid) |
SECURITY DEFINER — returns one project + my_role if the caller may open it; used on the project detail page |
get_my_project_role(uuid) |
Resolves owner vs admin vs user for permission checks in the UI |
The frontend (src/lib/projectTeam.ts) calls these RPCs first and falls back to
direct table queries only when needed. Project detail loading uses
.maybeSingle() and checks role before rendering owner-only actions.
Re-run section 9 after pulling updates if invitees still cannot see shared projects or if everyone incorrectly appears as Owner.
Configuration lives in .env.local at the repo root (Vite exposes VITE_*
variables only).
-
Create it from the template:
cp .env.example .env.local
-
Fill in:
VITE_SUPABASE_URL=https://<project-ref>.supabase.co VITE_SUPABASE_PUBLISHABLE_KEY=sb_publishable_xxxxxxxxxxxx VITE_API_URL=http://localhost:8000
-
Install and run:
npm install npm run dev # http://localhost:8443
Restart the dev server after changing
.env.local. In production (e.g. Vercel), set the same three variables; pointVITE_API_URLat your deployed backend URL.
| Area | Behavior |
|---|---|
| Sign up | Username (required), email, password with live strength meter; if email exists but unverified, resends confirmation instead of “try logging in” |
| Log in | Username or email + password; blocked until email is verified (resend link offered) |
| Verify email | /verify-email — resend confirmation or sign out; dashboard and protected routes require verified email |
| Dashboard header | Shows username; notifications bell (pending project invites); Settings; red Sign out |
| Settings | Change username (instant), email (confirmation to new address), password (new + confirm); delete account via sudo delete [username] modal |
| Projects | Create/edit with GitHub repo, Slack webhook (get one from Slack apps), runbooks — GitHub repo and Slack webhook are verified by the backend before save (see Integration validation) |
| Delete project | Owner only — dashboard trash icon → confirm → type sudo delete [Project Name] |
| Runbooks | .md or .pdf; must include four sections (validated semantically on upload) |
Each project has three roles. The dashboard lists projects you own and projects you joined as admin or user, with a role badge on each card.
| Role | Label in UI | What they can do |
|---|---|---|
| Owner | Owner | Delete the project, transfer ownership, promote/demote admins, invite teammates, remove members, approve edit requests, report/analyze incidents, assign incidents, review assignment requests and incident fixes, fix incidents directly |
| Admin | Admin | Invite teammates, remove users (not other admins), submit edit requests for owner approval, report/analyze incidents, assign incidents, review assignment requests and incident fixes, fix incidents directly |
| Member | User | View active incidents and AI analysis, request assignment to an incident (owner/admin must approve), submit fixes only when assigned (owner/admin must approve before resolve) |
Invitations
- Owners and admins invite by username or email from the Team & permissions modal (project page header: green Invite button).
- The invitee must already be a registered SentinelAI user (confirmed account). Otherwise the app shows: Invalid user/email. Please ask them to register to SentinelAI.
- Pending invites appear in the notifications bell (dashboard header, before Settings). Accept or decline from the dropdown; the list refreshes on focus and every 30 seconds.
- Inviters see pending invites inside the team modal; hover a row to reveal a cancel (×) button.
Team modal (project page)
- Open via Invite (owners/admins) or Team (members — view-only team list).
- Each member row shows a role dropdown (when you have permission): set User / Admin, Make owner, or Remove access.
- Only the owner can change someone between User and Admin, transfer ownership, or remove admins.
- Admins can remove users only.
Destructive confirmations (two-step modals with smooth fade/slide transitions)
| Action | Who | Step 1 | Step 2 — type exactly |
|---|---|---|---|
| Delete account | Any user | Warning | sudo delete [username] |
| Delete project | Owner | Warning | sudo delete [Project Name] |
| Transfer ownership | Owner | Warning (irreversible unless new owner transfers back) | sudo chown [username] |
| Leave project | Admin or user | Warning (rejoin only by re-invite) | sudo deluser [username] [Project Name] |
Leave project is in the project page header, to the right of Invite / Team (not inside the team modal).
Project edits by admins
- Admins cannot change project settings directly. Add/Edit Project submits a request; the owner approves or declines on the project detail page.
Incidents & fixes
Only owners and admins can describe an alert and run Analyze Incident (the RAG pipeline: GitHub commits → Chroma runbook search → Gemini → optional Slack). Regular users cannot trigger analysis — this prevents low-privilege members from injecting arbitrary alert text into the pipeline.
Incident workflow
flowchart TB
subgraph report [Report — Owner / Admin only]
ALERT[Describe alert] --> ANALYZE[Analyze Incident]
ANALYZE --> RAG[GitHub + Chroma + Gemini]
RAG --> SAVE[(Saved incident — active)]
end
subgraph assign [Assignment]
SAVE --> VIEW[All members view active incidents]
VIEW --> ADMIN_ASSIGN[Owner/Admin assigns teammate]
VIEW --> USER_REQ[User requests assignment]
USER_REQ --> ADMIN_APPROVE{Owner/Admin approves?}
ADMIN_APPROVE -->|Yes| ASSIGNED[User assigned]
ADMIN_APPROVE -->|No| VIEW
ADMIN_ASSIGN --> ASSIGNED
end
subgraph resolve [Resolution]
ASSIGNED --> USER_FIX[Assigned user: Submit fix + description]
ADMIN_ASSIGN --> ADMIN_FIX[Owner/Admin: Fix incident + description]
USER_FIX --> FIX_REVIEW{Owner/Admin reviews fix}
FIX_REVIEW -->|Accept| RESOLVED[(Incident resolved)]
FIX_REVIEW -->|Decline / Request changes| FEEDBACK[Feedback shown on incident]
FEEDBACK --> USER_FIX
ADMIN_FIX --> RESOLVED
RESOLVED --> POSTMORTEM[Postmortem posted to Slack]
end
Postmortem on resolve
When an incident is marked resolved (admin auto-fix or approved user fix), SentinelAI automatically posts a structured postmortem to the project’s Slack webhook. The report includes:
- Incident overview — ID, title, duration, reporter, assignee
- Impact — alert description and affected services
- Root cause — Gemini analysis plus commit evidence
- Detection & investigation — runbook sections and GitHub commits consulted
- Recommended remediation — Gemini next steps
- Resolution — approved fix description from the engineer
- Verification — all fix rejections with feedback, plus final approval
- Closure — who closed the incident and when
- Timeline — creation → analysis → assignment → fix submissions/rejections/approval
Postmortem posting is best-effort (like the initial Slack alert): if the webhook fails, the incident stays resolved and the UI shows a warning. A postmortem_posted flag prevents duplicate posts on reload.
sequenceDiagram
participant U as Assigned user
participant A as Owner / Admin
participant DB as Supabase
U->>U: Submit fix (describe what was fixed)
U->>DB: submit_incident_fix (pending)
A->>A: Review fix description
alt Accept
A->>DB: review_incident_fix (approve)
DB-->>U: Incident resolved
else Decline / Request changes
A->>A: Enter feedback text
A->>DB: review_incident_fix (decline + review_note)
DB-->>U: Feedback visible on incident card
U->>U: Revise and Submit fix again
end
| Step | Who | What happens |
|---|---|---|
| Report | Owner / Admin | Describes the alert → backend runs RAG → incident saved as active |
| View | All members | See active incidents, AI analysis, assignee, and alert description |
| Assign | Owner / Admin | Pick any project member from the assign dropdown on an incident card |
| Request assignment | User | Clicks Request assignment on an unassigned incident; owner/admin Approve or Decline in Pending assignment requests |
| Fix (admin) | Owner / Admin | Fix incident → describe what was fixed → incident resolved immediately |
| Fix (user) | Assigned user only | Submit fix → describe what was fixed → owner/admin Accept or Decline / Request changes with written feedback |
| Resubmit | Assigned user | After decline, feedback appears on the incident card; user revises and Submit fix again |
Fix review UI
-
Submit fix opens a modal with a required “What did you fix?” text area (assignees and admins).
-
Pending fix reviews (owners/admins) show the submitted description plus Accept fix or Decline / Request changes.
-
Decline / Request changes opens a feedback modal; feedback is stored as
review_noteand shown on the incident card so the assignee can revise and resubmit. -
Analyses are saved as incidents in Supabase (not ephemeral UI state).
-
Database RLS allows only admins to insert incidents; assignment and fix rules are enforced in RPCs (
assign_incident,request_incident_assignment,review_incident_assignment,submit_incident_fix). -
Slack posting is best-effort: if analysis succeeds but the webhook fails (e.g. revoked URL,
no_service), the incident is still saved and the UI shows a warning with a clear Slack error — analysis is not rolled back.
Re-run sections 10–11 at the bottom of supabase/schema.sql if assignment columns, RPCs, or fix-feedback validation are missing.
When you Create project (or save edits that change GitHub/Slack fields), the frontend calls the backend before writing to Supabase:
| Check | Endpoint | What it does |
|---|---|---|
| GitHub repo | GET /api/github/validate?repo=… |
Parses URL or owner/name, calls GitHub GET /repos/{owner}/{repo} with GITHUB_TOKEN — confirms the repo exists and is readable |
| Slack webhook | POST /api/slack/validate |
Validates Incoming Webhook URL shape, sends a one-line test message; Slack must respond with ok |
If either check fails, the project is not created and the form shows a combined error (e.g. GitHub: Repository not found… / Slack: The Slack webhook URL is invalid, disabled, or was revoked…).
Requirements
- Backend must be running (
docker compose up --build) andVITE_API_URLmust point at it — same as runbook validation. - Private GitHub repos need a valid
GITHUB_TOKENinbackend/.env.localwith read access to that repo. - Use a real Incoming Webhook URL from api.slack.com/apps
(
https://hooks.slack.com/services/T…/B…/…), not a Slack Workflow link.
Edit mode: GitHub and Slack are re-validated only when those fields change (so saving the project name alone does not send another Slack test message).
Slack validation message: “SentinelAI webhook validation — you can ignore this message.” — one post per changed webhook URL.
ChromaDB and native AI/HTTP dependencies run in a container so behavior is
consistent everywhere. Indexed runbooks persist in the chroma-data volume.
-
Configure
backend/.env.local:cd backend cp .env.example .env.localGEMINI_API_KEY=your-gemini-api-key GITHUB_TOKEN=your-github-pat SLACK_WEBHOOK_URL=https://hooks.slack.com/services/XXX/YYY/ZZZ # optional fallback FRONTEND_URL=http://localhost:8443 # production: your Vercel URL # Optional Gemini model overrides (defaults shown): # GEMINI_EMBEDDING_MODEL=gemini-embedding-001 # runbook vectors + validation # GEMINI_MODEL=gemini-2.5-flash # incident analysis
-
Build and start:
docker compose up --build # http://localhost:8000- API: http://localhost:8000
- Docs: http://localhost:8000/docs
docker compose down # stop (keeps ChromaDB volume)
docker compose down -v # stop and wipe indexed runbooks| Service | Role | Required env |
|---|---|---|
| Vercel | Frontend SPA | VITE_SUPABASE_URL, VITE_SUPABASE_PUBLISHABLE_KEY, VITE_API_URL (Render backend URL) |
| Render | Backend Docker | GEMINI_API_KEY, GITHUB_TOKEN, FRONTEND_URL (Vercel URL, no trailing slash) |
| Supabase | Auth + DB + storage | Run schema.sql; configure auth URLs (below) |
If the confirmation link opens localhost:3000, that is a Supabase dashboard
setting — not your Vercel app. Supabase defaults Site URL to
http://localhost:3000. Our app runs on port 8443 locally and your Vercel
URL in production.
Fix in Supabase → Authentication → URL Configuration:
| Setting | Change from | Change to |
|---|---|---|
| Site URL | http://localhost:3000 |
https://your-app.vercel.app |
| Redirect URLs | (add these) | https://your-app.vercel.app/auth/callback |
http://localhost:8443/auth/callback |
||
https://*.vercel.app/auth/callback (optional previews) |
Then sign up again (or resend confirmation) — old emails still contain the old localhost link.
The app also passes emailRedirectTo in code (SignUp.tsx, Settings.tsx) pointing
at /auth/callback on whatever origin you signed up from.
- Before verify: Supabase Auth stores a pending row in
auth.users(required for sending the email).public.profilesis not created yet. - After verify: A database trigger (and
/auth/callback) creates your row inprofileswith your username. Only then can you use the dashboard.
Email template (recommended): In Supabase → Authentication → Email Templates → Confirm signup, replace the default link with a direct app callback so confirmation works when opened from any browser or device (avoids PKCE “code verifier not found”):
<h2>Confirm your signup</h2>
<p><a href="{{ .SiteURL }}/auth/callback?token_hash={{ .TokenHash }}&type=signup">Confirm your email</a></p>Set Site URL to your production Vercel URL. After changing the template, sign up again or use Resend confirmation email so new links use the updated format.
Re-run the updated supabase/schema.sql in the SQL Editor
to apply the deferred-profile triggers and remove any old unconfirmed profile rows.
- Add env vars above;
VITE_API_URLmust be your Render URL (not localhost). - Redeploy after changing env vars (Vite bakes
VITE_*at build time). vercel.jsonrewrites all routes toindex.htmlso/auth/callbackand/dashboardwork on refresh.
- Deploy from
backend/Dockerfile; attach a persistent disk at/app/data/chromaso indexed runbooks survive restarts. - Set
FRONTEND_URL=https://your-app.vercel.appfor CORS (regex also allowshttps://*.vercel.apppreviews).
| Symptom | Likely cause |
|---|---|
| Email link opens localhost | Supabase Site URL still localhost; add production redirect URLs |
| Email confirm lands on 404 | Missing vercel.json SPA rewrite or redirect URL not allowlisted |
| PKCE code verifier not found | Update the Confirm signup email template (see above) and resend confirmation |
| Runbook upload / analyze fails | VITE_API_URL unset on Vercel → browser calls localhost |
| CORS error from frontend | FRONTEND_URL missing/wrong on Render |
Upstream error: no_service (analyze) |
Invalid or revoked Slack webhook — update the project’s Incoming Webhook URL; incident analysis still saves after the Slack best-effort fix, with a warning in the UI |
| Create project blocked — GitHub error | Repo URL wrong, repo missing, or private repo without GITHUB_TOKEN on the backend |
| Create project blocked — Slack error | Webhook disabled/revoked/wrong URL; regenerate at api.slack.com/apps |
| Invitee dashboard empty / wrong Owner role | Re-run full supabase/schema.sql, especially section 9 (RLS + get_my_projects / get_accessible_project) |
Frontend (.env.local)
| Variable | Required | Purpose |
|---|---|---|
VITE_SUPABASE_URL |
yes | Supabase project URL |
VITE_SUPABASE_PUBLISHABLE_KEY |
yes | Browser-safe Supabase key |
VITE_API_URL |
yes in prod. | Render backend URL — required on Vercel; defaults to http://localhost:8000 in dev only |
Backend (backend/.env.local)
| Variable | Required | Purpose |
|---|---|---|
GEMINI_API_KEY |
yes | Powers both Gemini models below |
GEMINI_EMBEDDING_MODEL |
optional | Runbook embeddings + validation (default gemini-embedding-001) |
GEMINI_MODEL |
optional | Incident analysis (default gemini-2.5-flash) |
GITHUB_TOKEN |
rec. | Rate limits; private repos |
SLACK_WEBHOOK_URL |
optional | Global fallback webhook |
FRONTEND_URL |
prod. | CORS allowlist for your frontend |
| Method | Path | Description |
|---|---|---|
| GET | /health |
Liveness + configured integrations |
| GET | /api/github/validate |
Verify repo exists and is readable (?repo=owner/name or URL) |
| GET | /api/github/commits |
Recent commits for ?repo=owner/name |
| GET | /api/github/deployments |
Recent deployments |
| POST | /api/slack/validate |
Verify Slack Incoming Webhook URL (sends test message) |
| POST | /api/runbooks/validate-file |
Upload .md/.pdf; semantic section validation |
| POST | /api/runbooks/index-file |
Parse + index a runbook file into ChromaDB |
| POST | /api/runbooks |
Index runbook JSON body |
| GET | /api/runbooks/search |
Semantic search (?q=...) |
| POST | /api/incidents/analyze |
Full pipeline → analysis; Slack post is best-effort (slack_posted, optional slack_error) |
| POST | /api/incidents/postmortem |
Post structured postmortem to Slack when an incident is resolved |
| POST | /api/incidents/notify |
Post a pre-built analysis to Slack |
Each runbook must cover these four topics (checked semantically, not just by heading text):
- How to set up and run the service
- How to test or verify that it works
- What common errors or symptoms to look for
- What action to take for each error
# GitHub — repo URL or owner/name
curl "http://localhost:8000/api/github/validate?repo=your-org/your-repo"
# Slack — Incoming Webhook URL (sends a short test message to the channel)
curl -X POST http://localhost:8000/api/slack/validate \
-H 'Content-Type: application/json' \
-d '{"webhook_url": "https://hooks.slack.com/services/XXX/YYY/ZZZ"}'curl -X POST http://localhost:8000/api/incidents/analyze \
-H 'Content-Type: application/json' \
-d '{
"github_repo": "your-org/your-repo",
"description": "5xx spike on the API gateway after the latest deploy",
"slack_webhook_url": "https://hooks.slack.com/services/XXX/YYY/ZZZ",
"deployment": "418"
}'- Run
supabase/schema.sql, configure.env.localfiles, start backend (docker compose up --build) and frontend (npm run dev). - Sign up with a username, email, and strong password; confirm email.
- On the dashboard, click New Project — add a GitHub repo, Slack webhook
(from https://api.slack.com/apps), and upload runbooks (
.md/.pdf). The backend validates GitHub and Slack before the project is saved; fix any errors shown on the form. - Invite teammates from the project page (Invite → Team & permissions). They accept from the notifications bell on their dashboard (shared projects appear after section 9 RLS is applied in Supabase).
- Open the project. Owners/admins: describe an alert under Report incident and click Analyze Incident. Users: view active incidents, request assignment, and submit fixes once assigned.
- Owners/admins assign incidents or approve assignment requests; assigned users Submit fix with a description. Owners/admins Accept or Decline / Request changes with feedback the assignee can see and act on.
- When a fix is accepted (or an admin resolves directly), SentinelAI posts a full postmortem to Slack automatically.
- Use Settings to update username, email, or password, or delete your
account (
sudo delete [username]). Owners delete projects from the dashboard (sudo delete [Project Name]). Non-owners can Leave project from the project header (sudo deluser [username] [Project Name]).