#![allow(
clippy::print_stdout,
clippy::needless_pass_by_value,
clippy::needless_borrows_for_generic_args
)]
use batpak::prelude::*;
use serde::Serialize;
const TRANSFER: EventKind = EventKind::custom(4, 1);
#[derive(Clone, Serialize)]
struct TransferRequest {
from: String,
to: String,
amount_cents: u64,
memo: String,
}
struct AmountLimitGate {
max_cents: u64,
}
impl Gate<TransferRequest> for AmountLimitGate {
fn name(&self) -> &'static str {
"amount_limit"
}
fn evaluate(&self, ctx: &TransferRequest) -> Result<(), Denial> {
if ctx.amount_cents > self.max_cents {
Err(Denial::new(
self.name(),
format!(
"Transfer of ${:.2} exceeds limit of ${:.2}",
ctx.amount_cents as f64 / 100.0,
self.max_cents as f64 / 100.0,
),
)
.with_code("AMOUNT_EXCEEDED")
.with_context("requested", format!("{}", ctx.amount_cents))
.with_context("limit", format!("{}", self.max_cents)))
} else {
Ok(())
}
}
fn description(&self) -> &'static str {
"Rejects transfers exceeding the configured amount limit"
}
}
struct SanctionsGate {
blocked_entities: &'static [&'static str],
}
impl Gate<TransferRequest> for SanctionsGate {
fn name(&self) -> &'static str {
"sanctions_check"
}
fn evaluate(&self, ctx: &TransferRequest) -> Result<(), Denial> {
for blocked in self.blocked_entities {
if ctx.to.contains(blocked) || ctx.from.contains(blocked) {
return Err(Denial::new(self.name(), "Entity is on sanctions list")
.with_code("SANCTIONED_ENTITY")
.with_context("entity", blocked.to_string()));
}
}
Ok(())
}
}
struct BusinessHoursGate {
is_business_hours: bool, }
impl Gate<TransferRequest> for BusinessHoursGate {
fn name(&self) -> &'static str {
"business_hours"
}
fn evaluate(&self, _ctx: &TransferRequest) -> Result<(), Denial> {
if self.is_business_hours {
Ok(())
} else {
Err(Denial::new(
self.name(),
"Transfers only allowed during business hours (9am-5pm)",
)
.with_code("OUTSIDE_HOURS"))
}
}
}
fn try_transfer(store: &Store, pipeline: &Pipeline<TransferRequest>, transfer: TransferRequest) {
let label = format!(
"${:.2} {} → {}",
transfer.amount_cents as f64 / 100.0,
transfer.from,
transfer.to
);
let proposal = Proposal::new(transfer.clone());
match pipeline.evaluate(&transfer, proposal) {
Ok(receipt) => {
println!(" APPROVED: {}", label);
println!(" Gates passed: {:?}", receipt.gates_passed());
let coord =
Coordinate::new(&format!("transfer:{}", transfer.from), "banking:transfers")
.expect("valid coordinate");
let result: Result<_, StoreError> = pipeline.commit(receipt, |payload| {
let r = store.append(&coord, TRANSFER, &payload)?;
Ok(Committed {
payload,
event_id: r.event_id,
sequence: r.sequence,
hash: [0u8; 32],
})
});
match result {
Ok(committed) => println!(" Committed: event_id={:032x}", committed.event_id),
Err(e) => println!(" Commit failed: {e}"),
}
}
Err(denial) => {
println!(" DENIED: {}", label);
println!(" Gate: {}", denial.gate);
println!(" Code: {}", denial.code);
println!(" Reason: {}", denial.message);
if !denial.context.is_empty() {
for (k, v) in &denial.context {
println!(" {}: {}", k, v);
}
}
}
}
println!();
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let dir = tempfile::tempdir()?;
let store = Store::open(StoreConfig::new(dir.path()))?;
let mut gates: GateSet<TransferRequest> = GateSet::new();
gates.push(SanctionsGate {
blocked_entities: &["evil-corp", "shady-llc"],
});
gates.push(AmountLimitGate {
max_cents: 1_000_000, });
gates.push(BusinessHoursGate {
is_business_hours: true,
});
let pipeline = Pipeline::new(gates);
println!("=== Bank Transfer Policy Gates ===\n");
println!("Gates: sanctions_check → amount_limit → business_hours\n");
try_transfer(
&store,
&pipeline,
TransferRequest {
from: "acme-inc".into(),
to: "widgets-ltd".into(),
amount_cents: 50000, memo: "Invoice #1234".into(),
},
);
try_transfer(
&store,
&pipeline,
TransferRequest {
from: "acme-inc".into(),
to: "evil-corp".into(),
amount_cents: 100, memo: "Totally legitimate".into(),
},
);
try_transfer(
&store,
&pipeline,
TransferRequest {
from: "acme-inc".into(),
to: "mega-corp".into(),
amount_cents: 5_000_000, memo: "Big purchase".into(),
},
);
println!("--- Committed Events ---");
let entries = store.query(&Region::scope("banking:transfers"));
println!(
" {} transfer(s) committed (2 denied, never stored)\n",
entries.len()
);
for entry in &entries {
let stored = store.get(entry.event_id)?;
println!(" {} → {}", entry.coord, stored.event.payload);
}
println!("\n--- evaluate_all: collect all denials at once ---");
let mut all_gates: GateSet<TransferRequest> = GateSet::new();
all_gates.push(SanctionsGate {
blocked_entities: &["evil-corp"],
});
all_gates.push(AmountLimitGate {
max_cents: 1_000_000,
});
all_gates.push(BusinessHoursGate {
is_business_hours: false, });
let bad_transfer = TransferRequest {
from: "evil-corp".into(),
to: "widgets-ltd".into(),
amount_cents: 5_000_000,
memo: "Everything wrong".into(),
};
let all_denials = all_gates.evaluate_all(&bad_transfer);
println!(" {} denials for one transfer:", all_denials.len());
for d in &all_denials {
println!(" [{}] {} (code: {})", d.gate, d.message, d.code);
}
store.close()?;
println!("\nGates are pure predicates. No I/O, no side effects, composable.");
Ok(())
}