aion-server 0.26.0

Aion workflow server library: HTTP, gRPC, WebSocket, and worker endpoints. Run it with the `aion` binary from the aion-cli crate.
Documentation
//! Which queues of a deployed document declare a built-in agent worker.
//!
//! # Why this is read from the SOURCE and not from the contract
//!
//! A `.aion` package's identity-bound `PackageContract` carries the task queue
//! and, per action, whether it is an agent seam — but it carries nothing about
//! the `harness` section: not the kind, not the concurrency, not the agent's
//! command or environment. The section is deliberately not flow meaning (the
//! MIR ratchet pins a document WITH one to the same lowering as the same
//! document WITHOUT one), so it never reaches the contract.
//!
//! What DOES reach the server is the archive's AWL provenance: the authored
//! document verbatim, plus the schema files it imports. That is the only place
//! the harness section exists on this side of the wire, so that is where this
//! reads it.
//!
//! # Auto-provision keys on the SECTION, not on deploys generally
//!
//! A document with no `harness` section mints nothing. That is not a fallback —
//! a queue with no section is the shape of a queue served by a worker SDK
//! build, by `aion worker shell`, or by declared `run` bodies, and standing a
//! built-in agent worker up on it would be the server inventing a launch
//! nobody declared.

use aion_awl::{CompiledHarness, WorkerDecl};

/// One queue of a deployed document that declares its own agent launch.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HarnessQueue {
    /// The task queue — the `worker` block's own name.
    pub task_queue: String,
    /// The compiled launch, proving the section is complete for its kind.
    ///
    /// Held rather than discarded because compiling it is the ONLY thing that
    /// proves an `aion worker agent` on this document would start: a section
    /// that refuses at the worker would otherwise be minted here and discovered
    /// as a crash loop.
    pub harness: CompiledHarness,
}

/// Why a deployed document's harness queues could not be read.
#[derive(Debug, thiserror::Error)]
pub enum HarnessQueueError {
    /// The archived document did not parse.
    ///
    /// The package still loaded — its identity is the compiled beams, and the
    /// archived source is provenance — so this names a package whose carried
    /// source disagrees with what it was built from.
    #[error(
        "the deployed package's archived AWL document could not be parsed, so this server \
         cannot tell whether it declares a built-in agent worker: {message}"
    )]
    Parse {
        /// The parser's own diagnosis.
        message: String,
    },
    /// A `harness` section is present but incomplete for the kind it declares.
    #[error(
        "task queue `{task_queue}` declares a `harness` section that is not complete: {message}. \
         Nothing was minted for it — fix the section, run `aion awl check`, and deploy again"
    )]
    Harness {
        /// The queue whose section refused.
        task_queue: String,
        /// The checker's own refusal.
        message: String,
    },
    /// Every action on the queue carries a declared body, so an agent worker
    /// would be started and never dispatched anything.
    #[error(
        "task queue `{task_queue}` declares a `harness` section but every one of its actions \
         ({actions}) carries a declared `run` body, which the server executes from the deployed \
         contract. There is nothing on that queue for an agent worker to serve, so none was \
         minted"
    )]
    NoServiceableAction {
        /// The queue with nothing to serve.
        task_queue: String,
        /// The action names, comma-separated, that all carry bodies.
        actions: String,
    },
}

/// Read every queue in `source` that declares a built-in agent launch.
///
/// An empty result is the ordinary answer, not a failure: most documents
/// declare no `harness` section and mint nothing.
///
/// # Errors
///
/// Returns [`HarnessQueueError::Parse`] when the archived document does not
/// parse, and — for a queue that DOES declare a section —
/// [`HarnessQueueError::Harness`] when the section is incomplete or
/// [`HarnessQueueError::NoServiceableAction`] when the queue has nothing an
/// agent could be dispatched. A queue with no section contributes neither a
/// result nor an error.
pub fn harness_queues(source: &str) -> Result<Vec<HarnessQueue>, HarnessQueueError> {
    let document = aion_awl::parse(source).map_err(|error| HarnessQueueError::Parse {
        message: error.to_string(),
    })?;
    let mut queues = Vec::new();
    for worker in &document.workers {
        if worker.harness.is_none() {
            continue;
        }
        queues.push(read_queue(worker)?);
    }
    Ok(queues)
}

/// Compile one declaring worker block into a mintable queue, or say why not.
fn read_queue(worker: &WorkerDecl) -> Result<HarnessQueue, HarnessQueueError> {
    let compiled =
        aion_awl::compile_harness(worker).map_err(|error| HarnessQueueError::Harness {
            task_queue: worker.name.clone(),
            message: error.to_string(),
        })?;
    // `compile_harness` answers `None` only for a block with NO section, which
    // the caller already ruled out. Reporting the impossible arm as the section
    // refusing keeps this total without a panic and without a silent skip.
    let Some(harness) = compiled else {
        return Err(HarnessQueueError::Harness {
            task_queue: worker.name.clone(),
            message: "the section vanished between the presence test and the compile".to_owned(),
        });
    };
    let bodied: Vec<&str> = worker
        .actions
        .iter()
        .filter(|action| action.body.is_some())
        .map(|action| action.name.as_str())
        .collect();
    if bodied.len() == worker.actions.len() {
        return Err(HarnessQueueError::NoServiceableAction {
            task_queue: worker.name.clone(),
            actions: if bodied.is_empty() {
                "the queue declares none".to_owned()
            } else {
                bodied.join(", ")
            },
        });
    }
    Ok(HarnessQueue {
        task_queue: worker.name.clone(),
        harness,
    })
}

#[cfg(test)]
#[path = "queues_tests.rs"]
mod tests;