Governance API
Table of Contents
upsquad/governance/v1/approval.proto
Approval
Approval is the read-projection of a governance_approvals row + its resolved fields.
| Field | Type | Label | Description |
|---|---|---|---|
| approval_id | string | approval_id is the UUID primary key. | |
| org_id | string | org_id is the tenant scope. | |
| session_id | string | session_id is the paused session awaiting this approval. | |
| agent_id | string | agent_id is the agent that requested the action. | |
| tool_name | string | tool_name identifies the gated tool. | |
| target | string | target is the specific resource. | |
| status | string | status is one of pending | |
| template | string | template is the dev_only | |
| required_clearance | int32 | required_clearance — minimum clearance the decider must hold. | |
| requested_at | google.protobuf.Timestamp | requested_at is the insertion wall-clock time. | |
| deadline | google.protobuf.Timestamp | deadline is the scheduler-enforced expiry. | |
| resolved_at | google.protobuf.Timestamp | resolved_at is set iff status in {approved,denied,expired}. | |
| resolved_by | string | resolved_by is the operator_id that won first-write (empty on expiry). | |
| resolution_reason | string | resolution_reason is the operator-provided rationale, if any. | |
| channels_dispatched | string | repeated | channels_dispatched is the set of adapters that have fanned out. |
| delegation_chain | DelegationLink | repeated | delegation_chain records the delegation hops (LLD 8 populates). |
| policy_id | string | policy_id that produced requires_approval. | |
| escalation_level | int32 | escalation_level — 0 original; incremented on each escalation (LLD 9). | |
| metadata | Approval.MetadataEntry | repeated | metadata is the per-request context captured at RequestApproval time (issue #1675), round-tripped from the governance_approvals.metadata JSONB column so it is visible on the ListApprovals read path — not only GetApproval. It carries a human-readable "summary" key the approvals queue renders BEFORE a decision (e.g. "Appoint Vaisakh as additional admin of Go-to-Market"), plus structured keys (op_type, unit_id, unit_name, candidate_name, op_lines, …) per gated change kind. Names are resolved at request time (a later rename does not rewrite the summary). Empty for pre-#1675 approvals — clients must degrade gracefully. |
| action_type | string | action_type is the governance action that raised the approval (e.g. "tool_call", "approval_gate" for workflow/coordinator governance gates, "a2a_egress", "org_draft_publish"). It is round-tripped from the governance_approvals.action_type column — written by every open path — so the Approvals queue can render workflow context and the client resume router (client#492) can route on the real primary signal instead of the tool_name fallback (issue #1679 / WF-01). Empty only for legacy rows predating the column default; clients must degrade gracefully. | |
| agent_name | string | agent_name is the DISPLAY NAME of the requesting agent, captured at RequestApproval time and denormalised onto the row (issue #2325). |
"Who is asking" is a first-order input to an approval decision — an L4 request from the principal architect and an L4 request from a smoke-test delegate warrant different scrutiny — and before #2325 only the raw agent_id UUID reached the reviewer.
WRITE-TIME, NOT A READ-TIME JOIN (architect ruling on #2325): - Correctness. An approval is a point-in-time decision record. A join to agents.name on the read path would show the reviewer the agent's CURRENT name — or nothing at all after a retire/delete — not who actually asked. This mirrors the #1675 precedent documented on metadata above: names are resolved at request time, and a later rename does not rewrite the record. - Scaling. A per-row join on the hot ListApprovals path to render a display string is a cost paid on every queue poll, forever.
Empty when the requester could not be resolved to a name: a sessionless / system request that carries no agent_id, an agents row already gone at lookup time, or a legacy row predating migration 185. Clients MUST degrade to rendering agent_id. |
Approval.MetadataEntry
| Field | Type | Label | Description |
|---|---|---|---|
| key | string | ||
| value | string |
DelegateRequest
DelegateRequest transfers approval authority to another member. Behaviour deferred to LLD 8.
| Field | Type | Label | Description |
|---|---|---|---|
| org_id | string | org_id for RLS. | |
| approval_id | string | approval_id of the pending approval. | |
| from_member_id | string | from_member_id is the current approver. | |
| to_member_id | string | to_member_id is the delegatee (must satisfy required_clearance). | |
| reason | string | reason is an audit-trail explanation. |
DelegateResponse
DelegateResponse returns the updated approval.
| Field | Type | Label | Description |
|---|---|---|---|
| approval | Approval | approval is the post-delegation row. |
DelegationLink
DelegationLink is one hop in the delegation chain.
| Field | Type | Label | Description |
|---|---|---|---|
| from_member_id | string | from_member_id is the delegator. | |
| to_member_id | string | to_member_id is the delegatee. | |
| to_clearance | int32 | to_clearance is the delegatee's clearance at delegation time. | |
| at | google.protobuf.Timestamp | at is the delegation wall-clock time. | |
| reason | string | reason is the audit-trail explanation. |
EmergencyRotateHMACRequest
EmergencyRotateHMACRequest is the admin-authenticated payload for rotating a tenant's approval-callback HMAC key out-of-band.
| Field | Type | Label | Description |
|---|---|---|---|
| org_id | string | org_id is the tenant whose HMAC secret should be rotated. | |
| reason | string | reason is a free-form audit-trail explanation for why the admin triggered the rotation (e.g. "suspected leak in incident X-2026-042"). |
EmergencyRotateHMACResponse
EmergencyRotateHMACResponse reports the rotation outcome. The actor (admin subject) is recorded in the audit row server-side; it is never returned in the response body.
| Field | Type | Label | Description |
|---|---|---|---|
| previous_key_id | string | previous_key_id identifies the key that was just retired. Empty if the tenant had no prior secret (first rotation after migration). | |
| new_key_id | string | new_key_id identifies the freshly-issued secret. | |
| rotated_at | google.protobuf.Timestamp | rotated_at is the wall-clock time the rotation committed. |
GetApprovalRequest
GetApprovalRequest identifies a single approval.
| Field | Type | Label | Description |
|---|---|---|---|
| org_id | string | org_id for RLS. | |
| approval_id | string | approval_id is the UUID to fetch. |
GetApprovalResponse
GetApprovalResponse wraps a single Approval for GetApproval. Carries optional risk + impact enrichment for the redesigned client portal /approvals detail panel (GAP-6, tracker #1031). Both fields are populated server-side by the GetApproval handler; clients that pre- date GAP-6 simply ignore the new fields, so the surface remains backwards compatible.
| Field | Type | Label | Description |
|---|---|---|---|
| approval | Approval | approval is the requested row. | |
| risk | RiskAssessment | risk is a heuristic assessment derived from the recent history of (agent_id, tool_name) approvals for the tenant. Always populated; an empty store yields RISK_LEVEL_LOW with similar_count=0. | |
| impact_summary | string | impact_summary is a one-line plain-English description of what committing this approval would let the agent do — composed from template + tool_name + target. Never localised (English only) and never empty for a well-formed approval. |
ListApprovalsRequest
ListApprovalsRequest carries page-based pagination + optional filters.
| Field | Type | Label | Description |
|---|---|---|---|
| org_id | string | org_id for RLS. | |
| status_filter | string | status_filter, if set, restricts to approvals with this status. | |
| session_id_filter | string | session_id_filter, if set, restricts to approvals for this session. | |
| page | int32 | page is the 1-based page number (default 1). | |
| page_size | int32 | page_size caps at 100 (default 50). |
ListApprovalsResponse
ListApprovalsResponse carries a single page plus the total count.
| Field | Type | Label | Description |
|---|---|---|---|
| approvals | Approval | repeated | approvals is the current page, ordered by requested_at DESC. |
| total_count | int32 | total_count is the unpaginated count matching the filter. |
ListRecentDecisionsRequest
ListRecentDecisionsRequest carries the window + cursor for the historical-decisions read.
| Field | Type | Label | Description |
|---|---|---|---|
| org_id | string | org_id for RLS. | |
| window_days | int32 | window_days bounds the resolved_at lower bound to now() - N days. Default 7 when zero/unset; clamped server-side to [1, 90] so the table scan stays bounded. | |
| page_size | int32 | page_size caps at 100 (default 50). Server clamps invalid values. | |
| page_token | string | page_token is the opaque cursor returned by a previous response. Empty for the first page. | |
| verdict_filter | string | verdict_filter restricts the result set to one of "approved", "denied", or "expired". Empty returns all three buckets. | |
| agent_id_filter | string | agent_id_filter restricts the result set to decisions on a single agent's approvals. Empty matches all agents. |
ListRecentDecisionsResponse
ListRecentDecisionsResponse carries one page plus the next cursor.
| Field | Type | Label | Description |
|---|---|---|---|
| decisions | RecentDecision | repeated | decisions is the current page, ordered by decided_at DESC. |
| next_page_token | string | next_page_token is empty when no further pages exist; otherwise pass it back as ListRecentDecisionsRequest.page_token. |
RecentDecision
RecentDecision is the trimmed projection of a resolved approval row for the Approvals "Recent decisions" tab. Drops args, metadata, and the delegation chain so this list endpoint stays cheap.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | id is the approval UUID. | |
| verdict | string | verdict is one of "approved" | |
| decided_at | google.protobuf.Timestamp | decided_at is the wall-clock time the decision was recorded (governance_approvals.resolved_at). | |
| decided_by | string | decided_by is the operator member_id that won first-write. Empty string when verdict=="expired" (no operator on scheduler-driven expiry). | |
| summary | string | summary is a short human-readable label for the gated action, server-built from tool_name + target. Examples: "github.merge owner/repo#42" "tool_call deploy.production" | |
| tool_name | string | tool_name is preserved verbatim so UIs can group / filter without string-parsing the summary. | |
| target | string | target is preserved verbatim for the same reason. | |
| agent_id | string | agent_id of the requesting agent, surfaced for the per-agent column. | |
| session_id | string | session_id of the paused session that owned the approval. |
RecordDecisionRequest
RecordDecisionRequest is the single converged entry point for all approval decisions regardless of channel. First-write-wins.
| Field | Type | Label | Description |
|---|---|---|---|
| org_id | string | org_id for RLS. | |
| approval_id | string | approval_id of the pending approval to resolve. | |
| decision | Decision | decision is the recorded outcome (required; must be APPROVED or DENIED). | |
| operator_id | string | operator_id is the member recording the decision. | |
| reason | string | reason is a human-readable explanation written to the audit trail. | |
| channel | Channel | channel identifies the adapter that recorded the decision. | |
| idempotency_key | string | idempotency_key prevents double-submission from webhook retries. | |
| response_payload | bytes | response_payload is the LBE-B answer envelope {choice, text, answered_by} for a user_input prompt (#2238 / #2249). It is written atomically with the status flip through the SAME first-write CAS (Store.TryResolveWithPayload), so the resolved row is the answer's single source of truth (the interpreter reads it back as ${step.choice} / ${step.text}). ADDITIVE and OPTIONAL: nil/empty on every approval_gate / tool_call decision, which resolve byte-identically to the pre-#2249 path. Raw JSON bytes so the server does not re-serialize the caller's envelope. LBE-B2 (#2249). |
RecordDecisionResponse
RecordDecisionResponse reports the first-write-wins outcome.
| Field | Type | Label | Description |
|---|---|---|---|
| result | RecordResult | result is OK, DUPLICATE, or CONFLICT per §6 state machine. | |
| approval | Approval | approval is the current post-transaction row. |
RequestApprovalRequest
RequestApprovalRequest carries the fields needed to open a pending approval. Dedup is on (org_id, session_id, tool_name, args_sha256).
| Field | Type | Label | Description |
|---|---|---|---|
| org_id | string | org_id is the tenant scope (RLS). | |
| team_id | string | team_id scopes the approval to a team. | |
| session_id | string | session_id ties the approval to a paused agent session. | |
| agent_id | string | agent_id is the agent that requested the action. | |
| member_id | string | member_id is the agent's effective member identity. | |
| clearance | int32 | clearance of the requesting principal. | |
| action_type | string | action_type is the governance action type (e.g. "tool_call"). | |
| tool_name | string | tool_name identifies the gated tool. | |
| target | string | target is the specific resource (e.g. "github.merge"). | |
| args | bytes | args is the full tool args payload (truncated > 256 KiB). | |
| args_sha256 | string | args_sha256 is the dedup key; sha256 over the full args bytes. | |
| policy_id | string | policy_id is the policy that produced requires_approval. | |
| required_clearance | int32 | required_clearance — minimum clearance the decider must hold. | |
| template | string | template is one of dev_only | |
| deadline | google.protobuf.Timestamp | deadline is the scheduler-enforced expiry wall-clock time. | |
| channels | string | repeated | channels enumerate the fan-out adapters (LLD 7 implements dispatch). |
| metadata | RequestApprovalRequest.MetadataEntry | repeated | metadata is arbitrary per-request context for audit. |
RequestApprovalRequest.MetadataEntry
| Field | Type | Label | Description |
|---|---|---|---|
| key | string | ||
| value | string |
RequestApprovalResponse
RequestApprovalResponse carries the approval_id and dedup status.
| Field | Type | Label | Description |
|---|---|---|---|
| approval_id | string | approval_id is the UUID of the (possibly pre-existing) pending row. | |
| was_deduplicated | bool | was_deduplicated is true when an open approval matched the dedup key. | |
| deadline | google.protobuf.Timestamp | deadline is the effective expiry (the existing row's expires_at on dedup, or the requested deadline on fresh insert). |
RequestChangesRequest
RequestChangesRequest is the operator-driven 'Send back for changes' payload (GAP-9). The approval MUST currently be status='pending'; resolved approvals (approved/denied/expired) cannot be sent back.
| Field | Type | Label | Description |
|---|---|---|---|
| org_id | string | org_id for RLS. | |
| approval_id | string | approval_id of the pending approval to flag. | |
| operator_id | string | operator_id is the member sending the approval back. Recorded on the approval_events row as actor_member_id. | |
| comment | string | comment is the free-text rationale shown to the requester. Required; empty comments are rejected with InvalidArgument because the whole point of the action is the message back. |
RequestChangesResponse
RequestChangesResponse acknowledges the request and returns the post-write delegation chain so the dashboard can re-render without a follow-up GetApproval call.
| Field | Type | Label | Description |
|---|---|---|---|
| success | bool | success is true when the audit row was committed. False today is unreachable — failures surface as gRPC errors, not success=false — but the field is kept so callers can branch defensively if a future wave adds soft-failure modes. | |
| updated_chain | DelegationLink | repeated | updated_chain is the delegation chain at the time of the write, ordered oldest → newest. Empty when no delegations exist. |
RiskAssessment
RiskAssessment carries the heuristic risk score and the inputs that produced it. Fields are stable and additive; clients can ignore fields they do not yet understand.
| Field | Type | Label | Description |
|---|---|---|---|
| level | RiskLevel | level is the bucketed risk tier (see RiskLevel docs). | |
| similar_count | int32 | similar_count is the number of recent approvals (LookbackN=20) matching (agent_id, tool_name) for this tenant. Includes the current pending row when the dedup key already existed. | |
| explanation | string | explanation is a human-readable, English-only sentence describing why the level was chosen — e.g. "3 of last 5 approvals for this (agent, tool) were denied". Never empty; defaults to a no-history sentence when similar_count == 0. | |
| approved_count | int32 | approved_count is the number of resolved-approved rows in the lookback window. Sum of approved+denied+expired may be < similar_count when some rows are still pending. | |
| denied_count | int32 | denied_count is the number of resolved-denied rows in the lookback window. | |
| expired_count | int32 | expired_count is the number of resolved-expired rows in the lookback window. |
Channel
Channel identifies the adapter that recorded a decision. Dashboard is the built-in operator UI; the others are LLD 7 adapters.
| Name | Number | Description |
|---|---|---|
| CHANNEL_UNSPECIFIED | 0 | CHANNEL_UNSPECIFIED is the zero value. |
| CHANNEL_DASHBOARD | 1 | CHANNEL_DASHBOARD is the operator console. |
| CHANNEL_EMAIL | 2 | CHANNEL_EMAIL is the signed email reply webhook. |
| CHANNEL_SLACK | 3 | CHANNEL_SLACK is the Slack interactive message webhook. |
| CHANNEL_SCM | 4 | CHANNEL_SCM is the SCM (GitHub/GitLab) review webhook. |
| CHANNEL_API | 5 | CHANNEL_API is a direct programmatic RecordDecision caller. |
Decision
Decision is the outcome recorded by RecordDecision.
| Name | Number | Description |
|---|---|---|
| DECISION_UNSPECIFIED | 0 | DECISION_UNSPECIFIED is the zero value; never a valid input. |
| DECISION_APPROVED | 1 | DECISION_APPROVED resumes the paused session with operator_input. |
| DECISION_DENIED | 2 | DECISION_DENIED terminates the paused session. |
RecordResult
RecordResult disambiguates RecordDecision outcomes.
| Name | Number | Description |
|---|---|---|
| RECORD_RESULT_UNSPECIFIED | 0 | RECORD_RESULT_UNSPECIFIED is the zero value. |
| RECORD_RESULT_OK | 1 | RECORD_RESULT_OK — first writer, state transitioned. |
| RECORD_RESULT_DUPLICATE | 2 | RECORD_RESULT_DUPLICATE — same decision already recorded, or idempotency_key collision. State unchanged; idempotent from the caller's perspective. |
| RECORD_RESULT_CONFLICT | 3 | RECORD_RESULT_CONFLICT — a different decision already won. State unchanged; channel_conflict event written for auditability. |
RiskLevel
RiskLevel buckets the heuristic risk score into UI-friendly tiers. LOW = green, MED = amber, HIGH = red on the dashboard verdict bar.
| Name | Number | Description |
|---|---|---|
| RISK_LEVEL_UNSPECIFIED | 0 | RISK_LEVEL_UNSPECIFIED is the zero value; never returned by the server. |
| RISK_LEVEL_LOW | 1 | RISK_LEVEL_LOW — no recent denials; non-critical template. |
| RISK_LEVEL_MED | 2 | RISK_LEVEL_MED — at least one recent denial OR full_pipeline template. |
| RISK_LEVEL_HIGH | 3 | RISK_LEVEL_HIGH — denial-rate >= 40% in recent window OR critical_path template. |
ApprovalAdminService
ApprovalAdminService hosts platform-admin-only RPCs on the approval subsystem. Callers MUST carry clearance >= PLATFORM_ADMIN (100). This is a narrow, explicitly-separate service so admin surfaces and tenant surfaces cannot accidentally share an auth middleware or a Connect handler mount. Wave 2.1 (#424) migrates the previous HTTP admin endpoint (POST /internal/admin/approvals/rotate-hmac) onto this service.
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| EmergencyRotateHMAC | EmergencyRotateHMACRequest | EmergencyRotateHMACResponse | EmergencyRotateHMAC rotates the approval-callback HMAC key for a tenant with grace_until=now and revokes every outstanding signed link for the old key. Audit row attributes action=admin and records the Clerk subject of the admin operator. Enforced server-side: platform-admin clearance gate + per-org rate limit (1/min) to prevent a compromised admin credential from thrashing tenant HMAC secrets. See LLD §6 + PR #423 blocker 5. |
ApprovalService
ApprovalService is the tenant-facing entry point for the Wave 2 approval-chain engine. All mutating RPCs are first-write-wins on approval status (founder decision #6) and dedup on (org_id, session_id, tool_name, args_sha256).
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| RequestApproval | RequestApprovalRequest | RequestApprovalResponse | RequestApproval is called from the runtime (MCP middleware / worker control plane) when a governance Check returns requires_approval. Idempotent on (org_id, session_id, tool_name, args_sha256): when an approval for the same dedup key is already pending, the existing approval_id is returned and was_deduplicated=true. Caller is responsible for invoking LifecycleService.PauseSession with correlation_id=approval_id after this returns. |
| RecordDecision | RecordDecisionRequest | RecordDecisionResponse | RecordDecision is the single converged entry point for all channels (dashboard, email webhook, Slack webhook, SCM webhook, API). Transactionally UPDATE ... WHERE status='pending' — first writer wins; losers get RECORD_RESULT_CONFLICT (different decision) or RECORD_RESULT_DUPLICATE (identical decision or idempotency_key collision). On OK: audit + approval_events written in the same tx, LifecycleService.ResumeSession (APPROVED) or TerminateSession (DENIED) invoked on the paused session. |
| Delegate | DelegateRequest | DelegateResponse | Delegate transfers approval authority to another member. The delegatee MUST have clearance >= required_clearance. LLD 8 will implement the behaviour; the RPC is defined here for contract stability so channel adapters can reference it today. |
| GetApproval | GetApprovalRequest | GetApprovalResponse | GetApproval returns a single approval by id, RLS-scoped to org_id. |
| ListApprovals | ListApprovalsRequest | ListApprovalsResponse | ListApprovals returns a paginated list of approvals for the tenant. |
| RequestChanges | RequestChangesRequest | RequestChangesResponse | RequestChanges marks the approval as awaiting requester response and posts a comment to the approval thread. Powers the 'Send back for changes' button on the operator triage workspace (GAP-9). The approval row stays status='pending' and the delegation chain stays in WAITING — only an append-only audit row of type 'changes_requested' is written. The operator's free-text comment is carried in that event's payload so the dashboard timeline can render the conversation. Returns the updated chain so the client does not need a follow-up GetApproval round-trip. |
| ListRecentDecisions | ListRecentDecisionsRequest | ListRecentDecisionsResponse | ListRecentDecisions returns approvals that have been resolved (approved |
upsquad/governance/v1/egress.proto
AddEntryRequest
AddEntryRequest appends a new allow-list entry for the calling tenant.
| Field | Type | Label | Description |
|---|---|---|---|
| tenant_id | string | tenant_id is the owning tenant UUID. | |
| domain_pattern | string | domain_pattern is the egress target (exact host or *.suffix); no scheme, no path. | |
| justification | string | justification is the free-form reason string (>= 20 chars, server-enforced). | |
| reason | Reason | reason is the categorical Reason enum value for the entry. |
AddEntryResponse
AddEntryResponse carries the server-materialised entry with its assigned id + created_at.
| Field | Type | Label | Description |
|---|---|---|---|
| entry | AllowlistEntry | entry is the newly created entry. |
AllowlistEntry
AllowlistEntry is a single row of the tenant_egress_allowlist surfaced to the admin RPCs. Maps 1:1 to the PG row plus computed revoked/active state.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | id is the server-assigned UUID primary key of the entry. | |
| tenant_id | string | tenant_id is the owning tenant UUID. Every entry is tenant-scoped. | |
| domain_pattern | string | domain_pattern is the egress target (exact host or *.suffix); no scheme, no path. | |
| added_by | string | added_by is the member UUID that created the entry. | |
| justification | string | justification is the free-form reason string (>= 20 chars). | |
| reason | Reason | reason is the categorical Reason enum value for the entry. | |
| created_at | google.protobuf.Timestamp | created_at is the wall-clock time the entry was inserted. | |
| revoked_at | google.protobuf.Timestamp | revoked_at is the wall-clock time the entry was soft-deleted, or unset while the entry is active. |
ListAllowlistRequest
ListAllowlistRequest asks for the non-revoked allow-list entries for the calling tenant.
| Field | Type | Label | Description |
|---|---|---|---|
| tenant_id | string | tenant_id is the owning tenant UUID. |
ListAllowlistResponse
ListAllowlistResponse carries the current active allow-list entries.
| Field | Type | Label | Description |
|---|---|---|---|
| entries | AllowlistEntry | repeated | entries is the list of non-revoked entries ordered by created_at asc. |
RevokeEntryRequest
RevokeEntryRequest soft-deletes an existing allow-list entry by ID.
| Field | Type | Label | Description |
|---|---|---|---|
| tenant_id | string | tenant_id is the owning tenant UUID (double-checked against entry_id to prevent cross-tenant revocation). | |
| entry_id | string | entry_id is the UUID of the entry to revoke. |
RevokeEntryResponse
RevokeEntryResponse carries the revoked entry with revoked_at set.
| Field | Type | Label | Description |
|---|---|---|---|
| entry | AllowlistEntry | entry is the revoked entry post-update. |
Reason
Reason enumerates the allowed values of tenant_egress_allowlist.reason. The enum order is append-only; renumbering is a breaking change.
| Name | Number | Description |
|---|---|---|
| REASON_UNSPECIFIED | 0 | REASON_UNSPECIFIED is the default/unknown sentinel; requests that send this value are rejected by the server. |
| REASON_MCP_INTEGRATION | 1 | REASON_MCP_INTEGRATION marks an entry created as part of enabling an MCP tool integration (auto-seeded from the integration catalog). |
| REASON_TENANT_WEBHOOK | 2 | REASON_TENANT_WEBHOOK marks an entry for an outbound tenant-owned webhook (approval callbacks, agent lifecycle notifications). |
| REASON_THIRD_PARTY_API | 3 | REASON_THIRD_PARTY_API marks an entry for a general third-party SaaS API the tenant has authorised. |
| REASON_INTERNAL_SERVICE | 4 | REASON_INTERNAL_SERVICE marks an entry for a platform-internal service reachable over the public internet (e.g. an isolated microservice with no private-network reach). |
| REASON_OTHER | 5 | REASON_OTHER is a catch-all for entries that do not fit the above categories; the justification must explain the use case. |
EgressService
EgressService exposes the tenant egress allow-list admin RPCs.
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| ListAllowlist | ListAllowlistRequest | ListAllowlistResponse | ListAllowlist returns non-revoked entries for the calling tenant. |
| AddEntry | AddEntryRequest | AddEntryResponse | AddEntry appends a new allow-list entry. The server enforces justification length >= 20 chars and rejects TLD-only wildcards. |
| RevokeEntry | RevokeEntryRequest | RevokeEntryResponse | RevokeEntry soft-deletes an entry by ID. The audit trail is preserved. |
upsquad/governance/v1/governance.proto
AuditEvent
AuditEvent represents a single row from the agent_audit_log table.
| Field | Type | Label | Description |
|---|---|---|---|
| event_id | string | event_id is the primary key (UUID). | |
| timestamp | google.protobuf.Timestamp | timestamp is when the event was created. | |
| agent_id | string | agent_id is the agent that performed the action. | |
| session_id | string | session_id is the session in which the action occurred. | |
| action_type | string | action_type is the type of action (e.g. "tool_call", "llm_call"). | |
| input_hash | string | input_hash is the hash of the action input. | |
| output_hash | string | output_hash is the hash of the action output. | |
| detail | string | detail is the JSONB detail column serialised as a JSON string. | |
| duration_ms | int32 | duration_ms is the action duration in milliseconds. | |
| token_usage | string | token_usage is the JSONB token usage column serialised as a JSON string. |
CascadeTraceEntry
CascadeTraceEntry records the outcome of a single cascade layer walk.
| Field | Type | Label | Description |
|---|---|---|---|
| layer | string | layer is one of "platform", "org", "org_unit", "member". | |
| scope_id | string | scope_id is the id of the scope row walked for this layer. | |
| verdict | string | verdict is the layer-local verdict before cascade merging. | |
| reason | string | reason is a short explanation of why this layer produced that verdict. | |
| policy_id | string | policy_id is the matched policy id for this layer, empty when the layer produced a default verdict with no policy row. |
CheckRequest
CheckRequest carries the agent action details for governance evaluation.
| Field | Type | Label | Description |
|---|---|---|---|
| org_id | string | ||
| team_id | string | ||
| agent_id | string | ||
| member_id | string | ||
| clearance | int32 | ||
| action_type | string | ||
| target | string | ||
| metadata | CheckRequest.MetadataEntry | repeated |
CheckRequest.MetadataEntry
| Field | Type | Label | Description |
|---|---|---|---|
| key | string | ||
| value | string |
CheckResponse
CheckResponse carries the governance verdict.
| Field | Type | Label | Description |
|---|---|---|---|
| verdict | string | verdict is one of "allow", "deny", "requires_approval". | |
| reason | string | reason is a human-readable explanation of the decision. | |
| denied_by | string | denied_by indicates which layer denied: "platform", "org", "org_unit", "member", or empty. | |
| policy_id | string | policy_id is the ID of the policy that triggered the decision. | |
| approval_id | string | approval_id is set when verdict is "requires_approval". | |
| cascade_trace | CascadeTraceEntry | repeated | cascade_trace is the ordered list of per-layer verdicts evaluated during a 4-layer cascade Check (Wave D). Empty when the engine is configured with no CascadeStore (legacy 3-layer path). |
| conflict_id | string | conflict_id is reserved for Wave E — always empty in Wave D. |
EvaluateDryRunRequest
EvaluateDryRunRequest runs the cascade without creating approvals.
| Field | Type | Label | Description |
|---|---|---|---|
| action_type | string | action_type is the governed action (e.g. "tool_call"). | |
| target | string | target is the fine-grained target. | |
| member_id | string | member_id is the caller member id. | |
| agent_id | string | agent_id is the agent performing the action (optional). | |
| team_id | string | team_id is the legacy team id (optional). | |
| clearance | int32 | clearance is the caller clearance level. | |
| metadata | EvaluateDryRunRequest.MetadataEntry | repeated | metadata propagates any extra context. |
| simulate_policies | GovernancePolicy | repeated | simulate_policies overlays candidate policies on top of persisted rows for the duration of this call. |
EvaluateDryRunRequest.MetadataEntry
| Field | Type | Label | Description |
|---|---|---|---|
| key | string | ||
| value | string |
GovernancePolicy
GovernancePolicy is the stored representation of a policy row at any cascade layer.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | id is the policy uuid. | |
| org_id | string | org_id is the owning tenant. | |
| scope_type | string | scope_type is one of "org", "org_unit", "member". | |
| scope_id | string | scope_id is the org_unit id, member id, or empty string for "org". | |
| action_type | string | action_type is the governed action (e.g. "tool_call"). | |
| target | string | target is the fine-grained target (e.g. "github_create_pr" or "*"). | |
| effect | string | effect is one of "allow", "deny", "requires_approval". | |
| min_clearance | int32 | min_clearance is the clearance floor enforced by this policy. | |
| conditions | string | conditions is the JSON-encoded condition map. |
ListAuditEventsRequest
ListAuditEventsRequest carries optional filters and pagination for querying the agent_audit_log table. All filters are optional; omitting a filter means "match all" for that dimension. Results are returned newest-first.
| Field | Type | Label | Description |
|---|---|---|---|
| start_time | google.protobuf.Timestamp | optional | start_time lower-bounds the created_at filter (inclusive). |
| end_time | google.protobuf.Timestamp | optional | end_time upper-bounds the created_at filter (exclusive). |
| agent_id | string | optional | agent_id filters events to a single agent. |
| action_type | string | optional | action_type filters events by action type (e.g. "tool_call", "llm_call"). |
| page | int32 | page is the 1-based page number. Defaults to 1. | |
| page_size | int32 | page_size is the number of events per page. Max 100, default 50. |
ListAuditEventsResponse
ListAuditEventsResponse carries a page of audit events plus total count.
| Field | Type | Label | Description |
|---|---|---|---|
| events | AuditEvent | repeated | events is the list of audit events for the requested page. |
| total_count | int32 | total_count is the total number of events matching the filters. |
ListPoliciesRequest
ListPoliciesRequest filters policies by scope layer.
| Field | Type | Label | Description |
|---|---|---|---|
| scope_type | string | scope_type is one of "org", "org_unit", "member". | |
| scope_id | string | scope_id is the unit_id / member_id; empty selects all rows for the layer in the caller's org. | |
| action_type | string | action_type optionally filters by governed action. | |
| page_size | int32 | page_size caps returned rows (default 50, max 200). | |
| page_token | string | page_token is an opaque pagination token returned by a prior call. |
ListPoliciesResponse
ListPoliciesResponse returns a page of policies.
| Field | Type | Label | Description |
|---|---|---|---|
| policies | GovernancePolicy | repeated | policies is the returned page. |
| next_page_token | string | next_page_token is empty when no more rows exist. |
OrgDateScope
OrgDateScope verifies every chained session for org in [since, until). Empty since / until disables that bound.
| Field | Type | Label | Description |
|---|---|---|---|
| org_id | string | ||
| since | google.protobuf.Timestamp | ||
| until | google.protobuf.Timestamp |
PutMemberPolicyRequest
PutMemberPolicyRequest upserts a member-scoped policy.
| Field | Type | Label | Description |
|---|---|---|---|
| member_id | string | member_id is the target member. | |
| action_type | string | action_type is the governed action. | |
| target | string | target is the fine-grained target or "*". | |
| effect | string | effect is one of "allow", "deny", "requires_approval". | |
| min_clearance | int32 | min_clearance is the clearance floor. | |
| conditions | string | conditions is the JSON-encoded condition map. |
PutMemberPolicyResponse
PutMemberPolicyResponse echoes the upserted row.
| Field | Type | Label | Description |
|---|---|---|---|
| policy | GovernancePolicy | policy is the stored policy after upsert. |
PutOrgUnitPolicyRequest
PutOrgUnitPolicyRequest upserts an org_unit-scoped policy.
| Field | Type | Label | Description |
|---|---|---|---|
| unit_id | string | unit_id is the target org_unit. | |
| action_type | string | action_type is the governed action. | |
| target | string | target is the fine-grained target or "*". | |
| effect | string | effect is one of "allow", "deny", "requires_approval". | |
| min_clearance | int32 | min_clearance is the clearance floor. | |
| conditions | string | conditions is the JSON-encoded condition map (may be empty). |
PutOrgUnitPolicyResponse
PutOrgUnitPolicyResponse echoes the upserted row.
| Field | Type | Label | Description |
|---|---|---|---|
| policy | GovernancePolicy | policy is the stored policy after upsert. |
ResolveApprovalRequest
ResolveApprovalRequest asks for the resolution of a pending approval.
| Field | Type | Label | Description |
|---|---|---|---|
| org_id | string | ||
| approval_id | string |
ResolveApprovalResponse
ResolveApprovalResponse carries the approval resolution result.
| Field | Type | Label | Description |
|---|---|---|---|
| verdict | string | verdict is one of "allow", "deny". | |
| reason | string | reason is a human-readable explanation. | |
| approval_id | string | approval_id echoes back the approval ID. |
SessionScope
SessionScope verifies the chain for a single (org, session).
| Field | Type | Label | Description |
|---|---|---|---|
| org_id | string | ||
| session_id | string |
VerifyChainRequest
VerifyChainRequest selects the scope to verify. Exactly one of the scope oneof variants must be set.
| Field | Type | Label | Description |
|---|---|---|---|
| session | SessionScope | ||
| org_range | OrgDateScope |
VerifyChainResponse
VerifyChainResponse reports the outcome of a verifier walk.
| Field | Type | Label | Description |
|---|---|---|---|
| ok | bool | ok is true only when every chain_epoch>=1 row in scope linked correctly (prev_hash / row_hash / content invariants all held). | |
| total_rows | int32 | total_rows is the count of chained rows inspected. | |
| broken_at_row | int32 | broken_at_row is 1-based within the failing session; 0 when ok. | |
| broken_row_id | string | broken_row_id is the id of the first offending row; empty when ok. | |
| session_id | string | session_id echoes the failing session (session scope) or the first broken session (org scope). Empty when ok. | |
| verified_at | google.protobuf.Timestamp | verified_at is the wall-clock time the walk completed. | |
| sessions_verified | int32 | sessions_verified is the number of chained sessions walked (org scope). | |
| sessions_broken | int32 | sessions_broken is the number of sessions that broke (org scope). | |
| broken_reason | string | broken_reason is a human-readable reason for the first break; empty when ok. |
GovernanceService
GovernanceService exposes the governance engine over gRPC.
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| Check | CheckRequest | CheckResponse | Check evaluates org + team + clearance policies for the given action. |
| ResolveApproval | ResolveApprovalRequest | ResolveApprovalResponse | ResolveApproval polls for an approval resolution until resolved or timeout. |
| PutOrgUnitPolicy | PutOrgUnitPolicyRequest | PutOrgUnitPolicyResponse | PutOrgUnitPolicy upserts a governance policy scoped to a single org_unit (Wave D — 4-layer cascade). Requires guardrails.edit on the target org_unit. |
| PutMemberPolicy | PutMemberPolicyRequest | PutMemberPolicyResponse | PutMemberPolicy upserts a governance policy scoped to a single member (tightest cascade layer; overrides org_unit / org). Requires guardrails.edit. |
| ListPolicies | ListPoliciesRequest | ListPoliciesResponse | ListPolicies returns paginated governance policies filtered by scope_type (org |
| EvaluateDryRun | EvaluateDryRunRequest | CheckResponse | EvaluateDryRun runs the 4-layer cascade without mutating state, returning the full cascade_trace for UI preview / debugging. Does NOT create approval rows. |
| ListAuditEvents | ListAuditEventsRequest | ListAuditEventsResponse | ListAuditEvents returns a paginated list of audit log entries for the calling tenant, with optional time-range and attribute filters. |
Deprecated: use upsquad.audit.v1.AuditService.ListAuditEvents instead. This method is retained for backward compatibility with client portal versions deployed before the AuditService extraction (#404) and will be removed after the portal migration window closes. | | VerifyChain | VerifyChainRequest | VerifyChainResponse | VerifyChain walks the audit-log hash chain for the requested scope and reports integrity. Available scopes: a single session, or an org over a date range. Authorisation is enforced at the gateway (COMPLIANCE_AUDITOR clearance or above). See LLD docs/lld/wave1-item4-audit-hash-chain.md.
Deprecated: use upsquad.audit.v1.AuditService.VerifyChain instead. See the note on ListAuditEvents above — same migration window. |
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) |