cardactiondispatch

package
v1.11.0 Latest Latest
Warning

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

Go to latest
Published: Jul 20, 2026 License: Apache-2.0 Imports: 23 Imported by: 0

Documentation

Index

Constants

View Source
const (
	HeaderSignature = "X-Octo-Signature"
	HeaderTimestamp = "X-Octo-Timestamp"
	HeaderEventID   = "X-Octo-Event-ID"
)
View Source
const MaxDecisionResponseBytes = 64 << 10

Variables

View Source
var ErrServiceAlreadyInstalled = errors.New("cardactiondispatch: service already installed")

Functions

func CanonicalRequest

func CanonicalRequest(method, path, timestamp, eventID string, body []byte) string

func Install

func Install(ctx ValueStore, service *Service) error

func Retryable

func Retryable(err error) bool

func Sign

func Sign(secret, method, path, timestamp, eventID string, body []byte) string

func Verify

func Verify(secret, signature, method, path, timestamp, eventID string, body []byte) bool

Types

type DecisionRequest

type DecisionRequest struct {
	EventID     int64                  `json:"event_id,string"`
	ActionID    string                 `json:"action_id"`
	Decision    string                 `json:"decision"`
	OperatorUID string                 `json:"operator_uid"`
	DocID       string                 `json:"doc_id,omitempty"`
	RequestID   string                 `json:"request_id,omitempty"`
	Inputs      map[string]interface{} `json:"inputs"`
	Data        map[string]interface{} `json:"data,omitempty"`
	MessageID   string                 `json:"message_id"`
	ChannelID   string                 `json:"channel_id"`
	ChannelType uint8                  `json:"channel_type"`
	SpaceID     string                 `json:"space_id,omitempty"`
	ActedAt     int64                  `json:"acted_at"`
}

func DecisionRequestFromEvent

func DecisionRequestFromEvent(event Event) DecisionRequest

type DecisionResult

type DecisionResult struct {
	Disposition  Disposition       `json:"disposition"`
	State        State             `json:"state"`
	RequesterUID string            `json:"requester_uid,omitempty"`
	Display      map[string]string `json:"display,omitempty"`
}

func DecodeDecisionResult

func DecodeDecisionResult(reader io.Reader) (DecisionResult, error)

type DeliveryError

type DeliveryError struct {
	Category string
	Status   int
	// contains filtered or unexported fields
}

func (*DeliveryError) Error

func (e *DeliveryError) Error() string

func (*DeliveryError) Unwrap

func (e *DeliveryError) Unwrap() error

type Dispatcher

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

func NewDispatcher

func NewDispatcher(queue dispatchQueue, registry *Registry, deliverer callbackDeliverer, finalizer Finalizer, cfg DispatcherConfig) (*Dispatcher, error)

func (*Dispatcher) ProcessOne

func (d *Dispatcher) ProcessOne(ctx context.Context, now time.Time) (bool, error)

ProcessOne claims and completely handles at most one due event. Callback and finalization failures are converted into a durable retry/DLQ transition; an error is returned only when the queue state itself could not be made safe.

func (*Dispatcher) Start

func (d *Dispatcher) Start(parent context.Context) error

func (*Dispatcher) Stop

func (d *Dispatcher) Stop()

func (*Dispatcher) String

func (d *Dispatcher) String() string

type DispatcherConfig

type DispatcherConfig struct {
	LeaseDuration   time.Duration
	PollInterval    time.Duration
	ReclaimInterval time.Duration
	Metrics         *Metrics
	Logger          interface {
		Warn(string, ...zap.Field)
		Error(string, ...zap.Field)
	}
}

type Disposition

type Disposition string
const (
	DispositionApplied   Disposition = "applied"
	DispositionReplayed  Disposition = "replayed"
	DispositionForbidden Disposition = "forbidden"
	DispositionConflict  Disposition = "conflict"
	DispositionNotFound  Disposition = "not_found"
)

type Event

