trigger

package
v0.1.0 Latest Latest
Warning

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

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

Documentation

Overview

Package trigger serves the AgentPaaS Trigger API over gRPC on port 7718 and REST through grpc-gateway on port 7717.

Authentication is required even on loopback. Requests must present a valid Bearer API key or mTLS identity before invoking TriggerService methods.

CORS is deny-by-default: only explicitly allowed origins receive CORS response headers, and browser-originated requests without explicit authentication still receive an unauthenticated response.

Index

Constants

View Source
const (
	EventCancelRequested = "cancel_requested"
	EventCancelGraceful  = "cancel_graceful"
	EventCancelForced    = "cancel_forced"
	EventCancelTimeout   = "cancel_timeout"
)
View Source
const (
	DefaultGRPCPort   = 7718
	DefaultRESTPort   = 7717
	DefaultMaxPayload = 1 << 20
)
View Source
const CancelGracePeriod = 30 * time.Second
View Source
const (
	// DefaultIdempotencyTTL is how long idempotency entries are valid for replay.
	DefaultIdempotencyTTL = 24 * time.Hour
)

Variables

View Source
var (
	ErrInvalidAPIKey  = errors.New("invalid API key")
	ErrRevokedKey     = errors.New("API key has been revoked")
	ErrKeyNotFound    = errors.New("API key not found")
	ErrAlreadyRevoked = errors.New("API key already revoked")
)

Functions

func AuthInterceptor

func AuthInterceptor(auth Authenticator) grpc.UnaryServerInterceptor

AuthInterceptor returns a gRPC unary interceptor that requires authentication.

func AuthStreamInterceptor

func AuthStreamInterceptor(auth Authenticator) grpc.StreamServerInterceptor

AuthStreamInterceptor returns a gRPC stream interceptor that requires authentication.

func CanonicalRequestHash

func CanonicalRequestHash(callerID, agentName, lockDigest string, payload []byte, contentType, apiVersion string) string

CanonicalRequestHash computes the SHA-256 hash over the canonical request fields.

func IsDSTNonexistentTime

func IsDSTNonexistentTime(t time.Time) bool

IsDSTNonexistentTime checks if a time falls in a DST gap.

func IsDSTRepeatedTime

func IsDSTRepeatedTime(t time.Time) bool

IsDSTRepeatedTime checks if a time falls in a DST overlap.

func VerifyDelivery

func VerifyDelivery(secret string, body []byte, signature string, timestampStr string) error

VerifyDelivery verifies a received webhook signature and timestamp.

func VerifyHMAC

func VerifyHMAC(secret string, timestamp time.Time, body []byte, signature string) bool

VerifyHMAC verifies a webhook HMAC signature.

func VerifyTimestamp

func VerifyTimestamp(timestamp time.Time, now time.Time) bool

VerifyTimestamp checks that a timestamp is within the webhook replay window.

func WithAuthToken

func WithAuthToken(parent context.Context, token string) context.Context

WithAuthToken stores the bearer token in context for HTTP/gateway requests.

func WithCaller

func WithCaller(parent context.Context, caller CallerID, method AuthMethod) context.Context

WithCaller stores the authenticated caller ID and method in context.

Types

type A2AEnvelope

type A2AEnvelope struct {
	// SourceAgentCard is the source agent's card reference.
	SourceAgentCard string `json:"source_agent_card"`
	// TargetAgentCard is the target agent's card reference.
	TargetAgentCard string `json:"target_agent_card"`
	// TargetLockDigest is the expected digest of the target agent lock.
	TargetLockDigest string `json:"target_lock_digest,omitempty"`
	// ParentTaskID is the originating task.
	ParentTaskID string `json:"parent_task_id"`
	// ParentRunID is the originating run.
	ParentRunID string `json:"parent_run_id"`
	// ContextID groups related handoffs.
	ContextID string `json:"context_id"`
	// CorrelationID for tracing.
	CorrelationID string `json:"correlation_id"`
	// MessageRole: "user", "assistant", "system".
	MessageRole string `json:"message_role"`
	// Parts are the message parts (A2A format).
	Parts []A2APart `json:"parts"`
	// ArtifactRefs reference external artifacts without embedding them.
	ArtifactRefs []ArtifactRef `json:"artifact_refs"`
	// Metadata is an arbitrary key-value map.
	Metadata map[string]string `json:"metadata"`
}

