Project

10 min read

← Digest

2026-06-22

RESOLVEQ: A QUIET CONSOLE FOR AI SUPPORT OPERATIONS

How I designed an orchestration layer over ServiceNow, Jira and GitHub around one uniform schema, a single SQL boundary, honest computed-from-rows metrics, and an upgrade path that scales by adding data instead of rewriting modules.

Next.js 16React 19SQLitebetter-sqlite3TypeScriptTailwind v4

The Thesis Enterprise support teams live in two systems that quietly drift apart: ServiceNow, where L1 works, and Jira, where engineers do. Fields fall out of sync, attachments break, statuses contradict each other, and nobody has a single view of what automation is actually doing.

ResolveQ is the orchestration layer that fixes the view first. It normalizes both tools into one schema and shows, in real time, what the AI is handling and what still needs a human. It's a "quiet console": a Next.js 16 app with a real SQLite data layer, custom session auth, role-based access, and a connector registry with real REST clients — built to be extended by adding a row, not rewriting a module.

ResolveQ in Use

Product Evidence

ResolveQ live operations dashboard with computed KPIs

Live operations dashboard

ResolveQ connectors page with ServiceNow, Jira and GitHub

Connector registry & status

How I Think: One Schema, Many Tools The first decision was to refuse to model each tool separately. A ServiceNow Incident and a Jira Issue are both flattened into a single normalized Request through a typed Workflow State Resolver — a table that maps each provider's native status onto one shared status, so a ticket is never closed prematurely on one side.

The second decision was a single SQL boundary. Pages and routes never touch SQL; they call typed repository functions that return domain types. That one rule is what makes the whole system cheap to change later: swapping SQLite for Postgres becomes a driver and dialect change behind a stable interface, and call sites don't move.

One Schema, Many Tools

System Shape

External toolswebhooksWebhookadaptersnormalizeUniformRequest schemaBrowseroperatorsEdge proxy gatecookie filterAuthed layoutrequireRole()Repository layeronly code touching SQLSQLiteIngress and access both resolve to the same typed repositories
  • Uniform schema

    A ServiceNow Incident and a Jira Issue both flatten into one Request via a typed state resolver — a ticket is never closed prematurely on one side.

  • One SQL boundary

    Pages and routes never write SQL. Every read and write goes through thin repositories, so a Postgres swap is a driver change behind a stable interface.

  • Extend by data

    New integrations and status mappings are array entries, not new modules.

The pattern I keep reaching for is extensibility by data, not code. Connectors and the state resolver are arrays and tables — you extend the system by adding an entry, and the type system fails the build if a mapping drifts.

Secure by Construction Authentication is custom on purpose: scrypt password hashing with a per-user salt and timing-safe comparison, plus opaque high-entropy session tokens delivered in an httpOnly, SameSite=Lax, Secure-in-prod cookie. Sessions are backed by a database table, so logout, account disable, and password reset all delete rows — real server-side revocation, not a stateless token you can only hope expires.

Enforcement is two-layer. A cheap cookie-presence check at the edge bounces anonymous traffic fast; the authoritative check runs server-side in the authed route-group layout, validating the token against the DB on every request. The edge can't reach SQLite, so it can never be the source of truth — it's just a first filter. Authorization is re-checked on every admin path, and a last-admin guard makes it impossible to lock everyone out.

Security by Construction

Two-layer Auth

UntrustedRequest+ cookieEdgegatefastAuthoritative (server)requireRolevalidatetokensessions tablescrypt + saltEdge can't reach the DB, so it can never be the source of truth
  • Edge + server

    A cheap cookie check at the edge bounces anonymous traffic; the authoritative check runs server-side and validates the token against the DB on every request.

  • Revocable sessions

    scrypt password hashing and opaque high-entropy tokens in an httpOnly cookie, backed by a sessions table — logout, disable and reset delete rows for real revocation.

  • Last-admin guard

    You cannot demote or disable the final active admin; admin actions re-check role server-side, not just in the UI.

Built to Scale by Adding Data Every design choice has a stated upgrade path, and none of them require rewriting the app. The app tier is stateless — all durable state is in the DB — so horizontal scaling only needs a shared store. The data tier moves from SQLite to Postgres behind the same repository interface, with read replicas for the read-heavy analytics queries.

Ingestion is queue-ready: webhook routes are thin (normalize and upsert), so under load you drop a queue between the route and the repository and let workers drain it. And the live KPIs become a refreshed rollup table once row counts make per-request scans expensive — the page contract never changes.

Built to Scale by Adding Data

Upgrade Path

TodayAt scaleSQLite via repositoriesPostgres + read replicasWebhook -> upsert (direct)Queue -> workers (retry)Live COUNT / AVG per requestRefreshed rollup tableSame repository interface — call sites don't move
  • Stateless app tier

    All durable state is in the DB; the process holds nothing per-user. Horizontal scaling needs only a shared store.

  • Queue-ready ingestion

    Webhook routes are thin (normalize + upsert). Under load, drop a queue between the route and the repository so spikes never block.

  • Rollups when it hurts

    KPIs are computed live today; swap to a refreshed rollup table once row counts make per-request scans expensive — the page contract is unchanged.

The same blueprint extends to the harder roadmap: Redis dedup and locks before turning on real bidirectional sync (the echo loop will corrupt state without them), and pgvector inside Postgres for historical-incident matching, so there's no separate vector database to operate.

The Target Deployment Shape The current app is deliberately lean — a single Next.js process over SQLite. But the architecture maps cleanly onto a managed deployment, and drawing that target made the boundaries explicit: edge gate to CloudFront/WAF, the stateless app to Fargate, webhook ingress to API Gateway, async sync to SQS and Lambda, the store to RDS with pgvector.

Target Deployment Shape

AWS — roadmap, not today

ResolveQ target AWS deployment architecture

The current app runs as a single Next.js process over SQLite. Dashed edges mark the roadmap path; solid marks what ships today.

What I Learned ResolveQ reinforced a pattern I keep seeing in systems work: leverage lives at the boundaries. One uniform schema absorbs every new tool. One SQL boundary localizes every data change. One auth boundary secures every page without rewiring routes. One stated upgrade path per subsystem means the system can grow under load without a rewrite.

The system is small on purpose — and it's small precisely so it can grow by adding data rather than code.

← All PostsPortfolio →