Documentation
¶
Overview ¶
Package notify implements infrastructure adapters for notification operations.
The notify package provides concrete implementations of the OperationProvider port defined in the domain layer, enabling workflow steps to send notifications through multiple backends (desktop, webhook) without shell scripting. The provider supports built-in notification backends with 10-second HTTP timeout for network-based backends and platform detection for desktop notifications.
Architecture Role ¶
In the hexagonal architecture:
- Implements domain/ports.OperationProvider (NotifyOperationProvider adapter)
- Registers 1 notification operation (notify.send) via OperationRegistry at startup
- Application layer orchestrates notification steps via the OperationProvider port
- Domain layer defines operation contracts without notification backend coupling
All notification-specific types remain internal to this infrastructure adapter. The domain layer reuses existing OperationSchema, OperationResult, and InputSchema types without requiring new entities. This prevents domain layer pollution while maintaining full compile-time type safety.
Operation Types ¶
## Declarative Operations (operations.go)
Supported notification operations:
- notify.send: Send notification via configured backend (desktop, webhook)
Each operation is registered as an OperationSchema with input validation, output schema, and backend selection support.
Provider Implementation ¶
## NotifyOperationProvider (provider.go)
Operation execution and dispatch:
- ListOperations: Enumerate registered notification operations
- GetOperation: Retrieve operation schema by name
- Execute: Dispatch to backend based on backend input parameter
The provider uses a map-based dispatch with Backend interface to route to the appropriate notification backend. This keeps all notification logic in one cohesive package without requiring interface-per-backend abstractions (ADR-003).
Notification Backends ¶
## Backend Interface (types.go)
All backends implement a common Send interface:
- Send(ctx context.Context, payload NotificationPayload) (*BackendResult, error)
## DesktopBackend (desktop.go)
Platform-native desktop notifications:
- Linux: Uses notify-send via libnotify
- macOS: Uses osascript with 'display notification' AppleScript
- Detects platform at runtime and fails gracefully on unsupported systems
## WebhookBackend (webhook.go)
Generic HTTP webhook integration:
- JSON POST with standard AWF payload structure
- Supports any webhook-compatible service (Discord, Teams, PagerDuty, etc.)
- Fields: workflow, status, duration, message, outputs
Configuration ¶
## NotifyConfig (types.go)
Configuration resolution from .awf/config.yaml:
- default_backend: Backend to use when not specified in operation inputs
Configuration values are loaded at provider initialization and injected into backend instances.
Error Handling ¶
Operations return standard Go errors via fmt.Errorf with contextual messages.
Common error patterns:
- notify_missing_config: Required configuration value not provided
- notify_backend_not_found: Requested backend not available
- notify_missing_input: Required backend-specific input not provided
- notify_http_error: HTTP backend returned non-2xx status
- notify_timeout: Backend request exceeded 10-second timeout
- notify_platform_unsupported: Desktop backend unavailable on platform
All errors are wrapped with fmt.Errorf for error chain support.
Integration Points ¶
## CLI Wiring (internal/interfaces/cli/run.go)
Provider registration at startup:
- Create NotifyOperationProvider instance with config
- Create CompositeOperationProvider wrapping GitHub + Notify providers
- Connect to ExecutionService via SetOperationProvider()
- Follows F054 GitHub plugin wiring pattern
The provider is instantiated in the composition root (run.go) and injected into the application layer via dependency inversion. This enables compile-time wiring without RPC overhead (ADR-001).
Performance and Security ¶
Performance characteristics:
- HTTP timeout: 10 seconds for network-based backends (webhook)
- Desktop notifications: Platform-specific, typically <100ms
- No retries: Failed notifications return error immediately
Security measures:
- webhook URLs and tokens never logged
- Input validation prevents command injection in desktop backend
- Context cancellation propagated to all backends
- HTTP client follows redirects only for same-host (prevents SSRF)
Index ¶
- func AllOperations() []pluginmodel.OperationSchema
- type Backend
- type BackendResult
- type NotificationPayload
- type NotifyConfig
- type NotifyOperationProvider
- func (p *NotifyOperationProvider) Execute(ctx context.Context, name string, inputs map[string]any) (*pluginmodel.OperationResult, error)
- func (p *NotifyOperationProvider) GetOperation(name string) (*pluginmodel.OperationSchema, bool)
- func (p *NotifyOperationProvider) ListOperations() []*pluginmodel.OperationSchema
- func (p *NotifyOperationProvider) RegisterBackend(name string, backend Backend) error
- func (p *NotifyOperationProvider) SetDefaultBackend(name string)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AllOperations ¶
func AllOperations() []pluginmodel.OperationSchema
Types ¶
type Backend ¶
type Backend interface {
// Send delivers a notification using this backend.
// Returns BackendResult on success or error on failure.
// The context is used for cancellation and timeout control.
Send(ctx context.Context, payload NotificationPayload) (*BackendResult, error)
}
Backend defines how a notification is delivered. Each backend implementation (desktop, webhook) implements this interface with backend-specific delivery logic.
func NewDesktopBackend ¶
func NewDesktopBackend() Backend
NewDesktopBackend creates a new desktop notification backend (exported). No configuration is required for desktop notifications. This is the public API used for CLI wiring in run.go.
func NewWebhookBackend ¶
func NewWebhookBackend() Backend
NewWebhookBackend creates a new webhook notification backend (exported). Webhook URLs are provided per-request via metadata rather than at construction time. This is the public API used for CLI wiring in run.go.
type BackendResult ¶
type BackendResult struct {
// Backend identifies which backend handled the notification
Backend string
// StatusCode is the HTTP status code for network backends (0 for desktop)
StatusCode int
// Response is the response body or confirmation message
Response string
}
BackendResult contains the outcome of a notification delivery. It is returned by backend implementations after successful or failed sends.
type NotificationPayload ¶
type NotificationPayload struct {
// Title is the notification title (optional, defaults to "AWF Workflow")
Title string
// Message is the notification body (required)
Message string
// Priority indicates the notification urgency: "low", "default", or "high"
// (optional, defaults to "default")
Priority string
// Metadata contains additional context like workflow name, status, duration
Metadata map[string]string
}
NotificationPayload contains the data sent to a notification backend. It is an immutable value object constructed by the provider and passed to backend implementations.
type NotifyConfig ¶
type NotifyConfig struct {
// DefaultBackend is the backend to use when not specified in operation inputs
// (valid values: "desktop", "webhook")
DefaultBackend string
}
NotifyConfig holds notification plugin configuration from .awf/config.yaml. These values are loaded at provider initialization and used by backends to resolve configuration like URLs and default backend selection.
type NotifyOperationProvider ¶
type NotifyOperationProvider struct {
// contains filtered or unexported fields
}
NotifyOperationProvider implements ports.OperationProvider for notification operations. Dispatches notify.send operation to backend-specific handlers (desktop, webhook).
The provider manages:
- Operation schema registry (notify.send)
- Backend dispatch via Backend interface
- Dynamic backend registration via RegisterBackend
- Input validation and payload construction
func NewNotifyOperationProvider ¶
func NewNotifyOperationProvider(logger ports.Logger) *NotifyOperationProvider
NewNotifyOperationProvider creates a new notification operation provider.
The provider starts with an empty backend registry. Use RegisterBackend to add notification backends dynamically. This enables the open/closed principle: new backends can be added without modifying the provider implementation.
Parameters:
- logger: structured logger for operation tracing
Returns:
- *NotifyOperationProvider: configured provider ready for backend registration
func (*NotifyOperationProvider) Execute ¶
func (p *NotifyOperationProvider) Execute(ctx context.Context, name string, inputs map[string]any) (*pluginmodel.OperationResult, error)
Execute runs a notification operation by name with the given inputs. Dispatches to backend-specific handlers based on the 'backend' input.
Implements ports.OperationProvider.
func (*NotifyOperationProvider) GetOperation ¶
func (p *NotifyOperationProvider) GetOperation(name string) (*pluginmodel.OperationSchema, bool)
GetOperation returns an operation schema by name. Implements ports.OperationProvider.
func (*NotifyOperationProvider) ListOperations ¶
func (p *NotifyOperationProvider) ListOperations() []*pluginmodel.OperationSchema
ListOperations returns all available notification operations. Implements ports.OperationProvider.
func (*NotifyOperationProvider) RegisterBackend ¶
func (p *NotifyOperationProvider) RegisterBackend(name string, backend Backend) error
RegisterBackend registers a notification backend with the provider.
Backends are registered by name (e.g., "desktop", "webhook") and must implement the Backend interface. Registration is idempotent: duplicate registrations for the same name return an error.
Parameters:
- name: backend identifier (must be unique)
- backend: implementation of the Backend interface
Returns:
- error: non-nil if name is already registered or backend is nil
func (*NotifyOperationProvider) SetDefaultBackend ¶
func (p *NotifyOperationProvider) SetDefaultBackend(name string)
SetDefaultBackend configures the fallback backend name.
The default backend is used when Execute is called without an explicit 'backend' input parameter. If no default is set and no backend is specified in inputs, Execute returns an error.
Parameters:
- name: backend identifier to use as default