interests

package
v0.19.915 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 6, 2026 License: AGPL-3.0 Imports: 7 Imported by: 0

Documentation

Overview

Package interests defines the shared "what events do you want?" model used by both Slack channel subscriptions and webhooks.

One JSONB shape is stored on both subscriber tables. One classifier turns a signal lifecycle event into a list of slugs (resource:..., op:..., outcome:..., event:...) and one matcher decides whether a given Interests config wants to receive that event. UI variants (slack vs webhook) collapse/expand the same underlying fields differently but persist identically.

AllEvents is the new-subscription default; once a user opts into per-resource configuration the Resources map becomes the source of truth and AllEvents must be false.

Index

Constants

View Source
const (
	SlugPrefixResource = "resource:"
	SlugPrefixOp       = "op:"
	SlugPrefixOutcome  = "outcome:"
	SlugPrefixEvent    = "event:"
)

Slug prefix constants. Webhook payloads embed a top-level `interests` array of these slugs so consumers can route by prefix without re-implementing the classifier.

View Source
const (
	SlugOutcomeCompletion = SlugPrefixOutcome + "completion"
	SlugOutcomeFailures   = SlugPrefixOutcome + "failures"
)

Outcome slugs. outcome:completion is emitted on terminal events of any status; outcome:failures is emitted only when the terminal status is failed/cancelled.

View Source
const (
	SlugEventLifecycleStarted   = SlugPrefixEvent + "lifecycle.started"
	SlugEventLifecycleSucceeded = SlugPrefixEvent + "lifecycle.succeeded"
	SlugEventLifecycleFailed    = SlugPrefixEvent + "lifecycle.failed"
	SlugEventLifecycleCancelled = SlugPrefixEvent + "lifecycle.cancelled"
)

Lifecycle event slugs (workflow / step transitions).

View Source
const (
	SlugEventApprovalRequest          = SlugPrefixEvent + "approval.request"
	SlugEventApprovalResponse         = SlugPrefixEvent + "approval.response"
	SlugEventApprovalResponseApproved = SlugPrefixEvent + "approval.response.approved"
	SlugEventApprovalResponseRejected = SlugPrefixEvent + "approval.response.rejected"
)

Approval handshake slugs. event:approval.response is emitted as a generic fallback when the specific approved/rejected outcome cannot be resolved (e.g. classify() called without a DB). The webhook hook upgrades the payload's interests array to the more specific form when it has the data.

View Source
const (
	SlugEventDriftDetected = SlugPrefixEvent + "drift.detected"
)

Drift detection slug. event:drift.detected is emitted by the drift-detected signal that fires from inside the plan-only check of a drift_run / drift_run_reprovision_sandbox workflow when the plan has changes (i.e. drift was actually found). Subscribers opt in via the per-resource `drift_detected` flag, which is gated independently of `outcome`.

Variables

AllResources is the canonical, ordered list of resource kinds. UI code renders rows in this order; defaults() / docs walk it.

View Source
var SubOps = map[ResourceKind][]string{
	ResourceInstalls:              {"provision", "deprovision", "reprovision"},
	ResourceComponents:            {"deploy", "teardown"},
	ResourceSandboxes:             {"provision", "reprovision", "deprovision"},
	ResourceInstallConfigurations: {"inputs", "secrets"},
	ResourceRunners:               {"provision", "reprovision", "inactive"},
	ResourceActions:               {"run"},
}

SubOps is the canonical sub-op vocabulary per resource. The classifier maps real WorkflowType constants from internal/app/workflow.go onto these slugs. UIs render checkbox lists from this map.

Note: "drift" is intentionally NOT listed for components or sandboxes even though the classifier still emits the slug. Drift workflow lifecycle events are pure noise (one started/completed pair per cron tick per resource) and are unconditionally suppressed in match.go. Subscribers opt into drift notifications through DriftDetected, which gates the dedicated drift-detected event that only fires when the plan-only check observes actual changes.

Functions

func Classify

func Classify(event signal.SignalPhaseEvent, outcome *signal.SignalPhaseOutcome, db *gorm.DB) []string

Classify returns the slug list for a single event. The webhook hook stamps these onto the outbound payload's top-level `interests` array so consumers can route by prefix without re-implementing the classifier.

outcome may be nil (BeforePhase / "started" emission). db may be nil — when nil, classification falls back to the WorkflowType-only path and skips step / approval-response disambiguation.

func Matches

func Matches(event signal.SignalPhaseEvent, outcome *signal.SignalPhaseOutcome, db *gorm.DB, in Interests) bool

Matches reports whether the given Interests config wants to receive this lifecycle event. Hooks (Slack channel sub, webhook) call this once per subscription before dispatching.

Semantics:

  • AllEvents=true: matches every classifiable event.
  • Empty Resources (and AllEvents=false): never matches.
  • Resource missing from Resources: never matches.
  • Resource present:
  • Ops empty → every sub-op for this resource matches.
  • Ops non-empty → only the listed sub-op matches.
  • Approval events are gated by ApprovalRequests / ApprovalResponses (independent of Outcome).
  • Lifecycle events apply Outcome:
  • "" / "all" → every started + terminal event.
  • "completion" → terminal events only (suppress started).
  • "failures" → only failed/cancelled terminal events.

