triggers

package
v0.0.0-...-bc6a2cb Latest Latest
Warning

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

Go to latest
Published: Jul 21, 2026 License: AGPL-3.0 Imports: 43 Imported by: 0

Documentation

Index

Constants

View Source
const (
	TargetKindAssistant = "assistant"
	TargetKindNoop      = "noop"
	StatusActive        = "active"
	StatusPaused        = "paused"
	StatusFired         = "fired"
	StatusCancelled     = "cancelled"

	DefinitionSlugSlack     = "slack"
	DefinitionSlugLinear    = "linear"
	DefinitionSlugGithub    = "github"
	DefinitionSlugCron      = "cron"
	DefinitionSlugWake      = "wake"
	DefinitionSlugDashboard = "dashboard"
)
View Source
const MaxWakeHorizon = 30 * 24 * time.Hour

MaxWakeHorizon caps how far in the future a wake trigger may be scheduled.

Variables

View Source
var (
	ErrBadRequest = errors.New("trigger bad request")
	ErrAuthFailed = errors.New("trigger webhook authentication failed")
)

Functions

func BuildScheduleOptions

func BuildScheduleOptions(instance triggerrepo.TriggerInstance, schedule string, taskQueue string, workflowName string) client.ScheduleOptions

func CancelTriggerWakeWorkflow

func CancelTriggerWakeWorkflow(ctx context.Context, temporalEnv *tenv.Environment, instanceID uuid.UUID) error

CancelTriggerWakeWorkflow: the workflow body honours cancellation by exiting before dispatch. NotFound is swallowed so cancelling an already- fired or never-started wake is a no-op.

func DeleteTriggerCronWorkflowSchedule

func DeleteTriggerCronWorkflowSchedule(ctx context.Context, temporalEnv *tenv.Environment, instanceID uuid.UUID) error

func ExecuteTriggerDispatchWorkflow

func ExecuteTriggerDispatchWorkflow(ctx context.Context, temporalEnv *tenv.Environment, input TriggerDispatchWorkflowInput) error

func ExecuteTriggerWakeWorkflow

func ExecuteTriggerWakeWorkflow(ctx context.Context, temporalEnv *tenv.Environment, instanceID uuid.UUID, fireAt time.Time) error

ExecuteTriggerWakeWorkflow is idempotent: the deterministic per-instance workflow ID makes repeated scheduling of the same wake a Temporal-level no-op (WORKFLOW_ID_REUSE_POLICY_REJECT_DUPLICATE).

func ScheduleTriggerCronWorkflow

func ScheduleTriggerCronWorkflow(ctx context.Context, temporalEnv *tenv.Environment, opts ScheduleTriggerCronWorkflowOptions) error

func TriggerWakeWorkflowID

func TriggerWakeWorkflowID(id uuid.UUID) string

func ValidateTargetKind

func ValidateTargetKind(targetKind string) error

func ValidateWakeFireAt

func ValidateWakeFireAt(fireAt, now time.Time) error

ValidateWakeFireAt enforces the create-time bounds on a wake's fire_at. Pure helper so callers can test bounds without round-tripping a DB.

func WakeConfigFields

func WakeConfigFields(configJSON []byte) (string, string)

WakeConfigFields decodes a wake trigger instance's config_json blob and returns the (correlation_id, fire_at) pair used by audit log call sites. Returns empty strings on decode failure so callers can still record audit events without taking down the workflow.

Types

type App

type App struct {
	// contains filtered or unexported fields
}

func NewApp

func NewApp(
	logger *slog.Logger,
	db *pgxpool.Pool,
	temporalEnv *tenv.Environment,
	envLoader EnvironmentLoader,
	deliveryLogger DeliveryLogger,
	auditLogger *audit.Logger,
	serverURL *url.URL,
	siteURL *url.URL,
	slackClient *slackclient.SlackClient,
	dispatchers ...Dispatcher,
) *App

func (*App) CancelAssistantWakes

func (a *App) CancelAssistantWakes(ctx context.Context, projectID, assistantID uuid.UUID) error

CancelAssistantWakes is the cascade hook on assistant deletion: errors per instance are folded into the returned error so a partial sweep still cancels what it can.

func (*App) CancelWakeInstance

func (a *App) CancelWakeInstance(ctx context.Context, projectID uuid.UUID, instanceID uuid.UUID, hooks ...InstanceDBHook) (triggerrepo.TriggerInstance, error)

