1#![forbid(unsafe_code)]
22
23mod check;
24mod eval;
25pub mod program;
26
27use rustev_contract::definition::{Determinism, Operation};
28use rustev_contract::descriptor::{
29 BackendDescriptor, InputExcess, InputLimit, OperationSupport, OutputKind,
30};
31use rustev_contract::ids::ArtifactId;
32use rustev_contract::run::{Charge, CostBound, CostModel};
33use rustev_contract::schema;
34use rustev_core::seams::{
35 AdapterFailure, AttemptCall, AttemptReport, BoxFuture, CancelAck, CancelSignal,
36 DecisionBackend, RemoteEnd,
37};
38
39pub use check::{PlanMismatch, PlanMismatchKind};
40pub use eval::Evaluated;
41pub use program::{ProgramError, ProgramErrorKind, RulesProgram};
42
43use program::{CTask, ProgramErrorKind as K};
44
45#[derive(Debug, Clone)]
47pub struct RulesBackend {
48 program: RulesProgram,
49 tasks: Vec<CTask>,
50 descriptor: BackendDescriptor,
51}
52
53impl RulesBackend {
54 pub fn new(program: RulesProgram) -> Result<Self, ProgramError> {
58 let tasks = program::check(&program)?;
59 let artifact = program.artifact_id().map_err(|e| ProgramError {
60 at: String::new(),
61 kind: K::Canonical(e),
62 })?;
63 let descriptor = derive_descriptor(&program, &tasks, artifact);
64 Ok(Self {
65 program,
66 tasks,
67 descriptor,
68 })
69 }
70
71 pub fn from_bytes(bytes: &[u8]) -> Result<Self, ProgramError> {
73 use rustev_contract::Document;
74 let program = RulesProgram::parse(bytes).map_err(|e| ProgramError {
75 at: String::new(),
76 kind: K::Document(e),
77 })?;
78 Self::new(program)
79 }
80
81 pub fn program(&self) -> &RulesProgram {
83 &self.program
84 }
85
86 pub fn artifact(&self) -> &ArtifactId {
88 &self.descriptor.artifact
89 }
90
91 pub fn evaluate(&self, projection: &[u8], cancel: &CancelSignal) -> Evaluated {
94 self.evaluate_observed(projection, &mut |_| cancel.is_raised())
95 }
96
97 pub(crate) fn evaluate_observed(
98 &self,
99 projection: &[u8],
100 cancelled: &mut dyn FnMut(eval::Point) -> bool,
101 ) -> Evaluated {
102 eval::evaluate(
103 &self.tasks,
104 self.program.limits.max_projection_bytes,
105 projection,
106 cancelled,
107 )
108 }
109
110 fn charge(&self) -> Charge {
111 Charge::Observed {
112 units: self.program.cost.units_per_call,
113 }
114 }
115}
116
117fn derive_descriptor(
119 program: &RulesProgram,
120 tasks: &[CTask],
121 artifact: ArtifactId,
122) -> BackendDescriptor {
123 let operations = [
124 Operation::Classify,
125 Operation::Proposition,
126 Operation::Rubric,
127 ]
128 .into_iter()
129 .filter_map(|op| {
130 tasks
131 .iter()
132 .filter(|t| t.operation == op)
133 .map(|t| t.options.len() as u64)
134 .max()
135 .map(|max_options| OperationSupport {
136 isolation: None,
137 screen: None,
138 operation: op,
139 output: OutputKind::Logits,
140 max_options,
141 })
142 })
143 .collect();
144 BackendDescriptor {
145 schema: schema::BACKEND.into(),
146 backend_id: program.backend_id.clone(),
147 artifact,
148 operations,
149 input_limit: InputLimit {
150 max_bytes: program.limits.max_projection_bytes,
151 on_excess: InputExcess::Refuse,
152 },
153 determinism: Determinism::Bitwise,
154 }
155}
156
157impl DecisionBackend for RulesBackend {
158 fn descriptor(&self) -> &BackendDescriptor {
159 &self.descriptor
160 }
161
162 fn cost_model(&self) -> CostModel {
164 CostModel::Bounded
165 }
166
167 fn cost_bound(&self, _projection: &[u8]) -> CostBound {
168 CostBound::Bounded {
169 max_units: self.program.cost.units_per_call,
170 }
171 }
172
173 fn infer<'a>(&'a self, call: AttemptCall<'a>) -> BoxFuture<'a, AttemptReport> {
174 Box::pin(async move {
175 let charge = self.charge();
176 match self.evaluate(call.projection, &call.cancel) {
177 Evaluated::Output(output) => AttemptReport {
178 result: Ok(output),
179 charge,
180 cancel: CancelAck::NotRequested,
181 remote: RemoteEnd::Finished,
182 },
183 Evaluated::Failed(detail) => AttemptReport {
184 result: Err((AdapterFailure::Permanent, detail)),
185 charge,
186 cancel: CancelAck::NotRequested,
187 remote: RemoteEnd::Finished,
188 },
189 Evaluated::Cancelled(point) => AttemptReport {
192 result: Err((AdapterFailure::Cancelled, format!("cancelled {point}"))),
193 charge,
194 cancel: CancelAck::Stopped,
195 remote: RemoteEnd::Finished,
196 },
197 }
198 })
199 }
200}
201
202#[cfg(test)]
203mod tests;