newton-task-submission 0.7.2

Newton task submission domain and planner
//! Immutable task execution intents.

use crate::SubmissionId;
use alloy::primitives::{Bytes, B256};
use newton_core::newton_prover_task_manager::INewtonProverTaskManager::{Task, TaskResponse};
use serde::{Deserialize, Serialize};
use std::fmt::{self, Write as _};

/// Immutable member of a task batch intent.
#[derive(Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BatchIntentItem {
    /// Task-domain submission represented by this effect.
    pub submission_id: SubmissionId,
    /// Full task contract input.
    pub task: Task,
    /// Full task-response contract input.
    pub response: TaskResponse,
    /// Aggregated signature material.
    pub signature_data: Bytes,
    /// Per-task attestation material.
    pub attestation_data: Bytes,
    /// Expected canonical task hash used for effect verification.
    pub expected_task_hash: B256,
    /// Expected canonical response hash used for effect verification.
    pub expected_response_hash: B256,
}

impl fmt::Debug for BatchIntentItem {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("BatchIntentItem")
            .field("submission_id", &self.submission_id)
            .field("task_id", &self.task.taskId)
            .field("signature_data_len", &self.signature_data.len())
            .field("attestation_data_len", &self.attestation_data.len())
            .field("expected_task_hash", &self.expected_task_hash)
            .field("expected_response_hash", &self.expected_response_hash)
            .finish()
    }
}

/// Task-domain request for on-chain execution.
#[derive(Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum TaskExecutionIntent {
    /// Atomic gateway-originated task batch.
    #[serde(alias = "batch_create_and_respond")]
    CreateAndRespond {
        /// Allowlisted contract configuration role.
        contract_role: String,
        /// Ordered task effects executed atomically.
        items: Vec<BatchIntentItem>,
    },
    /// Responses for tasks already created on-chain.
    #[serde(alias = "batch_respond")]
    Respond {
        /// Allowlisted contract configuration role.
        contract_role: String,
        /// Ordered response effects executed atomically.
        items: Vec<BatchIntentItem>,
    },
}

impl TaskExecutionIntent {
    /// Returns the allowlisted contract configuration role.
    pub fn contract_role(&self) -> &str {
        match self {
            Self::CreateAndRespond { contract_role, .. } | Self::Respond { contract_role, .. } => contract_role,
        }
    }

    /// Returns the ordered task effects represented by the intent.
    pub fn items(&self) -> &[BatchIntentItem] {
        match self {
            Self::CreateAndRespond { items, .. } | Self::Respond { items, .. } => items,
        }
    }
}

impl fmt::Debug for TaskExecutionIntent {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::CreateAndRespond { contract_role, items } => formatter
                .debug_struct("CreateAndRespond")
                .field("contract_role", contract_role)
                .field("items", items)
                .finish(),
            Self::Respond { contract_role, items } => formatter
                .debug_struct("Respond")
                .field("contract_role", contract_role)
                .field("items", items)
                .finish(),
        }
    }
}

/// Renders a batch's task IDs as one stable, greppable log field.
///
/// Admission logs one `task_id` per request; every later line describes a whole
/// batch. Rendering the members with the same `Display` the admission line uses
/// keeps one substring search over a task ID sufficient to recover the entire
/// admission-to-chain trace.
pub fn task_ids_field(items: &[BatchIntentItem]) -> String {
    join_ids(items.iter().map(|item| item.task.taskId))
}

/// Renders a batch's submission IDs as one stable, greppable log field.
pub fn submission_ids_field(items: &[BatchIntentItem]) -> String {
    join_ids(items.iter().map(|item| item.submission_id))
}

fn join_ids<T: fmt::Display>(ids: impl Iterator<Item = T>) -> String {
    let mut joined = String::new();
    for id in ids {
        if !joined.is_empty() {
            joined.push(',');
        }
        let _ = write!(joined, "{id}");
    }
    joined
}