CancelWakeInstance flips an active wake to 'cancelled' and cancels its pending workflow. Idempotent for already-cancelled or already-fired wakes.

func (*App) Create

func (a *App) Create(ctx context.Context, params CreateParams, hooks ...InstanceDBHook) (triggerrepo.TriggerInstance, error)

func (*App) CreateWakeInstance

func (a *App) CreateWakeInstance(ctx context.Context, params CreateWakeInstanceParams, hooks ...InstanceDBHook) (triggerrepo.TriggerInstance, error)

func (*App) Delete

func (a *App) Delete(ctx context.Context, projectID uuid.UUID, id uuid.UUID, hooks ...InstanceDBHook) error

func (*App) Dispatch

func (a *App) Dispatch(ctx context.Context, input Task) error

func (*App) GetInstance

func (a *App) GetInstance(ctx context.Context, projectID uuid.UUID, id uuid.UUID) (triggerrepo.TriggerInstance, error)

func (*App) IngestDirect

func (a *App) IngestDirect(ctx context.Context, instanceID uuid.UUID, payload []byte, receivedAt time.Time) (*Task, error)

IngestDirect handles a synchronous, app-invoked trigger event (e.g. a message from the dashboard assistant sidebar) through the same path as webhook and schedule ingress: it builds the event from the instance's direct-ingress definition, runs ProcessEvent (status/filter/target checks + delivery log), and dispatches the resulting task inline. Returns the dispatched task, or nil when the instance is paused or the event was filtered out.

func (*App) ListActiveAssistantWakes

func (a *App) ListActiveAssistantWakes(ctx context.Context, projectID uuid.UUID, assistantID uuid.UUID) ([]triggerrepo.TriggerInstance, error)

func (*App) ListDefinitions

func (a *App) ListDefinitions() []Definition

func (*App) ListInstances

func (a *App) ListInstances(ctx context.Context, projectID uuid.UUID) ([]triggerrepo.TriggerInstance, error)

func (*App) MarkInstanceFired

func (a *App) MarkInstanceFired(ctx context.Context, instanceID string) error

MarkInstanceFired transitions an active wake instance to 'fired' and records a wake:fired audit log. The UPDATE is guarded on status='active' so a cancel that won the race is preserved (the workflow returns nil; the already-cancelled row keeps its terminal state).

func (*App) ProcessEvent

func (a *App) ProcessEvent(ctx context.Context, instance triggerrepo.TriggerInstance, envelope EventEnvelope) (*Task, error)

func (*App) ProcessScheduled

func (a *App) ProcessScheduled(ctx context.Context, input ProcessScheduledInput) (*Task, error)

func (*App) ProcessWebhook

func (a *App) ProcessWebhook(ctx context.Context, instanceID uuid.UUID, body []byte, headers http.Header) (*WebhookIngressResult, error)

func (*App) RegisterDispatcher

func (a *App) RegisterDispatcher(dispatcher Dispatcher)

func (*App) SetStatus

func (a *App) SetStatus(ctx context.Context, projectID uuid.UUID, id uuid.UUID, status string, hooks ...InstanceDBHook) (triggerrepo.TriggerInstance, error)

func (*App) Update

func (a *App) Update(ctx context.Context, params UpdateParams, hooks ...InstanceDBHook) (triggerrepo.TriggerInstance, error)

func (*App) WebhookURL

func (a *App) WebhookURL(instance triggerrepo.TriggerInstance) *string

type Config

type Config interface {
	Filter(event any) (bool, error)
}

type CreateParams

type CreateParams struct {
	OrganizationID string
	ProjectID      uuid.UUID
	DefinitionSlug string
	Name           string
	EnvironmentID  uuid.NullUUID
	TargetKind     string
	TargetRef      string
	TargetDisplay  string
	Config         map[string]any
	Status         string
}

type CreateWakeInstanceParams

type CreateWakeInstanceParams struct {
	OrganizationID string
	ProjectID      uuid.UUID
	Name           string
	AssistantID    uuid.UUID
	TargetDisplay  string
	FireAt         time.Time
	Note           *string
	CorrelationID  string
}

CreateWakeInstanceParams configures a one-shot self-wake for an assistant thread. CorrelationID must match the originating thread's correlation ID so the firing event lands on the same `assistant_threads` row.

type Definition