type Event struct {
	EventID     int64                  `json:"event_id"`
	SenderUID   string                 `json:"sender_uid"`
	Owner       string                 `json:"owner"`
	ActionType  string                 `json:"action_type"`
	MessageID   string                 `json:"message_id"`
	ChannelID   string                 `json:"channel_id"`
	ChannelType uint8                  `json:"channel_type"`
	SpaceID     string                 `json:"space_id,omitempty"`
	ActionID    string                 `json:"action_id"`
	OperatorUID string                 `json:"operator_uid"`
	ClientToken string                 `json:"client_token,omitempty"`
	ActedAt     int64                  `json:"acted_at"`
	Inputs      map[string]interface{} `json:"inputs"`
	Data        map[string]interface{} `json:"data,omitempty"`
}

type Finalizer

type Finalizer interface {
	Finalize(ctx context.Context, event Event, result DecisionResult) error
}

type FinalizerFunc

type FinalizerFunc func(context.Context, Event, DecisionResult) error

func (FinalizerFunc) Finalize

func (f FinalizerFunc) Finalize(ctx context.Context, event Event, result DecisionResult) error

type FinalizerKey

type FinalizerKey struct {
	Owner      string
	ActionType string
}

FinalizerKey binds a route to an optional specialized terminal renderer. Routes without a binding use the standard approval fallback.

type FinalizerRegistry

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

FinalizerRegistry keeps custom terminal visuals explicit while preserving config-only onboarding for standard approval consumers.

func NewFinalizerRegistry

func NewFinalizerRegistry(fallback Finalizer, bindings map[FinalizerKey]Finalizer) (*FinalizerRegistry, error)

func (*FinalizerRegistry) Finalize

func (r *FinalizerRegistry) Finalize(ctx context.Context, event Event, result DecisionResult) error

type HTTPDeliverer

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

func NewHTTPDeliverer

func NewHTTPDeliverer(transport http.RoundTripper, clock func() time.Time) *HTTPDeliverer

func (*HTTPDeliverer) Deliver

func (d *HTTPDeliverer) Deliver(ctx context.Context, route *Route, request DecisionRequest) (DecisionResult, error)

type Lease

type Lease struct {
	Event   Event
	Token   string
	Attempt int
}

type Metrics

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

func NewMetrics

func NewMetrics(reg prometheus.Registerer) *Metrics

type NackOutcome

type NackOutcome string
const (
	NackRequeued      NackOutcome = "requeued"
	NackDeadLettered  NackOutcome = "dead_lettered"
	NackTokenMismatch NackOutcome = "token_mismatch"
)

type NotifyCapability

type NotifyCapability struct {
	SenderUID string
	Owner     string
}

NotifyCapability is the server-authoritative identity granted to one first-party notification caller. The bearer token never supplies owner or sender metadata; it resolves to this value at the ingress boundary.

type QueueConfig

type QueueConfig struct {
	Prefix       string
	LiveTTL      time.Duration
	DLQRetention time.Duration
}

type QueueDepths

type QueueDepths struct {
	Ready  int64
	Leased int64
	DLQ    int64
}

type RedisQueue

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

func NewRedisQueue

func NewRedisQueue(client *rd.Client, cfg QueueConfig) (*RedisQueue, error)

func (*RedisQueue) Ack

func (q *RedisQueue) Ack(eventID int64, token string) (bool, error)

func (*RedisQueue) Claim

func (q *RedisQueue) Claim(now time.Time, leaseDuration time.Duration) (*Lease, error)

func (*RedisQueue) Defer

func (q *RedisQueue) Defer(eventID int64, token string, due time.Time) (bool, error)

Defer returns a capacity-blocked lease to ready without consuming a delivery attempt. The lease token and leased-set membership are checked atomically, so a stale worker cannot move a lease owned by another replica.

func (*RedisQueue) Depths

func (q *RedisQueue) Depths() (QueueDepths, error)

func (*RedisQueue) Enqueue

func (q *RedisQueue) Enqueue(event Event, due time.Time) error

func (*RedisQueue) Nack

