aion_store/package.rs
1//! Deployed-package persistence contract.
2//!
3//! Packages deployed at runtime (the live `Engine::load_package` seam) are
4//! part of the engine's durable state: a run is pinned to the package version
5//! recorded in its `WorkflowStarted` event, so startup recovery can only
6//! resolve that pin if the deployed archive itself survives the restart.
7//! This module defines the store-side contract for that durability — archive
8//! rows keyed by `(workflow_type, content_hash)` plus the per-type route
9//! pointer that decides which version new starts resolve.
10//!
11//! The store treats both values as opaque engine truth: the archive bytes are
12//! the canonical `.aion` container (re-validated by the engine on reload) and
13//! the content hash is its 64-hex textual form. No store backend parses
14//! either.
15
16use async_trait::async_trait;
17use chrono::{DateTime, Utc};
18
19use crate::StoreError;
20
21/// One persisted deployed-package archive.
22#[derive(Clone, Debug, PartialEq, Eq)]
23pub struct PackageRecord {
24 /// Logical workflow type the package's manifest entry module names.
25 pub workflow_type: String,
26 /// Canonical 64-hex textual content hash identifying this version.
27 pub content_hash: String,
28 /// Complete `.aion` archive bytes as deployed.
29 pub archive: Vec<u8>,
30 /// When this version was (last) deployed.
31 pub deployed_at: DateTime<Utc>,
32}
33
34/// One persisted route pointer: the version new starts of a type resolve.
35#[derive(Clone, Debug, PartialEq, Eq)]
36pub struct PackageRouteRecord {
37 /// Workflow type the pointer routes.
38 pub workflow_type: String,
39 /// Canonical 64-hex textual content hash the route points at.
40 pub content_hash: String,
41}
42
43/// Durable persistence contract for runtime-deployed workflow packages.
44///
45/// Every Aion event store must implement this: deployed packages are part of
46/// the same durability promise as event history, and a backend that kept
47/// history but dropped packages would strand every recovered run on a
48/// version the catalog cannot resolve.
49#[async_trait]
50pub trait PackageStore: Send + Sync + 'static {
51 /// Persists `record` and atomically points the type's route at it.
52 ///
53 /// This mirrors the engine's load semantics one-to-one: a successful
54 /// load always re-points the route of `record.workflow_type` at
55 /// `record.content_hash`, so the persisted package and the persisted
56 /// route pointer must commit together — a crash between them would
57 /// resurrect a stale route on restart. Re-persisting an existing
58 /// `(workflow_type, content_hash)` replaces the row (idempotent
59 /// re-deploy) and still re-points the route.
60 async fn put_package(&self, record: PackageRecord) -> Result<(), StoreError>;
61
62 /// Persists `record` and atomically points every member workflow type at
63 /// its content hash.
64 ///
65 /// Multi-entry archives use this operation so a crash cannot commit the
66 /// primary route while leaving a synthesized child on an older explicit
67 /// pointer. `route_workflow_types` contains the primary plus every
68 /// additional manifest entry and must commit in the same transaction as
69 /// the archive row.
70 async fn put_package_with_routes(
71 &self,
72 record: PackageRecord,
73 route_workflow_types: &[String],
74 ) -> Result<(), StoreError>;
75
76 /// Lists every persisted package in ascending `deployed_at` order
77 /// (ties broken by `(workflow_type, content_hash)` text order), so
78 /// startup reload re-applies deploys deterministically.
79 async fn list_packages(&self) -> Result<Vec<PackageRecord>, StoreError>;
80
81 /// Deletes the persisted archive for `(workflow_type, content_hash)`.
82 ///
83 /// Deleting an absent row is a no-op, never an error: unload must be
84 /// idempotent and versions loaded from operator-supplied files were
85 /// never persisted.
86 async fn delete_package(
87 &self,
88 workflow_type: &str,
89 content_hash: &str,
90 ) -> Result<(), StoreError>;
91
92 /// Upserts the route pointer for `workflow_type` to `content_hash`.
93 ///
94 /// Used by explicit route re-points (rollback / roll-forward) targeting
95 /// an already-loaded version; the pointed-at version is not required to
96 /// have a persisted archive (it may be an operator-file load), and the
97 /// engine resolves that loudly at reload time.
98 async fn put_package_route(
99 &self,
100 workflow_type: &str,
101 content_hash: &str,
102 ) -> Result<(), StoreError>;
103
104 /// Lists every persisted route pointer in `workflow_type` text order.
105 async fn list_package_routes(&self) -> Result<Vec<PackageRouteRecord>, StoreError>;
106}