fix(ticket): validate configuration adapters through structured WIP

This commit is contained in:
2026-10-09 03:04:47 +09:00
parent e67d75615d
commit e90e2ab391
3 changed files with 47 additions and 43 deletions
Generated
+1
View File
@@ -6766,6 +6766,7 @@ dependencies = [
"uuid", "uuid",
"webauthn-authenticator-rs", "webauthn-authenticator-rs",
"webauthn-rs", "webauthn-rs",
"wip-protocol",
"workdir", "workdir",
"worker", "worker",
"worker-runtime", "worker-runtime",
+1
View File
@@ -56,6 +56,7 @@ zeroize.workspace = true
webauthn-rs = { workspace = true } webauthn-rs = { workspace = true }
[dev-dependencies] [dev-dependencies]
wip-protocol.workspace = true
agen.workspace = true agen.workspace = true
tempfile.workspace = true tempfile.workspace = true
tools.workspace = true tools.workspace = true
@@ -139,26 +139,27 @@ impl worker::WorkspaceClient for ConfigRouterClient {
Ok(response) Ok(response)
} }
} }
fn config_interface(path: &str) -> String { fn config_interface(path: &str) -> wip_protocol::InterfaceReference {
// Use the published contextual interface identity, not a model-managed validator. // Use the published structured Interface identity, not a model-managed validator.
let hex = path wip_protocol::InterfaceReference {
.bytes() scope: path.into(),
.map(|byte| format!("{byte:02x}")) name: "yoi.workspace-config/node/v1".into(),
.collect::<String>(); }
format!("yoi.workspace-config/node/v1/@/{hex}")
} }
async fn config_discover(runtime: &worker::wip::WipRuntime, path: &str) { async fn config_inspect(runtime: &worker::wip::WipRuntime, path: &str) {
let observed = runtime let output = runtime.inspect(path.into(), true).await.unwrap();
.discover(path.into(), 0, true) let observed: Value = serde_json::from_str(output.content.as_deref().unwrap()).unwrap();
.await
.unwrap()
.content
.unwrap();
assert!( assert!(
observed.contains(&config_interface(path)), observed["interfaces"]
"interface must come from live discovery: {observed}" .as_array()
.unwrap()
.iter()
.any(|interface| {
interface["reference"]
== json!({"scope": path, "name": config_interface(path).name})
}),
"Interface must come from live Object inspection: {observed}"
); );
runtime.inspect(config_interface(path), true).await.unwrap();
} }
async fn config_call( async fn config_call(
runtime: &worker::wip::WipRuntime, runtime: &worker::wip::WipRuntime,
@@ -167,7 +168,7 @@ async fn config_call(
args: Value, args: Value,
) -> std::result::Result<agen::tool::ToolOutput, agen::tool::ToolError> { ) -> std::result::Result<agen::tool::ToolOutput, agen::tool::ToolError> {
runtime runtime
.call( .invoke(
path.into(), path.into(),
config_interface(path), config_interface(path),
operation.into(), operation.into(),
@@ -183,23 +184,24 @@ async fn catalog_call(
base: &str, base: &str,
operation: &str, operation: &str,
) -> Value { ) -> Value {
let hex = path let interface = json!({"scope": path, "name": base});
.bytes() let inspected = runtime.inspect(path.into(), true).await.unwrap();
.map(|byte| format!("{byte:02x}")) let observed: Value = serde_json::from_str(inspected.content.as_deref().unwrap()).unwrap();
.collect::<String>(); assert!(
let interface = format!("{base}/@/{hex}"); observed["interfaces"]
let discovered = runtime .as_array()
.discover(path.into(), 0, true) .unwrap()
.await .iter()
.unwrap() .any(|published| published["reference"] == interface),
.content "{observed}"
.unwrap(); );
assert!(discovered.contains(&interface), "{discovered}");
runtime.inspect(interface.clone(), true).await.unwrap();
let output = runtime let output = runtime
.call( .invoke(
path.into(), path.into(),
interface, wip_protocol::InterfaceReference {
scope: path.into(),
name: base.into(),
},
operation.into(), operation.into(),
json!({}), json!({}),
agen::tool::ToolExecutionContext::direct(), agen::tool::ToolExecutionContext::direct(),
@@ -378,7 +380,7 @@ async fn workspace_config_real_selected_invoke_backend_wip_edit_create_delete_im
.connection_id, .connection_id,
attached.connection_id attached.connection_id
); );
config_discover(&wip, CONTENT_ROOT).await; config_inspect(&wip, CONTENT_ROOT).await;
config_call( config_call(
&wip, &wip,
CONTENT_ROOT, CONTENT_ROOT,
@@ -388,7 +390,7 @@ async fn workspace_config_real_selected_invoke_backend_wip_edit_create_delete_im
.await .await
.unwrap(); .unwrap();
let path = "/workspace-config/notes/wip.md"; let path = "/workspace-config/notes/wip.md";
config_discover(&wip, path).await; config_inspect(&wip, path).await;
assert!( assert!(
config_call(&wip, path, "read", json!({})) config_call(&wip, path, "read", json!({}))
.await .await
@@ -422,7 +424,7 @@ async fn workspace_config_real_selected_invoke_backend_wip_edit_create_delete_im
config_call(&wip, path, "write", json!({"content":"replacement note"})) config_call(&wip, path, "write", json!({"content":"replacement note"}))
.await .await
.unwrap(); .unwrap();
config_discover(&wip, CONTENT_ROOT).await; config_inspect(&wip, CONTENT_ROOT).await;
config_call(&wip,CONTENT_ROOT,"apply_changes",json!({"changes":[ config_call(&wip,CONTENT_ROOT,"apply_changes",json!({"changes":[
{"kind":"create","path":"fragments/prompts.dcdl","content_type":"decodal","content":"{ common = { language = \"CONFIG_WIP_E2E\"; }; }"}, {"kind":"create","path":"fragments/prompts.dcdl","content_type":"decodal","content":"{ common = { language = \"CONFIG_WIP_E2E\"; }; }"},
{"kind":"update","path":"main.dcdl","content":"{ prompts = import \"./fragments/prompts.dcdl\"; } as WorkspaceConfigSchema"} {"kind":"update","path":"main.dcdl","content":"{ prompts = import \"./fragments/prompts.dcdl\"; } as WorkspaceConfigSchema"}
@@ -436,7 +438,7 @@ async fn workspace_config_real_selected_invoke_backend_wip_edit_create_delete_im
.unwrap(); .unwrap();
let prompts = crate::prompt_settings::project_prompts_from_workspace_config(&state).unwrap(); let prompts = crate::prompt_settings::project_prompts_from_workspace_config(&state).unwrap();
assert_eq!(prompts.templates["common.language"], "CONFIG_WIP_E2E"); assert_eq!(prompts.templates["common.language"], "CONFIG_WIP_E2E");
config_discover(&wip, path).await; config_inspect(&wip, path).await;
config_call(&wip, path, "delete", json!({})).await.unwrap(); config_call(&wip, path, "delete", json!({})).await.unwrap();
assert!( assert!(
fixture fixture
@@ -449,7 +451,7 @@ async fn workspace_config_real_selected_invoke_backend_wip_edit_create_delete_im
.get(&config_source::VirtualPath::parse("notes/wip.md").unwrap()) .get(&config_source::VirtualPath::parse("notes/wip.md").unwrap())
.is_none() .is_none()
); );
config_discover(&wip, CONTENT_ROOT).await; config_inspect(&wip, CONTENT_ROOT).await;
let before = fixture let before = fixture
.api .api
.config_store .config_store
@@ -492,7 +494,7 @@ async fn workspace_config_real_selected_invoke_backend_wip_edit_create_delete_im
dispatched + 1, dispatched + 1,
"a lost response cannot trigger automatic retry" "a lost response cannot trigger automatic retry"
); );
config_discover(&wip, CONTENT_ROOT).await; config_inspect(&wip, CONTENT_ROOT).await;
let readonly = workspace_config::attach( let readonly = workspace_config::attach(
&fixture.api, &fixture.api,
&fixture.worker, &fixture.worker,
@@ -633,15 +635,15 @@ async fn assert_config_production_http_adapter(
) )
.unwrap(); .unwrap();
assert_config_catalog(&wip, &attached).await; assert_config_catalog(&wip, &attached).await;
config_discover(&wip, "/workspace-config/main.dcdl").await; config_inspect(&wip, "/workspace-config/main.dcdl").await;
let main_interface = wip let main_interface = wip
.inspect(config_interface("/workspace-config/main.dcdl"), true) .inspect("/workspace-config/main.dcdl".into(), true)
.await .await
.unwrap() .unwrap()
.content .content
.unwrap(); .unwrap();
assert!( assert!(
!main_interface.contains("\"delete\""), !main_interface.contains("delete("),
"required entrypoint cannot advertise delete: {main_interface}" "required entrypoint cannot advertise delete: {main_interface}"
); );
assert!( assert!(
@@ -652,7 +654,7 @@ async fn assert_config_production_http_adapter(
.unwrap() .unwrap()
.contains("WorkspaceConfigSchema") .contains("WorkspaceConfigSchema")
); );
config_discover(&wip, "/workspace-config").await; config_inspect(&wip, "/workspace-config").await;
config_call( config_call(
&wip, &wip,
"/workspace-config", "/workspace-config",