Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1,177 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SPS — Statewide Protective Services

Marketing website + operations backend: a sales pipeline, a management dashboard, and an authenticated API your Trinity AI (CoAIleague) connects to for automated outreach (email/SMS/phone) and lead orchestration. Hosted on Railway.

Runtime note: main is the live Node/Express + Drizzle + Postgres spine. The old branch-note about feature/spine-backend is stale.

Architecture

public/ (static site + /admin dashboard)
   │  contact form  → POST /api/v1/ingress/lead
   │  careers form  → POST /api/v1/ingress/apply  (multipart résumé)
   ▼
server/ (Express + TypeScript)
   ├─ lib/coerce.ts     Zod normalizers (data-coercion checkpoint)
   ├─ lib/auth.ts       bcrypt + signed-cookie sessions
   ├─ routes/ingress.ts public form intake → Postgres + email notify
   ├─ routes/admin.ts   management dashboard API (login-guarded)
   ├─ routes/api/v1/trinity.ts  Trinity bridge (HMAC-signed; see below)
   ├─ utils/storage.ts  file storage — CoAIleague bridge canonical; local dev-only (prod-forbidden)
   └─ db/schema.ts      operational tables (Drizzle)
   ▼
Railway Postgres — SPS operational spine (shifts/time/clients/officers/money DRAFTS)

Authority split (read CLAUDE.md): SPS is the spine (operational source of truth for shifts, time, clients, officers, and money drafts). CoAIleague is the brain and the canonical platform authority for money (Stripe), identity/entitlement, and metering — SPS moves no money; it PREPARES + SIGNALS over the signed bridge. "Single source of truth" applies per-domain, not globally.

Tables: users · leads (pipeline) · activities (outreach log) · applications · incident_logs · schedules.

Management dashboard (/admin)

Sign-in protected console for the team:

  • Sales pipeline — leads by stage (new → contacted → qualified → won/lost), inline stage changes.
  • Needs-contact queue — leads overdue for follow-up (or never contacted).
  • Applications inbox — applicants, status, and résumé download (auth-gated).
  • Activity log — per-lead timeline of every email/SMS/call, including what Trinity did.

Admin bootstrap (create-if-absent): on first boot, if no admin exists, one is created from ADMIN_EMAIL / ADMIN_PASSWORD. After that the password is managed in-app — a redeploy will not overwrite it (a UI password change is never reverted, and the env var is not a standing credential). To recover a locked-out single-tenant owner, set the explicit break-glass ADMIN_PASSWORD_FORCE_SYNC=true, redeploy once, then unset it.

Trinity API (for CoAIleague)

Auth — HMAC-signed bridge. Inbound CoAIleague→SPS calls are verified by the trinityAuth middleware (HMAC over the exact body via verifyBridge; contract in server/lib/bridgeSecurity.ts). A bearer Authorization: Bearer <TRINITY_API_KEY> is accepted in additive mode during rollout, but production should run TRINITY_REQUIRE_SIGNATURE=true (a.k.a. BRIDGE_REQUIRE_LOCKDOWN) so unsigned calls are refused — and outbound emit (TRINITY_INGEST_URL set) hard-fails at boot without a signing secret. Do not rely on bearer-only in production.

  • GET /api/v1/trinity/sync{ pipeline[], needsContact[], newApplications[], openSchedules[], knowledgeBase, counts }. knowledgeBase carries the pricing engine, guard capabilities, and contract rules (server-side constants in server/constants/) so Trinity can quote consistently. Pipeline stages: new → contacted → ready_to_close → qualified → won/lost; setting ready_to_close emails a hot-lead alert to the team.
  • POST /api/v1/trinity/action → one of:
    { "type":"create_lead", "clientName":"", "email":"", "industryType":"hotel|construction|retail|hospital|clinic|data_center|bar_nightclub|apartment|hoa|other", "guardTypeRequired":"" }  // Trinity recommendation
    { "type":"log_activity", "leadId":"<uuid>", "channel":"call|email|sms|note", "body":"", "nextFollowupAt":"ISO" }
    { "type":"set_followup", "leadId":"<uuid>", "nextFollowupAt":"ISO" }
    { "type":"update_lead_status", "id":"<uuid>", "status":"contacted|qualified|won|lost" }
    { "type":"update_application_status", "id":"<uuid>", "status":"screening|interview|hired|rejected" }
    { "type":"create_incident", "location":"", "description":"", "severity":"low|medium|high|critical" }
    { "type":"create_schedule", "postLocation":"", "startTime":"ISO", "endTime":"ISO" }

log_activity stamps lastContactedAt and advances a new lead to contacted.

Local development

npm install
cp .env.example .env        # set DATABASE_URL, SESSION_SECRET, ADMIN_PASSWORD, TRINITY_API_KEY
npm run dev                 # applies idempotent migrations on boot; creates the admin if absent

npm run typecheck · npm run build · npm run db:generate.

Environment variables

Var Required Purpose
DATABASE_URL Railway Postgres (SPS operational spine)
SESSION_SECRET ✅ (prod) signs dashboard login cookies (openssl rand -hex 32)
ADMIN_EMAIL / ADMIN_PASSWORD bootstrap only — creates the admin if absent; not a standing credential (see Admin bootstrap)
ADMIN_PASSWORD_FORCE_SYNC break-glass true force-resets the existing admin's password from ADMIN_PASSWORD on one boot; leave unset
TRINITY_API_KEY bridge key; also the HMAC signing secret unless TRINITY_SIGNING_SECRET is set
TRINITY_REQUIRE_SIGNATURE ✅ (prod) true refuses unsigned bridge calls (production posture)
SITE_ID ✅ (bridge) canonical tenant identity stamped on bridge events; UUID sites must set TRINITY_BILLING_PLAN
STORAGE_PROVIDER ✅ (prod) coaileague (canonical) or s3/R2. local is refused in production — dev only
RESEND_API_KEY optional email alerts (else logged)
PORT auto Railway injects it

Deploy on Railway

  1. Add a Postgres plugin → provides DATABASE_URL.
  2. Set SESSION_SECRET, ADMIN_PASSWORD, TRINITY_API_KEY, SITE_ID, STORAGE_PROVIDER (coaileague/s3), and the production bridge posture TRINITY_REQUIRE_SIGNATURE=true.
  3. Deploy main — the Dockerfile builds the Node app, applies idempotent migrations via ensureSchema() on boot, and serves public/ natively (clean URLs). Migration execution is not readiness: /readyz fail-closes (503) until schema + required indexes + bootstrap are actually present, so a partial migration can't serve traffic; /livez is liveness only.
  4. Verify GET /livez (live) then GET /readyz (ready), sign into /admin, then connect Trinity to /api/v1/trinity/sync.

Backup & rollback

  • Backup: the SPS operational database (Railway Postgres) must have a verified, restorable backup before any destructive migration — see docs/go-live/RETIREMENT_PROOF_PACKETS.md.
  • Rollback: roll back by redeploying the prior main SHA. Migrations are additive + idempotent (Law 6), so a redeploy of an earlier SHA does not require a down-migration; a destructive change is owner-gated (docs/go-live/SPS_OWNER_GATES.md) and needs a restore path.

About

Statewide Protective Services

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages