Date: 2026-08-13
Scope: Close every gap from the usability critique so the default path is:
install → login → (wizard if needed) → Brain indexes the real schema → Agent answers
Success metric for Sunday: A fresh Docker install + seed-demo-data.sh, and a multi-schema Postgres DB, both complete “ask about largest tables / row counts” without auth or “Brain ready but empty” failures.
Companion: Security track is in OSS_SECURITY_REVIEW.md (run in parallel; gate “internet multi-user” on S1–S5).
- One source of truth for “ready.” Connection green + Brain complete + Agent boot OK must mean the same tables the user expects are queryable by the Agent.
- Fail loud, early. Never burn 6–11 tool steps before saying “MCP token expired.”
- Default path is narrow. Index → optional notes → ask Agent. Advanced jobs stay behind a fold.
- Ship in thin vertical slices that each leave main greener than before.
| ID | Workstream | Severity | Est. size | Depends on |
|---|---|---|---|---|
| W1 | Agent ↔ DeepSQL MCP auth reliability | Blocker | L | — |
| W2 | Multi-schema Postgres Brain + coverage gate | Blocker | L | — |
| W3 | Revive first-run onboarding / setup gate | Blocker | M | W2 for BrainInit step quality |
| W4 | Brain UI honesty (stages, stepper, jobs) | Blocker | M | W2 for coverage fields |
| W5 | Unresolved false positives (PK joins) | High | M | — |
| W6 | Polish (suggestions, title, dbType, dedupe) | Medium | S | — |
| W7 | Docs + smoke gates for launch | High | S | W1–W4 |
Parallelize W1 ∥ W2 ∥ W5 ∥ W6; then W3/W4 on top of W2 APIs; finish with W7.
Three credential stores diverge:
| Store | Used by |
|---|---|
| Browser session cookie / JWT | UI connection health |
~/.config/deepsql/auth.json |
CLI |
Hermes profile DEEPSQL_AUTH_TOKEN (env snapshot in long-lived MCP child) |
Agent tools |
UI “ADMIN” only proves (1). Agent failures are (3) stale/revoked + MCP process not live-reloading env. Logout revokes DB token but leaves disk plaintext. Provisioner often skipped on native runs (deepsql-agent:8788 DNS). Circuit breaker then reports “unreachable.”
Goal: Every Agent session starts with a freshly minted (or extended) MCP token that the running MCP process can see, and the UI surfaces health before chat.
- Provisioner writes
~/.hermes/profiles/u-<user>/deepsql.token(0600). - Set
DEEPSQL_TOKEN_FILEinmcp_servers.deepsql.env(keepDEEPSQL_AUTH_TOKENas fallback). - MCP lib already re-reads file + self-heals on 401 — wire it in provisioners.
Files:
scripts/local-agent-provisioner.py, Compose agent provision path, scripts/self-host/setup-agent.sh, mcp/deepsql-phase1-lib.js (verify/tests).
AgentBridgeService.ensureProfile/callProvisioner: if provisioner configured, fail the session bootstrap on non-2xx (don’t silently return profile name with stale disk token).- Native AGENTS.md / start scripts: require
AGENT_PROVISION_SECRET+ local provisioner whenSECURITY_AUTH_ENABLED=true.
Files:
AgentBridgeService.java, AgentBridgeController.java, scripts/start-backend.sh (or docs only if script already supports it).
- On
revokeAgentTokens, clear profile token file / blankDEEPSQL_AUTH_TOKENvia provisioner hook (or best-effort file delete under known profile path).
Files:
AuthController.java, AgentBridgeService.java, provisioner.
- Extend
POST /api/agent/session(or addGET /api/agent/health) to: mint/extend → provision → probeGET /api/connectionswith the just-written token. AgentChatPanelboot: if probe fails, show blocking banner: “Agent cannot reach DeepSQL (auth). Reconnect / check Agent runtime.” Disable send. No chat until green.
Files:
AgentBridgeController.java, src/lib/api/agentClient.js, AgentChatPanel.jsx.
- Extend
scripts/self-host/e2e-agent-check.py(or twin) to fail CI/smoke if Agent tool path 401s. - Document: plain
deepsqlCLI ≠ Hermes MCP auth.
- Fresh install: Agent answers “how many customers?” on
demo_shopwithout manual token copy. - Logout → login: Agent still works (re-mint + token file).
- Rotating token on disk without Hermes restart: next tool call succeeds (token file).
- Failed provision: UI shows explicit error before first user message.
PostgresIntrospectionProvider hardcodes schemaname = 'public' (and siblings). Brain init progress is stage-bucket %, not schema coverage — so 2 public views → COMPLETED 100% while 18 business tables exist. CLI SQL bypasses introspection, so the product feels schizophrenic.
Replace = 'public' with:
schemaname NOT IN ('pg_catalog','information_schema','pg_toast')
AND schemaname NOT LIKE 'pg_temp_%'
AND schemaname NOT LIKE 'pg_toast_temp_%'Always set TableMetadata.schema from catalog; use map keys schema.name to avoid collisions.
Epicenter: PostgresIntrospectionProvider.java (scanSchema, getTablesAndViews, getAllTablesWithMetadata, column/index/FK batches).
Also align: QueryExecutorService.getPostgreSQLObjects, SchemaIntrospectionService, privilege checks, chat “list tables” prompts that still say public-only, advisor/stats paths that copy the filter. Copy the exclusion pattern already used in Brain classification services.
- Add
includedSchemas(nullable JSON list; null/empty = all non-system). - Wizard: after successful test, multi-select schemas (
pg_namespace). - Brain UI: edit allow-list → Re-initialize.
- Update privilege docs/snippets beyond
GRANT … ON SCHEMA public.
Files:
DatabaseConnection / ConnectionRequest, migrations or ddl-auto field, ConnectionWizard, PrivilegesAccordion.js, introspection filter ANY(?).
After SCHEMA_SCAN:
liveUserTableCount= countable non-system (or allow-listed) base tables visible to the JDBC user.tablesDiscovered= what we indexed.- If
liveUserTableCount > 0 && tablesDiscovered == 0→ FAILED with clear message. - If coverage < threshold (e.g. < 80% of live base tables) → do not
markCompleted(100); stay in aNEEDS_ATTENTION/ incomplete state withcoveragePercent,schemasScanned,skippedSchemas. - Init-status API exposes these fields for UI.
Files:
BrainInitStageExecutor.java, init-status DTOs, BackgroundJobsTab.jsx / Brain enrichment card.
After multi-schema scan, surface missing USAGE/SELECT as Brain warnings (not silent empty). Update wizard grant template to selected schemas.
-
acme_erp-shaped DB: Brain listscrm.*/sales.*/ … (or explicit allow-list), not onlypg_stat_statements. - Zero user tables indexed ⇒ cannot show “Complete 100%.”
-
demo_shop(public only) still greens. - Duplicate table names in two schemas don’t overwrite each other in snapshots.
Onboarding.jsx is complete product work but unrouted. Bootstrap/install.sh may set setup.complete=true early, so that flag ≠ “ready to use.” Login always goes to dashboard. Payload field names in the wizard don’t match ConnectionRequest.
| Concept | Signal |
|---|---|
| Admin exists | user table / bootstrap done |
| Product ready | hasConnections && hasLlmConfig (and optionally Brain init done for first connection) |
| Wizard complete | setup.wizard.complete or derive from above — stop using bootstrap’s setup.complete alone |
App.jsx:/onboardingprotected;/setup→/onboarding.- After login (
useAuth.jsx): if!hasConnections→/onboarding(primary). Soft-nudge if!hasLlmConfig. Login.jsx: when status shows no connections / incomplete wizard, CTA “Finish setup” →/onboarding(and link to README for install-script admins).- Fix wizard save fields:
connectionName,dbType: postgres,database(notname/databaseType/databaseName).
- Sidebar / Home: if zero connections, unlock Docs + onboarding CTA; keep other nav locked with “Add a database.”
- Optional: post-login modal “Load demo_shop” calling seed guidance or deep-link to docs/script output.
- Fresh admin with no connections lands in wizard, not a locked dashboard.
- Completing wizard creates connection, configures LLM, kicks Brain init, marks product ready.
-
/onboardingno longer blank.
- Frontend
STAGE_ORDER≠ backendInitStage→ grey stages at 100%. - Stepper defaults to Initialize / background-jobs and never auto-advances on COMPLETED.
- Ten scheduled jobs dominate the “teach your business” home.
Make BackgroundJobsTab.jsx STAGE_ORDER / labels match InitStage.java exactly (DATA_SAMPLING, COLUMN_VALUE_COLLECTION, INFERRED_RELATIONSHIPS, RAG_EMBEDDING, BRAIN_ANALYSIS, SEMANTIC_MODELING, …). On COMPLETED, mark all pipeline stages done.
- When init
COMPLETEDand user hasn’t chosen a tab → default to Add context / schema-context, not jobs. - Step 1 shows checkmark when complete; only the active step is highlighted.
- Show coverage line from W2: “Indexed 18/18 tables across 5 schemas” or “Indexed 0/18 — fix grants or schema list.”
- Brain home: enrichment card + next-step CTA only.
- “Scheduled maintenance (N)” collapsed by default; expand for power users.
- Prefer connection-scoped jobs in the summary; globals behind toggle.
- Complete init ⇒ all listed stages green; stepper on “Add context.”
- First paint after ready is not a wall of 10 job cards.
- Incomplete coverage from W2 is visible on the enrichment card.
KeyColumnAnalysisService.detectAntiPatterns emits UNINDEXED_JOIN when joinCount >= 5 && indexName == null, but enrichWithIndexStats is a stub — never sets indexName. Normal PK joins become Unresolved ANTI_PATTERN noise.
- Implement index enrichment via dialect introspection (PK/UK/indexes →
indexName). - Skip
UNINDEXED_*when column is PK/UK /TRUE_KEY. - Ambiguity panel: only HIGH actionable types; allow dismiss/acknowledge to stick.
- Retune copy: “Possible missing index” ≠ “Brain can’t disambiguate.”
Files: KeyColumnAnalysisService.java, SchemaAmbiguityService.java, UnresolvedPanel.jsx.
-
demo_shopcustomers.id / orders.customer_id do not appear as Unresolved solely for being join keys with a PK/index. - Genuine unindexed join columns still surface.
| Item | Fix | Files |
|---|---|---|
| Hardcoded “bookings” suggestions | Schema-aware chips from top tables / generic fallbacks | AgentChatPanel.jsx |
| Title “DBA Agent” | <title>DeepSQL</title> (+ optional login badge copy) |
index.html, Login.jsx |
| POSTGRES vs POSTGRESQL | Canonicalize to postgres on write via DatabaseProviderRegistry.getCanonicalName; display “PostgreSQL” |
CredentialService.java, src/lib/dbType.js, connection UIs |
| Duplicate connections | Dedupe or 409 on same owner+host+port+database; wizard warn | CredentialService / ConnectionController |
| Login tone | Soften “work email” for OSS or dual copy | Login.jsx |
- Suggestions never mention tables absent from the active connection.
- Browser tab says DeepSQL.
- New connection with same host/db doesn’t silently double.
- In-app Docs: short “Web UI first hour” — add connection → Brain → Agent (link CLI docs second).
- README: call out multi-schema + Agent health; “Brain ready means indexed coverage.”
- Smoke matrix (must pass on release branch):
| Check | Command / assertion |
|---|---|
| Install + login | install.sh / existing smoke |
| demo_shop Agent Q&A | e2e-agent-check customer count |
| Multi-schema fixture | mini crm+sales DB; Brain tablesDiscovered >= N; Agent lists those tables |
| Coverage gate | public-only views fixture must not report Complete 100% if user tables exist elsewhere |
| Onboarding route | /onboarding renders stepper |
| Stage UI | COMPLETED ⇒ no grey pipeline stages |
- Release checklist in PR template / DocsSection.
PR1 W2a — Postgres non-system schema scan + qualified names (backend)
PR2 W1 — Token file + provision fail-loud + Agent boot probe (backend + scripts + Agent UI)
PR3 W2b — Coverage gate + init-status fields (backend)
PR4 W4 — Stage enum sync + stepper + collapse jobs + coverage UI (frontend)
PR5 W5 — Index enrichment + Unresolved filter (backend + small UI)
PR6 W3 — Onboarding route + auth gate + payload fix + login CTA (frontend + setup semantics)
PR7 W2c — Schema allow-list on connection + wizard (full-stack)
PR8 W6 — Polish (frontend + normalize)
PR9 W7 — Docs + smoke fixtures (scripts + docs)
Minimum viable Sunday cut if time-boxed: PR1 + PR2 + PR3 + PR4 + PR5 + thin PR6 (route + redirect only) + agent/demo smoke. Defer allow-list UI (PR7) if default non-system scan is enough; keep privilege messaging in Brain errors.
- W1 Agent MCP auth reliable + boot banner
- W2a/b Multi-schema scan + coverage gate
- W4 Brain stages/stepper/jobs honesty
- W5 Unresolved PK noise fixed
- W3 thin:
/onboardingrouted + redirect when no connections
- S1 Kill-session SQLi
- S2 ACL on apply/kill (and ideally slow-log/growth)
- S3 Hermes bind
127.0.0.1 - S4 Compose: no public DB/Redis; strong DB password; Redis auth
- S5 Actuator lockdown + JWT fail-closed under prod
| Posture | Extra requirements |
|---|---|
| Private single-admin | Network: only :3000 public; messaging honest |
| Internet multi-user | All Criticals + H4 SET allowlist, H5 SSRF, H6 share password |
| Risk | Mitigation |
|---|---|
| Multi-schema scan slows Brain init / embedding cost | Cap concurrency; allow-list; skip system-like schemas later |
| Larger snapshots / RAG volume | Monitor; document; optional “index views” toggle default off for pg_stat_* |
| Provisioner required in all envs | Fail closed in UI; make AGENT_PROVISION_SECRET mandatory in .env.example |
| Breaking clients that assumed public-only | Changelog; allow-list for lock-down |
- Renaming Brain → something else (IA is fine once jobs are demoted).
- Re-enabling
AGENTS_ENABLEDscheduled-agent product. - Full Flyway adoption.
- Desktop Electron polish.
Ship when: green connection + Brain complete ⇒ Agent can name and query the user’s real tables (including non-public schemas) without a human pasting MCP tokens — and dangerous APIs enforce connection ACL.