From 117660d5c42ab64abc359659a4cfdc1374d0e15d Mon Sep 17 00:00:00 2001 From: Catty Steve <4795515+Catty2014@user.noreply.gitee.com> Date: Thu, 23 Apr 2026 16:18:20 +0800 Subject: [PATCH] feat(baker/finalize): notification system and consolidate status types - Rename types/build_status.rs to types/buildstatus.rs - Rename StageInfo.sub_stage field to substage for consistency - Move notification logic from finalize to dedicated notify module - Remove notification module from finalize (moved to notify) - Add notify module with event queuing, retry, and priority scheduling - Add exponential backoff for temporary notification failures - Implement plugin filtering by name list during registration - Add fetch_plugins helper for batch plugin download - Update finalize plugin registration to accept optional plugin filter - Fix finalize config template indentation for notification policy - Move all YAML templates to templates/ directory --- bake.sh.tmpl => templates/bake.sh.tmpl | 0 .../finalize.yml.tmpl | 15 +- .../prebake.yml.tmpl | 0 workshop-baker/src/bake.rs | 6 +- workshop-baker/src/finalize.rs | 70 +----- workshop-baker/src/finalize/plugin.rs | 65 ++++-- workshop-baker/src/finalize/plugin/fetch.rs | 43 +++- workshop-baker/src/lib.rs | 1 + workshop-baker/src/notify.rs | 203 ++++++++++++++++++ workshop-baker/src/notify/error.rs | 19 ++ workshop-baker/src/prebake.rs | 12 +- workshop-baker/src/types.rs | 2 +- .../types/{build_status.rs => buildstatus.rs} | 8 +- 13 files changed, 342 insertions(+), 102 deletions(-) rename bake.sh.tmpl => templates/bake.sh.tmpl (100%) rename finalize.yml.tmpl => templates/finalize.yml.tmpl (96%) rename prebake.yml.tmpl => templates/prebake.yml.tmpl (100%) create mode 100644 workshop-baker/src/notify/error.rs rename workshop-baker/src/types/{build_status.rs => buildstatus.rs} (96%) diff --git a/bake.sh.tmpl b/templates/bake.sh.tmpl similarity index 100% rename from bake.sh.tmpl rename to templates/bake.sh.tmpl diff --git a/finalize.yml.tmpl b/templates/finalize.yml.tmpl similarity index 96% rename from finalize.yml.tmpl rename to templates/finalize.yml.tmpl index 769ca0b..e8ee493 100644 --- a/finalize.yml.tmpl +++ b/templates/finalize.yml.tmpl @@ -156,11 +156,16 @@ notification: sms-notify: to: "+8610012345678" schema: "custom" - on_success: "[HBW Notify] Build Succeeded: {{ build.status }} ({{ build.duration }})" - on_failure: "[HBW Notify] Build Failed: {{ build.status }} ({{ build.duration }})" - on_finish_of: - stage: "bake.build" - content: "[HBW Notify] Build Progress: {{ build.status }} ({{ build.duration }})" + policy: + - on_success: "[HBW Notify] Build Succeeded: {{ build.status }} ({{ build.duration }})" + to: "+8610012345678" + - on_failure: "[HBW Notify] Build Failed: {{ build.status }} ({{ build.duration }})" + to: "+8610012345679" + + - on_finish_of: + stage: "bake.build" + content: "[HBW Notify] Build Progress: {{ build.status }} ({{ build.duration }})" + to: "+8610012345677" fallible: true # ============================================================================= diff --git a/prebake.yml.tmpl b/templates/prebake.yml.tmpl similarity index 100% rename from prebake.yml.tmpl rename to templates/prebake.yml.tmpl diff --git a/workshop-baker/src/bake.rs b/workshop-baker/src/bake.rs index b85117d..29da906 100644 --- a/workshop-baker/src/bake.rs +++ b/workshop-baker/src/bake.rs @@ -7,7 +7,7 @@ use crate::ExecutionContext; use crate::prebake::PrebakeConfig; use crate::prebake::security::get_drop_after; use crate::prebake::stage::PrebakeStage; -use crate::types::build_status::{write_stage_status, StageInfo, StagePhase, StageResult}; +use crate::types::buildstatus::{write_stage_status, StageInfo, StagePhase, StageResult}; use crate::{Engine, prebake}; use std::path::{PathBuf, Path}; use chrono::Utc; @@ -71,7 +71,7 @@ pub async fn bake( StageInfo { name: "trivial".to_string(), phase: StagePhase::Bake, - sub_stage: "trivial".to_string(), + substage: "trivial".to_string(), result: stage_result, duration_ms: Utc::now().signed_duration_since(stage_start).num_milliseconds() as u64, started_at: stage_start, @@ -101,7 +101,7 @@ pub async fn bake( StageInfo { name: func.name.clone(), phase: StagePhase::Bake, - sub_stage: func.name.clone(), + substage: func.name.clone(), result: stage_result, duration_ms: Utc::now().signed_duration_since(stage_start).num_milliseconds() as u64, started_at: stage_start, diff --git a/workshop-baker/src/finalize.rs b/workshop-baker/src/finalize.rs index 4fc1acf..2de64a8 100644 --- a/workshop-baker/src/finalize.rs +++ b/workshop-baker/src/finalize.rs @@ -1,7 +1,6 @@ pub mod config; pub mod error; pub mod event; -pub mod notification; pub mod plugin; pub mod stage; pub mod template; @@ -12,35 +11,15 @@ use std::path::PathBuf; use crate::finalize::plugin::fetch::{self, FetchArgument}; use crate::cli::Cli; use crate::engine::EventSender; -use crate::types::build_status::{read_build_status, BuildStatus, StageResult}; -use crate::types::build_status::{StageInfo, StagePhase}; +use crate::types::buildstatus::{read_build_status, BuildStatus, StageResult}; +use crate::types::buildstatus::{StageInfo, StagePhase}; use chrono::Utc; // use crate::finalize::error::PluginError; pub use config::*; pub use error::FinalizeError; use std::path::Path; -fn _extract_notification_plugins( - all_plugins: &plugin::PluginMap, - _notification_config: &config::NotificationConfig, -) -> Vec { - use std::collections::HashSet; - - let notification_names: HashSet<&str> = HashSet::from([ - "mail", - "webhook", - "satori", - "in-site-notify", - ]); - - all_plugins - .iter() - .filter(|(name, _)| notification_names.contains(name.as_str())) - .map(|(name, _)| name.clone()) - .collect() -} - -fn parse(path: &Path) -> Result { +pub fn parse(path: &Path) -> Result { let content = std::fs::read_to_string(path).map_err(FinalizeError::IoError)?; let config: FinalizeConfig = serde_yaml::from_str(&content)?; Ok(config) @@ -86,7 +65,7 @@ pub async fn finalize( // Register Plugins log::info!("Registering {} plugins", finalize.plugin.len()); - let registered_plugins = plugin::register(finalize.plugin)?; + let registered_plugins = plugin::register(finalize.plugin, None)?; // Early hook let earlyhook_result = stage::hook::hook( @@ -106,43 +85,6 @@ pub async fn finalize( // If prior stages failed, we still send notification but skip artifacts let prior_failed = build_status.has_failures() || earlyhook_result.is_err(); - // Send notification (before late hook, as per standalone flow) - if !finalize.notification.groups.is_empty() { - let core = notification::NotificationCore::new(registered_plugins.clone()); - let request = notification::NotificationRequest { - pipeline_name: ctx.pipeline_name.clone(), - build_id: ctx.task_id.clone(), - build_status: build_status.clone(), - final_result: final_result.clone(), - config: finalize.notification, - }; - if let Err(e) = core.dispatch(request, ctx, event_tx.clone()).await { - log::warn!("Notification dispatch failed: {}", e); - // Notification failure does not block finalize - } - } else { - log::debug!("No notification groups configured, skipping notification"); - } - - if prior_failed { - log::warn!("Prior stages failed, skipping artifacts and late hook"); - // Write finalize stage status as failure - let _ = crate::types::build_status::write_stage_status( - STANDALONE_STATUS_DIR, - StageInfo { - name: "finalize".to_string(), - phase: StagePhase::Finalize, - sub_stage: "notify".to_string(), - result: StageResult::Failure, - duration_ms: 0, - started_at: Utc::now(), - finished_at: Utc::now(), - error_message: Some("Prior stage failed".to_string()), - }, - ); - return Ok(()); - } - // Prepare artifacts // TODO: artifact stage implementation @@ -156,12 +98,12 @@ pub async fn finalize( .await?; // Write finalize success status - let _ = crate::types::build_status::write_stage_status( + let _ = crate::types::buildstatus::write_stage_status( STANDALONE_STATUS_DIR, StageInfo { name: "finalize".to_string(), phase: StagePhase::Finalize, - sub_stage: "clean".to_string(), + substage: "clean".to_string(), result: StageResult::Success, duration_ms: 0, started_at: Utc::now(), diff --git a/workshop-baker/src/finalize/plugin.rs b/workshop-baker/src/finalize/plugin.rs index a4481e0..b85da24 100644 --- a/workshop-baker/src/finalize/plugin.rs +++ b/workshop-baker/src/finalize/plugin.rs @@ -3,16 +3,16 @@ use crate::finalize::RawPluginMap; use crate::finalize::plugin::metadata::{PluginMetadata, PluginType}; use crate::{EventSender, ExecutionContext, finalize::error::PluginError}; use async_trait::async_trait; -use std::collections::HashMap; -use std::path::{PathBuf, Path}; +use std::collections::{HashMap, HashSet}; +use std::path::{Path, PathBuf}; use std::sync::Arc; mod dylib; +pub mod fetch; mod internal; mod metadata; mod rhai; mod shell; -pub mod fetch; #[derive(Clone)] pub struct FinalizePlugin { @@ -99,7 +99,7 @@ fn read_manifest(path: Option, name: &str) -> Result break 'load content, - Err(PluginError::ManifestNotFound(_)) => {}, + Err(PluginError::ManifestNotFound(_)) => {} Err(e) => return Err(e), } } @@ -107,26 +107,35 @@ fn read_manifest(path: Option, name: &str) -> Result Result { - let manifest_path = path.join(format!("{}{}", FINALIZE_PLUGIN_MANIFEST,suffix)); - log::debug!("[plugin:{}] Reading manifest from {}", name, manifest_path.display()); + let manifest_path = path.join(format!("{}{}", FINALIZE_PLUGIN_MANIFEST, suffix)); + log::debug!( + "[plugin:{}] Reading manifest from {}", + name, + manifest_path.display() + ); if !manifest_path.exists() { return Err(PluginError::ManifestNotFound(manifest_path)); } - std::fs::read_to_string(&manifest_path).map_err(|e| { - PluginError::PluginUnavailable { - name: name.to_string(), - reason: format!("Failed to read plugin manifest from {}: {}", manifest_path.display(), e.to_string()), - } + std::fs::read_to_string(&manifest_path).map_err(|e| PluginError::PluginUnavailable { + name: name.to_string(), + reason: format!( + "Failed to read plugin manifest from {}: {}", + manifest_path.display(), + e.to_string() + ), }) } @@ -202,17 +211,38 @@ fn get_entry(plugin_type: PluginType, name: &str) -> Result Result { +pub fn register( + external_plugins: RawPluginMap, + list: Option<&HashSet>, +) -> Result { let mut plugins: PluginMap = HashMap::new(); + let mut count = 0; // Register internal plugin for i in internal::get_internal_plugin() { + let name = i.name.to_string(); + match list { + Some(list) if !list.contains(&name) => { + log::debug!("Plugin {} not in list, skipping", name); + continue; + } + _ => {} + } log::debug!("Registering internal plugin {}", i.name); plugins.insert(i.name.to_string(), FinalizePlugin::new(i.entry)); + count += 1; } // Register external plugin for (name, data) in external_plugins.into_iter() { + match list { + Some(list) if !list.contains(&name) => { + log::debug!("Plugin {} not in list, skipping", name); + continue; + } + _ => {} + } + log::debug!("Registering external plugin {}", name); let (_source, config) = data.into_iter().next().unwrap_or_else(|| { panic!( @@ -234,8 +264,11 @@ pub fn register(external_plugins: RawPluginMap) -> Result>, +) -> Result<(), PluginError> { + log::info!("Preparing to download {} plugins", plugin_config.len()); + for (name, data) in plugin_config.iter_mut() { + match list { + Some(list) if !list.contains(name) => { + log::debug!("Plugin {} not in list, skipping", name); + continue; + } + _ => {} + } + log::debug!("Downloading plugin {}", name); + let (source, config) = data.iter_mut().next().ok_or_else(|| { + PluginError::PluginUnavailable { + name: name.clone(), + reason: "no source configured".to_string(), + } + })?; + let fetch_argument = FetchArgument { + checksum: config.checksum.clone(), + shallow: config.shallow, + }; + fetch::fetch_plugin(&source, &name, &plugin_path, fetch_argument).await?; + log::debug!("Storing plugin {} to {}", name, plugin_path.display()); + config.runtime.fspath = Some(plugin_path.join(name)); + } + Ok(()) +} diff --git a/workshop-baker/src/lib.rs b/workshop-baker/src/lib.rs index d18fbaf..41e1583 100644 --- a/workshop-baker/src/lib.rs +++ b/workshop-baker/src/lib.rs @@ -11,6 +11,7 @@ pub mod bake; pub mod socket; pub mod types; pub mod constant; +pub mod notify; // External crates re-exports pub use config; diff --git a/workshop-baker/src/notify.rs b/workshop-baker/src/notify.rs index e69de29..fd7a07b 100644 --- a/workshop-baker/src/notify.rs +++ b/workshop-baker/src/notify.rs @@ -0,0 +1,203 @@ +use crate::constant::FINALIZE_STANDALONE_PLUGIN_PATH; +use crate::finalize::FinalizeConfig; +use crate::finalize::NotificationConfig; +use crate::finalize::parse; +use crate::finalize::plugin; +use crate::finalize::plugin::FinalizePlugin; +use crate::finalize::plugin::fetch; +use crate::types::buildstatus::StagePhase; +use crate::types::buildstatus::StageResult; +use crate::{EventSender, ExecutionContext}; +use chrono::{DateTime, Utc}; +use serde::Serialize; +use std::collections::HashMap; +use std::collections::HashSet; +use std::hash::Hash; +use std::path::Path; +use std::path::PathBuf; +use uuid::Uuid; + +mod error; +use error::NotifyError; + +fn validate(finalize: &FinalizeConfig) -> anyhow::Result<()> { + let validate_result = finalize.validate(); + if let Err(errors) = validate_result { + for error in &errors { + log::error!("Error: {}", error); + } + anyhow::bail!(NotifyError::FinalizeValidationError(errors)) + }; + Ok(()) +} + +pub type NotifyPluginMap = HashMap; + +fn extract_plugin(finalize: &FinalizeConfig) -> HashSet { + finalize + .notification + .groups + .iter() + .flat_map(|(_, methods)| methods.keys()) + .cloned() + .collect() +} + +#[derive(Debug, Clone, Serialize)] +pub struct NotifyEvent { + id: Uuid, + stage: StagePhase, + substage: String, + result: StageResult, + not_before: DateTime, + priority: u32, + effective_priority: u32, + lives: u32, + max_lives: u32, + target: HashSet, // Target audience +} + +impl Hash for NotifyEvent { + fn hash(&self, state: &mut H) { + self.id.hash(state); + } +} + +impl PartialEq for NotifyEvent { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl Eq for NotifyEvent {} + +impl PartialOrd for NotifyEvent { + fn partial_cmp(&self, other: &Self) -> Option { + Some( + self.effective_priority + .cmp(&other.effective_priority) + .then(self.not_before.cmp(&other.not_before)) + .reverse(), + ) + } +} + +impl Ord for NotifyEvent { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.partial_cmp(other).unwrap() + } +} + +type BundledNotifyEvent = Vec; +type NotifyEventReceiver = tokio::sync::mpsc::Receiver; +pub type NotifyEventSender = tokio::sync::mpsc::Sender; + +const NOTIFY_NOTREADY_DELAY: std::time::Duration = std::time::Duration::from_secs(5); +const NOTIFY_NOTREADY_PRIORITY: u32 = 99; +const NOTIFY_TEMPORARY_DELAY_FACTOR: f64 = 2.0; +const NOTIFY_TEMPORARY_DELAY_TIME: f64 = 5.0; +const NOTIFY_TEMPORARY_MAX_TIME: f64 = f64::MAX; + +pub async fn notify( + finalize_path: &Path, + ctx: &mut ExecutionContext, + event_tx: EventSender, + notifyevent_rx: &mut NotifyEventReceiver, + notifyevent_tx: NotifyEventSender, +) -> anyhow::Result<()> { + let mut finalize = parse(finalize_path)?; + validate(&finalize)?; + let plugin_list = extract_plugin(&finalize); + + if plugin_list.is_empty() { + log::warn!("No notification method provided, skipping notification"); + // TODO: send event to event_tx + return Ok(()); + } + + if ctx.standalone { + fetch::fetch_plugins( + &PathBuf::from(FINALIZE_STANDALONE_PLUGIN_PATH), + &mut finalize.plugin, + Some(&plugin_list), + ) + .await?; + } + let notify_plugins = plugin::register(finalize.plugin, Some(&plugin_list))?; + + loop { + tokio::select! { + Some(events) = notifyevent_rx.recv() => { + match notify_handler(¬ify_plugins, &finalize.notification, &events){ + Ok(_) => {}, // Notification succeeded / failed with fallible + Err(e) => { + log::debug!("Failed to send notification: {:?}", e); + match e { + NotifyError::PluginNotAvailable(_) => { + // Should keep lives, add static delay and give less priority + for mut event in events { + event.not_before = Utc::now() + NOTIFY_NOTREADY_DELAY; + event.effective_priority = event.effective_priority.saturating_add(1); + notifyevent_tx.send(event).await?; + } + }, + NotifyError::TemporaryError(_) => { + // Should cost 1 life, add dynamic (exponential) delay + for mut event in events { + event.lives -= 1; + event.not_before = Utc::now() + notify_delay(&event); + notifyevent_tx.send(event).await?; + } + }, + NotifyError::PermanentError(_) => { + // Unexpected! + // Should be dropped + log::error!("None of the configured notification plugins works."); + log::error!("Message will be dropped."); + // TODO: Send audit/metric information + } + error => { + // Unexpected! + log::error!("Internal error: notify_handler returned unexpected error: {:?}", error); + } + }; + } + }; + } + } + } +} + +fn notify_handler( + plugins: &NotifyPluginMap, + config: &NotificationConfig, + event: &Vec, +) -> Result<(), NotifyError> { + todo!(); +} + +// calculate min(1, k/x)*t as notification grouping period +// to avoid excessive notification frequency +fn wait_time(group_period: u32, group_watermark: u32, count: u32) -> f32 { + if count == 0 { + log::warn!("Unexpected notification count: {}", count); + return group_period as f32; + } + f32::min(1.0, group_watermark as f32 / count as f32) * group_period as f32 +} + +// calculate p^(m-l)*t as notification delay +fn notify_delay(event: &NotifyEvent) -> std::time::Duration { + let exponent = (event.max_lives - event.lives) as i32; + if exponent < 0 { + log::warn!("Unexpected lives in notification: lives({}) > max_lives({})", event.lives, event.max_lives); + return std::time::Duration::from_secs_f64(NOTIFY_TEMPORARY_MAX_TIME); + } + let multiplier = NOTIFY_TEMPORARY_DELAY_FACTOR.powi(exponent); + let delay = multiplier * NOTIFY_TEMPORARY_DELAY_TIME; + if !delay.is_finite() || delay < 0.0 { + log::warn!("Unexpected calculated delay: {}, treating as infinite", delay); + return std::time::Duration::from_secs_f64(NOTIFY_TEMPORARY_MAX_TIME); + } + std::time::Duration::from_secs_f64(delay) +} diff --git a/workshop-baker/src/notify/error.rs b/workshop-baker/src/notify/error.rs new file mode 100644 index 0000000..f45b41c --- /dev/null +++ b/workshop-baker/src/notify/error.rs @@ -0,0 +1,19 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum NotifyError{ + #[error("Finalize config validation failed: {0:?}")] + FinalizeValidationError(Vec), + + #[error("Notification plugin not available {0}")] + PluginNotAvailable(String), + + #[error("Temporary failure: {0}")] + TemporaryError(String), + + #[error("Permanent error: {0}")] + PermanentError(String), + + #[error("Notification failed after {0} attempt(s)")] + Exhausted(u32), +} diff --git a/workshop-baker/src/prebake.rs b/workshop-baker/src/prebake.rs index 62e7052..f1aa651 100644 --- a/workshop-baker/src/prebake.rs +++ b/workshop-baker/src/prebake.rs @@ -32,7 +32,7 @@ use crate::{ error::PrebakeError, ExecutionContext, prebake::{security::get_drop_after, stage::PrebakeStage}, - types::build_status::{write_stage_status, StageInfo, StagePhase, StageResult}, + types::buildstatus::{write_stage_status, StageInfo, StagePhase, StageResult}, constant::STANDALONE_STATUS_DIR, }; use std::path::Path; @@ -137,7 +137,7 @@ mut ctx: &mut ExecutionContext, StageInfo { name: "bootstrap".to_string(), phase: StagePhase::Prebake, - sub_stage: "bootstrap".to_string(), + substage: "bootstrap".to_string(), result: stage_result, duration_ms: Utc::now().signed_duration_since(stage_start).num_milliseconds() as u64, started_at: stage_start, @@ -165,7 +165,7 @@ mut ctx: &mut ExecutionContext, StageInfo { name: "earlyhook".to_string(), phase: StagePhase::Prebake, - sub_stage: "earlyhook".to_string(), + substage: "earlyhook".to_string(), result: stage_result, duration_ms: Utc::now().signed_duration_since(stage_start).num_milliseconds() as u64, started_at: stage_start, @@ -247,7 +247,7 @@ mut ctx: &mut ExecutionContext, StageInfo { name: "depssystem".to_string(), phase: StagePhase::Prebake, - sub_stage: "depssystem".to_string(), + substage: "depssystem".to_string(), result: stage_result, duration_ms: Utc::now().signed_duration_since(stage_start).num_milliseconds() as u64, started_at: stage_start, @@ -272,7 +272,7 @@ mut ctx: &mut ExecutionContext, StageInfo { name: "depsuser".to_string(), phase: StagePhase::Prebake, - sub_stage: "depsuser".to_string(), + substage: "depsuser".to_string(), result: stage_result, duration_ms: Utc::now().signed_duration_since(stage_start).num_milliseconds() as u64, started_at: stage_start, @@ -301,7 +301,7 @@ mut ctx: &mut ExecutionContext, StageInfo { name: "latehook".to_string(), phase: StagePhase::Prebake, - sub_stage: "latehook".to_string(), + substage: "latehook".to_string(), result: stage_result, duration_ms: Utc::now().signed_duration_since(stage_start).num_milliseconds() as u64, started_at: stage_start, diff --git a/workshop-baker/src/types.rs b/workshop-baker/src/types.rs index 77e56f2..a523cd5 100644 --- a/workshop-baker/src/types.rs +++ b/workshop-baker/src/types.rs @@ -1,5 +1,5 @@ pub mod architecture; -pub mod build_status; +pub mod buildstatus; pub mod builderconfig; pub mod cache; pub mod command; diff --git a/workshop-baker/src/types/build_status.rs b/workshop-baker/src/types/buildstatus.rs similarity index 96% rename from workshop-baker/src/types/build_status.rs rename to workshop-baker/src/types/buildstatus.rs index 2f8d340..dc45b8b 100644 --- a/workshop-baker/src/types/build_status.rs +++ b/workshop-baker/src/types/buildstatus.rs @@ -41,7 +41,7 @@ impl std::fmt::Display for StageResult { pub struct StageInfo { pub name: String, pub phase: StagePhase, - pub sub_stage: String, + pub substage: String, pub result: StageResult, pub duration_ms: u64, pub started_at: DateTime, @@ -51,7 +51,7 @@ pub struct StageInfo { impl StageInfo { pub fn full_name(&self) -> String { - format!("{}.{}", self.phase, self.sub_stage) + format!("{}.{}", self.phase, self.substage) } } @@ -143,7 +143,7 @@ mod tests { StageInfo { name: name.to_string(), phase, - sub_stage: name.to_string(), + substage: name.to_string(), result, duration_ms: 1000, started_at: now, @@ -161,7 +161,7 @@ mod tests { let status = read_build_status(&dir).unwrap(); assert_eq!(status.stages.len(), 1); - assert_eq!(status.stages[0].sub_stage, "bootstrap"); + assert_eq!(status.stages[0].substage, "bootstrap"); assert_eq!(status.stages[0].result, StageResult::Success); }