feat: deterministic stub orchestrator (v0.1.0)
The orchestrator-llm module turns a goal text into a Plan JSON that the F∆I hub can apply. v0.1.0 is a deterministic stub — it always emits a three-step plan (explain, explain-stub-notice, save_flow with debug.echo) regardless of the goal. Why ship a stub: it validates the module → plan → apply pipeline end-to-end before adding LLM integration. The output is a structurally valid Plan that round-trips cleanly through fai_hub::plan::Plan deserialization. v0.2.0 will replace the stub body with an actual LLM call once the F∆I platform exposes outbound HTTP to permitted modules via wasi-http. Files: - module.yaml — capability orchestrator.plan@0.1.0, no permissions - wit/world.wit — copy of fai:platform@0.1.0 - src/lib.rs — wit_bindgen Guest impl that delegates to plan.rs - src/plan.rs — pure plan-builder (host + WASM compatible) - tests/plan_compatibility.rs — plan JSON shape assertions - rust-toolchain.toml — pin to Rust 1.86 with wasm32-wasip2 target - README.md — usage and status Signed-off-by: flemming-it <sf@flemming.it>
This commit is contained in:
parent
8b2678f3e8
commit
3bb8fc9e70
8 changed files with 430 additions and 1 deletions
47
tests/plan_compatibility.rs
Normal file
47
tests/plan_compatibility.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
//! Verify that plan JSON emitted by orchestrator-llm parses
|
||||
//! cleanly as a `fai_hub::plan::Plan`. This protects against
|
||||
//! schema drift between this module and the hub.
|
||||
//!
|
||||
//! Note: this test runs only on the host target. The WASM build
|
||||
//! does not include test harnesses.
|
||||
#![cfg(not(target_arch = "wasm32"))]
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use orchestrator_llm::build_plan;
|
||||
use serde_json::Value;
|
||||
|
||||
#[test]
|
||||
fn emitted_plan_has_required_top_level_fields() {
|
||||
let json = build_plan("test goal");
|
||||
let parsed: Value = serde_json::from_str(&json).expect("plan must be valid json");
|
||||
assert!(parsed["schema_version"].is_u64());
|
||||
assert!(parsed["goal"].is_string());
|
||||
assert!(parsed["steps"].is_array());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_step_has_kind_tag() {
|
||||
let json = build_plan("test goal");
|
||||
let parsed: Value = serde_json::from_str(&json).unwrap();
|
||||
for step in parsed["steps"].as_array().unwrap() {
|
||||
assert!(step["kind"].is_string(), "step missing kind tag: {step}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn save_flow_step_yaml_parses_as_flow_definition() {
|
||||
// The save_flow step embeds a YAML flow body. Verify it
|
||||
// round-trips through serde_yaml.
|
||||
let json = build_plan("anything");
|
||||
let parsed: Value = serde_json::from_str(&json).unwrap();
|
||||
let save_flow_step = parsed["steps"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|s| s["kind"] == "save_flow")
|
||||
.expect("expected a save_flow step");
|
||||
let yaml_text = save_flow_step["yaml"].as_str().unwrap();
|
||||
let flow: Value = serde_yaml::from_str(yaml_text).expect("embedded yaml must parse");
|
||||
assert_eq!(flow["name"], "orchestrator-stub-greeting");
|
||||
assert!(flow["steps"].is_array());
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue