Action Type DSL

The Action Type DSL defines action types declaratively. Each type configures field constraints, content validation, processing behavior, lifecycle hooks, and permissions without touching the core processing pipeline. All built-in definitions live in cloudillo-action (dsl/definitions.rs) and are validated at compile time.

ActionDefinition Structure

Each action type is an ActionDefinition:

struct ActionDefinition {
    r#type: String,                  // e.g., "POST", "CMNT", "REACT"
    version: String,                 // Semantic version, e.g., "1.0"
    description: String,
    metadata: Option<ActionMetadata>,    // category, tags, deprecated/experimental
    subtypes: Option<HashMap<String, String>>, // subtype code → description
    fields: FieldConstraints,        // required/forbidden per field
    schema: Option<ContentSchemaWrapper>, // validation for the content field
    behavior: BehaviorFlags,         // processing behavior
    key_pattern: Option<String>,     // dedup/override key template
    hooks: ActionHooks,              // lifecycle hooks
    permissions: Option<PermissionRules>,
}

The action type code in the JWT t claim combines type and subtype as TYPE:SUBTYPE (e.g., POST:IMG, REACT:LIKE, CONN:DEL). The DEL subtype is the conventional delete/revoke marker.

Field Constraints

Only five fields are configurable, and only their optionality — the field types are fixed:

struct FieldConstraints {
    content: Option<FieldConstraint>,     // JWT claim `c`  (json)
    audience: Option<FieldConstraint>,    // JWT claim `aud` (idTag)
    parent: Option<FieldConstraint>,      // JWT claim `p`   (actionId)
    subject: Option<FieldConstraint>,     // JWT claim `sub` (actionId/string)
    attachments: Option<FieldConstraint>, // JWT claim `a`   (fileId[])
}

Each field is Required, Forbidden, or — when omitted — optional:

Constraint Meaning
Required Field must be present and valid
Forbidden Field must be absent
(omitted) Field is optional

The content field is additionally validated against the optional schema (string length, enum, or object properties).

Behavior Flags

Behavior flags control how an action is processed and delivered. The implemented flags:

Flag Description
broadcast Send to all followers when posting to own wall (no audience)
allow_unknown Accept actions from non-connected/non-following senders
ephemeral Don’t persist; forward to WebSocket only (e.g. PRES)
approvable Can receive an APRV approval; enables auto-approve for trusted sources
requires_subscription Child actions require a valid SUBS subscription
subscribable This action can have SUBS pointing to it (CONV)
deliver_subject Deliver the subject action alongside this one
deliver_to_subject_owner Also deliver to the subject’s owner (INVT, REPOST)
default_flags Default capability flags applied at creation (e.g. rco)
gated_by_parent_flag Reject if the parent’s flags disable this action (e.g. C for CMNT)
gated_by_subject_flag Reject if the subject’s flags disable this action (e.g. R for REACT)
Note

requires_acceptance, local_only, ttl, sync, and federated exist in the struct but are reserved and not yet implemented.

Hooks

Hooks run native Rust logic at four lifecycle points. In the DSL definitions they are declared as HookImplementation::None; the actual native implementations are wired up through a registry (native_hooks/).

struct ActionHooks {
    on_create,   // local user creates the action
    on_receive,  // federated action arrives at /api/inbox
    on_accept,   // user accepts a confirmation action
    on_reject,   // user rejects a confirmation action
}

Permissions

struct PermissionRules {
    can_create: Option<String>,        // e.g. "authenticated"
    can_receive: Option<String>,       // e.g. "any", "followers", "authenticated"
    requires_following: Option<bool>,
    requires_connected: Option<bool>,
}

can_create / can_receive hold named rule strings ("authenticated", "any", "followers"). The requires_* booleans add relationship gates.

Key Pattern

key_pattern is a template that produces the database key used to deduplicate and override actions. A new action with the same key supersedes the previous one. Templates interpolate {type}, {issuer}, {audience}, {subject}, {parent}, and content fields like {content.name}.

Type key_pattern Effect
POST, CMNT, CONV None Every action is unique (action ID is the identity)
REACT, SUBS {type}:{subject}:{issuer} One per user per subject
CONN, FLLW {type}:{issuer}:{audience} One per user per target
REPOST {type}:{subject}:{issuer}:{audience} One per subject per target wall
STAT {type}:{parent} One stat row per action

Override example: Alice LIKEs a post, then changes to LOVE. Both REACT tokens share the key REACT:{subject}:alice.example.com, so the LOVE token supersedes the LIKE.

Validation Flow

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

  1. Check field constraints (Required present, Forbidden absent)
  2. Validate the content field against schema
  3. Apply behavior gates (gated_by_parent_flag, gated_by_subject_flag, requires_subscription)
  4. Enforce permissions (allow_unknown, requires_following/requires_connected)

See Also