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