A2AEnvelope is an Agent2Agent-compatible message envelope.

type A2APart

type A2APart struct {
	Type string `json:"type"`
	Data string `json:"data"`
}

A2APart is a single message part in the A2A envelope.

type APIKeyAuthenticator

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

APIKeyAuthenticator authenticates via Bearer token API keys.

func NewAPIKeyAuthenticator

func NewAPIKeyAuthenticator(keys map[string]*APIKeyMeta) *APIKeyAuthenticator

NewAPIKeyAuthenticator creates an authenticator with the given keys.

func (*APIKeyAuthenticator) Authenticate

func (a *APIKeyAuthenticator) Authenticate(requestContext context.Context) (CallerID, AuthMethod, error)

Authenticate extracts a Bearer token from gRPC metadata or HTTP context and validates it against the registered keys.

type APIKeyMeta

type APIKeyMeta struct {
	ID      string
	KeyHash string
	Scopes  []string
	Revoked bool
}

APIKeyMeta holds metadata for an API key.

type APIKeyRecord

type APIKeyRecord struct {
	ID          string     `json:"id"`
	KeyHash     string     `json:"key_hash"`
	Scopes      []string   `json:"scopes"`
	Description string     `json:"description"`
	CreatedAt   time.Time  `json:"created_at"`
	CreatedBy   string     `json:"created_by"`
	Revoked     bool       `json:"revoked"`
	RevokedAt   *time.Time `json:"revoked_at,omitempty"`
	LastUsedAt  *time.Time `json:"last_used_at,omitempty"`
}

APIKeyRecord is the persisted record for an API key. It never contains the raw key, only a hash and metadata.

func (*APIKeyRecord) HasScope

func (r *APIKeyRecord) HasScope(scope string) bool

HasScope checks if a key record has the given scope.

type APIKeyStore

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

APIKeyStore manages API key lifecycle with file-based persistence.

func NewAPIKeyStore

func NewAPIKeyStore(filePath string, auditAppender audit.AuditAppender) (*APIKeyStore, error)

NewAPIKeyStore creates a store backed by the given file path.

func (*APIKeyStore) Authenticate

func (s *APIKeyStore) Authenticate(ctx context.Context) (CallerID, AuthMethod, error)

Authenticate implements Authenticator for APIKeyStore.

func (*APIKeyStore) CreateKey

func (s *APIKeyStore) CreateKey(ctx context.Context, scopes []string, description, createdBy string) (string, *APIKeyRecord, error)

CreateKey generates a new API key, persists its hash, and returns the raw key once.

func (*APIKeyStore) ListKeys

func (s *APIKeyStore) ListKeys() []*APIKeyRecord

ListKeys returns all key records without raw keys.

func (*APIKeyStore) RevokeKey

func (s *APIKeyStore) RevokeKey(ctx context.Context, keyID, revokedBy string) error

RevokeKey marks a key as revoked.

func (*APIKeyStore) RotateKey

func (s *APIKeyStore) RotateKey(ctx context.Context, oldKeyID, rotatedBy string) (string, *APIKeyRecord, error)

RotateKey revokes the old key and creates a replacement with the same scopes.

func (*APIKeyStore) ValidateKey

func (s *APIKeyStore) ValidateKey(ctx context.Context, rawKey string) (*APIKeyRecord, error)

ValidateKey checks a raw API key against the store.

type ArtifactRef

type ArtifactRef struct {
	// URI is the artifact location (e.g. "agentpaas://artifact/<id>").
	URI string `json:"uri"`
	// Digest is the SHA-256 of the artifact.
	Digest string `json:"digest"`
	// Size is the artifact byte size.
	Size int64 `json:"size"`
}

ArtifactRef references an artifact without embedding it.

type AuthMethod

type AuthMethod string

AuthMethod is the authentication method used for a request.

