feat(ticket): expose case tags and independent assignment handling

This commit is contained in:
2026-10-12 07:33:11 +09:00
parent 56795fb92c
commit 9b0d42fdbc
79 changed files with 9326 additions and 713 deletions
+142
View File
@@ -362,6 +362,12 @@ impl_openapi_schema!(
TicketOrchestrationPlanRecord,
TicketOrchestrationPlanRecordList,
TicketOrchestrationPlanSearchRequest,
TicketAssignmentsQuery,
TicketAssignmentsPage,
TicketAssignmentAnswerRequest,
TicketAssignmentAnswerResponse,
TicketAssignmentDiagnosticsQuery,
TicketAssignmentDiagnosticsPage,
TicketQueryRequest,
TicketQueryResponse,
TicketQueueResponse,
@@ -3198,6 +3204,60 @@ pub trait ServerApi {
#[path] ticket_id: String,
#[body] request: TicketCaseUpdateRequest,
) -> Result<TicketSummary, RepositoryApiError>;
#[get("/workspaces/{workspace_id}/tickets/{ticket_id}/assignment-tracking/assignments", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500], bearer_auth = true, browser_auth = true)]
async fn ticket_assignments_page(
&self,
#[extension] context: ServerRequestContext,
#[path] workspace_id: String,
#[path] ticket_id: String,
#[query] query: TicketAssignmentsQuery,
) -> Result<TicketAssignmentsPage, RepositoryApiError>;
#[get("/api/w/{workspace_id}/tickets/{ticket_id}/assignment-tracking/assignments", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500], bearer_auth = true, browser_auth = true)]
async fn ticket_assignments_page_browser(
&self,
#[extension] context: ServerRequestContext,
#[path] workspace_id: String,
#[path] ticket_id: String,
#[query] query: TicketAssignmentsQuery,
) -> Result<TicketAssignmentsPage, RepositoryApiError>;
#[get("/workspaces/{workspace_id}/tickets/{ticket_id}/assignment-diagnostics", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500], bearer_auth = true, browser_auth = true)]
async fn ticket_assignment_diagnostics(
&self,
#[extension] context: ServerRequestContext,
#[path] workspace_id: String,
#[path] ticket_id: String,
#[query] query: TicketAssignmentDiagnosticsQuery,
) -> Result<TicketAssignmentDiagnosticsPage, RepositoryApiError>;
#[get("/api/w/{workspace_id}/tickets/{ticket_id}/assignment-diagnostics", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500], bearer_auth = true, browser_auth = true)]
async fn ticket_assignment_diagnostics_browser(
&self,
#[extension] context: ServerRequestContext,
#[path] workspace_id: String,
#[path] ticket_id: String,
#[query] query: TicketAssignmentDiagnosticsQuery,
) -> Result<TicketAssignmentDiagnosticsPage, RepositoryApiError>;
#[post("/workspaces/{workspace_id}/tickets/{ticket_id}/assignment-answer", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 422, 500], bearer_auth = true, browser_auth = true)]
async fn ticket_assignment_answer(
&self,
#[extension] context: ServerRequestContext,
#[path] workspace_id: String,
#[path] ticket_id: String,
#[body] request: TicketAssignmentAnswerRequest,
) -> Result<TicketAssignmentAnswerResponse, RepositoryApiError>;
#[post("/api/w/{workspace_id}/tickets/{ticket_id}/assignment-answer", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 409, 422, 500], bearer_auth = true, browser_auth = true)]
async fn ticket_assignment_answer_browser(
&self,
#[extension] context: ServerRequestContext,
#[path] workspace_id: String,
#[path] ticket_id: String,
#[body] request: TicketAssignmentAnswerRequest,
) -> Result<TicketAssignmentAnswerResponse, RepositoryApiError>;
#[get("/workspaces/{workspace_id}/tickets/{ticket_id}/assignment-tracking", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500], bearer_auth = true, browser_auth = true)]
async fn ticket_assignment_tracking_get(
&self,
@@ -7604,6 +7664,21 @@ impl TicketAttention {
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[serde(deny_unknown_fields)]
pub struct TicketQueryRequest {
/// Exact reason of the current closed-case judgment; reopened/history-only reasons do not match.
#[cfg_attr(feature = "typescript", ts(type = "TicketClosureReason | null"))]
pub closure_reason: Option<ticket::TicketClosureReason>,
/// Exact current assigned principal; retained relationships do not match.
#[cfg_attr(
feature = "typescript",
ts(type = "TicketAssignmentPrincipalSnapshot | null")
)]
pub assigned_principal: Option<ticket::TicketAssignmentPrincipalSnapshot>,
/// Explicit self-reported handling only; never inferred from Worker state.
#[cfg_attr(
feature = "typescript",
ts(type = "TicketAssignmentTrackingStatus | null")
)]
pub handling_status: Option<ticket::TicketAssignmentTrackingStatus>,
pub query: Option<String>,
#[serde(default)]
#[cfg_attr(feature = "typescript", ts(type = "Array<BrowserTicketState>"))]
@@ -7860,6 +7935,73 @@ pub struct TicketAssignmentTrackingHistoryPage {
pub next_event_id: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct TicketAssignmentsQuery {
pub after_assignment_id: Option<String>,
#[schemars(range(min = 1, max = 100))]
pub limit: Option<usize>,
}
transparent_ticket_dto!(TicketAssignmentsPage, ticket::TicketAssignmentsPage);
transparent_ticket_dto!(
TicketAssignmentAnswerRequest,
ticket::TicketAssignmentAnswer
);
transparent_ticket_dto!(
TicketAssignmentAnswerResponse,
ticket::TicketAssignmentAnswerReceipt
);
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct TicketAssignmentDiagnosticsQuery {
pub assignment_id: String,
pub after_input_event_id: Option<String>,
pub after_start_operation_id: Option<String>,
#[schemars(range(min = 1, max = 100))]
pub limit: Option<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum TicketAssignmentInputDeliveryState {
NotReserved,
Reserved,
RequestPersisted,
RuntimeAccepted,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
pub struct TicketAssignmentInputDiagnostic {
pub input_event_id: String,
pub delivery_id: Option<String>,
pub notification: TicketAssignmentInputDeliveryState,
/// Absence of a Runtime ack is not proof of failed external delivery.
pub acceptance_outcome_unknown: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
pub struct TicketAssignmentStartDiagnostic {
pub operation_id: String,
/// Durable start/assignment reservation result, not Worker liveness.
pub claim_state: String,
pub assignment_id: Option<String>,
pub runtime_id: Option<String>,
pub worker_id: Option<String>,
/// Existing records do not attest that a model processed a particular input.
pub actual_processing_unknown: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
pub struct TicketAssignmentDiagnosticsPage {
pub assignment_id: String,
pub current: bool,
pub input_origin_known: bool,
/// Noncurrent/done/closed/released records are history, not pending work.
pub actionable: bool,
pub inputs: Vec<TicketAssignmentInputDiagnostic>,
pub next_input_event_id: Option<String>,
/// Assignment-scoped admission evidence; not attributed to individual input.
pub starts: Vec<TicketAssignmentStartDiagnostic>,
pub next_start_operation_id: Option<String>,
}
transparent_ticket_dto!(WorkspaceTicketTagListRequest, ticket::TagListRequest);
transparent_ticket_dto!(
WorkspaceTicketTagMutationRequest,
@@ -500,6 +500,48 @@ fn validate_ticket_browser_operations(
request: Some("TicketCaseUpdateRequest"),
successes: &[("200", "TicketSummary")],
},
OperationContract {
path: "/workspaces/{workspace_id}/tickets/{ticket_id}/assignment-tracking/assignments",
method: "get",
operation_id: "ticket_assignments_page",
request: None,
successes: &[("200", "TicketAssignmentsPage")],
},
OperationContract {
path: "/workspaces/{workspace_id}/tickets/{ticket_id}/assignment-diagnostics",
method: "get",
operation_id: "ticket_assignment_diagnostics",
request: None,
successes: &[("200", "TicketAssignmentDiagnosticsPage")],
},
OperationContract {
path: "/workspaces/{workspace_id}/tickets/{ticket_id}/assignment-answer",
method: "post",
operation_id: "ticket_assignment_answer",
request: Some("TicketAssignmentAnswer"),
successes: &[("200", "TicketAssignmentAnswerReceipt")],
},
OperationContract {
path: "/api/w/{workspace_id}/tickets/{ticket_id}/assignment-tracking/assignments",
method: "get",
operation_id: "ticket_assignments_page_browser",
request: None,
successes: &[("200", "TicketAssignmentsPage")],
},
OperationContract {
path: "/api/w/{workspace_id}/tickets/{ticket_id}/assignment-diagnostics",
method: "get",
operation_id: "ticket_assignment_diagnostics_browser",
request: None,
successes: &[("200", "TicketAssignmentDiagnosticsPage")],
},
OperationContract {
path: "/api/w/{workspace_id}/tickets/{ticket_id}/assignment-answer",
method: "post",
operation_id: "ticket_assignment_answer_browser",
request: Some("TicketAssignmentAnswer"),
successes: &[("200", "TicketAssignmentAnswerReceipt")],
},
OperationContract {
path: "/workspaces/{workspace_id}/tickets/{ticket_id}/assignment-tracking",
method: "get",