holochain_types/
app.rs

1//! Everything to do with App (hApp) installation and uninstallation
2//!
3//! An App is a essentially a collection of Cells which are intended to be
4//! available for a particular Holochain use-case, such as a microservice used
5//! by some UI in a broader application.
6//!
7//! Each Cell maintains its own identity separate from any App.
8//! Access to Cells can be shared between different Apps.
9
10mod app_bundle;
11mod app_manifest;
12mod error;
13
14use crate::{dna::DnaBundle, prelude::*};
15pub use app_bundle::*;
16pub use app_manifest::app_manifest_validated::*;
17pub use app_manifest::*;
18use derive_more::Into;
19pub use error::*;
20use holo_hash::{AgentPubKey, DnaHash};
21use holochain_serialized_bytes::prelude::*;
22use holochain_util::ffs;
23use holochain_zome_types::cell::CloneId;
24use holochain_zome_types::prelude::*;
25use indexmap::IndexMap;
26use std::{collections::HashMap, path::PathBuf};
27
28/// The unique identifier for an installed app in this conductor
29pub type InstalledAppId = String;
30
31/// The source of the DNA to be installed, either as binary data, or from a path
32#[derive(Debug, serde::Serialize, serde::Deserialize)]
33#[serde(tag = "type", content = "value", rename_all = "snake_case")]
34pub enum DnaSource {
35    /// register the dna loaded from a bundle file on disk
36    Path(PathBuf),
37    /// register the dna as provided in the DnaBundle data structure
38    Bundle(Box<DnaBundle>),
39    /// register the dna from an existing registered DNA (assumes properties will be set)
40    Hash(DnaHash),
41}
42
43/// The source of coordinators to be installed, either as binary data, or from a path
44#[derive(Debug, serde::Serialize, serde::Deserialize)]
45#[serde(tag = "type", content = "value", rename_all = "snake_case")]
46pub enum CoordinatorSource {
47    /// Coordinators loaded from a bundle file on disk
48    Path(PathBuf),
49    /// Coordinators provided in the [`CoordinatorBundle`] data structure
50    Bundle(Box<CoordinatorBundle>),
51}
52
53/// The instructions on how to get the DNA to be registered
54#[derive(Debug, serde::Serialize, serde::Deserialize)]
55pub struct RegisterDnaPayload {
56    /// Modifier overrides
57    #[serde(default)]
58    pub modifiers: DnaModifiersOpt<YamlProperties>,
59    /// Where to find the DNA
60    pub source: DnaSource,
61}
62
63#[derive(Debug, serde::Serialize, serde::Deserialize)]
64/// The instructions on how to update coordinators for a dna file.
65pub struct UpdateCoordinatorsPayload {
66    /// The hash of the dna to swap coordinators for.
67    pub dna_hash: DnaHash,
68    /// Where to find the coordinators.
69    pub source: CoordinatorSource,
70}
71
72/// The parameters to create a clone of an existing cell.
73#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
74pub struct CreateCloneCellPayload {
75    /// The DNA's role name to clone
76    pub role_name: RoleName,
77    /// Modifiers to set for the new cell.
78    /// At least one of the modifiers must be set to obtain a distinct hash for
79    /// the clone cell's DNA.
80    pub modifiers: DnaModifiersOpt<YamlProperties>,
81    /// Optionally set a proof of membership for the clone cell
82    pub membrane_proof: Option<MembraneProof>,
83    /// Optionally a name for the DNA clone
84    pub name: Option<String>,
85}
86
87/// Parameters to specify the clone cell to be disabled.
88#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
89pub struct DisableCloneCellPayload {
90    /// The clone id or cell id of the clone cell
91    pub clone_cell_id: CloneCellId,
92}
93
94/// Parameters to specify the clone cell to be enabled.
95pub type EnableCloneCellPayload = DisableCloneCellPayload;
96
97/// Parameters to delete a disabled clone cell of an app.
98#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
99pub struct DeleteCloneCellPayload {
100    /// The app id that the DNA to clone belongs to
101    pub app_id: InstalledAppId,
102
103    /// The clone id or cell id of the clone cell
104    pub clone_cell_id: CloneCellId,
105}
106
107/// All the information necessary to install an app
108#[derive(Debug, serde::Serialize, serde::Deserialize)]
109pub struct InstallAppPayload {
110    /// Where to obtain the AppBundle, which contains the app manifest and DNA bundles
111    /// to be installed. This is the main payload of app installation.
112    pub source: AppBundleSource,
113
114    /// The agent to use when creating Cells for this App.
115    /// If None, a new agent key will be generated in the right circumstances (read on).
116    ///
117    /// It's always OK to provide a pregenerated agent key here, but there is at least one
118    /// major benefit to letting Holochain generate keys for you (other than
119    /// the sheer convenience of not having to generate your own):
120    ///
121    /// If you are using a device seed in your conductor config, the agent key will be derived
122    /// from that seed using a sensible scheme based on the total number of app installations
123    /// in this conductor, which means you can fairly easily regenerate all of your auto-generated
124    /// agent keys if you lose access to the device with your conductor data
125    /// (as long as you retain exclusive access to the device seed of course).
126    ///
127    /// Holochain will only generate an agent key for you if a device seed tag
128    /// is set and pointing to a seed present in lair. If this config is not set, installation
129    /// will fail if no agent key is provided. This safety mechanism can however be overridden
130    /// by setting the `allow_throwaway_random_agent_key` flag on this payload, which will cause
131    /// Holochain to generate a totally random (non-recoverable) agent key.
132    ///
133    /// If you are not using a device seed, or if your app has special requirements for agent keys,
134    /// you can always provide your own here, no matter what setting you're using.
135    #[serde(default)]
136    pub agent_key: Option<AgentPubKey>,
137
138    /// The unique identifier for an installed app in this conductor.
139    /// If not specified, it will be derived from the app name in the bundle manifest.
140    #[serde(default)]
141    pub installed_app_id: Option<InstalledAppId>,
142
143    /// Optional: Overwrites all network seeds for all DNAs of Cells created by this app.
144    /// This has a lower precedence than role-specific network seeds provided in the  `role_settings` field of the `InstallAppPayload`.
145    ///
146    /// The app can still use existing Cells, i.e. this does not require that
147    /// all Cells have DNAs with the same overridden DNA.
148    #[serde(default)]
149    pub network_seed: Option<NetworkSeed>,
150
151    /// Specify role specific settings or modifiers that will override any settings in
152    /// the dna manifets.
153    #[serde(default)]
154    pub roles_settings: Option<RoleSettingsMap>,
155
156    /// Optional: If app installation fails due to genesis failure, normally the app will be
157    /// immediately uninstalled. When this flag is set, the app is left installed with empty cells intact.
158    /// This can be useful for using `graft_records_onto_source_chain`, or for diagnostics.
159    #[serde(default)]
160    pub ignore_genesis_failure: bool,
161
162    /// By default, if an agent key is not specified, the conductor will generate a new one by
163    /// deriving a key from the device seed specified in the config. If the device seed is not set,
164    /// app installation will fail. If this flag is set, a random key will be created if no device
165    /// seed is present. This is a risky decision, because it will mean that if you lose control of
166    /// this device, you will not be able to regenerate your agent key from the device seed.
167    /// Use only in situations where you know that this is a throwaway key!
168    #[serde(default)]
169    pub allow_throwaway_random_agent_key: bool,
170}
171
172/// Alias
173pub type MemproofMap = HashMap<RoleName, MembraneProof>;
174/// Alias
175pub type ModifiersMap = HashMap<RoleName, DnaModifiersOpt<YamlProperties>>;
176/// Alias
177pub type ExistingCellsMap = HashMap<RoleName, CellId>;
178/// Alias
179pub type RoleSettingsMap = HashMap<RoleName, RoleSettings>;
180/// Alias
181pub type RoleSettingsMapYaml = HashMap<RoleName, RoleSettingsYaml>;
182
183/// Settings for a Role that may be passed on installation of an app
184#[derive(Debug, serde::Serialize, serde::Deserialize)]
185#[serde(tag = "type", content = "value", rename_all = "snake_case")]
186pub enum RoleSettings {
187    /// If the role has the UseExisting strategy defined in the app manifest
188    /// the cell id to use needs to be specified here.
189    UseExisting {
190        /// Existing cell id to use
191        cell_id: CellId,
192    },
193    /// Optional settings for a normally provisioned cell
194    Provisioned {
195        /// When the app being installed has the `allow_deferred_memproofs` manifest flag set,
196        /// passing `None` for this field for all roles in the app will allow the app to enter
197        /// the "deferred membrane proofs" state, so that memproofs can be provided later.
198        /// If `Some` is used here, whatever memproofs are
199        /// provided will be used, and the app will be installed as normal.
200        membrane_proof: Option<MembraneProof>,
201        /// Overwrites the dna modifiers from the dna manifest. Only
202        /// modifier fields for which `Some(T)` is provided will be overwritten.
203        modifiers: Option<DnaModifiersOpt<YamlProperties>>,
204    },
205}
206
207impl Default for RoleSettings {
208    fn default() -> Self {
209        Self::Provisioned {
210            membrane_proof: None,
211            modifiers: None,
212        }
213    }
214}
215
216impl From<RoleSettingsYaml> for RoleSettings {
217    fn from(role_settings: RoleSettingsYaml) -> Self {
218        match role_settings {
219            RoleSettingsYaml::Provisioned {
220                membrane_proof,
221                modifiers,
222            } => Self::Provisioned {
223                membrane_proof,
224                modifiers,
225            },
226            RoleSettingsYaml::UseExisting { cell_id } => Self::UseExisting { cell_id },
227        }
228    }
229}
230
231/// A version of RoleSettings that serializes to YAML without the content attribute
232#[derive(Debug, serde::Serialize, serde::Deserialize)]
233#[serde(tag = "type", rename_all = "snake_case")]
234pub enum RoleSettingsYaml {
235    /// If the role has the UseExisting strategy defined in the app manifest
236    /// the cell id to use needs to be specified here.
237    UseExisting {
238        /// Existing cell id to use
239        cell_id: CellId,
240    },
241    /// Optional settings for a normally provisioned cell
242    Provisioned {
243        /// When the app being installed has the `allow_deferred_memproofs` manifest flag set,
244        /// passing `None` for this field for all roles in the app will allow the app to enter
245        /// the "deferred membrane proofs" state, so that memproofs can be provided later.
246        /// If `Some` is used here, whatever memproofs are
247        /// provided will be used, and the app will be installed as normal.
248        membrane_proof: Option<MembraneProof>,
249        /// Overwrites the dna modifiers from the dna manifest. Only
250        /// modifier fields for which `Some(T)` is provided will be overwritten.
251        modifiers: Option<DnaModifiersOpt<YamlProperties>>,
252    },
253}
254
255/// The possible locations of an AppBundle
256#[derive(Debug, serde::Serialize, serde::Deserialize)]
257#[serde(tag = "type", content = "value", rename_all = "snake_case")]
258pub enum AppBundleSource {
259    /// The raw bytes of an app bundle
260    #[serde(with = "serde_bytes")]
261    Bytes(Vec<u8>),
262    /// A local file path
263    Path(PathBuf),
264    // /// A URL
265    // Url(String),
266}
267
268impl AppBundleSource {
269    /// Get the bundle from the source. Consumes the source.
270    pub async fn resolve(self) -> Result<AppBundle, AppBundleError> {
271        Ok(match self {
272            Self::Bytes(bytes) => AppBundle::decode(&bytes)?,
273            Self::Path(path) => AppBundle::decode(&ffs::read(&path).await?)?,
274            // Self::Url(url) => todo!("reqwest::get"),
275        })
276    }
277}
278
279/// Information needed to specify a DNA as part of an App
280#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
281pub struct InstallAppDnaPayload {
282    /// The hash of the DNA
283    pub hash: DnaHash,
284    /// The RoleName which will be assigned to this DNA when installed
285    pub role_name: RoleName,
286    /// App-specific proof-of-membrane-membership, if required by this app
287    pub membrane_proof: Option<MembraneProof>,
288}
289
290impl InstallAppDnaPayload {
291    /// Create a payload from hash. Good for tests.
292    pub fn hash_only(hash: DnaHash, role_name: RoleName) -> Self {
293        Self {
294            hash,
295            role_name,
296            membrane_proof: None,
297        }
298    }
299}
300
301/// Data about an installed Cell.
302#[derive(Clone, Debug, Into, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
303pub struct InstalledCell {
304    cell_id: CellId,
305    role_name: RoleName,
306}
307
308impl InstalledCell {
309    /// Constructor
310    pub fn new(cell_id: CellId, role_name: RoleName) -> Self {
311        Self { cell_id, role_name }
312    }
313
314    /// Get the CellId
315    pub fn into_id(self) -> CellId {
316        self.cell_id
317    }
318
319    /// Get the RoleName
320    pub fn into_role_name(self) -> RoleName {
321        self.role_name
322    }
323
324    /// Get the inner data as a tuple
325    pub fn into_inner(self) -> (CellId, RoleName) {
326        (self.cell_id, self.role_name)
327    }
328
329    /// Get the CellId
330    pub fn as_id(&self) -> &CellId {
331        &self.cell_id
332    }
333
334    /// Get the RoleName
335    pub fn as_role_name(&self) -> &RoleName {
336        &self.role_name
337    }
338}
339
340/// An app which has been installed.
341/// An installed app is merely its collection of "roles", associated with an ID.
342#[derive(
343    Clone,
344    Debug,
345    PartialEq,
346    Eq,
347    serde::Serialize,
348    serde::Deserialize,
349    derive_more::Constructor,
350    shrinkwraprs::Shrinkwrap,
351)]
352#[shrinkwrap(mutable, unsafe_ignore_visibility)]
353pub struct InstalledApp {
354    #[shrinkwrap(main_field)]
355    app: InstalledAppCommon,
356    /// The status of the installed app
357    pub status: AppStatus,
358}
359
360impl InstalledApp {
361    /// Constructor for freshly installed app
362    pub fn new_fresh(app: InstalledAppCommon) -> Self {
363        Self {
364            app,
365            status: AppStatus::Disabled(DisabledAppReason::NeverStarted),
366        }
367    }
368
369    /// Constructor for freshly installed app
370    #[cfg(feature = "test_utils")]
371    pub fn new_running(app: InstalledAppCommon) -> Self {
372        Self {
373            app,
374            status: AppStatus::Running,
375        }
376    }
377
378    /// Return the common app info, as well as a status which encodes the remaining
379    /// information
380    pub fn into_app_and_status(self) -> (InstalledAppCommon, AppStatus) {
381        (self.app, self.status)
382    }
383
384    /// Accessor
385    pub fn status(&self) -> &AppStatus {
386        &self.status
387    }
388
389    /// Accessor
390    pub fn id(&self) -> &InstalledAppId {
391        &self.app.installed_app_id
392    }
393}
394
395/// A map from InstalledAppId -> InstalledApp
396pub type InstalledAppMap = IndexMap<InstalledAppId, InstalledApp>;
397
398/// An active app
399#[derive(
400    Clone,
401    Debug,
402    PartialEq,
403    Eq,
404    serde::Serialize,
405    serde::Deserialize,
406    derive_more::From,
407    shrinkwraprs::Shrinkwrap,
408)]
409#[shrinkwrap(mutable, unsafe_ignore_visibility)]
410pub struct RunningApp(InstalledAppCommon);
411
412impl RunningApp {
413    /// Convert to a StoppedApp with the given reason
414    pub fn into_stopped(self, reason: StoppedAppReason) -> StoppedApp {
415        StoppedApp {
416            app: self.0,
417            reason,
418        }
419    }
420
421    /// Move inner type out
422    pub fn into_common(self) -> InstalledAppCommon {
423        self.0
424    }
425}
426
427impl From<RunningApp> for InstalledApp {
428    fn from(app: RunningApp) -> Self {
429        Self {
430            app: app.into_common(),
431            status: AppStatus::Running,
432        }
433    }
434}
435
436/// An app which is either Paused or Disabled, i.e. not Running
437#[derive(
438    Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, shrinkwraprs::Shrinkwrap,
439)]
440#[shrinkwrap(mutable, unsafe_ignore_visibility)]
441pub struct StoppedApp {
442    #[shrinkwrap(main_field)]
443    app: InstalledAppCommon,
444    reason: StoppedAppReason,
445}
446
447impl StoppedApp {
448    /// Constructor
449    pub fn new_fresh(app: InstalledAppCommon) -> Self {
450        Self {
451            app,
452            reason: StoppedAppReason::Disabled(DisabledAppReason::NeverStarted),
453        }
454    }
455
456    /// If the app is Stopped, convert into a StoppedApp.
457    /// Returns None if app is Running.
458    pub fn from_app(app: &InstalledApp) -> Option<Self> {
459        StoppedAppReason::from_status(app.status()).map(|reason| Self {
460            app: app.as_ref().clone(),
461            reason,
462        })
463    }
464
465    /// Convert to a RunningApp
466    pub fn into_active(self) -> RunningApp {
467        RunningApp(self.app)
468    }
469
470    /// Move inner type out
471    pub fn into_common(self) -> InstalledAppCommon {
472        self.app
473    }
474}
475
476impl From<StoppedApp> for InstalledAppCommon {
477    fn from(d: StoppedApp) -> Self {
478        d.app
479    }
480}
481
482impl From<StoppedApp> for InstalledApp {
483    fn from(d: StoppedApp) -> Self {
484        Self {
485            app: d.app,
486            status: d.reason.into(),
487        }
488    }
489}
490
491/// The common data between apps of any status
492#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
493pub struct InstalledAppCommon {
494    /// The unique identifier for an installed app in this conductor
495    pub installed_app_id: InstalledAppId,
496
497    /// The agent key used to install this app.
498    pub agent_key: AgentPubKey,
499
500    /// Assignments of DNA roles to cells and their clones, as specified in the AppManifest
501    pub role_assignments: IndexMap<RoleName, AppRoleAssignment>,
502
503    /// The manifest used to install the app.
504    pub manifest: AppManifest,
505
506    /// The timestamp when this app was installed
507    pub installed_at: Timestamp,
508}
509
510impl InstalledAppCommon {
511    /// Constructor
512    pub fn new<S: ToString, I: IntoIterator<Item = (RoleName, AppRoleAssignment)>>(
513        installed_app_id: S,
514        agent_key: AgentPubKey,
515        role_assignments: I,
516        manifest: AppManifest,
517        installed_at: Timestamp,
518    ) -> AppResult<Self> {
519        let role_assignments: IndexMap<_, _> = role_assignments.into_iter().collect();
520        // ensure no role name contains a clone id delimiter
521        if let Some((illegal_role_name, _)) = role_assignments
522            .iter()
523            .find(|(role_name, _)| role_name.contains(CLONE_ID_DELIMITER))
524        {
525            return Err(AppError::IllegalRoleName(illegal_role_name.clone()));
526        }
527        Ok(InstalledAppCommon {
528            installed_app_id: installed_app_id.to_string(),
529            agent_key,
530            role_assignments,
531            manifest,
532            installed_at,
533        })
534    }
535
536    /// Accessor
537    pub fn id(&self) -> &InstalledAppId {
538        &self.installed_app_id
539    }
540
541    /// Accessor
542    pub fn provisioned_cells(&self) -> impl Iterator<Item = (&RoleName, CellId)> {
543        self.role_assignments
544            .iter()
545            .filter_map(|(role_name, role)| {
546                role.provisioned_dna_hash()
547                    .map(|d| (role_name, CellId::new(d.clone(), self.agent_key.clone())))
548            })
549    }
550
551    /// Accessor
552    pub fn clone_cells(&self) -> impl Iterator<Item = (&CloneId, CellId)> {
553        self.role_assignments
554            .iter()
555            .flat_map(|app_role_assignment| {
556                app_role_assignment
557                    .1
558                    .as_primary()
559                    .into_iter()
560                    .flat_map(|p| {
561                        p.clones
562                            .iter()
563                            .map(|(id, d)| (id, CellId::new(d.clone(), self.agent_key.clone())))
564                    })
565            })
566    }
567
568    /// Accessor
569    pub fn disabled_clone_cells(&self) -> impl Iterator<Item = (&CloneId, CellId)> {
570        self.role_assignments
571            .iter()
572            .flat_map(|app_role_assignment| {
573                app_role_assignment
574                    .1
575                    .as_primary()
576                    .into_iter()
577                    .flat_map(|p| {
578                        p.disabled_clones
579                            .iter()
580                            .map(|(id, d)| (id, CellId::new(d.clone(), self.agent_key.clone())))
581                    })
582            })
583    }
584
585    /// Accessor
586    pub fn clone_cells_for_role_name(
587        &self,
588        role_name: &RoleName,
589    ) -> Option<impl Iterator<Item = (&CloneId, CellId)>> {
590        Some(
591            self.role_assignments
592                .get(role_name)?
593                .as_primary()?
594                .clones
595                .iter()
596                .map(|(id, dna_hash)| (id, CellId::new(dna_hash.clone(), self.agent_key.clone()))),
597        )
598    }
599
600    /// Accessor
601    pub fn disabled_clone_cells_for_role_name(
602        &self,
603        role_name: &RoleName,
604    ) -> Option<impl Iterator<Item = (&CloneId, CellId)>> {
605        Some(
606            self.role_assignments
607                .get(role_name)?
608                .as_primary()?
609                .disabled_clones
610                .iter()
611                .map(|(id, dna_hash)| (id, CellId::new(dna_hash.clone(), self.agent_key.clone()))),
612        )
613    }
614
615    /// Accessor
616    pub fn clone_cell_ids(&self) -> impl Iterator<Item = CellId> + '_ {
617        self.clone_cells().map(|(_, cell_id)| cell_id)
618    }
619
620    /// Accessor
621    pub fn disabled_clone_cell_ids(&self) -> impl Iterator<Item = CellId> + '_ {
622        self.disabled_clone_cells().map(|(_, cell_id)| cell_id)
623    }
624
625    /// Iterator of all cells, both provisioned and cloned.
626    // NOTE: as our app state model becomes more nuanced, we need to give careful attention to
627    // the definition of this function, since this represents all cells in use by the conductor.
628    // Any cell which exists and is not returned by this function is fair game for purging
629    // during app installation. See [`Conductor::remove_dangling_cells`].
630    pub fn all_cells(&self) -> impl Iterator<Item = CellId> + '_ {
631        self.provisioned_cells()
632            .map(|(_, c)| c)
633            .chain(self.clone_cell_ids())
634            .chain(self.disabled_clone_cell_ids())
635    }
636
637    /// Iterator of all running cells, both provisioned and cloned.
638    /// Provisioned cells will always be running if the app is running,
639    /// but some cloned cells may be disabled and will not be returned.
640    pub fn all_enabled_cells(&self) -> impl Iterator<Item = CellId> + '_ {
641        self.provisioned_cells()
642            .map(|(_, c)| c)
643            .chain(self.clone_cell_ids())
644    }
645
646    /// Iterator of all "required" cells, meaning Cells which must be running
647    /// for this App to be able to run.
648    ///
649    /// Currently this is simply all provisioned cells, but this concept may
650    /// become more nuanced in the future.
651    pub fn required_cells(&self) -> impl Iterator<Item = CellId> + '_ {
652        self.provisioned_cells().map(|(_, c)| c)
653    }
654
655    /// Accessor for particular role
656    pub fn role(&self, role_name: &RoleName) -> AppResult<&AppRoleAssignment> {
657        self.role_assignments
658            .get(role_name)
659            .ok_or_else(|| AppError::RoleNameMissing(role_name.clone()))
660    }
661
662    /// If the role is primary, i.e. of variant [`AppRoleAssignment::Primary`], return it
663    /// as [`AppRolePrimary`]. If the role is not primary, return Err.
664    pub fn primary_role(&self, role_name: &RoleName) -> AppResult<&AppRolePrimary> {
665        let app_id = self.installed_app_id.clone();
666        self.role(role_name)?
667            .as_primary()
668            .ok_or_else(|| AppError::NonPrimaryCell(app_id, role_name.clone()))
669    }
670
671    fn role_mut(&mut self, role_name: &RoleName) -> AppResult<&mut AppRoleAssignment> {
672        self.role_assignments
673            .get_mut(role_name)
674            .ok_or_else(|| AppError::RoleNameMissing(role_name.clone()))
675    }
676
677    fn primary_role_mut(&mut self, role_name: &RoleName) -> AppResult<&mut AppRolePrimary> {
678        let app_id = self.installed_app_id.clone();
679        self.role_mut(role_name)?
680            .as_primary_mut()
681            .ok_or_else(|| AppError::NonPrimaryCell(app_id, role_name.clone()))
682    }
683
684    /// Accessor
685    pub fn roles(&self) -> &IndexMap<RoleName, AppRoleAssignment> {
686        &self.role_assignments
687    }
688
689    /// Accessor
690    pub fn primary_roles(&self) -> impl Iterator<Item = (&RoleName, &AppRolePrimary)> {
691        self.role_assignments
692            .iter()
693            .filter_map(|(name, role)| Some((name, role.as_primary()?)))
694    }
695
696    /// Add a clone cell.
697    pub fn add_clone(&mut self, role_name: &RoleName, dna_hash: &DnaHash) -> AppResult<CloneId> {
698        let app_role_assignment = self.primary_role_mut(role_name)?;
699
700        if app_role_assignment.is_clone_limit_reached() {
701            return Err(AppError::CloneLimitExceeded(
702                app_role_assignment.clone_limit,
703                app_role_assignment.clone(),
704            ));
705        }
706        let clone_id = CloneId::new(role_name, app_role_assignment.next_clone_index);
707        if app_role_assignment.clones.contains_key(&clone_id) {
708            return Err(AppError::DuplicateCloneIds(clone_id));
709        }
710
711        // add clone
712        app_role_assignment
713            .clones
714            .insert(clone_id.clone(), dna_hash.clone());
715        // increment next clone index
716        app_role_assignment.next_clone_index += 1;
717        Ok(clone_id)
718    }
719
720    /// Get a clone cell id from its clone id.
721    pub fn get_clone_dna_hash(&self, clone_cell_id: &CloneCellId) -> AppResult<DnaHash> {
722        let cell_id = match clone_cell_id {
723            CloneCellId::DnaHash(dna_hash) => dna_hash,
724            CloneCellId::CloneId(clone_id) => self
725                .primary_role(&clone_id.as_base_role_name())?
726                .clones
727                .get(clone_id)
728                .ok_or_else(|| {
729                    AppError::CloneCellNotFound(CloneCellId::CloneId(clone_id.clone()))
730                })?,
731        };
732        Ok(cell_id.clone())
733    }
734
735    /// Get the clone id from either clone or cell id.
736    pub fn get_clone_id(&self, clone_cell_id: &CloneCellId) -> AppResult<CloneId> {
737        let clone_id = match clone_cell_id {
738            CloneCellId::CloneId(id) => id,
739            CloneCellId::DnaHash(id) => {
740                self.clone_cells()
741                    .find(|(_, cell_id)| cell_id.dna_hash() == id)
742                    .ok_or_else(|| AppError::CloneCellNotFound(CloneCellId::DnaHash(id.clone())))?
743                    .0
744            }
745        };
746        Ok(clone_id.clone())
747    }
748
749    /// Get the clone id from either clone or cell id.
750    pub fn get_disabled_clone_id(&self, clone_cell_id: &CloneCellId) -> AppResult<CloneId> {
751        let clone_id = match clone_cell_id {
752            CloneCellId::CloneId(id) => id.clone(),
753            CloneCellId::DnaHash(id) => self
754                .role_assignments
755                .iter()
756                .flat_map(|(_, role_assignment)| {
757                    role_assignment
758                        .as_primary()
759                        .into_iter()
760                        .flat_map(|r| r.disabled_clones.iter())
761                })
762                .find(|(_, cell_id)| *cell_id == id)
763                .ok_or_else(|| AppError::CloneCellNotFound(CloneCellId::DnaHash(id.clone())))?
764                .0
765                .clone(),
766        };
767        Ok(clone_id)
768    }
769
770    /// Disable a clone cell.
771    ///
772    /// Removes the cell from the list of clones, so it is not accessible any
773    /// longer. If the cell is already disabled, do nothing and return Ok.
774    pub fn disable_clone_cell(&mut self, clone_id: &CloneId) -> AppResult<()> {
775        let app_role_assignment = self.primary_role_mut(&clone_id.as_base_role_name())?;
776        // remove clone from role's clones map
777        match app_role_assignment.clones.remove(clone_id) {
778            None => {
779                if app_role_assignment.disabled_clones.contains_key(clone_id) {
780                    Ok(())
781                } else {
782                    Err(AppError::CloneCellNotFound(CloneCellId::CloneId(
783                        clone_id.to_owned(),
784                    )))
785                }
786            }
787            Some(cell_id) => {
788                // insert clone into disabled clones map
789                let insert_result = app_role_assignment
790                    .disabled_clones
791                    .insert(clone_id.to_owned(), cell_id);
792                assert!(
793                    insert_result.is_none(),
794                    "disable: clone cell is already disabled"
795                );
796                Ok(())
797            }
798        }
799    }
800
801    /// Enable a disabled clone cell.
802    ///
803    /// The clone cell is added back to the list of clones and can be accessed
804    /// again. If the cell is already enabled, do nothing and return Ok.
805    ///
806    /// # Returns
807    /// The enabled clone cell.
808    pub fn enable_clone_cell(&mut self, clone_id: &CloneId) -> AppResult<InstalledCell> {
809        let app_role_assignment = self.primary_role_mut(&clone_id.as_base_role_name())?;
810        // remove clone from disabled clones map
811        match app_role_assignment.disabled_clones.remove(clone_id) {
812            None => app_role_assignment
813                .clones
814                .get(clone_id)
815                .cloned()
816                .map(|dna_hash| {
817                    Ok(InstalledCell {
818                        role_name: clone_id.as_app_role_name().to_owned(),
819                        cell_id: CellId::new(dna_hash, self.agent_key.clone()),
820                    })
821                })
822                .unwrap_or_else(|| {
823                    Err(AppError::CloneCellNotFound(CloneCellId::CloneId(
824                        clone_id.to_owned(),
825                    )))
826                }),
827            Some(dna_hash) => {
828                // insert clone back into role's clones map
829                let insert_result = app_role_assignment
830                    .clones
831                    .insert(clone_id.to_owned(), dna_hash.clone());
832                assert!(
833                    insert_result.is_none(),
834                    "enable: clone cell already enabled"
835                );
836                Ok(InstalledCell {
837                    role_name: clone_id.as_app_role_name().to_owned(),
838                    cell_id: CellId::new(dna_hash, self.agent_key.clone()),
839                })
840            }
841        }
842    }
843
844    /// Delete a disabled clone cell.
845    pub fn delete_clone_cell(&mut self, clone_id: &CloneId) -> AppResult<()> {
846        let app_role_assignment = self.primary_role_mut(&clone_id.as_base_role_name())?;
847        app_role_assignment
848            .disabled_clones
849            .remove(clone_id)
850            .map(|_| ())
851            .ok_or_else(|| {
852                if app_role_assignment.clones.contains_key(clone_id) {
853                    AppError::CloneCellMustBeDisabledBeforeDeleting(CloneCellId::CloneId(
854                        clone_id.to_owned(),
855                    ))
856                } else {
857                    AppError::CloneCellNotFound(CloneCellId::CloneId(clone_id.to_owned()))
858                }
859            })
860    }
861
862    /// Accessor
863    pub fn agent_key(&self) -> &AgentPubKey {
864        &self.agent_key
865    }
866
867    /// Constructor for apps not using a manifest.
868    /// Allows for cloning up to 256 times and implies immediate provisioning.
869    #[cfg(feature = "test_utils")]
870    pub fn new_legacy<S: ToString, I: IntoIterator<Item = InstalledCell>>(
871        installed_app_id: S,
872        installed_cells: I,
873    ) -> AppResult<Self> {
874        use itertools::Itertools;
875
876        let installed_app_id = installed_app_id.to_string();
877        let installed_cells: Vec<_> = installed_cells.into_iter().collect();
878
879        // Get the agent key of the first cell
880        // NB: currently this has no significance.
881        let agent_key = installed_cells
882            .first()
883            .expect("Can't create app with 0 cells")
884            .cell_id
885            .agent_pubkey()
886            .to_owned();
887
888        // ensure all cells use the same agent key
889        if installed_cells
890            .iter()
891            .any(|c| *c.cell_id.agent_pubkey() != agent_key)
892        {
893            panic!(
894                "All cells in an app must use the same agent key. Cell data: {:#?}",
895                installed_cells
896            );
897        }
898
899        // ensure all cells use the same agent key
900        let duplicates: Vec<RoleName> = installed_cells
901            .iter()
902            .map(|c| c.role_name.to_owned())
903            .counts()
904            .into_iter()
905            .filter_map(|(role_name, count)| if count > 1 { Some(role_name) } else { None })
906            .collect();
907        if !duplicates.is_empty() {
908            return Err(AppError::DuplicateRoleNames(installed_app_id, duplicates));
909        }
910
911        let manifest = AppManifest::from_legacy(installed_cells.clone().into_iter());
912
913        let role_assignments = installed_cells
914            .into_iter()
915            .map(|InstalledCell { role_name, cell_id }| {
916                let role = AppRolePrimary {
917                    base_dna_hash: cell_id.dna_hash().clone(),
918                    is_provisioned: true,
919                    clones: HashMap::new(),
920                    clone_limit: 256,
921                    next_clone_index: 0,
922                    disabled_clones: HashMap::new(),
923                };
924                (role_name, role.into())
925            })
926            .collect();
927
928        Ok(Self {
929            installed_app_id,
930            agent_key,
931            role_assignments,
932            manifest,
933            installed_at: Timestamp::now(),
934        })
935    }
936
937    // pub fn dependencies(&self) -> Vec<
938
939    /// Return the manifest if available
940    pub fn manifest(&self) -> &AppManifest {
941        &self.manifest
942    }
943
944    /// Return the list of role assignments
945    pub fn role_assignments(&self) -> &IndexMap<RoleName, AppRoleAssignment> {
946        &self.role_assignments
947    }
948
949    /// Accessor
950    pub fn installed_at(&self) -> &Timestamp {
951        &self.installed_at
952    }
953}
954
955/// The status of an installed app.
956///
957/// App Status is a combination of two pieces of independent state:
958/// - Enabled/Disabled, which is a designation set by the user via the conductor admin interface.
959/// - Running/Stopped, which is a fact about the reality of the app in the course of its operation.
960///
961/// The combinations of these basic states give rise to the unified App Status.
962#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, SerializedBytes)]
963#[serde(tag = "type", content = "value", rename_all = "snake_case")]
964pub enum AppStatus {
965    /// The app is enabled and running normally.
966    Running,
967
968    /// Enabled, but stopped due to some recoverable problem.
969    /// The app "hopes" to be Running again as soon as possible.
970    /// Holochain may restart the app automatically if it can. It may also be
971    /// restarted manually via the `StartApp` admin method.
972    /// Paused apps will be automatically set to Running when the conductor restarts.
973    Paused(PausedAppReason),
974
975    /// Disabled and stopped, either manually by the user, or automatically due
976    /// to an unrecoverable error. App must be Enabled before running again,
977    /// and will not restart automaticaly on conductor reboot.
978    Disabled(DisabledAppReason),
979
980    /// The app is installed, but genesis has not completed because Membrane Proofs
981    /// have not been provided.
982    AwaitingMemproofs,
983}
984
985/// The AppStatus without the reasons.
986#[derive(Clone, Debug, PartialEq, Eq)]
987#[allow(missing_docs)]
988pub enum AppStatusKind {
989    Running,
990    Paused,
991    Disabled,
992    AwaitingMemproofs,
993}
994
995impl From<AppStatus> for AppStatusKind {
996    fn from(status: AppStatus) -> Self {
997        match status {
998            AppStatus::Running => Self::Running,
999            AppStatus::Paused(_) => Self::Paused,
1000            AppStatus::Disabled(_) => Self::Disabled,
1001            AppStatus::AwaitingMemproofs => Self::AwaitingMemproofs,
1002        }
1003    }
1004}
1005
1006/// Represents a state transition operation from one state to another
1007#[derive(Clone, Debug, PartialEq, Eq)]
1008pub enum AppStatusTransition {
1009    /// Attempt to unpause a Paused app
1010    Start,
1011    /// Attempt to pause a Running app
1012    Pause(PausedAppReason),
1013    /// Gets an app running no matter what
1014    Enable,
1015    /// Disables an app, no matter what
1016    Disable(DisabledAppReason),
1017}
1018
1019impl AppStatus {
1020    /// Does this status correspond to an Enabled state?
1021    /// If false, this indicates a Disabled state.
1022    pub fn is_enabled(&self) -> bool {
1023        matches!(self, Self::Running | Self::Paused(_))
1024    }
1025
1026    /// Does this status correspond to a Running state?
1027    /// If false, this indicates a Stopped state.
1028    pub fn is_running(&self) -> bool {
1029        matches!(self, Self::Running)
1030    }
1031
1032    /// Does this status correspond to a Paused state?
1033    pub fn is_paused(&self) -> bool {
1034        matches!(self, Self::Paused(_))
1035    }
1036
1037    /// Transition a status from one state to another.
1038    /// If None, the transition was not valid, and the status did not change.
1039    pub fn transition(&mut self, transition: AppStatusTransition) -> AppStatusFx {
1040        use AppStatus::*;
1041        use AppStatusFx::*;
1042        use AppStatusTransition::*;
1043        match (&self, transition) {
1044            (Running, Pause(reason)) => Some((Paused(reason), SpinDown)),
1045            (Running, Disable(reason)) => Some((Disabled(reason), SpinDown)),
1046            (Running, Start) | (Running, Enable) => None,
1047
1048            (Paused(_), Start) => Some((Running, SpinUp)),
1049            (Paused(_), Enable) => Some((Running, SpinUp)),
1050            (Paused(_), Disable(reason)) => Some((Disabled(reason), SpinDown)),
1051            (Paused(_), Pause(_)) => None,
1052
1053            (Disabled(_), Enable) => Some((Running, SpinUp)),
1054            (Disabled(_), Pause(_)) | (Disabled(_), Disable(_)) | (Disabled(_), Start) => None,
1055
1056            (AwaitingMemproofs, Enable | Start) => Some((
1057                AwaitingMemproofs,
1058                Error("Cannot enable an app which is AwaitingMemproofs".to_string()),
1059            )),
1060            (AwaitingMemproofs, _) => None,
1061        }
1062        .map(|(new_status, delta)| {
1063            *self = new_status;
1064            delta
1065        })
1066        .unwrap_or(NoChange)
1067    }
1068}
1069
1070/// A declaration of the side effects of a particular AppStatusTransition.
1071///
1072/// Two values of this type may also be combined into one,
1073/// to capture the overall effect of a series of transitions.
1074///
1075/// The intent of this type is to make sure that any operation which causes an
1076/// app state transition is followed up with a call to process_app_status_fx
1077/// in order to reconcile the cell state with the new app state.
1078#[derive(Clone, Debug, PartialEq, Eq)]
1079#[must_use = "be sure to run this value through `process_app_status_fx` to handle any transition effects"]
1080pub enum AppStatusFx {
1081    /// The transition did not result in any change to CellState.
1082    NoChange,
1083    /// The transition may cause some Cells to be removed.
1084    SpinDown,
1085    /// The transition may cause some Cells to be added (fallibly).
1086    SpinUp,
1087    /// The transition may cause some Cells to be removed and some to be (fallibly) added.
1088    Both,
1089    /// The transition was invalid and should produce an error.
1090    Error(String),
1091}
1092
1093impl Default for AppStatusFx {
1094    fn default() -> Self {
1095        Self::NoChange
1096    }
1097}
1098
1099impl AppStatusFx {
1100    /// Combine two effects into one. Think "monoidal append", if that helps.
1101    pub fn combine(self, other: Self) -> Self {
1102        use AppStatusFx::*;
1103        match (self, other) {
1104            (NoChange, a) | (a, NoChange) => a,
1105            (SpinDown, SpinDown) => SpinDown,
1106            (SpinUp, SpinUp) => SpinUp,
1107            (Both, _) | (_, Both) => Both,
1108            (SpinDown, SpinUp) | (SpinUp, SpinDown) => Both,
1109            (Error(err1), Error(err2)) => Error(format!("{err1}. {err2}")),
1110            (Error(err), _) | (_, Error(err)) => Error(err),
1111        }
1112    }
1113}
1114
1115/// The various reasons for why an App is not in the Running state.
1116#[derive(
1117    Clone,
1118    Debug,
1119    PartialEq,
1120    Eq,
1121    serde::Serialize,
1122    serde::Deserialize,
1123    SerializedBytes,
1124    derive_more::From,
1125)]
1126#[serde(rename_all = "snake_case")]
1127pub enum StoppedAppReason {
1128    /// Same meaning as [`AppStatus::Paused`].
1129    Paused(PausedAppReason),
1130
1131    /// Same meaning as [`AppStatus::Disabled`].
1132    Disabled(DisabledAppReason),
1133
1134    /// Same meaning as [`AppStatus::AwaitingMemproofs`].
1135    AwaitingMemproofs,
1136}
1137
1138impl StoppedAppReason {
1139    /// Convert a status into a StoppedAppReason.
1140    /// If the status is Running, returns None.
1141    pub fn from_status(status: &AppStatus) -> Option<Self> {
1142        match status {
1143            AppStatus::Paused(reason) => Some(Self::Paused(reason.clone())),
1144            AppStatus::Disabled(reason) => Some(Self::Disabled(reason.clone())),
1145            AppStatus::AwaitingMemproofs => Some(Self::AwaitingMemproofs),
1146            AppStatus::Running => None,
1147        }
1148    }
1149}
1150
1151impl From<StoppedAppReason> for AppStatus {
1152    fn from(reason: StoppedAppReason) -> Self {
1153        match reason {
1154            StoppedAppReason::Paused(reason) => Self::Paused(reason),
1155            StoppedAppReason::Disabled(reason) => Self::Disabled(reason),
1156            StoppedAppReason::AwaitingMemproofs => Self::AwaitingMemproofs,
1157        }
1158    }
1159}
1160
1161/// The reason for an app being in a Paused state.
1162/// NB: there is no way to manually pause an app.
1163#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, SerializedBytes)]
1164#[serde(tag = "type", content = "value", rename_all = "snake_case")]
1165pub enum PausedAppReason {
1166    /// The pause was due to a RECOVERABLE error
1167    Error(String),
1168}
1169
1170/// The reason for an app being in a Disabled state.
1171#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, SerializedBytes)]
1172#[serde(tag = "type", content = "value", rename_all = "snake_case")]
1173pub enum DisabledAppReason {
1174    /// The app is freshly installed, and never started
1175    NeverStarted,
1176    /// The app is fully installed and deferred memproofs have been provided by the UI,
1177    /// but the app has not been enabled.
1178    /// The app can be enabled via the app interface in this state, which is why this is
1179    /// separate from other disabled states.
1180    NotStartedAfterProvidingMemproofs,
1181    /// The disabling was done manually by the user (via admin interface)
1182    User,
1183    /// Disabling app in order to revoke its agent key and render all chains read-only.
1184    DeletingAgentKey,
1185    /// The disabling was due to an UNRECOVERABLE error
1186    Error(String),
1187}
1188
1189/// App "roles" correspond to cell entries in the AppManifest.
1190#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, derive_more::From)]
1191pub enum AppRoleAssignment {
1192    /// A "primary" role assignment indicates that this app "owns" this cell.
1193    /// The cell was created during app installation, and corresponds to the
1194    /// Create and CloneOnly CellProvisioning strategies.
1195    Primary(AppRolePrimary),
1196    /// A "dependency" role assignment indicates that the cell is owned by some other app,
1197    /// and this cell depends upon it.
1198    Dependency(AppRoleDependency),
1199}
1200
1201impl AppRoleAssignment {
1202    /// Use the Primary variant
1203    pub fn as_primary(&self) -> Option<&AppRolePrimary> {
1204        match self {
1205            Self::Primary(p) => Some(p),
1206            Self::Dependency(_) => None,
1207        }
1208    }
1209
1210    /// Use the Primary variant
1211    pub fn as_primary_mut(&mut self) -> Option<&mut AppRolePrimary> {
1212        match self {
1213            Self::Primary(p) => Some(p),
1214            Self::Dependency(_) => None,
1215        }
1216    }
1217
1218    /// Accessor
1219    pub fn provisioned_dna_hash(&self) -> Option<&DnaHash> {
1220        match self {
1221            Self::Primary(p) => p.provisioned_dna_hash(),
1222            Self::Dependency(_) => None,
1223        }
1224    }
1225
1226    // /// Accessor
1227    // pub fn cell_id(&self) -> &CellId {
1228    //     match self {
1229    //         Self::Primary(p) => p.dna_hash(),
1230    //         Self::Dependency(d) => &d.cell_id,
1231    //     }
1232    // }
1233}
1234
1235/// An app role whose cell(s) were created by the installation of this app.
1236#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1237pub struct AppRolePrimary {
1238    /// The Id of the Cell which will be provisioned for this role.
1239    /// This also identifies the basis for cloned DNAs, and this is how the
1240    /// Agent is determined for clones (always the same as the provisioned cell).
1241    pub base_dna_hash: DnaHash,
1242    /// Records whether the base cell has actually been provisioned or not.
1243    /// If true, then `base_dna_hash` refers to an actual existing Cell with
1244    /// that DNA hash.
1245    /// If false, then `base_dna_hash` is referring to a future cell which will
1246    /// be created with that DNA hash.
1247    pub is_provisioned: bool,
1248    /// The number of allowed clone cells.
1249    pub clone_limit: u32,
1250
1251    /// The index of the next clone cell to be created.
1252    pub next_clone_index: u32,
1253
1254    /// Cells which were cloned at runtime. The length cannot grow beyond
1255    /// `clone_limit`.
1256    pub clones: HashMap<CloneId, DnaHash>,
1257    /// Clone cells that have been disabled. These cells cannot be called
1258    /// any longer and are not returned as part of the app info either.
1259    /// Disabled clone cells can be deleted through the Admin API.
1260    pub disabled_clones: HashMap<CloneId, DnaHash>,
1261}
1262
1263impl AppRolePrimary {
1264    /// Constructor. List of clones always starts empty.
1265    pub fn new(base_dna_hash: DnaHash, is_provisioned: bool, clone_limit: u32) -> Self {
1266        Self {
1267            base_dna_hash,
1268            is_provisioned,
1269            clone_limit,
1270            clones: HashMap::new(),
1271            next_clone_index: 0,
1272            disabled_clones: HashMap::new(),
1273        }
1274    }
1275
1276    /// Accessor
1277    pub fn dna_hash(&self) -> &DnaHash {
1278        &self.base_dna_hash
1279    }
1280
1281    /// Accessor
1282    pub fn provisioned_dna_hash(&self) -> Option<&DnaHash> {
1283        if self.is_provisioned {
1284            Some(&self.base_dna_hash)
1285        } else {
1286            None
1287        }
1288    }
1289
1290    /// Accessor
1291    pub fn clone_ids(&self) -> impl Iterator<Item = &CloneId> {
1292        self.clones.keys()
1293    }
1294
1295    /// Accessor
1296    pub fn clone_limit(&self) -> u32 {
1297        self.clone_limit
1298    }
1299
1300    /// Accessor
1301    pub fn is_clone_limit_reached(&self) -> bool {
1302        self.clones.len() as u32 == self.clone_limit
1303    }
1304}
1305
1306/// An app role which is filled by a cell created by another app's primary role.
1307#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1308pub struct AppRoleDependency {
1309    /// The cell which is depended upon.
1310    pub cell_id: CellId,
1311    /// Whether this dependency is protected: if true, the dependent cell's app
1312    /// cannot be uninstalled without first uninstalling this app (except by
1313    /// using the `force` flag of UninstallApp).
1314    pub protected: bool,
1315}
1316
1317#[cfg(test)]
1318mod tests {
1319    use super::RunningApp;
1320    use crate::prelude::*;
1321    use ::fixt::prelude::*;
1322    use holo_hash::fixt::*;
1323    use serde_json;
1324    use std::collections::HashSet;
1325
1326    #[test]
1327    fn illegal_role_name_is_rejected() {
1328        let result = InstalledAppCommon::new(
1329            "test_app",
1330            fixt!(AgentPubKey),
1331            vec![(
1332                CLONE_ID_DELIMITER.into(),
1333                AppRolePrimary::new(fixt!(DnaHash), false, 0).into(),
1334            )],
1335            AppManifest::V1(AppManifestV1 {
1336                name: "test_app".to_string(),
1337                description: None,
1338                roles: vec![],
1339                allow_deferred_memproofs: false,
1340            }),
1341            Timestamp::now(),
1342        );
1343        assert!(result.is_err())
1344    }
1345
1346    #[test]
1347    fn clone_management() {
1348        let base_dna_hash = fixt!(DnaHash);
1349        let new_clone = || fixt!(DnaHash);
1350        let clone_limit = 3;
1351        let role1 = AppRolePrimary::new(base_dna_hash, false, clone_limit).into();
1352        let agent = fixt!(AgentPubKey);
1353        let role_name: RoleName = "role_name".into();
1354        let manifest = AppManifest::V1(AppManifestV1 {
1355            name: "test_app".to_string(),
1356            description: None,
1357            roles: vec![],
1358            allow_deferred_memproofs: false,
1359        });
1360        let mut app: RunningApp = InstalledAppCommon::new(
1361            "app",
1362            agent.clone(),
1363            vec![(role_name.clone(), role1)],
1364            manifest,
1365            Timestamp::now(),
1366        )
1367        .unwrap()
1368        .into();
1369
1370        // Can add clones up to the limit
1371        let clones: Vec<_> = vec![new_clone(), new_clone(), new_clone()];
1372        let clone_id_0 = app.add_clone(&role_name, &clones[0]).unwrap();
1373        let clone_id_1 = app.add_clone(&role_name, &clones[1]).unwrap();
1374        let clone_id_2 = app.add_clone(&role_name, &clones[2]).unwrap();
1375
1376        assert_eq!(clone_id_0, CloneId::new(&role_name, 0));
1377        assert_eq!(clone_id_1, CloneId::new(&role_name, 1));
1378        assert_eq!(clone_id_2, CloneId::new(&role_name, 2));
1379
1380        assert_eq!(
1381            app.clone_cell_ids()
1382                .map(|id| id.dna_hash().clone())
1383                .collect::<HashSet<_>>(),
1384            clones.clone().into_iter().collect::<HashSet<_>>()
1385        );
1386        assert_eq!(app.clone_cells().count(), 3);
1387
1388        // Adding the same clone twice should return an error
1389        let result_add_clone_twice = app.add_clone(&role_name, &clones[0]);
1390        assert!(result_add_clone_twice.is_err());
1391
1392        // Adding a clone beyond the clone_limit is an error
1393        matches::assert_matches!(
1394            app.add_clone(&role_name, &new_clone()),
1395            Err(AppError::CloneLimitExceeded(3, _))
1396        );
1397
1398        // Disable a clone cell
1399        app.disable_clone_cell(&clone_id_0).unwrap();
1400        // Assert it is moved to disabled clone cells
1401        assert!(!app
1402            .clone_cells()
1403            .any(|(clone_id, _)| *clone_id == clone_id_0));
1404        assert_eq!(app.clone_cells().count(), 2);
1405        assert!(app
1406            .disabled_clone_cells()
1407            .any(|(clone_id, _)| *clone_id == clone_id_0));
1408
1409        // Enable a disabled clone cell
1410        let enabled_cell = app.enable_clone_cell(&clone_id_0).unwrap();
1411        assert_eq!(
1412            enabled_cell.role_name,
1413            clone_id_0.as_app_role_name().to_owned()
1414        );
1415
1416        // Enabling an already enabled cell does nothing.
1417        let enabled_cell_2 = app.enable_clone_cell(&clone_id_0).unwrap();
1418        assert_eq!(enabled_cell_2, enabled_cell);
1419
1420        // Assert it is accessible from the app again
1421        assert!(app
1422            .clone_cells()
1423            .any(|(clone_id, _)| *clone_id == clone_id_0));
1424        assert_eq!(
1425            app.clone_cell_ids()
1426                .map(|id| id.dna_hash().clone())
1427                .collect::<HashSet<_>>(),
1428            clones.clone().into_iter().collect::<HashSet<_>>()
1429        );
1430        assert_eq!(app.clone_cells().count(), 3);
1431
1432        // Disable and delete a clone cell
1433        app.disable_clone_cell(&clone_id_0).unwrap();
1434        // Disabling is also idempotent
1435        app.disable_clone_cell(&clone_id_0).unwrap();
1436
1437        app.delete_clone_cell(&clone_id_0).unwrap();
1438        // Assert the deleted cell cannot be enabled
1439        assert!(app.enable_clone_cell(&clone_id_0).is_err());
1440    }
1441
1442    #[test]
1443    fn dna_source_serialization() {
1444        use serde_json;
1445
1446        let dna_source: DnaSource = DnaSource::Path("is the goal".into());
1447
1448        assert_eq!(
1449            serde_json::to_string(&dna_source).unwrap(),
1450            "{\"type\":\"path\",\"value\":\"is the goal\"}"
1451        );
1452    }
1453
1454    #[test]
1455    fn coordinator_source_serialization() {
1456        let coordinator_source: CoordinatorSource = CoordinatorSource::Path("is the goal".into());
1457        assert_eq!(
1458            serde_json::to_string(&coordinator_source).unwrap(),
1459            "{\"type\":\"path\",\"value\":\"is the goal\"}"
1460        );
1461    }
1462
1463    #[test]
1464    fn role_settings_serialization() {
1465        let role_settings: RoleSettings = RoleSettings::Provisioned {
1466            membrane_proof: None,
1467            modifiers: None,
1468        };
1469        assert_eq!(
1470            serde_json::to_string(&role_settings).unwrap(),
1471            "{\"type\":\"provisioned\",\"value\":{\"membrane_proof\":null,\"modifiers\":null}}"
1472        );
1473    }
1474
1475    #[test]
1476    fn app_bundle_source_serialization() {
1477        let app_bundle_source: AppBundleSource = AppBundleSource::Path("is the goal".into());
1478        assert_eq!(
1479            serde_json::to_string(&app_bundle_source).unwrap(),
1480            "{\"type\":\"path\",\"value\":\"is the goal\"}"
1481        );
1482    }
1483
1484    #[test]
1485    fn app_status_serialization() {
1486        let app_status: AppStatus = AppStatus::Running;
1487        assert_eq!(
1488            serde_json::to_string(&app_status).unwrap(),
1489            "{\"type\":\"running\"}"
1490        );
1491
1492        let app_status: AppStatus = AppStatus::Disabled(DisabledAppReason::NeverStarted);
1493        assert_eq!(
1494            serde_json::to_string(&app_status).unwrap(),
1495            "{\"type\":\"disabled\",\"value\":{\"type\":\"never_started\"}}"
1496        );
1497    }
1498
1499    #[test]
1500    fn paused_app_reason_serialization() {
1501        let reason = PausedAppReason::Error("s are here to learn from".into());
1502        assert_eq!(
1503            serde_json::to_string(&reason).unwrap(),
1504            "{\"type\":\"error\",\"value\":\"s are here to learn from\"}"
1505        );
1506    }
1507
1508    #[test]
1509    fn disabled_app_reason_serialization() {
1510        let reason = DisabledAppReason::User;
1511        assert_eq!(
1512            serde_json::to_string(&reason).unwrap(),
1513            "{\"type\":\"user\"}"
1514        );
1515    }
1516}