const (
	// AuthMethodNone means no valid authentication method was presented.
	AuthMethodNone AuthMethod = "none"
	// AuthMethodAPIKey means the request authenticated with a Bearer API key.
	AuthMethodAPIKey AuthMethod = "api_key"
	// AuthMethodMTLS means the request authenticated with an mTLS identity.
	AuthMethodMTLS AuthMethod = "mtls"
)

func CallerMethodFromContext

func CallerMethodFromContext(parent context.Context) (AuthMethod, bool)

CallerMethodFromContext returns the auth method, if any.

type Authenticator

type Authenticator interface {
	Authenticate(context.Context) (CallerID, AuthMethod, error)
}

Authenticator checks credentials and returns the caller ID and method.

type CORSMiddleware

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

CORSMiddleware wraps an HTTP handler with deny-by-default CORS.

func NewCORSMiddleware

func NewCORSMiddleware(allowedOrigins []string) *CORSMiddleware

NewCORSMiddleware creates a middleware with the given allowed origins.

func (*CORSMiddleware) Wrap

func (c *CORSMiddleware) Wrap(next http.Handler) http.Handler

Wrap wraps the given handler with CORS handling.

type CallerID

type CallerID string

CallerID is the stable caller identity, for example "api_key:<id>" or "spiffe:<subject>".

func CallerFromContext

func CallerFromContext(parent context.Context) (CallerID, bool)

CallerFromContext returns the authenticated caller ID, if any.

type CronConfig

type CronConfig struct {
	// Schedules is the list of cron schedules to manage.
	Schedules []*CronSchedule
	// StatePath is an optional path to a JSON file for schedule persistence.
	StatePath string
	// Audit is the audit appender for cron events.
	Audit audit.AuditAppender
	// TriggerService is the service to invoke when cron fires.
	TriggerService *TriggerService
}

CronConfig configures the cron scheduler.

type CronExpr

type CronExpr struct {
	Minute     CronField
	Hour       CronField
	DayOfMonth CronField
	Month      CronField
	DayOfWeek  CronField
}

CronExpr is a parsed 5-field cron expression.

func ParseCron

func ParseCron(expr string) (*CronExpr, error)

ParseCron parses a 5-field cron expression.

func (*CronExpr) Match

func (e *CronExpr) Match(t time.Time) bool

Match checks if the cron expression matches the given time.

type CronField

type CronField struct {
	Values []int
}

CronField represents a parsed cron field.

func (CronField) Match

func (f CronField) Match(v int) bool

Match checks if the field matches the given value.

type CronSchedule

type CronSchedule struct {
	// Expr is the 5-field cron expression.
	Expr string
	// ScheduleID is the unique identifier for this schedule.
	ScheduleID string
	// AgentName is the target agent to invoke.
	AgentName string
	// AgentVersion is the target agent version (optional).
	AgentVersion string
	// Payload is the fixed payload bytes for the cron invoke.
	Payload []byte
	// ContentType is the payload content type.
	ContentType string
	// Timezone is the timezone (empty = local).
	Timezone string
	// MissedRunPolicy is "skip" (default) or "catchup".
	MissedRunPolicy string
	// ConcurrencyPolicy is "forbid" (default).
	ConcurrencyPolicy string
	// IdempotencyKey is an optional idempotency key prefix.
	IdempotencyKey string
}

CronSchedule defines a cron trigger.

type CronScheduler

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

CronScheduler manages cron-triggered invocations.

func NewCronScheduler

func NewCronScheduler(cfg CronConfig) *CronScheduler

NewCronScheduler creates a new cron scheduler.

func (*CronScheduler) AddSchedule

func (cs *CronScheduler) AddSchedule(ctx context.Context, schedule *CronSchedule) (string, error)

AddSchedule adds a schedule to the scheduler at runtime. It validates the cron expression, generates a ScheduleID if one is not provided, and persists the updated list when StatePath is configured.

func (*CronScheduler) ListSchedules

func (cs *CronScheduler) ListSchedules() []*CronSchedule

ListSchedules returns a copy of all currently registered schedules.

func (*CronScheduler) RemoveSchedule

func (cs *CronScheduler) RemoveSchedule(ctx context.Context, scheduleID string) error

RemoveSchedule removes a schedule by its ScheduleID. Returns a NotFound error if no schedule with the given ID exists.

