Docs
DriftReport CRD
Every active Srenix finding is a cluster-scoped DriftReport custom resource. kubectl get driftreports is your cluster's real-time drift state.
DriftReports are first-class Kubernetes objects. They persist in etcd, appear in kubectl get, work with OPA admission policies, and can be read by ArgoCD ApplicationSet generators and other tooling. The CRD is cluster-scoped (the affected resource's namespace lives in spec.resourceRef.namespace). Each cycle upserts one DriftReport per active diagnostic — re-detecting the same subject updates the existing CR (lastObserved, observationCount), and CRs whose subject no longer appears in the latest run are deleted.
Field reference generated from the chart's CRD (crd-driftreport.yaml, chart 0.1.0-alpha.1, OSS commit d6237fd, synced 2026-06-26). API: driftreports.srenix.ai/v1alpha1, short names drift, drifts.
Spec fields
| Field | Type | Description |
|---|---|---|
| subject required | string | Stable identity for de-duplication across cron ticks. Examples: "Secret/mcp/openproject-secrets/openproject-url", "ExternalSecret/mail/mail-service-api-key", "Service:LiveKit-SIP". |
| severity required | string | One of: info | warning | critical. |
| source required | string | Producer of the report. One of: probe, analyzer-name, fixer-name. e.g. "Postgres", "SecretKeyMissing", "ProactiveSecretKeyCheck", "StaleErrorPods". |
| message required | string | Human-readable description, identical to what would have appeared in Slack. Includes the suggested remediation. |
| remediation | string | Optional kubectl-ready hint (a one-liner the operator can paste). Probes set this; analyzers usually embed the hint into `message` instead. |
| investigation | string | Layer-2 investigator summary attached when a registered pkg/ai.Investigator (OSS rule-based or paid LLM) produces a root-cause narrative for this finding. Surfaced as a 🔬 block in Slack and Alertmanager payloads. |
| category | string | Logical bucket: "probe", "analyzer", "fixer-action", "fixer-skipped". Useful for kubectl filtering. One of: probe | analyzer | fixer-action | fixer-skipped. |
| resourceRef | object | The Kubernetes resource the report is about, when identifiable. Allows `kubectl get drifts -l ...`. |
| resourceRef.apiVersion | string | |
| resourceRef.kind | string | |
| resourceRef.namespace | string | |
| resourceRef.name | string | |
| resourceRef.cloud | object | Set when this drift is about a cloud-provider resource (AWS RDS, GCP Cloud SQL, Azure SQL, etc.) — populated by pkg/cloud probes. Mutually exclusive with the K8s fields above (a drift targets either a K8s object OR a cloud resource, not both). |
| resourceRef.cloud.provider | string | aws | gcp | azure One of: aws | gcp | azure. |
| resourceRef.cloud.region | string | Cloud region or location (e.g. "us-east-1", "us-central1", "eastus"). Empty for global resources (IAM, KMS). |
| resourceRef.cloud.service | string | Cloud service identifier (e.g. "rds", "ebs", "eks", "iam", "alb", "acm", "kms", "s3", "vpc"; "cloudsql", "gke", "lb"; "sql", "aks", "appgw", "keyvault"). |
| resourceRef.cloud.id | string | Cloud-provider resource identifier. Format varies by provider: AWS uses ARN or short id, GCP uses project/zone/name, Azure uses /subscriptions/.../resourceGroups/... |
Status fields
Status is written by Srenix only (a status subresource). There is no open/resolved state machine — an active finding has a DriftReport; a cleared finding's CR is deleted on the next reconcile. The exception is ticket bookkeeping: status.ticket records the external ticket Srenix filed so subsequent cycles don't open duplicates.
| Field | Type | Description |
|---|---|---|
| firstObserved | string | (date-time) |
| lastObserved | string | (date-time) |
| observationCount | integer | |
| runID | string | ID of the most recent srenix run that observed this drift. |
| ticket | object | Reference to the external issue-tracker ticket Srenix filed for this unfixable diagnostic. Populated by pkg/ticketing sinks (OpenProject in OSS; Jira / ServiceNow in Srenix Enterprise). Presence means a ticket already exists — Srenix will not open another one on subsequent cycles. |
| ticket.provider | string | Sink identifier. One of: "openproject", "jira", "servicenow". |
| ticket.key | string | Provider-native ticket key (e.g. "WP-1287", "OPS-42", "INC0012345"). |
| ticket.url | string | Canonical URL an operator clicks through to view the ticket in the provider UI. |
| ticket.openedAt | string | Timestamp when Srenix first filed this ticket. Stable across subsequent cycles. (date-time) |
| ticket.severity | string | Severity stamped on the ticket at open / last comment. Lets Srenix detect a severity transition (M2) and comment the change to the existing ticket. |
| ticket.resolved | boolean | True once Srenix auto-closed this ticket because the finding cleared (M2 resolve-on-clear). Idempotency guard: Srenix never re-resolves an already-resolved ticket. Cleared back to false if the finding recurs. |
| ticket.resolvedAt | string | Timestamp Srenix resolved this ticket on clear (M2). (date-time) |
| ticket.lastCommentedAt | string | Timestamp of the most recent recurrence / severity comment Srenix posted. Drives the commentInterval debounce (M2) so a flapping finding can't spam the tracker. (date-time) |
kubectl output columns
The CRD registers printer columns, so plain kubectl get driftreports is already useful (no NAMESPACE column — the CRD is cluster-scoped):
| Column | Type | JSONPath |
|---|---|---|
| Severity | string | .spec.severity |
| Source | string | .spec.source |
| Subject | string | .spec.subject |
| Last Seen | date | .status.lastObserved |
| Count | integer | .status.observationCount |
| Ticket | string | .status.ticket.key |
kubectl examples
# List all active findings (cluster-scoped — no namespace flag needed)
kubectl get driftreports
kubectl get drifts # short name
# Filter by severity (lowercase enum: info | warning | critical)
kubectl get driftreports -o json \
| jq '.items[] | select(.spec.severity == "critical")'
# Filter by category: probe | analyzer | fixer-action | fixer-skipped
kubectl get driftreports -o json \
| jq '.items[] | select(.spec.category == "analyzer")'
# Findings about resources in one namespace (namespace lives in resourceRef)
kubectl get driftreports -o json \
| jq '.items[] | select(.spec.resourceRef.namespace == "mcp")'
# Describe a specific finding (name is a hash of spec.subject)
kubectl describe driftreport <name>
# Watch findings appear/clear in real time
kubectl get driftreports -w Using DriftReports in GitOps
DriftReports are standard Kubernetes CRs. You can reference them in OPA/Gatekeeper constraints (block deploys while Critical findings reference a namespace), ArgoCD ApplicationSet generators (exclude clusters with drift), or your own controllers via a standard informer on the driftreports.srenix.ai GVR.