Skip to main content

Dashboard API

Table of Contents

Top

upsquad/dashboard/v1/dashboard.proto

ActivityEvent

ActivityEvent is the projection the Dashboard activity ticker renders. Each field is a UI-facing string rather than a schema reference because the ticker is a presentation surface — consumers that need raw audit detail call AuditService.ListAuditEvents.

Field semantics mirror the issue body sketch (#1041) and the v1 projection in internal/dashboard/projection.go.

FieldTypeLabelDescription
event_idstringevent_id is the agent_audit_log row id (UUID). Stable; suitable for client-side de-duplication when the stream reconnects.
atgoogle.protobuf.Timestampat is the agent_audit_log.created_at timestamp.
actorstringactor is a human-readable name for who performed the action — agent display name (from the agents table) or "system" for gateway-plane config events that have no agent_id (migration 078).
verbstringverb is a short action label suitable for headline copy: "approved", "paused", "deployed", "ran tool", "called LLM", etc. Derived from agent_audit_log.action_type via projection.go's verbForActionType.
targetstringtarget is a human-readable subject — the tool name for tool_call, the model id for llm_call, the config key for config.* events. Empty when the action has no meaningful target (e.g. session start).
verdictstringverdict is a one-of {pass, fail, info} categorisation — pass for successful actions, fail for blocked/denied/error actions, info for neutral lifecycle events (session_started, etc.).
deep_linkstringdeep_link is the relative URL path that opens the full audit detail view in the admin portal — typically "/admin/audit/<event_id>". Frontend prepends the tenant subdomain at render time.

GetTodayStripRequest

GetTodayStripRequest is intentionally empty. Tenant identity flows from the JWT-derived scope claims (org_id) on the gRPC metadata. A future optional as_of field could shift the "today" window for back-fill / snapshot replay; left out of v1 to keep the contract minimal.

GetTodayStripResponse

GetTodayStripResponse holds the six scalar values that compose the narrative strip plus a server-side generated_at for client-side staleness display.

Cost values use double USD (consistent with AnalyticsService, which also exposes cost as double rather than google.type.Money — see analytics.proto CostBreakdownEntry.cost / GetDashboardKPIsResponse.cost_usd). Deviation from issue body sketch: standardising on the in-tree convention avoids importing googleapis types that no other UpsQuad RPC uses.

FieldTypeLabelDescription
pending_approvalsint32pending_approvals is the count of governance_approvals rows where status='pending' for the caller's org, regardless of age. Sourced from the same projection ApprovalService.ListApprovals exposes.
spend_today_usddoublespend_today_usd is the sum of usage_records.quantity * unit_cost for the caller's org over [today_utc_midnight, now). Same scalar computation AnalyticsService.GetDashboardKPIs.cost_usd performs over its window, narrowed to the today bucket here.
spend_avg_30d_usddoublespend_avg_30d_usd is the 30-day daily average baseline used by the narrative's delta indicator (e.g. "↓18% vs avg"). Computed as sum(usage_records.quantity * unit_cost) over the 30 days preceding today_utc_midnight, divided by 30. Returns 0 when there is no 30-day history (avoids a divide-by-zero spike on tenant onboarding).
agents_activeint32agents_active is the distinct count of agents with at least one agent_sessions row in status='active' OR 'initializing' at query time. Soft-deleted agents are excluded.
agents_pausedint32agents_paused is the distinct count of agents with at least one agent_sessions row in status='suspended' at query time. Soft-deleted agents are excluded.
hours_saved_todaydoublehours_saved_today is the v1 heuristic COUNT(agent_audit_log rows in [today_utc_midnight, now)) * 0.5 i.e. each completed agent action is treated as ~30 minutes of human time avoided. Documented as a heuristic so consumers do not treat this as a precise productivity metric; the formula will be revisited when per-action duration estimates land.
generated_atgoogle.protobuf.Timestampgenerated_at is the server wall-clock at aggregation time, used by the client to render "as of HH:MM:SS" alongside the strip.

StreamActivityRequest

StreamActivityRequest is intentionally empty for v1. Tenant identity flows from the JWT-derived scope claims (org_id) on the gRPC metadata. Future filters (action_type allowlist, since-cursor for snapshot resume) would land here without breaking the stream contract.

DashboardService

DashboardService exposes aggregator RPCs that compose multiple existing reads into a single response. All RPCs are tenant-scoped via the JWT-derived scope claims injected by the scope middleware. There is no direct DB access that an existing service does not already permit; this is purely a composition boundary.

Method NameRequest TypeResponse TypeDescription
GetTodayStripGetTodayStripRequestGetTodayStripResponseGetTodayStrip returns the top-of-Dashboard narrative summary for the calling tenant. Composes pending-approval count, today's spend, the 30-day daily-average baseline, agent activity counts, and an estimated "hours saved" heuristic into a single round-trip.

The aggregation window for spend / hours-saved is "today" defined as the calling tenant's UTC midnight to now. (Tenant-local timezone is out of scope for v1; revisit when locale plumbing lands.) | | StreamActivity | StreamActivityRequest | ActivityEvent stream | StreamActivity opens a server-streaming RPC that pushes ActivityEvent messages for newly-recorded audit events as they happen. Backed by the Postgres LISTEN/NOTIFY channel audit_log (migration 082): every INSERT into agent_audit_log emits a NOTIFY whose payload is the new row id; the Hub re-fetches the row, projects it into ActivityEvent, and fans out to subscribers whose tenant scope matches.

Tenant isolation: the connection's tenant org_id is taken from the ambient scope (JWT-derived, set by the scope middleware). Events for any other tenant are dropped before reaching the wire. Re-fetch bypasses RLS row_security so the Hub can see every notified row, but the per-subscriber org filter is the security boundary.

Lifetime: the stream stays open until the caller cancels (or the process restarts). On cancellation the subscriber is removed from the Hub and any in-flight events are dropped. Clients that need a backlog should call AuditService.ListAuditEvents on connect; this RPC is forward-only. |

Scalar Value Types

.proto TypeNotesC++JavaPythonGoC#PHPRuby
doubledoubledoublefloatfloat64doublefloatFloat
floatfloatfloatfloatfloat32floatfloatFloat
int32Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead.int32intintint32intintegerBignum or Fixnum (as required)
int64Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead.int64longint/longint64longinteger/stringBignum
uint32Uses variable-length encoding.uint32intint/longuint32uintintegerBignum or Fixnum (as required)
uint64Uses variable-length encoding.uint64longint/longuint64ulonginteger/stringBignum or Fixnum (as required)
sint32Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s.int32intintint32intintegerBignum or Fixnum (as required)
sint64Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s.int64longint/longint64longinteger/stringBignum
fixed32Always four bytes. More efficient than uint32 if values are often greater than 2^28.uint32intintuint32uintintegerBignum or Fixnum (as required)
fixed64Always eight bytes. More efficient than uint64 if values are often greater than 2^56.uint64longint/longuint64ulonginteger/stringBignum
sfixed32Always four bytes.int32intintint32intintegerBignum or Fixnum (as required)
sfixed64Always eight bytes.int64longint/longint64longinteger/stringBignum
boolboolbooleanbooleanboolboolbooleanTrueClass/FalseClass
stringA string must always contain UTF-8 encoded or 7-bit ASCII text.stringStringstr/unicodestringstringstringString (UTF-8)
bytesMay contain any arbitrary sequence of bytes.stringByteStringstr[]byteByteStringstringString (ASCII-8BIT)