func (*CronScheduler) Start

func (cs *CronScheduler) Start()

Start begins the cron scheduler. It checks every minute on the minute.

func (*CronScheduler) Stop

func (cs *CronScheduler) Stop()

Stop stops the cron scheduler.

type EgressChecker

type EgressChecker interface {
	IsURLAllowed(rawURL string) bool
}

EgressChecker checks whether a webhook URL is allowed by policy.

func NewEgressChecker

func NewEgressChecker(rules []policy.EgressRule) EgressChecker

NewEgressChecker creates an EgressChecker from policy egress rules.

type EventBus

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

EventBus manages event subscriptions for runs.

func NewEventBus

func NewEventBus() *EventBus

NewEventBus creates a new event bus.

func (*EventBus) GetEvents

func (b *EventBus) GetEvents(runID string) []RunEvent

GetEvents returns all events for a run.

func (*EventBus) Publish

func (b *EventBus) Publish(runID string, eventType EventType, data any) *RunEvent

Publish publishes an event for a run. Non-blocking if there are no subscribers.

func (*EventBus) RegisterRun

func (b *EventBus) RegisterRun(runID string)

RegisterRun creates a buffer for a new run.

func (*EventBus) Subscribe

func (b *EventBus) Subscribe(runID string, fromEventID int64) (<-chan RunEvent, func())

Subscribe subscribes to events for a run, replaying events after fromEventID.

func (*EventBus) UnregisterRun

func (b *EventBus) UnregisterRun(runID string)

UnregisterRun removes a run's buffer.

type EventType

type EventType string

EventType is the type of run event.

const (
	EventRunCreated   EventType = "run_created"
	EventRunStarted   EventType = "run_started"
	EventRunProgress  EventType = "run_progress"
	EventRunSucceeded EventType = "run_succeeded"
	EventRunFailed    EventType = "run_failed"
	EventRunCancelled EventType = "run_cancelled"
	EventHeartbeat    EventType = "heartbeat"
)

type HandoffConcurrencyPolicy

type HandoffConcurrencyPolicy string

HandoffConcurrencyPolicy controls concurrent handoffs to the same target.

const (
	HandoffConcurrencyAllow  HandoffConcurrencyPolicy = "allow"
	HandoffConcurrencyForbid HandoffConcurrencyPolicy = "forbid"
)

type HandoffConfig

type HandoffConfig struct {
	// SourceAgent is the agent that initiates the handoff.
	SourceAgent string
	// TargetAgent is the agent to invoke.
	TargetAgent string
	// TargetAgentVersion is the target agent version (optional).
	TargetAgentVersion string
	// TargetLockDigest is the expected digest of the target agent lock.
	TargetLockDigest string
	// PayloadMode controls how the handoff payload is built.
	PayloadMode PayloadMode
	// FixedJSON is the payload for PayloadModeFixedJSON.
	FixedJSON json.RawMessage
	// ContentType for the handoff invoke request.
	ContentType string
	// MaxDepth limits handoff chain depth (default 5).
	MaxDepth int
	// IdempotencyKeyPrefix for the handoff invoke.
	IdempotencyKeyPrefix string
	// ConcurrencyPolicy controls concurrent handoffs to the target.
	ConcurrencyPolicy HandoffConcurrencyPolicy
}

HandoffConfig defines a static approved handoff rule.

type HandoffManager

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

HandoffManager manages local handoff triggers.

func NewHandoffManager

func NewHandoffManager(cfgs []*HandoffConfig, auditAppender audit.AuditAppender) *HandoffManager

NewHandoffManager creates a new handoff manager.

func (*HandoffManager) SetTriggerService

func (hm *HandoffManager) SetTriggerService(svc *TriggerService)

SetTriggerService wires the invoke function to TriggerService.Invoke.

func (*HandoffManager) Trigger

func (hm *HandoffManager) Trigger(ctx context.Context, req *HandoffRequest) (*HandoffResult, error)

Trigger attempts a handoff for the given source agent.

type HandoffRequest

