build(web): consume T812 public startup contracts and HTTP fixtures

This commit is contained in:
2026-11-06 11:06:10 +09:00
parent 838b6cd3c3
commit 590a270757
10 changed files with 1225 additions and 17 deletions
@@ -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}}
@@ -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}}
@@ -304,6 +304,7 @@ fn validate_worker_operations(
) -> Result<BTreeSet<String>, 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<BTreeSet<String>, 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<BTreeSet<String>, 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<BTreeSet<String>, 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<BTreeSet<String>, 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<String, GenerationError> {
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<String, GenerationError> {
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<String, Value>, context: &str) -> Result<String, GenerationError> {
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<string> & { 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();
+947 -1
View File
File diff suppressed because it is too large Load Diff
@@ -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
@@ -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
@@ -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; };
@@ -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
@@ -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
@@ -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<Diagnostic>; 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<Segment>; profile?: string | null; runtime_id: string; singleton_key?: string | null; ticket_assignment?: CreateWorkspaceWorkerTicketAssignmentRequest | null; workdir_attachments?: Array<BrowserWorkerWorkingDirectorySelection>; };
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<CausalLink> & { readonly "__yoiArrayLength": "0..4"; }; correlation?: CorrelationRef | null; diagnostics: Array<FailureDiagnostic> & { readonly "__yoiArrayLength": "0..8"; }; operations: Array<OperationContext> & { 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<InvocationArgumentValue>; 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<Diagnostic>; 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<string> & { 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<string> & { 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<string> & { 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<StartupSecondaryFailure> & { 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";