feat: expose read-only notification routing and delivery diagnostics

This commit is contained in:
2026-10-15 17:55:54 +09:00
parent e70cba4618
commit b71fc9ee54
19 changed files with 1114 additions and 6 deletions
+12
View File
@@ -7,6 +7,8 @@ pub use api_macros::axum as server_support;
pub use api_macros::reqwest as client_support;
pub use api_macros::{ApiContract, BinaryBody, HttpMethod, TransportMetadata, WebSocketOperation};
mod drive;
mod notification;
pub use notification::*;
pub mod repository_openapi_typescript;
pub use drive::*;
mod workspace_config;
@@ -366,6 +368,8 @@ impl_openapi_schema!(
TicketAssignmentsPage,
TicketAssignmentAnswerRequest,
TicketAssignmentAnswerResponse,
NotificationDiagnosticsQuery,
NotificationDiagnosticsPage,
TicketAssignmentDiagnosticsQuery,
TicketAssignmentDiagnosticsPage,
TicketQueryRequest,
@@ -3241,6 +3245,14 @@ pub trait ServerApi {
#[query] query: TicketAssignmentsQuery,
) -> Result<TicketAssignmentsPage, RepositoryApiError>;
#[get("/api/w/{workspace_id}/notification-diagnostics", status = 200, error_status = 400, additional_error_statuses = [401, 403, 404, 500], bearer_auth = true, browser_auth = true)]
async fn notification_diagnostics(
&self,
#[extension] context: ServerRequestContext,
#[path] workspace_id: String,
#[query] query: NotificationDiagnosticsQuery,
) -> Result<NotificationDiagnosticsPage, 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,
+82
View File
@@ -0,0 +1,82 @@
//! Read-only notification evidence. Acceptance is not model processing or execution authority.
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct NotificationDiagnosticsQuery {
pub ticket: Option<String>,
pub merge_request_id: Option<String>,
pub job_id: Option<String>,
pub source_event_id: Option<String>,
pub assignment_id: Option<String>,
pub delivery_id: Option<String>,
/// Exact source identity for a single-record lookup.
pub source_id: Option<String>,
pub cursor: Option<String>,
pub limit: Option<usize>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum NotificationDiagnosticStage {
Historical,
AwaitingRouting,
Suppressed,
Deferred,
Pending,
Reserved,
RequestPersisted,
RuntimeRejected,
AcceptanceUnknown,
DeliveryFailed,
RuntimeAccepted,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct NotificationDiagnostic {
pub source_id: String,
pub source_event_id: String,
pub event_kind: String,
pub ticket: Option<String>,
pub merge_request_id: Option<String>,
pub job_id: Option<String>,
pub recipient_role: String,
pub assignment_id: Option<String>,
/// Current assignment identity only; null for fixed-worker or absent recipients.
pub recipient_current: Option<bool>,
pub disposition: Option<String>,
pub reason: Option<String>,
pub purpose: Option<String>,
pub delivery_id: Option<String>,
pub stage: NotificationDiagnosticStage,
pub target_runtime_id: Option<String>,
pub target_worker_id: Option<String>,
pub last_failure_code: Option<String>,
pub last_failure_at: Option<String>,
/// Saved delivery candidate, NOT permission to send. Live recipient and binding checks remain required.
pub retry_candidate: bool,
/// No model-processing evidence is currently collected by this read contract.
pub model_processing_confirmed: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct NotificationSourceCheck {
pub resource_kind: String,
pub reference: String,
/// Whether an exact durable origin is present; null means that reference form was not verified.
pub origin_present: Option<bool>,
/// Whether the origin has any captured source (independent of pagination/recipient filters).
/// A present origin with false here is unconnected, not intentionally suppressed.
pub captured: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct NotificationDiagnosticsPage {
pub items: Vec<NotificationDiagnostic>,
pub next_cursor: Option<String>,
pub source_checks: Vec<NotificationSourceCheck>,
}
@@ -536,6 +536,13 @@ fn validate_ticket_browser_operations(
request: None,
successes: &[("200", "TicketAssignmentsPage")],
},
OperationContract {
path: "/api/w/{workspace_id}/notification-diagnostics",
method: "get",
operation_id: "notification_diagnostics",
request: None,
successes: &[("200", "NotificationDiagnosticsPage")],
},
OperationContract {
path: "/workspaces/{workspace_id}/tickets/{ticket_id}/assignment-diagnostics",
method: "get",