Governance signals schema
This page documents the platform schema for governance signals, the operator-actionable observation surface.Tables
governance_signals
The single row per open observation. The platform writes via the governance_signal_emit RPC; consumers (REST handlers, dispatcher, UI, CLI) read directly.
| Column | Type | Notes | |||||
|---|---|---|---|---|---|---|---|
id | text PK | gs-{12-hex} (mirrors pa- convention from pending_advisories). | |||||
scope | text CHECK | `platform | org | team | agent`. | ||
scope_id | text | platform_id / org_id / team_id (uuid::text) / agent_id. | |||||
source | text CHECK | `sideband.drift | sideband.coherence | sideband.fault_line | sideband.fleet. Future protection./posture.` land additively. | ||
pattern_type | text | Free-form per source (e.g., cluster_partition, pairwise_governance_floor). | |||||
severity | text CHECK | `info | warn | high | critical`. | ||
detected_at | timestamptz | Default now(); refreshed on coalescing upsert. | |||||
detected_by | text | Detector name (e.g., observer.sweepFleet). | |||||
org_id | text FK → orgs.org_id | Denormalized for RLS perf. | |||||
team_id | uuid | Nullable for platform/org/agent scope. | |||||
agent_ids | text[] | Affected agents — informational fan-out. | |||||
detail | jsonb | Pattern-specific payload. | |||||
source_ref | jsonb | Detector run / sweep_id / cadence info. | |||||
status | text CHECK | `open | acknowledged | resolved | dismissed | expired`. | |
acknowledged_by | uuid FK → auth.users.id | ||||||
acknowledged_at | timestamptz | ||||||
acknowledged_actor_role | text CHECK | `platform_admin | org_owner | org_admin | team_admin | member | system` (mirrors the audit actor_role). |
resolution_status | text CHECK | `action_taken | wont_fix | duplicate | false_positive | self_resolved`. | |
action_taken | text | Operator-authored note. | |||||
resolved_by | uuid, resolved_at timestamptz | ||||||
expires_at | timestamptz | TTL from posture (default 30d). | |||||
webhook_delivery_id | uuid | Last dispatch attempt FK (informational). | |||||
notification_state | jsonb | {<channel>: {state, attempts, destination_id, delivered_at, last_error}}. | |||||
created_at, updated_at | timestamptz | updated_at maintained by trigger. |
Indexes
governance_notification_destinations
| Column | Type | Notes | |||
|---|---|---|---|---|---|
id | uuid PK | ||||
org_id | text FK | ||||
channel | text CHECK | `webhook | slack | pagerduty`. | |
config | jsonb | Channel-specific. | |||
filter | jsonb | {sources?, severities?, scopes?, pattern_types?} AND-folded narrowing. | |||
enabled | boolean | Disable without delete to preserve audit. | |||
display_name | text | UI label. | |||
last_tested_at | timestamptz | Set by … /test endpoint. | |||
last_test_status, last_test_error | text |
governance_escalation_rules
| Column | Type | Notes |
|---|---|---|
id | uuid PK | |
org_id | text FK | |
name | text | Operator-authored. |
predicate | jsonb | {source?, pattern_type?, severity_min?, severity_max?, scope?, team_id?, threshold_count?, window_minutes?} AND-folded. |
destination_ids | uuid[] | Non-empty CHECK. |
enabled | boolean | |
last_fired_at, fire_count | Telemetry for tuning. |
RLS
Service-role bypass model: the API boundary applies authz in TypeScript, and PostgREST goes through the service role. RLS is enabled on all three tables with no user-facing policies — this is fail-closed against accidental exposure (a future PostgREST exposure can’t leak governance signals across orgs). Future tightening to user-driven RLS UPDATE policies is a follow-up once a shared cross-typeorg_members.user_id (TEXT) ↔ auth.uid() (UUID) policy helper is in place.
RPCs
governance_signal_emit
SECURITY DEFINER + service-role only. INSERT ... ON CONFLICT DO UPDATE on the open-dedup index — repeated cron emissions of the same condition refresh detected_at, severity, agent_ids, detail, source_ref on the existing open row instead of creating a new one.
governance_signal_acknowledge / _resolve / _dismiss
Operator state transitions. Each captures acknowledged_actor_role and is SECURITY DEFINER so the API can invoke after applying RBAC in TypeScript.
REST endpoints
See api-reference/governance for full schemas. Quick map:| Method | Path | RBAC |
|---|---|---|
| GET | /v1/orgs/:org_id/governance/signals | any org member |
| GET | /v1/teams/:team_id/governance/signals | any org member |
| GET | /v1/agents/:agent_id/governance/signals | any org member |
| GET | /v1/governance/signals/:id | any org member of row’s org |
| POST | /v1/governance/signals/:id/{acknowledge,resolve,dismiss} | org_admin / org_owner |
| GET | /v1/orgs/:org_id/governance/coverage | any org member |
* | /v1/orgs/:org_id/governance/notification-destinations[/:id][/test] | org_admin / org_owner |
* | /v1/orgs/:org_id/governance/escalation-rules[/:id] | org_admin / org_owner |
Webhook event taxonomy
| Event | Fired when |
|---|---|
governance.signal.fired | New row inserted (or open row coalesced). |
governance.signal.acknowledged | _acknowledge RPC. |
governance.signal.resolved | _resolve RPC. |
governance.signal.dismissed | _dismiss RPC. |
governance.escalation.triggered | A rule’s predicate matched and dispatch fired. |
sideband.coherence.fired→ usegovernance.signal.firedfiltered onpayload.source == "sideband.coherence".sideband.fault_line.fired→ same.sideband.fleet.fired→ same.drift.detected→ usegovernance.signal.firedfiltered onpayload.source == "sideband.drift".
X-Mnemom-Signature: sha256=…) following the AAP webhook contract; subscribers should verify the signature before trusting the payload.
Naming convention discipline
source is closed, hierarchical, append-only — mirrors the pending_advisories.source taxonomy. Adding a new value requires:
- A schema amendment.
- Migration extending the CHECK constraint with ASSERT-after-DDL guard.
- Producer code (typically observer).
- Consumer-tolerance discipline (gateway / UI / CLI / SDKs).
- Posture-gating extension if the source is detector-driven.
Related
- Governance signals concept.
- Pending Advisories Schema — narrowed to
runtime.*+manual.*.