wip(tickets): backend createdBy (#5) + bulk status/priority/delete (#6)

Ajoute created_by sur IssueIndexEntry/TicketSummaryDto + filtre createdBy
(#5), et les use cases BulkUpdateIssueStatus/Priority + BulkDeleteIssues
avec les DTO/outils MCP idea_ticket_bulk_* associés (#6).

NON VERT : BulkDeleteIssues::execute référence BatchIssueResult::deleted(),
constructeur pas encore ajouté à BatchIssueResult (seuls ok/err existent) ->
`cargo check` échoue (E0599 dans application::issues::mod). Reste à
DevBackend avant toute QA/merge.

Branché sur feature/sdk-integration (dépend du commit #2 dans
orchestrator/mcp/server.rs et tools.rs) ; à rebaser sur develop une fois
feature/sdk-integration mergé.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-28 18:28:39 +02:00
parent 1ef1fd9e40
commit 6da52c6a02
20 changed files with 1061 additions and 34 deletions

View File

@ -693,15 +693,39 @@ impl McpServer {
),
));
}
if matches!(
name,
"idea_ticket_update"
| "idea_ticket_update_status"
| "idea_ticket_update_priority"
| "idea_ticket_update_carnet"
| "idea_ticket_link"
| "idea_ticket_unlink"
) {
if is_ticket_policy_bulk_mutation_tool(name) {
let refs = arguments
.get("refs")
.and_then(Value::as_array)
.ok_or_else(|| {
JsonRpcError::new(
error_codes::INVALID_PARAMS,
format!("tool `{name}` requires ticket refs under the active policy"),
)
})?;
for raw_ref in refs {
let raw_ref = raw_ref.as_str().ok_or_else(|| {
JsonRpcError::new(
error_codes::INVALID_PARAMS,
format!(
"tool `{name}` requires string ticket refs under the active policy"
),
)
})?;
let issue_ref = IssueRef::from_str(raw_ref).map_err(|e| {
JsonRpcError::new(
error_codes::INVALID_PARAMS,
format!("invalid ticket ref: {e}"),
)
})?;
if !policy.permits_ticket_mutation(name, issue_ref) {
return Err(JsonRpcError::new(
error_codes::INVALID_PARAMS,
format!("tool `{name}` is not permitted for ticket {issue_ref}"),
));
}
}
} else if is_ticket_policy_mutation_tool(name) {
let raw_ref = arguments
.get("ref")
.and_then(Value::as_str)
@ -880,6 +904,25 @@ fn parse_ask_agents_requests(
Ok(out)
}
fn is_ticket_policy_mutation_tool(name: &str) -> bool {
matches!(
name,
"idea_ticket_update"
| "idea_ticket_update_status"
| "idea_ticket_update_priority"
| "idea_ticket_update_carnet"
| "idea_ticket_link"
| "idea_ticket_unlink"
)
}
fn is_ticket_policy_bulk_mutation_tool(name: &str) -> bool {
matches!(
name,
"idea_ticket_bulk_update_status" | "idea_ticket_bulk_update_priority"
)
}
/// Maps a [`ToolMapError`] to the right JSON-RPC error code.
fn map_err_to_jsonrpc(err: ToolMapError) -> JsonRpcError {
match err {

View File

@ -60,6 +60,8 @@ pub fn is_ticket_tool(name: &str) -> bool {
| "idea_ticket_update"
| "idea_ticket_update_status"
| "idea_ticket_update_priority"
| "idea_ticket_bulk_update_status"
| "idea_ticket_bulk_update_priority"
| "idea_ticket_read_carnet"
| "idea_ticket_update_carnet"
| "idea_ticket_link"
@ -74,6 +76,31 @@ pub fn catalogue() -> Vec<ToolDef> {
let ticket_ref = json!({ "type": "string", "pattern": "^#[1-9][0-9]*$" });
let status = json!({ "type": "string", "enum": ["open", "inProgress", "QA", "closed"] });
let priority = json!({ "type": "string", "enum": ["low", "medium", "high", "critical"] });
let refs = json!({
"type": "array",
"minItems": 1,
"uniqueItems": true,
"items": ticket_ref.clone()
});
let created_by = json!({
"oneOf": [
{
"type": "object",
"properties": { "kind": { "const": "user" } },
"required": ["kind"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": { "const": "agent" },
"agentId": { "type": "string", "format": "uuid" }
},
"required": ["kind", "agentId"],
"additionalProperties": false
}
]
});
let sort = json!({
"type": "object",
"properties": {
@ -145,6 +172,7 @@ pub fn catalogue() -> Vec<ToolDef> {
"uniqueItems": true
},
"assignedAgentId": { "type": "string", "format": "uuid" },
"createdBy": created_by,
"text": { "type": "string" },
"sort": sort.clone(),
"limit": { "type": "integer", "minimum": 1 },
@ -199,6 +227,34 @@ pub fn catalogue() -> Vec<ToolDef> {
"additionalProperties": false
}),
},
ToolDef {
name: "idea_ticket_bulk_update_status",
description:
"Update the status of several IdeA tickets and return one result per ticket.",
input_schema: json!({
"type": "object",
"properties": {
"refs": refs.clone(),
"status": status.clone()
},
"required": ["refs", "status"],
"additionalProperties": false
}),
},
ToolDef {
name: "idea_ticket_bulk_update_priority",
description:
"Update the priority of several IdeA tickets and return one result per ticket.",
input_schema: json!({
"type": "object",
"properties": {
"refs": refs.clone(),
"priority": priority.clone()
},
"required": ["refs", "priority"],
"additionalProperties": false
}),
},
ToolDef {
name: "idea_ticket_read_carnet",
description: "Read the editable carnet attached to a ticket.",

View File

@ -74,6 +74,8 @@ pub const WRITE_ACTION_TOOLS: &[&str] = &[
"idea_ticket_update",
"idea_ticket_update_status",
"idea_ticket_update_priority",
"idea_ticket_bulk_update_status",
"idea_ticket_bulk_update_priority",
"idea_ticket_update_carnet",
"idea_ticket_link",
"idea_ticket_unlink",