use std::sync::Arc;
use std::time::Duration;
use async_trait::async_trait;
use serde_json::{json, Map, Value};
pub const QUESTION_SET_RANK: &str = "sp08-rank-v1";
pub const QUESTION_SET_OP: &str = "sp03-op-v1";
pub const DEADLINE: Duration = Duration::from_secs(4);
pub const MAX_CANDIDATES: usize = 20;
pub const DEFAULT_RANK_MIN_CONFIDENCE: f64 = 0.25;
#[derive(Debug, Clone, PartialEq)]
pub enum Fallback {
FlagOff,
NoEndpoint,
Timeout,
Http(u16),
Malformed,
LowConfidence,
OverCap,
}
impl Fallback {
pub fn reason(&self) -> String {
match self {
Fallback::FlagOff => "flag-off".into(),
Fallback::NoEndpoint => "no-endpoint".into(),
Fallback::Timeout => "timeout".into(),
Fallback::Http(c) => format!("http-{c}"),
Fallback::Malformed => "malformed".into(),
Fallback::LowConfidence => "low-confidence".into(),
Fallback::OverCap => "over-cap".into(),
}
}
}
#[derive(Debug, Clone)]
pub struct JevConfig {
pub endpoint: String,
pub model: String,
pub bearer: Option<String>,
pub timeout: Duration,
pub rank_min_confidence: f64,
pub op_min_confidence: Option<f64>,
}
pub fn flag_on(env: &dyn Fn(&str) -> Option<String>) -> bool {
env("SOLID_POD_JEV").as_deref() == Some("1")
}
pub fn resolve(env: &dyn Fn(&str) -> Option<String>) -> Result<JevConfig, Fallback> {
if !flag_on(env) {
return Err(Fallback::FlagOff);
}
let get = |k: &str| env(k).filter(|v| !v.trim().is_empty());
let rank_min = get("SOLID_POD_JEV_RANK_MIN_CONFIDENCE")
.and_then(|v| v.parse::<f64>().ok())
.filter(|v| (0.0..=1.0).contains(v))
.unwrap_or(DEFAULT_RANK_MIN_CONFIDENCE);
let op_min = get("SOLID_POD_JEV_OP_MIN_CONFIDENCE")
.and_then(|v| v.parse::<f64>().ok())
.filter(|v| *v > 0.0 && *v <= 1.0);
let timeout = get("SOLID_POD_JEV_TIMEOUT_MS")
.and_then(|v| v.parse::<u64>().ok())
.map(Duration::from_millis)
.map(|d| d.min(DEADLINE))
.unwrap_or(DEADLINE);
if let Some(endpoint) = get("SOLID_POD_JEV_ENDPOINT") {
if get("SOLID_POD_JEV_LOCAL").as_deref() != Some("1") {
return Err(Fallback::NoEndpoint);
}
let bearer = get("SOLID_POD_JEV_KEY_ENV").and_then(|name| get(&name));
return Ok(JevConfig {
endpoint,
model: get("SOLID_POD_JEV_MODEL").unwrap_or_else(|| "jev-latest".into()),
bearer,
timeout,
rank_min_confidence: rank_min,
op_min_confidence: op_min,
});
}
if get("AGENTBOX_SYSTEM_ONE_PROXY").as_deref() == Some("1")
&& get("AGENTBOX_SYSTEM_ONE_BACKEND_LOCAL").as_deref() == Some("1")
{
if let (Some(endpoint), Some(model)) = (
get("AGENTBOX_SYSTEM_ONE_ENDPOINT"),
get("AGENTBOX_SYSTEM_ONE_MODEL"),
) {
return Ok(JevConfig {
endpoint,
model,
bearer: None,
timeout,
rank_min_confidence: rank_min,
op_min_confidence: op_min,
});
}
}
Err(Fallback::NoEndpoint)
}
#[async_trait]
pub trait JevTransport: Send + Sync {
async fn post(&self, cfg: &JevConfig, body: Vec<u8>) -> Result<(u16, Vec<u8>), Fallback>;
}
pub struct ReqwestTransport;
#[async_trait]
impl JevTransport for ReqwestTransport {
async fn post(&self, cfg: &JevConfig, body: Vec<u8>) -> Result<(u16, Vec<u8>), Fallback> {
let client = reqwest::Client::builder()
.timeout(cfg.timeout)
.connect_timeout(cfg.timeout)
.redirect(reqwest::redirect::Policy::none())
.build()
.map_err(|_| Fallback::NoEndpoint)?;
let mut req = client
.post(&cfg.endpoint)
.header("Content-Type", "application/json")
.body(body);
if let Some(token) = &cfg.bearer {
req = req.header("Authorization", format!("Bearer {token}"));
}
let resp = req.send().await.map_err(|_| Fallback::Timeout)?;
let status = resp.status().as_u16();
let bytes = resp.bytes().await.map_err(|_| Fallback::Timeout)?;
if bytes.len() > 1 << 20 {
return Err(Fallback::Malformed);
}
Ok((status, bytes.to_vec()))
}
}
#[derive(Clone)]
pub struct JevRuntime {
pub config: Result<JevConfig, Fallback>,
pub transport: Arc<dyn JevTransport>,
}
impl JevRuntime {
pub fn from_process_env() -> Self {
Self {
config: resolve(&|k| std::env::var(k).ok()),
transport: Arc::new(ReqwestTransport),
}
}
}
pub fn enabled_in_process_env() -> bool {
flag_on(&|k| std::env::var(k).ok())
}
fn log_outcome(question_set: &str, outcome: &Result<(), Fallback>) {
match outcome {
Ok(()) => tracing::info!(target: "jev", question_set, "accepted"),
Err(Fallback::FlagOff) => {
tracing::debug!(target: "jev", question_set, reason = "flag-off", "fallback")
}
Err(f) => tracing::info!(target: "jev", question_set, reason = %f.reason(), "fallback"),
}
}
async fn call(
cfg: &JevConfig,
transport: &dyn JevTransport,
request: &Value,
) -> Result<Map<String, Value>, Fallback> {
let body = serde_json::to_vec(request).map_err(|_| Fallback::Malformed)?;
let (status, bytes) = transport.post(cfg, body).await?;
if !(200..300).contains(&status) {
return Err(Fallback::Http(status));
}
let v: Value = serde_json::from_slice(&bytes).map_err(|_| Fallback::Malformed)?;
match v.get("answers") {
Some(Value::Object(m)) if !m.is_empty() => Ok(m.clone()),
_ => Err(Fallback::Malformed),
}
}
fn answer<'a>(answers: &'a Map<String, Value>, id: &str, ty: &str) -> Result<&'a Value, Fallback> {
let a = answers.get(id).ok_or(Fallback::Malformed)?;
if a.get("type").and_then(Value::as_str) != Some(ty) {
return Err(Fallback::Malformed);
}
Ok(a)
}
fn unit(v: Option<&Value>) -> Option<f64> {
v.and_then(Value::as_f64)
.filter(|x| x.is_finite() && (0.0..=1.0).contains(x))
}
fn choice(
answers: &Map<String, Value>,
id: &str,
allowed: &[String],
) -> Result<(String, f64), Fallback> {
let a = answer(answers, id, "choice")?;
let key = a
.get("choice")
.and_then(Value::as_str)
.ok_or(Fallback::Malformed)?;
if !allowed.iter().any(|k| k == key) {
return Err(Fallback::Malformed);
}
let conf = match unit(a.get("confidence")) {
Some(c) => c,
None => {
let p =
unit(a.get("probabilities").and_then(|m| m.get(key))).ok_or(Fallback::Malformed)?;
let n = allowed.len() as f64;
if n < 2.0 {
return Err(Fallback::Malformed);
}
((p - 1.0 / n) / (1.0 - 1.0 / n)).clamp(0.0, 1.0)
}
};
Ok((key.to_string(), conf))
}
fn noul(answers: &Map<String, Value>, id: &str) -> Result<f64, Fallback> {
unit(answer(answers, id, "noul")?.get("noul")).ok_or(Fallback::Malformed)
}
fn score(answers: &Map<String, Value>, id: &str, levels: usize) -> Result<(f64, f64), Fallback> {
let a = answer(answers, id, "score")?;
let s = a
.get("score")
.and_then(Value::as_f64)
.filter(|s| s.is_finite() && *s >= 0.0 && *s <= (levels as f64 - 1.0))
.ok_or(Fallback::Malformed)?;
let c = unit(a.get("confidence")).ok_or(Fallback::Malformed)?;
Ok((s, c))
}
const RANK_LEVELS: [&str; 3] = [
"Unrelated: the entry has nothing to do with the query",
"Related but not what the query asks for",
"Directly relevant: this entry is what the query is looking for",
];
#[derive(Debug, Clone)]
pub struct RankCandidate {
pub name: String,
pub path: String,
pub is_container: bool,
pub detail: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct RankOutcome {
pub order: Vec<usize>,
pub scores: Vec<f64>,
pub no_suitable: f64,
}
pub fn rank_request(model: &str, query: &str, cands: &[RankCandidate]) -> Value {
rank_request_for(model, query, cands, "pod resource", "name and path")
}
pub fn rank_request_for(
model: &str,
query: &str,
cands: &[RankCandidate],
noun: &str,
judged_by: &str,
) -> Value {
let mut state_c = Map::new();
let mut questions = Map::new();
for (i, c) in cands.iter().enumerate() {
state_c.insert(
format!("c{i}"),
match &c.detail {
Some(d) => json!({ "name": c.name, "path": c.path, "is_container": c.is_container, "detail": d }),
None => json!({ "name": c.name, "path": c.path, "is_container": c.is_container }),
},
);
questions.insert(
format!("rel_c{i}"),
json!({
"type": "score",
"instructions": format!(
"Rate how directly the {noun} `candidates.c{i}` (judge by its {judged_by} only) is what the user's `query` is looking for."
),
"criteria": RANK_LEVELS,
}),
);
}
questions.insert(
"no_suitable".into(),
json!({
"type": "noul",
"instructions": "Is it true that none of the entries in `candidates` is a suitable answer to the user's `query`?"
}),
);
json!({ "model": model, "state": { "query": query, "candidates": state_c }, "questions": questions })
}
pub fn rank_apply(
answers: &Map<String, Value>,
n: usize,
min_confidence: f64,
) -> Result<RankOutcome, Fallback> {
let mut scores = Vec::with_capacity(n);
let mut conf_sum = 0.0;
for i in 0..n {
let (s, c) = score(answers, &format!("rel_c{i}"), RANK_LEVELS.len())?;
scores.push(s);
conf_sum += c;
}
let no_suitable = noul(answers, "no_suitable")?;
if n > 0 && conf_sum / (n as f64) < min_confidence {
return Err(Fallback::LowConfidence);
}
let mut order: Vec<usize> = (0..n).collect();
if no_suitable < 0.5 {
order.sort_by(|a, b| {
scores[*b]
.partial_cmp(&scores[*a])
.unwrap_or(std::cmp::Ordering::Equal)
});
}
Ok(RankOutcome {
order,
scores,
no_suitable,
})
}
pub async fn rank(
rt: &JevRuntime,
query: &str,
cands: &[RankCandidate],
) -> Result<RankOutcome, Fallback> {
rank_for(rt, query, cands, "pod resource", "name and path").await
}
pub async fn rank_for(
rt: &JevRuntime,
query: &str,
cands: &[RankCandidate],
noun: &str,
judged_by: &str,
) -> Result<RankOutcome, Fallback> {
let res = async {
let cfg = rt.config.as_ref().map_err(Clone::clone)?;
if cands.is_empty() {
return Err(Fallback::Malformed);
}
if cands.len() > MAX_CANDIDATES {
return Err(Fallback::OverCap);
}
let req = rank_request_for(&cfg.model, query, cands, noun, judged_by);
let answers = call(cfg, rt.transport.as_ref(), &req).await?;
rank_apply(&answers, cands.len(), cfg.rank_min_confidence)
}
.await;
log_outcome(
QUESTION_SET_RANK,
&res.as_ref().map(|_| ()).map_err(Clone::clone),
);
res
}
pub fn apply_rank(items: &mut [Value], readable: &[bool], outcome: &RankOutcome) -> Value {
let slots: Vec<usize> = readable
.iter()
.enumerate()
.filter_map(|(i, r)| r.then_some(i))
.collect();
let originals: Vec<Value> = slots.iter().map(|s| items[*s].clone()).collect();
for (slot, idx) in slots.iter().zip(outcome.order.iter()) {
items[*slot] = originals[*idx].clone();
}
json!({
"questionSet": QUESTION_SET_RANK,
"noSuitable": outcome.no_suitable,
"reranked": outcome.no_suitable < 0.5,
})
}
pub const FORGE_QUERY_PARAM: &str = "jev_query";
const FORGE_DETAIL_CHARS: usize = 300;
fn forge_listing_key(path: &str) -> Option<&'static str> {
let segs: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
match segs.as_slice() {
[_, "api", "repos"] | [_, "api", "repos", _] => Some("repos"),
[_, "api", "repos", _, _, "issues"] => Some("issues"),
_ => None,
}
}
pub async fn rerank_forge_listing(
rt: &JevRuntime,
method: &str,
path: &str,
query: &str,
status: u16,
body: &[u8],
) -> Option<Vec<u8>> {
if !method.eq_ignore_ascii_case("GET") || status != 200 {
return None;
}
let key = forge_listing_key(path)?;
let user_query = query_param(query, FORGE_QUERY_PARAM)?;
let mut doc: Value = serde_json::from_slice(body).ok()?;
let items = doc.get_mut(key)?.as_array_mut()?;
let head = items.len().min(MAX_CANDIDATES);
let cands: Vec<RankCandidate> = items[..head]
.iter()
.map(|it| {
if key == "repos" {
let owner = it["owner"].as_str().unwrap_or("");
let name = it["name"].as_str().unwrap_or("");
RankCandidate {
name: format!("{owner}/{name}"),
path: format!("{owner}/{name}"),
is_container: false,
detail: it["description"]
.as_str()
.filter(|d| !d.is_empty())
.map(|d| d.chars().take(FORGE_DETAIL_CHARS).collect()),
}
} else {
RankCandidate {
name: it["title"]
.as_str()
.unwrap_or("")
.chars()
.take(200)
.collect(),
path: format!("#{}", it["number"]),
is_container: false,
detail: None,
}
}
})
.collect();
let (noun, judged_by) = if key == "repos" {
("forge repository", "name and description")
} else {
("forge issue", "title")
};
let outcome = rank_for(rt, &user_query, &cands, noun, judged_by)
.await
.ok()?;
let mut seen = vec![false; head];
if outcome.order.len() != head
|| outcome
.order
.iter()
.any(|&i| i >= head || std::mem::replace(&mut seen[i], true))
{
return None;
}
let readable = vec![true; head];
let note = apply_rank(&mut items[..head], &readable, &outcome);
doc.as_object_mut()?.insert("jev".into(), note);
serde_json::to_vec(&doc).ok()
}
fn query_param(query: &str, name: &str) -> Option<String> {
let q: actix_web::web::Query<std::collections::HashMap<String, String>> =
actix_web::web::Query::from_query(query).ok()?;
q.get(name)
.map(|v| v.trim().chars().take(256).collect::<String>())
.filter(|v| !v.is_empty())
}
#[derive(Debug, Clone)]
pub struct OpCandidate {
pub path: String,
pub name: String,
pub is_container: bool,
pub can_read: bool,
pub can_write: bool,
pub can_append: bool,
}
#[derive(Debug, Clone, Copy)]
pub struct ContainerPerms {
pub can_write: bool,
pub can_append: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Proposal {
pub operation: String,
pub target: Option<String>,
pub confidence: Option<f64>,
pub source: &'static str,
}
impl Proposal {
pub fn baseline() -> Self {
Self {
operation: "clarify".into(),
target: None,
confidence: None,
source: "baseline",
}
}
pub fn to_json(&self) -> Value {
let mut o =
json!({ "operation": self.operation, "target": self.target, "source": self.source });
if let Some(c) = self.confidence {
o["confidence"] = json!(c);
}
o
}
}
pub fn allowed_operations(cands: &[OpCandidate], container: ContainerPerms) -> Vec<&'static str> {
let mut ops = Vec::new();
if cands.iter().any(|c| c.can_read && !c.is_container) {
ops.push("read");
}
if container.can_append {
ops.push("create");
}
if cands.iter().any(|c| c.can_append && !c.is_container) {
ops.push("append");
}
if cands.iter().any(|c| c.can_write && !c.is_container) {
ops.push("patch_preview");
}
if container.can_write && cands.iter().any(|c| c.can_read && !c.is_container) {
ops.push("copy");
}
ops.push("clarify");
ops
}
fn op_description(op: &str) -> &'static str {
match op {
"read" => "The user wants to read or view the content of one existing resource.",
"create" => "The user wants to create a new resource in the container.",
"append" => "The user wants to add content to the end of one existing resource without removing anything.",
"patch_preview" => "The user wants to change part of one existing resource; only a preview of a non-destructive patch will be shown.",
"copy" => "The user wants to duplicate one existing resource into the container.",
_ => "The request is unclear, or none of the other operations fits, or the user must say more before anything is done.",
}
}
pub fn op_request(
model: &str,
request: &str,
container_path: &str,
cands: &[OpCandidate],
ops: &[&str],
) -> Value {
let mut state_c = Map::new();
let mut target_criteria = Map::new();
for (i, c) in cands.iter().enumerate() {
state_c.insert(
format!("c{i}"),
json!({ "name": c.name, "path": c.path, "is_container": c.is_container }),
);
target_criteria.insert(
format!("c{i}"),
Value::String(format!(
"The request is about the resource `candidates.c{i}`."
)),
);
}
target_criteria.insert(
"none".into(),
Value::String("The request is not about any one listed resource (for example it creates something new, or is unclear).".into()),
);
let mut op_criteria = Map::new();
for op in ops {
op_criteria.insert((*op).into(), Value::String(op_description(op).into()));
}
json!({
"model": model,
"state": { "request": request, "container": container_path, "candidates": state_c },
"questions": {
"operation": {
"type": "choice",
"instructions": "Which single operation on the pod does the user's `request` ask for, within `container`? Only the listed operations are permitted for this user.",
"criteria": op_criteria,
},
"target": {
"type": "choice",
"instructions": "Which one resource among `candidates` is the user's `request` about, if any?",
"criteria": target_criteria,
},
"unambiguous": {
"type": "noul",
"instructions": "Is it true that the user's `request` is specific enough that a single operation on a single resource is clearly intended?"
}
}
})
}
pub fn op_apply(
answers: &Map<String, Value>,
container_path: &str,
cands: &[OpCandidate],
ops: &[&str],
min_confidence: Option<f64>,
) -> Result<Proposal, Fallback> {
let op_keys: Vec<String> = ops.iter().map(|s| s.to_string()).collect();
let (op, op_conf) = choice(answers, "operation", &op_keys)?;
let mut tgt_keys: Vec<String> = (0..cands.len()).map(|i| format!("c{i}")).collect();
tgt_keys.push("none".into());
let (tgt, tgt_conf) = choice(answers, "target", &tgt_keys)?;
let unambiguous = noul(answers, "unambiguous")?;
let idx = tgt.strip_prefix('c').and_then(|n| n.parse::<usize>().ok());
let grounded: Option<String> = match (op.as_str(), idx) {
("clarify", _) => None,
("create", _) => Some(container_path.to_string()),
("read", Some(i)) if cands[i].can_read && !cands[i].is_container => {
Some(cands[i].path.clone())
}
("append", Some(i)) if cands[i].can_append && !cands[i].is_container => {
Some(cands[i].path.clone())
}
("patch_preview", Some(i)) if cands[i].can_write && !cands[i].is_container => {
Some(cands[i].path.clone())
}
("copy", Some(i)) if cands[i].can_read && !cands[i].is_container => {
Some(cands[i].path.clone())
}
_ => return Err(Fallback::Malformed),
};
let Some(threshold) = min_confidence else {
return Err(Fallback::LowConfidence);
};
let needs_target = !matches!(op.as_str(), "clarify" | "create");
if op_conf < threshold || (needs_target && tgt_conf < threshold) || unambiguous < 0.5 {
return Err(Fallback::LowConfidence);
}
Ok(Proposal {
operation: op,
target: grounded,
confidence: Some(op_conf),
source: "jev",
})
}
pub async fn propose(
rt: &JevRuntime,
request: &str,
container_path: &str,
cands: &[OpCandidate],
container: ContainerPerms,
) -> Result<Proposal, Fallback> {
let res = async {
let cfg = rt.config.as_ref().map_err(Clone::clone)?;
if cands.len() > MAX_CANDIDATES {
return Err(Fallback::OverCap);
}
let ops = allowed_operations(cands, container);
if ops.len() < 2 {
return Err(Fallback::Malformed);
}
let req = op_request(&cfg.model, request, container_path, cands, &ops);
let answers = call(cfg, rt.transport.as_ref(), &req).await?;
op_apply(&answers, container_path, cands, &ops, cfg.op_min_confidence)
}
.await;
log_outcome(
QUESTION_SET_OP,
&res.as_ref().map(|_| ()).map_err(Clone::clone),
);
res
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
use std::sync::Mutex;
fn env(pairs: &[(&str, &str)]) -> impl Fn(&str) -> Option<String> {
let m: HashMap<String, String> = pairs
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect();
move |k| m.get(k).cloned()
}
#[test]
fn resolve_flag_off_and_local_only_rules() {
assert_eq!(resolve(&env(&[])).unwrap_err(), Fallback::FlagOff);
assert_eq!(
resolve(&env(&[
("SOLID_POD_JEV", "0"),
("SOLID_POD_JEV_ENDPOINT", "http://x")
]))
.unwrap_err(),
Fallback::FlagOff
);
assert_eq!(
resolve(&env(&[
("SOLID_POD_JEV", "1"),
("SOLID_POD_JEV_ENDPOINT", "http://x")
]))
.unwrap_err(),
Fallback::NoEndpoint
);
assert_eq!(
resolve(&env(&[
("SOLID_POD_JEV", "1"),
("AGENTBOX_SYSTEM_ONE_PROXY", "1"),
("AGENTBOX_SYSTEM_ONE_ENDPOINT", "http://p"),
("AGENTBOX_SYSTEM_ONE_MODEL", "m"),
]))
.unwrap_err(),
Fallback::NoEndpoint
);
assert_eq!(
resolve(&env(&[("SOLID_POD_JEV", "1"), ("TYPESAFE_API_KEY", "k")])).unwrap_err(),
Fallback::NoEndpoint
);
let c = resolve(&env(&[
("SOLID_POD_JEV", "1"),
("AGENTBOX_SYSTEM_ONE_PROXY", "1"),
("AGENTBOX_SYSTEM_ONE_BACKEND_LOCAL", "1"),
("AGENTBOX_SYSTEM_ONE_ENDPOINT", "http://p"),
("AGENTBOX_SYSTEM_ONE_MODEL", "m"),
]))
.unwrap();
assert_eq!(
(c.endpoint.as_str(), c.model.as_str(), c.bearer.is_none()),
("http://p", "m", true)
);
let c = resolve(&env(&[
("SOLID_POD_JEV", "1"),
("SOLID_POD_JEV_ENDPOINT", "http://e"),
("SOLID_POD_JEV_LOCAL", "1"),
("SOLID_POD_JEV_KEY_ENV", "MY_KEY"),
("MY_KEY", "secret"),
]))
.unwrap();
assert_eq!(c.bearer.as_deref(), Some("secret"));
assert_eq!(c.timeout, DEADLINE);
assert!(
c.op_min_confidence.is_none(),
"default accepts nothing for operations"
);
}
fn cfg(op_min: Option<f64>) -> JevConfig {
JevConfig {
endpoint: "http://mock".into(),
model: "jev-latest".into(),
bearer: None,
timeout: DEADLINE,
rank_min_confidence: 0.25,
op_min_confidence: op_min,
}
}
struct Mock {
reply: Result<(u16, Vec<u8>), Fallback>,
seen: Mutex<Vec<Vec<u8>>>,
}
impl Mock {
fn ok(v: Value) -> Arc<Self> {
Arc::new(Self {
reply: Ok((200, serde_json::to_vec(&v).unwrap())),
seen: Mutex::new(vec![]),
})
}
fn raw(status: u16, b: &[u8]) -> Arc<Self> {
Arc::new(Self {
reply: Ok((status, b.to_vec())),
seen: Mutex::new(vec![]),
})
}
fn err(f: Fallback) -> Arc<Self> {
Arc::new(Self {
reply: Err(f),
seen: Mutex::new(vec![]),
})
}
}
#[async_trait]
impl JevTransport for Mock {
async fn post(&self, _c: &JevConfig, body: Vec<u8>) -> Result<(u16, Vec<u8>), Fallback> {
self.seen.lock().unwrap().push(body);
self.reply.clone()
}
}
fn rt(c: Result<JevConfig, Fallback>, m: &Arc<Mock>) -> JevRuntime {
JevRuntime {
config: c,
transport: m.clone(),
}
}
fn rcands() -> Vec<RankCandidate> {
["a.ttl", "b.ttl", "c.ttl"]
.iter()
.map(|n| RankCandidate {
name: n.to_string(),
path: format!("/p/{n}"),
is_container: false,
detail: None,
})
.collect()
}
fn rank_reply(scores: [f64; 3], conf: f64, none: f64) -> Value {
let mut a = Map::new();
for (i, s) in scores.iter().enumerate() {
a.insert(
format!("rel_c{i}"),
json!({"type":"score","score":s,"confidence":conf,"legend":{}}),
);
}
a.insert("no_suitable".into(), json!({"type":"noul","noul":none}));
json!({"model":"jev-latest","answers":a,"usage":{"input_tokens":1,"output_tokens":1}})
}
#[test]
fn rank_wire_shape() {
let r = rank_request("jev-latest", "my notes", &rcands());
assert_eq!(r["model"], "jev-latest");
assert!(r["state"]["candidates"].is_object());
let q = r["questions"].as_object().unwrap();
assert_eq!(q.len(), 4);
assert_eq!(q["rel_c0"]["type"], "score");
assert!(
q["rel_c0"]["criteria"].is_array(),
"Score criteria is an ordered list"
);
assert!(q["rel_c0"]["instructions"]
.as_str()
.unwrap()
.contains("candidates.c0"));
assert_eq!(q["no_suitable"]["type"], "noul");
}
#[actix_web::test]
async fn rank_accept_and_reorder_keeps_unreadable_in_place() {
let m = Mock::ok(rank_reply([0.2, 1.9, 1.0], 0.9, 0.1));
let out = rank(&rt(Ok(cfg(None)), &m), "q", &rcands()).await.unwrap();
assert_eq!(out.order, vec![1, 2, 0]);
let mut items = vec![json!("r0"), json!("hidden"), json!("r1"), json!("r2")];
let note = apply_rank(&mut items, &[true, false, true, true], &out);
assert_eq!(
items,
vec![json!("r1"), json!("hidden"), json!("r2"), json!("r0")]
);
assert_eq!(note["questionSet"], QUESTION_SET_RANK);
assert_eq!(note["reranked"], true);
}
#[actix_web::test]
async fn rank_no_suitable_annotates_without_reordering() {
let m = Mock::ok(rank_reply([0.2, 1.9, 1.0], 0.9, 0.9));
let out = rank(&rt(Ok(cfg(None)), &m), "q", &rcands()).await.unwrap();
assert_eq!(out.order, vec![0, 1, 2]);
}
#[actix_web::test]
async fn rank_every_fallback_trigger() {
let good = rank_reply([0.2, 1.9, 1.0], 0.9, 0.1);
let cases: Vec<(JevRuntime, Fallback)> = vec![
(
rt(Err(Fallback::FlagOff), &Mock::ok(good.clone())),
Fallback::FlagOff,
),
(
rt(Err(Fallback::NoEndpoint), &Mock::ok(good.clone())),
Fallback::NoEndpoint,
),
(
rt(Ok(cfg(None)), &Mock::err(Fallback::Timeout)),
Fallback::Timeout,
),
(
rt(Ok(cfg(None)), &Mock::raw(429, b"{}")),
Fallback::Http(429),
),
(
rt(Ok(cfg(None)), &Mock::raw(529, b"{}")),
Fallback::Http(529),
),
(
rt(Ok(cfg(None)), &Mock::raw(500, b"{}")),
Fallback::Http(500),
),
(
rt(Ok(cfg(None)), &Mock::raw(200, b"not json")),
Fallback::Malformed,
),
(
rt(Ok(cfg(None)), &Mock::ok(json!({"answers":{}}))),
Fallback::Malformed,
),
(
rt(
Ok(cfg(None)),
&Mock::ok(rank_reply([0.2, 1.9, 5.0], 0.9, 0.1)),
),
Fallback::Malformed,
),
(
rt(
Ok(cfg(None)),
&Mock::ok(rank_reply([0.2, 1.9, 1.0], 0.05, 0.1)),
),
Fallback::LowConfidence,
),
];
for (r, want) in cases {
assert_eq!(rank(&r, "q", &rcands()).await.unwrap_err(), want);
}
let mut missing = rank_reply([0.2, 1.9, 1.0], 0.9, 0.1);
missing["answers"].as_object_mut().unwrap().remove("rel_c1");
assert_eq!(
rank(&rt(Ok(cfg(None)), &Mock::ok(missing)), "q", &rcands())
.await
.unwrap_err(),
Fallback::Malformed
);
let mut wrong = rank_reply([0.2, 1.9, 1.0], 0.9, 0.1);
wrong["answers"]["no_suitable"] = json!({"type":"score","score":1.0,"confidence":1.0});
assert_eq!(
rank(&rt(Ok(cfg(None)), &Mock::ok(wrong)), "q", &rcands())
.await
.unwrap_err(),
Fallback::Malformed
);
let many: Vec<RankCandidate> = (0..MAX_CANDIDATES + 1)
.map(|i| RankCandidate {
name: format!("{i}"),
path: format!("/{i}"),
is_container: false,
detail: None,
})
.collect();
let m = Mock::ok(rank_reply([0.0; 3], 1.0, 0.0));
assert_eq!(
rank(&rt(Ok(cfg(None)), &m), "q", &many).await.unwrap_err(),
Fallback::OverCap
);
assert!(m.seen.lock().unwrap().is_empty());
}
fn ocands() -> Vec<OpCandidate> {
vec![
OpCandidate {
path: "/p/a.ttl".into(),
name: "a.ttl".into(),
is_container: false,
can_read: true,
can_write: true,
can_append: true,
},
OpCandidate {
path: "/p/b.ttl".into(),
name: "b.ttl".into(),
is_container: false,
can_read: true,
can_write: false,
can_append: false,
},
]
}
const PERMS: ContainerPerms = ContainerPerms {
can_write: false,
can_append: true,
};
fn op_reply(op: &str, tgt: &str, conf: f64, unamb: f64) -> Value {
json!({"model":"m","answers":{
"operation":{"type":"choice","choice":op,"confidence":conf,"probabilities":{}},
"target":{"type":"choice","choice":tgt,"confidence":conf,"probabilities":{}},
"unambiguous":{"type":"noul","noul":unamb}
},"usage":{"input_tokens":1,"output_tokens":1}})
}
#[test]
fn allowed_ops_follow_wac() {
assert_eq!(
allowed_operations(&ocands(), PERMS),
vec!["read", "create", "append", "patch_preview", "clarify"]
);
assert_eq!(
allowed_operations(
&[],
ContainerPerms {
can_write: false,
can_append: false
}
),
vec!["clarify"]
);
}
#[test]
fn op_wire_shape_criteria_are_maps_and_ids_present() {
let ops = allowed_operations(&ocands(), PERMS);
let r = op_request("jev-latest", "open b", "/p/", &ocands(), &ops);
let q = r["questions"].as_object().unwrap();
assert!(q["operation"]["criteria"].is_object());
assert!(
q["operation"]["criteria"].get("copy").is_none(),
"copy not permitted here"
);
assert!(q["operation"]["criteria"].get("clarify").is_some());
assert!(q["target"]["criteria"]
.as_object()
.unwrap()
.contains_key("none"));
assert_eq!(q["unambiguous"]["type"], "noul");
assert!(!r.to_string().contains("body"));
}
#[actix_web::test]
async fn op_default_threshold_accepts_nothing() {
let m = Mock::ok(op_reply("read", "c1", 0.99, 0.99));
let e = propose(&rt(Ok(cfg(None)), &m), "open b", "/p/", &ocands(), PERMS)
.await
.unwrap_err();
assert_eq!(e, Fallback::LowConfidence);
}
#[actix_web::test]
async fn op_accept_path_grounds_target() {
let m = Mock::ok(op_reply("read", "c1", 0.9, 0.9));
let p = propose(
&rt(Ok(cfg(Some(0.5))), &m),
"open b",
"/p/",
&ocands(),
PERMS,
)
.await
.unwrap();
assert_eq!(p.operation, "read");
assert_eq!(p.target.as_deref(), Some("/p/b.ttl"));
assert_eq!(p.source, "jev");
let m = Mock::ok(op_reply("create", "none", 0.9, 0.9));
let p = propose(
&rt(Ok(cfg(Some(0.5))), &m),
"make one",
"/p/",
&ocands(),
PERMS,
)
.await
.unwrap();
assert_eq!(
(p.operation.as_str(), p.target.as_deref()),
("create", Some("/p/"))
);
}
#[actix_web::test]
async fn op_rejects_what_wac_does_not_allow() {
let m = Mock::ok(op_reply("copy", "c0", 0.9, 0.9));
assert_eq!(
propose(&rt(Ok(cfg(Some(0.1))), &m), "r", "/p/", &ocands(), PERMS)
.await
.unwrap_err(),
Fallback::Malformed
);
let m = Mock::ok(op_reply("patch_preview", "c1", 0.9, 0.9));
assert_eq!(
propose(&rt(Ok(cfg(Some(0.1))), &m), "r", "/p/", &ocands(), PERMS)
.await
.unwrap_err(),
Fallback::Malformed
);
let m = Mock::ok(op_reply("read", "c9", 0.9, 0.9));
assert_eq!(
propose(&rt(Ok(cfg(Some(0.1))), &m), "r", "/p/", &ocands(), PERMS)
.await
.unwrap_err(),
Fallback::Malformed
);
}
#[actix_web::test]
async fn op_every_fallback_trigger_and_low_confidence() {
let go = |m: Arc<Mock>, c: Result<JevConfig, Fallback>| async move {
propose(&rt(c, &m), "r", "/p/", &ocands(), PERMS)
.await
.unwrap_err()
};
assert_eq!(
go(
Mock::ok(op_reply("read", "c1", 0.9, 0.9)),
Err(Fallback::FlagOff)
)
.await,
Fallback::FlagOff
);
assert_eq!(
go(
Mock::ok(op_reply("read", "c1", 0.9, 0.9)),
Err(Fallback::NoEndpoint)
)
.await,
Fallback::NoEndpoint
);
assert_eq!(
go(Mock::err(Fallback::Timeout), Ok(cfg(Some(0.5)))).await,
Fallback::Timeout
);
assert_eq!(
go(Mock::raw(503, b"x"), Ok(cfg(Some(0.5)))).await,
Fallback::Http(503)
);
assert_eq!(
go(Mock::raw(200, b"<html>"), Ok(cfg(Some(0.5)))).await,
Fallback::Malformed
);
assert_eq!(
go(
Mock::ok(op_reply("read", "c1", 0.2, 0.9)),
Ok(cfg(Some(0.5)))
)
.await,
Fallback::LowConfidence
);
assert_eq!(
go(
Mock::ok(op_reply("read", "c1", 0.9, 0.4)),
Ok(cfg(Some(0.5)))
)
.await,
Fallback::LowConfidence
);
}
#[test]
fn choice_confidence_derived_from_probabilities_when_absent() {
let mut a = Map::new();
a.insert(
"q".into(),
json!({"type":"choice","choice":"x","probabilities":{"x":0.6,"y":0.2,"z":0.2}}),
);
let (_, c) = choice(&a, "q", &["x".into(), "y".into(), "z".into()]).unwrap();
assert!((c - 0.4).abs() < 1e-9);
}
#[test]
fn baseline_shape() {
assert_eq!(
Proposal::baseline().to_json(),
json!({"operation":"clarify","target":null,"source":"baseline"})
);
}
#[actix_web::test]
async fn reqwest_transport_sends_expected_wire_and_headers() {
use wiremock::matchers::{header, method};
use wiremock::{Mock as WMock, MockServer, ResponseTemplate};
let server = MockServer::start().await;
WMock::given(method("POST"))
.and(header("content-type", "application/json"))
.and(header("authorization", "Bearer sekrit"))
.respond_with(ResponseTemplate::new(200).set_body_json(rank_reply(
[0.0, 2.0, 1.0],
0.9,
0.1,
)))
.mount(&server)
.await;
let mut c = cfg(None);
c.endpoint = format!("{}/v1/systemone", server.uri());
c.bearer = Some("sekrit".into());
let r = JevRuntime {
config: Ok(c),
transport: Arc::new(ReqwestTransport),
};
let out = rank(&r, "q", &rcands()).await.unwrap();
assert_eq!(out.order, vec![1, 2, 0]);
let seen = server.received_requests().await.unwrap();
assert_eq!(seen.len(), 1);
let body: Value = serde_json::from_slice(&seen[0].body).unwrap();
assert!(body["questions"]["rel_c0"]["criteria"].is_array());
}
#[actix_web::test]
async fn reqwest_transport_times_out_to_fallback() {
use wiremock::{Mock as WMock, MockServer, ResponseTemplate};
let server = MockServer::start().await;
WMock::given(wiremock::matchers::any())
.respond_with(ResponseTemplate::new(200).set_delay(Duration::from_millis(600)))
.mount(&server)
.await;
let mut c = cfg(None);
c.endpoint = server.uri();
c.timeout = Duration::from_millis(100);
let r = JevRuntime {
config: Ok(c),
transport: Arc::new(ReqwestTransport),
};
assert_eq!(
rank(&r, "q", &rcands()).await.unwrap_err(),
Fallback::Timeout
);
}
}