use serde::{Deserialize, Serialize};
use crate::airlock::AirlockMode;
use crate::decision::PolicyDecision;
use crate::reversibility::ResponseLevel;
pub const COLORADO_SB26_189_ANCHOR: &str = "colorado-sb26-189-admt";
pub const RETENTION_FLOOR_YEARS: i64 = 3;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ConsequentialDomain {
Education,
Employment,
Housing,
FinancialOrLending,
HealthCare,
Insurance,
LegalOrGovernmentServices,
Other,
}
impl ConsequentialDomain {
pub fn as_str(self) -> &'static str {
match self {
ConsequentialDomain::Education => "education",
ConsequentialDomain::Employment => "employment",
ConsequentialDomain::Housing => "housing",
ConsequentialDomain::FinancialOrLending => "financial_or_lending",
ConsequentialDomain::HealthCare => "health_care",
ConsequentialDomain::Insurance => "insurance",
ConsequentialDomain::LegalOrGovernmentServices => "legal_or_government_services",
ConsequentialDomain::Other => "other",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AutomationRole {
HumanOnly,
AdmtAssisted,
AdmtMaterial,
}
impl AutomationRole {
pub fn as_str(self) -> &'static str {
match self {
AutomationRole::HumanOnly => "human_only",
AutomationRole::AdmtAssisted => "admt_assisted",
AutomationRole::AdmtMaterial => "admt_material",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct ColoradoAdmtConfig {
pub treat_assist_as_covered: bool,
}
impl Default for ColoradoAdmtConfig {
fn default() -> Self {
Self {
treat_assist_as_covered: true,
}
}
}
impl ColoradoAdmtConfig {
pub fn from_json(value: &serde_json::Value) -> Self {
let d = Self::default();
Self {
treat_assist_as_covered: value
.get("treat_assist_as_covered")
.and_then(serde_json::Value::as_bool)
.unwrap_or(d.treat_assist_as_covered),
}
}
}
#[derive(Debug, Clone)]
pub struct AdmtDecisionContext {
pub domain: Option<ConsequentialDomain>,
pub automation: AutomationRole,
pub disclosed: bool,
}
impl AdmtDecisionContext {
pub fn is_covered(&self, cfg: ColoradoAdmtConfig) -> bool {
if self.domain.is_none() {
return false;
}
match self.automation {
AutomationRole::HumanOnly => false,
AutomationRole::AdmtMaterial => true,
AutomationRole::AdmtAssisted => cfg.treat_assist_as_covered,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ColoradoAdmtStatus {
Exempt,
CompliantDisclosed,
MissingDisclosure,
}
impl ColoradoAdmtStatus {
pub fn as_str(self) -> &'static str {
match self {
ColoradoAdmtStatus::Exempt => "exempt",
ColoradoAdmtStatus::CompliantDisclosed => "compliant_disclosed",
ColoradoAdmtStatus::MissingDisclosure => "missing_disclosure",
}
}
pub fn is_covered(self) -> bool {
matches!(
self,
ColoradoAdmtStatus::CompliantDisclosed | ColoradoAdmtStatus::MissingDisclosure
)
}
pub fn is_disclosed(self) -> bool {
matches!(self, ColoradoAdmtStatus::CompliantDisclosed)
}
pub fn disclosure_required(self) -> bool {
self.is_covered()
}
}
pub fn check(ctx: &AdmtDecisionContext, cfg: ColoradoAdmtConfig) -> ColoradoAdmtStatus {
if !ctx.is_covered(cfg) {
return ColoradoAdmtStatus::Exempt;
}
if ctx.disclosed {
ColoradoAdmtStatus::CompliantDisclosed
} else {
ColoradoAdmtStatus::MissingDisclosure
}
}
pub fn response_level(status: ColoradoAdmtStatus, mode: AirlockMode) -> ResponseLevel {
match status {
ColoradoAdmtStatus::MissingDisclosure if mode == AirlockMode::Enforce => {
ResponseLevel::RequireApproval
}
_ => ResponseLevel::AllowAndLog,
}
}
pub fn enforce(status: ColoradoAdmtStatus, mode: AirlockMode) -> PolicyDecision {
match status {
ColoradoAdmtStatus::Exempt => PolicyDecision::allow(format!(
"colorado_sb26_189: decision is not a covered ADMT consequential decision [{COLORADO_SB26_189_ANCHOR}]"
)),
ColoradoAdmtStatus::CompliantDisclosed => PolicyDecision::allow(format!(
"colorado_sb26_189: covered ADMT consequential decision carries the required disclosure [{COLORADO_SB26_189_ANCHOR}]"
)),
ColoradoAdmtStatus::MissingDisclosure => {
let reason = format!(
"colorado_sb26_189: covered ADMT consequential decision is missing the required disclosure [{COLORADO_SB26_189_ANCHOR}]"
);
match mode {
AirlockMode::Enforce => PolicyDecision::requires_approval(reason),
AirlockMode::Shadow => PolicyDecision::allow(format!("shadow: {reason}")),
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ColoradoAdmtRecord {
pub system_id: String,
pub decided_at: String,
pub input_summary: serde_json::Value,
pub outcome: String,
pub domain: ConsequentialDomain,
pub automation: AutomationRole,
pub disclosed: bool,
pub status: ColoradoAdmtStatus,
pub retention_floor_years: i64,
}
impl ColoradoAdmtRecord {
pub fn new(
ctx: &AdmtDecisionContext,
status: ColoradoAdmtStatus,
system_id: impl Into<String>,
decided_at: impl Into<String>,
input_summary: serde_json::Value,
outcome: impl Into<String>,
) -> Self {
Self {
system_id: system_id.into(),
decided_at: decided_at.into(),
input_summary,
outcome: outcome.into(),
domain: ctx.domain.unwrap_or(ConsequentialDomain::Other),
automation: ctx.automation,
disclosed: ctx.disclosed,
status,
retention_floor_years: RETENTION_FLOOR_YEARS,
}
}
pub fn to_audit_details(&self) -> serde_json::Value {
let mut v = serde_json::to_value(self).unwrap_or(serde_json::Value::Null);
if let Some(obj) = v.as_object_mut() {
obj.insert(
"anchor".to_string(),
serde_json::Value::String(COLORADO_SB26_189_ANCHOR.to_string()),
);
}
v
}
pub fn from_audit_details(details: &serde_json::Value) -> Option<Self> {
serde_json::from_value(details.clone()).ok()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn consequential_material(disclosed: bool) -> AdmtDecisionContext {
AdmtDecisionContext {
domain: Some(ConsequentialDomain::Employment),
automation: AutomationRole::AdmtMaterial,
disclosed,
}
}
#[test]
fn fires_on_consequential_admt_decision_without_disclosure() {
let ctx = consequential_material(false);
let status = check(&ctx, ColoradoAdmtConfig::default());
assert_eq!(status, ColoradoAdmtStatus::MissingDisclosure);
assert!(status.is_covered());
assert!(status.disclosure_required());
let d = enforce(status, AirlockMode::Enforce);
assert!(d.needs_approval());
assert!(d.reason.contains(COLORADO_SB26_189_ANCHOR));
assert_eq!(
response_level(status, AirlockMode::Enforce),
ResponseLevel::RequireApproval
);
assert_eq!(response_level(status, AirlockMode::Enforce).rung(), "R3");
}
#[test]
fn covered_and_disclosed_is_compliant() {
let ctx = consequential_material(true);
let status = check(&ctx, ColoradoAdmtConfig::default());
assert_eq!(status, ColoradoAdmtStatus::CompliantDisclosed);
assert!(status.is_covered());
assert!(status.is_disclosed());
assert!(enforce(status, AirlockMode::Enforce).is_allowed());
}
#[test]
fn non_consequential_decision_does_not_trip_the_rule() {
let ctx = AdmtDecisionContext {
domain: None,
automation: AutomationRole::AdmtMaterial,
disclosed: false,
};
let status = check(&ctx, ColoradoAdmtConfig::default());
assert_eq!(status, ColoradoAdmtStatus::Exempt);
assert!(!status.is_covered());
assert!(!status.disclosure_required());
for mode in [AirlockMode::Enforce, AirlockMode::Shadow] {
assert!(enforce(status, mode).is_allowed());
assert_eq!(response_level(status, mode), ResponseLevel::AllowAndLog);
}
}
#[test]
fn human_only_consequential_decision_is_exempt() {
let ctx = AdmtDecisionContext {
domain: Some(ConsequentialDomain::Housing),
automation: AutomationRole::HumanOnly,
disclosed: false,
};
assert_eq!(
check(&ctx, ColoradoAdmtConfig::default()),
ColoradoAdmtStatus::Exempt
);
}
#[test]
fn assist_is_covered_by_default_but_configurable() {
let ctx = AdmtDecisionContext {
domain: Some(ConsequentialDomain::FinancialOrLending),
automation: AutomationRole::AdmtAssisted,
disclosed: false,
};
assert_eq!(
check(&ctx, ColoradoAdmtConfig::default()),
ColoradoAdmtStatus::MissingDisclosure
);
let narrow = ColoradoAdmtConfig {
treat_assist_as_covered: false,
};
assert_eq!(check(&ctx, narrow), ColoradoAdmtStatus::Exempt);
}
#[test]
fn shadow_mode_allows_but_logs_missing_disclosure() {
let d = enforce(ColoradoAdmtStatus::MissingDisclosure, AirlockMode::Shadow);
assert!(d.is_allowed());
assert!(d.reason.starts_with("shadow:"));
assert_eq!(
response_level(ColoradoAdmtStatus::MissingDisclosure, AirlockMode::Shadow),
ResponseLevel::AllowAndLog
);
}
#[test]
fn config_from_json_defaults_conservative() {
let cfg = ColoradoAdmtConfig::from_json(&serde_json::json!({}));
assert!(cfg.treat_assist_as_covered);
let narrowed =
ColoradoAdmtConfig::from_json(&serde_json::json!({"treat_assist_as_covered": false}));
assert!(!narrowed.treat_assist_as_covered);
}
#[test]
fn record_round_trips_through_audit_details() {
let ctx = consequential_material(false);
let status = check(&ctx, ColoradoAdmtConfig::default());
let rec = ColoradoAdmtRecord::new(
&ctx,
status,
"agt_01hxmodel/v3",
"2026-07-19T12:00:00Z",
serde_json::json!({"features": ["tenure_months", "region"]}),
"denied",
);
assert_eq!(rec.retention_floor_years, RETENTION_FLOOR_YEARS);
let details = rec.to_audit_details();
assert_eq!(details["anchor"], COLORADO_SB26_189_ANCHOR);
assert_eq!(details["status"], "missing_disclosure");
assert_eq!(details["domain"], "employment");
let back = ColoradoAdmtRecord::from_audit_details(&details).expect("valid record");
assert_eq!(back, rec);
}
#[test]
fn status_wire_labels_round_trip() {
for s in [
ColoradoAdmtStatus::Exempt,
ColoradoAdmtStatus::CompliantDisclosed,
ColoradoAdmtStatus::MissingDisclosure,
] {
let json = serde_json::to_string(&s).unwrap();
assert_eq!(json, format!("\"{}\"", s.as_str()));
let back: ColoradoAdmtStatus = serde_json::from_str(&json).unwrap();
assert_eq!(back, s);
}
}
}