franken-decision 0.2.9

Decision Contract schema and runtime for FrankenSuite.
Documentation

Decision Contract schema and runtime for FrankenSuite (bd-3ai21).

The third leg of the foundation tripod alongside franken_kernel (types) and franken_evidence (audit ledger). Every FrankenSuite project that makes runtime decisions uses this crate's contract schema.

Core abstractions

  • [DecisionContract] — trait defining state space, actions, losses, and posterior updates. Implementable in <50 lines.
  • [LossMatrix] — non-negative loss values indexed by (state, action), serializable to TOML for runtime reconfiguration.
  • [Posterior] — discrete probability distribution with O(|S|) no-allocation Bayesian updates.
  • [FallbackPolicy] — calibration drift, e-process breach, and confidence interval width thresholds.
  • [DecisionAuditEntry] — links decisions to [EvidenceLedger] entries.

Example

use franken_decision::{
    DecisionContract, EvalContext, FallbackPolicy, LossMatrix, Posterior, evaluate,
};
use franken_kernel::DecisionId;

// Define a simple 2-state, 2-action contract.
struct MyContract {
    states: Vec<String>,
    actions: Vec<String>,
    losses: LossMatrix,
    policy: FallbackPolicy,
}

impl DecisionContract for MyContract {
    fn name(&self) -> &str { "example" }
    fn state_space(&self) -> &[String] { &self.states }
    fn action_set(&self) -> &[String] { &self.actions }
    fn loss_matrix(&self) -> &LossMatrix { &self.losses }
    fn update_posterior(&self, posterior: &mut Posterior, observation: usize) {
        let likelihoods = [0.9, 0.1];
        posterior.bayesian_update(&likelihoods);
    }
    fn choose_action(&self, posterior: &Posterior) -> usize {
        self.losses.bayes_action(posterior)
    }
    fn fallback_action(&self) -> usize { 0 }
    fn fallback_policy(&self) -> &FallbackPolicy { &self.policy }
}

let contract = MyContract {
    states: vec!["good".into(), "bad".into()],
    actions: vec!["continue".into(), "stop".into()],
    losses: LossMatrix::new(
        vec!["good".into(), "bad".into()],
        vec!["continue".into(), "stop".into()],
        vec![0.0, 0.3, 0.8, 0.1],
    ).unwrap(),
    policy: FallbackPolicy::default(),
};

let posterior = Posterior::uniform(2);
let decision_id = DecisionId::from_parts(1_700_000_000_000, 42);
let trace_id = franken_kernel::TraceId::from_parts(1_700_000_000_000, 1);

let ctx = EvalContext {
    calibration_score: 0.9,
    e_process: 0.5,
    ci_width: 0.1,
    decision_id,
    trace_id,
    ts_unix_ms: 1_700_000_000_000,
};
let outcome = evaluate(&contract, &posterior, &ctx);
assert!(!outcome.fallback_active);