use schemars::JsonSchema;
use semantic_memory::{MemoryStore, ProjectionClaimVersion, ProjectionQuery};
use serde::{Deserialize, Serialize};
use stack_ids::ScopeKey;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum GovernanceMode {
#[default]
FailOpen,
Strict,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, thiserror::Error)]
pub enum GovernanceGateError {
#[error("governance observation failed in strict mode: {reason}")]
ObservationFailed { reason: String },
#[error("no governance claims found in strict mode")]
NoGovernanceClaims,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct GovernanceObservation {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub effect_preflight_status: Option<String>,
#[serde(default)]
pub assurance_ready: bool,
#[serde(default)]
pub authority_delegation_valid: bool,
#[serde(default)]
pub continuity_incident_active: bool,
#[serde(default)]
pub constitutional_amendment_pending: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mechanism_fit_disposition: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub governance_degradations: Vec<GovernanceDegradation>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct GovernanceDegradation {
pub surface: String,
pub reason: String,
pub blocks_promotion: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum GovernanceGateResult {
Allow,
AdvisoryOnly { reason: String },
Blocked { reason: String },
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct GovernanceReceiptV1 {
pub schema_version: String,
pub gate_result: GovernanceGateResult,
pub observation_summary: GovernanceObservationSummary,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct GovernanceObservationSummary {
pub effect_preflight_present: bool,
pub assurance_ready: bool,
pub authority_delegation_valid: bool,
pub continuity_incident_active: bool,
pub constitutional_amendment_pending: bool,
pub mechanism_fit_present: bool,
pub degradation_count: usize,
}
pub const GOVERNANCE_RECEIPT_V1_SCHEMA: &str = "governance_receipt_v1";
pub const GOVERNANCE_PROJECTION_FAMILY: &str = "governance";
pub const GOVERNANCE_SCOPE_NAMESPACE: &str = "governance";
pub mod predicates {
pub const EFFECT_PREFLIGHT: &str = "effect_preflight_status";
pub const ASSURANCE_READY: &str = "assurance_ready";
pub const AUTHORITY_DELEGATION_VALID: &str = "authority_chain_validity";
pub const CONTINUITY_INCIDENT_ACTIVE: &str = "continuity_incident_active";
pub const CONSTITUTIONAL_AMENDMENT_PENDING: &str = "constitutional_amendment_pending";
pub const MECHANISM_FIT: &str = "mechanism_fit_disposition";
}
pub async fn observe_governance(store: &MemoryStore) -> GovernanceObservation {
match observe_governance_inner(store).await {
Ok(obs) => obs,
Err(err) => {
tracing::warn!(
error = %err,
"governance observation failed, returning default (fail-open)"
);
GovernanceObservation::default()
}
}
}
pub async fn observe_governance_with_mode(
store: &MemoryStore,
mode: GovernanceMode,
) -> Result<GovernanceObservation, GovernanceGateError> {
match observe_governance_inner(store).await {
Ok(obs) => {
if mode == GovernanceMode::Strict && is_empty_observation(&obs) {
tracing::warn!(
"strict governance mode: no governance claims found, failing closed"
);
return Err(GovernanceGateError::NoGovernanceClaims);
}
Ok(obs)
}
Err(err) => match mode {
GovernanceMode::FailOpen => {
tracing::warn!(
error = %err,
"governance observation failed, returning default (fail-open)"
);
Ok(GovernanceObservation::default())
}
GovernanceMode::Strict => {
tracing::error!(
error = %err,
"governance observation failed in strict mode, failing closed"
);
Err(GovernanceGateError::ObservationFailed {
reason: err.to_string(),
})
}
},
}
}
fn is_empty_observation(obs: &GovernanceObservation) -> bool {
obs.effect_preflight_status.is_none()
&& !obs.assurance_ready
&& !obs.authority_delegation_valid
&& !obs.continuity_incident_active
&& !obs.constitutional_amendment_pending
&& obs.mechanism_fit_disposition.is_none()
&& obs.governance_degradations.is_empty()
}
async fn observe_governance_inner(
store: &MemoryStore,
) -> Result<GovernanceObservation, semantic_memory::MemoryError> {
let query = ProjectionQuery {
scope: ScopeKey {
namespace: GOVERNANCE_SCOPE_NAMESPACE.to_string(),
domain: None,
workspace_id: None,
repo_id: None,
},
text_query: None,
valid_at: None,
recorded_at_or_before: None,
subject_entity_id: None,
canonical_entity_id: None,
claim_state: Some("active".to_string()),
claim_id: None,
claim_version_id: None,
limit: 100,
};
let claims = store.query_claim_versions(query).await?;
if claims.is_empty() {
tracing::debug!("no governance claims found in scope, returning default observation");
return Ok(GovernanceObservation::default());
}
let gov_claims: Vec<&ProjectionClaimVersion> = claims
.iter()
.filter(|c| c.projection_family == GOVERNANCE_PROJECTION_FAMILY)
.collect();
if gov_claims.is_empty() {
tracing::debug!(
total_claims = claims.len(),
"claims found but none in governance projection family"
);
return Ok(GovernanceObservation::default());
}
let mut observation = GovernanceObservation::default();
let mut degradations = Vec::new();
for claim in &gov_claims {
match claim.predicate.as_str() {
predicates::EFFECT_PREFLIGHT => {
observation.effect_preflight_status = Some(claim.content.clone());
}
predicates::ASSURANCE_READY => {
observation.assurance_ready = parse_bool_claim(&claim.content);
}
predicates::AUTHORITY_DELEGATION_VALID => {
observation.authority_delegation_valid = parse_bool_claim(&claim.content);
}
predicates::CONTINUITY_INCIDENT_ACTIVE => {
observation.continuity_incident_active = parse_bool_claim(&claim.content);
}
predicates::CONSTITUTIONAL_AMENDMENT_PENDING => {
observation.constitutional_amendment_pending = parse_bool_claim(&claim.content);
}
predicates::MECHANISM_FIT => {
observation.mechanism_fit_disposition = Some(claim.content.clone());
}
other => {
tracing::trace!(
predicate = other,
"unrecognized governance predicate, skipping"
);
}
}
if claim.freshness != "current" || claim.contradiction_status != "none" {
degradations.push(GovernanceDegradation {
surface: claim.predicate.clone(),
reason: format!(
"freshness={}, contradiction={}",
claim.freshness, claim.contradiction_status
),
blocks_promotion: claim.freshness == "superseded"
|| claim.contradiction_status != "none",
});
}
}
observation.governance_degradations = degradations;
tracing::debug!(
claim_count = gov_claims.len(),
effect_preflight = observation.effect_preflight_status.is_some(),
assurance_ready = observation.assurance_ready,
authority_valid = observation.authority_delegation_valid,
incident_active = observation.continuity_incident_active,
amendment_pending = observation.constitutional_amendment_pending,
mechanism_fit = observation.mechanism_fit_disposition.is_some(),
degradation_count = observation.governance_degradations.len(),
"governance observation populated from semantic-memory projections"
);
Ok(observation)
}
fn parse_bool_claim(content: &str) -> bool {
matches!(content.trim().to_lowercase().as_str(), "true" | "1" | "yes")
}
pub fn gate_execution(observation: &GovernanceObservation) -> GovernanceGateResult {
if observation.continuity_incident_active {
return GovernanceGateResult::Blocked {
reason: "continuity incident is active".into(),
};
}
if observation.effect_preflight_status.is_some() && !observation.authority_delegation_valid {
return GovernanceGateResult::Blocked {
reason: "authority delegation chain is not valid".into(),
};
}
if observation.constitutional_amendment_pending {
return GovernanceGateResult::AdvisoryOnly {
reason: "constitutional amendment is pending".into(),
};
}
if observation
.governance_degradations
.iter()
.any(|d| d.blocks_promotion)
{
return GovernanceGateResult::AdvisoryOnly {
reason: "governance degradation blocks promotion".into(),
};
}
GovernanceGateResult::Allow
}
pub fn gate_execution_with_mode(
observation: &GovernanceObservation,
mode: GovernanceMode,
) -> Result<GovernanceGateResult, GovernanceGateError> {
let result = gate_execution(observation);
match (&result, mode) {
(GovernanceGateResult::Blocked { reason }, GovernanceMode::Strict) => {
Err(GovernanceGateError::ObservationFailed {
reason: format!("governance blocked in strict mode: {reason}"),
})
}
_ => Ok(result),
}
}
pub fn build_governance_receipt(
observation: &GovernanceObservation,
gate_result: &GovernanceGateResult,
) -> GovernanceReceiptV1 {
GovernanceReceiptV1 {
schema_version: GOVERNANCE_RECEIPT_V1_SCHEMA.into(),
gate_result: gate_result.clone(),
observation_summary: GovernanceObservationSummary {
effect_preflight_present: observation.effect_preflight_status.is_some(),
assurance_ready: observation.assurance_ready,
authority_delegation_valid: observation.authority_delegation_valid,
continuity_incident_active: observation.continuity_incident_active,
constitutional_amendment_pending: observation.constitutional_amendment_pending,
mechanism_fit_present: observation.mechanism_fit_disposition.is_some(),
degradation_count: observation.governance_degradations.len(),
},
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_observation_allows_execution() {
let obs = GovernanceObservation::default();
let result = gate_execution(&obs);
assert_eq!(result, GovernanceGateResult::Allow);
}
#[test]
fn active_incident_blocks_execution() {
let obs = GovernanceObservation {
continuity_incident_active: true,
..Default::default()
};
let result = gate_execution(&obs);
assert!(matches!(result, GovernanceGateResult::Blocked { .. }));
}
#[test]
fn pending_amendment_downgrades_to_advisory() {
let obs = GovernanceObservation {
constitutional_amendment_pending: true,
..Default::default()
};
let result = gate_execution(&obs);
assert!(matches!(result, GovernanceGateResult::AdvisoryOnly { .. }));
}
#[test]
fn governance_receipt_roundtrip() {
let obs = GovernanceObservation::default();
let gate = gate_execution(&obs);
let receipt = build_governance_receipt(&obs, &gate);
assert_eq!(receipt.schema_version, GOVERNANCE_RECEIPT_V1_SCHEMA);
let json = serde_json::to_string(&receipt).unwrap();
let roundtrip: GovernanceReceiptV1 = serde_json::from_str(&json).unwrap();
assert_eq!(roundtrip.schema_version, GOVERNANCE_RECEIPT_V1_SCHEMA);
}
#[test]
fn parse_bool_claim_values() {
assert!(parse_bool_claim("true"));
assert!(parse_bool_claim("True"));
assert!(parse_bool_claim("TRUE"));
assert!(parse_bool_claim("1"));
assert!(parse_bool_claim("yes"));
assert!(parse_bool_claim(" true "));
assert!(!parse_bool_claim("false"));
assert!(!parse_bool_claim("0"));
assert!(!parse_bool_claim("no"));
assert!(!parse_bool_claim(""));
assert!(!parse_bool_claim("unknown"));
}
#[test]
fn degradation_blocks_promotion_downgrades_to_advisory() {
let obs = GovernanceObservation {
governance_degradations: vec![GovernanceDegradation {
surface: "effect_preflight_status".into(),
reason: "freshness=superseded, contradiction=none".into(),
blocks_promotion: true,
}],
..Default::default()
};
let result = gate_execution(&obs);
assert!(matches!(result, GovernanceGateResult::AdvisoryOnly { .. }));
}
#[test]
fn non_blocking_degradation_allows_execution() {
let obs = GovernanceObservation {
governance_degradations: vec![GovernanceDegradation {
surface: "mechanism_fit_disposition".into(),
reason: "freshness=stale, contradiction=none".into(),
blocks_promotion: false,
}],
..Default::default()
};
let result = gate_execution(&obs);
assert_eq!(result, GovernanceGateResult::Allow);
}
#[test]
fn invalid_authority_without_preflight_allows() {
let obs = GovernanceObservation {
authority_delegation_valid: false,
..Default::default()
};
let result = gate_execution(&obs);
assert_eq!(result, GovernanceGateResult::Allow);
}
#[test]
fn invalid_authority_with_preflight_blocks() {
let obs = GovernanceObservation {
effect_preflight_status: Some("commit_eligible".into()),
authority_delegation_valid: false,
..Default::default()
};
let result = gate_execution(&obs);
assert!(matches!(result, GovernanceGateResult::Blocked { .. }));
}
#[tokio::test]
async fn observe_governance_empty_store_returns_default() {
let dir = tempfile::tempdir().expect("create temp dir");
let config = semantic_memory::MemoryConfig {
base_dir: dir.path().to_path_buf(),
..Default::default()
};
let store = MemoryStore::open(config).expect("open store");
let obs = observe_governance(&store).await;
assert_eq!(obs.effect_preflight_status, None);
assert!(!obs.assurance_ready);
assert!(!obs.authority_delegation_valid);
assert!(!obs.continuity_incident_active);
assert!(!obs.constitutional_amendment_pending);
assert_eq!(obs.mechanism_fit_disposition, None);
assert!(obs.governance_degradations.is_empty());
assert_eq!(gate_execution(&obs), GovernanceGateResult::Allow);
}
#[tokio::test]
async fn observe_governance_reads_real_artifacts() {
let dir = tempfile::tempdir().expect("create temp dir");
let config = semantic_memory::MemoryConfig {
base_dir: dir.path().to_path_buf(),
..Default::default()
};
let store = MemoryStore::open(config).expect("open store");
insert_governance_claim(&store, predicates::EFFECT_PREFLIGHT, "commit_eligible").await;
insert_governance_claim(&store, predicates::ASSURANCE_READY, "true").await;
insert_governance_claim(&store, predicates::AUTHORITY_DELEGATION_VALID, "true").await;
insert_governance_claim(&store, predicates::CONTINUITY_INCIDENT_ACTIVE, "false").await;
insert_governance_claim(&store, predicates::CONSTITUTIONAL_AMENDMENT_PENDING, "true").await;
insert_governance_claim(
&store,
predicates::MECHANISM_FIT,
"eligible_for_local_review",
)
.await;
let obs = observe_governance(&store).await;
assert_eq!(
obs.effect_preflight_status.as_deref(),
Some("commit_eligible")
);
assert!(obs.assurance_ready);
assert!(obs.authority_delegation_valid);
assert!(!obs.continuity_incident_active);
assert!(obs.constitutional_amendment_pending);
assert_eq!(
obs.mechanism_fit_disposition.as_deref(),
Some("eligible_for_local_review")
);
let gate = gate_execution(&obs);
assert!(
matches!(gate, GovernanceGateResult::AdvisoryOnly { .. }),
"expected AdvisoryOnly due to pending amendment, got: {:?}",
gate
);
let receipt = build_governance_receipt(&obs, &gate);
assert!(receipt.observation_summary.effect_preflight_present);
assert!(receipt.observation_summary.assurance_ready);
assert!(receipt.observation_summary.authority_delegation_valid);
assert!(!receipt.observation_summary.continuity_incident_active);
assert!(receipt.observation_summary.constitutional_amendment_pending);
assert!(receipt.observation_summary.mechanism_fit_present);
}
#[tokio::test]
async fn observe_governance_active_incident_blocks() {
let dir = tempfile::tempdir().expect("create temp dir");
let config = semantic_memory::MemoryConfig {
base_dir: dir.path().to_path_buf(),
..Default::default()
};
let store = MemoryStore::open(config).expect("open store");
insert_governance_claim(&store, predicates::CONTINUITY_INCIDENT_ACTIVE, "true").await;
let obs = observe_governance(&store).await;
assert!(obs.continuity_incident_active);
let gate = gate_execution(&obs);
assert!(
matches!(gate, GovernanceGateResult::Blocked { .. }),
"expected Blocked due to active incident, got: {:?}",
gate
);
}
#[test]
fn strict_mode_blocks_on_blocked_gate_result() {
let obs = GovernanceObservation {
continuity_incident_active: true,
..Default::default()
};
let result = gate_execution_with_mode(&obs, GovernanceMode::Strict);
assert!(result.is_err(), "strict mode should error on blocked gate");
}
#[test]
fn strict_mode_allows_on_allow_gate_result() {
let obs = GovernanceObservation {
assurance_ready: true,
authority_delegation_valid: true,
..Default::default()
};
let result = gate_execution_with_mode(&obs, GovernanceMode::Strict);
assert!(result.is_ok());
assert_eq!(result.unwrap(), GovernanceGateResult::Allow);
}
#[test]
fn strict_mode_allows_advisory_only() {
let obs = GovernanceObservation {
constitutional_amendment_pending: true,
..Default::default()
};
let result = gate_execution_with_mode(&obs, GovernanceMode::Strict);
assert!(result.is_ok());
assert!(matches!(
result.unwrap(),
GovernanceGateResult::AdvisoryOnly { .. }
));
}
#[test]
fn fail_open_mode_returns_blocked_without_error() {
let obs = GovernanceObservation {
continuity_incident_active: true,
..Default::default()
};
let result = gate_execution_with_mode(&obs, GovernanceMode::FailOpen);
assert!(result.is_ok());
assert!(matches!(
result.unwrap(),
GovernanceGateResult::Blocked { .. }
));
}
#[tokio::test]
async fn strict_mode_errors_on_empty_store() {
let dir = tempfile::tempdir().expect("create temp dir");
let config = semantic_memory::MemoryConfig {
base_dir: dir.path().to_path_buf(),
..Default::default()
};
let store = MemoryStore::open(config).expect("open store");
let result = observe_governance_with_mode(&store, GovernanceMode::Strict).await;
assert!(
result.is_err(),
"strict mode should error when no governance claims exist"
);
assert!(matches!(
result.unwrap_err(),
GovernanceGateError::NoGovernanceClaims
));
}
#[tokio::test]
async fn strict_mode_allows_populated_store() {
let dir = tempfile::tempdir().expect("create temp dir");
let config = semantic_memory::MemoryConfig {
base_dir: dir.path().to_path_buf(),
..Default::default()
};
let store = MemoryStore::open(config).expect("open store");
insert_governance_claim(&store, predicates::ASSURANCE_READY, "true").await;
let result = observe_governance_with_mode(&store, GovernanceMode::Strict).await;
assert!(
result.is_ok(),
"strict mode should succeed with governance claims present"
);
let obs = result.unwrap();
assert!(obs.assurance_ready);
}
#[tokio::test]
async fn fail_open_mode_returns_default_on_empty_store() {
let dir = tempfile::tempdir().expect("create temp dir");
let config = semantic_memory::MemoryConfig {
base_dir: dir.path().to_path_buf(),
..Default::default()
};
let store = MemoryStore::open(config).expect("open store");
let result = observe_governance_with_mode(&store, GovernanceMode::FailOpen).await;
assert!(result.is_ok(), "fail-open mode should not error");
assert_eq!(
gate_execution(&result.unwrap()),
GovernanceGateResult::Allow
);
}
async fn insert_governance_claim(store: &MemoryStore, predicate: &str, content: &str) {
let id = uuid::Uuid::new_v4().to_string();
let claim_id = format!("gov-claim-{}", predicate);
let sql = format!(
"INSERT INTO claim_versions (
claim_version_id, claim_id, claim_state, projection_family,
subject_entity_id, predicate, object_anchor,
scope_namespace, scope_domain, scope_workspace_id, scope_repo_id,
recorded_at, preferred_open,
source_envelope_id, source_authority,
freshness, contradiction_status, content, confidence
) VALUES (
'{}', '{}', 'active', '{}',
'governance-entity', '{}', '\"{}\"',
'{}', NULL, NULL, NULL,
datetime('now'), 0,
'gov-envelope-{}', 'governance',
'current', 'none', '{}', 1.0
)",
id,
claim_id,
GOVERNANCE_PROJECTION_FAMILY,
predicate,
content,
GOVERNANCE_SCOPE_NAMESPACE,
predicate,
content,
);
store
.raw_execute(&sql, vec![])
.await
.expect("insert governance claim");
}
}