LISAOS // DOCS
GATEWAY // DASHBOARD API

Gateway Module — `dashboard-api/`

1. Purpose

The dashboard-api/ module is the same-origin proxy + vault-parser backend for the dashboard frontend (see [dashboard](/docs/gateway/modules/dashboard)). It exists so the browser dashboard can reach a handful of gateway surfaces without header gymnastics: it parses two vault markdown registries into JSON (scheduled jobs, skills), proxies five auth-sensitive surfaces that AK-415's tightened matrix broke (agent-memory list, vault-index status/scan/type-effectiveness, timeline SSE), and keeps the write token server-side. Its routes are mounted under /api/dashboard and treated by the auth matrix as an internal same-origin surface: dashboard reads are served without a caller Bearer (the write token is held server-side, never exposed to the browser), while the write-proxy routes are gated to an explicit internal allow-list.

2. File Inventory

FileLinesResponsibility
index.ts291createDashboardApiRouter — jobs/skills parse routes, 5 AK-415 proxy routes, SSE passthrough
vault-parser.ts189parseMarkdownTable, parseSkillsRegistry + ParsedSkill
Total480

3. Public API Surface

REST Endpoints

Mounted at /api/dashboard. Auth posture: the dashboard read routes are internal (same-origin, no caller Bearer); a small, fixed set of write-proxy POSTs is admitted through an internal allow-list, and everything else falls through to the write Bearer. The exact allow-list membership — which write-proxy paths are admitted — is withheld (auth-page / GF-8 parity); the concept publishes, the map does not.

MethodPathAuthBodyResponseNotes
GET/api/dashboard/scheduled-jobsinternal{ data: jobs[] }Parses CS.AK.LISA.Data.LaunchdRegistry.md Registry table; 60s cache; extracts paired_skill
GET/api/dashboard/skillsinternal{ data: ParsedSkill[] }Parses PER.EX.NINJ_DEV-AI.Data.ClaudeCodeSkills.md; 60s cache
GET/api/dashboard/skills/usageinternal{ data: stats }Delegates to activityRepo.getSkillUsageStats()
GET/api/dashboard/vault-index/statusinternal{ scanning, documents, chunks }Passthrough
GET/api/dashboard/vault-index/type-effectivenessinternal{ data }Passthrough
GET/api/dashboard/timeline/stream/:dispatch_idinternalSSE streamDispatch-scoped event filter mirroring dispatch/timeline-routes.ts; native EventSource
POST/api/dashboard/<write-proxy>internal.strict() schemas{ data } / 409A fixed internal allow-list of parse/scan/eval + agent-memory-list proxies. Exact membership withheld; the token stays server-side and is never shipped to the browser.

The write-proxy POST routes are conditionally registered (only if their backing repo/layer is injected) and MUST stay in sync with the internal allow-list — per-PR review enforces the allow-list ↔ route sync (a route absent from the allow-list falls through to the Bearer gate and breaks tests before any internal surface ships).

MCP Tools

None.

4. Internal API

  • createDashboardApiRouter(vaultRoot, activityRepo, agentMemoryRepo?, vaultIndexLayer?, dispatchRepo?): { router }.
  • vault-parser.ts: parseMarkdownTable(content, requiredColumns), parseSkillsRegistry(markdown): ParsedSkill[], ParsedSkill interface.

5. Background Services

None. Two in-memory 60s caches (jobsCache, skillsCache) for the parsed-vault routes. The SSE route subscribes to timelineBus per-connection with a 15s keepalive ping and cleans up on req.on('close').

6. Data Contracts

  • AgentMemoryListBodySchema (.strict(), Dispatch 913 F-4) — agent_name (1–64), optional limit (1–500), memory_type (4-tier enum: task_blueprint/skill_heuristic/domain_pattern/lesson_learned), since (datetime). .strict() is load-bearing — extra fields → 400, not silently dropped.
  • EmptyBodySchema (.strict(), {}) — the two vault-index POST proxies assert no body fields sneak through (raiden D911 O-1).

7. Dependencies

  • Gateway modules consumed: activity/ (ActivityRepository), agent-memory/ (AgentMemoryRepository), vault-index/ (VaultIndexLayer, isScanInProgress), dispatch/ (DispatchRepository, timelineBus, TimelineEvent).
  • External libraries: express, zod, node:fs/promises, node:path.
  • Environment variables: VAULT_ROOT (falls back to the injected vaultRoot for the scan route).

8. Test Coverage

LayerFileCases
Routertest/dashboard-api.test.ts (267 L)8 it blocks
Proxy + auth posturetest/dashboard-proxy.test.ts (345 L)13 it blocks

Covers vault parsing (jobs/skills), the 5 proxy routes, .strict() body rejection, 409-on-scan-in-progress, and SSE passthrough semantics.

9. Known Limitations

  • Internal GET surface under public exposure: the dashboard read routes are served without a caller Bearer (internal same-origin). This is intentional (the dashboard read surface) but means these routes are world-readable if the gateway is exposed publicly — the load-bearing constraint that compute-cost routes must NOT mount here (they'd leak). New read proxies here must carry only non-sensitive data.
  • Vault-parse routes fail soft (return { data: [] } + a WARN) if the registry file is missing/renamed — a silent empty dashboard rather than an error. The LaunchdRegistry path is hard-coded (CS.AK.LISA.Data.LaunchdRegistry.md); note the registry was renamed to SchedulerRegistry.md per AK-320 elsewhere — potential path drift to verify (the route still points at LaunchdRegistry.md).
  • POST proxy registration is conditional on injected deps — a missing injection silently omits the route (which then 401s via the matrix rather than 404s meaningfully).

10. Change History

DateDispatchSummary
2026-07-042297Initial module spec (smoke-clone-3, LisaOS audit campaign Phase 4)
913 (R-2 #14)5 same-origin proxy routes for AK-415-broken dashboard surfaces; .strict() bodies
Module created — vault-parser jobs/skills routes

On this page