func (q *RedisQueue) Nack(lease Lease, now time.Time, delay time.Duration, maxAttempts int, reason string) (NackOutcome, error)

func (*RedisQueue) ReclaimExpired

func (q *RedisQueue) ReclaimExpired(now time.Time, limit int) (int, error)

func (*RedisQueue) Renew

func (q *RedisQueue) Renew(eventID int64, token string, now time.Time, leaseDuration time.Duration) (bool, error)

func (*RedisQueue) ReplayDLQ

func (q *RedisQueue) ReplayDLQ(eventID int64, due time.Time) (bool, error)

type Registry

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

func NewRegistry

func NewRegistry(specs []RouteSpec, getenv func(string) string) (*Registry, error)

func (*Registry) CanNotify

func (r *Registry) CanNotify(capability NotifyCapability, actionType string) bool

CanNotify keeps a capability scoped to only the action types that explicitly declare its notify_token_env. A token for one owner cannot mint another owner's card or access a callback-only route.

func (*Registry) NotifyProducers

func (r *Registry) NotifyProducers() []NotifyCapability

func (*Registry) Resolve

func (r *Registry) Resolve(senderUID, owner, actionType string) Resolution

func (*Registry) ResolveNotifyToken

func (r *Registry) ResolveNotifyToken(token string) (NotifyCapability, bool)

ResolveNotifyToken performs a constant-time comparison against every configured first-party notification capability. Tokens are unique across capabilities, so at most one result can match.

func (*Registry) Route

func (r *Registry) Route(senderUID, owner, actionType string) (*Route, bool)

func (*Registry) ValidateNotifyTokenExclusions

func (r *Registry) ValidateNotifyTokenExclusions(tokens ...string) error

ValidateNotifyTokenExclusions prevents a route-scoped approval token from accidentally inheriting a broader legacy/docs notify capability.

type Resolution

type Resolution struct {
	Kind  ResolutionKind
	Route *Route
}

type ResolutionKind

type ResolutionKind string
const (
	ResolutionCallback ResolutionKind = "callback"
	ResolutionBotPull  ResolutionKind = "bot_pull"
	ResolutionReject   ResolutionKind = "reject"
)

type Route

type Route struct {
	SenderUID   string
	Owner       string
	ActionType  string
	URL         string
	Timeout     time.Duration
	MaxAttempts int
	BaseBackoff time.Duration
	MaxBackoff  time.Duration
	MaxInFlight int
	// contains filtered or unexported fields
}

type RouteSpec

type RouteSpec struct {
	SenderUID      string
	Owner          string
	ActionType     string
	URL            string
	SecretEnv      string
	NotifyTokenEnv string
	Timeout        time.Duration
	MaxAttempts    int
	BaseBackoff    time.Duration
	MaxBackoff     time.Duration
	MaxInFlight    int
}

func LoadRouteSpecs

func LoadRouteSpecs(raw string) ([]RouteSpec, error)

type Service

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

func FromContext

func FromContext(ctx ValueStore) (*Service, bool)

func NewService

func NewService(registry *Registry, queue eventQueue, sequence sequenceGenerator) (*Service, error)

func (*Service) CanNotify

func (s *Service) CanNotify(capability NotifyCapability, actionType string) bool

func (*Service) Enqueue

func (s *Service) Enqueue(event Event) (int64, error)

func (*Service) NotifyProducers

func (s *Service) NotifyProducers() []NotifyCapability

func (*Service) Resolve

func (s *Service) Resolve(senderUID, owner, actionType string) Resolution

func (*Service) ResolveNotifyToken

func (s *Service) ResolveNotifyToken(token string) (NotifyCapability, bool)

type State

type State string
const (
	StatePending   State = "pending"
	StateApproved  State = "approved"
	StateDenied    State = "denied"
	StateCancelled State = "cancelled"
)

type ValueStore

type ValueStore interface {
	SetValue(value interface{}, key string)
	Value(key string) interface{}
}

Jump to

Keyboard shortcuts

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