fix: simplify Ticket reporting and speed up browser lists

Remove model-supplied assignment and retry identities from implementation reports; retain Host idempotency, authentication and notification evidence.

Add a SQL-only browser projection and decouple auxiliary page loads. Preserve authoritative MR evidence queries. Include regression tests and component benchmarks, documenting the remaining no-match search cost.
This commit is contained in:
2026-10-16 01:58:15 +09:00
parent 9f1cbbd385
commit b67aa2de7d
42 changed files with 3197 additions and 152 deletions
+13 -2
View File
@@ -9,6 +9,8 @@ pub use api_macros::{ApiContract, BinaryBody, HttpMethod, TransportMetadata, Web
mod drive;
mod notification;
pub use notification::*;
mod ticket_browse;
pub use ticket_browse::*;
pub mod repository_openapi_typescript;
pub use drive::*;
mod workspace_config;
@@ -376,6 +378,8 @@ impl_openapi_schema!(
TicketAssignmentDiagnosticsPage,
TicketQueryRequest,
TicketQueryResponse,
TicketBrowseQueryRequest,
TicketBrowseQueryResponse,
TicketQueueResponse,
TicketRecord,
TicketRecordRef,
@@ -3033,6 +3037,14 @@ pub trait ServerApi {
#[body] request: TicketTagsSetRequest,
) -> Result<WorkspaceTicketTagList, RepositoryApiError>;
/// Lightweight browser list; deliberately excludes live MR evidence and external observations.
#[post("/api/w/{workspace_id}/tickets/browse", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500], bearer_auth = true, browser_auth = true)]
async fn ticket_browse(
&self,
#[path] workspace_id: String,
#[body] request: TicketBrowseQueryRequest,
) -> Result<TicketBrowseQueryResponse, RepositoryApiError>;
#[post("/api/w/{workspace_id}/tickets/query", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500], bearer_auth = true, browser_auth = true)]
async fn ticket_query(
&self,
@@ -8058,9 +8070,8 @@ pub enum TicketImplementationResultKind {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct TicketImplementationResultRequest {
/// Host-owned replay identity; model-facing tools derive it from the Tool call.
pub operation_key: String,
pub assignment_id: String,
pub orchestrator_assignment_id: String,
pub kind: TicketImplementationResultKind,
pub body: String,
pub merge_request: Option<MergeRequestReviewSubject>,
@@ -459,6 +459,13 @@ fn validate_ticket_browser_operations(
request: Some("TicketTagSetRequest"),
successes: &[("200", "Array_of_WorkspaceTag")],
},
OperationContract {
path: "/api/w/{workspace_id}/tickets/browse",
method: "post",
operation_id: "ticket_browse",
request: Some("TicketBrowseQueryRequest"),
successes: &[("200", "TicketBrowseQueryResponse")],
},
OperationContract {
path: "/api/w/{workspace_id}/tickets/query",
method: "post",
+65
View File
@@ -0,0 +1,65 @@
//! Lightweight Tickets browser contract, separate from authoritative evidence queries.
use crate::{BrowserTicketState, QueryPage};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[serde(deny_unknown_fields)]
pub struct TicketBrowseQueryRequest {
pub query: Option<String>,
#[serde(default)]
#[cfg_attr(feature = "typescript", ts(type = "Array<BrowserTicketState>"))]
pub states: Vec<ticket::TicketState>,
/// Every supplied active tag ID must match (AND).
#[serde(default)]
pub tag_ids: Vec<String>,
#[cfg_attr(
feature = "typescript",
ts(type = "TicketAssignmentPrincipalSnapshot | null")
)]
pub assigned_principal: Option<ticket::TicketAssignmentPrincipalSnapshot>,
#[cfg_attr(
feature = "typescript",
ts(type = "TicketAssignmentTrackingStatus | null")
)]
pub handling_status: Option<ticket::TicketAssignmentTrackingStatus>,
/// Exact current closed-case reason; historical reasons do not match this filter.
#[cfg_attr(feature = "typescript", ts(type = "TicketClosureReason | null"))]
pub closure_reason: Option<ticket::TicketClosureReason>,
/// Only updated_desc is supported; defaults to updated_desc.
pub sort: Option<String>,
#[schemars(range(min = 0, max = 1000))]
pub limit: Option<usize>,
/// Browse-only cursor bound to the Workspace, canonical filters, and sort.
pub cursor: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[serde(deny_unknown_fields)]
pub struct TicketBrowseQueryItem {
pub id: String,
pub resource_key: String,
pub title: String,
pub state: BrowserTicketState,
pub updated_at: Option<String>,
#[cfg_attr(feature = "typescript", ts(type = "Array<WorkspaceTag>"))]
pub tags: Vec<ticket::WorkspaceTag>,
#[cfg_attr(feature = "typescript", ts(type = "TicketClosureJudgment | null"))]
pub current_closure: Option<ticket::TicketClosureJudgment>,
#[cfg_attr(
feature = "typescript",
ts(type = "Array<TicketAssignmentTrackingProjection>")
)]
pub assignment_tracking: Vec<ticket::TicketAssignmentTrackingProjection>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[serde(deny_unknown_fields)]
pub struct TicketBrowseQueryResponse {
pub items: Vec<TicketBrowseQueryItem>,
pub page: QueryPage,
pub record_authority: String,
}