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 protocol_major: u32,
239 pub entry: String,
240 pub entries: Vec<lenso_contracts::ConsoleUiArtifactEntry>,
241 pub style_assets: Vec<lenso_contracts::ConsoleUiArtifactStyleAsset>,
242 pub manifest: lenso_contracts::ConsoleModuleManifest,
243 pub requested_permissions: Vec<lenso_contracts::ConsolePermissionRequest>,
244}
245
246#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
247#[serde(deny_unknown_fields)]
248pub struct ConsoleCompositionArtifact {
249 pub module_id: String,
250 pub module_release_digest: String,
251 pub locator: String,
252 pub digest: String,
253 pub format: lenso_contracts::ConsoleUiArtifactFormat,
254 pub protocol_major: u32,
255 pub entry: String,
256 pub entries: Vec<lenso_contracts::ConsoleUiArtifactEntry>,
257 pub style_assets: Vec<lenso_contracts::ConsoleUiArtifactStyleAsset>,
258 pub manifest: lenso_contracts::ConsoleModuleManifest,
259 pub requested_permissions: Vec<lenso_contracts::ConsolePermissionRequest>,
260}
261
262#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
263#[serde(tag = "kind", rename_all = "snake_case")]
264pub enum ModuleRootChange {
265 Install {
266 selection: DesiredModuleSelection,
267 },
268 Update {
269 module_id: String,
270 version_requirement: String,
271 },
272 Uninstall {
273 module_id: String,
274 },
275 SelectOptional {
276 module_id: String,
277 requirement: String,
278 selected: bool,
279 },
280 SwitchDelivery {
281 module_id: String,
282 delivery: ManagedDeliveryKind,
283 },
284 Restore {
285 target_lock_digest: String,
286 },
287 Repair {
288 repair_plan_digest: String,
289 },
290}
291
292#[derive(
293 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
294)]
295#[serde(rename_all = "snake_case")]
296pub enum EnvironmentManagementMode {
297 Disabled,
298 ReadOnly,
299 Full,
300}
301
302#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
303#[serde(deny_unknown_fields)]
304pub struct ModuleEnvironmentPolicy {
305 pub protocol: String,
306 pub policy_id: String,
307 pub revision: String,
308 pub mode: EnvironmentManagementMode,
309 pub require_distinct_approver: bool,
310 pub maximum_approval_age_seconds: u64,
311 pub maximum_lease_seconds: u64,
312 pub require_backup_for_non_local_destructive_effects: bool,
313}
314
315#[derive(
316 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
317)]
318#[serde(rename_all = "snake_case")]
319pub enum ModuleRiskClass {
320 Ordinary,
321 DestructiveMigration,
322 DataDeletion,
323 BackupRestore,
324 BackupWaiver,
325 TrustOverride,
326}
327
328#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
329#[serde(deny_unknown_fields)]
330pub struct ModuleApprovalBoundary {
331 pub boundary_id: String,
332 pub risk_class: ModuleRiskClass,
333 pub required_authority: String,
334 pub effect_ids: Vec<String>,
335 #[serde(default, skip_serializing_if = "Option::is_none")]
336 pub backup_evidence_digest: Option<String>,
337}
338
339#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
340#[serde(deny_unknown_fields)]
341pub struct ModuleApproval {
342 pub protocol: String,
343 pub approval_id: String,
344 pub plan_digest: String,
345 pub application_id: String,
346 pub environment_id: String,
347 pub expected_target_revision: u64,
348 pub boundary_id: String,
349 pub risk_class: ModuleRiskClass,
350 pub actor_id: String,
351 pub verified_authorities: Vec<String>,
352 pub reason: String,
353 pub issued_at: DateTime<Utc>,
354 pub expires_at: DateTime<Utc>,
355 pub nonce: String,
356}
357
358#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
359#[serde(deny_unknown_fields)]
360pub struct ModuleChangePlan {
361 pub protocol: String,
362 pub plan_id: String,
363 pub plan_digest: String,
364 pub application_id: String,
365 pub environment_id: String,
366 pub expected_target_revision: u64,
367 pub request: ModuleRootChange,
368 pub current_desired_digest: String,
369 pub target_desired: DesiredModuleComposition,
370 pub target_desired_digest: String,
371 #[serde(default, skip_serializing_if = "Option::is_none")]
372 pub current_lock_digest: Option<String>,
373 pub target_lock: ApplicationModuleLock,
374 pub target_lock_digest: String,
375 pub catalog_snapshot_digest: String,
376 pub resolver_version: String,
377 pub trust_policy_digest: String,
378 pub compatibility_evidence_digest: String,
379 #[serde(default, skip_serializing_if = "Option::is_none")]
380 pub cargo_lock_candidate: Option<crate::CargoLockCandidate>,
381 pub read_set: Vec<ModulePathPrecondition>,
382 pub effects: Vec<ModulePlanEffect>,
383 #[serde(default, skip_serializing_if = "Vec::is_empty")]
384 pub approval_boundaries: Vec<ModuleApprovalBoundary>,
385 pub validation_commands: Vec<String>,
386 pub next_actions: Vec<String>,
387 pub created_at: DateTime<Utc>,
388}
389
390#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
391#[serde(deny_unknown_fields)]
392pub struct ModulePathPrecondition {
393 pub path: String,
394 pub existence: PathExistence,
395 #[serde(default, skip_serializing_if = "Option::is_none")]
396 pub content_digest: Option<String>,
397 pub file_type: ManagedFileType,
398 #[serde(default, skip_serializing_if = "Option::is_none")]
399 pub mode: Option<u32>,
400}
401
402#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
403#[serde(rename_all = "snake_case")]
404pub enum PathExistence {
405 Present,
406 Absent,
407}
408
409#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
410#[serde(rename_all = "snake_case")]
411pub enum ManagedFileType {
412 Regular,
413 Directory,
414 Absent,
415}
416
417#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
418#[serde(rename_all = "snake_case")]
419pub enum ModuleFileOwnership {
420 User,
421 Generated,
422}
423
424#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
425#[serde(tag = "kind", rename_all = "snake_case")]
426pub enum ModulePlanEffect {
427 WorkspaceFile {
428 effect_id: String,
429 path: String,
430 ownership: ModuleFileOwnership,
431 change: ModuleFileChange,
432 #[serde(default, skip_serializing_if = "Option::is_none")]
433 before_digest: Option<String>,
434 #[serde(default, skip_serializing_if = "Option::is_none")]
435 after_digest: Option<String>,
436 #[serde(default, skip_serializing_if = "Option::is_none")]
437 after_content: Option<String>,
438 #[serde(default, skip_serializing_if = "Option::is_none")]
439 after_mode: Option<u32>,
440 patch: String,
441 reversible_before_migration: bool,
442 },
443 Configuration {
444 effect_id: String,
445 config_revision_digest: String,
446 unresolved_required_fields: Vec<String>,
447 },
448 Migration {
449 effect_id: String,
450 module_id: String,
451 release_digest: String,
452 migration_id: String,
453 artifact_locator: String,
454 artifact_digest: String,
455 store_scope: String,
456 execution: MigrationExecutionMode,
457 risk_class: ModuleRiskClass,
458 },
459 Protected {
460 effect_id: String,
461 risk_class: ModuleRiskClass,
462 subject: String,
463 evidence_digest: String,
464 },
465 ConsoleComposition {
466 effect_id: String,
467 console_service_id: String,
468 candidate_lock_digest: String,
469 artifacts: Vec<ConsoleCompositionArtifact>,
470 },
471 ServiceInstallation {
472 effect_id: String,
473 service_id: String,
474 service_release_digest: String,
475 #[serde(default, skip_serializing_if = "Option::is_none")]
476 installation_plan: Option<crate::ServiceInstallationPlan>,
477 #[serde(default, skip_serializing_if = "Option::is_none")]
478 adapter: Option<ServiceDeploymentAdapterKind>,
479 #[serde(default, skip_serializing_if = "Option::is_none")]
480 action: Option<ServiceDeploymentAction>,
481 },
482 ServiceRemoval {
483 effect_id: String,
484 service_id: String,
485 service_release_digest: String,
486 #[serde(default, skip_serializing_if = "Option::is_none")]
487 adapter: Option<ServiceDeploymentAdapterKind>,
488 #[serde(default, skip_serializing_if = "Option::is_none")]
489 action: Option<ServiceDeploymentAction>,
490 },
491 ServiceRestart {
492 effect_id: String,
493 service_id: String,
494 service_release_digest: String,
495 #[serde(default, skip_serializing_if = "Option::is_none")]
496 adapter: Option<ServiceDeploymentAdapterKind>,
497 #[serde(default, skip_serializing_if = "Option::is_none")]
498 action: Option<ServiceDeploymentAction>,
499 },
500 Validate {
501 effect_id: String,
502 command: String,
503 expected_evidence: String,
504 },
505 Restart {
506 effect_id: String,
507 target: String,
508 },
509 Activate {
510 effect_id: String,
511 target_lock_digest: String,
512 },
513}
514
515impl ModulePlanEffect {
516 #[must_use]
517 pub fn effect_id(&self) -> &str {
518 match self {
519 Self::WorkspaceFile { effect_id, .. }
520 | Self::Configuration { effect_id, .. }
521 | Self::Migration { effect_id, .. }
522 | Self::Protected { effect_id, .. }
523 | Self::ConsoleComposition { effect_id, .. }
524 | Self::ServiceInstallation { effect_id, .. }
525 | Self::ServiceRemoval { effect_id, .. }
526 | Self::ServiceRestart { effect_id, .. }
527 | Self::Validate { effect_id, .. }
528 | Self::Restart { effect_id, .. }
529 | Self::Activate { effect_id, .. } => effect_id,
530 }
531 }
532
533 #[must_use]
534 pub fn risk_class(&self) -> ModuleRiskClass {
535 match self {
536 Self::Migration { risk_class, .. } | Self::Protected { risk_class, .. } => *risk_class,
537 _ => ModuleRiskClass::Ordinary,
538 }
539 }
540}
541
542#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
543#[serde(rename_all = "snake_case")]
544pub enum ModuleFileChange {
545 Create,
546 Modify,
547 Delete,
548}
549
550#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
551#[serde(rename_all = "snake_case")]
552pub enum MigrationExecutionMode {
553 Transactional,
554 IdempotentExternal,
555}
556
557#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
558#[serde(deny_unknown_fields)]
559pub struct ModuleRepairPlan {
560 pub protocol: String,
561 pub repair_plan_id: String,
562 pub repair_plan_digest: String,
563 pub original_operation_id: String,
564 pub original_operation_revision: u64,
565 pub application_id: String,
566 pub environment_id: String,
567 pub observed_state_digest: String,
568 pub completed_effect_ids: Vec<String>,
569 pub actions: Vec<ModuleRepairAction>,
570 pub approval_boundaries: Vec<ModuleApprovalBoundary>,
571 pub created_at: DateTime<Utc>,
572}
573
574#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
575#[serde(tag = "kind", rename_all = "snake_case")]
576pub enum ModuleRepairAction {
577 Resume {
578 effect_ids: Vec<String>,
579 },
580 ForwardFix {
581 module_release_digest: String,
582 },
583 RestoreWorkspace {
584 path_digests: BTreeMap<String, String>,
585 },
586 ReconcileComposition {
587 desired_digest: String,
588 lock_digest: String,
589 },
590 RestoreBackup {
591 backup_evidence_digest: String,
592 store_scope: String,
593 },
594 RecordIntervention {
595 evidence_digest: String,
596 },
597}
598
599#[derive(
600 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
601)]
602#[serde(rename_all = "snake_case")]
603pub enum ModuleOperationState {
604 Planned,
605 AwaitingApproval,
606 Ready,
607 ApplyingFiles,
608 FilesApplied,
609 StagingConfiguration,
610 Migrating,
611 Verifying,
612 Activating,
613 Succeeded,
614 Blocked,
615 Cancelled,
616 Restored,
617 RepairRequired,
618 Reconciled,
619}
620
621#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
622#[serde(deny_unknown_fields)]
623pub struct ModuleOperation {
624 pub protocol: String,
625 pub operation_id: String,
626 pub idempotency_key: String,
627 pub application_id: String,
628 pub environment_id: String,
629 pub plan_digest: String,
630 pub operation_kind: ModuleOperationKind,
631 pub expected_target_revision: u64,
632 pub current_lock_digest: Option<String>,
633 pub target_lock_digest: String,
634 pub actor_id: String,
635 pub verified_authorities: Vec<String>,
636 pub policy_revision: String,
637 pub state: ModuleOperationState,
638 pub revision: u64,
639 pub fencing_token: u64,
640 pub attempt: u32,
641 #[serde(default, skip_serializing_if = "Vec::is_empty")]
642 pub approvals: Vec<ModuleApproval>,
643 #[serde(default, skip_serializing_if = "Vec::is_empty")]
644 pub effect_receipts: Vec<ModuleEffectReceipt>,
645 #[serde(default, skip_serializing_if = "Vec::is_empty")]
646 pub workspace_backups: Vec<ModuleWorkspaceBackup>,
647 #[serde(default, skip_serializing_if = "Vec::is_empty")]
648 pub errors: Vec<ModuleOperationError>,
649 #[serde(default, skip_serializing_if = "Vec::is_empty")]
650 pub next_actions: Vec<String>,
651 #[serde(default, skip_serializing_if = "Option::is_none")]
652 pub repair_operation_id: Option<String>,
653 pub updated_at: DateTime<Utc>,
654}
655
656#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
657#[serde(deny_unknown_fields)]
658pub struct ModuleWorkspaceBackup {
659 pub path: String,
660 pub existence: PathExistence,
661 pub file_type: ManagedFileType,
662 #[serde(default, skip_serializing_if = "Option::is_none")]
663 pub content_base64: Option<String>,
664 #[serde(default, skip_serializing_if = "Option::is_none")]
665 pub content_digest: Option<String>,
666 #[serde(default, skip_serializing_if = "Option::is_none")]
667 pub mode: Option<u32>,
668}
669
670#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
671#[serde(rename_all = "snake_case")]
672pub enum ModuleOperationKind {
673 Install,
674 Update,
675 Uninstall,
676 DeliveryTransition,
677 Restore,
678 Repair,
679}
680
681#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
682#[serde(deny_unknown_fields)]
683pub struct ModuleEffectReceipt {
684 pub receipt_id: String,
685 pub effect_id: String,
686 pub effect_digest: String,
687 pub operation_id: String,
688 pub attempt: u32,
689 pub fencing_token: u64,
690 pub outcome: ModuleEffectOutcome,
691 pub evidence_references: Vec<ArtifactReference>,
692 pub committed_at: DateTime<Utc>,
693}
694
695#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
696#[serde(rename_all = "snake_case")]
697pub enum ModuleEffectOutcome {
698 Applied,
699 AlreadyApplied,
700 Verified,
701 Restored,
702 Activated,
703}
704
705#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
706#[serde(deny_unknown_fields)]
707pub struct ModuleOperationError {
708 pub code: String,
709 pub message: String,
710 pub evidence_references: Vec<ArtifactReference>,
711 pub recorded_at: DateTime<Utc>,
712}
713
714#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
715#[serde(deny_unknown_fields)]
716pub struct ModuleOperationLease {
717 pub application_id: String,
718 pub holder_id: String,
719 pub fencing_token: u64,
720 pub revision: u64,
721 pub acquired_at: DateTime<Utc>,
722 pub expires_at: DateTime<Utc>,
723}
724
725#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
726#[serde(deny_unknown_fields)]
727pub struct ModuleResumeEvidence {
728 pub plan_digest: String,
729 pub observed_target_digest: String,
730 pub completed_effect_ids: Vec<String>,
731 pub next_effect_id: String,
732 pub next_effect_idempotent: bool,
733 pub observed_at: DateTime<Utc>,
734}
735
736#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
737#[serde(deny_unknown_fields)]
738pub struct ModuleOperationJournalEvent {
739 pub protocol: String,
740 pub event_id: String,
741 pub operation_id: String,
742 pub attempt: u32,
743 pub revision: u64,
744 pub prior_event_digest: Option<String>,
745 pub plan_digest: String,
746 pub actor_id: String,
747 pub fencing_token: u64,
748 pub transition: ModuleOperationTransition,
749 pub outcome_code: String,
750 pub evidence_references: Vec<ArtifactReference>,
751 pub operation_after: ModuleOperation,
752 pub recorded_at: DateTime<Utc>,
753}
754
755#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
756#[serde(deny_unknown_fields)]
757pub struct ModuleOperationTransition {
758 pub from: ModuleOperationState,
759 pub to: ModuleOperationState,
760}
761
762#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
763#[serde(deny_unknown_fields)]
764pub struct ModuleOperationJournal {
765 pub protocol: String,
766 pub operation_id: String,
767 pub events: Vec<ModuleOperationJournalEvent>,
768}
769
770pub fn desired_composition_digest(
771 composition: &DesiredModuleComposition,
772) -> Result<String, serde_json::Error> {
773 digest_json(composition)
774}
775
776pub fn application_module_lock_digest(
777 module_lock: &ApplicationModuleLock,
778) -> Result<String, serde_json::Error> {
779 digest_json(module_lock)
780}
781
782pub fn module_change_plan_digest(plan: &ModuleChangePlan) -> Result<String, serde_json::Error> {
783 let mut unsigned = plan.clone();
784 unsigned.plan_digest.clear();
785 digest_json(&unsigned)
786}
787
788pub fn module_repair_plan_digest(plan: &ModuleRepairPlan) -> Result<String, serde_json::Error> {
789 let mut unsigned = plan.clone();
790 unsigned.repair_plan_digest.clear();
791 digest_json(&unsigned)
792}
793
794pub fn journal_event_digest(
795 event: &ModuleOperationJournalEvent,
796) -> Result<String, serde_json::Error> {
797 digest_json(event)
798}