Skip to content

Commit 63d94f9

Browse files
committed
feat: Implement core types and structures for audit trail functionality
1 parent cc62a4c commit 63d94f9

File tree

11 files changed

+400
-2
lines changed

11 files changed

+400
-2
lines changed

audit-trail-rs/src/core/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
//! Core types and builders for audit trails.
5-
//!
6-
//! This module is intentionally minimal while the Move contract API is stabilized.
5+
6+
pub mod types;
7+
8+
pub use types::*;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2020-2025 IOTA Stiftung
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use iota_interaction::types::base_types::IotaAddress;
5+
use iota_interaction::types::id::UID;
6+
use serde::{Deserialize, Serialize};
7+
8+
use super::locking::LockingConfig;
9+
use super::metadata::ImmutableMetadata;
10+
use super::permission::Permission;
11+
use super::record::Record;
12+
use super::role_map::RoleMap;
13+
14+
/// An audit trail stored on-chain.
15+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
16+
pub struct AuditTrail<D = super::record::RecordData> {
17+
pub id: UID,
18+
pub creator: IotaAddress,
19+
pub created_at: u64,
20+
pub sequence_number: u64,
21+
pub records: Vec<Record<D>>,
22+
pub locking_config: LockingConfig,
23+
pub roles: RoleMap,
24+
pub immutable_metadata: Option<ImmutableMetadata>,
25+
pub updatable_metadata: Option<String>,
26+
pub version: u64,
27+
#[serde(skip)]
28+
pub _phantom: std::marker::PhantomData<Permission>,
29+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2020-2025 IOTA Stiftung
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use iota_interaction::types::base_types::{IotaAddress, ObjectID};
5+
use iota_interaction::types::id::UID;
6+
use serde::{Deserialize, Serialize};
7+
8+
/// Capability data returned by the Move capability module.
9+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10+
pub struct Capability {
11+
pub id: UID,
12+
pub security_vault_id: ObjectID,
13+
pub role: String,
14+
pub issued_to: Option<IotaAddress>,
15+
pub valid_from: Option<u64>,
16+
pub valid_until: Option<u64>,
17+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright 2020-2025 IOTA Stiftung
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use iota_interaction::types::base_types::{IotaAddress, ObjectID};
5+
use serde::{Deserialize, Serialize};
6+
7+
/// Generic wrapper for audit trail events.
8+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9+
pub struct Event<D> {
10+
#[serde(flatten)]
11+
pub data: D,
12+
}
13+
14+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15+
pub struct AuditTrailCreated {
16+
pub trail_id: ObjectID,
17+
pub creator: IotaAddress,
18+
pub timestamp: u64,
19+
pub has_initial_record: bool,
20+
}
21+
22+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
23+
pub struct AuditTrailDeleted {
24+
pub trail_id: ObjectID,
25+
pub timestamp: u64,
26+
}
27+
28+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29+
pub struct RecordAdded {
30+
pub trail_id: ObjectID,
31+
pub sequence_number: u64,
32+
pub added_by: IotaAddress,
33+
pub timestamp: u64,
34+
}
35+
36+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
37+
pub struct RecordDeleted {
38+
pub trail_id: ObjectID,
39+
pub sequence_number: u64,
40+
pub deleted_by: IotaAddress,
41+
pub timestamp: u64,
42+
}
43+
44+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
45+
pub struct CapabilityIssued {
46+
pub security_vault_id: ObjectID,
47+
pub capability_id: ObjectID,
48+
pub role: String,
49+
pub issued_to: Option<IotaAddress>,
50+
pub valid_from: Option<u64>,
51+
pub valid_until: Option<u64>,
52+
}
53+
54+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
55+
pub struct CapabilityDestroyed {
56+
pub security_vault_id: ObjectID,
57+
pub capability_id: ObjectID,
58+
pub role: String,
59+
pub issued_to: Option<IotaAddress>,
60+
pub valid_from: Option<u64>,
61+
pub valid_until: Option<u64>,
62+
}
63+
64+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
65+
pub struct CapabilityRevoked {
66+
pub security_vault_id: ObjectID,
67+
pub capability_id: ObjectID,
68+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright 2020-2025 IOTA Stiftung
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use serde::{Deserialize, Serialize};
5+
6+
/// Defines a locking window (time or count based).
7+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8+
pub struct LockingWindow {
9+
pub time_window_seconds: Option<u64>,
10+
pub count_window: Option<u64>,
11+
}
12+
13+
impl LockingWindow {
14+
pub fn none() -> Self {
15+
Self {
16+
time_window_seconds: None,
17+
count_window: None,
18+
}
19+
}
20+
21+
pub fn time_based(seconds: u64) -> Self {
22+
Self {
23+
time_window_seconds: Some(seconds),
24+
count_window: None,
25+
}
26+
}
27+
28+
pub fn count_based(count: u64) -> Self {
29+
Self {
30+
time_window_seconds: None,
31+
count_window: Some(count),
32+
}
33+
}
34+
}
35+
36+
/// Locking configuration for the audit trail.
37+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
38+
pub struct LockingConfig {
39+
pub delete_record_lock: LockingWindow,
40+
}
41+
42+
impl LockingConfig {
43+
pub fn none() -> Self {
44+
Self {
45+
delete_record_lock: LockingWindow::none(),
46+
}
47+
}
48+
49+
pub fn time_based(seconds: u64) -> Self {
50+
Self {
51+
delete_record_lock: LockingWindow::time_based(seconds),
52+
}
53+
}
54+
55+
pub fn count_based(count: u64) -> Self {
56+
Self {
57+
delete_record_lock: LockingWindow::count_based(count),
58+
}
59+
}
60+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright 2020-2025 IOTA Stiftung
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use serde::{Deserialize, Serialize};
5+
6+
/// Metadata set at trail creation and never updated.
7+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8+
pub struct ImmutableMetadata {
9+
pub name: String,
10+
pub description: Option<String>,
11+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2020-2025 IOTA Stiftung
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! Core data types for audit trails.
5+
6+
pub mod audit_trail;
7+
pub mod capability;
8+
pub mod event;
9+
pub mod locking;
10+
pub mod metadata;
11+
pub mod permission;
12+
pub mod record;
13+
pub mod record_correction;
14+
pub mod role_map;
15+
16+
pub use audit_trail::*;
17+
pub use capability::*;
18+
pub use event::*;
19+
pub use locking::*;
20+
pub use metadata::*;
21+
pub use permission::*;
22+
pub use record::*;
23+
pub use record_correction::*;
24+
pub use role_map::*;
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright 2020-2025 IOTA Stiftung
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use serde::{Deserialize, Serialize};
5+
6+
/// Permission enum matching the Move permission module.
7+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
8+
pub enum Permission {
9+
DeleteAuditTrail,
10+
AddRecord,
11+
DeleteRecord,
12+
CorrectRecord,
13+
UpdateLockingConfig,
14+
UpdateLockingConfigForDeleteRecord,
15+
UpdateLockingConfigForDeleteTrail,
16+
AddRoles,
17+
UpdateRoles,
18+
DeleteRoles,
19+
AddCapabilities,
20+
RevokeCapabilities,
21+
UpdateMetadata,
22+
DeleteMetadata,
23+
}
24+
25+
/// Convenience wrapper for permission sets.
26+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27+
pub struct PermissionSet {
28+
pub permissions: Vec<Permission>,
29+
}
30+
31+
impl PermissionSet {
32+
pub fn empty() -> Self {
33+
Self { permissions: vec![] }
34+
}
35+
36+
pub fn from_vec(permissions: Vec<Permission>) -> Self {
37+
Self { permissions }
38+
}
39+
40+
pub fn admin_permissions() -> Self {
41+
Self::from_vec(vec![
42+
Permission::DeleteAuditTrail,
43+
Permission::AddCapabilities,
44+
Permission::RevokeCapabilities,
45+
Permission::AddRoles,
46+
Permission::UpdateRoles,
47+
Permission::DeleteRoles,
48+
])
49+
}
50+
51+
pub fn record_admin_permissions() -> Self {
52+
Self::from_vec(vec![
53+
Permission::AddRecord,
54+
Permission::DeleteRecord,
55+
Permission::CorrectRecord,
56+
])
57+
}
58+
59+
pub fn locking_admin_permissions() -> Self {
60+
Self::from_vec(vec![
61+
Permission::UpdateLockingConfig,
62+
Permission::UpdateLockingConfigForDeleteTrail,
63+
Permission::UpdateLockingConfigForDeleteRecord,
64+
])
65+
}
66+
67+
pub fn role_admin_permissions() -> Self {
68+
Self::from_vec(vec![
69+
Permission::AddRoles,
70+
Permission::UpdateRoles,
71+
Permission::DeleteRoles,
72+
])
73+
}
74+
75+
pub fn cap_admin_permissions() -> Self {
76+
Self::from_vec(vec![Permission::AddCapabilities, Permission::RevokeCapabilities])
77+
}
78+
79+
pub fn metadata_admin_permissions() -> Self {
80+
Self::from_vec(vec![Permission::UpdateMetadata, Permission::DeleteMetadata])
81+
}
82+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright 2020-2025 IOTA Stiftung
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use iota_interaction::types::base_types::IotaAddress;
5+
use serde::{Deserialize, Serialize};
6+
7+
use super::record_correction::RecordCorrection;
8+
9+
/// Supported record data types.
10+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11+
pub enum RecordData {
12+
Bytes(Vec<u8>),
13+
Text(String),
14+
}
15+
16+
impl RecordData {
17+
pub fn bytes(data: impl Into<Vec<u8>>) -> Self {
18+
Self::Bytes(data.into())
19+
}
20+
21+
pub fn text(data: impl Into<String>) -> Self {
22+
Self::Text(data.into())
23+
}
24+
}
25+
26+
/// A single record in the audit trail.
27+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
28+
pub struct Record<D = RecordData> {
29+
pub data: D,
30+
pub metadata: Option<String>,
31+
pub sequence_number: u64,
32+
pub added_by: IotaAddress,
33+
pub added_at: u64,
34+
pub correction: RecordCorrection,
35+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright 2020-2025 IOTA Stiftung
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use serde::{Deserialize, Serialize};
5+
6+
/// Bidirectional correction tracking for audit records.
7+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8+
pub struct RecordCorrection {
9+
pub replaces: Vec<u64>,
10+
pub is_replaced_by: Option<u64>,
11+
}
12+
13+
impl RecordCorrection {
14+
pub fn new() -> Self {
15+
Self {
16+
replaces: Vec::new(),
17+
is_replaced_by: None,
18+
}
19+
}
20+
21+
pub fn with_replaces(replaces: Vec<u64>) -> Self {
22+
Self {
23+
replaces,
24+
is_replaced_by: None,
25+
}
26+
}
27+
28+
pub fn is_correction(&self) -> bool {
29+
!self.replaces.is_empty()
30+
}
31+
32+
pub fn is_replaced(&self) -> bool {
33+
self.is_replaced_by.is_some()
34+
}
35+
}

0 commit comments

Comments
 (0)