Status Monitor
Feature Overview [edit]
When memo serve --sse runs, a read-only operations companion starts in the same process on http://127.0.0.1:3124/ (config ports.status, override --status-port, disable --no-status). Stdio memo serve starts the companion only when --status/--status-port is explicitly passed. The page is a bookmarkable, self-contained dark-theme HTML app (no runtime CDN dependencies, stable path /, <title> contains spec-memo) with tabbed workspaces:
- Activity & Status: server health cards, topology badge, vault/project list and filter, live MCP tool/HTTP activity log streamed over SSE, and client registry.
- Memory, Prompts & Intent Stories, Activity & Invoicing, Derived Rules, Wiki, Vaults: master-detail explorers over vault data.
- Backups: dedicated snapshot management tab (create/list/inspect/restore/delete/download).
The monitor never mutates vault records except the deliberate operator endpoints: backup create/restore/delete and reset (POST /api/vaults/backups, POST /api/vaults/restore, DELETE /api/vaults/backups/{filename}, POST /api/vaults/reset, POST /api/vaults/export, POST /api/vaults/import, POST /api/wiki/regenerate) and the vault-manager routes. Everything else is read-only.
Business Rules & Logic [edit]
- Bind default is loopback
127.0.0.1; binding non-loopback without an auth token throws at startup. Auth precedence:options.authToken,SPEC_MEMO_AUTH_TOKEN,SPEC_MEMO_STATUS_TOKEN,SPEC_MEMO_SSE_TOKEN. - Authorized requests may present
Authorization: Bearer <token>,?token=/?authToken=, or thespec_memo_status_tokencookie.GET /loginshows a sign-in form;POST /api/auth/loginsets an HttpOnlySameSite=Strictcookie and redirects;/api/auth/logoutclears it.?token=on/is promoted into a cookie and stripped from the URL. When a token is configured, unauthenticated API calls return401 { error: "Unauthorized" }, and/redirects to/login?next=...(same-origin path only). - Activity event contract:
{ id, seq, ts, type: http|tool|system, kind: read|write|meta, ok, durationMs, summary, tool?, method?, path?, statusCode?, projectId?, clientIp?, clientName?, clientType?, operation? }. The ring buffer holds at most 200 events (FIFO drop;seqkeeps increasing). All fields are sanitized (secret redaction + absolute-path redaction) before buffering. - Project filter semantics: All vaults shows every event; a specific vault shows events where
projectIdmatches orprojectIdis absent, so transport/system lines stay visible while drilling into one project. The filter is reflected in the URL?project=andsessionStorage, and applies client-side plus as a serverproject=query. GET /api/eventsreturns{ events, nextSeq };GET /api/events/streamsends a namedsnapshotevent thenactivityevents, honoringproject=andafterSeq(invalidafterSeq->0).- Backups tab rules: Create Backup with All vaults selected opens a confirmation stating all vaults are included and calls the persist API with
projectIdomitted andconfirmFullBackup: true; omitting the confirm returns400 { error: "confirmFullBackup required for full backup" }and writes nothing. A selected project creates a single-project snapshot. UnknownprojectIdreturns400 { error: "Unknown projectId" }. - Inventory rows show filename, created, human-readable size, entry count, scope (
full/project), and encryption. Filters: filename substring, scope, project, encryption, created-at range, size bounds, and multi-select kinds present (AND across selected kinds). Invalidencryptedvalues return400. - Row actions: Restore (overwrite warning + optional password), Download (streams
application/ziporapplication/jsonwithContent-Disposition: attachment), Delete (operator types the exact filename in the UI; API requires{ confirm: true }, else400and the file stays). Clicking a row opens the details drawer (GET /api/vaults/backups/{filename}/inspect). Inspect rejects passwords in the query string (400); an encrypted archive without a password returns200 { ok: true, encrypted: true, inspectable: false }; a wrong password returns401with no vault mutation; a missing file returns404; a filename escapingbackups/returns400. - Each completed create/restore/delete/reset emits exactly one
type: "system",kind: "write"activity event withokand a summary containing the filename or restore counts; no secrets or absolute paths. - Mutating routes require the configured token and take the vault lock via
withVaultLock, so export/import/reset/restore/delete do not overlap. Import caps multipart bodies at 64 MiB (413when exceeded). - Unknown
/api/*paths return404 { error: "Not found" }without stack traces.
Technical Architecture [edit]
src/status.tsstartStatusServer(options)returns{ server, port, host, url, close }; the HTML is generated bygenerateStatusHtmland the login page bygenerateLoginHtml.close()invokescloseAllConnections()thenserver.close().GET /api/statusreturnsstatus,service,version,host,port,mode,role,topology(TopologyInfo),mcp({ host, port, activeTransports, available }or{ available: false, disconnected: true }),projectsCount,uptimeMs(frombus.startedAt),eventsBuffered,clients,activeClientsCount. Topology role:local-vault,intermediary-proxy(hybrid or remote/proxy), orfinal-remote(SSE daemon).GET /api/vaultsreturns the enriched array fromgetVaultProjectList(id,displayName,aliasOf,recordCount).GET /api/clientsreturnsbus.listClients(). Vault-manager routes:POST/DELETE /api/vaults/alias,POST /api/vaults/merge,/rename,/create,PATCH /api/vaults/{id}orPOST /api/vaults/update,POST /api/vaults/delete,POST /api/vaults/sync(directionpull|push|both,syncDual->200/502).- Activity bus (
src/activity.ts):createActivityBus({ capacity })withcapture,list(filter?),subscribe(listener, filter?), client registry (registerClient,updateClientActivity,disconnectClient,listClients, capped at 100 clients),close. HTTP request lifecycle is captured insrc/server.ts(captureHttpEvent) and the status server records telemetry for every request. - Backup routes are backed by
src/backup.ts(persistVaultBackup,listBackups,filterBackupList,inspectBackup,deleteBackup,resolveBackupPath,exportVault,importVault,resetVault) and zip helpers insrc/status-backup.ts(packVaultZip,unpackVaultZip,parseMultipartFormData).exportVaultwalksRECORD_SUBDIRS(traps,decisions,specs,plans,logs,reviews,scratch,prompts,sessions);staterecords live underplans/. Archives packvault-backup.jsonas plaintextspec-memo-vault-v1or (with password)spec-memo-encrypted-vault-v1. - Provenance:
0023-mcp-status-monitor.spec.md(bus, activity log, vault filter; spec default:3001),0026-status-vault-backup-ui.spec.md(sidebar export/import; later moved),0030-vault-reset-and-proxy-monitor.spec.md(reset, backups list, restore, 3-mode topology),0032-status-backup-page.spec.md(dedicated Backups tab, full-backup confirm, inventory filters, restore/delete/download/inspect; supersedes the old All-vaults disabled export). See Backup & Restore, Deployment Modes, and SSE Transport.