extern crate alloc;
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
use crate::{Header, Id128, SubstrateKind};
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Event {
#[cfg_attr(feature = "serde", serde(flatten))]
pub header: Header,
pub verb: String,
pub substrate: SubstrateKind,
pub actor: Option<String>,
pub kind: EventKind,
pub payload: EventPayload,
pub payload_schema_version: u32,
pub profile_state_version: Option<u64>,
pub aggregate: Option<AggregateRef>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum EventOutcome {
#[default]
Success,
Denied,
Error,
}
impl EventOutcome {
pub const fn name(self) -> &'static str {
match self {
Self::Success => "success",
Self::Denied => "denied",
Self::Error => "error",
}
}
}
impl fmt::Display for EventOutcome {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum EventSink {
CallerEventStore,
OperatorAudit,
}
impl EventSink {
pub const fn name(self) -> &'static str {
match self {
Self::CallerEventStore => "caller_event_store",
Self::OperatorAudit => "operator_audit",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum EventKind {
Audit,
RecallExecuted,
RerankExecuted,
SearchExecuted,
ToolCheckDecided,
LinkCreated,
EntityCreated,
EntityUpdated,
EntityDeleted,
EntityMerged,
NoteMerged,
NoteCreated,
NoteUpdated,
NoteDeleted,
EdgeUpdated,
EdgeDeleted,
TaskTransitioned,
FeedbackExplicit,
FeedbackUnjudged,
ProfileResolutionRecommended,
ProfileMerged,
EmbeddingModelChanged,
EmbeddingMigrationCompleted,
EmbeddingMigrationFailed,
EmbeddingDriftDetected,
EmbedderInitialized,
ProposalCreated,
ProposalReviewed,
ProposalApplied,
ProposalWithdrawn,
ChannelPollStarted,
ChannelPollSucceeded,
ChannelPollFailed,
ChannelBackoffArmed,
ChannelBackoffReset,
ChannelHeartbeatPersistFailed,
ConfigLocked,
CheckpointOutcomeRecorded,
PhaseStarted,
PhaseCompleted,
PhaseCancelled,
Refusal,
}
impl EventKind {
pub const ALL: [Self; 42] = [
Self::Audit,
Self::RecallExecuted,
Self::RerankExecuted,
Self::SearchExecuted,
Self::ToolCheckDecided,
Self::LinkCreated,
Self::EntityCreated,
Self::EntityUpdated,
Self::EntityDeleted,
Self::EntityMerged,
Self::NoteMerged,
Self::NoteCreated,
Self::NoteUpdated,
Self::NoteDeleted,
Self::EdgeUpdated,
Self::EdgeDeleted,
Self::TaskTransitioned,
Self::FeedbackExplicit,
Self::FeedbackUnjudged,
Self::ProfileResolutionRecommended,
Self::ProfileMerged,
Self::EmbeddingModelChanged,
Self::EmbeddingMigrationCompleted,
Self::EmbeddingMigrationFailed,
Self::EmbeddingDriftDetected,
Self::EmbedderInitialized,
Self::ProposalCreated,
Self::ProposalReviewed,
Self::ProposalApplied,
Self::ProposalWithdrawn,
Self::ChannelPollStarted,
Self::ChannelPollSucceeded,
Self::ChannelPollFailed,
Self::ChannelBackoffArmed,
Self::ChannelBackoffReset,
Self::ChannelHeartbeatPersistFailed,
Self::ConfigLocked,
Self::CheckpointOutcomeRecorded,
Self::PhaseStarted,
Self::PhaseCompleted,
Self::PhaseCancelled,
Self::Refusal,
];
pub const fn sink(self) -> EventSink {
match self {
Self::Audit => EventSink::CallerEventStore,
Self::RecallExecuted => EventSink::CallerEventStore,
Self::RerankExecuted => EventSink::CallerEventStore,
Self::SearchExecuted => EventSink::CallerEventStore,
Self::ToolCheckDecided => EventSink::CallerEventStore,
Self::LinkCreated => EventSink::CallerEventStore,
Self::EntityCreated => EventSink::CallerEventStore,
Self::EntityUpdated => EventSink::CallerEventStore,
Self::EntityDeleted => EventSink::CallerEventStore,
Self::EntityMerged => EventSink::CallerEventStore,
Self::NoteMerged => EventSink::CallerEventStore,
Self::NoteCreated => EventSink::CallerEventStore,
Self::NoteUpdated => EventSink::CallerEventStore,
Self::NoteDeleted => EventSink::CallerEventStore,
Self::EdgeUpdated => EventSink::CallerEventStore,
Self::EdgeDeleted => EventSink::CallerEventStore,
Self::TaskTransitioned => EventSink::CallerEventStore,
Self::FeedbackExplicit => EventSink::CallerEventStore,
Self::FeedbackUnjudged => EventSink::CallerEventStore,
Self::ProfileResolutionRecommended => EventSink::CallerEventStore,
Self::ProfileMerged => EventSink::CallerEventStore,
Self::EmbeddingModelChanged => EventSink::CallerEventStore,
Self::EmbeddingMigrationCompleted => EventSink::CallerEventStore,
Self::EmbeddingMigrationFailed => EventSink::CallerEventStore,
Self::EmbeddingDriftDetected => EventSink::CallerEventStore,
Self::EmbedderInitialized => EventSink::CallerEventStore,
Self::ProposalCreated => EventSink::CallerEventStore,
Self::ProposalReviewed => EventSink::CallerEventStore,
Self::ProposalApplied => EventSink::CallerEventStore,
Self::ProposalWithdrawn => EventSink::CallerEventStore,
Self::ChannelPollStarted => EventSink::CallerEventStore,
Self::ChannelPollSucceeded => EventSink::CallerEventStore,
Self::ChannelPollFailed => EventSink::CallerEventStore,
Self::ChannelBackoffArmed => EventSink::CallerEventStore,
Self::ChannelBackoffReset => EventSink::CallerEventStore,
Self::ChannelHeartbeatPersistFailed => EventSink::CallerEventStore,
Self::ConfigLocked => EventSink::CallerEventStore,
Self::CheckpointOutcomeRecorded => EventSink::CallerEventStore,
Self::PhaseStarted => EventSink::CallerEventStore,
Self::PhaseCompleted => EventSink::CallerEventStore,
Self::PhaseCancelled => EventSink::CallerEventStore,
Self::Refusal => EventSink::CallerEventStore,
}
}
pub const fn name(self) -> &'static str {
match self {
Self::Audit => "audit",
Self::RecallExecuted => "recall_executed",
Self::RerankExecuted => "rerank_executed",
Self::SearchExecuted => "search_executed",
Self::ToolCheckDecided => "tool_check_decided",
Self::LinkCreated => "link_created",
Self::EntityCreated => "entity_created",
Self::EntityUpdated => "entity_updated",
Self::EntityDeleted => "entity_deleted",
Self::EntityMerged => "entity_merged",
Self::NoteMerged => "note_merged",
Self::NoteCreated => "note_created",
Self::NoteUpdated => "note_updated",
Self::NoteDeleted => "note_deleted",
Self::EdgeUpdated => "edge_updated",
Self::EdgeDeleted => "edge_deleted",
Self::TaskTransitioned => "task_transitioned",
Self::FeedbackExplicit => "feedback_explicit",
Self::FeedbackUnjudged => "feedback_unjudged",
Self::ProfileResolutionRecommended => "profile_resolution_recommended",
Self::ProfileMerged => "profile_merged",
Self::EmbeddingModelChanged => "embedding_model_changed",
Self::EmbeddingMigrationCompleted => "embedding_migration_completed",
Self::EmbeddingMigrationFailed => "embedding_migration_failed",
Self::EmbeddingDriftDetected => "embedding_drift_detected",
Self::EmbedderInitialized => "embedder_initialized",
Self::ProposalCreated => "proposal_created",
Self::ProposalReviewed => "proposal_reviewed",
Self::ProposalApplied => "proposal_applied",
Self::ProposalWithdrawn => "proposal_withdrawn",
Self::ChannelPollStarted => "channel_poll_started",
Self::ChannelPollSucceeded => "channel_poll_succeeded",
Self::ChannelPollFailed => "channel_poll_failed",
Self::ChannelBackoffArmed => "channel_backoff_armed",
Self::ChannelBackoffReset => "channel_backoff_reset",
Self::ChannelHeartbeatPersistFailed => "channel_heartbeat_persist_failed",
Self::ConfigLocked => "config_locked",
Self::CheckpointOutcomeRecorded => "checkpoint_outcome_recorded",
Self::PhaseStarted => "phase_started",
Self::PhaseCompleted => "phase_completed",
Self::PhaseCancelled => "phase_cancelled",
Self::Refusal => "refusal",
}
}
}
impl fmt::Display for EventKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
const EVENT_KIND_VALID: &[&str] = &[
"audit",
"recall_executed",
"rerank_executed",
"search_executed",
"tool_check_decided",
"link_created",
"entity_created",
"entity_updated",
"entity_deleted",
"entity_merged",
"note_merged",
"note_created",
"note_updated",
"note_deleted",
"edge_updated",
"edge_deleted",
"task_transitioned",
"feedback_explicit",
"feedback_unjudged",
"profile_resolution_recommended",
"profile_merged",
"embedding_model_changed",
"embedding_migration_completed",
"embedding_migration_failed",
"embedding_drift_detected",
"embedder_initialized",
"proposal_created",
"proposal_reviewed",
"proposal_applied",
"proposal_withdrawn",
"channel_poll_started",
"channel_poll_succeeded",
"channel_poll_failed",
"channel_backoff_armed",
"channel_backoff_reset",
"channel_heartbeat_persist_failed",
"config_locked",
"checkpoint_outcome_recorded",
"phase_started",
"phase_completed",
"phase_cancelled",
"refusal",
];
impl core::str::FromStr for EventKind {
type Err = crate::error::UnknownVariant;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.trim().to_ascii_lowercase().as_str() {
"audit" => Ok(Self::Audit),
"recall_executed" => Ok(Self::RecallExecuted),
"rerank_executed" => Ok(Self::RerankExecuted),
"search_executed" => Ok(Self::SearchExecuted),
"tool_check_decided" => Ok(Self::ToolCheckDecided),
"link_created" => Ok(Self::LinkCreated),
"entity_created" => Ok(Self::EntityCreated),
"entity_updated" => Ok(Self::EntityUpdated),
"entity_deleted" => Ok(Self::EntityDeleted),
"entity_merged" => Ok(Self::EntityMerged),
"note_merged" => Ok(Self::NoteMerged),
"note_created" => Ok(Self::NoteCreated),
"note_updated" => Ok(Self::NoteUpdated),
"note_deleted" => Ok(Self::NoteDeleted),
"edge_updated" => Ok(Self::EdgeUpdated),
"edge_deleted" => Ok(Self::EdgeDeleted),
"task_transitioned" => Ok(Self::TaskTransitioned),
"feedback_explicit" => Ok(Self::FeedbackExplicit),
"feedback_unjudged" => Ok(Self::FeedbackUnjudged),
"profile_resolution_recommended" => Ok(Self::ProfileResolutionRecommended),
"profile_merged" => Ok(Self::ProfileMerged),
"embedding_model_changed" => Ok(Self::EmbeddingModelChanged),
"embedding_migration_completed" => Ok(Self::EmbeddingMigrationCompleted),
"embedding_migration_failed" => Ok(Self::EmbeddingMigrationFailed),
"embedding_drift_detected" => Ok(Self::EmbeddingDriftDetected),
"embedder_initialized" => Ok(Self::EmbedderInitialized),
"proposal_created" => Ok(Self::ProposalCreated),
"proposal_reviewed" => Ok(Self::ProposalReviewed),
"proposal_applied" => Ok(Self::ProposalApplied),
"proposal_withdrawn" => Ok(Self::ProposalWithdrawn),
"channel_poll_started" => Ok(Self::ChannelPollStarted),
"channel_poll_succeeded" => Ok(Self::ChannelPollSucceeded),
"channel_poll_failed" => Ok(Self::ChannelPollFailed),
"channel_backoff_armed" => Ok(Self::ChannelBackoffArmed),
"channel_backoff_reset" => Ok(Self::ChannelBackoffReset),
"channel_heartbeat_persist_failed" => Ok(Self::ChannelHeartbeatPersistFailed),
"config_locked" => Ok(Self::ConfigLocked),
"checkpoint_outcome_recorded" => Ok(Self::CheckpointOutcomeRecorded),
"phase_started" => Ok(Self::PhaseStarted),
"phase_completed" => Ok(Self::PhaseCompleted),
"phase_cancelled" => Ok(Self::PhaseCancelled),
"refusal" => Ok(Self::Refusal),
other => Err(crate::error::UnknownVariant::new(
"event_kind",
other,
EVENT_KIND_VALID,
)),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AggregateRef {
pub kind: String,
pub id: Id128,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serde",
serde(tag = "kind", content = "payload", rename_all = "snake_case")
)]
pub enum EventPayload {
Json(String),
RerankExecuted(RerankExecutedPayload),
ToolCheckDecided(ToolCheckDecidedPayload),
#[cfg(feature = "serde")]
ProposalCreated(ProposalCreatedPayload),
ProposalReviewed(ProposalReviewedPayload),
ProposalApplied(ProposalAppliedPayload),
ProposalWithdrawn(ProposalWithdrawnPayload),
}
impl Default for EventPayload {
fn default() -> Self {
Self::Json("{}".into())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ToolCheckDecidedPayload {
pub actor: String,
pub tool: String,
pub registered: bool,
pub decision: String,
pub source: String,
pub id: Option<String>,
pub scope: Option<String>,
pub caller_verb: String,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum RerankerKind {
#[default]
Native,
Weighted,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct RerankExecutedPayload {
pub reranker: RerankerKind,
pub served_by_profile_id: Option<String>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub model_id: Option<Id128>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub query_id: Option<String>,
pub tiers: Vec<String>,
pub ignored_weights: Vec<String>,
pub unidentified_candidates: usize,
pub candidates: Vec<Id128>,
pub reranked: Vec<(Id128, Vec<(String, f32)>)>,
pub final_scores: Vec<(Id128, f32)>,
pub latency_us: u64,
pub hook_applied: bool,
pub hook_target_match: bool,
}
impl RerankExecutedPayload {
pub fn is_valid(&self) -> bool {
let reranked_ok = self
.reranked
.iter()
.all(|(_, scores)| scores.iter().all(|(_, s)| s.is_finite()));
let final_ok = self.final_scores.iter().all(|(_, s)| s.is_finite());
reranked_ok && final_ok
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for RerankExecutedPayload {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(serde::Deserialize)]
struct Raw {
#[serde(default)]
reranker: RerankerKind,
served_by_profile_id: Option<String>,
model_id: Option<Id128>,
query_id: Option<String>,
#[serde(default)]
tiers: Vec<String>,
#[serde(default)]
ignored_weights: Vec<String>,
#[serde(default)]
unidentified_candidates: usize,
candidates: Vec<Id128>,
reranked: Vec<(Id128, Vec<(String, f32)>)>,
final_scores: Vec<(Id128, f32)>,
latency_us: u64,
hook_applied: bool,
hook_target_match: bool,
}
let raw = Raw::deserialize(deserializer)?;
for (_, score) in &raw.final_scores {
if !score.is_finite() {
return Err(serde::de::Error::custom(alloc::format!(
"RerankExecutedPayload final_scores must be finite, got {score}"
)));
}
}
for (_, sections) in &raw.reranked {
for (section_name, score) in sections {
if !score.is_finite() {
return Err(serde::de::Error::custom(alloc::format!(
"RerankExecutedPayload reranked section '{section_name}' score must be finite, got {score}"
)));
}
}
}
Ok(RerankExecutedPayload {
reranker: raw.reranker,
served_by_profile_id: raw.served_by_profile_id,
model_id: raw.model_id,
query_id: raw.query_id,
tiers: raw.tiers,
ignored_weights: raw.ignored_weights,
unidentified_candidates: raw.unidentified_candidates,
candidates: raw.candidates,
reranked: raw.reranked,
final_scores: raw.final_scores,
latency_us: raw.latency_us,
hook_applied: raw.hook_applied,
hook_target_match: raw.hook_target_match,
})
}
}
#[cfg(feature = "serde")]
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct ProposalCreatedPayload {
pub proposal_id: Id128,
pub proposer: String,
pub title: String,
pub description: String,
pub changeset: ProposalChangeset,
pub reviewers: Vec<String>,
pub expiry: Option<crate::Timestamp>,
pub parent_id: Option<Id128>,
}
#[cfg(feature = "serde")]
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct EntityDraft {
pub kind: String,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub properties: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tags: Vec<String>,
}
#[cfg(feature = "serde")]
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct ProposalEntityPatch {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
with = "serde_opt_opt"
)]
pub description: Option<Option<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub properties: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<String>>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
with = "serde_opt_opt"
)]
pub entity_type: Option<Option<String>>,
}
#[cfg(feature = "serde")]
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct NoteDraft {
pub kind: String,
pub content: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub properties: Option<serde_json::Value>,
}
#[cfg(feature = "serde")]
mod serde_opt_opt {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<T, S>(val: &Option<Option<T>>, s: S) -> Result<S::Ok, S::Error>
where
T: Serialize,
S: Serializer,
{
match val {
None => unreachable!("skip_serializing_if guards the None case"),
Some(inner) => inner.serialize(s),
}
}
pub fn deserialize<'de, T, D>(d: D) -> Result<Option<Option<T>>, D::Error>
where
T: Deserialize<'de>,
D: Deserializer<'de>,
{
let opt: Option<T> = Option::deserialize(d)?;
Ok(Some(opt))
}
}
#[cfg(feature = "serde")]
#[derive(Clone, Debug, PartialEq, serde::Serialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum ProposalChangeset {
AddEntity {
entity: EntityDraft,
},
UpdateEntity {
id: Id128,
patch: ProposalEntityPatch,
},
AddEdge {
source: Id128,
target: Id128,
relation: crate::EdgeRelation,
weight: Option<f32>,
},
AddNote {
note: NoteDraft,
},
MergeEntities {
into: Id128,
from: Id128,
},
SupersedeEntity {
old: Id128,
new: Id128,
},
Compound {
steps: Vec<ProposalChangeset>,
},
}
#[cfg(feature = "serde")]
impl ProposalChangeset {
fn validate(&self) -> Result<(), alloc::string::String> {
match self {
Self::AddEdge { weight, .. } => {
if let Some(w) = weight {
if !w.is_finite() {
return Err(alloc::format!(
"ProposalChangeset AddEdge weight must be finite, got {w}"
));
}
if !(*w >= 0.0 && *w <= 1.0) {
return Err(alloc::format!(
"ProposalChangeset AddEdge weight must be in [0.0, 1.0], got {w}"
));
}
}
Ok(())
}
Self::Compound { steps } => {
for step in steps {
step.validate()?;
}
Ok(())
}
_ => Ok(()),
}
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for ProposalChangeset {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(serde::Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
enum ProposalChangesetRaw {
AddEntity {
entity: EntityDraft,
},
UpdateEntity {
id: Id128,
patch: ProposalEntityPatch,
},
AddEdge {
source: Id128,
target: Id128,
relation: crate::EdgeRelation,
weight: Option<f32>,
},
AddNote {
note: NoteDraft,
},
MergeEntities {
into: Id128,
from: Id128,
},
SupersedeEntity {
old: Id128,
new: Id128,
},
Compound {
steps: Vec<ProposalChangeset>,
},
}
let raw = ProposalChangesetRaw::deserialize(deserializer)?;
let cs = match raw {
ProposalChangesetRaw::AddEntity { entity } => Self::AddEntity { entity },
ProposalChangesetRaw::UpdateEntity { id, patch } => Self::UpdateEntity { id, patch },
ProposalChangesetRaw::AddEdge {
source,
target,
relation,
weight,
} => Self::AddEdge {
source,
target,
relation,
weight,
},
ProposalChangesetRaw::AddNote { note } => Self::AddNote { note },
ProposalChangesetRaw::MergeEntities { into, from } => {
Self::MergeEntities { into, from }
}
ProposalChangesetRaw::SupersedeEntity { old, new } => {
Self::SupersedeEntity { old, new }
}
ProposalChangesetRaw::Compound { steps } => Self::Compound { steps },
};
cs.validate().map_err(serde::de::Error::custom)?;
Ok(cs)
}
}
#[cfg(not(feature = "serde"))]
#[derive(Clone, Debug, PartialEq)]
pub enum ProposalChangeset {
AddEdge {
source: Id128,
target: Id128,
relation: crate::EdgeRelation,
weight: Option<f32>,
},
MergeEntities {
into: Id128,
from: Id128,
},
SupersedeEntity {
old: Id128,
new: Id128,
},
Compound {
steps: Vec<ProposalChangeset>,
},
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ProposalReviewedPayload {
pub proposal_id: Id128,
pub reviewer: String,
pub decision: ProposalDecision,
pub comment: Option<String>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum ProposalDecision {
Approve,
Reject,
Comment,
RequestChanges,
}
impl ProposalDecision {
pub fn as_str(self) -> &'static str {
match self {
Self::Approve => "approve",
Self::Reject => "reject",
Self::Comment => "comment",
Self::RequestChanges => "request_changes",
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ProposalAppliedPayload {
pub proposal_id: Id128,
pub applied_at: crate::Timestamp,
pub applied_by: String,
pub result: ApplyResult,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum ApplyResult {
Success {
created_records: Vec<Id128>,
},
Failed {
error: String,
applied_step_count: u32,
},
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ProposalWithdrawnPayload {
pub proposal_id: Id128,
pub by: String,
pub reason: Option<String>,
}
pub struct EventBuilder {
verb: String,
substrate: SubstrateKind,
actor: Option<String>,
kind: EventKind,
payload: EventPayload,
payload_schema_version: u32,
profile_state_version: Option<u64>,
aggregate: Option<AggregateRef>,
}
impl EventBuilder {
pub fn new(
verb: impl Into<String>,
substrate: SubstrateKind,
actor: impl Into<String>,
) -> Self {
Self {
verb: verb.into(),
substrate,
actor: Some(actor.into()),
kind: EventKind::Audit,
payload: EventPayload::default(),
payload_schema_version: 1,
profile_state_version: None,
aggregate: None,
}
}
pub fn kind(mut self, kind: EventKind) -> Self {
self.kind = kind;
self
}
pub fn payload(mut self, payload: EventPayload) -> Self {
self.payload = payload;
self
}
pub fn payload_schema_version(mut self, version: u32) -> Self {
self.payload_schema_version = version;
self
}
pub fn profile_state_version(mut self, version: u64) -> Self {
self.profile_state_version = Some(version);
self
}
pub fn aggregate(mut self, aggregate: AggregateRef) -> Self {
self.aggregate = Some(aggregate);
self
}
pub fn build(self, header: Header) -> Event {
Event {
header,
verb: self.verb,
substrate: self.substrate,
actor: self.actor,
kind: self.kind,
payload: self.payload,
payload_schema_version: self.payload_schema_version,
profile_state_version: self.profile_state_version,
aggregate: self.aggregate,
}
}
}
#[cfg(test)]
#[path = "event_tests.rs"]
mod tests;