Gridlock is a Node.js monolith with a pure HTML/CSS/JS frontend, six AI agent services, a Better-SQLite3 database, and a Cloudflare tunnel handling all public traffic. This document describes how every layer fits together — useful for contributors, integration partners, and MSP technical staff who want to understand what is running inside the shield.
Gridlock deliberately avoids framework bloat. There is no React, no ORM, no message queue, and no microservice boundary. The entire product runs as a single Node.js process. This makes it fast to deploy, easy to reason about, and resilient — there is nothing to go wrong except the one thing you can see.
All inbound requests flow through Cloudflare's edge, through the tunnel, into the Express HTTP server running on the homeserver, where they are routed to either static file serving (the frontend) or the API layer (agents, auth, dashboard, compliance). The AI agents call out to the Z.AI provider abstraction layer, which selects the appropriate upstream model and returns structured JSON. Results are persisted to SQLite and sent back to the browser.
Pure HTML/CSS/JS frontend at public/
Edge CDN + WAF + tunnel
Node.js HTTP server + router
Six AI service modules
Better-SQLite3 persistence
Every component in the stack was chosen for operational simplicity, not fashion. The constraint "never change the tech stack" exists precisely because this combination works reliably in production.
Runtime for the HTTP server, agent services, billing webhooks, and all background jobs.
BackendMinimal HTTP framework. Routes are defined in src/routes/. Middleware handles auth, rate limiting, and error formatting.
Synchronous SQLite bindings. Faster than async alternatives for the read-heavy dashboard query patterns. One file, zero infra.
DatabaseAll frontend files live in public/. No build step, no bundler, no framework. Every page is a plain .html file with linked CSS and JS.
Primary AI provider powering all six agents. Accessed through the provider abstraction layer, which supports failover to alternate models.
AI ProviderError tracking and performance monitoring. All unhandled exceptions are captured with context. No stack traces ever reach API responses.
ObservabilityAdding React, Vue, an ORM, a message broker, or any frontend build tool is explicitly prohibited. The constraint exists because the current stack deploys as a single process with zero build steps, which is why uptime is so high. Complexity is the enemy of reliability.
Each agent is a service module in src/agents/ or src/services/.
Agents share no direct memory — they communicate through the database and through the dashboard
UI that orchestrates execution. An agent run is triggered by a user action or a scheduled job,
constructs a prompt with the relevant context, calls the AI provider, parses the response, and
writes the result to the appropriate database table.
| Agent | Source File | Trigger | Primary Output |
|---|---|---|---|
| MSP Hunter | src/agents/msp-hunter.js |
Manual execution or campaign schedule | Scored lead records written to leads table with contact data and email variants |
| Threat Researcher | src/services/threat-researcher.js |
Manual prompt or CVE watchlist trigger | Threat brief with CVSS score, MITRE mapping, and remediation steps written to security_events |
| Tech Support | src/services/tech-support.js |
Ticket submission via dashboard or API | Resolution steps appended to the ticket record; escalation flag set when confidence is low |
| Compliance Engine | src/services/compliance-engine.js |
Manual audit trigger or scheduled weekly run | Compliance report with gap analysis written to compliance_reports |
| Account Manager | src/services/account-manager.js |
Nightly scheduled job | Health score, churn risk flag, and expansion signals written to the organization record |
| Onboarding Agent | src/routes/onboarding.js |
New account creation; re-triggered on each wizard step | Step completion events, initial security score seed, and guided tour state stored per user |
All agent calls go through a thin abstraction layer rather than calling the AI provider SDK directly. This layer handles:
agent_executions table for plan-level throttling.If the AI provider is unreachable, agents return a clear error message in the dashboard rather than hanging or silently failing. The error is captured in Sentry with the full request context. Scheduled jobs back off with exponential delay and retry up to three times before marking the execution as failed.
Gridlock uses a single Better-SQLite3 database file. All queries use parameterized statements — there is no string concatenation in SQL calls anywhere in the codebase. The key tables are described below.
| Column | Type | Description |
|---|---|---|
id | INTEGER PK | Auto-increment primary key |
email | TEXT UNIQUE | User email address (login identifier) |
password_hash | TEXT | bcrypt hash — plain text is never stored |
name | TEXT | Display name |
role | TEXT | admin or member |
org_id | INTEGER FK | Foreign key to organizations |
email_verified | INTEGER | 0 or 1 boolean |
created_at | TEXT | ISO 8601 timestamp |
| Column | Type | Description |
|---|---|---|
id | INTEGER PK | Auto-increment primary key |
name | TEXT | Organization display name |
industry | TEXT | Industry vertical slug |
team_size | INTEGER | Approximate headcount |
plan | TEXT | starter, pro, or ultimate |
security_score | INTEGER | Latest composite security score (0–100) |
ls_customer_id | TEXT | LemonSqueezy customer identifier |
created_at | TEXT | ISO 8601 timestamp |
| Column | Type | Description |
|---|---|---|
id | INTEGER PK | Auto-increment primary key |
org_id | INTEGER FK | Owning organization |
type | TEXT | Event category (e.g. threat, dns_anomaly, compliance_gap) |
severity | TEXT | critical, high, medium, or low |
title | TEXT | Short human-readable event title |
description | TEXT | Full event detail and AI-generated context |
resolved | INTEGER | 0 or 1 boolean |
created_at | TEXT | ISO 8601 timestamp |
| Column | Type | Description |
|---|---|---|
id | INTEGER PK | Auto-increment primary key |
org_id | INTEGER FK | Owning organization |
agent_id | TEXT | Agent slug (e.g. threat-researcher) |
status | TEXT | pending, running, completed, or failed |
input_prompt | TEXT | User-supplied prompt text |
output | TEXT | Structured JSON result from the agent |
tokens_used | INTEGER | Total tokens consumed (prompt + completion) |
duration_ms | INTEGER | Wall-clock time of the AI call in milliseconds |
created_at | TEXT | ISO 8601 timestamp |
compliance_reports — Stores full audit results per framework per org, including gap items and remediation status.leads — MSP Hunter prospects with composite score, sub-scores, contact data, pipeline status, and generated email variants.tickets — Support tickets with AI triage output, status history, and escalation log.cve_watchlist — Per-org CVE subscriptions with CVSS data, MITRE mapping, and last intelligence update timestamp.audit_log — Immutable record of all sensitive actions: logins, password changes, agent executions, billing events, and member changes, each with user ID, IP, and timestamp.Gridlock is a security product — it must be hardened against the same threats it helps customers detect. The security model is defense in depth across five layers:
org_id — even if a token is valid, it cannot access another organization's data. Admin-only routes enforce role separately from authentication.
Every sensitive action is written to the audit_log table synchronously before the
response is returned: user registrations, logins and logouts, password changes, agent executions,
compliance report generation, billing events, team member additions and removals, and API key
operations. Each record includes the user ID, IP address, user agent, action type, and a
JSON payload with the relevant context. The audit log is append-only — no delete routes
exist for it.
JWT tokens are for interactive dashboard sessions and expire in 24 hours. API keys (available at Dashboard → Settings → API Keys) are long-lived machine credentials for integration use. API keys can be scoped to read-only or read-write and revoked at any time. Never use a JWT token in automated scripts — use an API key instead.
Gridlock uses LemonSqueezy for subscription management. The billing flow works as follows:
POST /api/billing/webhook.plan field in the database.Every incoming webhook request is verified using HMAC-SHA256 with the LemonSqueezy webhook
secret before any processing occurs. Requests with invalid or missing signatures return
401 Unauthorized and are logged. No billing state is ever mutated without a valid
signature.
Production runs on a homeserver at 100.70.240.55, accessible publicly via a
Cloudflare tunnel. The full deployment path from browser to database looks like this:
lockthegrid.com
WAF + CDN
Tunnel daemon
Auto-restart wrapper
Express server
On-disk DB
The Node.js process runs as a Windows service managed by NSSM (Non-Sucking Service Manager). NSSM handles:
C:\builds\gridlock\logs\.
The tunnel runs as a separate cloudflared process that maintains an outbound
connection to Cloudflare's edge. There are no inbound ports open on the homeserver's firewall —
all traffic enters through the tunnel. This means:
master on GitHub.100.70.240.55.C:\builds\gridlock and run git pull origin master.npm install if package.json changed.nssm restart gridlock.curl https://lockthegrid.com/api/dashboard/health.lockthegrid.com has live production users and live Stripe keys. Never push breaking changes
without testing locally first. Test with npm test before every deploy. If a
migration changes the database schema, test the migration on a copy of the production database
file before running it on the server.
Complete reference for every endpoint — methods, paths, auth requirements, parameters, and error codes.
From account creation through your first agent execution — the practical guide for new Gridlock users.
Deep dive into the lead generation agent — scoring algorithm, campaign management, and CRM pipeline integration.