Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 22 additions & 28 deletions crates/arc-kit-au/src/badge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,7 @@ impl From<&EvidenceChain> for ProvenanceBadge {
let critical_missing: Vec<_> = missing
.iter()
.filter(|m| {
**m == "source_document"
|| **m == "classification"
|| **m == "extracted_rows"
**m == "source_document" || **m == "classification" || **m == "extracted_rows"
})
.map(|s| s.to_string())
.collect();
Expand All @@ -86,8 +84,8 @@ impl From<&EvidenceChain> for ProvenanceBadge {

#[cfg(test)]
mod tests {
use crate::node::Confidence;
use super::*;
use crate::node::Confidence;
use crate::node::{EvidenceNode, NodeId, NodeType, SourceDoc};
use chrono::TimeZone;
use chrono::Utc;
Expand Down Expand Up @@ -146,18 +144,16 @@ mod tests {
source_document: NodeId::new(NodeType::SourceDoc, "abc"),
extraction_confidence: Confidence::from(0.95),
})],
classifications: vec![EvidenceNode::Classification(
crate::node::Classification {
tx_id: "tx_1".to_string(),
category: "Meals".to_string(),
sub_category: None,
confidence: Confidence::from(0.92),
rule_used: None,
actor: "operator".to_string(),
classified_at: Utc.with_ymd_and_hms(2024, 2, 1, 11, 0, 0).unwrap(),
note: None,
},
)],
classifications: vec![EvidenceNode::Classification(crate::node::Classification {
tx_id: "tx_1".to_string(),
category: "Meals".to_string(),
sub_category: None,
confidence: Confidence::from(0.92),
rule_used: None,
actor: "operator".to_string(),
classified_at: Utc.with_ymd_and_hms(2024, 2, 1, 11, 0, 0).unwrap(),
note: None,
})],
proposals: vec![],
approvals: vec![EvidenceNode::OperatorApproval(
crate::node::OperatorApproval {
Expand Down Expand Up @@ -204,18 +200,16 @@ mod tests {
tx_id: "tx_3".to_string(),
source_documents: vec![],
extracted_rows: vec![],
classifications: vec![EvidenceNode::Classification(
crate::node::Classification {
tx_id: "tx_3".to_string(),
category: "Meals".to_string(),
sub_category: None,
confidence: Confidence::from(0.92),
rule_used: None,
actor: "operator".to_string(),
classified_at: Utc.with_ymd_and_hms(2024, 2, 1, 11, 0, 0).unwrap(),
note: None,
},
)],
classifications: vec![EvidenceNode::Classification(crate::node::Classification {
tx_id: "tx_3".to_string(),
category: "Meals".to_string(),
sub_category: None,
confidence: Confidence::from(0.92),
rule_used: None,
actor: "operator".to_string(),
classified_at: Utc.with_ymd_and_hms(2024, 2, 1, 11, 0, 0).unwrap(),
note: None,
})],
proposals: vec![],
approvals: vec![EvidenceNode::OperatorApproval(
crate::node::OperatorApproval {
Expand Down
12 changes: 6 additions & 6 deletions crates/arc-kit-au/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
//! Provides a fluent API for building evidence chains from ingest,
//! classify, approve, and export operations.


use crate::edge::EdgeType;
use crate::graph::EvidenceGraph;
use crate::node::{
Expand Down Expand Up @@ -76,7 +75,8 @@ impl<'a> EvidenceBuilder<'a> {
pub fn ensure_proposal(&mut self, proposal: ModelProposal) -> NodeId {
let prop_id = proposal.node_id();
let tx_id = NodeId::new(NodeType::Transaction, &proposal.tx_id);
self.graph.ensure_node(EvidenceNode::ModelProposal(proposal));
self.graph
.ensure_node(EvidenceNode::ModelProposal(proposal));
self.graph
.ensure_edge(tx_id, prop_id.clone(), EdgeType::ProposedBy);
prop_id
Expand All @@ -98,16 +98,16 @@ impl<'a> EvidenceBuilder<'a> {
let wb_id = wb_row.node_id();
let tx_id = NodeId::new(NodeType::Transaction, &wb_row.tx_id);
self.graph.ensure_node(EvidenceNode::WorkbookRow(wb_row));
self.graph.ensure_edge(tx_id, wb_id.clone(), EdgeType::ExportedTo);
self.graph
.ensure_edge(tx_id, wb_id.clone(), EdgeType::ExportedTo);
wb_id
}

/// Idempotent: ensure a validation issue node with ValidatedAs edge.
pub fn ensure_validation_issue(&mut self, issue: ValidationIssue) -> NodeId {
let vi_id = issue.node_id();
let tx_id = NodeId::new(NodeType::Transaction, &issue.tx_id);
self.graph
.ensure_node(EvidenceNode::ValidationIssue(issue));
self.graph.ensure_node(EvidenceNode::ValidationIssue(issue));
self.graph
.ensure_edge(tx_id, vi_id.clone(), EdgeType::ValidatedAs);
vi_id
Expand All @@ -133,8 +133,8 @@ impl<'a> EvidenceBuilder<'a> {

#[cfg(test)]
mod tests {
use crate::node::Confidence;
use super::*;
use crate::node::Confidence;
use chrono::{TimeZone, Utc};
use rust_decimal::Decimal;

Expand Down
12 changes: 6 additions & 6 deletions crates/arc-kit-au/src/edge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,18 @@ pub struct EvidenceEdge {

impl EvidenceEdge {
pub fn new(from: NodeId, to: NodeId, edge_type: EdgeType) -> Self {
Self { from, to, edge_type }
Self {
from,
to,
edge_type,
}
}
}

/// Edge traversal utilities.
pub trait EdgeTraversal {
/// Find all edges of a specific type from a node.
fn edges_of_type<'a>(
&'a self,
from: &'a NodeId,
edge_type: EdgeType,
) -> Vec<&'a EvidenceEdge>;
fn edges_of_type<'a>(&'a self, from: &'a NodeId, edge_type: EdgeType) -> Vec<&'a EvidenceEdge>;

/// Find all incoming edges to a node.
fn incoming_edges<'a>(&'a self, to: &'a NodeId) -> Vec<&'a EvidenceEdge>;
Expand Down
14 changes: 9 additions & 5 deletions crates/arc-kit-au/src/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,18 @@ impl EvidenceGraph {

/// Get all nodes of a specific type.
pub fn nodes_of_type(&self, node_type: NodeType) -> Vec<&EvidenceNode> {
self.nodes.iter().filter(|n| n.node_type() == node_type).collect()
self.nodes
.iter()
.filter(|n| n.node_type() == node_type)
.collect()
}

/// Get all edges of a specific type.
pub fn edges_of_type(&self, edge_type: EdgeType) -> Vec<&EvidenceEdge> {
self.edges.iter().filter(|e| e.edge_type == edge_type).collect()
self.edges
.iter()
.filter(|e| e.edge_type == edge_type)
.collect()
}

/// Find all outgoing edges from a node.
Expand Down Expand Up @@ -358,9 +364,7 @@ mod tests {
let tx = test_tx();
let doc_id = graph.add_node(EvidenceNode::SourceDoc(doc)).unwrap();
let tx_id = graph.add_node(EvidenceNode::Transaction(tx)).unwrap();
graph
.add_edge(doc_id, tx_id, EdgeType::Produces)
.unwrap();
graph.add_edge(doc_id, tx_id, EdgeType::Produces).unwrap();

let json = graph.to_json().unwrap();
let restored = EvidenceGraph::from_json(&json).unwrap();
Expand Down
18 changes: 9 additions & 9 deletions crates/arc-kit-au/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,20 @@
//! assert!(restored.is_empty());
//! ```

pub mod node;
pub mod badge;
pub mod builder;
pub mod edge;
pub mod graph;
pub mod builder;
pub mod trace;
pub mod missing;
pub mod badge;
pub mod node;
pub mod store;
pub mod trace;

pub use node::{Confidence, EvidenceNode, NodeId, NodeType};
pub use edge::{EvidenceEdge, EdgeType};
pub use graph::{EvidenceGraph, WorkQueueSummary};
pub use badge::ProvenanceBadge;
pub use builder::EvidenceBuilder;
pub use trace::{EvidenceChain, EvidenceTracer};
pub use edge::{EdgeType, EvidenceEdge};
pub use graph::{EvidenceGraph, WorkQueueSummary};
pub use missing::{MissingElement, ProvenanceGap, ProvenanceScanner};
pub use badge::ProvenanceBadge;
pub use node::{Confidence, EvidenceNode, NodeId, NodeType};
pub use store::EvidenceStore;
pub use trace::{EvidenceChain, EvidenceTracer};
28 changes: 13 additions & 15 deletions crates/arc-kit-au/src/missing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,7 @@ impl ProvenanceScanner for EvidenceGraph {

// Check incoming edges for source rows
let incoming = self.incoming_edges(&tx_node_id);
let has_rows = incoming
.iter()
.any(|e| e.edge_type == EdgeType::Produces);
let has_rows = incoming.iter().any(|e| e.edge_type == EdgeType::Produces);

if has_rows {
// Check if rows have source documents
Expand Down Expand Up @@ -156,9 +154,9 @@ impl ProvenanceScanner for EvidenceGraph {

#[cfg(test)]
mod tests {
use crate::node::Confidence;
use super::*;
use crate::builder::EvidenceBuilder;
use crate::node::Confidence;
use crate::node::{Classification, ExtractedRow, NodeId, SourceDoc, Transaction};
use chrono::TimeZone;
use chrono::Utc;
Expand Down Expand Up @@ -228,9 +226,7 @@ mod tests {
// build_full_chain doesn't create approvals or exports, so those will be gaps
let gaps = graph.find_missing_provenance();
assert_eq!(gaps.len(), 1);
assert!(gaps[0]
.missing
.contains(&MissingElement::OperatorApproval));
assert!(gaps[0].missing.contains(&MissingElement::OperatorApproval));
assert!(gaps[0].missing.contains(&MissingElement::WorkbookExport));
// But source and classification should be present
assert!(!gaps[0].missing.contains(&MissingElement::SourceDocument));
Expand All @@ -254,12 +250,8 @@ mod tests {

let gaps = graph.find_missing_provenance();
assert_eq!(gaps.len(), 1);
assert!(gaps[0]
.missing
.contains(&MissingElement::Classification));
assert!(gaps[0]
.missing
.contains(&MissingElement::OperatorApproval));
assert!(gaps[0].missing.contains(&MissingElement::Classification));
assert!(gaps[0].missing.contains(&MissingElement::OperatorApproval));
assert!(gaps[0].missing.contains(&MissingElement::WorkbookExport));
}

Expand Down Expand Up @@ -293,11 +285,17 @@ mod tests {

#[test]
fn missing_element_display_format() {
assert_eq!(MissingElement::SourceDocument.to_string(), "source_document");
assert_eq!(
MissingElement::SourceDocument.to_string(),
"source_document"
);
assert_eq!(
MissingElement::OperatorApproval.to_string(),
"operator_approval"
);
assert_eq!(MissingElement::WorkbookExport.to_string(), "workbook_export");
assert_eq!(
MissingElement::WorkbookExport.to_string(),
"workbook_export"
);
}
}
2 changes: 1 addition & 1 deletion crates/arc-kit-au/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,9 @@ impl EvidenceStore {

#[cfg(test)]
mod tests {
use crate::node::Confidence;
use super::*;
use crate::builder::EvidenceBuilder;
use crate::node::Confidence;
use crate::node::{Classification, ExtractedRow, SourceDoc, Transaction};
use chrono::TimeZone;
use chrono::Utc;
Expand Down
2 changes: 1 addition & 1 deletion crates/arc-kit-au/src/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,9 @@ impl EvidenceTracer for EvidenceGraph {

#[cfg(test)]
mod tests {
use crate::node::Confidence;
use super::*;
use crate::builder::EvidenceBuilder;
use crate::node::Confidence;
use crate::node::{Classification, ExtractedRow, OperatorApproval, SourceDoc, Transaction};
use chrono::TimeZone;
use chrono::Utc;
Expand Down
6 changes: 5 additions & 1 deletion crates/b00t-iface/src/core/governance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,11 @@ impl GovernancePolicy {
/// Solver-friendly encoding as a serializable constraint set.
pub fn to_constraints(&self) -> GovernanceConstraints {
GovernanceConstraints {
allowed_starters: self.allowed_starters.iter().map(|r| r.to_string()).collect(),
allowed_starters: self
.allowed_starters
.iter()
.map(|r| r.to_string())
.collect(),
max_ttl_secs: self.max_ttl.as_secs(),
auto_restart: self.auto_restart,
crash_budget: self.crash_budget,
Expand Down
33 changes: 24 additions & 9 deletions crates/b00t-iface/src/core/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@
//! ```

use super::surface::{AuditRecord, MaintenanceAction};
use std::time::Duration;
use std::fmt;
use std::time::Duration;

/// The finite set of states a surface machine can be in.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand Down Expand Up @@ -114,7 +114,11 @@ impl SurfaceMachine {
}

/// Attempt a transition. Returns the trigger label if valid.
pub fn transition(&mut self, to: MachineState, elapsed: Duration) -> Result<&'static str, String> {
pub fn transition(
&mut self,
to: MachineState,
elapsed: Duration,
) -> Result<&'static str, String> {
let trigger = valid_transition(self.state, to)
.ok_or_else(|| format!("invalid transition: {} → {}", self.state, to))?;

Expand All @@ -130,7 +134,11 @@ impl SurfaceMachine {
}

/// Process a maintenance action and transition accordingly.
pub fn apply_maintenance(&mut self, action: &MaintenanceAction, elapsed: Duration) -> Result<(), String> {
pub fn apply_maintenance(
&mut self,
action: &MaintenanceAction,
elapsed: Duration,
) -> Result<(), String> {
match action {
MaintenanceAction::NoOp => {
self.transition(MachineState::Healthy, elapsed)?;
Expand Down Expand Up @@ -163,18 +171,24 @@ mod tests {
#[test]
fn valid_lifecycle() {
let mut m = SurfaceMachine::new("test");
m.transition(MachineState::Ready, Duration::from_millis(10)).unwrap();
m.transition(MachineState::Running, Duration::from_millis(50)).unwrap();
m.apply_maintenance(&MaintenanceAction::NoOp, Duration::from_millis(5)).unwrap();
m.transition(MachineState::Terminated, Duration::from_millis(20)).unwrap();
m.transition(MachineState::Ready, Duration::from_millis(10))
.unwrap();
m.transition(MachineState::Running, Duration::from_millis(50))
.unwrap();
m.apply_maintenance(&MaintenanceAction::NoOp, Duration::from_millis(5))
.unwrap();
m.transition(MachineState::Terminated, Duration::from_millis(20))
.unwrap();
assert!(m.is_terminal());
assert_eq!(m.transitions.len(), 4);
}

#[test]
fn invalid_transition_fails() {
let mut m = SurfaceMachine::new("test");
let err = m.transition(MachineState::Running, Duration::ZERO).unwrap_err();
let err = m
.transition(MachineState::Running, Duration::ZERO)
.unwrap_err();
assert!(err.contains("invalid transition"));
}

Expand Down Expand Up @@ -203,7 +217,8 @@ mod tests {
let mut m = SurfaceMachine::new("test");
m.transition(MachineState::Ready, Duration::ZERO).unwrap();
m.transition(MachineState::Running, Duration::ZERO).unwrap();
m.apply_maintenance(&MaintenanceAction::Restart, Duration::ZERO).unwrap();
m.apply_maintenance(&MaintenanceAction::Restart, Duration::ZERO)
.unwrap();
assert_eq!(m.crash_count, 1);
assert_eq!(m.state, MachineState::Ready);
}
Expand Down
Loading