type Definition struct {
	Slug                 string
	Title                string
	Description          string
	Kind                 Kind
	ConfigSchema         []byte
	CompiledConfigSchema *jsonschema.Schema
	EnvRequirements      []EnvRequirement
	EventType            reflect.Type
	DecodeConfig         func(raw map[string]any) (Config, error)
	AuthenticateWebhook  func(body []byte, headers http.Header, env map[string]string, config Config) error
	HandleWebhook        func(body []byte, headers http.Header, config Config) (*WebhookIngressResult, error)
	BuildScheduledEvent  func(instance triggerrepo.TriggerInstance, config Config, firedAt time.Time) (*EventEnvelope, error)
	BuildDirectEvent     func(instance triggerrepo.TriggerInstance, config Config, payload []byte, receivedAt time.Time) (*EventEnvelope, error)
	ExtractSchedule      func(config Config) (string, error)
}

func GetDefinition

func GetDefinition(slug string) (Definition, bool)

func List

func List() []Definition

func NewWebhookDefinition

func NewWebhookDefinition(
	vendor WebhookVendor,
	schema []byte,
	compiled *jsonschema.Schema,
	decodeConfigFn func(raw map[string]any) (Config, error),
) Definition

NewWebhookDefinition builds a Definition from a WebhookVendor, the JSON-schema-described config (built by the vendor via buildInputSchema), and a DecodeConfig closure that decodes raw config, validates event types, and compiles the CEL filter. The shared wiring — HMAC authentication with optional pre-verify bypass, envelope assembly with vendor-supplied event id + correlation id, content-hash event-id fallback — lives here so vendors don't reimplement it.

type DeliveryLogger

type DeliveryLogger interface {
	LogTriggerDelivery(triggerrepo.TriggerInstance, EventEnvelope, DeliveryStatus, string, error)
}

func NewTriggerDeliveryLogger

func NewTriggerDeliveryLogger(write func(context.Context, TriggerDeliveryLog)) DeliveryLogger

type DeliveryStatus

type DeliveryStatus string
const (
	DeliveryStatusFailed  DeliveryStatus = "failed"
	DeliveryStatusSent    DeliveryStatus = "sent"
	DeliveryStatusSkipped DeliveryStatus = "skipped"
)

type Dispatcher

type Dispatcher interface {
	Kind() string
	Dispatch(context.Context, Task) error
}

type EnvRequirement

type EnvRequirement struct {
	Name        string
	Description string
	Required    bool
}

type EnvironmentLoader

type EnvironmentLoader interface {
	Load(context.Context, uuid.UUID, toolconfig.SlugOrID) (map[string]string, error)
}

type EventEnvelope

type EventEnvelope struct {
	EventID           string
	CorrelationID     string
	TriggerInstanceID string
	DefinitionSlug    string
	Event             any
	RawPayload        []byte
	ReceivedAt        time.Time
}

type HMACScheme

type HMACScheme struct {
	// NewHash returns a fresh HMAC writer keyed by the signing secret. Nil
	// disables signature verification.
	NewHash func(key []byte) hash.Hash
	// Header names the request header carrying the signature.
	Header string
	// Encoding renders the computed MAC: "hex" (default) or "base64".
	Encoding string
	// Prefix is prepended to the encoded MAC before constant-time compare
	// (e.g. "sha256=" for GitHub, "v0=" for Slack).
	Prefix string
	// Template shapes the bytes fed to the MAC. Placeholders: {body}, {timestamp}.
	// Defaults to "{body}".
	Template string
	// TimestampHeader names a request header carrying a unix timestamp
	// substituted into Template and bounded for replay protection. Empty
	// disables timestamp checking.
	TimestampHeader string
	// TimestampSkew caps how far the timestamp may drift from now. Defaults
	// to 300s when TimestampHeader is set.
	TimestampSkew time.Duration
}

HMACScheme describes a vendor's request-signature scheme. It covers the HMAC-based schemes used by Slack, Linear, GitHub, Stripe, and most other webhook senders. NewHash is the constructor (e.g. hmac.New(sha256.New, key)); when nil, authentication is disabled (no signature is required).

func (HMACScheme) Verify

func (s HMACScheme) Verify(body []byte, headers http.Header, secret string) error

Verify computes the expected signature for body under secret and compares it against the value in s.Header using a constant-time comparison. When a TimestampHeader is configured the timestamp is parsed and bounded by TimestampSkew to reject replays.

type InstanceDBHook

type InstanceDBHook func(ctx context.Context, dbtx pgx.Tx, instance triggerrepo.TriggerInstance) error

