fix: align read projections with query contracts

This commit is contained in:
2026-08-17 05:37:06 +09:00
parent 4964583868
commit 977c8e7b21
7 changed files with 320 additions and 61 deletions
@@ -422,12 +422,12 @@ fn list_schema() -> serde_json::Value {
"type":"object",
"additionalProperties": false,
"properties":{
"text":{"type":["string","null"]},
"query":{"type":["string","null"]},
"states":{"type":"array","items":{"type":"string"},"default":[]},
"linked_ticket_id":{"type":["string","null"]},
"updated_after":{"type":["string","null"]},
"updated_before":{"type":["string","null"]},
"sort":{"type":["string","null"],"enum":["updated_desc","title",null]},
"sort":{"type":["string","null"],"enum":["relevance","updated_desc","created_desc","title",null]},
"limit":{"type":["integer","null"],"minimum":1,"maximum":100},
"cursor":{"type":["string","null"]}
}
@@ -510,7 +510,7 @@ fn id_ticket_schema(required: &[&str]) -> serde_json::Value {
#[derive(Debug, Serialize, Deserialize)]
struct QueryObjectiveInput {
text: Option<String>,
query: Option<String>,
#[serde(default)]
states: Vec<String>,
linked_ticket_id: Option<String>,
+104 -25
View File
@@ -69,33 +69,103 @@ impl WorkspaceTicketReadKind {
}
}
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
enum WorkspaceTicketStateFilter {
Planning,
Ready,
Queued,
Inprogress,
Done,
Closed,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
enum WorkspaceTicketEvidenceFilter {
ImplementationReport,
ImplementationReportAfterRescope,
MergeRequest,
Commit,
ApprovedReview,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
enum WorkspaceTicketReviewFilter {
None,
Pending,
Approved,
RequestChanges,
UnresolvedChanges,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
enum WorkspaceTicketAttentionFilter {
DoneNotClosed,
ImplementationReportNotClosed,
ReportAfterRescope,
UnresolvedReview,
MissingCommit,
Blocked,
Unblocked,
Ready,
AwaitingReview,
UnresolvedChanges,
StaleAfterRescope,
MissingEvidence,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
enum WorkspaceTicketRelationFilter {
DependsOn,
Blocks,
Related,
Supersedes,
DuplicateOf,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
enum WorkspaceTicketSort {
Relevance,
UpdatedDesc,
CreatedDesc,
Priority,
Title,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
struct WorkspaceQueryTicketInput {
/// Full-text match over Ticket title, item body, and bounded thread excerpts.
text: Option<String>,
query: Option<String>,
/// Exact workflow states. Empty means every state.
#[serde(default)]
states: Vec<String>,
states: Vec<WorkspaceTicketStateFilter>,
/// Exact typed event kinds that must occur in the bounded thread window.
#[serde(default)]
event_kinds: Vec<String>,
/// Required evidence kinds: implementation_report, implementation_report_after_rescope,
/// merge_request, commit, or approved_review.
#[serde(default)]
evidence: Vec<String>,
/// Current authoritative Merge Request review status.
review_status: Option<String>,
/// Attention filters: blocked, ready, awaiting_review, unresolved_changes,
/// stale_after_rescope, or missing_evidence.
evidence: Vec<WorkspaceTicketEvidenceFilter>,
/// Current authoritative Merge Request review status: none, pending, approved,
/// request_changes, or unresolved_changes.
review_status: Option<WorkspaceTicketReviewFilter>,
/// Attention filters include done_not_closed, implementation_report_not_closed,
/// report_after_rescope, unresolved_review, missing_commit, blocked, and unblocked.
#[serde(default)]
attention: Vec<String>,
attention: Vec<WorkspaceTicketAttentionFilter>,
related_ticket_id: Option<String>,
relation_kind: Option<String>,
relation_kind: Option<WorkspaceTicketRelationFilter>,
linked_objective_id: Option<String>,
updated_after: Option<String>,
updated_before: Option<String>,
/// updated_desc (default), priority, or title.
sort: Option<String>,
/// relevance (default when query is present), updated_desc, created_desc,
/// priority, or title.
sort: Option<WorkspaceTicketSort>,
/// Page size; bounded by the Backend to 1..=100.
limit: Option<usize>,
/// Opaque cursor returned by a prior QueryTicket page.
@@ -306,14 +376,7 @@ impl TicketFeatureAccess {
}
}
const READ_ONLY_TOOL_NAMES: &[&str] = &[
"QueryTicket",
"ShowTicket",
"TicketDependencyCheck",
"TicketDoctor",
"TicketRelationQuery",
"TicketOrchestrationPlanQuery",
];
const READ_ONLY_TOOL_NAMES: &[&str] = &["QueryTicket", "ShowTicket"];
const AUTHORING_TOOL_NAMES: &[&str] = &[
"TicketCreate",
@@ -337,12 +400,8 @@ const WORKSPACE_AUTHORING_TOOL_NAMES: &[&str] = &[
"TicketComment",
"TicketQueue",
"TicketClose",
"TicketDependencyCheck",
"TicketDoctor",
"TicketRelationRecord",
"TicketRelationRemove",
"TicketRelationQuery",
"TicketOrchestrationPlanQuery",
];
#[cfg(test)]
@@ -353,10 +412,8 @@ const WORKFLOW_TOOL_NAMES: &[&str] = &[
"TicketWorkflowState",
"TicketClose",
"TicketDependencyCheck",
"TicketDoctor",
"TicketRelationRecord",
"TicketRelationRemove",
"TicketRelationQuery",
"TicketOrchestrationPlanRecord",
"TicketOrchestrationPlanQuery",
];
@@ -364,9 +421,11 @@ const WORKFLOW_TOOL_NAMES: &[&str] = &[
const WORKFLOW_ADDITIONAL_TOOL_NAMES: &[&str] = &[
"TicketWorkflowState",
"TicketClose",
"TicketDependencyCheck",
"TicketRelationRecord",
"TicketRelationRemove",
"TicketOrchestrationPlanRecord",
"TicketOrchestrationPlanQuery",
];
#[derive(Clone, Debug)]
@@ -1232,10 +1291,30 @@ mod tests {
assert!(query.input_schema["properties"]["evidence"].is_object());
assert!(query.input_schema["properties"]["attention"].is_object());
assert!(query.input_schema["properties"]["cursor"].is_object());
let query_schema = serde_json::to_string(&query.input_schema).unwrap();
assert!(query_schema.contains("done_not_closed"));
assert!(query_schema.contains("request_changes"));
assert!(query_schema.contains("created_desc"));
assert!(
query_schema.len() < 8_000,
"QueryTicket schema grew unexpectedly"
);
let (show, _) = workspace_ticket_read_definition(client, WorkspaceTicketReadKind::Show)();
assert_eq!(show.name, "ShowTicket");
assert!(show.input_schema["properties"]["event_limit"].is_object());
let tool_names = TicketFeatureAccess::workspace_authoring().tool_names();
assert_eq!(tool_names.len(), 9);
assert!(
tool_names.len() < 13,
"authoring catalog must stay below the prior broad catalog"
);
let workflow_names = TicketFeatureAccess::workflow().tool_names();
assert_eq!(workflow_names.len(), 10);
assert!(
workflow_names.len() < 12,
"workflow catalog must stay below the prior broad catalog"
);
assert_eq!(TicketFeatureAccess::review().tool_names().len(), 2);
assert!(tool_names.contains(&"QueryTicket"));
assert!(tool_names.contains(&"ShowTicket"));
assert!(!tool_names.contains(&"TicketList"));