diff --git a/crates/runtime-api/tests/fixtures/startup-browser-registration-response-lost.problem.json b/crates/runtime-api/tests/fixtures/startup-browser-registration-response-lost.problem.json new file mode 100644 index 000000000..109543b79 --- /dev/null +++ b/crates/runtime-api/tests/fixtures/startup-browser-registration-response-lost.problem.json @@ -0,0 +1 @@ +{"type":"urn:yoi:problem:dependency","title":"Dependency failure","status":502,"detail":"Worker creation could not complete Session registration: Unknown principal has an unknown session.record outcome. Creation acceptance and execution outcome are reported separately.","instance":"urn:yoi:worker-startup:worker-af7808a90c24ed19bb134795f4460b01f7d40be31567155dff4431898e871343","yoi_failure":{"version":1,"required_extensions":[],"domain":"session_registration","reason":"outcome_unknown","category":"unknown","details_version":1,"details":{"reason":"outcome_unknown","principal":"unknown","required_operation":"session.record"},"explanation":"Worker creation could not complete Session registration: Unknown principal has an unknown session.record outcome. Creation acceptance and execution outcome are reported separately.","context":{"operations":[{"operation":"session.record","stage":"registration","operation_ref":null},{"operation":"worker_startup","stage":"binding","operation_ref":null}],"causes":[],"diagnostics":[],"correlation":null,"supplementary_omitted":false}},"yoi_startup":{"version":1,"operation":{"acceptance":"unknown","execution":"errored","finalize":null,"backend_compensation":"unknown","correlation":"worker-af7808a90c24ed19bb134795f4460b01f7d40be31567155dff4431898e871343"},"secondary":[{"kind":"persistence","phase":"runtime_record_delete","outcome":"outcome_unknown"}],"resources":{"resources":"worker_startup","cleanup":"confirmed","durable_effects_possible":false},"persistence":"persisted","backend_result_persistence":"persisted","io_kind":null}} \ No newline at end of file diff --git a/crates/runtime-api/tests/fixtures/startup-browser-session-record-denied.problem.json b/crates/runtime-api/tests/fixtures/startup-browser-session-record-denied.problem.json new file mode 100644 index 000000000..10de5012e --- /dev/null +++ b/crates/runtime-api/tests/fixtures/startup-browser-session-record-denied.problem.json @@ -0,0 +1 @@ +{"type":"urn:yoi:problem:conflict","title":"Operation conflict","status":409,"detail":"Worker creation could not complete Session registration: Subject is not authorized for required operation session.record. Creation acceptance and execution outcome are reported separately.","instance":"urn:yoi:worker-startup:worker-8d4e95ba9450471443009bb32726792468b06281cc2664258dfb2eae7ffd7bf5","yoi_failure":{"version":1,"required_extensions":[],"domain":"session_registration","reason":"permission_denied","category":"authorization","details_version":1,"details":{"reason":"permission_denied","principal":"subject","required_operation":"session.record"},"explanation":"Worker creation could not complete Session registration: Subject is not authorized for required operation session.record. Creation acceptance and execution outcome are reported separately.","context":{"operations":[{"operation":"session.record","stage":"registration","operation_ref":null},{"operation":"worker_startup","stage":"binding","operation_ref":null}],"causes":[],"diagnostics":[],"correlation":null,"supplementary_omitted":false}},"yoi_startup":{"version":1,"operation":{"acceptance":"unknown","execution":"errored","finalize":null,"backend_compensation":"unknown","correlation":"worker-8d4e95ba9450471443009bb32726792468b06281cc2664258dfb2eae7ffd7bf5"},"secondary":[{"kind":"persistence","phase":"runtime_record_delete","outcome":"outcome_unknown"}],"resources":{"resources":"worker_startup","cleanup":"confirmed","durable_effects_possible":false},"persistence":"persisted","backend_result_persistence":"persisted","io_kind":null}} \ No newline at end of file diff --git a/crates/server-api/src/repository_openapi_typescript.rs b/crates/server-api/src/repository_openapi_typescript.rs index 269e5b1c7..71ec4605d 100644 --- a/crates/server-api/src/repository_openapi_typescript.rs +++ b/crates/server-api/src/repository_openapi_typescript.rs @@ -304,6 +304,7 @@ fn validate_worker_operations( ) -> Result, GenerationError> { let operations = [ OperationContract { + error: "RepositoryApiError", path: "/api/workers", method: "get", operation_id: "workspace_worker_list_alias", @@ -311,6 +312,7 @@ fn validate_worker_operations( successes: &[("200", "WorkerListResponse")], }, OperationContract { + error: "WorkerCreateApiError", path: "/api/workers", method: "post", operation_id: "workspace_worker_create_alias", @@ -318,6 +320,7 @@ fn validate_worker_operations( successes: &[("200", "BrowserCreateWorkerResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/orchestrator", method: "get", operation_id: "workspace_orchestrator_status", @@ -325,6 +328,7 @@ fn validate_worker_operations( successes: &[("200", "BrowserWorkspaceOrchestratorResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/orchestrator", method: "post", operation_id: "workspace_orchestrator_start", @@ -332,6 +336,7 @@ fn validate_worker_operations( successes: &[("200", "BrowserWorkspaceOrchestratorResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/workers", method: "get", operation_id: "workspace_worker_list", @@ -339,6 +344,7 @@ fn validate_worker_operations( successes: &[("200", "WorkerListResponse")], }, OperationContract { + error: "WorkerCreateApiError", path: "/api/w/{workspace_id}/workers", method: "post", operation_id: "workspace_worker_create", @@ -346,6 +352,7 @@ fn validate_worker_operations( successes: &[("200", "BrowserCreateWorkerResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/workers/{worker_ref}", method: "get", operation_id: "workspace_worker_detail", @@ -353,6 +360,7 @@ fn validate_worker_operations( successes: &[("200", "WorkerSummary")], }, OperationContract { + error: "RepositoryApiError", path: "/api/workers/launch-options", method: "get", operation_id: "workspace_worker_launch_options_alias", @@ -360,6 +368,7 @@ fn validate_worker_operations( successes: &[("200", "WorkerLaunchOptionsResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/workers/launch-options", method: "get", operation_id: "workspace_worker_launch_options", @@ -375,6 +384,7 @@ fn validate_companion_operations( ) -> Result, GenerationError> { let operations = [ OperationContract { + error: "RepositoryApiError", path: "/api/companion/status", method: "get", operation_id: "companion_status_alias", @@ -382,6 +392,7 @@ fn validate_companion_operations( successes: &[("200", "CompanionStatusResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/companion/status", method: "get", operation_id: "companion_status", @@ -389,6 +400,7 @@ fn validate_companion_operations( successes: &[("200", "CompanionStatusResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/companion/transcript", method: "get", operation_id: "companion_transcript_alias", @@ -396,6 +408,7 @@ fn validate_companion_operations( successes: &[("200", "CompanionTranscriptProjection")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/companion/transcript", method: "get", operation_id: "companion_transcript", @@ -403,6 +416,7 @@ fn validate_companion_operations( successes: &[("200", "CompanionTranscriptProjection")], }, OperationContract { + error: "RepositoryApiError", path: "/api/companion/messages", method: "post", operation_id: "companion_message_alias", @@ -410,6 +424,7 @@ fn validate_companion_operations( successes: &[("200", "CompanionMessageResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/companion/messages", method: "post", operation_id: "companion_message", @@ -417,6 +432,7 @@ fn validate_companion_operations( successes: &[("200", "CompanionMessageResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/companion/cancel", method: "post", operation_id: "companion_cancel_alias", @@ -424,6 +440,7 @@ fn validate_companion_operations( successes: &[("200", "CompanionMessageResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/companion/cancel", method: "post", operation_id: "companion_cancel", @@ -439,6 +456,7 @@ fn validate_ticket_browser_operations( ) -> Result, GenerationError> { let operations = [ OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/tickets/{id}/link-summary", method: "get", operation_id: "ticket_link_summary_get", @@ -446,6 +464,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "TicketLinkSummary")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/tickets/{id}/parent", method: "get", operation_id: "ticket_parent_get", @@ -453,6 +472,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "TicketParentLink")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/tickets/{id}/parent", method: "put", operation_id: "ticket_parent_set", @@ -460,6 +480,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "TicketParentLink")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/tickets/{id}/children", method: "get", operation_id: "ticket_children_query", @@ -467,6 +488,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "TicketChildrenPage")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/ticket-tags", method: "get", operation_id: "workspace_ticket_tag_list", @@ -474,6 +496,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "Array_of_WorkspaceTag")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/ticket-tags", method: "post", operation_id: "workspace_ticket_tag_mutate", @@ -481,6 +504,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "WorkspaceTag")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/tickets/{id}/tags", method: "put", operation_id: "ticket_tags_set", @@ -488,6 +512,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "Array_of_WorkspaceTag")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/tickets/browse", method: "post", operation_id: "ticket_browse", @@ -495,6 +520,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "TicketBrowseQueryResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/tickets/query", method: "post", operation_id: "ticket_query", @@ -502,6 +528,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "TicketQueryResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/tickets", method: "get", operation_id: "ticket_list", @@ -509,6 +536,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "TicketListResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/tickets", method: "post", operation_id: "ticket_create_record", @@ -516,6 +544,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "TicketRef")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/tickets/{id}", method: "get", operation_id: "ticket_get", @@ -523,6 +552,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "TicketDetail")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/tickets/{id}", method: "patch", operation_id: "ticket_edit", @@ -530,6 +560,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "TicketDetail")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/tickets/{id}/posts/{event_ref}", method: "get", operation_id: "ticket_post_get", @@ -537,6 +568,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "TicketEventDetail")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/tickets/{id}/posts/{event_ref}", method: "patch", operation_id: "ticket_post_edit", @@ -544,6 +576,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "TicketDetail")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/tickets/{id}/assignments/{role}", method: "put", operation_id: "ticket_assignment_set", @@ -551,6 +584,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "TicketRoleAssignmentMutationResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/tickets/{id}/implementation-cancellations", method: "post", operation_id: "ticket_implementation_cancel", @@ -558,6 +592,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "TicketDetail")], }, OperationContract { + error: "RepositoryApiError", path: "/workspaces/{workspace_id}/tickets/{ticket_id}/case", method: "post", operation_id: "ticket_case_update", @@ -565,6 +600,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "TicketSummary")], }, OperationContract { + error: "RepositoryApiError", path: "/workspaces/{workspace_id}/tickets/{ticket_id}/assignment-tracking/assignments", method: "get", operation_id: "ticket_assignments_page", @@ -572,6 +608,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "TicketAssignmentsPage")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/notification-diagnostics", method: "get", operation_id: "notification_diagnostics", @@ -579,6 +616,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "NotificationDiagnosticsPage")], }, OperationContract { + error: "RepositoryApiError", path: "/workspaces/{workspace_id}/tickets/{ticket_id}/assignment-diagnostics", method: "get", operation_id: "ticket_assignment_diagnostics", @@ -586,6 +624,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "TicketAssignmentDiagnosticsPage")], }, OperationContract { + error: "RepositoryApiError", path: "/workspaces/{workspace_id}/tickets/{ticket_id}/implementation-results", method: "post", operation_id: "ticket_implementation_result", @@ -593,6 +632,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "TicketImplementationResultReceipt")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/tickets/{ticket_id}/implementation-results", method: "post", operation_id: "ticket_implementation_result_browser", @@ -600,6 +640,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "TicketImplementationResultReceipt")], }, OperationContract { + error: "RepositoryApiError", path: "/workspaces/{workspace_id}/tickets/{ticket_id}/implementation-results/{result_id}", method: "get", operation_id: "ticket_implementation_result_get", @@ -607,6 +648,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "TicketImplementationResultReceipt")], }, OperationContract { + error: "RepositoryApiError", path: "/workspaces/{workspace_id}/tickets/{ticket_id}/assignment-answer", method: "post", operation_id: "ticket_assignment_answer", @@ -614,6 +656,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "TicketAssignmentAnswerReceipt")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/tickets/{ticket_id}/assignment-tracking/assignments", method: "get", operation_id: "ticket_assignments_page_browser", @@ -621,6 +664,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "TicketAssignmentsPage")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/tickets/{ticket_id}/assignment-diagnostics", method: "get", operation_id: "ticket_assignment_diagnostics_browser", @@ -628,6 +672,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "TicketAssignmentDiagnosticsPage")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/tickets/{ticket_id}/assignment-answer", method: "post", operation_id: "ticket_assignment_answer_browser", @@ -635,6 +680,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "TicketAssignmentAnswerReceipt")], }, OperationContract { + error: "RepositoryApiError", path: "/workspaces/{workspace_id}/tickets/{ticket_id}/assignment-tracking", method: "get", operation_id: "ticket_assignment_tracking_get", @@ -642,6 +688,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "Array_of_TicketAssignmentTrackingProjection")], }, OperationContract { + error: "RepositoryApiError", path: "/workspaces/{workspace_id}/tickets/{ticket_id}/assignment-tracking", method: "post", operation_id: "ticket_assignment_tracking_update", @@ -649,6 +696,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "TicketAssignmentTrackingProjection")], }, OperationContract { + error: "RepositoryApiError", path: "/workspaces/{workspace_id}/tickets/{ticket_id}/assignment-tracking/history", method: "get", operation_id: "ticket_assignment_tracking_history", @@ -656,6 +704,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "TicketAssignmentTrackingHistoryPage")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/tickets/{id}/ready", method: "post", operation_id: "ticket_ready", @@ -663,6 +712,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "TicketDetail")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/tickets/{id}/events", method: "post", operation_id: "ticket_event_append", @@ -670,6 +720,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "TicketDetail")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/tickets/{id}/queue", method: "post", operation_id: "ticket_queue", @@ -677,6 +728,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "TicketQueueOutcome")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/objectives", method: "get", operation_id: "objective_list", @@ -684,6 +736,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "ObjectiveListResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/objectives/{objective_id}", method: "get", operation_id: "objective_get", @@ -691,6 +744,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "ObjectiveDetail")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/merge-requests", method: "get", operation_id: "merge_request_list", @@ -698,6 +752,7 @@ fn validate_ticket_browser_operations( successes: &[("200", "MergeRequestListResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/merge-requests/{merge_request_id}", method: "get", operation_id: "merge_request_show", @@ -713,6 +768,7 @@ fn validate_repository_operations( ) -> Result, GenerationError> { let operations = [ OperationContract { + error: "RepositoryApiError", path: "/api/repositories", method: "get", operation_id: "repository_list_alias", @@ -720,6 +776,7 @@ fn validate_repository_operations( successes: &[("200", "RepositoryListResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/repositories/{repository_key}", method: "get", operation_id: "repository_detail_alias", @@ -727,6 +784,7 @@ fn validate_repository_operations( successes: &[("200", "RepositoryDetailResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/repositories", method: "get", operation_id: "repository_list", @@ -734,6 +792,7 @@ fn validate_repository_operations( successes: &[("200", "RepositoryListResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/repositories", method: "post", operation_id: "repository_create", @@ -744,6 +803,7 @@ fn validate_repository_operations( ], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/repositories/{repository_key}", method: "get", operation_id: "repository_detail", @@ -751,6 +811,7 @@ fn validate_repository_operations( successes: &[("200", "RepositoryDetailResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/repositories/{repository_key}/ssh-connection-test", method: "post", operation_id: "repository_ssh_connection_probe", @@ -758,6 +819,7 @@ fn validate_repository_operations( successes: &[("200", "RepositorySshConnectionProbeResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/repositories/{repository_key}/ssh-connection-test", method: "put", operation_id: "repository_ssh_host_trust_confirm", @@ -765,6 +827,7 @@ fn validate_repository_operations( successes: &[("200", "RepositorySshHostTrust")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/repositories/{repository_key}/log", method: "get", operation_id: "repository_log", @@ -772,6 +835,7 @@ fn validate_repository_operations( successes: &[("200", "RepositoryLogResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/repositories/{repository_key}/log", method: "get", operation_id: "repository_log_alias", @@ -779,6 +843,7 @@ fn validate_repository_operations( successes: &[("200", "RepositoryLogResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/hosts", method: "get", operation_id: "host_list", @@ -786,6 +851,7 @@ fn validate_repository_operations( successes: &[("200", "HostListResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/hosts", method: "get", operation_id: "host_list_alias", @@ -793,6 +859,7 @@ fn validate_repository_operations( successes: &[("200", "HostListResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/hosts/{host_id}/workers", method: "get", operation_id: "host_worker_list", @@ -800,6 +867,7 @@ fn validate_repository_operations( successes: &[("200", "HostWorkerListResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/hosts/{host_id}/workers", method: "get", operation_id: "host_worker_list_alias", @@ -815,6 +883,7 @@ fn validate_workdir_operations( ) -> Result, GenerationError> { let operations = [ OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/runtimes/{runtime_id}/working-directories", method: "get", operation_id: "runtime_workdir_list", @@ -822,6 +891,7 @@ fn validate_workdir_operations( successes: &[("200", "WorkingDirectoryListResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/runtimes/{runtime_id}/working-directories", method: "post", operation_id: "runtime_workdir_create", @@ -832,6 +902,7 @@ fn validate_workdir_operations( ], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/runtimes/{runtime_id}/working-directories/{working_directory_id}", method: "get", operation_id: "runtime_workdir_detail", @@ -839,6 +910,7 @@ fn validate_workdir_operations( successes: &[("200", "WorkingDirectoryDetailResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/runtimes/{runtime_id}/working-directories/{working_directory_id}", method: "delete", operation_id: "runtime_workdir_cleanup", @@ -846,6 +918,7 @@ fn validate_workdir_operations( successes: &[("200", "WorkingDirectoryRemovalResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/external-workdir-grants", method: "post", operation_id: "external_workdir_grant_create", @@ -853,6 +926,7 @@ fn validate_workdir_operations( successes: &[("201", "ExternalWorkdirGrantResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/external-workdir-grants/{grant_id}", method: "get", operation_id: "external_workdir_grant_get", @@ -860,6 +934,7 @@ fn validate_workdir_operations( successes: &[("200", "ExternalWorkdirGrantResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/external-workdir-grants/{grant_id}", method: "delete", operation_id: "external_workdir_grant_revoke", @@ -867,6 +942,7 @@ fn validate_workdir_operations( successes: &[("200", "ExternalWorkdirGrantResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/working-directories", method: "get", operation_id: "workspace_workdir_list", @@ -874,6 +950,7 @@ fn validate_workdir_operations( successes: &[("200", "WorkingDirectoryListResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/working-directories", method: "post", operation_id: "workspace_workdir_create", @@ -884,6 +961,7 @@ fn validate_workdir_operations( ], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/working-directories/{working_directory_id}", method: "get", operation_id: "workspace_workdir_detail", @@ -891,6 +969,7 @@ fn validate_workdir_operations( successes: &[("200", "WorkingDirectoryDetailResponse")], }, OperationContract { + error: "RepositoryApiError", path: "/api/w/{workspace_id}/working-directories/{working_directory_id}", method: "delete", operation_id: "workspace_workdir_cleanup", @@ -1332,12 +1411,28 @@ fn validate_operation_contracts( &format!("{} response {status}", operation.operation_id), )?; let schema = json_content_schema(response, operation.operation_id)?; + let startup_error = operation.error == "WorkerCreateApiError" + && !successes.contains_key(status.as_str()); let expected = successes .get(status.as_str()) .copied() - .unwrap_or("RepositoryApiError"); + .unwrap_or(if startup_error { + "RepositoryApiError" + } else { + operation.error + }); require_schema_ref(schema, expected, operation.operation_id)?; roots.insert(expected.to_owned()); + if startup_error { + let content = object_field(response, "content", operation.operation_id)?; + let problem = + object_field(content, "application/problem+json", operation.operation_id)?; + let schema = problem.get("schema").ok_or_else(|| { + GenerationError::invalid("missing startup Problem Details schema") + })?; + require_schema_ref(schema, "StartupProblemDetails", operation.operation_id)?; + roots.insert("StartupProblemDetails".to_owned()); + } } for (status, expected) in successes { if !responses.contains_key(status) { @@ -1352,6 +1447,7 @@ fn validate_operation_contracts( } struct OperationContract { + error: &'static str, path: &'static str, method: &'static str, operation_id: &'static str, @@ -1789,8 +1885,31 @@ fn render_typed_schema( ) -> Result { match kind { "string" => { - ensure_schema_keywords(schema, &["type"], context)?; - Ok("string".to_owned()) + ensure_schema_keywords( + schema, + &["type", "minLength", "maxLength", "pattern"], + context, + )?; + let mut constraints = Vec::new(); + if schema.contains_key("minLength") || schema.contains_key("maxLength") { + let range = + render_length_range(schema.get("minLength"), schema.get("maxLength"), context)?; + constraints.push(format!("readonly \"__yoiStringLength\": \"{range}\";")); + } + if let Some(pattern) = schema.get("pattern") { + let pattern = pattern.as_str().ok_or_else(|| { + GenerationError::invalid(format!("{context} string pattern must be a string")) + })?; + constraints.push(format!( + "readonly \"__yoiStringPattern\": {};", + serde_json::to_string(pattern).expect("string encoding") + )); + } + if constraints.is_empty() { + Ok("string".to_owned()) + } else { + Ok(format!("string & {{ {} }}", constraints.join(" "))) + } } "boolean" => { ensure_schema_keywords(schema, &["type"], context)?; @@ -1880,9 +1999,12 @@ fn render_typed_schema( .join(", ") )) } - _ => Err(GenerationError::invalid(format!( - "{context} contains unsupported variable array bounds" - ))), + (minimum, maximum) => { + let range = render_length_range(minimum, maximum, context)?; + Ok(format!( + "Array<{item}> & {{ readonly \"__yoiArrayLength\": \"{range}\"; }}" + )) + } } } "object" => { @@ -1899,6 +2021,36 @@ fn render_typed_schema( } } +// Nominal markers preserve wire bounds in generated declarations; they are not +// runtime validators. Only finite safe non-negative integer constraints are admitted. +fn render_length_range( + minimum: Option<&Value>, + maximum: Option<&Value>, + context: &str, +) -> Result { + let bound = |value: &Value| { + value + .as_u64() + .filter(|value| *value <= MAX_SAFE_INTEGER as u64) + .ok_or_else(|| { + GenerationError::invalid(format!( + "{context} length bound must be a safe non-negative integer" + )) + }) + }; + let minimum = minimum.map(bound).transpose()?.unwrap_or(0); + let maximum = maximum.map(bound).transpose()?; + if maximum.is_some_and(|maximum| minimum > maximum) { + return Err(GenerationError::invalid(format!( + "{context} length range is inverted" + ))); + } + Ok(format!( + "{minimum}..{}", + maximum.map_or_else(|| "unbounded".into(), |maximum| maximum.to_string()) + )) +} + fn render_object(schema: &Map, context: &str) -> Result { let empty_properties = Map::new(); let properties = match schema.get("properties") { @@ -2282,6 +2434,50 @@ mod tests { )); } + #[test] + fn bounded_strings_and_variable_arrays_preserve_constraints_without_becoming_validators() { + assert_eq!( + render_schema( + &serde_json::json!({"type":"string", "minLength":1, "maxLength":128}), + "bounded" + ) + .unwrap(), + "string & { readonly \"__yoiStringLength\": \"1..128\"; }" + ); + assert_eq!( + render_schema( + &serde_json::json!({"type":"array", "items":{"type":"string"}, "maxItems":8}), + "bounded" + ) + .unwrap(), + "Array & { readonly \"__yoiArrayLength\": \"0..8\"; }" + ); + assert_eq!( + render_schema( + &serde_json::json!({"type":"string", "pattern":"^[a-z]+$"}), + "pattern" + ) + .unwrap(), + "string & { readonly \"__yoiStringPattern\": \"^[a-z]+$\"; }" + ); + assert!( + render_schema( + &serde_json::json!({"type":"string", "pattern":42}), + "pattern" + ) + .is_err() + ); + for schema in [ + serde_json::json!({"type":"string", "maxLength":-1}), + serde_json::json!({"type":"string", "minLength":2, "maxLength":1}), + serde_json::json!({"type":"string", "maxLength":1.5}), + serde_json::json!({"type":"array", "items":{"type":"string"}, "maxItems":9007199254740992u64}), + serde_json::json!({"type":"array", "items":{"type":"string"}, "minItems":9, "maxItems":8}), + ] { + assert!(render_schema(&schema, "bounded").is_err(), "{schema}"); + } + } + #[test] fn generator_rejects_lossy_reference_siblings() { let mut document: Value = serde_json::from_str(OPENAPI).unwrap(); @@ -2303,13 +2499,13 @@ mod tests { #[test] fn generator_rejects_unimplemented_ordinary_schema_keywords() { let mut document: Value = serde_json::from_str(OPENAPI).unwrap(); - document["components"]["schemas"]["RepositorySource"]["properties"]["uri"]["maxLength"] = - serde_json::json!(1); + document["components"]["schemas"]["RepositorySource"]["properties"]["uri"]["format"] = + serde_json::json!("email"); let error = generate_repository_typescript(&document.to_string()).unwrap_err(); assert!( error .to_string() - .contains("unsupported schema keyword `maxLength`") + .contains("unsupported schema keyword `format`") ); let mut document: Value = serde_json::from_str(OPENAPI).unwrap(); diff --git a/openapi/server-api.json b/openapi/server-api.json index 864bc1037..894587dcf 100644 --- a/openapi/server-api.json +++ b/openapi/server-api.json @@ -644,6 +644,41 @@ ], "type": "string" }, + "Category": { + "enum": [ + "authorization", + "invalid_input", + "conflict", + "dependency", + "internal", + "unknown" + ], + "type": "string" + }, + "CausalLink": { + "description": "Only explicit causal references belong here. Cleanup/audit failures stay in domain results.", + "properties": { + "correlation": { + "$ref": "#/components/schemas/CorrelationRef" + }, + "domain": { + "maxLength": 128, + "minLength": 1, + "type": "string" + }, + "reason": { + "maxLength": 128, + "minLength": 1, + "type": "string" + } + }, + "required": [ + "domain", + "reason", + "correlation" + ], + "type": "object" + }, "ChatConversation": { "additionalProperties": false, "properties": { @@ -1388,6 +1423,14 @@ ], "type": "object" }, + "CompensationOutcome": { + "enum": [ + "completed", + "residual", + "unknown" + ], + "type": "string" + }, "CompiledFlowDefinition": { "additionalProperties": false, "properties": { @@ -1860,6 +1903,12 @@ }, "type": "object" }, + "CorrelationRef": { + "description": "Correlation is deliberately a distinct field/type from operation identity.", + "maxLength": 128, + "minLength": 1, + "type": "string" + }, "CreateRemoteRuntimeRequest": { "additionalProperties": false, "properties": { @@ -2066,6 +2115,14 @@ ], "type": "object" }, + "CreationAcceptance": { + "enum": [ + "rejected", + "accepted", + "unknown" + ], + "type": "string" + }, "DefaultIntakeReadyBodyRequest": { "additionalProperties": false, "properties": { @@ -3218,6 +3275,17 @@ ], "type": "object" }, + "ExecutionOutcome": { + "enum": [ + "accepted", + "busy", + "rejected", + "errored", + "unsupported", + "unknown" + ], + "type": "string" + }, "ExtensibleTicketStatus": { "oneOf": [ { @@ -3451,6 +3519,72 @@ }, "type": "object" }, + "FailureContext": { + "description": "Common metadata is untrusted after deserialization. No audience label confers trust.", + "properties": { + "causes": { + "description": "Ordered immediate-to-root chain, at most four references; never recursive payloads.", + "items": { + "$ref": "#/components/schemas/CausalLink" + }, + "maxItems": 4, + "type": "array" + }, + "correlation": { + "anyOf": [ + { + "$ref": "#/components/schemas/CorrelationRef" + }, + { + "type": "null" + } + ] + }, + "diagnostics": { + "items": { + "$ref": "#/components/schemas/FailureDiagnostic" + }, + "maxItems": 8, + "type": "array" + }, + "operations": { + "items": { + "$ref": "#/components/schemas/OperationContext" + }, + "maxItems": 8, + "type": "array" + }, + "supplementary_omitted": { + "type": "boolean" + } + }, + "required": [ + "operations", + "causes", + "diagnostics", + "supplementary_omitted" + ], + "type": "object" + }, + "FailureDiagnostic": { + "properties": { + "code": { + "maxLength": 128, + "minLength": 1, + "type": "string" + }, + "explanation": { + "maxLength": 2048, + "minLength": 1, + "type": "string" + } + }, + "required": [ + "code", + "explanation" + ], + "type": "object" + }, "FeatureInvocation": { "properties": { "arguments": { @@ -3543,6 +3677,39 @@ } ] }, + "FinalizeFacts": { + "additionalProperties": false, + "properties": { + "compensation": { + "$ref": "#/components/schemas/CompensationOutcome" + }, + "stage": { + "$ref": "#/components/schemas/FinalizeStage" + } + }, + "required": [ + "stage", + "compensation" + ], + "type": "object" + }, + "FinalizeStage": { + "enum": [ + "worker_identity", + "workspace_api_binding", + "create_reservation", + "worker_registry", + "workdir_attachment", + "workdir_registry", + "ticket_assignment_bind", + "ticket_assignment_current", + "ticket_state_accept", + "session_registration", + "initial_input", + "publication" + ], + "type": "string" + }, "FindingSeverity": { "enum": [ "blocker", @@ -6934,6 +7101,34 @@ ], "type": "object" }, + "OperationContext": { + "properties": { + "operation": { + "maxLength": 128, + "minLength": 1, + "type": "string" + }, + "operation_ref": { + "description": "A reference, not an idempotency/retry grant. Disclosure remains domain-owned.", + "maxLength": 128, + "minLength": 1, + "type": [ + "string", + "null" + ] + }, + "stage": { + "maxLength": 128, + "minLength": 1, + "type": "string" + } + }, + "required": [ + "operation", + "stage" + ], + "type": "object" + }, "OperationCounts": { "properties": { "delete": { @@ -13099,6 +13294,42 @@ ], "type": "object" }, + "RuntimeStartupCause": { + "additionalProperties": false, + "description": "Safe local Runtime cause when no more specific domain attachment was supplied.\nThe primary cause is selected where the failure occurs, not parsed from its diagnostic.", + "properties": { + "reason": { + "$ref": "#/components/schemas/RuntimeStartupReason" + }, + "stage": { + "$ref": "#/components/schemas/StartupStage" + } + }, + "required": [ + "stage", + "reason" + ], + "type": "object" + }, + "RuntimeStartupReason": { + "description": "Coarse local causes do not claim a domain authorization verdict.", + "enum": [ + "startup_failed", + "segment_empty", + "system_prompt_missing", + "metadata_pending", + "metadata_missing", + "metadata_inactive", + "manifest_missing", + "allocation_failed", + "session_read_failed", + "metadata_read_failed", + "provider_failed", + "attribution_failed", + "feature_failed" + ], + "type": "string" + }, "RuntimeSummary": { "properties": { "arch": { @@ -14325,6 +14556,52 @@ "minimum": 0, "type": "integer" }, + "SessionOperation": { + "enum": [ + "session.record" + ], + "type": "string" + }, + "SessionPrincipal": { + "enum": [ + "operator", + "subject", + "unknown" + ], + "type": "string" + }, + "SessionRegistrationCause": { + "additionalProperties": false, + "description": "No account/session identifiers, paths, or free-form diagnostics are retained.", + "properties": { + "principal": { + "$ref": "#/components/schemas/SessionPrincipal" + }, + "reason": { + "$ref": "#/components/schemas/SessionRegistrationReason" + }, + "required_operation": { + "$ref": "#/components/schemas/SessionOperation" + } + }, + "required": [ + "reason", + "principal", + "required_operation" + ], + "type": "object" + }, + "SessionRegistrationReason": { + "enum": [ + "permission_denied", + "attribution_missing", + "binding_mismatch", + "transport", + "outcome_unknown", + "invalid_response" + ], + "type": "string" + }, "SetCookieHeader": { "description": "Sensitive `Set-Cookie` response header value.\n\nFormatting is available only for transport encoding; `Debug` always redacts the value.", "type": "string" @@ -14810,10 +15087,568 @@ ], "type": "object" }, + "StartupBoundaryCause": { + "additionalProperties": false, + "description": "An observation at the receiving/finalizing boundary, not a reported Runtime\nexecution result. It deliberately cannot hold raw transport or error text.", + "properties": { + "reason": { + "$ref": "#/components/schemas/StartupBoundaryReason" + } + }, + "required": [ + "reason" + ], + "type": "object" + }, + "StartupBoundaryReason": { + "enum": [ + "timeout", + "transport", + "decode", + "invalid_response", + "too_large", + "unsupported_version", + "unsupported_reason", + "finalize_failed" + ], + "type": "string" + }, + "StartupCleanup": { + "oneOf": [ + { + "enum": [ + "not_needed", + "required", + "confirmed" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "failed": { + "$ref": "#/components/schemas/StartupCleanupFailure" + } + }, + "required": [ + "failed" + ], + "type": "object" + } + ] + }, + "StartupCleanupFailure": { + "enum": [ + "ownership", + "workdir", + "artifacts", + "allocation", + "feature", + "unsupported" + ], + "type": "string" + }, + "StartupCorrelation": { + "description": "Opaque, caller-selected lookup token, not an operation identity or capability.\nNo whitespace, URI/path/query syntax or control characters are accepted.", + "maxLength": 128, + "minLength": 1, + "pattern": "^[A-Za-z0-9._-]+$", + "type": "string" + }, "StartupFailureAttachment": { "description": "A bounded, projected envelope at the Worker/Runtime or metadata boundary.\nNo constructor accepts Display text, arbitrary JSON fields, or an unprojected error.", "type": "string" }, + "StartupFailurePhase": { + "oneOf": [ + { + "enum": [ + "cleanup", + "runtime_record_delete", + "runtime_snapshot_save", + "runtime_removal_publish", + "metadata_save" + ], + "type": "string" + }, + { + "const": "runtime_reconciliation_record_save", + "description": "Retaining create reconciliation observations in the canonical Worker record.", + "type": "string" + }, + { + "const": "runtime_reconciliation_publish", + "description": "Publishing a retained record to Runtime subscribers; not a durable write.", + "type": "string" + } + ] + }, + "StartupIoKind": { + "description": "Path-free IO classification. Nonportable OS details remain at the resource owner.", + "enum": [ + "not_found", + "permission_denied", + "already_exists", + "invalid_input", + "invalid_data", + "interrupted", + "timed_out", + "would_block", + "unexpected_eof", + "other" + ], + "type": "string" + }, + "StartupOperationFacts": { + "additionalProperties": false, + "description": "Explicit owner observations. There is deliberately no Default and no conversion\nfrom HTTP status, retry hints, resource facts or failure reason into these fields.", + "properties": { + "acceptance": { + "$ref": "#/components/schemas/CreationAcceptance" + }, + "backend_compensation": { + "anyOf": [ + { + "$ref": "#/components/schemas/CompensationOutcome" + }, + { + "type": "null" + } + ], + "description": "Backend compensation can occur before/without finalization; never infer\nthis observation from Runtime cleanup, resources or finalize compensation." + }, + "correlation": { + "anyOf": [ + { + "$ref": "#/components/schemas/StartupCorrelation" + }, + { + "type": "null" + } + ] + }, + "execution": { + "$ref": "#/components/schemas/ExecutionOutcome" + }, + "finalize": { + "anyOf": [ + { + "$ref": "#/components/schemas/FinalizeFacts" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "acceptance", + "execution" + ], + "type": "object" + }, + "StartupPersistenceOutcome": { + "description": "Persisted is evidence only for this attachment/record write, not execution completion.", + "enum": [ + "persisted", + "not_saved", + "conflict", + "outcome_unknown" + ], + "type": "string" + }, + "StartupProblemDetails": { + "description": "Public generator/reader DTO. Deserialization does not grant disclosure authority.", + "properties": { + "detail": { + "maxLength": 2048, + "minLength": 1, + "type": "string" + }, + "instance": { + "maxLength": 2048, + "type": "string" + }, + "status": { + "format": "uint16", + "maximum": 599, + "minimum": 400, + "type": "integer" + }, + "title": { + "maxLength": 2048, + "minLength": 1, + "type": "string" + }, + "type": { + "maxLength": 2048, + "type": "string" + }, + "yoi_failure": { + "oneOf": [ + { + "description": "DTO/schema source only. Deserialization does not produce a ProjectedFailure.", + "properties": { + "category": { + "$ref": "#/components/schemas/Category" + }, + "context": { + "$ref": "#/components/schemas/FailureContext" + }, + "details": { + "$ref": "#/components/schemas/SessionRegistrationCause" + }, + "details_version": { + "format": "uint16", + "maximum": 65535, + "minimum": 1, + "type": "integer" + }, + "domain": { + "const": "session_registration", + "type": "string" + }, + "explanation": { + "maxLength": 2048, + "minLength": 1, + "type": "string" + }, + "reason": { + "maxLength": 128, + "minLength": 1, + "type": "string" + }, + "required_extensions": { + "items": { + "type": "string" + }, + "maxItems": 8, + "type": "array" + }, + "version": { + "format": "uint16", + "maximum": 65535, + "minimum": 1, + "type": "integer" + } + }, + "required": [ + "version", + "required_extensions", + "domain", + "reason", + "category", + "details_version", + "details", + "explanation", + "context" + ], + "type": "object" + }, + { + "description": "DTO/schema source only. Deserialization does not produce a ProjectedFailure.", + "properties": { + "category": { + "$ref": "#/components/schemas/Category" + }, + "context": { + "$ref": "#/components/schemas/FailureContext" + }, + "details": { + "$ref": "#/components/schemas/RuntimeStartupCause" + }, + "details_version": { + "format": "uint16", + "maximum": 65535, + "minimum": 1, + "type": "integer" + }, + "domain": { + "const": "worker_startup", + "type": "string" + }, + "explanation": { + "maxLength": 2048, + "minLength": 1, + "type": "string" + }, + "reason": { + "maxLength": 128, + "minLength": 1, + "type": "string" + }, + "required_extensions": { + "items": { + "type": "string" + }, + "maxItems": 8, + "type": "array" + }, + "version": { + "format": "uint16", + "maximum": 65535, + "minimum": 1, + "type": "integer" + } + }, + "required": [ + "version", + "required_extensions", + "domain", + "reason", + "category", + "details_version", + "details", + "explanation", + "context" + ], + "type": "object" + }, + { + "description": "DTO/schema source only. Deserialization does not produce a ProjectedFailure.", + "properties": { + "category": { + "$ref": "#/components/schemas/Category" + }, + "context": { + "$ref": "#/components/schemas/FailureContext" + }, + "details": { + "$ref": "#/components/schemas/StartupBoundaryCause" + }, + "details_version": { + "format": "uint16", + "maximum": 65535, + "minimum": 1, + "type": "integer" + }, + "domain": { + "const": "startup_boundary", + "type": "string" + }, + "explanation": { + "maxLength": 2048, + "minLength": 1, + "type": "string" + }, + "reason": { + "maxLength": 128, + "minLength": 1, + "type": "string" + }, + "required_extensions": { + "items": { + "type": "string" + }, + "maxItems": 8, + "type": "array" + }, + "version": { + "format": "uint16", + "maximum": 65535, + "minimum": 1, + "type": "integer" + } + }, + "required": [ + "version", + "required_extensions", + "domain", + "reason", + "category", + "details_version", + "details", + "explanation", + "context" + ], + "type": "object" + } + ] + }, + "yoi_startup": { + "$ref": "#/components/schemas/StartupProblemFacts" + } + }, + "required": [ + "type", + "title", + "status", + "detail", + "instance", + "yoi_failure", + "yoi_startup" + ], + "type": "object" + }, + "StartupProblemFacts": { + "additionalProperties": false, + "description": "The extension contains only typed allowlisted observations; never the attachment.", + "properties": { + "backend_result_persistence": { + "anyOf": [ + { + "$ref": "#/components/schemas/StartupPersistenceOutcome" + }, + { + "type": "null" + } + ], + "description": "Backend-owned result write only; never evidence of Session metadata persistence." + }, + "io_kind": { + "anyOf": [ + { + "$ref": "#/components/schemas/StartupIoKind" + }, + { + "type": "null" + } + ] + }, + "operation": { + "$ref": "#/components/schemas/StartupOperationFacts" + }, + "persistence": { + "anyOf": [ + { + "$ref": "#/components/schemas/StartupPersistenceOutcome" + }, + { + "type": "null" + } + ], + "description": "Session metadata persistence, independent of the Backend reservation result." + }, + "resources": { + "anyOf": [ + { + "$ref": "#/components/schemas/StartupResourceFacts" + }, + { + "type": "null" + } + ] + }, + "secondary": { + "items": { + "$ref": "#/components/schemas/StartupSecondaryFailure" + }, + "maxItems": 8, + "type": "array" + }, + "version": { + "format": "uint16", + "maximum": 1, + "minimum": 1, + "type": "integer" + } + }, + "required": [ + "version", + "operation", + "secondary" + ], + "type": "object" + }, + "StartupResourceFacts": { + "properties": { + "cleanup": { + "$ref": "#/components/schemas/StartupCleanup" + }, + "durable_effects_possible": { + "type": "boolean" + }, + "resources": { + "$ref": "#/components/schemas/StartupResources" + } + }, + "required": [ + "resources", + "cleanup", + "durable_effects_possible" + ], + "type": "object" + }, + "StartupResources": { + "description": "Independent resource observations from the existing execution owner. These diagnostics\nnever promote a result to RolledBack or authorize replay; the backend disposition does.", + "enum": [ + "none", + "worker_startup", + "unproven" + ], + "type": "string" + }, + "StartupSecondaryFailure": { + "description": "A separate secondary fact, not a replacement primary cause or a causal-chain link.", + "oneOf": [ + { + "properties": { + "kind": { + "const": "projection_unavailable", + "type": "string" + }, + "phase": { + "$ref": "#/components/schemas/StartupFailurePhase" + } + }, + "required": [ + "kind", + "phase" + ], + "type": "object" + }, + { + "properties": { + "failure": { + "$ref": "#/components/schemas/StartupCleanupFailure" + }, + "kind": { + "const": "cleanup", + "type": "string" + } + }, + "required": [ + "kind", + "failure" + ], + "type": "object" + }, + { + "properties": { + "kind": { + "const": "persistence", + "type": "string" + }, + "outcome": { + "$ref": "#/components/schemas/StartupPersistenceOutcome" + }, + "phase": { + "$ref": "#/components/schemas/StartupFailurePhase" + } + }, + "required": [ + "kind", + "phase", + "outcome" + ], + "type": "object" + } + ] + }, + "StartupStage": { + "description": "Owner-selected startup stage; independent of resource cleanup disposition.", + "enum": [ + "preflight", + "metadata", + "reconstruction", + "recovery", + "binding", + "feature", + "activation_effects", + "controller" + ], + "type": "string" + }, "SubjectBodyApiError": { "additionalProperties": false, "description": "Only action handlers with a causally bound request may provide resolution evidence.", @@ -20793,6 +21628,7 @@ }, "WorkerOperationState": { "enum": [ + "unknown", "accepted", "unsupported", "rejected" @@ -23363,7 +24199,7 @@ "info": { "title": "Yoi Server API", "version": "0.1.0", - "x-yoi-source-digest": "sha256:894ccff36a463bec91b635956924114aa0db87d8b3f815230d6e8395511d2f6e" + "x-yoi-source-digest": "sha256:2dad54b31293643566259ea555e11c46effce33aab9f30c8a861a46303024bad" }, "openapi": "3.1.0", "paths": { @@ -48730,6 +49566,11 @@ "schema": { "$ref": "#/components/schemas/RepositoryApiError" } + }, + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/StartupProblemDetails" + } } }, "description": "Error response" @@ -48740,6 +49581,11 @@ "schema": { "$ref": "#/components/schemas/RepositoryApiError" } + }, + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/StartupProblemDetails" + } } }, "description": "Error response" @@ -48750,6 +49596,11 @@ "schema": { "$ref": "#/components/schemas/RepositoryApiError" } + }, + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/StartupProblemDetails" + } } }, "description": "Error response" @@ -48760,6 +49611,11 @@ "schema": { "$ref": "#/components/schemas/RepositoryApiError" } + }, + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/StartupProblemDetails" + } } }, "description": "Error response" @@ -48770,6 +49626,11 @@ "schema": { "$ref": "#/components/schemas/RepositoryApiError" } + }, + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/StartupProblemDetails" + } } }, "description": "Error response" @@ -48780,6 +49641,11 @@ "schema": { "$ref": "#/components/schemas/RepositoryApiError" } + }, + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/StartupProblemDetails" + } } }, "description": "Error response" @@ -48790,6 +49656,11 @@ "schema": { "$ref": "#/components/schemas/RepositoryApiError" } + }, + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/StartupProblemDetails" + } } }, "description": "Error response" @@ -48800,6 +49671,26 @@ "schema": { "$ref": "#/components/schemas/RepositoryApiError" } + }, + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/StartupProblemDetails" + } + } + }, + "description": "Error response" + }, + "504": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + }, + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/StartupProblemDetails" + } } }, "description": "Error response" @@ -49947,6 +50838,11 @@ "schema": { "$ref": "#/components/schemas/RepositoryApiError" } + }, + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/StartupProblemDetails" + } } }, "description": "Error response" @@ -49957,6 +50853,11 @@ "schema": { "$ref": "#/components/schemas/RepositoryApiError" } + }, + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/StartupProblemDetails" + } } }, "description": "Error response" @@ -49967,6 +50868,11 @@ "schema": { "$ref": "#/components/schemas/RepositoryApiError" } + }, + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/StartupProblemDetails" + } } }, "description": "Error response" @@ -49977,6 +50883,11 @@ "schema": { "$ref": "#/components/schemas/RepositoryApiError" } + }, + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/StartupProblemDetails" + } } }, "description": "Error response" @@ -49987,6 +50898,11 @@ "schema": { "$ref": "#/components/schemas/RepositoryApiError" } + }, + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/StartupProblemDetails" + } } }, "description": "Error response" @@ -49997,6 +50913,11 @@ "schema": { "$ref": "#/components/schemas/RepositoryApiError" } + }, + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/StartupProblemDetails" + } } }, "description": "Error response" @@ -50007,6 +50928,11 @@ "schema": { "$ref": "#/components/schemas/RepositoryApiError" } + }, + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/StartupProblemDetails" + } } }, "description": "Error response" @@ -50017,6 +50943,26 @@ "schema": { "$ref": "#/components/schemas/RepositoryApiError" } + }, + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/StartupProblemDetails" + } + } + }, + "description": "Error response" + }, + "504": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryApiError" + } + }, + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/StartupProblemDetails" + } } }, "description": "Error response" diff --git a/web/workspace/src/lib/generated/companion-api.ts b/web/workspace/src/lib/generated/companion-api.ts index ed272d568..5864393a5 100644 --- a/web/workspace/src/lib/generated/companion-api.ts +++ b/web/workspace/src/lib/generated/companion-api.ts @@ -1,6 +1,6 @@ // @generated by yoi-companion-openapi-typescript@1. Do not edit. // Input: openapi/server-api.json -// Input contract: 0.1.0 (sha256:894ccff36a463bec91b635956924114aa0db87d8b3f815230d6e8395511d2f6e) +// Input contract: 0.1.0 (sha256:2dad54b31293643566259ea555e11c46effce33aab9f30c8a861a46303024bad) // Options: companion-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output // Output: web/workspace/src/lib/generated/companion-api.ts // Regenerate/check: cargo run -q -p server-api --example generate_companion_api_types -- --check diff --git a/web/workspace/src/lib/generated/repository-api.ts b/web/workspace/src/lib/generated/repository-api.ts index 1984f7db8..877fab10e 100644 --- a/web/workspace/src/lib/generated/repository-api.ts +++ b/web/workspace/src/lib/generated/repository-api.ts @@ -1,6 +1,6 @@ // @generated by yoi-repository-openapi-typescript@1. Do not edit. // Input: openapi/server-api.json -// Input contract: 0.1.0 (sha256:894ccff36a463bec91b635956924114aa0db87d8b3f815230d6e8395511d2f6e) +// Input contract: 0.1.0 (sha256:2dad54b31293643566259ea555e11c46effce33aab9f30c8a861a46303024bad) // 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 // Regenerate/check: cargo run -q -p server-api --example generate_repository_openapi_types -- --check diff --git a/web/workspace/src/lib/generated/runtime-api.ts b/web/workspace/src/lib/generated/runtime-api.ts index b2570df8c..79a2a31cf 100644 --- a/web/workspace/src/lib/generated/runtime-api.ts +++ b/web/workspace/src/lib/generated/runtime-api.ts @@ -1,6 +1,6 @@ // @generated by yoi-runtime-openapi-typescript@1. Do not edit. // Input: openapi/server-api.json -// Input contract: 0.1.0 (sha256:894ccff36a463bec91b635956924114aa0db87d8b3f815230d6e8395511d2f6e) +// Input contract: 0.1.0 (sha256:2dad54b31293643566259ea555e11c46effce33aab9f30c8a861a46303024bad) // Options: runtime-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output // Output: web/workspace/src/lib/generated/runtime-api.ts // Regenerate/check: cargo run -q -p server-api --example generate_runtime_api_types -- --check @@ -175,7 +175,7 @@ export type WorkerImplementationSummary = { display_hint: string; kind: string; export type WorkerMaintenanceState = "compacting"; -export type WorkerOperationState = "accepted" | "unsupported" | "rejected"; +export type WorkerOperationState = "unknown" | "accepted" | "unsupported" | "rejected"; export type WorkerRestoreRequest = { expected_observation_token: string; request_id: string; }; diff --git a/web/workspace/src/lib/generated/ticket-api.ts b/web/workspace/src/lib/generated/ticket-api.ts index b23760724..232e27bbf 100644 --- a/web/workspace/src/lib/generated/ticket-api.ts +++ b/web/workspace/src/lib/generated/ticket-api.ts @@ -1,6 +1,6 @@ // @generated by yoi-ticket-browser-openapi-typescript@1. Do not edit. // Input: openapi/server-api.json -// Input contract: 0.1.0 (sha256:894ccff36a463bec91b635956924114aa0db87d8b3f815230d6e8395511d2f6e) +// Input contract: 0.1.0 (sha256:2dad54b31293643566259ea555e11c46effce33aab9f30c8a861a46303024bad) // 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 // Regenerate/check: cargo run -q -p server-api --example generate_ticket_api_types -- --check diff --git a/web/workspace/src/lib/generated/workdir-api.ts b/web/workspace/src/lib/generated/workdir-api.ts index 986c6fe70..cc5043dd9 100644 --- a/web/workspace/src/lib/generated/workdir-api.ts +++ b/web/workspace/src/lib/generated/workdir-api.ts @@ -1,6 +1,6 @@ // @generated by yoi-workdir-openapi-typescript@1. Do not edit. // Input: openapi/server-api.json -// Input contract: 0.1.0 (sha256:894ccff36a463bec91b635956924114aa0db87d8b3f815230d6e8395511d2f6e) +// Input contract: 0.1.0 (sha256:2dad54b31293643566259ea555e11c46effce33aab9f30c8a861a46303024bad) // Options: workdir-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output // Output: web/workspace/src/lib/generated/workdir-api.ts // Regenerate/check: cargo run -q -p server-api --example generate_workdir_api_types -- --check diff --git a/web/workspace/src/lib/generated/worker-launch-api.ts b/web/workspace/src/lib/generated/worker-launch-api.ts index 9571bddb5..967af2325 100644 --- a/web/workspace/src/lib/generated/worker-launch-api.ts +++ b/web/workspace/src/lib/generated/worker-launch-api.ts @@ -1,6 +1,6 @@ // @generated by yoi-worker-openapi-typescript@1. Do not edit. // Input: openapi/server-api.json -// Input contract: 0.1.0 (sha256:894ccff36a463bec91b635956924114aa0db87d8b3f815230d6e8395511d2f6e) +// Input contract: 0.1.0 (sha256:2dad54b31293643566259ea555e11c46effce33aab9f30c8a861a46303024bad) // Options: worker-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output // 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 @@ -11,24 +11,46 @@ export type BrowserWorkerWorkingDirectorySelection = { alias: string; relative_c export type BrowserWorkspaceOrchestratorResponse = { diagnostics: Array; disposition: string; online: boolean; worker?: WorkerLaunchWorkerSummary | null; workspace_id: string; }; +export type Category = "authorization" | "invalid_input" | "conflict" | "dependency" | "internal" | "unknown"; + +export type CausalLink = { correlation: CorrelationRef; domain: string & { readonly "__yoiStringLength": "1..128"; }; reason: string & { readonly "__yoiStringLength": "1..128"; }; }; + +export type CompensationOutcome = "completed" | "residual" | "unknown"; + +export type CorrelationRef = string & { readonly "__yoiStringLength": "1..128"; }; + export type CreateWorkspaceWorkerRequest = { control_operation_id?: string | null; display_name: string; execution_grant?: WorkerDelegationRequest | null; feature_connections: WorkspaceWorkerFeatureConnectionsRequest; initial_submit?: Array; profile?: string | null; runtime_id: string; singleton_key?: string | null; ticket_assignment?: CreateWorkspaceWorkerTicketAssignmentRequest | null; workdir_attachments?: Array; }; export type CreateWorkspaceWorkerTicketAssignmentRequest = { operation_id: string; orchestrator_assignment_id?: string | null; ticket_id: string; }; +export type CreationAcceptance = "rejected" | "accepted" | "unknown"; + export type Diagnostic = { code: string; message: string; severity: DiagnosticSeverity; }; export type DiagnosticSeverity = "info" | "warning" | "error"; +export type ExecutionOutcome = "accepted" | "busy" | "rejected" | "errored" | "unsupported" | "unknown"; + export type ExternalWorkdirPermissions = { command: boolean; read: boolean; write: boolean; }; +export type FailureContext = { causes: Array & { readonly "__yoiArrayLength": "0..4"; }; correlation?: CorrelationRef | null; diagnostics: Array & { readonly "__yoiArrayLength": "0..8"; }; operations: Array & { readonly "__yoiArrayLength": "0..8"; }; supplementary_omitted: boolean; }; + +export type FailureDiagnostic = { code: string & { readonly "__yoiStringLength": "1..128"; }; explanation: string & { readonly "__yoiStringLength": "1..2048"; }; }; + export type FeatureInvocation = { arguments: Array; identity: FeatureInvocationIdentity; invocation_id: string; name: string; }; export type FeatureInvocationIdentity = string; +export type FinalizeFacts = { compensation: CompensationOutcome; stage: FinalizeStage; }; + +export type FinalizeStage = "worker_identity" | "workspace_api_binding" | "create_reservation" | "worker_registry" | "workdir_attachment" | "workdir_registry" | "ticket_assignment_bind" | "ticket_assignment_current" | "ticket_state_accept" | "session_registration" | "initial_input" | "publication"; + export type InvocationArgumentValue = { name: string; value: InvocationValue; }; export type InvocationValue = { kind: "string"; value: string; } | { kind: "integer"; value: number; } | { kind: "boolean"; value: boolean; }; +export type OperationContext = { operation: string & { readonly "__yoiStringLength": "1..128"; }; operation_ref?: string & { readonly "__yoiStringLength": "1..128"; } | null; stage: string & { readonly "__yoiStringLength": "1..128"; }; }; + export type PasteArtifactAvailability = "available" | "unavailable" | "integrity_failed"; export type PasteArtifactMediaType = "text_plain_utf8"; @@ -39,6 +61,10 @@ export type PermissionResource = { installation_id: string; kind: "plugin_instal export type RepositoryApiError = { diagnostics?: Array; error: string; failure?: StartupFailureAttachment | null; message: string; }; +export type RuntimeStartupCause = { reason: RuntimeStartupReason; stage: StartupStage; }; + +export type RuntimeStartupReason = "startup_failed" | "segment_empty" | "system_prompt_missing" | "metadata_pending" | "metadata_missing" | "metadata_inactive" | "manifest_missing" | "allocation_failed" | "session_read_failed" | "metadata_read_failed" | "provider_failed" | "attribution_failed" | "feature_failed"; + export type RuntimeWorkerWorkdirAttachmentSummary = { alias: string; working_directory: RuntimeWorkingDirectorySummary; }; export type RuntimeWorkingDirectoryCleanupTarget = { kind: string; repository_id: string; working_directory_id: string; }; @@ -49,8 +75,46 @@ export type ScopedPermission = { operation: string; resource: PermissionResource export type Segment = { content: string; kind: "text"; } | { chars: number; content: string; id: number; kind: "paste"; lines: number; } | { artifact: PasteArtifactRef; kind: "paste_artifact"; } | { file: UploadedFileRef; kind: "uploaded_file"; } | { kind: "file_ref"; path: string; } | { invocation: FeatureInvocation; kind: "feature_invoke"; } | { kind: "flow"; selector: string; } | { kind: "unknown"; }; +export type SessionOperation = "session.record"; + +export type SessionPrincipal = "operator" | "subject" | "unknown"; + +export type SessionRegistrationCause = { principal: SessionPrincipal; reason: SessionRegistrationReason; required_operation: SessionOperation; }; + +export type SessionRegistrationReason = "permission_denied" | "attribution_missing" | "binding_mismatch" | "transport" | "outcome_unknown" | "invalid_response"; + +export type StartupBoundaryCause = { reason: StartupBoundaryReason; }; + +export type StartupBoundaryReason = "timeout" | "transport" | "decode" | "invalid_response" | "too_large" | "unsupported_version" | "unsupported_reason" | "finalize_failed"; + +export type StartupCleanup = "not_needed" | "required" | "confirmed" | { failed: StartupCleanupFailure; }; + +export type StartupCleanupFailure = "ownership" | "workdir" | "artifacts" | "allocation" | "feature" | "unsupported"; + +export type StartupCorrelation = string & { readonly "__yoiStringLength": "1..128"; readonly "__yoiStringPattern": "^[A-Za-z0-9._-]+$"; }; + export type StartupFailureAttachment = string; +export type StartupFailurePhase = "cleanup" | "runtime_record_delete" | "runtime_snapshot_save" | "runtime_removal_publish" | "metadata_save" | "runtime_reconciliation_record_save" | "runtime_reconciliation_publish"; + +export type StartupIoKind = "not_found" | "permission_denied" | "already_exists" | "invalid_input" | "invalid_data" | "interrupted" | "timed_out" | "would_block" | "unexpected_eof" | "other"; + +export type StartupOperationFacts = { acceptance: CreationAcceptance; backend_compensation?: CompensationOutcome | null; correlation?: StartupCorrelation | null; execution: ExecutionOutcome; finalize?: FinalizeFacts | null; }; + +export type StartupPersistenceOutcome = "persisted" | "not_saved" | "conflict" | "outcome_unknown"; + +export type StartupProblemDetails = { detail: string & { readonly "__yoiStringLength": "1..2048"; }; instance: string & { readonly "__yoiStringLength": "0..2048"; }; status: number & { readonly "__yoiIntegerRange": "400..599"; }; title: string & { readonly "__yoiStringLength": "1..2048"; }; type: string & { readonly "__yoiStringLength": "0..2048"; }; yoi_failure: { category: Category; context: FailureContext; details: SessionRegistrationCause; details_version: number & { readonly "__yoiIntegerRange": "1..65535"; }; domain: "session_registration"; explanation: string & { readonly "__yoiStringLength": "1..2048"; }; reason: string & { readonly "__yoiStringLength": "1..128"; }; required_extensions: Array & { readonly "__yoiArrayLength": "0..8"; }; version: number & { readonly "__yoiIntegerRange": "1..65535"; }; } | { category: Category; context: FailureContext; details: RuntimeStartupCause; details_version: number & { readonly "__yoiIntegerRange": "1..65535"; }; domain: "worker_startup"; explanation: string & { readonly "__yoiStringLength": "1..2048"; }; reason: string & { readonly "__yoiStringLength": "1..128"; }; required_extensions: Array & { readonly "__yoiArrayLength": "0..8"; }; version: number & { readonly "__yoiIntegerRange": "1..65535"; }; } | { category: Category; context: FailureContext; details: StartupBoundaryCause; details_version: number & { readonly "__yoiIntegerRange": "1..65535"; }; domain: "startup_boundary"; explanation: string & { readonly "__yoiStringLength": "1..2048"; }; reason: string & { readonly "__yoiStringLength": "1..128"; }; required_extensions: Array & { readonly "__yoiArrayLength": "0..8"; }; version: number & { readonly "__yoiIntegerRange": "1..65535"; }; }; yoi_startup: StartupProblemFacts; }; + +export type StartupProblemFacts = { backend_result_persistence?: StartupPersistenceOutcome | null; io_kind?: StartupIoKind | null; operation: StartupOperationFacts; persistence?: StartupPersistenceOutcome | null; resources?: StartupResourceFacts | null; secondary: Array & { readonly "__yoiArrayLength": "0..8"; }; version: number & { readonly "__yoiIntegerRange": "1..1"; }; }; + +export type StartupResourceFacts = { cleanup: StartupCleanup; durable_effects_possible: boolean; resources: StartupResources; }; + +export type StartupResources = "none" | "worker_startup" | "unproven"; + +export type StartupSecondaryFailure = { kind: "projection_unavailable"; phase: StartupFailurePhase; } | { failure: StartupCleanupFailure; kind: "cleanup"; } | { kind: "persistence"; outcome: StartupPersistenceOutcome; phase: StartupFailurePhase; }; + +export type StartupStage = "preflight" | "metadata" | "reconstruction" | "recovery" | "binding" | "feature" | "activation_effects" | "controller"; + export type SubjektivWorkerConnectionRequest = { subject_id: string; }; export type SubscriptionWorkerAvailability = "observed" | "unavailable";