InstanceDBHook runs inside the transaction that mutates a trigger instance, after the primary repo write succeeds and before commit. Returning an error rolls the entire mutation back.

type Kind

type Kind string
const (
	KindWebhook  Kind = "webhook"
	KindSchedule Kind = "schedule"
	KindDirect   Kind = "direct"
)

type NoopDispatcher

type NoopDispatcher struct {
	// contains filtered or unexported fields
}

func NewNoopDispatcher

func NewNoopDispatcher(logger *slog.Logger) *NoopDispatcher

func (*NoopDispatcher) Dispatch

func (d *NoopDispatcher) Dispatch(ctx context.Context, input Task) error

func (*NoopDispatcher) Kind

func (d *NoopDispatcher) Kind() string

type ProcessScheduledInput

type ProcessScheduledInput struct {
	TriggerInstanceID string
	FiredAt           string
}

type ScheduleTriggerCronWorkflowOptions

type ScheduleTriggerCronWorkflowOptions struct {
	InstanceID     uuid.UUID
	InstanceStatus string
	Schedule       string
}

type ScheduleWorkflowInput

type ScheduleWorkflowInput struct {
	TriggerInstanceID string `json:"trigger_instance_id"`
	FiredAt           string `json:"fired_at,omitempty"`
}

type Task

type Task struct {
	TriggerInstanceID string
	DefinitionSlug    string
	TargetKind        string
	TargetRef         string
	TargetDisplay     string
	EventID           string
	CorrelationID     string
	EventJSON         []byte
	RawPayload        []byte
}

type TriggerCronWorkflowInput

type TriggerCronWorkflowInput struct {
	TriggerInstanceID string `json:"trigger_instance_id"`
}

type TriggerDeliveryLog

type TriggerDeliveryLog struct {
	Timestamp  time.Time
	Instance   triggerrepo.TriggerInstance
	Attributes map[attr.Key]any
}

type TriggerDispatchWorkflowInput

type TriggerDispatchWorkflowInput struct {
	Task Task
}

type UpdateParams

type UpdateParams struct {
	ID             uuid.UUID
	ProjectID      uuid.UUID
	DefinitionSlug string
	Name           string
	EnvironmentID  uuid.NullUUID
	TargetKind     string
	TargetRef      string
	TargetDisplay  string
	Config         map[string]any
	Status         string
}

type WebhookIngest

type WebhookIngest struct {
	Response      *WebhookResponse
	Event         any
	EventID       string
	CorrelationID string
}

WebhookIngest is the result of a vendor's Ingest step: an optional inline response (e.g. Slack's url_verification challenge echo), the normalized event, and the routing metadata that NewWebhookDefinition folds into the EventEnvelope.

type WebhookIngressResult

type WebhookIngressResult struct {
	Response *WebhookResponse
	Event    *EventEnvelope
	Task     *Task
}

type WebhookResponse

type WebhookResponse struct {
	Status      int
	ContentType string
	Body        []byte
}

type WebhookVendor

type WebhookVendor struct {
	Slug                string
	Title               string
	Description         string
	EventType           reflect.Type
	EnvRequirements     []EnvRequirement
	SecretEnv           string
	Signature           HMACScheme
	SupportedEventTypes []string
	// PreVerify inspects the request before signature verification. Return
	// skipSignature=true to authorize the request without checking the HMAC
	// (e.g. Slack's url_verification handshake, which must echo a challenge
	// before any signing secret is necessarily configured). Return an error
	// to reject. May be nil, in which case signature verification always runs.
	PreVerify func(body []byte, headers http.Header) (skipSignature bool, err error)
	// Ingest decodes body + headers into a normalized event for downstream
	// consumers, the vendor's delivery id (used for dedup; falls back to a
	// content hash when empty), and the correlation id that routes the event
	// to an assistant conversation. A nil Response with a nil Event acks
	// without dispatching (e.g. an unsupported Slack interaction envelope).
	Ingest func(body []byte, headers http.Header) (*WebhookIngest, error)
}

WebhookVendor is the per-vendor description a webhook trigger implementation supplies. NewWebhookDefinition assembles the shared Definition wiring (authenticate via HMACScheme, ingest into a normalized EventEnvelope, default-deny event-type filtering) around it, so adding a new webhook source is mostly a matter of describing its signature scheme, env var, event types, and an Ingest function that decodes the body into a typed event plus correlation id.

Jump to

Keyboard shortcuts

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