use std::sync::Arc;
use lonca_core::keys::KeyPair;
use lonca_core::oplog::{FORMAT_CURRENT, OpEntry, SignedOp};
use lonca_core::ops::{Actor, Attestation, Facts, HumanSignature, Op};
use lonca_core::{Error, GitId, Result};
use crate::config::PolicyBundle;
use crate::decide::{Ctx, decide};
use crate::state::State;
#[derive(Clone, Debug)]
pub struct Submission {
pub actor: Actor,
pub op: Op,
pub human: Option<HumanSignature>,
pub facts: Option<Facts>,
pub symbols_engine: Option<u32>,
pub evidence: Vec<Attestation>,
}
impl Submission {
pub fn new(actor: Actor, op: Op) -> Self {
Self {
actor,
op,
human: None,
facts: None,
symbols_engine: None,
evidence: vec![],
}
}
}
pub struct Chain {
pub state: State,
key: KeyPair,
}
impl Chain {
pub fn new(key: KeyPair) -> Self {
Self {
state: State::new(),
key,
}
}
pub fn from_ops(key: KeyPair, ops: &[SignedOp]) -> Self {
Self::from_state(key, State::replay(ops))
}
pub fn from_state(key: KeyPair, state: State) -> Self {
Self { state, key }
}
pub fn public_key(&self) -> String {
self.key.public_hex()
}
pub fn set_key(&mut self, key: KeyPair) {
self.key = key;
}
pub fn entry_time(&self, wall_ms: i64) -> i64 {
wall_ms.max(self.state.last_time_ms)
}
pub fn prepare(&self, sub: Submission, time_ms: i64, policy: &PolicyBundle) -> SignedOp {
let decision = decide(
&self.state,
&sub.actor,
&sub.op,
sub.human.as_ref(),
&Ctx {
format: FORMAT_CURRENT,
time_ms,
policy,
facts: sub.facts.as_ref(),
evidence: &sub.evidence,
symbols_engine: sub.symbols_engine,
},
);
let entry = OpEntry {
format: FORMAT_CURRENT,
seq: self.state.next_seq,
prev: self.state.head_hash.clone(),
time_ms,
actor: sub.actor,
op: sub.op,
evidence: sub.evidence,
facts: sub.facts,
symbols_engine: sub.symbols_engine,
decision,
human: sub.human,
};
SignedOp::sign(entry, &self.key)
}
pub fn commit(&mut self, op: &SignedOp) {
self.state.apply(op);
}
}
pub fn replay_checked(
ops: &[SignedOp],
mut policy_at: impl FnMut(Option<&GitId>) -> Result<Arc<PolicyBundle>>,
) -> Result<State> {
let mut state = State::new();
for op in ops {
let e = &op.entry;
if e.switches_daemon_key().is_some() {
let policy = policy_at(state.policy.as_ref())?;
let d = decide(
&state,
&e.actor,
&e.op,
e.human.as_ref(),
&Ctx {
format: e.format,
time_ms: e.time_ms,
policy: &policy,
facts: e.facts.as_ref(),
evidence: &e.evidence,
symbols_engine: e.symbols_engine,
},
);
if d != e.decision {
return Err(Error::Integrity(format!(
"op {}: {} is not authorised (replay gives {:?} {:?})",
e.seq,
e.op.kind().as_str(),
d.outcome,
d.reasons
)));
}
}
state.apply(op);
}
Ok(state)
}