fix: preserve notification uncertainty and target provenance

This commit is contained in:
2026-10-15 18:15:47 +09:00
parent f50fa47a48
commit e33729647c
10 changed files with 188 additions and 13 deletions
+15
View File
@@ -33,6 +33,16 @@ pub enum NotificationDiagnosticStage {
RuntimeAccepted, RuntimeAccepted,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum NotificationTargetResolution {
Unresolved,
Reserved,
RequestPersisted,
RuntimeAccepted,
MultipleAuthorizedTargets,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct NotificationDiagnostic { pub struct NotificationDiagnostic {
@@ -51,6 +61,11 @@ pub struct NotificationDiagnostic {
pub purpose: Option<String>, pub purpose: Option<String>,
pub delivery_id: Option<String>, pub delivery_id: Option<String>,
pub stage: NotificationDiagnosticStage, pub stage: NotificationDiagnosticStage,
/// Conservative: a persisted request without ACK is enough; this does not prove a send occurred.
/// Can remain true alongside a later preflight failure, rejection or historical recipient.
pub acceptance_outcome_unknown: bool,
/// Failure records do not identify a particular authorized replacement target.
pub target_resolution: NotificationTargetResolution,
pub target_runtime_id: Option<String>, pub target_runtime_id: Option<String>,
pub target_worker_id: Option<String>, pub target_worker_id: Option<String>,
pub last_failure_code: Option<String>, pub last_failure_code: Option<String>,
@@ -1,4 +1,51 @@
use super::*; use super::*;
#[tokio::test]
async fn notification_wip_executes_same_read_and_honors_profile_denial() {
for denied in [false, true] {
let client = Arc::new(Client {
requests: Mutex::new(vec![]),
status: 200,
body: json!({"items":[],"next_cursor":null,"source_checks":[]}).to_string(),
});
let tools = native_ticket_tools(client.clone(), TicketFeatureAccess::read_only()).unwrap();
let handler = TicketCollectionWipHandler {
tools: operation_map(
tools
.into_iter()
.filter(|t| t.projection.surface == NativeTicketSurface::Collection)
.collect(),
),
permissions: denied.then_some(ToolPermissionConfig {
default_action: ToolPermissionAction::Deny,
rules: vec![],
}),
collection_route: "/tickets".into(),
observations: Arc::new(Mutex::new(TicketObservationState::default())),
};
let args = BTreeMap::from([("ticket".into(), WipValue::String("T-755".into()))]);
let result = handler
.call(
"notification_diagnostics",
&args,
WipCallContext {
execution: ToolExecutionContext::direct(),
security_context: "worker".into(),
},
)
.await;
assert_eq!(result.is_err(), denied);
let requests = client.requests.lock().unwrap();
assert_eq!(requests.len(), usize::from(!denied));
if !denied {
assert_eq!(requests[0].method, WorkspaceRequestMethod::Get);
assert_eq!(
requests[0].path,
"/api/w/workspace/notification-diagnostics?ticket=T-755"
);
}
}
}
use crate::worker::{WorkspaceClientError, WorkspaceRequest, WorkspaceResponse}; use crate::worker::{WorkspaceClientError, WorkspaceRequest, WorkspaceResponse};
#[derive(Debug)] #[derive(Debug)]
struct Client { struct Client {
@@ -32,15 +79,23 @@ async fn notification_tool_and_wip_share_bounded_read_contract() {
let client = Arc::new(Client { let client = Arc::new(Client {
requests: Mutex::new(vec![]), requests: Mutex::new(vec![]),
status: 200, status: 200,
body: json!({"items":[],"next_cursor":null,"source_checks":[]}).to_string(), body: json!({"items":[{"source_id":"notification-source:5","source_event_id":"mr-event:m:e","event_kind":"mr_review","recipient_role":"orchestrator","stage":"acceptance_unknown","acceptance_outcome_unknown":true,"target_resolution":"multiple_authorized_targets","retry_candidate":true,"model_processing_confirmed":false}],"next_cursor":null,"source_checks":[]}).to_string(),
}); });
let (meta, tool) = definition(client.clone())(); let (meta, tool) = definition(client.clone())();
let schema = jsonschema::validator_for(&meta.input_schema).unwrap(); let schema = jsonschema::validator_for(&meta.input_schema).unwrap();
let input = json!({"ticket":"T-755","merge_request_id":"mr/1?","job_id":"job","cursor":"notification-source:4","limit":10}); let input = json!({"ticket":"T-755","merge_request_id":"mr/1?","job_id":"job","cursor":"notification-source:4","limit":10});
assert!(schema.is_valid(&input)); assert!(schema.is_valid(&input));
tool.execute(&input.to_string(), ToolExecutionContext::direct()) let output = tool
.execute(&input.to_string(), ToolExecutionContext::direct())
.await .await
.unwrap(); .unwrap();
let page: Value = serde_json::from_str(output.content.as_deref().unwrap()).unwrap();
assert_eq!(page["items"][0]["acceptance_outcome_unknown"], true);
assert_eq!(
page["items"][0]["target_resolution"],
"multiple_authorized_targets"
);
assert!(page["items"][0]["target_worker_id"].is_null());
let requests = client.requests.lock().unwrap(); let requests = client.requests.lock().unwrap();
assert_eq!(requests.len(), 1); assert_eq!(requests.len(), 1);
assert_eq!(requests[0].method, WorkspaceRequestMethod::Get); assert_eq!(requests[0].method, WorkspaceRequestMethod::Get);
@@ -42,6 +42,10 @@ fn source_checks(
.and_then(|v| v.parse::<i64>().ok()), .and_then(|v| v.parse::<i64>().ok()),
) { ) {
Some(conn.query_row("SELECT EXISTS(SELECT 1 FROM typed_ticket_events e LEFT JOIN workspace_resource_keys k ON k.workspace_id=e.workspace_id AND k.resource_kind='ticket' AND k.resource_id=e.ticket_id WHERE e.workspace_id=?1 AND (e.ticket_id=?2 OR k.resource_key=?2) AND e.event_index=?3)",params![workspace,ticket,index],|r|r.get(0))?) Some(conn.query_row("SELECT EXISTS(SELECT 1 FROM typed_ticket_events e LEFT JOIN workspace_resource_keys k ON k.workspace_id=e.workspace_id AND k.resource_kind='ticket' AND k.resource_id=e.ticket_id WHERE e.workspace_id=?1 AND (e.ticket_id=?2 OR k.resource_key=?2) AND e.event_index=?3)",params![workspace,ticket,index],|r|r.get(0))?)
} else if let Some((mr, event)) =
id.strip_prefix("mr-event:").and_then(|v| v.split_once(':'))
{
Some(conn.query_row("SELECT EXISTS(SELECT 1 FROM merge_request_thread_events WHERE workspace_id=?1 AND merge_request_id=?2 AND event_id=?3)",params![workspace,mr,event],|r|r.get(0))?)
} else { } else {
None None
}; };
@@ -157,7 +161,9 @@ pub(super) fn page_with_connection(
COALESCE(json_extract(a.target_json,'$.worker.runtime_id'),json_extract(aa.source_json,'$.runtime_id'),json_extract(r.target_json,'$.worker.runtime_id'),json_extract(ar.reserved_source_json,'$.runtime_id')), COALESCE(json_extract(a.target_json,'$.worker.runtime_id'),json_extract(aa.source_json,'$.runtime_id'),json_extract(r.target_json,'$.worker.runtime_id'),json_extract(ar.reserved_source_json,'$.runtime_id')),
COALESCE(json_extract(a.target_json,'$.worker.worker_id'),json_extract(aa.source_json,'$.worker_id'),json_extract(r.target_json,'$.worker.worker_id'),json_extract(ar.reserved_source_json,'$.worker_id')), COALESCE(json_extract(a.target_json,'$.worker.worker_id'),json_extract(aa.source_json,'$.worker_id'),json_extract(r.target_json,'$.worker.worker_id'),json_extract(ar.reserved_source_json,'$.worker_id')),
f.code,f.recorded_at, f.code,f.recorded_at,
CASE WHEN s.role IN ('orchestrator','worker') AND s.assignment_id!='' THEN EXISTS(SELECT 1 FROM ticket_current_worker_assignments c WHERE c.workspace_id=s.workspace_id AND c.ticket_id=s.ticket_id AND c.role=s.role AND c.assignment_id=s.assignment_id) END CASE WHEN s.role IN ('orchestrator','worker') AND s.assignment_id!='' THEN EXISTS(SELECT 1 FROM ticket_current_worker_assignments c WHERE c.workspace_id=s.workspace_id AND c.ticket_id=s.ticket_id AND c.role=s.role AND c.assignment_id=s.assignment_id) END,
EXISTS(SELECT 1 FROM notification_failures uf WHERE uf.workspace_id=s.workspace_id AND uf.delivery_id=d.delivery_id AND uf.code IN ('acceptance_unknown','acknowledgement_failed')),
(SELECT COUNT(*) FROM (SELECT DISTINCT at.runtime_id,at.worker_id FROM notification_assignment_targets at WHERE at.workspace_id=s.workspace_id AND at.delivery_id=d.delivery_id))
{joins} {joins}
LEFT JOIN notification_requests r ON r.workspace_id=s.workspace_id AND r.delivery_id=d.delivery_id LEFT JOIN notification_requests r ON r.workspace_id=s.workspace_id AND r.delivery_id=d.delivery_id
LEFT JOIN notification_acknowledgements a ON a.workspace_id=s.workspace_id AND a.delivery_id=d.delivery_id LEFT JOIN notification_acknowledgements a ON a.workspace_id=s.workspace_id AND a.delivery_id=d.delivery_id
@@ -176,6 +182,31 @@ pub(super) fn page_with_connection(
let accepted: bool = row.get(14)?; let accepted: bool = row.get(14)?;
let failure: Option<String> = row.get(17)?; let failure: Option<String> = row.get(17)?;
let recipient_current: Option<bool> = row.get(19)?; let recipient_current: Option<bool> = row.get(19)?;
let unresolved_send: bool = row.get(20)?;
let multiple_targets = row.get::<_, i64>(21)? > 1;
let acceptance_outcome_unknown = !accepted && (prepared || unresolved_send);
use server_api::NotificationTargetResolution as Target;
let target_resolution = if accepted {
Target::RuntimeAccepted
} else if multiple_targets {
Target::MultipleAuthorizedTargets
} else if prepared {
Target::RequestPersisted
} else if reserved {
Target::Reserved
} else {
Target::Unresolved
};
let target_runtime_id = if multiple_targets && !accepted {
None
} else {
bounded(row.get(15)?)
};
let target_worker_id = if multiple_targets && !accepted {
None
} else {
bounded(row.get(16)?)
};
let stage = if accepted { let stage = if accepted {
Stage::RuntimeAccepted Stage::RuntimeAccepted
} else if recipient_current == Some(false) { } else if recipient_current == Some(false) {
@@ -186,7 +217,7 @@ pub(super) fn page_with_connection(
Stage::Suppressed Stage::Suppressed
} else if disposition.as_deref() == Some("defer") { } else if disposition.as_deref() == Some("defer") {
Stage::Deferred Stage::Deferred
} else if failure.as_deref() == Some("acceptance_unknown") { } else if unresolved_send {
Stage::AcceptanceUnknown Stage::AcceptanceUnknown
} else if failure.as_deref() == Some("runtime_rejected") { } else if failure.as_deref() == Some("runtime_rejected") {
Stage::RuntimeRejected Stage::RuntimeRejected
@@ -218,8 +249,10 @@ pub(super) fn page_with_connection(
purpose: bounded(row.get(10)?), purpose: bounded(row.get(10)?),
delivery_id: bounded(row.get(11)?), delivery_id: bounded(row.get(11)?),
stage, stage,
target_runtime_id: bounded(row.get(15)?), acceptance_outcome_unknown,
target_worker_id: bounded(row.get(16)?), target_resolution,
target_runtime_id,
target_worker_id,
last_failure_code: bounded(failure), last_failure_code: bounded(failure),
last_failure_at: bounded(row.get(18)?), last_failure_at: bounded(row.get(18)?),
retry_candidate, retry_candidate,
@@ -1,5 +1,55 @@
// Production Store and SQLite. No Runtime/model side effects are required for diagnostics. // Production Store and SQLite. No Runtime/model side effects are required for diagnostics.
#[tokio::test] #[tokio::test]
async fn notification_diagnostics_preserves_unknown_acceptance_and_marks_replacement_ambiguity() {
use server_api::{NotificationDiagnosticsQuery as Q,NotificationDiagnosticStage as S,NotificationTargetResolution as T};
for initial_failure in [NotificationFailureCode::AcceptanceUnknown,NotificationFailureCode::AcknowledgementFailed] {
let (_dir,store)=tracking_store_fixture().await;
tracking_store_assign(&store,"a","assign-a");
let source=tracking_store_source();
let delivery=delivery_store_reserve_initial(&store,&source);
store.prepare_ticket_assignment_delivery_content("w",&delivery.delivery_id,"frozen body",&source).unwrap();
let key=NotificationDeliveryKey::AssignmentInput{assignment_id:"a".into(),input_event_id:delivery.input_event_id.clone()};
store.record_notification_delivery_failure("w",&key,initial_failure).unwrap();
store.record_notification_delivery_failure("w",&key,NotificationFailureCode::TargetStopped).unwrap();
let query=Q{delivery_id:Some(delivery.delivery_id.clone()),..Default::default()};
let read=||store.notification_diagnostics("w",&query).unwrap().items.remove(0);
assert_eq!(read().stage,S::AcceptanceUnknown);
assert!(read().acceptance_outcome_unknown);
assert_eq!(read().last_failure_code.as_deref(),Some("target_stopped"));
store.with_conn(|conn|{conn.execute("UPDATE worker_singleton_owners SET worker_id='replacement' WHERE workspace_id='w'",[])?;Ok(())}).unwrap();
let replacement=TicketAssignmentTrackingSourceContext{worker_id:"replacement".into(),..source};
store.prepare_ticket_assignment_delivery_content("w",&delivery.delivery_id,"ignored new body",&replacement).unwrap();
let ambiguous=read();
assert_eq!(ambiguous.target_resolution,T::MultipleAuthorizedTargets);
assert!(ambiguous.target_runtime_id.is_none() && ambiguous.target_worker_id.is_none());
store.ack_ticket_assignment_delivery("w",&delivery.delivery_id,&replacement).unwrap();
let accepted=read();
assert_eq!(accepted.target_resolution,T::RuntimeAccepted);
assert_eq!(accepted.target_worker_id.as_deref(),Some("replacement"));
assert!(!accepted.acceptance_outcome_unknown);
}
}
#[tokio::test]
async fn notification_diagnostics_detects_exact_missing_mr_event_after_earlier_capture() {
use server_api::NotificationDiagnosticsQuery as Q;
let (_dir,store)=tracking_store_fixture().await;
tracking_store_assign(&store,"a","assign-a");
store.with_conn(|conn| {
conn.execute("INSERT INTO merge_requests(workspace_id,merge_request_id,repository_id,state,selector_to,created_at,updated_at) SELECT workspace_id,'mr-diagnostic',repository_id,'open','develop','1','1' FROM repositories WHERE workspace_id='w' LIMIT 1",[])?;
for (event,seq) in [("first",1),("missing",2)] {
conn.execute("INSERT INTO merge_request_thread_events(workspace_id,merge_request_id,event_id,sequence,kind,payload_json,created_at) VALUES('w','mr-diagnostic',?1,?2,'review','{}','1')",params![event,seq])?;
}
conn.execute("INSERT INTO notification_sources(workspace_id,ticket_id,input_event_id,role,assignment_id,kind,facts_json) SELECT workspace_id,ticket_id,'mr-event:mr-diagnostic:first',role,assignment_id,'mr_review',json_set(facts_json,'$.merge_request_id','mr-diagnostic') FROM notification_sources WHERE assignment_id='a' LIMIT 1",[])?;
Ok(())
}).unwrap();
let page=store.notification_diagnostics("w",&Q{merge_request_id:Some("mr-diagnostic".into()),source_event_id:Some("mr-event:mr-diagnostic:missing".into()),..Default::default()}).unwrap();
assert!(page.items.is_empty());
assert!(page.source_checks[0].captured);
assert_eq!(page.source_checks[1].origin_present,Some(true));
assert!(!page.source_checks[1].captured);
}
#[tokio::test]
async fn notification_diagnostics_bounds_saved_reasons_without_returning_source_payloads() { async fn notification_diagnostics_bounds_saved_reasons_without_returning_source_payloads() {
use server_api::NotificationDiagnosticsQuery as Q; use server_api::NotificationDiagnosticsQuery as Q;
let (_dir,store)=tracking_store_fixture().await; let (_dir,store)=tracking_store_fixture().await;
+21 -1
View File
@@ -5635,6 +5635,10 @@
"NotificationDiagnostic": { "NotificationDiagnostic": {
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"acceptance_outcome_unknown": {
"description": "Conservative: a persisted request without ACK is enough; this does not prove a send occurred.\nCan remain true alongside a later preflight failure, rejection or historical recipient.",
"type": "boolean"
},
"assignment_id": { "assignment_id": {
"type": [ "type": [
"string", "string",
@@ -5719,6 +5723,10 @@
"stage": { "stage": {
"$ref": "#/components/schemas/NotificationDiagnosticStage" "$ref": "#/components/schemas/NotificationDiagnosticStage"
}, },
"target_resolution": {
"$ref": "#/components/schemas/NotificationTargetResolution",
"description": "Failure records do not identify a particular authorized replacement target."
},
"target_runtime_id": { "target_runtime_id": {
"type": [ "type": [
"string", "string",
@@ -5744,6 +5752,8 @@
"event_kind", "event_kind",
"recipient_role", "recipient_role",
"stage", "stage",
"acceptance_outcome_unknown",
"target_resolution",
"retry_candidate", "retry_candidate",
"model_processing_confirmed" "model_processing_confirmed"
], ],
@@ -5884,6 +5894,16 @@
], ],
"type": "object" "type": "object"
}, },
"NotificationTargetResolution": {
"enum": [
"unresolved",
"reserved",
"request_persisted",
"runtime_accepted",
"multiple_authorized_targets"
],
"type": "string"
},
"Nullable_string": { "Nullable_string": {
"type": [ "type": [
"string", "string",
@@ -17487,7 +17507,7 @@
"info": { "info": {
"title": "Yoi Server API", "title": "Yoi Server API",
"version": "0.1.0", "version": "0.1.0",
"x-yoi-source-digest": "sha256:ba0b73fac609dc65712a95c3b96f1044b16f82a5775f591813c02e8f392b5a04" "x-yoi-source-digest": "sha256:d3f51c6f6b7091ca393c9d197309e280a18357889e9e6df69bbdfcaeb13d0cc3"
}, },
"openapi": "3.1.0", "openapi": "3.1.0",
"paths": { "paths": {
@@ -1,6 +1,6 @@
// @generated by yoi-companion-openapi-typescript@1. Do not edit. // @generated by yoi-companion-openapi-typescript@1. Do not edit.
// Input: openapi/server-api.json // Input: openapi/server-api.json
// Input contract: 0.1.0 (sha256:ba0b73fac609dc65712a95c3b96f1044b16f82a5775f591813c02e8f392b5a04) // Input contract: 0.1.0 (sha256:d3f51c6f6b7091ca393c9d197309e280a18357889e9e6df69bbdfcaeb13d0cc3)
// Options: companion-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output // Options: companion-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
// Output: web/workspace/src/lib/generated/companion-api.ts // Output: web/workspace/src/lib/generated/companion-api.ts
// Regenerate/check: cargo run -q -p server-api --example generate_companion_api_types -- --check // Regenerate/check: cargo run -q -p server-api --example generate_companion_api_types -- --check
@@ -1,6 +1,6 @@
// @generated by yoi-repository-openapi-typescript@1. Do not edit. // @generated by yoi-repository-openapi-typescript@1. Do not edit.
// Input: openapi/server-api.json // Input: openapi/server-api.json
// Input contract: 0.1.0 (sha256:ba0b73fac609dc65712a95c3b96f1044b16f82a5775f591813c02e8f392b5a04) // Input contract: 0.1.0 (sha256:d3f51c6f6b7091ca393c9d197309e280a18357889e9e6df69bbdfcaeb13d0cc3)
// Options: repository-operation-schema-closure,openapi-3.1,format:uint64=safe-number,strict-safe-integers,sorted-output // Options: repository-operation-schema-closure,openapi-3.1,format:uint64=safe-number,strict-safe-integers,sorted-output
// Output: web/workspace/src/lib/generated/repository-api.ts // Output: web/workspace/src/lib/generated/repository-api.ts
// Regenerate/check: cargo run -q -p server-api --example generate_repository_openapi_types -- --check // Regenerate/check: cargo run -q -p server-api --example generate_repository_openapi_types -- --check
@@ -1,6 +1,6 @@
// @generated by yoi-runtime-openapi-typescript@1. Do not edit. // @generated by yoi-runtime-openapi-typescript@1. Do not edit.
// Input: openapi/server-api.json // Input: openapi/server-api.json
// Input contract: 0.1.0 (sha256:ba0b73fac609dc65712a95c3b96f1044b16f82a5775f591813c02e8f392b5a04) // Input contract: 0.1.0 (sha256:d3f51c6f6b7091ca393c9d197309e280a18357889e9e6df69bbdfcaeb13d0cc3)
// Options: runtime-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output // Options: runtime-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
// Output: web/workspace/src/lib/generated/runtime-api.ts // Output: web/workspace/src/lib/generated/runtime-api.ts
// Regenerate/check: cargo run -q -p server-api --example generate_runtime_api_types -- --check // Regenerate/check: cargo run -q -p server-api --example generate_runtime_api_types -- --check
@@ -1,6 +1,6 @@
// @generated by yoi-ticket-browser-openapi-typescript@1. Do not edit. // @generated by yoi-ticket-browser-openapi-typescript@1. Do not edit.
// Input: openapi/server-api.json // Input: openapi/server-api.json
// Input contract: 0.1.0 (sha256:ba0b73fac609dc65712a95c3b96f1044b16f82a5775f591813c02e8f392b5a04) // Input contract: 0.1.0 (sha256:d3f51c6f6b7091ca393c9d197309e280a18357889e9e6df69bbdfcaeb13d0cc3)
// Options: ticket-objective-merge-request-browser-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output // Options: ticket-objective-merge-request-browser-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
// Output: web/workspace/src/lib/generated/ticket-api.ts // Output: web/workspace/src/lib/generated/ticket-api.ts
// Regenerate/check: cargo run -q -p server-api --example generate_ticket_api_types -- --check // Regenerate/check: cargo run -q -p server-api --example generate_ticket_api_types -- --check
@@ -61,7 +61,7 @@ export type MergeStrategy = "fast_forward" | "merge";
export type NewTicket = { assignee?: string | null; author?: string | null; body: MarkdownText; kind: string; labels: Array<string>; priority: string; queued_at?: string | null; queued_by?: string | null; readiness?: string | null; risk_flags: Array<string>; slug?: string | null; targets?: Array<TicketTarget>; title: string; workflow_state?: TicketState | null; }; export type NewTicket = { assignee?: string | null; author?: string | null; body: MarkdownText; kind: string; labels: Array<string>; priority: string; queued_at?: string | null; queued_by?: string | null; readiness?: string | null; risk_flags: Array<string>; slug?: string | null; targets?: Array<TicketTarget>; title: string; workflow_state?: TicketState | null; };
export type NotificationDiagnostic = { assignment_id?: string | null; delivery_id?: string | null; disposition?: string | null; event_kind: string; job_id?: string | null; last_failure_at?: string | null; last_failure_code?: string | null; merge_request_id?: string | null; model_processing_confirmed: boolean; purpose?: string | null; reason?: string | null; recipient_current?: boolean | null; recipient_role: string; retry_candidate: boolean; source_event_id: string; source_id: string; stage: NotificationDiagnosticStage; target_runtime_id?: string | null; target_worker_id?: string | null; ticket?: string | null; }; export type NotificationDiagnostic = { acceptance_outcome_unknown: boolean; assignment_id?: string | null; delivery_id?: string | null; disposition?: string | null; event_kind: string; job_id?: string | null; last_failure_at?: string | null; last_failure_code?: string | null; merge_request_id?: string | null; model_processing_confirmed: boolean; purpose?: string | null; reason?: string | null; recipient_current?: boolean | null; recipient_role: string; retry_candidate: boolean; source_event_id: string; source_id: string; stage: NotificationDiagnosticStage; target_resolution: NotificationTargetResolution; target_runtime_id?: string | null; target_worker_id?: string | null; ticket?: string | null; };
export type NotificationDiagnosticStage = "historical" | "awaiting_routing" | "suppressed" | "deferred" | "pending" | "reserved" | "request_persisted" | "runtime_rejected" | "acceptance_unknown" | "delivery_failed" | "runtime_accepted"; export type NotificationDiagnosticStage = "historical" | "awaiting_routing" | "suppressed" | "deferred" | "pending" | "reserved" | "request_persisted" | "runtime_rejected" | "acceptance_unknown" | "delivery_failed" | "runtime_accepted";
@@ -69,6 +69,8 @@ export type NotificationDiagnosticsPage = { items: Array<NotificationDiagnostic>
export type NotificationSourceCheck = { captured: boolean; origin_present?: boolean | null; reference: string; resource_kind: string; }; export type NotificationSourceCheck = { captured: boolean; origin_present?: boolean | null; reference: string; resource_kind: string; };
export type NotificationTargetResolution = "unresolved" | "reserved" | "request_persisted" | "runtime_accepted" | "multiple_authorized_targets";
export type ObjectiveDetail = { body: string; body_truncated: boolean; created_at?: string | null; event_page: QueryPage; events: Array<ObjectiveEventDetail>; id: string; linked_ticket_summaries: Array<ObjectiveLinkedTicketSummary>; linked_tickets: Array<string>; record_source: string; resource_key: string; resources: Array<ObjectiveResourceSummary>; state: string; title: string; updated_at?: string | null; }; export type ObjectiveDetail = { body: string; body_truncated: boolean; created_at?: string | null; event_page: QueryPage; events: Array<ObjectiveEventDetail>; id: string; linked_ticket_summaries: Array<ObjectiveLinkedTicketSummary>; linked_tickets: Array<string>; record_source: string; resource_key: string; resources: Array<ObjectiveResourceSummary>; state: string; title: string; updated_at?: string | null; };
export type ObjectiveEventDetail = { body?: string | null; created_at: string; event_ref: string; kind: string; }; export type ObjectiveEventDetail = { body?: string | null; created_at: string; event_ref: string; kind: string; };
@@ -1,6 +1,6 @@
// @generated by yoi-worker-openapi-typescript@1. Do not edit. // @generated by yoi-worker-openapi-typescript@1. Do not edit.
// Input: openapi/server-api.json // Input: openapi/server-api.json
// Input contract: 0.1.0 (sha256:ba0b73fac609dc65712a95c3b96f1044b16f82a5775f591813c02e8f392b5a04) // Input contract: 0.1.0 (sha256:d3f51c6f6b7091ca393c9d197309e280a18357889e9e6df69bbdfcaeb13d0cc3)
// Options: worker-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output // Options: worker-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
// Output: web/workspace/src/lib/generated/worker-launch-api.ts // Output: web/workspace/src/lib/generated/worker-launch-api.ts
// Regenerate/check: cargo run -q -p server-api --example generate_worker_launch_api_types -- --check // Regenerate/check: cargo run -q -p server-api --example generate_worker_launch_api_types -- --check