use serde::{Deserialize, Serialize};
use serde_json::Value;
const COLLECTIVE: &str = "collective";
pub(crate) const AUTO_ATOMISE_SYNCHRONOUS: &str = "synchronous";
pub const VALID_SCOPES: &[&str] = &["private", "team", "unit", "org", "collective"];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
#[serde(rename_all = "snake_case")]
pub enum MemoryScope {
Private,
Team,
Unit,
Org,
Collective,
}
impl MemoryScope {
pub const COUNT: usize = 5;
#[must_use]
pub const fn all() -> &'static [Self; Self::COUNT] {
&[
Self::Private,
Self::Team,
Self::Unit,
Self::Org,
Self::Collective,
]
}
#[must_use]
pub const fn all_strs() -> &'static [&'static str; Self::COUNT] {
&["private", "team", "unit", "org", COLLECTIVE]
}
#[must_use]
pub fn from_str(s: &str) -> Option<Self> {
match s {
"private" => Some(Self::Private),
"team" => Some(Self::Team),
"unit" => Some(Self::Unit),
"org" => Some(Self::Org),
COLLECTIVE => Some(Self::Collective),
_ => None,
}
}
#[must_use]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Private => "private",
Self::Team => "team",
Self::Unit => "unit",
Self::Org => "org",
Self::Collective => COLLECTIVE,
}
}
}
impl std::fmt::Display for MemoryScope {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl Default for MemoryScope {
fn default() -> Self {
Self::Private
}
}
impl std::str::FromStr for MemoryScope {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_str(s).ok_or_else(|| {
format!(
"invalid memory scope '{s}' (expected one of: {})",
VALID_SCOPES.join(", ")
)
})
}
}
pub const VALID_AGENT_TYPES: &[&str] = &[
"ai:claude-opus-4.6",
"ai:claude-opus-4.7",
"ai:codex-5.4",
"ai:grok-4.2",
"human",
"system",
];
pub const MAX_NAMESPACE_DEPTH: usize = 8;
#[must_use]
pub fn namespace_depth(ns: &str) -> usize {
if ns.is_empty() {
return 0;
}
ns.split('/').filter(|s| !s.is_empty()).count()
}
#[allow(dead_code)]
#[must_use]
pub fn namespace_parent(ns: &str) -> Option<String> {
ns.rsplit_once('/').map(|(parent, _)| parent.to_string())
}
#[allow(dead_code)]
#[must_use]
pub fn namespace_ancestors(ns: &str) -> Vec<String> {
if ns.is_empty() {
return Vec::new();
}
let mut out = Vec::with_capacity(namespace_depth(ns));
let mut current = ns.to_string();
loop {
out.push(current.clone());
match namespace_parent(¤t) {
Some(p) if !p.is_empty() => current = p,
_ => break,
}
}
out
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GovernanceDecision {
Allow,
Deny(crate::governance::GovernanceRefusal),
Pending(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum GovernedAction {
Store,
Delete,
Promote,
Reflect,
}
impl GovernedAction {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Store => "store",
Self::Delete => "delete",
Self::Promote => "promote",
Self::Reflect => "reflect",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Approval {
pub agent_id: String,
pub approved_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PendingAction {
pub id: String,
pub action_type: String,
pub memory_id: Option<String>,
pub namespace: String,
pub payload: Value,
pub requested_by: String,
pub requested_at: String,
pub status: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub decided_by: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub decided_at: Option<String>,
#[serde(default)]
pub approvals: Vec<Approval>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PendingDecision {
pub id: String,
pub approved: bool,
pub decider: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NamespaceMetaEntry {
pub namespace: String,
pub standard_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent_namespace: Option<String>,
#[serde(default)]
pub updated_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum GovernanceLevel {
Any,
Registered,
Owner,
Approve,
}
impl GovernanceLevel {
#[allow(dead_code)]
#[must_use]
pub fn as_str(&self) -> &'static str {
match self {
Self::Any => "any",
Self::Registered => "registered",
Self::Owner => "owner",
Self::Approve => "approve",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ApproverType {
Human,
Agent(String),
Consensus(u32),
}
impl ApproverType {
#[allow(dead_code)]
#[must_use]
pub fn kind(&self) -> &'static str {
match self {
Self::Human => "human",
Self::Agent(_) => "agent",
Self::Consensus(_) => "consensus",
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct GovernancePolicy {
#[serde(flatten)]
pub core: CorePolicy,
#[serde(flatten)]
pub atomisation: AtomisationPolicy,
#[serde(flatten)]
pub synthesis: SynthesisPolicy,
#[serde(flatten)]
pub multistep: MultistepPolicy,
#[serde(flatten)]
pub kind_class: KindClassificationPolicy,
#[serde(flatten)]
pub persona: PersonaPolicy,
#[serde(flatten)]
pub export: ExportPolicy,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CorePolicy {
pub write: GovernanceLevel,
#[serde(default = "default_promote_level")]
pub promote: GovernanceLevel,
#[serde(default = "default_delete_level")]
pub delete: GovernanceLevel,
#[serde(default = "default_approver")]
pub approver: ApproverType,
#[serde(default = "default_inherit")]
pub inherit: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_reflection_depth: Option<u32>,
}
impl Default for CorePolicy {
fn default() -> Self {
Self {
write: GovernanceLevel::Any,
promote: default_promote_level(),
delete: default_delete_level(),
approver: default_approver(),
inherit: default_inherit(),
max_reflection_depth: None,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct ExportPolicy {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub auto_export_reflections_to_filesystem: Option<bool>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct AtomisationPolicy {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub auto_atomise: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub auto_atomise_threshold_cl100k: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub auto_atomise_max_atom_tokens: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub auto_atomise_max_retries: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub auto_atomise_mode: Option<AutoAtomiseMode>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct PersonaPolicy {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub auto_persona_trigger_every_n_memories: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub auto_export_personas_to_filesystem: Option<bool>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct SynthesisPolicy {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub legacy_per_pair_classifier: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub synthesis_failure_mode: Option<SynthesisFailureMode>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub synthesis_max_deletes_per_call: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub synthesis_max_candidate_chars: Option<u32>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct KindClassificationPolicy {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub auto_classify_kind: Option<MemoryKindAutoClassify>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct MultistepPolicy {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub multistep_max_content_chars: Option<u32>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum AutoAtomiseMode {
Off,
Deferred,
Synchronous,
}
impl AutoAtomiseMode {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Off => "off",
Self::Deferred => "deferred",
Self::Synchronous => AUTO_ATOMISE_SYNCHRONOUS,
}
}
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum SynthesisFailureMode {
#[default]
FallThrough,
BlockWrite,
}
impl SynthesisFailureMode {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::FallThrough => "fall_through",
Self::BlockWrite => "block_write",
}
}
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum MemoryKindAutoClassify {
#[default]
Off,
RegexOnly,
RegexThenLlm,
}
fn default_promote_level() -> GovernanceLevel {
GovernanceLevel::Any
}
fn default_delete_level() -> GovernanceLevel {
GovernanceLevel::Owner
}
fn default_approver() -> ApproverType {
ApproverType::Human
}
fn default_inherit() -> bool {
true
}
impl GovernancePolicy {
pub fn from_metadata(metadata: &Value) -> Option<Result<Self, serde_json::Error>> {
let gov = metadata.get(crate::META_KEY_GOVERNANCE)?;
if gov.is_null() {
return None;
}
Some(serde_json::from_value(gov.clone()))
}
#[must_use]
pub fn default_for_managed_namespace() -> Self {
Self {
core: CorePolicy {
write: GovernanceLevel::Owner,
..CorePolicy::default()
},
..Self::default()
}
}
#[must_use]
pub fn effective_max_reflection_depth(&self) -> u32 {
self.core.max_reflection_depth.unwrap_or(3)
}
#[must_use]
pub fn effective_auto_export_reflections_to_filesystem(&self) -> bool {
self.export
.auto_export_reflections_to_filesystem
.unwrap_or(false)
}
#[must_use]
pub fn effective_auto_atomise(&self) -> bool {
self.atomisation.auto_atomise.unwrap_or(false)
}
#[must_use]
pub fn effective_auto_atomise_threshold_cl100k(&self) -> u32 {
self.atomisation
.auto_atomise_threshold_cl100k
.unwrap_or(500)
}
#[must_use]
pub fn effective_auto_atomise_max_atom_tokens(&self) -> u32 {
self.atomisation.auto_atomise_max_atom_tokens.unwrap_or(200)
}
#[must_use]
pub fn effective_auto_atomise_max_retries(&self) -> Option<u32> {
self.atomisation.auto_atomise_max_retries
}
#[must_use]
pub fn effective_auto_persona_trigger_every_n_memories(&self) -> Option<u32> {
self.persona.auto_persona_trigger_every_n_memories
}
#[must_use]
pub fn effective_auto_export_personas_to_filesystem(&self) -> bool {
self.persona
.auto_export_personas_to_filesystem
.unwrap_or(false)
}
#[must_use]
pub fn effective_auto_atomise_mode(&self) -> AutoAtomiseMode {
if let Some(m) = self.atomisation.auto_atomise_mode {
return m;
}
if self.atomisation.auto_atomise.unwrap_or(false) {
AutoAtomiseMode::Deferred
} else {
AutoAtomiseMode::Off
}
}
#[must_use]
pub fn effective_legacy_per_pair_classifier(&self) -> bool {
self.synthesis.legacy_per_pair_classifier.unwrap_or(false)
}
#[must_use]
pub fn effective_synthesis_failure_mode(&self) -> SynthesisFailureMode {
self.synthesis.synthesis_failure_mode.unwrap_or_default()
}
#[must_use]
pub fn effective_synthesis_max_deletes_per_call(&self) -> u32 {
self.synthesis.synthesis_max_deletes_per_call.unwrap_or(1)
}
#[must_use]
pub fn effective_synthesis_max_candidate_chars(&self) -> usize {
self.synthesis.synthesis_max_candidate_chars.unwrap_or(1500) as usize
}
#[must_use]
pub fn effective_multistep_max_content_chars(&self) -> usize {
self.multistep.multistep_max_content_chars.unwrap_or(1500) as usize
}
#[must_use]
pub fn effective_auto_classify_kind(&self) -> MemoryKindAutoClassify {
self.kind_class.auto_classify_kind.unwrap_or_default()
}
}
pub const AGENTS_NAMESPACE: &str = "_agents";
#[must_use]
pub fn agent_registration_title(agent_id: &str) -> String {
format!("agent:{agent_id}")
}
#[derive(Debug, Clone, Deserialize)]
pub struct BindAgentPubkeyBody {
pub pubkey_b64: String,
}
#[derive(Debug, Deserialize)]
pub struct RegisterAgentBody {
pub agent_id: String,
pub agent_type: String,
#[serde(default)]
pub capabilities: Option<Vec<String>>,
}
#[derive(Debug, Serialize)]
pub struct AgentRegistration {
pub agent_id: String,
pub agent_type: String,
pub capabilities: Vec<String>,
pub registered_at: String,
pub last_seen_at: String,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn governance_policy_default_resolves_form_fields_to_none_and_compiled_defaults() {
let p = GovernancePolicy::default();
assert_eq!(p.core.write, GovernanceLevel::Any);
assert_eq!(p.core.promote, GovernanceLevel::Any);
assert_eq!(p.core.delete, GovernanceLevel::Owner);
assert_eq!(p.core.approver, ApproverType::Human);
assert!(p.core.inherit);
assert!(p.core.max_reflection_depth.is_none());
assert!(p.export.auto_export_reflections_to_filesystem.is_none());
assert!(p.atomisation.auto_atomise.is_none());
assert!(p.atomisation.auto_atomise_threshold_cl100k.is_none());
assert!(p.atomisation.auto_atomise_max_atom_tokens.is_none());
assert!(p.atomisation.auto_atomise_max_retries.is_none());
assert!(p.persona.auto_persona_trigger_every_n_memories.is_none());
assert!(p.persona.auto_export_personas_to_filesystem.is_none());
assert!(p.atomisation.auto_atomise_mode.is_none());
assert!(p.synthesis.legacy_per_pair_classifier.is_none());
assert!(p.kind_class.auto_classify_kind.is_none());
assert!(p.synthesis.synthesis_failure_mode.is_none());
assert!(p.synthesis.synthesis_max_deletes_per_call.is_none());
assert!(p.synthesis.synthesis_max_candidate_chars.is_none());
assert!(p.multistep.multistep_max_content_chars.is_none());
}
#[test]
fn default_for_managed_namespace_tightens_write_to_owner() {
let p = GovernancePolicy::default_for_managed_namespace();
assert_eq!(p.core.write, GovernanceLevel::Owner);
assert!(p.core.inherit);
assert!(p.core.max_reflection_depth.is_none());
assert!(p.atomisation.auto_atomise.is_none());
assert!(p.atomisation.auto_atomise_mode.is_none());
assert!(p.synthesis.synthesis_failure_mode.is_none());
assert!(p.multistep.multistep_max_content_chars.is_none());
}
#[test]
fn effective_max_reflection_depth_defaults_to_three_when_none() {
let p = GovernancePolicy::default();
assert_eq!(p.effective_max_reflection_depth(), 3);
}
#[test]
fn effective_max_reflection_depth_returns_override_when_set() {
let mut p = GovernancePolicy::default();
p.core.max_reflection_depth = Some(7);
assert_eq!(p.effective_max_reflection_depth(), 7);
}
#[test]
fn effective_max_reflection_depth_returns_zero_kill_switch() {
let mut p = GovernancePolicy::default();
p.core.max_reflection_depth = Some(0);
assert_eq!(p.effective_max_reflection_depth(), 0);
}
#[test]
fn effective_auto_export_reflections_to_filesystem_defaults_false() {
let p = GovernancePolicy::default();
assert!(!p.effective_auto_export_reflections_to_filesystem());
}
#[test]
fn effective_auto_export_reflections_to_filesystem_returns_override() {
let mut p = GovernancePolicy::default();
p.export.auto_export_reflections_to_filesystem = Some(true);
assert!(p.effective_auto_export_reflections_to_filesystem());
p.export.auto_export_reflections_to_filesystem = Some(false);
assert!(!p.effective_auto_export_reflections_to_filesystem());
}
#[test]
fn effective_auto_atomise_defaults_false() {
let p = GovernancePolicy::default();
assert!(!p.effective_auto_atomise());
}
#[test]
fn effective_auto_atomise_returns_override() {
let mut p = GovernancePolicy::default();
p.atomisation.auto_atomise = Some(true);
assert!(p.effective_auto_atomise());
}
#[test]
fn effective_auto_atomise_threshold_cl100k_defaults_to_500() {
let p = GovernancePolicy::default();
assert_eq!(p.effective_auto_atomise_threshold_cl100k(), 500);
}
#[test]
fn effective_auto_atomise_threshold_cl100k_returns_override() {
let mut p = GovernancePolicy::default();
p.atomisation.auto_atomise_threshold_cl100k = Some(1000);
assert_eq!(p.effective_auto_atomise_threshold_cl100k(), 1000);
}
#[test]
fn effective_auto_atomise_max_atom_tokens_defaults_to_200() {
let p = GovernancePolicy::default();
assert_eq!(p.effective_auto_atomise_max_atom_tokens(), 200);
}
#[test]
fn effective_auto_atomise_max_atom_tokens_returns_override() {
let mut p = GovernancePolicy::default();
p.atomisation.auto_atomise_max_atom_tokens = Some(50);
assert_eq!(p.effective_auto_atomise_max_atom_tokens(), 50);
}
#[test]
fn effective_auto_atomise_max_retries_returns_none_by_default() {
let p = GovernancePolicy::default();
assert_eq!(p.effective_auto_atomise_max_retries(), None);
}
#[test]
fn effective_auto_atomise_max_retries_returns_override() {
let mut p = GovernancePolicy::default();
p.atomisation.auto_atomise_max_retries = Some(3);
assert_eq!(p.effective_auto_atomise_max_retries(), Some(3));
}
#[test]
fn effective_auto_persona_trigger_returns_none_by_default() {
let p = GovernancePolicy::default();
assert_eq!(p.effective_auto_persona_trigger_every_n_memories(), None);
}
#[test]
fn effective_auto_persona_trigger_returns_override() {
let mut p = GovernancePolicy::default();
p.persona.auto_persona_trigger_every_n_memories = Some(5);
assert_eq!(p.effective_auto_persona_trigger_every_n_memories(), Some(5));
}
#[test]
fn effective_auto_export_personas_to_filesystem_defaults_false() {
let p = GovernancePolicy::default();
assert!(!p.effective_auto_export_personas_to_filesystem());
}
#[test]
fn effective_auto_export_personas_to_filesystem_returns_override() {
let mut p = GovernancePolicy::default();
p.persona.auto_export_personas_to_filesystem = Some(true);
assert!(p.effective_auto_export_personas_to_filesystem());
}
#[test]
fn effective_auto_atomise_mode_off_when_disabled() {
let p = GovernancePolicy::default();
assert_eq!(p.effective_auto_atomise_mode(), AutoAtomiseMode::Off);
}
#[test]
fn effective_auto_atomise_mode_explicit_off_wins_over_enabled_flag() {
let mut p = GovernancePolicy::default();
p.atomisation.auto_atomise = Some(true);
p.atomisation.auto_atomise_mode = Some(AutoAtomiseMode::Off);
assert_eq!(p.effective_auto_atomise_mode(), AutoAtomiseMode::Off);
}
#[test]
fn effective_auto_atomise_mode_legacy_flag_implies_deferred() {
let mut p = GovernancePolicy::default();
p.atomisation.auto_atomise = Some(true);
assert_eq!(p.effective_auto_atomise_mode(), AutoAtomiseMode::Deferred);
}
#[test]
fn effective_auto_atomise_mode_explicit_synchronous() {
let mut p = GovernancePolicy::default();
p.atomisation.auto_atomise = Some(true);
p.atomisation.auto_atomise_mode = Some(AutoAtomiseMode::Synchronous);
assert_eq!(
p.effective_auto_atomise_mode(),
AutoAtomiseMode::Synchronous
);
}
#[test]
fn effective_auto_atomise_mode_explicit_deferred_when_flag_absent() {
let mut p = GovernancePolicy::default();
p.atomisation.auto_atomise_mode = Some(AutoAtomiseMode::Deferred);
assert_eq!(p.effective_auto_atomise_mode(), AutoAtomiseMode::Deferred);
}
#[test]
fn auto_atomise_mode_as_str_labels() {
assert_eq!(AutoAtomiseMode::Off.as_str(), "off");
assert_eq!(AutoAtomiseMode::Deferred.as_str(), "deferred");
assert_eq!(AutoAtomiseMode::Synchronous.as_str(), "synchronous");
}
#[test]
fn effective_legacy_per_pair_classifier_defaults_false() {
let p = GovernancePolicy::default();
assert!(!p.effective_legacy_per_pair_classifier());
}
#[test]
fn effective_legacy_per_pair_classifier_returns_override() {
let mut p = GovernancePolicy::default();
p.synthesis.legacy_per_pair_classifier = Some(true);
assert!(p.effective_legacy_per_pair_classifier());
}
#[test]
fn effective_synthesis_failure_mode_defaults_to_fall_through() {
let p = GovernancePolicy::default();
assert_eq!(
p.effective_synthesis_failure_mode(),
SynthesisFailureMode::FallThrough
);
}
#[test]
fn effective_synthesis_failure_mode_returns_override() {
let mut p = GovernancePolicy::default();
p.synthesis.synthesis_failure_mode = Some(SynthesisFailureMode::BlockWrite);
assert_eq!(
p.effective_synthesis_failure_mode(),
SynthesisFailureMode::BlockWrite
);
}
#[test]
fn synthesis_failure_mode_as_str_labels() {
assert_eq!(SynthesisFailureMode::FallThrough.as_str(), "fall_through");
assert_eq!(SynthesisFailureMode::BlockWrite.as_str(), "block_write");
}
#[test]
fn synthesis_failure_mode_default_is_fall_through() {
let v: SynthesisFailureMode = SynthesisFailureMode::default();
assert_eq!(v, SynthesisFailureMode::FallThrough);
}
#[test]
fn effective_synthesis_max_deletes_per_call_defaults_to_one() {
let p = GovernancePolicy::default();
assert_eq!(p.effective_synthesis_max_deletes_per_call(), 1);
}
#[test]
fn effective_synthesis_max_deletes_per_call_returns_override() {
let mut p = GovernancePolicy::default();
p.synthesis.synthesis_max_deletes_per_call = Some(8);
assert_eq!(p.effective_synthesis_max_deletes_per_call(), 8);
}
#[test]
fn effective_synthesis_max_candidate_chars_defaults_to_1500() {
let p = GovernancePolicy::default();
assert_eq!(p.effective_synthesis_max_candidate_chars(), 1500);
}
#[test]
fn effective_synthesis_max_candidate_chars_returns_override() {
let mut p = GovernancePolicy::default();
p.synthesis.synthesis_max_candidate_chars = Some(2_500);
assert_eq!(p.effective_synthesis_max_candidate_chars(), 2_500);
}
#[test]
fn effective_multistep_max_content_chars_defaults_to_1500() {
let p = GovernancePolicy::default();
assert_eq!(p.effective_multistep_max_content_chars(), 1500);
}
#[test]
fn effective_multistep_max_content_chars_returns_override() {
let mut p = GovernancePolicy::default();
p.multistep.multistep_max_content_chars = Some(3_000);
assert_eq!(p.effective_multistep_max_content_chars(), 3_000);
}
#[test]
fn memory_kind_auto_classify_default_is_off() {
let v: MemoryKindAutoClassify = MemoryKindAutoClassify::default();
assert_eq!(v, MemoryKindAutoClassify::Off);
}
#[test]
fn memory_kind_auto_classify_serde_round_trip() {
for v in [
MemoryKindAutoClassify::Off,
MemoryKindAutoClassify::RegexOnly,
MemoryKindAutoClassify::RegexThenLlm,
] {
let s = serde_json::to_value(v).unwrap();
let back: MemoryKindAutoClassify = serde_json::from_value(s).unwrap();
assert_eq!(back, v);
}
}
#[test]
fn auto_atomise_mode_serde_round_trip() {
for v in [
AutoAtomiseMode::Off,
AutoAtomiseMode::Deferred,
AutoAtomiseMode::Synchronous,
] {
let s = serde_json::to_value(v).unwrap();
let back: AutoAtomiseMode = serde_json::from_value(s).unwrap();
assert_eq!(back, v);
}
}
#[test]
fn synthesis_failure_mode_serde_round_trip() {
for v in [
SynthesisFailureMode::FallThrough,
SynthesisFailureMode::BlockWrite,
] {
let s = serde_json::to_value(v).unwrap();
let back: SynthesisFailureMode = serde_json::from_value(s).unwrap();
assert_eq!(back, v);
}
}
#[test]
fn governance_policy_serde_round_trip_with_all_v070_fields() {
let mut p = GovernancePolicy::default();
p.core.max_reflection_depth = Some(5);
p.atomisation.auto_atomise = Some(true);
p.atomisation.auto_atomise_mode = Some(AutoAtomiseMode::Synchronous);
p.atomisation.auto_atomise_threshold_cl100k = Some(750);
p.atomisation.auto_atomise_max_atom_tokens = Some(150);
p.atomisation.auto_atomise_max_retries = Some(2);
p.persona.auto_persona_trigger_every_n_memories = Some(10);
p.persona.auto_export_personas_to_filesystem = Some(true);
p.export.auto_export_reflections_to_filesystem = Some(true);
p.synthesis.legacy_per_pair_classifier = Some(false);
p.kind_class.auto_classify_kind = Some(MemoryKindAutoClassify::RegexOnly);
p.synthesis.synthesis_failure_mode = Some(SynthesisFailureMode::BlockWrite);
p.synthesis.synthesis_max_deletes_per_call = Some(4);
p.synthesis.synthesis_max_candidate_chars = Some(2_000);
p.multistep.multistep_max_content_chars = Some(3_000);
let v = serde_json::to_value(&p).unwrap();
let back: GovernancePolicy = serde_json::from_value(v).unwrap();
assert_eq!(back.core.max_reflection_depth, Some(5));
assert_eq!(
back.atomisation.auto_atomise_mode,
Some(AutoAtomiseMode::Synchronous)
);
assert_eq!(back.atomisation.auto_atomise_threshold_cl100k, Some(750));
assert_eq!(back.persona.auto_persona_trigger_every_n_memories, Some(10));
assert_eq!(
back.synthesis.synthesis_failure_mode,
Some(SynthesisFailureMode::BlockWrite)
);
assert_eq!(back.synthesis.synthesis_max_deletes_per_call, Some(4));
assert_eq!(back.multistep.multistep_max_content_chars, Some(3_000));
}
#[test]
fn from_metadata_returns_none_when_governance_key_absent() {
let meta = json!({"unrelated": 42});
assert!(GovernancePolicy::from_metadata(&meta).is_none());
}
#[test]
fn from_metadata_returns_none_when_governance_key_is_null() {
let meta = json!({"governance": null});
assert!(GovernancePolicy::from_metadata(&meta).is_none());
}
#[test]
fn from_metadata_parses_governance_blob() {
let meta = json!({
"governance": {
"write": "owner",
"max_reflection_depth": 4,
},
});
let parsed = GovernancePolicy::from_metadata(&meta).unwrap().unwrap();
assert_eq!(parsed.core.write, GovernanceLevel::Owner);
assert_eq!(parsed.core.max_reflection_depth, Some(4));
}
#[test]
fn from_metadata_propagates_parse_error_for_malformed_payload() {
let meta = json!({"governance": {"write": 42}});
let res = GovernancePolicy::from_metadata(&meta).unwrap();
assert!(res.is_err());
}
use super::MemoryScope;
use std::str::FromStr;
#[test]
fn memory_scope_inherent_from_str_known_variants() {
assert_eq!(MemoryScope::from_str("private"), Some(MemoryScope::Private));
assert_eq!(MemoryScope::from_str("team"), Some(MemoryScope::Team));
assert_eq!(MemoryScope::from_str("unit"), Some(MemoryScope::Unit));
assert_eq!(MemoryScope::from_str("org"), Some(MemoryScope::Org));
assert_eq!(
MemoryScope::from_str("collective"),
Some(MemoryScope::Collective)
);
}
#[test]
fn memory_scope_inherent_from_str_unknown_returns_none() {
assert_eq!(MemoryScope::from_str("bogus"), None);
assert_eq!(MemoryScope::from_str(""), None);
assert_eq!(MemoryScope::from_str("Private"), None); }
#[test]
fn memory_scope_as_str_canonical_strings() {
assert_eq!(MemoryScope::Private.as_str(), "private");
assert_eq!(MemoryScope::Team.as_str(), "team");
assert_eq!(MemoryScope::Unit.as_str(), "unit");
assert_eq!(MemoryScope::Org.as_str(), "org");
assert_eq!(MemoryScope::Collective.as_str(), "collective");
}
#[test]
fn memory_scope_display_matches_as_str() {
assert_eq!(format!("{}", MemoryScope::Private), "private");
assert_eq!(format!("{}", MemoryScope::Collective), "collective");
}
#[test]
fn memory_scope_default_is_private() {
assert_eq!(MemoryScope::default(), MemoryScope::Private);
}
#[test]
fn memory_scope_fromstr_trait_round_trips_known() {
assert_eq!(
<MemoryScope as FromStr>::from_str("team").unwrap(),
MemoryScope::Team
);
}
#[test]
fn memory_scope_fromstr_trait_error_message_lists_valid_scopes() {
let err = <MemoryScope as FromStr>::from_str("unknown_scope")
.expect_err("unknown scope must error");
assert!(
err.contains("'unknown_scope'"),
"error names the input: {err}"
);
assert!(err.contains("private"), "error lists private: {err}");
assert!(err.contains("collective"), "error lists collective: {err}");
}
#[test]
fn memory_scope_all_strs_matches_valid_scopes_const() {
let enum_strs: &[&str] = MemoryScope::all_strs();
assert_eq!(enum_strs, VALID_SCOPES, "all_strs must match VALID_SCOPES");
}
#[test]
fn memory_scope_all_round_trips_through_serde() {
for variant in MemoryScope::all() {
let json = serde_json::to_string(variant).unwrap();
let parsed: MemoryScope = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, *variant, "serde round-trip for {variant:?}");
}
}
}