Action Type DSL

The Action Type DSL (Domain-Specific Language) defines action types declaratively, configuring validation rules, processing behavior, and lifecycle hooks without modifying core code.

ActionDefinition Structure

Each action type is defined by an ActionDefinition:

struct ActionDefinition {
    action_type: String,      // e.g., "POST", "CMNT", "FLLW"
    version: u32,             // Schema version
    metadata: Metadata,       // Display name, description, etc.
    subtypes: Vec<String>,    // e.g., POST:IMG, POST:VID
    fields: Vec<FieldDef>,    // Field definitions
    schema: Schema,           // JSON Schema for validation
    behavior: Behavior,       // Processing behavior flags
    hooks: Hooks,             // Lifecycle hooks
    permissions: Permissions, // Permission requirements
    key_pattern: String,      // Database key pattern for overriding
}

Fields

Each field in an action is defined with validation rules:

struct FieldDef {
    name: String,             // Field name in JWT (e.g., "c", "p", "vis")
    field_type: FieldType,    // string, object, array, number, boolean
    required: bool,           // Is this field required?
    validate: Option<Schema>, // Additional validation rules
}

Behavior Flags

Behavior flags control how actions are processed:

Flag Description Example Types
broadcast Send to multiple recipients (followers) POST, REPOST
allow_unknown Accept actions from unknown senders CONN
ephemeral Don’t persist to database PRES (presence)
approvable Can be approved/rejected by recipient CONN, FLLW
requires_subscription Sender must be subscribed MSG, CONV
deliver_subject Deliver to subject owner CMNT, REACT
deliver_to_subject_owner Alternative delivery to subject owner CMNT

Behavior Examples

POST Action (broadcast content):

Behavior {
    broadcast: true,              // Send to all followers
    allow_unknown: false,         // Must be from followed/connected user
    ephemeral: false,             // Persist to database
    approvable: false,            // No approval needed
    requires_subscription: false, // No subscription required
    deliver_subject: false,       // Not a reply
}

CONN Action (connection request):

Behavior {
    broadcast: false,             // Send only to audience
    allow_unknown: true,          // Accept from anyone (with PoW)
    ephemeral: false,             // Persist to database
    approvable: true,             // Recipient can accept/reject
    requires_subscription: false, // No subscription required
    deliver_subject: false,       // Direct to audience
}

Hook System

Hooks allow custom logic at different points in the action lifecycle:

struct Hooks {
    on_create: Option<HookFn>,   // Called when action is created locally
    on_receive: Option<HookFn>,  // Called when action is received from federation
    on_accept: Option<HookFn>,   // Called when approvable action is accepted
    on_reject: Option<HookFn>,   // Called when approvable action is rejected
}

Hooks handle tasks such as: validating business logic and generating notifications on create/receive, establishing connections or granting permissions on accept, and cleaning up pending state on reject.

Permissions

Define who can perform which operations:

struct Permissions {
    create: PermissionRule,    // Who can create this action type
    read: PermissionRule,      // Who can read actions of this type
    delete: PermissionRule,    // Who can delete (owner only typically)
}

Permission Rules

Rule Description
Owner Only the action issuer
Audience The action’s audience field
Connected Mutually connected users
Follower Users who follow the issuer
Verified Any authenticated user
Public Anyone, including unauthenticated

Key Pattern

The key_pattern determines how actions are stored and whether they can be overridden:

Action Type Key Pattern Override Behavior
POST {iss}:{id} Each post is unique
CMNT {iss}:{p}:{id} Each comment is unique (p = parent)
REACT {iss}:{sub} One reaction per user per subject
FLLW {iss}:{aud} One follow per user per target
CONN {iss}:{aud} One connection request per pair

Override example:

User reacts to post with LIKE
  → Stored at key: "alice.example.com:a1~post123"

User changes reaction to LOVE
  → Same key: "alice.example.com:a1~post123"
  → Previous LIKE is REPLACED by LOVE

Example Definition

POST Definition

ActionDefinition {
    action_type: "POST",
    version: 1,
    metadata: Metadata {
        display_name: "Post",
        description: "A public post or status update",
        category: Category::Content,
    },
    subtypes: vec!["IMG", "VID", "LNK", "TXT"],
    fields: vec![
        FieldDef { name: "c", field_type: String, required: true },
        FieldDef { name: "a", field_type: Array, required: false },
        FieldDef { name: "vis", field_type: String, required: false },
        FieldDef { name: "f", field_type: String, required: false },
    ],
    behavior: Behavior {
        broadcast: true,
        allow_unknown: false,
        ephemeral: false,
        approvable: false,
        requires_subscription: false,
        deliver_subject: false,
    },
    hooks: Hooks {
        on_create: Some(post_on_create),
        on_receive: Some(post_on_receive),
        on_accept: None,
        on_reject: None,
    },
    permissions: Permissions {
        create: PermissionRule::Owner,
        read: PermissionRule::ByVisibility,
        delete: PermissionRule::Owner,
    },
    key_pattern: "{iss}:{id}",
}

Other action types (CMNT, CONV, etc.) follow the same pattern with type-specific fields, behavior flags, and hooks.

DSL Validation

When an action is created or received, it’s validated against its DSL definition:

validate_action(action, definition):
    1. Check required fields exist
    2. Validate field types
    3. Run schema validation
    4. Check behavior constraints:
       - If requires_subscription: verify sender is subscribed
       - If !allow_unknown: verify sender is known (following/connected)
       - If approvable: set initial status to pending
    5. Return validation result

See Also