use serde::{Deserialize, Serialize}; use time::OffsetDateTime; use uuid::Uuid; /// Versioned internal job request schema published to the message bus. /// Keep additions backward compatible; never reuse or repurpose existing fields. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct JobRequest { /// Schema identifier for routing and evolution. #[serde(default = "default_jobrequest_schema")] pub schema_version: String, // e.g., "jobrequest.v1" /// Unique request identifier for idempotency and tracing correlation. pub request_id: Uuid, /// Source system of this request (forge or manual trigger). pub source: SourceSystem, /// Repository clone URL (SSH or HTTPS). pub repo_url: String, /// Repository owner (parsed from webhook or URL); optional for backward-compat. #[serde(default)] pub repo_owner: Option, /// Repository name (parsed from webhook or URL); optional for backward-compat. #[serde(default)] pub repo_name: Option, /// Commit SHA to check out. pub commit_sha: String, /// Optional path to the workflow file within the repo (KDL). pub workflow_path: Option, /// Optional specific job id from the workflow to run. pub workflow_job_id: Option, /// Optional scheduling hint selecting a base image or host group. pub runs_on: Option, /// Submission timestamp (UTC). pub submitted_at: OffsetDateTime, } fn default_jobrequest_schema() -> String { "jobrequest.v1".to_string() } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum SourceSystem { Github, Forgejo, Manual, } impl JobRequest { pub fn new( source: SourceSystem, repo_url: impl Into, commit_sha: impl Into, ) -> Self { Self { schema_version: default_jobrequest_schema(), request_id: Uuid::new_v4(), source, repo_url: repo_url.into(), repo_owner: None, repo_name: None, commit_sha: commit_sha.into(), workflow_path: None, workflow_job_id: None, runs_on: None, submitted_at: OffsetDateTime::now_utc(), } } } /// Final job result reported by the orchestrator back to the Integration layer over MQ. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct JobResult { /// Schema identifier used as routing key/type. e.g., "jobresult.v1" #[serde(default = "default_jobresult_schema")] pub schema_version: String, /// Correlates to the original JobRequest.request_id pub request_id: Uuid, /// Repository and commit info (for convenience in consumers) pub repo_url: String, /// Repository owner (when known). Optional for backward-compat. #[serde(default)] pub repo_owner: Option, /// Repository name (when known). Optional for backward-compat. #[serde(default)] pub repo_name: Option, pub commit_sha: String, /// Outcome info pub success: bool, pub exit_code: i32, /// Optional human summary pub summary: Option, /// Completion timestamp pub completed_at: OffsetDateTime, } fn default_jobresult_schema() -> String { "jobresult.v1".to_string() } impl JobResult { pub fn new( request_id: Uuid, repo_url: String, commit_sha: String, success: bool, exit_code: i32, summary: Option, ) -> Self { Self { schema_version: default_jobresult_schema(), request_id, repo_url, repo_owner: None, repo_name: None, commit_sha, success, exit_code, summary, completed_at: OffsetDateTime::now_utc(), } } } /// Combined dead-letter message that includes the original JobRequest and /// an error summary so operators/tooling can inspect both together. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DeadLetter { #[serde(default = "default_deadletter_schema")] pub schema_version: String, // e.g., "deadletter.v1" pub request_id: Uuid, pub occurred_at: OffsetDateTime, /// Stage where the failure occurred (e.g., "prepare", "start", "other"). pub stage: String, /// Human-readable error summary (single-line preferred). pub error: String, /// Original job request that triggered the failure. pub original: JobRequest, /// Optional bag of extra diagnostic info. #[serde(default)] pub extra: Option>, } fn default_deadletter_schema() -> String { "deadletter.v1".to_string() } impl DeadLetter { pub fn new(stage: impl Into, error: impl Into, original: JobRequest) -> Self { let request_id = original.request_id; Self { schema_version: default_deadletter_schema(), request_id, occurred_at: OffsetDateTime::now_utc(), stage: stage.into(), error: error.into(), original, extra: None, } } }