type HandoffRequest struct {
	// SourceAgent is the agent requesting the handoff.
	SourceAgent string
	// ParentRunID is the run that triggered this handoff.
	ParentRunID string
	// ContextID groups related handoffs.
	ContextID string
	// CorrelationID for tracing (generated if empty).
	CorrelationID string
	// SummaryRef references a summary of the parent run.
	SummaryRef string
	// ArtifactRefs are artifacts from the parent run.
	ArtifactRefs []ArtifactRef
	// Metadata is passed through to the envelope.
	Metadata map[string]string
}

HandoffRequest is the input to trigger a handoff.

type HandoffResult

type HandoffResult struct {
	// Invoked is true if the handoff was triggered.
	Invoked bool
	// RunID is the new run ID (if invoked).
	RunID string
	// Envelope is the A2A envelope that was sent.
	Envelope *A2AEnvelope
	// Reason explains why the handoff was skipped or denied.
	Reason string
}

HandoffResult is the outcome of a handoff attempt.

type IdempotencyEntry

type IdempotencyEntry struct {
	Key         string    `json:"key"`
	RunID       string    `json:"run_id"`
	RequestHash string    `json:"request_hash"`
	CallerID    string    `json:"caller_id"`
	AgentName   string    `json:"agent_name"`
	CreatedAt   time.Time `json:"created_at"`
	ExpiresAt   time.Time `json:"expires_at"`
}

IdempotencyEntry records a previous invoke for replay.

type IdempotencyResult

type IdempotencyResult int

IdempotencyResult is the outcome of checking idempotency.

const (
	// IdempotencyNew means no existing entry was found and the caller may proceed.
	IdempotencyNew IdempotencyResult = iota
	// IdempotencyReplayed means a matching previous entry was found.
	IdempotencyReplayed
	// IdempotencyConflict means the key exists with a different request hash.
	IdempotencyConflict
)

type IdempotencyStore

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

IdempotencyStore is a durable idempotency table backed by a JSON file.

func NewIdempotencyStore

func NewIdempotencyStore(filePath string, ttl time.Duration, auditAppender audit.AuditAppender) (*IdempotencyStore, error)

NewIdempotencyStore creates a store backed by the given file path.

func (*IdempotencyStore) CheckOrReserve

func (s *IdempotencyStore) CheckOrReserve(ctx context.Context, key, runID, requestHash, callerID, agentName string) (IdempotencyResult, *IdempotencyEntry, error)

CheckOrReserve checks for an existing entry, or reserves a new one.

func (*IdempotencyStore) EntryCount

func (s *IdempotencyStore) EntryCount() int

EntryCount returns the number of active entries.

func (*IdempotencyStore) PurgeExpired

func (s *IdempotencyStore) PurgeExpired()

PurgeExpired removes entries that have passed their expiry time.

type PayloadMode

type PayloadMode string

PayloadMode determines how the handoff payload is constructed.

const (
	PayloadModeEmpty       PayloadMode = "empty"
	PayloadModeSummaryRef  PayloadMode = "summary_ref"
	PayloadModeArtifactRef PayloadMode = "artifact_ref"
	PayloadModeFixedJSON   PayloadMode = "fixed_json"
)

type RunEntry

type RunEntry struct {
	RunID      string
	AgentName  string
	Status     triggerv1.RunStatus
	CreatedAt  time.Time
	StartedAt  time.Time
	FinishedAt time.Time
	CancelCtx  context.CancelFunc
	// contains filtered or unexported fields
}

type RunEvent

type RunEvent struct {
	EventID   int64     `json:"event_id"`
	RunID     string    `json:"run_id"`
	Type      EventType `json:"type"`
	Timestamp time.Time `json:"timestamp"`
	Data      any       `json:"data,omitempty"`
}

RunEvent is a single event in a run's lifecycle.

func (*RunEvent) IsTerminal

func (e *RunEvent) IsTerminal() bool

IsTerminal returns true if this event ends the run lifecycle.

type RunStore

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

func NewRunStore

func NewRunStore() *RunStore

func (*RunStore) Get

func (rs *RunStore) Get(runID string) (*RunEntry, bool)

func (*RunStore) List

func (rs *RunStore) List() []*RunEntry

func (*RunStore) MarkFinished

func (rs *RunStore) MarkFinished(runID string, runStatus triggerv1.RunStatus)

