use std::fs;
use std::path::Path;
use warble_cli::{
compile_project_to_ir_with_overlay, default_component_sources, BuiltinContextResolver,
};
const PREPARED_WIDGETS: &str = r#"{
"context_version": 2,
"parseable": true,
"metrics": [
{
"name": "id",
"owner": "widgets",
"declared": false,
"additivity": null
}
],
"dimensions": [],
"models": [
{
"name": "widgets",
"has_timestamp": false,
"columns": [
"id"
]
}
],
"lineage": {
"nodes": [
{
"id": "model:widgets",
"kind": "model"
}
],
"edges": []
},
"lineage_diagnostics": [],
"analysis": {
"impact": {
"model:widgets": {
"downstream": [],
"severity": {
"rank": 0,
"name": "none"
}
}
},
"consumers": {
"queries": 0,
"dashboards": 0
}
}
}"#;
fn overlay_compile(dir: &Path, overlay: Option<&Path>) -> Result<serde_json::Value, String> {
let sources = default_component_sources(dir)?;
compile_project_to_ir_with_overlay(dir, &sources, &BuiltinContextResolver, overlay)
}
fn overlay_write(dir: &Path, body: &str) -> std::path::PathBuf {
let path = dir.join("overlay.yml");
fs::write(&path, body).unwrap();
path
}
fn overlay_write_project(dir: &Path, profile_extra: &str) {
fs::create_dir_all(dir.join("context")).unwrap();
fs::create_dir_all(dir.join("components/asker/steps")).unwrap();
fs::create_dir_all(dir.join("components/writer/steps")).unwrap();
fs::write(
dir.join("profile.yml"),
format!(
"profile: fixture\ncontext:\n project: ./context/binding.yml\n{profile_extra}components:\n - use: asker\n"
),
)
.unwrap();
fs::write(dir.join("context/context.json"), PREPARED_WIDGETS).unwrap();
fs::write(
dir.join("context/binding.yml"),
"kind: prepared\nproject: widgets-layer\ndocument: context/context.json\n",
)
.unwrap();
for (id, capability, param) in [
("asker", "llm:cheap", "tone"),
("writer", "data_write", "table"),
] {
fs::write(
dir.join(format!("components/{id}/component.yml")),
format!(
r#"
id: {id}
verb: {id}
type: analytical
realization_kind: skill
binding_mode: runtime_selected
context_precondition: []
params:
- {{ name: {param}, bind: optional, default: "unset" }}
llm_steps:
- {{ name: step, tier: cheap, prompt_ref: steps/step.md }}
trigger: {{ kind: one_shot }}
guardrails:
- {{ name: read_only_execution, locked: true }}
required_capabilities: [{capability}]
borrowed_actions: []
effect:
render_blocks: []
outcome:
kind: none
"#
),
)
.unwrap();
fs::write(
dir.join(format!("components/{id}/steps/step.md")),
"Do the thing.\n",
)
.unwrap();
}
}
#[test]
fn no_overlay_compiles_exactly_as_before() {
let project = tempfile::tempdir().unwrap();
overlay_write_project(project.path(), "");
let ir = overlay_compile(project.path(), None).expect("the unpatched project must compile");
let components = ir["components"].as_array().unwrap();
assert_eq!(components.len(), 1);
assert_eq!(components[0]["id"], "asker");
}
#[test]
fn an_overlay_mounts_unmounts_binds_and_replaces_the_charter() {
let project = tempfile::tempdir().unwrap();
overlay_write_project(
project.path(),
"system_prompt: |\n Base charter.\nconfig:\n capability_ceiling: [llm:cheap, data_write]\n",
);
let overlay = overlay_write(
project.path(),
"overlay: 1\nsystem_prompt: |\n Patched charter.\nmount:\n - use: writer\n bind: { table: orders }\nbind:\n asker:\n tone: formal\n",
);
let ir = overlay_compile(project.path(), Some(&overlay)).expect("a valid overlay must compile");
let components = ir["components"].as_array().unwrap();
assert_eq!(components.len(), 2, "the patch adds one mount");
let ids: Vec<&str> = components
.iter()
.map(|c| c["id"].as_str().unwrap())
.collect();
assert!(ids.contains(&"asker") && ids.contains(&"writer"), "{ids:?}");
let asker = components.iter().find(|c| c["id"] == "asker").unwrap();
let writer = components.iter().find(|c| c["id"] == "writer").unwrap();
assert_eq!(
asker["binds"]["tone"], "formal",
"patched bind reaches the IR"
);
assert_eq!(writer["binds"]["table"], "orders", "a new mount's bind too");
assert!(
asker["brief"].as_str().unwrap().contains("Patched charter"),
"{}",
asker["brief"]
);
}
#[test]
fn an_overlay_mount_can_mark_a_component_callee_only() {
let project = tempfile::tempdir().unwrap();
overlay_write_project(project.path(), "");
let overlay = overlay_write(
project.path(),
"overlay: 1\nmount:\n - use: writer\n entrypoint: false\n",
);
let ir = overlay_compile(project.path(), Some(&overlay))
.expect("an overlay may add a callee-only mount");
let writer = ir["components"]
.as_array()
.unwrap()
.iter()
.find(|component| component["id"] == "writer")
.expect("writer mount reaches IR");
assert_eq!(writer["entrypoint"], false);
}
#[test]
fn an_overlay_bind_merges_rather_than_replacing() {
let project = tempfile::tempdir().unwrap();
overlay_write_project(project.path(), "");
fs::write(
project.path().join("profile.yml"),
"profile: fixture\ncontext:\n project: ./context/binding.yml\ncomponents:\n - use: asker\n bind:\n tone: formal\n",
)
.unwrap();
let overlay = overlay_write(
project.path(),
"overlay: 1\nbind:\n asker:\n other: 1\n",
);
let ir = overlay_compile(project.path(), Some(&overlay)).expect("merging bind must compile");
let binds = &ir["components"][0]["binds"];
assert_eq!(binds["tone"], "formal", "the base value survives the patch");
}
#[test]
fn an_empty_overlay_is_a_no_op() {
let project = tempfile::tempdir().unwrap();
overlay_write_project(project.path(), "");
let overlay = overlay_write(project.path(), "overlay: 1\n");
let patched =
overlay_compile(project.path(), Some(&overlay)).expect("an empty overlay is legal");
let plain = overlay_compile(project.path(), None).unwrap();
assert_eq!(
patched, plain,
"an empty overlay must change nothing at all"
);
}
#[test]
fn a_patch_mounting_a_component_outside_the_ceiling_is_refused() {
let project = tempfile::tempdir().unwrap();
overlay_write_project(
project.path(),
"config:\n capability_ceiling: [llm:cheap]\n",
);
let overlay = overlay_write(project.path(), "overlay: 1\nmount:\n - use: writer\n");
let err = overlay_compile(project.path(), Some(&overlay))
.expect_err("a patch may not mount past the profile's ceiling");
assert!(err.contains("writer"), "{err}");
assert!(err.contains("data_write"), "{err}");
assert!(err.contains("capability_ceiling"), "{err}");
}
#[test]
fn mounting_an_already_mounted_id_is_refused() {
let project = tempfile::tempdir().unwrap();
overlay_write_project(project.path(), "");
let overlay = overlay_write(project.path(), "overlay: 1\nmount:\n - use: asker\n");
let err = overlay_compile(project.path(), Some(&overlay))
.expect_err("mounting what is already mounted must be refused");
assert!(err.contains("already mounts"), "{err}");
assert!(err.contains("asker"), "{err}");
}
#[test]
fn unmounting_an_absent_id_is_refused() {
let project = tempfile::tempdir().unwrap();
overlay_write_project(project.path(), "");
let overlay = overlay_write(project.path(), "overlay: 1\nunmount:\n - nosuch\n");
let err = overlay_compile(project.path(), Some(&overlay))
.expect_err("unmounting something absent must be refused, not silently do nothing");
assert!(err.contains("nosuch"), "{err}");
assert!(err.contains("does not mount"), "{err}");
}
#[test]
fn unmounting_a_referenced_callee_fails_post_overlay_graph_validation() {
let project = tempfile::tempdir().unwrap();
overlay_write_project(project.path(), "");
fs::write(
project.path().join("profile.yml"),
"profile: fixture\ncontext:\n project: ./context/binding.yml\ncomponents:\n - use: asker\n - use: writer\n entrypoint: false\n",
)
.unwrap();
fs::write(
project.path().join("components/asker/component.yml"),
r#"
id: asker
verb: asker
type: analytical
realization_kind: skill
binding_mode: runtime_selected
context_precondition: []
params:
- { name: tone, bind: optional, default: "unset" }
llm_steps:
- name: step
tier: cheap
prompt_ref: steps/step.md
component_calls:
- { alias: write, component: writer }
trigger: { kind: one_shot }
guardrails:
- { name: read_only_execution, locked: true }
required_capabilities: [llm:cheap]
borrowed_actions: []
effect:
render_blocks: []
outcome: { kind: none }
"#,
)
.unwrap();
let overlay = overlay_write(project.path(), "overlay: 1\nunmount:\n - writer\n");
let err = overlay_compile(project.path(), Some(&overlay))
.expect_err("removing an authorized callee must leave a loud missing-target error");
for fragment in ["asker", "step", "write", "writer", "missing mount"] {
assert!(err.contains(fragment), "missing {fragment:?} in {err}");
}
}
#[test]
fn binding_an_absent_id_is_refused() {
let project = tempfile::tempdir().unwrap();
overlay_write_project(project.path(), "");
let overlay = overlay_write(
project.path(),
"overlay: 1\nbind:\n nosuch:\n tone: formal\n",
);
let err = overlay_compile(project.path(), Some(&overlay))
.expect_err("binding values on something absent must be refused");
assert!(err.contains("nosuch"), "{err}");
assert!(err.contains("does not mount"), "{err}");
}
#[test]
fn the_same_id_mounted_and_unmounted_is_refused() {
let project = tempfile::tempdir().unwrap();
overlay_write_project(project.path(), "");
let overlay = overlay_write(
project.path(),
"overlay: 1\nmount:\n - use: writer\nunmount:\n - writer\n",
);
let err = overlay_compile(project.path(), Some(&overlay))
.expect_err("a contradictory patch must be refused rather than resolved");
assert!(err.contains("contradictory"), "{err}");
assert!(err.contains("writer"), "{err}");
}
#[test]
fn mounting_the_same_id_twice_is_refused() {
let project = tempfile::tempdir().unwrap();
overlay_write_project(project.path(), "");
let overlay = overlay_write(
project.path(),
"overlay: 1\nmount:\n - use: writer\n bind: { table: a }\n - use: writer\n bind: { table: b }\n",
);
let err = overlay_compile(project.path(), Some(&overlay))
.expect_err("a duplicate mount must be refused");
assert!(err.contains("more than once"), "{err}");
}
#[test]
fn unmounting_everything_is_refused() {
let project = tempfile::tempdir().unwrap();
overlay_write_project(project.path(), "");
let overlay = overlay_write(project.path(), "overlay: 1\nunmount:\n - asker\n");
let err = overlay_compile(project.path(), Some(&overlay))
.expect_err("a harness with no behaviors is not a valid product");
assert!(err.contains("no mounted behaviors"), "{err}");
}
#[test]
fn an_unsupported_format_version_is_refused() {
let project = tempfile::tempdir().unwrap();
overlay_write_project(project.path(), "");
let overlay = overlay_write(project.path(), "overlay: 2\n");
let err = overlay_compile(project.path(), Some(&overlay))
.expect_err("an unknown overlay format version must be refused");
assert!(err.contains("format version 2"), "{err}");
assert!(
err.contains("supported: 1"),
"the message must name what this build accepts: {err}"
);
}
#[test]
fn an_unknown_overlay_key_is_refused() {
let project = tempfile::tempdir().unwrap();
overlay_write_project(project.path(), "");
let overlay = overlay_write(
project.path(),
"overlay: 1\ntier_overrides:\n asker:\n step: strong\n",
);
let err = overlay_compile(project.path(), Some(&overlay))
.expect_err("an unknown overlay key must be refused, not ignored");
assert!(err.contains("tier_overrides"), "{err}");
}
#[test]
fn a_contradictory_patch_changes_nothing_before_failing() {
let project = tempfile::tempdir().unwrap();
overlay_write_project(project.path(), "system_prompt: |\n Base charter.\n");
let overlay = overlay_write(
project.path(),
"overlay: 1\nsystem_prompt: |\n Patched charter.\nunmount:\n - nosuch\n",
);
let err = overlay_compile(project.path(), Some(&overlay)).expect_err("the unmount is invalid");
assert!(err.contains("nosuch"), "{err}");
let plain = overlay_compile(project.path(), None).unwrap();
assert!(
plain["components"][0]["brief"]
.as_str()
.unwrap()
.contains("Base charter"),
"a refused patch must leave nothing behind: {}",
plain["components"][0]["brief"]
);
}