use super::semantic_coverage::Declared;
use super::{InstallError, Result};
pub(crate) const MAX_SOURCE_BYTES: usize = 65_536;
pub(crate) const MAX_FINDINGS: usize = 32;
const MAX_RESPONSE_BYTES: usize = 16_384;
const ANSWER_DEADLINE_MS: u64 = 2000;
const SET_RECHECK: std::time::Duration = std::time::Duration::from_mins(1);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum NotInspected {
Warming,
Unavailable,
}
impl NotInspected {
pub(crate) const fn coverage_reason(self) -> &'static str {
match self {
Self::Warming => "semantic.warming",
Self::Unavailable => "semantic.unavailable",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum SemanticCategory {
Secret,
Pii,
PromptInjection,
}
impl SemanticCategory {
pub(crate) const fn as_str(self) -> &'static str {
match self {
Self::Secret => "secret",
Self::Pii => "pii",
Self::PromptInjection => "prompt_injection",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct SemanticFinding {
pub(crate) category: SemanticCategory,
pub(crate) start: usize,
pub(crate) end: usize,
pub(crate) confidence_milli: u16,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct SemanticReport {
pub(crate) bytes_inspected: usize,
pub(crate) findings: Vec<SemanticFinding>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct CategorySet(u8);
impl CategorySet {
pub(crate) const ALL: Self = Self(0b111);
pub(crate) const NONE: Self = Self(0);
pub(crate) const EVERY: [SemanticCategory; 3] = [
SemanticCategory::Secret,
SemanticCategory::Pii,
SemanticCategory::PromptInjection,
];
pub(crate) const fn with(self, category: SemanticCategory) -> Self {
Self(self.0 | (1 << category as u8))
}
pub(crate) const fn union(self, other: Self) -> Self {
Self(self.0 | other.0)
}
pub(crate) const fn intersection(self, other: Self) -> Self {
Self(self.0 & other.0)
}
pub(crate) const fn contains(self, category: SemanticCategory) -> bool {
self.0 & (1 << category as u8) != 0
}
#[cfg(test)]
pub(crate) fn names(self) -> Vec<&'static str> {
Self::EVERY
.into_iter()
.filter(|category| self.contains(*category))
.map(SemanticCategory::as_str)
.collect()
}
}
#[derive(serde::Deserialize)]
#[serde(deny_unknown_fields)]
struct Wire {
schema_version: u32,
strategy: String,
source_sha256: String,
bytes_inspected: usize,
#[serde(default)]
categories: Option<Vec<SemanticCategory>>,
findings: Vec<SemanticFinding>,
}
pub(crate) fn bound(
config: &crate::core::config::Config,
) -> Option<std::sync::Arc<crate::core::config::IntelligenceRuntimeConfig>> {
let runtime = &config.intelligence_runtime;
if !(runtime.enabled && runtime.accept_proprietary && super::bootstrap::permits_config(runtime))
{
super::semantic_worker::retire();
return None;
}
attached(runtime).then(|| std::sync::Arc::new(runtime.clone()))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Offer {
Offered,
NotOffered,
Unverified,
}
fn attached(runtime: &crate::core::config::IntelligenceRuntimeConfig) -> bool {
#[cfg(any(test, feature = "test-support"))]
if test_seam::invoke_override().is_some() {
return true;
}
offer(runtime) == Offer::Offered
}
pub(crate) fn offer(runtime: &crate::core::config::IntelligenceRuntimeConfig) -> Offer {
match declared(runtime) {
None => Offer::Unverified,
Some(declared) if declared.categories() == CategorySet::NONE => Offer::NotOffered,
Some(_) => Offer::Offered,
}
}
pub(crate) fn declared(
runtime: &crate::core::config::IntelligenceRuntimeConfig,
) -> Option<Declared> {
use std::collections::HashMap;
use std::sync::{Mutex, OnceLock, PoisonError};
use std::time::Instant;
type Known = HashMap<[u8; 32], (Declared, Option<Instant>)>;
static KNOWN: OnceLock<Mutex<Known>> = OnceLock::new();
let settings = serde_json::to_vec(runtime).ok()?;
let key = *blake3::hash(&settings).as_bytes();
let known = KNOWN.get_or_init(Mutex::default);
let now = Instant::now();
let remembered = known
.lock()
.unwrap_or_else(PoisonError::into_inner)
.get(&key)
.filter(|(_, until)| !until.is_some_and(|until| now >= until))
.map(|(declared, _)| declared.clone());
if remembered.is_some() {
return remembered;
}
let (declared, definite) = declares(runtime)?;
let until = (!definite).then(|| now + SET_RECHECK);
known
.lock()
.unwrap_or_else(PoisonError::into_inner)
.insert(key, (declared.clone(), until));
Some(declared)
}
fn declares(runtime: &crate::core::config::IntelligenceRuntimeConfig) -> Option<(Declared, bool)> {
let trust = super::trust_key(&runtime.trust_key_hex).ok()?;
let package = super::install::verified_active(
std::path::Path::new(&runtime.root),
&runtime.manifest_sha256,
&trust,
)
.ok()?;
if !super::health::supports_semantic_detectors(&package) {
return Some((Declared::default(), true));
}
let Some(set) = super::install::semantic_set(&package) else {
use super::install::SemanticState;
return match super::install::semantic_state(&package) {
SemanticState::Unverified => None,
SemanticState::SetNotPresent => Some((Declared::default(), false)),
SemanticState::Offered { .. } | SemanticState::NotOffered => {
Some((Declared::default(), true))
}
};
};
declared_by(&set).map(|declared| (declared, true))
}
pub(super) fn declared_by(set: &super::install::SemanticSet) -> Option<Declared> {
let bytes = super::read_regular(
&set.directory.join(super::manifest::SEMANTIC_DESCRIPTOR),
super::manifest::MAX_SEMANTIC_DESCRIPTOR,
)
.ok()?;
if super::sha256(&bytes) != set.descriptor_sha256 {
return None;
}
Declared::parse(&bytes).filter(|declared| declared.categories() != CategorySet::NONE)
}
pub(crate) fn inspect_bound(
config: &crate::core::config::IntelligenceRuntimeConfig,
text: &str,
) -> std::result::Result<(SemanticReport, CategorySet), NotInspected> {
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
if text.len() > MAX_SOURCE_BYTES || text.contains('\0') {
return Err(NotInspected::Unavailable);
}
let deadline = Instant::now() + Duration::from_millis(ANSWER_DEADLINE_MS);
let deadline_unix_ms = SystemTime::now()
.duration_since(UNIX_EPOCH)
.ok()
.and_then(|now| u64::try_from(now.as_millis()).ok())
.and_then(|now| now.checked_add(ANSWER_DEADLINE_MS))
.ok_or(NotInspected::Unavailable)?;
let ask = |piece: &str| -> std::result::Result<(SemanticReport, CategorySet), NotInspected> {
#[cfg(any(test, feature = "test-support"))]
if let Some(stand_in) = test_seam::invoke_override() {
let raw = stand_in(config, piece).map_err(|_| NotInspected::Unavailable)?;
return validate_answer(&raw, piece, false).map_err(|_| NotInspected::Unavailable);
}
let (raw, names_categories) = invoke(config, piece, deadline, deadline_unix_ms)?;
validate_answer(&raw, piece, names_categories).map_err(|_| NotInspected::Unavailable)
};
let (mut report, examined) = ask(text)?;
while report.bytes_inspected < text.len()
&& report.findings.len() >= MAX_FINDINGS
&& deadline.saturating_duration_since(Instant::now())
>= Duration::from_millis(CONTINUE_MIN_MS)
{
let resume = report.bytes_inspected;
let from = continuation_start(text, resume);
let Ok((next, next_examined)) = ask(&text[from..]) else {
break;
};
let reached = from + next.bytes_inspected;
if next_examined != examined || reached <= resume {
break;
}
report.findings.extend(
next.findings
.into_iter()
.filter(|finding| from + finding.start >= resume)
.map(|finding| SemanticFinding {
start: from + finding.start,
end: from + finding.end,
..finding
}),
);
report.bytes_inspected = reached;
}
Ok((report, examined))
}
const CONTINUATION_CONTEXT_BYTES: usize = 512;
const CONTINUE_MIN_MS: u64 = 300;
pub(crate) fn continuation_start(text: &str, resume: usize) -> usize {
let mut from = resume.saturating_sub(CONTINUATION_CONTEXT_BYTES);
while !text.is_char_boundary(from) {
from += 1;
}
text[from..resume]
.find(char::is_whitespace)
.map_or(resume, |at| {
let space = from + at;
space + text[space..].chars().next().map_or(0, char::len_utf8)
})
}
#[cfg(any(test, feature = "test-support"))]
pub mod test_seam {
use super::Result;
use crate::core::config::IntelligenceRuntimeConfig;
use std::cell::Cell;
pub type Invoke = fn(&IntelligenceRuntimeConfig, &str) -> Result<String>;
thread_local! {
static INVOKE: Cell<Option<Invoke>> = const { Cell::new(None) };
}
pub(super) fn invoke_override() -> Option<Invoke> {
INVOKE.with(Cell::get)
}
pub fn stand_in(invoke: Invoke) -> impl Drop {
struct Reset;
impl Drop for Reset {
fn drop(&mut self) {
INVOKE.with(|slot| slot.set(None));
}
}
INVOKE.with(|slot| slot.set(Some(invoke)));
Reset
}
pub fn clean_answer(text: &str) -> String {
serde_json::json!({
"schema_version": 1,
"strategy": "semantic_v1",
"source_sha256": super::super::sha256(text.as_bytes()),
"bytes_inspected": text.len(),
"findings": [],
})
.to_string()
}
}
#[cfg(test)]
pub(crate) fn validate(raw: &str, text: &str) -> Result<SemanticReport> {
validate_answer(raw, text, false).map(|(report, _)| report)
}
fn validate_answer(
raw: &str,
text: &str,
names_categories: bool,
) -> Result<(SemanticReport, CategorySet)> {
if raw.len() > MAX_RESPONSE_BYTES {
return Err(InstallError::Exchange);
}
let wire: Wire = serde_json::from_str(raw).map_err(|_| InstallError::Exchange)?;
let examined = match (&wire.categories, names_categories) {
(None, false) => CategorySet::ALL,
(Some(named), true)
if !named.is_empty() && named.windows(2).all(|pair| pair[0] < pair[1]) =>
{
named
.iter()
.fold(CategorySet::NONE, |set, category| set.with(*category))
}
_ => return Err(InstallError::Exchange),
};
let bytes = wire.bytes_inspected;
let valid = wire.schema_version == 1
&& wire.findings.iter().all(|f| examined.contains(f.category))
&& wire.strategy == "semantic_v1"
&& wire.source_sha256 == super::sha256(text.as_bytes())
&& bytes <= text.len()
&& text.is_char_boundary(bytes)
&& wire.findings.len() <= MAX_FINDINGS
&& wire.findings.iter().all(|f| {
f.start < f.end
&& f.end <= bytes
&& text.is_char_boundary(f.start)
&& text.is_char_boundary(f.end)
&& f.confidence_milli <= 1000
})
&& wire
.findings
.windows(2)
.all(|pair| pair[0].end <= pair[1].start);
if !valid {
return Err(InstallError::Exchange);
}
Ok((
SemanticReport {
bytes_inspected: bytes,
findings: wire.findings,
},
examined,
))
}
fn invoke(
config: &crate::core::config::IntelligenceRuntimeConfig,
text: &str,
deadline: std::time::Instant,
deadline_unix_ms: u64,
) -> std::result::Result<(String, bool), NotInspected> {
match selection(config) {
Some(true) => {}
None if super::semantic_worker::state(config).0 == "warming" => {
return Err(NotInspected::Warming);
}
Some(false) | None => return Err(NotInspected::Unavailable),
}
let input = request_input(text, declared(config).as_ref()).to_string();
let answer = super::semantic_worker::request(config, &input, deadline, deadline_unix_ms)?;
if !selected(config) {
return Err(NotInspected::Unavailable);
}
Ok(answer)
}
fn request_input(text: &str, declared: Option<&Declared>) -> serde_json::Value {
let mut input = serde_json::json!({
"schema_version": 1,
"categories": ["secret", "pii", "prompt_injection"],
"text": text,
"max_findings": MAX_FINDINGS,
});
if declared.is_some_and(Declared::declares_coverage) {
input["coverage_acknowledged"] = true.into();
}
input
}
fn selected(config: &crate::core::config::IntelligenceRuntimeConfig) -> bool {
selection(config) == Some(true)
}
fn selection(config: &crate::core::config::IntelligenceRuntimeConfig) -> Option<bool> {
if crate::core::config::Config::load_global().intelligence_runtime != *config {
return Some(false);
}
match standing(std::path::Path::new(&config.root), &config.manifest_sha256) {
Standing::Active => Some(true),
Standing::Unknown => None,
Standing::Moved => {
super::semantic_worker::retire();
Some(false)
}
}
}
#[derive(Debug, PartialEq, Eq)]
enum Standing {
Active,
Moved,
Unknown,
}
fn standing(root: &std::path::Path, manifest_sha256: &str) -> Standing {
match super::install::active_manifest(root) {
Ok(active) if active == manifest_sha256 => Standing::Active,
Err(InstallError::Busy) => match super::install::active_manifest_unlocked(root) {
Ok(active) if active == manifest_sha256 => Standing::Active,
_ => Standing::Unknown,
},
Ok(_) | Err(_) => Standing::Moved,
}
}
pub(crate) fn worker_state(
config: &crate::core::config::IntelligenceRuntimeConfig,
) -> (&'static str, Vec<String>) {
super::semantic_worker::state(config)
}
#[cfg(test)]
mod tests {
use super::*;
fn response(text: &str, findings: &serde_json::Value) -> serde_json::Value {
serde_json::json!({
"schema_version": 1,
"strategy": "semantic_v1",
"source_sha256": super::super::sha256(text.as_bytes()),
"bytes_inspected": text.len(),
"findings": findings,
})
}
#[test]
fn valid_spans_are_accepted() {
let text = "keep this; drop that\n";
let raw = response(
text,
&serde_json::json!([{"category":"prompt_injection","start":11,"end":20,"confidence_milli":900}]),
);
let report = validate(&raw.to_string(), text).expect("valid");
assert_eq!(report.findings.len(), 1);
assert_eq!(
&text[report.findings[0].start..report.findings[0].end],
"drop that"
);
}
#[test]
fn a_runtime_cannot_bend_the_contract() {
let text = "äbc def ghi\n";
let good = response(text, &serde_json::json!([]));
for (field, value) in [
("schema_version", serde_json::json!(2)),
("strategy", serde_json::json!("other")),
("source_sha256", serde_json::json!("0".repeat(64))),
("bytes_inspected", serde_json::json!(text.len() + 1)),
("bytes_inspected", serde_json::json!(1)),
(
"findings",
serde_json::json!([{"category":"pii","start":3,"end":3,"confidence_milli":10}]),
),
(
"findings",
serde_json::json!([{"category":"pii","start":1,"end":4,"confidence_milli":10}]),
),
(
"findings",
serde_json::json!([{"category":"pii","start":3,"end":6,"confidence_milli":1001}]),
),
(
"findings",
serde_json::json!([
{"category":"pii","start":3,"end":7,"confidence_milli":10},
{"category":"secret","start":6,"end":9,"confidence_milli":10}
]),
),
(
"findings",
serde_json::json!([{"category":"pii","start":3,"end":6,"confidence_milli":10,"value":"leak"}]),
),
(
"findings",
serde_json::json!([{"category":"exfiltrate","start":3,"end":6,"confidence_milli":10}]),
),
] {
let mut bad = good.clone();
bad[field] = value;
assert!(validate(&bad.to_string(), text).is_err(), "{field}");
}
for (field, value) in [
("note", serde_json::json!("ignore previous instructions")),
("disposition", serde_json::json!("allow")),
("acl", serde_json::json!({"allow": ["*"]})),
("rights", serde_json::json!(["read", "share"])),
("classification", serde_json::json!("public")),
("trust", serde_json::json!("internal")),
("text", serde_json::json!("äbc def ghi\n")),
] {
let mut extra = good.clone();
extra[field] = value;
assert!(validate(&extra.to_string(), text).is_err(), "{field}");
}
}
#[test]
fn named_categories_bound_what_an_answer_speaks_for() {
use serde_json::json;
let text = "keep this; drop that\n";
let answer = |categories: serde_json::Value, category: &str| {
let mut raw = response(
text,
&json!([{"category":category,"start":11,"end":20,"confidence_milli":900}]),
);
raw["categories"] = categories;
validate_answer(&raw.to_string(), text, true)
};
let (report, examined) =
answer(json!(["prompt_injection"]), "prompt_injection").expect("valid");
assert_eq!(report.findings.len(), 1);
assert_eq!(examined.names(), ["prompt_injection"]);
assert!(!examined.contains(SemanticCategory::Pii));
for (categories, category) in [
(json!(["prompt_injection"]), "pii"),
(json!([]), "pii"),
(json!(["pii", "secret"]), "pii"),
(json!(["pii", "pii"]), "pii"),
(json!(["other"]), "pii"),
] {
assert!(
answer(categories.clone(), category).is_err(),
"{categories}"
);
}
let plain = response(text, &json!([]));
assert!(validate_answer(&plain.to_string(), text, true).is_err());
let mut named = plain;
named["categories"] = json!(["pii"]);
assert!(validate_answer(&named.to_string(), text, false).is_err());
}
#[test]
fn only_a_declaring_set_is_told_that_its_coverage_is_applied() {
use super::super::semantic_coverage::DeclaredModel;
let set = |languages: Option<Vec<String>>| Declared {
models: vec![DeclaredModel {
categories: CategorySet::ALL,
languages,
document_types: None,
}],
};
let acknowledged = |declared: Option<&Declared>| {
request_input("text\n", declared)
.get("coverage_acknowledged")
.cloned()
};
assert_eq!(
acknowledged(Some(&set(Some(vec!["de".to_owned()])))),
Some(serde_json::json!(true))
);
assert_eq!(acknowledged(Some(&set(None))), None);
assert_eq!(acknowledged(None), None);
}
#[test]
fn oversized_input_is_never_sent() {
let runtime = crate::core::config::IntelligenceRuntimeConfig::default();
assert!(inspect_bound(&runtime, &"x".repeat(MAX_SOURCE_BYTES + 1)).is_err());
assert!(inspect_bound(&runtime, "a\0b").is_err());
}
#[test]
fn a_runtime_that_does_not_offer_the_capability_is_not_attached() {
let directory = tempfile::tempdir().expect("runtime root");
let root = std::fs::canonicalize(directory.path()).expect("canonical runtime root");
let config = crate::core::config::Config {
intelligence_runtime: crate::core::config::IntelligenceRuntimeConfig {
enabled: true,
accept_proprietary: true,
staging: true,
root: root.to_str().expect("utf-8 runtime root").to_owned(),
..Default::default()
},
..Default::default()
};
assert!(bound(&config).is_none());
assert_eq!(offer(&config.intelligence_runtime), Offer::Unverified);
let _runtime = test_seam::stand_in(|_, text| Ok(test_seam::clean_answer(text)));
assert!(bound(&config).is_some());
}
#[test]
fn the_rest_after_the_findings_bound_is_inspected_too() {
fn capped(
_: &crate::core::config::IntelligenceRuntimeConfig,
text: &str,
) -> Result<String> {
let mentions: Vec<usize> = text.match_indices("Anna").map(|(at, _)| at).collect();
let inspected = mentions.get(MAX_FINDINGS).copied().unwrap_or(text.len());
let findings: Vec<_> = mentions
.iter()
.take(MAX_FINDINGS)
.map(|at| serde_json::json!({"category": "pii", "start": at, "end": at + 4, "confidence_milli": 900}))
.collect();
let mut answer = response(text, &serde_json::json!(findings));
answer["bytes_inspected"] = inspected.into();
Ok(answer.to_string())
}
let text = "Anna Meier unterschrieb.\n".repeat(80);
let _runtime = test_seam::stand_in(capped);
let (report, _) = inspect_bound(&Default::default(), &text).expect("inspected");
assert_eq!(report.bytes_inspected, text.len());
assert_eq!(report.findings.len(), 80);
assert!(
report
.findings
.windows(2)
.all(|pair| pair[0].end <= pair[1].start)
);
assert!(
report
.findings
.iter()
.all(|f| &text[f.start..f.end] == "Anna")
);
}
#[cfg(unix)]
#[test]
fn a_held_store_is_not_taken_for_a_moved_package() {
let _data = crate::core::data_dir::isolated_data_dir();
use std::os::unix::fs::PermissionsExt;
let directory = tempfile::Builder::new()
.permissions(std::fs::Permissions::from_mode(0o700))
.tempdir()
.expect("runtime root");
let root = std::fs::canonicalize(directory.path()).expect("canonical runtime root");
assert_eq!(
super::super::install::active_manifest(&root).expect("a fresh store is read"),
"none"
);
assert_eq!(standing(&root, "none"), Standing::Active);
assert_eq!(standing(&root, "another"), Standing::Moved);
let held = std::fs::File::open(root.join("install.lock")).expect("store lock");
super::super::install::lock_exclusive_bounded(&held).expect("hold the store");
assert_eq!(standing(&root, "none"), Standing::Active);
assert_eq!(standing(&root, "another"), Standing::Unknown);
drop(held);
assert_eq!(standing(&root, "none"), Standing::Active);
}
mod worker {
use super::super::NotInspected;
use crate::core::intelligence_runtime::semantic_worker::{
Event, Launcher, Link, Phase, Started, Supervisor, Timing,
};
use std::collections::VecDeque;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::mpsc::{Receiver, Sender, SyncSender, channel, sync_channel};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
const KEY: [u8; 32] = [1; 32];
const OTHER: [u8; 32] = [2; 32];
const LONG: Duration = Duration::from_secs(10);
const HOUR: Duration = Duration::from_hours(1);
struct Peer {
events: SyncSender<Event>,
requests: Receiver<Vec<u8>>,
stopped: Arc<AtomicBool>,
}
struct Wire {
requests: Sender<Vec<u8>>,
stopped: Arc<AtomicBool>,
}
impl Link for Wire {
fn send(&mut self, line: Vec<u8>) -> bool {
self.requests.send(line).is_ok()
}
fn stop(&mut self) {
self.stopped.store(true, Ordering::SeqCst);
}
}
fn child(version: &'static str) -> (Option<Started>, Peer) {
let (events, incoming) = sync_channel(4);
let (requests, sent) = channel();
let stopped = Arc::new(AtomicBool::new(false));
let link = Wire {
requests,
stopped: Arc::clone(&stopped),
};
let started = Started {
link: Box::new(link),
events: incoming,
version,
takes_lease: false,
lease: None,
};
let peer = Peer {
events,
requests: sent,
stopped,
};
(Some(started), peer)
}
struct Children {
next: Mutex<VecDeque<Option<Started>>>,
launched: AtomicUsize,
gate: Mutex<Option<Receiver<()>>>,
}
impl Launcher for Arc<Children> {
fn launch(
&self,
_: &crate::core::config::IntelligenceRuntimeConfig,
) -> Option<Started> {
self.launched.fetch_add(1, Ordering::SeqCst);
let gate = self.gate.lock().expect("gate").take();
if let Some(gate) = gate {
let _ = gate.recv_timeout(LONG);
}
self.next.lock().expect("starts").pop_front().flatten()
}
}
fn supervised(
starts: Vec<Option<Started>>,
gate: Option<Receiver<()>>,
timing: Timing,
) -> (Arc<Supervisor>, Arc<Children>) {
let children = Arc::new(Children {
next: Mutex::new(starts.into()),
launched: AtomicUsize::new(0),
gate: Mutex::new(gate),
});
let supervisor = Supervisor::with(Box::new(Arc::clone(&children)), timing);
(supervisor, children)
}
fn timing(idle: Duration, drain: Duration, backoff: Duration) -> Timing {
Timing {
idle,
drain,
backoff: [backoff; 3],
}
}
fn ask(
supervisor: &Arc<Supervisor>,
key: [u8; 32],
wait: Duration,
) -> Result<(String, bool), NotInspected> {
let config = crate::core::config::IntelligenceRuntimeConfig::default();
supervisor.request(&config, key, "{}", Instant::now() + wait, 0)
}
fn asking(
supervisor: &Arc<Supervisor>,
wait: Duration,
) -> std::thread::JoinHandle<Result<(String, bool), NotInspected>> {
let supervisor = Arc::clone(supervisor);
std::thread::spawn(move || ask(&supervisor, KEY, wait))
}
fn line(value: &serde_json::Value) -> Event {
Event::Line(format!("{value}\n").into_bytes())
}
fn ready(versions: &[&str]) -> Event {
line(&serde_json::json!({
"schema_version": 1, "status": "ready", "versions": versions
}))
}
fn output(answer: &str) -> Event {
line(&serde_json::json!({"status": "ok", "output": answer}))
}
fn eventually(what: &str, reached: impl Fn() -> bool) {
let end = Instant::now() + LONG;
while !reached() {
assert!(Instant::now() < end, "never: {what}");
std::thread::sleep(Duration::from_millis(2));
}
}
fn in_phase(supervisor: &Arc<Supervisor>, key: [u8; 32], phase: Phase) {
eventually(phase.as_str(), || supervisor.phase(key).0 == phase);
}
fn warm(supervisor: &Arc<Supervisor>, key: [u8; 32], peer: &Peer) {
assert_eq!(ask(supervisor, key, LONG), Err(NotInspected::Warming));
peer.events.send(ready(&["1.1.0"])).expect("worker output");
in_phase(supervisor, key, Phase::Warm);
}
#[test]
fn a_cold_start_is_reported_as_warming_and_never_waited_for() {
let (open, gate) = channel();
let (started, peer) = child("1.1.0");
let (supervisor, children) =
supervised(vec![started], Some(gate), timing(HOUR, LONG, HOUR));
assert_eq!(supervisor.phase(KEY).0, Phase::Cold);
let began = Instant::now();
assert_eq!(ask(&supervisor, KEY, LONG), Err(NotInspected::Warming));
assert_eq!(ask(&supervisor, KEY, LONG), Err(NotInspected::Warming));
assert!(
began.elapsed() < LONG / 2,
"an inspection waited for the worker to start"
);
assert_eq!(supervisor.phase(KEY).0, Phase::Warming);
open.send(()).expect("start released");
peer.events.send(ready(&["1.1.0"])).expect("worker output");
in_phase(&supervisor, KEY, Phase::Warm);
assert_eq!(supervisor.phase(KEY).1, ["1.1.0"]);
let inspection = asking(&supervisor, LONG);
let request = peer.requests.recv_timeout(LONG).expect("request line");
assert_eq!(request.last(), Some(&b'\n'));
let request: serde_json::Value =
serde_json::from_slice(&request).expect("request object");
assert_eq!(request["version"], "1.1.0");
assert_eq!(request["input"], "{}");
peer.events.send(output("answer")).expect("worker output");
assert_eq!(
inspection.join().expect("inspection"),
Ok(("answer".to_owned(), true))
);
assert_eq!(children.launched.load(Ordering::SeqCst), 1);
}
#[test]
fn an_entitlement_refusal_is_not_inspected_never_a_clean_answer() {
let (started, peer) = child("1.1.0");
let (supervisor, children) = supervised(vec![started], None, timing(HOUR, LONG, HOUR));
warm(&supervisor, KEY, &peer);
let expired = asking(&supervisor, LONG);
peer.requests.recv_timeout(LONG).expect("request line");
let refusal = serde_json::json!({"status": "refused", "reason": "entitlement"});
peer.events.send(line(&refusal)).expect("worker output");
assert_eq!(
expired.join().expect("inspection"),
Err(NotInspected::Unavailable)
);
assert_eq!(supervisor.phase(KEY).0, Phase::Warm, "the worker is kept");
let renewed = asking(&supervisor, LONG);
peer.requests.recv_timeout(LONG).expect("request line");
peer.events.send(output("answer")).expect("worker output");
assert_eq!(
renewed.join().expect("inspection"),
Ok(("answer".to_owned(), true))
);
assert_eq!(children.launched.load(Ordering::SeqCst), 1);
}
#[test]
fn a_late_answer_is_never_taken_for_the_next_request() {
let (started, peer) = child("1.1.0");
let (supervisor, _) = supervised(vec![started], None, timing(HOUR, HOUR, HOUR));
warm(&supervisor, KEY, &peer);
assert_eq!(
ask(&supervisor, KEY, Duration::from_millis(50)),
Err(NotInspected::Unavailable)
);
peer.requests.recv_timeout(LONG).expect("first request");
assert_eq!(supervisor.phase(KEY).0, Phase::Warm, "the worker is kept");
let next = asking(&supervisor, LONG);
assert!(
peer.requests
.recv_timeout(Duration::from_millis(200))
.is_err(),
"a request was written while an answer was outstanding"
);
peer.events.send(output("late")).expect("worker output");
peer.requests.recv_timeout(LONG).expect("second request");
peer.events.send(output("own")).expect("worker output");
assert_eq!(
next.join().expect("inspection"),
Ok(("own".to_owned(), true))
);
}
#[test]
fn an_answer_that_never_comes_stops_the_worker() {
let (started, peer) = child("1.1.0");
let drain = Duration::from_millis(50);
let (supervisor, children) = supervised(vec![started], None, timing(HOUR, drain, HOUR));
warm(&supervisor, KEY, &peer);
assert_eq!(
ask(&supervisor, KEY, Duration::from_millis(50)),
Err(NotInspected::Unavailable)
);
assert_eq!(ask(&supervisor, KEY, LONG), Err(NotInspected::Unavailable));
assert_eq!(supervisor.phase(KEY).0, Phase::Failed);
assert!(peer.stopped.load(Ordering::SeqCst));
assert_eq!(
peer.requests.try_iter().count(),
1,
"only the first request was written"
);
assert_eq!(children.launched.load(Ordering::SeqCst), 1, "backoff holds");
}
#[test]
fn a_caller_never_waits_past_its_deadline_for_a_busy_worker() {
let (started, peer) = child("1.1.0");
let (supervisor, _) = supervised(vec![started], None, timing(HOUR, HOUR, HOUR));
warm(&supervisor, KEY, &peer);
let holder = asking(&supervisor, LONG);
peer.requests.recv_timeout(LONG).expect("first request");
let (done, finished) = channel();
let second = Arc::clone(&supervisor);
std::thread::spawn(move || {
let _ = done.send(ask(&second, KEY, Duration::from_millis(100)));
});
assert_eq!(
finished
.recv_timeout(LONG / 2)
.expect("a waiting caller outlived its deadline"),
Err(NotInspected::Unavailable)
);
assert!(
peer.requests.try_recv().is_err(),
"nothing else was written"
);
peer.events.send(output("held")).expect("worker output");
assert_eq!(
holder.join().expect("inspection"),
Ok(("held".to_owned(), true))
);
}
#[test]
fn restarts_are_bounded_and_wait_their_turn() {
let zero = Duration::ZERO;
let (supervisor, children) = supervised(Vec::new(), None, timing(HOUR, LONG, zero));
for _ in 0..4 {
assert_eq!(ask(&supervisor, KEY, LONG), Err(NotInspected::Warming));
in_phase(&supervisor, KEY, Phase::Failed);
}
for _ in 0..3 {
assert_eq!(ask(&supervisor, KEY, LONG), Err(NotInspected::Unavailable));
}
assert_eq!(supervisor.phase(KEY).0, Phase::Failed);
assert_eq!(children.launched.load(Ordering::SeqCst), 4);
let (waiting, children) = supervised(Vec::new(), None, timing(HOUR, LONG, HOUR));
assert_eq!(ask(&waiting, KEY, LONG), Err(NotInspected::Warming));
in_phase(&waiting, KEY, Phase::Failed);
assert_eq!(ask(&waiting, KEY, LONG), Err(NotInspected::Unavailable));
assert_eq!(children.launched.load(Ordering::SeqCst), 1);
}
#[test]
fn a_worker_without_the_negotiated_contract_fails() {
let (started, peer) = child("1.0.0");
let (supervisor, _) = supervised(vec![started], None, timing(HOUR, LONG, HOUR));
assert_eq!(ask(&supervisor, KEY, LONG), Err(NotInspected::Warming));
peer.events.send(ready(&["1.1.0"])).expect("worker output");
in_phase(&supervisor, KEY, Phase::Failed);
assert!(peer.stopped.load(Ordering::SeqCst));
assert!(supervisor.phase(KEY).1.is_empty());
}
#[test]
fn changed_settings_stop_the_worker_and_start_another() {
let (first, earlier) = child("1.1.0");
let (second, later) = child("1.1.0");
let (supervisor, children) =
supervised(vec![first, second], None, timing(HOUR, LONG, HOUR));
warm(&supervisor, KEY, &earlier);
warm(&supervisor, OTHER, &later);
assert!(earlier.stopped.load(Ordering::SeqCst));
assert_eq!(children.launched.load(Ordering::SeqCst), 2);
assert_eq!(supervisor.phase(KEY).0, Phase::Cold);
assert!(earlier.requests.try_recv().is_err());
supervisor.retire();
assert!(later.stopped.load(Ordering::SeqCst));
assert_eq!(supervisor.phase(OTHER).0, Phase::Cold);
}
#[test]
fn an_idle_exit_is_cold_and_an_early_exit_is_a_failure() {
let (first, idle) = child("1.1.0");
let (second, _next) = child("1.1.0");
let (supervisor, children) = supervised(
vec![first, second],
None,
timing(Duration::ZERO, LONG, HOUR),
);
warm(&supervisor, KEY, &idle);
idle.events.send(Event::Closed).expect("worker output");
in_phase(&supervisor, KEY, Phase::Cold);
assert_eq!(ask(&supervisor, KEY, LONG), Err(NotInspected::Warming));
eventually("second start", || {
children.launched.load(Ordering::SeqCst) == 2
});
let (only, early) = child("1.1.0");
let (supervisor, _) = supervised(vec![only], None, timing(HOUR, LONG, HOUR));
warm(&supervisor, KEY, &early);
early.events.send(Event::Closed).expect("worker output");
in_phase(&supervisor, KEY, Phase::Failed);
assert_eq!(ask(&supervisor, KEY, LONG), Err(NotInspected::Unavailable));
}
}
}