func (*RunStore) MarkStarted

func (rs *RunStore) MarkStarted(runID string)

func (*RunStore) Register

func (rs *RunStore) Register(runID, agentName string) *RunEntry

func (*RunStore) SetCancelFunc

func (rs *RunStore) SetCancelFunc(runID string, cancel context.CancelFunc)

type SSEHandler

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

SSEHandler handles Server-Sent Events for a specific run.

func NewSSEHandler

func NewSSEHandler(bus *EventBus) *SSEHandler

NewSSEHandler creates an SSE handler backed by the given event bus.

func (*SSEHandler) ServeSSE

func (h *SSEHandler) ServeSSE(w http.ResponseWriter, r *http.Request, runID string)

ServeSSE handles an SSE request for a specific run.

type Server

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

Server is the Trigger API server, serving gRPC and REST gateway endpoints.

func New

func New(cfg ServerConfig) (*Server, error)

New creates a new Trigger API server.

func (*Server) SetInvokeFunc

func (s *Server) SetInvokeFunc(fn func(ctx context.Context, agentName string) (string, error))

SetInvokeFunc wires Invoke to call the daemon's Run handler.

func (*Server) Start

func (s *Server) Start(parent context.Context) error

Start begins serving gRPC and REST.

func (*Server) Stop

func (s *Server) Stop()

Stop gracefully shuts down the server.

type ServerConfig

type ServerConfig struct {
	GRPCAddr        string
	RESTAddr        string
	Exposed         bool
	Authenticator   Authenticator
	CORS            *CORSMiddleware
	Audit           audit.AuditAppender
	MaxPayloadBytes int
	// IdempotencyStore handles idempotency key replay/conflict.
	IdempotencyStore *IdempotencyStore
	EventBus         *EventBus
}

ServerConfig configures the Trigger API server.

type TriggerService

type TriggerService struct {
	triggerv1.UnimplementedTriggerServiceServer
	// contains filtered or unexported fields
}

TriggerService implements triggerv1.TriggerServiceServer.

func NewTriggerService

func NewTriggerService(a audit.AuditAppender, maxPayload int, deps ...any) *TriggerService

NewTriggerService creates the trigger service implementation.

func (*TriggerService) CancelRun

func (*TriggerService) GetRun

GetRun retrieves a run by ID.

func (*TriggerService) Invoke

Invoke triggers an agent run. T01 returns a pending stub run.

func (*TriggerService) InvokeStream

InvokeStream triggers a run and streams lifecycle updates.

func (*TriggerService) ListRuns

ListRuns lists known runs.

func (*TriggerService) SetInvokeFunc

func (s *TriggerService) SetInvokeFunc(fn func(ctx context.Context, agentName string) (string, error))

SetInvokeFunc wires Invoke to the given run handler.

type WebhookConfig

type WebhookConfig struct {
	Name   string
	URL    string
	Secret string
}

WebhookConfig configures a webhook delivery target.

type WebhookDeliverer

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

WebhookDeliverer delivers events to webhook targets.

func NewWebhookDeliverer

func NewWebhookDeliverer(hooks []*WebhookConfig, auditAppender audit.AuditAppender, egressChecker EgressChecker) *WebhookDeliverer

NewWebhookDeliverer creates a webhook deliverer.

func (*WebhookDeliverer) Deliver

func (d *WebhookDeliverer) Deliver(ctx context.Context, event *RunEvent)

Deliver delivers an event to all configured hooks asynchronously.

func (*WebhookDeliverer) DeliverSync

func (d *WebhookDeliverer) DeliverSync(ctx context.Context, event *RunEvent) []WebhookDelivery

DeliverSync delivers an event to all configured hooks synchronously.

func (*WebhookDeliverer) GetDeliveries

func (d *WebhookDeliverer) GetDeliveries() []WebhookDelivery

GetDeliveries returns recorded deliveries.

type WebhookDelivery

type WebhookDelivery struct {
	Event      *RunEvent
	Hook       *WebhookConfig
	Attempt    int
	Status     string
	StatusCode int
	Error      string
	Timestamp  time.Time
}

WebhookDelivery represents a single delivery attempt.

Jump to

Keyboard shortcuts

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