outcome may be nil at BeforePhase (started). db may be nil — when nil, classification falls back to the WorkflowType-only path which is enough for execute-workflow events but skips step-scoped enrichment.

func OpSlug

func OpSlug(kind ResourceKind, op string) string

OpSlug returns "op:<kind>.<op>".

func ResourceSlug

func ResourceSlug(kind ResourceKind) string

ResourceSlug returns "resource:<kind>".

func SupportsDriftDetected

func SupportsDriftDetected(kind ResourceKind) bool

SupportsDriftDetected reports whether DriftDetected is meaningful for the given resource. Currently true for components and sandboxes — the only resources whose workflows can produce a drift-detected event.

func Validate

func Validate(in Interests) error

Validate checks that resource keys + ops + outcome on an Interests config match the canonical taxonomy declared in this package. Empty configs and AllEvents=true are both valid; the picker UI is the place where shape is enforced visually, but the API still refuses outright garbage.

Used by both the slack channel subscription create handler and the webhook create/update handlers. Lifted from internal/app/slack/service so both surfaces share one implementation.

Types

type Interests

type Interests struct {
	AllEvents bool                         `json:"all_events,omitempty"`
	Resources map[ResourceKind]ResourceCfg `json:"resources,omitempty"`
}

Interests is the full per-subscriber config. Stored as JSONB on both slack_channel_subscriptions and webhooks.

AllEvents acts as a sentinel: when true, every supported event matches regardless of Resources content. Toggling AllEvents off in the picker materializes Default() (the per-resource opt-out baseline) into Resources and flips AllEvents to false.

func AllEvents

func AllEvents() Interests

AllEvents returns the new-subscription default: a sentinel Interests config that matches every supported lifecycle and approval event. This is what the "Send all events" toggle in the picker writes when it's checked, and what new webhooks/slack subs default to.

func Default

func Default() Interests

Default returns the materialised "power user opted out of AllEvents" baseline. Four resources (installs, components, sandboxes, install_configurations) are present with empty ops (= all sub-ops), Outcome=Completion, and approval flags both true. Runners + actions are absent (= off).

Toggling "Send all events" off in the picker writes this exact shape so users land on a sensible per-resource starting point instead of an empty config that silently drops every event.

func (Interests) GormDataType

func (Interests) GormDataType() string

GormDataType tells GORM to materialise the column as JSONB.

func (Interests) IsZero

func (i Interests) IsZero() bool

IsZero is true for the zero-value Interests (no AllEvents, no resources). Used by callers to distinguish "never configured" from "explicitly empty".

func (*Interests) Scan

func (i *Interests) Scan(v interface{}) error

Scan implements sql.Scanner so Interests can be loaded from a JSONB column. Mirrors the pattern used by IntermediateAppConfig in services/ctl-api/internal/app/onboarding.go.

func (Interests) Value

func (i Interests) Value() (driver.Value, error)

Value implements driver.Valuer so Interests can be persisted to a JSONB column.

type Outcome

type Outcome string

Outcome filters lifecycle events on terminal status. Approval events are gated separately by ResourceCfg.ApprovalRequests / ApprovalResponses and ignore this field.

const (
	// OutcomeAll forwards every started + terminal lifecycle event.
	OutcomeAll Outcome = "all"
	// OutcomeCompletion forwards only terminal events (succeeded / failed /
	// cancelled). Started events are suppressed.
	OutcomeCompletion Outcome = "completion"
	// OutcomeFailures forwards only failed / cancelled terminal events.
	OutcomeFailures Outcome = "failures"
)

type ResourceCfg

type ResourceCfg struct {
	Ops               []string `json:"ops,omitempty"`
	Outcome           Outcome  `json:"outcome,omitempty"`
	ApprovalRequests  bool     `json:"approval_requests,omitempty"`
	ApprovalResponses bool     `json:"approval_responses,omitempty"`
	DriftDetected     bool     `json:"drift_detected,omitempty"`
}

ResourceCfg is the per-resource filter. An empty Ops slice means "every sub-op for this resource". Outcome=="" is treated as OutcomeAll.

ApprovalRequests / ApprovalResponses are independent of Outcome — approval events do not have a started/succeeded/failed lifecycle, only a requested / approved / rejected handshake.

DriftDetected is independent of Outcome too. It gates the drift_detected event that fires from inside the plan-only check of a drift_run / drift_run_reprovision_sandbox workflow when the plan has changes. Only meaningful for resources where SupportsDriftDetected returns true (components, sandboxes); set on other resources is harmless but never matches. Drift workflow lifecycle events themselves are unconditionally suppressed by the matcher — DriftDetected is the only knob that surfaces drift to subscribers.

LOCKSTEP: changes to this struct's JSON shape must also update bins/cli/cmd/orgs.go (interestsJSONHelp) and the "Filtering events with interests" section of docs/guides/webhooks.mdx.

type ResourceKind

type ResourceKind string

ResourceKind is the top-level event taxonomy exposed in the picker UI. The classifier maps each lifecycle / approval event to exactly one ResourceKind.

const (
	ResourceInstalls              ResourceKind = "installs"
	ResourceComponents            ResourceKind = "components"
	ResourceSandboxes             ResourceKind = "sandboxes"
	ResourceInstallConfigurations ResourceKind = "install_configurations"
	ResourceRunners               ResourceKind = "runners"
	ResourceActions               ResourceKind = "actions"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL