Actions & Outcomes¶
The Action is the fundamental unit of work in Acteon. Every request to the gateway creates an action that flows through the dispatch pipeline and produces an ActionOutcome.
The Action Type¶
An Action represents a request to perform some operation via an external provider:
pub struct Action {
pub id: ActionId, // Auto-generated UUID v4
pub namespace: Namespace, // Logical grouping (e.g., "notifications")
pub tenant: TenantId, // Multi-tenant isolation (e.g., "tenant-1")
pub provider: ProviderId, // Target provider (e.g., "email")
pub action_type: String, // Discriminator (e.g., "send_email")
pub payload: serde_json::Value,// Arbitrary JSON payload
pub metadata: ActionMetadata, // Key-value labels
pub dedup_key: Option<String>, // Deduplication identifier
pub status: Option<String>, // State machine state
pub fingerprint: Option<String>, // Event correlation
pub starts_at: Option<DateTime<Utc>>, // Event lifecycle start
pub ends_at: Option<DateTime<Utc>>, // Event lifecycle end
pub created_at: DateTime<Utc>, // Creation timestamp
}
Creating Actions¶
Use the builder pattern:
use acteon_core::Action;
use serde_json::json;
let action = Action::new(
"notifications", // namespace
"tenant-1", // tenant
"email", // provider
"send_email", // action_type
json!({ // payload
"to": "user@example.com",
"subject": "Welcome!",
"body": "Hello, world!"
}),
)
.with_dedup_key("welcome-user@example.com")
.with_metadata(ActionMetadata {
labels: HashMap::from([
("priority".into(), "high".into()),
("campaign".into(), "onboarding".into()),
]),
})
.with_status("firing")
.with_fingerprint("alert-cluster1-cpu");
JSON Representation¶
When sending actions via the API:
{
"namespace": "notifications",
"tenant": "tenant-1",
"provider": "email",
"action_type": "send_email",
"payload": {
"to": "user@example.com",
"subject": "Welcome!"
},
"dedup_key": "welcome-user@example.com",
"metadata": {
"labels": {
"priority": "high"
}
}
}
Field Reference¶
| Field | Required | Description |
|---|---|---|
namespace | Yes | Logical grouping for isolation |
tenant | Yes | Multi-tenant identifier |
provider | Yes | Target provider name |
action_type | Yes | Action discriminator for rule matching |
payload | Yes | Arbitrary JSON payload passed to the provider |
dedup_key | No | Key for deduplication (same key = same action) |
metadata.labels | No | Key-value labels for rule matching and grouping |
status | No | Current state (for state machine rules) |
fingerprint | No | Event correlation identifier |
starts_at | No | Event lifecycle start time |
ends_at | No | Event lifecycle end time |
Identity Types¶
Acteon uses newtypes for all identifiers to prevent accidental mixing:
| Type | Description | Example |
|---|---|---|
Namespace | Logical namespace | "notifications" |
TenantId | Tenant identifier | "tenant-1" |
ActionId | UUID v4 action ID | Auto-generated |
ProviderId | Provider name | "email" |
The ActionOutcome Type¶
Every dispatch produces exactly one ActionOutcome:
pub enum ActionOutcome {
Executed(ProviderResponse),
Deduplicated,
Suppressed { rule: String },
Rerouted { original_provider: String, new_provider: String, response: ProviderResponse },
Throttled { retry_after: Duration },
Failed(ActionError),
Grouped { group_id: String, group_size: usize, notify_at: DateTime<Utc> },
StateChanged { fingerprint: String, previous_state: String, new_state: String, notify: bool },
PendingApproval { approval_id: String, expires_at: DateTime<Utc>, approve_url: String, reject_url: String, notification_sent: bool },
ChainStarted { chain_id: String, chain_name: String, total_steps: usize, first_step: String },
}
Outcome Variants¶
graph LR
A[Action] --> B{Rule Engine}
B -->|No match| C[Executed]
B -->|Dedup rule| D[Deduplicated]
B -->|Suppress rule| E[Suppressed]
B -->|Reroute rule| F[Rerouted]
B -->|Throttle rule| G[Throttled]
B -->|Group rule| H[Grouped]
B -->|State machine rule| I[StateChanged]
B -->|Approval rule| J[PendingApproval]
B -->|Chain rule| K[ChainStarted]
C -->|Provider error| L[Failed] | Outcome | Description | When It Happens |
|---|---|---|
Executed | Provider executed the action successfully | Action passed all rules and was dispatched |
Deduplicated | Action was already processed | Same dedup_key within TTL window |
Suppressed | Action was blocked by a rule | Matches a suppress rule condition |
Rerouted | Action was sent to a different provider | Matches a reroute rule |
Throttled | Action hit rate limit | Exceeds max_count in window_seconds |
Failed | Provider returned an error after all retries | All retry attempts exhausted |
Grouped | Action was added to an event group | Matches a group rule |
StateChanged | Event transitioned to a new state | Matches a state_machine rule |
PendingApproval | Waiting for human approval | Matches a require_approval rule |
ChainStarted | Multi-step chain initiated | Matches a chain rule |
The ProviderResponse Type¶
Successful executions include a ProviderResponse:
pub struct ProviderResponse {
pub status: ResponseStatus, // Success | Failure | Partial
pub body: serde_json::Value, // Response payload
pub headers: HashMap<String, String>, // Response metadata
}
The ActionKey Type¶
Internally, Acteon uses an ActionKey for state store operations:
pub struct ActionKey {
pub namespace: Namespace,
pub tenant: TenantId,
pub action_id: ActionId,
pub discriminator: Option<String>,
}
The canonical form is: namespace:tenant:action_id[:discriminator]
This key is used for:
- Deduplication checks
- Distributed lock acquisition
- State store lookups
- Audit record correlation