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}