//! The contract message is what the model actually receives, so the index it carries
//! is checked here rather than left to a daemon test to notice.
use std::path::Path;
use klbr_core::runtime_turn::{contract_message, skill_message};
use klbr_runtime::skills::SkillRoots;
fn write_skill(root: &Path, id: &str, text: &str) {
let directory = root.join(id);
std::fs::create_dir_all(&directory).unwrap();
std::fs::write(directory.join("SKILL.md"), text).unwrap();
}
#[test]
fn the_contract_carries_ids_only_and_names_what_it_refused() {
let builtin = tempfile::tempdir().unwrap();
let managed = tempfile::tempdir().unwrap();
write_skill(
builtin.path(),
"workspace",
"# workspace\n\nBODY-ONLY-MARKER\n",
);
write_skill(managed.path(), "Workspace", "# Workspace\n\nwrong id\n");
let roots = SkillRoots {
builtin: builtin.path().to_path_buf(),
managed: Some(managed.path().to_path_buf()),
};
let contract = contract_message(&roots);
assert!(
contract.contains("Available skill ids: workspace"),
"{contract}"
);
assert!(contract.contains("Not offered: `Workspace`"), "{contract}");
// The body is loaded by id, so no body text may ride along in the index.
assert!(!contract.contains("BODY-ONLY-MARKER"), "{contract}");
assert!(contract.contains("## skills"), "{contract}");
}
#[test]
fn refusals_are_named_up_to_a_bound_and_then_counted() {
let builtin = tempfile::tempdir().unwrap();
for index in 0..7 {
write_skill(
builtin.path(),
&format!("Bad{index}"),
"# bad\n\nuppercase id\n",
);
}
let roots = SkillRoots {
builtin: builtin.path().to_path_buf(),
managed: None,
};
let contract = contract_message(&roots);
assert_eq!(contract.matches("Not offered: `").count(), 4, "{contract}");
assert!(contract.contains("3 more refused ids"), "{contract}");
}
#[test]
fn a_default_loaded_skill_is_its_own_message_and_absence_is_not_a_failure() {
let builtin = tempfile::tempdir().unwrap();
write_skill(
builtin.path(),
"maintenance",
"# maintenance\n\nobserve first\n",
);
let roots = SkillRoots {
builtin: builtin.path().to_path_buf(),
managed: None,
};
let message = skill_message(&roots, "maintenance").unwrap();
assert!(message.starts_with("## skill: maintenance"), "{message}");
assert!(message.ends_with("observe first"), "{message}");
assert!(skill_message(&roots, "absent").is_none());
}
/// The contract is prompt text paid on every request, so it has a budget rather than a style
/// preference. Everything about one subject belongs in that subject's skill body, which is loaded
/// by id when a cell wants it, instead of in the head every cell reads first.
#[test]
fn the_runtime_contract_stays_within_its_prompt_budget() {
const BUDGET_BYTES: usize = 6 * 1024;
let contract = klbr_core::runtime_turn::RUNTIME_CONTRACT;
assert!(
contract.len() <= BUDGET_BYTES,
"the runtime contract is {} bytes, over its {BUDGET_BYTES}-byte budget: move prose that \
belongs to one subject into `skills/<id>/SKILL.md` and point at the id",
contract.len()
);
}