fix(ticket): validate configuration adapters through structured WIP
This commit is contained in:
Generated
+1
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user