Merge latest develop after WIP namespace integration

Normal merge of develop@5a4c823c preserves integrated T-698 controller/native projections alongside the T-692/T-693 reconciliation. Target branch remains untouched.

Validation after this target movement: root cargo check; full worker(serial) and workspace-server tests; cargo fmt --all -- --check; git diff --check HEAD. Earlier reconciliation passed session-store, server-api generated/OpenAPI, full Web and production browser-shell checks.
This commit is contained in:
2026-10-05 17:34:47 +09:00
11 changed files with 1559 additions and 207 deletions
+12 -12
View File
@@ -1594,12 +1594,12 @@ where
let engine = worker.engine_mut();
if feature_config.merge_request.any() && wip_mode {
let feature_route = wip_mount_registry
.allocate_feature_route("merge-request")
let namespace_route = wip_mount_registry
.allocate_namespace("merge-request", "merge-requests")
.map_err(|error| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("allocate Merge Request WIP route: {error}"),
format!("allocate Merge Request WIP namespace: {error}"),
)
})?;
crate::feature::builtin::merge_request::mount_workspace_http_merge_request_wip(
@@ -1607,7 +1607,7 @@ where
workspace_client.clone(),
feature_config.merge_request,
wip_permissions.clone(),
&feature_route,
&namespace_route,
)
.map_err(|error| {
std::io::Error::new(
@@ -1624,12 +1624,12 @@ where
intake: feature_config.ticket.intake,
workflow: feature_config.ticket.workflow,
};
let feature_route = wip_mount_registry
.allocate_feature_route("ticket")
let namespace_route = wip_mount_registry
.allocate_namespace("ticket", "tickets")
.map_err(|error| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("allocate Ticket WIP route: {error}"),
format!("allocate Ticket WIP namespace: {error}"),
)
})?;
crate::feature::builtin::ticket::mount_workspace_http_ticket_wip(
@@ -1637,7 +1637,7 @@ where
workspace_client.clone(),
ticket_access,
wip_permissions.clone(),
&feature_route,
&namespace_route,
)
.map_err(|error| {
std::io::Error::new(
@@ -1658,19 +1658,19 @@ where
engine.register_tool(definition);
}
if wip_mode {
let feature_route = wip_mount_registry
.allocate_feature_route("objective")
let namespace_route = wip_mount_registry
.allocate_namespace("objective", "objectives")
.map_err(|error| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("allocate Objective WIP route: {error}"),
format!("allocate Objective WIP namespace: {error}"),
)
})?;
crate::feature::builtin::objective::mount_workspace_http_objective_wip(
&mut wip_mount_registry,
workspace_client.clone(),
wip_permissions.clone(),
&feature_route,
&namespace_route,
)
.map_err(|error| {
std::io::Error::new(
@@ -5,9 +5,9 @@ use crate::feature::{
};
use crate::permission::permission_action_for;
use crate::wip::{
WipCallContext, WipDynamicItem, WipDynamicItemResolver, WipDynamicMount, WipFeatureRoute,
WipMountError, WipMountRegistry, WipOperationError, WipOperationHandler, WipOperationOutput,
WipProjection, WipProjectionKind, json_to_wip, wip_to_json,
WipCallContext, WipDynamicItem, WipDynamicItemResolver, WipDynamicMount, WipMountError,
WipMountRegistry, WipNamespaceRoute, WipOperationError, WipOperationHandler,
WipOperationOutput, WipProjection, WipProjectionKind, json_to_wip, wip_to_json,
};
use crate::worker::{WorkspaceClient, WorkspaceRequest, WorkspaceRequestMethod};
use agen::tool::{Tool, ToolError, ToolExecutionContext, ToolMeta, ToolOutput};
@@ -529,7 +529,7 @@ fn native_merge_request_tools(
let (meta, tool) = definition(client.clone(), kind)();
jsonschema::validator_for(&meta.input_schema).map_err(|error| {
WipMountError::InvalidProjection {
route: "/features/merge-request/merge-requests".into(),
route: "/merge-requests".into(),
message: format!("Merge Request tool schema cannot be retained: {error}"),
}
})?;
@@ -562,9 +562,9 @@ pub fn mount_workspace_http_merge_request_wip(
client: Arc<dyn WorkspaceClient>,
config: MergeRequestFeatureConfig,
permissions: Option<ToolPermissionConfig>,
feature_route: &WipFeatureRoute,
namespace_route: &WipNamespaceRoute,
) -> Result<(), WipMountError> {
let collection_route = feature_route.child("merge-requests")?;
let collection_route = namespace_route.root().to_string();
let tools = native_merge_request_tools(client, config)?;
let claimed_tools = tools
.iter()
@@ -1049,7 +1049,7 @@ fn merge_request_operation_declaration(
.get("properties")
.and_then(Value::as_object)
.ok_or_else(|| WipMountError::InvalidProjection {
route: "/features/merge-request/merge-requests".into(),
route: "/merge-requests".into(),
message: format!(
"Merge Request tool `{}` schema has no properties",
tool.name
@@ -1474,7 +1474,9 @@ mod tests {
#[test]
fn native_mount_uses_host_route_and_accepts_only_one_canonical_item_segment() {
let mut registry = WipMountRegistry::new();
let feature_route = registry.allocate_feature_route("merge-request").unwrap();
let namespace_route = registry
.allocate_namespace("merge-request", "merge-requests")
.unwrap();
mount_workspace_http_merge_request_wip(
&mut registry,
Arc::new(TestWorkspaceHttpClient::new("workspace", "http://unused")),
@@ -1484,17 +1486,14 @@ mod tests {
..Default::default()
},
None,
&feature_route,
&namespace_route,
)
.unwrap();
assert_eq!(
registry.routes().collect::<Vec<_>>(),
["/features/merge-request/merge-requests"]
);
assert_eq!(registry.routes().collect::<Vec<_>>(), ["/merge-requests"]);
let resolver = MergeRequestItemResolver {
tools: HashMap::new(),
permissions: None,
collection_route: "/features/merge-request/merge-requests".into(),
collection_route: "/merge-requests".into(),
revisions: Arc::new(Mutex::new(MergeRequestRevisionState::default())),
};
assert!(
@@ -1645,7 +1644,7 @@ mod tests {
let resolver = MergeRequestItemResolver {
tools: HashMap::new(),
permissions: None,
collection_route: "/features/merge-request/merge-requests".into(),
collection_route: "/merge-requests".into(),
revisions: Arc::clone(&revisions),
};
let first = resolver.resolve("MR-1").unwrap().object.validator;
+21 -24
View File
@@ -21,9 +21,9 @@ use wip_protocol::{
use crate::permission::permission_action_for;
use crate::wip::{
WipCallContext, WipDynamicItem, WipDynamicItemResolver, WipDynamicMount, WipFeatureRoute,
WipMountError, WipMountRegistry, WipOperationError, WipOperationHandler, WipOperationOutput,
WipProjection, WipProjectionKind, json_to_wip, wip_to_json,
WipCallContext, WipDynamicItem, WipDynamicItemResolver, WipDynamicMount, WipMountError,
WipMountRegistry, WipNamespaceRoute, WipOperationError, WipOperationHandler,
WipOperationOutput, WipProjection, WipProjectionKind, json_to_wip, wip_to_json,
};
use crate::worker::{WorkspaceClient, WorkspaceRequest, WorkspaceRequestMethod};
@@ -551,9 +551,9 @@ pub fn mount_workspace_http_objective_wip(
registry: &mut WipMountRegistry,
client: Arc<dyn WorkspaceClient>,
permissions: Option<ToolPermissionConfig>,
feature_route: &WipFeatureRoute,
namespace_route: &WipNamespaceRoute,
) -> Result<(), WipMountError> {
let collection_route = feature_route.child("objectives")?;
let collection_route = namespace_route.root().to_string();
let backend = WorkspaceHttpObjectiveBackend::new(client);
let revisions = Arc::new(Mutex::new(ObjectiveRevisionState::default()));
let collection_descriptor = objective_collection_descriptor();
@@ -1775,7 +1775,9 @@ mod tests {
}));
let mut registry = WipMountRegistry::new();
let feature_route = registry.allocate_feature_route("objective").unwrap();
let namespace_route = registry
.allocate_namespace("objective", "objectives")
.unwrap();
mount_workspace_http_objective_wip(
&mut registry,
Arc::new(crate::worker::TestWorkspaceHttpClient::new(
@@ -1783,18 +1785,16 @@ mod tests {
"http://backend",
)),
None,
&feature_route,
&namespace_route,
)
.unwrap();
assert_eq!(
registry.routes().collect::<Vec<_>>(),
["/features/objective/objectives"]
assert_eq!(registry.routes().collect::<Vec<_>>(), ["/objectives"]);
assert_eq!(namespace_route.root(), "/objectives");
assert!(
registry
.allocate_namespace("objective", "../tickets")
.is_err()
);
assert_eq!(
feature_route.child("objectives").unwrap(),
"/features/objective/objectives"
);
assert!(feature_route.child("../tickets").is_err());
}
#[test]
@@ -1805,7 +1805,7 @@ mod tests {
crate::worker::TestWorkspaceHttpClient::new("workspace", "http://backend"),
)),
permissions: None,
collection_route: "/features/objective/objectives".into(),
collection_route: "/objectives".into(),
revisions: Arc::clone(&revisions),
};
let initial = resolver.resolve("O-3").unwrap();
@@ -1971,7 +1971,7 @@ mod tests {
let collection = ObjectiveCollectionWipHandler {
backend,
permissions: None,
collection_route: "/features/objective/objectives".into(),
collection_route: "/objectives".into(),
revisions: Arc::new(Mutex::new(ObjectiveRevisionState::default())),
};
let result = collection
@@ -2013,7 +2013,7 @@ mod tests {
let collection = ObjectiveCollectionWipHandler {
backend: backend.clone(),
permissions: None,
collection_route: "/features/objective/objectives".into(),
collection_route: "/objectives".into(),
revisions: Arc::clone(&revisions),
};
let context = || WipCallContext {
@@ -2170,7 +2170,7 @@ mod tests {
crate::worker::TestWorkspaceHttpClient::new("workspace", base_url),
)),
permissions: None,
collection_route: "/features/objective/objectives".into(),
collection_route: "/objectives".into(),
revisions: Arc::new(Mutex::new(ObjectiveRevisionState::default())),
};
let output = match handler
@@ -2191,10 +2191,7 @@ mod tests {
let output = crate::wip::wip_to_json(&output.value).unwrap();
assert_eq!(output["next_cursor"], "next-page");
assert_eq!(output["has_more"], true);
assert_eq!(
output["objectives"][0]["path"],
"/features/objective/objectives/O-3"
);
assert_eq!(output["objectives"][0]["path"], "/objectives/O-3");
assert_eq!(output["objectives"][0]["linked_tickets"][0], "T-7");
assert!(!output.to_string().contains("00001OBJECTIVE"));
assert!(!output.to_string().contains("00001TICKET"));
@@ -2243,7 +2240,7 @@ mod tests {
let resolver = ObjectiveItemResolver {
backend: backend.clone(),
permissions: None,
collection_route: "/features/objective/objectives".into(),
collection_route: "/objectives".into(),
revisions: Arc::clone(&revisions),
};
let before = resolver.resolve("O-3").unwrap().object.validator;
+18 -17
View File
@@ -34,9 +34,9 @@ use crate::feature::{
};
use crate::permission::permission_action_for;
use crate::wip::{
WipCallContext, WipDynamicItem, WipDynamicItemResolver, WipDynamicMount, WipFeatureRoute,
WipMountError, WipMountRegistry, WipOperationError, WipOperationHandler, WipOperationOutput,
WipProjection, WipProjectionKind, json_to_wip, wip_to_json,
WipCallContext, WipDynamicItem, WipDynamicItemResolver, WipDynamicMount, WipMountError,
WipMountRegistry, WipNamespaceRoute, WipOperationError, WipOperationHandler,
WipOperationOutput, WipProjection, WipProjectionKind, json_to_wip, wip_to_json,
};
use crate::worker::{WorkspaceClient, WorkspaceRequest, WorkspaceRequestMethod};
use agen::tool::{Tool, ToolError, ToolExecutionContext, ToolMeta, ToolOutput};
@@ -1377,7 +1377,7 @@ fn native_ticket_tools(
let (meta, tool) = definition();
let projection = native_ticket_operation(&meta.name).ok_or_else(|| {
WipMountError::InvalidProjection {
route: "/features/ticket/tickets".into(),
route: "/tickets".into(),
message: format!(
"enabled Ticket tool `{}` has no native projection",
meta.name
@@ -1386,7 +1386,7 @@ fn native_ticket_tools(
})?;
jsonschema::validator_for(&meta.input_schema).map_err(|error| {
WipMountError::InvalidProjection {
route: "/features/ticket/tickets".into(),
route: "/tickets".into(),
message: format!("Ticket tool schema cannot be retained: {error}"),
}
})?;
@@ -1418,9 +1418,9 @@ pub fn mount_workspace_http_ticket_wip(
client: Arc<dyn WorkspaceClient>,
access: TicketFeatureAccess,
permissions: Option<ToolPermissionConfig>,
feature_route: &WipFeatureRoute,
namespace_route: &WipNamespaceRoute,
) -> Result<(), WipMountError> {
let collection_route = feature_route.child("tickets")?;
let collection_route = namespace_route.root().to_string();
let tools = native_ticket_tools(client, access)?;
let claimed_tools = tools
.iter()
@@ -1920,7 +1920,7 @@ fn ticket_operation_declaration(
.get("properties")
.and_then(Value::as_object)
.ok_or_else(|| WipMountError::InvalidProjection {
route: "/features/ticket/tickets".into(),
route: "/tickets".into(),
message: format!("Ticket tool `{}` schema has no properties", tool.name),
})?;
let required = tool
@@ -2103,7 +2103,7 @@ mod tests {
#[test]
fn native_ticket_mount_uses_host_route_and_rejects_non_ticket_children() {
let mut registry = WipMountRegistry::new();
let feature_route = registry.allocate_feature_route("ticket").unwrap();
let namespace_route = registry.allocate_namespace("ticket", "tickets").unwrap();
mount_workspace_http_ticket_wip(
&mut registry,
Arc::new(crate::worker::TestWorkspaceHttpClient::new(
@@ -2112,20 +2112,21 @@ mod tests {
)),
TicketFeatureAccess::review(),
None,
&feature_route,
&namespace_route,
)
.unwrap();
assert_eq!(
registry.routes().collect::<Vec<_>>(),
["/features/ticket/tickets"]
assert_eq!(registry.routes().collect::<Vec<_>>(), ["/tickets"]);
assert!(
registry
.allocate_namespace("ticket", "../objectives")
.is_err()
);
assert!(feature_route.child("../objectives").is_err());
let revisions = Arc::new(Mutex::new(TicketRevisionState::default()));
let resolver = TicketItemResolver {
tools: HashMap::new(),
permissions: None,
collection_route: "/features/ticket/tickets".into(),
collection_route: "/tickets".into(),
revisions,
};
assert!(resolver.resolve("T-42").is_some());
@@ -2189,7 +2190,7 @@ mod tests {
let resolver = TicketItemResolver {
tools: HashMap::new(),
permissions: None,
collection_route: "/features/ticket/tickets".into(),
collection_route: "/tickets".into(),
revisions: Arc::clone(&revisions),
};
let unobserved_internal = resolver.resolve("00001TICKET").unwrap().object.validator;
@@ -2243,7 +2244,7 @@ mod tests {
let resolver = TicketItemResolver {
tools: HashMap::new(),
permissions: None,
collection_route: "/features/ticket/tickets".into(),
collection_route: "/tickets".into(),
revisions: Arc::clone(&revisions),
};
let target_v1 = resolver.resolve("T-43").unwrap().object.validator;
+1423 -124
View File
File diff suppressed because it is too large Load Diff