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
| File | Lines | Responsibility |
|---|---|---|
index.ts | 291 | createDashboardApiRouter — jobs/skills parse routes, 5 AK-415 proxy routes, SSE passthrough |
vault-parser.ts | 189 | parseMarkdownTable, parseSkillsRegistry + ParsedSkill |
| Total | 480 |
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.
| Method | Path | Auth | Body | Response | Notes |
|---|---|---|---|---|---|
| GET | /api/dashboard/scheduled-jobs | internal | — | { data: jobs[] } | Parses CS.AK.LISA.Data.LaunchdRegistry.md Registry table; 60s cache; extracts paired_skill |
| GET | /api/dashboard/skills | internal | — | { data: ParsedSkill[] } | Parses PER.EX.NINJ_DEV-AI.Data.ClaudeCodeSkills.md; 60s cache |
| GET | /api/dashboard/skills/usage | internal | — | { data: stats } | Delegates to activityRepo.getSkillUsageStats() |
| GET | /api/dashboard/vault-index/status | internal | — | { scanning, documents, chunks } | Passthrough |
| GET | /api/dashboard/vault-index/type-effectiveness | internal | — | { data } | Passthrough |
| GET | /api/dashboard/timeline/stream/:dispatch_id | internal | — | SSE stream | Dispatch-scoped event filter mirroring dispatch/timeline-routes.ts; native EventSource |
| POST | /api/dashboard/<write-proxy> | internal | .strict() schemas | { data } / 409 | A 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[],ParsedSkillinterface.
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), optionallimit(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 injectedvaultRootfor the scan route).
8. Test Coverage
| Layer | File | Cases |
|---|---|---|
| Router | test/dashboard-api.test.ts (267 L) | 8 it blocks |
| Proxy + auth posture | test/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 toSchedulerRegistry.mdper AK-320 elsewhere — potential path drift to verify (the route still points atLaunchdRegistry.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
| Date | Dispatch | Summary |
|---|---|---|
| 2026-07-04 | 2297 | Initial 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 |