Skip to main content

lenso_module_management/
contracts.rs

1use chrono::{DateTime, Utc};
2use lenso_contracts::{
3    ArtifactReference, ModuleDelivery, ModuleLifecycleState, ModuleVerificationCell,
4    VerificationEvaluation, digest_json,
5};
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8use std::collections::BTreeMap;
9
10pub const DESIRED_MODULE_COMPOSITION_PROTOCOL: &str = "lenso.desired-module-composition.v1";
11pub const APPLICATION_MODULE_LOCK_PROTOCOL: &str = "lenso.application-module-lock.v1";
12pub const MODULE_CHANGE_PLAN_PROTOCOL: &str = "lenso.module-change-plan.v1";
13pub const MODULE_REPAIR_PLAN_PROTOCOL: &str = "lenso.module-repair-plan.v1";
14pub const MODULE_ENVIRONMENT_POLICY_PROTOCOL: &str = "lenso.module-environment-policy.v1";
15pub const MODULE_APPROVAL_PROTOCOL: &str = "lenso.module-approval.v1";
16pub const MODULE_OPERATION_PROTOCOL: &str = "lenso.module-operation.v1";
17pub const MODULE_OPERATION_JOURNAL_PROTOCOL: &str = "lenso.module-operation-journal.v1";
18pub const LINKED_COMPOSITION_SEAM_PROTOCOL: &str = "lenso.linked-composition-seam.v1";
19pub const MODULE_PLANNING_CONTEXT_PROTOCOL: &str = "lenso.module-planning-context.v1";
20pub const MODULE_MANAGEMENT_SNAPSHOT_PROTOCOL: &str = "lenso.module-management-snapshot.v1";
21
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
23#[serde(deny_unknown_fields)]
24pub struct LinkedCompositionSeam {
25    pub protocol: String,
26    pub host_manifest_path: String,
27    pub host_source_path: String,
28    pub generated_crate_path: String,
29    pub dependency_name: String,
30    pub lenso_version: String,
31}
32
33#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
34#[serde(deny_unknown_fields)]
35pub struct ModulePlanningContext {
36    pub protocol: String,
37    pub system_id: String,
38    pub application_id: String,
39    pub environment_id: String,
40    pub expected_target_revision: u64,
41    pub catalog_snapshot_digest: String,
42    pub trust_policy_digest: String,
43    pub compatibility_evidence_digest: String,
44    pub resolver_version: String,
45    pub candidates: Vec<crate::ModuleResolutionCandidate>,
46    #[serde(default, skip_serializing_if = "Vec::is_empty")]
47    pub service_deployments: Vec<ServiceDeploymentBinding>,
48    #[serde(default)]
49    pub cargo_offline: bool,
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
53#[serde(rename_all = "snake_case")]
54pub enum ServiceDeploymentAdapterKind {
55    Local,
56    ExternallyManaged,
57    Kubernetes,
58}
59
60#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
61#[serde(tag = "kind", rename_all = "snake_case")]
62pub enum ServiceDeploymentAction {
63    Command {
64        program: String,
65        #[serde(default, skip_serializing_if = "Vec::is_empty")]
66        args: Vec<String>,
67        #[serde(default, skip_serializing_if = "Option::is_none")]
68        working_directory: Option<String>,
69    },
70    Evidence {
71        receipt: ArtifactReference,
72    },
73}
74
75#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
76#[serde(deny_unknown_fields)]
77pub struct ServiceDeploymentBinding {
78    pub service_id: String,
79    pub service_release_digest: String,
80    pub adapter: ServiceDeploymentAdapterKind,
81    #[serde(default, skip_serializing_if = "Option::is_none")]
82    pub installation: Option<crate::ServiceInstallation>,
83    #[serde(default, skip_serializing_if = "Option::is_none")]
84    pub install: Option<ServiceDeploymentAction>,
85    #[serde(default, skip_serializing_if = "Option::is_none")]
86    pub remove: Option<ServiceDeploymentAction>,
87    #[serde(default, skip_serializing_if = "Option::is_none")]
88    pub restart: Option<ServiceDeploymentAction>,
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
92#[serde(rename_all = "snake_case")]
93pub enum ModuleManagementSnapshotStatus {
94    Ready,
95    Unconfigured,
96    Invalid,
97}
98
99#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
100#[serde(deny_unknown_fields)]
101pub struct ModuleManagementSnapshot {
102    pub protocol: String,
103    pub status: ModuleManagementSnapshotStatus,
104    #[serde(default, skip_serializing_if = "Option::is_none")]
105    pub application_id: Option<String>,
106    #[serde(default, skip_serializing_if = "Option::is_none")]
107    pub desired: Option<DesiredModuleComposition>,
108    #[serde(default, skip_serializing_if = "Option::is_none")]
109    pub desired_digest: Option<String>,
110    #[serde(default, skip_serializing_if = "Option::is_none")]
111    pub application_lock: Option<ApplicationModuleLock>,
112    #[serde(default, skip_serializing_if = "Option::is_none")]
113    pub application_lock_digest: Option<String>,
114    pub planning_available: bool,
115    #[serde(default, skip_serializing_if = "Option::is_none")]
116    pub planning_context_digest: Option<String>,
117    pub execution_available: bool,
118    #[serde(default, skip_serializing_if = "Option::is_none")]
119    pub environment_policy: Option<ModuleEnvironmentPolicy>,
120    #[serde(default, skip_serializing_if = "Vec::is_empty")]
121    pub issues: Vec<String>,
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
125#[serde(deny_unknown_fields)]
126pub struct DesiredModuleComposition {
127    pub protocol: String,
128    pub application_id: String,
129    pub revision: u64,
130    pub selected: Vec<DesiredModuleSelection>,
131    #[serde(default, skip_serializing_if = "Vec::is_empty")]
132    pub local_overrides: Vec<LocalModuleOverride>,
133}
134
135#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
136#[serde(deny_unknown_fields)]
137pub struct DesiredModuleSelection {
138    pub module_id: String,
139    pub version_requirement: String,
140    #[serde(default, skip_serializing_if = "Vec::is_empty")]
141    pub optional_requirements: Vec<String>,
142    #[serde(default, skip_serializing_if = "Option::is_none")]
143    pub exact_release_digest: Option<String>,
144    #[serde(default, skip_serializing_if = "Option::is_none")]
145    pub delivery_preference: Option<ManagedDeliveryKind>,
146}
147
148#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
149#[serde(deny_unknown_fields)]
150pub struct LocalModuleOverride {
151    pub module_id: String,
152    pub path: String,
153    pub content_digest: String,
154    pub acknowledged_unverified: bool,
155}
156
157#[derive(
158    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
159)]
160#[serde(rename_all = "snake_case")]
161pub enum ManagedDeliveryKind {
162    Linked,
163    Provider,
164    Autonomous,
165}
166
167impl From<&ModuleDelivery> for ManagedDeliveryKind {
168    fn from(value: &ModuleDelivery) -> Self {
169        match value {
170            ModuleDelivery::Linked(_) => Self::Linked,
171            ModuleDelivery::Service(service) => match service.responsibility_profile {
172                lenso_contracts::ServiceResponsibilityProfile::Provider => Self::Provider,
173                lenso_contracts::ServiceResponsibilityProfile::Autonomous => Self::Autonomous,
174            },
175        }
176    }
177}
178
179#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
180#[serde(deny_unknown_fields)]
181pub struct ApplicationModuleLock {
182    pub protocol: String,
183    pub application_id: String,
184    pub desired_composition_digest: String,
185    pub catalog_snapshot_digest: String,
186    pub trust_policy_digest: String,
187    pub resolver_version: String,
188    pub modules: Vec<LockedModule>,
189    #[serde(default, skip_serializing_if = "Vec::is_empty")]
190    pub capability_bindings: Vec<LockedCapabilityBinding>,
191}
192
193#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
194#[serde(deny_unknown_fields)]
195pub struct LockedModule {
196    pub module_id: String,
197    pub version: String,
198    pub release_digest: String,
199    pub manifest_digest: String,
200    pub delivery: ModuleDelivery,
201    pub reason: LockedModuleReason,
202    #[serde(default, skip_serializing_if = "Vec::is_empty")]
203    pub dependency_module_ids: Vec<String>,
204    #[serde(default, skip_serializing_if = "Vec::is_empty")]
205    pub crate_features: Vec<String>,
206    #[serde(default, skip_serializing_if = "Vec::is_empty")]
207    pub migration_artifacts: Vec<ArtifactReference>,
208    #[serde(default, skip_serializing_if = "Option::is_none")]
209    pub console_ui_artifact: Option<LockedConsoleUiArtifact>,
210    pub verification: VerificationEvaluation,
211    pub verification_cell: ModuleVerificationCell,
212    pub lifecycle: ModuleLifecycleState,
213    #[serde(default, skip_serializing_if = "Option::is_none")]
214    pub local_override_digest: Option<String>,
215}
216
217#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
218#[serde(rename_all = "snake_case")]
219pub enum LockedModuleReason {
220    Direct,
221    Transitive,
222}
223
224#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
225#[serde(deny_unknown_fields)]
226pub struct LockedCapabilityBinding {
227    pub capability: String,
228    pub provider_module_id: String,
229    pub consumer_module_id: String,
230}
231
232#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
233#[serde(deny_unknown_fields)]
234pub struct LockedConsoleUiArtifact {
235    pub locator: String,
236    pub digest: String,
237    pub format: lenso_contracts::ConsoleUiArtifactFormat,
238    pub entries: Vec<lenso_contracts::ConsoleUiArtifactEntry>,
239    pub bridge_protocol: String,
240    pub requested_permissions: Vec<lenso_contracts::ConsolePermissionRequest>,
241}
242
243#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
244#[serde(deny_unknown_fields)]
245pub struct ConsoleCompositionArtifact {
246    pub module_id: String,
247    pub module_release_digest: String,
248    pub locator: String,
249    pub digest: String,
250    pub format: lenso_contracts::ConsoleUiArtifactFormat,
251    pub entries: Vec<lenso_contracts::ConsoleUiArtifactEntry>,
252    pub bridge_protocol: String,
253    pub requested_permissions: Vec<lenso_contracts::ConsolePermissionRequest>,
254}
255
256#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
257#[serde(tag = "kind", rename_all = "snake_case")]
258pub enum ModuleRootChange {
259    Install {
260        selection: DesiredModuleSelection,
261    },
262    Update {
263        module_id: String,
264        version_requirement: String,
265    },
266    Uninstall {
267        module_id: String,
268    },
269    SelectOptional {
270        module_id: String,
271        requirement: String,
272        selected: bool,
273    },
274    SwitchDelivery {
275        module_id: String,
276        delivery: ManagedDeliveryKind,
277    },
278    Restore {
279        target_lock_digest: String,
280    },
281    Repair {
282        repair_plan_digest: String,
283    },
284}
285
286#[derive(
287    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
288)]
289#[serde(rename_all = "snake_case")]
290pub enum EnvironmentManagementMode {
291    Disabled,
292    ReadOnly,
293    Full,
294}
295
296#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
297#[serde(deny_unknown_fields)]
298pub struct ModuleEnvironmentPolicy {
299    pub protocol: String,
300    pub policy_id: String,
301    pub revision: String,
302    pub mode: EnvironmentManagementMode,
303    pub require_distinct_approver: bool,
304    pub maximum_approval_age_seconds: u64,
305    pub maximum_lease_seconds: u64,
306    pub require_backup_for_non_local_destructive_effects: bool,
307}
308
309#[derive(
310    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
311)]
312#[serde(rename_all = "snake_case")]
313pub enum ModuleRiskClass {
314    Ordinary,
315    DestructiveMigration,
316    DataDeletion,
317    BackupRestore,
318    BackupWaiver,
319    TrustOverride,
320}
321
322#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
323#[serde(deny_unknown_fields)]
324pub struct ModuleApprovalBoundary {
325    pub boundary_id: String,
326    pub risk_class: ModuleRiskClass,
327    pub required_authority: String,
328    pub effect_ids: Vec<String>,
329    #[serde(default, skip_serializing_if = "Option::is_none")]
330    pub backup_evidence_digest: Option<String>,
331}
332
333#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
334#[serde(deny_unknown_fields)]
335pub struct ModuleApproval {
336    pub protocol: String,
337    pub approval_id: String,
338    pub plan_digest: String,
339    pub application_id: String,
340    pub environment_id: String,
341    pub expected_target_revision: u64,
342    pub boundary_id: String,
343    pub risk_class: ModuleRiskClass,
344    pub actor_id: String,
345    pub verified_authorities: Vec<String>,
346    pub reason: String,
347    pub issued_at: DateTime<Utc>,
348    pub expires_at: DateTime<Utc>,
349    pub nonce: String,
350}
351
352#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
353#[serde(deny_unknown_fields)]
354pub struct ModuleChangePlan {
355    pub protocol: String,
356    pub plan_id: String,
357    pub plan_digest: String,
358    pub application_id: String,
359    pub environment_id: String,
360    pub expected_target_revision: u64,
361    pub request: ModuleRootChange,
362    pub current_desired_digest: String,
363    pub target_desired: DesiredModuleComposition,
364    pub target_desired_digest: String,
365    #[serde(default, skip_serializing_if = "Option::is_none")]
366    pub current_lock_digest: Option<String>,
367    pub target_lock: ApplicationModuleLock,
368    pub target_lock_digest: String,
369    pub catalog_snapshot_digest: String,
370    pub resolver_version: String,
371    pub trust_policy_digest: String,
372    pub compatibility_evidence_digest: String,
373    #[serde(default, skip_serializing_if = "Option::is_none")]
374    pub cargo_lock_candidate: Option<crate::CargoLockCandidate>,
375    pub read_set: Vec<ModulePathPrecondition>,
376    pub effects: Vec<ModulePlanEffect>,
377    #[serde(default, skip_serializing_if = "Vec::is_empty")]
378    pub approval_boundaries: Vec<ModuleApprovalBoundary>,
379    pub validation_commands: Vec<String>,
380    pub next_actions: Vec<String>,
381    pub created_at: DateTime<Utc>,
382}
383
384#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
385#[serde(deny_unknown_fields)]
386pub struct ModulePathPrecondition {
387    pub path: String,
388    pub existence: PathExistence,
389    #[serde(default, skip_serializing_if = "Option::is_none")]
390    pub content_digest: Option<String>,
391    pub file_type: ManagedFileType,
392    #[serde(default, skip_serializing_if = "Option::is_none")]
393    pub mode: Option<u32>,
394}
395
396#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
397#[serde(rename_all = "snake_case")]
398pub enum PathExistence {
399    Present,
400    Absent,
401}
402
403#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
404#[serde(rename_all = "snake_case")]
405pub enum ManagedFileType {
406    Regular,
407    Directory,
408    Absent,
409}
410
411#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
412#[serde(rename_all = "snake_case")]
413pub enum ModuleFileOwnership {
414    User,
415    Generated,
416}
417
418#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
419#[serde(tag = "kind", rename_all = "snake_case")]
420pub enum ModulePlanEffect {
421    WorkspaceFile {
422        effect_id: String,
423        path: String,
424        ownership: ModuleFileOwnership,
425        change: ModuleFileChange,
426        #[serde(default, skip_serializing_if = "Option::is_none")]
427        before_digest: Option<String>,
428        #[serde(default, skip_serializing_if = "Option::is_none")]
429        after_digest: Option<String>,
430        #[serde(default, skip_serializing_if = "Option::is_none")]
431        after_content: Option<String>,
432        #[serde(default, skip_serializing_if = "Option::is_none")]
433        after_mode: Option<u32>,
434        patch: String,
435        reversible_before_migration: bool,
436    },
437    Configuration {
438        effect_id: String,
439        config_revision_digest: String,
440        unresolved_required_fields: Vec<String>,
441    },
442    Migration {
443        effect_id: String,
444        module_id: String,
445        release_digest: String,
446        migration_id: String,
447        artifact_locator: String,
448        artifact_digest: String,
449        store_scope: String,
450        execution: MigrationExecutionMode,
451        risk_class: ModuleRiskClass,
452    },
453    Protected {
454        effect_id: String,
455        risk_class: ModuleRiskClass,
456        subject: String,
457        evidence_digest: String,
458    },
459    ConsoleComposition {
460        effect_id: String,
461        console_service_id: String,
462        candidate_lock_digest: String,
463        artifacts: Vec<ConsoleCompositionArtifact>,
464    },
465    ServiceInstallation {
466        effect_id: String,
467        service_id: String,
468        service_release_digest: String,
469        #[serde(default, skip_serializing_if = "Option::is_none")]
470        installation_plan: Option<crate::ServiceInstallationPlan>,
471        #[serde(default, skip_serializing_if = "Option::is_none")]
472        adapter: Option<ServiceDeploymentAdapterKind>,
473        #[serde(default, skip_serializing_if = "Option::is_none")]
474        action: Option<ServiceDeploymentAction>,
475    },
476    ServiceRemoval {
477        effect_id: String,
478        service_id: String,
479        service_release_digest: String,
480        #[serde(default, skip_serializing_if = "Option::is_none")]
481        adapter: Option<ServiceDeploymentAdapterKind>,
482        #[serde(default, skip_serializing_if = "Option::is_none")]
483        action: Option<ServiceDeploymentAction>,
484    },
485    ServiceRestart {
486        effect_id: String,
487        service_id: String,
488        service_release_digest: String,
489        #[serde(default, skip_serializing_if = "Option::is_none")]
490        adapter: Option<ServiceDeploymentAdapterKind>,
491        #[serde(default, skip_serializing_if = "Option::is_none")]
492        action: Option<ServiceDeploymentAction>,
493    },
494    Validate {
495        effect_id: String,
496        command: String,
497        expected_evidence: String,
498    },
499    Restart {
500        effect_id: String,
501        target: String,
502    },
503    Activate {
504        effect_id: String,
505        target_lock_digest: String,
506    },
507}
508
509impl ModulePlanEffect {
510    #[must_use]
511    pub fn effect_id(&self) -> &str {
512        match self {
513            Self::WorkspaceFile { effect_id, .. }
514            | Self::Configuration { effect_id, .. }
515            | Self::Migration { effect_id, .. }
516            | Self::Protected { effect_id, .. }
517            | Self::ConsoleComposition { effect_id, .. }
518            | Self::ServiceInstallation { effect_id, .. }
519            | Self::ServiceRemoval { effect_id, .. }
520            | Self::ServiceRestart { effect_id, .. }
521            | Self::Validate { effect_id, .. }
522            | Self::Restart { effect_id, .. }
523            | Self::Activate { effect_id, .. } => effect_id,
524        }
525    }
526
527    #[must_use]
528    pub fn risk_class(&self) -> ModuleRiskClass {
529        match self {
530            Self::Migration { risk_class, .. } | Self::Protected { risk_class, .. } => *risk_class,
531            _ => ModuleRiskClass::Ordinary,
532        }
533    }
534}
535
536#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
537#[serde(rename_all = "snake_case")]
538pub enum ModuleFileChange {
539    Create,
540    Modify,
541    Delete,
542}
543
544#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
545#[serde(rename_all = "snake_case")]
546pub enum MigrationExecutionMode {
547    Transactional,
548    IdempotentExternal,
549}
550
551#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
552#[serde(deny_unknown_fields)]
553pub struct ModuleRepairPlan {
554    pub protocol: String,
555    pub repair_plan_id: String,
556    pub repair_plan_digest: String,
557    pub original_operation_id: String,
558    pub original_operation_revision: u64,
559    pub application_id: String,
560    pub environment_id: String,
561    pub observed_state_digest: String,
562    pub completed_effect_ids: Vec<String>,
563    pub actions: Vec<ModuleRepairAction>,
564    pub approval_boundaries: Vec<ModuleApprovalBoundary>,
565    pub created_at: DateTime<Utc>,
566}
567
568#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
569#[serde(tag = "kind", rename_all = "snake_case")]
570pub enum ModuleRepairAction {
571    Resume {
572        effect_ids: Vec<String>,
573    },
574    ForwardFix {
575        module_release_digest: String,
576    },
577    RestoreWorkspace {
578        path_digests: BTreeMap<String, String>,
579    },
580    ReconcileComposition {
581        desired_digest: String,
582        lock_digest: String,
583    },
584    RestoreBackup {
585        backup_evidence_digest: String,
586        store_scope: String,
587    },
588    RecordIntervention {
589        evidence_digest: String,
590    },
591}
592
593#[derive(
594    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
595)]
596#[serde(rename_all = "snake_case")]
597pub enum ModuleOperationState {
598    Planned,
599    AwaitingApproval,
600    Ready,
601    ApplyingFiles,
602    FilesApplied,
603    StagingConfiguration,
604    Migrating,
605    Verifying,
606    Activating,
607    Succeeded,
608    Blocked,
609    Cancelled,
610    Restored,
611    RepairRequired,
612    Reconciled,
613}
614
615#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
616#[serde(deny_unknown_fields)]
617pub struct ModuleOperation {
618    pub protocol: String,
619    pub operation_id: String,
620    pub idempotency_key: String,
621    pub application_id: String,
622    pub environment_id: String,
623    pub plan_digest: String,
624    pub operation_kind: ModuleOperationKind,
625    pub expected_target_revision: u64,
626    pub current_lock_digest: Option<String>,
627    pub target_lock_digest: String,
628    pub actor_id: String,
629    pub verified_authorities: Vec<String>,
630    pub policy_revision: String,
631    pub state: ModuleOperationState,
632    pub revision: u64,
633    pub fencing_token: u64,
634    pub attempt: u32,
635    #[serde(default, skip_serializing_if = "Vec::is_empty")]
636    pub approvals: Vec<ModuleApproval>,
637    #[serde(default, skip_serializing_if = "Vec::is_empty")]
638    pub effect_receipts: Vec<ModuleEffectReceipt>,
639    #[serde(default, skip_serializing_if = "Vec::is_empty")]
640    pub workspace_backups: Vec<ModuleWorkspaceBackup>,
641    #[serde(default, skip_serializing_if = "Vec::is_empty")]
642    pub errors: Vec<ModuleOperationError>,
643    #[serde(default, skip_serializing_if = "Vec::is_empty")]
644    pub next_actions: Vec<String>,
645    #[serde(default, skip_serializing_if = "Option::is_none")]
646    pub repair_operation_id: Option<String>,
647    pub updated_at: DateTime<Utc>,
648}
649
650#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
651#[serde(deny_unknown_fields)]
652pub struct ModuleWorkspaceBackup {
653    pub path: String,
654    pub existence: PathExistence,
655    pub file_type: ManagedFileType,
656    #[serde(default, skip_serializing_if = "Option::is_none")]
657    pub content_base64: Option<String>,
658    #[serde(default, skip_serializing_if = "Option::is_none")]
659    pub content_digest: Option<String>,
660    #[serde(default, skip_serializing_if = "Option::is_none")]
661    pub mode: Option<u32>,
662}
663
664#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
665#[serde(rename_all = "snake_case")]
666pub enum ModuleOperationKind {
667    Install,
668    Update,
669    Uninstall,
670    DeliveryTransition,
671    Restore,
672    Repair,
673}
674
675#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
676#[serde(deny_unknown_fields)]
677pub struct ModuleEffectReceipt {
678    pub receipt_id: String,
679    pub effect_id: String,
680    pub effect_digest: String,
681    pub operation_id: String,
682    pub attempt: u32,
683    pub fencing_token: u64,
684    pub outcome: ModuleEffectOutcome,
685    pub evidence_references: Vec<ArtifactReference>,
686    pub committed_at: DateTime<Utc>,
687}
688
689#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
690#[serde(rename_all = "snake_case")]
691pub enum ModuleEffectOutcome {
692    Applied,
693    AlreadyApplied,
694    Verified,
695    Restored,
696    Activated,
697}
698
699#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
700#[serde(deny_unknown_fields)]
701pub struct ModuleOperationError {
702    pub code: String,
703    pub message: String,
704    pub evidence_references: Vec<ArtifactReference>,
705    pub recorded_at: DateTime<Utc>,
706}
707
708#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
709#[serde(deny_unknown_fields)]
710pub struct ModuleOperationLease {
711    pub application_id: String,
712    pub holder_id: String,
713    pub fencing_token: u64,
714    pub revision: u64,
715    pub acquired_at: DateTime<Utc>,
716    pub expires_at: DateTime<Utc>,
717}
718
719#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
720#[serde(deny_unknown_fields)]
721pub struct ModuleResumeEvidence {
722    pub plan_digest: String,
723    pub observed_target_digest: String,
724    pub completed_effect_ids: Vec<String>,
725    pub next_effect_id: String,
726    pub next_effect_idempotent: bool,
727    pub observed_at: DateTime<Utc>,
728}
729
730#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
731#[serde(deny_unknown_fields)]
732pub struct ModuleOperationJournalEvent {
733    pub protocol: String,
734    pub event_id: String,
735    pub operation_id: String,
736    pub attempt: u32,
737    pub revision: u64,
738    pub prior_event_digest: Option<String>,
739    pub plan_digest: String,
740    pub actor_id: String,
741    pub fencing_token: u64,
742    pub transition: ModuleOperationTransition,
743    pub outcome_code: String,
744    pub evidence_references: Vec<ArtifactReference>,
745    pub operation_after: ModuleOperation,
746    pub recorded_at: DateTime<Utc>,
747}
748
749#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
750#[serde(deny_unknown_fields)]
751pub struct ModuleOperationTransition {
752    pub from: ModuleOperationState,
753    pub to: ModuleOperationState,
754}
755
756#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
757#[serde(deny_unknown_fields)]
758pub struct ModuleOperationJournal {
759    pub protocol: String,
760    pub operation_id: String,
761    pub events: Vec<ModuleOperationJournalEvent>,
762}
763
764pub fn desired_composition_digest(
765    composition: &DesiredModuleComposition,
766) -> Result<String, serde_json::Error> {
767    digest_json(composition)
768}
769
770pub fn application_module_lock_digest(
771    module_lock: &ApplicationModuleLock,
772) -> Result<String, serde_json::Error> {
773    digest_json(module_lock)
774}
775
776pub fn module_change_plan_digest(plan: &ModuleChangePlan) -> Result<String, serde_json::Error> {
777    let mut unsigned = plan.clone();
778    unsigned.plan_digest.clear();
779    digest_json(&unsigned)
780}
781
782pub fn module_repair_plan_digest(plan: &ModuleRepairPlan) -> Result<String, serde_json::Error> {
783    let mut unsigned = plan.clone();
784    unsigned.repair_plan_digest.clear();
785    digest_json(&unsigned)
786}
787
788pub fn journal_event_digest(
789    event: &ModuleOperationJournalEvent,
790) -> Result<String, serde_json::Error> {
791    digest_json(event)
792}