Dashboard API
Table of Contents
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.
| Field | Type | Label | Description |
|---|---|---|---|
| event_id | string | event_id is the agent_audit_log row id (UUID). Stable; suitable for client-side de-duplication when the stream reconnects. | |
| at | google.protobuf.Timestamp | at is the agent_audit_log.created_at timestamp. | |
| actor | string | actor 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). | |
| verb | string | verb 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. | |
| target | string | target 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). | |
| verdict | string | verdict 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_link | string | deep_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.
| Field | Type | Label | Description |
|---|---|---|---|
| pending_approvals | int32 | pending_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_usd | double | spend_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_usd | double | spend_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_active | int32 | agents_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_paused | int32 | agents_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_today | double | hours_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_at | google.protobuf.Timestamp | generated_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 Name | Request Type | Response Type | Description |
|---|---|---|---|
| GetTodayStrip | GetTodayStripRequest | GetTodayStripResponse | GetTodayStrip 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 Type | Notes | C++ | Java | Python | Go | C# | PHP | Ruby |
|---|---|---|---|---|---|---|---|---|
| double | double | double | float | float64 | double | float | Float | |
| float | float | float | float | float32 | float | float | Float | |
| int32 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |
| int64 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. | int64 | long | int/long | int64 | long | integer/string | Bignum |
| uint32 | Uses variable-length encoding. | uint32 | int | int/long | uint32 | uint | integer | Bignum or Fixnum (as required) |
| uint64 | Uses variable-length encoding. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum or Fixnum (as required) |
| sint32 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |
| sint64 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 | long | int/long | int64 | long | integer/string | Bignum |
| fixed32 | Always four bytes. More efficient than uint32 if values are often greater than 2^28. | uint32 | int | int | uint32 | uint | integer | Bignum or Fixnum (as required) |
| fixed64 | Always eight bytes. More efficient than uint64 if values are often greater than 2^56. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum |
| sfixed32 | Always four bytes. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |
| sfixed64 | Always eight bytes. | int64 | long | int/long | int64 | long | integer/string | Bignum |
| bool | bool | boolean | boolean | bool | bool | boolean | TrueClass/FalseClass | |
| string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode | string | string | string | String (UTF-8) |
| bytes | May contain any arbitrary sequence of bytes. | string | ByteString | str | []byte | ByteString | string | String (ASCII-8BIT) |