feat: expose authorized Worker Drive tools and native WIP entries
Use T-722 identity-bound API for paged discovery, bounded text read/create/write/edit, folders, relocate/delete, revision-fixed images, Workdir artifact publication, and mutation receipts. Keep IDs as Workspace-bound decimal strings, observed CAS preconditions and Client validators hidden from model arguments, and never substitute revisions or blindly replay uncertain effects. Activate via host-bound Feature/Profile/schema and centralized instructions. Publish only the Drive entrance as indexable; resolve selected stable IDs directly with coherent self-scoped WIP publication. Internal SubWorkers and Reviewer children deny undelegated Drive access at feature and client boundaries; no child grant or local storage authority is synthesized. T-723 validation: root cargo check; manifest 190; tools 78 unit +39 integration +1 doctest; Worker 918 unit +110 integration; Runtime 348 unit +34 binary; Server 708 library +11 binary including 17 real DB+LocalFileSystem signed-API adapter tests. Native image capture/replay, multichunk revision bounds, Workdir import, concurrent CAS, current grants, response-loss reconciliation and dependent config WIP0.2 regressions pass. WIP crates.io 0.2.0 resolution verifier, fmt and diff checks pass.
This commit is contained in:
@@ -56,6 +56,7 @@ zeroize.workspace = true
|
||||
webauthn-rs = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
wip-protocol.workspace = true
|
||||
agen.workspace = true
|
||||
tempfile.workspace = true
|
||||
tools.workspace = true
|
||||
|
||||
@@ -32729,6 +32729,7 @@ mod tests {
|
||||
mod ticket_worker_claim_tests;
|
||||
mod ticket_worker_launch_tests;
|
||||
mod value_profiles_tests;
|
||||
mod worker_drive_tests;
|
||||
mod worker_operations_tests;
|
||||
include!("server_workspace_config_tests.rs");
|
||||
use super::*;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -139,26 +139,33 @@ impl worker::WorkspaceClient for ConfigRouterClient {
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
fn config_interface(path: &str) -> String {
|
||||
// Use the published contextual interface identity, not a model-managed validator.
|
||||
let hex = path
|
||||
.bytes()
|
||||
.map(|byte| format!("{byte:02x}"))
|
||||
.collect::<String>();
|
||||
format!("yoi.workspace-config/node/v1/@/{hex}")
|
||||
fn contextual_reference(base: &str, path: &str) -> wip_protocol::InterfaceReference {
|
||||
wip_protocol::InterfaceReference {
|
||||
scope: path.into(),
|
||||
name: base.into(),
|
||||
}
|
||||
}
|
||||
async fn config_discover(runtime: &worker::wip::WipRuntime, path: &str) {
|
||||
let observed = runtime
|
||||
.discover(path.into(), 0, true)
|
||||
.await
|
||||
.unwrap()
|
||||
.content
|
||||
.unwrap();
|
||||
fn config_interface(path: &str) -> wip_protocol::InterfaceReference {
|
||||
// Use the published contextual interface identity, not a model-managed validator.
|
||||
contextual_reference("yoi.workspace-config/node/v1", path)
|
||||
}
|
||||
async fn inspect_interface(runtime: &worker::wip::WipRuntime, path: &str, base: &str) -> Value {
|
||||
let output = runtime.inspect(path.into(), true).await.unwrap();
|
||||
let observed: Value = serde_json::from_str(output.content.as_deref().unwrap()).unwrap();
|
||||
assert!(
|
||||
observed.contains(&config_interface(path)),
|
||||
"interface must come from live discovery: {observed}"
|
||||
observed["interfaces"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.any(|interface| {
|
||||
interface["reference"] == json!({"scope": path, "name": base})
|
||||
}),
|
||||
"interface must come from live inspection: {observed}"
|
||||
);
|
||||
runtime.inspect(config_interface(path), true).await.unwrap();
|
||||
observed
|
||||
}
|
||||
async fn config_inspect(runtime: &worker::wip::WipRuntime, path: &str) -> Value {
|
||||
inspect_interface(runtime, path, "yoi.workspace-config/node/v1").await
|
||||
}
|
||||
async fn config_call(
|
||||
runtime: &worker::wip::WipRuntime,
|
||||
@@ -167,7 +174,7 @@ async fn config_call(
|
||||
args: Value,
|
||||
) -> std::result::Result<agen::tool::ToolOutput, agen::tool::ToolError> {
|
||||
runtime
|
||||
.call(
|
||||
.invoke(
|
||||
path.into(),
|
||||
config_interface(path),
|
||||
operation.into(),
|
||||
@@ -183,23 +190,11 @@ async fn catalog_call(
|
||||
base: &str,
|
||||
operation: &str,
|
||||
) -> Value {
|
||||
let hex = path
|
||||
.bytes()
|
||||
.map(|byte| format!("{byte:02x}"))
|
||||
.collect::<String>();
|
||||
let interface = format!("{base}/@/{hex}");
|
||||
let discovered = runtime
|
||||
.discover(path.into(), 0, true)
|
||||
.await
|
||||
.unwrap()
|
||||
.content
|
||||
.unwrap();
|
||||
assert!(discovered.contains(&interface), "{discovered}");
|
||||
runtime.inspect(interface.clone(), true).await.unwrap();
|
||||
inspect_interface(runtime, path, base).await;
|
||||
let output = runtime
|
||||
.call(
|
||||
.invoke(
|
||||
path.into(),
|
||||
interface,
|
||||
contextual_reference(base, path),
|
||||
operation.into(),
|
||||
json!({}),
|
||||
agen::tool::ToolExecutionContext::direct(),
|
||||
@@ -378,7 +373,7 @@ async fn workspace_config_real_selected_invoke_backend_wip_edit_create_delete_im
|
||||
.connection_id,
|
||||
attached.connection_id
|
||||
);
|
||||
config_discover(&wip, CONTENT_ROOT).await;
|
||||
config_inspect(&wip, CONTENT_ROOT).await;
|
||||
config_call(
|
||||
&wip,
|
||||
CONTENT_ROOT,
|
||||
@@ -388,15 +383,10 @@ async fn workspace_config_real_selected_invoke_backend_wip_edit_create_delete_im
|
||||
.await
|
||||
.unwrap();
|
||||
let path = "/workspace-config/notes/wip.md";
|
||||
config_discover(&wip, path).await;
|
||||
assert!(
|
||||
config_call(&wip, path, "read", json!({}))
|
||||
.await
|
||||
.unwrap()
|
||||
.content
|
||||
.unwrap()
|
||||
.contains("original note")
|
||||
);
|
||||
config_inspect(&wip, path).await;
|
||||
let read = config_call(&wip, path, "read", json!({})).await.unwrap();
|
||||
let read: Value = serde_json::from_str(read.content.as_deref().unwrap()).unwrap();
|
||||
assert_eq!(read["content"], "original note");
|
||||
config_call(
|
||||
&wip,
|
||||
path,
|
||||
@@ -422,7 +412,7 @@ async fn workspace_config_real_selected_invoke_backend_wip_edit_create_delete_im
|
||||
config_call(&wip, path, "write", json!({"content":"replacement note"}))
|
||||
.await
|
||||
.unwrap();
|
||||
config_discover(&wip, CONTENT_ROOT).await;
|
||||
config_inspect(&wip, CONTENT_ROOT).await;
|
||||
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":"update","path":"main.dcdl","content":"{ prompts = import \"./fragments/prompts.dcdl\"; } as WorkspaceConfigSchema"}
|
||||
@@ -436,7 +426,7 @@ async fn workspace_config_real_selected_invoke_backend_wip_edit_create_delete_im
|
||||
.unwrap();
|
||||
let prompts = crate::prompt_settings::project_prompts_from_workspace_config(&state).unwrap();
|
||||
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();
|
||||
assert!(
|
||||
fixture
|
||||
@@ -449,7 +439,7 @@ async fn workspace_config_real_selected_invoke_backend_wip_edit_create_delete_im
|
||||
.get(&config_source::VirtualPath::parse("notes/wip.md").unwrap())
|
||||
.is_none()
|
||||
);
|
||||
config_discover(&wip, CONTENT_ROOT).await;
|
||||
config_inspect(&wip, CONTENT_ROOT).await;
|
||||
let before = fixture
|
||||
.api
|
||||
.config_store
|
||||
@@ -492,7 +482,7 @@ async fn workspace_config_real_selected_invoke_backend_wip_edit_create_delete_im
|
||||
dispatched + 1,
|
||||
"a lost response cannot trigger automatic retry"
|
||||
);
|
||||
config_discover(&wip, CONTENT_ROOT).await;
|
||||
config_inspect(&wip, CONTENT_ROOT).await;
|
||||
let readonly = workspace_config::attach(
|
||||
&fixture.api,
|
||||
&fixture.worker,
|
||||
@@ -633,26 +623,26 @@ async fn assert_config_production_http_adapter(
|
||||
)
|
||||
.unwrap();
|
||||
assert_config_catalog(&wip, &attached).await;
|
||||
config_discover(&wip, "/workspace-config/main.dcdl").await;
|
||||
let main_interface = wip
|
||||
.inspect(config_interface("/workspace-config/main.dcdl"), true)
|
||||
.await
|
||||
.unwrap()
|
||||
.content
|
||||
.unwrap();
|
||||
let main_interface = config_inspect(&wip, "/workspace-config/main.dcdl").await;
|
||||
assert!(
|
||||
!main_interface.contains("\"delete\""),
|
||||
main_interface["interfaces"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.all(|interface| !interface["signature"].as_str().unwrap().contains("delete(")),
|
||||
"required entrypoint cannot advertise delete: {main_interface}"
|
||||
);
|
||||
let main = config_call(&wip, "/workspace-config/main.dcdl", "read", json!({}))
|
||||
.await
|
||||
.unwrap();
|
||||
let main: Value = serde_json::from_str(main.content.as_deref().unwrap()).unwrap();
|
||||
assert!(
|
||||
config_call(&wip, "/workspace-config/main.dcdl", "read", json!({}))
|
||||
.await
|
||||
.unwrap()
|
||||
.content
|
||||
main["content"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.contains("WorkspaceConfigSchema")
|
||||
);
|
||||
config_discover(&wip, "/workspace-config").await;
|
||||
config_inspect(&wip, "/workspace-config").await;
|
||||
config_call(
|
||||
&wip,
|
||||
"/workspace-config",
|
||||
|
||||
Reference in New Issue
Block a user